forked from RepoMirrors/btrfs
485 lines
10 KiB
Go
485 lines
10 KiB
Go
package send
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"github.com/dennwc/btrfs"
|
|
"io"
|
|
"io/ioutil"
|
|
"time"
|
|
)
|
|
|
|
func NewStreamReader(r io.Reader) (*StreamReader, error) {
|
|
// read magic and version
|
|
buf := make([]byte, len(sendStreamMagic)+4)
|
|
_, err := io.ReadFull(r, buf)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("cannot read magic: %v", err)
|
|
} else if string(buf[:sendStreamMagicSize]) != sendStreamMagic {
|
|
return nil, errors.New("unexpected stream header")
|
|
}
|
|
version := sendEndianess.Uint32(buf[sendStreamMagicSize:])
|
|
if version != sendStreamVersion {
|
|
return nil, fmt.Errorf("stream version %d not supported", version)
|
|
}
|
|
return &StreamReader{r: r}, nil
|
|
}
|
|
|
|
type StreamReader struct {
|
|
r io.Reader
|
|
buf [cmdHeaderSize]byte
|
|
}
|
|
|
|
func (r *StreamReader) readCmdHeader() (h cmdHeader, err error) {
|
|
_, err = io.ReadFull(r.r, r.buf[:cmdHeaderSize])
|
|
if err == io.EOF {
|
|
return
|
|
} else if err != nil {
|
|
err = fmt.Errorf("cannot read command header: %v", err)
|
|
return
|
|
}
|
|
err = h.Unmarshal(r.buf[:cmdHeaderSize])
|
|
// TODO: check CRC
|
|
return
|
|
}
|
|
|
|
type SendTLV struct {
|
|
Attr sendCmdAttr
|
|
Val interface{}
|
|
}
|
|
|
|
func (r *StreamReader) readTLV(rd io.Reader) (*SendTLV, error) {
|
|
_, err := io.ReadFull(rd, r.buf[:tlvHeaderSize])
|
|
if err == io.EOF {
|
|
return nil, err
|
|
} else if err != nil {
|
|
return nil, fmt.Errorf("cannot read tlv header: %v", err)
|
|
}
|
|
var h tlvHeader
|
|
if err = h.Unmarshal(r.buf[:tlvHeaderSize]); err != nil {
|
|
return nil, err
|
|
}
|
|
typ := sendCmdAttr(h.Type)
|
|
if sendCmdAttr(typ) > sendAttrMax { // || th.Len > _BTRFS_SEND_BUF_SIZE {
|
|
return nil, fmt.Errorf("invalid tlv in cmd: %q", typ)
|
|
}
|
|
buf := make([]byte, h.Len)
|
|
_, err = io.ReadFull(rd, buf)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("cannot read tlv: %v", err)
|
|
}
|
|
var v interface{}
|
|
switch typ {
|
|
case sendAttrCtransid, sendAttrCloneCtransid,
|
|
sendAttrUid, sendAttrGid, sendAttrMode,
|
|
sendAttrIno, sendAttrFileOffset, sendAttrSize,
|
|
sendAttrCloneOffset, sendAttrCloneLen:
|
|
if len(buf) != 8 {
|
|
return nil, fmt.Errorf("unexpected int64 size: %v", h.Len)
|
|
}
|
|
v = sendEndianess.Uint64(buf[:8])
|
|
case sendAttrPath, sendAttrPathTo, sendAttrClonePath, sendAttrXattrName:
|
|
v = string(buf)
|
|
case sendAttrData, sendAttrXattrData:
|
|
v = buf
|
|
case sendAttrUuid, sendAttrCloneUuid:
|
|
if h.Len != btrfs.UUIDSize {
|
|
return nil, fmt.Errorf("unexpected UUID size: %v", h.Len)
|
|
}
|
|
var u btrfs.UUID
|
|
copy(u[:], buf)
|
|
v = u
|
|
case sendAttrAtime, sendAttrMtime, sendAttrCtime, sendAttrOtime:
|
|
if h.Len != 12 {
|
|
return nil, fmt.Errorf("unexpected timestamp size: %v", h.Len)
|
|
}
|
|
v = time.Unix( // btrfs_timespec
|
|
int64(sendEndianess.Uint64(buf[:8])),
|
|
int64(sendEndianess.Uint32(buf[8:])),
|
|
)
|
|
default:
|
|
return nil, fmt.Errorf("unsupported tlv type: %v (len: %v)", typ, h.Len)
|
|
}
|
|
return &SendTLV{Attr: typ, Val: v}, nil
|
|
}
|
|
func (r *StreamReader) ReadCommand() (_ Cmd, gerr error) {
|
|
h, err := r.readCmdHeader()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var tlvs []SendTLV
|
|
rd := io.LimitReader(r.r, int64(h.Len))
|
|
defer io.Copy(ioutil.Discard, rd)
|
|
for {
|
|
tlv, err := r.readTLV(rd)
|
|
if err == io.EOF {
|
|
break
|
|
} else if err != nil {
|
|
return nil, fmt.Errorf("command %v: %v", h.Cmd, err)
|
|
}
|
|
tlvs = append(tlvs, *tlv)
|
|
}
|
|
var c Cmd
|
|
switch h.Cmd {
|
|
case sendCmdEnd:
|
|
c = &StreamEnd{}
|
|
case sendCmdSubvol:
|
|
c = &SubvolCmd{}
|
|
case sendCmdSnapshot:
|
|
c = &SnapshotCmd{}
|
|
case sendCmdChown:
|
|
c = &ChownCmd{}
|
|
case sendCmdChmod:
|
|
c = &ChmodCmd{}
|
|
case sendCmdUtimes:
|
|
c = &UTimesCmd{}
|
|
case sendCmdMkdir:
|
|
c = &MkdirCmd{}
|
|
case sendCmdRename:
|
|
c = &RenameCmd{}
|
|
case sendCmdMkfile:
|
|
c = &MkfileCmd{}
|
|
case sendCmdWrite:
|
|
c = &WriteCmd{}
|
|
case sendCmdTruncate:
|
|
c = &TruncateCmd{}
|
|
}
|
|
if c == nil {
|
|
return &UnknownSendCmd{Kind: h.Cmd, Params: tlvs}, nil
|
|
}
|
|
if err := c.decode(tlvs); err != nil {
|
|
return nil, err
|
|
}
|
|
return c, nil
|
|
}
|
|
|
|
type errUnexpectedAttrType struct {
|
|
Cmd CmdType
|
|
Val SendTLV
|
|
}
|
|
|
|
func (e errUnexpectedAttrType) Error() string {
|
|
return fmt.Sprintf("unexpected type for %q (in %q): %T",
|
|
e.Val.Attr, e.Cmd, e.Val.Val)
|
|
}
|
|
|
|
type errUnexpectedAttr struct {
|
|
Cmd CmdType
|
|
Val SendTLV
|
|
}
|
|
|
|
func (e errUnexpectedAttr) Error() string {
|
|
return fmt.Sprintf("unexpected attr %q for %q (%T)",
|
|
e.Val.Attr, e.Cmd, e.Val.Val)
|
|
}
|
|
|
|
type Cmd interface {
|
|
Type() CmdType
|
|
decode(tlvs []SendTLV) error
|
|
}
|
|
|
|
type UnknownSendCmd struct {
|
|
Kind CmdType
|
|
Params []SendTLV
|
|
}
|
|
|
|
func (c UnknownSendCmd) Type() CmdType {
|
|
return c.Kind
|
|
}
|
|
func (c *UnknownSendCmd) decode(tlvs []SendTLV) error {
|
|
c.Params = tlvs
|
|
return nil
|
|
}
|
|
|
|
type StreamEnd struct{}
|
|
|
|
func (c StreamEnd) Type() CmdType {
|
|
return sendCmdEnd
|
|
}
|
|
func (c *StreamEnd) decode(tlvs []SendTLV) error {
|
|
if len(tlvs) != 0 {
|
|
return fmt.Errorf("unexpected TLVs for stream end command: %#v", tlvs)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type SubvolCmd struct {
|
|
Path string
|
|
UUID btrfs.UUID
|
|
CTransID uint64
|
|
}
|
|
|
|
func (c SubvolCmd) Type() CmdType {
|
|
return sendCmdSubvol
|
|
}
|
|
func (c *SubvolCmd) decode(tlvs []SendTLV) error {
|
|
for _, tlv := range tlvs {
|
|
var ok bool
|
|
switch tlv.Attr {
|
|
case sendAttrPath:
|
|
c.Path, ok = tlv.Val.(string)
|
|
case sendAttrUuid:
|
|
c.UUID, ok = tlv.Val.(btrfs.UUID)
|
|
case sendAttrCtransid:
|
|
c.CTransID, ok = tlv.Val.(uint64)
|
|
default:
|
|
return errUnexpectedAttr{Val: tlv, Cmd: c.Type()}
|
|
}
|
|
if !ok {
|
|
return errUnexpectedAttrType{Val: tlv, Cmd: c.Type()}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type SnapshotCmd struct {
|
|
Path string
|
|
UUID btrfs.UUID
|
|
CTransID uint64
|
|
CloneUUID btrfs.UUID
|
|
CloneTransID uint64
|
|
}
|
|
|
|
func (c SnapshotCmd) Type() CmdType {
|
|
return sendCmdSnapshot
|
|
}
|
|
func (c *SnapshotCmd) decode(tlvs []SendTLV) error {
|
|
for _, tlv := range tlvs {
|
|
var ok bool
|
|
switch tlv.Attr {
|
|
case sendAttrPath:
|
|
c.Path, ok = tlv.Val.(string)
|
|
case sendAttrUuid:
|
|
c.UUID, ok = tlv.Val.(btrfs.UUID)
|
|
case sendAttrCtransid:
|
|
c.CTransID, ok = tlv.Val.(uint64)
|
|
case sendAttrCloneUuid:
|
|
c.CloneUUID, ok = tlv.Val.(btrfs.UUID)
|
|
case sendAttrCloneCtransid:
|
|
c.CloneTransID, ok = tlv.Val.(uint64)
|
|
default:
|
|
return errUnexpectedAttr{Val: tlv, Cmd: c.Type()}
|
|
}
|
|
if !ok {
|
|
return errUnexpectedAttrType{Val: tlv, Cmd: c.Type()}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type ChownCmd struct {
|
|
Path string
|
|
UID, GID uint64
|
|
}
|
|
|
|
func (c ChownCmd) Type() CmdType {
|
|
return sendCmdChown
|
|
}
|
|
func (c *ChownCmd) decode(tlvs []SendTLV) error {
|
|
for _, tlv := range tlvs {
|
|
var ok bool
|
|
switch tlv.Attr {
|
|
case sendAttrPath:
|
|
c.Path, ok = tlv.Val.(string)
|
|
case sendAttrUid:
|
|
c.UID, ok = tlv.Val.(uint64)
|
|
case sendAttrGid:
|
|
c.GID, ok = tlv.Val.(uint64)
|
|
default:
|
|
return errUnexpectedAttr{Val: tlv, Cmd: c.Type()}
|
|
}
|
|
if !ok {
|
|
return errUnexpectedAttrType{Val: tlv, Cmd: c.Type()}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type ChmodCmd struct {
|
|
Path string
|
|
Mode uint64
|
|
}
|
|
|
|
func (c ChmodCmd) Type() CmdType {
|
|
return sendCmdChmod
|
|
}
|
|
func (c *ChmodCmd) decode(tlvs []SendTLV) error {
|
|
for _, tlv := range tlvs {
|
|
var ok bool
|
|
switch tlv.Attr {
|
|
case sendAttrPath:
|
|
c.Path, ok = tlv.Val.(string)
|
|
case sendAttrMode:
|
|
c.Mode, ok = tlv.Val.(uint64)
|
|
default:
|
|
return errUnexpectedAttr{Val: tlv, Cmd: c.Type()}
|
|
}
|
|
if !ok {
|
|
return errUnexpectedAttrType{Val: tlv, Cmd: c.Type()}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type UTimesCmd struct {
|
|
Path string
|
|
ATime, MTime, CTime time.Time
|
|
}
|
|
|
|
func (c UTimesCmd) Type() CmdType {
|
|
return sendCmdUtimes
|
|
}
|
|
func (c *UTimesCmd) decode(tlvs []SendTLV) error {
|
|
for _, tlv := range tlvs {
|
|
var ok bool
|
|
switch tlv.Attr {
|
|
case sendAttrPath:
|
|
c.Path, ok = tlv.Val.(string)
|
|
case sendAttrAtime:
|
|
c.ATime, ok = tlv.Val.(time.Time)
|
|
case sendAttrMtime:
|
|
c.MTime, ok = tlv.Val.(time.Time)
|
|
case sendAttrCtime:
|
|
c.CTime, ok = tlv.Val.(time.Time)
|
|
default:
|
|
return errUnexpectedAttr{Val: tlv, Cmd: c.Type()}
|
|
}
|
|
if !ok {
|
|
return errUnexpectedAttrType{Val: tlv, Cmd: c.Type()}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type MkdirCmd struct {
|
|
Path string
|
|
Ino uint64
|
|
}
|
|
|
|
func (c MkdirCmd) Type() CmdType {
|
|
return sendCmdMkdir
|
|
}
|
|
func (c *MkdirCmd) decode(tlvs []SendTLV) error {
|
|
for _, tlv := range tlvs {
|
|
var ok bool
|
|
switch tlv.Attr {
|
|
case sendAttrPath:
|
|
c.Path, ok = tlv.Val.(string)
|
|
case sendAttrIno:
|
|
c.Ino, ok = tlv.Val.(uint64)
|
|
default:
|
|
return errUnexpectedAttr{Val: tlv, Cmd: c.Type()}
|
|
}
|
|
if !ok {
|
|
return errUnexpectedAttrType{Val: tlv, Cmd: c.Type()}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type RenameCmd struct {
|
|
From, To string
|
|
}
|
|
|
|
func (c RenameCmd) Type() CmdType {
|
|
return sendCmdRename
|
|
}
|
|
func (c *RenameCmd) decode(tlvs []SendTLV) error {
|
|
for _, tlv := range tlvs {
|
|
var ok bool
|
|
switch tlv.Attr {
|
|
case sendAttrPath:
|
|
c.From, ok = tlv.Val.(string)
|
|
case sendAttrPathTo:
|
|
c.To, ok = tlv.Val.(string)
|
|
default:
|
|
return errUnexpectedAttr{Val: tlv, Cmd: c.Type()}
|
|
}
|
|
if !ok {
|
|
return errUnexpectedAttrType{Val: tlv, Cmd: c.Type()}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type MkfileCmd struct {
|
|
Path string
|
|
Ino uint64
|
|
}
|
|
|
|
func (c MkfileCmd) Type() CmdType {
|
|
return sendCmdMkfile
|
|
}
|
|
func (c *MkfileCmd) decode(tlvs []SendTLV) error {
|
|
for _, tlv := range tlvs {
|
|
var ok bool
|
|
switch tlv.Attr {
|
|
case sendAttrPath:
|
|
c.Path, ok = tlv.Val.(string)
|
|
case sendAttrIno:
|
|
c.Ino, ok = tlv.Val.(uint64)
|
|
default:
|
|
return errUnexpectedAttr{Val: tlv, Cmd: c.Type()}
|
|
}
|
|
if !ok {
|
|
return errUnexpectedAttrType{Val: tlv, Cmd: c.Type()}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type WriteCmd struct {
|
|
Path string
|
|
Off uint64
|
|
Data []byte
|
|
}
|
|
|
|
func (c WriteCmd) Type() CmdType {
|
|
return sendCmdWrite
|
|
}
|
|
func (c *WriteCmd) decode(tlvs []SendTLV) error {
|
|
for _, tlv := range tlvs {
|
|
var ok bool
|
|
switch tlv.Attr {
|
|
case sendAttrPath:
|
|
c.Path, ok = tlv.Val.(string)
|
|
case sendAttrFileOffset:
|
|
c.Off, ok = tlv.Val.(uint64)
|
|
case sendAttrData:
|
|
c.Data, ok = tlv.Val.([]byte)
|
|
default:
|
|
return errUnexpectedAttr{Val: tlv, Cmd: c.Type()}
|
|
}
|
|
if !ok {
|
|
return errUnexpectedAttrType{Val: tlv, Cmd: c.Type()}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type TruncateCmd struct {
|
|
Path string
|
|
Size uint64
|
|
}
|
|
|
|
func (c TruncateCmd) Type() CmdType {
|
|
return sendCmdTruncate
|
|
}
|
|
func (c *TruncateCmd) decode(tlvs []SendTLV) error {
|
|
for _, tlv := range tlvs {
|
|
var ok bool
|
|
switch tlv.Attr {
|
|
case sendAttrPath:
|
|
c.Path, ok = tlv.Val.(string)
|
|
case sendAttrSize:
|
|
c.Size, ok = tlv.Val.(uint64)
|
|
default:
|
|
return errUnexpectedAttr{Val: tlv, Cmd: c.Type()}
|
|
}
|
|
if !ok {
|
|
return errUnexpectedAttrType{Val: tlv, Cmd: c.Type()}
|
|
}
|
|
}
|
|
return nil
|
|
}
|