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