Skip to content

Change TempFile saving method to use atomic renames - #13992

Draft
phoerious wants to merge 2 commits into
developfrom
fix/qfile-save-failure
Draft

phoerious wants to merge 2 commits into
developfrom
fix/qfile-save-failure

Conversation

@phoerious

@phoerious phoerious commented Nov 2, 2026 •

Copy link
Copy Markdown
Member

Fixes #13970

The previous TempFile saving method failed under Qt6 on certain remote file systems that don't support hard-linking or O_TMPFILE renames, such as remote SMB shares. Here, QFile::rename() created an unamed Q_TMPFILE, and tried to rename it with linkat(), which failed. Since QFile::rename() does not do file replacements, KeePassXC had to remove the target before moving the new file in place, which could cause to data loss when the rename failed.

This patch changes TempFile to a hybrid approach: The database is first written to a temporary file. The temporary file is committed to disk, and then streamed to a named sibling of the target file. Streaming avoids using QFile::copy() or QFile::rename(), which can trigger the failure. After straming the file back, the target file is replaced atomically (if possible) with the sibling.

Warning

Needs testing. It's quite possible that it fixes the addressed issue, but reintroduces the Dropbox locking issues that the original TempFile saving method was supposed to solve.

Codex created a first rough draft and wrote the tests.

Type of change

  • ✅ Bug fix (non-breaking change that fixes an issue)

@phoerious phoerious added this to the v2.8.1 milestone Nov 2, 2026
@phoerious
phoerious requested review from a team and a lite review from Copilot November 2, 2026 17:21
@phoerious phoerious added high priority 🚨 pr: bugfix Pull request fixes a bug pr: ai-assisted Pull request contains significant contributions by generative AI labels Nov 2, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Two active issues remain in src/core/Database.cpp: an unbalanced Windows error message and a source-text change that would invalidate existing translations.

2 open issues
What changed in this PR

This pull request reworks KeePassXC’s TempFile database-saving path to stage data in a sibling file, synchronize it, and perform platform-native replacement, improving reliability on remote filesystems.

Changes:

  • Added POSIX and Windows file synchronization and replacement helpers.
  • Reworked staged saves to preserve the original database on replacement failures and retain recovery copies.
  • Updated save-method documentation and removed the obsolete restore helper.
  • Added regression coverage for staged saves, atomic saves, permissions, recovery, failures, and cleanup.

Review comments remain regarding an unbalanced Windows error message, translation-string compatibility, long filenames exceeding filesystem limits, and replacement of dangling Windows symlinks.

File Description
tests/​TestDatabase.h Declares the new staged-save test cases.
tests/​TestDatabase.cpp Adds coverage for staged saves, atomic saves, failure preservation, recovery, permissions, and temporary-file cleanup.
src/​core/​Database.h Updates save-method documentation and removes the obsolete restore helper declaration.
src/​core/​Database.cpp Implements file synchronization, native replacement, staged sibling writes, recovery handling, and related save-path logic.

Review effort: Lite

Comment thread src/core/Database.cpp Resolved
Comment thread src/core/Database.cpp Resolved
@phoerious
phoerious force-pushed the fix/qfile-save-failure branch from 8ec9e7f to 97d19fc Compare November 2, 2026 17:28
@phoerious
phoerious requested a lite review from Copilot November 2, 2026 17:28
@phoerious
phoerious force-pushed the fix/qfile-save-failure branch 2 times, most recently from 68dc292 to 1765b06 Compare November 2, 2026 17:29

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

A critical test assertion omits “database” from the expected backup-path pattern, so the staged-save recovery check fails at regex matching instead of validating recovery.

1 open issue
2 resolved since last review

Review effort: Lite

Comment thread tests/TestDatabase.cpp Resolved
@phoerious
phoerious force-pushed the fix/qfile-save-failure branch from 1765b06 to 6482ea5 Compare November 2, 2026 17:35
@codecov

codecov Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 69.89247% with 28 lines in your changes missing coverage. Please review.
✅ Project coverage is 61.24%. Comparing base (c263a11) to head (45056dc).

Files with missing lines Patch % Lines
src/core/Database.cpp 69.57% 28 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           develop   #13992      +/-   ##
===========================================
+ Coverage    61.19%   61.24%   +0.05%     
===========================================
  Files          396      396              
  Lines        37926    37988      +62     
===========================================
+ Hits         23208    23264      +56     
- Misses       14718    14724       +6     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Fixes #13970

The previous TempFile saving method failed under Qt6 on certain remote file systems that don't support hard-linking or O_TMPFILE renames, such as remote SMB shares. Here, QFile::rename() created an unamed Q_TMPFILE, and tried to rename it with linkat(), which failed. Since QFile::rename() does not do file replacements, KeePassXC had to remove the target before moving the new file in place, which could cause to data loss when the rename failed.

This patch changes TempFile to a hybrid approach: The database is first written to a temporary file. The temporary file is committed to disk, and then streamed to a named sibling of the target file. Streaming avoids using QFile::copy() or QFile::rename(), which can trigger the failure. After straming the file back, the target file is replaced atomically (if possible) with the sibling.
@phoerious
phoerious force-pushed the fix/qfile-save-failure branch 2 times, most recently from e74c406 to b92e84c Compare November 3, 2026 12:15
@phoerious
phoerious force-pushed the fix/qfile-save-failure branch from b92e84c to 45056dc Compare November 3, 2026 12:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

high priority 🚨 pr: ai-assisted Pull request contains significant contributions by generative AI pr: bugfix Pull request fixes a bug

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Temporary file save method deletes the database on SMB/CIFS shares without hard-link support

2 participants