*Implement in_check_period/in_notification_period for livestatus to make multisite...
[shinken.git] / shinken / daemon.py
blobf5a3b90ceab10e8be3b6a34298259e095adb75f8
1 #!/usr/bin/env python
2 #Copyright (C) 2009-2010 :
3 # Gabes Jean, naparuba@gmail.com
4 # Gerhard Lausser, Gerhard.Lausser@consol.de
6 #This file is part of Shinken.
8 #Shinken is free software: you can redistribute it and/or modify
9 #it under the terms of the GNU Affero General Public License as published by
10 #the Free Software Foundation, either version 3 of the License, or
11 #(at your option) any later version.
13 #Shinken is distributed in the hope that it will be useful,
14 #but WITHOUT ANY WARRANTY; without even the implied warranty of
15 #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 #GNU Affero General Public License for more details.
18 #You should have received a copy of the GNU Affero General Public License
19 #along with Shinken. If not, see <http://www.gnu.org/licenses/>.
21 import os, errno, sys, ConfigParser
23 if os.name != 'nt':
24 from pwd import getpwnam
25 from grp import getgrnam
28 ########################## DAEMON PART ###############################
29 # The standard I/O file descriptors are redirected to /dev/null by default.
30 if (hasattr(os, "devnull")):
31 REDIRECT_TO = os.devnull
32 else:
33 REDIRECT_TO = "/dev/null"
35 UMASK = 0
36 VERSION = "0.4"
38 class Daemon:
39 #the instances will have their own init
40 def __init__(self):
41 pass
44 def unlink(self):
45 print "Unlinking", self.pidfile
46 os.unlink(self.pidfile)
49 def findpid(self):
50 f = open(self.pidfile)
51 p = f.read()
52 f.close()
53 return int(p)
56 #Check if previous run are still launched by reading the pidfile
57 #if the pid exist by not the pid, we remove the pidfile
58 #if still exit, we can replace (kill) the other run
59 #or just bail out
60 def check_parallel_run(self, do_replace):
61 if os.path.exists(self.pidfile):
62 p = self.findpid()
63 try:
64 os.kill(p, 0)
65 except os.error, detail:
66 if detail.errno == errno.ESRCH:
67 print "stale pidfile exists. removing it."
68 os.unlink(self.pidfile)
69 else:
70 #if replace, kill the old process
71 if do_replace:
72 print "Replacing",p
73 os.kill(p, 3)
74 else:
75 raise SystemExit, "valid pidfile exists. Exiting."
78 #Make the program a daemon. It can redirect all outputs (stdout, stderr) to debug file if need
79 #or to devnull if no debug
80 def create_daemon(self, do_debug=False, debug_file=''):
81 #First we manage the file descriptor, because debug file can be
82 #relative to pwd
83 import resource
84 maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
85 if (maxfd == resource.RLIM_INFINITY):
86 maxfd = 1024
88 # Iterate through and close all file descriptors.
89 for fd in range(0, maxfd):
90 try:
91 os.close(fd)
92 except OSError:# ERROR, fd wasn't open to begin with (ignored)
93 pass
94 #If debug, all will be writen to if
95 if do_debug:
96 os.open(debug_file, os.O_CREAT | os.O_WRONLY | os.O_TRUNC)
97 else:#else, nowhere...
98 os.open(REDIRECT_TO, os.O_RDWR)
99 os.dup2(0, 1)# standard output (1)
100 os.dup2(0, 2)# standard error (2)
102 #Now the Fork/Fork
103 try:
104 pid = os.fork()
105 except OSError, e:
106 raise Exception, "%s [%d]" % (e.strerror, e.errno)
107 if (pid == 0):
108 os.setsid()
109 try:
110 pid = os.fork()
111 except OSError, e:
112 raise Exception, "%s [%d]" % (e.strerror, e.errno)
113 if (pid == 0):
114 os.chdir(self.workdir)
115 os.umask(UMASK)
116 else:
117 os._exit(0)
118 else:
119 os._exit(0)
121 #Ok, we daemonize :)
122 #We writ the pid file now
123 f = open(self.pidfile, "w")
124 f.write("%d" % os.getpid())
125 f.close()
128 #Just give the uid of a user by looking at it's name
129 def find_uid_from_name(self):
130 try:
131 return getpwnam(self.user)[2]
132 except KeyError , exp:
133 print "Error: the user", self.user, "is unknown"
134 return None
136 #Just give the gid of a group by looking at it's name
137 def find_gid_from_name(self):
138 try:
139 return getgrnam(self.group)[2]
140 except KeyError , exp:
141 print "Error: the group", self.group, "is unknown"
142 return None
145 #Change user of the running program. Just insult the admin
146 #if he wants root run (it can override). If change failed,
147 #it exit 2
148 def change_user(self, insane=False):
149 if (self.user == 'root' or self.group == 'root') and not insane:
150 print "What's ??? You want the application run under the root account?"
151 print "I am not agree with it. If you really whant it, put :"
152 print "idontcareaboutsecurity=yes"
153 print "in the config file"
154 print "Exiting"
155 sys.exit(2)
157 uid = self.find_uid_from_name()
158 gid = self.find_gid_from_name()
159 if uid == None or gid == None:
160 print "Exiting"
161 sys.exit(2)
162 try:
163 #First group, then user :)
164 os.setregid(gid, gid)
165 os.setreuid(uid, uid)
166 except OSError, e:
167 print "Error : cannot change user/group to %s/%s (%s [%d])" % (self.user, self.group, e.strerror, e.errno)
168 print "Exiting"
169 sys.exit(2)
172 #Parse self.config_file and get all properties in it
173 #If properties need a pythonization, we do it. It
174 #also put default value id the propertie is missing in
175 #the config_file
176 def parse_config_file(self):
177 properties = self.__class__.properties
178 if self.config_file != None:
179 config = ConfigParser.ConfigParser()
180 config.read(self.config_file)
181 if config._sections == {}:
182 print "Bad or missing config file : %s " % self.config_file
183 sys.exit(2)
184 for (key, value) in config.items('daemon'):
185 if key in properties and properties[key]['pythonize'] != None:
186 value = properties[key]['pythonize'](value)
187 setattr(self, key, value)
188 else:
189 print "No config file specified, use defaults parameters"
190 #Now fill all defaults where missing parameters
191 for prop in properties:
192 if not hasattr(self, prop):
193 value = properties[prop]['default']
194 if prop in properties and properties[prop]['pythonize'] != None:
195 value = properties[prop]['pythonize'](value)
196 setattr(self, prop, value)
197 print "Using default value :", prop, value
200 #Some paths can be relatives. We must have a full path by taking
201 #the config file by reference
202 def relative_paths_to_full(self, reference_path):
203 #print "Create relative paths with", reference_path
204 properties = self.__class__.properties
205 for prop in properties:
206 if 'path' in properties[prop] and properties[prop]['path']:
207 path = getattr(self, prop)
208 new_path = path
209 #Windows full paths are like c:/blabla
210 #Unixes are like /blabla
211 #So we look for : on windows, / for Unixes
212 if os.name != 'nt':
213 #os.sep = / on Unix
214 #so here if not
215 if path != '' and path[0] != os.sep :
216 new_path = reference_path + os.sep + path
217 else:
218 #os.sep = \ on windows, and we must look at c: like format
219 if len(path) > 2 and path[1] != ':':
220 new_path = reference_path + os.sep + path
221 setattr(self, prop, new_path)
222 #print "Setting %s for %s" % (new_path, prop)
225 def manage_signal(self, sig, frame):
226 print "Dummy signal function Do not use this function dumbass dev ! "
227 sys.exit(0)
230 #Set an exit function that is call when we quit
231 def set_exit_handler(self):
232 func = self.manage_signal
233 if os.name == "nt":
234 try:
235 import win32api
236 win32api.SetConsoleCtrlHandler(func, True)
237 except ImportError:
238 version = ".".join(map(str, sys.version_info[:2]))
239 raise Exception("pywin32 not installed for Python " + version)
240 else:
241 import signal
242 signal.signal(signal.SIGTERM, func)
245 def get_header(self):
246 return ["Shinken %s" % VERSION,
247 "Copyright (c) 2009-2010 :",
248 "Gabes Jean (naparuba@gmail.com)",
249 "Gerhard Lausser, Gerhard.Lausser@consol.de",
250 "License: AGPL"]
252 def print_header(self):
253 for line in self.get_header():
254 print line