Skip to content

Commit 5d2794a

Browse files
serhiy-storchakashughes-ukpbsds
authored
gh-67837, gh-112998: Fix dirs creation in concurrent extraction (GH-115082)
Avoid race conditions in the creation of directories during concurrent extraction in tarfile and zipfile. Co-authored-by: Samantha Hughes <[email protected]> Co-authored-by: Peder Bergebakken Sundt <[email protected]>
1 parent bf75f1b commit 5d2794a

File tree

4 files changed

+31
-3
lines changed

4 files changed

+31
-3
lines changed

Lib/tarfile.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2411,7 +2411,7 @@ def _extract_member(self, tarinfo, targetpath, set_attrs=True,
24112411
if upperdirs and not os.path.exists(upperdirs):
24122412
# Create directories that are not part of the archive with
24132413
# default permissions.
2414-
os.makedirs(upperdirs)
2414+
os.makedirs(upperdirs, exist_ok=True)
24152415

24162416
if tarinfo.islnk() or tarinfo.issym():
24172417
self._dbg(1, "%s -> %s" % (tarinfo.name, tarinfo.linkname))

Lib/test/archiver_tests.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import os
44
import sys
55

6+
from test.support import swap_attr
67
from test.support import os_helper
78

89
class OverwriteTests:
@@ -153,3 +154,24 @@ def test_overwrite_broken_dir_symlink_as_implicit_dir(self):
153154
self.extractall(ar)
154155
self.assertTrue(os.path.islink(target))
155156
self.assertFalse(os.path.exists(target2))
157+
158+
def test_concurrent_extract_dir(self):
159+
target = os.path.join(self.testdir, 'test')
160+
def concurrent_mkdir(*args, **kwargs):
161+
orig_mkdir(*args, **kwargs)
162+
orig_mkdir(*args, **kwargs)
163+
with swap_attr(os, 'mkdir', concurrent_mkdir) as orig_mkdir:
164+
with self.open(self.ar_with_dir) as ar:
165+
self.extractall(ar)
166+
self.assertTrue(os.path.isdir(target))
167+
168+
def test_concurrent_extract_implicit_dir(self):
169+
target = os.path.join(self.testdir, 'test')
170+
def concurrent_mkdir(*args, **kwargs):
171+
orig_mkdir(*args, **kwargs)
172+
orig_mkdir(*args, **kwargs)
173+
with swap_attr(os, 'mkdir', concurrent_mkdir) as orig_mkdir:
174+
with self.open(self.ar_with_implicit_dir) as ar:
175+
self.extractall(ar)
176+
self.assertTrue(os.path.isdir(target))
177+
self.assertTrue(os.path.isfile(os.path.join(target, 'file')))

Lib/zipfile/__init__.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1802,11 +1802,15 @@ def _extract_member(self, member, targetpath, pwd):
18021802
# Create all upper directories if necessary.
18031803
upperdirs = os.path.dirname(targetpath)
18041804
if upperdirs and not os.path.exists(upperdirs):
1805-
os.makedirs(upperdirs)
1805+
os.makedirs(upperdirs, exist_ok=True)
18061806

18071807
if member.is_dir():
18081808
if not os.path.isdir(targetpath):
1809-
os.mkdir(targetpath)
1809+
try:
1810+
os.mkdir(targetpath)
1811+
except FileExistsError:
1812+
if not os.path.isdir(targetpath):
1813+
raise
18101814
return targetpath
18111815

18121816
with self.open(member, pwd=pwd) as source, \
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Avoid race conditions in the creation of directories during concurrent
2+
extraction in :mod:`tarfile` and :mod:`zipfile`.

0 commit comments

Comments
 (0)