| 1 | # This program is free software; you can redistribute it and/or modify it under |
|---|
| 2 | # the terms of the GNU General Public License as published by the Free Software |
|---|
| 3 | # Foundation; either version 2 of the License, or (at your option) any later |
|---|
| 4 | # version. |
|---|
| 5 | # |
|---|
| 6 | # This program is distributed in the hope that it will be useful, but WITHOUT |
|---|
| 7 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
|---|
| 8 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. |
|---|
| 9 | # |
|---|
| 10 | # You should have received a copy of the GNU General Public License along with |
|---|
| 11 | # this program; if not, write to the Free Software Foundation, Inc., |
|---|
| 12 | # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
|---|
| 13 | """ Copyright (c) 2002-2003 LOGILAB S.A. (Paris, FRANCE). |
|---|
| 14 | http://www.logilab.fr/ -- mailto:contact@logilab.fr |
|---|
| 15 | |
|---|
| 16 | a daemon mix-in class |
|---|
| 17 | """ |
|---|
| 18 | |
|---|
| 19 | __revision__ = '$Id: daemon.py,v 1.10 2005-11-22 13:13:01 syt Exp $' |
|---|
| 20 | |
|---|
| 21 | import os, signal, sys, time |
|---|
| 22 | from logilab.common.logger import make_logger, LOG_ALERT, LOG_NOTICE |
|---|
| 23 | |
|---|
| 24 | class DaemonMixIn: |
|---|
| 25 | """ mixin to make a daemon from watchers/queriers |
|---|
| 26 | """ |
|---|
| 27 | |
|---|
| 28 | def __init__(self, configmod) : |
|---|
| 29 | self.delay = configmod.DELAY |
|---|
| 30 | self.name = str(self.__class__).split('.')[-1] |
|---|
| 31 | self._pid_file = os.path.join('/tmp', '%s.pid'%self.name) |
|---|
| 32 | if os.path.exists(self._pid_file): |
|---|
| 33 | raise Exception('''Another instance of %s must be running. |
|---|
| 34 | If it i not the case, remove the file %s''' % (self.name, self._pid_file)) |
|---|
| 35 | self._alive = 1 |
|---|
| 36 | self._sleeping = 0 |
|---|
| 37 | treshold = configmod.LOG_TRESHOLD |
|---|
| 38 | if configmod.NODETACH: |
|---|
| 39 | configmod.log = make_logger('print', treshold, self.name).log |
|---|
| 40 | else: |
|---|
| 41 | configmod.log = make_logger('syslog', treshold, self.name).log |
|---|
| 42 | self.config = configmod |
|---|
| 43 | |
|---|
| 44 | def _daemonize(self): |
|---|
| 45 | if not self.config.NODETACH: |
|---|
| 46 | # fork so the parent can exist |
|---|
| 47 | if (os.fork()): |
|---|
| 48 | return -1 |
|---|
| 49 | # deconnect from tty and create a new session |
|---|
| 50 | os.setsid() |
|---|
| 51 | # fork again so the parent, (the session group leader), can exit. |
|---|
| 52 | # as a non-session group leader, we can never regain a controlling |
|---|
| 53 | # terminal. |
|---|
| 54 | if (os.fork()): |
|---|
| 55 | return -1 |
|---|
| 56 | # move to the root to avoit mount pb |
|---|
| 57 | os.chdir('/') |
|---|
| 58 | # set paranoid umask |
|---|
| 59 | os.umask(077) |
|---|
| 60 | # write pid in a file |
|---|
| 61 | f = open(self._pid_file, 'w') |
|---|
| 62 | f.write(str(os.getpid())) |
|---|
| 63 | f.close() |
|---|
| 64 | # close standard descriptors |
|---|
| 65 | sys.stdin.close() |
|---|
| 66 | sys.stdout.close() |
|---|
| 67 | sys.stderr.close() |
|---|
| 68 | # put signal handler |
|---|
| 69 | signal.signal(signal.SIGTERM, self.signal_handler) |
|---|
| 70 | signal.signal(signal.SIGHUP, self.signal_handler) |
|---|
| 71 | |
|---|
| 72 | |
|---|
| 73 | def run(self): |
|---|
| 74 | """ optionaly go in daemon mode and |
|---|
| 75 | do what concrete classe has to do and pauses for delay between runs |
|---|
| 76 | If self.delay is negative, do a pause before starting |
|---|
| 77 | """ |
|---|
| 78 | if self._daemonize() == -1: |
|---|
| 79 | return |
|---|
| 80 | self.config.log(LOG_NOTICE, '%s instance started' % self.name) |
|---|
| 81 | if self.delay < 0: |
|---|
| 82 | self.delay = -self.delay |
|---|
| 83 | time.sleep(self.delay) |
|---|
| 84 | while 1: |
|---|
| 85 | try: |
|---|
| 86 | self._run() |
|---|
| 87 | except Exception, e: |
|---|
| 88 | # display for info, sleep, and hope the problem will be solved |
|---|
| 89 | # later. |
|---|
| 90 | self.config.log(LOG_ALERT, 'Internal error: %s'%(e)) |
|---|
| 91 | if not self._alive: |
|---|
| 92 | break |
|---|
| 93 | try: |
|---|
| 94 | self._sleeping = 1 |
|---|
| 95 | time.sleep(self.delay) |
|---|
| 96 | self._sleeping = 0 |
|---|
| 97 | except SystemExit: |
|---|
| 98 | break |
|---|
| 99 | self.config.log(LOG_NOTICE, '%s instance exited'%self.name) |
|---|
| 100 | # remove pid file |
|---|
| 101 | os.remove(self._pid_file) |
|---|
| 102 | |
|---|
| 103 | def signal_handler(self, sig_num, stack_frame): |
|---|
| 104 | if sig_num == signal.SIGTERM: |
|---|
| 105 | if self._sleeping: |
|---|
| 106 | # we are sleeping so we can exit without fear |
|---|
| 107 | self.config.log(LOG_NOTICE, 'exit on SIGTERM') |
|---|
| 108 | sys.exit(0) |
|---|
| 109 | else: |
|---|
| 110 | self.config.log(LOG_NOTICE, 'exit on SIGTERM (on next turn)') |
|---|
| 111 | self._alive = 0 |
|---|
| 112 | elif sig_num == signal.SIGHUP: |
|---|
| 113 | self.config.log(LOG_NOTICE, 'reloading configuration on SIGHUP') |
|---|
| 114 | reload(self.config) |
|---|
| 115 | |
|---|
| 116 | def _run(self): |
|---|
| 117 | """should be overidden in the mixed class""" |
|---|
| 118 | raise NotImplementedError() |
|---|
| 119 | |
|---|
| 120 | ## command line utilities ###################################################### |
|---|
| 121 | |
|---|
| 122 | L_OPTIONS = ["help", "log=", "delay=", 'no-detach'] |
|---|
| 123 | S_OPTIONS = 'hl:d:n' |
|---|
| 124 | |
|---|
| 125 | def print_help(modconfig): |
|---|
| 126 | print """ --help or -h |
|---|
| 127 | displays this message |
|---|
| 128 | --log <log_level> |
|---|
| 129 | log treshold (7 record everything, 0 record only emergency.) |
|---|
| 130 | Defaults to %s |
|---|
| 131 | --delay <delay> |
|---|
| 132 | the number of seconds between two runs. |
|---|
| 133 | Defaults to %s""" % (modconfig.LOG_TRESHOLD, modconfig.DELAY) |
|---|
| 134 | |
|---|
| 135 | def handle_option(modconfig, opt_name, opt_value, help_meth): |
|---|
| 136 | if opt_name in ('-h','--help'): |
|---|
| 137 | help_meth() |
|---|
| 138 | sys.exit(0) |
|---|
| 139 | elif opt_name in ('-l','--log'): |
|---|
| 140 | modconfig.LOG_TRESHOLD = int(opt_value) |
|---|
| 141 | elif opt_name in ('-d', '--delay'): |
|---|
| 142 | modconfig.DELAY = int(opt_value) |
|---|
| 143 | elif opt_name in ('-n', '--no-detach'): |
|---|
| 144 | modconfig.NODETACH = 1 |
|---|