1#!/usr/bin/env python
2
3###
4# Copyright (c) 2005, Jeremiah Fincher
5# Copyright (c) 2009, James McCoy
6# All rights reserved.
7#
8# Redistribution and use in source and binary forms, with or without
9# modification, are permitted provided that the following conditions are met:
10#
11#   * Redistributions of source code must retain the above copyright notice,
12#     this list of conditions, and the following disclaimer.
13#   * Redistributions in binary form must reproduce the above copyright notice,
14#     this list of conditions, and the following disclaimer in the
15#     documentation and/or other materials provided with the distribution.
16#   * Neither the name of the author of this software nor the name of
17#     contributors to this software may be used to endorse or promote products
18#     derived from this software without specific prior written consent.
19#
20# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23# ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
24# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30# POSSIBILITY OF SUCH DAMAGE.
31###
32
33VERBOSE = False
34
35def readPid(filename):
36    fd = open(filename)
37    try:
38        return int(fd.read().strip())
39    finally:
40        fd.close()
41
42def isAlive(pid):
43    try:
44        os.kill(pid, 0)
45        return True
46    except OSError:
47        return False
48
49def debug(s):
50    if VERBOSE:
51        if not s.endswith(os.linesep):
52            s += os.linesep
53        sys.stdout.write(s)
54
55if __name__ == '__main__':
56    # XXX I wanted this for conf.version, but this will create directories. We
57    # really need to refactor conf so it either doesn't create directories, or
58    # so that static information (like the version) can be imported from
59    # somewhere else.
60    # import supybot.conf as conf
61    import os
62    import sys
63    import optparse
64    import subprocess
65
66    parser = optparse.OptionParser(usage='Usage: %prog [options]')
67    parser.add_option('', '--verbose', action='store_true',
68                      help='Makes output verbose.')
69    parser.add_option('', '--botdir',
70                      help='Determines what directory the bot resides in and '
71                      'should be started from.')
72    parser.add_option('', '--pidfile',
73                      help='Determines what file to look in for the pid of '
74                      'the running bot.  This should be relative to the '
75                      'given bot directory. Note that for this to actually '
76                      'work, you have to make a matching entry in the '
77                      'supybot.pidFile config in the supybot registry.')
78    parser.add_option('', '--supybot', default='supybot',
79                      help='Determines where the supybot executable is '
80                      'located.  If not given, assumes that supybot is '
81                      'in $PATH.')
82    parser.add_option('', '--conffile',
83                      help='Determines what configuration file should be '
84                      'given to the supybot executable when (re)starting the '
85                      'bot.')
86
87    (options, args) = parser.parse_args()
88    VERBOSE = options.verbose
89
90    if args:
91        parser.error('Extra arguments given.')
92    if not options.botdir:
93        parser.error('No botdir given.')
94    if not options.pidfile:
95        parser.error('No pidfile given.')
96    if not options.conffile:
97        parser.error('No conffile given.')
98
99    os.chdir(options.botdir)
100    open(options.pidfile, 'a').close()
101
102    pid = None
103    try:
104        pid = readPid(options.pidfile)
105        debug('Found pidFile with proper pid contents of %s' % pid)
106    except ValueError as e:
107        foundBot = False
108
109    if pid is not None:
110        foundBot = isAlive(pid)
111        if foundBot:
112            debug('Pid %s is alive and belongs to us.' % pid)
113        else:
114            debug('Pid %s is not the bot.' % pid)
115
116    if not foundBot:
117        # First, we check if the pidfile is writable.  If not, supybot will just exit,
118        # so we go ahead and refuse to start it.
119        try:
120            open(options.pidfile, 'r+')
121        except EnvironmentError as e:
122            debug('pidfile (%s) is not writable: %s' % (options.pidfile, e))
123            sys.exit(-1)
124        debug('Bot not found, starting.')
125        cmdline = [options.supybot, '--daemon', options.conffile]
126        inst = subprocess.Popen(cmdline, close_fds=True,
127                                stderr=subprocess.STDOUT,
128                                stdin=None, stdout=subprocess.PIPE)
129        debug('Output from supybot: %r' % inst.stdout.read())
130        ret = inst.wait()
131        debug('Bot started, command line %r returned %s.' % (' '.join(cmdline),
132                                                             ret))
133        sys.exit(ret)
134    else:
135        sys.exit(0)
136
137# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79:
138