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