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 2007 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #pragma ident	"%Z%%M%	%I%	%E% SMI"
27 
28 #include <stdio.h>
29 #include <errno.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <unistd.h>
33 #include <sys/types.h>
34 #include <sys/stat.h>
35 #include <limits.h>
36 #include <fcntl.h>
37 #include <strings.h>
38 
39 #include <sys/mman.h>
40 #include <sys/elf.h>
41 #include <sys/multiboot.h>
42 
43 #include "message.h"
44 #include "bootadm.h"
45 
46 direct_or_multi_t bam_direct = BAM_DIRECT_NOT_SET;
47 
48 error_t
49 dboot_or_multiboot(const char *root)
50 {
51 	char fname[PATH_MAX];
52 	char *image;
53 	uchar_t *ident;
54 	int fd, m;
55 	multiboot_header_t *mbh;
56 
57 	(void) snprintf(fname, PATH_MAX, "%s/%s", root,
58 	    "platform/i86pc/kernel/unix");
59 	fd = open(fname, O_RDONLY);
60 	if (fd < 0) {
61 		bam_error(OPEN_FAIL, fname, strerror(errno));
62 		return (BAM_ERROR);
63 	}
64 
65 	/*
66 	 * mmap the first 8K
67 	 */
68 	image = mmap(NULL, 8192, PROT_READ, MAP_SHARED, fd, 0);
69 	if (image == MAP_FAILED) {
70 		bam_error(MMAP_FAIL, fname, strerror(errno));
71 		return (BAM_ERROR);
72 	}
73 
74 	ident = (uchar_t *)image;
75 	if (ident[EI_MAG0] != ELFMAG0 || ident[EI_MAG1] != ELFMAG1 ||
76 	    ident[EI_MAG2] != ELFMAG2 || ident[EI_MAG3] != ELFMAG3) {
77 		bam_error(NOT_ELF_FILE, fname);
78 		return (BAM_ERROR);
79 	}
80 	if (ident[EI_CLASS] != ELFCLASS32) {
81 		bam_error(WRONG_ELF_CLASS, fname, ident[EI_CLASS]);
82 		return (BAM_ERROR);
83 	}
84 
85 	/*
86 	 * The GRUB multiboot header must be 32-bit aligned and completely
87 	 * contained in the 1st 8K of the file.  If the unix binary has
88 	 * a multiboot header, then it is a 'dboot' kernel.  Otherwise,
89 	 * this kernel must be booted via multiboot -- we call this a
90 	 * 'multiboot' kernel.
91 	 */
92 	bam_direct = BAM_DIRECT_MULTIBOOT;
93 	for (m = 0; m < 8192 - sizeof (multiboot_header_t); m += 4) {
94 		mbh = (void *)(image + m);
95 		if (mbh->magic == MB_HEADER_MAGIC) {
96 			bam_direct = BAM_DIRECT_DBOOT;
97 			break;
98 		}
99 	}
100 	(void) munmap(image, 8192);
101 	(void) close(fd);
102 	return (BAM_SUCCESS);
103 }
104 
105 #define	INST_RELEASE	"var/sadm/system/admin/INST_RELEASE"
106 
107 /*
108  * Return true if root has been bfu'ed.  bfu will blow away
109  * var/sadm/system/admin/INST_RELEASE, so if it's still there, we can
110  * assume the system has not been bfu'ed.
111  */
112 static int
113 is_bfu_system(const char *root)
114 {
115 	static int is_bfu = -1;
116 	char path[PATH_MAX];
117 	struct stat sb;
118 
119 	if (is_bfu != -1)
120 		return (is_bfu);
121 
122 	(void) snprintf(path, sizeof (path), "%s/%s", root, INST_RELEASE);
123 	if (stat(path, &sb) != 0) {
124 		is_bfu = 1;
125 	} else {
126 		is_bfu = 0;
127 	}
128 	return (is_bfu);
129 }
130 
131 #define	MENU_URL(root)	(is_bfu_system(root) ?		\
132 	"http://www.sun.com/msg/SUNOS-8000-CF" :	\
133 	"http://www.sun.com/msg/SUNOS-8000-AK")
134 
135 /*
136  * Simply allocate a new line and copy in cmd + sep + arg
137  */
138 void
139 update_line(line_t *linep)
140 {
141 	size_t size;
142 
143 	free(linep->line);
144 	size = strlen(linep->cmd) + strlen(linep->sep) + strlen(linep->arg) + 1;
145 	linep->line = s_calloc(1, size);
146 	(void) snprintf(linep->line, size, "%s%s%s", linep->cmd, linep->sep,
147 	    linep->arg);
148 }
149 
150 /*
151  * The parse_kernel_line function examines a menu.lst kernel line.  For
152  * multiboot, this is:
153  *
154  * kernel <multiboot path> <flags1> <kernel path> <flags2>
155  *
156  * <multiboot path> is either /platform/i86pc/multiboot or /boot/multiboot
157  *
158  * <kernel path> may be missing, or may be any full or relative path to unix.
159  *	We check for it by looking for a word ending in "/unix".  If it ends
160  *	in "kernel/unix", we upgrade it to a 32-bit entry.  If it ends in
161  *	"kernel/amd64/unix", we upgrade it to the default entry.  Otherwise,
162  *	it's a custom kernel, and we skip it.
163  *
164  * <flags*> are anything that doesn't fit either of the above - these will be
165  *	copied over.
166  *
167  * For direct boot, the defaults are
168  *
169  * kernel$ <kernel path> <flags>
170  *
171  * <kernel path> is one of:
172  *	/platform/i86pc/kernel/$ISADIR/unix
173  *	/platform/i86pc/kernel/unix
174  *	/platform/i86pc/kernel/amd64/unix
175  *	/boot/platform/i86pc/kernel/unix
176  *
177  * If <kernel path> is any of the last three, the command may also be "kernel".
178  *
179  * <flags> is anything that isn't <kernel path>.
180  *
181  * This function is only called if it applies to our target boot environment.
182  * If we can't make any sense of the kernel line, an error is printed and
183  * BAM_ERROR is returned.
184  *
185  * The desired install type is given in the global variable bam_direct.
186  * If the kernel line is of a different install type, we change it to the
187  * preferred type.  If the kernel line is already of the correct install
188  * type, we do nothing.  Either way, BAM_SUCCESS is returned.
189  *
190  * For safety, we do one more check: if the kernel path starts with /boot,
191  * we verify that the new kernel exists before changing it.  This is mainly
192  * done for bfu, as it may cause the failsafe archives to be a different
193  * boot architecture from the newly bfu'ed system.
194  */
195 static error_t
196 parse_kernel_line(line_t *linep, const char *root, uint8_t *flags)
197 {
198 	char path[PATH_MAX];
199 	int len, left, total_len;
200 	struct stat sb;
201 	char *new_ptr, *new_arg, *old_ptr;
202 	menu_cmd_t which;
203 
204 	/* Used when changing a multiboot line to dboot */
205 	char *unix_ptr, *flags1_ptr, *flags2_ptr;
206 
207 	/*
208 	 * Note that BAM_ENTRY_DBOOT refers to the entry we're looking at, not
209 	 * necessarily the system type.
210 	 */
211 	if (strncmp(linep->arg, DIRECT_BOOT_32,
212 	    sizeof (DIRECT_BOOT_32) - 1) == 0) {
213 		*flags |= BAM_ENTRY_DBOOT | BAM_ENTRY_32BIT;
214 	} else if ((strncmp(linep->arg, DIRECT_BOOT_KERNEL,
215 	    sizeof (DIRECT_BOOT_KERNEL) - 1) == 0) ||
216 	    (strncmp(linep->arg, DIRECT_BOOT_64,
217 	    sizeof (DIRECT_BOOT_64) - 1) == 0) ||
218 	    (strncmp(linep->arg, DIRECT_BOOT_FAILSAFE_KERNEL,
219 	    sizeof (DIRECT_BOOT_FAILSAFE_KERNEL) - 1) == 0)) {
220 		*flags |= BAM_ENTRY_DBOOT;
221 	} else if ((strncmp(linep->arg, MULTI_BOOT,
222 	    sizeof (MULTI_BOOT) - 1) == 0) ||
223 	    (strncmp(linep->arg, MULTI_BOOT_FAILSAFE,
224 	    sizeof (MULTI_BOOT_FAILSAFE) - 1) == 0)) {
225 		*flags &= ~BAM_ENTRY_DBOOT;
226 	} else {
227 		bam_error(NO_KERNEL_MATCH, linep->lineNum, MENU_URL(root));
228 		return (BAM_ERROR);
229 	}
230 
231 	if (((*flags & BAM_ENTRY_DBOOT) && (bam_direct == BAM_DIRECT_DBOOT)) ||
232 	    (((*flags & BAM_ENTRY_DBOOT) == 0) &&
233 	    (bam_direct == BAM_DIRECT_MULTIBOOT))) {
234 
235 		/* No action needed */
236 		return (BAM_SUCCESS);
237 	}
238 
239 	if (*flags & BAM_ENTRY_MINIROOT) {
240 		/*
241 		 * We're changing boot architectures - make sure
242 		 * the multiboot failsafe still exists.
243 		 */
244 		(void) snprintf(path, PATH_MAX, "%s%s", root,
245 		    (*flags & BAM_ENTRY_DBOOT) ? MULTI_BOOT_FAILSAFE :
246 		    DIRECT_BOOT_FAILSAFE_KERNEL);
247 		if (stat(path, &sb) != 0) {
248 			if (bam_verbose) {
249 				bam_error(FAILSAFE_MISSING, linep->lineNum);
250 			}
251 			return (BAM_SUCCESS);
252 		}
253 	}
254 
255 	/*
256 	 * Make sure we have the correct cmd - either kernel or kernel$
257 	 * The failsafe entry should always be KERNEL_CMD.
258 	 */
259 	which = ((bam_direct == BAM_DIRECT_MULTIBOOT) ||
260 	    (*flags & BAM_ENTRY_MINIROOT)) ? KERNEL_CMD : KERNEL_DOLLAR_CMD;
261 	free(linep->cmd);
262 	len = strlen(menu_cmds[which]) + 1;
263 	linep->cmd = s_calloc(1, len);
264 	(void) strncpy(linep->cmd, menu_cmds[which], len);
265 
266 	/*
267 	 * Since all arguments are copied, the new arg string should be close
268 	 * in size to the old one.  Just add 32 to cover the difference in
269 	 * the boot path.
270 	 */
271 	total_len = strlen(linep->arg) + 32;
272 	new_arg = s_calloc(1, total_len);
273 	old_ptr = strchr(linep->arg, ' ');
274 	if (old_ptr != NULL)
275 		old_ptr++;
276 
277 	/*
278 	 * Transitioning from dboot to multiboot is pretty simple.  We
279 	 * copy in multiboot and any args.
280 	 */
281 	if (bam_direct == BAM_DIRECT_MULTIBOOT) {
282 		if (old_ptr == NULL) {
283 			(void) snprintf(new_arg, total_len, "%s",
284 			    (*flags & BAM_ENTRY_MINIROOT) ?
285 			    MULTI_BOOT_FAILSAFE : MULTI_BOOT);
286 		} else {
287 			(void) snprintf(new_arg, total_len, "%s %s",
288 			    (*flags & BAM_ENTRY_MINIROOT) ?
289 			    MULTI_BOOT_FAILSAFE : MULTI_BOOT, old_ptr);
290 		}
291 		goto done;
292 	}
293 
294 	/*
295 	 * Transitioning from multiboot to directboot is a bit more
296 	 * complicated, since we may have two sets of arguments to
297 	 * copy and a unix path to parse.
298 	 *
299 	 * First, figure out if there's a unix path.
300 	 */
301 	if ((old_ptr != NULL) &&
302 	    ((unix_ptr = strstr(old_ptr, "/unix")) != NULL)) {
303 		/* See if there's anything past unix */
304 		flags2_ptr = unix_ptr + sizeof ("/unix");
305 		if (*flags2_ptr == '\0') {
306 			flags2_ptr = NULL;
307 		}
308 
309 		while ((unix_ptr > old_ptr) && (*unix_ptr != ' '))
310 			unix_ptr--;
311 
312 		if (unix_ptr == old_ptr) {
313 			flags1_ptr = NULL;
314 		} else {
315 			flags1_ptr = old_ptr;
316 		}
317 
318 		if (strstr(unix_ptr, "kernel/unix") != NULL) {
319 			*flags |= BAM_ENTRY_32BIT;
320 		} else if ((strstr(unix_ptr, "kernel/amd64/unix") == NULL) &&
321 		    (!bam_force)) {
322 			/*
323 			 * If the above strstr returns NULL, but bam_force is
324 			 * set, we'll be upgrading an Install kernel.  The
325 			 * result probably won't be what was intended, but we'll
326 			 * try it anyways.
327 			 */
328 			return (BAM_SKIP);
329 		}
330 	} else if (old_ptr != NULL) {
331 		flags1_ptr = old_ptr;
332 		unix_ptr = flags1_ptr + strlen(old_ptr);
333 		flags2_ptr = NULL;
334 	} else {
335 		unix_ptr = flags1_ptr = flags2_ptr = NULL;
336 	}
337 
338 	if (*flags & BAM_ENTRY_MINIROOT) {
339 		(void) snprintf(new_arg, total_len, "%s",
340 		    DIRECT_BOOT_FAILSAFE_KERNEL);
341 	} else if (*flags & BAM_ENTRY_32BIT) {
342 		(void) snprintf(new_arg, total_len, "%s", DIRECT_BOOT_32);
343 	} else {
344 		(void) snprintf(new_arg, total_len, "%s", DIRECT_BOOT_KERNEL);
345 	}
346 
347 	/*
348 	 * We now want to copy flags1_ptr through unix_ptr, and
349 	 * flags2_ptr through the end of the string
350 	 */
351 	if (flags1_ptr != NULL) {
352 		len = strlcat(new_arg, " ", total_len);
353 		left = total_len - len;
354 		new_ptr = new_arg + len;
355 
356 		if ((unix_ptr - flags1_ptr) < left)
357 			left = (unix_ptr - flags1_ptr) + 1;
358 		(void) strlcpy(new_ptr, flags1_ptr, left);
359 	}
360 	if (flags2_ptr != NULL) {
361 		(void) strlcat(new_arg, " ", total_len);
362 		(void) strlcat(new_arg, flags2_ptr, total_len);
363 	}
364 
365 done:
366 	free(linep->arg);
367 	linep->arg = new_arg;
368 	update_line(linep);
369 	return (BAM_SUCCESS);
370 }
371 
372 /*
373  * Similar to above, except this time we're looking at a module line,
374  * which is quite a bit simpler.
375  *
376  * Under multiboot, the archive line is:
377  *
378  * module /platform/i86pc/boot_archive
379  *
380  * Under directboot, the archive line is:
381  *
382  * module$ /platform/i86pc/$ISADIR/boot_archive
383  *
384  * which may be specified exactly as either of:
385  *
386  * module /platform/i86pc/boot_archive
387  * module /platform/i86pc/amd64/boot_archive
388  *
389  * For either dboot or multiboot, the failsafe is:
390  *
391  * module /boot/x86.miniroot-safe
392  */
393 static error_t
394 parse_module_line(line_t *linep, const char *root, uint8_t flags)
395 {
396 	int len;
397 	menu_cmd_t which;
398 	char *new;
399 
400 	/*
401 	 * If necessary, BAM_ENTRY_MINIROOT was already set in flags
402 	 * in upgrade_menu().  We re-check BAM_ENTRY_DBOOT here in here
403 	 * in case the kernel and module lines differ.
404 	 */
405 	if ((strcmp(linep->arg, DIRECT_BOOT_ARCHIVE) == 0) ||
406 	    (strcmp(linep->arg, DIRECT_BOOT_ARCHIVE_64) == 0)) {
407 		flags |= BAM_ENTRY_DBOOT;
408 	} else if ((strcmp(linep->arg, MULTI_BOOT_ARCHIVE) == 0) ||
409 	    (strcmp(linep->arg, MINIROOT) == 0)) {
410 		flags &= ~BAM_ENTRY_DBOOT;
411 	} else {
412 		bam_error(NO_MODULE_MATCH, linep->lineNum, MENU_URL(root));
413 		return (BAM_ERROR);
414 	}
415 
416 	if (((flags & BAM_ENTRY_DBOOT) && (bam_direct == BAM_DIRECT_DBOOT)) ||
417 	    (((flags & BAM_ENTRY_DBOOT) == 0) &&
418 	    (bam_direct == BAM_DIRECT_MULTIBOOT)) ||
419 	    ((flags & BAM_ENTRY_MINIROOT) &&
420 	    (strcmp(linep->cmd, menu_cmds[MODULE_CMD]) == 0))) {
421 
422 		/* No action needed */
423 		return (BAM_SUCCESS);
424 	}
425 
426 	/*
427 	 * Make sure we have the correct cmd - either module or module$
428 	 * The failsafe entry should always be MODULE_CMD.
429 	 */
430 	which = ((bam_direct == BAM_DIRECT_MULTIBOOT) ||
431 	    (flags & BAM_ENTRY_MINIROOT)) ? MODULE_CMD : MODULE_DOLLAR_CMD;
432 	free(linep->cmd);
433 	len = strlen(menu_cmds[which]) + 1;
434 	linep->cmd = s_calloc(1, len);
435 	(void) strncpy(linep->cmd, menu_cmds[which], len);
436 
437 	if (flags & BAM_ENTRY_MINIROOT) {
438 		new = MINIROOT;
439 	} else if ((bam_direct == BAM_DIRECT_DBOOT) &&
440 	    ((flags & BAM_ENTRY_32BIT) == 0)) {
441 		new = DIRECT_BOOT_ARCHIVE;
442 	} else {
443 		new = MULTI_BOOT_ARCHIVE;
444 	}
445 
446 	free(linep->arg);
447 	len = strlen(new) + 1;
448 	linep->arg = s_calloc(1, len);
449 	(void) strncpy(linep->arg, new, len);
450 	update_line(linep);
451 
452 	return (BAM_SUCCESS);
453 }
454 
455 /*ARGSUSED*/
456 error_t
457 upgrade_menu(menu_t *mp, char *root, char *opt)
458 {
459 	entry_t	*cur_entry;
460 	line_t	*cur_line;
461 	int	i, skipit = 0, num_entries = 0;
462 	int	*hand_entries = NULL;
463 	boolean_t found_kernel = B_FALSE;
464 	error_t	rv;
465 	char	*rootdev, *grubdisk = NULL;
466 
467 	rootdev = get_special(root);
468 	if (rootdev) {
469 		grubdisk = os_to_grubdisk(rootdev, strlen(root) == 1);
470 		free(rootdev);
471 		rootdev = NULL;
472 	}
473 
474 	/* Loop through all OS entries in the menu.lst file */
475 	for (cur_entry = mp->entries; cur_entry != NULL;
476 	    cur_entry = cur_entry->next, skipit = 0) {
477 
478 		if ((cur_entry->flags & BAM_ENTRY_CHAINLOADER) ||
479 		    ((cur_entry->flags & BAM_ENTRY_MINIROOT) && !bam_force))
480 			continue;
481 
482 		/*
483 		 * We only change entries added by bootadm and live upgrade,
484 		 * and warn on the rest, unless the -f flag was passed.
485 		 */
486 		if ((!(cur_entry->flags & (BAM_ENTRY_BOOTADM|BAM_ENTRY_LU))) &&
487 		    !bam_force) {
488 			if (num_entries == 0) {
489 				hand_entries = s_calloc(1, sizeof (int));
490 			} else {
491 				hand_entries = s_realloc(hand_entries,
492 				    (num_entries + 1) * sizeof (int));
493 			}
494 			hand_entries[num_entries++] = cur_entry->entryNum;
495 			continue;
496 		}
497 
498 		/*
499 		 * We make two loops through the lines.  First, we check if
500 		 * there is a root entry, and if so, whether we should be
501 		 * checking this entry.
502 		 */
503 		if ((grubdisk != NULL) && (cur_entry->flags & BAM_ENTRY_ROOT)) {
504 			for (cur_line = cur_entry->start; cur_line != NULL;
505 			    cur_line = cur_line->next) {
506 				if ((cur_line->cmd == NULL) ||
507 				    (cur_line->arg == NULL))
508 					continue;
509 
510 				if (strcmp(cur_line->cmd,
511 				    menu_cmds[ROOT_CMD]) == 0) {
512 					if (strcmp(cur_line->arg,
513 					    grubdisk) != 0) {
514 						/* A different slice */
515 						skipit = 1;
516 					}
517 					break;
518 				}
519 				if (cur_line == cur_entry->end)
520 					break;
521 			}
522 		}
523 		if (skipit)
524 			continue;
525 
526 		for (cur_line = cur_entry->start; cur_line != NULL;
527 		    cur_line = cur_line->next) {
528 
529 			/*
530 			 * We only compare for the length of KERNEL_CMD,
531 			 * so that KERNEL_DOLLAR_CMD will also match.
532 			 */
533 			if (strncmp(cur_line->cmd, menu_cmds[KERNEL_CMD],
534 			    strlen(menu_cmds[KERNEL_CMD])) == 0) {
535 				rv = parse_kernel_line(cur_line, root,
536 				    &(cur_entry->flags));
537 				if (rv == BAM_SKIP) {
538 					break;
539 				} else if (rv != BAM_SUCCESS) {
540 					return (rv);
541 				}
542 				found_kernel = B_TRUE;
543 			} else if (strncmp(cur_line->cmd,
544 			    menu_cmds[MODULE_CMD],
545 			    strlen(menu_cmds[MODULE_CMD])) == 0) {
546 				rv = parse_module_line(cur_line, root,
547 				    cur_entry->flags);
548 				if (rv != BAM_SUCCESS) {
549 					return (rv);
550 				}
551 			}
552 			if (cur_line == cur_entry->end)
553 				break;
554 		}
555 	}
556 
557 	/*
558 	 * We only want to output one error, to avoid confusing a user.  We
559 	 * rank "No kernels changed" as a higher priority than "will not
560 	 * update hand-added entries", since the former implies the latter.
561 	 */
562 	if (found_kernel == B_FALSE) {
563 		bam_error(NO_KERNELS_FOUND, MENU_URL(root));
564 		return (BAM_ERROR);
565 	} else if (num_entries > 0) {
566 		bam_error(HAND_ADDED_ENTRY, MENU_URL(root));
567 		bam_print_stderr("Entry Number%s: ", (num_entries > 1) ?
568 		    "s" : "");
569 		for (i = 0; i < num_entries; i++) {
570 			bam_print_stderr("%d ", hand_entries[i]);
571 		}
572 		bam_print_stderr("\n");
573 	}
574 	return (BAM_WRITE);
575 }
576