From f03fa748e70cfb3681f803b369563b7886aa4448 Mon Sep 17 00:00:00 2001 From: Denys Smirnov Date: Fri, 16 Sep 2016 20:09:14 +0300 Subject: [PATCH] implement subvolumes list; regenerate headers; generate btrfs_tree.h --- btrfs_h.go | 2 + btrfs_tree.go | 54 +++ btrfs_tree_h.go | 745 ++++++++++++++++++++++++++++++++ btrfs_tree_hc.go | 30 +- cmd/gbtrfs.go | 18 + cmd/hgen.go | 9 +- ioctl_h.go | 74 ++-- ioctl_h_test.go => size_test.go | 4 + subvolume.go | 122 ++++++ utils.go | 33 ++ 10 files changed, 1047 insertions(+), 44 deletions(-) create mode 100644 btrfs_tree.go create mode 100644 btrfs_tree_h.go rename ioctl_h_test.go => size_test.go (95%) diff --git a/btrfs_h.go b/btrfs_h.go index 2206361..b1af5c4 100644 --- a/btrfs_h.go +++ b/btrfs_h.go @@ -2,6 +2,8 @@ package btrfs import "strings" +const maxUint64 = 1<<64 - 1 + const BTRFS_LABEL_SIZE = 256 type FeatureFlags uint64 diff --git a/btrfs_tree.go b/btrfs_tree.go new file mode 100644 index 0000000..a7e6f6b --- /dev/null +++ b/btrfs_tree.go @@ -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 +} diff --git a/btrfs_tree_h.go b/btrfs_tree_h.go new file mode 100644 index 0000000..d5ebd66 --- /dev/null +++ b/btrfs_tree_h.go @@ -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 +} diff --git a/btrfs_tree_hc.go b/btrfs_tree_hc.go index 5b74214..75c392c 100644 --- a/btrfs_tree_hc.go +++ b/btrfs_tree_hc.go @@ -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 diff --git a/cmd/gbtrfs.go b/cmd/gbtrfs.go index 2389992..3c42a77 100644 --- a/cmd/gbtrfs.go +++ b/cmd/gbtrfs.go @@ -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 ", + 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 ] [-c ] [-f ] [...]", Short: "Send the subvolume(s) to stdout.", diff --git a/cmd/hgen.go b/cmd/hgen.go index 02616a4..89d2b61 100644 --- a/cmd/hgen.go +++ b/cmd/hgen.go @@ -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) diff --git a/ioctl_h.go b/ioctl_h.go index 386246d..1c73d5f 100644 --- a/ioctl_h.go +++ b/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 "" + } + 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 ( diff --git a/ioctl_h_test.go b/size_test.go similarity index 95% rename from ioctl_h_test.go rename to size_test.go index f8f0582..edc114a 100644 --- a/ioctl_h_test.go +++ b/size_test.go @@ -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) { diff --git a/subvolume.go b/subvolume.go index 42ee9c2..e860830 100644 --- a/subvolume.go +++ b/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 +} diff --git a/utils.go b/utils.go index 9a6fe0d..6071f85 100644 --- a/utils.go +++ b/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 +}