github.com/dennwc/btrfs@v0.0.0-20221026161108-3097362dc072/subvolume.go (about) 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 UUID UUID 257 ParentUUID UUID 258 ReceivedUUID UUID 259 260 CTime time.Time 261 OTime time.Time 262 STime time.Time 263 RTime time.Time 264 265 CTransID uint64 266 OTransID uint64 267 STransID uint64 268 RTransID uint64 269 270 Path string 271 } 272 273 func (s *SubvolInfo) fillFromItem(it *rootItem) { 274 s.UUID = it.UUID 275 s.ReceivedUUID = it.ReceivedUUID 276 s.ParentUUID = it.ParentUUID 277 278 s.CTime = it.CTime 279 s.OTime = it.OTime 280 s.STime = it.STime 281 s.RTime = it.RTime 282 283 s.CTransID = it.CTransID 284 s.OTransID = it.OTransID 285 s.STransID = it.STransID 286 s.RTransID = it.RTransID 287 } 288 289 func subvolSearchByUUID(mnt *os.File, uuid UUID) (*SubvolInfo, error) { 290 id, err := lookupUUIDSubvolItem(mnt, uuid) 291 if err != nil { 292 return nil, err 293 } 294 return subvolSearchByRootID(mnt, id, "") 295 } 296 297 func subvolSearchByReceivedUUID(mnt *os.File, uuid UUID) (*SubvolInfo, error) { 298 id, err := lookupUUIDReceivedSubvolItem(mnt, uuid) 299 if err != nil { 300 return nil, err 301 } 302 return subvolSearchByRootID(mnt, id, "") 303 } 304 305 func subvolSearchByPath(mnt *os.File, path string) (*SubvolInfo, error) { 306 if !filepath.IsAbs(path) { 307 path = filepath.Join(mnt.Name(), path) 308 } 309 id, err := getPathRootID(path) 310 if err != nil { 311 return nil, err 312 } 313 return subvolSearchByRootID(mnt, id, path) 314 } 315 316 func subvolidResolve(mnt *os.File, subvolID objectID) (string, error) { 317 return subvolidResolveSub(mnt, "", subvolID) 318 } 319 320 func subvolidResolveSub(mnt *os.File, path string, subvolID objectID) (string, error) { 321 if subvolID == fsTreeObjectid { 322 return "", nil 323 } 324 sk := btrfs_ioctl_search_key{ 325 tree_id: rootTreeObjectid, 326 min_objectid: subvolID, 327 max_objectid: subvolID, 328 min_type: rootBackrefKey, 329 max_type: rootBackrefKey, 330 max_offset: maxUint64, 331 max_transid: maxUint64, 332 nr_items: 1, 333 } 334 results, err := treeSearchRaw(mnt, sk) 335 if err != nil { 336 return "", err 337 } else if len(results) < 1 { 338 return "", ErrNotFound 339 } 340 res := results[0] 341 if objectID(res.Offset) != fsTreeObjectid { 342 spath, err := subvolidResolveSub(mnt, path, objectID(res.Offset)) 343 if err != nil { 344 return "", err 345 } 346 path = spath + "/" 347 } 348 backRef := asRootRef(res.Data) 349 if backRef.DirID != firstFreeObjectid { 350 arg := btrfs_ioctl_ino_lookup_args{ 351 treeid: objectID(res.Offset), 352 objectid: backRef.DirID, 353 } 354 if err := iocInoLookup(mnt, &arg); err != nil { 355 return "", err 356 } 357 path += arg.Name() 358 } 359 return path + backRef.Name, nil 360 } 361 362 // subvolSearchByRootID 363 // 364 // Path is optional, and will be resolved automatically if not set. 365 func subvolSearchByRootID(mnt *os.File, rootID objectID, path string) (*SubvolInfo, error) { 366 robj, err := readRootItem(mnt, rootID) 367 if err != nil { 368 return nil, err 369 } 370 info := &SubvolInfo{ 371 RootID: uint64(rootID), 372 Path: path, 373 } 374 info.fillFromItem(robj) 375 if path == "" { 376 info.Path, err = subvolidResolve(mnt, objectID(info.RootID)) 377 } 378 return info, err 379 }