forked from RepoMirrors/btrfs
264 lines
6.0 KiB
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
|
|
}
|