xref: /illumos-gate/usr/src/cmd/boot/bootadm/bootadm.c (revision 40541d5d290c13c7837bf676a9b7d6302a2acbb5)
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, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
28 
29 /*
30  * bootadm(1M) is a new utility for managing bootability of
31  * Solaris *Newboot* environments. It has two primary tasks:
32  * 	- Allow end users to manage bootability of Newboot Solaris instances
33  *	- Provide services to other subsystems in Solaris (primarily Install)
34  */
35 
36 /* Headers */
37 #include <stdio.h>
38 #include <errno.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <unistd.h>
42 #include <sys/types.h>
43 #include <sys/stat.h>
44 #include <stdarg.h>
45 #include <limits.h>
46 #include <signal.h>
47 #include <sys/wait.h>
48 #include <sys/mnttab.h>
49 #include <sys/statvfs.h>
50 #include <libnvpair.h>
51 #include <ftw.h>
52 #include <fcntl.h>
53 #include <strings.h>
54 #include <sys/systeminfo.h>
55 #include <sys/dktp/fdisk.h>
56 
57 #include <pwd.h>
58 #include <grp.h>
59 #include <device_info.h>
60 
61 #include <libintl.h>
62 #include <locale.h>
63 
64 #include <assert.h>
65 
66 #include "message.h"
67 
68 #ifndef TEXT_DOMAIN
69 #define	TEXT_DOMAIN	"SUNW_OST_OSCMD"
70 #endif	/* TEXT_DOMAIN */
71 
72 /* Type definitions */
73 
74 /* Primary subcmds */
75 typedef enum {
76 	BAM_MENU = 3,
77 	BAM_ARCHIVE
78 } subcmd_t;
79 
80 /* GRUB menu per-line classification */
81 typedef enum {
82 	BAM_INVALID = 0,
83 	BAM_EMPTY,
84 	BAM_COMMENT,
85 	BAM_GLOBAL,
86 	BAM_ENTRY,
87 	BAM_TITLE
88 } menu_flag_t;
89 
90 /* struct for menu.lst contents */
91 typedef struct line {
92 	int  lineNum;	/* Line number in menu.lst */
93 	int  entryNum;	/* menu boot entry #. ENTRY_INIT if not applicable */
94 	char *cmd;
95 	char *sep;
96 	char *arg;
97 	char *line;
98 	menu_flag_t flags;
99 	struct line *next;
100 } line_t;
101 
102 typedef struct {
103 	line_t *start;
104 	line_t *end;
105 } menu_t;
106 
107 typedef enum {
108     OPT_ABSENT = 0,	/* No option */
109     OPT_REQ,		/* option required */
110     OPT_OPTIONAL	/* option may or may not be present */
111 } option_t;
112 
113 typedef enum {
114     BAM_ERROR = -1,	/* Must be negative. add_boot_entry() depends on it */
115     BAM_SUCCESS = 0,
116     BAM_WRITE = 2
117 } error_t;
118 
119 typedef struct {
120 	char	*subcmd;
121 	option_t option;
122 	error_t (*handler)();
123 } subcmd_defn_t;
124 
125 
126 #define	BAM_MAXLINE	8192
127 
128 #define	LINE_INIT	0	/* lineNum initial value */
129 #define	ENTRY_INIT	-1	/* entryNum initial value */
130 #define	ALL_ENTRIES	-2	/* selects all boot entries */
131 
132 #define	GRUB_DIR		"/boot/grub"
133 #define	MULTI_BOOT		"/platform/i86pc/multiboot"
134 #define	BOOT_ARCHIVE		"/platform/i86pc/boot_archive"
135 #define	GRUB_MENU		"/boot/grub/menu.lst"
136 #define	MENU_TMP		"/boot/grub/menu.lst.tmp"
137 #define	RAMDISK_SPECIAL		"/ramdisk"
138 #define	STUBBOOT		"/stubboot"
139 
140 /* lock related */
141 #define	BAM_LOCK_FILE		"/var/run/bootadm.lock"
142 #define	LOCK_FILE_PERMS		(S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)
143 
144 #define	CREATE_RAMDISK		"/boot/solaris/bin/create_ramdisk"
145 #define	CREATE_DISKMAP		"/boot/solaris/bin/create_diskmap"
146 #define	GRUBDISK_MAP		"/var/run/solaris_grubdisk.map"
147 
148 #define	GRUB_slice		"/etc/lu/GRUB_slice"
149 #define	GRUB_root		"/etc/lu/GRUB_root"
150 #define	GRUB_backup_menu	"/etc/lu/GRUB_backup_menu"
151 #define	GRUB_slice_mntpt	"/tmp/GRUB_slice_mntpt"
152 #define	LU_ACTIVATE_FILE	"/etc/lu/DelayUpdate/activate.sh"
153 
154 #define	INSTALLGRUB		"/sbin/installgrub"
155 #define	STAGE1			"/boot/grub/stage1"
156 #define	STAGE2			"/boot/grub/stage2"
157 
158 /*
159  * Default file attributes
160  */
161 #define	DEFAULT_DEV_MODE	0644	/* default permissions */
162 #define	DEFAULT_DEV_UID		0	/* user root */
163 #define	DEFAULT_DEV_GID		3	/* group sys */
164 
165 /*
166  * Menu related
167  * menu_cmd_t and menu_cmds must be kept in sync
168  */
169 typedef enum {
170 	DEFAULT_CMD = 0,
171 	TIMEOUT_CMD,
172 	TITLE_CMD,
173 	ROOT_CMD,
174 	KERNEL_CMD,
175 	MODULE_CMD,
176 	SEP_CMD,
177 	COMMENT_CMD
178 } menu_cmd_t;
179 
180 static char *menu_cmds[] = {
181 	"default",	/* DEFAULT_CMD */
182 	"timeout",	/* TIMEOUT_CMD */
183 	"title",	/* TITLE_CMD */
184 	"root",		/* ROOT_CMD */
185 	"kernel",	/* KERNEL_CMD */
186 	"module",	/* MODULE_CMD */
187 	" ",		/* SEP_CMD */
188 	"#",		/* COMMENT_CMD */
189 	NULL
190 };
191 
192 #define	OPT_ENTRY_NUM	"entry"
193 
194 /*
195  * archive related
196  */
197 typedef struct {
198 	line_t *head;
199 	line_t *tail;
200 } filelist_t;
201 
202 #define	BOOT_FILE_LIST	"boot/solaris/filelist.ramdisk"
203 #define	ETC_FILE_LIST	"etc/boot/solaris/filelist.ramdisk"
204 
205 #define	FILE_STAT	"boot/solaris/filestat.ramdisk"
206 #define	FILE_STAT_TMP	"boot/solaris/filestat.ramdisk.tmp"
207 #define	DIR_PERMS	(S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH)
208 #define	FILE_STAT_MODE	(S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)
209 
210 #define	BAM_HDR		"---------- ADDED BY BOOTADM - DO NOT EDIT ----------"
211 #define	BAM_FTR		"---------------------END BOOTADM--------------------"
212 
213 
214 /* Globals */
215 static char *prog;
216 static subcmd_t bam_cmd;
217 static char *bam_root;
218 static int bam_rootlen;
219 static int bam_root_readonly;
220 static int bam_alt_root;
221 static char *bam_subcmd;
222 static char *bam_opt;
223 static int bam_debug;
224 static char **bam_argv;
225 static int bam_argc;
226 static int bam_force;
227 static int bam_verbose;
228 static int bam_check;
229 static int bam_smf_check;
230 static int bam_lock_fd = -1;
231 static char rootbuf[PATH_MAX] = "/";
232 static int bam_update_all;
233 
234 /* function prototypes */
235 static void parse_args_internal(int argc, char *argv[]);
236 static void parse_args(int argc, char *argv[]);
237 static error_t bam_menu(char *subcmd, char *opt, int argc, char *argv[]);
238 static error_t bam_archive(char *subcmd, char *opt);
239 
240 static void bam_error(char *format, ...);
241 static void bam_print(char *format, ...);
242 static void bam_exit(int excode);
243 static void bam_lock(void);
244 static void bam_unlock(void);
245 
246 static int exec_cmd(char *cmdline, char *output, int64_t osize);
247 static error_t read_globals(menu_t *mp, char *menu_path,
248     char *globalcmd, int quiet);
249 
250 static menu_t *menu_read(char *menu_path);
251 static error_t menu_write(char *root, menu_t *mp);
252 static void linelist_free(line_t *start);
253 static void menu_free(menu_t *mp);
254 static void line_free(line_t *lp);
255 static void filelist_free(filelist_t *flistp);
256 static error_t list2file(char *root, char *tmp,
257     char *final, line_t *start);
258 static error_t list_entry(menu_t *mp, char *menu_path, char *opt);
259 static error_t delete_entry(menu_t *mp, char *menu_path, char *opt);
260 static error_t delete_all_entries(menu_t *mp, char *menu_path, char *opt);
261 static error_t update_entry(menu_t *mp, char *root, char *opt);
262 static error_t update_temp(menu_t *mp, char *root, char *opt);
263 
264 static error_t update_archive(char *root, char *opt);
265 static error_t list_archive(char *root, char *opt);
266 static error_t update_all(char *root, char *opt);
267 static error_t read_list(char *root, filelist_t  *flistp);
268 static error_t set_global(menu_t *mp, char *globalcmd, int val);
269 static error_t set_option(menu_t *mp, char *globalcmd, char *opt);
270 
271 static long s_strtol(char *str);
272 static char *s_fgets(char *buf, int n, FILE *fp);
273 static int s_fputs(char *str, FILE *fp);
274 
275 static void *s_calloc(size_t nelem, size_t sz);
276 static char *s_strdup(char *str);
277 static int is_readonly(char *);
278 static int is_amd64(void);
279 static void append_to_flist(filelist_t *, char *);
280 
281 #if defined(__sparc)
282 static void sparc_abort(void);
283 #endif
284 
285 /* Menu related sub commands */
286 static subcmd_defn_t menu_subcmds[] = {
287 	"set_option",		OPT_OPTIONAL,	set_option,	/* PUB */
288 	"list_entry",		OPT_OPTIONAL,	list_entry,	/* PUB */
289 	"delete_all_entries",	OPT_ABSENT,	delete_all_entries, /* PVT */
290 	"update_entry",		OPT_REQ,	update_entry,	/* menu */
291 	"update_temp",		OPT_OPTIONAL,	update_temp,	/* reboot */
292 	NULL,			0,		NULL	/* must be last */
293 };
294 
295 /* Archive related sub commands */
296 static subcmd_defn_t arch_subcmds[] = {
297 	"update",		OPT_ABSENT,	update_archive, /* PUB */
298 	"update_all",		OPT_ABSENT,	update_all,	/* PVT */
299 	"list",			OPT_OPTIONAL,	list_archive,	/* PUB */
300 	NULL,			0,		NULL	/* must be last */
301 };
302 
303 static struct {
304 	nvlist_t *new_nvlp;
305 	nvlist_t *old_nvlp;
306 	int need_update;
307 } walk_arg;
308 
309 static void
310 usage(void)
311 {
312 	(void) fprintf(stderr, "USAGE:\n");
313 
314 
315 	/* archive usage */
316 	(void) fprintf(stderr, "\t%s update-archive [-vn] [-R altroot]\n",
317 	    prog);
318 	(void) fprintf(stderr, "\t%s list-archive [-R altroot]\n", prog);
319 #ifndef __sparc
320 	/* x86 only */
321 	(void) fprintf(stderr, "\t%s set-menu [-R altroot] key=value\n", prog);
322 	(void) fprintf(stderr, "\t%s list-menu [-R altroot]\n", prog);
323 #endif
324 }
325 
326 int
327 main(int argc, char *argv[])
328 {
329 	error_t ret;
330 
331 	(void) setlocale(LC_ALL, "");
332 	(void) textdomain(TEXT_DOMAIN);
333 
334 	if ((prog = strrchr(argv[0], '/')) == NULL) {
335 		prog = argv[0];
336 	} else {
337 		prog++;
338 	}
339 
340 	if (geteuid() != 0) {
341 		bam_error(MUST_BE_ROOT);
342 		bam_exit(1);
343 	}
344 
345 	bam_lock();
346 
347 	/*
348 	 * Don't depend on caller's umask
349 	 */
350 	(void) umask(0022);
351 
352 	parse_args(argc, argv);
353 
354 #if defined(__sparc)
355 	/*
356 	 * There are only two valid invocations of bootadm
357 	 * on SPARC:
358 	 *
359 	 *	- SPARC diskless server creating boot_archive for i386 clients
360 	 *	- archive creation call during reboot of a SPARC system
361 	 *
362 	 *	The latter should be a NOP
363 	 */
364 	if (bam_cmd != BAM_ARCHIVE) {
365 		sparc_abort();
366 	}
367 #endif
368 
369 	switch (bam_cmd) {
370 		case BAM_MENU:
371 			ret = bam_menu(bam_subcmd, bam_opt, bam_argc, bam_argv);
372 			break;
373 		case BAM_ARCHIVE:
374 			ret = bam_archive(bam_subcmd, bam_opt);
375 			break;
376 		default:
377 			usage();
378 			bam_exit(1);
379 	}
380 
381 	if (ret != BAM_SUCCESS)
382 		bam_exit(1);
383 
384 	bam_unlock();
385 	return (0);
386 }
387 
388 #if defined(__sparc)
389 
390 static void
391 sparc_abort(void)
392 {
393 	bam_error(NOT_ON_SPARC);
394 	bam_exit(1);
395 }
396 
397 #endif
398 
399 /*
400  * Equivalence of public and internal commands:
401  *	update-archive  -- -a update
402  *	list-archive	-- -a list
403  *	set-menu	-- -m set_option
404  *	list-menu	-- -m list_entry
405  *	update-menu	-- -m update_entry
406  */
407 static struct cmd_map {
408 	char *bam_cmdname;
409 	int bam_cmd;
410 	char *bam_subcmd;
411 } cmd_map[] = {
412 	{ "update-archive",	BAM_ARCHIVE,	"update"},
413 	{ "list-archive",	BAM_ARCHIVE,	"list"},
414 	{ "set-menu",		BAM_MENU,	"set_option"},
415 	{ "list-menu",		BAM_MENU,	"list_entry"},
416 	{ "update-menu",	BAM_MENU,	"update_entry"},
417 	{ NULL,			0,		NULL}
418 };
419 
420 /*
421  * Commands syntax published in bootadm(1M) are parsed here
422  */
423 static void
424 parse_args(int argc, char *argv[])
425 {
426 	struct cmd_map *cmp = cmd_map;
427 
428 	/* command conforming to the final spec */
429 	if (argc > 1 && argv[1][0] != '-') {
430 		/*
431 		 * Map commands to internal table.
432 		 */
433 		while (cmp->bam_cmdname) {
434 			if (strcmp(argv[1], cmp->bam_cmdname) == 0) {
435 				bam_cmd = cmp->bam_cmd;
436 				bam_subcmd = cmp->bam_subcmd;
437 				break;
438 			}
439 			cmp++;
440 		}
441 		if (cmp->bam_cmdname == NULL) {
442 			usage();
443 			bam_exit(1);
444 		}
445 		argc--;
446 		argv++;
447 	}
448 
449 	parse_args_internal(argc, argv);
450 }
451 
452 /*
453  * A combination of public and private commands are parsed here.
454  * The internal syntax and the corresponding functionality are:
455  *	-a update	-- update-archive
456  *	-a list		-- list-archive
457  *	-a update-all	-- (reboot to sync all mounted OS archive)
458  *	-m update_entry	-- update-menu
459  *	-m list_entry	-- list-menu
460  *	-m update_temp	-- (reboot -- [boot-args])
461  *	-m delete_all_entries -- (called from install)
462  */
463 static void
464 parse_args_internal(int argc, char *argv[])
465 {
466 	int c, error;
467 	extern char *optarg;
468 	extern int optind, opterr;
469 
470 	/* Suppress error message from getopt */
471 	opterr = 0;
472 
473 	error = 0;
474 	while ((c = getopt(argc, argv, "a:d:fm:no:vR:")) != -1) {
475 		switch (c) {
476 		case 'a':
477 			if (bam_cmd) {
478 				error = 1;
479 				bam_error(MULT_CMDS, c);
480 			}
481 			bam_cmd = BAM_ARCHIVE;
482 			bam_subcmd = optarg;
483 			break;
484 		case 'd':
485 			if (bam_debug) {
486 				error = 1;
487 				bam_error(DUP_OPT, c);
488 			}
489 			bam_debug = s_strtol(optarg);
490 			break;
491 		case 'f':
492 			if (bam_force) {
493 				error = 1;
494 				bam_error(DUP_OPT, c);
495 			}
496 			bam_force = 1;
497 			break;
498 		case 'm':
499 			if (bam_cmd) {
500 				error = 1;
501 				bam_error(MULT_CMDS, c);
502 			}
503 			bam_cmd = BAM_MENU;
504 			bam_subcmd = optarg;
505 			break;
506 		case 'n':
507 			if (bam_check) {
508 				error = 1;
509 				bam_error(DUP_OPT, c);
510 			}
511 			bam_check = 1;
512 			bam_smf_check = bam_root_readonly;
513 			break;
514 		case 'o':
515 			if (bam_opt) {
516 				error = 1;
517 				bam_error(DUP_OPT, c);
518 			}
519 			bam_opt = optarg;
520 			break;
521 		case 'v':
522 			if (bam_verbose) {
523 				error = 1;
524 				bam_error(DUP_OPT, c);
525 			}
526 			bam_verbose = 1;
527 			break;
528 		case 'R':
529 			if (bam_root) {
530 				error = 1;
531 				bam_error(DUP_OPT, c);
532 				break;
533 			} else if (realpath(optarg, rootbuf) == NULL) {
534 				error = 1;
535 				bam_error(CANT_RESOLVE, optarg,
536 				    strerror(errno));
537 				break;
538 			}
539 			bam_alt_root = 1;
540 			bam_root = rootbuf;
541 			if (rootbuf[strlen(rootbuf) - 1] != '/')
542 				(void) strcat(rootbuf, "/");
543 			bam_rootlen = strlen(rootbuf);
544 			break;
545 		case '?':
546 			error = 1;
547 			bam_error(BAD_OPT, optopt);
548 			break;
549 		default :
550 			error = 1;
551 			bam_error(BAD_OPT, c);
552 			break;
553 		}
554 	}
555 
556 	/*
557 	 * A command option must be specfied
558 	 */
559 	if (!bam_cmd) {
560 		if (bam_opt && strcmp(bam_opt, "all") == 0) {
561 			usage();
562 			bam_exit(0);
563 		}
564 		bam_error(NEED_CMD);
565 		error = 1;
566 	}
567 
568 	if (error) {
569 		usage();
570 		bam_exit(1);
571 	}
572 
573 	if (optind > argc) {
574 		bam_error(INT_ERROR, "parse_args");
575 		bam_exit(1);
576 	} else if (optind < argc) {
577 		bam_argv = &argv[optind];
578 		bam_argc = argc - optind;
579 	}
580 
581 	/*
582 	 * -n implies verbose mode
583 	 */
584 	if (bam_check)
585 		bam_verbose = 1;
586 }
587 
588 static error_t
589 check_subcmd_and_options(
590 	char *subcmd,
591 	char *opt,
592 	subcmd_defn_t *table,
593 	error_t (**fp)())
594 {
595 	int i;
596 
597 	if (subcmd == NULL) {
598 		bam_error(NEED_SUBCMD);
599 		return (BAM_ERROR);
600 	}
601 
602 	if (bam_root == NULL) {
603 		bam_root = rootbuf;
604 		bam_rootlen = 1;
605 	}
606 
607 	/* verify that subcmd is valid */
608 	for (i = 0; table[i].subcmd != NULL; i++) {
609 		if (strcmp(table[i].subcmd, subcmd) == 0)
610 			break;
611 	}
612 
613 	if (table[i].subcmd == NULL) {
614 		bam_error(INVALID_SUBCMD, subcmd);
615 		return (BAM_ERROR);
616 	}
617 
618 	/* subcmd verifies that opt is appropriate */
619 	if (table[i].option != OPT_OPTIONAL) {
620 		if ((table[i].option == OPT_REQ) ^ (opt != NULL)) {
621 			if (opt)
622 				bam_error(NO_OPT_REQ, subcmd);
623 			else
624 				bam_error(MISS_OPT, subcmd);
625 			return (BAM_ERROR);
626 		}
627 	}
628 
629 	*fp = table[i].handler;
630 
631 	return (BAM_SUCCESS);
632 }
633 
634 
635 static char *
636 mount_grub_slice(int *mnted, char **physlice, char **logslice, char **fs_type)
637 {
638 	struct extmnttab mnt;
639 	struct stat sb;
640 	char buf[BAM_MAXLINE], dev[PATH_MAX], phys[PATH_MAX], fstype[32];
641 	char cmd[PATH_MAX];
642 	char *mntpt;
643 	int p, l, f;
644 	FILE *fp;
645 
646 	assert(mnted);
647 	*mnted = 0;
648 
649 	/*
650 	 * physlice, logslice, fs_type  args may be NULL
651 	 */
652 	if (physlice)
653 		*physlice = NULL;
654 	if (logslice)
655 		*logslice = NULL;
656 	if (fs_type)
657 		*fs_type = NULL;
658 
659 	if (stat(GRUB_slice, &sb) != 0) {
660 		bam_error(MISSING_SLICE_FILE, GRUB_slice, strerror(errno));
661 		return (NULL);
662 	}
663 
664 	fp = fopen(GRUB_slice, "r");
665 	if (fp == NULL) {
666 		bam_error(OPEN_FAIL, GRUB_slice, strerror(errno));
667 		return (NULL);
668 	}
669 
670 	dev[0] = fstype[0] = phys[0] = '\0';
671 	p = sizeof ("PHYS_SLICE=") - 1;
672 	l = sizeof ("LOG_SLICE=") - 1;
673 	f = sizeof ("LOG_FSTYP=") - 1;
674 	while (s_fgets(buf, sizeof (buf), fp) != NULL) {
675 		if (strncmp(buf, "PHYS_SLICE=", p) == 0) {
676 			(void) strlcpy(phys, buf + p, sizeof (phys));
677 			continue;
678 		}
679 		if (strncmp(buf, "LOG_SLICE=", l) == 0) {
680 			(void) strlcpy(dev, buf + l, sizeof (dev));
681 			continue;
682 		}
683 		if (strncmp(buf, "LOG_FSTYP=", f) == 0) {
684 			(void) strlcpy(fstype, buf + f, sizeof (fstype));
685 			continue;
686 		}
687 	}
688 	(void) fclose(fp);
689 
690 	if (dev[0] == '\0' || fstype[0] == '\0' || phys[0] == '\0') {
691 		bam_error(BAD_SLICE_FILE, GRUB_slice);
692 		return (NULL);
693 	}
694 
695 	if (physlice) {
696 		*physlice = s_strdup(phys);
697 	}
698 	if (logslice) {
699 		*logslice = s_strdup(dev);
700 	}
701 	if (fs_type) {
702 		*fs_type = s_strdup(fstype);
703 	}
704 
705 	/*
706 	 * Check if the slice is already mounted
707 	 */
708 	fp = fopen(MNTTAB, "r");
709 	if (fp == NULL) {
710 		bam_error(OPEN_FAIL, MNTTAB, strerror(errno));
711 		goto error;
712 	}
713 
714 	resetmnttab(fp);
715 
716 	mntpt = NULL;
717 	while (getextmntent(fp, &mnt, sizeof (mnt)) == 0) {
718 		if (strcmp(mnt.mnt_special, dev) == 0) {
719 			mntpt = s_strdup(mnt.mnt_mountp);
720 			break;
721 		}
722 	}
723 
724 	(void) fclose(fp);
725 
726 	if (mntpt) {
727 		return (mntpt);
728 	}
729 
730 
731 	/*
732 	 * GRUB slice is not mounted, we need to mount it now.
733 	 * First create the mountpoint
734 	 */
735 	mntpt = s_calloc(1, PATH_MAX);
736 	(void) snprintf(mntpt, PATH_MAX, "%s.%d", GRUB_slice_mntpt, getpid());
737 	if (mkdir(mntpt, 0755) == -1 && errno != EEXIST) {
738 		bam_error(MKDIR_FAILED, mntpt, strerror(errno));
739 		free(mntpt);
740 		goto error;
741 	}
742 
743 	(void) snprintf(cmd, sizeof (cmd), "/sbin/mount -F %s %s %s",
744 	    fstype, dev, mntpt);
745 
746 	if (exec_cmd(cmd, NULL, 0) != 0) {
747 		bam_error(MOUNT_FAILED, dev, fstype);
748 		if (rmdir(mntpt) != 0) {
749 			bam_error(RMDIR_FAILED, mntpt, strerror(errno));
750 		}
751 		free(mntpt);
752 		goto error;
753 	}
754 
755 	*mnted = 1;
756 	return (mntpt);
757 
758 error:
759 	if (physlice) {
760 		free(*physlice);
761 		*physlice = NULL;
762 	}
763 	if (logslice) {
764 		free(*logslice);
765 		*logslice = NULL;
766 	}
767 	if (fs_type) {
768 		free(*fs_type);
769 		*fs_type = NULL;
770 	}
771 	return (NULL);
772 }
773 
774 static void
775 umount_grub_slice(
776 	int mnted,
777 	char *mntpt,
778 	char *physlice,
779 	char *logslice,
780 	char *fs_type)
781 {
782 	char cmd[PATH_MAX];
783 
784 	/*
785 	 * If we have not dealt with GRUB slice
786 	 * we have nothing to do - just return.
787 	 */
788 	if (mntpt == NULL)
789 		return;
790 
791 
792 	/*
793 	 * If we mounted the filesystem earlier in mount_grub_slice()
794 	 * unmount it now.
795 	 */
796 	if (mnted) {
797 		(void) snprintf(cmd, sizeof (cmd), "/sbin/umount %s",
798 		    mntpt);
799 		if (exec_cmd(cmd, NULL, 0) != 0) {
800 			bam_error(UMOUNT_FAILED, mntpt);
801 		}
802 		if (rmdir(mntpt) != 0) {
803 			bam_error(RMDIR_FAILED, mntpt, strerror(errno));
804 		}
805 	}
806 
807 	if (physlice)
808 		free(physlice);
809 	if (logslice)
810 		free(logslice);
811 	if (fs_type)
812 		free(fs_type);
813 
814 	free(mntpt);
815 }
816 
817 static char *
818 use_stubboot(void)
819 {
820 	int mnted;
821 	struct stat sb;
822 	struct extmnttab mnt;
823 	FILE *fp;
824 	char cmd[PATH_MAX];
825 
826 	if (stat(STUBBOOT, &sb) != 0) {
827 		bam_error(STUBBOOT_DIR_NOT_FOUND);
828 		return (NULL);
829 	}
830 
831 	/*
832 	 * Check if stubboot is mounted. If not, mount it
833 	 */
834 	fp = fopen(MNTTAB, "r");
835 	if (fp == NULL) {
836 		bam_error(OPEN_FAIL, MNTTAB, strerror(errno));
837 		return (NULL);
838 	}
839 
840 	resetmnttab(fp);
841 
842 	mnted = 0;
843 	while (getextmntent(fp, &mnt, sizeof (mnt)) == 0) {
844 		if (strcmp(mnt.mnt_mountp, STUBBOOT) == 0) {
845 			mnted = 1;
846 			break;
847 		}
848 	}
849 
850 	(void) fclose(fp);
851 
852 	if (mnted)
853 		return (STUBBOOT);
854 
855 	/*
856 	 * Stubboot is not mounted, mount it now.
857 	 * It should exist in /etc/vfstab
858 	 */
859 	(void) snprintf(cmd, sizeof (cmd), "/sbin/mount %s",
860 	    STUBBOOT);
861 	if (exec_cmd(cmd, NULL, 0) != 0) {
862 		bam_error(MOUNT_MNTPT_FAILED, STUBBOOT);
863 		return (NULL);
864 	}
865 
866 	return (STUBBOOT);
867 }
868 
869 static void
870 disp_active_menu_locn(char *menu_path, char *logslice, char *fstype, int mnted)
871 {
872 	/*
873 	 * Check if we did a temp mount of an unmounted device.
874 	 * If yes, print the block device and fstype for that device
875 	 * else it is already mounted, so we print the path to the GRUB menu.
876 	 */
877 	if (mnted) {
878 		bam_print(GRUB_MENU_DEVICE, logslice);
879 		bam_print(GRUB_MENU_FSTYPE, fstype);
880 	} else {
881 		bam_print(GRUB_MENU_PATH, menu_path);
882 	}
883 }
884 
885 /*
886  * NOTE: A single "/" is also considered a trailing slash and will
887  * be deleted.
888  */
889 static void
890 elide_trailing_slash(const char *src, char *dst, size_t dstsize)
891 {
892 	size_t dstlen;
893 
894 	assert(src);
895 	assert(dst);
896 
897 	(void) strlcpy(dst, src, dstsize);
898 
899 	dstlen = strlen(dst);
900 	if (dst[dstlen - 1] == '/') {
901 		dst[dstlen - 1] = '\0';
902 	}
903 }
904 
905 static error_t
906 bam_menu(char *subcmd, char *opt, int largc, char *largv[])
907 {
908 	error_t ret;
909 	char menu_path[PATH_MAX];
910 	menu_t *menu;
911 	char *mntpt, *menu_root, *logslice, *fstype;
912 	struct stat sb;
913 	int mnted;	/* set if we did a mount */
914 	error_t (*f)(menu_t *mp, char *menu_path, char *opt);
915 
916 	/*
917 	 * Check arguments
918 	 */
919 	ret = check_subcmd_and_options(subcmd, opt, menu_subcmds, &f);
920 	if (ret == BAM_ERROR) {
921 		return (BAM_ERROR);
922 	}
923 
924 	mntpt = NULL;
925 	mnted = 0;
926 	logslice = fstype = NULL;
927 
928 	/*
929 	 * If the user provides an alternate root, we
930 	 * assume they know what they are doing and we
931 	 * use it. Else we check if there is an
932 	 * alternate location (other than /boot/grub)
933 	 * for the GRUB menu
934 	 */
935 	if (bam_alt_root) {
936 		menu_root = bam_root;
937 	} else if (stat(GRUB_slice, &sb) == 0) {
938 		mntpt = mount_grub_slice(&mnted, NULL, &logslice, &fstype);
939 		menu_root = mntpt;
940 	} else if (stat(STUBBOOT, &sb) == 0) {
941 		menu_root = use_stubboot();
942 	} else {
943 		menu_root = bam_root;
944 	}
945 
946 	if (menu_root == NULL) {
947 		bam_error(CANNOT_LOCATE_GRUB_MENU);
948 		return (BAM_ERROR);
949 	}
950 
951 	elide_trailing_slash(menu_root, menu_path, sizeof (menu_path));
952 	(void) strlcat(menu_path, GRUB_MENU, sizeof (menu_path));
953 
954 	/*
955 	 * If listing the menu, display the active menu
956 	 * location
957 	 */
958 	if (strcmp(subcmd, "list_entry") == 0) {
959 		disp_active_menu_locn(menu_path, logslice, fstype, mnted);
960 	}
961 
962 	menu = menu_read(menu_path);
963 	assert(menu);
964 
965 	/*
966 	 * Special handling for setting timeout and default
967 	 */
968 	if (strcmp(subcmd, "set_option") == 0) {
969 		if (largc != 1 || largv[0] == NULL) {
970 			usage();
971 			menu_free(menu);
972 			umount_grub_slice(mnted, mntpt, NULL, logslice, fstype);
973 			return (BAM_ERROR);
974 		}
975 		opt = largv[0];
976 	} else if (largc != 0) {
977 		usage();
978 		menu_free(menu);
979 		umount_grub_slice(mnted, mntpt, NULL, logslice, fstype);
980 		return (BAM_ERROR);
981 	}
982 
983 	/*
984 	 * Once the sub-cmd handler has run
985 	 * only the line field is guaranteed to have valid values
986 	 */
987 	if (strcmp(subcmd, "update_entry") == 0)
988 		ret = f(menu, bam_root, opt);
989 	else
990 		ret = f(menu, menu_path, opt);
991 	if (ret == BAM_WRITE) {
992 		ret = menu_write(menu_root, menu);
993 	}
994 
995 	menu_free(menu);
996 
997 	umount_grub_slice(mnted, mntpt, NULL, logslice, fstype);
998 
999 	return (ret);
1000 }
1001 
1002 
1003 static error_t
1004 bam_archive(
1005 	char *subcmd,
1006 	char *opt)
1007 {
1008 	error_t ret;
1009 	error_t (*f)(char *root, char *opt);
1010 
1011 	/*
1012 	 * Check arguments
1013 	 */
1014 	ret = check_subcmd_and_options(subcmd, opt, arch_subcmds, &f);
1015 	if (ret != BAM_SUCCESS) {
1016 		return (BAM_ERROR);
1017 	}
1018 
1019 #if defined(__sparc)
1020 	/*
1021 	 * A NOP if called on SPARC during reboot
1022 	 */
1023 	if (strcmp(subcmd, "update_all") == 0)
1024 		return (BAM_SUCCESS);
1025 	else if (strcmp(subcmd, "update") != 0)
1026 		sparc_abort();
1027 #endif
1028 
1029 	/*
1030 	 * Check archive not supported with update_all
1031 	 * since it is awkward to display out-of-sync
1032 	 * information for each BE.
1033 	 */
1034 	if (bam_check && strcmp(subcmd, "update_all") == 0) {
1035 		bam_error(CHECK_NOT_SUPPORTED, subcmd);
1036 		return (BAM_ERROR);
1037 	}
1038 
1039 	if (strcmp(subcmd, "update_all") == 0)
1040 		bam_update_all = 1;
1041 
1042 	ret = f(bam_root, opt);
1043 
1044 	bam_update_all = 0;
1045 
1046 	return (ret);
1047 }
1048 
1049 /*PRINTFLIKE1*/
1050 static void
1051 bam_error(char *format, ...)
1052 {
1053 	va_list ap;
1054 
1055 	va_start(ap, format);
1056 	(void) fprintf(stderr, "%s: ", prog);
1057 	(void) vfprintf(stderr, format, ap);
1058 	va_end(ap);
1059 }
1060 
1061 /*PRINTFLIKE1*/
1062 static void
1063 bam_print(char *format, ...)
1064 {
1065 	va_list ap;
1066 
1067 	va_start(ap, format);
1068 	(void) vfprintf(stdout, format, ap);
1069 	va_end(ap);
1070 }
1071 
1072 static void
1073 bam_exit(int excode)
1074 {
1075 	bam_unlock();
1076 	exit(excode);
1077 }
1078 
1079 static void
1080 bam_lock(void)
1081 {
1082 	struct flock lock;
1083 	pid_t pid;
1084 
1085 	bam_lock_fd = open(BAM_LOCK_FILE, O_CREAT|O_RDWR, LOCK_FILE_PERMS);
1086 	if (bam_lock_fd < 0) {
1087 		/*
1088 		 * We may be invoked early in boot for archive verification.
1089 		 * In this case, root is readonly and /var/run may not exist.
1090 		 * Proceed without the lock
1091 		 */
1092 		if (errno == EROFS || errno == ENOENT) {
1093 			bam_root_readonly = 1;
1094 			return;
1095 		}
1096 
1097 		bam_error(OPEN_FAIL, BAM_LOCK_FILE, strerror(errno));
1098 		bam_exit(1);
1099 	}
1100 
1101 	lock.l_type = F_WRLCK;
1102 	lock.l_whence = SEEK_SET;
1103 	lock.l_start = 0;
1104 	lock.l_len = 0;
1105 
1106 	if (fcntl(bam_lock_fd, F_SETLK, &lock) == -1) {
1107 		if (errno != EACCES && errno != EAGAIN) {
1108 			bam_error(LOCK_FAIL, BAM_LOCK_FILE, strerror(errno));
1109 			(void) close(bam_lock_fd);
1110 			bam_lock_fd = -1;
1111 			bam_exit(1);
1112 		}
1113 		pid = 0;
1114 		(void) pread(bam_lock_fd, &pid, sizeof (pid_t), 0);
1115 		bam_print(FILE_LOCKED, pid);
1116 
1117 		lock.l_type = F_WRLCK;
1118 		lock.l_whence = SEEK_SET;
1119 		lock.l_start = 0;
1120 		lock.l_len = 0;
1121 		if (fcntl(bam_lock_fd, F_SETLKW, &lock) == -1) {
1122 			bam_error(LOCK_FAIL, BAM_LOCK_FILE, strerror(errno));
1123 			(void) close(bam_lock_fd);
1124 			bam_lock_fd = -1;
1125 			bam_exit(1);
1126 		}
1127 	}
1128 
1129 	/* We own the lock now */
1130 	pid = getpid();
1131 	(void) write(bam_lock_fd, &pid, sizeof (pid));
1132 }
1133 
1134 static void
1135 bam_unlock(void)
1136 {
1137 	struct flock unlock;
1138 
1139 	/*
1140 	 * NOP if we don't hold the lock
1141 	 */
1142 	if (bam_lock_fd < 0) {
1143 		return;
1144 	}
1145 
1146 	unlock.l_type = F_UNLCK;
1147 	unlock.l_whence = SEEK_SET;
1148 	unlock.l_start = 0;
1149 	unlock.l_len = 0;
1150 
1151 	if (fcntl(bam_lock_fd, F_SETLK, &unlock) == -1) {
1152 		bam_error(UNLOCK_FAIL, BAM_LOCK_FILE, strerror(errno));
1153 	}
1154 
1155 	if (close(bam_lock_fd) == -1) {
1156 		bam_error(CLOSE_FAIL, BAM_LOCK_FILE, strerror(errno));
1157 	}
1158 	bam_lock_fd = -1;
1159 }
1160 
1161 static error_t
1162 list_archive(char *root, char *opt)
1163 {
1164 	filelist_t flist;
1165 	filelist_t *flistp = &flist;
1166 	line_t *lp;
1167 
1168 	assert(root);
1169 	assert(opt == NULL);
1170 
1171 	flistp->head = flistp->tail = NULL;
1172 	if (read_list(root, flistp) != BAM_SUCCESS) {
1173 		return (BAM_ERROR);
1174 	}
1175 	assert(flistp->head && flistp->tail);
1176 
1177 	for (lp = flistp->head; lp; lp = lp->next) {
1178 		bam_print(PRINT, lp->line);
1179 	}
1180 
1181 	filelist_free(flistp);
1182 
1183 	return (BAM_SUCCESS);
1184 }
1185 
1186 /*
1187  * This routine writes a list of lines to a file.
1188  * The list is *not* freed
1189  */
1190 static error_t
1191 list2file(char *root, char *tmp, char *final, line_t *start)
1192 {
1193 	char tmpfile[PATH_MAX];
1194 	char path[PATH_MAX];
1195 	FILE *fp;
1196 	int ret;
1197 	struct stat sb;
1198 	mode_t mode;
1199 	uid_t root_uid;
1200 	gid_t sys_gid;
1201 	struct passwd *pw;
1202 	struct group *gp;
1203 
1204 
1205 	(void) snprintf(path, sizeof (path), "%s%s", root, final);
1206 
1207 	if (start == NULL) {
1208 		if (stat(path, &sb) != -1) {
1209 			bam_print(UNLINK_EMPTY, path);
1210 			if (unlink(path) != 0) {
1211 				bam_error(UNLINK_FAIL, path, strerror(errno));
1212 				return (BAM_ERROR);
1213 			} else {
1214 				return (BAM_SUCCESS);
1215 			}
1216 		}
1217 	}
1218 
1219 	/*
1220 	 * Preserve attributes of existing file if possible,
1221 	 * otherwise ask the system for uid/gid of root/sys.
1222 	 * If all fails, fall back on hard-coded defaults.
1223 	 */
1224 	if (stat(path, &sb) != -1) {
1225 		mode = sb.st_mode;
1226 		root_uid = sb.st_uid;
1227 		sys_gid = sb.st_gid;
1228 	} else {
1229 		mode = DEFAULT_DEV_MODE;
1230 		if ((pw = getpwnam(DEFAULT_DEV_USER)) != NULL) {
1231 			root_uid = pw->pw_uid;
1232 		} else {
1233 			if (bam_verbose)
1234 				bam_error(CANT_FIND_USER,
1235 				    DEFAULT_DEV_USER, DEFAULT_DEV_UID);
1236 			root_uid = (uid_t)DEFAULT_DEV_UID;
1237 		}
1238 		if ((gp = getgrnam(DEFAULT_DEV_GROUP)) != NULL) {
1239 			sys_gid = gp->gr_gid;
1240 		} else {
1241 			if (bam_verbose)
1242 				bam_error(CANT_FIND_GROUP,
1243 				    DEFAULT_DEV_GROUP, DEFAULT_DEV_GID);
1244 			sys_gid = (gid_t)DEFAULT_DEV_GID;
1245 		}
1246 	}
1247 
1248 	(void) snprintf(tmpfile, sizeof (tmpfile), "%s%s", root, tmp);
1249 
1250 	/* Truncate tmpfile first */
1251 	fp = fopen(tmpfile, "w");
1252 	if (fp == NULL) {
1253 		bam_error(OPEN_FAIL, tmpfile, strerror(errno));
1254 		return (BAM_ERROR);
1255 	}
1256 	ret = fclose(fp);
1257 	if (ret == EOF) {
1258 		bam_error(CLOSE_FAIL, tmpfile, strerror(errno));
1259 		return (BAM_ERROR);
1260 	}
1261 
1262 	/* Now open it in append mode */
1263 	fp = fopen(tmpfile, "a");
1264 	if (fp == NULL) {
1265 		bam_error(OPEN_FAIL, tmpfile, strerror(errno));
1266 		return (BAM_ERROR);
1267 	}
1268 
1269 	for (; start; start = start->next) {
1270 		ret = s_fputs(start->line, fp);
1271 		if (ret == EOF) {
1272 			bam_error(WRITE_FAIL, tmpfile, strerror(errno));
1273 			(void) fclose(fp);
1274 			return (BAM_ERROR);
1275 		}
1276 	}
1277 
1278 	ret = fclose(fp);
1279 	if (ret == EOF) {
1280 		bam_error(CLOSE_FAIL, tmpfile, strerror(errno));
1281 		return (BAM_ERROR);
1282 	}
1283 
1284 	/*
1285 	 * Set up desired attributes.  Ignore failures on filesystems
1286 	 * not supporting these operations - pcfs reports unsupported
1287 	 * operations as EINVAL.
1288 	 */
1289 	ret = chmod(tmpfile, mode);
1290 	if (ret == -1 &&
1291 	    errno != EINVAL && errno != ENOTSUP) {
1292 		bam_error(CHMOD_FAIL, tmpfile, strerror(errno));
1293 		return (BAM_ERROR);
1294 	}
1295 
1296 	ret = chown(tmpfile, root_uid, sys_gid);
1297 	if (ret == -1 &&
1298 	    errno != EINVAL && errno != ENOTSUP) {
1299 		bam_error(CHOWN_FAIL, tmpfile, strerror(errno));
1300 		return (BAM_ERROR);
1301 	}
1302 
1303 
1304 	/*
1305 	 * Do an atomic rename
1306 	 */
1307 	ret = rename(tmpfile, path);
1308 	if (ret != 0) {
1309 		bam_error(RENAME_FAIL, path, strerror(errno));
1310 		return (BAM_ERROR);
1311 	}
1312 
1313 	return (BAM_SUCCESS);
1314 }
1315 
1316 /*
1317  * This function should always return 0 - since we want
1318  * to create stat data for *all* files in the list.
1319  */
1320 /*ARGSUSED*/
1321 static int
1322 cmpstat(
1323 	const char *file,
1324 	const struct stat *stat,
1325 	int flags,
1326 	struct FTW *ftw)
1327 {
1328 	uint_t sz;
1329 	uint64_t *value;
1330 	uint64_t filestat[2];
1331 	int error;
1332 
1333 	/*
1334 	 * We only want regular files
1335 	 */
1336 	if (!S_ISREG(stat->st_mode))
1337 		return (0);
1338 
1339 	/*
1340 	 * new_nvlp may be NULL if there were errors earlier
1341 	 * but this is not fatal to update determination.
1342 	 */
1343 	if (walk_arg.new_nvlp) {
1344 		filestat[0] = stat->st_size;
1345 		filestat[1] = stat->st_mtime;
1346 		error = nvlist_add_uint64_array(walk_arg.new_nvlp,
1347 		    file + bam_rootlen, filestat, 2);
1348 		if (error)
1349 			bam_error(NVADD_FAIL, file, strerror(error));
1350 	}
1351 
1352 	/*
1353 	 * The remaining steps are only required if we haven't made a
1354 	 * decision about update or if we are checking (-n)
1355 	 */
1356 	if (walk_arg.need_update && !bam_check)
1357 		return (0);
1358 
1359 	/*
1360 	 * If we are invoked as part of system/filesyste/boot-archive
1361 	 * SMF service, ignore amd64 modules unless we are booted amd64.
1362 	 */
1363 	if (bam_smf_check && !is_amd64() && strstr(file, "/amd64/") == 0)
1364 		return (0);
1365 
1366 	/*
1367 	 * We need an update if file doesn't exist in old archive
1368 	 */
1369 	if (walk_arg.old_nvlp == NULL ||
1370 	    nvlist_lookup_uint64_array(walk_arg.old_nvlp,
1371 	    file + bam_rootlen, &value, &sz) != 0) {
1372 		if (bam_smf_check)	/* ignore new during smf check */
1373 			return (0);
1374 		walk_arg.need_update = 1;
1375 		if (bam_verbose)
1376 			bam_print(PARSEABLE_NEW_FILE, file);
1377 		return (0);
1378 	}
1379 
1380 	/*
1381 	 * File exists in old archive. Check if file has changed
1382 	 */
1383 	assert(sz == 2);
1384 	bcopy(value, filestat, sizeof (filestat));
1385 
1386 	if (filestat[0] != stat->st_size ||
1387 	    filestat[1] != stat->st_mtime) {
1388 		walk_arg.need_update = 1;
1389 		if (bam_verbose)
1390 			if (bam_smf_check)
1391 				bam_print("    %s\n", file);
1392 			else
1393 				bam_print(PARSEABLE_OUT_DATE, file);
1394 	}
1395 
1396 	return (0);
1397 }
1398 
1399 /*
1400  * Check flags and presence of required files.
1401  * The force flag and/or absence of files should
1402  * trigger an update.
1403  * Suppress stdout output if check (-n) option is set
1404  * (as -n should only produce parseable output.)
1405  */
1406 static void
1407 check_flags_and_files(char *root)
1408 {
1409 	char path[PATH_MAX];
1410 	struct stat sb;
1411 
1412 	/*
1413 	 * if force, create archive unconditionally
1414 	 */
1415 	if (bam_force) {
1416 		walk_arg.need_update = 1;
1417 		if (bam_verbose && !bam_check)
1418 			bam_print(UPDATE_FORCE);
1419 		return;
1420 	}
1421 
1422 	/*
1423 	 * If archive is missing, create archive
1424 	 */
1425 	(void) snprintf(path, sizeof (path), "%s%s", root, BOOT_ARCHIVE);
1426 	if (stat(path, &sb) != 0) {
1427 		if (bam_verbose && !bam_check)
1428 			bam_print(UPDATE_ARCH_MISS, path);
1429 		walk_arg.need_update = 1;
1430 		return;
1431 	}
1432 }
1433 
1434 static error_t
1435 read_one_list(char *root, filelist_t  *flistp, char *filelist)
1436 {
1437 	char path[PATH_MAX];
1438 	FILE *fp;
1439 	char buf[BAM_MAXLINE];
1440 
1441 	(void) snprintf(path, sizeof (path), "%s%s", root, filelist);
1442 
1443 	fp = fopen(path, "r");
1444 	if (fp == NULL) {
1445 		if (bam_debug)
1446 			bam_error(FLIST_FAIL, path, strerror(errno));
1447 		return (BAM_ERROR);
1448 	}
1449 	while (s_fgets(buf, sizeof (buf), fp) != NULL) {
1450 		/* skip blank lines */
1451 		if (strspn(buf, " \t") == strlen(buf))
1452 			continue;
1453 		append_to_flist(flistp, buf);
1454 	}
1455 	if (fclose(fp) != 0) {
1456 		bam_error(CLOSE_FAIL, path, strerror(errno));
1457 		return (BAM_ERROR);
1458 	}
1459 	return (BAM_SUCCESS);
1460 }
1461 
1462 static error_t
1463 read_list(char *root, filelist_t  *flistp)
1464 {
1465 	int rval;
1466 
1467 	flistp->head = flistp->tail = NULL;
1468 
1469 	/*
1470 	 * Read current lists of files - only the first is mandatory
1471 	 */
1472 	rval = read_one_list(root, flistp, BOOT_FILE_LIST);
1473 	if (rval != BAM_SUCCESS)
1474 		return (rval);
1475 	(void) read_one_list(root, flistp, ETC_FILE_LIST);
1476 
1477 	if (flistp->head == NULL) {
1478 		bam_error(NO_FLIST);
1479 		return (BAM_ERROR);
1480 	}
1481 
1482 	return (BAM_SUCCESS);
1483 }
1484 
1485 static void
1486 getoldstat(char *root)
1487 {
1488 	char path[PATH_MAX];
1489 	int fd, error;
1490 	struct stat sb;
1491 	char *ostat;
1492 
1493 	(void) snprintf(path, sizeof (path), "%s%s", root, FILE_STAT);
1494 	fd = open(path, O_RDONLY);
1495 	if (fd == -1) {
1496 		if (bam_verbose)
1497 			bam_print(OPEN_FAIL, path, strerror(errno));
1498 		walk_arg.need_update = 1;
1499 		return;
1500 	}
1501 
1502 	if (fstat(fd, &sb) != 0) {
1503 		bam_error(STAT_FAIL, path, strerror(errno));
1504 		(void) close(fd);
1505 		walk_arg.need_update = 1;
1506 		return;
1507 	}
1508 
1509 	ostat = s_calloc(1, sb.st_size);
1510 
1511 	if (read(fd, ostat, sb.st_size) != sb.st_size) {
1512 		bam_error(READ_FAIL, path, strerror(errno));
1513 		(void) close(fd);
1514 		free(ostat);
1515 		walk_arg.need_update = 1;
1516 		return;
1517 	}
1518 
1519 	(void) close(fd);
1520 
1521 	walk_arg.old_nvlp = NULL;
1522 	error = nvlist_unpack(ostat, sb.st_size, &walk_arg.old_nvlp, 0);
1523 
1524 	free(ostat);
1525 
1526 	if (error) {
1527 		bam_error(UNPACK_FAIL, path, strerror(error));
1528 		walk_arg.old_nvlp = NULL;
1529 		walk_arg.need_update = 1;
1530 		return;
1531 	}
1532 }
1533 
1534 static void
1535 create_newstat(void)
1536 {
1537 	int error;
1538 
1539 	error = nvlist_alloc(&walk_arg.new_nvlp, NV_UNIQUE_NAME, 0);
1540 	if (error) {
1541 		/*
1542 		 * Not fatal - we can still create archive
1543 		 */
1544 		walk_arg.new_nvlp = NULL;
1545 		bam_error(NVALLOC_FAIL, strerror(error));
1546 	}
1547 }
1548 
1549 static void
1550 walk_list(char *root, filelist_t *flistp)
1551 {
1552 	char path[PATH_MAX];
1553 	line_t *lp;
1554 
1555 	for (lp = flistp->head; lp; lp = lp->next) {
1556 		(void) snprintf(path, sizeof (path), "%s%s", root, lp->line);
1557 		/* XXX shouldn't we use FTW_MOUNT ? */
1558 		if (nftw(path, cmpstat, 20, 0) == -1) {
1559 			/*
1560 			 * Some files may not exist.
1561 			 * For example: etc/rtc_config on a x86 diskless system
1562 			 * Emit verbose message only
1563 			 */
1564 			if (bam_verbose)
1565 				bam_print(NFTW_FAIL, path, strerror(errno));
1566 		}
1567 	}
1568 }
1569 
1570 static void
1571 savenew(char *root)
1572 {
1573 	char path[PATH_MAX];
1574 	char path2[PATH_MAX];
1575 	size_t sz;
1576 	char *nstat;
1577 	int fd, wrote, error;
1578 
1579 	nstat = NULL;
1580 	sz = 0;
1581 	error = nvlist_pack(walk_arg.new_nvlp, &nstat, &sz,
1582 	    NV_ENCODE_XDR, 0);
1583 	if (error) {
1584 		bam_error(PACK_FAIL, strerror(error));
1585 		return;
1586 	}
1587 
1588 	(void) snprintf(path, sizeof (path), "%s%s", root, FILE_STAT_TMP);
1589 	fd = open(path, O_RDWR|O_CREAT|O_TRUNC, FILE_STAT_MODE);
1590 	if (fd == -1) {
1591 		bam_error(OPEN_FAIL, path, strerror(errno));
1592 		free(nstat);
1593 		return;
1594 	}
1595 	wrote = write(fd, nstat, sz);
1596 	if (wrote != sz) {
1597 		bam_error(WRITE_FAIL, path, strerror(errno));
1598 		(void) close(fd);
1599 		free(nstat);
1600 		return;
1601 	}
1602 	(void) close(fd);
1603 	free(nstat);
1604 
1605 	(void) snprintf(path2, sizeof (path2), "%s%s", root, FILE_STAT);
1606 	if (rename(path, path2) != 0) {
1607 		bam_error(RENAME_FAIL, path2, strerror(errno));
1608 	}
1609 }
1610 
1611 static void
1612 clear_walk_args(void)
1613 {
1614 	if (walk_arg.old_nvlp)
1615 		nvlist_free(walk_arg.old_nvlp);
1616 	if (walk_arg.new_nvlp)
1617 		nvlist_free(walk_arg.new_nvlp);
1618 	walk_arg.need_update = 0;
1619 	walk_arg.old_nvlp = NULL;
1620 	walk_arg.new_nvlp = NULL;
1621 }
1622 
1623 /*
1624  * Returns:
1625  *	0 - no update necessary
1626  *	1 - update required.
1627  *	BAM_ERROR (-1) - An error occurred
1628  *
1629  * Special handling for check (-n):
1630  * ================================
1631  * The check (-n) option produces parseable output.
1632  * To do this, we suppress all stdout messages unrelated
1633  * to out of sync files.
1634  * All stderr messages are still printed though.
1635  *
1636  */
1637 static int
1638 update_required(char *root)
1639 {
1640 	struct stat sb;
1641 	char path[PATH_MAX];
1642 	filelist_t flist;
1643 	filelist_t *flistp = &flist;
1644 	int need_update;
1645 
1646 	flistp->head = flistp->tail = NULL;
1647 
1648 	walk_arg.need_update = 0;
1649 
1650 	/*
1651 	 * Without consulting stat data, check if we need update
1652 	 */
1653 	check_flags_and_files(root);
1654 
1655 	/*
1656 	 * In certain deployment scenarios, filestat may not
1657 	 * exist. Ignore it during boot-archive SMF check.
1658 	 */
1659 	if (bam_smf_check) {
1660 		(void) snprintf(path, sizeof (path), "%s%s", root, FILE_STAT);
1661 		if (stat(path, &sb) != 0)
1662 			return (0);
1663 	}
1664 
1665 	/*
1666 	 * consult stat data only if we haven't made a decision
1667 	 * about update. If checking (-n) however, we always
1668 	 * need stat data (since we want to compare old and new)
1669 	 */
1670 	if (!walk_arg.need_update || bam_check)
1671 		getoldstat(root);
1672 
1673 	/*
1674 	 * read list of files
1675 	 */
1676 	if (read_list(root, flistp) != BAM_SUCCESS) {
1677 		clear_walk_args();
1678 		return (BAM_ERROR);
1679 	}
1680 
1681 	assert(flistp->head && flistp->tail);
1682 
1683 	/*
1684 	 * At this point either the update is required
1685 	 * or the decision is pending. In either case
1686 	 * we need to create new stat nvlist
1687 	 */
1688 	create_newstat();
1689 
1690 	/*
1691 	 * This walk does 2 things:
1692 	 *  	- gets new stat data for every file
1693 	 *	- (optional) compare old and new stat data
1694 	 */
1695 	walk_list(root, &flist);
1696 
1697 	/* done with the file list */
1698 	filelist_free(flistp);
1699 
1700 	/*
1701 	 * if we didn't succeed in  creating new stat data above
1702 	 * just return result of update check so that archive is built.
1703 	 */
1704 	if (walk_arg.new_nvlp == NULL) {
1705 		bam_error(NO_NEW_STAT);
1706 		need_update = walk_arg.need_update;
1707 		clear_walk_args();
1708 		return (need_update ? 1 : 0);
1709 	}
1710 
1711 
1712 	/*
1713 	 * If no update required, discard newstat
1714 	 */
1715 	if (!walk_arg.need_update) {
1716 		clear_walk_args();
1717 		return (0);
1718 	}
1719 
1720 	/*
1721 	 * At this point we need an update - so save new stat data
1722 	 * However, if only checking (-n), don't save new stat data.
1723 	 */
1724 	if (!bam_check)
1725 		savenew(root);
1726 
1727 	clear_walk_args();
1728 
1729 	return (1);
1730 }
1731 
1732 static error_t
1733 create_ramdisk(char *root)
1734 {
1735 	char *cmdline, path[PATH_MAX];
1736 	size_t len;
1737 	struct stat sb;
1738 
1739 	/*
1740 	 * Setup command args for create_ramdisk.ksh
1741 	 */
1742 	(void) snprintf(path, sizeof (path), "%s%s", root, CREATE_RAMDISK);
1743 	if (stat(path, &sb) != 0) {
1744 		bam_error(ARCH_EXEC_MISS, path, strerror(errno));
1745 		return (BAM_ERROR);
1746 	}
1747 
1748 	len = strlen(path) + strlen(root) + 10;	/* room for space + -R */
1749 	cmdline = s_calloc(1, len);
1750 
1751 	if (strlen(root) > 1) {
1752 		(void) snprintf(cmdline, len, "%s -R %s", path, root);
1753 		/* chop off / at the end */
1754 		cmdline[strlen(cmdline) - 1] = '\0';
1755 	} else
1756 		(void) snprintf(cmdline, len, "%s", path);
1757 
1758 	if (exec_cmd(cmdline, NULL, 0) != 0) {
1759 		bam_error(ARCHIVE_FAIL, cmdline);
1760 		free(cmdline);
1761 		return (BAM_ERROR);
1762 	}
1763 	free(cmdline);
1764 
1765 	/*
1766 	 * Verify that the archive has been created
1767 	 */
1768 	(void) snprintf(path, sizeof (path), "%s%s", root, BOOT_ARCHIVE);
1769 	if (stat(path, &sb) != 0) {
1770 		bam_error(ARCHIVE_NOT_CREATED, path);
1771 		return (BAM_ERROR);
1772 	}
1773 
1774 	return (BAM_SUCCESS);
1775 }
1776 
1777 /*
1778  * Checks if target filesystem is on a ramdisk
1779  * 1 - is miniroot
1780  * 0 - is not
1781  * When in doubt assume it is not a ramdisk.
1782  */
1783 static int
1784 is_ramdisk(char *root)
1785 {
1786 	struct extmnttab mnt;
1787 	FILE *fp;
1788 	int found;
1789 	char mntpt[PATH_MAX];
1790 	char *cp;
1791 
1792 	/*
1793 	 * There are 3 situations where creating archive is
1794 	 * of dubious value:
1795 	 *	- create boot_archive on a lofi-mounted boot_archive
1796 	 *	- create it on a ramdisk which is the root filesystem
1797 	 *	- create it on a ramdisk mounted somewhere else
1798 	 * The first is not easy to detect and checking for it is not
1799 	 * worth it.
1800 	 * The other two conditions are handled here
1801 	 */
1802 
1803 	fp = fopen(MNTTAB, "r");
1804 	if (fp == NULL) {
1805 		bam_error(OPEN_FAIL, MNTTAB, strerror(errno));
1806 		return (0);
1807 	}
1808 
1809 	resetmnttab(fp);
1810 
1811 	/*
1812 	 * Remove any trailing / from the mount point
1813 	 */
1814 	(void) strlcpy(mntpt, root, sizeof (mntpt));
1815 	if (strcmp(root, "/") != 0) {
1816 		cp = mntpt + strlen(mntpt) - 1;
1817 		if (*cp == '/')
1818 			*cp = '\0';
1819 	}
1820 	found = 0;
1821 	while (getextmntent(fp, &mnt, sizeof (mnt)) == 0) {
1822 		if (strcmp(mnt.mnt_mountp, mntpt) == 0) {
1823 			found = 1;
1824 			break;
1825 		}
1826 	}
1827 
1828 	if (!found) {
1829 		if (bam_verbose)
1830 			bam_error(NOT_IN_MNTTAB, mntpt);
1831 		(void) fclose(fp);
1832 		return (0);
1833 	}
1834 
1835 	if (strstr(mnt.mnt_special, RAMDISK_SPECIAL) != NULL) {
1836 		if (bam_verbose)
1837 			bam_error(IS_RAMDISK, bam_root);
1838 		(void) fclose(fp);
1839 		return (1);
1840 	}
1841 
1842 	(void) fclose(fp);
1843 
1844 	return (0);
1845 }
1846 
1847 static int
1848 is_newboot(char *root)
1849 {
1850 	char path[PATH_MAX];
1851 	struct stat sb;
1852 
1853 	/*
1854 	 * We can't boot without MULTI_BOOT
1855 	 */
1856 	(void) snprintf(path, sizeof (path), "%s%s", root, MULTI_BOOT);
1857 	if (stat(path, &sb) == -1) {
1858 		if (bam_verbose)
1859 			bam_print(FILE_MISS, path);
1860 		return (0);
1861 	}
1862 
1863 	/*
1864 	 * We can't generate archive without GRUB_DIR
1865 	 */
1866 	(void) snprintf(path, sizeof (path), "%s%s", root, GRUB_DIR);
1867 	if (stat(path, &sb) == -1) {
1868 		if (bam_verbose)
1869 			bam_print(DIR_MISS, path);
1870 		return (0);
1871 	}
1872 
1873 	return (1);
1874 }
1875 
1876 static int
1877 is_readonly(char *root)
1878 {
1879 	struct statvfs vfs;
1880 
1881 	/*
1882 	 * Check for RDONLY filesystem
1883 	 * When in doubt assume it is not readonly
1884 	 */
1885 	if (statvfs(root, &vfs) != 0) {
1886 		if (bam_verbose)
1887 			bam_error(STATVFS_FAIL, root, strerror(errno));
1888 		return (0);
1889 	}
1890 
1891 	if (vfs.f_flag & ST_RDONLY) {
1892 		return (1);
1893 	}
1894 
1895 	return (0);
1896 }
1897 
1898 static error_t
1899 update_archive(char *root, char *opt)
1900 {
1901 	error_t ret;
1902 
1903 	assert(root);
1904 	assert(opt == NULL);
1905 
1906 	/*
1907 	 * root must belong to a GRUB boot OS,
1908 	 * don't care on sparc except for diskless clients
1909 	 */
1910 	if (!is_newboot(root)) {
1911 		/*
1912 		 * Emit message only if not in context of update_all.
1913 		 * If in update_all, emit only if verbose flag is set.
1914 		 */
1915 		if (!bam_update_all || bam_verbose)
1916 			bam_print(NOT_GRUB_BOOT, root);
1917 		return (BAM_SUCCESS);
1918 	}
1919 
1920 	/*
1921 	 * root must be writable
1922 	 * Note: statvfs() does not always report the truth
1923 	 */
1924 	if (is_readonly(root)) {
1925 		if (!bam_smf_check && bam_verbose)
1926 			bam_print(RDONLY_FS, root);
1927 		return (BAM_SUCCESS);
1928 	}
1929 
1930 	/*
1931 	 * Don't generate archive on ramdisk
1932 	 */
1933 	if (is_ramdisk(root)) {
1934 		if (bam_verbose)
1935 			bam_print(SKIP_RAMDISK);
1936 		return (BAM_SUCCESS);
1937 	}
1938 
1939 	/*
1940 	 * Now check if updated is really needed
1941 	 */
1942 	ret = update_required(root);
1943 
1944 	/*
1945 	 * The check command (-n) is *not* a dry run
1946 	 * It only checks if the archive is in sync.
1947 	 */
1948 	if (bam_check) {
1949 		bam_exit((ret != 0) ? 1 : 0);
1950 	}
1951 
1952 	if (ret == 1) {
1953 		/* create the ramdisk */
1954 		ret = create_ramdisk(root);
1955 	}
1956 	return (ret);
1957 }
1958 
1959 static void
1960 restore_grub_slice(void)
1961 {
1962 	struct stat sb;
1963 	char *mntpt, *physlice;
1964 	int mnted;	/* set if we did a mount */
1965 	char menupath[PATH_MAX], cmd[PATH_MAX];
1966 
1967 	if (stat(GRUB_slice, &sb) != 0) {
1968 		bam_error(MISSING_SLICE_FILE, GRUB_slice, strerror(errno));
1969 		return;
1970 	}
1971 
1972 	/*
1973 	 * If we are doing an luactivate, don't attempt to restore GRUB or else
1974 	 * we may not be able to get to DCA boot environments. Let luactivate
1975 	 * handle GRUB/DCA installation
1976 	 */
1977 	if (stat(LU_ACTIVATE_FILE, &sb) == 0) {
1978 		return;
1979 	}
1980 
1981 	mnted = 0;
1982 	physlice = NULL;
1983 	mntpt = mount_grub_slice(&mnted, &physlice, NULL, NULL);
1984 	if (mntpt == NULL) {
1985 		bam_error(CANNOT_RESTORE_GRUB_SLICE);
1986 		return;
1987 	}
1988 
1989 	(void) snprintf(menupath, sizeof (menupath), "%s%s", mntpt, GRUB_MENU);
1990 	if (stat(menupath, &sb) == 0) {
1991 		umount_grub_slice(mnted, mntpt, physlice, NULL, NULL);
1992 		return;
1993 	}
1994 
1995 	/*
1996 	 * The menu is missing - we need to do a restore
1997 	 */
1998 	bam_print(RESTORING_GRUB);
1999 
2000 	(void) snprintf(cmd, sizeof (cmd), "%s %s %s %s",
2001 	    INSTALLGRUB, STAGE1, STAGE2, physlice);
2002 
2003 	if (exec_cmd(cmd, NULL, 0) != 0) {
2004 		bam_error(RESTORE_GRUB_FAILED);
2005 		umount_grub_slice(mnted, mntpt, physlice, NULL, NULL);
2006 		return;
2007 	}
2008 
2009 	if (stat(GRUB_backup_menu, &sb) != 0) {
2010 		bam_error(MISSING_BACKUP_MENU,
2011 		    GRUB_backup_menu, strerror(errno));
2012 		umount_grub_slice(mnted, mntpt, physlice, NULL, NULL);
2013 		return;
2014 	}
2015 
2016 	(void) snprintf(cmd, sizeof (cmd), "/bin/cp %s %s",
2017 	    GRUB_backup_menu, menupath);
2018 
2019 	if (exec_cmd(cmd, NULL, 0) != 0) {
2020 		bam_error(RESTORE_MENU_FAILED, menupath);
2021 		umount_grub_slice(mnted, mntpt, physlice, NULL, NULL);
2022 		return;
2023 	}
2024 
2025 	/* Success */
2026 	umount_grub_slice(mnted, mntpt, physlice, NULL, NULL);
2027 }
2028 
2029 static error_t
2030 update_all(char *root, char *opt)
2031 {
2032 	struct extmnttab mnt;
2033 	struct stat sb;
2034 	FILE *fp;
2035 	char multibt[PATH_MAX];
2036 	error_t ret = BAM_SUCCESS;
2037 
2038 	assert(root);
2039 	assert(opt == NULL);
2040 
2041 	if (bam_rootlen != 1 || *root != '/') {
2042 		elide_trailing_slash(root, multibt, sizeof (multibt));
2043 		bam_error(ALT_ROOT_INVALID, multibt);
2044 		return (BAM_ERROR);
2045 	}
2046 
2047 	/*
2048 	 * First update archive for current root
2049 	 */
2050 	if (update_archive(root, opt) != BAM_SUCCESS)
2051 		ret = BAM_ERROR;
2052 
2053 	/*
2054 	 * Now walk the mount table, performing archive update
2055 	 * for all mounted Newboot root filesystems
2056 	 */
2057 	fp = fopen(MNTTAB, "r");
2058 	if (fp == NULL) {
2059 		bam_error(OPEN_FAIL, MNTTAB, strerror(errno));
2060 		ret = BAM_ERROR;
2061 		goto out;
2062 	}
2063 
2064 	resetmnttab(fp);
2065 
2066 	while (getextmntent(fp, &mnt, sizeof (mnt)) == 0) {
2067 		if (mnt.mnt_special == NULL)
2068 			continue;
2069 		if (strncmp(mnt.mnt_special, "/dev/", strlen("/dev/")) != 0)
2070 			continue;
2071 		if (strcmp(mnt.mnt_mountp, "/") == 0)
2072 			continue;
2073 
2074 		(void) snprintf(multibt, sizeof (multibt), "%s%s",
2075 		    mnt.mnt_mountp, MULTI_BOOT);
2076 
2077 		if (stat(multibt, &sb) == -1)
2078 			continue;
2079 
2080 		/*
2081 		 * We put a trailing slash to be consistent with root = "/"
2082 		 * case, such that we don't have to print // in some cases.
2083 		 */
2084 		(void) snprintf(rootbuf, sizeof (rootbuf), "%s/",
2085 		    mnt.mnt_mountp);
2086 		bam_rootlen = strlen(rootbuf);
2087 		if (update_archive(rootbuf, opt) != BAM_SUCCESS)
2088 			ret = BAM_ERROR;
2089 	}
2090 
2091 	(void) fclose(fp);
2092 
2093 out:
2094 	if (stat(GRUB_slice, &sb) == 0) {
2095 		restore_grub_slice();
2096 	}
2097 
2098 	return (ret);
2099 }
2100 
2101 static void
2102 append_line(menu_t *mp, line_t *lp)
2103 {
2104 	if (mp->start == NULL) {
2105 		mp->start = lp;
2106 	} else {
2107 		mp->end->next = lp;
2108 	}
2109 	mp->end = lp;
2110 }
2111 
2112 /*
2113  * A line in menu.lst looks like
2114  * [ ]*<cmd>[ \t=]*<arg>*
2115  */
2116 static void
2117 line_parser(menu_t *mp, char *str, int *lineNum, int *entryNum)
2118 {
2119 	/*
2120 	 * save state across calls. This is so that
2121 	 * header gets the right entry# after title has
2122 	 * been processed
2123 	 */
2124 	static line_t *prev;
2125 
2126 	line_t	*lp;
2127 	char *cmd, *sep, *arg;
2128 	char save, *cp, *line;
2129 	menu_flag_t flag = BAM_INVALID;
2130 
2131 	if (str == NULL) {
2132 		return;
2133 	}
2134 
2135 	/*
2136 	 * First save a copy of the entire line.
2137 	 * We use this later to set the line field.
2138 	 */
2139 	line = s_strdup(str);
2140 
2141 	/* Eat up leading whitespace */
2142 	while (*str == ' ' || *str == '\t')
2143 		str++;
2144 
2145 	if (*str == '#') {		/* comment */
2146 		cmd = s_strdup("#");
2147 		sep = NULL;
2148 		arg = s_strdup(str + 1);
2149 		flag = BAM_COMMENT;
2150 	} else if (*str == '\0') {	/* blank line */
2151 		cmd = sep = arg = NULL;
2152 		flag = BAM_EMPTY;
2153 	} else {
2154 		/*
2155 		 * '=' is not a documented separator in grub syntax.
2156 		 * However various development bits use '=' as a
2157 		 * separator. In addition, external users also
2158 		 * use = as a separator. So we will allow that usage.
2159 		 */
2160 		cp = str;
2161 		while (*str != ' ' && *str != '\t' && *str != '=') {
2162 			if (*str == '\0') {
2163 				cmd = s_strdup(cp);
2164 				sep = arg = NULL;
2165 				break;
2166 			}
2167 			str++;
2168 		}
2169 
2170 		if (*str != '\0') {
2171 			save = *str;
2172 			*str = '\0';
2173 			cmd = s_strdup(cp);
2174 			*str = save;
2175 
2176 			str++;
2177 			save = *str;
2178 			*str = '\0';
2179 			sep = s_strdup(str - 1);
2180 			*str = save;
2181 
2182 			while (*str == ' ' || *str == '\t')
2183 				str++;
2184 			if (*str == '\0')
2185 				arg = NULL;
2186 			else
2187 				arg = s_strdup(str);
2188 		}
2189 	}
2190 
2191 	lp = s_calloc(1, sizeof (line_t));
2192 
2193 	lp->cmd = cmd;
2194 	lp->sep = sep;
2195 	lp->arg = arg;
2196 	lp->line = line;
2197 	lp->lineNum = ++(*lineNum);
2198 	if (cmd && strcmp(cmd, menu_cmds[TITLE_CMD]) == 0) {
2199 		lp->entryNum = ++(*entryNum);
2200 		lp->flags = BAM_TITLE;
2201 		if (prev && prev->flags == BAM_COMMENT &&
2202 		    prev->arg && strcmp(prev->arg, BAM_HDR) == 0)
2203 			prev->entryNum = lp->entryNum;
2204 	} else if (flag != BAM_INVALID) {
2205 		/*
2206 		 * For header comments, the entry# is "fixed up"
2207 		 * by the subsequent title
2208 		 */
2209 		lp->entryNum = *entryNum;
2210 		lp->flags = flag;
2211 	} else {
2212 		lp->entryNum = *entryNum;
2213 		lp->flags = (*entryNum == ENTRY_INIT) ? BAM_GLOBAL : BAM_ENTRY;
2214 	}
2215 
2216 	append_line(mp, lp);
2217 
2218 	prev = lp;
2219 }
2220 
2221 static void
2222 update_numbering(menu_t *mp)
2223 {
2224 	int lineNum;
2225 	int entryNum;
2226 	int old_default_value;
2227 	line_t *lp, *prev, *default_lp, *default_entry;
2228 	char buf[PATH_MAX];
2229 
2230 	if (mp->start == NULL) {
2231 		return;
2232 	}
2233 
2234 	lineNum = LINE_INIT;
2235 	entryNum = ENTRY_INIT;
2236 	old_default_value = ENTRY_INIT;
2237 	lp = default_lp = default_entry = NULL;
2238 
2239 	prev = NULL;
2240 	for (lp = mp->start; lp; prev = lp, lp = lp->next) {
2241 		lp->lineNum = ++lineNum;
2242 
2243 		/*
2244 		 * Get the value of the default command
2245 		 */
2246 		if (lp->entryNum == ENTRY_INIT && lp->cmd &&
2247 		    strcmp(lp->cmd, menu_cmds[DEFAULT_CMD]) == 0 &&
2248 		    lp->arg) {
2249 			old_default_value = atoi(lp->arg);
2250 			default_lp = lp;
2251 		}
2252 
2253 		/*
2254 		 * If not boot entry, nothing else to fix for this
2255 		 * entry
2256 		 */
2257 		if (lp->entryNum == ENTRY_INIT)
2258 			continue;
2259 
2260 		/*
2261 		 * Record the position of the default entry.
2262 		 * The following works because global
2263 		 * commands like default and timeout should precede
2264 		 * actual boot entries, so old_default_value
2265 		 * is already known (or default cmd is missing).
2266 		 */
2267 		if (default_entry == NULL &&
2268 		    old_default_value != ENTRY_INIT &&
2269 		    lp->entryNum == old_default_value) {
2270 			default_entry = lp;
2271 		}
2272 
2273 		/*
2274 		 * Now fixup the entry number
2275 		 */
2276 		if (lp->cmd && strcmp(lp->cmd, menu_cmds[TITLE_CMD]) == 0) {
2277 			lp->entryNum = ++entryNum;
2278 			/* fixup the bootadm header */
2279 			if (prev && prev->flags == BAM_COMMENT &&
2280 			    prev->arg && strcmp(prev->arg, BAM_HDR) == 0) {
2281 				prev->entryNum = lp->entryNum;
2282 			}
2283 		} else {
2284 			lp->entryNum = entryNum;
2285 		}
2286 	}
2287 
2288 	/*
2289 	 * No default command in menu, simply return
2290 	 */
2291 	if (default_lp == NULL) {
2292 		return;
2293 	}
2294 
2295 	free(default_lp->arg);
2296 	free(default_lp->line);
2297 
2298 	if (default_entry == NULL) {
2299 		default_lp->arg = s_strdup("0");
2300 	} else {
2301 		(void) snprintf(buf, sizeof (buf), "%d",
2302 		    default_entry->entryNum);
2303 		default_lp->arg = s_strdup(buf);
2304 	}
2305 
2306 	/*
2307 	 * The following is required since only the line field gets
2308 	 * written back to menu.lst
2309 	 */
2310 	(void) snprintf(buf, sizeof (buf), "%s%s%s",
2311 	    menu_cmds[DEFAULT_CMD], menu_cmds[SEP_CMD], default_lp->arg);
2312 	default_lp->line = s_strdup(buf);
2313 }
2314 
2315 
2316 static menu_t *
2317 menu_read(char *menu_path)
2318 {
2319 	FILE *fp;
2320 	char buf[BAM_MAXLINE], *cp;
2321 	menu_t *mp;
2322 	int line, entry, len, n;
2323 
2324 	mp = s_calloc(1, sizeof (menu_t));
2325 
2326 	fp = fopen(menu_path, "r");
2327 	if (fp == NULL) { /* Let the caller handle this error */
2328 		return (mp);
2329 	}
2330 
2331 
2332 	/* Note: GRUB boot entry number starts with 0 */
2333 	line = LINE_INIT;
2334 	entry = ENTRY_INIT;
2335 	cp = buf;
2336 	len = sizeof (buf);
2337 	while (s_fgets(cp, len, fp) != NULL) {
2338 		n = strlen(cp);
2339 		if (cp[n - 1] == '\\') {
2340 			len -= n - 1;
2341 			assert(len >= 2);
2342 			cp += n - 1;
2343 			continue;
2344 		}
2345 		line_parser(mp, buf, &line, &entry);
2346 		cp = buf;
2347 		len = sizeof (buf);
2348 	}
2349 
2350 	if (fclose(fp) == EOF) {
2351 		bam_error(CLOSE_FAIL, menu_path, strerror(errno));
2352 	}
2353 
2354 	return (mp);
2355 }
2356 
2357 static error_t
2358 selector(menu_t *mp, char *opt, int *entry, char **title)
2359 {
2360 	char *eq;
2361 	char *opt_dup;
2362 	int entryNum;
2363 
2364 	assert(mp);
2365 	assert(mp->start);
2366 	assert(opt);
2367 
2368 	opt_dup = s_strdup(opt);
2369 
2370 	if (entry)
2371 		*entry = ENTRY_INIT;
2372 	if (title)
2373 		*title = NULL;
2374 
2375 	eq = strchr(opt_dup, '=');
2376 	if (eq == NULL) {
2377 		bam_error(INVALID_OPT, opt);
2378 		free(opt_dup);
2379 		return (BAM_ERROR);
2380 	}
2381 
2382 	*eq = '\0';
2383 	if (entry && strcmp(opt_dup, OPT_ENTRY_NUM) == 0) {
2384 		assert(mp->end);
2385 		entryNum = s_strtol(eq + 1);
2386 		if (entryNum < 0 || entryNum > mp->end->entryNum) {
2387 			bam_error(INVALID_ENTRY, eq + 1);
2388 			free(opt_dup);
2389 			return (BAM_ERROR);
2390 		}
2391 		*entry = entryNum;
2392 	} else if (title && strcmp(opt_dup, menu_cmds[TITLE_CMD]) == 0) {
2393 		*title = opt + (eq - opt_dup) + 1;
2394 	} else {
2395 		bam_error(INVALID_OPT, opt);
2396 		free(opt_dup);
2397 		return (BAM_ERROR);
2398 	}
2399 
2400 	free(opt_dup);
2401 	return (BAM_SUCCESS);
2402 }
2403 
2404 /*
2405  * If invoked with no titles/entries (opt == NULL)
2406  * only title lines in file are printed.
2407  *
2408  * If invoked with a title or entry #, all
2409  * lines in *every* matching entry are listed
2410  */
2411 static error_t
2412 list_entry(menu_t *mp, char *menu_path, char *opt)
2413 {
2414 	line_t *lp;
2415 	int entry = ENTRY_INIT;
2416 	int found;
2417 	char *title = NULL;
2418 
2419 	assert(mp);
2420 	assert(menu_path);
2421 
2422 	if (mp->start == NULL) {
2423 		bam_error(NO_MENU, menu_path);
2424 		return (BAM_ERROR);
2425 	}
2426 
2427 	if (opt != NULL) {
2428 		if (selector(mp, opt, &entry, &title) != BAM_SUCCESS) {
2429 			return (BAM_ERROR);
2430 		}
2431 		assert((entry != ENTRY_INIT) ^ (title != NULL));
2432 	} else {
2433 		(void) read_globals(mp, menu_path, menu_cmds[DEFAULT_CMD], 0);
2434 		(void) read_globals(mp, menu_path, menu_cmds[TIMEOUT_CMD], 0);
2435 	}
2436 
2437 	found = 0;
2438 	for (lp = mp->start; lp; lp = lp->next) {
2439 		if (lp->flags == BAM_COMMENT || lp->flags == BAM_EMPTY)
2440 			continue;
2441 		if (opt == NULL && lp->flags == BAM_TITLE) {
2442 			bam_print(PRINT_TITLE, lp->entryNum,
2443 			    lp->arg);
2444 			found = 1;
2445 			continue;
2446 		}
2447 		if (entry != ENTRY_INIT && lp->entryNum == entry) {
2448 			bam_print(PRINT, lp->line);
2449 			found = 1;
2450 			continue;
2451 		}
2452 
2453 		/*
2454 		 * We set the entry value here so that all lines
2455 		 * in entry get printed. If we subsequently match
2456 		 * title in other entries, all lines in those
2457 		 * entries get printed as well.
2458 		 */
2459 		if (title && lp->flags == BAM_TITLE && lp->arg &&
2460 		    strncmp(title, lp->arg, strlen(title)) == 0) {
2461 			bam_print(PRINT, lp->line);
2462 			entry = lp->entryNum;
2463 			found = 1;
2464 			continue;
2465 		}
2466 	}
2467 
2468 	if (!found) {
2469 		bam_error(NO_MATCH_ENTRY);
2470 		return (BAM_ERROR);
2471 	}
2472 
2473 	return (BAM_SUCCESS);
2474 }
2475 
2476 static int
2477 add_boot_entry(menu_t *mp,
2478 	char *title,
2479 	char *root,
2480 	char *kernel,
2481 	char *module)
2482 {
2483 	menu_t dummy;
2484 	int lineNum, entryNum;
2485 	char linebuf[BAM_MAXLINE];
2486 
2487 	assert(mp);
2488 
2489 	if (title == NULL) {
2490 		bam_error(SUBOPT_MISS, menu_cmds[TITLE_CMD]);
2491 		return (BAM_ERROR);
2492 	}
2493 	if (root == NULL) {
2494 		bam_error(SUBOPT_MISS, menu_cmds[ROOT_CMD]);
2495 		return (BAM_ERROR);
2496 	}
2497 	if (kernel == NULL) {
2498 		bam_error(SUBOPT_MISS, menu_cmds[KERNEL_CMD]);
2499 		return (BAM_ERROR);
2500 	}
2501 	if (module == NULL) {
2502 		bam_error(SUBOPT_MISS, menu_cmds[MODULE_CMD]);
2503 		return (BAM_ERROR);
2504 	}
2505 
2506 	if (mp->start) {
2507 		lineNum = mp->end->lineNum;
2508 		entryNum = mp->end->entryNum;
2509 	} else {
2510 		lineNum = LINE_INIT;
2511 		entryNum = ENTRY_INIT;
2512 	}
2513 
2514 	/*
2515 	 * No separator for comment (HDR/FTR) commands
2516 	 * The syntax for comments is #<comment>
2517 	 */
2518 	(void) snprintf(linebuf, sizeof (linebuf), "%s%s",
2519 	    menu_cmds[COMMENT_CMD], BAM_HDR);
2520 	dummy.start = dummy.end = NULL;
2521 	line_parser(&dummy, linebuf, &lineNum, &entryNum);
2522 	if (dummy.start == NULL || dummy.start->flags != BAM_COMMENT) {
2523 		line_free(dummy.start);
2524 		bam_error(INVALID_HDR, BAM_HDR);
2525 		return (BAM_ERROR);
2526 	}
2527 	assert(dummy.start == dummy.end);
2528 	append_line(mp, dummy.start);
2529 
2530 	(void) snprintf(linebuf, sizeof (linebuf), "%s%s%s",
2531 	    menu_cmds[TITLE_CMD], menu_cmds[SEP_CMD], title);
2532 	dummy.start = dummy.end = NULL;
2533 	line_parser(&dummy, linebuf, &lineNum, &entryNum);
2534 	if (dummy.start == NULL || dummy.start->flags != BAM_TITLE) {
2535 		line_free(dummy.start);
2536 		bam_error(INVALID_TITLE, title);
2537 		return (BAM_ERROR);
2538 	}
2539 	assert(dummy.start == dummy.end);
2540 	append_line(mp, dummy.start);
2541 
2542 
2543 	(void) snprintf(linebuf, sizeof (linebuf), "%s%s%s",
2544 	    menu_cmds[ROOT_CMD], menu_cmds[SEP_CMD], root);
2545 	dummy.start = dummy.end = NULL;
2546 	line_parser(&dummy, linebuf, &lineNum, &entryNum);
2547 	if (dummy.start == NULL || dummy.start->flags != BAM_ENTRY) {
2548 		line_free(dummy.start);
2549 		bam_error(INVALID_ROOT, root);
2550 		return (BAM_ERROR);
2551 	}
2552 	assert(dummy.start == dummy.end);
2553 	append_line(mp, dummy.start);
2554 
2555 
2556 	(void) snprintf(linebuf, sizeof (linebuf), "%s%s%s",
2557 	    menu_cmds[KERNEL_CMD], menu_cmds[SEP_CMD], kernel);
2558 	dummy.start = dummy.end = NULL;
2559 	line_parser(&dummy, linebuf, &lineNum, &entryNum);
2560 	if (dummy.start == NULL || dummy.start->flags != BAM_ENTRY) {
2561 		line_free(dummy.start);
2562 		bam_error(INVALID_KERNEL, kernel);
2563 		return (BAM_ERROR);
2564 	}
2565 	assert(dummy.start == dummy.end);
2566 	append_line(mp, dummy.start);
2567 
2568 	(void) snprintf(linebuf, sizeof (linebuf), "%s%s%s",
2569 	    menu_cmds[MODULE_CMD], menu_cmds[SEP_CMD], module);
2570 	dummy.start = dummy.end = NULL;
2571 	line_parser(&dummy, linebuf, &lineNum, &entryNum);
2572 	if (dummy.start == NULL || dummy.start->flags != BAM_ENTRY) {
2573 		line_free(dummy.start);
2574 		bam_error(INVALID_MODULE, module);
2575 		return (BAM_ERROR);
2576 	}
2577 	assert(dummy.start == dummy.end);
2578 	append_line(mp, dummy.start);
2579 
2580 	(void) snprintf(linebuf, sizeof (linebuf), "%s%s",
2581 	    menu_cmds[COMMENT_CMD], BAM_FTR);
2582 	dummy.start = dummy.end = NULL;
2583 	line_parser(&dummy, linebuf, &lineNum, &entryNum);
2584 	if (dummy.start == NULL || dummy.start->flags != BAM_COMMENT) {
2585 		line_free(dummy.start);
2586 		bam_error(INVALID_FOOTER, BAM_FTR);
2587 		return (BAM_ERROR);
2588 	}
2589 	assert(dummy.start == dummy.end);
2590 	append_line(mp, dummy.start);
2591 
2592 	return (entryNum);
2593 }
2594 
2595 static error_t
2596 do_delete(menu_t *mp, int entryNum)
2597 {
2598 	int bootadm_entry = 0;
2599 	line_t *lp, *prev, *save;
2600 	int deleted;
2601 
2602 	assert(entryNum != ENTRY_INIT);
2603 
2604 	deleted = 0;
2605 	prev = NULL;
2606 	for (lp = mp->start; lp; ) {
2607 
2608 		if (lp->entryNum == ENTRY_INIT) {
2609 			prev = lp;
2610 			lp = lp->next;
2611 			continue;
2612 		}
2613 
2614 		if (entryNum != ALL_ENTRIES && lp->entryNum != entryNum) {
2615 			prev = lp;
2616 			lp = lp->next;
2617 			continue;
2618 		}
2619 
2620 		/*
2621 		 * can only delete bootadm entries
2622 		 */
2623 		if (lp->flags == BAM_COMMENT && strcmp(lp->arg, BAM_HDR) == 0) {
2624 			bootadm_entry = 1;
2625 		}
2626 
2627 		if (!bootadm_entry) {
2628 			prev = lp;
2629 			lp = lp->next;
2630 			continue;
2631 		}
2632 
2633 		if (lp->flags == BAM_COMMENT && strcmp(lp->arg, BAM_FTR) == 0)
2634 			bootadm_entry = 0;
2635 
2636 		if (prev == NULL)
2637 			mp->start = lp->next;
2638 		else
2639 			prev->next = lp->next;
2640 		if (mp->end == lp)
2641 			mp->end = prev;
2642 		save = lp->next;
2643 		line_free(lp);
2644 		lp = save;	/* prev stays the same */
2645 
2646 		deleted = 1;
2647 	}
2648 
2649 	if (!deleted && entryNum != ALL_ENTRIES) {
2650 		bam_error(NO_BOOTADM_MATCH);
2651 		return (BAM_ERROR);
2652 	}
2653 
2654 	/*
2655 	 * Now that we have deleted an entry, update
2656 	 * the entry numbering and the default cmd.
2657 	 */
2658 	update_numbering(mp);
2659 
2660 	return (BAM_SUCCESS);
2661 }
2662 
2663 static error_t
2664 delete_entry(menu_t *mp, char *menu_path, char *opt)
2665 {
2666 	int entry = ENTRY_INIT;
2667 	char *title = NULL;
2668 	line_t *lp;
2669 
2670 	assert(mp);
2671 	assert(opt);
2672 
2673 	/*
2674 	 * Do a quick check. If the file is empty
2675 	 * we have nothing to delete
2676 	 */
2677 	if (mp->start == NULL) {
2678 		bam_print(EMPTY_FILE, menu_path);
2679 		return (BAM_SUCCESS);
2680 	}
2681 
2682 	if (selector(mp, opt, &entry, &title) != BAM_SUCCESS) {
2683 		return (BAM_ERROR);
2684 	}
2685 	assert((entry != ENTRY_INIT) ^ (title != NULL));
2686 
2687 	for (lp = mp->start; lp; lp = lp->next) {
2688 		if (entry != ENTRY_INIT)
2689 			break;
2690 		assert(title);
2691 		if (lp->flags == BAM_TITLE &&
2692 		    lp->arg && strcmp(lp->arg, title) == 0) {
2693 			entry = lp->entryNum;
2694 			break;
2695 		}
2696 	}
2697 
2698 	if (entry == ENTRY_INIT) {
2699 		bam_error(NO_MATCH, title);
2700 		return (BAM_ERROR);
2701 	}
2702 
2703 	if (do_delete(mp, entry) != BAM_SUCCESS) {
2704 		return (BAM_ERROR);
2705 	}
2706 
2707 	return (BAM_WRITE);
2708 }
2709 
2710 static error_t
2711 delete_all_entries(menu_t *mp, char *menu_path, char *opt)
2712 {
2713 	assert(mp);
2714 	assert(opt == NULL);
2715 
2716 	if (mp->start == NULL) {
2717 		bam_print(EMPTY_FILE, menu_path);
2718 		return (BAM_SUCCESS);
2719 	}
2720 
2721 	if (do_delete(mp, ALL_ENTRIES) != BAM_SUCCESS) {
2722 		return (BAM_ERROR);
2723 	}
2724 
2725 	return (BAM_WRITE);
2726 }
2727 
2728 static FILE *
2729 open_diskmap(void)
2730 {
2731 	FILE *fp;
2732 	char cmd[PATH_MAX];
2733 
2734 	/* make sure we have a map file */
2735 	fp = fopen(GRUBDISK_MAP, "r");
2736 	if (fp == NULL) {
2737 		(void) snprintf(cmd, sizeof (cmd),
2738 		    "%s > /dev/null", CREATE_DISKMAP);
2739 		(void) system(cmd);
2740 		fp = fopen(GRUBDISK_MAP, "r");
2741 	}
2742 	return (fp);
2743 }
2744 
2745 #define	SECTOR_SIZE	512
2746 
2747 static int
2748 get_partition(char *device)
2749 {
2750 	int i, fd, is_pcfs, partno = -1;
2751 	struct mboot *mboot;
2752 	char boot_sect[SECTOR_SIZE];
2753 	char *wholedisk, *slice;
2754 
2755 	/* form whole disk (p0) */
2756 	slice = device + strlen(device) - 2;
2757 	is_pcfs = (*slice != 's');
2758 	if (!is_pcfs)
2759 		*slice = '\0';
2760 	wholedisk = s_calloc(1, strlen(device) + 3);
2761 	(void) snprintf(wholedisk, strlen(device) + 3, "%sp0", device);
2762 	if (!is_pcfs)
2763 		*slice = 's';
2764 
2765 	/* read boot sector */
2766 	fd = open(wholedisk, O_RDONLY);
2767 	free(wholedisk);
2768 	if (fd == -1 || read(fd, boot_sect, SECTOR_SIZE) != SECTOR_SIZE) {
2769 		return (partno);
2770 	}
2771 	(void) close(fd);
2772 
2773 	/* parse fdisk table */
2774 	mboot = (struct mboot *)((void *)boot_sect);
2775 	for (i = 0; i < FD_NUMPART; i++) {
2776 		struct ipart *part =
2777 		    (struct ipart *)(uintptr_t)mboot->parts + i;
2778 		if (is_pcfs) {	/* looking for solaris boot part */
2779 			if (part->systid == 0xbe) {
2780 				partno = i;
2781 				break;
2782 			}
2783 		} else {	/* look for solaris partition, old and new */
2784 			if (part->systid == SUNIXOS ||
2785 			    part->systid == SUNIXOS2) {
2786 				partno = i;
2787 				break;
2788 			}
2789 		}
2790 	}
2791 	return (partno);
2792 }
2793 
2794 static char *
2795 get_grubdisk(char *rootdev, FILE *fp, int on_bootdev)
2796 {
2797 	char *grubdisk;	/* (hd#,#,#) */
2798 	char *slice;
2799 	char *grubhd;
2800 	int fdiskpart;
2801 	int found = 0;
2802 	char *devname, *ctdname = strstr(rootdev, "dsk/");
2803 	char linebuf[PATH_MAX];
2804 
2805 	if (ctdname == NULL)
2806 		return (NULL);
2807 
2808 	ctdname += strlen("dsk/");
2809 	slice = strrchr(ctdname, 's');
2810 	if (slice)
2811 		*slice = '\0';
2812 
2813 	rewind(fp);
2814 	while (s_fgets(linebuf, sizeof (linebuf), fp) != NULL) {
2815 		grubhd = strtok(linebuf, " \t\n");
2816 		if (grubhd)
2817 			devname = strtok(NULL, " \t\n");
2818 		else
2819 			devname = NULL;
2820 		if (devname && strcmp(devname, ctdname) == 0) {
2821 			found = 1;
2822 			break;
2823 		}
2824 	}
2825 
2826 	if (slice)
2827 		*slice = 's';
2828 
2829 	if (found == 0) {
2830 		if (bam_verbose)
2831 			bam_print(DISKMAP_FAIL_NONFATAL, rootdev);
2832 		grubhd = "0";	/* assume disk 0 if can't match */
2833 	}
2834 
2835 	fdiskpart = get_partition(rootdev);
2836 	if (fdiskpart == -1)
2837 		return (NULL);
2838 
2839 	grubdisk = s_calloc(1, 10);
2840 	if (slice) {
2841 		(void) snprintf(grubdisk, 10, "(hd%s,%d,%c)",
2842 		    grubhd, fdiskpart, slice[1] + 'a' - '0');
2843 	} else
2844 		(void) snprintf(grubdisk, 10, "(hd%s,%d)",
2845 		    grubhd, fdiskpart);
2846 
2847 	/* if root not on bootdev, change GRUB disk to 0 */
2848 	if (!on_bootdev)
2849 		grubdisk[3] = '0';
2850 	return (grubdisk);
2851 }
2852 
2853 static char *get_title(char *rootdir)
2854 {
2855 	static char title[80];	/* from /etc/release */
2856 	char *cp, release[PATH_MAX];
2857 	FILE *fp;
2858 
2859 	/* open the /etc/release file */
2860 	(void) snprintf(release, sizeof (release), "%s/etc/release", rootdir);
2861 
2862 	fp = fopen(release, "r");
2863 	if (fp == NULL)
2864 		return ("Solaris");	/* default to Solaris */
2865 
2866 	while (s_fgets(title, sizeof (title), fp) != NULL) {
2867 		cp = strstr(title, "Solaris");
2868 		if (cp)
2869 			break;
2870 	}
2871 	(void) fclose(fp);
2872 	return (cp);
2873 }
2874 
2875 static char *
2876 get_special(char *mountp)
2877 {
2878 	FILE *mntfp;
2879 	struct mnttab mp = {0}, mpref = {0};
2880 
2881 	mntfp = fopen(MNTTAB, "r");
2882 	if (mntfp == NULL) {
2883 		return (0);
2884 	}
2885 
2886 	if (*mountp == '\0')
2887 		mpref.mnt_mountp = "/";
2888 	else
2889 		mpref.mnt_mountp = mountp;
2890 	if (getmntany(mntfp, &mp, &mpref) != 0) {
2891 		(void) fclose(mntfp);
2892 		return (NULL);
2893 	}
2894 	(void) fclose(mntfp);
2895 
2896 	return (s_strdup(mp.mnt_special));
2897 }
2898 
2899 static char *
2900 os_to_grubdisk(char *osdisk, int on_bootdev)
2901 {
2902 	FILE *fp;
2903 	char *grubdisk;
2904 
2905 	/* translate /dev/dsk name to grub disk name */
2906 	fp = open_diskmap();
2907 	if (fp == NULL) {
2908 		bam_error(DISKMAP_FAIL, osdisk);
2909 		return (NULL);
2910 	}
2911 	grubdisk = get_grubdisk(osdisk, fp, on_bootdev);
2912 	(void) fclose(fp);
2913 	return (grubdisk);
2914 }
2915 
2916 /*
2917  * Check if root is on the boot device
2918  * Return 0 (false) on error
2919  */
2920 static int
2921 menu_on_bootdev(char *menu_root, FILE *fp)
2922 {
2923 	int ret;
2924 	char *grubhd, *bootp, *special;
2925 
2926 	special = get_special(menu_root);
2927 	if (special == NULL)
2928 		return (0);
2929 	bootp = strstr(special, "p0:boot");
2930 	if (bootp)
2931 		*bootp = '\0';
2932 	grubhd = get_grubdisk(special, fp, 1);
2933 	free(special);
2934 
2935 	if (grubhd == NULL)
2936 		return (0);
2937 	ret = grubhd[3] == '0';
2938 	free(grubhd);
2939 	return (ret);
2940 }
2941 
2942 /*ARGSUSED*/
2943 static error_t
2944 update_entry(menu_t *mp, char *menu_root, char *opt)
2945 {
2946 	FILE *fp;
2947 	int entry;
2948 	line_t *lp;
2949 	char *grubdisk, *title, *osdev, *osroot;
2950 	int bootadm_entry, entry_to_delete;
2951 
2952 	assert(mp);
2953 	assert(opt);
2954 
2955 	osdev = strtok(opt, ",");
2956 	osroot = strtok(NULL, ",");
2957 	if (osroot == NULL)
2958 		osroot = menu_root;
2959 	title = get_title(osroot);
2960 
2961 	/* translate /dev/dsk name to grub disk name */
2962 	fp = open_diskmap();
2963 	if (fp == NULL) {
2964 		bam_error(DISKMAP_FAIL, osdev);
2965 		return (BAM_ERROR);
2966 	}
2967 	grubdisk = get_grubdisk(osdev, fp, menu_on_bootdev(menu_root, fp));
2968 	(void) fclose(fp);
2969 	if (grubdisk == NULL) {
2970 		bam_error(DISKMAP_FAIL, osdev);
2971 		return (BAM_ERROR);
2972 	}
2973 
2974 	/* delete existing entries with matching grub hd name */
2975 	for (;;) {
2976 		entry_to_delete = -1;
2977 		bootadm_entry = 0;
2978 		for (lp = mp->start; lp; lp = lp->next) {
2979 			/*
2980 			 * can only delete bootadm entries
2981 			 */
2982 			if (lp->flags == BAM_COMMENT) {
2983 				if (strcmp(lp->arg, BAM_HDR) == 0)
2984 					bootadm_entry = 1;
2985 				else if (strcmp(lp->arg, BAM_FTR) == 0)
2986 					bootadm_entry = 0;
2987 			}
2988 
2989 			if (bootadm_entry && lp->flags == BAM_ENTRY &&
2990 			    strcmp(lp->cmd, menu_cmds[ROOT_CMD]) == 0 &&
2991 			    strcmp(lp->arg, grubdisk) == 0) {
2992 				entry_to_delete = lp->entryNum;
2993 			}
2994 		}
2995 		if (entry_to_delete == -1)
2996 			break;
2997 		(void) do_delete(mp, entry_to_delete);
2998 	}
2999 
3000 	/* add the entry for normal Solaris */
3001 	entry = add_boot_entry(mp, title, grubdisk,
3002 	    "/platform/i86pc/multiboot",
3003 	    "/platform/i86pc/boot_archive");
3004 
3005 	/* add the entry for failsafe archive */
3006 	(void) add_boot_entry(mp, "Solaris failsafe", grubdisk,
3007 	    "/boot/multiboot kernel/unix -s",
3008 	    "/boot/x86.miniroot-safe");
3009 	free(grubdisk);
3010 
3011 	if (entry == BAM_ERROR) {
3012 		return (BAM_ERROR);
3013 	}
3014 	(void) set_global(mp, menu_cmds[DEFAULT_CMD], entry);
3015 	return (BAM_WRITE);
3016 }
3017 
3018 static char *
3019 read_grub_root(void)
3020 {
3021 	FILE *fp;
3022 	struct stat sb;
3023 	char buf[BAM_MAXLINE];
3024 	char *rootstr;
3025 
3026 	if (stat(GRUB_slice, &sb) != 0) {
3027 		bam_error(MISSING_SLICE_FILE, GRUB_slice, strerror(errno));
3028 		return (NULL);
3029 	}
3030 
3031 	if (stat(GRUB_root, &sb) != 0) {
3032 		bam_error(MISSING_ROOT_FILE, GRUB_root, strerror(errno));
3033 		return (NULL);
3034 	}
3035 
3036 	fp = fopen(GRUB_root, "r");
3037 	if (fp == NULL) {
3038 		bam_error(OPEN_FAIL, GRUB_root, strerror(errno));
3039 		return (NULL);
3040 	}
3041 
3042 	if (s_fgets(buf, sizeof (buf), fp) == NULL) {
3043 		bam_error(EMPTY_FILE, GRUB_root, strerror(errno));
3044 		(void) fclose(fp);
3045 		return (NULL);
3046 	}
3047 
3048 	/*
3049 	 * Copy buf here as check below may trash the buffer
3050 	 */
3051 	rootstr = s_strdup(buf);
3052 
3053 	if (s_fgets(buf, sizeof (buf), fp) != NULL) {
3054 		bam_error(BAD_ROOT_FILE, GRUB_root);
3055 		free(rootstr);
3056 		rootstr = NULL;
3057 	}
3058 
3059 	(void) fclose(fp);
3060 
3061 	return (rootstr);
3062 }
3063 
3064 /*
3065  * This function is for supporting reboot with args.
3066  * The opt value can be:
3067  * NULL		delete temp entry, if present
3068  * entry=#	switches default entry to 1
3069  * else		treated as boot-args and setup a temperary menu entry
3070  *		and make it the default
3071  */
3072 #define	REBOOT_TITLE	"Solaris_reboot_transient"
3073 
3074 static error_t
3075 update_temp(menu_t *mp, char *menupath, char *opt)
3076 {
3077 	int entry;
3078 	char *grubdisk, *rootdev;
3079 	char kernbuf[1024];
3080 	struct stat sb;
3081 
3082 	assert(mp);
3083 
3084 	if (opt != NULL &&
3085 	    strncmp(opt, "entry=", strlen("entry=")) == 0 &&
3086 	    selector(mp, opt, &entry, NULL) == BAM_SUCCESS) {
3087 		/* this is entry=# option */
3088 		return (set_global(mp, menu_cmds[DEFAULT_CMD], entry));
3089 	}
3090 
3091 	/* If no option, delete exiting reboot menu entry */
3092 	if (opt == NULL)
3093 		return (delete_entry(mp, menupath, "title="REBOOT_TITLE));
3094 
3095 	/*
3096 	 * add a new menu entry base on opt and make it the default
3097 	 */
3098 	grubdisk = NULL;
3099 	if (stat(GRUB_slice, &sb) != 0) {
3100 		/*
3101 		 * 1. First get root disk name from mnttab
3102 		 * 2. Translate disk name to grub name
3103 		 * 3. Add the new menu entry
3104 		 */
3105 		rootdev = get_special("/");
3106 		if (rootdev) {
3107 			grubdisk = os_to_grubdisk(rootdev, 1);
3108 			free(rootdev);
3109 		}
3110 	} else {
3111 		/*
3112 		 * This is an LU BE. The GRUB_root file
3113 		 * contains entry for GRUB's "root" cmd.
3114 		 */
3115 		grubdisk = read_grub_root();
3116 	}
3117 	if (grubdisk == NULL) {
3118 		bam_error(REBOOT_WITH_ARGS_FAILED);
3119 		return (BAM_ERROR);
3120 	}
3121 
3122 	/* add an entry for Solaris reboot */
3123 	(void) snprintf(kernbuf, sizeof (kernbuf),
3124 	    "/platform/i86pc/multiboot %s", opt);
3125 	entry = add_boot_entry(mp, REBOOT_TITLE, grubdisk, kernbuf,
3126 	    "/platform/i86pc/boot_archive");
3127 	free(grubdisk);
3128 
3129 	if (entry == BAM_ERROR) {
3130 		bam_error(REBOOT_WITH_ARGS_FAILED);
3131 		return (BAM_ERROR);
3132 	}
3133 	(void) set_global(mp, menu_cmds[DEFAULT_CMD], entry);
3134 	return (BAM_WRITE);
3135 }
3136 
3137 static error_t
3138 set_global(menu_t *mp, char *globalcmd, int val)
3139 {
3140 	line_t *lp, *found, *last;
3141 	char *cp, *str;
3142 	char prefix[BAM_MAXLINE];
3143 	size_t len;
3144 
3145 	assert(mp);
3146 	assert(globalcmd);
3147 
3148 	if (strcmp(globalcmd, menu_cmds[DEFAULT_CMD]) == 0) {
3149 		if (val < 0 || mp->end == NULL || val > mp->end->entryNum) {
3150 			(void) snprintf(prefix, sizeof (prefix), "%d", val);
3151 			bam_error(INVALID_ENTRY, prefix);
3152 			return (BAM_ERROR);
3153 		}
3154 	}
3155 
3156 	found = last = NULL;
3157 	for (lp = mp->start; lp; lp = lp->next) {
3158 		if (lp->flags != BAM_GLOBAL)
3159 			continue;
3160 
3161 		last = lp; /* track the last global found */
3162 
3163 		if (lp->cmd == NULL) {
3164 			bam_error(NO_CMD, lp->lineNum);
3165 			continue;
3166 		}
3167 		if (strcmp(globalcmd, lp->cmd) != 0)
3168 			continue;
3169 
3170 		if (found) {
3171 			bam_error(DUP_CMD, globalcmd, lp->lineNum, bam_root);
3172 		}
3173 		found = lp;
3174 	}
3175 
3176 	if (found == NULL) {
3177 		lp = s_calloc(1, sizeof (line_t));
3178 		if (last == NULL) {
3179 			lp->next = mp->start;
3180 			mp->start = lp;
3181 			mp->end = (mp->end) ? mp->end : lp;
3182 		} else {
3183 			lp->next = last->next;
3184 			last->next = lp;
3185 			if (lp->next == NULL)
3186 				mp->end = lp;
3187 		}
3188 		lp->flags = BAM_GLOBAL; /* other fields not needed for writes */
3189 		len = strlen(globalcmd) + strlen(menu_cmds[SEP_CMD]);
3190 		len += 10;	/* val < 10 digits */
3191 		lp->line = s_calloc(1, len);
3192 		(void) snprintf(lp->line, len, "%s%s%d",
3193 		    globalcmd, menu_cmds[SEP_CMD], val);
3194 		return (BAM_WRITE);
3195 	}
3196 
3197 	/*
3198 	 * We are changing an existing entry. Retain any prefix whitespace,
3199 	 * but overwrite everything else. This preserves tabs added for
3200 	 * readability.
3201 	 */
3202 	str = found->line;
3203 	cp = prefix;
3204 	while (*str == ' ' || *str == '\t')
3205 		*(cp++) = *(str++);
3206 	*cp = '\0'; /* Terminate prefix */
3207 	len = strlen(prefix) + strlen(globalcmd);
3208 	len += strlen(menu_cmds[SEP_CMD]) + 10;
3209 
3210 	free(found->line);
3211 	found->line = s_calloc(1, len);
3212 	(void) snprintf(found->line, len,
3213 		"%s%s%s%d", prefix, globalcmd, menu_cmds[SEP_CMD], val);
3214 
3215 	return (BAM_WRITE); /* need a write to menu */
3216 }
3217 
3218 /*ARGSUSED*/
3219 static error_t
3220 set_option(menu_t *mp, char *menu_path, char *opt)
3221 {
3222 	int optnum, optval;
3223 	char *val;
3224 
3225 	assert(mp);
3226 	assert(opt);
3227 
3228 	val = strchr(opt, '=');
3229 	if (val == NULL) {
3230 		bam_error(INVALID_ENTRY, opt);
3231 		return (BAM_ERROR);
3232 	}
3233 
3234 	*val = '\0';
3235 	if (strcmp(opt, "default") == 0) {
3236 		optnum = DEFAULT_CMD;
3237 	} else if (strcmp(opt, "timeout") == 0) {
3238 		optnum = TIMEOUT_CMD;
3239 	} else {
3240 		bam_error(INVALID_ENTRY, opt);
3241 		return (BAM_ERROR);
3242 	}
3243 
3244 	optval = s_strtol(val + 1);
3245 	*val = '=';
3246 	return (set_global(mp, menu_cmds[optnum], optval));
3247 }
3248 
3249 /*
3250  * The quiet argument suppresses messages. This is used
3251  * when invoked in the context of other commands (e.g. list_entry)
3252  */
3253 static error_t
3254 read_globals(menu_t *mp, char *menu_path, char *globalcmd, int quiet)
3255 {
3256 	line_t *lp;
3257 	char *arg;
3258 	int done, ret = BAM_SUCCESS;
3259 
3260 	assert(mp);
3261 	assert(menu_path);
3262 	assert(globalcmd);
3263 
3264 	if (mp->start == NULL) {
3265 		if (!quiet)
3266 			bam_error(NO_MENU, menu_path);
3267 		return (BAM_ERROR);
3268 	}
3269 
3270 	done = 0;
3271 	for (lp = mp->start; lp; lp = lp->next) {
3272 		if (lp->flags != BAM_GLOBAL)
3273 			continue;
3274 
3275 		if (lp->cmd == NULL) {
3276 			if (!quiet)
3277 				bam_error(NO_CMD, lp->lineNum);
3278 			continue;
3279 		}
3280 
3281 		if (strcmp(globalcmd, lp->cmd) != 0)
3282 			continue;
3283 
3284 		/* Found global. Check for duplicates */
3285 		if (done && !quiet) {
3286 			bam_error(DUP_CMD, globalcmd, lp->lineNum, bam_root);
3287 			ret = BAM_ERROR;
3288 		}
3289 
3290 		arg = lp->arg ? lp->arg : "";
3291 		bam_print(GLOBAL_CMD, globalcmd, arg);
3292 		done = 1;
3293 	}
3294 
3295 	if (!done && bam_verbose)
3296 		bam_print(NO_ENTRY, globalcmd);
3297 
3298 	return (ret);
3299 }
3300 
3301 static error_t
3302 menu_write(char *root, menu_t *mp)
3303 {
3304 	return (list2file(root, MENU_TMP, GRUB_MENU, mp->start));
3305 }
3306 
3307 static void
3308 line_free(line_t *lp)
3309 {
3310 	if (lp == NULL)
3311 		return;
3312 
3313 	if (lp->cmd)
3314 		free(lp->cmd);
3315 	if (lp->sep)
3316 		free(lp->sep);
3317 	if (lp->arg)
3318 		free(lp->arg);
3319 	if (lp->line)
3320 		free(lp->line);
3321 	free(lp);
3322 }
3323 
3324 static void
3325 linelist_free(line_t *start)
3326 {
3327 	line_t *lp;
3328 
3329 	while (start) {
3330 		lp = start;
3331 		start = start->next;
3332 		line_free(lp);
3333 	}
3334 }
3335 
3336 static void
3337 filelist_free(filelist_t *flistp)
3338 {
3339 	linelist_free(flistp->head);
3340 	flistp->head = NULL;
3341 	flistp->tail = NULL;
3342 }
3343 
3344 static void
3345 menu_free(menu_t *mp)
3346 {
3347 	assert(mp);
3348 
3349 	if (mp->start)
3350 		linelist_free(mp->start);
3351 	free(mp);
3352 
3353 }
3354 
3355 /*
3356  * Utility routines
3357  */
3358 
3359 
3360 /*
3361  * Returns 0 on success
3362  * Any other value indicates an error
3363  */
3364 static int
3365 exec_cmd(char *cmdline, char *output, int64_t osize)
3366 {
3367 	char buf[BUFSIZ];
3368 	int ret;
3369 	FILE *ptr;
3370 	size_t len;
3371 	sigset_t set;
3372 	void (*disp)(int);
3373 
3374 	/*
3375 	 * For security
3376 	 * - only absolute paths are allowed
3377 	 * - set IFS to space and tab
3378 	 */
3379 	if (*cmdline != '/') {
3380 		bam_error(ABS_PATH_REQ, cmdline);
3381 		return (-1);
3382 	}
3383 	(void) putenv("IFS= \t");
3384 
3385 	/*
3386 	 * We may have been exec'ed with SIGCHLD blocked
3387 	 * unblock it here
3388 	 */
3389 	(void) sigemptyset(&set);
3390 	(void) sigaddset(&set, SIGCHLD);
3391 	if (sigprocmask(SIG_UNBLOCK, &set, NULL) != 0) {
3392 		bam_error(CANT_UNBLOCK_SIGCHLD, strerror(errno));
3393 		return (-1);
3394 	}
3395 
3396 	/*
3397 	 * Set SIGCHLD disposition to SIG_DFL for popen/pclose
3398 	 */
3399 	disp = sigset(SIGCHLD, SIG_DFL);
3400 	if (disp == SIG_ERR) {
3401 		bam_error(FAILED_SIG, strerror(errno));
3402 		return (-1);
3403 	}
3404 	if (disp == SIG_HOLD) {
3405 		bam_error(BLOCKED_SIG, cmdline);
3406 		return (-1);
3407 	}
3408 
3409 	ptr = popen(cmdline, "r");
3410 	if (ptr == NULL) {
3411 		bam_error(POPEN_FAIL, cmdline, strerror(errno));
3412 		return (-1);
3413 	}
3414 
3415 	/*
3416 	 * If we simply do a pclose() following a popen(), pclose()
3417 	 * will close the reader end of the pipe immediately even
3418 	 * if the child process has not started/exited. pclose()
3419 	 * does wait for cmd to terminate before returning though.
3420 	 * When the executed command writes its output to the pipe
3421 	 * there is no reader process and the command dies with
3422 	 * SIGPIPE. To avoid this we read repeatedly until read
3423 	 * terminates with EOF. This indicates that the command
3424 	 * (writer) has closed the pipe and we can safely do a
3425 	 * pclose().
3426 	 *
3427 	 * Since pclose() does wait for the command to exit,
3428 	 * we can safely reap the exit status of the command
3429 	 * from the value returned by pclose()
3430 	 */
3431 	while (fgets(buf, sizeof (buf), ptr) != NULL) {
3432 		/* if (bam_verbose)  XXX */
3433 			bam_print(PRINT_NO_NEWLINE, buf);
3434 		if (output && osize > 0) {
3435 			(void) snprintf(output, osize, "%s", buf);
3436 			len = strlen(buf);
3437 			output += len;
3438 			osize -= len;
3439 		}
3440 	}
3441 
3442 	ret = pclose(ptr);
3443 	if (ret == -1) {
3444 		bam_error(PCLOSE_FAIL, cmdline, strerror(errno));
3445 		return (-1);
3446 	}
3447 
3448 	if (WIFEXITED(ret)) {
3449 		return (WEXITSTATUS(ret));
3450 	} else {
3451 		bam_error(EXEC_FAIL, cmdline, ret);
3452 		return (-1);
3453 	}
3454 }
3455 
3456 /*
3457  * Since this function returns -1 on error
3458  * it cannot be used to convert -1. However,
3459  * that is sufficient for what we need.
3460  */
3461 static long
3462 s_strtol(char *str)
3463 {
3464 	long l;
3465 	char *res = NULL;
3466 
3467 	if (str == NULL) {
3468 		return (-1);
3469 	}
3470 
3471 	errno = 0;
3472 	l = strtol(str, &res, 10);
3473 	if (errno || *res != '\0') {
3474 		return (-1);
3475 	}
3476 
3477 	return (l);
3478 }
3479 
3480 /*
3481  * Wrapper around fputs, that adds a newline (since fputs doesn't)
3482  */
3483 static int
3484 s_fputs(char *str, FILE *fp)
3485 {
3486 	char linebuf[BAM_MAXLINE];
3487 
3488 	(void) snprintf(linebuf, sizeof (linebuf), "%s\n", str);
3489 	return (fputs(linebuf, fp));
3490 }
3491 
3492 /*
3493  * Wrapper around fgets, that strips newlines returned by fgets
3494  */
3495 static char *
3496 s_fgets(char *buf, int buflen, FILE *fp)
3497 {
3498 	int n;
3499 
3500 	buf = fgets(buf, buflen, fp);
3501 	if (buf) {
3502 		n = strlen(buf);
3503 		if (n == buflen - 1 && buf[n-1] != '\n')
3504 			bam_error(TOO_LONG, buflen - 1, buf);
3505 		buf[n-1] = (buf[n-1] == '\n') ? '\0' : buf[n-1];
3506 	}
3507 
3508 	return (buf);
3509 }
3510 
3511 static void *
3512 s_calloc(size_t nelem, size_t sz)
3513 {
3514 	void *ptr;
3515 
3516 	ptr = calloc(nelem, sz);
3517 	if (ptr == NULL) {
3518 		bam_error(NO_MEM, nelem*sz);
3519 		bam_exit(1);
3520 	}
3521 	return (ptr);
3522 }
3523 
3524 static char *
3525 s_strdup(char *str)
3526 {
3527 	char *ptr;
3528 
3529 	if (str == NULL)
3530 		return (NULL);
3531 
3532 	ptr = strdup(str);
3533 	if (ptr == NULL) {
3534 		bam_error(NO_MEM, strlen(str) + 1);
3535 		bam_exit(1);
3536 	}
3537 	return (ptr);
3538 }
3539 
3540 /*
3541  * Returns 1 if amd64 (or sparc, for syncing x86 diskless clients)
3542  * Returns 0 otherwise
3543  */
3544 static int
3545 is_amd64(void)
3546 {
3547 	static int amd64 = -1;
3548 	char isabuf[257];	/* from sysinfo(2) manpage */
3549 
3550 	if (amd64 != -1)
3551 		return (amd64);
3552 
3553 	if (sysinfo(SI_ISALIST, isabuf, sizeof (isabuf)) > 0 &&
3554 	    strncmp(isabuf, "amd64 ", strlen("amd64 ")) == 0)
3555 		amd64 = 1;
3556 	else if (strstr(isabuf, "i386") == NULL)
3557 		amd64 = 1;		/* diskless server */
3558 	else
3559 		amd64 = 0;
3560 
3561 	return (amd64);
3562 }
3563 
3564 static void
3565 append_to_flist(filelist_t *flistp, char *s)
3566 {
3567 	line_t *lp;
3568 
3569 	lp = s_calloc(1, sizeof (line_t));
3570 	lp->line = s_strdup(s);
3571 	if (flistp->head == NULL)
3572 		flistp->head = lp;
3573 	else
3574 		flistp->tail->next = lp;
3575 	flistp->tail = lp;
3576 }
3577