summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorCF Bolz-Tereick <cfbolz@gmx.de>2024-11-13 21:39:10 (GMT)
committerGitHub <noreply@github.com>2024-11-13 21:39:10 (GMT)
commitc695e37a3f95c225ee08d1e882d23fa200b5ec34 (patch)
treee6fa1a1d16ddbf14862c5eaf3009fdba25a2b1d8
parentf6b0361c17552197f44be16435e4a5cb4b1d60ca (diff)
downloadcpython-c695e37a3f95c225ee08d1e882d23fa200b5ec34.zip
cpython-c695e37a3f95c225ee08d1e882d23fa200b5ec34.tar.gz
cpython-c695e37a3f95c225ee08d1e882d23fa200b5ec34.tar.bz2
GH-126606: don't write incomplete pyc files (GH-126627)
Co-authored-by: Kirill Podoprigora <kirill.bast9@mail.ru> Co-authored-by: Brett Cannon <brett@python.org>
-rw-r--r--Lib/importlib/_bootstrap_external.py6
-rw-r--r--Lib/test/test_importlib/test_util.py32
-rw-r--r--Misc/NEWS.d/next/Core_and_Builtins/2024-11-09-16-10-22.gh-issue-126066.9zs4m4.rst3
3 files changed, 40 insertions, 1 deletions
diff --git a/Lib/importlib/_bootstrap_external.py b/Lib/importlib/_bootstrap_external.py
index 1b76328..fa36159 100644
--- a/Lib/importlib/_bootstrap_external.py
+++ b/Lib/importlib/_bootstrap_external.py
@@ -209,7 +209,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:
diff --git a/Lib/test/test_importlib/test_util.py b/Lib/test/test_importlib/test_util.py
index 6680427..0bdd1b4 100644
--- a/Lib/test/test_importlib/test_util.py
+++ b/Lib/test/test_importlib/test_util.py
@@ -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
@@ -775,5 +777,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
index 0000000..9c00723
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2024-11-09-16-10-22.gh-issue-126066.9zs4m4.rst
@@ -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.