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 (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved. 23 * Copyright 2012 Milan Jurik. All rights reserved. 24 */ 25 26 /* 27 * Copyright 2018 Nexenta Systems, Inc. 28 * Copyright 2016 Toomas Soome <tsoome@me.com> 29 */ 30 31 /* 32 * Loader menu management. 33 */ 34 35 #include <stdio.h> 36 #include <stdlib.h> 37 #include <string.h> 38 #include <wchar.h> 39 #include <errno.h> 40 #include <limits.h> 41 #include <alloca.h> 42 #include <unistd.h> 43 #include <sys/types.h> 44 #include <sys/stat.h> 45 #include <sys/queue.h> 46 #include <libbe.h> 47 #include <ficl.h> 48 #include <ficlplatform/emu.h> 49 #include <ofmt.h> 50 51 #include "bootadm.h" 52 53 extern int bam_rootlen; 54 extern int bam_alt_root; 55 extern char *rootbuf; 56 extern char *bam_root; 57 58 #define BOOT_DIR "/boot" 59 #define CONF_DIR BOOT_DIR "/conf.d" 60 #define MENU BOOT_DIR "/menu.lst" 61 #define TRANSIENT BOOT_DIR "/transient.conf" 62 63 typedef struct menu_entry { 64 int me_idx; 65 boolean_t me_active; 66 char *me_title; 67 char *me_type; 68 char *me_bootfs; 69 STAILQ_ENTRY(menu_entry) me_next; 70 } menu_entry_t; 71 STAILQ_HEAD(menu_lst, menu_entry); 72 73 static error_t set_option(struct menu_lst *, char *, char *); 74 static error_t list_entry(struct menu_lst *, char *, char *); 75 static error_t update_entry(struct menu_lst *, char *, char *); 76 static error_t update_temp(struct menu_lst *, char *, char *); 77 static error_t list_setting(struct menu_lst *menu, char *, char *); 78 79 /* Menu related sub commands */ 80 static subcmd_defn_t menu_subcmds[] = { 81 "set_option", OPT_ABSENT, set_option, 0, /* PUB */ 82 "list_entry", OPT_OPTIONAL, list_entry, 1, /* PUB */ 83 "update_entry", OPT_REQ, update_entry, 0, /* menu */ 84 "update_temp", OPT_OPTIONAL, update_temp, 0, /* reboot */ 85 "list_setting", OPT_OPTIONAL, list_setting, 1, /* menu */ 86 NULL, 0, NULL, 0 /* must be last */ 87 }; 88 89 #define NUM_COLS (5) 90 91 static boolean_t 92 print_menu_cb(ofmt_arg_t *ofarg, char *buf, uint_t bufsize) 93 { 94 menu_entry_t *entry = ofarg->ofmt_cbarg; 95 96 switch (ofarg->ofmt_id) { 97 case 0: 98 (void) snprintf(buf, bufsize, "%d", entry->me_idx); 99 break; 100 case 1: 101 (void) snprintf(buf, bufsize, "%s", entry->me_title); 102 break; 103 case 2: 104 (void) snprintf(buf, bufsize, "%s", entry->me_bootfs); 105 break; 106 case 3: 107 (void) snprintf(buf, bufsize, "%s", entry->me_type); 108 break; 109 case 4: 110 if (entry->me_active == B_TRUE) 111 (void) snprintf(buf, bufsize, " *"); 112 else 113 (void) snprintf(buf, bufsize, " -"); 114 break; 115 default: 116 return (B_FALSE); 117 } 118 return (B_TRUE); 119 } 120 121 static void 122 init_hdr_cols(ofmt_field_t *hdr) 123 { 124 uint_t i; 125 126 for (i = 0; i < NUM_COLS; i++) { 127 char *name = NULL; 128 129 switch (i) { 130 case 0: 131 name = _("INDEX"); 132 break; 133 case 1: 134 name = _("NAME"); 135 break; 136 case 2: 137 name = _("DEVICE"); 138 break; 139 case 3: 140 name = _("TYPE"); 141 break; 142 case 4: 143 name = _("DEFAULT"); 144 break; 145 } 146 147 hdr[i].of_name = name; 148 hdr[i].of_id = i; 149 hdr[i].of_cb = print_menu_cb; 150 151 if (name != NULL) { 152 wchar_t wname[128]; 153 size_t sz = mbstowcs(wname, name, sizeof (wname) / 154 sizeof (wchar_t)); 155 if (sz > 0) { 156 int wcsw = wcswidth(wname, sz); 157 if (wcsw > 0) 158 hdr[i].of_width = wcsw; 159 else 160 hdr[i].of_width = sz; 161 } else { 162 hdr[i].of_width = strlen(name); 163 } 164 } 165 } 166 } 167 168 static void 169 menu_update_widths(ofmt_field_t *hdr, struct menu_lst *menu) 170 { 171 size_t len[NUM_COLS]; 172 menu_entry_t *entry; 173 int i; 174 175 for (i = 0; i < NUM_COLS; i++) 176 len[i] = hdr[i].of_width + 1; 177 178 STAILQ_FOREACH(entry, menu, me_next) { 179 size_t entry_len; 180 181 entry_len = strlen(entry->me_title) + 1; 182 if (entry_len > len[1]) 183 len[1] = entry_len; 184 185 entry_len = strlen(entry->me_bootfs) + 1; 186 if (entry_len > len[2]) 187 len[2] = entry_len; 188 189 entry_len = strlen(entry->me_type) + 1; 190 if (entry_len > len[3]) 191 len[3] = entry_len; 192 } 193 194 for (i = 0; i < NUM_COLS; i++) 195 hdr[i].of_width = len[i]; 196 } 197 198 static ofmt_field_t * 199 init_menu_template(struct menu_lst *menu) 200 { 201 ofmt_field_t *temp; 202 203 if ((temp = calloc(NUM_COLS + 1, sizeof (ofmt_field_t))) == NULL) 204 return (temp); 205 206 init_hdr_cols(temp); 207 menu_update_widths(temp, menu); 208 return (temp); 209 } 210 211 static void 212 print_nodes(boolean_t parsable, struct menu_lst *menu) 213 { 214 ofmt_status_t oferr; 215 ofmt_handle_t ofmt; 216 uint_t ofmtflags = 0; 217 ofmt_field_t *menu_template; 218 menu_entry_t *entry; 219 220 if (parsable == B_TRUE) 221 ofmtflags = OFMT_PARSABLE; 222 223 menu_template = init_menu_template(menu); 224 oferr = ofmt_open(NULL, menu_template, ofmtflags, 0, &ofmt); 225 226 if (oferr != OFMT_SUCCESS) { 227 char buf[OFMT_BUFSIZE]; 228 229 (void) ofmt_strerror(ofmt, oferr, buf, sizeof (buf)); 230 (void) printf("bootadm: %s\n", buf); 231 free(menu_template); 232 return; 233 } 234 235 STAILQ_FOREACH(entry, menu, me_next) 236 ofmt_print(ofmt, entry); 237 238 ofmt_close(ofmt); 239 free(menu_template); 240 } 241 242 /* 243 * Get the be_active_on_boot for bootfs. 244 */ 245 static boolean_t 246 menu_active_on_boot(be_node_list_t *be_nodes, const char *bootfs) 247 { 248 be_node_list_t *be_node; 249 boolean_t rv = B_FALSE; 250 251 for (be_node = be_nodes; be_node != NULL; 252 be_node = be_node->be_next_node) { 253 if (strcmp(be_node->be_root_ds, bootfs) == 0) { 254 rv = be_node->be_active_on_boot; 255 break; 256 } 257 } 258 259 return (rv); 260 } 261 262 error_t 263 menu_read(struct menu_lst *menu, char *menu_path) 264 { 265 FILE *fp; 266 be_node_list_t *be_nodes; 267 menu_entry_t *mp; 268 char buf[PATH_MAX]; 269 char *title; 270 char *bootfs; 271 char *type; 272 char *key, *value; 273 int i = 0; 274 int ret = BAM_SUCCESS; 275 276 fp = fopen(menu_path, "r"); 277 if (fp == NULL) 278 return (BAM_ERROR); 279 280 if (be_list(NULL, &be_nodes) != BE_SUCCESS) 281 be_nodes = NULL; 282 283 /* 284 * menu.lst entry is on two lines, one for title, one for bootfs 285 * so we process both lines in succession. 286 */ 287 title = NULL; 288 type = NULL; 289 bootfs = NULL; 290 do { 291 if (fgets(buf, PATH_MAX, fp) == NULL) { 292 if (!feof(fp)) 293 ret = BAM_ERROR; 294 goto done; 295 } 296 key = strtok(buf, " \n"); 297 if (strcmp(key, "title") != 0) { 298 ret = BAM_ERROR; 299 goto done; 300 } 301 value = strtok(NULL, " \n"); 302 if ((title = strdup(value)) == NULL) { 303 ret = BAM_ERROR; 304 goto done; 305 } 306 307 if (fgets(buf, PATH_MAX, fp) == NULL) { 308 ret = BAM_ERROR; 309 goto done; 310 } 311 312 key = strtok(buf, " \n"); 313 if ((type = strdup(key)) == NULL) { 314 ret = BAM_ERROR; 315 goto done; 316 } 317 value = strtok(NULL, " \n"); 318 if ((bootfs = strdup(value)) == NULL) { 319 ret = BAM_ERROR; 320 goto done; 321 } 322 if ((mp = malloc(sizeof (menu_entry_t))) == NULL) { 323 ret = BAM_ERROR; 324 goto done; 325 } 326 mp->me_idx = i++; 327 mp->me_title = title; 328 mp->me_type = type; 329 mp->me_bootfs = bootfs; 330 mp->me_active = menu_active_on_boot(be_nodes, bootfs); 331 STAILQ_INSERT_TAIL(menu, mp, me_next); 332 333 title = NULL; 334 type = NULL; 335 bootfs = NULL; 336 } while (feof(fp) == 0); 337 338 done: 339 free(title); 340 free(type); 341 free(bootfs); 342 (void) fclose(fp); 343 be_free_list(be_nodes); 344 return (ret); 345 } 346 347 void 348 menu_free(struct menu_lst *menu) 349 { 350 menu_entry_t *entry; 351 STAILQ_FOREACH(entry, menu, me_next) { 352 STAILQ_REMOVE_HEAD(menu, me_next); 353 free(entry->me_title); 354 free(entry->me_type); 355 free(entry->me_bootfs); 356 free(entry); 357 } 358 } 359 360 error_t 361 bam_loader_menu(char *subcmd, char *opt, int largc, char *largv[]) 362 { 363 error_t ret; 364 char menu_path[PATH_MAX]; 365 char clean_menu_root[PATH_MAX]; 366 char menu_root[PATH_MAX]; 367 struct stat sb; 368 error_t (*f)(struct menu_lst *, char *, char *); 369 char *special; 370 char *pool = NULL; 371 zfs_mnted_t zmnted; 372 char *zmntpt; 373 char *osdev; 374 char *osroot; 375 const char *fcn = "bam_loader_menu()"; 376 struct menu_lst menu = {0}; 377 378 STAILQ_INIT(&menu); 379 380 /* 381 * Check arguments 382 */ 383 ret = check_subcmd_and_options(subcmd, opt, menu_subcmds, &f); 384 if (ret == BAM_ERROR) { 385 return (BAM_ERROR); 386 } 387 388 assert(bam_root); 389 390 (void) strlcpy(menu_root, bam_root, sizeof (menu_root)); 391 osdev = osroot = NULL; 392 393 if (strcmp(subcmd, "update_entry") == 0) { 394 assert(opt); 395 396 osdev = strtok(opt, ","); 397 assert(osdev); 398 osroot = strtok(NULL, ","); 399 if (osroot) { 400 /* fixup bam_root so that it points at osroot */ 401 if (realpath(osroot, rootbuf) == NULL) { 402 bam_error(_("cannot resolve path %s: %s\n"), 403 osroot, strerror(errno)); 404 return (BAM_ERROR); 405 } 406 bam_alt_root = 1; 407 bam_root = rootbuf; 408 bam_rootlen = strlen(rootbuf); 409 } 410 } 411 412 if (stat(menu_root, &sb) == -1) { 413 bam_error(_("cannot find menu\n")); 414 return (BAM_ERROR); 415 } 416 417 if (!is_zfs(menu_root)) { 418 bam_error(_("only ZFS root is supported\n")); 419 return (BAM_ERROR); 420 } 421 422 assert(strcmp(menu_root, bam_root) == 0); 423 special = get_special(menu_root); 424 INJECT_ERROR1("Z_MENU_GET_SPECIAL", special = NULL); 425 if (special == NULL) { 426 bam_error(_("cant find special file for mount-point %s\n"), 427 menu_root); 428 return (BAM_ERROR); 429 } 430 pool = strtok(special, "/"); 431 INJECT_ERROR1("Z_MENU_GET_POOL", pool = NULL); 432 if (pool == NULL) { 433 free(special); 434 bam_error(_("cant find pool for mount-point %s\n"), menu_root); 435 return (BAM_ERROR); 436 } 437 BAM_DPRINTF(("%s: derived pool=%s from special\n", fcn, pool)); 438 439 zmntpt = mount_top_dataset(pool, &zmnted); 440 INJECT_ERROR1("Z_MENU_MOUNT_TOP_DATASET", zmntpt = NULL); 441 if (zmntpt == NULL) { 442 bam_error(_("cannot mount pool dataset for pool: %s\n"), pool); 443 free(special); 444 return (BAM_ERROR); 445 } 446 BAM_DPRINTF(("%s: top dataset mountpoint=%s\n", fcn, zmntpt)); 447 448 (void) strlcpy(menu_root, zmntpt, sizeof (menu_root)); 449 BAM_DPRINTF(("%s: zfs menu_root=%s\n", fcn, menu_root)); 450 451 elide_trailing_slash(menu_root, clean_menu_root, 452 sizeof (clean_menu_root)); 453 454 BAM_DPRINTF(("%s: cleaned menu root is <%s>\n", fcn, clean_menu_root)); 455 456 (void) strlcpy(menu_path, clean_menu_root, sizeof (menu_path)); 457 (void) strlcat(menu_path, MENU, sizeof (menu_path)); 458 459 BAM_DPRINTF(("%s: menu path is: %s\n", fcn, menu_path)); 460 461 /* 462 * update_entry is special case, its used by installer 463 * and needs to create menu.lst file for loader 464 */ 465 if (menu_read(&menu, menu_path) == BAM_ERROR && 466 strcmp(subcmd, "update_entry") != 0) { 467 bam_error(_("cannot find menu file: %s\n"), menu_path); 468 if (special != NULL) 469 free(special); 470 return (BAM_ERROR); 471 } 472 473 /* 474 * If listing the menu, display the menu location 475 */ 476 if (strcmp(subcmd, "list_entry") == 0) 477 bam_print(_("the location for the active menu is: %s\n"), 478 menu_path); 479 480 /* 481 * We already checked the following case in 482 * check_subcmd_and_suboptions() above. Complete the 483 * final step now. 484 */ 485 if (strcmp(subcmd, "set_option") == 0) { 486 assert(largc == 1 && largv[0] && largv[1] == NULL); 487 opt = largv[0]; 488 } else if (strcmp(subcmd, "list_setting") != 0) { 489 assert(largc == 0 && largv == NULL); 490 } 491 492 /* 493 * Once the sub-cmd handler has run 494 * only the line field is guaranteed to have valid values 495 */ 496 if (strcmp(subcmd, "update_entry") == 0) { 497 ret = f(&menu, menu_root, osdev); 498 } else if (strcmp(subcmd, "upgrade") == 0) { 499 ret = f(&menu, bam_root, menu_root); 500 } else if (strcmp(subcmd, "list_entry") == 0) { 501 ret = f(&menu, menu_path, opt); 502 } else if (strcmp(subcmd, "list_setting") == 0) { 503 ret = f(&menu, ((largc > 0) ? largv[0] : ""), 504 ((largc > 1) ? largv[1] : "")); 505 } else 506 ret = f(&menu, NULL, opt); 507 508 if (ret == BAM_WRITE) { 509 BAM_DPRINTF(("%s: writing menu to clean-menu-root: <%s>\n", 510 fcn, clean_menu_root)); 511 /* ret = menu_write(clean_menu_root, menu); */ 512 } 513 514 INJECT_ERROR1("POOL_SET", pool = "/pooldata"); 515 assert((is_zfs(menu_root)) ^ (pool == NULL)); 516 if (pool) { 517 (void) umount_top_dataset(pool, zmnted, zmntpt); 518 free(special); 519 } 520 521 menu_free(&menu); 522 return (ret); 523 } 524 525 /* 526 * To suppress output from ficl. We do not want to see messages 527 * from interpreting loader config. 528 */ 529 530 /*ARGSUSED*/ 531 static void 532 ficlTextOutSilent(ficlCallback *cb, char *text) 533 { 534 } 535 536 /*ARGSUSED*/ 537 static error_t 538 set_option(struct menu_lst *menu, char *dummy, char *opt) 539 { 540 char path[PATH_MAX]; 541 char *val; 542 char *rest; 543 int optval; 544 menu_entry_t *entry; 545 nvlist_t *be_attrs; 546 FILE *fp; 547 int rv, ret = BAM_SUCCESS; 548 549 assert(menu); 550 assert(opt); 551 assert(dummy == NULL); 552 553 val = strchr(opt, '='); 554 if (val != NULL) { 555 *val++ = '\0'; 556 } 557 558 if (strcmp(opt, "default") == 0) { 559 errno = 0; 560 optval = strtol(val, &rest, 10); 561 if (errno != 0 || *rest != '\0') { 562 bam_error(_("invalid boot entry number: %s\n"), val); 563 return (BAM_ERROR); 564 } 565 STAILQ_FOREACH(entry, menu, me_next) { 566 if (entry->me_idx == optval) 567 break; 568 } 569 if (entry == NULL) { 570 bam_error(_("invalid boot entry number: %s\n"), val); 571 return (BAM_ERROR); 572 } 573 if (nvlist_alloc(&be_attrs, NV_UNIQUE_NAME, 0) != 0) { 574 bam_error(_("out of memory\n")); 575 return (BAM_ERROR); 576 } 577 if (nvlist_add_string(be_attrs, BE_ATTR_ORIG_BE_NAME, 578 entry->me_title) != 0) { 579 bam_error(_("out of memory\n")); 580 nvlist_free(be_attrs); 581 return (BAM_ERROR); 582 } 583 ret = be_activate(be_attrs); 584 nvlist_free(be_attrs); 585 if (ret != 0) 586 ret = BAM_ERROR; 587 return (ret); 588 } else if (strcmp(opt, "timeout") == 0) { 589 errno = 0; 590 optval = strtol(val, &rest, 10); 591 if (errno != 0 || *rest != '\0') { 592 bam_error(_("invalid timeout: %s\n"), val); 593 return (BAM_ERROR); 594 } 595 596 (void) snprintf(path, PATH_MAX, "%s" CONF_DIR "/timeout", 597 bam_root); 598 599 fp = fopen(path, "w"); 600 if (fp == NULL) { 601 bam_error(_("failed to open file: %s: %s\n"), 602 path, strerror(errno)); 603 return (BAM_ERROR); 604 } 605 /* 606 * timeout=-1 is to disable auto boot in illumos, but 607 * loader needs "NO" to disable auto boot. 608 */ 609 if (optval == -1) 610 rv = fprintf(fp, "autoboot_delay=\"NO\"\n"); 611 else 612 rv = fprintf(fp, "autoboot_delay=\"%d\"\n", optval); 613 614 if (rv < 0) { 615 bam_error(_("write to file failed: %s: %s\n"), 616 path, strerror(errno)); 617 (void) fclose(fp); 618 ret = BAM_ERROR; 619 } else 620 rv = fclose(fp); 621 622 if (rv < 0) { 623 bam_error(_("failed to close file: %s: %s\n"), 624 path, strerror(errno)); 625 ret = BAM_ERROR; 626 } 627 if (ret == BAM_ERROR) 628 (void) unlink(path); 629 630 return (BAM_SUCCESS); 631 } 632 633 bam_error(_("invalid option: %s\n"), opt); 634 return (BAM_ERROR); 635 } 636 637 static int 638 bam_mount_be(menu_entry_t *entry, char **dir) 639 { 640 nvlist_t *be_attrs = NULL; 641 const char *tmpdir = getenv("TMPDIR"); 642 const char *tmpname = "bam.XXXXXX"; 643 be_node_list_t *be_node, *be_nodes = NULL; 644 int ret; 645 646 *dir = NULL; 647 if (tmpdir == NULL) 648 tmpdir = "/tmp"; 649 650 ret = asprintf(dir, "%s/%s", tmpdir, tmpname); 651 if (ret < 0) { 652 return (BE_ERR_NOMEM); 653 } 654 *dir = mkdtemp(*dir); 655 656 if (nvlist_alloc(&be_attrs, NV_UNIQUE_NAME, 0) != 0) { 657 ret = BE_ERR_NOMEM; 658 goto out; 659 } 660 661 ret = be_list(NULL, &be_nodes); 662 if (ret != BE_SUCCESS) { 663 goto out; 664 } 665 666 for (be_node = be_nodes; be_node; 667 be_node = be_node->be_next_node) 668 if (strcmp(be_node->be_root_ds, entry->me_bootfs) == 0) 669 break; 670 671 if (nvlist_add_string(be_attrs, BE_ATTR_ORIG_BE_NAME, 672 be_node->be_node_name) != 0) { 673 ret = BE_ERR_NOMEM; 674 goto out; 675 } 676 677 if (nvlist_add_string(be_attrs, BE_ATTR_MOUNTPOINT, *dir) != 0) { 678 ret = BE_ERR_NOMEM; 679 goto out; 680 } 681 682 ret = be_mount(be_attrs); 683 if (ret == BE_ERR_MOUNTED) { 684 /* 685 * if BE is mounted, dir does not point to correct directory 686 */ 687 (void) rmdir(*dir); 688 free(*dir); 689 *dir = NULL; 690 } 691 out: 692 if (be_nodes != NULL) 693 be_free_list(be_nodes); 694 nvlist_free(be_attrs); 695 return (ret); 696 } 697 698 static int 699 bam_umount_be(char *dir) 700 { 701 nvlist_t *be_attrs; 702 int ret; 703 704 if (dir == NULL) /* nothing to do */ 705 return (BE_SUCCESS); 706 707 if (nvlist_alloc(&be_attrs, NV_UNIQUE_NAME, 0) != 0) 708 return (BE_ERR_NOMEM); 709 710 if (nvlist_add_string(be_attrs, BE_ATTR_ORIG_BE_NAME, dir) != 0) { 711 ret = BE_ERR_NOMEM; 712 goto out; 713 } 714 715 ret = be_unmount(be_attrs); 716 out: 717 nvlist_free(be_attrs); 718 return (ret); 719 } 720 721 /* 722 * display details of menu entry or single property 723 */ 724 static error_t 725 list_menu_entry(menu_entry_t *entry, char *setting) 726 { 727 int ret = BAM_SUCCESS; 728 char *ptr, *dir; 729 char buf[MAX_INPUT]; 730 ficlVm *vm; 731 int mounted; 732 733 if (strcmp(entry->me_type, "bootfs") != 0 || 734 strchr(entry->me_bootfs, ':') != NULL) { 735 (void) printf("\nTitle: %s\n", entry->me_title); 736 (void) printf("Type: %s\n", entry->me_type); 737 (void) printf("Device: %s\n", entry->me_bootfs); 738 return (ret); 739 } 740 741 mounted = bam_mount_be(entry, &dir); 742 if (mounted != BE_SUCCESS && mounted != BE_ERR_MOUNTED) { 743 if (dir != NULL) { 744 (void) rmdir(dir); 745 free(dir); 746 } 747 bam_error(_("%s is not mounted\n"), entry->me_title); 748 return (BAM_ERROR); 749 } 750 751 vm = bf_init("", ficlTextOutSilent); 752 if (vm == NULL) { 753 bam_error(_("error setting up forth interpreter\n")); 754 ret = BAM_ERROR; 755 goto done; 756 } 757 758 /* should only get FICL_VM_STATUS_OUT_OF_TEXT */ 759 (void) snprintf(buf, MAX_INPUT, "set currdev=zfs:%s:", 760 entry->me_bootfs); 761 ret = ficlVmEvaluate(vm, buf); 762 if (ret != FICL_VM_STATUS_OUT_OF_TEXT) { 763 bam_error(_("error interpreting boot config\n")); 764 ret = BAM_ERROR; 765 goto done; 766 } 767 (void) snprintf(buf, MAX_INPUT, "include /boot/forth/loader.4th"); 768 ret = ficlVmEvaluate(vm, buf); 769 if (ret != FICL_VM_STATUS_OUT_OF_TEXT) { 770 bam_error(_("error interpreting boot config\n")); 771 ret = BAM_ERROR; 772 goto done; 773 } 774 (void) snprintf(buf, MAX_INPUT, "start"); 775 ret = ficlVmEvaluate(vm, buf); 776 if (ret != FICL_VM_STATUS_OUT_OF_TEXT) { 777 bam_error(_("error interpreting boot config\n")); 778 ret = BAM_ERROR; 779 goto done; 780 } 781 (void) snprintf(buf, MAX_INPUT, "boot"); 782 ret = ficlVmEvaluate(vm, buf); 783 if (ret != FICL_VM_STATUS_OUT_OF_TEXT) { 784 bam_error(_("error interpreting boot config\n")); 785 ret = BAM_ERROR; 786 goto done; 787 } 788 789 ret = BAM_SUCCESS; 790 if (*setting == '\0') 791 (void) printf("\nTitle: %s\n", entry->me_title); 792 else if (strcasecmp(setting, "title") == 0) { 793 (void) printf("%s\n", entry->me_title); 794 goto done; 795 } 796 797 ptr = getenv("autoboot_delay"); 798 if (ptr != NULL) { 799 char *timeout = "-1"; 800 801 if (strcasecmp(ptr, "NO") != 0) 802 timeout = ptr; 803 804 if (*setting == '\0') 805 (void) printf("Timeout: %s\n", timeout); 806 else if (strcasecmp(setting, "timeout") == 0) { 807 (void) printf("%s\n", timeout); 808 goto done; 809 } 810 811 } 812 ptr = getenv("console"); 813 if (ptr != NULL) { 814 if (*setting == '\0') 815 (void) printf("Console: %s\n", ptr); 816 else if (strcasecmp(setting, "console") == 0) { 817 (void) printf("%s\n", ptr); 818 goto done; 819 } 820 } 821 822 if (*setting == '\0') 823 (void) printf("Bootfs: %s\n", entry->me_bootfs); 824 else if (strcasecmp(setting, "bootfs") == 0) { 825 (void) printf("%s\n", entry->me_bootfs); 826 goto done; 827 } 828 829 ptr = getenv("kernelname"); 830 if (ptr != NULL) { 831 if (*setting == '\0') { 832 (void) printf("Kernel: %s\n", ptr); 833 } else if (strcasecmp(setting, "kernel") == 0) { 834 (void) printf("%s\n", ptr); 835 goto done; 836 } 837 } 838 839 ptr = getenv("boot-args"); 840 if (ptr != NULL) { 841 if (*setting == '\0') { 842 (void) printf("Boot-args: \"%s\"\n", ptr); 843 } else if (strcasecmp(setting, "boot-args") == 0) { 844 (void) printf("%s\n", ptr); 845 goto done; 846 } 847 } 848 849 if (*setting == '\0' || strcasecmp(setting, "modules") == 0) { 850 (void) printf("\nModules:\n"); 851 ficlVmSetTextOut(vm, ficlCallbackDefaultTextOut); 852 (void) snprintf(buf, MAX_INPUT, "show-module-options"); 853 ret = ficlVmEvaluate(vm, buf); 854 if (ret != FICL_VM_STATUS_OUT_OF_TEXT) { 855 bam_error(_("error interpreting boot config\n")); 856 ret = BAM_ERROR; 857 goto done; 858 } 859 ret = BAM_SUCCESS; 860 goto done; 861 } 862 863 /* if we got here with setting string, its unknown property */ 864 if (*setting != '\0') { 865 bam_error(_("unknown property: %s\n"), setting); 866 ret = BAM_ERROR; 867 } else 868 ret = BAM_SUCCESS; 869 done: 870 bf_fini(); 871 if (mounted != BE_ERR_MOUNTED) { 872 (void) bam_umount_be(dir); 873 } 874 875 if (dir != NULL) { 876 (void) rmdir(dir); 877 free(dir); 878 } 879 880 return (ret); 881 } 882 883 /*ARGSUSED*/ 884 static error_t 885 list_entry(struct menu_lst *menu, char *menu_root, char *opt) 886 { 887 error_t ret = BAM_SUCCESS; 888 menu_entry_t *entry; 889 char *ptr, *title = NULL; 890 int i, e = -1; 891 892 if (opt == NULL) { 893 print_nodes(B_FALSE, menu); 894 return (ret); 895 } 896 897 if ((ptr = strchr(opt, '=')) == NULL) { 898 bam_error(_("invalid option: %s\n"), opt); 899 return (BAM_ERROR); 900 } 901 902 i = ptr - opt; 903 if (strncmp(opt, "entry", i) == 0) { 904 e = atoi(ptr+1); 905 } else if (strncmp(opt, "title", i) == 0) { 906 title = ptr+1; 907 } else { 908 bam_error(_("invalid option: %s\n"), opt); 909 return (BAM_ERROR); 910 } 911 912 STAILQ_FOREACH(entry, menu, me_next) { 913 if (title != NULL) { 914 if (strcmp(title, entry->me_title) == 0) 915 break; 916 } else if (entry->me_idx == e) 917 break; 918 } 919 920 if (entry == NULL) { 921 bam_error(_("no matching entry found\n")); 922 return (BAM_ERROR); 923 } 924 925 return (list_menu_entry(entry, "")); 926 } 927 928 /* 929 * For now this is just stub entry to support grub interface, the 930 * known consumer is installer ict.py code, calling as: 931 * bootadm update-menu -R /a -Z -o rdisk 932 * Later this can be converted to do something useful. 933 */ 934 /*ARGSUSED*/ 935 static error_t 936 update_entry(struct menu_lst *menu, char *menu_root, char *osdev) 937 { 938 char path[PATH_MAX]; 939 char *pool = menu_root + 1; 940 be_node_list_t *be_nodes, *be_node; 941 int rv; 942 FILE *fp; 943 944 (void) snprintf(path, PATH_MAX, "%s%s", menu_root, MENU); 945 rv = be_list(NULL, &be_nodes); 946 947 if (rv != BE_SUCCESS) 948 return (BAM_ERROR); 949 950 fp = fopen(path, "w"); 951 if (fp == NULL) { 952 be_free_list(be_nodes); 953 return (BAM_ERROR); 954 } 955 956 for (be_node = be_nodes; be_node; be_node = be_node->be_next_node) { 957 if (strcmp(be_node->be_rpool, pool) == 0) { 958 (void) fprintf(fp, "title %s\n", be_node->be_node_name); 959 (void) fprintf(fp, "bootfs %s\n", be_node->be_root_ds); 960 } 961 } 962 963 be_free_list(be_nodes); 964 (void) fclose(fp); 965 return (BAM_SUCCESS); 966 } 967 968 /*ARGSUSED*/ 969 static error_t 970 update_temp(struct menu_lst *menu, char *dummy, char *opt) 971 { 972 error_t ret = BAM_ERROR; 973 char path[PATH_MAX]; 974 char buf[MAX_INPUT]; 975 struct mnttab mpref = { 0 }; 976 struct mnttab mp = { 0 }; 977 ficlVm *vm; 978 char *o; 979 FILE *fp; 980 981 (void) snprintf(path, PATH_MAX, "%s" TRANSIENT, bam_root); 982 /* 983 * if opt == NULL, remove transient config 984 */ 985 if (opt == NULL) { 986 (void) unlink(path); 987 return (BAM_SUCCESS); 988 } 989 990 fp = fopen(MNTTAB, "r"); 991 if (fp == NULL) 992 return (BAM_ERROR); 993 994 mpref.mnt_mountp = "/"; 995 if (getmntany(fp, &mp, &mpref) != 0) { 996 (void) fclose(fp); 997 return (BAM_ERROR); 998 } 999 (void) fclose(fp); 1000 1001 vm = bf_init("", ficlTextOutSilent); 1002 if (vm == NULL) { 1003 bam_error(_("Error setting up forth interpreter\n")); 1004 return (ret); 1005 } 1006 1007 /* 1008 * Need to check current boot config, so fire up the ficl. 1009 */ 1010 (void) snprintf(buf, MAX_INPUT, "set currdev=zfs:%s:", mp.mnt_special); 1011 ret = ficlVmEvaluate(vm, buf); 1012 if (ret != FICL_VM_STATUS_OUT_OF_TEXT) { 1013 bam_error(_("Error interpreting boot config\n")); 1014 bf_fini(); 1015 return (BAM_ERROR); 1016 } 1017 (void) snprintf(buf, MAX_INPUT, "include /boot/forth/loader.4th"); 1018 ret = ficlVmEvaluate(vm, buf); 1019 if (ret != FICL_VM_STATUS_OUT_OF_TEXT) { 1020 bam_error(_("Error interpreting boot config\n")); 1021 bf_fini(); 1022 return (BAM_ERROR); 1023 } 1024 (void) snprintf(buf, MAX_INPUT, "start"); 1025 ret = ficlVmEvaluate(vm, buf); 1026 if (ret != FICL_VM_STATUS_OUT_OF_TEXT) { 1027 bam_error(_("Error interpreting boot config\n")); 1028 bf_fini(); 1029 return (BAM_ERROR); 1030 } 1031 (void) snprintf(buf, MAX_INPUT, "boot"); 1032 ret = ficlVmEvaluate(vm, buf); 1033 if (ret != FICL_VM_STATUS_OUT_OF_TEXT) { 1034 bam_error(_("Error interpreting boot config\n")); 1035 bf_fini(); 1036 return (BAM_ERROR); 1037 } 1038 bf_fini(); 1039 1040 if (opt[0] == '-') { 1041 fp = fopen(path, "w"); 1042 if (fp == NULL) 1043 return (BAM_ERROR); 1044 (void) fprintf(fp, "boot-args=\"%s\"\n", opt); 1045 (void) fclose(fp); 1046 return (BAM_SUCCESS); 1047 } 1048 1049 /* 1050 * it should be the case with "kernel args" 1051 * so, we split the opt at first space 1052 * and store bootfile= and boot-args= 1053 */ 1054 o = strchr(opt, ' '); 1055 if (o == NULL) { 1056 fp = fopen(path, "w"); 1057 if (fp == NULL) 1058 return (BAM_ERROR); 1059 (void) fprintf(fp, "bootfile=\"%s;unix\"\n", opt); 1060 (void) fclose(fp); 1061 return (BAM_SUCCESS); 1062 } 1063 *o++ = '\0'; 1064 fp = fopen(path, "w"); 1065 if (fp == NULL) 1066 return (BAM_ERROR); 1067 (void) fprintf(fp, "bootfile=\"%s;unix\"\n", opt); 1068 (void) fprintf(fp, "boot-args=\"%s\"\n", o); 1069 (void) fflush(fp); 1070 (void) fclose(fp); 1071 return (ret); 1072 } 1073 1074 static error_t 1075 list_setting(struct menu_lst *menu, char *which, char *setting) 1076 { 1077 int entry = -1; 1078 menu_entry_t *m; 1079 be_node_list_t *be_nodes, *be_node = NULL; 1080 int ret; 1081 1082 assert(which); 1083 assert(setting); 1084 1085 /* 1086 * which can be: 1087 * "" - list default entry 1088 * number - use for entry number 1089 * property name 1090 */ 1091 if (*which != '\0') { 1092 if (isdigit(*which)) { 1093 char *rest; 1094 errno = 0; 1095 entry = strtol(which, &rest, 10); 1096 if (errno != 0 || *rest != '\0') { 1097 bam_error(_("invalid boot entry number: %s\n"), 1098 which); 1099 return (BAM_ERROR); 1100 } 1101 } else 1102 setting = which; 1103 } 1104 1105 /* find default entry */ 1106 if (entry == -1) { 1107 ret = be_list(NULL, &be_nodes); 1108 if (ret != BE_SUCCESS) { 1109 bam_error(_("No BE's found\n")); 1110 return (BAM_ERROR); 1111 } 1112 STAILQ_FOREACH(m, menu, me_next) { 1113 entry++; 1114 for (be_node = be_nodes; be_node; 1115 be_node = be_node->be_next_node) { 1116 if (strcmp(be_node->be_root_ds, 1117 m->me_bootfs) == 0) 1118 break; 1119 } 1120 if (be_node != NULL && 1121 be_node->be_active_on_boot == B_TRUE) 1122 break; /* found active node */ 1123 } 1124 be_free_list(be_nodes); 1125 if (be_node == NULL) { 1126 bam_error(_("None of BE nodes is marked active\n")); 1127 return (BAM_ERROR); 1128 } 1129 } else { 1130 STAILQ_FOREACH(m, menu, me_next) 1131 if (m->me_idx == entry) 1132 break; 1133 1134 if (m == NULL) { 1135 bam_error(_("no matching entry found\n")); 1136 return (BAM_ERROR); 1137 } 1138 } 1139 1140 return (list_menu_entry(m, setting)); 1141 }