2016-09-15 17:39:46 +00:00
|
|
|
package btrfs
|
|
|
|
|
|
|
|
import (
|
2016-09-19 17:01:22 +00:00
|
|
|
"fmt"
|
2016-09-15 17:39:46 +00:00
|
|
|
"io"
|
|
|
|
"os"
|
2016-09-19 17:01:22 +00:00
|
|
|
"path/filepath"
|
2016-09-15 17:39:46 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
func Send(w io.Writer, parent string, subvols ...string) error {
|
|
|
|
if len(subvols) == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
2016-09-19 17:01:22 +00:00
|
|
|
// 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
|
2016-09-15 17:39:46 +00:00
|
|
|
}
|
2016-09-19 17:01:22 +00:00
|
|
|
var (
|
|
|
|
cloneSrc []uint64
|
|
|
|
parentID uint64
|
|
|
|
)
|
2016-09-15 17:39:46 +00:00
|
|
|
if parent != "" {
|
2016-09-19 17:01:22 +00:00
|
|
|
parent, err = filepath.Abs(parent)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
f, err := os.Open(parent)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("cannot open parent: %v", err)
|
|
|
|
}
|
|
|
|
id, err := getPathRootID(f)
|
|
|
|
f.Close()
|
|
|
|
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 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)
|
|
|
|
}
|
2016-09-15 17:39:46 +00:00
|
|
|
}
|
2016-09-19 17:01:22 +00:00
|
|
|
//full := len(cloneSrc) == 0
|
|
|
|
for i, sub := range paths {
|
|
|
|
//if len(cloneSrc) > 1 {
|
|
|
|
// // TODO: find_good_parent
|
|
|
|
//}
|
|
|
|
//if !full { // TODO
|
|
|
|
// cloneSrc = append(cloneSrc, )
|
|
|
|
//}
|
|
|
|
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)
|
2016-10-01 11:44:34 +00:00
|
|
|
fs.Close()
|
2016-09-19 17:01:22 +00:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("error sending %s: %v", sub, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func send(w io.Writer, subvol *os.File, parent uint64, sources []uint64, flags uint64) error {
|
|
|
|
pr, pw, err := os.Pipe()
|
2016-09-15 17:39:46 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2016-09-19 17:01:22 +00:00
|
|
|
errc := make(chan error, 1)
|
|
|
|
go func() {
|
|
|
|
defer pr.Close()
|
|
|
|
_, err := io.Copy(w, pr)
|
|
|
|
errc <- err
|
2016-09-15 17:39:46 +00:00
|
|
|
}()
|
2016-09-19 17:01:22 +00:00
|
|
|
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()
|
2016-09-15 17:39:46 +00:00
|
|
|
return err
|
|
|
|
}
|
2016-09-19 17:01:22 +00:00
|
|
|
return wait()
|
2016-09-15 17:39:46 +00:00
|
|
|
}
|