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  }