diff --git a/.gitignore b/.gitignore index 18892a31fe..b305318e13 100644 --- a/.gitignore +++ b/.gitignore @@ -43,6 +43,7 @@ stamp-h1 /cf-serverd/Makefile /cf-testd/Makefile /cf-upgrade/Makefile +/cf-watchd/Makefile /contrib/vagrant-ci/centos-9s-x64/Makefile /examples/Makefile /ext/Makefile @@ -112,6 +113,8 @@ xml_tmp_suite /cf-testd/cf-testd.exe /cf-upgrade/cf-upgrade /cf-upgrade/cf-upgrade.exe +/cf-watchd/cf-watchd +/cf-watchd/cf-watchd.exe /ext/rpmvercmp /ext/rpmvercmp.exe diff --git a/Makefile.am b/Makefile.am index eaf020ee91..7313935af8 100644 --- a/Makefile.am +++ b/Makefile.am @@ -45,6 +45,7 @@ SUBDIRS = \ cf-upgrade \ cf-net \ cf-secret \ + cf-watchd \ misc \ python \ ext \ diff --git a/cf-watchd/Makefile.am b/cf-watchd/Makefile.am new file mode 100644 index 0000000000..43c2398aac --- /dev/null +++ b/cf-watchd/Makefile.am @@ -0,0 +1,52 @@ +# +# Copyright 2021 Northern.tech AS +# +# This file is part of CFEngine 3 - written and maintained by Northern.tech AS. +# +# 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; version 3. +# +# 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 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 +# +# To the extent this program is licensed as part of the Enterprise +# versions of CFEngine, the applicable Commercial Open Source License +# (COSL) may apply to this file if you as a licensee so wish it. See +# included file COSL.txt. +# +noinst_LTLIBRARIES = libcf-watchd.la + +AM_CPPFLAGS = -I$(srcdir)/../libpromises -I$(srcdir)/../libntech/libutils \ + -I$(srcdir)/../libcfecompat \ + -I$(srcdir)/../libcfnet \ + $(OPENSSL_CPPFLAGS) \ + $(PCRE2_CPPFLAGS) \ + $(ENTERPRISE_CPPFLAGS) + +AM_CFLAGS = @CFLAGS@ \ + $(OPENSSL_CFLAGS) \ + $(ENTERPRISE_CFLAGS) + +libcf_watchd_la_LIBADD = ../libpromises/libpromises.la + +libcf_watchd_la_SOURCES = cf-watchd.c + +if !BUILTIN_EXTENSIONS + bin_PROGRAMS = cf-watchd + cf_watchd_LDADD = libcf-watchd.la + cf_watchd_SOURCES = +endif + +CLEANFILES = *.gcno *.gcda + +# +# Some basic clean ups +# +MOSTLYCLEANFILES = *~ *.orig *.rej diff --git a/cf-watchd/cf-watchd.c b/cf-watchd/cf-watchd.c new file mode 100644 index 0000000000..cb1e519081 --- /dev/null +++ b/cf-watchd/cf-watchd.c @@ -0,0 +1,257 @@ +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + + +static GenericAgentConfig *CheckOpts(int argc, char **argv); +static void WatchStart(); + +/*****************************************************************************/ +/* Globals */ +/*****************************************************************************/ + +int NO_FORK = false; + +/*******************************************************************/ +/* Command line options */ +/*******************************************************************/ + + +static const char *const CF_WATCHD_SHORT_DESCRIPTION = + "event watching daemon for CFEngine"; + +static const char *const CF_WATCHD_MANPAGE_LONG_DESCRIPTION = + "cf-watchd is the event watching and reacting daemon for CFEngine. It watches specific events defined in the policy " + "and runs policy code on reaction to these events."; + +static const Component COMPONENT = +{ + .name = "cf-watchd", + .website = CF_WEBSITE, + .copyright = CF_COPYRIGHT +}; + +static const struct option OPTIONS[] = +{ + {"help", no_argument, 0, 'h'}, + {"debug", no_argument, 0, 'd'}, + {"verbose", no_argument, 0, 'v'}, + {"version", no_argument, 0, 'V'}, + {"no-lock", no_argument, 0, 'K'}, + {"file", required_argument, 0, 'f'}, + {"log-level", required_argument, 0, 'g'}, + {"inform", no_argument, 0, 'I'}, + {"no-fork", no_argument, 0, 'F'}, + {"man", no_argument, 0, 'M'}, + {"color", optional_argument, 0, 'C'}, + {"timestamp", no_argument, 0, 'l'}, + /* Only long option for the rest */ + {"ignore-preferred-augments", no_argument, 0, 0}, + {NULL, 0, 0, '\0'} +}; + +static const char *const HINTS[] = +{ + "Print the help message", + "Enable debugging output", + "Output verbose information about the behaviour of cf-watchd", + "Output the version of the software", + "Ignore system lock", + "Specify an alternative input file than the default. This option is overridden by FILE if supplied as argument.", + "Specify how detailed logs should be. Possible values: 'error', 'warning', 'notice', 'info', 'verbose', 'debug'", + "Print basic information about changes made to the system, i.e. promises repaired", + "Run process in foreground, not as a daemon", + "Outputs the man page of the program", + "Enable colorized output. Possible values: 'always', 'auto', 'never'. If option is used, the default value is 'auto'", + "Log timestamps on each line of log output", + "Ignore def_preferred.json file in favor of def.json", + NULL +}; + +/*****************************************************************************/ + +int main(int argc, char **argv) +{ + GenericAgentConfig *config = CheckOpts(argc, argv); + EvalContext *ctx = EvalContextNew(); + GenericAgentConfigApply(ctx, config); + + pid_t existing_pid = ReadPID("cf-watchd.pid"); + if ((existing_pid != -1) && (kill(existing_pid, 0) == 0)) + { + Log(LOG_LEVEL_ERR, "Another instance of cf-watchd is already running, terminating"); + return 1; + } + +#ifdef __MINGW32__ + + if (!NO_FORK) + { + Log(LOG_LEVEL_VERBOSE, "Windows does not support starting processes in the background - starting in foreground"); + } + +#else /* !__MINGW32__ */ + + if ((!NO_FORK) && (fork() != 0)) + { + Log(LOG_LEVEL_INFO, "cf-watchd: starting"); + _exit(EXIT_SUCCESS); + } + + if (!NO_FORK) + { + ActAsDaemon(); + } + +#endif /* !__MINGW32__ */ + + + umask(077); + WritePID("cf-watchd.pid"); + + signal(SIGINT, HandleSignalsForDaemon); + signal(SIGTERM, HandleSignalsForDaemon); + signal(SIGBUS, HandleSignalsForDaemon); + signal(SIGHUP, HandleSignalsForDaemon); + signal(SIGPIPE, SIG_IGN); + signal(SIGUSR1, HandleSignalsForDaemon); + signal(SIGUSR2, HandleSignalsForDaemon); + + WatchStart(); + + GenericAgentFinalize(ctx, config); + CallCleanupFunctions(); + + return 0; +} + +static GenericAgentConfig *CheckOpts(int argc, char **argv) +{ + extern char *optarg; + int c; + GenericAgentConfig *config = GenericAgentConfigNewDefault(AGENT_TYPE_WATCH, GetTTYInteractive()); + + int longopt_idx; + while ((c = getopt_long(argc, argv, "dvIf:g:VKMFhC::l", + OPTIONS, &longopt_idx)) != -1) + { + switch (c) + { + case 'f': + GenericAgentConfigSetInputFile(config, GetInputDir(), optarg); + MINUSF = true; + break; + + case 'd': + LogSetGlobalLevel(LOG_LEVEL_DEBUG); + NO_FORK = true; + break; + + case 'K': + config->ignore_locks = true; + break; + + case 'I': + LogSetGlobalLevel(LOG_LEVEL_INFO); + break; + + case 'v': + LogSetGlobalLevel(LOG_LEVEL_VERBOSE); + NO_FORK = true; + break; + + case 'g': + LogSetGlobalLevelArgOrExit(optarg); + break; + + case 'F': + NO_FORK = true; + break; + + case 'V': + { + Writer *w = FileWriter(stdout); + GenericAgentWriteVersion(w); + FileWriterDetach(w); + } + DoCleanupAndExit(EXIT_SUCCESS); + + case 'h': + { + Writer *w = FileWriter(stdout); + WriterWriteHelp(w, &COMPONENT, OPTIONS, HINTS, NULL, false, true); + FileWriterDetach(w); + } + DoCleanupAndExit(EXIT_SUCCESS); + + case 'M': + { + Writer *out = FileWriter(stdout); + ManPageWrite(out, "cf-watchd", time(NULL), + CF_WATCHD_SHORT_DESCRIPTION, + CF_WATCHD_MANPAGE_LONG_DESCRIPTION, + OPTIONS, HINTS, + NULL, false, + true); + FileWriterDetach(out); + DoCleanupAndExit(EXIT_SUCCESS); + } + + case 'C': + if (!GenericAgentConfigParseColor(config, optarg)) + { + DoCleanupAndExit(EXIT_FAILURE); + } + break; + + case 'l': + LoggingEnableTimestamps(true); + break; + + /* long options only */ + case 0: + { + const char *const option_name = OPTIONS[longopt_idx].name; + if (StringEqual(option_name, "ignore-preferred-augments")) + { + config->ignore_preferred_augments = true; + } + break; + } + + default: + { + Writer *w = FileWriter(stdout); + WriterWriteHelp(w, &COMPONENT, OPTIONS, HINTS, NULL, false, true); + FileWriterDetach(w); + } + DoCleanupAndExit(EXIT_FAILURE); + } + } + + if (!GenericAgentConfigParseArguments(config, argc - optind, argv + optind)) + { + Log(LOG_LEVEL_ERR, "Too many arguments"); + DoCleanupAndExit(EXIT_FAILURE); + } + + return config; +} + +static void WatchStart() +{ + for (int i = 0; !IsPendingTermination(); i++) + { + /* Do something */ + Log(LOG_LEVEL_INFO, "cf-watchd: %d", i); + sleep(1); + } +} diff --git a/configure.ac b/configure.ac index 6202c22835..ebae9acbc4 100644 --- a/configure.ac +++ b/configure.ac @@ -2005,6 +2005,7 @@ AC_CONFIG_FILES([Makefile cf-testd/Makefile cf-net/Makefile cf-secret/Makefile + cf-watchd/Makefile config.post.h contrib/vagrant-ci/centos-9s-x64/Makefile misc/Makefile diff --git a/libpromises/cf3.defs.h b/libpromises/cf3.defs.h index 7a25a2452a..fd53bf8a68 100644 --- a/libpromises/cf3.defs.h +++ b/libpromises/cf3.defs.h @@ -398,6 +398,7 @@ typedef enum #define CF_RUNC "runagent" #define CF_KEYGEN "keygenerator" #define CF_HUBC "hub" +#define CF_WATCHC "watch" typedef enum { @@ -409,6 +410,7 @@ typedef enum AGENT_TYPE_RUNAGENT, AGENT_TYPE_KEYGEN, AGENT_TYPE_HUB, + AGENT_TYPE_WATCH, AGENT_TYPE_NOAGENT } AgentType; diff --git a/libpromises/constants.c b/libpromises/constants.c index f635c58871..7562b5aa64 100644 --- a/libpromises/constants.c +++ b/libpromises/constants.c @@ -72,6 +72,7 @@ const char *const CF_AGENTTYPES[] = /* see enum cfagenttype */ CF_RUNC, CF_KEYGEN, CF_HUBC, + CF_WATCHC, "", };