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