#include <libgen.h>
#include <stdint.h>
#include <grp.h>
+#include <stdio.h>
#include <sys/syscall.h>
#include <lxc/lxccontainer.h>
#include "console.h"
#include "cgroup.h"
#include "commands.h"
+#include "criu.h"
#include "log.h"
#include "bdev.h"
#include "utils.h"
#include <../include/ifaddrs.h>
#endif
+#if IS_BIONIC
+#include <../include/lxcmntent.h>
+#else
+#include <mntent.h>
+#endif
+
#define MAX_BUFFER 4096
#define NOT_SUPPORTED_ERROR "the requested function %s is not currently supported with unprivileged containers"
}
#endif
-
lxc_log_define(lxc_container, lxc);
static bool do_lxcapi_destroy(struct lxc_container *c);
static rettype fnname(struct lxc_container *c) \
{ \
rettype ret; \
- struct lxc_conf *old = current_config; \
current_config = c ? c->lxc_conf : NULL; \
ret = do_##fnname(c); \
- current_config = old; \
+ current_config = NULL; \
return ret; \
}
static rettype fnname(struct lxc_container *c, t1 a1) \
{ \
rettype ret; \
- struct lxc_conf *old = current_config; \
current_config = c ? c->lxc_conf : NULL; \
ret = do_##fnname(c, a1); \
- current_config = old; \
+ current_config = NULL; \
return ret; \
}
static rettype fnname(struct lxc_container *c, t1 a1, t2 a2) \
{ \
rettype ret; \
- struct lxc_conf *old = current_config; \
current_config = c ? c->lxc_conf : NULL; \
ret = do_##fnname(c, a1, a2); \
- current_config = old; \
+ current_config = NULL; \
return ret; \
}
static rettype fnname(struct lxc_container *c, t1 a1, t2 a2, t3 a3) \
{ \
rettype ret; \
- struct lxc_conf *old = current_config; \
current_config = c ? c->lxc_conf : NULL; \
ret = do_##fnname(c, a1, a2, a3); \
- current_config = old; \
+ current_config = NULL; \
return ret; \
}
int stdoutfd, int stderrfd, int escape)
{
int ret;
- struct lxc_conf *old = current_config;
- current_config = c ? c->lxc_conf : NULL;
+
+ if (!c)
+ return -1;
+
+ current_config = c->lxc_conf;
ret = lxc_console(c, ttynum, stdinfd, stdoutfd, stderrfd, escape);
- current_config = old;
+ current_config = NULL;
return ret;
}
container_mem_unlock(c);
if (useinit) {
- ret = lxc_execute(c->name, argv, 1, conf, c->config_path);
+ ret = lxc_execute(c->name, argv, 1, conf, c->config_path, daemonize);
return ret == 0 ? true : false;
}
pid = fork();
if (pid < 0) {
SYSERROR("Error doing dual-fork");
- return false;
+ exit(1);
}
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;
+ exit(1);
}
lxc_check_inherited(conf, true, -1);
- close(0);
- close(1);
- close(2);
- open("/dev/zero", O_RDONLY);
- open("/dev/null", O_RDWR);
- open("/dev/null", O_RDWR);
+ if (null_stdfds() < 0) {
+ ERROR("failed to close fds");
+ exit(1);
+ }
setsid();
} else {
if (!am_single_threaded()) {
if (pid_fp == NULL) {
SYSERROR("Failed to create pidfile '%s' for '%s'",
c->pidfile, c->name);
+ if (daemonize)
+ exit(1);
return false;
}
SYSERROR("Failed to write '%s'", c->pidfile);
fclose(pid_fp);
pid_fp = NULL;
+ if (daemonize)
+ exit(1);
return false;
}
pid_fp = NULL;
}
-reboot:
conf->reboot = 0;
+reboot:
if (lxc_check_inherited(conf, daemonize, -1)) {
ERROR("Inherited fds found");
ret = 1;
goto out;
}
- ret = lxc_start(c->name, argv, conf, c->config_path);
+ ret = lxc_start(c->name, argv, conf, c->config_path, daemonize);
c->error_num = ret;
- if (conf->reboot) {
+ if (conf->reboot == 1) {
INFO("container requested reboot");
- conf->reboot = 0;
+ conf->reboot = 2;
goto reboot;
}
static bool lxcapi_start(struct lxc_container *c, int useinit, char * const argv[])
{
bool ret;
- struct lxc_conf *old = current_config;
current_config = c ? c->lxc_conf : NULL;
ret = do_lxcapi_start(c, useinit, argv);
- current_config = old;
+ current_config = NULL;
return ret;
}
if (!c)
return false;
- struct lxc_conf *old = current_config;
current_config = c->lxc_conf;
va_start(ap, useinit);
free(inargs);
}
- current_config = old;
+ current_config = NULL;
return bret;
}
return p;
}
-static bool create_run_template(struct lxc_container *c, char *tpath, bool quiet,
+static bool create_run_template(struct lxc_container *c, char *tpath, bool need_null_stdfds,
char *const argv[])
{
pid_t pid;
char **newargv;
struct lxc_conf *conf = c->lxc_conf;
- if (quiet) {
- close(0);
- close(1);
- close(2);
- open("/dev/zero", O_RDONLY);
- open("/dev/null", O_RDWR);
- open("/dev/null", O_RDWR);
+ if (need_null_stdfds && null_stdfds() < 0) {
+ exit(1);
}
src = c->lxc_conf->rootfs.path;
char *const argv[])
{
bool ret;
- struct lxc_conf *old = current_config;
current_config = c ? c->lxc_conf : NULL;
ret = do_lxcapi_create(c, t, bdevtype, specs, flags, argv);
- current_config = old;
+ current_config = NULL;
return ret;
}
if (!c)
return false;
- struct lxc_conf *old = current_config;
- current_config = c ? c->lxc_conf : NULL;
+ current_config = c->lxc_conf;
/*
* since we're going to wait for create to finish, I don't think we
out:
free(args);
- current_config = old;
+ current_config = NULL;
return bret;
}
{
bool bret = false;
int ret;
+ struct lxc_conf *conf;
if (!c || !do_lxcapi_is_defined(c))
return false;
+ conf = c->lxc_conf;
if (container_disk_lock(c))
return false;
goto out;
}
- if (c->lxc_conf && c->lxc_conf->rootfs.path && c->lxc_conf->rootfs.mount) {
+ if (conf && !lxc_list_empty(&conf->hooks[LXCHOOK_DESTROY])) {
+ /* Start of environment variable setup for hooks */
+ if (setenv("LXC_NAME", c->name, 1)) {
+ SYSERROR("failed to set environment variable for container name");
+ }
+ if (setenv("LXC_CONFIG_FILE", conf->rcfile, 1)) {
+ SYSERROR("failed to set environment variable for config path");
+ }
+ if (setenv("LXC_ROOTFS_MOUNT", conf->rootfs.mount, 1)) {
+ SYSERROR("failed to set environment variable for rootfs mount");
+ }
+ if (setenv("LXC_ROOTFS_PATH", conf->rootfs.path, 1)) {
+ SYSERROR("failed to set environment variable for rootfs mount");
+ }
+ if (conf->console.path && setenv("LXC_CONSOLE", conf->console.path, 1)) {
+ SYSERROR("failed to set environment variable for console path");
+ }
+ if (conf->console.log_path && setenv("LXC_CONSOLE_LOGPATH", conf->console.log_path, 1)) {
+ SYSERROR("failed to set environment variable for console log");
+ }
+ /* End of environment variable setup for hooks */
+
+ if (run_lxc_hooks(c->name, "destroy", conf, c->get_config_path(c), NULL)) {
+ ERROR("Error executing clone hook for %s", c->name);
+ goto out;
+ }
+ }
+
+ if (current_config && conf == current_config) {
+ current_config = NULL;
+ if (conf->logfd != -1) {
+ close(conf->logfd);
+ conf->logfd = -1;
+ }
+ }
+
+ if (conf && conf->rootfs.path && conf->rootfs.mount) {
if (am_unpriv())
- ret = userns_exec_1(c->lxc_conf, bdev_destroy_wrapper, c->lxc_conf);
+ ret = userns_exec_1(conf, bdev_destroy_wrapper, conf);
else
- ret = do_bdev_destroy(c->lxc_conf);
+ ret = do_bdev_destroy(conf);
if (ret < 0) {
ERROR("Error destroying rootfs for %s", c->name);
goto out;
char *path = alloca(strlen(p1) + strlen(c->name) + 2);
sprintf(path, "%s/%s", p1, c->name);
if (am_unpriv())
- ret = userns_exec_1(c->lxc_conf, lxc_rmdir_onedev_wrapper, path);
+ ret = userns_exec_1(conf, lxc_rmdir_onedev_wrapper, path);
else
ret = lxc_rmdir_onedev(path, "snaps");
if (ret < 0) {
char **hookargs)
{
struct lxc_container * ret;
- struct lxc_conf *old = current_config;
current_config = c ? c->lxc_conf : NULL;
ret = do_lxcapi_clone(c, newname, lxcpath, flags, bdevtype, bdevdata, newsize, hookargs);
- current_config = old;
+ current_config = NULL;
return ret;
}
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)
{
- struct lxc_conf *old = current_config;
int ret;
if (!c)
current_config = c->lxc_conf;
ret = lxc_attach(c->name, c->config_path, exec_function, exec_payload, options, attached_process);
- current_config = old;
+ current_config = NULL;
return ret;
}
static int lxcapi_attach_run_wait(struct lxc_container *c, lxc_attach_options_t *options, const char *program, const char * const argv[])
{
int ret;
- struct lxc_conf *old = current_config;
current_config = c ? c->lxc_conf : NULL;
ret = do_lxcapi_attach_run_wait(c, options, program, argv);
- current_config = old;
+ current_config = NULL;
return ret;
}
WRAP_API_2(bool, lxcapi_detach_interface, const char *, const char *)
-struct criu_opts {
- /* The type of criu invocation, one of "dump" or "restore" */
- char *action;
-
- /* The directory to pass to criu */
- char *directory;
-
- /* The container to dump */
- struct lxc_container *c;
-
- /* Enable criu verbose mode? */
- bool verbose;
-
- /* dump: stop the container or not after dumping? */
- bool stop;
-
- /* restore: the file to write the init process' pid into */
- char *pidfile;
- const char *cgroup_path;
-};
-
-static void exec_criu(struct criu_opts *opts)
-{
- char **argv, log[PATH_MAX], buf[257];
- int static_args = 14, argc = 0, i, ret;
- int netnr = 0;
- struct lxc_list *it;
-
- /* The command line always looks like:
- * criu $(action) --tcp-established --file-locks --link-remap --force-irmap \
- * --manage-cgroups action-script foo.sh -D $(directory) \
- * -o $(directory)/$(action).log
- * +1 for final NULL */
-
- if (strcmp(opts->action, "dump") == 0) {
- /* -t pid */
- static_args += 2;
-
- /* --leave-running */
- if (!opts->stop)
- static_args++;
- } else if (strcmp(opts->action, "restore") == 0) {
- /* --root $(lxc_mount_point) --restore-detached
- * --restore-sibling --pidfile $foo --cgroup-root $foo */
- static_args += 8;
- } else {
- return;
- }
-
- if (opts->verbose)
- static_args++;
-
- ret = snprintf(log, PATH_MAX, "%s/%s.log", opts->directory, opts->action);
- if (ret < 0 || ret >= PATH_MAX) {
- ERROR("logfile name too long\n");
- return;
- }
-
- argv = malloc(static_args * sizeof(*argv));
- if (!argv)
- return;
-
- memset(argv, 0, static_args * sizeof(*argv));
-
-#define DECLARE_ARG(arg) \
- do { \
- if (arg == NULL) { \
- ERROR("Got NULL argument for criu"); \
- goto err; \
- } \
- argv[argc++] = strdup(arg); \
- if (!argv[argc-1]) \
- goto err; \
- } while (0)
-
- argv[argc++] = on_path("criu", NULL);
- if (!argv[argc-1]) {
- ERROR("Couldn't find criu binary\n");
- goto err;
- }
-
- DECLARE_ARG(opts->action);
- DECLARE_ARG("--tcp-established");
- DECLARE_ARG("--file-locks");
- DECLARE_ARG("--link-remap");
- DECLARE_ARG("--force-irmap");
- DECLARE_ARG("--manage-cgroups");
- DECLARE_ARG("--action-script");
- DECLARE_ARG(DATADIR "/lxc/lxc-restore-net");
- DECLARE_ARG("-D");
- DECLARE_ARG(opts->directory);
- DECLARE_ARG("-o");
- DECLARE_ARG(log);
-
- if (opts->verbose)
- DECLARE_ARG("-vvvvvv");
-
- if (strcmp(opts->action, "dump") == 0) {
- char pid[32];
-
- if (sprintf(pid, "%d", lxcapi_init_pid(opts->c)) < 0)
- goto err;
-
- DECLARE_ARG("-t");
- DECLARE_ARG(pid);
- if (!opts->stop)
- DECLARE_ARG("--leave-running");
- } else if (strcmp(opts->action, "restore") == 0) {
- DECLARE_ARG("--root");
- DECLARE_ARG(opts->c->lxc_conf->rootfs.mount);
- DECLARE_ARG("--restore-detached");
- DECLARE_ARG("--restore-sibling");
- DECLARE_ARG("--pidfile");
- DECLARE_ARG(opts->pidfile);
- DECLARE_ARG("--cgroup-root");
- DECLARE_ARG(opts->cgroup_path);
-
- lxc_list_for_each(it, &opts->c->lxc_conf->network) {
- char eth[128], *veth;
- void *m;
- struct lxc_netdev *n = it->elem;
-
- if (n->name) {
- if (strlen(n->name) >= sizeof(eth))
- goto err;
- strncpy(eth, n->name, sizeof(eth));
- } else
- sprintf(eth, "eth%d", netnr);
-
- veth = n->priv.veth_attr.pair;
-
- ret = snprintf(buf, sizeof(buf), "%s=%s", eth, veth);
- if (ret < 0 || ret >= sizeof(buf))
- goto err;
-
- /* final NULL and --veth-pair eth0=vethASDF */
- m = realloc(argv, (argc + 1 + 2) * sizeof(*argv));
- if (!m)
- goto err;
- argv = m;
-
- DECLARE_ARG("--veth-pair");
- DECLARE_ARG(buf);
- argv[argc] = NULL;
-
- }
- }
-
- netnr = 0;
- lxc_list_for_each(it, &opts->c->lxc_conf->network) {
- struct lxc_netdev *n = it->elem;
- char veth[128];
-
- /*
- * Here, we set some parameters that lxc-restore-net
- * will examine to figure out the right network to
- * restore.
- */
- snprintf(buf, sizeof(buf), "LXC_CRIU_BRIDGE%d", netnr);
- if (setenv(buf, n->link, 1))
- goto err;
-
- if (strcmp("restore", opts->action) == 0)
- strncpy(veth, n->priv.veth_attr.pair, sizeof(veth));
- else {
- char *tmp;
- ret = snprintf(buf, sizeof(buf), "lxc.network.%d.veth.pair", netnr);
- if (ret < 0 || ret >= sizeof(buf))
- goto err;
- tmp = lxcapi_get_running_config_item(opts->c, buf);
- strncpy(veth, tmp, sizeof(veth));
- free(tmp);
- }
-
- snprintf(buf, sizeof(buf), "LXC_CRIU_VETH%d", netnr);
- if (setenv(buf, veth, 1))
- goto err;
-
- netnr++;
- }
-
-#undef DECLARE_ARG
- execv(argv[0], argv);
-err:
- for (i = 0; argv[i]; i++)
- free(argv[i]);
- free(argv);
-}
-
-/* Check and make sure the container has a configuration that we know CRIU can
- * dump. */
-static bool criu_ok(struct lxc_container *c)
-{
- struct lxc_list *it;
- bool found_deny_rule = false;
-
- if (geteuid()) {
- ERROR("Must be root to checkpoint\n");
- return false;
- }
-
- /* We only know how to restore containers with veth networks. */
- lxc_list_for_each(it, &c->lxc_conf->network) {
- struct lxc_netdev *n = it->elem;
- if (n->type != LXC_NET_VETH && n->type != LXC_NET_NONE) {
- ERROR("Found network that is not VETH or NONE\n");
- return false;
- }
- }
-
- // These requirements come from http://criu.org/LXC
- if (c->lxc_conf->console.path &&
- strcmp(c->lxc_conf->console.path, "none") != 0) {
- ERROR("lxc.console must be none\n");
- return false;
- }
-
- if (c->lxc_conf->tty != 0) {
- ERROR("lxc.tty must be 0\n");
- return false;
- }
-
- lxc_list_for_each(it, &c->lxc_conf->cgroup) {
- struct lxc_cgroup *cg = it->elem;
- if (strcmp(cg->subsystem, "devices.deny") == 0 &&
- strcmp(cg->value, "c 5:1 rwm") == 0) {
-
- found_deny_rule = true;
- break;
- }
- }
-
- if (!found_deny_rule) {
- ERROR("couldn't find devices.deny = c 5:1 rwm");
- return false;
- }
-
- return true;
-}
-
-static bool dump_net_info(struct lxc_container *c, char *directory)
-{
- int netnr;
- struct lxc_list *it;
-
- netnr = 0;
- lxc_list_for_each(it, &c->lxc_conf->network) {
- char *veth = NULL, *bridge = NULL, veth_path[PATH_MAX], eth[128];
- struct lxc_netdev *n = it->elem;
- bool has_error = true;
- int pret;
-
- pret = snprintf(veth_path, PATH_MAX, "lxc.network.%d.veth.pair", netnr);
- if (pret < 0 || pret >= PATH_MAX)
- goto out;
-
- veth = lxcapi_get_running_config_item(c, veth_path);
- if (!veth) {
- /* criu_ok() checks that all interfaces are
- * LXC_NET{VETH,NONE}, and VETHs should have this
- * config */
- assert(n->type == LXC_NET_NONE);
- break;
- }
-
- bridge = lxcapi_get_running_config_item(c, veth_path);
- if (!bridge)
- goto out;
-
- pret = snprintf(veth_path, PATH_MAX, "%s/veth%d", directory, netnr);
- if (pret < 0 || pret >= PATH_MAX || print_to_file(veth_path, veth) < 0)
- goto out;
-
- if (n->name) {
- if (strlen(n->name) >= 128)
- goto out;
- strncpy(eth, n->name, 128);
- } else
- sprintf(eth, "eth%d", netnr);
-
- has_error = false;
-out:
- free(veth);
- free(bridge);
- if (has_error)
- return false;
- }
-
- return true;
-}
-
static bool do_lxcapi_checkpoint(struct lxc_container *c, char *directory, bool stop, bool verbose)
{
pid_t pid;
int status;
+ char path[PATH_MAX];
if (!criu_ok(c))
return false;
if (mkdir(directory, 0700) < 0 && errno != EEXIST)
return false;
+ status = snprintf(path, sizeof(path), "%s/inventory.img", directory);
+ if (status < 0 || status >= sizeof(path))
+ return false;
+
+ if (access(path, F_OK) == 0) {
+ ERROR("please use a fresh directory for the dump directory\n");
+ return false;
+ }
+
if (!dump_net_info(c, directory))
return false;
} else {
pid_t w = waitpid(pid, &status, 0);
if (w == -1) {
- perror("waitpid");
+ SYSERROR("waitpid");
return false;
}
WRAP_API_3(bool, lxcapi_checkpoint, char *, bool, bool)
-static bool restore_net_info(struct lxc_container *c)
-{
- struct lxc_list *it;
- bool has_error = true;
-
- if (container_mem_lock(c))
- return false;
-
- lxc_list_for_each(it, &c->lxc_conf->network) {
- struct lxc_netdev *netdev = it->elem;
- char template[IFNAMSIZ];
- snprintf(template, sizeof(template), "vethXXXXXX");
-
- if (!netdev->priv.veth_attr.pair)
- netdev->priv.veth_attr.pair = lxc_mkifname(template);
-
- if (!netdev->priv.veth_attr.pair)
- goto out_unlock;
- }
-
- has_error = false;
-
-out_unlock:
- container_mem_unlock(c);
- return !has_error;
-}
-
static bool do_lxcapi_restore(struct lxc_container *c, char *directory, bool verbose)
{
pid_t pid;
- struct lxc_rootfs *rootfs;
- char pidfile[L_tmpnam];
- struct lxc_handler *handler;
- bool has_error = true;
+ int status, nread;
+ int pipefd[2];
if (!criu_ok(c))
return false;
return false;
}
- if (!tmpnam(pidfile))
- return false;
-
- handler = lxc_init(c->name, c->lxc_conf, c->config_path);
- if (!handler)
+ if (pipe(pipefd)) {
+ ERROR("failed to create pipe");
return false;
-
- if (!cgroup_init(handler)) {
- ERROR("failed initing cgroups");
- goto out_fini_handler;
- }
-
- if (!cgroup_create(handler)) {
- ERROR("failed creating groups");
- goto out_fini_handler;
- }
-
- if (!restore_net_info(c)) {
- ERROR("failed restoring network info");
- goto out_fini_handler;
}
pid = fork();
- if (pid < 0)
- goto out_fini_handler;
+ if (pid < 0) {
+ close(pipefd[0]);
+ close(pipefd[1]);
+ return false;
+ }
if (pid == 0) {
- struct criu_opts os;
-
- if (unshare(CLONE_NEWNS))
- exit(1);
-
- /* CRIU needs the lxc root bind mounted so that it is the root of some
- * mount. */
- rootfs = &c->lxc_conf->rootfs;
-
- if (rootfs_is_blockdev(c->lxc_conf)) {
- if (do_rootfs_setup(c->lxc_conf, c->name, c->config_path) < 0)
- exit(1);
- }
- else {
- if (mkdir(rootfs->mount, 0755) < 0 && errno != EEXIST)
- exit(1);
-
- if (mount(rootfs->path, rootfs->mount, NULL, MS_BIND, NULL) < 0) {
- rmdir(rootfs->mount);
- exit(1);
- }
- }
-
- os.action = "restore";
- os.directory = directory;
- os.c = c;
- os.pidfile = pidfile;
- os.verbose = verbose;
- os.cgroup_path = cgroup_canonical_path(handler);
-
- /* exec_criu() returning is an error */
- exec_criu(&os);
- umount(rootfs->mount);
- rmdir(rootfs->mount);
- exit(1);
- } else {
- int status;
-
- pid_t w = waitpid(pid, &status, 0);
-
- if (w == -1) {
- perror("waitpid");
- goto out_fini_handler;
- }
-
- if (WIFEXITED(status)) {
- if (WEXITSTATUS(status)) {
- goto out_fini_handler;
- }
- else {
- int ret;
- FILE *f = fopen(pidfile, "r");
- if (!f) {
- perror("reading pidfile");
- ERROR("couldn't read restore's init pidfile %s\n", pidfile);
- goto out_fini_handler;
- }
-
- ret = fscanf(f, "%d", (int*) &handler->pid);
- fclose(f);
- if (ret != 1) {
- ERROR("reading restore pid failed");
- goto out_fini_handler;
- }
+ close(pipefd[0]);
+ // this never returns
+ do_restore(c, pipefd[1], directory, verbose);
+ }
- if (lxc_set_state(c->name, handler, RUNNING))
- goto out_fini_handler;
- }
- } else {
- ERROR("CRIU was killed with signal %d\n", WTERMSIG(status));
- goto out_fini_handler;
- }
+ close(pipefd[1]);
- if (lxc_poll(c->name, handler)) {
- lxc_abort(c->name, handler);
- goto out_fini_handler;
- }
+ nread = read(pipefd[0], &status, sizeof(status));
+ close(pipefd[0]);
+ if (sizeof(status) != nread) {
+ ERROR("reading status from pipe failed");
+ goto err_wait;
}
- has_error = false;
+ // If the criu process was killed or exited nonzero, wait() for the
+ // handler, since the restore process died. Otherwise, we don't need to
+ // wait, since the child becomes the monitor process.
+ if (!WIFEXITED(status) || WEXITSTATUS(status))
+ goto err_wait;
+ return true;
-out_fini_handler:
- lxc_fini(c->name, handler);
- return !has_error;
+err_wait:
+ if (wait_for_pid(pid))
+ ERROR("restore process died");
+ return false;
}
WRAP_API_2(bool, lxcapi_restore, char *, bool)
if (!c)
return -1;
- struct lxc_conf *old = current_config;
current_config = c->lxc_conf;
va_start(ap, arg);
ret = do_lxcapi_attach_run_wait(c, options, program, (const char * const *)argv);
free((void*)argv);
out:
- current_config = old;
+ current_config = NULL;
return ret;
}