static int vmdk_open_vmdk4(BlockDriverState *bs,

                           BlockDriverState *file,

                           int flags)

{

    int ret;

    uint32_t magic;

    uint32_t l1_size, l1_entry_sectors;

    VMDK4Header header;

    VmdkExtent *extent;

    int64_t l1_backup_offset = 0;



    ret = bdrv_pread(file, sizeof(magic), &header, sizeof(header));

    if (ret < 0) {

        return ret;

    }

    if (header.capacity == 0) {

        uint64_t desc_offset = le64_to_cpu(header.desc_offset);

        if (desc_offset) {

            return vmdk_open_desc_file(bs, flags, desc_offset << 9);

        }

    }



    if (le64_to_cpu(header.gd_offset) == VMDK4_GD_AT_END) {

        /*

         * The footer takes precedence over the header, so read it in. The

         * footer starts at offset -1024 from the end: One sector for the

         * footer, and another one for the end-of-stream marker.

         */

        struct {

            struct {

                uint64_t val;

                uint32_t size;

                uint32_t type;

                uint8_t pad[512 - 16];

            } QEMU_PACKED footer_marker;



            uint32_t magic;

            VMDK4Header header;

            uint8_t pad[512 - 4 - sizeof(VMDK4Header)];



            struct {

                uint64_t val;

                uint32_t size;

                uint32_t type;

                uint8_t pad[512 - 16];

            } QEMU_PACKED eos_marker;

        } QEMU_PACKED footer;



        ret = bdrv_pread(file,

            bs->file->total_sectors * 512 - 1536,

            &footer, sizeof(footer));

        if (ret < 0) {

            return ret;

        }



        /* Some sanity checks for the footer */

        if (be32_to_cpu(footer.magic) != VMDK4_MAGIC ||

            le32_to_cpu(footer.footer_marker.size) != 0  ||

            le32_to_cpu(footer.footer_marker.type) != MARKER_FOOTER ||

            le64_to_cpu(footer.eos_marker.val) != 0  ||

            le32_to_cpu(footer.eos_marker.size) != 0  ||

            le32_to_cpu(footer.eos_marker.type) != MARKER_END_OF_STREAM)

        {

            return -EINVAL;

        }



        header = footer.header;

    }



    if (le32_to_cpu(header.version) >= 3) {

        char buf[64];

        snprintf(buf, sizeof(buf), "VMDK version %d",

                 le32_to_cpu(header.version));

        qerror_report(QERR_UNKNOWN_BLOCK_FORMAT_FEATURE,

                bs->device_name, "vmdk", buf);

        return -ENOTSUP;

    }



    l1_entry_sectors = le32_to_cpu(header.num_gtes_per_gte)

                        * le64_to_cpu(header.granularity);

    if (l1_entry_sectors == 0) {

        return -EINVAL;

    }

    l1_size = (le64_to_cpu(header.capacity) + l1_entry_sectors - 1)

                / l1_entry_sectors;

    if (le32_to_cpu(header.flags) & VMDK4_FLAG_RGD) {

        l1_backup_offset = le64_to_cpu(header.rgd_offset) << 9;

    }

    extent = vmdk_add_extent(bs, file, false,

                          le64_to_cpu(header.capacity),

                          le64_to_cpu(header.gd_offset) << 9,

                          l1_backup_offset,

                          l1_size,

                          le32_to_cpu(header.num_gtes_per_gte),

                          le64_to_cpu(header.granularity));

    extent->compressed =

        le16_to_cpu(header.compressAlgorithm) == VMDK4_COMPRESSION_DEFLATE;

    extent->has_marker = le32_to_cpu(header.flags) & VMDK4_FLAG_MARKER;

    extent->version = le32_to_cpu(header.version);

    extent->has_zero_grain = le32_to_cpu(header.flags) & VMDK4_FLAG_ZERO_GRAIN;

    ret = vmdk_init_tables(bs, extent);

    if (ret) {

        /* free extent allocated by vmdk_add_extent */

        vmdk_free_last_extent(bs);

    }

    return ret;

}
