xref: /illumos-gate/usr/src/cmd/rcm_daemon/common/filesys_rcm.c (revision 7a6d80f1660abd4755c68cbd094d4a914681d26e)
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 *
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 *
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
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
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
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
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
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
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
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
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
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
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
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
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 *
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
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
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
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
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
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 *
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
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
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
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 *
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 **
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
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
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
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
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