-
Notifications
You must be signed in to change notification settings - Fork 2.4k
/
fs.go
160 lines (139 loc) · 4.57 KB
/
fs.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
package walker
import (
"errors"
"io/fs"
"os"
"path/filepath"
"strings"
"golang.org/x/xerrors"
"github.com/aquasecurity/trivy/pkg/log"
xio "github.com/aquasecurity/trivy/pkg/x/io"
)
// FS is the filesystem walker
type FS struct{}
func NewFS() *FS {
return &FS{}
}
// Walk walks the filesystem rooted at root, calling fn for each unfiltered file.
func (w *FS) Walk(root string, opt Option, fn WalkFunc) error {
opt.SkipFiles = w.BuildSkipPaths(root, opt.SkipFiles)
opt.SkipDirs = w.BuildSkipPaths(root, opt.SkipDirs)
opt.SkipDirs = append(opt.SkipDirs, defaultSkipDirs...)
walkDirFunc := w.WalkDirFunc(root, fn, opt)
walkDirFunc = w.onError(walkDirFunc)
// Walk the filesystem
if err := filepath.WalkDir(root, walkDirFunc); err != nil {
return xerrors.Errorf("walk dir error: %w", err)
}
return nil
}
func (w *FS) WalkDirFunc(root string, fn WalkFunc, opt Option) fs.WalkDirFunc {
return func(filePath string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
// For exported rootfs (e.g. images/alpine/etc/alpine-release)
relPath, err := filepath.Rel(root, filePath)
if err != nil {
return xerrors.Errorf("filepath rel (%s): %w", relPath, err)
}
relPath = filepath.ToSlash(relPath)
// Skip unnecessary files
switch {
case d.IsDir():
if SkipPath(relPath, opt.SkipDirs) {
return filepath.SkipDir
}
return nil
case !d.Type().IsRegular():
return nil
case SkipPath(relPath, opt.SkipFiles):
return nil
}
info, err := d.Info()
if err != nil {
return xerrors.Errorf("file info error: %w", err)
}
if err = fn(relPath, info, fileOpener(filePath)); err != nil {
return xerrors.Errorf("failed to analyze file: %w", err)
}
return nil
}
}
func (w *FS) onError(wrapped fs.WalkDirFunc) fs.WalkDirFunc {
return func(filePath string, d fs.DirEntry, err error) error {
err = wrapped(filePath, d, err)
switch {
// Unwrap fs.SkipDir error
case errors.Is(err, fs.SkipDir):
return fs.SkipDir
// Ignore permission errors
case os.IsPermission(err):
return nil
case err != nil:
// halt traversal on any other error
return xerrors.Errorf("unknown error with %s: %w", filePath, err)
}
return nil
}
}
// BuildSkipPaths builds correct patch for defaultSkipDirs and skipFiles
func (w *FS) BuildSkipPaths(base string, paths []string) []string {
var relativePaths []string
absBase, err := filepath.Abs(base)
if err != nil {
log.Warn("Failed to get an absolute path", log.String("base", base), log.Err(err))
return nil
}
for _, path := range paths {
// Supports three types of flag specification.
// All of them are converted into the relative path from the root directory.
// 1. Relative skip dirs/files from the root directory
// The specified dirs and files will be used as is.
// e.g. $ trivy fs --skip-dirs bar ./foo
// The skip dir from the root directory will be `bar/`.
// 2. Relative skip dirs/files from the working directory
// The specified dirs and files wll be converted to the relative path from the root directory.
// e.g. $ trivy fs --skip-dirs ./foo/bar ./foo
// The skip dir will be converted to `bar/`.
// 3. Absolute skip dirs/files
// The specified dirs and files wll be converted to the relative path from the root directory.
// e.g. $ trivy fs --skip-dirs /bar/foo/baz ./foo
// When the working directory is
// 3.1 /bar: the skip dir will be converted to `baz/`.
// 3.2 /hoge : the skip dir will be converted to `../../bar/foo/baz/`.
absSkipPath, err := filepath.Abs(path)
if err != nil {
log.Warn("Failed to get an absolute path", log.String("base", base), log.Err(err))
continue
}
rel, err := filepath.Rel(absBase, absSkipPath)
if err != nil {
log.Warn("Failed to get a relative path", log.String("from", base),
log.String("to", path), log.Err(err))
continue
}
var relPath string
switch {
case !filepath.IsAbs(path) && strings.HasPrefix(rel, ".."):
// #1: Use the path as is
relPath = path
case !filepath.IsAbs(path) && !strings.HasPrefix(rel, ".."):
// #2: Use the relative path from the root directory
relPath = rel
case filepath.IsAbs(path):
// #3: Use the relative path from the root directory
relPath = rel
}
relPath = filepath.ToSlash(relPath)
relativePaths = append(relativePaths, relPath)
}
relativePaths = CleanSkipPaths(relativePaths)
return relativePaths
}
// fileOpener returns a function opening a file.
func fileOpener(filePath string) func() (xio.ReadSeekCloserAt, error) {
return func() (xio.ReadSeekCloserAt, error) {
return os.Open(filePath)
}
}