Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 31 additions & 3 deletions Lib/contextlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -805,10 +805,38 @@ class chdir(AbstractContextManager):
def __init__(self, path):
self.path = path
self._old_cwd = []
if self._supports_fd():
self._getcwd = self._getcwd_fd
self._close = os.close

@staticmethod
def _supports_fd():
return os.chdir in os.supports_fd

@staticmethod
def _getcwd():
return os.getcwd()

@staticmethod
def _close(dir):
pass

@staticmethod
def _getcwd_fd():
return os.open('.', os.O_RDONLY)

def __enter__(self):
self._old_cwd.append(os.getcwd())
os.chdir(self.path)
dir = self._getcwd()
try:
os.chdir(self.path)
except:
self._close(dir)
raise
self._old_cwd.append(dir)

def __exit__(self, *excinfo):
os.chdir(self._old_cwd.pop())
dir = self._old_cwd.pop()
try:
os.chdir(dir)
finally:
self._close(dir)
57 changes: 57 additions & 0 deletions Lib/test/test_contextlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -1361,6 +1361,63 @@ def test_exception(self):
self.assertEqual(str(re), "boom")
self.assertEqual(os.getcwd(), old_cwd)

def test_failed_chdir(self):
old_cwd = os.getcwd()
target = self.make_relative_path('non_existent_directory')
self.assertNotEqual(old_cwd, target)
ctx = chdir(target)
with self.assertRaises(OSError):
with ctx:
self.fail("chdir should have raised an exception")
self.assertFalse(ctx._old_cwd)
self.assertEqual(os.getcwd(), old_cwd)

@unittest.skipUnless(chdir._supports_fd(), "chdir requires fd support")
def test_chdir_to_fd(self):
old_cwd = os.getcwd()
target = self.make_relative_path('data')
fd = os.open(target, os.O_RDONLY)
try:
with chdir(fd):
self.assertEqual(os.getcwd(), target)
finally:
os.close(fd)
self.assertEqual(os.getcwd(), old_cwd)

@unittest.skipUnless(chdir._supports_fd(),
"chdir requires fd support for long path")
@unittest.skipUnless(os_helper.can_symlink(), "need symlink support")
def test_original_path_too_long(self):
with tempfile.TemporaryDirectory() as dir:
with chdir(dir):
count = 0
big_name = 'a' * os.pathconf('.', 'PC_NAME_MAX')
path_max = os.pathconf('.', 'PC_PATH_MAX')
while len(dir) < path_max + 1:
dir = os.path.join(dir, big_name)
os.mkdir(big_name)
os.symlink(big_name, 'a', target_is_directory=True)
os.chdir('a')
count += 1

self.assertTrue(count > 0)
os.mkdir('a')
with chdir('a'):
self.assertTrue(len(os.getcwd()) > path_max)

@unittest.skipUnless(chdir._supports_fd(),
"chdir requires fd support for deleted dir")
def test_original_path_deleted(self):
original = os.getcwd()
dir = tempfile.TemporaryDirectory()
try:
os.chdir(dir.name)
with chdir(original):
dir.cleanup()
finally:
os.chdir(original)
dir.cleanup()


if __name__ == "__main__":
unittest.main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Use fds in contextlib.chdir where supported. This fixes bugs if the original
working directory is greater than PATH_MAX and/or is deleted before the
context manager exits.
Loading