main pico / pkg / rsync-receiver / rsyncreceiver / flist.go
Eric Bower  ·  2026-05-31
  1package rsyncreceiver
  2
  3import (
  4	"fmt"
  5	"io"
  6	"path/filepath"
  7	"time"
  8
  9	"github.com/picosh/pico/pkg/rsync-receiver/rsync"
 10	"github.com/picosh/pico/pkg/rsync-receiver/utils"
 11)
 12
 13// rsync/flist.c:receive_file_entry.
 14func (rt *Transfer) receiveFileEntry(flags uint16, last *utils.ReceiverFile) (*utils.ReceiverFile, error) {
 15	f := &utils.ReceiverFile{}
 16
 17	var l1 int
 18	if flags&rsync.XMIT_SAME_NAME != 0 {
 19		l, err := rt.Conn.ReadByte()
 20		if err != nil {
 21			return nil, err
 22		}
 23		l1 = int(l)
 24	}
 25
 26	var l2 int
 27	if flags&rsync.XMIT_LONG_NAME != 0 {
 28		l, err := rt.Conn.ReadInt32()
 29		if err != nil {
 30			return nil, err
 31		}
 32		l2 = int(l)
 33	} else {
 34		l, err := rt.Conn.ReadByte()
 35		if err != nil {
 36			return nil, err
 37		}
 38		l2 = int(l)
 39	}
 40	// linux/limits.h
 41	const PATH_MAX = 4096
 42	if l2 >= PATH_MAX-l1 {
 43		const lastname = ""
 44		return nil, fmt.Errorf("overflow: flags=0x%x l1=%d l2=%d lastname=%s",
 45			flags, l1, l2, lastname)
 46	}
 47	b := make([]byte, l1+l2)
 48	readb := b
 49	if l1 > 0 {
 50		copy(b, []byte(last.Name))
 51		readb = b[l1:]
 52	}
 53	if _, err := io.ReadFull(rt.Conn.Reader, readb); err != nil {
 54		return nil, err
 55	}
 56	// TODO: does rsync’s clean_fname() and sanitize_path() combination do
 57	// anything more than Go’s filepath.Clean()?
 58	f.Name = filepath.Clean(string(b))
 59
 60	length, err := rt.Conn.ReadInt64()
 61	if err != nil {
 62		return nil, err
 63	}
 64	f.Length = length
 65
 66	if flags&rsync.XMIT_SAME_TIME != 0 {
 67		f.ModTime = last.ModTime
 68	} else {
 69		modTime, err := rt.Conn.ReadInt32()
 70		if err != nil {
 71			return nil, err
 72		}
 73		f.ModTime = time.Unix(int64(modTime), 0)
 74	}
 75
 76	if flags&rsync.XMIT_SAME_MODE != 0 {
 77		f.Mode = last.Mode
 78	} else {
 79		mode, err := rt.Conn.ReadInt32()
 80		if err != nil {
 81			return nil, err
 82		}
 83		f.Mode = mode
 84	}
 85
 86	if rt.Opts.PreserveUid {
 87		if flags&rsync.XMIT_SAME_UID != 0 {
 88			f.Uid = last.Uid
 89		} else {
 90			uid, err := rt.Conn.ReadInt32()
 91			if err != nil {
 92				return nil, err
 93			}
 94			f.Uid = uid
 95		}
 96	}
 97
 98	if rt.Opts.PreserveGid {
 99		if flags&rsync.XMIT_SAME_GID != 0 {
100			f.Gid = last.Gid
101		} else {
102			gid, err := rt.Conn.ReadInt32()
103			if err != nil {
104				return nil, err
105			}
106			f.Gid = gid
107		}
108	}
109
110	mode := f.Mode & rsync.S_IFMT
111	isDev := mode == rsync.S_IFCHR || mode == rsync.S_IFBLK
112	isSpecial := mode == rsync.S_IFIFO || mode == rsync.S_IFSOCK
113	isLink := mode == rsync.S_IFLNK
114
115	if rt.Opts.PreserveDevices && (isDev || isSpecial) {
116		// TODO(protocol >= 28): rdev/major/minor handling
117		if flags&rsync.XMIT_SAME_RDEV_pre28 != 0 {
118			f.Rdev = last.Rdev
119		} else {
120			rdev, err := rt.Conn.ReadInt32()
121			if err != nil {
122				return nil, err
123			}
124			f.Rdev = rdev
125		}
126	}
127
128	if rt.Opts.PreserveLinks && isLink {
129		length, err := rt.Conn.ReadInt32()
130		if err != nil {
131			return nil, err
132		}
133		b := make([]byte, length)
134		if _, err := io.ReadFull(rt.Conn.Reader, b); err != nil {
135			return nil, err
136		}
137		f.LinkTarget = string(b)
138	}
139
140	return f, nil
141}
142
143// rsync/flist.c:recv_file_list.
144func (rt *Transfer) ReceiveFileList() ([]*utils.ReceiverFile, error) {
145	lastFileEntry := new(utils.ReceiverFile)
146	var fileList []*utils.ReceiverFile
147	for {
148		b, err := rt.Conn.ReadByte()
149		if err != nil {
150			return nil, err
151		}
152		if b == 0 {
153			break
154		}
155		flags := uint16(b)
156		// log.Printf("flags: %x", flags)
157		// TODO(protocol >= 28): extended flags
158
159		f, err := rt.receiveFileEntry(flags, lastFileEntry)
160		if err != nil {
161			return nil, err
162		}
163		lastFileEntry = f
164		// TODO: include depth in output?
165		rt.Logger.Debug("recv_file_list", "file", f.Name, "length", f.Length, "mode", f.Mode, "uid", f.Uid, "gid", f.Gid, "flags", flags)
166
167		fileList = append(fileList, f)
168	}
169
170	utils.SortFileList(fileList)
171
172	if rt.Opts.PreserveUid || rt.Opts.PreserveGid {
173		// receive the uid/gid list
174		users, groups, err := rt.RecvIdList()
175		if err != nil {
176			return nil, err
177		}
178		_ = users
179		_ = groups
180	}
181
182	// read the i/o error flag
183	ioErrors, err := rt.Conn.ReadInt32()
184	if err != nil {
185		return nil, err
186	}
187	rt.Logger.Debug("ioErrors", "errs", ioErrors)
188	rt.IOErrors = ioErrors
189
190	return fileList, nil
191}