xref: /illumos-gate/usr/src/cmd/boot/bootadm/bootadm_upgrade.c (revision dd72704bd9e794056c558153663c739e2012d721)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  * Copyright 2017 Toomas Soome <tsoome@me.com>
25  */
26 
27 #include <stdio.h>
28 #include <errno.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <unistd.h>
32 #include <sys/types.h>
33 #include <sys/stat.h>
34 #include <limits.h>
35 #include <fcntl.h>
36 #include <strings.h>
37 
38 #include <sys/mman.h>
39 #include <sys/elf.h>
40 #include <sys/multiboot.h>
41 
42 #include "bootadm.h"
43 
44 direct_or_multi_t bam_direct = BAM_DIRECT_NOT_SET;
45 hv_t bam_is_hv = BAM_HV_UNKNOWN;
46 findroot_t bam_is_findroot = BAM_FINDROOT_UNKNOWN;
47 
48 static void
49 get_findroot_cap(const char *osroot)
50 {
51 	FILE		*fp;
52 	char		path[PATH_MAX];
53 	char		buf[BAM_MAXLINE];
54 	struct stat	sb;
55 	int		dboot;
56 	int		error;
57 	int		ret;
58 	const char	*fcn = "get_findroot_cap()";
59 
60 	(void) snprintf(path, sizeof (path), "%s/%s",
61 	    osroot, "boot/grub/capability");
62 
63 	if (stat(path, &sb) == -1) {
64 		bam_is_findroot = BAM_FINDROOT_ABSENT;
65 		BAM_DPRINTF(("%s: findroot capability absent\n", fcn));
66 		return;
67 	}
68 
69 	fp = fopen(path, "r");
70 	error = errno;
71 	INJECT_ERROR1("GET_CAP_FINDROOT_FOPEN", fp = NULL);
72 	if (fp == NULL) {
73 		bam_error(_("failed to open file: %s: %s\n"), path,
74 		    strerror(error));
75 		return;
76 	}
77 
78 	dboot = 0;
79 	while (s_fgets(buf, sizeof (buf), fp) != NULL) {
80 		if (strcmp(buf, "findroot") == 0) {
81 			BAM_DPRINTF(("%s: findroot capability present\n", fcn));
82 			bam_is_findroot = BAM_FINDROOT_PRESENT;
83 		}
84 		if (strcmp(buf, "dboot") == 0) {
85 			BAM_DPRINTF(("%s: dboot capability present\n", fcn));
86 			dboot = 1;
87 		}
88 	}
89 
90 	assert(dboot);
91 
92 	if (bam_is_findroot == BAM_FINDROOT_UNKNOWN) {
93 		bam_is_findroot = BAM_FINDROOT_ABSENT;
94 		BAM_DPRINTF(("%s: findroot capability absent\n", fcn));
95 	}
96 
97 	ret = fclose(fp);
98 	error = errno;
99 	INJECT_ERROR1("GET_CAP_FINDROOT_FCLOSE", ret = 1);
100 	if (ret != 0) {
101 		bam_error(_("failed to close file: %s: %s\n"),
102 		    path, strerror(error));
103 	}
104 }
105 
106 error_t
107 get_boot_cap(const char *osroot)
108 {
109 	char		fname[PATH_MAX];
110 	char		*image;
111 	uchar_t		*ident;
112 	uchar_t		class;
113 	int		fd;
114 	int		m;
115 	multiboot_header_t *mbh;
116 	struct stat	sb;
117 	int		error;
118 	const char	*fcn = "get_boot_cap()";
119 
120 	if (is_sparc()) {
121 		/* there is no non dboot sparc new-boot */
122 		bam_direct = BAM_DIRECT_DBOOT;
123 		BAM_DPRINTF(("%s: is sparc - always DBOOT\n", fcn));
124 		return (BAM_SUCCESS);
125 	}
126 
127 	/*
128 	 * The install media can support both 64 and 32 bit boot
129 	 * by using boot archive as ramdisk image. However, to save
130 	 * the memory, the ramdisk may only have either 32 or 64
131 	 * bit kernel files. To avoid error message about missing unix,
132 	 * we should try both variants here and only complain if neither
133 	 * is found. Since the 64-bit systems are more common, we start
134 	 * from amd64.
135 	 */
136 	class = ELFCLASS64;
137 	(void) snprintf(fname, PATH_MAX, "%s/%s", osroot,
138 	    "platform/i86pc/kernel/amd64/unix");
139 	fd = open(fname, O_RDONLY);
140 	if (fd < 0) {
141 		class = ELFCLASS32;
142 		(void) snprintf(fname, PATH_MAX, "%s/%s", osroot,
143 		    "platform/i86pc/kernel/unix");
144 		fd = open(fname, O_RDONLY);
145 	}
146 	error = errno;
147 	INJECT_ERROR1("GET_CAP_UNIX_OPEN", fd = -1);
148 	if (fd < 0) {
149 		bam_error(_("failed to open file: %s: %s\n"), fname,
150 		    strerror(error));
151 		return (BAM_ERROR);
152 	}
153 
154 	/*
155 	 * Verify that this is a sane unix at least 8192 bytes in length
156 	 */
157 	if (fstat(fd, &sb) == -1 || sb.st_size < 8192) {
158 		(void) close(fd);
159 		bam_error(_("invalid or corrupted binary: %s\n"), fname);
160 		return (BAM_ERROR);
161 	}
162 
163 	/*
164 	 * mmap the first 8K
165 	 */
166 	image = mmap(NULL, 8192, PROT_READ, MAP_SHARED, fd, 0);
167 	error = errno;
168 	INJECT_ERROR1("GET_CAP_MMAP", image = MAP_FAILED);
169 	if (image == MAP_FAILED) {
170 		bam_error(_("failed to mmap file: %s: %s\n"), fname,
171 		    strerror(error));
172 		return (BAM_ERROR);
173 	}
174 
175 	ident = (uchar_t *)image;
176 	if (ident[EI_MAG0] != ELFMAG0 || ident[EI_MAG1] != ELFMAG1 ||
177 	    ident[EI_MAG2] != ELFMAG2 || ident[EI_MAG3] != ELFMAG3) {
178 		bam_error(_("%s is not an ELF file.\n"), fname);
179 		return (BAM_ERROR);
180 	}
181 	if (ident[EI_CLASS] != class) {
182 		bam_error(_("%s is wrong ELF class 0x%x\n"), fname,
183 		    ident[EI_CLASS]);
184 		return (BAM_ERROR);
185 	}
186 
187 	/*
188 	 * The GRUB multiboot header must be 32-bit aligned and completely
189 	 * contained in the 1st 8K of the file.  If the unix binary has
190 	 * a multiboot header, then it is a 'dboot' kernel.  Otherwise,
191 	 * this kernel must be booted via multiboot -- we call this a
192 	 * 'multiboot' kernel.
193 	 */
194 	bam_direct = BAM_DIRECT_MULTIBOOT;
195 	for (m = 0; m < 8192 - sizeof (multiboot_header_t); m += 4) {
196 		mbh = (void *)(image + m);
197 		if (mbh->magic == MB_HEADER_MAGIC) {
198 			BAM_DPRINTF(("%s: is DBOOT unix\n", fcn));
199 			bam_direct = BAM_DIRECT_DBOOT;
200 			break;
201 		}
202 	}
203 	(void) munmap(image, 8192);
204 	(void) close(fd);
205 
206 	INJECT_ERROR1("GET_CAP_MULTIBOOT", bam_direct = BAM_DIRECT_MULTIBOOT);
207 	if (bam_direct == BAM_DIRECT_DBOOT) {
208 		if (bam_is_hv == BAM_HV_PRESENT) {
209 			BAM_DPRINTF(("%s: is xVM system\n", fcn));
210 		} else {
211 			BAM_DPRINTF(("%s: is *NOT* xVM system\n", fcn));
212 		}
213 	} else {
214 		BAM_DPRINTF(("%s: is MULTIBOOT unix\n", fcn));
215 	}
216 
217 	/* Not a fatal error if this fails */
218 	get_findroot_cap(osroot);
219 
220 	BAM_DPRINTF(("%s: returning SUCCESS\n", fcn));
221 	return (BAM_SUCCESS);
222 }
223 
224 #define	INST_RELEASE	"var/sadm/system/admin/INST_RELEASE"
225 
226 /*
227  * Return true if root has been bfu'ed.  bfu will blow away
228  * var/sadm/system/admin/INST_RELEASE, so if it's still there, we can
229  * assume the system has not been bfu'ed.
230  */
231 static int
232 is_bfu_system(const char *root)
233 {
234 	static int		is_bfu = -1;
235 	char			path[PATH_MAX];
236 	struct stat		sb;
237 	const char		*fcn = "is_bfu_system()";
238 
239 	if (is_bfu != -1) {
240 		BAM_DPRINTF(("%s: already done bfu test. bfu is %s present\n",
241 		    fcn, is_bfu ? "" : "NOT"));
242 		return (is_bfu);
243 	}
244 
245 	(void) snprintf(path, sizeof (path), "%s/%s", root, INST_RELEASE);
246 	if (stat(path, &sb) != 0) {
247 		is_bfu = 1;
248 		BAM_DPRINTF(("%s: returning SUCCESS\n", fcn));
249 	} else {
250 		is_bfu = 0;
251 		BAM_DPRINTF(("%s: returning FAILURE\n", fcn));
252 	}
253 	return (is_bfu);
254 }
255 
256 #define	MENU_URL(root)	(is_bfu_system(root) ?		\
257 	"http://illumos.org/msg/SUNOS-8000-CF" :	\
258 	"http://illumos.org/msg/SUNOS-8000-AK")
259 
260 /*
261  * Simply allocate a new line and copy in cmd + sep + arg
262  */
263 void
264 update_line(line_t *linep)
265 {
266 	size_t		size;
267 	const char	*fcn = "update_line()";
268 
269 	BAM_DPRINTF(("%s: line before update: %s\n", fcn, linep->line));
270 	free(linep->line);
271 	size = strlen(linep->cmd) + strlen(linep->sep) + strlen(linep->arg) + 1;
272 	linep->line = s_calloc(1, size);
273 	(void) snprintf(linep->line, size, "%s%s%s", linep->cmd, linep->sep,
274 	    linep->arg);
275 	BAM_DPRINTF(("%s: line after update: %s\n", fcn, linep->line));
276 }
277 
278 static char *
279 skip_wspace(char *ptr)
280 {
281 	const char		*fcn = "skip_wspace()";
282 
283 	INJECT_ERROR1("SKIP_WSPACE", ptr = NULL);
284 	if (ptr == NULL) {
285 		BAM_DPRINTF(("%s: NULL ptr\n", fcn));
286 		return (NULL);
287 	}
288 
289 	BAM_DPRINTF(("%s: ptr on entry: %s\n", fcn, ptr));
290 	for (; *ptr != '\0'; ptr++) {
291 		if ((*ptr != ' ') && (*ptr != '\t') &&
292 		    (*ptr != '\n'))
293 			break;
294 	}
295 
296 	ptr = (*ptr == '\0' ? NULL : ptr);
297 
298 	BAM_DPRINTF(("%s: ptr on exit: %s\n", fcn, ptr ? ptr : "NULL"));
299 
300 	return (ptr);
301 }
302 
303 static char *
304 rskip_bspace(char *bound, char *ptr)
305 {
306 	const char		*fcn = "rskip_bspace()";
307 	assert(bound);
308 	assert(ptr);
309 	assert(bound <= ptr);
310 	assert(*bound != ' ' && *bound != '\t' && *bound != '\n');
311 
312 	BAM_DPRINTF(("%s: ptr on entry: %s\n", fcn, ptr));
313 	for (; ptr > bound; ptr--) {
314 		if (*ptr == ' ' || *ptr == '\t' || *ptr == '\n')
315 			break;
316 	}
317 
318 	BAM_DPRINTF(("%s: ptr on exit: %s\n", fcn, ptr));
319 	return (ptr);
320 }
321 
322 /*
323  * The parse_kernel_line function examines a menu.lst kernel line.  For
324  * multiboot, this is:
325  *
326  * kernel <multiboot path> <flags1> <kernel path> <flags2>
327  *
328  * <multiboot path> is either /platform/i86pc/multiboot or /boot/multiboot
329  *
330  * <kernel path> may be missing, or may be any full or relative path to unix.
331  *	We check for it by looking for a word ending in "/unix".  If it ends
332  *	in "kernel/unix", we upgrade it to a 32-bit entry.  If it ends in
333  *	"kernel/amd64/unix", we upgrade it to the default entry.  Otherwise,
334  *	it's a custom kernel, and we skip it.
335  *
336  * <flags*> are anything that doesn't fit either of the above - these will be
337  *	copied over.
338  *
339  * For direct boot, the defaults are
340  *
341  * kernel$ <kernel path> <flags>
342  *
343  * <kernel path> is one of:
344  *	/platform/i86pc/kernel/$ISADIR/unix
345  *	/boot/platform/i86pc/kernel/$ISADIR/unix
346  *	/platform/i86pc/kernel/unix
347  *	/platform/i86pc/kernel/amd64/unix
348  *	/boot/platform/i86pc/kernel/unix
349  *	/boot/platform/i86pc/kernel/amd64/unix
350  *
351  * If <kernel path> is any of the last four, the command may also be "kernel".
352  *
353  * <flags> is anything that isn't <kernel path>.
354  *
355  * This function is only called to convert a multiboot entry to a dboot entry
356  *
357  * For safety, we do one more check: if the kernel path starts with /boot,
358  * we verify that the new kernel exists before changing it.  This is mainly
359  * done for bfu, as it may cause the failsafe archives to be a different
360  * boot architecture from the newly bfu'ed system.
361  */
362 static error_t
363 cvt_kernel_line(line_t *line, const char *osroot, entry_t *entry)
364 {
365 	char		path[PATH_MAX], path_64[PATH_MAX];
366 	char		linebuf[PATH_MAX];
367 	char		new_arg[PATH_MAX];
368 	struct stat	sb, sb_64;
369 	char		*old_ptr;
370 	char		*unix_ptr;
371 	char		*flags1_ptr;
372 	char		*flags2_ptr;
373 	const char	*fcn = "cvt_kernel_line()";
374 
375 	BAM_DPRINTF(("%s: entered. args: %s %s\n", fcn, line->line, osroot));
376 
377 	/*
378 	 * We only convert multiboot to dboot and nothing else.
379 	 */
380 	if (!(entry->flags & BAM_ENTRY_MULTIBOOT)) {
381 		BAM_DPRINTF(("%s: not MULTIBOOT, not converting\n", fcn));
382 		return (BAM_SUCCESS);
383 	}
384 
385 	if (entry->flags & BAM_ENTRY_FAILSAFE) {
386 		/*
387 		 * We're attempting to change failsafe to dboot.
388 		 * In the bfu case, we may not have a dboot failsafe
389 		 * kernel i.e. a "unix" under the "/boot" hierarchy.
390 		 * If so, just emit a message in verbose mode and
391 		 * return success.
392 		 */
393 		BAM_DPRINTF(("%s: trying to convert failsafe to DBOOT\n", fcn));
394 		(void) snprintf(path, PATH_MAX, "%s%s", osroot,
395 		    DIRECT_BOOT_FAILSAFE_32);
396 		(void) snprintf(path_64, PATH_MAX, "%s%s", osroot,
397 		    DIRECT_BOOT_FAILSAFE_64);
398 		if (stat(path, &sb) != 0 && stat(path_64, &sb_64) != 0) {
399 			if (bam_verbose) {
400 				bam_error(_("bootadm -m upgrade run, but the "
401 				    "failsafe archives have not been\nupdated. "
402 				    "Not updating line %d\n"), line->lineNum);
403 			}
404 			BAM_DPRINTF(("%s: no FAILSAFE unix, not converting\n",
405 			    fcn));
406 			return (BAM_SUCCESS);
407 		}
408 	}
409 
410 	/*
411 	 * Make sure we have the correct cmd
412 	 */
413 
414 	free(line->cmd);
415 	line->cmd = s_strdup(menu_cmds[KERNEL_DOLLAR_CMD]);
416 	BAM_DPRINTF(("%s: converted kernel cmd to %s\n", fcn, line->cmd));
417 
418 	assert(sizeof (linebuf) > strlen(line->arg) + 32);
419 	(void) strlcpy(linebuf, line->arg, sizeof (linebuf));
420 
421 	old_ptr = strpbrk(linebuf, " \t\n");
422 	old_ptr = skip_wspace(old_ptr);
423 	if (old_ptr == NULL) {
424 		/*
425 		 * only multiboot and nothing else
426 		 * i.e. flags1 = unix = flags2 = NULL
427 		 */
428 		flags1_ptr = unix_ptr = flags2_ptr = NULL;
429 		BAM_DPRINTF(("%s: NULL flags1, unix, flags2\n", fcn))
430 		goto create;
431 	}
432 
433 	/*
434 	 *
435 	 * old_ptr is either at "flags1" or "unix"
436 	 */
437 	if ((unix_ptr = strstr(old_ptr, "/unix")) != NULL) {
438 
439 		/*
440 		 * There is a  unix.
441 		 */
442 		BAM_DPRINTF(("%s: unix present\n", fcn));
443 
444 		/* See if there's a flags2 past unix */
445 		flags2_ptr = unix_ptr + strlen("/unix");
446 		flags2_ptr = skip_wspace(flags2_ptr);
447 		if (flags2_ptr) {
448 			BAM_DPRINTF(("%s: flags2 present: %s\n", fcn,
449 			    flags2_ptr));
450 		} else {
451 			BAM_DPRINTF(("%s: flags2 absent\n", fcn));
452 		}
453 
454 		/* see if there is a flags1 before unix */
455 		unix_ptr = rskip_bspace(old_ptr, unix_ptr);
456 
457 		if (unix_ptr == old_ptr) {
458 			flags1_ptr = NULL;
459 			BAM_DPRINTF(("%s: flags1 absent\n", fcn));
460 		} else {
461 			flags1_ptr = old_ptr;
462 			*unix_ptr = '\0';
463 			unix_ptr++;
464 			BAM_DPRINTF(("%s: flags1 present: %s\n", fcn,
465 			    flags1_ptr));
466 		}
467 
468 	} else  {
469 		/* There is no unix, there is only a bunch of flags */
470 		flags1_ptr = old_ptr;
471 		unix_ptr = flags2_ptr = NULL;
472 		BAM_DPRINTF(("%s: flags1 present: %s, unix, flags2 absent\n",
473 		    fcn, flags1_ptr));
474 	}
475 
476 	/*
477 	 * With dboot, unix is fixed and is at the beginning. We need to
478 	 * migrate flags1 and flags2
479 	 */
480 create:
481 	if (entry->flags & BAM_ENTRY_FAILSAFE) {
482 		(void) snprintf(new_arg, sizeof (new_arg), "%s",
483 		    DIRECT_BOOT_FAILSAFE_KERNEL);
484 	} else {
485 		(void) snprintf(new_arg, sizeof (new_arg), "%s",
486 		    DIRECT_BOOT_KERNEL);
487 	}
488 	BAM_DPRINTF(("%s: converted unix: %s\n", fcn, new_arg));
489 
490 	if (flags1_ptr != NULL) {
491 		(void) strlcat(new_arg, " ", sizeof (new_arg));
492 		(void) strlcat(new_arg, flags1_ptr, sizeof (new_arg));
493 	}
494 
495 	if (flags2_ptr != NULL) {
496 		(void) strlcat(new_arg, " ", sizeof (new_arg));
497 		(void) strlcat(new_arg, flags2_ptr, sizeof (new_arg));
498 	}
499 
500 	BAM_DPRINTF(("%s: converted unix with flags : %s\n", fcn, new_arg));
501 
502 	free(line->arg);
503 	line->arg = s_strdup(new_arg);
504 	update_line(line);
505 	BAM_DPRINTF(("%s: converted line is: %s\n", fcn, line->line));
506 	return (BAM_SUCCESS);
507 }
508 
509 /*
510  * Similar to above, except this time we're looking at a module line,
511  * which is quite a bit simpler.
512  *
513  * Under multiboot, the archive line is:
514  *
515  * module /platform/i86pc/boot_archive
516  *
517  * Under directboot, the archive line is:
518  *
519  * module$ /platform/i86pc/$ISADIR/boot_archive
520  *
521  * which may be specified exactly as either of:
522  *
523  * module /platform/i86pc/boot_archive
524  * module /platform/i86pc/amd64/boot_archive
525  *
526  * Under multiboot, the failsafe is:
527  *
528  * module /boot/x86.miniroot-safe
529  *
530  * Under dboot, the failsafe is:
531  *
532  * module$ /boot/$ISADIR/x86.miniroot-safe
533  *
534  * which may be specified exactly as either of:
535  *
536  * module /boot/x86.miniroot-safe
537  * module /boot/amd64/x86.miniroot-safe
538  */
539 static error_t
540 cvt_module_line(line_t *line, entry_t *entry)
541 {
542 	const char		*fcn = "cvt_module_line()";
543 
544 	BAM_DPRINTF(("%s: entered. arg: %s\n", fcn, line->line));
545 
546 	/*
547 	 * We only convert multiboot to dboot and nothing else
548 	 */
549 	if (!(entry->flags & BAM_ENTRY_MULTIBOOT)) {
550 		BAM_DPRINTF(("%s: not MULTIBOOT, not converting\n", fcn));
551 		return (BAM_SUCCESS);
552 	}
553 
554 	if (entry->flags & BAM_ENTRY_FAILSAFE) {
555 		if (strcmp(line->arg, FAILSAFE_ARCHIVE) == 0) {
556 			BAM_DPRINTF(("%s: failsafe module line needs no "
557 			    "conversion: %s\n", fcn, line->arg));
558 			BAM_DPRINTF(("%s: returning SUCCESS\n", fcn));
559 			return (BAM_SUCCESS);
560 		}
561 	} else if (strcmp(line->arg, MULTIBOOT_ARCHIVE) != 0) {
562 		bam_error(_("module command on line %d not recognized.\n"),
563 		    line->lineNum);
564 		BAM_DPRINTF(("%s: returning FAILURE\n", fcn));
565 		return (BAM_MSG);
566 	}
567 
568 	free(line->cmd);
569 	free(line->arg);
570 	line->cmd = s_strdup(menu_cmds[MODULE_DOLLAR_CMD]);
571 
572 	line->arg = s_strdup(entry->flags & BAM_ENTRY_FAILSAFE ?
573 	    FAILSAFE_ARCHIVE : DIRECT_BOOT_ARCHIVE);
574 
575 	update_line(line);
576 	BAM_DPRINTF(("%s: converted module line is: %s\n", fcn, line->line));
577 	BAM_DPRINTF(("%s: returning SUCCESS\n", fcn));
578 	return (BAM_SUCCESS);
579 }
580 
581 static void
582 bam_warn_hand_entries(menu_t *mp, char *osroot)
583 {
584 	int		hand_num;
585 	int		hand_max;
586 	int		*hand_list;
587 	int		i;
588 	entry_t		*entry;
589 	const char	*fcn = "bam_warn_hand_entries()";
590 
591 	if (bam_force) {
592 		/*
593 		 * No warning needed, we are automatically converting
594 		 * the "hand" entries
595 		 */
596 		BAM_DPRINTF(("%s: force specified, no warnings about hand "
597 		    "entries\n",  fcn));
598 		return;
599 	}
600 
601 	hand_num = 0;
602 	hand_max = BAM_ENTRY_NUM;
603 	hand_list = s_calloc(1, hand_max);
604 
605 	for (entry = mp->entries; entry; entry = entry->next) {
606 		if (entry->flags & (BAM_ENTRY_BOOTADM|BAM_ENTRY_LU))
607 			continue;
608 		BAM_DPRINTF(("%s: found hand entry #: %d\n", fcn,
609 		    entry->entryNum));
610 		if (++hand_num > hand_max) {
611 			hand_max *= 2;
612 			hand_list = s_realloc(hand_list,
613 			    hand_max * sizeof (int));
614 		}
615 		hand_list[hand_num - 1] = entry->entryNum;
616 	}
617 
618 	bam_error(_("bootadm(8) will only upgrade GRUB menu entries added "
619 	    "by \nbootadm(8) or lu(8). The following entries on %s will "
620 	    "not be upgraded.\nFor details on manually updating entries, "
621 	    "see %s\n"), osroot, MENU_URL(osroot));
622 	bam_print_stderr("Entry Number%s: ", (hand_num > 1) ?
623 	    "s" : "");
624 	for (i = 0; i < hand_num; i++) {
625 		bam_print_stderr("%d ", hand_list[i]);
626 	}
627 	bam_print_stderr("\n");
628 }
629 
630 static entry_t *
631 find_matching_entry(
632 	entry_t *estart,
633 	char *grubsign,
634 	char *grubroot,
635 	int root_opt)
636 {
637 	entry_t		*entry;
638 	line_t		*line;
639 	char		opt[10];
640 	const char	*fcn = "find_matching_entry()";
641 
642 	assert(grubsign);
643 	assert(root_opt == 0 || root_opt == 1);
644 
645 	(void) snprintf(opt, sizeof (opt), "%d", root_opt);
646 	BAM_DPRINTF(("%s: entered. args: %s %s %s\n", fcn, grubsign,
647 	    grubroot, opt));
648 
649 	for (entry = estart; entry; entry = entry->next) {
650 
651 		if (!(entry->flags & (BAM_ENTRY_BOOTADM|BAM_ENTRY_LU)) &&
652 		    !bam_force) {
653 			BAM_DPRINTF(("%s: skipping hand entry #: %d\n",
654 			    fcn, entry->entryNum));
655 			continue;
656 		}
657 
658 		if (entry->flags & BAM_ENTRY_ROOT) {
659 			for (line = entry->start; line; line = line->next) {
660 				if (line->cmd == NULL || line->arg == NULL) {
661 					if (line == entry->end) {
662 						BAM_DPRINTF(("%s: entry has "
663 						    "ended\n", fcn));
664 						break;
665 					} else {
666 						BAM_DPRINTF(("%s: skipping "
667 						    "NULL line\n", fcn));
668 						continue;
669 					}
670 				}
671 				if (strcmp(line->cmd, menu_cmds[ROOT_CMD])
672 				    == 0 && strcmp(line->arg, grubroot) == 0) {
673 					BAM_DPRINTF(("%s: found matching root "
674 					    "line: %s,%s\n", fcn,
675 					    line->line, grubsign));
676 					return (entry);
677 				}
678 				if (line == entry->end) {
679 					BAM_DPRINTF(("%s: entry has ended\n",
680 					    fcn));
681 					break;
682 				}
683 			}
684 		} else if (entry->flags & BAM_ENTRY_FINDROOT) {
685 			for (line = entry->start; line; line = line->next) {
686 				if (line->cmd == NULL || line->arg == NULL) {
687 					if (line == entry->end) {
688 						BAM_DPRINTF(("%s: entry has "
689 						    "ended\n", fcn));
690 						break;
691 					} else {
692 						BAM_DPRINTF(("%s: skipping "
693 						    "NULL line\n", fcn));
694 						continue;
695 					}
696 				}
697 				if (strcmp(line->cmd, menu_cmds[FINDROOT_CMD])
698 				    == 0 && strcmp(line->arg, grubsign) == 0) {
699 					BAM_DPRINTF(("%s: found matching "
700 					    "findroot line: %s,%s\n", fcn,
701 					    line->line, grubsign));
702 					return (entry);
703 				}
704 				if (line == entry->end) {
705 					BAM_DPRINTF(("%s: entry has ended\n",
706 					    fcn));
707 					break;
708 				}
709 			}
710 		} else if (root_opt) {
711 			/* Neither root nor findroot */
712 			BAM_DPRINTF(("%s: no root or findroot and root is "
713 			    "opt: %d\n", fcn, entry->entryNum));
714 			return (entry);
715 		}
716 	}
717 
718 	BAM_DPRINTF(("%s: no matching entry found\n", fcn));
719 	return (NULL);
720 }
721 
722 /*
723  * The following is a set of routines that attempt to convert the
724  * menu entries for the supplied osroot into a format compatible
725  * with the GRUB installation on osroot.
726  *
727  * Each of these conversion routines make no assumptions about
728  * the current state of the menu entry, it does its best to
729  * convert the menu entry to the new state. In the process
730  * we may either upgrade or downgrade.
731  *
732  * We don't make any heroic efforts at conversion. It is better
733  * to be conservative and bail out at the first sign of error. We will
734  * in such cases, point the user at the knowledge-base article
735  * so that they can upgrade manually.
736  */
737 static error_t
738 bam_add_findroot(menu_t *mp, char *grubsign, char *grubroot, int root_opt)
739 {
740 	entry_t		*entry;
741 	line_t		*line;
742 	line_t		*newlp;
743 	int		update_num;
744 	char		linebuf[PATH_MAX];
745 	const char	*fcn = "bam_add_findroot()";
746 
747 	update_num = 0;
748 
749 	bam_print(_("converting entries to findroot...\n"));
750 
751 	entry = find_matching_entry(mp->entries, grubsign, grubroot, root_opt);
752 	while (entry != NULL) {
753 		if (entry->flags & BAM_ENTRY_FINDROOT) {
754 			/* already converted */
755 			BAM_DPRINTF(("%s: entry %d already converted to "
756 			    "findroot\n", fcn, entry->entryNum));
757 			entry = find_matching_entry(entry->next, grubsign,
758 			    grubroot, root_opt);
759 			continue;
760 		}
761 		for (line = entry->start; line; line = line->next) {
762 			if (line->cmd == NULL || line->arg == NULL) {
763 				if (line == entry->end) {
764 					BAM_DPRINTF(("%s: entry has ended\n",
765 					    fcn));
766 					break;
767 				} else {
768 					BAM_DPRINTF(("%s: skipping NULL line\n",
769 					    fcn));
770 					continue;
771 				}
772 			}
773 			if (strcmp(line->cmd, menu_cmds[TITLE_CMD]) == 0) {
774 				newlp = s_calloc(1, sizeof (line_t));
775 				newlp->cmd = s_strdup(menu_cmds[FINDROOT_CMD]);
776 				newlp->sep = s_strdup(" ");
777 				newlp->arg = s_strdup(grubsign);
778 				(void) snprintf(linebuf, sizeof (linebuf),
779 				    "%s%s%s", newlp->cmd, newlp->sep,
780 				    newlp->arg);
781 				newlp->line = s_strdup(linebuf);
782 				bam_add_line(mp, entry, line, newlp);
783 				update_num = 1;
784 				entry->flags &= ~BAM_ENTRY_ROOT;
785 				entry->flags |= BAM_ENTRY_FINDROOT;
786 				BAM_DPRINTF(("%s: added findroot line: %s\n",
787 				    fcn, newlp->line));
788 				line = newlp;
789 			}
790 			if (strcmp(line->cmd, menu_cmds[ROOT_CMD]) == 0) {
791 				BAM_DPRINTF(("%s: freeing root line: %s\n",
792 				    fcn, line->line));
793 				unlink_line(mp, line);
794 				line_free(line);
795 			}
796 			if (line == entry->end) {
797 				BAM_DPRINTF(("%s: entry has ended\n", fcn));
798 				break;
799 			}
800 		}
801 		entry = find_matching_entry(entry->next, grubsign, grubroot,
802 		    root_opt);
803 	}
804 
805 	if (update_num) {
806 		BAM_DPRINTF(("%s: updated numbering\n", fcn));
807 		update_numbering(mp);
808 	}
809 
810 	BAM_DPRINTF(("%s: returning SUCCESS\n", fcn));
811 	return (BAM_SUCCESS);
812 }
813 
814 static error_t
815 bam_add_hv(menu_t *mp, char *grubsign, char *grubroot, int root_opt)
816 {
817 	entry_t		*entry;
818 	const char	*fcn = "bam_add_hv()";
819 
820 	bam_print(_("adding xVM entries...\n"));
821 
822 	entry = find_matching_entry(mp->entries, grubsign, grubroot, root_opt);
823 	while (entry != NULL) {
824 		if (entry->flags & BAM_ENTRY_HV) {
825 			BAM_DPRINTF(("%s: entry %d already converted to "
826 			    "xvm HV\n", fcn, entry->entryNum));
827 			return (BAM_SUCCESS);
828 		}
829 		entry = find_matching_entry(entry->next, grubsign, grubroot,
830 		    root_opt);
831 	}
832 
833 	(void) add_boot_entry(mp, NEW_HV_ENTRY, grubsign, XEN_MENU,
834 	    XEN_KERNEL_MODULE_LINE, DIRECT_BOOT_ARCHIVE, NULL);
835 
836 	BAM_DPRINTF(("%s: added xVM HV entry via add_boot_entry()\n", fcn));
837 
838 	update_numbering(mp);
839 
840 	BAM_DPRINTF(("%s: returning SUCCESS\n", fcn));
841 
842 	return (BAM_SUCCESS);
843 }
844 
845 static error_t
846 bam_add_dboot(
847 	menu_t *mp,
848 	char *osroot,
849 	char *grubsign,
850 	char *grubroot,
851 	int root_opt)
852 {
853 	int		msg = 0;
854 	entry_t		*entry;
855 	line_t		*line;
856 	error_t		ret;
857 	const char 	*fcn = "bam_add_dboot()";
858 
859 	bam_print(_("converting entries to dboot...\n"));
860 
861 	entry = find_matching_entry(mp->entries, grubsign, grubroot, root_opt);
862 	while (entry != NULL) {
863 		for (line = entry->start; line; line = line->next) {
864 			if (line->cmd == NULL || line->arg == NULL) {
865 				if (line == entry->end) {
866 					BAM_DPRINTF(("%s: entry has ended\n",
867 					    fcn));
868 					break;
869 				} else {
870 					BAM_DPRINTF(("%s: skipping NULL line\n",
871 					    fcn));
872 					continue;
873 				}
874 			}
875 
876 			/*
877 			 * If we have a kernel$ command, assume it
878 			 * is dboot already.  If it is not a dboot
879 			 * entry, something funny is going on and
880 			 * we will leave it alone
881 			 */
882 			if (strcmp(line->cmd, menu_cmds[KERNEL_CMD]) == 0) {
883 				ret = cvt_kernel_line(line, osroot, entry);
884 				INJECT_ERROR1("ADD_DBOOT_KERN_ERR",
885 				    ret = BAM_ERROR);
886 				INJECT_ERROR1("ADD_DBOOT_KERN_MSG",
887 				    ret = BAM_MSG);
888 				if (ret == BAM_ERROR) {
889 					BAM_DPRINTF(("%s: cvt_kernel_line() "
890 					    "failed\n", fcn));
891 					return (ret);
892 				} else if (ret == BAM_MSG) {
893 					msg = 1;
894 					BAM_DPRINTF(("%s: BAM_MSG returned "
895 					    "from cvt_kernel_line()\n", fcn));
896 				}
897 			}
898 			if (strcmp(line->cmd, menu_cmds[MODULE_CMD]) == 0) {
899 				ret = cvt_module_line(line, entry);
900 				INJECT_ERROR1("ADD_DBOOT_MOD_ERR",
901 				    ret = BAM_ERROR);
902 				INJECT_ERROR1("ADD_DBOOT_MOD_MSG",
903 				    ret = BAM_MSG);
904 				if (ret == BAM_ERROR) {
905 					BAM_DPRINTF(("%s: cvt_module_line() "
906 					    "failed\n", fcn));
907 					return (ret);
908 				} else if (ret == BAM_MSG) {
909 					BAM_DPRINTF(("%s: BAM_MSG returned "
910 					    "from cvt_module_line()\n", fcn));
911 					msg = 1;
912 				}
913 			}
914 
915 			if (line == entry->end) {
916 				BAM_DPRINTF(("%s: entry has ended\n", fcn));
917 				break;
918 			}
919 		}
920 		entry = find_matching_entry(entry->next, grubsign, grubroot,
921 		    root_opt);
922 	}
923 
924 	ret = msg ? BAM_MSG : BAM_SUCCESS;
925 	BAM_DPRINTF(("%s: returning ret = %d\n", fcn, ret));
926 	return (ret);
927 }
928 
929 /*ARGSUSED*/
930 error_t
931 upgrade_menu(menu_t *mp, char *osroot, char *menu_root)
932 {
933 	char		*osdev;
934 	char		*grubsign;
935 	char		*grubroot;
936 	int		ret1;
937 	int		ret2;
938 	int		ret3;
939 	const char	*fcn = "upgrade_menu()";
940 
941 	assert(osroot);
942 	assert(menu_root);
943 
944 	BAM_DPRINTF(("%s: entered. args: %s %s\n", fcn, osroot, menu_root));
945 
946 	/*
947 	 * We only support upgrades. Xen may not be present
948 	 * on smaller metaclusters so we don't check for that.
949 	 */
950 	if (bam_is_findroot != BAM_FINDROOT_PRESENT ||
951 	    bam_direct != BAM_DIRECT_DBOOT) {
952 		bam_error(_("automated downgrade of GRUB menu to older "
953 		    "version not supported.\n"));
954 		return (BAM_ERROR);
955 	}
956 
957 	/*
958 	 * First get the GRUB signature
959 	 */
960 	osdev = get_special(osroot);
961 	INJECT_ERROR1("UPGRADE_OSDEV", osdev = NULL);
962 	if (osdev == NULL) {
963 		bam_error(_("cant find special file for mount-point %s\n"),
964 		    osroot);
965 		return (BAM_ERROR);
966 	}
967 
968 	grubsign = get_grubsign(osroot, osdev);
969 	INJECT_ERROR1("UPGRADE_GRUBSIGN", grubsign = NULL);
970 	if (grubsign == NULL) {
971 		free(osdev);
972 		bam_error(_("cannot find GRUB signature for %s\n"), osroot);
973 		return (BAM_ERROR);
974 	}
975 
976 	/* not fatal if we can't get grubroot */
977 	grubroot = get_grubroot(osroot, osdev, menu_root);
978 	INJECT_ERROR1("UPGRADE_GRUBROOT", grubroot = NULL);
979 
980 	free(osdev);
981 
982 	ret1 = bam_add_findroot(mp, grubsign,
983 	    grubroot, root_optional(osroot, menu_root));
984 	INJECT_ERROR1("UPGRADE_ADD_FINDROOT", ret1 = BAM_ERROR);
985 	if (ret1 == BAM_ERROR)
986 		goto abort;
987 
988 	if (bam_is_hv == BAM_HV_PRESENT) {
989 		ret2 = bam_add_hv(mp, grubsign, grubroot,
990 		    root_optional(osroot, menu_root));
991 		INJECT_ERROR1("UPGRADE_ADD_HV", ret2 = BAM_ERROR);
992 		if (ret2 == BAM_ERROR)
993 			goto abort;
994 	} else
995 		ret2 = BAM_SUCCESS;
996 
997 	ret3 = bam_add_dboot(mp, osroot, grubsign,
998 	    grubroot, root_optional(osroot, menu_root));
999 	INJECT_ERROR1("UPGRADE_ADD_DBOOT", ret3 = BAM_ERROR);
1000 	if (ret3 == BAM_ERROR)
1001 		goto abort;
1002 
1003 	if (ret1 == BAM_MSG || ret2 == BAM_MSG || ret3 == BAM_MSG) {
1004 		bam_error(_("one or more GRUB menu entries were not "
1005 		    "automatically upgraded\nFor details on manually "
1006 		    "updating entries, see %s\n"), MENU_URL(osroot));
1007 	} else {
1008 		bam_warn_hand_entries(mp, osroot);
1009 	}
1010 
1011 	free(grubsign);
1012 
1013 	BAM_DPRINTF(("%s: returning ret = %d\n", fcn, BAM_WRITE));
1014 	return (BAM_WRITE);
1015 
1016 abort:
1017 	free(grubsign);
1018 	bam_error(_("error upgrading GRUB menu entries on %s. Aborting.\n"
1019 	    "For details on manually updating entries, see %s\n"), osroot,
1020 	    MENU_URL(osroot));
1021 	return (BAM_ERROR);
1022 }
1023