Eric Bower
·
2026-05-31
1package rsyncsender
2
3import (
4 "io"
5 "path/filepath"
6 "strings"
7
8 "github.com/picosh/pico/pkg/rsync-receiver/rsyncwire"
9)
10
11type filterRuleList struct {
12 Filters []*filterRule
13}
14
15// exclude.c:add_rule.
16func (l *filterRuleList) addRule(fr *filterRule) {
17 if strings.HasSuffix(fr.pattern, "/") {
18 fr.flag |= filtruleDirectory
19 fr.pattern = strings.TrimSuffix(fr.pattern, "/")
20 }
21 if strings.ContainsFunc(fr.pattern, func(r rune) bool {
22 return r == '*' || r == '[' || r == '?'
23 }) {
24 fr.flag |= filtruleWild
25 }
26 l.Filters = append(l.Filters, fr)
27}
28
29// exclude.c:check_filter.
30func (l *filterRuleList) matches(name string) bool {
31 for _, fr := range l.Filters {
32 if fr.matches(name) {
33 return true
34 }
35 }
36 return false
37}
38
39// exclude.c:recv_filter_list.
40func RecvFilterList(c *rsyncwire.Conn) (*filterRuleList, error) {
41 var l filterRuleList
42 const exclusionListEnd = 0
43 for {
44 length, err := c.ReadInt32()
45 if err != nil {
46 return nil, err
47 }
48 if length == exclusionListEnd {
49 break
50 }
51 line := make([]byte, length)
52 if _, err := io.ReadFull(c.Reader, line); err != nil {
53 return nil, err
54 }
55 fr, err := parseFilter(string(line))
56 if err != nil {
57 return nil, err
58 }
59 l.addRule(fr)
60 }
61 return &l, nil
62}
63
64const (
65 filtruleInclude = 1 << iota
66 filtruleClearList
67 filtruleDirectory
68 filtruleWild
69)
70
71type filterRule struct {
72 flag int
73 pattern string
74}
75
76// exclude.c:rule_matches.
77func (fr *filterRule) matches(name string) bool {
78 if fr.flag&filtruleWild != 0 {
79 panic("wildcard filter rules not yet implemented")
80 }
81 if !strings.ContainsRune(fr.pattern, '/') &&
82 fr.flag&filtruleWild == 0 {
83 name = filepath.Base(name)
84 }
85 return fr.pattern == name
86}
87
88// exclude.c:parse_filter_str / exclude.c:parse_rule_tok.
89func parseFilter(line string) (*filterRule, error) {
90 rule := new(filterRule)
91
92 // We only support what rsync calls XFLG_OLD_PREFIXES
93 if strings.HasPrefix(line, "- ") {
94 // clear include flag
95 rule.flag &= ^filtruleInclude
96 line = strings.TrimPrefix(line, "- ")
97 } else if strings.HasPrefix(line, "+ ") {
98 // set include flag
99 rule.flag |= filtruleInclude
100 line = strings.TrimPrefix(line, "+ ")
101 } else if strings.HasPrefix(line, "!") {
102 // set clear_list flag
103 rule.flag |= filtruleClearList
104 }
105
106 rule.pattern = line
107
108 return rule, nil
109}