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

Reply via email to