Antonio Mika
·
2026-10-10
1package rsync
2
3import (
4 "errors"
5 "io"
6 "time"
7)
8
9// ErrFileIgnored lets Put skip a file without failing the transfer or
10// acknowledging it for --remove-source-files.
11var ErrFileIgnored = errors.New("file ignored")
12
13// FileInfo describes an entry in an FS.
14type FileInfo struct {
15 // Name is slash-separated and relative to the directory being listed.
16 Name string
17 Size int64
18 ModTime time.Time
19 IsDir bool
20}
21
22// File is a file opened for reading.
23type File interface {
24 io.ReaderAt
25 io.Closer
26}
27
28// FS is the storage the server reads from and writes to. Paths are
29// slash-separated, relative to the root of the storage, and never start
30// with a slash. The root itself is "".
31type FS interface {
32 // Stat returns the entry at name, or an error wrapping fs.ErrNotExist.
33 Stat(name string) (FileInfo, error)
34 // ReadDir returns the entries below dir with names relative to dir,
35 // descending into subdirectories when recursive is set.
36 ReadDir(dir string, recursive bool) ([]FileInfo, error)
37 // Open opens a regular file for reading.
38 Open(name string) (File, FileInfo, error)
39 // Put stores a regular file with the data read from r. If the transfer
40 // fails, r returns an error instead of io.EOF and Put must not keep
41 // what it read. The returned message, if any, is shown to the user.
42 // Return an error wrapping ErrFileIgnored for an intentional skip.
43 Put(name string, info FileInfo, r io.Reader) (string, error)
44 // Remove deletes a file or an empty directory.
45 Remove(name string, isDir bool) error
46}
47
48// mapFile gives windowed access to a file being sent, like rsync's
49// map_ptr. Returned slices are valid until the next call. After a read error
50// it returns zeroes and records the error, which the caller checks once the
51// file is done.
52type mapFile struct {
53 r io.ReaderAt
54 size int64
55 window []byte
56 start int64
57 defSize int64
58 err error
59}
60
61func newMapFile(r io.ReaderAt, size int64, readSize int64, blength int32) *mapFile {
62 if blength > 0 && readSize%int64(blength) != 0 {
63 readSize += int64(blength) - readSize%int64(blength)
64 }
65 return &mapFile{r: r, size: size, defSize: alignUp(readSize)}
66}
67
68func alignUp(n int64) int64 { return (n + 1023) &^ 1023 }
69
70func (m *mapFile) ptr(offset int64, n int) []byte {
71 if n == 0 {
72 return nil
73 }
74 if m.err != nil {
75 return make([]byte, n)
76 }
77 end := offset + int64(n)
78 if offset >= m.start && end <= m.start+int64(len(m.window)) {
79 return m.window[offset-m.start : end-m.start]
80 }
81 start := offset &^ 1023
82 size := max(m.defSize, alignUp(end-start))
83 if start+size > m.size {
84 size = m.size - start
85 }
86 buf := make([]byte, size)
87 // Reuse the overlap with the current window.
88 read := buf
89 readAt := start
90 if start >= m.start && start < m.start+int64(len(m.window)) {
91 k := copy(buf, m.window[start-m.start:])
92 read = buf[k:]
93 readAt = start + int64(k)
94 }
95 if len(read) > 0 {
96 if err := readFullAt(m.r, read, readAt); err != nil {
97 m.err = err
98 return make([]byte, n)
99 }
100 }
101 m.window = buf
102 m.start = start
103 return m.window[offset-m.start : end-m.start]
104}