From 43632657e8ca98f6b726f6a7acb553c1c09e99a7 Mon Sep 17 00:00:00 2001 From: Denys Smirnov Date: Wed, 21 Sep 2016 15:31:08 +0300 Subject: [PATCH] native Recv WIP --- cmd/gbtrfs.go | 2 +- receive.go | 142 ++++++++++++++++++++++++++++++++++++++++++------- send_h.go | 144 ++++++++++++++++++++++++++++++++++++++++++++++++++ subvolume.go | 9 ++-- utils.go | 2 +- 5 files changed, 275 insertions(+), 24 deletions(-) create mode 100644 send_h.go diff --git a/cmd/gbtrfs.go b/cmd/gbtrfs.go index dee48f0..b499077 100644 --- a/cmd/gbtrfs.go +++ b/cmd/gbtrfs.go @@ -96,7 +96,7 @@ var SendCmd = &cobra.Command{ } var ReceiveCmd = &cobra.Command{ - Use: "receive [-ve] [-f ] [--max-errors ] ", + Use: "receive [-v] [-f ] [--max-errors ] ", Short: "Receive subvolumes from stdin.", Long: `Receives one or more subvolumes that were previously sent with btrfs send. The received subvolumes are stored diff --git a/receive.go b/receive.go index 3e2a74a..1136f7b 100644 --- a/receive.go +++ b/receive.go @@ -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 } diff --git a/send_h.go b/send_h.go new file mode 100644 index 0000000..9c93c92 --- /dev/null +++ b/send_h.go @@ -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 diff --git a/subvolume.go b/subvolume.go index 74d8207..1cfe913 100644 --- a/subvolume.go +++ b/subvolume.go @@ -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) { diff --git a/utils.go b/utils.go index 9151aa9..3a76988 100644 --- a/utils.go +++ b/utils.go @@ -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 }