# Twisted, the Framework of Your Internet
# Copyright (C) 2001 Matthew W. Lefkowitz
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of version 2.1 of the GNU Lesser General Public
# License as published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
from __future__ import nested_scopes
from twisted import copyright
from twisted.python import usage,util,runtime,register,plugin
from twisted.python import log,logfile
from twisted.persisted import styles
util.addPluginDir()
# System Imports
from cPickle import load,loads
from cStringIO import StringIO
import traceback
import imp
import sys, os
reactorTypes = {
'gtk': 'twisted.internet.gtkreactor',
'gtk2': 'twisted.internet.gtk2reactor',
'default': 'twisted.internet.default',
'win32': 'twisted.internet.win32eventreactor',
'win': 'twisted.internet.win32eventreactor',
'poll': 'twisted.internet.pollreactor',
'qt': 'twisted.internet.qtreactor',
'c' : 'twisted.internet.cReactor',
'kqueue': 'twisted.internet.kqreactor'
}
class ServerOptions(usage.Options):
synopsis = "Usage: twistd [options]"
optFlags = [['nodaemon','n', "don't daemonize"],
['savestats', None, "save the Stats object rather than the text output of the profiler."],
['debug', 'b', "run the application in the Python Debugger (implies nodaemon), sending SIGINT will drop into debugger"],
['quiet','q', "be a little more quiet"],
['no_save','o', "do not save state on shutdown"],
['syslog', None, "Log to syslog, not to file"],
['euid', '', "Set only effective user-id rather than real user-id. "
"(This option has no effect unless the server is running as root, "
"in which case it means not to shed all privileges after binding "
"ports, retaining the option to regain privileges in cases such as "
"spawning processes. Use with caution.)"],
['encrypted', 'e', "The specified tap/aos/xml file is encrypted."]]
optParameters = [['logfile','l', None,
"log to a specified file, - for stdout"],
['profile', 'p', None,
"Run in profile mode, dumping results to specified file"],
['file','f','twistd.tap',
"read the given .tap file"],
['prefix', None,'twisted',
"use the given prefix when syslogging"],
['python','y', None,
"read an application from within a Python file"],
['xml', 'x', None,
"Read an application from a .tax file (Marmalade format)."],
['source', 's', None,
"Read an application from a .tas file (AOT format)."],
['pidfile','','twistd.pid',
"Name of the pidfile"],
['rundir','d','.',
'Change to a supplied directory before running'],
['chroot', None, None,
'Chroot to a supplied directory before running'],
['reactor', 'r', None,
'Which reactor to use out of: %s.' % ', '.join(reactorTypes.keys())]]
def opt_plugin(self, pkgname):
"""read config.tac from a plugin package, as with -y
"""
try:
fname = imp.find_module(pkgname)[1]
except ImportError:
print "Error: Package %s not found. Is it in your ~/TwistedPlugins directory?" % pkgname
sys.exit()
self.opts['python'] = os.path.join(fname, 'config.tac')
def opt_version(self):
"""Print version information and exit.
"""
print 'twistd (the Twisted daemon) %s' % copyright.version
print copyright.copyright
sys.exit()
def opt_spew(self):
"""Print an insanely verbose log of everything that happens. Useful
when debugging freezes or locks in complex code."""
def spewer(frame, s, ignored):
from twisted.python import reflect
if frame.f_locals.has_key('self'):
se = frame.f_locals['self']
print 'method %s of %s at %s' % (
frame.f_code.co_name, reflect.qual(se.__class__), id(se)
)
sys.settrace(spewer)
opt_g = opt_plugin
def decrypt(passphrase, data):
import md5
from Crypto.Cipher import AES
return AES.new(md5.new(passphrase).digest()[:16]).decrypt(data)
def createApplicationDecoder(config):
mainMod = sys.modules['__main__']
# Twisted Imports
class EverythingEphemeral(styles.Ephemeral):
def __getattr__(self, key):
try:
return getattr(mainMod, key)
except AttributeError:
if initRun:
raise
else:
log.msg("Warning! Loading from __main__: %s" % key)
return styles.Ephemeral()
# Application creation/unserializing
if config['python']:
def decode(filename, data):
log.msg('Loading %s...' % (filename,))
d = {'__file__': filename}
exec data in d, d
try:
return d['application']
except KeyError:
log.msg("Error - python file %s must set a variable named 'application', an instance of twisted.internet.app.Application. No such variable was found!" % (repr(filename),))
sys.exit()
filename = os.path.abspath(config['python'])
mode = 'r'
elif config['xml']:
def decode(filename, data):
from twisted.persisted.marmalade import unjellyFromXML
log.msg('<Loading file="%s" />' % (filename,))
sys.modules['__main__'] = EverythingEphemeral()
application = unjellyFromXML(StringIO(data))
application.persistStyle = 'xml'
sys.modules['__main__'] = mainMod
styles.doUpgrade()
return application
filename = config['xml']
mode = 'r'
elif config['source']:
def decode(filename, data):
from twisted.persisted.aot import unjellyFromSource
log.msg("Loading %s..." % (filename,))
sys.modules['__main__'] = EverythingEphemeral()
application = unjellyFromSource(StringIO(data))
application.persistStyle = 'aot'
sys.modules['__main__'] = mainMod
styles.doUpgrade()
return application
filename = config['source']
mode = 'r'
else:
def decode(filename, data):
log.msg("Loading %s..." % (filename,))
sys.modules['__main__'] = EverythingEphemeral()
application = loads(data)
sys.modules['__main__'] = mainMod
styles.doUpgrade()
return application
filename = config['file']
mode = 'rb'
return filename, decode, mode
def loadApplication(config, passphrase):
filename, decode, mode = createApplicationDecoder(config)
if config['encrypted']:
data = open(filename, 'rb').read()
data = decrypt(passphrase, data)
try:
return decode(filename, data)
except:
# Too bad about this.
log.msg("Error loading Application - perhaps you used the wrong passphrase?")
raise
else:
data = open(filename, mode).read()
return decode(filename, data)
def debugSignalHandler(*args):
"""Break into debugger."""
import pdb
pdb.set_trace()
def runApp(config):
global initRun
platformType = runtime.platform.getType()
register.checkLicenseFile()
sys.path.append(config['rundir'])
# Install a reactor immediately. The application will not load properly
# unless this is done FIRST; otherwise the first 'reactor' import would
# trigger an automatic installation of the default reactor.
# To make this callable from within a running Twisted app, allow as the
# reactor None to bypass this and use whatever reactor is currently in use.
if config['reactor']:
if platformType == 'java':
from twisted.internet import javareactor
javareactor.install()
else:
from twisted.python.reflect import namedModule
namedModule(reactorTypes[config['reactor']]).install()
if platformType != 'posix' or config['debug']:
# only posix can fork, and debugging requires nodaemon
config['nodaemon'] = 1
if config['encrypted']:
import getpass
passphrase = getpass.getpass('Passphrase: ')
else:
passphrase = None
# Load the servers.
# This will fix up accidental function definitions in evaluation spaces
# and the like.
initRun = 0
if os.path.exists(config['pidfile']):
try:
pid = int(open(config['pidfile']).read())
except ValueError:
sys.exit('Pidfile %s contains non numeric value' % config['pidfile'])
try:
os.kill(pid, 0)
except OSError, why:
import errno
if why[0] == errno.ESRCH:
# The pid doesnt exists.
if not config['quiet']:
print 'Removing stale pidfile %s' % config['pidfile']
os.remove(config['pidfile'])
else:
sys.exit('Can\'t check status of PID %s from pidfile %s: %s' % (pid, config['pidfile'], why[1]))
else:
sys.exit("""\
Another twistd server is running, PID %s\n
This could either be a previously started instance of your application or a
different application entirely. To start a new one, either run it in some other
directory, or use my --pidfile and --logfile parameters to avoid clashes.
""" % pid)
if config['logfile'] == '-':
if not config['nodaemon']:
print 'daemons cannot log to stdout'
os._exit(1)
logFile = sys.stdout
elif config['nodaemon'] and not config['logfile']:
logFile = sys.stdout
elif config['syslog']:
from twisted.python import syslog
syslog.startLogging(config['prefix'])
else:
logPath = os.path.abspath(config['logfile'] or 'twistd.log')
logFile = logfile.LogFile(os.path.basename(logPath), os.path.dirname(logPath))
# rotate logs on SIGUSR1
if os.name == "posix":
import signal
def rotateLog(signal, frame, logFile=logFile):
from twisted.internet import reactor
reactor.callLater(0, logFile.rotate)
signal.signal(signal.SIGUSR1, rotateLog)
oldstdin = sys.stdin
oldstdout = sys.stdout
oldstderr = sys.stderr
if not config['syslog']:
log.startLogging(logFile)
sys.stdout.flush()
log.msg("twistd %s (%s %s) starting up" % (copyright.version,
sys.executable,
runtime.shortPythonVersion()))
if register.LICENSE_USER:
log.msg("license user: %s <%s>" % (register.LICENSE_USER, register.LICENSE_EMAIL))
if register.LICENSE_KEY:
log.msg("license code: %s" % (register.LICENSE_KEY))
if register.LICENSE_ORG:
log.msg("organization: %s" % register.LICENSE_ORG)
from twisted.internet import reactor
log.msg('reactor class: %s' % reactor.__class__)
try:
application = loadApplication(config, passphrase)
except Exception, e:
s = "Failed to load application: %s" % (e,)
traceback.print_exc(file=log.logfile)
log.msg(s)
log.deferr()
sys.exit('\n' + s + '\n')
# If we're asked to chroot and os.chroot does not exist,
# just fail.
if config['chroot'] is not None:
os.chroot(config['chroot'])
if platformType != 'java':
# java can't chdir
os.chdir(config['rundir'])
if not config['nodaemon']:
# Turn into a daemon.
if os.fork(): # launch child and...
os._exit(0) # kill off parent
os.setsid()
os.umask(077)
for i in range(3):
try:
os.close(i)
except OSError, e:
import errno
if e.errno != errno.EBADF:
raise
# Load any view plugins which have been registered in plugins.tml file
# This needs to be moved to an event which occurs on web server startup
# Once glyph is done with the Reactors
# (Note: 'view' is probably a bad name for a plugin, since this is really a
# 'twisted.web.view'. I suppose 'tap' was a bad precedent for plugin system
# naming. --glyph)
plugins = plugin.getPlugIns('view')
for plug in plugins:
try:
plug.load()
except Exception, e:
log.msg("Loading view %s failed. %s" % (plug, e))
log.msg("Loaded.")
initRun = 1
# java doesn't have getpid, and Windows' getpid is near-useless
usepid = ((os.name != 'java') and (os.name != 'nt'))
if usepid:
open(config['pidfile'],'wb').write(str(os.getpid()))
if os.name == 'nt':
# C-c can't interrupt select.select in win32.
class Win32KillTimeout:
"""I time out every 1/10 second in order to allow C-c to kill the server.
"""
def callMeAgain(self):
reactor.callLater(0.1, self.callMeAgain)
reactor.callLater(0.1, Win32KillTimeout().callMeAgain)
application.bindPorts()
if config['euid']:
application.setEUID()
else:
application.setUID()
try:
if config['profile']:
import profile
p = profile.Profile()
p.runctx("application.run(%d)" % (not config['no_save']), globals(), locals())
if config['savestats']:
p.dump_stats(config['profile'])
else:
# XXX - omfg python sucks
tmp, sys.stdout = sys.stdout, open(config['profile'], 'a')
p.print_stats()
sys.stdout, tmp = tmp, sys.stdout
tmp.close()
elif config['debug']:
import pdb
from twisted.python import failure
def newinit(self, exc_value=None, exc_type=None, exc_tb=None,
init=failure.Failure.__init__.im_func):
r = init(self, exc_value, exc_type, exc_tb)
if (exc_value, exc_type, exc_tb) == (None, None, None):
exc = sys.exc_info()
if not exc[0] == failure.Failure:
print "Jumping into debugger - %s" % exc[1]
pdb.post_mortem(exc[2])
return r
failure.Failure.__init__ = newinit
sys.stdout = oldstdout
sys.stderr = oldstderr
if os.name == "posix":
import signal
signal.signal(signal.SIGINT, debugSignalHandler)
pdb.run("application.run(%d)" % (not config['no_save']),
globals(), locals())
else:
application.run(not config['no_save'])
except:
if config['nodaemon']:
file = oldstdout
else:
file = open("TWISTD-CRASH.log",'a')
traceback.print_exc(file=file)
file.flush()
if usepid:
os.unlink(config['pidfile'])
log.msg("Server Shut Down.")
def run():
# make default be "--help"
if len(sys.argv) == 1:
sys.argv.append("--help")
config = ServerOptions()
try:
config.parseOptions()
except usage.error, ue:
config.opt_help()
print "%s: %s" % (sys.argv[0], ue)
os._exit(1)
runApp(config)
|