# -*- 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