/* * storage_source.c: Storage source object accessors to real storage * * 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 * . */ #include #include #include #include #include #include "virerror.h" #include "storage_source.h" #include "storage_backend.h" #include "viralloc.h" #include "virlog.h" #include "virstring.h" #include "virhash.h" #define VIR_FROM_THIS VIR_FROM_STORAGE VIR_LOG_INIT("storage.storage_source"); static bool virStorageFileIsInitialized(const virStorageSource *src) { return src && src->drv; } static virStorageFileBackendPtr virStorageFileGetBackendForSupportCheck(const virStorageSource *src) { int actualType; if (!src) return NULL; if (src->drv) return src->drv->backend; actualType = virStorageSourceGetActualType(src); return virStorageFileBackendForTypeInternal(actualType, src->protocol, false); } static bool virStorageFileSupportsBackingChainTraversal(virStorageSourcePtr src) { virStorageFileBackendPtr backend; if (!(backend = virStorageFileGetBackendForSupportCheck(src))) return false; return backend->storageFileGetUniqueIdentifier && backend->storageFileRead && backend->storageFileAccess; } /** * virStorageFileSupportsSecurityDriver: * * @src: a storage file structure * * Check if a storage file supports operations needed by the security * driver to perform labelling */ bool virStorageFileSupportsSecurityDriver(const virStorageSource *src) { virStorageFileBackendPtr backend; if (!(backend = virStorageFileGetBackendForSupportCheck(src))) return false; return !!backend->storageFileChown; } /** * virStorageFileSupportsAccess: * * @src: a storage file structure * * Check if a storage file supports checking if the storage source is accessible * for the given vm. */ bool virStorageFileSupportsAccess(const virStorageSource *src) { virStorageFileBackendPtr backend; if (!(backend = virStorageFileGetBackendForSupportCheck(src))) return false; return !!backend->storageFileAccess; } void virStorageFileDeinit(virStorageSourcePtr src) { if (!virStorageFileIsInitialized(src)) return; if (src->drv->backend && src->drv->backend->backendDeinit) src->drv->backend->backendDeinit(src); VIR_FREE(src->drv); } /** * virStorageFileInitAs: * * @src: storage source definition * @uid: uid used to access the file, or -1 for current uid * @gid: gid used to access the file, or -1 for current gid * * Initialize a storage source to be used with storage driver. Use the provided * uid and gid if possible for the operations. * * Returns 0 if the storage file was successfully initialized, -1 if the * initialization failed. Libvirt error is reported. */ int virStorageFileInitAs(virStorageSourcePtr src, uid_t uid, gid_t gid) { int actualType = virStorageSourceGetActualType(src); if (VIR_ALLOC(src->drv) < 0) return -1; if (uid == (uid_t) -1) src->drv->uid = geteuid(); else src->drv->uid = uid; if (gid == (gid_t) -1) src->drv->gid = getegid(); else src->drv->gid = gid; if (!(src->drv->backend = virStorageFileBackendForType(actualType, src->protocol))) goto error; if (src->drv->backend->backendInit && src->drv->backend->backendInit(src) < 0) goto error; return 0; error: VIR_FREE(src->drv); return -1; } /** * virStorageFileInit: * * See virStorageFileInitAs. The file is initialized to be accessed by the * current user. */ int virStorageFileInit(virStorageSourcePtr src) { return virStorageFileInitAs(src, -1, -1); } /** * virStorageFileCreate: Creates an empty storage file via storage driver * * @src: file structure pointing to the file * * Returns 0 on success, -2 if the function isn't supported by the backend, * -1 on other failure. Errno is set in case of failure. */ int virStorageFileCreate(virStorageSourcePtr src) { int ret; if (!virStorageFileIsInitialized(src) || !src->drv->backend->storageFileCreate) { errno = ENOSYS; return -2; } ret = src->drv->backend->storageFileCreate(src); VIR_DEBUG("created storage file %p: ret=%d, errno=%d", src, ret, errno); return ret; } /** * virStorageFileUnlink: Unlink storage file via storage driver * * @src: file structure pointing to the file * * Unlinks the file described by the @file structure. * * Returns 0 on success, -2 if the function isn't supported by the backend, * -1 on other failure. Errno is set in case of failure. */ int virStorageFileUnlink(virStorageSourcePtr src) { int ret; if (!virStorageFileIsInitialized(src) || !src->drv->backend->storageFileUnlink) { errno = ENOSYS; return -2; } ret = src->drv->backend->storageFileUnlink(src); VIR_DEBUG("unlinked storage file %p: ret=%d, errno=%d", src, ret, errno); return ret; } /** * virStorageFileStat: returns stat struct of a file via storage driver * * @src: file structure pointing to the file * @stat: stat structure to return data * * Returns 0 on success, -2 if the function isn't supported by the backend, * -1 on other failure. Errno is set in case of failure. */ int virStorageFileStat(virStorageSourcePtr src, struct stat *st) { int ret; if (!virStorageFileIsInitialized(src) || !src->drv->backend->storageFileStat) { errno = ENOSYS; return -2; } ret = src->drv->backend->storageFileStat(src, st); VIR_DEBUG("stat of storage file %p: ret=%d, errno=%d", src, ret, errno); return ret; } /** * virStorageFileRead: read bytes from a file into a buffer * * @src: file structure pointing to the file * @offset: number of bytes to skip in the storage file * @len: maximum number of bytes read from the storage file * @buf: buffer to read the data into. (buffer shall be freed by caller) * * Returns the count of bytes read on success and -1 on failure, -2 if the * function isn't supported by the backend. * Libvirt error is reported on failure. */ ssize_t virStorageFileRead(virStorageSourcePtr src, size_t offset, size_t len, char **buf) { ssize_t ret; if (!virStorageFileIsInitialized(src)) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("storage file backend not initialized")); return -1; } if (!src->drv->backend->storageFileRead) { virReportError(VIR_ERR_INTERNAL_ERROR, _("storage file reading is not supported for " "storage type %s (protocol: %s)"), virStorageTypeToString(src->type), virStorageNetProtocolTypeToString(src->protocol)); return -2; } ret = src->drv->backend->storageFileRead(src, offset, len, buf); VIR_DEBUG("read '%zd' bytes from storage '%p' starting at offset '%zu'", ret, src, offset); return ret; } /* * virStorageFileGetUniqueIdentifier: Get a unique string describing the volume * * @src: file structure pointing to the file * * Returns a string uniquely describing a single volume (canonical path). * The string shall not be freed and is valid until the storage file is * deinitialized. Returns NULL on error and sets a libvirt error code */ const char * virStorageFileGetUniqueIdentifier(virStorageSourcePtr src) { if (!virStorageFileIsInitialized(src)) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("storage file backend not initialized")); return NULL; } if (!src->drv->backend->storageFileGetUniqueIdentifier) { virReportError(VIR_ERR_INTERNAL_ERROR, _("unique storage file identifier not implemented for " "storage type %s (protocol: %s)'"), virStorageTypeToString(src->type), virStorageNetProtocolTypeToString(src->protocol)); return NULL; } return src->drv->backend->storageFileGetUniqueIdentifier(src); } /** * virStorageFileAccess: Check accessibility of a storage file * * @src: storage file to check access permissions * @mode: accessibility check options (see man 2 access) * * Returns 0 on success, -1 on error and sets errno. No libvirt * error is reported. Returns -2 if the operation isn't supported * by libvirt storage backend. */ int virStorageFileAccess(virStorageSourcePtr src, int mode) { if (!virStorageFileIsInitialized(src) || !src->drv->backend->storageFileAccess) { errno = ENOSYS; return -2; } return src->drv->backend->storageFileAccess(src, mode); } /** * virStorageFileChown: Change owner of a storage file * * @src: storage file to change owner of * @uid: new owner id * @gid: new group id * * Returns 0 on success, -1 on error and sets errno. No libvirt * error is reported. Returns -2 if the operation isn't supported * by libvirt storage backend. */ int virStorageFileChown(const virStorageSource *src, uid_t uid, gid_t gid) { if (!virStorageFileIsInitialized(src) || !src->drv->backend->storageFileChown) { errno = ENOSYS; return -2; } VIR_DEBUG("chown of storage file %p to %u:%u", src, (unsigned int)uid, (unsigned int)gid); return src->drv->backend->storageFileChown(src, uid, gid); } /** * virStorageFileReportBrokenChain: * * @errcode: errno when accessing @src * @src: inaccessible file in the backing chain of @parent * @parent: root virStorageSource being checked * * Reports the correct error message if @src is missing in the backing chain * for @parent. */ void virStorageFileReportBrokenChain(int errcode, virStorageSourcePtr src, virStorageSourcePtr parent) { unsigned int access_user = src->drv->uid; unsigned int access_group = src->drv->gid; if (src == parent) { virReportSystemError(errcode, _("Cannot access storage file '%s' " "(as uid:%u, gid:%u)"), src->path, access_user, access_group); } else { virReportSystemError(errcode, _("Cannot access backing file '%s' " "of storage file '%s' (as uid:%u, gid:%u)"), src->path, parent->path, access_user, access_group); } } /* Recursive workhorse for virStorageFileGetMetadata. */ static int virStorageFileGetMetadataRecurse(virStorageSourcePtr src, virStorageSourcePtr parent, uid_t uid, gid_t gid, bool allow_probe, bool report_broken, virHashTablePtr cycle, unsigned int depth) { int ret = -1; const char *uniqueName; char *buf = NULL; ssize_t headerLen; virStorageSourcePtr backingStore = NULL; int backingFormat; VIR_DEBUG("path=%s format=%d uid=%u gid=%u probe=%d", src->path, src->format, (unsigned int)uid, (unsigned int)gid, allow_probe); /* exit if we can't load information about the current image */ if (!virStorageFileSupportsBackingChainTraversal(src)) return 0; if (virStorageFileInitAs(src, uid, gid) < 0) return -1; if (virStorageFileAccess(src, F_OK) < 0) { virStorageFileReportBrokenChain(errno, src, parent); goto cleanup; } if (!(uniqueName = virStorageFileGetUniqueIdentifier(src))) goto cleanup; if (virHashLookup(cycle, uniqueName)) { virReportError(VIR_ERR_INTERNAL_ERROR, _("backing store for %s (%s) is self-referential"), src->path, uniqueName); goto cleanup; } if (virHashAddEntry(cycle, uniqueName, (void *)1) < 0) goto cleanup; if ((headerLen = virStorageFileRead(src, 0, VIR_STORAGE_MAX_HEADER, &buf)) < 0) goto cleanup; if (virStorageFileGetMetadataInternal(src, buf, headerLen, &backingFormat) < 0) goto cleanup; if (src->backingStoreRaw) { if (!(backingStore = virStorageSourceNewFromBacking(src))) goto cleanup; if (backingFormat == VIR_STORAGE_FILE_AUTO && !allow_probe) backingStore->format = VIR_STORAGE_FILE_RAW; else if (backingFormat == VIR_STORAGE_FILE_AUTO_SAFE) backingStore->format = VIR_STORAGE_FILE_AUTO; else backingStore->format = backingFormat; if ((ret = virStorageFileGetMetadataRecurse(backingStore, parent, uid, gid, allow_probe, report_broken, cycle, depth + 1)) < 0) { if (report_broken) goto cleanup; /* if we fail somewhere midway, just accept and return a * broken chain */ ret = 0; goto cleanup; } } else { /* add terminator */ if (VIR_ALLOC(backingStore) < 0) goto cleanup; } src->backingStore = backingStore; backingStore = NULL; ret = 0; cleanup: if (virStorageSourceHasBacking(src)) src->backingStore->id = depth; VIR_FREE(buf); virStorageFileDeinit(src); virStorageSourceFree(backingStore); return ret; } /** * virStorageFileGetMetadata: * * Extract metadata about the storage volume with the specified * image format. If image format is VIR_STORAGE_FILE_AUTO, it * will probe to automatically identify the format. Recurses through * the entire chain. * * Open files using UID and GID (or pass -1 for the current user/group). * Treat any backing files without explicit type as raw, unless ALLOW_PROBE. * * Callers are advised never to use VIR_STORAGE_FILE_AUTO as a * format, since a malicious guest can turn a raw file into any * other non-raw format at will. * * If @report_broken is true, the whole function fails with a possibly sane * error instead of just returning a broken chain. * * Caller MUST free result after use via virStorageSourceFree. */ int virStorageFileGetMetadata(virStorageSourcePtr src, uid_t uid, gid_t gid, bool allow_probe, bool report_broken) { VIR_DEBUG("path=%s format=%d uid=%u gid=%u probe=%d, report_broken=%d", src->path, src->format, (unsigned int)uid, (unsigned int)gid, allow_probe, report_broken); virHashTablePtr cycle = NULL; virStorageType actualType = virStorageSourceGetActualType(src); int ret = -1; if (!(cycle = virHashCreate(5, NULL))) return -1; if (src->format <= VIR_STORAGE_FILE_NONE) { if (actualType == VIR_STORAGE_TYPE_DIR) src->format = VIR_STORAGE_FILE_DIR; else if (allow_probe) src->format = VIR_STORAGE_FILE_AUTO; else src->format = VIR_STORAGE_FILE_RAW; } ret = virStorageFileGetMetadataRecurse(src, src, uid, gid, allow_probe, report_broken, cycle, 1); virHashFree(cycle); return ret; } /** * virStorageFileGetBackingStoreStr: * @src: storage object * * Extracts the backing store string as stored in the storage volume described * by @src and returns it to the user. Caller is responsible for freeing it. * In case when the string can't be retrieved or does not exist NULL is * returned. */ char * virStorageFileGetBackingStoreStr(virStorageSourcePtr src) { virStorageSourcePtr tmp = NULL; char *buf = NULL; ssize_t headerLen; char *ret = NULL; /* exit if we can't load information about the current image */ if (!virStorageFileSupportsBackingChainTraversal(src)) return NULL; if (virStorageFileAccess(src, F_OK) < 0) return NULL; if ((headerLen = virStorageFileRead(src, 0, VIR_STORAGE_MAX_HEADER, &buf)) < 0) return NULL; if (!(tmp = virStorageSourceCopy(src, false))) goto cleanup; if (virStorageFileGetMetadataInternal(tmp, buf, headerLen, NULL) < 0) goto cleanup; VIR_STEAL_PTR(ret, tmp->backingStoreRaw); cleanup: VIR_FREE(buf); virStorageSourceFree(tmp); return ret; }