/* * storage_backend.c: internal storage driver backend contract * * Copyright (C) 2007-2016 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 * 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 * . * * Author: Daniel P. Berrange */ #include #include #include #include #include #include #include #include #include #include #include #include "dirname.h" #ifdef __linux__ # include # include # ifndef FS_NOCOW_FL # define FS_NOCOW_FL 0x00800000 /* Do not cow file */ # endif #endif #if WITH_SELINUX # include #endif #if HAVE_LINUX_BTRFS_H # include #endif #include "datatypes.h" #include "virerror.h" #include "viralloc.h" #include "internal.h" #include "secret_conf.h" #include "viruuid.h" #include "virstoragefile.h" #include "storage_backend.h" #include "virlog.h" #include "virfile.h" #include "stat-time.h" #include "virstring.h" #include "virxml.h" #include "fdstream.h" #if WITH_STORAGE_LVM # include "storage_backend_logical.h" #endif #if WITH_STORAGE_ISCSI # include "storage_backend_iscsi.h" #endif #if WITH_STORAGE_SCSI # include "storage_backend_scsi.h" #endif #if WITH_STORAGE_MPATH # include "storage_backend_mpath.h" #endif #if WITH_STORAGE_DISK # include "storage_backend_disk.h" #endif #if WITH_STORAGE_DIR # include "storage_backend_fs.h" #endif #if WITH_STORAGE_RBD # include "storage_backend_rbd.h" #endif #if WITH_STORAGE_SHEEPDOG # include "storage_backend_sheepdog.h" #endif #if WITH_STORAGE_GLUSTER # include "storage_backend_gluster.h" #endif #if WITH_STORAGE_ZFS # include "storage_backend_zfs.h" #endif #define VIR_FROM_THIS VIR_FROM_STORAGE VIR_LOG_INIT("storage.storage_backend"); static virStorageBackendPtr backends[] = { #if WITH_STORAGE_DIR &virStorageBackendDirectory, #endif #if WITH_STORAGE_FS &virStorageBackendFileSystem, &virStorageBackendNetFileSystem, #endif #if WITH_STORAGE_LVM &virStorageBackendLogical, #endif #if WITH_STORAGE_ISCSI &virStorageBackendISCSI, #endif #if WITH_STORAGE_SCSI &virStorageBackendSCSI, #endif #if WITH_STORAGE_MPATH &virStorageBackendMpath, #endif #if WITH_STORAGE_DISK &virStorageBackendDisk, #endif #if WITH_STORAGE_RBD &virStorageBackendRBD, #endif #if WITH_STORAGE_SHEEPDOG &virStorageBackendSheepdog, #endif #if WITH_STORAGE_GLUSTER &virStorageBackendGluster, #endif #if WITH_STORAGE_ZFS &virStorageBackendZFS, #endif NULL }; static virStorageFileBackendPtr fileBackends[] = { #if WITH_STORAGE_FS &virStorageFileBackendFile, &virStorageFileBackendBlock, #endif #if WITH_STORAGE_GLUSTER &virStorageFileBackendGluster, #endif NULL }; #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 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.volume, 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; memcpy(enc_secret->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; } enum { QEMU_IMG_BACKING_FORMAT_NONE = 0, QEMU_IMG_BACKING_FORMAT_FLAG, QEMU_IMG_BACKING_FORMAT_OPTIONS, 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) { char *help = NULL; char *start; char *end; char *tmp; int ret = -1; int exitstatus; virCommandPtr cmd = virCommandNewArgList(qemuimg, "-h", NULL); virCommandAddEnvString(cmd, "LC_ALL=C"); virCommandSetOutputBuffer(cmd, &help); virCommandClearCaps(cmd); /* qemuimg doesn't return zero exit status on -h, * therefore we need to provide pointer for storing * exit status, although we don't parse it any later */ if (virCommandRun(cmd, &exitstatus) < 0) goto cleanup; if ((start = strstr(help, " create ")) == NULL || (end = strstr(start, "\n")) == NULL) { virReportError(VIR_ERR_INTERNAL_ERROR, _("unable to parse qemu-img output '%s'"), help); goto cleanup; } if (((tmp = strstr(start, "-F fmt")) && tmp < end) || ((tmp = strstr(start, "-F backing_fmt")) && tmp < end)) { ret = QEMU_IMG_BACKING_FORMAT_FLAG; } else if ((tmp = strstr(start, "[-o options]")) && tmp < end) { if (virStorageBackendQemuImgSupportsCompat(qemuimg)) ret = QEMU_IMG_BACKING_FORMAT_OPTIONS_COMPAT; else ret = QEMU_IMG_BACKING_FORMAT_OPTIONS; } else { ret = QEMU_IMG_BACKING_FORMAT_NONE; } cleanup: virCommandFree(cmd); VIR_FREE(help); return ret; } 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; int inputFormat; }; static int virStorageBackendCreateQemuImgOpts(char **opts, struct _virStorageBackendQemuImgInfo info) { virBuffer buf = VIR_BUFFER_INITIALIZER; 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; } /* 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) { virCommandPtr cmd = NULL; const char *type; const char *backingType = NULL; const char *inputType = NULL; char *opts = NULL; 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, }; 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 (inputvol) { if (!(info.inputPath = inputvol->target.path)) { virReportError(VIR_ERR_INVALID_ARG, "%s", _("missing input volume target path")); return NULL; } info.inputFormat = inputvol->target.format; if (inputvol->type == VIR_STORAGE_VOL_BLOCK) info.inputFormat = VIR_STORAGE_FILE_RAW; if (!(inputType = virStorageFileFormatTypeToString(info.inputFormat))) { virReportError(VIR_ERR_INTERNAL_ERROR, _("unknown storage vol type %d"), info.inputFormat); return NULL; } } if (vol->target.backingStore) { int accessRetCode = -1; char *absolutePath = NULL; 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 NULL; } /* 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 NULL; } if (!(backingType = virStorageFileFormatTypeToString(info.backingFormat))) { virReportError(VIR_ERR_INTERNAL_ERROR, _("unknown storage vol backing store type %d"), info.backingFormat); return NULL; } /* 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 NULL; accessRetCode = access(absolutePath ? absolutePath : info.backingPath, R_OK); VIR_FREE(absolutePath); if (accessRetCode != 0) { virReportSystemError(errno, _("inaccessible backing store volume %s"), info.backingPath); return NULL; } } if (info.encryption) { virStorageEncryptionPtr enc; if (info.format != VIR_STORAGE_FILE_QCOW && info.format != VIR_STORAGE_FILE_QCOW2) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("qcow volume encryption unsupported with " "volume format %s"), type); return NULL; } enc = vol->target.encryption; 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 NULL; } if (enc->nsecrets > 1) { virReportError(VIR_ERR_XML_ERROR, "%s", _("too many secrets for qcow encryption")); return NULL; } if (enc->format == VIR_STORAGE_ENCRYPTION_FORMAT_DEFAULT || enc->nsecrets == 0) { if (virStorageGenerateQcowEncryption(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; backingType = NULL; } if (info.inputPath) virCommandAddArgList(cmd, "convert", "-f", inputType, "-O", type, NULL); else virCommandAddArgList(cmd, "create", "-f", type, NULL); if (info.backingPath) virCommandAddArgList(cmd, "-b", info.backingPath, NULL); if (imgformat >= QEMU_IMG_BACKING_FORMAT_OPTIONS) { if (info.format == VIR_STORAGE_FILE_QCOW2 && !info.compat && imgformat == QEMU_IMG_BACKING_FORMAT_OPTIONS_COMPAT) info.compat = "0.10"; if (virStorageBackendCreateQemuImgOpts(&opts, info) < 0) { virCommandFree(cmd); return NULL; } if (opts) virCommandAddArgList(cmd, "-o", opts, NULL); VIR_FREE(opts); } else { if (info.backingPath) { if (imgformat == QEMU_IMG_BACKING_FORMAT_FLAG) virCommandAddArgList(cmd, "-F", backingType, NULL); else VIR_DEBUG("Unable to set backing store format for %s with %s", info.path, create_tool); } if (info.encryption) virCommandAddArg(cmd, "-e"); } 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; } int virStorageBackendCreateQemuImg(virConnectPtr conn, virStoragePoolObjPtr pool, virStorageVolDefPtr vol, virStorageVolDefPtr inputvol, unsigned int flags) { int ret = -1; char *create_tool; int imgformat; virCommandPtr cmd; 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; cmd = virStorageBackendCreateQemuImgCmdFromVol(conn, pool, vol, inputvol, flags, create_tool, imgformat); if (!cmd) goto cleanup; ret = virStorageBackendCreateExecCommand(pool, vol, cmd); virCommandFree(cmd); cleanup: 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, we need to use an external * tool for converting */ if ((vol->type == VIR_STORAGE_VOL_FILE && vol->target.format != VIR_STORAGE_FILE_RAW) || (inputvol->type == VIR_STORAGE_VOL_FILE && inputvol->target.format != VIR_STORAGE_FILE_RAW)) { 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) { 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; virStorageSourcePtr meta = NULL; 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 (!(meta = virStorageFileGetMetadataFromBuf(target->path, buf, len, target->format, NULL))) { ret = -1; goto cleanup; } if (meta->capacity) target->capacity = meta->capacity; } if (withBlockVolFormat) { if ((ret = virStorageBackendDetectBlockVolFormatFD(target, fd, readflags)) < 0) goto cleanup; } cleanup: virStorageSourceFree(meta); 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 (S_ISREG(sb->st_mode)) { #ifndef WIN32 target->allocation = (unsigned long long)sb->st_blocks * (unsigned long long)DEV_BSIZE; #else target->allocation = sb->st_size; #endif /* Regular files may be sparse, so logical size (capacity) is not same * as actual allocation above */ target->capacity = sb->st_size; } else if (S_ISDIR(sb->st_mode)) { target->allocation = 0; target->capacity = 0; } else if (fd >= 0) { off_t end; /* XXX this is POSIX compliant, but doesn't work for CHAR files, * only BLOCK. There is a Linux specific ioctl() for getting * size of both CHAR / BLOCK devices we should check for in * configure */ end = lseek(fd, 0, SEEK_END); if (end == (off_t)-1) { virReportSystemError(errno, _("cannot seek to end of file '%s'"), target->path); return -1; } target->allocation = end; target->capacity = end; } 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 ((dh = opendir(pool->def->target.path)) == NULL) { 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 (dent->d_name[0] == '.') continue; if (virAsprintf(&stablepath, "%s/%s", pool->def->target.path, dent->d_name) == -1) { closedir(dh); return NULL; } if (virFileLinkPointsTo(stablepath, devpath)) { closedir(dh); return stablepath; } VIR_FREE(stablepath); } if (!direrr && loop && ++retry < 100) { usleep(100 * 1000); goto retry; } closedir(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(virStorageVolDefPtr vol, off_t size, int fd) { if (ftruncate(fd, 0) < 0) { virReportSystemError(errno, _("Failed to truncate volume with " "path '%s' to 0 bytes"), vol->target.path); return -1; } if (ftruncate(fd, size) < 0) { virReportSystemError(errno, _("Failed to truncate volume with " "path '%s' to %ju bytes"), vol->target.path, (uintmax_t)size); return -1; } return 0; } static int virStorageBackendWipeLocal(virStorageVolDefPtr vol, 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'"), vol->target.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, vol->target.path); goto cleanup; } remaining -= written; } if (fdatasync(fd) < 0) { virReportSystemError(errno, _("cannot sync data to volume with path '%s'"), vol->target.path); goto cleanup; } VIR_DEBUG("Wrote %llu bytes to volume with path '%s'", wipe_len, vol->target.path); ret = 0; cleanup: VIR_FREE(writebuf); return ret; } int virStorageBackendVolWipeLocal(virConnectPtr conn ATTRIBUTE_UNUSED, virStoragePoolObjPtr pool ATTRIBUTE_UNUSED, virStorageVolDefPtr vol, unsigned int algorithm, unsigned int flags) { int ret = -1, fd = -1; const char *alg_char = NULL; struct stat st; virCommandPtr cmd = NULL; 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) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("wiping for ploop volumes is not supported")); goto cleanup; } fd = open(vol->target.path, O_RDWR); if (fd == -1) { virReportSystemError(errno, _("Failed to open storage volume with path '%s'"), vol->target.path); goto cleanup; } if (fstat(fd, &st) == -1) { virReportSystemError(errno, _("Failed to stat storage volume with path '%s'"), vol->target.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; } if (algorithm != VIR_STORAGE_VOL_WIPE_ALG_ZERO) { cmd = virCommandNew(SCRUB); virCommandAddArgList(cmd, "-f", "-p", alg_char, vol->target.path, NULL); if (virCommandRun(cmd, NULL) < 0) goto cleanup; ret = 0; goto cleanup; } else { if (S_ISREG(st.st_mode) && st.st_blocks < (st.st_size / DEV_BSIZE)) { ret = virStorageBackendVolZeroSparseFileLocal(vol, st.st_size, fd); } else { ret = virStorageBackendWipeLocal(vol, fd, vol->target.allocation, st.st_blksize); } } cleanup: virCommandFree(cmd); VIR_FORCE_CLOSE(fd); return ret; } #ifdef GLUSTER_CLI int virStorageBackendFindGlusterPoolSources(const char *host, int pooltype, virStoragePoolSourceListPtr list) { 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; cmd = virCommandNewArgList(GLUSTER_CLI, "--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) { VIR_INFO("failed to query host '%s' for gluster volumes: %s", host, outbuf); ret = 0; goto cleanup; } if (!(doc = virXMLParseStringCtxt(outbuf, _("(gluster_cli_output)"), &ctxt))) goto cleanup; if ((nnodes = virXPathNodeSet("//volumes/volume", ctxt, &nodes)) <= 0) { VIR_INFO("no gluster volumes available on '%s'", host); ret = 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 = 0; cleanup: VIR_FREE(nodes); xmlXPathFreeContext(ctxt); xmlFreeDoc(doc); VIR_FREE(outbuf); virCommandFree(cmd); return ret; } #else /* #ifdef GLUSTER_CLI */ int virStorageBackendFindGlusterPoolSources(const char *host ATTRIBUTE_UNUSED, int pooltype ATTRIBUTE_UNUSED, virStoragePoolSourceListPtr list ATTRIBUTE_UNUSED) { VIR_INFO("gluster cli tool not installed"); return 0; } #endif /* #ifdef GLUSTER_CLI */