fix(prefs): replace the desktop preference store atomically

Upstream shared_preferences_windows and _linux write the whole preference
document with a bare `writeAsStringSync`. That opens with the default
`FileMode.write`, which truncates the live file before writing it, so every
single preference write has a window in which the only copy on disk is empty
or half-written. A crash, power loss, forced reboot or antivirus interception
inside that window leaves a document that fails to parse on every subsequent
launch — and the store holds the credential-vault key, so the loss is not
recoverable by rewriting it. This is the corruption class behind #1732; the
recovery path already landed is a band-aid over it.

Vendor both packages under packages/ — the convention saf_util and
wakelock_plus already follow — and stage, flush, then rename over the target.
The flush has to precede the rename or it could publish contents that were
never committed, the same corruption by another route. Staging uses one fixed
sibling name rather than a stamped one, because the file is a plaintext copy
of the vault key, tracker refresh tokens and Seerr cookies; it is created in
the target's own directory so rename stays on one volume and the mode matches
what the canonical file would have had, and a stale one is swept once the
canonical document has been read cleanly. Both deltas are marked in-source and
in provenance.json with the refresh contract.

Atomicity is proven, not asserted. A hard link to the store observes the old
document after a write, which only holds when the directory entry was replaced
— truncate-in-place would have rewritten the shared inode, and that test does
fail against unpatched upstream. Upstream's own suites still pass unchanged in
both packages and now run in CI, so the patch keeps the contract it inherited.
Windows `MoveFileExW` replacement semantics cannot be proven on a POSIX runner
or a memory file system, so they get their own test on the existing
windows-latest job, including replacement while a reader holds the file open —
antivirus and Search Indexer both do.
This commit is contained in:
edde746
2026-08-01 06:59:20 +02:00
parent 9ecf8db90f
commit 3f49bcabf8
43 changed files with 3243 additions and 13 deletions
+13 -3
View File
@@ -99,11 +99,14 @@ dev_dependencies:
drift_dev: ^2.28.3
shared_preferences_platform_interface: ^2.4.0
path_provider_platform_interface: ^2.1.0
# Drives the real desktop preference backend against a temp file in tests, so
# the #1732 corrupt-store repair loop is covered on any host and not only on
# a Windows/Linux runner. Both classes expose `fs`/`pathProvider` for this.
# Drive the real desktop preference backends against a temp file in tests, so
# the #1732 corrupt-store repair loop and the vendored atomic write are both
# covered against genuine file I/O. All four expose `fs`/`pathProvider`.
# The two shared_preferences packages resolve to the vendored copies below.
shared_preferences_linux: ^2.4.1
shared_preferences_windows: ^2.4.1
path_provider_linux: ^2.2.1
path_provider_windows: ^2.3.0
plugin_platform_interface: ^2.1.0
freezed: ^3.2.5
analyzer: 10.2.0
@@ -124,6 +127,13 @@ dependency_overrides:
url: https://github.com/edde746/auto_updater
ref: 9e150f7
path: packages/auto_updater_windows
# Vendored 2.4.1 with an atomic store replacement (CHANGELOG, provenance.json)
# — upstream truncates the live preference document on every write, which is
# the corruption behind #1732.
shared_preferences_windows:
path: packages/shared_preferences_windows
shared_preferences_linux:
path: packages/shared_preferences_linux
sentry:
org: plezy
project: plezy