2021-08-13 16:20:17 +00:00
|
|
|
package aghos
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"io"
|
2021-10-14 16:39:21 +00:00
|
|
|
"io/fs"
|
2021-08-13 16:20:17 +00:00
|
|
|
|
|
|
|
"github.com/AdguardTeam/AdGuardHome/internal/aghio"
|
|
|
|
"github.com/AdguardTeam/golibs/errors"
|
|
|
|
"github.com/AdguardTeam/golibs/stringutil"
|
|
|
|
)
|
|
|
|
|
|
|
|
// FileWalker is the signature of a function called for files in the file tree.
|
|
|
|
// As opposed to filepath.Walk it only walk the files (not directories) matching
|
|
|
|
// the provided pattern and those returned by function itself. All patterns
|
2021-10-14 16:39:21 +00:00
|
|
|
// should be valid for fs.Glob. If cont is false, the walking terminates. Each
|
|
|
|
// opened file is also limited for reading to MaxWalkedFileSize.
|
2021-08-13 16:20:17 +00:00
|
|
|
//
|
2021-10-14 16:39:21 +00:00
|
|
|
// TODO(e.burkov, a.garipov): Move into another package like aghfs.
|
2021-08-13 16:20:17 +00:00
|
|
|
//
|
|
|
|
// TODO(e.burkov): Think about passing filename or any additional data.
|
|
|
|
type FileWalker func(r io.Reader) (patterns []string, cont bool, err error)
|
|
|
|
|
|
|
|
// MaxWalkedFileSize is the maximum length of the file that FileWalker can
|
|
|
|
// check.
|
|
|
|
const MaxWalkedFileSize = 1024 * 1024
|
|
|
|
|
2021-10-14 16:39:21 +00:00
|
|
|
// checkFile tries to open and process a single file located on sourcePath in
|
|
|
|
// the specified fsys. The path is skipped if it's a directory.
|
|
|
|
func checkFile(
|
|
|
|
fsys fs.FS,
|
|
|
|
c FileWalker,
|
|
|
|
sourcePath string,
|
|
|
|
) (patterns []string, cont bool, err error) {
|
|
|
|
var f fs.File
|
|
|
|
f, err = fsys.Open(sourcePath)
|
2021-08-13 16:20:17 +00:00
|
|
|
if err != nil {
|
2021-10-14 16:39:21 +00:00
|
|
|
if errors.Is(err, fs.ErrNotExist) {
|
|
|
|
// Ignore non-existing files since this may only happen when the
|
|
|
|
// file was removed after filepath.Glob matched it.
|
2021-08-13 16:20:17 +00:00
|
|
|
return nil, true, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil, false, err
|
|
|
|
}
|
|
|
|
defer func() { err = errors.WithDeferred(err, f.Close()) }()
|
|
|
|
|
2021-10-14 16:39:21 +00:00
|
|
|
var fi fs.FileInfo
|
|
|
|
if fi, err = f.Stat(); err != nil {
|
|
|
|
return nil, true, err
|
|
|
|
}
|
|
|
|
if fi.IsDir() {
|
|
|
|
// Skip the directories.
|
|
|
|
return nil, true, nil
|
|
|
|
}
|
|
|
|
|
2021-08-13 16:20:17 +00:00
|
|
|
var r io.Reader
|
2021-10-14 16:39:21 +00:00
|
|
|
// Ignore the error since LimitReader function returns error only if passed
|
|
|
|
// limit value is less than zero, but the constant used.
|
2021-08-13 16:20:17 +00:00
|
|
|
//
|
|
|
|
// TODO(e.burkov): Make variable.
|
|
|
|
r, _ = aghio.LimitReader(f, MaxWalkedFileSize)
|
|
|
|
|
|
|
|
return c(r)
|
|
|
|
}
|
|
|
|
|
2021-10-14 16:39:21 +00:00
|
|
|
// handlePatterns parses the patterns in fsys and ignores duplicates using
|
|
|
|
// srcSet. srcSet must be non-nil.
|
|
|
|
func handlePatterns(
|
|
|
|
fsys fs.FS,
|
|
|
|
srcSet *stringutil.Set,
|
|
|
|
patterns ...string,
|
|
|
|
) (sub []string, err error) {
|
2021-08-13 16:20:17 +00:00
|
|
|
sub = make([]string, 0, len(patterns))
|
|
|
|
for _, p := range patterns {
|
|
|
|
var matches []string
|
2021-10-14 16:39:21 +00:00
|
|
|
matches, err = fs.Glob(fsys, p)
|
2021-08-13 16:20:17 +00:00
|
|
|
if err != nil {
|
|
|
|
// Enrich error with the pattern because filepath.Glob
|
|
|
|
// doesn't do it.
|
|
|
|
return nil, fmt.Errorf("invalid pattern %q: %w", p, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, m := range matches {
|
|
|
|
if srcSet.Has(m) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
srcSet.Add(m)
|
|
|
|
sub = append(sub, m)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return sub, nil
|
|
|
|
}
|
|
|
|
|
2021-10-14 16:39:21 +00:00
|
|
|
// Walk starts walking the files in fsys defined by patterns from initial.
|
|
|
|
// It only returns true if fw signed to stop walking.
|
|
|
|
func (fw FileWalker) Walk(fsys fs.FS, initial ...string) (ok bool, err error) {
|
|
|
|
// The slice of sources keeps the order in which the files are walked since
|
|
|
|
// srcSet.Values() returns strings in undefined order.
|
2021-08-13 16:20:17 +00:00
|
|
|
srcSet := stringutil.NewSet()
|
|
|
|
var src []string
|
2021-10-14 16:39:21 +00:00
|
|
|
src, err = handlePatterns(fsys, srcSet, initial...)
|
2021-08-13 16:20:17 +00:00
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
|
|
|
|
var filename string
|
|
|
|
defer func() { err = errors.Annotate(err, "checking %q: %w", filename) }()
|
|
|
|
|
|
|
|
for i := 0; i < len(src); i++ {
|
|
|
|
var patterns []string
|
|
|
|
var cont bool
|
|
|
|
filename = src[i]
|
2021-10-14 16:39:21 +00:00
|
|
|
patterns, cont, err = checkFile(fsys, fw, src[i])
|
2021-08-13 16:20:17 +00:00
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if !cont {
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
var subsrc []string
|
2021-10-14 16:39:21 +00:00
|
|
|
subsrc, err = handlePatterns(fsys, srcSet, patterns...)
|
2021-08-13 16:20:17 +00:00
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
|
|
|
|
src = append(src, subsrc...)
|
|
|
|
}
|
|
|
|
|
|
|
|
return false, nil
|
|
|
|
}
|