forked from RepoMirrors/btrfs
1
0
Fork 0
btrfs/send.go

246 lines
6.0 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 = 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 == parent.RootID {
return 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 bestParent.RootID, nil
}
if !parent.ParentUUID.IsZero() {
return findGoodParent(mnt, parent.RootID, cloneSrc)
}
return 0, ErrNotFound
}