diff --git a/rnfs/fsutil.py b/rnfs/fsutil.py index d28ad76..ffd4fe4 100644 --- a/rnfs/fsutil.py +++ b/rnfs/fsutil.py @@ -132,9 +132,10 @@ def _temp_path(directory: str) -> str: def write_atomic(path: str, data: bytes, *, mode: int = 0o644, durable: bool = True, exclusive: bool = False): """Replace path with data, so a crash or power loss leaves the old or the new file whole. Raises OSError. - data goes to a new temporary file in path's directory (O_EXCL and O_NOFOLLOW: never an existing file or - symlink, created with mode, which the umask can only narrow), synced to disk and renamed over path (retried - on Windows while path is in use); a symlink at path is replaced, not written through. If durable, the + data goes to a new temporary file in path's directory (O_EXCL, plus O_NOFOLLOW where it exists, i.e. not on + Windows: never an existing file or symlink; created with mode, which the umask can only narrow), synced to + disk and renamed over path (retried on Windows while path is in use); a symlink at path is replaced, not + written through. If durable, the directory is then synced so the rename survives a power loss too. With exclusive, an existing path is kept and FileExistsError raised instead. On any failure before the rename the temporary file is removed and path is left as it was. diff --git a/tests/test_fsutil.py b/tests/test_fsutil.py index 4857e93..e1a2fc8 100644 --- a/tests/test_fsutil.py +++ b/tests/test_fsutil.py @@ -206,7 +206,8 @@ def test_symlink_at_path_is_replaced_not_followed(self): self.assertFalse(os.path.islink(self.path)) self.assert_only_file(b"new") - @unittest.skipUnless(CAN_SYMLINK, "symlinks") + # Windows has no O_NOFOLLOW, and O_EXCL there creates a dangling symlink's target (as mkstemp would) + @unittest.skipUnless(CAN_SYMLINK and fsutil.O_NOFOLLOW, "symlinks and O_NOFOLLOW") def test_refuses_a_symlink_at_the_temporary_name(self): # Someone able to guess the name plants a symlink there: it is neither followed nor removed temp = os.path.join(self.tmp.name, fsutil.PARTIAL_PREFIX + "planted") diff --git a/tests/test_node.py b/tests/test_node.py index 4a85129..0a69e29 100644 --- a/tests/test_node.py +++ b/tests/test_node.py @@ -353,7 +353,8 @@ def test_remote_pin_within_quota(self): self.assertTrue(self.a.backend.remote_pin(IDENTITY, Cid.parse(cid), True)) source = remote_source(IDENTITY) self.assertTrue(self.wait_for(lambda: source in self.a.storage.pins().get(Cid.parse(cid), set()))) - self.assertIn(Cid.parse(cid), self.a.publisher.provided()) + # The worker refreshes the publisher just after pinning + self.assertTrue(self.wait_for(lambda: Cid.parse(cid) in self.a.publisher.provided())) self.assertTrue(self.a.backend.remote_pin(IDENTITY, Cid.parse(cid), False)) self.assertEqual(self.a.storage.pins(), {})