xref: /illumos-gate/usr/src/cmd/rcm_daemon/common/filesys_rcm.c (revision 24da5b34f49324ed742a340010ed5bd3d4e06625)
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, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
28 
29 /*
30  * This module adds support to the RCM framework for mounted filesystems.
31  *
32  * The module provides this functionality:
33  * 	1) reports device usage for mounted filesystems
34  *	2) prevents offline operations for mounted resources
35  *	3) prevents suspend operations (unless forced) of those filesystems
36  *	   deemed critical for the continued operation of the OS
37  *	4) propagates RCM operations from mounted resources to the consumers
38  *	   of files within the mounted filesystems
39  */
40 
41 #include <stdio.h>
42 #include <assert.h>
43 #include <string.h>
44 #include <synch.h>
45 #include <libintl.h>
46 #include <errno.h>
47 #include <sys/mnttab.h>
48 #include <sys/param.h>
49 #include <sys/stat.h>
50 #include <sys/utssys.h>
51 
52 #include "rcm_module.h"
53 
54 /* Definitions */
55 
56 #define	HASH_DEFAULT		4
57 #define	HASH_THRESHOLD		256
58 
59 #define	OPT_IGNORE		"ignore"
60 
61 #define	MSG_HDR_STD		gettext("mounted filesystem")
62 #define	MSG_HDR_STD_MULTI	gettext("mounted filesystems")
63 #define	MSG_HDR_CRIT		gettext("cannot suspend filesystem")
64 #define	MSG_HDR_CRIT_MULTI	gettext("cannot suspend filesystems")
65 #define	MSG_SEPARATOR		gettext(", ")
66 #define	MSG_FAIL_USAGE		gettext("failed to construct usage string.")
67 #define	MSG_FAIL_DEPENDENTS	gettext("failed while calling dependents.")
68 #define	MSG_FAIL_REMOVE		gettext("filesystems cannot be removed.")
69 #define	MSG_FAIL_INTERNAL	gettext("internal processing failure.")
70 
71 typedef struct hashentry {
72 	int n_mounts;
73 	char *special;
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 %I%");
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.  Always returns failure.
256  *	Since no real action is taken, QUERY or not doesn't matter.
257  */
258 int
259 mnt_offline(rcm_handle_t *hd, char *rsrc, id_t id, uint_t flags,
260     char **errorp, rcm_info_t **dependent_info)
261 {
262 	char **dependents;
263 
264 	assert(hd != NULL);
265 	assert(rsrc != NULL);
266 	assert(id == (id_t)0);
267 	assert(errorp != NULL);
268 
269 	rcm_log_message(RCM_TRACE1, "FILESYS: offline(%s)\n", rsrc);
270 
271 	/* Retrieve necessary info from the cache */
272 	if (use_cache(rsrc, errorp, &dependents) < 0)
273 		return (RCM_FAILURE);
274 
275 	/* Convert the gathered dependents into an error message */
276 	*errorp = create_message(MSG_HDR_STD, MSG_HDR_STD_MULTI, dependents);
277 	if (*errorp == NULL) {
278 		rcm_log_message(RCM_ERROR,
279 		    "FILESYS: failed to construct offline message (%s).\n",
280 		    strerror(errno));
281 	}
282 	free_list(dependents);
283 
284 	return (RCM_FAILURE);
285 }
286 
287 /*
288  * mnt_online()
289  *
290  *	Filesystem resources aren't offlined, so there's really nothing to do
291  *	here.
292  */
293 int
294 mnt_online(rcm_handle_t *hd, char *rsrc, id_t id, uint_t flag, char **errorp,
295     rcm_info_t **dependent_reason)
296 {
297 	assert(hd != NULL);
298 	assert(rsrc != NULL);
299 	assert(id == (id_t)0);
300 	assert(errorp != NULL);
301 
302 	rcm_log_message(RCM_TRACE1, "FILESYS: online(%s)\n", rsrc);
303 
304 	return (RCM_SUCCESS);
305 }
306 
307 /*
308  * mnt_getinfo()
309  *
310  *	Report how a given resource is in use by this module.  And also
311  *	possibly include dependent consumers of the mounted filesystems.
312  */
313 int
314 mnt_getinfo(rcm_handle_t *hd, char *rsrc, id_t id, uint_t flag, char **usagep,
315     char **errorp, nvlist_t *props, rcm_info_t **depend_info)
316 {
317 	int rv = RCM_SUCCESS;
318 	char **dependents;
319 
320 	assert(hd != NULL);
321 	assert(rsrc != NULL);
322 	assert(id == (id_t)0);
323 	assert(usagep != NULL);
324 	assert(errorp != NULL);
325 	assert(props != NULL);
326 
327 	rcm_log_message(RCM_TRACE1, "FILESYS: getinfo(%s)\n", rsrc);
328 
329 	/* Retrieve necessary info from the cache */
330 	if (use_cache(rsrc, errorp, &dependents) < 0)
331 		return (RCM_FAILURE);
332 
333 	/* Convert the gathered dependents into a usage message */
334 	*usagep = create_message(MSG_HDR_STD, MSG_HDR_STD_MULTI, dependents);
335 	if (*usagep == NULL) {
336 		rcm_log_message(RCM_ERROR,
337 		    "FILESYS: failed to construct usage message (%s).\n",
338 		    strerror(errno));
339 		*errorp = strdup(MSG_FAIL_USAGE);
340 		free_list(dependents);
341 		return (RCM_FAILURE);
342 	}
343 
344 	/* Recurse on dependents if necessary */
345 	if ((flag & RCM_INCLUDE_DEPENDENT) && (dependents != NULL)) {
346 		prune_dependents(dependents, rsrc);
347 		if (dependents[0] != NULL) {
348 			if ((rv = rcm_get_info_list(hd, dependents, flag,
349 			    depend_info)) != RCM_SUCCESS) {
350 				*errorp = strdup(MSG_FAIL_DEPENDENTS);
351 			}
352 		}
353 	}
354 
355 	/* Free up info retrieved from the cache */
356 	free_list(dependents);
357 
358 	return (rv);
359 }
360 
361 /*
362  * mnt_suspend()
363  *
364  *	Notify all dependents that the resource is being suspended.
365  *	Since no real action is taken, QUERY or not doesn't matter.
366  */
367 int
368 mnt_suspend(rcm_handle_t *hd, char *rsrc, id_t id, timespec_t *interval,
369     uint_t flag, char **errorp, rcm_info_t **depend_info)
370 {
371 	int rv = RCM_SUCCESS;
372 	char **dependents;
373 
374 	assert(hd != NULL);
375 	assert(rsrc != NULL);
376 	assert(id == (id_t)0);
377 	assert(interval != NULL);
378 	assert(errorp != NULL);
379 
380 	rcm_log_message(RCM_TRACE1, "FILESYS: suspend(%s)\n", rsrc);
381 
382 	/* Retrieve necessary info from the cache */
383 	if (use_cache(rsrc, errorp, &dependents) < 0)
384 		return (RCM_FAILURE);
385 
386 	/* Unforced suspensions fail if any of the dependents are critical */
387 	if (detect_critical_failure(errorp, flag, dependents)) {
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_request_suspend_list(hd, dependents, flag,
397 			    interval, depend_info)) != RCM_SUCCESS) {
398 				*errorp = strdup(MSG_FAIL_DEPENDENTS);
399 			}
400 	}
401 	free_list(dependents);
402 
403 	return (rv);
404 }
405 
406 /*
407  * mnt_resume()
408  *
409  *	Resume all the dependents of a suspended filesystem.
410  */
411 int
412 mnt_resume(rcm_handle_t *hd, char *rsrc, id_t id, uint_t flag, char **errorp,
413     rcm_info_t **depend_info)
414 {
415 	int rv = RCM_SUCCESS;
416 	char **dependents;
417 
418 	assert(hd != NULL);
419 	assert(rsrc != NULL);
420 	assert(id == (id_t)0);
421 	assert(errorp != NULL);
422 
423 	rcm_log_message(RCM_TRACE1, "FILESYS: resume(%s)\n", rsrc);
424 
425 	/* Retrieve necessary info from the cache */
426 	if (use_cache(rsrc, errorp, &dependents) < 0)
427 		return (RCM_FAILURE);
428 
429 	/* Recurse on dependents if necessary */
430 	if ((flag & RCM_INCLUDE_DEPENDENT) && (dependents != NULL)) {
431 		prune_dependents(dependents, rsrc);
432 		if (dependents[0] != NULL) {
433 			if ((rv = rcm_notify_resume_list(hd, dependents, flag,
434 			    depend_info)) != RCM_SUCCESS) {
435 				*errorp = strdup(MSG_FAIL_DEPENDENTS);
436 			}
437 		}
438 	}
439 	free_list(dependents);
440 
441 	return (rv);
442 }
443 
444 /*
445  * mnt_remove()
446  *
447  *	Remove should never be called since offline always fails.
448  *
449  *	Return failure and log the mistake if a remove is ever received for a
450  *	mounted filesystem resource.
451  */
452 int
453 mnt_remove(rcm_handle_t *hd, char *rsrc, id_t id, uint_t flag, char **errorp,
454     rcm_info_t **depend_info)
455 {
456 	assert(hd != NULL);
457 	assert(rsrc != NULL);
458 	assert(id == (id_t)0);
459 	assert(errorp != NULL);
460 
461 	rcm_log_message(RCM_TRACE1, "FILESYS: remove(%s)\n", rsrc);
462 
463 	/* Log the mistake */
464 	rcm_log_message(RCM_ERROR, "FILESYS: invalid remove of \"%s\"\n", rsrc);
465 	*errorp = strdup(MSG_FAIL_REMOVE);
466 
467 	return (RCM_FAILURE);
468 }
469 
470 /*
471  * Cache management routines
472  */
473 
474 /*
475  * cache_create()
476  *
477  *	This routine constructs a new cache of the current mnttab file.
478  *
479  *	Locking: the cache must be locked prior to calling this function.
480  *
481  *	Return Values: NULL with errno set on failure, new cache point on
482  *	success.
483  */
484 static cache_t *
485 cache_create()
486 {
487 	FILE *fp;
488 	cache_t *cache;
489 	int i;
490 	uint32_t size;
491 	struct stat st;
492 	struct mnttab mt;
493 
494 	/*
495 	 * To keep the hash table relatively sparse, default values are
496 	 * used for smaller mnttab files and these values are scaled up
497 	 * as a fraction of the total mnttab file size for larger ones.
498 	 */
499 	if (stat(MNTTAB, &st) < 0) {
500 		rcm_log_message(RCM_ERROR,
501 		    "FILESYS: failed to stat \"%s\" (%s).\n", MNTTAB,
502 		    strerror(errno));
503 		errno = EBADF;
504 		return (NULL);
505 	}
506 	if (st.st_size > HASH_THRESHOLD) {
507 		size = st.st_size / HASH_THRESHOLD;
508 		for (i = 0; size > 1; i++, size >>= 1);
509 		for (; i > -1; i--, size <<= 1);
510 	} else {
511 		size = HASH_DEFAULT;
512 	}
513 
514 	/* Allocate a new empty cache */
515 	if ((cache = (cache_t *)calloc(1, sizeof (cache_t))) == NULL) {
516 		rcm_log_message(RCM_ERROR,
517 		    "FILESYS: failed to allocate cache (%s).\n",
518 		    strerror(errno));
519 		errno = ENOMEM;
520 		return (NULL);
521 	}
522 	cache->hash_size = size;
523 	cache->timestamp = st.st_mtime;
524 
525 	/* Allocate an empty hash table for the registered special devices */
526 	cache->mounts = (hashentry_t **)calloc(size, sizeof (hashentry_t *));
527 	if (cache->mounts == NULL) {
528 		rcm_log_message(RCM_ERROR,
529 		    "FILESYS: failed to allocate mount table (%s).\n",
530 		    strerror(errno));
531 		free_cache(&cache);
532 		errno = ENOMEM;
533 		return (NULL);
534 	}
535 
536 	/* Open the mnttab file */
537 	if ((fp = fopen(MNTTAB, "r")) == NULL) {
538 		rcm_log_message(RCM_ERROR,
539 		    "FILESYS: failed to open \"%s\" (%s).\n", MNTTAB,
540 		    strerror(errno));
541 		free_cache(&cache);
542 		errno = EIO;
543 		return (NULL);
544 	}
545 
546 	/* Insert each mnttab entry into the cache */
547 	while (getmntent(fp, &mt) == 0) {
548 
549 		/* Well, not each entry... some are meant to be ignored */
550 		if ((mt.mnt_mntopts != NULL) &&
551 		    (hasmntopt(&mt, OPT_IGNORE) != NULL))
552 			continue;
553 
554 		if (cache_insert(cache, &mt) < 0) {
555 			rcm_log_message(RCM_ERROR,
556 			    "FILESYS: cache insertion failure (%s).\n",
557 			    strerror(errno));
558 			free_cache(&cache);
559 			(void) fclose(fp);
560 			errno = EFAULT;
561 			return (NULL);
562 		}
563 	}
564 
565 	/* Close the mnttab file */
566 	(void) fclose(fp);
567 
568 	return (cache);
569 }
570 
571 /*
572  * free_cache()
573  *
574  *	Free up all the memory associated with a cache.
575  *
576  *	Locking: the cache must be locked before calling this function.
577  */
578 static void
579 free_cache(cache_t **cachep)
580 {
581 	uint32_t index;
582 	hashentry_t *entry;
583 	hashentry_t *entry_tmp;
584 
585 	/* Do nothing with empty caches */
586 	if ((cachep == NULL) || (*cachep == NULL))
587 		return;
588 
589 	if ((*cachep)->mounts) {
590 		/* Walk through the hashtable, emptying it */
591 		for (index = 0; index < (*cachep)->hash_size; index++) {
592 			entry = (*cachep)->mounts[index];
593 			while (entry) {
594 				entry_tmp = entry->next;
595 				free_entry(&entry);
596 				entry = entry_tmp;
597 			}
598 		}
599 		free((*cachep)->mounts);
600 	}
601 
602 	free(*cachep);
603 	*cachep = NULL;
604 }
605 
606 /*
607  * free_entry()
608  *
609  *	Free up memory associated with a hashtable entry.
610  *
611  *	Locking: the cache must be locked before calling this function.
612  */
613 static void
614 free_entry(hashentry_t **entryp)
615 {
616 	if (entryp) {
617 		if (*entryp) {
618 			if ((*entryp)->special)
619 				free((*entryp)->special);
620 			free_list((*entryp)->mountps);
621 			free(*entryp);
622 		}
623 		*entryp = NULL;
624 	}
625 }
626 
627 /*
628  * free_list()
629  *
630  *	Free up memory associated with a null terminated list of names.
631  */
632 static void
633 free_list(char **list)
634 {
635 	int i;
636 
637 	if (list) {
638 		for (i = 0; list[i] != NULL; i++)
639 			free(list[i]);
640 		free(list);
641 	}
642 }
643 
644 /*
645  * cache_sync()
646  *
647  *	Resynchronize the mnttab cache with the mnttab file.
648  *
649  *	Locking: the cache must be locked before calling this function.
650  *
651  *	Return Values: -1 with errno set on failure, 0 on success.
652  */
653 static int
654 cache_sync(rcm_handle_t *hd, cache_t **cachep)
655 {
656 	uint32_t index;
657 	cache_t *new_cache;
658 	cache_t *old_cache;
659 	hashentry_t *entry;
660 	struct stat st;
661 
662 	/* Only accept valid arguments */
663 	if ((hd == NULL) || (cachep == NULL)) {
664 		rcm_log_message(RCM_ERROR,
665 		    "FILESYS: invalid arguments to cache_sync().\n");
666 		errno = EINVAL;
667 		return (-1);
668 	}
669 
670 	/* Do nothing if there's already an up-to-date cache */
671 	old_cache = *cachep;
672 	if (old_cache) {
673 		if (stat(MNTTAB, &st) == 0) {
674 			if (old_cache->timestamp >= st.st_mtime) {
675 				return (0);
676 			}
677 		} else {
678 			rcm_log_message(RCM_WARNING,
679 			    "FILESYS: failed to stat \"%s\", cache is stale "
680 			    "(%s).\n", MNTTAB, strerror(errno));
681 			errno = EIO;
682 			return (-1);
683 		}
684 	}
685 
686 	/* Create a new cache based on the new mnttab file.  */
687 	if ((new_cache = cache_create()) == NULL) {
688 		rcm_log_message(RCM_WARNING,
689 		    "FILESYS: failed creating cache, cache is stale (%s).\n",
690 		    strerror(errno));
691 		errno = EIO;
692 		return (-1);
693 	}
694 
695 	/* Register any specials found in the new cache but not the old one */
696 	for (index = 0; index < new_cache->hash_size; index++) {
697 		for (entry = new_cache->mounts[index]; entry != NULL;
698 		    entry = entry->next) {
699 			if (cache_lookup(old_cache, entry->special) == NULL) {
700 				register_rsrc(hd, entry->special);
701 			}
702 		}
703 	}
704 
705 	/* Pass the new cache pointer to the calling function */
706 	*cachep = new_cache;
707 
708 	/* If there wasn't an old cache, return successfully now */
709 	if (old_cache == NULL)
710 		return (0);
711 
712 	/*
713 	 * If there was an old cache, then unregister whatever specials it
714 	 * contains that aren't in the new cache.  And then destroy the old
715 	 * cache.
716 	 */
717 	for (index = 0; index < old_cache->hash_size; index++) {
718 		for (entry = old_cache->mounts[index]; entry != NULL;
719 		    entry = entry->next) {
720 			if (cache_lookup(new_cache, entry->special) == NULL) {
721 				unregister_rsrc(hd, entry->special);
722 			}
723 		}
724 	}
725 	free_cache(&old_cache);
726 
727 	return (0);
728 }
729 
730 /*
731  * cache_insert()
732  *
733  *	Given a cache and a mnttab entry, this routine inserts that entry in
734  *	the cache.  The mnttab entry's special device is added to the 'mounts'
735  *	hashtable of the cache, and the entry's mountp value is added to the
736  *	list of associated mountpoints for the corresponding hashtable entry.
737  *
738  *	Locking: the cache must be locked before calling this function.
739  *
740  *	Return Values: -1 with errno set on failure, 0 on success.
741  */
742 static int
743 cache_insert(cache_t *cache, struct mnttab *mt)
744 {
745 	uint32_t index;
746 	hashentry_t *entry;
747 	char **mountps;
748 
749 	/* Only accept valid arguments */
750 	if ((cache == NULL) ||
751 	    (cache->mounts == NULL) ||
752 	    (mt == NULL) ||
753 	    (mt->mnt_special == NULL) ||
754 	    (mt->mnt_mountp == NULL)) {
755 		errno = EINVAL;
756 		return (-1);
757 	}
758 
759 	/*
760 	 * Disregard any non-loopback mounts whose special device names
761 	 * don't begin with "/dev".
762 	 */
763 	if ((strncmp(mt->mnt_special, "/dev", strlen("/dev")) != 0) &&
764 	    (strcmp(mt->mnt_fstype, "lofs") != 0))
765 		return (0);
766 
767 	/*
768 	 * Find the special device's entry in the mounts hashtable, allocating
769 	 * a new entry if necessary.
770 	 */
771 	index = hash(cache->hash_size, mt->mnt_special);
772 	for (entry = cache->mounts[index]; entry != NULL; entry = entry->next) {
773 		if (strcmp(entry->special, mt->mnt_special) == 0)
774 			break;
775 	}
776 	if (entry == NULL) {
777 		entry = (hashentry_t *)calloc(1, sizeof (hashentry_t));
778 		if ((entry == NULL) ||
779 		    ((entry->special = strdup(mt->mnt_special)) == NULL)) {
780 			rcm_log_message(RCM_ERROR,
781 			    "FILESYS: failed to allocate special device name "
782 			    "(%s).\n", strerror(errno));
783 			free_entry(&entry);
784 			errno = ENOMEM;
785 			return (-1);
786 		}
787 		entry->next = cache->mounts[index];
788 		cache->mounts[index] = entry;
789 	}
790 
791 	/*
792 	 * Keep entries in the list of mounts unique, so exit early if the
793 	 * mount is already in the list.
794 	 */
795 	for (index = 0; index < entry->n_mounts; index++) {
796 		if (strcmp(entry->mountps[index], mt->mnt_mountp) == 0)
797 			return (0);
798 	}
799 
800 	/*
801 	 * Add this mountpoint to the list of mounts associated with the
802 	 * special device.
803 	 */
804 	mountps = (char **)realloc(entry->mountps,
805 	    (entry->n_mounts + 2) * sizeof (char *));
806 	if ((mountps == NULL) ||
807 	    ((mountps[entry->n_mounts] = strdup(mt->mnt_mountp)) == NULL)) {
808 		rcm_log_message(RCM_ERROR,
809 		    "FILESYS: failed to allocate mountpoint name (%s).\n",
810 		    strerror(errno));
811 		if (entry->n_mounts == 0) {
812 			cache->mounts[index] = entry->next;
813 			free_entry(&entry);
814 		}
815 		errno = ENOMEM;
816 		return (-1);
817 	}
818 	mountps[entry->n_mounts + 1] = NULL;
819 	entry->n_mounts++;
820 	entry->mountps = mountps;
821 
822 	return (0);
823 }
824 
825 /*
826  * cache_lookup()
827  *
828  *	Searches the cached table of mounts for a special device entry.
829  *
830  *	Locking: the cache must be locked before calling this function.
831  *
832  *	Return Value: NULL with errno set if failure, pointer to existing
833  *	cache entry when successful.
834  */
835 static hashentry_t *
836 cache_lookup(cache_t *cache, char *rsrc)
837 {
838 	uint32_t index;
839 	hashentry_t *entry;
840 
841 	/* Only accept valid arguments */
842 	if ((cache == NULL) || (cache->mounts == NULL) || (rsrc == NULL)) {
843 		errno = EINVAL;
844 		return (NULL);
845 	}
846 
847 	/* Search the cached mounts table for the resource's entry */
848 	index = hash(cache->hash_size, rsrc);
849 	if (cache->mounts[index]) {
850 		for (entry = cache->mounts[index]; entry != NULL;
851 		    entry = entry->next) {
852 			if (strcmp(entry->special, rsrc) == 0)
853 				return (entry);
854 		}
855 	}
856 
857 	errno = ENOENT;
858 	return (NULL);
859 }
860 
861 /*
862  * Miscellaneous Functions
863  */
864 
865 /*
866  * hash()
867  *
868  *	A naive hashing function that converts a string 's' to an index in a
869  * 	hash table of size 'h'.  It seems to spread entries around well enough.
870  */
871 static uint32_t
872 hash(uint32_t h, char *s)
873 {
874 	uint32_t sum = 0;
875 	unsigned char *byte;
876 
877 	if ((byte = (unsigned char *)s) != NULL) {
878 		while (*byte) {
879 			sum += 0x3F & (uint32_t)*byte;
880 			byte++;
881 		}
882 	}
883 
884 	return (sum % h);
885 }
886 
887 /*
888  * register_rsrc()
889  *
890  *	Registers for any given resource, unless it's "/".
891  */
892 static void
893 register_rsrc(rcm_handle_t *hd, char *rsrc)
894 {
895 	/* Only accept valid arguments */
896 	if ((hd == NULL) || (rsrc == NULL))
897 		return;
898 
899 	/*
900 	 * Register any resource other than "/" or "/devices"
901 	 */
902 	if ((strcmp(rsrc, "/") != 0) && (strcmp(rsrc, "/devices") != 0)) {
903 		rcm_log_message(RCM_DEBUG, "FILESYS: registering %s\n", rsrc);
904 		if (rcm_register_interest(hd, rsrc, 0, NULL) != RCM_SUCCESS) {
905 			rcm_log_message(RCM_WARNING,
906 			    "FILESYS: failed to register %s\n", rsrc);
907 		}
908 	}
909 
910 }
911 
912 /*
913  * unregister_rsrc()
914  *
915  *	Unregister a resource.  This does a little filtering since we know
916  *	"/" can't be registered, so we never bother unregistering for it.
917  */
918 static void
919 unregister_rsrc(rcm_handle_t *hd, char *rsrc)
920 {
921 	assert(hd != NULL);
922 	assert(rsrc != NULL);
923 
924 	/* Unregister any resource other than "/" */
925 	if (strcmp(rsrc, "/") != 0) {
926 		rcm_log_message(RCM_DEBUG, "FILESYS: unregistering %s\n", rsrc);
927 		(void) rcm_unregister_interest(hd, rsrc, 0);
928 	}
929 }
930 
931 /*
932  * create_message()
933  *
934  *	Given some header strings and a list of dependent names, this
935  *	constructs a single string.  If there's only one dependent, the
936  *	string consists of the first header and the only dependent appended
937  *	to the end of the string enclosed in quotemarks.  If there are
938  *	multiple dependents, then the string uses the second header and the
939  *	full list of dependents is appended at the end as a comma separated
940  *	list of names enclosed in quotemarks.
941  */
942 static char *
943 create_message(char *header, char *header_multi, char **dependents)
944 {
945 	int i;
946 	size_t len;
947 	int ndependents;
948 	char *msg_buf;
949 	char *msg_header;
950 	char *separator = MSG_SEPARATOR;
951 
952 	assert(header != NULL);
953 	assert(header_multi != NULL);
954 	assert(dependents != NULL);
955 
956 	/* Count the number of dependents */
957 	for (ndependents = 0; dependents[ndependents] != NULL; ndependents++);
958 
959 	/* If there are no dependents, fail */
960 	if (ndependents == 0) {
961 		errno = ENOENT;
962 		return (NULL);
963 	}
964 
965 	/* Pick the appropriate header to use based on amount of dependents */
966 	if (ndependents == 1) {
967 		msg_header = header;
968 	} else {
969 		msg_header = header_multi;
970 	}
971 
972 	/* Compute the size required for the message buffer */
973 	len = strlen(msg_header) + 2;	/* +2 for the space and a NULL */
974 	for (i = 0; dependents[i] != NULL; i++)
975 		len += strlen(dependents[i]) + 2;	/* +2 for quotemarks */
976 	len += strlen(separator) * (ndependents - 1);
977 
978 	/* Allocate the message buffer */
979 	if ((msg_buf = (char *)calloc(len, sizeof (char))) == NULL) {
980 		rcm_log_message(RCM_ERROR,
981 		    "FILESYS: failed to allocate message buffer (%s).\n",
982 		    strerror(errno));
983 		errno = ENOMEM;
984 		return (NULL);
985 	}
986 
987 	/* Fill in the message buffer */
988 	(void) snprintf(msg_buf, len, "%s ", msg_header);
989 	for (i = 0; dependents[i] != NULL; i++) {
990 		(void) strlcat(msg_buf, "\"", len);
991 		(void) strlcat(msg_buf, dependents[i], len);
992 		(void) strlcat(msg_buf, "\"", len);
993 		if ((i + 1) < ndependents)
994 			(void) strlcat(msg_buf, separator, len);
995 	}
996 
997 	return (msg_buf);
998 }
999 
1000 /*
1001  * create_dependents()
1002  *
1003  *	Creates a copy of the list of dependent mounts associated with a
1004  *	given hashtable entry from the cache.
1005  *
1006  *	Return Values: NULL with errno set on failure, the resulting list of
1007  *	dependent resources when successful.
1008  */
1009 static char **
1010 create_dependents(hashentry_t *entry)
1011 {
1012 	int i;
1013 	char **dependents;
1014 
1015 	if (entry == NULL) {
1016 		errno = EINVAL;
1017 		return (NULL);
1018 	}
1019 
1020 	if (entry->n_mounts == 0) {
1021 		errno = ENOENT;
1022 		return (NULL);
1023 	}
1024 
1025 	/* Allocate space for the full dependency list */
1026 	dependents = (char **)calloc(entry->n_mounts + 1, sizeof (char *));
1027 	if (dependents == NULL) {
1028 		rcm_log_message(RCM_ERROR,
1029 		    "FILESYS: failed to allocate dependents (%s).\n",
1030 		    strerror(errno));
1031 		errno = ENOMEM;
1032 		return (NULL);
1033 	}
1034 
1035 	/* Copy all the dependent names into the new list of dependents */
1036 	for (i = 0; i < entry->n_mounts; i++) {
1037 		if ((dependents[i] = strdup(entry->mountps[i])) == NULL) {
1038 			rcm_log_message(RCM_ERROR,
1039 			    "FILESYS: failed to allocate dependent \"%s\" "
1040 			    "(%s).\n", entry->mountps[i], strerror(errno));
1041 			free_list(dependents);
1042 			errno = ENOMEM;
1043 			return (NULL);
1044 		}
1045 	}
1046 
1047 	return (dependents);
1048 }
1049 
1050 /*
1051  * detect_critical_failure()
1052  *
1053  *	Given a list of dependents, a place to store an error message, and
1054  *	the flags associated with an operation, this function detects whether
1055  *	or not the operation should fail due to the presence of any critical
1056  *	filesystem resources.  When a failure is detected, an appropriate
1057  *	error message is constructed and passed back to the caller.  This is
1058  *	called during a suspend request operation.
1059  *
1060  *	Return Values: 0 when a critical resource failure shouldn't prevent
1061  *	the operation, and 1 when such a failure condition does exist.
1062  */
1063 static int
1064 detect_critical_failure(char **errorp, uint_t flags, char **dependents)
1065 {
1066 	int i;
1067 	int n_critical;
1068 	char *tmp;
1069 
1070 	/* Do nothing if the operation is forced or there are no dependents */
1071 	if ((errorp == NULL) || (flags & RCM_FORCE) || (dependents == NULL))
1072 		return (0);
1073 
1074 	/*
1075 	 * Count how many of the dependents are critical, and shift the
1076 	 * critical resources to the head of the list.
1077 	 */
1078 	if (dependents) {
1079 		for (i = 0, n_critical = 0; dependents[i] != NULL; i++) {
1080 			if (is_critical(dependents[i])) {
1081 				if (n_critical != i) {
1082 					tmp = dependents[n_critical];
1083 					dependents[n_critical] = dependents[i];
1084 					dependents[i] = tmp;
1085 				}
1086 				n_critical++;
1087 			}
1088 		}
1089 	}
1090 
1091 	/* If no criticals were found, do nothing and return */
1092 	if (n_critical == 0)
1093 		return (0);
1094 
1095 	/*
1096 	 * Criticals were found.  Prune the list appropriately and construct
1097 	 * an error message.
1098 	 */
1099 
1100 	/* Prune non-criticals out of the list */
1101 	for (i = n_critical; dependents[i] != NULL; i++) {
1102 		free(dependents[i]);
1103 		dependents[i] = NULL;
1104 	}
1105 
1106 	/* Construct the critical resource error message */
1107 	*errorp = create_message(MSG_HDR_CRIT, MSG_HDR_CRIT_MULTI, dependents);
1108 
1109 	return (1);
1110 }
1111 
1112 /*
1113  * is_critical()
1114  *
1115  *	Test a resource to determine if it's critical to the system and thus
1116  *	cannot be suspended.
1117  *
1118  *	Return Values: 1 if the named resource is critical, 0 if not.
1119  */
1120 static int
1121 is_critical(char *rsrc)
1122 {
1123 	assert(rsrc != NULL);
1124 
1125 	if ((strcmp(rsrc, "/") == 0) ||
1126 	    (strcmp(rsrc, "/usr") == 0) ||
1127 	    (strcmp(rsrc, "/usr/lib") == 0) ||
1128 	    (strcmp(rsrc, "/usr/bin") == 0) ||
1129 	    (strcmp(rsrc, "/tmp") == 0) ||
1130 	    (strcmp(rsrc, "/var") == 0) ||
1131 	    (strcmp(rsrc, "/var/run") == 0) ||
1132 	    (strcmp(rsrc, "/etc") == 0) ||
1133 	    (strcmp(rsrc, "/etc/mnttab") == 0) ||
1134 	    (strcmp(rsrc, "/sbin") == 0))
1135 		return (1);
1136 
1137 	return (0);
1138 }
1139 
1140 /*
1141  * use_cache()
1142  *
1143  *	This routine handles all the tasks necessary to lookup a resource
1144  *	in the cache and extract a separate list of dependents for that
1145  *	entry.  If an error occurs while doing this, an appropriate error
1146  *	message is passed back to the caller.
1147  *
1148  *	Locking: the cache is locked for the whole duration of this function.
1149  */
1150 static int
1151 use_cache(char *rsrc, char **errorp, char ***dependentsp)
1152 {
1153 	hashentry_t *entry;
1154 
1155 	(void) mutex_lock(&cache_lock);
1156 	if ((entry = cache_lookup(mnt_cache, rsrc)) == NULL) {
1157 		rcm_log_message(RCM_ERROR,
1158 		    "FILESYS: failed looking up \"%s\" in cache (%s).\n",
1159 		    rsrc, strerror(errno));
1160 		*errorp = strdup(MSG_FAIL_INTERNAL);
1161 		(void) mutex_unlock(&cache_lock);
1162 		return (-1);
1163 	}
1164 	*dependentsp = create_dependents(entry);
1165 	(void) mutex_unlock(&cache_lock);
1166 
1167 	return (0);
1168 }
1169 
1170 /*
1171  * prune_dependents()
1172  *
1173  *	Before calling back into RCM with a list of dependents, the list
1174  *	must be cleaned up a little.  To avoid infinite recursion, "/" and
1175  *	the named resource must be pruned out of the list.
1176  */
1177 static void
1178 prune_dependents(char **dependents, char *rsrc)
1179 {
1180 	int i;
1181 	int n;
1182 
1183 	if (dependents) {
1184 
1185 		/* Set 'n' to the total length of the list */
1186 		for (n = 0; dependents[n] != NULL; n++);
1187 
1188 		/*
1189 		 * Move offending dependents to the tail of the list and
1190 		 * then truncate the list.
1191 		 */
1192 		for (i = 0; dependents[i] != NULL; i++) {
1193 			if ((strcmp(dependents[i], rsrc) == 0) ||
1194 			    (strcmp(dependents[i], "/") == 0)) {
1195 				free(dependents[i]);
1196 				dependents[i] = dependents[n - 1];
1197 				dependents[n] = NULL;
1198 				i--;
1199 				n--;
1200 			}
1201 		}
1202 	}
1203 }
1204