This patch introduces the hability to restore a saved container using CRIU. It should be possible to start it using traditional methods: a simple container start; or from a saved state. Signed-off-by: Julio Faracco <jcfaracco@xxxxxxxxx> --- src/lxc/lxc_conf.c | 3 + src/lxc/lxc_conf.h | 2 + src/lxc/lxc_container.c | 188 ++++++++++++++++++++- src/lxc/lxc_container.h | 3 +- src/lxc/lxc_controller.c | 93 ++++++++++- src/lxc/lxc_driver.c | 341 ++++++++++++++++++++++++++++++++++++++- src/lxc/lxc_process.c | 26 ++- src/lxc/lxc_process.h | 1 + 8 files changed, 638 insertions(+), 19 deletions(-) diff --git a/src/lxc/lxc_conf.c b/src/lxc/lxc_conf.c index e6ad91205e..690cef7d39 100644 --- a/src/lxc/lxc_conf.c +++ b/src/lxc/lxc_conf.c @@ -240,6 +240,8 @@ virLXCDriverConfigNew(void) cfg->logDir = g_strdup(LXC_LOG_DIR); cfg->autostartDir = g_strdup(LXC_AUTOSTART_DIR); + cfg->saveImageFormat = NULL; + return cfg; } @@ -291,4 +293,5 @@ virLXCDriverConfigDispose(void *obj) g_free(cfg->stateDir); g_free(cfg->logDir); g_free(cfg->securityDriverName); + g_free(cfg->saveImageFormat); } diff --git a/src/lxc/lxc_conf.h b/src/lxc/lxc_conf.h index 664bafc7b9..08bea11c02 100644 --- a/src/lxc/lxc_conf.h +++ b/src/lxc/lxc_conf.h @@ -61,6 +61,8 @@ struct _virLXCDriverConfig { char *securityDriverName; bool securityDefaultConfined; bool securityRequireConfined; + + char *saveImageFormat; }; struct _virLXCDriver { diff --git a/src/lxc/lxc_container.c b/src/lxc/lxc_container.c index 2a5f8711c4..03c086d029 100644 --- a/src/lxc/lxc_container.c +++ b/src/lxc/lxc_container.c @@ -51,6 +51,7 @@ #include "virerror.h" #include "virlog.h" #include "lxc_container.h" +#include "lxc_criu.h" #include "viralloc.h" #include "virnetdevveth.h" #include "viruuid.h" @@ -84,6 +85,7 @@ struct __lxc_child_argv { char **ttyPaths; int handshakefd; int *nsInheritFDs; + int restorefd; }; static int lxcContainerMountFSBlock(virDomainFSDefPtr fs, @@ -235,8 +237,8 @@ static virCommandPtr lxcContainerBuildInitCmd(virDomainDefPtr vmDef, * * Returns 0 on success or -1 in case of error */ -static int lxcContainerSetupFDs(int *ttyfd, - size_t npassFDs, int *passFDs) +static int lxcContainerSetupFDs(int *ttyfd, size_t npassFDs, + int *passFDs, int restorefd) { int rc = -1; int open_max; @@ -335,6 +337,10 @@ static int lxcContainerSetupFDs(int *ttyfd, for (fd = last_fd + 1; fd < open_max; fd++) { int tmpfd = fd; + + if (tmpfd == restorefd) + continue; + VIR_MASS_CLOSE(tmpfd); } @@ -1017,6 +1023,30 @@ static int lxcContainerMountFSDevPTS(virDomainDefPtr def, return 0; } + +static int lxcContainerMountFSDevPTSRestore(virDomainDefPtr def, + const char *stateDir) +{ + int flags = MS_MOVE; + g_autofree char *path = NULL; + + VIR_DEBUG("Mount /dev/pts stateDir=%s", stateDir); + + path = g_strdup_printf("%s/%s.devpts", stateDir, def->name); + + VIR_DEBUG("Trying to move %s to /dev/pts", path); + + if (mount(path, "/dev/pts", NULL, flags, NULL) < 0) { + virReportSystemError(errno, + _("Failed to mount %s on /dev/pts"), + path); + return -1; + } + + return 0; +} + + static int lxcContainerSetupDevices(char **ttyPaths, size_t nttyPaths) { size_t i; @@ -1843,6 +1873,139 @@ static int lxcAttachNS(int *ns_fd) return 0; } + +/* + * lxcContainerChildRestore: + * @data: pointer to container arguments + */ +static int lxcContainerChildRestore(void *data) +{ + lxc_child_argv_t *argv = data; + virDomainDefPtr vmDef = argv->config; + int ttyfd = -1; + int ret = -1; + virDomainFSDefPtr root; + g_autofree char *ttyPath = NULL; + g_autofree char *sec_mount_options = NULL; + g_autofree char *stateDir = NULL; + g_autofree char *rootfs_mount = NULL; + + if (NULL == vmDef) { + virReportError(VIR_ERR_INTERNAL_ERROR, + "%s", _("lxcChild() passed invalid vm definition")); + goto cleanup; + } + + if (lxcContainerWaitForContinue(argv->monitor) < 0) { + virReportSystemError(errno, "%s", + _("Failed to read the container continue message")); + goto cleanup; + } + VIR_DEBUG("Received container continue message"); + + if (lxcContainerSetID(vmDef) < 0) + goto cleanup; + + root = virDomainGetFilesystemForTarget(vmDef, "/"); + + if (argv->nttyPaths) { + const char *tty = argv->ttyPaths[0]; + + if (STRPREFIX(tty, "/dev/pts/")) + tty += strlen("/dev/pts/"); + + ttyPath = g_strdup_printf("%s/%s.devpts/%s", + LXC_STATE_DIR, vmDef->name, tty); + } else { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("At least one tty is required")); + goto cleanup; + } + + VIR_DEBUG("Container TTY path: %s", ttyPath); + + ttyfd = open(ttyPath, O_RDWR); + if (ttyfd < 0) { + virReportSystemError(errno, + _("Failed to open tty %s"), + ttyPath); + goto cleanup; + } + VIR_DEBUG("Container TTY fd: %d", ttyfd); + + if (!(sec_mount_options = virSecurityManagerGetMountOptions( + argv->securityDriver, + vmDef))) + goto cleanup; + + if (lxcContainerPrepareRoot(vmDef, root, sec_mount_options) < 0) + goto cleanup; + + if (lxcContainerSendContinue(argv->handshakefd) < 0) { + virReportSystemError(errno, "%s", + _("Failed to send continue signal to controller")); + goto cleanup; + } + + VIR_DEBUG("Setting up container's std streams"); + + if (lxcContainerSetupFDs(&ttyfd, + argv->npassFDs, argv->passFDs, argv->restorefd) < 0) + goto cleanup; + + /* CRIU needs the container's root bind mounted so that it is the root of + * some mount. + */ + rootfs_mount = g_strdup_printf("%s/save/%s", LXC_STATE_DIR, vmDef->name); + + if (virFileMakePath(rootfs_mount) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Failed to mkdir rootfs mount path")); + goto cleanup; + } + + if (mount(root->src->path, rootfs_mount, NULL, MS_BIND, NULL) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Failed to create rootfs mountpoint")); + goto cleanup; + } + + if (virFileResolveAllLinks(LXC_STATE_DIR, &stateDir) < 0) + goto cleanup; + + /* Mounts /dev/pts */ + if (lxcContainerMountFSDevPTSRestore(vmDef, stateDir) < 0) { + virReportSystemError(errno, "%s", + _("Failed to mount dev/pts")); + goto cleanup; + } + + if (setsid() < 0) { + virReportSystemError(errno, "%s", + _("Unable to become session leader")); + } + + ret = 0; + + cleanup: + VIR_FORCE_CLOSE(argv->monitor); + VIR_FORCE_CLOSE(argv->handshakefd); + VIR_FORCE_CLOSE(ttyfd); + + if (ret == 0) { + VIR_DEBUG("Executing container restore criu function"); + ret = lxcCriuRestore(vmDef, argv->restorefd, 0); + } else { + VIR_DEBUG("Tearing down container"); + fprintf(stderr, + _("Failure in libvirt_lxc startup: %s\n"), + virGetLastErrorMessage()); + } + + return ret; +} + + /** * lxcContainerSetUserGroup: * @cmd: command to update @@ -2049,7 +2212,7 @@ static int lxcContainerChild(void *data) VIR_FORCE_CLOSE(argv->handshakefd); VIR_FORCE_CLOSE(argv->monitor); if (lxcContainerSetupFDs(&ttyfd, - argv->npassFDs, argv->passFDs) < 0) + argv->npassFDs, argv->passFDs, -1) < 0) goto cleanup; /* Make init process of the container the leader of the new session. @@ -2143,7 +2306,8 @@ int lxcContainerStart(virDomainDefPtr def, int handshakefd, int *nsInheritFDs, size_t nttyPaths, - char **ttyPaths) + char **ttyPaths, + int restorefd) { pid_t pid; int cflags; @@ -2162,6 +2326,7 @@ int lxcContainerStart(virDomainDefPtr def, .ttyPaths = ttyPaths, .handshakefd = handshakefd, .nsInheritFDs = nsInheritFDs, + .restorefd = restorefd, }; /* allocate a stack for the container */ @@ -2207,9 +2372,18 @@ int lxcContainerStart(virDomainDefPtr def, VIR_DEBUG("Inheriting a UTS namespace"); } - VIR_DEBUG("Cloning container init process"); - pid = clone(lxcContainerChild, stacktop, cflags, &args); - VIR_DEBUG("clone() completed, new container PID is %d", pid); + if (restorefd == -1) + VIR_DEBUG("Cloning container init process"); + else + VIR_DEBUG("Cloning container process that will spawn criu restore"); + + if (restorefd != -1) + pid = clone(lxcContainerChildRestore, stacktop, SIGCHLD, &args); + else + pid = clone(lxcContainerChild, stacktop, cflags, &args); + + if (restorefd == -1) + VIR_DEBUG("clone() completed, new container PID is %d", pid); if (pid < 0) { virReportSystemError(errno, "%s", diff --git a/src/lxc/lxc_container.h b/src/lxc/lxc_container.h index 94a6c5309c..cf61a033fc 100644 --- a/src/lxc/lxc_container.h +++ b/src/lxc/lxc_container.h @@ -54,7 +54,8 @@ int lxcContainerStart(virDomainDefPtr def, int handshakefd, int *nsInheritFDs, size_t nttyPaths, - char **ttyPaths); + char **ttyPaths, + int restorefd); int lxcContainerSetupHostdevCapsMakePath(const char *dev); diff --git a/src/lxc/lxc_controller.c b/src/lxc/lxc_controller.c index 8f166a436a..5bd0712ba9 100644 --- a/src/lxc/lxc_controller.c +++ b/src/lxc/lxc_controller.c @@ -139,6 +139,8 @@ struct _virLXCController { virCgroupPtr cgroup; virLXCFusePtr fuse; + + int restore; }; #include "lxc_controller_dispatch.h" @@ -1006,6 +1008,54 @@ static int lxcControllerClearCapabilities(void) return 0; } + +static int lxcControllerFindRestoredPid(int fd) +{ + int initpid = 0; + int ret = -1; + g_autofree char *checkpointdir = NULL; + g_autofree char *pidfile = NULL; + g_autofree char *checkpointfd = NULL; + int pidfilefd; + char c; + + if (fd < 0) + goto cleanup; + + checkpointfd = g_strdup_printf("/proc/self/fd/%d", fd); + + if (virFileResolveLink(checkpointfd, &checkpointdir) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Failed to readlink checkpoint dir path")); + goto cleanup; + } + + pidfile = g_strdup_printf("%s/restore.pid", checkpointdir); + + if ((pidfilefd = virFileOpenAs(pidfile, O_RDONLY, 0, -1, -1, 0)) < 0) { + virReportSystemError(pidfilefd, + _("Failed to open domain's pidfile '%s'"), + pidfile); + goto cleanup; + } + + while ((saferead(pidfilefd, &c, 1) == 1) && c != EOF) + initpid = initpid*10 + c - '0'; + + ret = initpid; + + if (virFileRemove(pidfile, -1, -1) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Failed to delete pidfile path")); + } + + cleanup: + VIR_FORCE_CLOSE(fd); + VIR_FORCE_CLOSE(pidfilefd); + return ret; +} + + static bool wantReboot; static virMutex lock = VIR_MUTEX_INITIALIZER; @@ -2310,6 +2360,7 @@ virLXCControllerRun(virLXCControllerPtr ctrl) int rc = -1; int control[2] = { -1, -1}; int containerhandshake[2] = { -1, -1 }; + bool restore_mode = (ctrl->restore != -1); char **containerTTYPaths = g_new0(char *, ctrl->nconsoles); size_t i; @@ -2368,7 +2419,8 @@ virLXCControllerRun(virLXCControllerPtr ctrl) containerhandshake[1], ctrl->nsFDs, ctrl->nconsoles, - containerTTYPaths)) < 0) + containerTTYPaths, + ctrl->restore)) < 0) goto cleanup; VIR_FORCE_CLOSE(control[1]); VIR_FORCE_CLOSE(containerhandshake[1]); @@ -2380,10 +2432,10 @@ virLXCControllerRun(virLXCControllerPtr ctrl) for (i = 0; i < VIR_LXC_DOMAIN_NAMESPACE_LAST; i++) VIR_FORCE_CLOSE(ctrl->nsFDs[i]); - if (virLXCControllerSetupCgroupLimits(ctrl) < 0) + if (!restore_mode && virLXCControllerSetupCgroupLimits(ctrl) < 0) goto cleanup; - if (virLXCControllerSetupUserns(ctrl) < 0) + if (!restore_mode && virLXCControllerSetupUserns(ctrl) < 0) goto cleanup; if (virLXCControllerMoveInterfaces(ctrl) < 0) @@ -2408,6 +2460,26 @@ virLXCControllerRun(virLXCControllerPtr ctrl) if (lxcControllerClearCapabilities() < 0) goto cleanup; + if (restore_mode) { + int status; + int ret = waitpid(-1, &status, 0); + int initpid; + + VIR_DEBUG("Got sig child %d", ret); + + /* We have two basic cases here. + * - CRIU died bacause of restore error and we do not have a running container + * - CRIU detached itself from the running container + */ + if ((initpid = lxcControllerFindRestoredPid(ctrl->restore)) < 0) { + virReportSystemError(errno, "%s", + _("Unable to get restored task pid")); + virNetDaemonQuit(ctrl->daemon); + goto cleanup; + } + ctrl->initpid = initpid; + } + for (i = 0; i < ctrl->nconsoles; i++) if (virLXCControllerConsoleSetNonblocking(&(ctrl->consoles[i])) < 0) goto cleanup; @@ -2450,6 +2522,7 @@ int main(int argc, char *argv[]) char **veths = NULL; int ns_fd[VIR_LXC_DOMAIN_NAMESPACE_LAST]; int handshakeFd = -1; + int restore = -1; bool bg = false; const struct option options[] = { { "background", 0, NULL, 'b' }, @@ -2462,6 +2535,7 @@ int main(int argc, char *argv[]) { "share-net", 1, NULL, 'N' }, { "share-ipc", 1, NULL, 'I' }, { "share-uts", 1, NULL, 'U' }, + { "restore", 1, NULL, 'r' }, { "help", 0, NULL, 'h' }, { 0, 0, 0, 0 }, }; @@ -2488,7 +2562,7 @@ int main(int argc, char *argv[]) while (1) { int c; - c = getopt_long(argc, argv, "dn:v:p:m:c:s:h:S:N:I:U:", + c = getopt_long(argc, argv, "dn:v:p:m:c:s:h:S:N:I:U:r:", options, NULL); if (c == -1) @@ -2560,6 +2634,14 @@ int main(int argc, char *argv[]) securityDriver = optarg; break; + case 'r': + if (virStrToLong_i(optarg, NULL, 10, &restore) < 0) { + fprintf(stderr, "malformed --restore argument '%s'", + optarg); + goto cleanup; + } + break; + case 'h': case '?': fprintf(stderr, "\n"); @@ -2576,6 +2658,7 @@ int main(int argc, char *argv[]) fprintf(stderr, " -N FD, --share-net FD\n"); fprintf(stderr, " -I FD, --share-ipc FD\n"); fprintf(stderr, " -U FD, --share-uts FD\n"); + fprintf(stderr, " -r FD, --restore FD\n"); fprintf(stderr, " -h, --help\n"); fprintf(stderr, "\n"); rc = 0; @@ -2628,6 +2711,8 @@ int main(int argc, char *argv[]) ctrl->passFDs = passFDs; ctrl->npassFDs = npassFDs; + ctrl->restore = restore; + for (i = 0; i < VIR_LXC_DOMAIN_NAMESPACE_LAST; i++) { if (ns_fd[i] != -1) { if (!ctrl->nsFDs) {/*allocate only once */ diff --git a/src/lxc/lxc_driver.c b/src/lxc/lxc_driver.c index 4416acf923..44306685f5 100644 --- a/src/lxc/lxc_driver.c +++ b/src/lxc/lxc_driver.c @@ -44,6 +44,7 @@ #include "lxc_driver.h" #include "lxc_native.h" #include "lxc_process.h" +#include "lxc_criu.h" #include "virnetdevbridge.h" #include "virnetdevveth.h" #include "virnetdevopenvswitch.h" @@ -1012,7 +1013,7 @@ static int lxcDomainCreateWithFiles(virDomainPtr dom, ret = virLXCProcessStart(dom->conn, driver, vm, nfiles, files, (flags & VIR_DOMAIN_START_AUTODESTROY), - VIR_DOMAIN_RUNNING_BOOTED); + -1, VIR_DOMAIN_RUNNING_BOOTED); if (ret == 0) { event = virDomainEventLifecycleNewFromObj(vm, @@ -1135,7 +1136,7 @@ lxcDomainCreateXMLWithFiles(virConnectPtr conn, if (virLXCProcessStart(conn, driver, vm, nfiles, files, (flags & VIR_DOMAIN_START_AUTODESTROY), - VIR_DOMAIN_RUNNING_BOOTED) < 0) { + -1, VIR_DOMAIN_RUNNING_BOOTED) < 0) { virDomainAuditStart(vm, "booted", false); virLXCDomainObjEndJob(driver, vm); if (!vm->persistent) @@ -2731,6 +2732,338 @@ static int lxcDomainResume(virDomainPtr dom) return ret; } + +static int +lxcDoDomainSave(virLXCDriverPtr driver, virDomainObjPtr vm, + const char *to) +{ + virCapsPtr caps = NULL; + virLXCSaveHeader hdr; + virLXCDriverConfigPtr cfg = virLXCDriverGetConfig(driver); + g_autofree char *checkpointdir = NULL; + g_autofree char *criufile = NULL; + g_autofree char *xml = NULL; + uint32_t xml_len; + int compressed = 0; + int fd = -1; + int criufd = -1; + int ret = -1; + ssize_t r; + + if (!(caps = virLXCDriverGetCapabilities(driver, false))) + return -1; + + checkpointdir = g_path_get_dirname(to); + + if (lxcCriuDump(vm, checkpointdir) != 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Failed to checkpoint domain with CRIU")); + goto cleanup; + } + + if ((compressed = lxcCriuCompress(checkpointdir, + cfg->saveImageFormat)) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Failed to compress criu files")); + goto cleanup; + } + + hdr.compressed = compressed; + + if ((fd = virFileOpenAs(to, O_CREAT | O_TRUNC | O_WRONLY, + S_IRUSR | S_IWUSR, -1, -1, 0)) < 0) { + virReportSystemError(-fd, + _("Failed to create domain save file '%s'"), to); + goto cleanup; + } + + if ((xml = virDomainDefFormat(vm->def, driver->xmlopt, 0)) == NULL) + goto cleanup; + + xml_len = strlen(xml) + 1; + + memset(&hdr, 0, sizeof(hdr)); + memcpy(hdr.magic, LXC_SAVE_MAGIC, sizeof(hdr.magic)); + hdr.version = LXC_SAVE_VERSION; + hdr.xmlLen = xml_len; + + if (safewrite(fd, &hdr, sizeof(hdr)) != sizeof(hdr)) { + virReportError(VIR_ERR_OPERATION_FAILED, "%s", + _("Failed to write save file header")); + goto cleanup; + } + + if (safewrite(fd, xml, xml_len) != xml_len) { + virReportError(VIR_ERR_OPERATION_FAILED, "%s", + _("Failed to write xml description")); + goto cleanup; + } + + criufile = g_strdup_printf("%s/criu.save", checkpointdir); + if ((criufd = virFileOpenAs(criufile, O_RDONLY, + 0, -1, -1, 0)) < 0) { + virReportError(VIR_ERR_OPERATION_FAILED, "%s", + _("Failed to read criu file")); + goto cleanup; + } + + do { + char buf[1024]; + + if ((r = saferead(criufd, buf, sizeof(buf))) < 0) { + virReportSystemError(errno, + _("Unable to read from file '%s'"), + criufile); + goto cleanup; + } + + if (safewrite(fd, buf, r) < 0) { + virReportSystemError(errno, + _("Unable to write to file '%s'"), + to); + goto cleanup; + } + } while (r); + + if (virFileRemove(criufile, -1, -1) < 0) + VIR_WARN("failed to remove scratch file '%s'", + criufile); + + virDomainObjSetState(vm, VIR_DOMAIN_SHUTOFF, + VIR_DOMAIN_SHUTOFF_SAVED); + + ret = 0; + cleanup: + VIR_FORCE_CLOSE(fd); + VIR_FORCE_CLOSE(criufd); + virObjectUnref(caps); + virObjectUnref(cfg); + return ret; +} + +static int +lxcDomainSaveFlags(virDomainPtr dom, const char *to, const char *dxml, + unsigned int flags) +{ + int ret = -1; + virLXCDriverPtr driver = dom->conn->privateData; + virDomainObjPtr vm; + bool remove_dom = false; + + virCheckFlags(0, -1); + if (dxml) { + virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", + _("xml modification unsupported")); + return -1; + } + + if (!(vm = lxcDomObjFromDomain(dom))) + goto cleanup; + + if (virDomainSaveFlagsEnsureACL(dom->conn, vm->def) < 0) + goto cleanup; + + if (virLXCDomainObjBeginJob(driver, vm, LXC_JOB_MODIFY) < 0) + goto cleanup; + + if (!virDomainObjIsActive(vm)) { + virReportError(VIR_ERR_OPERATION_INVALID, "%s", + _("Domain is not running")); + goto endjob; + } + + if (lxcDoDomainSave(driver, vm, to) < 0) + goto endjob; + + if (!vm->persistent) + remove_dom = true; + + ret = 0; + + endjob: + virLXCDomainObjEndJob(driver, vm); + + cleanup: + if (remove_dom && vm) + virDomainObjListRemove(driver->domains, vm); + virDomainObjEndAPI(&vm); + return ret; +} + +static int +lxcDomainSave(virDomainPtr dom, const char *to) +{ + return lxcDomainSaveFlags(dom, to, NULL, 0); +} + +static int +lxcDomainRestoreFlags(virConnectPtr conn, const char *from, + const char* dxml, unsigned int flags) +{ + virLXCDriverPtr driver = conn->privateData; + virLXCDriverConfigPtr cfg = NULL; + virLXCSaveHeader hdr; + virDomainObjPtr vm = NULL; + virDomainDefPtr def = NULL; + virCapsPtr caps = NULL; + int ret = -1; + int restorefd; + g_autofree char *xml = NULL; + g_autofree char *xml_image_path = NULL; + g_autofree char *criufile = NULL; + g_autofree char *savedir = NULL; + g_autofree char *checkpointdir = NULL; + ssize_t r; + int criufd; + int fd; + + virCheckFlags(0, -1); + if (dxml) { + virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", + _("xml modification unsupported")); + goto out; + } + + if ((fd = virFileOpenAs(from, O_RDONLY, 0, -1, -1, 0)) < 0) { + virReportSystemError(-fd, + _("Failed to open domain image file '%s'"), + xml_image_path); + goto out; + } + + if (saferead(fd, &hdr, sizeof(hdr)) != sizeof(hdr)) { + virReportError(VIR_ERR_OPERATION_FAILED, + "%s", _("failed to read lxc header")); + return -1; + } + + if (memcmp(hdr.magic, LXC_SAVE_MAGIC, sizeof(hdr.magic)) != 0) { + virReportError(VIR_ERR_OPERATION_FAILED, "%s", + _("image magic is incorrect")); + return -1; + } + + if (hdr.version > LXC_SAVE_VERSION) { + virReportError(VIR_ERR_OPERATION_FAILED, + _("image version is not supported (%d > %d)"), + hdr.version, LXC_SAVE_VERSION); + return -1; + } + + cfg = virLXCDriverGetConfig(driver); + + xml = g_new0(char, hdr.xmlLen); + + if (saferead(fd, xml, hdr.xmlLen) != hdr.xmlLen) { + virReportError(VIR_ERR_OPERATION_FAILED, "%s", _("failed to read XML")); + goto cleanup; + } + + checkpointdir = g_path_get_dirname(from); + + criufile = g_strdup_printf("%s/criu.save", checkpointdir); + if ((criufd = virFileOpenAs(criufile, O_CREAT | O_TRUNC | O_WRONLY, + S_IRUSR | S_IWUSR, -1, -1, 0)) < 0) { + virReportError(VIR_ERR_OPERATION_FAILED, "%s", + _("Failed to read criu file")); + goto cleanup; + } + + do { + char buf[1024]; + + if ((r = saferead(fd, buf, sizeof(buf))) < 0) { + virReportSystemError(errno, + _("Unable to read from file '%s'"), + criufile); + goto cleanup; + } + + if (safewrite(criufd, buf, r) < 0) { + virReportSystemError(errno, + _("Unable to write to file '%s'"), + from); + goto cleanup; + } + } while (r); + + if (lxcCriuDecompress(checkpointdir, + cfg->saveImageFormat) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Failed to decompress criu files")); + goto cleanup; + } + + if (!xml) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("no domain XML parsed")); + goto cleanup; + } + + if (!(caps = virLXCDriverGetCapabilities(driver, false))) + goto cleanup; + + if (!(def = virDomainDefParseString(xml, driver->xmlopt, caps, + VIR_DOMAIN_DEF_PARSE_INACTIVE))) + goto cleanup; + + if (virDomainRestoreFlagsEnsureACL(conn, def) < 0) + goto cleanup; + + if (!(vm = virDomainObjListAdd(driver->domains, def, + driver->xmlopt, + VIR_DOMAIN_OBJ_LIST_ADD_LIVE | + VIR_DOMAIN_OBJ_LIST_ADD_CHECK_LIVE, + NULL))) + goto cleanup; + def = NULL; + + if (virLXCDomainObjBeginJob(driver, vm, LXC_JOB_MODIFY) < 0) { + if (!vm->persistent) + virDomainObjListRemove(driver->domains, vm); + goto cleanup; + } + + savedir = g_strdup_printf("%s/save/", checkpointdir); + + restorefd = open(savedir, O_DIRECTORY); + if (restorefd < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + "%s", _("Can't open images dir")); + if (!vm->persistent) + virDomainObjListRemove(driver->domains, vm); + virLXCDomainObjEndJob(driver, vm); + goto cleanup; + } + + ret = virLXCProcessStart(conn, driver, vm, + 0, NULL, + 0, restorefd, + VIR_DOMAIN_RUNNING_RESTORED); + + VIR_FORCE_CLOSE(restorefd); + + if (ret < 0 && !vm->persistent) + virDomainObjListRemove(driver->domains, vm); + + virLXCDomainObjEndJob(driver, vm); + cleanup: + VIR_FORCE_CLOSE(fd); + VIR_FORCE_CLOSE(criufd); + out: + virDomainDefFree(def); + virDomainObjEndAPI(&vm); + virObjectUnref(cfg); + return ret; +} + +static int +lxcDomainRestore(virConnectPtr conn, const char *from) +{ + return lxcDomainRestoreFlags(conn, from, NULL, 0); +} + + static int lxcDomainOpenConsole(virDomainPtr dom, const char *dev_name, @@ -5088,6 +5421,10 @@ static virHypervisorDriver lxcHypervisorDriver = { .domainLookupByName = lxcDomainLookupByName, /* 0.4.2 */ .domainSuspend = lxcDomainSuspend, /* 0.7.2 */ .domainResume = lxcDomainResume, /* 0.7.2 */ + .domainSave = lxcDomainSave, /* x.x.x */ + .domainSaveFlags = lxcDomainSaveFlags, /* x.x.x */ + .domainRestore = lxcDomainRestore, /* x.x.x */ + .domainRestoreFlags = lxcDomainRestoreFlags, /* x.x.x */ .domainDestroy = lxcDomainDestroy, /* 0.4.4 */ .domainDestroyFlags = lxcDomainDestroyFlags, /* 0.9.4 */ .domainGetOSType = lxcDomainGetOSType, /* 0.4.2 */ diff --git a/src/lxc/lxc_process.c b/src/lxc/lxc_process.c index cbc04a3dcd..8dc8c42558 100644 --- a/src/lxc/lxc_process.c +++ b/src/lxc/lxc_process.c @@ -117,8 +117,8 @@ virLXCProcessReboot(virLXCDriverPtr driver, vm->newDef = NULL; virLXCProcessStop(driver, vm, VIR_DOMAIN_SHUTOFF_SHUTDOWN); vm->newDef = savedDef; - if (virLXCProcessStart(conn, driver, vm, - 0, NULL, autodestroy, reason) < 0) { + if (virLXCProcessStart(conn, driver, vm, 0, + NULL, autodestroy, -1, reason) < 0) { VIR_WARN("Unable to handle reboot of vm %s", vm->def->name); goto cleanup; @@ -942,7 +942,8 @@ virLXCProcessBuildControllerCmd(virLXCDriverPtr driver, size_t nfiles, int handshakefd, int * const logfd, - const char *pidfile) + const char *pidfile, + int restorefd) { size_t i; g_autofree char *filterstr = NULL; @@ -1016,6 +1017,12 @@ virLXCProcessBuildControllerCmd(virLXCDriverPtr driver, for (i = 0; veths && veths[i]; i++) virCommandAddArgList(cmd, "--veth", veths[i], NULL); + if (restorefd != -1) { + virCommandAddArg(cmd, "--restore"); + virCommandAddArgFormat(cmd, "%d", restorefd); + virCommandPassFD(cmd, restorefd, 0); + } + virCommandPassFD(cmd, handshakefd, 0); virCommandDaemonize(cmd); virCommandSetPidFile(cmd, pidfile); @@ -1186,6 +1193,8 @@ virLXCProcessEnsureRootFS(virDomainObjPtr vm) * @driver: pointer to driver structure * @vm: pointer to virtual machine structure * @autoDestroy: mark the domain for auto destruction + * @restorefd: file descriptor pointing to the restore directory (-1 if not + * restoring) * @reason: reason for switching vm to running state * * Starts a vm @@ -1197,6 +1206,7 @@ int virLXCProcessStart(virConnectPtr conn, virDomainObjPtr vm, unsigned int nfiles, int *files, bool autoDestroy, + int restorefd, virDomainRunningReason reason) { int rc = -1, r; @@ -1388,7 +1398,8 @@ int virLXCProcessStart(virConnectPtr conn, files, nfiles, handshakefds[1], &logfd, - pidfile))) + pidfile, + restorefd))) goto cleanup; /* now that we know it is about to start call the hook if present */ @@ -1491,6 +1502,9 @@ int virLXCProcessStart(virConnectPtr conn, if (!priv->machineName) goto cleanup; + if (restorefd != -1) + goto skip_cgroup_checks; + /* We know the cgroup must exist by this synchronization * point so lets detect that first, since it gives us a * more reliable way to kill everything off if something @@ -1507,6 +1521,8 @@ int virLXCProcessStart(virConnectPtr conn, goto cleanup; } + skip_cgroup_checks: + /* And we can get the first monitor connection now too */ if (!(priv->monitor = virLXCProcessConnectMonitor(driver, vm))) { /* Intentionally overwrite the real monitor error message, @@ -1587,7 +1603,7 @@ virLXCProcessAutostartDomain(virDomainObjPtr vm, if (vm->autostart && !virDomainObjIsActive(vm)) { ret = virLXCProcessStart(data->conn, data->driver, vm, - 0, NULL, false, + 0, NULL, false, -1, VIR_DOMAIN_RUNNING_BOOTED); virDomainAuditStart(vm, "booted", ret >= 0); if (ret < 0) { diff --git a/src/lxc/lxc_process.h b/src/lxc/lxc_process.h index 383f6f714d..dab300784f 100644 --- a/src/lxc/lxc_process.h +++ b/src/lxc/lxc_process.h @@ -28,6 +28,7 @@ int virLXCProcessStart(virConnectPtr conn, virDomainObjPtr vm, unsigned int nfiles, int *files, bool autoDestroy, + int restorefd, virDomainRunningReason reason); int virLXCProcessStop(virLXCDriverPtr driver, virDomainObjPtr vm, -- 2.27.0