mirror of
https://git.proxmox.com/git/mirror_lxc
synced 2025-05-29 04:12:23 +00:00

Here is a patch to introduce a configurable system-wide lxcpath. It seems to work with lxc-create, lxc-start, and basic python3 lxc usage through the api. For shell functions, a new /usr/share/lxc/lxc.functions is introduced which sets some of the basic global variables, including evaluating the right place for lxc_path. I have not converted any of the other python code, as I was not sure where we should keep the common functions (i.e. for now just default_lxc_path()). configure.ac: add an option for setting the global config file name. utils: add a default_lxc_path() function Use default_lxc_path in .c files define get_lxc_path() and set_lxc_path() in C api use get_lxc_path() in lua api create sh helper for getting default path from config file fix up scripts to use lxc.functions Changelog: feb6: fix lxc_path in lxc.functions utils.c: as Dwight pointed out, don't close a NULL fin. utils.c: fix the parsing of lxcpath line lxc-start: print which rcfile we are using commands.c: As Dwight alluded to, the sockname handling was just ridiculous. Clean that up. use Dwight's recommendation for lxc.functions path: $datadir/lxc make lxccontainer->get_config_path() return const char * Per Dwight's suggestion, much nicer than returning strdup. feb6 (v2): lxccontainer: set c->config_path before using it. convert legacy lxc-ls Signed-off-by: Serge Hallyn <serge.hallyn@ubuntu.com> Acked-by: Stéphane Graber <stgraber@ubuntu.com>
1044 lines
21 KiB
C
1044 lines
21 KiB
C
/* liblxcapi
|
|
*
|
|
* Copyright © 2012 Serge Hallyn <serge.hallyn@ubuntu.com>.
|
|
* Copyright © 2012 Canonical Ltd.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2, as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* This program 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 General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*/
|
|
|
|
#include "lxc.h"
|
|
#include "state.h"
|
|
#include "lxccontainer.h"
|
|
#include "conf.h"
|
|
#include "config.h"
|
|
#include "confile.h"
|
|
#include "cgroup.h"
|
|
#include "commands.h"
|
|
#include "log.h"
|
|
#include <unistd.h>
|
|
#include <sys/types.h>
|
|
#include <sys/wait.h>
|
|
#include <errno.h>
|
|
#include <lxc/utils.h>
|
|
|
|
lxc_log_define(lxc_container, lxc);
|
|
|
|
/* LOCKING
|
|
* c->privlock protects the struct lxc_container from multiple threads.
|
|
* c->slock protects the on-disk container data
|
|
* NOTHING mutexes two independent programs with their own struct
|
|
* lxc_container for the same c->name, between API calls. For instance,
|
|
* c->config_read(); c->start(); Between those calls, data on disk
|
|
* could change (which shouldn't bother the caller unless for instance
|
|
* the rootfs get moved). c->config_read(); update; c->config_write();
|
|
* Two such updaters could race. The callers should therefore check their
|
|
* results. Trying to prevent that would necessarily expose us to deadlocks
|
|
* due to hung callers. So I prefer to keep the locks only within our own
|
|
* functions, not across functions.
|
|
*
|
|
* If you're going to fork while holding a lxccontainer, increment
|
|
* c->numthreads (under privlock) before forking. When deleting,
|
|
* decrement numthreads under privlock, then if it hits 0 you can delete.
|
|
* Do not ever use a lxccontainer whose numthreads you did not bump.
|
|
*/
|
|
|
|
static void lxc_container_free(struct lxc_container *c)
|
|
{
|
|
if (!c)
|
|
return;
|
|
|
|
if (c->configfile) {
|
|
free(c->configfile);
|
|
c->configfile = NULL;
|
|
}
|
|
if (c->error_string) {
|
|
free(c->error_string);
|
|
c->error_string = NULL;
|
|
}
|
|
if (c->slock) {
|
|
sem_close(c->slock);
|
|
c->slock = NULL;
|
|
}
|
|
if (c->privlock) {
|
|
sem_destroy(c->privlock);
|
|
free(c->privlock);
|
|
c->privlock = NULL;
|
|
}
|
|
if (c->name) {
|
|
free(c->name);
|
|
c->name = NULL;
|
|
}
|
|
if (c->lxc_conf) {
|
|
lxc_conf_free(c->lxc_conf);
|
|
c->lxc_conf = NULL;
|
|
}
|
|
if (c->config_path) {
|
|
free(c->config_path);
|
|
c->config_path = NULL;
|
|
}
|
|
free(c);
|
|
}
|
|
|
|
int lxc_container_get(struct lxc_container *c)
|
|
{
|
|
if (!c)
|
|
return 0;
|
|
|
|
if (lxclock(c->privlock, 0))
|
|
return 0;
|
|
if (c->numthreads < 1) {
|
|
// bail without trying to unlock, bc the privlock is now probably
|
|
// in freed memory
|
|
return 0;
|
|
}
|
|
c->numthreads++;
|
|
lxcunlock(c->privlock);
|
|
return 1;
|
|
}
|
|
|
|
int lxc_container_put(struct lxc_container *c)
|
|
{
|
|
if (!c)
|
|
return -1;
|
|
if (lxclock(c->privlock, 0))
|
|
return -1;
|
|
if (--c->numthreads < 1) {
|
|
lxcunlock(c->privlock);
|
|
lxc_container_free(c);
|
|
return 1;
|
|
}
|
|
lxcunlock(c->privlock);
|
|
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;
|
|
bool ret = false;
|
|
int statret;
|
|
|
|
if (!c)
|
|
return false;
|
|
|
|
if (lxclock(c->privlock, 0))
|
|
return false;
|
|
if (!c->configfile)
|
|
goto out;
|
|
statret = stat(c->configfile, &statbuf);
|
|
if (statret != 0)
|
|
goto out;
|
|
ret = true;
|
|
|
|
out:
|
|
lxcunlock(c->privlock);
|
|
return ret;
|
|
}
|
|
|
|
static const char *lxcapi_state(struct lxc_container *c)
|
|
{
|
|
const char *ret;
|
|
lxc_state_t s;
|
|
|
|
if (!c)
|
|
return NULL;
|
|
if (lxclock(c->slock, 0))
|
|
return NULL;
|
|
s = lxc_getstate(c->name);
|
|
ret = lxc_state2str(s);
|
|
lxcunlock(c->slock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static bool is_stopped_nolock(struct lxc_container *c)
|
|
{
|
|
lxc_state_t s;
|
|
s = lxc_getstate(c->name);
|
|
return (s == STOPPED);
|
|
}
|
|
|
|
static bool lxcapi_is_running(struct lxc_container *c)
|
|
{
|
|
const char *s;
|
|
|
|
if (!c)
|
|
return false;
|
|
s = lxcapi_state(c);
|
|
if (!s || strcmp(s, "STOPPED") == 0)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
static bool lxcapi_freeze(struct lxc_container *c)
|
|
{
|
|
int ret;
|
|
if (!c)
|
|
return false;
|
|
|
|
if (lxclock(c->slock, 0))
|
|
return false;
|
|
ret = lxc_freeze(c->name);
|
|
lxcunlock(c->slock);
|
|
if (ret)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
static bool lxcapi_unfreeze(struct lxc_container *c)
|
|
{
|
|
int ret;
|
|
if (!c)
|
|
return false;
|
|
|
|
if (lxclock(c->slock, 0))
|
|
return false;
|
|
ret = lxc_unfreeze(c->name);
|
|
lxcunlock(c->slock);
|
|
if (ret)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
static pid_t lxcapi_init_pid(struct lxc_container *c)
|
|
{
|
|
pid_t ret;
|
|
if (!c)
|
|
return -1;
|
|
|
|
if (lxclock(c->slock, 0))
|
|
return -1;
|
|
ret = get_init_pid(c->name);
|
|
lxcunlock(c->slock);
|
|
return ret;
|
|
}
|
|
|
|
static bool load_config_locked(struct lxc_container *c, const char *fname)
|
|
{
|
|
if (!c->lxc_conf)
|
|
c->lxc_conf = lxc_conf_init();
|
|
if (c->lxc_conf && !lxc_config_read(fname, c->lxc_conf))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
static bool lxcapi_load_config(struct lxc_container *c, const char *alt_file)
|
|
{
|
|
bool ret = false;
|
|
const char *fname;
|
|
if (!c)
|
|
return false;
|
|
|
|
fname = c->configfile;
|
|
if (alt_file)
|
|
fname = alt_file;
|
|
if (!fname)
|
|
return false;
|
|
if (lxclock(c->slock, 0))
|
|
return false;
|
|
ret = load_config_locked(c, fname);
|
|
lxcunlock(c->slock);
|
|
return ret;
|
|
}
|
|
|
|
static void lxcapi_want_daemonize(struct lxc_container *c)
|
|
{
|
|
if (!c)
|
|
return;
|
|
c->daemonize = 1;
|
|
}
|
|
|
|
static bool lxcapi_wait(struct lxc_container *c, const char *state, int timeout)
|
|
{
|
|
int ret;
|
|
|
|
if (!c)
|
|
return false;
|
|
|
|
ret = lxc_wait(c->name, state, timeout);
|
|
return ret == 0;
|
|
}
|
|
|
|
|
|
static bool wait_on_daemonized_start(struct lxc_container *c)
|
|
{
|
|
/* we'll probably want to make this timeout configurable? */
|
|
int timeout = 5, ret, status;
|
|
|
|
/*
|
|
* our child is going to fork again, then exit. reap the
|
|
* child
|
|
*/
|
|
ret = wait(&status);
|
|
if (ret == -1 || !WIFEXITED(status) || WEXITSTATUS(status) != 0)
|
|
DEBUG("failed waiting for first dual-fork child");
|
|
return lxcapi_wait(c, "RUNNING", timeout);
|
|
}
|
|
|
|
/*
|
|
* I can't decide if it'd be more convenient for callers if we accept '...',
|
|
* or a null-terminated array (i.e. execl vs execv)
|
|
*/
|
|
static bool lxcapi_start(struct lxc_container *c, int useinit, char * const argv[])
|
|
{
|
|
int ret;
|
|
struct lxc_conf *conf;
|
|
int daemonize = 0;
|
|
char *default_args[] = {
|
|
"/sbin/init",
|
|
'\0',
|
|
};
|
|
|
|
/* container exists */
|
|
if (!c)
|
|
return false;
|
|
/* container has been setup */
|
|
if (!c->lxc_conf)
|
|
return false;
|
|
|
|
/* is this app meant to be run through lxcinit, as in lxc-execute? */
|
|
if (useinit && !argv)
|
|
return false;
|
|
|
|
if (lxclock(c->privlock, 0))
|
|
return false;
|
|
conf = c->lxc_conf;
|
|
daemonize = c->daemonize;
|
|
lxcunlock(c->privlock);
|
|
|
|
if (useinit) {
|
|
ret = lxc_execute(c->name, argv, 1, conf);
|
|
return ret == 0 ? true : false;
|
|
}
|
|
|
|
if (!argv)
|
|
argv = default_args;
|
|
|
|
/*
|
|
* say, I'm not sure - what locks do we want here? Any?
|
|
* Is liblxc's locking enough here to protect the on disk
|
|
* container? We don't want to exclude things like lxc_info
|
|
* while container is running...
|
|
*/
|
|
if (daemonize) {
|
|
if (!lxc_container_get(c))
|
|
return false;
|
|
pid_t pid = fork();
|
|
if (pid < 0) {
|
|
lxc_container_put(c);
|
|
return false;
|
|
}
|
|
if (pid != 0)
|
|
return wait_on_daemonized_start(c);
|
|
/* second fork to be reparented by init */
|
|
pid = fork();
|
|
if (pid < 0) {
|
|
SYSERROR("Error doing dual-fork");
|
|
return false;
|
|
}
|
|
if (pid != 0)
|
|
exit(0);
|
|
/* like daemon(), chdir to / and redirect 0,1,2 to /dev/null */
|
|
if (chdir("/")) {
|
|
SYSERROR("Error chdir()ing to /.");
|
|
return false;
|
|
}
|
|
close(0);
|
|
close(1);
|
|
close(2);
|
|
open("/dev/null", O_RDONLY);
|
|
open("/dev/null", O_RDWR);
|
|
open("/dev/null", O_RDWR);
|
|
setsid();
|
|
}
|
|
|
|
if (clearenv()) {
|
|
SYSERROR("failed to clear environment");
|
|
/* don't error out though */
|
|
}
|
|
|
|
if (putenv("container=lxc")) {
|
|
fprintf(stderr, "failed to set environment variable");
|
|
if (daemonize) {
|
|
lxc_container_put(c);
|
|
exit(1);
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
reboot:
|
|
conf->reboot = 0;
|
|
ret = lxc_start(c->name, argv, conf);
|
|
|
|
if (conf->reboot) {
|
|
INFO("container requested reboot");
|
|
conf->reboot = 0;
|
|
goto reboot;
|
|
}
|
|
|
|
if (daemonize) {
|
|
lxc_container_put(c);
|
|
exit (ret == 0 ? true : false);
|
|
} else {
|
|
return (ret == 0 ? true : false);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* note there MUST be an ending NULL
|
|
*/
|
|
static bool lxcapi_startl(struct lxc_container *c, int useinit, ...)
|
|
{
|
|
va_list ap;
|
|
char **inargs = NULL, **temp;
|
|
int n_inargs = 0;
|
|
bool bret = false;
|
|
|
|
/* container exists */
|
|
if (!c)
|
|
return false;
|
|
|
|
/* build array of arguments if any */
|
|
va_start(ap, useinit);
|
|
while (1) {
|
|
char *arg;
|
|
arg = va_arg(ap, char *);
|
|
if (!arg)
|
|
break;
|
|
n_inargs++;
|
|
temp = realloc(inargs, n_inargs * sizeof(*inargs));
|
|
if (!temp)
|
|
goto out;
|
|
inargs = temp;
|
|
inargs[n_inargs - 1] = strdup(arg); // not sure if it's safe not to copy
|
|
}
|
|
va_end(ap);
|
|
|
|
/* add trailing NULL */
|
|
if (n_inargs) {
|
|
n_inargs++;
|
|
temp = realloc(inargs, n_inargs * sizeof(*inargs));
|
|
if (!temp)
|
|
goto out;
|
|
inargs = temp;
|
|
inargs[n_inargs - 1] = NULL;
|
|
}
|
|
|
|
bret = lxcapi_start(c, useinit, inargs);
|
|
|
|
out:
|
|
if (inargs) {
|
|
int i;
|
|
for (i = 0; i < n_inargs; i++) {
|
|
if (inargs[i])
|
|
free(inargs[i]);
|
|
}
|
|
free(inargs);
|
|
}
|
|
|
|
return bret;
|
|
}
|
|
|
|
static bool lxcapi_stop(struct lxc_container *c)
|
|
{
|
|
int ret;
|
|
|
|
if (!c)
|
|
return false;
|
|
|
|
ret = lxc_stop(c->name);
|
|
|
|
return ret == 0;
|
|
}
|
|
|
|
static bool valid_template(char *t)
|
|
{
|
|
struct stat statbuf;
|
|
int statret;
|
|
|
|
statret = stat(t, &statbuf);
|
|
if (statret == 0)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* create the standard expected container dir
|
|
*/
|
|
static bool create_container_dir(struct lxc_container *c)
|
|
{
|
|
char *s;
|
|
int len, ret;
|
|
|
|
len = strlen(c->config_path) + strlen(c->name) + 2;
|
|
s = malloc(len);
|
|
if (!s)
|
|
return false;
|
|
ret = snprintf(s, len, "%s/%s", c->config_path, c->name);
|
|
if (ret < 0 || ret >= len) {
|
|
free(s);
|
|
return false;
|
|
}
|
|
ret = mkdir(s, 0755);
|
|
if (ret) {
|
|
if (errno == EEXIST)
|
|
ret = 0;
|
|
else
|
|
SYSERROR("failed to create container path for %s\n", c->name);
|
|
}
|
|
free(s);
|
|
return ret == 0;
|
|
}
|
|
|
|
/*
|
|
* backing stores not (yet) supported
|
|
* for ->create, argv contains the arguments to pass to the template,
|
|
* terminated by NULL. If no arguments, you can just pass NULL.
|
|
*/
|
|
static bool lxcapi_create(struct lxc_container *c, char *t, char *const argv[])
|
|
{
|
|
bool bret = false;
|
|
pid_t pid;
|
|
int ret, status;
|
|
char *tpath = NULL;
|
|
int len, nargs = 0;
|
|
char **newargv;
|
|
|
|
if (!c)
|
|
return false;
|
|
|
|
len = strlen(LXCTEMPLATEDIR) + strlen(t) + strlen("/lxc-") + 1;
|
|
tpath = malloc(len);
|
|
if (!tpath)
|
|
return false;
|
|
ret = snprintf(tpath, len, "%s/lxc-%s", LXCTEMPLATEDIR, t);
|
|
if (ret < 0 || ret >= len)
|
|
goto out;
|
|
if (!valid_template(tpath)) {
|
|
ERROR("bad template: %s\n", t);
|
|
goto out;
|
|
}
|
|
|
|
if (!create_container_dir(c))
|
|
goto out;
|
|
|
|
if (!c->save_config(c, NULL)) {
|
|
ERROR("failed to save starting configuration for %s\n", c->name);
|
|
goto out;
|
|
}
|
|
|
|
/* we're going to fork. but since we'll wait for our child, we
|
|
don't need to lxc_container_get */
|
|
|
|
if (lxclock(c->slock, 0)) {
|
|
ERROR("failed to grab global container lock for %s\n", c->name);
|
|
goto out;
|
|
}
|
|
|
|
pid = fork();
|
|
if (pid < 0) {
|
|
SYSERROR("failed to fork task for container creation template\n");
|
|
goto out_unlock;
|
|
}
|
|
|
|
if (pid == 0) { // child
|
|
char *patharg, *namearg;
|
|
int i;
|
|
|
|
close(0);
|
|
close(1);
|
|
close(2);
|
|
open("/dev/null", O_RDONLY);
|
|
open("/dev/null", O_RDWR);
|
|
open("/dev/null", O_RDWR);
|
|
|
|
/*
|
|
* create our new array, pre-pend the template name and
|
|
* base args
|
|
*/
|
|
if (argv)
|
|
for (; argv[nargs]; nargs++) ;
|
|
nargs += 3; // template, path and name args
|
|
newargv = malloc(nargs * sizeof(*newargv));
|
|
if (!newargv)
|
|
exit(1);
|
|
newargv[0] = t;
|
|
|
|
len = strlen(c->config_path) + strlen(c->name) + strlen("--path=") + 2;
|
|
patharg = malloc(len);
|
|
if (!patharg)
|
|
exit(1);
|
|
ret = snprintf(patharg, len, "--path=%s/%s", c->config_path, c->name);
|
|
if (ret < 0 || ret >= len)
|
|
exit(1);
|
|
newargv[1] = patharg;
|
|
len = strlen("--name=") + strlen(c->name) + 1;
|
|
namearg = malloc(len);
|
|
if (!namearg)
|
|
exit(1);
|
|
ret = snprintf(namearg, len, "--name=%s", c->name);
|
|
if (ret < 0 || ret >= len)
|
|
exit(1);
|
|
newargv[2] = namearg;
|
|
|
|
/* add passed-in args */
|
|
if (argv)
|
|
for (i = 3; i < nargs; i++)
|
|
newargv[i] = argv[i-3];
|
|
|
|
/* add trailing NULL */
|
|
nargs++;
|
|
newargv = realloc(newargv, nargs * sizeof(*newargv));
|
|
if (!newargv)
|
|
exit(1);
|
|
newargv[nargs - 1] = NULL;
|
|
|
|
/* execute */
|
|
ret = execv(tpath, newargv);
|
|
SYSERROR("failed to execute template %s", tpath);
|
|
exit(1);
|
|
}
|
|
|
|
again:
|
|
ret = waitpid(pid, &status, 0);
|
|
if (ret == -1) {
|
|
if (errno == -EINTR)
|
|
goto again;
|
|
SYSERROR("waitpid failed");
|
|
goto out_unlock;
|
|
}
|
|
if (ret != pid)
|
|
goto again;
|
|
if (!WIFEXITED(status)) { // did not exit normally
|
|
// we could set an error code and string inside the
|
|
// container_struct here if we like
|
|
ERROR("container creation template exited abnormally\n");
|
|
goto out_unlock;
|
|
}
|
|
|
|
if (WEXITSTATUS(status) != 0) {
|
|
ERROR("container creation template for %s exited with %d\n",
|
|
c->name, WEXITSTATUS(status));
|
|
goto out_unlock;
|
|
}
|
|
|
|
// now clear out the lxc_conf we have, reload from the created
|
|
// container
|
|
if (c->lxc_conf)
|
|
lxc_conf_free(c->lxc_conf);
|
|
c->lxc_conf = NULL;
|
|
bret = load_config_locked(c, c->configfile);
|
|
|
|
out_unlock:
|
|
lxcunlock(c->slock);
|
|
out:
|
|
if (tpath)
|
|
free(tpath);
|
|
return bret;
|
|
}
|
|
|
|
static bool lxcapi_shutdown(struct lxc_container *c, int timeout)
|
|
{
|
|
bool retv;
|
|
pid_t pid;
|
|
|
|
if (!c)
|
|
return false;
|
|
|
|
if (!timeout)
|
|
timeout = -1;
|
|
if (!c->is_running(c))
|
|
return true;
|
|
pid = c->init_pid(c);
|
|
if (pid <= 0)
|
|
return true;
|
|
kill(pid, SIGPWR);
|
|
retv = c->wait(c, "STOPPED", timeout);
|
|
if (!retv && timeout > 0) {
|
|
c->stop(c);
|
|
retv = c->wait(c, "STOPPED", 0); // 0 means don't wait
|
|
}
|
|
return retv;
|
|
}
|
|
|
|
static bool lxcapi_createl(struct lxc_container *c, char *t, ...)
|
|
{
|
|
bool bret = false;
|
|
char **args = NULL, **temp;
|
|
va_list ap;
|
|
int nargs = 0;
|
|
|
|
if (!c)
|
|
return false;
|
|
|
|
/*
|
|
* since we're going to wait for create to finish, I don't think we
|
|
* need to get a copy of the arguments.
|
|
*/
|
|
va_start(ap, t);
|
|
while (1) {
|
|
char *arg;
|
|
arg = va_arg(ap, char *);
|
|
if (!arg)
|
|
break;
|
|
nargs++;
|
|
temp = realloc(args, (nargs+1) * sizeof(*args));
|
|
if (!temp)
|
|
goto out;
|
|
args = temp;
|
|
args[nargs - 1] = arg;
|
|
}
|
|
va_end(ap);
|
|
args[nargs] = NULL;
|
|
|
|
bret = c->create(c, t, args);
|
|
|
|
out:
|
|
if (args)
|
|
free(args);
|
|
return bret;
|
|
}
|
|
|
|
static bool lxcapi_clear_config_item(struct lxc_container *c, const char *key)
|
|
{
|
|
int ret;
|
|
|
|
if (!c || !c->lxc_conf)
|
|
return false;
|
|
if (lxclock(c->privlock, 0)) {
|
|
return false;
|
|
}
|
|
ret = lxc_clear_config_item(c->lxc_conf, key);
|
|
lxcunlock(c->privlock);
|
|
return ret == 0;
|
|
}
|
|
|
|
static int lxcapi_get_config_item(struct lxc_container *c, const char *key, char *retv, int inlen)
|
|
{
|
|
int ret;
|
|
|
|
if (!c || !c->lxc_conf)
|
|
return -1;
|
|
if (lxclock(c->privlock, 0)) {
|
|
return -1;
|
|
}
|
|
ret = lxc_get_config_item(c->lxc_conf, key, retv, inlen);
|
|
lxcunlock(c->privlock);
|
|
return ret;
|
|
}
|
|
|
|
static int lxcapi_get_keys(struct lxc_container *c, const char *key, char *retv, int inlen)
|
|
{
|
|
if (!key)
|
|
return lxc_listconfigs(retv, inlen);
|
|
/*
|
|
* Support 'lxc.network.<idx>', i.e. 'lxc.network.0'
|
|
* This is an intelligent result to show which keys are valid given
|
|
* the type of nic it is
|
|
*/
|
|
if (!c || !c->lxc_conf)
|
|
return -1;
|
|
if (lxclock(c->privlock, 0))
|
|
return -1;
|
|
int ret = -1;
|
|
if (strncmp(key, "lxc.network.", 12) == 0)
|
|
ret = lxc_list_nicconfigs(c->lxc_conf, key, retv, inlen);
|
|
lxcunlock(c->privlock);
|
|
return ret;
|
|
}
|
|
|
|
|
|
/* default config file - should probably come through autoconf */
|
|
#define LXC_DEFAULT_CONFIG "/etc/lxc/default.conf"
|
|
static bool lxcapi_save_config(struct lxc_container *c, const char *alt_file)
|
|
{
|
|
if (!alt_file)
|
|
alt_file = c->configfile;
|
|
if (!alt_file)
|
|
return false; // should we write to stdout if no file is specified?
|
|
if (!c->lxc_conf)
|
|
if (!c->load_config(c, LXC_DEFAULT_CONFIG)) {
|
|
ERROR("Error loading default configuration file %s while saving %s\n", LXC_DEFAULT_CONFIG, c->name);
|
|
return false;
|
|
}
|
|
|
|
FILE *fout = fopen(alt_file, "w");
|
|
if (!fout)
|
|
return false;
|
|
if (lxclock(c->privlock, 0)) {
|
|
fclose(fout);
|
|
return false;
|
|
}
|
|
write_config(fout, c->lxc_conf);
|
|
fclose(fout);
|
|
lxcunlock(c->privlock);
|
|
return true;
|
|
}
|
|
|
|
static bool lxcapi_destroy(struct lxc_container *c)
|
|
{
|
|
pid_t pid;
|
|
int ret, status;
|
|
|
|
if (!c)
|
|
return false;
|
|
|
|
pid = fork();
|
|
if (pid < 0)
|
|
return false;
|
|
if (pid == 0) { // child
|
|
ret = execlp("lxc-destroy", "lxc-destroy", "-n", c->name, NULL);
|
|
perror("execl");
|
|
exit(1);
|
|
}
|
|
|
|
again:
|
|
ret = waitpid(pid, &status, 0);
|
|
if (ret == -1) {
|
|
if (errno == -EINTR)
|
|
goto again;
|
|
perror("waitpid");
|
|
return false;
|
|
}
|
|
if (ret != pid)
|
|
goto again;
|
|
if (!WIFEXITED(status)) { // did not exit normally
|
|
// we could set an error code and string inside the
|
|
// container_struct here if we like
|
|
return false;
|
|
}
|
|
|
|
return WEXITSTATUS(status) == 0;
|
|
}
|
|
|
|
static bool lxcapi_set_config_item(struct lxc_container *c, const char *key, const char *v)
|
|
{
|
|
int ret;
|
|
bool b = false;
|
|
struct lxc_config_t *config;
|
|
|
|
if (!c)
|
|
return false;
|
|
|
|
if (lxclock(c->privlock, 0))
|
|
return false;
|
|
|
|
if (!c->lxc_conf)
|
|
c->lxc_conf = lxc_conf_init();
|
|
if (!c->lxc_conf)
|
|
goto err;
|
|
config = lxc_getconfig(key);
|
|
if (!config)
|
|
goto err;
|
|
ret = config->cb(key, v, c->lxc_conf);
|
|
if (!ret)
|
|
b = true;
|
|
|
|
err:
|
|
lxcunlock(c->privlock);
|
|
return b;
|
|
}
|
|
|
|
static char *lxcapi_config_file_name(struct lxc_container *c)
|
|
{
|
|
if (!c || !c->configfile)
|
|
return NULL;
|
|
return strdup(c->configfile);
|
|
}
|
|
|
|
static const char *lxcapi_get_config_path(struct lxc_container *c)
|
|
{
|
|
if (!c || !c->config_path)
|
|
return NULL;
|
|
return (const char *)(c->config_path);
|
|
}
|
|
|
|
static bool lxcapi_set_config_path(struct lxc_container *c, const char *path)
|
|
{
|
|
char *p;
|
|
bool b = false;
|
|
|
|
if (!c)
|
|
return b;
|
|
|
|
if (lxclock(c->privlock, 0))
|
|
return b;
|
|
|
|
p = strdup(path);
|
|
if (!p)
|
|
goto err;
|
|
b = true;
|
|
if (c->config_path)
|
|
free(c->config_path);
|
|
c->config_path = p;
|
|
err:
|
|
lxcunlock(c->privlock);
|
|
return b;
|
|
}
|
|
|
|
|
|
static bool lxcapi_set_cgroup_item(struct lxc_container *c, const char *subsys, const char *value)
|
|
{
|
|
int ret;
|
|
bool b = false;
|
|
|
|
if (!c)
|
|
return false;
|
|
|
|
if (lxclock(c->privlock, 0))
|
|
return false;
|
|
|
|
if (is_stopped_nolock(c))
|
|
goto err;
|
|
|
|
ret = lxc_cgroup_set(c->name, subsys, value);
|
|
if (!ret)
|
|
b = true;
|
|
err:
|
|
lxcunlock(c->privlock);
|
|
return b;
|
|
}
|
|
|
|
static int lxcapi_get_cgroup_item(struct lxc_container *c, const char *subsys, char *retv, int inlen)
|
|
{
|
|
int ret = -1;
|
|
|
|
if (!c || !c->lxc_conf)
|
|
return -1;
|
|
|
|
if (lxclock(c->privlock, 0))
|
|
return -1;
|
|
|
|
if (is_stopped_nolock(c))
|
|
goto out;
|
|
|
|
ret = lxc_cgroup_get(c->name, subsys, retv, inlen);
|
|
|
|
out:
|
|
lxcunlock(c->privlock);
|
|
return ret;
|
|
}
|
|
|
|
|
|
struct lxc_container *lxc_container_new(const char *name)
|
|
{
|
|
struct lxc_container *c;
|
|
int ret, len;
|
|
|
|
c = malloc(sizeof(*c));
|
|
if (!c) {
|
|
fprintf(stderr, "failed to malloc lxc_container\n");
|
|
return NULL;
|
|
}
|
|
memset(c, 0, sizeof(*c));
|
|
|
|
c->config_path = default_lxc_path();
|
|
if (!c->config_path) {
|
|
fprintf(stderr, "Out of memory");
|
|
goto err;
|
|
}
|
|
|
|
c->name = malloc(strlen(name)+1);
|
|
if (!c->name) {
|
|
fprintf(stderr, "Error allocating lxc_container name\n");
|
|
goto err;
|
|
}
|
|
strcpy(c->name, name);
|
|
|
|
c->numthreads = 1;
|
|
c->slock = lxc_newlock(name);
|
|
if (!c->slock) {
|
|
fprintf(stderr, "failed to create lock\n");
|
|
goto err;
|
|
}
|
|
|
|
c->privlock = lxc_newlock(NULL);
|
|
if (!c->privlock) {
|
|
fprintf(stderr, "failed to alloc privlock\n");
|
|
goto err;
|
|
}
|
|
|
|
len = strlen(c->config_path)+strlen(c->name)+strlen("/config")+2;
|
|
c->configfile = malloc(len);
|
|
if (!c->configfile) {
|
|
fprintf(stderr, "Error allocating config file pathname\n");
|
|
goto err;
|
|
}
|
|
ret = snprintf(c->configfile, len, "%s/%s/config", c->config_path, c->name);
|
|
if (ret < 0 || ret >= len) {
|
|
fprintf(stderr, "Error printing out config file name\n");
|
|
goto err;
|
|
}
|
|
|
|
if (file_exists(c->configfile))
|
|
lxcapi_load_config(c, NULL);
|
|
|
|
// assign the member functions
|
|
c->is_defined = lxcapi_is_defined;
|
|
c->state = lxcapi_state;
|
|
c->is_running = lxcapi_is_running;
|
|
c->freeze = lxcapi_freeze;
|
|
c->unfreeze = lxcapi_unfreeze;
|
|
c->init_pid = lxcapi_init_pid;
|
|
c->load_config = lxcapi_load_config;
|
|
c->want_daemonize = lxcapi_want_daemonize;
|
|
c->start = lxcapi_start;
|
|
c->startl = lxcapi_startl;
|
|
c->stop = lxcapi_stop;
|
|
c->config_file_name = lxcapi_config_file_name;
|
|
c->wait = lxcapi_wait;
|
|
c->set_config_item = lxcapi_set_config_item;
|
|
c->destroy = lxcapi_destroy;
|
|
c->save_config = lxcapi_save_config;
|
|
c->get_keys = lxcapi_get_keys;
|
|
c->create = lxcapi_create;
|
|
c->createl = lxcapi_createl;
|
|
c->shutdown = lxcapi_shutdown;
|
|
c->clear_config_item = lxcapi_clear_config_item;
|
|
c->get_config_item = lxcapi_get_config_item;
|
|
c->get_cgroup_item = lxcapi_get_cgroup_item;
|
|
c->set_cgroup_item = lxcapi_set_cgroup_item;
|
|
c->get_config_path = lxcapi_get_config_path;
|
|
c->set_config_path = lxcapi_set_config_path;
|
|
|
|
/* we'll allow the caller to update these later */
|
|
if (lxc_log_init(NULL, "none", NULL, "lxc_container", 0)) {
|
|
fprintf(stderr, "failed to open log\n");
|
|
goto err;
|
|
}
|
|
|
|
return c;
|
|
|
|
err:
|
|
lxc_container_free(c);
|
|
return NULL;
|
|
}
|
|
|
|
int lxc_get_wait_states(const char **states)
|
|
{
|
|
int i;
|
|
|
|
if (states)
|
|
for (i=0; i<MAX_STATE; i++)
|
|
states[i] = lxc_state2str(i);
|
|
return MAX_STATE;
|
|
}
|