/*
 * QEMU Block driver for RADOS (Ceph)
 *
 * Copyright (C) 2010-2011 Christian Brunner <chb@muc.de>,
 *                         Josh Durgin <josh.durgin@dreamhost.com>
 *
 * This work is licensed under the terms of the GNU GPL, version 2.  See
 * the COPYING file in the top-level directory.
 *
 * Contributions after 2012-01-13 are licensed under the terms of the
 * GNU GPL, version 2 or (at your option) any later version.
 */

#include "qemu/osdep.h"

#include <rbd/librbd.h>
#include "qapi/error.h"
#include "qemu/error-report.h"
#include "qemu/module.h"
#include "qemu/option.h"
#include "block/block-io.h"
#include "block/block_int.h"
#include "block/qdict.h"
#include "crypto/secret.h"
#include "qemu/cutils.h"
#include "sysemu/replay.h"
#include "qapi/qmp/qstring.h"
#include "qapi/qmp/qdict.h"
#include "qapi/qmp/qjson.h"
#include "qapi/qmp/qlist.h"
#include "qapi/qobject-input-visitor.h"
#include "qapi/qapi-visit-block-core.h"

/*
 * When specifying the image filename use:
 *
 * rbd:poolname/devicename[@snapshotname][:option1=value1[:option2=value2...]]
 *
 * poolname must be the name of an existing rados pool.
 *
 * devicename is the name of the rbd image.
 *
 * Each option given is used to configure rados, and may be any valid
 * Ceph option, "id", or "conf".
 *
 * The "id" option indicates what user we should authenticate as to
 * the Ceph cluster.  If it is excluded we will use the Ceph default
 * (normally 'admin').
 *
 * The "conf" option specifies a Ceph configuration file to read.  If
 * it is not specified, we will read from the default Ceph locations
 * (e.g., /etc/ceph/ceph.conf).  To avoid reading _any_ configuration
 * file, specify conf=/dev/null.
 *
 * Configuration values containing :, @, or = can be escaped with a
 * leading "\".
 */

#define OBJ_MAX_SIZE (1UL << OBJ_DEFAULT_OBJ_ORDER)

#define RBD_MAX_SNAPS 100

#define RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN 8

static const char rbd_luks_header_verification[
        RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN] = {
    'L', 'U', 'K', 'S', 0xBA, 0xBE, 0, 1
};

static const char rbd_luks2_header_verification[
        RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN] = {
    'L', 'U', 'K', 'S', 0xBA, 0xBE, 0, 2
};

static const char rbd_layered_luks_header_verification[
        RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN] = {
    'R', 'B', 'D', 'L', 0xBA, 0xBE, 0, 1
};

static const char rbd_layered_luks2_header_verification[
        RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN] = {
    'R', 'B', 'D', 'L', 0xBA, 0xBE, 0, 2
};

typedef enum {
    RBD_AIO_READ,
    RBD_AIO_WRITE,
    RBD_AIO_DISCARD,
    RBD_AIO_FLUSH,
    RBD_AIO_WRITE_ZEROES
} RBDAIOCmd;

typedef struct BDRVRBDState {
    rados_t cluster;
    rados_ioctx_t io_ctx;
    rbd_image_t image;
    char *image_name;
    char *snap;
    char *namespace;
    uint64_t image_size;
    uint64_t object_size;
} BDRVRBDState;

typedef struct RBDTask {
    BlockDriverState *bs;
    Coroutine *co;
    bool complete;
    int64_t ret;
} RBDTask;

typedef struct RBDDiffIterateReq {
    uint64_t offs;
    uint64_t bytes;
    bool exists;
} RBDDiffIterateReq;

static int qemu_rbd_connect(rados_t *cluster, rados_ioctx_t *io_ctx,
                            BlockdevOptionsRbd *opts, bool cache,
                            const char *keypairs, const char *secretid,
                            Error **errp);

static char *qemu_rbd_strchr(char *src, char delim)
{
    char *p;

    for (p = src; *p; ++p) {
        if (*p == delim) {
            return p;
        }
        if (*p == '\\' && p[1] != '\0') {
            ++p;
        }
    }

    return NULL;
}


static char *qemu_rbd_next_tok(char *src, char delim, char **p)
{
    char *end;

    *p = NULL;

    end = qemu_rbd_strchr(src, delim);
    if (end) {
        *p = end + 1;
        *end = '\0';
    }
    return src;
}

static void qemu_rbd_unescape(char *src)
{
    char *p;

    for (p = src; *src; ++src, ++p) {
        if (*src == '\\' && src[1] != '\0') {
            src++;
        }
        *p = *src;
    }
    *p = '\0';
}

static void qemu_rbd_parse_filename(const char *filename, QDict *options,
                                    Error **errp)
{
    const char *start;
    char *p, *buf;
    QList *keypairs = NULL;
    char *found_str, *image_name;

    if (!strstart(filename, "rbd:", &start)) {
        error_setg(errp, "File name must start with 'rbd:'");
        return;
    }

    buf = g_strdup(start);
    p = buf;

    found_str = qemu_rbd_next_tok(p, '/', &p);
    if (!p) {
        error_setg(errp, "Pool name is required");
        goto done;
    }
    qemu_rbd_unescape(found_str);
    qdict_put_str(options, "pool", found_str);

    if (qemu_rbd_strchr(p, '@')) {
        image_name = qemu_rbd_next_tok(p, '@', &p);

        found_str = qemu_rbd_next_tok(p, ':', &p);
        qemu_rbd_unescape(found_str);
        qdict_put_str(options, "snapshot", found_str);
    } else {
        image_name = qemu_rbd_next_tok(p, ':', &p);
    }
    /* Check for namespace in the image_name */
    if (qemu_rbd_strchr(image_name, '/')) {
        found_str = qemu_rbd_next_tok(image_name, '/', &image_name);
        qemu_rbd_unescape(found_str);
        qdict_put_str(options, "namespace", found_str);
    } else {
        qdict_put_str(options, "namespace", "");
    }
    qemu_rbd_unescape(image_name);
    qdict_put_str(options, "image", image_name);
    if (!p) {
        goto done;
    }

    /* The following are essentially all key/value pairs, and we treat
     * 'id' and 'conf' a bit special.  Key/value pairs may be in any order. */
    while (p) {
        char *name, *value;
        name = qemu_rbd_next_tok(p, '=', &p);
        if (!p) {
            error_setg(errp, "conf option %s has no value", name);
            break;
        }

        qemu_rbd_unescape(name);

        value = qemu_rbd_next_tok(p, ':', &p);
        qemu_rbd_unescape(value);

        if (!strcmp(name, "conf")) {
            qdict_put_str(options, "conf", value);
        } else if (!strcmp(name, "id")) {
            qdict_put_str(options, "user", value);
        } else {
            /*
             * We pass these internally to qemu_rbd_set_keypairs(), so
             * we can get away with the simpler list of [ "key1",
             * "value1", "key2", "value2" ] rather than a raw dict
             * { "key1": "value1", "key2": "value2" } where we can't
             * guarantee order, or even a more correct but complex
             * [ { "key1": "value1" }, { "key2": "value2" } ]
             */
            if (!keypairs) {
                keypairs = qlist_new();
            }
            qlist_append_str(keypairs, name);
            qlist_append_str(keypairs, value);
        }
    }

    if (keypairs) {
        qdict_put(options, "=keyvalue-pairs",
                  qstring_from_gstring(qobject_to_json(QOBJECT(keypairs))));
    }

done:
    g_free(buf);
    qobject_unref(keypairs);
    return;
}

static int qemu_rbd_set_auth(rados_t cluster, BlockdevOptionsRbd *opts,
                             Error **errp)
{
    char *key, *acr;
    int r;
    GString *accu;
    RbdAuthModeList *auth;

    if (opts->key_secret) {
        key = qcrypto_secret_lookup_as_base64(opts->key_secret, errp);
        if (!key) {
            return -EIO;
        }
        r = rados_conf_set(cluster, "key", key);
        g_free(key);
        if (r < 0) {
            error_setg_errno(errp, -r, "Could not set 'key'");
            return r;
        }
    }

    if (opts->has_auth_client_required) {
        accu = g_string_new("");
        for (auth = opts->auth_client_required; auth; auth = auth->next) {
            if (accu->str[0]) {
                g_string_append_c(accu, ';');
            }
            g_string_append(accu, RbdAuthMode_str(auth->value));
        }
        acr = g_string_free(accu, FALSE);
        r = rados_conf_set(cluster, "auth_client_required", acr);
        g_free(acr);
        if (r < 0) {
            error_setg_errno(errp, -r,
                             "Could not set 'auth_client_required'");
            return r;
        }
    }

    return 0;
}

static int qemu_rbd_set_keypairs(rados_t cluster, const char *keypairs_json,
                                 Error **errp)
{
    QList *keypairs;
    QString *name;
    QString *value;
    const char *key;
    size_t remaining;
    int ret = 0;

    if (!keypairs_json) {
        return ret;
    }
    keypairs = qobject_to(QList,
                          qobject_from_json(keypairs_json, &error_abort));
    remaining = qlist_size(keypairs) / 2;
    assert(remaining);

    while (remaining--) {
        name = qobject_to(QString, qlist_pop(keypairs));
        value = qobject_to(QString, qlist_pop(keypairs));
        assert(name && value);
        key = qstring_get_str(name);

        ret = rados_conf_set(cluster, key, qstring_get_str(value));
        qobject_unref(value);
        if (ret < 0) {
            error_setg_errno(errp, -ret, "invalid conf option %s", key);
            qobject_unref(name);
            ret = -EINVAL;
            break;
        }
        qobject_unref(name);
    }

    qobject_unref(keypairs);
    return ret;
}

#ifdef LIBRBD_SUPPORTS_ENCRYPTION
static int qemu_rbd_convert_luks_options(
        RbdEncryptionOptionsLUKSBase *luks_opts,
        char **passphrase,
        size_t *passphrase_len,
        Error **errp)
{
    return qcrypto_secret_lookup(luks_opts->key_secret, (uint8_t **)passphrase,
                                 passphrase_len, errp);
}

static int qemu_rbd_convert_luks_create_options(
        RbdEncryptionCreateOptionsLUKSBase *luks_opts,
        rbd_encryption_algorithm_t *alg,
        char **passphrase,
        size_t *passphrase_len,
        Error **errp)
{
    int r = 0;

    r = qemu_rbd_convert_luks_options(
            qapi_RbdEncryptionCreateOptionsLUKSBase_base(luks_opts),
            passphrase, passphrase_len, errp);
    if (r < 0) {
        return r;
    }

    if (luks_opts->has_cipher_alg) {
        switch (luks_opts->cipher_alg) {
            case QCRYPTO_CIPHER_ALG_AES_128: {
                *alg = RBD_ENCRYPTION_ALGORITHM_AES128;
                break;
            }
            case QCRYPTO_CIPHER_ALG_AES_256: {
                *alg = RBD_ENCRYPTION_ALGORITHM_AES256;
                break;
            }
            default: {
                r = -ENOTSUP;
                error_setg_errno(errp, -r, "unknown encryption algorithm: %u",
                                 luks_opts->cipher_alg);
                return r;
            }
        }
    } else {
        /* default alg */
        *alg = RBD_ENCRYPTION_ALGORITHM_AES256;
    }

    return 0;
}

static int qemu_rbd_encryption_format(rbd_image_t image,
                                      RbdEncryptionCreateOptions *encrypt,
                                      Error **errp)
{
    int r = 0;
    g_autofree char *passphrase = NULL;
    rbd_encryption_format_t format;
    rbd_encryption_options_t opts;
    rbd_encryption_luks1_format_options_t luks_opts;
    rbd_encryption_luks2_format_options_t luks2_opts;
    size_t opts_size;
    uint64_t raw_size, effective_size;

    r = rbd_get_size(image, &raw_size);
    if (r < 0) {
        error_setg_errno(errp, -r, "cannot get raw image size");
        return r;
    }

    switch (encrypt->format) {
        case RBD_IMAGE_ENCRYPTION_FORMAT_LUKS: {
            memset(&luks_opts, 0, sizeof(luks_opts));
            format = RBD_ENCRYPTION_FORMAT_LUKS1;
            opts = &luks_opts;
            opts_size = sizeof(luks_opts);
            r = qemu_rbd_convert_luks_create_options(
                    qapi_RbdEncryptionCreateOptionsLUKS_base(&encrypt->u.luks),
                    &luks_opts.alg, &passphrase, &luks_opts.passphrase_size,
                    errp);
            if (r < 0) {
                return r;
            }
            luks_opts.passphrase = passphrase;
            break;
        }
        case RBD_IMAGE_ENCRYPTION_FORMAT_LUKS2: {
            memset(&luks2_opts, 0, sizeof(luks2_opts));
            format = RBD_ENCRYPTION_FORMAT_LUKS2;
            opts = &luks2_opts;
            opts_size = sizeof(luks2_opts);
            r = qemu_rbd_convert_luks_create_options(
                    qapi_RbdEncryptionCreateOptionsLUKS2_base(
                            &encrypt->u.luks2),
                    &luks2_opts.alg, &passphrase, &luks2_opts.passphrase_size,
                    errp);
            if (r < 0) {
                return r;
            }
            luks2_opts.passphrase = passphrase;
            break;
        }
        default: {
            r = -ENOTSUP;
            error_setg_errno(
                    errp, -r, "unknown image encryption format: %u",
                    encrypt->format);
            return r;
        }
    }

    r = rbd_encryption_format(image, format, opts, opts_size);
    if (r < 0) {
        error_setg_errno(errp, -r, "encryption format fail");
        return r;
    }

    r = rbd_get_size(image, &effective_size);
    if (r < 0) {
        error_setg_errno(errp, -r, "cannot get effective image size");
        return r;
    }

    r = rbd_resize(image, raw_size + (raw_size - effective_size));
    if (r < 0) {
        error_setg_errno(errp, -r, "cannot resize image after format");
        return r;
    }

    return 0;
}

static int qemu_rbd_encryption_load(rbd_image_t image,
                                    RbdEncryptionOptions *encrypt,
                                    Error **errp)
{
    int r = 0;
    g_autofree char *passphrase = NULL;
    rbd_encryption_luks1_format_options_t luks_opts;
    rbd_encryption_luks2_format_options_t luks2_opts;
#ifdef LIBRBD_SUPPORTS_ENCRYPTION_LOAD2
    rbd_encryption_luks_format_options_t luks_any_opts;
#endif
    rbd_encryption_format_t format;
    rbd_encryption_options_t opts;
    size_t opts_size;

    switch (encrypt->format) {
        case RBD_IMAGE_ENCRYPTION_FORMAT_LUKS: {
            memset(&luks_opts, 0, sizeof(luks_opts));
            format = RBD_ENCRYPTION_FORMAT_LUKS1;
            opts = &luks_opts;
            opts_size = sizeof(luks_opts);
            r = qemu_rbd_convert_luks_options(
                    qapi_RbdEncryptionOptionsLUKS_base(&encrypt->u.luks),
                    &passphrase, &luks_opts.passphrase_size, errp);
            if (r < 0) {
                return r;
            }
            luks_opts.passphrase = passphrase;
            break;
        }
        case RBD_IMAGE_ENCRYPTION_FORMAT_LUKS2: {
            memset(&luks2_opts, 0, sizeof(luks2_opts));
            format = RBD_ENCRYPTION_FORMAT_LUKS2;
            opts = &luks2_opts;
            opts_size = sizeof(luks2_opts);
            r = qemu_rbd_convert_luks_options(
                    qapi_RbdEncryptionOptionsLUKS2_base(&encrypt->u.luks2),
                    &passphrase, &luks2_opts.passphrase_size, errp);
            if (r < 0) {
                return r;
            }
            luks2_opts.passphrase = passphrase;
            break;
        }
#ifdef LIBRBD_SUPPORTS_ENCRYPTION_LOAD2
        case RBD_IMAGE_ENCRYPTION_FORMAT_LUKS_ANY: {
            memset(&luks_any_opts, 0, sizeof(luks_any_opts));
            format = RBD_ENCRYPTION_FORMAT_LUKS;
            opts = &luks_any_opts;
            opts_size = sizeof(luks_any_opts);
            r = qemu_rbd_convert_luks_options(
                    qapi_RbdEncryptionOptionsLUKSAny_base(&encrypt->u.luks_any),
                    &passphrase, &luks_any_opts.passphrase_size, errp);
            if (r < 0) {
                return r;
            }
            luks_any_opts.passphrase = passphrase;
            break;
        }
#endif
        default: {
            r = -ENOTSUP;
            error_setg_errno(
                    errp, -r, "unknown image encryption format: %u",
                    encrypt->format);
            return r;
        }
    }

    r = rbd_encryption_load(image, format, opts, opts_size);
    if (r < 0) {
        error_setg_errno(errp, -r, "encryption load fail");
        return r;
    }

    return 0;
}

#ifdef LIBRBD_SUPPORTS_ENCRYPTION_LOAD2
static int qemu_rbd_encryption_load2(rbd_image_t image,
                                     RbdEncryptionOptions *encrypt,
                                     Error **errp)
{
    int r = 0;
    int encrypt_count = 1;
    int i;
    RbdEncryptionOptions *curr_encrypt;
    rbd_encryption_spec_t *specs;
    rbd_encryption_luks1_format_options_t *luks_opts;
    rbd_encryption_luks2_format_options_t *luks2_opts;
    rbd_encryption_luks_format_options_t *luks_any_opts;

    /* count encryption options */
    for (curr_encrypt = encrypt->parent; curr_encrypt;
         curr_encrypt = curr_encrypt->parent) {
        ++encrypt_count;
    }

    specs = g_new0(rbd_encryption_spec_t, encrypt_count);

    curr_encrypt = encrypt;
    for (i = 0; i < encrypt_count; ++i) {
        switch (curr_encrypt->format) {
            case RBD_IMAGE_ENCRYPTION_FORMAT_LUKS: {
                specs[i].format = RBD_ENCRYPTION_FORMAT_LUKS1;

                luks_opts = g_new0(rbd_encryption_luks1_format_options_t, 1);
                specs[i].opts = luks_opts;
                specs[i].opts_size = sizeof(*luks_opts);

                r = qemu_rbd_convert_luks_options(
                        qapi_RbdEncryptionOptionsLUKS_base(
                                &curr_encrypt->u.luks),
                        (char **)&luks_opts->passphrase,
                        &luks_opts->passphrase_size,
                        errp);
                break;
            }
            case RBD_IMAGE_ENCRYPTION_FORMAT_LUKS2: {
                specs[i].format = RBD_ENCRYPTION_FORMAT_LUKS2;

                luks2_opts = g_new0(rbd_encryption_luks2_format_options_t, 1);
                specs[i].opts = luks2_opts;
                specs[i].opts_size = sizeof(*luks2_opts);

                r = qemu_rbd_convert_luks_options(
                        qapi_RbdEncryptionOptionsLUKS2_base(
                                &curr_encrypt->u.luks2),
                        (char **)&luks2_opts->passphrase,
                        &luks2_opts->passphrase_size,
                        errp);
                break;
            }
            case RBD_IMAGE_ENCRYPTION_FORMAT_LUKS_ANY: {
                specs[i].format = RBD_ENCRYPTION_FORMAT_LUKS;

                luks_any_opts = g_new0(rbd_encryption_luks_format_options_t, 1);
                specs[i].opts = luks_any_opts;
                specs[i].opts_size = sizeof(*luks_any_opts);

                r = qemu_rbd_convert_luks_options(
                        qapi_RbdEncryptionOptionsLUKSAny_base(
                                &curr_encrypt->u.luks_any),
                        (char **)&luks_any_opts->passphrase,
                        &luks_any_opts->passphrase_size,
                        errp);
                break;
            }
            default: {
                r = -ENOTSUP;
                error_setg_errno(
                        errp, -r, "unknown image encryption format: %u",
                        curr_encrypt->format);
            }
        }

        if (r < 0) {
            goto exit;
        }

        curr_encrypt = curr_encrypt->parent;
    }

    r = rbd_encryption_load2(image, specs, encrypt_count);
    if (r < 0) {
        error_setg_errno(errp, -r, "layered encryption load fail");
        goto exit;
    }

exit:
    for (i = 0; i < encrypt_count; ++i) {
        if (!specs[i].opts) {
            break;
        }

        switch (specs[i].format) {
            case RBD_ENCRYPTION_FORMAT_LUKS1: {
                luks_opts = specs[i].opts;
                g_free((void *)luks_opts->passphrase);
                break;
            }
            case RBD_ENCRYPTION_FORMAT_LUKS2: {
                luks2_opts = specs[i].opts;
                g_free((void *)luks2_opts->passphrase);
                break;
            }
            case RBD_ENCRYPTION_FORMAT_LUKS: {
                luks_any_opts = specs[i].opts;
                g_free((void *)luks_any_opts->passphrase);
                break;
            }
        }

        g_free(specs[i].opts);
    }
    g_free(specs);
    return r;
}
#endif
#endif

/* FIXME Deprecate and remove keypairs or make it available in QMP. */
static int qemu_rbd_do_create(BlockdevCreateOptions *options,
                              const char *keypairs, const char *password_secret,
                              Error **errp)
{
    BlockdevCreateOptionsRbd *opts = &options->u.rbd;
    rados_t cluster;
    rados_ioctx_t io_ctx;
    int obj_order = 0;
    int ret;

    assert(options->driver == BLOCKDEV_DRIVER_RBD);
    if (opts->location->snapshot) {
        error_setg(errp, "Can't use snapshot name for image creation");
        return -EINVAL;
    }

#ifndef LIBRBD_SUPPORTS_ENCRYPTION
    if (opts->encrypt) {
        error_setg(errp, "RBD library does not support image encryption");
        return -ENOTSUP;
    }
#endif

    if (opts->has_cluster_size) {
        int64_t objsize = opts->cluster_size;
        if ((objsize - 1) & objsize) {    /* not a power of 2? */
            error_setg(errp, "obj size needs to be power of 2");
            return -EINVAL;
        }
        if (objsize < 4096) {
            error_setg(errp, "obj size too small");
            return -EINVAL;
        }
        obj_order = ctz32(objsize);
    }

    ret = qemu_rbd_connect(&cluster, &io_ctx, opts->location, false, keypairs,
                           password_secret, errp);
    if (ret < 0) {
        return ret;
    }

    ret = rbd_create(io_ctx, opts->location->image, opts->size, &obj_order);
    if (ret < 0) {
        error_setg_errno(errp, -ret, "error rbd create");
        goto out;
    }

#ifdef LIBRBD_SUPPORTS_ENCRYPTION
    if (opts->encrypt) {
        rbd_image_t image;

        ret = rbd_open(io_ctx, opts->location->image, &image, NULL);
        if (ret < 0) {
            error_setg_errno(errp, -ret,
                             "error opening image '%s' for encryption format",
                             opts->location->image);
            goto out;
        }

        ret = qemu_rbd_encryption_format(image, opts->encrypt, errp);
        rbd_close(image);
        if (ret < 0) {
            /* encryption format fail, try removing the image */
            rbd_remove(io_ctx, opts->location->image);
            goto out;
        }
    }
#endif

    ret = 0;
out:
    rados_ioctx_destroy(io_ctx);
    rados_shutdown(cluster);
    return ret;
}

static int qemu_rbd_co_create(BlockdevCreateOptions *options, Error **errp)
{
    return qemu_rbd_do_create(options, NULL, NULL, errp);
}

static int qemu_rbd_extract_encryption_create_options(
        QemuOpts *opts,
        RbdEncryptionCreateOptions **spec,
        Error **errp)
{
    QDict *opts_qdict;
    QDict *encrypt_qdict;
    Visitor *v;
    int ret = 0;

    opts_qdict = qemu_opts_to_qdict(opts, NULL);
    qdict_extract_subqdict(opts_qdict, &encrypt_qdict, "encrypt.");
    qobject_unref(opts_qdict);
    if (!qdict_size(encrypt_qdict)) {
        *spec = NULL;
        goto exit;
    }

    /* Convert options into a QAPI object */
    v = qobject_input_visitor_new_flat_confused(encrypt_qdict, errp);
    if (!v) {
        ret = -EINVAL;
        goto exit;
    }

    visit_type_RbdEncryptionCreateOptions(v, NULL, spec, errp);
    visit_free(v);
    if (!*spec) {
        ret = -EINVAL;
        goto exit;
    }

exit:
    qobject_unref(encrypt_qdict);
    return ret;
}

static int coroutine_fn qemu_rbd_co_create_opts(BlockDriver *drv,
                                                const char *filename,
                                                QemuOpts *opts,
                                                Error **errp)
{
    BlockdevCreateOptions *create_options;
    BlockdevCreateOptionsRbd *rbd_opts;
    BlockdevOptionsRbd *loc;
    RbdEncryptionCreateOptions *encrypt = NULL;
    Error *local_err = NULL;
    const char *keypairs, *password_secret;
    QDict *options = NULL;
    int ret = 0;

    create_options = g_new0(BlockdevCreateOptions, 1);
    create_options->driver = BLOCKDEV_DRIVER_RBD;
    rbd_opts = &create_options->u.rbd;

    rbd_opts->location = g_new0(BlockdevOptionsRbd, 1);

    password_secret = qemu_opt_get(opts, "password-secret");

    /* Read out options */
    rbd_opts->size = ROUND_UP(qemu_opt_get_size_del(opts, BLOCK_OPT_SIZE, 0),
                              BDRV_SECTOR_SIZE);
    rbd_opts->cluster_size = qemu_opt_get_size_del(opts,
                                                   BLOCK_OPT_CLUSTER_SIZE, 0);
    rbd_opts->has_cluster_size = (rbd_opts->cluster_size != 0);

    options = qdict_new();
    qemu_rbd_parse_filename(filename, options, &local_err);
    if (local_err) {
        ret = -EINVAL;
        error_propagate(errp, local_err);
        goto exit;
    }

    ret = qemu_rbd_extract_encryption_create_options(opts, &encrypt, errp);
    if (ret < 0) {
        goto exit;
    }
    rbd_opts->encrypt     = encrypt;

    /*
     * Caution: while qdict_get_try_str() is fine, getting non-string
     * types would require more care.  When @options come from -blockdev
     * or blockdev_add, its members are typed according to the QAPI
     * schema, but when they come from -drive, they're all QString.
     */
    loc = rbd_opts->location;
    loc->pool        = g_strdup(qdict_get_try_str(options, "pool"));
    loc->conf        = g_strdup(qdict_get_try_str(options, "conf"));
    loc->user        = g_strdup(qdict_get_try_str(options, "user"));
    loc->q_namespace = g_strdup(qdict_get_try_str(options, "namespace"));
    loc->image       = g_strdup(qdict_get_try_str(options, "image"));
    keypairs         = qdict_get_try_str(options, "=keyvalue-pairs");

    ret = qemu_rbd_do_create(create_options, keypairs, password_secret, errp);
    if (ret < 0) {
        goto exit;
    }

exit:
    qobject_unref(options);
    qapi_free_BlockdevCreateOptions(create_options);
    return ret;
}

static char *qemu_rbd_mon_host(BlockdevOptionsRbd *opts, Error **errp)
{
    const char **vals;
    const char *host, *port;
    char *rados_str;
    InetSocketAddressBaseList *p;
    int i, cnt;

    if (!opts->has_server) {
        return NULL;
    }

    for (cnt = 0, p = opts->server; p; p = p->next) {
        cnt++;
    }

    vals = g_new(const char *, cnt + 1);

    for (i = 0, p = opts->server; p; p = p->next, i++) {
        host = p->value->host;
        port = p->value->port;

        if (strchr(host, ':')) {
            vals[i] = g_strdup_printf("[%s]:%s", host, port);
        } else {
            vals[i] = g_strdup_printf("%s:%s", host, port);
        }
    }
    vals[i] = NULL;

    rados_str = i ? g_strjoinv(";", (char **)vals) : NULL;
    g_strfreev((char **)vals);
    return rados_str;
}

static int qemu_rbd_connect(rados_t *cluster, rados_ioctx_t *io_ctx,
                            BlockdevOptionsRbd *opts, bool cache,
                            const char *keypairs, const char *secretid,
                            Error **errp)
{
    char *mon_host = NULL;
    Error *local_err = NULL;
    int r;

    if (secretid) {
        if (opts->key_secret) {
            error_setg(errp,
                       "Legacy 'password-secret' clashes with 'key-secret'");
            return -EINVAL;
        }
        opts->key_secret = g_strdup(secretid);
    }

    mon_host = qemu_rbd_mon_host(opts, &local_err);
    if (local_err) {
        error_propagate(errp, local_err);
        r = -EINVAL;
        goto out;
    }

    r = rados_create(cluster, opts->user);
    if (r < 0) {
        error_setg_errno(errp, -r, "error initializing");
        goto out;
    }

    /* try default location when conf=NULL, but ignore failure */
    r = rados_conf_read_file(*cluster, opts->conf);
    if (opts->conf && r < 0) {
        error_setg_errno(errp, -r, "error reading conf file %s", opts->conf);
        goto failed_shutdown;
    }

    r = qemu_rbd_set_keypairs(*cluster, keypairs, errp);
    if (r < 0) {
        goto failed_shutdown;
    }

    if (mon_host) {
        r = rados_conf_set(*cluster, "mon_host", mon_host);
        if (r < 0) {
            goto failed_shutdown;
        }
    }

    r = qemu_rbd_set_auth(*cluster, opts, errp);
    if (r < 0) {
        goto failed_shutdown;
    }

    /*
     * Fallback to more conservative semantics if setting cache
     * options fails. Ignore errors from setting rbd_cache because the
     * only possible error is that the option does not exist, and
     * librbd defaults to no caching. If write through caching cannot
     * be set up, fall back to no caching.
     */
    if (cache) {
        rados_conf_set(*cluster, "rbd_cache", "true");
    } else {
        rados_conf_set(*cluster, "rbd_cache", "false");
    }

    r = rados_connect(*cluster);
    if (r < 0) {
        error_setg_errno(errp, -r, "error connecting");
        goto failed_shutdown;
    }

    r = rados_ioctx_create(*cluster, opts->pool, io_ctx);
    if (r < 0) {
        error_setg_errno(errp, -r, "error opening pool %s", opts->pool);
        goto failed_shutdown;
    }

#ifdef HAVE_RBD_NAMESPACE_EXISTS
    if (opts->q_namespace && strlen(opts->q_namespace) > 0) {
        bool exists;

        r = rbd_namespace_exists(*io_ctx, opts->q_namespace, &exists);
        if (r < 0) {
            error_setg_errno(errp, -r, "error checking namespace");
            goto failed_ioctx_destroy;
        }

        if (!exists) {
            error_setg(errp, "namespace '%s' does not exist",
                       opts->q_namespace);
            r = -ENOENT;
            goto failed_ioctx_destroy;
        }
    }
#endif

    /*
     * Set the namespace after opening the io context on the pool,
     * if nspace == NULL or if nspace == "", it is just as we did nothing
     */
    rados_ioctx_set_namespace(*io_ctx, opts->q_namespace);

    r = 0;
    goto out;

#ifdef HAVE_RBD_NAMESPACE_EXISTS
failed_ioctx_destroy:
    rados_ioctx_destroy(*io_ctx);
#endif
failed_shutdown:
    rados_shutdown(*cluster);
out:
    g_free(mon_host);
    return r;
}

static int qemu_rbd_convert_options(QDict *options, BlockdevOptionsRbd **opts,
                                    Error **errp)
{
    Visitor *v;

    /* Convert the remaining options into a QAPI object */
    v = qobject_input_visitor_new_flat_confused(options, errp);
    if (!v) {
        return -EINVAL;
    }

    visit_type_BlockdevOptionsRbd(v, NULL, opts, errp);
    visit_free(v);
    if (!opts) {
        return -EINVAL;
    }

    return 0;
}

static int qemu_rbd_attempt_legacy_options(QDict *options,
                                           BlockdevOptionsRbd **opts,
                                           char **keypairs)
{
    char *filename;
    int r;

    filename = g_strdup(qdict_get_try_str(options, "filename"));
    if (!filename) {
        return -EINVAL;
    }
    qdict_del(options, "filename");

    qemu_rbd_parse_filename(filename, options, NULL);

    /* keypairs freed by caller */
    *keypairs = g_strdup(qdict_get_try_str(options, "=keyvalue-pairs"));
    if (*keypairs) {
        qdict_del(options, "=keyvalue-pairs");
    }

    r = qemu_rbd_convert_options(options, opts, NULL);

    g_free(filename);
    return r;
}

static int qemu_rbd_open(BlockDriverState *bs, QDict *options, int flags,
                         Error **errp)
{
    BDRVRBDState *s = bs->opaque;
    BlockdevOptionsRbd *opts = NULL;
    const QDictEntry *e;
    Error *local_err = NULL;
    char *keypairs, *secretid;
    rbd_image_info_t info;
    int r;

    keypairs = g_strdup(qdict_get_try_str(options, "=keyvalue-pairs"));
    if (keypairs) {
        qdict_del(options, "=keyvalue-pairs");
    }

    secretid = g_strdup(qdict_get_try_str(options, "password-secret"));
    if (secretid) {
        qdict_del(options, "password-secret");
    }

    r = qemu_rbd_convert_options(options, &opts, &local_err);
    if (local_err) {
        /* If keypairs are present, that means some options are present in
         * the modern option format.  Don't attempt to parse legacy option
         * formats, as we won't support mixed usage. */
        if (keypairs) {
            error_propagate(errp, local_err);
            goto out;
        }

        /* If the initial attempt to convert and process the options failed,
         * we may be attempting to open an image file that has the rbd options
         * specified in the older format consisting of all key/value pairs
         * encoded in the filename.  Go ahead and attempt to parse the
         * filename, and see if we can pull out the required options. */
        r = qemu_rbd_attempt_legacy_options(options, &opts, &keypairs);
        if (r < 0) {
            /* Propagate the original error, not the legacy parsing fallback
             * error, as the latter was just a best-effort attempt. */
            error_propagate(errp, local_err);
            goto out;
        }
        /* Take care whenever deciding to actually deprecate; once this ability
         * is removed, we will not be able to open any images with legacy-styled
         * backing image strings. */
        warn_report("RBD options encoded in the filename as keyvalue pairs "
                    "is deprecated");
    }

    /* Remove the processed options from the QDict (the visitor processes
     * _all_ options in the QDict) */
    while ((e = qdict_first(options))) {
        qdict_del(options, e->key);
    }

    r = qemu_rbd_connect(&s->cluster, &s->io_ctx, opts,
                         !(flags & BDRV_O_NOCACHE), keypairs, secretid, errp);
    if (r < 0) {
        goto out;
    }

    s->snap = g_strdup(opts->snapshot);
    s->image_name = g_strdup(opts->image);

    /* rbd_open is always r/w */
    r = rbd_open(s->io_ctx, s->image_name, &s->image, s->snap);
    if (r < 0) {
        error_setg_errno(errp, -r, "error reading header from %s",
                         s->image_name);
        goto failed_open;
    }

    if (opts->encrypt) {
#ifdef LIBRBD_SUPPORTS_ENCRYPTION
        if (opts->encrypt->parent) {
#ifdef LIBRBD_SUPPORTS_ENCRYPTION_LOAD2
            r = qemu_rbd_encryption_load2(s->image, opts->encrypt, errp);
#else
            r = -ENOTSUP;
            error_setg(errp, "RBD library does not support layered encryption");
#endif
        } else {
            r = qemu_rbd_encryption_load(s->image, opts->encrypt, errp);
        }
        if (r < 0) {
            goto failed_post_open;
        }
#else
        r = -ENOTSUP;
        error_setg(errp, "RBD library does not support image encryption");
        goto failed_post_open;
#endif
    }

    r = rbd_stat(s->image, &info, sizeof(info));
    if (r < 0) {
        error_setg_errno(errp, -r, "error getting image info from %s",
                         s->image_name);
        goto failed_post_open;
    }
    s->image_size = info.size;
    s->object_size = info.obj_size;

    /* If we are using an rbd snapshot, we must be r/o, otherwise
     * leave as-is */
    if (s->snap != NULL) {
        bdrv_graph_rdlock_main_loop();
        r = bdrv_apply_auto_read_only(bs, "rbd snapshots are read-only", errp);
        bdrv_graph_rdunlock_main_loop();
        if (r < 0) {
            goto failed_post_open;
        }
    }

#ifdef LIBRBD_SUPPORTS_WRITE_ZEROES
    bs->supported_zero_flags = BDRV_REQ_MAY_UNMAP | BDRV_REQ_NO_FALLBACK;
#endif

    /* When extending regular files, we get zeros from the OS */
    bs->supported_truncate_flags = BDRV_REQ_ZERO_WRITE;

    r = 0;
    goto out;

failed_post_open:
    rbd_close(s->image);
failed_open:
    rados_ioctx_destroy(s->io_ctx);
    g_free(s->snap);
    g_free(s->image_name);
    rados_shutdown(s->cluster);
out:
    qapi_free_BlockdevOptionsRbd(opts);
    g_free(keypairs);
    g_free(secretid);
    return r;
}


/* Since RBD is currently always opened R/W via the API,
 * we just need to check if we are using a snapshot or not, in
 * order to determine if we will allow it to be R/W */
static int qemu_rbd_reopen_prepare(BDRVReopenState *state,
                                   BlockReopenQueue *queue, Error **errp)
{
    BDRVRBDState *s = state->bs->opaque;
    int ret = 0;

    GRAPH_RDLOCK_GUARD_MAINLOOP();

    if (s->snap && state->flags & BDRV_O_RDWR) {
        error_setg(errp,
                   "Cannot change node '%s' to r/w when using RBD snapshot",
                   bdrv_get_device_or_node_name(state->bs));
        ret = -EINVAL;
    }

    return ret;
}

static void qemu_rbd_close(BlockDriverState *bs)
{
    BDRVRBDState *s = bs->opaque;

    rbd_close(s->image);
    rados_ioctx_destroy(s->io_ctx);
    g_free(s->snap);
    g_free(s->image_name);
    rados_shutdown(s->cluster);
}

/* Resize the RBD image and update the 'image_size' with the current size */
static int qemu_rbd_resize(BlockDriverState *bs, uint64_t size)
{
    BDRVRBDState *s = bs->opaque;
    int r;

    r = rbd_resize(s->image, size);
    if (r < 0) {
        return r;
    }

    s->image_size = size;

    return 0;
}

static void qemu_rbd_finish_bh(void *opaque)
{
    RBDTask *task = opaque;
    task->complete = true;
    aio_co_wake(task->co);
}

/*
 * This is the completion callback function for all rbd aio calls
 * started from qemu_rbd_start_co().
 *
 * Note: this function is being called from a non qemu thread so
 * we need to be careful about what we do here. Generally we only
 * schedule a BH, and do the rest of the io completion handling
 * from qemu_rbd_finish_bh() which runs in a qemu context.
 */
static void qemu_rbd_completion_cb(rbd_completion_t c, RBDTask *task)
{
    task->ret = rbd_aio_get_return_value(c);
    rbd_aio_release(c);
    aio_bh_schedule_oneshot(bdrv_get_aio_context(task->bs),
                            qemu_rbd_finish_bh, task);
}

static int coroutine_fn qemu_rbd_start_co(BlockDriverState *bs,
                                          uint64_t offset,
                                          uint64_t bytes,
                                          QEMUIOVector *qiov,
                                          int flags,
                                          RBDAIOCmd cmd)
{
    BDRVRBDState *s = bs->opaque;
    RBDTask task = { .bs = bs, .co = qemu_coroutine_self() };
    rbd_completion_t c;
    int r;

    assert(!qiov || qiov->size == bytes);

    if (cmd == RBD_AIO_WRITE || cmd == RBD_AIO_WRITE_ZEROES) {
        /*
         * RBD APIs don't allow us to write more than actual size, so in order
         * to support growing images, we resize the image before write
         * operations that exceed the current size.
         */
        if (offset + bytes > s->image_size) {
            r = qemu_rbd_resize(bs, offset + bytes);
            if (r < 0) {
                return r;
            }
        }
    }

    r = rbd_aio_create_completion(&task,
                                  (rbd_callback_t) qemu_rbd_completion_cb, &c);
    if (r < 0) {
        return r;
    }

    switch (cmd) {
    case RBD_AIO_READ:
        r = rbd_aio_readv(s->image, qiov->iov, qiov->niov, offset, c);
        break;
    case RBD_AIO_WRITE:
        r = rbd_aio_writev(s->image, qiov->iov, qiov->niov, offset, c);
        break;
    case RBD_AIO_DISCARD:
        r = rbd_aio_discard(s->image, offset, bytes, c);
        break;
    case RBD_AIO_FLUSH:
        r = rbd_aio_flush(s->image, c);
        break;
#ifdef LIBRBD_SUPPORTS_WRITE_ZEROES
    case RBD_AIO_WRITE_ZEROES: {
        int zero_flags = 0;
#ifdef RBD_WRITE_ZEROES_FLAG_THICK_PROVISION
        if (!(flags & BDRV_REQ_MAY_UNMAP)) {
            zero_flags = RBD_WRITE_ZEROES_FLAG_THICK_PROVISION;
        }
#endif
        r = rbd_aio_write_zeroes(s->image, offset, bytes, c, zero_flags, 0);
        break;
    }
#endif
    default:
        r = -EINVAL;
    }

    if (r < 0) {
        error_report("rbd request failed early: cmd %d offset %" PRIu64
                     " bytes %" PRIu64 " flags %d r %d (%s)", cmd, offset,
                     bytes, flags, r, strerror(-r));
        rbd_aio_release(c);
        return r;
    }

    while (!task.complete) {
        qemu_coroutine_yield();
    }

    if (task.ret < 0) {
        error_report("rbd request failed: cmd %d offset %" PRIu64 " bytes %"
                     PRIu64 " flags %d task.ret %" PRIi64 " (%s)", cmd, offset,
                     bytes, flags, task.ret, strerror(-task.ret));
        return task.ret;
    }

    /* zero pad short reads */
    if (cmd == RBD_AIO_READ && task.ret < qiov->size) {
        qemu_iovec_memset(qiov, task.ret, 0, qiov->size - task.ret);
    }

    return 0;
}

static int
coroutine_fn qemu_rbd_co_preadv(BlockDriverState *bs, int64_t offset,
                                int64_t bytes, QEMUIOVector *qiov,
                                BdrvRequestFlags flags)
{
    return qemu_rbd_start_co(bs, offset, bytes, qiov, flags, RBD_AIO_READ);
}

static int
coroutine_fn qemu_rbd_co_pwritev(BlockDriverState *bs, int64_t offset,
                                 int64_t bytes, QEMUIOVector *qiov,
                                 BdrvRequestFlags flags)
{
    return qemu_rbd_start_co(bs, offset, bytes, qiov, flags, RBD_AIO_WRITE);
}

static int coroutine_fn qemu_rbd_co_flush(BlockDriverState *bs)
{
    return qemu_rbd_start_co(bs, 0, 0, NULL, 0, RBD_AIO_FLUSH);
}

static int coroutine_fn qemu_rbd_co_pdiscard(BlockDriverState *bs,
                                             int64_t offset, int64_t bytes)
{
    return qemu_rbd_start_co(bs, offset, bytes, NULL, 0, RBD_AIO_DISCARD);
}

#ifdef LIBRBD_SUPPORTS_WRITE_ZEROES
static int
coroutine_fn qemu_rbd_co_pwrite_zeroes(BlockDriverState *bs, int64_t offset,
                                       int64_t bytes, BdrvRequestFlags flags)
{
    return qemu_rbd_start_co(bs, offset, bytes, NULL, flags,
                             RBD_AIO_WRITE_ZEROES);
}
#endif

static int coroutine_fn
qemu_rbd_co_get_info(BlockDriverState *bs, BlockDriverInfo *bdi)
{
    BDRVRBDState *s = bs->opaque;
    bdi->cluster_size = s->object_size;
    return 0;
}

static ImageInfoSpecific *qemu_rbd_get_specific_info(BlockDriverState *bs,
                                                     Error **errp)
{
    BDRVRBDState *s = bs->opaque;
    ImageInfoSpecific *spec_info;
    char buf[RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN] = {0};
    int r;

    if (s->image_size >= RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN) {
        r = rbd_read(s->image, 0,
                     RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN, buf);
        if (r < 0) {
            error_setg_errno(errp, -r, "cannot read image start for probe");
            return NULL;
        }
    }

    spec_info = g_new(ImageInfoSpecific, 1);
    *spec_info = (ImageInfoSpecific){
        .type  = IMAGE_INFO_SPECIFIC_KIND_RBD,
        .u.rbd.data = g_new0(ImageInfoSpecificRbd, 1),
    };

    if (memcmp(buf, rbd_luks_header_verification,
               RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN) == 0) {
        spec_info->u.rbd.data->encryption_format =
                RBD_IMAGE_ENCRYPTION_FORMAT_LUKS;
        spec_info->u.rbd.data->has_encryption_format = true;
    } else if (memcmp(buf, rbd_luks2_header_verification,
               RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN) == 0) {
        spec_info->u.rbd.data->encryption_format =
                RBD_IMAGE_ENCRYPTION_FORMAT_LUKS2;
        spec_info->u.rbd.data->has_encryption_format = true;
    } else if (memcmp(buf, rbd_layered_luks_header_verification,
               RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN) == 0) {
        spec_info->u.rbd.data->encryption_format =
                RBD_IMAGE_ENCRYPTION_FORMAT_LUKS;
        spec_info->u.rbd.data->has_encryption_format = true;
    } else if (memcmp(buf, rbd_layered_luks2_header_verification,
               RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN) == 0) {
        spec_info->u.rbd.data->encryption_format =
                RBD_IMAGE_ENCRYPTION_FORMAT_LUKS2;
        spec_info->u.rbd.data->has_encryption_format = true;
    } else {
        spec_info->u.rbd.data->has_encryption_format = false;
    }

    return spec_info;
}

/*
 * rbd_diff_iterate2 allows to interrupt the exection by returning a negative
 * value in the callback routine. Choose a value that does not conflict with
 * an existing exitcode and return it if we want to prematurely stop the
 * execution because we detected a change in the allocation status.
 */
#define QEMU_RBD_EXIT_DIFF_ITERATE2 -9000

static int qemu_rbd_diff_iterate_cb(uint64_t offs, size_t len,
                                    int exists, void *opaque)
{
    RBDDiffIterateReq *req = opaque;

    assert(req->offs + req->bytes <= offs);

    /* treat a hole like an unallocated area and bail out */
    if (!exists) {
        return 0;
    }

    if (!req->exists && offs > req->offs) {
        /*
         * we started in an unallocated area and hit the first allocated
         * block. req->bytes must be set to the length of the unallocated area
         * before the allocated area. stop further processing.
         */
        req->bytes = offs - req->offs;
        return QEMU_RBD_EXIT_DIFF_ITERATE2;
    }

    if (req->exists && offs > req->offs + req->bytes) {
        /*
         * we started in an allocated area and jumped over an unallocated area,
         * req->bytes contains the length of the allocated area before the
         * unallocated area. stop further processing.
         */
        return QEMU_RBD_EXIT_DIFF_ITERATE2;
    }

    req->bytes += len;
    req->exists = true;

    return 0;
}

static int coroutine_fn qemu_rbd_co_block_status(BlockDriverState *bs,
                                                 bool want_zero, int64_t offset,
                                                 int64_t bytes, int64_t *pnum,
                                                 int64_t *map,
                                                 BlockDriverState **file)
{
    BDRVRBDState *s = bs->opaque;
    int status, r;
    RBDDiffIterateReq req = { .offs = offset };
    uint64_t features, flags;
    uint64_t head = 0;

    assert(offset + bytes <= s->image_size);

    /* default to all sectors allocated */
    status = BDRV_BLOCK_DATA | BDRV_BLOCK_OFFSET_VALID;
    *map = offset;
    *file = bs;
    *pnum = bytes;

    /* check if RBD image supports fast-diff */
    r = rbd_get_features(s->image, &features);
    if (r < 0) {
        return status;
    }
    if (!(features & RBD_FEATURE_FAST_DIFF)) {
        return status;
    }

    /* check if RBD fast-diff result is valid */
    r = rbd_get_flags(s->image, &flags);
    if (r < 0) {
        return status;
    }
    if (flags & RBD_FLAG_FAST_DIFF_INVALID) {
        return status;
    }

#if LIBRBD_VERSION_CODE < LIBRBD_VERSION(1, 17, 0)
    /*
     * librbd had a bug until early 2022 that affected all versions of ceph that
     * supported fast-diff. This bug results in reporting of incorrect offsets
     * if the offset parameter to rbd_diff_iterate2 is not object aligned.
     * Work around this bug by rounding down the offset to object boundaries.
     * This is OK because we call rbd_diff_iterate2 with whole_object = true.
     * However, this workaround only works for non cloned images with default
     * striping.
     *
     * See: https://tracker.ceph.com/issues/53784
     */

    /* check if RBD image has non-default striping enabled */
    if (features & RBD_FEATURE_STRIPINGV2) {
        return status;
    }

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
    /*
     * check if RBD image is a clone (= has a parent).
     *
     * rbd_get_parent_info is deprecated from Nautilus onwards, but the
     * replacement rbd_get_parent is not present in Luminous and Mimic.
     */
    if (rbd_get_parent_info(s->image, NULL, 0, NULL, 0, NULL, 0) != -ENOENT) {
        return status;
    }
#pragma GCC diagnostic pop

    head = req.offs & (s->object_size - 1);
    req.offs -= head;
    bytes += head;
#endif

    r = rbd_diff_iterate2(s->image, NULL, req.offs, bytes, true, true,
                          qemu_rbd_diff_iterate_cb, &req);
    if (r < 0 && r != QEMU_RBD_EXIT_DIFF_ITERATE2) {
        return status;
    }
    assert(req.bytes <= bytes);
    if (!req.exists) {
        if (r == 0) {
            /*
             * rbd_diff_iterate2 does not invoke callbacks for unallocated
             * areas. This here catches the case where no callback was
             * invoked at all (req.bytes == 0).
             */
            assert(req.bytes == 0);
            req.bytes = bytes;
        }
        status = BDRV_BLOCK_ZERO | BDRV_BLOCK_OFFSET_VALID;
    }

    assert(req.bytes > head);
    *pnum = req.bytes - head;
    return status;
}

static int64_t coroutine_fn qemu_rbd_co_getlength(BlockDriverState *bs)
{
    BDRVRBDState *s = bs->opaque;
    int r;

    r = rbd_get_size(s->image, &s->image_size);
    if (r < 0) {
        return r;
    }

    return s->image_size;
}

static int coroutine_fn qemu_rbd_co_truncate(BlockDriverState *bs,
                                             int64_t offset,
                                             bool exact,
                                             PreallocMode prealloc,
                                             BdrvRequestFlags flags,
                                             Error **errp)
{
    int r;

    if (prealloc != PREALLOC_MODE_OFF) {
        error_setg(errp, "Unsupported preallocation mode '%s'",
                   PreallocMode_str(prealloc));
        return -ENOTSUP;
    }

    r = qemu_rbd_resize(bs, offset);
    if (r < 0) {
        error_setg_errno(errp, -r, "Failed to resize file");
        return r;
    }

    return 0;
}

static int qemu_rbd_snap_create(BlockDriverState *bs,
                                QEMUSnapshotInfo *sn_info)
{
    BDRVRBDState *s = bs->opaque;
    int r;

    if (sn_info->name[0] == '\0') {
        return -EINVAL; /* we need a name for rbd snapshots */
    }

    /*
     * rbd snapshots are using the name as the user controlled unique identifier
     * we can't use the rbd snapid for that purpose, as it can't be set
     */
    if (sn_info->id_str[0] != '\0' &&
        strcmp(sn_info->id_str, sn_info->name) != 0) {
        return -EINVAL;
    }

    if (strlen(sn_info->name) >= sizeof(sn_info->id_str)) {
        return -ERANGE;
    }

    r = rbd_snap_create(s->image, sn_info->name);
    if (r < 0) {
        error_report("failed to create snap: %s", strerror(-r));
        return r;
    }

    return 0;
}

static int qemu_rbd_snap_remove(BlockDriverState *bs,
                                const char *snapshot_id,
                                const char *snapshot_name,
                                Error **errp)
{
    BDRVRBDState *s = bs->opaque;
    int r;

    if (!snapshot_name) {
        error_setg(errp, "rbd need a valid snapshot name");
        return -EINVAL;
    }

    /* If snapshot_id is specified, it must be equal to name, see
       qemu_rbd_snap_list() */
    if (snapshot_id && strcmp(snapshot_id, snapshot_name)) {
        error_setg(errp,
                   "rbd do not support snapshot id, it should be NULL or "
                   "equal to snapshot name");
        return -EINVAL;
    }

    r = rbd_snap_remove(s->image, snapshot_name);
    if (r < 0) {
        error_setg_errno(errp, -r, "Failed to remove the snapshot");
    }
    return r;
}

static int qemu_rbd_snap_rollback(BlockDriverState *bs,
                                  const char *snapshot_name)
{
    BDRVRBDState *s = bs->opaque;

    return rbd_snap_rollback(s->image, snapshot_name);
}

static int qemu_rbd_snap_list(BlockDriverState *bs,
                              QEMUSnapshotInfo **psn_tab)
{
    BDRVRBDState *s = bs->opaque;
    QEMUSnapshotInfo *sn_info, *sn_tab = NULL;
    int i, snap_count;
    rbd_snap_info_t *snaps;
    int max_snaps = RBD_MAX_SNAPS;

    do {
        snaps = g_new(rbd_snap_info_t, max_snaps);
        snap_count = rbd_snap_list(s->image, snaps, &max_snaps);
        if (snap_count <= 0) {
            g_free(snaps);
        }
    } while (snap_count == -ERANGE);

    if (snap_count <= 0) {
        goto done;
    }

    sn_tab = g_new0(QEMUSnapshotInfo, snap_count);

    for (i = 0; i < snap_count; i++) {
        const char *snap_name = snaps[i].name;

        sn_info = sn_tab + i;
        pstrcpy(sn_info->id_str, sizeof(sn_info->id_str), snap_name);
        pstrcpy(sn_info->name, sizeof(sn_info->name), snap_name);

        sn_info->vm_state_size = snaps[i].size;
        sn_info->date_sec = 0;
        sn_info->date_nsec = 0;
        sn_info->vm_clock_nsec = 0;
    }
    rbd_snap_list_end(snaps);
    g_free(snaps);

 done:
    *psn_tab = sn_tab;
    return snap_count;
}

static void coroutine_fn qemu_rbd_co_invalidate_cache(BlockDriverState *bs,
                                                      Error **errp)
{
    BDRVRBDState *s = bs->opaque;
    int r = rbd_invalidate_cache(s->image);
    if (r < 0) {
        error_setg_errno(errp, -r, "Failed to invalidate the cache");
    }
}

static QemuOptsList qemu_rbd_create_opts = {
    .name = "rbd-create-opts",
    .head = QTAILQ_HEAD_INITIALIZER(qemu_rbd_create_opts.head),
    .desc = {
        {
            .name = BLOCK_OPT_SIZE,
            .type = QEMU_OPT_SIZE,
            .help = "Virtual disk size"
        },
        {
            .name = BLOCK_OPT_CLUSTER_SIZE,
            .type = QEMU_OPT_SIZE,
            .help = "RBD object size"
        },
        {
            .name = "password-secret",
            .type = QEMU_OPT_STRING,
            .help = "ID of secret providing the password",
        },
        {
            .name = "encrypt.format",
            .type = QEMU_OPT_STRING,
            .help = "Encrypt the image, format choices: 'luks', 'luks2'",
        },
        {
            .name = "encrypt.cipher-alg",
            .type = QEMU_OPT_STRING,
            .help = "Name of encryption cipher algorithm"
                    " (allowed values: aes-128, aes-256)",
        },
        {
            .name = "encrypt.key-secret",
            .type = QEMU_OPT_STRING,
            .help = "ID of secret providing LUKS passphrase",
        },
        { /* end of list */ }
    }
};

static const char *const qemu_rbd_strong_runtime_opts[] = {
    "pool",
    "namespace",
    "image",
    "conf",
    "snapshot",
    "user",
    "server.",
    "password-secret",

    NULL
};

static BlockDriver bdrv_rbd = {
    .format_name            = "rbd",
    .instance_size          = sizeof(BDRVRBDState),
    .bdrv_parse_filename    = qemu_rbd_parse_filename,
    .bdrv_file_open         = qemu_rbd_open,
    .bdrv_close             = qemu_rbd_close,
    .bdrv_reopen_prepare    = qemu_rbd_reopen_prepare,
    .bdrv_co_create         = qemu_rbd_co_create,
    .bdrv_co_create_opts    = qemu_rbd_co_create_opts,
    .bdrv_has_zero_init     = bdrv_has_zero_init_1,
    .bdrv_co_get_info       = qemu_rbd_co_get_info,
    .bdrv_get_specific_info = qemu_rbd_get_specific_info,
    .create_opts            = &qemu_rbd_create_opts,
    .bdrv_co_getlength      = qemu_rbd_co_getlength,
    .bdrv_co_truncate       = qemu_rbd_co_truncate,
    .protocol_name          = "rbd",

    .bdrv_co_preadv         = qemu_rbd_co_preadv,
    .bdrv_co_pwritev        = qemu_rbd_co_pwritev,
    .bdrv_co_flush_to_disk  = qemu_rbd_co_flush,
    .bdrv_co_pdiscard       = qemu_rbd_co_pdiscard,
#ifdef LIBRBD_SUPPORTS_WRITE_ZEROES
    .bdrv_co_pwrite_zeroes  = qemu_rbd_co_pwrite_zeroes,
#endif
    .bdrv_co_block_status   = qemu_rbd_co_block_status,

    .bdrv_snapshot_create   = qemu_rbd_snap_create,
    .bdrv_snapshot_delete   = qemu_rbd_snap_remove,
    .bdrv_snapshot_list     = qemu_rbd_snap_list,
    .bdrv_snapshot_goto     = qemu_rbd_snap_rollback,
    .bdrv_co_invalidate_cache = qemu_rbd_co_invalidate_cache,

    .strong_runtime_opts    = qemu_rbd_strong_runtime_opts,
};

static void bdrv_rbd_init(void)
{
    bdrv_register(&bdrv_rbd);
}

block_init(bdrv_rbd_init);