| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent b0ae041 commit f272dd3
2 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1166,20 +1166,34 @@ def _obtain_lock_or_raise(self) -> None: | |||
| 1166 | 1166 | if self._has_lock(): | |
| 1167 | 1167 | return | |
| 1168 | 1168 | lock_file = self._lock_file_path() | |
| 1169 | - # Create the lock in one step, the way Git and gitdb's LockedFD do. Testing | ||
| 1170 | - # for the file first leaves a window in which another holder creates it and | ||
| 1171 | - # both proceed, and O_CREAT|O_EXCL additionally refuses to follow a symbolic | ||
| 1172 | - # link planted at the lock path instead of writing through it. | ||
| 1169 | + # Create the lock in one step. Checking for it first would allow another | ||
| 1170 | + # holder to create it between the check and the open. | ||
| 1173 | 1171 | try: | |
| 1174 | - fd = os.open(lock_file, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600) | ||
| 1172 | + if sys.platform == "win32": | ||
| 1173 | + import _winapi | ||
| 1174 | + | ||
| 1175 | + # Unlike POSIX, Windows follows dangling symlinks even with O_EXCL. | ||
| 1176 | + # Open the reparse point itself so an existing link is rejected. | ||
| 1177 | + handle = _winapi.CreateFile( | ||
| 1178 | + lock_file, | ||
| 1179 | + _winapi.GENERIC_WRITE, | ||
| 1180 | + 0, | ||
| 1181 | + 0, | ||
| 1182 | + 1, # CREATE_NEW | ||
| 1183 | + 0x00200000, # FILE_FLAG_OPEN_REPARSE_POINT | ||
| 1184 | + 0, | ||
| 1185 | + ) | ||
| 1186 | + _winapi.CloseHandle(handle) | ||
| 1187 | + else: | ||
| 1188 | + fd = os.open(lock_file, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600) | ||
| 1189 | + os.close(fd) | ||
| 1175 | 1190 | except FileExistsError as e: | |
| 1176 | 1191 | raise OSError( | |
| 1177 | 1192 | "Lock for file %r did already exist, delete %r in case the lock is illegal" | |
| 1178 | 1193 | % (self._file_path, lock_file) | |
| 1179 | 1194 | ) from e | |
| 1180 | 1195 | except OSError as e: | |
| 1181 | 1196 | raise OSError(str(e)) from e | |
| 1182 | - os.close(fd) | ||
| 1183 | 1197 | ||
| 1184 | 1198 | self._owns_lock = True | |
| 1185 | 1199 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -447,17 +447,28 @@ def test_lock_file(self): | |||
| 447 | 447 | lock_file._obtain_lock_or_raise() | |
| 448 | 448 | lock_file._release_lock() | |
| 449 | 449 | ||
| 450 | + @ddt.data(False, True) | ||
| 450 | 451 | @requires_symlinks | |
| 451 | - def test_lock_file_does_not_follow_a_symlink(self): | ||
| 452 | + def test_lock_file_does_not_follow_a_symlink(self, target_exists): | ||
| 452 | 453 | with tempfile.TemporaryDirectory() as tdir: | |
| 453 | 454 | my_file = os.path.join(tdir, "my-lock-file") | |
| 454 | 455 | outside = os.path.join(tdir, "outside-the-lock") | |
| 456 | + content = b"Do not modify the symlink target." | ||
| 457 | + if target_exists: | ||
| 458 | + with open(outside, "wb") as stream: | ||
| 459 | + stream.write(content) | ||
| 455 | 460 | os.symlink(outside, my_file + ".lock") | |
| 456 | 461 | ||
| 457 | 462 | lock_file = LockFile(my_file) | |
| 458 | 463 | self.assertRaises(IOError, lock_file._obtain_lock_or_raise) | |
| 459 | 464 | assert not lock_file._has_lock() | |
| 460 | - assert not os.path.exists(outside) | ||
| 465 | + lock_file._release_lock() | ||
| 466 | + assert os.path.islink(my_file + ".lock") | ||
| 467 | + if target_exists: | ||
| 468 | + with open(outside, "rb") as stream: | ||
| 469 | + self.assertEqual(stream.read(), content) | ||
| 470 | + else: | ||
| 471 | + assert not os.path.exists(outside) | ||
| 461 | 472 | ||
| 462 | 473 | def test_lock_file_is_obtained_by_a_single_holder(self): | |
| 463 | 474 | with tempfile.TemporaryDirectory() as tdir: | |
@@ -483,7 +494,11 @@ def obtain(): | |||
| 483 | 494 | for thread in threads: | |
| 484 | 495 | thread.join() | |
| 485 | 496 | ||
| 486 | - self.assertEqual(1, len(holders)) | ||
| 497 | + try: | ||
| 498 | + self.assertEqual(1, len(holders)) | ||
| 499 | + finally: | ||
| 500 | + for lock_file in holders: | ||
| 501 | + lock_file._release_lock() | ||
| 487 | 502 | ||
| 488 | 503 | def test_blocking_lock_file(self): | |
| 489 | 504 | with tempfile.TemporaryDirectory() as tdir: | |
| Back | FazBrowse Home | New Git URL |
0 commit comments