[Zope-Checkins] SVN: zdaemon/trunk/ Rearranging to more typical layout

Jim Fulton jim at zope.com
Sat Oct 29 19:38:49 EDT 2005


Log message for revision 39724:
  Rearranging to more typical layout

Changed:
  D   zdaemon/trunk/DEPENDENCIES.cfg
  D   zdaemon/trunk/SETUP.cfg
  D   zdaemon/trunk/__init__.py
  D   zdaemon/trunk/component.xml
  D   zdaemon/trunk/sample.conf
  D   zdaemon/trunk/schema.xml
  A   zdaemon/trunk/src/zdaemon/DEPENDENCIES.cfg
  A   zdaemon/trunk/src/zdaemon/SETUP.cfg
  A   zdaemon/trunk/src/zdaemon/__init__.py
  A   zdaemon/trunk/src/zdaemon/component.xml
  A   zdaemon/trunk/src/zdaemon/sample.conf
  A   zdaemon/trunk/src/zdaemon/schema.xml
  A   zdaemon/trunk/src/zdaemon/tests/
  A   zdaemon/trunk/src/zdaemon/zdctl.py
  A   zdaemon/trunk/src/zdaemon/zdoptions.py
  A   zdaemon/trunk/src/zdaemon/zdrun.py
  A   zdaemon/trunk/src/zope/
  A   zdaemon/trunk/src/zope/__init__.py
  D   zdaemon/trunk/tests/
  D   zdaemon/trunk/zdctl.py
  D   zdaemon/trunk/zdoptions.py
  D   zdaemon/trunk/zdrun.py

-=-
Deleted: zdaemon/trunk/DEPENDENCIES.cfg
===================================================================
--- zdaemon/trunk/DEPENDENCIES.cfg	2005-10-29 23:30:35 UTC (rev 39723)
+++ zdaemon/trunk/DEPENDENCIES.cfg	2005-10-29 23:38:48 UTC (rev 39724)
@@ -1 +0,0 @@
-ZConfig

Deleted: zdaemon/trunk/SETUP.cfg
===================================================================
--- zdaemon/trunk/SETUP.cfg	2005-10-29 23:30:35 UTC (rev 39723)
+++ zdaemon/trunk/SETUP.cfg	2005-10-29 23:38:48 UTC (rev 39724)
@@ -1,2 +0,0 @@
-script  zdctl.py
-script  zdrun.py

Deleted: zdaemon/trunk/__init__.py
===================================================================
--- zdaemon/trunk/__init__.py	2005-10-29 23:30:35 UTC (rev 39723)
+++ zdaemon/trunk/__init__.py	2005-10-29 23:38:48 UTC (rev 39724)
@@ -1,14 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
-# All Rights Reserved.
-#
-# This software is subject to the provisions of the Zope Public License,
-# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
-# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
-# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
-# FOR A PARTICULAR PURPOSE.
-#
-##############################################################################
-"""zdaemon -- a package to manage a daemon application."""

Deleted: zdaemon/trunk/component.xml
===================================================================
--- zdaemon/trunk/component.xml	2005-10-29 23:30:35 UTC (rev 39723)
+++ zdaemon/trunk/component.xml	2005-10-29 23:38:48 UTC (rev 39724)
@@ -1,275 +0,0 @@
-<component>
-
-  <!-- Note on logging configuration:
-
-       This schema component expects to use a section type named
-       "eventlog"; this type needs to be provided by some other
-       component that the top-level schema needs to import.
-
-       The import is not performed here to allow applications to
-       load the type from different components.
-    -->
-
-  <sectiontype name="runner">
-
-    <description>
-      This section describes the options for zdctl.py and zdrun.py.
-      The only required option is "program".  Many other options have
-      no default value specified in the schema; in some cases, the
-      program calculates a dynamic default, in others, the feature
-      associated with the option is disabled.
-
-      For those options that also have corresponding command-line
-      options, the command line option (short and long form) are given
-      here too.
-    </description>
-
-    <section name="*" type="ZConfig.logger.log"
-             attribute="eventlog"
-             required="no">
-      <description>
-        Log configuration for zdctl.py and zdrun.py.  These
-        applications will normally use the eventlog section at the top
-        level of the configuration, but will use this eventlog section
-        if it exists.
-
-        (This is done so that the combined schema for the runner and
-        the controlled application will write to the same logs by
-        default, but a separation of logs can be achieved if desired.)
-      </description>
-    </section>
-
-    <key name="program" datatype="string-list"
-         required="yes">
-      <description>
-        Command-line option: -p or --program (zdctl.py only).
-
-        This option gives the command used to start the subprocess
-        managed by zdrun.py.  This is currently a simple list of
-        whitespace-delimited words. The first word is the program
-        file, subsequent words are its command line arguments.  If the
-        program file contains no slashes, it is searched using $PATH.
-        (XXX There is no way to to include whitespace in the program
-        file or an argument, and under certain circumstances other
-        shell metacharacters are also a problem, e.g. the "foreground"
-        command of zdctl.py.)
-
-        NOTE: zdrun.py doesn't use this option; it uses its positional
-        arguments.  Rather, zdctl.py uses this option to determine the
-        positional argument with which to invoke zdrun.py.  (XXX This
-        could be better.)
-      </description>
-    </key>
-
-    <key name="python" datatype="existing-path"
-         required="no">
-      <description>
-        Path to the Python interpreter.  Used by zdctl.py to start the
-        zdrun.py process.  Defaults to sys.executable.
-      </description>
-    </key>
-
-    <key name="zdrun" datatype="existing-path"
-         required="no">
-      <description>
-        Path to the zdrun.py script.  Used by zdctl.py to start the
-        zdrun.py process.  Defaults to a file named "zdrun.py" in the
-        same directory as zdctl.py.
-      </description>
-    </key>
-
-    <key name="socket-name" datatype="existing-dirpath"
-         required="no"
-         default="zdsock">
-      <description>
-        Command-line option: -s or --socket-name.
-
-        The pathname of the Unix domain socket used for communication
-        between zdctl.py and zdrun.py.  The default is relative to the
-        current directory in which zdctl.py and zdrun.py are started.
-        You want to specify an absolute pathname here.
-      </description>
-    </key>
-
-    <key name="daemon" datatype="boolean"
-         required="no"
-         default="false">
-      <description>
-        Command-line option: -d or --daemon.
-
-        If this option is true, zdrun.py runs in the background as a
-        true daemon.  It forks a child process which becomes the
-        subprocess manager, while the parent exits (making the shell
-        that started it believe it is done).  The child process also
-        does the following:
-
-        - if the directory option is set, change into that directory
-
-        - redirect stdin, stdout and stderr to /dev/null
-
-        - call setsid() so it becomes a session leader
-
-        - call umask() with specified value
-      </description>
-    </key>
-
-    <key name="directory" datatype="existing-directory"
-         required="no">
-      <description>
-        Command-line option: -z or --directory.
-
-        If the daemon option is true, this option can specify a
-        directory into which zdrun.py changes as part of the
-        "daemonizing".  If the daemon option is false, this option is
-        ignored.
-      </description>
-    </key>
-
-    <key name="backoff-limit" datatype="integer"
-         required="no"
-         default="10">
-      <description>
-        Command-line option: -b or --backoff-limit.
-
-        When the subprocess crashes, zdrun.py inserts a one-second
-        delay before it restarts it.  When the subprocess crashes
-        again right away, the delay is incremented by one second, and
-        so on.  What happens when the delay has reached the value of
-        backoff-limit (in seconds), depends on the value of the
-        forever option.  If forever is false, zdrun.py gives up at
-        this point, and exits.  An always-crashing subprocess will
-        have been restarted exactly backoff-limit times in this case.
-        If forever is true, zdrun.py continues to attempt to restart
-        the process, keeping the delay at backoff-limit seconds.
-
-        If the subprocess stays up for more than backoff-limit
-        seconds, the delay is reset to 1 second.
-      </description>
-    </key>
-
-    <key name="forever" datatype="boolean"
-         required="no"
-         default="false">
-      <description>
-        Command-line option: -f or --forever.
-
-        If this option is true, zdrun.py will keep restarting a
-        crashing subprocess forever.  If it is false, it will give up
-        after backoff-limit crashes in a row.  See the description of
-        backoff-limit for details.
-      </description>
-    </key>
-
-    <key name="exit-codes" datatype="zdaemon.zdoptions.list_of_ints"
-         required="no"
-         default="0,2">
-      <description>
-        Command-line option: -x or --exit-codes.
-
-        If the subprocess exits with an exit status that is equal to
-        one of the integers in this list, zdrun.py will not restart
-        it.  The default list requires some explanation.  Exit status
-        0 is considered a willful successful exit; the ZEO and Zope
-        server processes use this exit status when they want to stop
-        without being restarted.  (Including in response to a
-        SIGTERM.)  Exit status 2 is typically issued for command line
-        syntax errors; in this case, restarting the program will not
-        help!
-
-        NOTE: this mechanism overrides the backoff-limit and forever
-        options; i.e. even if forever is true, a subprocess exit
-        status code in this list makes zdrun.py give up.  To disable
-        this, change the value to an empty list.
-      </description>
-    </key>
-
-    <key name="user" datatype="string"
-         required="no">
-      <description>
-        Command-line option: -u or --user.
-
-        When zdrun.py is started by root, this option specifies the
-        user as who the the zdrun.py process (and hence the daemon
-        subprocess) will run.  This can be a user name or a numeric
-        user id.  Both the user and the group are set from the
-        corresponding password entry, using setuid() and setgid().
-        This is done before zdrun.py does anything else besides
-        parsing its command line arguments.
-
-        NOTE: when zdrun.py is not started by root, specifying this
-        option is an error.  (XXX This may be a mistake.)
-
-        XXX The zdrun.py event log file may be opened *before*
-        setuid() is called.  Is this good or bad?
-      </description>
-    </key>
-
-    <key name="umask" datatype="zdaemon.zdoptions.octal_type"
-         required="no"
-         default="022">
-      <description>
-        Command-line option: -m or --umask.
-
-        When daemon mode is used, this option specifies the octal umask
-        of the subprocess.
-      </description>
-    </key>
-
-    <key name="hang-around" datatype="boolean"
-         required="no"
-         default="false">
-      <description>
-        If this option is true, the zdrun.py process will remain even
-        when the daemon subprocess is stopped.  In this case, zdctl.py
-        will restart zdrun.py as necessary.  If this option is false,
-        zdrun.py will exit when the daemon subprocess is stopped
-        (unless zdrun.py intends to restart it).
-      </description>
-    </key>
-
-    <key name="default-to-interactive" datatype="boolean"
-         required="no"
-         default="true">
-      <description>
-        If this option is true, zdctl.py enters interactive mode
-        when it is invoked without a positional command argument.  If
-        it is false, you must use the -i or --interactive command line
-        option to zdctl.py to enter interactive mode.
-      </description>
-    </key>
-
-    <key name="logfile" datatype="existing-dirpath"
-         required="no">
-      <description>
-        This option specifies a log file that is the default target of
-        the "logtail" zdctl.py command.
-
-        NOTE: This is NOT the log file to which zdrun.py writes its
-        logging messages!  That log file is specified by the
-        &lt;eventlog&gt; section.
-      </description>
-    </key>
-
-    <key name="transcript" datatype="existing-dirpath"
-         required="no">
-      <description>
-        The name of a file in which a transcript of all output from
-        the command being run will be written to when daemonized.
-
-        If not specified, output from the command will be discarded.
-
-        This only takes effect when the "daemon" option is enabled.
-      </description>
-    </key>
-
-    <key name="prompt" datatype="string"
-         required="no">
-       <description>
-         The prompt shown by the controller program.  The default must
-         be provided by the application.
-       </description>
-    </key>
-
-  </sectiontype>
-
-</component>

Deleted: zdaemon/trunk/sample.conf
===================================================================
--- zdaemon/trunk/sample.conf	2005-10-29 23:30:35 UTC (rev 39723)
+++ zdaemon/trunk/sample.conf	2005-10-29 23:38:48 UTC (rev 39724)
@@ -1,24 +0,0 @@
-# Sample config file for zdctl.py and zdrun.py (which share a schema).
-
-<runner>
-  # Harmless example
-  program       sleep 100
-  # Repeat the defaults
-  backoff-limit 10
-  daemon	True
-  forever	True
-  socket-name	zdsock
-  exit-codes	0,2
-  # user has no default
-  umask		022
-  directory	.
-  default-to-interactive True
-  hang-around   False
-</runner>
-
-<eventlog>
-  level info
-  <logfile>
-    path /tmp/zdrun.log
-  </logfile>
-</eventlog>

Deleted: zdaemon/trunk/schema.xml
===================================================================
--- zdaemon/trunk/schema.xml	2005-10-29 23:30:35 UTC (rev 39723)
+++ zdaemon/trunk/schema.xml	2005-10-29 23:38:48 UTC (rev 39724)
@@ -1,26 +0,0 @@
-<schema>
-
-  <description>
-    This schema describes various options that control zdctl.py and
-    zdrun.py.  zdrun.py is the "daemon process manager"; it runs a
-    subprocess in the background and restarts it when it crashes.
-    zdctl.py is the user interface to zdrun.py; it can tell zdrun.py
-    to start, stop or restart the subprocess, send it a signal, etc.
-
-    There are two sections: &lt;runner&gt; defines options unique
-    zdctl.py and zdrun.py, and &lt;eventlog&gt; defines a standard
-    event logging section used by zdrun.py.
-
-    More information about zdctl.py and zdrun.py can be found in the
-    file Doc/zdctl.txt.  This all is specific to Unix/Linux.
-  </description>
-
-  <import package="ZConfig.components.logger"/>
-
-  <import package="zdaemon"/>
-
-  <section name="*" type="runner" attribute="runner" required="yes" />
-
-  <section name="*" type="eventlog" attribute="eventlog" required="no" />
-
-</schema>

Copied: zdaemon/trunk/src/zdaemon/DEPENDENCIES.cfg (from rev 39721, zdaemon/trunk/DEPENDENCIES.cfg)

Copied: zdaemon/trunk/src/zdaemon/SETUP.cfg (from rev 39721, zdaemon/trunk/SETUP.cfg)

Copied: zdaemon/trunk/src/zdaemon/__init__.py (from rev 39721, zdaemon/trunk/__init__.py)

Copied: zdaemon/trunk/src/zdaemon/component.xml (from rev 39721, zdaemon/trunk/component.xml)

Copied: zdaemon/trunk/src/zdaemon/sample.conf (from rev 39721, zdaemon/trunk/sample.conf)

Copied: zdaemon/trunk/src/zdaemon/schema.xml (from rev 39721, zdaemon/trunk/schema.xml)

Copied: zdaemon/trunk/src/zdaemon/tests (from rev 39721, zdaemon/trunk/tests)

Copied: zdaemon/trunk/src/zdaemon/zdctl.py (from rev 39721, zdaemon/trunk/zdctl.py)

Copied: zdaemon/trunk/src/zdaemon/zdoptions.py (from rev 39721, zdaemon/trunk/zdoptions.py)

Copied: zdaemon/trunk/src/zdaemon/zdrun.py (from rev 39721, zdaemon/trunk/zdrun.py)


Property changes on: zdaemon/trunk/src/zope
___________________________________________________________________
Name: svn:externals
   + testing -r39704 svn://svn.zope.org/repos/main/zope.testing/trunk/src/zope/testing


Added: zdaemon/trunk/src/zope/__init__.py
===================================================================
--- zdaemon/trunk/src/zope/__init__.py	2005-10-29 23:30:35 UTC (rev 39723)
+++ zdaemon/trunk/src/zope/__init__.py	2005-10-29 23:38:48 UTC (rev 39724)
@@ -0,0 +1,2 @@
+from pkgutil import extend_path
+__path__ = extend_path(__path__, __name__)


Property changes on: zdaemon/trunk/src/zope/__init__.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Deleted: zdaemon/trunk/zdctl.py
===================================================================
--- zdaemon/trunk/zdctl.py	2005-10-29 23:30:35 UTC (rev 39723)
+++ zdaemon/trunk/zdctl.py	2005-10-29 23:38:48 UTC (rev 39724)
@@ -1,591 +0,0 @@
-#!python
-##############################################################################
-#
-# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
-# All Rights Reserved.
-#
-# This software is subject to the provisions of the Zope Public License,
-# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
-# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
-# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
-# FOR A PARTICULAR PURPOSE.
-#
-##############################################################################
-"""zdctl -- control an application run by zdaemon.
-
-Usage: python zdctl.py [-C URL] [-S schema.xml] [-h] [-p PROGRAM]
-       [zdrun-options] [action [arguments]]
-
-Options:
--C/--configure URL -- configuration file or URL
--S/--schema XML Schema -- XML schema for configuration file
--h/--help -- print usage message and exit
--b/--backoff-limit SECONDS -- set backoff limit to SECONDS (default 10)
--d/--daemon -- run as a proper daemon; fork a subprocess, close files etc.
--f/--forever -- run forever (by default, exit when backoff limit is exceeded)
--h/--help -- print this usage message and exit
--i/--interactive -- start an interactive shell after executing commands
--l/--logfile -- log file to be read by logtail command
--p/--program PROGRAM -- the program to run
--s/--socket-name SOCKET -- Unix socket name for client (default "zdsock")
--u/--user USER -- run as this user (or numeric uid)
--m/--umask UMASK -- use this umask for daemon subprocess (default is 022)
--x/--exit-codes LIST -- list of fatal exit codes (default "0,2")
--z/--directory DIRECTORY -- directory to chdir to when using -d (default off)
-action [arguments] -- see below
-
-Actions are commands like "start", "stop" and "status".  If -i is
-specified or no action is specified on the command line, a "shell"
-interpreting actions typed interactively is started (unless the
-configuration option default_to_interactive is set to false).  Use the
-action "help" to find out about available actions.
-"""
-
-import os
-import re
-import cmd
-import sys
-import time
-import signal
-import socket
-import stat
-
-if __name__ == "__main__":
-    # Add the parent of the script directory to the module search path
-    # (but only when the script is run from inside the zdaemon package)
-    from os.path import dirname, basename, abspath, normpath
-    scriptdir = dirname(normpath(abspath(sys.argv[0])))
-    if basename(scriptdir).lower() == "zdaemon":
-        sys.path.append(dirname(scriptdir))
-
-from zdaemon.zdoptions import RunnerOptions
-
-
-def string_list(arg):
-    return arg.split()
-
-
-class ZDCtlOptions(RunnerOptions):
-
-    positional_args_allowed = 1
-
-    def __init__(self):
-        RunnerOptions.__init__(self)
-        self.add("schemafile", short="S:", long="schema=",
-                 default="schema.xml",
-                 handler=self.set_schemafile)
-        self.add("interactive", None, "i", "interactive", flag=1)
-        self.add("default_to_interactive", "runner.default_to_interactive",
-                 default=1)
-        self.add("program", "runner.program", "p:", "program=",
-                 handler=string_list,
-                 required="no program specified; use -p or -C")
-        self.add("logfile", "runner.logfile", "l:", "logfile=")
-        self.add("python", "runner.python")
-        self.add("zdrun", "runner.zdrun")
-        programname = os.path.basename(sys.argv[0])
-        base, ext = os.path.splitext(programname)
-        if ext == ".py":
-            programname = base
-        self.add("prompt", "runner.prompt", default=(programname + ">"))
-
-    def realize(self, *args, **kwds):
-        RunnerOptions.realize(self, *args, **kwds)
-
-        # Maybe the config file requires -i or positional args
-        if not self.args and not self.interactive:
-            if not self.default_to_interactive:
-                self.usage("either -i or an action argument is required")
-            self.interactive = 1
-
-        # Where's python?
-        if not self.python:
-            self.python = sys.executable
-
-        # Where's zdrun?
-        if not self.zdrun:
-            if __name__ == "__main__":
-                file = sys.argv[0]
-            else:
-                file = __file__
-            file = os.path.normpath(os.path.abspath(file))
-            dir = os.path.dirname(file)
-            self.zdrun = os.path.join(dir, "zdrun.py")
-
-    def set_schemafile(self, file):
-        self.schemafile = file
-
-
-
-class ZDCmd(cmd.Cmd):
-
-    def __init__(self, options):
-        self.options = options
-        self.prompt = self.options.prompt + ' '
-        cmd.Cmd.__init__(self)
-        self.get_status()
-        if self.zd_status:
-            m = re.search("(?m)^args=(.*)$", self.zd_status)
-            if m:
-                s = m.group(1)
-                args = eval(s, {"__builtins__": {}})
-                if args != self.options.program:
-                    print "WARNING! zdrun is managing a different program!"
-                    print "our program   =", self.options.program
-                    print "daemon's args =", args
-
-    def emptyline(self):
-        # We don't want a blank line to repeat the last command.
-        # Showing status is a nice alternative.
-        self.do_status()
-
-    def send_action(self, action):
-        """Send an action to the zdrun server and return the response.
-
-        Return None if the server is not up or any other error happened.
-        """
-        sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
-        try:
-            sock.connect(self.options.sockname)
-            sock.send(action + "\n")
-            sock.shutdown(1) # We're not writing any more
-            response = ""
-            while 1:
-                data = sock.recv(1000)
-                if not data:
-                    break
-                response += data
-            sock.close()
-            return response
-        except socket.error, msg:
-            return None
-
-    def get_status(self):
-        self.zd_up = 0
-        self.zd_pid = 0
-        self.zd_status = None
-        resp = self.send_action("status")
-        if not resp:
-            return
-        m = re.search("(?m)^application=(\d+)$", resp)
-        if not m:
-            return
-        self.zd_up = 1
-        self.zd_pid = int(m.group(1))
-        self.zd_status = resp
-
-    def awhile(self, cond, msg):
-        try:
-            self.get_status()
-            while not cond():
-                sys.stdout.write(". ")
-                sys.stdout.flush()
-                time.sleep(1)
-                self.get_status()
-        except KeyboardInterrupt:
-            print "^C"
-        else:
-            print msg % self.__dict__
-
-    def help_help(self):
-        print "help          -- Print a list of available actions."
-        print "help <action> -- Print help for <action>."
-
-    def do_EOF(self, arg):
-        print
-        return 1
-
-    def help_EOF(self):
-        print "To quit, type ^D or use the quit command."
-
-    def do_start(self, arg):
-        self.get_status()
-        if not self.zd_up:
-            args = [
-                self.options.python,
-                self.options.zdrun,
-                ]
-            args += self._get_override("-S", "schemafile")
-            args += self._get_override("-C", "configfile")
-            args += self._get_override("-b", "backofflimit")
-            args += self._get_override("-d", "daemon", flag=1)
-            args += self._get_override("-f", "forever", flag=1)
-            args += self._get_override("-s", "sockname")
-            args += self._get_override("-u", "user")
-            if self.options.umask:
-                args += self._get_override("-m", "umask",
-                                           oct(self.options.umask))
-            args += self._get_override(
-                "-x", "exitcodes", ",".join(map(str, self.options.exitcodes)))
-            args += self._get_override("-z", "directory")
-            args.extend(self.options.program)
-            if self.options.daemon:
-                flag = os.P_NOWAIT
-            else:
-                flag = os.P_WAIT
-            os.spawnvp(flag, args[0], args)
-        elif not self.zd_pid:
-            self.send_action("start")
-        else:
-            print "daemon process already running; pid=%d" % self.zd_pid
-            return
-        self.awhile(lambda: self.zd_pid,
-                    "daemon process started, pid=%(zd_pid)d")
-
-    def _get_override(self, opt, name, svalue=None, flag=0):
-        value = getattr(self.options, name)
-        if value is None:
-            return []
-        configroot = self.options.configroot
-        if configroot is not None:
-            for n, cn in self.options.names_list:
-                if n == name and cn:
-                    v = configroot
-                    for p in cn.split("."):
-                        v = getattr(v, p, None)
-                        if v is None:
-                            break
-                    if v == value: # We didn't override anything
-                        return []
-                    break
-        if flag:
-            if value:
-                args = [opt]
-            else:
-                args = []
-        else:
-            if svalue is None:
-                svalue = str(value)
-            args = [opt, svalue]
-        return args
-
-    def help_start(self):
-        print "start -- Start the daemon process."
-        print "         If it is already running, do nothing."
-
-    def do_stop(self, arg):
-        self.get_status()
-        if not self.zd_up:
-            print "daemon manager not running"
-        elif not self.zd_pid:
-            print "daemon process not running"
-        else:
-            self.send_action("stop")
-            self.awhile(lambda: not self.zd_pid, "daemon process stopped")
-
-    def help_stop(self):
-        print "stop -- Stop the daemon process."
-        print "        If it is not running, do nothing."
-
-    def do_restart(self, arg):
-        self.get_status()
-        pid = self.zd_pid
-        if not pid:
-            self.do_start(arg)
-        else:
-            self.send_action("restart")
-            self.awhile(lambda: self.zd_pid not in (0, pid),
-                        "daemon process restarted, pid=%(zd_pid)d")
-
-    def help_restart(self):
-        print "restart -- Stop and then start the daemon process."
-
-    def do_kill(self, arg):
-        if not arg:
-            sig = signal.SIGTERM
-        else:
-            try:
-                sig = int(arg)
-            except: # int() can raise any number of exceptions
-                print "invalid signal number", `arg`
-                return
-        self.get_status()
-        if not self.zd_pid:
-            print "daemon process not running"
-            return
-        print "kill(%d, %d)" % (self.zd_pid, sig)
-        try:
-            os.kill(self.zd_pid, sig)
-        except os.error, msg:
-            print "Error:", msg
-        else:
-            print "signal %d sent to process %d" % (sig, self.zd_pid)
-
-    def help_kill(self):
-        print "kill [sig] -- Send signal sig to the daemon process."
-        print "              The default signal is SIGTERM."
-
-    def do_wait(self, arg):
-        self.awhile(lambda: not self.zd_pid, "daemon process stopped")
-        self.do_status()
-
-    def help_wait(self):
-        print "wait -- Wait for the daemon process to exit."
-
-    def do_status(self, arg=""):
-        if arg not in ["", "-l"]:
-            print "status argument must be absent or -l"
-            return
-        self.get_status()
-        if not self.zd_up:
-            print "daemon manager not running"
-        elif not self.zd_pid:
-            print "daemon manager running; daemon process not running"
-        else:
-            print "program running; pid=%d" % self.zd_pid
-        if arg == "-l" and self.zd_status:
-            print self.zd_status
-
-    def help_status(self):
-        print "status [-l] -- Print status for the daemon process."
-        print "               With -l, show raw status output as well."
-
-    def do_show(self, arg):
-        if not arg:
-            arg = "options"
-        try:
-            method = getattr(self, "show_" + arg)
-        except AttributeError, err:
-            print err
-            self.help_show()
-            return
-        method()
-
-    def show_options(self):
-        print "zdctl/zdrun options:"
-        print "schemafile:  ", repr(self.options.schemafile)
-        print "configfile:  ", repr(self.options.configfile)
-        print "interactive: ", repr(self.options.interactive)
-        print "default_to_interactive:",
-        print                  repr(self.options.default_to_interactive)
-        print "zdrun:       ", repr(self.options.zdrun)
-        print "python:      ", repr(self.options.python)
-        print "program:     ", repr(self.options.program)
-        print "backofflimit:", repr(self.options.backofflimit)
-        print "daemon:      ", repr(self.options.daemon)
-        print "forever:     ", repr(self.options.forever)
-        print "sockname:    ", repr(self.options.sockname)
-        print "exitcodes:   ", repr(self.options.exitcodes)
-        print "user:        ", repr(self.options.user)
-        umask = self.options.umask
-        if not umask:
-            # Here we're just getting the current umask so we can report it:
-            umask = os.umask(0777)
-            os.umask(umask)
-        print "umask:       ", oct(umask)
-        print "directory:   ", repr(self.options.directory)
-        print "logfile:     ", repr(self.options.logfile)
-        print "hang_around: ", repr(self.options.hang_around)
-
-    def show_python(self):
-        print "Python info:"
-        version = sys.version.replace("\n", "\n              ")
-        print "Version:     ", version
-        print "Platform:    ", sys.platform
-        print "Executable:  ", repr(sys.executable)
-        print "Arguments:   ", repr(sys.argv)
-        print "Directory:   ", repr(os.getcwd())
-        print "Path:"
-        for dir in sys.path:
-            print "    " + repr(dir)
-
-    def show_all(self):
-        self.show_options()
-        print
-        self.show_python()
-
-    def help_show(self):
-        print "show options -- show zdctl options"
-        print "show python -- show Python version and details"
-        print "show all -- show all of the above"
-
-    def complete_show(self, text, *ignored):
-        options = ["options", "python", "all"]
-        return [x for x in options if x.startswith(text)]
-
-    def do_logreopen(self, arg):
-        self.do_kill(str(signal.SIGUSR2))
-
-    def help_logreopen(self):
-        print "logreopen -- Send a SIGUSR2 signal to the daemon process."
-        print "             This is designed to reopen the log file."
-
-    def do_logtail(self, arg):
-        if not arg:
-            arg = self.options.logfile
-            if not arg:
-                print "No default log file specified; use logtail <logfile>"
-                return
-        try:
-            helper = TailHelper(arg)
-            helper.tailf()
-        except KeyboardInterrupt:
-            print
-        except IOError, msg:
-            print msg
-        except OSError, msg:
-            print msg
-
-    def help_logtail(self):
-        print "logtail [logfile] -- Run tail -f on the given logfile."
-        print "                     A default file may exist."
-        print "                     Hit ^C to exit this mode."
-
-    def do_shell(self, arg):
-        if not arg:
-            arg = os.getenv("SHELL") or "/bin/sh"
-        try:
-            os.system(arg)
-        except KeyboardInterrupt:
-            print
-
-    def help_shell(self):
-        print "shell [command] -- Execute a shell command."
-        print "                   Without a command, start an interactive sh."
-        print "An alias for this command is ! [command]"
-
-    def do_reload(self, arg):
-        if arg:
-            args = arg.split()
-            if self.options.configfile:
-                args = ["-C", self.options.configfile] + args
-        else:
-            args = None
-        options = ZDCtlOptions()
-        options.positional_args_allowed = 0
-        try:
-            options.realize(args)
-        except SystemExit:
-            print "Configuration not reloaded"
-        else:
-            self.options = options
-            if self.options.configfile:
-                print "Configuration reloaded from", self.options.configfile
-            else:
-                print "Configuration reloaded without a config file"
-
-    def help_reload(self):
-        print "reload [options] -- Reload the configuration."
-        print "    Without options, this reparses the command line."
-        print "    With options, this substitutes 'options' for the"
-        print "    command line, except that if no -C option is given,"
-        print "    the last configuration file is used."
-
-    def do_foreground(self, arg):
-        self.get_status()
-        pid = self.zd_pid
-        if pid:
-            print "To run the program in the foreground, please stop it first."
-            return
-        program = " ".join(self.options.program)
-        print program
-        try:
-            os.system(program)
-        except KeyboardInterrupt:
-            print
-
-    def do_fg(self, arg):
-        self.do_foreground(arg)
-
-    def help_foreground(self):
-        print "foreground -- Run the program in the forground."
-        print "fg -- an alias for foreground."
-
-    def help_fg(self):
-        self.help_foreground()
-
-    def do_quit(self, arg):
-        self.get_status()
-        if not self.zd_up:
-            print "daemon manager not running"
-        elif not self.zd_pid:
-            print "daemon process not running; stopping daemon manager"
-            self.send_action("exit")
-            self.awhile(lambda: not self.zd_up, "daemon manager stopped")
-        else:
-            print "daemon process and daemon manager still running"
-        return 1
-
-    def help_quit(self):
-        print "quit -- Exit the zdctl shell."
-        print "        If the daemon process is not running,"
-        print "        stop the daemon manager."
-
-
-class TailHelper:
-
-    MAX_BUFFSIZE = 1024
-
-    def __init__(self, fname):
-        self.f = open(fname, 'r')
-
-    def tailf(self):
-        sz, lines = self.tail(10)
-        for line in lines:
-            sys.stdout.write(line)
-            sys.stdout.flush()
-        while 1:
-            newsz = self.fsize()
-            bytes_added = newsz - sz
-            if bytes_added < 0:
-                sz = 0
-                print "==> File truncated <=="
-                bytes_added = newsz
-            if bytes_added > 0:
-                self.f.seek(-bytes_added, 2)
-                bytes = self.f.read(bytes_added)
-                sys.stdout.write(bytes)
-                sys.stdout.flush()
-                sz = newsz
-            time.sleep(1)
-
-    def tail(self, max=10):
-        self.f.seek(0, 2)
-        pos = sz = self.f.tell()
-
-        lines = []
-        bytes = []
-        num_bytes = 0
-
-        while 1:
-            if pos == 0:
-                break
-            self.f.seek(pos)
-            byte = self.f.read(1)
-            if byte == '\n':
-                if len(lines) == max:
-                    break
-                bytes.reverse()
-                line = ''.join(bytes)
-                line and lines.append(line)
-                bytes = []
-            bytes.append(byte)
-            num_bytes = num_bytes + 1
-            if num_bytes > self.MAX_BUFFSIZE:
-                break
-            pos = pos - 1
-        lines.reverse()
-        return sz, lines
-
-    def fsize(self):
-        return os.fstat(self.f.fileno())[stat.ST_SIZE]
-
-def main(args=None, options=None, cmdclass=ZDCmd):
-    if options is None:
-        options = ZDCtlOptions()
-    options.realize(args)
-    c = cmdclass(options)
-    if options.args:
-        c.onecmd(" ".join(options.args))
-    if options.interactive:
-        try:
-            import readline
-        except ImportError:
-            pass
-        print "program:", " ".join(options.program)
-        c.do_status()
-        c.cmdloop()
-
-if __name__ == "__main__":
-    main()

Deleted: zdaemon/trunk/zdoptions.py
===================================================================
--- zdaemon/trunk/zdoptions.py	2005-10-29 23:30:35 UTC (rev 39723)
+++ zdaemon/trunk/zdoptions.py	2005-10-29 23:38:48 UTC (rev 39724)
@@ -1,411 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2003 Zope Corporation and Contributors.
-# All Rights Reserved.
-#
-# This software is subject to the provisions of the Zope Public License,
-# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
-# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
-# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
-# FOR A PARTICULAR PURPOSE.
-#
-##############################################################################
-
-"""Option processing for zdaemon and related code."""
-
-import os
-import sys
-import getopt
-
-import ZConfig
-
-class ZDOptions:
-
-    doc = None
-    progname = None
-    configfile = None
-    schemadir = None
-    schemafile = "schema.xml"
-    schema = None
-    confighandlers = None
-    configroot = None
-
-    # Class variable to control automatic processing of an <eventlog>
-    # section.  This should be the (possibly dotted) name of something
-    # accessible from configroot, typically "eventlog".
-    logsectionname = None
-    config_logger = None # The configured event logger, if any
-
-    # Class variable deciding whether positional arguments are allowed.
-    # If you want positional arguments, set this to 1 in your subclass.
-    positional_args_allowed = 0
-
-    def __init__(self):
-        self.names_list = []
-        self.short_options = []
-        self.long_options = []
-        self.options_map = {}
-        self.default_map = {}
-        self.required_map = {}
-        self.environ_map = {}
-        self.zconfig_options = []
-        self.add(None, None, "h", "help", self.help)
-        self.add("configfile", None, "C:", "configure=")
-        self.add(None, None, "X:", handler=self.zconfig_options.append)
-
-    def help(self, dummy):
-        """Print a long help message (self.doc) to stdout and exit(0).
-
-        Occurrences of "%s" in self.doc are replaced by self.progname.
-        """
-        doc = self.doc
-        if doc.find("%s") > 0:
-            doc = doc.replace("%s", self.progname)
-        print doc,
-        sys.exit(0)
-
-    def usage(self, msg):
-        """Print a brief error message to stderr and exit(2)."""
-        sys.stderr.write("Error: %s\n" % str(msg))
-        sys.stderr.write("For help, use %s -h\n" % self.progname)
-        sys.exit(2)
-
-    def remove(self,
-               name=None,               # attribute name on self
-               confname=None,           # name in ZConfig (may be dotted)
-               short=None,              # short option name
-               long=None,               # long option name
-               ):
-        """Remove all traces of name, confname, short and/or long."""
-        if name:
-            for n, cn in self.names_list[:]:
-                if n == name:
-                    self.names_list.remove((n, cn))
-            if self.default_map.has_key(name):
-                del self.default_map[name]
-            if self.required_map.has_key(name):
-                del self.required_map[name]
-        if confname:
-            for n, cn in self.names_list[:]:
-                if cn == confname:
-                    self.names_list.remove((n, cn))
-        if short:
-            key = "-" + short[0]
-            if self.options_map.has_key(key):
-                del self.options_map[key]
-        if long:
-            key = "--" + long
-            if key[-1] == "=":
-                key = key[:-1]
-            if self.options_map.has_key(key):
-                del self.options_map[key]
-
-    def add(self,
-            name=None,                  # attribute name on self
-            confname=None,              # name in ZConfig (may be dotted)
-            short=None,                 # short option name
-            long=None,                  # long option name
-            handler=None,               # handler (defaults to string)
-            default=None,               # default value
-            required=None,              # message if not provided
-            flag=None,                  # if not None, flag value
-            env=None,                   # if not None, environment variable
-            ):
-        """Add information about a configuration option.
-
-        This can take several forms:
-
-        add(name, confname)
-            Configuration option 'confname' maps to attribute 'name'
-        add(name, None, short, long)
-            Command line option '-short' or '--long' maps to 'name'
-        add(None, None, short, long, handler)
-            Command line option calls handler
-        add(name, None, short, long, handler)
-            Assign handler return value to attribute 'name'
-
-        In addition, one of the following keyword arguments may be given:
-
-        default=...  -- if not None, the default value
-        required=... -- if nonempty, an error message if no value provided
-        flag=...     -- if not None, flag value for command line option
-        env=...      -- if not None, name of environment variable that
-                        overrides the configuration file or default
-        """
-
-        if flag is not None:
-            if handler is not None:
-                raise ValueError("use at most one of flag= and handler=")
-            if not long and not short:
-                raise ValueError("flag= requires a command line flag")
-            if short and short.endswith(":"):
-                raise ValueError("flag= requires a command line flag")
-            if long and long.endswith("="):
-                raise ValueError("flag= requires a command line flag")
-            handler = lambda arg, flag=flag: flag
-
-        if short and long:
-            if short.endswith(":") != long.endswith("="):
-                raise ValueError("inconsistent short/long options: %r %r" % (
-                    short, long))
-
-        if short:
-            if short[0] == "-":
-                raise ValueError("short option should not start with '-'")
-            key, rest = short[:1], short[1:]
-            if rest not in ("", ":"):
-                raise ValueError("short option should be 'x' or 'x:'")
-            key = "-" + key
-            if self.options_map.has_key(key):
-                raise ValueError("duplicate short option key '%s'" % key)
-            self.options_map[key] = (name, handler)
-            self.short_options.append(short)
-
-        if long:
-            if long[0] == "-":
-                raise ValueError("long option should not start with '-'")
-            key = long
-            if key[-1] == "=":
-                key = key[:-1]
-            key = "--" + key
-            if self.options_map.has_key(key):
-                raise ValueError("duplicate long option key '%s'" % key)
-            self.options_map[key] = (name, handler)
-            self.long_options.append(long)
-
-        if env:
-            self.environ_map[env] = (name, handler)
-
-        if name:
-            if not hasattr(self, name):
-                setattr(self, name, None)
-            self.names_list.append((name, confname))
-            if default is not None:
-                self.default_map[name] = default
-            if required:
-                self.required_map[name] = required
-
-    def realize(self, args=None, progname=None, doc=None,
-                raise_getopt_errs=True):
-        """Realize a configuration.
-
-        Optional arguments:
-
-        args     -- the command line arguments, less the program name
-                    (default is sys.argv[1:])
-
-        progname -- the program name (default is sys.argv[0])
-
-        doc      -- usage message (default is __main__.__doc__)
-        """
-
-         # Provide dynamic default method arguments
-        if args is None:
-            try:
-                args = sys.argv[1:]
-            except AttributeError:
-                args = ()
-
-        if progname is None:
-            try:
-                progname = sys.argv[0]
-            except (AttributeError, IndexError):
-                progname = 'zope'
-
-        if doc is None:
-            import __main__
-            doc = __main__.__doc__
-        self.progname = progname
-        self.doc = doc
-
-        self.options = []
-        self.args = []
-
-        # Call getopt
-        try:
-            self.options, self.args = getopt.getopt(
-                args, "".join(self.short_options), self.long_options)
-        except getopt.error, msg:
-            if raise_getopt_errs:
-                self.usage(msg)
-
-        # Check for positional args
-        if self.args and not self.positional_args_allowed:
-            self.usage("positional arguments are not supported")
-
-        # Process options returned by getopt
-        for opt, arg in self.options:
-            name, handler = self.options_map[opt]
-            if handler is not None:
-                try:
-                    arg = handler(arg)
-                except ValueError, msg:
-                    self.usage("invalid value for %s %r: %s" % (opt, arg, msg))
-            if name and arg is not None:
-                if getattr(self, name) is not None:
-                    self.usage("conflicting command line option %r" % opt)
-                setattr(self, name, arg)
-
-        # Process environment variables
-        for envvar in self.environ_map.keys():
-            name, handler = self.environ_map[envvar]
-            if name and getattr(self, name, None) is not None:
-                continue
-            if os.environ.has_key(envvar):
-                value = os.environ[envvar]
-                if handler is not None:
-                    try:
-                        value = handler(value)
-                    except ValueError, msg:
-                        self.usage("invalid environment value for %s %r: %s"
-                                   % (envvar, value, msg))
-                if name and value is not None:
-                    setattr(self, name, value)
-
-        if self.configfile is None:
-            self.configfile = self.default_configfile()
-        if self.zconfig_options and self.configfile is None:
-            self.usage("configuration overrides (-X) cannot be used"
-                       " without a configuration file")
-        if self.configfile is not None:
-            # Process config file
-            self.load_schema()
-            try:
-                self.load_configfile()
-            except ZConfig.ConfigurationError, msg:
-                self.usage(str(msg))
-
-        # Copy config options to attributes of self.  This only fills
-        # in options that aren't already set from the command line.
-        for name, confname in self.names_list:
-            if confname and getattr(self, name) is None:
-                parts = confname.split(".")
-                obj = self.configroot
-                for part in parts:
-                    if obj is None:
-                        break
-                    # Here AttributeError is not a user error!
-                    obj = getattr(obj, part)
-                setattr(self, name, obj)
-
-        # Process defaults
-        for name, value in self.default_map.items():
-            if getattr(self, name) is None:
-                setattr(self, name, value)
-
-        # Process required options
-        for name, message in self.required_map.items():
-            if getattr(self, name) is None:
-                self.usage(message)
-
-        if self.logsectionname:
-            self.load_logconf(self.logsectionname)
-
-    def default_configfile(self):
-        """Return the name of the default config file, or None."""
-        # This allows a default configuration file to be used without
-        # affecting the -C command line option; setting self.configfile
-        # before calling realize() makes the -C option unusable since
-        # then realize() thinks it has already seen the option.  If no
-        # -C is used, realize() will call this method to try to locate
-        # a configuration file.
-        return None
-
-    def load_schema(self):
-        if self.schema is None:
-            # Load schema
-            if self.schemadir is None:
-                self.schemadir = os.path.dirname(__file__)
-            self.schemafile = os.path.join(self.schemadir, self.schemafile)
-            self.schema = ZConfig.loadSchema(self.schemafile)
-
-    def load_configfile(self):
-        self.configroot, self.confighandlers = \
-            ZConfig.loadConfig(self.schema, self.configfile,
-                               self.zconfig_options)
-
-    def load_logconf(self, sectname="eventlog"):
-        parts = sectname.split(".")
-        obj = self.configroot
-        for p in parts:
-            if obj == None:
-                break
-            obj = getattr(obj, p)
-        self.config_logger = obj
-        if obj is not None:
-            obj.startup()
-
-
-class RunnerOptions(ZDOptions):
-
-    uid = gid = None
-
-    def __init__(self):
-        ZDOptions.__init__(self)
-        self.add("backofflimit", "runner.backoff_limit",
-                 "b:", "backoff-limit=", int, default=10)
-        self.add("daemon", "runner.daemon", "d", "daemon", flag=1, default=0)
-        self.add("forever", "runner.forever", "f", "forever",
-                 flag=1, default=0)
-        self.add("sockname", "runner.socket_name", "s:", "socket-name=",
-                 ZConfig.datatypes.existing_dirpath, default="zdsock")
-        self.add("exitcodes", "runner.exit_codes", "x:", "exit-codes=",
-                 list_of_ints, default=[0, 2])
-        self.add("user", "runner.user", "u:", "user=")
-        self.add("umask", "runner.umask", "m:", "umask=", octal_type,
-                 default=022)
-        self.add("directory", "runner.directory", "z:", "directory=",
-                 ZConfig.datatypes.existing_directory)
-        self.add("hang_around", "runner.hang_around", default=0)
-
-    def realize(self, *args, **kwds):
-        ZDOptions.realize(self, *args, **kwds)
-
-        # Additional checking of user option; set uid and gid
-        if self.user is not None:
-            import pwd
-            try:
-                uid = int(self.user)
-            except ValueError:
-                try:
-                    pwrec = pwd.getpwnam(self.user)
-                except KeyError:
-                    self.usage("username %r not found" % self.user)
-                uid = pwrec[2]
-            else:
-                try:
-                    pwrec = pwd.getpwuid(uid)
-                except KeyError:
-                    self.usage("uid %r not found" % self.user)
-            gid = pwrec[3]
-            self.uid = uid
-            self.gid = gid
-
-
-# ZConfig datatype
-
-def list_of_ints(arg):
-    if not arg:
-        return []
-    else:
-        return map(int, arg.split(","))
-
-def octal_type(arg):
-    return int(arg, 8)
-
-
-def _test():
-    # Stupid test program
-    z = ZDOptions()
-    z.add("program", "zdctl.program", "p:", "program=")
-    print z.names_list
-    z.realize()
-    names = z.names_list[:]
-    names.sort()
-    for name, confname in names:
-        print "%-20s = %.56r" % (name, getattr(z, name))
-
-if __name__ == "__main__":
-    __file__ = sys.argv[0]
-    _test()

Deleted: zdaemon/trunk/zdrun.py
===================================================================
--- zdaemon/trunk/zdrun.py	2005-10-29 23:30:35 UTC (rev 39723)
+++ zdaemon/trunk/zdrun.py	2005-10-29 23:38:48 UTC (rev 39724)
@@ -1,719 +0,0 @@
-#!python
-##############################################################################
-#
-# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
-# All Rights Reserved.
-#
-# This software is subject to the provisions of the Zope Public License,
-# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
-# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
-# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
-# FOR A PARTICULAR PURPOSE
-#
-##############################################################################
-"""zrdun -- run an application as a daemon.
-
-Usage: python zrdun.py [zrdun-options] program [program-arguments]
-
-Options:
--C/--configure URL -- configuration file or URL
--S/--schema XML Schema -- XML schema for configuration file
--b/--backoff-limit SECONDS -- set backoff limit to SECONDS (default 10)
--d/--daemon -- run as a proper daemon; fork a subprocess, setsid(), etc.
--f/--forever -- run forever (by default, exit when backoff limit is exceeded)
--h/--help -- print this usage message and exit
--s/--socket-name SOCKET -- Unix socket name for client (default "zdsock")
--u/--user USER -- run as this user (or numeric uid)
--m/--umask UMASK -- use this umask for daemon subprocess (default is 022)
--t/--transcript FILE -- transript of output from daemon-mode program
--x/--exit-codes LIST -- list of fatal exit codes (default "0,2")
--z/--directory DIRECTORY -- directory to chdir to when using -d (default off)
-program [program-arguments] -- an arbitrary application to run
-
-This daemon manager has two purposes: it restarts the application when
-it dies, and (when requested to do so with the -d option) it runs the
-application in the background, detached from the foreground tty
-session that started it (if any).
-
-Exit codes: if at any point the application exits with an exit status
-listed by the -x option, it is not restarted.  Any other form of
-termination (either being killed by a signal or exiting with an exit
-status not listed in the -x option) causes it to be restarted.
-
-Backoff limit: when the application exits (nearly) immediately after a
-restart, the daemon manager starts slowing down by delaying between
-restarts.  The delay starts at 1 second and is increased by one on
-each restart up to the backoff limit given by the -b option; it is
-reset when the application runs for more than the backoff limit
-seconds.  By default, when the delay reaches the backoff limit, the
-daemon manager exits (under the assumption that the application has a
-persistent fault).  The -f (forever) option prevents this exit; use it
-when you expect that a temporary external problem (such as a network
-outage or an overfull disk) may prevent the application from starting
-but you want the daemon manager to keep trying.
-"""
-
-"""
-XXX TO DO
-
-- Finish OO design -- use multiple classes rather than folding
-  everything into one class.
-
-- Add unit tests.
-
-- Add doc strings.
-
-"""
-
-import os
-import sys
-import time
-import errno
-import logging
-import socket
-import select
-import signal
-from stat import ST_MODE
-
-if __name__ == "__main__":
-    # Add the parent of the script directory to the module search path
-    # (but only when the script is run from inside the zdaemon package)
-    from os.path import dirname, basename, abspath, normpath
-    scriptdir = dirname(normpath(abspath(sys.argv[0])))
-    if basename(scriptdir).lower() == "zdaemon":
-        sys.path.append(dirname(scriptdir))
-
-from zdaemon.zdoptions import RunnerOptions
-
-
-class ZDRunOptions(RunnerOptions):
-
-    positional_args_allowed = 1
-    logsectionname = "runner.eventlog"
-    program = None
-
-    def __init__(self):
-        RunnerOptions.__init__(self)
-        self.add("schemafile", short="S:", long="schema=",
-                 default="schema.xml",
-                 handler=self.set_schemafile)
-        self.add("transcript", "runner.transcript", "t:", "transcript=",
-                 default="/dev/null")
-
-    def set_schemafile(self, file):
-        self.schemafile = file
-
-    def realize(self, *args, **kwds):
-        RunnerOptions.realize(self, *args, **kwds)
-        if self.args:
-            self.program = self.args
-        if not self.program:
-            self.usage("no program specified (use -C or positional args)")
-        if self.sockname:
-            # Convert socket name to absolute path
-            self.sockname = os.path.abspath(self.sockname)
-        if self.config_logger is None:
-            # This doesn't perform any configuration of the logging
-            # package, but that's reasonable in this case.
-            self.logger = logging.getLogger()
-        else:
-            self.logger = self.config_logger()
-
-    def load_logconf(self, sectname):
-        """Load alternate eventlog if the specified section isn't present."""
-        RunnerOptions.load_logconf(self, sectname)
-        if self.config_logger is None and sectname != "eventlog":
-            RunnerOptions.load_logconf(self, "eventlog")
-
-
-class Subprocess:
-
-    """A class to manage a subprocess."""
-
-    # Initial state; overridden by instance variables
-    pid = 0 # Subprocess pid; 0 when not running
-    lasttime = 0 # Last time the subprocess was started; 0 if never
-
-    def __init__(self, options, args=None):
-        """Constructor.
-
-        Arguments are a ZDRunOptions instance and a list of program
-        arguments; the latter's first item must be the program name.
-        """
-        if args is None:
-            args = options.args
-        if not args:
-            options.usage("missing 'program' argument")
-        self.options = options
-        self.args = args
-        self._set_filename(args[0])
-
-    def _set_filename(self, program):
-        """Internal: turn a program name into a file name, using $PATH."""
-        if "/" in program:
-            filename = program
-            try:
-                st = os.stat(filename)
-            except os.error:
-                self.options.usage("can't stat program %r" % program)
-        else:
-            path = get_path()
-            for dir in path:
-                filename = os.path.join(dir, program)
-                try:
-                    st = os.stat(filename)
-                except os.error:
-                    continue
-                mode = st[ST_MODE]
-                if mode & 0111:
-                    break
-            else:
-                self.options.usage("can't find program %r on PATH %s" %
-                                   (program, path))
-        if not os.access(filename, os.X_OK):
-            self.options.usage("no permission to run program %r" % filename)
-        self.filename = filename
-
-    def spawn(self):
-        """Start the subprocess.  It must not be running already.
-
-        Return the process id.  If the fork() call fails, return 0.
-        """
-        assert not self.pid
-        self.lasttime = time.time()
-        try:
-            pid = os.fork()
-        except os.error:
-            return 0
-        if pid != 0:
-            # Parent
-            self.pid = pid
-            self.options.logger.info("spawned process pid=%d" % pid)
-            return pid
-        else:
-            # Child
-            try:
-                # Close file descriptors except std{in,out,err}.
-                # XXX We don't know how many to close; hope 100 is plenty.
-                for i in range(3, 100):
-                    try:
-                        os.close(i)
-                    except os.error:
-                        pass
-                try:
-                    os.execv(self.filename, self.args)
-                except os.error, err:
-                    sys.stderr.write("can't exec %r: %s\n" %
-                                     (self.filename, err))
-            finally:
-                os._exit(127)
-            # Does not return
-
-    def kill(self, sig):
-        """Send a signal to the subprocess.  This may or may not kill it.
-
-        Return None if the signal was sent, or an error message string
-        if an error occurred or if the subprocess is not running.
-        """
-        if not self.pid:
-            return "no subprocess running"
-        try:
-            os.kill(self.pid, sig)
-        except os.error, msg:
-            return str(msg)
-        return None
-
-    def setstatus(self, sts):
-        """Set process status returned by wait() or waitpid().
-
-        This simply notes the fact that the subprocess is no longer
-        running by setting self.pid to 0.
-        """
-        self.pid = 0
-
-
-class Daemonizer:
-
-    def main(self, args=None):
-        self.options = ZDRunOptions()
-        self.options.realize(args)
-        self.logger = self.options.logger
-        self.set_uid()
-        self.run()
-
-    def set_uid(self):
-        if self.options.uid is None:
-            return
-        uid = os.geteuid()
-        if uid != 0 and uid != self.options.uid:
-            self.options.usage("only root can use -u USER to change users")
-        os.setgid(self.options.gid)
-        os.setuid(self.options.uid)
-
-    def run(self):
-        self.proc = Subprocess(self.options)
-        self.opensocket()
-        try:
-            self.setsignals()
-            if self.options.daemon:
-                self.daemonize()
-            self.runforever()
-        finally:
-            try:
-                os.unlink(self.options.sockname)
-            except os.error:
-                pass
-
-    mastersocket = None
-    commandsocket = None
-
-    def opensocket(self):
-        sockname = self.options.sockname
-        tempname = "%s.%d" % (sockname, os.getpid())
-        self.unlink_quietly(tempname)
-        while 1:
-            sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
-            try:
-                sock.bind(tempname)
-                os.chmod(tempname, 0700)
-                try:
-                    os.link(tempname, sockname)
-                    break
-                except os.error:
-                    # Lock contention, or stale socket.
-                    self.checkopen()
-                    # Stale socket -- delete, sleep, and try again.
-                    msg = "Unlinking stale socket %s; sleep 1" % sockname
-                    sys.stderr.write(msg + "\n")
-                    self.logger.warn(msg)
-                    self.unlink_quietly(sockname)
-                    sock.close()
-                    time.sleep(1)
-                    continue
-            finally:
-                self.unlink_quietly(tempname)
-        sock.listen(1)
-        sock.setblocking(0)
-        self.mastersocket = sock
-
-    def unlink_quietly(self, filename):
-        try:
-            os.unlink(filename)
-        except os.error:
-            pass
-
-    def checkopen(self):
-        s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
-        try:
-            s.connect(self.options.sockname)
-            s.send("status\n")
-            data = s.recv(1000)
-            s.close()
-        except socket.error:
-            pass
-        else:
-            while data.endswith("\n"):
-                data = data[:-1]
-            msg = ("Another zrdun is already up using socket %r:\n%s" %
-                   (self.options.sockname, data))
-            sys.stderr.write(msg + "\n")
-            self.logger.critical(msg)
-            sys.exit(1)
-
-    def setsignals(self):
-        signal.signal(signal.SIGTERM, self.sigexit)
-        signal.signal(signal.SIGHUP, self.sigexit)
-        signal.signal(signal.SIGINT, self.sigexit)
-        signal.signal(signal.SIGCHLD, self.sigchild)
-
-    def sigexit(self, sig, frame):
-        self.logger.critical("daemon manager killed by %s" % signame(sig))
-        sys.exit(1)
-
-    waitstatus = None
-
-    def sigchild(self, sig, frame):
-        try:
-            pid, sts = os.waitpid(-1, os.WNOHANG)
-        except os.error:
-            return
-        if pid:
-            self.waitstatus = pid, sts
-
-    def daemonize(self):
-
-        # To daemonize, we need to become the leader of our own session
-        # (process) group.  If we do not, signals sent to our
-        # parent process will also be sent to us.   This might be bad because
-        # signals such as SIGINT can be sent to our parent process during
-        # normal (uninteresting) operations such as when we press Ctrl-C in the
-        # parent terminal window to escape from a logtail command.
-        # To disassociate ourselves from our parent's session group we use
-        # os.setsid.  It means "set session id", which has the effect of
-        # disassociating a process from is current session and process group
-        # and setting itself up as a new session leader.
-        #
-        # Unfortunately we cannot call setsid if we're already a session group
-        # leader, so we use "fork" to make a copy of ourselves that is
-        # guaranteed to not be a session group leader.
-        #
-        # We also change directories, set stderr and stdout to null, and
-        # change our umask.
-        #
-        # This explanation was (gratefully) garnered from
-        # http://www.hawklord.uklinux.net/system/daemons/d3.htm
-
-        pid = os.fork()
-        if pid != 0:
-            # Parent
-            self.logger.debug("daemon manager forked; parent exiting")
-            os._exit(0)
-        # Child
-        self.logger.info("daemonizing the process")
-        if self.options.directory:
-            try:
-                os.chdir(self.options.directory)
-            except os.error, err:
-                self.logger.warn("can't chdir into %r: %s"
-                                 % (self.options.directory, err))
-            else:
-                self.logger.info("set current directory: %r"
-                                 % self.options.directory)
-        os.close(0)
-        sys.stdin = sys.__stdin__ = open("/dev/null")
-        os.close(1)
-        sys.stdout = sys.__stdout__ = open(self.options.transcript, "a", 0)
-        os.close(2)
-        sys.stderr = sys.__stderr__ = open(self.options.transcript, "a", 0)
-        os.setsid()
-        os.umask(self.options.umask)
-        # XXX Stevens, in his Advanced Unix book, section 13.3 (page
-        # 417) recommends calling umask(0) and closing unused
-        # file descriptors.  In his Network Programming book, he
-        # additionally recommends ignoring SIGHUP and forking again
-        # after the setsid() call, for obscure SVR4 reasons.
-
-    mood = 1 # 1: up, 0: down, -1: suicidal
-    delay = 0 # If nonzero, delay starting or killing until this time
-    killing = 0 # If true, send SIGKILL when delay expires
-    proc = None # Subprocess instance
-
-    def runforever(self):
-        self.logger.info("daemon manager started")
-        min_mood = not self.options.hang_around
-        while self.mood >= min_mood or self.proc.pid:
-            if self.mood > 0 and not self.proc.pid and not self.delay:
-                pid = self.proc.spawn()
-                if not pid:
-                    # Can't fork.  Try again later...
-                    self.delay = time.time() + self.backofflimit
-            if self.waitstatus:
-                self.reportstatus()
-            r, w, x = [self.mastersocket], [], []
-            if self.commandsocket:
-                r.append(self.commandsocket)
-            timeout = self.options.backofflimit
-            if self.delay:
-                timeout = max(0, min(timeout, self.delay - time.time()))
-                if timeout <= 0:
-                    self.delay = 0
-                    if self.killing and self.proc.pid:
-                        self.proc.kill(signal.SIGKILL)
-                        self.delay = time.time() + self.options.backofflimit
-            try:
-                r, w, x = select.select(r, w, x, timeout)
-            except select.error, err:
-                if err[0] != errno.EINTR:
-                    raise
-                r = w = x = []
-            if self.waitstatus:
-                self.reportstatus()
-            if self.commandsocket and self.commandsocket in r:
-                try:
-                    self.dorecv()
-                except socket.error, msg:
-                    self.logger.exception("socket.error in dorecv(): %s"
-                                          % str(msg))
-                    self.commandsocket = None
-            if self.mastersocket in r:
-                try:
-                    self.doaccept()
-                except socket.error, msg:
-                    self.logger.exception("socket.error in doaccept(): %s"
-                                          % str(msg))
-                    self.commandsocket = None
-        self.logger.info("Exiting")
-        sys.exit(0)
-
-    def reportstatus(self):
-        pid, sts = self.waitstatus
-        self.waitstatus = None
-        es, msg = decode_wait_status(sts)
-        msg = "pid %d: " % pid + msg
-        if pid != self.proc.pid:
-            msg = "unknown " + msg
-            self.logger.warn(msg)
-        else:
-            killing = self.killing
-            if killing:
-                self.killing = 0
-                self.delay = 0
-            else:
-                self.governor()
-            self.proc.setstatus(sts)
-            if es in self.options.exitcodes and not killing:
-                msg = msg + "; exiting now"
-                self.logger.info(msg)
-                sys.exit(es)
-            self.logger.info(msg)
-
-    backoff = 0
-
-    def governor(self):
-        # Back off if respawning too frequently
-        now = time.time()
-        if not self.proc.lasttime:
-            pass
-        elif now - self.proc.lasttime < self.options.backofflimit:
-            # Exited rather quickly; slow down the restarts
-            self.backoff += 1
-            if self.backoff >= self.options.backofflimit:
-                if self.options.forever:
-                    self.backoff = self.options.backofflimit
-                else:
-                    self.logger.critical("restarting too frequently; quit")
-                    sys.exit(1)
-            self.logger.info("sleep %s to avoid rapid restarts" % self.backoff)
-            self.delay = now + self.backoff
-        else:
-            # Reset the backoff timer
-            self.backoff = 0
-            self.delay = 0
-
-    def doaccept(self):
-        if self.commandsocket:
-            # Give up on previous command socket!
-            self.sendreply("Command superseded by new command")
-            self.commandsocket.close()
-            self.commandsocket = None
-        self.commandsocket, addr = self.mastersocket.accept()
-        self.commandbuffer = ""
-
-    def dorecv(self):
-        data = self.commandsocket.recv(1000)
-        if not data:
-            self.sendreply("Command not terminated by newline")
-            self.commandsocket.close()
-            self.commandsocket = None
-        self.commandbuffer += data
-        if "\n" in self.commandbuffer:
-            self.docommand()
-            self.commandsocket.close()
-            self.commandsocket = None
-        elif len(self.commandbuffer) > 10000:
-            self.sendreply("Command exceeds 10 KB")
-            self.commandsocket.close()
-            self.commandsocket = None
-
-    def docommand(self):
-        lines = self.commandbuffer.split("\n")
-        args = lines[0].split()
-        if not args:
-            self.sendreply("Empty command")
-            return
-        command = args[0]
-        methodname = "cmd_" + command
-        method = getattr(self, methodname, None)
-        if method:
-            method(args)
-        else:
-            self.sendreply("Unknown command %r; 'help' for a list" % args[0])
-
-    def cmd_start(self, args):
-        self.mood = 1 # Up
-        self.backoff = 0
-        self.delay = 0
-        self.killing = 0
-        if not self.proc.pid:
-            self.proc.spawn()
-            self.sendreply("Application started")
-        else:
-            self.sendreply("Application already started")
-
-    def cmd_stop(self, args):
-        self.mood = 0 # Down
-        self.backoff = 0
-        self.delay = 0
-        self.killing = 0
-        if self.proc.pid:
-            self.proc.kill(signal.SIGTERM)
-            self.sendreply("Sent SIGTERM")
-            self.killing = 1
-            self.delay = time.time() + self.options.backofflimit
-        else:
-            self.sendreply("Application already stopped")
-
-    def cmd_restart(self, args):
-        self.mood = 1 # Up
-        self.backoff = 0
-        self.delay = 0
-        self.killing = 0
-        if self.proc.pid:
-            self.proc.kill(signal.SIGTERM)
-            self.sendreply("Sent SIGTERM; will restart later")
-            self.killing = 1
-            self.delay = time.time() + self.options.backofflimit
-        else:
-            self.proc.spawn()
-            self.sendreply("Application started")
-
-    def cmd_exit(self, args):
-        self.mood = -1 # Suicidal
-        self.backoff = 0
-        self.delay = 0
-        self.killing = 0
-        if self.proc.pid:
-            self.proc.kill(signal.SIGTERM)
-            self.sendreply("Sent SIGTERM; will exit later")
-            self.killing = 1
-            self.delay = time.time() + self.options.backofflimit
-        else:
-            self.sendreply("Exiting now")
-            self.logger.info("Exiting")
-            sys.exit(0)
-
-    def cmd_kill(self, args):
-        if args[1:]:
-            try:
-                sig = int(args[1])
-            except:
-                self.sendreply("Bad signal %r" % args[1])
-                return
-        else:
-            sig = signal.SIGTERM
-        if not self.proc.pid:
-            self.sendreply("Application not running")
-        else:
-            msg = self.proc.kill(sig)
-            if msg:
-                self.sendreply("Kill %d failed: %s" % (sig, msg))
-            else:
-                self.sendreply("Signal %d sent" % sig)
-
-    def cmd_status(self, args):
-        if not self.proc.pid:
-            status = "stopped"
-        else:
-            status = "running"
-        self.sendreply("status=%s\n" % status +
-                       "now=%r\n" % time.time() +
-                       "mood=%d\n" % self.mood +
-                       "delay=%r\n" % self.delay +
-                       "backoff=%r\n" % self.backoff +
-                       "lasttime=%r\n" % self.proc.lasttime +
-                       "application=%r\n" % self.proc.pid +
-                       "manager=%r\n" % os.getpid() +
-                       "backofflimit=%r\n" % self.options.backofflimit +
-                       "filename=%r\n" % self.proc.filename +
-                       "args=%r\n" % self.proc.args)
-
-    def cmd_help(self, args):
-        self.sendreply(
-            "Available commands:\n"
-            "  help -- return command help\n"
-            "  status -- report application status (default command)\n"
-            "  kill [signal] -- send a signal to the application\n"
-            "                   (default signal is SIGTERM)\n"
-            "  start -- start the application if not already running\n"
-            "  stop -- stop the application if running\n"
-            "          (the daemon manager keeps running)\n"
-            "  restart -- stop followed by start\n"
-            "  exit -- stop the application and exit\n"
-            )
-
-    def sendreply(self, msg):
-        try:
-            if not msg.endswith("\n"):
-                msg = msg + "\n"
-            if hasattr(self.commandsocket, "sendall"):
-                self.commandsocket.sendall(msg)
-            else:
-                # This is quadratic, but msg is rarely more than 100 bytes :-)
-                while msg:
-                    sent = self.commandsocket.send(msg)
-                    msg = msg[sent:]
-        except socket.error, msg:
-            self.logger.warn("Error sending reply: %s" % str(msg))
-
-
-# Helpers for dealing with signals and exit status
-
-def decode_wait_status(sts):
-    """Decode the status returned by wait() or waitpid().
-
-    Return a tuple (exitstatus, message) where exitstatus is the exit
-    status, or -1 if the process was killed by a signal; and message
-    is a message telling what happened.  It is the caller's
-    responsibility to display the message.
-    """
-    if os.WIFEXITED(sts):
-        es = os.WEXITSTATUS(sts) & 0xffff
-        msg = "exit status %s" % es
-        return es, msg
-    elif os.WIFSIGNALED(sts):
-        sig = os.WTERMSIG(sts)
-        msg = "terminated by %s" % signame(sig)
-        if hasattr(os, "WCOREDUMP"):
-            iscore = os.WCOREDUMP(sts)
-        else:
-            iscore = sts & 0x80
-        if iscore:
-            msg += " (core dumped)"
-        return -1, msg
-    else:
-        msg = "unknown termination cause 0x%04x" % sts
-        return -1, msg
-
-_signames = None
-
-def signame(sig):
-    """Return a symbolic name for a signal.
-
-    Return "signal NNN" if there is no corresponding SIG name in the
-    signal module.
-    """
-
-    if _signames is None:
-        _init_signames()
-    return _signames.get(sig) or "signal %d" % sig
-
-def _init_signames():
-    global _signames
-    d = {}
-    for k, v in signal.__dict__.items():
-        k_startswith = getattr(k, "startswith", None)
-        if k_startswith is None:
-            continue
-        if k_startswith("SIG") and not k_startswith("SIG_"):
-            d[v] = k
-    _signames = d
-
-def get_path():
-    """Return a list corresponding to $PATH, or a default."""
-    path = ["/bin", "/usr/bin", "/usr/local/bin"]
-    if os.environ.has_key("PATH"):
-        p = os.environ["PATH"]
-        if p:
-            path = p.split(os.pathsep)
-    return path
-
-# Main program
-def main(args=None):
-    assert os.name == "posix", "This code makes many Unix-specific assumptions"
-
-    d = Daemonizer()
-    d.main(args)
-
-if __name__ == "__main__":
-    main()



More information about the Zope-Checkins mailing list