The "This computer" indexer walks $HOME, and `default_excludes` only kept it
out of the Drive mountpoint and our own state/cache dirs. A home directory
that also holds third-party FUSE mounts (sshfs, rclone) got walked into.
`scan` does ask for `same_file_system(true)`, which looks sufficient and is
not: `ignore` implements it as `is_same_file_system` -> `device_num(path)` ->
`path.metadata()`, run after the directory is queued. Learning that a mount is
foreign requires stat'ing it first, and against a mount whose server is gone
that stat never returns.
The consequence is worse than a stalled scan. The blocked thread sits in
uninterruptible FUSE wait, so it ignores SIGKILL: the daemon can never exit,
systemd's stop times out, and the unreapable process keeps the cache.db lock
the next start needs, turning one dead mount into a permanent restart loop.
No userspace recovery exists once a thread is in that state, so the fix has to
be to never enter it.
Exclude every mountpoint strictly below the scan root, read by path from
/proc/self/mounts (octal-unescaped, so a path with a space still matches).
`filter_entry` runs before the device stat, so the walker never touches them.
Re-read per scan rather than cached at startup: a mount that appeared since
the daemon started is the likeliest to be half-alive.
This does not change what is indexed — a nested mount is a different device,
so it was already going to be skipped. Only the stat in front of that decision
goes away.
Refs: docs/BUGS.md B90
- keyring 3 -> keyring-core 1 + dbus-secret-service-keyring-store 1
(crypto-rust). Same libdbus Secret Service backend; with no target the
store matches items keyring 3 wrote, so stored sessions survive.
- ksni 0.2 -> 0.3 via the blocking API, with assume_sni_available so a
tray that starts before the SNI watcher waits instead of failing. Drops
the clap 2 / atty chain (Dependabot atty advisory).
- gtk4 0.11, libadwaita 0.9, webkit6 0.6, rusqlite 0.40, fuser 0.18,
directories 6, sha1/sha2 0.11, base64 0.23.
- A queued rename that also changes folder now lands as one `move-multiple`
request carrying the target name instead of rename-then-move, so there is
no half-applied state and no second call for stale name requirements to
hit (B46 follow-up). A destination name collision moves under a conflict
name in one step; same-folder renames still use the rename endpoint.
- Shared with me shows the inviter and invite date (GUI subtitle, `pdfs
shared-with-me`), with a warning when the invitation signature does not
verify against the claimed inviter's keys. `DirEntry` gains `shared_by`,
`shared_at` and `shared_by_unverified`, all serde-defaulted.
- Bump `proton-sdk` / `proton-drive-rs` to 0.6.3 (anonymous-node moves,
`TrashTime` trashed detection).
The login backoff had no wake-up path. A unit started at boot sits in
wait_for_session and reaches the ceiling within ~90s; the user then signs
in and `enable --now` is a no-op on an already-active unit, so the daemon
kept sleeping and the mount appeared up to a full interval late.
- service::enable_start now runs `enable` + `restart` instead of
`enable --now`, so the GUI login and the tray's Connect interrupt the
sleep immediately and still start a unit that is down.
- `pdfs login` restarts the unit when it is already active. It stays out
of `enable` on purpose, so a headless setup driving `pdfs daemon` by
hand is left alone.
- Lower the backoff ceiling from 60s to 15s. It is still an 80% cut in
keyring/D-Bus wakeups (28,800/day -> 5,760/day) and it bounds the
worst-case latency for paths that never touch systemd.
Also corrects the flat-3s-poll figure in the doc comment: 86400/3 is
28,800 checks a day, not 48,000.
The daemon's wait-for-login loop slept a flat 3 seconds between checks and
never gave up, so an enabled service with no saved session polled the keyring
28,800 times a day. Each check is more than a sleep: auth::load() builds a
fresh keyring::Entry, so every iteration opens a new D-Bus connection to the
Secret Service and negotiates a session on it.
Back off from 3s to a 60s ceiling and say "not logged in" once at INFO rather
than on every iteration, which also stops the journal filling with tens of
thousands of identical lines.
The wait is extracted into wait_for_session() taking the load and sleep calls
as parameters, so the schedule is testable without a keyring and without real
time passing.
Refs #21