btrfs/subvolume.go

264 lines
6.0 KiB
Go

package btrfs
import (
"fmt"
"os"
"path/filepath"
"strings"
"syscall"
"time"
)
func checkSubVolumeName(name string) bool {
return name != "" && name[0] != 0 && !strings.ContainsRune(name, '/') &&
name != "." && name != ".."
}
func IsSubVolume(path string) (bool, error) {
var st syscall.Stat_t
if err := syscall.Stat(path, &st); err != nil {
return false, err
}
if st.Ino != firstFreeObjectid ||
st.Mode&syscall.S_IFMT != syscall.S_IFDIR {
return false, nil
}
return isBtrfs(path)
}
func CreateSubVolume(path string) error {
var inherit *btrfs_qgroup_inherit // TODO
cpath, err := filepath.Abs(path)
if err != nil {
return err
}
newName := filepath.Base(cpath)
dstDir := filepath.Dir(cpath)
if !checkSubVolumeName(newName) {
return fmt.Errorf("invalid subvolume name: %s", newName)
} else if len(newName) >= volNameMax {
return fmt.Errorf("subvolume name too long: %s", newName)
}
dst, err := openDir(dstDir)
if err != nil {
return err
}
defer dst.Close()
if inherit != nil {
panic("not implemented") // TODO
args := btrfs_ioctl_vol_args_v2{
flags: subvolQGroupInherit,
btrfs_ioctl_vol_args_v2_u1: btrfs_ioctl_vol_args_v2_u1{
//size: qgroup_inherit_size(inherit),
qgroup_inherit: inherit,
},
}
copy(args.name[:], newName)
return iocSubvolCreateV2(dst, &args)
}
var args btrfs_ioctl_vol_args
copy(args.name[:], newName)
return iocSubvolCreate(dst, &args)
}
func DeleteSubVolume(path string) error {
if ok, err := IsSubVolume(path); err != nil {
return err
} else if !ok {
return fmt.Errorf("not a subvolume: %s", path)
}
cpath, err := filepath.Abs(path)
if err != nil {
return err
}
dname := filepath.Dir(cpath)
vname := filepath.Base(cpath)
dir, err := openDir(dname)
if err != nil {
return err
}
defer dir.Close()
var args btrfs_ioctl_vol_args
copy(args.name[:], vname)
return iocSnapDestroy(dir, &args)
}
func SnapshotSubVolume(subvol, dst string, ro bool) error {
if ok, err := IsSubVolume(subvol); err != nil {
return err
} else if !ok {
return fmt.Errorf("not a subvolume: %s", subvol)
}
exists := false
if st, err := os.Stat(dst); err != nil && !os.IsNotExist(err) {
return err
} else if err == nil {
if !st.IsDir() {
return fmt.Errorf("'%s' exists and it is not a directory", dst)
}
exists = true
}
var (
newName string
dstDir string
)
if exists {
newName = filepath.Base(subvol)
dstDir = dst
} else {
newName = filepath.Base(dst)
dstDir = filepath.Dir(dst)
}
if !checkSubVolumeName(newName) {
return fmt.Errorf("invalid snapshot name '%s'", newName)
} else if len(newName) >= volNameMax {
return fmt.Errorf("snapshot name too long '%s'", newName)
}
fdst, err := openDir(dstDir)
if err != nil {
return err
}
defer fdst.Close()
// TODO: make SnapshotSubVolume a method on FS to use existing fd
f, err := openDir(subvol)
if err != nil {
return err
}
defer f.Close()
args := btrfs_ioctl_vol_args_v2{
fd: int64(f.Fd()),
}
if ro {
args.flags |= subvolReadOnly
}
// TODO
//if inherit != nil {
// args.flags |= subvolQGroupInherit
// args.size = qgroup_inherit_size(inherit)
// args.qgroup_inherit = inherit
//}
copy(args.name[:], newName)
return iocSnapCreateV2(fdst, &args)
}
func ListSubVolumes(path string) ([]Subvolume, error) {
f, err := openDir(path)
if err != nil {
return nil, err
}
defer f.Close()
//root, err := getPathRootID(f)
//if err != nil {
// return nil, fmt.Errorf("can't get rootid for '%s': %v", path, err)
//}
m, err := listSubVolumes(f)
if err != nil {
return nil, err
}
out := make([]Subvolume, 0, len(m))
for _, v := range m {
out = append(out, v)
}
return out, nil
}
type Subvolume struct {
ObjectID uint64
TransID uint64
Name string
RefTree uint64
DirID uint64
Gen uint64
OGen uint64
Flags uint64
UUID UUID
ParentUUID UUID
ReceivedUUID UUID
OTime time.Time
CTime time.Time
}
func listSubVolumes(f *os.File) (map[uint64]Subvolume, error) {
sk := btrfs_ioctl_search_key{
// search in the tree of tree roots
tree_id: 1,
// Set the min and max to backref keys. The search will
// only send back this type of key now.
min_type: rootBackrefKey,
max_type: rootBackrefKey,
min_objectid: firstFreeObjectid,
// Set all the other params to the max, we'll take any objectid
// and any trans.
max_objectid: lastFreeObjectid,
max_offset: maxUint64,
max_transid: maxUint64,
nr_items: 4096, // just a big number, doesn't matter much
}
m := make(map[uint64]Subvolume)
for {
out, err := treeSearchRaw(f, sk)
if err != nil {
return nil, err
} else if len(out) == 0 {
break
}
for _, obj := range out {
switch obj.Type {
case rootBackrefKey:
ref := asRootRef(obj.Data)
o := m[obj.ObjectID]
o.TransID = obj.TransID
o.ObjectID = obj.ObjectID
o.RefTree = obj.Offset
o.DirID = ref.DirID
o.Name = ref.Name
m[obj.ObjectID] = o
case rootItemKey:
o := m[obj.ObjectID]
o.TransID = obj.TransID
o.ObjectID = obj.ObjectID
// TODO: decode whole object?
o.Gen = asUint64(obj.Data[160:]) // size of btrfs_inode_item
o.Flags = asUint64(obj.Data[160+6*8:])
const sz = 439
const toff = sz - 8*8 - 4*12
o.CTime = asTime(obj.Data[toff+0*12:])
o.OTime = asTime(obj.Data[toff+1*12:])
o.OGen = asUint64(obj.Data[toff-3*8:])
const uoff = toff - 4*8 - 3*UUIDSize
copy(o.UUID[:], obj.Data[uoff+0*UUIDSize:])
copy(o.ParentUUID[:], obj.Data[uoff+1*UUIDSize:])
copy(o.ReceivedUUID[:], obj.Data[uoff+2*UUIDSize:])
m[obj.ObjectID] = o
}
}
// record the mins in key so we can make sure the
// next search doesn't repeat this root
last := out[len(out)-1]
sk.min_objectid = last.ObjectID
sk.min_type = last.Type
sk.min_offset = last.Offset + 1
if sk.min_offset == 0 { // overflow
sk.min_type++
} else {
continue
}
if sk.min_type > rootBackrefKey {
sk.min_type = rootItemKey
sk.min_objectid++
} else {
continue
}
if sk.min_objectid > sk.max_objectid {
break
}
}
return m, nil
}