native Recv WIP

This commit is contained in:
Denys Smirnov 2016-09-21 15:31:08 +03:00
parent 6a4b966441
commit 43632657e8
5 changed files with 275 additions and 24 deletions

View File

@ -96,7 +96,7 @@ var SendCmd = &cobra.Command{
}
var ReceiveCmd = &cobra.Command{
Use: "receive [-ve] [-f <infile>] [--max-errors <N>] <mount>",
Use: "receive [-v] [-f <infile>] [--max-errors <N>] <mount>",
Short: "Receive subvolumes from stdin.",
Long: `Receives one or more subvolumes that were previously
sent with btrfs send. The received subvolumes are stored

View File

@ -2,31 +2,135 @@ package btrfs
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"hash/crc32"
"io"
"os"
"os/exec"
"path/filepath"
"syscall"
)
func Receive(r io.Reader, mount string) error {
// TODO: write a native implementation?
//tf, err := ioutil.TempFile("","btrfs_snap")
//if err != nil {
// return err
//}
//defer func(){
// name := tf.Name()
// tf.Close()
// os.Remove(name)
//}()
buf := bytes.NewBuffer(nil)
cmd := exec.Command("btrfs", "receive", mount)
cmd.Stdin = r
cmd.Stderr = buf
if err := cmd.Run(); err != nil {
if buf.Len() != 0 {
return errors.New(buf.String())
const nativeReceive = false
func Receive(r io.Reader, dstDir string) error {
if !nativeReceive {
buf := bytes.NewBuffer(nil)
cmd := exec.Command("btrfs", "receive", dstDir)
cmd.Stdin = r
cmd.Stderr = buf
if err := cmd.Run(); err != nil {
if buf.Len() != 0 {
return errors.New(buf.String())
}
return err
}
return nil
}
var err error
dstDir, err = filepath.Abs(dstDir)
if err != nil {
return err
}
return nil
realMnt, err := findMountRoot(dstDir)
if err != nil {
return err
}
dir, err := os.OpenFile(dstDir, os.O_RDONLY|syscall.O_NOATIME, 0755)
if err != nil {
return err
}
mnt, err := os.OpenFile(realMnt, os.O_RDONLY|syscall.O_NOATIME, 0755)
if err != nil {
return err
}
// We want to resolve the path to the subvolume we're sitting in
// so that we can adjust the paths of any subvols we want to receive in.
subvolID, err := getPathRootID(mnt)
if err != nil {
return err
}
sr, err := newStreamReader(r)
if err != nil {
return err
}
_, _, _ = dir, subvolID, sr
panic("not implemented")
}
type streamReader struct {
r io.Reader
hbuf []byte
buf *bytes.Buffer
}
type sendCommandArgs struct {
Type tlvType
Data []byte
}
type sendCommand struct {
Type sendCmd
Args []sendCommandArgs
}
func (sr *streamReader) ReadCommand() (*sendCommand, error) {
sr.buf.Reset()
var h cmdHeader
if sr.hbuf == nil {
sr.hbuf = make([]byte, h.Size())
}
if _, err := io.ReadFull(sr.r, sr.hbuf); err != nil {
return nil, err
} else if err = h.Unmarshal(sr.hbuf); err != nil {
return nil, err
}
if sr.buf == nil {
sr.buf = bytes.NewBuffer(nil)
}
if _, err := io.CopyN(sr.buf, sr.r, int64(h.Len)); err != nil {
return nil, err
}
tbl := crc32.MakeTable(0)
crc := crc32.Checksum(sr.buf.Bytes(), tbl)
if crc != h.Crc {
return nil, fmt.Errorf("crc missmatch in command: %x vs %x", crc, h.Crc)
}
cmd := sendCommand{Type: sendCmd(h.Cmd)}
var th tlvHeader
data := sr.buf.Bytes()
for {
if n := len(data); n < th.Size() {
if n != 0 {
return nil, io.ErrUnexpectedEOF
}
break
}
if err := th.Unmarshal(data); err != nil {
return nil, err
}
data = data[th.Size():]
if th.Type > _BTRFS_SEND_A_MAX { // || th.Len > _BTRFS_SEND_BUF_SIZE {
return nil, fmt.Errorf("invalid tlv in cmd: %+v", th)
}
b := make([]byte, th.Len)
copy(b, data)
cmd.Args = append(cmd.Args, sendCommandArgs{Type: th.Type, Data: b})
}
return &cmd, nil
}
func newStreamReader(r io.Reader) (*streamReader, error) {
buf := make([]byte, sendStreamMagicSize+4)
_, err := io.ReadFull(r, buf)
if err != nil {
return nil, err
} else if bytes.Compare(buf[:sendStreamMagicSize], []byte(_BTRFS_SEND_STREAM_MAGIC)) != 0 {
return nil, errors.New("unexpected stream header")
}
version := binary.LittleEndian.Uint32(buf[sendStreamMagicSize:])
if version > _BTRFS_SEND_STREAM_VERSION {
return nil, fmt.Errorf("stream version %d not supported", version)
}
return &streamReader{r: r}, nil
}

144
send_h.go Normal file
View File

@ -0,0 +1,144 @@
package btrfs
import (
"encoding/binary"
"io"
)
const (
_BTRFS_SEND_STREAM_MAGIC = "btrfs-stream"
sendStreamMagicSize = len(_BTRFS_SEND_STREAM_MAGIC)
_BTRFS_SEND_STREAM_VERSION = 1
)
const (
_BTRFS_SEND_BUF_SIZE = 64 * 1024
_BTRFS_SEND_READ_SIZE = 48 * 1024
)
type tlvType uint16
const (
tlvU8 = tlvType(iota)
tlvU16
tlvU32
tlvU64
tlvBinary
tlvString
tlvUUID
tlvTimespec
)
type streamHeader struct {
Magic [len(_BTRFS_SEND_STREAM_MAGIC)]byte
Version uint32
}
type cmdHeader struct {
Len uint32 // len excluding the header
Cmd uint16
Crc uint32 // crc including the header with zero crc field
}
func (h *cmdHeader) Size() int { return 10 }
func (h *cmdHeader) Unmarshal(p []byte) error {
if len(p) < h.Size() {
return io.ErrUnexpectedEOF
}
h.Len = binary.LittleEndian.Uint32(p[0:])
h.Cmd = binary.LittleEndian.Uint16(p[4:])
h.Crc = binary.LittleEndian.Uint32(p[6:])
return nil
}
type tlvHeader struct {
Type tlvType
Len uint16 // len excluding the header
}
func (h *tlvHeader) Size() int { return 4 }
func (h *tlvHeader) Unmarshal(p []byte) error {
if len(p) < h.Size() {
return io.ErrUnexpectedEOF
}
h.Type = tlvType(binary.LittleEndian.Uint16(p[0:]))
h.Len = binary.LittleEndian.Uint16(p[2:])
return nil
}
type sendCmd uint16
const (
_BTRFS_SEND_C_UNSPEC = sendCmd(iota)
_BTRFS_SEND_C_SUBVOL
_BTRFS_SEND_C_SNAPSHOT
_BTRFS_SEND_C_MKFILE
_BTRFS_SEND_C_MKDIR
_BTRFS_SEND_C_MKNOD
_BTRFS_SEND_C_MKFIFO
_BTRFS_SEND_C_MKSOCK
_BTRFS_SEND_C_SYMLINK
_BTRFS_SEND_C_RENAME
_BTRFS_SEND_C_LINK
_BTRFS_SEND_C_UNLINK
_BTRFS_SEND_C_RMDIR
_BTRFS_SEND_C_SET_XATTR
_BTRFS_SEND_C_REMOVE_XATTR
_BTRFS_SEND_C_WRITE
_BTRFS_SEND_C_CLONE
_BTRFS_SEND_C_TRUNCATE
_BTRFS_SEND_C_CHMOD
_BTRFS_SEND_C_CHOWN
_BTRFS_SEND_C_UTIMES
_BTRFS_SEND_C_END
_BTRFS_SEND_C_UPDATE_EXTENT
__BTRFS_SEND_C_MAX
)
const _BTRFS_SEND_C_MAX = __BTRFS_SEND_C_MAX - 1
type sendCmdAttr uint16
const (
_BTRFS_SEND_A_UNSPEC = iota
_BTRFS_SEND_A_UUID
_BTRFS_SEND_A_CTRANSID
_BTRFS_SEND_A_INO
_BTRFS_SEND_A_SIZE
_BTRFS_SEND_A_MODE
_BTRFS_SEND_A_UID
_BTRFS_SEND_A_GID
_BTRFS_SEND_A_RDEV
_BTRFS_SEND_A_CTIME
_BTRFS_SEND_A_MTIME
_BTRFS_SEND_A_ATIME
_BTRFS_SEND_A_OTIME
_BTRFS_SEND_A_XATTR_NAME
_BTRFS_SEND_A_XATTR_DATA
_BTRFS_SEND_A_PATH
_BTRFS_SEND_A_PATH_TO
_BTRFS_SEND_A_PATH_LINK
_BTRFS_SEND_A_FILE_OFFSET
_BTRFS_SEND_A_DATA
_BTRFS_SEND_A_CLONE_UUID
_BTRFS_SEND_A_CLONE_CTRANSID
_BTRFS_SEND_A_CLONE_PATH
_BTRFS_SEND_A_CLONE_OFFSET
_BTRFS_SEND_A_CLONE_LEN
__BTRFS_SEND_A_MAX
)
const _BTRFS_SEND_A_MAX = __BTRFS_SEND_A_MAX - 1

View File

@ -17,7 +17,7 @@ func checkSubVolumeName(name string) bool {
func IsSubVolume(path string) (bool, error) {
var st syscall.Stat_t
if err := syscall.Stat(path, &st); err != nil {
return false, err
return false, &os.PathError{Op: "stat", Path: path, Err: err}
}
if st.Ino != firstFreeObjectid ||
st.Mode&syscall.S_IFMT != syscall.S_IFDIR {
@ -124,7 +124,7 @@ func SnapshotSubVolume(subvol, dst string, ro bool) error {
// TODO: make SnapshotSubVolume a method on FS to use existing fd
f, err := openDir(subvol)
if err != nil {
return err
return fmt.Errorf("cannot open dest dir: %v", err)
}
defer f.Close()
args := btrfs_ioctl_vol_args_v2{
@ -140,7 +140,10 @@ func SnapshotSubVolume(subvol, dst string, ro bool) error {
// args.qgroup_inherit = inherit
//}
copy(args.name[:], newName)
return iocSnapCreateV2(fdst, &args)
if err := iocSnapCreateV2(fdst, &args); err != nil {
return fmt.Errorf("ioc failed: %v", err)
}
return nil
}
func ListSubVolumes(path string) ([]Subvolume, error) {

View File

@ -14,7 +14,7 @@ import (
func isBtrfs(path string) (bool, error) {
var stfs syscall.Statfs_t
if err := syscall.Statfs(path, &stfs); err != nil {
return false, err
return false, &os.PathError{Op: "statfs", Path: path, Err: err}
}
return stfs.Type == SuperMagic, nil
}