xref: /illumos-gate/usr/src/cmd/boot/bootadm/bootadm_loader.c (revision 48bbca816818409505a6e214d0911fda44e622e3)
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 }
1330