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 }