Update of /cvsroot/mailman/mailman3/bin
In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv31133/bin
Added Files:
Tag: 3.0
genaliases listinfo mailmanctl paths.py qrunner webserver
withlist
Log Message:
Initial checkin of Mailman3 source
--- NEW FILE: genaliases ---
#! /usr/bin/env python
# Copyright (C) 2003 Barry A Warsaw
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
"""Regenerate Mailman specific aliases from scratch."""
import os
import sys
import logging
from optparse import OptionParser
import paths
from mailman import startup
from mailman.config import configure, configuration
from mailman.config.versions import version
from mailman.database import listdb
from mailman.i18n import _
def parseargs():
parser = OptionParser(version='Mailman ' + version,
usage="""\
%prog [options]
Regenerate Mailman aliases from scratch.""")
parser.add_option('-C', '--configdir', metavar='DIR', dest='configdir',
help=_("""\
Directory containing the Mailman configuration files. If this option is not
given, Mailman will search for a 'mmconfig' directory containing the
mailman.xml file."""))
# BAW: a hack to make formatted help fit in an Emacs buffer better
parser.formatter.width -= 1
parser.formatter.help_width -= 1
opts, args = parser.parse_args()
# No positional arguments are allowed
if args:
parser.print_help()
sys.exit(1)
# We must have a config directory. If one wasn't given, look for
# ../mmconfig
if opts.configdir is None:
here = os.path.abspath(os.path.dirname(__file__))
parent = os.path.normpath(os.path.join(here, '../mmconfig'))
cfile = os.path.join(parent, 'mailman.conf')
if os.path.exists(cfile):
opts.configdir = parent
else:
parser.print_help()
print
print _('No configuration directory found, please use -C')
sys.exit(1)
return opts, parser
def main():
modname = 'mailman.smtp.' + configuration.smtp.mta
__import__(modname)
module = sys.modules[modname]
# BAW: lock this file to prevent multiple overrights
fp = open(configuration.smtp.alias_file, 'w')
try:
module.genaliases(fp)
finally:
fp.flush()
fp.close()
if __name__ == '__main__':
options, parser = parseargs()
configure(options.configdir)
startup.startup()
try:
log = logging.getLogger('mailman.error')
main()
finally:
startup.shutdown()
--- NEW FILE: listinfo ---
#! /usr/bin/env python
# Copyright (C) 2003 Barry A. Warsaw
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
"""Show information about mailing lists."""
import os
import sys
import logging
from optparse import OptionParser
import paths
from mailman import startup
from mailman.config import configure, configuration
from mailman.config.versions import version
from mailman.i18n import _
from mailman.mlist import MailList
def parseargs():
parser = OptionParser(version='Mailman ' + version,
usage="""\
%prog [options]
Show information about mailing lists.""")
parser.add_option('-C', '--configdir', metavar='DIR', dest='configdir',
help=_("""\
Directory containing the Mailman configuration files. If this option is not
given, Mailman will search for a 'mmconfig' directory containing the
mailman.xml file."""))
parser.add_option('-d', '--domain', default=None,
help=_("""\
Limit listing to the specified domain only. If not given, all domains are
displayed."""))
parser.add_option('-D', '--listdomains', default=False,
action='store_true',
help=_('Lists the active mailing lists by domain.'))
parser.add_option('-n', '--nodescription',
default=True, dest='descriptions', action='store_false',
help=("""\
Do not include the list descriptions in the output."""))
# BAW: a hack to make formatted help fit in an Emacs buffer better
parser.formatter.width -= 1
parser.formatter.help_width -= 1
opts, args = parser.parse_args()
# No positional arguments are allowed
if args:
parser.print_help()
sys.exit(1)
# We must have a config directory. If one wasn't given, look for
# ../mmconfig
if opts.configdir is None:
here = os.path.abspath(os.path.dirname(__file__))
parent = os.path.normpath(os.path.join(here, '../mmconfig'))
cfile = os.path.join(parent, 'mailman.conf')
if os.path.exists(cfile):
opts.configdir = parent
else:
parser.print_help()
print
print _('No configuration directory found, please use -C')
sys.exit(1)
return opts, parser
def main():
from mailman.database import listdb
# Sort all the mailing lists by domain
listids = listdb.listIds()
domains = {}
for listid in listids:
listname, domain = listid.split('.', 1)
domains.setdefault(domain, []).append((listid, listname))
# Sorted by domain
if options.listdomains:
for domain in domains.keys():
if options.descriptions:
print _('Domains (listid, listname, description):')
else:
print _('Domains (listid, listname):')
for listid, listname in domains[domain]:
print ' ', listid, listname,
if options.descriptions:
mlist = MailList(listid)
print '==>', mlist.description
else:
print
return
if options.domain:
lists = domains.get(options.domain)
if not lists:
print _('No lists for domain: $options.domain')
return
if options.descriptions:
print _(
'listid, listname, description for domain $options.domain:')
else:
print _('listid, listname for domain $options.domain:')
for listid, listname in lists:
print ' ', listid, listname,
if options.descriptions:
mlist = MailList(listid)
print '==>', mlist.description
else:
print
return
# Otherwise, sort the lists by listid
listids.sort()
if options.descriptions:
print _('listid, listname, description:')
else:
print _('listid, listname:')
for listid in listids:
print ' ', listid, listid.split('.', 1)[0],
if options.descriptions:
mlist = MailList(listid)
print '==>', mlist.description
else:
print
if __name__ == '__main__':
options, parser = parseargs()
configure(options.configdir)
startup.startup()
try:
log = logging.getLogger('mailman.error')
main()
finally:
startup.shutdown()
--- NEW FILE: mailmanctl ---
#! /usr/bin/env python
# Copyright (C) 2003 Barry A. Warsaw
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
"""Primary start-up and shutdown script for Mailman's qrunners."""
import os
import sys
import errno
import signal
import socket
import logging
from optparse import OptionParser
import paths
from mailman.config import configure, configuration
from mailman.config.logs import initlogs, rotateLogs
from mailman.config.versions import version
from mailman.i18n import _
def kill_watcher(sig):
try:
fp = open(configuration.mailman.pidfile)
except IOError, e:
if e.errno <> errno.ENOENT: raise
print >> sys.stderr, _('No pid file: $config.pidfile')
print >> sys.stderr, _('Is Mailman even running?')
return
try:
pidstr = fp.read()
finally:
fp.close()
try:
pid = int(pidstr.strip())
except ValueError:
# For i18n convenience
print >> sys.stderr, _('PID unparseable in: $config.pidfile')
print >> sys.stderr, _('pid: $pid')
print >> sys.stderr, _('Is Mailman even running?')
return
try:
os.kill(pid, sig)
except OSError, e:
if e.errno <> errno.ESRCH:
raise
print >> sys.stderr, _('No child with pid: $pid')
print >> sys.stderr, e
print >> sys.stderr, _('Stale pid file removed.')
os.unlink(config.pidfile)
def read_pid():
fp = open(configuration.mailman.pidfile)
try:
pid = int(fp.read().strip())
finally:
fp.close()
return pid
def isalive(pid):
# Find out if the process exists by calling kill with a signal 0.
try:
os.kill(pid, 0)
except OSError, e:
if e.errno <> errno.ESRCH:
raise
return False
return True
def acquire_lock_1(force):
flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL
try:
return os.open(configuration.mailman.pidfile, flags, 0770)
except OSError, e:
if e.errno <> errno.EEXIST: raise
# The file already exists, meaning the watcher could already be
# running. If force is true, then we'll just blow away the lock
# file and re-acquire it. If not, re-raise the exception so
# higher ups can catch it and print an error message.
if force:
print >> sys.stderr, _('Removing stale pid file')
os.unlink(configuration.mailman.pidfile)
return acquire_lock_1(force=False)
else:
raise
def acquire_lock(force):
try:
fd = acquire_lock_1(force)
return os.fdopen(fd, 'w')
except OSError, e:
if e.errno <> errno.EEXIST:
raise
if isalive(read_pid()):
print >> sys.stderr, _("""\
The master qrunner lock could not be acquired because it appears as if another
master qrunner is already running.""")
else:
print >> sys.stderr, _("""\
The master qrunner lock could not be acquired. It appears as though there is
a stale master qrunner lock. Try re-running mailmanctl with the -s flag.""")
return False
def start_runner(qrname, slice):
pid = os.fork()
if pid:
# parent
return pid
# child
#
# Craft the command line arguments for the exec() call.
args = ['--runner=%s:%d' % (qrname, slice)]
args.extend(['-s', '-l'])
args.extend(['-C', options.configdir])
qrexe = os.path.join(configuration.dirs.bindir, 'qrunner')
pyexe = configuration.mailman.pythonexe
# mm_cfg.PYTHON, which is the absolute path to the Python interpreter,
# must be given as argv[0] due to Python's library search algorithm.
log.debug('mailmanctl starting qrunner: %s %s', qrexe, args)
os.execl(pyexe, pyexe, qrexe, *tuple(args))
# Should never get here
raise RuntimeError, 'os.execl() failed'
def start_all_runners():
kids = {}
for qrname, qrclass, count in configuration.smtp.queue:
for slice in range(count):
# queue runner name, slice, restart count
info = (qrname, slice, 0)
pid = start_runner(qrname, slice)
kids[pid] = info
return kids
def checkprivs():
uid, uname = configuration.mailman.userid
gid, gname = configuration.mailman.groupid
# If we're running as root (uid == 0), coerce the uid and gid to that
# which Mailman was configured for, and refuse to run if we didn't coerce
# the uid/gid.
myuid = os.getuid()
if myuid == 0:
os.setuid(uid)
os.setgid(gid)
elif myuid <> uid:
parser.print_help()
print
print _('Run this program as root or use -u.')
sys.exit(1)
def loop_forever(kids):
try:
while True:
try:
pid, status = os.wait()
except OSError, e:
# No children? We're done
if e.errno == errno.ECHILD:
break
# If the system call got interrupted, just restart it.
elif e.errno <> errno.EINTR:
raise
continue
killsig = exitstatus = None
if os.WIFSIGNALED(status):
killsig = os.WTERMSIG(status)
if os.WIFEXITED(status):
exitstatus = os.WEXITSTATUS(status)
# We'll restart the process unless we were given the "no-restart"
# switch, or if the process was SIGTERM'd or exitted with a
# SIGTERM exit status. This lets us better handle runaway
# restarts (say, if the subproc had a syntax error <wink>).
restarting = ''
if options.restart:
if (exitstatus is None and killsig <> signal.SIGTERM) or \
(killsig is None and exitstatus <> signal.SIGTERM):
restarting = '[restarting]'
qrname, slice, restarts = kids[pid]
del kids[pid]
log.info("""\
Master qrunner detected subprocess exit
(pid: %d, sig: %s, sts: %s, class: %s, slice: %d) %s""",
pid, killsig, exitstatus, qrname, slice+1, restarting)
# See if we've reached the maximum number of allowable restarts
if exitstatus <> signal.SIGINT:
restarts += 1
if restarts > configuration.smtp.max_restarts:
log.info("""\
Qrunner %s reached maximum restart limit of %d, not restarting.""",
qrname, configuration.smtp.max_restarts)
restarting = ''
# Now perhaps restart the process unless it exited with a SIGTERM
# or we aren't restarting.
if restarting:
newpid = start_runner(qrname, slice)
kids[newpid] = (qrname, slice, restarts)
finally:
# Should we leave the main loop for any reason, we want to be sure all
# of our children are exited cleanly. Send SIGTERMs to all the child
# processes and wait for them all to exit.
for pid in kids.keys():
try:
os.kill(pid, signal.SIGTERM)
except OSError, e:
if e.errno == errno.ESRCH:
# The child has already exited
log.error('ESRCH on pid: %d', pid)
del kids[pid]
# Wait for all the children to go away
while True:
try:
pid, status = os.wait()
except OSError, e:
if e.errno == errno.ECHILD:
break
elif e.errno <> errno.EINTR:
raise
continue
# And now remove our pidfile
os.unlink(configuration.mailman.pidfile)
def parseargs():
parser = OptionParser(version='Mailman ' + version,
usage="""\
%prog [options] [ start | stop | restart | reopen ]
Primary start-up and shutdown script for Mailman's qrunners.
This script starts, stops, and restarts the main Mailman queue runners, and
also serves as their process watchdog. It forks and execs the qrunners,
waiting on their process ids. When it detects a subprocess has exited, it may
restart it.
The qrunners respond to SIGINT, SIGTERM, and SIGHUP. SIGINT and SIGTERM both
cause the qrunners to exit cleanly, but the master will only restart qrunners
that have exited due to a SIGINT. SIGHUP causes the master and the qrunners
to close their log files, and reopen then upon the next printed message.
The master also responds to SIGINT, SIGTERM, and SIGHUP, which it simply
passes on to the qrunners (note that the master will close and reopen its own
log files on receipt of a SIGHUP). The master also leaves its own process id
in a pid file, but you normally don't need to use this directly. The 'start',
'stop', 'restart', and 'reopen' commands handle everything for you.
Commands:
start - Start the master daemon and all qrunners. Prints a message and
exits if the master daemon is already running.
stop - Stops the master daemon and all qrunners. After stopping, no
more messages will be processed.
restart - Restarts the qrunners, but not the master process. Use this
whenever you upgrade or update Mailman so that the qrunners will
use the newly installed code.
reopen - This will close all log files, causing them to be re-opened the
next time a message is written to them""")
parser.add_option('-C', '--configdir', metavar='DIR', dest='configdir',
help=_("""\
Directory containing the Mailman configuration files. If this option is not
given, Mailman will search for a 'mmconfig' directory containing the
mailman.xml file."""))
parser.add_option('-n', '--no-restart', dest='restart', default=True,
action='store_false', help=_("""\
Don't restart the qrunners when they exit because of an error or a
SIGINT. They are never restarted if they exit in response to a
SIGTERM. Use this only for debugging. Only useful if the 'start'
command is given."""))
parser.add_option('-u', '--run-as-user', default=True,
dest='checkprivs', action='store_false',
help=_("""\
Normally, this script will refuse to run if the user id and group id
are not set to the 'mailman' user and group (as defined when you
configured Mailman). If run as root, this script will change to this
user and group before the check is made.
This can be inconvenient for testing and debugging purposes, so the -u
flag means that the step that sets and checks the uid/gid is skipped,
and the program is run as the current user and group. This flag is
not recommended for normal production environments.
Note though, that if you run with -u and are not in the 'mailman' group,
you may have permission problems, such as begin unable to delete a
list's archives through the web. Tough luck!
This option is only useful with the 'start' command."""))
parser.add_option('-s', '--stale-lock-cleanup', default=False,
dest='force', action='store_true',
help=_("""\
If mailmanctl finds an existing master lock, it will normally exit
with an error message. With this option, mailmanctl will perform an
extra level of checking. If a process matching the host/pid described
in the lock file is running, mailmanctl will still exit, but if no
matching process is found, mailmanctl will remove the apparently stale
lock and make another attempt to claim the master lock."""))
parser.add_option('-q', '--quiet', default=False,
action='store_true',
help=_("Don't print status messages."))
# BAW: a hack to make formatted help fit in an Emacs buffer better
parser.formatter.width -= 1
parser.formatter.help_width -= 1
opts, args = parser.parse_args()
# A command is required
if len(args) <> 1:
parser.print_help()
print
print _('No command given')
sys.exit(1)
# We must have a config directory. If one wasn't given, look for
# ../mmconfig
if opts.configdir is None:
here = os.path.abspath(os.path.dirname(__file__))
parent = os.path.normpath(os.path.join(here, '../mmconfig'))
cfile = os.path.join(parent, 'mailman.conf')
if os.path.exists(cfile):
opts.configdir = parent
else:
parser.print_help()
print
print _('No configuration directory found, please use -C')
sys.exit(1)
return opts, parser, args[0]
def main(command):
if command == 'stop':
# Sent the master qrunner process a SIGINT, which is equivalent to
# giving cron/qrunner a ctrl-c or KeyboardInterrupt. This will
# effectively shut everything down.
if not options.quiet:
print _("Shutting down Mailman's master qrunner")
kill_watcher(signal.SIGTERM)
elif command == 'restart':
# Sent the master qrunner process a SIGHUP. This will cause the
# master qrunner to kill and restart all the worker qrunners, and to
# close and re-open its log files.
if not options.quiet:
print _("Restarting Mailman's master qrunner")
kill_watcher(signal.SIGINT)
elif command == 'reopen':
if not options.quiet:
print _('Re-opening all log files')
kill_watcher(signal.SIGHUP)
elif command == 'start':
if options.checkprivs:
checkprivs()
else:
print _('Warning! You may encounter permission problems.')
# Create and start all the child processes, consisting of the qrunner
# processes and the master process watcher.
#
# The master watcher process simply waits on the pids of the child
# qrunners. Unless explicitly disabled by a mailmanctl switch (or the
# children are killed with SIGTERM instead of SIGINT), the watcher
# will automatically restart any child process that exits. This
# allows us to be more robust, and also to implement restart by simply
# SIGINT'ing the qrunner children, and letting the watcher restart
# them.
#
# Make sure no other qrunner process is running.
fp = acquire_lock(options.force)
if not fp:
return
# Daemon process startup according to Stevens, Advanced Programming in
# the UNIX Environment, Chapter 13.
pid = os.fork()
if pid:
# parent
if not options.quiet:
print _("Starting Mailman's master qrunner.")
return
print >> fp, os.getpid()
fp.flush()
fp.close()
# Close stdin, stdout, and stderr and point them to /dev/null
os.close(0)
sys.stdin = sys.__stdin__ = open('/dev/null')
os.close(1)
sys.stdout = sys.__stdout__ = open('/dev/null', 'w')
os.close(2)
sys.stderr = sys.__stderr__ = open('/dev/null', 'w')
# Create a new session and become the session leader, but since we
# won't be opening any terminal devices, don't do the ultra-paranoid
# suggestion of doing a second fork after the setsid() call.
os.setsid()
# Instead of cd'ing to root, cd to the Mailman installation home
os.chdir(configuration.dirs.basedir)
# Clear our file mode creation umask
os.umask(0)
# I don't think we have any unneeded file descriptors.
#
# Now start all the qrunners. This returns a dictionary where the
# keys are qrunner pids and the values are tuples of the following
# form: (qrname, slice). This does its own fork and exec, and sets up
# its own signal handlers.
kids = start_all_runners()
# Set up a SIGHUP handler that will propagate to the qrunner children.
# This will tell them to close and reopen their log files
def sighup_handler(signum, frame):
# Closing our syslog will cause it to be re-opened at the next log
# print output.
rotateLogs()
for pid in kids.keys():
os.kill(pid, signal.SIGHUP)
# And just to tweak things...
log.info('Master watcher caught SIGHUP. Re-opening log files.')
signal.signal(signal.SIGHUP, sighup_handler)
# We also need to install a SIGTERM handler because that's what init
# will kill this process with when changing run levels.
def sigterm_handler(signum, frame):
for pid in kids.keys():
try:
os.kill(pid, signal.SIGTERM)
except OSError, e:
if e.errno <> errno.ESRCH: raise
log.info('Master watcher caught SIGTERM. Exiting.')
signal.signal(signal.SIGTERM, sigterm_handler)
# Finally, we need a SIGINT handler which will cause the sub-qrunners
# to exit, but the master will restart SIGINT'd sub-processes unless
# the -n flag was given.
def sigint_handler(signum, frame, kids=kids):
for pid in kids.keys():
os.kill(pid, signal.SIGINT)
log.info('Master watcher caught SIGINT. Restarting.')
signal.signal(signal.SIGINT, sigint_handler)
# Now we're ready to simply do our wait/restart loop. This is the
# master qrunner watcher.
loop_forever(kids)
if __name__ == '__main__':
options, parser, command = parseargs()
configure(options.configdir)
initlogs()
try:
log = logging.getLogger('mailman.queues')
main(command)
finally:
logging.shutdown()
--- NEW FILE: paths.py ---
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
# This file becomes paths.py which is installed in may directories. By
# importing this module, sys.path gets `hacked' so that the $prefix/Mailman
# directory is inserted at the start of that list. That directory really
# contains the Mailman modules in package form. This file exports two
# attributes that other modules may use to get the absolute path to the
# installed Mailman distribution.
import os
import sys
# Setting python paths
sys.path.insert(0, '/home/barry/projects/mailman3/src')
--- NEW FILE: qrunner ---
#! /usr/bin/env python
# Copyright (C) 2003 Barry A. Warsaw
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
"""Run one or more qrunners, once or repeatedly."""
import os
import sys
import signal
import logging
import traceback
from optparse import OptionParser
import paths
from mailman import startup
from mailman.config import configure, configuration
from mailman.config.logs import rotateLogs
from mailman.config.versions import version
from mailman.i18n import _
def make_qrunner(qrname, slice, once=False):
modulename = 'mailman.queues.' + qrname
try:
__import__(modulename)
except ImportError, e:
if options.subproc:
# Exit with SIGTERM exit code so mailmanctl won't try to restart us
traceback.print_exc()
sys.exit(signal.SIGTERM)
else:
raise
qrclass = getattr(sys.modules[modulename],
configuration.smtp.qrunners[qrname][0])
if not options.loop:
# Subclass to hack in the setting of the stop flag in _doperiodic()
class Once(qrclass):
def _doperiodic(self):
self.stop()
qrunner = Once(slice)
else:
qrunner = qrclass(slice)
log.info('%s qrunner created', qrname)
return qrunner
class InfiniteLoop:
status = False
def __init__(self, qrunner):
self._qrunner = qrunner
def name(self):
return self._qrunner.__class__.__name__
def stop(self):
self._qrunner.stop()
class StoppableLoop:
status = False
def __init__(self):
self._isdone = False
def name(self):
return 'Main loop'
def stop(self):
self._isdone = True
def isdone(self):
return self._isdone
def set_signals(loop):
# Set up the SIGTERM handler for stopping the loop
def sigterm_handler(signum, frame):
# Exit the qrunner cleanly
loop.stop()
loop.status = signal.SIGTERM
log.info('%s qrunner caught SIGTERM. Stopping.', loop.name())
signal.signal(signal.SIGTERM, sigterm_handler)
# Set up the SIGINT handler for stopping the loop. For us, SIGINT is
# the same as SIGTERM, but our parent treats the exit statuses
# differently (it restarts a SIGINT but not a SIGTERM).
def sigint_handler(signum, frame):
# Exit the qrunner cleanly
loop.stop()
loop.status = signal.SIGINT
log.info('%s qrunner caught SIGINT. Stopping.', loop.name())
signal.signal(signal.SIGINT, sigint_handler)
# SIGHUP just tells us to close our log files. They'll be
# automatically reopened at the next log print :)
def sighup_handler(signum, frame):
rotateLogs()
log.info('%s qrunner caught SIGHUP. Rotating logs.', loop.name())
signal.signal(signal.SIGHUP, sighup_handler)
def parseargs():
parser = OptionParser(version='Mailman ' + version,
usage="""\
%prog [options]
Run one or more qrunners once, or repeatedly.
Each named qrunner is run in round-robin fashion. In other words, the
first named qrunner is run to consume all the files currently in its
directory. When that qrunner is done, the next one is run to consume all the
files in /its/ directory, and so on. The number of total iterations can be
given on the command line.
-r is required unless --listall or -h is given, and it must be one of the
names displayed by the --listall switch.""")
parser.add_option('-C', '--configdir', metavar='DIR', dest='configdir',
help=_("""\
Directory containing the Mailman configuration files. If this option is not
given, Mailman will search for a 'mmconfig' directory containing the
mailman.xml file."""))
parser.add_option('--listall', default=False, action='store_true',
help=_("""List all the available qrunners."""))
parser.add_option('-r', '--runner', default=[], action='append',
dest='runners', help=_("""\
Run the named qrunner, which must be one of the strings returned by the
--listall option. Optional :slice if given, is used to assign multiple
qrunner processes to a queue. The number of slices is defined in the
configuration file for each qrunner individually; slice is the number of this
qrunner within that range. If slice is not given, it defaults to 1.
"""))
parser.add_option('-l', '--loop', default=False, action='store_true',
help=_("""\
Run each named qrunner indefinitely, until the process receives a
SIGTERM or SIGINT. Otherwise, each qrunner is run just once through
its main loop."""))
parser.add_option('-v', '--verbose', default=False, action='store_true',
help=_('More debugging is printed to the log file'))
parser.add_option('-s', '--subproc', default=False, action='store_true',
help=_("""\
This should only be used when running qrunner as a subprocess of the
mailmanctl startup script. It changes some of the exit-on-error
behavior to work better with that framework."""))
# BAW: a hack to make formatted help fit in an Emacs buffer better
parser.formatter.width -= 1
parser.formatter.help_width -= 1
opts, args = parser.parse_args()
# No positional arguments are allowed
if args:
parser.print_help()
sys.exit(1)
# We must have a config directory. If one wasn't given, look for
# ../mmconfig
if opts.configdir is None:
here = os.path.abspath(os.path.dirname(__file__))
parent = os.path.normpath(os.path.join(here, '../mmconfig'))
cfile = os.path.join(parent, 'mailman.conf')
if os.path.exists(cfile):
opts.configdir = parent
else:
parser.print_help()
print
print _('No configuration directory found, please use -C')
sys.exit(1)
# Post-process the runners list
runners = []
for rspec in opts.runners:
parts = rspec.split(':', 1)
if len(parts) == 1:
runners.append((rspec, 1))
elif len(parts) == 2:
runners.append((parts[0], int(parts[1])))
else:
parser.print_help()
print
print _('Bad qrunner specification: $rspec')
sys.exit(1)
opts.runners = runners
return opts, parser
def main():
# Handle list all
if options.listall:
print _('Available qrunners (queue, class, count):')
for qrname, qrclass, qrcount in configuration.smtp.queue:
print ' %10s %15s %s' % (qrname, qrclass, qrcount)
sys.exit(0)
# Fast track for one infinite runner
if len(options.runners) == 1 and options.loop:
qrname, slice = options.runners[0]
qrunner = make_qrunner(qrname, slice)
loop = InfiniteLoop(qrunner)
set_signals(loop)
# Now start up the main loop
log.info('%s qrunner started.', loop.name())
qrunner.run()
log.info('%s qrunner exiting.', loop.name())
else:
# Anything else we have to handle a bit more specially
qrunners = []
for qrname, slice in options.runners:
qrclass = configuration.qrunners[qrname][0]
qrunner = make_qrunner(qrname, slice, True)
qrunners.append(qrunner)
# This class is used to manage the main loop
loop = StoppableLoop()
set_signals(loop)
log.info('Main qrunner loop started.')
while not loop.isdone():
for qrunner in qrunners:
# In case the SIGTERM came in the middle of this iteration
if loop.isdone():
break
if options.verbose:
log.info('Now doing a %s qrunner iteration',
qrunner.__class__.__bases__[0].__name__)
qrunner.run()
if options.once:
break
log.info('Main qrunner loop exiting.')
return loop.status
if __name__ == '__main__':
options, parser = parseargs()
configure(options.configdir)
startup.startup()
try:
log = logging.getLogger('mailman.queues')
status = main()
sys.exit(status)
finally:
startup.shutdown()
--- NEW FILE: webserver ---
#! /usr/bin/env python
# Copyright (C) 2004 by Barry A. Warsaw
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the Free
# Software Foundation; either version 2 of the License, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc., 59 Temple
# Place - Suite 330, Boston, MA 02111-1307, USA.
import os
import sys
import logging
import traceback
from cStringIO import StringIO
from optparse import OptionParser
import paths
from twisted.internet import reactor
from twisted.web import server, resource
from mailman import startup
from mailman.config import configuration
from mailman.config.versions import version
from mailman.cgi.do_front import process as front
from mailman.dbmgr import dbmgr
from mailman.i18n import _
def parseargs():
parser = OptionParser(version='Mailman ' + version,
usage="""\
%prog [options]
Regenerate Mailman aliases from scratch.""")
parser.add_option('-C', '--configfile', metavar='FILE',
help=_("""\
The configuration file for Mailman's web interface. If this option is not
given, Mailman searches for the file etc/web.conf."""))
parser.add_option('-q', '--quiet', default=False, action='store_true',
help=_('Less verbosity'))
# BAW: a hack to make formatted help fit in an Emacs buffer better
parser.formatter.width -= 1
parser.formatter.help_width -= 1
opts, args = parser.parse_args()
# No positional arguments are allowed
if args:
parser.print_help()
sys.exit(1)
if opts.configfile is None:
here = os.path.abspath(os.path.dirname(__file__))
parent = os.path.normpath(os.path.join(here, '../etc'))
cfile = os.path.join(parent, 'webserver.conf')
if os.path.exists(cfile):
opts.configfile = os.path.abspath(cfile)
else:
parser.print_help()
print
print _('No configuration file found, please use -C')
sys.exit(1)
return opts, parser
class MailmanResource(resource.Resource):
isLeaf = True
def render(self, request):
# Get the first component
parts = request.path.split('/')[1:]
if not parts:
parts.append('listinfo')
# The module name is mailman.cgi.do_<part>
modname = 'mailman.cgi.do_%s' % parts[0]
try:
__import__(modname)
func = sys.modules[modname].process
except AttributeError:
log.error('bad process module: %s', modname)
return
# Start a transaction so if anything goes wrong, we can abort the
# entire creation of the list.
txnlist = txnuser = None
try:
txnlist = dbmgr.lists.begin()
txnuser = dbmgr.users.begin()
utext = func(request, parts[1:])
except:
sio = StringIO()
traceback.print_exc(file=sio)
log.error(sio.getvalue())
if txnlist: txnlist.abort()
if txnuser: txnuser.abort()
raise
else:
if txnlist: txnlist.commit()
if txnuser: txnuser.commit()
assert not dbmgr.lists._txnstack(), 'listdb: %s' % listdb._txnstack()
assert not dbmgr.users._txnstack(), 'userdb: %s' % userdb._txnstack()
if utext is None:
return
# All do_ methods return unicode strings, as is the default for all
# page templates. Twisted requires resource render() methods return
# 8-bit strings, so we need to encode the unicodes to the character
# set as specified in the request object.
#
# XXX For now, we encode everything to utf-8, but we should really
# check the Accept-Charset headers and adjust accordingly.
#
# XXX This is hinky, since .headers isn't part of the request's public
# interface.
ctype = request.headers.get('content-type')
if ctype <> 'text/css':
request.setHeader('Content-Type', 'text/html; charset="utf-8"')
return utext.encode('utf-8')
def main():
# Start the web server
site = server.Site(MailmanResource())
if configuration.webserver.interface == '*':
interface = ''
else:
interface = configuration.webserver.interface
reactor.listenTCP(configuration.webserver.port, site, interface=interface)
if not options.quiet:
print 'Ready on interface', configuration.webserver.interface, \
'port', configuration.webserver.port
reactor.run()
if __name__ == '__main__':
options, parser = parseargs()
configuration.configure('webserver', options.configfile)
startup.startup()
try:
log = logging.getLogger('mailman.error')
main()
finally:
startup.shutdown()
--- NEW FILE: withlist ---
#! /usr/bin/env python
# Copyright (C) 2003 Barry A Warsaw
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
"""Convenience script for interacting with a mailing list object."""
import os
import sys
import code
import logging
from optparse import OptionParser
import paths
from mailman import startup
from mailman.config import configuration
from mailman.config.versions import version
from mailman.errors import NoListError
from mailman.i18n import _
# `m' will be the MailList object and `r' will be the results of the callable
m = None
r = None
def do_list(listname, args, func):
# Don't import this at module scope, since it requires that the system be
# initialized first.
from mailman.mlist import MailList
global m
# first try to open mailing list
if not options.quiet:
print >> sys.stderr, _('Loading list $listname')
try:
m = MailList(listname)
except NoListError:
print >> sys.stderr, _('Unknown list: $listname')
m = None
# try to import the module and run the callable
if func:
return func(m, *args)
return None
def parseargs():
parser = OptionParser(version='Mailman ' + version,
usage=_("""\
%prog [options] [args ...]
Convenience script for interacting with a mailing list.
There are two ways to use this script: interactively or programmatically.
Using it interactively allows you to play with, examine and modify a mailing
list from Python's interactive interpreter. When running interactively, a
MailList object called `m' will be available in the global namespace.
Programmatically, you can write a function to operate on a mailing list,
and this script will take care of the housekeeping.
"""))
parser.add_option('-C', '--configfile', metavar='FILE',
help=_("""\
The configuration file for Mailman's web interface. If this option is not
given, Mailman searches for the file etc/web.conf."""))
parser.add_option('-i', '--interactive', action='store_true',
help=_("""\
Leaves you at an interactive prompt after all other processing is complete.
This is the default unless the -r option is given."""))
parser.add_option('-r', '--run', dest='callable', default=None,
help=_("""\
This can be used to run a script with the opened MailList object. This works
by attempting to import `module' (which must already be accessible on your
sys.path), and then calling `callable' from the module. callable can be a
class or function; it is called with the MailList object as the first
argument. If additional args are given on the command line, they are passed
as subsequent positional args to the callable.
Note that `module.' is optional; if it is omitted then a module with
the name `callable' will be imported.
The global variable `r' will be set to the results of this call."""))
parser.add_option('-l', '--list', action='append', metavar='LISTID',
help=_("""\
Specify the mailing list to operate on. The argument is the id of the mailing
list, of the form '[EMAIL PROTECTED]'. Multiple --list arguments are allowed,
and at least one --list argument is required if you use the -r option.
Wildcards are allowed. If the argument is just the string '*' then withlist
will iterate over all mailing lists. If '[EMAIL PROTECTED]' is used, all mailing lists
in the given domain will be operated on.
The variable `r' will be a list of all the results."""))
parser.add_option('-q', '--quiet', default=False, action='store_true',
help=_('Less verbosity'))
# BAW: a hack to make formatted help fit in an Emacs buffer better
parser.formatter.width -= 1
parser.formatter.help_width -= 1
opts, args = parser.parse_args()
# Were there lists provided on the command line?
if opts.configfile is None:
here = os.path.abspath(os.path.dirname(__file__))
parent = os.path.normpath(os.path.join(here, '../etc'))
cfile = os.path.join(parent, 'cli.conf')
if os.path.exists(cfile):
opts.configfile = os.path.abspath(cfile)
else:
parser.print_help()
print
print _('No configuration file found, please use -C')
sys.exit(1)
return opts, args, parser
def main():
# try to import the module for the callable
func = None
if options.callable:
i = options.callable.rfind('.')
if i < 0:
module = options.callable
callable = options.callable
else:
module = options.callable[:i]
callable = options.callable[i+1:]
if not options.quiet:
print >> sys.stderr, _('Importing $module...')
mod = __import__(module)
if not options.quiet:
print >> sys.stderr, _('Running ${module}.${callable}()...')
func = getattr(mod, callable)
## if options.all:
## r = [do_list(listid, func) for listid in listdb.listIds()]
## else:
## r = do_list(options.listname, args, func)
# Now go to interactive mode, perhaps
if options.interactive or not options.callable:
# Attempt to import the readline module, so we emulate the interactive
# console as closely as possible. Don't worry if it doesn't import.
# readline works by side-effect.
try:
import readline
except ImportError:
pass
namespace = globals().copy()
namespace.update(locals())
print _("The variable 'm' is the $options.listname MailList instance")
code.interact(local=namespace)
if __name__ == '__main__':
options, args, parser = parseargs()
configuration.configure('cli', options.configfile)
startup.startup()
log = logging.getLogger('mailman.error')
main()
_______________________________________________
Mailman-checkins mailing list
[EMAIL PROTECTED]
Unsubscribe: http://mail.python.org/mailman/options/mailman-checkins/archive%40jab.org