Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c6066afa6e |
No files matched your search
@@ -146,33 +146,44 @@ var errHashSize = errors.New("hash is not 32 bytes")
|
||||
|
||||
// databasePath resolves the database location: SFDUPES_DATABASE when
|
||||
// set and non-empty, the compiled-in default otherwise. It returns the
|
||||
// path, with the working directory, its symlinks resolved, put in front
|
||||
// of it when it is relative, and where the path came from, "default" or
|
||||
// "from SFDUPES_DATABASE", for the line each subcommand prints first on
|
||||
// stderr.
|
||||
// path, made absolute by absolutePath, and where the path came from,
|
||||
// "default" or "from SFDUPES_DATABASE", for the line each subcommand
|
||||
// prints first on stderr.
|
||||
func databasePath() (string, string, error) {
|
||||
path, source := defaultDatabasePath, "default"
|
||||
if p := os.Getenv(databaseEnv); p != "" {
|
||||
path, source = p, "from "+databaseEnv
|
||||
}
|
||||
|
||||
if filepath.IsAbs(path) {
|
||||
return path, source, nil
|
||||
}
|
||||
|
||||
// The working directory as the kernel reports it, with no symlink
|
||||
// in it, as README "Database" says. os.Getwd may return PWD
|
||||
// instead, which can name it through a symlink.
|
||||
wd, err := unix.Getwd()
|
||||
abs, err := absolutePath(path)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("resolve database path %s: %w", path, err)
|
||||
}
|
||||
|
||||
// Not filepath.Abs or filepath.Join: they clean the path, which
|
||||
// drops each ".." together with the directory name before it. When
|
||||
// that directory is a symlink, the cleaned path names another file.
|
||||
return abs, source, nil
|
||||
}
|
||||
|
||||
// absolutePath returns path unchanged when it is absolute, and
|
||||
// otherwise the working directory, a "/" and path. The working
|
||||
// directory is the one the kernel reports, with no symlink in it;
|
||||
// os.Getwd may return PWD instead, which can name it through a symlink.
|
||||
// The result is not cleaned, as filepath.Abs and filepath.Join would
|
||||
// clean it. Cleaning drops each ".." together with the directory name
|
||||
// before it. When that name is a symlink, the cleaned path names
|
||||
// another file than the one the system reaches by following the symlink
|
||||
// first.
|
||||
func absolutePath(path string) (string, error) {
|
||||
if filepath.IsAbs(path) {
|
||||
return path, nil
|
||||
}
|
||||
|
||||
wd, err := unix.Getwd()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// The working directory ends in "/" only when it is the root.
|
||||
return strings.TrimSuffix(wd, "/") + "/" + path, source, nil
|
||||
return strings.TrimSuffix(wd, "/") + "/" + path, nil
|
||||
}
|
||||
|
||||
// createDatabaseDirectory creates the directory the database at path
|
||||
|
||||
@@ -18,8 +18,6 @@ import (
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// The hash phase gives every file it reads three hashes (see
|
||||
@@ -221,22 +219,9 @@ func resolveRoots(roots []string) ([]string, error) {
|
||||
resolved := make([]string, 0, len(roots))
|
||||
|
||||
for _, root := range roots {
|
||||
a := root
|
||||
if !filepath.IsAbs(a) {
|
||||
// The working directory as the kernel reports it, with no
|
||||
// symlink in it. os.Getwd may return PWD instead, which can
|
||||
// name it through a symlink.
|
||||
wd, err := unix.Getwd()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("resolve %s: %w", root, err)
|
||||
}
|
||||
|
||||
// Not filepath.Abs or filepath.Join: they clean the path,
|
||||
// which drops each ".." together with the directory name
|
||||
// before it. EvalSymlinks follows a symlink before it
|
||||
// applies the ".." after it, as realpath does. The working
|
||||
// directory ends in "/" only when it is the root.
|
||||
a = strings.TrimSuffix(wd, "/") + "/" + root
|
||||
a, err := absolutePath(root)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("resolve %s: %w", root, err)
|
||||
}
|
||||
|
||||
r, err := filepath.EvalSymlinks(a)
|
||||
|
||||
+33
-27
@@ -2310,34 +2310,24 @@ func TestUnderRoot(t *testing.T) {
|
||||
}
|
||||
|
||||
// TestRunScanPassesOverDatabase scans a tree that holds the scan's own
|
||||
// database, given directly and through a symlinked directory, which
|
||||
// the scan resolves to the same real path. The walk passes over the
|
||||
// database file, its -wal and -shm files and its .lock file with no
|
||||
// line and no count, and the record an earlier scan left for the .lock
|
||||
// file is deleted. A rescan of the unchanged tree updates nothing, and
|
||||
// report lists none of the database's files.
|
||||
// database, and in one case also a hard link to the database file. The
|
||||
// walk knows the database's files by device ID and inode number, so it
|
||||
// passes over the database file, its -wal and -shm files, its .lock
|
||||
// file and the hard link with no line and no count. The record an
|
||||
// earlier scan left for the .lock file is deleted. A rescan of the
|
||||
// unchanged tree updates nothing, and report lists none of the
|
||||
// database's files.
|
||||
func TestRunScanPassesOverDatabase(t *testing.T) {
|
||||
for _, viaSymlink := range []bool{false, true} {
|
||||
t.Run(fmt.Sprintf("via symlink %v", viaSymlink), func(t *testing.T) {
|
||||
base := realTempDir(t)
|
||||
tree := filepath.Join(base, "real", "tree")
|
||||
operand := tree
|
||||
for _, viaHardLink := range []bool{false, true} {
|
||||
t.Run(fmt.Sprintf("via hard link %v", viaHardLink), func(t *testing.T) {
|
||||
tree := realTempDir(t)
|
||||
hardLink := filepath.Join(tree, "hardlink")
|
||||
|
||||
writeFile(t, tree, "a", pattern(1, 100))
|
||||
writeFile(t, tree, "b", pattern(1, 100))
|
||||
writeFile(t, tree, "empty1", nil)
|
||||
writeFile(t, tree, "empty2", nil)
|
||||
|
||||
if viaSymlink {
|
||||
err := os.Symlink(filepath.Join(base, "real"),
|
||||
filepath.Join(base, "link"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
operand = filepath.Join(base, "link", "tree")
|
||||
}
|
||||
|
||||
dbPath := filepath.Join(tree, "db.sqlite")
|
||||
t.Setenv(databaseEnv, dbPath)
|
||||
|
||||
@@ -2355,15 +2345,28 @@ func TestRunScanPassesOverDatabase(t *testing.T) {
|
||||
|
||||
closeScanDatabase(t.Context(), db, dbPath)
|
||||
|
||||
assertScanSummary(t,
|
||||
if viaHardLink {
|
||||
err = os.Link(dbPath, hardLink)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
stderr := assertScanSummary(t,
|
||||
"4 files seen (4 added, 0 updated, 1 removed, 0 unchanged)",
|
||||
operand)
|
||||
assertScanSummary(t,
|
||||
tree)
|
||||
stderr += assertScanSummary(t,
|
||||
"4 files seen (0 added, 0 updated, 0 removed, 4 unchanged)",
|
||||
operand)
|
||||
tree)
|
||||
|
||||
if strings.Contains(stderr, hardLink) {
|
||||
t.Errorf("scan stderr = %q, want no line naming %s",
|
||||
stderr, hardLink)
|
||||
}
|
||||
|
||||
report := runStdout(t, cmdReport)
|
||||
if strings.Contains(report, "db.sqlite") ||
|
||||
strings.Contains(report, hardLink) ||
|
||||
!strings.Contains(report, filepath.Join(tree, "empty2")) {
|
||||
t.Errorf("report = %q, want the empty files and none of "+
|
||||
"the database's files", report)
|
||||
@@ -2389,8 +2392,9 @@ func TestRunScanPassesOverDatabaseOperands(t *testing.T) {
|
||||
|
||||
// assertScanSummary scans operands and fails the test unless the
|
||||
// summary line gives counts with nothing skipped, and the line naming
|
||||
// the database is the only one that names one of its files.
|
||||
func assertScanSummary(t *testing.T, counts string, operands ...string) {
|
||||
// the database is the only one that names one of its files. It returns
|
||||
// everything the scan printed to stderr.
|
||||
func assertScanSummary(t *testing.T, counts string, operands ...string) string {
|
||||
t.Helper()
|
||||
|
||||
stderr := scanOK(t, operands...)
|
||||
@@ -2404,4 +2408,6 @@ func assertScanSummary(t *testing.T, counts string, operands ...string) {
|
||||
t.Errorf("scan %q: stderr = %q, want only the line naming the "+
|
||||
"database to name one of its files", operands, stderr)
|
||||
}
|
||||
|
||||
return stderr
|
||||
}
|
||||
Reference in new issue
Block a user