| /* |
| * 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 (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved. |
| * Copyright 2012 Milan Jurik. All rights reserved. |
| * Copyright (c) 2015 by Delphix. All rights reserved. |
| * Copyright 2016 Toomas Soome <tsoome@me.com> |
| * Copyright 2016 Nexenta Systems, Inc. |
| * Copyright 2018 OmniOS Community Edition (OmniOSce) Association. |
| */ |
| |
| /* |
| * bootadm(1M) is a new utility for managing bootability of |
| * Solaris *Newboot* environments. It has two primary tasks: |
| * - Allow end users to manage bootability of Newboot Solaris instances |
| * - Provide services to other subsystems in Solaris (primarily Install) |
| */ |
| |
| /* Headers */ |
| #include <stdio.h> |
| #include <errno.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <alloca.h> |
| #include <stdarg.h> |
| #include <limits.h> |
| #include <signal.h> |
| #include <sys/wait.h> |
| #include <sys/mnttab.h> |
| #include <sys/mntent.h> |
| #include <sys/statvfs.h> |
| #include <libnvpair.h> |
| #include <ftw.h> |
| #include <fcntl.h> |
| #include <strings.h> |
| #include <utime.h> |
| #include <sys/systeminfo.h> |
| #include <sys/dktp/fdisk.h> |
| #include <sys/param.h> |
| #include <dirent.h> |
| #include <ctype.h> |
| #include <libgen.h> |
| #include <sys/sysmacros.h> |
| #include <sys/elf.h> |
| #include <libscf.h> |
| #include <zlib.h> |
| #include <sys/lockfs.h> |
| #include <sys/filio.h> |
| #include <libbe.h> |
| #include <deflt.h> |
| #ifdef i386 |
| #include <libfdisk.h> |
| #endif |
| |
| #if !defined(_OBP) |
| #include <sys/ucode.h> |
| #endif |
| |
| #include <pwd.h> |
| #include <grp.h> |
| #include <device_info.h> |
| #include <sys/vtoc.h> |
| #include <sys/efi_partition.h> |
| #include <regex.h> |
| #include <locale.h> |
| #include <sys/mkdev.h> |
| |
| #include "bootadm.h" |
| |
| #ifndef TEXT_DOMAIN |
| #define TEXT_DOMAIN "SUNW_OST_OSCMD" |
| #endif /* TEXT_DOMAIN */ |
| |
| /* Type definitions */ |
| |
| /* Primary subcmds */ |
| typedef enum { |
| BAM_MENU = 3, |
| BAM_ARCHIVE, |
| BAM_INSTALL |
| } subcmd_t; |
| |
| #define LINE_INIT 0 /* lineNum initial value */ |
| #define ENTRY_INIT -1 /* entryNum initial value */ |
| #define ALL_ENTRIES -2 /* selects all boot entries */ |
| |
| #define PARTNO_NOTFOUND -1 /* Solaris partition not found */ |
| #define PARTNO_EFI -2 /* EFI partition table found */ |
| |
| #define GRUB_DIR "/boot/grub" |
| #define GRUB_STAGE2 GRUB_DIR "/stage2" |
| #define GRUB_MENU "/boot/grub/menu.lst" |
| #define MENU_TMP "/boot/grub/menu.lst.tmp" |
| #define GRUB_BACKUP_MENU "/etc/lu/GRUB_backup_menu" |
| #define RAMDISK_SPECIAL "/devices/ramdisk" |
| #define STUBBOOT "/stubboot" |
| #define MULTIBOOT "/platform/i86pc/multiboot" |
| #define GRUBSIGN_DIR "/boot/grub/bootsign" |
| #define GRUBSIGN_BACKUP "/etc/bootsign" |
| #define GRUBSIGN_UFS_PREFIX "rootfs" |
| #define GRUBSIGN_ZFS_PREFIX "pool_" |
| #define GRUBSIGN_LU_PREFIX "BE_" |
| #define UFS_SIGNATURE_LIST "/var/run/grub_ufs_signatures" |
| #define ZFS_LEGACY_MNTPT "/tmp/bootadm_mnt_zfs_legacy" |
| |
| /* SMF */ |
| #define BOOT_ARCHIVE_FMRI "system/boot-archive:default" |
| #define SCF_PG_CONFIG "config" |
| #define SCF_PROPERTY_FORMAT "format" |
| |
| /* BE defaults */ |
| #define BE_DEFAULTS "/etc/default/be" |
| #define BE_DFLT_BE_HAS_GRUB "BE_HAS_GRUB=" |
| |
| #define BOOTADM_RDONLY_TEST "BOOTADM_RDONLY_TEST" |
| |
| /* lock related */ |
| #define BAM_LOCK_FILE "/var/run/bootadm.lock" |
| #define LOCK_FILE_PERMS (S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) |
| |
| #define CREATE_RAMDISK "boot/solaris/bin/create_ramdisk" |
| #define CREATE_DISKMAP "boot/solaris/bin/create_diskmap" |
| #define EXTRACT_BOOT_FILELIST "boot/solaris/bin/extract_boot_filelist" |
| #define GRUBDISK_MAP "/var/run/solaris_grubdisk.map" |
| |
| #define GRUB_slice "/etc/lu/GRUB_slice" |
| #define GRUB_root "/etc/lu/GRUB_root" |
| #define GRUB_fdisk "/etc/lu/GRUB_fdisk" |
| #define GRUB_fdisk_target "/etc/lu/GRUB_fdisk_target" |
| #define FINDROOT_INSTALLGRUB "/etc/lu/installgrub.findroot" |
| #define LULIB "/usr/lib/lu/lulib" |
| #define LULIB_PROPAGATE_FILE "lulib_propagate_file" |
| #define CKSUM "/usr/bin/cksum" |
| #define LU_MENU_CKSUM "/etc/lu/menu.cksum" |
| #define BOOTADM "/sbin/bootadm" |
| |
| #define INSTALLGRUB "/sbin/installgrub" |
| #define STAGE1 "/boot/grub/stage1" |
| #define STAGE2 "/boot/grub/stage2" |
| |
| /* |
| * Default file attributes |
| */ |
| #define DEFAULT_DEV_MODE 0644 /* default permissions */ |
| #define DEFAULT_DEV_UID 0 /* user root */ |
| #define DEFAULT_DEV_GID 3 /* group sys */ |
| |
| /* |
| * Menu related |
| * menu_cmd_t and menu_cmds must be kept in sync |
| */ |
| char *menu_cmds[] = { |
| "default", /* DEFAULT_CMD */ |
| "timeout", /* TIMEOUT_CMD */ |
| "title", /* TITLE_CMD */ |
| "root", /* ROOT_CMD */ |
| "kernel", /* KERNEL_CMD */ |
| "kernel$", /* KERNEL_DOLLAR_CMD */ |
| "module", /* MODULE_CMD */ |
| "module$", /* MODULE_DOLLAR_CMD */ |
| " ", /* SEP_CMD */ |
| "#", /* COMMENT_CMD */ |
| "chainloader", /* CHAINLOADER_CMD */ |
| "args", /* ARGS_CMD */ |
| "findroot", /* FINDROOT_CMD */ |
| "bootfs", /* BOOTFS_CMD */ |
| NULL |
| }; |
| |
| char *bam_formats[] = { |
| "hsfs", |
| "ufs", |
| "cpio", |
| "ufs-nocompress", |
| NULL |
| }; |
| #define BAM_FORMAT_UNSET -1 |
| #define BAM_FORMAT_HSFS 0 |
| short bam_format = BAM_FORMAT_UNSET; |
| |
| #define OPT_ENTRY_NUM "entry" |
| |
| /* |
| * exec_cmd related |
| */ |
| typedef struct { |
| line_t *head; |
| line_t *tail; |
| } filelist_t; |
| |
| #define BOOT_FILE_LIST "boot/solaris/filelist.ramdisk" |
| #define ETC_FILE_LIST "etc/boot/solaris/filelist.ramdisk" |
| |
| #define FILE_STAT "boot/solaris/filestat.ramdisk" |
| #define FILE_STAT_TMP "boot/solaris/filestat.ramdisk.tmp" |
| #define DIR_PERMS (S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH) |
| #define FILE_STAT_MODE (S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) |
| |
| #define FILE_STAT_TIMESTAMP "boot/solaris/timestamp.cache" |
| |
| /* Globals */ |
| int bam_verbose; |
| int bam_force; |
| int bam_debug; |
| int bam_skip_lock; |
| static char *prog; |
| static subcmd_t bam_cmd; |
| char *bam_root; |
| int bam_rootlen; |
| static int bam_root_readonly; |
| int bam_alt_root; |
| static int bam_extend = 0; |
| static int bam_purge = 0; |
| static char *bam_subcmd; |
| static char *bam_opt; |
| static char **bam_argv; |
| static char *bam_pool; |
| static int bam_argc; |
| static int bam_check; |
| static int bam_saved_check; |
| static int bam_smf_check; |
| static int bam_lock_fd = -1; |
| static int bam_zfs; |
| static int bam_mbr; |
| char rootbuf[PATH_MAX] = "/"; |
| static int bam_update_all; |
| static int bam_alt_platform; |
| static char *bam_platform; |
| static char *bam_home_env = NULL; |
| |
| /* function prototypes */ |
| static void parse_args_internal(int, char *[]); |
| static void parse_args(int, char *argv[]); |
| static error_t bam_menu(char *, char *, int, char *[]); |
| static error_t bam_install(char *, char *); |
| static error_t bam_archive(char *, char *); |
| |
| static void bam_lock(void); |
| static void bam_unlock(void); |
| |
| static int exec_cmd(char *, filelist_t *); |
| static error_t read_globals(menu_t *, char *, char *, int); |
| static int menu_on_bootdisk(char *os_root, char *menu_root); |
| static menu_t *menu_read(char *); |
| static error_t menu_write(char *, menu_t *); |
| static void linelist_free(line_t *); |
| static void menu_free(menu_t *); |
| static void filelist_free(filelist_t *); |
| static error_t list2file(char *, char *, char *, line_t *); |
| static error_t list_entry(menu_t *, char *, char *); |
| static error_t list_setting(menu_t *, char *, char *); |
| static error_t delete_all_entries(menu_t *, char *, char *); |
| static error_t update_entry(menu_t *mp, char *menu_root, char *opt); |
| static error_t update_temp(menu_t *mp, char *dummy, char *opt); |
| |
| static error_t install_bootloader(void); |
| static error_t update_archive(char *, char *); |
| static error_t list_archive(char *, char *); |
| static error_t update_all(char *, char *); |
| static error_t read_list(char *, filelist_t *); |
| static error_t set_option(menu_t *, char *, char *); |
| static error_t set_kernel(menu_t *, menu_cmd_t, char *, char *, size_t); |
| static error_t get_kernel(menu_t *, menu_cmd_t, char *, size_t); |
| static char *expand_path(const char *); |
| |
| static long s_strtol(char *); |
| static int s_fputs(char *, FILE *); |
| |
| static int is_amd64(void); |
| static char *get_machine(void); |
| static void append_to_flist(filelist_t *, char *); |
| static int ufs_add_to_sign_list(char *sign); |
| static error_t synchronize_BE_menu(void); |
| |
| #if !defined(_OBP) |
| static void ucode_install(); |
| #endif |
| |
| /* Menu related sub commands */ |
| static subcmd_defn_t menu_subcmds[] = { |
| "set_option", OPT_ABSENT, set_option, 0, /* PUB */ |
| "list_entry", OPT_OPTIONAL, list_entry, 1, /* PUB */ |
| "delete_all_entries", OPT_ABSENT, delete_all_entries, 0, /* PVT */ |
| "update_entry", OPT_REQ, update_entry, 0, /* menu */ |
| "update_temp", OPT_OPTIONAL, update_temp, 0, /* reboot */ |
| "upgrade", OPT_ABSENT, upgrade_menu, 0, /* menu */ |
| "list_setting", OPT_OPTIONAL, list_setting, 1, /* menu */ |
| "disable_hypervisor", OPT_ABSENT, cvt_to_metal, 0, /* menu */ |
| "enable_hypervisor", OPT_ABSENT, cvt_to_hyper, 0, /* menu */ |
| NULL, 0, NULL, 0 /* must be last */ |
| }; |
| |
| /* Archive related sub commands */ |
| static subcmd_defn_t arch_subcmds[] = { |
| "update", OPT_ABSENT, update_archive, 0, /* PUB */ |
| "update_all", OPT_ABSENT, update_all, 0, /* PVT */ |
| "list", OPT_OPTIONAL, list_archive, 1, /* PUB */ |
| NULL, 0, NULL, 0 /* must be last */ |
| }; |
| |
| /* Install related sub commands */ |
| static subcmd_defn_t inst_subcmds[] = { |
| "install_bootloader", OPT_ABSENT, install_bootloader, 0, /* PUB */ |
| NULL, 0, NULL, 0 /* must be last */ |
| }; |
| |
| #define build_path(buf, len, root, prefix, suffix) \ |
| snprintf((buf), (len), "%s%s%s%s%s", (root), (prefix), get_machine(), \ |
| is_flag_on(IS_SPARC_TARGET) ? "" : "/amd64", (suffix)) |
| |
| /* |
| * Directory specific flags: |
| * NEED_UPDATE : the specified archive needs to be updated |
| * NO_EXTEND : don't extend the specified archive, but recreate it |
| */ |
| #define NEED_UPDATE 0x00000001 |
| #define NO_EXTEND 0x00000002 |
| |
| #define set_dir_flag(f) (walk_arg.dirinfo.flags |= (f)) |
| #define unset_dir_flag(f) (walk_arg.dirinfo.flags &= ~(f)) |
| #define is_dir_flag_on(f) (walk_arg.dirinfo.flags & (f) ? 1 : 0) |
| |
| #define get_cachedir() (walk_arg.dirinfo.cdir_path) |
| #define get_updatedir() (walk_arg.dirinfo.update_path) |
| #define get_count() (walk_arg.dirinfo.count) |
| #define has_cachedir() (walk_arg.dirinfo.has_dir) |
| #define set_dir_present() (walk_arg.dirinfo.has_dir = 1) |
| |
| /* |
| * dirinfo_t (specific cache directory information): |
| * cdir_path: path to the archive cache directory |
| * update_path: path to the update directory (contains the files that will be |
| * used to extend the archive) |
| * has_dir: the specified cache directory is active |
| * count: the number of files to update |
| * flags: directory specific flags |
| */ |
| typedef struct _dirinfo { |
| char cdir_path[PATH_MAX]; |
| char update_path[PATH_MAX]; |
| int has_dir; |
| int count; |
| int flags; |
| } dirinfo_t; |
| |
| /* |
| * Update flags: |
| * NEED_CACHE_DIR : cache directory is missing and needs to be created |
| * IS_SPARC_TARGET : the target mountpoint is a SPARC environment |
| * UPDATE_ERROR : an error occourred while traversing the list of files |
| * RDONLY_FSCHK : the target filesystem is read-only |
| * RAMDSK_FSCHK : the target filesystem is on a ramdisk |
| */ |
| #define NEED_CACHE_DIR 0x00000001 |
| #define IS_SPARC_TARGET 0x00000002 |
| #define UPDATE_ERROR 0x00000004 |
| #define RDONLY_FSCHK 0x00000008 |
| #define INVALIDATE_CACHE 0x00000010 |
| |
| #define is_flag_on(flag) (walk_arg.update_flags & flag ? 1 : 0) |
| #define set_flag(flag) (walk_arg.update_flags |= flag) |
| #define unset_flag(flag) (walk_arg.update_flags &= ~flag) |
| |
| /* |
| * struct walk_arg : |
| * update_flags: flags related to the current updating process |
| * new_nvlp/old_nvlp: new and old list of archive-files / attributes pairs |
| * sparcfile: list of file paths for mkisofs -path-list (SPARC only) |
| */ |
| static struct { |
| int update_flags; |
| nvlist_t *new_nvlp; |
| nvlist_t *old_nvlp; |
| FILE *sparcfile; |
| dirinfo_t dirinfo; |
| } walk_arg; |
| |
| struct safefile { |
| char *name; |
| struct safefile *next; |
| }; |
| |
| static struct safefile *safefiles = NULL; |
| |
| /* |
| * svc:/system/filesystem/usr:default service checks for this file and |
| * does a boot archive update and then reboot the system. |
| */ |
| #define NEED_UPDATE_FILE "/etc/svc/volatile/boot_archive_needs_update" |
| |
| /* |
| * svc:/system/boot-archive-update:default checks for this file and |
| * updates the boot archive. |
| */ |
| #define NEED_UPDATE_SAFE_FILE "/etc/svc/volatile/boot_archive_safefile_update" |
| |
| /* Thanks growisofs */ |
| #define CD_BLOCK ((off64_t)2048) |
| #define VOLDESC_OFF 16 |
| #define DVD_BLOCK (32*1024) |
| #define MAX_IVDs 16 |
| |
| struct iso_pdesc { |
| unsigned char type [1]; |
| unsigned char id [5]; |
| unsigned char void1 [80-5-1]; |
| unsigned char volume_space_size [8]; |
| unsigned char void2 [2048-80-8]; |
| }; |
| |
| /* |
| * COUNT_MAX: maximum number of changed files to justify a multisession update |
| * BA_SIZE_MAX: maximum size of the boot_archive to justify a multisession |
| * update |
| */ |
| #define COUNT_MAX 50 |
| #define BA_SIZE_MAX (50 * 1024 * 1024) |
| |
| #define bam_nowrite() (bam_check || bam_smf_check) |
| |
| static int sync_menu = 1; /* whether we need to sync the BE menus */ |
| |
| static void |
| usage(void) |
| { |
| (void) fprintf(stderr, "USAGE:\n"); |
| |
| /* archive usage */ |
| (void) fprintf(stderr, |
| "\t%s update-archive [-vnf] [-R altroot [-p platform]] " |
| "[-F format]\n", prog); |
| (void) fprintf(stderr, |
| "\t%s list-archive [-R altroot [-p platform]]\n", prog); |
| #if defined(_OBP) |
| (void) fprintf(stderr, |
| "\t%s install-bootloader [-fv] [-R altroot] [-P pool]\n", prog); |
| #else |
| (void) fprintf(stderr, |
| "\t%s install-bootloader [-Mfv] [-R altroot] [-P pool]\n", prog); |
| #endif |
| #if !defined(_OBP) |
| /* x86 only */ |
| (void) fprintf(stderr, "\t%s set-menu [-R altroot] key=value\n", prog); |
| (void) fprintf(stderr, "\t%s list-menu [-R altroot]\n", prog); |
| #endif |
| } |
| |
| /* |
| * Best effort attempt to restore the $HOME value. |
| */ |
| static void |
| restore_env() |
| { |
| char home_env[PATH_MAX]; |
| |
| if (bam_home_env) { |
| (void) snprintf(home_env, sizeof (home_env), "HOME=%s", |
| bam_home_env); |
| (void) putenv(home_env); |
| } |
| } |
| |
| |
| #define SLEEP_TIME 5 |
| #define MAX_TRIES 4 |
| |
| /* |
| * Sanitize the environment in which bootadm will execute its sub-processes |
| * (ex. mkisofs). This is done to prevent those processes from attempting |
| * to access files (ex. .mkisofsrc) or stat paths that might be on NFS |
| * or, potentially, insecure. |
| */ |
| static void |
| sanitize_env() |
| { |
| int stry = 0; |
| |
| /* don't depend on caller umask */ |
| (void) umask(0022); |
| |
| /* move away from a potential unsafe current working directory */ |
| while (chdir("/") == -1) { |
| if (errno != EINTR) { |
| bam_print("WARNING: unable to chdir to /"); |
| break; |
| } |
| } |
| |
| bam_home_env = getenv("HOME"); |
| while (bam_home_env != NULL && putenv("HOME=/") == -1) { |
| if (errno == ENOMEM) { |
| /* retry no more than MAX_TRIES times */ |
| if (++stry > MAX_TRIES) { |
| bam_print("WARNING: unable to recover from " |
| "system memory pressure... aborting \n"); |
| bam_exit(EXIT_FAILURE); |
| } |
| /* memory is tight, try to sleep */ |
| bam_print("Attempting to recover from memory pressure: " |
| "sleeping for %d seconds\n", SLEEP_TIME * stry); |
| (void) sleep(SLEEP_TIME * stry); |
| } else { |
| bam_print("WARNING: unable to sanitize HOME\n"); |
| } |
| } |
| } |
| |
| int |
| main(int argc, char *argv[]) |
| { |
| error_t ret = BAM_SUCCESS; |
| |
| (void) setlocale(LC_ALL, ""); |
| (void) textdomain(TEXT_DOMAIN); |
| |
| if ((prog = strrchr(argv[0], '/')) == NULL) { |
| prog = argv[0]; |
| } else { |
| prog++; |
| } |
| |
| INJECT_ERROR1("ASSERT_ON", assert(0)) |
| |
| sanitize_env(); |
| |
| parse_args(argc, argv); |
| |
| switch (bam_cmd) { |
| case BAM_MENU: |
| if (is_grub(bam_alt_root ? bam_root : "/")) { |
| ret = bam_menu(bam_subcmd, bam_opt, |
| bam_argc, bam_argv); |
| } else { |
| ret = bam_loader_menu(bam_subcmd, bam_opt, |
| bam_argc, bam_argv); |
| } |
| break; |
| case BAM_ARCHIVE: |
| ret = bam_archive(bam_subcmd, bam_opt); |
| break; |
| case BAM_INSTALL: |
| ret = bam_install(bam_subcmd, bam_opt); |
| break; |
| default: |
| usage(); |
| bam_exit(1); |
| } |
| |
| if (ret != BAM_SUCCESS) |
| bam_exit((ret == BAM_NOCHANGE) ? 2 : 1); |
| |
| bam_unlock(); |
| return (0); |
| } |
| |
| /* |
| * Equivalence of public and internal commands: |
| * update-archive -- -a update |
| * list-archive -- -a list |
| * set-menu -- -m set_option |
| * list-menu -- -m list_entry |
| * update-menu -- -m update_entry |
| * install-bootloader -- -i install_bootloader |
| */ |
| static struct cmd_map { |
| char *bam_cmdname; |
| int bam_cmd; |
| char *bam_subcmd; |
| } cmd_map[] = { |
| { "update-archive", BAM_ARCHIVE, "update"}, |
| { "list-archive", BAM_ARCHIVE, "list"}, |
| { "set-menu", BAM_MENU, "set_option"}, |
| { "list-menu", BAM_MENU, "list_entry"}, |
| { "update-menu", BAM_MENU, "update_entry"}, |
| { "install-bootloader", BAM_INSTALL, "install_bootloader"}, |
| { NULL, 0, NULL} |
| }; |
| |
| /* |
| * Commands syntax published in bootadm(1M) are parsed here |
| */ |
| static void |
| parse_args(int argc, char *argv[]) |
| { |
| struct cmd_map *cmp = cmd_map; |
| |
| /* command conforming to the final spec */ |
| if (argc > 1 && argv[1][0] != '-') { |
| /* |
| * Map commands to internal table. |
| */ |
| while (cmp->bam_cmdname) { |
| if (strcmp(argv[1], cmp->bam_cmdname) == 0) { |
| bam_cmd = cmp->bam_cmd; |
| bam_subcmd = cmp->bam_subcmd; |
| break; |
| } |
| cmp++; |
| } |
| if (cmp->bam_cmdname == NULL) { |
| usage(); |
| bam_exit(1); |
| } |
| argc--; |
| argv++; |
| } |
| |
| parse_args_internal(argc, argv); |
| } |
| |
| /* |
| * A combination of public and private commands are parsed here. |
| * The internal syntax and the corresponding functionality are: |
| * -a update -- update-archive |
| * -a list -- list-archive |
| * -a update_all -- (reboot to sync all mnted OS archive) |
| * -i install_bootloader -- install-bootloader |
| * -m update_entry -- update-menu |
| * -m list_entry -- list-menu |
| * -m update_temp -- (reboot -- [boot-args]) |
| * -m delete_all_entries -- (called from install) |
| * -m enable_hypervisor [args] -- cvt_to_hyper |
| * -m disable_hypervisor -- cvt_to_metal |
| * -m list_setting [entry] [value] -- list_setting |
| * |
| * A set of private flags is there too: |
| * -Q -- purge the cache directories and rebuild them |
| * -e -- use the (faster) archive update approach (used by |
| * reboot) |
| * -L -- skip locking |
| */ |
| static void |
| parse_args_internal(int argc, char *argv[]) |
| { |
| int c, i, error; |
| extern char *optarg; |
| extern int optind, opterr; |
| #if defined(_OBP) |
| const char *optstring = "a:d:fF:i:m:no:veQCLR:p:P:XZ"; |
| #else |
| const char *optstring = "a:d:fF:i:m:no:veQCMLR:p:P:XZ"; |
| #endif |
| |
| /* Suppress error message from getopt */ |
| opterr = 0; |
| |
| error = 0; |
| while ((c = getopt(argc, argv, optstring)) != -1) { |
| switch (c) { |
| case 'a': |
| if (bam_cmd) { |
| error = 1; |
| bam_error( |
| _("multiple commands specified: -%c\n"), c); |
| } |
| bam_cmd = BAM_ARCHIVE; |
| bam_subcmd = optarg; |
| break; |
| case 'd': |
| if (bam_debug) { |
| error = 1; |
| bam_error( |
| _("duplicate options specified: -%c\n"), c); |
| } |
| bam_debug = s_strtol(optarg); |
| break; |
| case 'f': |
| bam_force = 1; |
| break; |
| case 'F': |
| if (bam_format != BAM_FORMAT_UNSET) { |
| error = 1; |
| bam_error( |
| _("multiple formats specified: -%c\n"), c); |
| } |
| for (i = 0; bam_formats[i] != NULL; i++) { |
| if (strcmp(bam_formats[i], optarg) == 0) { |
| bam_format = i; |
| break; |
| } |
| } |
| if (bam_format == BAM_FORMAT_UNSET) { |
| error = 1; |
| bam_error( |
| _("unknown format specified: -%c %s\n"), |
| c, optarg); |
| } |
| break; |
| case 'Q': |
| bam_purge = 1; |
| break; |
| case 'L': |
| bam_skip_lock = 1; |
| break; |
| case 'i': |
| if (bam_cmd) { |
| error = 1; |
| bam_error( |
| _("multiple commands specified: -%c\n"), c); |
| } |
| bam_cmd = BAM_INSTALL; |
| bam_subcmd = optarg; |
| break; |
| case 'm': |
| if (bam_cmd) { |
| error = 1; |
| bam_error( |
| _("multiple commands specified: -%c\n"), c); |
| } |
| bam_cmd = BAM_MENU; |
| bam_subcmd = optarg; |
| break; |
| #if !defined(_OBP) |
| case 'M': |
| bam_mbr = 1; |
| break; |
| #endif |
| case 'n': |
| bam_check = 1; |
| /* |
| * We save the original value of bam_check. The new |
| * approach in case of a read-only filesystem is to |
| * behave as a check, so we need a way to restore the |
| * original value after the evaluation of the read-only |
| * filesystem has been done. |
| * Even if we don't allow at the moment a check with |
| * update_all, this approach is more robust than |
| * simply resetting bam_check to zero. |
| */ |
| bam_saved_check = 1; |
| break; |
| case 'o': |
| if (bam_opt) { |
| error = 1; |
| bam_error( |
| _("duplicate options specified: -%c\n"), c); |
| } |
| bam_opt = optarg; |
| break; |
| case 'v': |
| bam_verbose = 1; |
| break; |
| case 'C': |
| bam_smf_check = 1; |
| break; |
| case 'P': |
| if (bam_pool != NULL) { |
| error = 1; |
| bam_error( |
| _("duplicate options specified: -%c\n"), c); |
| } |
| bam_pool = optarg; |
| break; |
| case 'R': |
| if (bam_root) { |
| error = 1; |
| bam_error( |
| _("duplicate options specified: -%c\n"), c); |
| break; |
| } else if (realpath(optarg, rootbuf) == NULL) { |
| error = 1; |
| bam_error(_("cannot resolve path %s: %s\n"), |
| optarg, strerror(errno)); |
| break; |
| } |
| bam_alt_root = 1; |
| bam_root = rootbuf; |
| bam_rootlen = strlen(rootbuf); |
| break; |
| case 'p': |
| bam_alt_platform = 1; |
| bam_platform = optarg; |
| if ((strcmp(bam_platform, "i86pc") != 0) && |
| (strcmp(bam_platform, "sun4u") != 0) && |
| (strcmp(bam_platform, "sun4v") != 0)) { |
| error = 1; |
| bam_error(_("invalid platform %s - must be " |
| "one of sun4u, sun4v or i86pc\n"), |
| bam_platform); |
| } |
| break; |
| case 'X': |
| bam_is_hv = BAM_HV_PRESENT; |
| break; |
| case 'Z': |
| bam_zfs = 1; |
| break; |
| case 'e': |
| bam_extend = 1; |
| break; |
| case '?': |
| error = 1; |
| bam_error(_("invalid option or missing option " |
| "argument: -%c\n"), optopt); |
| break; |
| default : |
| error = 1; |
| bam_error(_("invalid option or missing option " |
| "argument: -%c\n"), c); |
| break; |
| } |
| } |
| |
| /* |
| * An alternate platform requires an alternate root |
| */ |
| if (bam_alt_platform && bam_alt_root == 0) { |
| usage(); |
| bam_exit(0); |
| } |
| |
| /* |
| * A command option must be specfied |
| */ |
| if (!bam_cmd) { |
| if (bam_opt && strcmp(bam_opt, "all") == 0) { |
| usage(); |
| bam_exit(0); |
| } |
| bam_error(_("a command option must be specified\n")); |
| error = 1; |
| } |
| |
| if (error) { |
| usage(); |
| bam_exit(1); |
| } |
| |
| if (optind > argc) { |
| bam_error(_("Internal error: %s\n"), "parse_args"); |
| bam_exit(1); |
| } else if (optind < argc) { |
| bam_argv = &argv[optind]; |
| bam_argc = argc - optind; |
| } |
| |
| /* |
| * mbr and pool are options for install_bootloader |
| */ |
| if (bam_cmd != BAM_INSTALL && (bam_mbr || bam_pool != NULL)) { |
| usage(); |
| bam_exit(0); |
| } |
| |
| /* |
| * -n implies verbose mode |
| */ |
| if (bam_check) |
| bam_verbose = 1; |
| } |
| |
| error_t |
| check_subcmd_and_options( |
| char *subcmd, |
| char *opt, |
| subcmd_defn_t *table, |
| error_t (**fp)()) |
| { |
| int i; |
| |
| if (subcmd == NULL) { |
| bam_error(_("this command requires a sub-command\n")); |
| return (BAM_ERROR); |
| } |
| |
| if (strcmp(subcmd, "set_option") == 0) { |
| if (bam_argc == 0 || bam_argv == NULL || bam_argv[0] == NULL) { |
| bam_error(_("missing argument for sub-command\n")); |
| usage(); |
| return (BAM_ERROR); |
| } else if (bam_argc > 1 || bam_argv[1] != NULL) { |
| bam_error(_("invalid trailing arguments\n")); |
| usage(); |
| return (BAM_ERROR); |
| } |
| } else if (strcmp(subcmd, "update_all") == 0) { |
| /* |
| * The only option we accept for the "update_all" |
| * subcmd is "fastboot". |
| */ |
| if (bam_argc > 1 || (bam_argc == 1 && |
| strcmp(bam_argv[0], "fastboot") != 0)) { |
| bam_error(_("invalid trailing arguments\n")); |
| usage(); |
| return (BAM_ERROR); |
| } |
| if (bam_argc == 1) |
| sync_menu = 0; |
| } else if (((strcmp(subcmd, "enable_hypervisor") != 0) && |
| (strcmp(subcmd, "list_setting") != 0)) && (bam_argc || bam_argv)) { |
| /* |
| * Of the remaining subcommands, only "enable_hypervisor" and |
| * "list_setting" take trailing arguments. |
| */ |
| bam_error(_("invalid trailing arguments\n")); |
| usage(); |
| return (BAM_ERROR); |
| } |
| |
| if (bam_root == NULL) { |
| bam_root = rootbuf; |
| bam_rootlen = 1; |
| } |
| |
| /* verify that subcmd is valid */ |
| for (i = 0; table[i].subcmd != NULL; i++) { |
| if (strcmp(table[i].subcmd, subcmd) == 0) |
| break; |
| } |
| |
| if (table[i].subcmd == NULL) { |
| bam_error(_("invalid sub-command specified: %s\n"), subcmd); |
| return (BAM_ERROR); |
| } |
| |
| if (table[i].unpriv == 0 && geteuid() != 0) { |
| bam_error(_("you must be root to run this command\n")); |
| return (BAM_ERROR); |
| } |
| |
| /* |
| * Currently only privileged commands need a lock |
| */ |
| if (table[i].unpriv == 0) |
| bam_lock(); |
| |
| /* subcmd verifies that opt is appropriate */ |
| if (table[i].option != OPT_OPTIONAL) { |
| if ((table[i].option == OPT_REQ) ^ (opt != NULL)) { |
| if (opt) |
| bam_error(_("this sub-command (%s) does not " |
| "take options\n"), subcmd); |
| else |
| bam_error(_("an option is required for this " |
| "sub-command: %s\n"), subcmd); |
| return (BAM_ERROR); |
| } |
| } |
| |
| *fp = table[i].handler; |
| |
| return (BAM_SUCCESS); |
| } |
| |
| /* |
| * NOTE: A single "/" is also considered a trailing slash and will |
| * be deleted. |
| */ |
| void |
| elide_trailing_slash(const char *src, char *dst, size_t dstsize) |
| { |
| size_t dstlen; |
| |
| assert(src); |
| assert(dst); |
| |
| (void) strlcpy(dst, src, dstsize); |
| |
| dstlen = strlen(dst); |
| if (dst[dstlen - 1] == '/') { |
| dst[dstlen - 1] = '\0'; |
| } |
| } |
| |
| static int |
| is_safe_exec(char *path) |
| { |
| struct stat sb; |
| |
| if (lstat(path, &sb) != 0) { |
| bam_error(_("stat of file failed: %s: %s\n"), path, |
| strerror(errno)); |
| return (BAM_ERROR); |
| } |
| |
| if (!S_ISREG(sb.st_mode)) { |
| bam_error(_("%s is not a regular file, skipping\n"), path); |
| return (BAM_ERROR); |
| } |
| |
| if (sb.st_uid != getuid()) { |
| bam_error(_("%s is not owned by %d, skipping\n"), |
| path, getuid()); |
| return (BAM_ERROR); |
| } |
| |
| if (sb.st_mode & S_IWOTH || sb.st_mode & S_IWGRP) { |
| bam_error(_("%s is others or group writable, skipping\n"), |
| path); |
| return (BAM_ERROR); |
| } |
| |
| return (BAM_SUCCESS); |
| } |
| |
| static error_t |
| list_setting(menu_t *mp, char *which, char *setting) |
| { |
| line_t *lp; |
| entry_t *ent; |
| |
| char *p = which; |
| int entry; |
| |
| int found; |
| |
| assert(which); |
| assert(setting); |
| |
| if (*which != NULL) { |
| /* |
| * If "which" is not a number, assume it's a setting we want |
| * to look for and so set up the routine to look for "which" |
| * in the default entry. |
| */ |
| while (*p != NULL) |
| if (!(isdigit((int)*p++))) { |
| setting = which; |
| which = mp->curdefault->arg; |
| break; |
| } |
| } else { |
| which = mp->curdefault->arg; |
| } |
| |
| entry = atoi(which); |
| |
| for (ent = mp->entries; ((ent != NULL) && (ent->entryNum != entry)); |
| ent = ent->next) |
| ; |
| |
| if (!ent) { |
| bam_error(_("no matching entry found\n")); |
| return (BAM_ERROR); |
| } |
| |
| found = (*setting == NULL); |
| |
| for (lp = ent->start; lp != NULL; lp = lp->next) { |
| if ((*setting == NULL) && (lp->flags != BAM_COMMENT)) |
| bam_print("%s\n", lp->line); |
| else if (lp->cmd != NULL && strcmp(setting, lp->cmd) == 0) { |
| bam_print("%s\n", lp->arg); |
| found = 1; |
| } |
| |
| if (lp == ent->end) |
| break; |
| } |
| |
| if (!found) { |
| bam_error(_("no matching entry found\n")); |
| return (BAM_ERROR); |
| } |
| |
| return (BAM_SUCCESS); |
| } |
| |
| static error_t |
| install_bootloader(void) |
| { |
| nvlist_t *nvl; |
| uint16_t flags = 0; |
| int found = 0; |
| struct extmnttab mnt; |
| struct stat statbuf = {0}; |
| be_node_list_t *be_nodes, *node; |
| FILE *fp; |
| char *root_ds = NULL; |
| int ret = BAM_ERROR; |
| |
| if (nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0) != 0) { |
| bam_error(_("out of memory\n")); |
| return (ret); |
| } |
| |
| /* |
| * if bam_alt_root is set, the stage files are used from alt root. |
| * if pool is set, the target devices are pool devices, stage files |
| * are read from pool bootfs unless alt root is set. |
| * |
| * use arguments as targets, stage files are from alt or current root |
| * if no arguments and no pool, install on current boot pool. |
| */ |
| |
| if (bam_alt_root) { |
| if (stat(bam_root, &statbuf) != 0) { |
| bam_error(_("stat of file failed: %s: %s\n"), bam_root, |
| strerror(errno)); |
| goto done; |
| } |
| if ((fp = fopen(MNTTAB, "r")) == NULL) { |
| bam_error(_("failed to open file: %s: %s\n"), |
| MNTTAB, strerror(errno)); |
| goto done; |
| } |
| resetmnttab(fp); |
| while (getextmntent(fp, &mnt, sizeof (mnt)) == 0) { |
| if (mnt.mnt_major == major(statbuf.st_dev) && |
| mnt.mnt_minor == minor(statbuf.st_dev)) { |
| found = 1; |
| root_ds = strdup(mnt.mnt_special); |
| break; |
| } |
| } |
| (void) fclose(fp); |
| |
| if (found == 0) { |
| bam_error(_("alternate root %s not in mnttab\n"), |
| bam_root); |
| goto done; |
| } |
| if (root_ds == NULL) { |
| bam_error(_("out of memory\n")); |
| goto done; |
| } |
| |
| if (be_list(NULL, &be_nodes, BE_LIST_DEFAULT) != BE_SUCCESS) { |
| bam_error(_("No BE's found\n")); |
| goto done; |
| } |
| for (node = be_nodes; node != NULL; node = node->be_next_node) |
| if (strcmp(root_ds, node->be_root_ds) == 0) |
| break; |
| |
| if (node == NULL) |
| bam_error(_("BE (%s) does not exist\n"), root_ds); |
| |
| free(root_ds); |
| root_ds = NULL; |
| if (node == NULL) { |
| be_free_list(be_nodes); |
| goto done; |
| } |
| ret = nvlist_add_string(nvl, BE_ATTR_ORIG_BE_NAME, |
| node->be_node_name); |
| ret |= nvlist_add_string(nvl, BE_ATTR_ORIG_BE_ROOT, |
| node->be_root_ds); |
| be_free_list(be_nodes); |
| if (ret != 0) { |
| ret = BAM_ERROR; |
| goto done; |
| } |
| } |
| |
| if (bam_force) |
| flags |= BE_INSTALLBOOT_FLAG_FORCE; |
| if (bam_mbr) |
| flags |= BE_INSTALLBOOT_FLAG_MBR; |
| if (bam_verbose) |
| flags |= BE_INSTALLBOOT_FLAG_VERBOSE; |
| |
| if (nvlist_add_uint16(nvl, BE_ATTR_INSTALL_FLAGS, flags) != 0) { |
| bam_error(_("out of memory\n")); |
| ret = BAM_ERROR; |
| goto done; |
| } |
| |
| /* |
| * if altroot was set, we got be name and be root, only need |
| * to set pool name as target. |
| * if no altroot, need to find be name and root from pool. |
| */ |
| if (bam_pool != NULL) { |
| ret = nvlist_add_string(nvl, BE_ATTR_ORIG_BE_POOL, bam_pool); |
| if (ret != 0) { |
| ret = BAM_ERROR; |
| goto done; |
| } |
| if (found) { |
| ret = be_installboot(nvl); |
| if (ret != 0) |
| ret = BAM_ERROR; |
| goto done; |
| } |
| } |
| |
| if (be_list(NULL, &be_nodes, BE_LIST_DEFAULT) != BE_SUCCESS) { |
| bam_error(_("No BE's found\n")); |
| ret = BAM_ERROR; |
| goto done; |
| } |
| |
| if (bam_pool != NULL) { |
| /* |
| * find active be_node in bam_pool |
| */ |
| for (node = be_nodes; node != NULL; node = node->be_next_node) { |
| if (strcmp(bam_pool, node->be_rpool) != 0) |
| continue; |
| if (node->be_active_on_boot) |
| break; |
| } |
| if (node == NULL) { |
| bam_error(_("No active BE in %s\n"), bam_pool); |
| be_free_list(be_nodes); |
| ret = BAM_ERROR; |
| goto done; |
| } |
| ret = nvlist_add_string(nvl, BE_ATTR_ORIG_BE_NAME, |
| node->be_node_name); |
| ret |= nvlist_add_string(nvl, BE_ATTR_ORIG_BE_ROOT, |
| node->be_root_ds); |
| be_free_list(be_nodes); |
| if (ret != 0) { |
| ret = BAM_ERROR; |
| goto done; |
| } |
| ret = be_installboot(nvl); |
| if (ret != 0) |
| ret = BAM_ERROR; |
| goto done; |
| } |
| |
| /* |
| * get dataset for "/" and fill up the args. |
| */ |
| if ((fp = fopen(MNTTAB, "r")) == NULL) { |
| bam_error(_("failed to open file: %s: %s\n"), |
| MNTTAB, strerror(errno)); |
| ret = BAM_ERROR; |
| be_free_list(be_nodes); |
| goto done; |
| } |
| resetmnttab(fp); |
| found = 0; |
| while (getextmntent(fp, &mnt, sizeof (mnt)) == 0) { |
| if (strcmp(mnt.mnt_mountp, "/") == 0) { |
| found = 1; |
| root_ds = strdup(mnt.mnt_special); |
| break; |
| } |
| } |
| (void) fclose(fp); |
| |
| if (found == 0) { |
| bam_error(_("alternate root %s not in mnttab\n"), "/"); |
| ret = BAM_ERROR; |
| be_free_list(be_nodes); |
| goto done; |
| } |
| if (root_ds == NULL) { |
| bam_error(_("out of memory\n")); |
| ret = BAM_ERROR; |
| be_free_list(be_nodes); |
| goto done; |
| } |
| |
| for (node = be_nodes; node != NULL; node = node->be_next_node) { |
| if (strcmp(root_ds, node->be_root_ds) == 0) |
| break; |
| } |
| |
| if (node == NULL) { |
| bam_error(_("No such BE: %s\n"), root_ds); |
| free(root_ds); |
| be_free_list(be_nodes); |
| ret = BAM_ERROR; |
| goto done; |
| } |
| free(root_ds); |
| |
| ret = nvlist_add_string(nvl, BE_ATTR_ORIG_BE_NAME, node->be_node_name); |
| ret |= nvlist_add_string(nvl, BE_ATTR_ORIG_BE_ROOT, node->be_root_ds); |
| ret |= nvlist_add_string(nvl, BE_ATTR_ORIG_BE_POOL, node->be_rpool); |
| be_free_list(be_nodes); |
| |
| if (ret != 0) |
| ret = BAM_ERROR; |
| else |
| ret = be_installboot(nvl) ? BAM_ERROR : 0; |
| done: |
| nvlist_free(nvl); |
| |
| return (ret); |
| } |
| |
| static error_t |
| bam_install(char *subcmd, char *opt) |
| { |
| error_t (*f)(void); |
| |
| /* |
| * Check arguments |
| */ |
| if (check_subcmd_and_options(subcmd, opt, inst_subcmds, &f) == |
| BAM_ERROR) |
| return (BAM_ERROR); |
| |
| return (f()); |
| } |
| |
| static error_t |
| bam_menu(char *subcmd, char *opt, int largc, char *largv[]) |
| { |
| error_t ret; |
| char menu_path[PATH_MAX]; |
| char clean_menu_root[PATH_MAX]; |
| char path[PATH_MAX]; |
| menu_t *menu; |
| char menu_root[PATH_MAX]; |
| struct stat sb; |
| error_t (*f)(menu_t *mp, char *menu_path, char *opt); |
| char *special = NULL; |
| char *pool = NULL; |
| zfs_mnted_t zmnted; |
| char *zmntpt = NULL; |
| char *osdev; |
| char *osroot; |
| const char *fcn = "bam_menu()"; |
| |
| /* |
| * Menu sub-command only applies to GRUB (i.e. x86) |
| */ |
| if (!is_grub(bam_alt_root ? bam_root : "/")) { |
| bam_error(_("not a GRUB 0.97 based Illumos instance. " |
| "Operation not supported\n")); |
| return (BAM_ERROR); |
| } |
| |
| /* |
| * Check arguments |
| */ |
| ret = check_subcmd_and_options(subcmd, opt, menu_subcmds, &f); |
| if (ret == BAM_ERROR) { |
| return (BAM_ERROR); |
| } |
| |
| assert(bam_root); |
| |
| (void) strlcpy(menu_root, bam_root, sizeof (menu_root)); |
| osdev = osroot = NULL; |
| |
| if (strcmp(subcmd, "update_entry") == 0) { |
| assert(opt); |
| |
| osdev = strtok(opt, ","); |
| assert(osdev); |
| osroot = strtok(NULL, ","); |
| if (osroot) { |
| /* fixup bam_root so that it points at osroot */ |
| if (realpath(osroot, rootbuf) == NULL) { |
| bam_error(_("cannot resolve path %s: %s\n"), |
| osroot, strerror(errno)); |
| return (BAM_ERROR); |
| } |
| bam_alt_root = 1; |
| bam_root = rootbuf; |
| bam_rootlen = strlen(rootbuf); |
| } |
| } |
| |
| /* |
| * We support menu on PCFS (under certain conditions), but |
| * not the OS root |
| */ |
| if (is_pcfs(bam_root)) { |
| bam_error(_("root <%s> on PCFS is not supported\n"), bam_root); |
| return (BAM_ERROR); |
| } |
| |
| if (stat(menu_root, &sb) == -1) { |
| bam_error(_("cannot find GRUB menu\n")); |
| return (BAM_ERROR); |
| } |
| |
| BAM_DPRINTF(("%s: menu root is %s\n", fcn, menu_root)); |
| |
| /* |
| * We no longer use the GRUB slice file. If it exists, then |
| * the user is doing something that is unsupported (such as |
| * standard upgrading an old Live Upgrade BE). If that |
| * happens, mimic existing behavior i.e. pretend that it is |
| * not a BE. Emit a warning though. |
| */ |
| if (bam_alt_root) { |
| (void) snprintf(path, sizeof (path), "%s%s", bam_root, |
| GRUB_slice); |
| } else { |
| (void) snprintf(path, sizeof (path), "%s", GRUB_slice); |
| } |
| |
| if (bam_verbose && stat(path, &sb) == 0) |
| bam_error(_("unsupported GRUB slice file (%s) exists - " |
| "ignoring.\n"), path); |
| |
| if (is_zfs(menu_root)) { |
| assert(strcmp(menu_root, bam_root) == 0); |
| special = get_special(menu_root); |
| INJECT_ERROR1("Z_MENU_GET_SPECIAL", special = NULL); |
| if (special == NULL) { |
| bam_error(_("cant find special file for " |
| "mount-point %s\n"), menu_root); |
| return (BAM_ERROR); |
| } |
| pool = strtok(special, "/"); |
| INJECT_ERROR1("Z_MENU_GET_POOL", pool = NULL); |
| if (pool == NULL) { |
| free(special); |
| bam_error(_("cant find pool for mount-point %s\n"), |
| menu_root); |
| return (BAM_ERROR); |
| } |
| BAM_DPRINTF(("%s: derived pool=%s from special\n", fcn, pool)); |
| |
| zmntpt = mount_top_dataset(pool, &zmnted); |
| INJECT_ERROR1("Z_MENU_MOUNT_TOP_DATASET", zmntpt = NULL); |
| if (zmntpt == NULL) { |
| bam_error(_("cannot mount pool dataset for pool: %s\n"), |
| pool); |
| free(special); |
| return (BAM_ERROR); |
| } |
| BAM_DPRINTF(("%s: top dataset mountpoint=%s\n", fcn, zmntpt)); |
| |
| (void) strlcpy(menu_root, zmntpt, sizeof (menu_root)); |
| BAM_DPRINTF(("%s: zfs menu_root=%s\n", fcn, menu_root)); |
| } |
| |
| elide_trailing_slash(menu_root, clean_menu_root, |
| sizeof (clean_menu_root)); |
| |
| BAM_DPRINTF(("%s: cleaned menu root is <%s>\n", fcn, clean_menu_root)); |
| |
| (void) strlcpy(menu_path, clean_menu_root, sizeof (menu_path)); |
| (void) strlcat(menu_path, GRUB_MENU, sizeof (menu_path)); |
| |
| BAM_DPRINTF(("%s: menu path is: %s\n", fcn, menu_path)); |
| |
| /* |
| * If listing the menu, display the menu location |
| */ |
| if (strcmp(subcmd, "list_entry") == 0) |
| bam_print(_("the location for the active GRUB menu is: %s\n"), |
| menu_path); |
| |
| if ((menu = menu_read(menu_path)) == NULL) { |
| bam_error(_("cannot find GRUB menu file: %s\n"), menu_path); |
| free(special); |
| |
| return (BAM_ERROR); |
| } |
| |
| /* |
| * We already checked the following case in |
| * check_subcmd_and_suboptions() above. Complete the |
| * final step now. |
| */ |
| if (strcmp(subcmd, "set_option") == 0) { |
| assert(largc == 1 && largv[0] && largv[1] == NULL); |
| opt = largv[0]; |
| } else if ((strcmp(subcmd, "enable_hypervisor") != 0) && |
| (strcmp(subcmd, "list_setting") != 0)) { |
| assert(largc == 0 && largv == NULL); |
| } |
| |
| ret = get_boot_cap(bam_root); |
| if (ret != BAM_SUCCESS) { |
| BAM_DPRINTF(("%s: Failed to get boot capability\n", fcn)); |
| goto out; |
| } |
| |
| /* |
| * Once the sub-cmd handler has run |
| * only the line field is guaranteed to have valid values |
| */ |
| if (strcmp(subcmd, "update_entry") == 0) { |
| ret = f(menu, menu_root, osdev); |
| } else if (strcmp(subcmd, "upgrade") == 0) { |
| ret = f(menu, bam_root, menu_root); |
| } else if (strcmp(subcmd, "list_entry") == 0) { |
| ret = f(menu, menu_path, opt); |
| } else if (strcmp(subcmd, "list_setting") == 0) { |
| ret = f(menu, ((largc > 0) ? largv[0] : ""), |
| ((largc > 1) ? largv[1] : "")); |
| } else if (strcmp(subcmd, "disable_hypervisor") == 0) { |
| if (is_sparc()) { |
| bam_error(_("%s operation unsupported on SPARC " |
| "machines\n"), subcmd); |
| ret = BAM_ERROR; |
| } else { |
| ret = f(menu, bam_root, NULL); |
| } |
| } else if (strcmp(subcmd, "enable_hypervisor") == 0) { |
| if (is_sparc()) { |
| bam_error(_("%s operation unsupported on SPARC " |
| "machines\n"), subcmd); |
| ret = BAM_ERROR; |
| } else { |
| char *extra_args = NULL; |
| |
| /* |
| * Compress all arguments passed in the largv[] array |
| * into one string that can then be appended to the |
| * end of the kernel$ string the routine to enable the |
| * hypervisor will build. |
| * |
| * This allows the caller to supply arbitrary unparsed |
| * arguments, such as dom0 memory settings or APIC |
| * options. |
| * |
| * This concatenation will be done without ANY syntax |
| * checking whatsoever, so it's the responsibility of |
| * the caller to make sure the arguments are valid and |
| * do not duplicate arguments the conversion routines |
| * may create. |
| */ |
| if (largc > 0) { |
| int extra_len, i; |
| |
| for (extra_len = 0, i = 0; i < largc; i++) |
| extra_len += strlen(largv[i]); |
| |
| /* |
| * Allocate space for argument strings, |
| * intervening spaces and terminating NULL. |
| */ |
| extra_args = alloca(extra_len + largc); |
| |
| (void) strcpy(extra_args, largv[0]); |
| |
| for (i = 1; i < largc; i++) { |
| (void) strcat(extra_args, " "); |
| (void) strcat(extra_args, largv[i]); |
| } |
| } |
| |
| ret = f(menu, bam_root, extra_args); |
| } |
| } else |
| ret = f(menu, NULL, opt); |
| |
| if (ret == BAM_WRITE) { |
| BAM_DPRINTF(("%s: writing menu to clean-menu-root: <%s>\n", |
| fcn, clean_menu_root)); |
| ret = menu_write(clean_menu_root, menu); |
| } |
| |
| out: |
| INJECT_ERROR1("POOL_SET", pool = "/pooldata"); |
| assert((is_zfs(menu_root)) ^ (pool == NULL)); |
| if (pool) { |
| (void) umount_top_dataset(pool, zmnted, zmntpt); |
| free(special); |
| } |
| menu_free(menu); |
| return (ret); |
| } |
| |
| |
| static error_t |
| bam_archive( |
| char *subcmd, |
| char *opt) |
| { |
| error_t ret; |
| error_t (*f)(char *root, char *opt); |
| const char *fcn = "bam_archive()"; |
| |
| /* |
| * Add trailing / for archive subcommands |
| */ |
| if (rootbuf[strlen(rootbuf) - 1] != '/') |
| (void) strcat(rootbuf, "/"); |
| bam_rootlen = strlen(rootbuf); |
| |
| /* |
| * Check arguments |
| */ |
| ret = check_subcmd_and_options(subcmd, opt, arch_subcmds, &f); |
| if (ret != BAM_SUCCESS) { |
| return (BAM_ERROR); |
| } |
| |
| ret = get_boot_cap(rootbuf); |
| if (ret != BAM_SUCCESS) { |
| BAM_DPRINTF(("%s: Failed to get boot capability\n", fcn)); |
| return (ret); |
| } |
| |
| /* |
| * Check archive not supported with update_all |
| * since it is awkward to display out-of-sync |
| * information for each BE. |
| */ |
| if (bam_check && strcmp(subcmd, "update_all") == 0) { |
| bam_error(_("the check option is not supported with " |
| "subcmd: %s\n"), subcmd); |
| return (BAM_ERROR); |
| } |
| |
| if (strcmp(subcmd, "update_all") == 0) |
| bam_update_all = 1; |
| |
| #if !defined(_OBP) |
| ucode_install(bam_root); |
| #endif |
| |
| ret = f(bam_root, opt); |
| |
| bam_update_all = 0; |
| |
| return (ret); |
| } |
| |
| /*PRINTFLIKE1*/ |
| void |
| bam_error(char *format, ...) |
| { |
| va_list ap; |
| |
| va_start(ap, format); |
| (void) fprintf(stderr, "%s: ", prog); |
| (void) vfprintf(stderr, format, ap); |
| va_end(ap); |
| } |
| |
| /*PRINTFLIKE1*/ |
| void |
| bam_derror(char *format, ...) |
| { |
| va_list ap; |
| |
| assert(bam_debug); |
| |
| va_start(ap, format); |
| (void) fprintf(stderr, "DEBUG: "); |
| (void) vfprintf(stderr, format, ap); |
| va_end(ap); |
| } |
| |
| /*PRINTFLIKE1*/ |
| void |
| bam_print(char *format, ...) |
| { |
| va_list ap; |
| |
| va_start(ap, format); |
| (void) vfprintf(stdout, format, ap); |
| va_end(ap); |
| } |
| |
| /*PRINTFLIKE1*/ |
| void |
| bam_print_stderr(char *format, ...) |
| { |
| va_list ap; |
| |
| va_start(ap, format); |
| (void) vfprintf(stderr, format, ap); |
| va_end(ap); |
| } |
| |
| void |
| bam_exit(int excode) |
| { |
| restore_env(); |
| bam_unlock(); |
| exit(excode); |
| } |
| |
| static void |
| bam_lock(void) |
| { |
| struct flock lock; |
| pid_t pid; |
| |
| if (bam_skip_lock) |
| return; |
| |
| bam_lock_fd = open(BAM_LOCK_FILE, O_CREAT|O_RDWR, LOCK_FILE_PERMS); |
| if (bam_lock_fd < 0) { |
| /* |
| * We may be invoked early in boot for archive verification. |
| * In this case, root is readonly and /var/run may not exist. |
| * Proceed without the lock |
| */ |
| if (errno == EROFS || errno == ENOENT) { |
| bam_root_readonly = 1; |
| return; |
| } |
| |
| bam_error(_("failed to open file: %s: %s\n"), |
| BAM_LOCK_FILE, strerror(errno)); |
| bam_exit(1); |
| } |
| |
| lock.l_type = F_WRLCK; |
| lock.l_whence = SEEK_SET; |
| lock.l_start = 0; |
| lock.l_len = 0; |
| |
| if (fcntl(bam_lock_fd, F_SETLK, &lock) == -1) { |
| if (errno != EACCES && errno != EAGAIN) { |
| bam_error(_("failed to lock file: %s: %s\n"), |
| BAM_LOCK_FILE, strerror(errno)); |
| (void) close(bam_lock_fd); |
| bam_lock_fd = -1; |
| bam_exit(1); |
| } |
| pid = 0; |
| (void) pread(bam_lock_fd, &pid, sizeof (pid_t), 0); |
| bam_print( |
| _("another instance of bootadm (pid %lu) is running\n"), |
| pid); |
| |
| lock.l_type = F_WRLCK; |
| lock.l_whence = SEEK_SET; |
| lock.l_start = 0; |
| lock.l_len = 0; |
| if (fcntl(bam_lock_fd, F_SETLKW, &lock) == -1) { |
| bam_error(_("failed to lock file: %s: %s\n"), |
| BAM_LOCK_FILE, strerror(errno)); |
| (void) close(bam_lock_fd); |
| bam_lock_fd = -1; |
| bam_exit(1); |
| } |
| } |
| |
| /* We own the lock now */ |
| pid = getpid(); |
| (void) write(bam_lock_fd, &pid, sizeof (pid)); |
| } |
| |
| static void |
| bam_unlock(void) |
| { |
| struct flock unlock; |
| |
| if (bam_skip_lock) |
| return; |
| |
| /* |
| * NOP if we don't hold the lock |
| */ |
| if (bam_lock_fd < 0) { |
| return; |
| } |
| |
| unlock.l_type = F_UNLCK; |
| unlock.l_whence = SEEK_SET; |
| unlock.l_start = 0; |
| unlock.l_len = 0; |
| |
| if (fcntl(bam_lock_fd, F_SETLK, &unlock) == -1) { |
| bam_error(_("failed to unlock file: %s: %s\n"), |
| BAM_LOCK_FILE, strerror(errno)); |
| } |
| |
| if (close(bam_lock_fd) == -1) { |
| bam_error(_("failed to close file: %s: %s\n"), |
| BAM_LOCK_FILE, strerror(errno)); |
| } |
| bam_lock_fd = -1; |
| } |
| |
| static error_t |
| list_archive(char *root, char *opt) |
| { |
| filelist_t flist; |
| filelist_t *flistp = &flist; |
| line_t *lp; |
| |
| assert(root); |
| assert(opt == NULL); |
| |
| flistp->head = flistp->tail = NULL; |
| if (read_list(root, flistp) != BAM_SUCCESS) { |
| return (BAM_ERROR); |
| } |
| assert(flistp->head && flistp->tail); |
| |
| for (lp = flistp->head; lp; lp = lp->next) { |
| bam_print(_("%s\n"), lp->line); |
| } |
| |
| filelist_free(flistp); |
| |
| return (BAM_SUCCESS); |
| } |
| |
| /* |
| * This routine writes a list of lines to a file. |
| * The list is *not* freed |
| */ |
| static error_t |
| list2file(char *root, char *tmp, char *final, line_t *start) |
| { |
| char tmpfile[PATH_MAX]; |
| char path[PATH_MAX]; |
| FILE *fp; |
| int ret; |
| struct stat sb; |
| mode_t mode; |
| uid_t root_uid; |
| gid_t sys_gid; |
| struct passwd *pw; |
| struct group *gp; |
| const char *fcn = "list2file()"; |
| |
| (void) snprintf(path, sizeof (path), "%s%s", root, final); |
| |
| if (start == NULL) { |
| /* Empty GRUB menu */ |
| if (stat(path, &sb) != -1) { |
| bam_print(_("file is empty, deleting file: %s\n"), |
| path); |
| if (unlink(path) != 0) { |
| bam_error(_("failed to unlink file: %s: %s\n"), |
| path, strerror(errno)); |
| return (BAM_ERROR); |
| } else { |
| return (BAM_SUCCESS); |
| } |
| } |
| return (BAM_SUCCESS); |
| } |
| |
| /* |
| * Preserve attributes of existing file if possible, |
| * otherwise ask the system for uid/gid of root/sys. |
| * If all fails, fall back on hard-coded defaults. |
| */ |
| if (stat(path, &sb) != -1) { |
| mode = sb.st_mode; |
| root_uid = sb.st_uid; |
| sys_gid = sb.st_gid; |
| } else { |
| mode = DEFAULT_DEV_MODE; |
| if ((pw = getpwnam(DEFAULT_DEV_USER)) != NULL) { |
| root_uid = pw->pw_uid; |
| } else { |
| bam_error(_("getpwnam: uid for %s failed, " |
| "defaulting to %d\n"), |
| DEFAULT_DEV_USER, DEFAULT_DEV_UID); |
| root_uid = (uid_t)DEFAULT_DEV_UID; |
| } |
| if ((gp = getgrnam(DEFAULT_DEV_GROUP)) != NULL) { |
| sys_gid = gp->gr_gid; |
| } else { |
| bam_error(_("getgrnam: gid for %s failed, " |
| "defaulting to %d\n"), |
| DEFAULT_DEV_GROUP, DEFAULT_DEV_GID); |
| sys_gid = (gid_t)DEFAULT_DEV_GID; |
| } |
| } |
| |
| (void) snprintf(tmpfile, sizeof (tmpfile), "%s%s", root, tmp); |
| |
| /* Truncate tmpfile first */ |
| fp = fopen(tmpfile, "w"); |
| if (fp == NULL) { |
| bam_error(_("failed to open file: %s: %s\n"), tmpfile, |
| strerror(errno)); |
| return (BAM_ERROR); |
| } |
| ret = fclose(fp); |
| INJECT_ERROR1("LIST2FILE_TRUNC_FCLOSE", ret = EOF); |
| if (ret == EOF) { |
| bam_error(_("failed to close file: %s: %s\n"), |
| tmpfile, strerror(errno)); |
| return (BAM_ERROR); |
| } |
| |
| /* Now open it in append mode */ |
| fp = fopen(tmpfile, "a"); |
| if (fp == NULL) { |
| bam_error(_("failed to open file: %s: %s\n"), tmpfile, |
| strerror(errno)); |
| return (BAM_ERROR); |
| } |
| |
| for (; start; start = start->next) { |
| ret = s_fputs(start->line, fp); |
| INJECT_ERROR1("LIST2FILE_FPUTS", ret = EOF); |
| if (ret == EOF) { |
| bam_error(_("write to file failed: %s: %s\n"), |
| tmpfile, strerror(errno)); |
| (void) fclose(fp); |
| return (BAM_ERROR); |
| } |
| } |
| |
| ret = fclose(fp); |
| INJECT_ERROR1("LIST2FILE_APPEND_FCLOSE", ret = EOF); |
| if (ret == EOF) { |
| bam_error(_("failed to close file: %s: %s\n"), |
| tmpfile, strerror(errno)); |
| return (BAM_ERROR); |
| } |
| |
| /* |
| * Set up desired attributes. Ignore failures on filesystems |
| * not supporting these operations - pcfs reports unsupported |
| * operations as EINVAL. |
| */ |
| ret = chmod(tmpfile, mode); |
| if (ret == -1 && |
| errno != EINVAL && errno != ENOTSUP) { |
| bam_error(_("chmod operation on %s failed - %s\n"), |
| tmpfile, strerror(errno)); |
| return (BAM_ERROR); |
| } |
| |
| ret = chown(tmpfile, root_uid, sys_gid); |
| if (ret == -1 && |
| errno != EINVAL && errno != ENOTSUP) { |
| bam_error(_("chgrp operation on %s failed - %s\n"), |
| tmpfile, strerror(errno)); |
| return (BAM_ERROR); |
| } |
| |
| /* |
| * Do an atomic rename |
| */ |
| ret = rename(tmpfile, path); |
| INJECT_ERROR1("LIST2FILE_RENAME", ret = -1); |
| if (ret != 0) { |
| bam_error(_("rename to file failed: %s: %s\n"), path, |
| strerror(errno)); |
| return (BAM_ERROR); |
| } |
| |
| BAM_DPRINTF(("%s: wrote file successfully: %s\n", fcn, path)); |
| return (BAM_SUCCESS); |
| } |
| |
| /* |
| * Checks if the path specified (without the file name at the end) exists |
| * and creates it if not. If the path exists and is not a directory, an attempt |
| * to unlink is made. |
| */ |
| static int |
| setup_path(char *path) |
| { |
| char *p; |
| int ret; |
| struct stat sb; |
| |
| p = strrchr(path, '/'); |
| if (p != NULL) { |
| *p = '\0'; |
| if (stat(path, &sb) != 0 || !(S_ISDIR(sb.st_mode))) { |
| /* best effort attempt, mkdirp will catch the error */ |
| (void) unlink(path); |
| if (bam_verbose) |
| bam_print(_("need to create directory " |
| "path for %s\n"), path); |
| ret = mkdirp(path, DIR_PERMS); |
| if (ret == -1) { |
| bam_error(_("mkdir of %s failed: %s\n"), |
| path, strerror(errno)); |
| *p = '/'; |
| return (BAM_ERROR); |
| } |
| } |
| *p = '/'; |
| return (BAM_SUCCESS); |
| } |
| return (BAM_SUCCESS); |
| } |
| |
| typedef union { |
| gzFile gzfile; |
| int fdfile; |
| } outfile; |
| |
| typedef struct { |
| char path[PATH_MAX]; |
| outfile out; |
| } cachefile; |
| |
| static int |
| setup_file(char *base, const char *path, cachefile *cf) |
| { |
| int ret; |
| char *strip; |
| |
| /* init gzfile or fdfile in case we fail before opening */ |
| if (bam_direct == BAM_DIRECT_DBOOT) |
| cf->out.gzfile = NULL; |
| else |
| cf->out.fdfile = -1; |
| |
| /* strip the trailing altroot path */ |
| strip = (char *)path + strlen(rootbuf); |
| |
| ret = snprintf(cf->path, sizeof (cf->path), "%s/%s", base, strip); |
| if (ret >= sizeof (cf->path)) { |
| bam_error(_("unable to create path on mountpoint %s, " |
| "path too long\n"), rootbuf); |
| return (BAM_ERROR); |
| } |
| |
| /* Check if path is present in the archive cache directory */ |
| if (setup_path(cf->path) == BAM_ERROR) |
| return (BAM_ERROR); |
| |
| if (bam_direct == BAM_DIRECT_DBOOT) { |
| if ((cf->out.gzfile = gzopen(cf->path, "wb")) == NULL) { |
| bam_error(_("failed to open file: %s: %s\n"), |
| cf->path, strerror(errno)); |
| return (BAM_ERROR); |
| } |
| (void) gzsetparams(cf->out.gzfile, Z_BEST_SPEED, |
| Z_DEFAULT_STRATEGY); |
| } else { |
| if ((cf->out.fdfile = open(cf->path, O_WRONLY | O_CREAT, 0644)) |
| == -1) { |
| bam_error(_("failed to open file: %s: %s\n"), |
| cf->path, strerror(errno)); |
| return (BAM_ERROR); |
| } |
| } |
| |
| return (BAM_SUCCESS); |
| } |
| |
| static int |
| cache_write(cachefile cf, char *buf, int size) |
| { |
| int err; |
| |
| if (bam_direct == BAM_DIRECT_DBOOT) { |
| if (gzwrite(cf.out.gzfile, buf, size) < 1) { |
| bam_error(_("failed to write to %s\n"), |
| gzerror(cf.out.gzfile, &err)); |
| if (err == Z_ERRNO && bam_verbose) { |
| bam_error(_("write to file failed: %s: %s\n"), |
| cf.path, strerror(errno)); |
| } |
| return (BAM_ERROR); |
| } |
| } else { |
| if (write(cf.out.fdfile, buf, size) < 1) { |
| bam_error(_("write to file failed: %s: %s\n"), |
| cf.path, strerror(errno)); |
| return (BAM_ERROR); |
| } |
| } |
| return (BAM_SUCCESS); |
| } |
| |
| static int |
| cache_close(cachefile cf) |
| { |
| int ret; |
| |
| if (bam_direct == BAM_DIRECT_DBOOT) { |
| if (cf.out.gzfile) { |
| ret = gzclose(cf.out.gzfile); |
| if (ret != Z_OK) { |
| bam_error(_("failed to close file: %s: %s\n"), |
| cf.path, strerror(errno)); |
| return (BAM_ERROR); |
| } |
| } |
| } else { |
| if (cf.out.fdfile != -1) { |
| ret = close(cf.out.fdfile); |
| if (ret != 0) { |
| bam_error(_("failed to close file: %s: %s\n"), |
| cf.path, strerror(errno)); |
| return (BAM_ERROR); |
| } |
| } |
| } |
| |
| return (BAM_SUCCESS); |
| } |
| |
| static int |
| dircache_updatefile(const char *path) |
| { |
| int ret, exitcode; |
| char buf[4096 * 4]; |
| FILE *infile; |
| cachefile outfile, outupdt; |
| |
| if (bam_nowrite()) { |
| set_dir_flag(NEED_UPDATE); |
| return (BAM_SUCCESS); |
| } |
| |
| if (!has_cachedir()) |
| return (BAM_SUCCESS); |
| |
| if ((infile = fopen(path, "rb")) == NULL) { |
| bam_error(_("failed to open file: %s: %s\n"), path, |
| strerror(errno)); |
| return (BAM_ERROR); |
| } |
| |
| ret = setup_file(get_cachedir(), path, &outfile); |
| if (ret == BAM_ERROR) { |
| exitcode = BAM_ERROR; |
| goto out; |
| } |
| if (!is_dir_flag_on(NO_EXTEND)) { |
| ret = setup_file(get_updatedir(), path, &outupdt); |
| if (ret == BAM_ERROR) |
| set_dir_flag(NO_EXTEND); |
| } |
| |
| while ((ret = fread(buf, 1, sizeof (buf), infile)) > 0) { |
| if (cache_write(outfile, buf, ret) == BAM_ERROR) { |
| exitcode = BAM_ERROR; |
| goto out; |
| } |
| if (!is_dir_flag_on(NO_EXTEND)) |
| if (cache_write(outupdt, buf, ret) == BAM_ERROR) |
| set_dir_flag(NO_EXTEND); |
| } |
| |
| set_dir_flag(NEED_UPDATE); |
| get_count()++; |
| if (get_count() > COUNT_MAX) |
| set_dir_flag(NO_EXTEND); |
| exitcode = BAM_SUCCESS; |
| out: |
| (void) fclose(infile); |
| if (cache_close(outfile) == BAM_ERROR) |
| exitcode = BAM_ERROR; |
| if (!is_dir_flag_on(NO_EXTEND) && |
| cache_close(outupdt) == BAM_ERROR) |
| exitcode = BAM_ERROR; |
| if (exitcode == BAM_ERROR) |
| set_flag(UPDATE_ERROR); |
| return (exitcode); |
| } |
| |
| static int |
| dircache_updatedir(const char *path, int updt) |
| { |
| int ret; |
| char dpath[PATH_MAX]; |
| char *strip; |
| struct stat sb; |
| |
| strip = (char *)path + strlen(rootbuf); |
| |
| ret = snprintf(dpath, sizeof (dpath), "%s/%s", updt ? |
| get_updatedir() : get_cachedir(), strip); |
| |
| if (ret >= sizeof (dpath)) { |
| bam_error(_("unable to create path on mountpoint %s, " |
| "path too long\n"), rootbuf); |
| set_flag(UPDATE_ERROR); |
| return (BAM_ERROR); |
| } |
| |
| if (stat(dpath, &sb) == 0 && S_ISDIR(sb.st_mode)) |
| return (BAM_SUCCESS); |
| |
| if (updt) { |
| if (!is_dir_flag_on(NO_EXTEND)) |
| if (!bam_nowrite() && mkdirp(dpath, DIR_PERMS) == -1) |
| set_dir_flag(NO_EXTEND); |
| } else { |
| if (!bam_nowrite() && mkdirp(dpath, DIR_PERMS) == -1) { |
| set_flag(UPDATE_ERROR); |
| return (BAM_ERROR); |
| } |
| } |
| |
| set_dir_flag(NEED_UPDATE); |
| return (BAM_SUCCESS); |
| } |
| |
| #define DO_CACHE_DIR 0 |
| #define DO_UPDATE_DIR 1 |
| |
| #if defined(_LP64) || defined(_LONGLONG_TYPE) |
| typedef Elf64_Ehdr _elfhdr; |
| #else |
| typedef Elf32_Ehdr _elfhdr; |
| #endif |
| |
| /* |
| * This routine updates the contents of the cache directory |
| */ |
| static int |
| update_dircache(const char *path, int flags) |
| { |
| int rc = BAM_SUCCESS; |
| |
| switch (flags) { |
| case FTW_F: |
| { |
| int fd; |
| _elfhdr elf; |
| |
| if ((fd = open(path, O_RDONLY)) < 0) { |
| bam_error(_("failed to open file: %s: %s\n"), |
| path, strerror(errno)); |
| set_flag(UPDATE_ERROR); |
| rc = BAM_ERROR; |
| break; |
| } |
| |
| /* |
| * libelf and gelf would be a cleaner and easier way to handle |
| * this, but libelf fails compilation if _ILP32 is defined && |
| * _FILE_OFFSET_BITS is != 32 ... |
| */ |
| if (read(fd, (void *)&elf, sizeof (_elfhdr)) < 0) { |
| bam_error(_("read failed for file: %s: %s\n"), |
| path, strerror(errno)); |
| set_flag(UPDATE_ERROR); |
| (void) close(fd); |
| rc = BAM_ERROR; |
| break; |
| } |
| (void) close(fd); |
| |
| if (memcmp(elf.e_ident, ELFMAG, 4) != 0) { |
| /* Not an ELF file, include in archive */ |
| rc = dircache_updatefile(path); |
| } else { |
| /* Include 64-bit ELF files only */ |
| switch (elf.e_ident[EI_CLASS]) { |
| case ELFCLASS32: |
| bam_print(_("WARNING: ELF file %s is 32-bit " |
| "and will be excluded\n"), path); |
| break; |
| case ELFCLASS64: |
| rc = dircache_updatefile(path); |
| break; |
| default: |
| bam_print(_("WARNING: ELF file %s is neither " |
| "32-bit nor 64-bit\n"), path); |
| break; |
| } |
| } |
| break; |
| } |
| case FTW_D: |
| if (strstr(path, "/amd64") != NULL) { |
| if (has_cachedir()) { |
| rc = dircache_updatedir(path, DO_UPDATE_DIR); |
| if (rc == BAM_SUCCESS) |
| rc = dircache_updatedir(path, |
| DO_CACHE_DIR); |
| } |
| } |
| break; |
| default: |
| rc = BAM_ERROR; |
| break; |
| } |
| |
| return (rc); |
| } |
| |
| /*ARGSUSED*/ |
| static int |
| cmpstat( |
| const char *file, |
| const struct stat *st, |
| int flags, |
| struct FTW *ftw) |
| { |
| uint_t sz; |
| uint64_t *value; |
| uint64_t filestat[2]; |
| int error, ret, status; |
| |
| struct safefile *safefilep; |
| FILE *fp; |
| struct stat sb; |
| regex_t re; |
| |
| /* |
| * On SPARC we create/update links too. |
| */ |
| if (flags != FTW_F && flags != FTW_D && (flags == FTW_SL && |
| !is_flag_on(IS_SPARC_TARGET))) |
| return (0); |
| |
| /* |
| * Ignore broken links |
| */ |
| if (flags == FTW_SL && stat(file, &sb) < 0) |
| return (0); |
| |
| /* |
| * new_nvlp may be NULL if there were errors earlier |
| * but this is not fatal to update determination. |
| */ |
| if (walk_arg.new_nvlp) { |
| filestat[0] = st->st_size; |
| filestat[1] = st->st_mtime; |
| error = nvlist_add_uint64_array(walk_arg.new_nvlp, |
| file + bam_rootlen, filestat, 2); |
| if (error) |
| bam_error(_("failed to update stat data for: %s: %s\n"), |
| file, strerror(error)); |
| } |
| |
| /* |
| * If we are invoked as part of system/filesystem/boot-archive, then |
| * there are a number of things we should not worry about |
| */ |
| if (bam_smf_check) { |
| /* ignore amd64 modules unless we are booted amd64. */ |
| if (!is_amd64() && strstr(file, "/amd64/") != 0) |
| return (0); |
| |
| /* read in list of safe files */ |
| if (safefiles == NULL) { |
| fp = fopen("/boot/solaris/filelist.safe", "r"); |
| if (fp != NULL) { |
| safefiles = s_calloc(1, |
| sizeof (struct safefile)); |
| safefilep = safefiles; |
| safefilep->name = s_calloc(1, MAXPATHLEN + |
| MAXNAMELEN); |
| safefilep->next = NULL; |
| while (s_fgets(safefilep->name, MAXPATHLEN + |
| MAXNAMELEN, fp) != NULL) { |
| safefilep->next = s_calloc(1, |
| sizeof (struct safefile)); |
| safefilep = safefilep->next; |
| safefilep->name = s_calloc(1, |
| MAXPATHLEN + MAXNAMELEN); |
| safefilep->next = NULL; |
| } |
| (void) fclose(fp); |
| } |
| } |
| } |
| |
| /* |
| * On SPARC we create a -path-list file for mkisofs |
| */ |
| if (is_flag_on(IS_SPARC_TARGET) && !bam_nowrite()) { |
| if (flags != FTW_D) { |
| char *strip; |
| |
| strip = (char *)file + strlen(rootbuf); |
| (void) fprintf(walk_arg.sparcfile, "/%s=%s\n", strip, |
| file); |
| } |
| } |
| |
| /* |
| * We are transitioning from the old model to the dircache or the cache |
| * directory was removed: create the entry without further checkings. |
| */ |
| if (is_flag_on(NEED_CACHE_DIR)) { |
| if (bam_verbose) |
| bam_print(_(" new %s\n"), file); |
| |
| if (is_flag_on(IS_SPARC_TARGET)) { |
| set_dir_flag(NEED_UPDATE); |
| return (0); |
| } |
| |
| ret = update_dircache(file, flags); |
| if (ret == BAM_ERROR) { |
| bam_error(_("directory cache update failed for %s\n"), |
| file); |
| return (-1); |
| } |
| |
| return (0); |
| } |
| |
| /* |
| * We need an update if file doesn't exist in old archive |
| */ |
| if (walk_arg.old_nvlp == NULL || |
| nvlist_lookup_uint64_array(walk_arg.old_nvlp, |
| file + bam_rootlen, &value, &sz) != 0) { |
| if (bam_smf_check) /* ignore new during smf check */ |
| return (0); |
| |
| if (is_flag_on(IS_SPARC_TARGET)) { |
| set_dir_flag(NEED_UPDATE); |
| } else { |
| ret = update_dircache(file, flags); |
| if (ret == BAM_ERROR) { |
| bam_error(_("directory cache update " |
| "failed for %s\n"), file); |
| return (-1); |
| } |
| } |
| |
| if (bam_verbose) |
| bam_print(_(" new %s\n"), file); |
| return (0); |
| } |
| |
| /* |
| * If we got there, the file is already listed as to be included in the |
| * iso image. We just need to know if we are going to rebuild it or not |
| */ |
| if (is_flag_on(IS_SPARC_TARGET) && |
| is_dir_flag_on(NEED_UPDATE) && !bam_nowrite()) |
| return (0); |
| /* |
| * File exists in old archive. Check if file has changed |
| */ |
| assert(sz == 2); |
| bcopy(value, filestat, sizeof (filestat)); |
| |
| if (flags != FTW_D && (filestat[0] != st->st_size || |
| filestat[1] != st->st_mtime)) { |
| if (bam_smf_check) { |
| safefilep = safefiles; |
| while (safefilep != NULL && |
| safefilep->name[0] != '\0') { |
| if (regcomp(&re, safefilep->name, |
| REG_EXTENDED|REG_NOSUB) == 0) { |
| status = regexec(&re, |
| file + bam_rootlen, 0, NULL, 0); |
| regfree(&re); |
| if (status == 0) { |
| (void) creat( |
| NEED_UPDATE_SAFE_FILE, |
| 0644); |
| return (0); |
| } |
| } |
| safefilep = safefilep->next; |
| } |
| } |
| |
| if (is_flag_on(IS_SPARC_TARGET)) { |
| set_dir_flag(NEED_UPDATE); |
| } else { |
| ret = update_dircache(file, flags); |
| if (ret == BAM_ERROR) { |
| bam_error(_("directory cache update failed " |
| "for %s\n"), file); |
| return (-1); |
| } |
| } |
| |
| if (bam_verbose) { |
| if (bam_smf_check) |
| bam_print(" %s\n", file); |
| else |
| bam_print(_(" changed %s\n"), file); |
| } |
| } |
| |
| return (0); |
| } |
| |
| /* |
| * Remove a directory path recursively |
| */ |
| static int |
| rmdir_r(char *path) |
| { |
| struct dirent *d = NULL; |
| DIR *dir = NULL; |
| char tpath[PATH_MAX]; |
| struct stat sb; |
| |
| if ((dir = opendir(path)) == NULL) |
| return (-1); |
| |
| while ((d = readdir(dir)) != NULL) { |
| if ((strcmp(d->d_name, ".") != 0) && |
| (strcmp(d->d_name, "..") != 0)) { |
| (void) snprintf(tpath, sizeof (tpath), "%s/%s", |
| path, d->d_name); |
| if (stat(tpath, &sb) == 0) { |
| if (sb.st_mode & S_IFDIR) |
| (void) rmdir_r(tpath); |
| else |
| (void) remove(tpath); |
| } |
| } |
| } |
| return (remove(path)); |
| } |
| |
| /* |
| * Check if cache directory exists and, if not, create it and update flags |
| * accordingly. If the path exists, but it's not a directory, a best effort |
| * attempt to remove and recreate it is made. |
| * If the user requested a 'purge', always recreate the directory from scratch. |
| */ |
| static int |
| set_cache_dir(char *root) |
| { |
| struct stat sb; |
| int ret = 0; |
| |
| ret = build_path(get_cachedir(), sizeof (get_cachedir()), |
| root, ARCHIVE_PREFIX, CACHEDIR_SUFFIX); |
| |
| if (ret >= sizeof (get_cachedir())) { |
| bam_error(_("unable to create path on mountpoint %s, " |
| "path too long\n"), rootbuf); |
| return (BAM_ERROR); |
| } |
| |
| if (bam_purge || is_flag_on(INVALIDATE_CACHE)) |
| (void) rmdir_r(get_cachedir()); |
| |
| if (stat(get_cachedir(), &sb) != 0 || !(S_ISDIR(sb.st_mode))) { |
| /* best effort unlink attempt, mkdir will catch errors */ |
| (void) unlink(get_cachedir()); |
| |
| if (bam_verbose) |
| bam_print(_("archive cache directory not found: %s\n"), |
| get_cachedir()); |
| ret = mkdir(get_cachedir(), DIR_PERMS); |
| if (ret < 0) { |
| bam_error(_("mkdir of %s failed: %s\n"), |
| get_cachedir(), strerror(errno)); |
| get_cachedir()[0] = '\0'; |
| return (ret); |
| } |
| set_flag(NEED_CACHE_DIR); |
| set_dir_flag(NO_EXTEND); |
| } |
| |
| return (BAM_SUCCESS); |
| } |
| |
| static int |
| set_update_dir(char *root) |
| { |
| struct stat sb; |
| int ret; |
| |
| if (is_dir_flag_on(NO_EXTEND)) |
| return (BAM_SUCCESS); |
| |
| if (!bam_extend) { |
| set_dir_flag(NO_EXTEND); |
| return (BAM_SUCCESS); |
| } |
| |
| ret = build_path(get_updatedir(), sizeof (get_updatedir()), |
| root, ARCHIVE_PREFIX, UPDATEDIR_SUFFIX); |
| |
| if (ret >= sizeof (get_updatedir())) { |
| bam_error(_("unable to create path on mountpoint %s, " |
| "path too long\n"), rootbuf); |
| return (BAM_ERROR); |
| } |
| |
| if (stat(get_updatedir(), &sb) == 0) { |
| if (S_ISDIR(sb.st_mode)) |
| ret = rmdir_r(get_updatedir()); |
| else |
| ret = unlink(get_updatedir()); |
| |
| if (ret != 0) |
| set_dir_flag(NO_EXTEND); |
| } |
| |
| if (mkdir(get_updatedir(), DIR_PERMS) < 0) |
| set_dir_flag(NO_EXTEND); |
| |
| return (BAM_SUCCESS); |
| } |
| |
| static int |
| is_valid_archive(char *root) |
| { |
| char archive_path[PATH_MAX]; |
| char timestamp_path[PATH_MAX]; |
| struct stat sb, timestamp; |
| int ret; |
| |
| ret = build_path(archive_path, sizeof (archive_path), |
| root, ARCHIVE_PREFIX, ARCHIVE_SUFFIX); |
| |
| if (ret >= sizeof (archive_path)) { |
| bam_error(_("unable to create path on mountpoint %s, " |
| "path too long\n"), rootbuf); |
| return (BAM_ERROR); |
| } |
| |
| if (stat(archive_path, &sb) != 0) { |
| if (bam_verbose && !bam_check) |
| bam_print(_("archive not found: %s\n"), archive_path); |
| set_dir_flag(NEED_UPDATE | NO_EXTEND); |
| return (BAM_SUCCESS); |
| } |
| |
| /* |
| * The timestamp file is used to prevent stale files in the archive |
| * cache. |
| * Stale files can happen if the system is booted back and forth across |
| * the transition from bootadm-before-the-cache to |
| * bootadm-after-the-cache, since older versions of bootadm don't know |
| * about the existence of the archive cache. |
| * |
| * Since only bootadm-after-the-cache versions know about about this |
| * file, we require that the boot archive be older than this file. |
| */ |
| ret = snprintf(timestamp_path, sizeof (timestamp_path), "%s%s", root, |
| FILE_STAT_TIMESTAMP); |
| |
| if (ret >= sizeof (timestamp_path)) { |
| bam_error(_("unable to create path on mountpoint %s, " |
| "path too long\n"), rootbuf); |
| return (BAM_ERROR); |
| } |
| |
| if (stat(timestamp_path, ×tamp) != 0 || |
| sb.st_mtime > timestamp.st_mtime) { |
| if (bam_verbose && !bam_check) |
| bam_print( |
| _("archive cache is out of sync. Rebuilding.\n")); |
| /* |
| * Don't generate a false positive for the boot-archive service |
| * but trigger an update of the archive cache in |
| * boot-archive-update. |
| */ |
| if (bam_smf_check) { |
| (void) creat(NEED_UPDATE_FILE, 0644); |
| return (BAM_SUCCESS); |
| } |
| |
| set_flag(INVALIDATE_CACHE); |
| set_dir_flag(NEED_UPDATE | NO_EXTEND); |
| return (BAM_SUCCESS); |
| } |
| |
| if (is_flag_on(IS_SPARC_TARGET)) |
| return (BAM_SUCCESS); |
| |
| if (bam_extend && sb.st_size > BA_SIZE_MAX) { |
| if (bam_verbose && !bam_check) |
| bam_print(_("archive %s is bigger than %d bytes and " |
| "will be rebuilt\n"), archive_path, BA_SIZE_MAX); |
| set_dir_flag(NO_EXTEND); |
| } |
| |
| return (BAM_SUCCESS); |
| } |
| |
| /* |
| * Check flags and presence of required files and directories. |
| * The force flag and/or absence of files should |
| * trigger an update. |
| * Suppress stdout output if check (-n) option is set |
| * (as -n should only produce parseable output.) |
| */ |
| static int |
| check_flags_and_files(char *root) |
| { |
| |
| struct stat sb; |
| int ret; |
| |
| /* |
| * If archive is missing, create archive |
| */ |
| ret = is_valid_archive(root); |
| if (ret == BAM_ERROR) |
| return (BAM_ERROR); |
| |
| if (bam_nowrite()) |
| return (BAM_SUCCESS); |
| |
| /* |
| * check if cache directories exist on x86. |
| * check (and always open) the cache file on SPARC. |
| */ |
| if (is_sparc()) { |
| ret = snprintf(get_cachedir(), |
| sizeof (get_cachedir()), "%s%s%s/%s", root, |
| ARCHIVE_PREFIX, get_machine(), CACHEDIR_SUFFIX); |
| |
| if (ret >= sizeof (get_cachedir())) { |
| bam_error(_("unable to create path on mountpoint %s, " |
| "path too long\n"), rootbuf); |
| return (BAM_ERROR); |
| } |
| |
| if (stat(get_cachedir(), &sb) != 0) { |
| set_flag(NEED_CACHE_DIR); |
| set_dir_flag(NEED_UPDATE); |
| } |
| |
| walk_arg.sparcfile = fopen(get_cachedir(), "w"); |
| if (walk_arg.sparcfile == NULL) { |
| bam_error(_("failed to open file: %s: %s\n"), |
| get_cachedir(), strerror(errno)); |
| return (BAM_ERROR); |
| } |
| |
| set_dir_present(); |
| } else { |
| if (set_cache_dir(root) != 0) |
| return (BAM_ERROR); |
| |
| set_dir_present(); |
| |
| if (set_update_dir(root) != 0) |
| return (BAM_ERROR); |
| } |
| |
| /* |
| * if force, create archive unconditionally |
| */ |
| if (bam_force) { |
| set_dir_flag(NEED_UPDATE); |
| if (bam_verbose) |
| bam_print(_("forced update of archive requested\n")); |
| return (BAM_SUCCESS); |
| } |
| |
| return (BAM_SUCCESS); |
| } |
| |
| static error_t |
| read_one_list(char *root, filelist_t *flistp, char *filelist) |
| { |
| char path[PATH_MAX]; |
| FILE *fp; |
| char buf[BAM_MAXLINE]; |
| const char *fcn = "read_one_list()"; |
| |
| (void) snprintf(path, sizeof (path), "%s%s", root, filelist); |
| |
| fp = fopen(path, "r"); |
| if (fp == NULL) { |
| BAM_DPRINTF(("%s: failed to open archive filelist: %s: %s\n", |
| fcn, path, strerror(errno))); |
| return (BAM_ERROR); |
| } |
| while (s_fgets(buf, sizeof (buf), fp) != NULL) { |
| /* skip blank lines */ |
| if (strspn(buf, " \t") == strlen(buf)) |
| continue; |
| append_to_flist(flistp, buf); |
| } |
| if (fclose(fp) != 0) { |
| bam_error(_("failed to close file: %s: %s\n"), |
| path, strerror(errno)); |
| return (BAM_ERROR); |
| } |
| return (BAM_SUCCESS); |
| } |
| |
| static error_t |
| read_list(char *root, filelist_t *flistp) |
| { |
| char path[PATH_MAX]; |
| char cmd[PATH_MAX]; |
| struct stat sb; |
| int n, rval; |
| const char *fcn = "read_list()"; |
| |
| flistp->head = flistp->tail = NULL; |
| |
| /* |
| * build and check path to extract_boot_filelist.ksh |
| */ |
| n = snprintf(path, sizeof (path), "%s%s", root, EXTRACT_BOOT_FILELIST); |
| if (n >= |