Repository: bitcoin/bitcoin
File: src/wallet/wallet.cpp (C++)

Code before:
            success = true;
        }
    }

    // In case of loading failure, we need to remember the wallet dirs to remove.
    // A `set` is used as it may be populated with the same wallet directory paths multiple times,
    // both before and after loading. This ensures the set is complete even if one of the wallets
    // fails to load.
    std::set<fs::path> wallet_dirs_to_remove;
    if (success) {
        Assume(!res.wallet); // We will set it here.
        // Check if the local wallet is empty after migration
        if (empty_local_wallet) {
            // This wallet has no records. We can safely remove it.
            std::vector<fs::path> paths_to_remove = local_wallet->GetDatabase().Files();
            local_wallet.reset();
            for (const auto& path_to_remove : paths_to_remove) fs::remove_all(path_to_remove);
        }

        // Migration successful, load all the migrated wallets.
        for (std::shared_ptr<CWallet>* wallet_ptr : {&local_wallet, &res.watchonly_wallet, &res.solvables_wallet}) {
            if (success && *wallet_ptr) {
                std::shared_ptr<CWallet>& wallet = *wallet_ptr;
                if (wallet->GetName().empty()) {
                    // Only store the migrated wallet.dat for removal, never its parent directory.
                    wallet_dirs_to_remove.insert(fs::PathFromString(wallet->GetDatabase().Filename()));
                } else {
                    // It is safe to delete the entire path because we created these wallets during migration
                    wallet_dirs_to_remove.insert(fs::PathFromString(wallet->GetDatabase().Filename()).parent_path());
                }
                // Load wallet
                assert(wallet.use_count() == 1);
                std::string wallet_name = wallet->GetName();
                wallet.reset();
                wallet = LoadWallet(context, wallet_name, /*load_on_start=*/std::nullopt, options, status, error, warnings);
                success = (wallet != nullptr);

                // When no wallet is set, set the main wallet.
                if (success && !res.wallet) {
                    res.wallet_name = wallet->GetName();
                    res.wallet = std::move(wallet);
                }
            }
        }
    }
    if (!success) {
        // 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));
        if (res.watchonly_wallet) created_wallets.push_back(std::move(res.watchonly_wallet));
        if (res.solvables_wallet) created_wallets.push_back(std::move(res.solvables_wallet));

Review comment (bug):
In 24095b090b8784a30a8524207d163ff970af1b97 "wallet: fix unnamed legacy wallet migration failure"

Should use `Files()` to cleanup the `-journal` file too.

Code after:
    res.backup_path = backup_path;

    bool success = false;

    // Unlock the wallet if needed
    if (local_wallet->IsLocked() && !local_wallet->Unlock(passphrase)) {
        if (passphrase.find('\0') == std::string::npos) {
            return util::Error{Untranslated("Error: Wallet decryption failed, the wallet passphrase was not provided or was incorrect.")};
        } else {
            return util::Error{Untranslated("Error: Wallet decryption failed, the wallet passphrase entered was incorrect. "
                                            "The passphrase contains a null character (ie - a zero byte). "
                                            "If this passphrase was set with a version of this software prior to 25.0, "
                                            "please try again with only the characters up to — but not including — "
                                            "the first null character.")};
        }
    }

    // Indicates whether the current wallet is empty after migration.
    // Notes:
    // When non-empty: the local wallet becomes the main spendable wallet.
    // When empty: The local wallet is excluded from the result, as the
    //             user does not expect an empty spendable wallet after
    //             migrating only watch-only scripts.
    bool empty_local_wallet = false;

    {
        LOCK(local_wallet->cs_wallet);
        // First change to using SQLite
        if (!local_wallet->MigrateToSQLite(error)) return util::Error{error};

        // Do the migration of keys and scripts for non-empty wallets, and cleanup if it fails
        if (HasLegacyRecords(*local_wallet)) {
            success = DoMigration(*local_wallet, context, error, res);
            // No scripts mean empty wallet after migration
            empty_local_wallet = local_wallet->GetAllScriptPubKeyMans().empty();
        } else {
            // Make sure that descriptors flag is actually set
            local_wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
            success = true;
        }
    }

    // In case of loading failure, we need to remember the wallet files we have created to remove.
    // A `set` is used as it may be populated with the same wallet directory paths multiple times,
    // both before and after loading. This ensures the set is complete even if one of the wallets
    // fails to load.
    std::set<fs::path> wallet_files_to_remove;
    std::set<fs::path> wallet_empty_dirs_to_remove;

    // Helper to track wallet files and directories for cleanup on failure.
    // Only directories of wallets created during migration (not the main wallet) are tracked.