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