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}