1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 /* 22 * Copyright 2006 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 #pragma ident "%Z%%M% %I% %E% SMI" 27 28 /* 29 * bootadm(1M) is a new utility for managing bootability of 30 * Solaris *Newboot* environments. It has two primary tasks: 31 * - Allow end users to manage bootability of Newboot Solaris instances 32 * - Provide services to other subsystems in Solaris (primarily Install) 33 */ 34 35 /* Headers */ 36 #include <stdio.h> 37 #include <errno.h> 38 #include <stdlib.h> 39 #include <string.h> 40 #include <unistd.h> 41 #include <sys/types.h> 42 #include <sys/stat.h> 43 #include <stdarg.h> 44 #include <limits.h> 45 #include <signal.h> 46 #include <sys/wait.h> 47 #include <sys/mnttab.h> 48 #include <sys/statvfs.h> 49 #include <libnvpair.h> 50 #include <ftw.h> 51 #include <fcntl.h> 52 #include <strings.h> 53 #include <sys/systeminfo.h> 54 #include <sys/dktp/fdisk.h> 55 #include <sys/param.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 struct line *prev; 101 } line_t; 102 103 typedef struct entry { 104 struct entry *next; 105 struct entry *prev; 106 line_t *start; 107 line_t *end; 108 } entry_t; 109 110 typedef struct { 111 line_t *start; 112 line_t *end; 113 line_t *curdefault; /* line containing default */ 114 line_t *olddefault; /* old default line (commented) */ 115 entry_t *entries; /* os entries */ 116 } menu_t; 117 118 typedef enum { 119 OPT_ABSENT = 0, /* No option */ 120 OPT_REQ, /* option required */ 121 OPT_OPTIONAL /* option may or may not be present */ 122 } option_t; 123 124 typedef enum { 125 BAM_ERROR = -1, /* Must be negative. add_boot_entry() depends on it */ 126 BAM_SUCCESS = 0, 127 BAM_WRITE = 2 128 } error_t; 129 130 typedef struct { 131 char *subcmd; 132 option_t option; 133 error_t (*handler)(); 134 int unpriv; /* is this an unprivileged command */ 135 } subcmd_defn_t; 136 137 138 #define BAM_MAXLINE 8192 139 140 #define LINE_INIT 0 /* lineNum initial value */ 141 #define ENTRY_INIT -1 /* entryNum initial value */ 142 #define ALL_ENTRIES -2 /* selects all boot entries */ 143 144 #define GRUB_DIR "/boot/grub" 145 #define MULTI_BOOT "/platform/i86pc/multiboot" 146 #define BOOT_ARCHIVE "/platform/i86pc/boot_archive" 147 #define GRUB_MENU "/boot/grub/menu.lst" 148 #define MENU_TMP "/boot/grub/menu.lst.tmp" 149 #define RAMDISK_SPECIAL "/ramdisk" 150 #define STUBBOOT "/stubboot" 151 152 /* lock related */ 153 #define BAM_LOCK_FILE "/var/run/bootadm.lock" 154 #define LOCK_FILE_PERMS (S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) 155 156 #define CREATE_RAMDISK "/boot/solaris/bin/create_ramdisk" 157 #define CREATE_DISKMAP "/boot/solaris/bin/create_diskmap" 158 #define GRUBDISK_MAP "/var/run/solaris_grubdisk.map" 159 160 #define GRUB_slice "/etc/lu/GRUB_slice" 161 #define GRUB_root "/etc/lu/GRUB_root" 162 #define GRUB_backup_menu "/etc/lu/GRUB_backup_menu" 163 #define GRUB_slice_mntpt "/tmp/GRUB_slice_mntpt" 164 #define LU_ACTIVATE_FILE "/etc/lu/DelayUpdate/activate.sh" 165 #define GRUB_fdisk "/etc/lu/GRUB_fdisk" 166 #define GRUB_fdisk_target "/etc/lu/GRUB_fdisk_target" 167 168 #define INSTALLGRUB "/sbin/installgrub" 169 #define STAGE1 "/boot/grub/stage1" 170 #define STAGE2 "/boot/grub/stage2" 171 172 /* 173 * Default file attributes 174 */ 175 #define DEFAULT_DEV_MODE 0644 /* default permissions */ 176 #define DEFAULT_DEV_UID 0 /* user root */ 177 #define DEFAULT_DEV_GID 3 /* group sys */ 178 179 /* 180 * Menu related 181 * menu_cmd_t and menu_cmds must be kept in sync 182 */ 183 typedef enum { 184 DEFAULT_CMD = 0, 185 TIMEOUT_CMD, 186 TITLE_CMD, 187 ROOT_CMD, 188 KERNEL_CMD, 189 MODULE_CMD, 190 SEP_CMD, 191 COMMENT_CMD 192 } menu_cmd_t; 193 194 static char *menu_cmds[] = { 195 "default", /* DEFAULT_CMD */ 196 "timeout", /* TIMEOUT_CMD */ 197 "title", /* TITLE_CMD */ 198 "root", /* ROOT_CMD */ 199 "kernel", /* KERNEL_CMD */ 200 "module", /* MODULE_CMD */ 201 " ", /* SEP_CMD */ 202 "#", /* COMMENT_CMD */ 203 NULL 204 }; 205 206 #define OPT_ENTRY_NUM "entry" 207 208 /* 209 * archive related 210 */ 211 typedef struct { 212 line_t *head; 213 line_t *tail; 214 } filelist_t; 215 216 #define BOOT_FILE_LIST "boot/solaris/filelist.ramdisk" 217 #define ETC_FILE_LIST "etc/boot/solaris/filelist.ramdisk" 218 219 #define FILE_STAT "boot/solaris/filestat.ramdisk" 220 #define FILE_STAT_TMP "boot/solaris/filestat.ramdisk.tmp" 221 #define DIR_PERMS (S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH) 222 #define FILE_STAT_MODE (S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) 223 224 #define BAM_HDR "---------- ADDED BY BOOTADM - DO NOT EDIT ----------" 225 #define BAM_FTR "---------------------END BOOTADM--------------------" 226 #define BAM_OLDDEF "BOOTADM SAVED DEFAULT: " 227 228 /* Globals */ 229 static char *prog; 230 static subcmd_t bam_cmd; 231 static char *bam_root; 232 static int bam_rootlen; 233 static int bam_root_readonly; 234 static int bam_alt_root; 235 static char *bam_subcmd; 236 static char *bam_opt; 237 static int bam_debug; 238 static char **bam_argv; 239 static int bam_argc; 240 static int bam_force; 241 static int bam_verbose; 242 static int bam_check; 243 static int bam_smf_check; 244 static int bam_lock_fd = -1; 245 static char rootbuf[PATH_MAX] = "/"; 246 static int bam_update_all; 247 248 /* function prototypes */ 249 static void parse_args_internal(int argc, char *argv[]); 250 static void parse_args(int argc, char *argv[]); 251 static error_t bam_menu(char *subcmd, char *opt, int argc, char *argv[]); 252 static error_t bam_archive(char *subcmd, char *opt); 253 254 static void bam_error(char *format, ...); 255 static void bam_print(char *format, ...); 256 static void bam_exit(int excode); 257 static void bam_lock(void); 258 static void bam_unlock(void); 259 260 static int exec_cmd(char *cmdline, char *output, int64_t osize); 261 static error_t read_globals(menu_t *mp, char *menu_path, 262 char *globalcmd, int quiet); 263 264 static menu_t *menu_read(char *menu_path); 265 static error_t menu_write(char *root, menu_t *mp); 266 static void linelist_free(line_t *start); 267 static void menu_free(menu_t *mp); 268 static void line_free(line_t *lp); 269 static void filelist_free(filelist_t *flistp); 270 static error_t list2file(char *root, char *tmp, 271 char *final, line_t *start); 272 static error_t list_entry(menu_t *mp, char *menu_path, char *opt); 273 static error_t delete_all_entries(menu_t *mp, char *menu_path, char *opt); 274 static error_t update_entry(menu_t *mp, char *root, char *opt); 275 static error_t update_temp(menu_t *mp, char *root, char *opt); 276 277 static error_t update_archive(char *root, char *opt); 278 static error_t list_archive(char *root, char *opt); 279 static error_t update_all(char *root, char *opt); 280 static error_t read_list(char *root, filelist_t *flistp); 281 static error_t set_global(menu_t *mp, char *globalcmd, int val); 282 static error_t set_option(menu_t *mp, char *globalcmd, char *opt); 283 284 static long s_strtol(char *str); 285 static char *s_fgets(char *buf, int n, FILE *fp); 286 static int s_fputs(char *str, FILE *fp); 287 288 static void *s_calloc(size_t nelem, size_t sz); 289 static char *s_strdup(char *str); 290 static int is_readonly(char *); 291 static int is_amd64(void); 292 static void append_to_flist(filelist_t *, char *); 293 294 #if defined(__sparc) 295 static void sparc_abort(void); 296 #endif 297 298 /* Menu related sub commands */ 299 static subcmd_defn_t menu_subcmds[] = { 300 "set_option", OPT_OPTIONAL, set_option, 0, /* PUB */ 301 "list_entry", OPT_OPTIONAL, list_entry, 1, /* PUB */ 302 "delete_all_entries", OPT_ABSENT, delete_all_entries, 0, /* PVT */ 303 "update_entry", OPT_REQ, update_entry, 0, /* menu */ 304 "update_temp", OPT_OPTIONAL, update_temp, 0, /* reboot */ 305 NULL, 0, NULL, 0 /* must be last */ 306 }; 307 308 /* Archive related sub commands */ 309 static subcmd_defn_t arch_subcmds[] = { 310 "update", OPT_ABSENT, update_archive, 0, /* PUB */ 311 "update_all", OPT_ABSENT, update_all, 0, /* PVT */ 312 "list", OPT_OPTIONAL, list_archive, 1, /* PUB */ 313 NULL, 0, NULL, 0 /* must be last */ 314 }; 315 316 static struct { 317 nvlist_t *new_nvlp; 318 nvlist_t *old_nvlp; 319 int need_update; 320 } walk_arg; 321 322 323 struct safefile { 324 char *name; 325 struct safefile *next; 326 }; 327 328 struct safefile *safefiles = NULL; 329 #define NEED_UPDATE_FILE "/etc/svc/volatile/boot_archive_needs_update" 330 331 static void 332 usage(void) 333 { 334 (void) fprintf(stderr, "USAGE:\n"); 335 336 337 /* archive usage */ 338 (void) fprintf(stderr, "\t%s update-archive [-vn] [-R altroot]\n", 339 prog); 340 (void) fprintf(stderr, "\t%s list-archive [-R altroot]\n", prog); 341 #ifndef __sparc 342 /* x86 only */ 343 (void) fprintf(stderr, "\t%s set-menu [-R altroot] key=value\n", prog); 344 (void) fprintf(stderr, "\t%s list-menu [-R altroot]\n", prog); 345 #endif 346 } 347 348 int 349 main(int argc, char *argv[]) 350 { 351 error_t ret; 352 353 (void) setlocale(LC_ALL, ""); 354 (void) textdomain(TEXT_DOMAIN); 355 356 if ((prog = strrchr(argv[0], '/')) == NULL) { 357 prog = argv[0]; 358 } else { 359 prog++; 360 } 361 362 363 /* 364 * Don't depend on caller's umask 365 */ 366 (void) umask(0022); 367 368 parse_args(argc, argv); 369 370 #if defined(__sparc) 371 /* 372 * There are only two valid invocations of bootadm 373 * on SPARC: 374 * 375 * - SPARC diskless server creating boot_archive for i386 clients 376 * - archive creation call during reboot of a SPARC system 377 * 378 * The latter should be a NOP 379 */ 380 if (bam_cmd != BAM_ARCHIVE) { 381 sparc_abort(); 382 } 383 #endif 384 385 switch (bam_cmd) { 386 case BAM_MENU: 387 ret = bam_menu(bam_subcmd, bam_opt, bam_argc, bam_argv); 388 break; 389 case BAM_ARCHIVE: 390 ret = bam_archive(bam_subcmd, bam_opt); 391 break; 392 default: 393 usage(); 394 bam_exit(1); 395 } 396 397 if (ret != BAM_SUCCESS) 398 bam_exit(1); 399 400 bam_unlock(); 401 return (0); 402 } 403 404 #if defined(__sparc) 405 406 static void 407 sparc_abort(void) 408 { 409 bam_error(NOT_ON_SPARC); 410 bam_exit(1); 411 } 412 413 #endif 414 415 /* 416 * Equivalence of public and internal commands: 417 * update-archive -- -a update 418 * list-archive -- -a list 419 * set-menu -- -m set_option 420 * list-menu -- -m list_entry 421 * update-menu -- -m update_entry 422 */ 423 static struct cmd_map { 424 char *bam_cmdname; 425 int bam_cmd; 426 char *bam_subcmd; 427 } cmd_map[] = { 428 { "update-archive", BAM_ARCHIVE, "update"}, 429 { "list-archive", BAM_ARCHIVE, "list"}, 430 { "set-menu", BAM_MENU, "set_option"}, 431 { "list-menu", BAM_MENU, "list_entry"}, 432 { "update-menu", BAM_MENU, "update_entry"}, 433 { NULL, 0, NULL} 434 }; 435 436 /* 437 * Commands syntax published in bootadm(1M) are parsed here 438 */ 439 static void 440 parse_args(int argc, char *argv[]) 441 { 442 struct cmd_map *cmp = cmd_map; 443 444 /* command conforming to the final spec */ 445 if (argc > 1 && argv[1][0] != '-') { 446 /* 447 * Map commands to internal table. 448 */ 449 while (cmp->bam_cmdname) { 450 if (strcmp(argv[1], cmp->bam_cmdname) == 0) { 451 bam_cmd = cmp->bam_cmd; 452 bam_subcmd = cmp->bam_subcmd; 453 break; 454 } 455 cmp++; 456 } 457 if (cmp->bam_cmdname == NULL) { 458 usage(); 459 bam_exit(1); 460 } 461 argc--; 462 argv++; 463 } 464 465 parse_args_internal(argc, argv); 466 } 467 468 /* 469 * A combination of public and private commands are parsed here. 470 * The internal syntax and the corresponding functionality are: 471 * -a update -- update-archive 472 * -a list -- list-archive 473 * -a update-all -- (reboot to sync all mounted OS archive) 474 * -m update_entry -- update-menu 475 * -m list_entry -- list-menu 476 * -m update_temp -- (reboot -- [boot-args]) 477 * -m delete_all_entries -- (called from install) 478 */ 479 static void 480 parse_args_internal(int argc, char *argv[]) 481 { 482 int c, error; 483 extern char *optarg; 484 extern int optind, opterr; 485 486 /* Suppress error message from getopt */ 487 opterr = 0; 488 489 error = 0; 490 while ((c = getopt(argc, argv, "a:d:fm:no:vCR:")) != -1) { 491 switch (c) { 492 case 'a': 493 if (bam_cmd) { 494 error = 1; 495 bam_error(MULT_CMDS, c); 496 } 497 bam_cmd = BAM_ARCHIVE; 498 bam_subcmd = optarg; 499 break; 500 case 'd': 501 if (bam_debug) { 502 error = 1; 503 bam_error(DUP_OPT, c); 504 } 505 bam_debug = s_strtol(optarg); 506 break; 507 case 'f': 508 if (bam_force) { 509 error = 1; 510 bam_error(DUP_OPT, c); 511 } 512 bam_force = 1; 513 break; 514 case 'm': 515 if (bam_cmd) { 516 error = 1; 517 bam_error(MULT_CMDS, c); 518 } 519 bam_cmd = BAM_MENU; 520 bam_subcmd = optarg; 521 break; 522 case 'n': 523 if (bam_check) { 524 error = 1; 525 bam_error(DUP_OPT, c); 526 } 527 bam_check = 1; 528 break; 529 case 'o': 530 if (bam_opt) { 531 error = 1; 532 bam_error(DUP_OPT, c); 533 } 534 bam_opt = optarg; 535 break; 536 case 'v': 537 if (bam_verbose) { 538 error = 1; 539 bam_error(DUP_OPT, c); 540 } 541 bam_verbose = 1; 542 break; 543 case 'C': 544 bam_smf_check = 1; 545 break; 546 case 'R': 547 if (bam_root) { 548 error = 1; 549 bam_error(DUP_OPT, c); 550 break; 551 } else if (realpath(optarg, rootbuf) == NULL) { 552 error = 1; 553 bam_error(CANT_RESOLVE, optarg, 554 strerror(errno)); 555 break; 556 } 557 bam_alt_root = 1; 558 bam_root = rootbuf; 559 bam_rootlen = strlen(rootbuf); 560 break; 561 case '?': 562 error = 1; 563 bam_error(BAD_OPT, optopt); 564 break; 565 default : 566 error = 1; 567 bam_error(BAD_OPT, c); 568 break; 569 } 570 } 571 572 /* 573 * A command option must be specfied 574 */ 575 if (!bam_cmd) { 576 if (bam_opt && strcmp(bam_opt, "all") == 0) { 577 usage(); 578 bam_exit(0); 579 } 580 bam_error(NEED_CMD); 581 error = 1; 582 } 583 584 if (error) { 585 usage(); 586 bam_exit(1); 587 } 588 589 if (optind > argc) { 590 bam_error(INT_ERROR, "parse_args"); 591 bam_exit(1); 592 } else if (optind < argc) { 593 bam_argv = &argv[optind]; 594 bam_argc = argc - optind; 595 } 596 597 /* 598 * -n implies verbose mode 599 */ 600 if (bam_check) 601 bam_verbose = 1; 602 } 603 604 static error_t 605 check_subcmd_and_options( 606 char *subcmd, 607 char *opt, 608 subcmd_defn_t *table, 609 error_t (**fp)()) 610 { 611 int i; 612 613 if (subcmd == NULL) { 614 bam_error(NEED_SUBCMD); 615 return (BAM_ERROR); 616 } 617 618 if (bam_argc != 0 || bam_argv) { 619 if (strcmp(subcmd, "set_option") != 0 || bam_argc != 1) { 620 bam_error(TRAILING_ARGS); 621 usage(); 622 return (BAM_ERROR); 623 } 624 } 625 626 if (bam_root == NULL) { 627 bam_root = rootbuf; 628 bam_rootlen = 1; 629 } 630 631 /* verify that subcmd is valid */ 632 for (i = 0; table[i].subcmd != NULL; i++) { 633 if (strcmp(table[i].subcmd, subcmd) == 0) 634 break; 635 } 636 637 if (table[i].subcmd == NULL) { 638 bam_error(INVALID_SUBCMD, subcmd); 639 return (BAM_ERROR); 640 } 641 642 if (table[i].unpriv == 0 && geteuid() != 0) { 643 bam_error(MUST_BE_ROOT); 644 return (BAM_ERROR); 645 } 646 647 /* 648 * Currently only privileged commands need a lock 649 */ 650 if (table[i].unpriv == 0) 651 bam_lock(); 652 653 /* subcmd verifies that opt is appropriate */ 654 if (table[i].option != OPT_OPTIONAL) { 655 if ((table[i].option == OPT_REQ) ^ (opt != NULL)) { 656 if (opt) 657 bam_error(NO_OPT_REQ, subcmd); 658 else 659 bam_error(MISS_OPT, subcmd); 660 return (BAM_ERROR); 661 } 662 } 663 664 *fp = table[i].handler; 665 666 return (BAM_SUCCESS); 667 } 668 669 670 static char * 671 mount_grub_slice(int *mnted, char **physlice, char **logslice, char **fs_type) 672 { 673 struct extmnttab mnt; 674 struct stat sb; 675 char buf[BAM_MAXLINE], dev[PATH_MAX], phys[PATH_MAX], fstype[32]; 676 char cmd[PATH_MAX]; 677 char *mntpt; 678 int p, l, f; 679 FILE *fp; 680 681 assert(mnted); 682 *mnted = 0; 683 684 /* 685 * physlice, logslice, fs_type args may be NULL 686 */ 687 if (physlice) 688 *physlice = NULL; 689 if (logslice) 690 *logslice = NULL; 691 if (fs_type) 692 *fs_type = NULL; 693 694 if (stat(GRUB_slice, &sb) != 0) { 695 bam_error(MISSING_SLICE_FILE, GRUB_slice, strerror(errno)); 696 return (NULL); 697 } 698 699 fp = fopen(GRUB_slice, "r"); 700 if (fp == NULL) { 701 bam_error(OPEN_FAIL, GRUB_slice, strerror(errno)); 702 return (NULL); 703 } 704 705 dev[0] = fstype[0] = phys[0] = '\0'; 706 p = sizeof ("PHYS_SLICE=") - 1; 707 l = sizeof ("LOG_SLICE=") - 1; 708 f = sizeof ("LOG_FSTYP=") - 1; 709 while (s_fgets(buf, sizeof (buf), fp) != NULL) { 710 if (strncmp(buf, "PHYS_SLICE=", p) == 0) { 711 (void) strlcpy(phys, buf + p, sizeof (phys)); 712 continue; 713 } 714 if (strncmp(buf, "LOG_SLICE=", l) == 0) { 715 (void) strlcpy(dev, buf + l, sizeof (dev)); 716 continue; 717 } 718 if (strncmp(buf, "LOG_FSTYP=", f) == 0) { 719 (void) strlcpy(fstype, buf + f, sizeof (fstype)); 720 continue; 721 } 722 } 723 (void) fclose(fp); 724 725 if (dev[0] == '\0' || fstype[0] == '\0' || phys[0] == '\0') { 726 bam_error(BAD_SLICE_FILE, GRUB_slice); 727 return (NULL); 728 } 729 730 if (physlice) { 731 *physlice = s_strdup(phys); 732 } 733 if (logslice) { 734 *logslice = s_strdup(dev); 735 } 736 if (fs_type) { 737 *fs_type = s_strdup(fstype); 738 } 739 740 /* 741 * Check if the slice is already mounted 742 */ 743 fp = fopen(MNTTAB, "r"); 744 if (fp == NULL) { 745 bam_error(OPEN_FAIL, MNTTAB, strerror(errno)); 746 goto error; 747 } 748 749 resetmnttab(fp); 750 751 mntpt = NULL; 752 while (getextmntent(fp, &mnt, sizeof (mnt)) == 0) { 753 if (strcmp(mnt.mnt_special, dev) == 0) { 754 mntpt = s_strdup(mnt.mnt_mountp); 755 break; 756 } 757 } 758 759 (void) fclose(fp); 760 761 if (mntpt) { 762 return (mntpt); 763 } 764 765 766 /* 767 * GRUB slice is not mounted, we need to mount it now. 768 * First create the mountpoint 769 */ 770 mntpt = s_calloc(1, PATH_MAX); 771 (void) snprintf(mntpt, PATH_MAX, "%s.%d", GRUB_slice_mntpt, getpid()); 772 if (mkdir(mntpt, 0755) == -1 && errno != EEXIST) { 773 bam_error(MKDIR_FAILED, mntpt, strerror(errno)); 774 free(mntpt); 775 goto error; 776 } 777 778 (void) snprintf(cmd, sizeof (cmd), "/sbin/mount -F %s %s %s", 779 fstype, dev, mntpt); 780 781 if (exec_cmd(cmd, NULL, 0) != 0) { 782 bam_error(MOUNT_FAILED, dev, fstype); 783 if (rmdir(mntpt) != 0) { 784 bam_error(RMDIR_FAILED, mntpt, strerror(errno)); 785 } 786 free(mntpt); 787 goto error; 788 } 789 790 *mnted = 1; 791 return (mntpt); 792 793 error: 794 if (physlice) { 795 free(*physlice); 796 *physlice = NULL; 797 } 798 if (logslice) { 799 free(*logslice); 800 *logslice = NULL; 801 } 802 if (fs_type) { 803 free(*fs_type); 804 *fs_type = NULL; 805 } 806 return (NULL); 807 } 808 809 static void 810 umount_grub_slice( 811 int mnted, 812 char *mntpt, 813 char *physlice, 814 char *logslice, 815 char *fs_type) 816 { 817 char cmd[PATH_MAX]; 818 819 /* 820 * If we have not dealt with GRUB slice 821 * we have nothing to do - just return. 822 */ 823 if (mntpt == NULL) 824 return; 825 826 827 /* 828 * If we mounted the filesystem earlier in mount_grub_slice() 829 * unmount it now. 830 */ 831 if (mnted) { 832 (void) snprintf(cmd, sizeof (cmd), "/sbin/umount %s", 833 mntpt); 834 if (exec_cmd(cmd, NULL, 0) != 0) { 835 bam_error(UMOUNT_FAILED, mntpt); 836 } 837 if (rmdir(mntpt) != 0) { 838 bam_error(RMDIR_FAILED, mntpt, strerror(errno)); 839 } 840 } 841 842 if (physlice) 843 free(physlice); 844 if (logslice) 845 free(logslice); 846 if (fs_type) 847 free(fs_type); 848 849 free(mntpt); 850 } 851 852 static char * 853 use_stubboot(void) 854 { 855 int mnted; 856 struct stat sb; 857 struct extmnttab mnt; 858 FILE *fp; 859 char cmd[PATH_MAX]; 860 861 if (stat(STUBBOOT, &sb) != 0) { 862 bam_error(STUBBOOT_DIR_NOT_FOUND); 863 return (NULL); 864 } 865 866 /* 867 * Check if stubboot is mounted. If not, mount it 868 */ 869 fp = fopen(MNTTAB, "r"); 870 if (fp == NULL) { 871 bam_error(OPEN_FAIL, MNTTAB, strerror(errno)); 872 return (NULL); 873 } 874 875 resetmnttab(fp); 876 877 mnted = 0; 878 while (getextmntent(fp, &mnt, sizeof (mnt)) == 0) { 879 if (strcmp(mnt.mnt_mountp, STUBBOOT) == 0) { 880 mnted = 1; 881 break; 882 } 883 } 884 885 (void) fclose(fp); 886 887 if (mnted) 888 return (STUBBOOT); 889 890 /* 891 * Stubboot is not mounted, mount it now. 892 * It should exist in /etc/vfstab 893 */ 894 (void) snprintf(cmd, sizeof (cmd), "/sbin/mount %s", 895 STUBBOOT); 896 if (exec_cmd(cmd, NULL, 0) != 0) { 897 bam_error(MOUNT_MNTPT_FAILED, STUBBOOT); 898 return (NULL); 899 } 900 901 return (STUBBOOT); 902 } 903 904 static void 905 disp_active_menu_locn(char *menu_path, char *logslice, char *fstype, int mnted) 906 { 907 /* 908 * Check if we did a temp mount of an unmounted device. 909 * If yes, print the block device and fstype for that device 910 * else it is already mounted, so we print the path to the GRUB menu. 911 */ 912 if (mnted) { 913 bam_print(GRUB_MENU_DEVICE, logslice); 914 bam_print(GRUB_MENU_FSTYPE, fstype); 915 } else { 916 bam_print(GRUB_MENU_PATH, menu_path); 917 } 918 } 919 920 /* 921 * NOTE: A single "/" is also considered a trailing slash and will 922 * be deleted. 923 */ 924 static void 925 elide_trailing_slash(const char *src, char *dst, size_t dstsize) 926 { 927 size_t dstlen; 928 929 assert(src); 930 assert(dst); 931 932 (void) strlcpy(dst, src, dstsize); 933 934 dstlen = strlen(dst); 935 if (dst[dstlen - 1] == '/') { 936 dst[dstlen - 1] = '\0'; 937 } 938 } 939 940 static error_t 941 bam_menu(char *subcmd, char *opt, int largc, char *largv[]) 942 { 943 error_t ret; 944 char menu_path[PATH_MAX]; 945 menu_t *menu; 946 char *mntpt, *menu_root, *logslice, *fstype; 947 struct stat sb; 948 int mnted; /* set if we did a mount */ 949 error_t (*f)(menu_t *mp, char *menu_path, char *opt); 950 951 /* 952 * Check arguments 953 */ 954 ret = check_subcmd_and_options(subcmd, opt, menu_subcmds, &f); 955 if (ret == BAM_ERROR) { 956 return (BAM_ERROR); 957 } 958 959 mntpt = NULL; 960 mnted = 0; 961 logslice = fstype = NULL; 962 963 /* 964 * If the user provides an alternate root, we 965 * assume they know what they are doing and we 966 * use it. Else we check if there is an 967 * alternate location (other than /boot/grub) 968 * for the GRUB menu 969 */ 970 if (bam_alt_root) { 971 menu_root = bam_root; 972 } else if (stat(GRUB_slice, &sb) == 0) { 973 mntpt = mount_grub_slice(&mnted, NULL, &logslice, &fstype); 974 menu_root = mntpt; 975 } else if (stat(STUBBOOT, &sb) == 0) { 976 menu_root = use_stubboot(); 977 } else { 978 menu_root = bam_root; 979 } 980 981 if (menu_root == NULL) { 982 bam_error(CANNOT_LOCATE_GRUB_MENU); 983 return (BAM_ERROR); 984 } 985 986 elide_trailing_slash(menu_root, menu_path, sizeof (menu_path)); 987 (void) strlcat(menu_path, GRUB_MENU, sizeof (menu_path)); 988 989 /* 990 * If listing the menu, display the active menu 991 * location 992 */ 993 if (strcmp(subcmd, "list_entry") == 0) { 994 disp_active_menu_locn(menu_path, logslice, fstype, mnted); 995 } 996 997 menu = menu_read(menu_path); 998 assert(menu); 999 1000 /* 1001 * Special handling for setting timeout and default 1002 */ 1003 if (strcmp(subcmd, "set_option") == 0) { 1004 if (largc != 1 || largv[0] == NULL) { 1005 usage(); 1006 menu_free(menu); 1007 umount_grub_slice(mnted, mntpt, NULL, logslice, fstype); 1008 return (BAM_ERROR); 1009 } 1010 opt = largv[0]; 1011 } else if (largc != 0) { 1012 usage(); 1013 menu_free(menu); 1014 umount_grub_slice(mnted, mntpt, NULL, logslice, fstype); 1015 return (BAM_ERROR); 1016 } 1017 1018 /* 1019 * Once the sub-cmd handler has run 1020 * only the line field is guaranteed to have valid values 1021 */ 1022 if (strcmp(subcmd, "update_entry") == 0) 1023 ret = f(menu, bam_root, opt); 1024 else 1025 ret = f(menu, menu_path, opt); 1026 if (ret == BAM_WRITE) { 1027 ret = menu_write(menu_root, menu); 1028 } 1029 1030 menu_free(menu); 1031 1032 umount_grub_slice(mnted, mntpt, NULL, logslice, fstype); 1033 1034 return (ret); 1035 } 1036 1037 1038 static error_t 1039 bam_archive( 1040 char *subcmd, 1041 char *opt) 1042 { 1043 error_t ret; 1044 error_t (*f)(char *root, char *opt); 1045 1046 /* 1047 * Add trailing / for archive subcommands 1048 */ 1049 if (rootbuf[strlen(rootbuf) - 1] != '/') 1050 (void) strcat(rootbuf, "/"); 1051 bam_rootlen = strlen(rootbuf); 1052 1053 /* 1054 * Check arguments 1055 */ 1056 ret = check_subcmd_and_options(subcmd, opt, arch_subcmds, &f); 1057 if (ret != BAM_SUCCESS) { 1058 return (BAM_ERROR); 1059 } 1060 1061 #if defined(__sparc) 1062 /* 1063 * A NOP if called on SPARC during reboot 1064 */ 1065 if (strcmp(subcmd, "update_all") == 0) 1066 return (BAM_SUCCESS); 1067 else if (strcmp(subcmd, "update") != 0) 1068 sparc_abort(); 1069 #endif 1070 1071 /* 1072 * Check archive not supported with update_all 1073 * since it is awkward to display out-of-sync 1074 * information for each BE. 1075 */ 1076 if (bam_check && strcmp(subcmd, "update_all") == 0) { 1077 bam_error(CHECK_NOT_SUPPORTED, subcmd); 1078 return (BAM_ERROR); 1079 } 1080 1081 if (strcmp(subcmd, "update_all") == 0) 1082 bam_update_all = 1; 1083 1084 ret = f(bam_root, opt); 1085 1086 bam_update_all = 0; 1087 1088 return (ret); 1089 } 1090 1091 /*PRINTFLIKE1*/ 1092 static void 1093 bam_error(char *format, ...) 1094 { 1095 va_list ap; 1096 1097 va_start(ap, format); 1098 (void) fprintf(stderr, "%s: ", prog); 1099 (void) vfprintf(stderr, format, ap); 1100 va_end(ap); 1101 } 1102 1103 /*PRINTFLIKE1*/ 1104 static void 1105 bam_print(char *format, ...) 1106 { 1107 va_list ap; 1108 1109 va_start(ap, format); 1110 (void) vfprintf(stdout, format, ap); 1111 va_end(ap); 1112 } 1113 1114 static void 1115 bam_exit(int excode) 1116 { 1117 bam_unlock(); 1118 exit(excode); 1119 } 1120 1121 static void 1122 bam_lock(void) 1123 { 1124 struct flock lock; 1125 pid_t pid; 1126 1127 bam_lock_fd = open(BAM_LOCK_FILE, O_CREAT|O_RDWR, LOCK_FILE_PERMS); 1128 if (bam_lock_fd < 0) { 1129 /* 1130 * We may be invoked early in boot for archive verification. 1131 * In this case, root is readonly and /var/run may not exist. 1132 * Proceed without the lock 1133 */ 1134 if (errno == EROFS || errno == ENOENT) { 1135 bam_root_readonly = 1; 1136 return; 1137 } 1138 1139 bam_error(OPEN_FAIL, BAM_LOCK_FILE, strerror(errno)); 1140 bam_exit(1); 1141 } 1142 1143 lock.l_type = F_WRLCK; 1144 lock.l_whence = SEEK_SET; 1145 lock.l_start = 0; 1146 lock.l_len = 0; 1147 1148 if (fcntl(bam_lock_fd, F_SETLK, &lock) == -1) { 1149 if (errno != EACCES && errno != EAGAIN) { 1150 bam_error(LOCK_FAIL, BAM_LOCK_FILE, strerror(errno)); 1151 (void) close(bam_lock_fd); 1152 bam_lock_fd = -1; 1153 bam_exit(1); 1154 } 1155 pid = 0; 1156 (void) pread(bam_lock_fd, &pid, sizeof (pid_t), 0); 1157 bam_print(FILE_LOCKED, pid); 1158 1159 lock.l_type = F_WRLCK; 1160 lock.l_whence = SEEK_SET; 1161 lock.l_start = 0; 1162 lock.l_len = 0; 1163 if (fcntl(bam_lock_fd, F_SETLKW, &lock) == -1) { 1164 bam_error(LOCK_FAIL, BAM_LOCK_FILE, strerror(errno)); 1165 (void) close(bam_lock_fd); 1166 bam_lock_fd = -1; 1167 bam_exit(1); 1168 } 1169 } 1170 1171 /* We own the lock now */ 1172 pid = getpid(); 1173 (void) write(bam_lock_fd, &pid, sizeof (pid)); 1174 } 1175 1176 static void 1177 bam_unlock(void) 1178 { 1179 struct flock unlock; 1180 1181 /* 1182 * NOP if we don't hold the lock 1183 */ 1184 if (bam_lock_fd < 0) { 1185 return; 1186 } 1187 1188 unlock.l_type = F_UNLCK; 1189 unlock.l_whence = SEEK_SET; 1190 unlock.l_start = 0; 1191 unlock.l_len = 0; 1192 1193 if (fcntl(bam_lock_fd, F_SETLK, &unlock) == -1) { 1194 bam_error(UNLOCK_FAIL, BAM_LOCK_FILE, strerror(errno)); 1195 } 1196 1197 if (close(bam_lock_fd) == -1) { 1198 bam_error(CLOSE_FAIL, BAM_LOCK_FILE, strerror(errno)); 1199 } 1200 bam_lock_fd = -1; 1201 } 1202 1203 static error_t 1204 list_archive(char *root, char *opt) 1205 { 1206 filelist_t flist; 1207 filelist_t *flistp = &flist; 1208 line_t *lp; 1209 1210 assert(root); 1211 assert(opt == NULL); 1212 1213 flistp->head = flistp->tail = NULL; 1214 if (read_list(root, flistp) != BAM_SUCCESS) { 1215 return (BAM_ERROR); 1216 } 1217 assert(flistp->head && flistp->tail); 1218 1219 for (lp = flistp->head; lp; lp = lp->next) { 1220 bam_print(PRINT, lp->line); 1221 } 1222 1223 filelist_free(flistp); 1224 1225 return (BAM_SUCCESS); 1226 } 1227 1228 /* 1229 * This routine writes a list of lines to a file. 1230 * The list is *not* freed 1231 */ 1232 static error_t 1233 list2file(char *root, char *tmp, char *final, line_t *start) 1234 { 1235 char tmpfile[PATH_MAX]; 1236 char path[PATH_MAX]; 1237 FILE *fp; 1238 int ret; 1239 struct stat sb; 1240 mode_t mode; 1241 uid_t root_uid; 1242 gid_t sys_gid; 1243 struct passwd *pw; 1244 struct group *gp; 1245 1246 1247 (void) snprintf(path, sizeof (path), "%s%s", root, final); 1248 1249 if (start == NULL) { 1250 if (stat(path, &sb) != -1) { 1251 bam_print(UNLINK_EMPTY, path); 1252 if (unlink(path) != 0) { 1253 bam_error(UNLINK_FAIL, path, strerror(errno)); 1254 return (BAM_ERROR); 1255 } else { 1256 return (BAM_SUCCESS); 1257 } 1258 } 1259 } 1260 1261 /* 1262 * Preserve attributes of existing file if possible, 1263 * otherwise ask the system for uid/gid of root/sys. 1264 * If all fails, fall back on hard-coded defaults. 1265 */ 1266 if (stat(path, &sb) != -1) { 1267 mode = sb.st_mode; 1268 root_uid = sb.st_uid; 1269 sys_gid = sb.st_gid; 1270 } else { 1271 mode = DEFAULT_DEV_MODE; 1272 if ((pw = getpwnam(DEFAULT_DEV_USER)) != NULL) { 1273 root_uid = pw->pw_uid; 1274 } else { 1275 if (bam_verbose) 1276 bam_error(CANT_FIND_USER, 1277 DEFAULT_DEV_USER, DEFAULT_DEV_UID); 1278 root_uid = (uid_t)DEFAULT_DEV_UID; 1279 } 1280 if ((gp = getgrnam(DEFAULT_DEV_GROUP)) != NULL) { 1281 sys_gid = gp->gr_gid; 1282 } else { 1283 if (bam_verbose) 1284 bam_error(CANT_FIND_GROUP, 1285 DEFAULT_DEV_GROUP, DEFAULT_DEV_GID); 1286 sys_gid = (gid_t)DEFAULT_DEV_GID; 1287 } 1288 } 1289 1290 (void) snprintf(tmpfile, sizeof (tmpfile), "%s%s", root, tmp); 1291 1292 /* Truncate tmpfile first */ 1293 fp = fopen(tmpfile, "w"); 1294 if (fp == NULL) { 1295 bam_error(OPEN_FAIL, tmpfile, strerror(errno)); 1296 return (BAM_ERROR); 1297 } 1298 ret = fclose(fp); 1299 if (ret == EOF) { 1300 bam_error(CLOSE_FAIL, tmpfile, strerror(errno)); 1301 return (BAM_ERROR); 1302 } 1303 1304 /* Now open it in append mode */ 1305 fp = fopen(tmpfile, "a"); 1306 if (fp == NULL) { 1307 bam_error(OPEN_FAIL, tmpfile, strerror(errno)); 1308 return (BAM_ERROR); 1309 } 1310 1311 for (; start; start = start->next) { 1312 ret = s_fputs(start->line, fp); 1313 if (ret == EOF) { 1314 bam_error(WRITE_FAIL, tmpfile, strerror(errno)); 1315 (void) fclose(fp); 1316 return (BAM_ERROR); 1317 } 1318 } 1319 1320 ret = fclose(fp); 1321 if (ret == EOF) { 1322 bam_error(CLOSE_FAIL, tmpfile, strerror(errno)); 1323 return (BAM_ERROR); 1324 } 1325 1326 /* 1327 * Set up desired attributes. Ignore failures on filesystems 1328 * not supporting these operations - pcfs reports unsupported 1329 * operations as EINVAL. 1330 */ 1331 ret = chmod(tmpfile, mode); 1332 if (ret == -1 && 1333 errno != EINVAL && errno != ENOTSUP) { 1334 bam_error(CHMOD_FAIL, tmpfile, strerror(errno)); 1335 return (BAM_ERROR); 1336 } 1337 1338 ret = chown(tmpfile, root_uid, sys_gid); 1339 if (ret == -1 && 1340 errno != EINVAL && errno != ENOTSUP) { 1341 bam_error(CHOWN_FAIL, tmpfile, strerror(errno)); 1342 return (BAM_ERROR); 1343 } 1344 1345 1346 /* 1347 * Do an atomic rename 1348 */ 1349 ret = rename(tmpfile, path); 1350 if (ret != 0) { 1351 bam_error(RENAME_FAIL, path, strerror(errno)); 1352 return (BAM_ERROR); 1353 } 1354 1355 return (BAM_SUCCESS); 1356 } 1357 1358 /* 1359 * This function should always return 0 - since we want 1360 * to create stat data for *all* files in the list. 1361 */ 1362 /*ARGSUSED*/ 1363 static int 1364 cmpstat( 1365 const char *file, 1366 const struct stat *stat, 1367 int flags, 1368 struct FTW *ftw) 1369 { 1370 uint_t sz; 1371 uint64_t *value; 1372 uint64_t filestat[2]; 1373 int error; 1374 1375 struct safefile *safefilep; 1376 FILE *fp; 1377 1378 /* 1379 * We only want regular files 1380 */ 1381 if (!S_ISREG(stat->st_mode)) 1382 return (0); 1383 1384 /* 1385 * new_nvlp may be NULL if there were errors earlier 1386 * but this is not fatal to update determination. 1387 */ 1388 if (walk_arg.new_nvlp) { 1389 filestat[0] = stat->st_size; 1390 filestat[1] = stat->st_mtime; 1391 error = nvlist_add_uint64_array(walk_arg.new_nvlp, 1392 file + bam_rootlen, filestat, 2); 1393 if (error) 1394 bam_error(NVADD_FAIL, file, strerror(error)); 1395 } 1396 1397 /* 1398 * The remaining steps are only required if we haven't made a 1399 * decision about update or if we are checking (-n) 1400 */ 1401 if (walk_arg.need_update && !bam_check) 1402 return (0); 1403 1404 /* 1405 * If we are invoked as part of system/filesyste/boot-archive, then 1406 * there are a number of things we should not worry about 1407 */ 1408 if (bam_smf_check) { 1409 /* ignore amd64 modules unless we are booted amd64. */ 1410 if (!is_amd64() && strstr(file, "/amd64/") != 0) 1411 return (0); 1412 1413 /* read in list of safe files */ 1414 if (safefiles == NULL) 1415 if (fp = fopen("/boot/solaris/filelist.safe", "r")) { 1416 safefiles = s_calloc(1, 1417 sizeof (struct safefile)); 1418 safefilep = safefiles; 1419 safefilep->name = s_calloc(1, MAXPATHLEN + 1420 MAXNAMELEN); 1421 safefilep->next = NULL; 1422 while (s_fgets(safefilep->name, MAXPATHLEN + 1423 MAXNAMELEN, fp) != NULL) { 1424 safefilep->next = s_calloc(1, 1425 sizeof (struct safefile)); 1426 safefilep = safefilep->next; 1427 safefilep->name = s_calloc(1, 1428 MAXPATHLEN + MAXNAMELEN); 1429 safefilep->next = NULL; 1430 } 1431 (void) fclose(fp); 1432 } 1433 1434 safefilep = safefiles; 1435 while (safefilep->next != NULL) 1436 if (strcmp(file, safefilep->name) != 0) { 1437 fp = fopen(NEED_UPDATE_FILE, "w"); 1438 if (fclose(fp) != 0) 1439 bam_error(CLOSE_FAIL, NEED_UPDATE_FILE, 1440 strerror(errno)); 1441 return (0); 1442 } 1443 } 1444 1445 /* 1446 * We need an update if file doesn't exist in old archive 1447 */ 1448 if (walk_arg.old_nvlp == NULL || 1449 nvlist_lookup_uint64_array(walk_arg.old_nvlp, 1450 file + bam_rootlen, &value, &sz) != 0) { 1451 if (bam_smf_check) /* ignore new during smf check */ 1452 return (0); 1453 walk_arg.need_update = 1; 1454 if (bam_verbose) 1455 bam_print(PARSEABLE_NEW_FILE, file); 1456 return (0); 1457 } 1458 1459 /* 1460 * File exists in old archive. Check if file has changed 1461 */ 1462 assert(sz == 2); 1463 bcopy(value, filestat, sizeof (filestat)); 1464 1465 if (filestat[0] != stat->st_size || 1466 filestat[1] != stat->st_mtime) { 1467 walk_arg.need_update = 1; 1468 if (bam_verbose) 1469 if (bam_smf_check) 1470 bam_print(" %s\n", file); 1471 else 1472 bam_print(PARSEABLE_OUT_DATE, file); 1473 } 1474 1475 return (0); 1476 } 1477 1478 /* 1479 * Check flags and presence of required files. 1480 * The force flag and/or absence of files should 1481 * trigger an update. 1482 * Suppress stdout output if check (-n) option is set 1483 * (as -n should only produce parseable output.) 1484 */ 1485 static void 1486 check_flags_and_files(char *root) 1487 { 1488 char path[PATH_MAX]; 1489 struct stat sb; 1490 1491 /* 1492 * if force, create archive unconditionally 1493 */ 1494 if (bam_force) { 1495 walk_arg.need_update = 1; 1496 if (bam_verbose && !bam_check) 1497 bam_print(UPDATE_FORCE); 1498 return; 1499 } 1500 1501 /* 1502 * If archive is missing, create archive 1503 */ 1504 (void) snprintf(path, sizeof (path), "%s%s", root, BOOT_ARCHIVE); 1505 if (stat(path, &sb) != 0) { 1506 if (bam_verbose && !bam_check) 1507 bam_print(UPDATE_ARCH_MISS, path); 1508 walk_arg.need_update = 1; 1509 return; 1510 } 1511 } 1512 1513 static error_t 1514 read_one_list(char *root, filelist_t *flistp, char *filelist) 1515 { 1516 char path[PATH_MAX]; 1517 FILE *fp; 1518 char buf[BAM_MAXLINE]; 1519 1520 (void) snprintf(path, sizeof (path), "%s%s", root, filelist); 1521 1522 fp = fopen(path, "r"); 1523 if (fp == NULL) { 1524 if (bam_debug) 1525 bam_error(FLIST_FAIL, path, strerror(errno)); 1526 return (BAM_ERROR); 1527 } 1528 while (s_fgets(buf, sizeof (buf), fp) != NULL) { 1529 /* skip blank lines */ 1530 if (strspn(buf, " \t") == strlen(buf)) 1531 continue; 1532 append_to_flist(flistp, buf); 1533 } 1534 if (fclose(fp) != 0) { 1535 bam_error(CLOSE_FAIL, path, strerror(errno)); 1536 return (BAM_ERROR); 1537 } 1538 return (BAM_SUCCESS); 1539 } 1540 1541 static error_t 1542 read_list(char *root, filelist_t *flistp) 1543 { 1544 int rval; 1545 1546 flistp->head = flistp->tail = NULL; 1547 1548 /* 1549 * Read current lists of files - only the first is mandatory 1550 */ 1551 rval = read_one_list(root, flistp, BOOT_FILE_LIST); 1552 if (rval != BAM_SUCCESS) 1553 return (rval); 1554 (void) read_one_list(root, flistp, ETC_FILE_LIST); 1555 1556 if (flistp->head == NULL) { 1557 bam_error(NO_FLIST); 1558 return (BAM_ERROR); 1559 } 1560 1561 return (BAM_SUCCESS); 1562 } 1563 1564 static void 1565 getoldstat(char *root) 1566 { 1567 char path[PATH_MAX]; 1568 int fd, error; 1569 struct stat sb; 1570 char *ostat; 1571 1572 (void) snprintf(path, sizeof (path), "%s%s", root, FILE_STAT); 1573 fd = open(path, O_RDONLY); 1574 if (fd == -1) { 1575 if (bam_verbose) 1576 bam_print(OPEN_FAIL, path, strerror(errno)); 1577 walk_arg.need_update = 1; 1578 return; 1579 } 1580 1581 if (fstat(fd, &sb) != 0) { 1582 bam_error(STAT_FAIL, path, strerror(errno)); 1583 (void) close(fd); 1584 walk_arg.need_update = 1; 1585 return; 1586 } 1587 1588 ostat = s_calloc(1, sb.st_size); 1589 1590 if (read(fd, ostat, sb.st_size) != sb.st_size) { 1591 bam_error(READ_FAIL, path, strerror(errno)); 1592 (void) close(fd); 1593 free(ostat); 1594 walk_arg.need_update = 1; 1595 return; 1596 } 1597 1598 (void) close(fd); 1599 1600 walk_arg.old_nvlp = NULL; 1601 error = nvlist_unpack(ostat, sb.st_size, &walk_arg.old_nvlp, 0); 1602 1603 free(ostat); 1604 1605 if (error) { 1606 bam_error(UNPACK_FAIL, path, strerror(error)); 1607 walk_arg.old_nvlp = NULL; 1608 walk_arg.need_update = 1; 1609 return; 1610 } 1611 } 1612 1613 static void 1614 create_newstat(void) 1615 { 1616 int error; 1617 1618 error = nvlist_alloc(&walk_arg.new_nvlp, NV_UNIQUE_NAME, 0); 1619 if (error) { 1620 /* 1621 * Not fatal - we can still create archive 1622 */ 1623 walk_arg.new_nvlp = NULL; 1624 bam_error(NVALLOC_FAIL, strerror(error)); 1625 } 1626 } 1627 1628 static void 1629 walk_list(char *root, filelist_t *flistp) 1630 { 1631 char path[PATH_MAX]; 1632 line_t *lp; 1633 1634 for (lp = flistp->head; lp; lp = lp->next) { 1635 (void) snprintf(path, sizeof (path), "%s%s", root, lp->line); 1636 /* XXX shouldn't we use FTW_MOUNT ? */ 1637 if (nftw(path, cmpstat, 20, 0) == -1) { 1638 /* 1639 * Some files may not exist. 1640 * For example: etc/rtc_config on a x86 diskless system 1641 * Emit verbose message only 1642 */ 1643 if (bam_verbose) 1644 bam_print(NFTW_FAIL, path, strerror(errno)); 1645 } 1646 } 1647 } 1648 1649 static void 1650 savenew(char *root) 1651 { 1652 char path[PATH_MAX]; 1653 char path2[PATH_MAX]; 1654 size_t sz; 1655 char *nstat; 1656 int fd, wrote, error; 1657 1658 nstat = NULL; 1659 sz = 0; 1660 error = nvlist_pack(walk_arg.new_nvlp, &nstat, &sz, 1661 NV_ENCODE_XDR, 0); 1662 if (error) { 1663 bam_error(PACK_FAIL, strerror(error)); 1664 return; 1665 } 1666 1667 (void) snprintf(path, sizeof (path), "%s%s", root, FILE_STAT_TMP); 1668 fd = open(path, O_RDWR|O_CREAT|O_TRUNC, FILE_STAT_MODE); 1669 if (fd == -1) { 1670 bam_error(OPEN_FAIL, path, strerror(errno)); 1671 free(nstat); 1672 return; 1673 } 1674 wrote = write(fd, nstat, sz); 1675 if (wrote != sz) { 1676 bam_error(WRITE_FAIL, path, strerror(errno)); 1677 (void) close(fd); 1678 free(nstat); 1679 return; 1680 } 1681 (void) close(fd); 1682 free(nstat); 1683 1684 (void) snprintf(path2, sizeof (path2), "%s%s", root, FILE_STAT); 1685 if (rename(path, path2) != 0) { 1686 bam_error(RENAME_FAIL, path2, strerror(errno)); 1687 } 1688 } 1689 1690 static void 1691 clear_walk_args(void) 1692 { 1693 if (walk_arg.old_nvlp) 1694 nvlist_free(walk_arg.old_nvlp); 1695 if (walk_arg.new_nvlp) 1696 nvlist_free(walk_arg.new_nvlp); 1697 walk_arg.need_update = 0; 1698 walk_arg.old_nvlp = NULL; 1699 walk_arg.new_nvlp = NULL; 1700 } 1701 1702 /* 1703 * Returns: 1704 * 0 - no update necessary 1705 * 1 - update required. 1706 * BAM_ERROR (-1) - An error occurred 1707 * 1708 * Special handling for check (-n): 1709 * ================================ 1710 * The check (-n) option produces parseable output. 1711 * To do this, we suppress all stdout messages unrelated 1712 * to out of sync files. 1713 * All stderr messages are still printed though. 1714 * 1715 */ 1716 static int 1717 update_required(char *root) 1718 { 1719 struct stat sb; 1720 char path[PATH_MAX]; 1721 filelist_t flist; 1722 filelist_t *flistp = &flist; 1723 int need_update; 1724 1725 flistp->head = flistp->tail = NULL; 1726 1727 walk_arg.need_update = 0; 1728 1729 /* 1730 * Without consulting stat data, check if we need update 1731 */ 1732 check_flags_and_files(root); 1733 1734 /* 1735 * In certain deployment scenarios, filestat may not 1736 * exist. Ignore it during boot-archive SMF check. 1737 */ 1738 if (bam_smf_check) { 1739 (void) snprintf(path, sizeof (path), "%s%s", root, FILE_STAT); 1740 if (stat(path, &sb) != 0) 1741 return (0); 1742 } 1743 1744 /* 1745 * consult stat data only if we haven't made a decision 1746 * about update. If checking (-n) however, we always 1747 * need stat data (since we want to compare old and new) 1748 */ 1749 if (!walk_arg.need_update || bam_check) 1750 getoldstat(root); 1751 1752 /* 1753 * read list of files 1754 */ 1755 if (read_list(root, flistp) != BAM_SUCCESS) { 1756 clear_walk_args(); 1757 return (BAM_ERROR); 1758 } 1759 1760 assert(flistp->head && flistp->tail); 1761 1762 /* 1763 * At this point either the update is required 1764 * or the decision is pending. In either case 1765 * we need to create new stat nvlist 1766 */ 1767 create_newstat(); 1768 1769 /* 1770 * This walk does 2 things: 1771 * - gets new stat data for every file 1772 * - (optional) compare old and new stat data 1773 */ 1774 walk_list(root, &flist); 1775 1776 /* done with the file list */ 1777 filelist_free(flistp); 1778 1779 /* 1780 * if we didn't succeed in creating new stat data above 1781 * just return result of update check so that archive is built. 1782 */ 1783 if (walk_arg.new_nvlp == NULL) { 1784 bam_error(NO_NEW_STAT); 1785 need_update = walk_arg.need_update; 1786 clear_walk_args(); 1787 return (need_update ? 1 : 0); 1788 } 1789 1790 1791 /* 1792 * If no update required, discard newstat 1793 */ 1794 if (!walk_arg.need_update) { 1795 clear_walk_args(); 1796 return (0); 1797 } 1798 1799 /* 1800 * At this point we need an update - so save new stat data 1801 * However, if only checking (-n), don't save new stat data. 1802 */ 1803 if (!bam_check) 1804 savenew(root); 1805 1806 clear_walk_args(); 1807 1808 return (1); 1809 } 1810 1811 static error_t 1812 create_ramdisk(char *root) 1813 { 1814 char *cmdline, path[PATH_MAX]; 1815 size_t len; 1816 struct stat sb; 1817 1818 /* 1819 * Setup command args for create_ramdisk.ksh 1820 */ 1821 (void) snprintf(path, sizeof (path), "%s%s", root, CREATE_RAMDISK); 1822 if (stat(path, &sb) != 0) { 1823 bam_error(ARCH_EXEC_MISS, path, strerror(errno)); 1824 return (BAM_ERROR); 1825 } 1826 1827 len = strlen(path) + strlen(root) + 10; /* room for space + -R */ 1828 cmdline = s_calloc(1, len); 1829 1830 if (strlen(root) > 1) { 1831 (void) snprintf(cmdline, len, "%s -R %s", path, root); 1832 /* chop off / at the end */ 1833 cmdline[strlen(cmdline) - 1] = '\0'; 1834 } else 1835 (void) snprintf(cmdline, len, "%s", path); 1836 1837 if (exec_cmd(cmdline, NULL, 0) != 0) { 1838 bam_error(ARCHIVE_FAIL, cmdline); 1839 free(cmdline); 1840 return (BAM_ERROR); 1841 } 1842 free(cmdline); 1843 1844 /* 1845 * Verify that the archive has been created 1846 */ 1847 (void) snprintf(path, sizeof (path), "%s%s", root, BOOT_ARCHIVE); 1848 if (stat(path, &sb) != 0) { 1849 bam_error(ARCHIVE_NOT_CREATED, path); 1850 return (BAM_ERROR); 1851 } 1852 1853 return (BAM_SUCCESS); 1854 } 1855 1856 /* 1857 * Checks if target filesystem is on a ramdisk 1858 * 1 - is miniroot 1859 * 0 - is not 1860 * When in doubt assume it is not a ramdisk. 1861 */ 1862 static int 1863 is_ramdisk(char *root) 1864 { 1865 struct extmnttab mnt; 1866 FILE *fp; 1867 int found; 1868 char mntpt[PATH_MAX]; 1869 char *cp; 1870 1871 /* 1872 * There are 3 situations where creating archive is 1873 * of dubious value: 1874 * - create boot_archive on a lofi-mounted boot_archive 1875 * - create it on a ramdisk which is the root filesystem 1876 * - create it on a ramdisk mounted somewhere else 1877 * The first is not easy to detect and checking for it is not 1878 * worth it. 1879 * The other two conditions are handled here 1880 */ 1881 1882 fp = fopen(MNTTAB, "r"); 1883 if (fp == NULL) { 1884 bam_error(OPEN_FAIL, MNTTAB, strerror(errno)); 1885 return (0); 1886 } 1887 1888 resetmnttab(fp); 1889 1890 /* 1891 * Remove any trailing / from the mount point 1892 */ 1893 (void) strlcpy(mntpt, root, sizeof (mntpt)); 1894 if (strcmp(root, "/") != 0) { 1895 cp = mntpt + strlen(mntpt) - 1; 1896 if (*cp == '/') 1897 *cp = '\0'; 1898 } 1899 found = 0; 1900 while (getextmntent(fp, &mnt, sizeof (mnt)) == 0) { 1901 if (strcmp(mnt.mnt_mountp, mntpt) == 0) { 1902 found = 1; 1903 break; 1904 } 1905 } 1906 1907 if (!found) { 1908 if (bam_verbose) 1909 bam_error(NOT_IN_MNTTAB, mntpt); 1910 (void) fclose(fp); 1911 return (0); 1912 } 1913 1914 if (strstr(mnt.mnt_special, RAMDISK_SPECIAL) != NULL) { 1915 if (bam_verbose) 1916 bam_error(IS_RAMDISK, bam_root); 1917 (void) fclose(fp); 1918 return (1); 1919 } 1920 1921 (void) fclose(fp); 1922 1923 return (0); 1924 } 1925 1926 static int 1927 is_newboot(char *root) 1928 { 1929 char path[PATH_MAX]; 1930 struct stat sb; 1931 1932 /* 1933 * We can't boot without MULTI_BOOT 1934 */ 1935 (void) snprintf(path, sizeof (path), "%s%s", root, MULTI_BOOT); 1936 if (stat(path, &sb) == -1) { 1937 if (bam_verbose) 1938 bam_print(FILE_MISS, path); 1939 return (0); 1940 } 1941 1942 /* 1943 * We can't generate archive without GRUB_DIR 1944 */ 1945 (void) snprintf(path, sizeof (path), "%s%s", root, GRUB_DIR); 1946 if (stat(path, &sb) == -1) { 1947 if (bam_verbose) 1948 bam_print(DIR_MISS, path); 1949 return (0); 1950 } 1951 1952 return (1); 1953 } 1954 1955 static int 1956 is_readonly(char *root) 1957 { 1958 struct statvfs vfs; 1959 1960 /* 1961 * Check for RDONLY filesystem 1962 * When in doubt assume it is not readonly 1963 */ 1964 if (statvfs(root, &vfs) != 0) { 1965 if (bam_verbose) 1966 bam_error(STATVFS_FAIL, root, strerror(errno)); 1967 return (0); 1968 } 1969 1970 if (vfs.f_flag & ST_RDONLY) { 1971 return (1); 1972 } 1973 1974 return (0); 1975 } 1976 1977 static error_t 1978 update_archive(char *root, char *opt) 1979 { 1980 error_t ret; 1981 1982 assert(root); 1983 assert(opt == NULL); 1984 1985 /* 1986 * root must belong to a GRUB boot OS, 1987 * don't care on sparc except for diskless clients 1988 */ 1989 if (!is_newboot(root)) { 1990 /* 1991 * Emit message only if not in context of update_all. 1992 * If in update_all, emit only if verbose flag is set. 1993 */ 1994 if (!bam_update_all || bam_verbose) 1995 bam_print(NOT_GRUB_BOOT, root); 1996 return (BAM_SUCCESS); 1997 } 1998 1999 /* 2000 * If smf check is requested when / is writable (can happen 2001 * on first reboot following an upgrade because service 2002 * dependency is messed up), skip the check. 2003 */ 2004 if (bam_smf_check && !bam_root_readonly) 2005 return (BAM_SUCCESS); 2006 2007 /* 2008 * root must be writable. This check applies to alternate 2009 * root (-R option); bam_root_readonly applies to '/' only. 2010 * Note: statvfs() does not always report the truth 2011 */ 2012 if (!bam_smf_check && !bam_check && is_readonly(root)) { 2013 if (bam_verbose) 2014 bam_print(RDONLY_FS, root); 2015 return (BAM_SUCCESS); 2016 } 2017 2018 /* 2019 * Don't generate archive on ramdisk 2020 */ 2021 if (is_ramdisk(root)) { 2022 if (bam_verbose) 2023 bam_print(SKIP_RAMDISK); 2024 return (BAM_SUCCESS); 2025 } 2026 2027 /* 2028 * Now check if updated is really needed 2029 */ 2030 ret = update_required(root); 2031 2032 /* 2033 * The check command (-n) is *not* a dry run 2034 * It only checks if the archive is in sync. 2035 */ 2036 if (bam_check) { 2037 bam_exit((ret != 0) ? 1 : 0); 2038 } 2039 2040 if (ret == 1) { 2041 /* create the ramdisk */ 2042 ret = create_ramdisk(root); 2043 } 2044 return (ret); 2045 } 2046 2047 static void 2048 update_fdisk(void) 2049 { 2050 struct stat sb; 2051 char cmd[PATH_MAX]; 2052 int ret1, ret2; 2053 2054 assert(stat(GRUB_fdisk, &sb) == 0); 2055 assert(stat(GRUB_fdisk_target, &sb) == 0); 2056 2057 (void) snprintf(cmd, sizeof (cmd), "/sbin/fdisk -F %s `/bin/cat %s`", 2058 GRUB_fdisk, GRUB_fdisk_target); 2059 2060 bam_print(UPDATING_FDISK); 2061 if (exec_cmd(cmd, NULL, 0) != 0) { 2062 bam_error(FDISK_UPDATE_FAILED); 2063 } 2064 2065 /* 2066 * We are done, remove the files. 2067 */ 2068 ret1 = unlink(GRUB_fdisk); 2069 ret2 = unlink(GRUB_fdisk_target); 2070 if (ret1 != 0 || ret2 != 0) { 2071 bam_error(FILE_REMOVE_FAILED, GRUB_fdisk, GRUB_fdisk_target); 2072 } 2073 } 2074 2075 static void 2076 restore_grub_slice(void) 2077 { 2078 struct stat sb; 2079 char *mntpt, *physlice; 2080 int mnted; /* set if we did a mount */ 2081 char menupath[PATH_MAX], cmd[PATH_MAX]; 2082 2083 if (stat(GRUB_slice, &sb) != 0) { 2084 bam_error(MISSING_SLICE_FILE, GRUB_slice, strerror(errno)); 2085 return; 2086 } 2087 2088 /* 2089 * If we are doing an luactivate, don't attempt to restore GRUB or else 2090 * we may not be able to get to DCA boot environments. Let luactivate 2091 * handle GRUB/DCA installation 2092 */ 2093 if (stat(LU_ACTIVATE_FILE, &sb) == 0) { 2094 return; 2095 } 2096 2097 mnted = 0; 2098 physlice = NULL; 2099 mntpt = mount_grub_slice(&mnted, &physlice, NULL, NULL); 2100 if (mntpt == NULL) { 2101 bam_error(CANNOT_RESTORE_GRUB_SLICE); 2102 return; 2103 } 2104 2105 (void) snprintf(menupath, sizeof (menupath), "%s%s", mntpt, GRUB_MENU); 2106 if (stat(menupath, &sb) == 0) { 2107 umount_grub_slice(mnted, mntpt, physlice, NULL, NULL); 2108 return; 2109 } 2110 2111 /* 2112 * The menu is missing - we need to do a restore 2113 */ 2114 bam_print(RESTORING_GRUB); 2115 2116 (void) snprintf(cmd, sizeof (cmd), "%s %s %s %s", 2117 INSTALLGRUB, STAGE1, STAGE2, physlice); 2118 2119 if (exec_cmd(cmd, NULL, 0) != 0) { 2120 bam_error(RESTORE_GRUB_FAILED); 2121 umount_grub_slice(mnted, mntpt, physlice, NULL, NULL); 2122 return; 2123 } 2124 2125 if (stat(GRUB_backup_menu, &sb) != 0) { 2126 bam_error(MISSING_BACKUP_MENU, 2127 GRUB_backup_menu, strerror(errno)); 2128 umount_grub_slice(mnted, mntpt, physlice, NULL, NULL); 2129 return; 2130 } 2131 2132 (void) snprintf(cmd, sizeof (cmd), "/bin/cp %s %s", 2133 GRUB_backup_menu, menupath); 2134 2135 if (exec_cmd(cmd, NULL, 0) != 0) { 2136 bam_error(RESTORE_MENU_FAILED, menupath); 2137 umount_grub_slice(mnted, mntpt, physlice, NULL, NULL); 2138 return; 2139 } 2140 2141 /* Success */ 2142 umount_grub_slice(mnted, mntpt, physlice, NULL, NULL); 2143 } 2144 2145 static error_t 2146 update_all(char *root, char *opt) 2147 { 2148 struct extmnttab mnt; 2149 struct stat sb; 2150 FILE *fp; 2151 char multibt[PATH_MAX]; 2152 error_t ret = BAM_SUCCESS; 2153 int ret1, ret2; 2154 2155 assert(root); 2156 assert(opt == NULL); 2157 2158 if (bam_rootlen != 1 || *root != '/') { 2159 elide_trailing_slash(root, multibt, sizeof (multibt)); 2160 bam_error(ALT_ROOT_INVALID, multibt); 2161 return (BAM_ERROR); 2162 } 2163 2164 /* 2165 * First update archive for current root 2166 */ 2167 if (update_archive(root, opt) != BAM_SUCCESS) 2168 ret = BAM_ERROR; 2169 2170 /* 2171 * Now walk the mount table, performing archive update 2172 * for all mounted Newboot root filesystems 2173 */ 2174 fp = fopen(MNTTAB, "r"); 2175 if (fp == NULL) { 2176 bam_error(OPEN_FAIL, MNTTAB, strerror(errno)); 2177 ret = BAM_ERROR; 2178 goto out; 2179 } 2180 2181 resetmnttab(fp); 2182 2183 while (getextmntent(fp, &mnt, sizeof (mnt)) == 0) { 2184 if (mnt.mnt_special == NULL) 2185 continue; 2186 if (strncmp(mnt.mnt_special, "/dev/", strlen("/dev/")) != 0) 2187 continue; 2188 if (strcmp(mnt.mnt_mountp, "/") == 0) 2189 continue; 2190 2191 (void) snprintf(multibt, sizeof (multibt), "%s%s", 2192 mnt.mnt_mountp, MULTI_BOOT); 2193 2194 if (stat(multibt, &sb) == -1) 2195 continue; 2196 2197 /* 2198 * We put a trailing slash to be consistent with root = "/" 2199 * case, such that we don't have to print // in some cases. 2200 */ 2201 (void) snprintf(rootbuf, sizeof (rootbuf), "%s/", 2202 mnt.mnt_mountp); 2203 bam_rootlen = strlen(rootbuf); 2204 if (update_archive(rootbuf, opt) != BAM_SUCCESS) 2205 ret = BAM_ERROR; 2206 } 2207 2208 (void) fclose(fp); 2209 2210 out: 2211 if (stat(GRUB_slice, &sb) == 0) { 2212 restore_grub_slice(); 2213 } 2214 2215 /* 2216 * Update fdisk table as we go down. Updating it when 2217 * the system is running will confuse biosdev. 2218 */ 2219 ret1 = stat(GRUB_fdisk, &sb); 2220 ret2 = stat(GRUB_fdisk_target, &sb); 2221 if ((ret1 == 0) && (ret2 == 0)) { 2222 update_fdisk(); 2223 } else if ((ret1 == 0) ^ (ret2 == 0)) { 2224 /* 2225 * It is an error for one file to be 2226 * present and the other absent. 2227 * It is normal for both files to be 2228 * absent - it indicates that no fdisk 2229 * update is required. 2230 */ 2231 bam_error(MISSING_FDISK_FILE, 2232 ret1 ? GRUB_fdisk : GRUB_fdisk_target); 2233 ret = BAM_ERROR; 2234 } 2235 2236 return (ret); 2237 } 2238 2239 static void 2240 append_line(menu_t *mp, line_t *lp) 2241 { 2242 if (mp->start == NULL) { 2243 mp->start = lp; 2244 } else { 2245 mp->end->next = lp; 2246 lp->prev = mp->end; 2247 } 2248 mp->end = lp; 2249 } 2250 2251 static void 2252 unlink_line(menu_t *mp, line_t *lp) 2253 { 2254 /* unlink from list */ 2255 if (lp->prev) 2256 lp->prev->next = lp->next; 2257 else 2258 mp->start = lp->next; 2259 if (lp->next) 2260 lp->next->prev = lp->prev; 2261 else 2262 mp->end = lp->prev; 2263 } 2264 2265 static entry_t * 2266 boot_entry_new(menu_t *mp, line_t *start, line_t *end) 2267 { 2268 entry_t *ent, *prev; 2269 2270 ent = s_calloc(1, sizeof (entry_t)); 2271 ent->start = start; 2272 ent->end = end; 2273 2274 if (mp->entries == NULL) { 2275 mp->entries = ent; 2276 return (ent); 2277 } 2278 2279 prev = mp->entries; 2280 while (prev->next) 2281 prev = prev-> next; 2282 prev->next = ent; 2283 ent->prev = prev; 2284 return (ent); 2285 } 2286 2287 static void 2288 boot_entry_addline(entry_t *ent, line_t *lp) 2289 { 2290 if (ent) 2291 ent->end = lp; 2292 } 2293 2294 /* 2295 * A line in menu.lst looks like 2296 * [ ]*<cmd>[ \t=]*<arg>* 2297 */ 2298 static void 2299 line_parser(menu_t *mp, char *str, int *lineNum, int *entryNum) 2300 { 2301 /* 2302 * save state across calls. This is so that 2303 * header gets the right entry# after title has 2304 * been processed 2305 */ 2306 static line_t *prev = NULL; 2307 static entry_t *curr_ent = NULL; 2308 2309 line_t *lp; 2310 char *cmd, *sep, *arg; 2311 char save, *cp, *line; 2312 menu_flag_t flag = BAM_INVALID; 2313 2314 if (str == NULL) { 2315 return; 2316 } 2317 2318 /* 2319 * First save a copy of the entire line. 2320 * We use this later to set the line field. 2321 */ 2322 line = s_strdup(str); 2323 2324 /* Eat up leading whitespace */ 2325 while (*str == ' ' || *str == '\t') 2326 str++; 2327 2328 if (*str == '#') { /* comment */ 2329 cmd = s_strdup("#"); 2330 sep = NULL; 2331 arg = s_strdup(str + 1); 2332 flag = BAM_COMMENT; 2333 } else if (*str == '\0') { /* blank line */ 2334 cmd = sep = arg = NULL; 2335 flag = BAM_EMPTY; 2336 } else { 2337 /* 2338 * '=' is not a documented separator in grub syntax. 2339 * However various development bits use '=' as a 2340 * separator. In addition, external users also 2341 * use = as a separator. So we will allow that usage. 2342 */ 2343 cp = str; 2344 while (*str != ' ' && *str != '\t' && *str != '=') { 2345 if (*str == '\0') { 2346 cmd = s_strdup(cp); 2347 sep = arg = NULL; 2348 break; 2349 } 2350 str++; 2351 } 2352 2353 if (*str != '\0') { 2354 save = *str; 2355 *str = '\0'; 2356 cmd = s_strdup(cp); 2357 *str = save; 2358 2359 str++; 2360 save = *str; 2361 *str = '\0'; 2362 sep = s_strdup(str - 1); 2363 *str = save; 2364 2365 while (*str == ' ' || *str == '\t') 2366 str++; 2367 if (*str == '\0') 2368 arg = NULL; 2369 else 2370 arg = s_strdup(str); 2371 } 2372 } 2373 2374 lp = s_calloc(1, sizeof (line_t)); 2375 2376 lp->cmd = cmd; 2377 lp->sep = sep; 2378 lp->arg = arg; 2379 lp->line = line; 2380 lp->lineNum = ++(*lineNum); 2381 if (cmd && strcmp(cmd, menu_cmds[TITLE_CMD]) == 0) { 2382 lp->entryNum = ++(*entryNum); 2383 lp->flags = BAM_TITLE; 2384 if (prev && prev->flags == BAM_COMMENT && 2385 prev->arg && strcmp(prev->arg, BAM_HDR) == 0) { 2386 prev->entryNum = lp->entryNum; 2387 curr_ent = boot_entry_new(mp, prev, lp); 2388 } else { 2389 curr_ent = boot_entry_new(mp, lp, lp); 2390 } 2391 } else if (flag != BAM_INVALID) { 2392 /* 2393 * For header comments, the entry# is "fixed up" 2394 * by the subsequent title 2395 */ 2396 lp->entryNum = *entryNum; 2397 lp->flags = flag; 2398 } else { 2399 lp->entryNum = *entryNum; 2400 lp->flags = (*entryNum == ENTRY_INIT) ? BAM_GLOBAL : BAM_ENTRY; 2401 } 2402 2403 /* record default, old default, and entry line ranges */ 2404 if (lp->flags == BAM_GLOBAL && 2405 strcmp(lp->cmd, menu_cmds[DEFAULT_CMD]) == 0) { 2406 mp->curdefault = lp; 2407 } else if (lp->flags == BAM_COMMENT && 2408 strncmp(lp->arg, BAM_OLDDEF, strlen(BAM_OLDDEF)) == 0) { 2409 mp->olddefault = lp; 2410 } else if (lp->flags == BAM_ENTRY || 2411 (lp->flags == BAM_COMMENT && strcmp(lp->arg, BAM_FTR) == 0)) { 2412 boot_entry_addline(curr_ent, lp); 2413 } 2414 append_line(mp, lp); 2415 2416 prev = lp; 2417 } 2418 2419 static void 2420 update_numbering(menu_t *mp) 2421 { 2422 int lineNum; 2423 int entryNum; 2424 int old_default_value; 2425 line_t *lp, *prev, *default_lp, *default_entry; 2426 char buf[PATH_MAX]; 2427 2428 if (mp->start == NULL) { 2429 return; 2430 } 2431 2432 lineNum = LINE_INIT; 2433 entryNum = ENTRY_INIT; 2434 old_default_value = ENTRY_INIT; 2435 lp = default_lp = default_entry = NULL; 2436 2437 prev = NULL; 2438 for (lp = mp->start; lp; prev = lp, lp = lp->next) { 2439 lp->lineNum = ++lineNum; 2440 2441 /* 2442 * Get the value of the default command 2443 */ 2444 if (lp->entryNum == ENTRY_INIT && lp->cmd && 2445 strcmp(lp->cmd, menu_cmds[DEFAULT_CMD]) == 0 && 2446 lp->arg) { 2447 old_default_value = atoi(lp->arg); 2448 default_lp = lp; 2449 } 2450 2451 /* 2452 * If not boot entry, nothing else to fix for this 2453 * entry 2454 */ 2455 if (lp->entryNum == ENTRY_INIT) 2456 continue; 2457 2458 /* 2459 * Record the position of the default entry. 2460 * The following works because global 2461 * commands like default and timeout should precede 2462 * actual boot entries, so old_default_value 2463 * is already known (or default cmd is missing). 2464 */ 2465 if (default_entry == NULL && 2466 old_default_value != ENTRY_INIT && 2467 lp->entryNum == old_default_value) { 2468 default_entry = lp; 2469 } 2470 2471 /* 2472 * Now fixup the entry number 2473 */ 2474 if (lp->cmd && strcmp(lp->cmd, menu_cmds[TITLE_CMD]) == 0) { 2475 lp->entryNum = ++entryNum; 2476 /* fixup the bootadm header */ 2477 if (prev && prev->flags == BAM_COMMENT && 2478 prev->arg && strcmp(prev->arg, BAM_HDR) == 0) { 2479 prev->entryNum = lp->entryNum; 2480 } 2481 } else { 2482 lp->entryNum = entryNum; 2483 } 2484 } 2485 2486 /* 2487 * No default command in menu, simply return 2488 */ 2489 if (default_lp == NULL) { 2490 return; 2491 } 2492 2493 free(default_lp->arg); 2494 free(default_lp->line); 2495 2496 if (default_entry == NULL) { 2497 default_lp->arg = s_strdup("0"); 2498 } else { 2499 (void) snprintf(buf, sizeof (buf), "%d", 2500 default_entry->entryNum); 2501 default_lp->arg = s_strdup(buf); 2502 } 2503 2504 /* 2505 * The following is required since only the line field gets 2506 * written back to menu.lst 2507 */ 2508 (void) snprintf(buf, sizeof (buf), "%s%s%s", 2509 menu_cmds[DEFAULT_CMD], menu_cmds[SEP_CMD], default_lp->arg); 2510 default_lp->line = s_strdup(buf); 2511 } 2512 2513 2514 static menu_t * 2515 menu_read(char *menu_path) 2516 { 2517 FILE *fp; 2518 char buf[BAM_MAXLINE], *cp; 2519 menu_t *mp; 2520 int line, entry, len, n; 2521 2522 mp = s_calloc(1, sizeof (menu_t)); 2523 2524 fp = fopen(menu_path, "r"); 2525 if (fp == NULL) { /* Let the caller handle this error */ 2526 return (mp); 2527 } 2528 2529 2530 /* Note: GRUB boot entry number starts with 0 */ 2531 line = LINE_INIT; 2532 entry = ENTRY_INIT; 2533 cp = buf; 2534 len = sizeof (buf); 2535 while (s_fgets(cp, len, fp) != NULL) { 2536 n = strlen(cp); 2537 if (cp[n - 1] == '\\') { 2538 len -= n - 1; 2539 assert(len >= 2); 2540 cp += n - 1; 2541 continue; 2542 } 2543 line_parser(mp, buf, &line, &entry); 2544 cp = buf; 2545 len = sizeof (buf); 2546 } 2547 2548 if (fclose(fp) == EOF) { 2549 bam_error(CLOSE_FAIL, menu_path, strerror(errno)); 2550 } 2551 2552 return (mp); 2553 } 2554 2555 static error_t 2556 selector(menu_t *mp, char *opt, int *entry, char **title) 2557 { 2558 char *eq; 2559 char *opt_dup; 2560 int entryNum; 2561 2562 assert(mp); 2563 assert(mp->start); 2564 assert(opt); 2565 2566 opt_dup = s_strdup(opt); 2567 2568 if (entry) 2569 *entry = ENTRY_INIT; 2570 if (title) 2571 *title = NULL; 2572 2573 eq = strchr(opt_dup, '='); 2574 if (eq == NULL) { 2575 bam_error(INVALID_OPT, opt); 2576 free(opt_dup); 2577 return (BAM_ERROR); 2578 } 2579 2580 *eq = '\0'; 2581 if (entry && strcmp(opt_dup, OPT_ENTRY_NUM) == 0) { 2582 assert(mp->end); 2583 entryNum = s_strtol(eq + 1); 2584 if (entryNum < 0 || entryNum > mp->end->entryNum) { 2585 bam_error(INVALID_ENTRY, eq + 1); 2586 free(opt_dup); 2587 return (BAM_ERROR); 2588 } 2589 *entry = entryNum; 2590 } else if (title && strcmp(opt_dup, menu_cmds[TITLE_CMD]) == 0) { 2591 *title = opt + (eq - opt_dup) + 1; 2592 } else { 2593 bam_error(INVALID_OPT, opt); 2594 free(opt_dup); 2595 return (BAM_ERROR); 2596 } 2597 2598 free(opt_dup); 2599 return (BAM_SUCCESS); 2600 } 2601 2602 /* 2603 * If invoked with no titles/entries (opt == NULL) 2604 * only title lines in file are printed. 2605 * 2606 * If invoked with a title or entry #, all 2607 * lines in *every* matching entry are listed 2608 */ 2609 static error_t 2610 list_entry(menu_t *mp, char *menu_path, char *opt) 2611 { 2612 line_t *lp; 2613 int entry = ENTRY_INIT; 2614 int found; 2615 char *title = NULL; 2616 2617 assert(mp); 2618 assert(menu_path); 2619 2620 if (mp->start == NULL) { 2621 bam_error(NO_MENU, menu_path); 2622 return (BAM_ERROR); 2623 } 2624 2625 if (opt != NULL) { 2626 if (selector(mp, opt, &entry, &title) != BAM_SUCCESS) { 2627 return (BAM_ERROR); 2628 } 2629 assert((entry != ENTRY_INIT) ^ (title != NULL)); 2630 } else { 2631 (void) read_globals(mp, menu_path, menu_cmds[DEFAULT_CMD], 0); 2632 (void) read_globals(mp, menu_path, menu_cmds[TIMEOUT_CMD], 0); 2633 } 2634 2635 found = 0; 2636 for (lp = mp->start; lp; lp = lp->next) { 2637 if (lp->flags == BAM_COMMENT || lp->flags == BAM_EMPTY) 2638 continue; 2639 if (opt == NULL && lp->flags == BAM_TITLE) { 2640 bam_print(PRINT_TITLE, lp->entryNum, 2641 lp->arg); 2642 found = 1; 2643 continue; 2644 } 2645 if (entry != ENTRY_INIT && lp->entryNum == entry) { 2646 bam_print(PRINT, lp->line); 2647 found = 1; 2648 continue; 2649 } 2650 2651 /* 2652 * We set the entry value here so that all lines 2653 * in entry get printed. If we subsequently match 2654 * title in other entries, all lines in those 2655 * entries get printed as well. 2656 */ 2657 if (title && lp->flags == BAM_TITLE && lp->arg && 2658 strncmp(title, lp->arg, strlen(title)) == 0) { 2659 bam_print(PRINT, lp->line); 2660 entry = lp->entryNum; 2661 found = 1; 2662 continue; 2663 } 2664 } 2665 2666 if (!found) { 2667 bam_error(NO_MATCH_ENTRY); 2668 return (BAM_ERROR); 2669 } 2670 2671 return (BAM_SUCCESS); 2672 } 2673 2674 static int 2675 add_boot_entry(menu_t *mp, 2676 char *title, 2677 char *root, 2678 char *kernel, 2679 char *module) 2680 { 2681 int lineNum, entryNum; 2682 char linebuf[BAM_MAXLINE]; 2683 2684 assert(mp); 2685 2686 if (title == NULL) { 2687 title = "Solaris"; /* default to Solaris */ 2688 } 2689 if (kernel == NULL) { 2690 bam_error(SUBOPT_MISS, menu_cmds[KERNEL_CMD]); 2691 return (BAM_ERROR); 2692 } 2693 if (module == NULL) { 2694 bam_error(SUBOPT_MISS, menu_cmds[MODULE_CMD]); 2695 return (BAM_ERROR); 2696 } 2697 2698 if (mp->start) { 2699 lineNum = mp->end->lineNum; 2700 entryNum = mp->end->entryNum; 2701 } else { 2702 lineNum = LINE_INIT; 2703 entryNum = ENTRY_INIT; 2704 } 2705 2706 /* 2707 * No separator for comment (HDR/FTR) commands 2708 * The syntax for comments is #<comment> 2709 */ 2710 (void) snprintf(linebuf, sizeof (linebuf), "%s%s", 2711 menu_cmds[COMMENT_CMD], BAM_HDR); 2712 line_parser(mp, linebuf, &lineNum, &entryNum); 2713 2714 (void) snprintf(linebuf, sizeof (linebuf), "%s%s%s", 2715 menu_cmds[TITLE_CMD], menu_cmds[SEP_CMD], title); 2716 line_parser(mp, linebuf, &lineNum, &entryNum); 2717 2718 if (root) { 2719 (void) snprintf(linebuf, sizeof (linebuf), "%s%s%s", 2720 menu_cmds[ROOT_CMD], menu_cmds[SEP_CMD], root); 2721 line_parser(mp, linebuf, &lineNum, &entryNum); 2722 } 2723 2724 (void) snprintf(linebuf, sizeof (linebuf), "%s%s%s", 2725 menu_cmds[KERNEL_CMD], menu_cmds[SEP_CMD], kernel); 2726 line_parser(mp, linebuf, &lineNum, &entryNum); 2727 2728 (void) snprintf(linebuf, sizeof (linebuf), "%s%s%s", 2729 menu_cmds[MODULE_CMD], menu_cmds[SEP_CMD], module); 2730 line_parser(mp, linebuf, &lineNum, &entryNum); 2731 2732 (void) snprintf(linebuf, sizeof (linebuf), "%s%s", 2733 menu_cmds[COMMENT_CMD], BAM_FTR); 2734 line_parser(mp, linebuf, &lineNum, &entryNum); 2735 2736 return (entryNum); 2737 } 2738 2739 static error_t 2740 do_delete(menu_t *mp, int entryNum) 2741 { 2742 line_t *lp, *freed; 2743 entry_t *ent, *tmp; 2744 int deleted; 2745 2746 assert(entryNum != ENTRY_INIT); 2747 2748 ent = mp->entries; 2749 while (ent) { 2750 lp = ent->start; 2751 /* check entry number and make sure it's a bootadm entry */ 2752 if (lp->flags != BAM_COMMENT || 2753 strcmp(lp->arg, BAM_HDR) != 0 || 2754 (entryNum != ALL_ENTRIES && lp->entryNum != entryNum)) { 2755 ent = ent->next; 2756 continue; 2757 } 2758 2759 /* free the entry content */ 2760 do { 2761 freed = lp; 2762 lp = lp->next; /* prev stays the same */ 2763 unlink_line(mp, freed); 2764 line_free(freed); 2765 } while (freed != ent->end); 2766 2767 /* free the entry_t structure */ 2768 tmp = ent; 2769 ent = ent->next; 2770 if (tmp->prev) 2771 tmp->prev->next = ent; 2772 else 2773 mp->entries = ent; 2774 if (ent) 2775 ent->prev = tmp->prev; 2776 deleted = 1; 2777 } 2778 2779 if (!deleted && entryNum != ALL_ENTRIES) { 2780 bam_error(NO_BOOTADM_MATCH); 2781 return (BAM_ERROR); 2782 } 2783 2784 /* 2785 * Now that we have deleted an entry, update 2786 * the entry numbering and the default cmd. 2787 */ 2788 update_numbering(mp); 2789 2790 return (BAM_SUCCESS); 2791 } 2792 2793 static error_t 2794 delete_all_entries(menu_t *mp, char *menu_path, char *opt) 2795 { 2796 assert(mp); 2797 assert(opt == NULL); 2798 2799 if (mp->start == NULL) { 2800 bam_print(EMPTY_FILE, menu_path); 2801 return (BAM_SUCCESS); 2802 } 2803 2804 if (do_delete(mp, ALL_ENTRIES) != BAM_SUCCESS) { 2805 return (BAM_ERROR); 2806 } 2807 2808 return (BAM_WRITE); 2809 } 2810 2811 static FILE * 2812 open_diskmap(char *root) 2813 { 2814 FILE *fp; 2815 char cmd[PATH_MAX]; 2816 2817 /* make sure we have a map file */ 2818 fp = fopen(GRUBDISK_MAP, "r"); 2819 if (fp == NULL) { 2820 (void) snprintf(cmd, sizeof (cmd), 2821 "%s%s > /dev/null", root, CREATE_DISKMAP); 2822 (void) system(cmd); 2823 fp = fopen(GRUBDISK_MAP, "r"); 2824 } 2825 return (fp); 2826 } 2827 2828 #define SECTOR_SIZE 512 2829 2830 static int 2831 get_partition(char *device) 2832 { 2833 int i, fd, is_pcfs, partno = -1; 2834 struct mboot *mboot; 2835 char boot_sect[SECTOR_SIZE]; 2836 char *wholedisk, *slice; 2837 2838 /* form whole disk (p0) */ 2839 slice = device + strlen(device) - 2; 2840 is_pcfs = (*slice != 's'); 2841 if (!is_pcfs) 2842 *slice = '\0'; 2843 wholedisk = s_calloc(1, strlen(device) + 3); 2844 (void) snprintf(wholedisk, strlen(device) + 3, "%sp0", device); 2845 if (!is_pcfs) 2846 *slice = 's'; 2847 2848 /* read boot sector */ 2849 fd = open(wholedisk, O_RDONLY); 2850 free(wholedisk); 2851 if (fd == -1 || read(fd, boot_sect, SECTOR_SIZE) != SECTOR_SIZE) { 2852 return (partno); 2853 } 2854 (void) close(fd); 2855 2856 /* parse fdisk table */ 2857 mboot = (struct mboot *)((void *)boot_sect); 2858 for (i = 0; i < FD_NUMPART; i++) { 2859 struct ipart *part = 2860 (struct ipart *)(uintptr_t)mboot->parts + i; 2861 if (is_pcfs) { /* looking for solaris boot part */ 2862 if (part->systid == 0xbe) { 2863 partno = i; 2864 break; 2865 } 2866 } else { /* look for solaris partition, old and new */ 2867 if (part->systid == SUNIXOS || 2868 part->systid == SUNIXOS2) { 2869 partno = i; 2870 break; 2871 } 2872 } 2873 } 2874 return (partno); 2875 } 2876 2877 static char * 2878 get_grubdisk(char *rootdev, FILE *fp, int on_bootdev) 2879 { 2880 char *grubdisk; /* (hd#,#,#) */ 2881 char *slice; 2882 char *grubhd; 2883 int fdiskpart; 2884 int found = 0; 2885 char *devname, *ctdname = strstr(rootdev, "dsk/"); 2886 char linebuf[PATH_MAX]; 2887 2888 if (ctdname == NULL) 2889 return (NULL); 2890 2891 ctdname += strlen("dsk/"); 2892 slice = strrchr(ctdname, 's'); 2893 if (slice) 2894 *slice = '\0'; 2895 2896 rewind(fp); 2897 while (s_fgets(linebuf, sizeof (linebuf), fp) != NULL) { 2898 grubhd = strtok(linebuf, " \t\n"); 2899 if (grubhd) 2900 devname = strtok(NULL, " \t\n"); 2901 else 2902 devname = NULL; 2903 if (devname && strcmp(devname, ctdname) == 0) { 2904 found = 1; 2905 break; 2906 } 2907 } 2908 2909 if (slice) 2910 *slice = 's'; 2911 2912 if (found == 0) { 2913 if (bam_verbose) 2914 bam_print(DISKMAP_FAIL_NONFATAL, rootdev); 2915 grubhd = "0"; /* assume disk 0 if can't match */ 2916 } 2917 2918 fdiskpart = get_partition(rootdev); 2919 if (fdiskpart == -1) 2920 return (NULL); 2921 2922 grubdisk = s_calloc(1, 10); 2923 if (slice) { 2924 (void) snprintf(grubdisk, 10, "(hd%s,%d,%c)", 2925 grubhd, fdiskpart, slice[1] + 'a' - '0'); 2926 } else 2927 (void) snprintf(grubdisk, 10, "(hd%s,%d)", 2928 grubhd, fdiskpart); 2929 2930 /* if root not on bootdev, change GRUB disk to 0 */ 2931 if (!on_bootdev) 2932 grubdisk[3] = '0'; 2933 return (grubdisk); 2934 } 2935 2936 static char * 2937 get_title(char *rootdir) 2938 { 2939 static char title[80]; /* from /etc/release */ 2940 char *cp = NULL, release[PATH_MAX]; 2941 FILE *fp; 2942 2943 /* open the /etc/release file */ 2944 (void) snprintf(release, sizeof (release), "%s/etc/release", rootdir); 2945 2946 fp = fopen(release, "r"); 2947 if (fp == NULL) 2948 return (NULL); 2949 2950 while (s_fgets(title, sizeof (title), fp) != NULL) { 2951 cp = strstr(title, "Solaris"); 2952 if (cp) 2953 break; 2954 } 2955 (void) fclose(fp); 2956 return (cp == NULL ? "Solaris" : cp); 2957 } 2958 2959 static char * 2960 get_special(char *mountp) 2961 { 2962 FILE *mntfp; 2963 struct mnttab mp = {0}, mpref = {0}; 2964 2965 mntfp = fopen(MNTTAB, "r"); 2966 if (mntfp == NULL) { 2967 return (0); 2968 } 2969 2970 if (*mountp == '\0') 2971 mpref.mnt_mountp = "/"; 2972 else 2973 mpref.mnt_mountp = mountp; 2974 if (getmntany(mntfp, &mp, &mpref) != 0) { 2975 (void) fclose(mntfp); 2976 return (NULL); 2977 } 2978 (void) fclose(mntfp); 2979 2980 return (s_strdup(mp.mnt_special)); 2981 } 2982 2983 static char * 2984 os_to_grubdisk(char *osdisk, int on_bootdev) 2985 { 2986 FILE *fp; 2987 char *grubdisk; 2988 2989 /* translate /dev/dsk name to grub disk name */ 2990 fp = open_diskmap(""); 2991 if (fp == NULL) { 2992 bam_error(DISKMAP_FAIL, osdisk); 2993 return (NULL); 2994 } 2995 grubdisk = get_grubdisk(osdisk, fp, on_bootdev); 2996 (void) fclose(fp); 2997 return (grubdisk); 2998 } 2999 3000 /* 3001 * Check if root is on the boot device 3002 * Return 0 (false) on error 3003 */ 3004 static int 3005 menu_on_bootdev(char *menu_root, FILE *fp) 3006 { 3007 int ret; 3008 char *grubhd, *bootp, *special; 3009 3010 special = get_special(menu_root); 3011 if (special == NULL) 3012 return (0); 3013 bootp = strstr(special, "p0:boot"); 3014 if (bootp) 3015 *bootp = '\0'; 3016 grubhd = get_grubdisk(special, fp, 1); 3017 free(special); 3018 3019 if (grubhd == NULL) 3020 return (0); 3021 ret = grubhd[3] == '0'; 3022 free(grubhd); 3023 return (ret); 3024 } 3025 3026 /* 3027 * look for matching bootadm entry with specified parameters 3028 * Here are the rules (based on existing usage): 3029 * - If title is specified, match on title only 3030 * - Else, match on grubdisk and module (don't care about kernel line). 3031 * note that, if root_opt is non-zero, the absence of root line is 3032 * considered a match. 3033 */ 3034 static entry_t * 3035 find_boot_entry(menu_t *mp, char *title, char *root, char *module, 3036 int root_opt, int *entry_num) 3037 { 3038 int i; 3039 line_t *lp; 3040 entry_t *ent; 3041 3042 /* find matching entry */ 3043 for (i = 0, ent = mp->entries; ent; i++, ent = ent->next) { 3044 lp = ent->start; 3045 3046 /* first line of entry must be bootadm comment */ 3047 lp = ent->start; 3048 if (lp->flags != BAM_COMMENT || strcmp(lp->arg, BAM_HDR) != 0) { 3049 continue; 3050 } 3051 3052 /* advance to title line */ 3053 lp = lp->next; 3054 if (title) { 3055 if (lp->flags == BAM_TITLE && lp->arg && 3056 strcmp(lp->arg, title) == 0) 3057 break; 3058 continue; /* check title only */ 3059 } 3060 3061 lp = lp->next; /* advance to root line */ 3062 if (lp == NULL || strcmp(lp->cmd, menu_cmds[ROOT_CMD]) == 0) { 3063 /* root command found, match grub disk */ 3064 if (strcmp(lp->arg, root) != 0) { 3065 continue; 3066 } 3067 lp = lp->next; /* advance to kernel line */ 3068 } else { 3069 /* no root command, see if root is optional */ 3070 if (root_opt == 0) { 3071 continue; 3072 } 3073 } 3074 3075 if (lp == NULL || lp->next == NULL) { 3076 continue; 3077 } 3078 3079 /* check for matching module entry (failsafe or normal) */ 3080 lp = lp->next; /* advance to module line */ 3081 if (strcmp(lp->cmd, menu_cmds[MODULE_CMD]) != 0 || 3082 strcmp(lp->arg, module) != 0) { 3083 continue; 3084 } 3085 break; /* match found */ 3086 } 3087 3088 *entry_num = i; 3089 return (ent); 3090 } 3091 3092 static int 3093 update_boot_entry(menu_t *mp, char *title, char *root, char *kernel, 3094 char *module, int root_opt) 3095 { 3096 int i; 3097 entry_t *ent; 3098 line_t *lp; 3099 char linebuf[BAM_MAXLINE]; 3100 3101 /* note: don't match on title, it's updated on upgrade */ 3102 ent = find_boot_entry(mp, NULL, root, module, root_opt, &i); 3103 if (ent == NULL) 3104 return (add_boot_entry(mp, title, root_opt ? NULL : root, 3105 kernel, module)); 3106 3107 /* replace title of exiting entry and delete root line */ 3108 lp = ent->start; 3109 lp = lp->next; /* title line */ 3110 (void) snprintf(linebuf, sizeof (linebuf), "%s%s%s", 3111 menu_cmds[TITLE_CMD], menu_cmds[SEP_CMD], title); 3112 free(lp->arg); 3113 free(lp->line); 3114 lp->arg = s_strdup(title); 3115 lp->line = s_strdup(linebuf); 3116 3117 lp = lp->next; /* root line */ 3118 if (strcmp(lp->cmd, menu_cmds[ROOT_CMD]) == 0) { 3119 if (root_opt) { /* root line not needed */ 3120 line_t *tmp = lp; 3121 lp = lp->next; 3122 unlink_line(mp, tmp); 3123 line_free(tmp); 3124 } else 3125 lp = lp->next; 3126 } 3127 return (i); 3128 } 3129 3130 /*ARGSUSED*/ 3131 static error_t 3132 update_entry(menu_t *mp, char *menu_root, char *opt) 3133 { 3134 FILE *fp; 3135 int entry; 3136 char *grubdisk, *title, *osdev, *osroot; 3137 struct stat sbuf; 3138 char failsafe[256]; 3139 3140 assert(mp); 3141 assert(opt); 3142 3143 osdev = strtok(opt, ","); 3144 osroot = strtok(NULL, ","); 3145 if (osroot == NULL) 3146 osroot = menu_root; 3147 title = get_title(osroot); 3148 3149 /* translate /dev/dsk name to grub disk name */ 3150 fp = open_diskmap(osroot); 3151 if (fp == NULL) { 3152 bam_error(DISKMAP_FAIL, osdev); 3153 return (BAM_ERROR); 3154 } 3155 grubdisk = get_grubdisk(osdev, fp, menu_on_bootdev(menu_root, fp)); 3156 (void) fclose(fp); 3157 if (grubdisk == NULL) { 3158 bam_error(DISKMAP_FAIL, osdev); 3159 return (BAM_ERROR); 3160 } 3161 3162 /* add the entry for normal Solaris */ 3163 entry = update_boot_entry(mp, title, grubdisk, 3164 "/platform/i86pc/multiboot", 3165 "/platform/i86pc/boot_archive", 3166 osroot == menu_root); 3167 3168 /* add the entry for failsafe archive */ 3169 (void) snprintf(failsafe, sizeof (failsafe), 3170 "%s/boot/x86.miniroot-safe", osroot); 3171 if (stat(failsafe, &sbuf) == 0) 3172 (void) update_boot_entry(mp, "Solaris failsafe", grubdisk, 3173 "/boot/multiboot kernel/unix -s", 3174 "/boot/x86.miniroot-safe", 3175 osroot == menu_root); 3176 free(grubdisk); 3177 3178 if (entry == BAM_ERROR) { 3179 return (BAM_ERROR); 3180 } 3181 (void) set_global(mp, menu_cmds[DEFAULT_CMD], entry); 3182 return (BAM_WRITE); 3183 } 3184 3185 static char * 3186 read_grub_root(void) 3187 { 3188 FILE *fp; 3189 struct stat sb; 3190 char buf[BAM_MAXLINE]; 3191 char *rootstr; 3192 3193 if (stat(GRUB_slice, &sb) != 0) { 3194 bam_error(MISSING_SLICE_FILE, GRUB_slice, strerror(errno)); 3195 return (NULL); 3196 } 3197 3198 if (stat(GRUB_root, &sb) != 0) { 3199 bam_error(MISSING_ROOT_FILE, GRUB_root, strerror(errno)); 3200 return (NULL); 3201 } 3202 3203 fp = fopen(GRUB_root, "r"); 3204 if (fp == NULL) { 3205 bam_error(OPEN_FAIL, GRUB_root, strerror(errno)); 3206 return (NULL); 3207 } 3208 3209 if (s_fgets(buf, sizeof (buf), fp) == NULL) { 3210 bam_error(EMPTY_FILE, GRUB_root, strerror(errno)); 3211 (void) fclose(fp); 3212 return (NULL); 3213 } 3214 3215 /* 3216 * Copy buf here as check below may trash the buffer 3217 */ 3218 rootstr = s_strdup(buf); 3219 3220 if (s_fgets(buf, sizeof (buf), fp) != NULL) { 3221 bam_error(BAD_ROOT_FILE, GRUB_root); 3222 free(rootstr); 3223 rootstr = NULL; 3224 } 3225 3226 (void) fclose(fp); 3227 3228 return (rootstr); 3229 } 3230 3231 static void 3232 save_default_entry(menu_t *mp) 3233 { 3234 int lineNum, entryNum; 3235 int entry = 0; /* default is 0 */ 3236 char linebuf[BAM_MAXLINE]; 3237 line_t *lp = mp->curdefault; 3238 3239 if (lp) 3240 entry = s_strtol(lp->arg); 3241 3242 (void) snprintf(linebuf, sizeof (linebuf), "#%s%d", BAM_OLDDEF, entry); 3243 line_parser(mp, linebuf, &lineNum, &entryNum); 3244 } 3245 3246 static void 3247 restore_default_entry(menu_t *mp) 3248 { 3249 int entry; 3250 char *str; 3251 line_t *lp = mp->olddefault; 3252 3253 if (lp == NULL) 3254 return; /* nothing to restore */ 3255 3256 str = lp->arg + strlen(BAM_OLDDEF); 3257 entry = s_strtol(str); 3258 (void) set_global(mp, menu_cmds[DEFAULT_CMD], entry); 3259 3260 /* delete saved old default line */ 3261 mp->olddefault = NULL; 3262 unlink_line(mp, lp); 3263 line_free(lp); 3264 } 3265 3266 /* 3267 * This function is for supporting reboot with args. 3268 * The opt value can be: 3269 * NULL delete temp entry, if present 3270 * entry=# switches default entry to 1 3271 * else treated as boot-args and setup a temperary menu entry 3272 * and make it the default 3273 */ 3274 #define REBOOT_TITLE "Solaris_reboot_transient" 3275 3276 /*ARGSUSED*/ 3277 static error_t 3278 update_temp(menu_t *mp, char *menupath, char *opt) 3279 { 3280 int entry; 3281 char *grubdisk, *rootdev; 3282 char kernbuf[1024]; 3283 struct stat sb; 3284 3285 assert(mp); 3286 3287 /* If no option, delete exiting reboot menu entry */ 3288 if (opt == NULL) { 3289 entry_t *ent = find_boot_entry(mp, REBOOT_TITLE, NULL, NULL, 3290 0, &entry); 3291 if (ent == NULL) /* not found is ok */ 3292 return (BAM_SUCCESS); 3293 (void) do_delete(mp, entry); 3294 restore_default_entry(mp); 3295 return (BAM_WRITE); 3296 } 3297 3298 /* if entry= is specified, set the default entry */ 3299 if (strncmp(opt, "entry=", strlen("entry=")) == 0 && 3300 selector(mp, opt, &entry, NULL) == BAM_SUCCESS) { 3301 /* this is entry=# option */ 3302 return (set_global(mp, menu_cmds[DEFAULT_CMD], entry)); 3303 } 3304 3305 /* 3306 * add a new menu entry base on opt and make it the default 3307 */ 3308 grubdisk = NULL; 3309 if (stat(GRUB_slice, &sb) != 0) { 3310 /* 3311 * 1. First get root disk name from mnttab 3312 * 2. Translate disk name to grub name 3313 * 3. Add the new menu entry 3314 */ 3315 rootdev = get_special("/"); 3316 if (rootdev) { 3317 grubdisk = os_to_grubdisk(rootdev, 1); 3318 free(rootdev); 3319 } 3320 } else { 3321 /* 3322 * This is an LU BE. The GRUB_root file 3323 * contains entry for GRUB's "root" cmd. 3324 */ 3325 grubdisk = read_grub_root(); 3326 } 3327 if (grubdisk == NULL) { 3328 bam_error(REBOOT_WITH_ARGS_FAILED); 3329 return (BAM_ERROR); 3330 } 3331 3332 /* add an entry for Solaris reboot */ 3333 (void) snprintf(kernbuf, sizeof (kernbuf), 3334 "/platform/i86pc/multiboot %s", opt); 3335 entry = add_boot_entry(mp, REBOOT_TITLE, grubdisk, kernbuf, 3336 "/platform/i86pc/boot_archive"); 3337 free(grubdisk); 3338 3339 if (entry == BAM_ERROR) { 3340 bam_error(REBOOT_WITH_ARGS_FAILED); 3341 return (BAM_ERROR); 3342 } 3343 3344 save_default_entry(mp); 3345 (void) set_global(mp, menu_cmds[DEFAULT_CMD], entry); 3346 return (BAM_WRITE); 3347 } 3348 3349 static error_t 3350 set_global(menu_t *mp, char *globalcmd, int val) 3351 { 3352 line_t *lp, *found, *last; 3353 char *cp, *str; 3354 char prefix[BAM_MAXLINE]; 3355 size_t len; 3356 3357 assert(mp); 3358 assert(globalcmd); 3359 3360 if (strcmp(globalcmd, menu_cmds[DEFAULT_CMD]) == 0) { 3361 if (val < 0 || mp->end == NULL || val > mp->end->entryNum) { 3362 (void) snprintf(prefix, sizeof (prefix), "%d", val); 3363 bam_error(INVALID_ENTRY, prefix); 3364 return (BAM_ERROR); 3365 } 3366 } 3367 3368 found = last = NULL; 3369 for (lp = mp->start; lp; lp = lp->next) { 3370 if (lp->flags != BAM_GLOBAL) 3371 continue; 3372 3373 last = lp; /* track the last global found */ 3374 3375 if (lp->cmd == NULL) { 3376 bam_error(NO_CMD, lp->lineNum); 3377 continue; 3378 } 3379 if (strcmp(globalcmd, lp->cmd) != 0) 3380 continue; 3381 3382 if (found) { 3383 bam_error(DUP_CMD, globalcmd, lp->lineNum, bam_root); 3384 } 3385 found = lp; 3386 } 3387 3388 if (found == NULL) { 3389 lp = s_calloc(1, sizeof (line_t)); 3390 if (last == NULL) { 3391 lp->next = mp->start; 3392 mp->start = lp; 3393 mp->end = (mp->end) ? mp->end : lp; 3394 } else { 3395 lp->next = last->next; 3396 last->next = lp; 3397 if (lp->next == NULL) 3398 mp->end = lp; 3399 } 3400 lp->flags = BAM_GLOBAL; /* other fields not needed for writes */ 3401 len = strlen(globalcmd) + strlen(menu_cmds[SEP_CMD]); 3402 len += 10; /* val < 10 digits */ 3403 lp->line = s_calloc(1, len); 3404 (void) snprintf(lp->line, len, "%s%s%d", 3405 globalcmd, menu_cmds[SEP_CMD], val); 3406 return (BAM_WRITE); 3407 } 3408 3409 /* 3410 * We are changing an existing entry. Retain any prefix whitespace, 3411 * but overwrite everything else. This preserves tabs added for 3412 * readability. 3413 */ 3414 str = found->line; 3415 cp = prefix; 3416 while (*str == ' ' || *str == '\t') 3417 *(cp++) = *(str++); 3418 *cp = '\0'; /* Terminate prefix */ 3419 len = strlen(prefix) + strlen(globalcmd); 3420 len += strlen(menu_cmds[SEP_CMD]) + 10; 3421 3422 free(found->line); 3423 found->line = s_calloc(1, len); 3424 (void) snprintf(found->line, len, 3425 "%s%s%s%d", prefix, globalcmd, menu_cmds[SEP_CMD], val); 3426 3427 return (BAM_WRITE); /* need a write to menu */ 3428 } 3429 3430 /*ARGSUSED*/ 3431 static error_t 3432 set_option(menu_t *mp, char *menu_path, char *opt) 3433 { 3434 int optnum, optval; 3435 char *val; 3436 3437 assert(mp); 3438 assert(opt); 3439 3440 val = strchr(opt, '='); 3441 if (val == NULL) { 3442 bam_error(INVALID_ENTRY, opt); 3443 return (BAM_ERROR); 3444 } 3445 3446 *val = '\0'; 3447 if (strcmp(opt, "default") == 0) { 3448 optnum = DEFAULT_CMD; 3449 } else if (strcmp(opt, "timeout") == 0) { 3450 optnum = TIMEOUT_CMD; 3451 } else { 3452 bam_error(INVALID_ENTRY, opt); 3453 return (BAM_ERROR); 3454 } 3455 3456 optval = s_strtol(val + 1); 3457 *val = '='; 3458 return (set_global(mp, menu_cmds[optnum], optval)); 3459 } 3460 3461 /* 3462 * The quiet argument suppresses messages. This is used 3463 * when invoked in the context of other commands (e.g. list_entry) 3464 */ 3465 static error_t 3466 read_globals(menu_t *mp, char *menu_path, char *globalcmd, int quiet) 3467 { 3468 line_t *lp; 3469 char *arg; 3470 int done, ret = BAM_SUCCESS; 3471 3472 assert(mp); 3473 assert(menu_path); 3474 assert(globalcmd); 3475 3476 if (mp->start == NULL) { 3477 if (!quiet) 3478 bam_error(NO_MENU, menu_path); 3479 return (BAM_ERROR); 3480 } 3481 3482 done = 0; 3483 for (lp = mp->start; lp; lp = lp->next) { 3484 if (lp->flags != BAM_GLOBAL) 3485 continue; 3486 3487 if (lp->cmd == NULL) { 3488 if (!quiet) 3489 bam_error(NO_CMD, lp->lineNum); 3490 continue; 3491 } 3492 3493 if (strcmp(globalcmd, lp->cmd) != 0) 3494 continue; 3495 3496 /* Found global. Check for duplicates */ 3497 if (done && !quiet) { 3498 bam_error(DUP_CMD, globalcmd, lp->lineNum, bam_root); 3499 ret = BAM_ERROR; 3500 } 3501 3502 arg = lp->arg ? lp->arg : ""; 3503 bam_print(GLOBAL_CMD, globalcmd, arg); 3504 done = 1; 3505 } 3506 3507 if (!done && bam_verbose) 3508 bam_print(NO_ENTRY, globalcmd); 3509 3510 return (ret); 3511 } 3512 3513 static error_t 3514 menu_write(char *root, menu_t *mp) 3515 { 3516 return (list2file(root, MENU_TMP, GRUB_MENU, mp->start)); 3517 } 3518 3519 static void 3520 line_free(line_t *lp) 3521 { 3522 if (lp == NULL) 3523 return; 3524 3525 if (lp->cmd) 3526 free(lp->cmd); 3527 if (lp->sep) 3528 free(lp->sep); 3529 if (lp->arg) 3530 free(lp->arg); 3531 if (lp->line) 3532 free(lp->line); 3533 free(lp); 3534 } 3535 3536 static void 3537 linelist_free(line_t *start) 3538 { 3539 line_t *lp; 3540 3541 while (start) { 3542 lp = start; 3543 start = start->next; 3544 line_free(lp); 3545 } 3546 } 3547 3548 static void 3549 filelist_free(filelist_t *flistp) 3550 { 3551 linelist_free(flistp->head); 3552 flistp->head = NULL; 3553 flistp->tail = NULL; 3554 } 3555 3556 static void 3557 menu_free(menu_t *mp) 3558 { 3559 entry_t *ent, *tmp; 3560 assert(mp); 3561 3562 if (mp->start) 3563 linelist_free(mp->start); 3564 ent = mp->entries; 3565 while (ent) { 3566 tmp = ent; 3567 ent = tmp->next; 3568 free(tmp); 3569 } 3570 3571 free(mp); 3572 } 3573 3574 /* 3575 * Utility routines 3576 */ 3577 3578 3579 /* 3580 * Returns 0 on success 3581 * Any other value indicates an error 3582 */ 3583 static int 3584 exec_cmd(char *cmdline, char *output, int64_t osize) 3585 { 3586 char buf[BUFSIZ]; 3587 int ret; 3588 FILE *ptr; 3589 size_t len; 3590 sigset_t set; 3591 void (*disp)(int); 3592 3593 /* 3594 * For security 3595 * - only absolute paths are allowed 3596 * - set IFS to space and tab 3597 */ 3598 if (*cmdline != '/') { 3599 bam_error(ABS_PATH_REQ, cmdline); 3600 return (-1); 3601 } 3602 (void) putenv("IFS= \t"); 3603 3604 /* 3605 * We may have been exec'ed with SIGCHLD blocked 3606 * unblock it here 3607 */ 3608 (void) sigemptyset(&set); 3609 (void) sigaddset(&set, SIGCHLD); 3610 if (sigprocmask(SIG_UNBLOCK, &set, NULL) != 0) { 3611 bam_error(CANT_UNBLOCK_SIGCHLD, strerror(errno)); 3612 return (-1); 3613 } 3614 3615 /* 3616 * Set SIGCHLD disposition to SIG_DFL for popen/pclose 3617 */ 3618 disp = sigset(SIGCHLD, SIG_DFL); 3619 if (disp == SIG_ERR) { 3620 bam_error(FAILED_SIG, strerror(errno)); 3621 return (-1); 3622 } 3623 if (disp == SIG_HOLD) { 3624 bam_error(BLOCKED_SIG, cmdline); 3625 return (-1); 3626 } 3627 3628 ptr = popen(cmdline, "r"); 3629 if (ptr == NULL) { 3630 bam_error(POPEN_FAIL, cmdline, strerror(errno)); 3631 return (-1); 3632 } 3633 3634 /* 3635 * If we simply do a pclose() following a popen(), pclose() 3636 * will close the reader end of the pipe immediately even 3637 * if the child process has not started/exited. pclose() 3638 * does wait for cmd to terminate before returning though. 3639 * When the executed command writes its output to the pipe 3640 * there is no reader process and the command dies with 3641 * SIGPIPE. To avoid this we read repeatedly until read 3642 * terminates with EOF. This indicates that the command 3643 * (writer) has closed the pipe and we can safely do a 3644 * pclose(). 3645 * 3646 * Since pclose() does wait for the command to exit, 3647 * we can safely reap the exit status of the command 3648 * from the value returned by pclose() 3649 */ 3650 while (fgets(buf, sizeof (buf), ptr) != NULL) { 3651 /* if (bam_verbose) XXX */ 3652 bam_print(PRINT_NO_NEWLINE, buf); 3653 if (output && osize > 0) { 3654 (void) snprintf(output, osize, "%s", buf); 3655 len = strlen(buf); 3656 output += len; 3657 osize -= len; 3658 } 3659 } 3660 3661 ret = pclose(ptr); 3662 if (ret == -1) { 3663 bam_error(PCLOSE_FAIL, cmdline, strerror(errno)); 3664 return (-1); 3665 } 3666 3667 if (WIFEXITED(ret)) { 3668 return (WEXITSTATUS(ret)); 3669 } else { 3670 bam_error(EXEC_FAIL, cmdline, ret); 3671 return (-1); 3672 } 3673 } 3674 3675 /* 3676 * Since this function returns -1 on error 3677 * it cannot be used to convert -1. However, 3678 * that is sufficient for what we need. 3679 */ 3680 static long 3681 s_strtol(char *str) 3682 { 3683 long l; 3684 char *res = NULL; 3685 3686 if (str == NULL) { 3687 return (-1); 3688 } 3689 3690 errno = 0; 3691 l = strtol(str, &res, 10); 3692 if (errno || *res != '\0') { 3693 return (-1); 3694 } 3695 3696 return (l); 3697 } 3698 3699 /* 3700 * Wrapper around fputs, that adds a newline (since fputs doesn't) 3701 */ 3702 static int 3703 s_fputs(char *str, FILE *fp) 3704 { 3705 char linebuf[BAM_MAXLINE]; 3706 3707 (void) snprintf(linebuf, sizeof (linebuf), "%s\n", str); 3708 return (fputs(linebuf, fp)); 3709 } 3710 3711 /* 3712 * Wrapper around fgets, that strips newlines returned by fgets 3713 */ 3714 static char * 3715 s_fgets(char *buf, int buflen, FILE *fp) 3716 { 3717 int n; 3718 3719 buf = fgets(buf, buflen, fp); 3720 if (buf) { 3721 n = strlen(buf); 3722 if (n == buflen - 1 && buf[n-1] != '\n') 3723 bam_error(TOO_LONG, buflen - 1, buf); 3724 buf[n-1] = (buf[n-1] == '\n') ? '\0' : buf[n-1]; 3725 } 3726 3727 return (buf); 3728 } 3729 3730 static void * 3731 s_calloc(size_t nelem, size_t sz) 3732 { 3733 void *ptr; 3734 3735 ptr = calloc(nelem, sz); 3736 if (ptr == NULL) { 3737 bam_error(NO_MEM, nelem*sz); 3738 bam_exit(1); 3739 } 3740 return (ptr); 3741 } 3742 3743 static char * 3744 s_strdup(char *str) 3745 { 3746 char *ptr; 3747 3748 if (str == NULL) 3749 return (NULL); 3750 3751 ptr = strdup(str); 3752 if (ptr == NULL) { 3753 bam_error(NO_MEM, strlen(str) + 1); 3754 bam_exit(1); 3755 } 3756 return (ptr); 3757 } 3758 3759 /* 3760 * Returns 1 if amd64 (or sparc, for syncing x86 diskless clients) 3761 * Returns 0 otherwise 3762 */ 3763 static int 3764 is_amd64(void) 3765 { 3766 static int amd64 = -1; 3767 char isabuf[257]; /* from sysinfo(2) manpage */ 3768 3769 if (amd64 != -1) 3770 return (amd64); 3771 3772 if (sysinfo(SI_ISALIST, isabuf, sizeof (isabuf)) > 0 && 3773 strncmp(isabuf, "amd64 ", strlen("amd64 ")) == 0) 3774 amd64 = 1; 3775 else if (strstr(isabuf, "i386") == NULL) 3776 amd64 = 1; /* diskless server */ 3777 else 3778 amd64 = 0; 3779 3780 return (amd64); 3781 } 3782 3783 static void 3784 append_to_flist(filelist_t *flistp, char *s) 3785 { 3786 line_t *lp; 3787 3788 lp = s_calloc(1, sizeof (line_t)); 3789 lp->line = s_strdup(s); 3790 if (flistp->head == NULL) 3791 flistp->head = lp; 3792 else 3793 flistp->tail->next = lp; 3794 flistp->tail = lp; 3795 } 3796