@cryptotaxi247 / netdata-1 / commits / 1ba65ff83

chore(go.d/pkgs/logs): validation fixes, resource safety, and cleanup (#20931)

Ilya Mashchenko committed Sep 8, 2025 at 15:46 UTC 1ba65ff83ca2644d6ef2f3a0d2ec197bd2af6e24
5 files changed +12 -110
src/go/plugin/go.d/pkg/logs/csv.go
+3
@@ -186,6 +186,9 @@ func parseCSVDelimiter(s string) (rune, error) {
186 if err != nil {
187 return 0, fmt.Errorf("invalid CSV delimiter: %v", err)
188 }
189 + if d < 0 {
190 + return 0, errors.New("invalid CSV delimiter: must be a non-negative integer")
191 + }
192 return rune(d), nil
193 }
194 if len(s) != 1 {
src/go/plugin/go.d/pkg/logs/lastline.go
-54
@@ -3,63 +3,9 @@
3 package logs
4
5 import (
6 - "errors"
7 - "os"
8 -
6 "github.com/clbanning/rfile/v2"
7 )
8
12 -const DefaultMaxLineWidth = 4 * 1024 // assume disk block size is 4K
13 -
14 -var ErrTooLongLine = errors.New("too long line")
15 -
16 -// ReadLastLine returns the last line of the file and any read error encountered.
17 -// It expects last line width <= maxLineWidth.
18 -// If maxLineWidth <= 0, it defaults to DefaultMaxLineWidth.
19 -func ReadLastLine(filename string, maxLineWidth int64) ([]byte, error) {
20 - if maxLineWidth <= 0 {
21 - maxLineWidth = DefaultMaxLineWidth
22 - }
23 - f, err := os.Open(filename)
24 - if err != nil {
25 - return nil, err
26 - }
27 - defer func() { _ = f.Close() }()
28 -
29 - stat, _ := f.Stat()
30 - endPos := stat.Size()
31 - if endPos == 0 {
32 - return []byte{}, nil
33 - }
34 - startPos := endPos - maxLineWidth
35 - if startPos < 0 {
36 - startPos = 0
37 - }
38 - buf := make([]byte, endPos-startPos)
39 - n, err := f.ReadAt(buf, startPos)
40 - if err != nil {
41 - return nil, err
42 - }
43 - lnPos := 0
44 - foundLn := false
45 - for i := n - 2; i >= 0; i-- {
46 - ch := buf[i]
47 - if ch == '\n' {
48 - foundLn = true
49 - lnPos = i
50 - break
51 - }
52 - }
53 - if foundLn {
54 - return buf[lnPos+1 : n], nil
55 - }
56 - if startPos == 0 {
57 - return buf[0:n], nil
58 - }
59 -
60 - return nil, ErrTooLongLine
61 -}
62 -
9 func ReadLastLines(filename string, n uint) ([]string, error) {
10 return rfile.Tail(filename, int(n))
11 }
src/go/plugin/go.d/pkg/logs/lastline_test.go deleted
-54
@@ -1,54 +0,0 @@
1 -// SPDX-License-Identifier: GPL-3.0-or-later
2 -
3 -package logs
4 -
5 -import (
6 - "os"
7 - "testing"
8 -
9 - "github.com/stretchr/testify/assert"
10 - "github.com/stretchr/testify/require"
11 -)
12 -
13 -func TestReadLastLine(t *testing.T) {
14 - tests := []struct {
15 - name string
16 - content string
17 - expected string
18 - err error
19 - }{
20 - {"empty", "", "", nil},
21 - {"empty-ln", "\n", "\n", nil},
22 - {"one-line", "hello", "hello", nil},
23 - {"one-line-ln", "hello\n", "hello\n", nil},
24 - {"multi-line", "hello\nworld", "world", nil},
25 - {"multi-line-ln", "hello\nworld\n", "world\n", nil},
26 - {"long-line", "hello hello hello", "", ErrTooLongLine},
27 - {"long-line-ln", "hello hello hello\n", "", ErrTooLongLine},
28 - }
29 - for _, test := range tests {
30 - t.Run(test.name, func(t *testing.T) {
31 - filename := prepareFile(t, test.content)
32 - defer func() { _ = os.Remove(filename) }()
33 -
34 - line, err := ReadLastLine(filename, 10)
35 -
36 - if test.err != nil {
37 - require.NotNil(t, err)
38 - assert.Contains(t, err.Error(), test.err.Error())
39 - } else {
40 - assert.Equal(t, test.expected, string(line))
41 - }
42 - })
43 - }
44 -}
45 -
46 -func prepareFile(t *testing.T, content string) string {
47 - t.Helper()
48 - file, err := os.CreateTemp("", "go-test")
49 - require.NoError(t, err)
50 - defer func() { _ = file.Close() }()
51 -
52 - _, _ = file.WriteString(content)
53 - return file.Name()
54 -}
src/go/plugin/go.d/pkg/logs/reader.go
+7 -1
@@ -45,7 +45,7 @@ func Open(path string, excludePath string, log *logger.Logger) (*Reader, error)
45 return nil, fmt.Errorf("bad path syntax: %q", path)
46 }
47 if _, err = filepath.Match(excludePath, "/"); err != nil {
48 - return nil, fmt.Errorf("bad exclude_path syntax: %q", path)
48 + return nil, fmt.Errorf("bad exclude_path syntax: %q", excludePath)
49 }
50 r := &Reader{
51 path: path,
@@ -61,6 +61,9 @@ func Open(path string, excludePath string, log *logger.Logger) (*Reader, error)
61
62 // CurrentFilename get current opened file name
63 func (r *Reader) CurrentFilename() string {
64 + if r.file == nil {
65 + return ""
66 + }
67 return r.file.Name()
68 }
69
@@ -77,9 +80,11 @@ func (r *Reader) open() error {
80 }
81 stat, err := file.Stat()
82 if err != nil {
83 + _ = file.Close()
84 return err
85 }
86 if _, err = file.Seek(stat.Size(), io.SeekStart); err != nil {
87 + _ = file.Close()
88 return err
89 }
90 r.file = file
@@ -129,6 +134,7 @@ func (r *Reader) Close() (err error) {
134 r.log.Debug("close log file: ", r.file.Name())
135 err = r.file.Close()
136 r.file = nil
137 + r.continuousEOF = 0
138 r.eofCounter = 0
139 return
140 }
src/go/plugin/go.d/pkg/logs/regexp.go
+2 -1
@@ -8,6 +8,7 @@ import (
8 "fmt"
9 "io"
10 "regexp"
11 + "slices"
12 )
13
14 type (
@@ -31,7 +32,7 @@ func NewRegExpParser(config RegExpConfig, in io.Reader) (*RegExpParser, error) {
32 return nil, fmt.Errorf("compile: %w", err)
33 }
34
34 - if pattern.NumSubexp() == 0 {
35 + if hasNamed := slices.ContainsFunc(pattern.SubexpNames(), func(s string) bool { return s != "" }); !hasNamed {
36 return nil, errors.New("pattern has no named subgroups")
37 }
38