diff options
author | mrbean-bremen <hansemrbean@googlemail.com> | 2021-10-29 21:03:46 +0200 |
---|---|---|
committer | mrbean-bremen <mrbean-bremen@users.noreply.github.com> | 2021-10-30 12:39:27 +0200 |
commit | 5f8f0e85c0e873dfa91088c953e68f9e26808bfa (patch) | |
tree | 087d504e2fc5dad5322d81bc52f4ea107baf811e | |
parent | c81445c78c830c9bbb0f3297788de58dc8b97255 (diff) | |
download | pyfakefs-5f8f0e85c0e873dfa91088c953e68f9e26808bfa.tar.gz |
Make sure nothing is changed if a rename failed
- rename is implemented via remove/add, a failed add shall revert the remove
- see #643
-rw-r--r-- | CHANGES.md | 3 | ||||
-rw-r--r-- | pyfakefs/fake_filesystem.py | 21 | ||||
-rw-r--r-- | pyfakefs/tests/fake_filesystem_shutil_test.py | 21 |
3 files changed, 40 insertions, 5 deletions
@@ -11,6 +11,9 @@ The released versions correspond to PyPi releases. * fixed handling of alternative path separator in `os.path.split`, `os.path.splitdrive` and `glob.glob` (see [#632](../../issues/632)) +* fixed handling of failed rename due to permission error + (see [#643](../../issues/643)) + ## [Version 4.5.1](https://pypi.python.org/pypi/pyfakefs/4.5.1) (2021-08-29) This is a bugfix release. diff --git a/pyfakefs/fake_filesystem.py b/pyfakefs/fake_filesystem.py index 4c07280..98086e3 100644 --- a/pyfakefs/fake_filesystem.py +++ b/pyfakefs/fake_filesystem.py @@ -2257,14 +2257,27 @@ class FakeFilesystem: if new_dir_object.has_parent_object(old_object): self.raise_os_error(errno.EINVAL, new_path) + self._do_rename(old_dir_object, old_name, new_dir_object, new_name) + + def _do_rename(self, old_dir_object, old_name, new_dir_object, new_name): object_to_rename = old_dir_object.get_entry(old_name) old_dir_object.remove_entry(old_name, recursive=False) object_to_rename.name = new_name new_name = new_dir_object._normalized_entryname(new_name) - if new_name in new_dir_object.entries: - # in case of overwriting remove the old entry first - new_dir_object.remove_entry(new_name) - new_dir_object.add_entry(object_to_rename) + old_entry = (new_dir_object.get_entry(new_name) + if new_name in new_dir_object.entries else None) + try: + if old_entry: + # in case of overwriting remove the old entry first + new_dir_object.remove_entry(new_name) + new_dir_object.add_entry(object_to_rename) + except OSError: + # adding failed, roll back the changes before re-raising + if old_entry and new_name not in new_dir_object.entries: + new_dir_object.add_entry(old_entry) + object_to_rename.name = old_name + old_dir_object.add_entry(object_to_rename) + raise def _handle_broken_link_with_trailing_sep(self, path: AnyStr) -> None: # note that the check for trailing sep has to be done earlier diff --git a/pyfakefs/tests/fake_filesystem_shutil_test.py b/pyfakefs/tests/fake_filesystem_shutil_test.py index 12b308f..5861e63 100644 --- a/pyfakefs/tests/fake_filesystem_shutil_test.py +++ b/pyfakefs/tests/fake_filesystem_shutil_test.py @@ -24,7 +24,7 @@ import tempfile import unittest from pyfakefs import fake_filesystem_unittest -from pyfakefs.fake_filesystem import is_root +from pyfakefs.fake_filesystem import is_root, set_uid, USER_ID from pyfakefs.tests.test_utils import RealFsTestMixin is_windows = sys.platform == 'win32' @@ -38,6 +38,8 @@ class RealFsTestCase(fake_filesystem_unittest.TestCase, RealFsTestMixin): def setUp(self): RealFsTestMixin.setUp(self) self.cwd = os.getcwd() + self.uid = USER_ID + set_uid(1000) if not self.use_real_fs(): self.setUpPyfakefs() self.filesystem = self.fs @@ -47,6 +49,7 @@ class RealFsTestCase(fake_filesystem_unittest.TestCase, RealFsTestMixin): self.fs.set_disk_usage(1000, self.base_path) def tearDown(self): + set_uid(self.uid) RealFsTestMixin.tearDown(self) @property @@ -57,6 +60,22 @@ class RealFsTestCase(fake_filesystem_unittest.TestCase, RealFsTestMixin): class FakeShutilModuleTest(RealFsTestCase): + @unittest.skipIf(is_windows, 'Posix specific behavior') + def test_catch_permission_error(self): + root_path = self.make_path('rootpath') + self.create_dir(root_path) + dir1_path = self.os.path.join(root_path, 'dir1') + dir2_path = self.os.path.join(root_path, 'dir2') + self.create_dir(dir1_path) + self.os.chmod(dir1_path, 0o555) # remove write permissions + self.create_dir(dir2_path) + old_file_path = self.os.path.join(dir2_path, 'f1.txt') + new_file_path = self.os.path.join(dir1_path, 'f1.txt') + self.create_file(old_file_path) + + with self.assertRaises(PermissionError): + shutil.move(old_file_path, new_file_path) + def test_rmtree(self): directory = self.make_path('xyzzy') dir_path = os.path.join(directory, 'subdir') |