Eric Bower
·
2026-05-31
1package rsyncsender
2
3import (
4 "fmt"
5 "io"
6 "log/slog"
7
8 "github.com/picosh/pico/pkg/rsync-receiver/rsync"
9 "github.com/picosh/pico/pkg/rsync-receiver/rsyncopts"
10 "github.com/picosh/pico/pkg/rsync-receiver/rsyncwire"
11 "github.com/picosh/pico/pkg/rsync-receiver/utils"
12)
13
14func ClientRun(logger *slog.Logger, opts *rsyncopts.Options, conn io.ReadWriter, filesystem utils.FS, paths []string, negotiate bool) error {
15 var err error
16
17 crd, cwr := rsyncwire.CounterPair(conn, conn)
18
19 const sessionChecksumSeed = 666
20
21 c := &rsyncwire.Conn{
22 Reader: crd,
23 Writer: cwr,
24 }
25
26 if negotiate {
27 remoteProtocol, err := c.ReadInt32()
28 if err != nil {
29 return err
30 }
31 logger.Debug("remote protocol", "remoteProtocol", remoteProtocol)
32 if err := c.WriteInt32(rsync.ProtocolVersion); err != nil {
33 return err
34 }
35 }
36
37 if err := c.WriteInt32(sessionChecksumSeed); err != nil {
38 return err
39 }
40
41 // Switch to multiplexing protocol, but only for server-side transmissions.
42 // Transmissions received from the client are not multiplexed.
43 mpx := &rsyncwire.MultiplexWriter{Writer: c.Writer}
44 c.Writer = mpx
45
46 defer func() {
47 if err != nil {
48 _, _ = mpx.WriteMsg(rsyncwire.MsgError, fmt.Appendf(nil, "gokr-rsync [sender]: %v\n", err))
49 }
50 }()
51
52 st := &Transfer{
53 Opts: opts,
54 Conn: c,
55 Seed: sessionChecksumSeed,
56 Files: filesystem,
57
58 Logger: logger,
59 }
60 // receive the exclusion list (openrsync’s is always empty)
61 exclusionList, err := RecvFilterList(st.Conn)
62 if err != nil {
63 return err
64 }
65 logger.Debug("exclusion list read", "filters", exclusionList.Filters)
66
67 stats, err := st.Do(crd, cwr, paths, exclusionList)
68 if err != nil {
69 return err
70 }
71
72 logger.Debug("handleConnSender done. stats", "stats", stats)
73
74 return err
75}