implement c->reboot(c) in the api.
Also if the container is not running, return -2. Currently
lxc-stop will return 0, so you cannot tell the difference
between successfull stopping and noop.
Per stgraber's email:
- Remove lxc-shutdown
- Change lxc-stop so that:
* Default behaviour is to call shutdown(), wait 15s for STOPPED, if
not STOPPED, print a message to the user and call stop() [ NOTE:
actually 60 seconds per followup thread]
* We have a -r option to reboot the container (with proper check that
the container indeed rebooted within the next 15s)
* We have a -s option to shutdown the container without the automatic
fallback to stop()
* Add a -k option allowing a user to just kill a container
(equivalent to old lxc-stop, no shutdown() call and no delay).
and update manpages.
Signed-off-by: Serge Hallyn <serge.hallyn@ubuntu.com>
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
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
lxc-netstat.1 \
lxc-ps.1 \
lxc-restart.1 \
- lxc-shutdown.1 \
lxc-start.1 \
lxc-stop.1 \
lxc-unfreeze.1 \
+++ /dev/null
-<!--
-
-Copyright (C) 2012 Canonical, Inc
-
-Authors: Serge Hallyn <serge.hallyn@canonical.com>
-
-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
-
--->
-
-<!DOCTYPE refentry PUBLIC @docdtd@ [
-
-<!ENTITY commonoptions SYSTEM "@builddir@/common_options.sgml">
-<!ENTITY seealso SYSTEM "@builddir@/see_also.sgml">
-]>
-
-<refentry>
-
- <docinfo><date>@LXC_GENERATE_DATE@</date></docinfo>
-
- <refmeta>
- <refentrytitle>lxc-shutdown</refentrytitle>
- <manvolnum>1</manvolnum>
- </refmeta>
-
- <refnamediv>
- <refname>lxc-shutdown</refname>
-
- <refpurpose>
- externally shut down or reboot a container
- </refpurpose>
- </refnamediv>
-
- <refsynopsisdiv>
- <cmdsynopsis>
- <command>lxc-shutdown</command>
- <arg choice="req">-n <replaceable>name</replaceable></arg>
- <arg choice="opt">-w</arg>
- <arg choice="opt">-r</arg>
- </cmdsynopsis>
- </refsynopsisdiv>
-
- <refsect1>
- <title>Description</title>
-
- <para>
- <command>lxc-shutdown</command> sends a SIGPWR signal to the
- specified container to request it to cleanly shut down. If
- <optional>-w</optional> is specified, then <command>lxc-shutdown</command>
- will wait until the container has shut down before exiting.
- If <optional>-r</optional> is specified, the container will be
- asked to reboot (using a SIGINT signal), and <optional>-w</optional>
- will be ignored. If the container ignore these signals, then
- nothing will happen. In that case, you can use <command>lxc-stop</command>
- to force the container to stop.
- </para>
-
- </refsect1>
-
- &commonoptions;
-
- &seealso;
-
- <refsect1>
- <title>Author</title>
- <para>Serge Hallyn <email>serge.hallyn@canonical.com</email></para>
- </refsect1>
-
-</refentry>
-
-<!-- Keep this comment at the end of the file
-Local variables:
-mode: sgml
-sgml-omittag:t
-sgml-shorttag:t
-sgml-minimize-attributes:nil
-sgml-always-quote-attributes:t
-sgml-indent-step:2
-sgml-indent-data:t
-sgml-parent-document:nil
-sgml-default-dtd-file:nil
-sgml-exposed-tags:nil
-sgml-local-catalogs:nil
-sgml-local-ecat-files:nil
-End:
--->
<cmdsynopsis>
<command>lxc-stop</command>
<arg choice="req">-n <replaceable>name</replaceable></arg>
+ <arg choice="opt">-W</arg>
+ <arg choice="opt">-r</arg>
+ <arg choice="opt">-t <replaceable>timeout</replaceable></arg>
+ <arg choice="opt">-k</arg>
+ <arg choice="opt">-s</arg>
</cmdsynopsis>
</refsynopsisdiv>
<title>Description</title>
<para>
- <command>lxc-stop</command> 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.
+ <command>lxc-stop</command> 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
+ <command>lxc.stopsignal</command> to force it to shut down.
</para>
-
+ <para>
+ The <optional>-W</optional>, <optional>-r</optional>, <optional>-s</optional>
+ and <optional>-k</optional> options specify the action to perform.
+ <optional>-W</optional> indicates that after performing the specified
+ action, <command>lxc-stop</command> should immediately exit, while
+ <optional>-t TIMEOUT</optional> specifies the maximum amount of time
+ to wait for the container to complete the shutdown or reboot.
+ </para>
</refsect1>
- &commonoptions;
+ <refsect1>
+ <title>Options</title>
+ <variablelist>
+
+ <varlistentry>
+ <term>
+ <option>-r,--reboot </option>
+ </term>
+ <listitem>
+ <para>
+ Request a reboot of the container.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <option>-s,--shutdown </option>
+ </term>
+ <listitem>
+ <para>
+ Only request a clean shutdown, do not kill the container tasks if the
+ clean shutdown fails.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <option>-k,--kill </option>
+ </term>
+ <listitem>
+ <para>
+ Rather than requesting a clean shutdown of the container, explicitly
+ kill all tasks in the container. This is the legacy
+ <command>lxc-stop</command> behavior.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <option>-W,--nowait </option>
+ </term>
+ <listitem>
+ <para>
+ Simply perform the requestion action (reboot, shutdown, or hard
+ kill) and exit.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <option>-t,--timeout <replaceable>TIMEOUT</replaceable></option>
+ </term>
+ <listitem>
+ <para>
+ Wait TIMEOUT seconds before hard-stopping the container of (in
+ the reboot case) returning failure.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+ </refsect1>
<refsect1>
<title>Diagnostic</title>
</listitem>
</varlistentry>
-
</variablelist>
</refsect1>
lxc-checkconfig \
lxc-version \
lxc-create \
- lxc-shutdown \
lxc-destroy
EXTRA_DIST = \
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;
+++ /dev/null
-#!/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
#include <lxc/lxc.h>
#include <lxc/log.h>
+#include <lxc/lxccontainer.h>
#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
};
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;
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;
}
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
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;
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;
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;
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 */
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)
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;
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;
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;
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 */