Source code for deadbeat.daemon

# -*- coding: utf-8 -*-

""" Daemonisation machinery for DeadBeat """

from __future__ import absolute_import, division, print_function, unicode_literals

import sys
import os
import os.path as pth
import resource
import atexit
import logging
from . import conf

__all__ = ["daemon_base_config", "detach"]

#: Daemon configuration insert
daemon_base_config = [
    conf.cfg_item("daemonize", conf.cast_boolean, "Run as daemon", default="Yes"),
    conf.cfg_item("work_dir", str, "Working directory path", default=pth.expanduser("~")),
    conf.cfg_item("chroot_dir", str, "Chroot directory path", default=None),
    conf.cfg_item("umask", str, "File mode creation mask"),
    conf.cfg_item("uid", str, "User id to run under"),
    conf.cfg_item("gid", str, "Group id to run under"),
    conf.cfg_item("pid", str, "Use this PID file", default=pth.join("/var/run", pth.splitext(pth.basename(sys.argv[0]))[0] + ".pid"))
]

[docs]def detach( work_dir=None, chroot_dir=None, umask=None, uid=None, gid=None, pid=None, files_preserve=[], daemonize=False): """ Daemonize process, trying to be well-behaving unix citizen. Function does double fork with resetting session id, closes all descriptors (except for files_preserve and standard logging ones, redirects stdin/stdout to /dev/null and (if pid is defined) creates pid file (and sets it to be deleted on exit). :param work_dir: Directory to `cd` to. :param chroot_dir: Directory to `chroot` to. :param umask: File mode creation mask to set. :param uid: User id to switch to. :param gid: Group id to switch to. :param pid: Path to .pid file. :param files_preserve: Iterable of file descriptors to keep open (detach already tries to NOT close logging file and socket descriptors). :param daemonize: Force daemonize to do nothing (useful for passing configuration option). """ if not daemonize: return try: # Dirs, limits, users if chroot_dir is not None: os.chdir(chroot_dir) os.chroot(chroot_dir) if umask is not None: os.umask(umask) if work_dir is not None: os.chdir(work_dir) if gid is not None: os.setgid(gid) if uid is not None: os.setuid(uid) # Doublefork, split session if os.fork() > 0: os._exit(0) try: os.setsid() except OSError: pass if os.fork() > 0: os._exit(0) # Close descriptors descr_preserve = set(f.fileno() for f in files_preserve) # Imprecise attempt to NOT close already open logging descriptors loggers = [logging.getLogger()] loggers.extend(logging.Logger.manager.loggerDict.values()) for logger in loggers: if hasattr(logger, 'handlers'): for handler in logger.handlers: try: descr_preserve.add(handler.stream.fileno()) except AttributeError: pass try: descr_preserve.add(handler.socket.fileno()) except AttributeError: pass maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1] if maxfd == resource.RLIM_INFINITY: maxfd = 65535 for fd in range(maxfd, 3, -1): # 3 means omit stdin, stdout, stderr if fd not in descr_preserve: try: os.close(fd) except Exception: pass # Redirect stdin, stdout, stderr to /dev/null devnull = os.open(os.devnull, os.O_RDWR) for fd in range(3): os.dup2(devnull, fd) # PID file if pid is not None: pidd = os.open(pid, os.O_RDWR | os.O_CREAT | os.O_EXCL | os.O_TRUNC) os.write(pidd, str(os.getpid()).encode("utf-8")) os.close(pidd) @atexit.register def unlink_pid(): try: os.unlink(pid) except Exception: pass except Exception: logging.exception("Failed daemonization") raise