]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.12] GH-126606: don't write incomplete pyc files (GH-126627) (GH-126810)
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Wed, 13 Nov 2024 22:49:47 +0000 (23:49 +0100)
committerGitHub <noreply@github.com>
Wed, 13 Nov 2024 22:49:47 +0000 (14:49 -0800)
GH-126606: don't write incomplete pyc files (GH-126627)
(cherry picked from commit c695e37a3f95c225ee08d1e882d23fa200b5ec34)

Co-authored-by: CF Bolz-Tereick <cfbolz@gmx.de>
Co-authored-by: Kirill Podoprigora <kirill.bast9@mail.ru>
Co-authored-by: Brett Cannon <brett@python.org>
Lib/importlib/_bootstrap_external.py
Lib/test/test_importlib/test_util.py
Misc/NEWS.d/next/Core_and_Builtins/2024-11-09-16-10-22.gh-issue-126066.9zs4m4.rst [new file with mode: 0644]

index 61dafc0f4cb953396958d92791440006771ea02c..9b8a8dfc5aae28cc5b0133dd1aca778b93971b0c 100644 (file)
@@ -204,7 +204,11 @@ def _write_atomic(path, data, mode=0o666):
         # We first write data to a temporary file, and then use os.replace() to
         # perform an atomic rename.
         with _io.FileIO(fd, 'wb') as file:
-            file.write(data)
+            bytes_written = file.write(data)
+        if bytes_written != len(data):
+            # Raise an OSError so the 'except' below cleans up the partially
+            # written file.
+            raise OSError("os.write() didn't write the full pyc file")
         _os.replace(path_tmp, path)
     except OSError:
         try:
index e018af2e16beab400e4a48b85517759ab53d111f..553e20874218352fd51ae3a51e9ef4b5a9e83dda 100644 (file)
@@ -6,12 +6,14 @@ machinery = util.import_importlib('importlib.machinery')
 importlib_util = util.import_importlib('importlib.util')
 
 import importlib.util
+from importlib import _bootstrap_external
 import os
 import pathlib
 import re
 import string
 import sys
 from test import support
+from test.support import os_helper
 import textwrap
 import types
 import unittest
@@ -758,5 +760,35 @@ class IncompatibleExtensionModuleRestrictionsTests(unittest.TestCase):
             self.run_with_own_gil(script)
 
 
+class MiscTests(unittest.TestCase):
+    def test_atomic_write_should_notice_incomplete_writes(self):
+        import _pyio
+
+        oldwrite = os.write
+        seen_write = False
+
+        truncate_at_length = 100
+
+        # Emulate an os.write that only writes partial data.
+        def write(fd, data):
+            nonlocal seen_write
+            seen_write = True
+            return oldwrite(fd, data[:truncate_at_length])
+
+        # Need to patch _io to be _pyio, so that io.FileIO is affected by the
+        # os.write patch.
+        with (support.swap_attr(_bootstrap_external, '_io', _pyio),
+              support.swap_attr(os, 'write', write)):
+            with self.assertRaises(OSError):
+                # Make sure we write something longer than the point where we
+                # truncate.
+                content = b'x' * (truncate_at_length * 2)
+                _bootstrap_external._write_atomic(os_helper.TESTFN, content)
+        assert seen_write
+
+        with self.assertRaises(OSError):
+            os.stat(support.os_helper.TESTFN) # Check that the file did not get written.
+
+
 if __name__ == '__main__':
     unittest.main()
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2024-11-09-16-10-22.gh-issue-126066.9zs4m4.rst b/Misc/NEWS.d/next/Core_and_Builtins/2024-11-09-16-10-22.gh-issue-126066.9zs4m4.rst
new file mode 100644 (file)
index 0000000..9c00723
--- /dev/null
@@ -0,0 +1,3 @@
+Fix :mod:`importlib` to not write an incomplete .pyc files when a ulimit or some
+other operating system mechanism is preventing the write to go through
+fully.