https://github.com/python/cpython/commit/5d2794a16bc1639e6053300c08a78d60526aadf2
commit: 5d2794a16bc1639e6053300c08a78d60526aadf2
branch: main
author: Serhiy Storchaka <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2024-02-11T12:38:07+02:00
summary:

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]>

files:
A Misc/NEWS.d/next/Library/2024-02-06-15-16-28.gh-issue-67837._JKa73.rst
M Lib/tarfile.py
M Lib/test/archiver_tests.py
M Lib/zipfile/__init__.py

diff --git a/Lib/tarfile.py b/Lib/tarfile.py
index 9775040cbe372c..f4dd0fdab4a3e4 100755
--- a/Lib/tarfile.py
+++ b/Lib/tarfile.py
@@ -2411,7 +2411,7 @@ def _extract_member(self, tarinfo, targetpath, 
set_attrs=True,
         if upperdirs and not os.path.exists(upperdirs):
             # Create directories that are not part of the archive with
             # default permissions.
-            os.makedirs(upperdirs)
+            os.makedirs(upperdirs, exist_ok=True)
 
         if tarinfo.islnk() or tarinfo.issym():
             self._dbg(1, "%s -> %s" % (tarinfo.name, tarinfo.linkname))
diff --git a/Lib/test/archiver_tests.py b/Lib/test/archiver_tests.py
index 1a4bbb9e5706c5..24745941b08923 100644
--- a/Lib/test/archiver_tests.py
+++ b/Lib/test/archiver_tests.py
@@ -3,6 +3,7 @@
 import os
 import sys
 
+from test.support import swap_attr
 from test.support import os_helper
 
 class OverwriteTests:
@@ -153,3 +154,24 @@ def 
test_overwrite_broken_dir_symlink_as_implicit_dir(self):
                 self.extractall(ar)
         self.assertTrue(os.path.islink(target))
         self.assertFalse(os.path.exists(target2))
+
+    def test_concurrent_extract_dir(self):
+        target = os.path.join(self.testdir, 'test')
+        def concurrent_mkdir(*args, **kwargs):
+            orig_mkdir(*args, **kwargs)
+            orig_mkdir(*args, **kwargs)
+        with swap_attr(os, 'mkdir', concurrent_mkdir) as orig_mkdir:
+            with self.open(self.ar_with_dir) as ar:
+                self.extractall(ar)
+        self.assertTrue(os.path.isdir(target))
+
+    def test_concurrent_extract_implicit_dir(self):
+        target = os.path.join(self.testdir, 'test')
+        def concurrent_mkdir(*args, **kwargs):
+            orig_mkdir(*args, **kwargs)
+            orig_mkdir(*args, **kwargs)
+        with swap_attr(os, 'mkdir', concurrent_mkdir) as orig_mkdir:
+            with self.open(self.ar_with_implicit_dir) as ar:
+                self.extractall(ar)
+        self.assertTrue(os.path.isdir(target))
+        self.assertTrue(os.path.isfile(os.path.join(target, 'file')))
diff --git a/Lib/zipfile/__init__.py b/Lib/zipfile/__init__.py
index 8005b4b34ccf76..cc08f602fe44e0 100644
--- a/Lib/zipfile/__init__.py
+++ b/Lib/zipfile/__init__.py
@@ -1802,11 +1802,15 @@ def _extract_member(self, member, targetpath, pwd):
         # Create all upper directories if necessary.
         upperdirs = os.path.dirname(targetpath)
         if upperdirs and not os.path.exists(upperdirs):
-            os.makedirs(upperdirs)
+            os.makedirs(upperdirs, exist_ok=True)
 
         if member.is_dir():
             if not os.path.isdir(targetpath):
-                os.mkdir(targetpath)
+                try:
+                    os.mkdir(targetpath)
+                except FileExistsError:
+                    if not os.path.isdir(targetpath):
+                        raise
             return targetpath
 
         with self.open(member, pwd=pwd) as source, \
diff --git 
a/Misc/NEWS.d/next/Library/2024-02-06-15-16-28.gh-issue-67837._JKa73.rst 
b/Misc/NEWS.d/next/Library/2024-02-06-15-16-28.gh-issue-67837._JKa73.rst
new file mode 100644
index 00000000000000..340b65f1883942
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-02-06-15-16-28.gh-issue-67837._JKa73.rst
@@ -0,0 +1,2 @@
+Avoid race conditions in the creation of directories during concurrent
+extraction in :mod:`tarfile` and :mod:`zipfile`.

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: [email protected]

Reply via email to