1 /*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21 /*
22 * Copyright 2008 Sun Microsystems, Inc. All rights reserved.
23 * Use is subject to license terms.
24 */
25
26 /*
27 * This module adds support to the RCM framework for mounted filesystems.
28 *
29 * The module provides this functionality:
30 * 1) reports device usage for mounted filesystems
31 * 2) prevents offline operations for mounted resources
32 * 3) prevents suspend operations (unless forced) of those filesystems
33 * deemed critical for the continued operation of the OS
34 * 4) propagates RCM operations from mounted resources to the consumers
35 * of files within the mounted filesystems
36 */
37
38 #include <stdio.h>
39 #include <assert.h>
40 #include <string.h>
41 #include <synch.h>
42 #include <libintl.h>
43 #include <errno.h>
44 #include <sys/mnttab.h>
45 #include <sys/param.h>
46 #include <sys/stat.h>
47 #include <sys/utssys.h>
48 #include <unistd.h>
49 #include <limits.h>
50
51 #include "rcm_module.h"
52
53 /* Definitions */
54
55 #define HASH_DEFAULT 4
56 #define HASH_THRESHOLD 256
57
58 #define OPT_IGNORE "ignore"
59
60 #define MSG_HDR_STD gettext("mounted filesystem")
61 #define MSG_HDR_STD_MULTI gettext("mounted filesystems")
62 #define MSG_HDR_CRIT gettext("cannot suspend filesystem")
63 #define MSG_HDR_CRIT_MULTI gettext("cannot suspend filesystems")
64 #define MSG_SEPARATOR gettext(", ")
65 #define MSG_FAIL_USAGE gettext("failed to construct usage string.")
66 #define MSG_FAIL_DEPENDENTS gettext("failed while calling dependents.")
67 #define MSG_FAIL_REMOVE gettext("filesystems cannot be removed.")
68 #define MSG_FAIL_INTERNAL gettext("internal processing failure.")
69
70 typedef struct hashentry {
71 int n_mounts;
72 char *special;
73 char *fstype;
74 char **mountps;
75 struct hashentry *next;
76 } hashentry_t;
77
78 typedef struct {
79 time_t timestamp;
80 uint32_t hash_size;
81 hashentry_t **mounts;
82 } cache_t;
83
84 /* Forward Declarations */
85
86 /* module interface routines */
87 static int mnt_register(rcm_handle_t *);
88 static int mnt_unregister(rcm_handle_t *);
89 static int mnt_getinfo(rcm_handle_t *, char *, id_t, uint_t, char **, char **,
90 nvlist_t *, rcm_info_t **);
91 static int mnt_suspend(rcm_handle_t *, char *, id_t, timespec_t *,
92 uint_t, char **, rcm_info_t **);
93 static int mnt_resume(rcm_handle_t *, char *, id_t, uint_t, char **,
94 rcm_info_t **);
95 static int mnt_offline(rcm_handle_t *, char *, id_t, uint_t, char **,
96 rcm_info_t **);
97 static int mnt_online(rcm_handle_t *, char *, id_t, uint_t, char **,
98 rcm_info_t **);
99 static int mnt_remove(rcm_handle_t *, char *, id_t, uint_t, char **,
100 rcm_info_t **);
101
102 /* cache functions */
103 static cache_t *cache_create();
104 static int cache_insert(cache_t *, struct mnttab *);
105 static int cache_sync(rcm_handle_t *, cache_t **);
106 static hashentry_t *cache_lookup(cache_t *, char *);
107 static void free_cache(cache_t **);
108 static void free_entry(hashentry_t **);
109 static void free_list(char **);
110
111 /* miscellaneous functions */
112 static uint32_t hash(uint32_t, char *);
113 static void register_rsrc(rcm_handle_t *, char *);
114 static void unregister_rsrc(rcm_handle_t *, char *);
115 static char *create_message(char *, char *, char **);
116 static int detect_critical_failure(char **, uint_t, char **);
117 static int is_critical(char *);
118 static int use_cache(char *, char **, char ***);
119 static void prune_dependents(char **, char *);
120 static char **create_dependents(hashentry_t *);
121
122 /* Module-Private data */
123
124 static struct rcm_mod_ops mnt_ops =
125 {
126 RCM_MOD_OPS_VERSION,
127 mnt_register,
128 mnt_unregister,
129 mnt_getinfo,
130 mnt_suspend,
131 mnt_resume,
132 mnt_offline,
133 mnt_online,
134 mnt_remove
135 };
136
137 static cache_t *mnt_cache;
138 static mutex_t cache_lock;
139
140 /* Module Interface Routines */
141
142 /*
143 * rcm_mod_init()
144 *
145 * Called when module is loaded. Returns the ops vector.
146 */
147 struct rcm_mod_ops *
rcm_mod_init()148 rcm_mod_init()
149 {
150 return (&mnt_ops);
151 }
152
153 /*
154 * rcm_mod_info()
155 *
156 * Returns a string identifying this module.
157 */
158 const char *
rcm_mod_info()159 rcm_mod_info()
160 {
161 return ("File system module 1.9");
162 }
163
164 /*
165 * rcm_mod_fini()
166 *
167 * Called when module is unloaded. Frees up all used memory.
168 *
169 * Locking: the cache is locked for the duration of this function.
170 */
171 int
rcm_mod_fini()172 rcm_mod_fini()
173 {
174 (void) mutex_lock(&cache_lock);
175 free_cache(&mnt_cache);
176 (void) mutex_unlock(&cache_lock);
177
178 return (RCM_SUCCESS);
179 }
180
181 /*
182 * mnt_register()
183 *
184 * Called to synchronize the module's registrations. Results in the
185 * construction of a new cache, destruction of any old cache data,
186 * and a full synchronization of the module's registrations.
187 *
188 * Locking: the cache is locked for the duration of this function.
189 */
190 int
mnt_register(rcm_handle_t * hd)191 mnt_register(rcm_handle_t *hd)
192 {
193 assert(hd != NULL);
194
195 rcm_log_message(RCM_TRACE1, "FILESYS: register()\n");
196
197 (void) mutex_lock(&cache_lock);
198
199 /* cache_sync() does all of the necessary work */
200 if (cache_sync(hd, &mnt_cache) < 0) {
201 rcm_log_message(RCM_ERROR,
202 "FILESYS: failed to synchronize cache (%s).\n",
203 strerror(errno));
204 (void) mutex_unlock(&cache_lock);
205 return (RCM_FAILURE);
206 }
207
208 (void) mutex_unlock(&cache_lock);
209
210 return (RCM_SUCCESS);
211 }
212
213 /*
214 * mnt_unregister()
215 *
216 * Manually walk through the cache, unregistering all the special
217 * files and mount points.
218 *
219 * Locking: the cache is locked throughout the execution of this
220 * routine because it reads and modifies cache links continuously.
221 */
222 int
mnt_unregister(rcm_handle_t * hd)223 mnt_unregister(rcm_handle_t *hd)
224 {
225 uint32_t index;
226 hashentry_t *entry;
227
228 assert(hd != NULL);
229
230 rcm_log_message(RCM_TRACE1, "FILESYS: unregister()\n");
231
232 (void) mutex_lock(&cache_lock);
233
234 /* Unregister everything in the cache */
235 if (mnt_cache) {
236 for (index = 0; index < mnt_cache->hash_size; index++) {
237 for (entry = mnt_cache->mounts[index]; entry != NULL;
238 entry = entry->next) {
239 unregister_rsrc(hd, entry->special);
240 }
241 }
242 }
243
244 /* Destroy the cache */
245 free_cache(&mnt_cache);
246
247 (void) mutex_unlock(&cache_lock);
248
249 return (RCM_SUCCESS);
250 }
251
252 /*
253 * mnt_offline()
254 *
255 * Filesystem resources cannot be offlined. They can however be retired
256 * if they don't provide a critical service. The offline entry point
257 * checks if this is a retire operation and if it is and the filesystem
258 * doesn't provide a critical service, the entry point returns success
259 * For all other cases, failure is returned.
260 * Since no real action is taken, QUERY or not doesn't matter.
261 */
262 int
mnt_offline(rcm_handle_t * hd,char * rsrc,id_t id,uint_t flags,char ** errorp,rcm_info_t ** dependent_info)263 mnt_offline(rcm_handle_t *hd, char *rsrc, id_t id, uint_t flags,
264 char **errorp, rcm_info_t **dependent_info)
265 {
266 char **dependents;
267 hashentry_t *entry;
268 int retval;
269 int i;
270
271 assert(hd != NULL);
272 assert(rsrc != NULL);
273 assert(id == (id_t)0);
274 assert(errorp != NULL);
275
276 *errorp = NULL;
277
278 rcm_log_message(RCM_TRACE1, "FILESYS: offline(%s)\n", rsrc);
279
280 /* Retrieve necessary info from the cache */
281 if (use_cache(rsrc, errorp, &dependents) < 0) {
282 if (flags & RCM_RETIRE_REQUEST)
283 return (RCM_NO_CONSTRAINT);
284 else
285 return (RCM_FAILURE);
286 }
287
288 if (flags & RCM_RETIRE_REQUEST) {
289 (void) mutex_lock(&cache_lock);
290 if ((entry = cache_lookup(mnt_cache, rsrc)) == NULL) {
291 rcm_log_message(RCM_ERROR, "FILESYS: "
292 "failed to look up \"%s\" in cache (%s).\n",
293 rsrc, strerror(errno));
294 (void) mutex_unlock(&cache_lock);
295 retval = RCM_NO_CONSTRAINT;
296 goto out;
297 }
298
299 if (strcmp(entry->fstype, "zfs") == 0) {
300 retval = RCM_NO_CONSTRAINT;
301 rcm_log_message(RCM_TRACE1,
302 "FILESYS: zfs: NO_CONSTRAINT: %s\n", rsrc);
303 } else {
304 retval = RCM_SUCCESS;
305 for (i = 0; dependents[i] != NULL; i++) {
306 if (is_critical(dependents[i])) {
307 retval = RCM_FAILURE;
308 rcm_log_message(RCM_TRACE1, "FILESYS: "
309 "CRITICAL %s\n", rsrc);
310 break;
311 }
312 }
313 }
314 (void) mutex_unlock(&cache_lock);
315 goto out;
316 }
317
318 retval = RCM_FAILURE;
319
320 /* Convert the gathered dependents into an error message */
321 *errorp = create_message(MSG_HDR_STD, MSG_HDR_STD_MULTI, dependents);
322 if (*errorp == NULL) {
323 rcm_log_message(RCM_ERROR,
324 "FILESYS: failed to construct offline message (%s).\n",
325 strerror(errno));
326 }
327
328 out:
329 free_list(dependents);
330 return (retval);
331 }
332
333 /*
334 * mnt_online()
335 *
336 * Filesystem resources aren't offlined, so there's really nothing to do
337 * here.
338 */
339 int
mnt_online(rcm_handle_t * hd,char * rsrc,id_t id,uint_t flag,char ** errorp,rcm_info_t ** dependent_reason)340 mnt_online(rcm_handle_t *hd, char *rsrc, id_t id, uint_t flag, char **errorp,
341 rcm_info_t **dependent_reason)
342 {
343 assert(hd != NULL);
344 assert(rsrc != NULL);
345 assert(id == (id_t)0);
346 assert(errorp != NULL);
347
348 rcm_log_message(RCM_TRACE1, "FILESYS: online(%s)\n", rsrc);
349
350 return (RCM_SUCCESS);
351 }
352
353 /*
354 * mnt_getinfo()
355 *
356 * Report how a given resource is in use by this module. And also
357 * possibly include dependent consumers of the mounted filesystems.
358 */
359 int
mnt_getinfo(rcm_handle_t * hd,char * rsrc,id_t id,uint_t flag,char ** usagep,char ** errorp,nvlist_t * props,rcm_info_t ** depend_info)360 mnt_getinfo(rcm_handle_t *hd, char *rsrc, id_t id, uint_t flag, char **usagep,
361 char **errorp, nvlist_t *props, rcm_info_t **depend_info)
362 {
363 int rv = RCM_SUCCESS;
364 char **dependents;
365
366 assert(hd != NULL);
367 assert(rsrc != NULL);
368 assert(id == (id_t)0);
369 assert(usagep != NULL);
370 assert(errorp != NULL);
371 assert(props != NULL);
372
373 rcm_log_message(RCM_TRACE1, "FILESYS: getinfo(%s)\n", rsrc);
374
375 /* Retrieve necessary info from the cache */
376 if (use_cache(rsrc, errorp, &dependents) < 0)
377 return (RCM_FAILURE);
378
379 /* Convert the gathered dependents into a usage message */
380 *usagep = create_message(MSG_HDR_STD, MSG_HDR_STD_MULTI, dependents);
381 if (*usagep == NULL) {
382 rcm_log_message(RCM_ERROR,
383 "FILESYS: failed to construct usage message (%s).\n",
384 strerror(errno));
385 *errorp = strdup(MSG_FAIL_USAGE);
386 free_list(dependents);
387 return (RCM_FAILURE);
388 }
389
390 /* Recurse on dependents if necessary */
391 if ((flag & RCM_INCLUDE_DEPENDENT) && (dependents != NULL)) {
392 prune_dependents(dependents, rsrc);
393 if (dependents[0] != NULL) {
394 if ((rv = rcm_get_info_list(hd, dependents, flag,
395 depend_info)) != RCM_SUCCESS) {
396 *errorp = strdup(MSG_FAIL_DEPENDENTS);
397 }
398 }
399 }
400
401 /* Free up info retrieved from the cache */
402 free_list(dependents);
403
404 return (rv);
405 }
406
407 /*
408 * mnt_suspend()
409 *
410 * Notify all dependents that the resource is being suspended.
411 * Since no real action is taken, QUERY or not doesn't matter.
412 */
413 int
mnt_suspend(rcm_handle_t * hd,char * rsrc,id_t id,timespec_t * interval,uint_t flag,char ** errorp,rcm_info_t ** depend_info)414 mnt_suspend(rcm_handle_t *hd, char *rsrc, id_t id, timespec_t *interval,
415 uint_t flag, char **errorp, rcm_info_t **depend_info)
416 {
417 int rv = RCM_SUCCESS;
418 char **dependents;
419
420 assert(hd != NULL);
421 assert(rsrc != NULL);
422 assert(id == (id_t)0);
423 assert(interval != NULL);
424 assert(errorp != NULL);
425
426 rcm_log_message(RCM_TRACE1, "FILESYS: suspend(%s)\n", rsrc);
427
428 /* Retrieve necessary info from the cache */
429 if (use_cache(rsrc, errorp, &dependents) < 0)
430 return (RCM_FAILURE);
431
432 /* Unforced suspensions fail if any of the dependents are critical */
433 if (detect_critical_failure(errorp, flag, dependents)) {
434 free_list(dependents);
435 return (RCM_FAILURE);
436 }
437
438 /* Recurse on dependents if necessary */
439 if ((flag & RCM_INCLUDE_DEPENDENT) && (dependents != NULL)) {
440 prune_dependents(dependents, rsrc);
441 if (dependents[0] != NULL)
442 if ((rv = rcm_request_suspend_list(hd, dependents, flag,
443 interval, depend_info)) != RCM_SUCCESS) {
444 *errorp = strdup(MSG_FAIL_DEPENDENTS);
445 }
446 }
447 free_list(dependents);
448
449 return (rv);
450 }
451
452 /*
453 * mnt_resume()
454 *
455 * Resume all the dependents of a suspended filesystem.
456 */
457 int
mnt_resume(rcm_handle_t * hd,char * rsrc,id_t id,uint_t flag,char ** errorp,rcm_info_t ** depend_info)458 mnt_resume(rcm_handle_t *hd, char *rsrc, id_t id, uint_t flag, char **errorp,
459 rcm_info_t **depend_info)
460 {
461 int rv = RCM_SUCCESS;
462 char **dependents;
463
464 assert(hd != NULL);
465 assert(rsrc != NULL);
466 assert(id == (id_t)0);
467 assert(errorp != NULL);
468
469 rcm_log_message(RCM_TRACE1, "FILESYS: resume(%s)\n", rsrc);
470
471 /* Retrieve necessary info from the cache */
472 if (use_cache(rsrc, errorp, &dependents) < 0)
473 return (RCM_FAILURE);
474
475 /* Recurse on dependents if necessary */
476 if ((flag & RCM_INCLUDE_DEPENDENT) && (dependents != NULL)) {
477 prune_dependents(dependents, rsrc);
478 if (dependents[0] != NULL) {
479 if ((rv = rcm_notify_resume_list(hd, dependents, flag,
480 depend_info)) != RCM_SUCCESS) {
481 *errorp = strdup(MSG_FAIL_DEPENDENTS);
482 }
483 }
484 }
485 free_list(dependents);
486
487 return (rv);
488 }
489
490 static int
get_spec(char * line,char * spec,size_t ssz)491 get_spec(char *line, char *spec, size_t ssz)
492 {
493 char *cp;
494 char *start;
495
496 if (strlcpy(spec, line, ssz) >= ssz) {
497 rcm_log_message(RCM_ERROR, "FILESYS: get_spec() failed: "
498 "line: %s\n", line);
499 return (-1);
500 }
501
502 cp = spec;
503 while (*cp == ' ' || *cp == '\t')
504 cp++;
505
506 if (*cp == '#')
507 return (-1);
508
509 start = cp;
510
511 while (*cp != ' ' && *cp != '\t' && *cp != '\0')
512 cp++;
513 *cp = '\0';
514
515 (void) memmove(spec, start, strlen(start) + 1);
516
517 return (0);
518 }
519
520 static int
path_match(char * rsrc,char * spec)521 path_match(char *rsrc, char *spec)
522 {
523 char r[PATH_MAX];
524 char s[PATH_MAX];
525 size_t len;
526
527 if (realpath(rsrc, r) == NULL)
528 goto error;
529
530 if (realpath(spec, s) == NULL)
531 goto error;
532
533 len = strlen("/devices/");
534
535 if (strncmp(r, "/devices/", len) != 0) {
536 errno = ENXIO;
537 goto error;
538 }
539
540 if (strncmp(s, "/devices/", len) != 0) {
541 errno = ENXIO;
542 goto error;
543 }
544
545 len = strlen(r);
546 if (strncmp(r, s, len) == 0 && (s[len] == '\0' || s[len] == ':'))
547 return (0);
548 else
549 return (1);
550
551 error:
552 rcm_log_message(RCM_DEBUG, "FILESYS: path_match() failed "
553 "rsrc=%s spec=%s: %s\n", rsrc, spec, strerror(errno));
554 return (-1);
555 }
556
557 #define VFSTAB "/etc/vfstab"
558 #define RETIRED_PREFIX "## RETIRED ##"
559
560 static int
disable_vfstab_entry(char * rsrc)561 disable_vfstab_entry(char *rsrc)
562 {
563 FILE *vfp;
564 FILE *tfp;
565 int retval;
566 int update;
567 char tmp[PATH_MAX];
568 char line[MNT_LINE_MAX + 1];
569
570 vfp = fopen(VFSTAB, "r");
571 if (vfp == NULL) {
572 rcm_log_message(RCM_ERROR, "FILESYS: failed to open /etc/vfstab"
573 " for reading: %s\n", strerror(errno));
574 return (RCM_FAILURE);
575 }
576
577 (void) snprintf(tmp, sizeof (tmp), "/etc/vfstab.retire.%lu", getpid());
578
579 tfp = fopen(tmp, "w");
580 if (tfp == NULL) {
581 rcm_log_message(RCM_ERROR, "FILESYS: failed to open "
582 "/etc/vfstab.retire for writing: %s\n", strerror(errno));
583 (void) fclose(vfp);
584 return (RCM_FAILURE);
585 }
586
587 retval = RCM_SUCCESS;
588 update = 0;
589 while (fgets(line, sizeof (line), vfp)) {
590
591 char spec[MNT_LINE_MAX + 1];
592 char newline[MNT_LINE_MAX + 1];
593 char *l;
594
595 if (get_spec(line, spec, sizeof (spec)) == -1) {
596 l = line;
597 goto foot;
598 }
599
600 if (path_match(rsrc, spec) != 0) {
601 l = line;
602 goto foot;
603 }
604
605 update = 1;
606
607 /* Paths match. Disable this entry */
608 (void) snprintf(newline, sizeof (newline), "%s %s",
609 RETIRED_PREFIX, line);
610
611 rcm_log_message(RCM_TRACE1, "FILESYS: disabling line\n\t%s\n",
612 line);
613
614 l = newline;
615 foot:
616 if (fputs(l, tfp) == EOF) {
617 rcm_log_message(RCM_ERROR, "FILESYS: failed to write "
618 "new vfstab: %s\n", strerror(errno));
619 update = 0;
620 retval = RCM_FAILURE;
621 break;
622 }
623 }
624
625 if (vfp)
626 (void) fclose(vfp);
627 if (tfp)
628 (void) fclose(tfp);
629
630 if (update) {
631 if (rename(tmp, VFSTAB) != 0) {
632 rcm_log_message(RCM_ERROR, "FILESYS: vfstab rename "
633 "failed: %s\n", strerror(errno));
634 retval = RCM_FAILURE;
635 }
636 }
637
638 (void) unlink(tmp);
639
640 return (retval);
641 }
642
643 /*
644 * mnt_remove()
645 *
646 * Remove will only be called in the retire case i.e. if RCM_RETIRE_NOTIFY
647 * flag is set.
648 *
649 * If the flag is not set, then return failure and log the mistake if a
650 * remove is ever received for a mounted filesystem resource.
651 */
652 int
mnt_remove(rcm_handle_t * hd,char * rsrc,id_t id,uint_t flag,char ** errorp,rcm_info_t ** depend_info)653 mnt_remove(rcm_handle_t *hd, char *rsrc, id_t id, uint_t flag, char **errorp,
654 rcm_info_t **depend_info)
655 {
656 assert(hd != NULL);
657 assert(rsrc != NULL);
658 assert(id == (id_t)0);
659 assert(errorp != NULL);
660
661 rcm_log_message(RCM_TRACE1, "FILESYS: remove(%s)\n", rsrc);
662
663 if (!(flag & RCM_RETIRE_NOTIFY)) {
664 /* Log the mistake */
665 rcm_log_message(RCM_ERROR, "FILESYS: invalid remove of "
666 "\"%s\"\n", rsrc);
667 *errorp = strdup(MSG_FAIL_REMOVE);
668 return (RCM_FAILURE);
669 }
670
671 return (disable_vfstab_entry(rsrc));
672 }
673
674 /*
675 * Cache management routines
676 */
677
678 /*
679 * cache_create()
680 *
681 * This routine constructs a new cache of the current mnttab file.
682 *
683 * Locking: the cache must be locked prior to calling this function.
684 *
685 * Return Values: NULL with errno set on failure, new cache point on
686 * success.
687 */
688 static cache_t *
cache_create()689 cache_create()
690 {
691 FILE *fp;
692 cache_t *cache;
693 int i;
694 uint32_t size;
695 struct stat st;
696 struct mnttab mt;
697
698 /*
699 * To keep the hash table relatively sparse, default values are
700 * used for smaller mnttab files and these values are scaled up
701 * as a fraction of the total mnttab file size for larger ones.
702 */
703 if (stat(MNTTAB, &st) < 0) {
704 rcm_log_message(RCM_ERROR,
705 "FILESYS: failed to stat \"%s\" (%s).\n", MNTTAB,
706 strerror(errno));
707 errno = EBADF;
708 return (NULL);
709 }
710 if (st.st_size > HASH_THRESHOLD) {
711 size = st.st_size / HASH_THRESHOLD;
712 for (i = 0; size > 1; i++, size >>= 1);
713 for (; i > -1; i--, size <<= 1);
714 } else {
715 size = HASH_DEFAULT;
716 }
717
718 /* Allocate a new empty cache */
719 if ((cache = (cache_t *)calloc(1, sizeof (cache_t))) == NULL) {
720 rcm_log_message(RCM_ERROR,
721 "FILESYS: failed to allocate cache (%s).\n",
722 strerror(errno));
723 errno = ENOMEM;
724 return (NULL);
725 }
726 cache->hash_size = size;
727 cache->timestamp = st.st_mtime;
728
729 /* Allocate an empty hash table for the registered special devices */
730 cache->mounts = (hashentry_t **)calloc(size, sizeof (hashentry_t *));
731 if (cache->mounts == NULL) {
732 rcm_log_message(RCM_ERROR,
733 "FILESYS: failed to allocate mount table (%s).\n",
734 strerror(errno));
735 free_cache(&cache);
736 errno = ENOMEM;
737 return (NULL);
738 }
739
740 /* Open the mnttab file */
741 if ((fp = fopen(MNTTAB, "r")) == NULL) {
742 rcm_log_message(RCM_ERROR,
743 "FILESYS: failed to open \"%s\" (%s).\n", MNTTAB,
744 strerror(errno));
745 free_cache(&cache);
746 errno = EIO;
747 return (NULL);
748 }
749
750 /* Insert each mnttab entry into the cache */
751 while (getmntent(fp, &mt) == 0) {
752
753 /* Well, not each entry... some are meant to be ignored */
754 if ((mt.mnt_mntopts != NULL) &&
755 (hasmntopt(&mt, OPT_IGNORE) != NULL))
756 continue;
757
758 if (cache_insert(cache, &mt) < 0) {
759 rcm_log_message(RCM_ERROR,
760 "FILESYS: cache insertion failure (%s).\n",
761 strerror(errno));
762 free_cache(&cache);
763 (void) fclose(fp);
764 errno = EFAULT;
765 return (NULL);
766 }
767 }
768
769 /* Close the mnttab file */
770 (void) fclose(fp);
771
772 return (cache);
773 }
774
775 /*
776 * free_cache()
777 *
778 * Free up all the memory associated with a cache.
779 *
780 * Locking: the cache must be locked before calling this function.
781 */
782 static void
free_cache(cache_t ** cachep)783 free_cache(cache_t **cachep)
784 {
785 uint32_t index;
786 hashentry_t *entry;
787 hashentry_t *entry_tmp;
788
789 /* Do nothing with empty caches */
790 if ((cachep == NULL) || (*cachep == NULL))
791 return;
792
793 if ((*cachep)->mounts) {
794 /* Walk through the hashtable, emptying it */
795 for (index = 0; index < (*cachep)->hash_size; index++) {
796 entry = (*cachep)->mounts[index];
797 while (entry) {
798 entry_tmp = entry->next;
799 free_entry(&entry);
800 entry = entry_tmp;
801 }
802 }
803 free((*cachep)->mounts);
804 }
805
806 free(*cachep);
807 *cachep = NULL;
808 }
809
810 /*
811 * free_entry()
812 *
813 * Free up memory associated with a hashtable entry.
814 *
815 * Locking: the cache must be locked before calling this function.
816 */
817 static void
free_entry(hashentry_t ** entryp)818 free_entry(hashentry_t **entryp)
819 {
820 if (entryp) {
821 if (*entryp) {
822 if ((*entryp)->special)
823 free((*entryp)->special);
824 if ((*entryp)->fstype)
825 free((*entryp)->fstype);
826 free_list((*entryp)->mountps);
827 free(*entryp);
828 }
829 *entryp = NULL;
830 }
831 }
832
833 /*
834 * free_list()
835 *
836 * Free up memory associated with a null terminated list of names.
837 */
838 static void
free_list(char ** list)839 free_list(char **list)
840 {
841 int i;
842
843 if (list) {
844 for (i = 0; list[i] != NULL; i++)
845 free(list[i]);
846 free(list);
847 }
848 }
849
850 /*
851 * cache_sync()
852 *
853 * Resynchronize the mnttab cache with the mnttab file.
854 *
855 * Locking: the cache must be locked before calling this function.
856 *
857 * Return Values: -1 with errno set on failure, 0 on success.
858 */
859 static int
cache_sync(rcm_handle_t * hd,cache_t ** cachep)860 cache_sync(rcm_handle_t *hd, cache_t **cachep)
861 {
862 uint32_t index;
863 cache_t *new_cache;
864 cache_t *old_cache;
865 hashentry_t *entry;
866 struct stat st;
867
868 /* Only accept valid arguments */
869 if ((hd == NULL) || (cachep == NULL)) {
870 rcm_log_message(RCM_ERROR,
871 "FILESYS: invalid arguments to cache_sync().\n");
872 errno = EINVAL;
873 return (-1);
874 }
875
876 /* Do nothing if there's already an up-to-date cache */
877 old_cache = *cachep;
878 if (old_cache) {
879 if (stat(MNTTAB, &st) == 0) {
880 if (old_cache->timestamp >= st.st_mtime) {
881 return (0);
882 }
883 } else {
884 rcm_log_message(RCM_WARNING,
885 "FILESYS: failed to stat \"%s\", cache is stale "
886 "(%s).\n", MNTTAB, strerror(errno));
887 errno = EIO;
888 return (-1);
889 }
890 }
891
892 /* Create a new cache based on the new mnttab file. */
893 if ((new_cache = cache_create()) == NULL) {
894 rcm_log_message(RCM_WARNING,
895 "FILESYS: failed creating cache, cache is stale (%s).\n",
896 strerror(errno));
897 errno = EIO;
898 return (-1);
899 }
900
901 /* Register any specials found in the new cache but not the old one */
902 for (index = 0; index < new_cache->hash_size; index++) {
903 for (entry = new_cache->mounts[index]; entry != NULL;
904 entry = entry->next) {
905 if (cache_lookup(old_cache, entry->special) == NULL) {
906 register_rsrc(hd, entry->special);
907 }
908 }
909 }
910
911 /* Pass the new cache pointer to the calling function */
912 *cachep = new_cache;
913
914 /* If there wasn't an old cache, return successfully now */
915 if (old_cache == NULL)
916 return (0);
917
918 /*
919 * If there was an old cache, then unregister whatever specials it
920 * contains that aren't in the new cache. And then destroy the old
921 * cache.
922 */
923 for (index = 0; index < old_cache->hash_size; index++) {
924 for (entry = old_cache->mounts[index]; entry != NULL;
925 entry = entry->next) {
926 if (cache_lookup(new_cache, entry->special) == NULL) {
927 unregister_rsrc(hd, entry->special);
928 }
929 }
930 }
931 free_cache(&old_cache);
932
933 return (0);
934 }
935
936 /*
937 * cache_insert()
938 *
939 * Given a cache and a mnttab entry, this routine inserts that entry in
940 * the cache. The mnttab entry's special device and filesystem type
941 * is added to the 'mounts' hashtable of the cache, and the entry's
942 * mountp value is added to the list of associated mountpoints for the
943 * corresponding hashtable entry.
944 *
945 * Locking: the cache must be locked before calling this function.
946 *
947 * Return Values: -1 with errno set on failure, 0 on success.
948 */
949 static int
cache_insert(cache_t * cache,struct mnttab * mt)950 cache_insert(cache_t *cache, struct mnttab *mt)
951 {
952 uint32_t index;
953 hashentry_t *entry;
954 char **mountps;
955
956 /* Only accept valid arguments */
957 if ((cache == NULL) ||
958 (cache->mounts == NULL) ||
959 (mt == NULL) ||
960 (mt->mnt_special == NULL) ||
961 (mt->mnt_mountp == NULL) ||
962 (mt->mnt_fstype == NULL)) {
963 errno = EINVAL;
964 return (-1);
965 }
966
967 /*
968 * Disregard any non-loopback mounts whose special device names
969 * don't begin with "/dev".
970 */
971 if ((strncmp(mt->mnt_special, "/dev", strlen("/dev")) != 0) &&
972 (strcmp(mt->mnt_fstype, "lofs") != 0))
973 return (0);
974
975 /*
976 * Find the special device's entry in the mounts hashtable, allocating
977 * a new entry if necessary.
978 */
979 index = hash(cache->hash_size, mt->mnt_special);
980 for (entry = cache->mounts[index]; entry != NULL; entry = entry->next) {
981 if (strcmp(entry->special, mt->mnt_special) == 0)
982 break;
983 }
984 if (entry == NULL) {
985 entry = (hashentry_t *)calloc(1, sizeof (hashentry_t));
986 if ((entry == NULL) ||
987 ((entry->special = strdup(mt->mnt_special)) == NULL) ||
988 ((entry->fstype = strdup(mt->mnt_fstype)) == NULL)) {
989 rcm_log_message(RCM_ERROR,
990 "FILESYS: failed to allocate special device name "
991 "or filesystem type: (%s).\n", strerror(errno));
992 free_entry(&entry);
993 errno = ENOMEM;
994 return (-1);
995 }
996 entry->next = cache->mounts[index];
997 cache->mounts[index] = entry;
998 }
999
1000 /*
1001 * Keep entries in the list of mounts unique, so exit early if the
1002 * mount is already in the list.
1003 */
1004 for (index = 0; index < entry->n_mounts; index++) {
1005 if (strcmp(entry->mountps[index], mt->mnt_mountp) == 0)
1006 return (0);
1007 }
1008
1009 /*
1010 * Add this mountpoint to the list of mounts associated with the
1011 * special device.
1012 */
1013 mountps = (char **)realloc(entry->mountps,
1014 (entry->n_mounts + 2) * sizeof (char *));
1015 if ((mountps == NULL) ||
1016 ((mountps[entry->n_mounts] = strdup(mt->mnt_mountp)) == NULL)) {
1017 rcm_log_message(RCM_ERROR,
1018 "FILESYS: failed to allocate mountpoint name (%s).\n",
1019 strerror(errno));
1020 if (entry->n_mounts == 0) {
1021 cache->mounts[index] = entry->next;
1022 free_entry(&entry);
1023 }
1024 errno = ENOMEM;
1025 return (-1);
1026 }
1027 mountps[entry->n_mounts + 1] = NULL;
1028 entry->n_mounts++;
1029 entry->mountps = mountps;
1030
1031 return (0);
1032 }
1033
1034 /*
1035 * cache_lookup()
1036 *
1037 * Searches the cached table of mounts for a special device entry.
1038 *
1039 * Locking: the cache must be locked before calling this function.
1040 *
1041 * Return Value: NULL with errno set if failure, pointer to existing
1042 * cache entry when successful.
1043 */
1044 static hashentry_t *
cache_lookup(cache_t * cache,char * rsrc)1045 cache_lookup(cache_t *cache, char *rsrc)
1046 {
1047 uint32_t index;
1048 hashentry_t *entry;
1049
1050 /* Only accept valid arguments */
1051 if ((cache == NULL) || (cache->mounts == NULL) || (rsrc == NULL)) {
1052 errno = EINVAL;
1053 return (NULL);
1054 }
1055
1056 /* Search the cached mounts table for the resource's entry */
1057 index = hash(cache->hash_size, rsrc);
1058 if (cache->mounts[index]) {
1059 for (entry = cache->mounts[index]; entry != NULL;
1060 entry = entry->next) {
1061 if (strcmp(entry->special, rsrc) == 0)
1062 return (entry);
1063 }
1064 }
1065
1066 errno = ENOENT;
1067 return (NULL);
1068 }
1069
1070 /*
1071 * Miscellaneous Functions
1072 */
1073
1074 /*
1075 * hash()
1076 *
1077 * A naive hashing function that converts a string 's' to an index in a
1078 * hash table of size 'h'. It seems to spread entries around well enough.
1079 */
1080 static uint32_t
hash(uint32_t h,char * s)1081 hash(uint32_t h, char *s)
1082 {
1083 uint32_t sum = 0;
1084 unsigned char *byte;
1085
1086 if ((byte = (unsigned char *)s) != NULL) {
1087 while (*byte) {
1088 sum += 0x3F & (uint32_t)*byte;
1089 byte++;
1090 }
1091 }
1092
1093 return (sum % h);
1094 }
1095
1096 /*
1097 * register_rsrc()
1098 *
1099 * Registers for any given resource, unless it's "/".
1100 */
1101 static void
register_rsrc(rcm_handle_t * hd,char * rsrc)1102 register_rsrc(rcm_handle_t *hd, char *rsrc)
1103 {
1104 /* Only accept valid arguments */
1105 if ((hd == NULL) || (rsrc == NULL))
1106 return;
1107
1108 /*
1109 * Register any resource other than "/" or "/devices"
1110 */
1111 if ((strcmp(rsrc, "/") != 0) && (strcmp(rsrc, "/devices") != 0)) {
1112 rcm_log_message(RCM_DEBUG, "FILESYS: registering %s\n", rsrc);
1113 if (rcm_register_interest(hd, rsrc, 0, NULL) != RCM_SUCCESS) {
1114 rcm_log_message(RCM_WARNING,
1115 "FILESYS: failed to register %s\n", rsrc);
1116 }
1117 }
1118
1119 }
1120
1121 /*
1122 * unregister_rsrc()
1123 *
1124 * Unregister a resource. This does a little filtering since we know
1125 * "/" can't be registered, so we never bother unregistering for it.
1126 */
1127 static void
unregister_rsrc(rcm_handle_t * hd,char * rsrc)1128 unregister_rsrc(rcm_handle_t *hd, char *rsrc)
1129 {
1130 assert(hd != NULL);
1131 assert(rsrc != NULL);
1132
1133 /* Unregister any resource other than "/" */
1134 if (strcmp(rsrc, "/") != 0) {
1135 rcm_log_message(RCM_DEBUG, "FILESYS: unregistering %s\n", rsrc);
1136 (void) rcm_unregister_interest(hd, rsrc, 0);
1137 }
1138 }
1139
1140 /*
1141 * create_message()
1142 *
1143 * Given some header strings and a list of dependent names, this
1144 * constructs a single string. If there's only one dependent, the
1145 * string consists of the first header and the only dependent appended
1146 * to the end of the string enclosed in quotemarks. If there are
1147 * multiple dependents, then the string uses the second header and the
1148 * full list of dependents is appended at the end as a comma separated
1149 * list of names enclosed in quotemarks.
1150 */
1151 static char *
create_message(char * header,char * header_multi,char ** dependents)1152 create_message(char *header, char *header_multi, char **dependents)
1153 {
1154 int i;
1155 size_t len;
1156 int ndependents;
1157 char *msg_buf;
1158 char *msg_header;
1159 char *separator = MSG_SEPARATOR;
1160
1161 assert(header != NULL);
1162 assert(header_multi != NULL);
1163 assert(dependents != NULL);
1164
1165 /* Count the number of dependents */
1166 for (ndependents = 0; dependents[ndependents] != NULL; ndependents++);
1167
1168 /* If there are no dependents, fail */
1169 if (ndependents == 0) {
1170 errno = ENOENT;
1171 return (NULL);
1172 }
1173
1174 /* Pick the appropriate header to use based on amount of dependents */
1175 if (ndependents == 1) {
1176 msg_header = header;
1177 } else {
1178 msg_header = header_multi;
1179 }
1180
1181 /* Compute the size required for the message buffer */
1182 len = strlen(msg_header) + 2; /* +2 for the space and a NULL */
1183 for (i = 0; dependents[i] != NULL; i++)
1184 len += strlen(dependents[i]) + 2; /* +2 for quotemarks */
1185 len += strlen(separator) * (ndependents - 1);
1186
1187 /* Allocate the message buffer */
1188 if ((msg_buf = (char *)calloc(len, sizeof (char))) == NULL) {
1189 rcm_log_message(RCM_ERROR,
1190 "FILESYS: failed to allocate message buffer (%s).\n",
1191 strerror(errno));
1192 errno = ENOMEM;
1193 return (NULL);
1194 }
1195
1196 /* Fill in the message buffer */
1197 (void) snprintf(msg_buf, len, "%s ", msg_header);
1198 for (i = 0; dependents[i] != NULL; i++) {
1199 (void) strlcat(msg_buf, "\"", len);
1200 (void) strlcat(msg_buf, dependents[i], len);
1201 (void) strlcat(msg_buf, "\"", len);
1202 if ((i + 1) < ndependents)
1203 (void) strlcat(msg_buf, separator, len);
1204 }
1205
1206 return (msg_buf);
1207 }
1208
1209 /*
1210 * create_dependents()
1211 *
1212 * Creates a copy of the list of dependent mounts associated with a
1213 * given hashtable entry from the cache.
1214 *
1215 * Return Values: NULL with errno set on failure, the resulting list of
1216 * dependent resources when successful.
1217 */
1218 static char **
create_dependents(hashentry_t * entry)1219 create_dependents(hashentry_t *entry)
1220 {
1221 int i;
1222 char **dependents;
1223
1224 if (entry == NULL) {
1225 errno = EINVAL;
1226 return (NULL);
1227 }
1228
1229 if (entry->n_mounts == 0) {
1230 errno = ENOENT;
1231 return (NULL);
1232 }
1233
1234 /* Allocate space for the full dependency list */
1235 dependents = (char **)calloc(entry->n_mounts + 1, sizeof (char *));
1236 if (dependents == NULL) {
1237 rcm_log_message(RCM_ERROR,
1238 "FILESYS: failed to allocate dependents (%s).\n",
1239 strerror(errno));
1240 errno = ENOMEM;
1241 return (NULL);
1242 }
1243
1244 /* Copy all the dependent names into the new list of dependents */
1245 for (i = 0; i < entry->n_mounts; i++) {
1246 if ((dependents[i] = strdup(entry->mountps[i])) == NULL) {
1247 rcm_log_message(RCM_ERROR,
1248 "FILESYS: failed to allocate dependent \"%s\" "
1249 "(%s).\n", entry->mountps[i], strerror(errno));
1250 free_list(dependents);
1251 errno = ENOMEM;
1252 return (NULL);
1253 }
1254 }
1255
1256 return (dependents);
1257 }
1258
1259 /*
1260 * detect_critical_failure()
1261 *
1262 * Given a list of dependents, a place to store an error message, and
1263 * the flags associated with an operation, this function detects whether
1264 * or not the operation should fail due to the presence of any critical
1265 * filesystem resources. When a failure is detected, an appropriate
1266 * error message is constructed and passed back to the caller. This is
1267 * called during a suspend request operation.
1268 *
1269 * Return Values: 0 when a critical resource failure shouldn't prevent
1270 * the operation, and 1 when such a failure condition does exist.
1271 */
1272 static int
detect_critical_failure(char ** errorp,uint_t flags,char ** dependents)1273 detect_critical_failure(char **errorp, uint_t flags, char **dependents)
1274 {
1275 int i;
1276 int n_critical;
1277 char *tmp;
1278
1279 /* Do nothing if the operation is forced or there are no dependents */
1280 if ((errorp == NULL) || (flags & RCM_FORCE) || (dependents == NULL))
1281 return (0);
1282
1283 /*
1284 * Count how many of the dependents are critical, and shift the
1285 * critical resources to the head of the list.
1286 */
1287 if (dependents) {
1288 for (i = 0, n_critical = 0; dependents[i] != NULL; i++) {
1289 if (is_critical(dependents[i])) {
1290 if (n_critical != i) {
1291 tmp = dependents[n_critical];
1292 dependents[n_critical] = dependents[i];
1293 dependents[i] = tmp;
1294 }
1295 n_critical++;
1296 }
1297 }
1298 }
1299
1300 /* If no criticals were found, do nothing and return */
1301 if (n_critical == 0)
1302 return (0);
1303
1304 /*
1305 * Criticals were found. Prune the list appropriately and construct
1306 * an error message.
1307 */
1308
1309 /* Prune non-criticals out of the list */
1310 for (i = n_critical; dependents[i] != NULL; i++) {
1311 free(dependents[i]);
1312 dependents[i] = NULL;
1313 }
1314
1315 /* Construct the critical resource error message */
1316 *errorp = create_message(MSG_HDR_CRIT, MSG_HDR_CRIT_MULTI, dependents);
1317
1318 return (1);
1319 }
1320
1321 /*
1322 * is_critical()
1323 *
1324 * Test a resource to determine if it's critical to the system and thus
1325 * cannot be suspended.
1326 *
1327 * Return Values: 1 if the named resource is critical, 0 if not.
1328 */
1329 static int
is_critical(char * rsrc)1330 is_critical(char *rsrc)
1331 {
1332 assert(rsrc != NULL);
1333
1334 if ((strcmp(rsrc, "/") == 0) ||
1335 (strcmp(rsrc, "/usr") == 0) ||
1336 (strcmp(rsrc, "/lib") == 0) ||
1337 (strcmp(rsrc, "/usr/lib") == 0) ||
1338 (strcmp(rsrc, "/bin") == 0) ||
1339 (strcmp(rsrc, "/usr/bin") == 0) ||
1340 (strcmp(rsrc, "/tmp") == 0) ||
1341 (strcmp(rsrc, "/var") == 0) ||
1342 (strcmp(rsrc, "/var/run") == 0) ||
1343 (strcmp(rsrc, "/etc") == 0) ||
1344 (strcmp(rsrc, "/etc/mnttab") == 0) ||
1345 (strcmp(rsrc, "/platform") == 0) ||
1346 (strcmp(rsrc, "/usr/platform") == 0) ||
1347 (strcmp(rsrc, "/sbin") == 0) ||
1348 (strcmp(rsrc, "/usr/sbin") == 0))
1349 return (1);
1350
1351 return (0);
1352 }
1353
1354
1355 /*
1356 * use_cache()
1357 *
1358 * This routine handles all the tasks necessary to lookup a resource
1359 * in the cache and extract a separate list of dependents for that
1360 * entry. If an error occurs while doing this, an appropriate error
1361 * message is passed back to the caller.
1362 *
1363 * Locking: the cache is locked for the whole duration of this function.
1364 */
1365 static int
use_cache(char * rsrc,char ** errorp,char *** dependentsp)1366 use_cache(char *rsrc, char **errorp, char ***dependentsp)
1367 {
1368 hashentry_t *entry;
1369
1370 (void) mutex_lock(&cache_lock);
1371 if ((entry = cache_lookup(mnt_cache, rsrc)) == NULL) {
1372 rcm_log_message(RCM_ERROR,
1373 "FILESYS: failed looking up \"%s\" in cache (%s).\n",
1374 rsrc, strerror(errno));
1375 *errorp = strdup(MSG_FAIL_INTERNAL);
1376 (void) mutex_unlock(&cache_lock);
1377 return (-1);
1378 }
1379 *dependentsp = create_dependents(entry);
1380 (void) mutex_unlock(&cache_lock);
1381
1382 return (0);
1383 }
1384
1385 /*
1386 * prune_dependents()
1387 *
1388 * Before calling back into RCM with a list of dependents, the list
1389 * must be cleaned up a little. To avoid infinite recursion, "/" and
1390 * the named resource must be pruned out of the list.
1391 */
1392 static void
prune_dependents(char ** dependents,char * rsrc)1393 prune_dependents(char **dependents, char *rsrc)
1394 {
1395 int i;
1396 int n;
1397
1398 if (dependents) {
1399
1400 /* Set 'n' to the total length of the list */
1401 for (n = 0; dependents[n] != NULL; n++);
1402
1403 /*
1404 * Move offending dependents to the tail of the list and
1405 * then truncate the list.
1406 */
1407 for (i = 0; dependents[i] != NULL; i++) {
1408 if ((strcmp(dependents[i], rsrc) == 0) ||
1409 (strcmp(dependents[i], "/") == 0)) {
1410 free(dependents[i]);
1411 dependents[i] = dependents[n - 1];
1412 dependents[n] = NULL;
1413 i--;
1414 n--;
1415 }
1416 }
1417 }
1418 }
1419