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