• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2009 Google Inc. All Rights Reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#      http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15"""Uses :py:class:`FakeOsModule` to provide a
16fake :py:mod:`os` module replacement.
17"""
18
19import errno
20import functools
21import inspect
22import os
23import sys
24import uuid
25from contextlib import contextmanager
26from stat import (
27    S_IFREG,
28    S_IFSOCK,
29)
30from typing import (
31    List,
32    Optional,
33    Callable,
34    Union,
35    Any,
36    Tuple,
37    cast,
38    AnyStr,
39    TYPE_CHECKING,
40    Set,
41)
42
43from pyfakefs.fake_file import (
44    FakeDirectory,
45    FakeDirWrapper,
46    StandardStreamWrapper,
47    FakeFileWrapper,
48    FakePipeWrapper,
49    FakeFile,
50    AnyFileWrapper,
51)
52from pyfakefs.fake_open import FakeFileOpen, _OpenModes
53from pyfakefs.fake_path import FakePathModule
54from pyfakefs.fake_scandir import scandir, walk, ScanDirIter
55from pyfakefs.helpers import (
56    FakeStatResult,
57    is_called_from_skipped_module,
58    is_int_type,
59    is_byte_string,
60    make_string_path,
61    IS_PYPY,
62    to_string,
63    matching_string,
64    AnyString,
65    to_bytes,
66    PERM_EXE,
67    PERM_DEF,
68    is_root,
69    get_uid,
70    get_gid,
71)
72
73if TYPE_CHECKING:
74    from pyfakefs.fake_filesystem import FakeFilesystem
75
76NR_STD_STREAMS = 3
77
78
79class FakeOsModule:
80    """Uses FakeFilesystem to provide a fake os module replacement.
81
82    Do not create os.path separately from os, as there is a necessary circular
83    dependency between os and os.path to replicate the behavior of the standard
84    Python modules.  What you want to do is to just let FakeOsModule take care
85    of `os.path` setup itself.
86
87    # You always want to do this.
88    filesystem = fake_filesystem.FakeFilesystem()
89    my_os_module = fake_os.FakeOsModule(filesystem)
90    """
91
92    use_original = False
93
94    @staticmethod
95    def dir() -> List[str]:
96        """Return the list of patched function names. Used for patching
97        functions imported from the module.
98        """
99        _dir = [
100            "access",
101            "chdir",
102            "chmod",
103            "chown",
104            "close",
105            "dup",
106            "dup2",
107            "fstat",
108            "fsync",
109            "getcwd",
110            "lchmod",
111            "link",
112            "listdir",
113            "lseek",
114            "lstat",
115            "makedirs",
116            "mkdir",
117            "mknod",
118            "open",
119            "read",
120            "readlink",
121            "remove",
122            "removedirs",
123            "rename",
124            "rmdir",
125            "scandir",
126            "stat",
127            "symlink",
128            "umask",
129            "unlink",
130            "utime",
131            "walk",
132            "write",
133            "getcwdb",
134            "replace",
135        ]
136        if sys.platform.startswith("linux"):
137            _dir += [
138                "fdatasync",
139                "getxattr",
140                "listxattr",
141                "removexattr",
142                "setxattr",
143            ]
144        if sys.platform != "win32":
145            _dir += [
146                "getgid",
147                "getuid",
148            ]
149        return _dir
150
151    def __init__(self, filesystem: "FakeFilesystem"):
152        """Also exposes self.path (to fake os.path).
153
154        Args:
155            filesystem: FakeFilesystem used to provide file system information
156        """
157        self.filesystem = filesystem
158        self.os_module: Any = os
159        self.path = FakePathModule(self.filesystem, self)
160        self._supports_follow_symlinks: Optional[Set] = None
161        self._supports_dir_fd: Optional[Set] = None
162        self._supports_effective_ids: Optional[Set] = None
163        self._supports_fd: Optional[Set] = None
164
165    @property
166    def devnull(self) -> str:
167        return self.path.devnull
168
169    @property
170    def sep(self) -> str:
171        return self.path.sep
172
173    @property
174    def altsep(self) -> Optional[str]:
175        return self.path.altsep
176
177    @property
178    def linesep(self) -> str:
179        return self.path.linesep
180
181    @property
182    def pathsep(self) -> str:
183        return self.path.pathsep
184
185    def fdopen(self, fd: int, *args: Any, **kwargs: Any) -> AnyFileWrapper:
186        """Redirector to open() builtin function.
187
188        Args:
189            fd: The file descriptor of the file to open.
190            *args: Pass through args.
191            **kwargs: Pass through kwargs.
192
193        Returns:
194            File object corresponding to file_des.
195
196        Raises:
197            TypeError: if file descriptor is not an integer.
198        """
199        if not is_int_type(fd):
200            raise TypeError("an integer is required")
201        return FakeFileOpen(self.filesystem)(fd, *args, **kwargs)
202
203    def _umask(self) -> int:
204        """Return the current umask."""
205        if self.filesystem.is_windows_fs:
206            # windows always returns 0 - it has no real notion of umask
207            return 0
208        if sys.platform == "win32":
209            # if we are testing Unix under Windows we assume a default mask
210            return 0o002
211        else:
212            # under Unix, we return the real umask;
213            # there is no pure getter for umask, so we have to first
214            # set a mode to get the previous one and then re-set that
215            mask = os.umask(0)
216            os.umask(mask)
217            return mask
218
219    def open(
220        self,
221        path: AnyStr,
222        flags: int,
223        mode: Optional[int] = None,
224        *,
225        dir_fd: Optional[int] = None,
226    ) -> int:
227        """Return the file descriptor for a FakeFile.
228
229        Args:
230            path: the path to the file
231            flags: low-level bits to indicate io operation
232            mode: bits to define default permissions
233                Note: only basic modes are supported, OS-specific modes are
234                ignored
235            dir_fd: If not `None`, the file descriptor of a directory,
236                with `file_path` being relative to this directory.
237
238        Returns:
239            A file descriptor.
240
241        Raises:
242            OSError: if the path cannot be found
243            ValueError: if invalid mode is given
244            NotImplementedError: if `os.O_EXCL` is used without `os.O_CREAT`
245        """
246        path = self._path_with_dir_fd(path, self.open, dir_fd)
247        if mode is None:
248            if self.filesystem.is_windows_fs:
249                mode = 0o666
250            else:
251                mode = 0o777 & ~self._umask()
252
253        has_directory_flag = (
254            hasattr(os, "O_DIRECTORY") and flags & os.O_DIRECTORY == os.O_DIRECTORY
255        )
256        if (
257            has_directory_flag
258            and self.filesystem.exists(path)
259            and not self.filesystem.isdir(path)
260        ):
261            raise OSError(errno.ENOTDIR, "path is not a directory", path)
262
263        has_follow_flag = (
264            hasattr(os, "O_NOFOLLOW") and flags & os.O_NOFOLLOW == os.O_NOFOLLOW
265        )
266        if has_follow_flag and self.filesystem.islink(path):
267            raise OSError(errno.ELOOP, "path is a symlink", path)
268
269        has_tmpfile_flag = (
270            hasattr(os, "O_TMPFILE") and flags & os.O_TMPFILE == os.O_TMPFILE
271        )
272        open_modes = _OpenModes(
273            must_exist=not flags & os.O_CREAT and not has_tmpfile_flag,
274            can_read=not flags & os.O_WRONLY,
275            can_write=flags & (os.O_RDWR | os.O_WRONLY) != 0,
276            truncate=flags & os.O_TRUNC != 0,
277            append=flags & os.O_APPEND != 0,
278            must_not_exist=flags & os.O_EXCL != 0,
279        )
280        if open_modes.must_not_exist and open_modes.must_exist:
281            raise NotImplementedError("O_EXCL without O_CREAT mode is not supported")
282        if has_tmpfile_flag:
283            # this is a workaround for tempfiles that do not have a filename
284            # as we do not support this directly, we just add a unique filename
285            # and set the file to delete on close
286            path = self.filesystem.joinpaths(
287                path, matching_string(path, str(uuid.uuid4()))
288            )
289
290        if not self.filesystem.is_windows_fs and self.filesystem.exists(path):
291            # handle opening directory - only allowed under Posix
292            # with read-only mode
293            obj = self.filesystem.resolve(path)
294            if isinstance(obj, FakeDirectory):
295                if (
296                    not open_modes.must_exist and not self.filesystem.is_macos
297                ) or open_modes.can_write:
298                    self.filesystem.raise_os_error(errno.EISDIR, path)
299                dir_wrapper = FakeDirWrapper(obj, path, self.filesystem)
300                file_des = self.filesystem.add_open_file(dir_wrapper)
301                dir_wrapper.filedes = file_des
302                return file_des
303
304        # low level open is always binary
305        str_flags = "b"
306        delete_on_close = has_tmpfile_flag
307        if hasattr(os, "O_TEMPORARY"):
308            delete_on_close = flags & os.O_TEMPORARY == os.O_TEMPORARY
309        fake_file = FakeFileOpen(
310            self.filesystem, delete_on_close=delete_on_close, raw_io=True
311        )(path, str_flags, open_modes=open_modes)
312        assert not isinstance(fake_file, StandardStreamWrapper)
313        if fake_file.file_object != self.filesystem.dev_null:
314            self.chmod(path, mode)
315        return fake_file.fileno()
316
317    def close(self, fd: int) -> None:
318        """Close a file descriptor.
319
320        Args:
321            fd: An integer file descriptor for the file object requested.
322
323        Raises:
324            OSError: bad file descriptor.
325            TypeError: if file descriptor is not an integer.
326        """
327        file_handle = self.filesystem.get_open_file(fd)
328        file_handle.close_fd(fd)
329
330    def dup(self, fd: int) -> int:
331        file_handle = self.filesystem.get_open_file(fd)
332        return self.filesystem.add_open_file(file_handle)
333
334    def dup2(self, fd: int, fd2: int, inheritable: bool = True) -> int:
335        if fd == fd2:
336            return fd
337        file_handle = self.filesystem.get_open_file(fd)
338        return self.filesystem.add_open_file(file_handle, fd2)
339
340    def read(self, fd: int, n: int) -> bytes:
341        """Read number of bytes from a file descriptor, returns bytes read.
342
343        Args:
344            fd: An integer file descriptor for the file object requested.
345            n: Number of bytes to read from file.
346
347        Returns:
348            Bytes read from file.
349
350        Raises:
351            OSError: bad file descriptor.
352            TypeError: if file descriptor is not an integer.
353        """
354        file_handle = self.filesystem.get_open_file(fd)
355        if isinstance(file_handle, FakeFileWrapper):
356            file_handle.raw_io = True
357        if isinstance(file_handle, FakeDirWrapper):
358            self.filesystem.raise_os_error(errno.EBADF, file_handle.file_path)
359        return file_handle.read(n)
360
361    def write(self, fd: int, contents: bytes) -> int:
362        """Write string to file descriptor, returns number of bytes written.
363
364        Args:
365            fd: An integer file descriptor for the file object requested.
366            contents: String of bytes to write to file.
367
368        Returns:
369            Number of bytes written.
370
371        Raises:
372            OSError: bad file descriptor.
373            TypeError: if file descriptor is not an integer.
374        """
375        file_handle = cast(FakeFileWrapper, self.filesystem.get_open_file(fd))
376        if isinstance(file_handle, FakeDirWrapper):
377            self.filesystem.raise_os_error(errno.EBADF, file_handle.file_path)
378
379        if isinstance(file_handle, FakePipeWrapper):
380            return file_handle.write(contents)
381
382        file_handle.raw_io = True
383        file_handle._sync_io()
384        file_handle.update_flush_pos()
385        file_handle.write(contents)
386        file_handle.flush()
387        return len(contents)
388
389    def lseek(self, fd: int, pos: int, whence: int):
390        file_handle = self.filesystem.get_open_file(fd)
391        if isinstance(file_handle, FakeFileWrapper):
392            file_handle.seek(pos, whence)
393        else:
394            raise OSError(errno.EBADF, "Bad file descriptor for fseek")
395
396    def pipe(self) -> Tuple[int, int]:
397        read_fd, write_fd = os.pipe()
398        read_wrapper = FakePipeWrapper(self.filesystem, read_fd, False)
399        file_des = self.filesystem.add_open_file(read_wrapper)
400        read_wrapper.filedes = file_des
401        write_wrapper = FakePipeWrapper(self.filesystem, write_fd, True)
402        file_des = self.filesystem.add_open_file(write_wrapper)
403        write_wrapper.filedes = file_des
404        return read_wrapper.filedes, write_wrapper.filedes
405
406    def fstat(self, fd: int) -> FakeStatResult:
407        """Return the os.stat-like tuple for the FakeFile object of file_des.
408
409        Args:
410            fd: The file descriptor of filesystem object to retrieve.
411
412        Returns:
413            The FakeStatResult object corresponding to entry_path.
414
415        Raises:
416            OSError: if the filesystem object doesn't exist.
417        """
418        # stat should return the tuple representing return value of os.stat
419        file_object = self.filesystem.get_open_file(fd).get_object()
420        assert isinstance(file_object, FakeFile)
421        return file_object.stat_result.copy()
422
423    def umask(self, mask: int) -> int:
424        """Change the current umask.
425
426        Args:
427            mask: (int) The new umask value.
428
429        Returns:
430            The old umask.
431
432        Raises:
433            TypeError: if new_mask is of an invalid type.
434        """
435        if not is_int_type(mask):
436            raise TypeError("an integer is required")
437        old_umask = self.filesystem.umask
438        self.filesystem.umask = mask
439        return old_umask
440
441    def chdir(self, path: AnyStr) -> None:
442        """Change current working directory to target directory.
443
444        Args:
445            path: The path to new current working directory.
446
447        Raises:
448            OSError: if user lacks permission to enter the argument directory
449                or if the target is not a directory.
450        """
451        try:
452            path = self.filesystem.resolve_path(path, allow_fd=True)
453        except OSError as exc:
454            if self.filesystem.is_macos and exc.errno == errno.EBADF:
455                raise OSError(errno.ENOTDIR, "Not a directory: " + str(path))
456            raise
457        self.filesystem.confirmdir(path)
458        directory = self.filesystem.resolve(path)
459        # A full implementation would check permissions all the way
460        # up the tree.
461        if not is_root() and not directory.has_permission(PERM_EXE):
462            self.filesystem.raise_os_error(errno.EACCES, directory.name)
463        self.filesystem.cwd = path  # type: ignore[assignment]
464
465    def getcwd(self) -> str:
466        """Return current working directory."""
467        return to_string(self.filesystem.cwd)
468
469    def getcwdb(self) -> bytes:
470        """Return current working directory as bytes."""
471        return to_bytes(self.filesystem.cwd)
472
473    def listdir(self, path: AnyStr) -> List[AnyStr]:
474        """Return a list of file names in target_directory.
475
476        Args:
477            path: Path to the target directory within the fake
478                filesystem.
479
480        Returns:
481            A list of file names within the target directory in arbitrary
482                order.
483
484        Raises:
485          OSError:  if the target is not a directory.
486        """
487        return self.filesystem.listdir(path)
488
489    XATTR_CREATE = 1
490    XATTR_REPLACE = 2
491
492    def getxattr(
493        self, path: AnyStr, attribute: AnyString, *, follow_symlinks: bool = True
494    ) -> Optional[bytes]:
495        """Return the value of the given extended filesystem attribute for
496        `path`.
497
498        Args:
499            path: File path, file descriptor or path-like object.
500            attribute: (str or bytes) The attribute name.
501            follow_symlinks: (bool) If True (the default), symlinks in the
502                path are traversed.
503
504        Returns:
505            The contents of the extended attribute as bytes or None if
506            the attribute does not exist.
507
508        Raises:
509            OSError: if the path does not exist.
510        """
511        if not self.filesystem.is_linux:
512            raise AttributeError("module 'os' has no attribute 'getxattr'")
513
514        if isinstance(attribute, bytes):
515            attribute = attribute.decode(sys.getfilesystemencoding())
516        file_obj = self.filesystem.resolve(path, follow_symlinks, allow_fd=True)
517        if attribute not in file_obj.xattr:
518            raise OSError(errno.ENODATA, "No data available", path)
519        return file_obj.xattr.get(attribute)
520
521    def listxattr(
522        self, path: Optional[AnyStr] = None, *, follow_symlinks: bool = True
523    ) -> List[str]:
524        """Return a list of the extended filesystem attributes on `path`.
525
526        Args:
527            path: File path, file descriptor or path-like object.
528               If None, the current directory is used.
529            follow_symlinks: (bool) If True (the default), symlinks in the
530                path are traversed.
531
532        Returns:
533            A list of all attribute names for the given path as str.
534
535        Raises:
536            OSError: if the path does not exist.
537        """
538        if not self.filesystem.is_linux:
539            raise AttributeError("module 'os' has no attribute 'listxattr'")
540
541        path_str = self.filesystem.cwd if path is None else path
542        file_obj = self.filesystem.resolve(
543            cast(AnyStr, path_str),  # pytype: disable=invalid-annotation
544            follow_symlinks,
545            allow_fd=True,
546        )
547        return list(file_obj.xattr.keys())
548
549    def removexattr(
550        self, path: AnyStr, attribute: AnyString, *, follow_symlinks: bool = True
551    ) -> None:
552        """Removes the extended filesystem attribute from `path`.
553
554        Args:
555            path: File path, file descriptor or path-like object
556            attribute: (str or bytes) The attribute name.
557            follow_symlinks: (bool) If True (the default), symlinks in the
558                path are traversed.
559
560        Raises:
561            OSError: if the path does not exist.
562        """
563        if not self.filesystem.is_linux:
564            raise AttributeError("module 'os' has no attribute 'removexattr'")
565
566        if isinstance(attribute, bytes):
567            attribute = attribute.decode(sys.getfilesystemencoding())
568        file_obj = self.filesystem.resolve(path, follow_symlinks, allow_fd=True)
569        if attribute in file_obj.xattr:
570            del file_obj.xattr[attribute]
571
572    def setxattr(
573        self,
574        path: AnyStr,
575        attribute: AnyString,
576        value: bytes,
577        flags: int = 0,
578        *,
579        follow_symlinks: bool = True,
580    ) -> None:
581        """Sets the value of the given extended filesystem attribute for
582        `path`.
583
584        Args:
585            path: File path, file descriptor or path-like object.
586            attribute: The attribute name (str or bytes).
587            value: (byte-like) The value to be set.
588            follow_symlinks: (bool) If True (the default), symlinks in the
589                path are traversed.
590
591        Raises:
592            OSError: if the path does not exist.
593            TypeError: if `value` is not a byte-like object.
594        """
595        if not self.filesystem.is_linux:
596            raise AttributeError("module 'os' has no attribute 'setxattr'")
597
598        if isinstance(attribute, bytes):
599            attribute = attribute.decode(sys.getfilesystemencoding())
600        if not is_byte_string(value):
601            raise TypeError("a bytes-like object is required")
602        file_obj = self.filesystem.resolve(path, follow_symlinks, allow_fd=True)
603        exists = attribute in file_obj.xattr
604        if exists and flags == self.XATTR_CREATE:
605            self.filesystem.raise_os_error(errno.ENODATA, file_obj.path)
606        if not exists and flags == self.XATTR_REPLACE:
607            self.filesystem.raise_os_error(errno.EEXIST, file_obj.path)
608        file_obj.xattr[attribute] = value
609
610    def scandir(self, path: str = ".") -> ScanDirIter:
611        """Return an iterator of DirEntry objects corresponding to the
612        entries in the directory given by path.
613
614        Args:
615            path: Path to the target directory within the fake filesystem.
616
617        Returns:
618            An iterator to an unsorted list of os.DirEntry objects for
619            each entry in path.
620
621        Raises:
622            OSError: if the target is not a directory.
623        """
624        return scandir(self.filesystem, path)
625
626    def walk(
627        self,
628        top: AnyStr,
629        topdown: bool = True,
630        onerror: Optional[bool] = None,
631        followlinks: bool = False,
632    ):
633        """Perform an os.walk operation over the fake filesystem.
634
635        Args:
636            top: The root directory from which to begin walk.
637            topdown: Determines whether to return the tuples with the root as
638                the first entry (`True`) or as the last, after all the child
639                directory tuples (`False`).
640          onerror: If not `None`, function which will be called to handle the
641                `os.error` instance provided when `os.listdir()` fails.
642          followlinks: If `True`, symbolic links are followed.
643
644        Yields:
645            (path, directories, nondirectories) for top and each of its
646            subdirectories.  See the documentation for the builtin os module
647            for further details.
648        """
649        return walk(self.filesystem, top, topdown, onerror, followlinks)
650
651    def readlink(self, path: AnyStr, dir_fd: Optional[int] = None) -> str:
652        """Read the target of a symlink.
653
654        Args:
655            path:  Symlink to read the target of.
656            dir_fd: If not `None`, the file descriptor of a directory,
657                with `path` being relative to this directory.
658
659        Returns:
660            the string representing the path to which the symbolic link points.
661
662        Raises:
663            TypeError: if `path` is None
664            OSError: (with errno=ENOENT) if path is not a valid path, or
665                     (with errno=EINVAL) if path is valid, but is not a symlink
666        """
667        path = self._path_with_dir_fd(path, self.readlink, dir_fd)
668        return self.filesystem.readlink(path)
669
670    def stat(
671        self,
672        path: AnyStr,
673        *,
674        dir_fd: Optional[int] = None,
675        follow_symlinks: bool = True,
676    ) -> FakeStatResult:
677        """Return the os.stat-like tuple for the FakeFile object of entry_path.
678
679        Args:
680            path:  path to filesystem object to retrieve.
681            dir_fd: (int) If not `None`, the file descriptor of a directory,
682                with `entry_path` being relative to this directory.
683            follow_symlinks: (bool) If `False` and `entry_path` points to a
684                symlink, the link itself is changed instead of the linked
685                object.
686
687        Returns:
688            The FakeStatResult object corresponding to entry_path.
689
690        Raises:
691            OSError: if the filesystem object doesn't exist.
692        """
693        path = self._path_with_dir_fd(path, self.stat, dir_fd)
694        return self.filesystem.stat(path, follow_symlinks)
695
696    def lstat(self, path: AnyStr, *, dir_fd: Optional[int] = None) -> FakeStatResult:
697        """Return the os.stat-like tuple for entry_path,
698        not following symlinks.
699
700        Args:
701            path:  path to filesystem object to retrieve.
702            dir_fd: If not `None`, the file descriptor of a directory, with
703                `path` being relative to this directory.
704
705        Returns:
706            the FakeStatResult object corresponding to `path`.
707
708        Raises:
709            OSError: if the filesystem object doesn't exist.
710        """
711        # stat should return the tuple representing return value of os.stat
712        path = self._path_with_dir_fd(path, self.lstat, dir_fd, check_supported=False)
713        return self.filesystem.stat(path, follow_symlinks=False)
714
715    def remove(self, path: AnyStr, dir_fd: Optional[int] = None) -> None:
716        """Remove the FakeFile object at the specified file path.
717
718        Args:
719            path: Path to file to be removed.
720            dir_fd: If not `None`, the file descriptor of a directory,
721                with `path` being relative to this directory.
722
723        Raises:
724            OSError: if path points to a directory.
725            OSError: if path does not exist.
726            OSError: if removal failed.
727        """
728        path = self._path_with_dir_fd(path, self.remove, dir_fd, check_supported=False)
729        self.filesystem.remove(path)
730
731    def unlink(self, path: AnyStr, *, dir_fd: Optional[int] = None) -> None:
732        """Remove the FakeFile object at the specified file path.
733
734        Args:
735            path: Path to file to be removed.
736            dir_fd: If not `None`, the file descriptor of a directory,
737                with `path` being relative to this directory.
738
739        Raises:
740            OSError: if path points to a directory.
741            OSError: if path does not exist.
742            OSError: if removal failed.
743        """
744        path = self._path_with_dir_fd(path, self.unlink, dir_fd)
745        self.filesystem.remove(path)
746
747    def rename(
748        self,
749        src: AnyStr,
750        dst: AnyStr,
751        *,
752        src_dir_fd: Optional[int] = None,
753        dst_dir_fd: Optional[int] = None,
754    ) -> None:
755        """Rename a FakeFile object at old_file_path to new_file_path,
756        preserving all properties.
757        Also replaces existing new_file_path object, if one existed
758        (Unix only).
759
760        Args:
761            src: Path to filesystem object to rename.
762            dst: Path to where the filesystem object will live
763                after this call.
764            src_dir_fd: If not `None`, the file descriptor of a directory,
765                with `src` being relative to this directory.
766            dst_dir_fd: If not `None`, the file descriptor of a directory,
767                with `dst` being relative to this directory.
768
769        Raises:
770            OSError: if old_file_path does not exist.
771            OSError: if new_file_path is an existing directory.
772            OSError: if new_file_path is an existing file (Windows only)
773            OSError: if new_file_path is an existing file and could not
774                be removed (Unix)
775            OSError: if `dirname(new_file)` does not exist
776            OSError: if the file would be moved to another filesystem
777                (e.g. mount point)
778        """
779        src = self._path_with_dir_fd(src, self.rename, src_dir_fd)
780        dst = self._path_with_dir_fd(dst, self.rename, dst_dir_fd)
781        self.filesystem.rename(src, dst)
782
783    def renames(self, old: AnyStr, new: AnyStr):
784        """Fakes `os.renames`, documentation taken from there.
785
786        Super-rename; create directories as necessary and delete any left
787        empty.  Works like rename, except creation of any intermediate
788        directories needed to make the new pathname good is attempted
789        first.  After the rename, directories corresponding to rightmost
790        path segments of the old name will be pruned until either the
791        whole path is consumed or a nonempty directory is found.
792
793        Note: this function can fail with the new directory structure made
794        if you lack permissions needed to unlink the leaf directory or
795        file.
796
797        """
798        head, tail = self.filesystem.splitpath(new)
799        if head and tail and not self.filesystem.exists(head):
800            self.makedirs(head)
801        self.rename(old, new)
802        head, tail = self.filesystem.splitpath(old)
803        if head and tail:
804            try:
805                self.removedirs(head)
806            except OSError:
807                pass
808
809    def replace(
810        self,
811        src: AnyStr,
812        dst: AnyStr,
813        *,
814        src_dir_fd: Optional[int] = None,
815        dst_dir_fd: Optional[int] = None,
816    ) -> None:
817        """Renames a FakeFile object at old_file_path to new_file_path,
818        preserving all properties.
819        Also replaces existing new_file_path object, if one existed.
820
821        Arg
822            src: Path to filesystem object to rename.
823            dst: Path to where the filesystem object will live
824                after this call.
825            src_dir_fd: If not `None`, the file descriptor of a directory,
826                with `src` being relative to this directory.
827            dst_dir_fd: If not `None`, the file descriptor of a directory,
828                with `dst` being relative to this directory.
829
830        Raises:
831            OSError: if old_file_path does not exist.
832            OSError: if new_file_path is an existing directory.
833            OSError: if new_file_path is an existing file and could
834                not be removed
835            OSError: if `dirname(new_file)` does not exist
836            OSError: if the file would be moved to another filesystem
837                (e.g. mount point)
838        """
839        src = self._path_with_dir_fd(
840            src, self.rename, src_dir_fd, check_supported=False
841        )
842        dst = self._path_with_dir_fd(
843            dst, self.rename, dst_dir_fd, check_supported=False
844        )
845        self.filesystem.rename(src, dst, force_replace=True)
846
847    def rmdir(self, path: AnyStr, *, dir_fd: Optional[int] = None) -> None:
848        """Remove a leaf Fake directory.
849
850        Args:
851            path: (str) Name of directory to remove.
852            dir_fd: If not `None`, the file descriptor of a directory,
853                with `path` being relative to this directory.
854
855        Raises:
856            OSError: if `path` does not exist or is not a directory,
857            or as per FakeFilesystem.remove_object. Cannot remove '.'.
858        """
859        path = self._path_with_dir_fd(path, self.rmdir, dir_fd)
860        self.filesystem.rmdir(path)
861
862    def removedirs(self, name: AnyStr) -> None:
863        """Remove a leaf fake directory and all empty intermediate ones.
864
865        Args:
866            name: the directory to be removed.
867
868        Raises:
869            OSError: if target_directory does not exist or is not a directory.
870            OSError: if target_directory is not empty.
871        """
872        name = self.filesystem.absnormpath(name)
873        directory = self.filesystem.confirmdir(name)
874        if directory.entries:
875            self.filesystem.raise_os_error(errno.ENOTEMPTY, self.path.basename(name))
876        else:
877            self.rmdir(name)
878        head, tail = self.path.split(name)
879        if not tail:
880            head, tail = self.path.split(head)
881        while head and tail:
882            head_dir = self.filesystem.confirmdir(head)
883            if head_dir.entries:
884                break
885            # only the top-level dir may not be a symlink
886            self.filesystem.rmdir(head, allow_symlink=True)
887            head, tail = self.path.split(head)
888
889    def mkdir(
890        self, path: AnyStr, mode: int = PERM_DEF, *, dir_fd: Optional[int] = None
891    ) -> None:
892        """Create a leaf Fake directory.
893
894        Args:
895            path: (str) Name of directory to create.
896                Relative paths are assumed to be relative to '/'.
897            mode: (int) Mode to create directory with.  This argument defaults
898                to 0o777.  The umask is applied to this mode.
899            dir_fd: If not `None`, the file descriptor of a directory,
900                with `path` being relative to this directory.
901
902        Raises:
903            OSError: if the directory name is invalid or parent directory is
904                read only or as per FakeFilesystem.add_object.
905        """
906        path = self._path_with_dir_fd(path, self.mkdir, dir_fd)
907        try:
908            self.filesystem.makedir(path, mode)
909        except OSError as e:
910            if e.errno == errno.EACCES:
911                self.filesystem.raise_os_error(e.errno, path)
912            raise
913
914    def makedirs(
915        self, name: AnyStr, mode: int = PERM_DEF, exist_ok: Optional[bool] = None
916    ) -> None:
917        """Create a leaf Fake directory + create any non-existent parent dirs.
918
919        Args:
920            name: (str) Name of directory to create.
921            mode: (int) Mode to create directory (and any necessary parent
922                directories) with. This argument defaults to 0o777.
923                The umask is applied to this mode.
924            exist_ok: (boolean) If exist_ok is False (the default), an OSError
925                is raised if the target directory already exists.
926
927        Raises:
928            OSError: if the directory already exists and exist_ok=False, or as
929                per :py:meth:`FakeFilesystem.create_dir`.
930        """
931        if exist_ok is None:
932            exist_ok = False
933
934        # copied and adapted from real implementation in os.py (Python 3.12)
935        head, tail = self.filesystem.splitpath(name)
936        if not tail:
937            head, tail = self.filesystem.splitpath(head)
938        if head and tail and not self.filesystem.exists(head):
939            try:
940                self.makedirs(head, exist_ok=exist_ok)
941            except FileExistsError:
942                pass
943            cdir = self.filesystem.cwd
944            if isinstance(tail, bytes):
945                if tail == bytes(cdir, "ASCII"):
946                    return
947            elif tail == cdir:
948                return
949        try:
950            self.mkdir(name, mode)
951        except OSError:
952            if not exist_ok or not self.filesystem.isdir(name):
953                raise
954
955    def _path_with_dir_fd(
956        self,
957        path: AnyStr,
958        fct: Callable,
959        dir_fd: Optional[int],
960        check_supported: bool = True,
961    ) -> AnyStr:
962        """Return the path considering dir_fd. Raise on invalid parameters."""
963        try:
964            path = make_string_path(path)
965        except TypeError:
966            # the error is handled later
967            path = path
968        if dir_fd is not None:
969            # check if fd is supported for the built-in real function
970            if check_supported and (fct not in self.supports_dir_fd):
971                raise NotImplementedError("dir_fd unavailable on this platform")
972            if isinstance(path, int):
973                raise ValueError(
974                    "%s: Can't specify dir_fd without matching path_str" % fct.__name__
975                )
976            if not self.path.isabs(path):
977                open_file = self.filesystem.get_open_file(dir_fd)
978                return self.path.join(  # type: ignore[type-var, return-value]
979                    cast(FakeFile, open_file.get_object()).path, path
980                )
981        return path
982
983    def truncate(self, path: AnyStr, length: int) -> None:
984        """Truncate the file corresponding to path, so that it is
985         length bytes in size. If length is larger than the current size,
986         the file is filled up with zero bytes.
987
988        Args:
989            path: (str or int) Path to the file, or an integer file
990                descriptor for the file object.
991            length: (int) Length of the file after truncating it.
992
993        Raises:
994            OSError: if the file does not exist or the file descriptor is
995                invalid.
996        """
997        file_object = self.filesystem.resolve(path, allow_fd=True)
998        file_object.size = length
999
1000    def ftruncate(self, fd: int, length: int) -> None:
1001        """Truncate the file corresponding to fd, so that it is
1002         length bytes in size. If length is larger than the current size,
1003         the file is filled up with zero bytes.
1004
1005        Args:
1006            fd: (int) File descriptor for the file object.
1007            length: (int) Maximum length of the file after truncating it.
1008
1009        Raises:
1010            OSError: if the file descriptor is invalid
1011        """
1012        file_object = self.filesystem.get_open_file(fd).get_object()
1013        if isinstance(file_object, FakeFileWrapper):
1014            file_object.size = length
1015        else:
1016            raise OSError(errno.EBADF, "Invalid file descriptor")
1017
1018    def access(
1019        self,
1020        path: AnyStr,
1021        mode: int,
1022        *,
1023        dir_fd: Optional[int] = None,
1024        effective_ids: bool = False,
1025        follow_symlinks: bool = True,
1026    ) -> bool:
1027        """Check if a file exists and has the specified permissions.
1028
1029        Args:
1030            path: (str) Path to the file.
1031            mode: (int) Permissions represented as a bitwise-OR combination of
1032                os.F_OK, os.R_OK, os.W_OK, and os.X_OK.
1033            dir_fd: If not `None`, the file descriptor of a directory, with
1034                `path` being relative to this directory.
1035            effective_ids: (bool) Unused. Only here to match the signature.
1036            follow_symlinks: (bool) If `False` and `path` points to a symlink,
1037                the link itself is queried instead of the linked object.
1038
1039        Returns:
1040            bool, `True` if file is accessible, `False` otherwise.
1041        """
1042        if effective_ids and self.filesystem.is_windows_fs:
1043            raise NotImplementedError(
1044                "access: effective_ids unavailable on this platform"
1045            )
1046        path = self._path_with_dir_fd(path, self.access, dir_fd)
1047        try:
1048            stat_result = self.stat(path, follow_symlinks=follow_symlinks)
1049        except OSError as os_error:
1050            if os_error.errno == errno.ENOENT:
1051                return False
1052            raise
1053        if is_root():
1054            mode &= ~os.W_OK
1055        return (mode & ((stat_result.st_mode >> 6) & 7)) == mode
1056
1057    def fchmod(
1058        self,
1059        fd: int,
1060        mode: int,
1061    ) -> None:
1062        """Change the permissions of an open file as encoded in integer mode.
1063
1064        Args:
1065            fd: (int) File descriptor.
1066            mode: (int) Permissions.
1067        """
1068        if self.filesystem.is_windows_fs and sys.version_info < (3, 13):
1069            raise AttributeError(
1070                "module 'os' has no attribute 'fchmod'. Did you mean: 'chmod'?"
1071            )
1072        self.filesystem.chmod(fd, mode)
1073
1074    def chmod(
1075        self,
1076        path: AnyStr,
1077        mode: int,
1078        *,
1079        dir_fd: Optional[int] = None,
1080        follow_symlinks: bool = True,
1081    ) -> None:
1082        """Change the permissions of a file as encoded in integer mode.
1083
1084        Args:
1085            path: (str) Path to the file.
1086            mode: (int) Permissions.
1087            dir_fd: If not `None`, the file descriptor of a directory, with
1088                `path` being relative to this directory.
1089            follow_symlinks: (bool) If `False` and `path` points to a symlink,
1090                the link itself is queried instead of the linked object.
1091        """
1092        if not follow_symlinks and (
1093            self.chmod not in self.supports_follow_symlinks or IS_PYPY
1094        ):
1095            raise NotImplementedError(
1096                "`follow_symlinks` for chmod() is not available on this system"
1097            )
1098        path = self._path_with_dir_fd(path, self.chmod, dir_fd)
1099        self.filesystem.chmod(path, mode, follow_symlinks)
1100
1101    def lchmod(self, path: AnyStr, mode: int) -> None:
1102        """Change the permissions of a file as encoded in integer mode.
1103        If the file is a link, the permissions of the link are changed.
1104
1105        Args:
1106          path: (str) Path to the file.
1107          mode: (int) Permissions.
1108        """
1109        if self.filesystem.is_windows_fs:
1110            raise NameError("name 'lchmod' is not defined")
1111        self.filesystem.chmod(path, mode, follow_symlinks=False)
1112
1113    def utime(
1114        self,
1115        path: AnyStr,
1116        times: Optional[Tuple[Union[int, float], Union[int, float]]] = None,
1117        ns: Optional[Tuple[int, int]] = None,
1118        dir_fd: Optional[int] = None,
1119        follow_symlinks: bool = True,
1120    ) -> None:
1121        """Change the access and modified times of a file.
1122
1123        Args:
1124            path: (str) Path to the file.
1125            times: 2-tuple of int or float numbers, of the form (atime, mtime)
1126                which is used to set the access and modified times in seconds.
1127                If None, both times are set to the current time.
1128            ns: 2-tuple of int numbers, of the form (atime, mtime)  which is
1129                used to set the access and modified times in nanoseconds.
1130                If None, both times are set to the current time.
1131            dir_fd: If not `None`, the file descriptor of a directory,
1132                with `path` being relative to this directory.
1133            follow_symlinks: (bool) If `False` and `path` points to a symlink,
1134                the link itself is queried instead of the linked object.
1135
1136            Raises:
1137                TypeError: If anything other than the expected types is
1138                    specified in the passed `times` or `ns` tuple,
1139                    or if the tuple length is not equal to 2.
1140                ValueError: If both times and ns are specified.
1141        """
1142        path = self._path_with_dir_fd(path, self.utime, dir_fd)
1143        self.filesystem.utime(path, times=times, ns=ns, follow_symlinks=follow_symlinks)
1144
1145    def chown(
1146        self,
1147        path: AnyStr,
1148        uid: int,
1149        gid: int,
1150        *,
1151        dir_fd: Optional[int] = None,
1152        follow_symlinks: bool = True,
1153    ) -> None:
1154        """Set ownership of a faked file.
1155
1156        Args:
1157            path: (str) Path to the file or directory.
1158            uid: (int) Numeric uid to set the file or directory to.
1159            gid: (int) Numeric gid to set the file or directory to.
1160            dir_fd: (int) If not `None`, the file descriptor of a directory,
1161                with `path` being relative to this directory.
1162            follow_symlinks: (bool) If `False` and path points to a symlink,
1163                the link itself is changed instead of the linked object.
1164
1165        Raises:
1166            OSError: if path does not exist.
1167
1168        `None` is also allowed for `uid` and `gid`.  This permits `os.rename`
1169        to use `os.chown` even when the source file `uid` and `gid` are
1170        `None` (unset).
1171        """
1172        path = self._path_with_dir_fd(path, self.chown, dir_fd)
1173        file_object = self.filesystem.resolve(path, follow_symlinks, allow_fd=True)
1174        if not isinstance(uid, int) or not isinstance(gid, int):
1175            raise TypeError("An integer is required")
1176        if uid != -1:
1177            file_object.st_uid = uid
1178        if gid != -1:
1179            file_object.st_gid = gid
1180
1181    def mknod(
1182        self,
1183        path: AnyStr,
1184        mode: Optional[int] = None,
1185        device: int = 0,
1186        *,
1187        dir_fd: Optional[int] = None,
1188    ) -> None:
1189        """Create a filesystem node named 'filename'.
1190
1191        Does not support device special files or named pipes as the real os
1192        module does.
1193
1194        Args:
1195            path: (str) Name of the file to create
1196            mode: (int) Permissions to use and type of file to be created.
1197                Default permissions are 0o666.  Only the stat.S_IFREG file type
1198                is supported by the fake implementation.  The umask is applied
1199                to this mode.
1200            device: not supported in fake implementation
1201            dir_fd: If not `None`, the file descriptor of a directory,
1202                with `path` being relative to this directory.
1203
1204        Raises:
1205          OSError: if called with unsupported options or the file can not be
1206          created.
1207        """
1208        if self.filesystem.is_windows_fs:
1209            raise AttributeError("module 'os' has no attribute 'mknode'")
1210        if mode is None:
1211            # note that a default value of 0o600 without a device type is
1212            # documented - this is not how it seems to work
1213            mode = S_IFREG | 0o600
1214        if device or not mode & S_IFREG and not is_root():
1215            self.filesystem.raise_os_error(errno.EPERM)
1216
1217        path = self._path_with_dir_fd(path, self.mknod, dir_fd)
1218        head, tail = self.path.split(path)
1219        if not tail:
1220            if self.filesystem.exists(head, check_link=True):
1221                self.filesystem.raise_os_error(errno.EEXIST, path)
1222            self.filesystem.raise_os_error(errno.ENOENT, path)
1223        if tail in (matching_string(tail, "."), matching_string(tail, "..")):
1224            self.filesystem.raise_os_error(errno.ENOENT, path)
1225        if self.filesystem.exists(path, check_link=True):
1226            self.filesystem.raise_os_error(errno.EEXIST, path)
1227        self.filesystem.add_object(
1228            head,
1229            FakeFile(tail, mode & ~self.filesystem.umask, filesystem=self.filesystem),
1230        )
1231
1232    def symlink(
1233        self,
1234        src: AnyStr,
1235        dst: AnyStr,
1236        target_is_directory: bool = False,
1237        *,
1238        dir_fd: Optional[int] = None,
1239    ) -> None:
1240        """Creates the specified symlink, pointed at the specified link target.
1241
1242        Args:
1243            src: The target of the symlink.
1244            dst: Path to the symlink to create.
1245            target_is_directory: Currently ignored.
1246            dir_fd: If not `None`, the file descriptor of a directory,
1247                with `dst` being relative to this directory.
1248
1249        Raises:
1250            OSError:  if the file already exists.
1251        """
1252        dst = self._path_with_dir_fd(dst, self.symlink, dir_fd)
1253        self.filesystem.create_symlink(dst, src, create_missing_dirs=False)
1254
1255    def link(
1256        self,
1257        src: AnyStr,
1258        dst: AnyStr,
1259        *,
1260        src_dir_fd: Optional[int] = None,
1261        dst_dir_fd: Optional[int] = None,
1262        follow_symlinks: Optional[bool] = None,
1263    ) -> None:
1264        """Create a hard link at dst, pointing at src.
1265
1266        Args:
1267            src: An existing path to the target file.
1268            dst: The destination path to create a new link at.
1269            src_dir_fd: If not `None`, the file descriptor of a directory,
1270                with `src` being relative to this directory.
1271            dst_dir_fd: If not `None`, the file descriptor of a directory,
1272                with `dst` being relative to this directory.
1273            follow_symlinks: (bool) If True (the default), symlinks in the
1274                path are traversed.
1275
1276        Raises:
1277            OSError:  if something already exists at new_path.
1278            OSError:  if the parent directory doesn't exist.
1279        """
1280        if IS_PYPY and follow_symlinks is not None:
1281            raise OSError(errno.EINVAL, "Invalid argument: follow_symlinks")
1282        if follow_symlinks is None:
1283            follow_symlinks = True
1284
1285        src = self._path_with_dir_fd(src, self.link, src_dir_fd)
1286        dst = self._path_with_dir_fd(dst, self.link, dst_dir_fd)
1287        self.filesystem.link(src, dst, follow_symlinks=follow_symlinks)
1288
1289    def fsync(self, fd: int) -> None:
1290        """Perform fsync for a fake file (in other words, do nothing).
1291
1292        Args:
1293            fd: The file descriptor of the open file.
1294
1295        Raises:
1296            OSError: file_des is an invalid file descriptor.
1297            TypeError: file_des is not an integer.
1298        """
1299        # Throw an error if file_des isn't valid
1300        if 0 <= fd < NR_STD_STREAMS:
1301            self.filesystem.raise_os_error(errno.EINVAL)
1302        file_object = cast(FakeFileWrapper, self.filesystem.get_open_file(fd))
1303        if self.filesystem.is_windows_fs:
1304            if not hasattr(file_object, "allow_update") or not file_object.allow_update:
1305                self.filesystem.raise_os_error(errno.EBADF, file_object.file_path)
1306
1307    def fdatasync(self, fd: int) -> None:
1308        """Perform fdatasync for a fake file (in other words, do nothing).
1309
1310        Args:
1311            fd: The file descriptor of the open file.
1312
1313        Raises:
1314            OSError: `fd` is an invalid file descriptor.
1315            TypeError: `fd` is not an integer.
1316        """
1317        if self.filesystem.is_windows_fs or self.filesystem.is_macos:
1318            raise AttributeError("module 'os' has no attribute 'fdatasync'")
1319        # Throw an error if file_des isn't valid
1320        if 0 <= fd < NR_STD_STREAMS:
1321            self.filesystem.raise_os_error(errno.EINVAL)
1322        self.filesystem.get_open_file(fd)
1323
1324    def sendfile(self, fd_out: int, fd_in: int, offset: int, count: int) -> int:
1325        """Copy count bytes from file descriptor fd_in to file descriptor
1326        fd_out starting at offset.
1327
1328        Args:
1329            fd_out: The file descriptor of the destination file.
1330            fd_in: The file descriptor of the source file.
1331            offset: The offset in bytes where to start the copy in the
1332                source file. If `None` (Linux only), copying is started at
1333                the current position, and the position is updated.
1334            count: The number of bytes to copy. If 0, all remaining bytes
1335                are copied (MacOs only).
1336
1337        Raises:
1338            OSError: If `fd_in` or `fd_out` is an invalid file descriptor.
1339            TypeError: If `fd_in` or `fd_out` is not an integer.
1340            TypeError: If `offset` is None under MacOs.
1341        """
1342        if self.filesystem.is_windows_fs:
1343            raise AttributeError("module 'os' has no attribute 'sendfile'")
1344        if 0 <= fd_in < NR_STD_STREAMS:
1345            self.filesystem.raise_os_error(errno.EINVAL)
1346        if 0 <= fd_out < NR_STD_STREAMS:
1347            self.filesystem.raise_os_error(errno.EINVAL)
1348        source = cast(FakeFileWrapper, self.filesystem.get_open_file(fd_in))
1349        dest = cast(FakeFileWrapper, self.filesystem.get_open_file(fd_out))
1350        if self.filesystem.is_macos:
1351            if dest.get_object().stat_result.st_mode & 0o777000 != S_IFSOCK:
1352                raise OSError("Socket operation on non-socket")
1353        if offset is None:
1354            if self.filesystem.is_macos:
1355                raise TypeError("None is not a valid offset")
1356            contents = source.read(count)
1357        else:
1358            position = source.tell()
1359            source.seek(offset)
1360            if count == 0 and self.filesystem.is_macos:
1361                contents = source.read()
1362            else:
1363                contents = source.read(count)
1364            source.seek(position)
1365        if contents:
1366            written = dest.write(contents)
1367            dest.flush()
1368            return written
1369        return 0
1370
1371    def getuid(self) -> int:
1372        """Returns the user id set in the fake filesystem.
1373        If not changed using ``set_uid``, this is the uid of the real system.
1374        """
1375        if self.filesystem.is_windows_fs:
1376            raise NameError("name 'getuid' is not defined")
1377        return get_uid()
1378
1379    def getgid(self) -> int:
1380        """Returns the group id set in the fake filesystem.
1381        If not changed using ``set_gid``, this is the gid of the real system.
1382        """
1383        if self.filesystem.is_windows_fs:
1384            raise NameError("name 'getgid' is not defined")
1385        return get_gid()
1386
1387    def fake_functions(self, original_functions) -> Set:
1388        functions = set()
1389        for fn in original_functions:
1390            if hasattr(self, fn.__name__):
1391                functions.add(getattr(self, fn.__name__))
1392            else:
1393                functions.add(fn)
1394        return functions
1395
1396    @property
1397    def supports_follow_symlinks(self) -> Set[Callable]:
1398        if self._supports_follow_symlinks is None:
1399            self._supports_follow_symlinks = self.fake_functions(
1400                self.os_module.supports_follow_symlinks
1401            )
1402        return self._supports_follow_symlinks
1403
1404    @property
1405    def supports_dir_fd(self) -> Set[Callable]:
1406        if self._supports_dir_fd is None:
1407            self._supports_dir_fd = self.fake_functions(self.os_module.supports_dir_fd)
1408        return self._supports_dir_fd
1409
1410    @property
1411    def supports_fd(self) -> Set[Callable]:
1412        if self._supports_fd is None:
1413            self._supports_fd = self.fake_functions(self.os_module.supports_fd)
1414        return self._supports_fd
1415
1416    @property
1417    def supports_effective_ids(self) -> Set[Callable]:
1418        if self._supports_effective_ids is None:
1419            self._supports_effective_ids = self.fake_functions(
1420                self.os_module.supports_effective_ids
1421            )
1422        return self._supports_effective_ids
1423
1424    def __getattr__(self, name: str) -> Any:
1425        """Forwards any unfaked calls to the standard os module."""
1426        return getattr(self.os_module, name)
1427
1428
1429def handle_original_call(f: Callable) -> Callable:
1430    """Decorator used for real pathlib Path methods to ensure that
1431    real os functions instead of faked ones are used.
1432    Applied to all non-private methods of `FakeOsModule`."""
1433
1434    @functools.wraps(f)
1435    def wrapped(*args, **kwargs):
1436        should_use_original = FakeOsModule.use_original
1437
1438        if not should_use_original and args:
1439            self = args[0]
1440            fs: FakeFilesystem = self.filesystem
1441            if self.filesystem.patcher:
1442                skip_names = fs.patcher.skip_names
1443                if is_called_from_skipped_module(
1444                    skip_names=skip_names,
1445                    case_sensitive=fs.is_case_sensitive,
1446                ):
1447                    should_use_original = True
1448
1449        if should_use_original:
1450            # remove the `self` argument for FakeOsModule methods
1451            if args and isinstance(args[0], FakeOsModule):
1452                args = args[1:]
1453            return getattr(os, f.__name__)(*args, **kwargs)
1454
1455        return f(*args, **kwargs)
1456
1457    return wrapped
1458
1459
1460for name, fn in inspect.getmembers(FakeOsModule, inspect.isfunction):
1461    if not fn.__name__.startswith("_"):
1462        setattr(FakeOsModule, name, handle_original_call(fn))
1463
1464
1465@contextmanager
1466def use_original_os():
1467    """Temporarily use original os functions instead of faked ones.
1468    Used to ensure that skipped modules do not use faked calls.
1469    """
1470    try:
1471        FakeOsModule.use_original = True
1472        yield
1473    finally:
1474        FakeOsModule.use_original = False
1475