*TAG : 0.5 release
[shinken.git] / shinken / daemon.py
blob03745af00ae76ad7135dc1c8b56314e15d21feeb
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 REDIRECT_TO = getattr(os, "devnull", "/dev/null")
32 UMASK = 0
33 VERSION = "0.5"
35 class Daemon:
36 #the instances will have their own init
37 def __init__(self):
38 pass
41 def unlink(self):
42 print "Unlinking", self.pidfile
43 os.unlink(self.pidfile)
46 def findpid(self):
47 f = open(self.pidfile)
48 p = f.read()
49 f.close()
50 return int(p)
53 #Check if previous run are still launched by reading the pidfile
54 #if the pid exist by not the pid, we remove the pidfile
55 #if still exit, we can replace (kill) the other run
56 #or just bail out
57 def check_parallel_run(self, do_replace):
58 if os.path.exists(self.pidfile):
59 p = self.findpid()
60 try:
61 os.kill(p, 0)
62 except os.error, detail:
63 if detail.errno == errno.ESRCH:
64 print "stale pidfile exists. removing it."
65 os.unlink(self.pidfile)
66 else:
67 #if replace, kill the old process
68 if do_replace:
69 print "Replacing",p
70 os.kill(p, 3)
71 else:
72 raise SystemExit, "valid pidfile exists. Exiting."
75 #Make the program a daemon. It can redirect all outputs (stdout, stderr) to debug file if need
76 #or to devnull if no debug
77 def create_daemon(self, do_debug=False, debug_file=''):
78 #First we manage the file descriptor, because debug file can be
79 #relative to pwd
80 import resource
81 maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
82 if (maxfd == resource.RLIM_INFINITY):
83 maxfd = 1024
85 # Iterate through and close all file descriptors.
86 for fd in range(0, maxfd):
87 try:
88 os.close(fd)
89 except OSError:# ERROR, fd wasn't open to begin with (ignored)
90 pass
91 #If debug, all will be writen to if
92 if do_debug:
93 os.open(debug_file, os.O_CREAT | os.O_WRONLY | os.O_TRUNC)
94 else:#else, nowhere...
95 os.open(REDIRECT_TO, os.O_RDWR)
96 os.dup2(0, 1)# standard output (1)
97 os.dup2(0, 2)# standard error (2)
99 #Now the Fork/Fork
100 try:
101 pid = os.fork()
102 except OSError, e:
103 raise Exception, "%s [%d]" % (e.strerror, e.errno)
104 if (pid == 0):
105 os.setsid()
106 try:
107 pid = os.fork()
108 except OSError, e:
109 raise Exception, "%s [%d]" % (e.strerror, e.errno)
110 if (pid == 0):
111 os.chdir(self.workdir)
112 os.umask(UMASK)
113 else:
114 os._exit(0)
115 else:
116 os._exit(0)
118 #Ok, we daemonize :)
119 #We writ the pid file now
120 f = open(self.pidfile, "w")
121 f.write("%d" % os.getpid())
122 f.close()
125 #Just give the uid of a user by looking at it's name
126 def find_uid_from_name(self):
127 try:
128 return getpwnam(self.user)[2]
129 except KeyError , exp:
130 print "Error: the user", self.user, "is unknown"
131 return None
133 #Just give the gid of a group by looking at it's name
134 def find_gid_from_name(self):
135 try:
136 return getgrnam(self.group)[2]
137 except KeyError , exp:
138 print "Error: the group", self.group, "is unknown"
139 return None
142 #Change user of the running program. Just insult the admin
143 #if he wants root run (it can override). If change failed,
144 #it exit 2
145 def change_user(self, insane=False):
146 if (self.user == 'root' or self.group == 'root') and not insane:
147 print "What's ??? You want the application run under the root account?"
148 print "I am not agree with it. If you really whant it, put :"
149 print "idontcareaboutsecurity=yes"
150 print "in the config file"
151 print "Exiting"
152 sys.exit(2)
154 uid = self.find_uid_from_name()
155 gid = self.find_gid_from_name()
156 if uid == None or gid == None:
157 print "Exiting"
158 sys.exit(2)
159 try:
160 #First group, then user :)
161 os.setregid(gid, gid)
162 os.setreuid(uid, uid)
163 except OSError, e:
164 print "Error : cannot change user/group to %s/%s (%s [%d])" % (self.user, self.group, e.strerror, e.errno)
165 print "Exiting"
166 sys.exit(2)
169 #Parse self.config_file and get all properties in it
170 #If properties need a pythonization, we do it. It
171 #also put default value id the propertie is missing in
172 #the config_file
173 def parse_config_file(self):
174 properties = self.__class__.properties
175 if self.config_file != None:
176 config = ConfigParser.ConfigParser()
177 config.read(self.config_file)
178 if config._sections == {}:
179 print "Bad or missing config file : %s " % self.config_file
180 sys.exit(2)
181 for (key, value) in config.items('daemon'):
182 if key in properties and properties[key]['pythonize'] != None:
183 value = properties[key]['pythonize'](value)
184 setattr(self, key, value)
185 else:
186 print "No config file specified, use defaults parameters"
187 #Now fill all defaults where missing parameters
188 for prop in properties:
189 if not hasattr(self, prop):
190 value = properties[prop]['default']
191 if prop in properties and properties[prop]['pythonize'] != None:
192 value = properties[prop]['pythonize'](value)
193 setattr(self, prop, value)
194 print "Using default value :", prop, value
197 #Some paths can be relatives. We must have a full path by taking
198 #the config file by reference
199 def relative_paths_to_full(self, reference_path):
200 #print "Create relative paths with", reference_path
201 properties = self.__class__.properties
202 for prop in properties:
203 if 'path' in properties[prop] and properties[prop]['path']:
204 path = getattr(self, prop)
205 new_path = path
206 #Windows full paths are like c:/blabla
207 #Unixes are like /blabla
208 #So we look for : on windows, / for Unixes
209 if os.name != 'nt':
210 #os.sep = / on Unix
211 #so here if not
212 if path != '' and path[0] != os.sep :
213 new_path = reference_path + os.sep + path
214 else:
215 #os.sep = \ on windows, and we must look at c: like format
216 if len(path) > 2 and path[1] != ':':
217 new_path = reference_path + os.sep + path
218 setattr(self, prop, new_path)
219 #print "Setting %s for %s" % (new_path, prop)
222 def manage_signal(self, sig, frame):
223 print "Dummy signal function Do not use this function dumbass dev ! "
224 sys.exit(0)
227 #Set an exit function that is call when we quit
228 def set_exit_handler(self):
229 func = self.manage_signal
230 if os.name == "nt":
231 try:
232 import win32api
233 win32api.SetConsoleCtrlHandler(func, True)
234 except ImportError:
235 version = ".".join(map(str, sys.version_info[:2]))
236 raise Exception("pywin32 not installed for Python " + version)
237 else:
238 import signal
239 signal.signal(signal.SIGTERM, func)
242 def get_header(self):
243 return ["Shinken %s" % VERSION,
244 "Copyright (c) 2009-2010 :",
245 "Gabes Jean (naparuba@gmail.com)",
246 "Gerhard Lausser, Gerhard.Lausser@consol.de",
247 "License: AGPL"]
249 def print_header(self):
250 for line in self.get_header():
251 print line