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