1 # -*- test-case-name: twisted.test.test_twistd -*-
2 # Copyright (c) Twisted Matrix Laboratories.
3 # See LICENSE for details.
7 from twisted.python import log, syslog, logfile, usage
8 from twisted.python.util import switchUID, uidFromString, gidFromString
9 from twisted.application import app, service
10 from twisted.internet.interfaces import IReactorDaemonize
11 from twisted import copyright
18 class ServerOptions(app.ServerOptions):
19 synopsis = "Usage: twistd [options]"
21 optFlags = [['nodaemon','n', "don't daemonize, don't use default umask of 0077"],
22 ['originalname', None, "Don't try to change the process name"],
23 ['syslog', None, "Log to syslog, not to file"],
25 "Set only effective user-id rather than real user-id. "
26 "(This option has no effect unless the server is running as "
27 "root, in which case it means not to shed all privileges "
28 "after binding ports, retaining the option to regain "
29 "privileges in cases such as spawning processes. "
30 "Use with caution.)"],
34 ['prefix', None,'twisted',
35 "use the given prefix when syslogging"],
36 ['pidfile','','twistd.pid',
37 "Name of the pidfile"],
38 ['chroot', None, None,
39 'Chroot to a supplied directory before running'],
40 ['uid', 'u', None, "The uid to run as.", uidFromString],
41 ['gid', 'g', None, "The gid to run as.", gidFromString],
43 "The (octal) file creation mask to apply.", _umask],
46 compData = usage.Completions(
47 optActions={"pidfile": usage.CompleteFiles("*.pid"),
48 "chroot": usage.CompleteDirs(descr="chroot directory"),
49 "gid": usage.CompleteGroups(descr="gid to run as"),
50 "uid": usage.CompleteUsernames(descr="uid to run as"),
51 "prefix": usage.Completer(descr="syslog prefix"),
55 def opt_version(self):
56 """Print version information and exit.
58 print 'twistd (the Twisted daemon) %s' % copyright.version
59 print copyright.copyright
63 def postOptions(self):
64 app.ServerOptions.postOptions(self)
66 self['pidfile'] = os.path.abspath(self['pidfile'])
69 def checkPID(pidfile):
72 if os.path.exists(pidfile):
74 pid = int(open(pidfile).read())
76 sys.exit('Pidfile %s contains non-numeric value' % pidfile)
80 if why[0] == errno.ESRCH:
81 # The pid doesnt exists.
82 log.msg('Removing stale pidfile %s' % pidfile, isError=True)
85 sys.exit("Can't check status of PID %s from pidfile %s: %s" %
86 (pid, pidfile, why[1]))
89 Another twistd server is running, PID %s\n
90 This could either be a previously started instance of your application or a
91 different application entirely. To start a new one, either run it in some other
92 directory, or use the --pidfile and --logfile parameters to avoid clashes.
97 class UnixAppLogger(app.AppLogger):
99 A logger able to log to syslog, to files, and to stdout.
101 @ivar _syslog: A flag indicating whether to use syslog instead of file
103 @type _syslog: C{bool}
105 @ivar _syslogPrefix: If C{sysLog} is C{True}, the string prefix to use for
107 @type _syslogPrefix: C{str}
109 @ivar _nodaemon: A flag indicating the process will not be daemonizing.
110 @type _nodaemon: C{bool}
113 def __init__(self, options):
114 app.AppLogger.__init__(self, options)
115 self._syslog = options.get("syslog", False)
116 self._syslogPrefix = options.get("prefix", "")
117 self._nodaemon = options.get("nodaemon", False)
120 def _getLogObserver(self):
122 Create and return a suitable log observer for the given configuration.
124 The observer will go to syslog using the prefix C{_syslogPrefix} if
125 C{_syslog} is true. Otherwise, it will go to the file named
126 C{_logfilename} or, if C{_nodaemon} is true and C{_logfilename} is
129 @return: An object suitable to be passed to C{log.addObserver}.
132 return syslog.SyslogObserver(self._syslogPrefix).emit
134 if self._logfilename == '-':
135 if not self._nodaemon:
136 sys.exit('Daemons cannot log to stdout, exiting!')
138 elif self._nodaemon and not self._logfilename:
141 if not self._logfilename:
142 self._logfilename = 'twistd.log'
143 logFile = logfile.LogFile.fromFullPath(self._logfilename)
149 # Override if signal is set to None or SIG_DFL (0)
150 if not signal.getsignal(signal.SIGUSR1):
151 def rotateLog(signal, frame):
152 from twisted.internet import reactor
153 reactor.callFromThread(logFile.rotate)
154 signal.signal(signal.SIGUSR1, rotateLog)
155 return log.FileLogObserver(logFile).emit
159 def daemonize(reactor, os):
161 Daemonizes the application on Unix. This is done by the usual double
164 @see: U{http://code.activestate.com/recipes/278731/}
165 @see: W. Richard Stevens, "Advanced Programming in the Unix Environment",
166 1992, Addison-Wesley, ISBN 0-201-56317-7
168 @param reactor: The reactor in use. If it provides L{IReactorDaemonize},
169 its daemonization-related callbacks will be invoked.
171 @param os: An object like the os module to use to perform the daemonization.
174 ## If the reactor requires hooks to be called for daemonization, call them.
175 ## Currently the only reactor which provides/needs that is KQueueReactor.
176 if IReactorDaemonize.providedBy(reactor):
177 reactor.beforeDaemonize()
179 if os.fork(): # launch child and...
180 os._exit(0) # kill off parent
182 if os.fork(): # launch child and...
183 os._exit(0) # kill off parent again.
184 null = os.open('/dev/null', os.O_RDWR)
189 if e.errno != errno.EBADF:
193 if IReactorDaemonize.providedBy(reactor):
194 reactor.afterDaemonize()
198 def launchWithName(name):
199 if name and name != sys.argv[0]:
200 exe = os.path.realpath(sys.executable)
201 log.msg('Changing process name to ' + name)
202 os.execv(exe, [name, sys.argv[0], '--originalname'] + sys.argv[1:])
206 class UnixApplicationRunner(app.ApplicationRunner):
208 An ApplicationRunner which does Unix-specific things, like fork,
209 shed privileges, and maintain a PID file.
211 loggerFactory = UnixAppLogger
213 def preApplication(self):
215 Do pre-application-creation setup.
217 checkPID(self.config['pidfile'])
218 self.config['nodaemon'] = (self.config['nodaemon']
219 or self.config['debug'])
220 self.oldstdout = sys.stdout
221 self.oldstderr = sys.stderr
224 def postApplication(self):
226 To be called after the application is created: start the
227 application and run the reactor. After the reactor stops,
228 clean up PID files and such.
230 self.startApplication(self.application)
231 self.startReactor(None, self.oldstdout, self.oldstderr)
232 self.removePID(self.config['pidfile'])
235 def removePID(self, pidfile):
237 Remove the specified PID file, if possible. Errors are logged, not
240 @type pidfile: C{str}
241 @param pidfile: The path to the PID tracking file.
248 if e.errno == errno.EACCES or e.errno == errno.EPERM:
249 log.msg("Warning: No permission to delete pid file")
251 log.err(e, "Failed to unlink PID file")
253 log.err(None, "Failed to unlink PID file")
256 def setupEnvironment(self, chroot, rundir, nodaemon, umask, pidfile):
258 Set the filesystem root, the working directory, and daemonize.
260 @type chroot: C{str} or L{NoneType}
261 @param chroot: If not None, a path to use as the filesystem root (using
265 @param rundir: The path to set as the working directory.
267 @type nodaemon: C{bool}
268 @param nodaemon: A flag which, if set, indicates that daemonization
271 @type umask: C{int} or L{NoneType}
272 @param umask: The value to which to change the process umask.
274 @type pidfile: C{str} or L{NoneType}
275 @param pidfile: If not C{None}, the path to a file into which to put
276 the PID of this process.
278 daemon = not nodaemon
280 if chroot is not None:
285 if daemon and umask is None:
287 if umask is not None:
290 from twisted.internet import reactor
291 daemonize(reactor, os)
293 f = open(pidfile,'wb')
294 f.write(str(os.getpid()))
298 def shedPrivileges(self, euid, uid, gid):
300 Change the UID and GID or the EUID and EGID of this process.
303 @param euid: A flag which, if set, indicates that only the I{effective}
304 UID and GID should be set.
306 @type uid: C{int} or C{NoneType}
307 @param uid: If not C{None}, the UID to which to switch.
309 @type gid: C{int} or C{NoneType}
310 @param gid: If not C{None}, the GID to which to switch.
312 if uid is not None or gid is not None:
313 extra = euid and 'e' or ''
314 desc = '%suid/%sgid %s/%s' % (extra, extra, uid, gid)
316 switchUID(uid, gid, euid)
318 log.msg('failed to set %s (are you root?) -- exiting.' % desc)
321 log.msg('set %s' % desc)
324 def startApplication(self, application):
326 Configure global process state based on the given application and run
329 @param application: An object which can be adapted to
330 L{service.IProcess} and L{service.IService}.
332 process = service.IProcess(application)
333 if not self.config['originalname']:
334 launchWithName(process.processName)
335 self.setupEnvironment(
336 self.config['chroot'], self.config['rundir'],
337 self.config['nodaemon'], self.config['umask'],
338 self.config['pidfile'])
340 service.IService(application).privilegedStartService()
342 uid, gid = self.config['uid'], self.config['gid']
348 self.shedPrivileges(self.config['euid'], uid, gid)
349 app.startApplication(application, not self.config['no_save'])