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