diff options
Diffstat (limited to 'internal/tracepoints')
| -rw-r--r-- | internal/tracepoints/selector.go | 85 | ||||
| -rw-r--r-- | internal/tracepoints/selector_test.go | 84 |
2 files changed, 169 insertions, 0 deletions
diff --git a/internal/tracepoints/selector.go b/internal/tracepoints/selector.go new file mode 100644 index 0000000..af2f39e --- /dev/null +++ b/internal/tracepoints/selector.go @@ -0,0 +1,85 @@ +package tracepoints + +import ( + "fmt" + "regexp" + "slices" + "strings" +) + +// Selector holds compiled include and exclude regexes for choosing which +// tracepoints to attach at BPF probe registration time. It is the single +// authoritative home for tracepoint-selection logic, keeping that concern +// out of the top-level flags.Config. +type Selector struct { + // Attach is the list of compiled regexes that select which tracepoints to + // load. An empty list means "attach all non-excluded tracepoints". + Attach []*regexp.Regexp + // Exclude is the list of compiled regexes that suppress specific + // tracepoints even when they match the Attach list. + Exclude []*regexp.Regexp +} + +// ParseSelector parses the comma-separated regex strings for the -tps and +// -tpsExclude CLI flags into a Selector. Either string may be empty, which +// leaves the corresponding list nil (i.e. "match all" for Attach, "exclude +// nothing" for Exclude). An error is returned if any regex fails to compile. +func ParseSelector(attach, exclude string) (Selector, error) { + attachRegexes, err := parseRegexList(attach) + if err != nil { + return Selector{}, err + } + excludeRegexes, err := parseRegexList(exclude) + if err != nil { + return Selector{}, err + } + return Selector{Attach: attachRegexes, Exclude: excludeRegexes}, nil +} + +// parseRegexList splits a comma-separated string of regex patterns and +// compiles each one. Returns nil (not an error) when the input is empty. +func parseRegexList(patterns string) ([]*regexp.Regexp, error) { + if len(patterns) == 0 { + return nil, nil + } + var regexes []*regexp.Regexp + for _, pattern := range strings.Split(patterns, ",") { + re, err := regexp.Compile(pattern) + if err != nil { + return nil, fmt.Errorf("unable to compile regex %q: %w", pattern, err) + } + regexes = append(regexes, re) + } + return regexes, nil +} + +// ShouldAttach reports whether the given tracepoint name passes the +// selector's attach/exclude regex filters. Exclusions are checked first; if +// the name matches any exclude pattern it is rejected regardless of the attach +// list. When the attach list is empty, all non-excluded tracepoints are +// accepted. +func (s Selector) ShouldAttach(tracepointName string) bool { + for _, re := range s.Exclude { + if re.MatchString(tracepointName) { + return false + } + } + if len(s.Attach) == 0 { + return true + } + for _, re := range s.Attach { + if re.MatchString(tracepointName) { + return true + } + } + return false +} + +// Clone returns a deep copy of the Selector so that modifications to the +// copy's slices do not affect the original. +func (s Selector) Clone() Selector { + return Selector{ + Attach: slices.Clone(s.Attach), + Exclude: slices.Clone(s.Exclude), + } +} diff --git a/internal/tracepoints/selector_test.go b/internal/tracepoints/selector_test.go new file mode 100644 index 0000000..d12f24b --- /dev/null +++ b/internal/tracepoints/selector_test.go @@ -0,0 +1,84 @@ +package tracepoints + +import "testing" + +func TestParseSelectorEmpty(t *testing.T) { + // An empty attach and exclude string means accept everything. + sel, err := ParseSelector("", "") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !sel.ShouldAttach("sys_enter_openat") { + t.Fatal("expected ShouldAttach=true for empty selector") + } +} + +func TestParseSelectorAttachFilter(t *testing.T) { + // Only openat tracepoints should be accepted when an explicit attach list + // is provided. + sel, err := ParseSelector("^sys_enter_openat$,^sys_exit_openat$", "") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !sel.ShouldAttach("sys_enter_openat") { + t.Error("expected ShouldAttach=true for sys_enter_openat") + } + if !sel.ShouldAttach("sys_exit_openat") { + t.Error("expected ShouldAttach=true for sys_exit_openat") + } + if sel.ShouldAttach("sys_enter_write") { + t.Error("expected ShouldAttach=false for sys_enter_write (not in attach list)") + } +} + +func TestParseSelectorExcludeFilter(t *testing.T) { + // Excluded tracepoints are rejected even when no explicit attach list exists. + sel, err := ParseSelector("", "name_to_handle_at,open_by_handle_at") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if sel.ShouldAttach("sys_enter_name_to_handle_at") { + t.Error("expected ShouldAttach=false for excluded tracepoint") + } + if !sel.ShouldAttach("sys_enter_openat") { + t.Error("expected ShouldAttach=true for non-excluded tracepoint") + } +} + +func TestParseSelectorExcludeTakesPrecedence(t *testing.T) { + // An exclude pattern beats an attach pattern when both match. + sel, err := ParseSelector("openat", "openat") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if sel.ShouldAttach("sys_enter_openat") { + t.Error("expected ShouldAttach=false: exclude must beat attach") + } +} + +func TestParseSelectorInvalidRegexReturnsError(t *testing.T) { + _, err := ParseSelector("[", "") + if err == nil { + t.Fatal("expected error for invalid attach regex") + } +} + +func TestParseSelectorInvalidExcludeRegexReturnsError(t *testing.T) { + _, err := ParseSelector("", "[") + if err == nil { + t.Fatal("expected error for invalid exclude regex") + } +} + +func TestSelectorCloneIsIndependent(t *testing.T) { + // Modifications to the clone's Attach slice must not affect the original. + sel, err := ParseSelector("openat", "") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + clone := sel.Clone() + clone.Attach = nil + if !sel.ShouldAttach("sys_enter_openat") { + t.Error("original Selector was mutated through clone") + } +} |
