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. Ignore failures on filesystems 950 * not supporting these operations - pcfs reports unsupported 951 * operations as EINVAL. 952 */ 953 ret = chmod(tmpfile, mode); 954 if (ret == -1 && 955 errno != EINVAL && errno != ENOTSUP) { 956 bam_error(CHMOD_FAIL, tmpfile, strerror(errno)); 957 return (BAM_ERROR); 958 } 959 960 ret = chown(tmpfile, root_uid, sys_gid); 961 if (ret == -1 && 962 errno != EINVAL && errno != ENOTSUP) { 963 bam_error(CHOWN_FAIL, tmpfile, strerror(errno)); 964 return (BAM_ERROR); 965 } 966 967 968 /* 969 * Do an atomic rename 970 */ 971 ret = rename(tmpfile, path); 972 if (ret != 0) { 973 bam_error(RENAME_FAIL, path, strerror(errno)); 974 return (BAM_ERROR); 975 } 976 977 return (BAM_SUCCESS); 978 } 979 980 /* 981 * This function should always return 0 - since we want 982 * to create stat data for *all* files in the list. 983 */ 984 /*ARGSUSED*/ 985 static int 986 cmpstat( 987 const char *file, 988 const struct stat *stat, 989 int flags, 990 struct FTW *ftw) 991 { 992 uint_t sz; 993 uint64_t *value; 994 uint64_t filestat[2]; 995 int error; 996 997 /* 998 * We only want regular files 999 */ 1000 if (!S_ISREG(stat->st_mode)) 1001 return (0); 1002 1003 /* 1004 * new_nvlp may be NULL if there were errors earlier 1005 * but this is not fatal to update determination. 1006 */ 1007 if (walk_arg.new_nvlp) { 1008 filestat[0] = stat->st_size; 1009 filestat[1] = stat->st_mtime; 1010 error = nvlist_add_uint64_array(walk_arg.new_nvlp, 1011 file + bam_rootlen, filestat, 2); 1012 if (error) 1013 bam_error(NVADD_FAIL, file, strerror(error)); 1014 } 1015 1016 /* 1017 * The remaining steps are only required if we haven't made a 1018 * decision about update or if we are checking (-n) 1019 */ 1020 if (walk_arg.need_update && !bam_check) 1021 return (0); 1022 1023 /* 1024 * If we are invoked as part of system/filesyste/boot-archive 1025 * SMF service, ignore amd64 modules unless we are booted amd64. 1026 */ 1027 if (bam_smf_check && !is_amd64() && strstr(file, "/amd64/") == 0) 1028 return (0); 1029 1030 /* 1031 * We need an update if file doesn't exist in old archive 1032 */ 1033 if (walk_arg.old_nvlp == NULL || 1034 nvlist_lookup_uint64_array(walk_arg.old_nvlp, 1035 file + bam_rootlen, &value, &sz) != 0) { 1036 if (bam_smf_check) /* ignore new during smf check */ 1037 return (0); 1038 walk_arg.need_update = 1; 1039 if (bam_verbose) 1040 bam_print(PARSEABLE_NEW_FILE, file); 1041 return (0); 1042 } 1043 1044 /* 1045 * File exists in old archive. Check if file has changed 1046 */ 1047 assert(sz == 2); 1048 bcopy(value, filestat, sizeof (filestat)); 1049 1050 if (filestat[0] != stat->st_size || 1051 filestat[1] != stat->st_mtime) { 1052 walk_arg.need_update = 1; 1053 if (bam_verbose) 1054 if (bam_smf_check) 1055 bam_print(" %s\n", file); 1056 else 1057 bam_print(PARSEABLE_OUT_DATE, file); 1058 } 1059 1060 return (0); 1061 } 1062 1063 /* 1064 * Check flags and presence of required files. 1065 * The force flag and/or absence of files should 1066 * trigger an update. 1067 * Suppress stdout output if check (-n) option is set 1068 * (as -n should only produce parseable output.) 1069 */ 1070 static void 1071 check_flags_and_files(char *root) 1072 { 1073 char path[PATH_MAX]; 1074 struct stat sb; 1075 1076 /* 1077 * if force, create archive unconditionally 1078 */ 1079 if (bam_force) { 1080 walk_arg.need_update = 1; 1081 if (bam_verbose && !bam_check) 1082 bam_print(UPDATE_FORCE); 1083 return; 1084 } 1085 1086 /* 1087 * If archive is missing, create archive 1088 */ 1089 (void) snprintf(path, sizeof (path), "%s%s", root, BOOT_ARCHIVE); 1090 if (stat(path, &sb) != 0) { 1091 if (bam_verbose && !bam_check) 1092 bam_print(UPDATE_ARCH_MISS, path); 1093 walk_arg.need_update = 1; 1094 return; 1095 } 1096 } 1097 1098 static error_t 1099 read_one_list(char *root, filelist_t *flistp, char *filelist) 1100 { 1101 char path[PATH_MAX]; 1102 FILE *fp; 1103 char buf[BAM_MAXLINE]; 1104 1105 (void) snprintf(path, sizeof (path), "%s%s", root, filelist); 1106 1107 fp = fopen(path, "r"); 1108 if (fp == NULL) { 1109 if (bam_debug) 1110 bam_error(FLIST_FAIL, path, strerror(errno)); 1111 return (BAM_ERROR); 1112 } 1113 while (s_fgets(buf, sizeof (buf), fp) != NULL) { 1114 append_to_flist(flistp, buf); 1115 } 1116 if (fclose(fp) != 0) { 1117 bam_error(CLOSE_FAIL, path, strerror(errno)); 1118 return (BAM_ERROR); 1119 } 1120 return (BAM_SUCCESS); 1121 } 1122 1123 static error_t 1124 read_list(char *root, filelist_t *flistp) 1125 { 1126 int rval; 1127 1128 flistp->head = flistp->tail = NULL; 1129 1130 /* 1131 * Read current lists of files - only the first is mandatory 1132 */ 1133 rval = read_one_list(root, flistp, BOOT_FILE_LIST); 1134 if (rval != BAM_SUCCESS) 1135 return (rval); 1136 (void) read_one_list(root, flistp, ETC_FILE_LIST); 1137 1138 if (flistp->head == NULL) { 1139 bam_error(NO_FLIST); 1140 return (BAM_ERROR); 1141 } 1142 1143 return (BAM_SUCCESS); 1144 } 1145 1146 static void 1147 getoldstat(char *root) 1148 { 1149 char path[PATH_MAX]; 1150 int fd, error; 1151 struct stat sb; 1152 char *ostat; 1153 1154 (void) snprintf(path, sizeof (path), "%s%s", root, FILE_STAT); 1155 fd = open(path, O_RDONLY); 1156 if (fd == -1) { 1157 if (bam_verbose) 1158 bam_print(OPEN_FAIL, path, strerror(errno)); 1159 walk_arg.need_update = 1; 1160 return; 1161 } 1162 1163 if (fstat(fd, &sb) != 0) { 1164 bam_error(STAT_FAIL, path, strerror(errno)); 1165 (void) close(fd); 1166 walk_arg.need_update = 1; 1167 return; 1168 } 1169 1170 ostat = s_calloc(1, sb.st_size); 1171 1172 if (read(fd, ostat, sb.st_size) != sb.st_size) { 1173 bam_error(READ_FAIL, path, strerror(errno)); 1174 (void) close(fd); 1175 free(ostat); 1176 walk_arg.need_update = 1; 1177 return; 1178 } 1179 1180 (void) close(fd); 1181 1182 walk_arg.old_nvlp = NULL; 1183 error = nvlist_unpack(ostat, sb.st_size, &walk_arg.old_nvlp, 0); 1184 1185 free(ostat); 1186 1187 if (error) { 1188 bam_error(UNPACK_FAIL, path, strerror(error)); 1189 walk_arg.old_nvlp = NULL; 1190 walk_arg.need_update = 1; 1191 return; 1192 } 1193 } 1194 1195 static void 1196 create_newstat(void) 1197 { 1198 int error; 1199 1200 error = nvlist_alloc(&walk_arg.new_nvlp, NV_UNIQUE_NAME, 0); 1201 if (error) { 1202 /* 1203 * Not fatal - we can still create archive 1204 */ 1205 walk_arg.new_nvlp = NULL; 1206 bam_error(NVALLOC_FAIL, strerror(error)); 1207 } 1208 } 1209 1210 static void 1211 walk_list(char *root, filelist_t *flistp) 1212 { 1213 char path[PATH_MAX]; 1214 line_t *lp; 1215 1216 for (lp = flistp->head; lp; lp = lp->next) { 1217 (void) snprintf(path, sizeof (path), "%s%s", root, lp->line); 1218 /* XXX shouldn't we use FTW_MOUNT ? */ 1219 if (nftw(path, cmpstat, 20, 0) == -1) { 1220 /* 1221 * Some files may not exist. 1222 * For example: etc/rtc_config on a x86 diskless system 1223 * Emit verbose message only 1224 */ 1225 if (bam_verbose) 1226 bam_print(NFTW_FAIL, path, strerror(errno)); 1227 } 1228 } 1229 } 1230 1231 static void 1232 savenew(char *root) 1233 { 1234 char path[PATH_MAX]; 1235 char path2[PATH_MAX]; 1236 size_t sz; 1237 char *nstat; 1238 int fd, wrote, error; 1239 1240 nstat = NULL; 1241 sz = 0; 1242 error = nvlist_pack(walk_arg.new_nvlp, &nstat, &sz, 1243 NV_ENCODE_XDR, 0); 1244 if (error) { 1245 bam_error(PACK_FAIL, strerror(error)); 1246 return; 1247 } 1248 1249 (void) snprintf(path, sizeof (path), "%s%s", root, FILE_STAT_TMP); 1250 fd = open(path, O_RDWR|O_CREAT|O_TRUNC, FILE_STAT_MODE); 1251 if (fd == -1) { 1252 bam_error(OPEN_FAIL, path, strerror(errno)); 1253 free(nstat); 1254 return; 1255 } 1256 wrote = write(fd, nstat, sz); 1257 if (wrote != sz) { 1258 bam_error(WRITE_FAIL, path, strerror(errno)); 1259 (void) close(fd); 1260 free(nstat); 1261 return; 1262 } 1263 (void) close(fd); 1264 free(nstat); 1265 1266 (void) snprintf(path2, sizeof (path2), "%s%s", root, FILE_STAT); 1267 if (rename(path, path2) != 0) { 1268 bam_error(RENAME_FAIL, path2, strerror(errno)); 1269 } 1270 } 1271 1272 static void 1273 clear_walk_args(void) 1274 { 1275 if (walk_arg.old_nvlp) 1276 nvlist_free(walk_arg.old_nvlp); 1277 if (walk_arg.new_nvlp) 1278 nvlist_free(walk_arg.new_nvlp); 1279 walk_arg.need_update = 0; 1280 walk_arg.old_nvlp = NULL; 1281 walk_arg.new_nvlp = NULL; 1282 } 1283 1284 /* 1285 * Returns: 1286 * 0 - no update necessary 1287 * 1 - update required. 1288 * BAM_ERROR (-1) - An error occurred 1289 * 1290 * Special handling for check (-n): 1291 * ================================ 1292 * The check (-n) option produces parseable output. 1293 * To do this, we suppress all stdout messages unrelated 1294 * to out of sync files. 1295 * All stderr messages are still printed though. 1296 * 1297 */ 1298 static int 1299 update_required(char *root) 1300 { 1301 struct stat sb; 1302 char path[PATH_MAX]; 1303 filelist_t flist; 1304 filelist_t *flistp = &flist; 1305 int need_update; 1306 1307 flistp->head = flistp->tail = NULL; 1308 1309 walk_arg.need_update = 0; 1310 1311 /* 1312 * Without consulting stat data, check if we need update 1313 */ 1314 check_flags_and_files(root); 1315 1316 /* 1317 * In certain deployment scenarios, filestat may not 1318 * exist. Ignore it during boot-archive SMF check. 1319 */ 1320 if (bam_smf_check) { 1321 (void) snprintf(path, sizeof (path), "%s%s", root, FILE_STAT); 1322 if (stat(path, &sb) != 0) 1323 return (0); 1324 } 1325 1326 /* 1327 * consult stat data only if we haven't made a decision 1328 * about update. If checking (-n) however, we always 1329 * need stat data (since we want to compare old and new) 1330 */ 1331 if (!walk_arg.need_update || bam_check) 1332 getoldstat(root); 1333 1334 /* 1335 * read list of files 1336 */ 1337 if (read_list(root, flistp) != BAM_SUCCESS) { 1338 clear_walk_args(); 1339 return (BAM_ERROR); 1340 } 1341 1342 assert(flistp->head && flistp->tail); 1343 1344 /* 1345 * At this point either the update is required 1346 * or the decision is pending. In either case 1347 * we need to create new stat nvlist 1348 */ 1349 create_newstat(); 1350 1351 /* 1352 * This walk does 2 things: 1353 * - gets new stat data for every file 1354 * - (optional) compare old and new stat data 1355 */ 1356 walk_list(root, &flist); 1357 1358 /* done with the file list */ 1359 filelist_free(flistp); 1360 1361 /* 1362 * if we didn't succeed in creating new stat data above 1363 * just return result of update check so that archive is built. 1364 */ 1365 if (walk_arg.new_nvlp == NULL) { 1366 bam_error(NO_NEW_STAT); 1367 need_update = walk_arg.need_update; 1368 clear_walk_args(); 1369 return (need_update ? 1 : 0); 1370 } 1371 1372 1373 /* 1374 * If no update required, discard newstat 1375 */ 1376 if (!walk_arg.need_update) { 1377 clear_walk_args(); 1378 return (0); 1379 } 1380 1381 /* 1382 * At this point we need an update - so save new stat data 1383 * However, if only checking (-n), don't save new stat data. 1384 */ 1385 if (!bam_check) 1386 savenew(root); 1387 1388 clear_walk_args(); 1389 1390 return (1); 1391 } 1392 1393 static error_t 1394 create_ramdisk(char *root) 1395 { 1396 char *cmdline, path[PATH_MAX]; 1397 size_t len; 1398 struct stat sb; 1399 1400 /* 1401 * Setup command args for create_ramdisk.ksh 1402 */ 1403 (void) snprintf(path, sizeof (path), "%s%s", root, CREATE_RAMDISK); 1404 if (stat(path, &sb) != 0) { 1405 bam_error(ARCH_EXEC_MISS, path, strerror(errno)); 1406 return (BAM_ERROR); 1407 } 1408 1409 len = strlen(path) + strlen(root) + 10; /* room for space + -R */ 1410 cmdline = s_calloc(1, len); 1411 1412 if (strlen(root) > 1) { 1413 (void) snprintf(cmdline, len, "%s -R %s", path, root); 1414 /* chop off / at the end */ 1415 cmdline[strlen(cmdline) - 1] = '\0'; 1416 } else 1417 (void) snprintf(cmdline, len, "%s", path); 1418 1419 if (exec_cmd(cmdline, NULL, 0) != 0) { 1420 bam_error(ARCHIVE_FAIL, cmdline); 1421 free(cmdline); 1422 return (BAM_ERROR); 1423 } 1424 free(cmdline); 1425 1426 /* 1427 * Verify that the archive has been created 1428 */ 1429 (void) snprintf(path, sizeof (path), "%s%s", root, BOOT_ARCHIVE); 1430 if (stat(path, &sb) != 0) { 1431 bam_error(ARCHIVE_NOT_CREATED, path); 1432 return (BAM_ERROR); 1433 } 1434 1435 return (BAM_SUCCESS); 1436 } 1437 1438 /* 1439 * Checks if target filesystem is on a ramdisk 1440 * 1 - is miniroot 1441 * 0 - is not 1442 * When in doubt assume it is not a ramdisk. 1443 */ 1444 static int 1445 is_ramdisk(char *root) 1446 { 1447 struct extmnttab mnt; 1448 FILE *fp; 1449 int found; 1450 1451 /* 1452 * There are 3 situations where creating archive is 1453 * of dubious value: 1454 * - create boot_archive on a boot_archive 1455 * - create it on a ramdisk which is the root filesystem 1456 * - create it on a ramdisk mounted somewhere else 1457 * The first is not easy to detect and checking for it is not 1458 * worth it. 1459 * The other two conditions are handled here 1460 */ 1461 1462 fp = fopen(MNTTAB, "r"); 1463 if (fp == NULL) { 1464 bam_error(OPEN_FAIL, MNTTAB, strerror(errno)); 1465 return (0); 1466 } 1467 1468 resetmnttab(fp); 1469 1470 found = 0; 1471 while (getextmntent(fp, &mnt, sizeof (mnt)) == 0) { 1472 if (strcmp(mnt.mnt_mountp, root) == 0) { 1473 found = 1; 1474 break; 1475 } 1476 } 1477 1478 if (!found) { 1479 if (bam_verbose) 1480 bam_error(NOT_IN_MNTTAB, root); 1481 (void) fclose(fp); 1482 return (0); 1483 } 1484 1485 if (strstr(mnt.mnt_special, RAMDISK_SPECIAL) != NULL) { 1486 if (bam_verbose) 1487 bam_error(IS_RAMDISK, bam_root); 1488 (void) fclose(fp); 1489 return (1); 1490 } 1491 1492 (void) fclose(fp); 1493 1494 return (0); 1495 } 1496 1497 static int 1498 is_newboot(char *root) 1499 { 1500 char path[PATH_MAX]; 1501 struct stat sb; 1502 1503 /* 1504 * We can't boot without MULTI_BOOT 1505 */ 1506 (void) snprintf(path, sizeof (path), "%s%s", root, MULTI_BOOT); 1507 if (stat(path, &sb) == -1) { 1508 if (bam_verbose) 1509 bam_print(FILE_MISS, path); 1510 return (0); 1511 } 1512 1513 /* 1514 * We can't generate archive without GRUB_DIR 1515 */ 1516 (void) snprintf(path, sizeof (path), "%s%s", root, GRUB_DIR); 1517 if (stat(path, &sb) == -1) { 1518 if (bam_verbose) 1519 bam_print(DIR_MISS, path); 1520 return (0); 1521 } 1522 1523 return (1); 1524 } 1525 1526 static int 1527 is_readonly(char *root) 1528 { 1529 struct statvfs vfs; 1530 1531 /* 1532 * Check for RDONLY filesystem 1533 * When in doubt assume it is not readonly 1534 */ 1535 if (statvfs(root, &vfs) != 0) { 1536 if (bam_verbose) 1537 bam_error(STATVFS_FAIL, root, strerror(errno)); 1538 return (0); 1539 } 1540 1541 if (vfs.f_flag & ST_RDONLY) { 1542 return (1); 1543 } 1544 1545 return (0); 1546 } 1547 1548 static error_t 1549 update_archive(char *root, char *opt) 1550 { 1551 error_t ret; 1552 1553 assert(root); 1554 assert(opt == NULL); 1555 1556 /* 1557 * root must belong to a newboot OS, 1558 * don't care on sparc except for diskless clients 1559 */ 1560 if (!is_newboot(root)) { 1561 if (bam_verbose) 1562 bam_print(NOT_NEWBOOT); 1563 return (BAM_SUCCESS); 1564 } 1565 1566 /* 1567 * root must be writable 1568 * Note: statvfs() does not always report the truth 1569 */ 1570 if (is_readonly(root)) { 1571 if (!bam_smf_check && bam_verbose) 1572 bam_print(RDONLY_FS, root); 1573 return (BAM_SUCCESS); 1574 } 1575 1576 /* 1577 * Don't generate archive on ramdisk 1578 */ 1579 if (is_ramdisk(root)) { 1580 if (bam_verbose) 1581 bam_print(SKIP_RAMDISK); 1582 return (BAM_SUCCESS); 1583 } 1584 1585 /* 1586 * Now check if updated is really needed 1587 */ 1588 ret = update_required(root); 1589 1590 /* 1591 * The check command (-n) is *not* a dry run 1592 * It only checks if the archive is in sync. 1593 */ 1594 if (bam_check) { 1595 bam_exit((ret != 0) ? 1 : 0); 1596 } 1597 1598 if (ret == 1) { 1599 /* create the ramdisk */ 1600 ret = create_ramdisk(root); 1601 } 1602 return (ret); 1603 } 1604 1605 static error_t 1606 update_all(char *root, char *opt) 1607 { 1608 struct extmnttab mnt; 1609 struct stat sb; 1610 FILE *fp; 1611 char multibt[PATH_MAX]; 1612 error_t ret = BAM_SUCCESS; 1613 1614 assert(bam_rootlen == 1 && root[0] == '/'); 1615 assert(opt == NULL); 1616 1617 /* 1618 * First update archive for current root 1619 */ 1620 if (update_archive(root, opt) != BAM_SUCCESS) 1621 ret = BAM_ERROR; 1622 1623 /* 1624 * Now walk the mount table, performing archive update 1625 * for all mounted Newboot root filesystems 1626 */ 1627 fp = fopen(MNTTAB, "r"); 1628 if (fp == NULL) { 1629 bam_error(OPEN_FAIL, MNTTAB, strerror(errno)); 1630 return (BAM_ERROR); 1631 } 1632 1633 resetmnttab(fp); 1634 1635 while (getextmntent(fp, &mnt, sizeof (mnt)) == 0) { 1636 if (mnt.mnt_special == NULL) 1637 continue; 1638 if (strncmp(mnt.mnt_special, "/dev/", strlen("/dev/")) != 0) 1639 continue; 1640 if (strcmp(mnt.mnt_mountp, "/") == 0) 1641 continue; 1642 1643 (void) snprintf(multibt, sizeof (multibt), "%s%s", 1644 mnt.mnt_mountp, MULTI_BOOT); 1645 1646 if (stat(multibt, &sb) == -1) 1647 continue; 1648 1649 /* 1650 * We put a trailing slash to be consistent with root = "/" 1651 * case, such that we don't have to print // in some cases. 1652 */ 1653 (void) snprintf(rootbuf, sizeof (rootbuf), "%s/", 1654 mnt.mnt_mountp); 1655 bam_rootlen = strlen(rootbuf); 1656 if (update_archive(rootbuf, opt) != BAM_SUCCESS) 1657 ret = BAM_ERROR; 1658 } 1659 1660 (void) fclose(fp); 1661 1662 return (ret); 1663 } 1664 1665 static void 1666 append_line(menu_t *mp, line_t *lp) 1667 { 1668 if (mp->start == NULL) { 1669 mp->start = lp; 1670 } else { 1671 mp->end->next = lp; 1672 } 1673 mp->end = lp; 1674 } 1675 1676 /* 1677 * A line in menu.lst looks like 1678 * [ ]*<cmd>[ \t=]*<arg>* 1679 */ 1680 static void 1681 line_parser(menu_t *mp, char *str, int *lineNum, int *entryNum) 1682 { 1683 /* 1684 * save state across calls. This is so that 1685 * header gets the right entry# after title has 1686 * been processed 1687 */ 1688 static line_t *prev; 1689 1690 line_t *lp; 1691 char *cmd, *sep, *arg; 1692 char save, *cp, *line; 1693 menu_flag_t flag = BAM_INVALID; 1694 1695 if (str == NULL) { 1696 return; 1697 } 1698 1699 /* 1700 * First save a copy of the entire line. 1701 * We use this later to set the line field. 1702 */ 1703 line = s_strdup(str); 1704 1705 /* Eat up leading whitespace */ 1706 while (*str == ' ' || *str == '\t') 1707 str++; 1708 1709 if (*str == '#') { /* comment */ 1710 cmd = s_strdup("#"); 1711 sep = NULL; 1712 arg = s_strdup(str + 1); 1713 flag = BAM_COMMENT; 1714 } else if (*str == '\0') { /* blank line */ 1715 cmd = sep = arg = NULL; 1716 flag = BAM_EMPTY; 1717 } else { 1718 /* 1719 * '=' is not a documented separator in grub syntax. 1720 * However various development bits use '=' as a 1721 * separator. In addition, external users also 1722 * use = as a separator. So we will allow that usage. 1723 */ 1724 cp = str; 1725 while (*str != ' ' && *str != '\t' && *str != '=') { 1726 if (*str == '\0') { 1727 cmd = s_strdup(cp); 1728 sep = arg = NULL; 1729 break; 1730 } 1731 str++; 1732 } 1733 1734 if (*str != '\0') { 1735 save = *str; 1736 *str = '\0'; 1737 cmd = s_strdup(cp); 1738 *str = save; 1739 1740 str++; 1741 save = *str; 1742 *str = '\0'; 1743 sep = s_strdup(str - 1); 1744 *str = save; 1745 1746 while (*str == ' ' || *str == '\t') 1747 str++; 1748 if (*str == '\0') 1749 arg = NULL; 1750 else 1751 arg = s_strdup(str); 1752 } 1753 } 1754 1755 lp = s_calloc(1, sizeof (line_t)); 1756 1757 lp->cmd = cmd; 1758 lp->sep = sep; 1759 lp->arg = arg; 1760 lp->line = line; 1761 lp->lineNum = ++(*lineNum); 1762 if (cmd && strcmp(cmd, menu_cmds[TITLE_CMD]) == 0) { 1763 lp->entryNum = ++(*entryNum); 1764 lp->flags = BAM_TITLE; 1765 if (prev && prev->flags == BAM_COMMENT && 1766 prev->arg && strcmp(prev->arg, BAM_HDR) == 0) 1767 prev->entryNum = lp->entryNum; 1768 } else if (flag != BAM_INVALID) { 1769 /* 1770 * For header comments, the entry# is "fixed up" 1771 * by the subsequent title 1772 */ 1773 lp->entryNum = *entryNum; 1774 lp->flags = flag; 1775 } else { 1776 lp->entryNum = *entryNum; 1777 lp->flags = (*entryNum == ENTRY_INIT) ? BAM_GLOBAL : BAM_ENTRY; 1778 } 1779 1780 append_line(mp, lp); 1781 1782 prev = lp; 1783 } 1784 1785 static menu_t * 1786 menu_read(char *menu_path) 1787 { 1788 FILE *fp; 1789 char buf[BAM_MAXLINE], *cp; 1790 menu_t *mp; 1791 int line, entry, len, n; 1792 1793 mp = s_calloc(1, sizeof (menu_t)); 1794 1795 fp = fopen(menu_path, "r"); 1796 if (fp == NULL) { /* Let the caller handle this error */ 1797 return (mp); 1798 } 1799 1800 1801 /* Note: GRUB boot entry number starts with 0 */ 1802 line = LINE_INIT; 1803 entry = ENTRY_INIT; 1804 cp = buf; 1805 len = sizeof (buf); 1806 while (s_fgets(cp, len, fp) != NULL) { 1807 n = strlen(cp); 1808 if (cp[n - 1] == '\\') { 1809 len -= n - 1; 1810 assert(len >= 2); 1811 cp += n - 1; 1812 continue; 1813 } 1814 line_parser(mp, buf, &line, &entry); 1815 cp = buf; 1816 len = sizeof (buf); 1817 } 1818 1819 if (fclose(fp) == EOF) { 1820 bam_error(CLOSE_FAIL, menu_path, strerror(errno)); 1821 } 1822 1823 return (mp); 1824 } 1825 1826 static error_t 1827 selector(menu_t *mp, char *opt, int *entry, char **title) 1828 { 1829 char *eq; 1830 char *opt_dup; 1831 int entryNum; 1832 1833 assert(mp); 1834 assert(mp->start); 1835 assert(opt); 1836 1837 opt_dup = s_strdup(opt); 1838 1839 if (entry) 1840 *entry = ENTRY_INIT; 1841 if (title) 1842 *title = NULL; 1843 1844 eq = strchr(opt_dup, '='); 1845 if (eq == NULL) { 1846 bam_error(INVALID_OPT, opt); 1847 free(opt_dup); 1848 return (BAM_ERROR); 1849 } 1850 1851 *eq = '\0'; 1852 if (entry && strcmp(opt_dup, OPT_ENTRY_NUM) == 0) { 1853 assert(mp->end); 1854 entryNum = s_strtol(eq + 1); 1855 if (entryNum < 0 || entryNum > mp->end->entryNum) { 1856 bam_error(INVALID_ENTRY, eq + 1); 1857 free(opt_dup); 1858 return (BAM_ERROR); 1859 } 1860 *entry = entryNum; 1861 } else if (title && strcmp(opt_dup, menu_cmds[TITLE_CMD]) == 0) { 1862 *title = opt + (eq - opt_dup) + 1; 1863 } else { 1864 bam_error(INVALID_OPT, opt); 1865 free(opt_dup); 1866 return (BAM_ERROR); 1867 } 1868 1869 free(opt_dup); 1870 return (BAM_SUCCESS); 1871 } 1872 1873 /* 1874 * If invoked with no titles/entries (opt == NULL) 1875 * only title lines in file are printed. 1876 * 1877 * If invoked with a title or entry #, all 1878 * lines in *every* matching entry are listed 1879 */ 1880 static error_t 1881 list_entry(menu_t *mp, char *menu_path, char *opt) 1882 { 1883 line_t *lp; 1884 int entry = ENTRY_INIT; 1885 int found; 1886 char *title = NULL; 1887 1888 assert(mp); 1889 assert(menu_path); 1890 1891 if (mp->start == NULL) { 1892 bam_error(NO_MENU, menu_path); 1893 return (BAM_ERROR); 1894 } 1895 1896 if (opt != NULL) { 1897 if (selector(mp, opt, &entry, &title) != BAM_SUCCESS) { 1898 return (BAM_ERROR); 1899 } 1900 assert((entry != ENTRY_INIT) ^ (title != NULL)); 1901 } else { 1902 (void) read_globals(mp, menu_path, menu_cmds[DEFAULT_CMD], 0); 1903 (void) read_globals(mp, menu_path, menu_cmds[TIMEOUT_CMD], 0); 1904 } 1905 1906 found = 0; 1907 for (lp = mp->start; lp; lp = lp->next) { 1908 if (lp->flags == BAM_COMMENT || lp->flags == BAM_EMPTY) 1909 continue; 1910 if (opt == NULL && lp->flags == BAM_TITLE) { 1911 bam_print(PRINT_TITLE, lp->entryNum, 1912 lp->arg); 1913 found = 1; 1914 continue; 1915 } 1916 if (entry != ENTRY_INIT && lp->entryNum == entry) { 1917 bam_print(PRINT, lp->line); 1918 found = 1; 1919 continue; 1920 } 1921 1922 /* 1923 * We set the entry value here so that all lines 1924 * in entry get printed. If we subsequently match 1925 * title in other entries, all lines in those 1926 * entries get printed as well. 1927 */ 1928 if (title && lp->flags == BAM_TITLE && lp->arg && 1929 strncmp(title, lp->arg, strlen(title)) == 0) { 1930 bam_print(PRINT, lp->line); 1931 entry = lp->entryNum; 1932 found = 1; 1933 continue; 1934 } 1935 } 1936 1937 if (!found) { 1938 bam_error(NO_MATCH_ENTRY); 1939 return (BAM_ERROR); 1940 } 1941 1942 return (BAM_SUCCESS); 1943 } 1944 1945 static int 1946 add_boot_entry(menu_t *mp, 1947 char *title, 1948 char *root, 1949 char *kernel, 1950 char *module) 1951 { 1952 menu_t dummy; 1953 int lineNum, entryNum; 1954 char linebuf[BAM_MAXLINE]; 1955 1956 assert(mp); 1957 1958 if (title == NULL) { 1959 bam_error(SUBOPT_MISS, menu_cmds[TITLE_CMD]); 1960 return (BAM_ERROR); 1961 } 1962 if (root == NULL) { 1963 bam_error(SUBOPT_MISS, menu_cmds[ROOT_CMD]); 1964 return (BAM_ERROR); 1965 } 1966 if (kernel == NULL) { 1967 bam_error(SUBOPT_MISS, menu_cmds[KERNEL_CMD]); 1968 return (BAM_ERROR); 1969 } 1970 if (module == NULL) { 1971 bam_error(SUBOPT_MISS, menu_cmds[MODULE_CMD]); 1972 return (BAM_ERROR); 1973 } 1974 1975 if (mp->start) { 1976 lineNum = mp->end->lineNum; 1977 entryNum = mp->end->entryNum; 1978 } else { 1979 lineNum = LINE_INIT; 1980 entryNum = ENTRY_INIT; 1981 } 1982 1983 /* 1984 * No separator for comment (HDR/FTR) commands 1985 * The syntax for comments is #<comment> 1986 */ 1987 (void) snprintf(linebuf, sizeof (linebuf), "%s%s", 1988 menu_cmds[COMMENT_CMD], BAM_HDR); 1989 dummy.start = dummy.end = NULL; 1990 line_parser(&dummy, linebuf, &lineNum, &entryNum); 1991 if (dummy.start == NULL || dummy.start->flags != BAM_COMMENT) { 1992 line_free(dummy.start); 1993 bam_error(INVALID_HDR, BAM_HDR); 1994 return (BAM_ERROR); 1995 } 1996 assert(dummy.start == dummy.end); 1997 append_line(mp, dummy.start); 1998 1999 (void) snprintf(linebuf, sizeof (linebuf), "%s%s%s", 2000 menu_cmds[TITLE_CMD], menu_cmds[SEP_CMD], title); 2001 dummy.start = dummy.end = NULL; 2002 line_parser(&dummy, linebuf, &lineNum, &entryNum); 2003 if (dummy.start == NULL || dummy.start->flags != BAM_TITLE) { 2004 line_free(dummy.start); 2005 bam_error(INVALID_TITLE, title); 2006 return (BAM_ERROR); 2007 } 2008 assert(dummy.start == dummy.end); 2009 append_line(mp, dummy.start); 2010 2011 2012 (void) snprintf(linebuf, sizeof (linebuf), "%s%s%s", 2013 menu_cmds[ROOT_CMD], menu_cmds[SEP_CMD], root); 2014 dummy.start = dummy.end = NULL; 2015 line_parser(&dummy, linebuf, &lineNum, &entryNum); 2016 if (dummy.start == NULL || dummy.start->flags != BAM_ENTRY) { 2017 line_free(dummy.start); 2018 bam_error(INVALID_ROOT, root); 2019 return (BAM_ERROR); 2020 } 2021 assert(dummy.start == dummy.end); 2022 append_line(mp, dummy.start); 2023 2024 2025 (void) snprintf(linebuf, sizeof (linebuf), "%s%s%s", 2026 menu_cmds[KERNEL_CMD], menu_cmds[SEP_CMD], kernel); 2027 dummy.start = dummy.end = NULL; 2028 line_parser(&dummy, linebuf, &lineNum, &entryNum); 2029 if (dummy.start == NULL || dummy.start->flags != BAM_ENTRY) { 2030 line_free(dummy.start); 2031 bam_error(INVALID_KERNEL, kernel); 2032 return (BAM_ERROR); 2033 } 2034 assert(dummy.start == dummy.end); 2035 append_line(mp, dummy.start); 2036 2037 (void) snprintf(linebuf, sizeof (linebuf), "%s%s%s", 2038 menu_cmds[MODULE_CMD], menu_cmds[SEP_CMD], module); 2039 dummy.start = dummy.end = NULL; 2040 line_parser(&dummy, linebuf, &lineNum, &entryNum); 2041 if (dummy.start == NULL || dummy.start->flags != BAM_ENTRY) { 2042 line_free(dummy.start); 2043 bam_error(INVALID_MODULE, module); 2044 return (BAM_ERROR); 2045 } 2046 assert(dummy.start == dummy.end); 2047 append_line(mp, dummy.start); 2048 2049 (void) snprintf(linebuf, sizeof (linebuf), "%s%s", 2050 menu_cmds[COMMENT_CMD], BAM_FTR); 2051 dummy.start = dummy.end = NULL; 2052 line_parser(&dummy, linebuf, &lineNum, &entryNum); 2053 if (dummy.start == NULL || dummy.start->flags != BAM_COMMENT) { 2054 line_free(dummy.start); 2055 bam_error(INVALID_FOOTER, BAM_FTR); 2056 return (BAM_ERROR); 2057 } 2058 assert(dummy.start == dummy.end); 2059 append_line(mp, dummy.start); 2060 2061 return (entryNum); 2062 } 2063 2064 static error_t 2065 do_delete(menu_t *mp, int entryNum) 2066 { 2067 int bootadm_entry = 0; 2068 line_t *lp, *prev, *save; 2069 int deleted; 2070 2071 assert(entryNum != ENTRY_INIT); 2072 2073 deleted = 0; 2074 prev = NULL; 2075 for (lp = mp->start; lp; ) { 2076 2077 if (lp->entryNum == ENTRY_INIT) { 2078 prev = lp; 2079 lp = lp->next; 2080 continue; 2081 } 2082 2083 if (entryNum != ALL_ENTRIES && lp->entryNum != entryNum) { 2084 prev = lp; 2085 lp = lp->next; 2086 continue; 2087 } 2088 2089 /* 2090 * can only delete bootadm entries 2091 */ 2092 if (lp->flags == BAM_COMMENT && strcmp(lp->arg, BAM_HDR) == 0) { 2093 bootadm_entry = 1; 2094 } 2095 2096 if (!bootadm_entry) { 2097 prev = lp; 2098 lp = lp->next; 2099 continue; 2100 } 2101 2102 if (lp->flags == BAM_COMMENT && strcmp(lp->arg, BAM_FTR) == 0) 2103 bootadm_entry = 0; 2104 2105 if (prev == NULL) 2106 mp->start = lp->next; 2107 else 2108 prev->next = lp->next; 2109 if (mp->end == lp) 2110 mp->end = prev; 2111 save = lp->next; 2112 line_free(lp); 2113 lp = save; /* prev stays the same */ 2114 2115 deleted = 1; 2116 } 2117 2118 if (!deleted && entryNum != ALL_ENTRIES) { 2119 bam_error(NO_BOOTADM_MATCH); 2120 return (BAM_ERROR); 2121 } 2122 2123 return (BAM_SUCCESS); 2124 } 2125 2126 static error_t 2127 delete_entry(menu_t *mp, char *menu_path, char *opt) 2128 { 2129 int entry = ENTRY_INIT; 2130 char *title = NULL; 2131 line_t *lp; 2132 2133 assert(mp); 2134 assert(opt); 2135 2136 /* 2137 * Do a quick check. If the file is empty 2138 * we have nothing to delete 2139 */ 2140 if (mp->start == NULL) { 2141 bam_print(EMPTY_FILE, menu_path); 2142 return (BAM_SUCCESS); 2143 } 2144 2145 if (selector(mp, opt, &entry, &title) != BAM_SUCCESS) { 2146 return (BAM_ERROR); 2147 } 2148 assert((entry != ENTRY_INIT) ^ (title != NULL)); 2149 2150 for (lp = mp->start; lp; lp = lp->next) { 2151 if (entry != ENTRY_INIT) 2152 break; 2153 assert(title); 2154 if (lp->flags == BAM_TITLE && 2155 lp->arg && strcmp(lp->arg, title) == 0) { 2156 entry = lp->entryNum; 2157 break; 2158 } 2159 } 2160 2161 if (entry == ENTRY_INIT) { 2162 bam_error(NO_MATCH, title); 2163 return (BAM_ERROR); 2164 } 2165 2166 if (do_delete(mp, entry) != BAM_SUCCESS) { 2167 return (BAM_ERROR); 2168 } 2169 2170 return (BAM_WRITE); 2171 } 2172 2173 static error_t 2174 delete_all_entries(menu_t *mp, char *menu_path, char *opt) 2175 { 2176 assert(mp); 2177 assert(opt == NULL); 2178 2179 if (mp->start == NULL) { 2180 bam_print(EMPTY_FILE, menu_path); 2181 return (BAM_SUCCESS); 2182 } 2183 2184 if (do_delete(mp, ALL_ENTRIES) != BAM_SUCCESS) { 2185 return (BAM_ERROR); 2186 } 2187 2188 return (BAM_WRITE); 2189 } 2190 2191 static FILE * 2192 open_diskmap(void) 2193 { 2194 FILE *fp; 2195 char cmd[PATH_MAX]; 2196 2197 /* make sure we have a map file */ 2198 fp = fopen(GRUBDISK_MAP, "r"); 2199 if (fp == NULL) { 2200 (void) snprintf(cmd, sizeof (cmd), 2201 "%s > /dev/null", CREATE_DISKMAP); 2202 (void) system(cmd); 2203 fp = fopen(GRUBDISK_MAP, "r"); 2204 } 2205 return (fp); 2206 } 2207 2208 #define SECTOR_SIZE 512 2209 2210 static int 2211 get_partition(char *device) 2212 { 2213 int i, fd, is_pcfs, partno = -1; 2214 struct mboot *mboot; 2215 char boot_sect[SECTOR_SIZE]; 2216 char *wholedisk, *slice; 2217 2218 /* form whole disk (p0) */ 2219 slice = device + strlen(device) - 2; 2220 is_pcfs = (*slice != 's'); 2221 if (!is_pcfs) 2222 *slice = '\0'; 2223 wholedisk = s_calloc(1, strlen(device) + 3); 2224 (void) snprintf(wholedisk, strlen(device) + 3, "%sp0", device); 2225 if (!is_pcfs) 2226 *slice = 's'; 2227 2228 /* read boot sector */ 2229 fd = open(wholedisk, O_RDONLY); 2230 free(wholedisk); 2231 if (fd == -1 || read(fd, boot_sect, SECTOR_SIZE) != SECTOR_SIZE) { 2232 return (partno); 2233 } 2234 (void) close(fd); 2235 2236 /* parse fdisk table */ 2237 mboot = (struct mboot *)((void *)boot_sect); 2238 for (i = 0; i < FD_NUMPART; i++) { 2239 struct ipart *part = 2240 (struct ipart *)(uintptr_t)mboot->parts + i; 2241 if (is_pcfs) { /* looking for solaris boot part */ 2242 if (part->systid == 0xbe) { 2243 partno = i; 2244 break; 2245 } 2246 } else { /* look for solaris partition, old and new */ 2247 if (part->systid == SUNIXOS || 2248 part->systid == SUNIXOS2) { 2249 partno = i; 2250 break; 2251 } 2252 } 2253 } 2254 return (partno); 2255 } 2256 2257 static char * 2258 get_grubdisk(char *rootdev, FILE *fp, int on_bootdev) 2259 { 2260 char *grubdisk; /* (hd#,#,#) */ 2261 char *slice; 2262 char *grubhd; 2263 int fdiskpart; 2264 int found = 0; 2265 char *devname, *ctdname = strstr(rootdev, "dsk/"); 2266 char linebuf[PATH_MAX]; 2267 2268 if (ctdname == NULL) 2269 return (NULL); 2270 2271 ctdname += strlen("dsk/"); 2272 slice = strrchr(ctdname, 's'); 2273 if (slice) 2274 *slice = '\0'; 2275 2276 rewind(fp); 2277 while (s_fgets(linebuf, sizeof (linebuf), fp) != NULL) { 2278 grubhd = strtok(linebuf, " \t\n"); 2279 if (grubhd) 2280 devname = strtok(NULL, " \t\n"); 2281 else 2282 devname = NULL; 2283 if (devname && strcmp(devname, ctdname) == 0) { 2284 found = 1; 2285 break; 2286 } 2287 } 2288 2289 if (slice) 2290 *slice = 's'; 2291 2292 if (found == 0) { 2293 if (bam_verbose) 2294 bam_print(DISKMAP_FAIL_NONFATAL, rootdev); 2295 grubhd = "0"; /* assume disk 0 if can't match */ 2296 } 2297 2298 fdiskpart = get_partition(rootdev); 2299 if (fdiskpart == -1) 2300 return (NULL); 2301 2302 grubdisk = s_calloc(1, 10); 2303 if (slice) { 2304 (void) snprintf(grubdisk, 10, "(hd%s,%d,%c)", 2305 grubhd, fdiskpart, slice[1] + 'a' - '0'); 2306 } else 2307 (void) snprintf(grubdisk, 10, "(hd%s,%d)", 2308 grubhd, fdiskpart); 2309 2310 /* if root not on bootdev, change GRUB disk to 0 */ 2311 if (!on_bootdev) 2312 grubdisk[3] = '0'; 2313 return (grubdisk); 2314 } 2315 2316 static char *get_title(char *rootdir) 2317 { 2318 static char title[80]; /* from /etc/release */ 2319 char *cp, release[PATH_MAX]; 2320 FILE *fp; 2321 2322 /* open the /etc/release file */ 2323 (void) snprintf(release, sizeof (release), "%s/etc/release", rootdir); 2324 2325 fp = fopen(release, "r"); 2326 if (fp == NULL) 2327 return ("Solaris"); /* default to Solaris */ 2328 2329 while (s_fgets(title, sizeof (title), fp) != NULL) { 2330 cp = strstr(title, "Solaris"); 2331 if (cp) 2332 break; 2333 } 2334 (void) fclose(fp); 2335 return (cp); 2336 } 2337 2338 static char * 2339 get_special(char *mountp) 2340 { 2341 FILE *mntfp; 2342 struct mnttab mp = {0}, mpref = {0}; 2343 2344 mntfp = fopen(MNTTAB, "r"); 2345 if (mntfp == NULL) { 2346 return (0); 2347 } 2348 2349 if (*mountp == '\0') 2350 mpref.mnt_mountp = "/"; 2351 else 2352 mpref.mnt_mountp = mountp; 2353 if (getmntany(mntfp, &mp, &mpref) != 0) { 2354 (void) fclose(mntfp); 2355 return (NULL); 2356 } 2357 (void) fclose(mntfp); 2358 2359 return (s_strdup(mp.mnt_special)); 2360 } 2361 2362 static char * 2363 os_to_grubdisk(char *osdisk, int on_bootdev) 2364 { 2365 FILE *fp; 2366 char *grubdisk; 2367 2368 /* translate /dev/dsk name to grub disk name */ 2369 fp = open_diskmap(); 2370 if (fp == NULL) { 2371 bam_error(DISKMAP_FAIL, osdisk); 2372 return (NULL); 2373 } 2374 grubdisk = get_grubdisk(osdisk, fp, on_bootdev); 2375 (void) fclose(fp); 2376 return (grubdisk); 2377 } 2378 2379 /* 2380 * Check if root is on the boot device 2381 * Return 0 (false) on error 2382 */ 2383 static int 2384 menu_on_bootdev(char *menu_root, FILE *fp) 2385 { 2386 int ret; 2387 char *grubhd, *bootp, *special; 2388 2389 special = get_special(menu_root); 2390 if (special == NULL) 2391 return (0); 2392 bootp = strstr(special, "p0:boot"); 2393 if (bootp) 2394 *bootp = '\0'; 2395 grubhd = get_grubdisk(special, fp, 1); 2396 free(special); 2397 2398 if (grubhd == NULL) 2399 return (0); 2400 ret = grubhd[3] == '0'; 2401 free(grubhd); 2402 return (ret); 2403 } 2404 2405 /*ARGSUSED*/ 2406 static error_t 2407 update_entry(menu_t *mp, char *menu_root, char *opt) 2408 { 2409 FILE *fp; 2410 int entry; 2411 line_t *lp; 2412 char *grubdisk, *title, *osdev, *osroot; 2413 int bootadm_entry, entry_to_delete; 2414 2415 assert(mp); 2416 assert(opt); 2417 2418 osdev = strtok(opt, ","); 2419 osroot = strtok(NULL, ","); 2420 if (osroot == NULL) 2421 osroot = menu_root; 2422 title = get_title(osroot); 2423 2424 /* translate /dev/dsk name to grub disk name */ 2425 fp = open_diskmap(); 2426 if (fp == NULL) { 2427 bam_error(DISKMAP_FAIL, osdev); 2428 return (BAM_ERROR); 2429 } 2430 grubdisk = get_grubdisk(osdev, fp, menu_on_bootdev(menu_root, fp)); 2431 (void) fclose(fp); 2432 if (grubdisk == NULL) { 2433 bam_error(DISKMAP_FAIL, osdev); 2434 return (BAM_ERROR); 2435 } 2436 2437 /* delete existing entries with matching grub hd name */ 2438 for (;;) { 2439 entry_to_delete = -1; 2440 bootadm_entry = 0; 2441 for (lp = mp->start; lp; lp = lp->next) { 2442 /* 2443 * can only delete bootadm entries 2444 */ 2445 if (lp->flags == BAM_COMMENT) { 2446 if (strcmp(lp->arg, BAM_HDR) == 0) 2447 bootadm_entry = 1; 2448 else if (strcmp(lp->arg, BAM_FTR) == 0) 2449 bootadm_entry = 0; 2450 } 2451 2452 if (bootadm_entry && lp->flags == BAM_ENTRY && 2453 strcmp(lp->cmd, menu_cmds[ROOT_CMD]) == 0 && 2454 strcmp(lp->arg, grubdisk) == 0) { 2455 entry_to_delete = lp->entryNum; 2456 } 2457 } 2458 if (entry_to_delete == -1) 2459 break; 2460 (void) do_delete(mp, entry_to_delete); 2461 } 2462 2463 /* add the entry for normal Solaris */ 2464 entry = add_boot_entry(mp, title, grubdisk, 2465 "/platform/i86pc/multiboot", 2466 "/platform/i86pc/boot_archive"); 2467 2468 /* add the entry for failsafe archive */ 2469 (void) add_boot_entry(mp, "Solaris failsafe", grubdisk, 2470 "/boot/multiboot kernel/unix -s", 2471 "/boot/x86.miniroot-safe"); 2472 free(grubdisk); 2473 2474 if (entry == BAM_ERROR) { 2475 return (BAM_ERROR); 2476 } 2477 (void) set_global(mp, menu_cmds[DEFAULT_CMD], entry); 2478 return (BAM_WRITE); 2479 } 2480 2481 /* 2482 * This function is for supporting reboot with args. 2483 * The opt value can be: 2484 * NULL delete temp entry, if present 2485 * entry=# switches default entry to 1 2486 * else treated as boot-args and setup a temperary menu entry 2487 * and make it the default 2488 */ 2489 #define REBOOT_TITLE "Solaris_reboot_transient" 2490 2491 static error_t 2492 update_temp(menu_t *mp, char *menupath, char *opt) 2493 { 2494 int entry; 2495 char *grubdisk, *rootdev; 2496 char kernbuf[1024]; 2497 2498 assert(mp); 2499 2500 if (opt != NULL && 2501 strncmp(opt, "entry=", strlen("entry=")) == 0 && 2502 selector(mp, opt, &entry, NULL) == BAM_SUCCESS) { 2503 /* this is entry=# option */ 2504 return (set_global(mp, menu_cmds[DEFAULT_CMD], entry)); 2505 } 2506 2507 /* If no option, delete exiting reboot menu entry */ 2508 if (opt == NULL) 2509 return (delete_entry(mp, menupath, "title="REBOOT_TITLE)); 2510 2511 /* 2512 * add a new menu entry base on opt and make it the default 2513 * 1. First get root disk name from mnttab 2514 * 2. Translate disk name to grub name 2515 * 3. Add the new menu entry 2516 */ 2517 rootdev = get_special("/"); 2518 if (rootdev) { 2519 grubdisk = os_to_grubdisk(rootdev, 1); 2520 free(rootdev); 2521 } 2522 if (grubdisk == NULL) { 2523 return (BAM_ERROR); 2524 } 2525 2526 /* add an entry for Solaris reboot */ 2527 (void) snprintf(kernbuf, sizeof (kernbuf), 2528 "/platform/i86pc/multiboot %s", opt); 2529 entry = add_boot_entry(mp, REBOOT_TITLE, grubdisk, kernbuf, 2530 "/platform/i86pc/boot_archive"); 2531 free(grubdisk); 2532 2533 if (entry == BAM_ERROR) { 2534 return (BAM_ERROR); 2535 } 2536 (void) set_global(mp, menu_cmds[DEFAULT_CMD], entry); 2537 return (BAM_WRITE); 2538 } 2539 2540 static error_t 2541 set_global(menu_t *mp, char *globalcmd, int val) 2542 { 2543 line_t *lp, *found, *last; 2544 char *cp, *str; 2545 char prefix[BAM_MAXLINE]; 2546 size_t len; 2547 2548 assert(mp); 2549 assert(globalcmd); 2550 2551 found = last = NULL; 2552 for (lp = mp->start; lp; lp = lp->next) { 2553 if (lp->flags != BAM_GLOBAL) 2554 continue; 2555 2556 last = lp; /* track the last global found */ 2557 2558 if (lp->cmd == NULL) { 2559 bam_error(NO_CMD, lp->lineNum); 2560 continue; 2561 } 2562 if (strcmp(globalcmd, lp->cmd) != 0) 2563 continue; 2564 2565 if (found) { 2566 bam_error(DUP_CMD, globalcmd, lp->lineNum, bam_root); 2567 } 2568 found = lp; 2569 } 2570 2571 if (found == NULL) { 2572 lp = s_calloc(1, sizeof (line_t)); 2573 if (last == NULL) { 2574 lp->next = mp->start; 2575 mp->start = lp; 2576 mp->end = (mp->end) ? mp->end : lp; 2577 } else { 2578 lp->next = last->next; 2579 last->next = lp; 2580 if (lp->next == NULL) 2581 mp->end = lp; 2582 } 2583 lp->flags = BAM_GLOBAL; /* other fields not needed for writes */ 2584 len = strlen(globalcmd) + strlen(menu_cmds[SEP_CMD]); 2585 len += 10; /* val < 10 digits */ 2586 lp->line = s_calloc(1, len); 2587 (void) snprintf(lp->line, len, "%s%s%d", 2588 globalcmd, menu_cmds[SEP_CMD], val); 2589 return (BAM_WRITE); 2590 } 2591 2592 /* 2593 * We are changing an existing entry. Retain any prefix whitespace, 2594 * but overwrite everything else. This preserves tabs added for 2595 * readability. 2596 */ 2597 str = found->line; 2598 cp = prefix; 2599 while (*str == ' ' || *str == '\t') 2600 *(cp++) = *(str++); 2601 *cp = '\0'; /* Terminate prefix */ 2602 len = strlen(prefix) + strlen(globalcmd); 2603 len += strlen(menu_cmds[SEP_CMD]) + 10; 2604 2605 free(found->line); 2606 found->line = s_calloc(1, len); 2607 (void) snprintf(found->line, len, 2608 "%s%s%s%d", prefix, globalcmd, menu_cmds[SEP_CMD], val); 2609 2610 return (BAM_WRITE); /* need a write to menu */ 2611 } 2612 2613 /*ARGSUSED*/ 2614 static error_t 2615 set_option(menu_t *mp, char *menu_path, char *opt) 2616 { 2617 int optnum, optval; 2618 char *val; 2619 2620 assert(mp); 2621 assert(opt); 2622 2623 val = strchr(opt, '='); 2624 if (val == NULL) { 2625 bam_error(INVALID_ENTRY, opt); 2626 return (BAM_ERROR); 2627 } 2628 2629 *val = '\0'; 2630 if (strcmp(opt, "default") == 0) { 2631 optnum = DEFAULT_CMD; 2632 } else if (strcmp(opt, "timeout") == 0) { 2633 optnum = TIMEOUT_CMD; 2634 } else { 2635 bam_error(INVALID_ENTRY, opt); 2636 return (BAM_ERROR); 2637 } 2638 2639 optval = s_strtol(val + 1); 2640 *val = '='; 2641 return (set_global(mp, menu_cmds[optnum], optval)); 2642 } 2643 2644 /* 2645 * The quiet argument suppresses messages. This is used 2646 * when invoked in the context of other commands (e.g. list_entry) 2647 */ 2648 static error_t 2649 read_globals(menu_t *mp, char *menu_path, char *globalcmd, int quiet) 2650 { 2651 line_t *lp; 2652 char *arg; 2653 int done, ret = BAM_SUCCESS; 2654 2655 assert(mp); 2656 assert(menu_path); 2657 assert(globalcmd); 2658 2659 if (mp->start == NULL) { 2660 if (!quiet) 2661 bam_error(NO_MENU, menu_path); 2662 return (BAM_ERROR); 2663 } 2664 2665 done = 0; 2666 for (lp = mp->start; lp; lp = lp->next) { 2667 if (lp->flags != BAM_GLOBAL) 2668 continue; 2669 2670 if (lp->cmd == NULL) { 2671 if (!quiet) 2672 bam_error(NO_CMD, lp->lineNum); 2673 continue; 2674 } 2675 2676 if (strcmp(globalcmd, lp->cmd) != 0) 2677 continue; 2678 2679 /* Found global. Check for duplicates */ 2680 if (done && !quiet) { 2681 bam_error(DUP_CMD, globalcmd, lp->lineNum, bam_root); 2682 ret = BAM_ERROR; 2683 } 2684 2685 arg = lp->arg ? lp->arg : ""; 2686 bam_print(GLOBAL_CMD, globalcmd, arg); 2687 done = 1; 2688 } 2689 2690 if (!done && bam_verbose) 2691 bam_print(NO_ENTRY, globalcmd); 2692 2693 return (ret); 2694 } 2695 2696 static error_t 2697 menu_write(char *root, menu_t *mp) 2698 { 2699 return (list2file(root, MENU_TMP, GRUB_MENU, mp->start)); 2700 } 2701 2702 static void 2703 line_free(line_t *lp) 2704 { 2705 if (lp == NULL) 2706 return; 2707 2708 if (lp->cmd) 2709 free(lp->cmd); 2710 if (lp->sep) 2711 free(lp->sep); 2712 if (lp->arg) 2713 free(lp->arg); 2714 if (lp->line) 2715 free(lp->line); 2716 free(lp); 2717 } 2718 2719 static void 2720 linelist_free(line_t *start) 2721 { 2722 line_t *lp; 2723 2724 while (start) { 2725 lp = start; 2726 start = start->next; 2727 line_free(lp); 2728 } 2729 } 2730 2731 static void 2732 filelist_free(filelist_t *flistp) 2733 { 2734 linelist_free(flistp->head); 2735 flistp->head = NULL; 2736 flistp->tail = NULL; 2737 } 2738 2739 static void 2740 menu_free(menu_t *mp) 2741 { 2742 assert(mp); 2743 2744 if (mp->start) 2745 linelist_free(mp->start); 2746 free(mp); 2747 2748 } 2749 2750 /* 2751 * Utility routines 2752 */ 2753 2754 2755 /* 2756 * Returns 0 on success 2757 * Any other value indicates an error 2758 */ 2759 static int 2760 exec_cmd(char *cmdline, char *output, int64_t osize) 2761 { 2762 char buf[BUFSIZ]; 2763 int ret; 2764 FILE *ptr; 2765 size_t len; 2766 sigset_t set; 2767 void (*disp)(int); 2768 2769 /* 2770 * For security 2771 * - only absolute paths are allowed 2772 * - set IFS to space and tab 2773 */ 2774 if (*cmdline != '/') { 2775 bam_error(ABS_PATH_REQ, cmdline); 2776 return (-1); 2777 } 2778 (void) putenv("IFS= \t"); 2779 2780 /* 2781 * We may have been exec'ed with SIGCHLD blocked 2782 * unblock it here 2783 */ 2784 (void) sigemptyset(&set); 2785 (void) sigaddset(&set, SIGCHLD); 2786 if (sigprocmask(SIG_UNBLOCK, &set, NULL) != 0) { 2787 bam_error(CANT_UNBLOCK_SIGCHLD, strerror(errno)); 2788 return (-1); 2789 } 2790 2791 /* 2792 * Set SIGCHLD disposition to SIG_DFL for popen/pclose 2793 */ 2794 disp = sigset(SIGCHLD, SIG_DFL); 2795 if (disp == SIG_ERR) { 2796 bam_error(FAILED_SIG, strerror(errno)); 2797 return (-1); 2798 } 2799 if (disp == SIG_HOLD) { 2800 bam_error(BLOCKED_SIG, cmdline); 2801 return (-1); 2802 } 2803 2804 ptr = popen(cmdline, "r"); 2805 if (ptr == NULL) { 2806 bam_error(POPEN_FAIL, cmdline, strerror(errno)); 2807 return (-1); 2808 } 2809 2810 /* 2811 * If we simply do a pclose() following a popen(), pclose() 2812 * will close the reader end of the pipe immediately even 2813 * if the child process has not started/exited. pclose() 2814 * does wait for cmd to terminate before returning though. 2815 * When the executed command writes its output to the pipe 2816 * there is no reader process and the command dies with 2817 * SIGPIPE. To avoid this we read repeatedly until read 2818 * terminates with EOF. This indicates that the command 2819 * (writer) has closed the pipe and we can safely do a 2820 * pclose(). 2821 * 2822 * Since pclose() does wait for the command to exit, 2823 * we can safely reap the exit status of the command 2824 * from the value returned by pclose() 2825 */ 2826 while (fgets(buf, sizeof (buf), ptr) != NULL) { 2827 /* if (bam_verbose) XXX */ 2828 bam_print(PRINT_NO_NEWLINE, buf); 2829 if (output && osize > 0) { 2830 (void) snprintf(output, osize, "%s", buf); 2831 len = strlen(buf); 2832 output += len; 2833 osize -= len; 2834 } 2835 } 2836 2837 ret = pclose(ptr); 2838 if (ret == -1) { 2839 bam_error(PCLOSE_FAIL, cmdline, strerror(errno)); 2840 return (-1); 2841 } 2842 2843 if (WIFEXITED(ret)) { 2844 return (WEXITSTATUS(ret)); 2845 } else { 2846 bam_error(EXEC_FAIL, cmdline, ret); 2847 return (-1); 2848 } 2849 } 2850 2851 /* 2852 * Since this function returns -1 on error 2853 * it cannot be used to convert -1. However, 2854 * that is sufficient for what we need. 2855 */ 2856 static long 2857 s_strtol(char *str) 2858 { 2859 long l; 2860 char *res = NULL; 2861 2862 if (str == NULL) { 2863 return (-1); 2864 } 2865 2866 errno = 0; 2867 l = strtol(str, &res, 10); 2868 if (errno || *res != '\0') { 2869 return (-1); 2870 } 2871 2872 return (l); 2873 } 2874 2875 /* 2876 * Wrapper around fputs, that adds a newline (since fputs doesn't) 2877 */ 2878 static int 2879 s_fputs(char *str, FILE *fp) 2880 { 2881 char linebuf[BAM_MAXLINE]; 2882 2883 (void) snprintf(linebuf, sizeof (linebuf), "%s\n", str); 2884 return (fputs(linebuf, fp)); 2885 } 2886 2887 /* 2888 * Wrapper around fgets, that strips newlines returned by fgets 2889 */ 2890 static char * 2891 s_fgets(char *buf, int buflen, FILE *fp) 2892 { 2893 int n; 2894 2895 buf = fgets(buf, buflen, fp); 2896 if (buf) { 2897 n = strlen(buf); 2898 if (n == buflen - 1 && buf[n-1] != '\n') 2899 bam_error(TOO_LONG, buflen - 1, buf); 2900 buf[n-1] = (buf[n-1] == '\n') ? '\0' : buf[n-1]; 2901 } 2902 2903 return (buf); 2904 } 2905 2906 static void * 2907 s_calloc(size_t nelem, size_t sz) 2908 { 2909 void *ptr; 2910 2911 ptr = calloc(nelem, sz); 2912 if (ptr == NULL) { 2913 bam_error(NO_MEM, nelem*sz); 2914 bam_exit(1); 2915 } 2916 return (ptr); 2917 } 2918 2919 static char * 2920 s_strdup(char *str) 2921 { 2922 char *ptr; 2923 2924 if (str == NULL) 2925 return (NULL); 2926 2927 ptr = strdup(str); 2928 if (ptr == NULL) { 2929 bam_error(NO_MEM, strlen(str) + 1); 2930 bam_exit(1); 2931 } 2932 return (ptr); 2933 } 2934 2935 /* 2936 * Returns 1 if amd64 (or sparc, for syncing x86 diskless clients) 2937 * Returns 0 otherwise 2938 */ 2939 static int 2940 is_amd64(void) 2941 { 2942 static int amd64 = -1; 2943 char isabuf[257]; /* from sysinfo(2) manpage */ 2944 2945 if (amd64 != -1) 2946 return (amd64); 2947 2948 if (sysinfo(SI_ISALIST, isabuf, sizeof (isabuf)) > 0 && 2949 strncmp(isabuf, "amd64 ", strlen("amd64 ")) == 0) 2950 amd64 = 1; 2951 else if (strstr(isabuf, "i386") == NULL) 2952 amd64 = 1; /* diskless server */ 2953 else 2954 amd64 = 0; 2955 2956 return (amd64); 2957 } 2958 2959 static void 2960 append_to_flist(filelist_t *flistp, char *s) 2961 { 2962 line_t *lp; 2963 2964 lp = s_calloc(1, sizeof (line_t)); 2965 lp->line = s_strdup(s); 2966 if (flistp->head == NULL) 2967 flistp->head = lp; 2968 else 2969 flistp->tail->next = lp; 2970 flistp->tail = lp; 2971 } 2972