restore: preserve hardlinks on restore#492
Merged
aawsome merged 1 commit intorustic-rs:mainfrom Mar 28, 2026
Merged
Conversation
Restore already records inode, device id, and link count metadata for files, but the restore path recreated every file as an independent plain file. That meant hardlinked files were silently de-linked on restore. Add a post-restore hardlink pass keyed by the stored `(device_id, inode)` identity. After file contents and metadata are restored, sibling paths in each hardlink group are replaced with hardlinks to a canonical restored path. Also add a focused integration test using the existing backup fixture that contains a hardlink pair. The test now asserts that restoring the snapshot recreates a shared inode, not just matching file contents.
Member
|
Hi @williamstein! Thanks a lot for proposing this PR. IMO this is a good first step. I would add the following enhancements:
However, we can just start with merging this PR and do the above in follow-up PRs. |
aawsome
approved these changes
Mar 28, 2026
Member
aawsome
left a comment
There was a problem hiding this comment.
LGTM! Thanks a lot @williamstein
aawsome
added a commit
that referenced
this pull request
Apr 5, 2026
With this PR, hardlinks are not crated and then removed, but only linked. Follow-up to #492
github-merge-queue bot
pushed a commit
that referenced
this pull request
Apr 5, 2026
## 🤖 New release * `rustic_core`: 0.10.1 -> 0.11.0 (✓ API compatible changes) <details><summary><i><b>Changelog</b></i></summary><p> <blockquote> ## [0.11.0](rustic_core-v0.10.1...rustic_core-v0.11.0) - 2026-04-05 ### Added - Optimize hardlink creation in restore ([#495](#495)) - add exclude-if-xattr option ([#491](#491)) ### Fixed - make `ignore`'s `.git_exclude()` mirror `.git_ignore()` ([#494](#494)) ### Other - update dependencies ([#496](#496)) - preserve hardlinks on restore ([#492](#492)) - use general tree modifier in `repair snapshots` ([#463](#463)) - [**breaking**] Optimize file streaming ([#489](#489)) - [**breaking**] use Cow to avoid OsString allocations ([#487](#487)) </blockquote> </p></details> --- This PR was generated with [release-plz](https://github.com/release-plz/release-plz/). Co-authored-by: rustic-release-plz[bot] <182542030+rustic-release-plz[bot]@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This is the simplest (and probably least efficient) way to address #16
This issue has been open since 2022, and it seems potentially like a way to have very confusing BUGS if you backup this folder:
then restore it, and suddenly it behave very differently after the restore.
I'm building software where rustic backup/restore has to actually work well -- it's not just for emergency recovery, but part of the normal lifecycle of user data. I ran some big tests of backup/restore, and only after fixing this hard link issue was the filesystem restored properly.
Fortunately, restore already records inode, device id, and link count metadata for files. But the restore path recreated every file as an independent plain file, so hardlinks were silently de-linked on restore.
This PR adds a post-restore hardlink pass keyed by the stored
(device_id, inode)identity. After file contents and metadata are restored, sibling paths in each hardlink group are replaced with hardlinks to a canonical restored path.Also add a test using the existing backup fixture that contains a hardlink pair.
Obviously, it could make good sense to close this if:
If you close this, it would probably be good though to mention clearly on the main rustic README page that hardlinks silently break on backup --> restore, as it can lead to major bugs for users, and it's only something they might find via careful testing / debugging of errors popping up later (that was the case for me). "hard link" isn't mentioned anywhere on main rustic README or issue tracker (it's in this rustic_core issue).
For anybody who does need this, I put a release here for linux:
https://github.com/sagemathinc/rustic/releases
Thanks!