OLD | NEW |
| (Empty) |
1 # -*- test-case-name: twisted.test.test_twistd -*- | |
2 # Copyright (c) 2001-2008 Twisted Matrix Laboratories. | |
3 # See LICENSE for details. | |
4 | |
5 import warnings | |
6 import os, errno, sys | |
7 | |
8 from twisted.python import log, syslog, logfile | |
9 from twisted.python.util import switchUID, uidFromString, gidFromString | |
10 from twisted.application import app, service | |
11 from twisted import copyright | |
12 | |
13 | |
14 class ServerOptions(app.ServerOptions): | |
15 synopsis = "Usage: twistd [options]" | |
16 | |
17 optFlags = [['nodaemon','n', "don't daemonize"], | |
18 ['quiet', 'q', "No-op for backwards compatibility."], | |
19 ['originalname', None, "Don't try to change the process name"], | |
20 ['syslog', None, "Log to syslog, not to file"], | |
21 ['euid', '', | |
22 "Set only effective user-id rather than real user-id. " | |
23 "(This option has no effect unless the server is running as " | |
24 "root, in which case it means not to shed all privileges " | |
25 "after binding ports, retaining the option to regain " | |
26 "privileges in cases such as spawning processes. " | |
27 "Use with caution.)"], | |
28 ] | |
29 | |
30 optParameters = [ | |
31 ['prefix', None,'twisted', | |
32 "use the given prefix when syslogging"], | |
33 ['pidfile','','twistd.pid', | |
34 "Name of the pidfile"], | |
35 ['chroot', None, None, | |
36 'Chroot to a supplied directory before running'], | |
37 ['uid', 'u', None, "The uid to run as.", uidFromString], | |
38 ['gid', 'g', None, "The gid to run as.", gidFromString], | |
39 ] | |
40 zsh_altArgDescr = {"prefix":"Use the given prefix when syslogging (default:
twisted)", | |
41 "pidfile":"Name of the pidfile (default: twistd.pid)",} | |
42 #zsh_multiUse = ["foo", "bar"] | |
43 #zsh_mutuallyExclusive = [("foo", "bar"), ("bar", "baz")] | |
44 zsh_actions = {"pidfile":'_files -g "*.pid"', "chroot":'_dirs'} | |
45 zsh_actionDescr = {"chroot":"chroot directory"} | |
46 | |
47 def opt_version(self): | |
48 """Print version information and exit. | |
49 """ | |
50 print 'twistd (the Twisted daemon) %s' % copyright.version | |
51 print copyright.copyright | |
52 sys.exit() | |
53 | |
54 | |
55 def postOptions(self): | |
56 app.ServerOptions.postOptions(self) | |
57 if self['pidfile']: | |
58 self['pidfile'] = os.path.abspath(self['pidfile']) | |
59 | |
60 | |
61 def checkPID(pidfile): | |
62 if not pidfile: | |
63 return | |
64 if os.path.exists(pidfile): | |
65 try: | |
66 pid = int(open(pidfile).read()) | |
67 except ValueError: | |
68 sys.exit('Pidfile %s contains non-numeric value' % pidfile) | |
69 try: | |
70 os.kill(pid, 0) | |
71 except OSError, why: | |
72 if why[0] == errno.ESRCH: | |
73 # The pid doesnt exists. | |
74 log.msg('Removing stale pidfile %s' % pidfile, isError=True) | |
75 os.remove(pidfile) | |
76 else: | |
77 sys.exit("Can't check status of PID %s from pidfile %s: %s" % | |
78 (pid, pidfile, why[1])) | |
79 else: | |
80 sys.exit("""\ | |
81 Another twistd server is running, PID %s\n | |
82 This could either be a previously started instance of your application or a | |
83 different application entirely. To start a new one, either run it in some other | |
84 directory, or use the --pidfile and --logfile parameters to avoid clashes. | |
85 """ % pid) | |
86 | |
87 def _getLogObserver(logfilename, sysLog, prefix, nodaemon): | |
88 """ | |
89 Create and return a suitable log observer for the given configuration. | |
90 | |
91 The observer will go to syslog using the prefix C{prefix} if C{sysLog} is | |
92 true. Otherwise, it will go to the file named C{logfilename} or, if | |
93 C{nodaemon} is true and C{logfilename} is C{"-"}, to stdout. | |
94 | |
95 @type logfilename: C{str} | |
96 @param logfilename: The name of the file to which to log, if other than the | |
97 default. | |
98 | |
99 @type sysLog: C{bool} | |
100 @param sysLog: A flag indicating whether to use syslog instead of file | |
101 logging. | |
102 | |
103 @type prefix: C{str} | |
104 @param prefix: If C{sysLog} is C{True}, the string prefix to use for syslog | |
105 messages. | |
106 | |
107 @type nodaemon: C{bool} | |
108 @param nodaemon: A flag indicating the process will not be daemonizing. | |
109 | |
110 @return: An object suitable to be passed to C{log.addObserver}. | |
111 """ | |
112 if sysLog: | |
113 observer = syslog.SyslogObserver(prefix).emit | |
114 else: | |
115 if logfilename == '-': | |
116 if not nodaemon: | |
117 print 'daemons cannot log to stdout' | |
118 os._exit(1) | |
119 logFile = sys.stdout | |
120 elif nodaemon and not logfilename: | |
121 logFile = sys.stdout | |
122 else: | |
123 logFile = logfile.LogFile.fromFullPath(logfilename or 'twistd.log') | |
124 try: | |
125 import signal | |
126 except ImportError: | |
127 pass | |
128 else: | |
129 def rotateLog(signal, frame): | |
130 from twisted.internet import reactor | |
131 reactor.callFromThread(logFile.rotate) | |
132 signal.signal(signal.SIGUSR1, rotateLog) | |
133 observer = log.FileLogObserver(logFile).emit | |
134 return observer | |
135 | |
136 | |
137 def startLogging(*args, **kw): | |
138 warnings.warn( | |
139 """ | |
140 Use ApplicationRunner instead of startLogging. | |
141 """, | |
142 category=PendingDeprecationWarning, | |
143 stacklevel=2) | |
144 observer = _getLogObserver(*args, **kw) | |
145 log.startLoggingWithObserver(observer) | |
146 sys.stdout.flush() | |
147 | |
148 | |
149 def daemonize(): | |
150 # See http://www.erlenstar.demon.co.uk/unix/faq_toc.html#TOC16 | |
151 if os.fork(): # launch child and... | |
152 os._exit(0) # kill off parent | |
153 os.setsid() | |
154 if os.fork(): # launch child and... | |
155 os._exit(0) # kill off parent again. | |
156 os.umask(077) | |
157 null = os.open('/dev/null', os.O_RDWR) | |
158 for i in range(3): | |
159 try: | |
160 os.dup2(null, i) | |
161 except OSError, e: | |
162 if e.errno != errno.EBADF: | |
163 raise | |
164 os.close(null) | |
165 | |
166 | |
167 def launchWithName(name): | |
168 if name and name != sys.argv[0]: | |
169 exe = os.path.realpath(sys.executable) | |
170 log.msg('Changing process name to ' + name) | |
171 os.execv(exe, [name, sys.argv[0], '--originalname'] + sys.argv[1:]) | |
172 | |
173 | |
174 class UnixApplicationRunner(app.ApplicationRunner): | |
175 """ | |
176 An ApplicationRunner which does Unix-specific things, like fork, | |
177 shed privileges, and maintain a PID file. | |
178 """ | |
179 | |
180 def preApplication(self): | |
181 """ | |
182 Do pre-application-creation setup. | |
183 """ | |
184 checkPID(self.config['pidfile']) | |
185 self.config['nodaemon'] = (self.config['nodaemon'] | |
186 or self.config['debug']) | |
187 self.oldstdout = sys.stdout | |
188 self.oldstderr = sys.stderr | |
189 | |
190 | |
191 def getLogObserver(self): | |
192 """ | |
193 Override to supply a log observer suitable for POSIX based on the given | |
194 arguments. | |
195 """ | |
196 return _getLogObserver( | |
197 self.config['logfile'], self.config['syslog'], | |
198 self.config['prefix'], self.config['nodaemon']) | |
199 | |
200 | |
201 def postApplication(self): | |
202 """ | |
203 To be called after the application is created: start the | |
204 application and run the reactor. After the reactor stops, | |
205 clean up PID files and such. | |
206 """ | |
207 self.startApplication(self.application) | |
208 self.startReactor(None, self.oldstdout, self.oldstderr) | |
209 self.removePID(self.config['pidfile']) | |
210 log.msg("Server Shut Down.") | |
211 | |
212 | |
213 def removePID(self, pidfile): | |
214 """ | |
215 Remove the specified PID file, if possible. Errors are logged, not | |
216 raised. | |
217 | |
218 @type pidfile: C{str} | |
219 @param pidfile: The path to the PID tracking file. | |
220 """ | |
221 if not pidfile: | |
222 return | |
223 try: | |
224 os.unlink(pidfile) | |
225 except OSError, e: | |
226 if e.errno == errno.EACCES or e.errno == errno.EPERM: | |
227 log.msg("Warning: No permission to delete pid file") | |
228 else: | |
229 log.msg("Failed to unlink PID file:") | |
230 log.deferr() | |
231 except: | |
232 log.msg("Failed to unlink PID file:") | |
233 log.deferr() | |
234 | |
235 | |
236 def setupEnvironment(self, chroot, rundir, nodaemon, pidfile): | |
237 """ | |
238 Set the filesystem root, the working directory, and daemonize. | |
239 | |
240 @type chroot: C{str} or L{NoneType} | |
241 @param chroot: If not None, a path to use as the filesystem root (using | |
242 L{os.chroot}). | |
243 | |
244 @type rundir: C{str} | |
245 @param rundir: The path to set as the working directory. | |
246 | |
247 @type nodaemon: C{bool} | |
248 @param nodaemon: A flag which, if set, indicates that daemonization | |
249 should not be done. | |
250 | |
251 @type pidfile: C{str} or C{NoneType} | |
252 @param pidfile: If not C{None}, the path to a file into which to put | |
253 the PID of this process. | |
254 """ | |
255 if chroot is not None: | |
256 os.chroot(chroot) | |
257 if rundir == '.': | |
258 rundir = '/' | |
259 os.chdir(rundir) | |
260 if not nodaemon: | |
261 daemonize() | |
262 if pidfile: | |
263 open(pidfile,'wb').write(str(os.getpid())) | |
264 | |
265 | |
266 def shedPrivileges(self, euid, uid, gid): | |
267 """ | |
268 Change the UID and GID or the EUID and EGID of this process. | |
269 | |
270 @type euid: C{bool} | |
271 @param euid: A flag which, if set, indicates that only the I{effective} | |
272 UID and GID should be set. | |
273 | |
274 @type uid: C{int} or C{NoneType} | |
275 @param uid: If not C{None}, the UID to which to switch. | |
276 | |
277 @type gid: C{int} or C{NoneType} | |
278 @param gid: If not C{None}, the GID to which to switch. | |
279 """ | |
280 if uid is not None or gid is not None: | |
281 switchUID(uid, gid, euid) | |
282 extra = euid and 'e' or '' | |
283 log.msg('set %suid/%sgid %s/%s' % (extra, extra, uid, gid)) | |
284 | |
285 | |
286 def startApplication(self, application): | |
287 """ | |
288 Configure global process state based on the given application and run | |
289 the application. | |
290 | |
291 @param application: An object which can be adapted to | |
292 L{service.IProcess} and L{service.IService}. | |
293 """ | |
294 process = service.IProcess(application) | |
295 if not self.config['originalname']: | |
296 launchWithName(process.processName) | |
297 self.setupEnvironment( | |
298 self.config['chroot'], self.config['rundir'], | |
299 self.config['nodaemon'], self.config['pidfile']) | |
300 | |
301 service.IService(application).privilegedStartService() | |
302 | |
303 uid, gid = self.config['uid'], self.config['gid'] | |
304 if uid is None: | |
305 uid = process.uid | |
306 if gid is None: | |
307 gid = process.gid | |
308 | |
309 self.shedPrivileges(self.config['euid'], uid, gid) | |
310 app.startApplication(application, not self.config['no_save']) | |
311 | |
312 | |
313 | |
OLD | NEW |