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