From 499897f2a640e04e97865f89400885084e6aee82 Mon Sep 17 00:00:00 2001 From: Sean Campbell Date: Wed, 6 Jun 2018 10:04:53 -0400 Subject: [PATCH] Refactor collectd binary to separate Unix-specific code. Move OS-specific code from collectd.c into cmd.c. This makes it easier to use the non-Linux-specific code when we port to Windows. --- Makefile.am | 2 + src/daemon/cmd.c | 271 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/daemon/cmd.h | 41 ++++++++ src/daemon/collectd.c | 256 ++--------------------------------------------- 4 files changed, 324 insertions(+), 246 deletions(-) create mode 100644 src/daemon/cmd.c create mode 100644 src/daemon/cmd.h diff --git a/Makefile.am b/Makefile.am index 612346e1..64414fbd 100644 --- a/Makefile.am +++ b/Makefile.am @@ -195,6 +195,8 @@ endif collectd_SOURCES = \ + src/daemon/cmd.c \ + src/daemon/cmd.h \ src/daemon/collectd.c \ src/daemon/collectd.h \ src/daemon/configfile.c \ diff --git a/src/daemon/cmd.c b/src/daemon/cmd.c new file mode 100644 index 00000000..7b779955 --- /dev/null +++ b/src/daemon/cmd.c @@ -0,0 +1,271 @@ +/** + * collectd - src/daemon/cmd.c + * Copyright (C) 2005-2007 Florian octo Forster + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + **/ + +#include "cmd.h" +#include "collectd.h" + +#include "common.h" +#include + +static void *do_flush(void __attribute__((unused)) * arg) { + INFO("Flushing all data."); + plugin_flush(/* plugin = */ NULL, + /* timeout = */ 0, + /* ident = */ NULL); + INFO("Finished flushing all data."); + pthread_exit(NULL); + return NULL; +} + +static void sig_int_handler(int __attribute__((unused)) signal) { + stop_collectd(); +} + +static void sig_term_handler(int __attribute__((unused)) signal) { + stop_collectd(); +} + +static void sig_usr1_handler(int __attribute__((unused)) signal) { + pthread_t thread; + pthread_attr_t attr; + + /* flushing the data might take a while, + * so it should be done asynchronously */ + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + pthread_create(&thread, &attr, do_flush, NULL); + pthread_attr_destroy(&attr); +} + +#if COLLECT_DAEMON +static int pidfile_create(void) { + FILE *fh; + const char *file = global_option_get("PIDFile"); + + if ((fh = fopen(file, "w")) == NULL) { + ERROR("fopen (%s): %s", file, STRERRNO); + return 1; + } + + fprintf(fh, "%i\n", (int)getpid()); + fclose(fh); + + return 0; +} /* static int pidfile_create (const char *file) */ + +static int pidfile_remove(void) { + const char *file = global_option_get("PIDFile"); + if (file == NULL) + return 0; + + return unlink(file); +} /* static int pidfile_remove (const char *file) */ +#endif /* COLLECT_DAEMON */ + +#ifdef KERNEL_LINUX +static int notify_upstart(void) { + char const *upstart_job = getenv("UPSTART_JOB"); + + if (upstart_job == NULL) + return 0; + + if (strcmp(upstart_job, "collectd") != 0) { + WARNING("Environment specifies unexpected UPSTART_JOB=\"%s\", expected " + "\"collectd\". Ignoring the variable.", + upstart_job); + return 0; + } + + NOTICE("Upstart detected, stopping now to signal readiness."); + raise(SIGSTOP); + unsetenv("UPSTART_JOB"); + + return 1; +} + +static int notify_systemd(void) { + size_t su_size; + const char *notifysocket = getenv("NOTIFY_SOCKET"); + if (notifysocket == NULL) + return 0; + + if ((strlen(notifysocket) < 2) || + ((notifysocket[0] != '@') && (notifysocket[0] != '/'))) { + ERROR("invalid notification socket NOTIFY_SOCKET=\"%s\": path must be " + "absolute", + notifysocket); + return 0; + } + NOTICE("Systemd detected, trying to signal readiness."); + + unsetenv("NOTIFY_SOCKET"); + + int fd; +#if defined(SOCK_CLOEXEC) + fd = socket(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, /* protocol = */ 0); +#else + fd = socket(AF_UNIX, SOCK_DGRAM, /* protocol = */ 0); +#endif + if (fd < 0) { + ERROR("creating UNIX socket failed: %s", STRERRNO); + return 0; + } + + struct sockaddr_un su = {0}; + su.sun_family = AF_UNIX; + if (notifysocket[0] != '@') { + /* regular UNIX socket */ + sstrncpy(su.sun_path, notifysocket, sizeof(su.sun_path)); + su_size = sizeof(su); + } else { + /* Linux abstract namespace socket: specify address as "\0foo", i.e. + * start with a null byte. Since null bytes have no special meaning in + * that case, we have to set su_size correctly to cover only the bytes + * that are part of the address. */ + sstrncpy(su.sun_path, notifysocket, sizeof(su.sun_path)); + su.sun_path[0] = 0; + su_size = sizeof(sa_family_t) + strlen(notifysocket); + if (su_size > sizeof(su)) + su_size = sizeof(su); + } + + const char buffer[] = "READY=1\n"; + if (sendto(fd, buffer, strlen(buffer), MSG_NOSIGNAL, (void *)&su, + (socklen_t)su_size) < 0) { + ERROR("sendto(\"%s\") failed: %s", notifysocket, STRERRNO); + close(fd); + return 0; + } + + unsetenv("NOTIFY_SOCKET"); + close(fd); + return 1; +} +#endif /* KERNEL_LINUX */ + +int main(int argc, char **argv) { + struct cmdline_config config = init_config(argc, argv); + +#if COLLECT_DAEMON + /* + * fork off child + */ + struct sigaction sig_chld_action = {.sa_handler = SIG_IGN}; + + sigaction(SIGCHLD, &sig_chld_action, NULL); + + /* + * Only daemonize if we're not being supervised + * by upstart or systemd (when using Linux). + */ + if (config.daemonize +#ifdef KERNEL_LINUX + && notify_upstart() == 0 && notify_systemd() == 0 +#endif + ) { + pid_t pid; + if ((pid = fork()) == -1) { + /* error */ + fprintf(stderr, "fork: %s", STRERRNO); + return 1; + } else if (pid != 0) { + /* parent */ + /* printf ("Running (PID %i)\n", pid); */ + return 0; + } + + /* Detach from session */ + setsid(); + + /* Write pidfile */ + if (pidfile_create()) + exit(2); + + /* close standard descriptors */ + close(2); + close(1); + close(0); + + int status = open("/dev/null", O_RDWR); + if (status != 0) { + ERROR("Error: Could not connect `STDIN' to `/dev/null' (status %d)", + status); + return 1; + } + + status = dup(0); + if (status != 1) { + ERROR("Error: Could not connect `STDOUT' to `/dev/null' (status %d)", + status); + return 1; + } + + status = dup(0); + if (status != 2) { + ERROR("Error: Could not connect `STDERR' to `/dev/null', (status %d)", + status); + return 1; + } + } /* if (config.daemonize) */ +#endif /* COLLECT_DAEMON */ + + struct sigaction sig_pipe_action = {.sa_handler = SIG_IGN}; + + sigaction(SIGPIPE, &sig_pipe_action, NULL); + + /* + * install signal handlers + */ + struct sigaction sig_int_action = {.sa_handler = sig_int_handler}; + + if (sigaction(SIGINT, &sig_int_action, NULL) != 0) { + ERROR("Error: Failed to install a signal handler for signal INT: %s", + STRERRNO); + return 1; + } + + struct sigaction sig_term_action = {.sa_handler = sig_term_handler}; + + if (sigaction(SIGTERM, &sig_term_action, NULL) != 0) { + ERROR("Error: Failed to install a signal handler for signal TERM: %s", + STRERRNO); + return 1; + } + + struct sigaction sig_usr1_action = {.sa_handler = sig_usr1_handler}; + + if (sigaction(SIGUSR1, &sig_usr1_action, NULL) != 0) { + ERROR("Error: Failed to install a signal handler for signal USR1: %s", + STRERRNO); + return 1; + } + + int exit_status = run_loop(config.test_readall); + +#if COLLECT_DAEMON + if (config.daemonize) + pidfile_remove(); +#endif /* COLLECT_DAEMON */ + + return exit_status; +} /* int main */ diff --git a/src/daemon/cmd.h b/src/daemon/cmd.h new file mode 100644 index 00000000..152ee63e --- /dev/null +++ b/src/daemon/cmd.h @@ -0,0 +1,41 @@ +/** + * collectd - src/daemon/cmd.h + * Copyright (C) 2018 Florian octo Forster + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + **/ + +#ifndef CMD_H +#define CMD_H + +#include + +struct cmdline_config { + bool test_config; + bool test_readall; + bool create_basedir; + const char *configfile; + bool daemonize; +}; + +void stop_collectd(void); +struct cmdline_config init_config(int argc, char **argv); +int run_loop(bool test_readall); + +#endif /* CMD_H */ diff --git a/src/daemon/collectd.c b/src/daemon/collectd.c index 7e859750..b4668e06 100644 --- a/src/daemon/collectd.c +++ b/src/daemon/collectd.c @@ -25,6 +25,7 @@ * Alvaro Barcellos **/ +#include "cmd.h" #include "collectd.h" #include "common.h" @@ -33,7 +34,6 @@ #include #include -#include #if HAVE_LOCALE_H #include @@ -53,32 +53,6 @@ static int loop; -static void *do_flush(void __attribute__((unused)) * arg) { - INFO("Flushing all data."); - plugin_flush(/* plugin = */ NULL, - /* timeout = */ 0, - /* ident = */ NULL); - INFO("Finished flushing all data."); - pthread_exit(NULL); - return NULL; -} - -static void sig_int_handler(int __attribute__((unused)) signal) { loop++; } - -static void sig_term_handler(int __attribute__((unused)) signal) { loop++; } - -static void sig_usr1_handler(int __attribute__((unused)) signal) { - pthread_t thread; - pthread_attr_t attr; - - /* flushing the data might take a while, - * so it should be done asynchronously */ - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); - pthread_create(&thread, &attr, do_flush, NULL); - pthread_attr_destroy(&attr); -} - static int init_hostname(void) { const char *str = global_option_get("Hostname"); if (str && str[0] != '\0') { @@ -318,120 +292,6 @@ static int do_shutdown(void) { return plugin_shutdown_all(); } /* int do_shutdown */ -#if COLLECT_DAEMON -static int pidfile_create(void) { - FILE *fh; - const char *file = global_option_get("PIDFile"); - - if ((fh = fopen(file, "w")) == NULL) { - ERROR("fopen (%s): %s", file, STRERRNO); - return 1; - } - - fprintf(fh, "%i\n", (int)getpid()); - fclose(fh); - - return 0; -} /* static int pidfile_create (const char *file) */ - -static int pidfile_remove(void) { - const char *file = global_option_get("PIDFile"); - if (file == NULL) - return 0; - - return unlink(file); -} /* static int pidfile_remove (const char *file) */ -#endif /* COLLECT_DAEMON */ - -#ifdef KERNEL_LINUX -static int notify_upstart(void) { - char const *upstart_job = getenv("UPSTART_JOB"); - - if (upstart_job == NULL) - return 0; - - if (strcmp(upstart_job, "collectd") != 0) { - WARNING("Environment specifies unexpected UPSTART_JOB=\"%s\", expected " - "\"collectd\". Ignoring the variable.", - upstart_job); - return 0; - } - - NOTICE("Upstart detected, stopping now to signal readiness."); - raise(SIGSTOP); - unsetenv("UPSTART_JOB"); - - return 1; -} - -static int notify_systemd(void) { - size_t su_size; - const char *notifysocket = getenv("NOTIFY_SOCKET"); - if (notifysocket == NULL) - return 0; - - if ((strlen(notifysocket) < 2) || - ((notifysocket[0] != '@') && (notifysocket[0] != '/'))) { - ERROR("invalid notification socket NOTIFY_SOCKET=\"%s\": path must be " - "absolute", - notifysocket); - return 0; - } - NOTICE("Systemd detected, trying to signal readiness."); - - unsetenv("NOTIFY_SOCKET"); - - int fd; -#if defined(SOCK_CLOEXEC) - fd = socket(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, /* protocol = */ 0); -#else - fd = socket(AF_UNIX, SOCK_DGRAM, /* protocol = */ 0); -#endif - if (fd < 0) { - ERROR("creating UNIX socket failed: %s", STRERRNO); - return 0; - } - - struct sockaddr_un su = {0}; - su.sun_family = AF_UNIX; - if (notifysocket[0] != '@') { - /* regular UNIX socket */ - sstrncpy(su.sun_path, notifysocket, sizeof(su.sun_path)); - su_size = sizeof(su); - } else { - /* Linux abstract namespace socket: specify address as "\0foo", i.e. - * start with a null byte. Since null bytes have no special meaning in - * that case, we have to set su_size correctly to cover only the bytes - * that are part of the address. */ - sstrncpy(su.sun_path, notifysocket, sizeof(su.sun_path)); - su.sun_path[0] = 0; - su_size = sizeof(sa_family_t) + strlen(notifysocket); - if (su_size > sizeof(su)) - su_size = sizeof(su); - } - - const char buffer[] = "READY=1\n"; - if (sendto(fd, buffer, strlen(buffer), MSG_NOSIGNAL, (void *)&su, - (socklen_t)su_size) < 0) { - ERROR("sendto(\"%s\") failed: %s", notifysocket, STRERRNO); - close(fd); - return 0; - } - - unsetenv("NOTIFY_SOCKET"); - close(fd); - return 1; -} -#endif /* KERNEL_LINUX */ - -struct cmdline_config { - bool test_config; - bool test_readall; - bool create_basedir; - const char *configfile; - bool daemonize; -}; - static void read_cmdline(int argc, char **argv, struct cmdline_config *config) { /* read options */ while (1) { @@ -516,9 +376,9 @@ static int configure_collectd(struct cmdline_config *config) { return 0; } -int main(int argc, char **argv) { - int exit_status = 0; +void stop_collectd(void) { loop++; } +struct cmdline_config init_config(int argc, char **argv) { struct cmdline_config config = { .daemonize = true, .create_basedir = true, .configfile = CONFIGFILE, }; @@ -526,7 +386,7 @@ int main(int argc, char **argv) { read_cmdline(argc, argv, &config); if (config.test_config) - return 0; + exit(EXIT_SUCCESS); if (optind < argc) exit_usage(1); @@ -536,109 +396,18 @@ int main(int argc, char **argv) { if (configure_collectd(&config) != 0) exit(EXIT_FAILURE); -#if COLLECT_DAEMON - /* - * fork off child - */ - struct sigaction sig_chld_action = {.sa_handler = SIG_IGN}; - - sigaction(SIGCHLD, &sig_chld_action, NULL); - - /* - * Only daemonize if we're not being supervised - * by upstart or systemd (when using Linux). - */ - if (config.daemonize -#ifdef KERNEL_LINUX - && notify_upstart() == 0 && notify_systemd() == 0 -#endif - ) { - pid_t pid; - if ((pid = fork()) == -1) { - /* error */ - fprintf(stderr, "fork: %s", STRERRNO); - return 1; - } else if (pid != 0) { - /* parent */ - /* printf ("Running (PID %i)\n", pid); */ - return 0; - } - - /* Detach from session */ - setsid(); - - /* Write pidfile */ - if (pidfile_create()) - exit(2); - - /* close standard descriptors */ - close(2); - close(1); - close(0); - - int status = open("/dev/null", O_RDWR); - if (status != 0) { - ERROR("Error: Could not connect `STDIN' to `/dev/null' (status %d)", - status); - return 1; - } - - status = dup(0); - if (status != 1) { - ERROR("Error: Could not connect `STDOUT' to `/dev/null' (status %d)", - status); - return 1; - } - - status = dup(0); - if (status != 2) { - ERROR("Error: Could not connect `STDERR' to `/dev/null', (status %d)", - status); - return 1; - } - } /* if (config.daemonize) */ -#endif /* COLLECT_DAEMON */ - - struct sigaction sig_pipe_action = {.sa_handler = SIG_IGN}; - - sigaction(SIGPIPE, &sig_pipe_action, NULL); - - /* - * install signal handlers - */ - struct sigaction sig_int_action = {.sa_handler = sig_int_handler}; - - if (sigaction(SIGINT, &sig_int_action, NULL) != 0) { - ERROR("Error: Failed to install a signal handler for signal INT: %s", - STRERRNO); - return 1; - } - - struct sigaction sig_term_action = {.sa_handler = sig_term_handler}; - - if (sigaction(SIGTERM, &sig_term_action, NULL) != 0) { - ERROR("Error: Failed to install a signal handler for signal TERM: %s", - STRERRNO); - return 1; - } - - struct sigaction sig_usr1_action = {.sa_handler = sig_usr1_handler}; + return config; +} - if (sigaction(SIGUSR1, &sig_usr1_action, NULL) != 0) { - ERROR("Error: Failed to install a signal handler for signal USR1: %s", - STRERRNO); - return 1; - } +int run_loop(bool test_readall) { + int exit_status = 0; - /* - * run the actual loops - */ if (do_init() != 0) { ERROR("Error: one or more plugin init callbacks failed."); exit_status = 1; } - if (config.test_readall) { + if (test_readall) { if (plugin_read_all_once() != 0) { ERROR("Error: one or more plugin read callbacks failed."); exit_status = 1; @@ -656,10 +425,5 @@ int main(int argc, char **argv) { exit_status = 1; } -#if COLLECT_DAEMON - if (config.daemonize) - pidfile_remove(); -#endif /* COLLECT_DAEMON */ - return exit_status; -} /* int main */ +} /* int run_loop */ -- 2.11.0