/* * storage_backend_fs.c: storage backend for FS and directory handling * * Copyright (C) 2007-2009 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, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * Author: Daniel P. Berrange */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "virterror_internal.h" #include "storage_backend_fs.h" #include "storage_conf.h" #include "util.h" #include "memory.h" #include "xml.h" enum lv_endian { LV_LITTLE_ENDIAN = 1, /* 1234 */ LV_BIG_ENDIAN /* 4321 */ }; enum { BACKING_STORE_OK, BACKING_STORE_INVALID, BACKING_STORE_ERROR, }; static int cowGetBackingStore(virConnectPtr, char **, const unsigned char *, size_t); static int qcowXGetBackingStore(virConnectPtr, char **, const unsigned char *, size_t); static int vmdk4GetBackingStore(virConnectPtr, char **, const unsigned char *, size_t); /* Either 'magic' or 'extension' *must* be provided */ struct FileTypeInfo { int type; /* One of the constants above */ const char *magic; /* Optional string of file magic * to check at head of file */ const char *extension; /* Optional file extension to check */ enum lv_endian endian; /* Endianness of file format */ int versionOffset; /* Byte offset from start of file * where we find version number, * -1 to skip version test */ int versionNumber; /* Version number to validate */ int sizeOffset; /* Byte offset from start of file * where we find capacity info, * -1 to use st_size as capacity */ int sizeBytes; /* Number of bytes for size field */ int sizeMultiplier; /* A scaling factor if size is not in bytes */ /* Store a COW base image path (possibly relative), * or NULL if there is no COW base image, to RES; * return BACKING_STORE_* */ int qcowCryptOffset; /* Byte offset from start of file * where to find encryption mode, * -1 if encryption is not used */ int (*getBackingStore)(virConnectPtr conn, char **res, const unsigned char *buf, size_t buf_size); }; struct FileTypeInfo const fileTypeInfo[] = { /* Bochs */ /* XXX Untested { VIR_STORAGE_VOL_FILE_BOCHS, "Bochs Virtual HD Image", NULL, LV_LITTLE_ENDIAN, 64, 0x20000, 32+16+16+4+4+4+4+4, 8, 1, -1, NULL },*/ /* CLoop */ /* XXX Untested { VIR_STORAGE_VOL_CLOOP, "#!/bin/sh\n#V2.0 Format\nmodprobe cloop file=$0 && mount -r -t iso9660 /dev/cloop $1\n", NULL, LV_LITTLE_ENDIAN, -1, 0, -1, 0, 0, -1, NULL }, */ /* Cow */ { VIR_STORAGE_VOL_FILE_COW, "OOOM", NULL, LV_BIG_ENDIAN, 4, 2, 4+4+1024+4, 8, 1, -1, cowGetBackingStore }, /* DMG */ /* XXX QEMU says there's no magic for dmg, but we should check... */ { VIR_STORAGE_VOL_FILE_DMG, NULL, ".dmg", 0, -1, 0, -1, 0, 0, -1, NULL }, /* XXX there's probably some magic for iso we can validate too... */ { VIR_STORAGE_VOL_FILE_ISO, NULL, ".iso", 0, -1, 0, -1, 0, 0, -1, NULL }, /* Parallels */ /* XXX Untested { VIR_STORAGE_VOL_FILE_PARALLELS, "WithoutFreeSpace", NULL, LV_LITTLE_ENDIAN, 16, 2, 16+4+4+4+4, 4, 512, -1, NULL }, */ /* QCow */ { VIR_STORAGE_VOL_FILE_QCOW, "QFI", NULL, LV_BIG_ENDIAN, 4, 1, 4+4+8+4+4, 8, 1, 4+4+8+4+4+8+1+1+2, qcowXGetBackingStore }, /* QCow 2 */ { VIR_STORAGE_VOL_FILE_QCOW2, "QFI", NULL, LV_BIG_ENDIAN, 4, 2, 4+4+8+4+4, 8, 1, 4+4+8+4+4+8, qcowXGetBackingStore }, /* VMDK 3 */ /* XXX Untested { VIR_STORAGE_VOL_FILE_VMDK, "COWD", NULL, LV_LITTLE_ENDIAN, 4, 1, 4+4+4, 4, 512, -1, NULL }, */ /* VMDK 4 */ { VIR_STORAGE_VOL_FILE_VMDK, "KDMV", NULL, LV_LITTLE_ENDIAN, 4, 1, 4+4+4, 8, 512, -1, vmdk4GetBackingStore }, /* Connectix / VirtualPC */ /* XXX Untested { VIR_STORAGE_VOL_FILE_VPC, "conectix", NULL, LV_BIG_ENDIAN, -1, 0, -1, 0, 0, -1, NULL}, */ }; #define VIR_FROM_THIS VIR_FROM_STORAGE static int cowGetBackingStore(virConnectPtr conn, char **res, const unsigned char *buf, size_t buf_size) { #define COW_FILENAME_MAXLEN 1024 *res = NULL; if (buf_size < 4+4+ COW_FILENAME_MAXLEN) return BACKING_STORE_INVALID; if (buf[4+4] == '\0') /* cow_header_v2.backing_file[0] */ return BACKING_STORE_OK; *res = strndup ((const char*)buf + 4+4, COW_FILENAME_MAXLEN); if (*res == NULL) { virReportOOMError(conn); return BACKING_STORE_ERROR; } return BACKING_STORE_OK; } static int qcowXGetBackingStore(virConnectPtr conn, char **res, const unsigned char *buf, size_t buf_size) { unsigned long long offset; unsigned long size; *res = NULL; if (buf_size < 4+4+8+4) return BACKING_STORE_INVALID; offset = (((unsigned long long)buf[4+4] << 56) | ((unsigned long long)buf[4+4+1] << 48) | ((unsigned long long)buf[4+4+2] << 40) | ((unsigned long long)buf[4+4+3] << 32) | ((unsigned long long)buf[4+4+4] << 24) | ((unsigned long long)buf[4+4+5] << 16) | ((unsigned long long)buf[4+4+6] << 8) | buf[4+4+7]); /* QCowHeader.backing_file_offset */ if (offset > buf_size) return BACKING_STORE_INVALID; size = ((buf[4+4+8] << 24) | (buf[4+4+8+1] << 16) | (buf[4+4+8+2] << 8) | buf[4+4+8+3]); /* QCowHeader.backing_file_size */ if (size == 0) return BACKING_STORE_OK; if (offset + size > buf_size || offset + size < offset) return BACKING_STORE_INVALID; if (size + 1 == 0) return BACKING_STORE_INVALID; if (VIR_ALLOC_N(*res, size + 1) < 0) { virReportOOMError(conn); return BACKING_STORE_ERROR; } memcpy(*res, buf + offset, size); (*res)[size] = '\0'; return BACKING_STORE_OK; } static int vmdk4GetBackingStore(virConnectPtr conn, char **res, const unsigned char *buf, size_t buf_size) { static const char prefix[] = "parentFileNameHint=\""; char desc[20*512 + 1], *start, *end; size_t len; *res = NULL; if (buf_size <= 0x200) return BACKING_STORE_INVALID; len = buf_size - 0x200; if (len > sizeof(desc) - 1) len = sizeof(desc) - 1; memcpy(desc, buf + 0x200, len); desc[len] = '\0'; start = strstr(desc, prefix); if (start == NULL) return BACKING_STORE_OK; start += strlen(prefix); end = strchr(start, '"'); if (end == NULL) return BACKING_STORE_INVALID; if (end == start) return BACKING_STORE_OK; *end = '\0'; *res = strdup(start); if (*res == NULL) { virReportOOMError(conn); return BACKING_STORE_ERROR; } return BACKING_STORE_OK; } /** * Return an absolute path corresponding to PATH, which is absolute or relative * to the directory containing BASE_FILE, or NULL on error */ static char *absolutePathFromBaseFile(const char *base_file, const char *path) { size_t base_size, path_size; char *res, *p; if (*path == '/') return strdup(path); base_size = strlen(base_file) + 1; path_size = strlen(path) + 1; if (VIR_ALLOC_N(res, base_size - 1 + path_size) < 0) return NULL; memcpy(res, base_file, base_size); p = strrchr(res, '/'); if (p != NULL) p++; else p = res; memcpy(p, path, path_size); if (VIR_REALLOC_N(res, (p + path_size) - res) < 0) { /* Ignore failure */ } return res; } /** * Probe the header of a file to determine what type of disk image * it is, and info about its capacity if available. */ static int virStorageBackendProbeTarget(virConnectPtr conn, virStorageVolTargetPtr target, char **backingStore, unsigned long long *allocation, unsigned long long *capacity, virStorageEncryptionPtr *encryption) { int fd; unsigned char head[20*512]; /* vmdk4GetBackingStore needs this much. */ int len, i, ret; if (backingStore) *backingStore = NULL; if (encryption) *encryption = NULL; if ((fd = open(target->path, O_RDONLY)) < 0) { virReportSystemError(conn, errno, _("cannot open volume '%s'"), target->path); return -1; } if ((ret = virStorageBackendUpdateVolTargetInfoFD(conn, target, fd, allocation, capacity)) < 0) { close(fd); return ret; /* Take care to propagate ret, it is not always -1 */ } if ((len = read(fd, head, sizeof(head))) < 0) { virReportSystemError(conn, errno, _("cannot read header '%s'"), target->path); close(fd); return -1; } close(fd); /* First check file magic */ for (i = 0 ; i < ARRAY_CARDINALITY(fileTypeInfo) ; i++) { int mlen; bool encrypted_qcow = false; if (fileTypeInfo[i].magic == NULL) continue; /* Validate magic data */ mlen = strlen(fileTypeInfo[i].magic); if (mlen > len) continue; if (memcmp(head, fileTypeInfo[i].magic, mlen) != 0) continue; /* Validate version number info */ if (fileTypeInfo[i].versionNumber != -1) { int version; if (fileTypeInfo[i].endian == LV_LITTLE_ENDIAN) { version = (head[fileTypeInfo[i].versionOffset+3] << 24) | (head[fileTypeInfo[i].versionOffset+2] << 16) | (head[fileTypeInfo[i].versionOffset+1] << 8) | head[fileTypeInfo[i].versionOffset]; } else { version = (head[fileTypeInfo[i].versionOffset] << 24) | (head[fileTypeInfo[i].versionOffset+1] << 16) | (head[fileTypeInfo[i].versionOffset+2] << 8) | head[fileTypeInfo[i].versionOffset+3]; } if (version != fileTypeInfo[i].versionNumber) continue; } /* Optionally extract capacity from file */ if (fileTypeInfo[i].sizeOffset != -1 && capacity) { if (fileTypeInfo[i].endian == LV_LITTLE_ENDIAN) { *capacity = ((unsigned long long)head[fileTypeInfo[i].sizeOffset+7] << 56) | ((unsigned long long)head[fileTypeInfo[i].sizeOffset+6] << 48) | ((unsigned long long)head[fileTypeInfo[i].sizeOffset+5] << 40) | ((unsigned long long)head[fileTypeInfo[i].sizeOffset+4] << 32) | ((unsigned long long)head[fileTypeInfo[i].sizeOffset+3] << 24) | ((unsigned long long)head[fileTypeInfo[i].sizeOffset+2] << 16) | ((unsigned long long)head[fileTypeInfo[i].sizeOffset+1] << 8) | ((unsigned long long)head[fileTypeInfo[i].sizeOffset]); } else { *capacity = ((unsigned long long)head[fileTypeInfo[i].sizeOffset] << 56) | ((unsigned long long)head[fileTypeInfo[i].sizeOffset+1] << 48) | ((unsigned long long)head[fileTypeInfo[i].sizeOffset+2] << 40) | ((unsigned long long)head[fileTypeInfo[i].sizeOffset+3] << 32) | ((unsigned long long)head[fileTypeInfo[i].sizeOffset+4] << 24) | ((unsigned long long)head[fileTypeInfo[i].sizeOffset+5] << 16) | ((unsigned long long)head[fileTypeInfo[i].sizeOffset+6] << 8) | ((unsigned long long)head[fileTypeInfo[i].sizeOffset+7]); } /* Avoid unlikely, but theoretically possible overflow */ if (*capacity > (ULLONG_MAX / fileTypeInfo[i].sizeMultiplier)) continue; *capacity *= fileTypeInfo[i].sizeMultiplier; } if (fileTypeInfo[i].qcowCryptOffset != -1) { int crypt_format; crypt_format = (head[fileTypeInfo[i].qcowCryptOffset] << 24) | (head[fileTypeInfo[i].qcowCryptOffset+1] << 16) | (head[fileTypeInfo[i].qcowCryptOffset+2] << 8) | head[fileTypeInfo[i].qcowCryptOffset+3]; encrypted_qcow = crypt_format != 0; } /* Validation passed, we know the file format now */ target->format = fileTypeInfo[i].type; if (fileTypeInfo[i].getBackingStore != NULL && backingStore) { char *base; switch (fileTypeInfo[i].getBackingStore(conn, &base, head, len)) { case BACKING_STORE_OK: break; case BACKING_STORE_INVALID: continue; case BACKING_STORE_ERROR: return -1; } if (base != NULL) { *backingStore = absolutePathFromBaseFile(target->path, base); VIR_FREE(base); if (*backingStore == NULL) { virReportOOMError(conn); return -1; } } } if (encryption != NULL && encrypted_qcow) { virStorageEncryptionPtr enc; if (VIR_ALLOC(enc) < 0) { virReportOOMError(conn); if (backingStore) VIR_FREE(*backingStore); return -1; } enc->format = VIR_STORAGE_ENCRYPTION_FORMAT_QCOW; *encryption = enc; /* XXX ideally we'd fill in secret UUID here * but we cannot guarentee 'conn' is non-NULL * at this point in time :-( So we only fill * in secrets when someone first queries a vol */ } return 0; } /* No magic, so check file extension */ for (i = 0 ; i < ARRAY_CARDINALITY(fileTypeInfo) ; i++) { if (fileTypeInfo[i].extension == NULL) continue; if (!virFileHasSuffix(target->path, fileTypeInfo[i].extension)) continue; target->format = fileTypeInfo[i].type; return 0; } /* All fails, so call it a raw file */ target->format = VIR_STORAGE_VOL_FILE_RAW; return 0; } #if WITH_STORAGE_FS #include struct _virNetfsDiscoverState { const char *host; virStoragePoolSourceList list; }; typedef struct _virNetfsDiscoverState virNetfsDiscoverState; static int virStorageBackendFileSystemNetFindPoolSourcesFunc(virConnectPtr conn ATTRIBUTE_UNUSED, virStoragePoolObjPtr pool ATTRIBUTE_UNUSED, char **const groups, void *data) { virNetfsDiscoverState *state = data; const char *name, *path; virStoragePoolSource *src; path = groups[0]; name = strrchr(path, '/'); if (name == NULL) { virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR, _("invalid netfs path (no /): %s"), path); return -1; } name += 1; if (*name == '\0') { virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR, _("invalid netfs path (ends in /): %s"), path); return -1; } if (VIR_REALLOC_N(state->list.sources, state->list.nsources+1) < 0) { virReportOOMError(conn); return -1; } memset(state->list.sources + state->list.nsources, 0, sizeof(*state->list.sources)); src = state->list.sources + state->list.nsources++; if (!(src->host.name = strdup(state->host)) || !(src->dir = strdup(path))) return -1; src->format = VIR_STORAGE_POOL_NETFS_NFS; return 0; } static char * virStorageBackendFileSystemNetFindPoolSources(virConnectPtr conn, const char *srcSpec, unsigned int flags ATTRIBUTE_UNUSED) { /* * # showmount --no-headers -e HOSTNAME * /tmp * * /A dir demo1.foo.bar,demo2.foo.bar * * Extract directory name (including possible interior spaces ...). */ const char *regexes[] = { "^(/.*\\S) +\\S+$" }; int vars[] = { 1 }; xmlDocPtr doc = NULL; xmlXPathContextPtr xpath_ctxt = NULL; virNetfsDiscoverState state = { .host = NULL, .list = { .type = VIR_STORAGE_POOL_NETFS, .nsources = 0, .sources = NULL } }; const char *prog[] = { SHOWMOUNT, "--no-headers", "--exports", NULL, NULL }; int exitstatus; char *retval = NULL; unsigned int i; doc = xmlReadDoc((const xmlChar *)srcSpec, "srcSpec.xml", NULL, XML_PARSE_NOENT | XML_PARSE_NONET | XML_PARSE_NOERROR | XML_PARSE_NOWARNING); if (doc == NULL) { virStorageReportError(conn, VIR_ERR_XML_ERROR, "%s", _("bad spec")); goto cleanup; } xpath_ctxt = xmlXPathNewContext(doc); if (xpath_ctxt == NULL) { virReportOOMError(conn); goto cleanup; } state.host = virXPathString(conn, "string(/source/host/@name)", xpath_ctxt); if (!state.host || !state.host[0]) { virStorageReportError(conn, VIR_ERR_XML_ERROR, "%s", _("missing in spec")); goto cleanup; } prog[3] = state.host; if (virStorageBackendRunProgRegex(conn, NULL, prog, 1, regexes, vars, virStorageBackendFileSystemNetFindPoolSourcesFunc, &state, &exitstatus) < 0) goto cleanup; retval = virStoragePoolSourceListFormat(conn, &state.list); if (retval == NULL) { virReportOOMError(conn); goto cleanup; } cleanup: for (i = 0; i < state.list.nsources; i++) virStoragePoolSourceFree(&state.list.sources[i]); VIR_FREE(state.list.sources); VIR_FREE(state.host); xmlFreeDoc(doc); xmlXPathFreeContext(xpath_ctxt); return retval; } /** * @conn connection to report errors against * @pool storage pool to check for status * * Determine if a storage pool is already mounted * * Return 0 if not mounted, 1 if mounted, -1 on error */ static int virStorageBackendFileSystemIsMounted(virConnectPtr conn, virStoragePoolObjPtr pool) { FILE *mtab; struct mntent ent; char buf[1024]; if ((mtab = fopen(_PATH_MOUNTED, "r")) == NULL) { virReportSystemError(conn, errno, _("cannot read mount list '%s'"), _PATH_MOUNTED); return -1; } while ((getmntent_r(mtab, &ent, buf, sizeof(buf))) != NULL) { if (STREQ(ent.mnt_dir, pool->def->target.path)) { fclose(mtab); return 1; } } fclose(mtab); return 0; } /** * @conn connection to report errors against * @pool storage pool to mount * * Ensure that a FS storage pool is mounted on its target location. * If already mounted, this is a no-op * * Returns 0 if successfully mounted, -1 on error */ static int virStorageBackendFileSystemMount(virConnectPtr conn, virStoragePoolObjPtr pool) { char *src; char *options; const char **mntargv; /* 'mount -t auto' doesn't seem to auto determine nfs (or cifs), * while plain 'mount' does. We have to craft separate argvs to * accommodate this */ int netauto = (pool->def->type == VIR_STORAGE_POOL_NETFS && pool->def->source.format == VIR_STORAGE_POOL_NETFS_AUTO); int glusterfs = (pool->def->type == VIR_STORAGE_POOL_NETFS && pool->def->source.format == VIR_STORAGE_POOL_NETFS_GLUSTERFS); int option_index; int source_index; const char *netfs_auto_argv[] = { MOUNT, NULL, /* source path */ pool->def->target.path, NULL, }; const char *fs_argv[] = { MOUNT, "-t", pool->def->type == VIR_STORAGE_POOL_FS ? virStoragePoolFormatFileSystemTypeToString(pool->def->source.format) : virStoragePoolFormatFileSystemNetTypeToString(pool->def->source.format), NULL, /* Fill in shortly - careful not to add extra fields before this */ pool->def->target.path, NULL, }; const char *glusterfs_argv[] = { MOUNT, "-t", pool->def->type == VIR_STORAGE_POOL_FS ? virStoragePoolFormatFileSystemTypeToString(pool->def->source.format) : virStoragePoolFormatFileSystemNetTypeToString(pool->def->source.format), NULL, "-o", NULL, pool->def->target.path, NULL, }; if (netauto) { mntargv = netfs_auto_argv; source_index = 1; } else if (glusterfs) { mntargv = glusterfs_argv; source_index = 3; option_index = 5; } else { mntargv = fs_argv; source_index = 3; } int ret; if (pool->def->type == VIR_STORAGE_POOL_NETFS) { if (pool->def->source.host.name == NULL) { virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR, "%s", _("missing source host")); return -1; } if (pool->def->source.dir == NULL) { virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR, "%s", _("missing source path")); return -1; } } else { if (pool->def->source.ndevice != 1) { virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR, "%s", _("missing source device")); return -1; } } /* Short-circuit if already mounted */ if ((ret = virStorageBackendFileSystemIsMounted(conn, pool)) != 0) { if (ret < 0) return -1; else return 0; } if (pool->def->type == VIR_STORAGE_POOL_NETFS) { if (pool->def->source.format == VIR_STORAGE_POOL_NETFS_GLUSTERFS) { if (virAsprintf(&options, "direct-io-mode=1") == -1) { virReportOOMError(conn); return -1; } } if (virAsprintf(&src, "%s:%s", pool->def->source.host.name, pool->def->source.dir) == -1) { virReportOOMError(conn); return -1; } } else { if ((src = strdup(pool->def->source.devices[0].path)) == NULL) { virReportOOMError(conn); return -1; } } mntargv[source_index] = src; if (glusterfs) { mntargv[option_index] = options; } if (virRun(conn, mntargv, NULL) < 0) { VIR_FREE(src); return -1; } VIR_FREE(src); return 0; } /** * @conn connection to report errors against * @pool storage pool to unmount * * Ensure that a FS storage pool is not mounted on its target location. * If already unmounted, this is a no-op * * Returns 0 if successfully unmounted, -1 on error */ static int virStorageBackendFileSystemUnmount(virConnectPtr conn, virStoragePoolObjPtr pool) { const char *mntargv[3]; int ret; if (pool->def->type == VIR_STORAGE_POOL_NETFS) { if (pool->def->source.host.name == NULL) { virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR, "%s", _("missing source host")); return -1; } if (pool->def->source.dir == NULL) { virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR, "%s", _("missing source dir")); return -1; } } else { if (pool->def->source.ndevice != 1) { virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR, "%s", _("missing source device")); return -1; } } /* Short-circuit if already unmounted */ if ((ret = virStorageBackendFileSystemIsMounted(conn, pool)) != 1) { if (ret < 0) return -1; else return 0; } mntargv[0] = UMOUNT; mntargv[1] = pool->def->target.path; mntargv[2] = NULL; if (virRun(conn, mntargv, NULL) < 0) { return -1; } return 0; } #endif /* WITH_STORAGE_FS */ /** * @conn connection to report errors against * @pool storage pool to start * * Starts a directory or FS based storage pool. * * - If it is a FS based pool, mounts the unlying source device on the pool * * Returns 0 on success, -1 on error */ #if WITH_STORAGE_FS static int virStorageBackendFileSystemStart(virConnectPtr conn, virStoragePoolObjPtr pool) { if (pool->def->type != VIR_STORAGE_POOL_DIR && virStorageBackendFileSystemMount(conn, pool) < 0) return -1; return 0; } #endif /* WITH_STORAGE_FS */ /** * @conn connection to report errors against * @pool storage pool to build * * Build a directory or FS based storage pool. * * - If it is a FS based pool, mounts the unlying source device on the pool * * Returns 0 on success, -1 on error */ static int virStorageBackendFileSystemBuild(virConnectPtr conn, virStoragePoolObjPtr pool, unsigned int flags ATTRIBUTE_UNUSED) { int err; if ((err = virFileMakePath(pool->def->target.path)) < 0) { virReportSystemError(conn, err, _("cannot create path '%s'"), pool->def->target.path); return -1; } return 0; } /** * Iterate over the pool's directory and enumerate all disk images * within it. This is non-recursive. */ static int virStorageBackendFileSystemRefresh(virConnectPtr conn, virStoragePoolObjPtr pool) { DIR *dir; struct dirent *ent; struct statvfs sb; virStorageVolDefPtr vol = NULL; if (!(dir = opendir(pool->def->target.path))) { virReportSystemError(conn, errno, _("cannot open path '%s'"), pool->def->target.path); goto cleanup; } while ((ent = readdir(dir)) != NULL) { int ret; char *backingStore; if (VIR_ALLOC(vol) < 0) goto no_memory; if ((vol->name = strdup(ent->d_name)) == NULL) goto no_memory; vol->type = VIR_STORAGE_VOL_FILE; vol->target.format = VIR_STORAGE_VOL_FILE_RAW; /* Real value is filled in during probe */ if (virAsprintf(&vol->target.path, "%s/%s", pool->def->target.path, vol->name) == -1) goto no_memory; if ((vol->key = strdup(vol->target.path)) == NULL) goto no_memory; if ((ret = virStorageBackendProbeTarget(conn, &vol->target, &backingStore, &vol->allocation, &vol->capacity, &vol->target.encryption) < 0)) { if (ret == -1) goto cleanup; else { /* Silently ignore non-regular files, * eg '.' '..', 'lost+found' */ virStorageVolDefFree(vol); vol = NULL; continue; } } if (backingStore != NULL) { if (vol->target.format == VIR_STORAGE_VOL_FILE_QCOW2 && STRPREFIX("fmt:", backingStore)) { char *fmtstr = backingStore + 4; char *path = strchr(fmtstr, ':'); if (!path) { VIR_FREE(backingStore); } else { *path = '\0'; if ((vol->backingStore.format = virStorageVolFormatFileSystemTypeFromString(fmtstr)) < 0) { VIR_FREE(backingStore); } else { memmove(backingStore, path, strlen(path) + 1); vol->backingStore.path = backingStore; if (virStorageBackendUpdateVolTargetInfo(conn, &vol->backingStore, NULL, NULL) < 0) VIR_FREE(vol->backingStore); } } } else { vol->backingStore.path = backingStore; if ((ret = virStorageBackendProbeTarget(conn, &vol->backingStore, NULL, NULL, NULL, NULL)) < 0) { if (ret == -1) goto cleanup; else { /* Silently ignore non-regular files, * eg '.' '..', 'lost+found' */ VIR_FREE(vol->backingStore); } } } } if (VIR_REALLOC_N(pool->volumes.objs, pool->volumes.count+1) < 0) goto no_memory; pool->volumes.objs[pool->volumes.count++] = vol; vol = NULL; } closedir(dir); if (statvfs(pool->def->target.path, &sb) < 0) { virReportSystemError(conn, errno, _("cannot statvfs path '%s'"), pool->def->target.path); return -1; } pool->def->capacity = ((unsigned long long)sb.f_frsize * (unsigned long long)sb.f_blocks); pool->def->available = ((unsigned long long)sb.f_bfree * (unsigned long long)sb.f_bsize); pool->def->allocation = pool->def->capacity - pool->def->available; return 0; no_memory: virReportOOMError(conn); /* fallthrough */ cleanup: if (dir) closedir(dir); virStorageVolDefFree(vol); virStoragePoolObjClearVols(pool); return -1; } /** * @conn connection to report errors against * @pool storage pool to start * * Stops a directory or FS based storage pool. * * - If it is a FS based pool, unmounts the unlying source device on the pool * - Releases all cached data about volumes */ #if WITH_STORAGE_FS static int virStorageBackendFileSystemStop(virConnectPtr conn, virStoragePoolObjPtr pool) { if (pool->def->type != VIR_STORAGE_POOL_DIR && virStorageBackendFileSystemUnmount(conn, pool) < 0) return -1; return 0; } #endif /* WITH_STORAGE_FS */ /** * @conn connection to report errors against * @pool storage pool to build * * Build a directory or FS based storage pool. * * - If it is a FS based pool, mounts the unlying source device on the pool * * Returns 0 on success, -1 on error */ static int virStorageBackendFileSystemDelete(virConnectPtr conn, virStoragePoolObjPtr pool, unsigned int flags ATTRIBUTE_UNUSED) { /* XXX delete all vols first ? */ if (unlink(pool->def->target.path) < 0) { virReportSystemError(conn, errno, _("cannot unlink path '%s'"), pool->def->target.path); return -1; } return 0; } /** * Set up a volume definition to be added to a pool's volume list, but * don't do any file creation or allocation. By separating the two processes, * we allow allocation progress reporting (by polling the volume's 'info' * function), and can drop the parent pool lock during the (slow) allocation. */ static int virStorageBackendFileSystemVolCreate(virConnectPtr conn, virStoragePoolObjPtr pool, virStorageVolDefPtr vol) { vol->type = VIR_STORAGE_VOL_FILE; VIR_FREE(vol->target.path); if (virAsprintf(&vol->target.path, "%s/%s", pool->def->target.path, vol->name) == -1) { virReportOOMError(conn); return -1; } VIR_FREE(vol->key); vol->key = strdup(vol->target.path); if (vol->key == NULL) { virReportOOMError(conn); return -1; } return 0; } static int createFileDir(virConnectPtr conn, virStorageVolDefPtr vol, virStorageVolDefPtr inputvol, unsigned int flags ATTRIBUTE_UNUSED) { if (inputvol) { virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR, "%s", _("cannot copy from volume to a directory volume")); return -1; } if (mkdir(vol->target.path, vol->target.perms.mode) < 0) { virReportSystemError(conn, errno, _("cannot create path '%s'"), vol->target.path); return -1; } return 0; } static int _virStorageBackendFileSystemVolBuild(virConnectPtr conn, virStorageVolDefPtr vol, virStorageVolDefPtr inputvol) { int fd; virStorageBackendBuildVolFrom create_func; int tool_type; if (inputvol) { if (vol->target.encryption != NULL) { virStorageReportError(conn, VIR_ERR_NO_SUPPORT, "%s", _("storage pool does not support " "building encrypted volumes from " "other volumes")); return -1; } create_func = virStorageBackendGetBuildVolFromFunction(conn, vol, inputvol); if (!create_func) return -1; } else if (vol->target.format == VIR_STORAGE_VOL_FILE_RAW) { create_func = virStorageBackendCreateRaw; } else if (vol->target.format == VIR_STORAGE_VOL_FILE_DIR) { create_func = createFileDir; } else if ((tool_type = virStorageBackendFindFSImageTool(NULL)) != -1) { create_func = virStorageBackendFSImageToolTypeToFunc(conn, tool_type); if (!create_func) return -1; } else { virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR, "%s", _("creation of non-raw images " "is not supported without qemu-img")); return -1; } if (create_func(conn, vol, inputvol, 0) < 0) return -1; if ((fd = open(vol->target.path, O_RDONLY)) < 0) { virReportSystemError(conn, errno, _("cannot read path '%s'"), vol->target.path); return -1; } /* We can only chown/grp if root */ if (getuid() == 0) { if (fchown(fd, vol->target.perms.uid, vol->target.perms.gid) < 0) { virReportSystemError(conn, errno, _("cannot set file owner '%s'"), vol->target.path); close(fd); return -1; } } if (fchmod(fd, vol->target.perms.mode) < 0) { virReportSystemError(conn, errno, _("cannot set file mode '%s'"), vol->target.path); close(fd); return -1; } /* Refresh allocation / permissions info, but not capacity */ if (virStorageBackendUpdateVolTargetInfoFD(conn, &vol->target, fd, &vol->allocation, NULL) < 0) { close(fd); return -1; } if (close(fd) < 0) { virReportSystemError(conn, errno, _("cannot close file '%s'"), vol->target.path); return -1; } return 0; } /** * Allocate a new file as a volume. This is either done directly * for raw/sparse files, or by calling qemu-img/qcow-create for * special kinds of files */ static int virStorageBackendFileSystemVolBuild(virConnectPtr conn, virStorageVolDefPtr vol) { return _virStorageBackendFileSystemVolBuild(conn, vol, NULL); } /* * Create a storage vol using 'inputvol' as input */ static int virStorageBackendFileSystemVolBuildFrom(virConnectPtr conn, virStorageVolDefPtr vol, virStorageVolDefPtr inputvol, unsigned int flags ATTRIBUTE_UNUSED) { return _virStorageBackendFileSystemVolBuild(conn, vol, inputvol); } /** * Remove a volume - just unlinks for now */ static int virStorageBackendFileSystemVolDelete(virConnectPtr conn, virStoragePoolObjPtr pool ATTRIBUTE_UNUSED, virStorageVolDefPtr vol, unsigned int flags ATTRIBUTE_UNUSED) { if (unlink(vol->target.path) < 0) { /* Silently ignore failures where the vol has already gone away */ if (errno != ENOENT) { virReportSystemError(conn, errno, _("cannot unlink file '%s'"), vol->target.path); return -1; } } return 0; } /** * Update info about a volume's capacity/allocation */ static int virStorageBackendFileSystemVolRefresh(virConnectPtr conn, virStoragePoolObjPtr pool ATTRIBUTE_UNUSED, virStorageVolDefPtr vol) { int ret; /* Refresh allocation / permissions info in case its changed */ ret = virStorageBackendUpdateVolInfo(conn, vol, 0); if (ret < 0) return ret; /* Load any secrets if posible */ if (vol->target.encryption && vol->target.encryption->format == VIR_STORAGE_ENCRYPTION_FORMAT_QCOW && vol->target.encryption->nsecrets == 0) { virSecretPtr sec; virStorageEncryptionSecretPtr encsec = NULL; sec = virSecretLookupByUsage(conn, VIR_SECRET_USAGE_TYPE_VOLUME, vol->target.path); if (sec) { if (VIR_ALLOC_N(vol->target.encryption->secrets, 1) < 0 || VIR_ALLOC(encsec) < 0) { VIR_FREE(vol->target.encryption->secrets); virReportOOMError(conn); virSecretFree(sec); return -1; } vol->target.encryption->nsecrets = 1; vol->target.encryption->secrets[0] = encsec; encsec->type = VIR_STORAGE_ENCRYPTION_SECRET_TYPE_PASSPHRASE; virSecretGetUUID(sec, encsec->uuid); virSecretFree(sec); } } return 0; } virStorageBackend virStorageBackendDirectory = { .type = VIR_STORAGE_POOL_DIR, .buildPool = virStorageBackendFileSystemBuild, .refreshPool = virStorageBackendFileSystemRefresh, .deletePool = virStorageBackendFileSystemDelete, .buildVol = virStorageBackendFileSystemVolBuild, .buildVolFrom = virStorageBackendFileSystemVolBuildFrom, .createVol = virStorageBackendFileSystemVolCreate, .refreshVol = virStorageBackendFileSystemVolRefresh, .deleteVol = virStorageBackendFileSystemVolDelete, }; #if WITH_STORAGE_FS virStorageBackend virStorageBackendFileSystem = { .type = VIR_STORAGE_POOL_FS, .buildPool = virStorageBackendFileSystemBuild, .startPool = virStorageBackendFileSystemStart, .refreshPool = virStorageBackendFileSystemRefresh, .stopPool = virStorageBackendFileSystemStop, .deletePool = virStorageBackendFileSystemDelete, .buildVol = virStorageBackendFileSystemVolBuild, .buildVolFrom = virStorageBackendFileSystemVolBuildFrom, .createVol = virStorageBackendFileSystemVolCreate, .refreshVol = virStorageBackendFileSystemVolRefresh, .deleteVol = virStorageBackendFileSystemVolDelete, }; virStorageBackend virStorageBackendNetFileSystem = { .type = VIR_STORAGE_POOL_NETFS, .buildPool = virStorageBackendFileSystemBuild, .startPool = virStorageBackendFileSystemStart, .findPoolSources = virStorageBackendFileSystemNetFindPoolSources, .refreshPool = virStorageBackendFileSystemRefresh, .stopPool = virStorageBackendFileSystemStop, .deletePool = virStorageBackendFileSystemDelete, .buildVol = virStorageBackendFileSystemVolBuild, .buildVolFrom = virStorageBackendFileSystemVolBuildFrom, .createVol = virStorageBackendFileSystemVolCreate, .refreshVol = virStorageBackendFileSystemVolRefresh, .deleteVol = virStorageBackendFileSystemVolDelete, }; #endif /* WITH_STORAGE_FS */