mirror of
https://github.com/dennwc/btrfs
synced 2025-03-11 06:48:27 +00:00
implement subvolumes list; regenerate headers; generate btrfs_tree.h
This commit is contained in:
parent
b300237e77
commit
f03fa748e7
@ -2,6 +2,8 @@ package btrfs
|
||||
|
||||
import "strings"
|
||||
|
||||
const maxUint64 = 1<<64 - 1
|
||||
|
||||
const BTRFS_LABEL_SIZE = 256
|
||||
|
||||
type FeatureFlags uint64
|
||||
|
54
btrfs_tree.go
Normal file
54
btrfs_tree.go
Normal file
@ -0,0 +1,54 @@
|
||||
package btrfs
|
||||
|
||||
import (
|
||||
"time"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
_BTRFS_BLOCK_GROUP_PROFILE_MASK = (blockGroupRaid0 |
|
||||
blockGroupRaid1 |
|
||||
blockGroupRaid5 |
|
||||
blockGroupRaid6 |
|
||||
blockGroupDup |
|
||||
blockGroupRaid10)
|
||||
)
|
||||
|
||||
type rootRef struct {
|
||||
DirID uint64
|
||||
Sequence uint64
|
||||
Name string
|
||||
}
|
||||
|
||||
func (rootRef) btrfsSize() int { return 18 }
|
||||
|
||||
func asUint64(p []byte) uint64 {
|
||||
return *(*uint64)(unsafe.Pointer(&p[0]))
|
||||
}
|
||||
|
||||
func asUint32(p []byte) uint32 {
|
||||
return *(*uint32)(unsafe.Pointer(&p[0]))
|
||||
}
|
||||
|
||||
func asUint16(p []byte) uint16 {
|
||||
return *(*uint16)(unsafe.Pointer(&p[0]))
|
||||
}
|
||||
|
||||
func asTime(p []byte) time.Time {
|
||||
sec, nsec := asUint64(p[0:]), asUint32(p[8:])
|
||||
return time.Unix(int64(sec), int64(nsec))
|
||||
}
|
||||
|
||||
func asRootRef(p []byte) rootRef {
|
||||
const sz = 18
|
||||
// assuming that it is highly unsafe to have sizeof(struct) > len(data)
|
||||
// (*btrfs_root_ref)(unsafe.Pointer(&p[0])) and sizeof(btrfs_root_ref) == 24
|
||||
ref := rootRef{
|
||||
DirID: asUint64(p[0:]),
|
||||
Sequence: asUint64(p[8:]),
|
||||
}
|
||||
if n := asUint16(p[16:]); n > 0 {
|
||||
ref.Name = string(p[sz : sz+n : sz+n])
|
||||
}
|
||||
return ref
|
||||
}
|
745
btrfs_tree_h.go
Normal file
745
btrfs_tree_h.go
Normal file
@ -0,0 +1,745 @@
|
||||
package btrfs
|
||||
|
||||
/*
|
||||
* This header contains the structure definitions and constants used
|
||||
* by file system objects that can be retrieved using
|
||||
* the _BTRFS_IOC_SEARCH_TREE ioctl. That means basically anything that
|
||||
* is needed to describe a leaf node's key or item contents.
|
||||
*/
|
||||
|
||||
/* holds pointers to all of the tree roots */
|
||||
|
||||
/* stores information about which extents are in use, and reference counts */
|
||||
|
||||
/*
|
||||
* chunk tree stores translations from logical -> physical block numbering
|
||||
* the super block points to the chunk tree
|
||||
*/
|
||||
|
||||
/*
|
||||
* stores information about which areas of a given device are in use.
|
||||
* one per device. The tree of tree roots points to the device tree
|
||||
*/
|
||||
|
||||
/* one per subvolume, storing files and directories */
|
||||
|
||||
/* directory objectid inside the root tree */
|
||||
|
||||
/* holds checksums of all the data extents */
|
||||
|
||||
/* holds quota configuration and tracking */
|
||||
|
||||
/* for storing items that use the _BTRFS_UUID_KEY* types */
|
||||
|
||||
/* tracks free space in block groups. */
|
||||
|
||||
/* device stats in the device tree */
|
||||
|
||||
/* for storing balance parameters in the root tree */
|
||||
|
||||
/* orhpan objectid for tracking unlinked/truncated files */
|
||||
|
||||
/* does write ahead logging to speed up fsyncs */
|
||||
|
||||
/* for space balancing */
|
||||
|
||||
/*
|
||||
* extent checksums all have this objectid
|
||||
* this allows them to share the logging tree
|
||||
* for fsyncs
|
||||
*/
|
||||
|
||||
/* For storing free space cache */
|
||||
|
||||
/*
|
||||
* The inode number assigned to the special inode for storing
|
||||
* free ino cache
|
||||
*/
|
||||
|
||||
/* dummy objectid represents multiple objectids */
|
||||
|
||||
/*
|
||||
* All files have objectids in this range.
|
||||
*/
|
||||
|
||||
/*
|
||||
* the device items go into the chunk tree. The key is in the form
|
||||
* [ 1 _BTRFS_DEV_ITEM_KEY device_id ]
|
||||
*/
|
||||
|
||||
/*
|
||||
* inode items have the data typically returned from stat and store other
|
||||
* info about object characteristics. There is one for every file and dir in
|
||||
* the FS
|
||||
*/
|
||||
|
||||
/* reserve 2-15 close to the inode for later flexibility */
|
||||
|
||||
/*
|
||||
* dir items are the name -> inode pointers in a directory. There is one
|
||||
* for every name in a directory.
|
||||
*/
|
||||
|
||||
/*
|
||||
* extent data is for file data
|
||||
*/
|
||||
|
||||
/*
|
||||
* extent csums are stored in a separate tree and hold csums for
|
||||
* an entire extent on disk.
|
||||
*/
|
||||
|
||||
/*
|
||||
* root items point to tree roots. They are typically in the root
|
||||
* tree used by the super block to find all the other trees
|
||||
*/
|
||||
|
||||
/*
|
||||
* root backrefs tie subvols and snapshots to the directory entries that
|
||||
* reference them
|
||||
*/
|
||||
|
||||
/*
|
||||
* root refs make a fast index for listing all of the snapshots and
|
||||
* subvolumes referenced by a given root. They point directly to the
|
||||
* directory item in the root that references the subvol
|
||||
*/
|
||||
|
||||
/*
|
||||
* extent items are in the extent map tree. These record which blocks
|
||||
* are used, and how many references there are to each block
|
||||
*/
|
||||
|
||||
/*
|
||||
* The same as the _BTRFS_EXTENT_ITEM_KEY, except it's metadata we already know
|
||||
* the length, so we save the level in key->offset instead of the length.
|
||||
*/
|
||||
|
||||
/*
|
||||
* block groups give us hints into the extent allocation trees. Which
|
||||
* blocks are free etc etc
|
||||
*/
|
||||
|
||||
/*
|
||||
* Every block group is represented in the free space tree by a free space info
|
||||
* item, which stores some accounting information. It is keyed on
|
||||
* (block_group_start, FREE_SPACE_INFO, block_group_length).
|
||||
*/
|
||||
|
||||
/*
|
||||
* A free space extent tracks an extent of space that is free in a block group.
|
||||
* It is keyed on (start, FREE_SPACE_EXTENT, length).
|
||||
*/
|
||||
|
||||
/*
|
||||
* When a block group becomes very fragmented, we convert it to use bitmaps
|
||||
* instead of extents. A free space bitmap is keyed on
|
||||
* (start, FREE_SPACE_BITMAP, length); the corresponding item is a bitmap with
|
||||
* (length / sectorsize) bits.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Records the overall state of the qgroups.
|
||||
* There's only one instance of this key present,
|
||||
* (0, _BTRFS_QGROUP_STATUS_KEY, 0)
|
||||
*/
|
||||
|
||||
/*
|
||||
* Records the currently used space of the qgroup.
|
||||
* One key per qgroup, (0, _BTRFS_QGROUP_INFO_KEY, qgroupid).
|
||||
*/
|
||||
|
||||
/*
|
||||
* Contains the user configured limits for the qgroup.
|
||||
* One key per qgroup, (0, _BTRFS_QGROUP_LIMIT_KEY, qgroupid).
|
||||
*/
|
||||
|
||||
/*
|
||||
* Records the child-parent relationship of qgroups. For
|
||||
* each relation, 2 keys are present:
|
||||
* (childid, _BTRFS_QGROUP_RELATION_KEY, parentid)
|
||||
* (parentid, _BTRFS_QGROUP_RELATION_KEY, childid)
|
||||
*/
|
||||
|
||||
/*
|
||||
* Obsolete name, see _BTRFS_TEMPORARY_ITEM_KEY.
|
||||
*/
|
||||
|
||||
/*
|
||||
* The key type for tree items that are stored persistently, but do not need to
|
||||
* exist for extended period of time. The items can exist in any tree.
|
||||
*
|
||||
* [subtype, _BTRFS_TEMPORARY_ITEM_KEY, data]
|
||||
*
|
||||
* Existing items:
|
||||
*
|
||||
* - balance status item
|
||||
* (_BTRFS_BALANCE_OBJECTID, _BTRFS_TEMPORARY_ITEM_KEY, 0)
|
||||
*/
|
||||
|
||||
/*
|
||||
* Obsolete name, see _BTRFS_PERSISTENT_ITEM_KEY
|
||||
*/
|
||||
|
||||
/*
|
||||
* The key type for tree items that are stored persistently and usually exist
|
||||
* for a long period, eg. filesystem lifetime. The item kinds can be status
|
||||
* information, stats or preference values. The item can exist in any tree.
|
||||
*
|
||||
* [subtype, _BTRFS_PERSISTENT_ITEM_KEY, data]
|
||||
*
|
||||
* Existing items:
|
||||
*
|
||||
* - device statistics, store IO stats in the device tree, one key for all
|
||||
* stats
|
||||
* (_BTRFS_DEV_STATS_OBJECTID, _BTRFS_DEV_STATS_KEY, 0)
|
||||
*/
|
||||
|
||||
/*
|
||||
* Persistantly stores the device replace state in the device tree.
|
||||
* The key is built like this: (0, _BTRFS_DEV_REPLACE_KEY, 0).
|
||||
*/
|
||||
|
||||
/*
|
||||
* Stores items that allow to quickly map UUIDs to something else.
|
||||
* These items are part of the filesystem UUID tree.
|
||||
* The key is built like this:
|
||||
* (UUID_upper_64_bits, _BTRFS_UUID_KEY*, UUID_lower_64_bits).
|
||||
*/
|
||||
|
||||
/* for UUIDs assigned to * received subvols */
|
||||
|
||||
/*
|
||||
* string items are for debugging. They just store a short string of
|
||||
* data in the FS
|
||||
*/
|
||||
|
||||
/* 32 bytes in various csum fields */
|
||||
|
||||
/* csum types */
|
||||
|
||||
/*
|
||||
* flags definitions for directory entry item type
|
||||
*
|
||||
* Used by:
|
||||
* struct btrfs_dir_item.type
|
||||
*/
|
||||
|
||||
/*
|
||||
* The key defines the order in the tree, and so it also defines (optimal)
|
||||
* block layout.
|
||||
*
|
||||
* objectid corresponds to the inode number.
|
||||
*
|
||||
* type tells us things about the object, and is a kind of stream selector.
|
||||
* so for a given inode, keys with type of 1 might refer to the inode data,
|
||||
* type of 2 may point to file data in the btree and type == 3 may point to
|
||||
* extents.
|
||||
*
|
||||
* offset is the starting byte offset for this key in the stream.
|
||||
*
|
||||
* btrfs_disk_key is in disk byte order. struct btrfs_key is always
|
||||
* in cpu native order. Otherwise they are identical and their sizes
|
||||
* should be the same (ie both packed)
|
||||
*/
|
||||
type btrfs_disk_key struct {
|
||||
objectid uint64
|
||||
type_ uint8
|
||||
offset uint64
|
||||
}
|
||||
|
||||
type btrfs_key struct {
|
||||
objectid uint64
|
||||
type_ uint8
|
||||
offset uint64
|
||||
}
|
||||
|
||||
type btrfs_dev_item struct {
|
||||
devid uint64
|
||||
total_bytes uint64
|
||||
bytes_used uint64
|
||||
io_align uint32
|
||||
io_width uint32
|
||||
sector_size uint32
|
||||
type_ uint64
|
||||
generation uint64
|
||||
start_offset uint64
|
||||
dev_group uint32
|
||||
seek_speed uint8
|
||||
bandwidth uint8
|
||||
uuid UUID
|
||||
fsid FSID
|
||||
}
|
||||
|
||||
type btrfs_stripe struct {
|
||||
devid uint64
|
||||
offset uint64
|
||||
dev_uuid UUID
|
||||
}
|
||||
|
||||
type btrfs_chunk struct {
|
||||
length uint64
|
||||
owner uint64
|
||||
stripe_len uint64
|
||||
type_ uint64
|
||||
io_align uint32
|
||||
io_width uint32
|
||||
sector_size uint32
|
||||
num_stripes uint16
|
||||
sub_stripes uint16
|
||||
stripe struct {
|
||||
devid uint64
|
||||
offset uint64
|
||||
dev_uuid UUID
|
||||
}
|
||||
}
|
||||
|
||||
/* additional stripes go here */
|
||||
type btrfs_free_space_entry struct {
|
||||
offset uint64
|
||||
bytes uint64
|
||||
type_ uint8
|
||||
}
|
||||
|
||||
type btrfs_free_space_header struct {
|
||||
location struct {
|
||||
objectid uint64
|
||||
type_ uint8
|
||||
offset uint64
|
||||
}
|
||||
generation uint64
|
||||
num_entries uint64
|
||||
num_bitmaps uint64
|
||||
}
|
||||
|
||||
/* Super block flags */
|
||||
/* Errors detected */
|
||||
|
||||
/*
|
||||
* items in the extent btree are used to record the objectid of the
|
||||
* owner of the block and the number of references
|
||||
*/
|
||||
type btrfs_extent_item struct {
|
||||
refs uint64
|
||||
generation uint64
|
||||
flags uint64
|
||||
}
|
||||
|
||||
type btrfs_extent_item_v0 struct {
|
||||
refs uint32
|
||||
}
|
||||
|
||||
/* following flags only apply to tree blocks */
|
||||
|
||||
/* use full backrefs for extent pointers in the block */
|
||||
|
||||
/*
|
||||
* this flag is only used internally by scrub and may be changed at any time
|
||||
* it is only declared here to avoid collisions
|
||||
*/
|
||||
type btrfs_tree_block_info struct {
|
||||
key struct {
|
||||
objectid uint64
|
||||
type_ uint8
|
||||
offset uint64
|
||||
}
|
||||
level uint8
|
||||
}
|
||||
|
||||
type btrfs_extent_data_ref struct {
|
||||
root uint64
|
||||
objectid uint64
|
||||
offset uint64
|
||||
count uint32
|
||||
}
|
||||
|
||||
type btrfs_shared_data_ref struct {
|
||||
count uint32
|
||||
}
|
||||
|
||||
type btrfs_extent_inline_ref struct {
|
||||
type_ uint8
|
||||
offset uint64
|
||||
}
|
||||
|
||||
/* old style backrefs item */
|
||||
type btrfs_extent_ref_v0 struct {
|
||||
root uint64
|
||||
generation uint64
|
||||
objectid uint64
|
||||
count uint32
|
||||
}
|
||||
|
||||
/* dev extents record free space on individual devices. The owner
|
||||
* field points back to the chunk allocation mapping tree that allocated
|
||||
* the extent. The chunk tree uuid field is a way to double check the owner
|
||||
*/
|
||||
type btrfs_dev_extent struct {
|
||||
chunk_tree uint64
|
||||
chunk_objectid uint64
|
||||
chunk_offset uint64
|
||||
length uint64
|
||||
chunk_tree_uuid UUID
|
||||
}
|
||||
|
||||
type btrfs_inode_ref struct {
|
||||
index uint64
|
||||
name_len uint16
|
||||
}
|
||||
|
||||
/* name goes here */
|
||||
type btrfs_inode_extref struct {
|
||||
parent_objectid uint64
|
||||
index uint64
|
||||
name_len uint16
|
||||
//name [0]uint8
|
||||
}
|
||||
|
||||
/* name goes here */
|
||||
type btrfs_timespec struct {
|
||||
sec uint64
|
||||
nsec uint32
|
||||
}
|
||||
|
||||
type btrfs_inode_item struct {
|
||||
generation uint64
|
||||
transid uint64
|
||||
size uint64
|
||||
nbytes uint64
|
||||
block_group uint64
|
||||
nlink uint32
|
||||
uid uint32
|
||||
gid uint32
|
||||
mode uint32
|
||||
rdev uint64
|
||||
flags uint64
|
||||
sequence uint64
|
||||
reserved [4]uint64
|
||||
atime struct {
|
||||
sec uint64
|
||||
nsec uint32
|
||||
}
|
||||
ctime struct {
|
||||
sec uint64
|
||||
nsec uint32
|
||||
}
|
||||
mtime struct {
|
||||
sec uint64
|
||||
nsec uint32
|
||||
}
|
||||
otime struct {
|
||||
sec uint64
|
||||
nsec uint32
|
||||
}
|
||||
}
|
||||
|
||||
type btrfs_dir_log_item struct {
|
||||
end uint64
|
||||
}
|
||||
|
||||
type btrfs_dir_item struct {
|
||||
location struct {
|
||||
objectid uint64
|
||||
type_ uint8
|
||||
offset uint64
|
||||
}
|
||||
transid uint64
|
||||
data_len uint16
|
||||
name_len uint16
|
||||
type_ uint8
|
||||
}
|
||||
|
||||
/*
|
||||
* Internal in-memory flag that a subvolume has been marked for deletion but
|
||||
* still visible as a directory
|
||||
*/
|
||||
type btrfs_root_item struct {
|
||||
inode struct {
|
||||
generation uint64
|
||||
transid uint64
|
||||
size uint64
|
||||
nbytes uint64
|
||||
block_group uint64
|
||||
nlink uint32
|
||||
uid uint32
|
||||
gid uint32
|
||||
mode uint32
|
||||
rdev uint64
|
||||
flags uint64
|
||||
sequence uint64
|
||||
reserved [4]uint64
|
||||
atime struct {
|
||||
sec uint64
|
||||
nsec uint32
|
||||
}
|
||||
ctime struct {
|
||||
sec uint64
|
||||
nsec uint32
|
||||
}
|
||||
mtime struct {
|
||||
sec uint64
|
||||
nsec uint32
|
||||
}
|
||||
otime struct {
|
||||
sec uint64
|
||||
nsec uint32
|
||||
}
|
||||
}
|
||||
generation uint64
|
||||
root_dirid uint64
|
||||
bytenr uint64
|
||||
byte_limit uint64
|
||||
bytes_used uint64
|
||||
last_snapshot uint64
|
||||
flags uint64
|
||||
refs uint32
|
||||
drop_progress struct {
|
||||
objectid uint64
|
||||
type_ uint8
|
||||
offset uint64
|
||||
}
|
||||
drop_level uint8
|
||||
level uint8
|
||||
generation_v2 uint64
|
||||
uuid UUID
|
||||
parent_uuid UUID
|
||||
received_uuid UUID
|
||||
ctransid uint64
|
||||
otransid uint64
|
||||
stransid uint64
|
||||
rtransid uint64
|
||||
ctime struct {
|
||||
sec uint64
|
||||
nsec uint32
|
||||
}
|
||||
otime struct {
|
||||
sec uint64
|
||||
nsec uint32
|
||||
}
|
||||
stime struct {
|
||||
sec uint64
|
||||
nsec uint32
|
||||
}
|
||||
rtime struct {
|
||||
sec uint64
|
||||
nsec uint32
|
||||
}
|
||||
reserved [8]uint64
|
||||
}
|
||||
|
||||
/*
|
||||
* this is used for both forward and backward root refs
|
||||
*/
|
||||
type btrfs_root_ref struct {
|
||||
dirid uint64
|
||||
sequence uint64
|
||||
name_len uint16
|
||||
}
|
||||
|
||||
type btrfs_disk_balance_args struct {
|
||||
profiles uint64
|
||||
usage uint64
|
||||
usage_min uint32
|
||||
usage_max uint32
|
||||
devid uint64
|
||||
pstart uint64
|
||||
pend uint64
|
||||
vstart uint64
|
||||
vend uint64
|
||||
target uint64
|
||||
flags uint64
|
||||
limit uint64
|
||||
limit_min uint32
|
||||
limit_max uint32
|
||||
stripes_min uint32
|
||||
stripes_max uint32
|
||||
unused [6]uint64
|
||||
}
|
||||
|
||||
/*
|
||||
* store balance parameters to disk so that balance can be properly
|
||||
* resumed after crash or unmount
|
||||
*/
|
||||
type btrfs_balance_item struct {
|
||||
flags uint64
|
||||
data struct {
|
||||
profiles uint64
|
||||
usage uint64
|
||||
usage_min uint32
|
||||
usage_max uint32
|
||||
devid uint64
|
||||
pstart uint64
|
||||
pend uint64
|
||||
vstart uint64
|
||||
vend uint64
|
||||
target uint64
|
||||
flags uint64
|
||||
limit uint64
|
||||
limit_min uint32
|
||||
limit_max uint32
|
||||
stripes_min uint32
|
||||
stripes_max uint32
|
||||
unused [6]uint64
|
||||
}
|
||||
meta struct {
|
||||
profiles uint64
|
||||
usage uint64
|
||||
usage_min uint32
|
||||
usage_max uint32
|
||||
devid uint64
|
||||
pstart uint64
|
||||
pend uint64
|
||||
vstart uint64
|
||||
vend uint64
|
||||
target uint64
|
||||
flags uint64
|
||||
limit uint64
|
||||
limit_min uint32
|
||||
limit_max uint32
|
||||
stripes_min uint32
|
||||
stripes_max uint32
|
||||
unused [6]uint64
|
||||
}
|
||||
sys struct {
|
||||
profiles uint64
|
||||
usage uint64
|
||||
usage_min uint32
|
||||
usage_max uint32
|
||||
devid uint64
|
||||
pstart uint64
|
||||
pend uint64
|
||||
vstart uint64
|
||||
vend uint64
|
||||
target uint64
|
||||
flags uint64
|
||||
limit uint64
|
||||
limit_min uint32
|
||||
limit_max uint32
|
||||
stripes_min uint32
|
||||
stripes_max uint32
|
||||
unused [6]uint64
|
||||
}
|
||||
unused [4]uint64
|
||||
}
|
||||
|
||||
type btrfs_file_extent_item struct {
|
||||
generation uint64
|
||||
ram_bytes uint64
|
||||
compression uint8
|
||||
encryption uint8
|
||||
other_encoding uint16
|
||||
type_ uint8
|
||||
disk_bytenr uint64
|
||||
disk_num_bytes uint64
|
||||
offset uint64
|
||||
num_bytes uint64
|
||||
}
|
||||
|
||||
type btrfs_csum_item struct {
|
||||
csum uint8
|
||||
}
|
||||
|
||||
type btrfs_dev_stats_item struct {
|
||||
values [_BTRFS_DEV_STAT_VALUES_MAX]uint64
|
||||
}
|
||||
|
||||
type btrfs_dev_replace_item struct {
|
||||
src_devid uint64
|
||||
cursor_left uint64
|
||||
cursor_right uint64
|
||||
cont_reading_from_srcdev_mode uint64
|
||||
replace_state uint64
|
||||
time_started uint64
|
||||
time_stopped uint64
|
||||
num_write_errors uint64
|
||||
num_uncorrectable_read_errors uint64
|
||||
}
|
||||
|
||||
/* different types of block groups (and chunks) */
|
||||
const (
|
||||
_BTRFS_RAID_RAID10 = iota
|
||||
_BTRFS_RAID_RAID1
|
||||
_BTRFS_RAID_DUP
|
||||
_BTRFS_RAID_RAID0
|
||||
_BTRFS_RAID_SINGLE
|
||||
_BTRFS_RAID_RAID5
|
||||
_BTRFS_RAID_RAID6
|
||||
_BTRFS_NR_RAID_TYPES
|
||||
)
|
||||
|
||||
/*
|
||||
* We need a bit for restriper to be able to tell when chunks of type
|
||||
* SINGLE are available. This "extended" profile format is used in
|
||||
* fs_info->avail_*_alloc_bits (in-memory) and balance item fields
|
||||
* (on-disk). The corresponding on-disk bit in chunk.type is reserved
|
||||
* to avoid remappings between two formats in future.
|
||||
*/
|
||||
|
||||
/*
|
||||
* A fake block group type that is used to communicate global block reserve
|
||||
* size to userspace via the SPACE_INFO ioctl.
|
||||
*/
|
||||
func chunk_to_extended(flags uint64) uint64 {
|
||||
if flags&uint64(_BTRFS_BLOCK_GROUP_PROFILE_MASK) == 0 {
|
||||
flags |= uint64(availAllocBitSingle)
|
||||
}
|
||||
|
||||
return flags
|
||||
}
|
||||
|
||||
func extended_to_chunk(flags uint64) uint64 {
|
||||
return flags &^ uint64(availAllocBitSingle)
|
||||
}
|
||||
|
||||
type btrfs_block_group_item struct {
|
||||
used uint64
|
||||
chunk_objectid uint64
|
||||
flags uint64
|
||||
}
|
||||
|
||||
type btrfs_free_space_info struct {
|
||||
extent_count uint32
|
||||
flags uint32
|
||||
}
|
||||
|
||||
func btrfs_qgroup_level(qgroupid uint64) uint64 {
|
||||
return qgroupid >> uint32(qgroupLevelShift)
|
||||
}
|
||||
|
||||
/*
|
||||
* is subvolume quota turned on?
|
||||
*/
|
||||
|
||||
/*
|
||||
* RESCAN is set during the initialization phase
|
||||
*/
|
||||
|
||||
/*
|
||||
* Some qgroup entries are known to be out of date,
|
||||
* either because the configuration has changed in a way that
|
||||
* makes a rescan necessary, or because the fs has been mounted
|
||||
* with a non-qgroup-aware version.
|
||||
* Turning qouta off and on again makes it inconsistent, too.
|
||||
*/
|
||||
type btrfs_qgroup_status_item struct {
|
||||
version uint64
|
||||
generation uint64
|
||||
flags uint64
|
||||
rescan uint64
|
||||
}
|
||||
|
||||
type btrfs_qgroup_info_item struct {
|
||||
generation uint64
|
||||
rfer uint64
|
||||
rfer_cmpr uint64
|
||||
excl uint64
|
||||
excl_cmpr uint64
|
||||
}
|
||||
|
||||
type btrfs_qgroup_limit_item struct {
|
||||
flags uint64
|
||||
max_rfer uint64
|
||||
max_excl uint64
|
||||
rsv_rfer uint64
|
||||
rsv_excl uint64
|
||||
}
|
@ -44,37 +44,37 @@ const (
|
||||
devStatsObjectid = 0
|
||||
|
||||
// For storing balance parameters in the root tree
|
||||
balanceObjectid = 0xfffffffffffffffc /* -4 */
|
||||
balanceObjectid = (1<<64 - 4)
|
||||
|
||||
// Orhpan objectid for tracking unlinked/truncated files
|
||||
orphanObjectid = 0xfffffffffffffffb /* -5 */
|
||||
orphanObjectid = (1<<64 - 5)
|
||||
|
||||
// Does write ahead logging to speed up fsyncs
|
||||
treeLogObjectid = 0xfffffffffffffffa /* -6 */
|
||||
treeLogFixupObjectid = 0xfffffffffffffff9 /* -7 */
|
||||
treeLogObjectid = (1<<64 - 6)
|
||||
treeLogFixupObjectid = (1<<64 - 7)
|
||||
|
||||
// For space balancing
|
||||
treeRelocObjectid = 0xfffffffffffffff8 /* -8 */
|
||||
dataRelocTreeObjectid = 0xfffffffffffffff7 /* -9 */
|
||||
treeRelocObjectid = (1<<64 - 8)
|
||||
dataRelocTreeObjectid = (1<<64 - 9)
|
||||
|
||||
// Extent checksums all have this objectid
|
||||
// this allows them to share the logging tree
|
||||
// for fsyncs
|
||||
extentCsumObjectid = 0xfffffffffffffff6 /* -10 */
|
||||
extentCsumObjectid = (1<<64 - 10)
|
||||
|
||||
// For storing free space cache
|
||||
freeSpaceObjectid = 0xfffffffffffffff5 /* -11 */
|
||||
freeSpaceObjectid = (1<<64 - 11)
|
||||
|
||||
// The inode number assigned to the special inode for storing
|
||||
// free ino cache
|
||||
freeInoObjectid = 0xfffffffffffffff4 /* -12 */
|
||||
freeInoObjectid = (1<<64 - 12)
|
||||
|
||||
// Dummy objectid represents multiple objectids
|
||||
multipleObjectids = 0xffffffffffffff01 /* -255 */
|
||||
multipleObjectids = (1<<64 - 255)
|
||||
|
||||
// All files have objectids in this range.
|
||||
firstFreeObjectid = 256
|
||||
lastFreeObjectid = 0xffffffffffffff00 /* -256 */
|
||||
lastFreeObjectid = (1<<64 - 256)
|
||||
firstChunkTreeObjectid = 256
|
||||
|
||||
// The device items go into the chunk tree. The key is in the form
|
||||
@ -135,6 +135,8 @@ const (
|
||||
|
||||
extentDataRefKey = 178
|
||||
|
||||
extentRefV0Key = 180
|
||||
|
||||
sharedBlockRefKey = 182
|
||||
|
||||
sharedDataRefKey = 184
|
||||
@ -221,6 +223,7 @@ const (
|
||||
csumSize = 32
|
||||
|
||||
// Csum types
|
||||
csumTypeCrc32 = 0
|
||||
|
||||
// Flags definitions for directory entry item type
|
||||
// Used by:
|
||||
@ -437,7 +440,12 @@ const (
|
||||
blockGroupData = (1 << 0)
|
||||
blockGroupSystem = (1 << 1)
|
||||
blockGroupMetadata = (1 << 2)
|
||||
blockGroupRaid0 = (1 << 3)
|
||||
blockGroupRaid1 = (1 << 4)
|
||||
blockGroupDup = (1 << 5)
|
||||
blockGroupRaid10 = (1 << 6)
|
||||
blockGroupRaid5 = (1 << 7)
|
||||
blockGroupRaid6 = (1 << 8)
|
||||
|
||||
// We need a bit for restriper to be able to tell when chunks of type
|
||||
// SINGLE are available. This "extended" profile format is used in
|
||||
|
@ -17,6 +17,7 @@ func init() {
|
||||
SubvolumeCmd.AddCommand(
|
||||
SubvolumeCreateCmd,
|
||||
SubvolumeDeleteCmd,
|
||||
SubvolumeListCmd,
|
||||
)
|
||||
}
|
||||
|
||||
@ -62,6 +63,23 @@ operation is safely stored on the media.`,
|
||||
},
|
||||
}
|
||||
|
||||
var SubvolumeListCmd = &cobra.Command{
|
||||
Use: "list <mount>",
|
||||
Short: "List subvolumes",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) != 1 {
|
||||
return fmt.Errorf("expected one destination argument")
|
||||
}
|
||||
list, err := btrfs.ListSubVolumes(args[0])
|
||||
if err == nil {
|
||||
for _, v := range list {
|
||||
fmt.Printf("%+v\n", v)
|
||||
}
|
||||
}
|
||||
return err
|
||||
},
|
||||
}
|
||||
|
||||
var SendCmd = &cobra.Command{
|
||||
Use: "send [-ve] [-p <parent>] [-c <clone-src>] [-f <outfile>] <subvol> [<subvol>...]",
|
||||
Short: "Send the subvolume(s) to stdout.",
|
||||
|
@ -9,7 +9,6 @@ import (
|
||||
"log"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
@ -23,7 +22,7 @@ var (
|
||||
)
|
||||
|
||||
var (
|
||||
reDefineIntConst = regexp.MustCompile(`#define\s+([A-Za-z_]+)\s+(\(?-?\d+(?:U?LL)?(?:\s*<<\s*\d+)?\)?)`)
|
||||
reDefineIntConst = regexp.MustCompile(`#define\s+([A-Za-z_][A-Za-z\d_]*)\s+(\(?-?\d+(?:U?LL)?(?:\s*<<\s*\d+)?\)?)`)
|
||||
reNegULL = regexp.MustCompile(`-(\d+)ULL`)
|
||||
)
|
||||
|
||||
@ -126,11 +125,7 @@ func process(w io.Writer, path string) error {
|
||||
name, val := sub[1], sub[2]
|
||||
if sub := reNegULL.FindAllStringSubmatch(val, -1); len(sub) > 0 {
|
||||
for _, s := range sub {
|
||||
v, err := strconv.ParseInt(s[1], 10, 64)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
val = strings.Replace(val, s[0], fmt.Sprintf("0x%x /* -%s */", uint64(-v), s[1]), -1)
|
||||
val = strings.Replace(val, s[0], fmt.Sprintf("(1<<64 - %s)", s[1]), -1)
|
||||
}
|
||||
}
|
||||
val = strings.Replace(val, "ULL", "", -1)
|
||||
|
74
ioctl_h.go
74
ioctl_h.go
@ -19,9 +19,31 @@ const (
|
||||
UUIDSize = 16
|
||||
)
|
||||
|
||||
var zeroUUID UUID
|
||||
|
||||
type UUID [UUIDSize]byte
|
||||
|
||||
func (id UUID) String() string { return hex.EncodeToString(id[:]) }
|
||||
func (id UUID) String() string {
|
||||
if id == zeroUUID {
|
||||
return "<zero>"
|
||||
}
|
||||
buf := make([]byte, UUIDSize*2+4)
|
||||
i := 0
|
||||
i += hex.Encode(buf[i:], id[:4])
|
||||
buf[i] = '-'
|
||||
i++
|
||||
i += hex.Encode(buf[i:], id[4:6])
|
||||
buf[i] = '-'
|
||||
i++
|
||||
i += hex.Encode(buf[i:], id[6:8])
|
||||
buf[i] = '-'
|
||||
i++
|
||||
i += hex.Encode(buf[i:], id[8:10])
|
||||
buf[i] = '-'
|
||||
i++
|
||||
i += hex.Encode(buf[i:], id[10:])
|
||||
return string(buf)
|
||||
}
|
||||
|
||||
type FSID [FSIDSize]byte
|
||||
|
||||
@ -121,7 +143,7 @@ type btrfs_ioctl_scrub_args struct {
|
||||
flags uint64 // in
|
||||
progress btrfs_scrub_progress // out
|
||||
// pad to 1k
|
||||
unused [1024 - 4*8 - unsafe.Sizeof(btrfs_scrub_progress{})]byte
|
||||
_ [1024 - 4*8 - unsafe.Sizeof(btrfs_scrub_progress{})]byte
|
||||
}
|
||||
|
||||
type contReadingFromSrcdevMode uint64
|
||||
@ -168,7 +190,7 @@ type btrfs_ioctl_dev_replace_args_u2 struct {
|
||||
cmd uint64 // in
|
||||
result uint64 // out
|
||||
status btrfs_ioctl_dev_replace_status_params // out
|
||||
unused [unsafe.Sizeof(btrfs_ioctl_dev_replace_start_params{}) - unsafe.Sizeof(btrfs_ioctl_dev_replace_status_params{})]byte
|
||||
_ [unsafe.Sizeof(btrfs_ioctl_dev_replace_start_params{}) - unsafe.Sizeof(btrfs_ioctl_dev_replace_status_params{})]byte
|
||||
spare [64]uint64
|
||||
}
|
||||
|
||||
@ -177,7 +199,7 @@ type btrfs_ioctl_dev_info_args struct {
|
||||
uuid UUID // in/out
|
||||
bytes_used uint64 // out
|
||||
total_bytes uint64 // out
|
||||
unused [379]uint64 // pad to 4k
|
||||
_ [379]uint64 // pad to 4k
|
||||
path [devicePathNameMax]byte // out
|
||||
}
|
||||
|
||||
@ -188,7 +210,7 @@ type btrfs_ioctl_fs_info_args struct {
|
||||
nodesize uint32 // out
|
||||
sectorsize uint32 // out
|
||||
clone_alignment uint32 // out
|
||||
reserved [122*8 + 4]byte // pad to 1k
|
||||
_ [122*8 + 4]byte // pad to 1k
|
||||
}
|
||||
|
||||
type btrfs_ioctl_feature_flags struct {
|
||||
@ -231,7 +253,7 @@ type btrfs_balance_args struct {
|
||||
limit argRange
|
||||
stripes_min uint32
|
||||
stripes_max uint32
|
||||
unused [48]byte
|
||||
_ [48]byte
|
||||
}
|
||||
|
||||
// report balance progress to userspace
|
||||
@ -250,13 +272,13 @@ const (
|
||||
)
|
||||
|
||||
type btrfs_ioctl_balance_args struct {
|
||||
flags uint64 // in/out
|
||||
state balanceState // out
|
||||
data btrfs_balance_args // in/out
|
||||
meta btrfs_balance_args // in/out
|
||||
sys btrfs_balance_args // in/out
|
||||
stat btrfs_balance_progress // out
|
||||
unused [72 * 8]byte // pad to 1k
|
||||
flags uint64 // in/out
|
||||
state balanceState // out
|
||||
data btrfs_balance_args // in/out
|
||||
meta btrfs_balance_args // in/out
|
||||
sys btrfs_balance_args // in/out
|
||||
stat btrfs_balance_progress // out
|
||||
_ [72 * 8]byte // pad to 1k
|
||||
}
|
||||
|
||||
const _BTRFS_INO_LOOKUP_PATH_MAX = 4080
|
||||
@ -283,7 +305,7 @@ type btrfs_ioctl_search_key struct {
|
||||
max_type uint32
|
||||
// how many items did userland ask for, and how many are we returning
|
||||
nr_items uint32
|
||||
unused [36]byte
|
||||
_ [36]byte
|
||||
}
|
||||
|
||||
type btrfs_ioctl_search_header struct {
|
||||
@ -347,7 +369,7 @@ type btrfs_ioctl_same_args struct {
|
||||
logical_offset uint64 // in - start of extent in source
|
||||
length uint64 // in - length of extent
|
||||
dest_count uint16 // in - total elements in info array
|
||||
reserved [6]byte
|
||||
_ [6]byte
|
||||
//info [0]btrfs_ioctl_same_extent_info
|
||||
}
|
||||
|
||||
@ -364,7 +386,7 @@ type btrfs_ioctl_defrag_range_args struct {
|
||||
// which compression method to use if turning on compression
|
||||
// for this defrag operation. If unspecified, zlib will be used
|
||||
compress_type uint32
|
||||
unused [16]byte // spare for later
|
||||
_ [16]byte // spare for later
|
||||
}
|
||||
|
||||
type btrfs_ioctl_space_info struct {
|
||||
@ -388,17 +410,17 @@ type btrfs_data_container struct {
|
||||
}
|
||||
|
||||
type btrfs_ioctl_ino_path_args struct {
|
||||
inum uint64 // in
|
||||
size uint64 // in
|
||||
reserved [32]byte
|
||||
inum uint64 // in
|
||||
size uint64 // in
|
||||
_ [32]byte
|
||||
// struct btrfs_data_container *fspath; out
|
||||
fspath uint64 // out
|
||||
}
|
||||
|
||||
type btrfs_ioctl_logical_ino_args struct {
|
||||
logical uint64 // in
|
||||
size uint64 // in
|
||||
reserved [32]byte
|
||||
logical uint64 // in
|
||||
size uint64 // in
|
||||
_ [32]byte
|
||||
// struct btrfs_data_container *inodes; out
|
||||
inodes uint64
|
||||
}
|
||||
@ -428,7 +450,7 @@ type btrfs_ioctl_get_dev_stats struct {
|
||||
nr_items uint64 // in/out
|
||||
flags uint64 // in/out
|
||||
values [_BTRFS_DEV_STAT_VALUES_MAX]uint64 // out values
|
||||
unused [128 - 2 - _BTRFS_DEV_STAT_VALUES_MAX]uint64 // pad to 1k
|
||||
_ [128 - 2 - _BTRFS_DEV_STAT_VALUES_MAX]uint64 // pad to 1k
|
||||
}
|
||||
|
||||
const (
|
||||
@ -445,7 +467,7 @@ type btrfs_ioctl_quota_ctl_args struct {
|
||||
type btrfs_ioctl_quota_rescan_args struct {
|
||||
flags uint64
|
||||
progress uint64
|
||||
reserved [6]uint64
|
||||
_ [6]uint64
|
||||
}
|
||||
|
||||
type btrfs_ioctl_qgroup_assign_args struct {
|
||||
@ -471,7 +493,7 @@ type btrfs_ioctl_received_subvol_args struct {
|
||||
stime btrfs_ioctl_timespec // in
|
||||
rtime btrfs_ioctl_timespec // out
|
||||
flags uint64 // in
|
||||
reserved [16]uint64 // in
|
||||
_ [16]uint64 // in
|
||||
}
|
||||
|
||||
const (
|
||||
@ -498,7 +520,7 @@ type btrfs_ioctl_send_args struct {
|
||||
clone_sources *uint64 // in
|
||||
parent_root uint64 // in
|
||||
flags uint64 // in
|
||||
reserved [4]uint64 // in
|
||||
_ [4]uint64 // in
|
||||
}
|
||||
|
||||
var (
|
||||
|
@ -47,6 +47,10 @@ var caseSizes = []struct {
|
||||
{obj: btrfs_ioctl_timespec{}, size: 16},
|
||||
{obj: btrfs_ioctl_received_subvol_args{}, size: 200},
|
||||
{obj: btrfs_ioctl_send_args{}, size: 72},
|
||||
|
||||
//{obj:btrfs_root_ref{},size:18},
|
||||
//{obj:btrfs_root_item{},size:439},
|
||||
//{obj:btrfs_inode_item{},size:160},
|
||||
}
|
||||
|
||||
func TestSizes(t *testing.T) {
|
122
subvolume.go
122
subvolume.go
@ -6,6 +6,7 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
func checkSubVolumeName(name string) bool {
|
||||
@ -119,11 +120,13 @@ func SnapshotSubVolume(subvol, dst string, ro bool) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fdst.Close()
|
||||
// TODO: make SnapshotSubVolume a method on FS to use existing fd
|
||||
f, err := openDir(subvol)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
args := btrfs_ioctl_vol_args_v2{
|
||||
fd: int64(f.Fd()),
|
||||
}
|
||||
@ -139,3 +142,122 @@ func SnapshotSubVolume(subvol, dst string, ro bool) error {
|
||||
copy(args.name[:], newName)
|
||||
return iocSnapCreateV2(fdst, &args)
|
||||
}
|
||||
|
||||
func ListSubVolumes(path string) ([]Subvolume, error) {
|
||||
f, err := openDir(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
//root, err := getPathRootID(f)
|
||||
//if err != nil {
|
||||
// return nil, fmt.Errorf("can't get rootid for '%s': %v", path, err)
|
||||
//}
|
||||
m, err := listSubVolumes(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := make([]Subvolume, 0, len(m))
|
||||
for _, v := range m {
|
||||
out = append(out, v)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
type Subvolume struct {
|
||||
ObjectID uint64
|
||||
TransID uint64
|
||||
Name string
|
||||
RefTree uint64
|
||||
DirID uint64
|
||||
Gen uint64
|
||||
OGen uint64
|
||||
Flags uint64
|
||||
UUID UUID
|
||||
ParentUUID UUID
|
||||
ReceivedUUID UUID
|
||||
OTime time.Time
|
||||
CTime time.Time
|
||||
}
|
||||
|
||||
func listSubVolumes(f *os.File) (map[uint64]Subvolume, error) {
|
||||
sk := btrfs_ioctl_search_key{
|
||||
// search in the tree of tree roots
|
||||
tree_id: 1,
|
||||
|
||||
// Set the min and max to backref keys. The search will
|
||||
// only send back this type of key now.
|
||||
min_type: rootBackrefKey,
|
||||
max_type: rootBackrefKey,
|
||||
|
||||
min_objectid: firstFreeObjectid,
|
||||
|
||||
// Set all the other params to the max, we'll take any objectid
|
||||
// and any trans.
|
||||
max_objectid: lastFreeObjectid,
|
||||
max_offset: maxUint64,
|
||||
max_transid: maxUint64,
|
||||
|
||||
nr_items: 4096, // just a big number, doesn't matter much
|
||||
}
|
||||
m := make(map[uint64]Subvolume)
|
||||
for {
|
||||
out, err := treeSearchRaw(f, sk)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if len(out) == 0 {
|
||||
break
|
||||
}
|
||||
for _, obj := range out {
|
||||
switch obj.Type {
|
||||
case rootBackrefKey:
|
||||
ref := asRootRef(obj.Data)
|
||||
o := m[obj.ObjectID]
|
||||
o.TransID = obj.TransID
|
||||
o.ObjectID = obj.ObjectID
|
||||
o.RefTree = obj.Offset
|
||||
o.DirID = ref.DirID
|
||||
o.Name = ref.Name
|
||||
m[obj.ObjectID] = o
|
||||
case rootItemKey:
|
||||
o := m[obj.ObjectID]
|
||||
o.TransID = obj.TransID
|
||||
o.ObjectID = obj.ObjectID
|
||||
// TODO: decode whole object?
|
||||
o.Gen = asUint64(obj.Data[160:]) // size of btrfs_inode_item
|
||||
o.Flags = asUint64(obj.Data[160+6*8:])
|
||||
const sz = 439
|
||||
const toff = sz - 8*8 - 4*12
|
||||
o.CTime = asTime(obj.Data[toff+0*12:])
|
||||
o.OTime = asTime(obj.Data[toff+1*12:])
|
||||
o.OGen = asUint64(obj.Data[toff-3*8:])
|
||||
const uoff = toff - 4*8 - 3*UUIDSize
|
||||
copy(o.UUID[:], obj.Data[uoff+0*UUIDSize:])
|
||||
copy(o.ParentUUID[:], obj.Data[uoff+1*UUIDSize:])
|
||||
copy(o.ReceivedUUID[:], obj.Data[uoff+2*UUIDSize:])
|
||||
m[obj.ObjectID] = o
|
||||
}
|
||||
}
|
||||
// record the mins in key so we can make sure the
|
||||
// next search doesn't repeat this root
|
||||
last := out[len(out)-1]
|
||||
sk.min_objectid = last.ObjectID
|
||||
sk.min_type = last.Type
|
||||
sk.min_offset = last.Offset + 1
|
||||
if sk.min_offset == 0 { // overflow
|
||||
sk.min_type++
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
if sk.min_type > rootBackrefKey {
|
||||
sk.min_type = rootItemKey
|
||||
sk.min_objectid++
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
if sk.min_objectid > sk.max_objectid {
|
||||
break
|
||||
}
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
33
utils.go
33
utils.go
@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func isBtrfs(path string) (bool, error) {
|
||||
@ -35,3 +36,35 @@ func openDir(path string) (*os.File, error) {
|
||||
}
|
||||
return file, nil
|
||||
}
|
||||
|
||||
type rawItem struct {
|
||||
TransID uint64
|
||||
ObjectID uint64
|
||||
Type uint32
|
||||
Offset uint64
|
||||
Data []byte
|
||||
}
|
||||
|
||||
func treeSearchRaw(f *os.File, key btrfs_ioctl_search_key) (out []rawItem, _ error) {
|
||||
args := btrfs_ioctl_search_args{
|
||||
key: key,
|
||||
}
|
||||
if err := iocTreeSearch(f, &args); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = make([]rawItem, 0, args.key.nr_items)
|
||||
buf := args.buf[:]
|
||||
for i := 0; i < int(args.key.nr_items); i++ {
|
||||
h := (*btrfs_ioctl_search_header)(unsafe.Pointer(&buf[0]))
|
||||
buf = buf[unsafe.Sizeof(btrfs_ioctl_search_header{}):]
|
||||
out = append(out, rawItem{
|
||||
TransID: h.transid,
|
||||
ObjectID: h.objectid,
|
||||
Type: h.typ,
|
||||
Offset: h.offset,
|
||||
Data: buf[:h.len], // TODO: reallocate?
|
||||
})
|
||||
buf = buf[h.len:]
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user