-
Notifications
You must be signed in to change notification settings - Fork 39.1k
wallet: Fix relative path backup during migration. #32273
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
e22c359
f6ee59b
70f1c99
63c6d36
41faef5
f0bb3d5
76fe0e5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4229,10 +4229,20 @@ util::Result<MigrationResult> MigrateLegacyToDescriptor(std::shared_ptr<CWallet> | |
| return util::Error{_("Error: This wallet is already a descriptor wallet")}; | ||
| } | ||
|
|
||
| // Make a backup of the DB | ||
| fs::path this_wallet_dir = fs::absolute(fs::PathFromString(local_wallet->GetDatabase().Filename())).parent_path(); | ||
| fs::path backup_filename = fs::PathFromString(strprintf("%s_%d.legacy.bak", (wallet_name.empty() ? "default_wallet" : wallet_name), GetTime())); | ||
| fs::path backup_path = this_wallet_dir / backup_filename; | ||
| // Make a backup of the DB in the wallet's directory with a unique filename | ||
| // using the wallet name and current timestamp. The backup filename is based | ||
| // on the name of the parent directory containing the wallet data in most | ||
| // cases, but in the case where the wallet name is a path to a data file, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In commit "wallet: Fix migration of wallets with pathnames." (70f1c99) IMO, it's confusing for this comment to distinguish between the case when the wallet is a directory and the case when it's a file, when there is no distinction in the code below. The code below is always returning the last path component no matter what kind of path is provided. |
||
| // the name of the data file is used, and in the case where the wallet name | ||
| // is blank, "default_wallet" is used. | ||
| const std::string backup_prefix = wallet_name.empty() ? "default_wallet" : [&] { | ||
| // fs::weakly_canonical resolves relative specifiers and remove trailing slashes. | ||
| const auto legacy_wallet_path = fs::weakly_canonical(GetWalletDir() / fs::PathFromString(wallet_name)); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In commit "wallet: Fix migration of wallets with pathnames." (70f1c99) This looks good, but just for comparison here is an updated version of the suggestion from: #32273 (review) diff
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -4231,14 +4231,15 @@ util::Result<MigrationResult> MigrateLegacyToDescriptor(std::shared_ptr<CWallet>
// Make a backup of the DB in the wallet's directory with a unique filename
// using the wallet name and current timestamp. The backup filename is based
- // on the name of the parent directory containing the wallet data in most
- // cases, but in the case where the wallet name is a path to a data file,
- // the name of the data file is used, and in the case where the wallet name
- // is blank, "default_wallet" is used.
- const std::string backup_prefix = wallet_name.empty() ? "default_wallet" : [&] {
- // fs::weakly_canonical resolves relative specifiers and remove trailing slashes.
- const auto legacy_wallet_path = fs::weakly_canonical(GetWalletDir() / fs::PathFromString(wallet_name));
- return fs::PathToString(legacy_wallet_path.filename());
+ // on walletname but expanded to "default_wallet" if it is empty and only
+ // the final filename portion is used if contains slashes.
+ std::string backup_prefix = wallet_name.empty() ? "default_wallet" : [&] {
+ // Normalize any .. components
+ fs::path p{fs::PathFromString(wallet_name).lexically_normal()};
+ // Drop trailing slash if any.
+ if (p.filename().empty()) p = p.parent_path();
+ // Return last path component
+ return fs::PathToString(p.filename());
}();
fs::path backup_filename = fs::PathFromString(strprintf("%s_%d.legacy.bak", backup_prefix, GetTime()));IMO, the suggestion is safer because it just uses string manipulation to choose a backup file prefix, avoiding the complexity of I also think the suggestion is better because it chooses the backup prefix based on the wallet name, not based on the last component of the symlink destination path if the wallet is a symlink. But current code is fine and a clear improvement over the status quo.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. addressing this in #33122
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. re: #32273 (comment)
Note this change is now dropped from #33122, but could be good to revisit at some point (I think it's still a good idea).
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can't recall if this was discussed elsewhere, but in some cases when the path ends in a relative specifier the parent directory name can't be recovered without filesystem access: e.g. the wallet name is
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It was discussed, see #32273 (review):
More generally, putting this specific suggestion aside, hopefully it is clear that turning a wallet name into a safe backup filename just requires string manipulation, and does not require accessing the filesystem, resolving symlinks, or calling GetWalletDir().
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree, maybe I had too strict of an idea of what the expectation would be for the destination of the backup: if the only goal is that the backup filename be safe and likely to work, then |
||
| return fs::PathToString(legacy_wallet_path.filename()); | ||
| }(); | ||
|
|
||
| fs::path backup_filename = fs::PathFromString(strprintf("%s_%d.legacy.bak", backup_prefix, GetTime())); | ||
| fs::path backup_path = fsbridge::AbsPathJoin(GetWalletDir(), backup_filename); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In commit "wallet: migration: Make backup in walletdir" (f6ee59b) Notes about this change (mostly for myself to avoid getting confused later)
|
||
| if (!local_wallet->BackupWallet(fs::PathToString(backup_path))) { | ||
| return util::Error{_("Error: Unable to make a backup of your wallet")}; | ||
| } | ||
|
|
@@ -4314,11 +4324,6 @@ util::Result<MigrationResult> MigrateLegacyToDescriptor(std::shared_ptr<CWallet> | |
| } | ||
| } | ||
| if (!success) { | ||
| // Migration failed, cleanup | ||
| // Before deleting the wallet's directory, copy the backup file to the top-level wallets dir | ||
| fs::path temp_backup_location = fsbridge::AbsPathJoin(GetWalletDir(), backup_filename); | ||
| fs::copy_file(backup_path, temp_backup_location, fs::copy_options::none); | ||
|
|
||
| // Make list of wallets to cleanup | ||
| std::vector<std::shared_ptr<CWallet>> created_wallets; | ||
| if (local_wallet) created_wallets.push_back(std::move(local_wallet)); | ||
|
|
@@ -4354,18 +4359,14 @@ util::Result<MigrationResult> MigrateLegacyToDescriptor(std::shared_ptr<CWallet> | |
| // Restore the backup | ||
| // Convert the backup file to the wallet db file by renaming it and moving it into the wallet's directory. | ||
| bilingual_str restore_error; | ||
| const auto& ptr_wallet = RestoreWallet(context, temp_backup_location, wallet_name, /*load_on_start=*/std::nullopt, status, restore_error, warnings, /*load_after_restore=*/false); | ||
| const auto& ptr_wallet = RestoreWallet(context, backup_path, wallet_name, /*load_on_start=*/std::nullopt, status, restore_error, warnings, /*load_after_restore=*/false); | ||
| if (!restore_error.empty()) { | ||
| error += restore_error + _("\nUnable to restore backup of wallet."); | ||
| return util::Error{error}; | ||
| } | ||
| // Verify that the legacy wallet is not loaded after restoring from the backup. | ||
| assert(!ptr_wallet); | ||
|
|
||
| // The wallet directory has been restored, but just in case, copy the previously created backup to the wallet dir | ||
| fs::copy_file(temp_backup_location, backup_path, fs::copy_options::none); | ||
| fs::remove(temp_backup_location); | ||
|
|
||
| return util::Error{error}; | ||
| } | ||
| return res; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In commit "wallet: Fix migration of wallets with pathnames." (70f1c99)
Note: This line of code was broken before this commit when
wallet_namecontained slashes. It would either cause theBackupWalletcall below to fail, or to write backup files to unexpected places, instead of writing them to-walletdiras intended after the previous commit.