| 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 |