/*
 * virstoragefile.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 "virstoragefilebackend.h"

#include <unistd.h>
#include <fcntl.h>
#include "viralloc.h"
#include "virxml.h"
#include "viruuid.h"
#include "virerror.h"
#include "virlog.h"
#include "virfile.h"
#include "vircommand.h"
#include "virhash.h"
#include "virendian.h"
#include "virstring.h"
#include "viruri.h"
#include "virbuffer.h"
#include "virjson.h"
#include "virstorageencryption.h"
#include "virsecret.h"
#include "virutil.h"

#define VIR_FROM_THIS VIR_FROM_STORAGE

VIR_LOG_INIT("util.storagefile");

static virClassPtr virStorageSourceClass;

VIR_ENUM_IMPL(virStorage,
              VIR_STORAGE_TYPE_LAST,
              "none",
              "file",
              "block",
              "dir",
              "network",
              "volume",
              "nvme",
);

VIR_ENUM_IMPL(virStorageFileFormat,
              VIR_STORAGE_FILE_LAST,
              "none",
              "raw", "dir", "bochs",
              "cloop", "dmg", "iso",
              "vpc", "vdi",
              /* Not direct file formats, but used for various drivers */
              "fat", "vhd", "ploop",
              /* Formats with backing file below here */
              "cow", "qcow", "qcow2", "qed", "vmdk",
);

VIR_ENUM_IMPL(virStorageFileFeature,
              VIR_STORAGE_FILE_FEATURE_LAST,
              "lazy_refcounts",
);

VIR_ENUM_IMPL(virStorageNetProtocol,
              VIR_STORAGE_NET_PROTOCOL_LAST,
              "none",
              "nbd",
              "rbd",
              "sheepdog",
              "gluster",
              "iscsi",
              "http",
              "https",
              "ftp",
              "ftps",
              "tftp",
              "ssh",
              "vxhs",
              "nfs",
);

VIR_ENUM_IMPL(virStorageNetHostTransport,
              VIR_STORAGE_NET_HOST_TRANS_LAST,
              "tcp",
              "unix",
              "rdma",
);

VIR_ENUM_IMPL(virStorageSourcePoolMode,
              VIR_STORAGE_SOURCE_POOL_MODE_LAST,
              "default",
              "host",
              "direct",
);

VIR_ENUM_IMPL(virStorageAuth,
              VIR_STORAGE_AUTH_TYPE_LAST,
              "none", "chap", "ceph",
);

enum lv_endian {
    LV_LITTLE_ENDIAN = 1, /* 1234 */
    LV_BIG_ENDIAN         /* 4321 */
};

enum {
    BACKING_STORE_OK,
    BACKING_STORE_INVALID,
    BACKING_STORE_ERROR,
};

#define FILE_TYPE_VERSIONS_LAST 3

struct FileEncryptionInfo {
    int format; /* Encryption format to assign */

    int magicOffset; /* Byte offset of the magic */
    const char *magic; /* Optional string of magic */

    enum lv_endian endian; /* Endianness of file format */

    int versionOffset;    /* Byte offset from start of file
                           * where we find version number,
                           * -1 to always fail the version test,
                           * -2 to always pass the version test */
    int versionSize;      /* Size in bytes of version data (0, 2, or 4) */
    int versionNumbers[FILE_TYPE_VERSIONS_LAST];
                          /* Version numbers to validate. Zeroes are ignored. */

    int modeOffset; /* Byte offset of the format native encryption mode */
    char modeValue; /* Value expected at offset */

    int payloadOffset; /* start offset of the volume data (in 512 byte sectors) */
};

struct FileTypeInfo {
    int magicOffset;    /* Byte offset of the magic */
    const char *magic;  /* Optional string of file magic
                         * to check at head of file */
    enum lv_endian endian; /* Endianness of file format */

    int versionOffset;    /* Byte offset from start of file
                           * where we find version number,
                           * -1 to always fail the version test,
                           * -2 to always pass the version test */
    int versionSize;      /* Size in bytes of version data (0, 2, or 4) */
    int versionNumbers[FILE_TYPE_VERSIONS_LAST];
                          /* Version numbers to validate. Zeroes are ignored. */
    int sizeOffset;       /* Byte offset from start of file
                           * where we find capacity info,
                           * -1 to use st_size as capacity */
    int sizeBytes;        /* Number of bytes for size field */
    int sizeMultiplier;   /* A scaling factor if size is not in bytes */
                          /* Store a COW base image path (possibly relative),
                           * or NULL if there is no COW base image, to RES;
                           * return BACKING_STORE_* */
    const struct FileEncryptionInfo *cryptInfo; /* Encryption info */
    int (*getBackingStore)(char **res, int *format,
                           const char *buf, size_t buf_size);
    int (*getFeatures)(virBitmapPtr *features, int format,
                       char *buf, ssize_t len);
};


static int cowGetBackingStore(char **, int *,
                              const char *, size_t);
static int qcowXGetBackingStore(char **, int *,
                                const char *, size_t);
static int qcow2GetFeatures(virBitmapPtr *features, int format,
                            char *buf, ssize_t len);
static int vmdk4GetBackingStore(char **, int *,
                                const char *, size_t);
static int
qedGetBackingStore(char **, int *, const char *, size_t);

#define QCOWX_HDR_VERSION (4)
#define QCOWX_HDR_BACKING_FILE_OFFSET (QCOWX_HDR_VERSION+4)
#define QCOWX_HDR_BACKING_FILE_SIZE (QCOWX_HDR_BACKING_FILE_OFFSET+8)
#define QCOWX_HDR_IMAGE_SIZE (QCOWX_HDR_BACKING_FILE_SIZE+4+4)

#define QCOW1_HDR_CRYPT (QCOWX_HDR_IMAGE_SIZE+8+1+1+2)
#define QCOW2_HDR_CRYPT (QCOWX_HDR_IMAGE_SIZE+8)

#define QCOW1_HDR_TOTAL_SIZE (QCOW1_HDR_CRYPT+4+8)
#define QCOW2_HDR_TOTAL_SIZE (QCOW2_HDR_CRYPT+4+4+8+8+4+4+8)

#define QCOW2_HDR_EXTENSION_END 0
#define QCOW2_HDR_EXTENSION_BACKING_FORMAT 0xE2792ACA

#define QCOW2v3_HDR_FEATURES_INCOMPATIBLE (QCOW2_HDR_TOTAL_SIZE)
#define QCOW2v3_HDR_FEATURES_COMPATIBLE (QCOW2v3_HDR_FEATURES_INCOMPATIBLE+8)
#define QCOW2v3_HDR_FEATURES_AUTOCLEAR (QCOW2v3_HDR_FEATURES_COMPATIBLE+8)

/* The location of the header size [4 bytes] */
#define QCOW2v3_HDR_SIZE       (QCOW2_HDR_TOTAL_SIZE+8+8+8+4)

#define QED_HDR_FEATURES_OFFSET (4+4+4+4)
#define QED_HDR_IMAGE_SIZE (QED_HDR_FEATURES_OFFSET+8+8+8+8)
#define QED_HDR_BACKING_FILE_OFFSET (QED_HDR_IMAGE_SIZE+8)
#define QED_HDR_BACKING_FILE_SIZE (QED_HDR_BACKING_FILE_OFFSET+4)
#define QED_F_BACKING_FILE 0x01
#define QED_F_BACKING_FORMAT_NO_PROBE 0x04

#define PLOOP_IMAGE_SIZE_OFFSET 36
#define PLOOP_SIZE_MULTIPLIER 512

#define LUKS_HDR_MAGIC_LEN 6
#define LUKS_HDR_VERSION_LEN 2
#define LUKS_HDR_CIPHER_NAME_LEN 32
#define LUKS_HDR_CIPHER_MODE_LEN 32
#define LUKS_HDR_HASH_SPEC_LEN 32
#define LUKS_HDR_PAYLOAD_LEN 4

/* Format described by qemu commit id '3e308f20e' */
#define LUKS_HDR_VERSION_OFFSET LUKS_HDR_MAGIC_LEN
#define LUKS_HDR_PAYLOAD_OFFSET (LUKS_HDR_MAGIC_LEN+\
                                 LUKS_HDR_VERSION_LEN+\
                                 LUKS_HDR_CIPHER_NAME_LEN+\
                                 LUKS_HDR_CIPHER_MODE_LEN+\
                                 LUKS_HDR_HASH_SPEC_LEN)

static struct FileEncryptionInfo const luksEncryptionInfo[] = {
    {
        .format = VIR_STORAGE_ENCRYPTION_FORMAT_LUKS,

        /* Magic is 'L','U','K','S', 0xBA, 0xBE */
        .magicOffset = 0,
        .magic = "\x4c\x55\x4b\x53\xba\xbe",
        .endian = LV_BIG_ENDIAN,

        .versionOffset  = LUKS_HDR_VERSION_OFFSET,
        .versionSize = LUKS_HDR_VERSION_LEN,
        .versionNumbers = {1},

        .modeOffset = -1,
        .modeValue = -1,

        .payloadOffset = LUKS_HDR_PAYLOAD_OFFSET,
    },
    { 0 }
};

static struct FileEncryptionInfo const qcow1EncryptionInfo[] = {
    {
        .format = VIR_STORAGE_ENCRYPTION_FORMAT_QCOW,

        .magicOffset = 0,
        .magic = NULL,
        .endian = LV_BIG_ENDIAN,

        .versionOffset  = -1,
        .versionSize = 0,
        .versionNumbers = {},

        .modeOffset = QCOW1_HDR_CRYPT,
        .modeValue = 1,

        .payloadOffset = -1,
    },
    { 0 }
};

static struct FileEncryptionInfo const qcow2EncryptionInfo[] = {
    {
        .format = VIR_STORAGE_ENCRYPTION_FORMAT_QCOW,

        .magicOffset = 0,
        .magic = NULL,
        .endian = LV_BIG_ENDIAN,

        .versionOffset  = -1,
        .versionSize = 0,
        .versionNumbers = {},

        .modeOffset = QCOW2_HDR_CRYPT,
        .modeValue = 1,

        .payloadOffset = -1,
    },
    {
        .format = VIR_STORAGE_ENCRYPTION_FORMAT_LUKS,

        .magicOffset = 0,
        .magic = NULL,
        .endian = LV_BIG_ENDIAN,

        .versionOffset  = -1,
        .versionSize = 0,
        .versionNumbers = {},

        .modeOffset = QCOW2_HDR_CRYPT,
        .modeValue = 2,

        .payloadOffset = -1,
    },
    { 0 }
};

static struct FileTypeInfo const fileTypeInfo[] = {
    [VIR_STORAGE_FILE_NONE] = { 0, NULL, LV_LITTLE_ENDIAN,
                                -1, 0, {0}, 0, 0, 0, NULL, NULL, NULL },
    [VIR_STORAGE_FILE_RAW] = { 0, NULL, LV_LITTLE_ENDIAN,
                               -1, 0, {0}, 0, 0, 0,
                               luksEncryptionInfo,
                               NULL, NULL },
    [VIR_STORAGE_FILE_DIR] = { 0, NULL, LV_LITTLE_ENDIAN,
                               -1, 0, {0}, 0, 0, 0, NULL, NULL, NULL },
    [VIR_STORAGE_FILE_BOCHS] = {
        /*"Bochs Virtual HD Image", */ /* Untested */
        0, NULL,
        LV_LITTLE_ENDIAN, 64, 4, {0x20000},
        32+16+16+4+4+4+4+4, 8, 1, NULL, NULL, NULL
    },
    [VIR_STORAGE_FILE_CLOOP] = {
        /* #!/bin/sh
           #V2.0 Format
           modprobe cloop file=$0 && mount -r -t iso9660 /dev/cloop $1
        */ /* Untested */
        0, NULL,
        LV_LITTLE_ENDIAN, -1, 0, {0},
        -1, 0, 0, NULL, NULL, NULL
    },
    [VIR_STORAGE_FILE_DMG] = {
        /* XXX QEMU says there's no magic for dmg,
         * /usr/share/misc/magic lists double magic (both offsets
         * would have to match) but then disables that check. */
        0, NULL,
        0, -1, 0, {0},
        -1, 0, 0, NULL, NULL, NULL
    },
    [VIR_STORAGE_FILE_ISO] = {
        32769, "CD001",
        LV_LITTLE_ENDIAN, -2, 0, {0},
        -1, 0, 0, NULL, NULL, NULL
    },
    [VIR_STORAGE_FILE_VPC] = {
        0, "conectix",
        LV_BIG_ENDIAN, 12, 4, {0x10000},
        8 + 4 + 4 + 8 + 4 + 4 + 2 + 2 + 4, 8, 1, NULL, NULL, NULL
    },
    /* TODO: add getBackingStore function */
    [VIR_STORAGE_FILE_VDI] = {
        64, "\x7f\x10\xda\xbe",
        LV_LITTLE_ENDIAN, 68, 4, {0x00010001},
        64 + 5 * 4 + 256 + 7 * 4, 8, 1, NULL, NULL, NULL},

    /* Not direct file formats, but used for various drivers */
    [VIR_STORAGE_FILE_FAT] = { 0, NULL, LV_LITTLE_ENDIAN,
                               -1, 0, {0}, 0, 0, 0, NULL, NULL, NULL },
    [VIR_STORAGE_FILE_VHD] = { 0, NULL, LV_LITTLE_ENDIAN,
                               -1, 0, {0}, 0, 0, 0, NULL, NULL, NULL },
    [VIR_STORAGE_FILE_PLOOP] = { 0, "WithouFreSpacExt", LV_LITTLE_ENDIAN,
                                 -2, 0, {0}, PLOOP_IMAGE_SIZE_OFFSET, 0,
                                 PLOOP_SIZE_MULTIPLIER, NULL, NULL, NULL },

    /* All formats with a backing store probe below here */
    [VIR_STORAGE_FILE_COW] = {
        0, "OOOM",
        LV_BIG_ENDIAN, 4, 4, {2},
        4+4+1024+4, 8, 1, NULL, cowGetBackingStore, NULL
    },
    [VIR_STORAGE_FILE_QCOW] = {
        0, "QFI",
        LV_BIG_ENDIAN, 4, 4, {1},
        QCOWX_HDR_IMAGE_SIZE, 8, 1,
        qcow1EncryptionInfo,
        qcowXGetBackingStore, NULL
    },
    [VIR_STORAGE_FILE_QCOW2] = {
        0, "QFI",
        LV_BIG_ENDIAN, 4, 4, {2, 3},
        QCOWX_HDR_IMAGE_SIZE, 8, 1,
        qcow2EncryptionInfo,
        qcowXGetBackingStore,
        qcow2GetFeatures
    },
    [VIR_STORAGE_FILE_QED] = {
        /* https://wiki.qemu.org/Features/QED */
        0, "QED",
        LV_LITTLE_ENDIAN, -2, 0, {0},
        QED_HDR_IMAGE_SIZE, 8, 1, NULL, qedGetBackingStore, NULL
    },
    [VIR_STORAGE_FILE_VMDK] = {
        0, "KDMV",
        LV_LITTLE_ENDIAN, 4, 4, {1, 2, 3},
        4+4+4, 8, 512, NULL, vmdk4GetBackingStore, NULL
    },
};
G_STATIC_ASSERT(G_N_ELEMENTS(fileTypeInfo) == VIR_STORAGE_FILE_LAST);


/* qcow2 compatible features in the order they appear on-disk */
enum qcow2CompatibleFeature {
    QCOW2_COMPATIBLE_FEATURE_LAZY_REFCOUNTS = 0,

    QCOW2_COMPATIBLE_FEATURE_LAST
};

/* conversion to virStorageFileFeature */
static const int qcow2CompatibleFeatureArray[] = {
    VIR_STORAGE_FILE_FEATURE_LAZY_REFCOUNTS,
};
G_STATIC_ASSERT(G_N_ELEMENTS(qcow2CompatibleFeatureArray) ==
       QCOW2_COMPATIBLE_FEATURE_LAST);

static int
cowGetBackingStore(char **res,
                   int *format,
                   const char *buf,
                   size_t buf_size)
{
#define COW_FILENAME_MAXLEN 1024
    *res = NULL;
    *format = VIR_STORAGE_FILE_AUTO;

    if (buf_size < 4+4+ COW_FILENAME_MAXLEN)
        return BACKING_STORE_INVALID;
    if (buf[4+4] == '\0') { /* cow_header_v2.backing_file[0] */
        *format = VIR_STORAGE_FILE_NONE;
        return BACKING_STORE_OK;
    }

    *res = g_strndup((const char *)buf + 4 + 4, COW_FILENAME_MAXLEN);
    return BACKING_STORE_OK;
}


static int
qcow2GetExtensions(const char *buf,
                   size_t buf_size,
                   int *backingFormat)
{
    size_t offset;
    size_t extension_start;
    size_t extension_end;
    int version = virReadBufInt32BE(buf + QCOWX_HDR_VERSION);

    if (version < 2) {
        /* QCow1 doesn't have the extensions capability
         * used to store backing format */
        return 0;
    }

    if (version == 2)
        extension_start = QCOW2_HDR_TOTAL_SIZE;
    else
        extension_start = virReadBufInt32BE(buf + QCOW2v3_HDR_SIZE);

    /*
     * Traditionally QCow2 files had a layout of
     *
     * [header]
     * [backingStoreName]
     *
     * Although the backingStoreName typically followed
     * the header immediately, this was not required by
     * the format. By specifying a higher byte offset for
     * the backing file offset in the header, it was
     * possible to leave space between the header and
     * start of backingStore.
     *
     * This hack is now used to store extensions to the
     * qcow2 format:
     *
     * [header]
     * [extensions]
     * [backingStoreName]
     *
     * Thus the file region to search for extensions is
     * between the end of the header (QCOW2_HDR_TOTAL_SIZE)
     * and the start of the backingStoreName (offset)
     *
     * for qcow2 v3 images, the length of the header
     * is stored at QCOW2v3_HDR_SIZE
     */
    extension_end = virReadBufInt64BE(buf + QCOWX_HDR_BACKING_FILE_OFFSET);
    if (extension_end > buf_size)
        return -1;

    /*
     * The extensions take format of
     *
     * int32: magic
     * int32: length
     * byte[length]: payload
     *
     * Unknown extensions can be ignored by skipping
     * over "length" bytes in the data stream.
     */
    offset = extension_start;
    while (offset < (buf_size-8) &&
           offset < (extension_end-8)) {
        unsigned int magic = virReadBufInt32BE(buf + offset);
        unsigned int len = virReadBufInt32BE(buf + offset + 4);

        offset += 8;

        if ((offset + len) < offset)
            break;

        if ((offset + len) > buf_size)
            break;

        switch (magic) {
        case QCOW2_HDR_EXTENSION_BACKING_FORMAT: {
            g_autofree char *tmp = NULL;
            if (!backingFormat)
                break;

            tmp = g_new0(char, len + 1);
            memcpy(tmp, buf + offset, len);
            tmp[len] = '\0';

            *backingFormat = virStorageFileFormatTypeFromString(tmp);
            if (*backingFormat <= VIR_STORAGE_FILE_NONE)
                return -1;
            break;
        }

        case QCOW2_HDR_EXTENSION_END:
            return 0;
        }

        offset += len;
    }

    return 0;
}


static int
qcowXGetBackingStore(char **res,
                     int *format,
                     const char *buf,
                     size_t buf_size)
{
    unsigned long long offset;
    unsigned int size;

    *res = NULL;
    *format = VIR_STORAGE_FILE_AUTO;

    if (buf_size < QCOWX_HDR_BACKING_FILE_OFFSET+8+4)
        return BACKING_STORE_INVALID;

    offset = virReadBufInt64BE(buf + QCOWX_HDR_BACKING_FILE_OFFSET);
    if (offset > buf_size)
        return BACKING_STORE_INVALID;

    if (offset == 0) {
        *format = VIR_STORAGE_FILE_NONE;
        return BACKING_STORE_OK;
    }

    size = virReadBufInt32BE(buf + QCOWX_HDR_BACKING_FILE_SIZE);
    if (size == 0) {
        *format = VIR_STORAGE_FILE_NONE;
        return BACKING_STORE_OK;
    }
    if (size > 1023)
        return BACKING_STORE_INVALID;
    if (offset + size > buf_size || offset + size < offset)
        return BACKING_STORE_INVALID;
    *res = g_new0(char, size + 1);
    memcpy(*res, buf + offset, size);
    (*res)[size] = '\0';

    if (qcow2GetExtensions(buf, buf_size, format) < 0)
        return BACKING_STORE_INVALID;

    return BACKING_STORE_OK;
}


static int
vmdk4GetBackingStore(char **res,
                     int *format,
                     const char *buf,
                     size_t buf_size)
{
    static const char prefix[] = "parentFileNameHint=\"";
    char *start, *end;
    size_t len;
    g_autofree char *desc = NULL;

    desc = g_new0(char, VIR_STORAGE_MAX_HEADER);

    *res = NULL;
    /*
     * Technically this should have been VMDK, since
     * VMDK spec / VMware impl only support VMDK backed
     * by VMDK. QEMU isn't following this though and
     * does probing on VMDK backing files, hence we set
     * AUTO
     */
    *format = VIR_STORAGE_FILE_AUTO;

    if (buf_size <= 0x200)
        return BACKING_STORE_INVALID;

    len = buf_size - 0x200;
    if (len > VIR_STORAGE_MAX_HEADER)
        len = VIR_STORAGE_MAX_HEADER;
    memcpy(desc, buf + 0x200, len);
    desc[len] = '\0';
    start = strstr(desc, prefix);
    if (start == NULL) {
        *format = VIR_STORAGE_FILE_NONE;
        return BACKING_STORE_OK;
    }
    start += strlen(prefix);
    end = strchr(start, '"');
    if (end == NULL)
        return BACKING_STORE_INVALID;

    if (end == start) {
        *format = VIR_STORAGE_FILE_NONE;
        return BACKING_STORE_OK;
    }
    *end = '\0';
    *res = g_strdup(start);

    return BACKING_STORE_OK;
}

static int
qedGetBackingStore(char **res,
                   int *format,
                   const char *buf,
                   size_t buf_size)
{
    unsigned long long flags;
    unsigned long offset, size;

    *res = NULL;
    /* Check if this image has a backing file */
    if (buf_size < QED_HDR_FEATURES_OFFSET+8)
        return BACKING_STORE_INVALID;
    flags = virReadBufInt64LE(buf + QED_HDR_FEATURES_OFFSET);
    if (!(flags & QED_F_BACKING_FILE)) {
        *format = VIR_STORAGE_FILE_NONE;
        return BACKING_STORE_OK;
    }

    /* Parse the backing file */
    if (buf_size < QED_HDR_BACKING_FILE_OFFSET+8)
        return BACKING_STORE_INVALID;
    offset = virReadBufInt32LE(buf + QED_HDR_BACKING_FILE_OFFSET);
    if (offset > buf_size)
        return BACKING_STORE_INVALID;
    size = virReadBufInt32LE(buf + QED_HDR_BACKING_FILE_SIZE);
    if (size == 0)
        return BACKING_STORE_OK;
    if (offset + size > buf_size || offset + size < offset)
        return BACKING_STORE_INVALID;
    *res = g_new0(char, size + 1);
    memcpy(*res, buf + offset, size);
    (*res)[size] = '\0';

    if (flags & QED_F_BACKING_FORMAT_NO_PROBE)
        *format = VIR_STORAGE_FILE_RAW;
    else
        *format = VIR_STORAGE_FILE_AUTO_SAFE;

    return BACKING_STORE_OK;
}


static bool
virStorageFileMatchesMagic(int magicOffset,
                           const char *magic,
                           char *buf,
                           size_t buflen)
{
    int mlen;

    if (magic == NULL)
        return false;

    /* Validate magic data */
    mlen = strlen(magic);
    if (magicOffset + mlen > buflen)
        return false;

    if (memcmp(buf + magicOffset, magic, mlen) != 0)
        return false;

    return true;
}


static bool
virStorageFileMatchesVersion(int versionOffset,
                             int versionSize,
                             const int *versionNumbers,
                             int endian,
                             char *buf,
                             size_t buflen)
{
    int version;
    size_t i;

    /* Validate version number info */
    if (versionOffset == -1)
        return false;

    /* -2 == non-versioned file format, so trivially match */
    if (versionOffset == -2)
        return true;

    /* A positive versionOffset, requires using a valid versionSize */
    if (versionSize != 2 && versionSize != 4)
        return false;

    if ((versionOffset + versionSize) > buflen)
        return false;

    if (endian == LV_LITTLE_ENDIAN) {
        if (versionSize == 4)
            version = virReadBufInt32LE(buf +
                                        versionOffset);
        else
            version = virReadBufInt16LE(buf +
                                        versionOffset);
    } else {
        if (versionSize == 4)
            version = virReadBufInt32BE(buf +
                                        versionOffset);
        else
            version = virReadBufInt16BE(buf +
                                        versionOffset);
    }

    for (i = 0;
         i < FILE_TYPE_VERSIONS_LAST && versionNumbers[i];
         i++) {
        VIR_DEBUG("Compare detected version %d vs one of the expected versions %d",
                  version, versionNumbers[i]);
        if (version == versionNumbers[i])
            return true;
    }

    return false;
}

bool
virStorageIsFile(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;
}


bool
virStorageIsRelative(const char *backing)
{
    if (backing[0] == '/')
        return false;

    if (!virStorageIsFile(backing))
        return false;

    return true;
}


static int
virStorageFileProbeFormatFromBuf(const char *path,
                                 char *buf,
                                 size_t buflen)
{
    int format = VIR_STORAGE_FILE_RAW;
    size_t i;
    int possibleFormat = VIR_STORAGE_FILE_RAW;
    VIR_DEBUG("path=%s, buf=%p, buflen=%zu", path, buf, buflen);

    /* First check file magic */
    for (i = 0; i < VIR_STORAGE_FILE_LAST; i++) {
        if (virStorageFileMatchesMagic(fileTypeInfo[i].magicOffset,
                                       fileTypeInfo[i].magic,
                                       buf, buflen)) {
            if (!virStorageFileMatchesVersion(fileTypeInfo[i].versionOffset,
                                              fileTypeInfo[i].versionSize,
                                              fileTypeInfo[i].versionNumbers,
                                              fileTypeInfo[i].endian,
                                              buf, buflen)) {
                possibleFormat = i;
                continue;
            }
            format = i;
            goto cleanup;
        }
    }

    if (possibleFormat != VIR_STORAGE_FILE_RAW)
        VIR_WARN("File %s matches %s magic, but version is wrong. "
                 "Please report new version to libvir-list@redhat.com",
                 path, virStorageFileFormatTypeToString(possibleFormat));

 cleanup:
    VIR_DEBUG("format=%d", format);
    return format;
}


static int
qcow2GetFeatures(virBitmapPtr *features,
                 int format,
                 char *buf,
                 ssize_t len)
{
    int version = -1;
    virBitmapPtr feat = NULL;
    uint64_t bits;
    size_t i;

    version = virReadBufInt32BE(buf + fileTypeInfo[format].versionOffset);

    if (version == 2)
        return 0;

    if (len < QCOW2v3_HDR_SIZE)
        return -1;

    feat = virBitmapNew(VIR_STORAGE_FILE_FEATURE_LAST);

    /* todo: check for incompatible or autoclear features? */
    bits = virReadBufInt64BE(buf + QCOW2v3_HDR_FEATURES_COMPATIBLE);
    for (i = 0; i < QCOW2_COMPATIBLE_FEATURE_LAST; i++) {
        if (bits & ((uint64_t) 1 << i))
            ignore_value(virBitmapSetBit(feat, qcow2CompatibleFeatureArray[i]));
    }

    *features = feat;
    return 0;
}


static bool
virStorageFileHasEncryptionFormat(const struct FileEncryptionInfo *info,
                                  char *buf,
                                  size_t len)
{
    if (!info->magic && info->modeOffset == -1)
        return false; /* Shouldn't happen - expect at least one */

    if (info->magic) {
        if (!virStorageFileMatchesMagic(info->magicOffset,
                                        info->magic,
                                        buf, len))
            return false;

        if (info->versionOffset != -1 &&
            !virStorageFileMatchesVersion(info->versionOffset,
                                          info->versionSize,
                                          info->versionNumbers,
                                          info->endian,
                                          buf, len))
            return false;

        return true;
    } else if (info->modeOffset != -1) {
        int crypt_format;

        if (info->modeOffset >= len)
            return false;

        crypt_format = virReadBufInt32BE(buf + info->modeOffset);
        if (crypt_format != info->modeValue)
            return false;

        return true;
    } else {
        return false;
    }
}


static int
virStorageFileGetEncryptionPayloadOffset(const struct FileEncryptionInfo *info,
                                         char *buf)
{
    int payload_offset = -1;

    if (info->payloadOffset != -1) {
        if (info->endian == LV_LITTLE_ENDIAN)
            payload_offset = virReadBufInt32LE(buf + info->payloadOffset);
        else
            payload_offset = virReadBufInt32BE(buf + info->payloadOffset);
    }

    return payload_offset;
}


/* Given a header in BUF with length LEN, as parsed from the storage file
 * assuming it has the given FORMAT, populate information into META
 * with information about the file and its backing store. Return format
 * of the backing store as BACKING_FORMAT. PATH and FORMAT have to be
 * pre-populated in META.
 *
 * Note that this function may be called repeatedly on @meta, so it must
 * clean up any existing allocated memory which would be overwritten.
 */
static int
virStorageFileGetMetadataInternal(virStorageSourcePtr meta,
                                  char *buf,
                                  size_t len)
{
    int format;
    size_t i;

    VIR_DEBUG("path=%s, buf=%p, len=%zu, meta->format=%d",
              meta->path, buf, len, meta->format);

    if (meta->format == VIR_STORAGE_FILE_AUTO)
        meta->format = virStorageFileProbeFormatFromBuf(meta->path, buf, len);

    if (meta->format <= VIR_STORAGE_FILE_NONE ||
        meta->format >= VIR_STORAGE_FILE_LAST) {
        virReportSystemError(EINVAL, _("unknown storage file meta->format %d"),
                             meta->format);
        return -1;
    }

    if (fileTypeInfo[meta->format].cryptInfo != NULL) {
        for (i = 0; fileTypeInfo[meta->format].cryptInfo[i].format != 0; i++) {
            if (virStorageFileHasEncryptionFormat(&fileTypeInfo[meta->format].cryptInfo[i],
                                                  buf, len)) {
                int expt_fmt = fileTypeInfo[meta->format].cryptInfo[i].format;
                if (!meta->encryption) {
                    meta->encryption = g_new0(virStorageEncryption, 1);
                    meta->encryption->format = expt_fmt;
                } else {
                    if (meta->encryption->format != expt_fmt) {
                        virReportError(VIR_ERR_XML_ERROR,
                                       _("encryption format %d doesn't match "
                                         "expected format %d"),
                                       meta->encryption->format, expt_fmt);
                        return -1;
                    }
                }
                meta->encryption->payload_offset =
                    virStorageFileGetEncryptionPayloadOffset(&fileTypeInfo[meta->format].cryptInfo[i], buf);
            }
        }
    }

    /* XXX we should consider moving virStorageBackendUpdateVolInfo
     * code into this method, for non-magic files
     */
    if (!fileTypeInfo[meta->format].magic)
        return 0;

    /* Optionally extract capacity from file */
    if (fileTypeInfo[meta->format].sizeOffset != -1) {
        if ((fileTypeInfo[meta->format].sizeOffset + 8) > len)
            return 0;

        if (fileTypeInfo[meta->format].endian == LV_LITTLE_ENDIAN)
            meta->capacity = virReadBufInt64LE(buf +
                                               fileTypeInfo[meta->format].sizeOffset);
        else
            meta->capacity = virReadBufInt64BE(buf +
                                               fileTypeInfo[meta->format].sizeOffset);
        /* Avoid unlikely, but theoretically possible overflow */
        if (meta->capacity > (ULLONG_MAX /
                              fileTypeInfo[meta->format].sizeMultiplier))
            return 0;
        meta->capacity *= fileTypeInfo[meta->format].sizeMultiplier;
    }

    VIR_FREE(meta->backingStoreRaw);
    if (fileTypeInfo[meta->format].getBackingStore != NULL) {
        int store = fileTypeInfo[meta->format].getBackingStore(&meta->backingStoreRaw,
                                                               &format,
                                                               buf, len);
        meta->backingStoreRawFormat = format;

        if (store == BACKING_STORE_INVALID)
            return 0;

        if (store == BACKING_STORE_ERROR)
            return -1;
    }

    virBitmapFree(meta->features);
    meta->features = NULL;
    if (fileTypeInfo[meta->format].getFeatures != NULL &&
        fileTypeInfo[meta->format].getFeatures(&meta->features, meta->format, buf, len) < 0)
        return -1;

    VIR_FREE(meta->compat);
    if (meta->format == VIR_STORAGE_FILE_QCOW2 && meta->features)
        meta->compat = g_strdup("1.1");

    return 0;
}


/**
 * virStorageFileProbeFormat:
 *
 * Probe for the format of 'path', returning the detected
 * disk format.
 *
 * Callers are advised never to trust the returned 'format'
 * unless it is listed as VIR_STORAGE_FILE_RAW, since a
 * malicious guest can turn a raw file into any other non-raw
 * format at will.
 *
 * Best option: Don't use this function
 */
int
virStorageFileProbeFormat(const char *path, uid_t uid, gid_t gid)
{
    struct stat sb;
    ssize_t len = VIR_STORAGE_MAX_HEADER;
    VIR_AUTOCLOSE fd = -1;
    g_autofree char *header = NULL;

    if ((fd = virFileOpenAs(path, O_RDONLY, 0, uid, gid, 0)) < 0) {
        virReportSystemError(-fd, _("Failed to open file '%s'"), path);
        return -1;
    }

    if (fstat(fd, &sb) < 0) {
        virReportSystemError(errno, _("cannot stat file '%s'"), path);
        return -1;
    }

    /* No header to probe for directories */
    if (S_ISDIR(sb.st_mode))
        return VIR_STORAGE_FILE_DIR;

    if (lseek(fd, 0, SEEK_SET) == (off_t)-1) {
        virReportSystemError(errno, _("cannot set to start of '%s'"), path);
        return -1;
    }

    if ((len = virFileReadHeaderFD(fd, len, &header)) < 0) {
        virReportSystemError(errno, _("cannot read header '%s'"), path);
        return -1;
    }

    return virStorageFileProbeFormatFromBuf(path, header, len);
}


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 (virStorageFileGetMetadataInternal(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 (virStorageFileGetMetadataInternal(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);
}


#ifdef WITH_UDEV
/* virStorageFileGetSCSIKey
 * @path: Path to the SCSI device
 * @key: Unique key to be returned
 * @ignoreError: Used to not report ENOSYS
 *
 * Using a udev specific function, query the @path to get and return a
 * unique @key for the caller to use.
 *
 * Returns:
 *     0 On success, with the @key filled in or @key=NULL if the
 *       returned string was empty.
 *    -1 When WITH_UDEV is undefined and a system error is reported
 *    -2 When WITH_UDEV is defined, but calling virCommandRun fails
 */
int
virStorageFileGetSCSIKey(const char *path,
                         char **key,
                         bool ignoreError G_GNUC_UNUSED)
{
    int status;
    g_autoptr(virCommand) cmd = NULL;

    cmd = virCommandNewArgList("/lib/udev/scsi_id",
                               "--replace-whitespace",
                               "--whitelisted",
                               "--device", path,
                               NULL
                               );
    *key = NULL;

    /* Run the program and capture its output */
    virCommandSetOutputBuffer(cmd, key);
    if (virCommandRun(cmd, &status) < 0)
        return -2;

    /* Explicitly check status == 0, rather than passing NULL
     * to virCommandRun because we don't want to raise an actual
     * error in this scenario, just return a NULL key.
     */
    if (status == 0 && *key) {
        char *nl = strchr(*key, '\n');
        if (nl)
            *nl = '\0';
    }

    if (*key && STREQ(*key, ""))
        VIR_FREE(*key);

    return 0;
}
#else
int virStorageFileGetSCSIKey(const char *path,
                             char **key G_GNUC_UNUSED,
                             bool ignoreError)
{
    if (!ignoreError)
        virReportSystemError(ENOSYS, _("Unable to get SCSI key for %s"), path);
    return -1;
}
#endif


#ifdef WITH_UDEV
/* virStorageFileGetNPIVKey
 * @path: Path to the NPIV device
 * @key: Unique key to be returned
 *
 * Using a udev specific function, query the @path to get and return a
 * unique @key for the caller to use. Unlike the GetSCSIKey method, an
 * NPIV LUN is uniquely identified by its ID_TARGET_PORT value.
 *
 * Returns:
 *     0 On success, with the @key filled in or @key=NULL if the
 *       returned output string didn't have the data we need to
 *       formulate a unique key value
 *    -1 When WITH_UDEV is undefined and a system error is reported
 *    -2 When WITH_UDEV is defined, but calling virCommandRun fails
 */
# define ID_SERIAL "ID_SERIAL="
# define ID_TARGET_PORT "ID_TARGET_PORT="
int
virStorageFileGetNPIVKey(const char *path,
                         char **key)
{
    int status;
    const char *serial;
    const char *port;
    g_autofree char *outbuf = NULL;
    g_autoptr(virCommand) cmd = NULL;

    cmd = virCommandNewArgList("/lib/udev/scsi_id",
                               "--replace-whitespace",
                               "--whitelisted",
                               "--export",
                               "--device", path,
                               NULL
                               );
    *key = NULL;

    /* Run the program and capture its output */
    virCommandSetOutputBuffer(cmd, &outbuf);
    if (virCommandRun(cmd, &status) < 0)
        return -2;

    /* Explicitly check status == 0, rather than passing NULL
     * to virCommandRun because we don't want to raise an actual
     * error in this scenario, just return a NULL key.
     */
    if (status == 0 && *outbuf &&
        (serial = strstr(outbuf, ID_SERIAL)) &&
        (port = strstr(outbuf, ID_TARGET_PORT))) {
        char *tmp;

        serial += strlen(ID_SERIAL);
        port += strlen(ID_TARGET_PORT);

        if ((tmp = strchr(serial, '\n')))
            *tmp = '\0';

        if ((tmp = strchr(port, '\n')))
            *tmp = '\0';

        if (*serial != '\0' && *port != '\0')
            *key = g_strdup_printf("%s_PORT%s", serial, port);
    }

    return 0;
}
#else
int virStorageFileGetNPIVKey(const char *path G_GNUC_UNUSED,
                             char **key G_GNUC_UNUSED)
{
    return -1;
}
#endif

/**
 * virStorageFileParseBackingStoreStr:
 * @str: backing store specifier string to parse
 * @target: returns target device portion of the string
 * @chainIndex: returns the backing store portion of the string
 *
 * Parses the backing store specifier string such as vda[1], or sda into
 * components and returns them via arguments. If the string did not specify an
 * index, 0 is assumed.
 *
 * Returns 0 on success -1 on error
 */
int
virStorageFileParseBackingStoreStr(const char *str,
                                   char **target,
                                   unsigned int *chainIndex)
{
    size_t nstrings;
    unsigned int idx = 0;
    char *suffix;
    g_auto(GStrv) strings = NULL;

    *chainIndex = 0;

    if (!(strings = virStringSplitCount(str, "[", 2, &nstrings)))
        return -1;

    if (nstrings == 2) {
        if (virStrToLong_uip(strings[1], &suffix, 10, &idx) < 0 ||
            STRNEQ(suffix, "]"))
            return -1;
    }

    if (target)
        *target = g_strdup(strings[0]);

    *chainIndex = idx;
    return 0;
}


int
virStorageFileParseChainIndex(const char *diskTarget,
                              const char *name,
                              unsigned int *chainIndex)
{
    unsigned int idx = 0;
    g_autofree char *target = NULL;

    *chainIndex = 0;

    if (!name || !diskTarget)
        return 0;

    if (virStorageFileParseBackingStoreStr(name, &target, &idx) < 0)
        return 0;

    if (idx == 0)
        return 0;

    if (STRNEQ(diskTarget, target)) {
        virReportError(VIR_ERR_INVALID_ARG,
                       _("requested target '%s' does not match target '%s'"),
                       target, diskTarget);
        return -1;
    }

    *chainIndex = idx;

    return 0;
}


/**
 * virStorageSourceIsBacking:
 * @src: storage source
 *
 * Returns true if @src is a eligible backing store structure. Useful
 * for iterators.
 */
bool
virStorageSourceIsBacking(const virStorageSource *src)
{
    return src && src->type != VIR_STORAGE_TYPE_NONE;
}

/**
 * virStorageSourceHasBacking:
 * @src: storage source
 *
 * Returns true if @src has backing store/chain.
 */
bool
virStorageSourceHasBacking(const virStorageSource *src)
{
    return virStorageSourceIsBacking(src) && src->backingStore &&
           src->backingStore->type != VIR_STORAGE_TYPE_NONE;
}


/* 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;
}


void
virStorageNetHostDefClear(virStorageNetHostDefPtr def)
{
    if (!def)
        return;

    VIR_FREE(def->name);
    VIR_FREE(def->socket);
}


void
virStorageNetHostDefFree(size_t nhosts,
                         virStorageNetHostDefPtr hosts)
{
    size_t i;

    if (!hosts)
        return;

    for (i = 0; i < nhosts; i++)
        virStorageNetHostDefClear(&hosts[i]);

    VIR_FREE(hosts);
}


static void
virStoragePermsFree(virStoragePermsPtr def)
{
    if (!def)
        return;

    VIR_FREE(def->label);
    VIR_FREE(def);
}


virStorageNetHostDefPtr
virStorageNetHostDefCopy(size_t nhosts,
                         virStorageNetHostDefPtr hosts)
{
    virStorageNetHostDefPtr ret = NULL;
    size_t i;

    ret = g_new0(virStorageNetHostDef, nhosts);

    for (i = 0; i < nhosts; i++) {
        virStorageNetHostDefPtr src = &hosts[i];
        virStorageNetHostDefPtr dst = &ret[i];

        dst->transport = src->transport;
        dst->port = src->port;

        dst->name = g_strdup(src->name);
        dst->socket = g_strdup(src->socket);
    }

    return ret;
}


void
virStorageAuthDefFree(virStorageAuthDefPtr authdef)
{
    if (!authdef)
        return;

    VIR_FREE(authdef->username);
    VIR_FREE(authdef->secrettype);
    virSecretLookupDefClear(&authdef->seclookupdef);
    VIR_FREE(authdef);
}


virStorageAuthDefPtr
virStorageAuthDefCopy(const virStorageAuthDef *src)
{
    g_autoptr(virStorageAuthDef) authdef = NULL;

    authdef = g_new0(virStorageAuthDef, 1);

    authdef->username = g_strdup(src->username);
    /* Not present for storage pool, but used for disk source */
    authdef->secrettype = g_strdup(src->secrettype);
    authdef->authType = src->authType;

    virSecretLookupDefCopy(&authdef->seclookupdef, &src->seclookupdef);

    return g_steal_pointer(&authdef);
}


virStorageAuthDefPtr
virStorageAuthDefParse(xmlNodePtr node,
                       xmlXPathContextPtr ctxt)
{
    VIR_XPATH_NODE_AUTORESTORE(ctxt)
    virStorageAuthDefPtr ret = NULL;
    xmlNodePtr secretnode = NULL;
    g_autoptr(virStorageAuthDef) authdef = NULL;
    g_autofree char *authtype = NULL;

    ctxt->node = node;

    authdef = g_new0(virStorageAuthDef, 1);

    if (!(authdef->username = virXPathString("string(./@username)", ctxt))) {
        virReportError(VIR_ERR_XML_ERROR, "%s",
                       _("missing username for auth"));
        goto cleanup;
    }

    authdef->authType = VIR_STORAGE_AUTH_TYPE_NONE;
    authtype = virXPathString("string(./@type)", ctxt);
    if (authtype) {
        /* Used by the storage pool instead of the secret type field
         * to define whether chap or ceph being used
         */
        if ((authdef->authType = virStorageAuthTypeFromString(authtype)) < 0) {
            virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
                           _("unknown auth type '%s'"), authtype);
            goto cleanup;
        }
    }

    if (!(secretnode = virXPathNode("./secret ", ctxt))) {
        virReportError(VIR_ERR_XML_ERROR, "%s",
                       _("Missing <secret> element in auth"));
        goto cleanup;
    }

    /* Used by the domain disk xml parsing in order to ensure the
     * <secret type='%s' value matches the expected secret type for
     * the style of disk (iscsi is chap, nbd is ceph). For some reason
     * the virSecretUsageType{From|To}String() cannot be linked here
     * and because only the domain parsing code cares - just keep
     * it as a string.
     */
    authdef->secrettype = virXMLPropString(secretnode, "type");

    if (virSecretLookupParseSecret(secretnode, &authdef->seclookupdef) < 0)
        goto cleanup;

    ret = g_steal_pointer(&authdef);

 cleanup:

    return ret;
}


void
virStorageAuthDefFormat(virBufferPtr buf,
                        virStorageAuthDefPtr authdef)
{
    if (authdef->authType == VIR_STORAGE_AUTH_TYPE_NONE) {
        virBufferEscapeString(buf, "<auth username='%s'>\n", authdef->username);
    } else {
        virBufferAsprintf(buf, "<auth type='%s' ",
                          virStorageAuthTypeToString(authdef->authType));
        virBufferEscapeString(buf, "username='%s'>\n", authdef->username);
    }

    virBufferAdjustIndent(buf, 2);
    virSecretLookupFormatSecret(buf, authdef->secrettype,
                                &authdef->seclookupdef);
    virBufferAdjustIndent(buf, -2);
    virBufferAddLit(buf, "</auth>\n");
}


void
virStoragePRDefFree(virStoragePRDefPtr prd)
{
    if (!prd)
        return;

    VIR_FREE(prd->path);
    VIR_FREE(prd->mgralias);
    VIR_FREE(prd);
}


virStoragePRDefPtr
virStoragePRDefParseXML(xmlXPathContextPtr ctxt)
{
    virStoragePRDefPtr prd;
    virStoragePRDefPtr ret = NULL;
    g_autofree char *managed = NULL;
    g_autofree char *type = NULL;
    g_autofree char *path = NULL;
    g_autofree char *mode = NULL;

    prd = g_new0(virStoragePRDef, 1);

    if (!(managed = virXPathString("string(./@managed)", ctxt))) {
        virReportError(VIR_ERR_XML_ERROR, "%s",
                       _("missing @managed attribute for <reservations/>"));
        goto cleanup;
    }

    if ((prd->managed = virTristateBoolTypeFromString(managed)) <= 0) {
        virReportError(VIR_ERR_XML_ERROR,
                       _("invalid value for 'managed': %s"), managed);
        goto cleanup;
    }

    type = virXPathString("string(./source[1]/@type)", ctxt);
    path = virXPathString("string(./source[1]/@path)", ctxt);
    mode = virXPathString("string(./source[1]/@mode)", ctxt);

    if (prd->managed == VIR_TRISTATE_BOOL_NO || type || path || mode) {
        if (!type) {
            virReportError(VIR_ERR_XML_ERROR, "%s",
                           _("missing connection type for <reservations/>"));
            goto cleanup;
        }

        if (!path) {
            virReportError(VIR_ERR_XML_ERROR, "%s",
                           _("missing path for <reservations/>"));
            goto cleanup;
        }

        if (!mode) {
            virReportError(VIR_ERR_XML_ERROR, "%s",
                           _("missing connection mode for <reservations/>"));
            goto cleanup;
        }
    }

    if (type && STRNEQ(type, "unix")) {
        virReportError(VIR_ERR_XML_ERROR,
                       _("unsupported connection type for <reservations/>: %s"),
                       type);
        goto cleanup;
    }

    if (mode && STRNEQ(mode, "client")) {
        virReportError(VIR_ERR_XML_ERROR,
                       _("unsupported connection mode for <reservations/>: %s"),
                       mode);
        goto cleanup;
    }

    prd->path = g_steal_pointer(&path);
    ret = g_steal_pointer(&prd);

 cleanup:
    virStoragePRDefFree(prd);
    return ret;
}


void
virStoragePRDefFormat(virBufferPtr buf,
                      virStoragePRDefPtr prd,
                      bool migratable)
{
    virBufferAsprintf(buf, "<reservations managed='%s'",
                      virTristateBoolTypeToString(prd->managed));
    if (prd->path &&
        (prd->managed == VIR_TRISTATE_BOOL_NO || !migratable)) {
        virBufferAddLit(buf, ">\n");
        virBufferAdjustIndent(buf, 2);
        virBufferAddLit(buf, "<source type='unix'");
        virBufferEscapeString(buf, " path='%s'", prd->path);
        virBufferAddLit(buf, " mode='client'/>\n");
        virBufferAdjustIndent(buf, -2);
        virBufferAddLit(buf, "</reservations>\n");
    } else {
        virBufferAddLit(buf, "/>\n");
    }
}


bool
virStoragePRDefIsEqual(virStoragePRDefPtr a,
                       virStoragePRDefPtr b)
{
    if (!a && !b)
        return true;

    if (!a || !b)
        return false;

    if (a->managed != b->managed ||
        STRNEQ_NULLABLE(a->path, b->path))
        return false;

    return true;
}


bool
virStoragePRDefIsManaged(virStoragePRDefPtr prd)
{
    return prd && prd->managed == VIR_TRISTATE_BOOL_YES;
}


bool
virStorageSourceChainHasManagedPR(virStorageSourcePtr src)
{
    virStorageSourcePtr n;

    for (n = src; virStorageSourceIsBacking(n); n = n->backingStore) {
        if (virStoragePRDefIsManaged(n->pr))
            return true;
    }

    return false;
}


static virStoragePRDefPtr
virStoragePRDefCopy(virStoragePRDefPtr src)
{
    virStoragePRDefPtr copy = NULL;
    virStoragePRDefPtr ret = NULL;

    copy = g_new0(virStoragePRDef, 1);

    copy->managed = src->managed;

    copy->path = g_strdup(src->path);
    copy->mgralias = g_strdup(src->mgralias);

    ret = g_steal_pointer(&copy);

    virStoragePRDefFree(copy);
    return ret;
}


static virStorageSourceNVMeDefPtr
virStorageSourceNVMeDefCopy(const virStorageSourceNVMeDef *src)
{
    virStorageSourceNVMeDefPtr ret = NULL;

    ret = g_new0(virStorageSourceNVMeDef, 1);

    ret->namespc = src->namespc;
    ret->managed = src->managed;
    virPCIDeviceAddressCopy(&ret->pciAddr, &src->pciAddr);
    return ret;
}


static bool
virStorageSourceNVMeDefIsEqual(const virStorageSourceNVMeDef *a,
                               const virStorageSourceNVMeDef *b)
{
    if (!a && !b)
        return true;

    if (!a || !b)
        return false;

    if (a->namespc != b->namespc ||
        a->managed != b->managed ||
        !virPCIDeviceAddressEqual(&a->pciAddr, &b->pciAddr))
        return false;

    return true;
}


void
virStorageSourceNVMeDefFree(virStorageSourceNVMeDefPtr def)
{
    if (!def)
        return;

    VIR_FREE(def);
}


bool
virStorageSourceChainHasNVMe(const virStorageSource *src)
{
    const virStorageSource *n;

    for (n = src; virStorageSourceIsBacking(n); n = n->backingStore) {
        if (n->type == VIR_STORAGE_TYPE_NVME)
            return true;
    }

    return false;
}


virSecurityDeviceLabelDefPtr
virStorageSourceGetSecurityLabelDef(virStorageSourcePtr src,
                                    const char *model)
{
    size_t i;

    for (i = 0; i < src->nseclabels; i++) {
        if (STREQ_NULLABLE(src->seclabels[i]->model, model))
            return src->seclabels[i];
    }

    return NULL;
}


static void
virStorageSourceSeclabelsClear(virStorageSourcePtr def)
{
    size_t i;

    if (def->seclabels) {
        for (i = 0; i < def->nseclabels; i++)
            virSecurityDeviceLabelDefFree(def->seclabels[i]);
        VIR_FREE(def->seclabels);
    }
}


static int
virStorageSourceSeclabelsCopy(virStorageSourcePtr to,
                              const virStorageSource *from)
{
    size_t i;

    if (from->nseclabels == 0)
        return 0;

    to->seclabels = g_new0(virSecurityDeviceLabelDefPtr, from->nseclabels);
    to->nseclabels = from->nseclabels;

    for (i = 0; i < to->nseclabels; i++) {
        if (!(to->seclabels[i] = virSecurityDeviceLabelDefCopy(from->seclabels[i])))
            goto error;
    }

    return 0;

 error:
    virStorageSourceSeclabelsClear(to);
    return -1;
}


void
virStorageNetCookieDefFree(virStorageNetCookieDefPtr def)
{
    if (!def)
        return;

    g_free(def->name);
    g_free(def->value);

    g_free(def);
}


static void
virStorageSourceNetCookiesClear(virStorageSourcePtr src)
{
    size_t i;

    if (!src || !src->cookies)
        return;

    for (i = 0; i < src->ncookies; i++)
        virStorageNetCookieDefFree(src->cookies[i]);

    g_clear_pointer(&src->cookies, g_free);
    src->ncookies = 0;
}


static void
virStorageSourceNetCookiesCopy(virStorageSourcePtr to,
                               const virStorageSource *from)
{
    size_t i;

    if (from->ncookies == 0)
        return;

    to->cookies = g_new0(virStorageNetCookieDefPtr, from->ncookies);
    to->ncookies = from->ncookies;

    for (i = 0; i < from->ncookies; i++) {
        to->cookies[i]->name = g_strdup(from->cookies[i]->name);
        to->cookies[i]->value = g_strdup(from->cookies[i]->value);
    }
}


/* see https://tools.ietf.org/html/rfc6265#section-4.1.1 */
static const char virStorageSourceCookieValueInvalidChars[] =
 "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F"
 "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F"
 " \",;\\";

/* in addition cookie name can't contain these */
static const char virStorageSourceCookieNameInvalidChars[] =
 "()<>@:/[]?={}";

static int
virStorageSourceNetCookieValidate(virStorageNetCookieDefPtr def)
{
    g_autofree char *val = g_strdup(def->value);
    const char *checkval = val;
    size_t len = strlen(val);

    /* name must have at least 1 character */
    if (*(def->name) == '\0') {
        virReportError(VIR_ERR_XML_ERROR, "%s",
                       _("cookie name must not be empty"));
        return -1;
    }

    /* check invalid characters in name */
    if (virStringHasChars(def->name, virStorageSourceCookieValueInvalidChars) ||
        virStringHasChars(def->name, virStorageSourceCookieNameInvalidChars)) {
        virReportError(VIR_ERR_XML_ERROR,
                       _("cookie name '%s' contains invalid characters"),
                       def->name);
        return -1;
    }

    /* check for optional quotes around the cookie value string */
    if (val[0] == '"') {
        if (val[len - 1] != '"') {
            virReportError(VIR_ERR_XML_ERROR,
                           _("value of cookie '%s' contains invalid characters"),
                           def->name);
            return -1;
        }

        val[len - 1] = '\0';
        checkval++;
    }

    /* check invalid characters in value */
    if (virStringHasChars(checkval, virStorageSourceCookieValueInvalidChars)) {
        virReportError(VIR_ERR_XML_ERROR,
                       _("value of cookie '%s' contains invalid characters"),
                       def->name);
        return -1;
    }

    return 0;
}


int
virStorageSourceNetCookiesValidate(virStorageSourcePtr src)
{
    size_t i;
    size_t j;

    for (i = 0; i < src->ncookies; i++) {
        if (virStorageSourceNetCookieValidate(src->cookies[i]) < 0)
            return -1;

        for (j = i + 1; j < src->ncookies; j++) {
            if (STREQ(src->cookies[i]->name, src->cookies[j]->name)) {
                virReportError(VIR_ERR_XML_ERROR, _("duplicate cookie '%s'"),
                               src->cookies[i]->name);
                return -1;
            }
        }
    }

    return 0;
}


static virStorageTimestampsPtr
virStorageTimestampsCopy(const virStorageTimestamps *src)
{
    virStorageTimestampsPtr ret;

    ret = g_new0(virStorageTimestamps, 1);

    memcpy(ret, src, sizeof(*src));

    return ret;
}


static virStoragePermsPtr
virStoragePermsCopy(const virStoragePerms *src)
{
    virStoragePermsPtr ret;

    ret = g_new0(virStoragePerms, 1);

    ret->mode = src->mode;
    ret->uid = src->uid;
    ret->gid = src->gid;

    ret->label = g_strdup(src->label);

    return ret;
}


static virStorageSourcePoolDefPtr
virStorageSourcePoolDefCopy(const virStorageSourcePoolDef *src)
{
    virStorageSourcePoolDefPtr ret;

    ret = g_new0(virStorageSourcePoolDef, 1);

    ret->voltype = src->voltype;
    ret->pooltype = src->pooltype;
    ret->actualtype = src->actualtype;
    ret->mode = src->mode;

    ret->pool = g_strdup(src->pool);
    ret->volume = g_strdup(src->volume);

    return ret;
}


static virStorageSourceSlicePtr
virStorageSourceSliceCopy(const virStorageSourceSlice *src)
{
    virStorageSourceSlicePtr ret = g_new0(virStorageSourceSlice, 1);

    ret->offset = src->offset;
    ret->size = src->size;
    ret->nodename = g_strdup(src->nodename);

    return ret;
}


static void
virStorageSourceSliceFree(virStorageSourceSlicePtr slice)
{
    if (!slice)
        return;

    g_free(slice->nodename);
    g_free(slice);
}


/**
 * virStorageSourcePtr:
 *
 * Deep-copies a virStorageSource structure. If @backing chain is true
 * then also copies the backing chain recursively, otherwise just
 * the top element is copied. This function doesn't copy the
 * storage driver access structure and thus the struct needs to be initialized
 * separately.
 */
virStorageSourcePtr
virStorageSourceCopy(const virStorageSource *src,
                     bool backingChain)
{
    g_autoptr(virStorageSource) def = virStorageSourceNew();

    def->id = src->id;
    def->type = src->type;
    def->protocol = src->protocol;
    def->format = src->format;
    def->capacity = src->capacity;
    def->allocation = src->allocation;
    def->has_allocation = src->has_allocation;
    def->physical = src->physical;
    def->readonly = src->readonly;
    def->shared = src->shared;
    def->haveTLS = src->haveTLS;
    def->tlsFromConfig = src->tlsFromConfig;
    def->detected = src->detected;
    def->debugLevel = src->debugLevel;
    def->debug = src->debug;
    def->iomode = src->iomode;
    def->cachemode = src->cachemode;
    def->discard = src->discard;
    def->detect_zeroes = src->detect_zeroes;
    def->sslverify = src->sslverify;
    def->readahead = src->readahead;
    def->timeout = src->timeout;
    def->metadataCacheMaxSize = src->metadataCacheMaxSize;

    /* storage driver metadata are not copied */
    def->drv = NULL;

    def->path = g_strdup(src->path);
    def->volume = g_strdup(src->volume);
    def->relPath = g_strdup(src->relPath);
    def->backingStoreRaw = g_strdup(src->backingStoreRaw);
    def->backingStoreRawFormat = src->backingStoreRawFormat;
    def->snapshot = g_strdup(src->snapshot);
    def->configFile = g_strdup(src->configFile);
    def->nodeformat = g_strdup(src->nodeformat);
    def->nodestorage = g_strdup(src->nodestorage);
    def->compat = g_strdup(src->compat);
    def->tlsAlias = g_strdup(src->tlsAlias);
    def->tlsCertdir = g_strdup(src->tlsCertdir);
    def->query = g_strdup(src->query);

    if (src->sliceStorage)
        def->sliceStorage = virStorageSourceSliceCopy(src->sliceStorage);

    if (src->nhosts) {
        if (!(def->hosts = virStorageNetHostDefCopy(src->nhosts, src->hosts)))
            return NULL;

        def->nhosts = src->nhosts;
    }

    virStorageSourceNetCookiesCopy(def, src);

    if (src->srcpool &&
        !(def->srcpool = virStorageSourcePoolDefCopy(src->srcpool)))
        return NULL;

    if (src->features)
        def->features = virBitmapNewCopy(src->features);

    if (src->encryption &&
        !(def->encryption = virStorageEncryptionCopy(src->encryption)))
        return NULL;

    if (src->perms &&
        !(def->perms = virStoragePermsCopy(src->perms)))
        return NULL;

    if (src->timestamps &&
        !(def->timestamps = virStorageTimestampsCopy(src->timestamps)))
        return NULL;

    if (virStorageSourceSeclabelsCopy(def, src) < 0)
        return NULL;

    if (src->auth &&
        !(def->auth = virStorageAuthDefCopy(src->auth)))
        return NULL;

    if (src->pr &&
        !(def->pr = virStoragePRDefCopy(src->pr)))
        return NULL;

    if (src->nvme)
        def->nvme = virStorageSourceNVMeDefCopy(src->nvme);

    if (virStorageSourceInitiatorCopy(&def->initiator, &src->initiator) < 0)
        return NULL;

    if (backingChain && src->backingStore) {
        if (!(def->backingStore = virStorageSourceCopy(src->backingStore,
                                                       true)))
            return NULL;
    }

    /* ssh config passthrough for libguestfs */
    def->ssh_host_key_check_disabled = src->ssh_host_key_check_disabled;
    def->ssh_user = g_strdup(src->ssh_user);

    def->nfs_user = g_strdup(src->nfs_user);
    def->nfs_group = g_strdup(src->nfs_group);
    def->nfs_uid = src->nfs_uid;
    def->nfs_gid = src->nfs_gid;

    return g_steal_pointer(&def);
}


/**
 * virStorageSourceIsSameLocation:
 *
 * Returns true if the sources @a and @b point to the same storage location.
 * This does not compare any other configuration option
 */
bool
virStorageSourceIsSameLocation(virStorageSourcePtr a,
                               virStorageSourcePtr b)
{
    size_t i;

    /* there are multiple possibilities to define an empty source */
    if (virStorageSourceIsEmpty(a) &&
        virStorageSourceIsEmpty(b))
        return true;

    if (virStorageSourceGetActualType(a) != virStorageSourceGetActualType(b))
        return false;

    if (STRNEQ_NULLABLE(a->path, b->path) ||
        STRNEQ_NULLABLE(a->volume, b->volume) ||
        STRNEQ_NULLABLE(a->snapshot, b->snapshot))
        return false;

    if (a->type == VIR_STORAGE_TYPE_NETWORK) {
        if (a->protocol != b->protocol ||
            a->nhosts != b->nhosts)
            return false;

        for (i = 0; i < a->nhosts; i++) {
            if (a->hosts[i].transport != b->hosts[i].transport ||
                a->hosts[i].port != b->hosts[i].port ||
                STRNEQ_NULLABLE(a->hosts[i].name, b->hosts[i].name) ||
                STRNEQ_NULLABLE(a->hosts[i].socket, b->hosts[i].socket))
                return false;
        }
    }

    if (a->type == VIR_STORAGE_TYPE_NVME &&
        !virStorageSourceNVMeDefIsEqual(a->nvme, b->nvme))
        return false;

    return true;
}


/**
 * virStorageSourceInitChainElement:
 * @newelem: New backing chain element disk source
 * @old: Existing top level disk source
 * @transferLabels: Transfer security labels.
 *
 * Transfers relevant information from the existing disk source to the new
 * backing chain element if they weren't supplied so that labelling info
 * and possibly other stuff is correct.
 *
 * If @transferLabels is true, security labels from the existing disk are copied
 * to the new disk. Otherwise the default domain imagelabel label will be used.
 *
 * Returns 0 on success, -1 on error.
 */
int
virStorageSourceInitChainElement(virStorageSourcePtr newelem,
                                 virStorageSourcePtr old,
                                 bool transferLabels)
{
    if (transferLabels &&
        !newelem->seclabels &&
        virStorageSourceSeclabelsCopy(newelem, old) < 0)
        return -1;

    newelem->shared = old->shared;
    newelem->readonly = old->readonly;

    return 0;
}


void
virStorageSourcePoolDefFree(virStorageSourcePoolDefPtr def)
{
    if (!def)
        return;

    VIR_FREE(def->pool);
    VIR_FREE(def->volume);

    VIR_FREE(def);
}


/**
 * virStorageSourceGetActualType:
 * @def: storage source definition
 *
 * Returns type of @def. In case when the type is VIR_STORAGE_TYPE_VOLUME
 * and virDomainDiskTranslateSourcePool was called on @def the actual type
 * of the storage volume is returned rather than VIR_STORAGE_TYPE_VOLUME.
 */
int
virStorageSourceGetActualType(const virStorageSource *def)
{
    if (def->type == VIR_STORAGE_TYPE_VOLUME &&
        def->srcpool &&
        def->srcpool->actualtype != VIR_STORAGE_TYPE_NONE)
        return def->srcpool->actualtype;

    return def->type;
}


bool
virStorageSourceIsLocalStorage(const virStorageSource *src)
{
    virStorageType type = virStorageSourceGetActualType(src);

    switch (type) {
    case VIR_STORAGE_TYPE_FILE:
    case VIR_STORAGE_TYPE_BLOCK:
    case VIR_STORAGE_TYPE_DIR:
        return true;

    case VIR_STORAGE_TYPE_NETWORK:
    case VIR_STORAGE_TYPE_VOLUME:
        /* While NVMe disks are local, they are not accessible via src->path.
         * Therefore, we have to return false here. */
    case VIR_STORAGE_TYPE_NVME:
    case VIR_STORAGE_TYPE_LAST:
    case VIR_STORAGE_TYPE_NONE:
        return false;
    }

    return false;
}


/**
 * virStorageSourceIsEmpty:
 *
 * @src: disk source to check
 *
 * Returns true if the guest disk has no associated host storage source
 * (such as an empty cdrom drive).
 */
bool
virStorageSourceIsEmpty(virStorageSourcePtr src)
{
    if (virStorageSourceIsLocalStorage(src) && !src->path)
        return true;

    if (src->type == VIR_STORAGE_TYPE_NONE)
        return true;

    if (src->type == VIR_STORAGE_TYPE_NETWORK &&
        src->protocol == VIR_STORAGE_NET_PROTOCOL_NONE)
        return true;

    return false;
}


/**
 * virStorageSourceIsBlockLocal:
 * @src: disk source definition
 *
 * Returns true if @src describes a locally accessible block storage source.
 * This includes block devices and host-mapped iSCSI volumes.
 */
bool
virStorageSourceIsBlockLocal(const virStorageSource *src)
{
    return virStorageSourceGetActualType(src) == VIR_STORAGE_TYPE_BLOCK;
}


/**
 * virStorageSourceBackingStoreClear:
 *
 * @src: disk source to clear
 *
 * Clears information about backing store of the current storage file.
 */
void
virStorageSourceBackingStoreClear(virStorageSourcePtr def)
{
    if (!def)
        return;

    VIR_FREE(def->relPath);
    VIR_FREE(def->backingStoreRaw);

    /* recursively free backing chain */
    virObjectUnref(def->backingStore);
    def->backingStore = NULL;
}


void
virStorageSourceClear(virStorageSourcePtr def)
{
    if (!def)
        return;

    VIR_FREE(def->path);
    VIR_FREE(def->volume);
    VIR_FREE(def->snapshot);
    VIR_FREE(def->configFile);
    VIR_FREE(def->query);
    virStorageSourceNetCookiesClear(def);
    virStorageSourcePoolDefFree(def->srcpool);
    virBitmapFree(def->features);
    VIR_FREE(def->compat);
    virStorageEncryptionFree(def->encryption);
    virStoragePRDefFree(def->pr);
    virStorageSourceNVMeDefFree(def->nvme);
    virStorageSourceSeclabelsClear(def);
    virStoragePermsFree(def->perms);
    VIR_FREE(def->timestamps);

    virStorageSourceSliceFree(def->sliceStorage);

    virStorageNetHostDefFree(def->nhosts, def->hosts);
    virStorageAuthDefFree(def->auth);
    virObjectUnref(def->privateData);

    VIR_FREE(def->nodestorage);
    VIR_FREE(def->nodeformat);

    virStorageSourceBackingStoreClear(def);

    VIR_FREE(def->tlsAlias);
    VIR_FREE(def->tlsCertdir);

    VIR_FREE(def->ssh_user);

    VIR_FREE(def->nfs_user);
    VIR_FREE(def->nfs_group);

    virStorageSourceInitiatorClear(&def->initiator);

    /* clear everything except the class header as the object APIs
     * will break otherwise */
    memset((char *) def + sizeof(def->parent), 0,
           sizeof(*def) - sizeof(def->parent));
}


static void
virStorageSourceDispose(void *obj)
{
    virStorageSourcePtr src = obj;

    virStorageSourceClear(src);
}


static int
virStorageSourceOnceInit(void)
{
    if (!VIR_CLASS_NEW(virStorageSource, virClassForObject()))
        return -1;

    return 0;
}


VIR_ONCE_GLOBAL_INIT(virStorageSource);


virStorageSourcePtr
virStorageSourceNew(void)
{
    virStorageSourcePtr ret;

    if (virStorageSourceInitialize() < 0)
        abort();

    if (!(ret = virObjectNew(virStorageSourceClass)))
        abort();

    return ret;
}


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;
}


static char *
virStorageFileCanonicalizeFormatPath(char **components,
                                     size_t ncomponents,
                                     bool beginSlash,
                                     bool beginDoubleSlash)
{
    g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
    size_t i;
    char *ret = NULL;

    if (beginSlash)
        virBufferAddLit(&buf, "/");

    if (beginDoubleSlash)
        virBufferAddLit(&buf, "/");

    for (i = 0; i < ncomponents; i++) {
        if (i != 0)
            virBufferAddLit(&buf, "/");

        virBufferAdd(&buf, components[i], -1);
    }

    /* if the output string is empty just return an empty string */
    if (!(ret = virBufferContentAndReset(&buf)))
        ret = g_strdup("");

    return ret;
}


static int
virStorageFileCanonicalizeInjectSymlink(const char *path,
                                        size_t at,
                                        char ***components,
                                        size_t *ncomponents)
{
    char **tmp = NULL;
    char **next;
    size_t ntmp = 0;
    int ret = -1;

    if (!(tmp = virStringSplitCount(path, "/", 0, &ntmp)))
        goto cleanup;

    /* prepend */
    for (next = tmp; *next; next++) {
        if (VIR_INSERT_ELEMENT(*components, at, *ncomponents, *next) < 0)
            goto cleanup;

        at++;
    }

    ret = 0;

 cleanup:
    virStringListFreeCount(tmp, ntmp);
    return ret;
}


char *
virStorageFileCanonicalizePath(const char *path,
                               virStorageFileSimplifyPathReadlinkCallback cb,
                               void *cbdata)
{
    GHashTable *cycle = NULL;
    bool beginSlash = false;
    bool beginDoubleSlash = false;
    char **components = NULL;
    size_t ncomponents = 0;
    size_t i = 0;
    size_t j = 0;
    int rc;
    char *ret = NULL;
    g_autofree char *linkpath = NULL;
    g_autofree char *currentpath = NULL;

    if (path[0] == '/') {
        beginSlash = true;

        if (path[1] == '/' && path[2] != '/')
            beginDoubleSlash = true;
    }

    if (!(cycle = virHashNew(NULL)))
        goto cleanup;

    if (!(components = virStringSplitCount(path, "/", 0, &ncomponents)))
        goto cleanup;

    j = 0;
    while (j < ncomponents) {
        /* skip slashes */
        if (STREQ(components[j], "")) {
            VIR_FREE(components[j]);
            VIR_DELETE_ELEMENT(components, j, ncomponents);
            continue;
        }
        j++;
    }

    while (i < ncomponents) {
        /* skip '.'s unless it's the last one remaining */
        if (STREQ(components[i], ".") &&
            (beginSlash || ncomponents  > 1)) {
            VIR_FREE(components[i]);
            VIR_DELETE_ELEMENT(components, i, ncomponents);
            continue;
        }

        /* resolve changes to parent directory */
        if (STREQ(components[i], "..")) {
            if (!beginSlash &&
                (i == 0 || STREQ(components[i - 1], ".."))) {
                i++;
                continue;
            }

            VIR_FREE(components[i]);
            VIR_DELETE_ELEMENT(components, i, ncomponents);

            if (i != 0) {
                VIR_FREE(components[i - 1]);
                VIR_DELETE_ELEMENT(components, i - 1, ncomponents);
                i--;
            }

            continue;
        }

        /* check if the actual path isn't resulting into a symlink */
        if (!(currentpath = virStorageFileCanonicalizeFormatPath(components,
                                                                 i + 1,
                                                                 beginSlash,
                                                                 beginDoubleSlash)))
            goto cleanup;

        if ((rc = cb(currentpath, &linkpath, cbdata)) < 0)
            goto cleanup;

        if (rc == 0) {
            if (virHashLookup(cycle, currentpath)) {
                virReportSystemError(ELOOP,
                                     _("Failed to canonicalize path '%s'"), path);
                goto cleanup;
            }

            if (virHashAddEntry(cycle, currentpath, (void *) 1) < 0)
                goto cleanup;

            if (linkpath[0] == '/') {
                /* kill everything from the beginning including the actual component */
                i++;
                while (i--) {
                    VIR_FREE(components[0]);
                    VIR_DELETE_ELEMENT(components, 0, ncomponents);
                }
                beginSlash = true;

                if (linkpath[1] == '/' && linkpath[2] != '/')
                    beginDoubleSlash = true;
                else
                    beginDoubleSlash = false;

                i = 0;
            } else {
                VIR_FREE(components[i]);
                VIR_DELETE_ELEMENT(components, i, ncomponents);
            }

            if (virStorageFileCanonicalizeInjectSymlink(linkpath,
                                                        i,
                                                        &components,
                                                        &ncomponents) < 0)
                goto cleanup;

            j = 0;
            while (j < ncomponents) {
                /* skip slashes */
                if (STREQ(components[j], "")) {
                    VIR_FREE(components[j]);
                    VIR_DELETE_ELEMENT(components, j, ncomponents);
                    continue;
                }
                j++;
            }

            VIR_FREE(linkpath);
            VIR_FREE(currentpath);

            continue;
        }

        VIR_FREE(currentpath);

        i++;
    }

    ret = virStorageFileCanonicalizeFormatPath(components, ncomponents,
                                               beginSlash, beginDoubleSlash);

 cleanup:
    virHashFree(cycle);
    virStringListFreeCount(components, ncomponents);

    return ret;
}


/**
 * 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;
}


/**
 * virStorageSourceIsRelative:
 * @src: storage source to check
 *
 * Returns true if given storage source definition is a relative path.
 */
bool
virStorageSourceIsRelative(virStorageSourcePtr src)
{
    virStorageType actual_type = virStorageSourceGetActualType(src);

    if (!src->path)
        return false;

    switch (actual_type) {
    case VIR_STORAGE_TYPE_FILE:
    case VIR_STORAGE_TYPE_BLOCK:
    case VIR_STORAGE_TYPE_DIR:
        return src->path[0] != '/';

    case VIR_STORAGE_TYPE_NETWORK:
    case VIR_STORAGE_TYPE_VOLUME:
    case VIR_STORAGE_TYPE_NVME:
    case VIR_STORAGE_TYPE_NONE:
    case VIR_STORAGE_TYPE_LAST:
        return false;
    }

    return false;
}


/**
 * virStorageSourceFindByNodeName:
 * @top: backing chain top
 * @nodeName: node name to find in backing chain
 *
 * Looks up the given storage source in the backing chain and returns the
 * pointer to it.
 * On failure NULL is returned and no error is reported.
 */
virStorageSourcePtr
virStorageSourceFindByNodeName(virStorageSourcePtr top,
                               const char *nodeName)
{
    virStorageSourcePtr tmp;

    for (tmp = top; virStorageSourceIsBacking(tmp); tmp = tmp->backingStore) {
        if ((tmp->nodeformat && STREQ(tmp->nodeformat, nodeName)) ||
            (tmp->nodestorage && STREQ(tmp->nodestorage, nodeName)))
            return tmp;
    }

    return NULL;
}


static unsigned int
virStorageSourceNetworkDefaultPort(virStorageNetProtocol protocol)
{
    switch (protocol) {
        case VIR_STORAGE_NET_PROTOCOL_HTTP:
            return 80;

        case VIR_STORAGE_NET_PROTOCOL_HTTPS:
            return 443;

        case VIR_STORAGE_NET_PROTOCOL_FTP:
            return 21;

        case VIR_STORAGE_NET_PROTOCOL_FTPS:
            return 990;

        case VIR_STORAGE_NET_PROTOCOL_TFTP:
            return 69;

        case VIR_STORAGE_NET_PROTOCOL_SHEEPDOG:
            return 7000;

        case VIR_STORAGE_NET_PROTOCOL_NBD:
            return 10809;

        case VIR_STORAGE_NET_PROTOCOL_SSH:
            return 22;

        case VIR_STORAGE_NET_PROTOCOL_ISCSI:
            return 3260;

        case VIR_STORAGE_NET_PROTOCOL_GLUSTER:
            return 24007;

        case VIR_STORAGE_NET_PROTOCOL_RBD:
            /* we don't provide a default for RBD */
            return 0;

        case VIR_STORAGE_NET_PROTOCOL_VXHS:
            return 9999;

        case VIR_STORAGE_NET_PROTOCOL_NFS:
            /* Port is not supported by NFS, so no default is provided */
            return 0;

        case VIR_STORAGE_NET_PROTOCOL_LAST:
        case VIR_STORAGE_NET_PROTOCOL_NONE:
            return 0;
    }

    return 0;
}


void
virStorageSourceNetworkAssignDefaultPorts(virStorageSourcePtr src)
{
    size_t i;

    for (i = 0; i < src->nhosts; i++) {
        if (src->hosts[i].transport == VIR_STORAGE_NET_HOST_TRANS_TCP &&
            src->hosts[i].port == 0)
            src->hosts[i].port = virStorageSourceNetworkDefaultPort(src->protocol);
    }
}


int
virStorageSourcePrivateDataParseRelPath(xmlXPathContextPtr ctxt,
                                        virStorageSourcePtr src)
{
    src->relPath = virXPathString("string(./relPath)", ctxt);
    return 0;
}


int
virStorageSourcePrivateDataFormatRelPath(virStorageSourcePtr src,
                                         virBufferPtr buf)
{
    if (src->relPath)
        virBufferEscapeString(buf, "<relPath>%s</relPath>\n", src->relPath);

    return 0;
}

void
virStorageSourceInitiatorParseXML(xmlXPathContextPtr ctxt,
                                  virStorageSourceInitiatorDefPtr initiator)
{
    initiator->iqn = virXPathString("string(./initiator/iqn/@name)", ctxt);
}

void
virStorageSourceInitiatorFormatXML(virStorageSourceInitiatorDefPtr initiator,
                                   virBufferPtr buf)
{
    if (!initiator->iqn)
        return;

    virBufferAddLit(buf, "<initiator>\n");
    virBufferAdjustIndent(buf, 2);
    virBufferEscapeString(buf, "<iqn name='%s'/>\n", initiator->iqn);
    virBufferAdjustIndent(buf, -2);
    virBufferAddLit(buf, "</initiator>\n");
}

int
virStorageSourceInitiatorCopy(virStorageSourceInitiatorDefPtr dest,
                              const virStorageSourceInitiatorDef *src)
{
    dest->iqn = g_strdup(src->iqn);
    return 0;
}

void
virStorageSourceInitiatorClear(virStorageSourceInitiatorDefPtr initiator)
{
    VIR_FREE(initiator->iqn);
}

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) {
        *backend = src->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)
{
    if (!virStorageFileIsInitialized(src))
        return;

    if (src->drv->backend &&
        src->drv->backend->backendDeinit)
        src->drv->backend->backendDeinit(src);

    VIR_FREE(src->drv);
}


/**
 * virStorageFileInitAs:
 *
 * @src: storage source definition
 * @uid: uid used to access the file, or -1 for current uid
 * @gid: gid used to access the file, or -1 for current gid
 *
 * Initialize a storage source to be used with storage driver. Use the provided
 * uid and gid if possible for the operations.
 *
 * Returns 0 if the storage file was successfully initialized, -1 if the
 * initialization failed. Libvirt error is reported.
 */
int
virStorageFileInitAs(virStorageSourcePtr src,
                     uid_t uid, gid_t gid)
{
    int actualType = virStorageSourceGetActualType(src);
    src->drv = g_new0(virStorageDriverData, 1);

    if (uid == (uid_t) -1)
        src->drv->uid = geteuid();
    else
        src->drv->uid = uid;

    if (gid == (gid_t) -1)
        src->drv->gid = getegid();
    else
        src->drv->gid = gid;

    if (virStorageFileBackendForType(actualType,
                                     src->protocol,
                                     true,
                                     &src->drv->backend) < 0)
        goto error;

    if (src->drv->backend->backendInit &&
        src->drv->backend->backendInit(src) < 0)
        goto error;

    return 0;

 error:
    VIR_FREE(src->drv);
    return -1;
}


/**
 * virStorageFileInit:
 *
 * See virStorageFileInitAs. The file is initialized to be accessed by the
 * current user.
 */
int
virStorageFileInit(virStorageSourcePtr src)
{
    return virStorageFileInitAs(src, -1, -1);
}


/**
 * virStorageFileCreate: Creates an empty storage file via storage driver
 *
 * @src: file structure pointing to the file
 *
 * Returns 0 on success, -2 if the function isn't supported by the backend,
 * -1 on other failure. Errno is set in case of failure.
 */
int
virStorageFileCreate(virStorageSourcePtr src)
{
    int ret;

    if (!virStorageFileIsInitialized(src) ||
        !src->drv->backend->storageFileCreate) {
        errno = ENOSYS;
        return -2;
    }

    ret = src->drv->backend->storageFileCreate(src);

    VIR_DEBUG("created storage file %p: ret=%d, errno=%d",
              src, ret, errno);

    return ret;
}


/**
 * virStorageFileUnlink: Unlink storage file via storage driver
 *
 * @src: file structure pointing to the file
 *
 * Unlinks the file described by the @file structure.
 *
 * Returns 0 on success, -2 if the function isn't supported by the backend,
 * -1 on other failure. Errno is set in case of failure.
 */
int
virStorageFileUnlink(virStorageSourcePtr src)
{
    int ret;

    if (!virStorageFileIsInitialized(src) ||
        !src->drv->backend->storageFileUnlink) {
        errno = ENOSYS;
        return -2;
    }

    ret = src->drv->backend->storageFileUnlink(src);

    VIR_DEBUG("unlinked storage file %p: ret=%d, errno=%d",
              src, ret, errno);

    return ret;
}


/**
 * virStorageFileStat: returns stat struct of a file via storage driver
 *
 * @src: file structure pointing to the file
 * @stat: stat structure to return data
 *
 * Returns 0 on success, -2 if the function isn't supported by the backend,
 * -1 on other failure. Errno is set in case of failure.
*/
int
virStorageFileStat(virStorageSourcePtr src,
                   struct stat *st)
{
    int ret;

    if (!virStorageFileIsInitialized(src) ||
        !src->drv->backend->storageFileStat) {
        errno = ENOSYS;
        return -2;
    }

    ret = src->drv->backend->storageFileStat(src, st);

    VIR_DEBUG("stat of storage file %p: ret=%d, errno=%d",
              src, ret, errno);

    return ret;
}


/**
 * virStorageFileRead: read bytes from a file into a buffer
 *
 * @src: file structure pointing to the file
 * @offset: number of bytes to skip in the storage file
 * @len: maximum number of bytes read from the storage file
 * @buf: buffer to read the data into. (buffer shall be freed by caller)
 *
 * Returns the count of bytes read on success and -1 on failure, -2 if the
 * function isn't supported by the backend.
 * Libvirt error is reported on failure.
 */
ssize_t
virStorageFileRead(virStorageSourcePtr src,
                   size_t offset,
                   size_t len,
                   char **buf)
{
    ssize_t ret;

    if (!virStorageFileIsInitialized(src)) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("storage file backend not initialized"));
        return -1;
    }

    if (!src->drv->backend->storageFileRead)
        return -2;

    ret = src->drv->backend->storageFileRead(src, offset, len, buf);

    VIR_DEBUG("read '%zd' bytes from storage '%p' starting at offset '%zu'",
              ret, src, offset);

    return ret;
}


/*
 * virStorageFileGetUniqueIdentifier: Get a unique string describing the volume
 *
 * @src: file structure pointing to the file
 *
 * Returns a string uniquely describing a single volume (canonical path).
 * The string shall not be freed and is valid until the storage file is
 * deinitialized. Returns NULL on error and sets a libvirt error code */
const char *
virStorageFileGetUniqueIdentifier(virStorageSourcePtr src)
{
    if (!virStorageFileIsInitialized(src)) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("storage file backend not initialized"));
        return NULL;
    }

    if (!src->drv->backend->storageFileGetUniqueIdentifier) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("unique storage file identifier not implemented for "
                         "storage type %s (protocol: %s)'"),
                       virStorageTypeToString(src->type),
                       virStorageNetProtocolTypeToString(src->protocol));
        return NULL;
    }

    return src->drv->backend->storageFileGetUniqueIdentifier(src);
}


/**
 * virStorageFileAccess: Check accessibility of a storage file
 *
 * @src: storage file to check access permissions
 * @mode: accessibility check options (see man 2 access)
 *
 * Returns 0 on success, -1 on error and sets errno. No libvirt
 * error is reported. Returns -2 if the operation isn't supported
 * by libvirt storage backend.
 */
int
virStorageFileAccess(virStorageSourcePtr src,
                     int mode)
{
    if (!virStorageFileIsInitialized(src) ||
        !src->drv->backend->storageFileAccess) {
        errno = ENOSYS;
        return -2;
    }

    return src->drv->backend->storageFileAccess(src, mode);
}


/**
 * virStorageFileChown: Change owner of a storage file
 *
 * @src: storage file to change owner of
 * @uid: new owner id
 * @gid: new group id
 *
 * Returns 0 on success, -1 on error and sets errno. No libvirt
 * error is reported. Returns -2 if the operation isn't supported
 * by libvirt storage backend.
 */
int
virStorageFileChown(const virStorageSource *src,
                    uid_t uid,
                    gid_t gid)
{
    if (!virStorageFileIsInitialized(src) ||
        !src->drv->backend->storageFileChown) {
        errno = ENOSYS;
        return -2;
    }

    VIR_DEBUG("chown of storage file %p to %u:%u",
              src, (unsigned int)uid, (unsigned int)gid);

    return src->drv->backend->storageFileChown(src, uid, gid);
}


/**
 * virStorageFileReportBrokenChain:
 *
 * @errcode: errno when accessing @src
 * @src: inaccessible file in the backing chain of @parent
 * @parent: root virStorageSource being checked
 *
 * Reports the correct error message if @src is missing in the backing chain
 * for @parent.
 */
void
virStorageFileReportBrokenChain(int errcode,
                                virStorageSourcePtr src,
                                virStorageSourcePtr parent)
{
    if (src->drv) {
        unsigned int access_user = src->drv->uid;
        unsigned int access_group = src->drv->gid;

        if (src == parent) {
            virReportSystemError(errcode,
                                 _("Cannot access storage file '%s' "
                                   "(as uid:%u, gid:%u)"),
                                 src->path, access_user, access_group);
        } else {
            virReportSystemError(errcode,
                                 _("Cannot access backing file '%s' "
                                   "of storage file '%s' (as uid:%u, gid:%u)"),
                                 src->path, parent->path, access_user, access_group);
        }
    } 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 (virStorageFileGetMetadataInternal(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 (virStorageFileGetMetadataInternal(tmp, buf, headerLen) < 0)
        return -1;

    *backing = g_steal_pointer(&tmp->backingStoreRaw);
    return 0;
}
