summaryrefslogtreecommitdiff
path: root/usr/src/cmd/boot/bootadm/bootadm_upgrade.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr/src/cmd/boot/bootadm/bootadm_upgrade.c')
-rw-r--r--usr/src/cmd/boot/bootadm/bootadm_upgrade.c575
1 files changed, 575 insertions, 0 deletions
diff --git a/usr/src/cmd/boot/bootadm/bootadm_upgrade.c b/usr/src/cmd/boot/bootadm/bootadm_upgrade.c
new file mode 100644
index 0000000000..0b375b7fd0
--- /dev/null
+++ b/usr/src/cmd/boot/bootadm/bootadm_upgrade.c
@@ -0,0 +1,575 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+/*
+ * Copyright 2007 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <limits.h>
+#include <fcntl.h>
+#include <strings.h>
+
+#include <sys/mman.h>
+#include <sys/elf.h>
+#include <sys/multiboot.h>
+
+#include "message.h"
+#include "bootadm.h"
+
+direct_or_multi_t bam_direct = BAM_DIRECT_NOT_SET;
+
+error_t
+dboot_or_multiboot(const char *root)
+{
+ char fname[PATH_MAX];
+ char *image;
+ uchar_t *ident;
+ int fd, m;
+ multiboot_header_t *mbh;
+
+ (void) snprintf(fname, PATH_MAX, "%s/%s", root,
+ "platform/i86pc/kernel/unix");
+ fd = open(fname, O_RDONLY);
+ if (fd < 0) {
+ bam_error(OPEN_FAIL, fname, strerror(errno));
+ return (BAM_ERROR);
+ }
+
+ /*
+ * mmap the first 8K
+ */
+ image = mmap(NULL, 8192, PROT_READ, MAP_SHARED, fd, 0);
+ if (image == MAP_FAILED) {
+ bam_error(MMAP_FAIL, fname, strerror(errno));
+ return (BAM_ERROR);
+ }
+
+ ident = (uchar_t *)image;
+ if (ident[EI_MAG0] != ELFMAG0 || ident[EI_MAG1] != ELFMAG1 ||
+ ident[EI_MAG2] != ELFMAG2 || ident[EI_MAG3] != ELFMAG3) {
+ bam_error(NOT_ELF_FILE, fname);
+ return (BAM_ERROR);
+ }
+ if (ident[EI_CLASS] != ELFCLASS32) {
+ bam_error(WRONG_ELF_CLASS, fname, ident[EI_CLASS]);
+ return (BAM_ERROR);
+ }
+
+ /*
+ * The GRUB multiboot header must be 32-bit aligned and completely
+ * contained in the 1st 8K of the file. If the unix binary has
+ * a multiboot header, then it is a 'dboot' kernel. Otherwise,
+ * this kernel must be booted via multiboot -- we call this a
+ * 'multiboot' kernel.
+ */
+ bam_direct = BAM_DIRECT_MULTIBOOT;
+ for (m = 0; m < 8192 - sizeof (multiboot_header_t); m += 4) {
+ mbh = (void *)(image + m);
+ if (mbh->magic == MB_HEADER_MAGIC) {
+ bam_direct = BAM_DIRECT_DBOOT;
+ break;
+ }
+ }
+ (void) munmap(image, 8192);
+ (void) close(fd);
+ return (BAM_SUCCESS);
+}
+
+#define INST_RELEASE "var/sadm/system/admin/INST_RELEASE"
+
+/*
+ * Return true if root has been bfu'ed. bfu will blow away
+ * var/sadm/system/admin/INST_RELEASE, so if it's still there, we can
+ * assume the system has not been bfu'ed.
+ */
+static int
+is_bfu_system(const char *root)
+{
+ static int is_bfu = -1;
+ char path[PATH_MAX];
+ struct stat sb;
+
+ if (is_bfu != -1)
+ return (is_bfu);
+
+ (void) snprintf(path, sizeof (path), "%s/%s", root, INST_RELEASE);
+ if (stat(path, &sb) != 0) {
+ is_bfu = 1;
+ } else {
+ is_bfu = 0;
+ }
+ return (is_bfu);
+}
+
+#define MENU_URL(root) (is_bfu_system(root) ? \
+ "http://www.sun.com/msg/SUNOS-8000-CF" : \
+ "http://www.sun.com/msg/SUNOS-8000-AK")
+
+/*
+ * Simply allocate a new line and copy in cmd + sep + arg
+ */
+void
+update_line(line_t *linep)
+{
+ size_t size;
+
+ free(linep->line);
+ size = strlen(linep->cmd) + strlen(linep->sep) + strlen(linep->arg) + 1;
+ linep->line = s_calloc(1, size);
+ (void) snprintf(linep->line, size, "%s%s%s", linep->cmd, linep->sep,
+ linep->arg);
+}
+
+/*
+ * The parse_kernel_line function examines a menu.lst kernel line. For
+ * multiboot, this is:
+ *
+ * kernel <multiboot path> <flags1> <kernel path> <flags2>
+ *
+ * <multiboot path> is either /platform/i86pc/multiboot or /boot/multiboot
+ *
+ * <kernel path> may be missing, or may be any full or relative path to unix.
+ * We check for it by looking for a word ending in "/unix". If it ends
+ * in "kernel/unix", we upgrade it to a 32-bit entry. If it ends in
+ * "kernel/amd64/unix", we upgrade it to the default entry. Otherwise,
+ * it's a custom kernel, and we skip it.
+ *
+ * <flags*> are anything that doesn't fit either of the above - these will be
+ * copied over.
+ *
+ * For direct boot, the defaults are
+ *
+ * kernel$ <kernel path> <flags>
+ *
+ * <kernel path> is one of:
+ * /platform/i86pc/kernel/$ISADIR/unix
+ * /platform/i86pc/kernel/unix
+ * /platform/i86pc/kernel/amd64/unix
+ * /boot/platform/i86pc/kernel/unix
+ *
+ * If <kernel path> is any of the last three, the command may also be "kernel".
+ *
+ * <flags> is anything that isn't <kernel path>.
+ *
+ * This function is only called if it applies to our target boot environment.
+ * If we can't make any sense of the kernel line, an error is printed and
+ * BAM_ERROR is returned.
+ *
+ * The desired install type is given in the global variable bam_direct.
+ * If the kernel line is of a different install type, we change it to the
+ * preferred type. If the kernel line is already of the correct install
+ * type, we do nothing. Either way, BAM_SUCCESS is returned.
+ *
+ * For safety, we do one more check: if the kernel path starts with /boot,
+ * we verify that the new kernel exists before changing it. This is mainly
+ * done for bfu, as it may cause the failsafe archives to be a different
+ * boot architecture from the newly bfu'ed system.
+ */
+static error_t
+parse_kernel_line(line_t *linep, const char *root, uint8_t *flags)
+{
+ char path[PATH_MAX];
+ int len, left, total_len;
+ struct stat sb;
+ char *new_ptr, *new_arg, *old_ptr;
+ menu_cmd_t which;
+
+ /* Used when changing a multiboot line to dboot */
+ char *unix_ptr, *flags1_ptr, *flags2_ptr;
+
+ /*
+ * Note that BAM_ENTRY_DBOOT refers to the entry we're looking at, not
+ * necessarily the system type.
+ */
+ if (strncmp(linep->arg, DIRECT_BOOT_32,
+ sizeof (DIRECT_BOOT_32) - 1) == 0) {
+ *flags |= BAM_ENTRY_DBOOT | BAM_ENTRY_32BIT;
+ } else if ((strncmp(linep->arg, DIRECT_BOOT_KERNEL,
+ sizeof (DIRECT_BOOT_KERNEL) - 1) == 0) ||
+ (strncmp(linep->arg, DIRECT_BOOT_64,
+ sizeof (DIRECT_BOOT_64) - 1) == 0) ||
+ (strncmp(linep->arg, DIRECT_BOOT_FAILSAFE_KERNEL,
+ sizeof (DIRECT_BOOT_FAILSAFE_KERNEL) - 1) == 0)) {
+ *flags |= BAM_ENTRY_DBOOT;
+ } else if ((strncmp(linep->arg, MULTI_BOOT,
+ sizeof (MULTI_BOOT) - 1) == 0) ||
+ (strncmp(linep->arg, MULTI_BOOT_FAILSAFE,
+ sizeof (MULTI_BOOT_FAILSAFE) - 1) == 0)) {
+ *flags &= ~BAM_ENTRY_DBOOT;
+ } else {
+ bam_error(NO_KERNEL_MATCH, linep->lineNum, MENU_URL(root));
+ return (BAM_ERROR);
+ }
+
+ if (((*flags & BAM_ENTRY_DBOOT) && (bam_direct == BAM_DIRECT_DBOOT)) ||
+ (((*flags & BAM_ENTRY_DBOOT) == 0) &&
+ (bam_direct == BAM_DIRECT_MULTIBOOT))) {
+
+ /* No action needed */
+ return (BAM_SUCCESS);
+ }
+
+ if (*flags & BAM_ENTRY_MINIROOT) {
+ /*
+ * We're changing boot architectures - make sure
+ * the multiboot failsafe still exists.
+ */
+ (void) snprintf(path, PATH_MAX, "%s%s", root,
+ (*flags & BAM_ENTRY_DBOOT) ? MULTI_BOOT_FAILSAFE :
+ DIRECT_BOOT_FAILSAFE_KERNEL);
+ if (stat(path, &sb) != 0) {
+ if (bam_verbose) {
+ bam_error(FAILSAFE_MISSING, linep->lineNum);
+ }
+ return (BAM_SUCCESS);
+ }
+ }
+
+ /*
+ * Make sure we have the correct cmd - either kernel or kernel$
+ * The failsafe entry should always be KERNEL_CMD.
+ */
+ which = ((bam_direct == BAM_DIRECT_MULTIBOOT) ||
+ (*flags & BAM_ENTRY_MINIROOT)) ? KERNEL_CMD : KERNEL_DOLLAR_CMD;
+ free(linep->cmd);
+ len = strlen(menu_cmds[which]) + 1;
+ linep->cmd = s_calloc(1, len);
+ (void) strncpy(linep->cmd, menu_cmds[which], len);
+
+ /*
+ * Since all arguments are copied, the new arg string should be close
+ * in size to the old one. Just add 32 to cover the difference in
+ * the boot path.
+ */
+ total_len = strlen(linep->arg) + 32;
+ new_arg = s_calloc(1, total_len);
+ old_ptr = strchr(linep->arg, ' ');
+ if (old_ptr != NULL)
+ old_ptr++;
+
+ /*
+ * Transitioning from dboot to multiboot is pretty simple. We
+ * copy in multiboot and any args.
+ */
+ if (bam_direct == BAM_DIRECT_MULTIBOOT) {
+ if (old_ptr == NULL) {
+ (void) snprintf(new_arg, total_len, "%s",
+ (*flags & BAM_ENTRY_MINIROOT) ?
+ MULTI_BOOT_FAILSAFE : MULTI_BOOT);
+ } else {
+ (void) snprintf(new_arg, total_len, "%s %s",
+ (*flags & BAM_ENTRY_MINIROOT) ?
+ MULTI_BOOT_FAILSAFE : MULTI_BOOT, old_ptr);
+ }
+ goto done;
+ }
+
+ /*
+ * Transitioning from multiboot to directboot is a bit more
+ * complicated, since we may have two sets of arguments to
+ * copy and a unix path to parse.
+ *
+ * First, figure out if there's a unix path.
+ */
+ if ((old_ptr != NULL) &&
+ ((unix_ptr = strstr(old_ptr, "/unix")) != NULL)) {
+ /* See if there's anything past unix */
+ flags2_ptr = unix_ptr + sizeof ("/unix");
+ if (*flags2_ptr == '\0') {
+ flags2_ptr = NULL;
+ }
+
+ while ((unix_ptr > old_ptr) && (*unix_ptr != ' '))
+ unix_ptr--;
+
+ if (unix_ptr == old_ptr) {
+ flags1_ptr = NULL;
+ } else {
+ flags1_ptr = old_ptr;
+ }
+
+ if (strstr(unix_ptr, "kernel/unix") != NULL) {
+ *flags |= BAM_ENTRY_32BIT;
+ } else if ((strstr(unix_ptr, "kernel/amd64/unix") == NULL) &&
+ (!bam_force)) {
+ /*
+ * If the above strstr returns NULL, but bam_force is
+ * set, we'll be upgrading an Install kernel. The
+ * result probably won't be what was intended, but we'll
+ * try it anyways.
+ */
+ return (BAM_SKIP);
+ }
+ } else if (old_ptr != NULL) {
+ flags1_ptr = old_ptr;
+ unix_ptr = flags1_ptr + strlen(old_ptr);
+ flags2_ptr = NULL;
+ } else {
+ unix_ptr = flags1_ptr = flags2_ptr = NULL;
+ }
+
+ if (*flags & BAM_ENTRY_MINIROOT) {
+ (void) snprintf(new_arg, total_len, "%s",
+ DIRECT_BOOT_FAILSAFE_KERNEL);
+ } else if (*flags & BAM_ENTRY_32BIT) {
+ (void) snprintf(new_arg, total_len, "%s", DIRECT_BOOT_32);
+ } else {
+ (void) snprintf(new_arg, total_len, "%s", DIRECT_BOOT_KERNEL);
+ }
+
+ /*
+ * We now want to copy flags1_ptr through unix_ptr, and
+ * flags2_ptr through the end of the string
+ */
+ if (flags1_ptr != NULL) {
+ len = strlcat(new_arg, " ", total_len);
+ left = total_len - len;
+ new_ptr = new_arg + len;
+
+ if ((unix_ptr - flags1_ptr) < left)
+ left = (unix_ptr - flags1_ptr) + 1;
+ (void) strlcpy(new_ptr, flags1_ptr, left);
+ }
+ if (flags2_ptr != NULL) {
+ (void) strlcat(new_arg, " ", total_len);
+ (void) strlcat(new_arg, flags2_ptr, total_len);
+ }
+
+done:
+ free(linep->arg);
+ linep->arg = new_arg;
+ update_line(linep);
+ return (BAM_SUCCESS);
+}
+
+/*
+ * Similar to above, except this time we're looking at a module line,
+ * which is quite a bit simpler.
+ *
+ * Under multiboot, the archive line is:
+ *
+ * module /platform/i86pc/boot_archive
+ *
+ * Under directboot, the archive line is:
+ *
+ * module$ /platform/i86pc/$ISADIR/boot_archive
+ *
+ * which may be specified exactly as either of:
+ *
+ * module /platform/i86pc/boot_archive
+ * module /platform/i86pc/amd64/boot_archive
+ *
+ * For either dboot or multiboot, the failsafe is:
+ *
+ * module /boot/x86.miniroot-safe
+ */
+static error_t
+parse_module_line(line_t *linep, const char *root, uint8_t flags)
+{
+ int len;
+ menu_cmd_t which;
+ char *new;
+
+ /*
+ * If necessary, BAM_ENTRY_MINIROOT was already set in flags
+ * in upgrade_menu(). We re-check BAM_ENTRY_DBOOT here in here
+ * in case the kernel and module lines differ.
+ */
+ if ((strcmp(linep->arg, DIRECT_BOOT_ARCHIVE) == 0) ||
+ (strcmp(linep->arg, DIRECT_BOOT_ARCHIVE_64) == 0)) {
+ flags |= BAM_ENTRY_DBOOT;
+ } else if ((strcmp(linep->arg, MULTI_BOOT_ARCHIVE) == 0) ||
+ (strcmp(linep->arg, MINIROOT) == 0)) {
+ flags &= ~BAM_ENTRY_DBOOT;
+ } else {
+ bam_error(NO_MODULE_MATCH, linep->lineNum, MENU_URL(root));
+ return (BAM_ERROR);
+ }
+
+ if (((flags & BAM_ENTRY_DBOOT) && (bam_direct == BAM_DIRECT_DBOOT)) ||
+ (((flags & BAM_ENTRY_DBOOT) == 0) &&
+ (bam_direct == BAM_DIRECT_MULTIBOOT)) ||
+ ((flags & BAM_ENTRY_MINIROOT) &&
+ (strcmp(linep->cmd, menu_cmds[MODULE_CMD]) == 0))) {
+
+ /* No action needed */
+ return (BAM_SUCCESS);
+ }
+
+ /*
+ * Make sure we have the correct cmd - either module or module$
+ * The failsafe entry should always be MODULE_CMD.
+ */
+ which = ((bam_direct == BAM_DIRECT_MULTIBOOT) ||
+ (flags & BAM_ENTRY_MINIROOT)) ? MODULE_CMD : MODULE_DOLLAR_CMD;
+ free(linep->cmd);
+ len = strlen(menu_cmds[which]) + 1;
+ linep->cmd = s_calloc(1, len);
+ (void) strncpy(linep->cmd, menu_cmds[which], len);
+
+ if (flags & BAM_ENTRY_MINIROOT) {
+ new = MINIROOT;
+ } else if ((bam_direct == BAM_DIRECT_DBOOT) &&
+ ((flags & BAM_ENTRY_32BIT) == 0)) {
+ new = DIRECT_BOOT_ARCHIVE;
+ } else {
+ new = MULTI_BOOT_ARCHIVE;
+ }
+
+ free(linep->arg);
+ len = strlen(new) + 1;
+ linep->arg = s_calloc(1, len);
+ (void) strncpy(linep->arg, new, len);
+ update_line(linep);
+
+ return (BAM_SUCCESS);
+}
+
+/*ARGSUSED*/
+error_t
+upgrade_menu(menu_t *mp, char *root, char *opt)
+{
+ entry_t *cur_entry;
+ line_t *cur_line;
+ int i, skipit = 0, num_entries = 0;
+ int *hand_entries = NULL;
+ boolean_t found_kernel = B_FALSE;
+ error_t rv;
+ char *rootdev, *grubdisk = NULL;
+
+ rootdev = get_special(root);
+ if (rootdev) {
+ grubdisk = os_to_grubdisk(rootdev, strlen(root) == 1);
+ free(rootdev);
+ rootdev = NULL;
+ }
+
+ /* Loop through all OS entries in the menu.lst file */
+ for (cur_entry = mp->entries; cur_entry != NULL;
+ cur_entry = cur_entry->next, skipit = 0) {
+
+ if ((cur_entry->flags & BAM_ENTRY_CHAINLOADER) ||
+ ((cur_entry->flags & BAM_ENTRY_MINIROOT) && !bam_force))
+ continue;
+
+ /*
+ * We only change entries added by bootadm and live upgrade,
+ * and warn on the rest, unless the -f flag was passed.
+ */
+ if ((!(cur_entry->flags & (BAM_ENTRY_BOOTADM|BAM_ENTRY_LU))) &&
+ !bam_force) {
+ if (num_entries == 0) {
+ hand_entries = s_calloc(1, sizeof (int));
+ } else {
+ hand_entries = s_realloc(hand_entries,
+ (num_entries + 1) * sizeof (int));
+ }
+ hand_entries[num_entries++] = cur_entry->entryNum;
+ continue;
+ }
+
+ /*
+ * We make two loops through the lines. First, we check if
+ * there is a root entry, and if so, whether we should be
+ * checking this entry.
+ */
+ if ((grubdisk != NULL) && (cur_entry->flags & BAM_ENTRY_ROOT)) {
+ for (cur_line = cur_entry->start; cur_line != NULL;
+ cur_line = cur_line->next) {
+ if ((cur_line->cmd == NULL) ||
+ (cur_line->arg == NULL))
+ continue;
+
+ if (strcmp(cur_line->cmd,
+ menu_cmds[ROOT_CMD]) == 0) {
+ if (strcmp(cur_line->arg,
+ grubdisk) != 0) {
+ /* A different slice */
+ skipit = 1;
+ }
+ break;
+ }
+ if (cur_line == cur_entry->end)
+ break;
+ }
+ }
+ if (skipit)
+ continue;
+
+ for (cur_line = cur_entry->start; cur_line != NULL;
+ cur_line = cur_line->next) {
+
+ /*
+ * We only compare for the length of KERNEL_CMD,
+ * so that KERNEL_DOLLAR_CMD will also match.
+ */
+ if (strncmp(cur_line->cmd, menu_cmds[KERNEL_CMD],
+ strlen(menu_cmds[KERNEL_CMD])) == 0) {
+ rv = parse_kernel_line(cur_line, root,
+ &(cur_entry->flags));
+ if (rv == BAM_SKIP) {
+ break;
+ } else if (rv != BAM_SUCCESS) {
+ return (rv);
+ }
+ found_kernel = B_TRUE;
+ } else if (strncmp(cur_line->cmd,
+ menu_cmds[MODULE_CMD],
+ strlen(menu_cmds[MODULE_CMD])) == 0) {
+ rv = parse_module_line(cur_line, root,
+ cur_entry->flags);
+ if (rv != BAM_SUCCESS) {
+ return (rv);
+ }
+ }
+ if (cur_line == cur_entry->end)
+ break;
+ }
+ }
+
+ /*
+ * We only want to output one error, to avoid confusing a user. We
+ * rank "No kernels changed" as a higher priority than "will not
+ * update hand-added entries", since the former implies the latter.
+ */
+ if (found_kernel == B_FALSE) {
+ bam_error(NO_KERNELS_FOUND, MENU_URL(root));
+ return (BAM_ERROR);
+ } else if (num_entries > 0) {
+ bam_error(HAND_ADDED_ENTRY, MENU_URL(root));
+ bam_print_stderr("Entry Number%s: ", (num_entries > 1) ?
+ "s" : "");
+ for (i = 0; i < num_entries; i++) {
+ bam_print_stderr("%d ", hand_entries[i]);
+ }
+ bam_print_stderr("\n");
+ }
+ return (BAM_WRITE);
+}