Initial import to Tizen
[profile/ivi/python-twisted.git] / twisted / scripts / _twistd_unix.py
1 # -*- test-case-name: twisted.test.test_twistd -*-
2 # Copyright (c) Twisted Matrix Laboratories.
3 # See LICENSE for details.
4
5 import os, errno, sys
6
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
12
13
14 def _umask(value):
15     return int(value, 8)
16
17
18 class ServerOptions(app.ServerOptions):
19     synopsis = "Usage: twistd [options]"
20
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"],
24                 ['euid', '',
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.)"],
31                ]
32
33     optParameters = [
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],
42                      ['umask', None, None,
43                       "The (octal) file creation mask to apply.", _umask],
44                     ]
45
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"),
52                     },
53         )
54
55     def opt_version(self):
56         """Print version information and exit.
57         """
58         print 'twistd (the Twisted daemon) %s' % copyright.version
59         print copyright.copyright
60         sys.exit()
61
62
63     def postOptions(self):
64         app.ServerOptions.postOptions(self)
65         if self['pidfile']:
66             self['pidfile'] = os.path.abspath(self['pidfile'])
67
68
69 def checkPID(pidfile):
70     if not pidfile:
71         return
72     if os.path.exists(pidfile):
73         try:
74             pid = int(open(pidfile).read())
75         except ValueError:
76             sys.exit('Pidfile %s contains non-numeric value' % pidfile)
77         try:
78             os.kill(pid, 0)
79         except OSError, why:
80             if why[0] == errno.ESRCH:
81                 # The pid doesnt exists.
82                 log.msg('Removing stale pidfile %s' % pidfile, isError=True)
83                 os.remove(pidfile)
84             else:
85                 sys.exit("Can't check status of PID %s from pidfile %s: %s" %
86                          (pid, pidfile, why[1]))
87         else:
88             sys.exit("""\
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.
93 """ %  pid)
94
95
96
97 class UnixAppLogger(app.AppLogger):
98     """
99     A logger able to log to syslog, to files, and to stdout.
100
101     @ivar _syslog: A flag indicating whether to use syslog instead of file
102         logging.
103     @type _syslog: C{bool}
104
105     @ivar _syslogPrefix: If C{sysLog} is C{True}, the string prefix to use for
106         syslog messages.
107     @type _syslogPrefix: C{str}
108
109     @ivar _nodaemon: A flag indicating the process will not be daemonizing.
110     @type _nodaemon: C{bool}
111     """
112
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)
118
119
120     def _getLogObserver(self):
121         """
122         Create and return a suitable log observer for the given configuration.
123
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
127         C{"-"}, to stdout.
128
129         @return: An object suitable to be passed to C{log.addObserver}.
130         """
131         if self._syslog:
132             return syslog.SyslogObserver(self._syslogPrefix).emit
133
134         if self._logfilename == '-':
135             if not self._nodaemon:
136                 sys.exit('Daemons cannot log to stdout, exiting!')
137             logFile = sys.stdout
138         elif self._nodaemon and not self._logfilename:
139             logFile = sys.stdout
140         else:
141             if not self._logfilename:
142                 self._logfilename = 'twistd.log'
143             logFile = logfile.LogFile.fromFullPath(self._logfilename)
144             try:
145                 import signal
146             except ImportError:
147                 pass
148             else:
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
156
157
158
159 def daemonize(reactor, os):
160     """
161     Daemonizes the application on Unix. This is done by the usual double
162     forking approach.
163
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
167
168     @param reactor: The reactor in use.  If it provides L{IReactorDaemonize},
169         its daemonization-related callbacks will be invoked.
170
171     @param os: An object like the os module to use to perform the daemonization.
172     """
173
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()
178
179     if os.fork():   # launch child and...
180         os._exit(0) # kill off parent
181     os.setsid()
182     if os.fork():   # launch child and...
183         os._exit(0) # kill off parent again.
184     null = os.open('/dev/null', os.O_RDWR)
185     for i in range(3):
186         try:
187             os.dup2(null, i)
188         except OSError, e:
189             if e.errno != errno.EBADF:
190                 raise
191     os.close(null)
192
193     if IReactorDaemonize.providedBy(reactor):
194         reactor.afterDaemonize()
195
196
197
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:])
203
204
205
206 class UnixApplicationRunner(app.ApplicationRunner):
207     """
208     An ApplicationRunner which does Unix-specific things, like fork,
209     shed privileges, and maintain a PID file.
210     """
211     loggerFactory = UnixAppLogger
212
213     def preApplication(self):
214         """
215         Do pre-application-creation setup.
216         """
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
222
223
224     def postApplication(self):
225         """
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.
229         """
230         self.startApplication(self.application)
231         self.startReactor(None, self.oldstdout, self.oldstderr)
232         self.removePID(self.config['pidfile'])
233
234
235     def removePID(self, pidfile):
236         """
237         Remove the specified PID file, if possible.  Errors are logged, not
238         raised.
239
240         @type pidfile: C{str}
241         @param pidfile: The path to the PID tracking file.
242         """
243         if not pidfile:
244             return
245         try:
246             os.unlink(pidfile)
247         except OSError, e:
248             if e.errno == errno.EACCES or e.errno == errno.EPERM:
249                 log.msg("Warning: No permission to delete pid file")
250             else:
251                 log.err(e, "Failed to unlink PID file")
252         except:
253             log.err(None, "Failed to unlink PID file")
254
255
256     def setupEnvironment(self, chroot, rundir, nodaemon, umask, pidfile):
257         """
258         Set the filesystem root, the working directory, and daemonize.
259
260         @type chroot: C{str} or L{NoneType}
261         @param chroot: If not None, a path to use as the filesystem root (using
262             L{os.chroot}).
263
264         @type rundir: C{str}
265         @param rundir: The path to set as the working directory.
266
267         @type nodaemon: C{bool}
268         @param nodaemon: A flag which, if set, indicates that daemonization
269             should not be done.
270
271         @type umask: C{int} or L{NoneType}
272         @param umask: The value to which to change the process umask.
273
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.
277         """
278         daemon = not nodaemon
279
280         if chroot is not None:
281             os.chroot(chroot)
282             if rundir == '.':
283                 rundir = '/'
284         os.chdir(rundir)
285         if daemon and umask is None:
286             umask = 077
287         if umask is not None:
288             os.umask(umask)
289         if daemon:
290             from twisted.internet import reactor
291             daemonize(reactor, os)
292         if pidfile:
293             f = open(pidfile,'wb')
294             f.write(str(os.getpid()))
295             f.close()
296
297
298     def shedPrivileges(self, euid, uid, gid):
299         """
300         Change the UID and GID or the EUID and EGID of this process.
301
302         @type euid: C{bool}
303         @param euid: A flag which, if set, indicates that only the I{effective}
304             UID and GID should be set.
305
306         @type uid: C{int} or C{NoneType}
307         @param uid: If not C{None}, the UID to which to switch.
308
309         @type gid: C{int} or C{NoneType}
310         @param gid: If not C{None}, the GID to which to switch.
311         """
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)
315             try:
316                 switchUID(uid, gid, euid)
317             except OSError:
318                 log.msg('failed to set %s (are you root?) -- exiting.' % desc)
319                 sys.exit(1)
320             else:
321                 log.msg('set %s' % desc)
322
323
324     def startApplication(self, application):
325         """
326         Configure global process state based on the given application and run
327         the application.
328
329         @param application: An object which can be adapted to
330             L{service.IProcess} and L{service.IService}.
331         """
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'])
339
340         service.IService(application).privilegedStartService()
341
342         uid, gid = self.config['uid'], self.config['gid']
343         if uid is None:
344             uid = process.uid
345         if gid is None:
346             gid = process.gid
347
348         self.shedPrivileges(self.config['euid'], uid, gid)
349         app.startApplication(application, not self.config['no_save'])