diff --git a/cf-reactor/cf-reactor.c b/cf-reactor/cf-reactor.c index 80169153a91..55ba9c3d873 100644 --- a/cf-reactor/cf-reactor.c +++ b/cf-reactor/cf-reactor.c @@ -30,11 +30,14 @@ #include #include #include +#include /* LoadPolicy */ +#include /* UpdateLastPolicyUpdateTime */ +#include /* GetInputDir */ #include #include #include #include /* signal, kill */ -#include /* GetSignalPipe, MakeSignalPipe, IsPendingTermination, HandleSignalsForDaemon */ +#include /* GetSignalPipe, MakeSignalPipe, IsPendingTermination, HandleSignalsForDaemon, ReloadConfigRequested, ClearRequestReloadConfig */ #include #include @@ -49,6 +52,7 @@ int NO_FORK = false; /*****************************************************************************/ #define DEFAULT_POLL_INTERVAL_SECS 30 +#define DEFAULT_POLICY_CHECK_INTERVAL_SECS (5 * 60) /*******************************************************************/ /* Command line options */ @@ -70,6 +74,7 @@ static const char *const CF_REACTOR_MANPAGE_LONG_DESCRIPTION = static const struct option OPTIONS[] = { {"debug", no_argument, 0, 'd'}, + {"file", required_argument, 0, 'f'}, {"no-fork", no_argument, 0, 'F'}, {"log-level", required_argument, 0, 'g'}, {"help", no_argument, 0, 'h'}, @@ -84,6 +89,7 @@ static const struct option OPTIONS[] = static const char *const HINTS[] = { "Enable debugging output and run in foreground", + "Specify an alternative input file than the default. This option is overridden by FILE if supplied as argument.", "Run as a foreground process (do not fork)", "Specify how detailed logs should be. Possible values: 'error', 'warning', 'notice', 'info', 'verbose', 'debug'", "Print the help message", @@ -102,11 +108,15 @@ static GenericAgentConfig *CheckOpts(int argc, char **argv) GenericAgentConfig *config = GenericAgentConfigNewDefault(AGENT_TYPE_REACTOR, GetTTYInteractive()); int longopt_idx; - while ((c = getopt_long(argc, argv, "dFg:hIlMvV", + while ((c = getopt_long(argc, argv, "df:Fg:hIlMvV", OPTIONS, &longopt_idx)) != -1) { switch (c) { + case 'f': + GenericAgentConfigSetInputFile(config, GetInputDir(), optarg); + MINUSF = true; + break; case 'd': LogSetGlobalLevel(LOG_LEVEL_DEBUG); NO_FORK = true; @@ -190,6 +200,54 @@ static GenericAgentConfig *CheckOpts(int argc, char **argv) /*****************************************************************************/ +static void CheckPolicyUpdates(EvalContext *ctx, Policy **policy, GenericAgentConfig *config) +{ + assert(config != NULL); + + time_t validated_at = ReadTimestampFromPolicyValidatedFile(config, NULL); + + bool reload_policy = false; + if (config->agent_specific.daemon.last_validated_at < validated_at) + { + Log(LOG_LEVEL_VERBOSE, "Policy changes detected by cf-reactor, reloading policy"); + reload_policy = true; + } + if (ReloadConfigRequested()) + { + Log(LOG_LEVEL_VERBOSE, "Policy reload requested, reloading policy"); + reload_policy = true; + } + + if (!reload_policy) + { + Log(LOG_LEVEL_DEBUG, "No policy changes seen by cf-reactor"); + return; + } + + ClearRequestReloadConfig(); + config->agent_specific.daemon.last_validated_at = validated_at; + + if (!GenericAgentArePromisesValid(config)) + { + Log(LOG_LEVEL_INFO, "Policy file '%s' has errors, cf-reactor keeping the previous policy", + config->input_file); + return; + } + + Log(LOG_LEVEL_NOTICE, "Rereading policy file '%s'", config->input_file); + + EvalContextClear(ctx); + PolicyDestroy(*policy); + *policy = NULL; + + UpdateLastPolicyUpdateTime(ctx); + GenericAgentDiscoverContext(ctx, config, NULL); + + *policy = LoadPolicy(ctx, config); +} + +/*****************************************************************************/ + int main(int argc, char *argv[]) { @@ -197,6 +255,20 @@ int main(int argc, char *argv[]) EvalContext *ctx = EvalContextNew(); GenericAgentConfigApply(ctx, config); + const char *program_invocation_name = argv[0]; + const char *last_dir_sep = strrchr(program_invocation_name, FILE_SEPARATOR); + const char *program_name = (last_dir_sep != NULL ? last_dir_sep + 1 : program_invocation_name); + GenericAgentDiscoverContext(ctx, config, program_name); + + Policy *policy = SelectAndLoadPolicy(config, ctx, false, false); + if (policy == NULL) + { + Log(LOG_LEVEL_ERR, "Error reading CFEngine policy. Exiting..."); + DoCleanupAndExit(EXIT_FAILURE); + } + + GenericAgentPostLoadInit(ctx); + #ifdef __MINGW32__ if (!NO_FORK) @@ -253,15 +325,17 @@ int main(int argc, char *argv[]) /* We need an initial value here for the first iteration of the cycle * below. */ time_t next_tick = time(NULL) + DEFAULT_POLL_INTERVAL_SECS; + time_t next_policy_check = time(NULL) + DEFAULT_POLICY_CHECK_INTERVAL_SECS; while (!IsPendingTermination()) { int max_fd = ReactorContextSetupFileDescriptors(&reactor_ctx); /* Determine how much time is remaining until the next tick. */ time_t last_tick = time(NULL); - time_t remaining = next_tick > last_tick ? next_tick - last_tick : 0; + time_t remaining_tick = next_tick > last_tick ? next_tick - last_tick : 0; + time_t remaining_policy_check = next_policy_check > last_tick ? next_policy_check - last_tick : 0; - struct timeval timeout = { .tv_sec = remaining }; + struct timeval timeout = { .tv_sec = MIN(remaining_tick, remaining_policy_check) }; int ret = select(max_fd, &reactor_ctx.readfds, NULL, NULL, &timeout); /* Reschedule the backstop tick against the current time (not @@ -290,14 +364,23 @@ int main(int argc, char *argv[]) { /*** timeout ***/ ReactorNovaHandleTimeout(&next_tick); - continue; } - /* else */ + else + { + ReactorContextHandleEvents(&reactor_ctx, &next_tick); + } + - ReactorContextHandleEvents(&reactor_ctx, &next_tick); + time_t now = time(NULL); + if (now >= next_policy_check || ReloadConfigRequested()) + { + CheckPolicyUpdates(ctx, &policy, config); + next_policy_check = now + DEFAULT_POLICY_CHECK_INTERVAL_SECS; + } } ReactorContextFinalize(&reactor_ctx); + PolicyDestroy(policy); GenericAgentFinalize(ctx, config); CallCleanupFunctions(); diff --git a/libpromises/cf3parse_logic.h b/libpromises/cf3parse_logic.h index 2f98c586019..e3658711388 100644 --- a/libpromises/cf3parse_logic.h +++ b/libpromises/cf3parse_logic.h @@ -223,6 +223,13 @@ static bool RelevantBundle(const char *agent, const char *blocktype) return true; } + // cf-reactor should keep agent bundles + if (StringEqual(agent, CF_AGENTTYPES[AGENT_TYPE_REACTOR]) + && StringEqual(blocktype, CF_AGENTTYPES[AGENT_TYPE_AGENT])) + { + return true; + } + /* Here are some additional bundle types handled by cfAgent */ Item *ip = SplitString("edit_line,edit_xml", ','); diff --git a/libpromises/generic_agent.c b/libpromises/generic_agent.c index 3e282c946ec..6cc9f6897c0 100644 --- a/libpromises/generic_agent.c +++ b/libpromises/generic_agent.c @@ -1194,12 +1194,15 @@ static bool IsPolicyPrecheckNeeded(GenericAgentConfig *config, bool force_valida bool GenericAgentCheckPolicy(GenericAgentConfig *config, bool force_validation, bool write_validated_file) { + assert(config != NULL); + if (!MissingInputFile(config->input_file)) { { if (config->agent_type == AGENT_TYPE_SERVER || config->agent_type == AGENT_TYPE_MONITOR || - config->agent_type == AGENT_TYPE_EXECUTOR) + config->agent_type == AGENT_TYPE_EXECUTOR || + config->agent_type == AGENT_TYPE_REACTOR) { time_t validated_at = ReadTimestampFromPolicyValidatedFile(config, NULL); config->agent_specific.daemon.last_validated_at = validated_at;