|
|
nono 1.7.0
//
// nono
// Copyright (C) 2024 nono project
// Licensed under nono-license.txt
//
//
// ディスクイメージドライバ (QCOW2 形式)
//
// https://www.qemu.org/docs/master/interop/qcow2.html
//
// L1 テーブル、L2 テーブル、データ、refcount テーブル、refcount ブロック等は
// いずれもクラスタ単位で割り当てる。
// 1クラスタのバイト数は 1U << qcow2_header.cluster_bits で可変だが、
// 通常 64KB。従ってこの場合、ディスクイメージの先頭の 64KB がインデックス 0、
// 次の $0001'0000 からの 64KB がインデックス 1 … と番号付けする。
//
// L2 テーブルは 1エントリ 8バイト (64bit) で1クラスタ分を収容する。
// 64KB クラスタの場合 1クラスタあたり 8192 エントリ。
// 1つの L2 テーブルで 64KB データクラスタ * 8192 エントリ = 512MB。
//
// L1 テーブルも 1エントリ 8バイト (64bit) で1クラスタ分なので、
// 64KB クラスタの場合 1クラスタあたり 8192 エントリ。
// L1 テーブルはヘッダで指定される位置に1つ。
// 運用中にエントリ数が増えたりはしない。
//
// refcount テーブル (1段目) は、1エントリ 8バイト (64bit) で、
// refcount ブロック (2段目) の開始アドレスを持つ。0 なら未使用(未割り当て)。
// refcount ブロックは、通常1エントリ 2バイト (16bit) で、ホストクラスタの
// 使用状況を表す。refcount が 0 なら未使用クラスタ、1 なら使用中。
// 2 以上ならスナップショット等で共有されていることを示すが、スナップショット
// 等を実装していないため現状起きない。
// 64KB/クラスタの場合 1ブロックに 32768 クラスタ分の refcount を収容。
// refcount の対象はデータクラスタではなくホストクラスタなので、単純に
// クラスタ #n はイメージファイル先頭から n * cluster_size バイト目のクラスタ
// を指す。
// 注: 実際は refcount_order によって可変に出来る構造だが、
// 現状 16bit のみ対応。
// ログレベルは
// 1: 容量の拡張などの構造が変化する事案。Attach 時のサマリ。
// 2: Read/Write アクセスのサマリ。
// 3: Read/Write アクセスの詳細。
#include "diskdriver_qcow2.h"
#include <fcntl.h>
#include <sys/stat.h>
#include <zlib.h>
#define Px64 "%08x'%08x"
#define V64(x) (uint32)((x) >> 32), (uint32)(x)
// 定義とか。
class QCOW2
{
public:
static constexpr uint32 HEADER_MAGIC = 0x514649fb;
// 暗号化方法。
static constexpr uint32 CRYPT_NONE = 0;
static constexpr uint32 CRYPT_AES = 1;
static constexpr uint32 CRYPT_LUKS = 2;
// 圧縮方法。
static constexpr uint32 COMPRESS_ZLIB = 0;
static constexpr uint32 COMPRESS_ZSTD = 1;
// ヘッダの拡張タイプ
static constexpr uint32 EXTTYPE_END = 0;
static constexpr uint32 EXTTYPE_BACKING_FILENAME = 0xe2792aca;
static constexpr uint32 EXTTYPE_FEATURE_NAME = 0x6803f857;
static constexpr uint32 EXTTYPE_BITMAPS = 0x23852875;
// L1 テーブルエントリ。
//
// 6 5 4 3 2 1 0
// 32109876 54321098 76543210 98765432 10987654 32109876 54321098 76543210
//+--------+--------+--------+--------+--------+--------+--------+--------+
//|U0000000 AAAAAAAA AAAAAAAA AAAAAAAA AAAAAAAA AAAAAAAA AAAAAAA0 00000000|
//+--------+--------+--------+--------+--------+--------+--------+--------+
// U (bit 63): %0 なら L2 テーブルは未使用もしくは CoW。
// %1 なら refcount==1。
// A (bit 55-9): L2 テーブル開始位置のイメージファイル上でのオフセット。
// クラスタ境界に整列していること。
// 0 ならこの L2 (とそのクラスタ) はすべて未確保。
// 0: Reserved
// L2 テーブルエントリ。
//
// 32109876 54321098 76543210 98765432 10987654 32109876 54321098 76543210
//+--------+--------+--------+--------+--------+--------+--------+--------+
//|SCDDDDDD DDDDDDDD DDDDDDDD DDDDDDDD DDDDDDDD DDDDDDDD DDDDDDDD DDDDDDDD|
//+--------+--------+--------+--------+--------+--------+--------+--------+
// S (bit 63): %0 なら未使用/圧縮/CoW クラスタを示す。
// %1 なら refcount==1 の標準クラスタを示す。
// C (bit 62): %0 なら標準クラスタ、%1 なら圧縮クラスタ。
// D (bit 61-0): 種類ごとのクラスタディスクリプタ。
// 標準クラスタディスクリプタの場合。
//
// 109876 54321098 76543210 98765432 10987654 32109876 54321098 76543210
//+--------+--------+--------+--------+--------+--------+--------+--------+
//| 000000 AAAAAAAA AAAAAAAA AAAAAAAA AAAAAAAA AAAAAAAA AAAAAAA0 0000000Z|
//+--------+--------+--------+--------+--------+--------+--------+--------+
// A (bit 55-9): クラスタオフセット。クラスタ境界に整列していること。
// オフセットが 0 で bit63 が %0 なら、クラスタは未確保。
// bit63 が %1 ならオフセットは 0 を取りうる。
// Z (bit 0): %1 ならこのクラスタはすべてゼロが読める。
// 0: Reserved
// 圧縮クラスタディスクリプタの場合。
//
// 109876 54321098 76543210 98765432 10987654 32109876 54321098 76543210
//+--------+--------+--------+--------+--------+--------+--------+--------+
//| NNNNNN NNAAAAAA AAAAAAAA AAAAAAAA AAAAAAAA AAAAAAAA AAAAAAAA AAAAAAAA|
//+--------+--------+--------+--------+--------+--------+--------+--------+
// A (bit(x-1)-0): オフセット。クラスタ境界整列の制約はない (通常整列
// していない)。
// N (bit61-x): Aで表されるオフセットを含むセクタを超えて、圧縮データに
// 使用される追加の 512 バイトセクタの数。
// クラスタ境界を越えても構わない。
// x = 62 - (cluster_bits - 8)。例えば cluster_bits=16 なら 54。
// L1、L2、標準クラスタのディスクリプタ配置をじっとにらむと
// ビット位置は衝突していない。
static constexpr uint64 DESC_USED = 0x80000000'00000000ULL;
static constexpr uint64 DESC_COMPRESSED = 0x40000000'00000000ULL;
static constexpr uint64 DESC_OFFSET_MASK = 0x00ffffff'fffffe00ULL;
static constexpr uint64 DESC_ALLZERO = 0x00000000'00000001ULL;
};
// QCOW2 ヘッダ。ファイル上の値はすべてビッグエンディアン。
struct qcow2_header
{
uint32 magic {}; // QCOW2::HEADER_MAGIC
uint32 version {}; // バージョン (2 か 3)
// バッキングファイルがある時はファイル名のオフセット。
// バッキングファイルがない時は 0。
uint64 backing_file_offset {};
// バッキングファイルがある時はファイル名の長さ。'\0' 終端ではない。
// バッキングファイルがない時は不定。
uint32 backing_file_size {};
// クラスタのビット数。通常 16 (64KB)。
// 9 未満 (512バイト未満) には出来ない。
// 現在の QEMU 実装ではクラスタサイズ 2MB が上限のようだ。
uint32 cluster_bits {};
// 仮想ディスクサイズ [byte]
uint64 disk_size {};
// 暗号化方法。0: 暗号化なし、1: AES、2:LUKS。
uint32 crypt_method {};
// L1 テーブルのエントリ数。
uint32 L1_size {};
// L1 テーブルの開始オフセット。クラスタ境界に整列していること。
uint64 L1_table_offset {};
// refcount テーブルの開始オフセット。クラスタ境界に整列していること。
uint64 refcount_table_offset {};
// refcount テーブルが専有しているクラスタ数。
uint32 refcount_table_clusters {};
// イメージに含まれているスナップショット数。
uint32 nb_snapshots {};
// スナップショットテーブルの開始オフセット。クラスタ境界に整列。
uint64 snapshots_offset {};
// ここから version >= 3 の場合のみ。
uint64 incompatible_features {};
uint64 compatible_features {};
uint64 autoclear_features {};
uint32 refcount_order {};
uint32 header_length {};
// ここから header_length > 104 の場合のみ。
uint8 compression_type {};
uint8 padding_105_111[7] {};
} __packed;
// コンストラクタ
DiskDriverQcow2::DiskDriverQcow2(const std::string& objname_,
const std::string& pathname_)
: inherited(objname_ + ".qcow2", pathname_)
{
// デフォルトは true (空き)。
freemap.SetDefault(true);
}
// デストラクタ
DiskDriverQcow2::~DiskDriverQcow2()
{
Close();
}
// 知らないファイルフォーマットなら黙って false を返す。
bool
DiskDriverQcow2::Match()
{
uint32 hdr[2];
fd = open(pathname.c_str(), O_RDONLY);
if (fd < 0) {
return false;
}
auto r = readpos(fd, hdr, sizeof(hdr), 0);
if (r < 0) {
return false;
}
uint32 magic = be32toh(hdr[0]);
uint32 version_ = be32toh(hdr[1]);
if (magic != QCOW2::HEADER_MAGIC) {
return false;
}
if (version_ != 2 && version_ != 3) {
return false;
}
return true;
}
// サポートしていない形式なら errmsg にエラーメッセージをセットして
// false を返す。
bool
DiskDriverQcow2::Attach(std::string& errmsg)
{
qcow2_header hdr;
assert(fd.Valid());
// v2 ヘッダは72バイト固定。
auto r = readpos(fd, &hdr, 72, 0);
if (r < 0) {
errmsg = "file is too small";
return false;
}
// イメージ上の値はすべてビッグエンディアン。
// メンバに持つか、ローカルだけか、デバッグ表示するだけかで処置が違う。
BE32TOH(hdr.magic);
version = be32toh(hdr.version);
uint64 backing_file_offset = be64toh(hdr.backing_file_offset);
BE32TOH(hdr.backing_file_size);
cluster_bits = be32toh(hdr.cluster_bits);
cluster_size = 1U << cluster_bits;
filesize = be64toh(hdr.disk_size);
uint32 crypt_method = be32toh(hdr.crypt_method);
L1_size = be32toh(hdr.L1_size);
L1_table_offset = be64toh(hdr.L1_table_offset);
refcount_table_offset = be64toh(hdr.refcount_table_offset);
refcount_table_clusters = be32toh(hdr.refcount_table_clusters);
BE32TOH(hdr.nb_snapshots);
BE64TOH(hdr.snapshots_offset);
// version 3 なら、追加で104バイト目までが固定。
uint64 incompatible_features = 0;
uint32 refcount_order = 4;
uint32 header_length = 72;
uint8 compression_type = 0;
if (version >= 3) {
r = readpos(fd, (uint8 *)&hdr + 72, 104 - 72, 72);
if (r < 0) {
errmsg = "file is too short (v3)";
return false;
}
incompatible_features = be64toh(hdr.incompatible_features);
BE64TOH(hdr.compatible_features);
BE64TOH(hdr.autoclear_features);
refcount_order = be32toh(hdr.refcount_order);
header_length = be32toh(hdr.header_length);
// 続きがあれば読む。ただし v3 では 112 バイト目までが定義済み。
if (header_length > 112) {
header_length = 112;
}
if (header_length > 104) {
r = readpos(fd, (uint8 *)&hdr + 104, header_length - 104, 104);
if (r < 0) {
errmsg = "file is too short (v3)";
return false;
}
compression_type = hdr.compression_type;
}
}
const char *crypt_method_str = CryptMethodToStr(crypt_method);
const char *compression_type_str = CompressionTypeToStr(compression_type);
if (loglevel >= 1) {
putmsgn("%s:", pathname.c_str());
putmsgn("magic = %08x (%s), version = 0x%x", hdr.magic,
(hdr.magic == QCOW2::HEADER_MAGIC ? "ok" : "bad"), version);
putmsgn("backing_file_offset = 0x" Px64, V64(backing_file_offset));
putmsgn("backing_file_size = 0x%08x", hdr.backing_file_size);
putmsgn("cluster_bits = %u (size = %u)",
cluster_bits, (1U << cluster_bits));
putmsgn("disk_size = 0x" Px64, V64(filesize));
if (crypt_method_str) {
putmsgn("crypt_method = 0x%x (%s)", crypt_method, crypt_method_str);
} else {
putmsgn("crypt_method = 0x%x (unknown)", crypt_method);
}
putmsgn("L1_size = 0x%08x", L1_size);
putmsgn("L1_table_offset = 0x" Px64, V64(L1_table_offset));
putmsgn("refcount_table_offset = 0x" Px64, V64(refcount_table_offset));
putmsgn("refcount_table_clusters = 0x%x", refcount_table_clusters);
putmsgn("nb_snapshots = 0x%x", hdr.nb_snapshots);
putmsgn("snapshots_offset = 0x" Px64, V64(hdr.snapshots_offset));
if (version >= 3) {
putmsgn("incompatible_features = 0x" Px64,
V64(incompatible_features));
putmsgn("compatible_features = 0x" Px64,
V64(hdr.compatible_features));
putmsgn("autoclear_features = 0x" Px64,
V64(hdr.autoclear_features));
putmsgn("refcount_order = %u (bits = %u)",
refcount_order, (1U << refcount_order));
putmsgn("header_length = %d", header_length);
if (compression_type_str) {
putmsgn("compression_type = 0x%x (%s)",
compression_type, compression_type_str);
} else {
putmsgn("compression_type = 0x%x (unknown)",
compression_type);
}
}
}
// header_length バイト目以降にある拡張部は使っていないので放置。
// 外部ファイルによる COW はサポートしていない。
if (backing_file_offset != 0) {
errmsg = "backing file not supported";
return false;
}
// 暗号化はサポートしていない。
if (crypt_method != QCOW2::CRYPT_NONE) {
if (crypt_method_str) {
errmsg = std::string(crypt_method_str);
} else {
errmsg = string_format("crypt_method %u", crypt_method);
}
errmsg += " not supported";
return false;
}
// incompatible_features を調べる。
// 未対応のビットが立っていたらオープン時にエラーにしなければならない。
// 現状全部対応していない。
if (incompatible_features != 0) {
errmsg = string_format("incompatible_features=0x" Px64 " not supported",
V64(incompatible_features));
return false;
}
// compatible_features は知らなくても安全に無視出来る?
// autoclear_features は、知らなければクリアしてよい…らしい。
// が書き込みを伴うため Open() で処理する。
if (refcount_order != 4) {
errmsg = string_format("refcount_bits=%u not supported",
1U << refcount_order);
return false;
}
// refblock_capacity は 1クラスタに収容できる refcount の数。
// 64KB/クラスタで refcount_bits=16 (refcount_order=4) なら 32768。
refblock_capacity = cluster_size >> (refcount_order - 3);
refblock_bits = cluster_bits - (refcount_order - 3);
refblock_mask = refblock_capacity - 1;
// 圧縮方法は zlib しかサポートしていない。
if (compression_type != QCOW2::COMPRESS_ZLIB) {
if (compression_type_str) {
errmsg = std::string(compression_type_str);
} else {
errmsg = string_format("compression_type %u", compression_type);
}
errmsg += " not supported";
return false;
}
return true;
}
bool
DiskDriverQcow2::Open(bool read_only)
{
assert(pathname.empty() == false);
if (read_only == false) {
// R/W なら開き直す。
fd = open(pathname.c_str(), O_RDWR);
if (fd < 0) {
warn("%s", pathname.c_str());
return false;
}
// autoclear_features は、知らなければクリアしてよい…らしい。
// 書き込み可能になったここで行う。
uint64 autoclear;
off_t offset = offsetof(qcow2_header, autoclear_features);
auto r = readpos(fd, &autoclear, sizeof(autoclear), offset);
if (__predict_false(r < 0)) {
// 読めないことはないはずだが、読めなかったらたぶん相当おかしい。
warn("%s: %s readpos(@0x%x) failed",
pathname.c_str(), __func__, (uint32)offset);
return false;
}
if (__predict_false(autoclear != 0)) {
autoclear = 0;
writepos(fd, &autoclear, sizeof(autoclear), offset);
// 失敗しても気にしない?
}
}
// L1 テーブルを読み込む。
L1_table.resize(L1_size);
if (ReadTable(L1_table, L1_table_offset) == false) {
return false;
}
// L2 テーブルキャッシュを初期化。
cluster_mask = cluster_size - 1;
L2_entries = cluster_size / sizeof(uint64);
L2_entries_bits = cluster_bits - 3;
L2_entries_mask = L2_entries - 1;
L2_table.resize(L2_entries);
cached_L1_index = (uint64)-1;
// refcount テーブルを読み込んでオンメモリ版を構成。
if (ReadRefcount() == false) {
return false;
}
// 圧縮クラスタ用のパラメータ。
// offset_bits がオフセット部のビット数。
// sector_mask はサイズ部(を右詰めしたもの) に対するマスク。
uint szbits = cluster_bits - 8;
compress_offset_bits = 62 - szbits;
compress_sector_mask = (1ULL << szbits) - 1;
// 圧縮クラスタを持つイメージへの書き込みは、この仕組みと用途を
// 考えるとメリットがないため、サポートしない。
// しかし圧縮クラスタを持っているかどうかはヘッダには書いてなく、
// L2 ディスクリプタを全部調べる必要がある。
int has_compressed = HasCompressed();
if (has_compressed < 0) {
return false;
}
if (has_compressed && read_only == false) {
warnx("%s: Compressed QCOW2 must be read-only", pathname.c_str());
return false;
}
// データキャッシュを初期化。
data_cluster.resize(cluster_size);
cached_data_index = (uint64)-1;
return true;
}
void
DiskDriverQcow2::Close()
{
fd.Close();
filesize = 0;
}
bool
DiskDriverQcow2::Read(void *buf, off_t offset, size_t size)
{
putmsg(2, "%s offset=0x" Px64 ", size=%zu", __func__, V64(offset), size);
uint64 cl_idx = offset >> cluster_bits;
uint64 cl_off = offset & cluster_mask;
for (; size > 0; cl_idx++) {
// L1/L2_index は、キャスト云々の面倒な事故を防ぐため uint64 に
// しているが、値域としては事実上 uint32 で収まる。
uint64 L1_index = cl_idx >> L2_entries_bits;
uint64 L2_index = cl_idx & L2_entries_mask;
uint64 cl_len = std::min((uint64)size, cluster_size - cl_off);
uint64 L1_desc;
uint64 L2_desc;
if (__predict_false(L1_index >= L1_size)) {
putmsg(0, "%s: L1_index(%u) is out of L1_size(%u)",
__func__, (uint32)L1_index, L1_size);
return false;
}
// キャッシュに乗っていればそこから使う。
if (cl_idx == cached_data_index) {
memcpy(buf, &data_cluster[cl_off], cl_len);
goto done;
}
L1_desc = L1_table[L1_index];
if (__predict_false((L1_desc & QCOW2::DESC_USED) == 0)) {
// L2 全体が未割当。
putmsg(2, " L1[0x%x] desc=0x" Px64, (uint32)L1_index, V64(L1_desc));
memset(buf, 0, cl_len);
goto done;
}
// 必要なら L2_table を更新。
if (__predict_false(cached_L1_index != L1_index)) {
uint64 L2_offset = L1_desc & QCOW2::DESC_OFFSET_MASK;
if (ReadTable(L2_table, L2_offset) == false) {
cached_L1_index = (uint64)-1;
return false;
}
cached_L1_index = L1_index;
}
L2_desc = L2_table[L2_index];
putmsg(2, " L2[0x%x/%04x] desc=0x" Px64,
(uint32)L1_index, (uint32)L2_index, V64(L2_desc));
// if (bit63:USED) {
// if (bit0:ALLZERO)
// ゼロクラスタ;
// else
// 標準クラスタ;
// } else {
// if (bit62:COMPRESSED)
// 圧縮クラスタ;
// else
// 未割当クラスタ;
// }
if (__predict_true((L2_desc & QCOW2::DESC_USED))) {
if (__predict_false((L2_desc & QCOW2::DESC_ALLZERO))) {
// ゼロクラスタ。
memset(buf, 0, cl_len);
} else {
// 標準クラスタ。
uint64 off = L2_desc & QCOW2::DESC_OFFSET_MASK;
auto r = readpos(fd, data_cluster.data(), data_cluster.size(),
(off_t)off);
if (__predict_false(r < 0)) {
warn("%s: %s readpos(@0x%" PRIx64 ") failed",
pathname.c_str(), __func__, off);
return false;
}
cached_data_index = cl_idx;
memcpy(buf, &data_cluster[cl_off], cl_len);
}
} else {
if ((L2_desc & QCOW2::DESC_COMPRESSED)) {
// 圧縮クラスタ。
uint64 offset_mask = (1ULL << compress_offset_bits) - 1;
uint64 off = L2_desc & offset_mask;
uint64 nsector =
(L2_desc >> compress_offset_bits) & compress_sector_mask;
// 圧縮データを読み込む。
std::vector<uint8> comp(512 * (nsector + 1));
auto r = readpos(fd, comp.data(), comp.size(), off);
if (__predict_false(r < 0)) {
warn("%s: %s(COMPRESSED) readpos(@0x%" PRIx64 ") failed",
pathname.c_str(), __func__, off);
return false;
}
// 展開。
z_stream z;
memset(&z, 0, sizeof(z));
if (inflateInit2(&z, -12) != Z_OK) {
warnx("%s: %s inflateInit2 failed",
pathname.c_str(), __func__);
return false;
}
z.next_in = comp.data();
z.avail_in = comp.size();
z.next_out = data_cluster.data();
z.avail_out = data_cluster.size();
inflate(&z, Z_FINISH);
inflateEnd(&z);
cached_data_index = cl_idx;
memcpy(buf, &data_cluster[cl_off], cl_len);
} else {
// 未割当クラスタ。
memset(buf, 0, cl_len);
}
}
done:
size -= cl_len;
if (size == 0) {
break;
}
buf = (void *)((uint8 *)buf + cl_len);
cl_off = 0;
}
return true;
}
bool
DiskDriverQcow2::Write(const void *buf, off_t offset, size_t size)
{
putmsg(2, "%s offset=0x" Px64 ", size=%zu", __func__, V64(offset), size);
uint64 cl_idx = offset >> cluster_bits;
uint64 cl_off = offset & cluster_mask;
for (; size > 0; cl_idx++) {
uint64 L1_index = cl_idx >> L2_entries_bits;
uint64 L2_index = cl_idx & L2_entries_mask;
uint64 cl_len = std::min((uint64)size, cluster_size - cl_off);
uint64 L1_desc;
uint64 L2_desc;
if (__predict_false(L1_index >= L1_size)) {
putmsg(0, "%s: L1_index(%u) is out of L1_size(%u)",
__func__, (uint32)L1_index, L1_size);
return false;
}
// キャッシュされてるところに書き込みが来たら無効にするだけ。
if (cl_idx == cached_data_index) {
cached_data_index = -1;
}
L1_desc = L1_table[L1_index];
if (__predict_false((L1_desc & QCOW2::DESC_USED) == 0)) {
// L2 全体が未割当なので、L2 テーブルを作成。
// テーブルの初期値はゼロクリアでよい。
// そして、この流れですぐ使うので L2_table をここで書き換える。
std::fill(L2_table.begin(), L2_table.end(), 0);
off_t newoff = AllocCluster(L2_table.data());
if (__predict_false(newoff < 0)) {
// エラーメッセージは表示済み。
return false;
}
cached_L1_index = L1_index;
// L1 のエントリを更新。
uint64 newdesc = QCOW2::DESC_USED | (uint64)newoff;
L1_table[L1_index] = newdesc;
L1_desc = newdesc;
bool r = WriteEntry(L1_table_offset, L1_index, newdesc);
if (__predict_false(r == false)) {
// エラーメッセージは表示済み。
return false;
}
putmsg(1, " L1[0x%x] desc=" Px64 " (new L2)",
(uint32)L1_index, V64(newdesc));
}
// 必要なら L2_table を更新。
uint64 L2_offset = L1_desc & QCOW2::DESC_OFFSET_MASK;
if (__predict_false(cached_L1_index != L1_index)) {
if (ReadTable(L2_table, L2_offset) == false) {
cached_L1_index = (uint64)-1;
return false;
}
cached_L1_index = L1_index;
}
L2_desc = L2_table[L2_index];
putmsg(2, " L2[0x%x/%04x] desc=0x" Px64,
(uint32)L1_index, (uint32)L2_index, V64(L2_desc));
// if (bit63:USED) {
// if (bit0:ALLZERO)
// ゼロクラスタ;
// else
// 標準クラスタ;
// } else {
// if (bit62:COMPRESSED)
// 圧縮クラスタ;
// else
// 未割当クラスタ;
// }
if (__predict_true((L2_desc & QCOW2::DESC_USED))) {
if (__predict_false((L2_desc & QCOW2::DESC_ALLZERO))) {
// ゼロクラスタ。
if (is_mem_zero(buf, cl_len)) {
// 書き込みデータがゼロなら何もしない。
} else {
// 割り当てて書き出す。
goto alloc;
}
} else {
// 標準クラスタ。
uint64 off = (L2_desc & QCOW2::DESC_OFFSET_MASK) + cl_off;
auto r = writepos(fd, buf, cl_len, (off_t)off);
if (__predict_false(r < 0)) {
warn("%s: %s writepos(@0x%" PRIx64 ") failed",
pathname.c_str(), __func__, off);
return false;
}
}
} else {
if ((L2_desc & QCOW2::DESC_COMPRESSED)) {
// 圧縮クラスタ。
// 書き込みは発生しないはずなのでここには来ないはずだが
// もし来てしまったらディスク I/O エラーで帰る。
warnx("%s: Compressed cluster not supported [L1=%u L2=%u]",
pathname.c_str(), (uint32)L1_index, (uint32)L2_index);
return false;
} else {
// 未割当クラスタ。
if (version >= 3 && is_mem_zero(buf, cl_len)) {
// 書き込みデータがすべてゼロならゼロクラスタにしたい。
// version 2 にはゼロクラスタはない。
uint64 newdesc = QCOW2::DESC_USED | QCOW2::DESC_ALLZERO;
L2_table[L2_index] = newdesc;
if (WriteEntry(L2_offset, L2_index, newdesc) == false) {
// エラーメッセージは表示済み。
return false;
}
putmsg(1, " L2[0x%x/%04x] desc=0x" Px64 " (new zero)",
(uint32)L1_index, (uint32)L2_index, V64(newdesc));
} else {
alloc:
std::vector<uint8> tmp(cluster_size);
memcpy(tmp.data() + cl_off, buf, cl_len);
off_t newoff = AllocCluster(tmp.data());
if (__predict_false(newoff < 0)) {
// エラーメッセージは表示済み。
return false;
}
// L2 のエントリを更新。
uint64 newdesc = QCOW2::DESC_USED | (uint64)newoff;
L2_table[L2_index] = newdesc;
if (WriteEntry(L2_offset, L2_index, newdesc) == false) {
// エラーメッセージは表示済み。
return false;
}
putmsg(1, " L2[0x%x/%04x] desc=0x" Px64 " (new data)",
(uint32)L1_index, (uint32)L2_index, V64(newdesc));
}
}
}
size -= cl_len;
if (size == 0) {
break;
}
buf = (const void *)((const uint8 *)buf + cl_len);
cl_off = 0;
}
return true;
}
// 内部関数
// イメージ中に圧縮クラスタがあるか調べる。
// L2 ディスクリプタをすべて調べて、圧縮クラスタが一つでも見付かれば 1 を返す。
// 見付からなければ 0 を返す。
// エラーが起きれば warn() 系でエラーメッセージを表示して -1 を返す。
// L2_table キャッシュも更新する。
int
DiskDriverQcow2::HasCompressed()
{
// どうせ L2 テーブルを全部読むのなら、起動直後は大抵ディスクの
// 先頭をアクセスするのだから、L1_table[] を後ろから前に読んでいって、
// L1_table[0] のテーブルをそのままキャッシュしておく。
int L1_index = L1_table.size() - 1;
for (; L1_index >= 0; L1_index--) {
uint64 L1_desc = L1_table[L1_index];
if ((L1_desc & QCOW2::DESC_USED) == 0) {
continue;
}
// L2 テーブルを読み込む。
uint64 L2_offset = L1_desc & QCOW2::DESC_OFFSET_MASK;
if (ReadTable(L2_table, L2_offset) == false) {
return -1;
}
cached_L1_index = L1_index;
// 全数検査。
constexpr uint64 mask = QCOW2::DESC_USED | QCOW2::DESC_COMPRESSED;
for (uint L2_index = 0; L2_index < L2_entries; L2_index++) {
uint64 L2_desc = L2_table[L2_index];
if ((L2_desc & mask) == QCOW2::DESC_COMPRESSED) {
// 圧縮クラスタあり!
return 1;
}
}
}
return 0;
}
// ファイルの off の位置から uint64 を buf.size() 個読み出して返す。
// 失敗すれば warn() 系でエラーメッセージを表示してから false を返す。
bool
DiskDriverQcow2::ReadTable(std::vector<uint64>& buf, uint64 off)
{
size_t bytes = buf.size() * sizeof(buf[0]);
auto r = readpos(fd, buf.data(), bytes, (off_t)off);
if (__predict_false(r < 0)) {
warn("%s: %s readpos(@0x%" PRIx64 ") failed",
pathname.c_str(), __func__, off);
return false;
}
for (uint i = 0, end = buf.size(); i < end; i++) {
BE64TOH(buf[i]);
}
return true;
}
// 新しいクラスタを確保して buf を書き込む。
// buf は cluster_size [バイト] であること。
// 書き込めれば新しいクラスタの位置 (ファイル先頭からのオフセット) を返す。
// 失敗すれば -1 を返す。
off_t
DiskDriverQcow2::AllocCluster(const void *buf)
{
// refcount テーブルから空きクラスタを探す。
uint64 hcl_idx = AllocRefcount();
if ((int64)hcl_idx == -1) {
return -1;
}
uint64 newoff = hcl_idx << cluster_bits;
auto r = writepos(fd, buf, cluster_size, (off_t)newoff);
if (__predict_false(r < 0)) {
warn("%s: %s writepos(@0x%" PRIx64 ") failed",
pathname.c_str(), __func__, newoff);
return -1;
}
return (off_t)newoff;
}
// ファイルの baseoff[index] の位置に data を書き出す。
// 失敗すれば warn() 系でエラーメッセージを表示してから false を返す。
bool
DiskDriverQcow2::WriteEntry(uint64 baseoff, uint64 index, uint64 data)
{
uint64 offset = baseoff + index * sizeof(uint64);
uint64 be_data = htobe64(data);
auto r = writepos(fd, &be_data, sizeof(be_data), (off_t)offset);
if (__predict_false(r < 0)) {
warn("%s: %s writepos(@0x%" PRIx64 ") failed",
pathname.c_str(), __func__, offset);
return false;
}
return true;
}
// ディスクイメージ上の refcount テーブルを読み込んで、オンメモリ版を構成する。
bool
DiskDriverQcow2::ReadRefcount()
{
uint64 reftable_capacity =
refcount_table_clusters << (cluster_bits - 3/*sizeof(uint64)*/);
uint32 reftable_idx;
// refcount テーブル(1段目) を一旦全部読み込む。
std::vector<uint64> reftable(reftable_capacity);
auto r = readpos(fd, reftable.data(), reftable_capacity * sizeof(uint64),
(off_t)refcount_table_offset);
if (__predict_false(r < 0)) {
warn("%s: %s readpos(@0x%" PRIx64 ") failed",
pathname.c_str(), __func__, refcount_table_offset);
return false;
}
for (reftable_idx = 0; reftable_idx < reftable_capacity; reftable_idx++) {
// refcount ブロック (2段目) の位置を取得。
uint64 refblock_offset = reftable[reftable_idx];
// 通常あるとは思えないが、仕様上は穴空きは許されている。
// どうせほぼゼロなので先にゼロかどうか調べる。
if (refblock_offset == 0) {
continue;
}
refblock_offset = be64toh(refblock_offset);
// 書き込み時のために、このブロックのオフセットを覚えておく。
if (reftable_idx >= refcount_block_offset.size()) {
refcount_block_offset.resize(reftable_idx + 1);
}
refcount_block_offset[reftable_idx] = refblock_offset;
// refcount ブロック(2段目) を読み込む。
std::vector<uint16> refblock(refblock_capacity);
r = readpos(fd, refblock.data(), cluster_size, (off_t)refblock_offset);
if (__predict_false(r < 0)) {
warn("%s: %s readpos(@0x%" PRIx64 ") failed",
pathname.c_str(), __func__, refblock_offset);
return false;
}
// ブロック内の refcount がすべてゼロなら飛ばしておく。
if (is_mem_zero(refblock.data(), cluster_size)) {
continue;
}
// 少なくとも使用中の refcount があるのでここまでは拡張。
uint64 e = reftable_idx << refblock_bits;
freemap.Expand(e + refblock_capacity);
for (uint i = 0; i < refblock_capacity; i++) {
// イメージ上の値は BE なので 0 かどうかだけで比較する。
// イメージ上は refcount なので >0 が使用中だが
// メモリ上は freemap なので false が使用中。
if (refblock[i] != 0) {
freemap.ClearBit(e + i);
}
}
}
return true;
}
// 空いてるホストクラスタを探して、ホストクラスタ番号を返す。
// 失敗すれば warn() 系でエラーメッセージを表示してから (int64)-1 を返す。
uint64
DiskDriverQcow2::AllocRefcount()
{
uint64 reftable_capacity =
refcount_table_clusters << (cluster_bits - 3/*sizeof(uint64)*/);
uint64 hcl_idx = 0;
// refcount を元に空きクラスタを探す。
if (FindCluster(&hcl_idx) == false) {
return -1;
}
if ((int64)hcl_idx >= 0) {
// 見付かった。
return hcl_idx;
}
// 割り当て済みの refcount ブロックに空きがなかった。
// refcount テーブル(1段目)から空きを探す。
uint64 new_table_idx = 0;
for (uint64 end = refcount_block_offset.size();
new_table_idx < end; new_table_idx++)
{
if (refcount_block_offset[new_table_idx] == 0) {
break;
}
}
if (__predict_false(new_table_idx >= reftable_capacity)) {
// refcount テーブル(1段目)の拡張は未実装。
// ホストイメージサイズが 16TB (64KB/クラスタ時) までは起きない。
warnx("%s: No free refcount table! (hcl=%" PRIx64 ")",
pathname.c_str(), hcl_idx);
return -1;
}
// new_table_idx が新しく割り当て可能になる refcount ブロック番号。
// new_table_idx == 1 ならホストクラスタ 32768-65535 が未使用なので、
// この先頭のクラスタを refcount ブロック用にする。
uint64 new_block_idx = (new_table_idx << refblock_bits) + 0/*先頭*/;
uint64 new_block_offset = new_block_idx << cluster_bits;
// オンメモリのほうを更新。
freemap.Expand(new_block_idx + refblock_capacity);
freemap.ClearBit(new_block_idx);
// イメージのほうも更新。
std::vector<uint8> refblock(cluster_size);
*(uint16 *)&refblock[0] = htobe16(1);
auto n = writepos(fd, refblock.data(), refblock.size(),
(off_t)new_block_offset);
if (__predict_false(n < 0)) {
warn("%s: %s writepos(@0x%" PRIx64 ", %zu) failed",
pathname.c_str(), __func__, new_block_offset, refblock.size());
return -1;
}
// refcount テーブル(1段目)に今追加したブロック(2段目)をリンク。
// オンメモリのほうを更新。
if (new_table_idx >= refcount_block_offset.size()) {
refcount_block_offset.resize(new_table_idx + 1);
}
refcount_block_offset[new_table_idx] = new_block_offset;
// イメージも更新。
auto r = WriteEntry(refcount_table_offset, new_table_idx, new_block_offset);
if (__predict_false(r == false)) {
return -1;
}
// もう一度探す。
// refcount ブロックが増えたので、今度は見付かるはず。
if (FindCluster(&hcl_idx) == false) {
return -1;
}
if ((int64)hcl_idx < 0) {
warnx("%s: Refcount block was added but no free cluster found??",
pathname.c_str());
}
return hcl_idx;
}
// freemap から空きホストクラスタを探す。
// 見付かれば *hcl_idx_p にホストクラスタ番号を書き戻して true を返す。
// 見付からなければ *hcl_idx_p に -1 を書き戻して true を返す。
// 失敗すれば warn() 系でエラーメッセージを表示してから false を返す。
bool
DiskDriverQcow2::FindCluster(uint64 *hcl_idx_p)
{
uint64 hcl_idx = 0;
for (size_t i = 0, iend = freemap.DataSize(); i < iend; i++) {
uint64 data = freemap.GetData(i);
if (__predict_true(data == 0)) {
continue;
}
// 空きのある 64 ビットが見つかった。
uint32 b = __builtin_ctzll(data);
hcl_idx = i * 64 + b;
// ここを使用中にする。
freemap.ClearBit(hcl_idx);
// イメージにもこのエントリだけ書き込む。
uint64 reftable_idx = hcl_idx >> refblock_bits;
uint64 refblock_idx = hcl_idx & refblock_mask;
uint64 refblock_offset = refcount_block_offset[reftable_idx];
uint64 offset = refblock_offset + refblock_idx * sizeof(uint16);
uint16 refcount = htobe16(1);
auto r = writepos(fd, &refcount, sizeof(refcount), (off_t)offset);
if (__predict_false(r < 0)) {
warn("%s: %s refcount writepos(@0x%" PRIx64 ") failed",
pathname.c_str(), __func__, offset);
return false;
}
*hcl_idx_p = hcl_idx;
return true;
}
// 見付からなかった。
*hcl_idx_p = -1;
return true;
}
// src から len バイトがすべてゼロなら true を返す。
/*static*/ bool
DiskDriverQcow2::is_mem_zero(const void *src, size_t len)
{
const uint8 *s = (const uint8 *)src;
size_t n = (size_t)(uintptr_t)s & 7;
if (__predict_false(n != 0)) {
n = 8 - n;
len -= n;
do {
if (*s++ != 0) {
return false;
}
} while (--n != 0);
}
n = len / 8;
do {
if (*(const uint64 *)s != 0) {
return false;
}
s += 8;
} while (--n != 0);
n = len & 7;
if (__predict_false(n != 0)) {
do {
if (*s++ != 0) {
return false;
}
} while (--n != 0);
}
return true;
}
// crypt_method に対応する文字列を返す。知らない値なら NULL を返す。
/*static*/ const char *
DiskDriverQcow2::CryptMethodToStr(uint32 crypt_method_)
{
switch (crypt_method_) {
case QCOW2::CRYPT_NONE: return "None";
case QCOW2::CRYPT_AES: return "AES";
case QCOW2::CRYPT_LUKS: return "LUKS";
default:
break;
}
return NULL;
}
// compression_type に対応する文字列を返す。知らない値なら NULL を返す。
/*static*/ const char *
DiskDriverQcow2::CompressionTypeToStr(uint32 compression_type_)
{
switch (compression_type_) {
case QCOW2::COMPRESS_ZLIB: return "zlib";
case QCOW2::COMPRESS_ZSTD: return "zstd";
default:
break;
}
return NULL;
}
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.