├── README.md ├── go.mod ├── go.sum ├── headers.go ├── cmd └── gbtrfs │ ├── go.mod │ ├── go.sum │ └── gbtrfs.go ├── btrfs_list.go ├── mtab └── mtab.go ├── ioctl └── ioctl.go ├── receive.go ├── uuid_tree.go ├── errors.go ├── xattr.go ├── btrfs_h.go ├── test └── btrfstest.go ├── size_test.go ├── utils.go ├── send ├── send_h.go └── send.go ├── usage.go ├── internal └── cmd │ └── hgen.go ├── btrfs_test.go ├── send.go ├── btrfs.go ├── btrfs_tree.go ├── subvolume.go ├── LICENSE ├── btrfs_tree_hc.go ├── btrfs_tree_h.go └── ioctl_h.go /README.md: -------------------------------------------------------------------------------- 1 | # btrfs 2 | Btrfs library in a pure Go 3 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/dennwc/btrfs 2 | 3 | go 1.12 4 | 5 | require github.com/dennwc/ioctl v1.0.0 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/dennwc/ioctl v1.0.0 h1:DsWAAjIxRqNcLn9x6mwfuf2pet3iB7aK90K4tF16rLg= 2 | github.com/dennwc/ioctl v1.0.0/go.mod h1:ellh2YB5ldny99SBU/VX7Nq0xiZbHphf1DrtHxxjMk0= 3 | -------------------------------------------------------------------------------- /headers.go: -------------------------------------------------------------------------------- 1 | package btrfs 2 | 3 | //go:generate go run ./cmd/hgen.go -u -g -t BTRFS_ -p btrfs -cs=treeKeyType:uint32=_KEY,objectID:uint64=_OBJECTID -cp=fileType=FT_,fileExtentType=FILE_EXTENT_,devReplaceItemState=DEV_REPLACE_ITEM_STATE_,blockGroup:uint64=BLOCK_GROUP_ -o btrfs_tree_hc.go btrfs_tree.h 4 | //go:generate gofmt -l -w btrfs_tree_hc.go 5 | -------------------------------------------------------------------------------- /cmd/gbtrfs/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/dennwc/btrfs/cmd/gbtrfs 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/dennwc/btrfs v0.0.0-20181021180244-694b569856e3 7 | github.com/dennwc/ioctl v1.0.0 // indirect 8 | github.com/inconshreveable/mousetrap v1.0.0 // indirect 9 | github.com/spf13/cobra v0.0.3 10 | github.com/spf13/pflag v1.0.3 // indirect 11 | ) 12 | -------------------------------------------------------------------------------- /btrfs_list.go: -------------------------------------------------------------------------------- 1 | package btrfs 2 | 3 | import "os" 4 | 5 | func getFileRootID(file *os.File) (objectID, error) { 6 | args := btrfs_ioctl_ino_lookup_args{ 7 | objectid: firstFreeObjectid, 8 | } 9 | if err := iocInoLookup(file, &args); err != nil { 10 | return 0, err 11 | } 12 | return args.treeid, nil 13 | } 14 | 15 | func getPathRootID(path string) (objectID, error) { 16 | fs, err := Open(path, true) 17 | if err != nil { 18 | return 0, err 19 | } 20 | defer fs.Close() 21 | return getFileRootID(fs.f) 22 | } 23 | -------------------------------------------------------------------------------- /mtab/mtab.go: -------------------------------------------------------------------------------- 1 | // Package mtab contains tools to work with /etc/mtab file. 2 | package mtab 3 | 4 | import ( 5 | "bufio" 6 | "io" 7 | "os" 8 | "strings" 9 | ) 10 | 11 | type MountPoint struct { 12 | Dev string 13 | Mount string 14 | Type string 15 | Opts string 16 | } 17 | 18 | // Mounts returns a list of mount point from /etc/mtab. 19 | func Mounts() ([]MountPoint, error) { 20 | file, err := os.Open("/etc/mtab") 21 | if err != nil { 22 | return nil, err 23 | } 24 | defer file.Close() 25 | r := bufio.NewReader(file) 26 | var out []MountPoint 27 | for { 28 | line, err := r.ReadString('\n') 29 | if err == io.EOF { 30 | break 31 | } else if err != nil { 32 | return nil, err 33 | } 34 | fields := strings.Fields(line) 35 | out = append(out, MountPoint{ 36 | Dev: fields[0], 37 | Mount: fields[1], 38 | Type: fields[2], 39 | Opts: fields[3], 40 | }) 41 | } 42 | return out, nil 43 | } 44 | -------------------------------------------------------------------------------- /cmd/gbtrfs/go.sum: -------------------------------------------------------------------------------- 1 | github.com/dennwc/btrfs v0.0.0-20181021180244-694b569856e3 h1:gvAC0SRt17o5OEwJU+0Iz298dfYF/aJlSfKf9NRay6c= 2 | github.com/dennwc/btrfs v0.0.0-20181021180244-694b569856e3/go.mod h1:8k+PMLjFlirprJbTSZJbkj8SEkfTAn3b0JhgPPE78HI= 3 | github.com/dennwc/ioctl v1.0.0 h1:DsWAAjIxRqNcLn9x6mwfuf2pet3iB7aK90K4tF16rLg= 4 | github.com/dennwc/ioctl v1.0.0/go.mod h1:ellh2YB5ldny99SBU/VX7Nq0xiZbHphf1DrtHxxjMk0= 5 | github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= 6 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 7 | github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8= 8 | github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= 9 | github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= 10 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 11 | -------------------------------------------------------------------------------- /ioctl/ioctl.go: -------------------------------------------------------------------------------- 1 | package ioctl 2 | 3 | import ( 4 | "github.com/dennwc/ioctl" 5 | "os" 6 | ) 7 | 8 | const ( 9 | None = ioctl.None 10 | Write = ioctl.Write 11 | Read = ioctl.Read 12 | ) 13 | 14 | // IOC 15 | // 16 | // Deprecated: use github/dennwc/ioctl 17 | func IOC(dir, typ, nr, size uintptr) uintptr { 18 | return ioctl.IOC(dir, typ, nr, size) 19 | } 20 | 21 | // IO 22 | // 23 | // Deprecated: use github/dennwc/ioctl 24 | func IO(typ, nr uintptr) uintptr { 25 | return ioctl.IO(typ, nr) 26 | } 27 | 28 | // IOC 29 | // 30 | // Deprecated: use github/dennwc/ioctl 31 | func IOR(typ, nr, size uintptr) uintptr { 32 | return ioctl.IOR(typ, nr, size) 33 | } 34 | 35 | // IOW 36 | // 37 | // Deprecated: use github/dennwc/ioctl 38 | func IOW(typ, nr, size uintptr) uintptr { 39 | return ioctl.IOW(typ, nr, size) 40 | } 41 | 42 | // IOWR 43 | // 44 | // Deprecated: use github/dennwc/ioctl 45 | func IOWR(typ, nr, size uintptr) uintptr { 46 | return ioctl.IOWR(typ, nr, size) 47 | } 48 | 49 | // Ioctl 50 | // 51 | // Deprecated: use github/dennwc/ioctl 52 | func Ioctl(f *os.File, ioc uintptr, addr uintptr) error { 53 | return ioctl.Ioctl(f, ioc, addr) 54 | } 55 | 56 | // Do 57 | // 58 | // Deprecated: use github/dennwc/ioctl 59 | func Do(f *os.File, ioc uintptr, arg interface{}) error { 60 | return ioctl.Do(f, ioc, arg) 61 | } 62 | -------------------------------------------------------------------------------- /receive.go: -------------------------------------------------------------------------------- 1 | package btrfs 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "io" 7 | "os" 8 | "os/exec" 9 | "path/filepath" 10 | "syscall" 11 | ) 12 | 13 | const nativeReceive = false 14 | 15 | func Receive(r io.Reader, dstDir string) error { 16 | if !nativeReceive { 17 | buf := bytes.NewBuffer(nil) 18 | cmd := exec.Command("btrfs", "receive", dstDir) 19 | cmd.Stdin = r 20 | cmd.Stderr = buf 21 | if err := cmd.Run(); err != nil { 22 | if buf.Len() != 0 { 23 | return errors.New(buf.String()) 24 | } 25 | return err 26 | } 27 | return nil 28 | } 29 | var err error 30 | dstDir, err = filepath.Abs(dstDir) 31 | if err != nil { 32 | return err 33 | } 34 | realMnt, err := findMountRoot(dstDir) 35 | if err != nil { 36 | return err 37 | } 38 | dir, err := os.OpenFile(dstDir, os.O_RDONLY|syscall.O_NOATIME, 0755) 39 | if err != nil { 40 | return err 41 | } 42 | mnt, err := os.OpenFile(realMnt, os.O_RDONLY|syscall.O_NOATIME, 0755) 43 | if err != nil { 44 | return err 45 | } 46 | // We want to resolve the path to the subvolume we're sitting in 47 | // so that we can adjust the paths of any subvols we want to receive in. 48 | subvolID, err := getFileRootID(mnt) 49 | if err != nil { 50 | return err 51 | } 52 | //sr, err := send.NewStreamReader(r) 53 | //if err != nil { 54 | // return err 55 | //} 56 | _, _ = dir, subvolID 57 | panic("not implemented") 58 | } 59 | -------------------------------------------------------------------------------- /uuid_tree.go: -------------------------------------------------------------------------------- 1 | package btrfs 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | "os" 7 | ) 8 | 9 | func lookupUUIDSubvolItem(f *os.File, uuid UUID) (objectID, error) { 10 | return uuidTreeLookupAny(f, uuid, uuidKeySubvol) 11 | } 12 | 13 | func lookupUUIDReceivedSubvolItem(f *os.File, uuid UUID) (objectID, error) { 14 | return uuidTreeLookupAny(f, uuid, uuidKeyReceivedSubvol) 15 | } 16 | 17 | func (id UUID) toKey() (objID objectID, off uint64) { 18 | objID = objectID(binary.LittleEndian.Uint64(id[:8])) 19 | off = binary.LittleEndian.Uint64(id[8:16]) 20 | return 21 | } 22 | 23 | // uuidTreeLookupAny searches uuid tree for a given uuid in specified field. 24 | // It returns ErrNotFound if object was not found. 25 | func uuidTreeLookupAny(f *os.File, uuid UUID, typ treeKeyType) (objectID, error) { 26 | objId, off := uuid.toKey() 27 | args := btrfs_ioctl_search_key{ 28 | tree_id: uuidTreeObjectid, 29 | min_objectid: objId, 30 | max_objectid: objId, 31 | min_type: typ, 32 | max_type: typ, 33 | min_offset: off, 34 | max_offset: off, 35 | max_transid: maxUint64, 36 | nr_items: 1, 37 | } 38 | res, err := treeSearchRaw(f, args) 39 | if err != nil { 40 | return 0, err 41 | } else if len(res) < 1 { 42 | return 0, ErrNotFound 43 | } 44 | out := res[0] 45 | if len(out.Data) != 8 { 46 | return 0, fmt.Errorf("btrfs: uuid item with illegal size %d", len(out.Data)) 47 | } 48 | return objectID(binary.LittleEndian.Uint64(out.Data)), nil 49 | } 50 | -------------------------------------------------------------------------------- /errors.go: -------------------------------------------------------------------------------- 1 | package btrfs 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | ) 7 | 8 | type ErrNotBtrfs struct { 9 | Path string 10 | } 11 | 12 | func (e ErrNotBtrfs) Error() string { 13 | return fmt.Sprintf("not a btrfs filesystem: %s", e.Path) 14 | } 15 | 16 | // Error codes as returned by the kernel 17 | type ErrCode int 18 | 19 | func (e ErrCode) Error() string { 20 | s, ok := errorString[e] 21 | if ok { 22 | return s 23 | } 24 | return fmt.Sprintf("error %d", int(e)) 25 | } 26 | 27 | const ( 28 | ErrDevRAID1MinNotMet = ErrCode(iota + 1) 29 | ErrDevRAID10MinNotMet 30 | ErrDevRAID5MinNotMet 31 | ErrDevRAID6MinNotMet 32 | ErrDevTargetReplace 33 | ErrDevMissingNotFound 34 | ErrDevOnlyWritable 35 | ErrDevExclRunInProgress 36 | ) 37 | 38 | var errorString = map[ErrCode]string{ 39 | ErrDevRAID1MinNotMet: "unable to go below two devices on raid1", 40 | ErrDevRAID10MinNotMet: "unable to go below four devices on raid10", 41 | ErrDevRAID5MinNotMet: "unable to go below two devices on raid5", 42 | ErrDevRAID6MinNotMet: "unable to go below three devices on raid6", 43 | ErrDevTargetReplace: "unable to remove the dev_replace target dev", 44 | ErrDevMissingNotFound: "no missing devices found to remove", 45 | ErrDevOnlyWritable: "unable to remove the only writeable device", 46 | ErrDevExclRunInProgress: "add/delete/balance/replace/resize operation in progress", 47 | } 48 | 49 | var ( 50 | ErrNotFound = errors.New("not found") 51 | errNotImplemented = errors.New("not implemented") 52 | ) 53 | -------------------------------------------------------------------------------- /xattr.go: -------------------------------------------------------------------------------- 1 | package btrfs 2 | 3 | import ( 4 | "bytes" 5 | "os" 6 | "syscall" 7 | ) 8 | 9 | const ( 10 | xattrPrefix = "btrfs." 11 | xattrCompression = xattrPrefix + "compression" 12 | ) 13 | 14 | type Compression string 15 | 16 | const ( 17 | CompressionNone = Compression("") 18 | LZO = Compression("lzo") 19 | ZLIB = Compression("zlib") 20 | ) 21 | 22 | func SetCompression(path string, v Compression) error { 23 | var value []byte 24 | if v != CompressionNone { 25 | var err error 26 | value, err = syscall.ByteSliceFromString(string(v)) 27 | if err != nil { 28 | return err 29 | } 30 | } 31 | err := syscall.Setxattr(path, xattrCompression, value, 0) 32 | if err != nil { 33 | return &os.PathError{Op: "setxattr", Path: path, Err: err} 34 | } 35 | return nil 36 | } 37 | 38 | func GetCompression(path string) (Compression, error) { 39 | var buf []byte 40 | for { 41 | sz, err := syscall.Getxattr(path, xattrCompression, nil) 42 | if err == syscall.ENODATA || sz == 0 { 43 | return CompressionNone, nil 44 | } else if err != nil { 45 | return CompressionNone, &os.PathError{Op: "getxattr", Path: path, Err: err} 46 | } 47 | if cap(buf) < sz { 48 | buf = make([]byte, sz) 49 | } else { 50 | buf = buf[:sz] 51 | } 52 | sz, err = syscall.Getxattr(path, xattrCompression, buf) 53 | if err == syscall.ENODATA { 54 | return CompressionNone, nil 55 | } else if err == syscall.ERANGE { 56 | // xattr changed by someone else, and is larger than our current buffer 57 | continue 58 | } else if err != nil { 59 | return CompressionNone, &os.PathError{Op: "getxattr", Path: path, Err: err} 60 | } 61 | buf = buf[:sz] 62 | break 63 | } 64 | buf = bytes.TrimSuffix(buf, []byte{0}) 65 | return Compression(buf), nil 66 | } 67 | -------------------------------------------------------------------------------- /btrfs_h.go: -------------------------------------------------------------------------------- 1 | package btrfs 2 | 3 | import "strings" 4 | 5 | const maxUint64 = 1<<64 - 1 6 | 7 | const labelSize = 256 8 | 9 | type FeatureFlags uint64 10 | 11 | const ( 12 | FeatureCompatROFreeSpaceTree = FeatureFlags(1 << 0) 13 | ) 14 | 15 | type IncompatFeatures uint64 16 | 17 | func (f IncompatFeatures) String() string { 18 | var s []string 19 | for i, name := range incompatFeatureNames { 20 | if uint64(f)&uint64(i) != 0 { 21 | s = append(s, name) 22 | } 23 | } 24 | return strings.Join(s, ",") 25 | } 26 | 27 | var incompatFeatureNames = []string{ 28 | "DefaultSubvol", 29 | "MixedGroups", 30 | "CompressLZO", 31 | "CompressLZOv2", 32 | "BigMetadata", 33 | "ExtendedIRef", 34 | "RAID56", 35 | "SkinnyMetadata", 36 | "NoHoles", 37 | } 38 | 39 | const ( 40 | FeatureIncompatMixedBackRef = IncompatFeatures(1 << 0) 41 | FeatureIncompatDefaultSubvol = IncompatFeatures(1 << 1) 42 | FeatureIncompatMixedGroups = IncompatFeatures(1 << 2) 43 | FeatureIncompatCompressLZO = IncompatFeatures(1 << 3) 44 | 45 | // Some patches floated around with a second compression method 46 | // lets save that incompat here for when they do get in. 47 | // Note we don't actually support it, we're just reserving the number. 48 | FeatureIncompatCompressLZOv2 = IncompatFeatures(1 << 4) 49 | 50 | // Older kernels tried to do bigger metadata blocks, but the 51 | // code was pretty buggy. Lets not let them try anymore. 52 | FeatureIncompatBigMetadata = IncompatFeatures(1 << 5) 53 | 54 | FeatureIncompatExtendedIRef = IncompatFeatures(1 << 6) 55 | FeatureIncompatRAID56 = IncompatFeatures(1 << 7) 56 | FeatureIncompatSkinnyMetadata = IncompatFeatures(1 << 8) 57 | FeatureIncompatNoHoles = IncompatFeatures(1 << 9) 58 | ) 59 | 60 | // Flags definition for balance. 61 | type BalanceFlags uint64 62 | 63 | // Restriper's general type filter. 64 | const ( 65 | BalanceData = BalanceFlags(1 << 0) 66 | BalanceSystem = BalanceFlags(1 << 1) 67 | BalanceMetadata = BalanceFlags(1 << 2) 68 | 69 | BalanceMask = (BalanceData | BalanceSystem | BalanceMetadata) 70 | 71 | BalanceForce = BalanceFlags(1 << 3) 72 | BalanceResume = BalanceFlags(1 << 4) 73 | ) 74 | -------------------------------------------------------------------------------- /test/btrfstest.go: -------------------------------------------------------------------------------- 1 | package btrfstest 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "io/ioutil" 7 | "log" 8 | "os" 9 | "os/exec" 10 | "strings" 11 | "testing" 12 | "time" 13 | ) 14 | 15 | func run(name string, args ...string) error { 16 | buf := bytes.NewBuffer(nil) 17 | cmd := exec.Command(name, args...) 18 | cmd.Stdout = buf 19 | cmd.Stderr = buf 20 | err := cmd.Run() 21 | if err == nil { 22 | return nil 23 | } else if buf.Len() == 0 { 24 | return err 25 | } 26 | return errors.New("error: " + strings.TrimSpace(string(buf.Bytes()))) 27 | } 28 | 29 | func Mkfs(file string, size int64) error { 30 | f, err := os.Create(file) 31 | if err != nil { 32 | return err 33 | } 34 | if err = f.Truncate(size); err != nil { 35 | f.Close() 36 | return err 37 | } 38 | if err = f.Close(); err != nil { 39 | return err 40 | } 41 | if err = run("mkfs.btrfs", file); err != nil { 42 | os.Remove(file) 43 | return err 44 | } 45 | return err 46 | } 47 | 48 | func Mount(mount string, file string) error { 49 | if err := run("mount", file, mount); err != nil { 50 | return err 51 | } 52 | return nil 53 | } 54 | 55 | func Unmount(mount string) error { 56 | for i := 0; i < 5; i++ { 57 | if err := run("umount", mount); err == nil { 58 | break 59 | } else { 60 | if strings.Contains(err.Error(), "busy") { 61 | time.Sleep(time.Second) 62 | } else { 63 | break 64 | } 65 | } 66 | } 67 | return nil 68 | } 69 | 70 | func New(t testing.TB, size int64) (string, func()) { 71 | f, err := ioutil.TempFile("", "btrfs_vol") 72 | if err != nil { 73 | t.Fatal(err) 74 | } 75 | name := f.Name() 76 | f.Close() 77 | rm := func() { 78 | os.Remove(name) 79 | } 80 | if err = Mkfs(name, size); err != nil { 81 | rm() 82 | } 83 | mount, err := ioutil.TempDir("", "btrfs_mount") 84 | if err != nil { 85 | rm() 86 | t.Fatal(err) 87 | } 88 | if err = Mount(mount, name); err != nil { 89 | rm() 90 | os.RemoveAll(mount) 91 | if txt := err.Error(); strings.Contains(txt, "permission denied") || 92 | strings.Contains(txt, "only root") { 93 | t.Skip(err) 94 | } else { 95 | t.Fatal(err) 96 | } 97 | } 98 | done := false 99 | return mount, func() { 100 | if done { 101 | return 102 | } 103 | if err := Unmount(mount); err != nil { 104 | log.Println("umount failed:", err) 105 | } 106 | if err := os.Remove(mount); err != nil { 107 | log.Println("cleanup failed:", err) 108 | } 109 | rm() 110 | done = true 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /size_test.go: -------------------------------------------------------------------------------- 1 | package btrfs 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | "unsafe" 7 | ) 8 | 9 | var caseSizes = []struct { 10 | obj interface{} 11 | size int 12 | }{ 13 | {obj: btrfs_ioctl_vol_args{}, size: 4096}, 14 | {obj: btrfs_qgroup_limit{}, size: 40}, 15 | {obj: btrfs_qgroup_inherit{}, size: 72}, 16 | {obj: btrfs_ioctl_qgroup_limit_args{}, size: 48}, 17 | {obj: btrfs_ioctl_vol_args_v2{}, size: 4096}, 18 | {obj: btrfs_scrub_progress{}, size: 120}, 19 | {obj: btrfs_ioctl_scrub_args{}, size: 1024}, 20 | {obj: btrfs_ioctl_dev_replace_start_params{}, size: 2072}, 21 | {obj: btrfs_ioctl_dev_replace_status_params{}, size: 48}, 22 | {obj: btrfs_ioctl_dev_replace_args_u1{}, size: 2600}, 23 | {obj: btrfs_ioctl_dev_replace_args_u2{}, size: 2600}, 24 | {obj: btrfs_ioctl_dev_info_args{}, size: 4096}, 25 | {obj: btrfs_ioctl_fs_info_args{}, size: 1024}, 26 | {obj: btrfs_ioctl_feature_flags{}, size: 24}, 27 | {obj: btrfs_balance_args{}, size: 136}, 28 | {obj: BalanceProgress{}, size: 24}, 29 | {obj: btrfs_ioctl_balance_args{}, size: 1024}, 30 | {obj: btrfs_ioctl_ino_lookup_args{}, size: 4096}, 31 | {obj: btrfs_ioctl_search_key{}, size: 104}, 32 | {obj: btrfs_ioctl_search_header{}, size: 32}, 33 | {obj: btrfs_ioctl_search_args{}, size: 4096}, 34 | {obj: btrfs_ioctl_search_args_v2{}, size: 112}, 35 | {obj: btrfs_ioctl_clone_range_args{}, size: 32}, 36 | {obj: btrfs_ioctl_same_extent_info{}, size: 32}, 37 | {obj: btrfs_ioctl_same_args{}, size: 24}, 38 | {obj: btrfs_ioctl_defrag_range_args{}, size: 48}, 39 | {obj: btrfs_ioctl_space_info{}, size: 24}, 40 | {obj: btrfs_ioctl_space_args{}, size: 16}, 41 | {obj: btrfs_data_container{}, size: 16}, 42 | {obj: btrfs_ioctl_ino_path_args{}, size: 56}, 43 | {obj: btrfs_ioctl_logical_ino_args{}, size: 56}, 44 | {obj: btrfs_ioctl_get_dev_stats{}, size: 1032}, 45 | {obj: btrfs_ioctl_quota_ctl_args{}, size: 16}, 46 | {obj: btrfs_ioctl_qgroup_assign_args{}, size: 24}, 47 | {obj: btrfs_ioctl_qgroup_create_args{}, size: 16}, 48 | {obj: btrfs_ioctl_timespec{}, size: 16}, 49 | {obj: btrfs_ioctl_received_subvol_args{}, size: 200}, 50 | {obj: btrfs_ioctl_send_args{}, size: 72}, 51 | 52 | //{obj:btrfs_timespec{},size:12}, 53 | //{obj:btrfs_root_ref{},size:18}, 54 | //{obj:btrfs_root_item{},size:439}, 55 | {obj: btrfs_root_item_raw{}, size: 439}, 56 | {obj: btrfs_root_item_raw_p1{}, size: 439 - 23 - int(unsafe.Sizeof(btrfs_root_item_raw_p3{}))}, 57 | {obj: btrfs_root_item_raw_p3{}, size: 439 - 23 - int(unsafe.Sizeof(btrfs_root_item_raw_p1{}))}, 58 | //{obj:btrfs_inode_item{},size:160}, 59 | {obj: btrfs_inode_item_raw{}, size: 160}, 60 | {obj: timeBlock{}, size: 4 * 12}, 61 | } 62 | 63 | func TestSizes(t *testing.T) { 64 | for _, c := range caseSizes { 65 | if sz := int(reflect.ValueOf(c.obj).Type().Size()); sz != c.size { 66 | t.Errorf("unexpected size of %T: %d (exp: %d)", c.obj, sz, c.size) 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /utils.go: -------------------------------------------------------------------------------- 1 | package btrfs 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "os" 7 | "path/filepath" 8 | "strings" 9 | "syscall" 10 | "unsafe" 11 | 12 | "github.com/dennwc/btrfs/mtab" 13 | ) 14 | 15 | func isBtrfs(path string) (bool, error) { 16 | var stfs syscall.Statfs_t 17 | if err := syscall.Statfs(path, &stfs); err != nil { 18 | return false, &os.PathError{Op: "statfs", Path: path, Err: err} 19 | } 20 | fsType := uint32(stfs.Type) 21 | return fsType == SuperMagic, nil 22 | } 23 | 24 | func findMountRoot(path string) (string, error) { 25 | mounts, err := mtab.Mounts() 26 | if err != nil { 27 | return "", err 28 | } 29 | longest := "" 30 | isBtrfs := false 31 | for _, m := range mounts { 32 | if !strings.HasPrefix(path, m.Mount) { 33 | continue 34 | } 35 | if len(longest) < len(m.Mount) { 36 | longest = m.Mount 37 | isBtrfs = m.Type == "btrfs" 38 | } 39 | } 40 | if longest == "" { 41 | return "", os.ErrNotExist 42 | } else if !isBtrfs { 43 | return "", ErrNotBtrfs{Path: longest} 44 | } 45 | return filepath.Abs(longest) 46 | } 47 | 48 | // openDir does the following checks before calling Open: 49 | // 1: path is in a btrfs filesystem 50 | // 2: path is a directory 51 | func openDir(path string) (*os.File, error) { 52 | if ok, err := isBtrfs(path); err != nil { 53 | return nil, err 54 | } else if !ok { 55 | return nil, ErrNotBtrfs{Path: path} 56 | } 57 | file, err := os.Open(path) 58 | if err != nil { 59 | return nil, err 60 | } else if st, err := file.Stat(); err != nil { 61 | file.Close() 62 | return nil, err 63 | } else if !st.IsDir() { 64 | file.Close() 65 | return nil, fmt.Errorf("not a directory: %s", path) 66 | } 67 | return file, nil 68 | } 69 | 70 | type searchResult struct { 71 | TransID uint64 72 | ObjectID objectID 73 | Type treeKeyType 74 | Offset uint64 75 | Data []byte 76 | } 77 | 78 | func treeSearchRaw(mnt *os.File, key btrfs_ioctl_search_key) (out []searchResult, _ error) { 79 | args := btrfs_ioctl_search_args{ 80 | key: key, 81 | } 82 | if err := iocTreeSearch(mnt, &args); err != nil { 83 | return nil, err 84 | } 85 | out = make([]searchResult, 0, args.key.nr_items) 86 | buf := args.buf[:] 87 | for i := 0; i < int(args.key.nr_items); i++ { 88 | h := (*btrfs_ioctl_search_header)(unsafe.Pointer(&buf[0])) 89 | buf = buf[unsafe.Sizeof(btrfs_ioctl_search_header{}):] 90 | out = append(out, searchResult{ 91 | TransID: h.transid, 92 | ObjectID: h.objectid, 93 | Offset: h.offset, 94 | Type: h.typ, 95 | Data: buf[:h.len:h.len], // TODO: reallocate? 96 | }) 97 | buf = buf[h.len:] 98 | } 99 | return out, nil 100 | } 101 | 102 | func stringFromBytes(input []byte) string { 103 | if i := bytes.IndexByte(input, 0); i >= 0 { 104 | input = input[:i] 105 | } 106 | return string(input) 107 | } 108 | -------------------------------------------------------------------------------- /cmd/gbtrfs/gbtrfs.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/dennwc/btrfs" 8 | "github.com/spf13/cobra" 9 | ) 10 | 11 | func init() { 12 | RootCmd.AddCommand( 13 | SubvolumeCmd, 14 | SendCmd, 15 | ReceiveCmd, 16 | ) 17 | 18 | SubvolumeCmd.AddCommand( 19 | SubvolumeCreateCmd, 20 | SubvolumeDeleteCmd, 21 | SubvolumeListCmd, 22 | ) 23 | 24 | SendCmd.Flags().StringP("parent", "p", "", "Send an incremental stream from to .") 25 | } 26 | 27 | var RootCmd = &cobra.Command{ 28 | Use: "btrfs [--help] [--version] [...] []", 29 | Short: "Use --help as an argument for information on a specific group or command.", 30 | } 31 | 32 | var SubvolumeCmd = &cobra.Command{ 33 | Use: "subvolume ", 34 | Aliases: []string{"subvol", "sub", "sv"}, 35 | } 36 | 37 | var SubvolumeCreateCmd = &cobra.Command{ 38 | Use: "create [-i ] [/]", 39 | Short: "Create a subvolume", 40 | Long: `Create a subvolume in . If is not given subvolume will be created in the current directory.`, 41 | RunE: func(cmd *cobra.Command, args []string) error { 42 | if len(args) == 0 { 43 | return fmt.Errorf("subvolume not specified") 44 | } else if len(args) > 1 { 45 | return fmt.Errorf("only one subvolume name is allowed") 46 | } 47 | return btrfs.CreateSubVolume(args[0]) 48 | }, 49 | } 50 | 51 | var SubvolumeDeleteCmd = &cobra.Command{ 52 | Use: "delete [options] [...]", 53 | Short: "Delete subvolume(s)", 54 | Long: `Delete subvolumes from the filesystem. The corresponding directory 55 | is removed instantly but the data blocks are removed later. 56 | The deletion does not involve full commit by default due to 57 | performance reasons (as a consequence, the subvolume may appear again 58 | after a crash). Use one of the --commit options to wait until the 59 | operation is safely stored on the media.`, 60 | RunE: func(cmd *cobra.Command, args []string) error { 61 | for _, arg := range args { 62 | if err := btrfs.DeleteSubVolume(arg); err != nil { 63 | return err 64 | } 65 | } 66 | return nil 67 | }, 68 | } 69 | 70 | var SubvolumeListCmd = &cobra.Command{ 71 | Use: "list ", 72 | Short: "List subvolumes", 73 | Aliases: []string{"ls"}, 74 | RunE: func(cmd *cobra.Command, args []string) error { 75 | if len(args) != 1 { 76 | return fmt.Errorf("expected one destination argument") 77 | } 78 | fs, err := btrfs.Open(args[0], true) 79 | if err != nil { 80 | return err 81 | } 82 | defer fs.Close() 83 | list, err := fs.ListSubvolumes(nil) 84 | if err == nil { 85 | for _, v := range list { 86 | fmt.Printf("%+v\n", v) 87 | } 88 | } 89 | return err 90 | }, 91 | } 92 | 93 | var SendCmd = &cobra.Command{ 94 | Use: "send [-v] [-p ] [-c ] [-f ] [...]", 95 | Short: "Send the subvolume(s) to stdout.", 96 | Long: `Sends the subvolume(s) specified by to stdout. 97 | should be read-only here.`, 98 | RunE: func(cmd *cobra.Command, args []string) error { 99 | parent, _ := cmd.Flags().GetString("parent") 100 | return btrfs.Send(os.Stdout, parent, args...) 101 | }, 102 | } 103 | 104 | var ReceiveCmd = &cobra.Command{ 105 | Use: "receive [-v] [-f ] [--max-errors ] ", 106 | Short: "Receive subvolumes from stdin.", 107 | Long: `Receives one or more subvolumes that were previously 108 | sent with btrfs send. The received subvolumes are stored 109 | into .`, 110 | RunE: func(cmd *cobra.Command, args []string) error { 111 | if len(args) != 1 { 112 | return fmt.Errorf("expected one destination argument") 113 | } 114 | return btrfs.Receive(os.Stdin, args[0]) 115 | }, 116 | } 117 | 118 | func main() { 119 | if err := RootCmd.Execute(); err != nil { 120 | fmt.Println(err) 121 | os.Exit(-1) 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /send/send_h.go: -------------------------------------------------------------------------------- 1 | package send 2 | 3 | import ( 4 | "encoding/binary" 5 | "io" 6 | "strconv" 7 | ) 8 | 9 | var sendEndianess = binary.LittleEndian 10 | 11 | const ( 12 | sendStreamMagic = "btrfs-stream\x00" 13 | sendStreamMagicSize = len(sendStreamMagic) 14 | sendStreamVersion = 1 15 | ) 16 | 17 | const ( 18 | sendBufSize = 64 * 1024 19 | sendReadSize = 48 * 1024 20 | ) 21 | 22 | const cmdHeaderSize = 10 23 | 24 | type cmdHeader struct { 25 | Len uint32 // len excluding the header 26 | Cmd CmdType 27 | Crc uint32 // crc including the header with zero crc field 28 | } 29 | 30 | func (h *cmdHeader) Size() int { return cmdHeaderSize } 31 | func (h *cmdHeader) Unmarshal(p []byte) error { 32 | if len(p) < cmdHeaderSize { 33 | return io.ErrUnexpectedEOF 34 | } 35 | h.Len = sendEndianess.Uint32(p[0:]) 36 | h.Cmd = CmdType(sendEndianess.Uint16(p[4:])) 37 | h.Crc = sendEndianess.Uint32(p[6:]) 38 | return nil 39 | } 40 | 41 | const tlvHeaderSize = 4 42 | 43 | type tlvHeader struct { 44 | Type uint16 45 | Len uint16 // len excluding the header 46 | } 47 | 48 | func (h *tlvHeader) Size() int { return tlvHeaderSize } 49 | func (h *tlvHeader) Unmarshal(p []byte) error { 50 | if len(p) < tlvHeaderSize { 51 | return io.ErrUnexpectedEOF 52 | } 53 | h.Type = sendEndianess.Uint16(p[0:]) 54 | h.Len = sendEndianess.Uint16(p[2:]) 55 | return nil 56 | } 57 | 58 | type CmdType uint16 59 | 60 | func (c CmdType) String() string { 61 | var name string 62 | if int(c) < len(cmdTypeNames) { 63 | name = cmdTypeNames[int(c)] 64 | } 65 | if name != "" { 66 | return name 67 | } 68 | return strconv.FormatInt(int64(c), 16) 69 | } 70 | 71 | var cmdTypeNames = []string{ 72 | "", 73 | 74 | "subvol", 75 | "snapshot", 76 | 77 | "mkfile", 78 | "mkdir", 79 | "mknod", 80 | "mkfifo", 81 | "mksock", 82 | "symlink", 83 | 84 | "rename", 85 | "link", 86 | "unlink", 87 | "rmdir", 88 | 89 | "set_xattr", 90 | "remove_xattr", 91 | 92 | "write", 93 | "clone", 94 | 95 | "truncate", 96 | "chmod", 97 | "chown", 98 | "utimes", 99 | 100 | "end", 101 | "update_extent", 102 | "", 103 | } 104 | 105 | const ( 106 | sendCmdUnspec = CmdType(iota) 107 | 108 | sendCmdSubvol 109 | sendCmdSnapshot 110 | 111 | sendCmdMkfile 112 | sendCmdMkdir 113 | sendCmdMknod 114 | sendCmdMkfifo 115 | sendCmdMksock 116 | sendCmdSymlink 117 | 118 | sendCmdRename 119 | sendCmdLink 120 | sendCmdUnlink 121 | sendCmdRmdir 122 | 123 | sendCmdSetXattr 124 | sendCmdRemoveXattr 125 | 126 | sendCmdWrite 127 | sendCmdClone 128 | 129 | sendCmdTruncate 130 | sendCmdChmod 131 | sendCmdChown 132 | sendCmdUtimes 133 | 134 | sendCmdEnd 135 | sendCmdUpdateExtent 136 | _sendCmdMax 137 | ) 138 | 139 | const sendCmdMax = _sendCmdMax - 1 140 | 141 | type sendCmdAttr uint16 142 | 143 | func (c sendCmdAttr) String() string { 144 | var name string 145 | if int(c) < len(sendAttrNames) { 146 | name = sendAttrNames[int(c)] 147 | } 148 | if name != "" { 149 | return name 150 | } 151 | return strconv.FormatInt(int64(c), 16) 152 | } 153 | 154 | const ( 155 | sendAttrUnspec = sendCmdAttr(iota) 156 | 157 | sendAttrUuid 158 | sendAttrCtransid 159 | 160 | sendAttrIno 161 | sendAttrSize 162 | sendAttrMode 163 | sendAttrUid 164 | sendAttrGid 165 | sendAttrRdev 166 | sendAttrCtime 167 | sendAttrMtime 168 | sendAttrAtime 169 | sendAttrOtime 170 | 171 | sendAttrXattrName 172 | sendAttrXattrData 173 | 174 | sendAttrPath 175 | sendAttrPathTo 176 | sendAttrPathLink 177 | 178 | sendAttrFileOffset 179 | sendAttrData 180 | 181 | sendAttrCloneUuid 182 | sendAttrCloneCtransid 183 | sendAttrClonePath 184 | sendAttrCloneOffset 185 | sendAttrCloneLen 186 | 187 | _sendAttrMax 188 | ) 189 | const sendAttrMax = _sendAttrMax - 1 190 | 191 | var sendAttrNames = []string{ 192 | "", 193 | 194 | "uuid", 195 | "ctransid", 196 | 197 | "ino", 198 | "size", 199 | "mode", 200 | "uid", 201 | "gid", 202 | "rdev", 203 | "ctime", 204 | "mtime", 205 | "atime", 206 | "otime", 207 | 208 | "xattrname", 209 | "xattrdata", 210 | 211 | "path", 212 | "pathto", 213 | "pathlink", 214 | 215 | "fileoffset", 216 | "data", 217 | 218 | "cloneuuid", 219 | "clonectransid", 220 | "clonepath", 221 | "cloneoffset", 222 | "clonelen", 223 | 224 | "", 225 | } 226 | -------------------------------------------------------------------------------- /usage.go: -------------------------------------------------------------------------------- 1 | package btrfs 2 | 3 | import ( 4 | "os" 5 | "sort" 6 | "syscall" 7 | ) 8 | 9 | func cmpChunkBlockGroup(f1, f2 blockGroup) int { 10 | var mask blockGroup 11 | 12 | if (f1 & _BTRFS_BLOCK_GROUP_TYPE_MASK) == 13 | (f2 & _BTRFS_BLOCK_GROUP_TYPE_MASK) { 14 | mask = _BTRFS_BLOCK_GROUP_PROFILE_MASK 15 | } else if f2&blockGroupSystem != 0 { 16 | return -1 17 | } else if f1&blockGroupSystem != 0 { 18 | return +1 19 | } else { 20 | mask = _BTRFS_BLOCK_GROUP_TYPE_MASK 21 | } 22 | 23 | if (f1 & mask) > (f2 & mask) { 24 | return +1 25 | } else if (f1 & mask) < (f2 & mask) { 26 | return -1 27 | } else { 28 | return 0 29 | } 30 | } 31 | 32 | type spaceInfoByBlockGroup []spaceInfo 33 | 34 | func (a spaceInfoByBlockGroup) Len() int { return len(a) } 35 | func (a spaceInfoByBlockGroup) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 36 | func (a spaceInfoByBlockGroup) Less(i, j int) bool { 37 | return cmpChunkBlockGroup(blockGroup(a[i].Flags), blockGroup(a[j].Flags)) < 0 38 | } 39 | 40 | type UsageInfo struct { 41 | Total uint64 42 | TotalUnused uint64 43 | TotalUsed uint64 44 | TotalChunks uint64 45 | 46 | FreeEstimated uint64 47 | FreeMin uint64 48 | 49 | LogicalDataChunks uint64 50 | RawDataChunks uint64 51 | RawDataUsed uint64 52 | 53 | LogicalMetaChunks uint64 54 | RawMetaChunks uint64 55 | RawMetaUsed uint64 56 | 57 | SystemUsed uint64 58 | SystemChunks uint64 59 | 60 | DataRatio float64 61 | MetadataRatio float64 62 | 63 | GlobalReserve uint64 64 | GlobalReserveUsed uint64 65 | } 66 | 67 | const minUnallocatedThreshold = 16 * 1024 * 1024 68 | 69 | func spaceUsage(f *os.File) (UsageInfo, error) { 70 | info, err := iocFsInfo(f) 71 | if err != nil { 72 | return UsageInfo{}, err 73 | } 74 | var u UsageInfo 75 | for i := uint64(0); i <= info.max_id; i++ { 76 | dev, err := iocDevInfo(f, i, UUID{}) 77 | if err == syscall.ENODEV { 78 | continue 79 | } else if err != nil { 80 | return UsageInfo{}, err 81 | } 82 | u.Total += dev.total_bytes 83 | } 84 | 85 | spaces, err := iocSpaceInfo(f) 86 | if err != nil { 87 | return UsageInfo{}, err 88 | } 89 | sort.Sort(spaceInfoByBlockGroup(spaces)) 90 | var ( 91 | maxDataRatio int = 1 92 | mixed bool 93 | ) 94 | for _, s := range spaces { 95 | ratio := 1 96 | bg := s.Flags.BlockGroup() 97 | switch { 98 | case bg&blockGroupRaid0 != 0: 99 | ratio = 1 100 | case bg&blockGroupRaid1 != 0: 101 | ratio = 2 102 | case bg&blockGroupRaid5 != 0: 103 | ratio = 0 104 | case bg&blockGroupRaid6 != 0: 105 | ratio = 0 106 | case bg&blockGroupDup != 0: 107 | ratio = 2 108 | case bg&blockGroupRaid10 != 0: 109 | ratio = 2 110 | } 111 | if ratio > maxDataRatio { 112 | maxDataRatio = ratio 113 | } 114 | if bg&spaceInfoGlobalRsv != 0 { 115 | u.GlobalReserve = s.TotalBytes 116 | u.GlobalReserveUsed = s.UsedBytes 117 | } 118 | if bg&(blockGroupData|blockGroupMetadata) == (blockGroupData | blockGroupMetadata) { 119 | mixed = true 120 | } 121 | if bg&blockGroupData != 0 { 122 | u.RawDataUsed += s.UsedBytes * uint64(ratio) 123 | u.RawDataChunks += s.TotalBytes * uint64(ratio) 124 | u.LogicalDataChunks += s.TotalBytes 125 | } 126 | if bg&blockGroupMetadata != 0 { 127 | u.RawMetaUsed += s.UsedBytes * uint64(ratio) 128 | u.RawMetaChunks += s.TotalBytes * uint64(ratio) 129 | u.LogicalMetaChunks += s.TotalBytes 130 | } 131 | if bg&blockGroupSystem != 0 { 132 | u.SystemUsed += s.UsedBytes * uint64(ratio) 133 | u.SystemChunks += s.TotalBytes * uint64(ratio) 134 | } 135 | } 136 | u.TotalChunks = u.RawDataChunks + u.SystemChunks 137 | u.TotalUsed = u.RawDataUsed + u.SystemUsed 138 | if !mixed { 139 | u.TotalChunks += u.RawMetaChunks 140 | u.TotalUsed += u.RawMetaUsed 141 | } 142 | u.TotalUnused = u.Total - u.TotalChunks 143 | 144 | u.DataRatio = float64(u.RawDataChunks) / float64(u.LogicalDataChunks) 145 | if mixed { 146 | u.MetadataRatio = u.DataRatio 147 | } else { 148 | u.MetadataRatio = float64(u.RawMetaChunks) / float64(u.LogicalMetaChunks) 149 | } 150 | 151 | // We're able to fill at least DATA for the unused space 152 | // 153 | // With mixed raid levels, this gives a rough estimate but more 154 | // accurate than just counting the logical free space 155 | // (l_data_chunks - l_data_used) 156 | // 157 | // In non-mixed case there's no difference. 158 | u.FreeEstimated = uint64(float64(u.RawDataChunks-u.RawDataUsed) / u.DataRatio) 159 | 160 | // For mixed-bg the metadata are left out in calculations thus global 161 | // reserve would be lost. Part of it could be permanently allocated, 162 | // we have to subtract the used bytes so we don't go under zero free. 163 | if mixed { 164 | u.FreeEstimated -= u.GlobalReserve - u.GlobalReserveUsed 165 | } 166 | u.FreeMin = u.FreeEstimated 167 | 168 | // Chop unallocatable space 169 | // FIXME: must be applied per device 170 | if u.TotalUnused >= minUnallocatedThreshold { 171 | u.FreeEstimated += uint64(float64(u.TotalUnused) / u.DataRatio) 172 | // Match the calculation of 'df', use the highest raid ratio 173 | u.FreeMin += u.TotalUnused / uint64(maxDataRatio) 174 | } 175 | return u, nil 176 | } 177 | -------------------------------------------------------------------------------- /internal/cmd/hgen.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "flag" 7 | "fmt" 8 | "io" 9 | "log" 10 | "os" 11 | "regexp" 12 | "strings" 13 | "unicode" 14 | ) 15 | 16 | var ( 17 | f_pkg = flag.String("p", "main", "package name for generated file") 18 | f_out = flag.String("o", "-", "output file") 19 | f_unexport = flag.Bool("u", true, "make all definitions unexported") 20 | f_goname = flag.Bool("g", true, "rename symbols to follow Go conventions") 21 | f_trim = flag.String("t", "", "prefix to trim from names") 22 | 23 | f_constSuf = flag.String("cs", "", "comma-separated list of constant suffixes to create typed constants") 24 | f_constPref = flag.String("cp", "", "comma-separated list of constant prefixes to create typed constants") 25 | ) 26 | 27 | var ( 28 | reDefineIntConst = regexp.MustCompile(`#define\s+([A-Za-z_][A-Za-z\d_]*)\s+(\(?-?\d+(?:U?LL)?(?:\s*<<\s*\d+)?\)?)`) 29 | reNegULL = regexp.MustCompile(`-(\d+)ULL`) 30 | ) 31 | 32 | var ( 33 | constTypes []constType 34 | ) 35 | 36 | type constType struct { 37 | Name string 38 | Type string 39 | Suffix string 40 | Prefix string 41 | } 42 | 43 | func constName(s string) string { 44 | s = strings.TrimPrefix(s, *f_trim) 45 | typ := "" 46 | for _, t := range constTypes { 47 | if t.Suffix != "" && strings.HasSuffix(s, t.Suffix) { 48 | //s = strings.TrimSuffix(s, t.Suffix) 49 | typ = t.Name 50 | break 51 | } else if t.Prefix != "" && strings.HasPrefix(s, t.Prefix) { 52 | typ = t.Name 53 | break 54 | } 55 | } 56 | if *f_goname { 57 | buf := bytes.NewBuffer(nil) 58 | buf.Grow(len(s)) 59 | up := !*f_unexport 60 | for _, r := range s { 61 | if r == '_' { 62 | up = true 63 | continue 64 | } 65 | if up { 66 | up = false 67 | r = unicode.ToUpper(r) 68 | } else { 69 | r = unicode.ToLower(r) 70 | } 71 | buf.WriteRune(r) 72 | } 73 | s = buf.String() 74 | } else if *f_unexport { 75 | s = "_" + s 76 | } 77 | if typ != "" { 78 | s += " " + typ 79 | } 80 | return s 81 | } 82 | 83 | func process(w io.Writer, path string) error { 84 | file, err := os.Open(path) 85 | if err != nil { 86 | return err 87 | } 88 | defer file.Close() 89 | r := bufio.NewReader(file) 90 | 91 | var ( 92 | comment = false 93 | firstComment = true 94 | firstLineInComment = false 95 | ) 96 | 97 | nl := true 98 | defer fmt.Fprintln(w, ")") 99 | for { 100 | line, err := r.ReadBytes('\n') 101 | if err == io.EOF { 102 | return nil 103 | } else if err != nil { 104 | return err 105 | } 106 | line = bytes.TrimSpace(line) 107 | if len(line) == 0 { 108 | if !nl { 109 | nl = true 110 | w.Write([]byte("\n")) 111 | } 112 | continue 113 | } 114 | nl = false 115 | 116 | if bytes.HasPrefix(line, []byte("/*")) { 117 | comment = true 118 | firstLineInComment = true 119 | line = bytes.TrimPrefix(line, []byte("/*")) 120 | } 121 | if comment { 122 | ends := bytes.HasSuffix(line, []byte("*/")) 123 | if ends { 124 | comment = false 125 | line = bytes.TrimSuffix(line, []byte("*/")) 126 | } 127 | line = bytes.TrimLeft(line, " \t*") 128 | if len(line) > 0 { 129 | if !firstComment { 130 | w.Write([]byte("\t")) 131 | } 132 | w.Write([]byte("// ")) 133 | if firstLineInComment { 134 | line[0] = byte(unicode.ToUpper(rune(line[0]))) 135 | } 136 | line = bytes.Replace(line, []byte(" "), []byte(" "), -1) 137 | w.Write(line) 138 | w.Write([]byte("\n")) 139 | firstLineInComment = false 140 | } 141 | if ends && firstComment { 142 | firstComment = false 143 | fmt.Fprint(w, "\nconst (\n") 144 | nl = true 145 | } 146 | firstLineInComment = firstLineInComment && !ends 147 | continue 148 | } 149 | if bytes.HasPrefix(line, []byte("#define")) { 150 | sub := reDefineIntConst.FindStringSubmatch(string(line)) 151 | if len(sub) > 0 { 152 | name, val := sub[1], sub[2] 153 | if sub := reNegULL.FindAllStringSubmatch(val, -1); len(sub) > 0 { 154 | for _, s := range sub { 155 | val = strings.Replace(val, s[0], fmt.Sprintf("(1<<64 - %s)", s[1]), -1) 156 | } 157 | } 158 | val = strings.Replace(val, "ULL", "", -1) 159 | fmt.Fprintf(w, "\t%s = %s\n", constName(name), val) 160 | continue 161 | } 162 | } 163 | } 164 | } 165 | 166 | func regConstTypes(str string, fnc func(*constType, string)) { 167 | for _, s := range strings.Split(str, ",") { 168 | kv := strings.Split(s, "=") 169 | if len(kv) != 2 { 170 | continue 171 | } 172 | st := strings.Split(kv[0], ":") 173 | typ := "int" 174 | if len(st) > 1 { 175 | typ = st[1] 176 | } 177 | t := constType{Name: st[0], Type: typ} 178 | fnc(&t, kv[1]) 179 | constTypes = append(constTypes, t) 180 | } 181 | } 182 | 183 | func main() { 184 | flag.Parse() 185 | if suf := *f_constSuf; suf != "" { 186 | regConstTypes(suf, func(t *constType, v string) { t.Suffix = v }) 187 | } 188 | if pref := *f_constPref; pref != "" { 189 | regConstTypes(pref, func(t *constType, v string) { t.Prefix = v }) 190 | } 191 | var w io.Writer = os.Stdout 192 | if path := *f_out; path != "" && path != "-" { 193 | file, err := os.Create(path) 194 | if err != nil { 195 | log.Fatal(err) 196 | } 197 | defer file.Close() 198 | w = file 199 | } 200 | 201 | fmt.Fprintf(w, "package %s\n\n", *f_pkg) 202 | fmt.Fprint(w, "// This code was auto-generated; DO NOT EDIT!\n\n") 203 | for _, t := range constTypes { 204 | fmt.Fprintf(w, "type %s %s\n\n", t.Name, t.Type) 205 | } 206 | for _, path := range flag.Args() { 207 | if err := process(w, path); err != nil { 208 | log.Fatal(err) 209 | } 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /btrfs_test.go: -------------------------------------------------------------------------------- 1 | package btrfs 2 | 3 | import ( 4 | "github.com/dennwc/btrfs/test" 5 | "io" 6 | "io/ioutil" 7 | "os" 8 | "path/filepath" 9 | "reflect" 10 | "sort" 11 | "testing" 12 | ) 13 | 14 | const sizeDef = 256 * 1024 * 1024 15 | 16 | func TestOpen(t *testing.T) { 17 | dir, closer := btrfstest.New(t, sizeDef) 18 | defer closer() 19 | fs, err := Open(dir, true) 20 | if err != nil { 21 | t.Fatal(err) 22 | } 23 | if err = fs.Close(); err != nil { 24 | t.Fatal(err) 25 | } 26 | } 27 | 28 | func TestIsSubvolume(t *testing.T) { 29 | dir, closer := btrfstest.New(t, sizeDef) 30 | defer closer() 31 | 32 | isSubvol := func(path string, expect bool) { 33 | ok, err := IsSubVolume(path) 34 | if err != nil { 35 | t.Errorf("failed to check subvolume %v: %v", path, err) 36 | return 37 | } else if ok != expect { 38 | t.Errorf("unexpected result for %v", path) 39 | } 40 | } 41 | mkdir := func(path string) { 42 | path = filepath.Join(dir, path) 43 | if err := os.MkdirAll(path, 0755); err != nil { 44 | t.Fatalf("cannot create dir %v: %v", path, err) 45 | } 46 | isSubvol(path, false) 47 | } 48 | 49 | mksub := func(path string) { 50 | path = filepath.Join(dir, path) 51 | if err := CreateSubVolume(path); err != nil { 52 | t.Fatalf("cannot create subvolume %v: %v", path, err) 53 | } 54 | isSubvol(path, true) 55 | } 56 | 57 | mksub("v1") 58 | 59 | mkdir("v1/d2") 60 | mksub("v1/v2") 61 | 62 | mkdir("v1/d2/d3") 63 | mksub("v1/d2/v3") 64 | 65 | mkdir("v1/v2/d3") 66 | mksub("v1/v2/v3") 67 | 68 | mkdir("d1") 69 | 70 | mkdir("d1/d2") 71 | mksub("d1/v2") 72 | 73 | mkdir("d1/d2/d3") 74 | mksub("d1/d2/v3") 75 | 76 | mkdir("d1/v2/d3") 77 | mksub("d1/v2/v3") 78 | } 79 | 80 | func TestSubvolumes(t *testing.T) { 81 | dir, closer := btrfstest.New(t, sizeDef) 82 | defer closer() 83 | fs, err := Open(dir, false) 84 | if err != nil { 85 | t.Fatal(err) 86 | } 87 | defer fs.Close() 88 | 89 | mksub := func(in string, path string) { 90 | if in != "" { 91 | path = filepath.Join(dir, in, path) 92 | } else { 93 | path = filepath.Join(dir, path) 94 | } 95 | if err := CreateSubVolume(path); err != nil { 96 | t.Fatalf("cannot create subvolume %v: %v", path, err) 97 | } 98 | } 99 | delsub := func(path string) { 100 | path = filepath.Join(dir, path) 101 | if err := DeleteSubVolume(path); err != nil { 102 | t.Fatalf("cannot delete subvolume %v: %v", path, err) 103 | } 104 | } 105 | expect := func(exp []string) { 106 | subs, err := fs.ListSubvolumes(nil) 107 | if err != nil { 108 | t.Fatal(err) 109 | } 110 | var got []string 111 | for _, s := range subs { 112 | if s.UUID.IsZero() { 113 | t.Fatalf("zero uuid in %+v", s) 114 | } 115 | if s.Path != "" { 116 | got = append(got, s.Path) 117 | } 118 | } 119 | sort.Strings(got) 120 | sort.Strings(exp) 121 | if !reflect.DeepEqual(got, exp) { 122 | t.Fatalf("list failed:\ngot: %v\nvs\nexp: %v", got, exp) 123 | } 124 | } 125 | 126 | names := []string{"foo", "bar", "baz"} 127 | for _, name := range names { 128 | mksub("", name) 129 | } 130 | for _, name := range names { 131 | mksub(names[0], name) 132 | } 133 | expect([]string{ 134 | "foo", "bar", "baz", 135 | "foo/foo", "foo/bar", "foo/baz", 136 | }) 137 | delsub("foo/bar") 138 | expect([]string{ 139 | "foo", "bar", "baz", 140 | "foo/foo", "foo/baz", 141 | }) 142 | 143 | path := filepath.Join(names[0], names[2]) 144 | mksub(path, "new") 145 | path = filepath.Join(path, "new") 146 | 147 | id, err := getPathRootID(filepath.Join(dir, path)) 148 | if err != nil { 149 | t.Fatal(err) 150 | } 151 | info, err := subvolSearchByRootID(fs.f, id, "") 152 | if err != nil { 153 | t.Fatal(err) 154 | } else if info.Path != path { 155 | t.Fatalf("wrong path returned: %v vs %v", info.Path, path) 156 | } 157 | } 158 | 159 | func TestCompression(t *testing.T) { 160 | dir, closer := btrfstest.New(t, sizeDef) 161 | defer closer() 162 | fs, err := Open(dir, true) 163 | if err != nil { 164 | t.Fatal(err) 165 | } 166 | defer fs.Close() 167 | if err := fs.CreateSubVolume("sub"); err != nil { 168 | t.Fatal(err) 169 | } 170 | path := filepath.Join(dir, "sub") 171 | 172 | if err := SetCompression(path, LZO); err != nil { 173 | t.Fatal(err) 174 | } 175 | if c, err := GetCompression(path); err != nil { 176 | t.Fatal(err) 177 | } else if c != LZO { 178 | t.Fatalf("unexpected compression returned: %q", string(c)) 179 | } 180 | } 181 | 182 | func TestCloneFile(t *testing.T) { 183 | dir, closer := btrfstest.New(t, sizeDef) 184 | defer closer() 185 | 186 | f1, err := os.Create(filepath.Join(dir, "1.dat")) 187 | if err != nil { 188 | t.Fatal(err) 189 | } 190 | defer f1.Close() 191 | 192 | const data = "btrfs_test" 193 | _, err = f1.WriteString(data) 194 | if err != nil { 195 | t.Fatal(err) 196 | } 197 | 198 | f2, err := os.Create(filepath.Join(dir, "2.dat")) 199 | if err != nil { 200 | t.Fatal(err) 201 | } 202 | defer f2.Close() 203 | 204 | err = CloneFile(f2, f1) 205 | if err != nil { 206 | t.Fatal(err) 207 | } 208 | 209 | buf := make([]byte, len(data)) 210 | n, err := f2.Read(buf) 211 | if err != nil && err != io.EOF { 212 | t.Fatal(err) 213 | } 214 | buf = buf[:n] 215 | if string(buf) != data { 216 | t.Fatalf("wrong data returned: %q", string(buf)) 217 | } 218 | } 219 | 220 | func TestResize(t *testing.T) { 221 | dir, err := ioutil.TempDir("", "btrfs_data_") 222 | if err != nil { 223 | t.Fatal(err) 224 | } 225 | defer os.RemoveAll(dir) 226 | fname := filepath.Join(dir, "data") 227 | if err = btrfstest.Mkfs(fname, sizeDef); err != nil { 228 | t.Fatal(err) 229 | } 230 | mnt := filepath.Join(dir, "mnt") 231 | if err = os.MkdirAll(mnt, 0755); err != nil { 232 | t.Fatal(err) 233 | } 234 | if err = btrfstest.Mount(mnt, fname); err != nil { 235 | t.Fatal(err) 236 | } 237 | defer btrfstest.Unmount(mnt) 238 | 239 | fs, err := Open(mnt, false) 240 | if err != nil { 241 | t.Fatal(err) 242 | } 243 | st, err := fs.Usage() 244 | fs.Close() 245 | if err != nil { 246 | t.Fatal(err) 247 | } 248 | 249 | if err = btrfstest.Unmount(mnt); err != nil { 250 | t.Fatal(err) 251 | } 252 | var newSize int64 = sizeDef 253 | newSize = int64(float64(newSize) * 1.1) 254 | if err = os.Truncate(fname, newSize); err != nil { 255 | t.Fatal(err) 256 | } 257 | if err = btrfstest.Mount(mnt, fname); err != nil { 258 | t.Fatal(err) 259 | } 260 | 261 | fs, err = Open(mnt, false) 262 | if err != nil { 263 | t.Fatal(err) 264 | } 265 | defer fs.Close() 266 | 267 | if err = fs.ResizeToMax(); err != nil { 268 | t.Fatal(err) 269 | } 270 | 271 | st2, err := fs.Usage() 272 | if err != nil { 273 | t.Fatal(err) 274 | } else if st.Total >= st2.Total { 275 | t.Fatal("to resized:", st.Total, st2.Total) 276 | } 277 | } 278 | -------------------------------------------------------------------------------- /send.go: -------------------------------------------------------------------------------- 1 | package btrfs 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "os" 7 | "path/filepath" 8 | "unsafe" 9 | ) 10 | 11 | func Send(w io.Writer, parent string, subvols ...string) error { 12 | if len(subvols) == 0 { 13 | return nil 14 | } 15 | // use first send subvol to determine mount_root 16 | subvol, err := filepath.Abs(subvols[0]) 17 | if err != nil { 18 | return err 19 | } 20 | mountRoot, err := findMountRoot(subvol) 21 | if err == os.ErrNotExist { 22 | return fmt.Errorf("cannot find a mountpoint for %s", subvol) 23 | } else if err != nil { 24 | return err 25 | } 26 | var ( 27 | cloneSrc []objectID 28 | parentID objectID 29 | ) 30 | if parent != "" { 31 | parent, err = filepath.Abs(parent) 32 | if err != nil { 33 | return err 34 | } 35 | id, err := getPathRootID(parent) 36 | if err != nil { 37 | return fmt.Errorf("cannot get parent root id: %v", err) 38 | } 39 | parentID = id 40 | cloneSrc = append(cloneSrc, id) 41 | } 42 | // check all subvolumes 43 | paths := make([]string, 0, len(subvols)) 44 | for _, sub := range subvols { 45 | sub, err = filepath.Abs(sub) 46 | if err != nil { 47 | return err 48 | } 49 | paths = append(paths, sub) 50 | mount, err := findMountRoot(sub) 51 | if err != nil { 52 | return fmt.Errorf("cannot find mount root for %v: %v", sub, err) 53 | } else if mount != mountRoot { 54 | return fmt.Errorf("all subvolumes must be from the same filesystem (%s is not)", sub) 55 | } 56 | ok, err := IsReadOnly(sub) 57 | if err != nil { 58 | return err 59 | } else if !ok { 60 | return fmt.Errorf("subvolume %s is not read-only", sub) 61 | } 62 | } 63 | mfs, err := Open(mountRoot, true) 64 | if err != nil { 65 | return err 66 | } 67 | defer mfs.Close() 68 | full := len(cloneSrc) == 0 69 | for i, sub := range paths { 70 | var rootID objectID 71 | if !full && parent != "" { 72 | rel, err := filepath.Rel(mountRoot, sub) 73 | if err != nil { 74 | return err 75 | } 76 | si, err := subvolSearchByPath(mfs.f, rel) 77 | if err != nil { 78 | return fmt.Errorf("cannot find subvolume %s: %v", rel, err) 79 | } 80 | rootID = objectID(si.RootID) 81 | parentID, err = findGoodParent(mfs.f, rootID, cloneSrc) 82 | if err != nil { 83 | return fmt.Errorf("cannot find good parent for %v: %v", rel, err) 84 | } 85 | } 86 | fs, err := Open(sub, true) 87 | if err != nil { 88 | return err 89 | } 90 | var flags uint64 91 | if i != 0 { // not first 92 | flags |= _BTRFS_SEND_FLAG_OMIT_STREAM_HEADER 93 | } 94 | if i < len(paths)-1 { // not last 95 | flags |= _BTRFS_SEND_FLAG_OMIT_END_CMD 96 | } 97 | err = send(w, fs.f, parentID, cloneSrc, flags) 98 | fs.Close() 99 | if err != nil { 100 | return fmt.Errorf("error sending %s: %v", sub, err) 101 | } 102 | if !full && parent != "" { 103 | cloneSrc = append(cloneSrc, rootID) 104 | } 105 | } 106 | return nil 107 | } 108 | 109 | func send(w io.Writer, subvol *os.File, parent objectID, sources []objectID, flags uint64) error { 110 | pr, pw, err := os.Pipe() 111 | if err != nil { 112 | return err 113 | } 114 | errc := make(chan error, 1) 115 | go func() { 116 | defer pr.Close() 117 | _, err := io.Copy(w, pr) 118 | errc <- err 119 | }() 120 | fd := pw.Fd() 121 | wait := func() error { 122 | pw.Close() 123 | return <-errc 124 | } 125 | args := &btrfs_ioctl_send_args{ 126 | send_fd: int64(fd), 127 | parent_root: parent, 128 | flags: flags, 129 | } 130 | if len(sources) != 0 { 131 | args.clone_sources = &sources[0] 132 | args.clone_sources_count = uint64(len(sources)) 133 | } 134 | if err := iocSend(subvol, args); err != nil { 135 | wait() 136 | return err 137 | } 138 | return wait() 139 | } 140 | 141 | // readRootItem reads a root item from the tree. 142 | // 143 | // TODO(dennwc): support older kernels: 144 | // In case we detect a root item smaller then sizeof(root_item), 145 | // we know it's an old version of the root structure and initialize all new fields to zero. 146 | // The same happens if we detect mismatching generation numbers as then we know the root was 147 | // once mounted with an older kernel that was not aware of the root item structure change. 148 | func readRootItem(mnt *os.File, rootID objectID) (*rootItem, error) { 149 | sk := btrfs_ioctl_search_key{ 150 | tree_id: rootTreeObjectid, 151 | // There may be more than one ROOT_ITEM key if there are 152 | // snapshots pending deletion, we have to loop through them. 153 | min_objectid: rootID, 154 | max_objectid: rootID, 155 | min_type: rootItemKey, 156 | max_type: rootItemKey, 157 | max_offset: maxUint64, 158 | max_transid: maxUint64, 159 | nr_items: 4096, 160 | } 161 | for ; sk.min_offset < maxUint64; sk.min_offset++ { 162 | results, err := treeSearchRaw(mnt, sk) 163 | if err != nil { 164 | return nil, err 165 | } else if len(results) == 0 { 166 | break 167 | } 168 | for _, r := range results { 169 | sk.min_objectid = r.ObjectID 170 | sk.min_type = r.Type 171 | sk.min_offset = r.Offset 172 | if r.ObjectID > rootID { 173 | break 174 | } 175 | if r.ObjectID == rootID && r.Type == rootItemKey { 176 | const sz = int(unsafe.Sizeof(btrfs_root_item_raw{})) 177 | if len(r.Data) > sz { 178 | return nil, fmt.Errorf("btrfs_root_item is larger than expected; kernel is newer than the library") 179 | } else if len(r.Data) < sz { // TODO 180 | return nil, fmt.Errorf("btrfs_root_item is smaller then expected; kernel version is too old") 181 | } 182 | p := asRootItem(r.Data).Decode() 183 | return &p, nil 184 | } 185 | } 186 | results = nil 187 | if sk.min_type != rootItemKey || sk.min_objectid != rootID { 188 | break 189 | } 190 | } 191 | return nil, ErrNotFound 192 | } 193 | 194 | func getParent(mnt *os.File, rootID objectID) (*SubvolInfo, error) { 195 | st, err := subvolSearchByRootID(mnt, rootID, "") 196 | if err != nil { 197 | return nil, fmt.Errorf("cannot find subvolume %d to determine parent: %v", rootID, err) 198 | } 199 | return subvolSearchByUUID(mnt, st.ParentUUID) 200 | } 201 | 202 | func findGoodParent(mnt *os.File, rootID objectID, cloneSrc []objectID) (objectID, error) { 203 | parent, err := getParent(mnt, rootID) 204 | if err != nil { 205 | return 0, fmt.Errorf("get parent failed: %v", err) 206 | } 207 | for _, id := range cloneSrc { 208 | if id == objectID(parent.RootID) { 209 | return objectID(parent.RootID), nil 210 | } 211 | } 212 | var ( 213 | bestParent *SubvolInfo 214 | bestDiff uint64 = maxUint64 215 | ) 216 | for _, id := range cloneSrc { 217 | parent2, err := getParent(mnt, id) 218 | if err == ErrNotFound { 219 | continue 220 | } else if err != nil { 221 | return 0, err 222 | } 223 | if parent2.RootID != parent.RootID { 224 | continue 225 | } 226 | parent2, err = subvolSearchByRootID(mnt, id, "") 227 | if err != nil { 228 | return 0, err 229 | } 230 | diff := int64(parent2.CTransID - parent.CTransID) 231 | if diff < 0 { 232 | diff = -diff 233 | } 234 | if uint64(diff) < bestDiff { 235 | bestParent, bestDiff = parent2, uint64(diff) 236 | } 237 | } 238 | if bestParent != nil { 239 | return objectID(bestParent.RootID), nil 240 | } 241 | if !parent.ParentUUID.IsZero() { 242 | return findGoodParent(mnt, objectID(parent.RootID), cloneSrc) 243 | } 244 | return 0, ErrNotFound 245 | } 246 | -------------------------------------------------------------------------------- /btrfs.go: -------------------------------------------------------------------------------- 1 | package btrfs 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "os" 7 | "path/filepath" 8 | "strconv" 9 | "syscall" 10 | 11 | "github.com/dennwc/ioctl" 12 | ) 13 | 14 | const SuperMagic uint32 = 0x9123683E 15 | 16 | func CloneFile(dst, src *os.File) error { 17 | return iocClone(dst, src) 18 | } 19 | 20 | func Open(path string, ro bool) (*FS, error) { 21 | if ok, err := IsSubVolume(path); err != nil { 22 | return nil, err 23 | } else if !ok { 24 | return nil, ErrNotBtrfs{Path: path} 25 | } 26 | var ( 27 | dir *os.File 28 | err error 29 | ) 30 | if ro { 31 | dir, err = os.OpenFile(path, os.O_RDONLY|syscall.O_NOATIME, 0644) 32 | if err != nil { 33 | // Try without O_NOATIME as it requires ownership of the file 34 | // or other priviliges 35 | dir, err = os.OpenFile(path, os.O_RDONLY, 0644) 36 | } 37 | } else { 38 | dir, err = os.Open(path) 39 | } 40 | if err != nil { 41 | return nil, err 42 | } else if st, err := dir.Stat(); err != nil { 43 | dir.Close() 44 | return nil, err 45 | } else if !st.IsDir() { 46 | dir.Close() 47 | return nil, fmt.Errorf("not a directory: %s", path) 48 | } 49 | return &FS{f: dir}, nil 50 | } 51 | 52 | type FS struct { 53 | f *os.File 54 | } 55 | 56 | func (f *FS) Close() error { 57 | return f.f.Close() 58 | } 59 | 60 | type Info struct { 61 | MaxID uint64 62 | NumDevices uint64 63 | FSID FSID 64 | NodeSize uint32 65 | SectorSize uint32 66 | CloneAlignment uint32 67 | } 68 | 69 | func (f *FS) SubVolumeID() (uint64, error) { 70 | id, err := getFileRootID(f.f) 71 | if err != nil { 72 | return 0, err 73 | } 74 | return uint64(id), nil 75 | } 76 | 77 | func (f *FS) Info() (out Info, err error) { 78 | var arg btrfs_ioctl_fs_info_args 79 | arg, err = iocFsInfo(f.f) 80 | if err == nil { 81 | out = Info{ 82 | MaxID: arg.max_id, 83 | NumDevices: arg.num_devices, 84 | FSID: arg.fsid, 85 | NodeSize: arg.nodesize, 86 | SectorSize: arg.sectorsize, 87 | CloneAlignment: arg.clone_alignment, 88 | } 89 | } 90 | return 91 | } 92 | 93 | type DevInfo struct { 94 | UUID UUID 95 | BytesUsed uint64 96 | TotalBytes uint64 97 | Path string 98 | } 99 | 100 | func (f *FS) GetDevInfo(id uint64) (out DevInfo, err error) { 101 | var arg btrfs_ioctl_dev_info_args 102 | arg.devid = id 103 | 104 | if err = ioctl.Do(f.f, _BTRFS_IOC_DEV_INFO, &arg); err != nil { 105 | return 106 | } 107 | out.UUID = arg.uuid 108 | out.BytesUsed = arg.bytes_used 109 | out.TotalBytes = arg.total_bytes 110 | out.Path = stringFromBytes(arg.path[:]) 111 | 112 | return 113 | } 114 | 115 | type DevStats struct { 116 | WriteErrs uint64 117 | ReadErrs uint64 118 | FlushErrs uint64 119 | // Checksum error, bytenr error or contents is illegal: this is an 120 | // indication that the block was damaged during read or write, or written to 121 | // wrong location or read from wrong location. 122 | CorruptionErrs uint64 123 | // An indication that blocks have not been written. 124 | GenerationErrs uint64 125 | Unknown []uint64 126 | } 127 | 128 | func (f *FS) GetDevStats(id uint64) (out DevStats, err error) { 129 | var arg btrfs_ioctl_get_dev_stats 130 | arg.devid = id 131 | arg.nr_items = _BTRFS_DEV_STAT_VALUES_MAX 132 | arg.flags = 0 133 | if err = ioctl.Do(f.f, _BTRFS_IOC_GET_DEV_STATS, &arg); err != nil { 134 | return 135 | } 136 | i := 0 137 | out.WriteErrs = arg.values[i] 138 | i++ 139 | out.ReadErrs = arg.values[i] 140 | i++ 141 | out.FlushErrs = arg.values[i] 142 | i++ 143 | out.CorruptionErrs = arg.values[i] 144 | i++ 145 | out.GenerationErrs = arg.values[i] 146 | i++ 147 | if int(arg.nr_items) > i { 148 | out.Unknown = arg.values[i:arg.nr_items] 149 | } 150 | return 151 | } 152 | 153 | type FSFeatureFlags struct { 154 | Compatible FeatureFlags 155 | CompatibleRO FeatureFlags 156 | Incompatible IncompatFeatures 157 | } 158 | 159 | func (f *FS) GetFeatures() (out FSFeatureFlags, err error) { 160 | var arg btrfs_ioctl_feature_flags 161 | if err = ioctl.Do(f.f, _BTRFS_IOC_GET_FEATURES, &arg); err != nil { 162 | return 163 | } 164 | out = FSFeatureFlags{ 165 | Compatible: arg.compat_flags, 166 | CompatibleRO: arg.compat_ro_flags, 167 | Incompatible: arg.incompat_flags, 168 | } 169 | return 170 | } 171 | 172 | func (f *FS) GetSupportedFeatures() (out FSFeatureFlags, err error) { 173 | var arg [3]btrfs_ioctl_feature_flags 174 | if err = ioctl.Do(f.f, _BTRFS_IOC_GET_SUPPORTED_FEATURES, &arg); err != nil { 175 | return 176 | } 177 | out = FSFeatureFlags{ 178 | Compatible: arg[0].compat_flags, 179 | CompatibleRO: arg[0].compat_ro_flags, 180 | Incompatible: arg[0].incompat_flags, 181 | } 182 | //for i, a := range arg { 183 | // out[i] = FSFeatureFlags{ 184 | // Compatible: a.compat_flags, 185 | // CompatibleRO: a.compat_ro_flags, 186 | // Incompatible: a.incompat_flags, 187 | // } 188 | //} 189 | return 190 | } 191 | 192 | func (f *FS) GetFlags() (SubvolFlags, error) { 193 | return iocSubvolGetflags(f.f) 194 | } 195 | 196 | func (f *FS) SetFlags(flags SubvolFlags) error { 197 | return iocSubvolSetflags(f.f, flags) 198 | } 199 | 200 | func (f *FS) Sync() (err error) { 201 | if err = ioctl.Ioctl(f.f, _BTRFS_IOC_START_SYNC, 0); err != nil { 202 | return 203 | } 204 | return ioctl.Ioctl(f.f, _BTRFS_IOC_WAIT_SYNC, 0) 205 | } 206 | 207 | func (f *FS) CreateSubVolume(name string) error { 208 | return CreateSubVolume(filepath.Join(f.f.Name(), name)) 209 | } 210 | 211 | func (f *FS) DeleteSubVolume(name string) error { 212 | return DeleteSubVolume(filepath.Join(f.f.Name(), name)) 213 | } 214 | 215 | func (f *FS) Snapshot(dst string, ro bool) error { 216 | return SnapshotSubVolume(f.f.Name(), filepath.Join(f.f.Name(), dst), ro) 217 | } 218 | 219 | func (f *FS) SnapshotSubVolume(name string, dst string, ro bool) error { 220 | return SnapshotSubVolume(filepath.Join(f.f.Name(), name), 221 | filepath.Join(f.f.Name(), dst), ro) 222 | } 223 | 224 | func (f *FS) Send(w io.Writer, parent string, subvols ...string) error { 225 | if parent != "" { 226 | parent = filepath.Join(f.f.Name(), parent) 227 | } 228 | sub := make([]string, 0, len(subvols)) 229 | for _, s := range subvols { 230 | sub = append(sub, filepath.Join(f.f.Name(), s)) 231 | } 232 | return Send(w, parent, sub...) 233 | } 234 | 235 | func (f *FS) Receive(r io.Reader) error { 236 | return Receive(r, f.f.Name()) 237 | } 238 | 239 | func (f *FS) ReceiveTo(r io.Reader, mount string) error { 240 | return Receive(r, filepath.Join(f.f.Name(), mount)) 241 | } 242 | 243 | func (f *FS) ListSubvolumes(filter func(SubvolInfo) bool) ([]SubvolInfo, error) { 244 | m, err := listSubVolumes(f.f, filter) 245 | if err != nil { 246 | return nil, err 247 | } 248 | out := make([]SubvolInfo, 0, len(m)) 249 | for _, v := range m { 250 | out = append(out, v) 251 | } 252 | return out, nil 253 | } 254 | 255 | func (f *FS) SubvolumeByUUID(uuid UUID) (*SubvolInfo, error) { 256 | id, err := lookupUUIDSubvolItem(f.f, uuid) 257 | if err != nil { 258 | return nil, err 259 | } 260 | return subvolSearchByRootID(f.f, id, "") 261 | } 262 | 263 | func (f *FS) SubvolumeByReceivedUUID(uuid UUID) (*SubvolInfo, error) { 264 | id, err := lookupUUIDReceivedSubvolItem(f.f, uuid) 265 | if err != nil { 266 | return nil, err 267 | } 268 | return subvolSearchByRootID(f.f, id, "") 269 | } 270 | 271 | func (f *FS) SubvolumeByPath(path string) (*SubvolInfo, error) { 272 | return subvolSearchByPath(f.f, path) 273 | } 274 | 275 | func (f *FS) Usage() (UsageInfo, error) { return spaceUsage(f.f) } 276 | 277 | func (f *FS) Balance(flags BalanceFlags) (BalanceProgress, error) { 278 | args := btrfs_ioctl_balance_args{flags: flags} 279 | err := iocBalanceV2(f.f, &args) 280 | return args.stat, err 281 | } 282 | 283 | func (f *FS) Resize(size int64) error { 284 | amount := strconv.FormatInt(size, 10) 285 | args := &btrfs_ioctl_vol_args{} 286 | args.SetName(amount) 287 | if err := iocResize(f.f, args); err != nil { 288 | return fmt.Errorf("resize failed: %v", err) 289 | } 290 | return nil 291 | } 292 | 293 | func (f *FS) ResizeToMax() error { 294 | args := &btrfs_ioctl_vol_args{} 295 | args.SetName("max") 296 | if err := iocResize(f.f, args); err != nil { 297 | return fmt.Errorf("resize failed: %v", err) 298 | } 299 | return nil 300 | } 301 | -------------------------------------------------------------------------------- /btrfs_tree.go: -------------------------------------------------------------------------------- 1 | package btrfs 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | "unsafe" 7 | ) 8 | 9 | const ( 10 | _BTRFS_BLOCK_GROUP_TYPE_MASK = (blockGroupData | 11 | blockGroupSystem | 12 | blockGroupMetadata) 13 | _BTRFS_BLOCK_GROUP_PROFILE_MASK = (blockGroupRaid0 | 14 | blockGroupRaid1 | 15 | blockGroupRaid5 | 16 | blockGroupRaid6 | 17 | blockGroupDup | 18 | blockGroupRaid10) 19 | _BTRFS_BLOCK_GROUP_MASK = _BTRFS_BLOCK_GROUP_TYPE_MASK | _BTRFS_BLOCK_GROUP_PROFILE_MASK 20 | ) 21 | 22 | type rootRef struct { 23 | DirID objectID 24 | Sequence uint64 25 | Name string 26 | } 27 | 28 | func (rootRef) btrfsSize() int { return 18 } 29 | 30 | func asUint64(p []byte) uint64 { 31 | return *(*uint64)(unsafe.Pointer(&p[0])) 32 | } 33 | 34 | func asUint32(p []byte) uint32 { 35 | return *(*uint32)(unsafe.Pointer(&p[0])) 36 | } 37 | 38 | func asUint16(p []byte) uint16 { 39 | return *(*uint16)(unsafe.Pointer(&p[0])) 40 | } 41 | 42 | func asRootRef(p []byte) rootRef { 43 | const sz = 18 44 | // assuming that it is highly unsafe to have sizeof(struct) > len(data) 45 | // (*btrfs_root_ref)(unsafe.Pointer(&p[0])) and sizeof(btrfs_root_ref) == 24 46 | ref := rootRef{ 47 | DirID: objectID(asUint64(p[0:])), 48 | Sequence: asUint64(p[8:]), 49 | } 50 | if n := asUint16(p[16:]); n > 0 { 51 | ref.Name = string(p[sz : sz+n : sz+n]) 52 | } 53 | return ref 54 | } 55 | 56 | var treeKeyNames = map[treeKeyType]string{ 57 | inodeItemKey: "inodeItem", 58 | inodeRefKey: "inodeRef", 59 | inodeExtrefKey: "inodeExtref", 60 | xattrItemKey: "xattrItemKey", 61 | orphanItemKey: "orphanItem", 62 | dirLogItemKey: "dirLogItem", 63 | dirLogIndexKey: "dirLogIndex", 64 | dirItemKey: "dirItem", 65 | dirIndexKey: "dirIndex", 66 | extentDataKey: "extentData", 67 | extentCsumKey: "extentCsum", 68 | rootItemKey: "rootItem", 69 | rootBackrefKey: "rootBackref", 70 | rootRefKey: "rootRef", 71 | extentItemKey: "extentItem", 72 | metadataItemKey: "metadataItem", 73 | treeBlockRefKey: "treeBlockRef", 74 | extentDataRefKey: "extentDataRef", 75 | extentRefV0Key: "extentRefV0", 76 | sharedBlockRefKey: "sharedBlockRef", 77 | sharedDataRefKey: "sharedDataRef", 78 | blockGroupItemKey: "blockGroupItem", 79 | freeSpaceInfoKey: "freeSpaceInfo", 80 | freeSpaceExtentKey: "freeSpaceExtent", 81 | freeSpaceBitmapKey: "freeSpaceBitmap", 82 | devExtentKey: "devExtent", 83 | devItemKey: "devItem", 84 | chunkItemKey: "chunkItem", 85 | qgroupStatusKey: "qgroupStatus", 86 | qgroupInfoKey: "qgroupInfo", 87 | qgroupLimitKey: "qgroupLimit", 88 | qgroupRelationKey: "qgroupRelation", 89 | temporaryItemKey: "temporaryItem", 90 | persistentItemKey: "persistentItem", 91 | devReplaceKey: "devReplace", 92 | uuidKeySubvol: "uuidKeySubvol", 93 | uuidKeyReceivedSubvol: "uuidKeyReceivedSubvol", 94 | stringItemKey: "stringItem", 95 | } 96 | 97 | func (t treeKeyType) String() string { 98 | if name, ok := treeKeyNames[t]; ok { 99 | return name 100 | } 101 | return fmt.Sprintf("%#x", int(t)) 102 | } 103 | 104 | // btrfs_disk_key_raw is a raw bytes for btrfs_disk_key structure 105 | type btrfs_disk_key_raw [17]byte 106 | 107 | func (p btrfs_disk_key_raw) Decode() diskKey { 108 | return diskKey{ 109 | ObjectID: asUint64(p[0:]), 110 | Type: p[8], 111 | Offset: asUint64(p[9:]), 112 | } 113 | } 114 | 115 | type diskKey struct { 116 | ObjectID uint64 117 | Type byte 118 | Offset uint64 119 | } 120 | 121 | // btrfs_timespec_raw is a raw bytes for btrfs_timespec structure. 122 | type btrfs_timespec_raw [12]byte 123 | 124 | func (t btrfs_timespec_raw) Decode() time.Time { 125 | sec, nsec := asUint64(t[0:]), asUint32(t[8:]) 126 | return time.Unix(int64(sec), int64(nsec)) 127 | } 128 | 129 | // timeBlock is a raw set of bytes for 4 time fields. 130 | // It is used to keep correct alignment when accessing structures from btrfs. 131 | type timeBlock [4]btrfs_timespec_raw 132 | 133 | type btrfs_inode_item_raw struct { 134 | generation uint64 135 | transid uint64 136 | size uint64 137 | nbytes uint64 138 | block_group uint64 139 | nlink uint32 140 | uid uint32 141 | gid uint32 142 | mode uint32 143 | rdev uint64 144 | flags uint64 145 | sequence uint64 146 | _ [4]uint64 // reserved 147 | // atime btrfs_timespec 148 | // ctime btrfs_timespec 149 | // mtime btrfs_timespec 150 | // otime btrfs_timespec 151 | times timeBlock 152 | } 153 | 154 | func (v btrfs_inode_item_raw) Decode() inodeItem { 155 | return inodeItem{ 156 | Gen: v.generation, 157 | TransID: v.transid, 158 | Size: v.size, 159 | NBytes: v.nbytes, 160 | BlockGroup: v.block_group, 161 | NLink: v.nlink, 162 | UID: v.uid, 163 | GID: v.gid, 164 | Mode: v.mode, 165 | RDev: v.rdev, 166 | Flags: v.flags, 167 | Sequence: v.sequence, 168 | ATime: v.times[0].Decode(), 169 | CTime: v.times[1].Decode(), 170 | MTime: v.times[2].Decode(), 171 | OTime: v.times[3].Decode(), 172 | } 173 | } 174 | 175 | type inodeItem struct { 176 | Gen uint64 // nfs style generation number 177 | TransID uint64 // transid that last touched this inode 178 | Size uint64 179 | NBytes uint64 180 | BlockGroup uint64 181 | NLink uint32 182 | UID uint32 183 | GID uint32 184 | Mode uint32 185 | RDev uint64 186 | Flags uint64 187 | Sequence uint64 // modification sequence number for NFS 188 | ATime time.Time 189 | CTime time.Time 190 | MTime time.Time 191 | OTime time.Time 192 | } 193 | 194 | func asRootItem(p []byte) *btrfs_root_item_raw { 195 | return (*btrfs_root_item_raw)(unsafe.Pointer(&p[0])) 196 | } 197 | 198 | type btrfs_root_item_raw [439]byte 199 | 200 | func (p btrfs_root_item_raw) Decode() rootItem { 201 | const ( 202 | off2 = unsafe.Sizeof(btrfs_root_item_raw_p1{}) 203 | off3 = off2 + 23 204 | ) 205 | p1 := (*btrfs_root_item_raw_p1)(unsafe.Pointer(&p[0])) 206 | p2 := p[off2 : off2+23] 207 | p2_k := (*btrfs_disk_key_raw)(unsafe.Pointer(&p[off2+4])) 208 | p2_b := p2[4+17:] 209 | p3 := (*btrfs_root_item_raw_p3)(unsafe.Pointer(&p[off3])) 210 | return rootItem{ 211 | Inode: p1.inode.Decode(), 212 | Gen: p1.generation, 213 | RootDirID: p1.root_dirid, 214 | ByteNr: p1.bytenr, 215 | ByteLimit: p1.byte_limit, 216 | BytesUsed: p1.bytes_used, 217 | LastSnapshot: p1.last_snapshot, 218 | Flags: p1.flags, 219 | // from here, Go structure become misaligned with C structure 220 | Refs: asUint32(p2[0:]), 221 | DropProgress: p2_k.Decode(), 222 | DropLevel: p2_b[0], 223 | Level: p2_b[1], 224 | // these fields are still misaligned by 1 bytes 225 | // TODO(dennwc): it's a copy of Gen to check structure version; hide it maybe? 226 | GenV2: p3.generation_v2, 227 | UUID: p3.uuid, 228 | ParentUUID: p3.parent_uuid, 229 | ReceivedUUID: p3.received_uuid, 230 | CTransID: p3.ctransid, 231 | OTransID: p3.otransid, 232 | STransID: p3.stransid, 233 | RTransID: p3.rtransid, 234 | CTime: p3.times[0].Decode(), 235 | OTime: p3.times[1].Decode(), 236 | STime: p3.times[2].Decode(), 237 | RTime: p3.times[3].Decode(), 238 | } 239 | } 240 | 241 | type rootItem struct { 242 | Inode inodeItem 243 | Gen uint64 244 | RootDirID uint64 245 | ByteNr uint64 246 | ByteLimit uint64 247 | BytesUsed uint64 248 | LastSnapshot uint64 249 | Flags uint64 250 | Refs uint32 251 | DropProgress diskKey 252 | DropLevel uint8 253 | Level uint8 254 | GenV2 uint64 255 | UUID UUID 256 | ParentUUID UUID 257 | ReceivedUUID UUID 258 | CTransID uint64 259 | OTransID uint64 260 | STransID uint64 261 | RTransID uint64 262 | CTime time.Time 263 | OTime time.Time 264 | STime time.Time 265 | RTime time.Time 266 | } 267 | 268 | type btrfs_root_item_raw_p1 struct { 269 | inode btrfs_inode_item_raw 270 | generation uint64 271 | root_dirid uint64 272 | bytenr uint64 273 | byte_limit uint64 274 | bytes_used uint64 275 | last_snapshot uint64 276 | flags uint64 277 | } 278 | type btrfs_root_item_raw_p2 struct { 279 | refs uint32 280 | drop_progress btrfs_disk_key_raw 281 | drop_level uint8 282 | level uint8 283 | } 284 | type btrfs_root_item_raw_p3 struct { 285 | generation_v2 uint64 286 | uuid UUID 287 | parent_uuid UUID 288 | received_uuid UUID 289 | ctransid uint64 290 | otransid uint64 291 | stransid uint64 292 | rtransid uint64 293 | // ctime btrfs_timespec 294 | // otime btrfs_timespec 295 | // stime btrfs_timespec 296 | // rtime btrfs_timespec 297 | times timeBlock 298 | _ [8]uint64 // reserved 299 | } 300 | -------------------------------------------------------------------------------- /subvolume.go: -------------------------------------------------------------------------------- 1 | package btrfs 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path/filepath" 7 | "strings" 8 | "syscall" 9 | "time" 10 | ) 11 | 12 | func checkSubVolumeName(name string) bool { 13 | return name != "" && name[0] != 0 && !strings.ContainsRune(name, '/') && 14 | name != "." && name != ".." 15 | } 16 | 17 | func IsSubVolume(path string) (bool, error) { 18 | var st syscall.Stat_t 19 | if err := syscall.Stat(path, &st); err != nil { 20 | return false, &os.PathError{Op: "stat", Path: path, Err: err} 21 | } 22 | if objectID(st.Ino) != firstFreeObjectid || 23 | st.Mode&syscall.S_IFMT != syscall.S_IFDIR { 24 | return false, nil 25 | } 26 | return isBtrfs(path) 27 | } 28 | 29 | func CreateSubVolume(path string) error { 30 | var inherit *btrfs_qgroup_inherit // TODO 31 | 32 | cpath, err := filepath.Abs(path) 33 | if err != nil { 34 | return err 35 | } 36 | newName := filepath.Base(cpath) 37 | dstDir := filepath.Dir(cpath) 38 | if !checkSubVolumeName(newName) { 39 | return fmt.Errorf("invalid subvolume name: %s", newName) 40 | } else if len(newName) >= volNameMax { 41 | return fmt.Errorf("subvolume name too long: %s", newName) 42 | } 43 | dst, err := openDir(dstDir) 44 | if err != nil { 45 | return err 46 | } 47 | defer dst.Close() 48 | if inherit != nil { 49 | panic("not implemented") // TODO 50 | args := btrfs_ioctl_vol_args_v2{ 51 | flags: subvolQGroupInherit, 52 | btrfs_ioctl_vol_args_v2_u1: btrfs_ioctl_vol_args_v2_u1{ 53 | //size: qgroup_inherit_size(inherit), 54 | qgroup_inherit: inherit, 55 | }, 56 | } 57 | copy(args.name[:], newName) 58 | return iocSubvolCreateV2(dst, &args) 59 | } 60 | var args btrfs_ioctl_vol_args 61 | copy(args.name[:], newName) 62 | return iocSubvolCreate(dst, &args) 63 | } 64 | 65 | func DeleteSubVolume(path string) error { 66 | if ok, err := IsSubVolume(path); err != nil { 67 | return err 68 | } else if !ok { 69 | return fmt.Errorf("not a subvolume: %s", path) 70 | } 71 | cpath, err := filepath.Abs(path) 72 | if err != nil { 73 | return err 74 | } 75 | dname := filepath.Dir(cpath) 76 | vname := filepath.Base(cpath) 77 | 78 | dir, err := openDir(dname) 79 | if err != nil { 80 | return err 81 | } 82 | defer dir.Close() 83 | var args btrfs_ioctl_vol_args 84 | copy(args.name[:], vname) 85 | return iocSnapDestroy(dir, &args) 86 | } 87 | 88 | func SnapshotSubVolume(subvol, dst string, ro bool) error { 89 | if ok, err := IsSubVolume(subvol); err != nil { 90 | return err 91 | } else if !ok { 92 | return fmt.Errorf("not a subvolume: %s", subvol) 93 | } 94 | exists := false 95 | if st, err := os.Stat(dst); err != nil && !os.IsNotExist(err) { 96 | return err 97 | } else if err == nil { 98 | if !st.IsDir() { 99 | return fmt.Errorf("'%s' exists and it is not a directory", dst) 100 | } 101 | exists = true 102 | } 103 | var ( 104 | newName string 105 | dstDir string 106 | ) 107 | if exists { 108 | newName = filepath.Base(subvol) 109 | dstDir = dst 110 | } else { 111 | newName = filepath.Base(dst) 112 | dstDir = filepath.Dir(dst) 113 | } 114 | if !checkSubVolumeName(newName) { 115 | return fmt.Errorf("invalid snapshot name '%s'", newName) 116 | } else if len(newName) >= volNameMax { 117 | return fmt.Errorf("snapshot name too long '%s'", newName) 118 | } 119 | fdst, err := openDir(dstDir) 120 | if err != nil { 121 | return err 122 | } 123 | defer fdst.Close() 124 | // TODO: make SnapshotSubVolume a method on FS to use existing fd 125 | f, err := openDir(subvol) 126 | if err != nil { 127 | return fmt.Errorf("cannot open dest dir: %v", err) 128 | } 129 | defer f.Close() 130 | args := btrfs_ioctl_vol_args_v2{ 131 | fd: int64(f.Fd()), 132 | } 133 | if ro { 134 | args.flags |= SubvolReadOnly 135 | } 136 | // TODO 137 | //if inherit != nil { 138 | // args.flags |= subvolQGroupInherit 139 | // args.size = qgroup_inherit_size(inherit) 140 | // args.qgroup_inherit = inherit 141 | //} 142 | copy(args.name[:], newName) 143 | if err := iocSnapCreateV2(fdst, &args); err != nil { 144 | return fmt.Errorf("snapshot create failed: %v", err) 145 | } 146 | return nil 147 | } 148 | 149 | func IsReadOnly(path string) (bool, error) { 150 | f, err := GetFlags(path) 151 | if err != nil { 152 | return false, err 153 | } 154 | return f.ReadOnly(), nil 155 | } 156 | 157 | func GetFlags(path string) (SubvolFlags, error) { 158 | fs, err := Open(path, true) 159 | if err != nil { 160 | return 0, err 161 | } 162 | defer fs.Close() 163 | return fs.GetFlags() 164 | } 165 | 166 | func listSubVolumes(f *os.File, filter func(SubvolInfo) bool) (map[objectID]SubvolInfo, error) { 167 | sk := btrfs_ioctl_search_key{ 168 | // search in the tree of tree roots 169 | tree_id: rootTreeObjectid, 170 | 171 | // Set the min and max to backref keys. The search will 172 | // only send back this type of key now. 173 | min_type: rootItemKey, 174 | max_type: rootBackrefKey, 175 | 176 | min_objectid: firstFreeObjectid, 177 | 178 | // Set all the other params to the max, we'll take any objectid 179 | // and any trans. 180 | max_objectid: lastFreeObjectid, 181 | max_offset: maxUint64, 182 | max_transid: maxUint64, 183 | 184 | nr_items: 4096, // just a big number, doesn't matter much 185 | } 186 | m := make(map[objectID]SubvolInfo) 187 | for { 188 | out, err := treeSearchRaw(f, sk) 189 | if err != nil { 190 | return nil, err 191 | } else if len(out) == 0 { 192 | break 193 | } 194 | for _, obj := range out { 195 | switch obj.Type { 196 | //case rootBackrefKey: 197 | // ref := asRootRef(obj.Data) 198 | // o := m[obj.ObjectID] 199 | // o.TransID = obj.TransID 200 | // o.ObjectID = obj.ObjectID 201 | // o.RefTree = obj.Offset 202 | // o.DirID = ref.DirID 203 | // o.Name = ref.Name 204 | // m[obj.ObjectID] = o 205 | case rootItemKey: 206 | o := m[obj.ObjectID] 207 | o.RootID = uint64(obj.ObjectID) 208 | robj := asRootItem(obj.Data).Decode() 209 | o.fillFromItem(&robj) 210 | m[obj.ObjectID] = o 211 | } 212 | } 213 | // record the mins in key so we can make sure the 214 | // next search doesn't repeat this root 215 | last := out[len(out)-1] 216 | sk.min_objectid = last.ObjectID 217 | sk.min_type = last.Type 218 | sk.min_offset = last.Offset + 1 219 | if sk.min_offset == 0 { // overflow 220 | sk.min_type++ 221 | } else { 222 | continue 223 | } 224 | if sk.min_type > rootBackrefKey { 225 | sk.min_type = rootItemKey 226 | sk.min_objectid++ 227 | } else { 228 | continue 229 | } 230 | if sk.min_objectid > sk.max_objectid { 231 | break 232 | } 233 | } 234 | // resolve paths 235 | for id, v := range m { 236 | if path, err := subvolidResolve(f, id); err == ErrNotFound { 237 | delete(m, id) 238 | continue 239 | } else if err != nil { 240 | return m, fmt.Errorf("cannot resolve path for %v: %v", id, err) 241 | } else { 242 | v.Path = path 243 | m[id] = v 244 | } 245 | if filter != nil && !filter(v) { 246 | delete(m, id) 247 | } 248 | } 249 | 250 | return m, nil 251 | } 252 | 253 | type SubvolInfo struct { 254 | RootID uint64 255 | 256 | Flags SubvolFlags 257 | 258 | UUID UUID 259 | ParentUUID UUID 260 | ReceivedUUID UUID 261 | 262 | CTime time.Time 263 | OTime time.Time 264 | STime time.Time 265 | RTime time.Time 266 | 267 | CTransID uint64 268 | OTransID uint64 269 | STransID uint64 270 | RTransID uint64 271 | 272 | Path string 273 | } 274 | 275 | func (s *SubvolInfo) fillFromItem(it *rootItem) { 276 | s.UUID = it.UUID 277 | s.ReceivedUUID = it.ReceivedUUID 278 | s.ParentUUID = it.ParentUUID 279 | s.Flags = SubvolFlags(it.Flags) 280 | 281 | s.CTime = it.CTime 282 | s.OTime = it.OTime 283 | s.STime = it.STime 284 | s.RTime = it.RTime 285 | 286 | s.CTransID = it.CTransID 287 | s.OTransID = it.OTransID 288 | s.STransID = it.STransID 289 | s.RTransID = it.RTransID 290 | } 291 | 292 | func subvolSearchByUUID(mnt *os.File, uuid UUID) (*SubvolInfo, error) { 293 | id, err := lookupUUIDSubvolItem(mnt, uuid) 294 | if err != nil { 295 | return nil, err 296 | } 297 | return subvolSearchByRootID(mnt, id, "") 298 | } 299 | 300 | func subvolSearchByReceivedUUID(mnt *os.File, uuid UUID) (*SubvolInfo, error) { 301 | id, err := lookupUUIDReceivedSubvolItem(mnt, uuid) 302 | if err != nil { 303 | return nil, err 304 | } 305 | return subvolSearchByRootID(mnt, id, "") 306 | } 307 | 308 | func subvolSearchByPath(mnt *os.File, path string) (*SubvolInfo, error) { 309 | if !filepath.IsAbs(path) { 310 | path = filepath.Join(mnt.Name(), path) 311 | } 312 | id, err := getPathRootID(path) 313 | if err != nil { 314 | return nil, err 315 | } 316 | return subvolSearchByRootID(mnt, id, path) 317 | } 318 | 319 | func subvolidResolve(mnt *os.File, subvolID objectID) (string, error) { 320 | return subvolidResolveSub(mnt, "", subvolID) 321 | } 322 | 323 | func subvolidResolveSub(mnt *os.File, path string, subvolID objectID) (string, error) { 324 | if subvolID == fsTreeObjectid { 325 | return "", nil 326 | } 327 | sk := btrfs_ioctl_search_key{ 328 | tree_id: rootTreeObjectid, 329 | min_objectid: subvolID, 330 | max_objectid: subvolID, 331 | min_type: rootBackrefKey, 332 | max_type: rootBackrefKey, 333 | max_offset: maxUint64, 334 | max_transid: maxUint64, 335 | nr_items: 1, 336 | } 337 | results, err := treeSearchRaw(mnt, sk) 338 | if err != nil { 339 | return "", err 340 | } else if len(results) < 1 { 341 | return "", ErrNotFound 342 | } 343 | res := results[0] 344 | if objectID(res.Offset) != fsTreeObjectid { 345 | spath, err := subvolidResolveSub(mnt, path, objectID(res.Offset)) 346 | if err != nil { 347 | return "", err 348 | } 349 | path = spath + "/" 350 | } 351 | backRef := asRootRef(res.Data) 352 | if backRef.DirID != firstFreeObjectid { 353 | arg := btrfs_ioctl_ino_lookup_args{ 354 | treeid: objectID(res.Offset), 355 | objectid: backRef.DirID, 356 | } 357 | if err := iocInoLookup(mnt, &arg); err != nil { 358 | return "", err 359 | } 360 | path += arg.Name() 361 | } 362 | return path + backRef.Name, nil 363 | } 364 | 365 | // subvolSearchByRootID 366 | // 367 | // Path is optional, and will be resolved automatically if not set. 368 | func subvolSearchByRootID(mnt *os.File, rootID objectID, path string) (*SubvolInfo, error) { 369 | robj, err := readRootItem(mnt, rootID) 370 | if err != nil { 371 | return nil, err 372 | } 373 | info := &SubvolInfo{ 374 | RootID: uint64(rootID), 375 | Path: path, 376 | } 377 | info.fillFromItem(robj) 378 | if path == "" { 379 | info.Path, err = subvolidResolve(mnt, objectID(info.RootID)) 380 | } 381 | return info, err 382 | } 383 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /send/send.go: -------------------------------------------------------------------------------- 1 | package send 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/dennwc/btrfs" 7 | "io" 8 | "io/ioutil" 9 | "time" 10 | ) 11 | 12 | func NewStreamReader(r io.Reader) (*StreamReader, error) { 13 | // read magic and version 14 | buf := make([]byte, len(sendStreamMagic)+4) 15 | _, err := io.ReadFull(r, buf) 16 | if err != nil { 17 | return nil, fmt.Errorf("cannot read magic: %v", err) 18 | } else if string(buf[:sendStreamMagicSize]) != sendStreamMagic { 19 | return nil, errors.New("unexpected stream header") 20 | } 21 | version := sendEndianess.Uint32(buf[sendStreamMagicSize:]) 22 | if version != sendStreamVersion { 23 | return nil, fmt.Errorf("stream version %d not supported", version) 24 | } 25 | return &StreamReader{r: r}, nil 26 | } 27 | 28 | type StreamReader struct { 29 | r io.Reader 30 | buf [cmdHeaderSize]byte 31 | } 32 | 33 | func (r *StreamReader) readCmdHeader() (h cmdHeader, err error) { 34 | _, err = io.ReadFull(r.r, r.buf[:cmdHeaderSize]) 35 | if err == io.EOF { 36 | return 37 | } else if err != nil { 38 | err = fmt.Errorf("cannot read command header: %v", err) 39 | return 40 | } 41 | err = h.Unmarshal(r.buf[:cmdHeaderSize]) 42 | // TODO: check CRC 43 | return 44 | } 45 | 46 | type SendTLV struct { 47 | Attr sendCmdAttr 48 | Val interface{} 49 | } 50 | 51 | func (r *StreamReader) readTLV(rd io.Reader) (*SendTLV, error) { 52 | _, err := io.ReadFull(rd, r.buf[:tlvHeaderSize]) 53 | if err == io.EOF { 54 | return nil, err 55 | } else if err != nil { 56 | return nil, fmt.Errorf("cannot read tlv header: %v", err) 57 | } 58 | var h tlvHeader 59 | if err = h.Unmarshal(r.buf[:tlvHeaderSize]); err != nil { 60 | return nil, err 61 | } 62 | typ := sendCmdAttr(h.Type) 63 | if sendCmdAttr(typ) > sendAttrMax { // || th.Len > _BTRFS_SEND_BUF_SIZE { 64 | return nil, fmt.Errorf("invalid tlv in cmd: %q", typ) 65 | } 66 | buf := make([]byte, h.Len) 67 | _, err = io.ReadFull(rd, buf) 68 | if err != nil { 69 | return nil, fmt.Errorf("cannot read tlv: %v", err) 70 | } 71 | var v interface{} 72 | switch typ { 73 | case sendAttrCtransid, sendAttrCloneCtransid, 74 | sendAttrUid, sendAttrGid, sendAttrMode, 75 | sendAttrIno, sendAttrFileOffset, sendAttrSize, 76 | sendAttrCloneOffset, sendAttrCloneLen: 77 | if len(buf) != 8 { 78 | return nil, fmt.Errorf("unexpected int64 size: %v", h.Len) 79 | } 80 | v = sendEndianess.Uint64(buf[:8]) 81 | case sendAttrPath, sendAttrPathTo, sendAttrClonePath, sendAttrXattrName: 82 | v = string(buf) 83 | case sendAttrData, sendAttrXattrData: 84 | v = buf 85 | case sendAttrUuid, sendAttrCloneUuid: 86 | if h.Len != btrfs.UUIDSize { 87 | return nil, fmt.Errorf("unexpected UUID size: %v", h.Len) 88 | } 89 | var u btrfs.UUID 90 | copy(u[:], buf) 91 | v = u 92 | case sendAttrAtime, sendAttrMtime, sendAttrCtime, sendAttrOtime: 93 | if h.Len != 12 { 94 | return nil, fmt.Errorf("unexpected timestamp size: %v", h.Len) 95 | } 96 | v = time.Unix( // btrfs_timespec 97 | int64(sendEndianess.Uint64(buf[:8])), 98 | int64(sendEndianess.Uint32(buf[8:])), 99 | ) 100 | default: 101 | return nil, fmt.Errorf("unsupported tlv type: %v (len: %v)", typ, h.Len) 102 | } 103 | return &SendTLV{Attr: typ, Val: v}, nil 104 | } 105 | func (r *StreamReader) ReadCommand() (_ Cmd, gerr error) { 106 | h, err := r.readCmdHeader() 107 | if err != nil { 108 | return nil, err 109 | } 110 | var tlvs []SendTLV 111 | rd := io.LimitReader(r.r, int64(h.Len)) 112 | defer io.Copy(ioutil.Discard, rd) 113 | for { 114 | tlv, err := r.readTLV(rd) 115 | if err == io.EOF { 116 | break 117 | } else if err != nil { 118 | return nil, fmt.Errorf("command %v: %v", h.Cmd, err) 119 | } 120 | tlvs = append(tlvs, *tlv) 121 | } 122 | var c Cmd 123 | switch h.Cmd { 124 | case sendCmdEnd: 125 | c = &StreamEnd{} 126 | case sendCmdSubvol: 127 | c = &SubvolCmd{} 128 | case sendCmdSnapshot: 129 | c = &SnapshotCmd{} 130 | case sendCmdChown: 131 | c = &ChownCmd{} 132 | case sendCmdChmod: 133 | c = &ChmodCmd{} 134 | case sendCmdUtimes: 135 | c = &UTimesCmd{} 136 | case sendCmdMkdir: 137 | c = &MkdirCmd{} 138 | case sendCmdRename: 139 | c = &RenameCmd{} 140 | case sendCmdMkfile: 141 | c = &MkfileCmd{} 142 | case sendCmdWrite: 143 | c = &WriteCmd{} 144 | case sendCmdTruncate: 145 | c = &TruncateCmd{} 146 | } 147 | if c == nil { 148 | return &UnknownSendCmd{Kind: h.Cmd, Params: tlvs}, nil 149 | } 150 | if err := c.decode(tlvs); err != nil { 151 | return nil, err 152 | } 153 | return c, nil 154 | } 155 | 156 | type errUnexpectedAttrType struct { 157 | Cmd CmdType 158 | Val SendTLV 159 | } 160 | 161 | func (e errUnexpectedAttrType) Error() string { 162 | return fmt.Sprintf("unexpected type for %q (in %q): %T", 163 | e.Val.Attr, e.Cmd, e.Val.Val) 164 | } 165 | 166 | type errUnexpectedAttr struct { 167 | Cmd CmdType 168 | Val SendTLV 169 | } 170 | 171 | func (e errUnexpectedAttr) Error() string { 172 | return fmt.Sprintf("unexpected attr %q for %q (%T)", 173 | e.Val.Attr, e.Cmd, e.Val.Val) 174 | } 175 | 176 | type Cmd interface { 177 | Type() CmdType 178 | decode(tlvs []SendTLV) error 179 | } 180 | 181 | type UnknownSendCmd struct { 182 | Kind CmdType 183 | Params []SendTLV 184 | } 185 | 186 | func (c UnknownSendCmd) Type() CmdType { 187 | return c.Kind 188 | } 189 | func (c *UnknownSendCmd) decode(tlvs []SendTLV) error { 190 | c.Params = tlvs 191 | return nil 192 | } 193 | 194 | type StreamEnd struct{} 195 | 196 | func (c StreamEnd) Type() CmdType { 197 | return sendCmdEnd 198 | } 199 | func (c *StreamEnd) decode(tlvs []SendTLV) error { 200 | if len(tlvs) != 0 { 201 | return fmt.Errorf("unexpected TLVs for stream end command: %#v", tlvs) 202 | } 203 | return nil 204 | } 205 | 206 | type SubvolCmd struct { 207 | Path string 208 | UUID btrfs.UUID 209 | CTransID uint64 210 | } 211 | 212 | func (c SubvolCmd) Type() CmdType { 213 | return sendCmdSubvol 214 | } 215 | func (c *SubvolCmd) decode(tlvs []SendTLV) error { 216 | for _, tlv := range tlvs { 217 | var ok bool 218 | switch tlv.Attr { 219 | case sendAttrPath: 220 | c.Path, ok = tlv.Val.(string) 221 | case sendAttrUuid: 222 | c.UUID, ok = tlv.Val.(btrfs.UUID) 223 | case sendAttrCtransid: 224 | c.CTransID, ok = tlv.Val.(uint64) 225 | default: 226 | return errUnexpectedAttr{Val: tlv, Cmd: c.Type()} 227 | } 228 | if !ok { 229 | return errUnexpectedAttrType{Val: tlv, Cmd: c.Type()} 230 | } 231 | } 232 | return nil 233 | } 234 | 235 | type SnapshotCmd struct { 236 | Path string 237 | UUID btrfs.UUID 238 | CTransID uint64 239 | CloneUUID btrfs.UUID 240 | CloneTransID uint64 241 | } 242 | 243 | func (c SnapshotCmd) Type() CmdType { 244 | return sendCmdSnapshot 245 | } 246 | func (c *SnapshotCmd) decode(tlvs []SendTLV) error { 247 | for _, tlv := range tlvs { 248 | var ok bool 249 | switch tlv.Attr { 250 | case sendAttrPath: 251 | c.Path, ok = tlv.Val.(string) 252 | case sendAttrUuid: 253 | c.UUID, ok = tlv.Val.(btrfs.UUID) 254 | case sendAttrCtransid: 255 | c.CTransID, ok = tlv.Val.(uint64) 256 | case sendAttrCloneUuid: 257 | c.CloneUUID, ok = tlv.Val.(btrfs.UUID) 258 | case sendAttrCloneCtransid: 259 | c.CloneTransID, ok = tlv.Val.(uint64) 260 | default: 261 | return errUnexpectedAttr{Val: tlv, Cmd: c.Type()} 262 | } 263 | if !ok { 264 | return errUnexpectedAttrType{Val: tlv, Cmd: c.Type()} 265 | } 266 | } 267 | return nil 268 | } 269 | 270 | type ChownCmd struct { 271 | Path string 272 | UID, GID uint64 273 | } 274 | 275 | func (c ChownCmd) Type() CmdType { 276 | return sendCmdChown 277 | } 278 | func (c *ChownCmd) decode(tlvs []SendTLV) error { 279 | for _, tlv := range tlvs { 280 | var ok bool 281 | switch tlv.Attr { 282 | case sendAttrPath: 283 | c.Path, ok = tlv.Val.(string) 284 | case sendAttrUid: 285 | c.UID, ok = tlv.Val.(uint64) 286 | case sendAttrGid: 287 | c.GID, ok = tlv.Val.(uint64) 288 | default: 289 | return errUnexpectedAttr{Val: tlv, Cmd: c.Type()} 290 | } 291 | if !ok { 292 | return errUnexpectedAttrType{Val: tlv, Cmd: c.Type()} 293 | } 294 | } 295 | return nil 296 | } 297 | 298 | type ChmodCmd struct { 299 | Path string 300 | Mode uint64 301 | } 302 | 303 | func (c ChmodCmd) Type() CmdType { 304 | return sendCmdChmod 305 | } 306 | func (c *ChmodCmd) decode(tlvs []SendTLV) error { 307 | for _, tlv := range tlvs { 308 | var ok bool 309 | switch tlv.Attr { 310 | case sendAttrPath: 311 | c.Path, ok = tlv.Val.(string) 312 | case sendAttrMode: 313 | c.Mode, ok = tlv.Val.(uint64) 314 | default: 315 | return errUnexpectedAttr{Val: tlv, Cmd: c.Type()} 316 | } 317 | if !ok { 318 | return errUnexpectedAttrType{Val: tlv, Cmd: c.Type()} 319 | } 320 | } 321 | return nil 322 | } 323 | 324 | type UTimesCmd struct { 325 | Path string 326 | ATime, MTime, CTime time.Time 327 | } 328 | 329 | func (c UTimesCmd) Type() CmdType { 330 | return sendCmdUtimes 331 | } 332 | func (c *UTimesCmd) decode(tlvs []SendTLV) error { 333 | for _, tlv := range tlvs { 334 | var ok bool 335 | switch tlv.Attr { 336 | case sendAttrPath: 337 | c.Path, ok = tlv.Val.(string) 338 | case sendAttrAtime: 339 | c.ATime, ok = tlv.Val.(time.Time) 340 | case sendAttrMtime: 341 | c.MTime, ok = tlv.Val.(time.Time) 342 | case sendAttrCtime: 343 | c.CTime, ok = tlv.Val.(time.Time) 344 | default: 345 | return errUnexpectedAttr{Val: tlv, Cmd: c.Type()} 346 | } 347 | if !ok { 348 | return errUnexpectedAttrType{Val: tlv, Cmd: c.Type()} 349 | } 350 | } 351 | return nil 352 | } 353 | 354 | type MkdirCmd struct { 355 | Path string 356 | Ino uint64 357 | } 358 | 359 | func (c MkdirCmd) Type() CmdType { 360 | return sendCmdMkdir 361 | } 362 | func (c *MkdirCmd) decode(tlvs []SendTLV) error { 363 | for _, tlv := range tlvs { 364 | var ok bool 365 | switch tlv.Attr { 366 | case sendAttrPath: 367 | c.Path, ok = tlv.Val.(string) 368 | case sendAttrIno: 369 | c.Ino, ok = tlv.Val.(uint64) 370 | default: 371 | return errUnexpectedAttr{Val: tlv, Cmd: c.Type()} 372 | } 373 | if !ok { 374 | return errUnexpectedAttrType{Val: tlv, Cmd: c.Type()} 375 | } 376 | } 377 | return nil 378 | } 379 | 380 | type RenameCmd struct { 381 | From, To string 382 | } 383 | 384 | func (c RenameCmd) Type() CmdType { 385 | return sendCmdRename 386 | } 387 | func (c *RenameCmd) decode(tlvs []SendTLV) error { 388 | for _, tlv := range tlvs { 389 | var ok bool 390 | switch tlv.Attr { 391 | case sendAttrPath: 392 | c.From, ok = tlv.Val.(string) 393 | case sendAttrPathTo: 394 | c.To, ok = tlv.Val.(string) 395 | default: 396 | return errUnexpectedAttr{Val: tlv, Cmd: c.Type()} 397 | } 398 | if !ok { 399 | return errUnexpectedAttrType{Val: tlv, Cmd: c.Type()} 400 | } 401 | } 402 | return nil 403 | } 404 | 405 | type MkfileCmd struct { 406 | Path string 407 | Ino uint64 408 | } 409 | 410 | func (c MkfileCmd) Type() CmdType { 411 | return sendCmdMkfile 412 | } 413 | func (c *MkfileCmd) decode(tlvs []SendTLV) error { 414 | for _, tlv := range tlvs { 415 | var ok bool 416 | switch tlv.Attr { 417 | case sendAttrPath: 418 | c.Path, ok = tlv.Val.(string) 419 | case sendAttrIno: 420 | c.Ino, ok = tlv.Val.(uint64) 421 | default: 422 | return errUnexpectedAttr{Val: tlv, Cmd: c.Type()} 423 | } 424 | if !ok { 425 | return errUnexpectedAttrType{Val: tlv, Cmd: c.Type()} 426 | } 427 | } 428 | return nil 429 | } 430 | 431 | type WriteCmd struct { 432 | Path string 433 | Off uint64 434 | Data []byte 435 | } 436 | 437 | func (c WriteCmd) Type() CmdType { 438 | return sendCmdWrite 439 | } 440 | func (c *WriteCmd) decode(tlvs []SendTLV) error { 441 | for _, tlv := range tlvs { 442 | var ok bool 443 | switch tlv.Attr { 444 | case sendAttrPath: 445 | c.Path, ok = tlv.Val.(string) 446 | case sendAttrFileOffset: 447 | c.Off, ok = tlv.Val.(uint64) 448 | case sendAttrData: 449 | c.Data, ok = tlv.Val.([]byte) 450 | default: 451 | return errUnexpectedAttr{Val: tlv, Cmd: c.Type()} 452 | } 453 | if !ok { 454 | return errUnexpectedAttrType{Val: tlv, Cmd: c.Type()} 455 | } 456 | } 457 | return nil 458 | } 459 | 460 | type TruncateCmd struct { 461 | Path string 462 | Size uint64 463 | } 464 | 465 | func (c TruncateCmd) Type() CmdType { 466 | return sendCmdTruncate 467 | } 468 | func (c *TruncateCmd) decode(tlvs []SendTLV) error { 469 | for _, tlv := range tlvs { 470 | var ok bool 471 | switch tlv.Attr { 472 | case sendAttrPath: 473 | c.Path, ok = tlv.Val.(string) 474 | case sendAttrSize: 475 | c.Size, ok = tlv.Val.(uint64) 476 | default: 477 | return errUnexpectedAttr{Val: tlv, Cmd: c.Type()} 478 | } 479 | if !ok { 480 | return errUnexpectedAttrType{Val: tlv, Cmd: c.Type()} 481 | } 482 | } 483 | return nil 484 | } 485 | -------------------------------------------------------------------------------- /btrfs_tree_hc.go: -------------------------------------------------------------------------------- 1 | package btrfs 2 | 3 | // This code was auto-generated; DO NOT EDIT! 4 | 5 | type treeKeyType uint32 6 | 7 | type objectID uint64 8 | 9 | type fileType int 10 | 11 | type fileExtentType int 12 | 13 | type devReplaceItemState int 14 | 15 | type blockGroup uint64 16 | 17 | // This header contains the structure definitions and constants used 18 | // by file system objects that can be retrieved using 19 | // the BTRFS_IOC_SEARCH_TREE ioctl. That means basically anything that 20 | // is needed to describe a leaf node's key or item contents. 21 | 22 | const ( 23 | // Holds pointers to all of the tree roots 24 | rootTreeObjectid objectID = 1 25 | 26 | // Stores information about which extents are in use, and reference counts 27 | extentTreeObjectid objectID = 2 28 | 29 | // Chunk tree stores translations from logical -> physical block numbering 30 | // the super block points to the chunk tree 31 | chunkTreeObjectid objectID = 3 32 | 33 | // Stores information about which areas of a given device are in use. 34 | // one per device. The tree of tree roots points to the device tree 35 | devTreeObjectid objectID = 4 36 | 37 | // One per subvolume, storing files and directories 38 | fsTreeObjectid objectID = 5 39 | 40 | // Directory objectid inside the root tree 41 | rootTreeDirObjectid objectID = 6 42 | 43 | // Holds checksums of all the data extents 44 | csumTreeObjectid objectID = 7 45 | 46 | // Holds quota configuration and tracking 47 | quotaTreeObjectid objectID = 8 48 | 49 | // For storing items that use the BTRFS_UUID_KEY* types 50 | uuidTreeObjectid objectID = 9 51 | 52 | // Tracks free space in block groups. 53 | freeSpaceTreeObjectid objectID = 10 54 | 55 | // Device stats in the device tree 56 | devStatsObjectid objectID = 0 57 | 58 | // For storing balance parameters in the root tree 59 | balanceObjectid objectID = (1<<64 - 4) 60 | 61 | // Orhpan objectid for tracking unlinked/truncated files 62 | orphanObjectid objectID = (1<<64 - 5) 63 | 64 | // Does write ahead logging to speed up fsyncs 65 | treeLogObjectid objectID = (1<<64 - 6) 66 | treeLogFixupObjectid objectID = (1<<64 - 7) 67 | 68 | // For space balancing 69 | treeRelocObjectid objectID = (1<<64 - 8) 70 | dataRelocTreeObjectid objectID = (1<<64 - 9) 71 | 72 | // Extent checksums all have this objectid 73 | // this allows them to share the logging tree 74 | // for fsyncs 75 | extentCsumObjectid objectID = (1<<64 - 10) 76 | 77 | // For storing free space cache 78 | freeSpaceObjectid objectID = (1<<64 - 11) 79 | 80 | // The inode number assigned to the special inode for storing 81 | // free ino cache 82 | freeInoObjectid objectID = (1<<64 - 12) 83 | 84 | // Dummy objectid represents multiple objectids 85 | multipleObjectids = (1<<64 - 255) 86 | 87 | // All files have objectids in this range. 88 | firstFreeObjectid objectID = 256 89 | lastFreeObjectid objectID = (1<<64 - 256) 90 | firstChunkTreeObjectid objectID = 256 91 | 92 | // The device items go into the chunk tree. The key is in the form 93 | // [ 1 BTRFS_DEV_ITEM_KEY device_id ] 94 | devItemsObjectid objectID = 1 95 | 96 | btreeInodeObjectid objectID = 1 97 | 98 | emptySubvolDirObjectid objectID = 2 99 | 100 | devReplaceDevid = 0 101 | 102 | // Inode items have the data typically returned from stat and store other 103 | // info about object characteristics. There is one for every file and dir in 104 | // the FS 105 | inodeItemKey treeKeyType = 1 106 | inodeRefKey treeKeyType = 12 107 | inodeExtrefKey treeKeyType = 13 108 | xattrItemKey treeKeyType = 24 109 | orphanItemKey treeKeyType = 48 110 | // Reserve 2-15 close to the inode for later flexibility 111 | 112 | // Dir items are the name -> inode pointers in a directory. There is one 113 | // for every name in a directory. 114 | dirLogItemKey treeKeyType = 60 115 | dirLogIndexKey treeKeyType = 72 116 | dirItemKey treeKeyType = 84 117 | dirIndexKey treeKeyType = 96 118 | // Extent data is for file data 119 | extentDataKey treeKeyType = 108 120 | 121 | // Extent csums are stored in a separate tree and hold csums for 122 | // an entire extent on disk. 123 | extentCsumKey treeKeyType = 128 124 | 125 | // Root items point to tree roots. They are typically in the root 126 | // tree used by the super block to find all the other trees 127 | rootItemKey treeKeyType = 132 128 | 129 | // Root backrefs tie subvols and snapshots to the directory entries that 130 | // reference them 131 | rootBackrefKey treeKeyType = 144 132 | 133 | // Root refs make a fast index for listing all of the snapshots and 134 | // subvolumes referenced by a given root. They point directly to the 135 | // directory item in the root that references the subvol 136 | rootRefKey treeKeyType = 156 137 | 138 | // Extent items are in the extent map tree. These record which blocks 139 | // are used, and how many references there are to each block 140 | extentItemKey treeKeyType = 168 141 | 142 | // The same as the BTRFS_EXTENT_ITEM_KEY, except it's metadata we already know 143 | // the length, so we save the level in key->offset instead of the length. 144 | metadataItemKey treeKeyType = 169 145 | 146 | treeBlockRefKey treeKeyType = 176 147 | 148 | extentDataRefKey treeKeyType = 178 149 | 150 | extentRefV0Key treeKeyType = 180 151 | 152 | sharedBlockRefKey treeKeyType = 182 153 | 154 | sharedDataRefKey treeKeyType = 184 155 | 156 | // Block groups give us hints into the extent allocation trees. Which 157 | // blocks are free etc etc 158 | blockGroupItemKey treeKeyType = 192 159 | 160 | // Every block group is represented in the free space tree by a free space info 161 | // item, which stores some accounting information. It is keyed on 162 | // (block_group_start, FREE_SPACE_INFO, block_group_length). 163 | freeSpaceInfoKey treeKeyType = 198 164 | 165 | // A free space extent tracks an extent of space that is free in a block group. 166 | // It is keyed on (start, FREE_SPACE_EXTENT, length). 167 | freeSpaceExtentKey treeKeyType = 199 168 | 169 | // When a block group becomes very fragmented, we convert it to use bitmaps 170 | // instead of extents. A free space bitmap is keyed on 171 | // (start, FREE_SPACE_BITMAP, length); the corresponding item is a bitmap with 172 | // (length / sectorsize) bits. 173 | freeSpaceBitmapKey treeKeyType = 200 174 | 175 | devExtentKey treeKeyType = 204 176 | devItemKey treeKeyType = 216 177 | chunkItemKey treeKeyType = 228 178 | 179 | // Records the overall state of the qgroups. 180 | // There's only one instance of this key present, 181 | // (0, BTRFS_QGROUP_STATUS_KEY, 0) 182 | qgroupStatusKey treeKeyType = 240 183 | // Records the currently used space of the qgroup. 184 | // One key per qgroup, (0, BTRFS_QGROUP_INFO_KEY, qgroupid). 185 | qgroupInfoKey treeKeyType = 242 186 | // Contains the user configured limits for the qgroup. 187 | // One key per qgroup, (0, BTRFS_QGROUP_LIMIT_KEY, qgroupid). 188 | qgroupLimitKey treeKeyType = 244 189 | // Records the child-parent relationship of qgroups. For 190 | // each relation, 2 keys are present: 191 | // (childid, BTRFS_QGROUP_RELATION_KEY, parentid) 192 | // (parentid, BTRFS_QGROUP_RELATION_KEY, childid) 193 | qgroupRelationKey treeKeyType = 246 194 | 195 | // Obsolete name, see BTRFS_TEMPORARY_ITEM_KEY. 196 | balanceItemKey treeKeyType = 248 197 | 198 | // The key type for tree items that are stored persistently, but do not need to 199 | // exist for extended period of time. The items can exist in any tree. 200 | // [subtype, BTRFS_TEMPORARY_ITEM_KEY, data] 201 | // Existing items: 202 | // - balance status item 203 | // (BTRFS_BALANCE_OBJECTID, BTRFS_TEMPORARY_ITEM_KEY, 0) 204 | temporaryItemKey treeKeyType = 248 205 | 206 | // Obsolete name, see BTRFS_PERSISTENT_ITEM_KEY 207 | devStatsKey treeKeyType = 249 208 | 209 | // The key type for tree items that are stored persistently and usually exist 210 | // for a long period, eg. filesystem lifetime. The item kinds can be status 211 | // information, stats or preference values. The item can exist in any tree. 212 | // [subtype, BTRFS_PERSISTENT_ITEM_KEY, data] 213 | // Existing items: 214 | // - device statistics, store IO stats in the device tree, one key for all 215 | // stats 216 | // (BTRFS_DEV_STATS_OBJECTID, BTRFS_DEV_STATS_KEY, 0) 217 | persistentItemKey treeKeyType = 249 218 | 219 | // Persistantly stores the device replace state in the device tree. 220 | // The key is built like this: (0, BTRFS_DEV_REPLACE_KEY, 0). 221 | devReplaceKey treeKeyType = 250 222 | 223 | // Stores items that allow to quickly map UUIDs to something else. 224 | // These items are part of the filesystem UUID tree. 225 | // The key is built like this: 226 | // (UUID_upper_64_bits, BTRFS_UUID_KEY*, UUID_lower_64_bits). 227 | uuidKeySubvol = 251 228 | uuidKeyReceivedSubvol = 252 229 | 230 | // String items are for debugging. They just store a short string of 231 | // data in the FS 232 | stringItemKey treeKeyType = 253 233 | 234 | // 32 bytes in various csum fields 235 | csumSize = 32 236 | 237 | // Csum types 238 | csumTypeCrc32 = 0 239 | 240 | // Flags definitions for directory entry item type 241 | // Used by: 242 | // struct btrfs_dir_item.type 243 | ftUnknown fileType = 0 244 | ftRegFile fileType = 1 245 | ftDir fileType = 2 246 | ftChrdev fileType = 3 247 | ftBlkdev fileType = 4 248 | ftFifo fileType = 5 249 | ftSock fileType = 6 250 | ftSymlink fileType = 7 251 | ftXattr fileType = 8 252 | ftMax fileType = 9 253 | 254 | // The key defines the order in the tree, and so it also defines (optimal) 255 | // block layout. 256 | // objectid corresponds to the inode number. 257 | // type tells us things about the object, and is a kind of stream selector. 258 | // so for a given inode, keys with type of 1 might refer to the inode data, 259 | // type of 2 may point to file data in the btree and type == 3 may point to 260 | // extents. 261 | // offset is the starting byte offset for this key in the stream. 262 | // btrfs_disk_key is in disk byte order. struct btrfs_key is always 263 | // in cpu native order. Otherwise they are identical and their sizes 264 | // should be the same (ie both packed) 265 | 266 | // The internal btrfs device id 267 | 268 | // Size of the device 269 | 270 | // Bytes used 271 | 272 | // Optimal io alignment for this device 273 | 274 | // Optimal io width for this device 275 | 276 | // Minimal io size for this device 277 | 278 | // Type and info about this device 279 | 280 | // Expected generation for this device 281 | 282 | // Starting byte of this partition on the device, 283 | // to allow for stripe alignment in the future 284 | 285 | // Grouping information for allocation decisions 286 | 287 | // Seek speed 0-100 where 100 is fastest 288 | 289 | // Bandwidth 0-100 where 100 is fastest 290 | 291 | // Btrfs generated uuid for this device 292 | 293 | // Uuid of FS who owns this device 294 | 295 | // Size of this chunk in bytes 296 | 297 | // Objectid of the root referencing this chunk 298 | 299 | // Optimal io alignment for this chunk 300 | 301 | // Optimal io width for this chunk 302 | 303 | // Minimal io size for this chunk 304 | 305 | // 2^16 stripes is quite a lot, a second limit is the size of a single 306 | // item in the btree 307 | 308 | // Sub stripes only matter for raid10 309 | // Additional stripes go here 310 | 311 | freeSpaceExtent = 1 312 | freeSpaceBitmap = 2 313 | 314 | headerFlagWritten = (1 << 0) 315 | headerFlagReloc = (1 << 1) 316 | 317 | // Super block flags 318 | // Errors detected 319 | superFlagError = (1 << 2) 320 | 321 | superFlagSeeding = (1 << 32) 322 | superFlagMetadump = (1 << 33) 323 | 324 | // Items in the extent btree are used to record the objectid of the 325 | // owner of the block and the number of references 326 | 327 | extentFlagData = (1 << 0) 328 | extentFlagTreeBlock = (1 << 1) 329 | 330 | // Following flags only apply to tree blocks 331 | 332 | // Use full backrefs for extent pointers in the block 333 | blockFlagFullBackref = (1 << 8) 334 | 335 | // This flag is only used internally by scrub and may be changed at any time 336 | // it is only declared here to avoid collisions 337 | extentFlagSuper = (1 << 48) 338 | 339 | // Old style backrefs item 340 | 341 | // Dev extents record free space on individual devices. The owner 342 | // field points back to the chunk allocation mapping tree that allocated 343 | // the extent. The chunk tree uuid field is a way to double check the owner 344 | 345 | // Name goes here 346 | 347 | // Name goes here 348 | 349 | // Nfs style generation number 350 | // Transid that last touched this inode 351 | 352 | // Modification sequence number for NFS 353 | 354 | // A little future expansion, for more than this we can 355 | // just grow the inode item and version it 356 | 357 | rootSubvolRdonly = (1 << 0) 358 | 359 | // Internal in-memory flag that a subvolume has been marked for deletion but 360 | // still visible as a directory 361 | rootSubvolDead = (1 << 48) 362 | 363 | // The following fields appear after subvol_uuids+subvol_times 364 | // were introduced. 365 | 366 | // This generation number is used to test if the new fields are valid 367 | // and up to date while reading the root item. Every time the root item 368 | // is written out, the "generation" field is copied into this field. If 369 | // anyone ever mounted the fs with an older kernel, we will have 370 | // mismatching generation values here and thus must invalidate the 371 | // new fields. See btrfs_update_root and btrfs_find_last_root for 372 | // details. 373 | // the offset of generation_v2 is also used as the start for the memset 374 | // when invalidating the fields. 375 | 376 | // This is used for both forward and backward root refs 377 | 378 | // Profiles to operate on, single is denoted by 379 | // BTRFS_AVAIL_ALLOC_BIT_SINGLE 380 | 381 | // Usage filter 382 | // BTRFS_BALANCE_ARGS_USAGE with a single value means '0..N' 383 | // BTRFS_BALANCE_ARGS_USAGE_RANGE - range syntax, min..max 384 | 385 | // Devid filter 386 | 387 | // Devid subset filter [pstart..pend) 388 | 389 | // Btrfs virtual address space subset filter [vstart..vend) 390 | 391 | // Profile to convert to, single is denoted by 392 | // BTRFS_AVAIL_ALLOC_BIT_SINGLE 393 | 394 | // BTRFS_BALANCE_ARGS_* 395 | 396 | // BTRFS_BALANCE_ARGS_LIMIT with value 'limit' 397 | // BTRFS_BALANCE_ARGS_LIMIT_RANGE - the extend version can use minimum 398 | // and maximum 399 | 400 | // Process chunks that cross stripes_min..stripes_max devices, 401 | // BTRFS_BALANCE_ARGS_STRIPES_RANGE 402 | 403 | // Store balance parameters to disk so that balance can be properly 404 | // resumed after crash or unmount 405 | // BTRFS_BALANCE_* 406 | 407 | fileExtentInline fileExtentType = 0 408 | fileExtentReg fileExtentType = 1 409 | fileExtentPrealloc fileExtentType = 2 410 | 411 | // Transaction id that created this extent 412 | // Max number of bytes to hold this extent in ram 413 | // when we split a compressed extent we can't know how big 414 | // each of the resulting pieces will be. So, this is 415 | // an upper limit on the size of the extent in ram instead of 416 | // an exact limit. 417 | 418 | // 32 bits for the various ways we might encode the data, 419 | // including compression and encryption. If any of these 420 | // are set to something a given disk format doesn't understand 421 | // it is treated like an incompat flag for reading and writing, 422 | // but not for stat. 423 | 424 | // Are we inline data or a real extent? 425 | 426 | // Disk space consumed by the extent, checksum blocks are included 427 | // in these numbers 428 | // At this offset in the structure, the inline extent data start. 429 | // The logical offset in file blocks (no csums) 430 | // this extent record is for. This allows a file extent to point 431 | // into the middle of an existing extent on disk, sharing it 432 | // between two snapshots (useful if some bytes in the middle of the 433 | // extent have changed 434 | // The logical number of file blocks (no csums included). This 435 | // always reflects the size uncompressed and without encoding. 436 | 437 | // Grow this item struct at the end for future enhancements and keep 438 | // the existing values unchanged 439 | 440 | devReplaceItemContReadingFromSrcdevModeAlways = 0 441 | devReplaceItemContReadingFromSrcdevModeAvoid = 1 442 | devReplaceItemStateNeverStarted devReplaceItemState = 0 443 | devReplaceItemStateStarted devReplaceItemState = 1 444 | devReplaceItemStateSuspended devReplaceItemState = 2 445 | devReplaceItemStateFinished devReplaceItemState = 3 446 | devReplaceItemStateCanceled devReplaceItemState = 4 447 | 448 | // Grow this item struct at the end for future enhancements and keep 449 | // the existing values unchanged 450 | 451 | // Different types of block groups (and chunks) 452 | blockGroupData blockGroup = (1 << 0) 453 | blockGroupSystem blockGroup = (1 << 1) 454 | blockGroupMetadata blockGroup = (1 << 2) 455 | blockGroupRaid0 blockGroup = (1 << 3) 456 | blockGroupRaid1 blockGroup = (1 << 4) 457 | blockGroupDup blockGroup = (1 << 5) 458 | blockGroupRaid10 blockGroup = (1 << 6) 459 | blockGroupRaid5 blockGroup = (1 << 7) 460 | blockGroupRaid6 blockGroup = (1 << 8) 461 | 462 | // We need a bit for restriper to be able to tell when chunks of type 463 | // SINGLE are available. This "extended" profile format is used in 464 | // fs_info->avail_*_alloc_bits (in-memory) and balance item fields 465 | // (on-disk). The corresponding on-disk bit in chunk.type is reserved 466 | // to avoid remappings between two formats in future. 467 | availAllocBitSingle = (1 << 48) 468 | 469 | // A fake block group type that is used to communicate global block reserve 470 | // size to userspace via the SPACE_INFO ioctl. 471 | spaceInfoGlobalRsv = (1 << 49) 472 | 473 | freeSpaceUsingBitmaps = (1 << 0) 474 | 475 | qgroupLevelShift = 48 476 | 477 | // Is subvolume quota turned on? 478 | qgroupStatusFlagOn = (1 << 0) 479 | // RESCAN is set during the initialization phase 480 | qgroupStatusFlagRescan = (1 << 1) 481 | // Some qgroup entries are known to be out of date, 482 | // either because the configuration has changed in a way that 483 | // makes a rescan necessary, or because the fs has been mounted 484 | // with a non-qgroup-aware version. 485 | // Turning qouta off and on again makes it inconsistent, too. 486 | qgroupStatusFlagInconsistent = (1 << 2) 487 | 488 | qgroupStatusVersion = 1 489 | 490 | // The generation is updated during every commit. As older 491 | // versions of btrfs are not aware of qgroups, it will be 492 | // possible to detect inconsistencies by checking the 493 | // generation on mount time 494 | 495 | // Flag definitions see above 496 | 497 | // Only used during scanning to record the progress 498 | // of the scan. It contains a logical address 499 | 500 | // Only updated when any of the other values change 501 | 502 | ) 503 | -------------------------------------------------------------------------------- /btrfs_tree_h.go: -------------------------------------------------------------------------------- 1 | package btrfs 2 | 3 | /* 4 | * This header contains the structure definitions and constants used 5 | * by file system objects that can be retrieved using 6 | * the _BTRFS_IOC_SEARCH_TREE ioctl. That means basically anything that 7 | * is needed to describe a leaf node's key or item contents. 8 | */ 9 | 10 | /* holds pointers to all of the tree roots */ 11 | 12 | /* stores information about which extents are in use, and reference counts */ 13 | 14 | /* 15 | * chunk tree stores translations from logical -> physical block numbering 16 | * the super block points to the chunk tree 17 | */ 18 | 19 | /* 20 | * stores information about which areas of a given device are in use. 21 | * one per device. The tree of tree roots points to the device tree 22 | */ 23 | 24 | /* one per subvolume, storing files and directories */ 25 | 26 | /* directory objectid inside the root tree */ 27 | 28 | /* holds checksums of all the data extents */ 29 | 30 | /* holds quota configuration and tracking */ 31 | 32 | /* for storing items that use the _BTRFS_UUID_KEY* types */ 33 | 34 | /* tracks free space in block groups. */ 35 | 36 | /* device stats in the device tree */ 37 | 38 | /* for storing balance parameters in the root tree */ 39 | 40 | /* orhpan objectid for tracking unlinked/truncated files */ 41 | 42 | /* does write ahead logging to speed up fsyncs */ 43 | 44 | /* for space balancing */ 45 | 46 | /* 47 | * extent checksums all have this objectid 48 | * this allows them to share the logging tree 49 | * for fsyncs 50 | */ 51 | 52 | /* For storing free space cache */ 53 | 54 | /* 55 | * The inode number assigned to the special inode for storing 56 | * free ino cache 57 | */ 58 | 59 | /* dummy objectid represents multiple objectids */ 60 | 61 | /* 62 | * All files have objectids in this range. 63 | */ 64 | 65 | /* 66 | * the device items go into the chunk tree. The key is in the form 67 | * [ 1 _BTRFS_DEV_ITEM_KEY device_id ] 68 | */ 69 | 70 | /* 71 | * inode items have the data typically returned from stat and store other 72 | * info about object characteristics. There is one for every file and dir in 73 | * the FS 74 | */ 75 | 76 | /* reserve 2-15 close to the inode for later flexibility */ 77 | 78 | /* 79 | * dir items are the name -> inode pointers in a directory. There is one 80 | * for every name in a directory. 81 | */ 82 | 83 | /* 84 | * extent data is for file data 85 | */ 86 | 87 | /* 88 | * extent csums are stored in a separate tree and hold csums for 89 | * an entire extent on disk. 90 | */ 91 | 92 | /* 93 | * root items point to tree roots. They are typically in the root 94 | * tree used by the super block to find all the other trees 95 | */ 96 | 97 | /* 98 | * root backrefs tie subvols and snapshots to the directory entries that 99 | * reference them 100 | */ 101 | 102 | /* 103 | * root refs make a fast index for listing all of the snapshots and 104 | * subvolumes referenced by a given root. They point directly to the 105 | * directory item in the root that references the subvol 106 | */ 107 | 108 | /* 109 | * extent items are in the extent map tree. These record which blocks 110 | * are used, and how many references there are to each block 111 | */ 112 | 113 | /* 114 | * The same as the _BTRFS_EXTENT_ITEM_KEY, except it's metadata we already know 115 | * the length, so we save the level in key->offset instead of the length. 116 | */ 117 | 118 | /* 119 | * block groups give us hints into the extent allocation trees. Which 120 | * blocks are free etc etc 121 | */ 122 | 123 | /* 124 | * Every block group is represented in the free space tree by a free space info 125 | * item, which stores some accounting information. It is keyed on 126 | * (block_group_start, FREE_SPACE_INFO, block_group_length). 127 | */ 128 | 129 | /* 130 | * A free space extent tracks an extent of space that is free in a block group. 131 | * It is keyed on (start, FREE_SPACE_EXTENT, length). 132 | */ 133 | 134 | /* 135 | * When a block group becomes very fragmented, we convert it to use bitmaps 136 | * instead of extents. A free space bitmap is keyed on 137 | * (start, FREE_SPACE_BITMAP, length); the corresponding item is a bitmap with 138 | * (length / sectorsize) bits. 139 | */ 140 | 141 | /* 142 | * Records the overall state of the qgroups. 143 | * There's only one instance of this key present, 144 | * (0, _BTRFS_QGROUP_STATUS_KEY, 0) 145 | */ 146 | 147 | /* 148 | * Records the currently used space of the qgroup. 149 | * One key per qgroup, (0, _BTRFS_QGROUP_INFO_KEY, qgroupid). 150 | */ 151 | 152 | /* 153 | * Contains the user configured limits for the qgroup. 154 | * One key per qgroup, (0, _BTRFS_QGROUP_LIMIT_KEY, qgroupid). 155 | */ 156 | 157 | /* 158 | * Records the child-parent relationship of qgroups. For 159 | * each relation, 2 keys are present: 160 | * (childid, _BTRFS_QGROUP_RELATION_KEY, parentid) 161 | * (parentid, _BTRFS_QGROUP_RELATION_KEY, childid) 162 | */ 163 | 164 | /* 165 | * Obsolete name, see _BTRFS_TEMPORARY_ITEM_KEY. 166 | */ 167 | 168 | /* 169 | * The key type for tree items that are stored persistently, but do not need to 170 | * exist for extended period of time. The items can exist in any tree. 171 | * 172 | * [subtype, _BTRFS_TEMPORARY_ITEM_KEY, data] 173 | * 174 | * Existing items: 175 | * 176 | * - balance status item 177 | * (_BTRFS_BALANCE_OBJECTID, _BTRFS_TEMPORARY_ITEM_KEY, 0) 178 | */ 179 | 180 | /* 181 | * Obsolete name, see _BTRFS_PERSISTENT_ITEM_KEY 182 | */ 183 | 184 | /* 185 | * The key type for tree items that are stored persistently and usually exist 186 | * for a long period, eg. filesystem lifetime. The item kinds can be status 187 | * information, stats or preference values. The item can exist in any tree. 188 | * 189 | * [subtype, _BTRFS_PERSISTENT_ITEM_KEY, data] 190 | * 191 | * Existing items: 192 | * 193 | * - device statistics, store IO stats in the device tree, one key for all 194 | * stats 195 | * (_BTRFS_DEV_STATS_OBJECTID, _BTRFS_DEV_STATS_KEY, 0) 196 | */ 197 | 198 | /* 199 | * Persistantly stores the device replace state in the device tree. 200 | * The key is built like this: (0, _BTRFS_DEV_REPLACE_KEY, 0). 201 | */ 202 | 203 | /* 204 | * Stores items that allow to quickly map UUIDs to something else. 205 | * These items are part of the filesystem UUID tree. 206 | * The key is built like this: 207 | * (UUID_upper_64_bits, _BTRFS_UUID_KEY*, UUID_lower_64_bits). 208 | */ 209 | 210 | /* for UUIDs assigned to * received subvols */ 211 | 212 | /* 213 | * string items are for debugging. They just store a short string of 214 | * data in the FS 215 | */ 216 | 217 | /* 32 bytes in various csum fields */ 218 | 219 | /* csum types */ 220 | 221 | /* 222 | * flags definitions for directory entry item type 223 | * 224 | * Used by: 225 | * struct btrfs_dir_item.type 226 | */ 227 | 228 | /* 229 | * The key defines the order in the tree, and so it also defines (optimal) 230 | * block layout. 231 | * 232 | * objectid corresponds to the inode number. 233 | * 234 | * type tells us things about the object, and is a kind of stream selector. 235 | * so for a given inode, keys with type of 1 might refer to the inode data, 236 | * type of 2 may point to file data in the btree and type == 3 may point to 237 | * extents. 238 | * 239 | * offset is the starting byte offset for this key in the stream. 240 | * 241 | * btrfs_disk_key is in disk byte order. struct btrfs_key is always 242 | * in cpu native order. Otherwise they are identical and their sizes 243 | * should be the same (ie both packed) 244 | */ 245 | type btrfs_disk_key struct { 246 | objectid uint64 247 | type_ uint8 248 | offset uint64 249 | } 250 | 251 | type btrfs_key struct { 252 | objectid uint64 253 | type_ uint8 254 | offset uint64 255 | } 256 | 257 | type btrfs_dev_item struct { 258 | devid uint64 259 | total_bytes uint64 260 | bytes_used uint64 261 | io_align uint32 262 | io_width uint32 263 | sector_size uint32 264 | type_ uint64 265 | generation uint64 266 | start_offset uint64 267 | dev_group uint32 268 | seek_speed uint8 269 | bandwidth uint8 270 | uuid UUID 271 | fsid FSID 272 | } 273 | 274 | type btrfs_stripe struct { 275 | devid uint64 276 | offset uint64 277 | dev_uuid UUID 278 | } 279 | 280 | type btrfs_chunk struct { 281 | length uint64 282 | owner uint64 283 | stripe_len uint64 284 | type_ uint64 285 | io_align uint32 286 | io_width uint32 287 | sector_size uint32 288 | num_stripes uint16 289 | sub_stripes uint16 290 | stripe struct { 291 | devid uint64 292 | offset uint64 293 | dev_uuid UUID 294 | } 295 | } 296 | 297 | /* additional stripes go here */ 298 | type btrfs_free_space_entry struct { 299 | offset uint64 300 | bytes uint64 301 | type_ uint8 302 | } 303 | 304 | type btrfs_free_space_header struct { 305 | location struct { 306 | objectid uint64 307 | type_ uint8 308 | offset uint64 309 | } 310 | generation uint64 311 | num_entries uint64 312 | num_bitmaps uint64 313 | } 314 | 315 | /* Super block flags */ 316 | /* Errors detected */ 317 | 318 | /* 319 | * items in the extent btree are used to record the objectid of the 320 | * owner of the block and the number of references 321 | */ 322 | type btrfs_extent_item struct { 323 | refs uint64 324 | generation uint64 325 | flags uint64 326 | } 327 | 328 | type btrfs_extent_item_v0 struct { 329 | refs uint32 330 | } 331 | 332 | /* following flags only apply to tree blocks */ 333 | 334 | /* use full backrefs for extent pointers in the block */ 335 | 336 | /* 337 | * this flag is only used internally by scrub and may be changed at any time 338 | * it is only declared here to avoid collisions 339 | */ 340 | type btrfs_tree_block_info struct { 341 | key struct { 342 | objectid uint64 343 | type_ uint8 344 | offset uint64 345 | } 346 | level uint8 347 | } 348 | 349 | type btrfs_extent_data_ref struct { 350 | root uint64 351 | objectid uint64 352 | offset uint64 353 | count uint32 354 | } 355 | 356 | type btrfs_shared_data_ref struct { 357 | count uint32 358 | } 359 | 360 | type btrfs_extent_inline_ref struct { 361 | type_ uint8 362 | offset uint64 363 | } 364 | 365 | /* old style backrefs item */ 366 | type btrfs_extent_ref_v0 struct { 367 | root uint64 368 | generation uint64 369 | objectid uint64 370 | count uint32 371 | } 372 | 373 | /* dev extents record free space on individual devices. The owner 374 | * field points back to the chunk allocation mapping tree that allocated 375 | * the extent. The chunk tree uuid field is a way to double check the owner 376 | */ 377 | type btrfs_dev_extent struct { 378 | chunk_tree uint64 379 | chunk_objectid uint64 380 | chunk_offset uint64 381 | length uint64 382 | chunk_tree_uuid UUID 383 | } 384 | 385 | type btrfs_inode_ref struct { 386 | index uint64 387 | name_len uint16 388 | } 389 | 390 | /* name goes here */ 391 | type btrfs_inode_extref struct { 392 | parent_objectid uint64 393 | index uint64 394 | name_len uint16 395 | //name [0]uint8 396 | } 397 | 398 | /* name goes here */ 399 | type btrfs_timespec struct { 400 | sec uint64 401 | nsec uint32 402 | } 403 | 404 | type btrfs_inode_item struct { 405 | generation uint64 406 | transid uint64 407 | size uint64 408 | nbytes uint64 409 | block_group uint64 410 | nlink uint32 411 | uid uint32 412 | gid uint32 413 | mode uint32 414 | rdev uint64 415 | flags uint64 416 | sequence uint64 417 | reserved [4]uint64 418 | atime struct { 419 | sec uint64 420 | nsec uint32 421 | } 422 | ctime struct { 423 | sec uint64 424 | nsec uint32 425 | } 426 | mtime struct { 427 | sec uint64 428 | nsec uint32 429 | } 430 | otime struct { 431 | sec uint64 432 | nsec uint32 433 | } 434 | } 435 | 436 | type btrfs_dir_log_item struct { 437 | end uint64 438 | } 439 | 440 | type btrfs_dir_item struct { 441 | location struct { 442 | objectid uint64 443 | type_ uint8 444 | offset uint64 445 | } 446 | transid uint64 447 | data_len uint16 448 | name_len uint16 449 | type_ uint8 450 | } 451 | 452 | /* 453 | * Internal in-memory flag that a subvolume has been marked for deletion but 454 | * still visible as a directory 455 | */ 456 | type btrfs_root_item struct { 457 | inode struct { 458 | generation uint64 459 | transid uint64 460 | size uint64 461 | nbytes uint64 462 | block_group uint64 463 | nlink uint32 464 | uid uint32 465 | gid uint32 466 | mode uint32 467 | rdev uint64 468 | flags uint64 469 | sequence uint64 470 | reserved [4]uint64 471 | atime struct { 472 | sec uint64 473 | nsec uint32 474 | } 475 | ctime struct { 476 | sec uint64 477 | nsec uint32 478 | } 479 | mtime struct { 480 | sec uint64 481 | nsec uint32 482 | } 483 | otime struct { 484 | sec uint64 485 | nsec uint32 486 | } 487 | } 488 | generation uint64 489 | root_dirid uint64 490 | bytenr uint64 491 | byte_limit uint64 492 | bytes_used uint64 493 | last_snapshot uint64 494 | flags uint64 495 | refs uint32 496 | drop_progress struct { 497 | objectid uint64 498 | type_ uint8 499 | offset uint64 500 | } 501 | drop_level uint8 502 | level uint8 503 | generation_v2 uint64 504 | uuid UUID 505 | parent_uuid UUID 506 | received_uuid UUID 507 | ctransid uint64 508 | otransid uint64 509 | stransid uint64 510 | rtransid uint64 511 | ctime struct { 512 | sec uint64 513 | nsec uint32 514 | } 515 | otime struct { 516 | sec uint64 517 | nsec uint32 518 | } 519 | stime struct { 520 | sec uint64 521 | nsec uint32 522 | } 523 | rtime struct { 524 | sec uint64 525 | nsec uint32 526 | } 527 | reserved [8]uint64 528 | } 529 | 530 | /* 531 | * this is used for both forward and backward root refs 532 | */ 533 | type btrfs_root_ref struct { 534 | dirid uint64 535 | sequence uint64 536 | name_len uint16 537 | } 538 | 539 | type btrfs_disk_balance_args struct { 540 | profiles uint64 541 | usage uint64 542 | usage_min uint32 543 | usage_max uint32 544 | devid uint64 545 | pstart uint64 546 | pend uint64 547 | vstart uint64 548 | vend uint64 549 | target uint64 550 | flags uint64 551 | limit uint64 552 | limit_min uint32 553 | limit_max uint32 554 | stripes_min uint32 555 | stripes_max uint32 556 | unused [6]uint64 557 | } 558 | 559 | /* 560 | * store balance parameters to disk so that balance can be properly 561 | * resumed after crash or unmount 562 | */ 563 | type btrfs_balance_item struct { 564 | flags uint64 565 | data struct { 566 | profiles uint64 567 | usage uint64 568 | usage_min uint32 569 | usage_max uint32 570 | devid uint64 571 | pstart uint64 572 | pend uint64 573 | vstart uint64 574 | vend uint64 575 | target uint64 576 | flags uint64 577 | limit uint64 578 | limit_min uint32 579 | limit_max uint32 580 | stripes_min uint32 581 | stripes_max uint32 582 | unused [6]uint64 583 | } 584 | meta struct { 585 | profiles uint64 586 | usage uint64 587 | usage_min uint32 588 | usage_max uint32 589 | devid uint64 590 | pstart uint64 591 | pend uint64 592 | vstart uint64 593 | vend uint64 594 | target uint64 595 | flags uint64 596 | limit uint64 597 | limit_min uint32 598 | limit_max uint32 599 | stripes_min uint32 600 | stripes_max uint32 601 | unused [6]uint64 602 | } 603 | sys struct { 604 | profiles uint64 605 | usage uint64 606 | usage_min uint32 607 | usage_max uint32 608 | devid uint64 609 | pstart uint64 610 | pend uint64 611 | vstart uint64 612 | vend uint64 613 | target uint64 614 | flags uint64 615 | limit uint64 616 | limit_min uint32 617 | limit_max uint32 618 | stripes_min uint32 619 | stripes_max uint32 620 | unused [6]uint64 621 | } 622 | unused [4]uint64 623 | } 624 | 625 | type btrfs_file_extent_item struct { 626 | generation uint64 627 | ram_bytes uint64 628 | compression uint8 629 | encryption uint8 630 | other_encoding uint16 631 | type_ uint8 632 | disk_bytenr uint64 633 | disk_num_bytes uint64 634 | offset uint64 635 | num_bytes uint64 636 | } 637 | 638 | type btrfs_csum_item struct { 639 | csum uint8 640 | } 641 | 642 | type btrfs_dev_stats_item struct { 643 | values [_BTRFS_DEV_STAT_VALUES_MAX]uint64 644 | } 645 | 646 | type btrfs_dev_replace_item struct { 647 | src_devid uint64 648 | cursor_left uint64 649 | cursor_right uint64 650 | cont_reading_from_srcdev_mode uint64 651 | replace_state uint64 652 | time_started uint64 653 | time_stopped uint64 654 | num_write_errors uint64 655 | num_uncorrectable_read_errors uint64 656 | } 657 | 658 | /* different types of block groups (and chunks) */ 659 | const ( 660 | _BTRFS_RAID_RAID10 = iota 661 | _BTRFS_RAID_RAID1 662 | _BTRFS_RAID_DUP 663 | _BTRFS_RAID_RAID0 664 | _BTRFS_RAID_SINGLE 665 | _BTRFS_RAID_RAID5 666 | _BTRFS_RAID_RAID6 667 | _BTRFS_NR_RAID_TYPES 668 | ) 669 | 670 | /* 671 | * We need a bit for restriper to be able to tell when chunks of type 672 | * SINGLE are available. This "extended" profile format is used in 673 | * fs_info->avail_*_alloc_bits (in-memory) and balance item fields 674 | * (on-disk). The corresponding on-disk bit in chunk.type is reserved 675 | * to avoid remappings between two formats in future. 676 | */ 677 | 678 | /* 679 | * A fake block group type that is used to communicate global block reserve 680 | * size to userspace via the SPACE_INFO ioctl. 681 | */ 682 | func chunk_to_extended(flags uint64) uint64 { 683 | if flags&uint64(_BTRFS_BLOCK_GROUP_PROFILE_MASK) == 0 { 684 | flags |= uint64(availAllocBitSingle) 685 | } 686 | 687 | return flags 688 | } 689 | 690 | func extended_to_chunk(flags uint64) uint64 { 691 | return flags &^ uint64(availAllocBitSingle) 692 | } 693 | 694 | type btrfs_block_group_item struct { 695 | used uint64 696 | chunk_objectid uint64 697 | flags uint64 698 | } 699 | 700 | type btrfs_free_space_info struct { 701 | extent_count uint32 702 | flags uint32 703 | } 704 | 705 | func btrfs_qgroup_level(qgroupid uint64) uint64 { 706 | return qgroupid >> uint32(qgroupLevelShift) 707 | } 708 | 709 | /* 710 | * is subvolume quota turned on? 711 | */ 712 | 713 | /* 714 | * RESCAN is set during the initialization phase 715 | */ 716 | 717 | /* 718 | * Some qgroup entries are known to be out of date, 719 | * either because the configuration has changed in a way that 720 | * makes a rescan necessary, or because the fs has been mounted 721 | * with a non-qgroup-aware version. 722 | * Turning qouta off and on again makes it inconsistent, too. 723 | */ 724 | type btrfs_qgroup_status_item struct { 725 | version uint64 726 | generation uint64 727 | flags uint64 728 | rescan uint64 729 | } 730 | 731 | type btrfs_qgroup_info_item struct { 732 | generation uint64 733 | rfer uint64 734 | rfer_cmpr uint64 735 | excl uint64 736 | excl_cmpr uint64 737 | } 738 | 739 | type btrfs_qgroup_limit_item struct { 740 | flags uint64 741 | max_rfer uint64 742 | max_excl uint64 743 | rsv_rfer uint64 744 | rsv_excl uint64 745 | } 746 | -------------------------------------------------------------------------------- /ioctl_h.go: -------------------------------------------------------------------------------- 1 | package btrfs 2 | 3 | import ( 4 | "encoding/binary" 5 | "encoding/hex" 6 | "github.com/dennwc/ioctl" 7 | "os" 8 | "strconv" 9 | "strings" 10 | "unsafe" 11 | ) 12 | 13 | var order = binary.LittleEndian 14 | 15 | const ioctlMagic = 0x94 16 | 17 | const devicePathNameMax = 1024 18 | 19 | const ( 20 | FSIDSize = 16 21 | UUIDSize = 16 22 | ) 23 | 24 | var zeroUUID UUID 25 | 26 | type UUID [UUIDSize]byte 27 | 28 | func (id UUID) IsZero() bool { return id == zeroUUID } 29 | func (id UUID) String() string { 30 | if id.IsZero() { 31 | return "" 32 | } 33 | buf := make([]byte, UUIDSize*2+4) 34 | i := 0 35 | i += hex.Encode(buf[i:], id[:4]) 36 | buf[i] = '-' 37 | i++ 38 | i += hex.Encode(buf[i:], id[4:6]) 39 | buf[i] = '-' 40 | i++ 41 | i += hex.Encode(buf[i:], id[6:8]) 42 | buf[i] = '-' 43 | i++ 44 | i += hex.Encode(buf[i:], id[8:10]) 45 | buf[i] = '-' 46 | i++ 47 | i += hex.Encode(buf[i:], id[10:]) 48 | return string(buf) 49 | } 50 | 51 | type FSID [FSIDSize]byte 52 | 53 | func (id FSID) String() string { return hex.EncodeToString(id[:]) } 54 | 55 | const volNameMax = 4087 56 | 57 | // this should be 4k 58 | type btrfs_ioctl_vol_args struct { 59 | fd int64 60 | name [volNameMax + 1]byte 61 | } 62 | 63 | func (arg *btrfs_ioctl_vol_args) SetName(name string) { 64 | n := copy(arg.name[:], name) 65 | arg.name[n] = 0 66 | } 67 | 68 | type btrfs_qgroup_limit struct { 69 | flags uint64 70 | max_referenced uint64 71 | max_exclusive uint64 72 | rsv_referenced uint64 73 | rsv_exclusive uint64 74 | } 75 | 76 | type btrfs_qgroup_inherit struct { 77 | flags uint64 78 | num_qgroups uint64 79 | num_ref_copies uint64 80 | num_excl_copies uint64 81 | lim btrfs_qgroup_limit 82 | //qgroups [0]uint64 83 | } 84 | 85 | type btrfs_ioctl_qgroup_limit_args struct { 86 | qgroupid uint64 87 | lim btrfs_qgroup_limit 88 | } 89 | 90 | type btrfs_ioctl_vol_args_v2_u1 struct { 91 | size uint64 92 | qgroup_inherit *btrfs_qgroup_inherit 93 | } 94 | 95 | const subvolNameMax = 4039 96 | 97 | type SubvolFlags uint64 98 | 99 | // Match flags like GetFlags translates in fs/btrfs/ioctl.c `btrfs_ioctl_subvol_getflags` 100 | const subvolReadOnlyMask = (SubvolReadOnly | SubvolRootReadOnly) 101 | 102 | func (f SubvolFlags) ReadOnly() bool { 103 | return f&subvolReadOnlyMask != 0 104 | } 105 | func (f SubvolFlags) String() string { 106 | if f == 0 { 107 | return "" 108 | } 109 | var out []string 110 | 111 | if f.ReadOnly() { 112 | out = append(out, "RO") 113 | f = f & (^subvolReadOnlyMask) 114 | } 115 | if f != 0 { 116 | out = append(out, "0x"+strconv.FormatInt(int64(f), 16)) 117 | } 118 | return strings.Join(out, "|") 119 | } 120 | 121 | // flags for subvolumes 122 | // 123 | // Used by: 124 | // struct btrfs_ioctl_vol_args_v2.flags 125 | // 126 | // BTRFS_SUBVOL_RDONLY is also provided/consumed by the following ioctls: 127 | // - BTRFS_IOC_SUBVOL_GETFLAGS 128 | // - BTRFS_IOC_SUBVOL_SETFLAGS 129 | const ( 130 | subvolCreateAsync = SubvolFlags(1 << 0) // deprecated in 5.7 131 | SubvolRootReadOnly = SubvolFlags(1 << 0) // BTRFS_ROOT_SUBVOL_RDONLY, only present in search result copies 132 | SubvolReadOnly = SubvolFlags(1 << 1) // BTRFS_SUBVOL_RDONLY 133 | subvolQGroupInherit = SubvolFlags(1 << 2) 134 | ) 135 | 136 | type btrfs_ioctl_vol_args_v2 struct { 137 | fd int64 138 | transid uint64 139 | flags SubvolFlags 140 | btrfs_ioctl_vol_args_v2_u1 141 | unused [2]uint64 142 | name [subvolNameMax + 1]byte 143 | } 144 | 145 | // structure to report errors and progress to userspace, either as a 146 | // result of a finished scrub, a canceled scrub or a progress inquiry 147 | type btrfs_scrub_progress struct { 148 | data_extents_scrubbed uint64 // # of data extents scrubbed 149 | tree_extents_scrubbed uint64 // # of tree extents scrubbed 150 | data_bytes_scrubbed uint64 // # of data bytes scrubbed 151 | tree_bytes_scrubbed uint64 // # of tree bytes scrubbed 152 | read_errors uint64 // # of read errors encountered (EIO) 153 | csum_errors uint64 // # of failed csum checks 154 | // # of occurences, where the metadata of a tree block did not match the expected values, like generation or logical 155 | verify_errors uint64 156 | // # of 4k data block for which no csum is present, probably the result of data written with nodatasum 157 | no_csum uint64 158 | csum_discards uint64 // # of csum for which no data was found in the extent tree. 159 | super_errors uint64 // # of bad super blocks encountered 160 | malloc_errors uint64 // # of internal kmalloc errors. These will likely cause an incomplete scrub 161 | uncorrectable_errors uint64 // # of errors where either no intact copy was found or the writeback failed 162 | corrected_errors uint64 // # of errors corrected 163 | // last physical address scrubbed. In case a scrub was aborted, this can be used to restart the scrub 164 | last_physical uint64 165 | // # of occurences where a read for a full (64k) bio failed, but the re- 166 | // check succeeded for each 4k piece. Intermittent error. 167 | unverified_errors uint64 168 | } 169 | 170 | type btrfs_ioctl_scrub_args struct { 171 | devid uint64 // in 172 | start uint64 // in 173 | end uint64 // in 174 | flags uint64 // in 175 | progress btrfs_scrub_progress // out 176 | // pad to 1k 177 | _ [1024 - 4*8 - unsafe.Sizeof(btrfs_scrub_progress{})]byte 178 | } 179 | 180 | type contReadingFromSrcdevMode uint64 181 | 182 | const ( 183 | _BTRFS_IOCTL_DEV_REPLACE_CONT_READING_FROM_SRCDEV_MODE_ALWAYS contReadingFromSrcdevMode = 0 184 | _BTRFS_IOCTL_DEV_REPLACE_CONT_READING_FROM_SRCDEV_MODE_AVOID contReadingFromSrcdevMode = 1 185 | ) 186 | 187 | type btrfs_ioctl_dev_replace_start_params struct { 188 | srcdevid uint64 // in, if 0, use srcdev_name instead 189 | cont_reading_from_srcdev_mode contReadingFromSrcdevMode // in 190 | srcdev_name [devicePathNameMax + 1]byte // in 191 | tgtdev_name [devicePathNameMax + 1]byte // in 192 | } 193 | 194 | type devReplaceState uint64 195 | 196 | const ( 197 | _BTRFS_IOCTL_DEV_REPLACE_STATE_NEVER_STARTED devReplaceState = 0 198 | _BTRFS_IOCTL_DEV_REPLACE_STATE_STARTED devReplaceState = 1 199 | _BTRFS_IOCTL_DEV_REPLACE_STATE_FINISHED devReplaceState = 2 200 | _BTRFS_IOCTL_DEV_REPLACE_STATE_CANCELED devReplaceState = 3 201 | _BTRFS_IOCTL_DEV_REPLACE_STATE_SUSPENDED devReplaceState = 4 202 | ) 203 | 204 | type btrfs_ioctl_dev_replace_status_params struct { 205 | replace_state devReplaceState // out 206 | progress_1000 uint64 // out, 0 <= x <= 1000 207 | time_started uint64 // out, seconds since 1-Jan-1970 208 | time_stopped uint64 // out, seconds since 1-Jan-1970 209 | num_write_errors uint64 // out 210 | num_uncorrectable_read_errors uint64 // out 211 | } 212 | 213 | type btrfs_ioctl_dev_replace_args_u1 struct { 214 | cmd uint64 // in 215 | result uint64 // out 216 | start btrfs_ioctl_dev_replace_start_params // in 217 | spare [64]uint64 218 | } 219 | 220 | type btrfs_ioctl_dev_replace_args_u2 struct { 221 | cmd uint64 // in 222 | result uint64 // out 223 | status btrfs_ioctl_dev_replace_status_params // out 224 | _ [unsafe.Sizeof(btrfs_ioctl_dev_replace_start_params{}) - unsafe.Sizeof(btrfs_ioctl_dev_replace_status_params{})]byte 225 | spare [64]uint64 226 | } 227 | 228 | type btrfs_ioctl_dev_info_args struct { 229 | devid uint64 // in/out 230 | uuid UUID // in/out 231 | bytes_used uint64 // out 232 | total_bytes uint64 // out 233 | _ [379]uint64 // pad to 4k 234 | path [devicePathNameMax]byte // out 235 | } 236 | 237 | type btrfs_ioctl_fs_info_args struct { 238 | max_id uint64 // out 239 | num_devices uint64 // out 240 | fsid FSID // out 241 | nodesize uint32 // out 242 | sectorsize uint32 // out 243 | clone_alignment uint32 // out 244 | _ [122*8 + 4]byte // pad to 1k 245 | } 246 | 247 | type btrfs_ioctl_feature_flags struct { 248 | compat_flags FeatureFlags 249 | compat_ro_flags FeatureFlags 250 | incompat_flags IncompatFeatures 251 | } 252 | 253 | type argRange [8]byte 254 | 255 | func (u argRange) asN() uint64 { 256 | return order.Uint64(u[:]) 257 | } 258 | func (u argRange) asMinMax() (min, max uint32) { 259 | return order.Uint32(u[:4]), order.Uint32(u[4:]) 260 | } 261 | 262 | // balance control ioctl modes 263 | //#define BTRFS_BALANCE_CTL_PAUSE 1 264 | //#define BTRFS_BALANCE_CTL_CANCEL 2 265 | //#define BTRFS_BALANCE_CTL_RESUME 3 266 | 267 | // this is packed, because it should be exactly the same as its disk 268 | // byte order counterpart (struct btrfs_disk_balance_args) 269 | type btrfs_balance_args struct { 270 | profiles uint64 271 | // usage filter 272 | // BTRFS_BALANCE_ARGS_USAGE with a single value means '0..N' 273 | // BTRFS_BALANCE_ARGS_USAGE_RANGE - range syntax, min..max 274 | usage argRange 275 | devid uint64 276 | pstart uint64 277 | pend uint64 278 | vstart uint64 279 | vend uint64 280 | target uint64 281 | flags uint64 282 | // BTRFS_BALANCE_ARGS_LIMIT with value 'limit' (limit number of processed chunks) 283 | // BTRFS_BALANCE_ARGS_LIMIT_RANGE - the extend version can use minimum and maximum 284 | limit argRange 285 | stripes_min uint32 286 | stripes_max uint32 287 | _ [48]byte 288 | } 289 | 290 | // Report balance progress to userspace. 291 | // 292 | // btrfs_balance_progress 293 | type BalanceProgress struct { 294 | Expected uint64 // estimated # of chunks that will be relocated to fulfill the request 295 | Considered uint64 // # of chunks we have considered so far 296 | Completed uint64 // # of chunks relocated so far 297 | } 298 | 299 | type BalanceState uint64 300 | 301 | const ( 302 | BalanceStateRunning BalanceState = (1 << 0) 303 | BalanceStatePauseReq BalanceState = (1 << 1) 304 | BalanceStateCancelReq BalanceState = (1 << 2) 305 | ) 306 | 307 | type btrfs_ioctl_balance_args struct { 308 | flags BalanceFlags // in/out 309 | state BalanceState // out 310 | data btrfs_balance_args // in/out 311 | meta btrfs_balance_args // in/out 312 | sys btrfs_balance_args // in/out 313 | stat BalanceProgress // out 314 | _ [72 * 8]byte // pad to 1k 315 | } 316 | 317 | const _BTRFS_INO_LOOKUP_PATH_MAX = 4080 318 | 319 | type btrfs_ioctl_ino_lookup_args struct { 320 | treeid objectID 321 | objectid objectID 322 | name [_BTRFS_INO_LOOKUP_PATH_MAX]byte 323 | } 324 | 325 | func (arg *btrfs_ioctl_ino_lookup_args) Name() string { 326 | n := 0 327 | for i, b := range arg.name { 328 | if b == '\x00' { 329 | n = i 330 | break 331 | } 332 | } 333 | return string(arg.name[:n]) 334 | } 335 | 336 | type btrfs_ioctl_search_key struct { 337 | tree_id objectID // which root are we searching. 0 is the tree of tree roots 338 | // keys returned will be >= min and <= max 339 | min_objectid objectID 340 | max_objectid objectID 341 | // keys returned will be >= min and <= max 342 | min_offset uint64 343 | max_offset uint64 344 | // max and min transids to search for 345 | min_transid uint64 346 | max_transid uint64 347 | // keys returned will be >= min and <= max 348 | min_type treeKeyType 349 | max_type treeKeyType 350 | // how many items did userland ask for, and how many are we returning 351 | nr_items uint32 352 | _ [36]byte 353 | } 354 | 355 | type btrfs_ioctl_search_header struct { 356 | transid uint64 357 | objectid objectID 358 | offset uint64 359 | typ treeKeyType 360 | len uint32 361 | } 362 | 363 | const _BTRFS_SEARCH_ARGS_BUFSIZE = (4096 - unsafe.Sizeof(btrfs_ioctl_search_key{})) 364 | 365 | // the buf is an array of search headers where 366 | // each header is followed by the actual item 367 | // the type field is expanded to 32 bits for alignment 368 | type btrfs_ioctl_search_args struct { 369 | key btrfs_ioctl_search_key 370 | buf [_BTRFS_SEARCH_ARGS_BUFSIZE]byte 371 | } 372 | 373 | // Extended version of TREE_SEARCH ioctl that can return more than 4k of bytes. 374 | // The allocated size of the buffer is set in buf_size. 375 | type btrfs_ioctl_search_args_v2 struct { 376 | key btrfs_ioctl_search_key // in/out - search parameters 377 | buf_size uint64 // in - size of buffer; out - on EOVERFLOW: needed size to store item 378 | //buf [0]uint64 // out - found items 379 | } 380 | 381 | // With a @src_length of zero, the range from @src_offset->EOF is cloned! 382 | type btrfs_ioctl_clone_range_args struct { 383 | src_fd int64 384 | src_offset uint64 385 | src_length uint64 386 | dest_offset uint64 387 | } 388 | 389 | // flags for the defrag range ioctl 390 | type defragRange uint64 391 | 392 | const ( 393 | _BTRFS_DEFRAG_RANGE_COMPRESS defragRange = 1 394 | _BTRFS_DEFRAG_RANGE_START_IO defragRange = 2 395 | ) 396 | 397 | const _BTRFS_SAME_DATA_DIFFERS = 1 398 | 399 | // For extent-same ioctl 400 | type btrfs_ioctl_same_extent_info struct { 401 | fd int64 // in - destination file 402 | logical_offset uint64 // in - start of extent in destination 403 | bytes_deduped uint64 // out - total # of bytes we were able to dedupe from this file 404 | // out; status of this dedupe operation: 405 | // 0 if dedup succeeds 406 | // < 0 for error 407 | // == BTRFS_SAME_DATA_DIFFERS if data differs 408 | status int32 409 | reserved uint32 410 | } 411 | 412 | type btrfs_ioctl_same_args struct { 413 | logical_offset uint64 // in - start of extent in source 414 | length uint64 // in - length of extent 415 | dest_count uint16 // in - total elements in info array 416 | _ [6]byte 417 | //info [0]btrfs_ioctl_same_extent_info 418 | } 419 | 420 | type btrfs_ioctl_defrag_range_args struct { 421 | start uint64 // start of the defrag operation 422 | len uint64 // number of bytes to defrag, use (u64)-1 to say all 423 | // flags for the operation, which can include turning 424 | // on compression for this one defrag 425 | flags uint64 426 | // any extent bigger than this will be considered 427 | // already defragged. Use 0 to take the kernel default 428 | // Use 1 to say every single extent must be rewritten 429 | extent_thresh uint32 430 | // which compression method to use if turning on compression 431 | // for this defrag operation. If unspecified, zlib will be used 432 | compress_type uint32 433 | _ [16]byte // spare for later 434 | } 435 | 436 | type btrfs_ioctl_space_info struct { 437 | flags uint64 438 | total_bytes uint64 439 | used_bytes uint64 440 | } 441 | 442 | type btrfs_ioctl_space_args struct { 443 | space_slots uint64 444 | total_spaces uint64 445 | //spaces [0]btrfs_ioctl_space_info 446 | } 447 | 448 | type btrfs_data_container struct { 449 | bytes_left uint32 // out -- bytes not needed to deliver output 450 | bytes_missing uint32 // out -- additional bytes needed for result 451 | elem_cnt uint32 // out 452 | elem_missed uint32 // out 453 | //val [0]uint64 454 | } 455 | 456 | type btrfs_ioctl_ino_path_args struct { 457 | inum uint64 // in 458 | size uint64 // in 459 | _ [32]byte 460 | // struct btrfs_data_container *fspath; out 461 | fspath uint64 // out 462 | } 463 | 464 | type btrfs_ioctl_logical_ino_args struct { 465 | logical uint64 // in 466 | size uint64 // in 467 | _ [32]byte 468 | // struct btrfs_data_container *inodes; out 469 | inodes uint64 470 | } 471 | 472 | // disk I/O failure stats 473 | const ( 474 | _BTRFS_DEV_STAT_WRITE_ERRS = iota // EIO or EREMOTEIO from lower layers 475 | _BTRFS_DEV_STAT_READ_ERRS // EIO or EREMOTEIO from lower layers 476 | _BTRFS_DEV_STAT_FLUSH_ERRS // EIO or EREMOTEIO from lower layers 477 | 478 | // stats for indirect indications for I/O failures 479 | 480 | // checksum error, bytenr error or contents is illegal: this is an 481 | // indication that the block was damaged during read or write, or written to 482 | // wrong location or read from wrong location 483 | _BTRFS_DEV_STAT_CORRUPTION_ERRS 484 | _BTRFS_DEV_STAT_GENERATION_ERRS // an indication that blocks have not been written 485 | 486 | _BTRFS_DEV_STAT_VALUES_MAX 487 | ) 488 | 489 | // Reset statistics after reading; needs SYS_ADMIN capability 490 | const _BTRFS_DEV_STATS_RESET = (1 << 0) 491 | 492 | type btrfs_ioctl_get_dev_stats struct { 493 | devid uint64 // in 494 | nr_items uint64 // in/out 495 | flags uint64 // in/out 496 | values [_BTRFS_DEV_STAT_VALUES_MAX]uint64 // out values 497 | _ [128 - 2 - _BTRFS_DEV_STAT_VALUES_MAX]uint64 // pad to 1k 498 | } 499 | 500 | const ( 501 | _BTRFS_QUOTA_CTL_ENABLE = 1 502 | _BTRFS_QUOTA_CTL_DISABLE = 2 503 | // 3 has formerly been reserved for BTRFS_QUOTA_CTL_RESCAN 504 | ) 505 | 506 | type btrfs_ioctl_quota_ctl_args struct { 507 | cmd uint64 508 | status uint64 509 | } 510 | 511 | type btrfs_ioctl_quota_rescan_args struct { 512 | flags uint64 513 | progress uint64 514 | _ [6]uint64 515 | } 516 | 517 | type btrfs_ioctl_qgroup_assign_args struct { 518 | assign uint64 519 | src uint64 520 | dst uint64 521 | } 522 | 523 | type btrfs_ioctl_qgroup_create_args struct { 524 | create uint64 525 | qgroupid uint64 526 | } 527 | 528 | type btrfs_ioctl_timespec struct { 529 | sec uint64 530 | nsec uint32 531 | } 532 | 533 | type btrfs_ioctl_received_subvol_args struct { 534 | uuid UUID // in 535 | stransid uint64 // in 536 | rtransid uint64 // out 537 | stime btrfs_ioctl_timespec // in 538 | rtime btrfs_ioctl_timespec // out 539 | flags uint64 // in 540 | _ [16]uint64 // in 541 | } 542 | 543 | const ( 544 | // Caller doesn't want file data in the send stream, even if the 545 | // search of clone sources doesn't find an extent. UPDATE_EXTENT 546 | // commands will be sent instead of WRITE commands. 547 | _BTRFS_SEND_FLAG_NO_FILE_DATA = 0x1 548 | // Do not add the leading stream header. Used when multiple snapshots 549 | // are sent back to back. 550 | _BTRFS_SEND_FLAG_OMIT_STREAM_HEADER = 0x2 551 | // Omit the command at the end of the stream that indicated the end 552 | // of the stream. This option is used when multiple snapshots are 553 | // sent back to back. 554 | _BTRFS_SEND_FLAG_OMIT_END_CMD = 0x4 555 | 556 | _BTRFS_SEND_FLAG_MASK = _BTRFS_SEND_FLAG_NO_FILE_DATA | 557 | _BTRFS_SEND_FLAG_OMIT_STREAM_HEADER | 558 | _BTRFS_SEND_FLAG_OMIT_END_CMD 559 | ) 560 | 561 | type btrfs_ioctl_send_args struct { 562 | send_fd int64 // in 563 | clone_sources_count uint64 // in 564 | clone_sources *objectID // in 565 | parent_root objectID // in 566 | flags uint64 // in 567 | _ [4]uint64 // in 568 | } 569 | 570 | var ( 571 | _BTRFS_IOC_SNAP_CREATE = ioctl.IOW(ioctlMagic, 1, unsafe.Sizeof(btrfs_ioctl_vol_args{})) 572 | _BTRFS_IOC_DEFRAG = ioctl.IOW(ioctlMagic, 2, unsafe.Sizeof(btrfs_ioctl_vol_args{})) 573 | _BTRFS_IOC_RESIZE = ioctl.IOW(ioctlMagic, 3, unsafe.Sizeof(btrfs_ioctl_vol_args{})) 574 | _BTRFS_IOC_SCAN_DEV = ioctl.IOW(ioctlMagic, 4, unsafe.Sizeof(btrfs_ioctl_vol_args{})) 575 | _BTRFS_IOC_TRANS_START = ioctl.IO(ioctlMagic, 6) 576 | _BTRFS_IOC_TRANS_END = ioctl.IO(ioctlMagic, 7) 577 | _BTRFS_IOC_SYNC = ioctl.IO(ioctlMagic, 8) 578 | _BTRFS_IOC_CLONE = ioctl.IOW(ioctlMagic, 9, 4) // int32 579 | _BTRFS_IOC_ADD_DEV = ioctl.IOW(ioctlMagic, 10, unsafe.Sizeof(btrfs_ioctl_vol_args{})) 580 | _BTRFS_IOC_RM_DEV = ioctl.IOW(ioctlMagic, 11, unsafe.Sizeof(btrfs_ioctl_vol_args{})) 581 | _BTRFS_IOC_BALANCE = ioctl.IOW(ioctlMagic, 12, unsafe.Sizeof(btrfs_ioctl_vol_args{})) 582 | _BTRFS_IOC_CLONE_RANGE = ioctl.IOW(ioctlMagic, 13, unsafe.Sizeof(btrfs_ioctl_clone_range_args{})) 583 | _BTRFS_IOC_SUBVOL_CREATE = ioctl.IOW(ioctlMagic, 14, unsafe.Sizeof(btrfs_ioctl_vol_args{})) 584 | _BTRFS_IOC_SNAP_DESTROY = ioctl.IOW(ioctlMagic, 15, unsafe.Sizeof(btrfs_ioctl_vol_args{})) 585 | _BTRFS_IOC_DEFRAG_RANGE = ioctl.IOW(ioctlMagic, 16, unsafe.Sizeof(btrfs_ioctl_defrag_range_args{})) 586 | _BTRFS_IOC_TREE_SEARCH = ioctl.IOWR(ioctlMagic, 17, unsafe.Sizeof(btrfs_ioctl_search_args{})) 587 | _BTRFS_IOC_INO_LOOKUP = ioctl.IOWR(ioctlMagic, 18, unsafe.Sizeof(btrfs_ioctl_ino_lookup_args{})) 588 | _BTRFS_IOC_DEFAULT_SUBVOL = ioctl.IOW(ioctlMagic, 19, 8) // uint64 589 | _BTRFS_IOC_SPACE_INFO = ioctl.IOWR(ioctlMagic, 20, unsafe.Sizeof(btrfs_ioctl_space_args{})) 590 | _BTRFS_IOC_START_SYNC = ioctl.IOR(ioctlMagic, 24, 8) // uint64 591 | _BTRFS_IOC_WAIT_SYNC = ioctl.IOW(ioctlMagic, 22, 8) // uint64 592 | _BTRFS_IOC_SNAP_CREATE_V2 = ioctl.IOW(ioctlMagic, 23, unsafe.Sizeof(btrfs_ioctl_vol_args_v2{})) 593 | _BTRFS_IOC_SUBVOL_CREATE_V2 = ioctl.IOW(ioctlMagic, 24, unsafe.Sizeof(btrfs_ioctl_vol_args_v2{})) 594 | _BTRFS_IOC_SUBVOL_GETFLAGS = ioctl.IOR(ioctlMagic, 25, 8) // uint64 595 | _BTRFS_IOC_SUBVOL_SETFLAGS = ioctl.IOW(ioctlMagic, 26, 8) // uint64 596 | _BTRFS_IOC_SCRUB = ioctl.IOWR(ioctlMagic, 27, unsafe.Sizeof(btrfs_ioctl_scrub_args{})) 597 | _BTRFS_IOC_SCRUB_CANCEL = ioctl.IO(ioctlMagic, 28) 598 | _BTRFS_IOC_SCRUB_PROGRESS = ioctl.IOWR(ioctlMagic, 29, unsafe.Sizeof(btrfs_ioctl_scrub_args{})) 599 | _BTRFS_IOC_DEV_INFO = ioctl.IOWR(ioctlMagic, 30, unsafe.Sizeof(btrfs_ioctl_dev_info_args{})) 600 | _BTRFS_IOC_FS_INFO = ioctl.IOR(ioctlMagic, 31, unsafe.Sizeof(btrfs_ioctl_fs_info_args{})) 601 | _BTRFS_IOC_BALANCE_V2 = ioctl.IOWR(ioctlMagic, 32, unsafe.Sizeof(btrfs_ioctl_balance_args{})) 602 | _BTRFS_IOC_BALANCE_CTL = ioctl.IOW(ioctlMagic, 33, 4) // int32 603 | _BTRFS_IOC_BALANCE_PROGRESS = ioctl.IOR(ioctlMagic, 34, unsafe.Sizeof(btrfs_ioctl_balance_args{})) 604 | _BTRFS_IOC_INO_PATHS = ioctl.IOWR(ioctlMagic, 35, unsafe.Sizeof(btrfs_ioctl_ino_path_args{})) 605 | _BTRFS_IOC_LOGICAL_INO = ioctl.IOWR(ioctlMagic, 36, unsafe.Sizeof(btrfs_ioctl_ino_path_args{})) 606 | _BTRFS_IOC_SET_RECEIVED_SUBVOL = ioctl.IOWR(ioctlMagic, 37, unsafe.Sizeof(btrfs_ioctl_received_subvol_args{})) 607 | _BTRFS_IOC_SEND = ioctl.IOW(ioctlMagic, 38, unsafe.Sizeof(btrfs_ioctl_send_args{})) 608 | _BTRFS_IOC_DEVICES_READY = ioctl.IOR(ioctlMagic, 39, unsafe.Sizeof(btrfs_ioctl_vol_args{})) 609 | _BTRFS_IOC_QUOTA_CTL = ioctl.IOWR(ioctlMagic, 40, unsafe.Sizeof(btrfs_ioctl_quota_ctl_args{})) 610 | _BTRFS_IOC_QGROUP_ASSIGN = ioctl.IOW(ioctlMagic, 41, unsafe.Sizeof(btrfs_ioctl_qgroup_assign_args{})) 611 | _BTRFS_IOC_QGROUP_CREATE = ioctl.IOW(ioctlMagic, 42, unsafe.Sizeof(btrfs_ioctl_qgroup_create_args{})) 612 | _BTRFS_IOC_QGROUP_LIMIT = ioctl.IOR(ioctlMagic, 43, unsafe.Sizeof(btrfs_ioctl_qgroup_limit_args{})) 613 | _BTRFS_IOC_QUOTA_RESCAN = ioctl.IOW(ioctlMagic, 44, unsafe.Sizeof(btrfs_ioctl_quota_rescan_args{})) 614 | _BTRFS_IOC_QUOTA_RESCAN_STATUS = ioctl.IOR(ioctlMagic, 45, unsafe.Sizeof(btrfs_ioctl_quota_rescan_args{})) 615 | _BTRFS_IOC_QUOTA_RESCAN_WAIT = ioctl.IO(ioctlMagic, 46) 616 | _BTRFS_IOC_GET_FSLABEL = ioctl.IOR(ioctlMagic, 49, labelSize) 617 | _BTRFS_IOC_SET_FSLABEL = ioctl.IOW(ioctlMagic, 50, labelSize) 618 | _BTRFS_IOC_GET_DEV_STATS = ioctl.IOWR(ioctlMagic, 52, unsafe.Sizeof(btrfs_ioctl_get_dev_stats{})) 619 | _BTRFS_IOC_DEV_REPLACE = ioctl.IOWR(ioctlMagic, 53, unsafe.Sizeof(btrfs_ioctl_dev_replace_args_u1{})) 620 | _BTRFS_IOC_FILE_EXTENT_SAME = ioctl.IOWR(ioctlMagic, 54, unsafe.Sizeof(btrfs_ioctl_same_args{})) 621 | _BTRFS_IOC_GET_FEATURES = ioctl.IOR(ioctlMagic, 57, unsafe.Sizeof(btrfs_ioctl_feature_flags{})) 622 | _BTRFS_IOC_SET_FEATURES = ioctl.IOW(ioctlMagic, 57, unsafe.Sizeof([2]btrfs_ioctl_feature_flags{})) 623 | _BTRFS_IOC_GET_SUPPORTED_FEATURES = ioctl.IOR(ioctlMagic, 57, unsafe.Sizeof([3]btrfs_ioctl_feature_flags{})) 624 | ) 625 | 626 | func iocSnapCreate(f *os.File, in *btrfs_ioctl_vol_args) error { 627 | return ioctl.Do(f, _BTRFS_IOC_SNAP_CREATE, in) 628 | } 629 | 630 | func iocSnapCreateV2(f *os.File, in *btrfs_ioctl_vol_args_v2) error { 631 | return ioctl.Do(f, _BTRFS_IOC_SNAP_CREATE_V2, in) 632 | } 633 | 634 | func iocDefrag(f *os.File, out *btrfs_ioctl_vol_args) error { 635 | return ioctl.Do(f, _BTRFS_IOC_DEFRAG, out) 636 | } 637 | 638 | func iocResize(f *os.File, in *btrfs_ioctl_vol_args) error { 639 | return ioctl.Do(f, _BTRFS_IOC_RESIZE, in) 640 | } 641 | 642 | func iocScanDev(f *os.File, out *btrfs_ioctl_vol_args) error { 643 | return ioctl.Do(f, _BTRFS_IOC_SCAN_DEV, out) 644 | } 645 | 646 | func iocTransStart(f *os.File) error { 647 | return ioctl.Do(f, _BTRFS_IOC_TRANS_START, nil) 648 | } 649 | 650 | func iocTransEnd(f *os.File) error { 651 | return ioctl.Do(f, _BTRFS_IOC_TRANS_END, nil) 652 | } 653 | 654 | func iocSync(f *os.File) error { 655 | return ioctl.Do(f, _BTRFS_IOC_SYNC, nil) 656 | } 657 | 658 | func iocClone(dst, src *os.File) error { 659 | return ioctl.Ioctl(dst, _BTRFS_IOC_CLONE, src.Fd()) 660 | } 661 | 662 | func iocAddDev(f *os.File, out *btrfs_ioctl_vol_args) error { 663 | return ioctl.Do(f, _BTRFS_IOC_ADD_DEV, out) 664 | } 665 | 666 | func iocRmDev(f *os.File, out *btrfs_ioctl_vol_args) error { 667 | return ioctl.Do(f, _BTRFS_IOC_RM_DEV, out) 668 | } 669 | 670 | func iocBalance(f *os.File, out *btrfs_ioctl_vol_args) error { 671 | return ioctl.Do(f, _BTRFS_IOC_BALANCE, out) 672 | } 673 | 674 | func iocCloneRange(f *os.File, out *btrfs_ioctl_clone_range_args) error { 675 | return ioctl.Do(f, _BTRFS_IOC_CLONE_RANGE, out) 676 | } 677 | 678 | func iocSubvolCreate(f *os.File, in *btrfs_ioctl_vol_args) error { 679 | return ioctl.Do(f, _BTRFS_IOC_SUBVOL_CREATE, in) 680 | } 681 | 682 | func iocSubvolCreateV2(f *os.File, in *btrfs_ioctl_vol_args_v2) error { 683 | return ioctl.Do(f, _BTRFS_IOC_SUBVOL_CREATE, in) 684 | } 685 | 686 | func iocSnapDestroy(f *os.File, in *btrfs_ioctl_vol_args) error { 687 | return ioctl.Do(f, _BTRFS_IOC_SNAP_DESTROY, in) 688 | } 689 | 690 | func iocDefragRange(f *os.File, out *btrfs_ioctl_defrag_range_args) error { 691 | return ioctl.Do(f, _BTRFS_IOC_DEFRAG_RANGE, out) 692 | } 693 | 694 | func iocTreeSearch(f *os.File, out *btrfs_ioctl_search_args) error { 695 | return ioctl.Do(f, _BTRFS_IOC_TREE_SEARCH, out) 696 | } 697 | 698 | func iocInoLookup(f *os.File, out *btrfs_ioctl_ino_lookup_args) error { 699 | return ioctl.Do(f, _BTRFS_IOC_INO_LOOKUP, out) 700 | } 701 | 702 | func iocDefaultSubvol(f *os.File, out *uint64) error { 703 | return ioctl.Do(f, _BTRFS_IOC_DEFAULT_SUBVOL, out) 704 | } 705 | 706 | type spaceFlags uint64 707 | 708 | func (f spaceFlags) BlockGroup() blockGroup { 709 | return blockGroup(f) & _BTRFS_BLOCK_GROUP_MASK 710 | } 711 | 712 | type spaceInfo struct { 713 | Flags spaceFlags 714 | TotalBytes uint64 715 | UsedBytes uint64 716 | } 717 | 718 | func iocSpaceInfo(f *os.File) ([]spaceInfo, error) { 719 | arg := &btrfs_ioctl_space_args{} 720 | if err := ioctl.Do(f, _BTRFS_IOC_SPACE_INFO, arg); err != nil { 721 | return nil, err 722 | } 723 | n := arg.total_spaces 724 | if n == 0 { 725 | return nil, nil 726 | } 727 | const ( 728 | argSize = unsafe.Sizeof(btrfs_ioctl_space_args{}) 729 | infoSize = unsafe.Sizeof(btrfs_ioctl_space_info{}) 730 | ) 731 | buf := make([]byte, argSize+uintptr(n)*infoSize) 732 | basePtr := unsafe.Pointer(&buf[0]) 733 | arg = (*btrfs_ioctl_space_args)(basePtr) 734 | arg.space_slots = n 735 | if err := ioctl.Do(f, _BTRFS_IOC_SPACE_INFO, arg); err != nil { 736 | return nil, err 737 | } else if arg.total_spaces == 0 { 738 | return nil, nil 739 | } 740 | if n > arg.total_spaces { 741 | n = arg.total_spaces 742 | } 743 | out := make([]spaceInfo, n) 744 | ptr := uintptr(basePtr) + argSize 745 | for i := 0; i < int(n); i++ { 746 | info := (*btrfs_ioctl_space_info)(unsafe.Pointer(ptr)) 747 | out[i] = spaceInfo{ 748 | Flags: spaceFlags(info.flags), 749 | TotalBytes: info.total_bytes, 750 | UsedBytes: info.used_bytes, 751 | } 752 | ptr += infoSize 753 | } 754 | return out, nil 755 | } 756 | 757 | func iocStartSync(f *os.File, out *uint64) error { 758 | return ioctl.Do(f, _BTRFS_IOC_START_SYNC, out) 759 | } 760 | 761 | func iocWaitSync(f *os.File, out *uint64) error { 762 | return ioctl.Do(f, _BTRFS_IOC_WAIT_SYNC, out) 763 | } 764 | 765 | func iocSubvolGetflags(f *os.File) (out SubvolFlags, err error) { 766 | err = ioctl.Do(f, _BTRFS_IOC_SUBVOL_GETFLAGS, &out) 767 | return 768 | } 769 | 770 | func iocSubvolSetflags(f *os.File, flags SubvolFlags) error { 771 | v := uint64(flags) 772 | return ioctl.Do(f, _BTRFS_IOC_SUBVOL_SETFLAGS, &v) 773 | } 774 | 775 | func iocScrub(f *os.File, out *btrfs_ioctl_scrub_args) error { 776 | return ioctl.Do(f, _BTRFS_IOC_SCRUB, out) 777 | } 778 | 779 | func iocScrubCancel(f *os.File) error { 780 | return ioctl.Do(f, _BTRFS_IOC_SCRUB_CANCEL, nil) 781 | } 782 | 783 | func iocScrubProgress(f *os.File, out *btrfs_ioctl_scrub_args) error { 784 | return ioctl.Do(f, _BTRFS_IOC_SCRUB_PROGRESS, out) 785 | } 786 | 787 | func iocFsInfo(f *os.File) (out btrfs_ioctl_fs_info_args, err error) { 788 | err = ioctl.Do(f, _BTRFS_IOC_FS_INFO, &out) 789 | return 790 | } 791 | 792 | func iocDevInfo(f *os.File, devid uint64, uuid UUID) (out btrfs_ioctl_dev_info_args, err error) { 793 | out.devid = devid 794 | out.uuid = uuid 795 | err = ioctl.Do(f, _BTRFS_IOC_DEV_INFO, &out) 796 | return 797 | } 798 | 799 | func iocBalanceV2(f *os.File, out *btrfs_ioctl_balance_args) error { 800 | return ioctl.Do(f, _BTRFS_IOC_BALANCE_V2, out) 801 | } 802 | 803 | func iocBalanceCtl(f *os.File, out *int32) error { 804 | return ioctl.Do(f, _BTRFS_IOC_BALANCE_CTL, out) 805 | } 806 | 807 | func iocBalanceProgress(f *os.File, out *btrfs_ioctl_balance_args) error { 808 | return ioctl.Do(f, _BTRFS_IOC_BALANCE_PROGRESS, out) 809 | } 810 | 811 | func iocInoPaths(f *os.File, out *btrfs_ioctl_ino_path_args) error { 812 | return ioctl.Do(f, _BTRFS_IOC_INO_PATHS, out) 813 | } 814 | 815 | func iocLogicalIno(f *os.File, out *btrfs_ioctl_ino_path_args) error { 816 | return ioctl.Do(f, _BTRFS_IOC_LOGICAL_INO, out) 817 | } 818 | 819 | func iocSetReceivedSubvol(f *os.File, out *btrfs_ioctl_received_subvol_args) error { 820 | return ioctl.Do(f, _BTRFS_IOC_SET_RECEIVED_SUBVOL, out) 821 | } 822 | 823 | func iocSend(f *os.File, in *btrfs_ioctl_send_args) error { 824 | return ioctl.Do(f, _BTRFS_IOC_SEND, in) 825 | } 826 | 827 | func iocDevicesReady(f *os.File, out *btrfs_ioctl_vol_args) error { 828 | return ioctl.Do(f, _BTRFS_IOC_DEVICES_READY, out) 829 | } 830 | 831 | func iocQuotaCtl(f *os.File, out *btrfs_ioctl_quota_ctl_args) error { 832 | return ioctl.Do(f, _BTRFS_IOC_QUOTA_CTL, out) 833 | } 834 | 835 | func iocQgroupAssign(f *os.File, out *btrfs_ioctl_qgroup_assign_args) error { 836 | return ioctl.Do(f, _BTRFS_IOC_QGROUP_ASSIGN, out) 837 | } 838 | 839 | func iocQgroupCreate(f *os.File, out *btrfs_ioctl_qgroup_create_args) error { 840 | return ioctl.Do(f, _BTRFS_IOC_QGROUP_CREATE, out) 841 | } 842 | 843 | func iocQgroupLimit(f *os.File, out *btrfs_ioctl_qgroup_limit_args) error { 844 | return ioctl.Do(f, _BTRFS_IOC_QGROUP_LIMIT, out) 845 | } 846 | 847 | func iocQuotaRescan(f *os.File, out *btrfs_ioctl_quota_rescan_args) error { 848 | return ioctl.Do(f, _BTRFS_IOC_QUOTA_RESCAN, out) 849 | } 850 | 851 | func iocQuotaRescanStatus(f *os.File, out *btrfs_ioctl_quota_rescan_args) error { 852 | return ioctl.Do(f, _BTRFS_IOC_QUOTA_RESCAN_STATUS, out) 853 | } 854 | 855 | func iocQuotaRescanWait(f *os.File) error { 856 | return ioctl.Do(f, _BTRFS_IOC_QUOTA_RESCAN_WAIT, nil) 857 | } 858 | 859 | func iocGetFslabel(f *os.File, out *[labelSize]byte) error { 860 | return ioctl.Do(f, _BTRFS_IOC_GET_FSLABEL, out) 861 | } 862 | 863 | func iocSetFslabel(f *os.File, out *[labelSize]byte) error { 864 | return ioctl.Do(f, _BTRFS_IOC_SET_FSLABEL, out) 865 | } 866 | 867 | func iocGetDevStats(f *os.File, out *btrfs_ioctl_get_dev_stats) error { 868 | return ioctl.Do(f, _BTRFS_IOC_GET_DEV_STATS, out) 869 | } 870 | 871 | //func iocDevReplace(f *os.File, out *btrfs_ioctl_dev_replace_args) error { 872 | // return ioctl.Do(f, _BTRFS_IOC_DEV_REPLACE, out) 873 | //} 874 | 875 | func iocFileExtentSame(f *os.File, out *btrfs_ioctl_same_args) error { 876 | return ioctl.Do(f, _BTRFS_IOC_FILE_EXTENT_SAME, out) 877 | } 878 | 879 | func iocSetFeatures(f *os.File, out *[2]btrfs_ioctl_feature_flags) error { 880 | return ioctl.Do(f, _BTRFS_IOC_SET_FEATURES, out) 881 | } 882 | --------------------------------------------------------------------------------