Add PEP 3143: Standard daemon process library by Ben Finney.
This commit is contained in:
parent
d95f8fcb42
commit
0aaa8e193e
|
@ -0,0 +1,585 @@
|
|||
PEP: 3143
|
||||
Title: Standard daemon process library
|
||||
Version: $Revision: 1.1 $
|
||||
Last-Modified: $Date: 2009-03-19 12:51 $
|
||||
Author: Ben Finney <ben+python@benfinney.id.au>
|
||||
Status: Draft
|
||||
Type: Standards Track
|
||||
Content-Type: text/x-rst
|
||||
Created: 2009-01-26
|
||||
Python-Version: 3.2
|
||||
Post-History:
|
||||
|
||||
|
||||
========
|
||||
Abstract
|
||||
========
|
||||
|
||||
Writing a program to become a well-behaved Unix daemon is somewhat
|
||||
complex and tricky to get right, yet the steps are largely similar for
|
||||
any daemon regardless of what else the program may need to do.
|
||||
|
||||
This PEP introduces a package to the Python standard library that
|
||||
provides a simple interface to the task of becoming a daemon process.
|
||||
|
||||
|
||||
.. contents::
|
||||
..
|
||||
Table of Contents:
|
||||
Abstract
|
||||
Specification
|
||||
Example usage
|
||||
Interface
|
||||
``DaemonContext`` objects
|
||||
Motivation
|
||||
Rationale
|
||||
Correct daemon behaviour
|
||||
A daemon is not a service
|
||||
Reference Implementation
|
||||
Other daemon implementations
|
||||
References
|
||||
Copyright
|
||||
|
||||
|
||||
=============
|
||||
Specification
|
||||
=============
|
||||
|
||||
Example usage
|
||||
=============
|
||||
|
||||
Simple example of direct `DaemonContext` usage::
|
||||
|
||||
import daemon
|
||||
|
||||
from spam import do_main_program
|
||||
|
||||
with daemon.DaemonContext() as daemon_context:
|
||||
do_main_program()
|
||||
|
||||
More complex example usage::
|
||||
|
||||
import os
|
||||
import grp
|
||||
import signal
|
||||
import daemon
|
||||
import lockfile
|
||||
|
||||
from spam import (
|
||||
initial_program_setup,
|
||||
do_main_program,
|
||||
program_cleanup,
|
||||
reload_program_config,
|
||||
)
|
||||
|
||||
context = daemon.DaemonContext(
|
||||
working_directory='/var/lib/foo',
|
||||
umask=0o002,
|
||||
pidfile=lockfile.FileLock('/var/run/spam.pid'),
|
||||
)
|
||||
|
||||
context.signal_map = {
|
||||
signal.SIGTERM: program_cleanup,
|
||||
signal.SIGHUP: 'terminate',
|
||||
signal.SIGUSR1: reload_program_config,
|
||||
}
|
||||
|
||||
mail_gid = grp.getgrnam('mail').gr_gid
|
||||
context.gid = mail_gid
|
||||
|
||||
important_file = open('spam.data', 'w')
|
||||
interesting_file = open('eggs.data', 'w')
|
||||
context.files_preserve = [important_file, interesting_file]
|
||||
|
||||
initial_program_setup()
|
||||
|
||||
with context:
|
||||
do_main_program()
|
||||
|
||||
|
||||
Interface
|
||||
=========
|
||||
|
||||
A new package, `daemon`, is added to the standard library.
|
||||
|
||||
A class, `DaemonContext`, is defined to represent the settings and
|
||||
process context for the program running as a daemon process.
|
||||
|
||||
|
||||
``DaemonContext`` objects
|
||||
=========================
|
||||
|
||||
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.
|
||||
|
||||
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.SIGCLD``: ``None``
|
||||
|
||||
* ``signal.SIGTTIN``: ``None``
|
||||
|
||||
* ``signal.SIGTTOU``: ``None``
|
||||
|
||||
* ``signal.SIGTSTP``: ``None``
|
||||
|
||||
* ``signal.SIGTERM``: ``'terminate'``
|
||||
|
||||
`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`.
|
||||
|
||||
|
||||
The following methods are defined.
|
||||
|
||||
`open()`
|
||||
:Return: ``None``
|
||||
|
||||
Open the daemon context, turning the current program into a daemon
|
||||
process. This performs the following steps:
|
||||
|
||||
* 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.
|
||||
|
||||
When the function returns, the running program is a daemon
|
||||
process.
|
||||
|
||||
`close()`
|
||||
:Return: ``None``
|
||||
|
||||
Close the daemon context. This does nothing by default, but may be
|
||||
overridden by a derived class.
|
||||
|
||||
`terminate(signal_number, stack_frame)`
|
||||
:Return: ``None``
|
||||
|
||||
Signal handler for the ``signal.SIGTERM`` signal. Performs the
|
||||
following steps:
|
||||
|
||||
* If the `pidfile` attribute is not ``None``, exit its context
|
||||
manager.
|
||||
|
||||
* Call the `close()` method.
|
||||
|
||||
* Raise a ``SystemExit`` exception.
|
||||
|
||||
The class also implements the context manager protocol via
|
||||
``__enter__`` and ``__exit__`` methods.
|
||||
|
||||
`__enter__()`
|
||||
:Return: The ``DaemonContext`` instance
|
||||
|
||||
Call the instance's `open()` method, then return the instance.
|
||||
|
||||
`__exit__(exc_type, exc_value, exc_traceback)`
|
||||
:Return: ``True`` or ``False`` as defined by the context manager
|
||||
protocol
|
||||
|
||||
Call the instance's `close()` method, then return ``True`` if the
|
||||
exception was handled or ``False`` if it was not.
|
||||
|
||||
|
||||
==========
|
||||
Motivation
|
||||
==========
|
||||
|
||||
The majority of programs written to be Unix daemons either implement
|
||||
behaviour very similar to that in the `specification`_, or are
|
||||
poorly-behaved daemons by the `correct daemon behaviour`_.
|
||||
|
||||
Since these steps should be much the same in most implementations but
|
||||
are very particular and easy to omit or implement incorrectly, they
|
||||
are a prime target for a standard well-tested implementation in the
|
||||
standard library.
|
||||
|
||||
|
||||
=========
|
||||
Rationale
|
||||
=========
|
||||
|
||||
Correct daemon behaviour
|
||||
========================
|
||||
|
||||
According to Stevens in [stevens]_ §2.6, a program should perform the
|
||||
following steps to become a Unix daemon process.
|
||||
|
||||
* Close all open file descriptors.
|
||||
|
||||
* Change current working directory.
|
||||
|
||||
* Reset the file access creation mask.
|
||||
|
||||
* Run in the background.
|
||||
|
||||
* Disassociate from process group.
|
||||
|
||||
* Ignore terminal I/O signals.
|
||||
|
||||
* Disassociate from control terminal.
|
||||
|
||||
* Don't reacquire a control terminal.
|
||||
|
||||
* Correctly handle the following circumstances:
|
||||
|
||||
* Started by System V `init` process.
|
||||
|
||||
* Daemon termination by ``SIGTERM`` signal.
|
||||
|
||||
* Children generate ``SIGCLD`` signal.
|
||||
|
||||
The `daemon` tool [slack-daemon]_ lists (in its summary of features)
|
||||
behaviour that should be performed when turning a program into a
|
||||
well-behaved Unix daemon process. It differs from this PEP's intent in
|
||||
that it invokes a *separate* program as a daemon process. The
|
||||
following features are appropriate for a daemon that starts itself
|
||||
once the program is already running:
|
||||
|
||||
* Sets up the correct process context for a daemon.
|
||||
|
||||
* Behaves sensibly when started by `initd(8)` or `inetd(8)`.
|
||||
|
||||
* Revokes any suid or sgid privileges to reduce security risks in case
|
||||
daemon is incorrectly installed with special privileges.
|
||||
|
||||
* Prevents the generation of core files to prevent leaking sensitive
|
||||
information from daemons run as root (optional).
|
||||
|
||||
* Names the daemon by creating and locking a PID file to guarantee
|
||||
that only one daemon with the given name can execute at any given
|
||||
time (optional).
|
||||
|
||||
* Sets the user and group under which to run the daemon (optional,
|
||||
root only).
|
||||
|
||||
* Creates a chroot gaol (optional, root only).
|
||||
|
||||
* Captures the daemon's stdout and stderr and directs them to syslog
|
||||
(optional).
|
||||
|
||||
A daemon is not a service
|
||||
=========================
|
||||
|
||||
This PEP addresses only Unix-style daemons, for which the above
|
||||
correct behaviour is relevant, as opposed to comparable behaviours on
|
||||
other operating systems.
|
||||
|
||||
There is a related concept in many systems, called a “service”. A
|
||||
service differs from the model in this PEP, in that rather than having
|
||||
the *current* program continue to run as a daemon process, a service
|
||||
starts an *additional* process to run in the background, and the
|
||||
current process communicates with that additional process via some
|
||||
defined channels.
|
||||
|
||||
The Unix-style daemon model in this PEP can be used, among other
|
||||
things, to implement the background-process part of a service; but
|
||||
this PEP does not address the other aspects of setting up and managing
|
||||
a service.
|
||||
|
||||
|
||||
========================
|
||||
Reference Implementation
|
||||
========================
|
||||
|
||||
The `python-daemon` package [python-daemon]_.
|
||||
|
||||
Other daemon implementations
|
||||
============================
|
||||
|
||||
Prior to this PEP, several existing third-party Python libraries or
|
||||
tools implemented some of this PEP's `correct daemon behaviour`_.
|
||||
|
||||
The `reference implementation`_ is a fairly direct successor from the
|
||||
following implementations:
|
||||
|
||||
* Many good ideas were contributed by the community to Python cookbook
|
||||
recipes #66012 [cookbook-66012]_ and #278731 [cookbook-278731]_.
|
||||
|
||||
* The `bda.daemon` library [bda.daemon]_ is an implementation of
|
||||
[cookbook-66012]_. It is the predecessor of [python-daemon]_.
|
||||
|
||||
Other Python daemon implementations that differ from this PEP:
|
||||
|
||||
* The `zdaemon` tool [zdaemon]_ was written for the Zope project. Like
|
||||
[slack-daemon]_, it differs from this specification because it is
|
||||
used to run another program as a daemon process.
|
||||
|
||||
* The Python library `daemon` [clapper-daemon]_ is (according to its
|
||||
homepage) no longer maintained. As of version 1.0.1, it implements
|
||||
the basic steps from [stevens]_.
|
||||
|
||||
* The `daemonize` library [seutter-daemonize]_ also implements the
|
||||
basic steps from [stevens]_.
|
||||
|
||||
* Ray Burr's `daemon.py` module [burr-daemon]_ provides the [stevens]_
|
||||
procedure as well as PID file handling and redirection of output to
|
||||
syslog.
|
||||
|
||||
* Twisted [twisted]_ includes, perhaps unsurprisingly, an
|
||||
implementation of a process daemonisation API that is integrated
|
||||
with the rest of the Twisted framework; it differs significantly
|
||||
from the API in this PEP.
|
||||
|
||||
* The Python `initd` library [dagitses-initd]_, which uses
|
||||
[clapper-daemon]_, implements an equivalent of Unix `initd(8)` for
|
||||
controlling a daemon process.
|
||||
|
||||
|
||||
==========
|
||||
References
|
||||
==========
|
||||
|
||||
.. [stevens]
|
||||
|
||||
`Unix Network Programming`, W. Richard Stevens, 1994 Prentice
|
||||
Hall.
|
||||
|
||||
.. [slack-daemon]
|
||||
|
||||
The (non-Python) “libslack” implementation of a `daemon` tool
|
||||
`<http://www.libslack.org/daemon/>`_ by “raf” <raf@raf.org>.
|
||||
|
||||
.. [python-daemon]
|
||||
|
||||
The `python-daemon` library
|
||||
`<http://pypi.python.org/pypi/python-daemon/>`_ by Ben Finney et
|
||||
al.
|
||||
|
||||
.. [cookbook-66012]
|
||||
|
||||
Python Cookbook recipe 66012, “Fork a daemon process on Unix”
|
||||
`<http://code.activestate.com/recipes/66012/>`_.
|
||||
|
||||
.. [cookbook-278731]
|
||||
|
||||
Python Cookbook recipe 278731, “Creating a daemon the Python way”
|
||||
`<http://code.activestate.com/recipes/278731/>`_.
|
||||
|
||||
.. [bda.daemon]
|
||||
|
||||
The `bda.daemon` library
|
||||
`<http://pypi.python.org/pypi/bda.daemon/>`_ by Robert
|
||||
Niederreiter et al.
|
||||
|
||||
.. [zdaemon]
|
||||
|
||||
The `zdaemon` tool `<http://pypi.python.org/pypi/zdaemon/>`_ by
|
||||
Guido van Rossum et al.
|
||||
|
||||
.. [clapper-daemon]
|
||||
|
||||
The `daemon` library `<http://pypi.python.org/pypi/daemon/>`_ by
|
||||
Brian Clapper.
|
||||
|
||||
.. [seutter-daemonize]
|
||||
|
||||
The `daemonize` library `<http://daemonize.sourceforge.net/>`_ by
|
||||
Jerry Seutter.
|
||||
|
||||
.. [burr-daemon]
|
||||
|
||||
The `daemon.py` module
|
||||
`<http://www.nightmare.com/~ryb/code/daemon.py>`_ by Ray Burr.
|
||||
|
||||
.. [twisted]
|
||||
|
||||
The `Twisted` application framework
|
||||
`<http://pypi.python.org/pypi/Twisted/>`_ by Glyph Lefkowitz et
|
||||
al.
|
||||
|
||||
.. [dagitses-initd]
|
||||
|
||||
The Python `initd` library `<http://pypi.python.org/pypi/initd/>`_
|
||||
by Michael Andreas Dagitses.
|
||||
|
||||
|
||||
=========
|
||||
Copyright
|
||||
=========
|
||||
|
||||
This work is hereby placed in the public domain. To the extent that
|
||||
placing a work in the public domain is not legally possible, the
|
||||
copyright holder hereby grants to all recipients of this work all
|
||||
rights and freedoms that would otherwise be restricted by copyright.
|
||||
|
||||
|
||||
..
|
||||
Local variables:
|
||||
mode: rst
|
||||
coding: utf-8
|
||||
time-stamp-start: "^:Last-Modified:[ ]+"
|
||||
time-stamp-end: "$"
|
||||
time-stamp-line-limit: 20
|
||||
time-stamp-format: "%:y-%02m-%02d %02H:%02M"
|
||||
End:
|
||||
vim: filetype=rst fileencoding=utf-8 :
|
Loading…
Reference in New Issue