Issue #17222: Raise FileExistsError when py_compile.compile would
overwrite a symlink or non-regular file with a regular file.
This commit is contained in:
parent
3fe35e6503
commit
33915eba7c
|
@ -41,6 +41,13 @@ byte-code cache files in the directory containing the source code.
|
||||||
is raised. This function returns the path to byte-compiled file, i.e.
|
is raised. This function returns the path to byte-compiled file, i.e.
|
||||||
whatever *cfile* value was used.
|
whatever *cfile* value was used.
|
||||||
|
|
||||||
|
If the path that *cfile* becomes (either explicitly specified or computed)
|
||||||
|
is a symlink or non-regular file, :exc:`FileExistsError` will be raised.
|
||||||
|
This is to act as a warning that import will turn those paths into regular
|
||||||
|
files if it is allowed to write byte-compiled files to those paths. This is
|
||||||
|
a side-effect of import using file renaming to place the final byte-compiled
|
||||||
|
file into place to prevent concurrent file writing issues.
|
||||||
|
|
||||||
*optimize* controls the optimization level and is passed to the built-in
|
*optimize* controls the optimization level and is passed to the built-in
|
||||||
:func:`compile` function. The default of ``-1`` selects the optimization
|
:func:`compile` function. The default of ``-1`` selects the optimization
|
||||||
level of the current interpreter.
|
level of the current interpreter.
|
||||||
|
@ -53,7 +60,9 @@ byte-code cache files in the directory containing the source code.
|
||||||
.. versionchanged:: 3.4
|
.. versionchanged:: 3.4
|
||||||
Changed code to use :mod:`importlib` for the byte-code cache file writing.
|
Changed code to use :mod:`importlib` for the byte-code cache file writing.
|
||||||
This means file creation/writing semantics now match what :mod:`importlib`
|
This means file creation/writing semantics now match what :mod:`importlib`
|
||||||
does, e.g. permissions, write-and-move semantics, etc.
|
does, e.g. permissions, write-and-move semantics, etc. Also added the
|
||||||
|
caveat that :exc:`FileExistsError` is raised if *cfile* is a symlink or
|
||||||
|
non-regular file.
|
||||||
|
|
||||||
|
|
||||||
.. function:: main(args=None)
|
.. function:: main(args=None)
|
||||||
|
|
|
@ -272,3 +272,8 @@ that may require changes to your code.
|
||||||
|
|
||||||
* :c:func:`PyErr_SetImportError` now sets :exc:`TypeError` when its **msg**
|
* :c:func:`PyErr_SetImportError` now sets :exc:`TypeError` when its **msg**
|
||||||
argument is not set. Previously only ``NULL`` was returned.
|
argument is not set. Previously only ``NULL`` was returned.
|
||||||
|
|
||||||
|
* :func:`py_compile.compile` now raises :exc:`FileExistsError` if the file path
|
||||||
|
it would write to is a symlink or a non-regular file. This is to act as a
|
||||||
|
warning that import will overwrite those files with a regular file regardless
|
||||||
|
of what type of file path they were originally.
|
||||||
|
|
|
@ -7,6 +7,7 @@ import imp
|
||||||
import importlib._bootstrap
|
import importlib._bootstrap
|
||||||
import importlib.machinery
|
import importlib.machinery
|
||||||
import os
|
import os
|
||||||
|
import os.path
|
||||||
import sys
|
import sys
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
|
@ -96,12 +97,25 @@ def compile(file, cfile=None, dfile=None, doraise=False, optimize=-1):
|
||||||
See compileall.py for a script/module that uses this module to
|
See compileall.py for a script/module that uses this module to
|
||||||
byte-compile all installed files (or all files in selected
|
byte-compile all installed files (or all files in selected
|
||||||
directories).
|
directories).
|
||||||
|
|
||||||
|
Do note that FileExistsError is raised if cfile ends up pointing at a
|
||||||
|
non-regular file or symlink. Because the compilation uses a file renaming,
|
||||||
|
the resulting file would be regular and thus not the same type of file as
|
||||||
|
it was previously.
|
||||||
"""
|
"""
|
||||||
if cfile is None:
|
if cfile is None:
|
||||||
if optimize >= 0:
|
if optimize >= 0:
|
||||||
cfile = imp.cache_from_source(file, debug_override=not optimize)
|
cfile = imp.cache_from_source(file, debug_override=not optimize)
|
||||||
else:
|
else:
|
||||||
cfile = imp.cache_from_source(file)
|
cfile = imp.cache_from_source(file)
|
||||||
|
if os.path.islink(cfile):
|
||||||
|
msg = ('{} is a symlink and will be changed into a regular file if '
|
||||||
|
'import writes a byte-compiled file to it')
|
||||||
|
raise FileExistsError(msg.format(file, cfile))
|
||||||
|
elif os.path.exists(cfile) and not os.path.isfile(cfile):
|
||||||
|
msg = ('{} is a non-regular file and will be changed into a regular '
|
||||||
|
'one if import writes a byte-compiled file to it')
|
||||||
|
raise FileExistsError(msg.format(file, cfile))
|
||||||
loader = importlib.machinery.SourceFileLoader('<py_compile>', file)
|
loader = importlib.machinery.SourceFileLoader('<py_compile>', file)
|
||||||
source_bytes = loader.get_data(file)
|
source_bytes = loader.get_data(file)
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -36,6 +36,26 @@ class PyCompileTests(unittest.TestCase):
|
||||||
self.assertTrue(os.path.exists(self.pyc_path))
|
self.assertTrue(os.path.exists(self.pyc_path))
|
||||||
self.assertFalse(os.path.exists(self.cache_path))
|
self.assertFalse(os.path.exists(self.cache_path))
|
||||||
|
|
||||||
|
def test_do_not_overwrite_symlinks(self):
|
||||||
|
# In the face of a cfile argument being a symlink, bail out.
|
||||||
|
# Issue #17222
|
||||||
|
try:
|
||||||
|
os.symlink(self.pyc_path + '.actual', self.pyc_path)
|
||||||
|
except OSError:
|
||||||
|
self.skipTest('need to be able to create a symlink for a file')
|
||||||
|
else:
|
||||||
|
assert os.path.islink(self.pyc_path)
|
||||||
|
with self.assertRaises(FileExistsError):
|
||||||
|
py_compile.compile(self.source_path, self.pyc_path)
|
||||||
|
|
||||||
|
@unittest.skipIf(not os.path.exists(os.devnull) or os.path.isfile(os.devnull),
|
||||||
|
'requires os.devnull and for it to be a non-regular file')
|
||||||
|
def test_do_not_overwrite_nonregular_files(self):
|
||||||
|
# In the face of a cfile argument being a non-regular file, bail out.
|
||||||
|
# Issue #17222
|
||||||
|
with self.assertRaises(FileExistsError):
|
||||||
|
py_compile.compile(self.source_path, os.devnull)
|
||||||
|
|
||||||
def test_cache_path(self):
|
def test_cache_path(self):
|
||||||
py_compile.compile(self.source_path)
|
py_compile.compile(self.source_path)
|
||||||
self.assertTrue(os.path.exists(self.cache_path))
|
self.assertTrue(os.path.exists(self.cache_path))
|
||||||
|
|
|
@ -1005,6 +1005,9 @@ Library
|
||||||
Python file. Patch by Ben Morgan.
|
Python file. Patch by Ben Morgan.
|
||||||
|
|
||||||
- Have py_compile use importlib as much as possible to avoid code duplication.
|
- Have py_compile use importlib as much as possible to avoid code duplication.
|
||||||
|
Code now raises FileExistsError if the file path to be used for the
|
||||||
|
byte-compiled file is a symlink or non-regular file as a warning that import
|
||||||
|
will not keep the file path type if it writes to that path.
|
||||||
|
|
||||||
- Issue #180022: Have site.addpackage() consider already known paths even when
|
- Issue #180022: Have site.addpackage() consider already known paths even when
|
||||||
none are explicitly passed in. Bug report and fix by Kirill.
|
none are explicitly passed in. Bug report and fix by Kirill.
|
||||||
|
|
Loading…
Reference in New Issue