diff --git a/configure.ac b/configure.ac index 414d71b68..8979f7589 100644 --- a/configure.ac +++ b/configure.ac @@ -334,7 +334,6 @@ AC_CONFIG_FILES([ doc/lxc-netstat.sgml doc/lxc-ps.sgml doc/lxc-restart.sgml - doc/lxc-shutdown.sgml doc/lxc-start-ephemeral.sgml doc/lxc-start.sgml doc/lxc-stop.sgml @@ -383,7 +382,6 @@ AC_CONFIG_FILES([ src/lxc/lxc-checkconfig src/lxc/lxc-version src/lxc/lxc-create - src/lxc/lxc-shutdown src/lxc/lxc-start-ephemeral src/lxc/lxc-destroy src/lxc/legacy/lxc-ls diff --git a/doc/Makefile.am b/doc/Makefile.am index 1a2469548..a00036a39 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -22,7 +22,6 @@ man_MANS = \ lxc-netstat.1 \ lxc-ps.1 \ lxc-restart.1 \ - lxc-shutdown.1 \ lxc-start.1 \ lxc-stop.1 \ lxc-unfreeze.1 \ diff --git a/doc/lxc-shutdown.sgml.in b/doc/lxc-shutdown.sgml.in deleted file mode 100644 index 9262c6d79..000000000 --- a/doc/lxc-shutdown.sgml.in +++ /dev/null @@ -1,98 +0,0 @@ - - - - -]> - - - - @LXC_GENERATE_DATE@ - - - lxc-shutdown - 1 - - - - lxc-shutdown - - - externally shut down or reboot a container - - - - - - lxc-shutdown - -n name - -w - -r - - - - - Description - - - lxc-shutdown sends a SIGPWR signal to the - specified container to request it to cleanly shut down. If - -w is specified, then lxc-shutdown - will wait until the container has shut down before exiting. - If -r is specified, the container will be - asked to reboot (using a SIGINT signal), and -w - will be ignored. If the container ignore these signals, then - nothing will happen. In that case, you can use lxc-stop - to force the container to stop. - - - - - &commonoptions; - - &seealso; - - - Author - Serge Hallyn serge.hallyn@canonical.com - - - - - diff --git a/doc/lxc-stop.sgml.in b/doc/lxc-stop.sgml.in index b4dcc1bcf..2dbff8003 100644 --- a/doc/lxc-stop.sgml.in +++ b/doc/lxc-stop.sgml.in @@ -50,6 +50,11 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA lxc-stop -n name + -W + -r + -t timeout + -k + -s @@ -57,14 +62,90 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Description - lxc-stop kills all the processes inside the - container. This command should be used if the processes are no - longer accessible and can no be exited normally. + lxc-stop reboots, cleanly shuts down, or kills + all the processes inside the container. By default, it will + request a clean shutdown of the container (by sending SIGPWR to + the container), wait 60 seconds for the container to exit, and + returns. If the container fails to cleanly exit, then after 60 + seconds the container will be sent the + lxc.stopsignal to force it to shut down. - + + The -W, -r, -s + and -k options specify the action to perform. + -W indicates that after performing the specified + action, lxc-stop should immediately exit, while + -t TIMEOUT specifies the maximum amount of time + to wait for the container to complete the shutdown or reboot. + - &commonoptions; + + Options + + + + + + + + + Request a reboot of the container. + + + + + + + + + + + Only request a clean shutdown, do not kill the container tasks if the + clean shutdown fails. + + + + + + + + + + + Rather than requesting a clean shutdown of the container, explicitly + kill all tasks in the container. This is the legacy + lxc-stop behavior. + + + + + + + + + + + Simply perform the requestion action (reboot, shutdown, or hard + kill) and exit. + + + + + + + + + + + Wait TIMEOUT seconds before hard-stopping the container of (in + the reboot case) returning failure. + + + + + + Diagnostic @@ -92,7 +173,6 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - diff --git a/src/lxc/Makefile.am b/src/lxc/Makefile.am index f8eb41b8d..578ee060b 100644 --- a/src/lxc/Makefile.am +++ b/src/lxc/Makefile.am @@ -123,7 +123,6 @@ bin_SCRIPTS = \ lxc-checkconfig \ lxc-version \ lxc-create \ - lxc-shutdown \ lxc-destroy EXTRA_DIST = \ diff --git a/src/lxc/arguments.h b/src/lxc/arguments.h index 002a91961..145474d44 100644 --- a/src/lxc/arguments.h +++ b/src/lxc/arguments.h @@ -61,9 +61,13 @@ struct lxc_arguments { int ttynum; char escape; - /* for lxc-wait */ + /* for lxc-wait and lxc-shutdown */ char *states; long timeout; + int nowait; + int reboot; + int hardstop; + int shutdown; /* close fds from parent? */ int close_all_fds; diff --git a/src/lxc/lxc-shutdown.in b/src/lxc/lxc-shutdown.in deleted file mode 100644 index c81e736b6..000000000 --- a/src/lxc/lxc-shutdown.in +++ /dev/null @@ -1,165 +0,0 @@ -#!/bin/sh - -# (C) Copyright Canonical 2011,2012 - -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. - -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. - -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -set -e - -. @DATADIR@/lxc/lxc.functions - -usage() { - echo "usage: lxc-shutdown -n name [-w] [-r] [-P lxcpath]" - echo " Cleanly shut down a container." - echo " -w: wait for shutdown to complete." - echo " -r: reboot (ignore -w)." - echo " -t timeout: wait at most timeout seconds (implies -w), then kill" - echo " the container." - echo " -P lxcpath: path to the lxc container directories." -} - -alarm() { - trap 'exit 0' TERM - pid=$1 - timeout=$2 - sleep $timeout - kill $pid -} - -dolxcstop() -{ - echo "Calling lxc-stop on $lxc_name" - lxc-stop -n $lxc_name -P "$lxc_path" - exit 0 -} - -usage_err() { - [ -n "$1" ] && echo "$1" >&2 - usage - exit 1 -} - -optarg_check() { - [ -n "$2" ] || usage_err "option '$1' requires an argument" -} - -timeout="-1" - -reboot=0 -dowait=0 - -while [ $# -gt 0 ]; do - opt="$1" - shift - case "$opt" in - -h|--help) - usage - exit 0 - ;; - -n|--name) - optarg_check $opt "$1" - lxc_name=$1 - shift - ;; - -w|--wait) - dowait=1 - ;; - -r|--reboot) - reboot=1 - ;; - -t|--timeout) - optarg_check $opt "$1" - timeout=$1 - dowait=1 - shift - ;; - -P|--lxcpath) - optarg_check $opt "$1" - lxc_path=$1 - dowait=1 - shift - ;; - --) - break;; - -?) - usage_err "unknown option '$opt'" - ;; - -*) - # split opts -abc into -a -b -c - set -- $(echo "${opt#-}" | sed 's/\(.\)/ -\1/g') "$@" - ;; - *) - usage_err "unknown option '$opt'" - exit 1 - ;; - esac -done - -if [ -z "$lxc_name" ]; then - echo "no container name specified" - usage - exit 1 -fi - -if [ ! -d "$lxc_path" ]; then - echo "$lxc_path: no such directory" - exit 1 -fi - -if [ "$(id -u)" != "0" ]; then - echo "This command has to be run as root" - exit 1 -fi - -which lxc-info > /dev/null 2>&1 || { echo "lxc-info not found."; exit 1; } -which lxc-wait > /dev/null 2>&1 || { echo "lxc-wait not found."; exit 1; } - -pid=`lxc-info -n $lxc_name -P "$lxc_path" -p 2>/dev/null | awk '{ print $2 }'` -if [ "$pid" = "-1" ]; then - echo "$lxc_name is not running" - exit 1 -fi - -if [ $reboot -eq 1 ]; then - kill -s INT $pid - exit 0 -else - kill -s PWR $pid -fi - -if [ $dowait -eq 0 ]; then - exit 0 -fi - -if [ $timeout != "-1" ]; then - trap dolxcstop EXIT - alarm $$ $timeout 2>/dev/null & - alarmpid=$! -fi - -while ! lxc-info -n $lxc_name -P "$lxc_path" --state-is STOPPED; do - sleep 1 -done - -if [ $timeout != "-1" ]; then - trap - EXIT - # include subprocesses; otherwise, we may have to wait until sleep completes - # if called from a non-interactive context - kill $alarmpid $(ps --no-headers --ppid $alarmpid -o pid) 2>/dev/null || : -fi - -echo "Container $lxc_name has shut down" - -exit 0 diff --git a/src/lxc/lxc_stop.c b/src/lxc/lxc_stop.c index 0ed8ca066..3d42d0e65 100644 --- a/src/lxc/lxc_stop.c +++ b/src/lxc/lxc_stop.c @@ -28,11 +28,29 @@ #include #include +#include #include "arguments.h" #include "commands.h" #include "utils.h" +static int my_parser(struct lxc_arguments* args, int c, char* arg) +{ + switch (c) { + case 'r': args->reboot = 1; break; + case 'W': args->nowait = 1; break; + case 't': args->timeout = atoi(arg); break; + case 'k': args->hardstop = 1; break; + case 's': args->shutdown = 1; break; + } + return 0; +} + static const struct option my_longopts[] = { + {"reboot", no_argument, 0, 'r'}, + {"nowait", no_argument, 0, 'W'}, + {"timeout", required_argument, 0, 't'}, + {"kill", no_argument, 0, 'k'}, + {"shutdown", no_argument, 0, 's'}, LXC_COMMON_OPTIONS }; @@ -44,14 +62,76 @@ static struct lxc_arguments my_args = { lxc-stop stops a container with the identifier NAME\n\ \n\ Options :\n\ - -n, --name=NAME NAME for name of the container\n", + -n, --name=NAME NAME for name of the container\n\ + -r, --reboot reboot the container\n\ + -W, --nowait don't wait for shutdown or reboot to complete\n\ + -t, --timeout=T wait T seconds before hard-stopping\n\ + -k, --kill kill container rather than request clean shutdown\n\ + -s, --shutdown Only request clean shutdown, don't later force kill\n", .options = my_longopts, - .parser = NULL, + .parser = my_parser, .checker = NULL, + .timeout = 60, }; +/* returns -1 on failure, 0 on success */ +int do_reboot_and_check(struct lxc_arguments *a, struct lxc_container *c) +{ + int ret; + pid_t pid; + pid_t newpid; + int timeout = a->timeout; + + pid = c->init_pid(c); + if (pid == -1) + return -1; + if (!c->reboot(c)) + return -1; + if (a->nowait) + return 0; + if (timeout <= 0) + goto out; + + for (;;) { + /* can we use c-> wait for this, assuming it will + * re-enter RUNNING? For now just sleep */ + int elapsed_time, curtime = 0; + struct timeval tv; + + newpid = c->init_pid(c); + if (newpid != -1 && newpid != pid) + return 0; + + ret = gettimeofday(&tv, NULL); + if (ret) + break; + curtime = tv.tv_sec; + + sleep(1); + ret = gettimeofday(&tv, NULL); + if (ret) + break; + elapsed_time = tv.tv_sec - curtime; + if (timeout - elapsed_time <= 0) + break; + timeout -= elapsed_time; + } + +out: + newpid = c->init_pid(c); + if (newpid == -1 || newpid == pid) { + printf("Reboot did not complete before timeout\n"); + return -1; + } + return 0; +} + int main(int argc, char *argv[]) { + struct lxc_container *c; + bool s; + int ret = -1; + if (lxc_arguments_parse(&my_args, argc, argv)) return -1; @@ -59,5 +139,36 @@ int main(int argc, char *argv[]) my_args.progname, my_args.quiet, my_args.lxcpath[0])) return -1; - return lxc_cmd_stop(my_args.name, my_args.lxcpath[0]); + c = lxc_container_new(my_args.name, my_args.lxcpath[0]); + if (!c) { + fprintf(stderr, "Error opening container\n"); + goto out; + } + + if (!c->is_running(c)) { + fprintf(stderr, "%s is not running\n", c->name); + ret = -2; + goto out; + } + + if (my_args.hardstop) { + ret = c->stop(c) ? 0 : -1; + goto out; + } + if (my_args.reboot) { + ret = do_reboot_and_check(&my_args, c); + goto out; + } + + s = c->shutdown(c, my_args.timeout); + if (!s) { + if (!my_args.shutdown) + ret = c->wait(c, "STOPPED", -1) ? 0 : -1; + else + ret = -1; // fail + } + +out: + lxc_container_put(c); + return ret; } diff --git a/src/lxc/lxccontainer.c b/src/lxc/lxccontainer.c index 2a45c1fe0..a1c156c64 100644 --- a/src/lxc/lxccontainer.c +++ b/src/lxc/lxccontainer.c @@ -45,6 +45,105 @@ lxc_log_define(lxc_container, lxc); +static bool file_exists(char *f) +{ + struct stat statbuf; + + return stat(f, &statbuf) == 0; +} + +/* + * A few functions to help detect when a container creation failed. + * If a container creation was killed partway through, then trying + * to actually start that container could harm the host. We detect + * this by creating a 'partial' file under the container directory, + * and keeping an advisory lock. When container creation completes, + * we remove that file. When we load or try to start a container, if + * we find that file, without a flock, we remove the container. + */ +int ongoing_create(struct lxc_container *c) +{ + int len = strlen(c->config_path) + strlen(c->name) + 10; + char *path = alloca(len); + int fd, ret; + ret = snprintf(path, len, "%s/%s/partial", c->config_path, c->name); + if (ret < 0 || ret >= len) { + ERROR("Error writing partial pathname"); + return -1; + } + + if (!file_exists(path)) + return 0; + if (process_lock()) + return -1; + if ((fd = open(path, O_RDWR)) < 0) { + // give benefit of the doubt + SYSERROR("Error opening partial file"); + process_unlock(); + return 0; + } + if ((ret = flock(fd, LOCK_EX | LOCK_NB)) == -1 && + errno == EWOULDBLOCK) { + // create is still ongoing + close(fd); + process_unlock(); + return 1; + } + // create completed but partial is still there. + close(fd); + process_unlock(); + return 2; +} + +int create_partial(struct lxc_container *c) +{ + // $lxcpath + '/' + $name + '/partial' + \0 + int len = strlen(c->config_path) + strlen(c->name) + 10; + char *path = alloca(len); + int fd, ret; + ret = snprintf(path, len, "%s/%s/partial", c->config_path, c->name); + if (ret < 0 || ret >= len) { + ERROR("Error writing partial pathname"); + return -1; + } + if (process_lock() < 0) + return -1; + if ((fd=open(path, O_CREAT | O_EXCL, 0755)) < 0) { + SYSERROR("Erorr creating partial file"); + process_unlock(); + return -1; + } + if (flock(fd, LOCK_EX) < 0) { + SYSERROR("Error locking partial file %s", path); + close(fd); + process_unlock(); + return -1; + } + process_unlock(); + + return fd; +} + +void remove_partial(struct lxc_container *c, int fd) +{ + // $lxcpath + '/' + $name + '/partial' + \0 + int len = strlen(c->config_path) + strlen(c->name) + 10; + char *path = alloca(len); + int ret; + + close(fd); + ret = snprintf(path, len, "%s/%s/partial", c->config_path, c->name); + if (ret < 0 || ret >= len) { + ERROR("Error writing partial pathname"); + return; + } + if (process_lock()) + return; + if (unlink(path) < 0) + SYSERROR("Error unlink partial file %s", path); + process_unlock(); +} + /* LOCKING * 1. c->privlock protects the struct lxc_container from multiple threads. * 2. c->slock protects the on-disk container data @@ -163,13 +262,6 @@ int lxc_container_put(struct lxc_container *c) return 0; } -static bool file_exists(char *f) -{ - struct stat statbuf; - - return stat(f, &statbuf) == 0; -} - static bool lxcapi_is_defined(struct lxc_container *c) { struct stat statbuf; @@ -349,6 +441,19 @@ static bool lxcapi_start(struct lxc_container *c, int useinit, char * const argv if (!c->lxc_conf) return false; + if ((ret = ongoing_create(c)) < 0) { + ERROR("Error checking for incomplete creation"); + return false; + } + if (ret == 2) { + ERROR("Error: %s creation was not completed", c->name); + c->destroy(c); + return false; + } else if (ret == 1) { + ERROR("Error: creation of %s is ongoing", c->name); + return false; + } + /* is this app meant to be run through lxcinit, as in lxc-execute? */ if (useinit && !argv) return false; @@ -550,7 +655,7 @@ static bool lxcapi_create(struct lxc_container *c, const char *t, char *const ar bool bret = false; pid_t pid; char *tpath = NULL, **newargv; - int ret, len, nargs = 0; + int partial_fd, ret, len, nargs = 0; if (!c) return false; @@ -576,6 +681,10 @@ static bool lxcapi_create(struct lxc_container *c, const char *t, char *const ar if (lxcapi_is_defined(c) && c->lxc_conf && c->lxc_conf->rootfs.path && access(c->lxc_conf->rootfs.path, F_OK) == 0) goto out; + /* Mark that this container is being created */ + if ((partial_fd = create_partial(c)) < 0) + goto out; + /* we're going to fork. but since we'll wait for our child, we * don't need to lxc_container_get */ @@ -659,6 +768,8 @@ static bool lxcapi_create(struct lxc_container *c, const char *t, char *const ar bret = load_config_locked(c, c->configfile); out_unlock: + if (partial_fd >= 0) + remove_partial(c, partial_fd); container_disk_unlock(c); out: if (tpath) @@ -666,6 +777,23 @@ out: return bret; } +static bool lxcapi_reboot(struct lxc_container *c) +{ + pid_t pid; + + if (!c) + return false; + if (!c->is_running(c)) + return false; + pid = c->init_pid(c); + if (pid <= 0) + return false; + if (kill(pid, SIGINT) < 0) + return false; + return true; + +} + static bool lxcapi_shutdown(struct lxc_container *c, int timeout) { bool retv; @@ -1699,6 +1827,12 @@ struct lxc_container *lxc_container_new(const char *name, const char *configpath if (file_exists(c->configfile)) lxcapi_load_config(c, NULL); + if (ongoing_create(c) == 2) { + ERROR("Error: %s creation was not completed", c->name); + c->destroy(c); + goto err; + } + // assign the member functions c->is_defined = lxcapi_is_defined; c->state = lxcapi_state; @@ -1720,6 +1854,7 @@ struct lxc_container *lxc_container_new(const char *name, const char *configpath c->create = lxcapi_create; c->createl = lxcapi_createl; c->shutdown = lxcapi_shutdown; + c->reboot = lxcapi_reboot; c->clear_config_item = lxcapi_clear_config_item; c->get_config_item = lxcapi_get_config_item; c->get_cgroup_item = lxcapi_get_cgroup_item; diff --git a/src/lxc/lxccontainer.h b/src/lxc/lxccontainer.h index 1ca751926..3a80d0f9f 100644 --- a/src/lxc/lxccontainer.h +++ b/src/lxc/lxccontainer.h @@ -50,6 +50,8 @@ struct lxc_container { bool (*save_config)(struct lxc_container *c, const char *alt_file); bool (*create)(struct lxc_container *c, const char *t, char *const argv[]); bool (*createl)(struct lxc_container *c, const char *t, ...); + /* send SIGINT to ask container to reboot */ + bool (*reboot)(struct lxc_container *c); /* send SIGPWR. if timeout is not 0 or -1, do a hard stop after timeout seconds */ bool (*shutdown)(struct lxc_container *c, int timeout); /* clear all network or capability items in the in-memory configuration */