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