10000 gh-95023: Added os.setns and os.unshare functions (#95046) · python/cpython@a371a7e · GitHub
[go: up one dir, main page]

Skip to content

Commit a371a7e

Browse files
noamcohen97tiranCAM-Gerlachvstinner
authored
gh-95023: Added os.setns and os.unshare functions (#95046)
Added os.setns and os.unshare to easily switch between namespaces on Linux. Co-authored-by: Christian Heimes <christian@python.org> Co-authored-by: CAM Gerlach <CAM.Gerlach@Gerlach.CAM> Co-authored-by: Victor Stinner <vstinner@python.org>
1 parent c1e02d4 commit a371a7e

File tree

11 files changed

+418
-1
lines changed

11 files changed

+418
-1
lines changed

Doc/library/os.rst

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -590,6 +590,44 @@ process and user.
590590
See the documentation for :func:`getgroups` for cases where it may not
591591
return the same group list set by calling setgroups().
592592

593+
.. function:: setns(fd, nstype=0)
594+
595+
Reassociate the current thread with a Linux namespace.
596+
See the :manpage:`setns(2)` and :manpage:`namespaces(7)` man pages for more
597+
details.
598+
599+
If *fd* refers to a :file:`/proc/{pid}/ns/` link, ``setns()`` reassociates the
600+
calling thread with the namespace associated with that link,
601+
and *nstype* may be set to one of the
602+
:ref:`CLONE_NEW* constants <os-unshare-clone-flags>`
603+
to impose constraints on the operation
604+
(``0`` means no constraints).
605+
606+
Since Linux 5.8, *fd* may refer to a PID file descriptor obtained from
607+
:func:`~os.pidfd_open`. In this case, ``setns()`` reassociates the calling thread
608+
into one or more of the same namespaces as the thread referred to by *fd*.
609+
This is subject to any constraints imposed by *nstype*,
610+
which is a bit mask combining one or more of the
611+
:ref:`CLONE_NEW* constants <os-unshare-clone-flags>`,
612+
e.g. ``setns(fd, os.CLONE_NEWUTS | os.CLONE_NEWPID)``.
613+
The caller's memberships in unspecified namespaces are left unchanged.
614+
615+
*fd* can be any object with a :meth:`~io.IOBase.fileno` method, or a raw file descriptor.
616+
617+
This example reassociates the thread with the ``init`` process's network namespace::
618+
619+
fd = os.open("/proc/1/ns/net", os.O_RDONLY)
620+
os.setns(fd, os.CLONE_NEWNET)
621+
os.close(fd)
622+
623+
.. availability:: Linux >= 3.0 with glibc >= 2.14.
624+
625+
.. versionadded:: 3.12
626+
627+
.. seealso::
628+
629+
The :func:`~os.unshare` function.
630+
593631
.. function:: setpgrp()
594632

595633
Call the system call :c:func:`setpgrp` or ``setpgrp(0, 0)`` depending on
@@ -756,6 +794,49 @@ process and user.
756794
The function is now always available and is also available on Windows.
757795

758796

797+
.. function:: unshare(flags)
798+
799+
Disassociate parts of the process execution context, and move them into a
800+
newly created namespace.
801+
See the :manpage:`unshare(2)`
802+
man page for more details.
803+
The *flags* argument is a bit mask, combining zero or more of the
804+
:ref:`CLONE_* constants <os-unshare-clone-flags>`,
805+
that specifies which parts of the execution context should be
806+
unshared from their existing associations and moved to a new namespace.
807+
If the *flags* argument is ``0``, no changes are made to the calling process's
808+
execution context.
809+
810+
.. availability:: Linux >= 2.6.16.
811+
812+
.. versionadded:: 3.12
813+
814+
.. seealso::
815+
816+
The :func:`~os.setns` function.
817+
818+
.. _os-unshare-clone-flags:
819+
820+
Flags to the :func:`unshare` function, if the implementation supports them.
821+
See :manpage:`unshare(2)` in the Linux manual
822+
for their exact effect and availability.
823+
824+
.. data:: CLONE_FILES
825+
CLONE_FS
826+
CLONE_NEWCGROUP
827+
CLONE_NEWIPC
828+
CLONE_NEWNET
829+
CLONE_NEWNS
830+
CLONE_NEWPID
831+
CLONE_NEWTIME
832+
CLONE_NEWUSER
833+
CLONE_NEWUTS
834+
CLONE_SIGHAND
835+
CLONE_SYSVSEM
836+
CLONE_THREAD
837+
CLONE_VM
838+
839+
759840
.. _os-newstreams:
760841

761842
File Object Creation

Include/internal/pycore_global_strings.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -489,6 +489,7 @@ struct _Py_global_strings {
489489
STRUCT_FOR_ID(node_depth)
490490
STRUCT_FOR_ID(node_offset)
491491
STRUCT_FOR_ID(ns)
492+
STRUCT_FOR_ID(nstype)
492493
STRUCT_FOR_ID(number)
493494
STRUCT_FOR_ID(obj)
494495
STRUCT_FOR_ID(object)

Include/internal/pycore_runtime_init_generated.h

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Lib/test/test_posix.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2191,6 +2191,53 @@ def test_utime(self):
21912191
os.utime("path", dir_fd=0)
21922192

21932193

2194+
class NamespacesTests(unittest.TestCase):
2195+
"""Tests for os.unshare() and os.setns()."""
2196+
2197+
@unittest.skipUnless(hasattr(os, 'unshare'), 'needs os.unshare()')
2198+
@unittest.skipUnless(hasattr(os, 'setns'), 'needs os.setns()')
2199+
@unittest.skipUnless(os.path.exists('/proc/self/ns/uts'), 'need /proc/self/ns/uts')
2200+
@support.requires_linux_version(3, 0, 0)
2201+
def test_unshare_setns(self):
2202+
code = """if 1:
2203+
import errno
2204+
import os
2205+
import sys
2206+
fd = os.open('/proc/self/ns/uts', os.O_RDONLY)
2207+
try:
2208+
original = os.readlink('/proc/self/ns/uts')
2209+
try:
2210+
os.unshare(os.CLONE_NEWUTS)
2211+
except OSError as e:
2212+
if e.errno == errno.ENOSPC:
2213+
# skip test if limit is exceeded
2214+
sys.exit()
2215+
raise
2216+
new = os.readlink('/proc/self/ns/uts')
2217+
if original == new:
2218+
raise Exception('os.unshare failed')
2219+
os.setns(fd, os.CLONE_NEWUTS)
2220+
restored = os.readlink('/proc/self/ns/uts')
2221+
if original != restored:
2222+
raise Exception('os.setns failed')
2223+
except PermissionError:
2224+
# The calling process did not have the required privileges
2225+
# for this operation
2226+
pass
2227+
except OSError as e:
2228+
# Skip the test on these errors:
2229+
# - ENOSYS: syscall not available
2230+
# - EINVAL: kernel was not configured with the CONFIG_UTS_NS option
2231+
# - ENOMEM: not enough memory
2232+
if e.errno not in (errno.ENOSYS, errno.EINVAL, errno.ENOMEM):
2233+
raise
2234+
finally:
2235+
os.close(fd)
2236+
"""
2237+
2238+
assert_python_ok("-c", code)
2239+
2240+
21942241
def tearDownModule():
21952242
support.reap_children()
21962243

Misc/ACKS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,7 @@ Hervé Coatanhay
344344
Riccardo Coccioli
345345
Nick Coghlan
346346
Josh Cogliati
347+
Noam Cohen
347348
Dave Cole
348349
Terrence Cole
349350
Benjamin Collar
Lines changed: 1 addition & 0 deletions
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

Lines changed: 150 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)
0