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