Mini Shell
# Copyright 2009 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
""" Uses :py:class:`FakeOsModule` to provide a
fake :py:mod:`os` module replacement.
"""
import errno
import functools
import inspect
import os
import sys
import uuid
from contextlib import contextmanager
from stat import (
S_IFREG,
S_IFSOCK,
)
from typing import (
List,
Optional,
Callable,
Union,
Any,
Tuple,
cast,
AnyStr,
TYPE_CHECKING,
Set,
)
from pyfakefs.extra_packages import use_scandir
from pyfakefs.fake_file import (
FakeDirectory,
FakeDirWrapper,
StandardStreamWrapper,
FakeFileWrapper,
FakePipeWrapper,
FakeFile,
AnyFileWrapper,
)
from pyfakefs.fake_open import FakeFileOpen, _OpenModes
from pyfakefs.fake_path import FakePathModule
from pyfakefs.fake_scandir import scandir, walk, ScanDirIter
from pyfakefs.helpers import (
FakeStatResult,
is_int_type,
is_byte_string,
make_string_path,
IS_PYPY,
to_string,
matching_string,
AnyString,
to_bytes,
PERM_EXE,
PERM_DEF,
is_root,
get_uid,
get_gid,
)
if TYPE_CHECKING:
from pyfakefs.fake_filesystem import FakeFilesystem
NR_STD_STREAMS = 3
class FakeOsModule:
"""Uses FakeFilesystem to provide a fake os module replacement.
Do not create os.path separately from os, as there is a necessary circular
dependency between os and os.path to replicate the behavior of the standard
Python modules. What you want to do is to just let FakeOsModule take care
of `os.path` setup itself.
# You always want to do this.
filesystem = fake_filesystem.FakeFilesystem()
my_os_module = fake_os.FakeOsModule(filesystem)
"""
use_original = False
@staticmethod
def dir() -> List[str]:
"""Return the list of patched function names. Used for patching
functions imported from the module.
"""
_dir = [
"access",
"chdir",
"chmod",
"chown",
"close",
"fstat",
"fsync",
"getcwd",
"lchmod",
"link",
"listdir",
"lstat",
"makedirs",
"mkdir",
"mknod",
"open",
"read",
"readlink",
"remove",
"removedirs",
"rename",
"rmdir",
"stat",
"symlink",
"umask",
"unlink",
"utime",
"walk",
"write",
"getcwdb",
"replace",
]
if sys.platform.startswith("linux"):
_dir += [
"fdatasync",
"getxattr",
"listxattr",
"removexattr",
"setxattr",
]
if sys.platform != "win32":
_dir += [
"getgid",
"getuid",
]
if use_scandir:
_dir += ["scandir"]
return _dir
def __init__(self, filesystem: "FakeFilesystem"):
"""Also exposes self.path (to fake os.path).
Args:
filesystem: FakeFilesystem used to provide file system information
"""
self.filesystem = filesystem
self.os_module: Any = os
self.path = FakePathModule(self.filesystem, self)
self._supports_follow_symlinks: Optional[Set] = None
self._supports_dir_fd: Optional[Set] = None
self._supports_effective_ids: Optional[Set] = None
self._supports_fd: Optional[Set] = None
@property
def devnull(self) -> str:
return self.path.devnull
@property
def sep(self) -> str:
return self.path.sep
@property
def altsep(self) -> Optional[str]:
return self.path.altsep
@property
def linesep(self) -> str:
return self.path.linesep
@property
def pathsep(self) -> str:
return self.path.pathsep
def fdopen(self, fd: int, *args: Any, **kwargs: Any) -> AnyFileWrapper:
"""Redirector to open() builtin function.
Args:
fd: The file descriptor of the file to open.
*args: Pass through args.
**kwargs: Pass through kwargs.
Returns:
File object corresponding to file_des.
Raises:
TypeError: if file descriptor is not an integer.
"""
if not is_int_type(fd):
raise TypeError("an integer is required")
return FakeFileOpen(self.filesystem)(fd, *args, **kwargs)
def _umask(self) -> int:
"""Return the current umask."""
if self.filesystem.is_windows_fs:
# windows always returns 0 - it has no real notion of umask
return 0
if sys.platform == "win32":
# if we are testing Unix under Windows we assume a default mask
return 0o002
else:
# under Unix, we return the real umask;
# as there is no pure getter for umask, so we have to first
# set a mode to get the previous one and then re-set that
mask = os.umask(0)
os.umask(mask)
return mask
def open(
self,
path: AnyStr,
flags: int,
mode: Optional[int] = None,
*,
dir_fd: Optional[int] = None
) -> int:
"""Return the file descriptor for a FakeFile.
Args:
path: the path to the file
flags: low-level bits to indicate io operation
mode: bits to define default permissions
Note: only basic modes are supported, OS-specific modes are
ignored
dir_fd: If not `None`, the file descriptor of a directory,
with `file_path` being relative to this directory.
Returns:
A file descriptor.
Raises:
OSError: if the path cannot be found
ValueError: if invalid mode is given
NotImplementedError: if `os.O_EXCL` is used without `os.O_CREAT`
"""
path = self._path_with_dir_fd(path, self.open, dir_fd)
if mode is None:
if self.filesystem.is_windows_fs:
mode = 0o666
else:
mode = 0o777 & ~self._umask()
has_tmpfile_flag = (
hasattr(os, "O_TMPFILE") and flags & os.O_TMPFILE == os.O_TMPFILE
)
open_modes = _OpenModes(
must_exist=not flags & os.O_CREAT and not has_tmpfile_flag,
can_read=not flags & os.O_WRONLY,
can_write=flags & (os.O_RDWR | os.O_WRONLY) != 0,
truncate=flags & os.O_TRUNC != 0,
append=flags & os.O_APPEND != 0,
must_not_exist=flags & os.O_EXCL != 0,
)
if open_modes.must_not_exist and open_modes.must_exist:
raise NotImplementedError("O_EXCL without O_CREAT mode is not supported")
if has_tmpfile_flag:
# this is a workaround for tempfiles that do not have a filename
# as we do not support this directly, we just add a unique filename
# and set the file to delete on close
path = self.filesystem.joinpaths(
path, matching_string(path, str(uuid.uuid4()))
)
if not self.filesystem.is_windows_fs and self.filesystem.exists(path):
# handle opening directory - only allowed under Posix
# with read-only mode
obj = self.filesystem.resolve(path)
if isinstance(obj, FakeDirectory):
if (
not open_modes.must_exist and not self.filesystem.is_macos
) or open_modes.can_write:
self.filesystem.raise_os_error(errno.EISDIR, path)
dir_wrapper = FakeDirWrapper(obj, path, self.filesystem)
file_des = self.filesystem._add_open_file(dir_wrapper)
dir_wrapper.filedes = file_des
return file_des
# low level open is always binary
str_flags = "b"
delete_on_close = has_tmpfile_flag
if hasattr(os, "O_TEMPORARY"):
delete_on_close = flags & os.O_TEMPORARY == os.O_TEMPORARY
fake_file = FakeFileOpen(
self.filesystem, delete_on_close=delete_on_close, raw_io=True
)(path, str_flags, open_modes=open_modes)
assert not isinstance(fake_file, StandardStreamWrapper)
if fake_file.file_object != self.filesystem.dev_null:
self.chmod(path, mode)
return fake_file.fileno()
def close(self, fd: int) -> None:
"""Close a file descriptor.
Args:
fd: An integer file descriptor for the file object requested.
Raises:
OSError: bad file descriptor.
TypeError: if file descriptor is not an integer.
"""
file_handle = self.filesystem.get_open_file(fd)
file_handle.close()
def read(self, fd: int, n: int) -> bytes:
"""Read number of bytes from a file descriptor, returns bytes read.
Args:
fd: An integer file descriptor for the file object requested.
n: Number of bytes to read from file.
Returns:
Bytes read from file.
Raises:
OSError: bad file descriptor.
TypeError: if file descriptor is not an integer.
"""
file_handle = self.filesystem.get_open_file(fd)
if isinstance(file_handle, FakeFileWrapper):
file_handle.raw_io = True
if isinstance(file_handle, FakeDirWrapper):
self.filesystem.raise_os_error(errno.EBADF, file_handle.file_path)
return file_handle.read(n)
def write(self, fd: int, contents: bytes) -> int:
"""Write string to file descriptor, returns number of bytes written.
Args:
fd: An integer file descriptor for the file object requested.
contents: String of bytes to write to file.
Returns:
Number of bytes written.
Raises:
OSError: bad file descriptor.
TypeError: if file descriptor is not an integer.
"""
file_handle = cast(FakeFileWrapper, self.filesystem.get_open_file(fd))
if isinstance(file_handle, FakeDirWrapper):
self.filesystem.raise_os_error(errno.EBADF, file_handle.file_path)
if isinstance(file_handle, FakePipeWrapper):
return file_handle.write(contents)
file_handle.raw_io = True
file_handle._sync_io()
file_handle.update_flush_pos()
file_handle.write(contents)
file_handle.flush()
return len(contents)
def pipe(self) -> Tuple[int, int]:
read_fd, write_fd = os.pipe()
read_wrapper = FakePipeWrapper(self.filesystem, read_fd, False)
file_des = self.filesystem._add_open_file(read_wrapper)
read_wrapper.filedes = file_des
write_wrapper = FakePipeWrapper(self.filesystem, write_fd, True)
file_des = self.filesystem._add_open_file(write_wrapper)
write_wrapper.filedes = file_des
return read_wrapper.filedes, write_wrapper.filedes
def fstat(self, fd: int) -> FakeStatResult:
"""Return the os.stat-like tuple for the FakeFile object of file_des.
Args:
fd: The file descriptor of filesystem object to retrieve.
Returns:
The FakeStatResult object corresponding to entry_path.
Raises:
OSError: if the filesystem object doesn't exist.
"""
# stat should return the tuple representing return value of os.stat
file_object = self.filesystem.get_open_file(fd).get_object()
assert isinstance(file_object, FakeFile)
return file_object.stat_result.copy()
def umask(self, mask: int) -> int:
"""Change the current umask.
Args:
mask: (int) The new umask value.
Returns:
The old umask.
Raises:
TypeError: if new_mask is of an invalid type.
"""
if not is_int_type(mask):
raise TypeError("an integer is required")
old_umask = self.filesystem.umask
self.filesystem.umask = mask
return old_umask
def chdir(self, path: AnyStr) -> None:
"""Change current working directory to target directory.
Args:
path: The path to new current working directory.
Raises:
OSError: if user lacks permission to enter the argument directory
or if the target is not a directory.
"""
try:
path = self.filesystem.resolve_path(path, allow_fd=True)
except OSError as exc:
if self.filesystem.is_macos and exc.errno == errno.EBADF:
raise OSError(errno.ENOTDIR, "Not a directory: " + str(path))
raise
self.filesystem.confirmdir(path)
directory = self.filesystem.resolve(path)
# A full implementation would check permissions all the way
# up the tree.
if not is_root() and not directory.st_mode | PERM_EXE:
self.filesystem.raise_os_error(errno.EACCES, directory.name)
self.filesystem.cwd = path # type: ignore[assignment]
def getcwd(self) -> str:
"""Return current working directory."""
return to_string(self.filesystem.cwd)
def getcwdb(self) -> bytes:
"""Return current working directory as bytes."""
return to_bytes(self.filesystem.cwd)
def listdir(self, path: AnyStr) -> List[AnyStr]:
"""Return a list of file names in target_directory.
Args:
path: Path to the target directory within the fake
filesystem.
Returns:
A list of file names within the target directory in arbitrary
order.
Raises:
OSError: if the target is not a directory.
"""
return self.filesystem.listdir(path)
XATTR_CREATE = 1
XATTR_REPLACE = 2
def getxattr(
self, path: AnyStr, attribute: AnyString, *, follow_symlinks: bool = True
) -> Optional[bytes]:
"""Return the value of the given extended filesystem attribute for
`path`.
Args:
path: File path, file descriptor or path-like object (for
Python >= 3.6).
attribute: (str or bytes) The attribute name.
follow_symlinks: (bool) If True (the default), symlinks in the
path are traversed.
Returns:
The contents of the extended attribute as bytes or None if
the attribute does not exist.
Raises:
OSError: if the path does not exist.
"""
if not self.filesystem.is_linux:
raise AttributeError("module 'os' has no attribute 'getxattr'")
if isinstance(attribute, bytes):
attribute = attribute.decode(sys.getfilesystemencoding())
file_obj = self.filesystem.resolve(path, follow_symlinks, allow_fd=True)
return file_obj.xattr.get(attribute)
def listxattr(
self, path: Optional[AnyStr] = None, *, follow_symlinks: bool = True
) -> List[str]:
"""Return a list of the extended filesystem attributes on `path`.
Args:
path: File path, file descriptor or path-like object (for
Python >= 3.6). If None, the current directory is used.
follow_symlinks: (bool) If True (the default), symlinks in the
path are traversed.
Returns:
A list of all attribute names for the given path as str.
Raises:
OSError: if the path does not exist.
"""
if not self.filesystem.is_linux:
raise AttributeError("module 'os' has no attribute 'listxattr'")
path_str = self.filesystem.cwd if path is None else path
file_obj = self.filesystem.resolve(
cast(AnyStr, path_str), # pytype: disable=invalid-annotation
follow_symlinks,
allow_fd=True,
)
return list(file_obj.xattr.keys())
def removexattr(
self, path: AnyStr, attribute: AnyString, *, follow_symlinks: bool = True
) -> None:
"""Removes the extended filesystem attribute attribute from `path`.
Args:
path: File path, file descriptor or path-like object (for
Python >= 3.6).
attribute: (str or bytes) The attribute name.
follow_symlinks: (bool) If True (the default), symlinks in the
path are traversed.
Raises:
OSError: if the path does not exist.
"""
if not self.filesystem.is_linux:
raise AttributeError("module 'os' has no attribute 'removexattr'")
if isinstance(attribute, bytes):
attribute = attribute.decode(sys.getfilesystemencoding())
file_obj = self.filesystem.resolve(path, follow_symlinks, allow_fd=True)
if attribute in file_obj.xattr:
del file_obj.xattr[attribute]
def setxattr(
self,
path: AnyStr,
attribute: AnyString,
value: bytes,
flags: int = 0,
*,
follow_symlinks: bool = True
) -> None:
"""Sets the value of the given extended filesystem attribute for
`path`.
Args:
path: File path, file descriptor or path-like object (for
Python >= 3.6).
attribute: The attribute name (str or bytes).
value: (byte-like) The value to be set.
follow_symlinks: (bool) If True (the default), symlinks in the
path are traversed.
Raises:
OSError: if the path does not exist.
TypeError: if `value` is not a byte-like object.
"""
if not self.filesystem.is_linux:
raise AttributeError("module 'os' has no attribute 'setxattr'")
if isinstance(attribute, bytes):
attribute = attribute.decode(sys.getfilesystemencoding())
if not is_byte_string(value):
raise TypeError("a bytes-like object is required")
file_obj = self.filesystem.resolve(path, follow_symlinks, allow_fd=True)
exists = attribute in file_obj.xattr
if exists and flags == self.XATTR_CREATE:
self.filesystem.raise_os_error(errno.ENODATA, file_obj.path)
if not exists and flags == self.XATTR_REPLACE:
self.filesystem.raise_os_error(errno.EEXIST, file_obj.path)
file_obj.xattr[attribute] = value
def scandir(self, path: str = ".") -> ScanDirIter:
"""Return an iterator of DirEntry objects corresponding to the
entries in the directory given by path.
Args:
path: Path to the target directory within the fake filesystem.
Returns:
An iterator to an unsorted list of os.DirEntry objects for
each entry in path.
Raises:
OSError: if the target is not a directory.
"""
return scandir(self.filesystem, path)
def walk(
self,
top: AnyStr,
topdown: bool = True,
onerror: Optional[bool] = None,
followlinks: bool = False,
):
"""Perform an os.walk operation over the fake filesystem.
Args:
top: The root directory from which to begin walk.
topdown: Determines whether to return the tuples with the root as
the first entry (`True`) or as the last, after all the child
directory tuples (`False`).
onerror: If not `None`, function which will be called to handle the
`os.error` instance provided when `os.listdir()` fails.
followlinks: If `True`, symbolic links are followed.
Yields:
(path, directories, nondirectories) for top and each of its
subdirectories. See the documentation for the builtin os module
for further details.
"""
return walk(self.filesystem, top, topdown, onerror, followlinks)
def readlink(self, path: AnyStr, dir_fd: Optional[int] = None) -> str:
"""Read the target of a symlink.
Args:
path: Symlink to read the target of.
dir_fd: If not `None`, the file descriptor of a directory,
with `path` being relative to this directory.
Returns:
the string representing the path to which the symbolic link points.
Raises:
TypeError: if `path` is None
OSError: (with errno=ENOENT) if path is not a valid path, or
(with errno=EINVAL) if path is valid, but is not a symlink
"""
path = self._path_with_dir_fd(path, self.readlink, dir_fd)
return self.filesystem.readlink(path)
def stat(
self,
path: AnyStr,
*,
dir_fd: Optional[int] = None,
follow_symlinks: bool = True
) -> FakeStatResult:
"""Return the os.stat-like tuple for the FakeFile object of entry_path.
Args:
path: path to filesystem object to retrieve.
dir_fd: (int) If not `None`, the file descriptor of a directory,
with `entry_path` being relative to this directory.
follow_symlinks: (bool) If `False` and `entry_path` points to a
symlink, the link itself is changed instead of the linked
object.
Returns:
The FakeStatResult object corresponding to entry_path.
Raises:
OSError: if the filesystem object doesn't exist.
"""
path = self._path_with_dir_fd(path, self.stat, dir_fd)
return self.filesystem.stat(path, follow_symlinks)
def lstat(self, path: AnyStr, *, dir_fd: Optional[int] = None) -> FakeStatResult:
"""Return the os.stat-like tuple for entry_path,
not following symlinks.
Args:
path: path to filesystem object to retrieve.
dir_fd: If not `None`, the file descriptor of a directory, with
`path` being relative to this directory.
Returns:
the FakeStatResult object corresponding to `path`.
Raises:
OSError: if the filesystem object doesn't exist.
"""
# stat should return the tuple representing return value of os.stat
path = self._path_with_dir_fd(path, self.lstat, dir_fd)
return self.filesystem.stat(path, follow_symlinks=False)
def remove(self, path: AnyStr, dir_fd: Optional[int] = None) -> None:
"""Remove the FakeFile object at the specified file path.
Args:
path: Path to file to be removed.
dir_fd: If not `None`, the file descriptor of a directory,
with `path` being relative to this directory.
Raises:
OSError: if path points to a directory.
OSError: if path does not exist.
OSError: if removal failed.
"""
path = self._path_with_dir_fd(path, self.remove, dir_fd)
self.filesystem.remove(path)
def unlink(self, path: AnyStr, *, dir_fd: Optional[int] = None) -> None:
"""Remove the FakeFile object at the specified file path.
Args:
path: Path to file to be removed.
dir_fd: If not `None`, the file descriptor of a directory,
with `path` being relative to this directory.
Raises:
OSError: if path points to a directory.
OSError: if path does not exist.
OSError: if removal failed.
"""
path = self._path_with_dir_fd(path, self.unlink, dir_fd)
self.filesystem.remove(path)
def rename(
self,
src: AnyStr,
dst: AnyStr,
*,
src_dir_fd: Optional[int] = None,
dst_dir_fd: Optional[int] = None
) -> None:
"""Rename a FakeFile object at old_file_path to new_file_path,
preserving all properties.
Also replaces existing new_file_path object, if one existed
(Unix only).
Args:
src: Path to filesystem object to rename.
dst: Path to where the filesystem object will live
after this call.
src_dir_fd: If not `None`, the file descriptor of a directory,
with `src` being relative to this directory.
dst_dir_fd: If not `None`, the file descriptor of a directory,
with `dst` being relative to this directory.
Raises:
OSError: if old_file_path does not exist.
OSError: if new_file_path is an existing directory.
OSError: if new_file_path is an existing file (Windows only)
OSError: if new_file_path is an existing file and could not
be removed (Unix)
OSError: if `dirname(new_file)` does not exist
OSError: if the file would be moved to another filesystem
(e.g. mount point)
"""
src = self._path_with_dir_fd(src, self.rename, src_dir_fd)
dst = self._path_with_dir_fd(dst, self.rename, dst_dir_fd)
self.filesystem.rename(src, dst)
def renames(self, old: AnyStr, new: AnyStr):
"""Fakes `os.renames`, documentation taken from there.
Super-rename; create directories as necessary and delete any left
empty. Works like rename, except creation of any intermediate
directories needed to make the new pathname good is attempted
first. After the rename, directories corresponding to rightmost
path segments of the old name will be pruned until either the
whole path is consumed or a nonempty directory is found.
Note: this function can fail with the new directory structure made
if you lack permissions needed to unlink the leaf directory or
file.
"""
head, tail = self.filesystem.splitpath(new)
if head and tail and not self.filesystem.exists(head):
self.makedirs(head)
self.rename(old, new)
head, tail = self.filesystem.splitpath(old)
if head and tail:
try:
self.removedirs(head)
except OSError:
pass
def replace(
self,
src: AnyStr,
dst: AnyStr,
*,
src_dir_fd: Optional[int] = None,
dst_dir_fd: Optional[int] = None
) -> None:
"""Renames a FakeFile object at old_file_path to new_file_path,
preserving all properties.
Also replaces existing new_file_path object, if one existed.
Arg
src: Path to filesystem object to rename.
dst: Path to where the filesystem object will live
after this call.
src_dir_fd: If not `None`, the file descriptor of a directory,
with `src` being relative to this directory.
dst_dir_fd: If not `None`, the file descriptor of a directory,
with `dst` being relative to this directory.
Raises:
OSError: if old_file_path does not exist.
OSError: if new_file_path is an existing directory.
OSError: if new_file_path is an existing file and could
not be removed
OSError: if `dirname(new_file)` does not exist
OSError: if the file would be moved to another filesystem
(e.g. mount point)
"""
src = self._path_with_dir_fd(src, self.rename, src_dir_fd)
dst = self._path_with_dir_fd(dst, self.rename, dst_dir_fd)
self.filesystem.rename(src, dst, force_replace=True)
def rmdir(self, path: AnyStr, *, dir_fd: Optional[int] = None) -> None:
"""Remove a leaf Fake directory.
Args:
path: (str) Name of directory to remove.
dir_fd: If not `None`, the file descriptor of a directory,
with `path` being relative to this directory.
Raises:
OSError: if `path` does not exist or is not a directory,
or as per FakeFilesystem.remove_object. Cannot remove '.'.
"""
path = self._path_with_dir_fd(path, self.rmdir, dir_fd)
self.filesystem.rmdir(path)
def removedirs(self, name: AnyStr) -> None:
"""Remove a leaf fake directory and all empty intermediate ones.
Args:
name: the directory to be removed.
Raises:
OSError: if target_directory does not exist or is not a directory.
OSError: if target_directory is not empty.
"""
name = self.filesystem.absnormpath(name)
directory = self.filesystem.confirmdir(name)
if directory.entries:
self.filesystem.raise_os_error(errno.ENOTEMPTY, self.path.basename(name))
else:
self.rmdir(name)
head, tail = self.path.split(name)
if not tail:
head, tail = self.path.split(head)
while head and tail:
head_dir = self.filesystem.confirmdir(head)
if head_dir.entries:
break
# only the top-level dir may not be a symlink
self.filesystem.rmdir(head, allow_symlink=True)
head, tail = self.path.split(head)
def mkdir(
self, path: AnyStr, mode: int = PERM_DEF, *, dir_fd: Optional[int] = None
) -> None:
"""Create a leaf Fake directory.
Args:
path: (str) Name of directory to create.
Relative paths are assumed to be relative to '/'.
mode: (int) Mode to create directory with. This argument defaults
to 0o777. The umask is applied to this mode.
dir_fd: If not `None`, the file descriptor of a directory,
with `path` being relative to this directory.
Raises:
OSError: if the directory name is invalid or parent directory is
read only or as per FakeFilesystem.add_object.
"""
path = self._path_with_dir_fd(path, self.mkdir, dir_fd)
try:
self.filesystem.makedir(path, mode)
except OSError as e:
if e.errno == errno.EACCES:
self.filesystem.raise_os_error(e.errno, path)
raise
def makedirs(
self, name: AnyStr, mode: int = PERM_DEF, exist_ok: Optional[bool] = None
) -> None:
"""Create a leaf Fake directory + create any non-existent parent dirs.
Args:
name: (str) Name of directory to create.
mode: (int) Mode to create directory (and any necessary parent
directories) with. This argument defaults to 0o777.
The umask is applied to this mode.
exist_ok: (boolean) If exist_ok is False (the default), an OSError
is raised if the target directory already exists.
Raises:
OSError: if the directory already exists and exist_ok=False, or as
per :py:meth:`FakeFilesystem.create_dir`.
"""
if exist_ok is None:
exist_ok = False
self.filesystem.makedirs(name, mode, exist_ok)
def _path_with_dir_fd(
self, path: AnyStr, fct: Callable, dir_fd: Optional[int]
) -> AnyStr:
"""Return the path considering dir_fd. Raise on invalid parameters."""
try:
path = make_string_path(path)
except TypeError:
# the error is handled later
path = path
if dir_fd is not None:
# check if fd is supported for the built-in real function
if fct not in self.supports_dir_fd:
raise NotImplementedError("dir_fd unavailable on this platform")
if isinstance(path, int):
raise ValueError(
"%s: Can't specify dir_fd without "
"matching path_str" % fct.__name__
)
if not self.path.isabs(path):
open_file = self.filesystem.get_open_file(dir_fd)
return self.path.join( # type: ignore[type-var, return-value]
cast(FakeFile, open_file.get_object()).path, path
)
return path
def truncate(self, path: AnyStr, length: int) -> None:
"""Truncate the file corresponding to path, so that it is
length bytes in size. If length is larger than the current size,
the file is filled up with zero bytes.
Args:
path: (str or int) Path to the file, or an integer file
descriptor for the file object.
length: (int) Length of the file after truncating it.
Raises:
OSError: if the file does not exist or the file descriptor is
invalid.
"""
file_object = self.filesystem.resolve(path, allow_fd=True)
file_object.size = length
def ftruncate(self, fd: int, length: int) -> None:
"""Truncate the file corresponding to fd, so that it is
length bytes in size. If length is larger than the current size,
the file is filled up with zero bytes.
Args:
fd: (int) File descriptor for the file object.
length: (int) Maximum length of the file after truncating it.
Raises:
OSError: if the file descriptor is invalid
"""
file_object = self.filesystem.get_open_file(fd).get_object()
if isinstance(file_object, FakeFileWrapper):
file_object.size = length
else:
raise OSError(errno.EBADF, "Invalid file descriptor")
def access(
self,
path: AnyStr,
mode: int,
*,
dir_fd: Optional[int] = None,
effective_ids: bool = False,
follow_symlinks: bool = True
) -> bool:
"""Check if a file exists and has the specified permissions.
Args:
path: (str) Path to the file.
mode: (int) Permissions represented as a bitwise-OR combination of
os.F_OK, os.R_OK, os.W_OK, and os.X_OK.
dir_fd: If not `None`, the file descriptor of a directory, with
`path` being relative to this directory.
effective_ids: (bool) Unused. Only here to match the signature.
follow_symlinks: (bool) If `False` and `path` points to a symlink,
the link itself is queried instead of the linked object.
Returns:
bool, `True` if file is accessible, `False` otherwise.
"""
if effective_ids and self.filesystem.is_windows_fs:
raise NotImplementedError(
"access: effective_ids unavailable on this platform"
)
path = self._path_with_dir_fd(path, self.access, dir_fd)
try:
stat_result = self.stat(path, follow_symlinks=follow_symlinks)
except OSError as os_error:
if os_error.errno == errno.ENOENT:
return False
raise
if is_root():
mode &= ~os.W_OK
return (mode & ((stat_result.st_mode >> 6) & 7)) == mode
def chmod(
self,
path: AnyStr,
mode: int,
*,
dir_fd: Optional[int] = None,
follow_symlinks: bool = True
) -> None:
"""Change the permissions of a file as encoded in integer mode.
Args:
path: (str) Path to the file.
mode: (int) Permissions.
dir_fd: If not `None`, the file descriptor of a directory, with
`path` being relative to this directory.
follow_symlinks: (bool) If `False` and `path` points to a symlink,
the link itself is queried instead of the linked object.
"""
if not follow_symlinks and (
self.chmod not in self.supports_follow_symlinks or IS_PYPY
):
raise NotImplementedError(
"`follow_symlinks` for chmod() is not available " "on this system"
)
path = self._path_with_dir_fd(path, self.chmod, dir_fd)
self.filesystem.chmod(path, mode, follow_symlinks)
def lchmod(self, path: AnyStr, mode: int) -> None:
"""Change the permissions of a file as encoded in integer mode.
If the file is a link, the permissions of the link are changed.
Args:
path: (str) Path to the file.
mode: (int) Permissions.
"""
if self.filesystem.is_windows_fs:
raise NameError("name 'lchmod' is not defined")
self.filesystem.chmod(path, mode, follow_symlinks=False)
def utime(
self,
path: AnyStr,
times: Optional[Tuple[Union[int, float], Union[int, float]]] = None,
ns: Optional[Tuple[int, int]] = None,
dir_fd: Optional[int] = None,
follow_symlinks: bool = True,
) -> None:
"""Change the access and modified times of a file.
Args:
path: (str) Path to the file.
times: 2-tuple of int or float numbers, of the form (atime, mtime)
which is used to set the access and modified times in seconds.
If None, both times are set to the current time.
ns: 2-tuple of int numbers, of the form (atime, mtime) which is
used to set the access and modified times in nanoseconds.
If None, both times are set to the current time.
dir_fd: If not `None`, the file descriptor of a directory,
with `path` being relative to this directory.
follow_symlinks: (bool) If `False` and `path` points to a symlink,
the link itself is queried instead of the linked object.
Raises:
TypeError: If anything other than the expected types is
specified in the passed `times` or `ns` tuple,
or if the tuple length is not equal to 2.
ValueError: If both times and ns are specified.
"""
path = self._path_with_dir_fd(path, self.utime, dir_fd)
self.filesystem.utime(path, times=times, ns=ns, follow_symlinks=follow_symlinks)
def chown(
self,
path: AnyStr,
uid: int,
gid: int,
*,
dir_fd: Optional[int] = None,
follow_symlinks: bool = True
) -> None:
"""Set ownership of a faked file.
Args:
path: (str) Path to the file or directory.
uid: (int) Numeric uid to set the file or directory to.
gid: (int) Numeric gid to set the file or directory to.
dir_fd: (int) If not `None`, the file descriptor of a directory,
with `path` being relative to this directory.
follow_symlinks: (bool) If `False` and path points to a symlink,
the link itself is changed instead of the linked object.
Raises:
OSError: if path does not exist.
`None` is also allowed for `uid` and `gid`. This permits `os.rename`
to use `os.chown` even when the source file `uid` and `gid` are
`None` (unset).
"""
path = self._path_with_dir_fd(path, self.chown, dir_fd)
file_object = self.filesystem.resolve(path, follow_symlinks, allow_fd=True)
if not isinstance(uid, int) or not isinstance(gid, int):
raise TypeError("An integer is required")
if uid != -1:
file_object.st_uid = uid
if gid != -1:
file_object.st_gid = gid
def mknod(
self,
path: AnyStr,
mode: Optional[int] = None,
device: int = 0,
*,
dir_fd: Optional[int] = None
) -> None:
"""Create a filesystem node named 'filename'.
Does not support device special files or named pipes as the real os
module does.
Args:
path: (str) Name of the file to create
mode: (int) Permissions to use and type of file to be created.
Default permissions are 0o666. Only the stat.S_IFREG file type
is supported by the fake implementation. The umask is applied
to this mode.
device: not supported in fake implementation
dir_fd: If not `None`, the file descriptor of a directory,
with `path` being relative to this directory.
Raises:
OSError: if called with unsupported options or the file can not be
created.
"""
if self.filesystem.is_windows_fs:
raise AttributeError("module 'os' has no attribute 'mknode'")
if mode is None:
# note that a default value of 0o600 without a device type is
# documented - this is not how it seems to work
mode = S_IFREG | 0o600
if device or not mode & S_IFREG and not is_root():
self.filesystem.raise_os_error(errno.EPERM)
path = self._path_with_dir_fd(path, self.mknod, dir_fd)
head, tail = self.path.split(path)
if not tail:
if self.filesystem.exists(head, check_link=True):
self.filesystem.raise_os_error(errno.EEXIST, path)
self.filesystem.raise_os_error(errno.ENOENT, path)
if tail in (matching_string(tail, "."), matching_string(tail, "..")):
self.filesystem.raise_os_error(errno.ENOENT, path)
if self.filesystem.exists(path, check_link=True):
self.filesystem.raise_os_error(errno.EEXIST, path)
self.filesystem.add_object(
head,
FakeFile(tail, mode & ~self.filesystem.umask, filesystem=self.filesystem),
)
def symlink(
self,
src: AnyStr,
dst: AnyStr,
target_is_directory: bool = False,
*,
dir_fd: Optional[int] = None
) -> None:
"""Creates the specified symlink, pointed at the specified link target.
Args:
src: The target of the symlink.
dst: Path to the symlink to create.
target_is_directory: Currently ignored.
dir_fd: If not `None`, the file descriptor of a directory,
with `src` being relative to this directory.
Raises:
OSError: if the file already exists.
"""
src = self._path_with_dir_fd(src, self.symlink, dir_fd)
self.filesystem.create_symlink(dst, src, create_missing_dirs=False)
def link(
self,
src: AnyStr,
dst: AnyStr,
*,
src_dir_fd: Optional[int] = None,
dst_dir_fd: Optional[int] = None
) -> None:
"""Create a hard link at new_path, pointing at old_path.
Args:
src: An existing path to the target file.
dst: The destination path to create a new link at.
src_dir_fd: If not `None`, the file descriptor of a directory,
with `src` being relative to this directory.
dst_dir_fd: If not `None`, the file descriptor of a directory,
with `dst` being relative to this directory.
Raises:
OSError: if something already exists at new_path.
OSError: if the parent directory doesn't exist.
"""
src = self._path_with_dir_fd(src, self.link, src_dir_fd)
dst = self._path_with_dir_fd(dst, self.link, dst_dir_fd)
self.filesystem.link(src, dst)
def fsync(self, fd: int) -> None:
"""Perform fsync for a fake file (in other words, do nothing).
Args:
fd: The file descriptor of the open file.
Raises:
OSError: file_des is an invalid file descriptor.
TypeError: file_des is not an integer.
"""
# Throw an error if file_des isn't valid
if 0 <= fd < NR_STD_STREAMS:
self.filesystem.raise_os_error(errno.EINVAL)
file_object = cast(FakeFileWrapper, self.filesystem.get_open_file(fd))
if self.filesystem.is_windows_fs:
if not hasattr(file_object, "allow_update") or not file_object.allow_update:
self.filesystem.raise_os_error(errno.EBADF, file_object.file_path)
def fdatasync(self, fd: int) -> None:
"""Perform fdatasync for a fake file (in other words, do nothing).
Args:
fd: The file descriptor of the open file.
Raises:
OSError: `fd` is an invalid file descriptor.
TypeError: `fd` is not an integer.
"""
if self.filesystem.is_windows_fs or self.filesystem.is_macos:
raise AttributeError("module 'os' has no attribute 'fdatasync'")
# Throw an error if file_des isn't valid
if 0 <= fd < NR_STD_STREAMS:
self.filesystem.raise_os_error(errno.EINVAL)
self.filesystem.get_open_file(fd)
def sendfile(self, fd_out: int, fd_in: int, offset: int, count: int) -> int:
"""Copy count bytes from file descriptor fd_in to file descriptor
fd_out starting at offset.
Args:
fd_out: The file descriptor of the destination file.
fd_in: The file descriptor of the source file.
offset: The offset in bytes where to start the copy in the
source file. If `None` (Linux only), copying is started at
the current position, and the position is updated.
count: The number of bytes to copy. If 0, all remaining bytes
are copied (MacOs only).
Raises:
OSError: If `fd_in` or `fd_out` is an invalid file descriptor.
TypeError: If `fd_in` or `fd_out` is not an integer.
TypeError: If `offset` is None under MacOs.
"""
if self.filesystem.is_windows_fs:
raise AttributeError("module 'os' has no attribute 'sendfile'")
if 0 <= fd_in < NR_STD_STREAMS:
self.filesystem.raise_os_error(errno.EINVAL)
if 0 <= fd_out < NR_STD_STREAMS:
self.filesystem.raise_os_error(errno.EINVAL)
source = cast(FakeFileWrapper, self.filesystem.get_open_file(fd_in))
dest = cast(FakeFileWrapper, self.filesystem.get_open_file(fd_out))
if self.filesystem.is_macos:
if dest.get_object().stat_result.st_mode & 0o777000 != S_IFSOCK:
raise OSError("Socket operation on non-socket")
if offset is None:
if self.filesystem.is_macos:
raise TypeError("None is not a valid offset")
contents = source.read(count)
else:
position = source.tell()
source.seek(offset)
if count == 0 and self.filesystem.is_macos:
contents = source.read()
else:
contents = source.read(count)
source.seek(position)
if contents:
written = dest.write(contents)
dest.flush()
return written
return 0
def getuid(self) -> int:
"""Returns the user id set in the fake filesystem.
If not changed using ``set_uid``, this is the uid of the real system.
"""
if self.filesystem.is_windows_fs:
raise NameError("name 'getuid' is not defined")
return get_uid()
def getgid(self) -> int:
"""Returns the group id set in the fake filesystem.
If not changed using ``set_gid``, this is the gid of the real system.
"""
if self.filesystem.is_windows_fs:
raise NameError("name 'getgid' is not defined")
return get_gid()
def fake_functions(self, original_functions) -> Set:
functions = set()
for fn in original_functions:
if hasattr(self, fn.__name__):
functions.add(getattr(self, fn.__name__))
else:
functions.add(fn)
return functions
@property
def supports_follow_symlinks(self) -> Set[Callable]:
if self._supports_follow_symlinks is None:
self._supports_follow_symlinks = self.fake_functions(
self.os_module.supports_follow_symlinks
)
return self._supports_follow_symlinks
@property
def supports_dir_fd(self) -> Set[Callable]:
if self._supports_dir_fd is None:
self._supports_dir_fd = self.fake_functions(self.os_module.supports_dir_fd)
return self._supports_dir_fd
@property
def supports_fd(self) -> Set[Callable]:
if self._supports_fd is None:
self._supports_fd = self.fake_functions(self.os_module.supports_fd)
return self._supports_fd
@property
def supports_effective_ids(self) -> Set[Callable]:
if self._supports_effective_ids is None:
self._supports_effective_ids = self.fake_functions(
self.os_module.supports_effective_ids
)
return self._supports_effective_ids
def __getattr__(self, name: str) -> Any:
"""Forwards any unfaked calls to the standard os module."""
return getattr(self.os_module, name)
if sys.version_info > (3, 10):
def handle_original_call(f: Callable) -> Callable:
"""Decorator used for real pathlib Path methods to ensure that
real os functions instead of faked ones are used.
Applied to all non-private methods of `FakeOsModule`."""
@functools.wraps(f)
def wrapped(*args, **kwargs):
if FakeOsModule.use_original:
# remove the `self` argument for FakeOsModule methods
if args and isinstance(args[0], FakeOsModule):
args = args[1:]
return getattr(os, f.__name__)(*args, **kwargs)
return f(*args, **kwargs)
return wrapped
for name, fn in inspect.getmembers(FakeOsModule, inspect.isfunction):
if not fn.__name__.startswith("_"):
setattr(FakeOsModule, name, handle_original_call(fn))
@contextmanager
def use_original_os():
"""Temporarily use original os functions instead of faked ones.
Used to ensure that skipped modules do not use faked calls.
"""
try:
FakeOsModule.use_original = True
yield
finally:
FakeOsModule.use_original = False
Zerion Mini Shell 1.0