]> gitweb.maison.local Git - rome.git/commitdiff
test.py : working in python3 (!!!)
authorlionel <lionel@isis.maison.local>
Fri, 26 Aug 2016 13:54:27 +0000 (15:54 +0200)
committerlionel <lionel@isis.maison.local>
Fri, 26 Aug 2016 13:54:27 +0000 (15:54 +0200)
44 files changed:
another.py [new file with mode: 0755]
modules/daemon/__init__.py [new file with mode: 0644]
modules/daemon/__pycache__/__init__.cpython-34.pyc [new file with mode: 0644]
modules/daemon/__pycache__/daemon.cpython-34.pyc [new file with mode: 0644]
modules/daemon/__pycache__/pidfile.cpython-34.pyc [new file with mode: 0644]
modules/daemon/__pycache__/runner.cpython-34.pyc [new file with mode: 0644]
modules/daemon/_metadata.py [new file with mode: 0644]
modules/daemon/daemon.py [new file with mode: 0644]
modules/daemon/pidfile.py [new file with mode: 0644]
modules/daemon/runner.py [new file with mode: 0644]
modules/lockfile/__init__.py [new file with mode: 0644]
modules/lockfile/__pycache__/__init__.cpython-34.pyc [new file with mode: 0644]
modules/lockfile/__pycache__/linklockfile.cpython-34.pyc [new file with mode: 0644]
modules/lockfile/__pycache__/pidlockfile.cpython-34.pyc [new file with mode: 0644]
modules/lockfile/linklockfile.py [new file with mode: 0644]
modules/lockfile/mkdirlockfile.py [new file with mode: 0644]
modules/lockfile/pidlockfile.py [new file with mode: 0644]
modules/lockfile/sqlitelockfile.py [new file with mode: 0644]
modules/lockfile/symlinklockfile.py [new file with mode: 0644]
test.py [changed mode: 0644->0755]
tmp/lockfile-0.12.2.tar.gz [new file with mode: 0644]
tmp/lockfile-0.12.2/ACKS [new file with mode: 0644]
tmp/lockfile-0.12.2/AUTHORS [new file with mode: 0644]
tmp/lockfile-0.12.2/ChangeLog [new file with mode: 0644]
tmp/lockfile-0.12.2/LICENSE [new file with mode: 0644]
tmp/lockfile-0.12.2/PKG-INFO [new file with mode: 0644]
tmp/lockfile-0.12.2/README.rst [new file with mode: 0644]
tmp/lockfile-0.12.2/RELEASE-NOTES [new file with mode: 0644]
tmp/lockfile-0.12.2/doc/source/Makefile [new file with mode: 0644]
tmp/lockfile-0.12.2/doc/source/conf.py [new file with mode: 0644]
tmp/lockfile-0.12.2/doc/source/index.rst [new file with mode: 0644]
tmp/lockfile-0.12.2/lockfile.egg-info/PKG-INFO [new file with mode: 0644]
tmp/lockfile-0.12.2/lockfile.egg-info/SOURCES.txt [new file with mode: 0644]
tmp/lockfile-0.12.2/lockfile.egg-info/dependency_links.txt [new file with mode: 0644]
tmp/lockfile-0.12.2/lockfile.egg-info/not-zip-safe [new file with mode: 0644]
tmp/lockfile-0.12.2/lockfile.egg-info/pbr.json [new file with mode: 0644]
tmp/lockfile-0.12.2/lockfile.egg-info/top_level.txt [new file with mode: 0644]
tmp/lockfile-0.12.2/setup.cfg [new file with mode: 0644]
tmp/lockfile-0.12.2/setup.py [new file with mode: 0644]
tmp/lockfile-0.12.2/test-requirements.txt [new file with mode: 0644]
tmp/lockfile-0.12.2/test/compliancetest.py [new file with mode: 0644]
tmp/lockfile-0.12.2/test/test_lockfile.py [new file with mode: 0644]
tmp/lockfile-0.12.2/tox.ini [new file with mode: 0644]
tmp/python-daemon-2.1.1.tar.gz [new file with mode: 0644]

diff --git a/another.py b/another.py
new file mode 100755 (executable)
index 0000000..317d8cf
--- /dev/null
@@ -0,0 +1,51 @@
+#!/usr/bin/python3
+# -*- coding:Utf-8 -*-
+
+import os,sys,inspect           # for the following
+cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],"modules")))
+if cmd_subfolder not in sys.path:
+  sys.path.insert(0, cmd_subfolder)
+
+import lockfile                # daemonization
+from daemon import runner      # daemonization
+
+
+from multiprocessing import Process
+
+def start():
+  with context:
+    pidfile = open(Config.WDIR+scriptname+".pid",'w')
+    pidfile.write(str(getpid()))
+    pidfile.close()
+    feed_the_db()
+
+def stop(pid):
+  try:
+    kill(int(pid),15)
+  except ProcessLookupError:
+    print("Nothing to kill… (No process with PID "+pid+")")
+
+if __name__ == "__main__":
+  scriptname = sys.argv[0]
+  context = daemon.DaemonContext(
+  working_directory=Config.WDIR,
+  pidfile=lockfile.FileLock(Config.WDIR+scriptname),
+  stdout=sys.stdout,
+  stderr=sys.stderr)
+  try:
+    if sys.argv[1] == 'start':
+      start()
+    elif sys.argv[1] == 'stop':
+      try:
+        pidfile = open(Config.WDIR+scriptname+".pid",'r')
+        pid = pidfile.read()
+        pidfile.close()
+        remove(name+".pid")
+        print(name+" (PID "+pid+")")
+        stop(pid)
+      except FileNotFoundError:
+        print("Nothing to kill… ("+scriptname+".pid not found)")
+    else:
+      print("\nUnknown option : "+sys.argv[1]+"\n\nUsage "+sys.argv[0]+" <start|stop>\n")
+  except IndexError:
+    print("\nUsage "+sys.argv[0]+" <start|stop>\n")
diff --git a/modules/daemon/__init__.py b/modules/daemon/__init__.py
new file mode 100644 (file)
index 0000000..b9a6d33
--- /dev/null
@@ -0,0 +1,49 @@
+# -*- coding: utf-8 -*-
+
+# daemon/__init__.py
+# Part of ‘python-daemon’, an implementation of PEP 3143.
+#
+# Copyright © 2009–2016 Ben Finney <ben+python@benfinney.id.au>
+# Copyright © 2006 Robert Niederreiter
+#
+# This is free software: you may copy, modify, and/or distribute this work
+# under the terms of the Apache License, version 2.0 as published by the
+# Apache Software Foundation.
+# No warranty expressed or implied. See the file ‘LICENSE.ASF-2’ for details.
+
+""" Library to implement a well-behaved Unix daemon process.
+
+    This library implements the well-behaved daemon specification of
+    :pep:`3143`, “Standard daemon process library”.
+
+    A well-behaved Unix daemon process is tricky to get right, but the
+    required steps are much the same for every daemon program. A
+    `DaemonContext` instance holds the behaviour and configured
+    process environment for the program; use the instance as a context
+    manager to enter a daemon state.
+
+    Simple example of usage::
+
+        import daemon
+
+        from spam import do_main_program
+
+        with daemon.DaemonContext():
+            do_main_program()
+
+    Customisation of the steps to become a daemon is available by
+    setting options on the `DaemonContext` instance; see the
+    documentation for that class for each option.
+
+    """
+
+from __future__ import (absolute_import, unicode_literals)
+
+from .daemon import DaemonContext
+
+\f
+# Local variables:
+# coding: utf-8
+# mode: python
+# End:
+# vim: fileencoding=utf-8 filetype=python :
diff --git a/modules/daemon/__pycache__/__init__.cpython-34.pyc b/modules/daemon/__pycache__/__init__.cpython-34.pyc
new file mode 100644 (file)
index 0000000..3abdd21
Binary files /dev/null and b/modules/daemon/__pycache__/__init__.cpython-34.pyc differ
diff --git a/modules/daemon/__pycache__/daemon.cpython-34.pyc b/modules/daemon/__pycache__/daemon.cpython-34.pyc
new file mode 100644 (file)
index 0000000..7a3a9dc
Binary files /dev/null and b/modules/daemon/__pycache__/daemon.cpython-34.pyc differ
diff --git a/modules/daemon/__pycache__/pidfile.cpython-34.pyc b/modules/daemon/__pycache__/pidfile.cpython-34.pyc
new file mode 100644 (file)
index 0000000..a6473f1
Binary files /dev/null and b/modules/daemon/__pycache__/pidfile.cpython-34.pyc differ
diff --git a/modules/daemon/__pycache__/runner.cpython-34.pyc b/modules/daemon/__pycache__/runner.cpython-34.pyc
new file mode 100644 (file)
index 0000000..bd50101
Binary files /dev/null and b/modules/daemon/__pycache__/runner.cpython-34.pyc differ
diff --git a/modules/daemon/_metadata.py b/modules/daemon/_metadata.py
new file mode 100644 (file)
index 0000000..6eb3569
--- /dev/null
@@ -0,0 +1,152 @@
+# -*- coding: utf-8 -*-
+
+# daemon/_metadata.py
+# Part of ‘python-daemon’, an implementation of PEP 3143.
+#
+# Copyright © 2008–2016 Ben Finney <ben+python@benfinney.id.au>
+#
+# This is free software: you may copy, modify, and/or distribute this work
+# under the terms of the Apache License, version 2.0 as published by the
+# Apache Software Foundation.
+# No warranty expressed or implied. See the file ‘LICENSE.ASF-2’ for details.
+
+""" Package metadata for the ‘python-daemon’ distribution. """
+
+from __future__ import (absolute_import, unicode_literals)
+
+import json
+import re
+import collections
+import datetime
+
+import pkg_resources
+
+\f
+distribution_name = "python-daemon"
+version_info_filename = "version_info.json"
+
+def get_distribution_version_info(filename=version_info_filename):
+    """ Get the version info from the installed distribution.
+
+        :param filename: Base filename of the version info resource.
+        :return: The version info as a mapping of fields. If the
+            distribution is not available, the mapping is empty.
+
+        The version info is stored as a metadata file in the
+        distribution.
+
+        """
+    version_info = {
+            'release_date': "UNKNOWN",
+            'version': "UNKNOWN",
+            'maintainer': "UNKNOWN",
+            }
+
+    try:
+        distribution = pkg_resources.get_distribution(distribution_name)
+    except pkg_resources.DistributionNotFound:
+        distribution = None
+
+    if distribution is not None:
+        if distribution.has_metadata(version_info_filename):
+            content = distribution.get_metadata(version_info_filename)
+            version_info = json.loads(content)
+
+    return version_info
+
+version_info = get_distribution_version_info()
+
+version_installed = version_info['version']
+
+\f
+rfc822_person_regex = re.compile(
+        "^(?P<name>[^<]+) <(?P<email>[^>]+)>$")
+
+ParsedPerson = collections.namedtuple('ParsedPerson', ['name', 'email'])
+
+def parse_person_field(value):
+    """ Parse a person field into name and email address.
+
+        :param value: The text value specifying a person.
+        :return: A 2-tuple (name, email) for the person's details.
+
+        If the `value` does not match a standard person with email
+        address, the `email` item is ``None``.
+
+        """
+    result = (None, None)
+
+    match = rfc822_person_regex.match(value)
+    if len(value):
+        if match is not None:
+            result = ParsedPerson(
+                    name=match.group('name'),
+                    email=match.group('email'))
+        else:
+            result = ParsedPerson(name=value, email=None)
+
+    return result    
+
+author_name = "Ben Finney"
+author_email = "ben+python@benfinney.id.au"
+author = "{name} <{email}>".format(name=author_name, email=author_email)
+
+\f
+class YearRange:
+    """ A range of years spanning a period. """
+
+    def __init__(self, begin, end=None):
+        self.begin = begin
+        self.end = end
+
+    def __unicode__(self):
+        text = "{range.begin:04d}".format(range=self)
+        if self.end is not None:
+            if self.end > self.begin:
+                text = "{range.begin:04d}–{range.end:04d}".format(range=self)
+        return text
+
+    __str__ = __unicode__
+
+
+def make_year_range(begin_year, end_date=None):
+    """ Construct the year range given a start and possible end date.
+
+        :param begin_date: The beginning year (text) for the range.
+        :param end_date: The end date (text, ISO-8601 format) for the
+            range, or a non-date token string.
+        :return: The range of years as a `YearRange` instance.
+
+        If the `end_date` is not a valid ISO-8601 date string, the
+        range has ``None`` for the end year.
+
+        """
+    begin_year = int(begin_year)
+
+    try:
+        end_date = datetime.datetime.strptime(end_date, "%Y-%m-%d")
+    except (TypeError, ValueError):
+        # Specified end_date value is not a valid date.
+        end_year = None
+    else:
+        end_year = end_date.year
+
+    year_range = YearRange(begin=begin_year, end=end_year)
+
+    return year_range
+
+copyright_year_begin = "2001"
+build_date = version_info['release_date']
+copyright_year_range = make_year_range(copyright_year_begin, build_date)
+
+copyright = "Copyright © {year_range} {author} and others".format(
+        year_range=copyright_year_range, author=author)
+license = "Apache-2"
+url = "https://alioth.debian.org/projects/python-daemon/"
+
+\f
+# Local variables:
+# coding: utf-8
+# mode: python
+# End:
+# vim: fileencoding=utf-8 filetype=python :
diff --git a/modules/daemon/daemon.py b/modules/daemon/daemon.py
new file mode 100644 (file)
index 0000000..5c0f392
--- /dev/null
@@ -0,0 +1,970 @@
+# -*- coding: utf-8 -*-
+
+# daemon/daemon.py
+# Part of ‘python-daemon’, an implementation of PEP 3143.
+#
+# Copyright © 2008–2016 Ben Finney <ben+python@benfinney.id.au>
+# 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 Apache License, version 2.0 as published by the
+# Apache Software Foundation.
+# No warranty expressed or implied. See the file ‘LICENSE.ASF-2’ for details.
+
+""" Daemon process behaviour.
+    """
+
+from __future__ import (absolute_import, unicode_literals)
+
+import os
+import sys
+import pwd
+import resource
+import errno
+import signal
+import socket
+import atexit
+try:
+    # Python 2 has both ‘str’ (bytes) and ‘unicode’ (text).
+    basestring = basestring
+    unicode = unicode
+except NameError:
+    # Python 3 names the Unicode data type ‘str’.
+    basestring = str
+    unicode = str
+
+\f
+class DaemonError(Exception):
+    """ Base exception class for errors from this module. """
+
+    def __init__(self, *args, **kwargs):
+        self._chain_from_context()
+
+        super(DaemonError, self).__init__(*args, **kwargs)
+
+    def _chain_from_context(self):
+        _chain_exception_from_existing_exception_context(self, as_cause=True)
+
+
+class DaemonOSEnvironmentError(DaemonError, OSError):
+    """ Exception raised when daemon OS environment setup receives error. """
+
+
+class DaemonProcessDetachError(DaemonError, OSError):
+    """ Exception raised when process detach fails. """
+
+\f
+class DaemonContext:
+    """ 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.
+
+            A daemon should not rely on the parent process's umask value,
+            which is beyond its control and may prevent creating a file with
+            the required access mode. So when the daemon context opens, the
+            umask is set to an explicit known value.
+
+            If the conventional value of 0 is too open, consider setting a
+            value such as 0o022, 0o027, 0o077, or another specific value.
+            Otherwise, ensure the daemon creates every file with an
+            explicit access mode for the purpose.
+
+        `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.
+
+        `initgroups`
+            :Default: ``False``
+
+            If true, set the daemon process's supplementary groups as
+            determined by the specified `uid`.
+
+            This will require that the current process UID has
+            permission to change the process's owning GID.
+
+        `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 mimimum of 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`.
+
+        """
+
+    __metaclass__ = type
+
+    def __init__(
+            self,
+            chroot_directory=None,
+            working_directory="/",
+            umask=0,
+            uid=None,
+            gid=None,
+            initgroups=False,
+            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
+        self.initgroups = initgroups
+
+        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 owner (UID and GID) to the `uid` and `gid`
+              attribute values.
+
+              If the `initgroups` attribute is true, also set the process's
+              supplementary groups to all the user's groups (i.e. those
+              groups whose membership includes the username corresponding
+              to `uid`).
+
+            * 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, self.initgroups)
+
+        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,
+            # <URL:http://docs.python.org/library/stdtypes.html#typecontextmanager>.
+            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.
+
+            :param signal_number: The OS signal number received.
+            :param stack_frame: The frame object at the point the
+                signal was received.
+            :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}".format(
+                    signal_number=signal_number))
+        raise exception
+
+    def _get_exclude_file_descriptors(self):
+        """ Get the set of file descriptors to exclude closing.
+
+            :return: A set containing the file descriptors for the
+                files to be preserved.
+
+            The file descriptors to be preserved are those from the
+            items in `files_preserve`, and also each of `stdin`,
+            `stdout`, and `stderr`. For each item:
+
+            * If the item is ``None``, it is omitted from the return
+              set.
+
+            * If the item's ``fileno()`` method returns a value, that
+              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
+            file_descriptor = _get_file_descriptor(item)
+            if file_descriptor is not None:
+                exclude_descriptors.add(file_descriptor)
+            else:
+                exclude_descriptors.add(item)
+
+        return exclude_descriptors
+
+    def _make_signal_handler(self, target):
+        """ Make the signal handler for a specified target object.
+
+            :param target: A specification of the target for the
+                handler; see below.
+            :return: The value for use by `signal.signal()`.
+
+            If `target` is ``None``, return ``signal.SIG_IGN``. If `target`
+            is a text string, return the attribute of this instance named
+            by that string. Otherwise, return `target` itself.
+
+            """
+        if target is None:
+            result = signal.SIG_IGN
+        elif isinstance(target, basestring):
+            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.
+
+            :return: The constructed signal map for this instance.
+
+            Construct 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 _get_file_descriptor(obj):
+    """ Get the file descriptor, if the object has one.
+
+        :param obj: The object expected to be a file-like object.
+        :return: The file descriptor iff the file supports it; otherwise
+            ``None``.
+
+        The object may be a non-file object. It may also be a
+        file-like object with no support for a file descriptor. In
+        either case, return ``None``.
+
+        """
+    file_descriptor = None
+    if hasattr(obj, 'fileno'):
+        try:
+            file_descriptor = obj.fileno()
+        except ValueError:
+            # The item doesn't support a file descriptor.
+            pass
+
+    return file_descriptor
+
+\f
+def change_working_directory(directory):
+    """ Change the working directory of this process.
+
+        :param directory: The target directory path.
+        :return: ``None``.
+
+        """
+    try:
+        os.chdir(directory)
+    except Exception as exc:
+        error = DaemonOSEnvironmentError(
+                "Unable to change working directory ({exc})".format(exc=exc))
+        raise error
+
+
+def change_root_directory(directory):
+    """ Change the root directory of this process.
+
+        :param directory: The target directory path.
+        :return: ``None``.
+
+        Set 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 ({exc})".format(exc=exc))
+        raise error
+
+
+def change_file_creation_mask(mask):
+    """ Change the file creation mask for this process.
+
+        :param mask: The numeric file creation mask to set.
+        :return: ``None``.
+
+        """
+    try:
+        os.umask(mask)
+    except Exception as exc:
+        error = DaemonOSEnvironmentError(
+                "Unable to change file creation mask ({exc})".format(exc=exc))
+        raise error
+
+
+def get_username_for_uid(uid):
+    """ Get the username for the specified UID. """
+    passwd_entry = pwd.getpwuid(uid)
+    username = passwd_entry.pw_name
+
+    return username
+
+
+def change_process_owner(uid, gid, initgroups=False):
+    """ Change the owning UID, GID, and groups of this process.
+
+        :param uid: The target UID for the daemon process.
+        :param gid: The target GID for the daemon process.
+        :param initgroups: If true, initialise the supplementary
+            groups of the process.
+        :return: ``None``.
+
+        Sets the owning GID and UID of the process (in that order, to
+        avoid permission errors) to the specified `gid` and `uid`
+        values.
+
+        If `initgroups` is true, the supplementary groups of the
+        process are also initialised, with those corresponding to the
+        username for the target UID.
+
+        All these operations require appropriate OS privileges. If
+        permission is denied, a ``DaemonOSEnvironmentError`` is
+        raised.
+
+        """
+    try:
+        username = get_username_for_uid(uid)
+    except KeyError:
+        # We don't have a username to pass to ‘os.initgroups’.
+        initgroups = False
+
+    try:
+        if initgroups:
+            os.initgroups(username, gid)
+        else:
+            os.setgid(gid)
+        os.setuid(uid)
+    except Exception as exc:
+        error = DaemonOSEnvironmentError(
+                "Unable to change process owner ({exc})".format(exc=exc))
+        raise error
+
+\f
+def prevent_core_dump():
+    """ Prevent this process from generating a core dump.
+
+        :return: ``None``.
+
+        Set the soft and hard limits for core dump size to zero. On Unix,
+        this entirely prevents the process from creating core dump.
+
+        """
+    core_resource = resource.RLIMIT_CORE
+
+    try:
+        # Ensure the resource limit exists on this platform, by requesting
+        # its current value.
+        core_limit_prev = resource.getrlimit(core_resource)
+    except ValueError as exc:
+        error = DaemonOSEnvironmentError(
+                "System does not support RLIMIT_CORE resource limit"
+                " ({exc})".format(exc=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)
+
+\f
+def detach_process_context():
+    """ Detach the process context from parent and session.
+
+        :return: ``None``.
+
+        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.
+
+            :param error_message: Message for the exception in case of a
+                detach failure.
+            :return: ``None``.
+            :raise DaemonProcessDetachError: If the fork fails.
+
+            """
+        try:
+            pid = os.fork()
+            if pid > 0:
+                os._exit(0)
+        except OSError as exc:
+            error = DaemonProcessDetachError(
+                    "{message}: [{exc.errno:d}] {exc.strerror}".format(
+                        message=error_message, exc=exc))
+            raise error
+
+    fork_then_exit_parent(error_message="Failed first fork")
+    os.setsid()
+    fork_then_exit_parent(error_message="Failed second fork")
+
+\f
+def is_process_started_by_init():
+    """ Determine whether the current process is started by `init`.
+
+        :return: ``True`` iff the parent process is `init`; otherwise
+            ``False``.
+
+        The `init` process is the one with process ID of 1.
+
+        """
+    result = False
+
+    init_pid = 1
+    if os.getppid() == init_pid:
+        result = True
+
+    return result
+
+
+def is_socket(fd):
+    """ Determine whether the file descriptor is a socket.
+
+        :param fd: The file descriptor to interrogate.
+        :return: ``True`` iff the file descriptor is a socket; otherwise
+            ``False``.
+
+        Query the socket type of `fd`. If there is no error, the file is a
+        socket.
+
+        """
+    result = False
+
+    file_socket = socket.fromfd(fd, socket.AF_INET, socket.SOCK_RAW)
+
+    try:
+        socket_type = 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 whether the current process is started by the superserver.
+
+        :return: ``True`` if this process was started by the internet
+            superserver; otherwise ``False``.
+
+        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 the process context is required.
+
+        :return: ``True`` iff the process is already detached; otherwise
+            ``False``.
+
+        The process environment is interrogated for the following:
+
+        * Process was started by `init`; or
+
+        * Process was started by `inetd`.
+
+        If any of the above are true, the process is deemed to be already
+        detached.
+
+        """
+    result = True
+    if is_process_started_by_init() or is_process_started_by_superserver():
+        result = False
+
+    return result
+
+\f
+def close_file_descriptor_if_open(fd):
+    """ Close a file descriptor if already open.
+
+        :param fd: The file descriptor to close.
+        :return: ``None``.
+
+        Close the file descriptor `fd`, suppressing an error in the
+        case the file was not open.
+
+        """
+    try:
+        os.close(fd)
+    except EnvironmentError as exc:
+        if exc.errno == errno.EBADF:
+            # File descriptor was not open.
+            pass
+        else:
+            error = DaemonOSEnvironmentError(
+                    "Failed to close file descriptor {fd:d} ({exc})".format(
+                        fd=fd, exc=exc))
+            raise error
+
+
+MAXFD = 2048
+
+def get_maximum_file_descriptors():
+    """ Get the maximum number of open file descriptors for this process.
+
+        :return: The number (integer) to use as the maximum number of open
+            files for this process.
+
+        The maximum is 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=set()):
+    """ Close all open file descriptors.
+
+        :param exclude: Collection of file descriptors to skip when closing
+            files.
+        :return: ``None``.
+
+        Closes every file descriptor (if open) of this process. If
+        specified, `exclude` is a set of file descriptors to *not*
+        close.
+
+        """
+    maxfd = get_maximum_file_descriptors()
+    for fd in reversed(range(maxfd)):
+        if fd not in exclude:
+            close_file_descriptor_if_open(fd)
+
+\f
+def redirect_stream(system_stream, target_stream):
+    """ Redirect a system stream to a specified file.
+
+        :param standard_stream: A file object representing a standard I/O
+            stream.
+        :param target_stream: The target file object for the redirected
+            stream, or ``None`` to specify the null device.
+        :return: ``None``.
+
+        `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())
+
+\f
+def make_default_signal_map():
+    """ Make the default signal map for this system.
+
+        :return: A mapping from signal number to handler object.
+
+        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.
+
+        :param signal_handler_map: A map from signal number to handler
+            object.
+        :return: ``None``.
+
+        See the `signal` module for details on signal numbers and signal
+        handlers.
+
+        """
+    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.
+
+        :param func: A callable function expecting no arguments.
+        :return: ``None``.
+
+        The function `func` is registered for a call with no arguments
+        at program exit.
+
+        """
+    atexit.register(func)
+
+\f
+def _chain_exception_from_existing_exception_context(exc, as_cause=False):
+    """ Decorate the specified exception with the existing exception context.
+
+        :param exc: The exception instance to decorate.
+        :param as_cause: If true, the existing context is declared to be
+            the cause of the exception.
+        :return: ``None``.
+
+        :PEP:`344` describes syntax and attributes (`__traceback__`,
+        `__context__`, `__cause__`) for use in exception chaining.
+
+        Python 2 does not have that syntax, so this function decorates
+        the exception with values from the current exception context.
+
+        """
+    (existing_exc_type, existing_exc, existing_traceback) = sys.exc_info()
+    if as_cause:
+        exc.__cause__ = existing_exc
+    else:
+        exc.__context__ = existing_exc
+    exc.__traceback__ = existing_traceback
+
+\f
+# Local variables:
+# coding: utf-8
+# mode: python
+# End:
+# vim: fileencoding=utf-8 filetype=python :
diff --git a/modules/daemon/pidfile.py b/modules/daemon/pidfile.py
new file mode 100644 (file)
index 0000000..f7fd279
--- /dev/null
@@ -0,0 +1,67 @@
+# -*- coding: utf-8 -*-
+
+# daemon/pidfile.py
+# Part of ‘python-daemon’, an implementation of PEP 3143.
+#
+# Copyright © 2008–2016 Ben Finney <ben+python@benfinney.id.au>
+#
+# This is free software: you may copy, modify, and/or distribute this work
+# under the terms of the Apache License, version 2.0 as published by the
+# Apache Software Foundation.
+# No warranty expressed or implied. See the file ‘LICENSE.ASF-2’ for details.
+
+""" Lockfile behaviour implemented via Unix PID files.
+    """
+
+from __future__ import (absolute_import, unicode_literals)
+
+from lockfile.pidlockfile import PIDLockFile
+
+\f
+class TimeoutPIDLockFile(PIDLockFile, object):
+    """ Lockfile with default timeout, implemented as a Unix PID file.
+
+        This uses the ``PIDLockFile`` implementation, with the
+        following changes:
+
+        * The `acquire_timeout` parameter to the initialiser will be
+          used as the default `timeout` parameter for the `acquire`
+          method.
+
+        """
+
+    def __init__(self, path, acquire_timeout=None, *args, **kwargs):
+        """ Set up the parameters of a TimeoutPIDLockFile.
+
+            :param path: Filesystem path to the PID file.
+            :param acquire_timeout: Value to use by default for the
+                `acquire` call.
+            :return: ``None``.
+
+            """
+        self.acquire_timeout = acquire_timeout
+        super(TimeoutPIDLockFile, self).__init__(path, *args, **kwargs)
+
+    def acquire(self, timeout=None, *args, **kwargs):
+        """ Acquire the lock.
+
+            :param timeout: Specifies the timeout; see below for valid
+                values.
+            :return: ``None``.
+
+            The `timeout` defaults to the value set during
+            initialisation with the `acquire_timeout` parameter. It is
+            passed to `PIDLockFile.acquire`; see that method for
+            details.
+
+            """
+        if timeout is None:
+            timeout = self.acquire_timeout
+        super(TimeoutPIDLockFile, self).acquire(timeout, *args, **kwargs)
+
+\f
+# Local variables:
+# coding: utf-8
+# mode: python
+# End:
+# vim: fileencoding=utf-8 filetype=python :
diff --git a/modules/daemon/runner.py b/modules/daemon/runner.py
new file mode 100644 (file)
index 0000000..f49909a
--- /dev/null
@@ -0,0 +1,324 @@
+# -*- coding: utf-8 -*-
+
+# daemon/runner.py
+# Part of ‘python-daemon’, an implementation of PEP 3143.
+#
+# Copyright © 2009–2016 Ben Finney <ben+python@benfinney.id.au>
+# Copyright © 2007–2008 Robert Niederreiter, Jens Klein
+# 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 Apache License, version 2.0 as published by the
+# Apache Software Foundation.
+# No warranty expressed or implied. See the file ‘LICENSE.ASF-2’ for details.
+
+""" Daemon runner library.
+    """
+
+from __future__ import (absolute_import, unicode_literals)
+
+import sys
+import os
+import signal
+import errno
+try:
+    # Python 3 standard library.
+    ProcessLookupError
+except NameError:
+    # No such class in Python 2.
+    ProcessLookupError = NotImplemented
+
+import lockfile
+
+from . import pidfile
+from .daemon import (basestring, unicode)
+from .daemon import DaemonContext
+from .daemon import _chain_exception_from_existing_exception_context
+
+\f
+class DaemonRunnerError(Exception):
+    """ Abstract base class for errors from DaemonRunner. """
+
+    def __init__(self, *args, **kwargs):
+        self._chain_from_context()
+
+        super(DaemonRunnerError, self).__init__(*args, **kwargs)
+
+    def _chain_from_context(self):
+        _chain_exception_from_existing_exception_context(self, as_cause=True)
+
+
+class DaemonRunnerInvalidActionError(DaemonRunnerError, ValueError):
+    """ Raised when specified action for DaemonRunner is invalid. """
+
+    def _chain_from_context(self):
+        # This exception is normally not caused by another.
+        _chain_exception_from_existing_exception_context(self, as_cause=False)
+
+
+class DaemonRunnerStartFailureError(DaemonRunnerError, RuntimeError):
+    """ Raised when failure starting DaemonRunner. """
+
+
+class DaemonRunnerStopFailureError(DaemonRunnerError, RuntimeError):
+    """ Raised when failure stopping DaemonRunner. """
+
+\f
+class DaemonRunner:
+    """ Controller for a callable running in a separate background process.
+
+        The first command-line argument is the action to take:
+
+        * 'start': Become a daemon and call `app.run()`.
+        * 'stop': Exit the daemon process specified in the PID file.
+        * 'restart': Stop, then start.
+
+        """
+
+    __metaclass__ = type
+
+    start_message = "started with pid {pid:d}"
+
+    def __init__(self, app):
+        """ Set up the parameters of a new runner.
+
+            :param app: The application instance; see below.
+            :return: ``None``.
+
+            The `app` argument must have the following attributes:
+
+            * `stdin_path`, `stdout_path`, `stderr_path`: Filesystem paths
+              to open and replace the existing `sys.stdin`, `sys.stdout`,
+              `sys.stderr`.
+
+            * `pidfile_path`: Absolute filesystem path to a file that will
+              be used as the PID file for the daemon. If ``None``, no PID
+              file will be used.
+
+            * `pidfile_timeout`: Used as the default acquisition timeout
+              value supplied to the runner's PID lock file.
+
+            * `run`: Callable that will be invoked when the daemon is
+              started.
+
+            """
+        self.parse_args()
+        self.app = app
+        self.daemon_context = DaemonContext()
+        self.daemon_context.stdin = open(app.stdin_path, 'rt')
+        self.daemon_context.stdout = open(app.stdout_path, 'w+t')
+        self.daemon_context.stderr = open(
+                app.stderr_path, 'w+t', buffering=0)
+
+        self.pidfile = None
+        if app.pidfile_path is not None:
+            self.pidfile = make_pidlockfile(
+                    app.pidfile_path, app.pidfile_timeout)
+        self.daemon_context.pidfile = self.pidfile
+
+    def _usage_exit(self, argv):
+        """ Emit a usage message, then exit.
+
+            :param argv: The command-line arguments used to invoke the
+                program, as a sequence of strings.
+            :return: ``None``.
+
+            """
+        progname = os.path.basename(argv[0])
+        usage_exit_code = 2
+        action_usage = "|".join(self.action_funcs.keys())
+        message = "usage: {progname} {usage}".format(
+                progname=progname, usage=action_usage)
+        emit_message(message)
+        sys.exit(usage_exit_code)
+
+    def parse_args(self, argv=None):
+        """ Parse command-line arguments.
+
+            :param argv: The command-line arguments used to invoke the
+                program, as a sequence of strings.
+
+            :return: ``None``.
+
+            The parser expects the first argument as the program name, the
+            second argument as the action to perform.
+
+            If the parser fails to parse the arguments, emit a usage
+            message and exit the program.
+
+            """
+        if argv is None:
+            argv = sys.argv
+
+        min_args = 2
+        if len(argv) < min_args:
+            self._usage_exit(argv)
+
+        self.action = unicode(argv[1])
+        if self.action not in self.action_funcs:
+            self._usage_exit(argv)
+
+    def _start(self):
+        """ Open the daemon context and run the application.
+
+            :return: ``None``.
+            :raises DaemonRunnerStartFailureError: If the PID file cannot
+                be locked by this process.
+
+            """
+        if is_pidfile_stale(self.pidfile):
+            self.pidfile.break_lock()
+
+        try:
+            self.daemon_context.open()
+        except lockfile.AlreadyLocked:
+            error = DaemonRunnerStartFailureError(
+                    "PID file {pidfile.path!r} already locked".format(
+                        pidfile=self.pidfile))
+            raise error
+
+        pid = os.getpid()
+        message = self.start_message.format(pid=pid)
+        emit_message(message)
+
+        self.app.run()
+
+    def _terminate_daemon_process(self):
+        """ Terminate the daemon process specified in the current PID file.
+
+            :return: ``None``.
+            :raises DaemonRunnerStopFailureError: If terminating the daemon
+                fails with an OS error.
+
+            """
+        pid = self.pidfile.read_pid()
+        try:
+            os.kill(pid, signal.SIGTERM)
+        except OSError as exc:
+            error = DaemonRunnerStopFailureError(
+                    "Failed to terminate {pid:d}: {exc}".format(
+                        pid=pid, exc=exc))
+            raise error
+
+    def _stop(self):
+        """ Exit the daemon process specified in the current PID file.
+
+            :return: ``None``.
+            :raises DaemonRunnerStopFailureError: If the PID file is not
+                already locked.
+
+            """
+        if not self.pidfile.is_locked():
+            error = DaemonRunnerStopFailureError(
+                    "PID file {pidfile.path!r} not locked".format(
+                        pidfile=self.pidfile))
+            raise error
+
+        if is_pidfile_stale(self.pidfile):
+            self.pidfile.break_lock()
+        else:
+            self._terminate_daemon_process()
+
+    def _restart(self):
+        """ Stop, then start.
+            """
+        self._stop()
+        self._start()
+
+    action_funcs = {
+            'start': _start,
+            'stop': _stop,
+            'restart': _restart,
+            }
+
+    def _get_action_func(self):
+        """ Get the function for the specified action.
+
+            :return: The function object corresponding to the specified
+                action.
+            :raises DaemonRunnerInvalidActionError: if the action is
+               unknown.
+
+            The action is specified by the `action` attribute, which is set
+            during `parse_args`.
+
+            """
+        try:
+            func = self.action_funcs[self.action]
+        except KeyError:
+            error = DaemonRunnerInvalidActionError(
+                    "Unknown action: {action!r}".format(
+                        action=self.action))
+            raise error
+        return func
+
+    def do_action(self):
+        """ Perform the requested action.
+
+            :return: ``None``.
+
+            The action is specified by the `action` attribute, which is set
+            during `parse_args`.
+
+            """
+        func = self._get_action_func()
+        func(self)
+
+
+def emit_message(message, stream=None):
+    """ Emit a message to the specified stream (default `sys.stderr`). """
+    if stream is None:
+        stream = sys.stderr
+    stream.write("{message}\n".format(message=message))
+    stream.flush()
+
+
+def make_pidlockfile(path, acquire_timeout):
+    """ Make a PIDLockFile instance with the given filesystem path. """
+    if not isinstance(path, basestring):
+        error = ValueError("Not a filesystem path: {path!r}".format(
+                path=path))
+        raise error
+    if not os.path.isabs(path):
+        error = ValueError("Not an absolute path: {path!r}".format(
+                path=path))
+        raise error
+    lockfile = pidfile.TimeoutPIDLockFile(path, acquire_timeout)
+
+    return lockfile
+
+
+def is_pidfile_stale(pidfile):
+    """ Determine whether a PID file is stale.
+
+        :return: ``True`` iff the PID file is stale; otherwise ``False``.
+
+        The PID file is “stale” if its contents are valid but do not
+        match the PID of a currently-running process.
+
+        """
+    result = False
+
+    pidfile_pid = pidfile.read_pid()
+    if pidfile_pid is not None:
+        try:
+            os.kill(pidfile_pid, signal.SIG_DFL)
+        except ProcessLookupError:
+            # The specified PID does not exist.
+            result = True
+        except OSError as exc:
+            if exc.errno == errno.ESRCH:
+                # Under Python 2, process lookup error is an OSError.
+                # The specified PID does not exist.
+                result = True
+
+    return result
+
+\f
+# Local variables:
+# coding: utf-8
+# mode: python
+# End:
+# vim: fileencoding=utf-8 filetype=python :
diff --git a/modules/lockfile/__init__.py b/modules/lockfile/__init__.py
new file mode 100644 (file)
index 0000000..a6f44a5
--- /dev/null
@@ -0,0 +1,347 @@
+# -*- coding: utf-8 -*-
+
+"""
+lockfile.py - Platform-independent advisory file locks.
+
+Requires Python 2.5 unless you apply 2.4.diff
+Locking is done on a per-thread basis instead of a per-process basis.
+
+Usage:
+
+>>> lock = LockFile('somefile')
+>>> try:
+...     lock.acquire()
+... except AlreadyLocked:
+...     print 'somefile', 'is locked already.'
+... except LockFailed:
+...     print 'somefile', 'can\\'t be locked.'
+... else:
+...     print 'got lock'
+got lock
+>>> print lock.is_locked()
+True
+>>> lock.release()
+
+>>> lock = LockFile('somefile')
+>>> print lock.is_locked()
+False
+>>> with lock:
+...    print lock.is_locked()
+True
+>>> print lock.is_locked()
+False
+
+>>> lock = LockFile('somefile')
+>>> # It is okay to lock twice from the same thread...
+>>> with lock:
+...     lock.acquire()
+...
+>>> # Though no counter is kept, so you can't unlock multiple times...
+>>> print lock.is_locked()
+False
+
+Exceptions:
+
+    Error - base class for other exceptions
+        LockError - base class for all locking exceptions
+            AlreadyLocked - Another thread or process already holds the lock
+            LockFailed - Lock failed for some other reason
+        UnlockError - base class for all unlocking exceptions
+            AlreadyUnlocked - File was not locked.
+            NotMyLock - File was locked but not by the current thread/process
+"""
+
+from __future__ import absolute_import
+
+import functools
+import os
+import socket
+import threading
+import warnings
+
+# Work with PEP8 and non-PEP8 versions of threading module.
+if not hasattr(threading, "current_thread"):
+    threading.current_thread = threading.currentThread
+if not hasattr(threading.Thread, "get_name"):
+    threading.Thread.get_name = threading.Thread.getName
+
+__all__ = ['Error', 'LockError', 'LockTimeout', 'AlreadyLocked',
+           'LockFailed', 'UnlockError', 'NotLocked', 'NotMyLock',
+           'LinkFileLock', 'MkdirFileLock', 'SQLiteFileLock',
+           'LockBase', 'locked']
+
+
+class Error(Exception):
+    """
+    Base class for other exceptions.
+
+    >>> try:
+    ...   raise Error
+    ... except Exception:
+    ...   pass
+    """
+    pass
+
+
+class LockError(Error):
+    """
+    Base class for error arising from attempts to acquire the lock.
+
+    >>> try:
+    ...   raise LockError
+    ... except Error:
+    ...   pass
+    """
+    pass
+
+
+class LockTimeout(LockError):
+    """Raised when lock creation fails within a user-defined period of time.
+
+    >>> try:
+    ...   raise LockTimeout
+    ... except LockError:
+    ...   pass
+    """
+    pass
+
+
+class AlreadyLocked(LockError):
+    """Some other thread/process is locking the file.
+
+    >>> try:
+    ...   raise AlreadyLocked
+    ... except LockError:
+    ...   pass
+    """
+    pass
+
+
+class LockFailed(LockError):
+    """Lock file creation failed for some other reason.
+
+    >>> try:
+    ...   raise LockFailed
+    ... except LockError:
+    ...   pass
+    """
+    pass
+
+
+class UnlockError(Error):
+    """
+    Base class for errors arising from attempts to release the lock.
+
+    >>> try:
+    ...   raise UnlockError
+    ... except Error:
+    ...   pass
+    """
+    pass
+
+
+class NotLocked(UnlockError):
+    """Raised when an attempt is made to unlock an unlocked file.
+
+    >>> try:
+    ...   raise NotLocked
+    ... except UnlockError:
+    ...   pass
+    """
+    pass
+
+
+class NotMyLock(UnlockError):
+    """Raised when an attempt is made to unlock a file someone else locked.
+
+    >>> try:
+    ...   raise NotMyLock
+    ... except UnlockError:
+    ...   pass
+    """
+    pass
+
+
+class _SharedBase(object):
+    def __init__(self, path):
+        self.path = path
+
+    def acquire(self, timeout=None):
+        """
+        Acquire the lock.
+
+        * If timeout is omitted (or None), wait forever trying to lock the
+          file.
+
+        * If timeout > 0, try to acquire the lock for that many seconds.  If
+          the lock period expires and the file is still locked, raise
+          LockTimeout.
+
+        * If timeout <= 0, raise AlreadyLocked immediately if the file is
+          already locked.
+        """
+        raise NotImplemented("implement in subclass")
+
+    def release(self):
+        """
+        Release the lock.
+
+        If the file is not locked, raise NotLocked.
+        """
+        raise NotImplemented("implement in subclass")
+
+    def __enter__(self):
+        """
+        Context manager support.
+        """
+        self.acquire()
+        return self
+
+    def __exit__(self, *_exc):
+        """
+        Context manager support.
+        """
+        self.release()
+
+    def __repr__(self):
+        return "<%s: %r>" % (self.__class__.__name__, self.path)
+
+
+class LockBase(_SharedBase):
+    """Base class for platform-specific lock classes."""
+    def __init__(self, path, threaded=True, timeout=None):
+        """
+        >>> lock = LockBase('somefile')
+        >>> lock = LockBase('somefile', threaded=False)
+        """
+        super(LockBase, self).__init__(path)
+        self.lock_file = os.path.abspath(path) + ".lock"
+        self.hostname = socket.gethostname()
+        self.pid = os.getpid()
+        if threaded:
+            t = threading.current_thread()
+            # Thread objects in Python 2.4 and earlier do not have ident
+            # attrs.  Worm around that.
+            ident = getattr(t, "ident", hash(t))
+            self.tname = "-%x" % (ident & 0xffffffff)
+        else:
+            self.tname = ""
+        dirname = os.path.dirname(self.lock_file)
+
+        # unique name is mostly about the current process, but must
+        # also contain the path -- otherwise, two adjacent locked
+        # files conflict (one file gets locked, creating lock-file and
+        # unique file, the other one gets locked, creating lock-file
+        # and overwriting the already existing lock-file, then one
+        # gets unlocked, deleting both lock-file and unique file,
+        # finally the last lock errors out upon releasing.
+        self.unique_name = os.path.join(dirname,
+                                        "%s%s.%s%s" % (self.hostname,
+                                                       self.tname,
+                                                       self.pid,
+                                                       hash(self.path)))
+        self.timeout = timeout
+
+    def is_locked(self):
+        """
+        Tell whether or not the file is locked.
+        """
+        raise NotImplemented("implement in subclass")
+
+    def i_am_locking(self):
+        """
+        Return True if this object is locking the file.
+        """
+        raise NotImplemented("implement in subclass")
+
+    def break_lock(self):
+        """
+        Remove a lock.  Useful if a locking thread failed to unlock.
+        """
+        raise NotImplemented("implement in subclass")
+
+    def __repr__(self):
+        return "<%s: %r -- %r>" % (self.__class__.__name__, self.unique_name,
+                                   self.path)
+
+
+def _fl_helper(cls, mod, *args, **kwds):
+    warnings.warn("Import from %s module instead of lockfile package" % mod,
+                  DeprecationWarning, stacklevel=2)
+    # This is a bit funky, but it's only for awhile.  The way the unit tests
+    # are constructed this function winds up as an unbound method, so it
+    # actually takes three args, not two.  We want to toss out self.
+    if not isinstance(args[0], str):
+        # We are testing, avoid the first arg
+        args = args[1:]
+    if len(args) == 1 and not kwds:
+        kwds["threaded"] = True
+    return cls(*args, **kwds)
+
+
+def LinkFileLock(*args, **kwds):
+    """Factory function provided for backwards compatibility.
+
+    Do not use in new code.  Instead, import LinkLockFile from the
+    lockfile.linklockfile module.
+    """
+    from . import linklockfile
+    return _fl_helper(linklockfile.LinkLockFile, "lockfile.linklockfile",
+                      *args, **kwds)
+
+
+def MkdirFileLock(*args, **kwds):
+    """Factory function provided for backwards compatibility.
+
+    Do not use in new code.  Instead, import MkdirLockFile from the
+    lockfile.mkdirlockfile module.
+    """
+    from . import mkdirlockfile
+    return _fl_helper(mkdirlockfile.MkdirLockFile, "lockfile.mkdirlockfile",
+                      *args, **kwds)
+
+
+def SQLiteFileLock(*args, **kwds):
+    """Factory function provided for backwards compatibility.
+
+    Do not use in new code.  Instead, import SQLiteLockFile from the
+    lockfile.mkdirlockfile module.
+    """
+    from . import sqlitelockfile
+    return _fl_helper(sqlitelockfile.SQLiteLockFile, "lockfile.sqlitelockfile",
+                      *args, **kwds)
+
+
+def locked(path, timeout=None):
+    """Decorator which enables locks for decorated function.
+
+    Arguments:
+     - path: path for lockfile.
+     - timeout (optional): Timeout for acquiring lock.
+
+     Usage:
+         @locked('/var/run/myname', timeout=0)
+         def myname(...):
+             ...
+    """
+    def decor(func):
+        @functools.wraps(func)
+        def wrapper(*args, **kwargs):
+            lock = FileLock(path, timeout=timeout)
+            lock.acquire()
+            try:
+                return func(*args, **kwargs)
+            finally:
+                lock.release()
+        return wrapper
+    return decor
+
+
+if hasattr(os, "link"):
+    from . import linklockfile as _llf
+    LockFile = _llf.LinkLockFile
+else:
+    from . import mkdirlockfile as _mlf
+    LockFile = _mlf.MkdirLockFile
+
+FileLock = LockFile
diff --git a/modules/lockfile/__pycache__/__init__.cpython-34.pyc b/modules/lockfile/__pycache__/__init__.cpython-34.pyc
new file mode 100644 (file)
index 0000000..71d7247
Binary files /dev/null and b/modules/lockfile/__pycache__/__init__.cpython-34.pyc differ
diff --git a/modules/lockfile/__pycache__/linklockfile.cpython-34.pyc b/modules/lockfile/__pycache__/linklockfile.cpython-34.pyc
new file mode 100644 (file)
index 0000000..9081b55
Binary files /dev/null and b/modules/lockfile/__pycache__/linklockfile.cpython-34.pyc differ
diff --git a/modules/lockfile/__pycache__/pidlockfile.cpython-34.pyc b/modules/lockfile/__pycache__/pidlockfile.cpython-34.pyc
new file mode 100644 (file)
index 0000000..b06896c
Binary files /dev/null and b/modules/lockfile/__pycache__/pidlockfile.cpython-34.pyc differ
diff --git a/modules/lockfile/linklockfile.py b/modules/lockfile/linklockfile.py
new file mode 100644 (file)
index 0000000..2ca9be0
--- /dev/null
@@ -0,0 +1,73 @@
+from __future__ import absolute_import
+
+import time
+import os
+
+from . import (LockBase, LockFailed, NotLocked, NotMyLock, LockTimeout,
+               AlreadyLocked)
+
+
+class LinkLockFile(LockBase):
+    """Lock access to a file using atomic property of link(2).
+
+    >>> lock = LinkLockFile('somefile')
+    >>> lock = LinkLockFile('somefile', threaded=False)
+    """
+
+    def acquire(self, timeout=None):
+        try:
+            open(self.unique_name, "wb").close()
+        except IOError:
+            raise LockFailed("failed to create %s" % self.unique_name)
+
+        timeout = timeout if timeout is not None else self.timeout
+        end_time = time.time()
+        if timeout is not None and timeout > 0:
+            end_time += timeout
+
+        while True:
+            # Try and create a hard link to it.
+            try:
+                os.link(self.unique_name, self.lock_file)
+            except OSError:
+                # Link creation failed.  Maybe we've double-locked?
+                nlinks = os.stat(self.unique_name).st_nlink
+                if nlinks == 2:
+                    # The original link plus the one I created == 2.  We're
+                    # good to go.
+                    return
+                else:
+                    # Otherwise the lock creation failed.
+                    if timeout is not None and time.time() > end_time:
+                        os.unlink(self.unique_name)
+                        if timeout > 0:
+                            raise LockTimeout("Timeout waiting to acquire"
+                                              " lock for %s" %
+                                              self.path)
+                        else:
+                            raise AlreadyLocked("%s is already locked" %
+                                                self.path)
+                    time.sleep(timeout is not None and timeout / 10 or 0.1)
+            else:
+                # Link creation succeeded.  We're good to go.
+                return
+
+    def release(self):
+        if not self.is_locked():
+            raise NotLocked("%s is not locked" % self.path)
+        elif not os.path.exists(self.unique_name):
+            raise NotMyLock("%s is locked, but not by me" % self.path)
+        os.unlink(self.unique_name)
+        os.unlink(self.lock_file)
+
+    def is_locked(self):
+        return os.path.exists(self.lock_file)
+
+    def i_am_locking(self):
+        return (self.is_locked() and
+                os.path.exists(self.unique_name) and
+                os.stat(self.unique_name).st_nlink == 2)
+
+    def break_lock(self):
+        if os.path.exists(self.lock_file):
+            os.unlink(self.lock_file)
diff --git a/modules/lockfile/mkdirlockfile.py b/modules/lockfile/mkdirlockfile.py
new file mode 100644 (file)
index 0000000..05a8c96
--- /dev/null
@@ -0,0 +1,84 @@
+from __future__ import absolute_import, division
+
+import time
+import os
+import sys
+import errno
+
+from . import (LockBase, LockFailed, NotLocked, NotMyLock, LockTimeout,
+               AlreadyLocked)
+
+
+class MkdirLockFile(LockBase):
+    """Lock file by creating a directory."""
+    def __init__(self, path, threaded=True, timeout=None):
+        """
+        >>> lock = MkdirLockFile('somefile')
+        >>> lock = MkdirLockFile('somefile', threaded=False)
+        """
+        LockBase.__init__(self, path, threaded, timeout)
+        # Lock file itself is a directory.  Place the unique file name into
+        # it.
+        self.unique_name = os.path.join(self.lock_file,
+                                        "%s.%s%s" % (self.hostname,
+                                                     self.tname,
+                                                     self.pid))
+
+    def acquire(self, timeout=None):
+        timeout = timeout if timeout is not None else self.timeout
+        end_time = time.time()
+        if timeout is not None and timeout > 0:
+            end_time += timeout
+
+        if timeout is None:
+            wait = 0.1
+        else:
+            wait = max(0, timeout / 10)
+
+        while True:
+            try:
+                os.mkdir(self.lock_file)
+            except OSError:
+                err = sys.exc_info()[1]
+                if err.errno == errno.EEXIST:
+                    # Already locked.
+                    if os.path.exists(self.unique_name):
+                        # Already locked by me.
+                        return
+                    if timeout is not None and time.time() > end_time:
+                        if timeout > 0:
+                            raise LockTimeout("Timeout waiting to acquire"
+                                              " lock for %s" %
+                                              self.path)
+                        else:
+                            # Someone else has the lock.
+                            raise AlreadyLocked("%s is already locked" %
+                                                self.path)
+                    time.sleep(wait)
+                else:
+                    # Couldn't create the lock for some other reason
+                    raise LockFailed("failed to create %s" % self.lock_file)
+            else:
+                open(self.unique_name, "wb").close()
+                return
+
+    def release(self):
+        if not self.is_locked():
+            raise NotLocked("%s is not locked" % self.path)
+        elif not os.path.exists(self.unique_name):
+            raise NotMyLock("%s is locked, but not by me" % self.path)
+        os.unlink(self.unique_name)
+        os.rmdir(self.lock_file)
+
+    def is_locked(self):
+        return os.path.exists(self.lock_file)
+
+    def i_am_locking(self):
+        return (self.is_locked() and
+                os.path.exists(self.unique_name))
+
+    def break_lock(self):
+        if os.path.exists(self.lock_file):
+            for name in os.listdir(self.lock_file):
+                os.unlink(os.path.join(self.lock_file, name))
+            os.rmdir(self.lock_file)
diff --git a/modules/lockfile/pidlockfile.py b/modules/lockfile/pidlockfile.py
new file mode 100644 (file)
index 0000000..069e85b
--- /dev/null
@@ -0,0 +1,190 @@
+# -*- coding: utf-8 -*-
+
+# pidlockfile.py
+#
+# Copyright © 2008–2009 Ben Finney <ben+python@benfinney.id.au>
+#
+# 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.
+
+""" Lockfile behaviour implemented via Unix PID files.
+    """
+
+from __future__ import absolute_import
+
+import errno
+import os
+import time
+
+from . import (LockBase, AlreadyLocked, LockFailed, NotLocked, NotMyLock,
+               LockTimeout)
+
+\f
+class PIDLockFile(LockBase):
+    """ Lockfile implemented as a Unix PID file.
+
+    The lock file is a normal file named by the attribute `path`.
+    A lock's PID file contains a single line of text, containing
+    the process ID (PID) of the process that acquired the lock.
+
+    >>> lock = PIDLockFile('somefile')
+    >>> lock = PIDLockFile('somefile')
+    """
+
+    def __init__(self, path, threaded=False, timeout=None):
+        # pid lockfiles don't support threaded operation, so always force
+        # False as the threaded arg.
+        LockBase.__init__(self, path, False, timeout)
+        self.unique_name = self.path
+
+    def read_pid(self):
+        """ Get the PID from the lock file.
+            """
+        return read_pid_from_pidfile(self.path)
+
+    def is_locked(self):
+        """ Test if the lock is currently held.
+
+            The lock is held if the PID file for this lock exists.
+
+            """
+        return os.path.exists(self.path)
+
+    def i_am_locking(self):
+        """ Test if the lock is held by the current process.
+
+        Returns ``True`` if the current process ID matches the
+        number stored in the PID file.
+        """
+        return self.is_locked() and os.getpid() == self.read_pid()
+
+    def acquire(self, timeout=None):
+        """ Acquire the lock.
+
+        Creates the PID file for this lock, or raises an error if
+        the lock could not be acquired.
+        """
+
+        timeout = timeout if timeout is not None else self.timeout
+        end_time = time.time()
+        if timeout is not None and timeout > 0:
+            end_time += timeout
+
+        while True:
+            try:
+                write_pid_to_pidfile(self.path)
+            except OSError as exc:
+                if exc.errno == errno.EEXIST:
+                    # The lock creation failed.  Maybe sleep a bit.
+                    if time.time() > end_time:
+                        if timeout is not None and timeout > 0:
+                            raise LockTimeout("Timeout waiting to acquire"
+                                              " lock for %s" %
+                                              self.path)
+                        else:
+                            raise AlreadyLocked("%s is already locked" %
+                                                self.path)
+                    time.sleep(timeout is not None and timeout / 10 or 0.1)
+                else:
+                    raise LockFailed("failed to create %s" % self.path)
+            else:
+                return
+
+    def release(self):
+        """ Release the lock.
+
+            Removes the PID file to release the lock, or raises an
+            error if the current process does not hold the lock.
+
+            """
+        if not self.is_locked():
+            raise NotLocked("%s is not locked" % self.path)
+        if not self.i_am_locking():
+            raise NotMyLock("%s is locked, but not by me" % self.path)
+        remove_existing_pidfile(self.path)
+
+    def break_lock(self):
+        """ Break an existing lock.
+
+            Removes the PID file if it already exists, otherwise does
+            nothing.
+
+            """
+        remove_existing_pidfile(self.path)
+
+
+def read_pid_from_pidfile(pidfile_path):
+    """ Read the PID recorded in the named PID file.
+
+        Read and return the numeric PID recorded as text in the named
+        PID file. If the PID file cannot be read, or if the content is
+        not a valid PID, return ``None``.
+
+        """
+    pid = None
+    try:
+        pidfile = open(pidfile_path, 'r')
+    except IOError:
+        pass
+    else:
+        # According to the FHS 2.3 section on PID files in /var/run:
+        #
+        #   The file must consist of the process identifier in
+        #   ASCII-encoded decimal, followed by a newline character.
+        #
+        #   Programs that read PID files should be somewhat flexible
+        #   in what they accept; i.e., they should ignore extra
+        #   whitespace, leading zeroes, absence of the trailing
+        #   newline, or additional lines in the PID file.
+
+        line = pidfile.readline().strip()
+        try:
+            pid = int(line)
+        except ValueError:
+            pass
+        pidfile.close()
+
+    return pid
+
+
+def write_pid_to_pidfile(pidfile_path):
+    """ Write the PID in the named PID file.
+
+        Get the numeric process ID (“PID”) of the current process
+        and write it to the named file as a line of text.
+
+        """
+    open_flags = (os.O_CREAT | os.O_EXCL | os.O_WRONLY)
+    open_mode = 0o644
+    pidfile_fd = os.open(pidfile_path, open_flags, open_mode)
+    pidfile = os.fdopen(pidfile_fd, 'w')
+
+    # According to the FHS 2.3 section on PID files in /var/run:
+    #
+    #   The file must consist of the process identifier in
+    #   ASCII-encoded decimal, followed by a newline character. For
+    #   example, if crond was process number 25, /var/run/crond.pid
+    #   would contain three characters: two, five, and newline.
+
+    pid = os.getpid()
+    pidfile.write("%s\n" % pid)
+    pidfile.close()
+
+
+def remove_existing_pidfile(pidfile_path):
+    """ Remove the named PID file if it exists.
+
+        Removing a PID file that doesn't already exist puts us in the
+        desired state, so we ignore the condition if the file does not
+        exist.
+
+        """
+    try:
+        os.remove(pidfile_path)
+    except OSError as exc:
+        if exc.errno == errno.ENOENT:
+            pass
+        else:
+            raise
diff --git a/modules/lockfile/sqlitelockfile.py b/modules/lockfile/sqlitelockfile.py
new file mode 100644 (file)
index 0000000..f997e24
--- /dev/null
@@ -0,0 +1,156 @@
+from __future__ import absolute_import, division
+
+import time
+import os
+
+try:
+    unicode
+except NameError:
+    unicode = str
+
+from . import LockBase, NotLocked, NotMyLock, LockTimeout, AlreadyLocked
+
+
+class SQLiteLockFile(LockBase):
+    "Demonstrate SQL-based locking."
+
+    testdb = None
+
+    def __init__(self, path, threaded=True, timeout=None):
+        """
+        >>> lock = SQLiteLockFile('somefile')
+        >>> lock = SQLiteLockFile('somefile', threaded=False)
+        """
+        LockBase.__init__(self, path, threaded, timeout)
+        self.lock_file = unicode(self.lock_file)
+        self.unique_name = unicode(self.unique_name)
+
+        if SQLiteLockFile.testdb is None:
+            import tempfile
+            _fd, testdb = tempfile.mkstemp()
+            os.close(_fd)
+            os.unlink(testdb)
+            del _fd, tempfile
+            SQLiteLockFile.testdb = testdb
+
+        import sqlite3
+        self.connection = sqlite3.connect(SQLiteLockFile.testdb)
+
+        c = self.connection.cursor()
+        try:
+            c.execute("create table locks"
+                      "("
+                      "   lock_file varchar(32),"
+                      "   unique_name varchar(32)"
+                      ")")
+        except sqlite3.OperationalError:
+            pass
+        else:
+            self.connection.commit()
+            import atexit
+            atexit.register(os.unlink, SQLiteLockFile.testdb)
+
+    def acquire(self, timeout=None):
+        timeout = timeout if timeout is not None else self.timeout
+        end_time = time.time()
+        if timeout is not None and timeout > 0:
+            end_time += timeout
+
+        if timeout is None:
+            wait = 0.1
+        elif timeout <= 0:
+            wait = 0
+        else:
+            wait = timeout / 10
+
+        cursor = self.connection.cursor()
+
+        while True:
+            if not self.is_locked():
+                # Not locked.  Try to lock it.
+                cursor.execute("insert into locks"
+                               "  (lock_file, unique_name)"
+                               "  values"
+                               "  (?, ?)",
+                               (self.lock_file, self.unique_name))
+                self.connection.commit()
+
+                # Check to see if we are the only lock holder.
+                cursor.execute("select * from locks"
+                               "  where unique_name = ?",
+                               (self.unique_name,))
+                rows = cursor.fetchall()
+                if len(rows) > 1:
+                    # Nope.  Someone else got there.  Remove our lock.
+                    cursor.execute("delete from locks"
+                                   "  where unique_name = ?",
+                                   (self.unique_name,))
+                    self.connection.commit()
+                else:
+                    # Yup.  We're done, so go home.
+                    return
+            else:
+                # Check to see if we are the only lock holder.
+                cursor.execute("select * from locks"
+                               "  where unique_name = ?",
+                               (self.unique_name,))
+                rows = cursor.fetchall()
+                if len(rows) == 1:
+                    # We're the locker, so go home.
+                    return
+
+            # Maybe we should wait a bit longer.
+            if timeout is not None and time.time() > end_time:
+                if timeout > 0:
+                    # No more waiting.
+                    raise LockTimeout("Timeout waiting to acquire"
+                                      " lock for %s" %
+                                      self.path)
+                else:
+                    # Someone else has the lock and we are impatient..
+                    raise AlreadyLocked("%s is already locked" % self.path)
+
+            # Well, okay.  We'll give it a bit longer.
+            time.sleep(wait)
+
+    def release(self):
+        if not self.is_locked():
+            raise NotLocked("%s is not locked" % self.path)
+        if not self.i_am_locking():
+            raise NotMyLock("%s is locked, but not by me (by %s)" %
+                            (self.unique_name, self._who_is_locking()))
+        cursor = self.connection.cursor()
+        cursor.execute("delete from locks"
+                       "  where unique_name = ?",
+                       (self.unique_name,))
+        self.connection.commit()
+
+    def _who_is_locking(self):
+        cursor = self.connection.cursor()
+        cursor.execute("select unique_name from locks"
+                       "  where lock_file = ?",
+                       (self.lock_file,))
+        return cursor.fetchone()[0]
+
+    def is_locked(self):
+        cursor = self.connection.cursor()
+        cursor.execute("select * from locks"
+                       "  where lock_file = ?",
+                       (self.lock_file,))
+        rows = cursor.fetchall()
+        return not not rows
+
+    def i_am_locking(self):
+        cursor = self.connection.cursor()
+        cursor.execute("select * from locks"
+                       "  where lock_file = ?"
+                       "    and unique_name = ?",
+                       (self.lock_file, self.unique_name))
+        return not not cursor.fetchall()
+
+    def break_lock(self):
+        cursor = self.connection.cursor()
+        cursor.execute("delete from locks"
+                       "  where lock_file = ?",
+                       (self.lock_file,))
+        self.connection.commit()
diff --git a/modules/lockfile/symlinklockfile.py b/modules/lockfile/symlinklockfile.py
new file mode 100644 (file)
index 0000000..23b41f5
--- /dev/null
@@ -0,0 +1,70 @@
+from __future__ import absolute_import
+
+import os
+import time
+
+from . import (LockBase, NotLocked, NotMyLock, LockTimeout,
+               AlreadyLocked)
+
+
+class SymlinkLockFile(LockBase):
+    """Lock access to a file using symlink(2)."""
+
+    def __init__(self, path, threaded=True, timeout=None):
+        # super(SymlinkLockFile).__init(...)
+        LockBase.__init__(self, path, threaded, timeout)
+        # split it back!
+        self.unique_name = os.path.split(self.unique_name)[1]
+
+    def acquire(self, timeout=None):
+        # Hopefully unnecessary for symlink.
+        # try:
+        #     open(self.unique_name, "wb").close()
+        # except IOError:
+        #     raise LockFailed("failed to create %s" % self.unique_name)
+        timeout = timeout if timeout is not None else self.timeout
+        end_time = time.time()
+        if timeout is not None and timeout > 0:
+            end_time += timeout
+
+        while True:
+            # Try and create a symbolic link to it.
+            try:
+                os.symlink(self.unique_name, self.lock_file)
+            except OSError:
+                # Link creation failed.  Maybe we've double-locked?
+                if self.i_am_locking():
+                    # Linked to out unique name. Proceed.
+                    return
+                else:
+                    # Otherwise the lock creation failed.
+                    if timeout is not None and time.time() > end_time:
+                        if timeout > 0:
+                            raise LockTimeout("Timeout waiting to acquire"
+                                              " lock for %s" %
+                                              self.path)
+                        else:
+                            raise AlreadyLocked("%s is already locked" %
+                                                self.path)
+                    time.sleep(timeout / 10 if timeout is not None else 0.1)
+            else:
+                # Link creation succeeded.  We're good to go.
+                return
+
+    def release(self):
+        if not self.is_locked():
+            raise NotLocked("%s is not locked" % self.path)
+        elif not self.i_am_locking():
+            raise NotMyLock("%s is locked, but not by me" % self.path)
+        os.unlink(self.lock_file)
+
+    def is_locked(self):
+        return os.path.islink(self.lock_file)
+
+    def i_am_locking(self):
+        return (os.path.islink(self.lock_file)
+                and os.readlink(self.lock_file) == self.unique_name)
+
+    def break_lock(self):
+        if os.path.islink(self.lock_file):  # exists && link
+            os.unlink(self.lock_file)
diff --git a/test.py b/test.py
old mode 100644 (file)
new mode 100755 (executable)
index 5f9c5f8..fa0441b
--- a/test.py
+++ b/test.py
@@ -4,9 +4,14 @@
 import logging                  # Logs
 import time                     # sleep
 import string                   # split strings
-import os,select
 
-from daemon import runner      # daemonization
+import os,sys,inspect           # for the following
+cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],"modules")))
+if cmd_subfolder not in sys.path:
+  sys.path.insert(0, cmd_subfolder)
+
+import lockfile                # daemonization
+from daemon import runner     # daemonization
 
 from multiprocessing import Process
 #import subprocess
diff --git a/tmp/lockfile-0.12.2.tar.gz b/tmp/lockfile-0.12.2.tar.gz
new file mode 100644 (file)
index 0000000..8965ed4
Binary files /dev/null and b/tmp/lockfile-0.12.2.tar.gz differ
diff --git a/tmp/lockfile-0.12.2/ACKS b/tmp/lockfile-0.12.2/ACKS
new file mode 100644 (file)
index 0000000..44519d1
--- /dev/null
@@ -0,0 +1,6 @@
+Thanks to the following people for help with lockfile.
+
+  Scott Dial
+  Ben Finney
+  Frank Niessink
+  Konstantin Veretennicov
diff --git a/tmp/lockfile-0.12.2/AUTHORS b/tmp/lockfile-0.12.2/AUTHORS
new file mode 100644 (file)
index 0000000..690325f
--- /dev/null
@@ -0,0 +1,12 @@
+(no author) <(no author)>
+ChangBo Guo(gcb) <eric.guo@easystack.cn>
+Davanum Srinivas <davanum@gmail.com>
+Doug Hellmann <doug@doughellmann.com>
+Elmo Todurov <elmo.todurov@skype.net>
+Joshua Harlow <harlowja@yahoo-inc.com>
+Julien Danjou <julien@danjou.info>
+Skip Montanaro <skip@pobox.com>
+Thomas Grainger <tagrain@gmail.com>
+Thomas Grainger <tom@yplanapp.com>
+Victor Stinner <vstinner@redhat.com>
+skip.montanaro <skip.montanaro@gmail.com>
diff --git a/tmp/lockfile-0.12.2/ChangeLog b/tmp/lockfile-0.12.2/ChangeLog
new file mode 100644 (file)
index 0000000..2d4aeb9
--- /dev/null
@@ -0,0 +1,193 @@
+CHANGES
+=======
+
+0.12.2
+------
+
+* Add warning to README.rst
+
+0.12.1
+------
+
+* Remove Python 2.6 classifier
+* Remove python 2.6
+* Remove two unused variables: fix flake8 F841 warn
+* Fix flake8 warnings
+* PBR setup requirement only
+* Support universal wheels
+
+0.11.0
+------
+
+* Add deprecated warnings to index.rst
+* Fix PIDLockFile.acquire() may loop indefinitely
+* Fix failure - from lockfile import *
+* lockfile.acquire doesn't accept a timeout of 0
+* Update README format for our release script
+* Begin moving some of the common code to a shared base
+* Add pbr to dependency list
+* The version of sphinx being brought in is broken
+* Fix Git URLs
+
+0.10.2
+------
+
+* Fix package name
+
+0.10.1
+------
+
+* Add missing cover env in tox
+
+0.10.0
+------
+
+* Fix documentation bug report address
+* Add py34 in tox
+* Remove old diff file
+* Add .gitreview, tox targets and use pbr
+* fix for timeout=0
+* remove 2.5, 3.1 and 3.4 from the list for the time being - may get added back later
+* Bugfix: locking two different files in the same directory caused an error during unlocking the last unlocking
+* typo
+
+0.9.1
+-----
+
+* ignore dist dir
+* update to python 3 imports
+* python 3 tweaks
+* python 3 tweaks
+* ignore Emacs backups
+* note nose as a dependency
+* remove this test file - way incompatible with current code
+* stuff to ignore
+* Add py33, py34, delete py24, py25
+* Update source location
+* merge delete
+* merge delete
+* more merge stuff
+* this didn't come across with svn merge
+* all screwed up now
+* merge
+* merge
+* Make it clear that the path and threaded attributes to SymlinkLockFile and MkdirLockFile have the same constraints as for LinkLockFile. In particular, the directory which will contain path must be writable
+* add pidlockfile test stuff from Ben Finney - still a few problems - maybe I can get him to solve them :-)
+* ignore Sphinx build directory
+* Catch up on a little documentation
+* adapt decorator patch from issue 5
+* Allow timeout in constructor - resolves issue 3
+* add info to raise statements - from issue 6, yyurevich@jellycrystal.com
+* add useful repr() - from issue 6, yyurevich@jellycrystal.com
+* add symlinklockfile module
+* + py24
+* good for the branch? must be good for the trunk
+* add tox stuff, ignore dist dir
+* new version, move to Google Code
+* 
+* 
+* * Thread support is currently broken. This is more likely because of problems in this module, but suppress those tests for now just the same
+* By the nature of what it's trying to do PIDLockFile doesn't support threaded operation
+* defer creating testdb until we've instantiated a SQLiteLockFile instance
+* tweak unique_name slightly
+* Specify mode in octal
+* update to match pidlockfile change
+* missing import
+* I think I finally have this correct
+* patch pidlockfile module too
+* use abs import here as well
+* *argh*
+* Update to elide new import syntax
+* * Move future import for division where it's used. * Use __absolute_import__ to spell relative imports
+* Some PIDLockFile tests are failing. Check in anyway so others can consider the problems
+* Account for fact that Thread objects in Python 2.4 and earlier do not have an ident attribute
+* Make this a daemon thread so if things go awry the test run won't hang
+* * Add pidlockfile (not quite working properly) * Rearrange MANIFEST.in slightly to include test directory
+* Split those test methods which try both threaded and non-threaded naming schemes. More to do. Obviously you need to have test cases when using the non-threaded naming scheme from multiple threads
+* acknowledge Ben and Frank, alphabetize list
+* I don't think these are needed any longer - they came back during the hg->svn conversion
+* grand renaming: "filelock" -> "lockfile" & "FileLock" -> "LockFile"
+* Update for packages
+* Avoid using the backwards compatibility functions for FileLock. That object is not deprecated
+* how does the test dir keep sneaking into MANIFEST? also, include 2.4.diff in dist
+* update for new structure, use of ident attr
+* adjust build setup
+* move test helpers into test dir
+* first cut at packagized lockfile
+* Protect some more complex locking stuff so if they fail we don't deadlock
+* merge r75 from head
+* * One implementation of tname, not two - make it an instance attribute as a result
+* beginnings of a packagized lockfile
+* get the structure right
+* start over with the branches..
+* hmmm
+* hmmm
+* get us back to lockfile 0.8
+* r72 from hg
+* r70 from hg
+* r69 from hg
+* r68 from hg
+* r67 from hg
+* r66 from hg
+* r65 from hg
+* r64 from hg
+* r64 from hg
+* r63 from hg
+* r62 from hg
+* r61 from hg
+* r60 from hg
+* r59 from hg
+* r58 from hg
+* r57 from hg
+* r56 from hg
+* r55 from hg
+* r54 from hg
+* r53 from hg
+* r52 from hg
+* r51 from hg
+* r50 from hg
+* r49 from hg
+* r47 from hg
+* r46 from hg
+* r45 from hg
+* r44 from hg
+* r43 from hg
+* r42 from hg
+* r41 from hg
+* r38 from hg
+* r37 from hg
+* r36 from hg
+* r35 from hg
+* r34 from hg
+* r33 from hg
+* r32 from hg
+* r31 from hg
+* r29 from hg
+* r28 from hg
+* r27 from hg
+* r26 from hg
+* r25 from hg
+* r24 from hg
+* r23 from hg
+* r22 from hg
+* r21 from hg
+* r20 from hg
+* r19 from hg
+* r18 from hg
+* r16 from hg
+* r14 from hg
+* r13 from hg
+* r12 from hg
+* r11 from hg
+* r10 from hg
+* r9 from hg
+* r8 from hg
+* r7 from hg
+* r6 from hg
+* r5 from hg
+* r4 from hg
+* r3 from hg
+* r2 from hg
+* r1 from hg
+* r0 from hg
+* Initial directory structure
diff --git a/tmp/lockfile-0.12.2/LICENSE b/tmp/lockfile-0.12.2/LICENSE
new file mode 100644 (file)
index 0000000..610c079
--- /dev/null
@@ -0,0 +1,21 @@
+This is the MIT license: http://www.opensource.org/licenses/mit-license.php
+
+Copyright (c) 2007 Skip Montanaro.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to
+deal in the Software without restriction, including without limitation the
+rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+sell copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+IN THE SOFTWARE.
diff --git a/tmp/lockfile-0.12.2/PKG-INFO b/tmp/lockfile-0.12.2/PKG-INFO
new file mode 100644 (file)
index 0000000..d348d5d
--- /dev/null
@@ -0,0 +1,56 @@
+Metadata-Version: 1.1
+Name: lockfile
+Version: 0.12.2
+Summary: Platform-independent file locking module
+Home-page: http://launchpad.net/pylockfile
+Author: OpenStack
+Author-email: openstack-dev@lists.openstack.org
+License: UNKNOWN
+Description: Note: This package is **deprecated**. It is highly preferred that instead of
+        using this code base that instead `fasteners`_ or `oslo.concurrency`_ is
+        used instead. For any questions or comments or further help needed
+        please email `openstack-dev`_ and prefix your email subject
+        with ``[oslo][pylockfile]`` (for a faster response).
+        
+        The lockfile package exports a LockFile class which provides a simple API for
+        locking files.  Unlike the Windows msvcrt.locking function, the fcntl.lockf
+        and flock functions, and the deprecated posixfile module, the API is
+        identical across both Unix (including Linux and Mac) and Windows platforms.
+        The lock mechanism relies on the atomic nature of the link (on Unix) and
+        mkdir (on Windows) system calls.  An implementation based on SQLite is also
+        provided, more as a demonstration of the possibilities it provides than as
+        production-quality code.
+        
+        Note: In version 0.9 the API changed in two significant ways:
+        
+         * It changed from a module defining several classes to a package containing
+           several modules, each defining a single class.
+        
+         * Where classes had been named SomethingFileLock before the last two words
+           have been reversed, so that class is now SomethingLockFile.
+        
+        The previous module-level definitions of LinkFileLock, MkdirFileLock and
+        SQLiteFileLock will be retained until the 1.0 release.
+        
+        To install:
+        
+            python setup.py install
+        
+        * Documentation: http://docs.openstack.org/developer/pylockfile
+        * Source: http://git.openstack.org/cgit/openstack/pylockfile
+        * Bugs: http://bugs.launchpad.net/pylockfile
+        
+        
+Platform: UNKNOWN
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: MIT License
+Classifier: Operating System :: POSIX :: Linux
+Classifier: Operating System :: MacOS
+Classifier: Operating System :: Microsoft :: Windows :: Windows NT/2000
+Classifier: Operating System :: POSIX
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.3
+Classifier: Topic :: Software Development :: Libraries :: Python Modules
diff --git a/tmp/lockfile-0.12.2/README.rst b/tmp/lockfile-0.12.2/README.rst
new file mode 100644 (file)
index 0000000..a439f10
--- /dev/null
@@ -0,0 +1,33 @@
+Note: This package is **deprecated**. It is highly preferred that instead of
+using this code base that instead `fasteners`_ or `oslo.concurrency`_ is
+used instead. For any questions or comments or further help needed
+please email `openstack-dev`_ and prefix your email subject
+with ``[oslo][pylockfile]`` (for a faster response).
+
+The lockfile package exports a LockFile class which provides a simple API for
+locking files.  Unlike the Windows msvcrt.locking function, the fcntl.lockf
+and flock functions, and the deprecated posixfile module, the API is
+identical across both Unix (including Linux and Mac) and Windows platforms.
+The lock mechanism relies on the atomic nature of the link (on Unix) and
+mkdir (on Windows) system calls.  An implementation based on SQLite is also
+provided, more as a demonstration of the possibilities it provides than as
+production-quality code.
+
+Note: In version 0.9 the API changed in two significant ways:
+
+ * It changed from a module defining several classes to a package containing
+   several modules, each defining a single class.
+
+ * Where classes had been named SomethingFileLock before the last two words
+   have been reversed, so that class is now SomethingLockFile.
+
+The previous module-level definitions of LinkFileLock, MkdirFileLock and
+SQLiteFileLock will be retained until the 1.0 release.
+
+To install:
+
+    python setup.py install
+
+* Documentation: http://docs.openstack.org/developer/pylockfile
+* Source: http://git.openstack.org/cgit/openstack/pylockfile
+* Bugs: http://bugs.launchpad.net/pylockfile
diff --git a/tmp/lockfile-0.12.2/RELEASE-NOTES b/tmp/lockfile-0.12.2/RELEASE-NOTES
new file mode 100644 (file)
index 0000000..8b452ed
--- /dev/null
@@ -0,0 +1,50 @@
+Version 0.9.1
+=============
+
+* This release moves the source location to Google Code.
+
+* Threaded support is currently broken.  (It might not actually be broken.
+  It might just be the tests which are broken.)
+
+Version 0.9
+===========
+
+* The lockfile module was reorganized into a package.
+
+* The names of the three main classes have changed as follows:
+
+    LinkFileLock -> LinkLockFile
+    MkdirFileLock -> MkdirLockFile
+    SQLiteFileLock -> SQLiteLockFile
+
+* A PIDLockFile class was added.
+
+Version 0.3
+===========
+
+* Fix 2.4.diff file error.
+
+* More documentation updates.
+
+Version 0.2
+===========
+
+* Added 2.4.diff file to patch lockfile to work with Python 2.4 (removes use
+  of with statement).
+
+* Renamed _FileLock base class to LockBase to expose it (and its docstrings)
+  to pydoc.
+
+* Got rid of time.sleep() calls in tests (thanks to Konstantin
+  Veretennicov).
+
+* Use thread.get_ident() as the thread discriminator.
+
+* Updated documentation a bit.
+
+* Added RELEASE-NOTES.
+
+Version 0.1
+===========
+
+* First release - All basic functionality there.
diff --git a/tmp/lockfile-0.12.2/doc/source/Makefile b/tmp/lockfile-0.12.2/doc/source/Makefile
new file mode 100644 (file)
index 0000000..1b1e8d2
--- /dev/null
@@ -0,0 +1,73 @@
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS    =
+SPHINXBUILD   = sphinx-build
+PAPER         =
+
+# Internal variables.
+PAPEROPT_a4     = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS   = -d .build/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+
+.PHONY: help clean html web pickle htmlhelp latex changes linkcheck
+
+help:
+       @echo "Please use \`make <target>' where <target> is one of"
+       @echo "  html      to make standalone HTML files"
+       @echo "  pickle    to make pickle files (usable by e.g. sphinx-web)"
+       @echo "  htmlhelp  to make HTML files and a HTML help project"
+       @echo "  latex     to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+       @echo "  changes   to make an overview over all changed/added/deprecated items"
+       @echo "  linkcheck to check all external links for integrity"
+
+clean:
+       -rm -rf .build/*
+
+html:
+       mkdir -p .build/html .build/doctrees
+       $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) .build/html
+       @echo
+       @echo "Build finished. The HTML pages are in .build/html."
+
+pickle:
+       mkdir -p .build/pickle .build/doctrees
+       $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) .build/pickle
+       @echo
+       @echo "Build finished; now you can process the pickle files or run"
+       @echo "  sphinx-web .build/pickle"
+       @echo "to start the sphinx-web server."
+
+web: pickle
+
+htmlhelp:
+       mkdir -p .build/htmlhelp .build/doctrees
+       $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) .build/htmlhelp
+       @echo
+       @echo "Build finished; now you can run HTML Help Workshop with the" \
+             ".hhp project file in .build/htmlhelp."
+
+latex:
+       mkdir -p .build/latex .build/doctrees
+       $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) .build/latex
+       @echo
+       @echo "Build finished; the LaTeX files are in .build/latex."
+       @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \
+             "run these through (pdf)latex."
+
+changes:
+       mkdir -p .build/changes .build/doctrees
+       $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) .build/changes
+       @echo
+       @echo "The overview file is in .build/changes."
+
+linkcheck:
+       mkdir -p .build/linkcheck .build/doctrees
+       $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) .build/linkcheck
+       @echo
+       @echo "Link check complete; look for any errors in the above output " \
+             "or in .build/linkcheck/output.txt."
+
+html.zip: html
+       (cd .build/html ; zip -r ../../$@ *)
diff --git a/tmp/lockfile-0.12.2/doc/source/conf.py b/tmp/lockfile-0.12.2/doc/source/conf.py
new file mode 100644 (file)
index 0000000..623edcb
--- /dev/null
@@ -0,0 +1,179 @@
+# -*- coding: utf-8 -*-
+#
+# lockfile documentation build configuration file, created by
+# sphinx-quickstart on Sat Sep 13 17:54:17 2008.
+#
+# This file is execfile()d with the current directory set to its containing dir.
+#
+# The contents of this file are pickled, so don't put values in the namespace
+# that aren't pickleable (module imports are okay, they're removed automatically).
+#
+# All configuration values have a default value; values that are commented out
+# serve to show the default value.
+
+import sys, os
+
+# If your extensions are in another directory, add it here. If the directory
+# is relative to the documentation root, use os.path.abspath to make it
+# absolute, like shown here.
+#sys.path.append(os.path.abspath('some/directory'))
+
+# General configuration
+# ---------------------
+
+# Add any Sphinx extension module names here, as strings. They can be extensions
+# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
+extensions = []
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['.templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General substitutions.
+project = 'lockfile'
+copyright = '2008, Skip Montanaro'
+
+# The default replacements for |version| and |release|, also used in various
+# other places throughout the built documents.
+#
+# The short X.Y version.
+version = '0.3'
+# The full version, including alpha/beta/rc tags.
+release = '0.3'
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+#today = ''
+# Else, today_fmt is used as the format for a strftime call.
+today_fmt = '%B %d, %Y'
+
+# List of documents that shouldn't be included in the build.
+#unused_docs = []
+
+# List of directories, relative to source directories, that shouldn't be searched
+# for source files.
+#exclude_dirs = []
+
+# The reST default role (used for this markup: `text`) to use for all documents.
+#default_role = None
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+#add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+#add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+#show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+
+# Options for HTML output
+# -----------------------
+
+# The style sheet to use for HTML and HTML Help pages. A file of that name
+# must exist either in Sphinx' static/ path, or in one of the custom paths
+# given in html_static_path.
+html_style = 'default.css'
+
+# The name for this set of Sphinx documents.  If None, it defaults to
+# "<project> v<release> documentation".
+#html_title = None
+
+# A shorter title for the navigation bar.  Default is the same as html_title.
+#html_short_title = None
+
+# The name of an image file (within the static path) to place at the top of
+# the sidebar.
+#html_logo = None
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+#html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+#html_static_path = ['.static']
+
+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+# using the given strftime format.
+html_last_updated_fmt = '%b %d, %Y'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+#html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+#html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+#html_additional_pages = {}
+
+# If false, no module index is generated.
+#html_use_modindex = True
+
+# If false, no index is generated.
+#html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+#html_split_index = False
+
+# If true, the reST sources are included in the HTML build as _sources/<name>.
+#html_copy_source = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a <link> tag referring to it.  The value of this option must be the
+# base URL from which the finished HTML is served.
+#html_use_opensearch = ''
+
+# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
+#html_file_suffix = ''
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'lockfiledoc'
+
+
+# Options for LaTeX output
+# ------------------------
+
+# The paper size ('letter' or 'a4').
+#latex_paper_size = 'letter'
+
+# The font size ('10pt', '11pt' or '12pt').
+#latex_font_size = '10pt'
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title, author, document class [howto/manual]).
+latex_documents = [
+  ('lockfile', 'lockfile.tex', 'lockfile Documentation',
+   'Skip Montanaro', 'manual'),
+]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+#latex_logo = None
+
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+#latex_use_parts = False
+
+# Additional stuff for the LaTeX preamble.
+#latex_preamble = ''
+
+# Documents to append as an appendix to all manuals.
+#latex_appendices = []
+
+# If false, no module index is generated.
+#latex_use_modindex = True
diff --git a/tmp/lockfile-0.12.2/doc/source/index.rst b/tmp/lockfile-0.12.2/doc/source/index.rst
new file mode 100644 (file)
index 0000000..5f1f2dd
--- /dev/null
@@ -0,0 +1,287 @@
+
+:mod:`lockfile` --- Platform-independent file locking
+=====================================================
+
+.. module:: lockfile
+   :synopsis: Platform-independent file locking
+.. moduleauthor:: Skip Montanaro <skip@pobox.com>
+.. sectionauthor:: Skip Montanaro <skip@pobox.com>
+
+.. warning::
+
+   This package is **deprecated**. It is highly preferred that instead of
+   using this code base that instead `fasteners`_ or `oslo.concurrency`_ is
+   used instead. For any questions or comments or further help needed
+   please email `openstack-dev`_ and prefix your email subject
+   with ``[oslo][pylockfile]`` (for a faster response).
+
+.. note::
+
+   This package is pre-release software.  Between versions 0.8 and 0.9 it
+   was changed from a module to a package.  It is quite possible that the
+   API and implementation will change again in important ways as people test
+   it and provide feedback and bug fixes.  In particular, if the mkdir-based
+   locking scheme is sufficient for both Windows and Unix platforms, the
+   link-based scheme may be deleted so that only a single locking scheme is
+   used, providing cross-platform lockfile cooperation.
+
+.. note::
+
+   The implementation uses the `with` statement, both in the tests and in the
+   main code, so will only work out-of-the-box with Python 2.5 or later.
+   However, the use of the `with` statement is minimal, so if you apply the
+   patch in the included 2.4.diff file you can use it with Python 2.4. It's
+   possible that it will work in Python 2.3 with that patch applied as well,
+   though the doctest code relies on APIs new in 2.4, so will have to be
+   rewritten somewhat to allow testing on 2.3. As they say, patches welcome.
+   ``;-)``
+
+The :mod:`lockfile` package exports a :class:`LockFile` class which provides
+a simple API for locking files.  Unlike the Windows :func:`msvcrt.locking`
+function, the Unix :func:`fcntl.flock`, :func:`fcntl.lockf` and the
+deprecated :mod:`posixfile` module, the API is identical across both Unix
+(including Linux and Mac) and Windows platforms.  The lock mechanism relies
+on the atomic nature of the :func:`link` (on Unix) and :func:`mkdir` (On
+Windows) system calls.  It also contains several lock-method-specific
+modules: :mod:`lockfile.linklockfile`, :mod:`lockfile.mkdirlockfile`, and
+:mod:`lockfile.sqlitelockfile`, each one exporting a single class.  For
+backwards compatibility with versions before 0.9 the :class:`LinkFileLock`,
+:class:`MkdirFileLock` and :class:`SQLiteFileLock` objects are exposed as
+attributes of the top-level lockfile package, though this use was deprecated
+starting with version 0.9 and will be removed in version 1.0.
+
+.. note::
+
+   The current implementation uses :func:`os.link` on Unix, but since that
+   function is unavailable on Windows it uses :func:`os.mkdir` there.  At
+   this point it's not clear that using the :func:`os.mkdir` method would be
+   insufficient on Unix systems.  If it proves to be adequate on Unix then
+   the implementation could be simplified and truly cross-platform locking
+   would be possible.
+
+.. note::
+
+   The current implementation doesn't provide for shared vs. exclusive
+   locks.  It should be possible for multiple reader processes to hold the
+   lock at the same time.
+
+The module defines the following exceptions:
+
+.. exception:: Error
+
+   This is the base class for all exceptions raised by the :class:`LockFile`
+   class.
+
+.. exception:: LockError
+
+   This is the base class for all exceptions raised when attempting to lock
+   a file.
+
+.. exception:: UnlockError
+
+   This is the base class for all exceptions raised when attempting to
+   unlock a file.
+
+.. exception:: LockTimeout
+
+   This exception is raised if the :func:`LockFile.acquire` method is
+   called with a timeout which expires before an existing lock is released.
+
+.. exception:: AlreadyLocked
+
+   This exception is raised if the :func:`LockFile.acquire` detects a
+   file is already locked when in non-blocking mode.
+
+.. exception:: LockFailed
+
+   This exception is raised if the :func:`LockFile.acquire` detects some
+   other condition (such as a non-writable directory) which prevents it from
+   creating its lock file.
+
+.. exception:: NotLocked
+
+   This exception is raised if the file is not locked when
+   :func:`LockFile.release` is called.
+
+.. exception:: NotMyLock
+
+   This exception is raised if the file is locked by another thread or
+   process when :func:`LockFile.release` is called.
+
+The following classes are provided:
+
+.. class:: linklockfile.LinkLockFile(path, threaded=True)
+
+   This class uses the :func:`link(2)` system call as the basic lock
+   mechanism.  *path* is an object in the file system to be locked.  It need
+   not exist, but its directory must exist and be writable at the time the
+   :func:`acquire` and :func:`release` methods are called.  *threaded* is
+   optional, but when set to :const:`True` locks will be distinguished
+   between threads in the same process.
+
+.. class:: symlinklockfile.SymlinkLockFile(path, threaded=True)
+
+   This class uses the :func:`symlink(2)` system call as the basic lock
+   mechanism.  The parameters have the same meaning and constraints as for
+   the :class:`LinkLockFile` class.
+
+.. class:: mkdirlockfile.MkdirLockFile(path, threaded=True)
+
+   This class uses the :func:`mkdir(2)` system call as the basic lock
+   mechanism.  The parameters have the same meaning and constraints as for
+   the :class:`LinkLockFile` class.
+
+.. class:: sqlitelockfile.SQLiteLockFile(path, threaded=True)
+
+   This class uses the :mod:`sqlite3` module to implement the lock
+   mechanism.  The parameters have the same meaning as for the
+   :class:`LinkLockFile` class.
+
+.. class:: LockBase(path, threaded=True)
+
+   This is the base class for all concrete implementations and is available
+   at the lockfile package level so programmers can implement other locking
+   schemes.
+
+.. function:: locked(path, timeout=None)
+
+   This function provides a decorator which insures the decorated function
+   is always called with the lock held.
+
+By default, the :const:`LockFile` object refers to the
+:class:`mkdirlockfile.MkdirLockFile` class on Windows.  On all other
+platforms it refers to the :class:`linklockfile.LinkLockFile` class.
+
+When locking a file the :class:`linklockfile.LinkLockFile` class creates a
+uniquely named hard link to an empty lock file.  That hard link contains the
+hostname, process id, and if locks between threads are distinguished, the
+thread identifier.  For example, if you want to lock access to a file named
+"README", the lock file is named "README.lock".  With per-thread locks
+enabled the hard link is named HOSTNAME-THREADID-PID.  With only per-process
+locks enabled the hard link is named HOSTNAME--PID.
+
+When using the :class:`mkdirlockfile.MkdirLockFile` class the lock file is a
+directory.  Referring to the example above, README.lock will be a directory
+and HOSTNAME-THREADID-PID will be an empty file within that directory.
+
+.. seealso::
+
+   Module :mod:`msvcrt`
+      Provides the :func:`locking` function, the standard Windows way of
+      locking (parts of) a file.
+
+   Module :mod:`posixfile`
+      The deprecated (since Python 1.5) way of locking files on Posix systems.
+
+   Module :mod:`fcntl`
+      Provides the current best way to lock files on Unix systems
+      (:func:`lockf` and :func:`flock`).
+
+LockFile Objects
+----------------
+
+:class:`LockFile` objects support the `context manager` protocol used by the
+statement:`with` statement. The timeout option is not supported when used in
+this fashion. While support for timeouts could be implemented, there is no
+support for handling the eventual :exc:`Timeout` exceptions raised by the
+:func:`__enter__` method, so you would have to protect the `with` statement with
+a `try` statement. The resulting construct would not be any simpler than just
+using a `try` statement in the first place.
+
+:class:`LockFile` has the following user-visible methods:
+
+.. method:: LockFile.acquire(timeout=None)
+
+   Lock the file associated with the :class:`LockFile` object.  If the
+   *timeout* is omitted or :const:`None` the caller will block until the
+   file is unlocked by the object currently holding the lock.  If the
+   *timeout* is zero or a negative number the :exc:`AlreadyLocked` exception
+   will be raised if the file is currently locked by another process or
+   thread.  If the *timeout* is positive, the caller will block for that
+   many seconds waiting for the lock to be released.  If the lock is not
+   released within that period the :exc:`LockTimeout` exception will be
+   raised.
+
+.. method:: LockFile.release()
+
+   Unlock the file associated with the :class:`LockFile` object.  If the
+   file is not currently locked, the :exc:`NotLocked` exception is raised.
+   If the file is locked by another thread or process the :exc:`NotMyLock`
+   exception is raised.
+
+.. method:: is_locked()
+
+   Return the status of the lock on the current file.  If any process or
+   thread (including the current one) is locking the file, :const:`True` is
+   returned, otherwise :const:`False` is returned.
+
+.. method:: break_lock()
+
+   If the file is currently locked, break it.
+
+.. method:: i_am_locking()
+
+   Returns true if the caller holds the lock.
+
+Examples
+--------
+
+This example is the "hello world" for the :mod:`lockfile` package::
+
+    from lockfile import LockFile
+    lock = LockFile("/some/file/or/other")
+    with lock:
+        print lock.path, 'is locked.'
+
+To use this with Python 2.4, you can execute::
+
+    from lockfile import LockFile
+    lock = LockFile("/some/file/or/other")
+    lock.acquire()
+    print lock.path, 'is locked.'
+    lock.release()
+
+If you don't want to wait forever, you might try::
+
+    from lockfile import LockFile
+    lock = LockFile("/some/file/or/other")
+    while not lock.i_am_locking():
+       try:
+           lock.acquire(timeout=60)    # wait up to 60 seconds
+       except LockTimeout:
+           lock.break_lock()
+           lock.acquire()
+    print "I locked", lock.path
+    lock.release()
+
+You can also insure that a lock is always held when appropriately decorated
+functions are called::
+
+    from lockfile import locked
+    @locked("/tmp/mylock")
+    def func(a, b):
+        return a + b
+
+Other Libraries
+---------------
+
+The idea of implementing advisory locking with a standard API is not new
+with :mod:`lockfile`.  There are a number of other libraries available:
+
+* locknix - http://pypi.python.org/pypi/locknix - Unix only
+* mx.MiscLockFile - from Marc André Lemburg, part of the mx.Base
+  distribution - cross-platform.
+* Twisted - http://twistedmatrix.com/trac/browser/trunk/twisted/python/lockfile.py
+* zc.lockfile - http://pypi.python.org/pypi/zc.lockfile
+
+
+Contacting the Author
+---------------------
+
+If you encounter any problems with ``lockfile``, would like help or want to
+submit a patch, check http://launchpad.net/pylockfile
+
+
+.. _fasteners: http://fasteners.readthedocs.org/
+.. _openstack-dev: mailto:openstack-dev@lists.openstack.org
+.. _oslo.concurrency: http://docs.openstack.org/developer/oslo.concurrency/
diff --git a/tmp/lockfile-0.12.2/lockfile.egg-info/PKG-INFO b/tmp/lockfile-0.12.2/lockfile.egg-info/PKG-INFO
new file mode 100644 (file)
index 0000000..d348d5d
--- /dev/null
@@ -0,0 +1,56 @@
+Metadata-Version: 1.1
+Name: lockfile
+Version: 0.12.2
+Summary: Platform-independent file locking module
+Home-page: http://launchpad.net/pylockfile
+Author: OpenStack
+Author-email: openstack-dev@lists.openstack.org
+License: UNKNOWN
+Description: Note: This package is **deprecated**. It is highly preferred that instead of
+        using this code base that instead `fasteners`_ or `oslo.concurrency`_ is
+        used instead. For any questions or comments or further help needed
+        please email `openstack-dev`_ and prefix your email subject
+        with ``[oslo][pylockfile]`` (for a faster response).
+        
+        The lockfile package exports a LockFile class which provides a simple API for
+        locking files.  Unlike the Windows msvcrt.locking function, the fcntl.lockf
+        and flock functions, and the deprecated posixfile module, the API is
+        identical across both Unix (including Linux and Mac) and Windows platforms.
+        The lock mechanism relies on the atomic nature of the link (on Unix) and
+        mkdir (on Windows) system calls.  An implementation based on SQLite is also
+        provided, more as a demonstration of the possibilities it provides than as
+        production-quality code.
+        
+        Note: In version 0.9 the API changed in two significant ways:
+        
+         * It changed from a module defining several classes to a package containing
+           several modules, each defining a single class.
+        
+         * Where classes had been named SomethingFileLock before the last two words
+           have been reversed, so that class is now SomethingLockFile.
+        
+        The previous module-level definitions of LinkFileLock, MkdirFileLock and
+        SQLiteFileLock will be retained until the 1.0 release.
+        
+        To install:
+        
+            python setup.py install
+        
+        * Documentation: http://docs.openstack.org/developer/pylockfile
+        * Source: http://git.openstack.org/cgit/openstack/pylockfile
+        * Bugs: http://bugs.launchpad.net/pylockfile
+        
+        
+Platform: UNKNOWN
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: MIT License
+Classifier: Operating System :: POSIX :: Linux
+Classifier: Operating System :: MacOS
+Classifier: Operating System :: Microsoft :: Windows :: Windows NT/2000
+Classifier: Operating System :: POSIX
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.3
+Classifier: Topic :: Software Development :: Libraries :: Python Modules
diff --git a/tmp/lockfile-0.12.2/lockfile.egg-info/SOURCES.txt b/tmp/lockfile-0.12.2/lockfile.egg-info/SOURCES.txt
new file mode 100644 (file)
index 0000000..72948af
--- /dev/null
@@ -0,0 +1,27 @@
+ACKS
+AUTHORS
+ChangeLog
+LICENSE
+README.rst
+RELEASE-NOTES
+setup.cfg
+setup.py
+test-requirements.txt
+tox.ini
+doc/source/Makefile
+doc/source/conf.py
+doc/source/index.rst
+lockfile/__init__.py
+lockfile/linklockfile.py
+lockfile/mkdirlockfile.py
+lockfile/pidlockfile.py
+lockfile/sqlitelockfile.py
+lockfile/symlinklockfile.py
+lockfile.egg-info/PKG-INFO
+lockfile.egg-info/SOURCES.txt
+lockfile.egg-info/dependency_links.txt
+lockfile.egg-info/not-zip-safe
+lockfile.egg-info/pbr.json
+lockfile.egg-info/top_level.txt
+test/compliancetest.py
+test/test_lockfile.py
\ No newline at end of file
diff --git a/tmp/lockfile-0.12.2/lockfile.egg-info/dependency_links.txt b/tmp/lockfile-0.12.2/lockfile.egg-info/dependency_links.txt
new file mode 100644 (file)
index 0000000..8b13789
--- /dev/null
@@ -0,0 +1 @@
+
diff --git a/tmp/lockfile-0.12.2/lockfile.egg-info/not-zip-safe b/tmp/lockfile-0.12.2/lockfile.egg-info/not-zip-safe
new file mode 100644 (file)
index 0000000..8b13789
--- /dev/null
@@ -0,0 +1 @@
+
diff --git a/tmp/lockfile-0.12.2/lockfile.egg-info/pbr.json b/tmp/lockfile-0.12.2/lockfile.egg-info/pbr.json
new file mode 100644 (file)
index 0000000..56252f0
--- /dev/null
@@ -0,0 +1 @@
+{"git_version": "c8798ce", "is_release": true}
\ No newline at end of file
diff --git a/tmp/lockfile-0.12.2/lockfile.egg-info/top_level.txt b/tmp/lockfile-0.12.2/lockfile.egg-info/top_level.txt
new file mode 100644 (file)
index 0000000..5a13159
--- /dev/null
@@ -0,0 +1 @@
+lockfile
diff --git a/tmp/lockfile-0.12.2/setup.cfg b/tmp/lockfile-0.12.2/setup.cfg
new file mode 100644 (file)
index 0000000..e51b5c1
--- /dev/null
@@ -0,0 +1,41 @@
+[metadata]
+name = lockfile
+summary = Platform-independent file locking module
+description-file = 
+       README.rst
+author = OpenStack
+author-email = openstack-dev@lists.openstack.org
+home-page = http://launchpad.net/pylockfile
+classifier = 
+       Intended Audience :: Developers
+       License :: OSI Approved :: MIT License
+       Operating System :: POSIX :: Linux
+       Operating System :: MacOS
+       Operating System :: Microsoft :: Windows :: Windows NT/2000
+       Operating System :: POSIX
+       Programming Language :: Python
+       Programming Language :: Python :: 2
+       Programming Language :: Python :: 2.7
+       Programming Language :: Python :: 3
+       Programming Language :: Python :: 3.3
+       Topic :: Software Development :: Libraries :: Python Modules
+
+[files]
+packages = lockfile
+
+[pbr]
+warnerrors = true
+
+[build_sphinx]
+source-dir = doc/source
+build-dir = doc/build
+all_files = 1
+
+[bdist_wheel]
+universal = 1
+
+[egg_info]
+tag_date = 0
+tag_svn_revision = 0
+tag_build = 
+
diff --git a/tmp/lockfile-0.12.2/setup.py b/tmp/lockfile-0.12.2/setup.py
new file mode 100644 (file)
index 0000000..782bb21
--- /dev/null
@@ -0,0 +1,29 @@
+# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT
+import setuptools
+
+# In python < 2.7.4, a lazy loading of package `pbr` will break
+# setuptools if some other modules registered functions in `atexit`.
+# solution from: http://bugs.python.org/issue15881#msg170215
+try:
+    import multiprocessing  # noqa
+except ImportError:
+    pass
+
+setuptools.setup(
+    setup_requires=['pbr>=1.8'],
+    pbr=True)
diff --git a/tmp/lockfile-0.12.2/test-requirements.txt b/tmp/lockfile-0.12.2/test-requirements.txt
new file mode 100644 (file)
index 0000000..446a41f
--- /dev/null
@@ -0,0 +1,5 @@
+# The order of packages is significant, because pip processes them in the order
+# of appearance. Changing the order has an impact on the overall integration
+# process, which may cause wedges in the gate later.
+nose
+sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2
diff --git a/tmp/lockfile-0.12.2/test/compliancetest.py b/tmp/lockfile-0.12.2/test/compliancetest.py
new file mode 100644 (file)
index 0000000..bf4e59c
--- /dev/null
@@ -0,0 +1,262 @@
+import os
+import threading
+import shutil
+
+import lockfile
+
+
+class ComplianceTest(object):
+    def __init__(self):
+        self.saved_class = lockfile.LockFile
+
+    def _testfile(self):
+        """Return platform-appropriate file.  Helper for tests."""
+        import tempfile
+        return os.path.join(tempfile.gettempdir(), 'trash-%s' % os.getpid())
+
+    def setup(self):
+        lockfile.LockFile = self.class_to_test
+
+    def teardown(self):
+        try:
+            tf = self._testfile()
+            if os.path.isdir(tf):
+                shutil.rmtree(tf)
+            elif os.path.isfile(tf):
+                os.unlink(tf)
+            elif not os.path.exists(tf):
+                pass
+            else:
+                raise SystemError("unrecognized file: %s" % tf)
+        finally:
+            lockfile.LockFile = self.saved_class
+
+    def _test_acquire_helper(self, tbool):
+        # As simple as it gets.
+        lock = lockfile.LockFile(self._testfile(), threaded=tbool)
+        lock.acquire()
+        assert lock.i_am_locking()
+        lock.release()
+        assert not lock.is_locked()
+
+#     def test_acquire_basic_threaded(self):
+#         self._test_acquire_helper(True)
+
+    def test_acquire_basic_unthreaded(self):
+        self._test_acquire_helper(False)
+
+    def _test_acquire_no_timeout_helper(self, tbool):
+        # No timeout test
+        e1, e2 = threading.Event(), threading.Event()
+        t = _in_thread(self._lock_wait_unlock, e1, e2)
+        e1.wait()         # wait for thread t to acquire lock
+        lock2 = lockfile.LockFile(self._testfile(), threaded=tbool)
+        assert lock2.is_locked()
+        if tbool:
+            assert not lock2.i_am_locking()
+        else:
+            assert lock2.i_am_locking()
+
+        try:
+            lock2.acquire(timeout=-1)
+        except lockfile.AlreadyLocked:
+            pass
+        else:
+            lock2.release()
+            raise AssertionError("did not raise AlreadyLocked in"
+                                 " thread %s" %
+                                 threading.current_thread().get_name())
+
+        try:
+            lock2.acquire(timeout=0)
+        except lockfile.AlreadyLocked:
+            pass
+        else:
+            lock2.release()
+            raise AssertionError("did not raise AlreadyLocked in"
+                                 " thread %s" %
+                                 threading.current_thread().get_name())
+
+        e2.set()          # tell thread t to release lock
+        t.join()
+
+#     def test_acquire_no_timeout_threaded(self):
+#         self._test_acquire_no_timeout_helper(True)
+
+#     def test_acquire_no_timeout_unthreaded(self):
+#         self._test_acquire_no_timeout_helper(False)
+
+    def _test_acquire_timeout_helper(self, tbool):
+        # Timeout test
+        e1, e2 = threading.Event(), threading.Event()
+        t = _in_thread(self._lock_wait_unlock, e1, e2)
+        e1.wait()                # wait for thread t to acquire lock
+        lock2 = lockfile.LockFile(self._testfile(), threaded=tbool)
+        assert lock2.is_locked()
+        try:
+            lock2.acquire(timeout=0.1)
+        except lockfile.LockTimeout:
+            pass
+        else:
+            lock2.release()
+            raise AssertionError("did not raise LockTimeout in thread %s" %
+                                 threading.current_thread().get_name())
+
+        e2.set()
+        t.join()
+
+    def test_acquire_timeout_threaded(self):
+        self._test_acquire_timeout_helper(True)
+
+    def test_acquire_timeout_unthreaded(self):
+        self._test_acquire_timeout_helper(False)
+
+    def _test_context_timeout_helper(self, tbool):
+        # Timeout test
+        e1, e2 = threading.Event(), threading.Event()
+        t = _in_thread(self._lock_wait_unlock, e1, e2)
+        e1.wait()                # wait for thread t to acquire lock
+        lock2 = lockfile.LockFile(self._testfile(), threaded=tbool,
+                                  timeout=0.2)
+        assert lock2.is_locked()
+        try:
+            lock2.acquire()
+        except lockfile.LockTimeout:
+            pass
+        else:
+            lock2.release()
+            raise AssertionError("did not raise LockTimeout in thread %s" %
+                                 threading.current_thread().get_name())
+
+        e2.set()
+        t.join()
+
+    def test_context_timeout_unthreaded(self):
+        self._test_context_timeout_helper(False)
+
+    def _test_release_basic_helper(self, tbool):
+        lock = lockfile.LockFile(self._testfile(), threaded=tbool)
+        lock.acquire()
+        assert lock.is_locked()
+        lock.release()
+        assert not lock.is_locked()
+        assert not lock.i_am_locking()
+        try:
+            lock.release()
+        except lockfile.NotLocked:
+            pass
+        except lockfile.NotMyLock:
+            raise AssertionError('unexpected exception: %s' %
+                                 lockfile.NotMyLock)
+        else:
+            raise AssertionError('erroneously unlocked file')
+
+#     def test_release_basic_threaded(self):
+#         self._test_release_basic_helper(True)
+
+    def test_release_basic_unthreaded(self):
+        self._test_release_basic_helper(False)
+
+#     def test_release_from_thread(self):
+#         e1, e2 = threading.Event(), threading.Event()
+#         t = _in_thread(self._lock_wait_unlock, e1, e2)
+#         e1.wait()
+#         lock2 = lockfile.LockFile(self._testfile(), threaded=False)
+#         assert not lock2.i_am_locking()
+#         try:
+#             lock2.release()
+#         except lockfile.NotMyLock:
+#             pass
+#         else:
+#             raise AssertionError('erroneously unlocked a file locked'
+#                                  ' by another thread.')
+#         e2.set()
+#         t.join()
+
+    def _test_is_locked_helper(self, tbool):
+        lock = lockfile.LockFile(self._testfile(), threaded=tbool)
+        lock.acquire(timeout=2)
+        assert lock.is_locked()
+        lock.release()
+        assert not lock.is_locked(), "still locked after release!"
+
+#     def test_is_locked_threaded(self):
+#         self._test_is_locked_helper(True)
+
+    def test_is_locked_unthreaded(self):
+        self._test_is_locked_helper(False)
+
+#     def test_i_am_locking_threaded(self):
+#         self._test_i_am_locking_helper(True)
+
+    def test_i_am_locking_unthreaded(self):
+        self._test_i_am_locking_helper(False)
+
+    def _test_i_am_locking_helper(self, tbool):
+        lock1 = lockfile.LockFile(self._testfile(), threaded=tbool)
+        assert not lock1.is_locked()
+        lock1.acquire()
+        try:
+            assert lock1.i_am_locking()
+            lock2 = lockfile.LockFile(self._testfile(), threaded=tbool)
+            assert lock2.is_locked()
+            if tbool:
+                assert not lock2.i_am_locking()
+        finally:
+            lock1.release()
+
+    def _test_break_lock_helper(self, tbool):
+        lock = lockfile.LockFile(self._testfile(), threaded=tbool)
+        lock.acquire()
+        assert lock.is_locked()
+        lock2 = lockfile.LockFile(self._testfile(), threaded=tbool)
+        assert lock2.is_locked()
+        lock2.break_lock()
+        assert not lock2.is_locked()
+        try:
+            lock.release()
+        except lockfile.NotLocked:
+            pass
+        else:
+            raise AssertionError('break lock failed')
+
+#     def test_break_lock_threaded(self):
+#         self._test_break_lock_helper(True)
+
+    def test_break_lock_unthreaded(self):
+        self._test_break_lock_helper(False)
+
+    def _lock_wait_unlock(self, event1, event2):
+        """Lock from another thread.  Helper for tests."""
+        l = lockfile.LockFile(self._testfile())
+        l.acquire()
+        try:
+            event1.set()   # we're in,
+            event2.wait()  # wait for boss's permission to leave
+        finally:
+            l.release()
+
+    def test_enter(self):
+        lock = lockfile.LockFile(self._testfile())
+        lock.acquire()
+        try:
+            assert lock.is_locked(), "Not locked after acquire!"
+        finally:
+            lock.release()
+        assert not lock.is_locked(), "still locked after release!"
+
+    def test_decorator(self):
+        @lockfile.locked(self._testfile())
+        def func(a, b):
+            return a + b
+        assert func(4, 3) == 7
+
+
+def _in_thread(func, *args, **kwargs):
+    """Execute func(*args, **kwargs) after dt seconds. Helper for tests."""
+    def _f():
+        func(*args, **kwargs)
+    t = threading.Thread(target=_f, name='/*/*')
+    t.setDaemon(True)
+    t.start()
+    return t
diff --git a/tmp/lockfile-0.12.2/test/test_lockfile.py b/tmp/lockfile-0.12.2/test/test_lockfile.py
new file mode 100644 (file)
index 0000000..ca1ed06
--- /dev/null
@@ -0,0 +1,41 @@
+import lockfile.linklockfile
+import lockfile.mkdirlockfile
+import lockfile.pidlockfile
+import lockfile.symlinklockfile
+
+from compliancetest import ComplianceTest
+
+
+class TestLinkLockFile(ComplianceTest):
+    class_to_test = lockfile.linklockfile.LinkLockFile
+
+
+class TestSymlinkLockFile(ComplianceTest):
+    class_to_test = lockfile.symlinklockfile.SymlinkLockFile
+
+
+class TestMkdirLockFile(ComplianceTest):
+    class_to_test = lockfile.mkdirlockfile.MkdirLockFile
+
+
+class TestPIDLockFile(ComplianceTest):
+    class_to_test = lockfile.pidlockfile.PIDLockFile
+
+
+# Check backwards compatibility
+class TestLinkFileLock(ComplianceTest):
+    class_to_test = lockfile.LinkFileLock
+
+
+class TestMkdirFileLock(ComplianceTest):
+    class_to_test = lockfile.MkdirFileLock
+
+try:
+    import sqlite3   # noqa
+except ImportError:
+    pass
+else:
+    import lockfile.sqlitelockfile
+
+    class TestSQLiteLockFile(ComplianceTest):
+        class_to_test = lockfile.sqlitelockfile.SQLiteLockFile
diff --git a/tmp/lockfile-0.12.2/tox.ini b/tmp/lockfile-0.12.2/tox.ini
new file mode 100644 (file)
index 0000000..750ad5f
--- /dev/null
@@ -0,0 +1,27 @@
+# content of: tox.ini , put in same dir as setup.py
+[tox]
+envlist = py27,py32,py33,py34
+
+[testenv]
+deps = -r{toxinidir}/test-requirements.txt
+commands=nosetests
+
+[testenv:venv]
+commands = {posargs}
+
+[testenv:pep8]
+deps = flake8
+commands = flake8
+
+[testenv:docs]
+commands = python setup.py build_sphinx
+
+[testenv:cover]
+deps = {[testenv]deps}
+       coverage
+commands =
+  nosetests --with-coverage --cover-erase --cover-package=lockfile --cover-inclusive []
+
+[flake8]
+exclude=.venv,.git,.tox,dist,doc
+show-source = True
diff --git a/tmp/python-daemon-2.1.1.tar.gz b/tmp/python-daemon-2.1.1.tar.gz
new file mode 100644 (file)
index 0000000..f5989e7
Binary files /dev/null and b/tmp/python-daemon-2.1.1.tar.gz differ