blob: f31cb53f69ec93f40344f7a3146e88ceb3f2b1f0 [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef GARNET_LIB_MACHINA_QCOW_H_
#define GARNET_LIB_MACHINA_QCOW_H_
#include <endian.h>
#include <fbl/unique_fd.h>
#include <fbl/unique_ptr.h>
#include <zircon/compiler.h>
#include <zircon/types.h>
#include <string>
#include "garnet/lib/machina/block_dispatcher.h"
#include "garnet/lib/machina/qcow_refcount.h"
#include "lib/fxl/macros.h"
namespace machina {
// Each QCOW file starts with this magic value "QFI\xfb".
static constexpr uint32_t kQcowMagic = 0x514649fb;
// The top bits in a table entry have special significance. The remaining bits
// identify the target. For L1 table entries this is physical offset of the L2
// table. For L2 table entries, this is the physical offset of the cluster.
static constexpr uint64_t kTableEntryCopiedBit = 1ul << 63;
static constexpr uint64_t kTableEntryCompressedBit = 1ul << 62;
static constexpr uint64_t kTableOffsetMask =
~(kTableEntryCopiedBit | kTableEntryCompressedBit);
struct BigToHostEndianTraits {
static uint16_t Convert(uint16_t val) { return be16toh(val); }
static uint32_t Convert(uint32_t val) { return be32toh(val); }
static uint64_t Convert(uint64_t val) { return be64toh(val); }
};
struct HostToBigEndianTraits {
static uint16_t Convert(uint16_t val) { return htobe16(val); }
static uint32_t Convert(uint32_t val) { return htobe32(val); }
static uint64_t Convert(uint64_t val) { return htobe64(val); }
};
struct QcowHeader {
uint32_t magic;
uint32_t version;
uint64_t backing_file_offset;
uint32_t backing_file_size;
uint32_t cluster_bits;
uint64_t size;
uint32_t crypt_method;
uint32_t l1_size;
uint64_t l1_table_offset;
uint64_t refcount_table_offset;
uint32_t refcount_table_clusters;
uint32_t nb_snapshots;
uint64_t snapshots_offset;
// Only present on version 3+
uint64_t incompatible_features;
uint64_t compatible_features;
uint64_t autoclear_features;
uint32_t refcount_order;
uint32_t header_length;
uint32_t cluster_size() const { return 1u << cluster_bits; }
// Return a new header that has been converted from host-endian to big-endian.
QcowHeader HostToBigEndian() const {
return ByteSwap<HostToBigEndianTraits>();
}
// Return a new header that has been converted from big-endian to host-endian.
QcowHeader BigToHostEndian() const {
return ByteSwap<BigToHostEndianTraits>();
}
private:
// Byte-swaps the members of the header using the desired scheme.
template <typename ByteOrderTraits>
QcowHeader ByteSwap() const {
return QcowHeader{
.magic = ByteOrderTraits::Convert(magic),
.version = ByteOrderTraits::Convert(version),
.backing_file_offset = ByteOrderTraits::Convert(backing_file_offset),
.backing_file_size = ByteOrderTraits::Convert(backing_file_size),
.cluster_bits = ByteOrderTraits::Convert(cluster_bits),
.size = ByteOrderTraits::Convert(size),
.crypt_method = ByteOrderTraits::Convert(crypt_method),
.l1_size = ByteOrderTraits::Convert(l1_size),
.l1_table_offset = ByteOrderTraits::Convert(l1_table_offset),
.refcount_table_offset =
ByteOrderTraits::Convert(refcount_table_offset),
.refcount_table_clusters =
ByteOrderTraits::Convert(refcount_table_clusters),
.nb_snapshots = ByteOrderTraits::Convert(nb_snapshots),
.snapshots_offset = ByteOrderTraits::Convert(snapshots_offset),
.incompatible_features =
ByteOrderTraits::Convert(incompatible_features),
.compatible_features = ByteOrderTraits::Convert(compatible_features),
.autoclear_features = ByteOrderTraits::Convert(autoclear_features),
.refcount_order = ByteOrderTraits::Convert(refcount_order),
.header_length = ByteOrderTraits::Convert(header_length),
};
}
} __PACKED;
class QcowFile {
public:
QcowFile();
~QcowFile();
QcowFile(QcowFile&& other);
QcowFile& operator=(QcowFile&& other);
const QcowHeader& header() const { return header_; }
// The size (in bytes) of the virtual disk exposed by this QCOW file.
size_t size() const { return header_.size; }
// The number of bytes in a single cluster.
//
// This will be the unit used for all block allocations (both data and meta-
// data blocks).
size_t cluster_size() const { return 1u << header_.cluster_bits; }
// Load the file header and verifiy the image is a valid QCOW file.
zx_status_t Load(int fd);
// Read |size| bytes from the given |linear_offset| in the file.
//
// It is not an error for a read to cross an unmapped cluster, but in that
// cast the section of |buf| that would contain those bytes for the unmapped
// cluster will be left unmodified.
zx_status_t Read(uint64_t linear_offset, void* buf, size_t size);
QcowRefcount* refcount_table() { return &refcount_table_; }
private:
FXL_DISALLOW_COPY_AND_ASSIGN(QcowFile);
fbl::unique_fd fd_;
QcowHeader header_;
class LookupTable;
fbl::unique_ptr<LookupTable> lookup_table_;
QcowRefcount refcount_table_;
};
class QcowDispatcher : public BlockDispatcher {
public:
static zx_status_t Create(int fd, bool read_only,
fbl::unique_ptr<BlockDispatcher>* out);
private:
QcowDispatcher(QcowFile qcow, bool read_only);
// |BlockDispatcher|
zx_status_t Flush() override;
zx_status_t Read(off_t disk_offset, void* buf, size_t size) override;
zx_status_t Write(off_t disk_offset, const void* buf, size_t size) override;
zx_status_t Submit() override;
QcowFile qcow_;
};
} // namespace machina
#endif // GARNET_LIB_MACHINA_QCOW_H_