mirror of
				https://github.com/Mikaela/Limnoria.git
				synced 2025-11-02 16:47:26 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			342 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			342 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
#!/usr/bin/env python
 | 
						|
 | 
						|
###
 | 
						|
# Copyright (c) 2003-2004, Jeremiah Fincher
 | 
						|
# Copyright (c) 2009, James Vega
 | 
						|
# 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.
 | 
						|
"""
 | 
						|
 | 
						|
import supybot
 | 
						|
 | 
						|
import re
 | 
						|
import os
 | 
						|
import sys
 | 
						|
import atexit
 | 
						|
import shutil
 | 
						|
import signal
 | 
						|
import cStringIO as StringIO
 | 
						|
 | 
						|
if sys.version_info < (2, 4, 0):
 | 
						|
    sys.stderr.write('This program requires Python >= 2.4.0')
 | 
						|
    sys.stderr.write(os.linesep)
 | 
						|
    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.i18n as i18n
 | 
						|
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 = 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:
 | 
						|
                    quitmsg = conf.supybot.plugins.Owner.quitMsg() or \
 | 
						|
                              'Ctrl-C at console.'
 | 
						|
                    irc.queueMsg(ircmsgs.quit(quitmsg))
 | 
						|
                    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.gen.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.gen.timeElapsed(seconds))
 | 
						|
    (user, system, _, _, _) = os.times()
 | 
						|
    log.info('Total CPU time taken: %s seconds.', user+system)
 | 
						|
    log.info('No more Irc objects, exiting.')
 | 
						|
 | 
						|
version = '0.83.4.1+git+fr1'
 | 
						|
if __name__ == '__main__':
 | 
						|
    parser = optparse.OptionParser(usage='Usage: %prog [options] configFile',
 | 
						|
                                   version='Supybot %s' % version)
 | 
						|
    parser.add_option('-P', '--profile', action='store_true', dest='profile',
 | 
						|
                      help='enables profiling')
 | 
						|
    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-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.')
 | 
						|
            sys.stderr.write(os.linesep)
 | 
						|
            sys.exit(-1)
 | 
						|
 | 
						|
    if len(args) > 1:
 | 
						|
        parser.error("""Only one configuration file should be specified.""")
 | 
						|
    elif not args:
 | 
						|
        parser.error(utils.str.normalizeWhitespace("""It seems you've given me
 | 
						|
        no configuration file.  If you do have a configuration file, be sure to
 | 
						|
        specify the filename.  If you don't have a configuration file, read
 | 
						|
        docs/GETTING_STARTED and follow the instructions."""))
 | 
						|
    else:
 | 
						|
        registryFilename = args.pop()
 | 
						|
        i18n.getLocaleFromRegistryFilename(registryFilename)
 | 
						|
        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)
 | 
						|
 | 
						|
    try:
 | 
						|
        import supybot.log as log
 | 
						|
    except supybot.registry.InvalidRegistryValue, e:
 | 
						|
        # This is raised here because supybot.log imports supybot.conf.
 | 
						|
        name = e.value._name
 | 
						|
        errmsg = textwrap.fill('%s: %s' % (name, e),
 | 
						|
                               width=78, subsequent_indent=' '*len(name))
 | 
						|
        sys.stderr.write(errmsg)
 | 
						|
        sys.stderr.write(os.linesep)
 | 
						|
        sys.stderr.write('Please fix this error in your configuration file '
 | 
						|
                         'and restart your bot.')
 | 
						|
        sys.stderr.write(os.linesep)
 | 
						|
        sys.exit(-1)
 | 
						|
    import supybot.conf as conf
 | 
						|
    import supybot.world as world
 | 
						|
    i18n.import_conf()
 | 
						|
    world.starting = True
 | 
						|
 | 
						|
    def closeRegistry():
 | 
						|
        # We only print if world.dying so we don't see these messages during
 | 
						|
        # upkeep.
 | 
						|
        logger = log.debug
 | 
						|
        if world.dying:
 | 
						|
            logger = log.info
 | 
						|
        logger('Writing registry file to %s', registryFilename)
 | 
						|
        registry.close(conf.supybot, registryFilename)
 | 
						|
        logger('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 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...
 | 
						|
        # ...It did.  I don't know why, but it seems largely useless.  It seems
 | 
						|
        # to me reasonable that we should respect the user's umask.
 | 
						|
        #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()
 | 
						|
        # We have to be really methodical here.
 | 
						|
        os.close(0)
 | 
						|
        os.close(1)
 | 
						|
        os.close(2)
 | 
						|
        fd = os.open('/dev/null', os.O_RDWR)
 | 
						|
        os.dup2(fd, 0)
 | 
						|
        os.dup2(fd, 1)
 | 
						|
        os.dup2(fd, 2)
 | 
						|
        signal.signal(signal.SIGHUP, signal.SIG_IGN)
 | 
						|
        log.info('Completed daemonization.  Current PID: %s', os.getpid())
 | 
						|
 | 
						|
    # Stop setting our own umask.  See comment above.
 | 
						|
    #os.umask(077)
 | 
						|
 | 
						|
    # 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%s' % (pid, os.linesep))
 | 
						|
            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.fatal('Error opening/writing pid file %s: %s', pidFile, e)
 | 
						|
            sys.exit(-1)
 | 
						|
 | 
						|
    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.irclib as irclib
 | 
						|
    import supybot.ircmsgs as ircmsgs
 | 
						|
    import supybot.drivers as drivers
 | 
						|
    import supybot.callbacks as callbacks
 | 
						|
    import supybot.plugins.Owner as Owner
 | 
						|
 | 
						|
    owner = Owner.Class()
 | 
						|
 | 
						|
    if options.profile:
 | 
						|
        import profile
 | 
						|
        world.profiling = True
 | 
						|
        profile.run('main()', '%s-%i.prof' % (nick, time.time()))
 | 
						|
    else:
 | 
						|
        main()
 | 
						|
 | 
						|
 | 
						|
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79:
 |