Skip to content

Commit

Permalink
Make sure the temp directory can be created during fs reset
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
mrbean-bremen committed Mar 9, 2024
1 parent 2551f7b commit 1cca32d
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 7 deletions.
2 changes: 2 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ The released versions correspond to PyPI releases.
(see [#959](../../issues/959))
* fixed handling of directory enumeration and search permissions under Posix systems
(see [#960](../../issues/960))
* fixed creation of the temp directory in the fake file system after a filesystem reset
(see [#965](../../issues/965))

## [Version 5.3.5](https://pypi.python.org/pypi/pyfakefs/5.3.5) (2024-01-30)
Fixes a regression.
Expand Down
11 changes: 6 additions & 5 deletions docs/troubleshooting.rst
Original file line number Diff line number Diff line change
Expand Up @@ -179,8 +179,7 @@ As ``pyfakefs`` does not fake the ``tempfile`` module (as described above),
a temporary directory is required to ensure that ``tempfile`` works correctly,
e.g., that ``tempfile.gettempdir()`` will return a valid value. This
means that any newly created fake file system will always have either a
directory named ``/tmp`` when running on Linux or Unix systems,
``/var/folders/<hash>/T`` when running on macOS, or
directory named ``/tmp`` when running on POSIX systems, or
``C:\Users\<user>\AppData\Local\Temp`` on Windows:

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


User rights
Expand Down
20 changes: 19 additions & 1 deletion pyfakefs/fake_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import io
import os
import sys
import traceback
from stat import (
S_IFREG,
S_IFDIR,
Expand Down Expand Up @@ -585,7 +586,24 @@ def remove_entry(self, pathname_name: str, recursive: bool = True) -> None:
if entry.st_mode & helpers.PERM_WRITE == 0:
self.filesystem.raise_os_error(errno.EACCES, pathname_name)
if self.filesystem.has_open_file(entry):
self.filesystem.raise_os_error(errno.EACCES, pathname_name)
raise_error = True
if os.name == "posix" and not hasattr(os, "O_TMPFILE"):
# special handling for emulating Windows under macOS and PyPi
# tempfile uses unlink based on the real OS while deleting
# a temporary file, so we ignore that error in this specific case
st = traceback.extract_stack(limit=6)
if sys.version_info < (3, 10):
if (
st[0].name == "TemporaryFile"
and st[0].line == "_os.unlink(name)"
):
raise_error = False
else:
# TemporaryFile implementation has changed in Python 3.10
if st[1].name == "opener" and st[1].line == "_os.unlink(name)":
raise_error = False
if raise_error:
self.filesystem.raise_os_error(errno.EACCES, pathname_name)
else:
if not helpers.is_root() and not self.has_permission(
helpers.PERM_WRITE | helpers.PERM_EXE
Expand Down
19 changes: 18 additions & 1 deletion pyfakefs/fake_filesystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -3013,10 +3013,27 @@ def _add_standard_streams(self) -> None:
self._add_open_file(StandardStreamWrapper(sys.stdout))
self._add_open_file(StandardStreamWrapper(sys.stderr))

def _tempdir_name(self):
"""This logic is extracted from tempdir._candidate_tempdir_list.
We cannot rely on tempdir.gettempdir() in an empty filesystem, as it tries
to write to the filesystem to ensure that the tempdir is valid.
"""
# reset the cached tempdir in tempfile
tempfile.tempdir = None
for env_name in "TMPDIR", "TEMP", "TMP":
dir_name = os.getenv(env_name)
if dir_name:
return dir_name
# we have to check the real OS temp path here, as this is what
# tempfile assumes
if os.name == "nt":
return os.path.expanduser(r"~\AppData\Local\Temp")
return "/tmp"

def _create_temp_dir(self):
# the temp directory is assumed to exist at least in `tempfile`,
# so we create it here for convenience
temp_dir = tempfile.gettempdir()
temp_dir = self._tempdir_name()
if not self.exists(temp_dir):
self.create_dir(temp_dir)
if sys.platform != "win32" and not self.exists("/tmp"):
Expand Down
26 changes: 26 additions & 0 deletions pyfakefs/tests/fake_filesystem_unittest_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -925,5 +925,31 @@ def test_using_fakefs(self):
self.assertEqual("test", f.contents)


class TestTempPathCreation(fake_filesystem_unittest.TestCase):
"""Regression test for #965. Checks that the temp file system
is properly created with a root-owned root path.
"""

def setUp(self):
self.setUpPyfakefs()

def check_write_tmp_after_reset(self, os_type):
self.fs.os = os_type
# Mark '/' to be modifiable by only root
os.chown("/", 0, 0)
os.chmod("/", 0b111_101_101)
with tempfile.TemporaryFile("wb") as f:
assert f.write(b"foo") == 3

def test_write_tmp_linux(self):
self.check_write_tmp_after_reset(OSType.LINUX)

def test_write_tmp_macos(self):
self.check_write_tmp_after_reset(OSType.MACOS)

def test_write_tmp_windows(self):
self.check_write_tmp_after_reset(OSType.WINDOWS)


if __name__ == "__main__":
unittest.main()

0 comments on commit 1cca32d

Please sign in to comment.