From 0bdd8bcf6989d6adcbe47b0b1ad5084faa1a738e Mon Sep 17 00:00:00 2001 From: Noam Cohen Date: Wed, 20 Jul 2022 08:57:43 +0300 Subject: [PATCH 01/40] gh-95023: added os.setns and os.unshare for namespaces switching on Linux --- Doc/library/os.rst | 30 ++++++ Lib/test/test_posix.py | 29 ++++++ ...2-07-20-09-04-55.gh-issue-95023.bs-xd7.rst | 1 + Modules/clinic/posixmodule.c.h | 97 ++++++++++++++++++- Modules/posixmodule.c | 92 ++++++++++++++++++ configure | 14 +++ configure.ac | 3 + pyconfig.h.in | 6 ++ 8 files changed, 271 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2022-07-20-09-04-55.gh-issue-95023.bs-xd7.rst diff --git a/Doc/library/os.rst b/Doc/library/os.rst index 4639a8b4afe593..d8f7be279c5642 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -569,6 +569,15 @@ process and user. See the documentation for :func:`getgroups` for cases where it may not return the same group list set by calling setgroups(). +.. function:: setns(fd, flags=0) + + Call the system call :c:func:`setns`. See the Linux manual for the semantics. + For flags see the ``CLONE_NEW*`` constants. + + .. availability:: Linux 3.0 or newer. + + .. versionadded:: 3.12 + .. function:: setpgrp() Call the system call :c:func:`setpgrp` or ``setpgrp(0, 0)`` depending on @@ -732,6 +741,27 @@ process and user. The function is now always available and is also available on Windows. +.. function:: unshare(flags) + + Call the system call :c:func:`unshare`. See the Linux manual for the semantics. + + .. availability:: Linux 2.6.16 or newer. + + .. versionadded:: 3.12 + +Parameters to the :func:`unshare` function, if the implementation supports them. + +.. data:: CLONE_FS + CLONE_FILES + CLONE_NEWNS + CLONE_NEWCGROUP + CLONE_NEWUTS + CLONE_NEWIPC + CLONE_NEWUSER + CLONE_NEWPID + CLONE_NEWNET + CLONE_NEWTIME + .. _os-newstreams: File Object Creation diff --git a/Lib/test/test_posix.py b/Lib/test/test_posix.py index ae25ef55885dd6..0ce12d4be6f2ec 100644 --- a/Lib/test/test_posix.py +++ b/Lib/test/test_posix.py @@ -2172,6 +2172,35 @@ def test_utime(self): 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(hasattr(os, 'readlink'), 'needs os.readlink()') + @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): + original = os.readlink('/proc/self/ns/uts') + original_fd = os.open('/proc/self/ns/uts', os.O_RDONLY) + self.addCleanup(os.close, original_fd) + + try: + os.unshare(os.CLONE_NEWUTS) + except OSError as e: + self.assertEqual(e.errno, errno.EPERM) + self.skipTest("unprivileged users cannot call unshare.") + + current = os.readlink('/proc/self/ns/uts') + self.assertNotEqual(original, current) + + self.assertRaises(OSError, os.setns, original_fd, os.CLONE_NEWNET) + os.setns(original_fd, os.CLONE_NEWUTS) + + current = os.readlink('/proc/self/ns/uts') + self.assertEqual(original, current) + + def tearDownModule(): support.reap_children() diff --git a/Misc/NEWS.d/next/Core and Builtins/2022-07-20-09-04-55.gh-issue-95023.bs-xd7.rst b/Misc/NEWS.d/next/Core and Builtins/2022-07-20-09-04-55.gh-issue-95023.bs-xd7.rst new file mode 100644 index 00000000000000..2e85ed6e660340 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2022-07-20-09-04-55.gh-issue-95023.bs-xd7.rst @@ -0,0 +1 @@ +Implement :func:`os.setns` and :func:`os.unshare` for Linux. Patch by Noam Cohen diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h index 1ce7d86204e6f3..e28423b8b56aae 100644 --- a/Modules/clinic/posixmodule.c.h +++ b/Modules/clinic/posixmodule.c.h @@ -4192,6 +4192,93 @@ os_pidfd_open(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObjec #endif /* (defined(__linux__) && defined(__NR_pidfd_open)) */ +#if (defined(__linux__) && defined(HAVE_SETNS)) + +PyDoc_STRVAR(os_setns__doc__, +"setns($module, /, fd, nstype=0)\n" +"--\n" +"\n" +"Allows the calling thread to move into different namespaces."); + +#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; + static const char * const _keywords[] = {"fd", "nstype", NULL}; + static _PyArg_Parser _parser = {NULL, _keywords, "setns", 0}; + 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(__linux__) && defined(HAVE_SETNS)) */ + +#if (defined(__linux__) && defined(HAVE_UNSHARE)) + +PyDoc_STRVAR(os_unshare__doc__, +"unshare($module, /, flags)\n" +"--\n" +"\n" +"Allows a process (or thread) to disassociate parts of its execution context."); + +#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; + static const char * const _keywords[] = {"flags", NULL}; + static _PyArg_Parser _parser = {NULL, _keywords, "unshare", 0}; + 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(__linux__) && defined(HAVE_UNSHARE)) */ + #if (defined(HAVE_READLINK) || defined(MS_WINDOWS)) PyDoc_STRVAR(os_readlink__doc__, @@ -9073,6 +9160,14 @@ os_waitstatus_to_exitcode(PyObject *module, PyObject *const *args, Py_ssize_t na #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) */ @@ -9352,4 +9447,4 @@ os_waitstatus_to_exitcode(PyObject *module, PyObject *const *args, Py_ssize_t na #ifndef OS_WAITSTATUS_TO_EXITCODE_METHODDEF #define OS_WAITSTATUS_TO_EXITCODE_METHODDEF #endif /* !defined(OS_WAITSTATUS_TO_EXITCODE_METHODDEF) */ -/*[clinic end generated code: output=bae15f09a1b3d2e7 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=c0971c07f439dcba input=a9049054013a1b77]*/ diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 40229bce0f4033..e5949aad517f14 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -8588,6 +8588,61 @@ os_pidfd_open_impl(PyObject *module, pid_t pid, unsigned int flags) #endif +#if defined(__linux__) && defined(HAVE_SETNS) +/*[clinic input] +os.setns + fd: fildes + nstype: int = 0 + +Allows the calling thread to move 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=c097c9aa123c43ce]*/ +{ + int res; + + Py_BEGIN_ALLOW_THREADS + res = setns(fd, nstype); + Py_END_ALLOW_THREADS + + if (res != 0) { + return posix_error(); + } + + Py_RETURN_NONE; +} +#endif + + +#if defined(__linux__) && defined(HAVE_UNSHARE) +/*[clinic input] +os.unshare + flags: int + +Allows a process (or thread) to disassociate parts of its execution context. +[clinic start generated code]*/ + +static PyObject * +os_unshare_impl(PyObject *module, int flags) +/*[clinic end generated code: output=1b3177906dd237ee input=f8d7bd2c69325537]*/ +{ + 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 @@ -14930,6 +14985,8 @@ static PyMethodDef posix_methods[] = { OS__ADD_DLL_DIRECTORY_METHODDEF OS__REMOVE_DLL_DIRECTORY_METHODDEF OS_WAITSTATUS_TO_EXITCODE_METHODDEF + OS_SETNS_METHODDEF + OS_UNSHARE_METHODDEF {NULL, NULL} /* Sentinel */ }; @@ -15375,6 +15432,41 @@ all_ins(PyObject *m) #ifdef SCHED_FX if (PyModule_AddIntConstant(m, "SCHED_FX", SCHED_FSS)) return -1; #endif + +/* constants for namespaces */ +#if defined(__linux__) && (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 +#endif + #endif #ifdef USE_XATTRS diff --git a/configure b/configure index d607c5e5d37a03..7e2f0c2ed8b2ab 100755 --- a/configure +++ b/configure @@ -17857,6 +17857,20 @@ fi 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 + + diff --git a/configure.ac b/configure.ac index c5924169e03a0b..466c9fadd1bdd4 100644 --- a/configure.ac +++ b/configure.ac @@ -4937,6 +4937,9 @@ AC_CHECK_FUNCS(setpgrp, []) ) +# 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 and libcrypt with crypt_r() since dnl at least 3.1.1 from 2015. diff --git a/pyconfig.h.in b/pyconfig.h.in index aa9fc559fa2511..54c42121d37a2e 100644 --- a/pyconfig.h.in +++ b/pyconfig.h.in @@ -1019,6 +1019,9 @@ /* 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 @@ -1383,6 +1386,9 @@ /* 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). */ From 3685a27ff437bb80437edc6a477125c044ae7a9b Mon Sep 17 00:00:00 2001 From: Noam Cohen Date: Wed, 20 Jul 2022 18:08:13 +0300 Subject: [PATCH 02/40] remove gil release --- Modules/posixmodule.c | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index e5949aad517f14..71741023892471 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -8601,13 +8601,7 @@ static PyObject * os_setns_impl(PyObject *module, int fd, int nstype) /*[clinic end generated code: output=5dbd055bfb66ecd0 input=c097c9aa123c43ce]*/ { - int res; - - Py_BEGIN_ALLOW_THREADS - res = setns(fd, nstype); - Py_END_ALLOW_THREADS - - if (res != 0) { + if (setns(fd, nstype) != 0) { return posix_error(); } @@ -8628,13 +8622,7 @@ static PyObject * os_unshare_impl(PyObject *module, int flags) /*[clinic end generated code: output=1b3177906dd237ee input=f8d7bd2c69325537]*/ { - int res; - - Py_BEGIN_ALLOW_THREADS - res = unshare(flags); - Py_END_ALLOW_THREADS - - if (res != 0) { + if (unshare(flags) != 0) { return posix_error(); } From 8f227409bd75aa6c4d7db30a0d5050137ceb66de Mon Sep 17 00:00:00 2001 From: Noam Cohen Date: Thu, 21 Jul 2022 16:51:37 +0300 Subject: [PATCH 03/40] better setns, unshare doc --- Doc/library/os.rst | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/Doc/library/os.rst b/Doc/library/os.rst index d8f7be279c5642..7893e2dd765fe8 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -569,10 +569,16 @@ process and user. See the documentation for :func:`getgroups` for cases where it may not return the same group list set by calling setgroups(). -.. function:: setns(fd, flags=0) +.. function:: setns(fd, nstype=0) - Call the system call :c:func:`setns`. See the Linux manual for the semantics. - For flags see the ``CLONE_NEW*`` constants. + Reassociate thread with a namespace. + If *fd* refers to a ``/proc/[pid]/ns/`` link, :func:`setns` reassociates the + calling thread with the namespace associated with that link, subject to any + constraints imposed by the *nstype* argument (or any if 0). + Since Linux 5.8, *fd* may refer to a PID file descriptor obtained from + :c:func:`pidfd_open`. In this case :func:`setns` reassociates the calling thread + into one or more of the same namespaces as the thread referred to by *fd* subject + to any constraints imposed by the *nstype* which is a bit mask specified by ORing together one or more of the ``CLONE_NEW*`` constants. the caller's memberships in unspecified namespaces are left unchanged. .. availability:: Linux 3.0 or newer. @@ -743,13 +749,19 @@ process and user. .. function:: unshare(flags) - Call the system call :c:func:`unshare`. See the Linux manual for the semantics. + Disassociate parts of the process execution context. + The *flags* argument is a bit mask that specifies which parts of the execution + context should be unshared. This argument is specified by ORing together zero + or more of the ``CLONE_*`` constants. + If *flags* is specified as zero, no changes are made to the calling process's + execution context. .. availability:: Linux 2.6.16 or newer. .. versionadded:: 3.12 -Parameters to the :func:`unshare` function, if the implementation supports them. +Flags to the :func:`unshare` function, if the implementation supports them. +See the Linux manual for the exact effect and availability. .. data:: CLONE_FS CLONE_FILES From 5115c8a552086dbbf57522aee8c32aec5f095f37 Mon Sep 17 00:00:00 2001 From: Noam Cohen Date: Thu, 21 Jul 2022 17:06:12 +0300 Subject: [PATCH 04/40] added example to setns --- Doc/library/os.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Doc/library/os.rst b/Doc/library/os.rst index 7893e2dd765fe8..252e28d9b885cb 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -580,6 +580,12 @@ process and user. into one or more of the same namespaces as the thread referred to by *fd* subject to any constraints imposed by the *nstype* which is a bit mask specified by ORing together one or more of the ``CLONE_NEW*`` constants. the caller's memberships in unspecified namespaces are left unchanged. + This example reassociates the thread with the ``init`` process' 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 or newer. .. versionadded:: 3.12 From 8845a86ce8cfc21fa072b69b510bb8039ae7ee36 Mon Sep 17 00:00:00 2001 From: Noam Cohen Date: Thu, 21 Jul 2022 17:06:25 +0300 Subject: [PATCH 05/40] added note about fileno for setns --- Doc/library/os.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/Doc/library/os.rst b/Doc/library/os.rst index 252e28d9b885cb..fcae394d2f2d53 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -579,6 +579,7 @@ process and user. :c:func:`pidfd_open`. In this case :func:`setns` reassociates the calling thread into one or more of the same namespaces as the thread referred to by *fd* subject to any constraints imposed by the *nstype* which is a bit mask specified by ORing together one or more of the ``CLONE_NEW*`` constants. the caller's memberships in unspecified namespaces are left unchanged. + *fd* can be any object with a :meth:`fileno()` method, or a raw file descriptor. This example reassociates the thread with the ``init`` process' network namespace:: From d7bb5822cad52c40d6d69ce4568424a692a2d276 Mon Sep 17 00:00:00 2001 From: Noam Cohen Date: Thu, 21 Jul 2022 17:06:40 +0300 Subject: [PATCH 06/40] added see also section for os.unshare --- Doc/library/os.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Doc/library/os.rst b/Doc/library/os.rst index fcae394d2f2d53..3318f375c37bb6 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -767,6 +767,10 @@ process and user. .. versionadded:: 3.12 + .. seealso:: + + The :func:`.setns` function. + Flags to the :func:`unshare` function, if the implementation supports them. See the Linux manual for the exact effect and availability. From 987613ac439ba779e1c9d1d272bba04a45f14adb Mon Sep 17 00:00:00 2001 From: Noam Cohen Date: Tue, 26 Jul 2022 12:12:05 +0300 Subject: [PATCH 07/40] Update configure.ac Co-authored-by: Christian Heimes --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 466c9fadd1bdd4..02608f7ea3b2e5 100644 --- a/configure.ac +++ b/configure.ac @@ -4938,7 +4938,7 @@ AC_CHECK_FUNCS(setpgrp, ) # check for namespace functions -AC_CHECK_FUNCS(setns unshare) +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 and libcrypt with crypt_r() since From 4c914130249a0b977c7ebca034cdd1ce86c0c7ab Mon Sep 17 00:00:00 2001 From: Noam Cohen Date: Tue, 26 Jul 2022 12:13:27 +0300 Subject: [PATCH 08/40] Revert "remove gil release" This reverts commit 3685a27ff437bb80437edc6a477125c044ae7a9b. --- Modules/posixmodule.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 71741023892471..e5949aad517f14 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -8601,7 +8601,13 @@ static PyObject * os_setns_impl(PyObject *module, int fd, int nstype) /*[clinic end generated code: output=5dbd055bfb66ecd0 input=c097c9aa123c43ce]*/ { - if (setns(fd, nstype) != 0) { + int res; + + Py_BEGIN_ALLOW_THREADS + res = setns(fd, nstype); + Py_END_ALLOW_THREADS + + if (res != 0) { return posix_error(); } @@ -8622,7 +8628,13 @@ static PyObject * os_unshare_impl(PyObject *module, int flags) /*[clinic end generated code: output=1b3177906dd237ee input=f8d7bd2c69325537]*/ { - if (unshare(flags) != 0) { + int res; + + Py_BEGIN_ALLOW_THREADS + res = unshare(flags); + Py_END_ALLOW_THREADS + + if (res != 0) { return posix_error(); } From 7d23963e960faa803a669075839b57c73c9bda6a Mon Sep 17 00:00:00 2001 From: Noam Cohen Date: Tue, 26 Jul 2022 14:25:25 +0300 Subject: [PATCH 09/40] better docs --- Doc/library/os.rst | 22 ++++++++++--------- ...2-07-20-09-04-55.gh-issue-95023.bs-xd7.rst | 2 +- Modules/clinic/posixmodule.c.h | 14 +++++++++--- Modules/posixmodule.c | 11 ++++++---- 4 files changed, 31 insertions(+), 18 deletions(-) diff --git a/Doc/library/os.rst b/Doc/library/os.rst index 3318f375c37bb6..ce6b960ebb2197 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -572,14 +572,17 @@ process and user. .. function:: setns(fd, nstype=0) Reassociate thread with a namespace. - If *fd* refers to a ``/proc/[pid]/ns/`` link, :func:`setns` reassociates the + If *fd* refers to a ``/proc/[pid]/ns/`` link, ``setns()`` reassociates the calling thread with the namespace associated with that link, subject to any - constraints imposed by the *nstype* argument (or any if 0). + constraints imposed by the *nstype* argument (or any if ``0``). Since Linux 5.8, *fd* may refer to a PID file descriptor obtained from - :c:func:`pidfd_open`. In this case :func:`setns` reassociates the calling thread - into one or more of the same namespaces as the thread referred to by *fd* subject - to any constraints imposed by the *nstype* which is a bit mask specified by ORing together one or more of the ``CLONE_NEW*`` constants. the caller's memberships in unspecified namespaces are left unchanged. - *fd* can be any object with a :meth:`fileno()` method, or a raw file descriptor. + :manpage:`pidfd_open(2)`. In this case ``setns()`` reassociates the calling thread + into one or more of the same namespaces as the thread referred to by *fd* + subject to any constraints imposed by the *nstype*, which is + a bit mask specified by combining one or more of the ``CLONE_NEW*`` constants + using ``|`` (bitwise or). + the caller's memberships in unspecified namespaces are left unchanged. + *fd* can be any object with a :meth:`fileno` method, or a raw file descriptor. This example reassociates the thread with the ``init`` process' network namespace:: @@ -757,10 +760,9 @@ process and user. .. function:: unshare(flags) Disassociate parts of the process execution context. - The *flags* argument is a bit mask that specifies which parts of the execution - context should be unshared. This argument is specified by ORing together zero - or more of the ``CLONE_*`` constants. - If *flags* is specified as zero, no changes are made to the calling process's + The *flags* argument is a bit mask combining zero or more of the ``CLONE_*`` constants using ``|`` (bitwise or), that specifies which parts of the execution + context should be unshared. + If *flags* is specified as zero, no changes are made to the calling process' execution context. .. availability:: Linux 2.6.16 or newer. diff --git a/Misc/NEWS.d/next/Core and Builtins/2022-07-20-09-04-55.gh-issue-95023.bs-xd7.rst b/Misc/NEWS.d/next/Core and Builtins/2022-07-20-09-04-55.gh-issue-95023.bs-xd7.rst index 2e85ed6e660340..ace582e6d7cbfd 100644 --- a/Misc/NEWS.d/next/Core and Builtins/2022-07-20-09-04-55.gh-issue-95023.bs-xd7.rst +++ b/Misc/NEWS.d/next/Core and Builtins/2022-07-20-09-04-55.gh-issue-95023.bs-xd7.rst @@ -1 +1 @@ -Implement :func:`os.setns` and :func:`os.unshare` for Linux. Patch by Noam Cohen +Implement :func:`os.setns` and :func:`os.unshare` for Linux. Patch by Noam Cohen. \ No newline at end of file diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h index e28423b8b56aae..15a70c663f63ec 100644 --- a/Modules/clinic/posixmodule.c.h +++ b/Modules/clinic/posixmodule.c.h @@ -4198,7 +4198,12 @@ PyDoc_STRVAR(os_setns__doc__, "setns($module, /, fd, nstype=0)\n" "--\n" "\n" -"Allows the calling thread to move into different namespaces."); +"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__}, @@ -4246,7 +4251,10 @@ PyDoc_STRVAR(os_unshare__doc__, "unshare($module, /, flags)\n" "--\n" "\n" -"Allows a process (or thread) to disassociate parts of its execution context."); +"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__}, @@ -9447,4 +9455,4 @@ os_waitstatus_to_exitcode(PyObject *module, PyObject *const *args, Py_ssize_t na #ifndef OS_WAITSTATUS_TO_EXITCODE_METHODDEF #define OS_WAITSTATUS_TO_EXITCODE_METHODDEF #endif /* !defined(OS_WAITSTATUS_TO_EXITCODE_METHODDEF) */ -/*[clinic end generated code: output=c0971c07f439dcba input=a9049054013a1b77]*/ +/*[clinic end generated code: output=78d14ea4616fdc42 input=a9049054013a1b77]*/ diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index e5949aad517f14..144aa7c7350cd3 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -8592,14 +8592,16 @@ os_pidfd_open_impl(PyObject *module, pid_t pid, unsigned int flags) /*[clinic input] os.setns fd: fildes + A file descriptor to a namespace. nstype: int = 0 + Type of namespace. -Allows the calling thread to move into different namespaces. +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=c097c9aa123c43ce]*/ +/*[clinic end generated code: output=5dbd055bfb66ecd0 input=42787871226bf3ee]*/ { int res; @@ -8620,13 +8622,14 @@ os_setns_impl(PyObject *module, int fd, int nstype) /*[clinic input] os.unshare flags: int + Namespaces to be unshared. -Allows a process (or thread) to disassociate parts of its execution context. +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=f8d7bd2c69325537]*/ +/*[clinic end generated code: output=1b3177906dd237ee input=9e065db3232b8b1b]*/ { int res; From b7abf203da06fb059cf68037170d985f878c21d7 Mon Sep 17 00:00:00 2001 From: Noam Cohen Date: Tue, 26 Jul 2022 14:33:05 +0300 Subject: [PATCH 10/40] assume Linux platform has a working readlink --- Lib/test/test_posix.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/test/test_posix.py b/Lib/test/test_posix.py index 0ce12d4be6f2ec..0464096024f103 100644 --- a/Lib/test/test_posix.py +++ b/Lib/test/test_posix.py @@ -2177,7 +2177,6 @@ class NamespacesTests(unittest.TestCase): @unittest.skipUnless(hasattr(os, 'unshare'), 'needs os.unshare()') @unittest.skipUnless(hasattr(os, 'setns'), 'needs os.setns()') - @unittest.skipUnless(hasattr(os, 'readlink'), 'needs os.readlink()') @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): From af74db5e36eeee95cf3a1f200913c47745285220 Mon Sep 17 00:00:00 2001 From: Noam Cohen Date: Tue, 26 Jul 2022 14:33:16 +0300 Subject: [PATCH 11/40] change ifdefs --- Modules/clinic/posixmodule.c.h | 10 +++++----- Modules/posixmodule.c | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h index 15a70c663f63ec..3a0bf07cf7d279 100644 --- a/Modules/clinic/posixmodule.c.h +++ b/Modules/clinic/posixmodule.c.h @@ -4192,7 +4192,7 @@ os_pidfd_open(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObjec #endif /* (defined(__linux__) && defined(__NR_pidfd_open)) */ -#if (defined(__linux__) && defined(HAVE_SETNS)) +#if defined(HAVE_SETNS) PyDoc_STRVAR(os_setns__doc__, "setns($module, /, fd, nstype=0)\n" @@ -4243,9 +4243,9 @@ os_setns(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kw return return_value; } -#endif /* (defined(__linux__) && defined(HAVE_SETNS)) */ +#endif /* defined(HAVE_SETNS) */ -#if (defined(__linux__) && defined(HAVE_UNSHARE)) +#if defined(HAVE_UNSHARE) PyDoc_STRVAR(os_unshare__doc__, "unshare($module, /, flags)\n" @@ -4285,7 +4285,7 @@ os_unshare(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject * return return_value; } -#endif /* (defined(__linux__) && defined(HAVE_UNSHARE)) */ +#endif /* defined(HAVE_UNSHARE) */ #if (defined(HAVE_READLINK) || defined(MS_WINDOWS)) @@ -9455,4 +9455,4 @@ os_waitstatus_to_exitcode(PyObject *module, PyObject *const *args, Py_ssize_t na #ifndef OS_WAITSTATUS_TO_EXITCODE_METHODDEF #define OS_WAITSTATUS_TO_EXITCODE_METHODDEF #endif /* !defined(OS_WAITSTATUS_TO_EXITCODE_METHODDEF) */ -/*[clinic end generated code: output=78d14ea4616fdc42 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=8340ed29593534d2 input=a9049054013a1b77]*/ diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 144aa7c7350cd3..0488c7580bd877 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -8588,7 +8588,7 @@ os_pidfd_open_impl(PyObject *module, pid_t pid, unsigned int flags) #endif -#if defined(__linux__) && defined(HAVE_SETNS) +#ifdef HAVE_SETNS /*[clinic input] os.setns fd: fildes @@ -8618,7 +8618,7 @@ os_setns_impl(PyObject *module, int fd, int nstype) #endif -#if defined(__linux__) && defined(HAVE_UNSHARE) +#ifdef HAVE_UNSHARE /*[clinic input] os.unshare flags: int @@ -15437,7 +15437,7 @@ all_ins(PyObject *m) #endif /* constants for namespaces */ -#if defined(__linux__) && (defined(HAVE_SETNS) || defined(HAVE_UNSHARE)) +#if defined(HAVE_SETNS) || defined(HAVE_UNSHARE) #ifdef CLONE_FS if (PyModule_AddIntMacro(m, CLONE_FS)) return -1; #endif From 51811269c2123eca53e28be0a9b1b804150d5a06 Mon Sep 17 00:00:00 2001 From: Noam Cohen Date: Tue, 26 Jul 2022 14:42:04 +0300 Subject: [PATCH 12/40] added missing `CLONE_*` consts also, alphabetically order consts in doc --- Doc/library/os.rst | 16 ++++++++++------ Modules/posixmodule.c | 12 ++++++++++++ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/Doc/library/os.rst b/Doc/library/os.rst index ce6b960ebb2197..22b6a602dc3ce2 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -776,16 +776,20 @@ process and user. Flags to the :func:`unshare` function, if the implementation supports them. See the Linux manual for the exact effect and availability. -.. data:: CLONE_FS - CLONE_FILES - CLONE_NEWNS +.. data:: CLONE_FILES CLONE_NEWCGROUP - CLONE_NEWUTS CLONE_NEWIPC - CLONE_NEWUSER - CLONE_NEWPID CLONE_NEWNET + CLONE_NEWNS + CLONE_NEWPID CLONE_NEWTIME + CLONE_NEWUSER + CLONE_NEWUTS + CLONE_SIGHAND + CLONE_SYSVSEM + CLONE_THREAD + CLONE_VM + CLONE_FS .. _os-newstreams: diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 0488c7580bd877..0ba7166c01b76b 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -15468,6 +15468,18 @@ all_ins(PyObject *m) #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 From 3a37ac28fe3bbdbbdeadbddf20857c40fe64e79e Mon Sep 17 00:00:00 2001 From: Noam Cohen Date: Tue, 26 Jul 2022 15:14:00 +0300 Subject: [PATCH 13/40] run unshare in a different process --- Lib/test/test_posix.py | 56 ++++++++++++++++++++++++++++++------------ 1 file changed, 40 insertions(+), 16 deletions(-) diff --git a/Lib/test/test_posix.py b/Lib/test/test_posix.py index 0464096024f103..596e3ead1a377e 100644 --- a/Lib/test/test_posix.py +++ b/Lib/test/test_posix.py @@ -2175,29 +2175,53 @@ def test_utime(self): class NamespacesTests(unittest.TestCase): """Tests for os.unshare() and os.setns().""" + @support.requires_subprocess() + def subprocess(self, code): + import subprocess + with subprocess.Popen((sys.executable, '-c', code), + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + encoding="utf-8" + ) as p: + p.wait() + return ( + p.returncode, + tuple(p.stdout), + tuple(p.stderr), + ) + @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): - original = os.readlink('/proc/self/ns/uts') - original_fd = os.open('/proc/self/ns/uts', os.O_RDONLY) - self.addCleanup(os.close, original_fd) - - try: - os.unshare(os.CLONE_NEWUTS) - except OSError as e: - self.assertEqual(e.errno, errno.EPERM) - self.skipTest("unprivileged users cannot call unshare.") - - current = os.readlink('/proc/self/ns/uts') - self.assertNotEqual(original, current) + rc, out, err = self.subprocess(""" +import os +import sys +fd = os.open('/proc/self/ns/uts', os.O_RDONLY) +try: + print(os.readlink('/proc/self/ns/uts')) + os.unshare(os.CLONE_NEWUTS) + print(os.readlink('/proc/self/ns/uts')) + os.setns(fd, os.CLONE_NEWUTS) + print(os.readlink('/proc/self/ns/uts')) +except OSError as e: + sys.stderr.write(str(e.errno)) + sys.exit(2) +finally: + os.close(fd) + """) - self.assertRaises(OSError, os.setns, original_fd, os.CLONE_NEWNET) - os.setns(original_fd, os.CLONE_NEWUTS) + if rc == 2: + e = int(err[0]) + self.assertIn(e, (errno.EPERM, errno.EINVAL, errno.ENOSPC, errno.ENOSYS)) + self.skipTest(f"could not call os.unshare / os.setns [Errno {e}].") - current = os.readlink('/proc/self/ns/uts') - self.assertEqual(original, current) + self.assertEqual(rc, 0) + self.assertEqual(err, ()) + original, new, back = out + self.assertNotEqual(original, new) + self.assertEqual(original, back) def tearDownModule(): From 138683342360830bd6b535caf2332d57fb2af204 Mon Sep 17 00:00:00 2001 From: Noam Cohen Date: Tue, 26 Jul 2022 15:49:42 +0300 Subject: [PATCH 14/40] reformat doc --- Doc/library/os.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Doc/library/os.rst b/Doc/library/os.rst index 22b6a602dc3ce2..330839e06ac7de 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -581,7 +581,7 @@ process and user. subject to any constraints imposed by the *nstype*, which is a bit mask specified by combining one or more of the ``CLONE_NEW*`` constants using ``|`` (bitwise or). - the caller's memberships in unspecified namespaces are left unchanged. + The callers memberships in unspecified namespaces are left unchanged. *fd* can be any object with a :meth:`fileno` method, or a raw file descriptor. This example reassociates the thread with the ``init`` process' network namespace:: @@ -760,7 +760,8 @@ process and user. .. function:: unshare(flags) Disassociate parts of the process execution context. - The *flags* argument is a bit mask combining zero or more of the ``CLONE_*`` constants using ``|`` (bitwise or), that specifies which parts of the execution + The *flags* argument is a bit mask combining zero or more of the ``CLONE_*`` + constants using ``|`` (bitwise or), that specifies which parts of the execution context should be unshared. If *flags* is specified as zero, no changes are made to the calling process' execution context. @@ -777,6 +778,7 @@ Flags to the :func:`unshare` function, if the implementation supports them. See the Linux manual for the exact effect and availability. .. data:: CLONE_FILES + CLONE_FS CLONE_NEWCGROUP CLONE_NEWIPC CLONE_NEWNET @@ -789,7 +791,6 @@ See the Linux manual for the exact effect and availability. CLONE_SYSVSEM CLONE_THREAD CLONE_VM - CLONE_FS .. _os-newstreams: From ae4b6610d71ee22eae7e66364420c39a99729091 Mon Sep 17 00:00:00 2001 From: Noam Cohen Date: Tue, 26 Jul 2022 15:49:55 +0300 Subject: [PATCH 15/40] indent test code --- Lib/test/test_posix.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/Lib/test/test_posix.py b/Lib/test/test_posix.py index 596e3ead1a377e..6c111828ff1fd4 100644 --- a/Lib/test/test_posix.py +++ b/Lib/test/test_posix.py @@ -2195,21 +2195,21 @@ def subprocess(self, code): @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): - rc, out, err = self.subprocess(""" -import os -import sys -fd = os.open('/proc/self/ns/uts', os.O_RDONLY) -try: - print(os.readlink('/proc/self/ns/uts')) - os.unshare(os.CLONE_NEWUTS) - print(os.readlink('/proc/self/ns/uts')) - os.setns(fd, os.CLONE_NEWUTS) - print(os.readlink('/proc/self/ns/uts')) -except OSError as e: - sys.stderr.write(str(e.errno)) - sys.exit(2) -finally: - os.close(fd) + rc, out, err = self.subprocess("""if 1: + import os + import sys + fd = os.open('/proc/self/ns/uts', os.O_RDONLY) + try: + print(os.readlink('/proc/self/ns/uts')) + os.unshare(os.CLONE_NEWUTS) + print(os.readlink('/proc/self/ns/uts')) + os.setns(fd, os.CLONE_NEWUTS) + print(os.readlink('/proc/self/ns/uts')) + except OSError as e: + sys.stderr.write(str(e.errno)) + sys.exit(2) + finally: + os.close(fd) """) if rc == 2: From 57b2c844a1267232053a389c24f4feda0e5f06c1 Mon Sep 17 00:00:00 2001 From: Noam Cohen Date: Tue, 26 Jul 2022 16:07:30 +0300 Subject: [PATCH 16/40] Apply suggestions from code review Co-authored-by: Christian Heimes --- Doc/library/os.rst | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Doc/library/os.rst b/Doc/library/os.rst index 330839e06ac7de..bc0858e92d74ce 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -571,7 +571,8 @@ process and user. .. function:: setns(fd, nstype=0) - Reassociate thread with a namespace. + Reassociate thread with a namespace, see the :manpage:`setns(2)` man page for more details. + If *fd* refers to a ``/proc/[pid]/ns/`` link, ``setns()`` reassociates the calling thread with the namespace associated with that link, subject to any constraints imposed by the *nstype* argument (or any if ``0``). @@ -579,8 +580,8 @@ process and user. :manpage:`pidfd_open(2)`. In this case ``setns()`` reassociates the calling thread into one or more of the same namespaces as the thread referred to by *fd* subject to any constraints imposed by the *nstype*, which is - a bit mask specified by combining one or more of the ``CLONE_NEW*`` constants - using ``|`` (bitwise or). + a bit mask specified by combining one or more of the ``CLONE_NEW*`` constants, + e.g. ``setns(fd, os.CLONE_NEWUTS | os.CLONE_NEWPID)``. The callers memberships in unspecified namespaces are left unchanged. *fd* can be any object with a :meth:`fileno` method, or a raw file descriptor. @@ -772,7 +773,7 @@ process and user. .. seealso:: - The :func:`.setns` function. + The :func:`~os.setns` function. Flags to the :func:`unshare` function, if the implementation supports them. See the Linux manual for the exact effect and availability. From b2df7f7e16ad25c8195229824a92a1d7e24c471e Mon Sep 17 00:00:00 2001 From: Noam Cohen Date: Tue, 26 Jul 2022 16:10:58 +0300 Subject: [PATCH 17/40] better doc --- Doc/library/os.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Doc/library/os.rst b/Doc/library/os.rst index bc0858e92d74ce..6a6534fbefcb29 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -577,7 +577,7 @@ process and user. calling thread with the namespace associated with that link, subject to any constraints imposed by the *nstype* argument (or any if ``0``). Since Linux 5.8, *fd* may refer to a PID file descriptor obtained from - :manpage:`pidfd_open(2)`. In this case ``setns()`` reassociates the calling thread + :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* subject to any constraints imposed by the *nstype*, which is a bit mask specified by combining one or more of the ``CLONE_NEW*`` constants, @@ -760,7 +760,8 @@ process and user. .. function:: unshare(flags) - Disassociate parts of the process execution context. + Disassociate parts of the process execution context, see the :manpage:`unshare(2)` + man page for more details. The *flags* argument is a bit mask combining zero or more of the ``CLONE_*`` constants using ``|`` (bitwise or), that specifies which parts of the execution context should be unshared. From dc51d011731f6877aec3958b5037b901f2262be6 Mon Sep 17 00:00:00 2001 From: Noam Cohen Date: Tue, 26 Jul 2022 16:29:53 +0300 Subject: [PATCH 18/40] remove whitespaces from doc --- Doc/library/os.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/os.rst b/Doc/library/os.rst index 6a6534fbefcb29..ea059830769f74 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -572,7 +572,7 @@ process and user. .. function:: setns(fd, nstype=0) Reassociate thread with a namespace, see the :manpage:`setns(2)` man page for more details. - + If *fd* refers to a ``/proc/[pid]/ns/`` link, ``setns()`` reassociates the calling thread with the namespace associated with that link, subject to any constraints imposed by the *nstype* argument (or any if ``0``). From 5a3cff5b8c6af0fcae8449c904659971c6da56c4 Mon Sep 17 00:00:00 2001 From: Noam Cohen Date: Tue, 26 Jul 2022 16:33:40 +0300 Subject: [PATCH 19/40] fix NEWS entry --- .../2022-07-20-09-04-55.gh-issue-95023.bs-xd7.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Core and Builtins/2022-07-20-09-04-55.gh-issue-95023.bs-xd7.rst b/Misc/NEWS.d/next/Core and Builtins/2022-07-20-09-04-55.gh-issue-95023.bs-xd7.rst index ace582e6d7cbfd..bf0558ba79c766 100644 --- a/Misc/NEWS.d/next/Core and Builtins/2022-07-20-09-04-55.gh-issue-95023.bs-xd7.rst +++ b/Misc/NEWS.d/next/Core and Builtins/2022-07-20-09-04-55.gh-issue-95023.bs-xd7.rst @@ -1 +1 @@ -Implement :func:`os.setns` and :func:`os.unshare` for Linux. Patch by Noam Cohen. \ No newline at end of file +Implement :func:`os.setns` and :func:`os.unshare` for Linux. Patch by Noam Cohen. From 1d451967b15b91de8b06877d8021eabe95993bd8 Mon Sep 17 00:00:00 2001 From: Noam Cohen Date: Tue, 26 Jul 2022 17:01:51 +0300 Subject: [PATCH 20/40] move test code to another file --- Lib/test/namespaces-test.py | 21 +++++++++++++++++++++ Lib/test/test_posix.py | 21 +++------------------ 2 files changed, 24 insertions(+), 18 deletions(-) create mode 100644 Lib/test/namespaces-test.py diff --git a/Lib/test/namespaces-test.py b/Lib/test/namespaces-test.py new file mode 100644 index 00000000000000..18e7b5e7e1163f --- /dev/null +++ b/Lib/test/namespaces-test.py @@ -0,0 +1,21 @@ +import os +import sys + + +def main(): + fd = os.open('/proc/self/ns/uts', os.O_RDONLY) + try: + print(os.readlink('/proc/self/ns/uts')) + os.unshare(os.CLONE_NEWUTS) + print(os.readlink('/proc/self/ns/uts')) + os.setns(fd, os.CLONE_NEWUTS) + print(os.readlink('/proc/self/ns/uts')) + except OSError as e: + sys.stderr.write(str(e.errno)) + sys.exit(2) + finally: + os.close(fd) + + +if __name__ == '__main__': + main() diff --git a/Lib/test/test_posix.py b/Lib/test/test_posix.py index 6c111828ff1fd4..48725b72fd979c 100644 --- a/Lib/test/test_posix.py +++ b/Lib/test/test_posix.py @@ -2176,9 +2176,9 @@ class NamespacesTests(unittest.TestCase): """Tests for os.unshare() and os.setns().""" @support.requires_subprocess() - def subprocess(self, code): + def subprocess(self, file_path): import subprocess - with subprocess.Popen((sys.executable, '-c', code), + with subprocess.Popen((sys.executable, file_path), stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8" @@ -2195,22 +2195,7 @@ def subprocess(self, code): @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): - rc, out, err = self.subprocess("""if 1: - import os - import sys - fd = os.open('/proc/self/ns/uts', os.O_RDONLY) - try: - print(os.readlink('/proc/self/ns/uts')) - os.unshare(os.CLONE_NEWUTS) - print(os.readlink('/proc/self/ns/uts')) - os.setns(fd, os.CLONE_NEWUTS) - print(os.readlink('/proc/self/ns/uts')) - except OSError as e: - sys.stderr.write(str(e.errno)) - sys.exit(2) - finally: - os.close(fd) - """) + rc, out, err = self.subprocess(support.findfile("namespaces-test.py")) if rc == 2: e = int(err[0]) From 84c4b8cb00f4642b6ae9ff66391d76b51756469b Mon Sep 17 00:00:00 2001 From: Noam Cohen Date: Tue, 26 Jul 2022 17:22:22 +0300 Subject: [PATCH 21/40] add glibc requirements to doc --- Doc/library/os.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/os.rst b/Doc/library/os.rst index ea059830769f74..55f5e2a7302a5c 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -591,7 +591,7 @@ process and user. os.setns(fd, os.CLONE_NEWNET) os.close(fd) - .. availability:: Linux 3.0 or newer. + .. availability:: Linux 3.0 or newer with glibc 2.14 or newer. .. versionadded:: 3.12 From b9d3a347d133db3636f7ea44b442f0d68cd72e6f Mon Sep 17 00:00:00 2001 From: Noam Cohen Date: Tue, 26 Jul 2022 17:27:17 +0300 Subject: [PATCH 22/40] Revert "move test code to another file" This reverts commit 1d451967b15b91de8b06877d8021eabe95993bd8. --- Lib/test/namespaces-test.py | 21 --------------------- Lib/test/test_posix.py | 21 ++++++++++++++++++--- 2 files changed, 18 insertions(+), 24 deletions(-) delete mode 100644 Lib/test/namespaces-test.py diff --git a/Lib/test/namespaces-test.py b/Lib/test/namespaces-test.py deleted file mode 100644 index 18e7b5e7e1163f..00000000000000 --- a/Lib/test/namespaces-test.py +++ /dev/null @@ -1,21 +0,0 @@ -import os -import sys - - -def main(): - fd = os.open('/proc/self/ns/uts', os.O_RDONLY) - try: - print(os.readlink('/proc/self/ns/uts')) - os.unshare(os.CLONE_NEWUTS) - print(os.readlink('/proc/self/ns/uts')) - os.setns(fd, os.CLONE_NEWUTS) - print(os.readlink('/proc/self/ns/uts')) - except OSError as e: - sys.stderr.write(str(e.errno)) - sys.exit(2) - finally: - os.close(fd) - - -if __name__ == '__main__': - main() diff --git a/Lib/test/test_posix.py b/Lib/test/test_posix.py index 48725b72fd979c..6c111828ff1fd4 100644 --- a/Lib/test/test_posix.py +++ b/Lib/test/test_posix.py @@ -2176,9 +2176,9 @@ class NamespacesTests(unittest.TestCase): """Tests for os.unshare() and os.setns().""" @support.requires_subprocess() - def subprocess(self, file_path): + def subprocess(self, code): import subprocess - with subprocess.Popen((sys.executable, file_path), + with subprocess.Popen((sys.executable, '-c', code), stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="utf-8" @@ -2195,7 +2195,22 @@ def subprocess(self, file_path): @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): - rc, out, err = self.subprocess(support.findfile("namespaces-test.py")) + rc, out, err = self.subprocess("""if 1: + import os + import sys + fd = os.open('/proc/self/ns/uts', os.O_RDONLY) + try: + print(os.readlink('/proc/self/ns/uts')) + os.unshare(os.CLONE_NEWUTS) + print(os.readlink('/proc/self/ns/uts')) + os.setns(fd, os.CLONE_NEWUTS) + print(os.readlink('/proc/self/ns/uts')) + except OSError as e: + sys.stderr.write(str(e.errno)) + sys.exit(2) + finally: + os.close(fd) + """) if rc == 2: e = int(err[0]) From 51c60d431be645d42dee52c0979c8aa61a1c7b4b Mon Sep 17 00:00:00 2001 From: Noam Cohen Date: Tue, 26 Jul 2022 18:28:13 +0300 Subject: [PATCH 23/40] proofreaders review fixes --- Doc/library/os.rst | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/Doc/library/os.rst b/Doc/library/os.rst index 55f5e2a7302a5c..83ced1e8b519a0 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -571,21 +571,23 @@ process and user. .. function:: setns(fd, nstype=0) - Reassociate thread with a namespace, see the :manpage:`setns(2)` man page for more details. + Reassociate the current with a namespace. + See the :manpage:`setns(2)` man page for more details. If *fd* refers to a ``/proc/[pid]/ns/`` link, ``setns()`` reassociates the calling thread with the namespace associated with that link, subject to any - constraints imposed by the *nstype* argument (or any if ``0``). + constraints imposed by the *nstype* argument (a *nstype* of ``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* - subject to any constraints imposed by the *nstype*, which is - a bit mask specified by combining one or more of the ``CLONE_NEW*`` constants, - e.g. ``setns(fd, os.CLONE_NEWUTS | os.CLONE_NEWPID)``. - The callers memberships in unspecified namespaces are left unchanged. + subject to any constraints imposed by *nstype*, which is a bit mask specified + by combining one or more of the ``CLONE_NEW*`` constants, + e.g., as ``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:`fileno` method, or a raw file descriptor. - This example reassociates the thread with the ``init`` process' network namespace:: + 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) @@ -595,6 +597,10 @@ process and user. .. versionadded:: 3.12 + .. seealso:: + + The :func:`~os.unshare` function. + .. function:: setpgrp() Call the system call :c:func:`setpgrp` or ``setpgrp(0, 0)`` depending on @@ -760,12 +766,15 @@ process and user. .. function:: unshare(flags) - Disassociate parts of the process execution context, see the :manpage:`unshare(2)` + 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 ``CLONE_*`` constants using ``|`` (bitwise or), that specifies which parts of the execution - context should be unshared. - If *flags* is specified as zero, no changes are made to the calling process' + context should be unshared from their existing associations and moved to a + new namespace.. + If *flags* is specified as zero, no changes are made to the calling process's execution context. .. availability:: Linux 2.6.16 or newer. From 225e06bfc2853e638ae552ff58d47a524e0df7b1 Mon Sep 17 00:00:00 2001 From: Noam Cohen Date: Wed, 27 Jul 2022 02:49:01 +0300 Subject: [PATCH 24/40] fix doc whitespaces --- Doc/library/os.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/os.rst b/Doc/library/os.rst index 83ced1e8b519a0..4f6c260478c26a 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -576,7 +576,7 @@ process and user. If *fd* refers to a ``/proc/[pid]/ns/`` link, ``setns()`` reassociates the calling thread with the namespace associated with that link, subject to any - constraints imposed by the *nstype* argument (a *nstype* of ``0`` means no + constraints imposed by the *nstype* argument (a *nstype* of ``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 From fb64bb76fa6a4c12b00a82d370da501f996bb581 Mon Sep 17 00:00:00 2001 From: Noam Cohen Date: Wed, 27 Jul 2022 08:52:18 +0300 Subject: [PATCH 25/40] better documentation Apply suggestions from code review Co-authored-by: CAM Gerlach --- Doc/library/os.rst | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Doc/library/os.rst b/Doc/library/os.rst index 4f6c260478c26a..7bf9650cd25e6e 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -574,18 +574,18 @@ process and user. Reassociate the current with a namespace. See the :manpage:`setns(2)` man page for more details. - If *fd* refers to a ``/proc/[pid]/ns/`` link, ``setns()`` reassociates the + If *fd* refers to a :file:`/proc/{pid}/ns/` link, ``setns()`` reassociates the calling thread with the namespace associated with that link, subject to any constraints imposed by the *nstype* argument (a *nstype* of ``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 + :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* subject to any constraints imposed by *nstype*, which is a bit mask specified by combining one or more of the ``CLONE_NEW*`` constants, e.g., as ``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:`fileno` method, or a raw file descriptor. + *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:: @@ -770,11 +770,11 @@ process and user. 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 ``CLONE_*`` + The *flags* argument is a bit mask, combining zero or more of the ``CLONE_*`` constants using ``|`` (bitwise or), that specifies which parts of the execution context should be unshared from their existing associations and moved to a - new namespace.. - If *flags* is specified as zero, no changes are made to the calling process's + new namespace. + If the *flags* argument is ``0``, no changes are made to the calling process's execution context. .. availability:: Linux 2.6.16 or newer. @@ -803,6 +803,7 @@ See the Linux manual for the exact effect and availability. CLONE_THREAD CLONE_VM + .. _os-newstreams: File Object Creation From 01d4af4bdd1d1f6baec0f3affaf870af0ecb93c3 Mon Sep 17 00:00:00 2001 From: Noam Cohen Date: Wed, 27 Jul 2022 09:27:41 +0300 Subject: [PATCH 26/40] fix docs --- Doc/library/os.rst | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Doc/library/os.rst b/Doc/library/os.rst index 7bf9650cd25e6e..19ca7eb4e08b56 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -571,8 +571,9 @@ process and user. .. function:: setns(fd, nstype=0) - Reassociate the current with a namespace. - See the :manpage:`setns(2)` man page for more details. + 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, subject to any @@ -585,7 +586,7 @@ process and user. by combining one or more of the ``CLONE_NEW*`` constants, e.g., as ``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. + *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:: @@ -771,7 +772,7 @@ process and user. See the :manpage:`unshare(2)` man page for more details. The *flags* argument is a bit mask, combining zero or more of the ``CLONE_*`` - constants using ``|`` (bitwise or), that specifies which parts of the execution + constants using ``|`` (:func:`bitwise or `), 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 @@ -786,7 +787,8 @@ process and user. The :func:`~os.setns` function. Flags to the :func:`unshare` function, if the implementation supports them. -See the Linux manual for the exact effect and availability. +See :manpage:`unshare(2)` in the Linux manual +for their exact effect and availability. .. data:: CLONE_FILES CLONE_FS From 15e6d8bb1b96827ffc8ae51111f2d790c7ddf514 Mon Sep 17 00:00:00 2001 From: Noam Cohen Date: Wed, 27 Jul 2022 09:37:27 +0300 Subject: [PATCH 27/40] remove bitwise or explanation --- Doc/library/os.rst | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Doc/library/os.rst b/Doc/library/os.rst index 19ca7eb4e08b56..8cd5423a89a517 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -772,9 +772,8 @@ process and user. See the :manpage:`unshare(2)` man page for more details. The *flags* argument is a bit mask, combining zero or more of the ``CLONE_*`` - constants using ``|`` (:func:`bitwise or `), that specifies which parts of the execution - context should be unshared from their existing associations and moved to a - new namespace. + constants, 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. From 375165ba9c1c86702c90272c91481dfeb9897631 Mon Sep 17 00:00:00 2001 From: Noam Cohen Date: Thu, 28 Jul 2022 08:14:06 +0300 Subject: [PATCH 28/40] Apply suggestions from code review Co-authored-by: CAM Gerlach --- Doc/library/os.rst | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/Doc/library/os.rst b/Doc/library/os.rst index 8cd5423a89a517..5986a40a3420ce 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -576,16 +576,21 @@ process and user. details. If *fd* refers to a :file:`/proc/{pid}/ns/` link, ``setns()`` reassociates the - calling thread with the namespace associated with that link, subject to any - constraints imposed by the *nstype* argument (a *nstype* of ``0`` means no - constraints). + calling thread with the namespace associated with that link, + and *nstype* may be set to one of the + :ref:`CLONE_NEW* constants ` + 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* - subject to any constraints imposed by *nstype*, which is a bit mask specified - by combining one or more of the ``CLONE_NEW*`` constants, - e.g., as ``setns(fd, os.CLONE_NEWUTS | os.CLONE_NEWPID)``. + 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 is a bit mask combining one or more of the + :ref:`CLONE_NEW* constants `, + 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:: @@ -785,6 +790,8 @@ process and user. 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. From 7e2b44c0c9dbc940e5b683fb2d1d671cd635aec7 Mon Sep 17 00:00:00 2001 From: Noam Cohen Date: Thu, 28 Jul 2022 08:14:55 +0300 Subject: [PATCH 29/40] fix typo --- Doc/library/os.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/os.rst b/Doc/library/os.rst index 5986a40a3420ce..9dad7d071ea30c 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -586,7 +586,7 @@ process and user. :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 is a bit mask combining one or more of the + which is a bit mask combining one or more of the :ref:`CLONE_NEW* constants `, e.g. ``setns(fd, os.CLONE_NEWUTS | os.CLONE_NEWPID)``. The caller's memberships in unspecified namespaces are left unchanged. From 3ae952c8ecd88f288a2fe452dfeab58afd249e46 Mon Sep 17 00:00:00 2001 From: Noam Cohen Date: Thu, 28 Jul 2022 08:22:38 +0300 Subject: [PATCH 30/40] ref --- Doc/library/os.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Doc/library/os.rst b/Doc/library/os.rst index 9dad7d071ea30c..289eeba7c78664 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -776,8 +776,9 @@ process and user. 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 ``CLONE_*`` - constants, that specifies which parts of the execution context should be + The *flags* argument is a bit mask, combining zero or more of the + :ref:`CLONE_* constants `, + 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. From 432d2744f392f4a7834ddef0b4e29827e9abd346 Mon Sep 17 00:00:00 2001 From: Noam Cohen Date: Thu, 28 Jul 2022 08:25:09 +0300 Subject: [PATCH 31/40] fix doc whitespace --- Doc/library/os.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/os.rst b/Doc/library/os.rst index 289eeba7c78664..fbf2d377283cbb 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -776,7 +776,7 @@ process and user. 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 + The *flags* argument is a bit mask, combining zero or more of the :ref:`CLONE_* constants `, that specifies which parts of the execution context should be unshared from their existing associations and moved to a new namespace. From 01713ec94a059d258e4d0a64ec8743bb1b6863ec Mon Sep 17 00:00:00 2001 From: Noam Cohen Date: Thu, 28 Jul 2022 08:28:48 +0300 Subject: [PATCH 32/40] regen posixmodule --- Modules/clinic/posixmodule.c.h | 105 ++++++++++++++++++++++++++++++++- 1 file changed, 104 insertions(+), 1 deletion(-) diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h index ddd41cae3ec452..c3b6ea22ded359 100644 --- a/Modules/clinic/posixmodule.c.h +++ b/Modules/clinic/posixmodule.c.h @@ -4192,6 +4192,101 @@ os_pidfd_open(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObjec #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; + static const char * const _keywords[] = {"fd", "nstype", NULL}; + static _PyArg_Parser _parser = {NULL, _keywords, "setns", 0}; + 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; + static const char * const _keywords[] = {"flags", NULL}; + static _PyArg_Parser _parser = {NULL, _keywords, "unshare", 0}; + 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__, @@ -9077,6 +9172,14 @@ os_waitstatus_to_exitcode(PyObject *module, PyObject *const *args, Py_ssize_t na #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) */ @@ -9360,4 +9463,4 @@ os_waitstatus_to_exitcode(PyObject *module, PyObject *const *args, Py_ssize_t na #ifndef OS_WAITSTATUS_TO_EXITCODE_METHODDEF #define OS_WAITSTATUS_TO_EXITCODE_METHODDEF #endif /* !defined(OS_WAITSTATUS_TO_EXITCODE_METHODDEF) */ -/*[clinic end generated code: output=c22a8b6de4a0ccb7 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=47c67bd1b48a05f3 input=a9049054013a1b77]*/ From a6bb345e97c8291734d775f8c0d32dc728285010 Mon Sep 17 00:00:00 2001 From: Noam Cohen Date: Thu, 28 Jul 2022 10:47:50 +0300 Subject: [PATCH 33/40] add name to `Misc/ACKS` --- Misc/ACKS | 1 + 1 file changed, 1 insertion(+) diff --git a/Misc/ACKS b/Misc/ACKS index 32475f874c36db..1382aed4d1dd13 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -342,6 +342,7 @@ Hervé Coatanhay Riccardo Coccioli Nick Coghlan Josh Cogliati +Noam Cohen Dave Cole Terrence Cole Benjamin Collar From dac402ade88393f2c696ae16206f9690c5fa5e88 Mon Sep 17 00:00:00 2001 From: Noam Cohen Date: Wed, 17 Aug 2022 08:07:37 +0300 Subject: [PATCH 34/40] fix doc availability --- Doc/library/os.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/library/os.rst b/Doc/library/os.rst index cb798205d81988..b80d4556fc4b0b 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -615,7 +615,7 @@ process and user. os.setns(fd, os.CLONE_NEWNET) os.close(fd) - .. availability:: Linux 3.0 or newer with glibc 2.14 or newer. + .. availability:: Linux >= 3.0 with glibc >= 2.14. .. versionadded:: 3.12 @@ -802,7 +802,7 @@ process and user. If the *flags* argument is ``0``, no changes are made to the calling process's execution context. - .. availability:: Linux 2.6.16 or newer. + .. availability:: Linux >= 2.6.16. .. versionadded:: 3.12 From ff7f961c4a9e8380fae20f5b7bdf197adda473a6 Mon Sep 17 00:00:00 2001 From: Noam Cohen Date: Wed, 17 Aug 2022 08:18:19 +0300 Subject: [PATCH 35/40] regen global strings --- Include/internal/pycore_global_strings.h | 1 + Include/internal/pycore_runtime_init_generated.h | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/Include/internal/pycore_global_strings.h b/Include/internal/pycore_global_strings.h index aada220395023d..c136916273c4e4 100644 --- a/Include/internal/pycore_global_strings.h +++ b/Include/internal/pycore_global_strings.h @@ -482,6 +482,7 @@ struct _Py_global_strings { 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) diff --git a/Include/internal/pycore_runtime_init_generated.h b/Include/internal/pycore_runtime_init_generated.h index 09890cd812015b..66067dde961f53 100644 --- a/Include/internal/pycore_runtime_init_generated.h +++ b/Include/internal/pycore_runtime_init_generated.h @@ -991,6 +991,7 @@ extern "C" { INIT_ID(node_depth), \ INIT_ID(node_offset), \ INIT_ID(ns), \ + INIT_ID(nstype), \ INIT_ID(number), \ INIT_ID(obj), \ INIT_ID(object), \ @@ -2284,6 +2285,8 @@ _PyUnicode_InitStaticStrings(void) { 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); @@ -6491,6 +6494,10 @@ _PyStaticObjects_CheckRefcnt(void) { _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"); From de7dd3d4ac763c8bf541e506d7142cb0d0d938f9 Mon Sep 17 00:00:00 2001 From: Noam Cohen Date: Mon, 17 Oct 2022 23:03:37 +0300 Subject: [PATCH 36/40] do not use stdout for test --- Lib/test/test_posix.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Lib/test/test_posix.py b/Lib/test/test_posix.py index 291b8347528f05..c72b08ca7929a8 100644 --- a/Lib/test/test_posix.py +++ b/Lib/test/test_posix.py @@ -2217,16 +2217,20 @@ def subprocess(self, code): @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): - rc, out, err = self.subprocess("""if 1: + rc, _, err = self.subprocess("""if 1: import os import sys fd = os.open('/proc/self/ns/uts', os.O_RDONLY) try: - print(os.readlink('/proc/self/ns/uts')) + original = os.readlink('/proc/self/ns/uts') os.unshare(os.CLONE_NEWUTS) - print(os.readlink('/proc/self/ns/uts')) + new = os.readlink('/proc/self/ns/uts') + if original == new: + raise Exception('os.unshare failed') os.setns(fd, os.CLONE_NEWUTS) - print(os.readlink('/proc/self/ns/uts')) + restored = os.readlink('/proc/self/ns/uts') + if original != restored: + raise Exception('os.setns failed') except OSError as e: sys.stderr.write(str(e.errno)) sys.exit(2) @@ -2241,9 +2245,6 @@ def test_unshare_setns(self): self.assertEqual(rc, 0) self.assertEqual(err, ()) - original, new, back = out - self.assertNotEqual(original, new) - self.assertEqual(original, back) def tearDownModule(): From 5c1bbddef4dd13f0361059fb4ab9bc758ec71123 Mon Sep 17 00:00:00 2001 From: Noam Cohen Date: Wed, 19 Oct 2022 07:55:43 +0300 Subject: [PATCH 37/40] move all test login to subprocess --- Lib/test/test_posix.py | 43 +++++++++++++++--------------------------- 1 file changed, 15 insertions(+), 28 deletions(-) diff --git a/Lib/test/test_posix.py b/Lib/test/test_posix.py index c72b08ca7929a8..928a31da76ceed 100644 --- a/Lib/test/test_posix.py +++ b/Lib/test/test_posix.py @@ -2197,33 +2197,22 @@ def test_utime(self): class NamespacesTests(unittest.TestCase): """Tests for os.unshare() and os.setns().""" - @support.requires_subprocess() - def subprocess(self, code): - import subprocess - with subprocess.Popen((sys.executable, '-c', code), - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - encoding="utf-8" - ) as p: - p.wait() - return ( - p.returncode, - tuple(p.stdout), - tuple(p.stderr), - ) - @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): - rc, _, err = self.subprocess("""if 1: + 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') - os.unshare(os.CLONE_NEWUTS) + try: + os.unshare(os.CLONE_NEWUTS) + except OSError as e: + if e.errno not in (errno.ENOSPC,): + raise new = os.readlink('/proc/self/ns/uts') if original == new: raise Exception('os.unshare failed') @@ -2231,20 +2220,18 @@ def test_unshare_setns(self): 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: - sys.stderr.write(str(e.errno)) - sys.exit(2) + if e.errno not in (errno.ENOSYS, errno.EINVAL, errno.ENOMEM): + raise finally: os.close(fd) - """) - - if rc == 2: - e = int(err[0]) - self.assertIn(e, (errno.EPERM, errno.EINVAL, errno.ENOSPC, errno.ENOSYS)) - self.skipTest(f"could not call os.unshare / os.setns [Errno {e}].") + """ - self.assertEqual(rc, 0) - self.assertEqual(err, ()) + assert_python_ok("-c", code) def tearDownModule(): From 29129d9ac37fbc583c541307c2c545a5e8b01cf6 Mon Sep 17 00:00:00 2001 From: Noam Cohen Date: Thu, 20 Oct 2022 07:52:53 +0300 Subject: [PATCH 38/40] Update Lib/test/test_posix.py Co-authored-by: Victor Stinner --- Lib/test/test_posix.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_posix.py b/Lib/test/test_posix.py index 928a31da76ceed..45469a58593d10 100644 --- a/Lib/test/test_posix.py +++ b/Lib/test/test_posix.py @@ -2211,7 +2211,7 @@ def test_unshare_setns(self): try: os.unshare(os.CLONE_NEWUTS) except OSError as e: - if e.errno not in (errno.ENOSPC,): + if e.errno != errno.ENOSPC: raise new = os.readlink('/proc/self/ns/uts') if original == new: From 94883a4d32efac4a1de9d5f83e686fe09f17873d Mon Sep 17 00:00:00 2001 From: Noam Cohen Date: Thu, 20 Oct 2022 08:30:55 +0300 Subject: [PATCH 39/40] add comments to tests --- Lib/test/test_posix.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Lib/test/test_posix.py b/Lib/test/test_posix.py index 45469a58593d10..348fe0fdceb16a 100644 --- a/Lib/test/test_posix.py +++ b/Lib/test/test_posix.py @@ -2225,6 +2225,10 @@ def test_unshare_setns(self): # 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: From 3e365d23d596d1178e1cda9c31dcd9eebeddd85b Mon Sep 17 00:00:00 2001 From: Noam Cohen Date: Thu, 20 Oct 2022 08:32:28 +0300 Subject: [PATCH 40/40] bugfix in test while handling `ENOSPC` --- Lib/test/test_posix.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_posix.py b/Lib/test/test_posix.py index 348fe0fdceb16a..6f66c85bcef414 100644 --- a/Lib/test/test_posix.py +++ b/Lib/test/test_posix.py @@ -2205,14 +2205,17 @@ 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: - raise + 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')