halt.c revision 753a6d457b330b1b29b2d3eefcd0831116ce950d
/*
* 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
* 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 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
/* All Rights Reserved */
/*
* University Copyright- Copyright (c) 1982, 1986, 1988
* The Regents of the University of California
* All Rights Reserved
*
* University Acknowledgment- Portions of this document are derived from
* software developed by the University of California, Berkeley, and its
* contributors.
* Portions contributed by Juergen Keil, <jk@tools.de>.
*/
/*
* Common code for halt(1M), poweroff(1M), and reboot(1M). We use
* argv[0] to determine which behavior to exhibit.
*/
#include <stdio.h>
#include <procfs.h>
#include <sys/systeminfo.h>
#include <alloca.h>
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <libgen.h>
#include <libscf.h>
#include <libscf_priv.h>
#include <limits.h>
#include <locale.h>
#include <libintl.h>
#include <syslog.h>
#include <signal.h>
#include <strings.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <strings.h>
#include <time.h>
#include <wait.h>
#include <ctype.h>
#include <utmpx.h>
#include <pwd.h>
#include <zone.h>
#include <spawn.h>
#include <libzfs.h>
#if defined(__i386)
#include <libgrubmgmt.h>
#endif
#if !defined(TEXT_DOMAIN)
#define TEXT_DOMAIN "SYS_TEST"
#endif
#if defined(__sparc)
#define CUR_ELFDATA ELFDATA2MSB
#define CUR_ELFDATA ELFDATA2LSB
#endif
static libzfs_handle_t *g_zfs;
extern int audit_halt_setup(int, char **);
extern int audit_halt_success(void);
extern int audit_halt_fail(void);
extern int audit_reboot_setup(void);
extern int audit_reboot_success(void);
extern int audit_reboot_fail(void);
static char *cmdname; /* basename(argv[0]), the name of the command */
typedef struct ctidlist_struct {
struct ctidlist_struct *next;
} ctidlist_t;
#define FMRI_STARTD_CONTRACT \
#define ZONEADM_PROG "/usr/sbin/zoneadm"
#define LUUMOUNT_PROG "/usr/sbin/luumount"
#define LUMOUNT_PROG "/usr/sbin/lumount"
#define BOOTADM_PROG "/sbin/bootadm"
/*
* The length of FASTBOOT_MOUNTPOINT must be less than MAXPATHLEN.
*/
#define FASTBOOT_MOUNTPOINT "/tmp/.fastboot.root"
/*
* Fast Reboot related variables
*/
static char fastboot_mounted[MAXPATHLEN];
#if defined(__i386)
static grub_boot_args_t fbarg;
static grub_boot_args_t *fbarg_used;
static int fbarg_entnum = GRUB_ENTRY_DEFAULT;
#endif /* __i386 */
static int validate_ufs_disk(char *, char *);
static int validate_zfs_pool(char *, char *);
static pid_t
{
static int init_pid = -1;
if (init_pid == -1) {
init_pid = -1;
}
}
return (init_pid);
}
/*
* Quiesce or resume init using /proc. When stopping init, we can't send
* SIGTSTP (since init ignores it) or SIGSTOP (since the kernel won't permit
* it).
*/
static int
direct_init(long command)
{
char ctlfile[MAXPATHLEN];
int ctlfd;
return (-1);
}
return (-1);
return (-1);
}
} else { /* command == PCRUN */
long cmds[2];
cmds[1] = 0;
return (-1);
}
}
return (0);
}
static void
{
scf_handle_t *h;
return;
if ((scf_handle_bind(h) != 0) ||
goto out;
goto out;
goto out;
out:
}
static void
{
if (startdct != -1)
}
#define FMRI_RESTARTER_PROP "/:properties/general/restarter"
#define FMRI_CONTRACT_PROP "/:properties/restarter/contract"
static int
{
return (-1);
return (-1);
return (0);
}
static void
{
scf_handle_t *h;
char *fmri;
if (length <= 0)
return;
length++;
return;
if (scf_handle_bind(h) != 0) {
return;
}
goto out;
goto out;
goto out;
continue;
snap)) != 0)
else
SCF_PG_GENERAL, pg) != 0)
continue;
prop) != 0 ||
continue;
continue;
continue;
continue;
continue;
}
}
}
out:
(void) scf_handle_unbind(h);
}
static void
{
}
static void
{
stop_startd();
}
static void
{
}
/*
* Copy an array of strings into buf, separated by spaces. Returns 0 on
* success.
*/
static int
{
return (-1);
return (-1);
return (-1);
}
return (0);
}
/*
* Halt every zone on the system. We are committed to doing a shutdown
* even if something goes wrong here. If something goes wrong, we just
* continue with the shutdown. Return non-zero if we need to wait for zones to
* halt later on.
*/
static int
{
int i;
char zname[ZONENAME_MAX];
/*
* Get a list of zones. If the number of zones changes in between the
* two zone_list calls, try again.
*/
for (;;) {
if (nz == 1)
return (0);
gettext("%s: Could not halt zones"
" (out of memory).\n"), cmdname);
return (0);
}
break;
}
if (nz == 2) {
cmdname);
} else {
}
for (i = 0; i < nz; i++) {
if (zones[i] == GLOBAL_ZONEID)
continue;
/*
* getzonenamebyid should only fail if we raced with
* another process trying to shut down the zone.
* We assume this happened and ignore the error.
*/
gettext("%s: Unexpected error while "
"looking up zone %ul: %s.\n"),
}
continue;
}
if (pid < 0) {
gettext("%s: Zone \"%s\" could not be"
" halted (could not fork(): %s).\n"),
continue;
}
if (pid == 0) {
gettext("%s: Zone \"%s\" could not be halted"
exit(0);
}
}
return (1);
}
/*
* This function tries to wait for all non-global zones to go away.
* It will timeout if no progress is made for 5 seconds, or a total of
* 30 seconds elapses.
*/
static void
{
int t = 0, t_prog = 0;
do {
if (nz == 1)
return;
(void) sleep(1);
t_prog = 0;
t++;
t_prog++;
if (t == 10) {
if (nz == 2) {
gettext("%s: Still waiting for 1 zone to "
"halt. Will wait up to 20 seconds.\n"),
cmdname);
} else {
gettext("%s: Still waiting for %i zones "
"to halt. Will wait up to 20 seconds.\n"),
}
}
}
/*
* Validate that this is a root disk or dataset
* Returns 0 if it is a root disk or dataset;
* returns 1 if it is a disk argument or dataset, but not valid or not root;
* returns -1 if it is not a valid argument or a disk argument.
*/
static int
{
static char root_dev_path[] = "/dev/dsk";
char kernpath[MAXPATHLEN];
int rc = 0;
return (-1);
}
/*
* Do a force umount just in case some other filesystem has
* been mounted there.
*/
}
/* Create the directory if it doesn't already exist */
gettext("Failed to create mountpoint %s\n"),
return (-1);
}
}
/* ufs root disk argument */
} else {
/* zfs root pool argument */
}
if (rc != 0)
return (rc);
gettext("%s: %s is not a root disk or dataset\n"),
return (1);
}
return (0);
}
static int
{
/* perform the mount */
return (-1);
}
return (0);
}
static int
{
int rc = 0;
"initialize ZFS library\n"));
return (-1);
}
/* Try to open the dataset */
return (-1);
/* perform the mount */
rc = -1;
}
return (rc);
}
/*
* Return 0 if not zfs, or is zfs and have successfully constructed the
* boot argument; returns non-zero otherwise.
* At successful completion fpth contains pointer where mount point ends.
* NOTE: arg is supposed to be the resolved path
*/
static int
char *bootfs_arg)
{
char physpath[MAXPATHLEN];
char mntsp[ZPOOL_MAXNAMELEN];
char bootfs[ZPOOL_MAXNAMELEN];
int rc = 0;
static char fmt[] = "-B zfs-bootfs=%s,bootpath=\"%s\"";
*is_zfs = 0;
return (-1);
}
}
}
if (mntlen > 1)
if (!*is_zfs)
return (0);
return (-1);
/* Try to open the dataset */
rc = -1;
goto validate_zfs_err_out;
}
rc = -1;
goto validate_zfs_err_out;
}
rc = -1;
goto validate_zfs_err_out;
}
rc = -1;
goto validate_zfs_err_out;
}
/*
* For the mirror physpath would contain the list of all
* bootable devices, pick up the first one.
*/
BOOTARGS_MAX) {
gettext("Boot arguments are too long\n"));
}
return (rc);
}
/*
* Validate that the file exists, and is an ELF file.
* Returns 0 on success, -1 on failure.
*/
static int
int *failsafe)
{
const char *location;
unsigned char ident[EI_NIDENT];
char physpath[MAXPATHLEN];
int elffd = -1;
(size_t)-1) {
gettext("Cannot resolve path for %s: %s\n"),
return (-1);
}
return (-1);
}
gettext("%s: %s: Kernel name must be unix\n"),
return (-1);
}
goto err_out;
*failsafe = 1;
*failsafe = 0;
else {
goto err_out;
}
goto err_out;
}
goto err_out;
}
if (format != CUR_ELFDATA) {
goto err_out;
}
return (0);
if (elffd >= 0) {
elffd = -1;
}
return (-1);
}
static int
{
int i;
int st;
const char *arg;
const char *argv[256];
return (errno);
} else if (pid == 0) {
i = 1;
do {
exit(-1);
} else {
else
st = -1;
}
return (st);
}
/*
* Invokes lumount for bename.
* At successfull completion returns zero and copies contents of bename
* into mountpoint[]
*/
static int
{
int rc;
NULL)) != 0)
else
return (rc);
}
/*
* Returns 0 on successful parsing of the arguments;
* returns EINVAL on parsing failures that should abort the reboot attempt;
* returns other error code to fall back to regular reboot.
*/
static int
{
char mountpoint[MAXPATHLEN];
char bootargs_saved[BOOTARGS_MAX];
char bootargs_scratch[BOOTARGS_MAX];
char bootfs_arg[BOOTARGS_MAX];
char unixfile[BOOTARGS_MAX];
int buflen; /* length of the bootargs_buf */
int mplen; /* length of the mount point */
int rootlen = 0; /* length of the root argument */
int unixlen = 0; /* length of the unix argument */
int off = 0; /* offset into the new boot argument */
int is_zfs = 0;
int rc = 0;
/*
* If argc is not 0, buflen is length of the argument being passed in;
* else it is 0 as bootargs_buf has been initialized to all 0's.
*/
/* Save a copy of the original argument */
/* Save another copy to be used by strtok */
head = &bootargs_scratch[0];
/* Get the first argument */
/*
* If this is a dry run request, verify that the drivers can handle
* fast reboot.
*/
*is_dryrun = 1;
}
/*
* Always perform a dry run to identify all the drivers that
* need to implement devo_reset().
*/
(uintptr_t)bootargs_saved) != 0) {
"have implemented quiesce(9E)\n"
"\timplemented quiesce(9E).\n"), cmdname);
} else if (*is_dryrun) {
"implemented quiesce(9E)\n"), cmdname);
}
/* Return if it is a true dry run. */
if (*is_dryrun)
return (rc);
#if defined(__i386)
/* Read boot args from GRUB menu */
/*
* If no boot arguments are given, or a GRUB menu entry
* number is provided, process the GRUB menu.
*/
int entnum;
if (bootargs_buf[0] == 0)
else {
errno = 0;
}
entnum)) == 0) {
}
}
/* Failed to read GRUB menu, fall back to normal reboot */
if (rc != 0) {
gettext("%s: Failed to process GRUB menu "
"entry for fast reboot.\n\t%s\n"),
gettext("%s: Falling back to regular reboot.\n"),
cmdname);
return (-1);
}
/* No need to process further */
fbarg_used = &fbarg;
return (0);
}
#endif /* __i386 */
/* Zero out the boot argument buffer as we will reconstruct it */
sizeof (mountpoint))) != 0)
return (EINVAL);
/*
* If BE is not specified, look for disk argument to construct
* mountpoint; if BE has been specified, mountpoint has already been
* constructed.
*/
int tmprc;
/*
* The first argument is a valid root argument.
* Get the next argument.
*/
sizeof (fastboot_mounted));
} else if (tmprc == -1) {
/*
* Not a disk argument. Use / as default root.
*/
} else {
/*
* Disk argument, but not valid or not root.
* Return failure.
*/
return (EINVAL);
}
}
/*
* Make mountpoint the first part of unixfile.
* If there is not disk argument, and BE has not been specified,
* mountpoint could be empty.
*/
/*
* Look for unix argument
*/
} else if (mplen != 0) {
/*
* No unix argument, but mountpoint is not empty, use
*/
char isa[20];
} else {
return (EINVAL);
}
}
/*
* We now have the complete unix argument. Verify that it exists and
* is an ELF file. Split the argument up into mountpoint and unix
* portions again. This is necessary to handle cases where mountpoint
* is specified on the command line as part of the unix argument,
* such as this:
*/
if (unixlen > 0) {
bootfs_arg, failsafe) != 0) {
/* Not a valid unix file */
return (EINVAL);
} else {
int space = 0;
/*
* Construct boot argument.
*/
/*
* mdep cannot start with space because bootadm
* creates bogus menu entries if it does.
*/
if (mplen > 0) {
space = 1;
}
}
} else {
/* Check to see if root is zfs */
const char *dp;
}
/* LINTED E_SEC_SPRINTF_UNBOUNDED_COPY */
}
/*
* Copy the rest of the arguments
*/
return (rc);
}
#define MAXARGS 5
static void
{
int r, i = 0;
cmd_argv[i++] = "-ea";
cmd_argv[i++] = "update_all";
if (do_fast_reboot)
cmd_argv[i++] = "fastboot";
/* if posix_spawn fails we emit a warning and continue */
if (r != 0)
"boot archive update\n"), cmdname);
else
;
}
int
{
int fast_reboot = 0;
int prom_reboot = 0;
const char *usage;
const char *optstring;
int need_check_zones = 0;
char bootargs_buf[BOOTARGS_MAX];
int failsafe = 0;
(void) textdomain(TEXT_DOMAIN);
optstring = "dlnqy";
cmd = A_SHUTDOWN;
optstring = "dlnqy";
cmd = A_SHUTDOWN;
fcn = AD_POWEROFF;
(void) audit_reboot_setup();
#if defined(__i386)
optstring = "dlnqpfe:";
#else
optstring = "dlnq";
#endif
cmd = A_SHUTDOWN;
} else {
return (1);
}
switch (c) {
case 'd':
if (zoneid == GLOBAL_ZONEID)
else {
gettext("%s: -d only valid from global"
" zone\n"), cmdname);
return (1);
}
break;
case 'l':
needlog = 0;
break;
case 'n':
nosync = 1;
break;
case 'q':
qflag = 1;
break;
case 'y':
break;
#if defined(__i386)
case 'p':
prom_reboot = 1;
break;
case 'f':
fast_reboot = 1;
break;
case 'e':
break;
#endif
default:
/*
* TRANSLATION_NOTE
* Don't translate the words "halt" or "reboot"
*/
return (1);
}
}
if (argc != 0) {
return (1);
}
/* Gather the arguments into bootargs_buf. */
0) {
return (1);
}
} else {
/*
* Initialize it to 0 in case of fastboot, the buffer
* will be used.
*/
}
if (geteuid() != 0) {
goto fail;
}
if (fast_reboot && prom_reboot) {
gettext("%s: -p and -f are mutually exclusive\n"),
cmdname);
return (EINVAL);
}
/*
* Check whether fast reboot is the default operating mode
*/
zoneid == GLOBAL_ZONEID)
if (bename && !fast_reboot) {
cmdname);
return (EINVAL);
}
/*
* If fast reboot, do some sanity check on the argument
*/
if (fast_reboot) {
int rc;
int is_dryrun = 0;
if (zoneid != GLOBAL_ZONEID) {
gettext("%s: Fast reboot only valid from global"
" zone\n"), cmdname);
return (EINVAL);
}
/*
* If dry run, or if arguments are invalid, return.
*/
if (is_dryrun)
return (rc);
goto fail;
else if (rc != 0)
fast_reboot = 0;
/*
* For all the other errors, we continue on in case user
* user want to force fast reboot, or fall back to regular
* reboot.
*/
if (strlen(bootargs_buf) != 0)
}
#if 0 /* For debugging */
#endif
/*
* TRANSLATION_NOTE
* Don't translate ``halt -y''
*/
goto fail;
}
if (needlog) {
char *tty;
user = "root";
else
}
/*
* We must assume success and log it before auditd is terminated.
*/
aval = audit_reboot_success();
else
aval = audit_halt_success();
if (aval == -1) {
if (needlog)
}
/*
* We start to fork a bunch of zoneadms to halt any active zones.
* This will proceed with halt in parallel until we call
* check_zone_haltedness later on.
*/
}
#if defined(__i386)
/* set new default entry in the GRUB entry */
if (fbarg_entnum != GRUB_ENTRY_DEFAULT) {
char buf[32];
}
#endif /* __i386 */
/* if we're dumping, do the archive update here and don't defer it */
/*
* If we're not forcing a crash dump, mark the system as quiescing for
* smf(5)'s benefit, and idle the init process.
*/
/*
* TRANSLATION_NOTE
* Don't translate the word "init"
*/
goto fail;
}
gettext("%s: could not create %s.\n"),
/*
* Stop all restarters so they do not try to restart services
* that are terminated.
*/
/*
* Wait a little while for zones to shutdown.
*/
if (need_check_zones) {
gettext("%s: Completing system halt.\n"),
cmdname);
}
}
/*
* Make sure we don't get stopped by a jobcontrol shell
* once we start killing everybody.
*/
/*
* If we're not forcing a crash dump, give everyone 5 seconds to
* handle a SIGTERM and clean up properly.
*/
if (delta < 5)
}
else
sync();
}
if (fast_reboot) {
if (failsafe)
else
fcn = AD_FASTREBOOT;
}
else
cmdname);
do {
/*
* TRANSLATION_NOTE
* Don't translate the word "init"
*/
}
if (get_initpid() != -1)
/* tell init to restate current level */
fail:
(void) audit_reboot_fail();
else
(void) audit_halt_fail();
if (fast_reboot) {
if (bename) {
} else if (strlen(fastboot_mounted) != 0) {
(void) umount(fastboot_mounted);
#if defined(__i386)
} else if (fbarg_used != NULL) {
#endif /* __i386 */
}
}
return (1);
}