[Zodb-checkins] CVS: ZODB3/ZEO - runsvr.py:1.1
Guido van Rossum
guido@python.org
Mon, 18 Nov 2002 20:12:16 -0500
Update of /cvs-repository/ZODB3/ZEO
In directory cvs.zope.org:/tmp/cvs-serv4001
Added Files:
runsvr.py
Log Message:
Initial version of generic start.py rewrite. This is very fresh, but
seems to work.
=== Added File ZODB3/ZEO/runsvr.py ===
#! /usr/bin/env 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.0 (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
#
##############################################################################
"""Start the ZEO storage server.
Usage: %s [-a ADDRESS] [-d DIRECTORY]
[-f FILENAME] [-s STORAGE] [-u USER]
Options:
-a/--address ADDRESS -- server address of the form PORT, HOST:PORT, or PATH
(a PATH must contain at least one "/")
-f/--filename FILENAME -- filename for FileStorage
-s/--storage STORAGE -- storage specification of the form
NAME=MODULE[:ATTRIBUTE]
(multiple -s options are supported)
-u/--user USER -- username or uid of user to setuid()
-z/--directory DIRECTORY -- home directory (default $INSTANCE_HOME or `pwd`)
-a is required; either -f must be used or -s must be used.
"""
# The code here is designed to be reused by other, similar servers.
# For the forseeable future, it must work under Python 2.1 as well as
# 2.2 and above.
# XXX The option parsing infrastructure could be shared with zdaemon.py
import os
import sys
import getopt
import signal
import socket
if __name__ == "__main__":
# Add the parent of the script directory to the module search path
from os.path import dirname, abspath, normpath
sys.path.append(dirname(dirname(normpath(abspath(sys.argv[0])))))
import zLOG
class Options:
"""A class to parse and hold the command line options.
Options are represented by various attributes (zeoport etc.).
Positional arguments are represented by the args attribute.
This also has a public usage() method that can be used to report
errors related to the command line.
"""
args = []
def __init__(self, args=None, progname=None, doc=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 sys.argv[0])
doc -- usage message (default, __main__.__doc__)
"""
if args is None:
args = sys.argv[1:]
if progname is None:
progname = sys.argv[0]
self.progname = progname
if doc is None:
import __main__
doc = __main__.__doc__
if doc and not doc.endswith("\n"):
doc += "\n"
self.doc = doc
try:
self.opts, self.args = getopt.getopt(args,
self._short_options,
self._long_options)
except getopt.error, msg:
self.usage(str(msg))
for opt, arg in self.opts:
self.handle_option(opt, arg)
self.check_options()
# Default set of options. Subclasses should override.
_short_options = "h"
_long_options = ["--help"]
def handle_option(self, opt, arg):
"""Handle one option. Subclasses should override.
This sets the various instance variables overriding the defaults.
When -h is detected, print the module docstring to stdout and exit(0).
"""
if opt == "-h" or opt == "--help":
self.help()
def check_options(self):
"""Check options. Subclasses may override.
This can be used to ensure certain options are set, etc.
"""
pass
def help(self):
"""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)
class ZEOOptions(Options):
family = None
address = None
user = None
storages = None
filename = None
directory = None
_short_options = "a:d:f:hs:u:"
_long_options = [
"--address=",
"--directory=",
"--filename=",
"--help",
"--storage=",
"--user=",
]
def handle_option(self, opt, arg):
# Alphabetical order please!
if opt in ("-a", "--address"):
if "/" in arg:
self.family = socket.AF_UNIX
self.address = arg
else:
self.family = socket.AF_INET
if ":" in arg:
host, port = arg.split(":", 1)
else:
host = ""
port = arg
try:
port = int(port)
except: # int() can raise all sorts of errors
self.usage("invalid port number: %r" % port)
self.address = (host, port)
elif opt in ("-d", "--directory"):
self.directory = arg
elif opt in ("-f", "--filename"):
self.filename = arg
elif opt in ("-s", "--storage"):
if self.storages is None:
self.storages = {}
if not "=" in arg:
self.usage("use -s/--storage storagename=module[:attribute]")
name, rest = arg.split("=", 1)
if ":" in rest:
module, attr = rest.split(":", 1)
else:
module = rest
attr = name
self.storages[name] = module, attr
elif opt in ("-u", "--user"):
self.user = arg
else:
# Pass it to the base class, for --help/-h
Options.handle_option(self, opt, arg)
def check_options(self):
if self.storages and self.filename:
self.usage("can't use both -s/--storage and -f/--filename")
if not self.storages and not self.filename:
self.usage("need one of -s/--storage or -f/--filename")
if self.family is None:
self.usage("need -a/--address [host:]port or unix path")
if self.args:
self.usage("no positional arguments supported")
class Server:
OptionsClass = ZEOOptions
def __init__(self, opts=None):
if opts is None:
opts = self.OptionsClass()
self.opts = opts
def main(self):
self.check_socket()
self.clear_socket()
self.set_uid()
self.change_dir()
self.open_storages()
self.setup_signals()
try:
self.create_server()
self.loop_forever()
finally:
self.clear_socket()
def check_socket(self):
if isinstance(self.opts.address, type("")):
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
else:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
s.connect(self.opts.address)
except socket.error:
pass
else:
s.close()
self.opts.usage("address %r still in use" % self.opts.address)
def clear_socket(self):
if isinstance(self.opts.address, type("")):
try:
os.unlink(self.opts.address)
except os.error:
pass
def set_uid(self):
if self.opts.user is None:
return
if os.name != "posix":
self.opts.usage("-u/-user only supported on Unix")
if os.geteuid() != 0:
self.opts.usage("only root can use -u/--user")
# XXX Is it important not to die if the following fails?
import pwd
try:
uid = int(self.opts.user)
except: # int() can raise all sorts of errors
try:
pwrec = pwd.getpwnam(self.opts.user)
except KeyError:
self.opts.usage("username %r not found" % self.opts.user)
uid = pwrec[2]
else:
try:
pwrec = pwd.getpwuid(uid)
except KeyError:
self.opts.usage("uid %r not found" % self.opts.user)
gid = pwrec[3]
os.setgid(gid)
os.setuid(uid)
def change_dir(self):
if self.opts.directory:
try:
os.chdir(self.opts.directory)
except os.error:
self.opts.usage("can't chdir into %r" % self.opts.directory)
def open_storages(self):
if self.opts.storages:
self.load_storages(self.opts.storages)
else:
from ZODB.FileStorage import FileStorage
info("opening storage '1': %r" % self.opts.filename)
storage = FileStorage(self.opts.filename)
self.storages = {"1": storage}
def load_storages(self, storages):
self.storages = {}
for name, (module, attr) in storages.items():
info("opening storage %r (%r:%r)" % (name, module, attr))
self.storages[name] = self.get_storage(module, attr)
_storage_cache = {}
def get_storage(self, module, attr):
# XXX This may fail with ImportError or AttributeError
path = sys.path
dir, module = os.path.split(module)
if module.lower().endswith('.py'):
module = module[:-3]
im = self._storage_cache.get((dir, module))
if im is None:
if dir:
path = [dir] + path
import imp
im = imp.find_module(module, path)
im = imp.load_module(module, *im)
self._storage_cache[(dir, module)] = im
return getattr(im, attr)
def setup_signals(self):
"""Set up signal handlers.
The signal handler for SIGFOO is a method handle_sigfoo().
If no handler method is defined for a signal, the signal
action is not changed from its initial value. The handler
method is called without additional arguments.
"""
if os.name != "posix":
return
if hasattr(signal, 'SIGXFSZ'):
signal.signal(signal.SIGXFSZ, signal.SIG_IGN) # Special case
init_signames()
for sig, name in signames.items():
method = getattr(self, "handle_" + name.lower(), None)
if method is not None:
def wrapper(sig_dummy, frame_dummy, method=method):
method()
signal.signal(sig, wrapper)
def create_server(self):
from ZEO.StorageServer import StorageServer
self.server = StorageServer(self.opts.address, self.storages)
def loop_forever(self):
import asyncore
asyncore.loop()
def handle_sigterm(self):
info("terminated by SIGTERM")
self.close_storages()
sys.exit(0)
def handle_sigint(self):
info("terminated by SIGINT")
self.close_storages()
sys.exit(0)
def handle_sigusr2(self):
# This requires a modern zLOG (from Zope 2.6 or later); older
# zLOG packages don't have the initialize() method
info("reinitializing zLOG")
# XXX Shouldn't this be below with _log()?
import zLOG
zLOG.initialize()
def close_storages(self):
for name, storage in self.storages.items():
info("closing storage %r" % name)
try:
storage.close()
except: # Keep going
exception("failed to close storage %r" % name)
# Signal names
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
signames = {}
for name, sig in signal.__dict__.items():
k_startswith = getattr(name, "startswith", None)
if k_startswith is None:
continue
if k_startswith("SIG") and not k_startswith("SIG_"):
signames[sig] = name
# Log messages with various severities.
# This uses zLOG, but the API is a simplified version of PEP 282
def critical(msg):
"""Log a critical message."""
_log(msg, zLOG.PANIC)
def error(msg):
"""Log an error message."""
_log(msg, zLOG.ERROR)
def exception(msg):
"""Log an exception (an error message with a traceback attached)."""
_log(msg, zLOG.ERROR, error=sys.exc_info())
def warn(msg):
"""Log a warning message."""
_log(msg, zLOG.PROBLEM)
def info(msg):
"""Log an informational message."""
_log(msg, zLOG.INFO)
def debug(msg):
"""Log a debugging message."""
_log(msg, zLOG.DEBUG)
def _log(msg, severity=zLOG.INFO, error=None):
"""Internal: generic logging function."""
zLOG.LOG("RUNSVR", severity, msg, "", error)
# Main program
def main(args=None):
opts = Server.OptionsClass(args)
s = Server(opts)
s.main()
if __name__ == "__main__":
main()