|
6 | 6 |
|
7 | 7 | import argparse |
8 | 8 | import ctypes |
| 9 | +import errno |
9 | 10 | import os |
10 | 11 | import logging |
11 | 12 | from pathlib import Path |
|
14 | 15 | except ImportError: |
15 | 16 | from contextlib2 import ExitStack |
16 | 17 |
|
17 | | -NAMESPACE_NAMES = frozenset('mnt ipc net pid user uts'.split()) |
| 18 | +NAMESPACE_NAMES = frozenset(['mnt','ipc','net','pid','user','uts']) |
18 | 19 |
|
19 | | -log = logging.getLogger('nsenter') |
20 | 20 |
|
21 | | -libc = ctypes.CDLL('libc.so.6') |
| 21 | +class Namespace(object): |
| 22 | + """A context manager for entering namespaces |
22 | 23 |
|
| 24 | + Args: |
| 25 | + pid: The PID for the owner of the namespace to enter |
| 26 | + ns_type: The type of namespace to enter must be one of |
| 27 | + mnt ipc net pid user uts |
23 | 28 |
|
24 | | -def nsfd(process, ns_type): |
25 | | - """ |
26 | | - Returns the namespace file descriptor for process (self or PID) and namespace type |
27 | | - """ |
28 | | - return Path('/proc') / str(process) / 'ns' / ns_type |
| 29 | + Raises: |
| 30 | + IOError: A non existent PID was provided |
| 31 | + ValueError: An improper ns_type was provided |
| 32 | + OSError: Unable to enter or exit the namespace |
29 | 33 |
|
30 | | -nsfd.__annotations__ = {'process': str, 'ns_type': str, 'return': Path} |
| 34 | + Example: |
| 35 | + with Namespace(<pid>, <ns_type>): |
| 36 | + #do something in the namespace |
| 37 | + pass |
| 38 | + """ |
31 | 39 |
|
| 40 | + _log = logging.getLogger(__name__) |
| 41 | + _libc = ctypes.CDLL('libc.so.6', use_errno=True) |
32 | 42 |
|
33 | | -class Namespace(object): |
34 | 43 | def __init__(self, pid, ns_type): |
| 44 | + if ns_type not in NAMESPACE_NAMES: |
| 45 | + raise ValueError('ns_type must be one of {0}'.format( |
| 46 | + ', '.join(NAMESPACE_NAMES) |
| 47 | + )) |
| 48 | + |
35 | 49 | self.pid = pid |
36 | 50 | self.ns_type = ns_type |
37 | | - self.parent_fd = nsfd('self', ns_type).open() |
38 | | - self.parent_fileno = self.parent_fd.fileno() |
39 | | - self.target_fd = nsfd(pid, ns_type).open() |
| 51 | + |
| 52 | + self.target_fd = self._nsfd(pid, ns_type).open() |
40 | 53 | self.target_fileno = self.target_fd.fileno() |
41 | 54 |
|
42 | | - __init__.__annotations__ = {'pid': str, 'ns_type': str} |
| 55 | + self.parent_fd = self._nsfd('self', ns_type).open() |
| 56 | + self.parent_fileno = self.parent_fd.fileno() |
43 | 57 |
|
44 | | - def __enter__(self): |
45 | | - log.debug('Entering %s namespace %s', self.ns_type, self.pid) |
46 | | - libc.setns(self.target_fileno, 0) |
| 58 | + __init__.__annotations__ = {'pid': str, 'ns_type': str} |
47 | 59 |
|
48 | | - def __exit__(self, type, value, tb): |
49 | | - log.debug('Leaving %s namespace %s', self.ns_type, self.pid) |
50 | | - libc.setns(self.parent_fileno, 0) |
| 60 | + def _nsfd(self, pid, ns_type): |
| 61 | + """Utility method to build a pathlib.Path instance pointing at the |
| 62 | + requested namespace entry |
| 63 | +
|
| 64 | + Args: |
| 65 | + pid: The PID |
| 66 | + ns_type: The namespace type to enter |
| 67 | +
|
| 68 | + Returns: |
| 69 | + pathlib.Path pointing to the /proc namespace entry |
| 70 | + """ |
| 71 | + return Path('/proc') / str(pid) / 'ns' / ns_type |
| 72 | + |
| 73 | + _nsfd.__annotations__ = {'process': str, 'ns_type': str, 'return': Path} |
| 74 | + |
| 75 | + def _close_files(self): |
| 76 | + """Utility method to close our open file handles""" |
51 | 77 | try: |
52 | 78 | self.target_fd.close() |
53 | 79 | except: |
54 | 80 | pass |
55 | | - self.parent_fd.close() |
| 81 | + self.parent_fd.close() |
| 82 | + |
| 83 | + def __enter__(self): |
| 84 | + self._log.debug('Entering %s namespace %s', self.ns_type, self.pid) |
56 | 85 |
|
| 86 | + if self._libc.setns(self.target_fileno, 0) == -1: |
| 87 | + e = ctypes.get_errno() |
| 88 | + self._close_files() |
| 89 | + raise OSError(e, errno.errorcode[e]) |
57 | 90 |
|
58 | | -def main(): |
59 | | - parser = argparse.ArgumentParser(description=__doc__) |
| 91 | + def __exit__(self, type, value, tb): |
| 92 | + self._log.debug('Leaving %s namespace %s', self.ns_type, self.pid) |
| 93 | + |
| 94 | + if self._libc.setns(self.parent_fileno, 0) == -1: |
| 95 | + e = ctypes.get_errno() |
| 96 | + self._close_files() |
| 97 | + raise OSError(e, errno.errorcode[e]) |
| 98 | + |
| 99 | + self._close_files() |
| 100 | + |
| 101 | +def main(): #pragma: no cover |
| 102 | + """Command line interface to the Namespace Contet Manager""" |
| 103 | + |
| 104 | + parser = argparse.ArgumentParser(prog='nsenter', description=__doc__) |
| 105 | + |
60 | 106 | parser.add_argument('--target', '-t', required=True, metavar='PID', |
61 | | - help='Specify a target process to get contexts from.') |
| 107 | + help='A target process to get contexts from') |
| 108 | + |
| 109 | + group = parser.add_argument_group('Namespaces') |
| 110 | + |
62 | 111 | for ns in NAMESPACE_NAMES: |
63 | | - parser.add_argument('--{0}'.format(ns), action='store_true', help='Enter the {0} namespace'.format(ns)) |
64 | | - parser.add_argument('--all', action='store_true', help='Enter all namespaces') |
| 112 | + group.add_argument('--{0}'.format(ns), action='store_true', |
| 113 | + help='Enter the {0} namespace'.format(ns)) |
| 114 | + |
| 115 | + parser.add_argument('--all', action='store_true', |
| 116 | + help='Enter all namespaces') |
| 117 | + |
65 | 118 | parser.add_argument('command', nargs='*', default=['/bin/sh']) |
66 | 119 |
|
67 | 120 | args = parser.parse_args() |
68 | 121 |
|
69 | | - with ExitStack() as stack: |
70 | | - namespaces = [] |
71 | | - for ns in NAMESPACE_NAMES: |
72 | | - if getattr(args, ns) or args.all: |
73 | | - namespaces.append(Namespace(args.target, ns)) |
74 | | - for ns in namespaces: |
75 | | - stack.enter_context(ns) |
76 | | - os.execl(args.command[0], *args.command) |
77 | | - |
78 | | -if __name__ == '__main__': |
| 122 | + #make sure we have --all or at least one namespace |
| 123 | + if (True not in [getattr(args, ns) for ns in NAMESPACE_NAMES] |
| 124 | + and not args.all): |
| 125 | + parser.error('You must specify at least one namespace') |
| 126 | + |
| 127 | + try: |
| 128 | + with ExitStack() as stack: |
| 129 | + namespaces = [] |
| 130 | + for ns in NAMESPACE_NAMES: |
| 131 | + if getattr(args, ns) or args.all: |
| 132 | + namespaces.append(Namespace(args.target, ns)) |
| 133 | + |
| 134 | + for ns in namespaces: |
| 135 | + stack.enter_context(ns) |
| 136 | + |
| 137 | + os.execlp(args.command[0], *args.command) |
| 138 | + except IOError as exc: |
| 139 | + parser.error('Unable to access PID: {0}'.format(exc)) |
| 140 | + except OSError as exc: |
| 141 | + parser.error('Unable to enter {0} namespace: {1}'.format( |
| 142 | + ns.ns_type, exc |
| 143 | + )) |
| 144 | + |
| 145 | + |
| 146 | +if __name__ == '__main__': #pragma: no cover |
79 | 147 | main() |
0 commit comments