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