[Zope3-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
- <eventlog> 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: <runner> defines options unique
- zdctl.py and zdrun.py, and <eventlog> 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 Zope3-Checkins
mailing list