mirror of
https://github.com/libvirt/libvirt.git
synced 2025-02-15 01:43:40 -06:00
Fix QEMU save/restore with block devices
The save process was relying on use of the shell >> append operator to ensure the save data was placed after the libvirt header + XML. This doesn't work for block devices though. Replace this code with use of 'dd' and its 'seek' parameter. This means that we need to pad the header + XML out to a multiple of dd block size (in this case we choose 512). The qemuMonitorMigateToCommand() monitor API is used for both save/coredump, and migration via UNIX socket. We can't simply switch this to use 'dd' since this causes problems with the migration usage. Thus, create a dedicated qemuMonitorMigateToFile which can accept an filename + offset, and remove the filename from the current qemuMonitorMigateToCommand() API * src/qemu/qemu_driver.c: Switch to qemuMonitorMigateToFile for save and core dump * src/qemu/qemu_monitor.c, src/qemu/qemu_monitor.h, src/qemu/qemu_monitor_json.c, src/qemu/qemu_monitor_json.h, src/qemu/qemu_monitor_text.c, src/qemu/qemu_monitor_text.h: Create a new qemuMonitorMigateToFile, separate from the existing qemuMonitorMigateToCommand to allow handling file offsets
This commit is contained in:
parent
ae42979a74
commit
93e0b3c8d6
@ -4715,6 +4715,7 @@ static int qemudDomainSaveFlag(virDomainPtr dom, const char *path,
|
||||
qemuDomainObjPrivatePtr priv;
|
||||
struct stat sb;
|
||||
int is_reg = 0;
|
||||
unsigned long long offset;
|
||||
|
||||
memset(&header, 0, sizeof(header));
|
||||
memcpy(header.magic, QEMUD_SAVE_MAGIC, sizeof(header.magic));
|
||||
@ -4788,104 +4789,137 @@ static int qemudDomainSaveFlag(virDomainPtr dom, const char *path,
|
||||
hdata.path = path;
|
||||
hdata.xml = xml;
|
||||
hdata.header = &header;
|
||||
offset = sizeof(header) + header.xml_len;
|
||||
|
||||
/* Due to way we append QEMU state on our header with dd,
|
||||
* we need to ensure there's a 512 byte boundary. Unfortunately
|
||||
* we don't have an explicit offset in the header, so we fake
|
||||
* it by padding the XML string with NULLs */
|
||||
if (offset % QEMU_MONITOR_MIGRATE_TO_FILE_BS) {
|
||||
unsigned long long pad =
|
||||
QEMU_MONITOR_MIGRATE_TO_FILE_BS -
|
||||
(offset % QEMU_MONITOR_MIGRATE_TO_FILE_BS);
|
||||
|
||||
if (VIR_REALLOC_N(xml, header.xml_len + pad) < 0) {
|
||||
virReportOOMError();
|
||||
goto endjob;
|
||||
}
|
||||
memset(xml + header.xml_len, 0, pad);
|
||||
offset += pad;
|
||||
header.xml_len += pad;
|
||||
}
|
||||
|
||||
/* Write header to file, followed by XML */
|
||||
|
||||
/* First try creating the file as root */
|
||||
if (is_reg &&
|
||||
(rc = virFileOperation(path, O_CREAT|O_TRUNC|O_WRONLY,
|
||||
S_IRUSR|S_IWUSR,
|
||||
getuid(), getgid(),
|
||||
qemudDomainSaveFileOpHook, &hdata,
|
||||
0)) != 0) {
|
||||
|
||||
/* If we failed as root, and the error was permission-denied
|
||||
(EACCES), assume it's on a network-connected share where
|
||||
root access is restricted (eg, root-squashed NFS). If the
|
||||
qemu user (driver->user) is non-root, just set a flag to
|
||||
bypass security driver shenanigans, and retry the operation
|
||||
after doing setuid to qemu user */
|
||||
|
||||
if ((rc != EACCES) ||
|
||||
driver->user == getuid()) {
|
||||
virReportSystemError(rc, _("Failed to create domain save file '%s'"),
|
||||
path);
|
||||
if (!is_reg) {
|
||||
int fd = open(path, O_WRONLY | O_TRUNC);
|
||||
if (fd < 0) {
|
||||
virReportSystemError(errno, _("unable to open %s"), path);
|
||||
goto endjob;
|
||||
}
|
||||
if ((rc = qemudDomainSaveFileOpHook(fd, &hdata)) != 0) {
|
||||
close(fd);
|
||||
goto endjob;
|
||||
}
|
||||
if (close(fd) < 0) {
|
||||
virReportSystemError(errno, _("unable to close %s"), path);
|
||||
goto endjob;
|
||||
}
|
||||
} else {
|
||||
if ((rc = virFileOperation(path, O_CREAT|O_TRUNC|O_WRONLY,
|
||||
S_IRUSR|S_IWUSR,
|
||||
getuid(), getgid(),
|
||||
qemudDomainSaveFileOpHook, &hdata,
|
||||
0)) != 0) {
|
||||
/* If we failed as root, and the error was permission-denied
|
||||
(EACCES), assume it's on a network-connected share where
|
||||
root access is restricted (eg, root-squashed NFS). If the
|
||||
qemu user (driver->user) is non-root, just set a flag to
|
||||
bypass security driver shenanigans, and retry the operation
|
||||
after doing setuid to qemu user */
|
||||
|
||||
if ((rc != EACCES) ||
|
||||
driver->user == getuid()) {
|
||||
virReportSystemError(rc, _("Failed to create domain save file '%s'"),
|
||||
path);
|
||||
goto endjob;
|
||||
}
|
||||
|
||||
#ifdef __linux__
|
||||
/* On Linux we can also verify the FS-type of the directory. */
|
||||
char *dirpath, *p;
|
||||
struct statfs st;
|
||||
int statfs_ret;
|
||||
/* On Linux we can also verify the FS-type of the directory. */
|
||||
char *dirpath, *p;
|
||||
struct statfs st;
|
||||
int statfs_ret;
|
||||
|
||||
if ((dirpath = strdup(path)) == NULL) {
|
||||
virReportOOMError();
|
||||
goto endjob;
|
||||
}
|
||||
if ((dirpath = strdup(path)) == NULL) {
|
||||
virReportOOMError();
|
||||
goto endjob;
|
||||
}
|
||||
|
||||
do {
|
||||
// Try less and less of the path until we get to a
|
||||
// directory we can stat. Even if we don't have 'x'
|
||||
// permission on any directory in the path on the NFS
|
||||
// server (assuming it's NFS), we will be able to stat the
|
||||
// mount point, and that will properly tell us if the
|
||||
// fstype is NFS.
|
||||
do {
|
||||
// Try less and less of the path until we get to a
|
||||
// directory we can stat. Even if we don't have 'x'
|
||||
// permission on any directory in the path on the NFS
|
||||
// server (assuming it's NFS), we will be able to stat the
|
||||
// mount point, and that will properly tell us if the
|
||||
// fstype is NFS.
|
||||
|
||||
if ((p = strrchr(dirpath, '/')) == NULL) {
|
||||
qemuReportError(VIR_ERR_INVALID_ARG,
|
||||
_("Invalid relative path '%s' for domain save file"),
|
||||
path);
|
||||
if ((p = strrchr(dirpath, '/')) == NULL) {
|
||||
qemuReportError(VIR_ERR_INVALID_ARG,
|
||||
_("Invalid relative path '%s' for domain save file"),
|
||||
path);
|
||||
VIR_FREE(dirpath);
|
||||
goto endjob;
|
||||
}
|
||||
|
||||
if (p == dirpath)
|
||||
*(p+1) = '\0';
|
||||
else
|
||||
*p = '\0';
|
||||
|
||||
statfs_ret = statfs(dirpath, &st);
|
||||
|
||||
} while ((statfs_ret == -1) && (p != dirpath));
|
||||
|
||||
if (statfs_ret == -1) {
|
||||
virReportSystemError(errno,
|
||||
_("Failed to create domain save file '%s'"
|
||||
" statfs of all elements of path failed."),
|
||||
path);
|
||||
VIR_FREE(dirpath);
|
||||
goto endjob;
|
||||
}
|
||||
|
||||
if (p == dirpath)
|
||||
*(p+1) = '\0';
|
||||
else
|
||||
*p = '\0';
|
||||
|
||||
statfs_ret = statfs(dirpath, &st);
|
||||
|
||||
} while ((statfs_ret == -1) && (p != dirpath));
|
||||
|
||||
if (statfs_ret == -1) {
|
||||
virReportSystemError(errno,
|
||||
_("Failed to create domain save file '%s'"
|
||||
" statfs of all elements of path failed."),
|
||||
path);
|
||||
if (st.f_type != NFS_SUPER_MAGIC) {
|
||||
virReportSystemError(rc,
|
||||
_("Failed to create domain save file '%s'"
|
||||
" (fstype of '%s' is 0x%X"),
|
||||
path, dirpath, (unsigned int) st.f_type);
|
||||
VIR_FREE(dirpath);
|
||||
goto endjob;
|
||||
}
|
||||
VIR_FREE(dirpath);
|
||||
goto endjob;
|
||||
}
|
||||
|
||||
if (st.f_type != NFS_SUPER_MAGIC) {
|
||||
virReportSystemError(rc,
|
||||
_("Failed to create domain save file '%s'"
|
||||
" (fstype of '%s' is 0x%X"),
|
||||
path, dirpath, (unsigned int) st.f_type);
|
||||
VIR_FREE(dirpath);
|
||||
goto endjob;
|
||||
}
|
||||
VIR_FREE(dirpath);
|
||||
#endif
|
||||
|
||||
/* Retry creating the file as driver->user */
|
||||
/* Retry creating the file as driver->user */
|
||||
|
||||
if ((rc = virFileOperation(path, O_CREAT|O_TRUNC|O_WRONLY,
|
||||
S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP,
|
||||
driver->user, driver->group,
|
||||
qemudDomainSaveFileOpHook, &hdata,
|
||||
VIR_FILE_OP_AS_UID)) != 0) {
|
||||
virReportSystemError(rc, _("Error from child process creating '%s'"),
|
||||
if ((rc = virFileOperation(path, O_CREAT|O_TRUNC|O_WRONLY,
|
||||
S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP,
|
||||
driver->user, driver->group,
|
||||
qemudDomainSaveFileOpHook, &hdata,
|
||||
VIR_FILE_OP_AS_UID)) != 0) {
|
||||
virReportSystemError(rc, _("Error from child process creating '%s'"),
|
||||
path);
|
||||
goto endjob;
|
||||
goto endjob;
|
||||
}
|
||||
|
||||
/* Since we had to setuid to create the file, and the fstype
|
||||
is NFS, we assume it's a root-squashing NFS share, and that
|
||||
the security driver stuff would have failed anyway */
|
||||
|
||||
bypassSecurityDriver = 1;
|
||||
}
|
||||
|
||||
/* Since we had to setuid to create the file, and the fstype
|
||||
is NFS, we assume it's a root-squashing NFS share, and that
|
||||
the security driver stuff would have failed anyway */
|
||||
|
||||
bypassSecurityDriver = 1;
|
||||
}
|
||||
|
||||
|
||||
@ -4898,7 +4932,7 @@ static int qemudDomainSaveFlag(virDomainPtr dom, const char *path,
|
||||
if (header.compressed == QEMUD_SAVE_FORMAT_RAW) {
|
||||
const char *args[] = { "cat", NULL };
|
||||
qemuDomainObjEnterMonitorWithDriver(driver, vm);
|
||||
rc = qemuMonitorMigrateToCommand(priv->mon, 1, args, path);
|
||||
rc = qemuMonitorMigrateToFile(priv->mon, 1, args, path, offset);
|
||||
qemuDomainObjExitMonitorWithDriver(driver, vm);
|
||||
} else {
|
||||
const char *prog = qemudSaveCompressionTypeToString(header.compressed);
|
||||
@ -4908,7 +4942,7 @@ static int qemudDomainSaveFlag(virDomainPtr dom, const char *path,
|
||||
NULL
|
||||
};
|
||||
qemuDomainObjEnterMonitorWithDriver(driver, vm);
|
||||
rc = qemuMonitorMigrateToCommand(priv->mon, 1, args, path);
|
||||
rc = qemuMonitorMigrateToFile(priv->mon, 1, args, path, offset);
|
||||
qemuDomainObjExitMonitorWithDriver(driver, vm);
|
||||
}
|
||||
|
||||
@ -5203,7 +5237,7 @@ static int qemudDomainCoreDump(virDomainPtr dom,
|
||||
}
|
||||
|
||||
qemuDomainObjEnterMonitorWithDriver(driver, vm);
|
||||
ret = qemuMonitorMigrateToCommand(priv->mon, 1, args, path);
|
||||
ret = qemuMonitorMigrateToFile(priv->mon, 1, args, path, 0);
|
||||
qemuDomainObjExitMonitorWithDriver(driver, vm);
|
||||
|
||||
if (ret < 0)
|
||||
@ -9896,7 +9930,7 @@ static int doTunnelMigrate(virDomainPtr dom,
|
||||
internalret = qemuMonitorMigrateToUnix(priv->mon, 1, unixfile);
|
||||
else if (qemuCmdFlags & QEMUD_CMD_FLAG_MIGRATE_QEMU_EXEC) {
|
||||
const char *args[] = { "nc", "-U", unixfile, NULL };
|
||||
internalret = qemuMonitorMigrateToCommand(priv->mon, 1, args, "/dev/null");
|
||||
internalret = qemuMonitorMigrateToCommand(priv->mon, 1, args);
|
||||
} else {
|
||||
internalret = -1;
|
||||
}
|
||||
|
@ -1178,17 +1178,40 @@ int qemuMonitorMigrateToHost(qemuMonitorPtr mon,
|
||||
|
||||
int qemuMonitorMigrateToCommand(qemuMonitorPtr mon,
|
||||
int background,
|
||||
const char * const *argv,
|
||||
const char *target)
|
||||
const char * const *argv)
|
||||
{
|
||||
int ret;
|
||||
DEBUG("mon=%p, fd=%d argv=%p target=%s",
|
||||
mon, mon->fd, argv, target);
|
||||
DEBUG("mon=%p, fd=%d argv=%p",
|
||||
mon, mon->fd, argv);
|
||||
|
||||
if (mon->json)
|
||||
ret = qemuMonitorJSONMigrateToCommand(mon, background, argv, target);
|
||||
ret = qemuMonitorJSONMigrateToCommand(mon, background, argv);
|
||||
else
|
||||
ret = qemuMonitorTextMigrateToCommand(mon, background, argv, target);
|
||||
ret = qemuMonitorTextMigrateToCommand(mon, background, argv);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int qemuMonitorMigrateToFile(qemuMonitorPtr mon,
|
||||
int background,
|
||||
const char * const *argv,
|
||||
const char *target,
|
||||
unsigned long long offset)
|
||||
{
|
||||
int ret;
|
||||
DEBUG("mon=%p, fd=%d argv=%p target=%s offset=%llu",
|
||||
mon, mon->fd, argv, target, offset);
|
||||
|
||||
if (offset % QEMU_MONITOR_MIGRATE_TO_FILE_BS) {
|
||||
qemuReportError(VIR_ERR_INTERNAL_ERROR,
|
||||
_("file offset must be a multiple of %llu"),
|
||||
QEMU_MONITOR_MIGRATE_TO_FILE_BS);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (mon->json)
|
||||
ret = qemuMonitorJSONMigrateToFile(mon, background, argv, target, offset);
|
||||
else
|
||||
ret = qemuMonitorTextMigrateToFile(mon, background, argv, target, offset);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -246,8 +246,15 @@ int qemuMonitorMigrateToHost(qemuMonitorPtr mon,
|
||||
|
||||
int qemuMonitorMigrateToCommand(qemuMonitorPtr mon,
|
||||
int background,
|
||||
const char * const *argv,
|
||||
const char *target);
|
||||
const char * const *argv);
|
||||
|
||||
# define QEMU_MONITOR_MIGRATE_TO_FILE_BS 512llu
|
||||
|
||||
int qemuMonitorMigrateToFile(qemuMonitorPtr mon,
|
||||
int background,
|
||||
const char * const *argv,
|
||||
const char *target,
|
||||
unsigned long long offset);
|
||||
|
||||
int qemuMonitorMigrateToUnix(qemuMonitorPtr mon,
|
||||
int background,
|
||||
|
@ -1483,8 +1483,36 @@ int qemuMonitorJSONMigrateToHost(qemuMonitorPtr mon,
|
||||
|
||||
int qemuMonitorJSONMigrateToCommand(qemuMonitorPtr mon,
|
||||
int background,
|
||||
const char * const *argv,
|
||||
const char *target)
|
||||
const char * const *argv)
|
||||
{
|
||||
char *argstr;
|
||||
char *dest = NULL;
|
||||
int ret = -1;
|
||||
|
||||
argstr = virArgvToString(argv);
|
||||
if (!argstr) {
|
||||
virReportOOMError();
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (virAsprintf(&dest, "exec:%s", argstr) < 0) {
|
||||
virReportOOMError();
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
ret = qemuMonitorJSONMigrate(mon, background, dest);
|
||||
|
||||
cleanup:
|
||||
VIR_FREE(argstr);
|
||||
VIR_FREE(dest);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int qemuMonitorJSONMigrateToFile(qemuMonitorPtr mon,
|
||||
int background,
|
||||
const char * const *argv,
|
||||
const char *target,
|
||||
unsigned long long offset)
|
||||
{
|
||||
char *argstr;
|
||||
char *dest = NULL;
|
||||
@ -1504,7 +1532,10 @@ int qemuMonitorJSONMigrateToCommand(qemuMonitorPtr mon,
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (virAsprintf(&dest, "exec:%s >>%s 2>/dev/null", argstr, safe_target) < 0) {
|
||||
if (virAsprintf(&dest, "exec:%s | dd of=%s bs=%llu seek=%llu",
|
||||
argstr, safe_target,
|
||||
QEMU_MONITOR_MIGRATE_TO_FILE_BS,
|
||||
offset / QEMU_MONITOR_MIGRATE_TO_FILE_BS) < 0) {
|
||||
virReportOOMError();
|
||||
goto cleanup;
|
||||
}
|
||||
|
@ -100,8 +100,13 @@ int qemuMonitorJSONMigrateToHost(qemuMonitorPtr mon,
|
||||
|
||||
int qemuMonitorJSONMigrateToCommand(qemuMonitorPtr mon,
|
||||
int background,
|
||||
const char * const *argv,
|
||||
const char *target);
|
||||
const char * const *argv);
|
||||
|
||||
int qemuMonitorJSONMigrateToFile(qemuMonitorPtr mon,
|
||||
int background,
|
||||
const char * const *argv,
|
||||
const char *target,
|
||||
unsigned long long offset);
|
||||
|
||||
int qemuMonitorJSONMigrateToUnix(qemuMonitorPtr mon,
|
||||
int background,
|
||||
|
@ -1202,8 +1202,36 @@ int qemuMonitorTextMigrateToHost(qemuMonitorPtr mon,
|
||||
|
||||
int qemuMonitorTextMigrateToCommand(qemuMonitorPtr mon,
|
||||
int background,
|
||||
const char * const *argv,
|
||||
const char *target)
|
||||
const char * const *argv)
|
||||
{
|
||||
char *argstr;
|
||||
char *dest = NULL;
|
||||
int ret = -1;
|
||||
|
||||
argstr = virArgvToString(argv);
|
||||
if (!argstr) {
|
||||
virReportOOMError();
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (virAsprintf(&dest, "exec:%s", argstr) < 0) {
|
||||
virReportOOMError();
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
ret = qemuMonitorTextMigrate(mon, background, dest);
|
||||
|
||||
cleanup:
|
||||
VIR_FREE(argstr);
|
||||
VIR_FREE(dest);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int qemuMonitorTextMigrateToFile(qemuMonitorPtr mon,
|
||||
int background,
|
||||
const char * const *argv,
|
||||
const char *target,
|
||||
unsigned long long offset)
|
||||
{
|
||||
char *argstr;
|
||||
char *dest = NULL;
|
||||
@ -1223,7 +1251,10 @@ int qemuMonitorTextMigrateToCommand(qemuMonitorPtr mon,
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (virAsprintf(&dest, "exec:%s >>%s 2>/dev/null", argstr, safe_target) < 0) {
|
||||
if (virAsprintf(&dest, "exec:%s | dd of=%s bs=%llu seek=%llu",
|
||||
argstr, safe_target,
|
||||
QEMU_MONITOR_MIGRATE_TO_FILE_BS,
|
||||
offset / QEMU_MONITOR_MIGRATE_TO_FILE_BS) < 0) {
|
||||
virReportOOMError();
|
||||
goto cleanup;
|
||||
}
|
||||
|
@ -99,8 +99,13 @@ int qemuMonitorTextMigrateToHost(qemuMonitorPtr mon,
|
||||
|
||||
int qemuMonitorTextMigrateToCommand(qemuMonitorPtr mon,
|
||||
int background,
|
||||
const char * const *argv,
|
||||
const char *target);
|
||||
const char * const *argv);
|
||||
|
||||
int qemuMonitorTextMigrateToFile(qemuMonitorPtr mon,
|
||||
int background,
|
||||
const char * const *argv,
|
||||
const char *target,
|
||||
unsigned long long offset);
|
||||
|
||||
int qemuMonitorTextMigrateToUnix(qemuMonitorPtr mon,
|
||||
int background,
|
||||
|
Loading…
Reference in New Issue
Block a user