1717
1818"""Command-line script that runs a server over roundup.cgi.client.
1919
20- $Id: roundup_server.py,v 1.62 2004-09-21 09:29:18 a1s Exp $
20+ $Id: roundup_server.py,v 1.63 2004-10-17 17:54:48 a1s Exp $
2121"""
2222__docformat__ = 'restructuredtext'
2323
2424# python version check
25- from roundup import version_check
25+ from roundup import configuration , version_check
2626from roundup import __version__ as roundup_version
2727
2828import sys , os , urllib , StringIO , traceback , cgi , binascii , getopt , imp
@@ -369,7 +369,7 @@ def usage(message=''):
369369 pairs on the command-line. Make sure the name part doesn't include
370370 any url-unsafe characters like spaces, as these confuse IE.
371371''' )% locals ()
372- sys .exit (0 )
372+ # sys.exit(0)
373373
374374
375375def daemonize (pidfile ):
@@ -461,6 +461,76 @@ def setuid(user):
461461 raise ValueError , _ ("User %(user)s doesn't exist" )% locals ()
462462 os .setuid (uid )
463463
464+ class TrackerHomeOption (configuration .FilePathOption ):
465+ # Tracker homes do not need description strings
466+ # attached to FilePathOption. Description appears once
467+ # before the trackers section.
468+ class_description = ""
469+
470+ class ServerConfig (configuration .Config ):
471+
472+ SETTINGS = (
473+ ("main" , (
474+ (configuration .Option , "host" , "" ,
475+ "Host name of the Roundup web server instance.\n "
476+ "If empty, listen on all network interfaces." ),
477+ (configuration .IntegerNumberOption , "port" , DEFAULT_PORT ,
478+ "Port to listen on." ),
479+ (configuration .NullableOption , "user" , "" ,
480+ "User ID as which the server will answer requests.\n "
481+ "In order to use this option, "
482+ "the server must be run initially as root.\n "
483+ "Availability: Unix." ),
484+ (configuration .NullableOption , "group" , "" ,
485+ "Group ID as which the server will answer requests.\n "
486+ "In order to use this option, "
487+ "the server must be run initially as root.\n "
488+ "Availability: Unix." ),
489+ (configuration .BooleanOption , "log_hostnames" , "no" ,
490+ "Log client machine names instead of IP addresses "
491+ "(much slower)" ),
492+ (configuration .NullableFilePathOption , "pidfile" , "" ,
493+ "File to which the server records "
494+ "the process id of the daemon.\n "
495+ "If this option is not set, "
496+ "the server will run in foreground\n " ),
497+ (configuration .NullableFilePathOption , "logfile" , "" ,
498+ "Log file path. If unset, log to stderr." ),
499+ )),
500+ ("trackers" , (), "Roundup trackers to serve.\n "
501+ "Each option in this section defines single Roundup tracker.\n "
502+ "Option name identifies the tracker and will appear in the URL.\n "
503+ "Option value is tracker home directory path.\n "
504+ "The path may be either absolute or relative\n "
505+ "to the directory containig this config file." ),
506+ )
507+
508+ def __init__ (self , config_file = None ):
509+ configuration .Config .__init__ (self , config_file , self .SETTINGS )
510+
511+ def _adjust_options (self , config ):
512+ """Add options for tracker homes"""
513+ # return early if there are no tracker definitions.
514+ # trackers must be specified on the command line.
515+ if not config .has_section ("trackers" ):
516+ return
517+ # config defaults appear in all sections.
518+ # filter them out.
519+ defaults = config .defaults ().keys ()
520+ for name in config .options ("trackers" ):
521+ if name not in defaults :
522+ self .add_option (TrackerHomeOption (self , "trackers" , name ))
523+
524+ def _get_name (self ):
525+ return "Roundup server"
526+
527+ def trackers (self ):
528+ """Return tracker definitions as a list of (name, home) pairs"""
529+ trackers = []
530+ for option in self ._get_section_options ("trackers" ):
531+ trackers .append ((option , self ["TRACKERS_" + option .upper ()]))
532+ return trackers
533+
464534undefined = []
465535def run (port = undefined , success_message = None ):
466536 ''' Script entry point - handle args and figure out what to to.
@@ -470,134 +540,98 @@ def run(port=undefined, success_message=None):
470540 if hasattr (socket , 'setdefaulttimeout' ):
471541 socket .setdefaulttimeout (60 )
472542
473- hostname = pidfile = logfile = user = group = svc_args = log_ip = undefined
474- config = None
543+ config = ServerConfig ()
475544
545+ options = "hvS"
546+ if RoundupService :
547+ options += 'c'
476548 try :
477- # handle the command-line args
478- options = 'n:p:g:u:d:l:C:hNv'
479- if RoundupService :
480- options += 'c'
481-
482- try :
483- optlist , args = getopt .getopt (sys .argv [1 :], options )
484- except getopt .GetoptError , e :
485- usage (str (e ))
549+ (optlist , args ) = config .getopt (sys .argv [1 :],
550+ options , ("help" , "version" , "save-config" ,),
551+ host = "n:" , port = "p:" , group = "g:" , user = "u:" ,
552+ logfile = "l:" , pidfile = "d:" , log_hostnames = "N" )
553+ except (getopt .GetoptError ), e : #, configuration.ConfigurationError), e:
554+ usage (str (e ))
555+ return
486556
557+ # if running in windows service mode, don't do any other stuff
558+ if ("-c" , "" ) in optlist :
559+ RoundupService .address = (config .HOST , config .PORT )
560+ # XXX why the 1st argument to the service is "-c"
561+ # instead of the script name???
562+ return win32serviceutil .HandleCommandLine (RoundupService ,
563+ argv = ["-c" ] + args )
564+
565+ # add tracker names from command line.
566+ # this is done early to let '--save-config' handle the trackers.
567+ if args :
568+ for arg in args :
569+ try :
570+ name , home = arg .split ('=' )
571+ except ValueError :
572+ raise ValueError , _ ("Instances must be name=home" )
573+ config .add_option (
574+ configuration .FilePathOption (config , "trackers" , name ))
575+ config ["TRACKERS_" + name .upper ()] = home
576+
577+ # handle remaining options
578+ if optlist :
487579 for (opt , arg ) in optlist :
488- if opt == '-n' : hostname = arg
489- elif opt == '-v' :
490- print '%s (python %s)' % (roundup_version , sys .version .split ()[0 ])
491- return
492- elif opt == '-p' : port = int (arg )
493- elif opt == '-u' : user = arg
494- elif opt == '-g' : group = arg
495- elif opt == '-d' : pidfile = os .path .abspath (arg )
496- elif opt == '-l' : logfile = os .path .abspath (arg )
497- elif opt == '-h' : usage ()
498- elif opt == '-N' : log_ip = 0
499- elif opt == '-c' : svc_args = [opt ] + args ; args = None
500- elif opt == '-C' : config = arg
501-
502- if svc_args and len (optlist ) > 1 :
503- raise ValueError , _ ("windows service option must be the only one" )
504-
505- if pidfile and not logfile :
506- raise ValueError , _ ("logfile *must* be specified if pidfile is" )
507-
508- # handle the config file
509- if config :
510- cfg = ConfigParser .ConfigParser ()
511- cfg .read (filename )
512- if port is undefined and cfg .has_option ('server' , 'port' ):
513- port = cfg .get ('server' , 'port' )
514- if user is undefined and cfg .has_option ('server' , 'user' ):
515- user = cfg .get ('server' , 'user' )
516- if group is undefined and cfg .has_option ('server' , 'group' ):
517- group = cfg .get ('server' , 'group' )
518- if log_ip is undefined and cfg .has_option ('server' , 'log_ip' ):
519- RoundupRequestHandler .LOG_IPADDRESS = cfg .getboolean ('server' ,
520- 'log_ip' )
521- if pidfile is undefined and cfg .has_option ('server' , 'pidfile' ):
522- pidfile = cfg .get ('server' , 'pidfile' )
523- if logfile is undefined and cfg .has_option ('server' , 'logfile' ):
524- logfile = cfg .get ('server' , 'logfile' )
525- homes = RoundupRequestHandler .TRACKER_HOMES
526- for section in cfg .sections ():
527- if section == 'server' :
528- continue
529- homes [section ] = cfg .get (section , 'home' )
530-
531- # defaults
532- if hostname is undefined :
533- hostname = ''
534- if port is undefined :
535- port = DEFAULT_PORT
536- if group is undefined :
537- group = None
538- if user is undefined :
539- user = None
540- if svc_args is undefined :
541- svc_args = None
542-
543- # obtain server before changing user id - allows to use port <
544- # 1024 if started as root
545- address = (hostname , port )
546- try :
547- httpd = server_class (address , RoundupRequestHandler )
548- except socket .error , e :
549- if e [0 ] == errno .EADDRINUSE :
550- raise socket .error , \
551- _ ("Unable to bind to port %s, port already in use." % port )
552- raise
553-
554- # change user and/or group
555- setgid (group )
556- setuid (user )
557-
558- # handle tracker specs
559- if args :
560- for arg in args :
561- try :
562- name , home = arg .split ('=' )
563- except ValueError :
564- raise ValueError , _ ("Instances must be name=home" )
565- home = os .path .abspath (home )
566- RoundupRequestHandler .TRACKER_HOMES [name ] = home
567- except SystemExit :
580+ if opt in ("-h" , "--help" ):
581+ usage ()
582+ elif opt in ("-v" , "--version" ):
583+ print '%s (python %s)' % (roundup_version ,
584+ sys .version .split ()[0 ])
585+ elif opt in ("-S" , "--save-config" ):
586+ config .save ()
587+ print _ ("Configuration saved to %s" ) % config .filepath
588+ # any of the above options prevent server from running
589+ return
590+
591+ RoundupRequestHandler .LOG_IPADDRESS = not config .LOG_HOSTNAMES
592+
593+ # obtain server before changing user id - allows to use port <
594+ # 1024 if started as root
595+ try :
596+ httpd = server_class ((config .HOST , config .PORT ), RoundupRequestHandler )
597+ except socket .error , e :
598+ if e [0 ] == errno .EADDRINUSE :
599+ raise socket .error , \
600+ _ ("Unable to bind to port %s, port already in use." ) \
601+ % config .PORT
568602 raise
569- except ValueError :
570- usage (error ())
571- # except:
572- # print error()
573- # sys.exit(1)
603+
604+ # change user and/or group
605+ setgid (config .GROUP )
606+ setuid (config .USER )
607+
608+ # apply tracker specs
609+ for (name , home ) in config .trackers ():
610+ home = os .path .abspath (home )
611+ RoundupRequestHandler .TRACKER_HOMES [name ] = home
574612
575613 # we don't want the cgi module interpreting the command-line args ;)
576614 sys .argv = sys .argv [:1 ]
577615
578616 # fork the server from our parent if a pidfile is specified
579- if pidfile :
617+ if config . PIDFILE :
580618 if not hasattr (os , 'fork' ):
581619 print _ ("Sorry, you can't run the server as a daemon"
582620 " on this Operating System" )
583621 sys .exit (0 )
584622 else :
585- daemonize (pidfile )
586-
587- if svc_args is not None :
588- # don't do any other stuff
589- RoundupService .address = address
590- return win32serviceutil .HandleCommandLine (RoundupService , argv = svc_args )
623+ daemonize (config .PIDFILE )
591624
592625 # redirect stdout/stderr to our logfile
593- if logfile :
626+ if config . LOGFILE :
594627 # appending, unbuffered
595- sys .stdout = sys .stderr = open (logfile , 'a' , 0 )
628+ sys .stdout = sys .stderr = open (config . LOGFILE , 'a' , 0 )
596629
597630 if success_message :
598631 print success_message
599632 else :
600- print _ ('Roundup server started on %(address)s' )% locals ()
633+ print _ ('Roundup server started on %(HOST)s:%(PORT)s' ) \
634+ % config
601635
602636 try :
603637 httpd .serve_forever ()
0 commit comments