2009-03-18 22:14:42 -04:00
|
|
|
PEP: 3143
|
|
|
|
Title: Standard daemon process library
|
2009-03-31 14:11:49 -04:00
|
|
|
Version: $Revision$
|
|
|
|
Last-Modified: $Date$
|
2009-03-18 22:14:42 -04:00
|
|
|
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 :
|