Skip to content

Commit 789f207

Browse files
committed
Make sure the temp directory can be created during fs reset
We cannot use the `tempdir` module to get the temp directory at that point, if the root dir is not writable by all. This is a consequence of the fixes fo file permission handling.
1 parent 2551f7b commit 789f207

5 files changed

+71
-7
lines changed

CHANGES.md

+2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ The released versions correspond to PyPI releases.
1818
(see [#959](../../issues/959))
1919
* fixed handling of directory enumeration and search permissions under Posix systems
2020
(see [#960](../../issues/960))
21+
* fixed creation of the temp directory in the fake file system after a filesystem reset
22+
(see [#965](../../issues/965))
2123

2224
## [Version 5.3.5](https://pypi.python.org/pypi/pyfakefs/5.3.5) (2024-01-30)
2325
Fixes a regression.

docs/troubleshooting.rst

+6-5
Original file line numberDiff line numberDiff line change
@@ -179,8 +179,7 @@ As ``pyfakefs`` does not fake the ``tempfile`` module (as described above),
179179
a temporary directory is required to ensure that ``tempfile`` works correctly,
180180
e.g., that ``tempfile.gettempdir()`` will return a valid value. This
181181
means that any newly created fake file system will always have either a
182-
directory named ``/tmp`` when running on Linux or Unix systems,
183-
``/var/folders/<hash>/T`` when running on macOS, or
182+
directory named ``/tmp`` when running on POSIX systems, or
184183
``C:\Users\<user>\AppData\Local\Temp`` on Windows:
185184

186185
.. code:: python
@@ -192,11 +191,13 @@ directory named ``/tmp`` when running on Linux or Unix systems,
192191
# the temp directory is always present at test start
193192
assert len(os.listdir("/")) == 1
194193
195-
Under macOS and linux, if the actual temp path is not `/tmp` (which is always the case
196-
under macOS), a symlink to the actual temp directory is additionally created as `/tmp`
197-
in the fake filesystem. Note that the file size of this link is ignored while
194+
Under macOS and linux, if the actual temp path is not `/tmp` (which will be the case if an environment variable
195+
`TEMPDIR`, `TEMP` or `TMP` points to another path), a symlink to the actual temp directory is additionally created
196+
as `/tmp` in the fake filesystem. Note that the file size of this link is ignored while
198197
calculating the fake filesystem size, so that the used size with an otherwise empty
199198
fake filesystem can always be assumed to be 0.
199+
Note also that the temp directory may not be what you expect, if you emulate another file system. For example,
200+
if you emulate Windows under Linux, the default temp directory will be at `C:\\tmp`.
200201

201202

202203
User rights

pyfakefs/fake_file.py

+19-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import io
1919
import os
2020
import sys
21+
import traceback
2122
from stat import (
2223
S_IFREG,
2324
S_IFDIR,
@@ -585,7 +586,24 @@ def remove_entry(self, pathname_name: str, recursive: bool = True) -> None:
585586
if entry.st_mode & helpers.PERM_WRITE == 0:
586587
self.filesystem.raise_os_error(errno.EACCES, pathname_name)
587588
if self.filesystem.has_open_file(entry):
588-
self.filesystem.raise_os_error(errno.EACCES, pathname_name)
589+
raise_error = True
590+
if os.name == "posix" and not hasattr(os, "O_TMPFILE"):
591+
# special handling for emulating Windows under macOS and PyPi
592+
# tempfile uses unlink based on the real OS while deleting
593+
# a temporary file, so we ignore that error in this specific case
594+
st = traceback.extract_stack(limit=6)
595+
if sys.version_info < (3, 10):
596+
if (
597+
st[1].name == "TemporaryFile"
598+
and st[1].line == "_os.unlink(name)"
599+
):
600+
raise_error = False
601+
else:
602+
# TemporaryFile implementation has changed in Python 3.10
603+
if st[0].name == "opener" and st[0].line == "_os.unlink(name)":
604+
raise_error = False
605+
if raise_error:
606+
self.filesystem.raise_os_error(errno.EACCES, pathname_name)
589607
else:
590608
if not helpers.is_root() and not self.has_permission(
591609
helpers.PERM_WRITE | helpers.PERM_EXE

pyfakefs/fake_filesystem.py

+18-1
Original file line numberDiff line numberDiff line change
@@ -3013,10 +3013,27 @@ def _add_standard_streams(self) -> None:
30133013
self._add_open_file(StandardStreamWrapper(sys.stdout))
30143014
self._add_open_file(StandardStreamWrapper(sys.stderr))
30153015

3016+
def _tempdir_name(self):
3017+
"""This logic is extracted from tempdir._candidate_tempdir_list.
3018+
We cannot rely on tempdir.gettempdir() in an empty filesystem, as it tries
3019+
to write to the filesystem to ensure that the tempdir is valid.
3020+
"""
3021+
# reset the cached tempdir in tempfile
3022+
tempfile.tempdir = None
3023+
for env_name in "TMPDIR", "TEMP", "TMP":
3024+
dir_name = os.getenv(env_name)
3025+
if dir_name:
3026+
return dir_name
3027+
# we have to check the real OS temp path here, as this is what
3028+
# tempfile assumes
3029+
if os.name == "nt":
3030+
return os.path.expanduser(r"~\AppData\Local\Temp")
3031+
return "/tmp"
3032+
30163033
def _create_temp_dir(self):
30173034
# the temp directory is assumed to exist at least in `tempfile`,
30183035
# so we create it here for convenience
3019-
temp_dir = tempfile.gettempdir()
3036+
temp_dir = self._tempdir_name()
30203037
if not self.exists(temp_dir):
30213038
self.create_dir(temp_dir)
30223039
if sys.platform != "win32" and not self.exists("/tmp"):

pyfakefs/tests/fake_filesystem_unittest_test.py

+26
Original file line numberDiff line numberDiff line change
@@ -925,5 +925,31 @@ def test_using_fakefs(self):
925925
self.assertEqual("test", f.contents)
926926

927927

928+
class TestTempPathCreation(fake_filesystem_unittest.TestCase):
929+
"""Regression test for #965. Checks that the temp file system
930+
is properly created with a root-owned root path.
931+
"""
932+
933+
def setUp(self):
934+
self.setUpPyfakefs()
935+
936+
def check_write_tmp_after_reset(self, os_type):
937+
self.fs.os = os_type
938+
# Mark '/' to be modifiable by only root
939+
os.chown("/", 0, 0)
940+
os.chmod("/", 0b111_101_101)
941+
with tempfile.TemporaryFile("wb") as f:
942+
assert f.write(b"foo") == 3
943+
944+
def test_write_tmp_linux(self):
945+
self.check_write_tmp_after_reset(OSType.LINUX)
946+
947+
def test_write_tmp_macos(self):
948+
self.check_write_tmp_after_reset(OSType.MACOS)
949+
950+
def test_write_tmp_windows(self):
951+
self.check_write_tmp_after_reset(OSType.WINDOWS)
952+
953+
928954
if __name__ == "__main__":
929955
unittest.main()

0 commit comments

Comments
 (0)