Skip to content

Commit 0466bbd

Browse files
committed
Add support for O_DIRECTORY and O_NOFOLLOW flags
- flags are considered in os.open if present
1 parent c4edbcb commit 0466bbd

File tree

3 files changed

+52
-1
lines changed

3 files changed

+52
-1
lines changed

CHANGES.md

+4
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ The released versions correspond to PyPI releases.
2323
* fixed handling of `dirfd` in `os.symlink` (see [#968](../../issues/968))
2424
* add missing `follow_symlink` argument to `os.link` (see [#973](../../issues/973))
2525

26+
### Enhancements
27+
* added support for `O_NOFOLLOW` and `O_DIRECTORY` flags in `os.open`
28+
(see [#972](../../issues/972) and [#974](../../issues/974))
29+
2630
## [Version 5.3.5](https://pypi.python.org/pypi/pyfakefs/5.3.5) (2024-01-30)
2731
Fixes a regression.
2832

pyfakefs/fake_os.py

+12
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,18 @@ def open(
247247
else:
248248
mode = 0o777 & ~self._umask()
249249

250+
has_directory_flag = (
251+
hasattr(os, "O_DIRECTORY") and flags & os.O_DIRECTORY == os.O_DIRECTORY
252+
)
253+
if has_directory_flag and not self.filesystem.isdir(path):
254+
raise OSError(errno.ENOTDIR, "path is not a directory", path)
255+
256+
has_follow_flag = (
257+
hasattr(os, "O_NOFOLLOW") and flags & os.O_NOFOLLOW == os.O_NOFOLLOW
258+
)
259+
if has_follow_flag and self.filesystem.islink(path):
260+
raise OSError(errno.ELOOP, "path is a symlink", path)
261+
250262
has_tmpfile_flag = (
251263
hasattr(os, "O_TMPFILE") and flags & os.O_TMPFILE == os.O_TMPFILE
252264
)

pyfakefs/tests/fake_os_test.py

+36-1
Original file line numberDiff line numberDiff line change
@@ -565,6 +565,42 @@ def test_open_raises_with_trailing_separator_windows(self):
565565
self.check_windows_only()
566566
self.check_open_raises_with_trailing_separator(errno.EINVAL)
567567

568+
@unittest.skipIf(not hasattr(os, "O_DIRECTORY"), "opening directory not supported")
569+
def test_open_raises_if_not_dir(self):
570+
self.check_posix_only()
571+
file_path = self.make_path("file.txt")
572+
self.create_file(file_path, contents="foo")
573+
with self.assertRaises(NotADirectoryError):
574+
self.os.open(file_path, os.O_RDONLY | os.O_DIRECTORY)
575+
dir_path = self.make_path("dir")
576+
self.create_dir(dir_path)
577+
with self.assertRaises(IsADirectoryError):
578+
self.os.open(dir_path, os.O_RDWR | os.O_DIRECTORY)
579+
580+
@unittest.skipIf(not hasattr(os, "O_NOFOLLOW"), "NOFOLLOW attribute not supported")
581+
def test_open_nofollow_symlink_raises(self):
582+
self.skip_if_symlink_not_supported()
583+
file_path = self.make_path("file.txt")
584+
self.create_file(file_path, contents="foo")
585+
link_path = self.make_path("link")
586+
self.create_symlink(link_path, file_path)
587+
with self.assertRaises(OSError) as cm:
588+
self.os.open(link_path, os.O_RDONLY | os.O_NOFOLLOW)
589+
assert cm.exception.errno == errno.ELOOP
590+
591+
@unittest.skipIf(not hasattr(os, "O_NOFOLLOW"), "NOFOLLOW attribute not supported")
592+
def test_open_nofollow_symlink_as_parent_works(self):
593+
self.skip_if_symlink_not_supported()
594+
dir_path = self.make_path("dir")
595+
self.create_dir(dir_path)
596+
link_path = self.make_path("link")
597+
self.create_symlink(link_path, dir_path)
598+
file_path = self.os.path.join(link_path, "file.txt")
599+
self.create_file(file_path, contents="foo")
600+
fd = self.os.open(file_path, os.O_RDONLY | os.O_NOFOLLOW)
601+
self.assertGreater(fd, 0)
602+
self.os.close(fd)
603+
568604
def test_lexists_with_trailing_separator_linux_windows(self):
569605
self.check_linux_and_windows()
570606
self.skip_if_symlink_not_supported()
@@ -5298,7 +5334,6 @@ def test_scandir_stat_nlink(self):
52985334
self.assertEqual(1, self.os.stat(self.file_path).st_nlink)
52995335

53005336
@unittest.skipIf(not hasattr(os, "O_DIRECTORY"), "opening directory not supported")
5301-
@unittest.skipIf(sys.version_info < (3, 7), "fd not supported for scandir")
53025337
def test_scandir_with_fd(self):
53035338
# regression test for #723
53045339
temp_dir = self.make_path("tmp", "dir")

0 commit comments

Comments
 (0)