Skip to content

Commit 0bdd8bc

Browse files
committed
gh-95023: added os.setns and os.unshare for namespaces switching on Linux
1 parent 49aeff4 commit 0bdd8bc

File tree

8 files changed

+271
-1
lines changed

8 files changed

+271
-1
lines changed

Doc/library/os.rst

+30
Original file line numberDiff line numberDiff line change
@@ -569,6 +569,15 @@ process and user.
569569
See the documentation for :func:`getgroups` for cases where it may not
570570
return the same group list set by calling setgroups().
571571

572+
.. function:: setns(fd, flags=0)
573+
574+
Call the system call :c:func:`setns`. See the Linux manual for the semantics.
575+
For flags see the ``CLONE_NEW*`` constants.
576+
577+
.. availability:: Linux 3.0 or newer.
578+
579+
.. versionadded:: 3.12
580+
572581
.. function:: setpgrp()
573582

574583
Call the system call :c:func:`setpgrp` or ``setpgrp(0, 0)`` depending on
@@ -732,6 +741,27 @@ process and user.
732741
The function is now always available and is also available on Windows.
733742

734743

744+
.. function:: unshare(flags)
745+
746+
Call the system call :c:func:`unshare`. See the Linux manual for the semantics.
747+
748+
.. availability:: Linux 2.6.16 or newer.
749+
750+
.. versionadded:: 3.12
751+
752+
Parameters to the :func:`unshare` function, if the implementation supports them.
753+
754+
.. data:: CLONE_FS
755+
CLONE_FILES
756+
CLONE_NEWNS
757+
CLONE_NEWCGROUP
758+
CLONE_NEWUTS
759+
CLONE_NEWIPC
760+
CLONE_NEWUSER
761+
CLONE_NEWPID
762+
CLONE_NEWNET
763+
CLONE_NEWTIME
764+
735765
.. _os-newstreams:
736766

737767
File Object Creation

Lib/test/test_posix.py

+29
Original file line numberDiff line numberDiff line change
@@ -2172,6 +2172,35 @@ def test_utime(self):
21722172
os.utime("path", dir_fd=0)
21732173

21742174

2175+
class NamespacesTests(unittest.TestCase):
2176+
"""Tests for os.unshare() and os.setns()."""
2177+
2178+
@unittest.skipUnless(hasattr(os, 'unshare'), 'needs os.unshare()')
2179+
@unittest.skipUnless(hasattr(os, 'setns'), 'needs os.setns()')
2180+
@unittest.skipUnless(hasattr(os, 'readlink'), 'needs os.readlink()')
2181+
@unittest.skipUnless(os.path.exists('/proc/self/ns/uts'), 'need /proc/self/ns/uts')
2182+
@support.requires_linux_version(3, 0, 0)
2183+
def test_unshare_setns(self):
2184+
original = os.readlink('/proc/self/ns/uts')
2185+
original_fd = os.open('/proc/self/ns/uts', os.O_RDONLY)
2186+
self.addCleanup(os.close, original_fd)
2187+
2188+
try:
2189+
os.unshare(os.CLONE_NEWUTS)
2190+
except OSError as e:
2191+
self.assertEqual(e.errno, errno.EPERM)
2192+
self.skipTest("unprivileged users cannot call unshare.")
2193+
2194+
current = os.readlink('/proc/self/ns/uts')
2195+
self.assertNotEqual(original, current)
2196+
2197+
self.assertRaises(OSError, os.setns, original_fd, os.CLONE_NEWNET)
2198+
os.setns(original_fd, os.CLONE_NEWUTS)
2199+
2200+
current = os.readlink('/proc/self/ns/uts')
2201+
self.assertEqual(original, current)
2202+
2203+
21752204
def tearDownModule():
21762205
support.reap_children()
21772206

Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Implement :func:`os.setns` and :func:`os.unshare` for Linux. Patch by Noam Cohen

Modules/clinic/posixmodule.c.h

+96-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Modules/posixmodule.c

+92
Original file line numberDiff line numberDiff line change
@@ -8588,6 +8588,61 @@ os_pidfd_open_impl(PyObject *module, pid_t pid, unsigned int flags)
85888588
#endif
85898589

85908590

8591+
#if defined(__linux__) && defined(HAVE_SETNS)
8592+
/*[clinic input]
8593+
os.setns
8594+
fd: fildes
8595+
nstype: int = 0
8596+
8597+
Allows the calling thread to move into different namespaces.
8598+
[clinic start generated code]*/
8599+
8600+
static PyObject *
8601+
os_setns_impl(PyObject *module, int fd, int nstype)
8602+
/*[clinic end generated code: output=5dbd055bfb66ecd0 input=c097c9aa123c43ce]*/
8603+
{
8604+
int res;
8605+
8606+
Py_BEGIN_ALLOW_THREADS
8607+
res = setns(fd, nstype);
8608+
Py_END_ALLOW_THREADS
8609+
8610+
if (res != 0) {
8611+
return posix_error();
8612+
}
8613+
8614+
Py_RETURN_NONE;
8615+
}
8616+
#endif
8617+
8618+
8619+
#if defined(__linux__) && defined(HAVE_UNSHARE)
8620+
/*[clinic input]
8621+
os.unshare
8622+
flags: int
8623+
8624+
Allows a process (or thread) to disassociate parts of its execution context.
8625+
[clinic start generated code]*/
8626+
8627+
static PyObject *
8628+
os_unshare_impl(PyObject *module, int flags)
8629+
/*[clinic end generated code: output=1b3177906dd237ee input=f8d7bd2c69325537]*/
8630+
{
8631+
int res;
8632+
8633+
Py_BEGIN_ALLOW_THREADS
8634+
res = unshare(flags);
8635+
Py_END_ALLOW_THREADS
8636+
8637+
if (res != 0) {
8638+
return posix_error();
8639+
}
8640+
8641+
Py_RETURN_NONE;
8642+
}
8643+
#endif
8644+
8645+
85918646
#if defined(HAVE_READLINK) || defined(MS_WINDOWS)
85928647
/*[clinic input]
85938648
os.readlink
@@ -14930,6 +14985,8 @@ static PyMethodDef posix_methods[] = {
1493014985
OS__ADD_DLL_DIRECTORY_METHODDEF
1493114986
OS__REMOVE_DLL_DIRECTORY_METHODDEF
1493214987
OS_WAITSTATUS_TO_EXITCODE_METHODDEF
14988+
OS_SETNS_METHODDEF
14989+
OS_UNSHARE_METHODDEF
1493314990
{NULL, NULL} /* Sentinel */
1493414991
};
1493514992

@@ -15375,6 +15432,41 @@ all_ins(PyObject *m)
1537515432
#ifdef SCHED_FX
1537615433
if (PyModule_AddIntConstant(m, "SCHED_FX", SCHED_FSS)) return -1;
1537715434
#endif
15435+
15436+
/* constants for namespaces */
15437+
#if defined(__linux__) && (defined(HAVE_SETNS) || defined(HAVE_UNSHARE))
15438+
#ifdef CLONE_FS
15439+
if (PyModule_AddIntMacro(m, CLONE_FS)) return -1;
15440+
#endif
15441+
#ifdef CLONE_FILES
15442+
if (PyModule_AddIntMacro(m, CLONE_FILES)) return -1;
15443+
#endif
15444+
#ifdef CLONE_NEWNS
15445+
if (PyModule_AddIntMacro(m, CLONE_NEWNS)) return -1;
15446+
#endif
15447+
#ifdef CLONE_NEWCGROUP
15448+
if (PyModule_AddIntMacro(m, CLONE_NEWCGROUP)) return -1;
15449+
#endif
15450+
#ifdef CLONE_NEWUTS
15451+
if (PyModule_AddIntMacro(m, CLONE_NEWUTS)) return -1;
15452+
#endif
15453+
#ifdef CLONE_NEWIPC
15454+
if (PyModule_AddIntMacro(m, CLONE_NEWIPC)) return -1;
15455+
#endif
15456+
#ifdef CLONE_NEWUSER
15457+
if (PyModule_AddIntMacro(m, CLONE_NEWUSER)) return -1;
15458+
#endif
15459+
#ifdef CLONE_NEWPID
15460+
if (PyModule_AddIntMacro(m, CLONE_NEWPID)) return -1;
15461+
#endif
15462+
#ifdef CLONE_NEWNET
15463+
if (PyModule_AddIntMacro(m, CLONE_NEWNET)) return -1;
15464+
#endif
15465+
#ifdef CLONE_NEWTIME
15466+
if (PyModule_AddIntMacro(m, CLONE_NEWTIME)) return -1;
15467+
#endif
15468+
#endif
15469+
1537815470
#endif
1537915471

1538015472
#ifdef USE_XATTRS

configure

+14
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

configure.ac

+3
Original file line numberDiff line numberDiff line change
@@ -4937,6 +4937,9 @@ AC_CHECK_FUNCS(setpgrp,
49374937
[])
49384938
)
49394939

4940+
# check for namespace functions
4941+
AC_CHECK_FUNCS(setns unshare)
4942+
49404943
dnl We search for both crypt and crypt_r as one or the other may be defined
49414944
dnl libxcrypt provides <crypt.h> and libcrypt with crypt_r() since
49424945
dnl at least 3.1.1 from 2015.

pyconfig.h.in

+6
Original file line numberDiff line numberDiff line change
@@ -1019,6 +1019,9 @@
10191019
/* Define to 1 if you have the `setlocale' function. */
10201020
#undef HAVE_SETLOCALE
10211021

1022+
/* Define to 1 if you have the `setns' function. */
1023+
#undef HAVE_SETNS
1024+
10221025
/* Define to 1 if you have the `setpgid' function. */
10231026
#undef HAVE_SETPGID
10241027

@@ -1383,6 +1386,9 @@
13831386
/* Define to 1 if you have the `unlinkat' function. */
13841387
#undef HAVE_UNLINKAT
13851388

1389+
/* Define to 1 if you have the `unshare' function. */
1390+
#undef HAVE_UNSHARE
1391+
13861392
/* Define if you have a useable wchar_t type defined in wchar.h; useable means
13871393
wchar_t must be an unsigned type with at least 16 bits. (see
13881394
Include/unicodeobject.h). */

0 commit comments

Comments
 (0)