.. availability:: Linux >= 5.10
.. versionadded:: 3.12
+.. function:: pidfd_getfd(pidfd, targetfd, *, flags=0)
+
+ Duplicate *targetfd* from the process referred to by the process file
+ descriptor *pidfd*, into the calling process. The returned file descriptor
+ is :ref:`non-inheritable <fd_inheritance>`.
+
+ *flags* is reserved, and currently must be ``0``.
+
+ See the :manpage:`pidfd_getfd(2)` man page for more details.
+
+ .. availability:: Linux >= 5.6, Android >= :func:`build-time <sys.getandroidapilevel>` API level 31
+ .. versionadded:: next
.. function:: plock(op, /)
Improved modules
================
-module_name
------------
+os
+--
-* TODO
+* Add :func:`os.pidfd_getfd` for duplicating a file descriptor from another
+ process via a pidfd. Available on Linux 5.6+.
+ (Contributed by Maurycy Pawłowski-Wieroński in :gh:`149464`.)
.. Add improved modules above alphabetically, not here at the end.
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(person));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(pi_factory));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(pid));
+ _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(pidfd));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(pointer_bits));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(policy));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(pos));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(take_bytes));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(target));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(target_is_directory));
+ _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(targetfd));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(task));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(tb_frame));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(tb_lasti));
STRUCT_FOR_ID(person)
STRUCT_FOR_ID(pi_factory)
STRUCT_FOR_ID(pid)
+ STRUCT_FOR_ID(pidfd)
STRUCT_FOR_ID(pointer_bits)
STRUCT_FOR_ID(policy)
STRUCT_FOR_ID(pos)
STRUCT_FOR_ID(take_bytes)
STRUCT_FOR_ID(target)
STRUCT_FOR_ID(target_is_directory)
+ STRUCT_FOR_ID(targetfd)
STRUCT_FOR_ID(task)
STRUCT_FOR_ID(tb_frame)
STRUCT_FOR_ID(tb_lasti)
INIT_ID(person), \
INIT_ID(pi_factory), \
INIT_ID(pid), \
+ INIT_ID(pidfd), \
INIT_ID(pointer_bits), \
INIT_ID(policy), \
INIT_ID(pos), \
INIT_ID(take_bytes), \
INIT_ID(target), \
INIT_ID(target_is_directory), \
+ INIT_ID(targetfd), \
INIT_ID(task), \
INIT_ID(tb_frame), \
INIT_ID(tb_lasti), \
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
assert(PyUnicode_GET_LENGTH(string) != 1);
+ string = &_Py_ID(pidfd);
+ _PyUnicode_InternStatic(interp, &string);
+ assert(_PyUnicode_CheckConsistency(string, 1));
+ assert(PyUnicode_GET_LENGTH(string) != 1);
string = &_Py_ID(pointer_bits);
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
assert(PyUnicode_GET_LENGTH(string) != 1);
+ string = &_Py_ID(targetfd);
+ _PyUnicode_InternStatic(interp, &string);
+ assert(_PyUnicode_CheckConsistency(string, 1));
+ assert(PyUnicode_GET_LENGTH(string) != 1);
string = &_Py_ID(task);
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
self.assertEqual(cm.exception.errno, errno.EINVAL)
os.close(os.pidfd_open(os.getpid(), 0))
+ @unittest.skipUnless(hasattr(os, "pidfd_getfd"), "pidfd_getfd unavailable")
+ def test_pidfd_getfd(self):
+ fd = os.open(__file__, os.O_RDONLY)
+ self.addCleanup(os.close, fd)
+ pidfd = os.pidfd_open(os.getpid(), 0)
+ self.addCleanup(os.close, pidfd)
+ try:
+ dupfd = os.pidfd_getfd(pidfd, fd)
+ except OSError as exc:
+ if exc.errno == errno.ENOSYS:
+ self.skipTest("system does not support pidfd_getfd")
+ if isinstance(exc, PermissionError):
+ self.skipTest(f"pidfd_getfd syscall blocked: {exc!r}")
+ raise
+ self.addCleanup(os.close, dupfd)
+
+ self.assertFalse(os.get_inheritable(dupfd)) # PEP 446
+ self.assertEqual(os.fstat(fd), os.fstat(dupfd))
+
+ with self.assertRaises(OSError) as cm:
+ os.pidfd_getfd(-1, 0)
+ self.assertEqual(cm.exception.errno, errno.EBADF)
+
+ with self.assertRaises(OSError) as cm:
+ bad_fd = os_helper.make_bad_fd()
+ os.pidfd_getfd(pidfd, bad_fd)
+ self.assertEqual(cm.exception.errno, errno.EBADF)
+
@os_helper.skip_unless_hardlink
@os_helper.skip_unless_symlink
def test_link_follow_symlinks(self):
--- /dev/null
+Add :func:`os.pidfd_getfd` for duplicating a file descriptor from another
+process via a pidfd. Available on Linux 5.6+. Patch by Maurycy
+Pawłowski-Wieroński.
#endif /* (defined(__linux__) && defined(__NR_pidfd_open) && !(defined(__ANDROID__) && __ANDROID_API__ < 31)) */
+#if (defined(__linux__) && defined(__NR_pidfd_getfd) && !(defined(__ANDROID__) && __ANDROID_API__ < 31))
+
+PyDoc_STRVAR(os_pidfd_getfd__doc__,
+"pidfd_getfd($module, /, pidfd, targetfd, *, flags=0)\n"
+"--\n"
+"\n"
+"Duplicate a file descriptor from the process referred to by *pidfd*.\n"
+"\n"
+" pidfd\n"
+" A process file descriptor.\n"
+" targetfd\n"
+" The file descriptor to duplicate from the target process.\n"
+" flags\n"
+" Reserved, must be 0.");
+
+#define OS_PIDFD_GETFD_METHODDEF \
+ {"pidfd_getfd", _PyCFunction_CAST(os_pidfd_getfd), METH_FASTCALL|METH_KEYWORDS, os_pidfd_getfd__doc__},
+
+static PyObject *
+os_pidfd_getfd_impl(PyObject *module, int pidfd, int targetfd,
+ unsigned int flags);
+
+static PyObject *
+os_pidfd_getfd(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
+{
+ PyObject *return_value = NULL;
+ #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
+
+ #define NUM_KEYWORDS 3
+ static struct {
+ PyGC_Head _this_is_not_used;
+ PyObject_VAR_HEAD
+ Py_hash_t ob_hash;
+ PyObject *ob_item[NUM_KEYWORDS];
+ } _kwtuple = {
+ .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
+ .ob_hash = -1,
+ .ob_item = { &_Py_ID(pidfd), &_Py_ID(targetfd), &_Py_ID(flags), },
+ };
+ #undef NUM_KEYWORDS
+ #define KWTUPLE (&_kwtuple.ob_base.ob_base)
+
+ #else // !Py_BUILD_CORE
+ # define KWTUPLE NULL
+ #endif // !Py_BUILD_CORE
+
+ static const char * const _keywords[] = {"pidfd", "targetfd", "flags", NULL};
+ static _PyArg_Parser _parser = {
+ .keywords = _keywords,
+ .fname = "pidfd_getfd",
+ .kwtuple = KWTUPLE,
+ };
+ #undef KWTUPLE
+ PyObject *argsbuf[3];
+ Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 2;
+ int pidfd;
+ int targetfd;
+ unsigned int flags = 0;
+
+ args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser,
+ /*minpos*/ 2, /*maxpos*/ 2, /*minkw*/ 0, /*varpos*/ 0, argsbuf);
+ if (!args) {
+ goto exit;
+ }
+ pidfd = PyLong_AsInt(args[0]);
+ if (pidfd == -1 && PyErr_Occurred()) {
+ goto exit;
+ }
+ targetfd = PyLong_AsInt(args[1]);
+ if (targetfd == -1 && PyErr_Occurred()) {
+ goto exit;
+ }
+ if (!noptargs) {
+ goto skip_optional_kwonly;
+ }
+ if (!_PyLong_UnsignedInt_Converter(args[2], &flags)) {
+ goto exit;
+ }
+skip_optional_kwonly:
+ return_value = os_pidfd_getfd_impl(module, pidfd, targetfd, flags);
+
+exit:
+ return return_value;
+}
+
+#endif /* (defined(__linux__) && defined(__NR_pidfd_getfd) && !(defined(__ANDROID__) && __ANDROID_API__ < 31)) */
+
#if defined(HAVE_SETNS)
PyDoc_STRVAR(os_setns__doc__,
#define OS_PIDFD_OPEN_METHODDEF
#endif /* !defined(OS_PIDFD_OPEN_METHODDEF) */
+#ifndef OS_PIDFD_GETFD_METHODDEF
+ #define OS_PIDFD_GETFD_METHODDEF
+#endif /* !defined(OS_PIDFD_GETFD_METHODDEF) */
+
#ifndef OS_SETNS_METHODDEF
#define OS_SETNS_METHODDEF
#endif /* !defined(OS_SETNS_METHODDEF) */
#ifndef OS__EMSCRIPTEN_LOG_METHODDEF
#define OS__EMSCRIPTEN_LOG_METHODDEF
#endif /* !defined(OS__EMSCRIPTEN_LOG_METHODDEF) */
-/*[clinic end generated code: output=e709b8b783fbc261 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=c4cf19262e42e352 input=a9049054013a1b77]*/
#endif
+#if defined(__linux__) && defined(__NR_pidfd_getfd) && \
+ !(defined(__ANDROID__) && __ANDROID_API__ < 31)
+/*[clinic input]
+os.pidfd_getfd
+ pidfd: int
+ A process file descriptor.
+ targetfd: int
+ The file descriptor to duplicate from the target process.
+ *
+ flags: unsigned_int = 0
+ Reserved, must be 0.
+
+Duplicate a file descriptor from the process referred to by *pidfd*.
+[clinic start generated code]*/
+
+static PyObject *
+os_pidfd_getfd_impl(PyObject *module, int pidfd, int targetfd,
+ unsigned int flags)
+/*[clinic end generated code: output=e1a1415a13c7137f input=ef6417fb10deb1cc]*/
+{
+ int fd = syscall(__NR_pidfd_getfd, pidfd, targetfd, flags);
+ if (fd < 0) {
+ return posix_error();
+ }
+ return PyLong_FromLong(fd);
+}
+#endif
+
+
#ifdef HAVE_SETNS
/*[clinic input]
os.setns
OS_WAITID_METHODDEF
OS_WAITPID_METHODDEF
OS_PIDFD_OPEN_METHODDEF
+ OS_PIDFD_GETFD_METHODDEF
OS_GETSID_METHODDEF
OS_SETSID_METHODDEF
OS_SETPGID_METHODDEF