mirror of
				https://github.com/Mikaela/Limnoria.git
				synced 2025-11-03 17:17:23 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			340 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			340 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
#!/usr/bin/env python
 | 
						|
 | 
						|
###
 | 
						|
# Copyright (c) 2003-2004, Jeremiah Fincher
 | 
						|
# All rights reserved.
 | 
						|
#
 | 
						|
# Redistribution and use in source and binary forms, with or without
 | 
						|
# modification, are permitted provided that the following conditions are met:
 | 
						|
#
 | 
						|
#   * Redistributions of source code must retain the above copyright notice,
 | 
						|
#     this list of conditions, and the following disclaimer.
 | 
						|
#   * Redistributions in binary form must reproduce the above copyright notice,
 | 
						|
#     this list of conditions, and the following disclaimer in the
 | 
						|
#     documentation and/or other materials provided with the distribution.
 | 
						|
#   * Neither the name of the author of this software nor the name of
 | 
						|
#     contributors to this software may be used to endorse or promote products
 | 
						|
#     derived from this software without specific prior written consent.
 | 
						|
#
 | 
						|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 | 
						|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 | 
						|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 | 
						|
# ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 | 
						|
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 | 
						|
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 | 
						|
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 | 
						|
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 | 
						|
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 | 
						|
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 | 
						|
# POSSIBILITY OF SUCH DAMAGE.
 | 
						|
###
 | 
						|
 | 
						|
"""
 | 
						|
This is the main program to run Supybot.
 | 
						|
"""
 | 
						|
 | 
						|
__revision__ = "$Id$"
 | 
						|
 | 
						|
import re
 | 
						|
import os
 | 
						|
import sys
 | 
						|
import atexit
 | 
						|
import shutil
 | 
						|
import signal
 | 
						|
import cStringIO as StringIO
 | 
						|
 | 
						|
if sys.version_info < (2, 3, 0):
 | 
						|
    sys.stderr.write('This program requires Python >= 2.3.0\n')
 | 
						|
    sys.exit(-1)
 | 
						|
 | 
						|
def _termHandler(signalNumber, stackFrame):
 | 
						|
    raise SystemExit, 'Signal #%s' % signalNumber
 | 
						|
 | 
						|
signal.signal(signal.SIGTERM, _termHandler)
 | 
						|
 | 
						|
import time
 | 
						|
import optparse
 | 
						|
import textwrap
 | 
						|
 | 
						|
started = time.time()
 | 
						|
 | 
						|
import supybot
 | 
						|
import supybot.utils as utils
 | 
						|
import supybot.registry as registry
 | 
						|
import supybot.questions as questions
 | 
						|
 | 
						|
def main():
 | 
						|
    import supybot.conf as conf
 | 
						|
    import supybot.world as world
 | 
						|
    import supybot.drivers as drivers
 | 
						|
    import supybot.schedule as schedule
 | 
						|
    # We schedule this event rather than have it actually run because if there
 | 
						|
    # is a failure between now and the time it takes the Owner plugin to load
 | 
						|
    # all the various plugins, our registry file might be wiped.  That's bad.
 | 
						|
    interrupted = False
 | 
						|
    when = time.time() + conf.supybot.upkeepInterval()
 | 
						|
    schedule.addPeriodicEvent(world.upkeep, when, name='upkeep', now=False)
 | 
						|
    world.startedAt = started
 | 
						|
    while world.ircs:
 | 
						|
        try:
 | 
						|
            drivers.run()
 | 
						|
        except KeyboardInterrupt:
 | 
						|
            if interrupted:
 | 
						|
                # Interrupted while waiting for queues to clear.  Let's clear
 | 
						|
                # them ourselves.
 | 
						|
                for irc in world.ircs:
 | 
						|
                    irc._reallyDie()
 | 
						|
                    continue
 | 
						|
            else:
 | 
						|
                interrupted = True
 | 
						|
                log.info('Exiting due to Ctrl-C.  '
 | 
						|
                         'If the bot doesn\'t exit within a few seconds, '
 | 
						|
                         'feel free to press Ctrl-C again to make it exit '
 | 
						|
                         'without flushing its message queues.')
 | 
						|
                world.upkeep()
 | 
						|
                for irc in world.ircs:
 | 
						|
                    irc.queueMsg(ircmsgs.quit('Ctrl-C at console.'))
 | 
						|
                    irc.die()
 | 
						|
        except SystemExit, e:
 | 
						|
            s = str(e)
 | 
						|
            if s:
 | 
						|
                log.info('Exiting due to %s', s)
 | 
						|
            break
 | 
						|
        except:
 | 
						|
            try: # Ok, now we're *REALLY* paranoid!
 | 
						|
                log.exception('Exception raised out of drivers.run:')
 | 
						|
            except Exception, e:
 | 
						|
                print 'Exception raised in log.exception.  This is *really*'
 | 
						|
                print 'bad.  Hopefully it won\'t happen again, but tell us'
 | 
						|
                print 'about it anyway, this is a significant problem.'
 | 
						|
                print 'Anyway, here\'s the exception: %s'% utils.exnToString(e)
 | 
						|
            except:
 | 
						|
                print 'Man, this really sucks.  Not only did log.exception'
 | 
						|
                print 'raise an exception, but freaking-a, it was a string'
 | 
						|
                print 'exception.  People who raise string exceptions should'
 | 
						|
                print 'die a slow, painful death.'
 | 
						|
    now = time.time()
 | 
						|
    seconds = now - world.startedAt
 | 
						|
    log.info('Total uptime: %s.', utils.timeElapsed(seconds))
 | 
						|
    (user, system, _, _, _) = os.times()
 | 
						|
    log.info('Total CPU time taken: %s seconds.', user+system)
 | 
						|
    log.info('No more Irc objects, exiting.')
 | 
						|
 | 
						|
if __name__ == '__main__':
 | 
						|
    ###
 | 
						|
    # Options:
 | 
						|
    # -p (profiling)
 | 
						|
    # -O (optimizing)
 | 
						|
    # -n, --nick (nick)
 | 
						|
    # --startup (commands to run onStart)
 | 
						|
    # --connect (commands to run afterConnect)
 | 
						|
    # --config (configuration values)
 | 
						|
    parser = optparse.OptionParser(usage='Usage: %prog [options] configFile',
 | 
						|
                                   version='supybot 0.79.9999+cvs')
 | 
						|
    parser.add_option('-P', '--profile', action='store_true', dest='profile',
 | 
						|
                      help='enables profiling')
 | 
						|
    parser.add_option('-O', action='count', dest='optimize',
 | 
						|
                      help='-O optimizes asserts out of the code; ' \
 | 
						|
                           '-OO optimizes asserts and uses psyco.')
 | 
						|
    parser.add_option('-n', '--nick', action='store',
 | 
						|
                      dest='nick', default='',
 | 
						|
                      help='nick the bot should use')
 | 
						|
    parser.add_option('-u', '--user', action='store',
 | 
						|
                      dest='user', default='',
 | 
						|
                      help='full username the bot should use')
 | 
						|
    parser.add_option('-i', '--ident', action='store',
 | 
						|
                      dest='ident', default='',
 | 
						|
                      help='ident the bot should use')
 | 
						|
    parser.add_option('-d', '--daemon', action='store_true',
 | 
						|
                      dest='daemon',
 | 
						|
                      help='Determines whether the bot will daemonize.  '
 | 
						|
                           'This is a no-op on non-POSIX systems.')
 | 
						|
    parser.add_option('', '--allow-eval', action='store_true',
 | 
						|
                      dest='allowEval',
 | 
						|
                      help='Determines whether the bot will '
 | 
						|
                           'allow the evaluation of arbitrary Python code.')
 | 
						|
    parser.add_option('', '--allow-default-owner', action='store_true',
 | 
						|
                      dest='allowDefaultOwner',
 | 
						|
                      help='Determines whether the bot will allow its '
 | 
						|
                           'defaultCapabilities not to include "-owner", thus '
 | 
						|
                           'giving all users the owner capability by default. '
 | 
						|
                           ' This is dumb, hence we require a command-line '
 | 
						|
                           'option.  Don\'t do this.')
 | 
						|
    parser.add_option('', '--allow-root', action='store_true',
 | 
						|
                      dest='allowRoot',
 | 
						|
                      help='Determines whether the bot will be allowed to run '
 | 
						|
                           'as root.  You don\'t want this.  Don\'t do it.  '
 | 
						|
                           'Even if you think you want it, you don\'t.  '
 | 
						|
                           'You\'re probably dumb if you do this.')
 | 
						|
    parser.add_option('', '--debug', action='store_true', dest='debug',
 | 
						|
                      help='Determines whether some extra debugging stuff will '
 | 
						|
                           'be logged in this script.')
 | 
						|
 | 
						|
    (options, args) = parser.parse_args()
 | 
						|
 | 
						|
    if os.name == 'posix':
 | 
						|
        if (os.getuid() == 0 or os.geteuid() == 0) and not options.allowRoot:
 | 
						|
            sys.stderr.write('Dude, don\'t even try to run this as root.\n')
 | 
						|
            sys.exit(-1)
 | 
						|
 | 
						|
    if len(args) > 1:
 | 
						|
        parser.error("""Only one configuration option should be specified.""")
 | 
						|
    elif not args:
 | 
						|
        parser.error(utils.normalizeWhitespace("""It seems you've given me no
 | 
						|
        configuration file.  If you have a configuration file, be sure to tell
 | 
						|
        its filename.  If you don't have a configuration file, read
 | 
						|
        docs/GETTING_STARTED and follow its directions."""))
 | 
						|
    else:
 | 
						|
        registryFilename = args.pop()
 | 
						|
        try:
 | 
						|
            # The registry *MUST* be opened before importing log or conf.
 | 
						|
            registry.open(registryFilename)
 | 
						|
            shutil.copy(registryFilename, registryFilename + '.bak')
 | 
						|
        except registry.InvalidRegistryFile, e:
 | 
						|
            s = '%s in %s.  Please fix this error and start supybot again.' % \
 | 
						|
                (e, registryFilename)
 | 
						|
            s = textwrap.fill(s)
 | 
						|
            sys.stderr.write(s)
 | 
						|
            sys.stderr.write(os.linesep)
 | 
						|
            raise
 | 
						|
            sys.exit(-1)
 | 
						|
        except EnvironmentError, e:
 | 
						|
            sys.stderr.write(str(e))
 | 
						|
            sys.stderr.write(os.linesep)
 | 
						|
            sys.exit(-1)
 | 
						|
 | 
						|
    import supybot.log as log
 | 
						|
    import supybot.conf as conf
 | 
						|
    import supybot.world as world
 | 
						|
    world.starting = True
 | 
						|
 | 
						|
    def closeRegistry():
 | 
						|
        # We only print if world.dying so we don't see these messages during
 | 
						|
        # upkeep.
 | 
						|
        if world.dying:
 | 
						|
            log.info('Writing registry file to %s', registryFilename)
 | 
						|
        registry.close(conf.supybot, registryFilename, annotated=True)
 | 
						|
        if world.dying:
 | 
						|
            log.info('Finished writing registry file.')
 | 
						|
    world.flushers.append(closeRegistry)
 | 
						|
    world.registryFilename = registryFilename
 | 
						|
 | 
						|
    nick = options.nick or conf.supybot.nick()
 | 
						|
    user = options.user or conf.supybot.user()
 | 
						|
    ident = options.ident or conf.supybot.ident()
 | 
						|
 | 
						|
    networks = conf.supybot.networks()
 | 
						|
    if not networks:
 | 
						|
        questions.output("""No networks defined.  Perhaps you should re-run the
 | 
						|
        wizard?""", fd=sys.stderr)
 | 
						|
        # XXX We should turn off logging here for a prettier presentation.
 | 
						|
        sys.exit(-1)
 | 
						|
 | 
						|
 | 
						|
 | 
						|
    if options.optimize:
 | 
						|
        # This doesn't work anymore.
 | 
						|
        __builtins__.__debug__ = False
 | 
						|
        if options.optimize > 1:
 | 
						|
            try:
 | 
						|
                import psyco
 | 
						|
                psyco.full()
 | 
						|
            except ImportError:
 | 
						|
                log.warning('Psyco isn\'t installed, cannot -OO.')
 | 
						|
 | 
						|
    conf.allowEval = options.allowEval
 | 
						|
    conf.allowDefaultOwner = options.allowDefaultOwner
 | 
						|
 | 
						|
    if not os.path.exists(conf.supybot.directories.log()):
 | 
						|
        os.mkdir(conf.supybot.directories.log())
 | 
						|
    if not os.path.exists(conf.supybot.directories.conf()):
 | 
						|
        os.mkdir(conf.supybot.directories.conf())
 | 
						|
    if not os.path.exists(conf.supybot.directories.data()):
 | 
						|
        os.mkdir(conf.supybot.directories.data())
 | 
						|
    if not os.path.exists(conf.supybot.directories.data.tmp()):
 | 
						|
        os.mkdir(conf.supybot.directories.tmp())
 | 
						|
 | 
						|
    userdataFilename = os.path.join(conf.supybot.directories.conf(),
 | 
						|
                                    'userdata.conf')
 | 
						|
    # Let's open this now since we've got our directories setup.
 | 
						|
    if not os.path.exists(userdataFilename):
 | 
						|
        fd = file(userdataFilename, 'w')
 | 
						|
        fd.write('\n')
 | 
						|
        fd.close()
 | 
						|
    registry.open(userdataFilename)
 | 
						|
 | 
						|
    import supybot.Owner as Owner
 | 
						|
    import supybot.irclib as irclib
 | 
						|
    import supybot.ircmsgs as ircmsgs
 | 
						|
    import supybot.drivers as drivers
 | 
						|
    import supybot.callbacks as callbacks
 | 
						|
 | 
						|
    owner = Owner.Class()
 | 
						|
    irclib._callbacks.append(owner)
 | 
						|
 | 
						|
    if options.debug:
 | 
						|
        for (name, module) in sys.modules.iteritems():
 | 
						|
            if hasattr(module, '__file__') and hasattr(module, '__revision__'):
 | 
						|
                if module.__file__.startswith(supybot.installDir):
 | 
						|
                    print '%s: %s' % (name, module.__revision__.split()[2])
 | 
						|
 | 
						|
    if os.name == 'posix' and options.daemon:
 | 
						|
        def fork():
 | 
						|
            child = os.fork()
 | 
						|
            if child != 0:
 | 
						|
                if options.debug:
 | 
						|
                    print 'Parent exiting, child PID: %s' % child
 | 
						|
                # We must us os._exit instead of sys.exit so atexit handlers
 | 
						|
                # don't run.  They shouldn't be dangerous, but they're ugly.
 | 
						|
                os._exit(0)
 | 
						|
        fork()
 | 
						|
        os.setsid()
 | 
						|
        # What the heck does this do?  I wonder if it breaks anything...
 | 
						|
        os.umask(0)
 | 
						|
        # Let's not do this for now (at least until I can make sure it works):
 | 
						|
        # Actually, let's never do this -- we'll always have files open in the
 | 
						|
        # bot directories, so they won't be able to be unmounted anyway.
 | 
						|
        # os.chdir('/')
 | 
						|
        fork()
 | 
						|
        # Since this is the indicator that no writing should be done to stdout,
 | 
						|
        # we'll set it to True before closing stdout et alii.
 | 
						|
        conf.daemonized = True
 | 
						|
        # Closing stdin shouldn't cause problems.  We'll let it raise an
 | 
						|
        # exception if it does.
 | 
						|
        sys.stdin.close()
 | 
						|
        # Closing these two might cause problems; we log writes to them as
 | 
						|
        # level WARNING on upkeep.
 | 
						|
        sys.stdout.close()
 | 
						|
        sys.stderr.close()
 | 
						|
        sys.stdout = StringIO.StringIO()
 | 
						|
        sys.stderr = StringIO.StringIO()
 | 
						|
        signal.signal(signal.SIGHUP, signal.SIG_IGN)
 | 
						|
        log.info('Completed daemonization.  Current PID: %s', os.getpid())
 | 
						|
 | 
						|
    # Let's write the PID file.  This has to go after daemonization, obviously.
 | 
						|
    pidFile = conf.supybot.pidFile()
 | 
						|
    if pidFile:
 | 
						|
        try:
 | 
						|
            fd = file(pidFile, 'w')
 | 
						|
            pid = os.getpid()
 | 
						|
            fd.write('%s\n' % pid)
 | 
						|
            fd.close()
 | 
						|
            def removePidFile():
 | 
						|
                try:
 | 
						|
                    os.remove(pidFile)
 | 
						|
                except EnvironmentError, e:
 | 
						|
                    log.error('Could not remove pid file: %s', e)
 | 
						|
            atexit.register(removePidFile)
 | 
						|
        except EnvironmentError, e:
 | 
						|
            log.error('Error opening pid file %s: %s', pidFile, e)
 | 
						|
 | 
						|
    if options.profile:
 | 
						|
        import profile
 | 
						|
        world.profiling = True
 | 
						|
        profile.run('main()', '%s-%i.prof' % (nick, time.time()))
 | 
						|
    else:
 | 
						|
        main()
 | 
						|
 | 
						|
 | 
						|
# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78:
 |