Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit a508770

Browse files
committed
Close #2501: Permission bits are once again correctly copied from the source file to the cached bytecode file. Test by Eric Snow.
1 parent 36d188c commit a508770

4 files changed

Lines changed: 4299 additions & 4187 deletions

File tree

Lib/importlib/_bootstrap.py

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -118,13 +118,14 @@ def _path_isdir(path):
118118
return _path_is_mode_type(path, 0o040000)
119119

120120

121-
def _write_atomic(path, data):
121+
def _write_atomic(path, data, mode=0o666):
122122
"""Best-effort function to write data to a path atomically.
123123
Be prepared to handle a FileExistsError if concurrent writing of the
124124
temporary file is attempted."""
125125
# id() is used to generate a pseudo-random filename.
126126
path_tmp = '{}.{}'.format(path, id(path))
127-
fd = _os.open(path_tmp, _os.O_EXCL | _os.O_CREAT | _os.O_WRONLY, 0o666)
127+
fd = _os.open(path_tmp,
128+
_os.O_EXCL | _os.O_CREAT | _os.O_WRONLY, mode & 0o666)
128129
try:
129130
# We first write data to a temporary file, and then use os.replace() to
130131
# perform an atomic rename.
@@ -887,6 +888,16 @@ def path_stats(self, path):
887888
"""
888889
return {'mtime': self.path_mtime(path)}
889890

891+
def _cache_bytecode(self, source_path, cache_path, data):
892+
"""Optional method which writes data (bytes) to a file path (a str).
893+
894+
Implementing this method allows for the writing of bytecode files.
895+
896+
The source path is needed in order to correctly transfer permissions
897+
"""
898+
# For backwards compatibility, we delegate to set_data()
899+
return self.set_data(cache_path, data)
900+
890901
def set_data(self, path, data):
891902
"""Optional method which writes data (bytes) to a file path (a str).
892903
@@ -974,7 +985,7 @@ def get_code(self, fullname):
974985
data.extend(_w_long(len(source_bytes)))
975986
data.extend(marshal.dumps(code_object))
976987
try:
977-
self.set_data(bytecode_path, data)
988+
self._cache_bytecode(source_path, bytecode_path, data)
978989
_verbose_message('wrote {!r}', bytecode_path)
979990
except NotImplementedError:
980991
pass
@@ -1029,7 +1040,11 @@ def path_stats(self, path):
10291040
st = _os.stat(path)
10301041
return {'mtime': st.st_mtime, 'size': st.st_size}
10311042

1032-
def set_data(self, path, data):
1043+
def _cache_bytecode(self, source_path, bytecode_path, data):
1044+
# Adapt between the two APIs
1045+
return self.set_data(bytecode_path, data, source_path=source_path)
1046+
1047+
def set_data(self, path, data, *, source_path=None):
10331048
"""Write bytes data to a file."""
10341049
parent, filename = _path_split(path)
10351050
path_parts = []
@@ -1049,8 +1064,14 @@ def set_data(self, path, data):
10491064
# If can't get proper access, then just forget about writing
10501065
# the data.
10511066
return
1067+
mode = 0o666
1068+
if source_path is not None:
1069+
try:
1070+
mode = _os.stat(source_path).st_mode
1071+
except OSError:
1072+
pass
10521073
try:
1053-
_write_atomic(path, data)
1074+
_write_atomic(path, data, mode)
10541075
_verbose_message('created {!r}', path)
10551076
except (PermissionError, FileExistsError):
10561077
# Don't worry if you can't write bytecode or someone is writing

Lib/test/test_import.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,12 +120,35 @@ def test_creation_mode(self):
120120
s = os.stat(fn)
121121
# Check that the umask is respected, and the executable bits
122122
# aren't set.
123-
self.assertEqual(stat.S_IMODE(s.st_mode), 0o666 & ~mask)
123+
self.assertEqual(oct(stat.S_IMODE(s.st_mode)), oct(0o666 & ~mask))
124124
finally:
125125
del sys.path[0]
126126
remove_files(TESTFN)
127127
unload(TESTFN)
128128

129+
@unittest.skipUnless(os.name == 'posix',
130+
"test meaningful only on posix systems")
131+
def test_cached_mode_issue_2051(self):
132+
mode = 0o600
133+
source = TESTFN + ".py"
134+
with script_helper.temp_dir() as tempdir:
135+
path = script_helper.make_script(tempdir, TESTFN,
136+
"key='top secret'")
137+
os.chmod(path, mode)
138+
compiled = imp.cache_from_source(path)
139+
sys.path.insert(0, tempdir)
140+
try:
141+
__import__(TESTFN)
142+
finally:
143+
sys.path.remove(tempdir)
144+
145+
if not os.path.exists(compiled):
146+
self.fail("__import__ did not result in creation of "
147+
"either a .pyc or .pyo file")
148+
stat_info = os.stat(compiled)
149+
150+
self.assertEqual(oct(stat.S_IMODE(stat_info.st_mode)), oct(mode))
151+
129152
def test_imp_module(self):
130153
# Verify that the imp module can correctly load and find .py files
131154
# XXX (ncoghlan): It would be nice to use support.CleanImport

Misc/NEWS

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ What's New in Python 3.3.0 Release Candidate 1?
1010
Core and Builtins
1111
-----------------
1212

13+
- Issue #2501: Source file permission bits are once again correctly
14+
copied to the cached bytecode file. (The migration to importlib
15+
reintroduced this problem because these was no regression test. A test
16+
has been added as part of this patch)
17+
1318
- Issue #15761: Fix crash when PYTHONEXECUTABLE is set on Mac OS X.
1419

1520
- Issue #15726: Fix incorrect bounds checking in PyState_FindModule.

0 commit comments

Comments
 (0)