123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981 |
- """
- This file implements the Boss of Bind (BoB, or bob) program.
- Its purpose is to start up the BIND 10 system, and then manage the
- processes, by starting and stopping processes, plus restarting
- processes that exit.
- To start the system, it first runs the c-channel program (msgq), then
- connects to that. It then runs the configuration manager, and reads
- its own configuration. Then it proceeds to starting other modules.
- The Python subprocess module is used for starting processes, but
- because this is not efficient for managing groups of processes,
- SIGCHLD signals are caught and processed using the signal module.
- Most of the logic is contained in the BoB class. However, since Python
- requires that signal processing happen in the main thread, we do
- signal handling outside of that class, in the code running for
- __main__.
- """
- import sys; sys.path.append ('@@PYTHONPATH@@')
- import os
- if "B10_FROM_SOURCE" in os.environ:
- SPECFILE_LOCATION = os.environ["B10_FROM_SOURCE"] + "/src/bin/bind10/bob.spec"
- else:
- PREFIX = "@prefix@"
- DATAROOTDIR = "@datarootdir@"
- SPECFILE_LOCATION = "@datadir@/@PACKAGE@/bob.spec".replace("${datarootdir}", DATAROOTDIR).replace("${prefix}", PREFIX)
-
- import subprocess
- import signal
- import re
- import errno
- import time
- import select
- import random
- import socket
- from optparse import OptionParser, OptionValueError
- import io
- import pwd
- import posix
- import isc.cc
- import isc.util.process
- import isc.net.parse
- import isc.log
- from isc.log_messages.bind10_messages import *
- import isc.bind10.component
- import isc.bind10.special_component
- isc.log.init("b10-boss")
- logger = isc.log.Logger("boss")
- DBG_PROCESS = 10
- DBG_COMMANDS = 30
- isc.util.process.rename(sys.argv[0])
- VERSION = "bind10 20110223 (BIND 10 @PACKAGE_VERSION@)"
- _BASETIME = time.gmtime()
- class RestartSchedule:
- """
- Keeps state when restarting something (in this case, a process).
- When a process dies unexpectedly, we need to restart it. However, if
- it fails to restart for some reason, then we should not simply keep
- restarting it at high speed.
- A more sophisticated algorithm can be developed, but for now we choose
- a simple set of rules:
- * If a process was been running for >=10 seconds, we restart it
- right away.
- * If a process was running for <10 seconds, we wait until 10 seconds
- after it was started.
- To avoid programs getting into lockstep, we use a normal distribution
- to avoid being restarted at exactly 10 seconds."""
- def __init__(self, restart_frequency=10.0):
- self.restart_frequency = restart_frequency
- self.run_start_time = None
- self.run_stop_time = None
- self.restart_time = None
-
- def set_run_start_time(self, when=None):
- if when is None:
- when = time.time()
- self.run_start_time = when
- sigma = self.restart_frequency * 0.05
- self.restart_time = when + random.normalvariate(self.restart_frequency,
- sigma)
- def set_run_stop_time(self, when=None):
- """We don't actually do anything with stop time now, but it
- might be useful for future algorithms."""
- if when is None:
- when = time.time()
- self.run_stop_time = when
- def get_restart_time(self, when=None):
- if when is None:
- when = time.time()
- return max(when, self.restart_time)
- class ProcessInfoError(Exception): pass
- class ProcessInfo:
- """Information about a process"""
- dev_null = open(os.devnull, "w")
- def __init__(self, name, args, env={}, dev_null_stdout=False,
- dev_null_stderr=False, uid=None, username=None):
- self.name = name
- self.args = args
- self.env = env
- self.dev_null_stdout = dev_null_stdout
- self.dev_null_stderr = dev_null_stderr
- self.restart_schedule = RestartSchedule()
- self.uid = uid
- self.username = username
- self.process = None
- self.pid = None
- def _preexec_work(self):
- """Function used before running a program that needs to run as a
- different user."""
-
-
-
- os.setpgrp()
-
- if self.uid is not None:
- try:
- posix.setuid(self.uid)
- except OSError as e:
- if e.errno == errno.EPERM:
-
- raise ProcessInfoError("Unable to change to user %s (uid %d)" % (self.username, self.uid))
- else:
-
- raise
- def _spawn(self):
- if self.dev_null_stdout:
- spawn_stdout = self.dev_null
- else:
- spawn_stdout = None
- if self.dev_null_stderr:
- spawn_stderr = self.dev_null
- else:
- spawn_stderr = None
-
-
-
- spawn_env = os.environ
- spawn_env.update(self.env)
- if 'B10_FROM_SOURCE' not in os.environ:
- spawn_env['PATH'] = "@@LIBEXECDIR@@:" + spawn_env['PATH']
- self.process = subprocess.Popen(self.args,
- stdin=subprocess.PIPE,
- stdout=spawn_stdout,
- stderr=spawn_stderr,
- close_fds=True,
- env=spawn_env,
- preexec_fn=self._preexec_work)
- self.pid = self.process.pid
- self.restart_schedule.set_run_start_time()
-
-
- def spawn(self):
- self._spawn()
- def respawn(self):
- self._spawn()
- class CChannelConnectError(Exception): pass
- class BoB:
- """Boss of BIND class."""
- def __init__(self, msgq_socket_file=None, data_path=None,
- config_filename=None, nocache=False, verbose=False, setuid=None,
- username=None, cmdctl_port=None, brittle=False):
- """
- Initialize the Boss of BIND. This is a singleton (only one can run).
- The msgq_socket_file specifies the UNIX domain socket file that the
- msgq process listens on. If verbose is True, then the boss reports
- what it is doing.
- Data path and config filename are passed trough to config manager
- (if provided) and specify the config file to be used.
- The cmdctl_port is passed to cmdctl and specify on which port it
- should listen.
- """
- self.cc_session = None
- self.ccs = None
- self.curproc = None
- self.dead_processes = {}
- self.msgq_socket_file = msgq_socket_file
- self.nocache = nocache
- self.processes = {}
- self.runnable = False
- self.uid = setuid
- self.username = username
- self.verbose = verbose
- self.data_path = data_path
- self.config_filename = config_filename
- self.cmdctl_port = cmdctl_port
- self.brittle = brittle
- self._component_configurator = isc.bind10.component.Configurator(self,
- isc.bind10.special_component.get_specials())
- self.__core_components = {
- 'sockcreator': {
- 'kind': 'core',
- 'special': 'sockcreator',
- 'priority': 200
- },
- 'msgq': {
- 'kind': 'core',
- 'special': 'msgq',
- 'priority': 199
- },
- 'cfgmgr': {
- 'kind': 'core',
- 'special': 'cfgmgr',
- 'priority': 198
- }
- }
- self.__started = False
- self.__stopping = False
- self.exitcode = 0
- def __propagate_component_config(self, config):
- comps = dict(config)
-
- for comp in self.__core_components:
- if comp in comps:
- raise Exception(comp + " is core component managed by " +
- "bind10 boss, do not set it")
- comps[comp] = self.__core_components[comp]
-
- self._component_configurator.reconfigure(comps)
- def config_handler(self, new_config):
-
- if not self.runnable:
- return
- logger.debug(DBG_COMMANDS, BIND10_RECEIVED_NEW_CONFIGURATION,
- new_config)
- try:
- if 'components' in new_config:
- self.__propagate_component_config(new_config['components'])
- return isc.config.ccsession.create_answer(0)
- except Exception as e:
- return isc.config.ccsession.create_answer(1, str(e))
- def get_processes(self):
- pids = list(self.processes.keys())
- pids.sort()
- process_list = [ ]
- for pid in pids:
- process_list.append([pid, self.processes[pid].name()])
- return process_list
- def _get_stats_data(self):
- return { "stats_data": {
- 'bind10.boot_time': time.strftime('%Y-%m-%dT%H:%M:%SZ', _BASETIME)
- }}
- def command_handler(self, command, args):
- logger.debug(DBG_COMMANDS, BIND10_RECEIVED_COMMAND, command)
- answer = isc.config.ccsession.create_answer(1, "command not implemented")
- if type(command) != str:
- answer = isc.config.ccsession.create_answer(1, "bad command")
- else:
- if command == "shutdown":
- self.runnable = False
- answer = isc.config.ccsession.create_answer(0)
- elif command == "getstats":
- answer = isc.config.ccsession.create_answer(0, self._get_stats_data())
- elif command == "sendstats":
-
- cmd = isc.config.ccsession.create_command(
- 'set', self._get_stats_data())
- seq = self.cc_session.group_sendmsg(cmd, 'Stats')
-
- try:
- self.cc_session.group_recvmsg(False, seq)
- except isc.cc.session.SessionTimeout:
- pass
- answer = isc.config.ccsession.create_answer(0)
- elif command == "ping":
- answer = isc.config.ccsession.create_answer(0, "pong")
- elif command == "show_processes":
- answer = isc.config.ccsession. \
- create_answer(0, self.get_processes())
- else:
- answer = isc.config.ccsession.create_answer(1,
- "Unknown command")
- return answer
- def kill_started_processes(self):
- """
- Called as part of the exception handling when a process fails to
- start, this runs through the list of started processes, killing
- each one. It then clears that list.
- """
- logger.info(BIND10_KILLING_ALL_PROCESSES)
- for pid in self.processes:
- logger.info(BIND10_KILL_PROCESS, self.processes[pid].name())
- self.processes[pid].kill()
- self.processes = {}
- if self._component_configurator.running():
- self._component_configurator.shutdown()
- def read_bind10_config(self):
- """
- Reads the parameters associated with the BoB module itself.
- This means the the list of components we should be running.
- """
- logger.info(BIND10_READING_BOSS_CONFIGURATION)
- config_data = self.ccs.get_full_config()
- self.__propagate_component_config(config_data['components'])
-
- def log_starting(self, process, port = None, address = None):
- """
- A convenience function to output a "Starting xxx" message if the
- logging is set to DEBUG with debuglevel DBG_PROCESS or higher.
- Putting this into a separate method ensures
- that the output form is consistent across all processes.
- The process name (passed as the first argument) is put into
- self.curproc, and is used to indicate which process failed to
- start if there is an error (and is used in the "Started" message
- on success). The optional port and address information are
- appended to the message (if present).
- """
- self.curproc = process
- if port is None and address is None:
- logger.info(BIND10_STARTING_PROCESS, self.curproc)
- elif address is None:
- logger.info(BIND10_STARTING_PROCESS_PORT, self.curproc,
- port)
- else:
- logger.info(BIND10_STARTING_PROCESS_PORT_ADDRESS,
- self.curproc, address, port)
- def log_started(self, pid = None):
- """
- A convenience function to output a 'Started xxxx (PID yyyy)'
- message. As with starting_message(), this ensures a consistent
- format.
- """
- if pid is None:
- logger.debug(DBG_PROCESS, BIND10_STARTED_PROCESS, self.curproc)
- else:
- logger.debug(DBG_PROCESS, BIND10_STARTED_PROCESS_PID, self.curproc, pid)
-
-
-
-
- def start_msgq(self):
- """
- Start the message queue and connect to the command channel.
- """
- self.log_starting("b10-msgq")
- msgq_proc = ProcessInfo("b10-msgq", ["b10-msgq"], self.c_channel_env,
- True, not self.verbose, uid=self.uid,
- username=self.username)
- msgq_proc.spawn()
- self.log_started(msgq_proc.pid)
-
- cc_connect_start = time.time()
- while self.cc_session is None:
-
- if (time.time() - cc_connect_start) > 5:
- raise CChannelConnectError("Unable to connect to c-channel after 5 seconds")
-
- try:
- self.cc_session = isc.cc.Session(self.msgq_socket_file)
- except isc.cc.session.SessionError:
- time.sleep(0.1)
- return msgq_proc
- def start_cfgmgr(self):
- """
- Starts the configuration manager process
- """
- self.log_starting("b10-cfgmgr")
- args = ["b10-cfgmgr"]
- if self.data_path is not None:
- args.append("--data-path=" + self.data_path)
- if self.config_filename is not None:
- args.append("--config-filename=" + self.config_filename)
- bind_cfgd = ProcessInfo("b10-cfgmgr", args,
- self.c_channel_env, uid=self.uid,
- username=self.username)
- bind_cfgd.spawn()
- self.log_started(bind_cfgd.pid)
-
-
-
-
- time.sleep(1)
- return bind_cfgd
- def start_ccsession(self, c_channel_env):
- """
- Start the CC Session
- The argument c_channel_env is unused but is supplied to keep the
- argument list the same for all start_xxx methods.
- """
- self.log_starting("ccsession")
- self.ccs = isc.config.ModuleCCSession(SPECFILE_LOCATION,
- self.config_handler,
- self.command_handler)
- self.ccs.start()
- self.log_started()
-
- def start_process(self, name, args, c_channel_env, port=None, address=None):
- """
- Given a set of command arguments, start the process and output
- appropriate log messages. If the start is successful, the process
- is added to the list of started processes.
- The port and address arguments are for log messages only.
- """
- self.log_starting(name, port, address)
- newproc = ProcessInfo(name, args, c_channel_env)
- newproc.spawn()
- self.log_started(newproc.pid)
- return newproc
- def start_simple(self, name):
- """
- Most of the BIND-10 processes are started with the command:
- <process-name> [-v]
- ... where -v is appended if verbose is enabled. This method
- generates the arguments from the name and starts the process.
- """
-
- args = [name]
- if self.verbose:
- args += ['-v']
-
- return self.start_process(name, args, self.c_channel_env)
-
-
-
-
- def start_auth(self):
- """
- Start the Authoritative server
- """
- authargs = ['b10-auth']
- if self.nocache:
- authargs += ['-n']
- if self.uid:
- authargs += ['-u', str(self.uid)]
- if self.verbose:
- authargs += ['-v']
-
- return self.start_process("b10-auth", authargs, self.c_channel_env)
- def start_resolver(self):
- """
- Start the Resolver. At present, all these arguments and switches
- are pure speculation. As with the auth daemon, they should be
- read from the configuration database.
- """
- self.curproc = "b10-resolver"
-
- resargs = ['b10-resolver']
- if self.uid:
- resargs += ['-u', str(self.uid)]
- if self.verbose:
- resargs += ['-v']
-
- return self.start_process("b10-resolver", resargs, self.c_channel_env)
- def start_cmdctl(self):
- """
- Starts the command control process
- """
- args = ["b10-cmdctl"]
- if self.cmdctl_port is not None:
- args.append("--port=" + str(self.cmdctl_port))
- return self.start_process("b10-cmdctl", args, self.c_channel_env,
- self.cmdctl_port)
- def start_all_processes(self):
- """
- Starts up all the processes. Any exception generated during the
- starting of the processes is handled by the caller.
- """
-
- self._component_configurator.startup(self.__core_components)
-
-
- c_channel_env = self.c_channel_env
- self.start_ccsession(c_channel_env)
-
-
-
-
- self.read_bind10_config()
-
-
-
-
-
-
-
-
- if self.uid is not None:
- posix.setuid(self.uid)
- def startup(self):
- """
- Start the BoB instance.
- Returns None if successful, otherwise an string describing the
- problem.
- """
-
-
- c_channel_env = {}
- if self.msgq_socket_file is not None:
- c_channel_env["BIND10_MSGQ_SOCKET_FILE"] = self.msgq_socket_file
- logger.debug(DBG_PROCESS, BIND10_CHECK_MSGQ_ALREADY_RUNNING)
-
- try:
- self.cc_session = isc.cc.Session(self.msgq_socket_file)
- logger.fatal(BIND10_MSGQ_ALREADY_RUNNING)
- return "b10-msgq already running, or socket file not cleaned , cannot start"
- except isc.cc.session.SessionError:
-
- pass
-
-
- try:
- self.c_channel_env = c_channel_env
- self.start_all_processes()
- except Exception as e:
- self.kill_started_processes()
- return "Unable to start " + self.curproc + ": " + str(e)
-
- self.runnable = True
- self.__started = True
- return None
- def stop_process(self, process, recipient):
- """
- Stop the given process, friendly-like. The process is the name it has
- (in logs, etc), the recipient is the address on msgq.
- """
- logger.info(BIND10_STOP_PROCESS, process)
-
- self.cc_session.group_sendmsg({'command': ['shutdown']}, recipient,
- recipient)
- def component_shutdown(self, exitcode=0):
- """
- Stop the Boss instance from a components' request. The exitcode
- indicates the desired exit code.
- If we did not start yet, it raises an exception, which is meant
- to propagate through the component and configurator to the startup
- routine and abort the startup imediatelly. If it is started up already,
- we just mark it so we terminate soon.
- It does set the exit code in both cases.
- """
- if self.__stopping:
- return
- self.exitcode = exitcode
- if not self.__started:
- raise Exception("Component failed during startup");
- else:
- self.runnable = False
- def shutdown(self):
- """Stop the BoB instance."""
- logger.info(BIND10_SHUTDOWN)
- self.__stopping = True
-
- try:
- self._component_configurator.shutdown()
- except:
- pass
-
-
-
- time.sleep(1)
- self.reap_children()
-
- processes_to_stop = list(self.processes.values())
- for component in processes_to_stop:
- if component.pid() is None:
-
- continue
- logger.info(BIND10_SEND_SIGTERM, component.name(),
- component.pid())
- try:
- component.kill()
- except OSError:
-
-
- pass
-
- alive = self.processes
-
-
- while alive:
-
- time.sleep(0.1)
- self.reap_children()
- processes_to_stop = list(self.processes.values())
- alive = False
- for component in processes_to_stop:
- if component.pid() is None:
-
- continue
- alive = True
- logger.info(BIND10_SEND_SIGKILL, component.name(),
- component.pid())
- try:
- component.kill(True)
- except OSError:
-
-
- pass
- logger.info(BIND10_SHUTDOWN_COMPLETE)
- def _get_process_exit_status(self):
- return os.waitpid(-1, os.WNOHANG)
- def reap_children(self):
- """Check to see if any of our child processes have exited,
- and note this for later handling.
- """
- while True:
- try:
- (pid, exit_status) = self._get_process_exit_status()
- except OSError as o:
- if o.errno == errno.ECHILD: break
-
- raise
- if pid == 0: break
- if pid in self.processes:
-
- component = self.processes.pop(pid)
-
-
- if component.running() and self.runnable:
- component.failed()
- else:
- logger.info(BIND10_UNKNOWN_CHILD_PROCESS_ENDED, pid)
- def restart_processes(self):
- """
- Restart any dead processes:
- * Returns the time when the next process is ready to be restarted.
- * If the server is shutting down, returns 0.
- * If there are no processes, returns None.
- The values returned can be safely passed into select() as the
- timeout value.
- """
- next_restart = None
-
- if not self.runnable:
- return 0
-
- still_dead = {}
- now = time.time()
- for proc_info in self.dead_processes.values():
- restart_time = proc_info.restart_schedule.get_restart_time(now)
- if restart_time > now:
- if (next_restart is None) or (next_restart > restart_time):
- next_restart = restart_time
- still_dead[proc_info.pid] = proc_info
- else:
- logger.info(BIND10_RESURRECTING_PROCESS, proc_info.name)
- try:
- proc_info.respawn()
- self.processes[proc_info.pid] = proc_info
- logger.info(BIND10_RESURRECTED_PROCESS, proc_info.name, proc_info.pid)
- except:
- still_dead[proc_info.pid] = proc_info
-
- self.dead_processes = still_dead
-
- return next_restart
- def register_process(self, pid, info):
- """
- Put another process into boss to watch over it. When the process
- dies, the info.failed() is called with the exit code.
- """
- self.processes[pid] = info
- options = None
- boss_of_bind = None
- def reaper(signal_number, stack_frame):
- """A child process has died (SIGCHLD received)."""
-
-
-
- pass
- def get_signame(signal_number):
- """Return the symbolic name for a signal."""
- for sig in dir(signal):
- if sig.startswith("SIG") and sig[3].isalnum():
- if getattr(signal, sig) == signal_number:
- return sig
- return "Unknown signal %d" % signal_number
- def fatal_signal(signal_number, stack_frame):
- """We need to exit (SIGINT or SIGTERM received)."""
- global options
- global boss_of_bind
- logger.info(BIND10_RECEIVED_SIGNAL, get_signame(signal_number))
- signal.signal(signal.SIGCHLD, signal.SIG_DFL)
- boss_of_bind.runnable = False
- def process_rename(option, opt_str, value, parser):
- """Function that renames the process if it is requested by a option."""
- isc.util.process.rename(value)
- def parse_args(args=sys.argv[1:], Parser=OptionParser):
- """
- Function for parsing command line arguments. Returns the
- options object from OptionParser.
- """
- parser = Parser(version=VERSION)
- parser.add_option("-m", "--msgq-socket-file", dest="msgq_socket_file",
- type="string", default=None,
- help="UNIX domain socket file the b10-msgq daemon will use")
- parser.add_option("-n", "--no-cache", action="store_true", dest="nocache",
- default=False, help="disable hot-spot cache in authoritative DNS server")
- parser.add_option("-u", "--user", dest="user", type="string", default=None,
- help="Change user after startup (must run as root)")
- parser.add_option("-v", "--verbose", dest="verbose", action="store_true",
- help="display more about what is going on")
- parser.add_option("--pretty-name", type="string", action="callback",
- callback=process_rename,
- help="Set the process name (displayed in ps, top, ...)")
- parser.add_option("-c", "--config-file", action="store",
- dest="config_file", default=None,
- help="Configuration database filename")
- parser.add_option("-p", "--data-path", dest="data_path",
- help="Directory to search for configuration files",
- default=None)
- parser.add_option("--cmdctl-port", dest="cmdctl_port", type="int",
- default=None, help="Port of command control")
- parser.add_option("--pid-file", dest="pid_file", type="string",
- default=None,
- help="file to dump the PID of the BIND 10 process")
- parser.add_option("--brittle", dest="brittle", action="store_true",
- help="debugging flag: exit if any component dies")
- (options, args) = parser.parse_args(args)
- if options.cmdctl_port is not None:
- try:
- isc.net.parse.port_parse(options.cmdctl_port)
- except ValueError as e:
- parser.error(e)
- if args:
- parser.print_help()
- sys.exit(1)
- return options
- def dump_pid(pid_file):
- """
- Dump the PID of the current process to the specified file. If the given
- file is None this function does nothing. If the file already exists,
- the existing content will be removed. If a system error happens in
- creating or writing to the file, the corresponding exception will be
- propagated to the caller.
- """
- if pid_file is None:
- return
- f = open(pid_file, "w")
- f.write('%d\n' % os.getpid())
- f.close()
- def unlink_pid_file(pid_file):
- """
- Remove the given file, which is basically expected to be the PID file
- created by dump_pid(). The specified may or may not exist; if it
- doesn't this function does nothing. Other system level errors in removing
- the file will be propagated as the corresponding exception.
- """
- if pid_file is None:
- return
- try:
- os.unlink(pid_file)
- except OSError as error:
- if error.errno is not errno.ENOENT:
- raise
- def main():
- global options
- global boss_of_bind
-
- sys.stdout = io.TextIOWrapper(sys.stdout.detach(), line_buffering=True)
- options = parse_args()
-
- setuid = None
- username = None
- if options.user:
-
- try:
- pw_ent = pwd.getpwuid(int(options.user))
- setuid = pw_ent.pw_uid
- username = pw_ent.pw_name
- except ValueError:
- pass
- except KeyError:
- pass
-
-
-
-
- try:
- pw_ent = pwd.getpwnam(options.user)
- setuid = pw_ent.pw_uid
- username = pw_ent.pw_name
- except KeyError:
- pass
- if setuid is None:
- logger.fatal(BIND10_INVALID_USER, options.user)
- sys.exit(1)
-
- logger.info(BIND10_STARTING, VERSION)
-
- wakeup_pipe = os.pipe()
- signal.set_wakeup_fd(wakeup_pipe[1])
-
-
- signal.signal(signal.SIGCHLD, reaper)
- signal.siginterrupt(signal.SIGCHLD, False)
- signal.signal(signal.SIGINT, fatal_signal)
- signal.signal(signal.SIGTERM, fatal_signal)
-
- signal.signal(signal.SIGPIPE, signal.SIG_IGN)
-
- boss_of_bind = BoB(options.msgq_socket_file, options.data_path,
- options.config_file, options.nocache, options.verbose,
- setuid, username, options.cmdctl_port, options.brittle)
- startup_result = boss_of_bind.startup()
- if startup_result:
- logger.fatal(BIND10_STARTUP_ERROR, startup_result)
- sys.exit(1)
- logger.info(BIND10_STARTUP_COMPLETE)
- dump_pid(options.pid_file)
-
-
- wakeup_fd = wakeup_pipe[0]
- ccs_fd = boss_of_bind.ccs.get_socket().fileno()
- while boss_of_bind.runnable:
-
- boss_of_bind.reap_children()
- next_restart = boss_of_bind.restart_processes()
- if next_restart is None:
- wait_time = None
- else:
- wait_time = max(next_restart - time.time(), 0)
-
-
-
- try:
- (rlist, wlist, xlist) = select.select([wakeup_fd, ccs_fd], [], [],
- wait_time)
- except select.error as err:
- if err.args[0] == errno.EINTR:
- (rlist, wlist, xlist) = ([], [], [])
- else:
- logger.fatal(BIND10_SELECT_ERROR, err)
- break
- for fd in rlist + xlist:
- if fd == ccs_fd:
- try:
- boss_of_bind.ccs.check_command()
- except isc.cc.session.ProtocolError:
- logger.fatal(BIND10_MSGQ_DISAPPEARED)
- self.runnable = False
- break
- elif fd == wakeup_fd:
- os.read(wakeup_fd, 32)
-
- signal.signal(signal.SIGCHLD, signal.SIG_DFL)
- boss_of_bind.shutdown()
- unlink_pid_file(options.pid_file)
- sys.exit(boss_of_bind.exitcode)
- if __name__ == "__main__":
- main()
|