summaryrefslogtreecommitdiff
path: root/linuxnamespaces/filedescriptor.py
diff options
context:
space:
mode:
Diffstat (limited to 'linuxnamespaces/filedescriptor.py')
-rw-r--r--linuxnamespaces/filedescriptor.py25
1 files changed, 22 insertions, 3 deletions
diff --git a/linuxnamespaces/filedescriptor.py b/linuxnamespaces/filedescriptor.py
index 2cf8442..d11d4d6 100644
--- a/linuxnamespaces/filedescriptor.py
+++ b/linuxnamespaces/filedescriptor.py
@@ -8,11 +8,28 @@ import os
import typing
+@typing.runtime_checkable
+class HasFileno(typing.Protocol):
+ def fileno(self) -> int:
+ ...
+
+
+FileDescriptorLike = int | HasFileno
+
+
class FileDescriptor(int):
"""Type tag for integers that represent file descriptors. It also provides
a few very generic file descriptor methods.
"""
+ def __new__(cls, value: FileDescriptorLike) -> typing.Self:
+ """Construct a FileDescriptor from an int or HasFileno."""
+ if isinstance(value, cls):
+ return value # No need to copy, it's immutable.
+ if not isinstance(value, int):
+ value = value.fileno()
+ return super(FileDescriptor, cls).__new__(cls, value)
+
def __enter__(self) -> "FileDescriptor":
"""When used as a context manager, close the file descriptor on scope
exit.
@@ -37,16 +54,18 @@ class FileDescriptor(int):
return FileDescriptor(os.dup(self))
return FileDescriptor(fcntl.fcntl(self, fcntl.F_DUPFD_CLOEXEC, 0))
- def dup2(self, fd2: int, inheritable: bool = True) -> "FileDescriptor":
+ def dup2(
+ self, fd2: FileDescriptorLike, inheritable: bool = True
+ ) -> "FileDescriptor":
"""Duplicate the file to the given file descriptor number."""
- return FileDescriptor(os.dup2(self, fd2, inheritable))
+ return FileDescriptor(os.dup2(self, FileDescriptor(fd2), inheritable))
@classmethod
def pidfd_open(cls, pid: int, flags: int = 0) -> typing.Self:
"""Convenience wrapper for os.pidfd_open."""
return cls(os.pidfd_open(pid, flags))
- def fileno(self) -> int:
+ def fileno(self) -> FileDescriptor:
"""Return self such that it satisfies the HasFileno protocol."""
return self