Skip to content

Commit

Permalink
gh-95023: Added os.setns and os.unshare functions (#95046)
Browse files Browse the repository at this point in the history
Added os.setns and os.unshare to easily switch between namespaces
on Linux.

Co-authored-by: Christian Heimes <[email protected]>
Co-authored-by: CAM Gerlach <[email protected]>
Co-authored-by: Victor Stinner <[email protected]>
  • Loading branch information
4 people authored Oct 20, 2022
1 parent c1e02d4 commit a371a7e
Show file tree
Hide file tree
Showing 11 changed files with 418 additions and 1 deletion.
81 changes: 81 additions & 0 deletions Doc/library/os.rst
Original file line number Diff line number Diff line change
Expand Up @@ -590,6 +590,44 @@ 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, 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
Expand Down Expand Up @@ -756,6 +794,49 @@ process and user.
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
Expand Down
1 change: 1 addition & 0 deletions Include/internal/pycore_global_strings.h
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,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)
Expand Down
7 changes: 7 additions & 0 deletions Include/internal/pycore_runtime_init_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

47 changes: 47 additions & 0 deletions Lib/test/test_posix.py
Original file line number Diff line number Diff line change
Expand Up @@ -2191,6 +2191,53 @@ 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(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()

Expand Down
1 change: 1 addition & 0 deletions Misc/ACKS
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,7 @@ Hervé Coatanhay
Riccardo Coccioli
Nick Coghlan
Josh Cogliati
Noam Cohen
Dave Cole
Terrence Cole
Benjamin Collar
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Implement :func:`os.setns` and :func:`os.unshare` for Linux. Patch by Noam Cohen.
151 changes: 150 additions & 1 deletion Modules/clinic/posixmodule.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit a371a7e

Please sign in to comment.