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{
|
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.",
|
Short: "Receive subvolumes from stdin.",
|
||||||
Long: `Receives one or more subvolumes that were previously
|
Long: `Receives one or more subvolumes that were previously
|
||||||
sent with btrfs send. The received subvolumes are stored
|
sent with btrfs send. The received subvolumes are stored
|
||||||
|
142
receive.go
142
receive.go
@ -2,31 +2,135 @@ package btrfs
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"hash/crc32"
|
||||||
"io"
|
"io"
|
||||||
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"syscall"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Receive(r io.Reader, mount string) error {
|
const nativeReceive = false
|
||||||
// TODO: write a native implementation?
|
|
||||||
//tf, err := ioutil.TempFile("","btrfs_snap")
|
func Receive(r io.Reader, dstDir string) error {
|
||||||
//if err != nil {
|
if !nativeReceive {
|
||||||
// return err
|
buf := bytes.NewBuffer(nil)
|
||||||
//}
|
cmd := exec.Command("btrfs", "receive", dstDir)
|
||||||
//defer func(){
|
cmd.Stdin = r
|
||||||
// name := tf.Name()
|
cmd.Stderr = buf
|
||||||
// tf.Close()
|
if err := cmd.Run(); err != nil {
|
||||||
// os.Remove(name)
|
if buf.Len() != 0 {
|
||||||
//}()
|
return errors.New(buf.String())
|
||||||
buf := bytes.NewBuffer(nil)
|
}
|
||||||
cmd := exec.Command("btrfs", "receive", mount)
|
return err
|
||||||
cmd.Stdin = r
|
|
||||||
cmd.Stderr = buf
|
|
||||||
if err := cmd.Run(); err != nil {
|
|
||||||
if buf.Len() != 0 {
|
|
||||||
return errors.New(buf.String())
|
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
dstDir, err = filepath.Abs(dstDir)
|
||||||
|
if err != nil {
|
||||||
return err
|
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) {
|
func IsSubVolume(path string) (bool, error) {
|
||||||
var st syscall.Stat_t
|
var st syscall.Stat_t
|
||||||
if err := syscall.Stat(path, &st); err != nil {
|
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 ||
|
if st.Ino != firstFreeObjectid ||
|
||||||
st.Mode&syscall.S_IFMT != syscall.S_IFDIR {
|
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
|
// TODO: make SnapshotSubVolume a method on FS to use existing fd
|
||||||
f, err := openDir(subvol)
|
f, err := openDir(subvol)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("cannot open dest dir: %v", err)
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
args := btrfs_ioctl_vol_args_v2{
|
args := btrfs_ioctl_vol_args_v2{
|
||||||
@ -140,7 +140,10 @@ func SnapshotSubVolume(subvol, dst string, ro bool) error {
|
|||||||
// args.qgroup_inherit = inherit
|
// args.qgroup_inherit = inherit
|
||||||
//}
|
//}
|
||||||
copy(args.name[:], newName)
|
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) {
|
func ListSubVolumes(path string) ([]Subvolume, error) {
|
||||||
|
2
utils.go
2
utils.go
@ -14,7 +14,7 @@ import (
|
|||||||
func isBtrfs(path string) (bool, error) {
|
func isBtrfs(path string) (bool, error) {
|
||||||
var stfs syscall.Statfs_t
|
var stfs syscall.Statfs_t
|
||||||
if err := syscall.Statfs(path, &stfs); err != nil {
|
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
|
return stfs.Type == SuperMagic, nil
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user