#!/usr/bin/python -t # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program 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 Library General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # # Copyright 2005 Dan Williams and Red Hat, Inc. import os import time import sys import signal import socket from plague import ArchUtils from plague import daemonize from optparse import OptionParser sys.path.append('/usr/share/plague/builder') import Config import Builder import BuilderMock def log(string): sys.stdout.write(string) sys.stdout.flush() def drop_privs(user): """Drop privileges, since we don't need to run as a privileged user after we've started up.""" import pwd import grp effective_user = user try: uid = int(effective_user) except ValueError: try: pwrec = pwd.getpwnam(effective_user) except KeyError: raise Exception("Username '%s' does not exist." % effective_user) uid = pwrec[2] else: try: pwrec = pwd.getpwuid(uid) except KeyError: raise Exception("User ID %d doesn't exist." % uid) gid = pwrec[3] if uid == 0: raise Exception("You cannot use the superuser as the 'builder_user' option.") # If we're already not running as root, ensure that who we are running as # matches what the admin configured in the config file cur_uid = os.getuid() if cur_uid != 0: if cur_uid != uid: try: cur_nam = pwd.getpwuid(cur_uid)[0] except KeyError: cur_nam = str(cur_uid) uid_nam = pwd.getpwuid(uid)[0] raise Exception("Attempting to run as '%s', but configured for" \ " '%s'" % (cur_nam, uid_nam)) # Otherwise, we're already running as the requested # user and we don't need to do anything else return # Make ourself members of the mock group build_user's group try: mock_req = grp.getgrnam('mock') except KeyError: raise Exception("The 'mock' group doesn't exist in the groups file.") groups = [mock_req[2], gid] os.setgroups(groups) try: os.setgid(gid) except OSError: raise Exception("Error dropping group privileges. '%s'" % sys.exc_info()) os.setuid(uid) def determine_build_arches(cfg): """ Attempt to autodetect what architectures this machine can build for, based on the kernel's uname. If that fails, fall back to options in the config file. """ machine_arch = os.uname()[4] arches = [] try: arches = ArchUtils.supported_arches[machine_arch] except KeyError: print "Unknown machine type. Please update plague's ArchUtils.py file." # Ok, grab from config file if we can't autodetermine if not len(arches): arches = cfg.get_list("General", "build_arches") for arch in arches: if not arch in BuilderMock.BuilderClassDict.keys(): print "Unknown arch '%s' is not supported." % arch sys.exit(1) return arches builder = None def exit_handler(signum=0, frame=0): global builder log("Received SIGTERM, quitting...\n") builder.stop() def main(): usage = "Usage: %s [-p ] [-l ] [-d] -c " % sys.argv[0] parser = OptionParser(usage=usage) parser.add_option("-p", "--pidfile", default=None, help='file to write the PID to') parser.add_option("-l", "--logfile", default=None, help="location of file to write log output to") parser.add_option("-d", "--daemon", default=False, action="store_true", help="daemonize (i.e., detach from the terminal)") parser.add_option("-c", "--configfile", default=None, help="location of the builder config file") (opts, args) = parser.parse_args() if not opts.configfile: log("Must specify a config file.\n") sys.exit(1) # Load in the config cfg = Config.BuilderConfig(opts.configfile) btype = cfg.get_str("General", "comm_type") if btype != 'passive' and btype != 'active': log("Builder communication type must be 'active' or 'passive', not '%s'. Exiting...\n" % btype) sys.exit(1) build_arches = determine_build_arches(cfg) if not len(build_arches): log("Cannot determine buildable arches for this builder. Exiting...\n") sys.exit(1) cfg.load_target_configs(build_arches) if len(cfg.targets()) == 0: log("No useable mock buildroots configured. Exiting...\n") sys.exit(1) if opts.daemon: ret = daemonize.createDaemon() if ret: log("Daemonizing failed!\n") sys.exit(2) if opts.pidfile: pidf = open(opts.pidfile, 'w', 1) pidf.write('%d\n' % os.getpid()) pidf.flush() pidf.close() if opts.logfile: logf = open(opts.logfile, 'a') sys.stdout = logf sys.stderr = logf work_dir = cfg.get_str("Directories", "builder_work_dir") if not os.path.exists(work_dir) or not os.access(work_dir, os.R_OK): log("%s does not exist or is not readable.\n" % work_dir) os._exit(1) # Stop running as root try: drop_privs(cfg.get_str("General", "builder_user")) except Exception, exc: log("Couldn't drop privileges: %s\n" % exc) os._exit(1) # Set up our termination handler signal.signal(signal.SIGTERM, exit_handler) global builder try: builder = Builder.Builder.new_builder(cfg, btype) except socket.error, exc: print exc[1] os._exit(1) # Start doing stuff builder.work() log("Shutting down...\n") builder.stop() builder.cleanup() try: time.sleep(2) except KeyboardInterrupt: pass log(" done.\n"); sys.stdout.flush() os._exit(0) if __name__ == '__main__': main()