Keep a non-root user's database in the XDG state directory (closes #171) #190

Merged
clawbot merged 1 commits from issue-171-xdg-state-database into next 2026-11-11 10:07:13 +01:00
Collaborator

Closes #171.

Without SFDUPES_DATABASE, a user other than root now keeps the database in sfdupes/db.sqlite under the XDG state directory. scan creates missing directories there with mode 700, as the XDG Base Directory specification asks. An XDG_STATE_HOME that is set and not an absolute path is a fatal error for every subcommand. Root keeps /var/lib/sfdupes/db.sqlite. SFDUPES_DATABASE still overrides both. All five subcommands use the same default. The help of sfdupes and of scan, the README and the command's doc comment say where the database is.

The state directory comes from github.com/adrg/xdg, the XDG library named in the Go package defaults. It reads XDG_STATE_HOME and HOME once at startup; tests that change them call xdg.Reload.

What the diff does not show:

  • Root means effective user id 0, so sudo sfdupes dwim still uses /var/lib/sfdupes.
  • Judgement call: an XDG_STATE_HOME starting with ~ is an error too. The library would expand it, but it is not an absolute path.
  • Judgement call: createDatabaseDirectory picks the mode from the user and SFDUPES_DATABASE, as databasePath does, instead of passing it through lockScanDatabase and openScanDatabase.
  • Judgement call: the permission-error hint the issue first asked for is not added. The owner's ruling (a sane non-root location) replaces it, and the definition of done does not ask for it.

Model: opus-5-5

Closes https://git.eeqj.de/sneak/sfdupes/issues/171. Without `SFDUPES_DATABASE`, a user other than root now keeps the database in `sfdupes/db.sqlite` under the XDG state directory. `scan` creates missing directories there with mode 700, as the XDG Base Directory specification asks. An `XDG_STATE_HOME` that is set and not an absolute path is a fatal error for every subcommand. Root keeps `/var/lib/sfdupes/db.sqlite`. `SFDUPES_DATABASE` still overrides both. All five subcommands use the same default. The help of `sfdupes` and of `scan`, the README and the command's doc comment say where the database is. The state directory comes from `github.com/adrg/xdg`, the XDG library named in the Go package defaults. It reads `XDG_STATE_HOME` and `HOME` once at startup; tests that change them call `xdg.Reload`. What the diff does not show: - Root means effective user id 0, so `sudo sfdupes dwim` still uses `/var/lib/sfdupes`. - Judgement call: an `XDG_STATE_HOME` starting with `~` is an error too. The library would expand it, but it is not an absolute path. - Judgement call: `createDatabaseDirectory` picks the mode from the user and `SFDUPES_DATABASE`, as `databasePath` does, instead of passing it through `lockScanDatabase` and `openScanDatabase`. - Judgement call: the permission-error hint the issue first asked for is not added. The owner's ruling (a sane non-root location) replaces it, and the definition of done does not ask for it. Model: opus-5-5
clawbot added the needs-review label 2026-11-11 05:00:38 +01:00
clawbot self-assigned this 2026-11-11 05:00:38 +01:00
Author
Collaborator

FAIL (needs-rework; the branch also conflicts with next).

  1. TODO.md, Completed Steps: the branch conflicts with current next, which added two entries at the top of the same list. Rebase onto next and keep all three entries, this one first.

  2. internal/sfdupes/db.go, createDatabaseDirectory with dbDirPerm (0o755): for a user other than root, scan creates a missing ~/.local, ~/.local/state and ~/.local/state/sfdupes with mode 755. The XDG Base Directory specification says a missing directory is created with mode 0700. The database lists every scanned path, and any user who can enter the home directory can read it. Acceptable: when the database is at the XDG default, scan creates missing directories with mode 0700, while root's /var/lib/sfdupes keeps 755; a test checks the mode; the README "Location" section says so.

  3. XDG_STATE_HOME set to a relative path is ignored without a word: the database goes to ~/.local/state, and the relative/state case of TestDefaultDatabasePath in internal/sfdupes/db_test.go asserts that. A setting that is set but invalid must stop the program at startup with an error. Acceptable: for a user other than root with SFDUPES_DATABASE unset, an XDG_STATE_HOME that is set and not an absolute path makes every subcommand exit 1 with an error naming XDG_STATE_HOME; that test case expects the error; the README "Location" section and the --help text say so. The --help text in internal/sfdupes/cli.go now says ~/.local/state is used "when that is unset", which is false for a relative value.

  4. README.md, Getting Started: "On Linux, any other user keeps it in ~/.local/state/sfdupes/db.sqlite." is false when XDG_STATE_HOME is set. Acceptable: "Any other user keeps it in sfdupes/db.sqlite in the XDG state directory, ~/.local/state by default on Linux."

Model: opus-5-5

FAIL (`needs-rework`; the branch also conflicts with `next`). 1. `TODO.md`, Completed Steps: the branch conflicts with current `next`, which added two entries at the top of the same list. Rebase onto `next` and keep all three entries, this one first. 2. `internal/sfdupes/db.go`, `createDatabaseDirectory` with `dbDirPerm` (`0o755`): for a user other than root, `scan` creates a missing `~/.local`, `~/.local/state` and `~/.local/state/sfdupes` with mode 755. The XDG Base Directory specification says a missing directory is created with mode 0700. The database lists every scanned path, and any user who can enter the home directory can read it. Acceptable: when the database is at the XDG default, `scan` creates missing directories with mode 0700, while root's `/var/lib/sfdupes` keeps 755; a test checks the mode; the README "Location" section says so. 3. `XDG_STATE_HOME` set to a relative path is ignored without a word: the database goes to `~/.local/state`, and the `relative/state` case of `TestDefaultDatabasePath` in `internal/sfdupes/db_test.go` asserts that. A setting that is set but invalid must stop the program at startup with an error. Acceptable: for a user other than root with `SFDUPES_DATABASE` unset, an `XDG_STATE_HOME` that is set and not an absolute path makes every subcommand exit 1 with an error naming `XDG_STATE_HOME`; that test case expects the error; the README "Location" section and the `--help` text say so. The `--help` text in `internal/sfdupes/cli.go` now says `~/.local/state` is used "when that is unset", which is false for a relative value. 4. `README.md`, Getting Started: "On Linux, any other user keeps it in `~/.local/state/sfdupes/db.sqlite`." is false when `XDG_STATE_HOME` is set. Acceptable: "Any other user keeps it in `sfdupes/db.sqlite` in the XDG state directory, `~/.local/state` by default on Linux." Model: opus-5-5
clawbot force-pushed issue-171-xdg-state-database from 6ded88234a to 20dac0bcbe 2026-11-11 07:06:37 +01:00 Compare
Author
Collaborator

Reworked in 20dac0b, rebased onto next.

  1. This entry is first in Completed Steps; the three entries next added follow it.
  2. At a non-root user's default path, scan creates missing directories with mode 700; root's and SFDUPES_DATABASE's stay 755. TestRunNonRootUsesStateDirectory checks both directories it creates; README "Location" says so.
  3. Now sfdupes: XDG_STATE_HOME must be an absolute path, got "relative/state", exit 1, from every subcommand. The relative/state case expects it, TestRunRelativeStateHomeFails runs each subcommand, and README "Location" and --help say so.
  4. Replaced with the sentence given.

Model: opus-5-5

Reworked in `20dac0b`, rebased onto `next`. 1. This entry is first in Completed Steps; the three entries `next` added follow it. 2. At a non-root user's default path, `scan` creates missing directories with mode 700; root's and `SFDUPES_DATABASE`'s stay 755. `TestRunNonRootUsesStateDirectory` checks both directories it creates; README "Location" says so. 3. Now `sfdupes: XDG_STATE_HOME must be an absolute path, got "relative/state"`, exit 1, from every subcommand. The `relative/state` case expects it, `TestRunRelativeStateHomeFails` runs each subcommand, and README "Location" and `--help` say so. 4. Replaced with the sentence given. Model: opus-5-5
Author
Collaborator

FAIL (needs-rework; the branch also conflicts with next).

  1. internal/sfdupes/cli.go: rebased onto current next, the tree does not compile. next now has the help change from #189. That change adds its own databaseHelp for the root and scan help, built from the string constant defaultDatabasePath, which this PR turns into a function. This PR adds a second databaseHelp, and both set the root command's Long. TODO.md Completed Steps also conflicts. Acceptable:

    • one databaseHelp, shown by sfdupes --help and sfdupes scan --help, naming root's path, the XDG state directory for any other user, and the XDG_STATE_HOME error;
    • next's phrase "where creating it needs root" removed, since it is false for a user other than root;
    • TestRunHelp checks the merged text for both commands, with no cmdScan skip, and no longer expects "needs root";
    • the root command's Long set in one place;
    • in TODO.md, this entry first and next's entry for the help change kept below it;
    • make check green on the rebased branch.
  2. internal/sfdupes/db.go, createDatabaseDirectory: README "Location" says the directories scan creates for SFDUPES_DATABASE get mode 755, for a user other than root too. No test covers that case; only the 700 case is tested. Acceptable: a test that runs as a user other than root sets SFDUPES_DATABASE to a path under a missing directory, runs scan, and asserts that directory's mode is dbDirPerm.

  3. PR body: 261 words, over the 250-word limit. Its "What the diff does not show" list holds items the diff does show: the README's macOS path, the README's SFDUPES_DATABASE example for reading root's database, and the Getting Started change. Acceptable: under 250 words once updated for the rebase, with those items cut.

Model: opus-5-5

FAIL (`needs-rework`; the branch also conflicts with `next`). 1. `internal/sfdupes/cli.go`: rebased onto current `next`, the tree does not compile. `next` now has the help change from https://git.eeqj.de/sneak/sfdupes/pulls/189. That change adds its own `databaseHelp` for the root and `scan` help, built from the string constant `defaultDatabasePath`, which this PR turns into a function. This PR adds a second `databaseHelp`, and both set the root command's `Long`. `TODO.md` Completed Steps also conflicts. Acceptable: - one `databaseHelp`, shown by `sfdupes --help` and `sfdupes scan --help`, naming root's path, the XDG state directory for any other user, and the `XDG_STATE_HOME` error; - `next`'s phrase "where creating it needs root" removed, since it is false for a user other than root; - `TestRunHelp` checks the merged text for both commands, with no `cmdScan` skip, and no longer expects "needs root"; - the root command's `Long` set in one place; - in `TODO.md`, this entry first and `next`'s entry for the help change kept below it; - `make check` green on the rebased branch. 2. `internal/sfdupes/db.go`, `createDatabaseDirectory`: README "Location" says the directories `scan` creates for `SFDUPES_DATABASE` get mode 755, for a user other than root too. No test covers that case; only the 700 case is tested. Acceptable: a test that runs as a user other than root sets `SFDUPES_DATABASE` to a path under a missing directory, runs `scan`, and asserts that directory's mode is `dbDirPerm`. 3. PR body: 261 words, over the 250-word limit. Its "What the diff does not show" list holds items the diff does show: the README's macOS path, the README's `SFDUPES_DATABASE` example for reading root's database, and the Getting Started change. Acceptable: under 250 words once updated for the rebase, with those items cut. Model: opus-5-5
Author
Collaborator

For the reworker (this ends the review loop): rebase onto next, then fix exactly the findings in the newest review as its "Acceptable" lines say, nothing else. The next review checks only those findings and anything the fix breaks.

Model: opus-5-5

For the reworker (this ends the review loop): rebase onto `next`, then fix exactly the findings in the newest review as its "Acceptable" lines say, nothing else. The next review checks only those findings and anything the fix breaks. Model: opus-5-5
clawbot force-pushed issue-171-xdg-state-database from 20dac0bcbe to 5cb336039c 2026-11-11 09:01:31 +01:00 Compare
Author
Collaborator

Reworked in 5cb3360, rebased onto next.

  1. One databaseHelp, without "needs root", shown by sfdupes --help and sfdupes scan --help; root's Long is set once; TestRunHelp checks all three help cases with no skip, so next's copy of that check is gone and its test is now TestRunHelpDescribesOutput; the TODO.md entry is first and names both help texts.
  2. TestRunNonRootDatabaseEnvDirectoryMode in internal/sfdupes/cli_test.go covers it.
  3. Trimmed, with the three items cut.

Model: opus-5-5

Reworked in `5cb3360`, rebased onto `next`. 1. One `databaseHelp`, without "needs root", shown by `sfdupes --help` and `sfdupes scan --help`; root's `Long` is set once; `TestRunHelp` checks all three help cases with no skip, so `next`'s copy of that check is gone and its test is now `TestRunHelpDescribesOutput`; the `TODO.md` entry is first and names both help texts. 2. `TestRunNonRootDatabaseEnvDirectoryMode` in `internal/sfdupes/cli_test.go` covers it. 3. Trimmed, with the three items cut. Model: opus-5-5
Author
Collaborator

For the reworker: the review found the code sound; the only conflict is TODO.md. Rebase onto next and drop every TODO.md change from this PR (take next's TODO.md as is; finished work is not logged there any more). Change nothing else; run make check; push. The PR then merges on a clean rebase.

Model: opus-5-5

For the reworker: the review found the code sound; the only conflict is `TODO.md`. Rebase onto `next` and drop every `TODO.md` change from this PR (take `next`'s `TODO.md` as is; finished work is not logged there any more). Change nothing else; run `make check`; push. The PR then merges on a clean rebase. Model: opus-5-5
clawbot added 1 commit 2026-11-11 09:40:23 +01:00
Without SFDUPES_DATABASE, a user other than root now uses
sfdupes/db.sqlite in the XDG state directory (~/.local/state by
default) instead of failing on /var/lib/sfdupes. scan creates the
missing directories there with mode 0700, as the XDG Base Directory
specification asks. An XDG_STATE_HOME that is not an absolute path is
a fatal error for every subcommand. Root keeps
/var/lib/sfdupes/db.sqlite. The help of sfdupes and of scan, and
README, say where the database is. Adds github.com/adrg/xdg, the
decided XDG library.

Model: opus-5-5
clawbot force-pushed issue-171-xdg-state-database from 5cb336039c to 049a74a10b 2026-11-11 09:40:23 +01:00 Compare
Author
Collaborator

Reworked in 049a74a, rebased onto next.

  • TODO.md: every change dropped; the file matches next.
  • The rebase also conflicted in go.mod (both github.com/adrg/xdg and next's github.com/rivo/uniseg kept) and in TestRunHelpDescribesOutput (next's PATH-operand checks kept, the database checks still in TestRunHelp). Nothing else changed.

Model: opus-5-5

Reworked in `049a74a`, rebased onto `next`. - `TODO.md`: every change dropped; the file matches `next`. - The rebase also conflicted in `go.mod` (both `github.com/adrg/xdg` and `next`'s `github.com/rivo/uniseg` kept) and in `TestRunHelpDescribesOutput` (`next`'s PATH-operand checks kept, the database checks still in `TestRunHelp`). Nothing else changed. Model: opus-5-5
Author
Collaborator

PASS: ready to merge into next.

  • Judgement call, not a finding: a relative HOME makes the default database /sfdupes/db.sqlite.

Model: opus-5-5

PASS: ready to merge into `next`. - Judgement call, not a finding: a relative `HOME` makes the default database `/sfdupes/db.sqlite`. Model: opus-5-5
clawbot merged commit 652df1c382 into next 2026-11-11 10:07:13 +01:00
Sign in to join this conversation.