Eric Bower
·
2026-05-31
1package rsyncreceiver
2
3import (
4 "fmt"
5 "io"
6 "log/slog"
7 "os"
8
9 "github.com/picosh/pico/pkg/rsync-receiver/rsync"
10 "github.com/picosh/pico/pkg/rsync-receiver/rsyncopts"
11 "github.com/picosh/pico/pkg/rsync-receiver/rsyncsender"
12 "github.com/picosh/pico/pkg/rsync-receiver/rsyncwire"
13 "github.com/picosh/pico/pkg/rsync-receiver/utils"
14)
15
16func ClientRun(logger *slog.Logger, opts *rsyncopts.Options, conn io.ReadWriter, filesystem utils.FS, paths []string, negotiate bool) error {
17 var err error
18
19 crd, cwr := rsyncwire.CounterPair(conn, conn)
20
21 const sessionChecksumSeed = 666
22
23 c := &rsyncwire.Conn{
24 Reader: crd,
25 Writer: cwr,
26 }
27
28 if negotiate {
29 remoteProtocol, err := c.ReadInt32()
30 if err != nil {
31 return err
32 }
33 logger.Debug("remote protocol", "protocol", remoteProtocol)
34 if err := c.WriteInt32(rsync.ProtocolVersion); err != nil {
35 return err
36 }
37 }
38
39 if err := c.WriteInt32(sessionChecksumSeed); err != nil {
40 return err
41 }
42
43 // Switch to multiplexing protocol, but only for server-side transmissions.
44 // Transmissions received from the client are not multiplexed.
45 mpx := &rsyncwire.MultiplexWriter{Writer: c.Writer}
46 c.Writer = mpx
47
48 defer func() {
49 if err != nil {
50 _, _ = mpx.WriteMsg(rsyncwire.MsgError, fmt.Appendf(nil, "gokr-rsync [receiver]: %v\n", err))
51 }
52 }()
53
54 rt := &Transfer{
55 Opts: &TransferOpts{
56 DryRun: opts.DryRun(),
57
58 DeleteMode: opts.DeleteMode(),
59 PreserveGid: opts.PreserveGid(),
60 PreserveUid: opts.PreserveUid(),
61 PreserveLinks: opts.PreserveLinks(),
62 PreservePerms: opts.PreservePerms(),
63 PreserveDevices: opts.PreserveDevices(),
64 PreserveSpecials: opts.PreserveSpecials(),
65 PreserveTimes: opts.PreserveMTimes(),
66 IgnoreTimes: opts.IgnoreTimes(),
67 SizeOnly: opts.SizeOnly(),
68 AlwaysChecksum: opts.AlwaysChecksum(),
69 // TODO: PreserveHardlinks: opts.PreserveHardlinks,
70 },
71 Dest: "/",
72 // TODO: what is Env used for and can we get rid of it?
73 Env: Osenv{
74 Stdout: os.Stdout,
75 Stderr: os.Stderr,
76 Stdin: os.Stdin,
77 },
78 Conn: c,
79 Seed: sessionChecksumSeed,
80
81 Files: filesystem,
82
83 Logger: logger,
84 }
85
86 if opts.DeleteMode() {
87 // receive the exclusion list (openrsync’s is always empty)
88 exclusionList, err := rsyncsender.RecvFilterList(c)
89 if err != nil {
90 return err
91 }
92 logger.Debug("exclusion list read", "filters", exclusionList.Filters)
93 }
94
95 // receive file list
96 logger.Debug("receiving file list")
97 fileList, err := rt.ReceiveFileList()
98 if err != nil {
99 return err
100 }
101 logger.Debug("received names", "files", fileList)
102 stats, err := rt.Do(c, fileList, true)
103 if err != nil {
104 return err
105 }
106
107 logger.Debug("stats", "stats", stats)
108 return nil
109}