github.com/dennwc/btrfs@v0.0.0-20221026161108-3097362dc072/send.go (about) 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 }