From d65aa3369f373ba6340248b0dbd59e394ee56026 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Fri, 30 Jun 2023 10:49:26 +0000 Subject: [PATCH] daemon: split standardize_path from daemon._wallets keying - standardize_path is made more lenient: it no longer calls os.path.realpath, as that was causing issues on Windows with some mounted drives - daemon._wallets is still keyed on the old strict standardize_path, but filesystem operations in WalletStorage use the new lenient standardize_path. - daemon._wallets is strict to forbid opening the same logical file twice concurrently - fs operations however work better on the non-resolved paths, so they use those closes https://github.com/spesmilo/electrum/issues/8495 --- electrum/daemon.py | 29 ++++++++++++++++++++--------- electrum/util.py | 5 +++-- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/electrum/daemon.py b/electrum/daemon.py index 8b6485801..086f10773 100644 --- a/electrum/daemon.py +++ b/electrum/daemon.py @@ -402,7 +402,7 @@ class Daemon(Logger): if not self.config.NETWORK_OFFLINE: self.network = Network(config, daemon=self) self.fx = FxThread(config=config) - # path -> wallet; make sure path is standardized. + # wallet_key -> wallet self._wallets = {} # type: Dict[str, Abstract_Wallet] self._wallet_lock = threading.RLock() daemon_jobs = [] @@ -452,6 +452,17 @@ class Daemon(Logger): if self.config.LIGHTNING_USE_GOSSIP: self.network.start_gossip() + @staticmethod + def _wallet_key_from_path(path) -> str: + """This does stricter path standardization than 'standardize_path'. + It is used for keying the _wallets dict, but not for the actual filesystem operations. (see #8495) + """ + path = standardize_path(path) + # also resolve symlinks and windows network mounts/etc: + path = os.path.realpath(path) + path = os.path.normcase(path) + return str(path) + def with_wallet_lock(func): def func_wrapper(self: 'Daemon', *args, **kwargs): with self._wallet_lock: @@ -461,9 +472,9 @@ class Daemon(Logger): @with_wallet_lock def load_wallet(self, path, password, *, manual_upgrades=True) -> Optional[Abstract_Wallet]: path = standardize_path(path) + wallet_key = self._wallet_key_from_path(path) # wizard will be launched if we return - if path in self._wallets: - wallet = self._wallets[path] + if wallet := self._wallets.get(wallet_key): return wallet wallet = self._load_wallet(path, password, manual_upgrades=manual_upgrades, config=self.config) if wallet is None: @@ -503,13 +514,13 @@ class Daemon(Logger): @with_wallet_lock def add_wallet(self, wallet: Abstract_Wallet) -> None: path = wallet.storage.path - path = standardize_path(path) - self._wallets[path] = wallet + wallet_key = self._wallet_key_from_path(path) + self._wallets[wallet_key] = wallet run_hook('daemon_wallet_loaded', self, wallet) def get_wallet(self, path: str) -> Optional[Abstract_Wallet]: - path = standardize_path(path) - return self._wallets.get(path) + wallet_key = self._wallet_key_from_path(path) + return self._wallets.get(wallet_key) @with_wallet_lock def get_wallets(self) -> Dict[str, Abstract_Wallet]: @@ -531,8 +542,8 @@ class Daemon(Logger): @with_wallet_lock async def _stop_wallet(self, path: str) -> bool: """Returns True iff a wallet was found.""" - path = standardize_path(path) - wallet = self._wallets.pop(path, None) + wallet_key = self._wallet_key_from_path(path) + wallet = self._wallets.pop(wallet_key, None) if not wallet: return False await wallet.stop() diff --git a/electrum/util.py b/electrum/util.py index c31ba5711..8312f1bd0 100644 --- a/electrum/util.py +++ b/electrum/util.py @@ -522,12 +522,13 @@ def assert_file_in_datadir_available(path, config_path): def standardize_path(path): + # note: os.path.realpath() is not used, as on Windows it can return non-working paths (see #8495). + # This means that we don't resolve symlinks! return os.path.normcase( - os.path.realpath( os.path.abspath( os.path.expanduser( path - )))) + ))) def get_new_wallet_name(wallet_folder: str) -> str: