Eric Bower
·
2026-05-31
1package rsyncreceiver
2
3import (
4 "fmt"
5 "io"
6 "os"
7
8 "github.com/picosh/pico/pkg/rsync-receiver/rsync"
9 "github.com/picosh/pico/pkg/rsync-receiver/rsyncchecksum"
10 "github.com/picosh/pico/pkg/rsync-receiver/rsynccommon"
11 "github.com/picosh/pico/pkg/rsync-receiver/utils"
12)
13
14// rsync/generator.c:generate_files().
15func (rt *Transfer) GenerateFiles(fileList []*utils.ReceiverFile) error {
16 phase := 0
17 for idx, f := range fileList {
18 // TODO: use a copy of f with .Mode |= S_IWUSR for directories, so
19 // that we can create files within all directories.
20 if err := rt.recvGenerator(idx, f); err != nil {
21 return err
22 }
23 }
24 phase++
25 rt.Logger.Debug("generateFiles", "phase", phase)
26 if err := rt.Conn.WriteInt32(-1); err != nil {
27 return err
28 }
29
30 // TODO: re-do any files that failed
31 phase++
32 rt.Logger.Debug("generateFiles", "phase", phase)
33 if err := rt.Conn.WriteInt32(-1); err != nil {
34 return err
35 }
36
37 rt.Logger.Debug("generateFiles finished")
38 return nil
39}
40
41// rsync/generator.c:skip_file.
42func (rt *Transfer) skipFile(f *utils.ReceiverFile, st os.FileInfo) (bool, error) {
43 if rt.Opts.AlwaysChecksum || rt.Opts.IgnoreTimes {
44 return false, nil
45 }
46
47 sizeMatch := st.Size() == f.Length
48 if rt.Opts.SizeOnly {
49 return sizeMatch, nil
50 }
51
52 timeMatch := st.ModTime().Equal(f.ModTime)
53 return sizeMatch && timeMatch, nil
54}
55
56// rsync/generator.c:recv_generator.
57func (rt *Transfer) recvGenerator(idx int, f *utils.ReceiverFile) error {
58 if rt.listOnly() {
59 if _, err := fmt.Fprintf(rt.Env.Stdout, "%s %11.0f %s %s\n",
60 f.FileMode().String(),
61 float64(f.Length), // TODO: rsync prints decimal separators
62 f.ModTime.Format("2006/01/02 15:04:05"),
63 f.Name); err != nil {
64 return err
65 }
66 return nil
67 }
68 rt.Logger.Debug("recv_generator", "file", f)
69
70 if !f.FileMode().IsRegular() {
71 // None of the Preserve* options is enabled, so just skip over
72 // non-regular files.
73 return nil
74 }
75
76 requestFullFile := func() error {
77 rt.Logger.Debug("requesting", "file", f)
78 if err := rt.Conn.WriteInt32(int32(idx)); err != nil {
79 return err
80 }
81 if rt.Opts.DryRun {
82 return nil
83 }
84 var sh rsync.SumHead
85 if err := sh.WriteTo(rt.Conn); err != nil {
86 return err
87 }
88 return nil
89 }
90
91 st, in, err := rt.Files.Read(&utils.SenderFile{WPath: f.Name})
92 if err != nil {
93 rt.Logger.Error("failed to open file", "st", st, "file", f, "err", err)
94 return requestFullFile()
95 }
96
97 defer func() { _ = in.Close() }()
98
99 skip, err := rt.skipFile(f, st)
100 if err != nil {
101 return err
102 }
103
104 if skip {
105 rt.Logger.Debug("skipping", "file", f)
106 return nil
107 }
108
109 if rt.Opts.DryRun {
110 if err := rt.Conn.WriteInt32(int32(idx)); err != nil {
111 return err
112 }
113
114 return nil
115 }
116
117 rt.Logger.Debug("sending sums", "file", f, "st", st)
118 if err := rt.Conn.WriteInt32(int32(idx)); err != nil {
119 return err
120 }
121
122 err = rt.generateAndSendSums(in, st.Size())
123 if err != nil {
124 rt.Logger.Error("failed to send sums", "file", f, "err", err)
125 }
126
127 return err
128}
129
130// rsync/generator.c:generate_and_send_sums.
131func (rt *Transfer) generateAndSendSums(in utils.ReaderAtCloser, fileLen int64) error {
132 sh := rsynccommon.SumSizesSqroot(fileLen)
133 if err := sh.WriteTo(rt.Conn); err != nil {
134 return err
135 }
136 buf := make([]byte, int(sh.BlockLength))
137 remaining := fileLen
138 for i := int32(0); i < sh.ChecksumCount; i++ {
139 n1 := min(int64(sh.BlockLength), remaining)
140 b := buf[:n1]
141 if _, err := io.ReadFull(in, b); err != nil {
142 return err
143 }
144
145 sum1 := rsyncchecksum.Checksum1(b)
146 sum2 := rsyncchecksum.Checksum2(rt.Seed, b)
147 if err := rt.Conn.WriteInt32(int32(sum1)); err != nil {
148 return err
149 }
150 if _, err := rt.Conn.Writer.Write(sum2); err != nil {
151 return err
152 }
153 remaining -= n1
154 }
155 return nil
156}