forked from RepoMirrors/btrfs
246 lines
6.1 KiB
Go
246 lines
6.1 KiB
Go
package btrfs
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"unsafe"
|
|
)
|
|
|
|
func Send(w io.Writer, parent string, subvols ...string) error {
|
|
if len(subvols) == 0 {
|
|
return nil
|
|
}
|
|
// use first send subvol to determine mount_root
|
|
subvol, err := filepath.Abs(subvols[0])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
mountRoot, err := findMountRoot(subvol)
|
|
if err == os.ErrNotExist {
|
|
return fmt.Errorf("cannot find a mountpoint for %s", subvol)
|
|
} else if err != nil {
|
|
return err
|
|
}
|
|
var (
|
|
cloneSrc []objectID
|
|
parentID objectID
|
|
)
|
|
if parent != "" {
|
|
parent, err = filepath.Abs(parent)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
id, err := getPathRootID(parent)
|
|
if err != nil {
|
|
return fmt.Errorf("cannot get parent root id: %v", err)
|
|
}
|
|
parentID = id
|
|
cloneSrc = append(cloneSrc, id)
|
|
}
|
|
// check all subvolumes
|
|
paths := make([]string, 0, len(subvols))
|
|
for _, sub := range subvols {
|
|
sub, err = filepath.Abs(sub)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
paths = append(paths, sub)
|
|
mount, err := findMountRoot(sub)
|
|
if err != nil {
|
|
return fmt.Errorf("cannot find mount root for %v: %v", sub, err)
|
|
} else if mount != mountRoot {
|
|
return fmt.Errorf("all subvolumes must be from the same filesystem (%s is not)", sub)
|
|
}
|
|
ok, err := IsReadOnly(sub)
|
|
if err != nil {
|
|
return err
|
|
} else if !ok {
|
|
return fmt.Errorf("subvolume %s is not read-only", sub)
|
|
}
|
|
}
|
|
mfs, err := Open(mountRoot, true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer mfs.Close()
|
|
full := len(cloneSrc) == 0
|
|
for i, sub := range paths {
|
|
var rootID objectID
|
|
if !full && parent != "" {
|
|
rel, err := filepath.Rel(mountRoot, sub)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
si, err := subvolSearchByPath(mfs.f, rel)
|
|
if err != nil {
|
|
return fmt.Errorf("cannot find subvolume %s: %v", rel, err)
|
|
}
|
|
rootID = objectID(si.RootID)
|
|
parentID, err = findGoodParent(mfs.f, rootID, cloneSrc)
|
|
if err != nil {
|
|
return fmt.Errorf("cannot find good parent for %v: %v", rel, err)
|
|
}
|
|
}
|
|
fs, err := Open(sub, true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var flags uint64
|
|
if i != 0 { // not first
|
|
flags |= _BTRFS_SEND_FLAG_OMIT_STREAM_HEADER
|
|
}
|
|
if i < len(paths)-1 { // not last
|
|
flags |= _BTRFS_SEND_FLAG_OMIT_END_CMD
|
|
}
|
|
err = send(w, fs.f, parentID, cloneSrc, flags)
|
|
fs.Close()
|
|
if err != nil {
|
|
return fmt.Errorf("error sending %s: %v", sub, err)
|
|
}
|
|
if !full && parent != "" {
|
|
cloneSrc = append(cloneSrc, rootID)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func send(w io.Writer, subvol *os.File, parent objectID, sources []objectID, flags uint64) error {
|
|
pr, pw, err := os.Pipe()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
errc := make(chan error, 1)
|
|
go func() {
|
|
defer pr.Close()
|
|
_, err := io.Copy(w, pr)
|
|
errc <- err
|
|
}()
|
|
fd := pw.Fd()
|
|
wait := func() error {
|
|
pw.Close()
|
|
return <-errc
|
|
}
|
|
args := &btrfs_ioctl_send_args{
|
|
send_fd: int64(fd),
|
|
parent_root: parent,
|
|
flags: flags,
|
|
}
|
|
if len(sources) != 0 {
|
|
args.clone_sources = &sources[0]
|
|
args.clone_sources_count = uint64(len(sources))
|
|
}
|
|
if err := iocSend(subvol, args); err != nil {
|
|
wait()
|
|
return err
|
|
}
|
|
return wait()
|
|
}
|
|
|
|
// readRootItem reads a root item from the tree.
|
|
//
|
|
// TODO(dennwc): support older kernels:
|
|
// In case we detect a root item smaller then sizeof(root_item),
|
|
// we know it's an old version of the root structure and initialize all new fields to zero.
|
|
// The same happens if we detect mismatching generation numbers as then we know the root was
|
|
// once mounted with an older kernel that was not aware of the root item structure change.
|
|
func readRootItem(mnt *os.File, rootID objectID) (*rootItem, error) {
|
|
sk := btrfs_ioctl_search_key{
|
|
tree_id: rootTreeObjectid,
|
|
// There may be more than one ROOT_ITEM key if there are
|
|
// snapshots pending deletion, we have to loop through them.
|
|
min_objectid: rootID,
|
|
max_objectid: rootID,
|
|
min_type: rootItemKey,
|
|
max_type: rootItemKey,
|
|
max_offset: maxUint64,
|
|
max_transid: maxUint64,
|
|
nr_items: 4096,
|
|
}
|
|
for ; sk.min_offset < maxUint64; sk.min_offset++ {
|
|
results, err := treeSearchRaw(mnt, sk)
|
|
if err != nil {
|
|
return nil, err
|
|
} else if len(results) == 0 {
|
|
break
|
|
}
|
|
for _, r := range results {
|
|
sk.min_objectid = r.ObjectID
|
|
sk.min_type = r.Type
|
|
sk.min_offset = r.Offset
|
|
if r.ObjectID > rootID {
|
|
break
|
|
}
|
|
if r.ObjectID == rootID && r.Type == rootItemKey {
|
|
const sz = int(unsafe.Sizeof(btrfs_root_item_raw{}))
|
|
if len(r.Data) > sz {
|
|
return nil, fmt.Errorf("btrfs_root_item is larger than expected; kernel is newer than the library")
|
|
} else if len(r.Data) < sz { // TODO
|
|
return nil, fmt.Errorf("btrfs_root_item is smaller then expected; kernel version is too old")
|
|
}
|
|
p := asRootItem(r.Data).Decode()
|
|
return &p, nil
|
|
}
|
|
}
|
|
results = nil
|
|
if sk.min_type != rootItemKey || sk.min_objectid != rootID {
|
|
break
|
|
}
|
|
}
|
|
return nil, ErrNotFound
|
|
}
|
|
|
|
func getParent(mnt *os.File, rootID objectID) (*SubvolInfo, error) {
|
|
st, err := subvolSearchByRootID(mnt, rootID, "")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("cannot find subvolume %d to determine parent: %v", rootID, err)
|
|
}
|
|
return subvolSearchByUUID(mnt, st.ParentUUID)
|
|
}
|
|
|
|
func findGoodParent(mnt *os.File, rootID objectID, cloneSrc []objectID) (objectID, error) {
|
|
parent, err := getParent(mnt, rootID)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("get parent failed: %v", err)
|
|
}
|
|
for _, id := range cloneSrc {
|
|
if id == objectID(parent.RootID) {
|
|
return objectID(parent.RootID), nil
|
|
}
|
|
}
|
|
var (
|
|
bestParent *SubvolInfo
|
|
bestDiff uint64 = maxUint64
|
|
)
|
|
for _, id := range cloneSrc {
|
|
parent2, err := getParent(mnt, id)
|
|
if err == ErrNotFound {
|
|
continue
|
|
} else if err != nil {
|
|
return 0, err
|
|
}
|
|
if parent2.RootID != parent.RootID {
|
|
continue
|
|
}
|
|
parent2, err = subvolSearchByRootID(mnt, id, "")
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
diff := int64(parent2.CTransID - parent.CTransID)
|
|
if diff < 0 {
|
|
diff = -diff
|
|
}
|
|
if uint64(diff) < bestDiff {
|
|
bestParent, bestDiff = parent2, uint64(diff)
|
|
}
|
|
}
|
|
if bestParent != nil {
|
|
return objectID(bestParent.RootID), nil
|
|
}
|
|
if !parent.ParentUUID.IsZero() {
|
|
return findGoodParent(mnt, objectID(parent.RootID), cloneSrc)
|
|
}
|
|
return 0, ErrNotFound
|
|
}
|