mirror of
https://github.com/dennwc/btrfs
synced 2025-03-11 06:48:27 +00:00
native Recv WIP
This commit is contained in:
parent
6a4b966441
commit
43632657e8
@ -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
|
||||
|
142
receive.go
142
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
|
||||
}
|
||||
|
144
send_h.go
Normal file
144
send_h.go
Normal 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
|
@ -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) {
|
||||
|
Loading…
Reference in New Issue
Block a user