# Copyright 2016 Red Hat, Inc. All Rights Reserved.
# Licensed to GPL under a Contributor Agreement.

import os
import sys
import resource
import atexit
import re
from ctypes import (cdll, c_int, c_long, c_char_p, c_size_t, string_at,
                        create_string_buffer, POINTER, c_void_p, CFUNCTYPE,
             pythonapi)
import pwd
import grp

try:
    from functools import reduce
except ImportError:
    pass

_pyroute2_module_available = False
_pyroute2_netns_available = False
try:
    import pyroute2
except ImportError:
    pass
else:
    _pyroute2_module_available = True

if _pyroute2_module_available:
    try:
        pyroute2.NetNS
    except AttributeError:
        pass
    else:
        _pyroute2_netns_available = True

import pickle
from copy import copy, deepcopy
import json

from ..utils import *
from ..namespaces import *
from ..version import PROCSZOO_VERSION
from .macros import *

from .atfork import atfork as c_atfork

if os.uname()[0] != "Linux":
    raise ImportError("only support Linux platform")

__version__ = PROCSZOO_VERSION
__all__ = [
    "cgroup_namespace_available", "ipc_namespace_available",
    "net_namespace_available", "mount_namespace_available",
    "pid_namespace_available", "user_namespace_available",
    "uts_namespace_available", "show_namespaces_status",
    "NamespaceGenericException", "UnknownNamespaceFound",
    "UnavailableNamespaceFound", "NamespaceSettingError",
    "NamespaceRequireSuperuserPrivilege",
    "CFunctionBaseException", "CFunctionNotFound", "CFunctionCallFailed",
    "workbench", "atfork", "sched_getcpu", "mount", "umount",
    "umount2", "unshare", "pivot_root", "adjust_namespaces",
    "setns", "spawn_namespaces", "check_namespaces_available_status",
    "show_namespaces_status", "gethostname", "sethostname",
    "getdomainname", "setdomainname", "show_available_c_functions",
    "get_namespace", "unregister_fork_handlers", "to_unicode", "to_bytes",
    "get_available_propagations", "__version__", "find_shell",
    "get_uid_from_name_or_uid", "get_gid_from_name_or_gid",
    "get_uid_by_name","get_gid_by_name", "get_name_by_uid",
    "get_name_by_gid", "get_current_users_and_groups",
    "getresuid", "getresgid", "setresuid", "setresgid"]

_HOST_NAME_MAX = 256
_CDLL = cdll.LoadLibrary(None)
_ACKCHAR = 0x006
_MAX_USERS_MAP = 5
_MAX_GROUPS_MAP = 5
_PREPARE_FORKHANDLERS = []
_PARENT_FORKHANDLERS = []
_CHILD_FORKHANDLERS = []
_CALLERS_REGISTERED = False

def _register_fork_handlers(prepare=None, parent=None, child=None):
    if prepare is not None:
        _PREPARE_FORKHANDLERS.append(prepare)
    if parent is not None:
        _PARENT_FORKHANDLERS.append(parent)
    if child is not None:
        _CHILD_FORKHANDLERS.append(child)

def _prepare_caller():
    for hdr in _PREPARE_FORKHANDLERS:
        if hdr:
            hdr()

def _parent_caller():
    for hdr in _PARENT_FORKHANDLERS:
        if hdr:
            hdr()

def _child_caller():
    for hdr in _CHILD_FORKHANDLERS:
        if hdr:
            hdr()

def _handler_registered_exist():
    return (_PREPARE_FORKHANDLERS
                or _PARENT_FORKHANDLERS
                or _CHILD_FORKHANDLERS)

def _unregister_fork_handlers(prepare=None, parent=None,
                                 child=None, strict=False):
    if prepare is not None and prepare in _PREPARE_FORKHANDLERS:
        _PREPARE_FORKHANDLERS.remove(prepare)
    elif parent is not None and parent in _PARENT_FORKHANDLERS:
        _PARENT_FORKHANDLERS.remove(parent)
    elif child is not None and child in _CHILD_FORKHANDLERS:
        _CHILD_FORKHANDLERS.remove(child)
    else:
        return

    if strict:
        return _unregister_fork_handlers(prepare, parent, child, strict)

def _fork():
    pid = os.fork()
    _errno_c_int = c_int.in_dll(pythonapi, "errno")
    if pid == - 1:
        raise RuntimeError(os.strerror(_errno_c_int.value))
    return pid

def _write2file(path, str=None):
    if path is None:
        raise RuntimeError("path cannot be none")
    if str is None:
        str = ""

    fo = open(path, 'w')
    fo.write(str)
    fo.close()

def _map_id(map_file, map=None, pid=None):
    if pid is None:
        pid = "self"
    else:
        pid = "%d" % pid
    path = "/proc/%s/%s" % (pid, map_file)
    if os.path.exists(path):
        _write2file(path, map)
    else:
        raise RuntimeError("%s: No such file" % path)

def _covert_map_to_tuple(_map, map_type=None):
    if map_type is None:
        map_type = 'user'

    if map_type not in ['group', 'user']:
        raise RuntimeError()

    _list = [v for v in _map.expandtabs().split(' ') if v != ' ']

    if len(_list) < 2 or len(_list) > 3:
        raise NamespaceSettingError()

    if len(_list) == 2:
        _range = 1
    else:
        try:
            _range = int(_list[2])
        except ValueError:
            raise NamespaceSettingError()

    if map_type == 'user':
        inter_id = get_uid_from_name_or_uid(_list[0])
        outer_id = get_uid_from_name_or_uid(_list[1])
    elif map_type == 'group':
        inter_id = get_gid_from_name_or_gid(_list[0])
        outer_id = get_gid_from_name_or_gid(_list[1])

    return tuple([inter_id, outer_id, _range])


def _i_am_superuser():
    return (os.geteuid() == 0)


def _i_am_not_superuser():
    return not _i_am_superuser()

def _accetable_user_map(user_map):
    if not user_map:
        return False

    ruid, euid, suid = getresuid()

    inter_id, outer_id, _range = _covert_map_to_tuple(user_map, 'user')

    if _i_am_not_superuser():
        _id = outer_id
        _max_id = outer_id + _range
        if _range > 3:
            return False
        while _id < _max_id:
            if _id not in [ruid, euid, suid]:
                return False
            _id += 1

    return True

def _accetable_group_map(group_map):
    if not group_map:
        return False

    rgid, egid, sgid = getresgid()

    inter_id, outer_id, _range = _covert_map_to_tuple(group_map, 'group')

    if _i_am_not_superuser():
        _id = outer_id
        _max_id = outer_id + _range
        if _range > 3:
            return False
        while _id < _max_id:
            if _id not in [rgid, egid, sgid]:
                return False
            _id += 1

    return True


def _write_to_uid_and_gid_map(maproot, users_map, groups_map, pid):
    if maproot is None:
        return

    if users_map is None:
        _maps = ["0 %d 1" % os.geteuid()]
    else:
        _maps = users_map

    if _maps:
        if len(_maps) > _MAX_USERS_MAP:
            raise NamespaceSettingError()
        map_str = "%s\n" % "\n".join(_maps)
        try:
            _map_id("uid_map", map_str, pid)
        except IOError:
            raise NamespaceRequireSuperuserPrivilege()

    if groups_map is None:
        _maps = ["0 %d 1" % os.getegid()]
    else:
        _maps = groups_map

    if _maps:
        if len(_maps) > _MAX_GROUPS_MAP:
            raise NamespaceSettingError()
        map_str = "%s\n" % "\n".join(_maps)
        try:
            _map_id("gid_map", map_str, pid)
        except IOError:
            raise NamespaceRequireSuperuserPrivilege()

def _find_my_init(pathes=None, name=None, file_mode=None, dir_mode=None):
    if pathes is None:
        if 'PATH' in os.environ:
            pathes = os.environ['PATH'].split(':')
        else:
            pathes = []
        cwd = os.path.dirname(os.path.abspath(__file__))
        absdir = os.path.abspath("%s/../.." % cwd)
        pathes += [path for path in ["%s/lib/procszoo" % absdir,
                    "%s/bin" % absdir,
                    "/usr/local/lib/procszoo",
                    "/usr/lib/procszoo"] if os.path.exists(path)]
    if name is None:
        name = "my_init"

    if file_mode is None:
        file_mode = os.R_OK
    if dir_mode is None:
        dir_mode = os.R_OK

    for path in pathes:
        my_init = "%s/%s" % (path, name)
        if os.path.exists(my_init):
            if os.access(my_init, file_mode):
                return my_init

    dirs_access_refused = []
    files_access_refused = []
    for path in pathes:
        my_init = "%s/%s" % (path, name)
        dirs = my_init.split('/')
        tmp_path = '/'
        for path_name in dirs:
            if not path_name: continue
            tmp_path = os.path.join(tmp_path, path_name)
            if tmp_path in dirs_access_refused: break
            if tmp_path in files_access_refused: break
            if os.path.exists(tmp_path):
                if os.path.isdir(tmp_path):
                    if not os.access(tmp_path, dir_mode):
                        dirs_access_refused.append(tmp_path)
                        break
                elif os.path.isfile(tmp_path):
                    if not os.access(tmp_path, file_mode):
                        files_access_refused.append(tmp_path)
                        break

    if dirs_access_refused or files_access_refused:
        if len(dirs_access_refused) + len(files_access_refused) > 1:
            err_str = "[%s]" % "\n".join(
                dirs_access_refused + files_access_refused)
        else:
            err_str = "'%s'" % " ".join(
                dirs_access_refused + files_access_refused)
        raise IOError("Permission denied: %s" % err_str)

    raise NamespaceSettingError()


def is_string_or_unicode(obj):
    if sys.version_info >= (3, 0):
        return isinstance(obj, (str, bytes))
    else:
        return isinstance(obj, basestring)

if sys.version_info >= (3, 0):
    def _to_str(bytes_or_str):
        if isinstance(bytes_or_str, bytes):
            value = bytes_or_str.decode('utf-8')
        else:
            value = bytes_or_str
        return value

    def _to_bytes(bytes_or_str):
        if isinstance(bytes_or_str, str):
            value = bytes_or_str.encode('utf-8')
        else:
            value = bytes_or_str
        return value
else:
    def _to_unicode(unicode_or_str):
        if isinstance(unicode_or_str, str):
            value = unicode_or_str.decode('utf-8')
        else:
            value = unicode_or_str
        return value

    def _to_str(unicode_or_str):
        if isinstance(unicode_or_str, unicode):
            value = unicode_or_str.encode('utf-8')
        else:
            value = unicode_or_str
        return value

def to_unicode(unicode_or_bytes_or_str):
    if sys.version_info >= (3, 0):
        return _to_str(unicode_or_bytes_or_str)
    else:
        return _to_unicode(unicode_or_bytes_or_str)

def to_bytes(unicode_or_bytes_or_str):
    if sys.version_info >= (3, 0):
        return _to_bytes(unicode_or_bytes_or_str)
    else:
        return _to_str(unicode_or_bytes_or_str)


class SpawnNamespacesConfig(object):
    def __init__(self, namespaces=None, maproot=True, mountproc=True,
                mountpoint=None, ns_bind_dir=None, nscmd=None,
                propagation=None, negative_namespaces=None,
                setgroups=None, users_map=None, groups_map=None,
                init_prog=None, func=None,
                interactive=None,
                strict=None,
                extra=None,
                pid=None,
                top_halves_child_pid=None,
                bottom_halves_child_pid=None,
                parse_conf=None,
                top_halves_before_sync=None,
                top_halves_half_sync=None,
                top_halves_after_sync=None,
                bottom_halves_before_fork=None,
                bottom_halves_before_sync=None,
                bottom_halves_half_sync=None,
                bottom_halves_after_sync=None,
                top_halves_entry_point=None,
                bottom_halves_entry_point=None,
                     entry_point=None):
        if namespaces is None:
            self.namespaces = adjust_namespaces()
        else:
            self.namespaces = namespaces
        self.maproot = maproot
        self.mountproc = mountproc
        if mountpoint is None:
            self.mountpoint = '/proc'
        else:
            self.mountpoint = mountpoint
        self.ns_bind_dir = ns_bind_dir
        self.nscmd = nscmd
        self.propagation = propagation
        self.negative_namespaces = negative_namespaces
        self.setgroups = setgroups
        if setgroups is not None and setgroups not in ['allow', 'deny']:
            raise NamespaceSettingError()
        self.users_map = users_map
        self.groups_map = groups_map
        self.init_prog = init_prog
        self.my_init = _find_my_init()
        self.func = func
        if interactive is None:
            self.interactive = True
        else:
            self.interactive=interactive
        if strict is None:
            self.strict = True
        else:
            self.strict = strict

        self.extra = extra

        if pid is None:
            self.pid = os.getpid()
        else:
            self.pid = pid
        self.top_halves_child_pid = top_halves_child_pid
        self.bottom_halves_child_pid = bottom_halves_child_pid

        if parse_conf is None:
            self.parse_conf = self._default_handler_to_parse_conf
        else:
            if not getattr(parse_conf, '__call__'):
                raise NamespaceSettingError('handler must be a callable')
            setattr(self, 'parse_conf', parse_conf)

        if top_halves_before_sync is None:
            self.top_halves_before_sync = self._default_top_halves_before_sync
        else:
            if not getattr(top_halves_before_sync, '__call__'):
                raise NamespaceSettingError('handler must be a callable')
            setattr(self, 'top_halves_before_sync', top_halves_before_sync)

        if top_halves_half_sync is None:
            self.top_halves_half_sync = self._default_top_halves_half_sync
        else:
            if not getattr(top_halves_half_sync, '__call__'):
                raise NamespaceSettingError('handler must be a callable')
            setattr(self, 'top_halves_half_sync', top_halves_half_sync)

        if top_halves_after_sync is None:
            self.top_halves_after_sync = self._default_null_handler
        else:
            if not getattr(top_halves_after_sync, '__call__'):
                raise NamespaceSettingError('handler must be a callable')
            setattr(self, 'top_halves_after_sync', top_halves_after_sync)

        if bottom_halves_before_fork is None:
            self.bottom_halves_before_fork = self._default_bottom_halves_before_fork
        else:
            if not getattr(bottom_halves_before_fork, '__call__'):
                raise NamespaceSettingError('handler must be a callable')
            setattr(self, 'bottom_halves_before_fork', bottom_halves_before_fork)

        if bottom_halves_before_sync is None:
            self.bottom_halves_before_sync = self._default_bottom_halves_before_sync
        else:
            if not getattr(bottom_halves_before_sync, '__call__'):
                raise NamespaceSettingError('handler must be a callable')
            setattr(self, 'bottom_halves_before_sync', bottom_halves_before_sync)

        if bottom_halves_half_sync is None:
            self.bottom_halves_half_sync = self._default_null_handler
        else:
            if not getattr(bottom_halves_half_sync, '__call__'):
                raise NamespaceSettingError('handler must be a callable')
            setattr(self, 'bottom_halves_half_sync', bottom_halves_half_sync)

        if bottom_halves_after_sync is None:
            self.bottom_halves_after_sync = self._default_bottom_halves_after_sync
        else:
            if not getattr(bottom_halves_after_sync, '__call__'):
                raise NamespaceSettingError('handler must be a callable')
            setattr(self, 'bottom_halves_after_sync', bottom_halves_after_sync)

        if top_halves_entry_point is None:
            self.top_halves_entry_point = self._default_top_halves_entry_point
        else:
            if not getattr(top_halves_entry_point, '__call__'):
                raise NamespaceSettingError('handler must be a callable')
            setattr(self, 'top_halves_entry_point', top_halves_entry_point)

        if bottom_halves_entry_point is None:
            self.bottom_halves_entry_point = self._default_bottom_halves_entry_point
        else:
            if not getattr(bottom_halves_entry_point, '__call__'):
                raise NamespaceSettingError('handler must be a callable')
            setattr(self, 'bottom_halves_entry_point', bottom_halves_entry_point)

        if entry_point is None:
            self.entry_point = self._default_entry_point
        else:
            if not getattr(entry_point, '__call__'):
                raise NamespaceSettingError('handler must be a callable')
            setattr(self, 'entry_point', entry_point)

    def _default_entry_point(self, *args, **kwargs):
        self.parse_conf(*args, **kwargs)

        r1, w1 = os.pipe()
        r2, w2 = os.pipe()

        pid = _fork()

        if pid == 0:
            self.bottom_halves_entry_point(r1, w1, r2, w2, *args, **kwargs)
            sys.exit(0)
        elif pid > 0:
            self.top_halves_entry_point(r1, w1, r2, w2, pid, *args, **kwargs)
        else:
            sys.exit(0)

    def _default_top_halves_entry_point(self, r1, w1, r2, w2, pid, *args, **kwargs):
        self.top_halves_child_pid = pid

        self.top_halves_before_sync(*args, **kwargs)

        os.close(w1)
        os.close(r2)

        child_pid = os.read(r1, 64)
        os.close(r1)
        try:
            child_pid = int(child_pid)
        except ValueError:
            raise RuntimeError("failed to get the child pid")

        self.bottom_halves_child_pid = child_pid

        self.top_halves_half_sync(*args, **kwargs)

        os.write(w2, to_bytes(chr(_ACKCHAR)))
        os.close(w2)

        self.top_halves_after_sync(*args, **kwargs)

        if self.interactive:
            os.waitpid(self.top_halves_child_pid, 0)
        else:
            sys.exit(0)

    def _default_bottom_halves_entry_point(self, r1, w1, r2, w2, *args, **kwargs):
        os.close(r1)
        os.close(w2)

        self.bottom_halves_before_fork(*args, **kwargs)

        r3, w3 = os.pipe()
        r4, w4 = os.pipe()
        pid = _fork()

        if pid == 0:
            if not self.interactive:
                process_id = os.setsid()
                if process_id == -1:
                    sys.exit(1)

            os.close(w1)
            os.close(r2)

            os.close(r3)
            os.close(w4)

            self.bottom_halves_before_sync(*args, **kwargs)

            os.write(w3, to_bytes(chr(_ACKCHAR)))
            os.close(w3)

            self.bottom_halves_half_sync(*args, **kwargs)

            if ord(os.read(r4, 1)) != _ACKCHAR:
                raise RuntimeError('sync failed')
            os.close(r4)

            if not self.interactive:
                devnull = "/dev/null"
                if hasattr(os, "devnull"):
                    devnull = os.devnull

                for fd in range(3, resource.getrlimit(resource.RLIMIT_NOFILE)[0]):
                    try:
                        os.close(fd)
                    except OSError:
                        pass

                os.chdir('/')
                devnull_fd = os.open(devnull, os.O_RDWR)
                os.dup2(devnull_fd, 0)
                os.dup2(devnull_fd, 1)
                os.dup2(devnull_fd, 2)
                os.close(devnull_fd)

            self.bottom_halves_after_sync(*args, **kwargs)

            sys.exit(0)
        else:

            os.close(w3)
            os.close(r4)

            if ord(os.read(r3, 1)) != _ACKCHAR:
                raise RuntimeError('sync failed')
            os.close(r3)

            os.write(w1, to_bytes("%d" % pid))
            os.close(w1)

            if ord(os.read(r2, 1)) != _ACKCHAR:
                raise RuntimeError('sync failed')
            os.close(r2)

            os.write(w4, to_bytes(chr(_ACKCHAR)))
            os.close(w4)

            if self.interactive:
                os.waitpid(pid, 0)
            else:
                sys.exit(0)


    def _default_handler_to_parse_conf(self):
        if self.init_prog is not None or self.nscmd is not None:
            if self.func is not None:
                raise NamespaceSettingError()

        check_namespaces_available_status()
        if not user_namespace_available():
            if self.strict:
                if (self.maproot or self.users_map or self.groups_map):
                    raise NamespaceSettingError(
                        'cannot do users/groups mapping')
            else:
                self.maproot = False
                self.users_map = None
                self.group_map = None
        if self.setgroups == "allow" and (
                self.maproot or self.users_map or self.groups_map):
            if self.strict:
                raise NamespaceSettingError('setgroups and users/groups mapping conflict')
            else:
                self.maproot = False
                self.users_map = None
                self.group_map = None
        if not pid_namespace_available():
            if self.strict and self.mountproc:
                raise NamespaceSettingError('cannot mount procfs')
            else:
                self.mountproc = False
                self.mountpoint = None
        if not mount_namespace_available():
            if self.strict and self.propagation is not None:
                raise NamespaceSettingError()
            else:
                self.propagation = None
        if self.setgroups == "allow" and (
                self.maproot or self.users_map or self.groups_map):
            raise NamespaceSettingError()

        self.namespaces = adjust_namespaces(self.namespaces,
                                                self.negative_namespaces)

        path = "/proc/self/setgroups"
        if not os.path.exists(path) and self.setgroups is not None:
            if self.setgroups != 'allow':
                raise NamespaceSettingError('do not support setgroups')

        if self._need_super_privilege():
            raise NamespaceRequireSuperuserPrivilege()

        if self.mountproc:
            if mount_namespace_available():
                if "mount" not in self.namespaces:
                    if self.strict:
                        raise NamespaceSettingError()
                    else:
                        self.namespaces.append("mount")
            else:
                raise NamespaceSettingError()

        if self.maproot:
            if user_namespace_available():
                if "user" not in self.namespaces:
                    if strict:
                        raise NamespaceSettingError()
                    else:
                        self.namespaces.append("user")
            else:
                raise NamespaceSettingError()

        if mount_namespace_available():
            if "mount" in self.namespaces and self.propagation is None:
                self.propagation = "private"

        path = "/proc/self/setgroups"
        if self.setgroups is None:
            if user_namespace_available() and "user" in self.namespaces:
                if self.maproot and os.path.exists(path):
                    self.setgroups = "deny"

        if "user" not in self.namespaces:
            self.maproot = False
            self.setgroups = None
            self.users_map = None
            self.groups_map = None

        if "pid" not in self.namespaces:
            self.mountproc = False

        if "mount" not in self.namespaces:
             self.ns_bind_dir = None
             self.propagation = None
             self.mountproc = False

        if self.users_map:
            _users_map = []
            for _map in self.users_map:
                inter_id, outer_id, _range = _covert_map_to_tuple(_map, 'user')
                _users_map.append('%d %d %d' % (inter_id, outer_id, _range))

            for _map in _users_map:
                if not _accetable_user_map(_map):
                    raise NamespaceRequireSuperuserPrivilege()
            self.users_map = _users_map

        if self.groups_map:
            _groups_map = []
            for _map in self.groups_map:
                inter_id, outer_id, _range = _covert_map_to_tuple(
                    _map, 'group')
                _groups_map.append('%d %d %d' % (inter_id, outer_id, _range))

            for _map in _groups_map:
                if not _accetable_group_map(_map):
                    raise NamespaceRequireSuperuserPrivilege()
            self.groups_map = _groups_map

        if self.maproot:
            if not self.users_map:
                self.users_map = ['0 %d 1' % os.geteuid()]
            if not self.groups_map:
                self.groups_map = ['0 %d 1' % os.getegid()]

    def _need_super_privilege(self):
        require_root_privilege = False
        if not user_namespace_available():
            require_root_privilege = True
        if self.namespaces and "user" not in self.namespaces:
            require_root_privilege = True
        if self.ns_bind_dir:
            require_root_privilege = True
        if require_root_privilege:
            euid = os.geteuid()
            require_root_privilege = (euid != 0)
        return require_root_privilege

    def _default_null_handler(self, *args, **kwargs):
        pass

    def _default_bottom_halves_before_fork(self, *args, **kwargs):
        unshare(self.namespaces)

    def _default_bottom_halves_before_sync(self, *args, **kwargs):
        if "mount" in self.namespaces and self.propagation is not None:
            workbench.set_propagation(self.propagation)
        if self.mountproc:
            workbench._mount_proc(mountpoint=self.mountpoint)

    def _default_bottom_halves_after_sync(self, *args, **kwargs):
        if self.func is None:
            if not self.nscmd:
                self.nscmd = [find_shell()]
            elif not isinstance(self.nscmd, list):
                self.nscmd = [self.nscmd]
            if "pid" not in self.namespaces:
                args = self.nscmd
            elif self.init_prog is not None:
                args = [self.init_prog] + self.nscmd
            else:
                args = [sys.executable, self.my_init, "--skip-startup-files",
                        "--skip-runit", "--quiet"] + self.nscmd
            os.execlp(args[0], *args)
        else:
            if hasattr(self.func, '__call__'):
                self.func(*args, **kwargs)
            else:
                raise NamespaceSettingError()

    def _default_top_halves_before_sync(self, *args, **kwargs):
        if self.setgroups == "allow" and self.maproot:
            raise NamespaceSettingError()

    def _default_top_halves_half_sync(self, *args, **kwargs):
        if "user" in self.namespaces:
            workbench.setgroups_control(self.setgroups,
                                            self.bottom_halves_child_pid)
            _write_to_uid_and_gid_map(
                self.maproot, self.users_map,
                self.groups_map, self.bottom_halves_child_pid)

        if self.ns_bind_dir is not None and "mount" in self.namespaces:
            workbench.bind_ns_files(self.bottom_halves_child_pid,
                                        self.namespaces, self.ns_bind_dir)

class CFunction(object):
    """
    wrapper class for C library function. These functions could be accessed
    by workbench._c_func_name, e.g., workbench._c_func_unshare.
    """
    def __init__(self, argtypes=None, restype=c_int,
                     exported_name=None,
                     failed=lambda res: res != 0,
                     possible_c_func_names=None,
                     extra=None, func=None):
        self.failed = failed
        self.func = func
        self.exported_name = exported_name

        if is_string_or_unicode(possible_c_func_names):
            self.possible_c_func_names = [possible_c_func_names]
        elif isinstance(possible_c_func_names, list):
            self.possible_c_func_names = possible_c_func_names
        elif possible_c_func_names is None:
            self.possible_c_func_names = [exported_name]
        self.extra = extra

        for name in self.possible_c_func_names:
            if hasattr(_CDLL, name):
                func = getattr(_CDLL, name)
                func.argtypes = argtypes
                func.restype = restype
                self.func = func
                break

class Workbench(object):
    """
    class used as a singleton.
    """
    def __init__(self):
        self.my_init = _find_my_init()
        self.functions = {}
        self.available_c_functions = []
        self.namespaces = Namespaces()
        self._init_c_functions()
        self._namespaces_available_status_checked = False

    def _init_c_functions(self):
        exported_name = "unshare"
        self.functions[exported_name] = CFunction(
            exported_name=exported_name,
            argtypes=[c_int])

        exported_name = "sched_getcpu"
        self.functions[exported_name] = CFunction(
            exported_name=exported_name,
            argtypes=None,
            failed=lambda res: res == -1)

        exported_name = "setns"
        self.functions[exported_name] = CFunction(
            exported_name=exported_name,
            argtypes=[c_int, c_int],
            extra={
                "default args": {
                    "file_instance": None,
                    "file_descriptor": None,
                    "path": None,
                    "namespace": 0}
                })

        exported_name = "syscall"
        extra = {}
        if SYSCALL_PIVOT_ROOT_AVAILABLE:
            extra["pivot_root"] = NR_PIVOT_ROOT
        if SYSCALL_SETNS_AVAILABLE:
            extra["setns"] = NR_SETNS

        self.functions[exported_name] = CFunction(
            exported_name=exported_name, extra=extra)

        exported_name = "mount"
        self.functions[exported_name] = CFunction(
            exported_name=exported_name,
            argtypes=[c_char_p, c_char_p, c_char_p, c_long, c_void_p],
            extra={
                "default args": {
                    "source": None,
                    "target": None,
                    "filesystemtype": None,
                    "flags": None,
                    "data": None,},

                "flag": {
                    "MS_NOSUID": 2, "MS_NODEV": 4,
                    "MS_NOEXEC": 8, "MS_REC": 16384,
                    "MS_PRIVATE": 1 << 18,
                    "MS_SLAVE": 1 << 19,
                    "MS_SHARED": 1 << 20,
                    "MS_BIND": 4096,},

                "propagation": {
                    "slave": ["MS_REC", "MS_SLAVE"],
                    "private": ["MS_REC", "MS_PRIVATE"],
                    "shared": ["MS_REC", "MS_SHARED"],
                    "bind": ["MS_BIND"],
                    "mount_proc": ["MS_NOSUID", "MS_NODEV", "MS_NOEXEC"],
                    "unchanged": [],},
                "private_propagation": ['mount_proc', 'unchanged', 'bind'],
                })

        exported_name = "umount"
        self.functions[exported_name] = CFunction(
            exported_name=exported_name,
            argtypes=[c_char_p])

        exported_name = "umount2"
        self.functions[exported_name] = CFunction(
            exported_name=exported_name,
            argtypes=[c_char_p, c_int],
            extra = {
                "flag": {
                    "MNT_FORCE": 1,
                    "MNT_DETACH": 2,
                    "MNT_EXPIRE": 4,
                    "UMOUNT_NOFOLLOW": 8,},
                "behaviors": {
                    "force": "MNT_FORCE",
                    "detach": "MNT_DETACH",
                    "expire": "MNT_EXPIRE",
                    "nofollow": "UMOUNT_NOFOLLOW",}
                })

        exported_name = "gethostname"
        self.functions[exported_name] = CFunction(
            exported_name = exported_name,
            argtypes=[c_char_p, c_size_t])

        exported_name = "sethostname"
        self.functions[exported_name] = CFunction(
            exported_name=exported_name,
            argtypes=[c_char_p, c_size_t])

        exported_name = "getdomainname"
        self.functions[exported_name] = CFunction(
            exported_name=exported_name,
            argtypes=[c_char_p, c_size_t])

        exported_name = "setdomainname"
        self.functions[exported_name] = CFunction(
            exported_name=exported_name,
            argtypes=[c_char_p, c_size_t])

        #begin: python 2.6 need functions
        exported_name = 'getresuid'
        self.functions[exported_name] = CFunction(
            exported_name=exported_name,
            argtypes=[POINTER(c_int), POINTER(c_int), POINTER(c_int)])

        exported_name = 'getresgid'
        self.functions[exported_name] = CFunction(
            exported_name=exported_name,
            argtypes=[POINTER(c_int), POINTER(c_int), POINTER(c_int)])

        exported_name = 'setresuid'
        self.functions[exported_name] = CFunction(
            exported_name=exported_name,
            argtypes=[c_int, c_int, c_int])

        exported_name = 'setresgid'
        self.functions[exported_name] = CFunction(
            exported_name=exported_name,
            argtypes=[c_int, c_int, c_int])
        #end: python 2.6 need functions

        for func_name in self.functions:
            if func_name == 'syscall': continue
            func_obj = self.functions[func_name]
            if func_obj.func:
                self.available_c_functions.append(func_name)
            else:
                try:
                    self._syscall_nr(func_name)
                except CFunctionUnknowSyscall as e:
                    pass
                else:
                    self.available_c_functions.append(func_name)

        func_name = "pivot_root"
        if func_name not in self.available_c_functions:
            try:
                self._syscall_nr(func_name)
            except CFunctionUnknowSyscall as e:
                pass
            else:
                self.available_c_functions.append(func_name)
        self.available_c_functions.sort()

    def _syscall_nr(self, syscall_name):
        func_obj = self.functions["syscall"]
        if syscall_name in func_obj.extra:
            return func_obj.extra[syscall_name]
        else:
            raise CFunctionUnknowSyscall()

    def __getattr__(self, name):
        if name.startswith("_c_func_"):
            c_func_name = name.replace("_c_func_", "")
            if c_func_name not in self.available_c_functions:
                if c_func_name != 'syscall':
                    raise CFunctionNotFound(c_func_name)
            func_obj = self.functions[c_func_name]
            c_func = func_obj.func
            context = locals()
            def c_func_wrapper(*args, **context):
                tmp_args = []
                for arg in args:
                    if is_string_or_unicode(arg):
                        tmp_args.append(to_bytes(arg))
                    else:
                        tmp_args.append(arg)
                res = c_func(*tmp_args)
                c_int_errno = c_int.in_dll(pythonapi, "errno")
                if func_obj.failed(res):
                    if c_int_errno.value == EPERM:
                        raise NamespaceRequireSuperuserPrivilege()
                    else:
                        raise CFunctionCallFailed(os.strerror(c_int_errno.value))
                return res

            return c_func_wrapper
        else:
            raise AttributeError("'CFunction' object has no attribute '%s'"
                                     % name)

    def get_available_propagations(self):
        func_obj = self.functions['mount']
        propagation = func_obj.extra['propagation']
        private_propagation = func_obj.extra['private_propagation']
        return [p for p in propagation.keys() if p not in private_propagation]

    def getresuid(self):
        try:
            os.getresuid
        except AttributeError:
            pass
        else:
            return os.getresuid()

        ruid = c_int()
        euid = c_int()
        suid = c_int()
        self._c_func_getresuid(ruid, euid, suid)

        return (ruid.value, euid.value, suid.value)

    def getresgid(self):
        try:
            os.getresgid
        except AttributeError:
            pass
        else:
            return os.getresgid()

        rgid = c_int()
        egid = c_int()
        sgid = c_int()
        self._c_func_getresgid(rgid, egid, sgid)

        return (rgid.value, egid.value, sgid.value)

    def setresuid(self, ruid, euid, suid):
        try:
            os.setresuid
        except AttributeError:
            pass
        else:
            return os.setresuid(ruid, euid, suid)

        return self._c_func_setresuid(ruid, euid, suid)

    def setresgid(self, rgid, egid, sgid):
        try:
            os.setresgid
        except AttributeError:
            pass
        else:
            return os.setresgid(rgid, egid, sgid)

        return self._c_func_setresgid(rgid, egid, sgid)

    def atfork(self, prepare=None, parent=None, child=None):
        """
        This function will let us to insert our codes before and after fork
            prepare()
            pid = os.fork()
            if pid == 0:
                child()
                ...
            elif pid > 0:
                parent()
                ...
        """
        global _CALLERS_REGISTERED
        _register_fork_handlers(prepare=prepare, parent=parent, child=child)

        if not _CALLERS_REGISTERED and _handler_registered_exist():
            ret = c_atfork(_prepare_caller, _parent_caller, _child_caller)
            if ret != 0:
                raise RuntimeError("Failed to call atfork(), return code %s" % ret)
            _CALLERS_REGISTERED = True

    def unregister_fork_handlers(self, prepare=None, parent=None,
                                     child=None, strict=False):
        return _unregister_fork_handlers(prepare, parent, child, strict)

    def check_namespaces_available_status(self):
        """
        On rhel6/7, the kernel default does not enable all namespaces
        that it supports.
        """
        if self._namespaces_available_status_checked:
            return

        unshare = self.functions["unshare"].func

        r, w = os.pipe()

        pid0 = _fork()
        if pid0 == 0:
            pid1 = _fork()
            if pid1 == 0:
                os.close(r)
                tmpfile = os.fdopen(w, 'wb')
                keys = []
                for ns in self.namespaces.namespaces:
                    ns_obj = self.get_namespace(ns)
                    val = ns_obj.value
                    res = unshare(c_int(val))
                    _errno_c_int = c_int.in_dll(pythonapi, "errno")
                    if res == -1:
                        if _errno_c_int.value != EINVAL:
                            keys.append(ns)
                    else:
                        keys.append(ns)

                pickle.dump(keys, tmpfile)
                tmpfile.close()
                sys.exit(0)
            else:
                os.waitpid(pid1, 0)
                sys.exit(0)
        else:
            os.close(w)
            tmpfile = os.fdopen(r, 'rb')
            os.waitpid(pid0, 0)
            keys = pickle.load(tmpfile)
            tmpfile.close()

            for ns_name in self.namespaces.namespaces:
                if ns_name not in keys:
                    ns_obj = self.get_namespace(ns_name)
                    ns_obj.available = False

            self._namespaces_available_status_checked = True

    def show_available_c_functions(self):
        return self.available_c_functions

    def sched_getcpu(self):
        return self._c_func_sched_getcpu()

    def cgroup_namespace_available(self):
        self.check_namespaces_available_status()
        return self.namespaces.cgroup_namespace_available

    def ipc_namespace_available(self):
        self.check_namespaces_available_status()
        return self.namespaces.ipc_namespace_available

    def net_namespace_available(self):
        self.check_namespaces_available_status()
        return self.namespaces.net_namespace_available

    def mount_namespace_available(self):
        self.check_namespaces_available_status()
        return self.namespaces.mount_namespace_available

    def pid_namespace_available(self):
        self.check_namespaces_available_status()
        return self.namespaces.pid_namespace_available

    def user_namespace_available(self):
        self.check_namespaces_available_status()
        return self.namespaces.user_namespace_available

    def uts_namespace_available(self):
        return self.namespaces.uts_namespace_available

    def mount(self, source=None, target=None, mount_type=None,
              filesystemtype=None, data=None):
        if not [arg for arg in [source, target, filesystemtype, mount_type]
                if arg is not None]:
            return
        func_obj = self.functions["mount"]

        if source is None:
            source = "none"
        if target is None:
            target = ""
        if filesystemtype is None:
            filesystemtype = ""
        if mount_type is None:
            mount_type = "unchanged"
        if data is None:
            data = ""

        flag = func_obj.extra["flag"]
        propagation = func_obj.extra["propagation"]
        mount_flags = propagation[mount_type]
        mount_vals = [flag[k] for k in mount_flags]
        flags = reduce(lambda res, val: res | val, mount_vals, 0)

        self._c_func_mount(source, target, filesystemtype, flags, data)

    def _mount_proc(self, mountpoint="/proc"):
        self.mount(source="none", target=mountpoint, mount_type="private")
        self.mount(source="proc", target=mountpoint, filesystemtype="proc",
                   mount_type="mount_proc")

    def umount(self, mountpoint=None):
        if mountpoint is None:
            return
        if not is_string_or_unicode(mountpoint):
            raise RuntimeError("mountpoint should be a path to a mount point")
        if not os.path.exists(mountpoint):
            raise RuntimeError("mount point '%s': cannot found")
        self._c_func_umount(mountpoint)

    def umount2(self, mountpoint=None, behavior=None):
        func_obj = self.functions["umount2"]
        if mountpoint is None:
            return
        if not is_string_or_unicode(mountpoint):
            raise RuntimeError("mountpoint should be a path to a mount point")
        if not os.path.exists(mountpoint):
            raise RuntimeError("mount point '%s': cannot found")

        behaviors = func_obj.extra["behaviors"]
        flag = func_obj.extra["flag"]
        if behavior is None or behavior not in behaviors.keys():
            raise RuntimeError("behavior should be one of [%s]"
                               % ", ".join(func_obj.behaviors.keys()))

        val = flag[behaviors[behavior]]
        self._c_func_umount2(mountpoint, c_int(val))

    def set_propagation(self, type=None):
        if type is None:
            return
        mount_func_obj = self.functions["mount"]
        propagation = mount_func_obj.extra["propagation"]
        if type not in propagation.keys():
            raise RuntimeError("%s: unknown propagation type" % type)
        if type == "unchanged":
            return
        self.mount(source="none", target="/", mount_type=type)

    def unshare(self, namespaces=None):
        if namespaces is None:
            return

        target_flags = []
        for ns_name in namespaces:
            ns_obj = self.get_namespace(ns_name)
            if ns_obj.available:
                target_flags.append(ns_obj.value)

        flags = reduce(lambda res, flag: res | flag, target_flags, 0)
        self._c_func_unshare(flags)

    def setns(self, **kwargs):
        """
        workbench.setns(path=path2ns, namespace=namespace)

        E.g., setns(pid=1234, namespace="pid")
        """
        keys = ["fd", "path", "pid", "file_obj"]
        wrong_keys = [k for k in keys if k in kwargs.keys()]
        if len(wrong_keys) != 1:
            raise TypeError("complicating named argument found: %s"
                            % ", ".join(wrong_keys))

        _kwargs = deepcopy(kwargs)
        namespace = 0
        if  "namespace" in kwargs:
            ns = kwargs["namespace"]
            if is_string_or_unicode(ns) and ns in self.namespaces.namespaces:
                namespace = self.get_namespace(ns)
            else:
                raise UnknownNamespaceFound([ns])

        _kwargs["namespace"] = namespace.value

        if "fd" in kwargs:
            fd = kwargs["fd"]
            if not isinstance(fd, int):
                raise TypeError("unavailable file descriptor found")
        elif "path" in kwargs:
            path = os.path.abspath(kwargs["path"])
            entry = os.path.basename(path)
            if "namespace" in kwargs:
                ns = kwargs["namespace"]
                ns_obj = self.get_namespace(ns)
                ns_obj_entry = ns_obj.entry
                if entry != ns_obj_entry:
                    raise TypeError("complicating path and namespace args found")
            if not os.path.exists(path):
                raise TypeError("%s not existed" % path)

            fo = open(path, 'r')
            _kwargs["file_obj"] = fo
            _kwargs["fd"] = fo.fileno()
            _kwargs["path"] = path
        elif "pid" in kwargs:
            pid = kwargs["pid"]
            if namespace == 0:
                raise TypeError("pid named argument need a namespace")
            if not isinstance(pid, int):
                raise TypeError("unknown pid found")
            ns = kwargs["namespace"]
            ns_obj = self.get_namespace(ns)
            entry = ns_obj.entry
            path = "/proc/%d/ns/%s" % (pid, entry)
            if os.path.exists(path):
                fo = open(path, 'r')
                _kwargs["file_obj"] = fo
                _kwargs["fd"] = fo.fileno()
        elif "file_obj" in kwargs:
            fo = kwargs["file_obj"]
            _kwargs["fd"] = fo.fileno()

        flags = c_int(_kwargs["namespace"])
        fd = c_int(_kwargs["fd"])
        if self.functions["setns"].func is None:
            try:
                NR_SETNS = self._syscall_nr("setns")
            except CFunctionUnknowSyscall as e:
                raise CFunctionNotFound()
            else:
                return self._c_func_syscall(c_long(NR_SETNS), fd, flags)
        else:
            return self._c_func_setns(fd, flags)

    def gethostname(self):
        buf_len = _HOST_NAME_MAX
        buf = create_string_buffer(buf_len)
        self._c_func_gethostname(buf, c_size_t(buf_len))
        return _to_str(string_at(buf))

    def sethostname(self, hostname=None):
        if hostname is None:
            return
        hostname = to_bytes(hostname)
        buf_len = c_size_t(len(hostname))
        buf = create_string_buffer(hostname)
        return self._c_func_sethostname(buf, buf_len)

    def getdomainname(self):
        """
        Note that this function will return string '(none)' if returned domain name is empty.
        """
        buf_len = _HOST_NAME_MAX
        buf = create_string_buffer(buf_len)
        self._c_func_getdomainname(buf, c_size_t(buf_len))
        return _to_str(string_at(buf))

    def setdomainname(self, domainname=None):
        if domainname is None:
            return
        domainname = to_bytes(domainname)
        buf_len = c_size_t(len(domainname))
        buf = create_string_buffer(domainname)
        return self._c_func_setdomainname(buf, buf_len)

    def pivot_root(self, new_root, put_old):
        if not is_string_or_unicode(new_root):
            raise RuntimeError("new_root argument is not an available path")
        if not is_string_or_unicode(put_old):
            raise RuntimeError("put_old argument is not an available path")
        if not os.path.exists(new_root):
            raise RuntimeError("%s: no such directory" % new_root)
        if not os.path.exists(put_old):
            raise RuntimeError("%s: no such directory" % put_old)

        try:
            NR_PIVOT_ROOT = self._syscall_nr("pivot_root")
        except CFunctionUnknowSyscall:
            raise CFunctionNotFound()
        else:
            return self._c_func_syscall(c_long(NR_PIVOT_ROOT),
                                            new_root, put_old)

    def adjust_namespaces(self, namespaces=None, negative_namespaces=None):
        self.check_namespaces_available_status()
        available_namespaces = []
        for ns_name in self.namespaces.namespaces:
            ns_obj = self.get_namespace(ns_name)
            if ns_obj.available:
                available_namespaces.append(ns_name)

        if namespaces is None:
            namespaces = available_namespaces

        unavailable_namespaces = [ns for ns in namespaces
                                  if ns not in self.namespaces.namespaces]
        if unavailable_namespaces:
            raise UnknownNamespaceFound(namespaces=unavailable_namespaces)

        if negative_namespaces:
            for ns in negative_namespaces:
                if ns in namespaces: namespaces.remove(ns)

        return namespaces

    def setgroups_control(self, setgroups=None, pid=None):
        if setgroups is None:
            return
        if pid is None:
            pid = "self"
        else:
            pid = "%d" % pid
        path = "/proc/%s/setgroups" % pid
        if not os.path.exists(path):
            if setgroups == "deny":
                raise NamespaceSettingError("cannot set setgroups to 'deny'")
            else:
                return

        ctrl_keys = self.namespaces.user.extra
        if setgroups not in ctrl_keys:
            raise RuntimeError("group control should be %s"
                               % ", ".join(ctrl_keys))
        hdr = open(path, 'r')
        line = hdr.read(16)
        old_setgroups = line.rstrip("\n")
        if old_setgroups == setgroups:
            return
        hdr.close()

        if os.path.exists(path):
            _write2file(path, setgroups)

    def get_namespace(self, name):
        if name is None:
            name="pid"
        return getattr(self.namespaces, name)

    def show_namespaces_status(self):
        status = []
        self.check_namespaces_available_status()
        namespaces = self.namespaces.namespaces
        for ns_name in namespaces:
            ns_obj = self.get_namespace(ns_name)
            status.append((ns_name, ns_obj.available))
        return status

    def bind_ns_files(self, pid, namespaces=None, ns_bind_dir=None):
        if ns_bind_dir is None or namespaces is None:
            return

        if not os.path.exists(ns_bind_dir):
            os.mkdir(ns_bind_dir)

        if not os.access(ns_bind_dir, os.R_OK | os.W_OK):
            raise RuntimeError("cannot access %s" % bind_ns_dir)

        path = "/proc/%d/ns" % pid
        for ns in namespaces:
            if ns == "mount": continue
            ns_obj = self.get_namespace(ns)
            if not ns_obj.available: continue
            entry = ns_obj.entry
            source = "%s/%s" % (path, entry)
            target = "%s/%s" % (ns_bind_dir.rstrip("/"), entry)
            if not os.path.exists(target):
                os.close(os.open(target, os.O_CREAT | os.O_RDWR))
            self.mount(source=source, target=target, mount_type="bind")


    def _namespace_available(self, namespace):
        ns_obj = self.get_namespace( namespace)
        return ns_obj.available


    def spawn_namespaces(
            self, namespaces=None, maproot=True, mountproc=True,
            mountpoint=None, ns_bind_dir=None, nscmd=None,
            propagation=None, negative_namespaces=None,
            setgroups=None, users_map=None, groups_map=None,
            init_prog=None, func=None, interactive=None):
        """
        workbench.spawn_namespace(namespaces=["pid", "net", "mount"])
        """
        SpawnNamespacesConfig(
            namespaces, maproot, mountproc, mountpoint, ns_bind_dir, nscmd,
            propagation, negative_namespaces,setgroups, users_map, groups_map,
            init_prog, func, interactive=interactive).entry_point()


class CFunctionBaseException(Exception):
    pass

class CFunctionCallFailed(CFunctionBaseException):
    pass

class CFunctionNotFound(CFunctionBaseException):
    pass

class CFunctionUnknowSyscall(CFunctionNotFound):
    pass

workbench = Workbench()
del Workbench

def get_available_propagations():
    return workbench.get_available_propagations()

def atfork(prepare=None, parent=None, child=None):
    return workbench.atfork(prepare, parent, child)

def sched_getcpu():
    return workbench.sched_getcpu()

def cgroup_namespace_available():
    return workbench.cgroup_namespace_available()

def ipc_namespace_available():
    return workbench.ipc_namespace_available()

def net_namespace_available():
    return workbench.net_namespace_available()

def mount_namespace_available():
    return workbench.mount_namespace_available()

def pid_namespace_available():
    return workbench.pid_namespace_available()

def user_namespace_available():
    return workbench.user_namespace_available()

def uts_namespace_available():
    return workbench.uts_namespace_available()

def mount(source=None, target=None, mount_type=None,
              filesystemtype=None, data=None):
    return workbench.mount(source, target, mount_type,
                               filesystemtype, data)

def umount(mountpoint=None):
    return workbench.umount(mountpoint)

def umount2(mountpoint=None, behavior=None):
    return workbench.umount2(mountpoint, behavior)

def unshare(namespaces=None):
    return workbench.unshare(namespaces)

def setns(**kwargs):
    """
    setns(fd, namespace)
    setns(path, namespace)
    setns(pid, namespace)
    setns(file_obj, namespace)
    """
    return workbench.setns(**kwargs)

def gethostname():
    return workbench.gethostname()

def sethostname(hostname=None):
    return workbench.sethostname(hostname)

def getdomainname():
    return workbench.getdomainname()

def setdomainname(domainname=None):
    return workbench.setdomainname(domainname)

def pivot_root(new_root, put_old):
    return workbench.pivot_root(new_root, put_old)

def adjust_namespaces(namespaces=None, negative_namespaces=None):
    return workbench.adjust_namespaces(namespaces, negative_namespaces)

def spawn_namespaces(namespaces=None, maproot=True, mountproc=True,
                         mountpoint="/proc", ns_bind_dir=None, nscmd=None,
                         propagation=None, negative_namespaces=None,
                         setgroups=None, users_map=None, groups_map=None,
                         init_prog=None, func=None, interactive=None):
    return workbench.spawn_namespaces(
        namespaces=namespaces, maproot=maproot, mountproc=mountproc,
        mountpoint=mountpoint, ns_bind_dir=ns_bind_dir, nscmd=nscmd,
        propagation=propagation, negative_namespaces=negative_namespaces,
        setgroups=setgroups, users_map=users_map, groups_map=groups_map,
        init_prog=init_prog, func=func, interactive=interactive)

def check_namespaces_available_status():
    return workbench.check_namespaces_available_status()

def get_namespace(name=None):
    return workbench.get_namespace(name)
def show_namespaces_status():
    return workbench.show_namespaces_status()

def show_available_c_functions():
    return workbench.show_available_c_functions()

def unregister_fork_handlers(prepare=None, parent=None,
                                 child=None, strict=False):
    return workbench.unregister_fork_handlers(prepare, parent, child, strict)

def find_shell(name=None, shell=None):
    if shell is not None:
        return shell
    if name is None:
        name = 'bash'
    fpath =pwd.getpwuid(os.geteuid()).pw_shell
    if os.path.isfile(fpath) and os.access(fpath, os.X_OK):
        if os.path.basename(fpath).endswith('sh'):
            return fpath

    if "SHELL" in os.environ:
        return os.environ.get("SHELL")
    for path in ["/bin", "/usr/bin", "/usr/loca/bin"]:
        fpath = "%s/%s" % (path, name)
        if os.path.isfile(fpath) and os.access(fpath, os.X_OK):
            return fpath
    return "sh"

def getresuid():
    return workbench.getresuid()

def getresgid():
    return workbench.getresgid()


def setresuid(ruid, euid, suid):
    return workbench.setresuid(ruid, euid, suid)


def setresgid(rgid, egid, sgid):
    return workbench.setresgid(rgid, egid, sgid)


def get_uid_from_name_or_uid(user_or_uid):
    try:
        uid = int(user_or_uid)
    except ValueError:
        uid = pwd.getpwnam(user_or_uid).pw_uid
    return uid


def get_gid_from_name_or_gid(group_or_gid):
    try:
        gid = int(group_or_gid)
    except ValueError:
        gid = grp.getgrnam(group_or_gid).gr_gid        
    return gid


def get_uid_by_name(user):
    return pwd.getpwnam(user).pw_uid


def get_gid_by_name(group):
    return grp.getgrnam(group).gr_gid


def get_name_by_uid(uid):
    return pwd.getpwuid(uid).pw_name


def get_name_by_gid(gid):
    return grp.getgrgid(gid).gr_name


def get_current_users_and_groups(displayer=None):
    ruid, euid, suid = getresuid()
    rgid, egid, sgid = getresgid()
    supplementary_groups = os.getgroups()

    _supplementary_groups = []
    for _id in supplementary_groups:
        _name = get_name_by_gid(_id)
        _supplementary_groups.append({'name': _name, 'id': _id})

    return {
        'users': {
            'real user': {'name': get_name_by_uid(ruid), 'id': ruid},
            'effective user': {'name': get_name_by_uid(euid), 'id': euid},
            'saved user': {'name': get_name_by_uid(suid), 'id': suid},},
        'groups': {
            'real group': {'name': get_name_by_gid(rgid), 'id': rgid},
            'effective group': {'name': get_name_by_gid(rgid), 'id': rgid},
            'saved group': {'name': get_name_by_gid(rgid), 'id': rgid},},
        'supplementary_groups': _supplementary_groups,
        }


if __name__ == "__main__":
    try:
        spawn_namespaces()
    except NamespaceRequireSuperuserPrivilege():
        warn(e)
        sys.exit(1)
