A MoonBit Network Block Device client
moon update
moon check --target native --deny-warn
moon test --target native --deny-warnmoon build --target native cmd/nbdqemu-img create -f raw demo.raw 16M
moon run --target native cmd/nbd -- serve demo.raw 11091moon run --target native cmd/nbd -- info 127.0.0.1 11091
moon run --target native cmd/nbd -- export 127.0.0.1 11091 backup.raw
qemu-img compare demo.raw backup.raw# 终端一用 --write 启动服务端
moon run --target native cmd/nbd -- serve demo.raw 11091 --write
# 终端二导入一个与导出大小相同的本地镜像
moon run --target native cmd/nbd -- import 127.0.0.1 11091 replacement.raw
# 校验远端镜像和本地文件,数据不同会以失败状态退出
moon run --target native cmd/nbd -- verify 127.0.0.1 11091 replacement.rawmoon run --target native cmd/nbd -- serve demo.raw 11091 --write# 列出服务端导出
moon run --target native cmd/nbd -- list 127.0.0.1 10809
# 查询默认导出信息
moon run --target native cmd/nbd -- info 127.0.0.1 10809
# 查看整个镜像的分配状态(需要对端支持 base:allocation)
moon run --target native cmd/nbd -- allocation 127.0.0.1 10809 0 16777216
# 从偏移 0 读取 512 字节到新文件
moon run --target native cmd/nbd -- read 127.0.0.1 10809 0 512 sector.bin
# 完整导出到新文件
moon run --target native cmd/nbd -- export 127.0.0.1 10809 backup.raw
# 根据对端分配信息稀疏导出
moon run --target native cmd/nbd -- export-sparse 127.0.0.1 10809 sparse-backup.raw
# 将同尺寸本地镜像导入可写远端
moon run --target native cmd/nbd -- import 127.0.0.1 10809 image.raw
# 比较本地镜像和远端导出
moon run --target native cmd/nbd -- verify 127.0.0.1 10809 image.raw
# 以只读方式导出本地镜像
moon run --target native cmd/nbd -- serve demo.raw 11091let disk = @nbd.Client::connect("127.0.0.1", 10809, b"")
let data = disk.read_at(0UL, 512)
disk.disconnect()bash scripts/qemu-interop.sh
bash scripts/qemu-server-interop.sh
bash scripts/qemu-file-server-interop.sh
bash scripts/qemu-export-interop.sh
bash scripts/qemu-large-offset-interop.sh
bash scripts/qemu-sparse-export-interop.shpub suberror NbdError {
Protocol(String)
InvalidArgument(String)
Server(UInt)
OutcomeUnknown(String)
LimitExceeded(String)
}pub(all) struct AllocationExtent {
offset : UInt64
length : UInt
flags : UInt
}pub(all) struct AllocationSummary {
range_bytes : UInt64
hole_bytes : UInt64
zero_bytes : UInt64
other_bytes : UInt64
hole_extents : Int
zero_extents : Int
other_extents : Int
}pub(all) struct ByteRange {
offset : UInt64
length : UInt64
}pub(all) struct CacheStats {
hits : UInt64
misses : UInt64
evictions : UInt64
entries : Int
}pub struct CachedClient {
// private fields
}async fn CachedClient::connect(host : String, port : Int, export_name : Bytes, cache_bytes : Int, line_size? : Int, structured? : Bool, timeout_ms? : Int) -> CachedClientasync fn CachedClient::write_zeroes(self : CachedClient, offset : UInt64, length : Int, no_hole? : Bool) -> Unitpub(all) struct ChunkMismatch {
offset : UInt64
length : UInt
expected : UInt
actual : UInt
}pub struct Client {
size : UInt64
flags : UInt
min_block : UInt
max_block : UInt
structured : Bool
// private fields
}async fn Client::allocation_summary(self : Client, offset : UInt64, length : UInt64, max_extents? : Int) -> AllocationSummaryasync fn Client::block_status(self : Client, offset : UInt64, length : Int) -> Array[AllocationExtent]async fn Client::compare_file(self : Client, file : File, offset : UInt64, length : UInt64, max_ranges? : Int) -> FileCompareStatsasync fn Client::copy_ranges_to_file(self : Client, file : File, ranges : RangeSet) -> RangeCopyStatsasync fn Client::copy_sparse_to_file(self : Client, file : File, max_extents? : Int) -> SparseCopyStatsasync fn Client::create_integrity_manifest(self : Client, chunk_size : Int, max_chunks? : Int) -> IntegrityManifestasync fn Client::scan_allocation(self : Client, offset : UInt64, length : UInt64, max_extents? : Int) -> Array[AllocationExtent]async fn Client::verify_integrity_manifest(self : Client, manifest : IntegrityManifest, max_mismatches? : Int) -> Array[ChunkMismatch]pub(all) struct ClientConfig {
host : String
port : Int
export_name : Bytes
structured : Bool
allocation : Bool
timeout_ms : Int
}fn ClientConfig::new(host : String, port : Int, export_name : Bytes, structured? : Bool, allocation? : Bool, timeout_ms? : Int) -> ClientConfig raiseasync fn ClientConfig::read_at_retry(self : ClientConfig, offset : UInt64, length : Int, retries? : Int) -> Bytespub(all) struct Difference {
offset : UInt64
length : UInt64
}pub(all) struct ExportEntry {
name : Bytes
description : Bytes
}pub struct FileExport {
// private fields
}pub struct MemoryExport {
// private fields
}pub(all) struct RangeCopyStats {
ranges : Int
bytes : UInt64
}pub struct RangeSet {
// private fields
}pub(all) struct RemoteExportInfo {
size : UInt64
flags : UInt
}pub(all) struct SparseCopyStats {
logical_bytes : UInt64
data_bytes : UInt64
zero_bytes : UInt64
extents : Int
}async fn compare_ranges(left : Client, right : Client, offset : UInt64, length : UInt64, max_ranges? : Int) -> Array[Difference]async fn copy_differences_to_file(source : Client, baseline : Client, file : File, offset : UInt64, length : UInt64, max_ranges? : Int) -> RangeCopyStatsasync fn inspect_export(host : String, port : Int, name : Bytes, timeout_ms? : Int) -> RemoteExportInfo