libvirt/src/storage_file/storage_source.c
Peter Krempa a43c8763bf virStorageSourceGetMetadata: Use depth limit instead of unique path checking
Prevent unbounded chains by limiting the recursion depth of
virStorageSourceGetMetadataRecurse to the maximum number of image layers
we limit anyways.

This removes the last use of virStorageSourceGetUniqueIdentifier which
will allow us to delete some crusty old infrastructure which isn't
really needed.

Signed-off-by: Peter Krempa <pkrempa@redhat.com>
Reviewed-by: Ján Tomko <jtomko@redhat.com>
2021-04-12 15:55:09 +02:00

1459 lines
41 KiB
C

/*
* storage_source.c: file utility functions for FS storage backend
*
* Copyright (C) 2007-2017 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
* <http://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <sys/types.h>
#include <unistd.h>
#include "internal.h"
#include "storage_file_backend.h"
#include "storage_file_probe.h"
#include "storage_source.h"
#include "storage_source_backingstore.h"
#include "viralloc.h"
#include "virerror.h"
#include "virfile.h"
#include "virhash.h"
#include "virlog.h"
#include "virobject.h"
#include "virstoragefile.h"
#include "virstring.h"
#include "virutil.h"
#define VIR_FROM_THIS VIR_FROM_STORAGE
VIR_LOG_INIT("storage_source");
static bool
virStorageSourceBackinStoreStringIsFile(const char *backing)
{
char *colon;
char *slash;
if (!backing)
return false;
colon = strchr(backing, ':');
slash = strchr(backing, '/');
/* Reject anything that looks like a protocol (such as nbd: or
* rbd:); if someone really does want a relative file name that
* includes ':', they can always prefix './'. */
if (colon && (!slash || colon < slash))
return false;
return true;
}
static bool
virStorageSourceBackinStoreStringIsRelative(const char *backing)
{
if (backing[0] == '/')
return false;
if (!virStorageSourceBackinStoreStringIsFile(backing))
return false;
return true;
}
static virStorageSourcePtr
virStorageSourceMetadataNew(const char *path,
int format)
{
g_autoptr(virStorageSource) def = virStorageSourceNew();
def->format = format;
def->type = VIR_STORAGE_TYPE_FILE;
def->path = g_strdup(path);
return g_steal_pointer(&def);
}
/**
* virStorageSourceGetMetadataFromBuf:
* @path: name of file, for error messages
* @buf: header bytes from @path
* @len: length of @buf
* @format: format of the storage file
*
* 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. Does not recurse.
*
* Callers are advised never to use VIR_STORAGE_FILE_AUTO as a format on a file
* that might be raw if that file will then be passed to a guest, since a
* malicious guest can turn a raw file into any other non-raw format at will.
*
* If the 'backingStoreRawFormat' field of the returned structure is
* VIR_STORAGE_FILE_AUTO it indicates the image didn't specify an explicit
* format for its backing store. Callers are advised against probing for the
* backing store format in this case.
*
* Caller MUST free the result after use via virObjectUnref.
*/
virStorageSourcePtr
virStorageSourceGetMetadataFromBuf(const char *path,
char *buf,
size_t len,
int format)
{
virStorageSourcePtr ret = NULL;
if (!(ret = virStorageSourceMetadataNew(path, format)))
return NULL;
if (virStorageFileProbeGetMetadata(ret, buf, len) < 0) {
virObjectUnref(ret);
return NULL;
}
return ret;
}
/**
* virStorageSourceGetMetadataFromFD:
*
* 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. Does not recurse.
*
* 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.
*
* Caller MUST free the result after use via virObjectUnref.
*/
virStorageSourcePtr
virStorageSourceGetMetadataFromFD(const char *path,
int fd,
int format)
{
ssize_t len = VIR_STORAGE_MAX_HEADER;
struct stat sb;
g_autofree char *buf = NULL;
g_autoptr(virStorageSource) meta = NULL;
if (fstat(fd, &sb) < 0) {
virReportSystemError(errno,
_("cannot stat file '%s'"), path);
return NULL;
}
if (!(meta = virStorageSourceMetadataNew(path, format)))
return NULL;
if (S_ISDIR(sb.st_mode)) {
/* No header to probe for directories, but also no backing file. Just
* update the metadata.*/
meta->type = VIR_STORAGE_TYPE_DIR;
meta->format = VIR_STORAGE_FILE_DIR;
return g_steal_pointer(&meta);
}
if (lseek(fd, 0, SEEK_SET) == (off_t)-1) {
virReportSystemError(errno, _("cannot seek to start of '%s'"), meta->path);
return NULL;
}
if ((len = virFileReadHeaderFD(fd, len, &buf)) < 0) {
virReportSystemError(errno, _("cannot read header '%s'"), meta->path);
return NULL;
}
if (virStorageFileProbeGetMetadata(meta, buf, len) < 0)
return NULL;
if (S_ISREG(sb.st_mode))
meta->type = VIR_STORAGE_TYPE_FILE;
else if (S_ISBLK(sb.st_mode))
meta->type = VIR_STORAGE_TYPE_BLOCK;
return g_steal_pointer(&meta);
}
/**
* virStorageSourceChainLookup:
* @chain: chain top to look in
* @startFrom: move the starting point of @chain if non-NULL
* @name: name of the file to look for in @chain
* @diskTarget: optional disk target to validate against
* @parent: Filled with parent virStorageSource of the returned value if non-NULL.
*
* Looks up a storage source definition corresponding to @name in @chain and
* returns the corresponding virStorageSource. If @startFrom is non-NULL, the
* lookup starts from that virStorageSource.
*
* @name can be:
* - NULL: the end of the source chain is returned
* - "vda[4]": Storage source with 'id' == 4 is returned. If @diskTarget is
* non-NULL it's also validated that the part before the square
* bracket matches the requested target
* - "/path/to/file": Literal path is matched. Symlink resolution is attempted
* if the filename doesn't string-match with the path.
*/
virStorageSourcePtr
virStorageSourceChainLookup(virStorageSourcePtr chain,
virStorageSourcePtr startFrom,
const char *name,
const char *diskTarget,
virStorageSourcePtr *parent)
{
virStorageSourcePtr prev;
const char *start = chain->path;
bool nameIsFile = virStorageSourceBackinStoreStringIsFile(name);
g_autofree char *target = NULL;
unsigned int idx = 0;
if (diskTarget)
start = diskTarget;
if (!parent)
parent = &prev;
*parent = NULL;
/* parse the "vda[4]" type string */
if (name &&
virStorageFileParseBackingStoreStr(name, &target, &idx) == 0) {
if (diskTarget &&
idx != 0 &&
STRNEQ(diskTarget, target)) {
virReportError(VIR_ERR_INVALID_ARG,
_("requested target '%s' does not match target '%s'"),
target, diskTarget);
return NULL;
}
}
if (startFrom) {
while (virStorageSourceIsBacking(chain) &&
chain != startFrom->backingStore)
chain = chain->backingStore;
*parent = startFrom;
}
while (virStorageSourceIsBacking(chain)) {
if (!name && !idx) {
if (!virStorageSourceHasBacking(chain))
break;
} else if (idx) {
VIR_DEBUG("%u: %s", chain->id, chain->path);
if (idx == chain->id)
break;
} else {
if (STREQ_NULLABLE(name, chain->relPath) ||
STREQ_NULLABLE(name, chain->path))
break;
if (nameIsFile && virStorageSourceIsLocalStorage(chain)) {
g_autofree char *parentDir = NULL;
int result;
if (*parent && virStorageSourceIsLocalStorage(*parent))
parentDir = g_path_get_dirname((*parent)->path);
else
parentDir = g_strdup(".");
result = virFileRelLinkPointsTo(parentDir, name,
chain->path);
if (result < 0)
goto error;
if (result > 0)
break;
}
}
*parent = chain;
chain = chain->backingStore;
}
if (!virStorageSourceIsBacking(chain))
goto error;
return chain;
error:
if (idx) {
virReportError(VIR_ERR_INVALID_ARG,
_("could not find backing store index '%u' in chain for '%s'"),
idx, NULLSTR(start));
} else if (name) {
if (startFrom)
virReportError(VIR_ERR_INVALID_ARG,
_("could not find image '%s' beneath '%s' in chain for '%s'"),
name, NULLSTR(startFrom->path), NULLSTR(start));
else
virReportError(VIR_ERR_INVALID_ARG,
_("could not find image '%s' in chain for '%s'"),
name, NULLSTR(start));
} else {
virReportError(VIR_ERR_INVALID_ARG,
_("could not find base image in chain for '%s'"),
NULLSTR(start));
}
*parent = NULL;
return NULL;
}
static virStorageSourcePtr
virStorageSourceNewFromBackingRelative(virStorageSourcePtr parent,
const char *rel)
{
g_autofree char *dirname = NULL;
g_autoptr(virStorageSource) def = virStorageSourceNew();
/* store relative name */
def->relPath = g_strdup(rel);
dirname = g_path_get_dirname(parent->path);
if (STRNEQ(dirname, "/")) {
def->path = g_strdup_printf("%s/%s", dirname, rel);
} else {
def->path = g_strdup_printf("/%s", rel);
}
if (virStorageSourceGetActualType(parent) == VIR_STORAGE_TYPE_NETWORK) {
def->type = VIR_STORAGE_TYPE_NETWORK;
/* copy the host network part */
def->protocol = parent->protocol;
if (parent->nhosts) {
if (!(def->hosts = virStorageNetHostDefCopy(parent->nhosts,
parent->hosts)))
return NULL;
def->nhosts = parent->nhosts;
}
def->volume = g_strdup(parent->volume);
} else {
/* set the type to _FILE, the caller shall update it to the actual type */
def->type = VIR_STORAGE_TYPE_FILE;
}
return g_steal_pointer(&def);
}
/**
* virStorageSourceNewFromBackingAbsolute
* @path: string representing absolute location of a storage source
* @src: filled with virStorageSource object representing @path
*
* Returns 0 on success, 1 if we could parse all location data but @path
* specified other data unrepresentable by libvirt (e.g. inline authentication).
* In both cases @src is filled. On error -1 is returned @src is NULL and an
* error is reported.
*/
int
virStorageSourceNewFromBackingAbsolute(const char *path,
virStorageSourcePtr *src)
{
const char *json;
const char *dirpath;
int rc = 0;
g_autoptr(virStorageSource) def = virStorageSourceNew();
*src = NULL;
if (virStorageSourceBackinStoreStringIsFile(path)) {
def->type = VIR_STORAGE_TYPE_FILE;
def->path = g_strdup(path);
} else {
if ((dirpath = STRSKIP(path, "fat:"))) {
def->type = VIR_STORAGE_TYPE_DIR;
def->format = VIR_STORAGE_FILE_FAT;
def->path = g_strdup(dirpath);
*src = g_steal_pointer(&def);
return 0;
}
def->type = VIR_STORAGE_TYPE_NETWORK;
VIR_DEBUG("parsing backing store string: '%s'", path);
/* handle URI formatted backing stores */
if ((json = STRSKIP(path, "json:")))
rc = virStorageSourceParseBackingJSON(def, json);
else if (strstr(path, "://"))
rc = virStorageSourceParseBackingURI(def, path);
else
rc = virStorageSourceParseBackingColon(def, path);
if (rc < 0)
return -1;
virStorageSourceNetworkAssignDefaultPorts(def);
/* Some of the legacy parsers parse authentication data since they are
* also used in other places. For backing store detection the
* authentication data would be invalid anyways, so we clear it */
if (def->auth) {
virStorageAuthDefFree(def->auth);
def->auth = NULL;
}
}
*src = g_steal_pointer(&def);
return rc;
}
/**
* virStorageSourceNewFromChild:
* @parent: storage source parent
* @child: returned child/backing store definition
* @parentRaw: raw child string (backingStoreRaw)
*
* Creates a storage source which describes the backing image of @parent and
* fills it into @backing depending on the passed parentRaw (backingStoreRaw)
* and other data. Note that for local storage this function accesses the file
* to update the actual type of the child store.
*
* Returns 0 on success, 1 if we could parse all location data but the child
* store specification contained other data unrepresentable by libvirt (e.g.
* inline authentication).
* In both cases @src is filled. On error -1 is returned @src is NULL and an
* error is reported.
*/
static int
virStorageSourceNewFromChild(virStorageSourcePtr parent,
const char *parentRaw,
virStorageSourcePtr *child)
{
struct stat st;
g_autoptr(virStorageSource) def = NULL;
int rc = 0;
*child = NULL;
if (virStorageSourceBackinStoreStringIsRelative(parentRaw)) {
if (!(def = virStorageSourceNewFromBackingRelative(parent, parentRaw)))
return -1;
} else {
if ((rc = virStorageSourceNewFromBackingAbsolute(parentRaw, &def)) < 0)
return -1;
}
/* possibly update local type */
if (def->type == VIR_STORAGE_TYPE_FILE) {
if (stat(def->path, &st) == 0) {
if (S_ISDIR(st.st_mode)) {
def->type = VIR_STORAGE_TYPE_DIR;
def->format = VIR_STORAGE_FILE_DIR;
} else if (S_ISBLK(st.st_mode)) {
def->type = VIR_STORAGE_TYPE_BLOCK;
}
}
}
/* copy parent's labelling and other top level stuff */
if (virStorageSourceInitChainElement(def, parent, true) < 0)
return -1;
def->detected = true;
*child = g_steal_pointer(&def);
return rc;
}
int
virStorageSourceNewFromBacking(virStorageSourcePtr parent,
virStorageSourcePtr *backing)
{
int rc;
if ((rc = virStorageSourceNewFromChild(parent,
parent->backingStoreRaw,
backing)) < 0)
return rc;
(*backing)->format = parent->backingStoreRawFormat;
(*backing)->readonly = true;
return rc;
}
/**
* @src: disk source definition structure
* @fd: file descriptor
* @sb: stat buffer
*
* Updates src->physical depending on the actual type of storage being used.
* To be called for domain storage source reporting as the volume code does
* not set/use the 'type' field for the voldef->source.target
*
* Returns 0 on success, -1 on error. No libvirt errors are reported.
*/
int
virStorageSourceUpdatePhysicalSize(virStorageSourcePtr src,
int fd,
struct stat const *sb)
{
off_t end;
virStorageType actual_type = virStorageSourceGetActualType(src);
switch (actual_type) {
case VIR_STORAGE_TYPE_FILE:
case VIR_STORAGE_TYPE_NETWORK:
src->physical = sb->st_size;
break;
case VIR_STORAGE_TYPE_BLOCK:
if ((end = lseek(fd, 0, SEEK_END)) == (off_t) -1)
return -1;
src->physical = end;
break;
case VIR_STORAGE_TYPE_DIR:
src->physical = 0;
break;
/* We shouldn't get VOLUME, but the switch requires all cases */
case VIR_STORAGE_TYPE_VOLUME:
case VIR_STORAGE_TYPE_NVME:
case VIR_STORAGE_TYPE_VHOST_USER:
case VIR_STORAGE_TYPE_NONE:
case VIR_STORAGE_TYPE_LAST:
return -1;
}
return 0;
}
/**
* @src: disk source definition structure
* @fd: file descriptor
* @sb: stat buffer
*
* Update the capacity, allocation, physical values for the storage @src
* Shared between the domain storage source for an inactive domain and the
* voldef source target as the result is not affected by the 'type' field.
*
* Returns 0 on success, -1 on error.
*/
int
virStorageSourceUpdateBackingSizes(virStorageSourcePtr src,
int fd,
struct stat const *sb)
{
/* Get info for normal formats */
if (S_ISREG(sb->st_mode) || fd == -1) {
#ifndef WIN32
src->allocation = (unsigned long long)sb->st_blocks *
(unsigned long long)DEV_BSIZE;
#else
src->allocation = sb->st_size;
#endif
/* Regular files may be sparse, so logical size (capacity) is not same
* as actual allocation above
*/
src->capacity = sb->st_size;
/* Allocation tracks when the file is sparse, physical is the
* last offset of the file. */
src->physical = sb->st_size;
} else if (S_ISDIR(sb->st_mode)) {
src->allocation = 0;
src->capacity = 0;
src->physical = 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
*
* NB. Because we configure with AC_SYS_LARGEFILE, off_t
* should be 64 bits on all platforms. For block devices, we
* have to seek (safe even if someone else is writing) to
* determine physical size, and assume that allocation is the
* same as physical (but can refine that assumption later if
* qemu is still running).
*/
if ((end = lseek(fd, 0, SEEK_END)) == (off_t)-1) {
virReportSystemError(errno,
_("failed to seek to end of %s"), src->path);
return -1;
}
src->physical = end;
src->allocation = end;
src->capacity = end;
}
return 0;
}
/**
* @src: disk source definition structure
* @buf: buffer to the storage file header
* @len: length of the storage file header
*
* Update the storage @src capacity.
*
* Returns 0 on success, -1 on error.
*/
int
virStorageSourceUpdateCapacity(virStorageSourcePtr src,
char *buf,
ssize_t len)
{
int format = src->format;
g_autoptr(virStorageSource) meta = NULL;
/* Raw files: capacity is physical size. For all other files: if
* the metadata has a capacity, use that, otherwise fall back to
* physical size. */
if (format == VIR_STORAGE_FILE_NONE) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("no disk format for %s was specified"),
src->path);
return -1;
}
if (format == VIR_STORAGE_FILE_RAW && !src->encryption) {
src->capacity = src->physical;
} else if ((meta = virStorageSourceGetMetadataFromBuf(src->path, buf,
len, format))) {
src->capacity = meta->capacity ? meta->capacity : src->physical;
if (src->encryption && meta->encryption)
src->encryption->payload_offset = meta->encryption->payload_offset;
} else {
return -1;
}
if (src->encryption && src->encryption->payload_offset != -1)
src->capacity -= src->encryption->payload_offset * 512;
return 0;
}
/**
* virStorageSourceRemoveLastPathComponent:
*
* @path: Path string to remove the last component from
*
* Removes the last path component of a path. This function is designed to be
* called on file paths only (no trailing slashes in @path). Caller is
* responsible to free the returned string.
*/
static char *
virStorageSourceRemoveLastPathComponent(const char *path)
{
char *ret;
ret = g_strdup(NULLSTR_EMPTY(path));
virFileRemoveLastComponent(ret);
return ret;
}
/*
* virStorageSourceGetRelativeBackingPath:
*
* Resolve relative path to be written to the overlay of @top image when
* collapsing the backing chain between @top and @base.
*
* Returns 0 on success; 1 if backing chain isn't relative and -1 on error.
*/
int
virStorageSourceGetRelativeBackingPath(virStorageSourcePtr top,
virStorageSourcePtr base,
char **relpath)
{
virStorageSourcePtr next;
g_autofree char *tmp = NULL;
g_autofree char *path = NULL;
*relpath = NULL;
for (next = top; virStorageSourceIsBacking(next); next = next->backingStore) {
if (!next->relPath)
return 1;
if (!(tmp = virStorageSourceRemoveLastPathComponent(path)))
return -1;
VIR_FREE(path);
path = g_strdup_printf("%s%s", tmp, next->relPath);
VIR_FREE(tmp);
if (next == base)
break;
}
if (next != base) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("failed to resolve relative backing name: "
"base image is not in backing chain"));
return -1;
}
*relpath = g_steal_pointer(&path);
return 0;
}
/**
* virStorageSourceFetchRelativeBackingPath:
* @src: storage object
* @relPath: filled with the relative path to the backing image of @src if
* the metadata of @src refer to it as relative.
*
* Fetches the backing store definition of @src by updating the metadata from
* disk and fills 'relPath' if the backing store string is relative. The data
* is used by virStorageSourceGetRelativeBackingPath to establish the relative
* path between two images.
*/
int
virStorageSourceFetchRelativeBackingPath(virStorageSourcePtr src,
char **relPath)
{
ssize_t headerLen;
int rv;
g_autofree char *buf = NULL;
g_autoptr(virStorageSource) tmp = NULL;
g_clear_pointer(relPath, g_free);
/* exit if we can't load information about the current image */
if (!virStorageSourceSupportsBackingChainTraversal(src))
return 0;
rv = virStorageSourceAccess(src, F_OK);
if (rv == -2)
return 0;
if (rv < 0) {
virStorageSourceReportBrokenChain(errno, src, src);
return -1;
}
if ((headerLen = virStorageSourceRead(src, 0, VIR_STORAGE_MAX_HEADER,
&buf)) < 0) {
if (headerLen == -2)
return 0;
return -1;
}
if (!(tmp = virStorageSourceCopy(src, false)))
return -1;
if (virStorageFileProbeGetMetadata(tmp, buf, headerLen) < 0)
return -1;
if (virStorageSourceBackinStoreStringIsRelative(tmp->backingStoreRaw))
*relPath = g_steal_pointer(&tmp->backingStoreRaw);
return 0;
}
static bool
virStorageSourceIsInitialized(const virStorageSource *src)
{
return src && src->drv;
}
/**
* virStorageSourceGetBackendForSupportCheck:
* @src: storage source to check support for
* @backend: pointer to the storage backend for @src if it's supported
*
* Returns 0 if @src is not supported by any storage backend currently linked
* 1 if it is supported and -1 on error with an error reported.
*/
static int
virStorageSourceGetBackendForSupportCheck(const virStorageSource *src,
virStorageFileBackendPtr *backend)
{
int actualType;
if (!src) {
*backend = NULL;
return 0;
}
if (src->drv) {
virStorageDriverDataPtr drv = src->drv;
*backend = drv->backend;
return 1;
}
actualType = virStorageSourceGetActualType(src);
if (virStorageFileBackendForType(actualType, src->protocol, false, backend) < 0)
return -1;
if (!*backend)
return 0;
return 1;
}
int
virStorageSourceSupportsBackingChainTraversal(const virStorageSource *src)
{
virStorageFileBackendPtr backend;
int rv;
if ((rv = virStorageSourceGetBackendForSupportCheck(src, &backend)) < 1)
return rv;
return backend->storageFileGetUniqueIdentifier &&
backend->storageFileRead &&
backend->storageFileAccess ? 1 : 0;
}
/**
* virStorageSourceSupportsSecurityDriver:
*
* @src: a storage file structure
*
* Check if a storage file supports operations needed by the security
* driver to perform labelling
*/
int
virStorageSourceSupportsSecurityDriver(const virStorageSource *src)
{
virStorageFileBackendPtr backend;
int rv;
if ((rv = virStorageSourceGetBackendForSupportCheck(src, &backend)) < 1)
return rv;
return backend->storageFileChown ? 1 : 0;
}
/**
* virStorageSourceSupportsAccess:
*
* @src: a storage file structure
*
* Check if a storage file supports checking if the storage source is accessible
* for the given vm.
*/
int
virStorageSourceSupportsAccess(const virStorageSource *src)
{
virStorageFileBackendPtr backend;
int rv;
if ((rv = virStorageSourceGetBackendForSupportCheck(src, &backend)) < 1)
return rv;
return backend->storageFileAccess ? 1 : 0;
}
/**
* virStorageSourceSupportsCreate:
* @src: a storage file structure
*
* Check if the storage driver supports creating storage described by @src
* via virStorageSourceCreate.
*/
int
virStorageSourceSupportsCreate(const virStorageSource *src)
{
virStorageFileBackendPtr backend;
int rv;
if ((rv = virStorageSourceGetBackendForSupportCheck(src, &backend)) < 1)
return rv;
return backend->storageFileCreate ? 1 : 0;
}
void
virStorageSourceDeinit(virStorageSourcePtr src)
{
virStorageDriverDataPtr drv = NULL;
if (!virStorageSourceIsInitialized(src))
return;
drv = src->drv;
if (drv->backend &&
drv->backend->backendDeinit)
drv->backend->backendDeinit(src);
VIR_FREE(src->drv);
}
/**
* virStorageSourceInitAs:
*
* @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
virStorageSourceInitAs(virStorageSourcePtr src,
uid_t uid, gid_t gid)
{
int actualType = virStorageSourceGetActualType(src);
virStorageDriverDataPtr drv = g_new0(virStorageDriverData, 1);
src->drv = drv;
if (uid == (uid_t) -1)
drv->uid = geteuid();
else
drv->uid = uid;
if (gid == (gid_t) -1)
drv->gid = getegid();
else
drv->gid = gid;
if (virStorageFileBackendForType(actualType,
src->protocol,
true,
&drv->backend) < 0)
goto error;
if (drv->backend->backendInit &&
drv->backend->backendInit(src) < 0)
goto error;
return 0;
error:
VIR_FREE(src->drv);
return -1;
}
/**
* virStorageSourceInit:
*
* See virStorageSourceInitAs. The file is initialized to be accessed by the
* current user.
*/
int
virStorageSourceInit(virStorageSourcePtr src)
{
return virStorageSourceInitAs(src, -1, -1);
}
/**
* virStorageSourceCreate: 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
virStorageSourceCreate(virStorageSourcePtr src)
{
virStorageDriverDataPtr drv = NULL;
int ret;
if (!virStorageSourceIsInitialized(src)) {
errno = ENOSYS;
return -2;
}
drv = src->drv;
if (!drv->backend->storageFileCreate) {
errno = ENOSYS;
return -2;
}
ret = drv->backend->storageFileCreate(src);
VIR_DEBUG("created storage file %p: ret=%d, errno=%d",
src, ret, errno);
return ret;
}
/**
* virStorageSourceUnlink: 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
virStorageSourceUnlink(virStorageSourcePtr src)
{
virStorageDriverDataPtr drv = NULL;
int ret;
if (!virStorageSourceIsInitialized(src)) {
errno = ENOSYS;
return -2;
}
drv = src->drv;
if (!drv->backend->storageFileUnlink) {
errno = ENOSYS;
return -2;
}
ret = drv->backend->storageFileUnlink(src);
VIR_DEBUG("unlinked storage file %p: ret=%d, errno=%d",
src, ret, errno);
return ret;
}
/**
* virStorageSourceStat: 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
virStorageSourceStat(virStorageSourcePtr src,
struct stat *st)
{
virStorageDriverDataPtr drv = NULL;
int ret;
if (!virStorageSourceIsInitialized(src)) {
errno = ENOSYS;
return -2;
}
drv = src->drv;
if (!drv->backend->storageFileStat) {
errno = ENOSYS;
return -2;
}
ret = drv->backend->storageFileStat(src, st);
VIR_DEBUG("stat of storage file %p: ret=%d, errno=%d",
src, ret, errno);
return ret;
}
/**
* virStorageSourceRead: 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
virStorageSourceRead(virStorageSourcePtr src,
size_t offset,
size_t len,
char **buf)
{
virStorageDriverDataPtr drv = NULL;
ssize_t ret;
if (!virStorageSourceIsInitialized(src)) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("storage file backend not initialized"));
return -1;
}
drv = src->drv;
if (!drv->backend->storageFileRead)
return -2;
ret = drv->backend->storageFileRead(src, offset, len, buf);
VIR_DEBUG("read '%zd' bytes from storage '%p' starting at offset '%zu'",
ret, src, offset);
return ret;
}
/*
* virStorageSourceGetUniqueIdentifier: 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 *
virStorageSourceGetUniqueIdentifier(virStorageSourcePtr src)
{
virStorageDriverDataPtr drv = NULL;
if (!virStorageSourceIsInitialized(src)) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("storage file backend not initialized"));
return NULL;
}
drv = src->drv;
if (!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 drv->backend->storageFileGetUniqueIdentifier(src);
}
/**
* virStorageSourceAccess: 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
virStorageSourceAccess(virStorageSourcePtr src,
int mode)
{
virStorageDriverDataPtr drv = NULL;
if (!virStorageSourceIsInitialized(src)) {
errno = ENOSYS;
return -2;
}
drv = src->drv;
if (!drv->backend->storageFileAccess) {
errno = ENOSYS;
return -2;
}
return drv->backend->storageFileAccess(src, mode);
}
/**
* virStorageSourceChown: 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
virStorageSourceChown(const virStorageSource *src,
uid_t uid,
gid_t gid)
{
virStorageDriverDataPtr drv = NULL;
if (!virStorageSourceIsInitialized(src)) {
errno = ENOSYS;
return -2;
}
drv = src->drv;
if (!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 drv->backend->storageFileChown(src, uid, gid);
}
/**
* virStorageSourceReportBrokenChain:
*
* @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
virStorageSourceReportBrokenChain(int errcode,
virStorageSourcePtr src,
virStorageSourcePtr parent)
{
if (src->drv) {
virStorageDriverDataPtr drv = src->drv;
unsigned int access_user = drv->uid;
unsigned int access_group = 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);
}
} else {
if (src == parent) {
virReportSystemError(errcode,
_("Cannot access storage file '%s'"),
src->path);
} else {
virReportSystemError(errcode,
_("Cannot access backing file '%s' "
"of storage file '%s'"),
src->path, parent->path);
}
}
}
static int
virStorageSourceGetMetadataRecurseReadHeader(virStorageSourcePtr src,
virStorageSourcePtr parent,
uid_t uid,
gid_t gid,
char **buf,
size_t *headerLen)
{
int ret = -1;
ssize_t len;
if (virStorageSourceInitAs(src, uid, gid) < 0)
return -1;
if (virStorageSourceAccess(src, F_OK) < 0) {
virStorageSourceReportBrokenChain(errno, src, parent);
goto cleanup;
}
if ((len = virStorageSourceRead(src, 0, VIR_STORAGE_MAX_HEADER, buf)) < 0)
goto cleanup;
*headerLen = len;
ret = 0;
cleanup:
virStorageSourceDeinit(src);
return ret;
}
/* Recursive workhorse for virStorageSourceGetMetadata. */
static int
virStorageSourceGetMetadataRecurse(virStorageSourcePtr src,
virStorageSourcePtr parent,
uid_t uid, gid_t gid,
bool report_broken,
size_t max_depth,
unsigned int depth)
{
virStorageFileFormat orig_format = src->format;
size_t headerLen;
int rv;
g_autofree char *buf = NULL;
g_autoptr(virStorageSource) backingStore = NULL;
VIR_DEBUG("path=%s format=%d uid=%u gid=%u depth=%u",
NULLSTR(src->path), src->format,
(unsigned int)uid, (unsigned int)gid, depth);
if (depth > max_depth) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("backing store for %s is self-referential or too deeply nested"),
NULLSTR(src->path));
return -1;
}
if (src->format == VIR_STORAGE_FILE_AUTO_SAFE)
src->format = VIR_STORAGE_FILE_AUTO;
/* exit if we can't load information about the current image */
rv = virStorageSourceSupportsBackingChainTraversal(src);
if (rv <= 0) {
if (orig_format == VIR_STORAGE_FILE_AUTO)
return -2;
return rv;
}
if (virStorageSourceGetMetadataRecurseReadHeader(src, parent, uid, gid,
&buf, &headerLen) < 0)
return -1;
if (virStorageFileProbeGetMetadata(src, buf, headerLen) < 0)
return -1;
/* If we probed the format we MUST ensure that nothing else than the current
* image is considered for security labelling and/or recursion. */
if (orig_format == VIR_STORAGE_FILE_AUTO) {
if (src->backingStoreRaw) {
src->format = VIR_STORAGE_FILE_RAW;
VIR_FREE(src->backingStoreRaw);
return -2;
}
}
if (src->backingStoreRaw) {
if ((rv = virStorageSourceNewFromBacking(src, &backingStore)) < 0)
return -1;
/* the backing file would not be usable for VM usage */
if (rv == 1)
return 0;
if ((rv = virStorageSourceGetMetadataRecurse(backingStore, parent,
uid, gid,
report_broken,
max_depth, depth + 1)) < 0) {
if (!report_broken)
return 0;
if (rv == -2) {
virReportError(VIR_ERR_OPERATION_INVALID,
_("format of backing image '%s' of image '%s' was not specified in the image metadata "
"(See https://libvirt.org/kbase/backing_chains.html for troubleshooting)"),
src->backingStoreRaw, NULLSTR(src->path));
}
return -1;
}
backingStore->id = depth;
src->backingStore = g_steal_pointer(&backingStore);
} else {
/* add terminator */
src->backingStore = virStorageSourceNew();
}
return 0;
}
/**
* virStorageSourceGetMetadata:
*
* 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 chain up to @max_depth layers.
*
* 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. Note that the inability for
* libvirt to traverse a given source is not considered an error.
*
* Caller MUST free result after use via virObjectUnref.
*/
int
virStorageSourceGetMetadata(virStorageSourcePtr src,
uid_t uid, gid_t gid,
size_t max_depth,
bool report_broken)
{
virStorageType actualType = virStorageSourceGetActualType(src);
VIR_DEBUG("path=%s format=%d uid=%u gid=%u max_depth=%zu report_broken=%d",
src->path, src->format, (unsigned int)uid, (unsigned int)gid,
max_depth, report_broken);
if (src->format <= VIR_STORAGE_FILE_NONE) {
if (actualType == VIR_STORAGE_TYPE_DIR)
src->format = VIR_STORAGE_FILE_DIR;
else
src->format = VIR_STORAGE_FILE_RAW;
}
return virStorageSourceGetMetadataRecurse(src, src, uid, gid,
report_broken, max_depth, 1);
}