summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--linuxnamespaces/atlocation.py17
-rw-r--r--tests/test_atlocation.py20
2 files changed, 36 insertions, 1 deletions
diff --git a/linuxnamespaces/atlocation.py b/linuxnamespaces/atlocation.py
index 6aada55..8da5982 100644
--- a/linuxnamespaces/atlocation.py
+++ b/linuxnamespaces/atlocation.py
@@ -9,6 +9,7 @@ code for doing so.
import enum
import errno
+import fcntl
import os
import os.path
import pathlib
@@ -102,6 +103,22 @@ class AtLocation:
os.close(self.fd)
self.fd = AT_FDCWD
+ def as_emptypath(self, inheritable: bool = True) -> "AtLocation":
+ """Return a new AtLocation with flag AT_EMPTY_PATH with a new file
+ descriptor. If self already is an empty path, its fd is duplicated. In
+ all cases, the caller is responsible for closing the result object.
+ """
+ if self.flags & AtFlags.AT_EMPTY_PATH:
+ newfd = fcntl.fcntl(
+ self.fd,
+ fcntl.F_DUPFD if inheritable else fcntl.F_DUPFD_CLOEXEC,
+ 0,
+ )
+ return AtLocation(newfd, flags=self.flags)
+ return AtLocation(
+ self.open(flags=os.O_PATH | (0 if inheritable else os.O_CLOEXEC))
+ )
+
def nosymfollow(self) -> "AtLocation":
"""Return a copy with the AT_SYMLINK_NOFOLLOW set."""
return AtLocation(
diff --git a/tests/test_atlocation.py b/tests/test_atlocation.py
index 9836f3b..5d7286a 100644
--- a/tests/test_atlocation.py
+++ b/tests/test_atlocation.py
@@ -80,7 +80,7 @@ class AtLocationTest(unittest.TestCase):
def create_all(
self, skip: typing.Container[str] = ()
) -> typing.Iterator[tuple[str, typing.ContextManager[AtLocation]]]:
- """Create various AtLocation objects referring to fils, directories
+ """Create various AtLocation objects referring to files, directories
and other things in various ways.
"""
for loctype in ("relative", "absolute", "emptypath", "withfd"):
@@ -146,6 +146,24 @@ class AtLocationTest(unittest.TestCase):
self.assertEqual(atloc.access(os.R_OK), should_exist)
self.assertEqual(atloc.exists(), should_exist)
+ def test_as_emptypath(self) -> None:
+ atloc = AtLocation(self.tempdir)
+ self.assertFalse(atloc.flags & linuxnamespaces.AtFlags.AT_EMPTY_PATH)
+ statres = atloc.stat()
+ atloc_ep = self.enterContext(atloc.as_emptypath())
+ self.assertTrue(atloc_ep.flags & linuxnamespaces.AtFlags.AT_EMPTY_PATH)
+ self.assertGreaterEqual(atloc_ep.fd, 0)
+ self.assertEqual(atloc_ep.location, "")
+ self.assertEqual(atloc_ep.stat().st_ino, statres.st_ino)
+ atloc_dup = self.enterContext(atloc_ep.as_emptypath())
+ self.assertTrue(
+ atloc_dup.flags & linuxnamespaces.AtFlags.AT_EMPTY_PATH
+ )
+ self.assertGreaterEqual(atloc_ep.fd, 0)
+ self.assertNotEqual(atloc_dup.fd, atloc_ep.fd)
+ self.assertEqual(atloc_dup.location, "")
+ self.assertEqual(atloc_dup.stat().st_ino, statres.st_ino)
+
@atloc_subtest(skip=("absent", "file", "symlink"))
def test_join_mkdir(self, _: str, atloc: AtLocation) -> None:
subdir = atloc / "subdir"