This commit was merged in pull request #165.
This commit is contained in:
1 parent
f0b679e9fa
commit
6caaff935a
10 files changed
+495
-81
No files matched your search
+77
-47
@@ -102,40 +102,44 @@ func scanPhase(name string) string {
|
||||
// scan command rejects anything less. Before the lock is taken, the
|
||||
// scan lists the filesystems that are not persistent, on which the walk
|
||||
// records nothing; unless noDefaultExcludes is set, the walk does not
|
||||
// enter the default excludes either. Once the database is open, the
|
||||
// scan notes its files, which the walk passes over.
|
||||
// enter the default excludes either. The walk neither enters nor
|
||||
// records the paths in excludePaths, the --exclude values; an operand
|
||||
// at or below one is a fatal error. Once the database is open, the scan
|
||||
// notes its files, which the walk passes over.
|
||||
func runScan(ctx context.Context, roots []string, workers int,
|
||||
oneFS, noDefaultExcludes bool,
|
||||
oneFS, noDefaultExcludes bool, excludePaths []string,
|
||||
) error {
|
||||
dbPath, source, err := databasePath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// A database file missing now is created by lockScanDatabase below.
|
||||
what := "database"
|
||||
printDatabasePath(dbPath, source)
|
||||
|
||||
_, err = os.Stat(dbPath)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
what = "new database"
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stderr, "scan: %s %s (%s)\n", what, escapePath(dbPath),
|
||||
source)
|
||||
|
||||
roots, err = resolveRoots(roots)
|
||||
roots, err = realPaths(roots)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
nonPersistent, err := nonPersistentFilesystems()
|
||||
var rules walkRules
|
||||
|
||||
rules.excludePaths, err = excludePathInodes(excludePaths)
|
||||
if err != nil {
|
||||
return fmt.Errorf("--exclude: %w", err)
|
||||
}
|
||||
|
||||
err = checkOperandsNotExcluded(roots, rules.excludePaths)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rules.nonPersistent, err = nonPersistentFilesystems()
|
||||
if err != nil {
|
||||
return fmt.Errorf("list the mounted filesystems: %w", err)
|
||||
}
|
||||
|
||||
rules := walkRules{nonPersistent: nonPersistent}
|
||||
if !noDefaultExcludes {
|
||||
rules.excludes = defaultExcludes(runtime.GOOS)
|
||||
rules.defaultExcludes = defaultExcludes(runtime.GOOS)
|
||||
}
|
||||
|
||||
lock, err := lockScanDatabase(dbPath)
|
||||
@@ -181,6 +185,21 @@ func runScan(ctx context.Context, roots []string, workers int,
|
||||
return nil
|
||||
}
|
||||
|
||||
// printDatabasePath prints on stderr the line that names the database
|
||||
// at dbPath, with source, saying whether it is new. A database file
|
||||
// missing now is new: lockScanDatabase creates it.
|
||||
func printDatabasePath(dbPath, source string) {
|
||||
what := "database"
|
||||
|
||||
_, err := os.Stat(dbPath)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
what = "new database"
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stderr, "scan: %s %s (%s)\n", what, escapePath(dbPath),
|
||||
source)
|
||||
}
|
||||
|
||||
// interruptContext returns a copy of ctx that the first SIGINT or
|
||||
// SIGTERM cancels; the scan command runs the scan under it. stop
|
||||
// releases the signals.
|
||||
@@ -209,24 +228,25 @@ func interrupted(walked int) error {
|
||||
return errInterrupted
|
||||
}
|
||||
|
||||
// resolveRoots converts each PATH operand to its real path: absolute,
|
||||
// with every symlink in it followed. Database records are keyed by
|
||||
// that path, so scan results depend neither on the working directory
|
||||
// nor on the symlinks an operand was given through. An operand that
|
||||
// cannot be resolved, because it or a symlink's target does not exist
|
||||
// or cannot be read, is a fatal error before any scanning.
|
||||
func resolveRoots(roots []string) ([]string, error) {
|
||||
resolved := make([]string, 0, len(roots))
|
||||
// realPaths returns the real path of each of paths, the PATH operands
|
||||
// or the --exclude values: absolute, with every symlink in it followed.
|
||||
// Database records are keyed by that path, so scan results depend
|
||||
// neither on the working directory nor on the symlinks an operand was
|
||||
// given through. A path that cannot be resolved, because it or a
|
||||
// symlink's target does not exist or cannot be read, is a fatal error
|
||||
// before any scanning.
|
||||
func realPaths(paths []string) ([]string, error) {
|
||||
resolved := make([]string, 0, len(paths))
|
||||
|
||||
for _, root := range roots {
|
||||
a, err := absolutePath(root)
|
||||
for _, path := range paths {
|
||||
a, err := absolutePath(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("resolve %s: %w", root, err)
|
||||
return nil, fmt.Errorf("resolve %s: %w", path, err)
|
||||
}
|
||||
|
||||
r, err := filepath.EvalSymlinks(a)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("resolve %s: %w", root, err)
|
||||
return nil, fmt.Errorf("resolve %s: %w", path, err)
|
||||
}
|
||||
|
||||
resolved = append(resolved, r)
|
||||
@@ -295,7 +315,7 @@ func walkReaches(above, r string, oneFS bool, rules walkRules,
|
||||
}
|
||||
|
||||
for ; dir != above; dir = filepath.Dir(dir) {
|
||||
if isDefaultExclude(dir, rules.excludes) {
|
||||
if isDefaultExclude(dir, rules.defaultExcludes) {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -418,7 +438,7 @@ func (s *scanState) runPhases(ctx context.Context, roots []string,
|
||||
|
||||
// walkableRoots returns the operands the walk can start from: regular
|
||||
// files, and directories not named .zfs, none of them inside a
|
||||
// directory named .zfs. The operands are real paths from resolveRoots,
|
||||
// directory named .zfs. The operands are real paths from realPaths,
|
||||
// so these checks apply to what a symlink operand points at. Every
|
||||
// other operand is warned about, counted as skipped, and dropped. A
|
||||
// dropped operand is no longer a root, so the records stored beneath it
|
||||
@@ -493,10 +513,11 @@ func (s *scanState) loadIndex(ctx context.Context, roots []string) error {
|
||||
// as verified. When its disk_size differs from the record's, it is
|
||||
// queued for the update phase, which writes the new disk_size. A record
|
||||
// at or below a path the walk left out, because -x stopped at it, it is
|
||||
// a default exclude, or it is not on a persistent filesystem, counts as
|
||||
// outside the roots: it is not deleted. That does not hold below a root
|
||||
// at or inside that path, since that root's own walk verifies the
|
||||
// records below it; nor is a line printed for such a root.
|
||||
// an --exclude path or a default exclude, or it is not on a persistent
|
||||
// filesystem, counts as outside the roots: it is not deleted. That does
|
||||
// not hold below a root at or inside that path, since that root's own
|
||||
// walk verifies the records below it; nor is a line printed for such a
|
||||
// root.
|
||||
func (s *scanState) walkPhase(events <-chan walkEvent, roots []string) {
|
||||
// The records the last scan left under the roots estimate how many
|
||||
// files the walk will find. A first scan has none, and no estimate.
|
||||
@@ -834,9 +855,9 @@ type dirJob struct {
|
||||
// file's statted record, a warning when fail is set, or a path the walk
|
||||
// left out: when otherFSDir is set, a directory -x did not enter
|
||||
// because it is on another filesystem than its operand, and when
|
||||
// leftOut is set, a default exclude or a directory or file on a
|
||||
// filesystem that is not persistent, with warn holding the line that
|
||||
// says so.
|
||||
// leftOut is set, an --exclude path, a default exclude or a directory or
|
||||
// file on a filesystem that is not persistent, with warn holding the
|
||||
// line that says so.
|
||||
type walkEvent struct {
|
||||
rec fileRec
|
||||
warn string
|
||||
@@ -887,7 +908,7 @@ func sendEvent(ctx context.Context, events chan<- walkEvent,
|
||||
// operandWarning returns the one-line warning for an operand the walk
|
||||
// does not start from, naming the path and what it is, or "" for one it
|
||||
// does: a regular file, or a directory not named .zfs, that is not
|
||||
// inside a directory named .zfs. resolveRoots has already followed the
|
||||
// inside a directory named .zfs. realPaths has already followed the
|
||||
// operand's symlinks, so a symlink here is an operand replaced by one
|
||||
// after it was resolved, and is not followed.
|
||||
func operandWarning(root string, fi fs.FileInfo) string {
|
||||
@@ -1118,8 +1139,9 @@ func walkOneDir(ctx context.Context, job dirJob, oneFS bool,
|
||||
// no longer exists, and one that has stopped being a regular file is
|
||||
// skipped. A file on a filesystem in rules that is not persistent, as
|
||||
// through a bind mount of one file, is not recorded: it gets an event
|
||||
// naming it instead. One of the scan's own database files in rules gets
|
||||
// no event at all.
|
||||
// naming it instead, and so does a file given with --exclude, reached
|
||||
// by any path, a hard link to it included. One of the scan's own
|
||||
// database files in rules gets no event at all.
|
||||
func emitFile(ctx context.Context, p string, e fs.DirEntry,
|
||||
rules walkRules, events chan<- walkEvent,
|
||||
) {
|
||||
@@ -1143,6 +1165,10 @@ func emitFile(ctx context.Context, p string, e fs.DirEntry,
|
||||
}
|
||||
|
||||
line := rules.nonPersistentLine(p, info)
|
||||
if line == "" {
|
||||
line = rules.excludePathLine(p, info)
|
||||
}
|
||||
|
||||
if line != "" {
|
||||
sendEvent(ctx, events, walkEvent{leftOut: p, warn: line})
|
||||
|
||||
@@ -1167,11 +1193,11 @@ func emitFile(ctx context.Context, p string, e fs.DirEntry,
|
||||
// subdirJob applies the descent rules to directory p: never enter
|
||||
// .zfs (ZFS snapshot pseudo-dirs would list every file once per
|
||||
// snapshot), never enter a directory on a filesystem in rules that is
|
||||
// not persistent, nor a default exclude in rules, and with -x never
|
||||
// enter a directory on a different filesystem than its operand. For the
|
||||
// last three it sends an event that names the directory, so the scan
|
||||
// keeps the records below it, except those below an operand at or
|
||||
// inside it.
|
||||
// not persistent, nor an --exclude path or a default exclude in rules,
|
||||
// and with -x never enter a directory on a different filesystem than
|
||||
// its operand. For all but .zfs it sends an event that names the
|
||||
// directory, so the scan keeps the records below it, except those below
|
||||
// an operand at or inside it.
|
||||
func subdirJob(ctx context.Context, p string, e fs.DirEntry,
|
||||
parent dirJob, oneFS bool, rules walkRules,
|
||||
events chan<- walkEvent,
|
||||
@@ -1196,13 +1222,17 @@ func subdirJob(ctx context.Context, p string, e fs.DirEntry,
|
||||
}
|
||||
|
||||
line := rules.nonPersistentLine(p, info)
|
||||
if line == "" {
|
||||
line = rules.excludePathLine(p, info)
|
||||
}
|
||||
|
||||
if line != "" {
|
||||
sendEvent(ctx, events, walkEvent{leftOut: p, warn: line})
|
||||
|
||||
return dirJob{}, false
|
||||
}
|
||||
|
||||
if isDefaultExclude(p, rules.excludes) {
|
||||
if isDefaultExclude(p, rules.defaultExcludes) {
|
||||
sendEvent(ctx, events, walkEvent{
|
||||
leftOut: p,
|
||||
warn: fmt.Sprintf("scan: skipped %s (default exclude)", p),
|
||||
|
||||
Reference in new issue
Block a user