0
0
mirror of https://github.com/python/cpython.git synced 2024-11-24 00:38:00 +01:00

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>
This commit is contained in:
CF Bolz-Tereick 2024-11-13 22:39:10 +01:00 committed by GitHub
parent f6b0361c17
commit c695e37a3f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 40 additions and 1 deletions

View File

@ -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 # We first write data to a temporary file, and then use os.replace() to
# perform an atomic rename. # perform an atomic rename.
with _io.FileIO(fd, 'wb') as file: 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) _os.replace(path_tmp, path)
except OSError: except OSError:
try: try:

View File

@ -6,12 +6,14 @@ machinery = util.import_importlib('importlib.machinery')
importlib_util = util.import_importlib('importlib.util') importlib_util = util.import_importlib('importlib.util')
import importlib.util import importlib.util
from importlib import _bootstrap_external
import os import os
import pathlib import pathlib
import re import re
import string import string
import sys import sys
from test import support from test import support
from test.support import os_helper
import textwrap import textwrap
import types import types
import unittest import unittest
@ -775,5 +777,35 @@ class IncompatibleExtensionModuleRestrictionsTests(unittest.TestCase):
self.run_with_own_gil(script) 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__': if __name__ == '__main__':
unittest.main() unittest.main()

View File

@ -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.