blob: 36d167fafbe4c617adafd1fa5d387f8eb7998217 [file] [log] [blame]
/*
* 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, &timestamp) != 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 >=