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 2005 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 * bootadm(1M) is a new utility for managing bootability of 31 * Solaris *Newboot* environments. It has two primary tasks: 32 * - Allow end users to manage bootability of Newboot Solaris instances 33 * - Provide services to other subsystems in Solaris (primarily Install) 34 */ 35 36 /* Headers */ 37 #include <stdio.h> 38 #include <errno.h> 39 #include <stdlib.h> 40 #include <string.h> 41 #include <unistd.h> 42 #include <sys/types.h> 43 #include <sys/stat.h> 44 #include <stdarg.h> 45 #include <limits.h> 46 #include <signal.h> 47 #include <sys/wait.h> 48 #include <sys/mnttab.h> 49 #include <sys/statvfs.h> 50 #include <libnvpair.h> 51 #include <ftw.h> 52 #include <fcntl.h> 53 #include <strings.h> 54 #include <sys/systeminfo.h> 55 #include <sys/dktp/fdisk.h> 56 57 #include <pwd.h> 58 #include <grp.h> 59 #include <device_info.h> 60 61 #include <libintl.h> 62 #include <locale.h> 63 64 #include <assert.h> 65 66 #include "message.h" 67 68 #ifndef TEXT_DOMAIN 69 #define TEXT_DOMAIN "SUNW_OST_OSCMD" 70 #endif /* TEXT_DOMAIN */ 71 72 /* Type definitions */ 73 74 /* Primary subcmds */ 75 typedef enum { 76 BAM_MENU = 3, 77 BAM_ARCHIVE 78 } subcmd_t; 79 80 /* GRUB menu per-line classification */ 81 typedef enum { 82 BAM_INVALID = 0, 83 BAM_EMPTY, 84 BAM_COMMENT, 85 BAM_GLOBAL, 86 BAM_ENTRY, 87 BAM_TITLE 88 } menu_flag_t; 89 90 /* struct for menu.lst contents */ 91 typedef struct line { 92 int lineNum; /* Line number in menu.lst */ 93 int entryNum; /* menu boot entry #. ENTRY_INIT if not applicable */ 94 char *cmd; 95 char *sep; 96 char *arg; 97 char *line; 98 menu_flag_t flags; 99 struct line *next; 100 } line_t; 101 102 typedef struct { 103 line_t *start; 104 line_t *end; 105 } menu_t; 106 107 typedef enum { 108 OPT_ABSENT = 0, /* No option */ 109 OPT_REQ, /* option required */ 110 OPT_OPTIONAL /* option may or may not be present */ 111 } option_t; 112 113 typedef enum { 114 BAM_ERROR = -1, 115 BAM_SUCCESS = 0, 116 BAM_WRITE = 2 117 } error_t; 118 119 typedef struct { 120 char *subcmd; 121 option_t option; 122 error_t (*handler)(); 123 } subcmd_defn_t; 124 125 126 #define BAM_MAXLINE 8192 127 128 #define LINE_INIT 0 /* lineNum initial value */ 129 #define ENTRY_INIT -1 /* entryNum initial value */ 130 #define ALL_ENTRIES -2 /* selects all boot entries */ 131 132 #define GRUB_DIR "/boot/grub" 133 #define MULTI_BOOT "/platform/i86pc/multiboot" 134 #define BOOT_ARCHIVE "/platform/i86pc/boot_archive" 135 #define GRUB_MENU "/boot/grub/menu.lst" 136 #define MENU_TMP "/boot/grub/menu.lst.tmp" 137 #define RAMDISK_SPECIAL "/ramdisk" 138 139 /* lock related */ 140 #define BAM_LOCK_FILE "/var/run/bootadm.lock" 141 #define LOCK_FILE_PERMS (S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) 142 143 #define CREATE_RAMDISK "/boot/solaris/bin/create_ramdisk" 144 #define CREATE_DISKMAP "/boot/solaris/bin/create_diskmap" 145 #define GRUBDISK_MAP "/var/run/solaris_grubdisk.map" 146 147 /* 148 * Default file attributes 149 */ 150 #define DEFAULT_DEV_MODE 0644 /* default permissions */ 151 #define DEFAULT_DEV_UID 0 /* user root */ 152 #define DEFAULT_DEV_GID 3 /* group sys */ 153 154 /* 155 * Menu related 156 * menu_cmd_t and menu_cmds must be kept in sync 157 */ 158 typedef enum { 159 DEFAULT_CMD = 0, 160 TIMEOUT_CMD, 161 TITLE_CMD, 162 ROOT_CMD, 163 KERNEL_CMD, 164 MODULE_CMD, 165 SEP_CMD, 166 COMMENT_CMD 167 } menu_cmd_t; 168 169 static char *menu_cmds[] = { 170 "default", /* DEFAULT_CMD */ 171 "timeout", /* TIMEOUT_CMD */ 172 "title", /* TITLE_CMD */ 173 "root", /* ROOT_CMD */ 174 "kernel", /* KERNEL_CMD */ 175 "module", /* MODULE_CMD */ 176 " ", /* SEP_CMD */ 177 "#", /* COMMENT_CMD */ 178 NULL 179 }; 180 181 #define OPT_ENTRY_NUM "entry" 182 183 /* 184 * archive related 185 */ 186 typedef struct { 187 line_t *head; 188 line_t *tail; 189 } filelist_t; 190 191 #define BOOT_FILE_LIST "boot/solaris/filelist.ramdisk" 192 #define ETC_FILE_LIST "etc/boot/solaris/filelist.ramdisk" 193 194 #define FILE_STAT "boot/solaris/filestat.ramdisk" 195 #define FILE_STAT_TMP "boot/solaris/filestat.ramdisk.tmp" 196 #define DIR_PERMS (S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH) 197 #define FILE_STAT_MODE (S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) 198 199 #define BAM_HDR "---------- ADDED BY BOOTADM - DO NOT EDIT ----------" 200 #define BAM_FTR "---------------------END BOOTADM--------------------" 201 202 203 /* Globals */ 204 static char *prog; 205 static subcmd_t bam_cmd; 206 static char *bam_root; 207 static int bam_rootlen; 208 static int bam_root_readonly; 209 static char *bam_subcmd; 210 static char *bam_opt; 211 static int bam_debug; 212 static char **bam_argv; 213 static int bam_argc; 214 static int bam_force; 215 static int bam_verbose; 216 static int bam_check; 217 static int bam_smf_check; 218 static int bam_lock_fd = -1; 219 static char rootbuf[PATH_MAX] = "/"; 220 221 /* function prototypes */ 222 static void parse_args_internal(int argc, char *argv[]); 223 static void parse_args(int argc, char *argv[]); 224 static error_t bam_menu(char *subcmd, char *opt, int argc, char *argv[]); 225 static error_t bam_archive(char *subcmd, char *opt); 226 227 static void bam_error(char *format, ...); 228 static void bam_print(char *format, ...); 229 static void bam_exit(int excode); 230 static void bam_lock(void); 231 static void bam_unlock(void); 232 233 static int exec_cmd(char *cmdline, char *output, int64_t osize); 234 static error_t read_globals(menu_t *mp, char *menu_path, 235 char *globalcmd, int quiet); 236 237 static menu_t *menu_read(char *menu_path); 238 static error_t menu_write(char *root, menu_t *mp); 239 static void linelist_free(line_t *start); 240 static void menu_free(menu_t *mp); 241 static void line_free(line_t *lp); 242 static void filelist_free(filelist_t *flistp); 243 static error_t list2file(char *root, char *tmp, 244 char *final, line_t *start); 245 static error_t list_entry(menu_t *mp, char *menu_path, char *opt); 246 static error_t delete_entry(menu_t *mp, char *menu_path, char *opt); 247 static error_t delete_all_entries(menu_t *mp, char *menu_path, char *opt); 248 static error_t update_entry(menu_t *mp, char *root, char *opt); 249 static error_t update_temp(menu_t *mp, char *root, char *opt); 250 251 static error_t update_archive(char *root, char *opt); 252 static error_t list_archive(char *root, char *opt); 253 static error_t update_all(char *root, char *opt); 254 static error_t read_list(char *root, filelist_t *flistp); 255 static error_t set_global(menu_t *mp, char *globalcmd, int val); 256 static error_t set_option(menu_t *mp, char *globalcmd, char *opt); 257 258 static long s_strtol(char *str); 259 static char *s_fgets(char *buf, int n, FILE *fp); 260 static int s_fputs(char *str, FILE *fp); 261 262 static void *s_calloc(size_t nelem, size_t sz); 263 static char *s_strdup(char *str); 264 static int is_readonly(char *); 265 static int is_amd64(void); 266 static void append_to_flist(filelist_t *, char *); 267 268 #if defined(__sparc) 269 static void sparc_abort(void); 270 #endif 271 272 /* Menu related sub commands */ 273 static subcmd_defn_t menu_subcmds[] = { 274 "set_option", OPT_OPTIONAL, set_option, /* PUB */ 275 "list_entry", OPT_OPTIONAL, list_entry, /* PUB */ 276 "delete_all_entries", OPT_ABSENT, delete_all_entries, /* PVT */ 277 "update_entry", OPT_REQ, update_entry, /* menu */ 278 "update_temp", OPT_OPTIONAL, update_temp, /* reboot */ 279 NULL, 0, NULL /* must be last */ 280 }; 281 282 /* Archive related sub commands */ 283 static subcmd_defn_t arch_subcmds[] = { 284 "update", OPT_ABSENT, update_archive, /* PUB */ 285 "update_all", OPT_ABSENT, update_all, /* PVT */ 286 "list", OPT_OPTIONAL, list_archive, /* PUB */ 287 NULL, 0, NULL /* must be last */ 288 }; 289 290 static struct { 291 nvlist_t *new_nvlp; 292 nvlist_t *old_nvlp; 293 int need_update; 294 } walk_arg; 295 296 static void 297 usage(void) 298 { 299 (void) fprintf(stderr, "USAGE:\n"); 300 301 302 /* archive usage */ 303 (void) fprintf(stderr, "\t%s update-archive [-vn] [-R altroot]\n", 304 prog); 305 (void) fprintf(stderr, "\t%s list-archive [-R altroot]\n", prog); 306 #ifndef __sparc 307 /* x86 only */ 308 (void) fprintf(stderr, "\t%s set-menu [-R altroot] key=value\n", prog); 309 (void) fprintf(stderr, "\t%s list-menu [-R altroot]\n", prog); 310 #endif 311 } 312 313 int 314 main(int argc, char *argv[]) 315 { 316 error_t ret; 317 318 (void) setlocale(LC_ALL, ""); 319 (void) textdomain(TEXT_DOMAIN); 320 321 if ((prog = strrchr(argv[0], '/')) == NULL) { 322 prog = argv[0]; 323 } else { 324 prog++; 325 } 326 327 if (geteuid() != 0) { 328 bam_error(MUST_BE_ROOT); 329 bam_exit(1); 330 } 331 332 bam_lock(); 333 334 /* 335 * Don't depend on caller's umask 336 */ 337 (void) umask(0022); 338 339 parse_args(argc, argv); 340 341 #if defined(__sparc) 342 /* 343 * There are only two valid invocations of bootadm 344 * on SPARC: 345 * 346 * - SPARC diskless server creating boot_archive for i386 clients 347 * - archive creation call during reboot of a SPARC system 348 * 349 * The latter should be a NOP 350 */ 351 if (bam_cmd != BAM_ARCHIVE) { 352 sparc_abort(); 353 } 354 #endif 355 356 switch (bam_cmd) { 357 case BAM_MENU: 358 ret = bam_menu(bam_subcmd, bam_opt, bam_argc, bam_argv); 359 break; 360 case BAM_ARCHIVE: 361 ret = bam_archive(bam_subcmd, bam_opt); 362 break; 363 default: 364 usage(); 365 bam_exit(1); 366 } 367 368 if (ret != BAM_SUCCESS) 369 bam_exit(1); 370 371 bam_unlock(); 372 return (0); 373 } 374 375 #if defined(__sparc) 376 377 static void 378 sparc_abort(void) 379 { 380 bam_error(NOT_ON_SPARC); 381 bam_exit(1); 382 } 383 384 #endif 385 386 /* 387 * Equivalence of public and internal commands: 388 * update-archive -- -a update 389 * list-archive -- -a list 390 * set-menu -- -m set_option 391 * list-menu -- -m list_entry 392 * update-menu -- -m update_entry 393 */ 394 static struct cmd_map { 395 char *bam_cmdname; 396 int bam_cmd; 397 char *bam_subcmd; 398 } cmd_map[] = { 399 { "update-archive", BAM_ARCHIVE, "update"}, 400 { "list-archive", BAM_ARCHIVE, "list"}, 401 { "set-menu", BAM_MENU, "set_option"}, 402 { "list-menu", BAM_MENU, "list_entry"}, 403 { "update-menu", BAM_MENU, "update_entry"}, 404 { NULL, 0, NULL} 405 }; 406 407 /* 408 * Commands syntax published in bootadm(1M) are parsed here 409 */ 410 static void 411 parse_args(int argc, char *argv[]) 412 { 413 struct cmd_map *cmp = cmd_map; 414 415 /* command conforming to the final spec */ 416 if (argc > 1 && argv[1][0] != '-') { 417 /* 418 * Map commands to internal table. 419 */ 420 while (cmp->bam_cmdname) { 421 if (strcmp(argv[1], cmp->bam_cmdname) == 0) { 422 bam_cmd = cmp->bam_cmd; 423 bam_subcmd = cmp->bam_subcmd; 424 break; 425 } 426 cmp++; 427 } 428 if (cmp->bam_cmdname == NULL) { 429 usage(); 430 bam_exit(1); 431 } 432 argc--; 433 argv++; 434 } 435 436 parse_args_internal(argc, argv); 437 } 438 439 /* 440 * A combination of public and private commands are parsed here. 441 * The internal syntax and the corresponding functionality are: 442 * -a update -- update-archive 443 * -a list -- list-archive 444 * -a update-all -- (reboot to sync all mounted OS archive) 445 * -m update_entry -- update-menu 446 * -m list_entry -- list-menu 447 * -m update_temp -- (reboot -- [boot-args]) 448 * -m delete_all_entries -- (called from install) 449 */ 450 static void 451 parse_args_internal(int argc, char *argv[]) 452 { 453 int c, error; 454 extern char *optarg; 455 extern int optind, opterr; 456 457 /* Suppress error message from getopt */ 458 opterr = 0; 459 460 error = 0; 461 while ((c = getopt(argc, argv, "a:d:fm:no:vR:")) != -1) { 462 switch (c) { 463 case 'a': 464 if (bam_cmd) { 465 error = 1; 466 bam_error(MULT_CMDS, c); 467 } 468 bam_cmd = BAM_ARCHIVE; 469 bam_subcmd = optarg; 470 break; 471 case 'd': 472 if (bam_debug) { 473 error = 1; 474 bam_error(DUP_OPT, c); 475 } 476 bam_debug = s_strtol(optarg); 477 break; 478 case 'f': 479 if (bam_force) { 480 error = 1; 481 bam_error(DUP_OPT, c); 482 } 483 bam_force = 1; 484 break; 485 case 'm': 486 if (bam_cmd) { 487 error = 1; 488 bam_error(MULT_CMDS, c); 489 } 490 bam_cmd = BAM_MENU; 491 bam_subcmd = optarg; 492 break; 493 case 'n': 494 if (bam_check) { 495 error = 1; 496 bam_error(DUP_OPT, c); 497 } 498 bam_check = 1; 499 bam_smf_check = bam_root_readonly; 500 break; 501 case 'o': 502 if (bam_opt) { 503 error = 1; 504 bam_error(DUP_OPT, c); 505 } 506 bam_opt = optarg; 507 break; 508 case 'v': 509 if (bam_verbose) { 510 error = 1; 511 bam_error(DUP_OPT, c); 512 } 513 bam_verbose = 1; 514 break; 515 case 'R': 516 if (bam_root) { 517 error = 1; 518 bam_error(DUP_OPT, c); 519 break; 520 } else if (realpath(optarg, rootbuf) == NULL) { 521 error = 1; 522 bam_error(CANT_RESOLVE, optarg, 523 strerror(errno)); 524 break; 525 } 526 bam_root = rootbuf; 527 if (rootbuf[strlen(rootbuf) - 1] != '/') 528 (void) strcat(rootbuf, "/"); 529 bam_rootlen = strlen(rootbuf); 530 break; 531 case '?': 532 error = 1; 533 bam_error(BAD_OPT, optopt); 534 break; 535 default : 536 error = 1; 537 bam_error(BAD_OPT, c); 538 break; 539 } 540 } 541 542 /* 543 * A command option must be specfied 544 */ 545 if (!bam_cmd) { 546 if (bam_opt && strcmp(bam_opt, "all") == 0) { 547 usage(); 548 bam_exit(0); 549 } 550 bam_error(NEED_CMD); 551 error = 1; 552 } 553 554 if (error) { 555 usage(); 556 bam_exit(1); 557 } 558 559 if (optind > argc) { 560 bam_error(INT_ERROR, "parse_args"); 561 bam_exit(1); 562 } else if (optind < argc) { 563 bam_argv = &argv[optind]; 564 bam_argc = argc - optind; 565 } 566 567 /* 568 * -n implies verbose mode 569 */ 570 if (bam_check) 571 bam_verbose = 1; 572 } 573 574 static error_t 575 check_subcmd_and_options( 576 char *subcmd, 577 char *opt, 578 subcmd_defn_t *table, 579 error_t (**fp)()) 580 { 581 int i; 582 583 if (subcmd == NULL) { 584 bam_error(NEED_SUBCMD); 585 return (BAM_ERROR); 586 } 587 588 if (bam_root == NULL) { 589 bam_root = rootbuf; 590 bam_rootlen = 1; 591 } 592 593 /* verify that subcmd is valid */ 594 for (i = 0; table[i].subcmd != NULL; i++) { 595 if (strcmp(table[i].subcmd, subcmd) == 0) 596 break; 597 } 598 599 if (table[i].subcmd == NULL) { 600 bam_error(INVALID_SUBCMD, subcmd); 601 return (BAM_ERROR); 602 } 603 604 /* subcmd verifies that opt is appropriate */ 605 if (table[i].option != OPT_OPTIONAL) { 606 if ((table[i].option == OPT_REQ) ^ (opt != NULL)) { 607 if (opt) 608 bam_error(NO_OPT_REQ, subcmd); 609 else 610 bam_error(MISS_OPT, subcmd); 611 return (BAM_ERROR); 612 } 613 } 614 615 *fp = table[i].handler; 616 617 return (BAM_SUCCESS); 618 } 619 620 static error_t 621 bam_menu(char *subcmd, char *opt, int largc, char *largv[]) 622 { 623 error_t ret; 624 char menu_path[PATH_MAX]; 625 menu_t *menu; 626 error_t (*f)(menu_t *mp, char *menu_path, char *opt); 627 628 /* 629 * Check arguments 630 */ 631 ret = check_subcmd_and_options(subcmd, opt, menu_subcmds, &f); 632 if (ret == BAM_ERROR) { 633 return (BAM_ERROR); 634 } 635 636 (void) snprintf(menu_path, sizeof (menu_path), "%s%s", 637 bam_root, GRUB_MENU); 638 639 menu = menu_read(menu_path); 640 assert(menu); 641 642 /* 643 * Special handling for setting timeout and default 644 */ 645 if (strcmp(subcmd, "set_option") == 0) { 646 if (largc != 1 || largv[0] == NULL) { 647 usage(); 648 return (BAM_ERROR); 649 } 650 opt = largv[0]; 651 } else if (largc != 0) { 652 usage(); 653 return (BAM_ERROR); 654 } 655 656 /* 657 * Once the sub-cmd handler has run 658 * only the line field is guaranteed to have valid values 659 */ 660 if (strcmp(subcmd, "update_entry") == 0) 661 ret = f(menu, bam_root, opt); 662 else 663 ret = f(menu, menu_path, opt); 664 if (ret == BAM_WRITE) { 665 ret = menu_write(bam_root, menu); 666 } 667 668 menu_free(menu); 669 670 return (ret); 671 } 672 673 674 static error_t 675 bam_archive( 676 char *subcmd, 677 char *opt) 678 { 679 error_t ret; 680 error_t (*f)(char *root, char *opt); 681 682 /* 683 * Check arguments 684 */ 685 ret = check_subcmd_and_options(subcmd, opt, arch_subcmds, &f); 686 if (ret != BAM_SUCCESS) { 687 return (BAM_ERROR); 688 } 689 690 #if defined(__sparc) 691 /* 692 * A NOP if called on SPARC during reboot 693 */ 694 if (strcmp(subcmd, "update_all") == 0) 695 return (BAM_SUCCESS); 696 else if (strcmp(subcmd, "update") != 0) 697 sparc_abort(); 698 #endif 699 700 /* 701 * Check archive not supported with update_all 702 * since it is awkward to display out-of-sync 703 * information for each BE. 704 */ 705 if (bam_check && strcmp(subcmd, "update_all") == 0) { 706 bam_error(CHECK_NOT_SUPPORTED, subcmd); 707 return (BAM_ERROR); 708 } 709 710 return (f(bam_root, opt)); 711 } 712 713 /*PRINTFLIKE1*/ 714 static void 715 bam_error(char *format, ...) 716 { 717 va_list ap; 718 719 va_start(ap, format); 720 (void) fprintf(stderr, "%s: ", prog); 721 (void) vfprintf(stderr, format, ap); 722 va_end(ap); 723 } 724 725 /*PRINTFLIKE1*/ 726 static void 727 bam_print(char *format, ...) 728 { 729 va_list ap; 730 731 va_start(ap, format); 732 (void) vfprintf(stdout, format, ap); 733 va_end(ap); 734 } 735 736 static void 737 bam_exit(int excode) 738 { 739 bam_unlock(); 740 exit(excode); 741 } 742 743 static void 744 bam_lock(void) 745 { 746 struct flock lock; 747 pid_t pid; 748 749 bam_lock_fd = open(BAM_LOCK_FILE, O_CREAT|O_RDWR, LOCK_FILE_PERMS); 750 if (bam_lock_fd < 0) { 751 /* 752 * We may be invoked early in boot for archive verification. 753 * In this case, root is readonly and /var/run may not exist. 754 * Proceed without the lock 755 */ 756 if (errno == EROFS || errno == ENOENT) { 757 bam_root_readonly = 1; 758 return; 759 } 760 761 bam_error(OPEN_FAIL, BAM_LOCK_FILE, strerror(errno)); 762 bam_exit(1); 763 } 764 765 lock.l_type = F_WRLCK; 766 lock.l_whence = SEEK_SET; 767 lock.l_start = 0; 768 lock.l_len = 0; 769 770 if (fcntl(bam_lock_fd, F_SETLK, &lock) == -1) { 771 if (errno != EACCES && errno != EAGAIN) { 772 bam_error(LOCK_FAIL, BAM_LOCK_FILE, strerror(errno)); 773 (void) close(bam_lock_fd); 774 bam_lock_fd = -1; 775 bam_exit(1); 776 } 777 pid = 0; 778 (void) pread(bam_lock_fd, &pid, sizeof (pid_t), 0); 779 bam_print(FILE_LOCKED, pid); 780 781 lock.l_type = F_WRLCK; 782 lock.l_whence = SEEK_SET; 783 lock.l_start = 0; 784 lock.l_len = 0; 785 if (fcntl(bam_lock_fd, F_SETLKW, &lock) == -1) { 786 bam_error(LOCK_FAIL, BAM_LOCK_FILE, strerror(errno)); 787 (void) close(bam_lock_fd); 788 bam_lock_fd = -1; 789 bam_exit(1); 790 } 791 } 792 793 /* We own the lock now */ 794 pid = getpid(); 795 (void) write(bam_lock_fd, &pid, sizeof (pid)); 796 } 797 798 static void 799 bam_unlock(void) 800 { 801 struct flock unlock; 802 803 /* 804 * NOP if we don't hold the lock 805 */ 806 if (bam_lock_fd < 0) { 807 return; 808 } 809 810 unlock.l_type = F_UNLCK; 811 unlock.l_whence = SEEK_SET; 812 unlock.l_start = 0; 813 unlock.l_len = 0; 814 815 if (fcntl(bam_lock_fd, F_SETLK, &unlock) == -1) { 816 bam_error(UNLOCK_FAIL, BAM_LOCK_FILE, strerror(errno)); 817 } 818 819 if (close(bam_lock_fd) == -1) { 820 bam_error(CLOSE_FAIL, BAM_LOCK_FILE, strerror(errno)); 821 } 822 bam_lock_fd = -1; 823 } 824 825 static error_t 826 list_archive(char *root, char *opt) 827 { 828 filelist_t flist; 829 filelist_t *flistp = &flist; 830 line_t *lp; 831 832 assert(root); 833 assert(opt == NULL); 834 835 flistp->head = flistp->tail = NULL; 836 if (read_list(root, flistp) != BAM_SUCCESS) { 837 return (BAM_ERROR); 838 } 839 assert(flistp->head && flistp->tail); 840 841 for (lp = flistp->head; lp; lp = lp->next) { 842 bam_print(PRINT, lp->line); 843 } 844 845 filelist_free(flistp); 846 847 return (BAM_SUCCESS); 848 } 849 850 /* 851 * This routine writes a list of lines to a file. 852 * The list is *not* freed 853 */ 854 static error_t 855 list2file(char *root, char *tmp, char *final, line_t *start) 856 { 857 char tmpfile[PATH_MAX]; 858 char path[PATH_MAX]; 859 FILE *fp; 860 int ret; 861 struct stat sb; 862 mode_t mode; 863 uid_t root_uid; 864 gid_t sys_gid; 865 struct passwd *pw; 866 struct group *gp; 867 868 869 (void) snprintf(path, sizeof (path), "%s%s", root, final); 870 871 if (start == NULL) { 872 if (stat(path, &sb) != -1) { 873 bam_print(UNLINK_EMPTY, path); 874 if (unlink(path) != 0) { 875 bam_error(UNLINK_FAIL, path, strerror(errno)); 876 return (BAM_ERROR); 877 } else { 878 return (BAM_SUCCESS); 879 } 880 } 881 } 882 883 /* 884 * Preserve attributes of existing file if possible, 885 * otherwise ask the system for uid/gid of root/sys. 886 * If all fails, fall back on hard-coded defaults. 887 */ 888 if (stat(path, &sb) != -1) { 889 mode = sb.st_mode; 890 root_uid = sb.st_uid; 891 sys_gid = sb.st_gid; 892 } else { 893 mode = DEFAULT_DEV_MODE; 894 if ((pw = getpwnam(DEFAULT_DEV_USER)) != NULL) { 895 root_uid = pw->pw_uid; 896 } else { 897 if (bam_verbose) 898 bam_error(CANT_FIND_USER, 899 DEFAULT_DEV_USER, DEFAULT_DEV_UID); 900 root_uid = (uid_t)DEFAULT_DEV_UID; 901 } 902 if ((gp = getgrnam(DEFAULT_DEV_GROUP)) != NULL) { 903 sys_gid = gp->gr_gid; 904 } else { 905 if (bam_verbose) 906 bam_error(CANT_FIND_GROUP, 907 DEFAULT_DEV_GROUP, DEFAULT_DEV_GID); 908 sys_gid = (gid_t)DEFAULT_DEV_GID; 909 } 910 } 911 912 (void) snprintf(tmpfile, sizeof (tmpfile), "%s%s", root, tmp); 913 914 /* Truncate tmpfile first */ 915 fp = fopen(tmpfile, "w"); 916 if (fp == NULL) { 917 bam_error(OPEN_FAIL, tmpfile, strerror(errno)); 918 return (BAM_ERROR); 919 } 920 ret = fclose(fp); 921 if (ret == EOF) { 922 bam_error(CLOSE_FAIL, tmpfile, strerror(errno)); 923 return (BAM_ERROR); 924 } 925 926 /* Now open it in append mode */ 927 fp = fopen(tmpfile, "a"); 928 if (fp == NULL) { 929 bam_error(OPEN_FAIL, tmpfile, strerror(errno)); 930 return (BAM_ERROR); 931 } 932 933 for (; start; start = start->next) { 934 ret = s_fputs(start->line, fp); 935 if (ret == EOF) { 936 bam_error(WRITE_FAIL, tmpfile, strerror(errno)); 937 (void) fclose(fp); 938 return (BAM_ERROR); 939 } 940 } 941 942 ret = fclose(fp); 943 if (ret == EOF) { 944 bam_error(CLOSE_FAIL, tmpfile, strerror(errno)); 945 return (BAM_ERROR); 946 } 947 948 /* 949 * Set up desired attributes 950 */ 951 ret = chmod(tmpfile, mode); 952 if (ret == -1) { 953 bam_error(CHMOD_FAIL, tmpfile, strerror(errno)); 954 return (BAM_ERROR); 955 } 956 957 ret = chown(tmpfile, root_uid, sys_gid); 958 if (ret == -1) { 959 bam_error(CHOWN_FAIL, tmpfile, strerror(errno)); 960 return (BAM_ERROR); 961 } 962 963 964 /* 965 * Do an atomic rename 966 */ 967 ret = rename(tmpfile, path); 968 if (ret != 0) { 969 bam_error(RENAME_FAIL, path, strerror(errno)); 970 return (BAM_ERROR); 971 } 972 973 return (BAM_SUCCESS); 974 } 975 976 /* 977 * This function should always return 0 - since we want 978 * to create stat data for *all* files in the list. 979 */ 980 /*ARGSUSED*/ 981 static int 982 cmpstat( 983 const char *file, 984 const struct stat *stat, 985 int flags, 986 struct FTW *ftw) 987 { 988 uint_t sz; 989 uint64_t *value; 990 uint64_t filestat[2]; 991 int error; 992 993 /* 994 * We only want regular files 995 */ 996 if (!S_ISREG(stat->st_mode)) 997 return (0); 998 999 /* 1000 * new_nvlp may be NULL if there were errors earlier 1001 * but this is not fatal to update determination. 1002 */ 1003 if (walk_arg.new_nvlp) { 1004 filestat[0] = stat->st_size; 1005 filestat[1] = stat->st_mtime; 1006 error = nvlist_add_uint64_array(walk_arg.new_nvlp, 1007 file + bam_rootlen, filestat, 2); 1008 if (error) 1009 bam_error(NVADD_FAIL, file, strerror(error)); 1010 } 1011 1012 /* 1013 * The remaining steps are only required if we haven't made a 1014 * decision about update or if we are checking (-n) 1015 */ 1016 if (walk_arg.need_update && !bam_check) 1017 return (0); 1018 1019 /* 1020 * If we are invoked as part of system/filesyste/boot-archive 1021 * SMF service, ignore amd64 modules unless we are booted amd64. 1022 */ 1023 if (bam_smf_check && !is_amd64() && strstr(file, "/amd64/") == 0) 1024 return (0); 1025 1026 /* 1027 * We need an update if file doesn't exist in old archive 1028 */ 1029 if (walk_arg.old_nvlp == NULL || 1030 nvlist_lookup_uint64_array(walk_arg.old_nvlp, 1031 file + bam_rootlen, &value, &sz) != 0) { 1032 if (bam_smf_check) /* ignore new during smf check */ 1033 return (0); 1034 walk_arg.need_update = 1; 1035 if (bam_verbose) 1036 bam_print(PARSEABLE_NEW_FILE, file); 1037 return (0); 1038 } 1039 1040 /* 1041 * File exists in old archive. Check if file has changed 1042 */ 1043 assert(sz == 2); 1044 bcopy(value, filestat, sizeof (filestat)); 1045 1046 if (filestat[0] != stat->st_size || 1047 filestat[1] != stat->st_mtime) { 1048 walk_arg.need_update = 1; 1049 if (bam_verbose) 1050 if (bam_smf_check) 1051 bam_print(" %s\n", file); 1052 else 1053 bam_print(PARSEABLE_OUT_DATE, file); 1054 } 1055 1056 return (0); 1057 } 1058 1059 /* 1060 * Check flags and presence of required files. 1061 * The force flag and/or absence of files should 1062 * trigger an update. 1063 * Suppress stdout output if check (-n) option is set 1064 * (as -n should only produce parseable output.) 1065 */ 1066 static void 1067 check_flags_and_files(char *root) 1068 { 1069 char path[PATH_MAX]; 1070 struct stat sb; 1071 1072 /* 1073 * if force, create archive unconditionally 1074 */ 1075 if (bam_force) { 1076 walk_arg.need_update = 1; 1077 if (bam_verbose && !bam_check) 1078 bam_print(UPDATE_FORCE); 1079 return; 1080 } 1081 1082 /* 1083 * If archive is missing, create archive 1084 */ 1085 (void) snprintf(path, sizeof (path), "%s%s", root, BOOT_ARCHIVE); 1086 if (stat(path, &sb) != 0) { 1087 if (bam_verbose && !bam_check) 1088 bam_print(UPDATE_ARCH_MISS, path); 1089 walk_arg.need_update = 1; 1090 return; 1091 } 1092 } 1093 1094 static error_t 1095 read_one_list(char *root, filelist_t *flistp, char *filelist) 1096 { 1097 char path[PATH_MAX]; 1098 FILE *fp; 1099 char buf[BAM_MAXLINE]; 1100 1101 (void) snprintf(path, sizeof (path), "%s%s", root, filelist); 1102 1103 fp = fopen(path, "r"); 1104 if (fp == NULL) { 1105 if (bam_debug) 1106 bam_error(FLIST_FAIL, path, strerror(errno)); 1107 return (BAM_ERROR); 1108 } 1109 while (s_fgets(buf, sizeof (buf), fp) != NULL) { 1110 append_to_flist(flistp, buf); 1111 } 1112 if (fclose(fp) != 0) { 1113 bam_error(CLOSE_FAIL, path, strerror(errno)); 1114 return (BAM_ERROR); 1115 } 1116 return (BAM_SUCCESS); 1117 } 1118 1119 static error_t 1120 read_list(char *root, filelist_t *flistp) 1121 { 1122 int rval; 1123 1124 flistp->head = flistp->tail = NULL; 1125 1126 /* 1127 * Read current lists of files - only the first is mandatory 1128 */ 1129 rval = read_one_list(root, flistp, BOOT_FILE_LIST); 1130 if (rval != BAM_SUCCESS) 1131 return (rval); 1132 (void) read_one_list(root, flistp, ETC_FILE_LIST); 1133 1134 if (flistp->head == NULL) { 1135 bam_error(NO_FLIST); 1136 return (BAM_ERROR); 1137 } 1138 1139 return (BAM_SUCCESS); 1140 } 1141 1142 static void 1143 getoldstat(char *root) 1144 { 1145 char path[PATH_MAX]; 1146 int fd, error; 1147 struct stat sb; 1148 char *ostat; 1149 1150 (void) snprintf(path, sizeof (path), "%s%s", root, FILE_STAT); 1151 fd = open(path, O_RDONLY); 1152 if (fd == -1) { 1153 if (bam_verbose) 1154 bam_print(OPEN_FAIL, path, strerror(errno)); 1155 walk_arg.need_update = 1; 1156 return; 1157 } 1158 1159 if (fstat(fd, &sb) != 0) { 1160 bam_error(STAT_FAIL, path, strerror(errno)); 1161 (void) close(fd); 1162 walk_arg.need_update = 1; 1163 return; 1164 } 1165 1166 ostat = s_calloc(1, sb.st_size); 1167 1168 if (read(fd, ostat, sb.st_size) != sb.st_size) { 1169 bam_error(READ_FAIL, path, strerror(errno)); 1170 (void) close(fd); 1171 free(ostat); 1172 walk_arg.need_update = 1; 1173 return; 1174 } 1175 1176 (void) close(fd); 1177 1178 walk_arg.old_nvlp = NULL; 1179 error = nvlist_unpack(ostat, sb.st_size, &walk_arg.old_nvlp, 0); 1180 1181 free(ostat); 1182 1183 if (error) { 1184 bam_error(UNPACK_FAIL, path, strerror(error)); 1185 walk_arg.old_nvlp = NULL; 1186 walk_arg.need_update = 1; 1187 return; 1188 } 1189 } 1190 1191 static void 1192 create_newstat(void) 1193 { 1194 int error; 1195 1196 error = nvlist_alloc(&walk_arg.new_nvlp, NV_UNIQUE_NAME, 0); 1197 if (error) { 1198 /* 1199 * Not fatal - we can still create archive 1200 */ 1201 walk_arg.new_nvlp = NULL; 1202 bam_error(NVALLOC_FAIL, strerror(error)); 1203 } 1204 } 1205 1206 static void 1207 walk_list(char *root, filelist_t *flistp) 1208 { 1209 char path[PATH_MAX]; 1210 line_t *lp; 1211 1212 for (lp = flistp->head; lp; lp = lp->next) { 1213 (void) snprintf(path, sizeof (path), "%s%s", root, lp->line); 1214 /* XXX shouldn't we use FTW_MOUNT ? */ 1215 if (nftw(path, cmpstat, 20, 0) == -1) { 1216 /* 1217 * Some files may not exist. 1218 * For example: etc/rtc_config on a x86 diskless system 1219 * Emit verbose message only 1220 */ 1221 if (bam_verbose) 1222 bam_print(NFTW_FAIL, path, strerror(errno)); 1223 } 1224 } 1225 } 1226 1227 static void 1228 savenew(char *root) 1229 { 1230 char path[PATH_MAX]; 1231 char path2[PATH_MAX]; 1232 size_t sz; 1233 char *nstat; 1234 int fd, wrote, error; 1235 1236 nstat = NULL; 1237 sz = 0; 1238 error = nvlist_pack(walk_arg.new_nvlp, &nstat, &sz, 1239 NV_ENCODE_XDR, 0); 1240 if (error) { 1241 bam_error(PACK_FAIL, strerror(error)); 1242 return; 1243 } 1244 1245 (void) snprintf(path, sizeof (path), "%s%s", root, FILE_STAT_TMP); 1246 fd = open(path, O_RDWR|O_CREAT|O_TRUNC, FILE_STAT_MODE); 1247 if (fd == -1) { 1248 bam_error(OPEN_FAIL, path, strerror(errno)); 1249 free(nstat); 1250 return; 1251 } 1252 wrote = write(fd, nstat, sz); 1253 if (wrote != sz) { 1254 bam_error(WRITE_FAIL, path, strerror(errno)); 1255 (void) close(fd); 1256 free(nstat); 1257 return; 1258 } 1259 (void) close(fd); 1260 free(nstat); 1261 1262 (void) snprintf(path2, sizeof (path2), "%s%s", root, FILE_STAT); 1263 if (rename(path, path2) != 0) { 1264 bam_error(RENAME_FAIL, path2, strerror(errno)); 1265 } 1266 } 1267 1268 static void 1269 clear_walk_args(void) 1270 { 1271 if (walk_arg.old_nvlp) 1272 nvlist_free(walk_arg.old_nvlp); 1273 if (walk_arg.new_nvlp) 1274 nvlist_free(walk_arg.new_nvlp); 1275 walk_arg.need_update = 0; 1276 walk_arg.old_nvlp = NULL; 1277 walk_arg.new_nvlp = NULL; 1278 } 1279 1280 /* 1281 * Returns: 1282 * 0 - no update necessary 1283 * 1 - update required. 1284 * BAM_ERROR (-1) - An error occurred 1285 * 1286 * Special handling for check (-n): 1287 * ================================ 1288 * The check (-n) option produces parseable output. 1289 * To do this, we suppress all stdout messages unrelated 1290 * to out of sync files. 1291 * All stderr messages are still printed though. 1292 * 1293 */ 1294 static int 1295 update_required(char *root) 1296 { 1297 struct stat sb; 1298 char path[PATH_MAX]; 1299 filelist_t flist; 1300 filelist_t *flistp = &flist; 1301 int need_update; 1302 1303 flistp->head = flistp->tail = NULL; 1304 1305 walk_arg.need_update = 0; 1306 1307 /* 1308 * Without consulting stat data, check if we need update 1309 */ 1310 check_flags_and_files(root); 1311 1312 /* 1313 * In certain deployment scenarios, filestat may not 1314 * exist. Ignore it during boot-archive SMF check. 1315 */ 1316 if (bam_smf_check) { 1317 (void) snprintf(path, sizeof (path), "%s%s", root, FILE_STAT); 1318 if (stat(path, &sb) != 0) 1319 return (0); 1320 } 1321 1322 /* 1323 * consult stat data only if we haven't made a decision 1324 * about update. If checking (-n) however, we always 1325 * need stat data (since we want to compare old and new) 1326 */ 1327 if (!walk_arg.need_update || bam_check) 1328 getoldstat(root); 1329 1330 /* 1331 * read list of files 1332 */ 1333 if (read_list(root, flistp) != BAM_SUCCESS) { 1334 clear_walk_args(); 1335 return (BAM_ERROR); 1336 } 1337 1338 assert(flistp->head && flistp->tail); 1339 1340 /* 1341 * At this point either the update is required 1342 * or the decision is pending. In either case 1343 * we need to create new stat nvlist 1344 */ 1345 create_newstat(); 1346 1347 /* 1348 * This walk does 2 things: 1349 * - gets new stat data for every file 1350 * - (optional) compare old and new stat data 1351 */ 1352 walk_list(root, &flist); 1353 1354 /* done with the file list */ 1355 filelist_free(flistp); 1356 1357 /* 1358 * if we didn't succeed in creating new stat data above 1359 * just return result of update check so that archive is built. 1360 */ 1361 if (walk_arg.new_nvlp == NULL) { 1362 bam_error(NO_NEW_STAT); 1363 need_update = walk_arg.need_update; 1364 clear_walk_args(); 1365 return (need_update ? 1 : 0); 1366 } 1367 1368 1369 /* 1370 * If no update required, discard newstat 1371 */ 1372 if (!walk_arg.need_update) { 1373 clear_walk_args(); 1374 return (0); 1375 } 1376 1377 /* 1378 * At this point we need an update - so save new stat data 1379 * However, if only checking (-n), don't save new stat data. 1380 */ 1381 if (!bam_check) 1382 savenew(root); 1383 1384 clear_walk_args(); 1385 1386 return (1); 1387 } 1388 1389 static error_t 1390 create_ramdisk(char *root) 1391 { 1392 char *cmdline, path[PATH_MAX]; 1393 size_t len; 1394 struct stat sb; 1395 1396 /* 1397 * Setup command args for create_ramdisk.ksh 1398 */ 1399 (void) snprintf(path, sizeof (path), "%s%s", root, CREATE_RAMDISK); 1400 if (stat(path, &sb) != 0) { 1401 bam_error(ARCH_EXEC_MISS, path, strerror(errno)); 1402 return (BAM_ERROR); 1403 } 1404 1405 len = strlen(path) + strlen(root) + 10; /* room for space + -R */ 1406 cmdline = s_calloc(1, len); 1407 1408 if (strlen(root) > 1) { 1409 (void) snprintf(cmdline, len, "%s -R %s", path, root); 1410 /* chop off / at the end */ 1411 cmdline[strlen(cmdline) - 1] = '\0'; 1412 } else 1413 (void) snprintf(cmdline, len, "%s", path); 1414 1415 if (exec_cmd(cmdline, NULL, 0) != 0) { 1416 bam_error(ARCHIVE_FAIL, cmdline); 1417 free(cmdline); 1418 return (BAM_ERROR); 1419 } 1420 free(cmdline); 1421 1422 /* 1423 * Verify that the archive has been created 1424 */ 1425 (void) snprintf(path, sizeof (path), "%s%s", root, BOOT_ARCHIVE); 1426 if (stat(path, &sb) != 0) { 1427 bam_error(ARCHIVE_NOT_CREATED, path); 1428 return (BAM_ERROR); 1429 } 1430 1431 return (BAM_SUCCESS); 1432 } 1433 1434 /* 1435 * Checks if target filesystem is on a ramdisk 1436 * 1 - is miniroot 1437 * 0 - is not 1438 * When in doubt assume it is not a ramdisk. 1439 */ 1440 static int 1441 is_ramdisk(char *root) 1442 { 1443 struct extmnttab mnt; 1444 FILE *fp; 1445 int found; 1446 1447 /* 1448 * There are 3 situations where creating archive is 1449 * of dubious value: 1450 * - create boot_archive on a boot_archive 1451 * - create it on a ramdisk which is the root filesystem 1452 * - create it on a ramdisk mounted somewhere else 1453 * The first is not easy to detect and checking for it is not 1454 * worth it. 1455 * The other two conditions are handled here 1456 */ 1457 1458 fp = fopen(MNTTAB, "r"); 1459 if (fp == NULL) { 1460 bam_error(OPEN_FAIL, MNTTAB, strerror(errno)); 1461 return (0); 1462 } 1463 1464 resetmnttab(fp); 1465 1466 found = 0; 1467 while (getextmntent(fp, &mnt, sizeof (mnt)) == 0) { 1468 if (strcmp(mnt.mnt_mountp, root) == 0) { 1469 found = 1; 1470 break; 1471 } 1472 } 1473 1474 if (!found) { 1475 if (bam_verbose) 1476 bam_error(NOT_IN_MNTTAB, root); 1477 (void) fclose(fp); 1478 return (0); 1479 } 1480 1481 if (strstr(mnt.mnt_special, RAMDISK_SPECIAL) != NULL) { 1482 if (bam_verbose) 1483 bam_error(IS_RAMDISK, bam_root); 1484 (void) fclose(fp); 1485 return (1); 1486 } 1487 1488 (void) fclose(fp); 1489 1490 return (0); 1491 } 1492 1493 static int 1494 is_newboot(char *root) 1495 { 1496 char path[PATH_MAX]; 1497 struct stat sb; 1498 1499 /* 1500 * We can't boot without MULTI_BOOT 1501 */ 1502 (void) snprintf(path, sizeof (path), "%s%s", root, MULTI_BOOT); 1503 if (stat(path, &sb) == -1) { 1504 if (bam_verbose) 1505 bam_print(FILE_MISS, path); 1506 return (0); 1507 } 1508 1509 /* 1510 * We can't generate archive without GRUB_DIR 1511 */ 1512 (void) snprintf(path, sizeof (path), "%s%s", root, GRUB_DIR); 1513 if (stat(path, &sb) == -1) { 1514 if (bam_verbose) 1515 bam_print(DIR_MISS, path); 1516 return (0); 1517 } 1518 1519 return (1); 1520 } 1521 1522 static int 1523 is_readonly(char *root) 1524 { 1525 struct statvfs vfs; 1526 1527 /* 1528 * Check for RDONLY filesystem 1529 * When in doubt assume it is not readonly 1530 */ 1531 if (statvfs(root, &vfs) != 0) { 1532 if (bam_verbose) 1533 bam_error(STATVFS_FAIL, root, strerror(errno)); 1534 return (0); 1535 } 1536 1537 if (vfs.f_flag & ST_RDONLY) { 1538 return (1); 1539 } 1540 1541 return (0); 1542 } 1543 1544 static error_t 1545 update_archive(char *root, char *opt) 1546 { 1547 error_t ret; 1548 1549 assert(root); 1550 assert(opt == NULL); 1551 1552 /* 1553 * root must belong to a newboot OS, 1554 * don't care on sparc except for diskless clients 1555 */ 1556 if (!is_newboot(root)) { 1557 if (bam_verbose) 1558 bam_print(NOT_NEWBOOT); 1559 return (BAM_SUCCESS); 1560 } 1561 1562 /* 1563 * root must be writable 1564 * Note: statvfs() does not always report the truth 1565 */ 1566 if (is_readonly(root)) { 1567 if (!bam_smf_check && bam_verbose) 1568 bam_print(RDONLY_FS, root); 1569 return (BAM_SUCCESS); 1570 } 1571 1572 /* 1573 * Don't generate archive on ramdisk 1574 */ 1575 if (is_ramdisk(root)) { 1576 if (bam_verbose) 1577 bam_print(SKIP_RAMDISK); 1578 return (BAM_SUCCESS); 1579 } 1580 1581 /* 1582 * Now check if updated is really needed 1583 */ 1584 ret = update_required(root); 1585 1586 /* 1587 * The check command (-n) is *not* a dry run 1588 * It only checks if the archive is in sync. 1589 */ 1590 if (bam_check) { 1591 bam_exit((ret != 0) ? 1 : 0); 1592 } 1593 1594 if (ret == 1) { 1595 /* create the ramdisk */ 1596 ret = create_ramdisk(root); 1597 } 1598 return (ret); 1599 } 1600 1601 static error_t 1602 update_all(char *root, char *opt) 1603 { 1604 struct extmnttab mnt; 1605 struct stat sb; 1606 FILE *fp; 1607 char multibt[PATH_MAX]; 1608 error_t ret = BAM_SUCCESS; 1609 1610 assert(bam_rootlen == 1 && root[0] == '/'); 1611 assert(opt == NULL); 1612 1613 /* 1614 * First update archive for current root 1615 */ 1616 if (update_archive(root, opt) != BAM_SUCCESS) 1617 ret = BAM_ERROR; 1618 1619 /* 1620 * Now walk the mount table, performing archive update 1621 * for all mounted Newboot root filesystems 1622 */ 1623 fp = fopen(MNTTAB, "r"); 1624 if (fp == NULL) { 1625 bam_error(OPEN_FAIL, MNTTAB, strerror(errno)); 1626 return (BAM_ERROR); 1627 } 1628 1629 resetmnttab(fp); 1630 1631 while (getextmntent(fp, &mnt, sizeof (mnt)) == 0) { 1632 if (mnt.mnt_special == NULL) 1633 continue; 1634 if (strncmp(mnt.mnt_special, "/dev/", strlen("/dev/")) != 0) 1635 continue; 1636 if (strcmp(mnt.mnt_mountp, "/") == 0) 1637 continue; 1638 1639 (void) snprintf(multibt, sizeof (multibt), "%s%s", 1640 mnt.mnt_mountp, MULTI_BOOT); 1641 1642 if (stat(multibt, &sb) == -1) 1643 continue; 1644 1645 /* 1646 * We put a trailing slash to be consistent with root = "/" 1647 * case, such that we don't have to print // in some cases. 1648 */ 1649 (void) snprintf(rootbuf, sizeof (rootbuf), "%s/", 1650 mnt.mnt_mountp); 1651 bam_rootlen = strlen(rootbuf); 1652 if (update_archive(rootbuf, opt) != BAM_SUCCESS) 1653 ret = BAM_ERROR; 1654 } 1655 1656 (void) fclose(fp); 1657 1658 return (ret); 1659 } 1660 1661 static void 1662 append_line(menu_t *mp, line_t *lp) 1663 { 1664 if (mp->start == NULL) { 1665 mp->start = lp; 1666 } else { 1667 mp->end->next = lp; 1668 } 1669 mp->end = lp; 1670 } 1671 1672 /* 1673 * A line in menu.lst looks like 1674 * [ ]*<cmd>[ \t=]*<arg>* 1675 */ 1676 static void 1677 line_parser(menu_t *mp, char *str, int *lineNum, int *entryNum) 1678 { 1679 /* 1680 * save state across calls. This is so that 1681 * header gets the right entry# after title has 1682 * been processed 1683 */ 1684 static line_t *prev; 1685 1686 line_t *lp; 1687 char *cmd, *sep, *arg; 1688 char save, *cp, *line; 1689 menu_flag_t flag = BAM_INVALID; 1690 1691 if (str == NULL) { 1692 return; 1693 } 1694 1695 /* 1696 * First save a copy of the entire line. 1697 * We use this later to set the line field. 1698 */ 1699 line = s_strdup(str); 1700 1701 /* Eat up leading whitespace */ 1702 while (*str == ' ' || *str == '\t') 1703 str++; 1704 1705 if (*str == '#') { /* comment */ 1706 cmd = s_strdup("#"); 1707 sep = NULL; 1708 arg = s_strdup(str + 1); 1709 flag = BAM_COMMENT; 1710 } else if (*str == '\0') { /* blank line */ 1711 cmd = sep = arg = NULL; 1712 flag = BAM_EMPTY; 1713 } else { 1714 /* 1715 * '=' is not a documented separator in grub syntax. 1716 * However various development bits use '=' as a 1717 * separator. In addition, external users also 1718 * use = as a separator. So we will allow that usage. 1719 */ 1720 cp = str; 1721 while (*str != ' ' && *str != '\t' && *str != '=') { 1722 if (*str == '\0') { 1723 cmd = s_strdup(cp); 1724 sep = arg = NULL; 1725 break; 1726 } 1727 str++; 1728 } 1729 1730 if (*str != '\0') { 1731 save = *str; 1732 *str = '\0'; 1733 cmd = s_strdup(cp); 1734 *str = save; 1735 1736 str++; 1737 save = *str; 1738 *str = '\0'; 1739 sep = s_strdup(str - 1); 1740 *str = save; 1741 1742 while (*str == ' ' || *str == '\t') 1743 str++; 1744 if (*str == '\0') 1745 arg = NULL; 1746 else 1747 arg = s_strdup(str); 1748 } 1749 } 1750 1751 lp = s_calloc(1, sizeof (line_t)); 1752 1753 lp->cmd = cmd; 1754 lp->sep = sep; 1755 lp->arg = arg; 1756 lp->line = line; 1757 lp->lineNum = ++(*lineNum); 1758 if (cmd && strcmp(cmd, menu_cmds[TITLE_CMD]) == 0) { 1759 lp->entryNum = ++(*entryNum); 1760 lp->flags = BAM_TITLE; 1761 if (prev && prev->flags == BAM_COMMENT && 1762 prev->arg && strcmp(prev->arg, BAM_HDR) == 0) 1763 prev->entryNum = lp->entryNum; 1764 } else if (flag != BAM_INVALID) { 1765 /* 1766 * For header comments, the entry# is "fixed up" 1767 * by the subsequent title 1768 */ 1769 lp->entryNum = *entryNum; 1770 lp->flags = flag; 1771 } else { 1772 lp->entryNum = *entryNum; 1773 lp->flags = (*entryNum == ENTRY_INIT) ? BAM_GLOBAL : BAM_ENTRY; 1774 } 1775 1776 append_line(mp, lp); 1777 1778 prev = lp; 1779 } 1780 1781 static menu_t * 1782 menu_read(char *menu_path) 1783 { 1784 FILE *fp; 1785 char buf[BAM_MAXLINE], *cp; 1786 menu_t *mp; 1787 int line, entry, len, n; 1788 1789 mp = s_calloc(1, sizeof (menu_t)); 1790 1791 fp = fopen(menu_path, "r"); 1792 if (fp == NULL) { /* Let the caller handle this error */ 1793 return (mp); 1794 } 1795 1796 1797 /* Note: GRUB boot entry number starts with 0 */ 1798 line = LINE_INIT; 1799 entry = ENTRY_INIT; 1800 cp = buf; 1801 len = sizeof (buf); 1802 while (s_fgets(cp, len, fp) != NULL) { 1803 n = strlen(cp); 1804 if (cp[n - 1] == '\\') { 1805 len -= n - 1; 1806 assert(len >= 2); 1807 cp += n - 1; 1808 continue; 1809 } 1810 line_parser(mp, buf, &line, &entry); 1811 cp = buf; 1812 len = sizeof (buf); 1813 } 1814 1815 if (fclose(fp) == EOF) { 1816 bam_error(CLOSE_FAIL, menu_path, strerror(errno)); 1817 } 1818 1819 return (mp); 1820 } 1821 1822 static error_t 1823 selector(menu_t *mp, char *opt, int *entry, char **title) 1824 { 1825 char *eq; 1826 char *opt_dup; 1827 int entryNum; 1828 1829 assert(mp); 1830 assert(mp->start); 1831 assert(opt); 1832 1833 opt_dup = s_strdup(opt); 1834 1835 if (entry) 1836 *entry = ENTRY_INIT; 1837 if (title) 1838 *title = NULL; 1839 1840 eq = strchr(opt_dup, '='); 1841 if (eq == NULL) { 1842 bam_error(INVALID_OPT, opt); 1843 free(opt_dup); 1844 return (BAM_ERROR); 1845 } 1846 1847 *eq = '\0'; 1848 if (entry && strcmp(opt_dup, OPT_ENTRY_NUM) == 0) { 1849 assert(mp->end); 1850 entryNum = s_strtol(eq + 1); 1851 if (entryNum < 0 || entryNum > mp->end->entryNum) { 1852 bam_error(INVALID_ENTRY, eq + 1); 1853 free(opt_dup); 1854 return (BAM_ERROR); 1855 } 1856 *entry = entryNum; 1857 } else if (title && strcmp(opt_dup, menu_cmds[TITLE_CMD]) == 0) { 1858 *title = opt + (eq - opt_dup) + 1; 1859 } else { 1860 bam_error(INVALID_OPT, opt); 1861 free(opt_dup); 1862 return (BAM_ERROR); 1863 } 1864 1865 free(opt_dup); 1866 return (BAM_SUCCESS); 1867 } 1868 1869 /* 1870 * If invoked with no titles/entries (opt == NULL) 1871 * only title lines in file are printed. 1872 * 1873 * If invoked with a title or entry #, all 1874 * lines in *every* matching entry are listed 1875 */ 1876 static error_t 1877 list_entry(menu_t *mp, char *menu_path, char *opt) 1878 { 1879 line_t *lp; 1880 int entry = ENTRY_INIT; 1881 int found; 1882 char *title = NULL; 1883 1884 assert(mp); 1885 assert(menu_path); 1886 1887 if (mp->start == NULL) { 1888 bam_error(NO_MENU, menu_path); 1889 return (BAM_ERROR); 1890 } 1891 1892 if (opt != NULL) { 1893 if (selector(mp, opt, &entry, &title) != BAM_SUCCESS) { 1894 return (BAM_ERROR); 1895 } 1896 assert((entry != ENTRY_INIT) ^ (title != NULL)); 1897 } else { 1898 (void) read_globals(mp, menu_path, menu_cmds[DEFAULT_CMD], 0); 1899 (void) read_globals(mp, menu_path, menu_cmds[TIMEOUT_CMD], 0); 1900 } 1901 1902 found = 0; 1903 for (lp = mp->start; lp; lp = lp->next) { 1904 if (lp->flags == BAM_COMMENT || lp->flags == BAM_EMPTY) 1905 continue; 1906 if (opt == NULL && lp->flags == BAM_TITLE) { 1907 bam_print(PRINT_TITLE, lp->entryNum, 1908 lp->arg); 1909 found = 1; 1910 continue; 1911 } 1912 if (entry != ENTRY_INIT && lp->entryNum == entry) { 1913 bam_print(PRINT, lp->line); 1914 found = 1; 1915 continue; 1916 } 1917 1918 /* 1919 * We set the entry value here so that all lines 1920 * in entry get printed. If we subsequently match 1921 * title in other entries, all lines in those 1922 * entries get printed as well. 1923 */ 1924 if (title && lp->flags == BAM_TITLE && lp->arg && 1925 strncmp(title, lp->arg, strlen(title)) == 0) { 1926 bam_print(PRINT, lp->line); 1927 entry = lp->entryNum; 1928 found = 1; 1929 continue; 1930 } 1931 } 1932 1933 if (!found) { 1934 bam_error(NO_MATCH_ENTRY); 1935 return (BAM_ERROR); 1936 } 1937 1938 return (BAM_SUCCESS); 1939 } 1940 1941 static int 1942 add_boot_entry(menu_t *mp, 1943 char *title, 1944 char *root, 1945 char *kernel, 1946 char *module) 1947 { 1948 menu_t dummy; 1949 int lineNum, entryNum; 1950 char linebuf[BAM_MAXLINE]; 1951 1952 assert(mp); 1953 1954 if (title == NULL) { 1955 bam_error(SUBOPT_MISS, menu_cmds[TITLE_CMD]); 1956 return (BAM_ERROR); 1957 } 1958 if (root == NULL) { 1959 bam_error(SUBOPT_MISS, menu_cmds[ROOT_CMD]); 1960 return (BAM_ERROR); 1961 } 1962 if (kernel == NULL) { 1963 bam_error(SUBOPT_MISS, menu_cmds[KERNEL_CMD]); 1964 return (BAM_ERROR); 1965 } 1966 if (module == NULL) { 1967 bam_error(SUBOPT_MISS, menu_cmds[MODULE_CMD]); 1968 return (BAM_ERROR); 1969 } 1970 1971 if (mp->start) { 1972 lineNum = mp->end->lineNum; 1973 entryNum = mp->end->entryNum; 1974 } else { 1975 lineNum = LINE_INIT; 1976 entryNum = ENTRY_INIT; 1977 } 1978 1979 /* 1980 * No separator for comment (HDR/FTR) commands 1981 * The syntax for comments is #<comment> 1982 */ 1983 (void) snprintf(linebuf, sizeof (linebuf), "%s%s", 1984 menu_cmds[COMMENT_CMD], BAM_HDR); 1985 dummy.start = dummy.end = NULL; 1986 line_parser(&dummy, linebuf, &lineNum, &entryNum); 1987 if (dummy.start == NULL || dummy.start->flags != BAM_COMMENT) { 1988 line_free(dummy.start); 1989 bam_error(INVALID_HDR, BAM_HDR); 1990 return (BAM_ERROR); 1991 } 1992 assert(dummy.start == dummy.end); 1993 append_line(mp, dummy.start); 1994 1995 (void) snprintf(linebuf, sizeof (linebuf), "%s%s%s", 1996 menu_cmds[TITLE_CMD], menu_cmds[SEP_CMD], title); 1997 dummy.start = dummy.end = NULL; 1998 line_parser(&dummy, linebuf, &lineNum, &entryNum); 1999 if (dummy.start == NULL || dummy.start->flags != BAM_TITLE) { 2000 line_free(dummy.start); 2001 bam_error(INVALID_TITLE, title); 2002 return (BAM_ERROR); 2003 } 2004 assert(dummy.start == dummy.end); 2005 append_line(mp, dummy.start); 2006 2007 2008 (void) snprintf(linebuf, sizeof (linebuf), "%s%s%s", 2009 menu_cmds[ROOT_CMD], menu_cmds[SEP_CMD], root); 2010 dummy.start = dummy.end = NULL; 2011 line_parser(&dummy, linebuf, &lineNum, &entryNum); 2012 if (dummy.start == NULL || dummy.start->flags != BAM_ENTRY) { 2013 line_free(dummy.start); 2014 bam_error(INVALID_ROOT, root); 2015 return (BAM_ERROR); 2016 } 2017 assert(dummy.start == dummy.end); 2018 append_line(mp, dummy.start); 2019 2020 2021 (void) snprintf(linebuf, sizeof (linebuf), "%s%s%s", 2022 menu_cmds[KERNEL_CMD], menu_cmds[SEP_CMD], kernel); 2023 dummy.start = dummy.end = NULL; 2024 line_parser(&dummy, linebuf, &lineNum, &entryNum); 2025 if (dummy.start == NULL || dummy.start->flags != BAM_ENTRY) { 2026 line_free(dummy.start); 2027 bam_error(INVALID_KERNEL, kernel); 2028 return (BAM_ERROR); 2029 } 2030 assert(dummy.start == dummy.end); 2031 append_line(mp, dummy.start); 2032 2033 (void) snprintf(linebuf, sizeof (linebuf), "%s%s%s", 2034 menu_cmds[MODULE_CMD], menu_cmds[SEP_CMD], module); 2035 dummy.start = dummy.end = NULL; 2036 line_parser(&dummy, linebuf, &lineNum, &entryNum); 2037 if (dummy.start == NULL || dummy.start->flags != BAM_ENTRY) { 2038 line_free(dummy.start); 2039 bam_error(INVALID_MODULE, module); 2040 return (BAM_ERROR); 2041 } 2042 assert(dummy.start == dummy.end); 2043 append_line(mp, dummy.start); 2044 2045 (void) snprintf(linebuf, sizeof (linebuf), "%s%s", 2046 menu_cmds[COMMENT_CMD], BAM_FTR); 2047 dummy.start = dummy.end = NULL; 2048 line_parser(&dummy, linebuf, &lineNum, &entryNum); 2049 if (dummy.start == NULL || dummy.start->flags != BAM_COMMENT) { 2050 line_free(dummy.start); 2051 bam_error(INVALID_FOOTER, BAM_FTR); 2052 return (BAM_ERROR); 2053 } 2054 assert(dummy.start == dummy.end); 2055 append_line(mp, dummy.start); 2056 2057 return (entryNum); 2058 } 2059 2060 static error_t 2061 do_delete(menu_t *mp, int entryNum) 2062 { 2063 int bootadm_entry = 0; 2064 line_t *lp, *prev, *save; 2065 int deleted; 2066 2067 assert(entryNum != ENTRY_INIT); 2068 2069 deleted = 0; 2070 prev = NULL; 2071 for (lp = mp->start; lp; ) { 2072 2073 if (lp->entryNum == ENTRY_INIT) { 2074 prev = lp; 2075 lp = lp->next; 2076 continue; 2077 } 2078 2079 if (entryNum != ALL_ENTRIES && lp->entryNum != entryNum) { 2080 prev = lp; 2081 lp = lp->next; 2082 continue; 2083 } 2084 2085 /* 2086 * can only delete bootadm entries 2087 */ 2088 if (lp->flags == BAM_COMMENT && strcmp(lp->arg, BAM_HDR) == 0) { 2089 bootadm_entry = 1; 2090 } 2091 2092 if (!bootadm_entry) { 2093 prev = lp; 2094 lp = lp->next; 2095 continue; 2096 } 2097 2098 if (lp->flags == BAM_COMMENT && strcmp(lp->arg, BAM_FTR) == 0) 2099 bootadm_entry = 0; 2100 2101 if (prev == NULL) 2102 mp->start = lp->next; 2103 else 2104 prev->next = lp->next; 2105 if (mp->end == lp) 2106 mp->end = prev; 2107 save = lp->next; 2108 line_free(lp); 2109 lp = save; /* prev stays the same */ 2110 2111 deleted = 1; 2112 } 2113 2114 if (!deleted && entryNum != ALL_ENTRIES) { 2115 bam_error(NO_BOOTADM_MATCH); 2116 return (BAM_ERROR); 2117 } 2118 2119 return (BAM_SUCCESS); 2120 } 2121 2122 static error_t 2123 delete_entry(menu_t *mp, char *menu_path, char *opt) 2124 { 2125 int entry = ENTRY_INIT; 2126 char *title = NULL; 2127 line_t *lp; 2128 2129 assert(mp); 2130 assert(opt); 2131 2132 /* 2133 * Do a quick check. If the file is empty 2134 * we have nothing to delete 2135 */ 2136 if (mp->start == NULL) { 2137 bam_print(EMPTY_FILE, menu_path); 2138 return (BAM_SUCCESS); 2139 } 2140 2141 if (selector(mp, opt, &entry, &title) != BAM_SUCCESS) { 2142 return (BAM_ERROR); 2143 } 2144 assert((entry != ENTRY_INIT) ^ (title != NULL)); 2145 2146 for (lp = mp->start; lp; lp = lp->next) { 2147 if (entry != ENTRY_INIT) 2148 break; 2149 assert(title); 2150 if (lp->flags == BAM_TITLE && 2151 lp->arg && strcmp(lp->arg, title) == 0) { 2152 entry = lp->entryNum; 2153 break; 2154 } 2155 } 2156 2157 if (entry == ENTRY_INIT) { 2158 bam_error(NO_MATCH, title); 2159 return (BAM_ERROR); 2160 } 2161 2162 if (do_delete(mp, entry) != BAM_SUCCESS) { 2163 return (BAM_ERROR); 2164 } 2165 2166 return (BAM_WRITE); 2167 } 2168 2169 static error_t 2170 delete_all_entries(menu_t *mp, char *menu_path, char *opt) 2171 { 2172 assert(mp); 2173 assert(opt == NULL); 2174 2175 if (mp->start == NULL) { 2176 bam_print(EMPTY_FILE, menu_path); 2177 return (BAM_SUCCESS); 2178 } 2179 2180 if (do_delete(mp, ALL_ENTRIES) != BAM_SUCCESS) { 2181 return (BAM_ERROR); 2182 } 2183 2184 return (BAM_WRITE); 2185 } 2186 2187 static FILE * 2188 open_diskmap(void) 2189 { 2190 FILE *fp; 2191 char cmd[PATH_MAX]; 2192 2193 /* make sure we have a map file */ 2194 fp = fopen(GRUBDISK_MAP, "r"); 2195 if (fp == NULL) { 2196 (void) snprintf(cmd, sizeof (cmd), 2197 "%s > /dev/null", CREATE_DISKMAP); 2198 (void) system(cmd); 2199 fp = fopen(GRUBDISK_MAP, "r"); 2200 } 2201 return (fp); 2202 } 2203 2204 #define SECTOR_SIZE 512 2205 2206 static int 2207 get_partition(char *device) 2208 { 2209 int i, fd, is_pcfs, partno = -1; 2210 struct mboot *mboot; 2211 char boot_sect[SECTOR_SIZE]; 2212 char *wholedisk, *slice; 2213 2214 /* form whole disk (p0) */ 2215 slice = device + strlen(device) - 2; 2216 is_pcfs = (*slice != 's'); 2217 if (!is_pcfs) 2218 *slice = '\0'; 2219 wholedisk = s_calloc(1, strlen(device) + 3); 2220 (void) snprintf(wholedisk, strlen(device) + 3, "%sp0", device); 2221 if (!is_pcfs) 2222 *slice = 's'; 2223 2224 /* read boot sector */ 2225 fd = open(wholedisk, O_RDONLY); 2226 free(wholedisk); 2227 if (fd == -1 || read(fd, boot_sect, SECTOR_SIZE) != SECTOR_SIZE) { 2228 return (partno); 2229 } 2230 (void) close(fd); 2231 2232 /* parse fdisk table */ 2233 mboot = (struct mboot *)((void *)boot_sect); 2234 for (i = 0; i < FD_NUMPART; i++) { 2235 struct ipart *part = 2236 (struct ipart *)(uintptr_t)mboot->parts + i; 2237 if (is_pcfs) { /* looking for solaris boot part */ 2238 if (part->systid == 0xbe) { 2239 partno = i; 2240 break; 2241 } 2242 } else { /* look for solaris partition, old and new */ 2243 if (part->systid == SUNIXOS || 2244 part->systid == SUNIXOS2) { 2245 partno = i; 2246 break; 2247 } 2248 } 2249 } 2250 return (partno); 2251 } 2252 2253 static char * 2254 get_grubdisk(char *rootdev, FILE *fp, int on_bootdev) 2255 { 2256 char *grubdisk; /* (hd#,#,#) */ 2257 char *slice; 2258 char *grubhd; 2259 int fdiskpart; 2260 int found = 0; 2261 char *devname, *ctdname = strstr(rootdev, "dsk/"); 2262 char linebuf[PATH_MAX]; 2263 2264 if (ctdname == NULL) 2265 return (NULL); 2266 2267 ctdname += strlen("dsk/"); 2268 slice = strrchr(ctdname, 's'); 2269 if (slice) 2270 *slice = '\0'; 2271 2272 rewind(fp); 2273 while (s_fgets(linebuf, sizeof (linebuf), fp) != NULL) { 2274 grubhd = strtok(linebuf, " \t\n"); 2275 if (grubhd) 2276 devname = strtok(NULL, " \t\n"); 2277 else 2278 devname = NULL; 2279 if (devname && strcmp(devname, ctdname) == 0) { 2280 found = 1; 2281 break; 2282 } 2283 } 2284 2285 if (slice) 2286 *slice = 's'; 2287 2288 if (found == 0) { 2289 if (bam_verbose) 2290 bam_print(DISKMAP_FAIL_NONFATAL, rootdev); 2291 grubhd = "0"; /* assume disk 0 if can't match */ 2292 } 2293 2294 fdiskpart = get_partition(rootdev); 2295 if (fdiskpart == -1) 2296 return (NULL); 2297 2298 grubdisk = s_calloc(1, 10); 2299 if (slice) { 2300 (void) snprintf(grubdisk, 10, "(hd%s,%d,%c)", 2301 grubhd, fdiskpart, slice[1] + 'a' - '0'); 2302 } else 2303 (void) snprintf(grubdisk, 10, "(hd%s,%d)", 2304 grubhd, fdiskpart); 2305 2306 /* if root not on bootdev, change GRUB disk to 0 */ 2307 if (!on_bootdev) 2308 grubdisk[3] = '0'; 2309 return (grubdisk); 2310 } 2311 2312 static char *get_title(char *rootdir) 2313 { 2314 static char title[80]; /* from /etc/release */ 2315 char *cp, release[PATH_MAX]; 2316 FILE *fp; 2317 2318 /* open the /etc/release file */ 2319 (void) snprintf(release, sizeof (release), "%s/etc/release", rootdir); 2320 2321 fp = fopen(release, "r"); 2322 if (fp == NULL) 2323 return ("Solaris"); /* default to Solaris */ 2324 2325 while (s_fgets(title, sizeof (title), fp) != NULL) { 2326 cp = strstr(title, "Solaris"); 2327 if (cp) 2328 break; 2329 } 2330 (void) fclose(fp); 2331 return (cp); 2332 } 2333 2334 static char * 2335 get_special(char *mountp) 2336 { 2337 FILE *mntfp; 2338 struct mnttab mp = {0}, mpref = {0}; 2339 2340 mntfp = fopen(MNTTAB, "r"); 2341 if (mntfp == NULL) { 2342 return (0); 2343 } 2344 2345 if (*mountp == '\0') 2346 mpref.mnt_mountp = "/"; 2347 else 2348 mpref.mnt_mountp = mountp; 2349 if (getmntany(mntfp, &mp, &mpref) != 0) { 2350 (void) fclose(mntfp); 2351 return (NULL); 2352 } 2353 (void) fclose(mntfp); 2354 2355 return (s_strdup(mp.mnt_special)); 2356 } 2357 2358 static char * 2359 os_to_grubdisk(char *osdisk, int on_bootdev) 2360 { 2361 FILE *fp; 2362 char *grubdisk; 2363 2364 /* translate /dev/dsk name to grub disk name */ 2365 fp = open_diskmap(); 2366 if (fp == NULL) { 2367 bam_error(DISKMAP_FAIL, osdisk); 2368 return (NULL); 2369 } 2370 grubdisk = get_grubdisk(osdisk, fp, on_bootdev); 2371 (void) fclose(fp); 2372 return (grubdisk); 2373 } 2374 2375 /* 2376 * Check if root is on the boot device 2377 * Return 0 (false) on error 2378 */ 2379 static int 2380 menu_on_bootdev(char *menu_root, FILE *fp) 2381 { 2382 int ret; 2383 char *grubhd, *bootp, *special; 2384 2385 special = get_special(menu_root); 2386 if (special == NULL) 2387 return (0); 2388 bootp = strstr(special, "p0:boot"); 2389 if (bootp) 2390 *bootp = '\0'; 2391 grubhd = get_grubdisk(special, fp, 1); 2392 free(special); 2393 2394 if (grubhd == NULL) 2395 return (0); 2396 ret = grubhd[3] == '0'; 2397 free(grubhd); 2398 return (ret); 2399 } 2400 2401 /*ARGSUSED*/ 2402 static error_t 2403 update_entry(menu_t *mp, char *menu_root, char *opt) 2404 { 2405 FILE *fp; 2406 int entry; 2407 line_t *lp; 2408 char *grubdisk, *title, *osdev, *osroot; 2409 int bootadm_entry, entry_to_delete; 2410 2411 assert(mp); 2412 assert(opt); 2413 2414 osdev = strtok(opt, ","); 2415 osroot = strtok(NULL, ","); 2416 if (osroot == NULL) 2417 osroot = menu_root; 2418 title = get_title(osroot); 2419 2420 /* translate /dev/dsk name to grub disk name */ 2421 fp = open_diskmap(); 2422 if (fp == NULL) { 2423 bam_error(DISKMAP_FAIL, osdev); 2424 return (BAM_ERROR); 2425 } 2426 grubdisk = get_grubdisk(osdev, fp, menu_on_bootdev(menu_root, fp)); 2427 (void) fclose(fp); 2428 if (grubdisk == NULL) { 2429 bam_error(DISKMAP_FAIL, osdev); 2430 return (BAM_ERROR); 2431 } 2432 2433 /* delete existing entries with matching grub hd name */ 2434 for (;;) { 2435 entry_to_delete = -1; 2436 bootadm_entry = 0; 2437 for (lp = mp->start; lp; lp = lp->next) { 2438 /* 2439 * can only delete bootadm entries 2440 */ 2441 if (lp->flags == BAM_COMMENT) { 2442 if (strcmp(lp->arg, BAM_HDR) == 0) 2443 bootadm_entry = 1; 2444 else if (strcmp(lp->arg, BAM_FTR) == 0) 2445 bootadm_entry = 0; 2446 } 2447 2448 if (bootadm_entry && lp->flags == BAM_ENTRY && 2449 strcmp(lp->cmd, menu_cmds[ROOT_CMD]) == 0 && 2450 strcmp(lp->arg, grubdisk) == 0) { 2451 entry_to_delete = lp->entryNum; 2452 } 2453 } 2454 if (entry_to_delete == -1) 2455 break; 2456 (void) do_delete(mp, entry_to_delete); 2457 } 2458 2459 /* add the entry for normal Solaris */ 2460 entry = add_boot_entry(mp, title, grubdisk, 2461 "/platform/i86pc/multiboot", 2462 "/platform/i86pc/boot_archive"); 2463 2464 /* add the entry for failsafe archive */ 2465 (void) add_boot_entry(mp, "Solaris failsafe", grubdisk, 2466 "/boot/multiboot kernel/unix -s", 2467 "/boot/x86.miniroot-safe"); 2468 free(grubdisk); 2469 2470 if (entry == BAM_ERROR) { 2471 return (BAM_ERROR); 2472 } 2473 (void) set_global(mp, menu_cmds[DEFAULT_CMD], entry); 2474 return (BAM_WRITE); 2475 } 2476 2477 /* 2478 * This function is for supporting reboot with args. 2479 * The opt value can be: 2480 * NULL delete temp entry, if present 2481 * entry=# switches default entry to 1 2482 * else treated as boot-args and setup a temperary menu entry 2483 * and make it the default 2484 */ 2485 #define REBOOT_TITLE "Solaris_reboot_transient" 2486 2487 static error_t 2488 update_temp(menu_t *mp, char *menupath, char *opt) 2489 { 2490 int entry; 2491 char *grubdisk, *rootdev; 2492 char kernbuf[1024]; 2493 2494 assert(mp); 2495 2496 if (opt != NULL && 2497 strncmp(opt, "entry=", strlen("entry=")) == 0 && 2498 selector(mp, opt, &entry, NULL) == BAM_SUCCESS) { 2499 /* this is entry=# option */ 2500 return (set_global(mp, menu_cmds[DEFAULT_CMD], entry)); 2501 } 2502 2503 /* If no option, delete exiting reboot menu entry */ 2504 if (opt == NULL) 2505 return (delete_entry(mp, menupath, "title="REBOOT_TITLE)); 2506 2507 /* 2508 * add a new menu entry base on opt and make it the default 2509 * 1. First get root disk name from mnttab 2510 * 2. Translate disk name to grub name 2511 * 3. Add the new menu entry 2512 */ 2513 rootdev = get_special("/"); 2514 if (rootdev) { 2515 grubdisk = os_to_grubdisk(rootdev, 1); 2516 free(rootdev); 2517 } 2518 if (grubdisk == NULL) { 2519 return (BAM_ERROR); 2520 } 2521 2522 /* add an entry for Solaris reboot */ 2523 (void) snprintf(kernbuf, sizeof (kernbuf), 2524 "/platform/i86pc/multiboot %s", opt); 2525 entry = add_boot_entry(mp, REBOOT_TITLE, grubdisk, kernbuf, 2526 "/platform/i86pc/boot_archive"); 2527 free(grubdisk); 2528 2529 if (entry == BAM_ERROR) { 2530 return (BAM_ERROR); 2531 } 2532 (void) set_global(mp, menu_cmds[DEFAULT_CMD], entry); 2533 return (BAM_WRITE); 2534 } 2535 2536 static error_t 2537 set_global(menu_t *mp, char *globalcmd, int val) 2538 { 2539 line_t *lp, *found, *last; 2540 char *cp, *str; 2541 char prefix[BAM_MAXLINE]; 2542 size_t len; 2543 2544 assert(mp); 2545 assert(globalcmd); 2546 2547 found = last = NULL; 2548 for (lp = mp->start; lp; lp = lp->next) { 2549 if (lp->flags != BAM_GLOBAL) 2550 continue; 2551 2552 last = lp; /* track the last global found */ 2553 2554 if (lp->cmd == NULL) { 2555 bam_error(NO_CMD, lp->lineNum); 2556 continue; 2557 } 2558 if (strcmp(globalcmd, lp->cmd) != 0) 2559 continue; 2560 2561 if (found) { 2562 bam_error(DUP_CMD, globalcmd, lp->lineNum, bam_root); 2563 } 2564 found = lp; 2565 } 2566 2567 if (found == NULL) { 2568 lp = s_calloc(1, sizeof (line_t)); 2569 if (last == NULL) { 2570 lp->next = mp->start; 2571 mp->start = lp; 2572 mp->end = (mp->end) ? mp->end : lp; 2573 } else { 2574 lp->next = last->next; 2575 last->next = lp; 2576 if (lp->next == NULL) 2577 mp->end = lp; 2578 } 2579 lp->flags = BAM_GLOBAL; /* other fields not needed for writes */ 2580 len = strlen(globalcmd) + strlen(menu_cmds[SEP_CMD]); 2581 len += 10; /* val < 10 digits */ 2582 lp->line = s_calloc(1, len); 2583 (void) snprintf(lp->line, len, "%s%s%d", 2584 globalcmd, menu_cmds[SEP_CMD], val); 2585 return (BAM_WRITE); 2586 } 2587 2588 /* 2589 * We are changing an existing entry. Retain any prefix whitespace, 2590 * but overwrite everything else. This preserves tabs added for 2591 * readability. 2592 */ 2593 str = found->line; 2594 cp = prefix; 2595 while (*str == ' ' || *str == '\t') 2596 *(cp++) = *(str++); 2597 *cp = '\0'; /* Terminate prefix */ 2598 len = strlen(prefix) + strlen(globalcmd); 2599 len += strlen(menu_cmds[SEP_CMD]) + 10; 2600 2601 free(found->line); 2602 found->line = s_calloc(1, len); 2603 (void) snprintf(found->line, len, 2604 "%s%s%s%d", prefix, globalcmd, menu_cmds[SEP_CMD], val); 2605 2606 return (BAM_WRITE); /* need a write to menu */ 2607 } 2608 2609 /*ARGSUSED*/ 2610 static error_t 2611 set_option(menu_t *mp, char *menu_path, char *opt) 2612 { 2613 int optnum, optval; 2614 char *val; 2615 2616 assert(mp); 2617 assert(opt); 2618 2619 val = strchr(opt, '='); 2620 if (val == NULL) { 2621 bam_error(INVALID_ENTRY, opt); 2622 return (BAM_ERROR); 2623 } 2624 2625 *val = '\0'; 2626 if (strcmp(opt, "default") == 0) { 2627 optnum = DEFAULT_CMD; 2628 } else if (strcmp(opt, "timeout") == 0) { 2629 optnum = TIMEOUT_CMD; 2630 } else { 2631 bam_error(INVALID_ENTRY, opt); 2632 return (BAM_ERROR); 2633 } 2634 2635 optval = s_strtol(val + 1); 2636 *val = '='; 2637 return (set_global(mp, menu_cmds[optnum], optval)); 2638 } 2639 2640 /* 2641 * The quiet argument suppresses messages. This is used 2642 * when invoked in the context of other commands (e.g. list_entry) 2643 */ 2644 static error_t 2645 read_globals(menu_t *mp, char *menu_path, char *globalcmd, int quiet) 2646 { 2647 line_t *lp; 2648 char *arg; 2649 int done, ret = BAM_SUCCESS; 2650 2651 assert(mp); 2652 assert(menu_path); 2653 assert(globalcmd); 2654 2655 if (mp->start == NULL) { 2656 if (!quiet) 2657 bam_error(NO_MENU, menu_path); 2658 return (BAM_ERROR); 2659 } 2660 2661 done = 0; 2662 for (lp = mp->start; lp; lp = lp->next) { 2663 if (lp->flags != BAM_GLOBAL) 2664 continue; 2665 2666 if (lp->cmd == NULL) { 2667 if (!quiet) 2668 bam_error(NO_CMD, lp->lineNum); 2669 continue; 2670 } 2671 2672 if (strcmp(globalcmd, lp->cmd) != 0) 2673 continue; 2674 2675 /* Found global. Check for duplicates */ 2676 if (done && !quiet) { 2677 bam_error(DUP_CMD, globalcmd, lp->lineNum, bam_root); 2678 ret = BAM_ERROR; 2679 } 2680 2681 arg = lp->arg ? lp->arg : ""; 2682 bam_print(GLOBAL_CMD, globalcmd, arg); 2683 done = 1; 2684 } 2685 2686 if (!done && bam_verbose) 2687 bam_print(NO_ENTRY, globalcmd); 2688 2689 return (ret); 2690 } 2691 2692 static error_t 2693 menu_write(char *root, menu_t *mp) 2694 { 2695 return (list2file(root, MENU_TMP, GRUB_MENU, mp->start)); 2696 } 2697 2698 static void 2699 line_free(line_t *lp) 2700 { 2701 if (lp == NULL) 2702 return; 2703 2704 if (lp->cmd) 2705 free(lp->cmd); 2706 if (lp->sep) 2707 free(lp->sep); 2708 if (lp->arg) 2709 free(lp->arg); 2710 if (lp->line) 2711 free(lp->line); 2712 free(lp); 2713 } 2714 2715 static void 2716 linelist_free(line_t *start) 2717 { 2718 line_t *lp; 2719 2720 while (start) { 2721 lp = start; 2722 start = start->next; 2723 line_free(lp); 2724 } 2725 } 2726 2727 static void 2728 filelist_free(filelist_t *flistp) 2729 { 2730 linelist_free(flistp->head); 2731 flistp->head = NULL; 2732 flistp->tail = NULL; 2733 } 2734 2735 static void 2736 menu_free(menu_t *mp) 2737 { 2738 assert(mp); 2739 2740 if (mp->start) 2741 linelist_free(mp->start); 2742 free(mp); 2743 2744 } 2745 2746 /* 2747 * Utility routines 2748 */ 2749 2750 2751 /* 2752 * Returns 0 on success 2753 * Any other value indicates an error 2754 */ 2755 static int 2756 exec_cmd(char *cmdline, char *output, int64_t osize) 2757 { 2758 char buf[BUFSIZ]; 2759 int ret; 2760 FILE *ptr; 2761 size_t len; 2762 sigset_t set; 2763 void (*disp)(int); 2764 2765 /* 2766 * For security 2767 * - only absolute paths are allowed 2768 * - set IFS to space and tab 2769 */ 2770 if (*cmdline != '/') { 2771 bam_error(ABS_PATH_REQ, cmdline); 2772 return (-1); 2773 } 2774 (void) putenv("IFS= \t"); 2775 2776 /* 2777 * We may have been exec'ed with SIGCHLD blocked 2778 * unblock it here 2779 */ 2780 (void) sigemptyset(&set); 2781 (void) sigaddset(&set, SIGCHLD); 2782 if (sigprocmask(SIG_UNBLOCK, &set, NULL) != 0) { 2783 bam_error(CANT_UNBLOCK_SIGCHLD, strerror(errno)); 2784 return (-1); 2785 } 2786 2787 /* 2788 * Set SIGCHLD disposition to SIG_DFL for popen/pclose 2789 */ 2790 disp = sigset(SIGCHLD, SIG_DFL); 2791 if (disp == SIG_ERR) { 2792 bam_error(FAILED_SIG, strerror(errno)); 2793 return (-1); 2794 } 2795 if (disp == SIG_HOLD) { 2796 bam_error(BLOCKED_SIG, cmdline); 2797 return (-1); 2798 } 2799 2800 ptr = popen(cmdline, "r"); 2801 if (ptr == NULL) { 2802 bam_error(POPEN_FAIL, cmdline, strerror(errno)); 2803 return (-1); 2804 } 2805 2806 /* 2807 * If we simply do a pclose() following a popen(), pclose() 2808 * will close the reader end of the pipe immediately even 2809 * if the child process has not started/exited. pclose() 2810 * does wait for cmd to terminate before returning though. 2811 * When the executed command writes its output to the pipe 2812 * there is no reader process and the command dies with 2813 * SIGPIPE. To avoid this we read repeatedly until read 2814 * terminates with EOF. This indicates that the command 2815 * (writer) has closed the pipe and we can safely do a 2816 * pclose(). 2817 * 2818 * Since pclose() does wait for the command to exit, 2819 * we can safely reap the exit status of the command 2820 * from the value returned by pclose() 2821 */ 2822 while (fgets(buf, sizeof (buf), ptr) != NULL) { 2823 /* if (bam_verbose) XXX */ 2824 bam_print(PRINT_NO_NEWLINE, buf); 2825 if (output && osize > 0) { 2826 (void) snprintf(output, osize, "%s", buf); 2827 len = strlen(buf); 2828 output += len; 2829 osize -= len; 2830 } 2831 } 2832 2833 ret = pclose(ptr); 2834 if (ret == -1) { 2835 bam_error(PCLOSE_FAIL, cmdline, strerror(errno)); 2836 return (-1); 2837 } 2838 2839 if (WIFEXITED(ret)) { 2840 return (WEXITSTATUS(ret)); 2841 } else { 2842 bam_error(EXEC_FAIL, cmdline, ret); 2843 return (-1); 2844 } 2845 } 2846 2847 /* 2848 * Since this function returns -1 on error 2849 * it cannot be used to convert -1. However, 2850 * that is sufficient for what we need. 2851 */ 2852 static long 2853 s_strtol(char *str) 2854 { 2855 long l; 2856 char *res = NULL; 2857 2858 if (str == NULL) { 2859 return (-1); 2860 } 2861 2862 errno = 0; 2863 l = strtol(str, &res, 10); 2864 if (errno || *res != '\0') { 2865 return (-1); 2866 } 2867 2868 return (l); 2869 } 2870 2871 /* 2872 * Wrapper around fputs, that adds a newline (since fputs doesn't) 2873 */ 2874 static int 2875 s_fputs(char *str, FILE *fp) 2876 { 2877 char linebuf[BAM_MAXLINE]; 2878 2879 (void) snprintf(linebuf, sizeof (linebuf), "%s\n", str); 2880 return (fputs(linebuf, fp)); 2881 } 2882 2883 /* 2884 * Wrapper around fgets, that strips newlines returned by fgets 2885 */ 2886 static char * 2887 s_fgets(char *buf, int buflen, FILE *fp) 2888 { 2889 int n; 2890 2891 buf = fgets(buf, buflen, fp); 2892 if (buf) { 2893 n = strlen(buf); 2894 if (n == buflen - 1 && buf[n-1] != '\n') 2895 bam_error(TOO_LONG, buflen - 1, buf); 2896 buf[n-1] = (buf[n-1] == '\n') ? '\0' : buf[n-1]; 2897 } 2898 2899 return (buf); 2900 } 2901 2902 static void * 2903 s_calloc(size_t nelem, size_t sz) 2904 { 2905 void *ptr; 2906 2907 ptr = calloc(nelem, sz); 2908 if (ptr == NULL) { 2909 bam_error(NO_MEM, nelem*sz); 2910 bam_exit(1); 2911 } 2912 return (ptr); 2913 } 2914 2915 static char * 2916 s_strdup(char *str) 2917 { 2918 char *ptr; 2919 2920 if (str == NULL) 2921 return (NULL); 2922 2923 ptr = strdup(str); 2924 if (ptr == NULL) { 2925 bam_error(NO_MEM, strlen(str) + 1); 2926 bam_exit(1); 2927 } 2928 return (ptr); 2929 } 2930 2931 /* 2932 * Returns 1 if amd64 (or sparc, for syncing x86 diskless clients) 2933 * Returns 0 otherwise 2934 */ 2935 static int 2936 is_amd64(void) 2937 { 2938 static int amd64 = -1; 2939 char isabuf[257]; /* from sysinfo(2) manpage */ 2940 2941 if (amd64 != -1) 2942 return (amd64); 2943 2944 if (sysinfo(SI_ISALIST, isabuf, sizeof (isabuf)) > 0 && 2945 strncmp(isabuf, "amd64 ", strlen("amd64 ")) == 0) 2946 amd64 = 1; 2947 else if (strstr(isabuf, "i386") == NULL) 2948 amd64 = 1; /* diskless server */ 2949 else 2950 amd64 = 0; 2951 2952 return (amd64); 2953 } 2954 2955 static void 2956 append_to_flist(filelist_t *flistp, char *s) 2957 { 2958 line_t *lp; 2959 2960 lp = s_calloc(1, sizeof (line_t)); 2961 lp->line = s_strdup(s); 2962 if (flistp->head == NULL) 2963 flistp->head = lp; 2964 else 2965 flistp->tail->next = lp; 2966 flistp->tail = lp; 2967 } 2968