diff --git a/virtwho/daemon/__init__.py b/virtwho/daemon/__init__.py deleted file mode 100644 index 594614ea..00000000 --- a/virtwho/daemon/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import print_function, absolute_import - -from .daemon import DaemonContext - -__all__ = ['DaemonContext'] diff --git a/virtwho/daemon/daemon.py b/virtwho/daemon/daemon.py deleted file mode 100644 index ed85b416..00000000 --- a/virtwho/daemon/daemon.py +++ /dev/null @@ -1,774 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import print_function - -# daemon/daemon.py -# Part of python-daemon, an implementation of PEP 3143. -# -# Copyright © 2008–2010 Ben Finney -# Copyright © 2007–2008 Robert Niederreiter, Jens Klein -# Copyright © 2004–2005 Chad J. Schroeder -# Copyright © 2003 Clark Evans -# Copyright © 2002 Noah Spurrier -# Copyright © 2001 Jürgen Hermann -# -# This is free software: you may copy, modify, and/or distribute this work -# under the terms of the Python Software Foundation License, version 2 or -# later as published by the Python Software Foundation. -# No warranty expressed or implied. See the file LICENSE.PSF-2 for details. - -""" Daemon process behaviour. - """ - -import os -import sys -import resource -import errno -import signal -import socket -import atexit - - -class DaemonError(Exception): - """ Base exception class for errors from this module. """ - - -class DaemonOSEnvironmentError(DaemonError, OSError): - """ Exception raised when daemon OS environment setup receives error. """ - - -class DaemonProcessDetachError(DaemonError, OSError): - """ Exception raised when process detach fails. """ - - -class DaemonContext(object): - """ Context for turning the current program into a daemon process. - - A `DaemonContext` instance represents the behaviour settings and - process context for the program when it becomes a daemon. The - behaviour and environment is customised by setting options on the - instance, before calling the `open` method. - - Each option can be passed as a keyword argument to the `DaemonContext` - constructor, or subsequently altered by assigning to an attribute on - the instance at any time prior to calling `open`. That is, for - options named `wibble` and `wubble`, the following invocation:: - - foo = daemon.DaemonContext(wibble=bar, wubble=baz) - foo.open() - - is equivalent to:: - - foo = daemon.DaemonContext() - foo.wibble = bar - foo.wubble = baz - foo.open() - - The following options are defined. - - `files_preserve` - :Default: ``None`` - - List of files that should *not* be closed when starting the - daemon. If ``None``, all open file descriptors will be closed. - - Elements of the list are file descriptors (as returned by a file - object's `fileno()` method) or Python `file` objects. Each - specifies a file that is not to be closed during daemon start. - - `chroot_directory` - :Default: ``None`` - - Full path to a directory to set as the effective root directory of - the process. If ``None``, specifies that the root directory is not - to be changed. - - `working_directory` - :Default: ``'/'`` - - Full path of the working directory to which the process should - change on daemon start. - - Since a filesystem cannot be unmounted if a process has its - current working directory on that filesystem, this should either - be left at default or set to a directory that is a sensible “home - directory” for the daemon while it is running. - - `umask` - :Default: ``0`` - - File access creation mask (“umask”) to set for the process on - daemon start. - - Since a process inherits its umask from its parent process, - starting the daemon will reset the umask to this value so that - files are created by the daemon with access modes as it expects. - - `pidfile` - :Default: ``None`` - - Context manager for a PID lock file. When the daemon context opens - and closes, it enters and exits the `pidfile` context manager. - - `detach_process` - :Default: ``None`` - - If ``True``, detach the process context when opening the daemon - context; if ``False``, do not detach. - - If unspecified (``None``) during initialisation of the instance, - this will be set to ``True`` by default, and ``False`` only if - detaching the process is determined to be redundant; for example, - in the case when the process was started by `init`, by `initd`, or - by `inetd`. - - `signal_map` - :Default: system-dependent - - Mapping from operating system signals to callback actions. - - The mapping is used when the daemon context opens, and determines - the action for each signal's signal handler: - - * A value of ``None`` will ignore the signal (by setting the - signal action to ``signal.SIG_IGN``). - - * A string value will be used as the name of an attribute on the - ``DaemonContext`` instance. The attribute's value will be used - as the action for the signal handler. - - * Any other value will be used as the action for the - signal handler. See the ``signal.signal`` documentation - for details of the signal handler interface. - - The default value depends on which signals are defined on the - running system. Each item from the list below whose signal is - actually defined in the ``signal`` module will appear in the - default map: - - * ``signal.SIGTTIN``: ``None`` - - * ``signal.SIGTTOU``: ``None`` - - * ``signal.SIGTSTP``: ``None`` - - * ``signal.SIGTERM``: ``'terminate'`` - - Depending on how the program will interact with its child - processes, it may need to specify a signal map that - includes the ``signal.SIGCHLD`` signal (received when a - child process exits). See the specific operating system's - documentation for more detail on how to determine what - circumstances dictate the need for signal handlers. - - `uid` - :Default: ``os.getuid()`` - - `gid` - :Default: ``os.getgid()`` - - The user ID (“UID”) value and group ID (“GID”) value to switch - the process to on daemon start. - - The default values, the real UID and GID of the process, will - relinquish any effective privilege elevation inherited by the - process. - - `prevent_core` - :Default: ``True`` - - If true, prevents the generation of core files, in order to avoid - leaking sensitive information from daemons run as `root`. - - `stdin` - :Default: ``None`` - - `stdout` - :Default: ``None`` - - `stderr` - :Default: ``None`` - - Each of `stdin`, `stdout`, and `stderr` is a file-like object - which will be used as the new file for the standard I/O stream - `sys.stdin`, `sys.stdout`, and `sys.stderr` respectively. The file - should therefore be open, with a minimum of mode 'r' in the case - of `stdin`, and mode 'w+' in the case of `stdout` and `stderr`. - - If the object has a `fileno()` method that returns a file - descriptor, the corresponding file will be excluded from being - closed during daemon start (that is, it will be treated as though - it were listed in `files_preserve`). - - If ``None``, the corresponding system stream is re-bound to the - file named by `os.devnull`. - - """ - - def __init__( - self, - chroot_directory=None, - working_directory='/', - umask=0, - uid=None, - gid=None, - prevent_core=True, - detach_process=None, - files_preserve=None, - pidfile=None, - stdin=None, - stdout=None, - stderr=None, - signal_map=None): - """ Set up a new instance. """ - self.chroot_directory = chroot_directory - self.working_directory = working_directory - self.umask = umask - self.prevent_core = prevent_core - self.files_preserve = files_preserve - self.pidfile = pidfile - self.stdin = stdin - self.stdout = stdout - self.stderr = stderr - - if uid is None: - uid = os.getuid() - self.uid = uid - if gid is None: - gid = os.getgid() - self.gid = gid - - if detach_process is None: - detach_process = is_detach_process_context_required() - self.detach_process = detach_process - - if signal_map is None: - signal_map = make_default_signal_map() - self.signal_map = signal_map - - self._is_open = False - - @property - def is_open(self): - """ ``True`` if the instance is currently open. """ - return self._is_open - - def open(self): - """ Become a daemon process. - :Return: ``None`` - - Open the daemon context, turning the current program into a daemon - process. This performs the following steps: - - * If this instance's `is_open` property is true, return - immediately. This makes it safe to call `open` multiple times on - an instance. - - * If the `prevent_core` attribute is true, set the resource limits - for the process to prevent any core dump from the process. - - * If the `chroot_directory` attribute is not ``None``, set the - effective root directory of the process to that directory (via - `os.chroot`). - - This allows running the daemon process inside a “chroot gaol” - as a means of limiting the system's exposure to rogue behaviour - by the process. Note that the specified directory needs to - already be set up for this purpose. - - * Set the process UID and GID to the `uid` and `gid` attribute - values. - - * Close all open file descriptors. This excludes those listed in - the `files_preserve` attribute, and those that correspond to the - `stdin`, `stdout`, or `stderr` attributes. - - * Change current working directory to the path specified by the - `working_directory` attribute. - - * Reset the file access creation mask to the value specified by - the `umask` attribute. - - * If the `detach_process` option is true, detach the current - process into its own process group, and disassociate from any - controlling terminal. - - * Set signal handlers as specified by the `signal_map` attribute. - - * If any of the attributes `stdin`, `stdout`, `stderr` are not - ``None``, bind the system streams `sys.stdin`, `sys.stdout`, - and/or `sys.stderr` to the files represented by the - corresponding attributes. Where the attribute has a file - descriptor, the descriptor is duplicated (instead of re-binding - the name). - - * If the `pidfile` attribute is not ``None``, enter its context - manager. - - * Mark this instance as open (for the purpose of future `open` and - `close` calls). - - * Register the `close` method to be called during Python's exit - processing. - - When the function returns, the running program is a daemon - process. - - """ - if self.is_open: - return - - if self.chroot_directory is not None: - change_root_directory(self.chroot_directory) - - if self.prevent_core: - prevent_core_dump() - - change_file_creation_mask(self.umask) - change_working_directory(self.working_directory) - change_process_owner(self.uid, self.gid) - - if self.detach_process: - detach_process_context() - - signal_handler_map = self._make_signal_handler_map() - set_signal_handlers(signal_handler_map) - - exclude_fds = self._get_exclude_file_descriptors() - close_all_open_files(exclude=exclude_fds) - - redirect_stream(sys.stdin, self.stdin) - redirect_stream(sys.stdout, self.stdout) - redirect_stream(sys.stderr, self.stderr) - - if self.pidfile is not None: - self.pidfile.__enter__() - - self._is_open = True - - register_atexit_function(self.close) - - def __enter__(self): - """ Context manager entry point. """ - self.open() - return self - - def close(self): - """ Exit the daemon process context. - :Return: ``None`` - - Close the daemon context. This performs the following steps: - - * If this instance's `is_open` property is false, return - immediately. This makes it safe to call `close` multiple times - on an instance. - - * If the `pidfile` attribute is not ``None``, exit its context - manager. - - * Mark this instance as closed (for the purpose of future `open` - and `close` calls). - - """ - if not self.is_open: - return - - if self.pidfile is not None: - # Follow the interface for telling a context manager to exit, - # . - self.pidfile.__exit__(None, None, None) - - self._is_open = False - - def __exit__(self, exc_type, exc_value, traceback): - """ Context manager exit point. """ - self.close() - - def terminate(self, signal_number, stack_frame): - """ Signal handler for end-process signals. - :Return: ``None`` - - Signal handler for the ``signal.SIGTERM`` signal. Performs the - following step: - - * Raise a ``SystemExit`` exception explaining the signal. - - """ - exception = SystemExit( - "Terminating on signal %(signal_number)r" % vars()) - raise exception - - def _get_exclude_file_descriptors(self): - """ Return the set of file descriptors to exclude closing. - - Returns a set containing the file descriptors for the - items in `files_preserve`, and also each of `stdin`, - `stdout`, and `stderr`: - - * If the item is ``None``, it is omitted from the return - set. - - * If the item has a ``fileno()`` method, that method's - return value is in the return set. - - * Otherwise, the item is in the return set verbatim. - - """ - files_preserve = self.files_preserve - if files_preserve is None: - files_preserve = [] - files_preserve.extend( - item for item in [self.stdin, self.stdout, self.stderr] - if hasattr(item, 'fileno')) - exclude_descriptors = set() - for item in files_preserve: - if item is None: - continue - if hasattr(item, 'fileno'): - exclude_descriptors.add(item.fileno()) - else: - exclude_descriptors.add(item) - return exclude_descriptors - - def _make_signal_handler(self, target): - """ Make the signal handler for a specified target object. - - If `target` is ``None``, returns ``signal.SIG_IGN``. If - `target` is a string, returns the attribute of this - instance named by that string. Otherwise, returns `target` - itself. - - """ - if target is None: - result = signal.SIG_IGN - elif isinstance(target, str): - name = target - result = getattr(self, name) - else: - result = target - - return result - - def _make_signal_handler_map(self): - """ Make the map from signals to handlers for this instance. - - Constructs a map from signal numbers to handlers for this - context instance, suitable for passing to - `set_signal_handlers`. - - """ - signal_handler_map = dict( - (signal_number, self._make_signal_handler(target)) - for (signal_number, target) in self.signal_map.items()) - return signal_handler_map - - -def change_working_directory(directory): - """ Change the working directory of this process. - """ - try: - os.chdir(directory) - except Exception as exc: - error = DaemonOSEnvironmentError( - "Unable to change working directory (%s)" % exc) - raise error - - -def change_root_directory(directory): - """ Change the root directory of this process. - - Sets the current working directory, then the process root - directory, to the specified `directory`. Requires appropriate - OS privileges for this process. - - """ - try: - os.chdir(directory) - os.chroot(directory) - except Exception as exc: - error = DaemonOSEnvironmentError( - "Unable to change root directory (%s)" % exc) - raise error - - -def change_file_creation_mask(mask): - """ Change the file creation mask for this process. - """ - try: - os.umask(mask) - except Exception as exc: - error = DaemonOSEnvironmentError( - "Unable to change file creation mask (%s)" % exc) - raise error - - -def change_process_owner(uid, gid): - """ Change the owning UID and GID of this process. - - Sets the GID then the UID of the process (in that order, to - avoid permission errors) to the specified `gid` and `uid` - values. Requires appropriate OS privileges for this process. - - """ - try: - os.setgid(gid) - os.setuid(uid) - except Exception as exc: - error = DaemonOSEnvironmentError( - "Unable to change file creation mask (%s)" % exc) - raise error - - -def prevent_core_dump(): - """ Prevent this process from generating a core dump. - - Sets the soft and hard limits for core dump size to zero. On - Unix, this prevents the process from creating core dump - altogether. - - """ - core_resource = resource.RLIMIT_CORE - - try: - # Ensure the resource limit exists on this platform, by requesting - # its current value - resource.getrlimit(core_resource) - except ValueError as exc: - error = DaemonOSEnvironmentError( - "System does not support RLIMIT_CORE resource limit (%s)" % exc) - raise error - - # Set hard and soft limits to zero, i.e. no core dump at all - core_limit = (0, 0) - resource.setrlimit(core_resource, core_limit) - - -def detach_process_context(): - """ Detach the process context from parent and session. - - Detach from the parent process and session group, allowing the - parent to exit while this process continues running. - - Reference: “Advanced Programming in the Unix Environment”, - section 13.3, by W. Richard Stevens, published 1993 by - Addison-Wesley. - - """ - - def fork_then_exit_parent(error_message): - """ Fork a child process, then exit the parent process. - - If the fork fails, raise a ``DaemonProcessDetachError`` - with ``error_message``. - - """ - try: - pid = os.fork() - if pid > 0: - # pylint: disable=W0212 - os._exit(0) - except OSError as exc: - error = DaemonProcessDetachError( - "%(error_message)s: [%(exc_errno)d] %(exc_strerror)s" % { - 'error_message': error_message, - 'exc_errno': exc.errno, - 'exc_strerror': exc.strerror}) - raise error - - fork_then_exit_parent(error_message="Failed first fork") - os.setsid() - fork_then_exit_parent(error_message="Failed second fork") - - -def is_process_started_by_init(): - """ Determine if the current process is started by `init`. - - The `init` process has the process ID of 1; if that is our - parent process ID, return ``True``, otherwise ``False``. - - """ - result = False - - init_pid = 1 - if os.getppid() == init_pid: - result = True - - return result - - -def is_socket(fd): - """ Determine if the file descriptor is a socket. - - Return ``False`` if querying the socket type of `fd` raises an - error; otherwise return ``True``. - - """ - result = False - - file_socket = socket.fromfd(fd, socket.AF_INET, socket.SOCK_RAW) - - try: - file_socket.getsockopt( - socket.SOL_SOCKET, socket.SO_TYPE) - except socket.error as exc: - exc_errno = exc.args[0] - if exc_errno == errno.ENOTSOCK: - # Socket operation on non-socket - pass - else: - # Some other socket error - result = True - else: - # No error getting socket type - result = True - - return result - - -def is_process_started_by_superserver(): - """ Determine if the current process is started by the superserver. - - The internet superserver creates a network socket, and - attaches it to the standard streams of the child process. If - that is the case for this process, return ``True``, otherwise - ``False``. - - """ - result = False - - stdin_fd = sys.__stdin__.fileno() - if is_socket(stdin_fd): - result = True - - return result - - -def is_detach_process_context_required(): - """ Determine whether detaching process context is required. - - Return ``True`` if the process environment indicates the - process is already detached: - - * Process was started by `init`; or - - * Process was started by `inetd`. - - """ - result = True - if is_process_started_by_init() or is_process_started_by_superserver(): - result = False - - return result - - -def close_file_descriptor_if_open(fd): - """ Close a file descriptor if already open. - - Close the file descriptor `fd`, suppressing an error in the - case the file was not open. - - """ - try: - os.close(fd) - except OSError as exc: - if exc.errno == errno.EBADF: - # File descriptor was not open - pass - else: - error = DaemonOSEnvironmentError( - "Failed to close file descriptor %(fd)d" - " (%(exc)s)" % {'fd': fd, 'exc': exc}) - raise error - - -MAXFD = 2048 - - -def get_maximum_file_descriptors(): - """ Return the maximum number of open file descriptors for this process. - - Return the process hard resource limit of maximum number of - open file descriptors. If the limit is “infinity”, a default - value of ``MAXFD`` is returned. - - """ - limits = resource.getrlimit(resource.RLIMIT_NOFILE) - result = limits[1] - if result == resource.RLIM_INFINITY: - result = MAXFD - return result - - -def close_all_open_files(exclude=None): - """ Close all open file descriptors. - - Closes every file descriptor (if open) of this process. If - specified, `exclude` is a set of file descriptors to *not* - close. - - """ - if exclude is None: - exclude = set() - maxfd = get_maximum_file_descriptors() - for fd in reversed(range(maxfd)): - if fd not in exclude: - close_file_descriptor_if_open(fd) - - -def redirect_stream(system_stream, target_stream): - """ Redirect a system stream to a specified file. - - `system_stream` is a standard system stream such as - ``sys.stdout``. `target_stream` is an open file object that - should replace the corresponding system stream object. - - If `target_stream` is ``None``, defaults to opening the - operating system's null device and using its file descriptor. - - """ - if target_stream is None: - target_fd = os.open(os.devnull, os.O_RDWR) - else: - target_fd = target_stream.fileno() - os.dup2(target_fd, system_stream.fileno()) - - -def make_default_signal_map(): - """ Make the default signal map for this system. - - The signals available differ by system. The map will not - contain any signals not defined on the running system. - - """ - name_map = { - 'SIGTSTP': None, - 'SIGTTIN': None, - 'SIGTTOU': None, - 'SIGTERM': 'terminate', - } - signal_map = dict( - (getattr(signal, name), target) - for (name, target) in name_map.items() - if hasattr(signal, name)) - - return signal_map - - -def set_signal_handlers(signal_handler_map): - """ Set the signal handlers as specified. - - The `signal_handler_map` argument is a map from signal number - to signal handler. See the `signal` module for details. - - """ - for (signal_number, handler) in signal_handler_map.items(): - signal.signal(signal_number, handler) - - -def register_atexit_function(func): - """ Register a function for processing at program exit. - - The function `func` is registered for a call with no arguments - at program exit. - - """ - atexit.register(func)