/*
* 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
* .
*/
#include
#include
#include
#include "internal.h"
#include "storage_file_backend.h"
#include "storage_file_probe.h"
#include "storage_source.h"
#include "viralloc.h"
#include "virerror.h"
#include "virfile.h"
#include "virhash.h"
#include "virjson.h"
#include "virlog.h"
#include "virobject.h"
#include "virstoragefile.h"
#include "virstring.h"
#include "viruri.h"
#include "virutil.h"
#define VIR_FROM_THIS VIR_FROM_STORAGE
VIR_LOG_INIT("storage_source");
static virStorageSourcePtr
virStorageFileMetadataNew(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);
}
/**
* virStorageFileGetMetadataFromBuf:
* @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
virStorageFileGetMetadataFromBuf(const char *path,
char *buf,
size_t len,
int format)
{
virStorageSourcePtr ret = NULL;
if (!(ret = virStorageFileMetadataNew(path, format)))
return NULL;
if (virStorageFileProbeGetMetadata(ret, buf, len) < 0) {
virObjectUnref(ret);
return NULL;
}
return ret;
}
/**
* virStorageFileGetMetadataFromFD:
*
* 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
virStorageFileGetMetadataFromFD(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 = virStorageFileMetadataNew(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);
}
/* Given a @chain, look for the backing store @name that is a backing file
* of @startFrom (or any member of @chain if @startFrom is NULL) and return
* that location within the chain. @chain must always point to the top of
* the chain. Pass NULL for @name and 0 for @idx to find the base of the
* chain. Pass nonzero @idx to find the backing source according to its
* position in the backing chain. If @parent is not NULL, set *@parent to
* the preferred name of the parent (or to NULL if @name matches the start
* of the chain). Since the results point within @chain, they must not be
* independently freed. Reports an error and returns NULL if @name is not
* found.
*/
virStorageSourcePtr
virStorageFileChainLookup(virStorageSourcePtr chain,
virStorageSourcePtr startFrom,
const char *name,
unsigned int idx,
virStorageSourcePtr *parent)
{
virStorageSourcePtr prev;
const char *start = chain->path;
bool nameIsFile = virStorageIsFile(name);
if (!parent)
parent = &prev;
*parent = 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);
}
static int
virStorageSourceParseBackingURI(virStorageSourcePtr src,
const char *uristr)
{
g_autoptr(virURI) uri = NULL;
const char *path = NULL;
g_auto(GStrv) scheme = NULL;
if (!(uri = virURIParse(uristr))) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("failed to parse backing file location '%s'"),
uristr);
return -1;
}
src->hosts = g_new0(virStorageNetHostDef, 1);
src->nhosts = 1;
if (!(scheme = virStringSplit(uri->scheme, "+", 2)))
return -1;
if (!scheme[0] ||
(src->protocol = virStorageNetProtocolTypeFromString(scheme[0])) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("invalid backing protocol '%s'"),
NULLSTR(scheme[0]));
return -1;
}
if (scheme[1] &&
(src->hosts->transport = virStorageNetHostTransportTypeFromString(scheme[1])) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("invalid protocol transport type '%s'"),
scheme[1]);
return -1;
}
if (uri->query) {
if (src->protocol == VIR_STORAGE_NET_PROTOCOL_HTTP ||
src->protocol == VIR_STORAGE_NET_PROTOCOL_HTTPS) {
src->query = g_strdup(uri->query);
} else {
/* handle socket stored as a query */
if (STRPREFIX(uri->query, "socket="))
src->hosts->socket = g_strdup(STRSKIP(uri->query, "socket="));
}
}
/* uri->path is NULL if the URI does not contain slash after host:
* transport://host:port */
if (uri->path)
path = uri->path;
else
path = "";
/* possibly skip the leading slash */
if (path[0] == '/')
path++;
/* NBD allows empty export name (path) */
if (src->protocol == VIR_STORAGE_NET_PROTOCOL_NBD &&
path[0] == '\0')
path = NULL;
src->path = g_strdup(path);
if (src->protocol == VIR_STORAGE_NET_PROTOCOL_GLUSTER) {
char *tmp;
if (!src->path) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("missing volume name and path for gluster volume"));
return -1;
}
if (!(tmp = strchr(src->path, '/')) ||
tmp == src->path) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("missing volume name or file name in "
"gluster source path '%s'"), src->path);
return -1;
}
src->volume = src->path;
src->path = g_strdup(tmp + 1);
tmp[0] = '\0';
}
src->hosts->port = uri->port;
src->hosts->name = g_strdup(uri->server);
/* Libvirt doesn't handle inline authentication. Make the caller aware. */
if (uri->user)
return 1;
return 0;
}
static int
virStorageSourceRBDAddHost(virStorageSourcePtr src,
char *hostport)
{
char *port;
size_t skip;
g_auto(GStrv) parts = NULL;
if (VIR_EXPAND_N(src->hosts, src->nhosts, 1) < 0)
return -1;
if ((port = strchr(hostport, ']'))) {
/* ipv6, strip brackets */
hostport += 1;
skip = 3;
} else {
port = strstr(hostport, "\\:");
skip = 2;
}
if (port) {
*port = '\0';
port += skip;
if (virStringParsePort(port, &src->hosts[src->nhosts - 1].port) < 0)
goto error;
}
parts = virStringSplit(hostport, "\\:", 0);
if (!parts)
goto error;
src->hosts[src->nhosts-1].name = virStringListJoin((const char **)parts, ":");
if (!src->hosts[src->nhosts-1].name)
goto error;
src->hosts[src->nhosts-1].transport = VIR_STORAGE_NET_HOST_TRANS_TCP;
src->hosts[src->nhosts-1].socket = NULL;
return 0;
error:
VIR_FREE(src->hosts[src->nhosts-1].name);
return -1;
}
int
virStorageSourceParseRBDColonString(const char *rbdstr,
virStorageSourcePtr src)
{
char *p, *e, *next;
g_autofree char *options = NULL;
g_autoptr(virStorageAuthDef) authdef = NULL;
/* optionally skip the "rbd:" prefix if provided */
if (STRPREFIX(rbdstr, "rbd:"))
rbdstr += strlen("rbd:");
src->path = g_strdup(rbdstr);
p = strchr(src->path, ':');
if (p) {
options = g_strdup(p + 1);
*p = '\0';
}
/* snapshot name */
if ((p = strchr(src->path, '@'))) {
src->snapshot = g_strdup(p + 1);
*p = '\0';
}
/* pool vs. image name */
if ((p = strchr(src->path, '/'))) {
src->volume = g_steal_pointer(&src->path);
src->path = g_strdup(p + 1);
*p = '\0';
}
/* options */
if (!options)
return 0; /* all done */
p = options;
while (*p) {
/* find : delimiter or end of string */
for (e = p; *e && *e != ':'; ++e) {
if (*e == '\\') {
e++;
if (*e == '\0')
break;
}
}
if (*e == '\0') {
next = e; /* last kv pair */
} else {
next = e + 1;
*e = '\0';
}
if (STRPREFIX(p, "id=")) {
/* formulate authdef for src->auth */
if (src->auth) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("duplicate 'id' found in '%s'"), src->path);
return -1;
}
authdef = g_new0(virStorageAuthDef, 1);
authdef->username = g_strdup(p + strlen("id="));
authdef->secrettype = g_strdup(virSecretUsageTypeToString(VIR_SECRET_USAGE_TYPE_CEPH));
src->auth = g_steal_pointer(&authdef);
/* Cannot formulate a secretType (eg, usage or uuid) given
* what is provided.
*/
}
if (STRPREFIX(p, "mon_host=")) {
char *h, *sep;
h = p + strlen("mon_host=");
while (h < e) {
for (sep = h; sep < e; ++sep) {
if (*sep == '\\' && (sep[1] == ',' ||
sep[1] == ';' ||
sep[1] == ' ')) {
*sep = '\0';
sep += 2;
break;
}
}
if (virStorageSourceRBDAddHost(src, h) < 0)
return -1;
h = sep;
}
}
if (STRPREFIX(p, "conf="))
src->configFile = g_strdup(p + strlen("conf="));
p = next;
}
return 0;
}
static int
virStorageSourceParseNBDColonString(const char *nbdstr,
virStorageSourcePtr src)
{
g_autofree char *nbd = g_strdup(nbdstr);
char *export_name;
char *host_spec;
char *unixpath;
char *port;
src->hosts = g_new0(virStorageNetHostDef, 1);
src->nhosts = 1;
/* We extract the parameters in a similar way qemu does it */
/* format: [] denotes optional sections, uppercase are variable strings
* nbd:unix:/PATH/TO/SOCKET[:exportname=EXPORTNAME]
* nbd:HOSTNAME:PORT[:exportname=EXPORTNAME]
*/
/* first look for ':exportname=' and cut it off */
if ((export_name = strstr(nbd, ":exportname="))) {
src->path = g_strdup(export_name + strlen(":exportname="));
export_name[0] = '\0';
}
/* Verify the prefix and contents. Note that we require a
* "host_spec" part to be present. */
if (!(host_spec = STRSKIP(nbd, "nbd:")) || host_spec[0] == '\0')
goto malformed;
if ((unixpath = STRSKIP(host_spec, "unix:"))) {
src->hosts->transport = VIR_STORAGE_NET_HOST_TRANS_UNIX;
if (unixpath[0] == '\0')
goto malformed;
src->hosts->socket = g_strdup(unixpath);
} else {
src->hosts->transport = VIR_STORAGE_NET_HOST_TRANS_TCP;
if (host_spec[0] == ':') {
/* no host given */
goto malformed;
} else if (host_spec[0] == '[') {
host_spec++;
/* IPv6 addr */
if (!(port = strstr(host_spec, "]:")))
goto malformed;
port[0] = '\0';
port += 2;
if (host_spec[0] == '\0')
goto malformed;
} else {
if (!(port = strchr(host_spec, ':')))
goto malformed;
port[0] = '\0';
port++;
}
if (virStringParsePort(port, &src->hosts->port) < 0)
return -1;
src->hosts->name = g_strdup(host_spec);
}
return 0;
malformed:
virReportError(VIR_ERR_INTERNAL_ERROR,
_("malformed nbd string '%s'"), nbdstr);
return -1;
}
static int
virStorageSourceParseBackingColon(virStorageSourcePtr src,
const char *path)
{
const char *p;
g_autofree char *protocol = NULL;
if (!(p = strchr(path, ':'))) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("invalid backing protocol string '%s'"),
path);
return -1;
}
protocol = g_strndup(path, p - path);
if ((src->protocol = virStorageNetProtocolTypeFromString(protocol)) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("invalid backing protocol '%s'"),
protocol);
return -1;
}
switch ((virStorageNetProtocol) src->protocol) {
case VIR_STORAGE_NET_PROTOCOL_NBD:
if (virStorageSourceParseNBDColonString(path, src) < 0)
return -1;
break;
case VIR_STORAGE_NET_PROTOCOL_RBD:
if (virStorageSourceParseRBDColonString(path, src) < 0)
return -1;
break;
case VIR_STORAGE_NET_PROTOCOL_SHEEPDOG:
case VIR_STORAGE_NET_PROTOCOL_LAST:
case VIR_STORAGE_NET_PROTOCOL_NONE:
virReportError(VIR_ERR_INTERNAL_ERROR,
_("backing store parser is not implemented for protocol %s"),
protocol);
return -1;
case VIR_STORAGE_NET_PROTOCOL_HTTP:
case VIR_STORAGE_NET_PROTOCOL_HTTPS:
case VIR_STORAGE_NET_PROTOCOL_FTP:
case VIR_STORAGE_NET_PROTOCOL_FTPS:
case VIR_STORAGE_NET_PROTOCOL_TFTP:
case VIR_STORAGE_NET_PROTOCOL_ISCSI:
case VIR_STORAGE_NET_PROTOCOL_GLUSTER:
case VIR_STORAGE_NET_PROTOCOL_SSH:
case VIR_STORAGE_NET_PROTOCOL_VXHS:
case VIR_STORAGE_NET_PROTOCOL_NFS:
virReportError(VIR_ERR_INTERNAL_ERROR,
_("malformed backing store path for protocol %s"),
protocol);
return -1;
}
return 0;
}
static int
virStorageSourceParseBackingJSONInternal(virStorageSourcePtr src,
virJSONValuePtr json,
const char *jsonstr,
bool allowformat);
static int
virStorageSourceParseBackingJSONPath(virStorageSourcePtr src,
virJSONValuePtr json,
const char *jsonstr G_GNUC_UNUSED,
int type)
{
const char *path;
if (!(path = virJSONValueObjectGetString(json, "filename"))) {
virReportError(VIR_ERR_INVALID_ARG, "%s",
_("missing 'filename' field in JSON backing volume "
"definition"));
return -1;
}
src->path = g_strdup(path);
src->type = type;
return 0;
}
static int
virStorageSourceParseBackingJSONUriStr(virStorageSourcePtr src,
const char *uri,
int protocol)
{
int rc;
if ((rc = virStorageSourceParseBackingURI(src, uri)) < 0)
return -1;
if (src->protocol != protocol) {
virReportError(VIR_ERR_INVALID_ARG,
_("expected protocol '%s' but got '%s' in URI JSON volume "
"definition"),
virStorageNetProtocolTypeToString(protocol),
virStorageNetProtocolTypeToString(src->protocol));
return -1;
}
return rc;
}
static int
virStorageSourceParseBackingJSONUriCookies(virStorageSourcePtr src,
virJSONValuePtr json,
const char *jsonstr)
{
const char *cookiestr;
g_auto(GStrv) cookies = NULL;
size_t ncookies = 0;
size_t i;
if (!virJSONValueObjectHasKey(json, "cookie"))
return 0;
if (!(cookiestr = virJSONValueObjectGetString(json, "cookie"))) {
virReportError(VIR_ERR_INVALID_ARG,
_("wrong format of 'cookie' field in backing store definition '%s'"),
jsonstr);
return -1;
}
if (!(cookies = virStringSplitCount(cookiestr, ";", 0, &ncookies)))
return -1;
src->cookies = g_new0(virStorageNetCookieDefPtr, ncookies);
src->ncookies = ncookies;
for (i = 0; i < ncookies; i++) {
char *cookiename = cookies[i];
char *cookievalue;
virSkipSpaces((const char **) &cookiename);
if (!(cookievalue = strchr(cookiename, '='))) {
virReportError(VIR_ERR_INVALID_ARG,
_("malformed http cookie '%s' in backing store definition '%s'"),
cookies[i], jsonstr);
return -1;
}
*cookievalue = '\0';
cookievalue++;
src->cookies[i] = g_new0(virStorageNetCookieDef, 1);
src->cookies[i]->name = g_strdup(cookiename);
src->cookies[i]->value = g_strdup(cookievalue);
}
return 0;
}
static int
virStorageSourceParseBackingJSONUri(virStorageSourcePtr src,
virJSONValuePtr json,
const char *jsonstr,
int protocol)
{
const char *uri;
if (!(uri = virJSONValueObjectGetString(json, "url"))) {
virReportError(VIR_ERR_INVALID_ARG, "%s",
_("missing 'url' in JSON backing volume definition"));
return -1;
}
if (protocol == VIR_STORAGE_NET_PROTOCOL_HTTPS ||
protocol == VIR_STORAGE_NET_PROTOCOL_FTPS) {
if (virJSONValueObjectHasKey(json, "sslverify")) {
const char *tmpstr;
bool tmp;
/* libguestfs still uses undocumented legacy value of 'off' */
if ((tmpstr = virJSONValueObjectGetString(json, "sslverify")) &&
STREQ(tmpstr, "off")) {
src->sslverify = VIR_TRISTATE_BOOL_NO;
} else {
if (virJSONValueObjectGetBoolean(json, "sslverify", &tmp) < 0) {
virReportError(VIR_ERR_INVALID_ARG,
_("malformed 'sslverify' field in backing store definition '%s'"),
jsonstr);
return -1;
}
src->sslverify = virTristateBoolFromBool(tmp);
}
}
}
if (protocol == VIR_STORAGE_NET_PROTOCOL_HTTPS ||
protocol == VIR_STORAGE_NET_PROTOCOL_HTTP) {
if (virStorageSourceParseBackingJSONUriCookies(src, json, jsonstr) < 0)
return -1;
}
if (virJSONValueObjectHasKey(json, "readahead") &&
virJSONValueObjectGetNumberUlong(json, "readahead", &src->readahead) < 0) {
virReportError(VIR_ERR_INVALID_ARG,
_("malformed 'readahead' field in backing store definition '%s'"),
jsonstr);
return -1;
}
if (virJSONValueObjectHasKey(json, "timeout") &&
virJSONValueObjectGetNumberUlong(json, "timeout", &src->timeout) < 0) {
virReportError(VIR_ERR_INVALID_ARG,
_("malformed 'timeout' field in backing store definition '%s'"),
jsonstr);
return -1;
}
return virStorageSourceParseBackingJSONUriStr(src, uri, protocol);
}
static int
virStorageSourceParseBackingJSONInetSocketAddress(virStorageNetHostDefPtr host,
virJSONValuePtr json)
{
const char *hostname;
const char *port;
if (!json) {
virReportError(VIR_ERR_INVALID_ARG, "%s",
_("missing remote server specification in JSON "
"backing volume definition"));
return -1;
}
hostname = virJSONValueObjectGetString(json, "host");
port = virJSONValueObjectGetString(json, "port");
if (!hostname) {
virReportError(VIR_ERR_INVALID_ARG, "%s",
_("missing hostname for tcp backing server in "
"JSON backing volume definition"));
return -1;
}
host->transport = VIR_STORAGE_NET_HOST_TRANS_TCP;
host->name = g_strdup(hostname);
if (virStringParsePort(port, &host->port) < 0)
return -1;
return 0;
}
static int
virStorageSourceParseBackingJSONSocketAddress(virStorageNetHostDefPtr host,
virJSONValuePtr json)
{
const char *type;
const char *socket;
if (!json) {
virReportError(VIR_ERR_INVALID_ARG, "%s",
_("missing remote server specification in JSON "
"backing volume definition"));
return -1;
}
if (!(type = virJSONValueObjectGetString(json, "type"))) {
virReportError(VIR_ERR_INVALID_ARG, "%s",
_("missing socket address type in "
"JSON backing volume definition"));
return -1;
}
if (STREQ(type, "tcp") || STREQ(type, "inet")) {
return virStorageSourceParseBackingJSONInetSocketAddress(host, json);
} else if (STREQ(type, "unix")) {
host->transport = VIR_STORAGE_NET_HOST_TRANS_UNIX;
socket = virJSONValueObjectGetString(json, "path");
/* check for old spelling for gluster protocol */
if (!socket)
socket = virJSONValueObjectGetString(json, "socket");
if (!socket) {
virReportError(VIR_ERR_INVALID_ARG, "%s",
_("missing socket path for udp backing server in "
"JSON backing volume definition"));
return -1;
}
host->socket = g_strdup(socket);
} else {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("backing store protocol '%s' is not yet supported"),
type);
return -1;
}
return 0;
}
static int
virStorageSourceParseBackingJSONGluster(virStorageSourcePtr src,
virJSONValuePtr json,
const char *jsonstr G_GNUC_UNUSED,
int opaque G_GNUC_UNUSED)
{
const char *uri = virJSONValueObjectGetString(json, "filename");
const char *volume = virJSONValueObjectGetString(json, "volume");
const char *path = virJSONValueObjectGetString(json, "path");
virJSONValuePtr server = virJSONValueObjectGetArray(json, "server");
size_t nservers;
size_t i;
/* legacy URI based syntax passed via 'filename' option */
if (uri)
return virStorageSourceParseBackingJSONUriStr(src, uri,
VIR_STORAGE_NET_PROTOCOL_GLUSTER);
if (!volume || !path || !server) {
virReportError(VIR_ERR_INVALID_ARG, "%s",
_("missing 'volume', 'path' or 'server' attribute in "
"JSON backing definition for gluster volume"));
return -1;
}
src->type = VIR_STORAGE_TYPE_NETWORK;
src->protocol = VIR_STORAGE_NET_PROTOCOL_GLUSTER;
src->volume = g_strdup(volume);
src->path = g_strdup(path);
nservers = virJSONValueArraySize(server);
if (nservers == 0) {
virReportError(VIR_ERR_INVALID_ARG, "%s",
_("at least 1 server is necessary in "
"JSON backing definition for gluster volume"));
return -1;
}
src->hosts = g_new0(virStorageNetHostDef, nservers);
src->nhosts = nservers;
for (i = 0; i < nservers; i++) {
if (virStorageSourceParseBackingJSONSocketAddress(src->hosts + i,
virJSONValueArrayGet(server, i)) < 0)
return -1;
}
return 0;
}
static int
virStorageSourceParseBackingJSONiSCSI(virStorageSourcePtr src,
virJSONValuePtr json,
const char *jsonstr G_GNUC_UNUSED,
int opaque G_GNUC_UNUSED)
{
const char *transport = virJSONValueObjectGetString(json, "transport");
const char *portal = virJSONValueObjectGetString(json, "portal");
const char *target = virJSONValueObjectGetString(json, "target");
const char *lun = virJSONValueObjectGetStringOrNumber(json, "lun");
const char *uri;
char *port;
/* legacy URI based syntax passed via 'filename' option */
if ((uri = virJSONValueObjectGetString(json, "filename")))
return virStorageSourceParseBackingJSONUriStr(src, uri,
VIR_STORAGE_NET_PROTOCOL_ISCSI);
src->type = VIR_STORAGE_TYPE_NETWORK;
src->protocol = VIR_STORAGE_NET_PROTOCOL_ISCSI;
if (!lun)
lun = "0";
src->hosts = g_new0(virStorageNetHostDef, 1);
src->nhosts = 1;
if (STRNEQ_NULLABLE(transport, "tcp")) {
virReportError(VIR_ERR_INVALID_ARG, "%s",
_("only TCP transport is supported for iSCSI volumes"));
return -1;
}
src->hosts->transport = VIR_STORAGE_NET_HOST_TRANS_TCP;
if (!portal) {
virReportError(VIR_ERR_INVALID_ARG, "%s",
_("missing 'portal' address in iSCSI backing definition"));
return -1;
}
if (!target) {
virReportError(VIR_ERR_INVALID_ARG, "%s",
_("missing 'target' in iSCSI backing definition"));
return -1;
}
src->hosts->name = g_strdup(portal);
if ((port = strrchr(src->hosts->name, ':')) &&
!strchr(port, ']')) {
if (virStringParsePort(port + 1, &src->hosts->port) < 0)
return -1;
*port = '\0';
}
src->path = g_strdup_printf("%s/%s", target, lun);
/* Libvirt doesn't handle inline authentication. Make the caller aware. */
if (virJSONValueObjectGetString(json, "user") ||
virJSONValueObjectGetString(json, "password"))
return 1;
return 0;
}
static int
virStorageSourceParseBackingJSONNbd(virStorageSourcePtr src,
virJSONValuePtr json,
const char *jsonstr G_GNUC_UNUSED,
int opaque G_GNUC_UNUSED)
{
const char *path = virJSONValueObjectGetString(json, "path");
const char *host = virJSONValueObjectGetString(json, "host");
const char *port = virJSONValueObjectGetString(json, "port");
const char *export = virJSONValueObjectGetString(json, "export");
virJSONValuePtr server = virJSONValueObjectGetObject(json, "server");
if (!path && !host && !server) {
virReportError(VIR_ERR_INVALID_ARG, "%s",
_("missing host specification of NBD server in JSON "
"backing volume definition"));
return -1;
}
src->type = VIR_STORAGE_TYPE_NETWORK;
src->protocol = VIR_STORAGE_NET_PROTOCOL_NBD;
src->path = g_strdup(export);
src->hosts = g_new0(virStorageNetHostDef, 1);
src->nhosts = 1;
if (server) {
if (virStorageSourceParseBackingJSONSocketAddress(src->hosts, server) < 0)
return -1;
} else {
if (path) {
src->hosts[0].transport = VIR_STORAGE_NET_HOST_TRANS_UNIX;
src->hosts[0].socket = g_strdup(path);
} else {
src->hosts[0].transport = VIR_STORAGE_NET_HOST_TRANS_TCP;
src->hosts[0].name = g_strdup(host);
if (virStringParsePort(port, &src->hosts[0].port) < 0)
return -1;
}
}
return 0;
}
static int
virStorageSourceParseBackingJSONSheepdog(virStorageSourcePtr src,
virJSONValuePtr json,
const char *jsonstr G_GNUC_UNUSED,
int opaque G_GNUC_UNUSED)
{
const char *filename;
const char *vdi = virJSONValueObjectGetString(json, "vdi");
virJSONValuePtr server = virJSONValueObjectGetObject(json, "server");
/* legacy URI based syntax passed via 'filename' option */
if ((filename = virJSONValueObjectGetString(json, "filename"))) {
if (strstr(filename, "://"))
return virStorageSourceParseBackingJSONUriStr(src, filename,
VIR_STORAGE_NET_PROTOCOL_SHEEPDOG);
/* libvirt doesn't implement a parser for the legacy non-URI syntax */
virReportError(VIR_ERR_INVALID_ARG, "%s",
_("missing sheepdog URI in JSON backing volume definition"));
return -1;
}
src->type = VIR_STORAGE_TYPE_NETWORK;
src->protocol = VIR_STORAGE_NET_PROTOCOL_SHEEPDOG;
if (!vdi) {
virReportError(VIR_ERR_INVALID_ARG, "%s", _("missing sheepdog vdi name"));
return -1;
}
src->path = g_strdup(vdi);
src->hosts = g_new0(virStorageNetHostDef, 1);
src->nhosts = 1;
if (virStorageSourceParseBackingJSONSocketAddress(src->hosts, server) < 0)
return -1;
return 0;
}
static int
virStorageSourceParseBackingJSONSSH(virStorageSourcePtr src,
virJSONValuePtr json,
const char *jsonstr G_GNUC_UNUSED,
int opaque G_GNUC_UNUSED)
{
const char *path = virJSONValueObjectGetString(json, "path");
const char *host = virJSONValueObjectGetString(json, "host");
const char *port = virJSONValueObjectGetString(json, "port");
const char *user = virJSONValueObjectGetString(json, "user");
const char *host_key_check = virJSONValueObjectGetString(json, "host_key_check");
virJSONValuePtr server = virJSONValueObjectGetObject(json, "server");
if (!(host || server) || !path) {
virReportError(VIR_ERR_INVALID_ARG, "%s",
_("missing host/server or path of SSH JSON backing "
"volume definition"));
return -1;
}
src->type = VIR_STORAGE_TYPE_NETWORK;
src->protocol = VIR_STORAGE_NET_PROTOCOL_SSH;
src->path = g_strdup(path);
src->hosts = g_new0(virStorageNetHostDef, 1);
src->nhosts = 1;
if (server) {
if (virStorageSourceParseBackingJSONInetSocketAddress(src->hosts,
server) < 0)
return -1;
} else {
src->hosts[0].transport = VIR_STORAGE_NET_HOST_TRANS_TCP;
src->hosts[0].name = g_strdup(host);
if (virStringParsePort(port, &src->hosts[0].port) < 0)
return -1;
}
/* these two are parsed just to be passed back as we don't model them yet */
src->ssh_user = g_strdup(user);
if (STREQ_NULLABLE(host_key_check, "no"))
src->ssh_host_key_check_disabled = true;
return 0;
}
static int
virStorageSourceParseBackingJSONRBD(virStorageSourcePtr src,
virJSONValuePtr json,
const char *jsonstr G_GNUC_UNUSED,
int opaque G_GNUC_UNUSED)
{
const char *filename;
const char *pool = virJSONValueObjectGetString(json, "pool");
const char *image = virJSONValueObjectGetString(json, "image");
const char *conf = virJSONValueObjectGetString(json, "conf");
const char *snapshot = virJSONValueObjectGetString(json, "snapshot");
virJSONValuePtr servers = virJSONValueObjectGetArray(json, "server");
size_t nservers;
size_t i;
src->type = VIR_STORAGE_TYPE_NETWORK;
src->protocol = VIR_STORAGE_NET_PROTOCOL_RBD;
/* legacy syntax passed via 'filename' option */
if ((filename = virJSONValueObjectGetString(json, "filename")))
return virStorageSourceParseRBDColonString(filename, src);
if (!pool || !image) {
virReportError(VIR_ERR_INVALID_ARG, "%s",
_("missing pool or image name in ceph backing volume "
"JSON specification"));
return -1;
}
src->volume = g_strdup(pool);
src->path = g_strdup(image);
src->snapshot = g_strdup(snapshot);
src->configFile = g_strdup(conf);
if (servers) {
nservers = virJSONValueArraySize(servers);
src->hosts = g_new0(virStorageNetHostDef, nservers);
src->nhosts = nservers;
for (i = 0; i < nservers; i++) {
if (virStorageSourceParseBackingJSONInetSocketAddress(src->hosts + i,
virJSONValueArrayGet(servers, i)) < 0)
return -1;
}
}
return 0;
}
static int
virStorageSourceParseBackingJSONRaw(virStorageSourcePtr src,
virJSONValuePtr json,
const char *jsonstr,
int opaque G_GNUC_UNUSED)
{
bool has_offset = virJSONValueObjectHasKey(json, "offset");
bool has_size = virJSONValueObjectHasKey(json, "size");
virJSONValuePtr file;
if (has_offset || has_size) {
src->sliceStorage = g_new0(virStorageSourceSlice, 1);
if (has_offset &&
virJSONValueObjectGetNumberUlong(json, "offset", &src->sliceStorage->offset) < 0) {
virReportError(VIR_ERR_INVALID_ARG, "%s",
_("malformed 'offset' property of 'raw' driver"));
return -1;
}
if (has_size &&
virJSONValueObjectGetNumberUlong(json, "size", &src->sliceStorage->size) < 0) {
virReportError(VIR_ERR_INVALID_ARG, "%s",
_("malformed 'size' property of 'raw' driver"));
return -1;
}
}
/* 'raw' is a format driver so it can have protocol driver children */
if (!(file = virJSONValueObjectGetObject(json, "file"))) {
virReportError(VIR_ERR_INVALID_ARG,
_("JSON backing volume definition '%s' lacks 'file' object"),
jsonstr);
return -1;
}
return virStorageSourceParseBackingJSONInternal(src, file, jsonstr, false);
}
static int
virStorageSourceParseBackingJSONVxHS(virStorageSourcePtr src,
virJSONValuePtr json,
const char *jsonstr G_GNUC_UNUSED,
int opaque G_GNUC_UNUSED)
{
const char *vdisk_id = virJSONValueObjectGetString(json, "vdisk-id");
virJSONValuePtr server = virJSONValueObjectGetObject(json, "server");
if (!vdisk_id || !server) {
virReportError(VIR_ERR_INVALID_ARG, "%s",
_("missing 'vdisk-id' or 'server' attribute in "
"JSON backing definition for VxHS volume"));
return -1;
}
src->type = VIR_STORAGE_TYPE_NETWORK;
src->protocol = VIR_STORAGE_NET_PROTOCOL_VXHS;
src->path = g_strdup(vdisk_id);
src->hosts = g_new0(virStorageNetHostDef, 1);
src->nhosts = 1;
if (virStorageSourceParseBackingJSONInetSocketAddress(src->hosts,
server) < 0)
return -1;
return 0;
}
static int
virStorageSourceParseBackingJSONNFS(virStorageSourcePtr src,
virJSONValuePtr json,
const char *jsonstr G_GNUC_UNUSED,
int opaque G_GNUC_UNUSED)
{
virJSONValuePtr server = virJSONValueObjectGetObject(json, "server");
int uidStore = -1;
int gidStore = -1;
int gotUID = virJSONValueObjectGetNumberInt(json, "user", &uidStore);
int gotGID = virJSONValueObjectGetNumberInt(json, "group", &gidStore);
if (!server) {
virReportError(VIR_ERR_INVALID_ARG, "%s",
_("missing 'server' attribute in JSON backing definition for NFS volume"));
return -1;
}
if (gotUID < 0 || gotGID < 0) {
virReportError(VIR_ERR_INVALID_ARG, "%s",
_("missing 'user' or 'group' attribute in JSON backing definition for NFS volume"));
return -1;
}
src->path = g_strdup(virJSONValueObjectGetString(json, "path"));
if (!src->path) {
virReportError(VIR_ERR_INVALID_ARG, "%s",
_("missing 'path' attribute in JSON backing definition for NFS volume"));
return -1;
}
src->nfs_user = g_strdup_printf("+%d", uidStore);
src->nfs_group = g_strdup_printf("+%d", gidStore);
src->type = VIR_STORAGE_TYPE_NETWORK;
src->protocol = VIR_STORAGE_NET_PROTOCOL_NFS;
src->hosts = g_new0(virStorageNetHostDef, 1);
src->nhosts = 1;
if (virStorageSourceParseBackingJSONInetSocketAddress(src->hosts,
server) < 0)
return -1;
return 0;
}
static int
virStorageSourceParseBackingJSONNVMe(virStorageSourcePtr src,
virJSONValuePtr json,
const char *jsonstr G_GNUC_UNUSED,
int opaque G_GNUC_UNUSED)
{
g_autoptr(virStorageSourceNVMeDef) nvme = g_new0(virStorageSourceNVMeDef, 1);
const char *device = virJSONValueObjectGetString(json, "device");
if (!device || virPCIDeviceAddressParse((char *) device, &nvme->pciAddr) < 0) {
virReportError(VIR_ERR_INVALID_ARG, "%s",
_("missing or malformed 'device' field of 'nvme' storage"));
return -1;
}
if (virJSONValueObjectGetNumberUlong(json, "namespace", &nvme->namespc) < 0 ||
nvme->namespc == 0) {
virReportError(VIR_ERR_INVALID_ARG, "%s",
_("missing or malformed 'namespace' field of 'nvme' storage"));
return -1;
}
src->type = VIR_STORAGE_TYPE_NVME;
src->nvme = g_steal_pointer(&nvme);
return 0;
}
struct virStorageSourceJSONDriverParser {
const char *drvname;
bool formatdriver;
/**
* The callback gets a pre-allocated storage source @src and the JSON
* object to parse. The callback shall return -1 on error and report error
* 0 on success and 1 in cases when the configuration itself is valid, but
* can't be converted to libvirt's configuration (e.g. inline authentication
* credentials are present).
*/
int (*func)(virStorageSourcePtr src, virJSONValuePtr json, const char *jsonstr, int opaque);
int opaque;
};
static const struct virStorageSourceJSONDriverParser jsonParsers[] = {
{"file", false, virStorageSourceParseBackingJSONPath, VIR_STORAGE_TYPE_FILE},
{"host_device", false, virStorageSourceParseBackingJSONPath, VIR_STORAGE_TYPE_BLOCK},
{"host_cdrom", false, virStorageSourceParseBackingJSONPath, VIR_STORAGE_TYPE_BLOCK},
{"http", false, virStorageSourceParseBackingJSONUri, VIR_STORAGE_NET_PROTOCOL_HTTP},
{"https", false, virStorageSourceParseBackingJSONUri, VIR_STORAGE_NET_PROTOCOL_HTTPS},
{"ftp", false, virStorageSourceParseBackingJSONUri, VIR_STORAGE_NET_PROTOCOL_FTP},
{"ftps", false, virStorageSourceParseBackingJSONUri, VIR_STORAGE_NET_PROTOCOL_FTPS},
{"tftp", false, virStorageSourceParseBackingJSONUri, VIR_STORAGE_NET_PROTOCOL_TFTP},
{"gluster", false, virStorageSourceParseBackingJSONGluster, 0},
{"iscsi", false, virStorageSourceParseBackingJSONiSCSI, 0},
{"nbd", false, virStorageSourceParseBackingJSONNbd, 0},
{"sheepdog", false, virStorageSourceParseBackingJSONSheepdog, 0},
{"ssh", false, virStorageSourceParseBackingJSONSSH, 0},
{"rbd", false, virStorageSourceParseBackingJSONRBD, 0},
{"raw", true, virStorageSourceParseBackingJSONRaw, 0},
{"nfs", false, virStorageSourceParseBackingJSONNFS, 0},
{"vxhs", false, virStorageSourceParseBackingJSONVxHS, 0},
{"nvme", false, virStorageSourceParseBackingJSONNVMe, 0},
};
static int
virStorageSourceParseBackingJSONInternal(virStorageSourcePtr src,
virJSONValuePtr json,
const char *jsonstr,
bool allowformat)
{
const char *drvname;
size_t i;
if (!(drvname = virJSONValueObjectGetString(json, "driver"))) {
virReportError(VIR_ERR_INVALID_ARG,
_("JSON backing volume definition '%s' lacks driver name"),
jsonstr);
return -1;
}
for (i = 0; i < G_N_ELEMENTS(jsonParsers); i++) {
if (STRNEQ(drvname, jsonParsers[i].drvname))
continue;
if (jsonParsers[i].formatdriver && !allowformat) {
virReportError(VIR_ERR_INVALID_ARG,
_("JSON backing volume definition '%s' must not have nested format drivers"),
jsonstr);
return -1;
}
return jsonParsers[i].func(src, json, jsonstr, jsonParsers[i].opaque);
}
virReportError(VIR_ERR_INTERNAL_ERROR,
_("missing parser implementation for JSON backing volume "
"driver '%s'"), drvname);
return -1;
}
static int
virStorageSourceParseBackingJSON(virStorageSourcePtr src,
const char *json)
{
g_autoptr(virJSONValue) root = NULL;
g_autoptr(virJSONValue) deflattened = NULL;
virJSONValuePtr file = NULL;
if (!(root = virJSONValueFromString(json)))
return -1;
if (!(deflattened = virJSONValueObjectDeflatten(root)))
return -1;
/* There are 2 possible syntaxes:
* 1) json:{"file":{"driver":...}}
* 2) json:{"driver":...}
* Remove the 'file' wrapper object in case 1.
*/
if (!virJSONValueObjectHasKey(deflattened, "driver"))
file = virJSONValueObjectGetObject(deflattened, "file");
if (!file)
file = deflattened;
return virStorageSourceParseBackingJSONInternal(src, file, json, true);
}
/**
* 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 (virStorageIsFile(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 (virStorageIsRelative(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_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 = virStorageFileGetMetadataFromBuf(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;
}
/**
* virStorageFileRemoveLastPathComponent:
*
* @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 *
virStorageFileRemoveLastPathComponent(const char *path)
{
char *ret;
ret = g_strdup(NULLSTR_EMPTY(path));
virFileRemoveLastComponent(ret);
return ret;
}
/*
* virStorageFileGetRelativeBackingPath:
*
* 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
virStorageFileGetRelativeBackingPath(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 = virStorageFileRemoveLastPathComponent(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;
}
static bool
virStorageFileIsInitialized(const virStorageSource *src)
{
return src && src->drv;
}
/**
* virStorageFileGetBackendForSupportCheck:
* @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
virStorageFileGetBackendForSupportCheck(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
virStorageFileSupportsBackingChainTraversal(const virStorageSource *src)
{
virStorageFileBackendPtr backend;
int rv;
if ((rv = virStorageFileGetBackendForSupportCheck(src, &backend)) < 1)
return rv;
return backend->storageFileGetUniqueIdentifier &&
backend->storageFileRead &&
backend->storageFileAccess ? 1 : 0;
}
/**
* virStorageFileSupportsSecurityDriver:
*
* @src: a storage file structure
*
* Check if a storage file supports operations needed by the security
* driver to perform labelling
*/
int
virStorageFileSupportsSecurityDriver(const virStorageSource *src)
{
virStorageFileBackendPtr backend;
int rv;
if ((rv = virStorageFileGetBackendForSupportCheck(src, &backend)) < 1)
return rv;
return backend->storageFileChown ? 1 : 0;
}
/**
* virStorageFileSupportsAccess:
*
* @src: a storage file structure
*
* Check if a storage file supports checking if the storage source is accessible
* for the given vm.
*/
int
virStorageFileSupportsAccess(const virStorageSource *src)
{
virStorageFileBackendPtr backend;
int rv;
if ((rv = virStorageFileGetBackendForSupportCheck(src, &backend)) < 1)
return rv;
return backend->storageFileAccess ? 1 : 0;
}
/**
* virStorageFileSupportsCreate:
* @src: a storage file structure
*
* Check if the storage driver supports creating storage described by @src
* via virStorageFileCreate.
*/
int
virStorageFileSupportsCreate(const virStorageSource *src)
{
virStorageFileBackendPtr backend;
int rv;
if ((rv = virStorageFileGetBackendForSupportCheck(src, &backend)) < 1)
return rv;
return backend->storageFileCreate ? 1 : 0;
}
void
virStorageFileDeinit(virStorageSourcePtr src)
{
virStorageDriverDataPtr drv = NULL;
if (!virStorageFileIsInitialized(src))
return;
drv = src->drv;
if (drv->backend &&
drv->backend->backendDeinit)
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);
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;
}
/**
* 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)
{
virStorageDriverDataPtr drv = NULL;
int ret;
if (!virStorageFileIsInitialized(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;
}
/**
* 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)
{
virStorageDriverDataPtr drv = NULL;
int ret;
if (!virStorageFileIsInitialized(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;
}
/**
* 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)
{
virStorageDriverDataPtr drv = NULL;
int ret;
if (!virStorageFileIsInitialized(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;
}
/**
* 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)
{
virStorageDriverDataPtr drv = NULL;
ssize_t ret;
if (!virStorageFileIsInitialized(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;
}
/*
* 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)
{
virStorageDriverDataPtr drv = NULL;
if (!virStorageFileIsInitialized(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);
}
/**
* 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)
{
virStorageDriverDataPtr drv = NULL;
if (!virStorageFileIsInitialized(src)) {
errno = ENOSYS;
return -2;
}
drv = src->drv;
if (!drv->backend->storageFileAccess) {
errno = ENOSYS;
return -2;
}
return 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)
{
virStorageDriverDataPtr drv = NULL;
if (!virStorageFileIsInitialized(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);
}
/**
* 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)
{
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
virStorageFileGetMetadataRecurseReadHeader(virStorageSourcePtr src,
virStorageSourcePtr parent,
uid_t uid,
gid_t gid,
char **buf,
size_t *headerLen,
GHashTable *cycle)
{
int ret = -1;
const char *uniqueName;
ssize_t len;
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 (virHashHasEntry(cycle, uniqueName)) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("backing store for %s (%s) is self-referential"),
NULLSTR(src->path), uniqueName);
goto cleanup;
}
if (virHashAddEntry(cycle, uniqueName, NULL) < 0)
goto cleanup;
if ((len = virStorageFileRead(src, 0, VIR_STORAGE_MAX_HEADER, buf)) < 0)
goto cleanup;
*headerLen = len;
ret = 0;
cleanup:
virStorageFileDeinit(src);
return ret;
}
/* Recursive workhorse for virStorageFileGetMetadata. */
static int
virStorageFileGetMetadataRecurse(virStorageSourcePtr src,
virStorageSourcePtr parent,
uid_t uid, gid_t gid,
bool report_broken,
GHashTable *cycle,
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",
NULLSTR(src->path), src->format,
(unsigned int)uid, (unsigned int)gid);
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 = virStorageFileSupportsBackingChainTraversal(src);
if (rv <= 0) {
if (orig_format == VIR_STORAGE_FILE_AUTO)
return -2;
return rv;
}
if (virStorageFileGetMetadataRecurseReadHeader(src, parent, uid, gid,
&buf, &headerLen, cycle) < 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 = virStorageFileGetMetadataRecurse(backingStore, parent,
uid, gid,
report_broken,
cycle, 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;
}
/**
* 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. 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
virStorageFileGetMetadata(virStorageSourcePtr src,
uid_t uid, gid_t gid,
bool report_broken)
{
GHashTable *cycle = NULL;
virStorageType actualType = virStorageSourceGetActualType(src);
int ret = -1;
VIR_DEBUG("path=%s format=%d uid=%u gid=%u report_broken=%d",
src->path, src->format, (unsigned int)uid, (unsigned int)gid,
report_broken);
if (!(cycle = virHashNew(NULL)))
return -1;
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;
}
ret = virStorageFileGetMetadataRecurse(src, src, uid, gid,
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.
*/
int
virStorageFileGetBackingStoreStr(virStorageSourcePtr src,
char **backing)
{
ssize_t headerLen;
int rv;
g_autofree char *buf = NULL;
g_autoptr(virStorageSource) tmp = NULL;
*backing = NULL;
/* exit if we can't load information about the current image */
if (!virStorageFileSupportsBackingChainTraversal(src))
return 0;
rv = virStorageFileAccess(src, F_OK);
if (rv == -2)
return 0;
if (rv < 0) {
virStorageFileReportBrokenChain(errno, src, src);
return -1;
}
if ((headerLen = virStorageFileRead(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;
*backing = g_steal_pointer(&tmp->backingStoreRaw);
return 0;
}