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