[libvirt] [PATCH 0/6] storage: Cleanups of the storage backends

I'm working on splitting of the storage backend codebase into loadable modules so that ceratin libraries (ceph, gluster) can be installed only when the functionality is required. This is a dump of cleanup patches, but they tend to get merge conflicts pretty often so I'm sending them prior to finishing the driver module stuff. Peter Krempa (6): storage: Remove common code from specific driver backend storage: Split utility functions from storage_backend.(ch) storage: fs: Compile file backends even if filesystem support is disabled storage: scsi: Fix build if SCSI backend is disabled but iSCSI is enabled storage: scsi: Remove private constants from header storage: sheepdog: Split out functions required for tests po/POTFILES.in | 1 + src/Makefile.am | 6 +- src/storage/storage_backend.c | 2807 +--------------------- src/storage/storage_backend.h | 124 - src/storage/storage_backend_disk.c | 153 +- src/storage/storage_backend_disk.h | 3 - src/storage/storage_backend_fs.c | 4 +- src/storage/storage_backend_gluster.c | 1 + src/storage/storage_backend_iscsi.c | 2 +- src/storage/storage_backend_logical.c | 1 + src/storage/storage_backend_mpath.c | 1 + src/storage/storage_backend_rbd.c | 1 + src/storage/storage_backend_scsi.c | 452 +--- src/storage/storage_backend_scsi.h | 8 - src/storage/storage_backend_sheepdog.c | 2 + src/storage/storage_backend_sheepdog.h | 5 - src/storage/storage_backend_sheepdog_priv.h | 29 + src/storage/storage_backend_zfs.c | 1 + src/storage/storage_driver.c | 1 + src/storage/storage_util.c | 3364 +++++++++++++++++++++++++++ src/storage/storage_util.h | 151 ++ tests/storagebackendsheepdogtest.c | 2 +- tests/storagevolxml2argvtest.c | 2 +- 23 files changed, 3602 insertions(+), 3519 deletions(-) create mode 100644 src/storage/storage_backend_sheepdog_priv.h create mode 100644 src/storage/storage_util.c create mode 100644 src/storage/storage_util.h -- 2.11.0

The storage driver helper functions that deal with parted were put into the disk backend code but are used commonly across. --- src/storage/storage_backend.c | 145 ++++++++++++++++++++++++++++++++++- src/storage/storage_backend_disk.c | 152 ------------------------------------- src/storage/storage_backend_disk.h | 3 - 3 files changed, 144 insertions(+), 156 deletions(-) diff --git a/src/storage/storage_backend.c b/src/storage/storage_backend.c index 453f20acf..f188cb081 100644 --- a/src/storage/storage_backend.c +++ b/src/storage/storage_backend.c @@ -2857,12 +2857,155 @@ virStorageBackendBLKIDFindEmpty(const char *device ATTRIBUTE_UNUSED, #if WITH_STORAGE_DISK +typedef enum { + VIR_STORAGE_PARTED_ERROR = -1, + VIR_STORAGE_PARTED_MATCH, /* Valid label found and matches format */ + VIR_STORAGE_PARTED_DIFFERENT, /* Valid label found but not match format */ + VIR_STORAGE_PARTED_UNKNOWN, /* No or unrecognized label */ + VIR_STORAGE_PARTED_NOPTTYPE, /* Did not find the Partition Table type */ + VIR_STORAGE_PARTED_PTTYPE_UNK, /* Partition Table type unknown*/ +} virStorageBackendPARTEDResult; + +/** + * Check for a valid disk label (partition table) on device using + * the PARTED command + * + * returns virStorageBackendPARTEDResult + */ +static virStorageBackendPARTEDResult +virStorageBackendPARTEDFindLabel(const char *device, + const char *format) +{ + const char *const args[] = { + device, "print", "--script", NULL, + }; + virCommandPtr cmd = virCommandNew(PARTED); + char *output = NULL; + char *error = NULL; + char *start, *end; + int ret = VIR_STORAGE_PARTED_ERROR; + + virCommandAddArgSet(cmd, args); + virCommandAddEnvString(cmd, "LC_ALL=C"); + virCommandSetOutputBuffer(cmd, &output); + virCommandSetErrorBuffer(cmd, &error); + + /* if parted succeeds we have a valid partition table */ + ret = virCommandRun(cmd, NULL); + if (ret < 0) { + if ((output && strstr(output, "unrecognised disk label")) || + (error && strstr(error, "unrecognised disk label"))) { + ret = VIR_STORAGE_PARTED_UNKNOWN; + } + goto cleanup; + } + + /* Search for "Partition Table:" in the output. If not present, + * then we cannot validate the partition table type. + */ + if (!(start = strstr(output, "Partition Table: ")) || + !(end = strstr(start, "\n"))) { + VIR_DEBUG("Unable to find tag in output: %s", output); + ret = VIR_STORAGE_PARTED_NOPTTYPE; + goto cleanup; + } + start += strlen("Partition Table: "); + *end = '\0'; + + /* on disk it's "msdos", but we document/use "dos" so deal with it here */ + if (STREQ(start, "msdos")) + start += 2; + + /* Make sure we know about this type */ + if (virStoragePoolFormatDiskTypeFromString(start) < 0) { + ret = VIR_STORAGE_PARTED_PTTYPE_UNK; + goto cleanup; + } + + /* Does the on disk match what the pool desired? */ + if (STREQ(start, format)) + ret = VIR_STORAGE_PARTED_MATCH; + + ret = VIR_STORAGE_PARTED_DIFFERENT; + + cleanup: + virCommandFree(cmd); + VIR_FREE(output); + VIR_FREE(error); + return ret; +} + + +/** + * Determine whether the label on the disk is valid or in a known format + * for the purpose of rewriting the label during build or being able to + * start a pool on a device. + * + * When 'writelabel' is true, if we find a valid disk label on the device, + * then we shouldn't be attempting to write as the volume may contain + * data. Force the usage of the overwrite flag to the build command in + * order to be certain. When the disk label is unrecognized, then it + * should be safe to write. + * + * When 'writelabel' is false, only if we find a valid disk label on the + * device should we allow the start since for this path we won't be + * rewriting the label. + * + * Return: 0 if it's OK + * -1 if something's wrong + */ static int virStorageBackendPARTEDValidLabel(const char *device, const char *format, bool writelabel) { - return virStorageBackendDiskValidLabel(device, format, writelabel); + int ret = -1; + virStorageBackendPARTEDResult check; + + check = virStorageBackendPARTEDFindLabel(device, format); + switch (check) { + case VIR_STORAGE_PARTED_ERROR: + virReportError(VIR_ERR_OPERATION_FAILED, "%s", + _("Error checking for disk label, failed to get " + "disk partition information")); + break; + + case VIR_STORAGE_PARTED_MATCH: + if (writelabel) + virReportError(VIR_ERR_OPERATION_INVALID, + _("Disk label already formatted using '%s'"), + format); + else + ret = 0; + break; + + case VIR_STORAGE_PARTED_DIFFERENT: + virReportError(VIR_ERR_OPERATION_FAILED, "%s", + _("Known, but different label format present, " + "requires build --overwrite")); + break; + + case VIR_STORAGE_PARTED_UNKNOWN: + if (writelabel) + ret = 0; + else + virReportError(VIR_ERR_OPERATION_FAILED, "%s", + _("Unrecognized disk label found, requires build")); + break; + + case VIR_STORAGE_PARTED_NOPTTYPE: + virReportError(VIR_ERR_OPERATION_FAILED, "%s", + _("Unable to determine Partition Type, " + "requires build --overwrite")); + break; + + case VIR_STORAGE_PARTED_PTTYPE_UNK: + virReportError(VIR_ERR_OPERATION_FAILED, "%s", + _("Unknown Partition Type, requires build --overwrite")); + break; + } + + return ret; } #else diff --git a/src/storage/storage_backend_disk.c b/src/storage/storage_backend_disk.c index 04ffc6ce2..eae6c90e2 100644 --- a/src/storage/storage_backend_disk.c +++ b/src/storage/storage_backend_disk.c @@ -418,158 +418,6 @@ virStorageBackendDiskRefreshPool(virConnectPtr conn ATTRIBUTE_UNUSED, } -typedef enum { - VIR_STORAGE_PARTED_ERROR = -1, - VIR_STORAGE_PARTED_MATCH, /* Valid label found and matches format */ - VIR_STORAGE_PARTED_DIFFERENT, /* Valid label found but not match format */ - VIR_STORAGE_PARTED_UNKNOWN, /* No or unrecognized label */ - VIR_STORAGE_PARTED_NOPTTYPE, /* Did not find the Partition Table type */ - VIR_STORAGE_PARTED_PTTYPE_UNK, /* Partition Table type unknown*/ -} virStorageBackendPARTEDResult; - -/** - * Check for a valid disk label (partition table) on device using - * the PARTED command - * - * returns virStorageBackendPARTEDResult - */ -static virStorageBackendPARTEDResult -virStorageBackendPARTEDFindLabel(const char *device, - const char *format) -{ - const char *const args[] = { - device, "print", "--script", NULL, - }; - virCommandPtr cmd = virCommandNew(PARTED); - char *output = NULL; - char *error = NULL; - char *start, *end; - int ret = VIR_STORAGE_PARTED_ERROR; - - virCommandAddArgSet(cmd, args); - virCommandAddEnvString(cmd, "LC_ALL=C"); - virCommandSetOutputBuffer(cmd, &output); - virCommandSetErrorBuffer(cmd, &error); - - /* if parted succeeds we have a valid partition table */ - ret = virCommandRun(cmd, NULL); - if (ret < 0) { - if ((output && strstr(output, "unrecognised disk label")) || - (error && strstr(error, "unrecognised disk label"))) { - ret = VIR_STORAGE_PARTED_UNKNOWN; - } - goto cleanup; - } - - /* Search for "Partition Table:" in the output. If not present, - * then we cannot validate the partition table type. - */ - if (!(start = strstr(output, "Partition Table: ")) || - !(end = strstr(start, "\n"))) { - VIR_DEBUG("Unable to find tag in output: %s", output); - ret = VIR_STORAGE_PARTED_NOPTTYPE; - goto cleanup; - } - start += strlen("Partition Table: "); - *end = '\0'; - - /* on disk it's "msdos", but we document/use "dos" so deal with it here */ - if (STREQ(start, "msdos")) - start += 2; - - /* Make sure we know about this type */ - if (virStoragePoolFormatDiskTypeFromString(start) < 0) { - ret = VIR_STORAGE_PARTED_PTTYPE_UNK; - goto cleanup; - } - - /* Does the on disk match what the pool desired? */ - if (STREQ(start, format)) - ret = VIR_STORAGE_PARTED_MATCH; - - ret = VIR_STORAGE_PARTED_DIFFERENT; - - cleanup: - virCommandFree(cmd); - VIR_FREE(output); - VIR_FREE(error); - return ret; -} - - -/** - * Determine whether the label on the disk is valid or in a known format - * for the purpose of rewriting the label during build or being able to - * start a pool on a device. - * - * When 'writelabel' is true, if we find a valid disk label on the device, - * then we shouldn't be attempting to write as the volume may contain - * data. Force the usage of the overwrite flag to the build command in - * order to be certain. When the disk label is unrecognized, then it - * should be safe to write. - * - * When 'writelabel' is false, only if we find a valid disk label on the - * device should we allow the start since for this path we won't be - * rewriting the label. - * - * Return: 0 if it's OK - * -1 if something's wrong - */ -int -virStorageBackendDiskValidLabel(const char *device, - const char *format, - bool writelabel) -{ - int ret = -1; - virStorageBackendPARTEDResult check; - - check = virStorageBackendPARTEDFindLabel(device, format); - switch (check) { - case VIR_STORAGE_PARTED_ERROR: - virReportError(VIR_ERR_OPERATION_FAILED, "%s", - _("Error checking for disk label, failed to get " - "disk partition information")); - break; - - case VIR_STORAGE_PARTED_MATCH: - if (writelabel) - virReportError(VIR_ERR_OPERATION_INVALID, - _("Disk label already formatted using '%s'"), - format); - else - ret = 0; - break; - - case VIR_STORAGE_PARTED_DIFFERENT: - virReportError(VIR_ERR_OPERATION_FAILED, "%s", - _("Known, but different label format present, " - "requires build --overwrite")); - break; - - case VIR_STORAGE_PARTED_UNKNOWN: - if (writelabel) - ret = 0; - else - virReportError(VIR_ERR_OPERATION_FAILED, "%s", - _("Unrecognized disk label found, requires build")); - break; - - case VIR_STORAGE_PARTED_NOPTTYPE: - virReportError(VIR_ERR_OPERATION_FAILED, "%s", - _("Unable to determine Partition Type, " - "requires build --overwrite")); - break; - - case VIR_STORAGE_PARTED_PTTYPE_UNK: - virReportError(VIR_ERR_OPERATION_FAILED, "%s", - _("Unknown Partition Type, requires build --overwrite")); - break; - } - - return ret; -} - - static int virStorageBackendDiskStartPool(virConnectPtr conn ATTRIBUTE_UNUSED, virStoragePoolObjPtr pool) diff --git a/src/storage/storage_backend_disk.h b/src/storage/storage_backend_disk.h index 79c0e6b75..aaabe6224 100644 --- a/src/storage/storage_backend_disk.h +++ b/src/storage/storage_backend_disk.h @@ -28,7 +28,4 @@ extern virStorageBackend virStorageBackendDisk; -int virStorageBackendDiskValidLabel(const char *device, - const char *format, - bool writelabel); #endif /* __VIR_STORAGE_BACKEND_DISK_H__ */ -- 2.11.0

On 01/18/2017 06:36 AM, Peter Krempa wrote:
The storage driver helper functions that deal with parted were put into the disk backend code but are used commonly across. --- src/storage/storage_backend.c | 145 ++++++++++++++++++++++++++++++++++- src/storage/storage_backend_disk.c | 152 ------------------------------------- src/storage/storage_backend_disk.h | 3 - 3 files changed, 144 insertions(+), 156 deletions(-)
This code should sign up for frequent flier miles. ACK, John

The file became a garbage dump for all kinds of utility functions over time. Move them to a separate file so that the files can become a clean interface for the storage backends. --- po/POTFILES.in | 1 + src/Makefile.am | 3 +- src/storage/storage_backend.c | 2950 +------------------------------- src/storage/storage_backend.h | 124 -- src/storage/storage_backend_disk.c | 1 + src/storage/storage_backend_fs.c | 1 + src/storage/storage_backend_gluster.c | 1 + src/storage/storage_backend_iscsi.c | 1 + src/storage/storage_backend_logical.c | 1 + src/storage/storage_backend_mpath.c | 1 + src/storage/storage_backend_rbd.c | 1 + src/storage/storage_backend_scsi.c | 1 + src/storage/storage_backend_sheepdog.c | 1 + src/storage/storage_backend_zfs.c | 1 + src/storage/storage_driver.c | 1 + src/storage/storage_util.c | 2915 +++++++++++++++++++++++++++++++ src/storage/storage_util.h | 148 ++ tests/storagevolxml2argvtest.c | 2 +- 18 files changed, 3112 insertions(+), 3042 deletions(-) create mode 100644 src/storage/storage_util.c create mode 100644 src/storage/storage_util.h diff --git a/po/POTFILES.in b/po/POTFILES.in index 59efd9190..e464c045d 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -177,6 +177,7 @@ src/storage/storage_backend_scsi.c src/storage/storage_backend_sheepdog.c src/storage/storage_backend_zfs.c src/storage/storage_driver.c +src/storage/storage_util.c src/test/test_driver.c src/uml/uml_conf.c src/uml/uml_driver.c diff --git a/src/Makefile.am b/src/Makefile.am index 21a78e07b..348bd2a30 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -972,7 +972,8 @@ SECRET_DRIVER_SOURCES = \ # Storage backend specific impls STORAGE_DRIVER_SOURCES = \ storage/storage_driver.h storage/storage_driver.c \ - storage/storage_backend.h storage/storage_backend.c + storage/storage_backend.h storage/storage_backend.c \ + storage/storage_util.h storage/storage_util.c STORAGE_DRIVER_FS_SOURCES = \ storage/storage_backend_fs.h storage/storage_backend_fs.c diff --git a/src/storage/storage_backend.c b/src/storage/storage_backend.c index f188cb081..92b08a2b4 100644 --- a/src/storage/storage_backend.c +++ b/src/storage/storage_backend.c @@ -24,54 +24,15 @@ #include <config.h> #include <string.h> -#include <stdio.h> -#include <regex.h> -#include <sys/types.h> -#include <sys/wait.h> -#include <unistd.h> -#include <fcntl.h> #include <sys/stat.h> -#include <sys/param.h> -#include <dirent.h> -#include "dirname.h" -#ifdef __linux__ -# include <sys/ioctl.h> -# include <linux/fs.h> -# ifndef FS_NOCOW_FL -# define FS_NOCOW_FL 0x00800000 /* Do not cow file */ -# endif -#endif - -#if WITH_BLKID -# include <blkid/blkid.h> -#endif - -#if WITH_SELINUX -# include <selinux/selinux.h> -#endif - -#if HAVE_LINUX_BTRFS_H -# include <linux/btrfs.h> -#endif #include "datatypes.h" #include "virerror.h" #include "viralloc.h" #include "internal.h" -#include "secret_conf.h" -#include "secret_util.h" -#include "vircrypto.h" -#include "viruuid.h" #include "virstoragefile.h" #include "storage_backend.h" #include "virlog.h" -#include "virfile.h" -#include "virjson.h" -#include "virqemu.h" -#include "stat-time.h" -#include "virstring.h" -#include "virxml.h" -#include "fdstream.h" #if WITH_STORAGE_LVM # include "storage_backend_logical.h" @@ -159,2902 +120,59 @@ static virStorageFileBackendPtr fileBackends[] = { }; -#define READ_BLOCK_SIZE_DEFAULT (1024 * 1024) -#define WRITE_BLOCK_SIZE_DEFAULT (4 * 1024) - -/* - * Perform the O(1) btrfs clone operation, if possible. - * Upon success, return 0. Otherwise, return -1 and set errno. - */ -#if HAVE_LINUX_BTRFS_H -static inline int -btrfsCloneFile(int dest_fd, int src_fd) -{ - return ioctl(dest_fd, BTRFS_IOC_CLONE, src_fd); -} -#else -static inline int -btrfsCloneFile(int dest_fd ATTRIBUTE_UNUSED, - int src_fd ATTRIBUTE_UNUSED) -{ - errno = ENOTSUP; - return -1; -} -#endif - -static int ATTRIBUTE_NONNULL(2) -virStorageBackendCopyToFD(virStorageVolDefPtr vol, - virStorageVolDefPtr inputvol, - int fd, - unsigned long long *total, - bool want_sparse, - bool reflink_copy) -{ - int inputfd = -1; - int amtread = -1; - int ret = 0; - size_t rbytes = READ_BLOCK_SIZE_DEFAULT; - int wbytes = 0; - int interval; - char *zerobuf = NULL; - char *buf = NULL; - struct stat st; - - if ((inputfd = open(inputvol->target.path, O_RDONLY)) < 0) { - ret = -errno; - virReportSystemError(errno, - _("could not open input path '%s'"), - inputvol->target.path); - goto cleanup; - } - -#ifdef __linux__ - if (ioctl(fd, BLKBSZGET, &wbytes) < 0) - wbytes = 0; -#endif - if ((wbytes == 0) && fstat(fd, &st) == 0) - wbytes = st.st_blksize; - if (wbytes < WRITE_BLOCK_SIZE_DEFAULT) - wbytes = WRITE_BLOCK_SIZE_DEFAULT; - - if (VIR_ALLOC_N(zerobuf, wbytes) < 0) { - ret = -errno; - goto cleanup; - } - - if (VIR_ALLOC_N(buf, rbytes) < 0) { - ret = -errno; - goto cleanup; - } - - if (reflink_copy) { - if (btrfsCloneFile(fd, inputfd) < 0) { - ret = -errno; - virReportSystemError(errno, - _("failed to clone files from '%s'"), - inputvol->target.path); - goto cleanup; - } else { - VIR_DEBUG("btrfs clone finished."); - goto cleanup; - } - } - - while (amtread != 0) { - int amtleft; - - if (*total < rbytes) - rbytes = *total; - - if ((amtread = saferead(inputfd, buf, rbytes)) < 0) { - ret = -errno; - virReportSystemError(errno, - _("failed reading from file '%s'"), - inputvol->target.path); - goto cleanup; - } - *total -= amtread; - - /* Loop over amt read in 512 byte increments, looking for sparse - * blocks */ - amtleft = amtread; - do { - interval = ((wbytes > amtleft) ? amtleft : wbytes); - int offset = amtread - amtleft; - - if (want_sparse && memcmp(buf+offset, zerobuf, interval) == 0) { - if (lseek(fd, interval, SEEK_CUR) < 0) { - ret = -errno; - virReportSystemError(errno, - _("cannot extend file '%s'"), - vol->target.path); - goto cleanup; - } - } else if (safewrite(fd, buf+offset, interval) < 0) { - ret = -errno; - virReportSystemError(errno, - _("failed writing to file '%s'"), - vol->target.path); - goto cleanup; - - } - } while ((amtleft -= interval) > 0); - } - - if (fdatasync(fd) < 0) { - ret = -errno; - virReportSystemError(errno, _("cannot sync data to file '%s'"), - vol->target.path); - goto cleanup; - } - - - if (VIR_CLOSE(inputfd) < 0) { - ret = -errno; - virReportSystemError(errno, - _("cannot close file '%s'"), - inputvol->target.path); - goto cleanup; - } - inputfd = -1; - - cleanup: - VIR_FORCE_CLOSE(inputfd); - - VIR_FREE(zerobuf); - VIR_FREE(buf); - - return ret; -} - -static int -virStorageBackendCreateBlockFrom(virConnectPtr conn ATTRIBUTE_UNUSED, - virStoragePoolObjPtr pool ATTRIBUTE_UNUSED, - virStorageVolDefPtr vol, - virStorageVolDefPtr inputvol, - unsigned int flags) -{ - int fd = -1; - int ret = -1; - unsigned long long remain; - struct stat st; - gid_t gid; - uid_t uid; - mode_t mode; - bool reflink_copy = false; - - virCheckFlags(VIR_STORAGE_VOL_CREATE_PREALLOC_METADATA | - VIR_STORAGE_VOL_CREATE_REFLINK, - -1); - - if (flags & VIR_STORAGE_VOL_CREATE_PREALLOC_METADATA) { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", - _("metadata preallocation is not supported for block " - "volumes")); - goto cleanup; - } - - if (flags & VIR_STORAGE_VOL_CREATE_REFLINK) - reflink_copy = true; - - if ((fd = open(vol->target.path, O_RDWR)) < 0) { - virReportSystemError(errno, - _("cannot create path '%s'"), - vol->target.path); - goto cleanup; - } - - remain = vol->target.capacity; - - if (inputvol) { - if (virStorageBackendCopyToFD(vol, inputvol, fd, &remain, - false, reflink_copy) < 0) - goto cleanup; - } - - if (fstat(fd, &st) == -1) { - virReportSystemError(errno, _("stat of '%s' failed"), - vol->target.path); - goto cleanup; - } - uid = (vol->target.perms->uid != st.st_uid) ? vol->target.perms->uid - : (uid_t) -1; - gid = (vol->target.perms->gid != st.st_gid) ? vol->target.perms->gid - : (gid_t) -1; - if (((uid != (uid_t) -1) || (gid != (gid_t) -1)) - && (fchown(fd, uid, gid) < 0)) { - virReportSystemError(errno, - _("cannot chown '%s' to (%u, %u)"), - vol->target.path, (unsigned int) uid, - (unsigned int) gid); - goto cleanup; - } - - mode = (vol->target.perms->mode == (mode_t) -1 ? - VIR_STORAGE_DEFAULT_VOL_PERM_MODE : vol->target.perms->mode); - if (fchmod(fd, mode) < 0) { - virReportSystemError(errno, - _("cannot set mode of '%s' to %04o"), - vol->target.path, mode); - goto cleanup; - } - if (VIR_CLOSE(fd) < 0) { - virReportSystemError(errno, - _("cannot close file '%s'"), - vol->target.path); - goto cleanup; - } - fd = -1; - - ret = 0; - cleanup: - VIR_FORCE_CLOSE(fd); - - return ret; -} - -static int -createRawFile(int fd, virStorageVolDefPtr vol, - virStorageVolDefPtr inputvol, - bool reflink_copy) -{ - bool need_alloc = true; - int ret = 0; - unsigned long long pos = 0; - - /* If the new allocation is lower than the capacity of the original file, - * the cloned volume will be sparse */ - if (inputvol && - vol->target.allocation < inputvol->target.capacity) - need_alloc = false; - - /* Seek to the final size, so the capacity is available upfront - * for progress reporting */ - if (ftruncate(fd, vol->target.capacity) < 0) { - ret = -errno; - virReportSystemError(errno, - _("cannot extend file '%s'"), - vol->target.path); - goto cleanup; - } - -/* Avoid issues with older kernel's <linux/fs.h> namespace pollution. */ -#if HAVE_FALLOCATE - 0 - /* Try to preallocate all requested disk space, but fall back to - * other methods if this fails with ENOSYS or EOPNOTSUPP. If allocation - * is 0 (or less than 0), then fallocate will fail with EINVAL. - * NOTE: do not use posix_fallocate; posix_fallocate falls back - * to writing zeroes block by block in case fallocate isn't - * available, and since we're going to copy data from another - * file it doesn't make sense to write the file twice. */ - if (vol->target.allocation && need_alloc) { - if (fallocate(fd, 0, 0, vol->target.allocation) == 0) { - need_alloc = false; - } else if (errno != ENOSYS && errno != EOPNOTSUPP) { - ret = -errno; - virReportSystemError(errno, - _("cannot allocate %llu bytes in file '%s'"), - vol->target.allocation, vol->target.path); - goto cleanup; - } - } -#endif - - if (inputvol) { - unsigned long long remain = inputvol->target.capacity; - /* allow zero blocks to be skipped if we've requested sparse - * allocation (allocation < capacity) or we have already - * been able to allocate the required space. */ - if ((ret = virStorageBackendCopyToFD(vol, inputvol, fd, &remain, - !need_alloc, reflink_copy)) < 0) - goto cleanup; - - /* If the new allocation is greater than the original capacity, - * but fallocate failed, fill the rest with zeroes. - */ - pos = inputvol->target.capacity - remain; - } - - if (need_alloc && (vol->target.allocation - pos > 0)) { - if (safezero(fd, pos, vol->target.allocation - pos) < 0) { - ret = -errno; - virReportSystemError(errno, _("cannot fill file '%s'"), - vol->target.path); - goto cleanup; - } - } - - if (fsync(fd) < 0) { - ret = -errno; - virReportSystemError(errno, _("cannot sync data to file '%s'"), - vol->target.path); - goto cleanup; - } - - cleanup: - return ret; -} - -int -virStorageBackendCreateRaw(virConnectPtr conn ATTRIBUTE_UNUSED, - virStoragePoolObjPtr pool, - virStorageVolDefPtr vol, - virStorageVolDefPtr inputvol, - unsigned int flags) -{ - int ret = -1; - int fd = -1; - int operation_flags; - bool reflink_copy = false; - mode_t open_mode = VIR_STORAGE_DEFAULT_VOL_PERM_MODE; - bool created = false; - - virCheckFlags(VIR_STORAGE_VOL_CREATE_PREALLOC_METADATA | - VIR_STORAGE_VOL_CREATE_REFLINK, - -1); - - if (flags & VIR_STORAGE_VOL_CREATE_PREALLOC_METADATA) { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", - _("metadata preallocation is not supported for raw " - "volumes")); - goto cleanup; - } - - if (vol->target.backingStore) { - virReportError(VIR_ERR_NO_SUPPORT, "%s", - _("backing storage not supported for raw volumes")); - goto cleanup; - } - - if (flags & VIR_STORAGE_VOL_CREATE_REFLINK) - reflink_copy = true; - - - if (vol->target.encryption != NULL) { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", - _("storage pool does not support encrypted volumes")); - goto cleanup; - } - - operation_flags = VIR_FILE_OPEN_FORCE_MODE | VIR_FILE_OPEN_FORCE_OWNER; - if (pool->def->type == VIR_STORAGE_POOL_NETFS) - operation_flags |= VIR_FILE_OPEN_FORK; - - if (vol->target.perms->mode != (mode_t) -1) - open_mode = vol->target.perms->mode; - - if ((fd = virFileOpenAs(vol->target.path, - O_RDWR | O_CREAT | O_EXCL, - open_mode, - vol->target.perms->uid, - vol->target.perms->gid, - operation_flags)) < 0) { - virReportSystemError(-fd, - _("Failed to create file '%s'"), - vol->target.path); - goto cleanup; - } - created = true; - - if (vol->target.nocow) { -#ifdef __linux__ - int attr; - - /* Set NOCOW flag. This is an optimisation for btrfs. - * The FS_IOC_SETFLAGS ioctl return value will be ignored since any - * failure of this operation should not block the volume creation. - */ - if (ioctl(fd, FS_IOC_GETFLAGS, &attr) < 0) { - virReportSystemError(errno, "%s", _("Failed to get fs flags")); - } else { - attr |= FS_NOCOW_FL; - if (ioctl(fd, FS_IOC_SETFLAGS, &attr) < 0) { - virReportSystemError(errno, "%s", - _("Failed to set NOCOW flag")); - } - } -#endif - } - - if ((ret = createRawFile(fd, vol, inputvol, reflink_copy)) < 0) - /* createRawFile already reported the exact error. */ - ret = -1; - - cleanup: - if (ret < 0 && created) - ignore_value(virFileRemove(vol->target.path, - vol->target.perms->uid, - vol->target.perms->gid)); - VIR_FORCE_CLOSE(fd); - return ret; -} - -static int -virStorageGenerateSecretUUID(virConnectPtr conn, - unsigned char *uuid) +virStorageBackendPtr +virStorageBackendForType(int type) { - unsigned attempt; - - for (attempt = 0; attempt < 65536; attempt++) { - virSecretPtr tmp; - if (virUUIDGenerate(uuid) < 0) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("unable to generate uuid")); - return -1; - } - tmp = conn->secretDriver->secretLookupByUUID(conn, uuid); - if (tmp == NULL) - return 0; - - virObjectUnref(tmp); - } - - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("too many conflicts when generating a uuid")); + size_t i; + for (i = 0; backends[i]; i++) + if (backends[i]->type == type) + return backends[i]; - return -1; + virReportError(VIR_ERR_INTERNAL_ERROR, + _("missing backend for pool type %d (%s)"), + type, NULLSTR(virStoragePoolTypeToString(type))); + return NULL; } -static int -virStorageGenerateQcowEncryption(virConnectPtr conn, - virStorageVolDefPtr vol) -{ - virSecretDefPtr def = NULL; - virBuffer buf = VIR_BUFFER_INITIALIZER; - virStorageEncryptionPtr enc; - virStorageEncryptionSecretPtr enc_secret = NULL; - virSecretPtr secret = NULL; - char *xml; - unsigned char value[VIR_STORAGE_QCOW_PASSPHRASE_SIZE]; - int ret = -1; - - if (conn->secretDriver == NULL || - conn->secretDriver->secretLookupByUUID == NULL || - conn->secretDriver->secretDefineXML == NULL || - conn->secretDriver->secretSetValue == NULL) { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", - _("secret storage not supported")); - goto cleanup; - } - - enc = vol->target.encryption; - if (enc->nsecrets != 0) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("secrets already defined")); - goto cleanup; - } - - if (VIR_ALLOC(enc_secret) < 0 || VIR_REALLOC_N(enc->secrets, 1) < 0 || - VIR_ALLOC(def) < 0) - goto cleanup; - - def->isephemeral = false; - def->isprivate = false; - if (virStorageGenerateSecretUUID(conn, def->uuid) < 0) - goto cleanup; - - def->usage_type = VIR_SECRET_USAGE_TYPE_VOLUME; - if (VIR_STRDUP(def->usage_id, vol->target.path) < 0) - goto cleanup; - xml = virSecretDefFormat(def); - virSecretDefFree(def); - def = NULL; - if (xml == NULL) - goto cleanup; - - secret = conn->secretDriver->secretDefineXML(conn, xml, 0); - if (secret == NULL) { - VIR_FREE(xml); - goto cleanup; - } - VIR_FREE(xml); - - if (virStorageGenerateQcowPassphrase(value) < 0) - goto cleanup; - - if (conn->secretDriver->secretSetValue(secret, value, sizeof(value), 0) < 0) - goto cleanup; - - enc_secret->type = VIR_STORAGE_ENCRYPTION_SECRET_TYPE_PASSPHRASE; - enc_secret->seclookupdef.type = VIR_SECRET_LOOKUP_TYPE_UUID; - memcpy(enc_secret->seclookupdef.u.uuid, secret->uuid, VIR_UUID_BUFLEN); - enc->format = VIR_STORAGE_ENCRYPTION_FORMAT_QCOW; - enc->secrets[0] = enc_secret; /* Space for secrets[0] allocated above */ - enc_secret = NULL; - enc->nsecrets = 1; - ret = 0; - - cleanup: - if (secret != NULL) { - if (ret != 0 && - conn->secretDriver->secretUndefine != NULL) - conn->secretDriver->secretUndefine(secret); - virObjectUnref(secret); - } - virBufferFreeAndReset(&buf); - virSecretDefFree(def); - VIR_FREE(enc_secret); - return ret; -} - -static int -virStorageBackendCreateExecCommand(virStoragePoolObjPtr pool, - virStorageVolDefPtr vol, - virCommandPtr cmd) +virStorageFileBackendPtr +virStorageFileBackendForTypeInternal(int type, + int protocol, + bool report) { - struct stat st; - gid_t gid; - uid_t uid; - mode_t mode = (vol->target.perms->mode == (mode_t) -1 ? - VIR_STORAGE_DEFAULT_VOL_PERM_MODE : - vol->target.perms->mode); - bool filecreated = false; - int ret = -1; - - if ((pool->def->type == VIR_STORAGE_POOL_NETFS) - && (((geteuid() == 0) - && (vol->target.perms->uid != (uid_t) -1) - && (vol->target.perms->uid != 0)) - || ((vol->target.perms->gid != (gid_t) -1) - && (vol->target.perms->gid != getegid())))) { - - virCommandSetUID(cmd, vol->target.perms->uid); - virCommandSetGID(cmd, vol->target.perms->gid); - virCommandSetUmask(cmd, S_IRWXUGO ^ mode); - - if (virCommandRun(cmd, NULL) == 0) { - /* command was successfully run, check if the file was created */ - if (stat(vol->target.path, &st) >= 0) { - filecreated = true; - - /* seems qemu-img disregards umask and open/creates using 0644. - * If that doesn't match what we expect, then let's try to - * re-open the file and attempt to force the mode change. - */ - if (mode != (st.st_mode & S_IRWXUGO)) { - int fd = -1; - int flags = VIR_FILE_OPEN_FORK | VIR_FILE_OPEN_FORCE_MODE; - - if ((fd = virFileOpenAs(vol->target.path, O_RDWR, mode, - vol->target.perms->uid, - vol->target.perms->gid, - flags)) >= 0) { - /* Success - means we're good */ - VIR_FORCE_CLOSE(fd); - ret = 0; - goto cleanup; - } - } - } - } - } + size_t i; - if (!filecreated) { - /* don't change uid/gid/mode if we retry */ - virCommandSetUID(cmd, -1); - virCommandSetGID(cmd, -1); - virCommandSetUmask(cmd, 0); + for (i = 0; fileBackends[i]; i++) { + if (fileBackends[i]->type == type) { + if (type == VIR_STORAGE_TYPE_NETWORK && + fileBackends[i]->protocol != protocol) + continue; - if (virCommandRun(cmd, NULL) < 0) - goto cleanup; - if (stat(vol->target.path, &st) < 0) { - virReportSystemError(errno, - _("failed to create %s"), vol->target.path); - goto cleanup; + return fileBackends[i]; } - filecreated = true; - } - - uid = (vol->target.perms->uid != st.st_uid) ? vol->target.perms->uid - : (uid_t) -1; - gid = (vol->target.perms->gid != st.st_gid) ? vol->target.perms->gid - : (gid_t) -1; - if (((uid != (uid_t) -1) || (gid != (gid_t) -1)) - && (chown(vol->target.path, uid, gid) < 0)) { - virReportSystemError(errno, - _("cannot chown %s to (%u, %u)"), - vol->target.path, (unsigned int) uid, - (unsigned int) gid); - goto cleanup; } - if (mode != (st.st_mode & S_IRWXUGO) && - chmod(vol->target.path, mode) < 0) { - virReportSystemError(errno, - _("cannot set mode of '%s' to %04o"), - vol->target.path, mode); - goto cleanup; - } - - ret = 0; - - cleanup: - if (ret < 0 && filecreated) - virFileRemove(vol->target.path, vol->target.perms->uid, - vol->target.perms->gid); - return ret; -} - -/* Create ploop directory with ploop image and DiskDescriptor.xml - * if function fails to create image file the directory will be deleted.*/ -int -virStorageBackendCreatePloop(virConnectPtr conn ATTRIBUTE_UNUSED, - virStoragePoolObjPtr pool ATTRIBUTE_UNUSED, - virStorageVolDefPtr vol, - virStorageVolDefPtr inputvol, - unsigned int flags) -{ - int ret = -1; - virCommandPtr cmd = NULL; - char *create_tool = NULL; - bool created = false; - - virCheckFlags(0, -1); - - if (inputvol && inputvol->target.format != VIR_STORAGE_FILE_PLOOP) { - virReportError(VIR_ERR_INTERNAL_ERROR, - _("unsupported input storage vol type %d"), - inputvol->target.format); - return -1; - } - - if (vol->target.encryption != NULL) { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", - _("encrypted ploop volumes are not supported with " - "ploop init")); - return -1; - } - - if (vol->target.backingStore != NULL) { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", - _("copy-on-write ploop volumes are not yet supported")); - return -1; - } + if (!report) + return NULL; - create_tool = virFindFileInPath("ploop"); - if (!create_tool && !inputvol) { + if (type == VIR_STORAGE_TYPE_NETWORK) { virReportError(VIR_ERR_INTERNAL_ERROR, - "%s", _("unable to find ploop, please install " - "ploop tools")); - return -1; - } - - if (!inputvol) { - if ((virDirCreate(vol->target.path, - (vol->target.perms->mode == (mode_t) -1 ? - VIR_STORAGE_DEFAULT_VOL_PERM_MODE: - vol->target.perms->mode), - vol->target.perms->uid, - vol->target.perms->gid, - 0)) < 0) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("error creating directory for ploop volume")); - goto cleanup; - } - cmd = virCommandNewArgList(create_tool, "init", "-s", NULL); - virCommandAddArgFormat(cmd, "%lluM", VIR_DIV_UP(vol->target.capacity, - (1024 * 1024))); - virCommandAddArgList(cmd, "-t", "ext4", NULL); - virCommandAddArgFormat(cmd, "%s/root.hds", vol->target.path); - - } else { - vol->target.capacity = inputvol->target.capacity; - cmd = virCommandNewArgList("cp", "-r", inputvol->target.path, - vol->target.path, NULL); - } - created = true; - ret = virCommandRun(cmd, NULL); - cleanup: - virCommandFree(cmd); - VIR_FREE(create_tool); - if (ret < 0 && created) - virFileDeleteTree(vol->target.path); - return ret; -} - -int -virStoragePloopResize(virStorageVolDefPtr vol, - unsigned long long capacity) -{ - int ret = -1; - virCommandPtr cmd = NULL; - char *resize_tool = NULL; - - resize_tool = virFindFileInPath("ploop"); - if (!resize_tool) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("unable to find ploop, please install ploop tools")); - return -1; - } - cmd = virCommandNewArgList(resize_tool, "resize", "-s", NULL); - virCommandAddArgFormat(cmd, "%lluM", VIR_DIV_UP(capacity, (1024 * 1024))); - - virCommandAddArgFormat(cmd, "%s/DiskDescriptor.xml", vol->target.path); - - ret = virCommandRun(cmd, NULL); - virCommandFree(cmd); - VIR_FREE(resize_tool); - return ret; -} - -/* Flag values shared w/ storagevolxml2argvtest.c. - * - * QEMU_IMG_BACKING_FORMAT_OPTIONS (added in qemu 0.11) - * QEMU_IMG_BACKING_FORMAT_OPTIONS_COMPAT - * was made necessary due to 2.0 change to change the default - * qcow2 file format from 0.10 to 1.1. - */ -enum { - QEMU_IMG_BACKING_FORMAT_OPTIONS = 0, - QEMU_IMG_BACKING_FORMAT_OPTIONS_COMPAT, -}; - -static bool -virStorageBackendQemuImgSupportsCompat(const char *qemuimg) -{ - bool ret = false; - char *output; - virCommandPtr cmd = NULL; - - cmd = virCommandNewArgList(qemuimg, "create", "-o", "?", "-f", "qcow2", - "/dev/null", NULL); - - virCommandAddEnvString(cmd, "LC_ALL=C"); - virCommandSetOutputBuffer(cmd, &output); - - if (virCommandRun(cmd, NULL) < 0) - goto cleanup; - - if (strstr(output, "\ncompat ")) - ret = true; - - cleanup: - virCommandFree(cmd); - VIR_FREE(output); - return ret; -} - - -static int -virStorageBackendQEMUImgBackingFormat(const char *qemuimg) -{ - /* As of QEMU 0.11 the [-o options] support was added via qemu - * commit id '9ea2ea71', so we start with that base and figure - * out what else we have */ - int ret = QEMU_IMG_BACKING_FORMAT_OPTIONS; - - /* QEMU 2.0 changed to using a format that only QEMU 1.1 and newer - * understands. Since we still support QEMU 0.12 and newer, we need - * to be able to handle the previous format as can be set via a - * compat=0.10 option. */ - if (virStorageBackendQemuImgSupportsCompat(qemuimg)) - ret = QEMU_IMG_BACKING_FORMAT_OPTIONS_COMPAT; - - return ret; -} - -/* The _virStorageBackendQemuImgInfo separates the command line building from - * the volume definition so that qemuDomainSnapshotCreateInactiveExternal can - * use it without needing to deal with a volume. - */ -struct _virStorageBackendQemuImgInfo { - int format; - const char *path; - unsigned long long size_arg; - bool encryption; - bool preallocate; - const char *compat; - virBitmapPtr features; - bool nocow; - - const char *backingPath; - int backingFormat; - - const char *inputPath; - const char *inputFormatStr; - int inputFormat; - - char *secretAlias; - const char *secretPath; -}; - - -static int -virStorageBackendCreateQemuImgOpts(virStorageEncryptionInfoDefPtr enc, - char **opts, - struct _virStorageBackendQemuImgInfo info) -{ - virBuffer buf = VIR_BUFFER_INITIALIZER; - - if (info.format == VIR_STORAGE_FILE_RAW && enc) { - virQEMUBuildLuksOpts(&buf, enc, info.secretAlias); - } else { - if (info.backingPath) - virBufferAsprintf(&buf, "backing_fmt=%s,", - virStorageFileFormatTypeToString(info.backingFormat)); - if (info.encryption) - virBufferAddLit(&buf, "encryption=on,"); - if (info.preallocate) - virBufferAddLit(&buf, "preallocation=metadata,"); - } - - if (info.nocow) - virBufferAddLit(&buf, "nocow=on,"); - - if (info.compat) - virBufferAsprintf(&buf, "compat=%s,", info.compat); - - if (info.features && info.format == VIR_STORAGE_FILE_QCOW2) { - if (virBitmapIsBitSet(info.features, - VIR_STORAGE_FILE_FEATURE_LAZY_REFCOUNTS)) { - if (STREQ_NULLABLE(info.compat, "0.10")) { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, - _("lazy_refcounts not supported with compat" - " level %s"), - info.compat); - goto error; - } - virBufferAddLit(&buf, "lazy_refcounts,"); - } - } - - virBufferTrim(&buf, ",", -1); - - if (virBufferCheckError(&buf) < 0) - goto error; - - *opts = virBufferContentAndReset(&buf); - return 0; - - error: - virBufferFreeAndReset(&buf); - return -1; -} - - -/* virStorageBackendCreateQemuImgCheckEncryption: - * @format: format of file found - * @conn: pointer to connection - * @vol: pointer to volume def - * - * Ensure the proper setup for encryption. - * - * Returns 0 on success, -1 on failure w/ error set - */ -static int -virStorageBackendCreateQemuImgCheckEncryption(int format, - const char *type, - virConnectPtr conn, - virStorageVolDefPtr vol) -{ - virStorageEncryptionPtr enc = vol->target.encryption; - - if (format == VIR_STORAGE_FILE_QCOW || format == VIR_STORAGE_FILE_QCOW2) { - if (enc->format != VIR_STORAGE_ENCRYPTION_FORMAT_QCOW && - enc->format != VIR_STORAGE_ENCRYPTION_FORMAT_DEFAULT) { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, - _("unsupported volume encryption format %d"), - vol->target.encryption->format); - return -1; - } - if (enc->nsecrets > 1) { - virReportError(VIR_ERR_XML_ERROR, "%s", - _("too many secrets for qcow encryption")); - return -1; - } - if (enc->format == VIR_STORAGE_ENCRYPTION_FORMAT_DEFAULT || - enc->nsecrets == 0) { - if (virStorageGenerateQcowEncryption(conn, vol) < 0) - return -1; - } - } else if (format == VIR_STORAGE_FILE_RAW) { - if (enc->format != VIR_STORAGE_ENCRYPTION_FORMAT_LUKS) { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, - _("unsupported volume encryption format %d"), - vol->target.encryption->format); - return -1; - } - if (enc->nsecrets > 1) { - virReportError(VIR_ERR_XML_ERROR, "%s", - _("too many secrets for luks encryption")); - return -1; - } - if (enc->nsecrets == 0) { - virReportError(VIR_ERR_XML_ERROR, "%s", - _("no secret provided for luks encryption")); - return -1; - } - if (!virCryptoHaveCipher(VIR_CRYPTO_CIPHER_AES256CBC)) { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", - _("luks encryption usage requires encrypted " - "secret generation to be supported")); - return -1; - } + _("missing storage backend for network files " + "using %s protocol"), + virStorageNetProtocolTypeToString(protocol)); } else { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, - _("volume encryption unsupported with format %s"), type); - return -1; + virReportError(VIR_ERR_INTERNAL_ERROR, + _("missing storage backend for '%s' storage"), + virStorageTypeToString(type)); } - return 0; + return NULL; } -static int -virStorageBackendCreateQemuImgSetInput(virStorageVolDefPtr inputvol, - struct _virStorageBackendQemuImgInfo *info) -{ - if (!(info->inputPath = inputvol->target.path)) { - virReportError(VIR_ERR_INVALID_ARG, "%s", - _("missing input volume target path")); - return -1; - } - - info->inputFormat = inputvol->target.format; - if (inputvol->type == VIR_STORAGE_VOL_BLOCK) - info->inputFormat = VIR_STORAGE_FILE_RAW; - if (!(info->inputFormatStr = - virStorageFileFormatTypeToString(info->inputFormat))) { - virReportError(VIR_ERR_INTERNAL_ERROR, - _("unknown storage vol type %d"), - info->inputFormat); - return -1; - } - - return 0; -} - - -static int -virStorageBackendCreateQemuImgSetBacking(virStoragePoolObjPtr pool, - virStorageVolDefPtr vol, - virStorageVolDefPtr inputvol, - struct _virStorageBackendQemuImgInfo *info) -{ - int accessRetCode = -1; - char *absolutePath = NULL; - - if (info->format == VIR_STORAGE_FILE_RAW) { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", - _("cannot set backing store for raw volume")); - return -1; - } - - info->backingFormat = vol->target.backingStore->format; - info->backingPath = vol->target.backingStore->path; - - if (info->preallocate) { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", - _("metadata preallocation conflicts with backing" - " store")); - return -1; - } - - /* XXX: Not strictly required: qemu-img has an option a different - * backing store, not really sure what use it serves though, and it - * may cause issues with lvm. Untested essentially. - */ - if (inputvol && inputvol->target.backingStore && - STRNEQ_NULLABLE(inputvol->target.backingStore->path, - info->backingPath)) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("a different backing store cannot be specified.")); - return -1; - } - - if (!virStorageFileFormatTypeToString(info->backingFormat)) { - virReportError(VIR_ERR_INTERNAL_ERROR, - _("unknown storage vol backing store type %d"), - info->backingFormat); - return -1; - } - - /* Convert relative backing store paths to absolute paths for access - * validation. - */ - if ('/' != *(info->backingPath) && - virAsprintf(&absolutePath, "%s/%s", pool->def->target.path, - info->backingPath) < 0) - return -1; - accessRetCode = access(absolutePath ? absolutePath : - info->backingPath, R_OK); - VIR_FREE(absolutePath); - if (accessRetCode != 0) { - virReportSystemError(errno, - _("inaccessible backing store volume %s"), - info->backingPath); - return -1; - } - - return 0; -} - - -static int -virStorageBackendCreateQemuImgSetOptions(virCommandPtr cmd, - int imgformat, - virStorageEncryptionInfoDefPtr enc, - struct _virStorageBackendQemuImgInfo info) -{ - char *opts = NULL; - - if (info.format == VIR_STORAGE_FILE_QCOW2 && !info.compat && - imgformat >= QEMU_IMG_BACKING_FORMAT_OPTIONS_COMPAT) - info.compat = "0.10"; - - if (virStorageBackendCreateQemuImgOpts(enc, &opts, info) < 0) - return -1; - if (opts) - virCommandAddArgList(cmd, "-o", opts, NULL); - VIR_FREE(opts); - - return 0; -} - - -/* Add a secret object to the command line: - * --object secret,id=$secretAlias,file=$secretPath - * - * NB: format=raw is assumed - */ -static int -virStorageBackendCreateQemuImgSecretObject(virCommandPtr cmd, - virStorageVolDefPtr vol, - struct _virStorageBackendQemuImgInfo *info) -{ - virBuffer buf = VIR_BUFFER_INITIALIZER; - char *commandStr = NULL; - - if (virAsprintf(&info->secretAlias, "%s_luks0", vol->name) < 0) - return -1; - - virBufferAsprintf(&buf, "secret,id=%s,file=", info->secretAlias); - virQEMUBuildBufferEscapeComma(&buf, info->secretPath); - - if (virBufferCheckError(&buf) < 0) { - virBufferFreeAndReset(&buf); - return -1; - } - - commandStr = virBufferContentAndReset(&buf); - - virCommandAddArgList(cmd, "--object", commandStr, NULL); - - VIR_FREE(commandStr); - return 0; -} - - -/* Create a qemu-img virCommand from the supplied binary path, - * volume definitions and imgformat - */ -virCommandPtr -virStorageBackendCreateQemuImgCmdFromVol(virConnectPtr conn, - virStoragePoolObjPtr pool, - virStorageVolDefPtr vol, - virStorageVolDefPtr inputvol, - unsigned int flags, - const char *create_tool, - int imgformat, - const char *secretPath) -{ - virCommandPtr cmd = NULL; - const char *type; - struct _virStorageBackendQemuImgInfo info = { - .format = vol->target.format, - .path = vol->target.path, - .encryption = vol->target.encryption != NULL, - .preallocate = !!(flags & VIR_STORAGE_VOL_CREATE_PREALLOC_METADATA), - .compat = vol->target.compat, - .features = vol->target.features, - .nocow = vol->target.nocow, - .secretPath = secretPath, - .secretAlias = NULL, - }; - virStorageEncryptionInfoDefPtr enc = NULL; - - virCheckFlags(VIR_STORAGE_VOL_CREATE_PREALLOC_METADATA, NULL); - - /* Treat output block devices as 'raw' format */ - if (vol->type == VIR_STORAGE_VOL_BLOCK) - info.format = VIR_STORAGE_FILE_RAW; - - if (!(type = virStorageFileFormatTypeToString(info.format))) { - virReportError(VIR_ERR_INTERNAL_ERROR, - _("unknown storage vol type %d"), - info.format); - return NULL; - } - - if (info.preallocate && info.format != VIR_STORAGE_FILE_QCOW2) { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", - _("metadata preallocation only available with qcow2")); - return NULL; - } - if (info.compat && info.format != VIR_STORAGE_FILE_QCOW2) { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", - _("compatibility option only available with qcow2")); - return NULL; - } - if (info.features && info.format != VIR_STORAGE_FILE_QCOW2) { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", - _("format features only available with qcow2")); - return NULL; - } - if (info.format == VIR_STORAGE_FILE_RAW && - vol->target.encryption != NULL) { - if (inputvol) { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", - _("cannot use inputvol with encrypted raw volume")); - return NULL; - } - if (!info.encryption) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("missing encryption description")); - return NULL; - } - if (vol->target.encryption->format == VIR_STORAGE_ENCRYPTION_FORMAT_LUKS) { - type = "luks"; - } else { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("Only luks encryption is supported for raw files")); - return NULL; - } - } - - if (inputvol && - virStorageBackendCreateQemuImgSetInput(inputvol, &info) < 0) - return NULL; - - if (vol->target.backingStore && - virStorageBackendCreateQemuImgSetBacking(pool, vol, inputvol, - &info) < 0) - return NULL; - - if (info.encryption && - virStorageBackendCreateQemuImgCheckEncryption(info.format, type, - conn, vol) < 0) - return NULL; - - - /* Size in KB */ - info.size_arg = VIR_DIV_UP(vol->target.capacity, 1024); - - cmd = virCommandNew(create_tool); - - /* ignore the backing volume when we're converting a volume */ - if (info.inputPath) - info.backingPath = NULL; - - if (info.inputPath) - virCommandAddArgList(cmd, "convert", "-f", info.inputFormatStr, - "-O", type, NULL); - else - virCommandAddArgList(cmd, "create", "-f", type, NULL); - - if (info.backingPath) - virCommandAddArgList(cmd, "-b", info.backingPath, NULL); - - if (info.format == VIR_STORAGE_FILE_RAW && - vol->target.encryption != NULL && - vol->target.encryption->format == VIR_STORAGE_ENCRYPTION_FORMAT_LUKS) { - if (virStorageBackendCreateQemuImgSecretObject(cmd, vol, &info) < 0) { - VIR_FREE(info.secretAlias); - virCommandFree(cmd); - return NULL; - } - enc = &vol->target.encryption->encinfo; - } - - if (virStorageBackendCreateQemuImgSetOptions(cmd, imgformat, - enc, info) < 0) { - VIR_FREE(info.secretAlias); - virCommandFree(cmd); - return NULL; - } - VIR_FREE(info.secretAlias); - - if (info.inputPath) - virCommandAddArg(cmd, info.inputPath); - virCommandAddArg(cmd, info.path); - if (!info.inputPath && (info.size_arg || !info.backingPath)) - virCommandAddArgFormat(cmd, "%lluK", info.size_arg); - - return cmd; -} - - -static char * -virStorageBackendCreateQemuImgSecretPath(virConnectPtr conn, - virStoragePoolObjPtr pool, - virStorageVolDefPtr vol) -{ - virStorageEncryptionPtr enc = vol->target.encryption; - char *secretPath = NULL; - int fd = -1; - uint8_t *secret = NULL; - size_t secretlen = 0; - - if (!enc) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("missing encryption description")); - return NULL; - } - - if (!conn || !conn->secretDriver || - !conn->secretDriver->secretLookupByUUID || - !conn->secretDriver->secretLookupByUsage || - !conn->secretDriver->secretGetValue) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("unable to look up encryption secret")); - return NULL; - } - - if (!(secretPath = virStoragePoolObjBuildTempFilePath(pool, vol))) - goto cleanup; - - if ((fd = mkostemp(secretPath, O_CLOEXEC)) < 0) { - virReportSystemError(errno, "%s", - _("failed to open luks secret file for write")); - goto error; - } - - if (virSecretGetSecretString(conn, &enc->secrets[0]->seclookupdef, - VIR_SECRET_USAGE_TYPE_VOLUME, - &secret, &secretlen) < 0) - goto error; - - if (safewrite(fd, secret, secretlen) < 0) { - virReportSystemError(errno, "%s", - _("failed to write luks secret file")); - goto error; - } - VIR_FORCE_CLOSE(fd); - - if ((vol->target.perms->uid != (uid_t) -1) && - (vol->target.perms->gid != (gid_t) -1)) { - if (chown(secretPath, vol->target.perms->uid, - vol->target.perms->gid) < 0) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("failed to chown luks secret file")); - goto error; - } - } - - cleanup: - VIR_DISPOSE_N(secret, secretlen); - VIR_FORCE_CLOSE(fd); - - return secretPath; - - error: - unlink(secretPath); - VIR_FREE(secretPath); - goto cleanup; -} - - -int -virStorageBackendCreateQemuImg(virConnectPtr conn, - virStoragePoolObjPtr pool, - virStorageVolDefPtr vol, - virStorageVolDefPtr inputvol, - unsigned int flags) -{ - int ret = -1; - char *create_tool; - int imgformat; - virCommandPtr cmd; - char *secretPath = NULL; - - virCheckFlags(VIR_STORAGE_VOL_CREATE_PREALLOC_METADATA, -1); - - create_tool = virFindFileInPath("qemu-img"); - if (!create_tool) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("creation of non-raw file images is " - "not supported without qemu-img.")); - return -1; - } - - imgformat = virStorageBackendQEMUImgBackingFormat(create_tool); - if (imgformat < 0) - goto cleanup; - - if (vol->target.format == VIR_STORAGE_FILE_RAW && - vol->target.encryption && - vol->target.encryption->format == VIR_STORAGE_ENCRYPTION_FORMAT_LUKS) { - if (!(secretPath = - virStorageBackendCreateQemuImgSecretPath(conn, pool, vol))) - goto cleanup; - } - - cmd = virStorageBackendCreateQemuImgCmdFromVol(conn, pool, vol, inputvol, - flags, create_tool, - imgformat, secretPath); - if (!cmd) - goto cleanup; - - ret = virStorageBackendCreateExecCommand(pool, vol, cmd); - - virCommandFree(cmd); - cleanup: - if (secretPath) { - unlink(secretPath); - VIR_FREE(secretPath); - } - VIR_FREE(create_tool); - return ret; -} - -virStorageBackendBuildVolFrom -virStorageBackendGetBuildVolFromFunction(virStorageVolDefPtr vol, - virStorageVolDefPtr inputvol) -{ - if (!inputvol) - return NULL; - - /* If either volume is a non-raw file vol, or uses encryption, - * we need to use an external tool for converting - */ - if ((vol->type == VIR_STORAGE_VOL_FILE && - (vol->target.format != VIR_STORAGE_FILE_RAW || - vol->target.encryption != NULL)) || - (inputvol->type == VIR_STORAGE_VOL_FILE && - (inputvol->target.format != VIR_STORAGE_FILE_RAW || - inputvol->target.encryption != NULL))) { - return virStorageBackendCreateQemuImg; - } - - if (vol->type == VIR_STORAGE_VOL_PLOOP) - return virStorageBackendCreatePloop; - if (vol->type == VIR_STORAGE_VOL_BLOCK) - return virStorageBackendCreateBlockFrom; - else - return virStorageBackendCreateRaw; -} - - -virStorageBackendPtr -virStorageBackendForType(int type) -{ - size_t i; - for (i = 0; backends[i]; i++) - if (backends[i]->type == type) - return backends[i]; - - virReportError(VIR_ERR_INTERNAL_ERROR, - _("missing backend for pool type %d (%s)"), - type, NULLSTR(virStoragePoolTypeToString(type))); - return NULL; -} - - -virStorageFileBackendPtr -virStorageFileBackendForTypeInternal(int type, - int protocol, - bool report) -{ - size_t i; - - for (i = 0; fileBackends[i]; i++) { - if (fileBackends[i]->type == type) { - if (type == VIR_STORAGE_TYPE_NETWORK && - fileBackends[i]->protocol != protocol) - continue; - - return fileBackends[i]; - } - } - - if (!report) - return NULL; - - if (type == VIR_STORAGE_TYPE_NETWORK) { - virReportError(VIR_ERR_INTERNAL_ERROR, - _("missing storage backend for network files " - "using %s protocol"), - virStorageNetProtocolTypeToString(protocol)); - } else { - virReportError(VIR_ERR_INTERNAL_ERROR, - _("missing storage backend for '%s' storage"), - virStorageTypeToString(type)); - } - - return NULL; -} - - -virStorageFileBackendPtr -virStorageFileBackendForType(int type, - int protocol) +virStorageFileBackendPtr +virStorageFileBackendForType(int type, + int protocol) { return virStorageFileBackendForTypeInternal(type, protocol, true); } - - -struct diskType { - int part_table_type; - unsigned short offset; - unsigned short length; - unsigned long long magic; -}; - - -static struct diskType const disk_types[] = { - { VIR_STORAGE_POOL_DISK_LVM2, 0x218, 8, 0x31303020324D564CULL }, - { VIR_STORAGE_POOL_DISK_GPT, 0x200, 8, 0x5452415020494645ULL }, - { VIR_STORAGE_POOL_DISK_DVH, 0x0, 4, 0x41A9E50BULL }, - { VIR_STORAGE_POOL_DISK_MAC, 0x0, 2, 0x5245ULL }, - { VIR_STORAGE_POOL_DISK_BSD, 0x40, 4, 0x82564557ULL }, - { VIR_STORAGE_POOL_DISK_SUN, 0x1fc, 2, 0xBEDAULL }, - /* - * NOTE: pc98 is funky; the actual signature is 0x55AA (just like dos), so - * we can't use that. At the moment I'm relying on the "dummy" IPL - * bootloader data that comes from parted. Luckily, the chances of running - * into a pc98 machine running libvirt are approximately nil. - */ - /*{ 0x1fe, 2, 0xAA55UL },*/ - { VIR_STORAGE_POOL_DISK_PC98, 0x0, 8, 0x314C5049000000CBULL }, - /* - * NOTE: the order is important here; some other disk types (like GPT and - * and PC98) also have 0x55AA at this offset. For that reason, the DOS - * one must be the last one. - */ - { VIR_STORAGE_POOL_DISK_DOS, 0x1fe, 2, 0xAA55ULL }, - { -1, 0x0, 0, 0x0ULL }, -}; - - -/* - * virStorageBackendDetectBlockVolFormatFD - * @target: target definition ptr of volume to update - * @fd: fd of storage volume to update, - * @readflags: VolReadErrorMode flags to handle read error after open - * is successful, but read is not. - * - * Returns 0 for success, -1 on a legitimate error condition, -2 if - * the read error is desired to be ignored (along with appropriate - * VIR_WARN of the issue). - */ -static int -virStorageBackendDetectBlockVolFormatFD(virStorageSourcePtr target, - int fd, - unsigned int readflags) -{ - size_t i; - off_t start; - unsigned char buffer[1024]; - ssize_t bytes; - - /* make sure to set the target format "unknown" to begin with */ - target->format = VIR_STORAGE_POOL_DISK_UNKNOWN; - - start = lseek(fd, 0, SEEK_SET); - if (start < 0) { - virReportSystemError(errno, - _("cannot seek to beginning of file '%s'"), - target->path); - return -1; - } - bytes = saferead(fd, buffer, sizeof(buffer)); - if (bytes < 0) { - if (readflags & VIR_STORAGE_VOL_READ_NOERROR) { - VIR_WARN("ignoring failed saferead of file '%s'", - target->path); - return -2; - } else { - virReportSystemError(errno, - _("cannot read beginning of file '%s'"), - target->path); - return -1; - } - } - - for (i = 0; disk_types[i].part_table_type != -1; i++) { - if (disk_types[i].offset + disk_types[i].length > bytes) - continue; - if (memcmp(buffer+disk_types[i].offset, &disk_types[i].magic, - disk_types[i].length) == 0) { - target->format = disk_types[i].part_table_type; - break; - } - } - - if (target->format == VIR_STORAGE_POOL_DISK_UNKNOWN) - VIR_DEBUG("cannot determine the target format for '%s'", - target->path); - - return 0; -} - - -/* - * Allows caller to silently ignore files with improper mode - * - * Returns -1 on error. If VIR_STORAGE_VOL_OPEN_NOERROR is passed, we - * return -2 if file mode is unexpected or the volume is a dangling - * symbolic link. - */ -int -virStorageBackendVolOpen(const char *path, struct stat *sb, - unsigned int flags) -{ - int fd, mode = 0; - char *base = last_component(path); - bool noerror = (flags & VIR_STORAGE_VOL_OPEN_NOERROR); - - if (lstat(path, sb) < 0) { - if (errno == ENOENT) { - if (noerror) { - VIR_WARN("ignoring missing file '%s'", path); - return -2; - } - virReportError(VIR_ERR_NO_STORAGE_VOL, - _("no storage vol with matching path '%s'"), - path); - return -1; - } - virReportSystemError(errno, - _("cannot stat file '%s'"), - path); - return -1; - } - - if (S_ISFIFO(sb->st_mode)) { - if (noerror) { - VIR_WARN("ignoring FIFO '%s'", path); - return -2; - } - virReportError(VIR_ERR_INTERNAL_ERROR, - _("Volume path '%s' is a FIFO"), path); - return -1; - } else if (S_ISSOCK(sb->st_mode)) { - if (noerror) { - VIR_WARN("ignoring socket '%s'", path); - return -2; - } - virReportError(VIR_ERR_INTERNAL_ERROR, - _("Volume path '%s' is a socket"), path); - return -1; - } - - /* O_NONBLOCK should only matter during open() for fifos and - * sockets, which we already filtered; but using it prevents a - * TOCTTOU race. However, later on we will want to read() the - * header from this fd, and virFileRead* routines require a - * blocking fd, so fix it up after verifying we avoided a race. - * - * Use of virFileOpenAs allows this path to open a file using - * the uid and gid as it was created in order to open. Since this - * path is not using O_CREAT or O_TMPFILE, mode is meaningless. - * Opening under user/group is especially important in an NFS - * root-squash environment. If the target path isn't on shared - * file system, the open will fail in the OPEN_FORK path. - */ - if ((fd = virFileOpenAs(path, O_RDONLY|O_NONBLOCK|O_NOCTTY, - 0, sb->st_uid, sb->st_gid, - VIR_FILE_OPEN_NOFORK|VIR_FILE_OPEN_FORK)) < 0) { - if ((errno == ENOENT || errno == ELOOP) && - S_ISLNK(sb->st_mode) && noerror) { - VIR_WARN("ignoring dangling symlink '%s'", path); - return -2; - } - if (errno == ENOENT && noerror) { - VIR_WARN("ignoring missing file '%s'", path); - return -2; - } - if (errno == ENXIO && noerror) { - VIR_WARN("ignoring missing fifo '%s'", path); - return -2; - } - if ((errno == EACCES || errno == EPERM) && noerror) { - VIR_WARN("ignoring permission error for '%s'", path); - return -2; - } - - virReportSystemError(errno, _("cannot open volume '%s'"), path); - return -1; - } - - if (fstat(fd, sb) < 0) { - virReportSystemError(errno, _("cannot stat file '%s'"), path); - VIR_FORCE_CLOSE(fd); - return -1; - } - - if (S_ISREG(sb->st_mode)) { - mode = VIR_STORAGE_VOL_OPEN_REG; - } else if (S_ISCHR(sb->st_mode)) { - mode = VIR_STORAGE_VOL_OPEN_CHAR; - } else if (S_ISBLK(sb->st_mode)) { - mode = VIR_STORAGE_VOL_OPEN_BLOCK; - } else if (S_ISDIR(sb->st_mode)) { - mode = VIR_STORAGE_VOL_OPEN_DIR; - - if (STREQ(base, ".") || - STREQ(base, "..")) { - VIR_FORCE_CLOSE(fd); - if (noerror) { - VIR_INFO("Skipping special dir '%s'", base); - return -2; - } - virReportError(VIR_ERR_INTERNAL_ERROR, - _("Cannot use volume path '%s'"), path); - return -1; - } - } else { - VIR_FORCE_CLOSE(fd); - if (noerror) { - VIR_WARN("ignoring unexpected type for file '%s'", path); - return -2; - } - virReportError(VIR_ERR_INTERNAL_ERROR, - _("unexpected type for file '%s'"), path); - return -1; - } - - if (virSetBlocking(fd, true) < 0) { - VIR_FORCE_CLOSE(fd); - virReportSystemError(errno, _("unable to set blocking mode for '%s'"), - path); - return -1; - } - - if (!(mode & flags)) { - VIR_FORCE_CLOSE(fd); - if (noerror) { - VIR_INFO("Skipping volume '%s'", path); - return -2; - } - - virReportError(VIR_ERR_INTERNAL_ERROR, - _("unexpected storage mode for '%s'"), path); - return -1; - } - - return fd; -} - -/* virStorageIsPloop function checks whether given directory is ploop volume's - * directory. - */ -bool -virStorageBackendIsPloopDir(char *path) -{ - bool ret = false; - char *root = NULL; - char *desc = NULL; - if (virAsprintf(&root, "%s/root.hds", path) < 0) - return ret; - if (!virFileExists(root)) - goto cleanup; - if (virAsprintf(&desc, "%s/DiskDescriptor.xml", path) < 0) - goto cleanup; - if (!virFileExists(desc)) - goto cleanup; - - ret = true; - cleanup: - VIR_FREE(root); - VIR_FREE(desc); - return ret; -} - -/* In case of ploop volumes, path to volume is the path to the ploop - * directory. To get information about allocation, header information - * and etc. we need to perform virStorageBackendVolOpen and - * virStorageBackendUpdateVolTargetFd once again. - */ -int -virStorageBackendRedoPloopUpdate(virStorageSourcePtr target, struct stat *sb, - int *fd, unsigned int flags) -{ - char *path = NULL; - int ret = -1; - - if (virAsprintf(&path, "%s/root.hds", target->path) < 0) - return -1; - VIR_FORCE_CLOSE(*fd); - if ((*fd = virStorageBackendVolOpen(path, sb, flags)) < 0) - goto cleanup; - ret = virStorageBackendUpdateVolTargetInfoFD(target, *fd, sb); - - cleanup: - - VIR_FREE(path); - return ret; -} - -/* - * virStorageBackendUpdateVolTargetInfo - * @target: target definition ptr of volume to update - * @withBlockVolFormat: true if caller determined a block file - * @openflags: various VolOpenCheckMode flags to handle errors on open - * @readflags: VolReadErrorMode flags to handle read error after open - * is successful, but read is not. - * - * Returns 0 for success, -1 on a legitimate error condition, and -2 - * if the openflags used VIR_STORAGE_VOL_OPEN_NOERROR and some sort of - * open error occurred. It is up to the caller to handle. A -2 may also - * be returned if the caller passed a readflagsflag. - */ -int -virStorageBackendUpdateVolTargetInfo(virStorageSourcePtr target, - bool withBlockVolFormat, - unsigned int openflags, - unsigned int readflags) -{ - int ret, fd = -1; - struct stat sb; - char *buf = NULL; - ssize_t len = VIR_STORAGE_MAX_HEADER; - - if ((ret = virStorageBackendVolOpen(target->path, &sb, openflags)) < 0) - goto cleanup; - fd = ret; - - if ((ret = virStorageBackendUpdateVolTargetInfoFD(target, fd, &sb)) < 0) - goto cleanup; - - if (target->type == VIR_STORAGE_VOL_FILE && - target->format != VIR_STORAGE_FILE_NONE) { - if (S_ISDIR(sb.st_mode)) { - if (virStorageBackendIsPloopDir(target->path)) { - if ((ret = virStorageBackendRedoPloopUpdate(target, &sb, &fd, - openflags)) < 0) - goto cleanup; - target->format = VIR_STORAGE_FILE_PLOOP; - } else { - ret = 0; - goto cleanup; - } - } - - if (lseek(fd, 0, SEEK_SET) == (off_t)-1) { - virReportSystemError(errno, _("cannot seek to start of '%s'"), target->path); - ret = -1; - goto cleanup; - } - - if ((len = virFileReadHeaderFD(fd, len, &buf)) < 0) { - if (readflags & VIR_STORAGE_VOL_READ_NOERROR) { - VIR_WARN("ignoring failed header read for '%s'", - target->path); - ret = -2; - } else { - virReportSystemError(errno, - _("cannot read header '%s'"), - target->path); - ret = -1; - } - goto cleanup; - } - - if (virStorageSourceUpdateCapacity(target, buf, len, false) < 0) { - ret = -1; - goto cleanup; - } - } - - if (withBlockVolFormat) { - if ((ret = virStorageBackendDetectBlockVolFormatFD(target, fd, - readflags)) < 0) - goto cleanup; - } - - cleanup: - VIR_FORCE_CLOSE(fd); - VIR_FREE(buf); - return ret; -} - -/* - * virStorageBackendUpdateVolInfo - * @vol: Pointer to a volume storage definition - * @withBlockVolFormat: true if the caller determined a block file - * @openflags: various VolOpenCheckMode flags to handle errors on open - * @readflags: various VolReadErrorMode flags to handle errors on read - * - * Returns 0 for success, -1 on a legitimate error condition, and -2 - * if the openflags used VIR_STORAGE_VOL_OPEN_NOERROR and some sort of - * open error occurred. It is up to the caller to handle. - */ -int -virStorageBackendUpdateVolInfo(virStorageVolDefPtr vol, - bool withBlockVolFormat, - unsigned int openflags, - unsigned int readflags) -{ - int ret; - - if ((ret = virStorageBackendUpdateVolTargetInfo(&vol->target, - withBlockVolFormat, - openflags, readflags)) < 0) - return ret; - - if (vol->target.backingStore && - (ret = virStorageBackendUpdateVolTargetInfo(vol->target.backingStore, - withBlockVolFormat, - VIR_STORAGE_VOL_OPEN_DEFAULT | - VIR_STORAGE_VOL_OPEN_NOERROR, - readflags) < 0)) - return ret; - - return 0; -} - -/* - * virStorageBackendUpdateVolTargetInfoFD: - * @target: target definition ptr of volume to update - * @fd: fd of storage volume to update, via virStorageBackendOpenVol*, or -1 - * @sb: details about file (must match @fd, if that is provided) - * - * Returns 0 for success, -1 on a legitimate error condition. - */ -int -virStorageBackendUpdateVolTargetInfoFD(virStorageSourcePtr target, - int fd, - struct stat *sb) -{ -#if WITH_SELINUX - security_context_t filecon = NULL; -#endif - - if (virStorageSourceUpdateBackingSizes(target, fd, sb) < 0) - return -1; - - if (!target->perms && VIR_ALLOC(target->perms) < 0) - return -1; - target->perms->mode = sb->st_mode & S_IRWXUGO; - target->perms->uid = sb->st_uid; - target->perms->gid = sb->st_gid; - - if (!target->timestamps && VIR_ALLOC(target->timestamps) < 0) - return -1; - target->timestamps->atime = get_stat_atime(sb); - target->timestamps->btime = get_stat_birthtime(sb); - target->timestamps->ctime = get_stat_ctime(sb); - target->timestamps->mtime = get_stat_mtime(sb); - - VIR_FREE(target->perms->label); - -#if WITH_SELINUX - /* XXX: make this a security driver call */ - if (fd >= 0) { - if (fgetfilecon_raw(fd, &filecon) == -1) { - if (errno != ENODATA && errno != ENOTSUP) { - virReportSystemError(errno, - _("cannot get file context of '%s'"), - target->path); - return -1; - } - } else { - if (VIR_STRDUP(target->perms->label, filecon) < 0) { - freecon(filecon); - return -1; - } - freecon(filecon); - } - } -#endif - - return 0; -} - -bool -virStorageBackendPoolPathIsStable(const char *path) -{ - if (path == NULL || STREQ(path, "/dev") || STREQ(path, "/dev/")) - return false; - - if (!STRPREFIX(path, "/dev/")) - return false; - - return true; -} - -/* - * Given a volume path directly in /dev/XXX, iterate over the - * entries in the directory pool->def->target.path and find the - * first symlink pointing to the volume path. - * - * If, the target.path is /dev/, then return the original volume - * path. - * - * If no symlink is found, then return the original volume path - * - * Typically target.path is one of the /dev/disk/by-XXX dirs - * with stable paths. - * - * If 'loop' is true, we use a timeout loop to give dynamic paths - * a change to appear. - */ -char * -virStorageBackendStablePath(virStoragePoolObjPtr pool, - const char *devpath, - bool loop) -{ - DIR *dh; - struct dirent *dent; - char *stablepath; - int opentries = 0; - int retry = 0; - int direrr; - - /* Logical pools are under /dev but already have stable paths */ - if (pool->def->type == VIR_STORAGE_POOL_LOGICAL || - !virStorageBackendPoolPathIsStable(pool->def->target.path)) - goto ret_strdup; - - /* We loop here because /dev/disk/by-{id,path} may not have existed - * before we started this operation, so we have to give it some time to - * get created. - */ - reopen: - if (virDirOpenQuiet(&dh, pool->def->target.path) < 0) { - opentries++; - if (loop && errno == ENOENT && opentries < 50) { - usleep(100 * 1000); - goto reopen; - } - virReportSystemError(errno, - _("cannot read dir '%s'"), - pool->def->target.path); - return NULL; - } - - /* The pool is pointing somewhere like /dev/disk/by-path - * or /dev/disk/by-id, so we need to check all symlinks in - * the target directory and figure out which one points - * to this device node. - * - * And it might need some time till the stable path shows - * up, so add timeout to retry here. Ignore readdir failures, - * since we have a fallback. - */ - retry: - while ((direrr = virDirRead(dh, &dent, NULL)) > 0) { - if (virAsprintf(&stablepath, "%s/%s", - pool->def->target.path, - dent->d_name) == -1) { - VIR_DIR_CLOSE(dh); - return NULL; - } - - if (virFileLinkPointsTo(stablepath, devpath)) { - VIR_DIR_CLOSE(dh); - return stablepath; - } - - VIR_FREE(stablepath); - } - - if (!direrr && loop && ++retry < 100) { - usleep(100 * 1000); - goto retry; - } - - VIR_DIR_CLOSE(dh); - - ret_strdup: - /* Couldn't find any matching stable link so give back - * the original non-stable dev path - */ - - ignore_value(VIR_STRDUP(stablepath, devpath)); - - return stablepath; -} - -/* - * Check whether the ploop image has snapshots. - * return: -1 - failed to check - * 0 - no snapshots - * 1 - at least one snapshot - */ -static int -virStorageBackendPloopHasSnapshots(char *path) -{ - virCommandPtr cmd = NULL; - char *output = NULL; - char *snap_tool = NULL; - int ret = -1; - - snap_tool = virFindFileInPath("ploop"); - if (!snap_tool) { - virReportError(VIR_ERR_INTERNAL_ERROR, - "%s", _("unable to find ploop, please install " - "ploop tools")); - return ret; - } - - cmd = virCommandNewArgList(snap_tool, "snapshot-list", NULL); - virCommandAddArgFormat(cmd, "%s/DiskDescriptor.xml", path); - virCommandSetOutputBuffer(cmd, &output); - - if ((ret = virCommandRun(cmd, NULL)) < 0) - goto cleanup; - - if (!strstr(output, "root.hds.")) { - ret = 1; - goto cleanup; - } - ret = 0; - - cleanup: - VIR_FREE(output); - virCommandFree(cmd); - return ret; -} - -int -virStorageBackendVolUploadLocal(virConnectPtr conn ATTRIBUTE_UNUSED, - virStoragePoolObjPtr pool ATTRIBUTE_UNUSED, - virStorageVolDefPtr vol, - virStreamPtr stream, - unsigned long long offset, - unsigned long long len, - unsigned int flags) -{ - char *path = NULL; - char *target_path = vol->target.path; - int ret = -1; - int has_snap = 0; - - virCheckFlags(0, -1); - /* if volume has target format VIR_STORAGE_FILE_PLOOP - * we need to restore DiskDescriptor.xml, according to - * new contents of volume. This operation will be perfomed - * when volUpload is fully finished. */ - if (vol->target.format == VIR_STORAGE_FILE_PLOOP) { - /* Fail if the volume contains snapshots or we failed to check it.*/ - has_snap = virStorageBackendPloopHasSnapshots(vol->target.path); - if (has_snap < 0) { - goto cleanup; - } else if (!has_snap) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("can't upload volume, all existing snapshots" - " will be lost")); - goto cleanup; - } - - if (virAsprintf(&path, "%s/root.hds", vol->target.path) < 0) - return -1; - target_path = path; - } - - /* Not using O_CREAT because the file is required to already exist at - * this point */ - ret = virFDStreamOpenBlockDevice(stream, target_path, - offset, len, O_WRONLY); - - cleanup: - VIR_FREE(path); - return ret; -} - -int -virStorageBackendVolDownloadLocal(virConnectPtr conn ATTRIBUTE_UNUSED, - virStoragePoolObjPtr pool ATTRIBUTE_UNUSED, - virStorageVolDefPtr vol, - virStreamPtr stream, - unsigned long long offset, - unsigned long long len, - unsigned int flags) -{ - char *path = NULL; - char *target_path = vol->target.path; - int ret = -1; - int has_snap = 0; - - virCheckFlags(0, -1); - if (vol->target.format == VIR_STORAGE_FILE_PLOOP) { - has_snap = virStorageBackendPloopHasSnapshots(vol->target.path); - if (has_snap < 0) { - goto cleanup; - } else if (!has_snap) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("can't download volume, all existing snapshots" - " will be lost")); - goto cleanup; - } - if (virAsprintf(&path, "%s/root.hds", vol->target.path) < 0) - goto cleanup; - target_path = path; - } - - ret = virFDStreamOpenBlockDevice(stream, target_path, - offset, len, O_RDONLY); - - cleanup: - VIR_FREE(path); - return ret; -} - - -/* If the volume we're wiping is already a sparse file, we simply - * truncate and extend it to its original size, filling it with - * zeroes. This behavior is guaranteed by POSIX: - * - * http://www.opengroup.org/onlinepubs/9699919799/functions/ftruncate.html - * - * If fildes refers to a regular file, the ftruncate() function shall - * cause the size of the file to be truncated to length. If the size - * of the file previously exceeded length, the extra data shall no - * longer be available to reads on the file. If the file previously - * was smaller than this size, ftruncate() shall increase the size of - * the file. If the file size is increased, the extended area shall - * appear as if it were zero-filled. - */ -static int -virStorageBackendVolZeroSparseFileLocal(const char *path, - off_t size, - int fd) -{ - if (ftruncate(fd, 0) < 0) { - virReportSystemError(errno, - _("Failed to truncate volume with " - "path '%s' to 0 bytes"), - path); - return -1; - } - - if (ftruncate(fd, size) < 0) { - virReportSystemError(errno, - _("Failed to truncate volume with " - "path '%s' to %ju bytes"), - path, (uintmax_t)size); - return -1; - } - - return 0; -} - - -static int -virStorageBackendWipeLocal(const char *path, - int fd, - unsigned long long wipe_len, - size_t writebuf_length) -{ - int ret = -1, written = 0; - unsigned long long remaining = 0; - size_t write_size = 0; - char *writebuf = NULL; - - VIR_DEBUG("wiping start: 0 len: %llu", wipe_len); - - if (VIR_ALLOC_N(writebuf, writebuf_length) < 0) - goto cleanup; - - if (lseek(fd, 0, SEEK_SET) < 0) { - virReportSystemError(errno, - _("Failed to seek to the start in volume " - "with path '%s'"), - path); - goto cleanup; - } - - remaining = wipe_len; - while (remaining > 0) { - - write_size = (writebuf_length < remaining) ? writebuf_length : remaining; - written = safewrite(fd, writebuf, write_size); - if (written < 0) { - virReportSystemError(errno, - _("Failed to write %zu bytes to " - "storage volume with path '%s'"), - write_size, path); - - goto cleanup; - } - - remaining -= written; - } - - if (fdatasync(fd) < 0) { - virReportSystemError(errno, - _("cannot sync data to volume with path '%s'"), - path); - goto cleanup; - } - - VIR_DEBUG("Wrote %llu bytes to volume with path '%s'", wipe_len, path); - - ret = 0; - - cleanup: - VIR_FREE(writebuf); - return ret; -} - - -static int -virStorageBackendVolWipeLocalFile(const char *path, - unsigned int algorithm, - unsigned long long allocation) -{ - int ret = -1, fd = -1; - const char *alg_char = NULL; - struct stat st; - virCommandPtr cmd = NULL; - - fd = open(path, O_RDWR); - if (fd == -1) { - virReportSystemError(errno, - _("Failed to open storage volume with path '%s'"), - path); - goto cleanup; - } - - if (fstat(fd, &st) == -1) { - virReportSystemError(errno, - _("Failed to stat storage volume with path '%s'"), - path); - goto cleanup; - } - - switch ((virStorageVolWipeAlgorithm) algorithm) { - case VIR_STORAGE_VOL_WIPE_ALG_ZERO: - alg_char = "zero"; - break; - case VIR_STORAGE_VOL_WIPE_ALG_NNSA: - alg_char = "nnsa"; - break; - case VIR_STORAGE_VOL_WIPE_ALG_DOD: - alg_char = "dod"; - break; - case VIR_STORAGE_VOL_WIPE_ALG_BSI: - alg_char = "bsi"; - break; - case VIR_STORAGE_VOL_WIPE_ALG_GUTMANN: - alg_char = "gutmann"; - break; - case VIR_STORAGE_VOL_WIPE_ALG_SCHNEIER: - alg_char = "schneier"; - break; - case VIR_STORAGE_VOL_WIPE_ALG_PFITZNER7: - alg_char = "pfitzner7"; - break; - case VIR_STORAGE_VOL_WIPE_ALG_PFITZNER33: - alg_char = "pfitzner33"; - break; - case VIR_STORAGE_VOL_WIPE_ALG_RANDOM: - alg_char = "random"; - break; - case VIR_STORAGE_VOL_WIPE_ALG_TRIM: - virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", - _("'trim' algorithm not supported")); - goto cleanup; - case VIR_STORAGE_VOL_WIPE_ALG_LAST: - virReportError(VIR_ERR_INVALID_ARG, - _("unsupported algorithm %d"), - algorithm); - goto cleanup; - } - - VIR_DEBUG("Wiping file '%s' with algorithm '%s'", path, alg_char); - - if (algorithm != VIR_STORAGE_VOL_WIPE_ALG_ZERO) { - cmd = virCommandNew(SCRUB); - virCommandAddArgList(cmd, "-f", "-p", alg_char, path, NULL); - - if (virCommandRun(cmd, NULL) < 0) - goto cleanup; - - ret = 0; - } else { - if (S_ISREG(st.st_mode) && st.st_blocks < (st.st_size / DEV_BSIZE)) { - ret = virStorageBackendVolZeroSparseFileLocal(path, st.st_size, fd); - } else { - ret = virStorageBackendWipeLocal(path, - fd, - allocation, - st.st_blksize); - } - if (ret < 0) - goto cleanup; - } - - cleanup: - virCommandFree(cmd); - VIR_FORCE_CLOSE(fd); - return ret; -} - - -static int -virStorageBackendVolWipePloop(virStorageVolDefPtr vol, - unsigned int algorithm) -{ - virCommandPtr cmd = NULL; - char *target_path = NULL; - char *disk_desc = NULL; - char *create_tool = NULL; - - int ret = -1; - - create_tool = virFindFileInPath("ploop"); - if (!create_tool) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("unable to find ploop tools, please install them")); - return -1; - } - - if (virAsprintf(&target_path, "%s/root.hds", vol->target.path) < 0) - goto cleanup; - - if (virAsprintf(&disk_desc, "%s/DiskDescriptor.xml", vol->target.path) < 0) - goto cleanup; - - if (virStorageBackendVolWipeLocalFile(target_path, - algorithm, - vol->target.allocation) < 0) - goto cleanup; - - if (virFileRemove(disk_desc, 0, 0) < 0) { - virReportError(errno, _("Failed to delete DiskDescriptor.xml of volume '%s'"), - vol->target.path); - goto cleanup; - } - if (virFileRemove(target_path, 0, 0) < 0) { - virReportError(errno, _("failed to delete root.hds of volume '%s'"), - vol->target.path); - goto cleanup; - } - - cmd = virCommandNewArgList(create_tool, "init", "-s", NULL); - - virCommandAddArgFormat(cmd, "%lluM", VIR_DIV_UP(vol->target.capacity, - (1024 * 1024))); - virCommandAddArgList(cmd, "-t", "ext4", NULL); - virCommandAddArg(cmd, target_path); - ret = virCommandRun(cmd, NULL); - - cleanup: - VIR_FREE(disk_desc); - VIR_FREE(target_path); - VIR_FREE(create_tool); - virCommandFree(cmd); - return ret; -} - - -int -virStorageBackendVolWipeLocal(virConnectPtr conn ATTRIBUTE_UNUSED, - virStoragePoolObjPtr pool ATTRIBUTE_UNUSED, - virStorageVolDefPtr vol, - unsigned int algorithm, - unsigned int flags) -{ - int ret = -1; - - virCheckFlags(0, -1); - - VIR_DEBUG("Wiping volume with path '%s' and algorithm %u", - vol->target.path, algorithm); - - if (vol->target.format == VIR_STORAGE_FILE_PLOOP) { - ret = virStorageBackendVolWipePloop(vol, algorithm); - } else { - ret = virStorageBackendVolWipeLocalFile(vol->target.path, - algorithm, - vol->target.allocation); - } - - return ret; -} - - -/** - * virStorageBackendFindGlusterPoolSources: - * @host: host to detect volumes on - * @pooltype: src->format is set to this value - * @list: list of storage pool sources to be filled - * @report: report error if the 'gluster' cli tool is missing - * - * Looks up gluster volumes on @host and fills them to @list. - * - * Returns number of volumes on the host on success, or -1 on error. - */ -int -virStorageBackendFindGlusterPoolSources(const char *host, - int pooltype, - virStoragePoolSourceListPtr list, - bool report) -{ - char *glusterpath = NULL; - char *outbuf = NULL; - virCommandPtr cmd = NULL; - xmlDocPtr doc = NULL; - xmlXPathContextPtr ctxt = NULL; - xmlNodePtr *nodes = NULL; - virStoragePoolSource *src = NULL; - size_t i; - int nnodes; - int rc; - - int ret = -1; - - if (!(glusterpath = virFindFileInPath(GLUSTER_CLI))) { - if (report) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("'gluster' command line tool not found")); - return -1; - } else { - return 0; - } - } - - cmd = virCommandNewArgList(glusterpath, - "--xml", - "--log-file=/dev/null", - "volume", "info", "all", NULL); - - virCommandAddArgFormat(cmd, "--remote-host=%s", host); - virCommandSetOutputBuffer(cmd, &outbuf); - - if (virCommandRun(cmd, &rc) < 0) - goto cleanup; - - if (rc != 0) { - ret = 0; - goto cleanup; - } - - if (!(doc = virXMLParseStringCtxt(outbuf, _("(gluster_cli_output)"), - &ctxt))) - goto cleanup; - - if ((nnodes = virXPathNodeSet("//volumes/volume", ctxt, &nodes)) < 0) - goto cleanup; - - for (i = 0; i < nnodes; i++) { - ctxt->node = nodes[i]; - - if (!(src = virStoragePoolSourceListNewSource(list))) - goto cleanup; - - if (!(src->dir = virXPathString("string(//name)", ctxt))) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("failed to extract gluster volume name")); - goto cleanup; - } - - if (VIR_ALLOC_N(src->hosts, 1) < 0) - goto cleanup; - src->nhost = 1; - - if (VIR_STRDUP(src->hosts[0].name, host) < 0) - goto cleanup; - - src->format = pooltype; - } - - ret = nnodes; - - cleanup: - VIR_FREE(nodes); - xmlXPathFreeContext(ctxt); - xmlFreeDoc(doc); - VIR_FREE(outbuf); - virCommandFree(cmd); - VIR_FREE(glusterpath); - return ret; -} - - -#if WITH_BLKID - -typedef enum { - VIR_STORAGE_BLKID_PROBE_ERROR = -1, - VIR_STORAGE_BLKID_PROBE_UNDEFINED, /* Nothing found */ - VIR_STORAGE_BLKID_PROBE_UNKNOWN, /* Don't know libvirt fs/part type */ - VIR_STORAGE_BLKID_PROBE_MATCH, /* Matches the on disk format */ - VIR_STORAGE_BLKID_PROBE_DIFFERENT, /* Format doesn't match on disk format */ -} virStorageBackendBLKIDProbeResult; - -/* - * Utility function to probe for a file system on the device using the - * blkid "superblock" (e.g. default) APIs. - * - * NB: In general this helper will handle the virStoragePoolFormatFileSystem - * format types; however, if called from the Disk path, the initial fstype - * check will fail forcing the usage of the ProbePart helper. - * - * Returns virStorageBackendBLKIDProbeResult enum - */ -static virStorageBackendBLKIDProbeResult -virStorageBackendBLKIDFindFS(blkid_probe probe, - const char *device, - const char *format) -{ - const char *fstype = NULL; - - /* Make sure we're doing a superblock probe from the start */ - blkid_probe_enable_superblocks(probe, true); - blkid_probe_reset_superblocks_filter(probe); - - if (blkid_do_probe(probe) != 0) { - VIR_INFO("No filesystem found on device '%s'", device); - return VIR_STORAGE_BLKID_PROBE_UNDEFINED; - } - - if (blkid_probe_lookup_value(probe, "TYPE", &fstype, NULL) == 0) { - if (STREQ(fstype, format)) - return VIR_STORAGE_BLKID_PROBE_MATCH; - - return VIR_STORAGE_BLKID_PROBE_DIFFERENT; - } - - if (blkid_known_fstype(format) == 0) - return VIR_STORAGE_BLKID_PROBE_UNKNOWN; - - return VIR_STORAGE_BLKID_PROBE_ERROR; -} - - -/* - * Utility function to probe for a partition on the device using the - * blkid "partitions" APIs. - * - * NB: In general, this API will be validating the virStoragePoolFormatDisk - * format types. - * - * Returns virStorageBackendBLKIDProbeResult enum - */ -static virStorageBackendBLKIDProbeResult -virStorageBackendBLKIDFindPart(blkid_probe probe, - const char *device, - const char *format) -{ - const char *pttype = NULL; - - /* A blkid_known_pttype on "dvh" and "pc98" returns a failure; - * however, the blkid_do_probe for "dvh" returns "sgi" and - * for "pc98" it returns "dos". So since those will cause problems - * with startup comparison, let's just treat them as UNKNOWN causing - * the caller to fallback to using PARTED */ - if (STREQ(format, "dvh") || STREQ(format, "pc98")) - return VIR_STORAGE_BLKID_PROBE_UNKNOWN; - - /* Make sure we're doing a partitions probe from the start */ - blkid_probe_enable_partitions(probe, true); - blkid_probe_reset_partitions_filter(probe); - - if (blkid_do_probe(probe) != 0) { - VIR_INFO("No partition found on device '%s'", device); - return VIR_STORAGE_BLKID_PROBE_UNDEFINED; - } - - if (blkid_probe_lookup_value(probe, "PTTYPE", &pttype, NULL) == 0) { - if (STREQ(pttype, format)) - return VIR_STORAGE_BLKID_PROBE_MATCH; - - return VIR_STORAGE_BLKID_PROBE_DIFFERENT; - } - - if (blkid_known_pttype(format) == 0) - return VIR_STORAGE_BLKID_PROBE_UNKNOWN; - - return VIR_STORAGE_BLKID_PROBE_ERROR; -} - - -/* - * @device: Path to device - * @format: Desired format - * @writelabel: True if desire to write the label - * - * Use the blkid_ APIs in order to get details regarding whether a file - * system or partition exists on the disk already. - * - * Returns: - * -2: Force usage of PARTED for unknown types - * -1: An error was encountered, with error message set - * 0: No file system found - */ -static int -virStorageBackendBLKIDFindEmpty(const char *device, - const char *format, - bool writelabel) -{ - - int ret = -1; - int rc; - blkid_probe probe = NULL; - - VIR_DEBUG("Probe for existing filesystem/partition format %s on device %s", - format, device); - - if (!(probe = blkid_new_probe_from_filename(device))) { - virReportError(VIR_ERR_STORAGE_PROBE_FAILED, - _("Failed to create filesystem probe for device %s"), - device); - return -1; - } - - /* Look for something on FS, if it either doesn't recognize the - * format type as a valid FS format type or it doesn't find a valid - * format type on the device, then perform the same check using - * partition probing. */ - rc = virStorageBackendBLKIDFindFS(probe, device, format); - if (rc == VIR_STORAGE_BLKID_PROBE_UNDEFINED || - rc == VIR_STORAGE_BLKID_PROBE_UNKNOWN) { - - rc = virStorageBackendBLKIDFindPart(probe, device, format); - } - - switch (rc) { - case VIR_STORAGE_BLKID_PROBE_UNDEFINED: - if (writelabel) - ret = 0; - else - virReportError(VIR_ERR_STORAGE_PROBE_FAILED, - _("Device '%s' is unrecognized, requires build"), - device); - break; - - case VIR_STORAGE_BLKID_PROBE_ERROR: - virReportError(VIR_ERR_STORAGE_PROBE_FAILED, - _("Failed to probe for format type '%s'"), format); - break; - - case VIR_STORAGE_BLKID_PROBE_UNKNOWN: - ret = -2; - break; - - case VIR_STORAGE_BLKID_PROBE_MATCH: - if (writelabel) - virReportError(VIR_ERR_STORAGE_POOL_BUILT, - _("Device '%s' already formatted using '%s'"), - device, format); - else - ret = 0; - break; - - case VIR_STORAGE_BLKID_PROBE_DIFFERENT: - if (writelabel) - virReportError(VIR_ERR_STORAGE_POOL_BUILT, - _("Format of device '%s' does not match the " - "expected format '%s', forced overwrite is " - "necessary"), - device, format); - else - virReportError(VIR_ERR_OPERATION_INVALID, - _("Format of device '%s' does not match the " - "expected format '%s'"), - device, format); - break; - } - - if (ret == 0 && blkid_do_probe(probe) != 1) { - virReportError(VIR_ERR_STORAGE_PROBE_FAILED, "%s", - _("Found additional probes to run, probing may " - "be incorrect")); - ret = -1; - } - - blkid_free_probe(probe); - - return ret; -} - -#else /* #if WITH_BLKID */ - -static int -virStorageBackendBLKIDFindEmpty(const char *device ATTRIBUTE_UNUSED, - const char *format ATTRIBUTE_UNUSED, - bool writelabel ATTRIBUTE_UNUSED) -{ - return -2; -} - -#endif /* #if WITH_BLKID */ - - -#if WITH_STORAGE_DISK - -typedef enum { - VIR_STORAGE_PARTED_ERROR = -1, - VIR_STORAGE_PARTED_MATCH, /* Valid label found and matches format */ - VIR_STORAGE_PARTED_DIFFERENT, /* Valid label found but not match format */ - VIR_STORAGE_PARTED_UNKNOWN, /* No or unrecognized label */ - VIR_STORAGE_PARTED_NOPTTYPE, /* Did not find the Partition Table type */ - VIR_STORAGE_PARTED_PTTYPE_UNK, /* Partition Table type unknown*/ -} virStorageBackendPARTEDResult; - -/** - * Check for a valid disk label (partition table) on device using - * the PARTED command - * - * returns virStorageBackendPARTEDResult - */ -static virStorageBackendPARTEDResult -virStorageBackendPARTEDFindLabel(const char *device, - const char *format) -{ - const char *const args[] = { - device, "print", "--script", NULL, - }; - virCommandPtr cmd = virCommandNew(PARTED); - char *output = NULL; - char *error = NULL; - char *start, *end; - int ret = VIR_STORAGE_PARTED_ERROR; - - virCommandAddArgSet(cmd, args); - virCommandAddEnvString(cmd, "LC_ALL=C"); - virCommandSetOutputBuffer(cmd, &output); - virCommandSetErrorBuffer(cmd, &error); - - /* if parted succeeds we have a valid partition table */ - ret = virCommandRun(cmd, NULL); - if (ret < 0) { - if ((output && strstr(output, "unrecognised disk label")) || - (error && strstr(error, "unrecognised disk label"))) { - ret = VIR_STORAGE_PARTED_UNKNOWN; - } - goto cleanup; - } - - /* Search for "Partition Table:" in the output. If not present, - * then we cannot validate the partition table type. - */ - if (!(start = strstr(output, "Partition Table: ")) || - !(end = strstr(start, "\n"))) { - VIR_DEBUG("Unable to find tag in output: %s", output); - ret = VIR_STORAGE_PARTED_NOPTTYPE; - goto cleanup; - } - start += strlen("Partition Table: "); - *end = '\0'; - - /* on disk it's "msdos", but we document/use "dos" so deal with it here */ - if (STREQ(start, "msdos")) - start += 2; - - /* Make sure we know about this type */ - if (virStoragePoolFormatDiskTypeFromString(start) < 0) { - ret = VIR_STORAGE_PARTED_PTTYPE_UNK; - goto cleanup; - } - - /* Does the on disk match what the pool desired? */ - if (STREQ(start, format)) - ret = VIR_STORAGE_PARTED_MATCH; - - ret = VIR_STORAGE_PARTED_DIFFERENT; - - cleanup: - virCommandFree(cmd); - VIR_FREE(output); - VIR_FREE(error); - return ret; -} - - -/** - * Determine whether the label on the disk is valid or in a known format - * for the purpose of rewriting the label during build or being able to - * start a pool on a device. - * - * When 'writelabel' is true, if we find a valid disk label on the device, - * then we shouldn't be attempting to write as the volume may contain - * data. Force the usage of the overwrite flag to the build command in - * order to be certain. When the disk label is unrecognized, then it - * should be safe to write. - * - * When 'writelabel' is false, only if we find a valid disk label on the - * device should we allow the start since for this path we won't be - * rewriting the label. - * - * Return: 0 if it's OK - * -1 if something's wrong - */ -static int -virStorageBackendPARTEDValidLabel(const char *device, - const char *format, - bool writelabel) -{ - int ret = -1; - virStorageBackendPARTEDResult check; - - check = virStorageBackendPARTEDFindLabel(device, format); - switch (check) { - case VIR_STORAGE_PARTED_ERROR: - virReportError(VIR_ERR_OPERATION_FAILED, "%s", - _("Error checking for disk label, failed to get " - "disk partition information")); - break; - - case VIR_STORAGE_PARTED_MATCH: - if (writelabel) - virReportError(VIR_ERR_OPERATION_INVALID, - _("Disk label already formatted using '%s'"), - format); - else - ret = 0; - break; - - case VIR_STORAGE_PARTED_DIFFERENT: - virReportError(VIR_ERR_OPERATION_FAILED, "%s", - _("Known, but different label format present, " - "requires build --overwrite")); - break; - - case VIR_STORAGE_PARTED_UNKNOWN: - if (writelabel) - ret = 0; - else - virReportError(VIR_ERR_OPERATION_FAILED, "%s", - _("Unrecognized disk label found, requires build")); - break; - - case VIR_STORAGE_PARTED_NOPTTYPE: - virReportError(VIR_ERR_OPERATION_FAILED, "%s", - _("Unable to determine Partition Type, " - "requires build --overwrite")); - break; - - case VIR_STORAGE_PARTED_PTTYPE_UNK: - virReportError(VIR_ERR_OPERATION_FAILED, "%s", - _("Unknown Partition Type, requires build --overwrite")); - break; - } - - return ret; -} - -#else - -static int -virStorageBackendPARTEDValidLabel(const char *device ATTRIBUTE_UNUSED, - const char *format ATTRIBUTE_UNUSED, - bool writelabel ATTRIBUTE_UNUSED) -{ - return -2; -} - - -#endif /* #if WITH_STORAGE_DISK */ - - -/* virStorageBackendDeviceIsEmpty: - * @devpath: Path to the device to check - * @format: Desired format string - * @writelabel: True if the caller expects to write the label - * - * Check if the @devpath has some sort of known file system using the - * BLKID API if available. - * - * Returns true if the probe deems the device has nothing valid on it - * or when we cannot check and we're not writing the label. - * - * Returns false if the probe finds something - */ -bool -virStorageBackendDeviceIsEmpty(const char *devpath, - const char *format, - bool writelabel) -{ - int ret; - - if ((ret = virStorageBackendBLKIDFindEmpty(devpath, format, - writelabel)) == -2) - ret = virStorageBackendPARTEDValidLabel(devpath, format, writelabel); - - if (ret == -2 && !writelabel) - ret = 0; - - if (ret == -2) { - virReportError(VIR_ERR_OPERATION_INVALID, - _("Unable to probe '%s' for existing data, " - "forced overwrite is necessary"), - devpath); - } - - return ret == 0; -} diff --git a/src/storage/storage_backend.h b/src/storage/storage_backend.h index 3f0403907..b8fb368bb 100644 --- a/src/storage/storage_backend.h +++ b/src/storage/storage_backend.h @@ -1,9 +1,6 @@ /* * storage_backend.h: internal storage driver backend contract * - * Copyright (C) 2007-2010, 2012-2014 Red Hat, Inc. - * Copyright (C) 2007-2008 Daniel P. Berrange - * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either @@ -17,8 +14,6 @@ * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see * <http://www.gnu.org/licenses/>. - * - * Author: Daniel P. Berrange <berrange@redhat.com> */ #ifndef __VIR_STORAGE_BACKEND_H__ @@ -28,7 +23,6 @@ # include "internal.h" # include "storage_conf.h" -# include "vircommand.h" # include "storage_driver.h" typedef char * (*virStorageBackendFindPoolSources)(virConnectPtr conn, @@ -102,67 +96,6 @@ typedef int (*virStorageBackendVolumeWipe)(virConnectPtr conn, unsigned int algorithm, unsigned int flags); -/* File creation/cloning functions used for cloning between backends */ -int virStorageBackendCreateRaw(virConnectPtr conn, - virStoragePoolObjPtr pool, - virStorageVolDefPtr vol, - virStorageVolDefPtr inputvol, - unsigned int flags); - -int virStorageBackendCreateQemuImg(virConnectPtr conn, - virStoragePoolObjPtr pool, - virStorageVolDefPtr vol, - virStorageVolDefPtr inputvol, - unsigned int flags); - -int virStorageBackendCreatePloop(virConnectPtr conn, - virStoragePoolObjPtr pool, - virStorageVolDefPtr vol, - virStorageVolDefPtr inputvol, - unsigned int flags); - -int virStoragePloopResize(virStorageVolDefPtr vol, - unsigned long long capacity); - -int virStorageBackendRedoPloopUpdate(virStorageSourcePtr target, - struct stat *sb, int *fd, - unsigned int flags); -bool virStorageBackendIsPloopDir(char *path); - -virStorageBackendBuildVolFrom -virStorageBackendGetBuildVolFromFunction(virStorageVolDefPtr vol, - virStorageVolDefPtr inputvol); - -int virStorageBackendFindGlusterPoolSources(const char *host, - int pooltype, - virStoragePoolSourceListPtr list, - bool report); - -int virStorageBackendVolUploadLocal(virConnectPtr conn, - virStoragePoolObjPtr pool, - virStorageVolDefPtr vol, - virStreamPtr stream, - unsigned long long offset, - unsigned long long len, - unsigned int flags); -int virStorageBackendVolDownloadLocal(virConnectPtr conn, - virStoragePoolObjPtr pool, - virStorageVolDefPtr vol, - virStreamPtr stream, - unsigned long long offset, - unsigned long long len, - unsigned int flags); - -int virStorageBackendVolWipeLocal(virConnectPtr conn, - virStoragePoolObjPtr pool, - virStorageVolDefPtr vol, - unsigned int algorithm, - unsigned int flags); - -bool virStorageBackendDeviceIsEmpty(const char *devpath, - const char *format, - bool writelabel); - typedef struct _virStorageBackend virStorageBackend; typedef virStorageBackend *virStorageBackendPtr; @@ -192,63 +125,6 @@ struct _virStorageBackend { virStorageBackendPtr virStorageBackendForType(int type); -/* VolOpenCheckMode flags */ -enum { - VIR_STORAGE_VOL_OPEN_NOERROR = 1 << 0, /* don't error if unexpected type - * encountered, just warn */ - VIR_STORAGE_VOL_OPEN_REG = 1 << 1, /* regular files okay */ - VIR_STORAGE_VOL_OPEN_BLOCK = 1 << 2, /* block files okay */ - VIR_STORAGE_VOL_OPEN_CHAR = 1 << 3, /* char files okay */ - VIR_STORAGE_VOL_OPEN_DIR = 1 << 4, /* directories okay */ -}; - -/* VolReadErrorMode flags - * If flag is present, then operation won't cause fatal error for - * specified operation, rather a VIR_WARN will be issued and a -2 returned - * for function call - */ -enum { - VIR_STORAGE_VOL_READ_NOERROR = 1 << 0, /* ignore *read errors */ -}; - -# define VIR_STORAGE_VOL_OPEN_DEFAULT (VIR_STORAGE_VOL_OPEN_REG |\ - VIR_STORAGE_VOL_OPEN_BLOCK) - -int virStorageBackendVolOpen(const char *path, struct stat *sb, - unsigned int flags) - ATTRIBUTE_RETURN_CHECK - ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2); - -# define VIR_STORAGE_DEFAULT_POOL_PERM_MODE 0755 -# define VIR_STORAGE_DEFAULT_VOL_PERM_MODE 0600 - -int virStorageBackendUpdateVolInfo(virStorageVolDefPtr vol, - bool withBlockVolFormat, - unsigned int openflags, - unsigned int readflags); -int virStorageBackendUpdateVolTargetInfo(virStorageSourcePtr target, - bool withBlockVolFormat, - unsigned int openflags, - unsigned int readflags); -int virStorageBackendUpdateVolTargetInfoFD(virStorageSourcePtr target, - int fd, - struct stat *sb); - -bool virStorageBackendPoolPathIsStable(const char *path); -char *virStorageBackendStablePath(virStoragePoolObjPtr pool, - const char *devpath, - bool loop); - -virCommandPtr -virStorageBackendCreateQemuImgCmdFromVol(virConnectPtr conn, - virStoragePoolObjPtr pool, - virStorageVolDefPtr vol, - virStorageVolDefPtr inputvol, - unsigned int flags, - const char *create_tool, - int imgformat, - const char *secretPath); - /* ------- virStorageFile backends ------------ */ typedef struct _virStorageFileBackend virStorageFileBackend; typedef virStorageFileBackend *virStorageFileBackendPtr; diff --git a/src/storage/storage_backend_disk.c b/src/storage/storage_backend_disk.c index eae6c90e2..eb2b687c5 100644 --- a/src/storage/storage_backend_disk.c +++ b/src/storage/storage_backend_disk.c @@ -30,6 +30,7 @@ #include "virerror.h" #include "virlog.h" #include "storage_backend_disk.h" +#include "storage_util.h" #include "viralloc.h" #include "vircommand.h" #include "virfile.h" diff --git a/src/storage/storage_backend_fs.c b/src/storage/storage_backend_fs.c index c3f795938..7d18ad7c9 100644 --- a/src/storage/storage_backend_fs.c +++ b/src/storage/storage_backend_fs.c @@ -39,6 +39,7 @@ #include "virerror.h" #include "storage_backend_fs.h" +#include "storage_util.h" #include "storage_conf.h" #include "virstoragefile.h" #include "vircommand.h" diff --git a/src/storage/storage_backend_gluster.c b/src/storage/storage_backend_gluster.c index e819f6299..7be2d9e81 100644 --- a/src/storage/storage_backend_gluster.c +++ b/src/storage/storage_backend_gluster.c @@ -31,6 +31,7 @@ #include "virstoragefile.h" #include "virstring.h" #include "viruri.h" +#include "storage_util.h" #define VIR_FROM_THIS VIR_FROM_STORAGE diff --git a/src/storage/storage_backend_iscsi.c b/src/storage/storage_backend_iscsi.c index 84ad6f3e8..8799349b6 100644 --- a/src/storage/storage_backend_iscsi.c +++ b/src/storage/storage_backend_iscsi.c @@ -44,6 +44,7 @@ #include "virstring.h" #include "viruuid.h" #include "secret_util.h" +#include "storage_util.h" #define VIR_FROM_THIS VIR_FROM_STORAGE diff --git a/src/storage/storage_backend_logical.c b/src/storage/storage_backend_logical.c index 6a6720e22..b0191aa45 100644 --- a/src/storage/storage_backend_logical.c +++ b/src/storage/storage_backend_logical.c @@ -39,6 +39,7 @@ #include "virlog.h" #include "virfile.h" #include "virstring.h" +#include "storage_util.h" #define VIR_FROM_THIS VIR_FROM_STORAGE diff --git a/src/storage/storage_backend_mpath.c b/src/storage/storage_backend_mpath.c index bf1b1abe0..a5d692a07 100644 --- a/src/storage/storage_backend_mpath.c +++ b/src/storage/storage_backend_mpath.c @@ -36,6 +36,7 @@ #include "virlog.h" #include "virfile.h" #include "virstring.h" +#include "storage_util.h" #define VIR_FROM_THIS VIR_FROM_STORAGE diff --git a/src/storage/storage_backend_rbd.c b/src/storage/storage_backend_rbd.c index b1c51ab1b..45beb107a 100644 --- a/src/storage/storage_backend_rbd.c +++ b/src/storage/storage_backend_rbd.c @@ -37,6 +37,7 @@ #include "rados/librados.h" #include "rbd/librbd.h" #include "secret_util.h" +#include "storage_util.h" #define VIR_FROM_THIS VIR_FROM_STORAGE diff --git a/src/storage/storage_backend_scsi.c b/src/storage/storage_backend_scsi.c index be02f6eec..d294d2ac0 100644 --- a/src/storage/storage_backend_scsi.c +++ b/src/storage/storage_backend_scsi.c @@ -35,6 +35,7 @@ #include "virfile.h" #include "vircommand.h" #include "virstring.h" +#include "storage_util.h" #define VIR_FROM_THIS VIR_FROM_STORAGE diff --git a/src/storage/storage_backend_sheepdog.c b/src/storage/storage_backend_sheepdog.c index ad2d75629..17202e487 100644 --- a/src/storage/storage_backend_sheepdog.c +++ b/src/storage/storage_backend_sheepdog.c @@ -33,6 +33,7 @@ #include "vircommand.h" #include "viralloc.h" #include "virstring.h" +#include "storage_util.h" #define VIR_FROM_THIS VIR_FROM_STORAGE diff --git a/src/storage/storage_backend_zfs.c b/src/storage/storage_backend_zfs.c index 3a43be41c..70c533a7f 100644 --- a/src/storage/storage_backend_zfs.c +++ b/src/storage/storage_backend_zfs.c @@ -27,6 +27,7 @@ #include "storage_backend_zfs.h" #include "virlog.h" #include "virstring.h" +#include "storage_util.h" #define VIR_FROM_THIS VIR_FROM_STORAGE diff --git a/src/storage/storage_driver.c b/src/storage/storage_driver.c index 8f1d3f04c..a52eae3e2 100644 --- a/src/storage/storage_driver.c +++ b/src/storage/storage_driver.c @@ -51,6 +51,7 @@ #include "virstring.h" #include "viraccessapicheck.h" #include "dirname.h" +#include "storage_util.h" #define VIR_FROM_THIS VIR_FROM_STORAGE diff --git a/src/storage/storage_util.c b/src/storage/storage_util.c new file mode 100644 index 000000000..353dec4a8 --- /dev/null +++ b/src/storage/storage_util.c @@ -0,0 +1,2915 @@ +/* + * storage_util.c: helper functions for the storage driver + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <http://www.gnu.org/licenses/>. + */ + +#include <config.h> + +#include <string.h> +#include <stdio.h> +#include <regex.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/stat.h> +#include <sys/param.h> +#include <dirent.h> +#include "dirname.h" +#ifdef __linux__ +# include <sys/ioctl.h> +# include <linux/fs.h> +# ifndef FS_NOCOW_FL +# define FS_NOCOW_FL 0x00800000 /* Do not cow file */ +# endif +#endif + +#if WITH_BLKID +# include <blkid/blkid.h> +#endif + +#if WITH_SELINUX +# include <selinux/selinux.h> +#endif + +#if HAVE_LINUX_BTRFS_H +# include <linux/btrfs.h> +#endif + +#include "datatypes.h" +#include "virerror.h" +#include "viralloc.h" +#include "internal.h" +#include "secret_conf.h" +#include "secret_util.h" +#include "vircrypto.h" +#include "viruuid.h" +#include "virstoragefile.h" +#include "storage_util.h" +#include "virlog.h" +#include "virfile.h" +#include "virjson.h" +#include "virqemu.h" +#include "stat-time.h" +#include "virstring.h" +#include "virxml.h" +#include "fdstream.h" + +#define VIR_FROM_THIS VIR_FROM_STORAGE + +VIR_LOG_INIT("storage.storage_util"); + +#define READ_BLOCK_SIZE_DEFAULT (1024 * 1024) +#define WRITE_BLOCK_SIZE_DEFAULT (4 * 1024) + +/* + * Perform the O(1) btrfs clone operation, if possible. + * Upon success, return 0. Otherwise, return -1 and set errno. + */ +#if HAVE_LINUX_BTRFS_H +static inline int +btrfsCloneFile(int dest_fd, int src_fd) +{ + return ioctl(dest_fd, BTRFS_IOC_CLONE, src_fd); +} +#else +static inline int +btrfsCloneFile(int dest_fd ATTRIBUTE_UNUSED, + int src_fd ATTRIBUTE_UNUSED) +{ + errno = ENOTSUP; + return -1; +} +#endif + +static int ATTRIBUTE_NONNULL(2) +virStorageBackendCopyToFD(virStorageVolDefPtr vol, + virStorageVolDefPtr inputvol, + int fd, + unsigned long long *total, + bool want_sparse, + bool reflink_copy) +{ + int inputfd = -1; + int amtread = -1; + int ret = 0; + size_t rbytes = READ_BLOCK_SIZE_DEFAULT; + int wbytes = 0; + int interval; + char *zerobuf = NULL; + char *buf = NULL; + struct stat st; + + if ((inputfd = open(inputvol->target.path, O_RDONLY)) < 0) { + ret = -errno; + virReportSystemError(errno, + _("could not open input path '%s'"), + inputvol->target.path); + goto cleanup; + } + +#ifdef __linux__ + if (ioctl(fd, BLKBSZGET, &wbytes) < 0) + wbytes = 0; +#endif + if ((wbytes == 0) && fstat(fd, &st) == 0) + wbytes = st.st_blksize; + if (wbytes < WRITE_BLOCK_SIZE_DEFAULT) + wbytes = WRITE_BLOCK_SIZE_DEFAULT; + + if (VIR_ALLOC_N(zerobuf, wbytes) < 0) { + ret = -errno; + goto cleanup; + } + + if (VIR_ALLOC_N(buf, rbytes) < 0) { + ret = -errno; + goto cleanup; + } + + if (reflink_copy) { + if (btrfsCloneFile(fd, inputfd) < 0) { + ret = -errno; + virReportSystemError(errno, + _("failed to clone files from '%s'"), + inputvol->target.path); + goto cleanup; + } else { + VIR_DEBUG("btrfs clone finished."); + goto cleanup; + } + } + + while (amtread != 0) { + int amtleft; + + if (*total < rbytes) + rbytes = *total; + + if ((amtread = saferead(inputfd, buf, rbytes)) < 0) { + ret = -errno; + virReportSystemError(errno, + _("failed reading from file '%s'"), + inputvol->target.path); + goto cleanup; + } + *total -= amtread; + + /* Loop over amt read in 512 byte increments, looking for sparse + * blocks */ + amtleft = amtread; + do { + interval = ((wbytes > amtleft) ? amtleft : wbytes); + int offset = amtread - amtleft; + + if (want_sparse && memcmp(buf+offset, zerobuf, interval) == 0) { + if (lseek(fd, interval, SEEK_CUR) < 0) { + ret = -errno; + virReportSystemError(errno, + _("cannot extend file '%s'"), + vol->target.path); + goto cleanup; + } + } else if (safewrite(fd, buf+offset, interval) < 0) { + ret = -errno; + virReportSystemError(errno, + _("failed writing to file '%s'"), + vol->target.path); + goto cleanup; + + } + } while ((amtleft -= interval) > 0); + } + + if (fdatasync(fd) < 0) { + ret = -errno; + virReportSystemError(errno, _("cannot sync data to file '%s'"), + vol->target.path); + goto cleanup; + } + + + if (VIR_CLOSE(inputfd) < 0) { + ret = -errno; + virReportSystemError(errno, + _("cannot close file '%s'"), + inputvol->target.path); + goto cleanup; + } + inputfd = -1; + + cleanup: + VIR_FORCE_CLOSE(inputfd); + + VIR_FREE(zerobuf); + VIR_FREE(buf); + + return ret; +} + +static int +virStorageBackendCreateBlockFrom(virConnectPtr conn ATTRIBUTE_UNUSED, + virStoragePoolObjPtr pool ATTRIBUTE_UNUSED, + virStorageVolDefPtr vol, + virStorageVolDefPtr inputvol, + unsigned int flags) +{ + int fd = -1; + int ret = -1; + unsigned long long remain; + struct stat st; + gid_t gid; + uid_t uid; + mode_t mode; + bool reflink_copy = false; + + virCheckFlags(VIR_STORAGE_VOL_CREATE_PREALLOC_METADATA | + VIR_STORAGE_VOL_CREATE_REFLINK, + -1); + + if (flags & VIR_STORAGE_VOL_CREATE_PREALLOC_METADATA) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("metadata preallocation is not supported for block " + "volumes")); + goto cleanup; + } + + if (flags & VIR_STORAGE_VOL_CREATE_REFLINK) + reflink_copy = true; + + if ((fd = open(vol->target.path, O_RDWR)) < 0) { + virReportSystemError(errno, + _("cannot create path '%s'"), + vol->target.path); + goto cleanup; + } + + remain = vol->target.capacity; + + if (inputvol) { + if (virStorageBackendCopyToFD(vol, inputvol, fd, &remain, + false, reflink_copy) < 0) + goto cleanup; + } + + if (fstat(fd, &st) == -1) { + virReportSystemError(errno, _("stat of '%s' failed"), + vol->target.path); + goto cleanup; + } + uid = (vol->target.perms->uid != st.st_uid) ? vol->target.perms->uid + : (uid_t) -1; + gid = (vol->target.perms->gid != st.st_gid) ? vol->target.perms->gid + : (gid_t) -1; + if (((uid != (uid_t) -1) || (gid != (gid_t) -1)) + && (fchown(fd, uid, gid) < 0)) { + virReportSystemError(errno, + _("cannot chown '%s' to (%u, %u)"), + vol->target.path, (unsigned int) uid, + (unsigned int) gid); + goto cleanup; + } + + mode = (vol->target.perms->mode == (mode_t) -1 ? + VIR_STORAGE_DEFAULT_VOL_PERM_MODE : vol->target.perms->mode); + if (fchmod(fd, mode) < 0) { + virReportSystemError(errno, + _("cannot set mode of '%s' to %04o"), + vol->target.path, mode); + goto cleanup; + } + if (VIR_CLOSE(fd) < 0) { + virReportSystemError(errno, + _("cannot close file '%s'"), + vol->target.path); + goto cleanup; + } + fd = -1; + + ret = 0; + cleanup: + VIR_FORCE_CLOSE(fd); + + return ret; +} + +static int +createRawFile(int fd, virStorageVolDefPtr vol, + virStorageVolDefPtr inputvol, + bool reflink_copy) +{ + bool need_alloc = true; + int ret = 0; + unsigned long long pos = 0; + + /* If the new allocation is lower than the capacity of the original file, + * the cloned volume will be sparse */ + if (inputvol && + vol->target.allocation < inputvol->target.capacity) + need_alloc = false; + + /* Seek to the final size, so the capacity is available upfront + * for progress reporting */ + if (ftruncate(fd, vol->target.capacity) < 0) { + ret = -errno; + virReportSystemError(errno, + _("cannot extend file '%s'"), + vol->target.path); + goto cleanup; + } + +/* Avoid issues with older kernel's <linux/fs.h> namespace pollution. */ +#if HAVE_FALLOCATE - 0 + /* Try to preallocate all requested disk space, but fall back to + * other methods if this fails with ENOSYS or EOPNOTSUPP. If allocation + * is 0 (or less than 0), then fallocate will fail with EINVAL. + * NOTE: do not use posix_fallocate; posix_fallocate falls back + * to writing zeroes block by block in case fallocate isn't + * available, and since we're going to copy data from another + * file it doesn't make sense to write the file twice. */ + if (vol->target.allocation && need_alloc) { + if (fallocate(fd, 0, 0, vol->target.allocation) == 0) { + need_alloc = false; + } else if (errno != ENOSYS && errno != EOPNOTSUPP) { + ret = -errno; + virReportSystemError(errno, + _("cannot allocate %llu bytes in file '%s'"), + vol->target.allocation, vol->target.path); + goto cleanup; + } + } +#endif + + if (inputvol) { + unsigned long long remain = inputvol->target.capacity; + /* allow zero blocks to be skipped if we've requested sparse + * allocation (allocation < capacity) or we have already + * been able to allocate the required space. */ + if ((ret = virStorageBackendCopyToFD(vol, inputvol, fd, &remain, + !need_alloc, reflink_copy)) < 0) + goto cleanup; + + /* If the new allocation is greater than the original capacity, + * but fallocate failed, fill the rest with zeroes. + */ + pos = inputvol->target.capacity - remain; + } + + if (need_alloc && (vol->target.allocation - pos > 0)) { + if (safezero(fd, pos, vol->target.allocation - pos) < 0) { + ret = -errno; + virReportSystemError(errno, _("cannot fill file '%s'"), + vol->target.path); + goto cleanup; + } + } + + if (fsync(fd) < 0) { + ret = -errno; + virReportSystemError(errno, _("cannot sync data to file '%s'"), + vol->target.path); + goto cleanup; + } + + cleanup: + return ret; +} + +int +virStorageBackendCreateRaw(virConnectPtr conn ATTRIBUTE_UNUSED, + virStoragePoolObjPtr pool, + virStorageVolDefPtr vol, + virStorageVolDefPtr inputvol, + unsigned int flags) +{ + int ret = -1; + int fd = -1; + int operation_flags; + bool reflink_copy = false; + mode_t open_mode = VIR_STORAGE_DEFAULT_VOL_PERM_MODE; + bool created = false; + + virCheckFlags(VIR_STORAGE_VOL_CREATE_PREALLOC_METADATA | + VIR_STORAGE_VOL_CREATE_REFLINK, + -1); + + if (flags & VIR_STORAGE_VOL_CREATE_PREALLOC_METADATA) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("metadata preallocation is not supported for raw " + "volumes")); + goto cleanup; + } + + if (vol->target.backingStore) { + virReportError(VIR_ERR_NO_SUPPORT, "%s", + _("backing storage not supported for raw volumes")); + goto cleanup; + } + + if (flags & VIR_STORAGE_VOL_CREATE_REFLINK) + reflink_copy = true; + + + if (vol->target.encryption != NULL) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("storage pool does not support encrypted volumes")); + goto cleanup; + } + + operation_flags = VIR_FILE_OPEN_FORCE_MODE | VIR_FILE_OPEN_FORCE_OWNER; + if (pool->def->type == VIR_STORAGE_POOL_NETFS) + operation_flags |= VIR_FILE_OPEN_FORK; + + if (vol->target.perms->mode != (mode_t) -1) + open_mode = vol->target.perms->mode; + + if ((fd = virFileOpenAs(vol->target.path, + O_RDWR | O_CREAT | O_EXCL, + open_mode, + vol->target.perms->uid, + vol->target.perms->gid, + operation_flags)) < 0) { + virReportSystemError(-fd, + _("Failed to create file '%s'"), + vol->target.path); + goto cleanup; + } + created = true; + + if (vol->target.nocow) { +#ifdef __linux__ + int attr; + + /* Set NOCOW flag. This is an optimisation for btrfs. + * The FS_IOC_SETFLAGS ioctl return value will be ignored since any + * failure of this operation should not block the volume creation. + */ + if (ioctl(fd, FS_IOC_GETFLAGS, &attr) < 0) { + virReportSystemError(errno, "%s", _("Failed to get fs flags")); + } else { + attr |= FS_NOCOW_FL; + if (ioctl(fd, FS_IOC_SETFLAGS, &attr) < 0) { + virReportSystemError(errno, "%s", + _("Failed to set NOCOW flag")); + } + } +#endif + } + + if ((ret = createRawFile(fd, vol, inputvol, reflink_copy)) < 0) + /* createRawFile already reported the exact error. */ + ret = -1; + + cleanup: + if (ret < 0 && created) + ignore_value(virFileRemove(vol->target.path, + vol->target.perms->uid, + vol->target.perms->gid)); + VIR_FORCE_CLOSE(fd); + return ret; +} + +static int +virStorageGenerateSecretUUID(virConnectPtr conn, + unsigned char *uuid) +{ + unsigned attempt; + + for (attempt = 0; attempt < 65536; attempt++) { + virSecretPtr tmp; + if (virUUIDGenerate(uuid) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("unable to generate uuid")); + return -1; + } + tmp = conn->secretDriver->secretLookupByUUID(conn, uuid); + if (tmp == NULL) + return 0; + + virObjectUnref(tmp); + } + + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("too many conflicts when generating a uuid")); + + return -1; +} + +static int +virStorageGenerateQcowEncryption(virConnectPtr conn, + virStorageVolDefPtr vol) +{ + virSecretDefPtr def = NULL; + virBuffer buf = VIR_BUFFER_INITIALIZER; + virStorageEncryptionPtr enc; + virStorageEncryptionSecretPtr enc_secret = NULL; + virSecretPtr secret = NULL; + char *xml; + unsigned char value[VIR_STORAGE_QCOW_PASSPHRASE_SIZE]; + int ret = -1; + + if (conn->secretDriver == NULL || + conn->secretDriver->secretLookupByUUID == NULL || + conn->secretDriver->secretDefineXML == NULL || + conn->secretDriver->secretSetValue == NULL) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("secret storage not supported")); + goto cleanup; + } + + enc = vol->target.encryption; + if (enc->nsecrets != 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("secrets already defined")); + goto cleanup; + } + + if (VIR_ALLOC(enc_secret) < 0 || VIR_REALLOC_N(enc->secrets, 1) < 0 || + VIR_ALLOC(def) < 0) + goto cleanup; + + def->isephemeral = false; + def->isprivate = false; + if (virStorageGenerateSecretUUID(conn, def->uuid) < 0) + goto cleanup; + + def->usage_type = VIR_SECRET_USAGE_TYPE_VOLUME; + if (VIR_STRDUP(def->usage_id, vol->target.path) < 0) + goto cleanup; + xml = virSecretDefFormat(def); + virSecretDefFree(def); + def = NULL; + if (xml == NULL) + goto cleanup; + + secret = conn->secretDriver->secretDefineXML(conn, xml, 0); + if (secret == NULL) { + VIR_FREE(xml); + goto cleanup; + } + VIR_FREE(xml); + + if (virStorageGenerateQcowPassphrase(value) < 0) + goto cleanup; + + if (conn->secretDriver->secretSetValue(secret, value, sizeof(value), 0) < 0) + goto cleanup; + + enc_secret->type = VIR_STORAGE_ENCRYPTION_SECRET_TYPE_PASSPHRASE; + enc_secret->seclookupdef.type = VIR_SECRET_LOOKUP_TYPE_UUID; + memcpy(enc_secret->seclookupdef.u.uuid, secret->uuid, VIR_UUID_BUFLEN); + enc->format = VIR_STORAGE_ENCRYPTION_FORMAT_QCOW; + enc->secrets[0] = enc_secret; /* Space for secrets[0] allocated above */ + enc_secret = NULL; + enc->nsecrets = 1; + + ret = 0; + + cleanup: + if (secret != NULL) { + if (ret != 0 && + conn->secretDriver->secretUndefine != NULL) + conn->secretDriver->secretUndefine(secret); + virObjectUnref(secret); + } + virBufferFreeAndReset(&buf); + virSecretDefFree(def); + VIR_FREE(enc_secret); + return ret; +} + +static int +virStorageBackendCreateExecCommand(virStoragePoolObjPtr pool, + virStorageVolDefPtr vol, + virCommandPtr cmd) +{ + struct stat st; + gid_t gid; + uid_t uid; + mode_t mode = (vol->target.perms->mode == (mode_t) -1 ? + VIR_STORAGE_DEFAULT_VOL_PERM_MODE : + vol->target.perms->mode); + bool filecreated = false; + int ret = -1; + + if ((pool->def->type == VIR_STORAGE_POOL_NETFS) + && (((geteuid() == 0) + && (vol->target.perms->uid != (uid_t) -1) + && (vol->target.perms->uid != 0)) + || ((vol->target.perms->gid != (gid_t) -1) + && (vol->target.perms->gid != getegid())))) { + + virCommandSetUID(cmd, vol->target.perms->uid); + virCommandSetGID(cmd, vol->target.perms->gid); + virCommandSetUmask(cmd, S_IRWXUGO ^ mode); + + if (virCommandRun(cmd, NULL) == 0) { + /* command was successfully run, check if the file was created */ + if (stat(vol->target.path, &st) >= 0) { + filecreated = true; + + /* seems qemu-img disregards umask and open/creates using 0644. + * If that doesn't match what we expect, then let's try to + * re-open the file and attempt to force the mode change. + */ + if (mode != (st.st_mode & S_IRWXUGO)) { + int fd = -1; + int flags = VIR_FILE_OPEN_FORK | VIR_FILE_OPEN_FORCE_MODE; + + if ((fd = virFileOpenAs(vol->target.path, O_RDWR, mode, + vol->target.perms->uid, + vol->target.perms->gid, + flags)) >= 0) { + /* Success - means we're good */ + VIR_FORCE_CLOSE(fd); + ret = 0; + goto cleanup; + } + } + } + } + } + + if (!filecreated) { + /* don't change uid/gid/mode if we retry */ + virCommandSetUID(cmd, -1); + virCommandSetGID(cmd, -1); + virCommandSetUmask(cmd, 0); + + if (virCommandRun(cmd, NULL) < 0) + goto cleanup; + if (stat(vol->target.path, &st) < 0) { + virReportSystemError(errno, + _("failed to create %s"), vol->target.path); + goto cleanup; + } + filecreated = true; + } + + uid = (vol->target.perms->uid != st.st_uid) ? vol->target.perms->uid + : (uid_t) -1; + gid = (vol->target.perms->gid != st.st_gid) ? vol->target.perms->gid + : (gid_t) -1; + if (((uid != (uid_t) -1) || (gid != (gid_t) -1)) + && (chown(vol->target.path, uid, gid) < 0)) { + virReportSystemError(errno, + _("cannot chown %s to (%u, %u)"), + vol->target.path, (unsigned int) uid, + (unsigned int) gid); + goto cleanup; + } + + if (mode != (st.st_mode & S_IRWXUGO) && + chmod(vol->target.path, mode) < 0) { + virReportSystemError(errno, + _("cannot set mode of '%s' to %04o"), + vol->target.path, mode); + goto cleanup; + } + + ret = 0; + + cleanup: + if (ret < 0 && filecreated) + virFileRemove(vol->target.path, vol->target.perms->uid, + vol->target.perms->gid); + return ret; +} + +/* Create ploop directory with ploop image and DiskDescriptor.xml + * if function fails to create image file the directory will be deleted.*/ +int +virStorageBackendCreatePloop(virConnectPtr conn ATTRIBUTE_UNUSED, + virStoragePoolObjPtr pool ATTRIBUTE_UNUSED, + virStorageVolDefPtr vol, + virStorageVolDefPtr inputvol, + unsigned int flags) +{ + int ret = -1; + virCommandPtr cmd = NULL; + char *create_tool = NULL; + bool created = false; + + virCheckFlags(0, -1); + + if (inputvol && inputvol->target.format != VIR_STORAGE_FILE_PLOOP) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("unsupported input storage vol type %d"), + inputvol->target.format); + return -1; + } + + if (vol->target.encryption != NULL) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("encrypted ploop volumes are not supported with " + "ploop init")); + return -1; + } + + if (vol->target.backingStore != NULL) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("copy-on-write ploop volumes are not yet supported")); + return -1; + } + + create_tool = virFindFileInPath("ploop"); + if (!create_tool && !inputvol) { + virReportError(VIR_ERR_INTERNAL_ERROR, + "%s", _("unable to find ploop, please install " + "ploop tools")); + return -1; + } + + if (!inputvol) { + if ((virDirCreate(vol->target.path, + (vol->target.perms->mode == (mode_t) -1 ? + VIR_STORAGE_DEFAULT_VOL_PERM_MODE: + vol->target.perms->mode), + vol->target.perms->uid, + vol->target.perms->gid, + 0)) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("error creating directory for ploop volume")); + goto cleanup; + } + cmd = virCommandNewArgList(create_tool, "init", "-s", NULL); + virCommandAddArgFormat(cmd, "%lluM", VIR_DIV_UP(vol->target.capacity, + (1024 * 1024))); + virCommandAddArgList(cmd, "-t", "ext4", NULL); + virCommandAddArgFormat(cmd, "%s/root.hds", vol->target.path); + + } else { + vol->target.capacity = inputvol->target.capacity; + cmd = virCommandNewArgList("cp", "-r", inputvol->target.path, + vol->target.path, NULL); + } + created = true; + ret = virCommandRun(cmd, NULL); + cleanup: + virCommandFree(cmd); + VIR_FREE(create_tool); + if (ret < 0 && created) + virFileDeleteTree(vol->target.path); + return ret; +} + +int +virStoragePloopResize(virStorageVolDefPtr vol, + unsigned long long capacity) +{ + int ret = -1; + virCommandPtr cmd = NULL; + char *resize_tool = NULL; + + resize_tool = virFindFileInPath("ploop"); + if (!resize_tool) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("unable to find ploop, please install ploop tools")); + return -1; + } + cmd = virCommandNewArgList(resize_tool, "resize", "-s", NULL); + virCommandAddArgFormat(cmd, "%lluM", VIR_DIV_UP(capacity, (1024 * 1024))); + + virCommandAddArgFormat(cmd, "%s/DiskDescriptor.xml", vol->target.path); + + ret = virCommandRun(cmd, NULL); + virCommandFree(cmd); + VIR_FREE(resize_tool); + return ret; +} + +/* Flag values shared w/ storagevolxml2argvtest.c. + * + * QEMU_IMG_BACKING_FORMAT_OPTIONS (added in qemu 0.11) + * QEMU_IMG_BACKING_FORMAT_OPTIONS_COMPAT + * was made necessary due to 2.0 change to change the default + * qcow2 file format from 0.10 to 1.1. + */ +enum { + QEMU_IMG_BACKING_FORMAT_OPTIONS = 0, + QEMU_IMG_BACKING_FORMAT_OPTIONS_COMPAT, +}; + +static bool +virStorageBackendQemuImgSupportsCompat(const char *qemuimg) +{ + bool ret = false; + char *output; + virCommandPtr cmd = NULL; + + cmd = virCommandNewArgList(qemuimg, "create", "-o", "?", "-f", "qcow2", + "/dev/null", NULL); + + virCommandAddEnvString(cmd, "LC_ALL=C"); + virCommandSetOutputBuffer(cmd, &output); + + if (virCommandRun(cmd, NULL) < 0) + goto cleanup; + + if (strstr(output, "\ncompat ")) + ret = true; + + cleanup: + virCommandFree(cmd); + VIR_FREE(output); + return ret; +} + + +static int +virStorageBackendQEMUImgBackingFormat(const char *qemuimg) +{ + /* As of QEMU 0.11 the [-o options] support was added via qemu + * commit id '9ea2ea71', so we start with that base and figure + * out what else we have */ + int ret = QEMU_IMG_BACKING_FORMAT_OPTIONS; + + /* QEMU 2.0 changed to using a format that only QEMU 1.1 and newer + * understands. Since we still support QEMU 0.12 and newer, we need + * to be able to handle the previous format as can be set via a + * compat=0.10 option. */ + if (virStorageBackendQemuImgSupportsCompat(qemuimg)) + ret = QEMU_IMG_BACKING_FORMAT_OPTIONS_COMPAT; + + return ret; +} + +/* The _virStorageBackendQemuImgInfo separates the command line building from + * the volume definition so that qemuDomainSnapshotCreateInactiveExternal can + * use it without needing to deal with a volume. + */ +struct _virStorageBackendQemuImgInfo { + int format; + const char *path; + unsigned long long size_arg; + bool encryption; + bool preallocate; + const char *compat; + virBitmapPtr features; + bool nocow; + + const char *backingPath; + int backingFormat; + + const char *inputPath; + const char *inputFormatStr; + int inputFormat; + + char *secretAlias; + const char *secretPath; +}; + + +static int +virStorageBackendCreateQemuImgOpts(virStorageEncryptionInfoDefPtr enc, + char **opts, + struct _virStorageBackendQemuImgInfo info) +{ + virBuffer buf = VIR_BUFFER_INITIALIZER; + + if (info.format == VIR_STORAGE_FILE_RAW && enc) { + virQEMUBuildLuksOpts(&buf, enc, info.secretAlias); + } else { + if (info.backingPath) + virBufferAsprintf(&buf, "backing_fmt=%s,", + virStorageFileFormatTypeToString(info.backingFormat)); + if (info.encryption) + virBufferAddLit(&buf, "encryption=on,"); + if (info.preallocate) + virBufferAddLit(&buf, "preallocation=metadata,"); + } + + if (info.nocow) + virBufferAddLit(&buf, "nocow=on,"); + + if (info.compat) + virBufferAsprintf(&buf, "compat=%s,", info.compat); + + if (info.features && info.format == VIR_STORAGE_FILE_QCOW2) { + if (virBitmapIsBitSet(info.features, + VIR_STORAGE_FILE_FEATURE_LAZY_REFCOUNTS)) { + if (STREQ_NULLABLE(info.compat, "0.10")) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("lazy_refcounts not supported with compat" + " level %s"), + info.compat); + goto error; + } + virBufferAddLit(&buf, "lazy_refcounts,"); + } + } + + virBufferTrim(&buf, ",", -1); + + if (virBufferCheckError(&buf) < 0) + goto error; + + *opts = virBufferContentAndReset(&buf); + return 0; + + error: + virBufferFreeAndReset(&buf); + return -1; +} + + +/* virStorageBackendCreateQemuImgCheckEncryption: + * @format: format of file found + * @conn: pointer to connection + * @vol: pointer to volume def + * + * Ensure the proper setup for encryption. + * + * Returns 0 on success, -1 on failure w/ error set + */ +static int +virStorageBackendCreateQemuImgCheckEncryption(int format, + const char *type, + virConnectPtr conn, + virStorageVolDefPtr vol) +{ + virStorageEncryptionPtr enc = vol->target.encryption; + + if (format == VIR_STORAGE_FILE_QCOW || format == VIR_STORAGE_FILE_QCOW2) { + if (enc->format != VIR_STORAGE_ENCRYPTION_FORMAT_QCOW && + enc->format != VIR_STORAGE_ENCRYPTION_FORMAT_DEFAULT) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("unsupported volume encryption format %d"), + vol->target.encryption->format); + return -1; + } + if (enc->nsecrets > 1) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("too many secrets for qcow encryption")); + return -1; + } + if (enc->format == VIR_STORAGE_ENCRYPTION_FORMAT_DEFAULT || + enc->nsecrets == 0) { + if (virStorageGenerateQcowEncryption(conn, vol) < 0) + return -1; + } + } else if (format == VIR_STORAGE_FILE_RAW) { + if (enc->format != VIR_STORAGE_ENCRYPTION_FORMAT_LUKS) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("unsupported volume encryption format %d"), + vol->target.encryption->format); + return -1; + } + if (enc->nsecrets > 1) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("too many secrets for luks encryption")); + return -1; + } + if (enc->nsecrets == 0) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("no secret provided for luks encryption")); + return -1; + } + if (!virCryptoHaveCipher(VIR_CRYPTO_CIPHER_AES256CBC)) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("luks encryption usage requires encrypted " + "secret generation to be supported")); + return -1; + } + } else { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("volume encryption unsupported with format %s"), type); + return -1; + } + + return 0; +} + + +static int +virStorageBackendCreateQemuImgSetInput(virStorageVolDefPtr inputvol, + struct _virStorageBackendQemuImgInfo *info) +{ + if (!(info->inputPath = inputvol->target.path)) { + virReportError(VIR_ERR_INVALID_ARG, "%s", + _("missing input volume target path")); + return -1; + } + + info->inputFormat = inputvol->target.format; + if (inputvol->type == VIR_STORAGE_VOL_BLOCK) + info->inputFormat = VIR_STORAGE_FILE_RAW; + if (!(info->inputFormatStr = + virStorageFileFormatTypeToString(info->inputFormat))) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("unknown storage vol type %d"), + info->inputFormat); + return -1; + } + + return 0; +} + + +static int +virStorageBackendCreateQemuImgSetBacking(virStoragePoolObjPtr pool, + virStorageVolDefPtr vol, + virStorageVolDefPtr inputvol, + struct _virStorageBackendQemuImgInfo *info) +{ + int accessRetCode = -1; + char *absolutePath = NULL; + + if (info->format == VIR_STORAGE_FILE_RAW) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("cannot set backing store for raw volume")); + return -1; + } + + info->backingFormat = vol->target.backingStore->format; + info->backingPath = vol->target.backingStore->path; + + if (info->preallocate) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("metadata preallocation conflicts with backing" + " store")); + return -1; + } + + /* XXX: Not strictly required: qemu-img has an option a different + * backing store, not really sure what use it serves though, and it + * may cause issues with lvm. Untested essentially. + */ + if (inputvol && inputvol->target.backingStore && + STRNEQ_NULLABLE(inputvol->target.backingStore->path, + info->backingPath)) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("a different backing store cannot be specified.")); + return -1; + } + + if (!virStorageFileFormatTypeToString(info->backingFormat)) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("unknown storage vol backing store type %d"), + info->backingFormat); + return -1; + } + + /* Convert relative backing store paths to absolute paths for access + * validation. + */ + if ('/' != *(info->backingPath) && + virAsprintf(&absolutePath, "%s/%s", pool->def->target.path, + info->backingPath) < 0) + return -1; + accessRetCode = access(absolutePath ? absolutePath : + info->backingPath, R_OK); + VIR_FREE(absolutePath); + if (accessRetCode != 0) { + virReportSystemError(errno, + _("inaccessible backing store volume %s"), + info->backingPath); + return -1; + } + + return 0; +} + + +static int +virStorageBackendCreateQemuImgSetOptions(virCommandPtr cmd, + int imgformat, + virStorageEncryptionInfoDefPtr enc, + struct _virStorageBackendQemuImgInfo info) +{ + char *opts = NULL; + + if (info.format == VIR_STORAGE_FILE_QCOW2 && !info.compat && + imgformat >= QEMU_IMG_BACKING_FORMAT_OPTIONS_COMPAT) + info.compat = "0.10"; + + if (virStorageBackendCreateQemuImgOpts(enc, &opts, info) < 0) + return -1; + if (opts) + virCommandAddArgList(cmd, "-o", opts, NULL); + VIR_FREE(opts); + + return 0; +} + + +/* Add a secret object to the command line: + * --object secret,id=$secretAlias,file=$secretPath + * + * NB: format=raw is assumed + */ +static int +virStorageBackendCreateQemuImgSecretObject(virCommandPtr cmd, + virStorageVolDefPtr vol, + struct _virStorageBackendQemuImgInfo *info) +{ + virBuffer buf = VIR_BUFFER_INITIALIZER; + char *commandStr = NULL; + + if (virAsprintf(&info->secretAlias, "%s_luks0", vol->name) < 0) + return -1; + + virBufferAsprintf(&buf, "secret,id=%s,file=", info->secretAlias); + virQEMUBuildBufferEscapeComma(&buf, info->secretPath); + + if (virBufferCheckError(&buf) < 0) { + virBufferFreeAndReset(&buf); + return -1; + } + + commandStr = virBufferContentAndReset(&buf); + + virCommandAddArgList(cmd, "--object", commandStr, NULL); + + VIR_FREE(commandStr); + return 0; +} + + +/* Create a qemu-img virCommand from the supplied binary path, + * volume definitions and imgformat + */ +virCommandPtr +virStorageBackendCreateQemuImgCmdFromVol(virConnectPtr conn, + virStoragePoolObjPtr pool, + virStorageVolDefPtr vol, + virStorageVolDefPtr inputvol, + unsigned int flags, + const char *create_tool, + int imgformat, + const char *secretPath) +{ + virCommandPtr cmd = NULL; + const char *type; + struct _virStorageBackendQemuImgInfo info = { + .format = vol->target.format, + .path = vol->target.path, + .encryption = vol->target.encryption != NULL, + .preallocate = !!(flags & VIR_STORAGE_VOL_CREATE_PREALLOC_METADATA), + .compat = vol->target.compat, + .features = vol->target.features, + .nocow = vol->target.nocow, + .secretPath = secretPath, + .secretAlias = NULL, + }; + virStorageEncryptionInfoDefPtr enc = NULL; + + virCheckFlags(VIR_STORAGE_VOL_CREATE_PREALLOC_METADATA, NULL); + + /* Treat output block devices as 'raw' format */ + if (vol->type == VIR_STORAGE_VOL_BLOCK) + info.format = VIR_STORAGE_FILE_RAW; + + if (!(type = virStorageFileFormatTypeToString(info.format))) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("unknown storage vol type %d"), + info.format); + return NULL; + } + + if (info.preallocate && info.format != VIR_STORAGE_FILE_QCOW2) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("metadata preallocation only available with qcow2")); + return NULL; + } + if (info.compat && info.format != VIR_STORAGE_FILE_QCOW2) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("compatibility option only available with qcow2")); + return NULL; + } + if (info.features && info.format != VIR_STORAGE_FILE_QCOW2) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("format features only available with qcow2")); + return NULL; + } + if (info.format == VIR_STORAGE_FILE_RAW && + vol->target.encryption != NULL) { + if (inputvol) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("cannot use inputvol with encrypted raw volume")); + return NULL; + } + if (!info.encryption) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("missing encryption description")); + return NULL; + } + if (vol->target.encryption->format == VIR_STORAGE_ENCRYPTION_FORMAT_LUKS) { + type = "luks"; + } else { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Only luks encryption is supported for raw files")); + return NULL; + } + } + + if (inputvol && + virStorageBackendCreateQemuImgSetInput(inputvol, &info) < 0) + return NULL; + + if (vol->target.backingStore && + virStorageBackendCreateQemuImgSetBacking(pool, vol, inputvol, + &info) < 0) + return NULL; + + if (info.encryption && + virStorageBackendCreateQemuImgCheckEncryption(info.format, type, + conn, vol) < 0) + return NULL; + + + /* Size in KB */ + info.size_arg = VIR_DIV_UP(vol->target.capacity, 1024); + + cmd = virCommandNew(create_tool); + + /* ignore the backing volume when we're converting a volume */ + if (info.inputPath) + info.backingPath = NULL; + + if (info.inputPath) + virCommandAddArgList(cmd, "convert", "-f", info.inputFormatStr, + "-O", type, NULL); + else + virCommandAddArgList(cmd, "create", "-f", type, NULL); + + if (info.backingPath) + virCommandAddArgList(cmd, "-b", info.backingPath, NULL); + + if (info.format == VIR_STORAGE_FILE_RAW && + vol->target.encryption != NULL && + vol->target.encryption->format == VIR_STORAGE_ENCRYPTION_FORMAT_LUKS) { + if (virStorageBackendCreateQemuImgSecretObject(cmd, vol, &info) < 0) { + VIR_FREE(info.secretAlias); + virCommandFree(cmd); + return NULL; + } + enc = &vol->target.encryption->encinfo; + } + + if (virStorageBackendCreateQemuImgSetOptions(cmd, imgformat, + enc, info) < 0) { + VIR_FREE(info.secretAlias); + virCommandFree(cmd); + return NULL; + } + VIR_FREE(info.secretAlias); + + if (info.inputPath) + virCommandAddArg(cmd, info.inputPath); + virCommandAddArg(cmd, info.path); + if (!info.inputPath && (info.size_arg || !info.backingPath)) + virCommandAddArgFormat(cmd, "%lluK", info.size_arg); + + return cmd; +} + + +static char * +virStorageBackendCreateQemuImgSecretPath(virConnectPtr conn, + virStoragePoolObjPtr pool, + virStorageVolDefPtr vol) +{ + virStorageEncryptionPtr enc = vol->target.encryption; + char *secretPath = NULL; + int fd = -1; + uint8_t *secret = NULL; + size_t secretlen = 0; + + if (!enc) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("missing encryption description")); + return NULL; + } + + if (!conn || !conn->secretDriver || + !conn->secretDriver->secretLookupByUUID || + !conn->secretDriver->secretLookupByUsage || + !conn->secretDriver->secretGetValue) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("unable to look up encryption secret")); + return NULL; + } + + if (!(secretPath = virStoragePoolObjBuildTempFilePath(pool, vol))) + goto cleanup; + + if ((fd = mkostemp(secretPath, O_CLOEXEC)) < 0) { + virReportSystemError(errno, "%s", + _("failed to open luks secret file for write")); + goto error; + } + + if (virSecretGetSecretString(conn, &enc->secrets[0]->seclookupdef, + VIR_SECRET_USAGE_TYPE_VOLUME, + &secret, &secretlen) < 0) + goto error; + + if (safewrite(fd, secret, secretlen) < 0) { + virReportSystemError(errno, "%s", + _("failed to write luks secret file")); + goto error; + } + VIR_FORCE_CLOSE(fd); + + if ((vol->target.perms->uid != (uid_t) -1) && + (vol->target.perms->gid != (gid_t) -1)) { + if (chown(secretPath, vol->target.perms->uid, + vol->target.perms->gid) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("failed to chown luks secret file")); + goto error; + } + } + + cleanup: + VIR_DISPOSE_N(secret, secretlen); + VIR_FORCE_CLOSE(fd); + + return secretPath; + + error: + unlink(secretPath); + VIR_FREE(secretPath); + goto cleanup; +} + + +int +virStorageBackendCreateQemuImg(virConnectPtr conn, + virStoragePoolObjPtr pool, + virStorageVolDefPtr vol, + virStorageVolDefPtr inputvol, + unsigned int flags) +{ + int ret = -1; + char *create_tool; + int imgformat; + virCommandPtr cmd; + char *secretPath = NULL; + + virCheckFlags(VIR_STORAGE_VOL_CREATE_PREALLOC_METADATA, -1); + + create_tool = virFindFileInPath("qemu-img"); + if (!create_tool) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("creation of non-raw file images is " + "not supported without qemu-img.")); + return -1; + } + + imgformat = virStorageBackendQEMUImgBackingFormat(create_tool); + if (imgformat < 0) + goto cleanup; + + if (vol->target.format == VIR_STORAGE_FILE_RAW && + vol->target.encryption && + vol->target.encryption->format == VIR_STORAGE_ENCRYPTION_FORMAT_LUKS) { + if (!(secretPath = + virStorageBackendCreateQemuImgSecretPath(conn, pool, vol))) + goto cleanup; + } + + cmd = virStorageBackendCreateQemuImgCmdFromVol(conn, pool, vol, inputvol, + flags, create_tool, + imgformat, secretPath); + if (!cmd) + goto cleanup; + + ret = virStorageBackendCreateExecCommand(pool, vol, cmd); + + virCommandFree(cmd); + cleanup: + if (secretPath) { + unlink(secretPath); + VIR_FREE(secretPath); + } + VIR_FREE(create_tool); + return ret; +} + +virStorageBackendBuildVolFrom +virStorageBackendGetBuildVolFromFunction(virStorageVolDefPtr vol, + virStorageVolDefPtr inputvol) +{ + if (!inputvol) + return NULL; + + /* If either volume is a non-raw file vol, or uses encryption, + * we need to use an external tool for converting + */ + if ((vol->type == VIR_STORAGE_VOL_FILE && + (vol->target.format != VIR_STORAGE_FILE_RAW || + vol->target.encryption != NULL)) || + (inputvol->type == VIR_STORAGE_VOL_FILE && + (inputvol->target.format != VIR_STORAGE_FILE_RAW || + inputvol->target.encryption != NULL))) { + return virStorageBackendCreateQemuImg; + } + + if (vol->type == VIR_STORAGE_VOL_PLOOP) + return virStorageBackendCreatePloop; + if (vol->type == VIR_STORAGE_VOL_BLOCK) + return virStorageBackendCreateBlockFrom; + else + return virStorageBackendCreateRaw; +} + + +struct diskType { + int part_table_type; + unsigned short offset; + unsigned short length; + unsigned long long magic; +}; + + +static struct diskType const disk_types[] = { + { VIR_STORAGE_POOL_DISK_LVM2, 0x218, 8, 0x31303020324D564CULL }, + { VIR_STORAGE_POOL_DISK_GPT, 0x200, 8, 0x5452415020494645ULL }, + { VIR_STORAGE_POOL_DISK_DVH, 0x0, 4, 0x41A9E50BULL }, + { VIR_STORAGE_POOL_DISK_MAC, 0x0, 2, 0x5245ULL }, + { VIR_STORAGE_POOL_DISK_BSD, 0x40, 4, 0x82564557ULL }, + { VIR_STORAGE_POOL_DISK_SUN, 0x1fc, 2, 0xBEDAULL }, + /* + * NOTE: pc98 is funky; the actual signature is 0x55AA (just like dos), so + * we can't use that. At the moment I'm relying on the "dummy" IPL + * bootloader data that comes from parted. Luckily, the chances of running + * into a pc98 machine running libvirt are approximately nil. + */ + /*{ 0x1fe, 2, 0xAA55UL },*/ + { VIR_STORAGE_POOL_DISK_PC98, 0x0, 8, 0x314C5049000000CBULL }, + /* + * NOTE: the order is important here; some other disk types (like GPT and + * and PC98) also have 0x55AA at this offset. For that reason, the DOS + * one must be the last one. + */ + { VIR_STORAGE_POOL_DISK_DOS, 0x1fe, 2, 0xAA55ULL }, + { -1, 0x0, 0, 0x0ULL }, +}; + + +/* + * virStorageBackendDetectBlockVolFormatFD + * @target: target definition ptr of volume to update + * @fd: fd of storage volume to update, + * @readflags: VolReadErrorMode flags to handle read error after open + * is successful, but read is not. + * + * Returns 0 for success, -1 on a legitimate error condition, -2 if + * the read error is desired to be ignored (along with appropriate + * VIR_WARN of the issue). + */ +static int +virStorageBackendDetectBlockVolFormatFD(virStorageSourcePtr target, + int fd, + unsigned int readflags) +{ + size_t i; + off_t start; + unsigned char buffer[1024]; + ssize_t bytes; + + /* make sure to set the target format "unknown" to begin with */ + target->format = VIR_STORAGE_POOL_DISK_UNKNOWN; + + start = lseek(fd, 0, SEEK_SET); + if (start < 0) { + virReportSystemError(errno, + _("cannot seek to beginning of file '%s'"), + target->path); + return -1; + } + bytes = saferead(fd, buffer, sizeof(buffer)); + if (bytes < 0) { + if (readflags & VIR_STORAGE_VOL_READ_NOERROR) { + VIR_WARN("ignoring failed saferead of file '%s'", + target->path); + return -2; + } else { + virReportSystemError(errno, + _("cannot read beginning of file '%s'"), + target->path); + return -1; + } + } + + for (i = 0; disk_types[i].part_table_type != -1; i++) { + if (disk_types[i].offset + disk_types[i].length > bytes) + continue; + if (memcmp(buffer+disk_types[i].offset, &disk_types[i].magic, + disk_types[i].length) == 0) { + target->format = disk_types[i].part_table_type; + break; + } + } + + if (target->format == VIR_STORAGE_POOL_DISK_UNKNOWN) + VIR_DEBUG("cannot determine the target format for '%s'", + target->path); + + return 0; +} + + +/* + * Allows caller to silently ignore files with improper mode + * + * Returns -1 on error. If VIR_STORAGE_VOL_OPEN_NOERROR is passed, we + * return -2 if file mode is unexpected or the volume is a dangling + * symbolic link. + */ +int +virStorageBackendVolOpen(const char *path, struct stat *sb, + unsigned int flags) +{ + int fd, mode = 0; + char *base = last_component(path); + bool noerror = (flags & VIR_STORAGE_VOL_OPEN_NOERROR); + + if (lstat(path, sb) < 0) { + if (errno == ENOENT) { + if (noerror) { + VIR_WARN("ignoring missing file '%s'", path); + return -2; + } + virReportError(VIR_ERR_NO_STORAGE_VOL, + _("no storage vol with matching path '%s'"), + path); + return -1; + } + virReportSystemError(errno, + _("cannot stat file '%s'"), + path); + return -1; + } + + if (S_ISFIFO(sb->st_mode)) { + if (noerror) { + VIR_WARN("ignoring FIFO '%s'", path); + return -2; + } + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Volume path '%s' is a FIFO"), path); + return -1; + } else if (S_ISSOCK(sb->st_mode)) { + if (noerror) { + VIR_WARN("ignoring socket '%s'", path); + return -2; + } + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Volume path '%s' is a socket"), path); + return -1; + } + + /* O_NONBLOCK should only matter during open() for fifos and + * sockets, which we already filtered; but using it prevents a + * TOCTTOU race. However, later on we will want to read() the + * header from this fd, and virFileRead* routines require a + * blocking fd, so fix it up after verifying we avoided a race. + * + * Use of virFileOpenAs allows this path to open a file using + * the uid and gid as it was created in order to open. Since this + * path is not using O_CREAT or O_TMPFILE, mode is meaningless. + * Opening under user/group is especially important in an NFS + * root-squash environment. If the target path isn't on shared + * file system, the open will fail in the OPEN_FORK path. + */ + if ((fd = virFileOpenAs(path, O_RDONLY|O_NONBLOCK|O_NOCTTY, + 0, sb->st_uid, sb->st_gid, + VIR_FILE_OPEN_NOFORK|VIR_FILE_OPEN_FORK)) < 0) { + if ((errno == ENOENT || errno == ELOOP) && + S_ISLNK(sb->st_mode) && noerror) { + VIR_WARN("ignoring dangling symlink '%s'", path); + return -2; + } + if (errno == ENOENT && noerror) { + VIR_WARN("ignoring missing file '%s'", path); + return -2; + } + if (errno == ENXIO && noerror) { + VIR_WARN("ignoring missing fifo '%s'", path); + return -2; + } + if ((errno == EACCES || errno == EPERM) && noerror) { + VIR_WARN("ignoring permission error for '%s'", path); + return -2; + } + + virReportSystemError(errno, _("cannot open volume '%s'"), path); + return -1; + } + + if (fstat(fd, sb) < 0) { + virReportSystemError(errno, _("cannot stat file '%s'"), path); + VIR_FORCE_CLOSE(fd); + return -1; + } + + if (S_ISREG(sb->st_mode)) { + mode = VIR_STORAGE_VOL_OPEN_REG; + } else if (S_ISCHR(sb->st_mode)) { + mode = VIR_STORAGE_VOL_OPEN_CHAR; + } else if (S_ISBLK(sb->st_mode)) { + mode = VIR_STORAGE_VOL_OPEN_BLOCK; + } else if (S_ISDIR(sb->st_mode)) { + mode = VIR_STORAGE_VOL_OPEN_DIR; + + if (STREQ(base, ".") || + STREQ(base, "..")) { + VIR_FORCE_CLOSE(fd); + if (noerror) { + VIR_INFO("Skipping special dir '%s'", base); + return -2; + } + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Cannot use volume path '%s'"), path); + return -1; + } + } else { + VIR_FORCE_CLOSE(fd); + if (noerror) { + VIR_WARN("ignoring unexpected type for file '%s'", path); + return -2; + } + virReportError(VIR_ERR_INTERNAL_ERROR, + _("unexpected type for file '%s'"), path); + return -1; + } + + if (virSetBlocking(fd, true) < 0) { + VIR_FORCE_CLOSE(fd); + virReportSystemError(errno, _("unable to set blocking mode for '%s'"), + path); + return -1; + } + + if (!(mode & flags)) { + VIR_FORCE_CLOSE(fd); + if (noerror) { + VIR_INFO("Skipping volume '%s'", path); + return -2; + } + + virReportError(VIR_ERR_INTERNAL_ERROR, + _("unexpected storage mode for '%s'"), path); + return -1; + } + + return fd; +} + +/* virStorageIsPloop function checks whether given directory is ploop volume's + * directory. + */ +bool +virStorageBackendIsPloopDir(char *path) +{ + bool ret = false; + char *root = NULL; + char *desc = NULL; + if (virAsprintf(&root, "%s/root.hds", path) < 0) + return ret; + if (!virFileExists(root)) + goto cleanup; + if (virAsprintf(&desc, "%s/DiskDescriptor.xml", path) < 0) + goto cleanup; + if (!virFileExists(desc)) + goto cleanup; + + ret = true; + cleanup: + VIR_FREE(root); + VIR_FREE(desc); + return ret; +} + +/* In case of ploop volumes, path to volume is the path to the ploop + * directory. To get information about allocation, header information + * and etc. we need to perform virStorageBackendVolOpen and + * virStorageBackendUpdateVolTargetFd once again. + */ +int +virStorageBackendRedoPloopUpdate(virStorageSourcePtr target, struct stat *sb, + int *fd, unsigned int flags) +{ + char *path = NULL; + int ret = -1; + + if (virAsprintf(&path, "%s/root.hds", target->path) < 0) + return -1; + VIR_FORCE_CLOSE(*fd); + if ((*fd = virStorageBackendVolOpen(path, sb, flags)) < 0) + goto cleanup; + ret = virStorageBackendUpdateVolTargetInfoFD(target, *fd, sb); + + cleanup: + + VIR_FREE(path); + return ret; +} + +/* + * virStorageBackendUpdateVolTargetInfo + * @target: target definition ptr of volume to update + * @withBlockVolFormat: true if caller determined a block file + * @openflags: various VolOpenCheckMode flags to handle errors on open + * @readflags: VolReadErrorMode flags to handle read error after open + * is successful, but read is not. + * + * Returns 0 for success, -1 on a legitimate error condition, and -2 + * if the openflags used VIR_STORAGE_VOL_OPEN_NOERROR and some sort of + * open error occurred. It is up to the caller to handle. A -2 may also + * be returned if the caller passed a readflagsflag. + */ +int +virStorageBackendUpdateVolTargetInfo(virStorageSourcePtr target, + bool withBlockVolFormat, + unsigned int openflags, + unsigned int readflags) +{ + int ret, fd = -1; + struct stat sb; + char *buf = NULL; + ssize_t len = VIR_STORAGE_MAX_HEADER; + + if ((ret = virStorageBackendVolOpen(target->path, &sb, openflags)) < 0) + goto cleanup; + fd = ret; + + if ((ret = virStorageBackendUpdateVolTargetInfoFD(target, fd, &sb)) < 0) + goto cleanup; + + if (target->type == VIR_STORAGE_VOL_FILE && + target->format != VIR_STORAGE_FILE_NONE) { + if (S_ISDIR(sb.st_mode)) { + if (virStorageBackendIsPloopDir(target->path)) { + if ((ret = virStorageBackendRedoPloopUpdate(target, &sb, &fd, + openflags)) < 0) + goto cleanup; + target->format = VIR_STORAGE_FILE_PLOOP; + } else { + ret = 0; + goto cleanup; + } + } + + if (lseek(fd, 0, SEEK_SET) == (off_t)-1) { + virReportSystemError(errno, _("cannot seek to start of '%s'"), target->path); + ret = -1; + goto cleanup; + } + + if ((len = virFileReadHeaderFD(fd, len, &buf)) < 0) { + if (readflags & VIR_STORAGE_VOL_READ_NOERROR) { + VIR_WARN("ignoring failed header read for '%s'", + target->path); + ret = -2; + } else { + virReportSystemError(errno, + _("cannot read header '%s'"), + target->path); + ret = -1; + } + goto cleanup; + } + + if (virStorageSourceUpdateCapacity(target, buf, len, false) < 0) { + ret = -1; + goto cleanup; + } + } + + if (withBlockVolFormat) { + if ((ret = virStorageBackendDetectBlockVolFormatFD(target, fd, + readflags)) < 0) + goto cleanup; + } + + cleanup: + VIR_FORCE_CLOSE(fd); + VIR_FREE(buf); + return ret; +} + +/* + * virStorageBackendUpdateVolInfo + * @vol: Pointer to a volume storage definition + * @withBlockVolFormat: true if the caller determined a block file + * @openflags: various VolOpenCheckMode flags to handle errors on open + * @readflags: various VolReadErrorMode flags to handle errors on read + * + * Returns 0 for success, -1 on a legitimate error condition, and -2 + * if the openflags used VIR_STORAGE_VOL_OPEN_NOERROR and some sort of + * open error occurred. It is up to the caller to handle. + */ +int +virStorageBackendUpdateVolInfo(virStorageVolDefPtr vol, + bool withBlockVolFormat, + unsigned int openflags, + unsigned int readflags) +{ + int ret; + + if ((ret = virStorageBackendUpdateVolTargetInfo(&vol->target, + withBlockVolFormat, + openflags, readflags)) < 0) + return ret; + + if (vol->target.backingStore && + (ret = virStorageBackendUpdateVolTargetInfo(vol->target.backingStore, + withBlockVolFormat, + VIR_STORAGE_VOL_OPEN_DEFAULT | + VIR_STORAGE_VOL_OPEN_NOERROR, + readflags) < 0)) + return ret; + + return 0; +} + +/* + * virStorageBackendUpdateVolTargetInfoFD: + * @target: target definition ptr of volume to update + * @fd: fd of storage volume to update, via virStorageBackendOpenVol*, or -1 + * @sb: details about file (must match @fd, if that is provided) + * + * Returns 0 for success, -1 on a legitimate error condition. + */ +int +virStorageBackendUpdateVolTargetInfoFD(virStorageSourcePtr target, + int fd, + struct stat *sb) +{ +#if WITH_SELINUX + security_context_t filecon = NULL; +#endif + + if (virStorageSourceUpdateBackingSizes(target, fd, sb) < 0) + return -1; + + if (!target->perms && VIR_ALLOC(target->perms) < 0) + return -1; + target->perms->mode = sb->st_mode & S_IRWXUGO; + target->perms->uid = sb->st_uid; + target->perms->gid = sb->st_gid; + + if (!target->timestamps && VIR_ALLOC(target->timestamps) < 0) + return -1; + target->timestamps->atime = get_stat_atime(sb); + target->timestamps->btime = get_stat_birthtime(sb); + target->timestamps->ctime = get_stat_ctime(sb); + target->timestamps->mtime = get_stat_mtime(sb); + + VIR_FREE(target->perms->label); + +#if WITH_SELINUX + /* XXX: make this a security driver call */ + if (fd >= 0) { + if (fgetfilecon_raw(fd, &filecon) == -1) { + if (errno != ENODATA && errno != ENOTSUP) { + virReportSystemError(errno, + _("cannot get file context of '%s'"), + target->path); + return -1; + } + } else { + if (VIR_STRDUP(target->perms->label, filecon) < 0) { + freecon(filecon); + return -1; + } + freecon(filecon); + } + } +#endif + + return 0; +} + +bool +virStorageBackendPoolPathIsStable(const char *path) +{ + if (path == NULL || STREQ(path, "/dev") || STREQ(path, "/dev/")) + return false; + + if (!STRPREFIX(path, "/dev/")) + return false; + + return true; +} + +/* + * Given a volume path directly in /dev/XXX, iterate over the + * entries in the directory pool->def->target.path and find the + * first symlink pointing to the volume path. + * + * If, the target.path is /dev/, then return the original volume + * path. + * + * If no symlink is found, then return the original volume path + * + * Typically target.path is one of the /dev/disk/by-XXX dirs + * with stable paths. + * + * If 'loop' is true, we use a timeout loop to give dynamic paths + * a change to appear. + */ +char * +virStorageBackendStablePath(virStoragePoolObjPtr pool, + const char *devpath, + bool loop) +{ + DIR *dh; + struct dirent *dent; + char *stablepath; + int opentries = 0; + int retry = 0; + int direrr; + + /* Logical pools are under /dev but already have stable paths */ + if (pool->def->type == VIR_STORAGE_POOL_LOGICAL || + !virStorageBackendPoolPathIsStable(pool->def->target.path)) + goto ret_strdup; + + /* We loop here because /dev/disk/by-{id,path} may not have existed + * before we started this operation, so we have to give it some time to + * get created. + */ + reopen: + if (virDirOpenQuiet(&dh, pool->def->target.path) < 0) { + opentries++; + if (loop && errno == ENOENT && opentries < 50) { + usleep(100 * 1000); + goto reopen; + } + virReportSystemError(errno, + _("cannot read dir '%s'"), + pool->def->target.path); + return NULL; + } + + /* The pool is pointing somewhere like /dev/disk/by-path + * or /dev/disk/by-id, so we need to check all symlinks in + * the target directory and figure out which one points + * to this device node. + * + * And it might need some time till the stable path shows + * up, so add timeout to retry here. Ignore readdir failures, + * since we have a fallback. + */ + retry: + while ((direrr = virDirRead(dh, &dent, NULL)) > 0) { + if (virAsprintf(&stablepath, "%s/%s", + pool->def->target.path, + dent->d_name) == -1) { + VIR_DIR_CLOSE(dh); + return NULL; + } + + if (virFileLinkPointsTo(stablepath, devpath)) { + VIR_DIR_CLOSE(dh); + return stablepath; + } + + VIR_FREE(stablepath); + } + + if (!direrr && loop && ++retry < 100) { + usleep(100 * 1000); + goto retry; + } + + VIR_DIR_CLOSE(dh); + + ret_strdup: + /* Couldn't find any matching stable link so give back + * the original non-stable dev path + */ + + ignore_value(VIR_STRDUP(stablepath, devpath)); + + return stablepath; +} + +/* + * Check whether the ploop image has snapshots. + * return: -1 - failed to check + * 0 - no snapshots + * 1 - at least one snapshot + */ +static int +virStorageBackendPloopHasSnapshots(char *path) +{ + virCommandPtr cmd = NULL; + char *output = NULL; + char *snap_tool = NULL; + int ret = -1; + + snap_tool = virFindFileInPath("ploop"); + if (!snap_tool) { + virReportError(VIR_ERR_INTERNAL_ERROR, + "%s", _("unable to find ploop, please install " + "ploop tools")); + return ret; + } + + cmd = virCommandNewArgList(snap_tool, "snapshot-list", NULL); + virCommandAddArgFormat(cmd, "%s/DiskDescriptor.xml", path); + virCommandSetOutputBuffer(cmd, &output); + + if ((ret = virCommandRun(cmd, NULL)) < 0) + goto cleanup; + + if (!strstr(output, "root.hds.")) { + ret = 1; + goto cleanup; + } + ret = 0; + + cleanup: + VIR_FREE(output); + virCommandFree(cmd); + return ret; +} + +int +virStorageBackendVolUploadLocal(virConnectPtr conn ATTRIBUTE_UNUSED, + virStoragePoolObjPtr pool ATTRIBUTE_UNUSED, + virStorageVolDefPtr vol, + virStreamPtr stream, + unsigned long long offset, + unsigned long long len, + unsigned int flags) +{ + char *path = NULL; + char *target_path = vol->target.path; + int ret = -1; + int has_snap = 0; + + virCheckFlags(0, -1); + /* if volume has target format VIR_STORAGE_FILE_PLOOP + * we need to restore DiskDescriptor.xml, according to + * new contents of volume. This operation will be perfomed + * when volUpload is fully finished. */ + if (vol->target.format == VIR_STORAGE_FILE_PLOOP) { + /* Fail if the volume contains snapshots or we failed to check it.*/ + has_snap = virStorageBackendPloopHasSnapshots(vol->target.path); + if (has_snap < 0) { + goto cleanup; + } else if (!has_snap) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("can't upload volume, all existing snapshots" + " will be lost")); + goto cleanup; + } + + if (virAsprintf(&path, "%s/root.hds", vol->target.path) < 0) + return -1; + target_path = path; + } + + /* Not using O_CREAT because the file is required to already exist at + * this point */ + ret = virFDStreamOpenBlockDevice(stream, target_path, + offset, len, O_WRONLY); + + cleanup: + VIR_FREE(path); + return ret; +} + +int +virStorageBackendVolDownloadLocal(virConnectPtr conn ATTRIBUTE_UNUSED, + virStoragePoolObjPtr pool ATTRIBUTE_UNUSED, + virStorageVolDefPtr vol, + virStreamPtr stream, + unsigned long long offset, + unsigned long long len, + unsigned int flags) +{ + char *path = NULL; + char *target_path = vol->target.path; + int ret = -1; + int has_snap = 0; + + virCheckFlags(0, -1); + if (vol->target.format == VIR_STORAGE_FILE_PLOOP) { + has_snap = virStorageBackendPloopHasSnapshots(vol->target.path); + if (has_snap < 0) { + goto cleanup; + } else if (!has_snap) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("can't download volume, all existing snapshots" + " will be lost")); + goto cleanup; + } + if (virAsprintf(&path, "%s/root.hds", vol->target.path) < 0) + goto cleanup; + target_path = path; + } + + ret = virFDStreamOpenBlockDevice(stream, target_path, + offset, len, O_RDONLY); + + cleanup: + VIR_FREE(path); + return ret; +} + + +/* If the volume we're wiping is already a sparse file, we simply + * truncate and extend it to its original size, filling it with + * zeroes. This behavior is guaranteed by POSIX: + * + * http://www.opengroup.org/onlinepubs/9699919799/functions/ftruncate.html + * + * If fildes refers to a regular file, the ftruncate() function shall + * cause the size of the file to be truncated to length. If the size + * of the file previously exceeded length, the extra data shall no + * longer be available to reads on the file. If the file previously + * was smaller than this size, ftruncate() shall increase the size of + * the file. If the file size is increased, the extended area shall + * appear as if it were zero-filled. + */ +static int +virStorageBackendVolZeroSparseFileLocal(const char *path, + off_t size, + int fd) +{ + if (ftruncate(fd, 0) < 0) { + virReportSystemError(errno, + _("Failed to truncate volume with " + "path '%s' to 0 bytes"), + path); + return -1; + } + + if (ftruncate(fd, size) < 0) { + virReportSystemError(errno, + _("Failed to truncate volume with " + "path '%s' to %ju bytes"), + path, (uintmax_t)size); + return -1; + } + + return 0; +} + + +static int +virStorageBackendWipeLocal(const char *path, + int fd, + unsigned long long wipe_len, + size_t writebuf_length) +{ + int ret = -1, written = 0; + unsigned long long remaining = 0; + size_t write_size = 0; + char *writebuf = NULL; + + VIR_DEBUG("wiping start: 0 len: %llu", wipe_len); + + if (VIR_ALLOC_N(writebuf, writebuf_length) < 0) + goto cleanup; + + if (lseek(fd, 0, SEEK_SET) < 0) { + virReportSystemError(errno, + _("Failed to seek to the start in volume " + "with path '%s'"), + path); + goto cleanup; + } + + remaining = wipe_len; + while (remaining > 0) { + + write_size = (writebuf_length < remaining) ? writebuf_length : remaining; + written = safewrite(fd, writebuf, write_size); + if (written < 0) { + virReportSystemError(errno, + _("Failed to write %zu bytes to " + "storage volume with path '%s'"), + write_size, path); + + goto cleanup; + } + + remaining -= written; + } + + if (fdatasync(fd) < 0) { + virReportSystemError(errno, + _("cannot sync data to volume with path '%s'"), + path); + goto cleanup; + } + + VIR_DEBUG("Wrote %llu bytes to volume with path '%s'", wipe_len, path); + + ret = 0; + + cleanup: + VIR_FREE(writebuf); + return ret; +} + + +static int +virStorageBackendVolWipeLocalFile(const char *path, + unsigned int algorithm, + unsigned long long allocation) +{ + int ret = -1, fd = -1; + const char *alg_char = NULL; + struct stat st; + virCommandPtr cmd = NULL; + + fd = open(path, O_RDWR); + if (fd == -1) { + virReportSystemError(errno, + _("Failed to open storage volume with path '%s'"), + path); + goto cleanup; + } + + if (fstat(fd, &st) == -1) { + virReportSystemError(errno, + _("Failed to stat storage volume with path '%s'"), + path); + goto cleanup; + } + + switch ((virStorageVolWipeAlgorithm) algorithm) { + case VIR_STORAGE_VOL_WIPE_ALG_ZERO: + alg_char = "zero"; + break; + case VIR_STORAGE_VOL_WIPE_ALG_NNSA: + alg_char = "nnsa"; + break; + case VIR_STORAGE_VOL_WIPE_ALG_DOD: + alg_char = "dod"; + break; + case VIR_STORAGE_VOL_WIPE_ALG_BSI: + alg_char = "bsi"; + break; + case VIR_STORAGE_VOL_WIPE_ALG_GUTMANN: + alg_char = "gutmann"; + break; + case VIR_STORAGE_VOL_WIPE_ALG_SCHNEIER: + alg_char = "schneier"; + break; + case VIR_STORAGE_VOL_WIPE_ALG_PFITZNER7: + alg_char = "pfitzner7"; + break; + case VIR_STORAGE_VOL_WIPE_ALG_PFITZNER33: + alg_char = "pfitzner33"; + break; + case VIR_STORAGE_VOL_WIPE_ALG_RANDOM: + alg_char = "random"; + break; + case VIR_STORAGE_VOL_WIPE_ALG_TRIM: + virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", + _("'trim' algorithm not supported")); + goto cleanup; + case VIR_STORAGE_VOL_WIPE_ALG_LAST: + virReportError(VIR_ERR_INVALID_ARG, + _("unsupported algorithm %d"), + algorithm); + goto cleanup; + } + + VIR_DEBUG("Wiping file '%s' with algorithm '%s'", path, alg_char); + + if (algorithm != VIR_STORAGE_VOL_WIPE_ALG_ZERO) { + cmd = virCommandNew(SCRUB); + virCommandAddArgList(cmd, "-f", "-p", alg_char, path, NULL); + + if (virCommandRun(cmd, NULL) < 0) + goto cleanup; + + ret = 0; + } else { + if (S_ISREG(st.st_mode) && st.st_blocks < (st.st_size / DEV_BSIZE)) { + ret = virStorageBackendVolZeroSparseFileLocal(path, st.st_size, fd); + } else { + ret = virStorageBackendWipeLocal(path, + fd, + allocation, + st.st_blksize); + } + if (ret < 0) + goto cleanup; + } + + cleanup: + virCommandFree(cmd); + VIR_FORCE_CLOSE(fd); + return ret; +} + + +static int +virStorageBackendVolWipePloop(virStorageVolDefPtr vol, + unsigned int algorithm) +{ + virCommandPtr cmd = NULL; + char *target_path = NULL; + char *disk_desc = NULL; + char *create_tool = NULL; + + int ret = -1; + + create_tool = virFindFileInPath("ploop"); + if (!create_tool) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("unable to find ploop tools, please install them")); + return -1; + } + + if (virAsprintf(&target_path, "%s/root.hds", vol->target.path) < 0) + goto cleanup; + + if (virAsprintf(&disk_desc, "%s/DiskDescriptor.xml", vol->target.path) < 0) + goto cleanup; + + if (virStorageBackendVolWipeLocalFile(target_path, + algorithm, + vol->target.allocation) < 0) + goto cleanup; + + if (virFileRemove(disk_desc, 0, 0) < 0) { + virReportError(errno, _("Failed to delete DiskDescriptor.xml of volume '%s'"), + vol->target.path); + goto cleanup; + } + if (virFileRemove(target_path, 0, 0) < 0) { + virReportError(errno, _("failed to delete root.hds of volume '%s'"), + vol->target.path); + goto cleanup; + } + + cmd = virCommandNewArgList(create_tool, "init", "-s", NULL); + + virCommandAddArgFormat(cmd, "%lluM", VIR_DIV_UP(vol->target.capacity, + (1024 * 1024))); + virCommandAddArgList(cmd, "-t", "ext4", NULL); + virCommandAddArg(cmd, target_path); + ret = virCommandRun(cmd, NULL); + + cleanup: + VIR_FREE(disk_desc); + VIR_FREE(target_path); + VIR_FREE(create_tool); + virCommandFree(cmd); + return ret; +} + + +int +virStorageBackendVolWipeLocal(virConnectPtr conn ATTRIBUTE_UNUSED, + virStoragePoolObjPtr pool ATTRIBUTE_UNUSED, + virStorageVolDefPtr vol, + unsigned int algorithm, + unsigned int flags) +{ + int ret = -1; + + virCheckFlags(0, -1); + + VIR_DEBUG("Wiping volume with path '%s' and algorithm %u", + vol->target.path, algorithm); + + if (vol->target.format == VIR_STORAGE_FILE_PLOOP) { + ret = virStorageBackendVolWipePloop(vol, algorithm); + } else { + ret = virStorageBackendVolWipeLocalFile(vol->target.path, + algorithm, + vol->target.allocation); + } + + return ret; +} + + +/** + * virStorageBackendFindGlusterPoolSources: + * @host: host to detect volumes on + * @pooltype: src->format is set to this value + * @list: list of storage pool sources to be filled + * @report: report error if the 'gluster' cli tool is missing + * + * Looks up gluster volumes on @host and fills them to @list. + * + * Returns number of volumes on the host on success, or -1 on error. + */ +int +virStorageBackendFindGlusterPoolSources(const char *host, + int pooltype, + virStoragePoolSourceListPtr list, + bool report) +{ + char *glusterpath = NULL; + char *outbuf = NULL; + virCommandPtr cmd = NULL; + xmlDocPtr doc = NULL; + xmlXPathContextPtr ctxt = NULL; + xmlNodePtr *nodes = NULL; + virStoragePoolSource *src = NULL; + size_t i; + int nnodes; + int rc; + + int ret = -1; + + if (!(glusterpath = virFindFileInPath(GLUSTER_CLI))) { + if (report) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("'gluster' command line tool not found")); + return -1; + } else { + return 0; + } + } + + cmd = virCommandNewArgList(glusterpath, + "--xml", + "--log-file=/dev/null", + "volume", "info", "all", NULL); + + virCommandAddArgFormat(cmd, "--remote-host=%s", host); + virCommandSetOutputBuffer(cmd, &outbuf); + + if (virCommandRun(cmd, &rc) < 0) + goto cleanup; + + if (rc != 0) { + ret = 0; + goto cleanup; + } + + if (!(doc = virXMLParseStringCtxt(outbuf, _("(gluster_cli_output)"), + &ctxt))) + goto cleanup; + + if ((nnodes = virXPathNodeSet("//volumes/volume", ctxt, &nodes)) < 0) + goto cleanup; + + for (i = 0; i < nnodes; i++) { + ctxt->node = nodes[i]; + + if (!(src = virStoragePoolSourceListNewSource(list))) + goto cleanup; + + if (!(src->dir = virXPathString("string(//name)", ctxt))) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("failed to extract gluster volume name")); + goto cleanup; + } + + if (VIR_ALLOC_N(src->hosts, 1) < 0) + goto cleanup; + src->nhost = 1; + + if (VIR_STRDUP(src->hosts[0].name, host) < 0) + goto cleanup; + + src->format = pooltype; + } + + ret = nnodes; + + cleanup: + VIR_FREE(nodes); + xmlXPathFreeContext(ctxt); + xmlFreeDoc(doc); + VIR_FREE(outbuf); + virCommandFree(cmd); + VIR_FREE(glusterpath); + return ret; +} + + +#if WITH_BLKID + +typedef enum { + VIR_STORAGE_BLKID_PROBE_ERROR = -1, + VIR_STORAGE_BLKID_PROBE_UNDEFINED, /* Nothing found */ + VIR_STORAGE_BLKID_PROBE_UNKNOWN, /* Don't know libvirt fs/part type */ + VIR_STORAGE_BLKID_PROBE_MATCH, /* Matches the on disk format */ + VIR_STORAGE_BLKID_PROBE_DIFFERENT, /* Format doesn't match on disk format */ +} virStorageBackendBLKIDProbeResult; + +/* + * Utility function to probe for a file system on the device using the + * blkid "superblock" (e.g. default) APIs. + * + * NB: In general this helper will handle the virStoragePoolFormatFileSystem + * format types; however, if called from the Disk path, the initial fstype + * check will fail forcing the usage of the ProbePart helper. + * + * Returns virStorageBackendBLKIDProbeResult enum + */ +static virStorageBackendBLKIDProbeResult +virStorageBackendBLKIDFindFS(blkid_probe probe, + const char *device, + const char *format) +{ + const char *fstype = NULL; + + /* Make sure we're doing a superblock probe from the start */ + blkid_probe_enable_superblocks(probe, true); + blkid_probe_reset_superblocks_filter(probe); + + if (blkid_do_probe(probe) != 0) { + VIR_INFO("No filesystem found on device '%s'", device); + return VIR_STORAGE_BLKID_PROBE_UNDEFINED; + } + + if (blkid_probe_lookup_value(probe, "TYPE", &fstype, NULL) == 0) { + if (STREQ(fstype, format)) + return VIR_STORAGE_BLKID_PROBE_MATCH; + + return VIR_STORAGE_BLKID_PROBE_DIFFERENT; + } + + if (blkid_known_fstype(format) == 0) + return VIR_STORAGE_BLKID_PROBE_UNKNOWN; + + return VIR_STORAGE_BLKID_PROBE_ERROR; +} + + +/* + * Utility function to probe for a partition on the device using the + * blkid "partitions" APIs. + * + * NB: In general, this API will be validating the virStoragePoolFormatDisk + * format types. + * + * Returns virStorageBackendBLKIDProbeResult enum + */ +static virStorageBackendBLKIDProbeResult +virStorageBackendBLKIDFindPart(blkid_probe probe, + const char *device, + const char *format) +{ + const char *pttype = NULL; + + /* A blkid_known_pttype on "dvh" and "pc98" returns a failure; + * however, the blkid_do_probe for "dvh" returns "sgi" and + * for "pc98" it returns "dos". So since those will cause problems + * with startup comparison, let's just treat them as UNKNOWN causing + * the caller to fallback to using PARTED */ + if (STREQ(format, "dvh") || STREQ(format, "pc98")) + return VIR_STORAGE_BLKID_PROBE_UNKNOWN; + + /* Make sure we're doing a partitions probe from the start */ + blkid_probe_enable_partitions(probe, true); + blkid_probe_reset_partitions_filter(probe); + + if (blkid_do_probe(probe) != 0) { + VIR_INFO("No partition found on device '%s'", device); + return VIR_STORAGE_BLKID_PROBE_UNDEFINED; + } + + if (blkid_probe_lookup_value(probe, "PTTYPE", &pttype, NULL) == 0) { + if (STREQ(pttype, format)) + return VIR_STORAGE_BLKID_PROBE_MATCH; + + return VIR_STORAGE_BLKID_PROBE_DIFFERENT; + } + + if (blkid_known_pttype(format) == 0) + return VIR_STORAGE_BLKID_PROBE_UNKNOWN; + + return VIR_STORAGE_BLKID_PROBE_ERROR; +} + + +/* + * @device: Path to device + * @format: Desired format + * @writelabel: True if desire to write the label + * + * Use the blkid_ APIs in order to get details regarding whether a file + * system or partition exists on the disk already. + * + * Returns: + * -2: Force usage of PARTED for unknown types + * -1: An error was encountered, with error message set + * 0: No file system found + */ +static int +virStorageBackendBLKIDFindEmpty(const char *device, + const char *format, + bool writelabel) +{ + + int ret = -1; + int rc; + blkid_probe probe = NULL; + + VIR_DEBUG("Probe for existing filesystem/partition format %s on device %s", + format, device); + + if (!(probe = blkid_new_probe_from_filename(device))) { + virReportError(VIR_ERR_STORAGE_PROBE_FAILED, + _("Failed to create filesystem probe for device %s"), + device); + return -1; + } + + /* Look for something on FS, if it either doesn't recognize the + * format type as a valid FS format type or it doesn't find a valid + * format type on the device, then perform the same check using + * partition probing. */ + rc = virStorageBackendBLKIDFindFS(probe, device, format); + if (rc == VIR_STORAGE_BLKID_PROBE_UNDEFINED || + rc == VIR_STORAGE_BLKID_PROBE_UNKNOWN) { + + rc = virStorageBackendBLKIDFindPart(probe, device, format); + } + + switch (rc) { + case VIR_STORAGE_BLKID_PROBE_UNDEFINED: + if (writelabel) + ret = 0; + else + virReportError(VIR_ERR_STORAGE_PROBE_FAILED, + _("Device '%s' is unrecognized, requires build"), + device); + break; + + case VIR_STORAGE_BLKID_PROBE_ERROR: + virReportError(VIR_ERR_STORAGE_PROBE_FAILED, + _("Failed to probe for format type '%s'"), format); + break; + + case VIR_STORAGE_BLKID_PROBE_UNKNOWN: + ret = -2; + break; + + case VIR_STORAGE_BLKID_PROBE_MATCH: + if (writelabel) + virReportError(VIR_ERR_STORAGE_POOL_BUILT, + _("Device '%s' already formatted using '%s'"), + device, format); + else + ret = 0; + break; + + case VIR_STORAGE_BLKID_PROBE_DIFFERENT: + if (writelabel) + virReportError(VIR_ERR_STORAGE_POOL_BUILT, + _("Format of device '%s' does not match the " + "expected format '%s', forced overwrite is " + "necessary"), + device, format); + else + virReportError(VIR_ERR_OPERATION_INVALID, + _("Format of device '%s' does not match the " + "expected format '%s'"), + device, format); + break; + } + + if (ret == 0 && blkid_do_probe(probe) != 1) { + virReportError(VIR_ERR_STORAGE_PROBE_FAILED, "%s", + _("Found additional probes to run, probing may " + "be incorrect")); + ret = -1; + } + + blkid_free_probe(probe); + + return ret; +} + +#else /* #if WITH_BLKID */ + +static int +virStorageBackendBLKIDFindEmpty(const char *device ATTRIBUTE_UNUSED, + const char *format ATTRIBUTE_UNUSED, + bool writelabel ATTRIBUTE_UNUSED) +{ + return -2; +} + +#endif /* #if WITH_BLKID */ + + +#if WITH_STORAGE_DISK + +typedef enum { + VIR_STORAGE_PARTED_ERROR = -1, + VIR_STORAGE_PARTED_MATCH, /* Valid label found and matches format */ + VIR_STORAGE_PARTED_DIFFERENT, /* Valid label found but not match format */ + VIR_STORAGE_PARTED_UNKNOWN, /* No or unrecognized label */ + VIR_STORAGE_PARTED_NOPTTYPE, /* Did not find the Partition Table type */ + VIR_STORAGE_PARTED_PTTYPE_UNK, /* Partition Table type unknown*/ +} virStorageBackendPARTEDResult; + +/** + * Check for a valid disk label (partition table) on device using + * the PARTED command + * + * returns virStorageBackendPARTEDResult + */ +static virStorageBackendPARTEDResult +virStorageBackendPARTEDFindLabel(const char *device, + const char *format) +{ + const char *const args[] = { + device, "print", "--script", NULL, + }; + virCommandPtr cmd = virCommandNew(PARTED); + char *output = NULL; + char *error = NULL; + char *start, *end; + int ret = VIR_STORAGE_PARTED_ERROR; + + virCommandAddArgSet(cmd, args); + virCommandAddEnvString(cmd, "LC_ALL=C"); + virCommandSetOutputBuffer(cmd, &output); + virCommandSetErrorBuffer(cmd, &error); + + /* if parted succeeds we have a valid partition table */ + ret = virCommandRun(cmd, NULL); + if (ret < 0) { + if ((output && strstr(output, "unrecognised disk label")) || + (error && strstr(error, "unrecognised disk label"))) { + ret = VIR_STORAGE_PARTED_UNKNOWN; + } + goto cleanup; + } + + /* Search for "Partition Table:" in the output. If not present, + * then we cannot validate the partition table type. + */ + if (!(start = strstr(output, "Partition Table: ")) || + !(end = strstr(start, "\n"))) { + VIR_DEBUG("Unable to find tag in output: %s", output); + ret = VIR_STORAGE_PARTED_NOPTTYPE; + goto cleanup; + } + start += strlen("Partition Table: "); + *end = '\0'; + + /* on disk it's "msdos", but we document/use "dos" so deal with it here */ + if (STREQ(start, "msdos")) + start += 2; + + /* Make sure we know about this type */ + if (virStoragePoolFormatDiskTypeFromString(start) < 0) { + ret = VIR_STORAGE_PARTED_PTTYPE_UNK; + goto cleanup; + } + + /* Does the on disk match what the pool desired? */ + if (STREQ(start, format)) + ret = VIR_STORAGE_PARTED_MATCH; + + ret = VIR_STORAGE_PARTED_DIFFERENT; + + cleanup: + virCommandFree(cmd); + VIR_FREE(output); + VIR_FREE(error); + return ret; +} + + +/** + * Determine whether the label on the disk is valid or in a known format + * for the purpose of rewriting the label during build or being able to + * start a pool on a device. + * + * When 'writelabel' is true, if we find a valid disk label on the device, + * then we shouldn't be attempting to write as the volume may contain + * data. Force the usage of the overwrite flag to the build command in + * order to be certain. When the disk label is unrecognized, then it + * should be safe to write. + * + * When 'writelabel' is false, only if we find a valid disk label on the + * device should we allow the start since for this path we won't be + * rewriting the label. + * + * Return: 0 if it's OK + * -1 if something's wrong + */ +static int +virStorageBackendPARTEDValidLabel(const char *device, + const char *format, + bool writelabel) +{ + int ret = -1; + virStorageBackendPARTEDResult check; + + check = virStorageBackendPARTEDFindLabel(device, format); + switch (check) { + case VIR_STORAGE_PARTED_ERROR: + virReportError(VIR_ERR_OPERATION_FAILED, "%s", + _("Error checking for disk label, failed to get " + "disk partition information")); + break; + + case VIR_STORAGE_PARTED_MATCH: + if (writelabel) + virReportError(VIR_ERR_OPERATION_INVALID, + _("Disk label already formatted using '%s'"), + format); + else + ret = 0; + break; + + case VIR_STORAGE_PARTED_DIFFERENT: + virReportError(VIR_ERR_OPERATION_FAILED, "%s", + _("Known, but different label format present, " + "requires build --overwrite")); + break; + + case VIR_STORAGE_PARTED_UNKNOWN: + if (writelabel) + ret = 0; + else + virReportError(VIR_ERR_OPERATION_FAILED, "%s", + _("Unrecognized disk label found, requires build")); + break; + + case VIR_STORAGE_PARTED_NOPTTYPE: + virReportError(VIR_ERR_OPERATION_FAILED, "%s", + _("Unable to determine Partition Type, " + "requires build --overwrite")); + break; + + case VIR_STORAGE_PARTED_PTTYPE_UNK: + virReportError(VIR_ERR_OPERATION_FAILED, "%s", + _("Unknown Partition Type, requires build --overwrite")); + break; + } + + return ret; +} + +#else + +static int +virStorageBackendPARTEDValidLabel(const char *device ATTRIBUTE_UNUSED, + const char *format ATTRIBUTE_UNUSED, + bool writelabel ATTRIBUTE_UNUSED) +{ + return -2; +} + + +#endif /* #if WITH_STORAGE_DISK */ + + +/* virStorageBackendDeviceIsEmpty: + * @devpath: Path to the device to check + * @format: Desired format string + * @writelabel: True if the caller expects to write the label + * + * Check if the @devpath has some sort of known file system using the + * BLKID API if available. + * + * Returns true if the probe deems the device has nothing valid on it + * or when we cannot check and we're not writing the label. + * + * Returns false if the probe finds something + */ +bool +virStorageBackendDeviceIsEmpty(const char *devpath, + const char *format, + bool writelabel) +{ + int ret; + + if ((ret = virStorageBackendBLKIDFindEmpty(devpath, format, + writelabel)) == -2) + ret = virStorageBackendPARTEDValidLabel(devpath, format, writelabel); + + if (ret == -2 && !writelabel) + ret = 0; + + if (ret == -2) { + virReportError(VIR_ERR_OPERATION_INVALID, + _("Unable to probe '%s' for existing data, " + "forced overwrite is necessary"), + devpath); + } + + return ret == 0; +} diff --git a/src/storage/storage_util.h b/src/storage/storage_util.h new file mode 100644 index 000000000..0555ae1e8 --- /dev/null +++ b/src/storage/storage_util.h @@ -0,0 +1,148 @@ +/* + * storage_util.h: utility functions for storage driver + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <http://www.gnu.org/licenses/>. + */ + +#ifndef __VIR_STORAGE_UTIL_H__ +# define __VIR_STORAGE_UTIL_H__ + +# include <sys/stat.h> + +# include "internal.h" +# include "storage_conf.h" +# include "vircommand.h" +# include "storage_driver.h" +# include "storage_backend.h" + +/* File creation/cloning functions used for cloning between backends */ +int virStorageBackendCreateRaw(virConnectPtr conn, + virStoragePoolObjPtr pool, + virStorageVolDefPtr vol, + virStorageVolDefPtr inputvol, + unsigned int flags); + +int virStorageBackendCreateQemuImg(virConnectPtr conn, + virStoragePoolObjPtr pool, + virStorageVolDefPtr vol, + virStorageVolDefPtr inputvol, + unsigned int flags); + +int virStorageBackendCreatePloop(virConnectPtr conn, + virStoragePoolObjPtr pool, + virStorageVolDefPtr vol, + virStorageVolDefPtr inputvol, + unsigned int flags); + +int virStoragePloopResize(virStorageVolDefPtr vol, + unsigned long long capacity); + +int virStorageBackendRedoPloopUpdate(virStorageSourcePtr target, + struct stat *sb, int *fd, + unsigned int flags); +bool virStorageBackendIsPloopDir(char *path); + +virStorageBackendBuildVolFrom +virStorageBackendGetBuildVolFromFunction(virStorageVolDefPtr vol, + virStorageVolDefPtr inputvol); + +int virStorageBackendFindGlusterPoolSources(const char *host, + int pooltype, + virStoragePoolSourceListPtr list, + bool report); + +int virStorageBackendVolUploadLocal(virConnectPtr conn, + virStoragePoolObjPtr pool, + virStorageVolDefPtr vol, + virStreamPtr stream, + unsigned long long offset, + unsigned long long len, + unsigned int flags); +int virStorageBackendVolDownloadLocal(virConnectPtr conn, + virStoragePoolObjPtr pool, + virStorageVolDefPtr vol, + virStreamPtr stream, + unsigned long long offset, + unsigned long long len, + unsigned int flags); + +int virStorageBackendVolWipeLocal(virConnectPtr conn, + virStoragePoolObjPtr pool, + virStorageVolDefPtr vol, + unsigned int algorithm, + unsigned int flags); + +bool virStorageBackendDeviceIsEmpty(const char *devpath, + const char *format, + bool writelabel); + +/* VolOpenCheckMode flags */ +enum { + VIR_STORAGE_VOL_OPEN_NOERROR = 1 << 0, /* don't error if unexpected type + * encountered, just warn */ + VIR_STORAGE_VOL_OPEN_REG = 1 << 1, /* regular files okay */ + VIR_STORAGE_VOL_OPEN_BLOCK = 1 << 2, /* block files okay */ + VIR_STORAGE_VOL_OPEN_CHAR = 1 << 3, /* char files okay */ + VIR_STORAGE_VOL_OPEN_DIR = 1 << 4, /* directories okay */ +}; + +/* VolReadErrorMode flags + * If flag is present, then operation won't cause fatal error for + * specified operation, rather a VIR_WARN will be issued and a -2 returned + * for function call + */ +enum { + VIR_STORAGE_VOL_READ_NOERROR = 1 << 0, /* ignore *read errors */ +}; + +# define VIR_STORAGE_VOL_OPEN_DEFAULT (VIR_STORAGE_VOL_OPEN_REG |\ + VIR_STORAGE_VOL_OPEN_BLOCK) + +int virStorageBackendVolOpen(const char *path, struct stat *sb, + unsigned int flags) + ATTRIBUTE_RETURN_CHECK + ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2); + +# define VIR_STORAGE_DEFAULT_POOL_PERM_MODE 0755 +# define VIR_STORAGE_DEFAULT_VOL_PERM_MODE 0600 + +int virStorageBackendUpdateVolInfo(virStorageVolDefPtr vol, + bool withBlockVolFormat, + unsigned int openflags, + unsigned int readflags); +int virStorageBackendUpdateVolTargetInfo(virStorageSourcePtr target, + bool withBlockVolFormat, + unsigned int openflags, + unsigned int readflags); +int virStorageBackendUpdateVolTargetInfoFD(virStorageSourcePtr target, + int fd, + struct stat *sb); + +bool virStorageBackendPoolPathIsStable(const char *path); +char *virStorageBackendStablePath(virStoragePoolObjPtr pool, + const char *devpath, + bool loop); + +virCommandPtr +virStorageBackendCreateQemuImgCmdFromVol(virConnectPtr conn, + virStoragePoolObjPtr pool, + virStorageVolDefPtr vol, + virStorageVolDefPtr inputvol, + unsigned int flags, + const char *create_tool, + int imgformat, + const char *secretPath); + +#endif /* __VIR_STORAGE_UTIL_H__ */ diff --git a/tests/storagevolxml2argvtest.c b/tests/storagevolxml2argvtest.c index e300821f8..bf9dbe5e0 100644 --- a/tests/storagevolxml2argvtest.c +++ b/tests/storagevolxml2argvtest.c @@ -3,7 +3,7 @@ #include "internal.h" #include "testutils.h" #include "datatypes.h" -#include "storage/storage_backend.h" +#include "storage/storage_util.h" #include "testutilsqemu.h" #include "virstring.h" -- 2.11.0

On 01/18/2017 06:36 AM, Peter Krempa wrote:
The file became a garbage dump for all kinds of utility functions over time. Move them to a separate file so that the files can become a clean interface for the storage backends. --- po/POTFILES.in | 1 + src/Makefile.am | 3 +- src/storage/storage_backend.c | 2950 +------------------------------- src/storage/storage_backend.h | 124 -- src/storage/storage_backend_disk.c | 1 + src/storage/storage_backend_fs.c | 1 + src/storage/storage_backend_gluster.c | 1 + src/storage/storage_backend_iscsi.c | 1 + src/storage/storage_backend_logical.c | 1 + src/storage/storage_backend_mpath.c | 1 + src/storage/storage_backend_rbd.c | 1 + src/storage/storage_backend_scsi.c | 1 + src/storage/storage_backend_sheepdog.c | 1 + src/storage/storage_backend_zfs.c | 1 + src/storage/storage_driver.c | 1 + src/storage/storage_util.c | 2915 +++++++++++++++++++++++++++++++ src/storage/storage_util.h | 148 ++ tests/storagevolxml2argvtest.c | 2 +- 18 files changed, 3112 insertions(+), 3042 deletions(-) create mode 100644 src/storage/storage_util.c create mode 100644 src/storage/storage_util.h
This fails to apply even for a 3.0 branch (not withstanding of course the two patches I pushed this morning that were ACK'd but held for 3.0.0 to be released). Conceptually though this would seem to be OK - maybe you could just provide a/your remote branch (wasn't sure which of the git://pipo.sk/pipo/libvirt.git/ ones it would be and I didn't want to search)... John I wonder how much of this "generalization" will be useful for the virtuozzo vstorage patches that were posted yesterday...

On Wed, Jan 18, 2017 at 10:56:52 -0500, John Ferlan wrote:
On 01/18/2017 06:36 AM, Peter Krempa wrote:
The file became a garbage dump for all kinds of utility functions over time. Move them to a separate file so that the files can become a clean interface for the storage backends. --- po/POTFILES.in | 1 + src/Makefile.am | 3 +- src/storage/storage_backend.c | 2950 +------------------------------- src/storage/storage_backend.h | 124 -- src/storage/storage_backend_disk.c | 1 + src/storage/storage_backend_fs.c | 1 + src/storage/storage_backend_gluster.c | 1 + src/storage/storage_backend_iscsi.c | 1 + src/storage/storage_backend_logical.c | 1 + src/storage/storage_backend_mpath.c | 1 + src/storage/storage_backend_rbd.c | 1 + src/storage/storage_backend_scsi.c | 1 + src/storage/storage_backend_sheepdog.c | 1 + src/storage/storage_backend_zfs.c | 1 + src/storage/storage_driver.c | 1 + src/storage/storage_util.c | 2915 +++++++++++++++++++++++++++++++ src/storage/storage_util.h | 148 ++ tests/storagevolxml2argvtest.c | 2 +- 18 files changed, 3112 insertions(+), 3042 deletions(-) create mode 100644 src/storage/storage_util.c create mode 100644 src/storage/storage_util.h
This fails to apply even for a 3.0 branch (not withstanding of course the two patches I pushed this morning that were ACK'd but held for 3.0.0 to be released).
I think it conflicts with: commit d04bb05fb7e9bf057b88032ffcd6709d1b70daef Author: John Ferlan <jferlan@redhat.com> Date: Tue Dec 6 06:17:20 2016 -0500 storage: Fix virStorageBackendUpdateVolTargetInfo type check
Conceptually though this would seem to be OK - maybe you could just provide a/your remote branch (wasn't sure which of the git://pipo.sk/pipo/libvirt.git/ ones it would be and I didn't want to search)...
I did not upload it yet to the branch, I did expect conflicts, but not that quick :) Let me fix it and I'll upload it in a while. Peter

The file became a garbage dump for all kinds of utility functions over time. Move them to a separate file so that the files can become a clean interface for the storage backends. --- po/POTFILES.in | 1 + src/Makefile.am | 3 +- src/storage/storage_backend.c | 2954 +------------------------------- src/storage/storage_backend.h | 125 -- src/storage/storage_backend_disk.c | 1 + src/storage/storage_backend_fs.c | 1 + src/storage/storage_backend_gluster.c | 1 + src/storage/storage_backend_iscsi.c | 1 + src/storage/storage_backend_logical.c | 1 + src/storage/storage_backend_mpath.c | 1 + src/storage/storage_backend_rbd.c | 1 + src/storage/storage_backend_scsi.c | 1 + src/storage/storage_backend_sheepdog.c | 1 + src/storage/storage_backend_zfs.c | 1 + src/storage/storage_driver.c | 1 + src/storage/storage_util.c | 2919 +++++++++++++++++++++++++++++++ src/storage/storage_util.h | 149 ++ tests/storagevolxml2argvtest.c | 2 +- 18 files changed, 3117 insertions(+), 3047 deletions(-) create mode 100644 src/storage/storage_util.c create mode 100644 src/storage/storage_util.h diff --git a/po/POTFILES.in b/po/POTFILES.in index 59efd9190..e464c045d 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -177,6 +177,7 @@ src/storage/storage_backend_scsi.c src/storage/storage_backend_sheepdog.c src/storage/storage_backend_zfs.c src/storage/storage_driver.c +src/storage/storage_util.c src/test/test_driver.c src/uml/uml_conf.c src/uml/uml_driver.c diff --git a/src/Makefile.am b/src/Makefile.am index 21a78e07b..348bd2a30 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -972,7 +972,8 @@ SECRET_DRIVER_SOURCES = \ # Storage backend specific impls STORAGE_DRIVER_SOURCES = \ storage/storage_driver.h storage/storage_driver.c \ - storage/storage_backend.h storage/storage_backend.c + storage/storage_backend.h storage/storage_backend.c \ + storage/storage_util.h storage/storage_util.c STORAGE_DRIVER_FS_SOURCES = \ storage/storage_backend_fs.h storage/storage_backend_fs.c diff --git a/src/storage/storage_backend.c b/src/storage/storage_backend.c index 5497eaa0f..92b08a2b4 100644 --- a/src/storage/storage_backend.c +++ b/src/storage/storage_backend.c @@ -24,54 +24,15 @@ #include <config.h> #include <string.h> -#include <stdio.h> -#include <regex.h> -#include <sys/types.h> -#include <sys/wait.h> -#include <unistd.h> -#include <fcntl.h> #include <sys/stat.h> -#include <sys/param.h> -#include <dirent.h> -#include "dirname.h" -#ifdef __linux__ -# include <sys/ioctl.h> -# include <linux/fs.h> -# ifndef FS_NOCOW_FL -# define FS_NOCOW_FL 0x00800000 /* Do not cow file */ -# endif -#endif - -#if WITH_BLKID -# include <blkid/blkid.h> -#endif - -#if WITH_SELINUX -# include <selinux/selinux.h> -#endif - -#if HAVE_LINUX_BTRFS_H -# include <linux/btrfs.h> -#endif #include "datatypes.h" #include "virerror.h" #include "viralloc.h" #include "internal.h" -#include "secret_conf.h" -#include "secret_util.h" -#include "vircrypto.h" -#include "viruuid.h" #include "virstoragefile.h" #include "storage_backend.h" #include "virlog.h" -#include "virfile.h" -#include "virjson.h" -#include "virqemu.h" -#include "stat-time.h" -#include "virstring.h" -#include "virxml.h" -#include "fdstream.h" #if WITH_STORAGE_LVM # include "storage_backend_logical.h" @@ -159,2906 +120,59 @@ static virStorageFileBackendPtr fileBackends[] = { }; -#define READ_BLOCK_SIZE_DEFAULT (1024 * 1024) -#define WRITE_BLOCK_SIZE_DEFAULT (4 * 1024) - -/* - * Perform the O(1) btrfs clone operation, if possible. - * Upon success, return 0. Otherwise, return -1 and set errno. - */ -#if HAVE_LINUX_BTRFS_H -static inline int -btrfsCloneFile(int dest_fd, int src_fd) -{ - return ioctl(dest_fd, BTRFS_IOC_CLONE, src_fd); -} -#else -static inline int -btrfsCloneFile(int dest_fd ATTRIBUTE_UNUSED, - int src_fd ATTRIBUTE_UNUSED) -{ - errno = ENOTSUP; - return -1; -} -#endif - -static int ATTRIBUTE_NONNULL(2) -virStorageBackendCopyToFD(virStorageVolDefPtr vol, - virStorageVolDefPtr inputvol, - int fd, - unsigned long long *total, - bool want_sparse, - bool reflink_copy) -{ - int inputfd = -1; - int amtread = -1; - int ret = 0; - size_t rbytes = READ_BLOCK_SIZE_DEFAULT; - int wbytes = 0; - int interval; - char *zerobuf = NULL; - char *buf = NULL; - struct stat st; - - if ((inputfd = open(inputvol->target.path, O_RDONLY)) < 0) { - ret = -errno; - virReportSystemError(errno, - _("could not open input path '%s'"), - inputvol->target.path); - goto cleanup; - } - -#ifdef __linux__ - if (ioctl(fd, BLKBSZGET, &wbytes) < 0) - wbytes = 0; -#endif - if ((wbytes == 0) && fstat(fd, &st) == 0) - wbytes = st.st_blksize; - if (wbytes < WRITE_BLOCK_SIZE_DEFAULT) - wbytes = WRITE_BLOCK_SIZE_DEFAULT; - - if (VIR_ALLOC_N(zerobuf, wbytes) < 0) { - ret = -errno; - goto cleanup; - } - - if (VIR_ALLOC_N(buf, rbytes) < 0) { - ret = -errno; - goto cleanup; - } - - if (reflink_copy) { - if (btrfsCloneFile(fd, inputfd) < 0) { - ret = -errno; - virReportSystemError(errno, - _("failed to clone files from '%s'"), - inputvol->target.path); - goto cleanup; - } else { - VIR_DEBUG("btrfs clone finished."); - goto cleanup; - } - } - - while (amtread != 0) { - int amtleft; - - if (*total < rbytes) - rbytes = *total; - - if ((amtread = saferead(inputfd, buf, rbytes)) < 0) { - ret = -errno; - virReportSystemError(errno, - _("failed reading from file '%s'"), - inputvol->target.path); - goto cleanup; - } - *total -= amtread; - - /* Loop over amt read in 512 byte increments, looking for sparse - * blocks */ - amtleft = amtread; - do { - interval = ((wbytes > amtleft) ? amtleft : wbytes); - int offset = amtread - amtleft; - - if (want_sparse && memcmp(buf+offset, zerobuf, interval) == 0) { - if (lseek(fd, interval, SEEK_CUR) < 0) { - ret = -errno; - virReportSystemError(errno, - _("cannot extend file '%s'"), - vol->target.path); - goto cleanup; - } - } else if (safewrite(fd, buf+offset, interval) < 0) { - ret = -errno; - virReportSystemError(errno, - _("failed writing to file '%s'"), - vol->target.path); - goto cleanup; - - } - } while ((amtleft -= interval) > 0); - } - - if (fdatasync(fd) < 0) { - ret = -errno; - virReportSystemError(errno, _("cannot sync data to file '%s'"), - vol->target.path); - goto cleanup; - } - - - if (VIR_CLOSE(inputfd) < 0) { - ret = -errno; - virReportSystemError(errno, - _("cannot close file '%s'"), - inputvol->target.path); - goto cleanup; - } - inputfd = -1; - - cleanup: - VIR_FORCE_CLOSE(inputfd); - - VIR_FREE(zerobuf); - VIR_FREE(buf); - - return ret; -} - -static int -virStorageBackendCreateBlockFrom(virConnectPtr conn ATTRIBUTE_UNUSED, - virStoragePoolObjPtr pool ATTRIBUTE_UNUSED, - virStorageVolDefPtr vol, - virStorageVolDefPtr inputvol, - unsigned int flags) -{ - int fd = -1; - int ret = -1; - unsigned long long remain; - struct stat st; - gid_t gid; - uid_t uid; - mode_t mode; - bool reflink_copy = false; - - virCheckFlags(VIR_STORAGE_VOL_CREATE_PREALLOC_METADATA | - VIR_STORAGE_VOL_CREATE_REFLINK, - -1); - - if (flags & VIR_STORAGE_VOL_CREATE_PREALLOC_METADATA) { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", - _("metadata preallocation is not supported for block " - "volumes")); - goto cleanup; - } - - if (flags & VIR_STORAGE_VOL_CREATE_REFLINK) - reflink_copy = true; - - if ((fd = open(vol->target.path, O_RDWR)) < 0) { - virReportSystemError(errno, - _("cannot create path '%s'"), - vol->target.path); - goto cleanup; - } - - remain = vol->target.capacity; - - if (inputvol) { - if (virStorageBackendCopyToFD(vol, inputvol, fd, &remain, - false, reflink_copy) < 0) - goto cleanup; - } - - if (fstat(fd, &st) == -1) { - virReportSystemError(errno, _("stat of '%s' failed"), - vol->target.path); - goto cleanup; - } - uid = (vol->target.perms->uid != st.st_uid) ? vol->target.perms->uid - : (uid_t) -1; - gid = (vol->target.perms->gid != st.st_gid) ? vol->target.perms->gid - : (gid_t) -1; - if (((uid != (uid_t) -1) || (gid != (gid_t) -1)) - && (fchown(fd, uid, gid) < 0)) { - virReportSystemError(errno, - _("cannot chown '%s' to (%u, %u)"), - vol->target.path, (unsigned int) uid, - (unsigned int) gid); - goto cleanup; - } - - mode = (vol->target.perms->mode == (mode_t) -1 ? - VIR_STORAGE_DEFAULT_VOL_PERM_MODE : vol->target.perms->mode); - if (fchmod(fd, mode) < 0) { - virReportSystemError(errno, - _("cannot set mode of '%s' to %04o"), - vol->target.path, mode); - goto cleanup; - } - if (VIR_CLOSE(fd) < 0) { - virReportSystemError(errno, - _("cannot close file '%s'"), - vol->target.path); - goto cleanup; - } - fd = -1; - - ret = 0; - cleanup: - VIR_FORCE_CLOSE(fd); - - return ret; -} - -static int -createRawFile(int fd, virStorageVolDefPtr vol, - virStorageVolDefPtr inputvol, - bool reflink_copy) -{ - bool need_alloc = true; - int ret = 0; - unsigned long long pos = 0; - - /* If the new allocation is lower than the capacity of the original file, - * the cloned volume will be sparse */ - if (inputvol && - vol->target.allocation < inputvol->target.capacity) - need_alloc = false; - - /* Seek to the final size, so the capacity is available upfront - * for progress reporting */ - if (ftruncate(fd, vol->target.capacity) < 0) { - ret = -errno; - virReportSystemError(errno, - _("cannot extend file '%s'"), - vol->target.path); - goto cleanup; - } - -/* Avoid issues with older kernel's <linux/fs.h> namespace pollution. */ -#if HAVE_FALLOCATE - 0 - /* Try to preallocate all requested disk space, but fall back to - * other methods if this fails with ENOSYS or EOPNOTSUPP. If allocation - * is 0 (or less than 0), then fallocate will fail with EINVAL. - * NOTE: do not use posix_fallocate; posix_fallocate falls back - * to writing zeroes block by block in case fallocate isn't - * available, and since we're going to copy data from another - * file it doesn't make sense to write the file twice. */ - if (vol->target.allocation && need_alloc) { - if (fallocate(fd, 0, 0, vol->target.allocation) == 0) { - need_alloc = false; - } else if (errno != ENOSYS && errno != EOPNOTSUPP) { - ret = -errno; - virReportSystemError(errno, - _("cannot allocate %llu bytes in file '%s'"), - vol->target.allocation, vol->target.path); - goto cleanup; - } - } -#endif - - if (inputvol) { - unsigned long long remain = inputvol->target.capacity; - /* allow zero blocks to be skipped if we've requested sparse - * allocation (allocation < capacity) or we have already - * been able to allocate the required space. */ - if ((ret = virStorageBackendCopyToFD(vol, inputvol, fd, &remain, - !need_alloc, reflink_copy)) < 0) - goto cleanup; - - /* If the new allocation is greater than the original capacity, - * but fallocate failed, fill the rest with zeroes. - */ - pos = inputvol->target.capacity - remain; - } - - if (need_alloc && (vol->target.allocation - pos > 0)) { - if (safezero(fd, pos, vol->target.allocation - pos) < 0) { - ret = -errno; - virReportSystemError(errno, _("cannot fill file '%s'"), - vol->target.path); - goto cleanup; - } - } - - if (fsync(fd) < 0) { - ret = -errno; - virReportSystemError(errno, _("cannot sync data to file '%s'"), - vol->target.path); - goto cleanup; - } - - cleanup: - return ret; -} - -int -virStorageBackendCreateRaw(virConnectPtr conn ATTRIBUTE_UNUSED, - virStoragePoolObjPtr pool, - virStorageVolDefPtr vol, - virStorageVolDefPtr inputvol, - unsigned int flags) -{ - int ret = -1; - int fd = -1; - int operation_flags; - bool reflink_copy = false; - mode_t open_mode = VIR_STORAGE_DEFAULT_VOL_PERM_MODE; - bool created = false; - - virCheckFlags(VIR_STORAGE_VOL_CREATE_PREALLOC_METADATA | - VIR_STORAGE_VOL_CREATE_REFLINK, - -1); - - if (flags & VIR_STORAGE_VOL_CREATE_PREALLOC_METADATA) { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", - _("metadata preallocation is not supported for raw " - "volumes")); - goto cleanup; - } - - if (vol->target.backingStore) { - virReportError(VIR_ERR_NO_SUPPORT, "%s", - _("backing storage not supported for raw volumes")); - goto cleanup; - } - - if (flags & VIR_STORAGE_VOL_CREATE_REFLINK) - reflink_copy = true; - - - if (vol->target.encryption != NULL) { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", - _("storage pool does not support encrypted volumes")); - goto cleanup; - } - - operation_flags = VIR_FILE_OPEN_FORCE_MODE | VIR_FILE_OPEN_FORCE_OWNER; - if (pool->def->type == VIR_STORAGE_POOL_NETFS) - operation_flags |= VIR_FILE_OPEN_FORK; - - if (vol->target.perms->mode != (mode_t) -1) - open_mode = vol->target.perms->mode; - - if ((fd = virFileOpenAs(vol->target.path, - O_RDWR | O_CREAT | O_EXCL, - open_mode, - vol->target.perms->uid, - vol->target.perms->gid, - operation_flags)) < 0) { - virReportSystemError(-fd, - _("Failed to create file '%s'"), - vol->target.path); - goto cleanup; - } - created = true; - - if (vol->target.nocow) { -#ifdef __linux__ - int attr; - - /* Set NOCOW flag. This is an optimisation for btrfs. - * The FS_IOC_SETFLAGS ioctl return value will be ignored since any - * failure of this operation should not block the volume creation. - */ - if (ioctl(fd, FS_IOC_GETFLAGS, &attr) < 0) { - virReportSystemError(errno, "%s", _("Failed to get fs flags")); - } else { - attr |= FS_NOCOW_FL; - if (ioctl(fd, FS_IOC_SETFLAGS, &attr) < 0) { - virReportSystemError(errno, "%s", - _("Failed to set NOCOW flag")); - } - } -#endif - } - - if ((ret = createRawFile(fd, vol, inputvol, reflink_copy)) < 0) - /* createRawFile already reported the exact error. */ - ret = -1; - - cleanup: - if (ret < 0 && created) - ignore_value(virFileRemove(vol->target.path, - vol->target.perms->uid, - vol->target.perms->gid)); - VIR_FORCE_CLOSE(fd); - return ret; -} - -static int -virStorageGenerateSecretUUID(virConnectPtr conn, - unsigned char *uuid) +virStorageBackendPtr +virStorageBackendForType(int type) { - unsigned attempt; - - for (attempt = 0; attempt < 65536; attempt++) { - virSecretPtr tmp; - if (virUUIDGenerate(uuid) < 0) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("unable to generate uuid")); - return -1; - } - tmp = conn->secretDriver->secretLookupByUUID(conn, uuid); - if (tmp == NULL) - return 0; - - virObjectUnref(tmp); - } - - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("too many conflicts when generating a uuid")); + size_t i; + for (i = 0; backends[i]; i++) + if (backends[i]->type == type) + return backends[i]; - return -1; + virReportError(VIR_ERR_INTERNAL_ERROR, + _("missing backend for pool type %d (%s)"), + type, NULLSTR(virStoragePoolTypeToString(type))); + return NULL; } -static int -virStorageGenerateQcowEncryption(virConnectPtr conn, - virStorageVolDefPtr vol) -{ - virSecretDefPtr def = NULL; - virBuffer buf = VIR_BUFFER_INITIALIZER; - virStorageEncryptionPtr enc; - virStorageEncryptionSecretPtr enc_secret = NULL; - virSecretPtr secret = NULL; - char *xml; - unsigned char value[VIR_STORAGE_QCOW_PASSPHRASE_SIZE]; - int ret = -1; - - if (conn->secretDriver == NULL || - conn->secretDriver->secretLookupByUUID == NULL || - conn->secretDriver->secretDefineXML == NULL || - conn->secretDriver->secretSetValue == NULL) { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", - _("secret storage not supported")); - goto cleanup; - } - - enc = vol->target.encryption; - if (enc->nsecrets != 0) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("secrets already defined")); - goto cleanup; - } - - if (VIR_ALLOC(enc_secret) < 0 || VIR_REALLOC_N(enc->secrets, 1) < 0 || - VIR_ALLOC(def) < 0) - goto cleanup; - - def->isephemeral = false; - def->isprivate = false; - if (virStorageGenerateSecretUUID(conn, def->uuid) < 0) - goto cleanup; - - def->usage_type = VIR_SECRET_USAGE_TYPE_VOLUME; - if (VIR_STRDUP(def->usage_id, vol->target.path) < 0) - goto cleanup; - xml = virSecretDefFormat(def); - virSecretDefFree(def); - def = NULL; - if (xml == NULL) - goto cleanup; - - secret = conn->secretDriver->secretDefineXML(conn, xml, 0); - if (secret == NULL) { - VIR_FREE(xml); - goto cleanup; - } - VIR_FREE(xml); - - if (virStorageGenerateQcowPassphrase(value) < 0) - goto cleanup; - - if (conn->secretDriver->secretSetValue(secret, value, sizeof(value), 0) < 0) - goto cleanup; - - enc_secret->type = VIR_STORAGE_ENCRYPTION_SECRET_TYPE_PASSPHRASE; - enc_secret->seclookupdef.type = VIR_SECRET_LOOKUP_TYPE_UUID; - memcpy(enc_secret->seclookupdef.u.uuid, secret->uuid, VIR_UUID_BUFLEN); - enc->format = VIR_STORAGE_ENCRYPTION_FORMAT_QCOW; - enc->secrets[0] = enc_secret; /* Space for secrets[0] allocated above */ - enc_secret = NULL; - enc->nsecrets = 1; - ret = 0; - - cleanup: - if (secret != NULL) { - if (ret != 0 && - conn->secretDriver->secretUndefine != NULL) - conn->secretDriver->secretUndefine(secret); - virObjectUnref(secret); - } - virBufferFreeAndReset(&buf); - virSecretDefFree(def); - VIR_FREE(enc_secret); - return ret; -} - -static int -virStorageBackendCreateExecCommand(virStoragePoolObjPtr pool, - virStorageVolDefPtr vol, - virCommandPtr cmd) +virStorageFileBackendPtr +virStorageFileBackendForTypeInternal(int type, + int protocol, + bool report) { - struct stat st; - gid_t gid; - uid_t uid; - mode_t mode = (vol->target.perms->mode == (mode_t) -1 ? - VIR_STORAGE_DEFAULT_VOL_PERM_MODE : - vol->target.perms->mode); - bool filecreated = false; - int ret = -1; - - if ((pool->def->type == VIR_STORAGE_POOL_NETFS) - && (((geteuid() == 0) - && (vol->target.perms->uid != (uid_t) -1) - && (vol->target.perms->uid != 0)) - || ((vol->target.perms->gid != (gid_t) -1) - && (vol->target.perms->gid != getegid())))) { - - virCommandSetUID(cmd, vol->target.perms->uid); - virCommandSetGID(cmd, vol->target.perms->gid); - virCommandSetUmask(cmd, S_IRWXUGO ^ mode); - - if (virCommandRun(cmd, NULL) == 0) { - /* command was successfully run, check if the file was created */ - if (stat(vol->target.path, &st) >= 0) { - filecreated = true; - - /* seems qemu-img disregards umask and open/creates using 0644. - * If that doesn't match what we expect, then let's try to - * re-open the file and attempt to force the mode change. - */ - if (mode != (st.st_mode & S_IRWXUGO)) { - int fd = -1; - int flags = VIR_FILE_OPEN_FORK | VIR_FILE_OPEN_FORCE_MODE; - - if ((fd = virFileOpenAs(vol->target.path, O_RDWR, mode, - vol->target.perms->uid, - vol->target.perms->gid, - flags)) >= 0) { - /* Success - means we're good */ - VIR_FORCE_CLOSE(fd); - ret = 0; - goto cleanup; - } - } - } - } - } + size_t i; - if (!filecreated) { - /* don't change uid/gid/mode if we retry */ - virCommandSetUID(cmd, -1); - virCommandSetGID(cmd, -1); - virCommandSetUmask(cmd, 0); + for (i = 0; fileBackends[i]; i++) { + if (fileBackends[i]->type == type) { + if (type == VIR_STORAGE_TYPE_NETWORK && + fileBackends[i]->protocol != protocol) + continue; - if (virCommandRun(cmd, NULL) < 0) - goto cleanup; - if (stat(vol->target.path, &st) < 0) { - virReportSystemError(errno, - _("failed to create %s"), vol->target.path); - goto cleanup; + return fileBackends[i]; } - filecreated = true; - } - - uid = (vol->target.perms->uid != st.st_uid) ? vol->target.perms->uid - : (uid_t) -1; - gid = (vol->target.perms->gid != st.st_gid) ? vol->target.perms->gid - : (gid_t) -1; - if (((uid != (uid_t) -1) || (gid != (gid_t) -1)) - && (chown(vol->target.path, uid, gid) < 0)) { - virReportSystemError(errno, - _("cannot chown %s to (%u, %u)"), - vol->target.path, (unsigned int) uid, - (unsigned int) gid); - goto cleanup; } - if (mode != (st.st_mode & S_IRWXUGO) && - chmod(vol->target.path, mode) < 0) { - virReportSystemError(errno, - _("cannot set mode of '%s' to %04o"), - vol->target.path, mode); - goto cleanup; - } - - ret = 0; - - cleanup: - if (ret < 0 && filecreated) - virFileRemove(vol->target.path, vol->target.perms->uid, - vol->target.perms->gid); - return ret; -} - -/* Create ploop directory with ploop image and DiskDescriptor.xml - * if function fails to create image file the directory will be deleted.*/ -int -virStorageBackendCreatePloop(virConnectPtr conn ATTRIBUTE_UNUSED, - virStoragePoolObjPtr pool ATTRIBUTE_UNUSED, - virStorageVolDefPtr vol, - virStorageVolDefPtr inputvol, - unsigned int flags) -{ - int ret = -1; - virCommandPtr cmd = NULL; - char *create_tool = NULL; - bool created = false; - - virCheckFlags(0, -1); - - if (inputvol && inputvol->target.format != VIR_STORAGE_FILE_PLOOP) { - virReportError(VIR_ERR_INTERNAL_ERROR, - _("unsupported input storage vol type %d"), - inputvol->target.format); - return -1; - } - - if (vol->target.encryption != NULL) { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", - _("encrypted ploop volumes are not supported with " - "ploop init")); - return -1; - } - - if (vol->target.backingStore != NULL) { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", - _("copy-on-write ploop volumes are not yet supported")); - return -1; - } + if (!report) + return NULL; - create_tool = virFindFileInPath("ploop"); - if (!create_tool && !inputvol) { + if (type == VIR_STORAGE_TYPE_NETWORK) { virReportError(VIR_ERR_INTERNAL_ERROR, - "%s", _("unable to find ploop, please install " - "ploop tools")); - return -1; - } - - if (!inputvol) { - if ((virDirCreate(vol->target.path, - (vol->target.perms->mode == (mode_t) -1 ? - VIR_STORAGE_DEFAULT_VOL_PERM_MODE: - vol->target.perms->mode), - vol->target.perms->uid, - vol->target.perms->gid, - 0)) < 0) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("error creating directory for ploop volume")); - goto cleanup; - } - cmd = virCommandNewArgList(create_tool, "init", "-s", NULL); - virCommandAddArgFormat(cmd, "%lluM", VIR_DIV_UP(vol->target.capacity, - (1024 * 1024))); - virCommandAddArgList(cmd, "-t", "ext4", NULL); - virCommandAddArgFormat(cmd, "%s/root.hds", vol->target.path); - - } else { - vol->target.capacity = inputvol->target.capacity; - cmd = virCommandNewArgList("cp", "-r", inputvol->target.path, - vol->target.path, NULL); - } - created = true; - ret = virCommandRun(cmd, NULL); - cleanup: - virCommandFree(cmd); - VIR_FREE(create_tool); - if (ret < 0 && created) - virFileDeleteTree(vol->target.path); - return ret; -} - -int -virStoragePloopResize(virStorageVolDefPtr vol, - unsigned long long capacity) -{ - int ret = -1; - virCommandPtr cmd = NULL; - char *resize_tool = NULL; - - resize_tool = virFindFileInPath("ploop"); - if (!resize_tool) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("unable to find ploop, please install ploop tools")); - return -1; - } - cmd = virCommandNewArgList(resize_tool, "resize", "-s", NULL); - virCommandAddArgFormat(cmd, "%lluM", VIR_DIV_UP(capacity, (1024 * 1024))); - - virCommandAddArgFormat(cmd, "%s/DiskDescriptor.xml", vol->target.path); - - ret = virCommandRun(cmd, NULL); - virCommandFree(cmd); - VIR_FREE(resize_tool); - return ret; -} - -/* Flag values shared w/ storagevolxml2argvtest.c. - * - * QEMU_IMG_BACKING_FORMAT_OPTIONS (added in qemu 0.11) - * QEMU_IMG_BACKING_FORMAT_OPTIONS_COMPAT - * was made necessary due to 2.0 change to change the default - * qcow2 file format from 0.10 to 1.1. - */ -enum { - QEMU_IMG_BACKING_FORMAT_OPTIONS = 0, - QEMU_IMG_BACKING_FORMAT_OPTIONS_COMPAT, -}; - -static bool -virStorageBackendQemuImgSupportsCompat(const char *qemuimg) -{ - bool ret = false; - char *output; - virCommandPtr cmd = NULL; - - cmd = virCommandNewArgList(qemuimg, "create", "-o", "?", "-f", "qcow2", - "/dev/null", NULL); - - virCommandAddEnvString(cmd, "LC_ALL=C"); - virCommandSetOutputBuffer(cmd, &output); - - if (virCommandRun(cmd, NULL) < 0) - goto cleanup; - - if (strstr(output, "\ncompat ")) - ret = true; - - cleanup: - virCommandFree(cmd); - VIR_FREE(output); - return ret; -} - - -static int -virStorageBackendQEMUImgBackingFormat(const char *qemuimg) -{ - /* As of QEMU 0.11 the [-o options] support was added via qemu - * commit id '9ea2ea71', so we start with that base and figure - * out what else we have */ - int ret = QEMU_IMG_BACKING_FORMAT_OPTIONS; - - /* QEMU 2.0 changed to using a format that only QEMU 1.1 and newer - * understands. Since we still support QEMU 0.12 and newer, we need - * to be able to handle the previous format as can be set via a - * compat=0.10 option. */ - if (virStorageBackendQemuImgSupportsCompat(qemuimg)) - ret = QEMU_IMG_BACKING_FORMAT_OPTIONS_COMPAT; - - return ret; -} - -/* The _virStorageBackendQemuImgInfo separates the command line building from - * the volume definition so that qemuDomainSnapshotCreateInactiveExternal can - * use it without needing to deal with a volume. - */ -struct _virStorageBackendQemuImgInfo { - int format; - const char *path; - unsigned long long size_arg; - bool encryption; - bool preallocate; - const char *compat; - virBitmapPtr features; - bool nocow; - - const char *backingPath; - int backingFormat; - - const char *inputPath; - const char *inputFormatStr; - int inputFormat; - - char *secretAlias; - const char *secretPath; -}; - - -static int -virStorageBackendCreateQemuImgOpts(virStorageEncryptionInfoDefPtr enc, - char **opts, - struct _virStorageBackendQemuImgInfo info) -{ - virBuffer buf = VIR_BUFFER_INITIALIZER; - - if (info.format == VIR_STORAGE_FILE_RAW && enc) { - virQEMUBuildLuksOpts(&buf, enc, info.secretAlias); - } else { - if (info.backingPath) - virBufferAsprintf(&buf, "backing_fmt=%s,", - virStorageFileFormatTypeToString(info.backingFormat)); - if (info.encryption) - virBufferAddLit(&buf, "encryption=on,"); - if (info.preallocate) - virBufferAddLit(&buf, "preallocation=metadata,"); - } - - if (info.nocow) - virBufferAddLit(&buf, "nocow=on,"); - - if (info.compat) - virBufferAsprintf(&buf, "compat=%s,", info.compat); - - if (info.features && info.format == VIR_STORAGE_FILE_QCOW2) { - if (virBitmapIsBitSet(info.features, - VIR_STORAGE_FILE_FEATURE_LAZY_REFCOUNTS)) { - if (STREQ_NULLABLE(info.compat, "0.10")) { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, - _("lazy_refcounts not supported with compat" - " level %s"), - info.compat); - goto error; - } - virBufferAddLit(&buf, "lazy_refcounts,"); - } - } - - virBufferTrim(&buf, ",", -1); - - if (virBufferCheckError(&buf) < 0) - goto error; - - *opts = virBufferContentAndReset(&buf); - return 0; - - error: - virBufferFreeAndReset(&buf); - return -1; -} - - -/* virStorageBackendCreateQemuImgCheckEncryption: - * @format: format of file found - * @conn: pointer to connection - * @vol: pointer to volume def - * - * Ensure the proper setup for encryption. - * - * Returns 0 on success, -1 on failure w/ error set - */ -static int -virStorageBackendCreateQemuImgCheckEncryption(int format, - const char *type, - virConnectPtr conn, - virStorageVolDefPtr vol) -{ - virStorageEncryptionPtr enc = vol->target.encryption; - - if (format == VIR_STORAGE_FILE_QCOW || format == VIR_STORAGE_FILE_QCOW2) { - if (enc->format != VIR_STORAGE_ENCRYPTION_FORMAT_QCOW && - enc->format != VIR_STORAGE_ENCRYPTION_FORMAT_DEFAULT) { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, - _("unsupported volume encryption format %d"), - vol->target.encryption->format); - return -1; - } - if (enc->nsecrets > 1) { - virReportError(VIR_ERR_XML_ERROR, "%s", - _("too many secrets for qcow encryption")); - return -1; - } - if (enc->format == VIR_STORAGE_ENCRYPTION_FORMAT_DEFAULT || - enc->nsecrets == 0) { - if (virStorageGenerateQcowEncryption(conn, vol) < 0) - return -1; - } - } else if (format == VIR_STORAGE_FILE_RAW) { - if (enc->format != VIR_STORAGE_ENCRYPTION_FORMAT_LUKS) { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, - _("unsupported volume encryption format %d"), - vol->target.encryption->format); - return -1; - } - if (enc->nsecrets > 1) { - virReportError(VIR_ERR_XML_ERROR, "%s", - _("too many secrets for luks encryption")); - return -1; - } - if (enc->nsecrets == 0) { - virReportError(VIR_ERR_XML_ERROR, "%s", - _("no secret provided for luks encryption")); - return -1; - } - if (!virCryptoHaveCipher(VIR_CRYPTO_CIPHER_AES256CBC)) { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", - _("luks encryption usage requires encrypted " - "secret generation to be supported")); - return -1; - } + _("missing storage backend for network files " + "using %s protocol"), + virStorageNetProtocolTypeToString(protocol)); } else { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, - _("volume encryption unsupported with format %s"), type); - return -1; + virReportError(VIR_ERR_INTERNAL_ERROR, + _("missing storage backend for '%s' storage"), + virStorageTypeToString(type)); } - return 0; + return NULL; } -static int -virStorageBackendCreateQemuImgSetInput(virStorageVolDefPtr inputvol, - struct _virStorageBackendQemuImgInfo *info) -{ - if (!(info->inputPath = inputvol->target.path)) { - virReportError(VIR_ERR_INVALID_ARG, "%s", - _("missing input volume target path")); - return -1; - } - - info->inputFormat = inputvol->target.format; - if (inputvol->type == VIR_STORAGE_VOL_BLOCK) - info->inputFormat = VIR_STORAGE_FILE_RAW; - if (!(info->inputFormatStr = - virStorageFileFormatTypeToString(info->inputFormat))) { - virReportError(VIR_ERR_INTERNAL_ERROR, - _("unknown storage vol type %d"), - info->inputFormat); - return -1; - } - - return 0; -} - - -static int -virStorageBackendCreateQemuImgSetBacking(virStoragePoolObjPtr pool, - virStorageVolDefPtr vol, - virStorageVolDefPtr inputvol, - struct _virStorageBackendQemuImgInfo *info) -{ - int accessRetCode = -1; - char *absolutePath = NULL; - - if (info->format == VIR_STORAGE_FILE_RAW) { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", - _("cannot set backing store for raw volume")); - return -1; - } - - info->backingFormat = vol->target.backingStore->format; - info->backingPath = vol->target.backingStore->path; - - if (info->preallocate) { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", - _("metadata preallocation conflicts with backing" - " store")); - return -1; - } - - /* XXX: Not strictly required: qemu-img has an option a different - * backing store, not really sure what use it serves though, and it - * may cause issues with lvm. Untested essentially. - */ - if (inputvol && inputvol->target.backingStore && - STRNEQ_NULLABLE(inputvol->target.backingStore->path, - info->backingPath)) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("a different backing store cannot be specified.")); - return -1; - } - - if (!virStorageFileFormatTypeToString(info->backingFormat)) { - virReportError(VIR_ERR_INTERNAL_ERROR, - _("unknown storage vol backing store type %d"), - info->backingFormat); - return -1; - } - - /* Convert relative backing store paths to absolute paths for access - * validation. - */ - if ('/' != *(info->backingPath) && - virAsprintf(&absolutePath, "%s/%s", pool->def->target.path, - info->backingPath) < 0) - return -1; - accessRetCode = access(absolutePath ? absolutePath : - info->backingPath, R_OK); - VIR_FREE(absolutePath); - if (accessRetCode != 0) { - virReportSystemError(errno, - _("inaccessible backing store volume %s"), - info->backingPath); - return -1; - } - - return 0; -} - - -static int -virStorageBackendCreateQemuImgSetOptions(virCommandPtr cmd, - int imgformat, - virStorageEncryptionInfoDefPtr enc, - struct _virStorageBackendQemuImgInfo info) -{ - char *opts = NULL; - - if (info.format == VIR_STORAGE_FILE_QCOW2 && !info.compat && - imgformat >= QEMU_IMG_BACKING_FORMAT_OPTIONS_COMPAT) - info.compat = "0.10"; - - if (virStorageBackendCreateQemuImgOpts(enc, &opts, info) < 0) - return -1; - if (opts) - virCommandAddArgList(cmd, "-o", opts, NULL); - VIR_FREE(opts); - - return 0; -} - - -/* Add a secret object to the command line: - * --object secret,id=$secretAlias,file=$secretPath - * - * NB: format=raw is assumed - */ -static int -virStorageBackendCreateQemuImgSecretObject(virCommandPtr cmd, - virStorageVolDefPtr vol, - struct _virStorageBackendQemuImgInfo *info) -{ - virBuffer buf = VIR_BUFFER_INITIALIZER; - char *commandStr = NULL; - - if (virAsprintf(&info->secretAlias, "%s_luks0", vol->name) < 0) - return -1; - - virBufferAsprintf(&buf, "secret,id=%s,file=", info->secretAlias); - virQEMUBuildBufferEscapeComma(&buf, info->secretPath); - - if (virBufferCheckError(&buf) < 0) { - virBufferFreeAndReset(&buf); - return -1; - } - - commandStr = virBufferContentAndReset(&buf); - - virCommandAddArgList(cmd, "--object", commandStr, NULL); - - VIR_FREE(commandStr); - return 0; -} - - -/* Create a qemu-img virCommand from the supplied binary path, - * volume definitions and imgformat - */ -virCommandPtr -virStorageBackendCreateQemuImgCmdFromVol(virConnectPtr conn, - virStoragePoolObjPtr pool, - virStorageVolDefPtr vol, - virStorageVolDefPtr inputvol, - unsigned int flags, - const char *create_tool, - int imgformat, - const char *secretPath) -{ - virCommandPtr cmd = NULL; - const char *type; - struct _virStorageBackendQemuImgInfo info = { - .format = vol->target.format, - .path = vol->target.path, - .encryption = vol->target.encryption != NULL, - .preallocate = !!(flags & VIR_STORAGE_VOL_CREATE_PREALLOC_METADATA), - .compat = vol->target.compat, - .features = vol->target.features, - .nocow = vol->target.nocow, - .secretPath = secretPath, - .secretAlias = NULL, - }; - virStorageEncryptionInfoDefPtr enc = NULL; - - virCheckFlags(VIR_STORAGE_VOL_CREATE_PREALLOC_METADATA, NULL); - - /* Treat output block devices as 'raw' format */ - if (vol->type == VIR_STORAGE_VOL_BLOCK) - info.format = VIR_STORAGE_FILE_RAW; - - if (!(type = virStorageFileFormatTypeToString(info.format))) { - virReportError(VIR_ERR_INTERNAL_ERROR, - _("unknown storage vol type %d"), - info.format); - return NULL; - } - - if (info.preallocate && info.format != VIR_STORAGE_FILE_QCOW2) { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", - _("metadata preallocation only available with qcow2")); - return NULL; - } - if (info.compat && info.format != VIR_STORAGE_FILE_QCOW2) { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", - _("compatibility option only available with qcow2")); - return NULL; - } - if (info.features && info.format != VIR_STORAGE_FILE_QCOW2) { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", - _("format features only available with qcow2")); - return NULL; - } - if (info.format == VIR_STORAGE_FILE_RAW && - vol->target.encryption != NULL) { - if (inputvol) { - virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", - _("cannot use inputvol with encrypted raw volume")); - return NULL; - } - if (!info.encryption) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("missing encryption description")); - return NULL; - } - if (vol->target.encryption->format == VIR_STORAGE_ENCRYPTION_FORMAT_LUKS) { - type = "luks"; - } else { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("Only luks encryption is supported for raw files")); - return NULL; - } - } - - if (inputvol && - virStorageBackendCreateQemuImgSetInput(inputvol, &info) < 0) - return NULL; - - if (vol->target.backingStore && - virStorageBackendCreateQemuImgSetBacking(pool, vol, inputvol, - &info) < 0) - return NULL; - - if (info.encryption && - virStorageBackendCreateQemuImgCheckEncryption(info.format, type, - conn, vol) < 0) - return NULL; - - - /* Size in KB */ - info.size_arg = VIR_DIV_UP(vol->target.capacity, 1024); - - cmd = virCommandNew(create_tool); - - /* ignore the backing volume when we're converting a volume */ - if (info.inputPath) - info.backingPath = NULL; - - if (info.inputPath) - virCommandAddArgList(cmd, "convert", "-f", info.inputFormatStr, - "-O", type, NULL); - else - virCommandAddArgList(cmd, "create", "-f", type, NULL); - - if (info.backingPath) - virCommandAddArgList(cmd, "-b", info.backingPath, NULL); - - if (info.format == VIR_STORAGE_FILE_RAW && - vol->target.encryption != NULL && - vol->target.encryption->format == VIR_STORAGE_ENCRYPTION_FORMAT_LUKS) { - if (virStorageBackendCreateQemuImgSecretObject(cmd, vol, &info) < 0) { - VIR_FREE(info.secretAlias); - virCommandFree(cmd); - return NULL; - } - enc = &vol->target.encryption->encinfo; - } - - if (virStorageBackendCreateQemuImgSetOptions(cmd, imgformat, - enc, info) < 0) { - VIR_FREE(info.secretAlias); - virCommandFree(cmd); - return NULL; - } - VIR_FREE(info.secretAlias); - - if (info.inputPath) - virCommandAddArg(cmd, info.inputPath); - virCommandAddArg(cmd, info.path); - if (!info.inputPath && (info.size_arg || !info.backingPath)) - virCommandAddArgFormat(cmd, "%lluK", info.size_arg); - - return cmd; -} - - -static char * -virStorageBackendCreateQemuImgSecretPath(virConnectPtr conn, - virStoragePoolObjPtr pool, - virStorageVolDefPtr vol) -{ - virStorageEncryptionPtr enc = vol->target.encryption; - char *secretPath = NULL; - int fd = -1; - uint8_t *secret = NULL; - size_t secretlen = 0; - - if (!enc) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("missing encryption description")); - return NULL; - } - - if (!conn || !conn->secretDriver || - !conn->secretDriver->secretLookupByUUID || - !conn->secretDriver->secretLookupByUsage || - !conn->secretDriver->secretGetValue) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("unable to look up encryption secret")); - return NULL; - } - - if (!(secretPath = virStoragePoolObjBuildTempFilePath(pool, vol))) - goto cleanup; - - if ((fd = mkostemp(secretPath, O_CLOEXEC)) < 0) { - virReportSystemError(errno, "%s", - _("failed to open luks secret file for write")); - goto error; - } - - if (virSecretGetSecretString(conn, &enc->secrets[0]->seclookupdef, - VIR_SECRET_USAGE_TYPE_VOLUME, - &secret, &secretlen) < 0) - goto error; - - if (safewrite(fd, secret, secretlen) < 0) { - virReportSystemError(errno, "%s", - _("failed to write luks secret file")); - goto error; - } - VIR_FORCE_CLOSE(fd); - - if ((vol->target.perms->uid != (uid_t) -1) && - (vol->target.perms->gid != (gid_t) -1)) { - if (chown(secretPath, vol->target.perms->uid, - vol->target.perms->gid) < 0) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("failed to chown luks secret file")); - goto error; - } - } - - cleanup: - VIR_DISPOSE_N(secret, secretlen); - VIR_FORCE_CLOSE(fd); - - return secretPath; - - error: - unlink(secretPath); - VIR_FREE(secretPath); - goto cleanup; -} - - -int -virStorageBackendCreateQemuImg(virConnectPtr conn, - virStoragePoolObjPtr pool, - virStorageVolDefPtr vol, - virStorageVolDefPtr inputvol, - unsigned int flags) -{ - int ret = -1; - char *create_tool; - int imgformat; - virCommandPtr cmd; - char *secretPath = NULL; - - virCheckFlags(VIR_STORAGE_VOL_CREATE_PREALLOC_METADATA, -1); - - create_tool = virFindFileInPath("qemu-img"); - if (!create_tool) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("creation of non-raw file images is " - "not supported without qemu-img.")); - return -1; - } - - imgformat = virStorageBackendQEMUImgBackingFormat(create_tool); - if (imgformat < 0) - goto cleanup; - - if (vol->target.format == VIR_STORAGE_FILE_RAW && - vol->target.encryption && - vol->target.encryption->format == VIR_STORAGE_ENCRYPTION_FORMAT_LUKS) { - if (!(secretPath = - virStorageBackendCreateQemuImgSecretPath(conn, pool, vol))) - goto cleanup; - } - - cmd = virStorageBackendCreateQemuImgCmdFromVol(conn, pool, vol, inputvol, - flags, create_tool, - imgformat, secretPath); - if (!cmd) - goto cleanup; - - ret = virStorageBackendCreateExecCommand(pool, vol, cmd); - - virCommandFree(cmd); - cleanup: - if (secretPath) { - unlink(secretPath); - VIR_FREE(secretPath); - } - VIR_FREE(create_tool); - return ret; -} - -virStorageBackendBuildVolFrom -virStorageBackendGetBuildVolFromFunction(virStorageVolDefPtr vol, - virStorageVolDefPtr inputvol) -{ - if (!inputvol) - return NULL; - - /* If either volume is a non-raw file vol, or uses encryption, - * we need to use an external tool for converting - */ - if ((vol->type == VIR_STORAGE_VOL_FILE && - (vol->target.format != VIR_STORAGE_FILE_RAW || - vol->target.encryption != NULL)) || - (inputvol->type == VIR_STORAGE_VOL_FILE && - (inputvol->target.format != VIR_STORAGE_FILE_RAW || - inputvol->target.encryption != NULL))) { - return virStorageBackendCreateQemuImg; - } - - if (vol->type == VIR_STORAGE_VOL_PLOOP) - return virStorageBackendCreatePloop; - if (vol->type == VIR_STORAGE_VOL_BLOCK) - return virStorageBackendCreateBlockFrom; - else - return virStorageBackendCreateRaw; -} - - -virStorageBackendPtr -virStorageBackendForType(int type) -{ - size_t i; - for (i = 0; backends[i]; i++) - if (backends[i]->type == type) - return backends[i]; - - virReportError(VIR_ERR_INTERNAL_ERROR, - _("missing backend for pool type %d (%s)"), - type, NULLSTR(virStoragePoolTypeToString(type))); - return NULL; -} - - -virStorageFileBackendPtr -virStorageFileBackendForTypeInternal(int type, - int protocol, - bool report) -{ - size_t i; - - for (i = 0; fileBackends[i]; i++) { - if (fileBackends[i]->type == type) { - if (type == VIR_STORAGE_TYPE_NETWORK && - fileBackends[i]->protocol != protocol) - continue; - - return fileBackends[i]; - } - } - - if (!report) - return NULL; - - if (type == VIR_STORAGE_TYPE_NETWORK) { - virReportError(VIR_ERR_INTERNAL_ERROR, - _("missing storage backend for network files " - "using %s protocol"), - virStorageNetProtocolTypeToString(protocol)); - } else { - virReportError(VIR_ERR_INTERNAL_ERROR, - _("missing storage backend for '%s' storage"), - virStorageTypeToString(type)); - } - - return NULL; -} - - -virStorageFileBackendPtr -virStorageFileBackendForType(int type, - int protocol) +virStorageFileBackendPtr +virStorageFileBackendForType(int type, + int protocol) { return virStorageFileBackendForTypeInternal(type, protocol, true); } - - -struct diskType { - int part_table_type; - unsigned short offset; - unsigned short length; - unsigned long long magic; -}; - - -static struct diskType const disk_types[] = { - { VIR_STORAGE_POOL_DISK_LVM2, 0x218, 8, 0x31303020324D564CULL }, - { VIR_STORAGE_POOL_DISK_GPT, 0x200, 8, 0x5452415020494645ULL }, - { VIR_STORAGE_POOL_DISK_DVH, 0x0, 4, 0x41A9E50BULL }, - { VIR_STORAGE_POOL_DISK_MAC, 0x0, 2, 0x5245ULL }, - { VIR_STORAGE_POOL_DISK_BSD, 0x40, 4, 0x82564557ULL }, - { VIR_STORAGE_POOL_DISK_SUN, 0x1fc, 2, 0xBEDAULL }, - /* - * NOTE: pc98 is funky; the actual signature is 0x55AA (just like dos), so - * we can't use that. At the moment I'm relying on the "dummy" IPL - * bootloader data that comes from parted. Luckily, the chances of running - * into a pc98 machine running libvirt are approximately nil. - */ - /*{ 0x1fe, 2, 0xAA55UL },*/ - { VIR_STORAGE_POOL_DISK_PC98, 0x0, 8, 0x314C5049000000CBULL }, - /* - * NOTE: the order is important here; some other disk types (like GPT and - * and PC98) also have 0x55AA at this offset. For that reason, the DOS - * one must be the last one. - */ - { VIR_STORAGE_POOL_DISK_DOS, 0x1fe, 2, 0xAA55ULL }, - { -1, 0x0, 0, 0x0ULL }, -}; - - -/* - * virStorageBackendDetectBlockVolFormatFD - * @target: target definition ptr of volume to update - * @fd: fd of storage volume to update, - * @readflags: VolReadErrorMode flags to handle read error after open - * is successful, but read is not. - * - * Returns 0 for success, -1 on a legitimate error condition, -2 if - * the read error is desired to be ignored (along with appropriate - * VIR_WARN of the issue). - */ -static int -virStorageBackendDetectBlockVolFormatFD(virStorageSourcePtr target, - int fd, - unsigned int readflags) -{ - size_t i; - off_t start; - unsigned char buffer[1024]; - ssize_t bytes; - - /* make sure to set the target format "unknown" to begin with */ - target->format = VIR_STORAGE_POOL_DISK_UNKNOWN; - - start = lseek(fd, 0, SEEK_SET); - if (start < 0) { - virReportSystemError(errno, - _("cannot seek to beginning of file '%s'"), - target->path); - return -1; - } - bytes = saferead(fd, buffer, sizeof(buffer)); - if (bytes < 0) { - if (readflags & VIR_STORAGE_VOL_READ_NOERROR) { - VIR_WARN("ignoring failed saferead of file '%s'", - target->path); - return -2; - } else { - virReportSystemError(errno, - _("cannot read beginning of file '%s'"), - target->path); - return -1; - } - } - - for (i = 0; disk_types[i].part_table_type != -1; i++) { - if (disk_types[i].offset + disk_types[i].length > bytes) - continue; - if (memcmp(buffer+disk_types[i].offset, &disk_types[i].magic, - disk_types[i].length) == 0) { - target->format = disk_types[i].part_table_type; - break; - } - } - - if (target->format == VIR_STORAGE_POOL_DISK_UNKNOWN) - VIR_DEBUG("cannot determine the target format for '%s'", - target->path); - - return 0; -} - - -/* - * Allows caller to silently ignore files with improper mode - * - * Returns -1 on error. If VIR_STORAGE_VOL_OPEN_NOERROR is passed, we - * return -2 if file mode is unexpected or the volume is a dangling - * symbolic link. - */ -int -virStorageBackendVolOpen(const char *path, struct stat *sb, - unsigned int flags) -{ - int fd, mode = 0; - char *base = last_component(path); - bool noerror = (flags & VIR_STORAGE_VOL_OPEN_NOERROR); - - if (lstat(path, sb) < 0) { - if (errno == ENOENT) { - if (noerror) { - VIR_WARN("ignoring missing file '%s'", path); - return -2; - } - virReportError(VIR_ERR_NO_STORAGE_VOL, - _("no storage vol with matching path '%s'"), - path); - return -1; - } - virReportSystemError(errno, - _("cannot stat file '%s'"), - path); - return -1; - } - - if (S_ISFIFO(sb->st_mode)) { - if (noerror) { - VIR_WARN("ignoring FIFO '%s'", path); - return -2; - } - virReportError(VIR_ERR_INTERNAL_ERROR, - _("Volume path '%s' is a FIFO"), path); - return -1; - } else if (S_ISSOCK(sb->st_mode)) { - if (noerror) { - VIR_WARN("ignoring socket '%s'", path); - return -2; - } - virReportError(VIR_ERR_INTERNAL_ERROR, - _("Volume path '%s' is a socket"), path); - return -1; - } - - /* O_NONBLOCK should only matter during open() for fifos and - * sockets, which we already filtered; but using it prevents a - * TOCTTOU race. However, later on we will want to read() the - * header from this fd, and virFileRead* routines require a - * blocking fd, so fix it up after verifying we avoided a race. - * - * Use of virFileOpenAs allows this path to open a file using - * the uid and gid as it was created in order to open. Since this - * path is not using O_CREAT or O_TMPFILE, mode is meaningless. - * Opening under user/group is especially important in an NFS - * root-squash environment. If the target path isn't on shared - * file system, the open will fail in the OPEN_FORK path. - */ - if ((fd = virFileOpenAs(path, O_RDONLY|O_NONBLOCK|O_NOCTTY, - 0, sb->st_uid, sb->st_gid, - VIR_FILE_OPEN_NOFORK|VIR_FILE_OPEN_FORK)) < 0) { - if ((errno == ENOENT || errno == ELOOP) && - S_ISLNK(sb->st_mode) && noerror) { - VIR_WARN("ignoring dangling symlink '%s'", path); - return -2; - } - if (errno == ENOENT && noerror) { - VIR_WARN("ignoring missing file '%s'", path); - return -2; - } - if (errno == ENXIO && noerror) { - VIR_WARN("ignoring missing fifo '%s'", path); - return -2; - } - if ((errno == EACCES || errno == EPERM) && noerror) { - VIR_WARN("ignoring permission error for '%s'", path); - return -2; - } - - virReportSystemError(errno, _("cannot open volume '%s'"), path); - return -1; - } - - if (fstat(fd, sb) < 0) { - virReportSystemError(errno, _("cannot stat file '%s'"), path); - VIR_FORCE_CLOSE(fd); - return -1; - } - - if (S_ISREG(sb->st_mode)) { - mode = VIR_STORAGE_VOL_OPEN_REG; - } else if (S_ISCHR(sb->st_mode)) { - mode = VIR_STORAGE_VOL_OPEN_CHAR; - } else if (S_ISBLK(sb->st_mode)) { - mode = VIR_STORAGE_VOL_OPEN_BLOCK; - } else if (S_ISDIR(sb->st_mode)) { - mode = VIR_STORAGE_VOL_OPEN_DIR; - - if (STREQ(base, ".") || - STREQ(base, "..")) { - VIR_FORCE_CLOSE(fd); - if (noerror) { - VIR_INFO("Skipping special dir '%s'", base); - return -2; - } - virReportError(VIR_ERR_INTERNAL_ERROR, - _("Cannot use volume path '%s'"), path); - return -1; - } - } else { - VIR_FORCE_CLOSE(fd); - if (noerror) { - VIR_WARN("ignoring unexpected type for file '%s'", path); - return -2; - } - virReportError(VIR_ERR_INTERNAL_ERROR, - _("unexpected type for file '%s'"), path); - return -1; - } - - if (virSetBlocking(fd, true) < 0) { - VIR_FORCE_CLOSE(fd); - virReportSystemError(errno, _("unable to set blocking mode for '%s'"), - path); - return -1; - } - - if (!(mode & flags)) { - VIR_FORCE_CLOSE(fd); - if (noerror) { - VIR_INFO("Skipping volume '%s'", path); - return -2; - } - - virReportError(VIR_ERR_INTERNAL_ERROR, - _("unexpected storage mode for '%s'"), path); - return -1; - } - - return fd; -} - -/* virStorageIsPloop function checks whether given directory is ploop volume's - * directory. - */ -bool -virStorageBackendIsPloopDir(char *path) -{ - bool ret = false; - char *root = NULL; - char *desc = NULL; - if (virAsprintf(&root, "%s/root.hds", path) < 0) - return ret; - if (!virFileExists(root)) - goto cleanup; - if (virAsprintf(&desc, "%s/DiskDescriptor.xml", path) < 0) - goto cleanup; - if (!virFileExists(desc)) - goto cleanup; - - ret = true; - cleanup: - VIR_FREE(root); - VIR_FREE(desc); - return ret; -} - -/* In case of ploop volumes, path to volume is the path to the ploop - * directory. To get information about allocation, header information - * and etc. we need to perform virStorageBackendVolOpen and - * virStorageBackendUpdateVolTargetFd once again. - */ -int -virStorageBackendRedoPloopUpdate(virStorageSourcePtr target, struct stat *sb, - int *fd, unsigned int flags) -{ - char *path = NULL; - int ret = -1; - - if (virAsprintf(&path, "%s/root.hds", target->path) < 0) - return -1; - VIR_FORCE_CLOSE(*fd); - if ((*fd = virStorageBackendVolOpen(path, sb, flags)) < 0) - goto cleanup; - ret = virStorageBackendUpdateVolTargetInfoFD(target, *fd, sb); - - cleanup: - - VIR_FREE(path); - return ret; -} - -/* - * virStorageBackendUpdateVolTargetInfo - * @voltype: Volume type - * @target: target definition ptr of volume to update - * @withBlockVolFormat: true if caller determined a block file - * @openflags: various VolOpenCheckMode flags to handle errors on open - * @readflags: VolReadErrorMode flags to handle read error after open - * is successful, but read is not. - * - * Returns 0 for success, -1 on a legitimate error condition, and -2 - * if the openflags used VIR_STORAGE_VOL_OPEN_NOERROR and some sort of - * open error occurred. It is up to the caller to handle. A -2 may also - * be returned if the caller passed a readflagsflag. - */ -int -virStorageBackendUpdateVolTargetInfo(virStorageVolType voltype, - virStorageSourcePtr target, - bool withBlockVolFormat, - unsigned int openflags, - unsigned int readflags) -{ - int ret, fd = -1; - struct stat sb; - char *buf = NULL; - ssize_t len = VIR_STORAGE_MAX_HEADER; - - if ((ret = virStorageBackendVolOpen(target->path, &sb, openflags)) < 0) - goto cleanup; - fd = ret; - - if ((ret = virStorageBackendUpdateVolTargetInfoFD(target, fd, &sb)) < 0) - goto cleanup; - - if ((voltype == VIR_STORAGE_VOL_FILE || voltype == VIR_STORAGE_VOL_BLOCK) && - target->format != VIR_STORAGE_FILE_NONE) { - if (S_ISDIR(sb.st_mode)) { - if (virStorageBackendIsPloopDir(target->path)) { - if ((ret = virStorageBackendRedoPloopUpdate(target, &sb, &fd, - openflags)) < 0) - goto cleanup; - target->format = VIR_STORAGE_FILE_PLOOP; - } else { - ret = 0; - goto cleanup; - } - } - - if (lseek(fd, 0, SEEK_SET) == (off_t)-1) { - virReportSystemError(errno, _("cannot seek to start of '%s'"), target->path); - ret = -1; - goto cleanup; - } - - if ((len = virFileReadHeaderFD(fd, len, &buf)) < 0) { - if (readflags & VIR_STORAGE_VOL_READ_NOERROR) { - VIR_WARN("ignoring failed header read for '%s'", - target->path); - ret = -2; - } else { - virReportSystemError(errno, - _("cannot read header '%s'"), - target->path); - ret = -1; - } - goto cleanup; - } - - if (virStorageSourceUpdateCapacity(target, buf, len, false) < 0) { - ret = -1; - goto cleanup; - } - } - - if (withBlockVolFormat) { - if ((ret = virStorageBackendDetectBlockVolFormatFD(target, fd, - readflags)) < 0) - goto cleanup; - } - - cleanup: - VIR_FORCE_CLOSE(fd); - VIR_FREE(buf); - return ret; -} - -/* - * virStorageBackendUpdateVolInfo - * @vol: Pointer to a volume storage definition - * @withBlockVolFormat: true if the caller determined a block file - * @openflags: various VolOpenCheckMode flags to handle errors on open - * @readflags: various VolReadErrorMode flags to handle errors on read - * - * Returns 0 for success, -1 on a legitimate error condition, and -2 - * if the openflags used VIR_STORAGE_VOL_OPEN_NOERROR and some sort of - * open error occurred. It is up to the caller to handle. - */ -int -virStorageBackendUpdateVolInfo(virStorageVolDefPtr vol, - bool withBlockVolFormat, - unsigned int openflags, - unsigned int readflags) -{ - int ret; - - if ((ret = virStorageBackendUpdateVolTargetInfo(vol->type, - &vol->target, - withBlockVolFormat, - openflags, readflags)) < 0) - return ret; - - if (vol->target.backingStore && - (ret = virStorageBackendUpdateVolTargetInfo(VIR_STORAGE_VOL_FILE, - vol->target.backingStore, - withBlockVolFormat, - VIR_STORAGE_VOL_OPEN_DEFAULT | - VIR_STORAGE_VOL_OPEN_NOERROR, - readflags) < 0)) - return ret; - - return 0; -} - -/* - * virStorageBackendUpdateVolTargetInfoFD: - * @target: target definition ptr of volume to update - * @fd: fd of storage volume to update, via virStorageBackendOpenVol*, or -1 - * @sb: details about file (must match @fd, if that is provided) - * - * Returns 0 for success, -1 on a legitimate error condition. - */ -int -virStorageBackendUpdateVolTargetInfoFD(virStorageSourcePtr target, - int fd, - struct stat *sb) -{ -#if WITH_SELINUX - security_context_t filecon = NULL; -#endif - - if (virStorageSourceUpdateBackingSizes(target, fd, sb) < 0) - return -1; - - if (!target->perms && VIR_ALLOC(target->perms) < 0) - return -1; - target->perms->mode = sb->st_mode & S_IRWXUGO; - target->perms->uid = sb->st_uid; - target->perms->gid = sb->st_gid; - - if (!target->timestamps && VIR_ALLOC(target->timestamps) < 0) - return -1; - target->timestamps->atime = get_stat_atime(sb); - target->timestamps->btime = get_stat_birthtime(sb); - target->timestamps->ctime = get_stat_ctime(sb); - target->timestamps->mtime = get_stat_mtime(sb); - - VIR_FREE(target->perms->label); - -#if WITH_SELINUX - /* XXX: make this a security driver call */ - if (fd >= 0) { - if (fgetfilecon_raw(fd, &filecon) == -1) { - if (errno != ENODATA && errno != ENOTSUP) { - virReportSystemError(errno, - _("cannot get file context of '%s'"), - target->path); - return -1; - } - } else { - if (VIR_STRDUP(target->perms->label, filecon) < 0) { - freecon(filecon); - return -1; - } - freecon(filecon); - } - } -#endif - - return 0; -} - -bool -virStorageBackendPoolPathIsStable(const char *path) -{ - if (path == NULL || STREQ(path, "/dev") || STREQ(path, "/dev/")) - return false; - - if (!STRPREFIX(path, "/dev/")) - return false; - - return true; -} - -/* - * Given a volume path directly in /dev/XXX, iterate over the - * entries in the directory pool->def->target.path and find the - * first symlink pointing to the volume path. - * - * If, the target.path is /dev/, then return the original volume - * path. - * - * If no symlink is found, then return the original volume path - * - * Typically target.path is one of the /dev/disk/by-XXX dirs - * with stable paths. - * - * If 'loop' is true, we use a timeout loop to give dynamic paths - * a change to appear. - */ -char * -virStorageBackendStablePath(virStoragePoolObjPtr pool, - const char *devpath, - bool loop) -{ - DIR *dh; - struct dirent *dent; - char *stablepath; - int opentries = 0; - int retry = 0; - int direrr; - - /* Logical pools are under /dev but already have stable paths */ - if (pool->def->type == VIR_STORAGE_POOL_LOGICAL || - !virStorageBackendPoolPathIsStable(pool->def->target.path)) - goto ret_strdup; - - /* We loop here because /dev/disk/by-{id,path} may not have existed - * before we started this operation, so we have to give it some time to - * get created. - */ - reopen: - if (virDirOpenQuiet(&dh, pool->def->target.path) < 0) { - opentries++; - if (loop && errno == ENOENT && opentries < 50) { - usleep(100 * 1000); - goto reopen; - } - virReportSystemError(errno, - _("cannot read dir '%s'"), - pool->def->target.path); - return NULL; - } - - /* The pool is pointing somewhere like /dev/disk/by-path - * or /dev/disk/by-id, so we need to check all symlinks in - * the target directory and figure out which one points - * to this device node. - * - * And it might need some time till the stable path shows - * up, so add timeout to retry here. Ignore readdir failures, - * since we have a fallback. - */ - retry: - while ((direrr = virDirRead(dh, &dent, NULL)) > 0) { - if (virAsprintf(&stablepath, "%s/%s", - pool->def->target.path, - dent->d_name) == -1) { - VIR_DIR_CLOSE(dh); - return NULL; - } - - if (virFileLinkPointsTo(stablepath, devpath)) { - VIR_DIR_CLOSE(dh); - return stablepath; - } - - VIR_FREE(stablepath); - } - - if (!direrr && loop && ++retry < 100) { - usleep(100 * 1000); - goto retry; - } - - VIR_DIR_CLOSE(dh); - - ret_strdup: - /* Couldn't find any matching stable link so give back - * the original non-stable dev path - */ - - ignore_value(VIR_STRDUP(stablepath, devpath)); - - return stablepath; -} - -/* - * Check whether the ploop image has snapshots. - * return: -1 - failed to check - * 0 - no snapshots - * 1 - at least one snapshot - */ -static int -virStorageBackendPloopHasSnapshots(char *path) -{ - virCommandPtr cmd = NULL; - char *output = NULL; - char *snap_tool = NULL; - int ret = -1; - - snap_tool = virFindFileInPath("ploop"); - if (!snap_tool) { - virReportError(VIR_ERR_INTERNAL_ERROR, - "%s", _("unable to find ploop, please install " - "ploop tools")); - return ret; - } - - cmd = virCommandNewArgList(snap_tool, "snapshot-list", NULL); - virCommandAddArgFormat(cmd, "%s/DiskDescriptor.xml", path); - virCommandSetOutputBuffer(cmd, &output); - - if ((ret = virCommandRun(cmd, NULL)) < 0) - goto cleanup; - - if (!strstr(output, "root.hds.")) { - ret = 1; - goto cleanup; - } - ret = 0; - - cleanup: - VIR_FREE(output); - virCommandFree(cmd); - return ret; -} - -int -virStorageBackendVolUploadLocal(virConnectPtr conn ATTRIBUTE_UNUSED, - virStoragePoolObjPtr pool ATTRIBUTE_UNUSED, - virStorageVolDefPtr vol, - virStreamPtr stream, - unsigned long long offset, - unsigned long long len, - unsigned int flags) -{ - char *path = NULL; - char *target_path = vol->target.path; - int ret = -1; - int has_snap = 0; - - virCheckFlags(0, -1); - /* if volume has target format VIR_STORAGE_FILE_PLOOP - * we need to restore DiskDescriptor.xml, according to - * new contents of volume. This operation will be perfomed - * when volUpload is fully finished. */ - if (vol->target.format == VIR_STORAGE_FILE_PLOOP) { - /* Fail if the volume contains snapshots or we failed to check it.*/ - has_snap = virStorageBackendPloopHasSnapshots(vol->target.path); - if (has_snap < 0) { - goto cleanup; - } else if (!has_snap) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("can't upload volume, all existing snapshots" - " will be lost")); - goto cleanup; - } - - if (virAsprintf(&path, "%s/root.hds", vol->target.path) < 0) - return -1; - target_path = path; - } - - /* Not using O_CREAT because the file is required to already exist at - * this point */ - ret = virFDStreamOpenBlockDevice(stream, target_path, - offset, len, O_WRONLY); - - cleanup: - VIR_FREE(path); - return ret; -} - -int -virStorageBackendVolDownloadLocal(virConnectPtr conn ATTRIBUTE_UNUSED, - virStoragePoolObjPtr pool ATTRIBUTE_UNUSED, - virStorageVolDefPtr vol, - virStreamPtr stream, - unsigned long long offset, - unsigned long long len, - unsigned int flags) -{ - char *path = NULL; - char *target_path = vol->target.path; - int ret = -1; - int has_snap = 0; - - virCheckFlags(0, -1); - if (vol->target.format == VIR_STORAGE_FILE_PLOOP) { - has_snap = virStorageBackendPloopHasSnapshots(vol->target.path); - if (has_snap < 0) { - goto cleanup; - } else if (!has_snap) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("can't download volume, all existing snapshots" - " will be lost")); - goto cleanup; - } - if (virAsprintf(&path, "%s/root.hds", vol->target.path) < 0) - goto cleanup; - target_path = path; - } - - ret = virFDStreamOpenBlockDevice(stream, target_path, - offset, len, O_RDONLY); - - cleanup: - VIR_FREE(path); - return ret; -} - - -/* If the volume we're wiping is already a sparse file, we simply - * truncate and extend it to its original size, filling it with - * zeroes. This behavior is guaranteed by POSIX: - * - * http://www.opengroup.org/onlinepubs/9699919799/functions/ftruncate.html - * - * If fildes refers to a regular file, the ftruncate() function shall - * cause the size of the file to be truncated to length. If the size - * of the file previously exceeded length, the extra data shall no - * longer be available to reads on the file. If the file previously - * was smaller than this size, ftruncate() shall increase the size of - * the file. If the file size is increased, the extended area shall - * appear as if it were zero-filled. - */ -static int -virStorageBackendVolZeroSparseFileLocal(const char *path, - off_t size, - int fd) -{ - if (ftruncate(fd, 0) < 0) { - virReportSystemError(errno, - _("Failed to truncate volume with " - "path '%s' to 0 bytes"), - path); - return -1; - } - - if (ftruncate(fd, size) < 0) { - virReportSystemError(errno, - _("Failed to truncate volume with " - "path '%s' to %ju bytes"), - path, (uintmax_t)size); - return -1; - } - - return 0; -} - - -static int -virStorageBackendWipeLocal(const char *path, - int fd, - unsigned long long wipe_len, - size_t writebuf_length) -{ - int ret = -1, written = 0; - unsigned long long remaining = 0; - size_t write_size = 0; - char *writebuf = NULL; - - VIR_DEBUG("wiping start: 0 len: %llu", wipe_len); - - if (VIR_ALLOC_N(writebuf, writebuf_length) < 0) - goto cleanup; - - if (lseek(fd, 0, SEEK_SET) < 0) { - virReportSystemError(errno, - _("Failed to seek to the start in volume " - "with path '%s'"), - path); - goto cleanup; - } - - remaining = wipe_len; - while (remaining > 0) { - - write_size = (writebuf_length < remaining) ? writebuf_length : remaining; - written = safewrite(fd, writebuf, write_size); - if (written < 0) { - virReportSystemError(errno, - _("Failed to write %zu bytes to " - "storage volume with path '%s'"), - write_size, path); - - goto cleanup; - } - - remaining -= written; - } - - if (fdatasync(fd) < 0) { - virReportSystemError(errno, - _("cannot sync data to volume with path '%s'"), - path); - goto cleanup; - } - - VIR_DEBUG("Wrote %llu bytes to volume with path '%s'", wipe_len, path); - - ret = 0; - - cleanup: - VIR_FREE(writebuf); - return ret; -} - - -static int -virStorageBackendVolWipeLocalFile(const char *path, - unsigned int algorithm, - unsigned long long allocation) -{ - int ret = -1, fd = -1; - const char *alg_char = NULL; - struct stat st; - virCommandPtr cmd = NULL; - - fd = open(path, O_RDWR); - if (fd == -1) { - virReportSystemError(errno, - _("Failed to open storage volume with path '%s'"), - path); - goto cleanup; - } - - if (fstat(fd, &st) == -1) { - virReportSystemError(errno, - _("Failed to stat storage volume with path '%s'"), - path); - goto cleanup; - } - - switch ((virStorageVolWipeAlgorithm) algorithm) { - case VIR_STORAGE_VOL_WIPE_ALG_ZERO: - alg_char = "zero"; - break; - case VIR_STORAGE_VOL_WIPE_ALG_NNSA: - alg_char = "nnsa"; - break; - case VIR_STORAGE_VOL_WIPE_ALG_DOD: - alg_char = "dod"; - break; - case VIR_STORAGE_VOL_WIPE_ALG_BSI: - alg_char = "bsi"; - break; - case VIR_STORAGE_VOL_WIPE_ALG_GUTMANN: - alg_char = "gutmann"; - break; - case VIR_STORAGE_VOL_WIPE_ALG_SCHNEIER: - alg_char = "schneier"; - break; - case VIR_STORAGE_VOL_WIPE_ALG_PFITZNER7: - alg_char = "pfitzner7"; - break; - case VIR_STORAGE_VOL_WIPE_ALG_PFITZNER33: - alg_char = "pfitzner33"; - break; - case VIR_STORAGE_VOL_WIPE_ALG_RANDOM: - alg_char = "random"; - break; - case VIR_STORAGE_VOL_WIPE_ALG_TRIM: - virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", - _("'trim' algorithm not supported")); - goto cleanup; - case VIR_STORAGE_VOL_WIPE_ALG_LAST: - virReportError(VIR_ERR_INVALID_ARG, - _("unsupported algorithm %d"), - algorithm); - goto cleanup; - } - - VIR_DEBUG("Wiping file '%s' with algorithm '%s'", path, alg_char); - - if (algorithm != VIR_STORAGE_VOL_WIPE_ALG_ZERO) { - cmd = virCommandNew(SCRUB); - virCommandAddArgList(cmd, "-f", "-p", alg_char, path, NULL); - - if (virCommandRun(cmd, NULL) < 0) - goto cleanup; - - ret = 0; - } else { - if (S_ISREG(st.st_mode) && st.st_blocks < (st.st_size / DEV_BSIZE)) { - ret = virStorageBackendVolZeroSparseFileLocal(path, st.st_size, fd); - } else { - ret = virStorageBackendWipeLocal(path, - fd, - allocation, - st.st_blksize); - } - if (ret < 0) - goto cleanup; - } - - cleanup: - virCommandFree(cmd); - VIR_FORCE_CLOSE(fd); - return ret; -} - - -static int -virStorageBackendVolWipePloop(virStorageVolDefPtr vol, - unsigned int algorithm) -{ - virCommandPtr cmd = NULL; - char *target_path = NULL; - char *disk_desc = NULL; - char *create_tool = NULL; - - int ret = -1; - - create_tool = virFindFileInPath("ploop"); - if (!create_tool) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("unable to find ploop tools, please install them")); - return -1; - } - - if (virAsprintf(&target_path, "%s/root.hds", vol->target.path) < 0) - goto cleanup; - - if (virAsprintf(&disk_desc, "%s/DiskDescriptor.xml", vol->target.path) < 0) - goto cleanup; - - if (virStorageBackendVolWipeLocalFile(target_path, - algorithm, - vol->target.allocation) < 0) - goto cleanup; - - if (virFileRemove(disk_desc, 0, 0) < 0) { - virReportError(errno, _("Failed to delete DiskDescriptor.xml of volume '%s'"), - vol->target.path); - goto cleanup; - } - if (virFileRemove(target_path, 0, 0) < 0) { - virReportError(errno, _("failed to delete root.hds of volume '%s'"), - vol->target.path); - goto cleanup; - } - - cmd = virCommandNewArgList(create_tool, "init", "-s", NULL); - - virCommandAddArgFormat(cmd, "%lluM", VIR_DIV_UP(vol->target.capacity, - (1024 * 1024))); - virCommandAddArgList(cmd, "-t", "ext4", NULL); - virCommandAddArg(cmd, target_path); - ret = virCommandRun(cmd, NULL); - - cleanup: - VIR_FREE(disk_desc); - VIR_FREE(target_path); - VIR_FREE(create_tool); - virCommandFree(cmd); - return ret; -} - - -int -virStorageBackendVolWipeLocal(virConnectPtr conn ATTRIBUTE_UNUSED, - virStoragePoolObjPtr pool ATTRIBUTE_UNUSED, - virStorageVolDefPtr vol, - unsigned int algorithm, - unsigned int flags) -{ - int ret = -1; - - virCheckFlags(0, -1); - - VIR_DEBUG("Wiping volume with path '%s' and algorithm %u", - vol->target.path, algorithm); - - if (vol->target.format == VIR_STORAGE_FILE_PLOOP) { - ret = virStorageBackendVolWipePloop(vol, algorithm); - } else { - ret = virStorageBackendVolWipeLocalFile(vol->target.path, - algorithm, - vol->target.allocation); - } - - return ret; -} - - -/** - * virStorageBackendFindGlusterPoolSources: - * @host: host to detect volumes on - * @pooltype: src->format is set to this value - * @list: list of storage pool sources to be filled - * @report: report error if the 'gluster' cli tool is missing - * - * Looks up gluster volumes on @host and fills them to @list. - * - * Returns number of volumes on the host on success, or -1 on error. - */ -int -virStorageBackendFindGlusterPoolSources(const char *host, - int pooltype, - virStoragePoolSourceListPtr list, - bool report) -{ - char *glusterpath = NULL; - char *outbuf = NULL; - virCommandPtr cmd = NULL; - xmlDocPtr doc = NULL; - xmlXPathContextPtr ctxt = NULL; - xmlNodePtr *nodes = NULL; - virStoragePoolSource *src = NULL; - size_t i; - int nnodes; - int rc; - - int ret = -1; - - if (!(glusterpath = virFindFileInPath(GLUSTER_CLI))) { - if (report) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("'gluster' command line tool not found")); - return -1; - } else { - return 0; - } - } - - cmd = virCommandNewArgList(glusterpath, - "--xml", - "--log-file=/dev/null", - "volume", "info", "all", NULL); - - virCommandAddArgFormat(cmd, "--remote-host=%s", host); - virCommandSetOutputBuffer(cmd, &outbuf); - - if (virCommandRun(cmd, &rc) < 0) - goto cleanup; - - if (rc != 0) { - ret = 0; - goto cleanup; - } - - if (!(doc = virXMLParseStringCtxt(outbuf, _("(gluster_cli_output)"), - &ctxt))) - goto cleanup; - - if ((nnodes = virXPathNodeSet("//volumes/volume", ctxt, &nodes)) < 0) - goto cleanup; - - for (i = 0; i < nnodes; i++) { - ctxt->node = nodes[i]; - - if (!(src = virStoragePoolSourceListNewSource(list))) - goto cleanup; - - if (!(src->dir = virXPathString("string(//name)", ctxt))) { - virReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("failed to extract gluster volume name")); - goto cleanup; - } - - if (VIR_ALLOC_N(src->hosts, 1) < 0) - goto cleanup; - src->nhost = 1; - - if (VIR_STRDUP(src->hosts[0].name, host) < 0) - goto cleanup; - - src->format = pooltype; - } - - ret = nnodes; - - cleanup: - VIR_FREE(nodes); - xmlXPathFreeContext(ctxt); - xmlFreeDoc(doc); - VIR_FREE(outbuf); - virCommandFree(cmd); - VIR_FREE(glusterpath); - return ret; -} - - -#if WITH_BLKID - -typedef enum { - VIR_STORAGE_BLKID_PROBE_ERROR = -1, - VIR_STORAGE_BLKID_PROBE_UNDEFINED, /* Nothing found */ - VIR_STORAGE_BLKID_PROBE_UNKNOWN, /* Don't know libvirt fs/part type */ - VIR_STORAGE_BLKID_PROBE_MATCH, /* Matches the on disk format */ - VIR_STORAGE_BLKID_PROBE_DIFFERENT, /* Format doesn't match on disk format */ -} virStorageBackendBLKIDProbeResult; - -/* - * Utility function to probe for a file system on the device using the - * blkid "superblock" (e.g. default) APIs. - * - * NB: In general this helper will handle the virStoragePoolFormatFileSystem - * format types; however, if called from the Disk path, the initial fstype - * check will fail forcing the usage of the ProbePart helper. - * - * Returns virStorageBackendBLKIDProbeResult enum - */ -static virStorageBackendBLKIDProbeResult -virStorageBackendBLKIDFindFS(blkid_probe probe, - const char *device, - const char *format) -{ - const char *fstype = NULL; - - /* Make sure we're doing a superblock probe from the start */ - blkid_probe_enable_superblocks(probe, true); - blkid_probe_reset_superblocks_filter(probe); - - if (blkid_do_probe(probe) != 0) { - VIR_INFO("No filesystem found on device '%s'", device); - return VIR_STORAGE_BLKID_PROBE_UNDEFINED; - } - - if (blkid_probe_lookup_value(probe, "TYPE", &fstype, NULL) == 0) { - if (STREQ(fstype, format)) - return VIR_STORAGE_BLKID_PROBE_MATCH; - - return VIR_STORAGE_BLKID_PROBE_DIFFERENT; - } - - if (blkid_known_fstype(format) == 0) - return VIR_STORAGE_BLKID_PROBE_UNKNOWN; - - return VIR_STORAGE_BLKID_PROBE_ERROR; -} - - -/* - * Utility function to probe for a partition on the device using the - * blkid "partitions" APIs. - * - * NB: In general, this API will be validating the virStoragePoolFormatDisk - * format types. - * - * Returns virStorageBackendBLKIDProbeResult enum - */ -static virStorageBackendBLKIDProbeResult -virStorageBackendBLKIDFindPart(blkid_probe probe, - const char *device, - const char *format) -{ - const char *pttype = NULL; - - /* A blkid_known_pttype on "dvh" and "pc98" returns a failure; - * however, the blkid_do_probe for "dvh" returns "sgi" and - * for "pc98" it returns "dos". So since those will cause problems - * with startup comparison, let's just treat them as UNKNOWN causing - * the caller to fallback to using PARTED */ - if (STREQ(format, "dvh") || STREQ(format, "pc98")) - return VIR_STORAGE_BLKID_PROBE_UNKNOWN; - - /* Make sure we're doing a partitions probe from the start */ - blkid_probe_enable_partitions(probe, true); - blkid_probe_reset_partitions_filter(probe); - - if (blkid_do_probe(probe) != 0) { - VIR_INFO("No partition found on device '%s'", device); - return VIR_STORAGE_BLKID_PROBE_UNDEFINED; - } - - if (blkid_probe_lookup_value(probe, "PTTYPE", &pttype, NULL) == 0) { - if (STREQ(pttype, format)) - return VIR_STORAGE_BLKID_PROBE_MATCH; - - return VIR_STORAGE_BLKID_PROBE_DIFFERENT; - } - - if (blkid_known_pttype(format) == 0) - return VIR_STORAGE_BLKID_PROBE_UNKNOWN; - - return VIR_STORAGE_BLKID_PROBE_ERROR; -} - - -/* - * @device: Path to device - * @format: Desired format - * @writelabel: True if desire to write the label - * - * Use the blkid_ APIs in order to get details regarding whether a file - * system or partition exists on the disk already. - * - * Returns: - * -2: Force usage of PARTED for unknown types - * -1: An error was encountered, with error message set - * 0: No file system found - */ -static int -virStorageBackendBLKIDFindEmpty(const char *device, - const char *format, - bool writelabel) -{ - - int ret = -1; - int rc; - blkid_probe probe = NULL; - - VIR_DEBUG("Probe for existing filesystem/partition format %s on device %s", - format, device); - - if (!(probe = blkid_new_probe_from_filename(device))) { - virReportError(VIR_ERR_STORAGE_PROBE_FAILED, - _("Failed to create filesystem probe for device %s"), - device); - return -1; - } - - /* Look for something on FS, if it either doesn't recognize the - * format type as a valid FS format type or it doesn't find a valid - * format type on the device, then perform the same check using - * partition probing. */ - rc = virStorageBackendBLKIDFindFS(probe, device, format); - if (rc == VIR_STORAGE_BLKID_PROBE_UNDEFINED || - rc == VIR_STORAGE_BLKID_PROBE_UNKNOWN) { - - rc = virStorageBackendBLKIDFindPart(probe, device, format); - } - - switch (rc) { - case VIR_STORAGE_BLKID_PROBE_UNDEFINED: - if (writelabel) - ret = 0; - else - virReportError(VIR_ERR_STORAGE_PROBE_FAILED, - _("Device '%s' is unrecognized, requires build"), - device); - break; - - case VIR_STORAGE_BLKID_PROBE_ERROR: - virReportError(VIR_ERR_STORAGE_PROBE_FAILED, - _("Failed to probe for format type '%s'"), format); - break; - - case VIR_STORAGE_BLKID_PROBE_UNKNOWN: - ret = -2; - break; - - case VIR_STORAGE_BLKID_PROBE_MATCH: - if (writelabel) - virReportError(VIR_ERR_STORAGE_POOL_BUILT, - _("Device '%s' already formatted using '%s'"), - device, format); - else - ret = 0; - break; - - case VIR_STORAGE_BLKID_PROBE_DIFFERENT: - if (writelabel) - virReportError(VIR_ERR_STORAGE_POOL_BUILT, - _("Format of device '%s' does not match the " - "expected format '%s', forced overwrite is " - "necessary"), - device, format); - else - virReportError(VIR_ERR_OPERATION_INVALID, - _("Format of device '%s' does not match the " - "expected format '%s'"), - device, format); - break; - } - - if (ret == 0 && blkid_do_probe(probe) != 1) { - virReportError(VIR_ERR_STORAGE_PROBE_FAILED, "%s", - _("Found additional probes to run, probing may " - "be incorrect")); - ret = -1; - } - - blkid_free_probe(probe); - - return ret; -} - -#else /* #if WITH_BLKID */ - -static int -virStorageBackendBLKIDFindEmpty(const char *device ATTRIBUTE_UNUSED, - const char *format ATTRIBUTE_UNUSED, - bool writelabel ATTRIBUTE_UNUSED) -{ - return -2; -} - -#endif /* #if WITH_BLKID */ - - -#if WITH_STORAGE_DISK - -typedef enum { - VIR_STORAGE_PARTED_ERROR = -1, - VIR_STORAGE_PARTED_MATCH, /* Valid label found and matches format */ - VIR_STORAGE_PARTED_DIFFERENT, /* Valid label found but not match format */ - VIR_STORAGE_PARTED_UNKNOWN, /* No or unrecognized label */ - VIR_STORAGE_PARTED_NOPTTYPE, /* Did not find the Partition Table type */ - VIR_STORAGE_PARTED_PTTYPE_UNK, /* Partition Table type unknown*/ -} virStorageBackendPARTEDResult; - -/** - * Check for a valid disk label (partition table) on device using - * the PARTED command - * - * returns virStorageBackendPARTEDResult - */ -static virStorageBackendPARTEDResult -virStorageBackendPARTEDFindLabel(const char *device, - const char *format) -{ - const char *const args[] = { - device, "print", "--script", NULL, - }; - virCommandPtr cmd = virCommandNew(PARTED); - char *output = NULL; - char *error = NULL; - char *start, *end; - int ret = VIR_STORAGE_PARTED_ERROR; - - virCommandAddArgSet(cmd, args); - virCommandAddEnvString(cmd, "LC_ALL=C"); - virCommandSetOutputBuffer(cmd, &output); - virCommandSetErrorBuffer(cmd, &error); - - /* if parted succeeds we have a valid partition table */ - ret = virCommandRun(cmd, NULL); - if (ret < 0) { - if ((output && strstr(output, "unrecognised disk label")) || - (error && strstr(error, "unrecognised disk label"))) { - ret = VIR_STORAGE_PARTED_UNKNOWN; - } - goto cleanup; - } - - /* Search for "Partition Table:" in the output. If not present, - * then we cannot validate the partition table type. - */ - if (!(start = strstr(output, "Partition Table: ")) || - !(end = strstr(start, "\n"))) { - VIR_DEBUG("Unable to find tag in output: %s", output); - ret = VIR_STORAGE_PARTED_NOPTTYPE; - goto cleanup; - } - start += strlen("Partition Table: "); - *end = '\0'; - - /* on disk it's "msdos", but we document/use "dos" so deal with it here */ - if (STREQ(start, "msdos")) - start += 2; - - /* Make sure we know about this type */ - if (virStoragePoolFormatDiskTypeFromString(start) < 0) { - ret = VIR_STORAGE_PARTED_PTTYPE_UNK; - goto cleanup; - } - - /* Does the on disk match what the pool desired? */ - if (STREQ(start, format)) - ret = VIR_STORAGE_PARTED_MATCH; - - ret = VIR_STORAGE_PARTED_DIFFERENT; - - cleanup: - virCommandFree(cmd); - VIR_FREE(output); - VIR_FREE(error); - return ret; -} - - -/** - * Determine whether the label on the disk is valid or in a known format - * for the purpose of rewriting the label during build or being able to - * start a pool on a device. - * - * When 'writelabel' is true, if we find a valid disk label on the device, - * then we shouldn't be attempting to write as the volume may contain - * data. Force the usage of the overwrite flag to the build command in - * order to be certain. When the disk label is unrecognized, then it - * should be safe to write. - * - * When 'writelabel' is false, only if we find a valid disk label on the - * device should we allow the start since for this path we won't be - * rewriting the label. - * - * Return: 0 if it's OK - * -1 if something's wrong - */ -static int -virStorageBackendPARTEDValidLabel(const char *device, - const char *format, - bool writelabel) -{ - int ret = -1; - virStorageBackendPARTEDResult check; - - check = virStorageBackendPARTEDFindLabel(device, format); - switch (check) { - case VIR_STORAGE_PARTED_ERROR: - virReportError(VIR_ERR_OPERATION_FAILED, "%s", - _("Error checking for disk label, failed to get " - "disk partition information")); - break; - - case VIR_STORAGE_PARTED_MATCH: - if (writelabel) - virReportError(VIR_ERR_OPERATION_INVALID, - _("Disk label already formatted using '%s'"), - format); - else - ret = 0; - break; - - case VIR_STORAGE_PARTED_DIFFERENT: - virReportError(VIR_ERR_OPERATION_FAILED, "%s", - _("Known, but different label format present, " - "requires build --overwrite")); - break; - - case VIR_STORAGE_PARTED_UNKNOWN: - if (writelabel) - ret = 0; - else - virReportError(VIR_ERR_OPERATION_FAILED, "%s", - _("Unrecognized disk label found, requires build")); - break; - - case VIR_STORAGE_PARTED_NOPTTYPE: - virReportError(VIR_ERR_OPERATION_FAILED, "%s", - _("Unable to determine Partition Type, " - "requires build --overwrite")); - break; - - case VIR_STORAGE_PARTED_PTTYPE_UNK: - virReportError(VIR_ERR_OPERATION_FAILED, "%s", - _("Unknown Partition Type, requires build --overwrite")); - break; - } - - return ret; -} - -#else - -static int -virStorageBackendPARTEDValidLabel(const char *device ATTRIBUTE_UNUSED, - const char *format ATTRIBUTE_UNUSED, - bool writelabel ATTRIBUTE_UNUSED) -{ - return -2; -} - - -#endif /* #if WITH_STORAGE_DISK */ - - -/* virStorageBackendDeviceIsEmpty: - * @devpath: Path to the device to check - * @format: Desired format string - * @writelabel: True if the caller expects to write the label - * - * Check if the @devpath has some sort of known file system using the - * BLKID API if available. - * - * Returns true if the probe deems the device has nothing valid on it - * or when we cannot check and we're not writing the label. - * - * Returns false if the probe finds something - */ -bool -virStorageBackendDeviceIsEmpty(const char *devpath, - const char *format, - bool writelabel) -{ - int ret; - - if ((ret = virStorageBackendBLKIDFindEmpty(devpath, format, - writelabel)) == -2) - ret = virStorageBackendPARTEDValidLabel(devpath, format, writelabel); - - if (ret == -2 && !writelabel) - ret = 0; - - if (ret == -2) { - virReportError(VIR_ERR_OPERATION_INVALID, - _("Unable to probe '%s' for existing data, " - "forced overwrite is necessary"), - devpath); - } - - return ret == 0; -} diff --git a/src/storage/storage_backend.h b/src/storage/storage_backend.h index b00b0f897..b8fb368bb 100644 --- a/src/storage/storage_backend.h +++ b/src/storage/storage_backend.h @@ -1,9 +1,6 @@ /* * storage_backend.h: internal storage driver backend contract * - * Copyright (C) 2007-2010, 2012-2014 Red Hat, Inc. - * Copyright (C) 2007-2008 Daniel P. Berrange - * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either @@ -17,8 +14,6 @@ * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see * <http://www.gnu.org/licenses/>. - * - * Author: Daniel P. Berrange <berrange@redhat.com> */ #ifndef __VIR_STORAGE_BACKEND_H__ @@ -28,7 +23,6 @@ # include "internal.h" # include "storage_conf.h" -# include "vircommand.h" # include "storage_driver.h" typedef char * (*virStorageBackendFindPoolSources)(virConnectPtr conn, @@ -102,67 +96,6 @@ typedef int (*virStorageBackendVolumeWipe)(virConnectPtr conn, unsigned int algorithm, unsigned int flags); -/* File creation/cloning functions used for cloning between backends */ -int virStorageBackendCreateRaw(virConnectPtr conn, - virStoragePoolObjPtr pool, - virStorageVolDefPtr vol, - virStorageVolDefPtr inputvol, - unsigned int flags); - -int virStorageBackendCreateQemuImg(virConnectPtr conn, - virStoragePoolObjPtr pool, - virStorageVolDefPtr vol, - virStorageVolDefPtr inputvol, - unsigned int flags); - -int virStorageBackendCreatePloop(virConnectPtr conn, - virStoragePoolObjPtr pool, - virStorageVolDefPtr vol, - virStorageVolDefPtr inputvol, - unsigned int flags); - -int virStoragePloopResize(virStorageVolDefPtr vol, - unsigned long long capacity); - -int virStorageBackendRedoPloopUpdate(virStorageSourcePtr target, - struct stat *sb, int *fd, - unsigned int flags); -bool virStorageBackendIsPloopDir(char *path); - -virStorageBackendBuildVolFrom -virStorageBackendGetBuildVolFromFunction(virStorageVolDefPtr vol, - virStorageVolDefPtr inputvol); - -int virStorageBackendFindGlusterPoolSources(const char *host, - int pooltype, - virStoragePoolSourceListPtr list, - bool report); - -int virStorageBackendVolUploadLocal(virConnectPtr conn, - virStoragePoolObjPtr pool, - virStorageVolDefPtr vol, - virStreamPtr stream, - unsigned long long offset, - unsigned long long len, - unsigned int flags); -int virStorageBackendVolDownloadLocal(virConnectPtr conn, - virStoragePoolObjPtr pool, - virStorageVolDefPtr vol, - virStreamPtr stream, - unsigned long long offset, - unsigned long long len, - unsigned int flags); - -int virStorageBackendVolWipeLocal(virConnectPtr conn, - virStoragePoolObjPtr pool, - virStorageVolDefPtr vol, - unsigned int algorithm, - unsigned int flags); - -bool virStorageBackendDeviceIsEmpty(const char *devpath, - const char *format, - bool writelabel); - typedef struct _virStorageBackend virStorageBackend; typedef virStorageBackend *virStorageBackendPtr; @@ -192,64 +125,6 @@ struct _virStorageBackend { virStorageBackendPtr virStorageBackendForType(int type); -/* VolOpenCheckMode flags */ -enum { - VIR_STORAGE_VOL_OPEN_NOERROR = 1 << 0, /* don't error if unexpected type - * encountered, just warn */ - VIR_STORAGE_VOL_OPEN_REG = 1 << 1, /* regular files okay */ - VIR_STORAGE_VOL_OPEN_BLOCK = 1 << 2, /* block files okay */ - VIR_STORAGE_VOL_OPEN_CHAR = 1 << 3, /* char files okay */ - VIR_STORAGE_VOL_OPEN_DIR = 1 << 4, /* directories okay */ -}; - -/* VolReadErrorMode flags - * If flag is present, then operation won't cause fatal error for - * specified operation, rather a VIR_WARN will be issued and a -2 returned - * for function call - */ -enum { - VIR_STORAGE_VOL_READ_NOERROR = 1 << 0, /* ignore *read errors */ -}; - -# define VIR_STORAGE_VOL_OPEN_DEFAULT (VIR_STORAGE_VOL_OPEN_REG |\ - VIR_STORAGE_VOL_OPEN_BLOCK) - -int virStorageBackendVolOpen(const char *path, struct stat *sb, - unsigned int flags) - ATTRIBUTE_RETURN_CHECK - ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2); - -# define VIR_STORAGE_DEFAULT_POOL_PERM_MODE 0755 -# define VIR_STORAGE_DEFAULT_VOL_PERM_MODE 0600 - -int virStorageBackendUpdateVolInfo(virStorageVolDefPtr vol, - bool withBlockVolFormat, - unsigned int openflags, - unsigned int readflags); -int virStorageBackendUpdateVolTargetInfo(virStorageVolType voltype, - virStorageSourcePtr target, - bool withBlockVolFormat, - unsigned int openflags, - unsigned int readflags); -int virStorageBackendUpdateVolTargetInfoFD(virStorageSourcePtr target, - int fd, - struct stat *sb); - -bool virStorageBackendPoolPathIsStable(const char *path); -char *virStorageBackendStablePath(virStoragePoolObjPtr pool, - const char *devpath, - bool loop); - -virCommandPtr -virStorageBackendCreateQemuImgCmdFromVol(virConnectPtr conn, - virStoragePoolObjPtr pool, - virStorageVolDefPtr vol, - virStorageVolDefPtr inputvol, - unsigned int flags, - const char *create_tool, - int imgformat, - const char *secretPath); - /* ------- virStorageFile backends ------------ */ typedef struct _virStorageFileBackend virStorageFileBackend; typedef virStorageFileBackend *virStorageFileBackendPtr; diff --git a/src/storage/storage_backend_disk.c b/src/storage/storage_backend_disk.c index 1c79cc5a9..819f1e5c4 100644 --- a/src/storage/storage_backend_disk.c +++ b/src/storage/storage_backend_disk.c @@ -30,6 +30,7 @@ #include "virerror.h" #include "virlog.h" #include "storage_backend_disk.h" +#include "storage_util.h" #include "viralloc.h" #include "vircommand.h" #include "virfile.h" diff --git a/src/storage/storage_backend_fs.c b/src/storage/storage_backend_fs.c index ecf74de49..53710db11 100644 --- a/src/storage/storage_backend_fs.c +++ b/src/storage/storage_backend_fs.c @@ -39,6 +39,7 @@ #include "virerror.h" #include "storage_backend_fs.h" +#include "storage_util.h" #include "storage_conf.h" #include "virstoragefile.h" #include "vircommand.h" diff --git a/src/storage/storage_backend_gluster.c b/src/storage/storage_backend_gluster.c index e819f6299..7be2d9e81 100644 --- a/src/storage/storage_backend_gluster.c +++ b/src/storage/storage_backend_gluster.c @@ -31,6 +31,7 @@ #include "virstoragefile.h" #include "virstring.h" #include "viruri.h" +#include "storage_util.h" #define VIR_FROM_THIS VIR_FROM_STORAGE diff --git a/src/storage/storage_backend_iscsi.c b/src/storage/storage_backend_iscsi.c index 84ad6f3e8..8799349b6 100644 --- a/src/storage/storage_backend_iscsi.c +++ b/src/storage/storage_backend_iscsi.c @@ -44,6 +44,7 @@ #include "virstring.h" #include "viruuid.h" #include "secret_util.h" +#include "storage_util.h" #define VIR_FROM_THIS VIR_FROM_STORAGE diff --git a/src/storage/storage_backend_logical.c b/src/storage/storage_backend_logical.c index 6a6720e22..b0191aa45 100644 --- a/src/storage/storage_backend_logical.c +++ b/src/storage/storage_backend_logical.c @@ -39,6 +39,7 @@ #include "virlog.h" #include "virfile.h" #include "virstring.h" +#include "storage_util.h" #define VIR_FROM_THIS VIR_FROM_STORAGE diff --git a/src/storage/storage_backend_mpath.c b/src/storage/storage_backend_mpath.c index bf1b1abe0..a5d692a07 100644 --- a/src/storage/storage_backend_mpath.c +++ b/src/storage/storage_backend_mpath.c @@ -36,6 +36,7 @@ #include "virlog.h" #include "virfile.h" #include "virstring.h" +#include "storage_util.h" #define VIR_FROM_THIS VIR_FROM_STORAGE diff --git a/src/storage/storage_backend_rbd.c b/src/storage/storage_backend_rbd.c index b1c51ab1b..45beb107a 100644 --- a/src/storage/storage_backend_rbd.c +++ b/src/storage/storage_backend_rbd.c @@ -37,6 +37,7 @@ #include "rados/librados.h" #include "rbd/librbd.h" #include "secret_util.h" +#include "storage_util.h" #define VIR_FROM_THIS VIR_FROM_STORAGE diff --git a/src/storage/storage_backend_scsi.c b/src/storage/storage_backend_scsi.c index be02f6eec..d294d2ac0 100644 --- a/src/storage/storage_backend_scsi.c +++ b/src/storage/storage_backend_scsi.c @@ -35,6 +35,7 @@ #include "virfile.h" #include "vircommand.h" #include "virstring.h" +#include "storage_util.h" #define VIR_FROM_THIS VIR_FROM_STORAGE diff --git a/src/storage/storage_backend_sheepdog.c b/src/storage/storage_backend_sheepdog.c index ad2d75629..17202e487 100644 --- a/src/storage/storage_backend_sheepdog.c +++ b/src/storage/storage_backend_sheepdog.c @@ -33,6 +33,7 @@ #include "vircommand.h" #include "viralloc.h" #include "virstring.h" +#include "storage_util.h" #define VIR_FROM_THIS VIR_FROM_STORAGE diff --git a/src/storage/storage_backend_zfs.c b/src/storage/storage_backend_zfs.c index 3a43be41c..70c533a7f 100644 --- a/src/storage/storage_backend_zfs.c +++ b/src/storage/storage_backend_zfs.c @@ -27,6 +27,7 @@ #include "storage_backend_zfs.h" #include "virlog.h" #include "virstring.h" +#include "storage_util.h" #define VIR_FROM_THIS VIR_FROM_STORAGE diff --git a/src/storage/storage_driver.c b/src/storage/storage_driver.c index 8f1d3f04c..a52eae3e2 100644 --- a/src/storage/storage_driver.c +++ b/src/storage/storage_driver.c @@ -51,6 +51,7 @@ #include "virstring.h" #include "viraccessapicheck.h" #include "dirname.h" +#include "storage_util.h" #define VIR_FROM_THIS VIR_FROM_STORAGE diff --git a/src/storage/storage_util.c b/src/storage/storage_util.c new file mode 100644 index 000000000..6efb610df --- /dev/null +++ b/src/storage/storage_util.c @@ -0,0 +1,2919 @@ +/* + * storage_util.c: helper functions for the storage driver + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <http://www.gnu.org/licenses/>. + */ + +#include <config.h> + +#include <string.h> +#include <stdio.h> +#include <regex.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/stat.h> +#include <sys/param.h> +#include <dirent.h> +#include "dirname.h" +#ifdef __linux__ +# include <sys/ioctl.h> +# include <linux/fs.h> +# ifndef FS_NOCOW_FL +# define FS_NOCOW_FL 0x00800000 /* Do not cow file */ +# endif +#endif + +#if WITH_BLKID +# include <blkid/blkid.h> +#endif + +#if WITH_SELINUX +# include <selinux/selinux.h> +#endif + +#if HAVE_LINUX_BTRFS_H +# include <linux/btrfs.h> +#endif + +#include "datatypes.h" +#include "virerror.h" +#include "viralloc.h" +#include "internal.h" +#include "secret_conf.h" +#include "secret_util.h" +#include "vircrypto.h" +#include "viruuid.h" +#include "virstoragefile.h" +#include "storage_util.h" +#include "virlog.h" +#include "virfile.h" +#include "virjson.h" +#include "virqemu.h" +#include "stat-time.h" +#include "virstring.h" +#include "virxml.h" +#include "fdstream.h" + +#define VIR_FROM_THIS VIR_FROM_STORAGE + +VIR_LOG_INIT("storage.storage_util"); + +#define READ_BLOCK_SIZE_DEFAULT (1024 * 1024) +#define WRITE_BLOCK_SIZE_DEFAULT (4 * 1024) + +/* + * Perform the O(1) btrfs clone operation, if possible. + * Upon success, return 0. Otherwise, return -1 and set errno. + */ +#if HAVE_LINUX_BTRFS_H +static inline int +btrfsCloneFile(int dest_fd, int src_fd) +{ + return ioctl(dest_fd, BTRFS_IOC_CLONE, src_fd); +} +#else +static inline int +btrfsCloneFile(int dest_fd ATTRIBUTE_UNUSED, + int src_fd ATTRIBUTE_UNUSED) +{ + errno = ENOTSUP; + return -1; +} +#endif + +static int ATTRIBUTE_NONNULL(2) +virStorageBackendCopyToFD(virStorageVolDefPtr vol, + virStorageVolDefPtr inputvol, + int fd, + unsigned long long *total, + bool want_sparse, + bool reflink_copy) +{ + int inputfd = -1; + int amtread = -1; + int ret = 0; + size_t rbytes = READ_BLOCK_SIZE_DEFAULT; + int wbytes = 0; + int interval; + char *zerobuf = NULL; + char *buf = NULL; + struct stat st; + + if ((inputfd = open(inputvol->target.path, O_RDONLY)) < 0) { + ret = -errno; + virReportSystemError(errno, + _("could not open input path '%s'"), + inputvol->target.path); + goto cleanup; + } + +#ifdef __linux__ + if (ioctl(fd, BLKBSZGET, &wbytes) < 0) + wbytes = 0; +#endif + if ((wbytes == 0) && fstat(fd, &st) == 0) + wbytes = st.st_blksize; + if (wbytes < WRITE_BLOCK_SIZE_DEFAULT) + wbytes = WRITE_BLOCK_SIZE_DEFAULT; + + if (VIR_ALLOC_N(zerobuf, wbytes) < 0) { + ret = -errno; + goto cleanup; + } + + if (VIR_ALLOC_N(buf, rbytes) < 0) { + ret = -errno; + goto cleanup; + } + + if (reflink_copy) { + if (btrfsCloneFile(fd, inputfd) < 0) { + ret = -errno; + virReportSystemError(errno, + _("failed to clone files from '%s'"), + inputvol->target.path); + goto cleanup; + } else { + VIR_DEBUG("btrfs clone finished."); + goto cleanup; + } + } + + while (amtread != 0) { + int amtleft; + + if (*total < rbytes) + rbytes = *total; + + if ((amtread = saferead(inputfd, buf, rbytes)) < 0) { + ret = -errno; + virReportSystemError(errno, + _("failed reading from file '%s'"), + inputvol->target.path); + goto cleanup; + } + *total -= amtread; + + /* Loop over amt read in 512 byte increments, looking for sparse + * blocks */ + amtleft = amtread; + do { + interval = ((wbytes > amtleft) ? amtleft : wbytes); + int offset = amtread - amtleft; + + if (want_sparse && memcmp(buf+offset, zerobuf, interval) == 0) { + if (lseek(fd, interval, SEEK_CUR) < 0) { + ret = -errno; + virReportSystemError(errno, + _("cannot extend file '%s'"), + vol->target.path); + goto cleanup; + } + } else if (safewrite(fd, buf+offset, interval) < 0) { + ret = -errno; + virReportSystemError(errno, + _("failed writing to file '%s'"), + vol->target.path); + goto cleanup; + + } + } while ((amtleft -= interval) > 0); + } + + if (fdatasync(fd) < 0) { + ret = -errno; + virReportSystemError(errno, _("cannot sync data to file '%s'"), + vol->target.path); + goto cleanup; + } + + + if (VIR_CLOSE(inputfd) < 0) { + ret = -errno; + virReportSystemError(errno, + _("cannot close file '%s'"), + inputvol->target.path); + goto cleanup; + } + inputfd = -1; + + cleanup: + VIR_FORCE_CLOSE(inputfd); + + VIR_FREE(zerobuf); + VIR_FREE(buf); + + return ret; +} + +static int +virStorageBackendCreateBlockFrom(virConnectPtr conn ATTRIBUTE_UNUSED, + virStoragePoolObjPtr pool ATTRIBUTE_UNUSED, + virStorageVolDefPtr vol, + virStorageVolDefPtr inputvol, + unsigned int flags) +{ + int fd = -1; + int ret = -1; + unsigned long long remain; + struct stat st; + gid_t gid; + uid_t uid; + mode_t mode; + bool reflink_copy = false; + + virCheckFlags(VIR_STORAGE_VOL_CREATE_PREALLOC_METADATA | + VIR_STORAGE_VOL_CREATE_REFLINK, + -1); + + if (flags & VIR_STORAGE_VOL_CREATE_PREALLOC_METADATA) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("metadata preallocation is not supported for block " + "volumes")); + goto cleanup; + } + + if (flags & VIR_STORAGE_VOL_CREATE_REFLINK) + reflink_copy = true; + + if ((fd = open(vol->target.path, O_RDWR)) < 0) { + virReportSystemError(errno, + _("cannot create path '%s'"), + vol->target.path); + goto cleanup; + } + + remain = vol->target.capacity; + + if (inputvol) { + if (virStorageBackendCopyToFD(vol, inputvol, fd, &remain, + false, reflink_copy) < 0) + goto cleanup; + } + + if (fstat(fd, &st) == -1) { + virReportSystemError(errno, _("stat of '%s' failed"), + vol->target.path); + goto cleanup; + } + uid = (vol->target.perms->uid != st.st_uid) ? vol->target.perms->uid + : (uid_t) -1; + gid = (vol->target.perms->gid != st.st_gid) ? vol->target.perms->gid + : (gid_t) -1; + if (((uid != (uid_t) -1) || (gid != (gid_t) -1)) + && (fchown(fd, uid, gid) < 0)) { + virReportSystemError(errno, + _("cannot chown '%s' to (%u, %u)"), + vol->target.path, (unsigned int) uid, + (unsigned int) gid); + goto cleanup; + } + + mode = (vol->target.perms->mode == (mode_t) -1 ? + VIR_STORAGE_DEFAULT_VOL_PERM_MODE : vol->target.perms->mode); + if (fchmod(fd, mode) < 0) { + virReportSystemError(errno, + _("cannot set mode of '%s' to %04o"), + vol->target.path, mode); + goto cleanup; + } + if (VIR_CLOSE(fd) < 0) { + virReportSystemError(errno, + _("cannot close file '%s'"), + vol->target.path); + goto cleanup; + } + fd = -1; + + ret = 0; + cleanup: + VIR_FORCE_CLOSE(fd); + + return ret; +} + +static int +createRawFile(int fd, virStorageVolDefPtr vol, + virStorageVolDefPtr inputvol, + bool reflink_copy) +{ + bool need_alloc = true; + int ret = 0; + unsigned long long pos = 0; + + /* If the new allocation is lower than the capacity of the original file, + * the cloned volume will be sparse */ + if (inputvol && + vol->target.allocation < inputvol->target.capacity) + need_alloc = false; + + /* Seek to the final size, so the capacity is available upfront + * for progress reporting */ + if (ftruncate(fd, vol->target.capacity) < 0) { + ret = -errno; + virReportSystemError(errno, + _("cannot extend file '%s'"), + vol->target.path); + goto cleanup; + } + +/* Avoid issues with older kernel's <linux/fs.h> namespace pollution. */ +#if HAVE_FALLOCATE - 0 + /* Try to preallocate all requested disk space, but fall back to + * other methods if this fails with ENOSYS or EOPNOTSUPP. If allocation + * is 0 (or less than 0), then fallocate will fail with EINVAL. + * NOTE: do not use posix_fallocate; posix_fallocate falls back + * to writing zeroes block by block in case fallocate isn't + * available, and since we're going to copy data from another + * file it doesn't make sense to write the file twice. */ + if (vol->target.allocation && need_alloc) { + if (fallocate(fd, 0, 0, vol->target.allocation) == 0) { + need_alloc = false; + } else if (errno != ENOSYS && errno != EOPNOTSUPP) { + ret = -errno; + virReportSystemError(errno, + _("cannot allocate %llu bytes in file '%s'"), + vol->target.allocation, vol->target.path); + goto cleanup; + } + } +#endif + + if (inputvol) { + unsigned long long remain = inputvol->target.capacity; + /* allow zero blocks to be skipped if we've requested sparse + * allocation (allocation < capacity) or we have already + * been able to allocate the required space. */ + if ((ret = virStorageBackendCopyToFD(vol, inputvol, fd, &remain, + !need_alloc, reflink_copy)) < 0) + goto cleanup; + + /* If the new allocation is greater than the original capacity, + * but fallocate failed, fill the rest with zeroes. + */ + pos = inputvol->target.capacity - remain; + } + + if (need_alloc && (vol->target.allocation - pos > 0)) { + if (safezero(fd, pos, vol->target.allocation - pos) < 0) { + ret = -errno; + virReportSystemError(errno, _("cannot fill file '%s'"), + vol->target.path); + goto cleanup; + } + } + + if (fsync(fd) < 0) { + ret = -errno; + virReportSystemError(errno, _("cannot sync data to file '%s'"), + vol->target.path); + goto cleanup; + } + + cleanup: + return ret; +} + +int +virStorageBackendCreateRaw(virConnectPtr conn ATTRIBUTE_UNUSED, + virStoragePoolObjPtr pool, + virStorageVolDefPtr vol, + virStorageVolDefPtr inputvol, + unsigned int flags) +{ + int ret = -1; + int fd = -1; + int operation_flags; + bool reflink_copy = false; + mode_t open_mode = VIR_STORAGE_DEFAULT_VOL_PERM_MODE; + bool created = false; + + virCheckFlags(VIR_STORAGE_VOL_CREATE_PREALLOC_METADATA | + VIR_STORAGE_VOL_CREATE_REFLINK, + -1); + + if (flags & VIR_STORAGE_VOL_CREATE_PREALLOC_METADATA) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("metadata preallocation is not supported for raw " + "volumes")); + goto cleanup; + } + + if (vol->target.backingStore) { + virReportError(VIR_ERR_NO_SUPPORT, "%s", + _("backing storage not supported for raw volumes")); + goto cleanup; + } + + if (flags & VIR_STORAGE_VOL_CREATE_REFLINK) + reflink_copy = true; + + + if (vol->target.encryption != NULL) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("storage pool does not support encrypted volumes")); + goto cleanup; + } + + operation_flags = VIR_FILE_OPEN_FORCE_MODE | VIR_FILE_OPEN_FORCE_OWNER; + if (pool->def->type == VIR_STORAGE_POOL_NETFS) + operation_flags |= VIR_FILE_OPEN_FORK; + + if (vol->target.perms->mode != (mode_t) -1) + open_mode = vol->target.perms->mode; + + if ((fd = virFileOpenAs(vol->target.path, + O_RDWR | O_CREAT | O_EXCL, + open_mode, + vol->target.perms->uid, + vol->target.perms->gid, + operation_flags)) < 0) { + virReportSystemError(-fd, + _("Failed to create file '%s'"), + vol->target.path); + goto cleanup; + } + created = true; + + if (vol->target.nocow) { +#ifdef __linux__ + int attr; + + /* Set NOCOW flag. This is an optimisation for btrfs. + * The FS_IOC_SETFLAGS ioctl return value will be ignored since any + * failure of this operation should not block the volume creation. + */ + if (ioctl(fd, FS_IOC_GETFLAGS, &attr) < 0) { + virReportSystemError(errno, "%s", _("Failed to get fs flags")); + } else { + attr |= FS_NOCOW_FL; + if (ioctl(fd, FS_IOC_SETFLAGS, &attr) < 0) { + virReportSystemError(errno, "%s", + _("Failed to set NOCOW flag")); + } + } +#endif + } + + if ((ret = createRawFile(fd, vol, inputvol, reflink_copy)) < 0) + /* createRawFile already reported the exact error. */ + ret = -1; + + cleanup: + if (ret < 0 && created) + ignore_value(virFileRemove(vol->target.path, + vol->target.perms->uid, + vol->target.perms->gid)); + VIR_FORCE_CLOSE(fd); + return ret; +} + +static int +virStorageGenerateSecretUUID(virConnectPtr conn, + unsigned char *uuid) +{ + unsigned attempt; + + for (attempt = 0; attempt < 65536; attempt++) { + virSecretPtr tmp; + if (virUUIDGenerate(uuid) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("unable to generate uuid")); + return -1; + } + tmp = conn->secretDriver->secretLookupByUUID(conn, uuid); + if (tmp == NULL) + return 0; + + virObjectUnref(tmp); + } + + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("too many conflicts when generating a uuid")); + + return -1; +} + +static int +virStorageGenerateQcowEncryption(virConnectPtr conn, + virStorageVolDefPtr vol) +{ + virSecretDefPtr def = NULL; + virBuffer buf = VIR_BUFFER_INITIALIZER; + virStorageEncryptionPtr enc; + virStorageEncryptionSecretPtr enc_secret = NULL; + virSecretPtr secret = NULL; + char *xml; + unsigned char value[VIR_STORAGE_QCOW_PASSPHRASE_SIZE]; + int ret = -1; + + if (conn->secretDriver == NULL || + conn->secretDriver->secretLookupByUUID == NULL || + conn->secretDriver->secretDefineXML == NULL || + conn->secretDriver->secretSetValue == NULL) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("secret storage not supported")); + goto cleanup; + } + + enc = vol->target.encryption; + if (enc->nsecrets != 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("secrets already defined")); + goto cleanup; + } + + if (VIR_ALLOC(enc_secret) < 0 || VIR_REALLOC_N(enc->secrets, 1) < 0 || + VIR_ALLOC(def) < 0) + goto cleanup; + + def->isephemeral = false; + def->isprivate = false; + if (virStorageGenerateSecretUUID(conn, def->uuid) < 0) + goto cleanup; + + def->usage_type = VIR_SECRET_USAGE_TYPE_VOLUME; + if (VIR_STRDUP(def->usage_id, vol->target.path) < 0) + goto cleanup; + xml = virSecretDefFormat(def); + virSecretDefFree(def); + def = NULL; + if (xml == NULL) + goto cleanup; + + secret = conn->secretDriver->secretDefineXML(conn, xml, 0); + if (secret == NULL) { + VIR_FREE(xml); + goto cleanup; + } + VIR_FREE(xml); + + if (virStorageGenerateQcowPassphrase(value) < 0) + goto cleanup; + + if (conn->secretDriver->secretSetValue(secret, value, sizeof(value), 0) < 0) + goto cleanup; + + enc_secret->type = VIR_STORAGE_ENCRYPTION_SECRET_TYPE_PASSPHRASE; + enc_secret->seclookupdef.type = VIR_SECRET_LOOKUP_TYPE_UUID; + memcpy(enc_secret->seclookupdef.u.uuid, secret->uuid, VIR_UUID_BUFLEN); + enc->format = VIR_STORAGE_ENCRYPTION_FORMAT_QCOW; + enc->secrets[0] = enc_secret; /* Space for secrets[0] allocated above */ + enc_secret = NULL; + enc->nsecrets = 1; + + ret = 0; + + cleanup: + if (secret != NULL) { + if (ret != 0 && + conn->secretDriver->secretUndefine != NULL) + conn->secretDriver->secretUndefine(secret); + virObjectUnref(secret); + } + virBufferFreeAndReset(&buf); + virSecretDefFree(def); + VIR_FREE(enc_secret); + return ret; +} + +static int +virStorageBackendCreateExecCommand(virStoragePoolObjPtr pool, + virStorageVolDefPtr vol, + virCommandPtr cmd) +{ + struct stat st; + gid_t gid; + uid_t uid; + mode_t mode = (vol->target.perms->mode == (mode_t) -1 ? + VIR_STORAGE_DEFAULT_VOL_PERM_MODE : + vol->target.perms->mode); + bool filecreated = false; + int ret = -1; + + if ((pool->def->type == VIR_STORAGE_POOL_NETFS) + && (((geteuid() == 0) + && (vol->target.perms->uid != (uid_t) -1) + && (vol->target.perms->uid != 0)) + || ((vol->target.perms->gid != (gid_t) -1) + && (vol->target.perms->gid != getegid())))) { + + virCommandSetUID(cmd, vol->target.perms->uid); + virCommandSetGID(cmd, vol->target.perms->gid); + virCommandSetUmask(cmd, S_IRWXUGO ^ mode); + + if (virCommandRun(cmd, NULL) == 0) { + /* command was successfully run, check if the file was created */ + if (stat(vol->target.path, &st) >= 0) { + filecreated = true; + + /* seems qemu-img disregards umask and open/creates using 0644. + * If that doesn't match what we expect, then let's try to + * re-open the file and attempt to force the mode change. + */ + if (mode != (st.st_mode & S_IRWXUGO)) { + int fd = -1; + int flags = VIR_FILE_OPEN_FORK | VIR_FILE_OPEN_FORCE_MODE; + + if ((fd = virFileOpenAs(vol->target.path, O_RDWR, mode, + vol->target.perms->uid, + vol->target.perms->gid, + flags)) >= 0) { + /* Success - means we're good */ + VIR_FORCE_CLOSE(fd); + ret = 0; + goto cleanup; + } + } + } + } + } + + if (!filecreated) { + /* don't change uid/gid/mode if we retry */ + virCommandSetUID(cmd, -1); + virCommandSetGID(cmd, -1); + virCommandSetUmask(cmd, 0); + + if (virCommandRun(cmd, NULL) < 0) + goto cleanup; + if (stat(vol->target.path, &st) < 0) { + virReportSystemError(errno, + _("failed to create %s"), vol->target.path); + goto cleanup; + } + filecreated = true; + } + + uid = (vol->target.perms->uid != st.st_uid) ? vol->target.perms->uid + : (uid_t) -1; + gid = (vol->target.perms->gid != st.st_gid) ? vol->target.perms->gid + : (gid_t) -1; + if (((uid != (uid_t) -1) || (gid != (gid_t) -1)) + && (chown(vol->target.path, uid, gid) < 0)) { + virReportSystemError(errno, + _("cannot chown %s to (%u, %u)"), + vol->target.path, (unsigned int) uid, + (unsigned int) gid); + goto cleanup; + } + + if (mode != (st.st_mode & S_IRWXUGO) && + chmod(vol->target.path, mode) < 0) { + virReportSystemError(errno, + _("cannot set mode of '%s' to %04o"), + vol->target.path, mode); + goto cleanup; + } + + ret = 0; + + cleanup: + if (ret < 0 && filecreated) + virFileRemove(vol->target.path, vol->target.perms->uid, + vol->target.perms->gid); + return ret; +} + +/* Create ploop directory with ploop image and DiskDescriptor.xml + * if function fails to create image file the directory will be deleted.*/ +int +virStorageBackendCreatePloop(virConnectPtr conn ATTRIBUTE_UNUSED, + virStoragePoolObjPtr pool ATTRIBUTE_UNUSED, + virStorageVolDefPtr vol, + virStorageVolDefPtr inputvol, + unsigned int flags) +{ + int ret = -1; + virCommandPtr cmd = NULL; + char *create_tool = NULL; + bool created = false; + + virCheckFlags(0, -1); + + if (inputvol && inputvol->target.format != VIR_STORAGE_FILE_PLOOP) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("unsupported input storage vol type %d"), + inputvol->target.format); + return -1; + } + + if (vol->target.encryption != NULL) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("encrypted ploop volumes are not supported with " + "ploop init")); + return -1; + } + + if (vol->target.backingStore != NULL) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("copy-on-write ploop volumes are not yet supported")); + return -1; + } + + create_tool = virFindFileInPath("ploop"); + if (!create_tool && !inputvol) { + virReportError(VIR_ERR_INTERNAL_ERROR, + "%s", _("unable to find ploop, please install " + "ploop tools")); + return -1; + } + + if (!inputvol) { + if ((virDirCreate(vol->target.path, + (vol->target.perms->mode == (mode_t) -1 ? + VIR_STORAGE_DEFAULT_VOL_PERM_MODE: + vol->target.perms->mode), + vol->target.perms->uid, + vol->target.perms->gid, + 0)) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("error creating directory for ploop volume")); + goto cleanup; + } + cmd = virCommandNewArgList(create_tool, "init", "-s", NULL); + virCommandAddArgFormat(cmd, "%lluM", VIR_DIV_UP(vol->target.capacity, + (1024 * 1024))); + virCommandAddArgList(cmd, "-t", "ext4", NULL); + virCommandAddArgFormat(cmd, "%s/root.hds", vol->target.path); + + } else { + vol->target.capacity = inputvol->target.capacity; + cmd = virCommandNewArgList("cp", "-r", inputvol->target.path, + vol->target.path, NULL); + } + created = true; + ret = virCommandRun(cmd, NULL); + cleanup: + virCommandFree(cmd); + VIR_FREE(create_tool); + if (ret < 0 && created) + virFileDeleteTree(vol->target.path); + return ret; +} + +int +virStoragePloopResize(virStorageVolDefPtr vol, + unsigned long long capacity) +{ + int ret = -1; + virCommandPtr cmd = NULL; + char *resize_tool = NULL; + + resize_tool = virFindFileInPath("ploop"); + if (!resize_tool) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("unable to find ploop, please install ploop tools")); + return -1; + } + cmd = virCommandNewArgList(resize_tool, "resize", "-s", NULL); + virCommandAddArgFormat(cmd, "%lluM", VIR_DIV_UP(capacity, (1024 * 1024))); + + virCommandAddArgFormat(cmd, "%s/DiskDescriptor.xml", vol->target.path); + + ret = virCommandRun(cmd, NULL); + virCommandFree(cmd); + VIR_FREE(resize_tool); + return ret; +} + +/* Flag values shared w/ storagevolxml2argvtest.c. + * + * QEMU_IMG_BACKING_FORMAT_OPTIONS (added in qemu 0.11) + * QEMU_IMG_BACKING_FORMAT_OPTIONS_COMPAT + * was made necessary due to 2.0 change to change the default + * qcow2 file format from 0.10 to 1.1. + */ +enum { + QEMU_IMG_BACKING_FORMAT_OPTIONS = 0, + QEMU_IMG_BACKING_FORMAT_OPTIONS_COMPAT, +}; + +static bool +virStorageBackendQemuImgSupportsCompat(const char *qemuimg) +{ + bool ret = false; + char *output; + virCommandPtr cmd = NULL; + + cmd = virCommandNewArgList(qemuimg, "create", "-o", "?", "-f", "qcow2", + "/dev/null", NULL); + + virCommandAddEnvString(cmd, "LC_ALL=C"); + virCommandSetOutputBuffer(cmd, &output); + + if (virCommandRun(cmd, NULL) < 0) + goto cleanup; + + if (strstr(output, "\ncompat ")) + ret = true; + + cleanup: + virCommandFree(cmd); + VIR_FREE(output); + return ret; +} + + +static int +virStorageBackendQEMUImgBackingFormat(const char *qemuimg) +{ + /* As of QEMU 0.11 the [-o options] support was added via qemu + * commit id '9ea2ea71', so we start with that base and figure + * out what else we have */ + int ret = QEMU_IMG_BACKING_FORMAT_OPTIONS; + + /* QEMU 2.0 changed to using a format that only QEMU 1.1 and newer + * understands. Since we still support QEMU 0.12 and newer, we need + * to be able to handle the previous format as can be set via a + * compat=0.10 option. */ + if (virStorageBackendQemuImgSupportsCompat(qemuimg)) + ret = QEMU_IMG_BACKING_FORMAT_OPTIONS_COMPAT; + + return ret; +} + +/* The _virStorageBackendQemuImgInfo separates the command line building from + * the volume definition so that qemuDomainSnapshotCreateInactiveExternal can + * use it without needing to deal with a volume. + */ +struct _virStorageBackendQemuImgInfo { + int format; + const char *path; + unsigned long long size_arg; + bool encryption; + bool preallocate; + const char *compat; + virBitmapPtr features; + bool nocow; + + const char *backingPath; + int backingFormat; + + const char *inputPath; + const char *inputFormatStr; + int inputFormat; + + char *secretAlias; + const char *secretPath; +}; + + +static int +virStorageBackendCreateQemuImgOpts(virStorageEncryptionInfoDefPtr enc, + char **opts, + struct _virStorageBackendQemuImgInfo info) +{ + virBuffer buf = VIR_BUFFER_INITIALIZER; + + if (info.format == VIR_STORAGE_FILE_RAW && enc) { + virQEMUBuildLuksOpts(&buf, enc, info.secretAlias); + } else { + if (info.backingPath) + virBufferAsprintf(&buf, "backing_fmt=%s,", + virStorageFileFormatTypeToString(info.backingFormat)); + if (info.encryption) + virBufferAddLit(&buf, "encryption=on,"); + if (info.preallocate) + virBufferAddLit(&buf, "preallocation=metadata,"); + } + + if (info.nocow) + virBufferAddLit(&buf, "nocow=on,"); + + if (info.compat) + virBufferAsprintf(&buf, "compat=%s,", info.compat); + + if (info.features && info.format == VIR_STORAGE_FILE_QCOW2) { + if (virBitmapIsBitSet(info.features, + VIR_STORAGE_FILE_FEATURE_LAZY_REFCOUNTS)) { + if (STREQ_NULLABLE(info.compat, "0.10")) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("lazy_refcounts not supported with compat" + " level %s"), + info.compat); + goto error; + } + virBufferAddLit(&buf, "lazy_refcounts,"); + } + } + + virBufferTrim(&buf, ",", -1); + + if (virBufferCheckError(&buf) < 0) + goto error; + + *opts = virBufferContentAndReset(&buf); + return 0; + + error: + virBufferFreeAndReset(&buf); + return -1; +} + + +/* virStorageBackendCreateQemuImgCheckEncryption: + * @format: format of file found + * @conn: pointer to connection + * @vol: pointer to volume def + * + * Ensure the proper setup for encryption. + * + * Returns 0 on success, -1 on failure w/ error set + */ +static int +virStorageBackendCreateQemuImgCheckEncryption(int format, + const char *type, + virConnectPtr conn, + virStorageVolDefPtr vol) +{ + virStorageEncryptionPtr enc = vol->target.encryption; + + if (format == VIR_STORAGE_FILE_QCOW || format == VIR_STORAGE_FILE_QCOW2) { + if (enc->format != VIR_STORAGE_ENCRYPTION_FORMAT_QCOW && + enc->format != VIR_STORAGE_ENCRYPTION_FORMAT_DEFAULT) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("unsupported volume encryption format %d"), + vol->target.encryption->format); + return -1; + } + if (enc->nsecrets > 1) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("too many secrets for qcow encryption")); + return -1; + } + if (enc->format == VIR_STORAGE_ENCRYPTION_FORMAT_DEFAULT || + enc->nsecrets == 0) { + if (virStorageGenerateQcowEncryption(conn, vol) < 0) + return -1; + } + } else if (format == VIR_STORAGE_FILE_RAW) { + if (enc->format != VIR_STORAGE_ENCRYPTION_FORMAT_LUKS) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("unsupported volume encryption format %d"), + vol->target.encryption->format); + return -1; + } + if (enc->nsecrets > 1) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("too many secrets for luks encryption")); + return -1; + } + if (enc->nsecrets == 0) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("no secret provided for luks encryption")); + return -1; + } + if (!virCryptoHaveCipher(VIR_CRYPTO_CIPHER_AES256CBC)) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("luks encryption usage requires encrypted " + "secret generation to be supported")); + return -1; + } + } else { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("volume encryption unsupported with format %s"), type); + return -1; + } + + return 0; +} + + +static int +virStorageBackendCreateQemuImgSetInput(virStorageVolDefPtr inputvol, + struct _virStorageBackendQemuImgInfo *info) +{ + if (!(info->inputPath = inputvol->target.path)) { + virReportError(VIR_ERR_INVALID_ARG, "%s", + _("missing input volume target path")); + return -1; + } + + info->inputFormat = inputvol->target.format; + if (inputvol->type == VIR_STORAGE_VOL_BLOCK) + info->inputFormat = VIR_STORAGE_FILE_RAW; + if (!(info->inputFormatStr = + virStorageFileFormatTypeToString(info->inputFormat))) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("unknown storage vol type %d"), + info->inputFormat); + return -1; + } + + return 0; +} + + +static int +virStorageBackendCreateQemuImgSetBacking(virStoragePoolObjPtr pool, + virStorageVolDefPtr vol, + virStorageVolDefPtr inputvol, + struct _virStorageBackendQemuImgInfo *info) +{ + int accessRetCode = -1; + char *absolutePath = NULL; + + if (info->format == VIR_STORAGE_FILE_RAW) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("cannot set backing store for raw volume")); + return -1; + } + + info->backingFormat = vol->target.backingStore->format; + info->backingPath = vol->target.backingStore->path; + + if (info->preallocate) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("metadata preallocation conflicts with backing" + " store")); + return -1; + } + + /* XXX: Not strictly required: qemu-img has an option a different + * backing store, not really sure what use it serves though, and it + * may cause issues with lvm. Untested essentially. + */ + if (inputvol && inputvol->target.backingStore && + STRNEQ_NULLABLE(inputvol->target.backingStore->path, + info->backingPath)) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("a different backing store cannot be specified.")); + return -1; + } + + if (!virStorageFileFormatTypeToString(info->backingFormat)) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("unknown storage vol backing store type %d"), + info->backingFormat); + return -1; + } + + /* Convert relative backing store paths to absolute paths for access + * validation. + */ + if ('/' != *(info->backingPath) && + virAsprintf(&absolutePath, "%s/%s", pool->def->target.path, + info->backingPath) < 0) + return -1; + accessRetCode = access(absolutePath ? absolutePath : + info->backingPath, R_OK); + VIR_FREE(absolutePath); + if (accessRetCode != 0) { + virReportSystemError(errno, + _("inaccessible backing store volume %s"), + info->backingPath); + return -1; + } + + return 0; +} + + +static int +virStorageBackendCreateQemuImgSetOptions(virCommandPtr cmd, + int imgformat, + virStorageEncryptionInfoDefPtr enc, + struct _virStorageBackendQemuImgInfo info) +{ + char *opts = NULL; + + if (info.format == VIR_STORAGE_FILE_QCOW2 && !info.compat && + imgformat >= QEMU_IMG_BACKING_FORMAT_OPTIONS_COMPAT) + info.compat = "0.10"; + + if (virStorageBackendCreateQemuImgOpts(enc, &opts, info) < 0) + return -1; + if (opts) + virCommandAddArgList(cmd, "-o", opts, NULL); + VIR_FREE(opts); + + return 0; +} + + +/* Add a secret object to the command line: + * --object secret,id=$secretAlias,file=$secretPath + * + * NB: format=raw is assumed + */ +static int +virStorageBackendCreateQemuImgSecretObject(virCommandPtr cmd, + virStorageVolDefPtr vol, + struct _virStorageBackendQemuImgInfo *info) +{ + virBuffer buf = VIR_BUFFER_INITIALIZER; + char *commandStr = NULL; + + if (virAsprintf(&info->secretAlias, "%s_luks0", vol->name) < 0) + return -1; + + virBufferAsprintf(&buf, "secret,id=%s,file=", info->secretAlias); + virQEMUBuildBufferEscapeComma(&buf, info->secretPath); + + if (virBufferCheckError(&buf) < 0) { + virBufferFreeAndReset(&buf); + return -1; + } + + commandStr = virBufferContentAndReset(&buf); + + virCommandAddArgList(cmd, "--object", commandStr, NULL); + + VIR_FREE(commandStr); + return 0; +} + + +/* Create a qemu-img virCommand from the supplied binary path, + * volume definitions and imgformat + */ +virCommandPtr +virStorageBackendCreateQemuImgCmdFromVol(virConnectPtr conn, + virStoragePoolObjPtr pool, + virStorageVolDefPtr vol, + virStorageVolDefPtr inputvol, + unsigned int flags, + const char *create_tool, + int imgformat, + const char *secretPath) +{ + virCommandPtr cmd = NULL; + const char *type; + struct _virStorageBackendQemuImgInfo info = { + .format = vol->target.format, + .path = vol->target.path, + .encryption = vol->target.encryption != NULL, + .preallocate = !!(flags & VIR_STORAGE_VOL_CREATE_PREALLOC_METADATA), + .compat = vol->target.compat, + .features = vol->target.features, + .nocow = vol->target.nocow, + .secretPath = secretPath, + .secretAlias = NULL, + }; + virStorageEncryptionInfoDefPtr enc = NULL; + + virCheckFlags(VIR_STORAGE_VOL_CREATE_PREALLOC_METADATA, NULL); + + /* Treat output block devices as 'raw' format */ + if (vol->type == VIR_STORAGE_VOL_BLOCK) + info.format = VIR_STORAGE_FILE_RAW; + + if (!(type = virStorageFileFormatTypeToString(info.format))) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("unknown storage vol type %d"), + info.format); + return NULL; + } + + if (info.preallocate && info.format != VIR_STORAGE_FILE_QCOW2) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("metadata preallocation only available with qcow2")); + return NULL; + } + if (info.compat && info.format != VIR_STORAGE_FILE_QCOW2) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("compatibility option only available with qcow2")); + return NULL; + } + if (info.features && info.format != VIR_STORAGE_FILE_QCOW2) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("format features only available with qcow2")); + return NULL; + } + if (info.format == VIR_STORAGE_FILE_RAW && + vol->target.encryption != NULL) { + if (inputvol) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("cannot use inputvol with encrypted raw volume")); + return NULL; + } + if (!info.encryption) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("missing encryption description")); + return NULL; + } + if (vol->target.encryption->format == VIR_STORAGE_ENCRYPTION_FORMAT_LUKS) { + type = "luks"; + } else { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Only luks encryption is supported for raw files")); + return NULL; + } + } + + if (inputvol && + virStorageBackendCreateQemuImgSetInput(inputvol, &info) < 0) + return NULL; + + if (vol->target.backingStore && + virStorageBackendCreateQemuImgSetBacking(pool, vol, inputvol, + &info) < 0) + return NULL; + + if (info.encryption && + virStorageBackendCreateQemuImgCheckEncryption(info.format, type, + conn, vol) < 0) + return NULL; + + + /* Size in KB */ + info.size_arg = VIR_DIV_UP(vol->target.capacity, 1024); + + cmd = virCommandNew(create_tool); + + /* ignore the backing volume when we're converting a volume */ + if (info.inputPath) + info.backingPath = NULL; + + if (info.inputPath) + virCommandAddArgList(cmd, "convert", "-f", info.inputFormatStr, + "-O", type, NULL); + else + virCommandAddArgList(cmd, "create", "-f", type, NULL); + + if (info.backingPath) + virCommandAddArgList(cmd, "-b", info.backingPath, NULL); + + if (info.format == VIR_STORAGE_FILE_RAW && + vol->target.encryption != NULL && + vol->target.encryption->format == VIR_STORAGE_ENCRYPTION_FORMAT_LUKS) { + if (virStorageBackendCreateQemuImgSecretObject(cmd, vol, &info) < 0) { + VIR_FREE(info.secretAlias); + virCommandFree(cmd); + return NULL; + } + enc = &vol->target.encryption->encinfo; + } + + if (virStorageBackendCreateQemuImgSetOptions(cmd, imgformat, + enc, info) < 0) { + VIR_FREE(info.secretAlias); + virCommandFree(cmd); + return NULL; + } + VIR_FREE(info.secretAlias); + + if (info.inputPath) + virCommandAddArg(cmd, info.inputPath); + virCommandAddArg(cmd, info.path); + if (!info.inputPath && (info.size_arg || !info.backingPath)) + virCommandAddArgFormat(cmd, "%lluK", info.size_arg); + + return cmd; +} + + +static char * +virStorageBackendCreateQemuImgSecretPath(virConnectPtr conn, + virStoragePoolObjPtr pool, + virStorageVolDefPtr vol) +{ + virStorageEncryptionPtr enc = vol->target.encryption; + char *secretPath = NULL; + int fd = -1; + uint8_t *secret = NULL; + size_t secretlen = 0; + + if (!enc) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("missing encryption description")); + return NULL; + } + + if (!conn || !conn->secretDriver || + !conn->secretDriver->secretLookupByUUID || + !conn->secretDriver->secretLookupByUsage || + !conn->secretDriver->secretGetValue) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("unable to look up encryption secret")); + return NULL; + } + + if (!(secretPath = virStoragePoolObjBuildTempFilePath(pool, vol))) + goto cleanup; + + if ((fd = mkostemp(secretPath, O_CLOEXEC)) < 0) { + virReportSystemError(errno, "%s", + _("failed to open luks secret file for write")); + goto error; + } + + if (virSecretGetSecretString(conn, &enc->secrets[0]->seclookupdef, + VIR_SECRET_USAGE_TYPE_VOLUME, + &secret, &secretlen) < 0) + goto error; + + if (safewrite(fd, secret, secretlen) < 0) { + virReportSystemError(errno, "%s", + _("failed to write luks secret file")); + goto error; + } + VIR_FORCE_CLOSE(fd); + + if ((vol->target.perms->uid != (uid_t) -1) && + (vol->target.perms->gid != (gid_t) -1)) { + if (chown(secretPath, vol->target.perms->uid, + vol->target.perms->gid) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("failed to chown luks secret file")); + goto error; + } + } + + cleanup: + VIR_DISPOSE_N(secret, secretlen); + VIR_FORCE_CLOSE(fd); + + return secretPath; + + error: + unlink(secretPath); + VIR_FREE(secretPath); + goto cleanup; +} + + +int +virStorageBackendCreateQemuImg(virConnectPtr conn, + virStoragePoolObjPtr pool, + virStorageVolDefPtr vol, + virStorageVolDefPtr inputvol, + unsigned int flags) +{ + int ret = -1; + char *create_tool; + int imgformat; + virCommandPtr cmd; + char *secretPath = NULL; + + virCheckFlags(VIR_STORAGE_VOL_CREATE_PREALLOC_METADATA, -1); + + create_tool = virFindFileInPath("qemu-img"); + if (!create_tool) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("creation of non-raw file images is " + "not supported without qemu-img.")); + return -1; + } + + imgformat = virStorageBackendQEMUImgBackingFormat(create_tool); + if (imgformat < 0) + goto cleanup; + + if (vol->target.format == VIR_STORAGE_FILE_RAW && + vol->target.encryption && + vol->target.encryption->format == VIR_STORAGE_ENCRYPTION_FORMAT_LUKS) { + if (!(secretPath = + virStorageBackendCreateQemuImgSecretPath(conn, pool, vol))) + goto cleanup; + } + + cmd = virStorageBackendCreateQemuImgCmdFromVol(conn, pool, vol, inputvol, + flags, create_tool, + imgformat, secretPath); + if (!cmd) + goto cleanup; + + ret = virStorageBackendCreateExecCommand(pool, vol, cmd); + + virCommandFree(cmd); + cleanup: + if (secretPath) { + unlink(secretPath); + VIR_FREE(secretPath); + } + VIR_FREE(create_tool); + return ret; +} + +virStorageBackendBuildVolFrom +virStorageBackendGetBuildVolFromFunction(virStorageVolDefPtr vol, + virStorageVolDefPtr inputvol) +{ + if (!inputvol) + return NULL; + + /* If either volume is a non-raw file vol, or uses encryption, + * we need to use an external tool for converting + */ + if ((vol->type == VIR_STORAGE_VOL_FILE && + (vol->target.format != VIR_STORAGE_FILE_RAW || + vol->target.encryption != NULL)) || + (inputvol->type == VIR_STORAGE_VOL_FILE && + (inputvol->target.format != VIR_STORAGE_FILE_RAW || + inputvol->target.encryption != NULL))) { + return virStorageBackendCreateQemuImg; + } + + if (vol->type == VIR_STORAGE_VOL_PLOOP) + return virStorageBackendCreatePloop; + if (vol->type == VIR_STORAGE_VOL_BLOCK) + return virStorageBackendCreateBlockFrom; + else + return virStorageBackendCreateRaw; +} + + +struct diskType { + int part_table_type; + unsigned short offset; + unsigned short length; + unsigned long long magic; +}; + + +static struct diskType const disk_types[] = { + { VIR_STORAGE_POOL_DISK_LVM2, 0x218, 8, 0x31303020324D564CULL }, + { VIR_STORAGE_POOL_DISK_GPT, 0x200, 8, 0x5452415020494645ULL }, + { VIR_STORAGE_POOL_DISK_DVH, 0x0, 4, 0x41A9E50BULL }, + { VIR_STORAGE_POOL_DISK_MAC, 0x0, 2, 0x5245ULL }, + { VIR_STORAGE_POOL_DISK_BSD, 0x40, 4, 0x82564557ULL }, + { VIR_STORAGE_POOL_DISK_SUN, 0x1fc, 2, 0xBEDAULL }, + /* + * NOTE: pc98 is funky; the actual signature is 0x55AA (just like dos), so + * we can't use that. At the moment I'm relying on the "dummy" IPL + * bootloader data that comes from parted. Luckily, the chances of running + * into a pc98 machine running libvirt are approximately nil. + */ + /*{ 0x1fe, 2, 0xAA55UL },*/ + { VIR_STORAGE_POOL_DISK_PC98, 0x0, 8, 0x314C5049000000CBULL }, + /* + * NOTE: the order is important here; some other disk types (like GPT and + * and PC98) also have 0x55AA at this offset. For that reason, the DOS + * one must be the last one. + */ + { VIR_STORAGE_POOL_DISK_DOS, 0x1fe, 2, 0xAA55ULL }, + { -1, 0x0, 0, 0x0ULL }, +}; + + +/* + * virStorageBackendDetectBlockVolFormatFD + * @target: target definition ptr of volume to update + * @fd: fd of storage volume to update, + * @readflags: VolReadErrorMode flags to handle read error after open + * is successful, but read is not. + * + * Returns 0 for success, -1 on a legitimate error condition, -2 if + * the read error is desired to be ignored (along with appropriate + * VIR_WARN of the issue). + */ +static int +virStorageBackendDetectBlockVolFormatFD(virStorageSourcePtr target, + int fd, + unsigned int readflags) +{ + size_t i; + off_t start; + unsigned char buffer[1024]; + ssize_t bytes; + + /* make sure to set the target format "unknown" to begin with */ + target->format = VIR_STORAGE_POOL_DISK_UNKNOWN; + + start = lseek(fd, 0, SEEK_SET); + if (start < 0) { + virReportSystemError(errno, + _("cannot seek to beginning of file '%s'"), + target->path); + return -1; + } + bytes = saferead(fd, buffer, sizeof(buffer)); + if (bytes < 0) { + if (readflags & VIR_STORAGE_VOL_READ_NOERROR) { + VIR_WARN("ignoring failed saferead of file '%s'", + target->path); + return -2; + } else { + virReportSystemError(errno, + _("cannot read beginning of file '%s'"), + target->path); + return -1; + } + } + + for (i = 0; disk_types[i].part_table_type != -1; i++) { + if (disk_types[i].offset + disk_types[i].length > bytes) + continue; + if (memcmp(buffer+disk_types[i].offset, &disk_types[i].magic, + disk_types[i].length) == 0) { + target->format = disk_types[i].part_table_type; + break; + } + } + + if (target->format == VIR_STORAGE_POOL_DISK_UNKNOWN) + VIR_DEBUG("cannot determine the target format for '%s'", + target->path); + + return 0; +} + + +/* + * Allows caller to silently ignore files with improper mode + * + * Returns -1 on error. If VIR_STORAGE_VOL_OPEN_NOERROR is passed, we + * return -2 if file mode is unexpected or the volume is a dangling + * symbolic link. + */ +int +virStorageBackendVolOpen(const char *path, struct stat *sb, + unsigned int flags) +{ + int fd, mode = 0; + char *base = last_component(path); + bool noerror = (flags & VIR_STORAGE_VOL_OPEN_NOERROR); + + if (lstat(path, sb) < 0) { + if (errno == ENOENT) { + if (noerror) { + VIR_WARN("ignoring missing file '%s'", path); + return -2; + } + virReportError(VIR_ERR_NO_STORAGE_VOL, + _("no storage vol with matching path '%s'"), + path); + return -1; + } + virReportSystemError(errno, + _("cannot stat file '%s'"), + path); + return -1; + } + + if (S_ISFIFO(sb->st_mode)) { + if (noerror) { + VIR_WARN("ignoring FIFO '%s'", path); + return -2; + } + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Volume path '%s' is a FIFO"), path); + return -1; + } else if (S_ISSOCK(sb->st_mode)) { + if (noerror) { + VIR_WARN("ignoring socket '%s'", path); + return -2; + } + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Volume path '%s' is a socket"), path); + return -1; + } + + /* O_NONBLOCK should only matter during open() for fifos and + * sockets, which we already filtered; but using it prevents a + * TOCTTOU race. However, later on we will want to read() the + * header from this fd, and virFileRead* routines require a + * blocking fd, so fix it up after verifying we avoided a race. + * + * Use of virFileOpenAs allows this path to open a file using + * the uid and gid as it was created in order to open. Since this + * path is not using O_CREAT or O_TMPFILE, mode is meaningless. + * Opening under user/group is especially important in an NFS + * root-squash environment. If the target path isn't on shared + * file system, the open will fail in the OPEN_FORK path. + */ + if ((fd = virFileOpenAs(path, O_RDONLY|O_NONBLOCK|O_NOCTTY, + 0, sb->st_uid, sb->st_gid, + VIR_FILE_OPEN_NOFORK|VIR_FILE_OPEN_FORK)) < 0) { + if ((errno == ENOENT || errno == ELOOP) && + S_ISLNK(sb->st_mode) && noerror) { + VIR_WARN("ignoring dangling symlink '%s'", path); + return -2; + } + if (errno == ENOENT && noerror) { + VIR_WARN("ignoring missing file '%s'", path); + return -2; + } + if (errno == ENXIO && noerror) { + VIR_WARN("ignoring missing fifo '%s'", path); + return -2; + } + if ((errno == EACCES || errno == EPERM) && noerror) { + VIR_WARN("ignoring permission error for '%s'", path); + return -2; + } + + virReportSystemError(errno, _("cannot open volume '%s'"), path); + return -1; + } + + if (fstat(fd, sb) < 0) { + virReportSystemError(errno, _("cannot stat file '%s'"), path); + VIR_FORCE_CLOSE(fd); + return -1; + } + + if (S_ISREG(sb->st_mode)) { + mode = VIR_STORAGE_VOL_OPEN_REG; + } else if (S_ISCHR(sb->st_mode)) { + mode = VIR_STORAGE_VOL_OPEN_CHAR; + } else if (S_ISBLK(sb->st_mode)) { + mode = VIR_STORAGE_VOL_OPEN_BLOCK; + } else if (S_ISDIR(sb->st_mode)) { + mode = VIR_STORAGE_VOL_OPEN_DIR; + + if (STREQ(base, ".") || + STREQ(base, "..")) { + VIR_FORCE_CLOSE(fd); + if (noerror) { + VIR_INFO("Skipping special dir '%s'", base); + return -2; + } + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Cannot use volume path '%s'"), path); + return -1; + } + } else { + VIR_FORCE_CLOSE(fd); + if (noerror) { + VIR_WARN("ignoring unexpected type for file '%s'", path); + return -2; + } + virReportError(VIR_ERR_INTERNAL_ERROR, + _("unexpected type for file '%s'"), path); + return -1; + } + + if (virSetBlocking(fd, true) < 0) { + VIR_FORCE_CLOSE(fd); + virReportSystemError(errno, _("unable to set blocking mode for '%s'"), + path); + return -1; + } + + if (!(mode & flags)) { + VIR_FORCE_CLOSE(fd); + if (noerror) { + VIR_INFO("Skipping volume '%s'", path); + return -2; + } + + virReportError(VIR_ERR_INTERNAL_ERROR, + _("unexpected storage mode for '%s'"), path); + return -1; + } + + return fd; +} + +/* virStorageIsPloop function checks whether given directory is ploop volume's + * directory. + */ +bool +virStorageBackendIsPloopDir(char *path) +{ + bool ret = false; + char *root = NULL; + char *desc = NULL; + if (virAsprintf(&root, "%s/root.hds", path) < 0) + return ret; + if (!virFileExists(root)) + goto cleanup; + if (virAsprintf(&desc, "%s/DiskDescriptor.xml", path) < 0) + goto cleanup; + if (!virFileExists(desc)) + goto cleanup; + + ret = true; + cleanup: + VIR_FREE(root); + VIR_FREE(desc); + return ret; +} + +/* In case of ploop volumes, path to volume is the path to the ploop + * directory. To get information about allocation, header information + * and etc. we need to perform virStorageBackendVolOpen and + * virStorageBackendUpdateVolTargetFd once again. + */ +int +virStorageBackendRedoPloopUpdate(virStorageSourcePtr target, struct stat *sb, + int *fd, unsigned int flags) +{ + char *path = NULL; + int ret = -1; + + if (virAsprintf(&path, "%s/root.hds", target->path) < 0) + return -1; + VIR_FORCE_CLOSE(*fd); + if ((*fd = virStorageBackendVolOpen(path, sb, flags)) < 0) + goto cleanup; + ret = virStorageBackendUpdateVolTargetInfoFD(target, *fd, sb); + + cleanup: + + VIR_FREE(path); + return ret; +} + +/* + * virStorageBackendUpdateVolTargetInfo + * @voltype: Volume type + * @target: target definition ptr of volume to update + * @withBlockVolFormat: true if caller determined a block file + * @openflags: various VolOpenCheckMode flags to handle errors on open + * @readflags: VolReadErrorMode flags to handle read error after open + * is successful, but read is not. + * + * Returns 0 for success, -1 on a legitimate error condition, and -2 + * if the openflags used VIR_STORAGE_VOL_OPEN_NOERROR and some sort of + * open error occurred. It is up to the caller to handle. A -2 may also + * be returned if the caller passed a readflagsflag. + */ +int +virStorageBackendUpdateVolTargetInfo(virStorageVolType voltype, + virStorageSourcePtr target, + bool withBlockVolFormat, + unsigned int openflags, + unsigned int readflags) +{ + int ret, fd = -1; + struct stat sb; + char *buf = NULL; + ssize_t len = VIR_STORAGE_MAX_HEADER; + + if ((ret = virStorageBackendVolOpen(target->path, &sb, openflags)) < 0) + goto cleanup; + fd = ret; + + if ((ret = virStorageBackendUpdateVolTargetInfoFD(target, fd, &sb)) < 0) + goto cleanup; + + if ((voltype == VIR_STORAGE_VOL_FILE || voltype == VIR_STORAGE_VOL_BLOCK) && + target->format != VIR_STORAGE_FILE_NONE) { + if (S_ISDIR(sb.st_mode)) { + if (virStorageBackendIsPloopDir(target->path)) { + if ((ret = virStorageBackendRedoPloopUpdate(target, &sb, &fd, + openflags)) < 0) + goto cleanup; + target->format = VIR_STORAGE_FILE_PLOOP; + } else { + ret = 0; + goto cleanup; + } + } + + if (lseek(fd, 0, SEEK_SET) == (off_t)-1) { + virReportSystemError(errno, _("cannot seek to start of '%s'"), target->path); + ret = -1; + goto cleanup; + } + + if ((len = virFileReadHeaderFD(fd, len, &buf)) < 0) { + if (readflags & VIR_STORAGE_VOL_READ_NOERROR) { + VIR_WARN("ignoring failed header read for '%s'", + target->path); + ret = -2; + } else { + virReportSystemError(errno, + _("cannot read header '%s'"), + target->path); + ret = -1; + } + goto cleanup; + } + + if (virStorageSourceUpdateCapacity(target, buf, len, false) < 0) { + ret = -1; + goto cleanup; + } + } + + if (withBlockVolFormat) { + if ((ret = virStorageBackendDetectBlockVolFormatFD(target, fd, + readflags)) < 0) + goto cleanup; + } + + cleanup: + VIR_FORCE_CLOSE(fd); + VIR_FREE(buf); + return ret; +} + +/* + * virStorageBackendUpdateVolInfo + * @vol: Pointer to a volume storage definition + * @withBlockVolFormat: true if the caller determined a block file + * @openflags: various VolOpenCheckMode flags to handle errors on open + * @readflags: various VolReadErrorMode flags to handle errors on read + * + * Returns 0 for success, -1 on a legitimate error condition, and -2 + * if the openflags used VIR_STORAGE_VOL_OPEN_NOERROR and some sort of + * open error occurred. It is up to the caller to handle. + */ +int +virStorageBackendUpdateVolInfo(virStorageVolDefPtr vol, + bool withBlockVolFormat, + unsigned int openflags, + unsigned int readflags) +{ + int ret; + + if ((ret = virStorageBackendUpdateVolTargetInfo(vol->type, + &vol->target, + withBlockVolFormat, + openflags, readflags)) < 0) + return ret; + + if (vol->target.backingStore && + (ret = virStorageBackendUpdateVolTargetInfo(VIR_STORAGE_VOL_FILE, + vol->target.backingStore, + withBlockVolFormat, + VIR_STORAGE_VOL_OPEN_DEFAULT | + VIR_STORAGE_VOL_OPEN_NOERROR, + readflags) < 0)) + return ret; + + return 0; +} + +/* + * virStorageBackendUpdateVolTargetInfoFD: + * @target: target definition ptr of volume to update + * @fd: fd of storage volume to update, via virStorageBackendOpenVol*, or -1 + * @sb: details about file (must match @fd, if that is provided) + * + * Returns 0 for success, -1 on a legitimate error condition. + */ +int +virStorageBackendUpdateVolTargetInfoFD(virStorageSourcePtr target, + int fd, + struct stat *sb) +{ +#if WITH_SELINUX + security_context_t filecon = NULL; +#endif + + if (virStorageSourceUpdateBackingSizes(target, fd, sb) < 0) + return -1; + + if (!target->perms && VIR_ALLOC(target->perms) < 0) + return -1; + target->perms->mode = sb->st_mode & S_IRWXUGO; + target->perms->uid = sb->st_uid; + target->perms->gid = sb->st_gid; + + if (!target->timestamps && VIR_ALLOC(target->timestamps) < 0) + return -1; + target->timestamps->atime = get_stat_atime(sb); + target->timestamps->btime = get_stat_birthtime(sb); + target->timestamps->ctime = get_stat_ctime(sb); + target->timestamps->mtime = get_stat_mtime(sb); + + VIR_FREE(target->perms->label); + +#if WITH_SELINUX + /* XXX: make this a security driver call */ + if (fd >= 0) { + if (fgetfilecon_raw(fd, &filecon) == -1) { + if (errno != ENODATA && errno != ENOTSUP) { + virReportSystemError(errno, + _("cannot get file context of '%s'"), + target->path); + return -1; + } + } else { + if (VIR_STRDUP(target->perms->label, filecon) < 0) { + freecon(filecon); + return -1; + } + freecon(filecon); + } + } +#endif + + return 0; +} + +bool +virStorageBackendPoolPathIsStable(const char *path) +{ + if (path == NULL || STREQ(path, "/dev") || STREQ(path, "/dev/")) + return false; + + if (!STRPREFIX(path, "/dev/")) + return false; + + return true; +} + +/* + * Given a volume path directly in /dev/XXX, iterate over the + * entries in the directory pool->def->target.path and find the + * first symlink pointing to the volume path. + * + * If, the target.path is /dev/, then return the original volume + * path. + * + * If no symlink is found, then return the original volume path + * + * Typically target.path is one of the /dev/disk/by-XXX dirs + * with stable paths. + * + * If 'loop' is true, we use a timeout loop to give dynamic paths + * a change to appear. + */ +char * +virStorageBackendStablePath(virStoragePoolObjPtr pool, + const char *devpath, + bool loop) +{ + DIR *dh; + struct dirent *dent; + char *stablepath; + int opentries = 0; + int retry = 0; + int direrr; + + /* Logical pools are under /dev but already have stable paths */ + if (pool->def->type == VIR_STORAGE_POOL_LOGICAL || + !virStorageBackendPoolPathIsStable(pool->def->target.path)) + goto ret_strdup; + + /* We loop here because /dev/disk/by-{id,path} may not have existed + * before we started this operation, so we have to give it some time to + * get created. + */ + reopen: + if (virDirOpenQuiet(&dh, pool->def->target.path) < 0) { + opentries++; + if (loop && errno == ENOENT && opentries < 50) { + usleep(100 * 1000); + goto reopen; + } + virReportSystemError(errno, + _("cannot read dir '%s'"), + pool->def->target.path); + return NULL; + } + + /* The pool is pointing somewhere like /dev/disk/by-path + * or /dev/disk/by-id, so we need to check all symlinks in + * the target directory and figure out which one points + * to this device node. + * + * And it might need some time till the stable path shows + * up, so add timeout to retry here. Ignore readdir failures, + * since we have a fallback. + */ + retry: + while ((direrr = virDirRead(dh, &dent, NULL)) > 0) { + if (virAsprintf(&stablepath, "%s/%s", + pool->def->target.path, + dent->d_name) == -1) { + VIR_DIR_CLOSE(dh); + return NULL; + } + + if (virFileLinkPointsTo(stablepath, devpath)) { + VIR_DIR_CLOSE(dh); + return stablepath; + } + + VIR_FREE(stablepath); + } + + if (!direrr && loop && ++retry < 100) { + usleep(100 * 1000); + goto retry; + } + + VIR_DIR_CLOSE(dh); + + ret_strdup: + /* Couldn't find any matching stable link so give back + * the original non-stable dev path + */ + + ignore_value(VIR_STRDUP(stablepath, devpath)); + + return stablepath; +} + +/* + * Check whether the ploop image has snapshots. + * return: -1 - failed to check + * 0 - no snapshots + * 1 - at least one snapshot + */ +static int +virStorageBackendPloopHasSnapshots(char *path) +{ + virCommandPtr cmd = NULL; + char *output = NULL; + char *snap_tool = NULL; + int ret = -1; + + snap_tool = virFindFileInPath("ploop"); + if (!snap_tool) { + virReportError(VIR_ERR_INTERNAL_ERROR, + "%s", _("unable to find ploop, please install " + "ploop tools")); + return ret; + } + + cmd = virCommandNewArgList(snap_tool, "snapshot-list", NULL); + virCommandAddArgFormat(cmd, "%s/DiskDescriptor.xml", path); + virCommandSetOutputBuffer(cmd, &output); + + if ((ret = virCommandRun(cmd, NULL)) < 0) + goto cleanup; + + if (!strstr(output, "root.hds.")) { + ret = 1; + goto cleanup; + } + ret = 0; + + cleanup: + VIR_FREE(output); + virCommandFree(cmd); + return ret; +} + +int +virStorageBackendVolUploadLocal(virConnectPtr conn ATTRIBUTE_UNUSED, + virStoragePoolObjPtr pool ATTRIBUTE_UNUSED, + virStorageVolDefPtr vol, + virStreamPtr stream, + unsigned long long offset, + unsigned long long len, + unsigned int flags) +{ + char *path = NULL; + char *target_path = vol->target.path; + int ret = -1; + int has_snap = 0; + + virCheckFlags(0, -1); + /* if volume has target format VIR_STORAGE_FILE_PLOOP + * we need to restore DiskDescriptor.xml, according to + * new contents of volume. This operation will be perfomed + * when volUpload is fully finished. */ + if (vol->target.format == VIR_STORAGE_FILE_PLOOP) { + /* Fail if the volume contains snapshots or we failed to check it.*/ + has_snap = virStorageBackendPloopHasSnapshots(vol->target.path); + if (has_snap < 0) { + goto cleanup; + } else if (!has_snap) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("can't upload volume, all existing snapshots" + " will be lost")); + goto cleanup; + } + + if (virAsprintf(&path, "%s/root.hds", vol->target.path) < 0) + return -1; + target_path = path; + } + + /* Not using O_CREAT because the file is required to already exist at + * this point */ + ret = virFDStreamOpenBlockDevice(stream, target_path, + offset, len, O_WRONLY); + + cleanup: + VIR_FREE(path); + return ret; +} + +int +virStorageBackendVolDownloadLocal(virConnectPtr conn ATTRIBUTE_UNUSED, + virStoragePoolObjPtr pool ATTRIBUTE_UNUSED, + virStorageVolDefPtr vol, + virStreamPtr stream, + unsigned long long offset, + unsigned long long len, + unsigned int flags) +{ + char *path = NULL; + char *target_path = vol->target.path; + int ret = -1; + int has_snap = 0; + + virCheckFlags(0, -1); + if (vol->target.format == VIR_STORAGE_FILE_PLOOP) { + has_snap = virStorageBackendPloopHasSnapshots(vol->target.path); + if (has_snap < 0) { + goto cleanup; + } else if (!has_snap) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("can't download volume, all existing snapshots" + " will be lost")); + goto cleanup; + } + if (virAsprintf(&path, "%s/root.hds", vol->target.path) < 0) + goto cleanup; + target_path = path; + } + + ret = virFDStreamOpenBlockDevice(stream, target_path, + offset, len, O_RDONLY); + + cleanup: + VIR_FREE(path); + return ret; +} + + +/* If the volume we're wiping is already a sparse file, we simply + * truncate and extend it to its original size, filling it with + * zeroes. This behavior is guaranteed by POSIX: + * + * http://www.opengroup.org/onlinepubs/9699919799/functions/ftruncate.html + * + * If fildes refers to a regular file, the ftruncate() function shall + * cause the size of the file to be truncated to length. If the size + * of the file previously exceeded length, the extra data shall no + * longer be available to reads on the file. If the file previously + * was smaller than this size, ftruncate() shall increase the size of + * the file. If the file size is increased, the extended area shall + * appear as if it were zero-filled. + */ +static int +virStorageBackendVolZeroSparseFileLocal(const char *path, + off_t size, + int fd) +{ + if (ftruncate(fd, 0) < 0) { + virReportSystemError(errno, + _("Failed to truncate volume with " + "path '%s' to 0 bytes"), + path); + return -1; + } + + if (ftruncate(fd, size) < 0) { + virReportSystemError(errno, + _("Failed to truncate volume with " + "path '%s' to %ju bytes"), + path, (uintmax_t)size); + return -1; + } + + return 0; +} + + +static int +virStorageBackendWipeLocal(const char *path, + int fd, + unsigned long long wipe_len, + size_t writebuf_length) +{ + int ret = -1, written = 0; + unsigned long long remaining = 0; + size_t write_size = 0; + char *writebuf = NULL; + + VIR_DEBUG("wiping start: 0 len: %llu", wipe_len); + + if (VIR_ALLOC_N(writebuf, writebuf_length) < 0) + goto cleanup; + + if (lseek(fd, 0, SEEK_SET) < 0) { + virReportSystemError(errno, + _("Failed to seek to the start in volume " + "with path '%s'"), + path); + goto cleanup; + } + + remaining = wipe_len; + while (remaining > 0) { + + write_size = (writebuf_length < remaining) ? writebuf_length : remaining; + written = safewrite(fd, writebuf, write_size); + if (written < 0) { + virReportSystemError(errno, + _("Failed to write %zu bytes to " + "storage volume with path '%s'"), + write_size, path); + + goto cleanup; + } + + remaining -= written; + } + + if (fdatasync(fd) < 0) { + virReportSystemError(errno, + _("cannot sync data to volume with path '%s'"), + path); + goto cleanup; + } + + VIR_DEBUG("Wrote %llu bytes to volume with path '%s'", wipe_len, path); + + ret = 0; + + cleanup: + VIR_FREE(writebuf); + return ret; +} + + +static int +virStorageBackendVolWipeLocalFile(const char *path, + unsigned int algorithm, + unsigned long long allocation) +{ + int ret = -1, fd = -1; + const char *alg_char = NULL; + struct stat st; + virCommandPtr cmd = NULL; + + fd = open(path, O_RDWR); + if (fd == -1) { + virReportSystemError(errno, + _("Failed to open storage volume with path '%s'"), + path); + goto cleanup; + } + + if (fstat(fd, &st) == -1) { + virReportSystemError(errno, + _("Failed to stat storage volume with path '%s'"), + path); + goto cleanup; + } + + switch ((virStorageVolWipeAlgorithm) algorithm) { + case VIR_STORAGE_VOL_WIPE_ALG_ZERO: + alg_char = "zero"; + break; + case VIR_STORAGE_VOL_WIPE_ALG_NNSA: + alg_char = "nnsa"; + break; + case VIR_STORAGE_VOL_WIPE_ALG_DOD: + alg_char = "dod"; + break; + case VIR_STORAGE_VOL_WIPE_ALG_BSI: + alg_char = "bsi"; + break; + case VIR_STORAGE_VOL_WIPE_ALG_GUTMANN: + alg_char = "gutmann"; + break; + case VIR_STORAGE_VOL_WIPE_ALG_SCHNEIER: + alg_char = "schneier"; + break; + case VIR_STORAGE_VOL_WIPE_ALG_PFITZNER7: + alg_char = "pfitzner7"; + break; + case VIR_STORAGE_VOL_WIPE_ALG_PFITZNER33: + alg_char = "pfitzner33"; + break; + case VIR_STORAGE_VOL_WIPE_ALG_RANDOM: + alg_char = "random"; + break; + case VIR_STORAGE_VOL_WIPE_ALG_TRIM: + virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", + _("'trim' algorithm not supported")); + goto cleanup; + case VIR_STORAGE_VOL_WIPE_ALG_LAST: + virReportError(VIR_ERR_INVALID_ARG, + _("unsupported algorithm %d"), + algorithm); + goto cleanup; + } + + VIR_DEBUG("Wiping file '%s' with algorithm '%s'", path, alg_char); + + if (algorithm != VIR_STORAGE_VOL_WIPE_ALG_ZERO) { + cmd = virCommandNew(SCRUB); + virCommandAddArgList(cmd, "-f", "-p", alg_char, path, NULL); + + if (virCommandRun(cmd, NULL) < 0) + goto cleanup; + + ret = 0; + } else { + if (S_ISREG(st.st_mode) && st.st_blocks < (st.st_size / DEV_BSIZE)) { + ret = virStorageBackendVolZeroSparseFileLocal(path, st.st_size, fd); + } else { + ret = virStorageBackendWipeLocal(path, + fd, + allocation, + st.st_blksize); + } + if (ret < 0) + goto cleanup; + } + + cleanup: + virCommandFree(cmd); + VIR_FORCE_CLOSE(fd); + return ret; +} + + +static int +virStorageBackendVolWipePloop(virStorageVolDefPtr vol, + unsigned int algorithm) +{ + virCommandPtr cmd = NULL; + char *target_path = NULL; + char *disk_desc = NULL; + char *create_tool = NULL; + + int ret = -1; + + create_tool = virFindFileInPath("ploop"); + if (!create_tool) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("unable to find ploop tools, please install them")); + return -1; + } + + if (virAsprintf(&target_path, "%s/root.hds", vol->target.path) < 0) + goto cleanup; + + if (virAsprintf(&disk_desc, "%s/DiskDescriptor.xml", vol->target.path) < 0) + goto cleanup; + + if (virStorageBackendVolWipeLocalFile(target_path, + algorithm, + vol->target.allocation) < 0) + goto cleanup; + + if (virFileRemove(disk_desc, 0, 0) < 0) { + virReportError(errno, _("Failed to delete DiskDescriptor.xml of volume '%s'"), + vol->target.path); + goto cleanup; + } + if (virFileRemove(target_path, 0, 0) < 0) { + virReportError(errno, _("failed to delete root.hds of volume '%s'"), + vol->target.path); + goto cleanup; + } + + cmd = virCommandNewArgList(create_tool, "init", "-s", NULL); + + virCommandAddArgFormat(cmd, "%lluM", VIR_DIV_UP(vol->target.capacity, + (1024 * 1024))); + virCommandAddArgList(cmd, "-t", "ext4", NULL); + virCommandAddArg(cmd, target_path); + ret = virCommandRun(cmd, NULL); + + cleanup: + VIR_FREE(disk_desc); + VIR_FREE(target_path); + VIR_FREE(create_tool); + virCommandFree(cmd); + return ret; +} + + +int +virStorageBackendVolWipeLocal(virConnectPtr conn ATTRIBUTE_UNUSED, + virStoragePoolObjPtr pool ATTRIBUTE_UNUSED, + virStorageVolDefPtr vol, + unsigned int algorithm, + unsigned int flags) +{ + int ret = -1; + + virCheckFlags(0, -1); + + VIR_DEBUG("Wiping volume with path '%s' and algorithm %u", + vol->target.path, algorithm); + + if (vol->target.format == VIR_STORAGE_FILE_PLOOP) { + ret = virStorageBackendVolWipePloop(vol, algorithm); + } else { + ret = virStorageBackendVolWipeLocalFile(vol->target.path, + algorithm, + vol->target.allocation); + } + + return ret; +} + + +/** + * virStorageBackendFindGlusterPoolSources: + * @host: host to detect volumes on + * @pooltype: src->format is set to this value + * @list: list of storage pool sources to be filled + * @report: report error if the 'gluster' cli tool is missing + * + * Looks up gluster volumes on @host and fills them to @list. + * + * Returns number of volumes on the host on success, or -1 on error. + */ +int +virStorageBackendFindGlusterPoolSources(const char *host, + int pooltype, + virStoragePoolSourceListPtr list, + bool report) +{ + char *glusterpath = NULL; + char *outbuf = NULL; + virCommandPtr cmd = NULL; + xmlDocPtr doc = NULL; + xmlXPathContextPtr ctxt = NULL; + xmlNodePtr *nodes = NULL; + virStoragePoolSource *src = NULL; + size_t i; + int nnodes; + int rc; + + int ret = -1; + + if (!(glusterpath = virFindFileInPath(GLUSTER_CLI))) { + if (report) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("'gluster' command line tool not found")); + return -1; + } else { + return 0; + } + } + + cmd = virCommandNewArgList(glusterpath, + "--xml", + "--log-file=/dev/null", + "volume", "info", "all", NULL); + + virCommandAddArgFormat(cmd, "--remote-host=%s", host); + virCommandSetOutputBuffer(cmd, &outbuf); + + if (virCommandRun(cmd, &rc) < 0) + goto cleanup; + + if (rc != 0) { + ret = 0; + goto cleanup; + } + + if (!(doc = virXMLParseStringCtxt(outbuf, _("(gluster_cli_output)"), + &ctxt))) + goto cleanup; + + if ((nnodes = virXPathNodeSet("//volumes/volume", ctxt, &nodes)) < 0) + goto cleanup; + + for (i = 0; i < nnodes; i++) { + ctxt->node = nodes[i]; + + if (!(src = virStoragePoolSourceListNewSource(list))) + goto cleanup; + + if (!(src->dir = virXPathString("string(//name)", ctxt))) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("failed to extract gluster volume name")); + goto cleanup; + } + + if (VIR_ALLOC_N(src->hosts, 1) < 0) + goto cleanup; + src->nhost = 1; + + if (VIR_STRDUP(src->hosts[0].name, host) < 0) + goto cleanup; + + src->format = pooltype; + } + + ret = nnodes; + + cleanup: + VIR_FREE(nodes); + xmlXPathFreeContext(ctxt); + xmlFreeDoc(doc); + VIR_FREE(outbuf); + virCommandFree(cmd); + VIR_FREE(glusterpath); + return ret; +} + + +#if WITH_BLKID + +typedef enum { + VIR_STORAGE_BLKID_PROBE_ERROR = -1, + VIR_STORAGE_BLKID_PROBE_UNDEFINED, /* Nothing found */ + VIR_STORAGE_BLKID_PROBE_UNKNOWN, /* Don't know libvirt fs/part type */ + VIR_STORAGE_BLKID_PROBE_MATCH, /* Matches the on disk format */ + VIR_STORAGE_BLKID_PROBE_DIFFERENT, /* Format doesn't match on disk format */ +} virStorageBackendBLKIDProbeResult; + +/* + * Utility function to probe for a file system on the device using the + * blkid "superblock" (e.g. default) APIs. + * + * NB: In general this helper will handle the virStoragePoolFormatFileSystem + * format types; however, if called from the Disk path, the initial fstype + * check will fail forcing the usage of the ProbePart helper. + * + * Returns virStorageBackendBLKIDProbeResult enum + */ +static virStorageBackendBLKIDProbeResult +virStorageBackendBLKIDFindFS(blkid_probe probe, + const char *device, + const char *format) +{ + const char *fstype = NULL; + + /* Make sure we're doing a superblock probe from the start */ + blkid_probe_enable_superblocks(probe, true); + blkid_probe_reset_superblocks_filter(probe); + + if (blkid_do_probe(probe) != 0) { + VIR_INFO("No filesystem found on device '%s'", device); + return VIR_STORAGE_BLKID_PROBE_UNDEFINED; + } + + if (blkid_probe_lookup_value(probe, "TYPE", &fstype, NULL) == 0) { + if (STREQ(fstype, format)) + return VIR_STORAGE_BLKID_PROBE_MATCH; + + return VIR_STORAGE_BLKID_PROBE_DIFFERENT; + } + + if (blkid_known_fstype(format) == 0) + return VIR_STORAGE_BLKID_PROBE_UNKNOWN; + + return VIR_STORAGE_BLKID_PROBE_ERROR; +} + + +/* + * Utility function to probe for a partition on the device using the + * blkid "partitions" APIs. + * + * NB: In general, this API will be validating the virStoragePoolFormatDisk + * format types. + * + * Returns virStorageBackendBLKIDProbeResult enum + */ +static virStorageBackendBLKIDProbeResult +virStorageBackendBLKIDFindPart(blkid_probe probe, + const char *device, + const char *format) +{ + const char *pttype = NULL; + + /* A blkid_known_pttype on "dvh" and "pc98" returns a failure; + * however, the blkid_do_probe for "dvh" returns "sgi" and + * for "pc98" it returns "dos". So since those will cause problems + * with startup comparison, let's just treat them as UNKNOWN causing + * the caller to fallback to using PARTED */ + if (STREQ(format, "dvh") || STREQ(format, "pc98")) + return VIR_STORAGE_BLKID_PROBE_UNKNOWN; + + /* Make sure we're doing a partitions probe from the start */ + blkid_probe_enable_partitions(probe, true); + blkid_probe_reset_partitions_filter(probe); + + if (blkid_do_probe(probe) != 0) { + VIR_INFO("No partition found on device '%s'", device); + return VIR_STORAGE_BLKID_PROBE_UNDEFINED; + } + + if (blkid_probe_lookup_value(probe, "PTTYPE", &pttype, NULL) == 0) { + if (STREQ(pttype, format)) + return VIR_STORAGE_BLKID_PROBE_MATCH; + + return VIR_STORAGE_BLKID_PROBE_DIFFERENT; + } + + if (blkid_known_pttype(format) == 0) + return VIR_STORAGE_BLKID_PROBE_UNKNOWN; + + return VIR_STORAGE_BLKID_PROBE_ERROR; +} + + +/* + * @device: Path to device + * @format: Desired format + * @writelabel: True if desire to write the label + * + * Use the blkid_ APIs in order to get details regarding whether a file + * system or partition exists on the disk already. + * + * Returns: + * -2: Force usage of PARTED for unknown types + * -1: An error was encountered, with error message set + * 0: No file system found + */ +static int +virStorageBackendBLKIDFindEmpty(const char *device, + const char *format, + bool writelabel) +{ + + int ret = -1; + int rc; + blkid_probe probe = NULL; + + VIR_DEBUG("Probe for existing filesystem/partition format %s on device %s", + format, device); + + if (!(probe = blkid_new_probe_from_filename(device))) { + virReportError(VIR_ERR_STORAGE_PROBE_FAILED, + _("Failed to create filesystem probe for device %s"), + device); + return -1; + } + + /* Look for something on FS, if it either doesn't recognize the + * format type as a valid FS format type or it doesn't find a valid + * format type on the device, then perform the same check using + * partition probing. */ + rc = virStorageBackendBLKIDFindFS(probe, device, format); + if (rc == VIR_STORAGE_BLKID_PROBE_UNDEFINED || + rc == VIR_STORAGE_BLKID_PROBE_UNKNOWN) { + + rc = virStorageBackendBLKIDFindPart(probe, device, format); + } + + switch (rc) { + case VIR_STORAGE_BLKID_PROBE_UNDEFINED: + if (writelabel) + ret = 0; + else + virReportError(VIR_ERR_STORAGE_PROBE_FAILED, + _("Device '%s' is unrecognized, requires build"), + device); + break; + + case VIR_STORAGE_BLKID_PROBE_ERROR: + virReportError(VIR_ERR_STORAGE_PROBE_FAILED, + _("Failed to probe for format type '%s'"), format); + break; + + case VIR_STORAGE_BLKID_PROBE_UNKNOWN: + ret = -2; + break; + + case VIR_STORAGE_BLKID_PROBE_MATCH: + if (writelabel) + virReportError(VIR_ERR_STORAGE_POOL_BUILT, + _("Device '%s' already formatted using '%s'"), + device, format); + else + ret = 0; + break; + + case VIR_STORAGE_BLKID_PROBE_DIFFERENT: + if (writelabel) + virReportError(VIR_ERR_STORAGE_POOL_BUILT, + _("Format of device '%s' does not match the " + "expected format '%s', forced overwrite is " + "necessary"), + device, format); + else + virReportError(VIR_ERR_OPERATION_INVALID, + _("Format of device '%s' does not match the " + "expected format '%s'"), + device, format); + break; + } + + if (ret == 0 && blkid_do_probe(probe) != 1) { + virReportError(VIR_ERR_STORAGE_PROBE_FAILED, "%s", + _("Found additional probes to run, probing may " + "be incorrect")); + ret = -1; + } + + blkid_free_probe(probe); + + return ret; +} + +#else /* #if WITH_BLKID */ + +static int +virStorageBackendBLKIDFindEmpty(const char *device ATTRIBUTE_UNUSED, + const char *format ATTRIBUTE_UNUSED, + bool writelabel ATTRIBUTE_UNUSED) +{ + return -2; +} + +#endif /* #if WITH_BLKID */ + + +#if WITH_STORAGE_DISK + +typedef enum { + VIR_STORAGE_PARTED_ERROR = -1, + VIR_STORAGE_PARTED_MATCH, /* Valid label found and matches format */ + VIR_STORAGE_PARTED_DIFFERENT, /* Valid label found but not match format */ + VIR_STORAGE_PARTED_UNKNOWN, /* No or unrecognized label */ + VIR_STORAGE_PARTED_NOPTTYPE, /* Did not find the Partition Table type */ + VIR_STORAGE_PARTED_PTTYPE_UNK, /* Partition Table type unknown*/ +} virStorageBackendPARTEDResult; + +/** + * Check for a valid disk label (partition table) on device using + * the PARTED command + * + * returns virStorageBackendPARTEDResult + */ +static virStorageBackendPARTEDResult +virStorageBackendPARTEDFindLabel(const char *device, + const char *format) +{ + const char *const args[] = { + device, "print", "--script", NULL, + }; + virCommandPtr cmd = virCommandNew(PARTED); + char *output = NULL; + char *error = NULL; + char *start, *end; + int ret = VIR_STORAGE_PARTED_ERROR; + + virCommandAddArgSet(cmd, args); + virCommandAddEnvString(cmd, "LC_ALL=C"); + virCommandSetOutputBuffer(cmd, &output); + virCommandSetErrorBuffer(cmd, &error); + + /* if parted succeeds we have a valid partition table */ + ret = virCommandRun(cmd, NULL); + if (ret < 0) { + if ((output && strstr(output, "unrecognised disk label")) || + (error && strstr(error, "unrecognised disk label"))) { + ret = VIR_STORAGE_PARTED_UNKNOWN; + } + goto cleanup; + } + + /* Search for "Partition Table:" in the output. If not present, + * then we cannot validate the partition table type. + */ + if (!(start = strstr(output, "Partition Table: ")) || + !(end = strstr(start, "\n"))) { + VIR_DEBUG("Unable to find tag in output: %s", output); + ret = VIR_STORAGE_PARTED_NOPTTYPE; + goto cleanup; + } + start += strlen("Partition Table: "); + *end = '\0'; + + /* on disk it's "msdos", but we document/use "dos" so deal with it here */ + if (STREQ(start, "msdos")) + start += 2; + + /* Make sure we know about this type */ + if (virStoragePoolFormatDiskTypeFromString(start) < 0) { + ret = VIR_STORAGE_PARTED_PTTYPE_UNK; + goto cleanup; + } + + /* Does the on disk match what the pool desired? */ + if (STREQ(start, format)) + ret = VIR_STORAGE_PARTED_MATCH; + + ret = VIR_STORAGE_PARTED_DIFFERENT; + + cleanup: + virCommandFree(cmd); + VIR_FREE(output); + VIR_FREE(error); + return ret; +} + + +/** + * Determine whether the label on the disk is valid or in a known format + * for the purpose of rewriting the label during build or being able to + * start a pool on a device. + * + * When 'writelabel' is true, if we find a valid disk label on the device, + * then we shouldn't be attempting to write as the volume may contain + * data. Force the usage of the overwrite flag to the build command in + * order to be certain. When the disk label is unrecognized, then it + * should be safe to write. + * + * When 'writelabel' is false, only if we find a valid disk label on the + * device should we allow the start since for this path we won't be + * rewriting the label. + * + * Return: 0 if it's OK + * -1 if something's wrong + */ +static int +virStorageBackendPARTEDValidLabel(const char *device, + const char *format, + bool writelabel) +{ + int ret = -1; + virStorageBackendPARTEDResult check; + + check = virStorageBackendPARTEDFindLabel(device, format); + switch (check) { + case VIR_STORAGE_PARTED_ERROR: + virReportError(VIR_ERR_OPERATION_FAILED, "%s", + _("Error checking for disk label, failed to get " + "disk partition information")); + break; + + case VIR_STORAGE_PARTED_MATCH: + if (writelabel) + virReportError(VIR_ERR_OPERATION_INVALID, + _("Disk label already formatted using '%s'"), + format); + else + ret = 0; + break; + + case VIR_STORAGE_PARTED_DIFFERENT: + virReportError(VIR_ERR_OPERATION_FAILED, "%s", + _("Known, but different label format present, " + "requires build --overwrite")); + break; + + case VIR_STORAGE_PARTED_UNKNOWN: + if (writelabel) + ret = 0; + else + virReportError(VIR_ERR_OPERATION_FAILED, "%s", + _("Unrecognized disk label found, requires build")); + break; + + case VIR_STORAGE_PARTED_NOPTTYPE: + virReportError(VIR_ERR_OPERATION_FAILED, "%s", + _("Unable to determine Partition Type, " + "requires build --overwrite")); + break; + + case VIR_STORAGE_PARTED_PTTYPE_UNK: + virReportError(VIR_ERR_OPERATION_FAILED, "%s", + _("Unknown Partition Type, requires build --overwrite")); + break; + } + + return ret; +} + +#else + +static int +virStorageBackendPARTEDValidLabel(const char *device ATTRIBUTE_UNUSED, + const char *format ATTRIBUTE_UNUSED, + bool writelabel ATTRIBUTE_UNUSED) +{ + return -2; +} + + +#endif /* #if WITH_STORAGE_DISK */ + + +/* virStorageBackendDeviceIsEmpty: + * @devpath: Path to the device to check + * @format: Desired format string + * @writelabel: True if the caller expects to write the label + * + * Check if the @devpath has some sort of known file system using the + * BLKID API if available. + * + * Returns true if the probe deems the device has nothing valid on it + * or when we cannot check and we're not writing the label. + * + * Returns false if the probe finds something + */ +bool +virStorageBackendDeviceIsEmpty(const char *devpath, + const char *format, + bool writelabel) +{ + int ret; + + if ((ret = virStorageBackendBLKIDFindEmpty(devpath, format, + writelabel)) == -2) + ret = virStorageBackendPARTEDValidLabel(devpath, format, writelabel); + + if (ret == -2 && !writelabel) + ret = 0; + + if (ret == -2) { + virReportError(VIR_ERR_OPERATION_INVALID, + _("Unable to probe '%s' for existing data, " + "forced overwrite is necessary"), + devpath); + } + + return ret == 0; +} diff --git a/src/storage/storage_util.h b/src/storage/storage_util.h new file mode 100644 index 000000000..9f17822e7 --- /dev/null +++ b/src/storage/storage_util.h @@ -0,0 +1,149 @@ +/* + * storage_util.h: utility functions for storage driver + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <http://www.gnu.org/licenses/>. + */ + +#ifndef __VIR_STORAGE_UTIL_H__ +# define __VIR_STORAGE_UTIL_H__ + +# include <sys/stat.h> + +# include "internal.h" +# include "storage_conf.h" +# include "vircommand.h" +# include "storage_driver.h" +# include "storage_backend.h" + +/* File creation/cloning functions used for cloning between backends */ +int virStorageBackendCreateRaw(virConnectPtr conn, + virStoragePoolObjPtr pool, + virStorageVolDefPtr vol, + virStorageVolDefPtr inputvol, + unsigned int flags); + +int virStorageBackendCreateQemuImg(virConnectPtr conn, + virStoragePoolObjPtr pool, + virStorageVolDefPtr vol, + virStorageVolDefPtr inputvol, + unsigned int flags); + +int virStorageBackendCreatePloop(virConnectPtr conn, + virStoragePoolObjPtr pool, + virStorageVolDefPtr vol, + virStorageVolDefPtr inputvol, + unsigned int flags); + +int virStoragePloopResize(virStorageVolDefPtr vol, + unsigned long long capacity); + +int virStorageBackendRedoPloopUpdate(virStorageSourcePtr target, + struct stat *sb, int *fd, + unsigned int flags); +bool virStorageBackendIsPloopDir(char *path); + +virStorageBackendBuildVolFrom +virStorageBackendGetBuildVolFromFunction(virStorageVolDefPtr vol, + virStorageVolDefPtr inputvol); + +int virStorageBackendFindGlusterPoolSources(const char *host, + int pooltype, + virStoragePoolSourceListPtr list, + bool report); + +int virStorageBackendVolUploadLocal(virConnectPtr conn, + virStoragePoolObjPtr pool, + virStorageVolDefPtr vol, + virStreamPtr stream, + unsigned long long offset, + unsigned long long len, + unsigned int flags); +int virStorageBackendVolDownloadLocal(virConnectPtr conn, + virStoragePoolObjPtr pool, + virStorageVolDefPtr vol, + virStreamPtr stream, + unsigned long long offset, + unsigned long long len, + unsigned int flags); + +int virStorageBackendVolWipeLocal(virConnectPtr conn, + virStoragePoolObjPtr pool, + virStorageVolDefPtr vol, + unsigned int algorithm, + unsigned int flags); + +bool virStorageBackendDeviceIsEmpty(const char *devpath, + const char *format, + bool writelabel); + +/* VolOpenCheckMode flags */ +enum { + VIR_STORAGE_VOL_OPEN_NOERROR = 1 << 0, /* don't error if unexpected type + * encountered, just warn */ + VIR_STORAGE_VOL_OPEN_REG = 1 << 1, /* regular files okay */ + VIR_STORAGE_VOL_OPEN_BLOCK = 1 << 2, /* block files okay */ + VIR_STORAGE_VOL_OPEN_CHAR = 1 << 3, /* char files okay */ + VIR_STORAGE_VOL_OPEN_DIR = 1 << 4, /* directories okay */ +}; + +/* VolReadErrorMode flags + * If flag is present, then operation won't cause fatal error for + * specified operation, rather a VIR_WARN will be issued and a -2 returned + * for function call + */ +enum { + VIR_STORAGE_VOL_READ_NOERROR = 1 << 0, /* ignore *read errors */ +}; + +# define VIR_STORAGE_VOL_OPEN_DEFAULT (VIR_STORAGE_VOL_OPEN_REG |\ + VIR_STORAGE_VOL_OPEN_BLOCK) + +int virStorageBackendVolOpen(const char *path, struct stat *sb, + unsigned int flags) + ATTRIBUTE_RETURN_CHECK + ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2); + +# define VIR_STORAGE_DEFAULT_POOL_PERM_MODE 0755 +# define VIR_STORAGE_DEFAULT_VOL_PERM_MODE 0600 + +int virStorageBackendUpdateVolInfo(virStorageVolDefPtr vol, + bool withBlockVolFormat, + unsigned int openflags, + unsigned int readflags); +int virStorageBackendUpdateVolTargetInfo(virStorageVolType voltype, + virStorageSourcePtr target, + bool withBlockVolFormat, + unsigned int openflags, + unsigned int readflags); +int virStorageBackendUpdateVolTargetInfoFD(virStorageSourcePtr target, + int fd, + struct stat *sb); + +bool virStorageBackendPoolPathIsStable(const char *path); +char *virStorageBackendStablePath(virStoragePoolObjPtr pool, + const char *devpath, + bool loop); + +virCommandPtr +virStorageBackendCreateQemuImgCmdFromVol(virConnectPtr conn, + virStoragePoolObjPtr pool, + virStorageVolDefPtr vol, + virStorageVolDefPtr inputvol, + unsigned int flags, + const char *create_tool, + int imgformat, + const char *secretPath); + +#endif /* __VIR_STORAGE_UTIL_H__ */ diff --git a/tests/storagevolxml2argvtest.c b/tests/storagevolxml2argvtest.c index e300821f8..bf9dbe5e0 100644 --- a/tests/storagevolxml2argvtest.c +++ b/tests/storagevolxml2argvtest.c @@ -3,7 +3,7 @@ #include "internal.h" #include "testutils.h" #include "datatypes.h" -#include "storage/storage_backend.h" +#include "storage/storage_util.h" #include "testutilsqemu.h" #include "virstring.h" -- 2.11.0

On 01/18/2017 01:47 PM, Peter Krempa wrote:
The file became a garbage dump for all kinds of utility functions over time. Move them to a separate file so that the files can become a clean interface for the storage backends. --- po/POTFILES.in | 1 + src/Makefile.am | 3 +- src/storage/storage_backend.c | 2954 +------------------------------- src/storage/storage_backend.h | 125 -- src/storage/storage_backend_disk.c | 1 + src/storage/storage_backend_fs.c | 1 + src/storage/storage_backend_gluster.c | 1 + src/storage/storage_backend_iscsi.c | 1 + src/storage/storage_backend_logical.c | 1 + src/storage/storage_backend_mpath.c | 1 + src/storage/storage_backend_rbd.c | 1 + src/storage/storage_backend_scsi.c | 1 + src/storage/storage_backend_sheepdog.c | 1 + src/storage/storage_backend_zfs.c | 1 + src/storage/storage_driver.c | 1 + src/storage/storage_util.c | 2919 +++++++++++++++++++++++++++++++ src/storage/storage_util.h | 149 ++ tests/storagevolxml2argvtest.c | 2 +- 18 files changed, 3117 insertions(+), 3047 deletions(-) create mode 100644 src/storage/storage_util.c create mode 100644 src/storage/storage_util.h
Rather than adding #include "storage_util.h", why not add it to src/storage/storage_backend.h since that's included by all that need it. IDC either way. While looking at what includes storage_backend.h I note that *_mpath.{ch} both include storage_backend.h, but *_mpath.c doesn't include *_mpath.h. It's the only one... ACK - John

On Wed, Jan 18, 2017 at 18:52:51 -0500, John Ferlan wrote:
On 01/18/2017 01:47 PM, Peter Krempa wrote:
The file became a garbage dump for all kinds of utility functions over time. Move them to a separate file so that the files can become a clean interface for the storage backends. --- po/POTFILES.in | 1 + src/Makefile.am | 3 +- src/storage/storage_backend.c | 2954 +------------------------------- src/storage/storage_backend.h | 125 -- src/storage/storage_backend_disk.c | 1 + src/storage/storage_backend_fs.c | 1 + src/storage/storage_backend_gluster.c | 1 + src/storage/storage_backend_iscsi.c | 1 + src/storage/storage_backend_logical.c | 1 + src/storage/storage_backend_mpath.c | 1 + src/storage/storage_backend_rbd.c | 1 + src/storage/storage_backend_scsi.c | 1 + src/storage/storage_backend_sheepdog.c | 1 + src/storage/storage_backend_zfs.c | 1 + src/storage/storage_driver.c | 1 + src/storage/storage_util.c | 2919 +++++++++++++++++++++++++++++++ src/storage/storage_util.h | 149 ++ tests/storagevolxml2argvtest.c | 2 +- 18 files changed, 3117 insertions(+), 3047 deletions(-) create mode 100644 src/storage/storage_util.c create mode 100644 src/storage/storage_util.h
Rather than adding #include "storage_util.h", why not add it to src/storage/storage_backend.h since that's included by all that need it. IDC either way.
That include is going to be dropped soon in the patch that I did not send yet. storage_backend.h will become the interface where storage backends are registered/loaded. The point of this patch was to actually allow this by moving the utility code one layer dow, so that this file becomes clean.
While looking at what includes storage_backend.h I note that *_mpath.{ch} both include storage_backend.h, but *_mpath.c doesn't include *_mpath.h. It's the only one...
That one will be fixed as well by the upcoming patch, since it will add public API of all the storage drivers to register themselves.
ACK -
Thanks.

The file backend code was mistakenly put into #if WITH_STORAGE_FS. This is not necessary since all the backends just access files on disk, and thus the code for WITH_STORAGE_DIR is sufficient to compile everything. --- src/storage/storage_backend_fs.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/storage/storage_backend_fs.c b/src/storage/storage_backend_fs.c index 7d18ad7c9..ba8aa20a2 100644 --- a/src/storage/storage_backend_fs.c +++ b/src/storage/storage_backend_fs.c @@ -1389,6 +1389,7 @@ virStorageBackend virStorageBackendNetFileSystem = { .downloadVol = virStorageBackendVolDownloadLocal, .wipeVol = virStorageBackendVolWipeLocal, }; +#endif /* WITH_STORAGE_FS */ typedef struct _virStorageFileBackendFsPriv virStorageFileBackendFsPriv; @@ -1573,5 +1574,3 @@ virStorageFileBackend virStorageFileBackendDir = { .storageFileGetUniqueIdentifier = virStorageFileBackendFileGetUniqueIdentifier, }; - -#endif /* WITH_STORAGE_FS */ -- 2.11.0

On 01/18/2017 06:36 AM, Peter Krempa wrote:
The file backend code was mistakenly put into #if WITH_STORAGE_FS. This is not necessary since all the backends just access files on disk, and thus the code for WITH_STORAGE_DIR is sufficient to compile everything. --- src/storage/storage_backend_fs.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/src/storage/storage_backend_fs.c b/src/storage/storage_backend_fs.c index 7d18ad7c9..ba8aa20a2 100644 --- a/src/storage/storage_backend_fs.c +++ b/src/storage/storage_backend_fs.c @@ -1389,6 +1389,7 @@ virStorageBackend virStorageBackendNetFileSystem = { .downloadVol = virStorageBackendVolDownloadLocal, .wipeVol = virStorageBackendVolWipeLocal, }; +#endif /* WITH_STORAGE_FS */
typedef struct _virStorageFileBackendFsPriv virStorageFileBackendFsPriv; @@ -1573,5 +1574,3 @@ virStorageFileBackend virStorageFileBackendDir = {
.storageFileGetUniqueIdentifier = virStorageFileBackendFileGetUniqueIdentifier, }; - -#endif /* WITH_STORAGE_FS */
Wouldn't there need to be a comparable adjustment to fileBackends in storage_backend.c FWIW: virStorageFileBackendDir is only defined, but not included in the fileBackends John

On 01/18/2017 06:36 AM, Peter Krempa wrote:
The file backend code was mistakenly put into #if WITH_STORAGE_FS. This is not necessary since all the backends just access files on disk, and thus the code for WITH_STORAGE_DIR is sufficient to compile everything. --- src/storage/storage_backend_fs.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-)
OK so with a better picture - I understand now. The WITH*_FS is needed in storage_backend.c.... ACK John
diff --git a/src/storage/storage_backend_fs.c b/src/storage/storage_backend_fs.c index 7d18ad7c9..ba8aa20a2 100644 --- a/src/storage/storage_backend_fs.c +++ b/src/storage/storage_backend_fs.c @@ -1389,6 +1389,7 @@ virStorageBackend virStorageBackendNetFileSystem = { .downloadVol = virStorageBackendVolDownloadLocal, .wipeVol = virStorageBackendVolWipeLocal, }; +#endif /* WITH_STORAGE_FS */
typedef struct _virStorageFileBackendFsPriv virStorageFileBackendFsPriv; @@ -1573,5 +1574,3 @@ virStorageFileBackend virStorageFileBackendDir = {
.storageFileGetUniqueIdentifier = virStorageFileBackendFileGetUniqueIdentifier, }; - -#endif /* WITH_STORAGE_FS */

The iSCSI backend driver was using stuff from the SCSI driver without making sure that it's compiled in. Move the common code into the storage_util.c since it does not contain any specific code. --- src/storage/storage_backend_iscsi.c | 1 - src/storage/storage_backend_scsi.c | 447 ----------------------------------- src/storage/storage_backend_scsi.h | 4 - src/storage/storage_util.c | 449 ++++++++++++++++++++++++++++++++++++ src/storage/storage_util.h | 3 + 5 files changed, 452 insertions(+), 452 deletions(-) diff --git a/src/storage/storage_backend_iscsi.c b/src/storage/storage_backend_iscsi.c index 8799349b6..281334124 100644 --- a/src/storage/storage_backend_iscsi.c +++ b/src/storage/storage_backend_iscsi.c @@ -32,7 +32,6 @@ #include "datatypes.h" #include "driver.h" -#include "storage_backend_scsi.h" #include "storage_backend_iscsi.h" #include "viralloc.h" #include "vircommand.h" diff --git a/src/storage/storage_backend_scsi.c b/src/storage/storage_backend_scsi.c index d294d2ac0..ecad1782c 100644 --- a/src/storage/storage_backend_scsi.c +++ b/src/storage/storage_backend_scsi.c @@ -25,7 +25,6 @@ #include <unistd.h> #include <stdio.h> -#include <dirent.h> #include <fcntl.h> #include "virerror.h" @@ -48,452 +47,6 @@ struct _virStoragePoolFCRefreshInfo { unsigned char pool_uuid[VIR_UUID_BUFLEN]; }; -/* Function to check if the type file in the given sysfs_path is a - * Direct-Access device (i.e. type 0). Return -1 on failure, type of - * the device otherwise. - */ -static int -getDeviceType(uint32_t host, - uint32_t bus, - uint32_t target, - uint32_t lun, - int *type) -{ - char *type_path = NULL; - char typestr[3]; - char *gottype, *p; - FILE *typefile; - int retval = 0; - - if (virAsprintf(&type_path, "/sys/bus/scsi/devices/%u:%u:%u:%u/type", - host, bus, target, lun) < 0) - goto out; - - typefile = fopen(type_path, "r"); - if (typefile == NULL) { - virReportSystemError(errno, - _("Could not find typefile '%s'"), - type_path); - /* there was no type file; that doesn't seem right */ - retval = -1; - goto out; - } - - gottype = fgets(typestr, 3, typefile); - VIR_FORCE_FCLOSE(typefile); - - if (gottype == NULL) { - virReportSystemError(errno, - _("Could not read typefile '%s'"), - type_path); - /* we couldn't read the type file; have to give up */ - retval = -1; - goto out; - } - - /* we don't actually care about p, but if you pass NULL and the last - * character is not \0, virStrToLong_i complains - */ - if (virStrToLong_i(typestr, &p, 10, type) < 0) { - virReportError(VIR_ERR_INTERNAL_ERROR, - _("Device type '%s' is not an integer"), - typestr); - /* Hm, type wasn't an integer; seems strange */ - retval = -1; - goto out; - } - - VIR_DEBUG("Device type is %d", *type); - - out: - VIR_FREE(type_path); - return retval; -} - -static char * -virStorageBackendSCSISerial(const char *dev) -{ - char *serial = NULL; -#ifdef WITH_UDEV - virCommandPtr cmd = virCommandNewArgList( - "/lib/udev/scsi_id", - "--replace-whitespace", - "--whitelisted", - "--device", dev, - NULL - ); - - /* Run the program and capture its output */ - virCommandSetOutputBuffer(cmd, &serial); - if (virCommandRun(cmd, NULL) < 0) - goto cleanup; -#endif - - if (serial && STRNEQ(serial, "")) { - char *nl = strchr(serial, '\n'); - if (nl) - *nl = '\0'; - } else { - VIR_FREE(serial); - ignore_value(VIR_STRDUP(serial, dev)); - } - -#ifdef WITH_UDEV - cleanup: - virCommandFree(cmd); -#endif - - return serial; -} - - -/* - * Attempt to create a new LUN - * - * Returns: - * - * 0 => Success - * -1 => Failure due to some sort of OOM or other fatal issue found when - * attempting to get/update information about a found volume - * -2 => Failure to find a stable path, not fatal, caller can try another - */ -static int -virStorageBackendSCSINewLun(virStoragePoolObjPtr pool, - uint32_t host ATTRIBUTE_UNUSED, - uint32_t bus, - uint32_t target, - uint32_t lun, - const char *dev) -{ - virStorageVolDefPtr vol = NULL; - char *devpath = NULL; - int retval = -1; - - /* Check if the pool is using a stable target path. The call to - * virStorageBackendStablePath will fail if the pool target path - * isn't stable and just return the strdup'd 'devpath' anyway. - * This would be indistinguishable to failing to find the stable - * path to the device if the virDirRead loop to search the - * target pool path for our devpath had failed. - */ - if (!virStorageBackendPoolPathIsStable(pool->def->target.path) && - !(STREQ(pool->def->target.path, "/dev") || - STREQ(pool->def->target.path, "/dev/"))) { - virReportError(VIR_ERR_INVALID_ARG, - _("unable to use target path '%s' for dev '%s'"), - NULLSTR(pool->def->target.path), dev); - goto cleanup; - } - - if (VIR_ALLOC(vol) < 0) - goto cleanup; - - vol->type = VIR_STORAGE_VOL_BLOCK; - - /* 'host' is dynamically allocated by the kernel, first come, - * first served, per HBA. As such it isn't suitable for use - * in the volume name. We only need uniqueness per-pool, so - * just leave 'host' out - */ - if (virAsprintf(&(vol->name), "unit:%u:%u:%u", bus, target, lun) < 0) - goto cleanup; - - if (virAsprintf(&devpath, "/dev/%s", dev) < 0) - goto cleanup; - - VIR_DEBUG("Trying to create volume for '%s'", devpath); - - /* Now figure out the stable path - * - * XXX this method is O(N) because it scans the pool target - * dir every time its run. Should figure out a more efficient - * way of doing this... - */ - if ((vol->target.path = virStorageBackendStablePath(pool, - devpath, - true)) == NULL) - goto cleanup; - - if (STREQ(devpath, vol->target.path) && - !(STREQ(pool->def->target.path, "/dev") || - STREQ(pool->def->target.path, "/dev/"))) { - - VIR_DEBUG("No stable path found for '%s' in '%s'", - devpath, pool->def->target.path); - - retval = -2; - goto cleanup; - } - - /* Allow a volume read failure to ignore or skip this block file */ - if ((retval = virStorageBackendUpdateVolInfo(vol, true, - VIR_STORAGE_VOL_OPEN_DEFAULT, - VIR_STORAGE_VOL_READ_NOERROR)) < 0) - goto cleanup; - - if (!(vol->key = virStorageBackendSCSISerial(vol->target.path))) - goto cleanup; - - pool->def->capacity += vol->target.capacity; - pool->def->allocation += vol->target.allocation; - - if (VIR_APPEND_ELEMENT(pool->volumes.objs, pool->volumes.count, vol) < 0) - goto cleanup; - - vol = NULL; - retval = 0; - - cleanup: - virStorageVolDefFree(vol); - VIR_FREE(devpath); - return retval; -} - - -static int -getNewStyleBlockDevice(const char *lun_path, - const char *block_name ATTRIBUTE_UNUSED, - char **block_device) -{ - char *block_path = NULL; - DIR *block_dir = NULL; - struct dirent *block_dirent = NULL; - int retval = -1; - int direrr; - - if (virAsprintf(&block_path, "%s/block", lun_path) < 0) - goto cleanup; - - VIR_DEBUG("Looking for block device in '%s'", block_path); - - if (virDirOpen(&block_dir, block_path) < 0) - goto cleanup; - - while ((direrr = virDirRead(block_dir, &block_dirent, block_path)) > 0) { - if (VIR_STRDUP(*block_device, block_dirent->d_name) < 0) - goto cleanup; - - VIR_DEBUG("Block device is '%s'", *block_device); - - break; - } - - if (direrr < 0) - goto cleanup; - - retval = 0; - - cleanup: - VIR_DIR_CLOSE(block_dir); - VIR_FREE(block_path); - return retval; -} - - -static int -getOldStyleBlockDevice(const char *lun_path ATTRIBUTE_UNUSED, - const char *block_name, - char **block_device) -{ - char *blockp = NULL; - int retval = -1; - - /* old-style; just parse out the sd */ - if (!(blockp = strrchr(block_name, ':'))) { - /* Hm, wasn't what we were expecting; have to give up */ - virReportError(VIR_ERR_INTERNAL_ERROR, - _("Failed to parse block name %s"), - block_name); - goto cleanup; - } else { - blockp++; - if (VIR_STRDUP(*block_device, blockp) < 0) - goto cleanup; - - VIR_DEBUG("Block device is '%s'", *block_device); - } - - retval = 0; - cleanup: - return retval; -} - - -/* - * Search a device entry for the "block" file - * - * Returns - * - * 0 => Found it - * -1 => Fatal error - * -2 => Didn't find in lun_path directory - */ -static int -getBlockDevice(uint32_t host, - uint32_t bus, - uint32_t target, - uint32_t lun, - char **block_device) -{ - char *lun_path = NULL; - DIR *lun_dir = NULL; - struct dirent *lun_dirent = NULL; - int retval = -1; - int direrr; - - *block_device = NULL; - - if (virAsprintf(&lun_path, "/sys/bus/scsi/devices/%u:%u:%u:%u", - host, bus, target, lun) < 0) - goto cleanup; - - if (virDirOpen(&lun_dir, lun_path) < 0) - goto cleanup; - - while ((direrr = virDirRead(lun_dir, &lun_dirent, lun_path)) > 0) { - if (STRPREFIX(lun_dirent->d_name, "block")) { - if (strlen(lun_dirent->d_name) == 5) { - if (getNewStyleBlockDevice(lun_path, - lun_dirent->d_name, - block_device) < 0) - goto cleanup; - } else { - if (getOldStyleBlockDevice(lun_path, - lun_dirent->d_name, - block_device) < 0) - goto cleanup; - } - break; - } - } - if (direrr < 0) - goto cleanup; - if (!*block_device) { - retval = -2; - goto cleanup; - } - - retval = 0; - - cleanup: - VIR_DIR_CLOSE(lun_dir); - VIR_FREE(lun_path); - return retval; -} - - -/* - * Process a Logical Unit entry from the scsi host device directory - * - * Returns: - * - * 0 => Found a valid entry - * -1 => Some sort of fatal error - * -2 => non-fatal error or a non-disk entry - */ -static int -processLU(virStoragePoolObjPtr pool, - uint32_t host, - uint32_t bus, - uint32_t target, - uint32_t lun) -{ - int retval = -1; - int device_type; - char *block_device = NULL; - - VIR_DEBUG("Processing LU %u:%u:%u:%u", - host, bus, target, lun); - - if (getDeviceType(host, bus, target, lun, &device_type) < 0) { - virReportError(VIR_ERR_INTERNAL_ERROR, - _("Failed to determine if %u:%u:%u:%u is a Direct-Access LUN"), - host, bus, target, lun); - return -1; - } - - /* We don't create volumes for devices other than disk and cdrom - * devices, but finding a device that isn't one of those types - * isn't an error, either. */ - if (!(device_type == VIR_STORAGE_DEVICE_TYPE_DISK || - device_type == VIR_STORAGE_DEVICE_TYPE_ROM)) - return -2; - - VIR_DEBUG("%u:%u:%u:%u is a Direct-Access LUN", - host, bus, target, lun); - - if ((retval = getBlockDevice(host, bus, target, lun, &block_device)) < 0) { - VIR_DEBUG("Failed to find block device for this LUN"); - return retval; - } - - retval = virStorageBackendSCSINewLun(pool, host, bus, target, lun, - block_device); - if (retval < 0) { - VIR_DEBUG("Failed to create new storage volume for %u:%u:%u:%u", - host, bus, target, lun); - goto cleanup; - } - - VIR_DEBUG("Created new storage volume for %u:%u:%u:%u successfully", - host, bus, target, lun); - - cleanup: - VIR_FREE(block_device); - return retval; -} - - -int -virStorageBackendSCSIFindLUs(virStoragePoolObjPtr pool, - uint32_t scanhost) -{ - int retval = 0; - uint32_t bus, target, lun; - const char *device_path = "/sys/bus/scsi/devices"; - DIR *devicedir = NULL; - struct dirent *lun_dirent = NULL; - char devicepattern[64]; - int found = 0; - - VIR_DEBUG("Discovering LUs on host %u", scanhost); - - virFileWaitForDevices(); - - if (virDirOpen(&devicedir, device_path) < 0) - return -1; - - snprintf(devicepattern, sizeof(devicepattern), "%u:%%u:%%u:%%u\n", scanhost); - - while ((retval = virDirRead(devicedir, &lun_dirent, device_path)) > 0) { - int rc; - - if (sscanf(lun_dirent->d_name, devicepattern, - &bus, &target, &lun) != 3) { - continue; - } - - VIR_DEBUG("Found possible LU '%s'", lun_dirent->d_name); - - rc = processLU(pool, scanhost, bus, target, lun); - if (rc == -1) { - retval = -1; - break; - } - if (rc == 0) - found++; - } - - VIR_DIR_CLOSE(devicedir); - - if (retval < 0) - return -1; - - VIR_DEBUG("Found %d LUs for pool %s", found, pool->def->name); - - return found; -} - static int virStorageBackendSCSITriggerRescan(uint32_t host) diff --git a/src/storage/storage_backend_scsi.h b/src/storage/storage_backend_scsi.h index 0984fd503..691b8cbf1 100644 --- a/src/storage/storage_backend_scsi.h +++ b/src/storage/storage_backend_scsi.h @@ -32,8 +32,4 @@ extern virStorageBackend virStorageBackendSCSI; -int -virStorageBackendSCSIFindLUs(virStoragePoolObjPtr pool, - uint32_t scanhost); - #endif /* __VIR_STORAGE_BACKEND_SCSI_H__ */ diff --git a/src/storage/storage_util.c b/src/storage/storage_util.c index 353dec4a8..7e69276a6 100644 --- a/src/storage/storage_util.c +++ b/src/storage/storage_util.c @@ -2913,3 +2913,452 @@ virStorageBackendDeviceIsEmpty(const char *devpath, return ret == 0; } + + +static char * +virStorageBackendSCSISerial(const char *dev) +{ + char *serial = NULL; +#ifdef WITH_UDEV + virCommandPtr cmd = virCommandNewArgList( + "/lib/udev/scsi_id", + "--replace-whitespace", + "--whitelisted", + "--device", dev, + NULL + ); + + /* Run the program and capture its output */ + virCommandSetOutputBuffer(cmd, &serial); + if (virCommandRun(cmd, NULL) < 0) + goto cleanup; +#endif + + if (serial && STRNEQ(serial, "")) { + char *nl = strchr(serial, '\n'); + if (nl) + *nl = '\0'; + } else { + VIR_FREE(serial); + ignore_value(VIR_STRDUP(serial, dev)); + } + +#ifdef WITH_UDEV + cleanup: + virCommandFree(cmd); +#endif + + return serial; +} + + +/* + * Attempt to create a new LUN + * + * Returns: + * + * 0 => Success + * -1 => Failure due to some sort of OOM or other fatal issue found when + * attempting to get/update information about a found volume + * -2 => Failure to find a stable path, not fatal, caller can try another + */ +static int +virStorageBackendSCSINewLun(virStoragePoolObjPtr pool, + uint32_t host ATTRIBUTE_UNUSED, + uint32_t bus, + uint32_t target, + uint32_t lun, + const char *dev) +{ + virStorageVolDefPtr vol = NULL; + char *devpath = NULL; + int retval = -1; + + /* Check if the pool is using a stable target path. The call to + * virStorageBackendStablePath will fail if the pool target path + * isn't stable and just return the strdup'd 'devpath' anyway. + * This would be indistinguishable to failing to find the stable + * path to the device if the virDirRead loop to search the + * target pool path for our devpath had failed. + */ + if (!virStorageBackendPoolPathIsStable(pool->def->target.path) && + !(STREQ(pool->def->target.path, "/dev") || + STREQ(pool->def->target.path, "/dev/"))) { + virReportError(VIR_ERR_INVALID_ARG, + _("unable to use target path '%s' for dev '%s'"), + NULLSTR(pool->def->target.path), dev); + goto cleanup; + } + + if (VIR_ALLOC(vol) < 0) + goto cleanup; + + vol->type = VIR_STORAGE_VOL_BLOCK; + + /* 'host' is dynamically allocated by the kernel, first come, + * first served, per HBA. As such it isn't suitable for use + * in the volume name. We only need uniqueness per-pool, so + * just leave 'host' out + */ + if (virAsprintf(&(vol->name), "unit:%u:%u:%u", bus, target, lun) < 0) + goto cleanup; + + if (virAsprintf(&devpath, "/dev/%s", dev) < 0) + goto cleanup; + + VIR_DEBUG("Trying to create volume for '%s'", devpath); + + /* Now figure out the stable path + * + * XXX this method is O(N) because it scans the pool target + * dir every time its run. Should figure out a more efficient + * way of doing this... + */ + if ((vol->target.path = virStorageBackendStablePath(pool, + devpath, + true)) == NULL) + goto cleanup; + + if (STREQ(devpath, vol->target.path) && + !(STREQ(pool->def->target.path, "/dev") || + STREQ(pool->def->target.path, "/dev/"))) { + + VIR_DEBUG("No stable path found for '%s' in '%s'", + devpath, pool->def->target.path); + + retval = -2; + goto cleanup; + } + + /* Allow a volume read failure to ignore or skip this block file */ + if ((retval = virStorageBackendUpdateVolInfo(vol, true, + VIR_STORAGE_VOL_OPEN_DEFAULT, + VIR_STORAGE_VOL_READ_NOERROR)) < 0) + goto cleanup; + + if (!(vol->key = virStorageBackendSCSISerial(vol->target.path))) + goto cleanup; + + pool->def->capacity += vol->target.capacity; + pool->def->allocation += vol->target.allocation; + + if (VIR_APPEND_ELEMENT(pool->volumes.objs, pool->volumes.count, vol) < 0) + goto cleanup; + + vol = NULL; + retval = 0; + + cleanup: + virStorageVolDefFree(vol); + VIR_FREE(devpath); + return retval; +} + + + +static int +getNewStyleBlockDevice(const char *lun_path, + const char *block_name ATTRIBUTE_UNUSED, + char **block_device) +{ + char *block_path = NULL; + DIR *block_dir = NULL; + struct dirent *block_dirent = NULL; + int retval = -1; + int direrr; + + if (virAsprintf(&block_path, "%s/block", lun_path) < 0) + goto cleanup; + + VIR_DEBUG("Looking for block device in '%s'", block_path); + + if (virDirOpen(&block_dir, block_path) < 0) + goto cleanup; + + while ((direrr = virDirRead(block_dir, &block_dirent, block_path)) > 0) { + if (VIR_STRDUP(*block_device, block_dirent->d_name) < 0) + goto cleanup; + + VIR_DEBUG("Block device is '%s'", *block_device); + + break; + } + + if (direrr < 0) + goto cleanup; + + retval = 0; + + cleanup: + VIR_DIR_CLOSE(block_dir); + VIR_FREE(block_path); + return retval; +} + + +static int +getOldStyleBlockDevice(const char *lun_path ATTRIBUTE_UNUSED, + const char *block_name, + char **block_device) +{ + char *blockp = NULL; + int retval = -1; + + /* old-style; just parse out the sd */ + if (!(blockp = strrchr(block_name, ':'))) { + /* Hm, wasn't what we were expecting; have to give up */ + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Failed to parse block name %s"), + block_name); + goto cleanup; + } else { + blockp++; + if (VIR_STRDUP(*block_device, blockp) < 0) + goto cleanup; + + VIR_DEBUG("Block device is '%s'", *block_device); + } + + retval = 0; + cleanup: + return retval; +} + + +/* + * Search a device entry for the "block" file + * + * Returns + * + * 0 => Found it + * -1 => Fatal error + * -2 => Didn't find in lun_path directory + */ +static int +getBlockDevice(uint32_t host, + uint32_t bus, + uint32_t target, + uint32_t lun, + char **block_device) +{ + char *lun_path = NULL; + DIR *lun_dir = NULL; + struct dirent *lun_dirent = NULL; + int retval = -1; + int direrr; + + *block_device = NULL; + + if (virAsprintf(&lun_path, "/sys/bus/scsi/devices/%u:%u:%u:%u", + host, bus, target, lun) < 0) + goto cleanup; + + if (virDirOpen(&lun_dir, lun_path) < 0) + goto cleanup; + + while ((direrr = virDirRead(lun_dir, &lun_dirent, lun_path)) > 0) { + if (STRPREFIX(lun_dirent->d_name, "block")) { + if (strlen(lun_dirent->d_name) == 5) { + if (getNewStyleBlockDevice(lun_path, + lun_dirent->d_name, + block_device) < 0) + goto cleanup; + } else { + if (getOldStyleBlockDevice(lun_path, + lun_dirent->d_name, + block_device) < 0) + goto cleanup; + } + break; + } + } + if (direrr < 0) + goto cleanup; + if (!*block_device) { + retval = -2; + goto cleanup; + } + + retval = 0; + + cleanup: + VIR_DIR_CLOSE(lun_dir); + VIR_FREE(lun_path); + return retval; +} + + +/* Function to check if the type file in the given sysfs_path is a + * Direct-Access device (i.e. type 0). Return -1 on failure, type of + * the device otherwise. + */ +static int +getDeviceType(uint32_t host, + uint32_t bus, + uint32_t target, + uint32_t lun, + int *type) +{ + char *type_path = NULL; + char typestr[3]; + char *gottype, *p; + FILE *typefile; + int retval = 0; + + if (virAsprintf(&type_path, "/sys/bus/scsi/devices/%u:%u:%u:%u/type", + host, bus, target, lun) < 0) + goto out; + + typefile = fopen(type_path, "r"); + if (typefile == NULL) { + virReportSystemError(errno, + _("Could not find typefile '%s'"), + type_path); + /* there was no type file; that doesn't seem right */ + retval = -1; + goto out; + } + + gottype = fgets(typestr, 3, typefile); + VIR_FORCE_FCLOSE(typefile); + + if (gottype == NULL) { + virReportSystemError(errno, + _("Could not read typefile '%s'"), + type_path); + /* we couldn't read the type file; have to give up */ + retval = -1; + goto out; + } + + /* we don't actually care about p, but if you pass NULL and the last + * character is not \0, virStrToLong_i complains + */ + if (virStrToLong_i(typestr, &p, 10, type) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Device type '%s' is not an integer"), + typestr); + /* Hm, type wasn't an integer; seems strange */ + retval = -1; + goto out; + } + + VIR_DEBUG("Device type is %d", *type); + + out: + VIR_FREE(type_path); + return retval; +} + + +/* + * Process a Logical Unit entry from the scsi host device directory + * + * Returns: + * + * 0 => Found a valid entry + * -1 => Some sort of fatal error + * -2 => non-fatal error or a non-disk entry + */ +static int +processLU(virStoragePoolObjPtr pool, + uint32_t host, + uint32_t bus, + uint32_t target, + uint32_t lun) +{ + int retval = -1; + int device_type; + char *block_device = NULL; + + VIR_DEBUG("Processing LU %u:%u:%u:%u", + host, bus, target, lun); + + if (getDeviceType(host, bus, target, lun, &device_type) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Failed to determine if %u:%u:%u:%u is a Direct-Access LUN"), + host, bus, target, lun); + return -1; + } + + /* We don't create volumes for devices other than disk and cdrom + * devices, but finding a device that isn't one of those types + * isn't an error, either. */ + if (!(device_type == VIR_STORAGE_DEVICE_TYPE_DISK || + device_type == VIR_STORAGE_DEVICE_TYPE_ROM)) + return -2; + + VIR_DEBUG("%u:%u:%u:%u is a Direct-Access LUN", + host, bus, target, lun); + + if ((retval = getBlockDevice(host, bus, target, lun, &block_device)) < 0) { + VIR_DEBUG("Failed to find block device for this LUN"); + return retval; + } + + retval = virStorageBackendSCSINewLun(pool, host, bus, target, lun, + block_device); + if (retval < 0) { + VIR_DEBUG("Failed to create new storage volume for %u:%u:%u:%u", + host, bus, target, lun); + goto cleanup; + } + + VIR_DEBUG("Created new storage volume for %u:%u:%u:%u successfully", + host, bus, target, lun); + + cleanup: + VIR_FREE(block_device); + return retval; +} + + +int +virStorageBackendSCSIFindLUs(virStoragePoolObjPtr pool, + uint32_t scanhost) +{ + int retval = 0; + uint32_t bus, target, lun; + const char *device_path = "/sys/bus/scsi/devices"; + DIR *devicedir = NULL; + struct dirent *lun_dirent = NULL; + char devicepattern[64]; + int found = 0; + + VIR_DEBUG("Discovering LUs on host %u", scanhost); + + virFileWaitForDevices(); + + if (virDirOpen(&devicedir, device_path) < 0) + return -1; + + snprintf(devicepattern, sizeof(devicepattern), "%u:%%u:%%u:%%u\n", scanhost); + + while ((retval = virDirRead(devicedir, &lun_dirent, device_path)) > 0) { + int rc; + + if (sscanf(lun_dirent->d_name, devicepattern, + &bus, &target, &lun) != 3) { + continue; + } + + VIR_DEBUG("Found possible LU '%s'", lun_dirent->d_name); + + rc = processLU(pool, scanhost, bus, target, lun); + if (rc == -1) { + retval = -1; + break; + } + if (rc == 0) + found++; + } + + VIR_DIR_CLOSE(devicedir); + + if (retval < 0) + return -1; + + VIR_DEBUG("Found %d LUs for pool %s", found, pool->def->name); + + return found; +} diff --git a/src/storage/storage_util.h b/src/storage/storage_util.h index 0555ae1e8..8bf0f42df 100644 --- a/src/storage/storage_util.h +++ b/src/storage/storage_util.h @@ -145,4 +145,7 @@ virStorageBackendCreateQemuImgCmdFromVol(virConnectPtr conn, int imgformat, const char *secretPath); +int virStorageBackendSCSIFindLUs(virStoragePoolObjPtr pool, + uint32_t scanhost); + #endif /* __VIR_STORAGE_UTIL_H__ */ -- 2.11.0

On 01/18/2017 06:36 AM, Peter Krempa wrote:
The iSCSI backend driver was using stuff from the SCSI driver without making sure that it's compiled in. Move the common code into the storage_util.c since it does not contain any specific code. --- src/storage/storage_backend_iscsi.c | 1 - src/storage/storage_backend_scsi.c | 447 ----------------------------------- src/storage/storage_backend_scsi.h | 4 - src/storage/storage_util.c | 449 ++++++++++++++++++++++++++++++++++++ src/storage/storage_util.h | 3 + 5 files changed, 452 insertions(+), 452 deletions(-)
Conceptually OK, but since patch 2 won't apply for me - I cannot apply this one. It seems to be fairly faithful move of code, although the order may change slightly which is no big deal John

On 01/18/2017 06:36 AM, Peter Krempa wrote:
The iSCSI backend driver was using stuff from the SCSI driver without making sure that it's compiled in. Move the common code into the storage_util.c since it does not contain any specific code. --- src/storage/storage_backend_iscsi.c | 1 - src/storage/storage_backend_scsi.c | 447 ----------------------------------- src/storage/storage_backend_scsi.h | 4 - src/storage/storage_util.c | 449 ++++++++++++++++++++++++++++++++++++ src/storage/storage_util.h | 3 + 5 files changed, 452 insertions(+), 452 deletions(-)
ACK - thanks! I was going to have to go down this path soon anyway for vHBA related work. Just scratching the surface... John

They are used only in the SCSI backend driver so there's no need to pollute the headers. --- src/storage/storage_backend_scsi.c | 4 ++++ src/storage/storage_backend_scsi.h | 4 ---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/storage/storage_backend_scsi.c b/src/storage/storage_backend_scsi.c index ecad1782c..0cc11486b 100644 --- a/src/storage/storage_backend_scsi.c +++ b/src/storage/storage_backend_scsi.c @@ -40,6 +40,10 @@ VIR_LOG_INIT("storage.storage_backend_scsi"); +#define LINUX_SYSFS_SCSI_HOST_PREFIX "/sys/class/scsi_host" +#define LINUX_SYSFS_SCSI_HOST_POSTFIX "device" +#define LINUX_SYSFS_SCSI_HOST_SCAN_STRING "- - -" + typedef struct _virStoragePoolFCRefreshInfo virStoragePoolFCRefreshInfo; typedef virStoragePoolFCRefreshInfo *virStoragePoolFCRefreshInfoPtr; struct _virStoragePoolFCRefreshInfo { diff --git a/src/storage/storage_backend_scsi.h b/src/storage/storage_backend_scsi.h index 691b8cbf1..1ba53a57c 100644 --- a/src/storage/storage_backend_scsi.h +++ b/src/storage/storage_backend_scsi.h @@ -26,10 +26,6 @@ # include "storage_backend.h" -# define LINUX_SYSFS_SCSI_HOST_PREFIX "/sys/class/scsi_host" -# define LINUX_SYSFS_SCSI_HOST_POSTFIX "device" -# define LINUX_SYSFS_SCSI_HOST_SCAN_STRING "- - -" - extern virStorageBackend virStorageBackendSCSI; #endif /* __VIR_STORAGE_BACKEND_SCSI_H__ */ -- 2.11.0

On 01/18/2017 06:36 AM, Peter Krempa wrote:
They are used only in the SCSI backend driver so there's no need to pollute the headers. --- src/storage/storage_backend_scsi.c | 4 ++++ src/storage/storage_backend_scsi.h | 4 ---- 2 files changed, 4 insertions(+), 4 deletions(-)
ACK John

Separate the headers so that functions only required for testing of the sheepdog backend are separated into their own file. --- src/Makefile.am | 3 ++- src/storage/storage_backend_sheepdog.c | 1 + src/storage/storage_backend_sheepdog.h | 5 ----- src/storage/storage_backend_sheepdog_priv.h | 29 +++++++++++++++++++++++++++++ tests/storagebackendsheepdogtest.c | 2 +- 5 files changed, 33 insertions(+), 7 deletions(-) create mode 100644 src/storage/storage_backend_sheepdog_priv.h diff --git a/src/Makefile.am b/src/Makefile.am index 348bd2a30..dc26ddf53 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -998,7 +998,8 @@ STORAGE_DRIVER_RBD_SOURCES = \ storage/storage_backend_rbd.h storage/storage_backend_rbd.c STORAGE_DRIVER_SHEEPDOG_SOURCES = \ - storage/storage_backend_sheepdog.h storage/storage_backend_sheepdog.c + storage/storage_backend_sheepdog.h storage/storage_backend_sheepdog.c \ + storage/storage_backend_sheepdog_priv.h STORAGE_DRIVER_GLUSTER_SOURCES = \ storage/storage_backend_gluster.h storage/storage_backend_gluster.c diff --git a/src/storage/storage_backend_sheepdog.c b/src/storage/storage_backend_sheepdog.c index 17202e487..36458a562 100644 --- a/src/storage/storage_backend_sheepdog.c +++ b/src/storage/storage_backend_sheepdog.c @@ -29,6 +29,7 @@ #include "virerror.h" #include "storage_backend_sheepdog.h" +#include "storage_backend_sheepdog_priv.h" #include "storage_conf.h" #include "vircommand.h" #include "viralloc.h" diff --git a/src/storage/storage_backend_sheepdog.h b/src/storage/storage_backend_sheepdog.h index b0d8440b3..df2ead5ed 100644 --- a/src/storage/storage_backend_sheepdog.h +++ b/src/storage/storage_backend_sheepdog.h @@ -29,11 +29,6 @@ # include "storage_backend.h" -int virStorageBackendSheepdogParseNodeInfo(virStoragePoolDefPtr pool, - char *output); -int virStorageBackendSheepdogParseVdiList(virStorageVolDefPtr vol, - char *output); - extern virStorageBackend virStorageBackendSheepdog; #endif /* __VIR_STORAGE_BACKEND_SHEEPDOG_H__ */ diff --git a/src/storage/storage_backend_sheepdog_priv.h b/src/storage/storage_backend_sheepdog_priv.h new file mode 100644 index 000000000..e324ef618 --- /dev/null +++ b/src/storage/storage_backend_sheepdog_priv.h @@ -0,0 +1,29 @@ +/* + * storage_backend_sheepdog_priv.h: header for functions necessary in tests + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <http://www.gnu.org/licenses/>. + */ + +#ifndef __VIR_STORAGE_BACKEND_SHEEPDOG_PRIV_H__ +# define __VIR_STORAGE_BACKEND_SHEEPDOG_PRIV_H__ + +# include "conf/storage_conf.h" + +int virStorageBackendSheepdogParseNodeInfo(virStoragePoolDefPtr pool, + char *output); +int virStorageBackendSheepdogParseVdiList(virStorageVolDefPtr vol, + char *output); + +#endif /* __VIR_STORAGE_BACKEND_SHEEPDOG_PRIV_H__ */ diff --git a/tests/storagebackendsheepdogtest.c b/tests/storagebackendsheepdogtest.c index fd258a800..1f6341f8a 100644 --- a/tests/storagebackendsheepdogtest.c +++ b/tests/storagebackendsheepdogtest.c @@ -32,7 +32,7 @@ #include "internal.h" #include "testutils.h" -#include "storage/storage_backend_sheepdog.h" +#include "storage/storage_backend_sheepdog_priv.h" #include "virstring.h" #define VIR_FROM_THIS VIR_FROM_NONE -- 2.11.0

On 01/18/2017 06:36 AM, Peter Krempa wrote:
Separate the headers so that functions only required for testing of the sheepdog backend are separated into their own file. --- src/Makefile.am | 3 ++- src/storage/storage_backend_sheepdog.c | 1 + src/storage/storage_backend_sheepdog.h | 5 ----- src/storage/storage_backend_sheepdog_priv.h | 29 +++++++++++++++++++++++++++++ tests/storagebackendsheepdogtest.c | 2 +- 5 files changed, 33 insertions(+), 7 deletions(-) create mode 100644 src/storage/storage_backend_sheepdog_priv.h
ACK John
participants (2)
-
John Ferlan
-
Peter Krempa