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