static int nbd_negotiate_handle_info(NBDClient *client, uint32_t length,

                                     uint32_t opt, uint16_t myflags,

                                     Error **errp)

{

    int rc;

    char name[NBD_MAX_NAME_SIZE + 1];

    NBDExport *exp;

    uint16_t requests;

    uint16_t request;

    uint32_t namelen;

    bool sendname = false;

    bool blocksize = false;

    uint32_t sizes[3];

    char buf[sizeof(uint64_t) + sizeof(uint16_t)];

    const char *msg;



    /* Client sends:

        4 bytes: L, name length (can be 0)

        L bytes: export name

        2 bytes: N, number of requests (can be 0)

        N * 2 bytes: N requests

    */

    if (length < sizeof(namelen) + sizeof(requests)) {

        msg = "overall request too short";



    if (nbd_read(client->ioc, &namelen, sizeof(namelen), errp) < 0) {

        return -EIO;


    be32_to_cpus(&namelen);

    length -= sizeof(namelen);

    if (namelen > length - sizeof(requests) || (length - namelen) % 2) {

        msg = "name length is incorrect";







    if (nbd_read(client->ioc, name, namelen, errp) < 0) {

        return -EIO;


    name[namelen] = '\0';

    length -= namelen;

    trace_nbd_negotiate_handle_export_name_request(name);



    if (nbd_read(client->ioc, &requests, sizeof(requests), errp) < 0) {

        return -EIO;


    be16_to_cpus(&requests);

    length -= sizeof(requests);

    trace_nbd_negotiate_handle_info_requests(requests);

    if (requests != length / sizeof(request)) {

        msg = "incorrect number of  requests for overall length";



    while (requests--) {

        if (nbd_read(client->ioc, &request, sizeof(request), errp) < 0) {

            return -EIO;


        be16_to_cpus(&request);

        length -= sizeof(request);

        trace_nbd_negotiate_handle_info_request(request,

                                                nbd_info_lookup(request));

        /* We care about NBD_INFO_NAME and NBD_INFO_BLOCK_SIZE;

         * everything else is either a request we don't know or

         * something we send regardless of request */

        switch (request) {

        case NBD_INFO_NAME:

            sendname = true;

            break;

        case NBD_INFO_BLOCK_SIZE:

            blocksize = true;

            break;



    assert(length == 0);



    exp = nbd_export_find(name);

    if (!exp) {

        return nbd_negotiate_send_rep_err(client->ioc, NBD_REP_ERR_UNKNOWN,

                                          opt, errp, "export '%s' not present",

                                          name);




    /* Don't bother sending NBD_INFO_NAME unless client requested it */

    if (sendname) {

        rc = nbd_negotiate_send_info(client, opt, NBD_INFO_NAME, namelen, name,

                                     errp);

        if (rc < 0) {

            return rc;





    /* Send NBD_INFO_DESCRIPTION only if available, regardless of

     * client request */

    if (exp->description) {

        size_t len = strlen(exp->description);



        rc = nbd_negotiate_send_info(client, opt, NBD_INFO_DESCRIPTION,

                                     len, exp->description, errp);

        if (rc < 0) {

            return rc;





    /* Send NBD_INFO_BLOCK_SIZE always, but tweak the minimum size

     * according to whether the client requested it, and according to

     * whether this is OPT_INFO or OPT_GO. */

    /* minimum - 1 for back-compat, or 512 if client is new enough.

     * TODO: consult blk_bs(blk)->bl.request_alignment? */

    sizes[0] = (opt == NBD_OPT_INFO || blocksize) ? BDRV_SECTOR_SIZE : 1;

    /* preferred - Hard-code to 4096 for now.

     * TODO: is blk_bs(blk)->bl.opt_transfer appropriate? */

    sizes[1] = 4096;

    /* maximum - At most 32M, but smaller as appropriate. */

    sizes[2] = MIN(blk_get_max_transfer(exp->blk), NBD_MAX_BUFFER_SIZE);

    trace_nbd_negotiate_handle_info_block_size(sizes[0], sizes[1], sizes[2]);

    cpu_to_be32s(&sizes[0]);

    cpu_to_be32s(&sizes[1]);

    cpu_to_be32s(&sizes[2]);

    rc = nbd_negotiate_send_info(client, opt, NBD_INFO_BLOCK_SIZE,

                                 sizeof(sizes), sizes, errp);

    if (rc < 0) {

        return rc;




    /* Send NBD_INFO_EXPORT always */

    trace_nbd_negotiate_new_style_size_flags(exp->size,

                                             exp->nbdflags | myflags);

    stq_be_p(buf, exp->size);

    stw_be_p(buf + 8, exp->nbdflags | myflags);

    rc = nbd_negotiate_send_info(client, opt, NBD_INFO_EXPORT,

                                 sizeof(buf), buf, errp);

    if (rc < 0) {

        return rc;




    /* If the client is just asking for NBD_OPT_INFO, but forgot to

     * request block sizes, return an error.

     * TODO: consult blk_bs(blk)->request_align, and only error if it

     * is not 1? */

    if (opt == NBD_OPT_INFO && !blocksize) {

        return nbd_negotiate_send_rep_err(client->ioc,

                                          NBD_REP_ERR_BLOCK_SIZE_REQD, opt,

                                          errp,

                                          "request NBD_INFO_BLOCK_SIZE to "

                                          "use this export");




    /* Final reply */

    rc = nbd_negotiate_send_rep(client->ioc, NBD_REP_ACK, opt, errp);

    if (rc < 0) {

        return rc;




    if (opt == NBD_OPT_GO) {

        client->exp = exp;

        QTAILQ_INSERT_TAIL(&client->exp->clients, client, next);

        nbd_export_get(client->exp);

        rc = 1;


    return rc;



 invalid:

    if (nbd_drop(client->ioc, length, errp) < 0) {

        return -EIO;


    return nbd_negotiate_send_rep_err(client->ioc, NBD_REP_ERR_INVALID, opt,

                                      errp, "%s", msg);
