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