lxccontainer.c revision 1f92162dc0432b6f7f8156d22348f22934cbea3f
bb2b38cd44b032118359afbc743efbea12f48e61bnicholes * Copyright © 2012 Serge Hallyn <serge.hallyn@ubuntu.com>.
bb2b38cd44b032118359afbc743efbea12f48e61bnicholes * Copyright © 2012 Canonical Ltd.
bb2b38cd44b032118359afbc743efbea12f48e61bnicholes * This library is free software; you can redistribute it and/or
bb2b38cd44b032118359afbc743efbea12f48e61bnicholes * modify it under the terms of the GNU Lesser General Public
bb2b38cd44b032118359afbc743efbea12f48e61bnicholes * License as published by the Free Software Foundation; either
bb2b38cd44b032118359afbc743efbea12f48e61bnicholes * version 2.1 of the License, or (at your option) any later version.
0662ed52e814f8f08ef0e09956413a792584eddffuankg * This library is distributed in the hope that it will be useful,
bb2b38cd44b032118359afbc743efbea12f48e61bnicholes * but WITHOUT ANY WARRANTY; without even the implied warranty of
bb2b38cd44b032118359afbc743efbea12f48e61bnicholes * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
bb2b38cd44b032118359afbc743efbea12f48e61bnicholes * Lesser General Public License for more details.
bb2b38cd44b032118359afbc743efbea12f48e61bnicholes * You should have received a copy of the GNU Lesser General Public
bb2b38cd44b032118359afbc743efbea12f48e61bnicholes * License along with this library; if not, write to the Free Software
bb2b38cd44b032118359afbc743efbea12f48e61bnicholes * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
bb2b38cd44b032118359afbc743efbea12f48e61bnicholes#include <../include/ifaddrs.h>
bb2b38cd44b032118359afbc743efbea12f48e61bnicholes#define NOT_SUPPORTED_ERROR "the requested function %s is not currently supported with unprivileged containers"
bb2b38cd44b032118359afbc743efbea12f48e61bnicholes/* Define faccessat() if missing from the C library */
bb2b38cd44b032118359afbc743efbea12f48e61bnicholesstatic int faccessat(int __fd, const char *__file, int __type, int __flag)
bb2b38cd44b032118359afbc743efbea12f48e61bnicholesreturn syscall(__NR_faccessat, __fd, __file, __type, __flag);
bb2b38cd44b032118359afbc743efbea12f48e61bnicholesstatic bool file_exists(const char *f)
bb2b38cd44b032118359afbc743efbea12f48e61bnicholesstatic bool config_file_exists(const char *lxcpath, const char *cname)
ac7985784d08a3655291f24f711812b4d8b1cbcffuankg /* $lxcpath + '/' + $cname + '/config' + \0 */
bb2b38cd44b032118359afbc743efbea12f48e61bnicholes int ret, len = strlen(lxcpath) + strlen(cname) + 9;
bb2b38cd44b032118359afbc743efbea12f48e61bnicholes ret = snprintf(fname, len, "%s/%s/config", lxcpath, cname);
0a39e7683f6611d66c55712f50bb240428d832a1bnicholes return false;
bb2b38cd44b032118359afbc743efbea12f48e61bnicholes * A few functions to help detect when a container creation failed.
bb2b38cd44b032118359afbc743efbea12f48e61bnicholes * If a container creation was killed partway through, then trying
bb2b38cd44b032118359afbc743efbea12f48e61bnicholes * to actually start that container could harm the host. We detect
ac7985784d08a3655291f24f711812b4d8b1cbcffuankg * this by creating a 'partial' file under the container directory,
0662ed52e814f8f08ef0e09956413a792584eddffuankg * and keeping an advisory lock. When container creation completes,
bb2b38cd44b032118359afbc743efbea12f48e61bnicholes * we remove that file. When we load or try to start a container, if
ac7985784d08a3655291f24f711812b4d8b1cbcffuankg * we find that file, without a flock, we remove the container.
bb2b38cd44b032118359afbc743efbea12f48e61bnicholes int len = strlen(c->config_path) + strlen(c->name) + 10;
bb2b38cd44b032118359afbc743efbea12f48e61bnicholes ret = snprintf(path, len, "%s/%s/partial", c->config_path, c->name);
bb2b38cd44b032118359afbc743efbea12f48e61bnicholes // give benefit of the doubt
bb2b38cd44b032118359afbc743efbea12f48e61bnicholes if (fcntl(fd, F_GETLK, &lk) == 0 && lk.l_pid != -1) {
ac7985784d08a3655291f24f711812b4d8b1cbcffuankg // create is still ongoing
ac7985784d08a3655291f24f711812b4d8b1cbcffuankg // create completed but partial is still there.
bb2b38cd44b032118359afbc743efbea12f48e61bnicholes // $lxcpath + '/' + $name + '/partial' + \0
bb2b38cd44b032118359afbc743efbea12f48e61bnicholes int len = strlen(c->config_path) + strlen(c->name) + 10;
bb2b38cd44b032118359afbc743efbea12f48e61bnicholes ret = snprintf(path, len, "%s/%s/partial", c->config_path, c->name);
bb2b38cd44b032118359afbc743efbea12f48e61bnicholes if ((fd=open(path, O_RDWR | O_CREAT | O_EXCL, 0755)) < 0) {
bb2b38cd44b032118359afbc743efbea12f48e61bnicholesstatic void remove_partial(struct lxc_container *c, int fd)
bb2b38cd44b032118359afbc743efbea12f48e61bnicholes // $lxcpath + '/' + $name + '/partial' + \0
bb2b38cd44b032118359afbc743efbea12f48e61bnicholes int len = strlen(c->config_path) + strlen(c->name) + 10;
bb2b38cd44b032118359afbc743efbea12f48e61bnicholes ret = snprintf(path, len, "%s/%s/partial", c->config_path, c->name);
bb2b38cd44b032118359afbc743efbea12f48e61bnicholes * 1. container_mem_lock(c) protects the struct lxc_container from multiple threads.
bb2b38cd44b032118359afbc743efbea12f48e61bnicholes * 2. container_disk_lock(c) protects the on-disk container data - in particular the
bb2b38cd44b032118359afbc743efbea12f48e61bnicholes * container configuration file.
bb2b38cd44b032118359afbc743efbea12f48e61bnicholes * The container_disk_lock also takes the container_mem_lock.
bb2b38cd44b032118359afbc743efbea12f48e61bnicholes * 3. thread_mutex protects process data (ex: fd table) from multiple threads.
bb2b38cd44b032118359afbc743efbea12f48e61bnicholes * NOTHING mutexes two independent programs with their own struct
bb2b38cd44b032118359afbc743efbea12f48e61bnicholes * lxc_container for the same c->name, between API calls. For instance,
bb2b38cd44b032118359afbc743efbea12f48e61bnicholes * c->config_read(); c->start(); Between those calls, data on disk
bb2b38cd44b032118359afbc743efbea12f48e61bnicholes * could change (which shouldn't bother the caller unless for instance
bb2b38cd44b032118359afbc743efbea12f48e61bnicholes * the rootfs get moved). c->config_read(); update; c->config_write();
ac7985784d08a3655291f24f711812b4d8b1cbcffuankg * Two such updaters could race. The callers should therefore check their
ac7985784d08a3655291f24f711812b4d8b1cbcffuankg * results. Trying to prevent that would necessarily expose us to deadlocks
bb2b38cd44b032118359afbc743efbea12f48e61bnicholes * due to hung callers. So I prefer to keep the locks only within our own
bb2b38cd44b032118359afbc743efbea12f48e61bnicholes * functions, not across functions.
bb2b38cd44b032118359afbc743efbea12f48e61bnicholes * If you're going to clone while holding a lxccontainer, increment
bb2b38cd44b032118359afbc743efbea12f48e61bnicholes * c->numthreads (under privlock) before forking. When deleting,
ac7985784d08a3655291f24f711812b4d8b1cbcffuankg * decrement numthreads under privlock, then if it hits 0 you can delete.
ac7985784d08a3655291f24f711812b4d8b1cbcffuankg * Do not ever use a lxccontainer whose numthreads you did not bump.
bb2b38cd44b032118359afbc743efbea12f48e61bnicholesstatic void lxc_container_free(struct lxc_container *c)
free(c);
if (container_mem_lock(c))
c->numthreads++;
if (container_mem_lock(c))
bool ret = false;
int statret;
if (container_mem_lock(c))
if (!c->configfile)
goto out;
if (statret != 0)
goto out;
ret = true;
out:
return ret;
lxc_state_t s;
return NULL;
return lxc_state2str(s);
lxc_state_t s;
return (s == STOPPED);
s = lxcapi_state(c);
int ret;
if (ret)
int ret;
if (ret)
int ttyfd;
return ttyfd;
if (!c->lxc_conf)
int lret;
const char *fname;
if (alt_file)
if (!fname)
need_disklock = true;
if (need_disklock)
if (lret)
if (need_disklock)
return ret;
if (!c || !c->lxc_conf)
if (container_mem_lock(c)) {
if (!c || !c->lxc_conf)
if (container_mem_lock(c)) {
int ret;
return ret == 0;
static bool am_single_threaded(void)
int count=0;
if (!dir) {
if (!direntp)
int ret;
bool daemonize = false;
char *default_args[] = {
if (!c->lxc_conf)
c->destroy(c);
if (container_mem_lock(c))
if (useinit) {
return ret == 0 ? true : false;
if (!argv)
if (daemonize) {
if (pid < 0)
if (pid != 0) {
if (pid < 0) {
if (pid != 0)
exit(0);
close(0);
setsid();
if (!am_single_threaded()) {
if (c->pidfile) {
goto reboot;
if (c->pidfile) {
if (daemonize)
return (ret == 0 ? true : false);
bool bret = false;
if (!inargs) {
goto out;
out:
if (inargs) {
char **arg;
return bret;
int ret;
return ret == 0;
free(s);
if (ret) {
ret = 0;
free(s);
return ret == 0;
char *dest;
int ret;
return NULL;
if (!bdev) {
return NULL;
if (geteuid() != 0) {
return NULL;
return bdev;
* is something like 'sshd', then return $templatepath/lxc-sshd.
static char *get_template_path(const char *t)
char *tpath;
return tpath;
if (!tpath)
return NULL;
return NULL;
return NULL;
return tpath;
char *const argv[])
if (!tpath)
if (pid < 0) {
char **newargv;
if (quiet) {
close(0);
if (!bdev) {
if (geteuid() == 0) {
if (detect_shared_rootfs()) {
if (geteuid() != 0) {
if (argv)
if (!newargv)
if (!patharg)
if (!namearg)
if (!rootfsarg)
if (argv)
nargs++;
if (!newargv)
if (!n2) {
if (!n2)
if (!n2)
if (hostid_mapped < 0) {
if (hostid_mapped < 0) {
if (!n2)
if (hostgid_mapped < 0) {
if (hostgid_mapped < 0) {
for (i = 0; i < nargs; i++)
if (!n2) {
long flen;
char *contents;
FILE *f;
#if HAVE_LIBGNUTLS
char *tpath;
if (f == NULL)
goto out_error;
goto out_error;
goto out_error;
goto out_error;
goto out_free_contents;
f = NULL;
if (ret < 0)
goto out_free_contents;
#if HAVE_LIBGNUTLS
if (!tpath) {
goto out_free_contents;
if (ret < 0) {
goto out_free_contents;
if (f == NULL) {
if (argv) {
while (*argv) {
argv++;
#if HAVE_LIBGNUTLS
for (i=0; i<SHA_DIGEST_LENGTH; i++)
fclose(f);
ret = 0;
int newret;
if (ret == 0)
if (ret < 0) {
if (c && c->lxc_conf) {
char *const argv[])
bool ret = false;
int partial_fd;
if (!tpath) {
goto out;
goto free_tpath;
if (!c->lxc_conf) {
ERROR("Error loading default configuration file %s", lxc_global_config_value("lxc.default_config"));
goto free_tpath;
if (!create_container_dir(c))
goto free_tpath;
goto out;
goto out;
ret = true;
goto out;
goto out;
if (pid < 0) {
goto out_unlock;
exit(0);
goto out_unlock;
goto out_unlock;
goto out_unlock;
goto out_unlock;
if (partial_fd >= 0)
out:
if (!ret && c)
lxcapi_destroy(c);
if (tpath)
return ret;
if (!c->is_running(c))
if (pid <= 0)
bool retv;
if (!c->is_running(c))
if (pid <= 0)
return retv;
bool bret = false;
if (!args) {
goto out;
out:
return bret;
int ret;
if (!c || !c->lxc_conf)
if (container_mem_lock(c))
return ret == 0;
if (!c->is_running(c))
goto out;
goto out;
if (userns < 0) {
goto out;
goto out;
goto out;
if (netns < 0) {
goto out;
goto out;
out:
if (!newnames) {
if (!newlist) {
if (sort)
qsort(newlist, pos + 1, sizeof(struct lxc_container *), (int (*)(const void *,const void *))container_cmp);
return (char **)bsearch(&cname, *names, size, sizeof(char *), (int (*)(const void *, const void *))string_cmp);
return NULL;
if (pid < 0) {
return NULL;
if (!enter_to_ns(c)) {
goto out;
goto out;
if (nbytes < 0) {
goto out;
count++;
ret = 0;
out:
if (interfaceArray)
count++;
for(i=0;i<count;i++)
if(interfaces)
return interfaces;
static char** lxcapi_get_ips(struct lxc_container *c, const char* interface, const char* family, int scope)
return NULL;
if (pid < 0) {
return NULL;
if (!enter_to_ns(c)) {
goto out;
goto out;
sizeof(addressOutputBuffer));
if (!address)
if (nbytes < 0) {
goto out;
count++;
ret = 0;
out:
if(interfaceArray)
count++;
for(i=0;i<count;i++)
if(addresses)
return addresses;
int ret;
if (!c || !c->lxc_conf)
if (container_mem_lock(c))
return ret;
char *ret;
if (!c || !c->lxc_conf)
return NULL;
if (container_mem_lock(c))
return NULL;
return ret;
if (!key)
if (!c || !c->lxc_conf)
if (container_mem_lock(c))
return ret;
int lret;
if (!alt_file)
if (!alt_file)
if (!c->lxc_conf) {
ERROR("Error loading default configuration file %s while saving %s", lxc_global_config_value("lxc.default_config"), c->name);
if (!create_container_dir(c))
need_disklock = true;
if (need_disklock)
if (lret)
if (!fout)
goto out;
ret = true;
out:
if (need_disklock)
return ret;
int ret, v = 0;
FILE *f;
bool bret = false;
if (container_disk_lock(c))
c->name);
goto out;
fclose(f);
goto out;
goto out;
fclose(f);
goto out;
if (ret != 0) {
goto out;
bret = true;
out:
return bret;
static void strip_newline(char *p)
struct lxc_container *p;
FILE *f;
int ret;
if (f == NULL)
goto out;
out:
fclose(f);
int ret, v;
FILE *f;
bool bret = false;
c->name);
goto out;
goto out;
fclose(f);
goto out;
bret = v != 0;
out:
return bret;
bool bret = false;
int ret;
if (!c || !lxcapi_is_defined(c))
if (container_disk_lock(c))
if (!is_stopped(c)) {
goto out;
goto out;
bdev_put(r);
goto out;
bdev_put(r);
mod_all_rdeps(c, false);
if (am_unpriv())
if (ret < 0) {
goto out;
bret = true;
out:
return bret;
if (!c->lxc_conf)
if (!c->lxc_conf)
if (!config)
if (container_mem_lock(c))
if (!c || !c->configfile)
return NULL;
if (!c || !c->config_path)
return NULL;
return (const char *)(c->config_path);
char *newpath;
if (!c->config_path)
if (!newpath)
if (c->configfile)
if (container_mem_lock(c))
goto err;
if (c->config_path)
c->config_path = p;
if (!set_config_filename(c)) {
err:
if (oldpath)
int ret;
if (is_stopped(c))
if (container_disk_lock(c))
return ret == 0;
static int lxcapi_get_cgroup_item(struct lxc_container *c, const char *subsys, char *retv, int inlen)
int ret;
if (is_stopped(c))
if (container_disk_lock(c))
return ret;
const char *lxc_get_version(void)
return LXC_VERSION;
if (ret < 0) {
if (in < 0) {
if (out < 0) {
if (len < 0) {
goto err;
if (len == 0)
goto err;
if (ret) {
err:
char *cpath;
for (i=0; i<NUM_LXC_HOOKS; i++) {
if (ret < 0)
FILE *f;
unsigned int seed;
fclose(f);
if (n->hwaddr)
int ret;
if (!oldpath)
int ret;
c->name);
int ret;
FILE *f;
bool bret;
c->name);
bret = true;
bret = false;
if (fclose(f) != 0)
bret = false;
return bret;
int need_rdep;
if (!bdev) {
if (need_rdep) {
mod_all_rdeps(c, true);
struct clone_update_data {
int flags;
char **hookargs;
if (setgid(0) < 0) {
if (setuid(0) < 0) {
if (!bdev)
only rootfs gets converted (copied/snapshotted) on clone.
int ret;
return ret;
char **hookargs)
if (!c || !c->is_defined(c))
return NULL;
if (container_mem_lock(c))
return NULL;
if (!is_stopped(c)) {
goto out;
goto out;
goto out;
goto out;
if (!fout) {
goto out;
goto out;
if (am_unpriv()) {
goto out;
if (!c2) {
goto out;
goto out;
if (ret < 0) {
goto out;
goto out;
if (ret < 0)
goto out;
goto out;
goto out;
if (pid > 0) {
if (ret)
goto out;
return c2;
if (am_unpriv())
&data);
if (ret < 0)
exit(0);
out:
if (c2) {
if (!storage_copied)
return NULL;
if (!bdev) {
if (!newc) {
if (!lxcapi_destroy(c)) {
static int lxcapi_attach(struct lxc_container *c, lxc_attach_exec_t exec_function, void *exec_payload, lxc_attach_options_t *options, pid_t *attached_process)
static int lxcapi_attach_run_wait(struct lxc_container *c, lxc_attach_options_t *options, const char *program, const char * const argv[])
char *fname;
int i = 0, ret;
if (ret != 0)
if (!c2) {
FILE *f;
fclose(f);
if (ret != 0) {
if (commentfile) {
if (s->name)
if (s->comment_pathname)
if (s->timestamp)
if (s->lxcpath)
free(s);
s = NULL;
return NULL;
if (!fin)
return NULL;
if (len > 0) {
free(s);
s = NULL;
if (!c || !lxcapi_is_defined(c))
if (!dir) {
if (!direntp)
goto out_free;
if (!nsnaps) {
goto out_free;
goto out_free;
goto out_free;
count++;
return count;
if (snaps) {
for (i=0; i<count; i++)
static bool lxcapi_snapshot_restore(struct lxc_container *c, const char *snapname, const char *newname)
int ret;
if (!bdev) {
if (!newname)
if (!lxcapi_destroy(c)) {
if (rest)
int ret;
goto err;
goto err;
goto err;
err:
if (snap)
int ret;
if (pid) {
if (!add)
exit(0);
exit(0);
static bool add_remove_device_node(struct lxc_container *c, const char *src_path, const char *dest_path, bool add)
int ret;
if (!c->is_running(c)) {
if (add) {
static bool lxcapi_add_device_node(struct lxc_container *c, const char *src_path, const char *dest_path)
if (am_unpriv()) {
static bool lxcapi_remove_device_node(struct lxc_container *c, const char *src_path, const char *dest_path)
if (am_unpriv()) {
static int lxcapi_attach_run_waitl(struct lxc_container *c, lxc_attach_options_t *options, const char *program, const char *arg, ...)
const char **argv;
int ret;
if (!argv) {
return ret;
struct lxc_container *c;
c = malloc(sizeof(*c));
return NULL;
memset(c, 0, sizeof(*c));
if (configpath)
if (!c->config_path) {
goto err;
if (!c->name) {
goto err;
goto err;
goto err;
if (!set_config_filename(c)) {
goto err;
goto err;
lxcapi_destroy(c);
c->daemonize = true;
goto err;
err:
return NULL;
if (states)
for (i=0; i<MAX_STATE; i++)
return MAX_STATE;
struct lxc_container *c;
if (!lxcpath)
if (!dir) {
if (cret)
if (names)
if (!direntp)
if (names) {
goto free_bad;
cfound++;
if (!cret) {
nfound++;
if (names)
goto free_bad;
if (!lxcapi_is_defined(c)) {
if (names)
goto free_bad;
goto free_bad;
nfound++;
return nfound;
for (i=0; i<cfound; i++)
for (i=0; i<nfound; i++)
int lxcpath_len;
struct lxc_container *c;
if (!lxcpath)
if (cret)
if (nret)
p += lxcpath_len;
goto free_cret_list;
ct_name_cnt++;
if (!cret)
lxcpath, p);
goto free_cret_list;
cret_cnt++;
if (nret)
goto free_ct_name;
goto out;
for (i = 0; i < cret_cnt; i++)
if (ct_name) {
for (i = 0; i < ct_name_cnt; i++)
out:
if (line)
fclose(f);
return ret;
char **active_name;
char **ct_name;
if (ct_cnt < 0)
return ct_cnt;
if (active_cnt < 0) {
goto free_ct_name;
for (i = 0; i < active_cnt; i++) {
goto free_active_name;
ct_cnt++;
active_cnt = 0;
struct lxc_container *c;
goto free_ct_list;
ct_list_cnt++;
if (cret)
if (nret)
goto free_ct_name;
return ct_cnt;
for (i = 0; i < ct_list_cnt; i++) {
if (ct_list)
for (i = 0; i < active_cnt; i++) {
if (active_name[i])
if (active_name)
for (i = 0; i < ct_cnt; i++) {
return ret;