main pico / pkg / rsync-receiver / rsyncsender / client.go
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}