[Zope-Checkins] CVS: ZODB3/zdaemon - zdaemon.py:1.15

Guido van Rossum guido@python.org
Wed, 13 Nov 2002 12:51:15 -0500


Update of /cvs-repository/ZODB3/zdaemon
In directory cvs.zope.org:/tmp/cvs-serv14881

Modified Files:
	zdaemon.py 
Log Message:
Make a start with moving functionality from the Daemonizer class into
separate classes.  Do the simplest one first: the option parsing stuff
is now a separate class, Options.  It has docstrings. :-)


=== ZODB3/zdaemon/zdaemon.py 1.14 => 1.15 ===
--- ZODB3/zdaemon/zdaemon.py:1.14	Tue Nov 12 19:47:31 2002
+++ ZODB3/zdaemon/zdaemon.py	Wed Nov 13 12:51:14 2002
@@ -79,9 +79,17 @@
 
 import zLOG
 
-class Daemonizer:
+class Options:
+
+    """A class to parse and hold the command line options.
+
+    Options are represented by instance attributes.
+    Positional arguments are represented by the args attribute.
+    """
 
-    # Settable options
+    progname = "zdaemon.py"             # Program name for usage message
+
+    # Options we know of, and their defaults
     backofflimit = 10                   # -b SECONDS
     isclient = 0                        # -c
     daemon = 0                          # -d
@@ -90,31 +98,37 @@
     exitcodes = [0, 2]                  # -x LIST
     zdirectory = "/"                    # -z DIRECTORY
 
-    def __init__(self):
-        self.filename = None
-        self.args = []
+    args = []                           # Positional arguments
 
-    def main(self, args=None):
-        self.prepare(args)
-        self.run()
+    def __init__(self, args=None, progname=None):
+        """Constructor.
+
+        Optional arguments:
+
+        args     -- the command line arguments, less the program name
+                    (default is sys.argv[1:] at the time of call)
+
+        progname -- the program name (default "zdaemon.py")
+        """
 
-    def prepare(self, args=None):
         if args is None:
             args = sys.argv[1:]
-        self.blather("args=%s" % repr(args))
+        if progname:
+            self.progname = progname
         try:
-            opts, args = getopt.getopt(args, "b:cdfhs:x:z:")
+            self.opts, self.args = getopt.getopt(args, "b:cdfhs:x:z:")
         except getopt.error, msg:
             self.usage(str(msg))
-        self.parseoptions(opts)
-        if self.isclient:
-            self.setcommand(args)
-        else:
-            self.setprogram(args)
-
-    def parseoptions(self, opts):
-        self.info("opts=%s" % repr(opts))
-        for o, a in opts:
+        self._interpret_options()
+
+    def _interpret_options(self):
+        """Internal: interpret the options parsed by getopt.getopt().
+
+        This sets the various instance variables overriding the defaults.
+
+        When -h is detected, print the module docstring to stdout and exit(0).
+        """
+        for o, a in self.opts:
             # Alphabetical order please!
             if o == "-b":
                 try:
@@ -129,7 +143,7 @@
                 self.forever += 1
             if o == "-h":
                 print __doc__,
-                self.exit()
+                sys.exit()
             if o == "-s":
                 self.sockname = a
             if o == "-x":
@@ -143,6 +157,31 @@
             if o == "-z":
                 self.zdirectory = a
 
+    def usage(self, msg):
+        """Write an 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)
+
+class Daemonizer:
+
+    def main(self, args=None):
+        self.prepare(args)
+        self.run()
+
+    def prepare(self, args=None):
+        self.opts = Options(args)
+        if self.opts.isclient:
+            self.setcommand(self.opts.args)
+        else:
+            self.setprogram(self.opts.args)
+
+    def errwrite(self, msg):
+        sys.stderr.write(msg)
+
+    def exit(self, sts=0):
+        sys.exit(sts)
+
     def setcommand(self, args):
         if not args:
             self.command = "status"
@@ -152,9 +191,10 @@
     def sendcommand(self):
         sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
         try:
-            sock.connect(self.sockname)
+            sock.connect(self.opts.sockname)
         except socket.error, msg:
-            self.errwrite("Can't connect to %r: %s\n" % (self.sockname, msg))
+            self.errwrite("Can't connect to %r: %s\n" %
+                          (self.opts.sockname, msg))
             self.exit(1)
         sock.send(self.command + "\n")
         sock.shutdown(1) # We're not writing any more
@@ -173,10 +213,11 @@
 
     def setprogram(self, args):
         if not args:
-            self.usage("missing 'program' argument")
+            self.opts.usage("missing 'program' argument")
         self.filename = self.checkcommand(args[0])
         self.args = args # A list of strings like for execvp()
-        self.info("filename=%r; args=%r" % (self.filename, self.args))
+        self.info("opts=%r; filename=%r; args=%r" %
+                  (self.opts.opts, self.filename, self.args))
 
     def checkcommand(self, command):
         if "/" in command:
@@ -184,7 +225,7 @@
             try:
                 st = os.stat(filename)
             except os.error:
-                self.usage("can't stat program %r" % command)
+                self.opts.usage("can't stat program %r" % command)
         else:
             path = self.getpath()
             for dir in path:
@@ -197,10 +238,10 @@
                 if mode & 0111:
                     break
             else:
-                self.usage("can't find program %r on PATH %s" %
+                self.opts.usage("can't find program %r on PATH %s" %
                            (command, path))
         if not os.access(filename, os.X_OK):
-            self.usage("no permission to run program %r" % filename)
+            self.opts.usage("no permission to run program %r" % filename)
         return filename
 
     def getpath(self):
@@ -212,18 +253,18 @@
         return path
 
     def run(self):
-        if self.isclient:
+        if self.opts.isclient:
             self.sendcommand()
             return
         self.opensocket()
         try:
             self.setsignals()
-            if self.daemon:
+            if self.opts.daemon:
                 self.daemonize()
             self.runforever()
         finally:
             try:
-                os.unlink(self.sockname)
+                os.unlink(self.opts.sockname)
             except os.error:
                 pass
 
@@ -233,14 +274,14 @@
     def opensocket(self):
         self.checkopen()
         try:
-            os.unlink(self.sockname)
+            os.unlink(self.opts.sockname)
         except os.error:
             pass
         self.mastersocket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
         oldumask = None
         try:
             oldumask = os.umask(077)
-            self.mastersocket.bind(self.sockname)
+            self.mastersocket.bind(self.opts.sockname)
         finally:
             if oldumask is not None:
                 os.umask(oldumask)
@@ -250,7 +291,7 @@
     def checkopen(self):
         s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
         try:
-            s.connect(self.sockname)
+            s.connect(self.opts.sockname)
             s.send("status\n")
             data = s.recv(1000)
             s.close()
@@ -260,7 +301,7 @@
             if not data.endswith("\n"):
                 data += "\n"
             msg = ("Another zdaemon is already up using socket %r:\n%s" %
-                   (self.sockname, data))
+                   (self.opts.sockname, data))
             self.errwrite(msg)
             self.panic(msg)
             self.exit(1)
@@ -290,14 +331,14 @@
             os._exit(0)
         # Child
         self.info("daemonizing the process")
-        if self.zdirectory:
+        if self.opts.zdirectory:
             try:
-                os.chdir(self.zdirectory)
+                os.chdir(self.opts.zdirectory)
             except os.error, err:
                 self.problem("can't chdir into %r: %s" %
-                             (self.zdirectory, err))
+                             (self.opts.zdirectory, err))
             else:
-                self.info("set current directory: %r" % self.zdirectory)
+                self.info("set current directory: %r" % self.opts.zdirectory)
         os.close(0)
         sys.stdin = sys.__stdin__ = open("/dev/null")
         os.close(1)
@@ -321,14 +362,14 @@
             r, w, x = [self.mastersocket], [], []
             if self.commandsocket:
                 r.append(self.commandsocket)
-            timeout = self.backofflimit
+            timeout = self.opts.backofflimit
             if self.delay:
                 timeout = max(0, min(timeout, self.delay - time.time()))
                 if timeout <= 0:
                     self.delay = 0
                     if self.killing and self.appid:
                         self.killapp(signal.SIGKILL)
-                        self.delay = time.time() + self.backofflimit
+                        self.delay = time.time() + self.opts.backofflimit
             try:
                 r, w, x = select.select(r, w, x, timeout)
             except select.error, err:
@@ -413,7 +454,7 @@
             self.killapp(signal.SIGTERM)
             self.sendreply("Sent SIGTERM")
             self.killing = 1
-            self.delay = time.time() + self.backofflimit
+            self.delay = time.time() + self.opts.backofflimit
         else:
             self.sendreply("Application already stopped")
 
@@ -426,7 +467,7 @@
             self.killapp(signal.SIGTERM)
             self.sendreply("Sent SIGTERM; will restart later")
             self.killing = 1
-            self.delay = time.time() + self.backofflimit
+            self.delay = time.time() + self.opts.backofflimit
         else:
             self.forkandexec()
             self.sendreply("Application started")
@@ -440,7 +481,7 @@
             self.killapp(signal.SIGTERM)
             self.sendreply("Sent SIGTERM; will exit later")
             self.killing = 1
-            self.delay = time.time() + self.backofflimit
+            self.delay = time.time() + self.opts.backofflimit
         else:
             self.sendreply("Exiting now")
             self.info("Exiting")
@@ -477,7 +518,7 @@
                        "lasttime=%r\n" % self.lasttime +
                        "application=%r\n" % self.appid +
                        "manager=%r\n" % os.getpid() + 
-                       "backofflimit=%r\n" % self.backofflimit +
+                       "backofflimit=%r\n" % self.opts.backofflimit +
                        "filename=%r\n" % self.filename +
                        "args=%r\n" % self.args)
 
@@ -528,12 +569,12 @@
         now = time.time()
         if not self.lasttime:
             pass
-        elif now - self.lasttime < self.backofflimit:
+        elif now - self.lasttime < self.opts.backofflimit:
             # Exited rather quickly; slow down the restarts
             self.backoff += 1
-            if self.backoff >= self.backofflimit:
-                if self.forever:
-                    self.backoff = self.backofflimit
+            if self.backoff >= self.opts.backofflimit:
+                if self.opts.forever:
+                    self.backoff = self.opts.backofflimit
                 else:
                     self.error("restarting too often; quit")
                     self.exit(1)
@@ -581,7 +622,7 @@
         if os.WIFEXITED(sts):
             es = os.WEXITSTATUS(sts)
             msg = "pid %d: exit status %s" % (pid, es)
-            if es in self.exitcodes:
+            if es in self.opts.exitcodes:
                 msg = msg + "; exiting"
                 self.info(msg)
                 self.exit(es)
@@ -624,18 +665,6 @@
                     self.signames[v] = k
 
     # Error handling
-
-    def usage(self, msg):
-        self.error(str(msg))
-        self.errwrite("Error: %s\n" % str(msg))
-        self.errwrite("For help, use zdaemon.py -h\n")
-        self.exit(2)
-
-    def errwrite(self, msg):
-        sys.stderr.write(msg)
-
-    def exit(self, sts=0):
-        sys.exit(sts)
 
     # Log messages with various severities