See the documentation for :func:`getgroups` for cases where it may not
return the same group list set by calling setgroups().
+.. function:: setns(fd, nstype=0)
+
+ Reassociate the current thread with a Linux namespace.
+ See the :manpage:`setns(2)` and :manpage:`namespaces(7)` man pages for more
+ details.
+
+ If *fd* refers to a :file:`/proc/{pid}/ns/` link, ``setns()`` reassociates the
+ calling thread with the namespace associated with that link,
+ and *nstype* may be set to one of the
+ :ref:`CLONE_NEW* constants <os-unshare-clone-flags>`
+ to impose constraints on the operation
+ (``0`` means no constraints).
+
+ Since Linux 5.8, *fd* may refer to a PID file descriptor obtained from
+ :func:`~os.pidfd_open`. In this case, ``setns()`` reassociates the calling thread
+ into one or more of the same namespaces as the thread referred to by *fd*.
+ This is subject to any constraints imposed by *nstype*,
+ which is a bit mask combining one or more of the
+ :ref:`CLONE_NEW* constants <os-unshare-clone-flags>`,
+ e.g. ``setns(fd, os.CLONE_NEWUTS | os.CLONE_NEWPID)``.
+ The caller's memberships in unspecified namespaces are left unchanged.
+
+ *fd* can be any object with a :meth:`~io.IOBase.fileno` method, or a raw file descriptor.
+
+ This example reassociates the thread with the ``init`` process's network namespace::
+
+ fd = os.open("/proc/1/ns/net", os.O_RDONLY)
+ os.setns(fd, os.CLONE_NEWNET)
+ os.close(fd)
+
+ .. availability:: Linux >= 3.0 with glibc >= 2.14.
+
+ .. versionadded:: 3.12
+
+ .. seealso::
+
+ The :func:`~os.unshare` function.
+
.. function:: setpgrp()
Call the system call :c:func:`setpgrp` or ``setpgrp(0, 0)`` depending on
The function is now always available and is also available on Windows.
+.. function:: unshare(flags)
+
+ Disassociate parts of the process execution context, and move them into a
+ newly created namespace.
+ See the :manpage:`unshare(2)`
+ man page for more details.
+ The *flags* argument is a bit mask, combining zero or more of the
+ :ref:`CLONE_* constants <os-unshare-clone-flags>`,
+ that specifies which parts of the execution context should be
+ unshared from their existing associations and moved to a new namespace.
+ If the *flags* argument is ``0``, no changes are made to the calling process's
+ execution context.
+
+ .. availability:: Linux >= 2.6.16.
+
+ .. versionadded:: 3.12
+
+ .. seealso::
+
+ The :func:`~os.setns` function.
+
+.. _os-unshare-clone-flags:
+
+Flags to the :func:`unshare` function, if the implementation supports them.
+See :manpage:`unshare(2)` in the Linux manual
+for their exact effect and availability.
+
+.. data:: CLONE_FILES
+ CLONE_FS
+ CLONE_NEWCGROUP
+ CLONE_NEWIPC
+ CLONE_NEWNET
+ CLONE_NEWNS
+ CLONE_NEWPID
+ CLONE_NEWTIME
+ CLONE_NEWUSER
+ CLONE_NEWUTS
+ CLONE_SIGHAND
+ CLONE_SYSVSEM
+ CLONE_THREAD
+ CLONE_VM
+
+
.. _os-newstreams:
File Object Creation
STRUCT_FOR_ID(node_depth)
STRUCT_FOR_ID(node_offset)
STRUCT_FOR_ID(ns)
+ STRUCT_FOR_ID(nstype)
STRUCT_FOR_ID(number)
STRUCT_FOR_ID(obj)
STRUCT_FOR_ID(object)
INIT_ID(node_depth), \
INIT_ID(node_offset), \
INIT_ID(ns), \
+ INIT_ID(nstype), \
INIT_ID(number), \
INIT_ID(obj), \
INIT_ID(object), \
PyUnicode_InternInPlace(&string);
string = &_Py_ID(ns);
PyUnicode_InternInPlace(&string);
+ string = &_Py_ID(nstype);
+ PyUnicode_InternInPlace(&string);
string = &_Py_ID(number);
PyUnicode_InternInPlace(&string);
string = &_Py_ID(obj);
_PyObject_Dump((PyObject *)&_Py_ID(ns));
Py_FatalError("immortal object has less refcnt than expected _PyObject_IMMORTAL_REFCNT");
};
+ if (Py_REFCNT((PyObject *)&_Py_ID(nstype)) < _PyObject_IMMORTAL_REFCNT) {
+ _PyObject_Dump((PyObject *)&_Py_ID(nstype));
+ Py_FatalError("immortal object has less refcnt than expected _PyObject_IMMORTAL_REFCNT");
+ };
if (Py_REFCNT((PyObject *)&_Py_ID(number)) < _PyObject_IMMORTAL_REFCNT) {
_PyObject_Dump((PyObject *)&_Py_ID(number));
Py_FatalError("immortal object has less refcnt than expected _PyObject_IMMORTAL_REFCNT");
os.utime("path", dir_fd=0)
+class NamespacesTests(unittest.TestCase):
+ """Tests for os.unshare() and os.setns()."""
+
+ @unittest.skipUnless(hasattr(os, 'unshare'), 'needs os.unshare()')
+ @unittest.skipUnless(hasattr(os, 'setns'), 'needs os.setns()')
+ @unittest.skipUnless(os.path.exists('/proc/self/ns/uts'), 'need /proc/self/ns/uts')
+ @support.requires_linux_version(3, 0, 0)
+ def test_unshare_setns(self):
+ code = """if 1:
+ import errno
+ import os
+ import sys
+ fd = os.open('/proc/self/ns/uts', os.O_RDONLY)
+ try:
+ original = os.readlink('/proc/self/ns/uts')
+ try:
+ os.unshare(os.CLONE_NEWUTS)
+ except OSError as e:
+ if e.errno == errno.ENOSPC:
+ # skip test if limit is exceeded
+ sys.exit()
+ raise
+ new = os.readlink('/proc/self/ns/uts')
+ if original == new:
+ raise Exception('os.unshare failed')
+ os.setns(fd, os.CLONE_NEWUTS)
+ restored = os.readlink('/proc/self/ns/uts')
+ if original != restored:
+ raise Exception('os.setns failed')
+ except PermissionError:
+ # The calling process did not have the required privileges
+ # for this operation
+ pass
+ except OSError as e:
+ # Skip the test on these errors:
+ # - ENOSYS: syscall not available
+ # - EINVAL: kernel was not configured with the CONFIG_UTS_NS option
+ # - ENOMEM: not enough memory
+ if e.errno not in (errno.ENOSYS, errno.EINVAL, errno.ENOMEM):
+ raise
+ finally:
+ os.close(fd)
+ """
+
+ assert_python_ok("-c", code)
+
+
def tearDownModule():
support.reap_children()
Riccardo Coccioli
Nick Coghlan
Josh Cogliati
+Noam Cohen
Dave Cole
Terrence Cole
Benjamin Collar
--- /dev/null
+Implement :func:`os.setns` and :func:`os.unshare` for Linux. Patch by Noam Cohen.
#endif /* (defined(__linux__) && defined(__NR_pidfd_open)) */
+#if defined(HAVE_SETNS)
+
+PyDoc_STRVAR(os_setns__doc__,
+"setns($module, /, fd, nstype=0)\n"
+"--\n"
+"\n"
+"Move the calling thread into different namespaces.\n"
+"\n"
+" fd\n"
+" A file descriptor to a namespace.\n"
+" nstype\n"
+" Type of namespace.");
+
+#define OS_SETNS_METHODDEF \
+ {"setns", _PyCFunction_CAST(os_setns), METH_FASTCALL|METH_KEYWORDS, os_setns__doc__},
+
+static PyObject *
+os_setns_impl(PyObject *module, int fd, int nstype);
+
+static PyObject *
+os_setns(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 2
+ static struct {
+ PyGC_Head _this_is_not_used;
+ PyObject_VAR_HEAD
+ PyObject *ob_item[NUM_KEYWORDS];
+ } _kwtuple = {
+ .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
+ .ob_item = { &_Py_ID(fd), &_Py_ID(nstype), },
+ };
+ #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[] = {"fd", "nstype", NULL};
+ static _PyArg_Parser _parser = {
+ .keywords = _keywords,
+ .fname = "setns",
+ .kwtuple = KWTUPLE,
+ };
+ #undef KWTUPLE
+ PyObject *argsbuf[2];
+ Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1;
+ int fd;
+ int nstype = 0;
+
+ args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 2, 0, argsbuf);
+ if (!args) {
+ goto exit;
+ }
+ if (!_PyLong_FileDescriptor_Converter(args[0], &fd)) {
+ goto exit;
+ }
+ if (!noptargs) {
+ goto skip_optional_pos;
+ }
+ nstype = _PyLong_AsInt(args[1]);
+ if (nstype == -1 && PyErr_Occurred()) {
+ goto exit;
+ }
+skip_optional_pos:
+ return_value = os_setns_impl(module, fd, nstype);
+
+exit:
+ return return_value;
+}
+
+#endif /* defined(HAVE_SETNS) */
+
+#if defined(HAVE_UNSHARE)
+
+PyDoc_STRVAR(os_unshare__doc__,
+"unshare($module, /, flags)\n"
+"--\n"
+"\n"
+"Disassociate parts of a process (or thread) execution context.\n"
+"\n"
+" flags\n"
+" Namespaces to be unshared.");
+
+#define OS_UNSHARE_METHODDEF \
+ {"unshare", _PyCFunction_CAST(os_unshare), METH_FASTCALL|METH_KEYWORDS, os_unshare__doc__},
+
+static PyObject *
+os_unshare_impl(PyObject *module, int flags);
+
+static PyObject *
+os_unshare(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 1
+ static struct {
+ PyGC_Head _this_is_not_used;
+ PyObject_VAR_HEAD
+ PyObject *ob_item[NUM_KEYWORDS];
+ } _kwtuple = {
+ .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
+ .ob_item = { &_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[] = {"flags", NULL};
+ static _PyArg_Parser _parser = {
+ .keywords = _keywords,
+ .fname = "unshare",
+ .kwtuple = KWTUPLE,
+ };
+ #undef KWTUPLE
+ PyObject *argsbuf[1];
+ int flags;
+
+ args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 1, 0, argsbuf);
+ if (!args) {
+ goto exit;
+ }
+ flags = _PyLong_AsInt(args[0]);
+ if (flags == -1 && PyErr_Occurred()) {
+ goto exit;
+ }
+ return_value = os_unshare_impl(module, flags);
+
+exit:
+ return return_value;
+}
+
+#endif /* defined(HAVE_UNSHARE) */
+
#if (defined(HAVE_READLINK) || defined(MS_WINDOWS))
PyDoc_STRVAR(os_readlink__doc__,
#define OS_PIDFD_OPEN_METHODDEF
#endif /* !defined(OS_PIDFD_OPEN_METHODDEF) */
+#ifndef OS_SETNS_METHODDEF
+ #define OS_SETNS_METHODDEF
+#endif /* !defined(OS_SETNS_METHODDEF) */
+
+#ifndef OS_UNSHARE_METHODDEF
+ #define OS_UNSHARE_METHODDEF
+#endif /* !defined(OS_UNSHARE_METHODDEF) */
+
#ifndef OS_READLINK_METHODDEF
#define OS_READLINK_METHODDEF
#endif /* !defined(OS_READLINK_METHODDEF) */
#ifndef OS_WAITSTATUS_TO_EXITCODE_METHODDEF
#define OS_WAITSTATUS_TO_EXITCODE_METHODDEF
#endif /* !defined(OS_WAITSTATUS_TO_EXITCODE_METHODDEF) */
-/*[clinic end generated code: output=471ab8f2ad3d46b0 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=90f5e6995114e5ca input=a9049054013a1b77]*/
#endif
+#ifdef HAVE_SETNS
+/*[clinic input]
+os.setns
+ fd: fildes
+ A file descriptor to a namespace.
+ nstype: int = 0
+ Type of namespace.
+
+Move the calling thread into different namespaces.
+[clinic start generated code]*/
+
+static PyObject *
+os_setns_impl(PyObject *module, int fd, int nstype)
+/*[clinic end generated code: output=5dbd055bfb66ecd0 input=42787871226bf3ee]*/
+{
+ int res;
+
+ Py_BEGIN_ALLOW_THREADS
+ res = setns(fd, nstype);
+ Py_END_ALLOW_THREADS
+
+ if (res != 0) {
+ return posix_error();
+ }
+
+ Py_RETURN_NONE;
+}
+#endif
+
+
+#ifdef HAVE_UNSHARE
+/*[clinic input]
+os.unshare
+ flags: int
+ Namespaces to be unshared.
+
+Disassociate parts of a process (or thread) execution context.
+[clinic start generated code]*/
+
+static PyObject *
+os_unshare_impl(PyObject *module, int flags)
+/*[clinic end generated code: output=1b3177906dd237ee input=9e065db3232b8b1b]*/
+{
+ int res;
+
+ Py_BEGIN_ALLOW_THREADS
+ res = unshare(flags);
+ Py_END_ALLOW_THREADS
+
+ if (res != 0) {
+ return posix_error();
+ }
+
+ Py_RETURN_NONE;
+}
+#endif
+
+
#if defined(HAVE_READLINK) || defined(MS_WINDOWS)
/*[clinic input]
os.readlink
OS__ADD_DLL_DIRECTORY_METHODDEF
OS__REMOVE_DLL_DIRECTORY_METHODDEF
OS_WAITSTATUS_TO_EXITCODE_METHODDEF
+ OS_SETNS_METHODDEF
+ OS_UNSHARE_METHODDEF
{NULL, NULL} /* Sentinel */
};
#ifdef SCHED_FX
if (PyModule_AddIntConstant(m, "SCHED_FX", SCHED_FSS)) return -1;
#endif
+
+/* constants for namespaces */
+#if defined(HAVE_SETNS) || defined(HAVE_UNSHARE)
+#ifdef CLONE_FS
+ if (PyModule_AddIntMacro(m, CLONE_FS)) return -1;
+#endif
+#ifdef CLONE_FILES
+ if (PyModule_AddIntMacro(m, CLONE_FILES)) return -1;
+#endif
+#ifdef CLONE_NEWNS
+ if (PyModule_AddIntMacro(m, CLONE_NEWNS)) return -1;
+#endif
+#ifdef CLONE_NEWCGROUP
+ if (PyModule_AddIntMacro(m, CLONE_NEWCGROUP)) return -1;
+#endif
+#ifdef CLONE_NEWUTS
+ if (PyModule_AddIntMacro(m, CLONE_NEWUTS)) return -1;
+#endif
+#ifdef CLONE_NEWIPC
+ if (PyModule_AddIntMacro(m, CLONE_NEWIPC)) return -1;
+#endif
+#ifdef CLONE_NEWUSER
+ if (PyModule_AddIntMacro(m, CLONE_NEWUSER)) return -1;
+#endif
+#ifdef CLONE_NEWPID
+ if (PyModule_AddIntMacro(m, CLONE_NEWPID)) return -1;
+#endif
+#ifdef CLONE_NEWNET
+ if (PyModule_AddIntMacro(m, CLONE_NEWNET)) return -1;
+#endif
+#ifdef CLONE_NEWTIME
+ if (PyModule_AddIntMacro(m, CLONE_NEWTIME)) return -1;
+#endif
+#ifdef CLONE_SYSVSEM
+ if (PyModule_AddIntMacro(m, CLONE_SYSVSEM)) return -1;
+#endif
+#ifdef CLONE_THREAD
+ if (PyModule_AddIntMacro(m, CLONE_THREAD)) return -1;
+#endif
+#ifdef CLONE_SIGHAND
+ if (PyModule_AddIntMacro(m, CLONE_SIGHAND)) return -1;
+#endif
+#ifdef CLONE_VM
+ if (PyModule_AddIntMacro(m, CLONE_VM)) return -1;
+#endif
+#endif
+
#endif
#ifdef USE_XATTRS
done
+# check for namespace functions
+for ac_func in setns unshare
+do :
+ as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh`
+ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var"
+if eval test \"x\$"$as_ac_var"\" = x"yes"; then :
+ cat >>confdefs.h <<_ACEOF
+#define `$as_echo "HAVE_$ac_func" | $as_tr_cpp` 1
+_ACEOF
+
+fi
+done
+
+
[])
)
+# check for namespace functions
+AC_CHECK_FUNCS([setns unshare])
+
dnl We search for both crypt and crypt_r as one or the other may be defined
dnl libxcrypt provides <crypt.h> and libcrypt with crypt_r() since
dnl at least 3.1.1 from 2015.
/* Define to 1 if you have the `setlocale' function. */
#undef HAVE_SETLOCALE
+/* Define to 1 if you have the `setns' function. */
+#undef HAVE_SETNS
+
/* Define to 1 if you have the `setpgid' function. */
#undef HAVE_SETPGID
/* Define to 1 if you have the `unlinkat' function. */
#undef HAVE_UNLINKAT
+/* Define to 1 if you have the `unshare' function. */
+#undef HAVE_UNSHARE
+
/* Define if you have a useable wchar_t type defined in wchar.h; useable means
wchar_t must be an unsigned type with at least 16 bits. (see
Include/unicodeobject.h). */