summaryrefslogtreecommitdiffstats
path: root/src/libnetdata/spawn_server
diff options
context:
space:
mode:
Diffstat (limited to 'src/libnetdata/spawn_server')
-rw-r--r--src/libnetdata/spawn_server/log-forwarder.c322
-rw-r--r--src/libnetdata/spawn_server/log-forwarder.h17
-rw-r--r--src/libnetdata/spawn_server/spawn-tester.c493
-rw-r--r--src/libnetdata/spawn_server/spawn_library.c51
-rw-r--r--src/libnetdata/spawn_server/spawn_library.h10
-rw-r--r--src/libnetdata/spawn_server/spawn_popen.c115
-rw-r--r--src/libnetdata/spawn_server/spawn_popen.h12
-rw-r--r--src/libnetdata/spawn_server/spawn_server.h15
-rw-r--r--src/libnetdata/spawn_server/spawn_server_internals.h90
-rw-r--r--src/libnetdata/spawn_server/spawn_server_libuv.c395
-rw-r--r--src/libnetdata/spawn_server/spawn_server_nofork.c (renamed from src/libnetdata/spawn_server/spawn_server.c)745
-rw-r--r--src/libnetdata/spawn_server/spawn_server_posix.c299
-rw-r--r--src/libnetdata/spawn_server/spawn_server_windows.c456
13 files changed, 2495 insertions, 525 deletions
diff --git a/src/libnetdata/spawn_server/log-forwarder.c b/src/libnetdata/spawn_server/log-forwarder.c
new file mode 100644
index 000000000..5c4db55ea
--- /dev/null
+++ b/src/libnetdata/spawn_server/log-forwarder.c
@@ -0,0 +1,322 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "../libnetdata.h"
+#include "log-forwarder.h"
+
+typedef struct LOG_FORWARDER_ENTRY {
+ int fd;
+ char *cmd;
+ pid_t pid;
+ BUFFER *wb;
+ size_t pfds_idx;
+ bool delete;
+
+ struct LOG_FORWARDER_ENTRY *prev;
+ struct LOG_FORWARDER_ENTRY *next;
+} LOG_FORWARDER_ENTRY;
+
+typedef struct LOG_FORWARDER {
+ LOG_FORWARDER_ENTRY *entries;
+ ND_THREAD *thread;
+ SPINLOCK spinlock;
+ int pipe_fds[2]; // Pipe for notifications
+ bool running;
+} LOG_FORWARDER;
+
+static void *log_forwarder_thread_func(void *arg);
+
+// --------------------------------------------------------------------------------------------------------------------
+// helper functions
+
+static inline LOG_FORWARDER_ENTRY *log_forwarder_find_entry_unsafe(LOG_FORWARDER *lf, int fd) {
+ for (LOG_FORWARDER_ENTRY *entry = lf->entries; entry; entry = entry->next) {
+ if (entry->fd == fd)
+ return entry;
+ }
+
+ return NULL;
+}
+
+static inline void log_forwarder_del_entry_unsafe(LOG_FORWARDER *lf, LOG_FORWARDER_ENTRY *entry) {
+ DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(lf->entries, entry, prev, next);
+ buffer_free(entry->wb);
+ freez(entry->cmd);
+ close(entry->fd);
+ freez(entry);
+}
+
+static inline void log_forwarder_wake_up_worker(LOG_FORWARDER *lf) {
+ char ch = 0;
+ ssize_t bytes_written = write(lf->pipe_fds[PIPE_WRITE], &ch, 1);
+ if (bytes_written != 1)
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "Failed to write to notification pipe");
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+// starting / stopping
+
+LOG_FORWARDER *log_forwarder_start(void) {
+ LOG_FORWARDER *lf = callocz(1, sizeof(LOG_FORWARDER));
+
+ spinlock_init(&lf->spinlock);
+ if (pipe(lf->pipe_fds) != 0) {
+ freez(lf);
+ return NULL;
+ }
+
+ // make sure read() will not block on this pipe
+ sock_setnonblock(lf->pipe_fds[PIPE_READ]);
+
+ lf->running = true;
+ lf->thread = nd_thread_create("log-fw", NETDATA_THREAD_OPTION_JOINABLE, log_forwarder_thread_func, lf);
+
+ return lf;
+}
+
+static inline void mark_all_entries_for_deletion_unsafe(LOG_FORWARDER *lf) {
+ for(LOG_FORWARDER_ENTRY *entry = lf->entries; entry ;entry = entry->next)
+ entry->delete = true;
+}
+
+void log_forwarder_stop(LOG_FORWARDER *lf) {
+ if(!lf || !lf->running) return;
+
+ // Signal the thread to stop
+ spinlock_lock(&lf->spinlock);
+ lf->running = false;
+
+ // mark them all for deletion
+ mark_all_entries_for_deletion_unsafe(lf);
+
+ // Send a byte to the pipe to wake up the thread
+ char ch = 0;
+ write(lf->pipe_fds[PIPE_WRITE], &ch, 1);
+ spinlock_unlock(&lf->spinlock);
+
+ // Wait for the thread to finish
+ close(lf->pipe_fds[PIPE_WRITE]); // force it to quit
+ nd_thread_join(lf->thread);
+ close(lf->pipe_fds[PIPE_READ]);
+
+ freez(lf);
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+// managing entries
+
+void log_forwarder_add_fd(LOG_FORWARDER *lf, int fd) {
+ if(!lf || !lf->running || fd < 0) return;
+
+ LOG_FORWARDER_ENTRY *entry = callocz(1, sizeof(LOG_FORWARDER_ENTRY));
+ entry->fd = fd;
+ entry->cmd = NULL;
+ entry->pid = 0;
+ entry->pfds_idx = 0;
+ entry->delete = false;
+ entry->wb = buffer_create(0, NULL);
+
+ spinlock_lock(&lf->spinlock);
+
+ // Append to the entries list
+ DOUBLE_LINKED_LIST_PREPEND_ITEM_UNSAFE(lf->entries, entry, prev, next);
+
+ // Send a byte to the pipe to wake up the thread
+ log_forwarder_wake_up_worker(lf);
+
+ spinlock_unlock(&lf->spinlock);
+}
+
+bool log_forwarder_del_and_close_fd(LOG_FORWARDER *lf, int fd) {
+ if(!lf || !lf->running || fd < 0) return false;
+
+ bool ret = false;
+
+ spinlock_lock(&lf->spinlock);
+
+ LOG_FORWARDER_ENTRY *entry = log_forwarder_find_entry_unsafe(lf, fd);
+ if(entry) {
+ entry->delete = true;
+
+ // Send a byte to the pipe to wake up the thread
+ log_forwarder_wake_up_worker(lf);
+
+ ret = true;
+ }
+
+ spinlock_unlock(&lf->spinlock);
+
+ return ret;
+}
+
+void log_forwarder_annotate_fd_name(LOG_FORWARDER *lf, int fd, const char *cmd) {
+ if(!lf || !lf->running || fd < 0 || !cmd || !*cmd) return;
+
+ spinlock_lock(&lf->spinlock);
+
+ LOG_FORWARDER_ENTRY *entry = log_forwarder_find_entry_unsafe(lf, fd);
+ if (entry) {
+ freez(entry->cmd);
+ entry->cmd = strdupz(cmd);
+ }
+
+ spinlock_unlock(&lf->spinlock);
+}
+
+void log_forwarder_annotate_fd_pid(LOG_FORWARDER *lf, int fd, pid_t pid) {
+ if(!lf || !lf->running || fd < 0) return;
+
+ spinlock_lock(&lf->spinlock);
+
+ LOG_FORWARDER_ENTRY *entry = log_forwarder_find_entry_unsafe(lf, fd);
+ if (entry)
+ entry->pid = pid;
+
+ spinlock_unlock(&lf->spinlock);
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+// log forwarder thread
+
+static inline void log_forwarder_log(LOG_FORWARDER *lf __maybe_unused, LOG_FORWARDER_ENTRY *entry, const char *msg) {
+ const char *s = msg;
+ while(*s && isspace((uint8_t)*s)) s++;
+ if(*s == '\0') return; // do not log empty lines
+
+ ND_LOG_STACK lgs[] = {
+ ND_LOG_FIELD_TXT(NDF_SYSLOG_IDENTIFIER, entry->cmd ? entry->cmd : "unknown"),
+ ND_LOG_FIELD_I64(NDF_TID, entry->pid),
+ ND_LOG_FIELD_END(),
+ };
+ ND_LOG_STACK_PUSH(lgs);
+
+ nd_log(NDLS_COLLECTORS, NDLP_WARNING, "STDERR: %s", msg);
+}
+
+// returns the number of entries active
+static inline size_t log_forwarder_remove_deleted_unsafe(LOG_FORWARDER *lf) {
+ size_t entries = 0;
+
+ LOG_FORWARDER_ENTRY *entry = lf->entries;
+ while(entry) {
+ LOG_FORWARDER_ENTRY *next = entry->next;
+
+ if(entry->delete) {
+ if (buffer_strlen(entry->wb))
+ // there is something not logged in it - log it
+ log_forwarder_log(lf, entry, buffer_tostring(entry->wb));
+
+ log_forwarder_del_entry_unsafe(lf, entry);
+ }
+ else
+ entries++;
+
+ entry = next;
+ }
+
+ return entries;
+}
+
+static void *log_forwarder_thread_func(void *arg) {
+ LOG_FORWARDER *lf = (LOG_FORWARDER *)arg;
+
+ while (1) {
+ spinlock_lock(&lf->spinlock);
+ if (!lf->running) {
+ mark_all_entries_for_deletion_unsafe(lf);
+ log_forwarder_remove_deleted_unsafe(lf);
+ spinlock_unlock(&lf->spinlock);
+ break;
+ }
+
+ // Count the number of fds
+ size_t nfds = 1 + log_forwarder_remove_deleted_unsafe(lf);
+
+ struct pollfd pfds[nfds];
+
+ // First, the notification pipe
+ pfds[0].fd = lf->pipe_fds[PIPE_READ];
+ pfds[0].events = POLLIN;
+
+ int idx = 1;
+ for(LOG_FORWARDER_ENTRY *entry = lf->entries; entry ; entry = entry->next, idx++) {
+ pfds[idx].fd = entry->fd;
+ pfds[idx].events = POLLIN;
+ entry->pfds_idx = idx;
+ }
+
+ spinlock_unlock(&lf->spinlock);
+
+ int timeout = 200; // 200ms
+ int ret = poll(pfds, nfds, timeout);
+
+ if (ret > 0) {
+ // Check the notification pipe
+ if (pfds[0].revents & POLLIN) {
+ // Read and discard the data
+ char buf[256];
+ ssize_t bytes_read = read(lf->pipe_fds[PIPE_READ], buf, sizeof(buf));
+ // Ignore the data; proceed regardless of the result
+ if (bytes_read == -1) {
+ if (errno != EAGAIN && errno != EWOULDBLOCK) {
+ // Handle read error if necessary
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "Failed to read from notification pipe");
+ return NULL;
+ }
+ }
+ }
+
+ // Now check the other fds
+ spinlock_lock(&lf->spinlock);
+
+ size_t to_remove = 0;
+
+ // read or mark them for deletion
+ for(LOG_FORWARDER_ENTRY *entry = lf->entries; entry ; entry = entry->next) {
+ if (entry->pfds_idx < 1 || entry->pfds_idx >= nfds || !(pfds[entry->pfds_idx].revents & POLLIN))
+ continue;
+
+ BUFFER *wb = entry->wb;
+ buffer_need_bytes(wb, 1024);
+
+ ssize_t bytes_read = read(entry->fd, &wb->buffer[wb->len], wb->size - wb->len - 1);
+ if(bytes_read > 0)
+ wb->len += bytes_read;
+ else if(bytes_read == 0 || (bytes_read == -1 && errno != EINTR && errno != EAGAIN)) {
+ // EOF or error
+ entry->delete = true;
+ to_remove++;
+ }
+
+ // log as many lines are they have been received
+ char *start = (char *)buffer_tostring(wb);
+ char *newline = strchr(start, '\n');
+ while(newline) {
+ *newline = '\0';
+ log_forwarder_log(lf, entry, start);
+
+ start = ++newline;
+ newline = strchr(newline, '\n');
+ }
+
+ if(start != wb->buffer) {
+ wb->len = strlen(start);
+ if (wb->len)
+ memmove(wb->buffer, start, wb->len);
+ }
+
+ entry->pfds_idx = 0;
+ }
+
+ spinlock_unlock(&lf->spinlock);
+ }
+ else if (ret == 0) {
+ // Timeout, nothing to do
+ continue;
+
+ }
+ else
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "Log forwarder: poll() error");
+ }
+
+ return NULL;
+}
diff --git a/src/libnetdata/spawn_server/log-forwarder.h b/src/libnetdata/spawn_server/log-forwarder.h
new file mode 100644
index 000000000..344601c1f
--- /dev/null
+++ b/src/libnetdata/spawn_server/log-forwarder.h
@@ -0,0 +1,17 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef NETDATA_LOG_FORWARDER_H
+#define NETDATA_LOG_FORWARDER_H
+
+#include "../libnetdata.h"
+
+typedef struct LOG_FORWARDER LOG_FORWARDER;
+
+LOG_FORWARDER *log_forwarder_start(void); // done once, at spawn_server_create()
+void log_forwarder_add_fd(LOG_FORWARDER *lf, int fd); // to add a new fd
+void log_forwarder_annotate_fd_name(LOG_FORWARDER *lf, int fd, const char *cmd); // set the syslog identifier
+void log_forwarder_annotate_fd_pid(LOG_FORWARDER *lf, int fd, pid_t pid); // set the pid of the child process
+bool log_forwarder_del_and_close_fd(LOG_FORWARDER *lf, int fd); // to remove an fd
+void log_forwarder_stop(LOG_FORWARDER *lf); // done once, at spawn_server_destroy()
+
+#endif //NETDATA_LOG_FORWARDER_H
diff --git a/src/libnetdata/spawn_server/spawn-tester.c b/src/libnetdata/spawn_server/spawn-tester.c
new file mode 100644
index 000000000..fbd9431ac
--- /dev/null
+++ b/src/libnetdata/spawn_server/spawn-tester.c
@@ -0,0 +1,493 @@
+#include "libnetdata/libnetdata.h"
+#include "libnetdata/required_dummies.h"
+
+#define ENV_VAR_KEY "SPAWN_TESTER"
+#define ENV_VAR_VALUE "1234567890"
+
+size_t warnings = 0;
+
+void child_check_environment(void) {
+ const char *s = getenv(ENV_VAR_KEY);
+ if(!s || !*s || strcmp(s, ENV_VAR_VALUE) != 0) {
+ nd_log(NDLS_COLLECTORS, NDLP_ERR,
+ "Wrong environment. Variable '%s' should have value '%s' but it has '%s'",
+ ENV_VAR_KEY, ENV_VAR_VALUE, s ? s : "(unset)");
+
+ exit(1);
+ }
+}
+
+static bool is_valid_fd(int fd) {
+ errno_clear();
+ return fcntl(fd, F_GETFD) != -1 || errno != EBADF;
+}
+
+void child_check_fds(void) {
+ for(int fd = 0; fd < 3; fd++) {
+ if(!is_valid_fd(fd)) {
+ nd_log(NDLS_COLLECTORS, NDLP_ERR,
+ "fd No %d should be a valid file descriptor - but it isn't.", fd);
+
+ exit(1);
+ }
+ }
+
+ for(int fd = 3; fd < /* os_get_fd_open_max() */ 1024; fd++) {
+ if(is_valid_fd(fd)) {
+ nd_log(NDLS_COLLECTORS, NDLP_ERR,
+ "fd No %d is a valid file descriptor - it shouldn't.", fd);
+
+ exit(1);
+ }
+ }
+
+ errno_clear();
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+// kill to stop
+
+int plugin_kill_to_stop() {
+ child_check_fds();
+ child_check_environment();
+
+ char buffer[1024];
+ while (fgets(buffer, sizeof(buffer), stdin) != NULL) {
+ fprintf(stderr, "+");
+ printf("%s", buffer);
+ fflush(stdout);
+ }
+
+ return 0;
+}
+
+void test_int_fds_plugin_kill_to_stop(SPAWN_SERVER *server, const char *argv0) {
+ const char *params[] = {
+ argv0,
+ "plugin-kill-to-stop",
+ NULL,
+ };
+
+ SPAWN_INSTANCE *si = spawn_server_exec(server, STDERR_FILENO, 0, params, NULL, 0, SPAWN_INSTANCE_TYPE_EXEC);
+ if(!si) {
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "Cannot run myself as plugin (spawn)");
+ exit(1);
+ }
+
+ const char *msg = "Hello World!\n";
+ ssize_t len = strlen(msg);
+ char buffer[len * 2];
+
+ for(size_t j = 0; j < 30 ;j++) {
+ fprintf(stderr, "-");
+ memset(buffer, 0, sizeof(buffer));
+
+ ssize_t rc = write(spawn_server_instance_write_fd(si), msg, len);
+
+ if (rc != len) {
+ nd_log(NDLS_COLLECTORS, NDLP_ERR,
+ "Cannot write to plugin. Expected to write %zd bytes, wrote %zd bytes",
+ len, rc);
+ exit(1);
+ }
+
+ rc = read(spawn_server_instance_read_fd(si), buffer, sizeof(buffer));
+ if (rc != len) {
+ nd_log(NDLS_COLLECTORS, NDLP_ERR,
+ "Cannot read from plugin. Expected to read %zd bytes, read %zd bytes",
+ len, rc);
+ exit(1);
+ }
+
+ if (memcmp(msg, buffer, len) != 0) {
+ nd_log(NDLS_COLLECTORS, NDLP_ERR,
+ "Read corrupted data. Expected '%s', Read '%s'",
+ msg, buffer);
+ exit(1);
+ }
+ }
+ fprintf(stderr, "\n");
+
+ int code = spawn_server_exec_kill(server, si);
+
+ nd_log(NDLS_COLLECTORS, NDLP_ERR,
+ "child exited with code %d",
+ code);
+
+ if(code != 15 && code != 0) {
+ nd_log(NDLS_COLLECTORS, NDLP_WARNING, "child should exit with code 0 or 15, but exited with code %d", code);
+ warnings++;
+ }
+}
+
+void test_popen_plugin_kill_to_stop(const char *argv0) {
+ char cmd[FILENAME_MAX + 100];
+ snprintfz(cmd, sizeof(cmd), "exec %s plugin-kill-to-stop", argv0);
+ POPEN_INSTANCE *pi = spawn_popen_run(cmd);
+ if(!pi) {
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "Cannot run myself as plugin (popen)");
+ exit(1);
+ }
+
+ const char *msg = "Hello World!\n";
+ size_t len = strlen(msg);
+ char buffer[len * 2];
+
+ for(size_t j = 0; j < 30 ;j++) {
+ fprintf(stderr, "-");
+ memset(buffer, 0, sizeof(buffer));
+
+ size_t rc = fwrite(msg, 1, len, spawn_popen_stdin(pi));
+ if (rc != len) {
+ nd_log(NDLS_COLLECTORS, NDLP_ERR,
+ "Cannot write to plugin. Expected to write %zu bytes, wrote %zu bytes",
+ len, rc);
+ exit(1);
+ }
+ fflush(spawn_popen_stdin(pi));
+
+ char *s = fgets(buffer, sizeof(buffer), spawn_popen_stdout(pi));
+ if (!s || strlen(s) != len) {
+ nd_log(NDLS_COLLECTORS, NDLP_ERR,
+ "Cannot read from plugin. Expected to read %zu bytes, read %zu bytes",
+ len, (size_t)(s ? strlen(s) : 0));
+ exit(1);
+ }
+ if (memcmp(msg, buffer, len) != 0) {
+ nd_log(NDLS_COLLECTORS, NDLP_ERR,
+ "Read corrupted data. Expected '%s', Read '%s'",
+ msg, buffer);
+ exit(1);
+ }
+ }
+ fprintf(stderr, "\n");
+
+ int code = spawn_popen_kill(pi);
+
+ nd_log(NDLS_COLLECTORS, NDLP_ERR,
+ "child exited with code %d",
+ code);
+
+ if(code != 0) {
+ nd_log(NDLS_COLLECTORS, NDLP_WARNING, "child should exit with code 0, but exited with code %d", code);
+ warnings++;
+ }
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+// close to stop
+
+int plugin_close_to_stop() {
+ child_check_fds();
+ child_check_environment();
+
+ char buffer[1024];
+ while (fgets(buffer, sizeof(buffer), stdin) != NULL) {
+ fprintf(stderr, "+");
+ printf("%s", buffer);
+ fflush(stdout);
+ }
+
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "child detected a closed pipe.");
+ exit(1);
+}
+
+void test_int_fds_plugin_close_to_stop(SPAWN_SERVER *server, const char *argv0) {
+ const char *params[] = {
+ argv0,
+ "plugin-close-to-stop",
+ NULL,
+ };
+
+ SPAWN_INSTANCE *si = spawn_server_exec(server, STDERR_FILENO, 0, params, NULL, 0, SPAWN_INSTANCE_TYPE_EXEC);
+ if(!si) {
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "Cannot run myself as plugin (spawn)");
+ exit(1);
+ }
+
+ const char *msg = "Hello World!\n";
+ ssize_t len = strlen(msg);
+ char buffer[len * 2];
+
+ for(size_t j = 0; j < 30 ;j++) {
+ fprintf(stderr, "-");
+ memset(buffer, 0, sizeof(buffer));
+
+ ssize_t rc = write(spawn_server_instance_write_fd(si), msg, len);
+ if (rc != len) {
+ nd_log(NDLS_COLLECTORS, NDLP_ERR,
+ "Cannot write to plugin. Expected to write %zd bytes, wrote %zd bytes",
+ len, rc);
+ exit(1);
+ }
+
+ rc = read(spawn_server_instance_read_fd(si), buffer, sizeof(buffer));
+ if (rc != len) {
+ nd_log(NDLS_COLLECTORS, NDLP_ERR,
+ "Cannot read from plugin. Expected to read %zd bytes, read %zd bytes",
+ len, rc);
+ exit(1);
+ }
+ if (memcmp(msg, buffer, len) != 0) {
+ nd_log(NDLS_COLLECTORS, NDLP_ERR,
+ "Read corrupted data. Expected '%s', Read '%s'",
+ msg, buffer);
+ exit(1);
+ }
+
+ break;
+ }
+ fprintf(stderr, "\n");
+
+ int code = spawn_server_exec_wait(server, si);
+
+ nd_log(NDLS_COLLECTORS, NDLP_ERR,
+ "child exited with code %d",
+ code);
+
+ if(!WIFEXITED(code) || WEXITSTATUS(code) != 1) {
+ nd_log(NDLS_COLLECTORS, NDLP_WARNING, "child should exit with code 1, but exited with code %d", code);
+ warnings++;
+ }
+}
+
+void test_popen_plugin_close_to_stop(const char *argv0) {
+ char cmd[FILENAME_MAX + 100];
+ snprintfz(cmd, sizeof(cmd), "exec %s plugin-close-to-stop", argv0);
+ POPEN_INSTANCE *pi = spawn_popen_run(cmd);
+ if(!pi) {
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "Cannot run myself as plugin (popen)");
+ exit(1);
+ }
+
+ const char *msg = "Hello World!\n";
+ size_t len = strlen(msg);
+ char buffer[len * 2];
+
+ for(size_t j = 0; j < 30 ;j++) {
+ fprintf(stderr, "-");
+ memset(buffer, 0, sizeof(buffer));
+
+ size_t rc = fwrite(msg, 1, len, spawn_popen_stdin(pi));
+ if (rc != len) {
+ nd_log(NDLS_COLLECTORS, NDLP_ERR,
+ "Cannot write to plugin. Expected to write %zu bytes, wrote %zu bytes",
+ len, rc);
+ exit(1);
+ }
+ fflush(spawn_popen_stdin(pi));
+
+ char *s = fgets(buffer, sizeof(buffer), spawn_popen_stdout(pi));
+ if (!s || strlen(s) != len) {
+ nd_log(NDLS_COLLECTORS, NDLP_ERR,
+ "Cannot read from plugin. Expected to read %zu bytes, read %zu bytes",
+ len, (size_t)(s ? strlen(s) : 0));
+ exit(1);
+ }
+ if (memcmp(msg, buffer, len) != 0) {
+ nd_log(NDLS_COLLECTORS, NDLP_ERR,
+ "Read corrupted data. Expected '%s', Read '%s'",
+ msg, buffer);
+ exit(1);
+ }
+
+ break;
+ }
+ fprintf(stderr, "\n");
+
+ int code = spawn_popen_wait(pi);
+
+ nd_log(NDLS_COLLECTORS, NDLP_ERR,
+ "child exited with code %d",
+ code);
+
+ if(code != 1) {
+ nd_log(NDLS_COLLECTORS, NDLP_WARNING, "child should exit with code 1, but exited with code %d", code);
+ warnings++;
+ }
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+// echo and exit
+
+#define ECHO_AND_EXIT_MSG "GOODBYE\n"
+
+int plugin_echo_and_exit() {
+ child_check_fds();
+ child_check_environment();
+
+ printf(ECHO_AND_EXIT_MSG);
+ exit(0);
+}
+
+void test_int_fds_plugin_echo_and_exit(SPAWN_SERVER *server, const char *argv0) {
+ const char *params[] = {
+ argv0,
+ "plugin-echo-and-exit",
+ NULL,
+ };
+
+ SPAWN_INSTANCE *si = spawn_server_exec(server, STDERR_FILENO, 0, params, NULL, 0, SPAWN_INSTANCE_TYPE_EXEC);
+ if(!si) {
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "Cannot run myself as plugin (spawn)");
+ exit(1);
+ }
+
+ char buffer[1024];
+ size_t reads = 0;
+
+ for(size_t j = 0; j < 30 ;j++) {
+ fprintf(stderr, "-");
+ memset(buffer, 0, sizeof(buffer));
+
+ ssize_t rc = read(spawn_server_instance_read_fd(si), buffer, sizeof(buffer));
+ if(rc <= 0)
+ break;
+
+ reads++;
+
+ if (rc != strlen(ECHO_AND_EXIT_MSG)) {
+ nd_log(NDLS_COLLECTORS, NDLP_ERR,
+ "Cannot read from plugin. Expected to read %zu bytes, read %zd bytes",
+ strlen(ECHO_AND_EXIT_MSG), rc);
+ exit(1);
+ }
+ if (memcmp(ECHO_AND_EXIT_MSG, buffer, strlen(ECHO_AND_EXIT_MSG)) != 0) {
+ nd_log(NDLS_COLLECTORS, NDLP_ERR,
+ "Read corrupted data. Expected '%s', Read '%s'",
+ ECHO_AND_EXIT_MSG, buffer);
+ exit(1);
+ }
+ }
+ fprintf(stderr, "\n");
+
+ if(reads != 1) {
+ nd_log(NDLS_COLLECTORS, NDLP_ERR,
+ "Cannot read from plugin. Expected to read %d times, but read %zu",
+ 1, reads);
+ exit(1);
+ }
+
+ int code = spawn_server_exec_wait(server, si);
+
+ nd_log(NDLS_COLLECTORS, NDLP_ERR,
+ "child exited with code %d",
+ code);
+
+ if(code != 0) {
+ nd_log(NDLS_COLLECTORS, NDLP_WARNING, "child should exit with code 0, but exited with code %d", code);
+ warnings++;
+ }
+}
+
+void test_popen_plugin_echo_and_exit(const char *argv0) {
+ char cmd[FILENAME_MAX + 100];
+ snprintfz(cmd, sizeof(cmd), "exec %s plugin-echo-and-exit", argv0);
+ POPEN_INSTANCE *pi = spawn_popen_run(cmd);
+ if(!pi) {
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "Cannot run myself as plugin (popen)");
+ exit(1);
+ }
+
+ char buffer[1024];
+ size_t reads = 0;
+ for(size_t j = 0; j < 30 ;j++) {
+ fprintf(stderr, "-");
+ memset(buffer, 0, sizeof(buffer));
+
+ char *s = fgets(buffer, sizeof(buffer), spawn_popen_stdout(pi));
+ if(!s) break;
+ reads++;
+ if (strlen(s) != strlen(ECHO_AND_EXIT_MSG)) {
+ nd_log(NDLS_COLLECTORS, NDLP_ERR,
+ "Cannot read from plugin. Expected to read %zu bytes, read %zu bytes",
+ strlen(ECHO_AND_EXIT_MSG), (size_t)(s ? strlen(s) : 0));
+ exit(1);
+ }
+ if (memcmp(ECHO_AND_EXIT_MSG, buffer, strlen(ECHO_AND_EXIT_MSG)) != 0) {
+ nd_log(NDLS_COLLECTORS, NDLP_ERR,
+ "Read corrupted data. Expected '%s', Read '%s'",
+ ECHO_AND_EXIT_MSG, buffer);
+ exit(1);
+ }
+ }
+ fprintf(stderr, "\n");
+
+ if(reads != 1) {
+ nd_log(NDLS_COLLECTORS, NDLP_ERR,
+ "Cannot read from plugin. Expected to read %d times, but read %zu",
+ 1, reads);
+ exit(1);
+ }
+
+ int code = spawn_popen_wait(pi);
+
+ nd_log(NDLS_COLLECTORS, NDLP_ERR,
+ "child exited with code %d",
+ code);
+
+ if(code != 0) {
+ nd_log(NDLS_COLLECTORS, NDLP_WARNING, "child should exit with code 0, but exited with code %d", code);
+ warnings++;
+ }
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+
+int main(int argc, const char **argv) {
+ if(argc > 1 && strcmp(argv[1], "plugin-kill-to-stop") == 0)
+ return plugin_kill_to_stop();
+
+ if(argc > 1 && strcmp(argv[1], "plugin-echo-and-exit") == 0)
+ return plugin_echo_and_exit();
+
+ if(argc > 1 && strcmp(argv[1], "plugin-close-to-stop") == 0)
+ return plugin_close_to_stop();
+
+ if(argc <= 1 || strcmp(argv[1], "test") != 0) {
+ fprintf(stderr, "Run me with 'test' parameter!\n");
+ exit(1);
+ }
+
+ nd_setenv(ENV_VAR_KEY, ENV_VAR_VALUE, 1);
+
+ fprintf(stderr, "\n\nTESTING fds\n\n");
+ SPAWN_SERVER *server = spawn_server_create(SPAWN_SERVER_OPTION_EXEC, "test", NULL, argc, argv);
+ if(!server) {
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "Cannot create spawn server");
+ exit(1);
+ }
+ for(size_t i = 0; i < 5; i++) {
+ fprintf(stderr, "\n\nTESTING fds No %zu (kill to stop)\n\n", i + 1);
+ test_int_fds_plugin_kill_to_stop(server, argv[0]);
+ }
+ for(size_t i = 0; i < 5; i++) {
+ fprintf(stderr, "\n\nTESTING fds No %zu (echo and exit)\n\n", i + 1);
+ test_int_fds_plugin_echo_and_exit(server, argv[0]);
+ }
+ for(size_t i = 0; i < 5; i++) {
+ fprintf(stderr, "\n\nTESTING fds No %zu (close to stop)\n\n", i + 1);
+ test_int_fds_plugin_close_to_stop(server, argv[0]);
+ }
+ spawn_server_destroy(server);
+
+ fprintf(stderr, "\n\nTESTING popen\n\n");
+ netdata_main_spawn_server_init("test", argc, argv);
+ for(size_t i = 0; i < 5; i++) {
+ fprintf(stderr, "\n\nTESTING popen No %zu (kill to stop)\n\n", i + 1);
+ test_popen_plugin_kill_to_stop(argv[0]);
+ }
+ for(size_t i = 0; i < 5; i++) {
+ fprintf(stderr, "\n\nTESTING popen No %zu (echo and exit)\n\n", i + 1);
+ test_popen_plugin_echo_and_exit(argv[0]);
+ }
+ for(size_t i = 0; i < 5; i++) {
+ fprintf(stderr, "\n\nTESTING popen No %zu (close to stop)\n\n", i + 1);
+ test_popen_plugin_close_to_stop(argv[0]);
+ }
+ netdata_main_spawn_server_cleanup();
+
+ fprintf(stderr, "\n\nTests passed! (%zu warnings)\n\n", warnings);
+
+ exit(0);
+}
diff --git a/src/libnetdata/spawn_server/spawn_library.c b/src/libnetdata/spawn_server/spawn_library.c
new file mode 100644
index 000000000..bdf64544c
--- /dev/null
+++ b/src/libnetdata/spawn_server/spawn_library.c
@@ -0,0 +1,51 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "spawn_library.h"
+
+BUFFER *argv_to_cmdline_buffer(const char **argv) {
+ BUFFER *wb = buffer_create(0, NULL);
+
+ for(size_t i = 0; argv[i] ;i++) {
+ const char *s = argv[i];
+ size_t len = strlen(s);
+ buffer_need_bytes(wb, len * 2 + 1);
+
+ bool needs_quotes = false;
+ for(const char *c = s; !needs_quotes && *c ; c++) {
+ switch(*c) {
+ case ' ':
+ case '\v':
+ case '\t':
+ case '\n':
+ case '"':
+ needs_quotes = true;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ if(needs_quotes && buffer_strlen(wb))
+ buffer_strcat(wb, " \"");
+ else if(buffer_strlen(wb))
+ buffer_putc(wb, ' ');
+
+ for(const char *c = s; *c ; c++) {
+ switch(*c) {
+ case '"':
+ buffer_putc(wb, '\\');
+ // fall through
+
+ default:
+ buffer_putc(wb, *c);
+ break;
+ }
+ }
+
+ if(needs_quotes)
+ buffer_strcat(wb, "\"");
+ }
+
+ return wb;
+}
diff --git a/src/libnetdata/spawn_server/spawn_library.h b/src/libnetdata/spawn_server/spawn_library.h
new file mode 100644
index 000000000..a9b9dc14d
--- /dev/null
+++ b/src/libnetdata/spawn_server/spawn_library.h
@@ -0,0 +1,10 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef NETDATA_SPAWN_LIBRARY_H
+#define NETDATA_SPAWN_LIBRARY_H
+
+#include "../libnetdata.h"
+
+BUFFER *argv_to_cmdline_buffer(const char **argv);
+
+#endif //NETDATA_SPAWN_LIBRARY_H
diff --git a/src/libnetdata/spawn_server/spawn_popen.c b/src/libnetdata/spawn_server/spawn_popen.c
index f354b1f2a..b8ea0afe6 100644
--- a/src/libnetdata/spawn_server/spawn_popen.c
+++ b/src/libnetdata/spawn_server/spawn_popen.c
@@ -2,6 +2,12 @@
#include "spawn_popen.h"
+struct popen_instance {
+ SPAWN_INSTANCE *si;
+ FILE *child_stdin_fp;
+ FILE *child_stdout_fp;
+};
+
SPAWN_SERVER *netdata_main_spawn_server = NULL;
static SPINLOCK netdata_main_spawn_server_spinlock = NETDATA_SPINLOCK_INITIALIZER;
@@ -27,6 +33,30 @@ void netdata_main_spawn_server_cleanup(void) {
}
}
+FILE *spawn_popen_stdin(POPEN_INSTANCE *pi) {
+ if(!pi->child_stdin_fp)
+ pi->child_stdin_fp = fdopen(spawn_server_instance_write_fd(pi->si), "w");
+
+ if(!pi->child_stdin_fp)
+ nd_log(NDLS_COLLECTORS, NDLP_ERR,
+ "Cannot open FILE on child's stdin on fd %d.",
+ spawn_server_instance_write_fd(pi->si));
+
+ return pi->child_stdin_fp;
+}
+
+FILE *spawn_popen_stdout(POPEN_INSTANCE *pi) {
+ if(!pi->child_stdout_fp)
+ pi->child_stdout_fp = fdopen(spawn_server_instance_read_fd(pi->si), "r");
+
+ if(!pi->child_stdout_fp)
+ nd_log(NDLS_COLLECTORS, NDLP_ERR,
+ "Cannot open FILE on child's stdout on fd %d.",
+ spawn_server_instance_read_fd(pi->si));
+
+ return pi->child_stdout_fp;
+}
+
POPEN_INSTANCE *spawn_popen_run_argv(const char **argv) {
netdata_main_spawn_server_init(NULL, 0, NULL);
@@ -35,29 +65,9 @@ POPEN_INSTANCE *spawn_popen_run_argv(const char **argv) {
if(si == NULL) return NULL;
- POPEN_INSTANCE *pi = mallocz(sizeof(*pi));
+ POPEN_INSTANCE *pi = callocz(1, sizeof(*pi));
pi->si = si;
- pi->child_stdin_fp = fdopen(spawn_server_instance_write_fd(si), "w");
- pi->child_stdout_fp = fdopen(spawn_server_instance_read_fd(si), "r");
-
- if(!pi->child_stdin_fp) {
- nd_log(NDLS_COLLECTORS, NDLP_ERR, "Cannot open FILE on child's stdin on fd %d.", spawn_server_instance_write_fd(si));
- goto cleanup;
- }
-
- if(!pi->child_stdout_fp) {
- nd_log(NDLS_COLLECTORS, NDLP_ERR, "Cannot open FILE on child's stdout on fd %d.", spawn_server_instance_read_fd(si));
- goto cleanup;
- }
-
return pi;
-
-cleanup:
- if(pi->child_stdin_fp) { fclose(pi->child_stdin_fp); spawn_server_instance_write_fd(si); }
- if(pi->child_stdout_fp) { fclose(pi->child_stdout_fp); spawn_server_instance_read_fd_unset(si); }
- spawn_server_exec_kill(netdata_main_spawn_server, si);
- freez(pi);
- return NULL;
}
POPEN_INSTANCE *spawn_popen_run_variadic(const char *cmd, ...) {
@@ -92,7 +102,33 @@ POPEN_INSTANCE *spawn_popen_run_variadic(const char *cmd, ...) {
POPEN_INSTANCE *spawn_popen_run(const char *cmd) {
if(!cmd || !*cmd) return NULL;
-
+
+//#if defined(OS_WINDOWS)
+// if(strncmp(cmd, "exec ", 5) == 0) {
+// size_t len = strlen(cmd);
+// char cmd_copy[strlen(cmd) + 1];
+// memcpy(cmd_copy, cmd, len + 1);
+// char *words[100];
+// size_t num_words = quoted_strings_splitter(cmd_copy, words, 100, isspace_map_pluginsd);
+// char *exec = get_word(words, num_words, 0);
+// char *prog = get_word(words, num_words, 1);
+// if (strcmp(exec, "exec") == 0 &&
+// prog &&
+// strendswith(prog, ".plugin") &&
+// !strendswith(prog, "charts.d.plugin") &&
+// !strendswith(prog, "ioping.plugin")) {
+// const char *argv[num_words - 1 + 1]; // remove exec, add terminator
+//
+// size_t dst = 0;
+// for (size_t i = 1; i < num_words; i++)
+// argv[dst++] = get_word(words, num_words, i);
+//
+// argv[dst] = NULL;
+// return spawn_popen_run_argv(argv);
+// }
+// }
+//#endif
+
const char *argv[] = {
"/bin/sh",
"-c",
@@ -121,11 +157,24 @@ static int spawn_popen_status_rc(int status) {
return -1;
}
+static void spawn_popen_close_files(POPEN_INSTANCE *pi) {
+ if(pi->child_stdin_fp) {
+ fclose(pi->child_stdin_fp);
+ pi->child_stdin_fp = NULL;
+ spawn_server_instance_write_fd_unset(pi->si);
+ }
+
+ if(pi->child_stdout_fp) {
+ fclose(pi->child_stdout_fp);
+ pi->child_stdout_fp = NULL;
+ spawn_server_instance_read_fd_unset(pi->si);
+ }
+}
+
int spawn_popen_wait(POPEN_INSTANCE *pi) {
if(!pi) return -1;
- fclose(pi->child_stdin_fp); pi->child_stdin_fp = NULL; spawn_server_instance_write_fd_unset(pi->si);
- fclose(pi->child_stdout_fp); pi->child_stdout_fp = NULL; spawn_server_instance_read_fd_unset(pi->si);
+ spawn_popen_close_files(pi);
int status = spawn_server_exec_wait(netdata_main_spawn_server, pi->si);
freez(pi);
return spawn_popen_status_rc(status);
@@ -134,9 +183,23 @@ int spawn_popen_wait(POPEN_INSTANCE *pi) {
int spawn_popen_kill(POPEN_INSTANCE *pi) {
if(!pi) return -1;
- fclose(pi->child_stdin_fp); pi->child_stdin_fp = NULL; spawn_server_instance_write_fd_unset(pi->si);
- fclose(pi->child_stdout_fp); pi->child_stdout_fp = NULL; spawn_server_instance_read_fd_unset(pi->si);
+ spawn_popen_close_files(pi);
int status = spawn_server_exec_kill(netdata_main_spawn_server, pi->si);
freez(pi);
return spawn_popen_status_rc(status);
}
+
+pid_t spawn_popen_pid(POPEN_INSTANCE *pi) {
+ if(!pi) return -1;
+ return spawn_server_instance_pid(pi->si);
+}
+
+int spawn_popen_read_fd(POPEN_INSTANCE *pi) {
+ if(!pi) return -1;
+ return spawn_server_instance_read_fd(pi->si);
+}
+
+int spawn_popen_write_fd(POPEN_INSTANCE *pi) {
+ if(!pi) return -1;
+ return spawn_server_instance_write_fd(pi->si);
+}
diff --git a/src/libnetdata/spawn_server/spawn_popen.h b/src/libnetdata/spawn_server/spawn_popen.h
index 253d1f34b..5c00f32ff 100644
--- a/src/libnetdata/spawn_server/spawn_popen.h
+++ b/src/libnetdata/spawn_server/spawn_popen.h
@@ -9,11 +9,7 @@ extern SPAWN_SERVER *netdata_main_spawn_server;
bool netdata_main_spawn_server_init(const char *name, int argc, const char **argv);
void netdata_main_spawn_server_cleanup(void);
-typedef struct {
- SPAWN_INSTANCE *si;
- FILE *child_stdin_fp;
- FILE *child_stdout_fp;
-} POPEN_INSTANCE;
+typedef struct popen_instance POPEN_INSTANCE;
POPEN_INSTANCE *spawn_popen_run(const char *cmd);
POPEN_INSTANCE *spawn_popen_run_argv(const char **argv);
@@ -21,4 +17,10 @@ POPEN_INSTANCE *spawn_popen_run_variadic(const char *cmd, ...);
int spawn_popen_wait(POPEN_INSTANCE *pi);
int spawn_popen_kill(POPEN_INSTANCE *pi);
+pid_t spawn_popen_pid(POPEN_INSTANCE *pi);
+int spawn_popen_read_fd(POPEN_INSTANCE *pi);
+int spawn_popen_write_fd(POPEN_INSTANCE *pi);
+FILE *spawn_popen_stdin(POPEN_INSTANCE *pi);
+FILE *spawn_popen_stdout(POPEN_INSTANCE *pi);
+
#endif //SPAWN_POPEN_H
diff --git a/src/libnetdata/spawn_server/spawn_server.h b/src/libnetdata/spawn_server/spawn_server.h
index 5ba66ae38..e68a53ab4 100644
--- a/src/libnetdata/spawn_server/spawn_server.h
+++ b/src/libnetdata/spawn_server/spawn_server.h
@@ -7,16 +7,12 @@
typedef enum __attribute__((packed)) {
SPAWN_INSTANCE_TYPE_EXEC = 0,
-#if !defined(OS_WINDOWS)
SPAWN_INSTANCE_TYPE_CALLBACK = 1
-#endif
} SPAWN_INSTANCE_TYPE;
typedef enum __attribute__((packed)) {
SPAWN_SERVER_OPTION_EXEC = (1 << 0),
-#if !defined(OS_WINDOWS)
SPAWN_SERVER_OPTION_CALLBACK = (1 << 1),
-#endif
} SPAWN_SERVER_OPTIONS;
// this is only used publicly for SPAWN_INSTANCE_TYPE_CALLBACK
@@ -27,7 +23,7 @@ typedef struct spawn_request {
pid_t pid; // the pid of the child
int sock; // the socket for this request
int fds[SPAWN_SERVER_TRANSFER_FDS]; // 0 = stdin, 1 = stdout, 2 = stderr, 3 = custom
- const char **environment; // the environment of the parent process
+ const char **envp; // the environment of the parent process
const char **argv; // the command line and its parameters
const void *data; // the data structure for the callback
size_t data_size; // the data structure size
@@ -36,17 +32,18 @@ typedef struct spawn_request {
struct spawn_request *prev, *next; // linking of active requests at the spawn server
} SPAWN_REQUEST;
-typedef void (*spawn_request_callback_t)(SPAWN_REQUEST *request);
+typedef int (*spawn_request_callback_t)(SPAWN_REQUEST *request);
-typedef struct spawm_instance SPAWN_INSTANCE;
+typedef struct spawn_instance SPAWN_INSTANCE;
typedef struct spawn_server SPAWN_SERVER;
SPAWN_SERVER* spawn_server_create(SPAWN_SERVER_OPTIONS options, const char *name, spawn_request_callback_t child_callback, int argc, const char **argv);
void spawn_server_destroy(SPAWN_SERVER *server);
+pid_t spawn_server_pid(SPAWN_SERVER *server);
SPAWN_INSTANCE* spawn_server_exec(SPAWN_SERVER *server, int stderr_fd, int custom_fd, const char **argv, const void *data, size_t data_size, SPAWN_INSTANCE_TYPE type);
-int spawn_server_exec_kill(SPAWN_SERVER *server, SPAWN_INSTANCE *instance);
-int spawn_server_exec_wait(SPAWN_SERVER *server, SPAWN_INSTANCE *instance);
+int spawn_server_exec_kill(SPAWN_SERVER *server, SPAWN_INSTANCE *si);
+int spawn_server_exec_wait(SPAWN_SERVER *server, SPAWN_INSTANCE *si);
int spawn_server_instance_read_fd(SPAWN_INSTANCE *si);
int spawn_server_instance_write_fd(SPAWN_INSTANCE *si);
diff --git a/src/libnetdata/spawn_server/spawn_server_internals.h b/src/libnetdata/spawn_server/spawn_server_internals.h
new file mode 100644
index 000000000..1031e3b1a
--- /dev/null
+++ b/src/libnetdata/spawn_server/spawn_server_internals.h
@@ -0,0 +1,90 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef NETDATA_SPAWN_SERVER_INTERNALS_H
+#define NETDATA_SPAWN_SERVER_INTERNALS_H
+
+#include "../libnetdata.h"
+#include "spawn_server.h"
+#include "spawn_library.h"
+#include "log-forwarder.h"
+
+#if defined(OS_WINDOWS)
+#define SPAWN_SERVER_VERSION_WINDOWS 1
+// #define SPAWN_SERVER_VERSION_UV 1
+// #define SPAWN_SERVER_VERSION_POSIX_SPAWN 1
+#else
+#define SPAWN_SERVER_VERSION_NOFORK 1
+// #define SPAWN_SERVER_VERSION_UV 1
+// #define SPAWN_SERVER_VERSION_POSIX_SPAWN 1
+#endif
+
+struct spawn_server {
+ size_t id;
+ size_t request_id;
+ const char *name;
+
+#if defined(SPAWN_SERVER_VERSION_UV)
+ uv_loop_t *loop;
+ uv_thread_t thread;
+ uv_async_t async;
+ bool stopping;
+
+ SPINLOCK spinlock;
+ struct work_item *work_queue;
+#endif
+
+#if defined(SPAWN_SERVER_VERSION_NOFORK)
+ SPAWN_SERVER_OPTIONS options;
+
+ ND_UUID magic; // for authorizing requests, the client needs to know our random UUID
+ // it is ignored for PING requests
+
+ int pipe[2];
+ int sock; // the listening socket of the server
+ pid_t server_pid;
+ char *path;
+ spawn_request_callback_t cb;
+
+ int argc;
+ const char **argv;
+#endif
+
+#if defined(SPAWN_SERVER_VERSION_POSIX_SPAWN)
+#endif
+
+#if defined(SPAWN_SERVER_VERSION_WINDOWS)
+ LOG_FORWARDER *log_forwarder;
+#endif
+};
+
+struct spawn_instance {
+ size_t request_id;
+ int sock;
+ int write_fd; // the child's input pipe, writing side
+ int read_fd; // the child's output pipe, reading side
+ int stderr_fd;
+ pid_t child_pid;
+
+#if defined(SPAWN_SERVER_VERSION_UV)
+ uv_process_t process;
+ int exit_code;
+ uv_sem_t sem;
+#endif
+
+#if defined(SPAWN_SERVER_VERSION_NOFORK)
+#endif
+
+#if defined(SPAWN_SERVER_VERSION_POSIX_SPAWN)
+ const char *cmdline;
+ bool exited;
+ int waitpid_status;
+ struct spawn_instance *prev, *next;
+#endif
+
+#if defined(SPAWN_SERVER_VERSION_WINDOWS)
+ HANDLE process_handle;
+ DWORD dwProcessId;
+#endif
+};
+
+#endif //NETDATA_SPAWN_SERVER_INTERNALS_H
diff --git a/src/libnetdata/spawn_server/spawn_server_libuv.c b/src/libnetdata/spawn_server/spawn_server_libuv.c
new file mode 100644
index 000000000..e01c5407e
--- /dev/null
+++ b/src/libnetdata/spawn_server/spawn_server_libuv.c
@@ -0,0 +1,395 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "spawn_server_internals.h"
+
+#if defined(SPAWN_SERVER_VERSION_UV)
+
+int spawn_server_instance_read_fd(SPAWN_INSTANCE *si) { return si->read_fd; }
+int spawn_server_instance_write_fd(SPAWN_INSTANCE *si) { return si->write_fd; }
+void spawn_server_instance_read_fd_unset(SPAWN_INSTANCE *si) { si->read_fd = -1; }
+void spawn_server_instance_write_fd_unset(SPAWN_INSTANCE *si) { si->write_fd = -1; }
+pid_t spawn_server_instance_pid(SPAWN_INSTANCE *si) { return uv_process_get_pid(&si->process); }
+
+typedef struct work_item {
+ int stderr_fd;
+ const char **argv;
+ uv_sem_t sem;
+ SPAWN_INSTANCE *instance;
+ struct work_item *prev;
+ struct work_item *next;
+} work_item;
+
+int uv_errno_to_errno(int uv_err) {
+ switch (uv_err) {
+ case 0: return 0;
+ case UV_E2BIG: return E2BIG;
+ case UV_EACCES: return EACCES;
+ case UV_EADDRINUSE: return EADDRINUSE;
+ case UV_EADDRNOTAVAIL: return EADDRNOTAVAIL;
+ case UV_EAFNOSUPPORT: return EAFNOSUPPORT;
+ case UV_EAGAIN: return EAGAIN;
+ case UV_EAI_ADDRFAMILY: return EAI_ADDRFAMILY;
+ case UV_EAI_AGAIN: return EAI_AGAIN;
+ case UV_EAI_BADFLAGS: return EAI_BADFLAGS;
+#if defined(EAI_CANCELED)
+ case UV_EAI_CANCELED: return EAI_CANCELED;
+#endif
+ case UV_EAI_FAIL: return EAI_FAIL;
+ case UV_EAI_FAMILY: return EAI_FAMILY;
+ case UV_EAI_MEMORY: return EAI_MEMORY;
+ case UV_EAI_NODATA: return EAI_NODATA;
+ case UV_EAI_NONAME: return EAI_NONAME;
+ case UV_EAI_OVERFLOW: return EAI_OVERFLOW;
+ case UV_EAI_SERVICE: return EAI_SERVICE;
+ case UV_EAI_SOCKTYPE: return EAI_SOCKTYPE;
+ case UV_EALREADY: return EALREADY;
+ case UV_EBADF: return EBADF;
+ case UV_EBUSY: return EBUSY;
+ case UV_ECANCELED: return ECANCELED;
+ case UV_ECHARSET: return EILSEQ; // No direct mapping, using EILSEQ
+ case UV_ECONNABORTED: return ECONNABORTED;
+ case UV_ECONNREFUSED: return ECONNREFUSED;
+ case UV_ECONNRESET: return ECONNRESET;
+ case UV_EDESTADDRREQ: return EDESTADDRREQ;
+ case UV_EEXIST: return EEXIST;
+ case UV_EFAULT: return EFAULT;
+ case UV_EFBIG: return EFBIG;
+ case UV_EHOSTUNREACH: return EHOSTUNREACH;
+ case UV_EINTR: return EINTR;
+ case UV_EINVAL: return EINVAL;
+ case UV_EIO: return EIO;
+ case UV_EISCONN: return EISCONN;
+ case UV_EISDIR: return EISDIR;
+ case UV_ELOOP: return ELOOP;
+ case UV_EMFILE: return EMFILE;
+ case UV_EMSGSIZE: return EMSGSIZE;
+ case UV_ENAMETOOLONG: return ENAMETOOLONG;
+ case UV_ENETDOWN: return ENETDOWN;
+ case UV_ENETUNREACH: return ENETUNREACH;
+ case UV_ENFILE: return ENFILE;
+ case UV_ENOBUFS: return ENOBUFS;
+ case UV_ENODEV: return ENODEV;
+ case UV_ENOENT: return ENOENT;
+ case UV_ENOMEM: return ENOMEM;
+ case UV_ENONET: return ENONET;
+ case UV_ENOSPC: return ENOSPC;
+ case UV_ENOSYS: return ENOSYS;
+ case UV_ENOTCONN: return ENOTCONN;
+ case UV_ENOTDIR: return ENOTDIR;
+ case UV_ENOTEMPTY: return ENOTEMPTY;
+ case UV_ENOTSOCK: return ENOTSOCK;
+ case UV_ENOTSUP: return ENOTSUP;
+ case UV_ENOTTY: return ENOTTY;
+ case UV_ENXIO: return ENXIO;
+ case UV_EPERM: return EPERM;
+ case UV_EPIPE: return EPIPE;
+ case UV_EPROTO: return EPROTO;
+ case UV_EPROTONOSUPPORT: return EPROTONOSUPPORT;
+ case UV_EPROTOTYPE: return EPROTOTYPE;
+ case UV_ERANGE: return ERANGE;
+ case UV_EROFS: return EROFS;
+ case UV_ESHUTDOWN: return ESHUTDOWN;
+ case UV_ESPIPE: return ESPIPE;
+ case UV_ESRCH: return ESRCH;
+ case UV_ETIMEDOUT: return ETIMEDOUT;
+ case UV_ETXTBSY: return ETXTBSY;
+ case UV_EXDEV: return EXDEV;
+ default: return EINVAL; // Use EINVAL for unknown libuv errors
+ }
+}
+
+static void posix_unmask_sigchld_on_thread(void) {
+ sigset_t sigset;
+ sigemptyset(&sigset); // Initialize the signal set to empty
+ sigaddset(&sigset, SIGCHLD); // Add SIGCHLD to the set
+
+ if(pthread_sigmask(SIG_UNBLOCK, &sigset, NULL) != 0)
+ netdata_log_error("SPAWN SERVER: cannot unmask SIGCHLD");
+}
+
+static void server_thread(void *arg) {
+ SPAWN_SERVER *server = (SPAWN_SERVER *)arg;
+ nd_log(NDLS_COLLECTORS, NDLP_ERR,
+ "SPAWN SERVER: started");
+
+ // this thread needs to process SIGCHLD (by libuv)
+ // otherwise the on_exit() callback is never run
+ posix_unmask_sigchld_on_thread();
+
+ // run the event loop
+ uv_run(server->loop, UV_RUN_DEFAULT);
+
+ nd_log(NDLS_COLLECTORS, NDLP_ERR,
+ "SPAWN SERVER: ended");
+}
+
+static void on_process_exit(uv_process_t *req, int64_t exit_status, int term_signal) {
+ SPAWN_INSTANCE *si = (SPAWN_INSTANCE *)req->data;
+ si->exit_code = (int)(term_signal ? term_signal : exit_status << 8);
+ uv_close((uv_handle_t *)req, NULL); // Properly close the process handle
+
+ nd_log(NDLS_COLLECTORS, NDLP_ERR,
+ "SPAWN SERVER: process with pid %d exited with code %d and term_signal %d",
+ si->child_pid, (int)exit_status, term_signal);
+
+ uv_sem_post(&si->sem); // Signal that the process has exited
+}
+
+static SPAWN_INSTANCE *spawn_process_with_libuv(uv_loop_t *loop, int stderr_fd, const char **argv) {
+ SPAWN_INSTANCE *si = NULL;
+ bool si_sem_init = false;
+
+ int stdin_pipe[2] = { -1, -1 };
+ int stdout_pipe[2] = { -1, -1 };
+
+ if (pipe(stdin_pipe) == -1) {
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN SERVER: stdin pipe() failed");
+ goto cleanup;
+ }
+
+ if (pipe(stdout_pipe) == -1) {
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN SERVER: stdout pipe() failed");
+ goto cleanup;
+ }
+
+ si = callocz(1, sizeof(SPAWN_INSTANCE));
+ si->exit_code = -1;
+
+ if (uv_sem_init(&si->sem, 0)) {
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN SERVER: uv_sem_init() failed");
+ goto cleanup;
+ }
+ si_sem_init = true;
+
+ uv_stdio_container_t stdio[3] = { 0 };
+ stdio[0].flags = UV_INHERIT_FD;
+ stdio[0].data.fd = stdin_pipe[PIPE_READ];
+ stdio[1].flags = UV_INHERIT_FD;
+ stdio[1].data.fd = stdout_pipe[PIPE_WRITE];
+ stdio[2].flags = UV_INHERIT_FD;
+ stdio[2].data.fd = stderr_fd;
+
+ uv_process_options_t options = { 0 };
+ options.stdio_count = 3;
+ options.stdio = stdio;
+ options.exit_cb = on_process_exit;
+ options.file = argv[0];
+ options.args = (char **)argv;
+ options.env = (char **)environ;
+
+ // uv_spawn() does not close all other open file descriptors
+ // we have to close them manually
+ int fds[3] = { stdio[0].data.fd, stdio[1].data.fd, stdio[2].data.fd };
+ os_close_all_non_std_open_fds_except(fds, 3, CLOSE_RANGE_CLOEXEC);
+
+ int rc = uv_spawn(loop, &si->process, &options);
+ if (rc) {
+ errno = uv_errno_to_errno(rc);
+ nd_log(NDLS_COLLECTORS, NDLP_ERR,
+ "SPAWN SERVER: uv_spawn() failed with error %s, %s",
+ uv_err_name(rc), uv_strerror(rc));
+ goto cleanup;
+ }
+
+ // Successfully spawned
+
+ // get the pid of the process spawned
+ si->child_pid = uv_process_get_pid(&si->process);
+
+ // on_process_exit() needs this to find the si
+ si->process.data = si;
+
+ nd_log(NDLS_COLLECTORS, NDLP_INFO,
+ "SPAWN SERVER: process created with pid %d", si->child_pid);
+
+ // close the child sides of the pipes
+ close(stdin_pipe[PIPE_READ]);
+ si->write_fd = stdin_pipe[PIPE_WRITE];
+ si->read_fd = stdout_pipe[PIPE_READ];
+ close(stdout_pipe[PIPE_WRITE]);
+
+ return si;
+
+cleanup:
+ if(stdin_pipe[PIPE_READ] != -1) close(stdin_pipe[PIPE_READ]);
+ if(stdin_pipe[PIPE_WRITE] != -1) close(stdin_pipe[PIPE_WRITE]);
+ if(stdout_pipe[PIPE_READ] != -1) close(stdout_pipe[PIPE_READ]);
+ if(stdout_pipe[PIPE_WRITE] != -1) close(stdout_pipe[PIPE_WRITE]);
+ if(si) {
+ if(si_sem_init)
+ uv_sem_destroy(&si->sem);
+
+ freez(si);
+ }
+ return NULL;
+}
+
+static void async_callback(uv_async_t *handle) {
+ nd_log(NDLS_COLLECTORS, NDLP_INFO, "SPAWN SERVER: dequeue commands started");
+ SPAWN_SERVER *server = (SPAWN_SERVER *)handle->data;
+
+ // Check if the server is stopping
+ if (__atomic_load_n(&server->stopping, __ATOMIC_RELAXED)) {
+ nd_log(NDLS_COLLECTORS, NDLP_INFO, "SPAWN SERVER: stopping...");
+ uv_stop(server->loop);
+ return;
+ }
+
+ work_item *item;
+ spinlock_lock(&server->spinlock);
+ while (server->work_queue) {
+ item = server->work_queue;
+ DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(server->work_queue, item, prev, next);
+ spinlock_unlock(&server->spinlock);
+
+ item->instance = spawn_process_with_libuv(server->loop, item->stderr_fd, item->argv);
+ uv_sem_post(&item->sem);
+
+ spinlock_lock(&server->spinlock);
+ }
+ spinlock_unlock(&server->spinlock);
+
+ nd_log(NDLS_COLLECTORS, NDLP_INFO, "SPAWN SERVER: dequeue commands done");
+}
+
+
+SPAWN_SERVER* spawn_server_create(SPAWN_SERVER_OPTIONS options __maybe_unused, const char *name, spawn_request_callback_t cb __maybe_unused, int argc __maybe_unused, const char **argv __maybe_unused) {
+ SPAWN_SERVER* server = callocz(1, sizeof(SPAWN_SERVER));
+ spinlock_init(&server->spinlock);
+
+ if (name)
+ server->name = strdupz(name);
+ else
+ server->name = strdupz("unnamed");
+
+ server->loop = callocz(1, sizeof(uv_loop_t));
+ if (uv_loop_init(server->loop)) {
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN PARENT: uv_loop_init() failed");
+ freez(server->loop);
+ freez((void *)server->name);
+ freez(server);
+ return NULL;
+ }
+
+ if (uv_async_init(server->loop, &server->async, async_callback)) {
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN PARENT: uv_async_init() failed");
+ uv_loop_close(server->loop);
+ freez(server->loop);
+ freez((void *)server->name);
+ freez(server);
+ return NULL;
+ }
+ server->async.data = server;
+
+ if (uv_thread_create(&server->thread, server_thread, server)) {
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN PARENT: uv_thread_create() failed");
+ uv_close((uv_handle_t*)&server->async, NULL);
+ uv_loop_close(server->loop);
+ freez(server->loop);
+ freez((void *)server->name);
+ freez(server);
+ return NULL;
+ }
+
+ return server;
+}
+
+static void close_handle(uv_handle_t* handle, void* arg __maybe_unused) {
+ if (!uv_is_closing(handle)) {
+ uv_close(handle, NULL);
+ }
+}
+
+void spawn_server_destroy(SPAWN_SERVER *server) {
+ if (!server) return;
+
+ __atomic_store_n(&server->stopping, true, __ATOMIC_RELAXED);
+
+ // Trigger the async callback to stop the event loop
+ uv_async_send(&server->async);
+
+ // Wait for the server thread to finish
+ uv_thread_join(&server->thread);
+
+ uv_stop(server->loop);
+ uv_close((uv_handle_t*)&server->async, NULL);
+
+ // Walk through and close any remaining handles
+ uv_walk(server->loop, close_handle, NULL);
+
+ uv_loop_close(server->loop);
+ freez(server->loop);
+ freez((void *)server->name);
+ freez(server);
+}
+
+SPAWN_INSTANCE* spawn_server_exec(SPAWN_SERVER *server, int stderr_fd __maybe_unused, int custom_fd __maybe_unused, const char **argv, const void *data __maybe_unused, size_t data_size __maybe_unused, SPAWN_INSTANCE_TYPE type) {
+ if (type != SPAWN_INSTANCE_TYPE_EXEC)
+ return NULL;
+
+ work_item item = { 0 };
+ item.stderr_fd = stderr_fd;
+ item.argv = argv;
+
+ if (uv_sem_init(&item.sem, 0)) {
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN PARENT: uv_sem_init() failed");
+ return NULL;
+ }
+
+ spinlock_lock(&server->spinlock);
+ // item is in the stack, but the server will remove it before sending to us
+ // the semaphore, so it is safe to have the item in the stack.
+ work_item *item_ptr = &item;
+ DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(server->work_queue, item_ptr, prev, next);
+ spinlock_unlock(&server->spinlock);
+
+ uv_async_send(&server->async);
+
+ nd_log(NDLS_COLLECTORS, NDLP_INFO, "SPAWN PARENT: queued command");
+
+ // Wait for the command to be executed
+ uv_sem_wait(&item.sem);
+ uv_sem_destroy(&item.sem);
+
+ if (!item.instance) {
+ nd_log(NDLS_COLLECTORS, NDLP_INFO, "SPAWN PARENT: process failed to be started");
+ return NULL;
+ }
+
+ nd_log(NDLS_COLLECTORS, NDLP_INFO, "SPAWN PARENT: process started");
+
+ return item.instance;
+}
+
+int spawn_server_exec_kill(SPAWN_SERVER *server __maybe_unused, SPAWN_INSTANCE *si) {
+ if(!si) return -1;
+
+ // close all pipe descriptors to force the child to exit
+ if(si->read_fd != -1) { close(si->read_fd); si->read_fd = -1; }
+ if(si->write_fd != -1) { close(si->write_fd); si->write_fd = -1; }
+
+ if (uv_process_kill(&si->process, SIGTERM)) {
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN PARENT: uv_process_kill() failed");
+ return -1;
+ }
+
+ return spawn_server_exec_wait(server, si);
+}
+
+int spawn_server_exec_wait(SPAWN_SERVER *server __maybe_unused, SPAWN_INSTANCE *si) {
+ if (!si) return -1;
+
+ // close all pipe descriptors to force the child to exit
+ if(si->read_fd != -1) { close(si->read_fd); si->read_fd = -1; }
+ if(si->write_fd != -1) { close(si->write_fd); si->write_fd = -1; }
+
+ // Wait for the process to exit
+ uv_sem_wait(&si->sem);
+ int exit_code = si->exit_code;
+
+ uv_sem_destroy(&si->sem);
+ freez(si);
+ return exit_code;
+}
+
+#endif
diff --git a/src/libnetdata/spawn_server/spawn_server.c b/src/libnetdata/spawn_server/spawn_server_nofork.c
index ef6755c32..9986740de 100644
--- a/src/libnetdata/spawn_server/spawn_server.c
+++ b/src/libnetdata/spawn_server/spawn_server_nofork.c
@@ -1,287 +1,20 @@
// SPDX-License-Identifier: GPL-3.0-or-later
-#include "../libnetdata.h"
+#include "spawn_server_internals.h"
-#include "spawn_server.h"
-
-#if defined(OS_WINDOWS)
-#include <windows.h>
-#include <io.h>
-#include <fcntl.h>
-#include <process.h>
-#include <sys/cygwin.h>
-#endif
-
-struct spawn_server {
- size_t id;
- size_t request_id;
- const char *name;
-#if !defined(OS_WINDOWS)
- SPAWN_SERVER_OPTIONS options;
-
- ND_UUID magic; // for authorizing requests, the client needs to know our random UUID
- // it is ignored for PING requests
-
- int pipe[2];
- int sock; // the listening socket of the server
- pid_t server_pid;
- char *path;
- spawn_request_callback_t cb;
-
- int argc;
- const char **argv;
-#endif
-};
-
-struct spawm_instance {
- size_t request_id;
- int sock;
- int write_fd;
- int read_fd;
- pid_t child_pid;
-
-#if defined(OS_WINDOWS)
- HANDLE process_handle;
- HANDLE read_handle;
- HANDLE write_handle;
-#endif
-};
+#if defined(SPAWN_SERVER_VERSION_NOFORK)
+// the child's output pipe, reading side
int spawn_server_instance_read_fd(SPAWN_INSTANCE *si) { return si->read_fd; }
+
+// the child's input pipe, writing side
int spawn_server_instance_write_fd(SPAWN_INSTANCE *si) { return si->write_fd; }
-pid_t spawn_server_instance_pid(SPAWN_INSTANCE *si) { return si->child_pid; }
+
void spawn_server_instance_read_fd_unset(SPAWN_INSTANCE *si) { si->read_fd = -1; }
void spawn_server_instance_write_fd_unset(SPAWN_INSTANCE *si) { si->write_fd = -1; }
+pid_t spawn_server_instance_pid(SPAWN_INSTANCE *si) { return si->child_pid; }
-#if defined(OS_WINDOWS)
-
-SPAWN_SERVER* spawn_server_create(SPAWN_SERVER_OPTIONS options __maybe_unused, const char *name, spawn_request_callback_t cb __maybe_unused, int argc __maybe_unused, const char **argv __maybe_unused) {
- SPAWN_SERVER* server = callocz(1, sizeof(SPAWN_SERVER));
- if(name)
- server->name = strdupz(name);
- else
- server->name = strdupz("unnamed");
- return server;
-}
-
-void spawn_server_destroy(SPAWN_SERVER *server) {
- if (server) {
- freez((void *)server->name);
- freez(server);
- }
-}
-
-static BUFFER *argv_to_windows(const char **argv) {
- BUFFER *wb = buffer_create(0, NULL);
-
- // argv[0] is the path
- char b[strlen(argv[0]) * 2 + 1024];
- cygwin_conv_path(CCP_POSIX_TO_WIN_A | CCP_ABSOLUTE, argv[0], b, sizeof(b));
-
- buffer_strcat(wb, "cmd.exe /C ");
-
- for(size_t i = 0; argv[i] ;i++) {
- const char *s = (i == 0) ? b : argv[i];
- size_t len = strlen(s);
- buffer_need_bytes(wb, len * 2 + 1);
-
- bool needs_quotes = false;
- for(const char *c = s; !needs_quotes && *c ; c++) {
- switch(*c) {
- case ' ':
- case '\v':
- case '\t':
- case '\n':
- case '"':
- needs_quotes = true;
- break;
-
- default:
- break;
- }
- }
-
- if(needs_quotes && buffer_strlen(wb))
- buffer_strcat(wb, " \"");
- else
- buffer_putc(wb, ' ');
-
- for(const char *c = s; *c ; c++) {
- switch(*c) {
- case '"':
- buffer_putc(wb, '\\');
- // fall through
-
- default:
- buffer_putc(wb, *c);
- break;
- }
- }
-
- if(needs_quotes)
- buffer_strcat(wb, "\"");
- }
-
- return wb;
-}
-
-SPAWN_INSTANCE* spawn_server_exec(SPAWN_SERVER *server, int stderr_fd, int custom_fd __maybe_unused, const char **argv, const void *data __maybe_unused, size_t data_size __maybe_unused, SPAWN_INSTANCE_TYPE type) {
- static SPINLOCK spinlock = NETDATA_SPINLOCK_INITIALIZER;
-
- if (type != SPAWN_INSTANCE_TYPE_EXEC)
- return NULL;
-
- int pipe_stdin[2] = { -1, -1 }, pipe_stdout[2] = { -1, -1 };
-
- errno_clear();
-
- SPAWN_INSTANCE *instance = callocz(1, sizeof(*instance));
- instance->request_id = __atomic_add_fetch(&server->request_id, 1, __ATOMIC_RELAXED);
-
- CLEAN_BUFFER *wb = argv_to_windows(argv);
- char *command = (char *)buffer_tostring(wb);
-
- if (pipe(pipe_stdin) == -1) {
- nd_log(NDLS_COLLECTORS, NDLP_ERR,
- "SPAWN PARENT: Cannot create stdin pipe() for request No %zu, command: %s",
- instance->request_id, command);
- goto cleanup;
- }
-
- if (pipe(pipe_stdout) == -1) {
- nd_log(NDLS_COLLECTORS, NDLP_ERR,
- "SPAWN PARENT: Cannot create stdout pipe() for request No %zu, command: %s",
- instance->request_id, command);
- goto cleanup;
- }
-
- // do not run multiple times this section
- // to prevent handles leaking
- spinlock_lock(&spinlock);
-
- // Convert POSIX file descriptors to Windows handles
- HANDLE stdin_read_handle = (HANDLE)_get_osfhandle(pipe_stdin[0]);
- HANDLE stdout_write_handle = (HANDLE)_get_osfhandle(pipe_stdout[1]);
- HANDLE stderr_handle = (HANDLE)_get_osfhandle(stderr_fd);
-
- if (stdin_read_handle == INVALID_HANDLE_VALUE || stdout_write_handle == INVALID_HANDLE_VALUE || stderr_handle == INVALID_HANDLE_VALUE) {
- spinlock_unlock(&spinlock);
- nd_log(NDLS_COLLECTORS, NDLP_ERR,
- "SPAWN PARENT: Invalid handle value(s) for request No %zu, command: %s",
- instance->request_id, command);
- goto cleanup;
- }
-
- // Set handle inheritance
- if (!SetHandleInformation(stdin_read_handle, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT) ||
- !SetHandleInformation(stdout_write_handle, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT) ||
- !SetHandleInformation(stderr_handle, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT)) {
- spinlock_unlock(&spinlock);
- nd_log(NDLS_COLLECTORS, NDLP_ERR,
- "SPAWN PARENT: Cannot set handle(s) inheritance for request No %zu, command: %s",
- instance->request_id, command);
- goto cleanup;
- }
-
- // Set up the STARTUPINFO structure
- STARTUPINFO si;
- PROCESS_INFORMATION pi;
- ZeroMemory(&si, sizeof(si));
- si.cb = sizeof(si);
- si.dwFlags = STARTF_USESTDHANDLES;
- si.hStdInput = stdin_read_handle;
- si.hStdOutput = stdout_write_handle;
- si.hStdError = stderr_handle;
-
- nd_log(NDLS_COLLECTORS, NDLP_ERR,
- "SPAWN PARENT: Running request No %zu, command: %s",
- instance->request_id, command);
-
- // Spawn the process
- if (!CreateProcess(NULL, command, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)) {
- spinlock_unlock(&spinlock);
- nd_log(NDLS_COLLECTORS, NDLP_ERR,
- "SPAWN PARENT: cannot CreateProcess() for request No %zu, command: %s",
- instance->request_id, command);
- goto cleanup;
- }
-
- CloseHandle(pi.hThread);
-
- // end of the critical section
- spinlock_unlock(&spinlock);
-
- // Close unused pipe ends
- close(pipe_stdin[0]); pipe_stdin[0] = -1;
- close(pipe_stdout[1]); pipe_stdout[1] = -1;
-
- // Store process information in instance
- instance->child_pid = cygwin_winpid_to_pid(pi.dwProcessId);
- if(instance->child_pid == -1) instance->child_pid = pi.dwProcessId;
-
- instance->process_handle = pi.hProcess;
-
- // Convert handles to POSIX file descriptors
- instance->write_fd = pipe_stdin[1];
- instance->read_fd = pipe_stdout[0];
-
- errno_clear();
- nd_log(NDLS_COLLECTORS, NDLP_ERR,
- "SPAWN PARENT: created process for request No %zu, pid %d, command: %s",
- instance->request_id, (int)instance->child_pid, command);
-
- return instance;
-
-cleanup:
- if (pipe_stdin[0] >= 0) close(pipe_stdin[0]);
- if (pipe_stdin[1] >= 0) close(pipe_stdin[1]);
- if (pipe_stdout[0] >= 0) close(pipe_stdout[0]);
- if (pipe_stdout[1] >= 0) close(pipe_stdout[1]);
- freez(instance);
- return NULL;
-}
-
-int spawn_server_exec_kill(SPAWN_SERVER *server __maybe_unused, SPAWN_INSTANCE *instance) {
- if(instance->read_fd != -1) { close(instance->read_fd); instance->read_fd = -1; }
- if(instance->write_fd != -1) { close(instance->write_fd); instance->write_fd = -1; }
- CloseHandle(instance->read_handle); instance->read_handle = NULL;
- CloseHandle(instance->write_handle); instance->write_handle = NULL;
-
- TerminateProcess(instance->process_handle, 0);
-
- DWORD exit_code;
- GetExitCodeProcess(instance->process_handle, &exit_code);
- CloseHandle(instance->process_handle);
-
- nd_log(NDLS_COLLECTORS, NDLP_ERR,
- "SPAWN PARENT: child of request No %zu, pid %d, killed and exited with code %d",
- instance->request_id, (int)instance->child_pid, (int)exit_code);
-
- freez(instance);
- return (int)exit_code;
-}
-
-int spawn_server_exec_wait(SPAWN_SERVER *server __maybe_unused, SPAWN_INSTANCE *instance) {
- if(instance->read_fd != -1) { close(instance->read_fd); instance->read_fd = -1; }
- if(instance->write_fd != -1) { close(instance->write_fd); instance->write_fd = -1; }
- CloseHandle(instance->read_handle); instance->read_handle = NULL;
- CloseHandle(instance->write_handle); instance->write_handle = NULL;
-
- WaitForSingleObject(instance->process_handle, INFINITE);
-
- DWORD exit_code = -1;
- GetExitCodeProcess(instance->process_handle, &exit_code);
- CloseHandle(instance->process_handle);
-
- nd_log(NDLS_COLLECTORS, NDLP_ERR,
- "SPAWN PARENT: child of request No %zu, pid %d, waited and exited with code %d",
- instance->request_id, (int)instance->child_pid, (int)exit_code);
-
- freez(instance);
- return (int)exit_code;
-}
-
-#else // !OS_WINDOWS
+pid_t spawn_server_pid(SPAWN_SERVER *server) { return server->server_pid; }
#ifdef __APPLE__
#include <crt_externs.h>
@@ -313,7 +46,7 @@ static int connect_to_spawn_server(const char *path, bool log) {
if (connect(sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
if(log)
- nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN PARENT: Cannot connect() to spawn server.");
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN PARENT: Cannot connect() to spawn server on path '%s'.", path);
close(sock);
return -1;
}
@@ -322,80 +55,6 @@ static int connect_to_spawn_server(const char *path, bool log) {
}
// --------------------------------------------------------------------------------------------------------------------
-// the child created by the spawn server
-
-static void spawn_server_run_child(SPAWN_SERVER *server, SPAWN_REQUEST *rq) {
- // close the server sockets;
- close(server->sock); server->sock = -1;
- if(server->pipe[0] != -1) { close(server->pipe[0]); server->pipe[0] = -1; }
- if(server->pipe[1] != -1) { close(server->pipe[1]); server->pipe[1] = -1; }
-
- // set the process name
- os_setproctitle("spawn-child", server->argc, server->argv);
-
- // get the fds from the request
- int stdin_fd = rq->fds[0];
- int stdout_fd = rq->fds[1];
- int stderr_fd = rq->fds[2];
- int custom_fd = rq->fds[3]; (void)custom_fd;
-
- // change stdio fds to the ones in the request
- if (dup2(stdin_fd, STDIN_FILENO) == -1) {
- nd_log(NDLS_COLLECTORS, NDLP_ERR,
- "SPAWN SERVER: cannot dup2(%d) stdin of request No %zu: %s",
- stdin_fd, rq->request_id, rq->cmdline);
- exit(1);
- }
- if (dup2(stdout_fd, STDOUT_FILENO) == -1) {
- nd_log(NDLS_COLLECTORS, NDLP_ERR,
- "SPAWN SERVER: cannot dup2(%d) stdin of request No %zu: %s",
- stdout_fd, rq->request_id, rq->cmdline);
- exit(1);
- }
- if (dup2(stderr_fd, STDERR_FILENO) == -1) {
- nd_log(NDLS_COLLECTORS, NDLP_ERR,
- "SPAWN SERVER: cannot dup2(%d) stderr of request No %zu: %s",
- stderr_fd, rq->request_id, rq->cmdline);
- exit(1);
- }
-
- // close the excess fds
- close(stdin_fd); stdin_fd = rq->fds[0] = STDIN_FILENO;
- close(stdout_fd); stdout_fd = rq->fds[1] = STDOUT_FILENO;
- close(stderr_fd); stderr_fd = rq->fds[2] = STDERR_FILENO;
-
- // overwrite the process environment
- environ = (char **)rq->environment;
-
- // Perform different actions based on the type
- switch (rq->type) {
-
- case SPAWN_INSTANCE_TYPE_EXEC:
- // close all fds except the ones we need
- os_close_all_non_std_open_fds_except(NULL, 0);
-
- // run the command
- execvp(rq->argv[0], (char **)rq->argv);
-
- nd_log(NDLS_COLLECTORS, NDLP_ERR,
- "SPAWN SERVER: Failed to execute command of request No %zu: %s",
- rq->request_id, rq->cmdline);
-
- exit(1);
- break;
-
- case SPAWN_INSTANCE_TYPE_CALLBACK:
- server->cb(rq);
- exit(0);
- break;
-
- default:
- nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN SERVER: unknown request type %u", rq->type);
- exit(1);
- }
-}
-
-// --------------------------------------------------------------------------------------------------------------------
// Encoding and decoding of spawn server request argv type of data
// Function to encode argv or envp
@@ -457,54 +116,6 @@ static const char** argv_decode(const char *buffer, size_t size) {
return argv;
}
-static BUFFER *argv_to_cmdline_buffer(const char **argv) {
- BUFFER *wb = buffer_create(0, NULL);
-
- for(size_t i = 0; argv[i] ;i++) {
- const char *s = argv[i];
- size_t len = strlen(s);
- buffer_need_bytes(wb, len * 2 + 1);
-
- bool needs_quotes = false;
- for(const char *c = s; !needs_quotes && *c ; c++) {
- switch(*c) {
- case ' ':
- case '\v':
- case '\t':
- case '\n':
- case '"':
- needs_quotes = true;
- break;
-
- default:
- break;
- }
- }
-
- if(needs_quotes && buffer_strlen(wb))
- buffer_strcat(wb, " \"");
- else
- buffer_putc(wb, ' ');
-
- for(const char *c = s; *c ; c++) {
- switch(*c) {
- case '"':
- buffer_putc(wb, '\\');
- // fall through
-
- default:
- buffer_putc(wb, *c);
- break;
- }
- }
-
- if(needs_quotes)
- buffer_strcat(wb, "\"");
- }
-
- return wb;
-}
-
// --------------------------------------------------------------------------------------------------------------------
// status reports
@@ -602,66 +213,206 @@ static void request_free(SPAWN_REQUEST *rq) {
if(rq->fds[3] != -1) close(rq->fds[3]);
if(rq->sock != -1) close(rq->sock);
freez((void *)rq->argv);
- freez((void *)rq->environment);
+ freez((void *)rq->envp);
freez((void *)rq->data);
freez((void *)rq->cmdline);
freez((void *)rq);
}
-static void spawn_server_execute_request(SPAWN_SERVER *server, SPAWN_REQUEST *rq) {
- switch(rq->type) {
- case SPAWN_INSTANCE_TYPE_EXEC:
- // close custom_fd - it is not needed for exec mode
- if(rq->fds[3] != -1) { close(rq->fds[3]); rq->fds[3] = -1; }
+static bool spawn_external_command(SPAWN_SERVER *server __maybe_unused, SPAWN_REQUEST *rq) {
+ // Close custom_fd - it is not needed for exec mode
+ if(rq->fds[3] != -1) { close(rq->fds[3]); rq->fds[3] = -1; }
- // create the cmdline for logs
- if(rq->argv) {
- CLEAN_BUFFER *wb = argv_to_cmdline_buffer(rq->argv);
- rq->cmdline = strdupz(buffer_tostring(wb));
- }
- break;
+ if(!rq->argv) {
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN PARENT: there is no argv pointer to exec");
+ return false;
+ }
- case SPAWN_INSTANCE_TYPE_CALLBACK:
- if(server->cb == NULL) {
- errno = ENOSYS;
- spawn_server_send_status_failure(rq);
- request_free(rq);
- return;
- }
- rq->cmdline = strdupz("callback() function");
- break;
+ if(rq->fds[0] == -1 || rq->fds[1] == -1 || rq->fds[2] == -1) {
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN PARENT: stdio fds are missing from the request");
+ return false;
+ }
- default:
- errno = EINVAL;
- spawn_server_send_status_failure(rq);
- request_free(rq);
- return;
+ CLEAN_BUFFER *wb = argv_to_cmdline_buffer(rq->argv);
+ rq->cmdline = strdupz(buffer_tostring(wb));
+
+ posix_spawn_file_actions_t file_actions;
+ if (posix_spawn_file_actions_init(&file_actions) != 0) {
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN PARENT: posix_spawn_file_actions_init() failed: %s", rq->cmdline);
+ return false;
+ }
+
+ posix_spawn_file_actions_adddup2(&file_actions, rq->fds[0], STDIN_FILENO);
+ posix_spawn_file_actions_adddup2(&file_actions, rq->fds[1], STDOUT_FILENO);
+ posix_spawn_file_actions_adddup2(&file_actions, rq->fds[2], STDERR_FILENO);
+ posix_spawn_file_actions_addclose(&file_actions, rq->fds[0]);
+ posix_spawn_file_actions_addclose(&file_actions, rq->fds[1]);
+ posix_spawn_file_actions_addclose(&file_actions, rq->fds[2]);
+
+ posix_spawnattr_t attr;
+ if (posix_spawnattr_init(&attr) != 0) {
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN PARENT: posix_spawnattr_init() failed: %s", rq->cmdline);
+ posix_spawn_file_actions_destroy(&file_actions);
+ return false;
+ }
+
+ // Set the flags to reset the signal mask and signal actions
+ sigset_t empty_mask;
+ sigemptyset(&empty_mask);
+ if (posix_spawnattr_setsigmask(&attr, &empty_mask) != 0) {
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN PARENT: posix_spawnattr_setsigmask() failed: %s", rq->cmdline);
+ posix_spawn_file_actions_destroy(&file_actions);
+ posix_spawnattr_destroy(&attr);
+ return false;
+ }
+
+ short flags = POSIX_SPAWN_SETSIGMASK | POSIX_SPAWN_SETSIGDEF;
+ if (posix_spawnattr_setflags(&attr, flags) != 0) {
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN PARENT: posix_spawnattr_setflags() failed: %s", rq->cmdline);
+ posix_spawn_file_actions_destroy(&file_actions);
+ posix_spawnattr_destroy(&attr);
+ return false;
+ }
+
+ int fds_to_keep[] = {
+ rq->fds[0],
+ rq->fds[1],
+ rq->fds[2],
+ nd_log_systemd_journal_fd(),
+ };
+ os_close_all_non_std_open_fds_except(fds_to_keep, _countof(fds_to_keep), CLOSE_RANGE_CLOEXEC);
+
+ errno_clear();
+ if (posix_spawn(&rq->pid, rq->argv[0], &file_actions, &attr, (char * const *)rq->argv, (char * const *)rq->envp) != 0) {
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN SERVER: posix_spawn() failed: %s", rq->cmdline);
+
+ posix_spawnattr_destroy(&attr);
+ posix_spawn_file_actions_destroy(&file_actions);
+ return false;
+ }
+
+ // Destroy the posix_spawnattr_t and posix_spawn_file_actions_t structures
+ posix_spawnattr_destroy(&attr);
+ posix_spawn_file_actions_destroy(&file_actions);
+
+ // Close the read end of the stdin pipe and the write end of the stdout pipe in the parent process
+ close(rq->fds[0]); rq->fds[0] = -1;
+ close(rq->fds[1]); rq->fds[1] = -1;
+ close(rq->fds[2]); rq->fds[2] = -1;
+
+ nd_log(NDLS_COLLECTORS, NDLP_DEBUG, "SPAWN SERVER: process created with pid %d: %s", rq->pid, rq->cmdline);
+ return true;
+}
+
+static bool spawn_server_run_callback(SPAWN_SERVER *server __maybe_unused, SPAWN_REQUEST *rq) {
+ rq->cmdline = strdupz("callback() function");
+
+ if(server->cb == NULL) {
+ errno = ENOSYS;
+ return false;
}
pid_t pid = fork();
if (pid < 0) {
// fork failed
- nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN SERVER: Failed to fork() child.");
- spawn_server_send_status_failure(rq);
- request_free(rq);
- return;
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN SERVER: Failed to fork() child for callback.");
+ return false;
}
else if (pid == 0) {
// the child
- spawn_server_run_child(server, rq);
- exit(63);
+ // close the server sockets;
+ close(server->sock); server->sock = -1;
+ if(server->pipe[0] != -1) { close(server->pipe[0]); server->pipe[0] = -1; }
+ if(server->pipe[1] != -1) { close(server->pipe[1]); server->pipe[1] = -1; }
+
+ // set the process name
+ os_setproctitle("spawn-callback", server->argc, server->argv);
+
+ // close all open file descriptors of the parent, but keep ours
+ int fds_to_keep[] = {
+ rq->fds[0],
+ rq->fds[1],
+ rq->fds[2],
+ rq->fds[3],
+ nd_log_systemd_journal_fd(),
+ };
+ os_close_all_non_std_open_fds_except(fds_to_keep, _countof(fds_to_keep), 0);
+ nd_log_reopen_log_files_for_spawn_server("spawn-callback");
+
+ // get the fds from the request
+ int stdin_fd = rq->fds[0];
+ int stdout_fd = rq->fds[1];
+ int stderr_fd = rq->fds[2];
+ int custom_fd = rq->fds[3]; (void)custom_fd;
+
+ // change stdio fds to the ones in the request
+ if (dup2(stdin_fd, STDIN_FILENO) == -1) {
+ nd_log(NDLS_COLLECTORS, NDLP_ERR,
+ "SPAWN SERVER: cannot dup2(%d) stdin of request No %zu: %s",
+ stdin_fd, rq->request_id, rq->cmdline);
+ exit(EXIT_FAILURE);
+ }
+ if (dup2(stdout_fd, STDOUT_FILENO) == -1) {
+ nd_log(NDLS_COLLECTORS, NDLP_ERR,
+ "SPAWN SERVER: cannot dup2(%d) stdin of request No %zu: %s",
+ stdout_fd, rq->request_id, rq->cmdline);
+ exit(EXIT_FAILURE);
+ }
+ if (dup2(stderr_fd, STDERR_FILENO) == -1) {
+ nd_log(NDLS_COLLECTORS, NDLP_ERR,
+ "SPAWN SERVER: cannot dup2(%d) stderr of request No %zu: %s",
+ stderr_fd, rq->request_id, rq->cmdline);
+ exit(EXIT_FAILURE);
+ }
+
+ // close the excess fds
+ close(stdin_fd); stdin_fd = rq->fds[0] = STDIN_FILENO;
+ close(stdout_fd); stdout_fd = rq->fds[1] = STDOUT_FILENO;
+ close(stderr_fd); stderr_fd = rq->fds[2] = STDERR_FILENO;
+
+ // overwrite the process environment
+ environ = (char **)rq->envp;
+
+ // run the callback and return its code
+ exit(server->cb(rq));
}
// the parent
rq->pid = pid;
+ return true;
+}
+
+static void spawn_server_execute_request(SPAWN_SERVER *server, SPAWN_REQUEST *rq) {
+ bool done;
+ switch(rq->type) {
+ case SPAWN_INSTANCE_TYPE_EXEC:
+ done = spawn_external_command(server, rq);
+ break;
+
+ case SPAWN_INSTANCE_TYPE_CALLBACK:
+ done = spawn_server_run_callback(server, rq);
+ break;
+
+ default:
+ errno = EINVAL;
+ done = false;
+ break;
+ }
+
+ if(!done) {
+ spawn_server_send_status_failure(rq);
+ request_free(rq);
+ return;
+ }
+
// let the parent know
spawn_server_send_status_success(rq);
// do not keep data we don't need at the parent
- freez((void *)rq->environment); rq->environment = NULL;
+ freez((void *)rq->envp); rq->envp = NULL;
freez((void *)rq->argv); rq->argv = NULL;
freez((void *)rq->data); rq->data = NULL;
rq->data_size = 0;
@@ -747,7 +498,7 @@ static bool spawn_server_send_request(ND_UUID *magic, SPAWN_REQUEST *request) {
bool ret = false;
size_t env_size = 0;
- void *encoded_env = argv_encode(request->environment, &env_size);
+ void *encoded_env = argv_encode(request->envp, &env_size);
if (!encoded_env)
goto cleanup;
@@ -974,7 +725,7 @@ static void spawn_server_receive_request(int sock, SPAWN_SERVER *server) {
[2] = stderr_fd,
[3] = custom_fd,
},
- .environment = argv_decode(envp_encoded, env_size),
+ .envp = argv_decode(envp_encoded, env_size),
.argv = argv_decode(argv_encoded, argv_size),
.data = data,
.data_size = data_size,
@@ -1023,6 +774,8 @@ static SPAWN_REQUEST *find_request_by_pid(pid_t pid) {
static void spawn_server_process_sigchld(void) {
// nd_log(NDLS_COLLECTORS, NDLP_INFO, "SPAWN SERVER: checking for exited children");
+ spawn_server_sigchld = false;
+
int status;
pid_t pid;
@@ -1039,36 +792,36 @@ static void spawn_server_process_sigchld(void) {
if(WIFEXITED(status)) {
if(WEXITSTATUS(status))
- nd_log(NDLS_COLLECTORS, NDLP_INFO,
+ nd_log(NDLS_COLLECTORS, NDLP_WARNING,
"SPAWN SERVER: child with pid %d (request %zu) exited with exit code %d: %s",
pid, request_id, WEXITSTATUS(status), rq ? rq->cmdline : "[request not found]");
send_report_remove_request = true;
}
else if(WIFSIGNALED(status)) {
if(WCOREDUMP(status))
- nd_log(NDLS_COLLECTORS, NDLP_INFO,
+ nd_log(NDLS_COLLECTORS, NDLP_WARNING,
"SPAWN SERVER: child with pid %d (request %zu) coredump'd due to signal %d: %s",
pid, request_id, WTERMSIG(status), rq ? rq->cmdline : "[request not found]");
else
- nd_log(NDLS_COLLECTORS, NDLP_INFO,
+ nd_log(NDLS_COLLECTORS, NDLP_WARNING,
"SPAWN SERVER: child with pid %d (request %zu) killed by signal %d: %s",
pid, request_id, WTERMSIG(status), rq ? rq->cmdline : "[request not found]");
send_report_remove_request = true;
}
else if(WIFSTOPPED(status)) {
- nd_log(NDLS_COLLECTORS, NDLP_INFO,
+ nd_log(NDLS_COLLECTORS, NDLP_WARNING,
"SPAWN SERVER: child with pid %d (request %zu) stopped due to signal %d: %s",
pid, request_id, WSTOPSIG(status), rq ? rq->cmdline : "[request not found]");
send_report_remove_request = false;
}
else if(WIFCONTINUED(status)) {
- nd_log(NDLS_COLLECTORS, NDLP_INFO,
+ nd_log(NDLS_COLLECTORS, NDLP_WARNING,
"SPAWN SERVER: child with pid %d (request %zu) continued due to signal %d: %s",
pid, request_id, SIGCONT, rq ? rq->cmdline : "[request not found]");
send_report_remove_request = false;
}
else {
- nd_log(NDLS_COLLECTORS, NDLP_INFO,
+ nd_log(NDLS_COLLECTORS, NDLP_WARNING,
"SPAWN SERVER: child with pid %d (request %zu) reports unhandled status: %s",
pid, request_id, rq ? rq->cmdline : "[request not found]");
send_report_remove_request = false;
@@ -1082,20 +835,21 @@ static void spawn_server_process_sigchld(void) {
}
}
-static void signals_unblock(void) {
+static void posix_unmask_sigchld_on_thread(void) {
sigset_t sigset;
- sigfillset(&sigset);
+ sigemptyset(&sigset); // Initialize the signal set to empty
+ sigaddset(&sigset, SIGCHLD); // Add SIGCHLD to the set
- if(pthread_sigmask(SIG_UNBLOCK, &sigset, NULL) == -1) {
- netdata_log_error("SPAWN SERVER: Could not unblock signals for threads");
- }
+ if(pthread_sigmask(SIG_UNBLOCK, &sigset, NULL) != 0)
+ nd_log(NDLS_COLLECTORS, NDLP_ERR,
+ "SPAWN SERVER: cannot unmask SIGCHLD");
}
-static void spawn_server_event_loop(SPAWN_SERVER *server) {
+static int spawn_server_event_loop(SPAWN_SERVER *server) {
int pipe_fd = server->pipe[1];
close(server->pipe[0]); server->pipe[0] = -1;
- signals_unblock();
+ posix_unmask_sigchld_on_thread();
// Set up the signal handler for SIGCHLD and SIGTERM
struct sigaction sa;
@@ -1104,13 +858,13 @@ static void spawn_server_event_loop(SPAWN_SERVER *server) {
sa.sa_flags = SA_RESTART | SA_NOCLDSTOP;
if (sigaction(SIGCHLD, &sa, NULL) == -1) {
nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN SERVER: sigaction() failed for SIGCHLD");
- exit(1);
+ return 1;
}
sa.sa_handler = spawn_server_sigterm_handler;
if (sigaction(SIGTERM, &sa, NULL) == -1) {
nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN SERVER: sigaction() failed for SIGTERM");
- exit(1);
+ return 1;
}
struct status_report sr = {
@@ -1121,7 +875,7 @@ static void spawn_server_event_loop(SPAWN_SERVER *server) {
};
if (write(pipe_fd, &sr, sizeof(sr)) != sizeof(sr)) {
nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN SERVER: failed to write initial status report.");
- exit(1);
+ return 1;
}
struct pollfd fds[2];
@@ -1131,13 +885,12 @@ static void spawn_server_event_loop(SPAWN_SERVER *server) {
fds[1].events = POLLHUP | POLLERR;
while(!spawn_server_exit) {
- int ret = poll(fds, 2, -1);
- if (spawn_server_sigchld) {
- spawn_server_sigchld = false;
+ int ret = poll(fds, 2, 500);
+ if (spawn_server_sigchld || ret == 0) {
spawn_server_process_sigchld();
errno_clear();
- if(ret == -1)
+ if(ret == -1 || ret == 0)
continue;
}
@@ -1148,7 +901,7 @@ static void spawn_server_event_loop(SPAWN_SERVER *server) {
if (fds[1].revents & (POLLHUP|POLLERR)) {
// Pipe has been closed (parent has exited)
- nd_log(NDLS_COLLECTORS, NDLP_DEBUG, "SPAWN SERVER: Parent process has exited");
+ nd_log(NDLS_COLLECTORS, NDLP_DEBUG, "SPAWN SERVER: Parent process closed socket (exited?)");
break;
}
@@ -1185,7 +938,7 @@ static void spawn_server_event_loop(SPAWN_SERVER *server) {
// nd_log(NDLS_COLLECTORS, NDLP_INFO, "SPAWN SERVER: all %zu children finished", killed);
}
- exit(1);
+ return 0;
}
// --------------------------------------------------------------------------------------------------------------------
@@ -1242,22 +995,24 @@ static bool spawn_server_create_listening_socket(SPAWN_SERVER *server) {
}
static void replace_stdio_with_dev_null() {
+ // we cannot log in this function - the logger is not yet initialized after fork()
+
int dev_null_fd = open("/dev/null", O_RDWR);
if (dev_null_fd == -1) {
- nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN SERVER: Failed to open /dev/null: %s", strerror(errno));
+ // nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN SERVER: Failed to open /dev/null: %s", strerror(errno));
return;
}
// Redirect stdin (fd 0)
if (dup2(dev_null_fd, STDIN_FILENO) == -1) {
- nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN SERVER: Failed to redirect stdin to /dev/null: %s", strerror(errno));
+ // nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN SERVER: Failed to redirect stdin to /dev/null: %s", strerror(errno));
close(dev_null_fd);
return;
}
// Redirect stdout (fd 1)
if (dup2(dev_null_fd, STDOUT_FILENO) == -1) {
- nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN SERVER: Failed to redirect stdout to /dev/null: %s", strerror(errno));
+ // nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN SERVER: Failed to redirect stdout to /dev/null: %s", strerror(errno));
close(dev_null_fd);
return;
}
@@ -1329,16 +1084,19 @@ SPAWN_SERVER* spawn_server_create(SPAWN_SERVER_OPTIONS options, const char *name
if (pid == 0) {
// the child - the spawn server
- {
- char buf[15];
- snprintfz(buf, sizeof(buf), "spawn-%s", server->name);
- os_setproctitle(buf, server->argc, server->argv);
- }
+ char buf[16];
+ snprintfz(buf, sizeof(buf), "spawn-%s", server->name);
+ os_setproctitle(buf, server->argc, server->argv);
replace_stdio_with_dev_null();
- os_close_all_non_std_open_fds_except((int[]){ server->sock, server->pipe[1] }, 2);
- nd_log_reopen_log_files_for_spawn_server();
- spawn_server_event_loop(server);
+ int fds_to_keep[] = {
+ server->sock,
+ server->pipe[1],
+ nd_log_systemd_journal_fd(),
+ };
+ os_close_all_non_std_open_fds_except(fds_to_keep, _countof(fds_to_keep), 0);
+ nd_log_reopen_log_files_for_spawn_server(buf);
+ exit(spawn_server_event_loop(server));
}
else if (pid > 0) {
// the parent
@@ -1362,6 +1120,8 @@ SPAWN_SERVER* spawn_server_create(SPAWN_SERVER_OPTIONS options, const char *name
goto cleanup;
}
+ nd_log(NDLS_COLLECTORS, NDLP_DEBUG, "SPAWN SERVER: server created on pid %d", server->server_pid);
+
return server;
}
@@ -1383,6 +1143,21 @@ void spawn_server_exec_destroy(SPAWN_INSTANCE *instance) {
freez(instance);
}
+static void log_invalid_magic(SPAWN_INSTANCE *instance, struct status_report *sr) {
+ unsigned char buf[sizeof(*sr) + 1];
+ memcpy(buf, sr, sizeof(*sr));
+ buf[sizeof(buf) - 1] = '\0';
+
+ for(size_t i = 0; i < sizeof(buf) - 1; i++) {
+ if (iscntrl(buf[i]) || !isprint(buf[i]))
+ buf[i] = '_';
+ }
+
+ nd_log(NDLS_COLLECTORS, NDLP_ERR,
+ "SPAWN PARENT: invalid final status report for child %d, request %zu (invalid magic %#x in response, reads like '%s')",
+ instance->child_pid, instance->request_id, sr->magic, buf);
+}
+
int spawn_server_exec_wait(SPAWN_SERVER *server __maybe_unused, SPAWN_INSTANCE *instance) {
int rc = -1;
@@ -1397,24 +1172,24 @@ int spawn_server_exec_wait(SPAWN_SERVER *server __maybe_unused, SPAWN_INSTANCE *
"SPAWN PARENT: failed to read final status report for child %d, request %zu",
instance->child_pid, instance->request_id);
- else if(sr.magic != STATUS_REPORT_MAGIC) {
- nd_log(NDLS_COLLECTORS, NDLP_ERR,
- "SPAWN PARENT: invalid final status report for child %d, request %zu (invalid magic %#x in response)",
- instance->child_pid, instance->request_id, sr.magic);
- }
- else switch(sr.status) {
- case STATUS_REPORT_EXITED:
- rc = sr.exited.waitpid_status;
- break;
+ else if(sr.magic != STATUS_REPORT_MAGIC)
+ log_invalid_magic(instance, &sr);
+ else {
+ switch (sr.status) {
+ case STATUS_REPORT_EXITED:
+ rc = sr.exited.waitpid_status;
+ break;
- case STATUS_REPORT_STARTED:
- case STATUS_REPORT_FAILED:
- default:
- errno = 0;
- nd_log(NDLS_COLLECTORS, NDLP_ERR,
- "SPAWN PARENT: invalid status report to exec spawn request %zu for pid %d (status = %u)",
- instance->request_id, instance->child_pid, sr.status);
- break;
+ case STATUS_REPORT_STARTED:
+ case STATUS_REPORT_FAILED:
+ default:
+ errno = 0;
+ nd_log(
+ NDLS_COLLECTORS, NDLP_ERR,
+ "SPAWN PARENT: invalid status report to exec spawn request %zu for pid %d (status = %u)",
+ instance->request_id, instance->child_pid, sr.status);
+ break;
+ }
}
instance->child_pid = 0;
@@ -1458,7 +1233,7 @@ SPAWN_INSTANCE* spawn_server_exec(SPAWN_SERVER *server, int stderr_fd, int custo
[2] = stderr_fd,
[3] = custom_fd,
},
- .environment = (const char **)environ,
+ .envp = (const char **)environ,
.argv = argv,
.data = data,
.data_size = data_size,
@@ -1530,4 +1305,4 @@ cleanup:
return NULL;
}
-#endif // !OS_WINDOWS
+#endif
diff --git a/src/libnetdata/spawn_server/spawn_server_posix.c b/src/libnetdata/spawn_server/spawn_server_posix.c
new file mode 100644
index 000000000..f96921bb9
--- /dev/null
+++ b/src/libnetdata/spawn_server/spawn_server_posix.c
@@ -0,0 +1,299 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "spawn_server_internals.h"
+
+#if defined(SPAWN_SERVER_VERSION_POSIX_SPAWN)
+
+#ifdef __APPLE__
+#include <crt_externs.h>
+#define environ (*_NSGetEnviron())
+#else
+extern char **environ;
+#endif
+
+int spawn_server_instance_read_fd(SPAWN_INSTANCE *si) { return si->read_fd; }
+int spawn_server_instance_write_fd(SPAWN_INSTANCE *si) { return si->write_fd; }
+void spawn_server_instance_read_fd_unset(SPAWN_INSTANCE *si) { si->read_fd = -1; }
+void spawn_server_instance_write_fd_unset(SPAWN_INSTANCE *si) { si->write_fd = -1; }
+pid_t spawn_server_instance_pid(SPAWN_INSTANCE *si) { return si->child_pid; }
+
+static struct {
+ bool sigchld_initialized;
+ SPINLOCK spinlock;
+ SPAWN_INSTANCE *instances;
+} spawn_globals = {
+ .spinlock = NETDATA_SPINLOCK_INITIALIZER,
+ .instances = NULL,
+};
+
+//static void sigchld_handler(int signum __maybe_unused) {
+// pid_t pid;
+// int status;
+//
+// while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
+// // Find the SPAWN_INSTANCE corresponding to this pid
+// spinlock_lock(&spawn_globals.spinlock);
+// for(SPAWN_INSTANCE *si = spawn_globals.instances; si ;si = si->next) {
+// if (si->child_pid == pid) {
+// __atomic_store_n(&si->waitpid_status, status, __ATOMIC_RELAXED);
+// __atomic_store_n(&si->exited, true, __ATOMIC_RELAXED);
+// break;
+// }
+// }
+// spinlock_unlock(&spawn_globals.spinlock);
+// }
+//}
+
+SPAWN_SERVER* spawn_server_create(SPAWN_SERVER_OPTIONS options __maybe_unused, const char *name, spawn_request_callback_t cb __maybe_unused, int argc __maybe_unused, const char **argv __maybe_unused) {
+ SPAWN_SERVER* server = callocz(1, sizeof(SPAWN_SERVER));
+
+ if (name)
+ server->name = strdupz(name);
+ else
+ server->name = strdupz("unnamed");
+
+ if(!spawn_globals.sigchld_initialized) {
+ spawn_globals.sigchld_initialized = true;
+
+// struct sigaction sa;
+// sa.sa_handler = sigchld_handler;
+// sigemptyset(&sa.sa_mask);
+// sa.sa_flags = SA_RESTART | SA_NOCLDSTOP;
+// if (sigaction(SIGCHLD, &sa, NULL) == -1) {
+// nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN PARENT: Failed to set SIGCHLD handler");
+// freez((void *)server->name);
+// freez(server);
+// return NULL;
+// }
+ }
+
+ return server;
+}
+
+void spawn_server_destroy(SPAWN_SERVER *server) {
+ if (!server) return;
+ freez((void *)server->name);
+ freez(server);
+}
+
+SPAWN_INSTANCE* spawn_server_exec(SPAWN_SERVER *server, int stderr_fd, int custom_fd __maybe_unused, const char **argv, const void *data __maybe_unused, size_t data_size __maybe_unused, SPAWN_INSTANCE_TYPE type) {
+ if (type != SPAWN_INSTANCE_TYPE_EXEC)
+ return NULL;
+
+ CLEAN_BUFFER *cmdline_wb = argv_to_cmdline_buffer(argv);
+ const char *cmdline = buffer_tostring(cmdline_wb);
+
+ SPAWN_INSTANCE *si = callocz(1, sizeof(SPAWN_INSTANCE));
+ si->child_pid = -1;
+ si->request_id = __atomic_add_fetch(&server->request_id, 1, __ATOMIC_RELAXED);
+
+ int stdin_pipe[2] = { -1, -1 };
+ int stdout_pipe[2] = { -1, -1 };
+
+ if (pipe(stdin_pipe) == -1) {
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN PARENT: stdin pipe() failed: %s", cmdline);
+ freez(si);
+ return NULL;
+ }
+
+ if (pipe(stdout_pipe) == -1) {
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN PARENT: stdout pipe() failed: %s", cmdline);
+ close(stdin_pipe[PIPE_READ]);
+ close(stdin_pipe[PIPE_WRITE]);
+ freez(si);
+ return NULL;
+ }
+
+ posix_spawn_file_actions_t file_actions;
+ posix_spawnattr_t attr;
+
+ if (posix_spawn_file_actions_init(&file_actions) != 0) {
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN PARENT: posix_spawn_file_actions_init() failed: %s", cmdline);
+ close(stdin_pipe[PIPE_READ]);
+ close(stdin_pipe[PIPE_WRITE]);
+ close(stdout_pipe[PIPE_READ]);
+ close(stdout_pipe[PIPE_WRITE]);
+ freez(si);
+ return NULL;
+ }
+
+ posix_spawn_file_actions_adddup2(&file_actions, stdin_pipe[PIPE_READ], STDIN_FILENO);
+ posix_spawn_file_actions_adddup2(&file_actions, stdout_pipe[PIPE_WRITE], STDOUT_FILENO);
+ posix_spawn_file_actions_addclose(&file_actions, stdin_pipe[PIPE_READ]);
+ posix_spawn_file_actions_addclose(&file_actions, stdin_pipe[PIPE_WRITE]);
+ posix_spawn_file_actions_addclose(&file_actions, stdout_pipe[PIPE_READ]);
+ posix_spawn_file_actions_addclose(&file_actions, stdout_pipe[PIPE_WRITE]);
+ if(stderr_fd != STDERR_FILENO) {
+ posix_spawn_file_actions_adddup2(&file_actions, stderr_fd, STDERR_FILENO);
+ posix_spawn_file_actions_addclose(&file_actions, stderr_fd);
+ }
+
+ if (posix_spawnattr_init(&attr) != 0) {
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN PARENT: posix_spawnattr_init() failed: %s", cmdline);
+ posix_spawn_file_actions_destroy(&file_actions);
+ close(stdin_pipe[PIPE_READ]);
+ close(stdin_pipe[PIPE_WRITE]);
+ close(stdout_pipe[PIPE_READ]);
+ close(stdout_pipe[PIPE_WRITE]);
+ freez(si);
+ return NULL;
+ }
+
+ // Set the flags to reset the signal mask and signal actions
+ sigset_t empty_mask;
+ sigemptyset(&empty_mask);
+ if (posix_spawnattr_setsigmask(&attr, &empty_mask) != 0) {
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN PARENT: posix_spawnattr_setsigmask() failed: %s", cmdline);
+ posix_spawn_file_actions_destroy(&file_actions);
+ posix_spawnattr_destroy(&attr);
+ return false;
+ }
+
+ short flags = POSIX_SPAWN_SETSIGMASK | POSIX_SPAWN_SETSIGDEF;
+ if (posix_spawnattr_setflags(&attr, flags) != 0) {
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN PARENT: posix_spawnattr_setflags() failed: %s", cmdline);
+ posix_spawn_file_actions_destroy(&file_actions);
+ posix_spawnattr_destroy(&attr);
+ return false;
+ }
+
+ spinlock_lock(&spawn_globals.spinlock);
+ DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(spawn_globals.instances, si, prev, next);
+ spinlock_unlock(&spawn_globals.spinlock);
+
+ // unfortunately, on CYGWIN/MSYS posix_spawn() is not thread safe
+ // so, we run it one by one.
+ static SPINLOCK spinlock = NETDATA_SPINLOCK_INITIALIZER;
+ spinlock_lock(&spinlock);
+
+ int fds[3] = { stdin_pipe[PIPE_READ], stdout_pipe[PIPE_WRITE], stderr_fd };
+ os_close_all_non_std_open_fds_except(fds, 3, CLOSE_RANGE_CLOEXEC);
+
+ errno_clear();
+ if (posix_spawn(&si->child_pid, argv[0], &file_actions, &attr, (char * const *)argv, environ) != 0) {
+ spinlock_unlock(&spinlock);
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN SERVER: posix_spawn() failed: %s", cmdline);
+
+ spinlock_lock(&spawn_globals.spinlock);
+ DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(spawn_globals.instances, si, prev, next);
+ spinlock_unlock(&spawn_globals.spinlock);
+
+ posix_spawnattr_destroy(&attr);
+ posix_spawn_file_actions_destroy(&file_actions);
+
+ close(stdin_pipe[PIPE_READ]);
+ close(stdin_pipe[PIPE_WRITE]);
+ close(stdout_pipe[PIPE_READ]);
+ close(stdout_pipe[PIPE_WRITE]);
+ freez(si);
+ return NULL;
+ }
+ spinlock_unlock(&spinlock);
+
+ // Destroy the posix_spawnattr_t and posix_spawn_file_actions_t structures
+ posix_spawnattr_destroy(&attr);
+ posix_spawn_file_actions_destroy(&file_actions);
+
+ // Close the read end of the stdin pipe and the write end of the stdout pipe in the parent process
+ close(stdin_pipe[PIPE_READ]);
+ close(stdout_pipe[PIPE_WRITE]);
+
+ si->write_fd = stdin_pipe[PIPE_WRITE];
+ si->read_fd = stdout_pipe[PIPE_READ];
+ si->cmdline = strdupz(cmdline);
+
+ nd_log(NDLS_COLLECTORS, NDLP_INFO,
+ "SPAWN SERVER: process created with pid %d: %s",
+ si->child_pid, cmdline);
+ return si;
+}
+
+int spawn_server_exec_kill(SPAWN_SERVER *server, SPAWN_INSTANCE *si) {
+ if (!si) return -1;
+
+ if (kill(si->child_pid, SIGTERM))
+ nd_log(NDLS_COLLECTORS, NDLP_ERR,
+ "SPAWN PARENT: kill() of pid %d failed: %s",
+ si->child_pid, si->cmdline);
+
+ return spawn_server_exec_wait(server, si);
+}
+
+static int spawn_server_waitpid(SPAWN_INSTANCE *si) {
+ int status;
+ pid_t pid;
+
+ pid = waitpid(si->child_pid, &status, 0);
+
+ if(pid != si->child_pid) {
+ nd_log(NDLS_COLLECTORS, NDLP_ERR,
+ "SPAWN PARENT: failed to wait for pid %d: %s",
+ si->child_pid, si->cmdline);
+
+ return -1;
+ }
+
+ errno_clear();
+
+ if(WIFEXITED(status)) {
+ if(WEXITSTATUS(status))
+ nd_log(NDLS_COLLECTORS, NDLP_INFO,
+ "SPAWN SERVER: child with pid %d (request %zu) exited with exit code %d: %s",
+ pid, si->request_id, WEXITSTATUS(status), si->cmdline);
+ }
+ else if(WIFSIGNALED(status)) {
+ if(WCOREDUMP(status))
+ nd_log(NDLS_COLLECTORS, NDLP_INFO,
+ "SPAWN SERVER: child with pid %d (request %zu) coredump'd due to signal %d: %s",
+ pid, si->request_id, WTERMSIG(status), si->cmdline);
+ else
+ nd_log(NDLS_COLLECTORS, NDLP_INFO,
+ "SPAWN SERVER: child with pid %d (request %zu) killed by signal %d: %s",
+ pid, si->request_id, WTERMSIG(status), si->cmdline);
+ }
+ else if(WIFSTOPPED(status)) {
+ nd_log(NDLS_COLLECTORS, NDLP_INFO,
+ "SPAWN SERVER: child with pid %d (request %zu) stopped due to signal %d: %s",
+ pid, si->request_id, WSTOPSIG(status), si->cmdline);
+ }
+ else if(WIFCONTINUED(status)) {
+ nd_log(NDLS_COLLECTORS, NDLP_INFO,
+ "SPAWN SERVER: child with pid %d (request %zu) continued due to signal %d: %s",
+ pid, si->request_id, SIGCONT, si->cmdline);
+ }
+ else {
+ nd_log(NDLS_COLLECTORS, NDLP_INFO,
+ "SPAWN SERVER: child with pid %d (request %zu) reports unhandled status: %s",
+ pid, si->request_id, si->cmdline);
+ }
+
+ return status;
+}
+
+int spawn_server_exec_wait(SPAWN_SERVER *server __maybe_unused, SPAWN_INSTANCE *si) {
+ if (!si) return -1;
+
+ // Close all pipe descriptors to force the child to exit
+ if (si->read_fd != -1) close(si->read_fd);
+ if (si->write_fd != -1) close(si->write_fd);
+
+ // Wait for the process to exit
+ int status = __atomic_load_n(&si->waitpid_status, __ATOMIC_RELAXED);
+ bool exited = __atomic_load_n(&si->exited, __ATOMIC_RELAXED);
+ if(!exited)
+ status = spawn_server_waitpid(si);
+ else
+ nd_log(NDLS_COLLECTORS, NDLP_INFO,
+ "SPAWN PARENT: child with pid %d exited with status %d (sighandler): %s",
+ si->child_pid, status, si->cmdline);
+
+ spinlock_lock(&spawn_globals.spinlock);
+ DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(spawn_globals.instances, si, prev, next);
+ spinlock_unlock(&spawn_globals.spinlock);
+
+ freez((void *)si->cmdline);
+ freez(si);
+ return status;
+}
+
+#endif
diff --git a/src/libnetdata/spawn_server/spawn_server_windows.c b/src/libnetdata/spawn_server/spawn_server_windows.c
new file mode 100644
index 000000000..f80925a24
--- /dev/null
+++ b/src/libnetdata/spawn_server/spawn_server_windows.c
@@ -0,0 +1,456 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "spawn_server_internals.h"
+
+#if defined(SPAWN_SERVER_VERSION_WINDOWS)
+
+int spawn_server_instance_read_fd(SPAWN_INSTANCE *si) { return si->read_fd; }
+int spawn_server_instance_write_fd(SPAWN_INSTANCE *si) { return si->write_fd; }
+void spawn_server_instance_read_fd_unset(SPAWN_INSTANCE *si) { si->read_fd = -1; }
+void spawn_server_instance_write_fd_unset(SPAWN_INSTANCE *si) { si->write_fd = -1; }
+
+pid_t spawn_server_instance_pid(SPAWN_INSTANCE *si) {
+ if(si->child_pid != -1)
+ return si->child_pid;
+
+ return (pid_t)si->dwProcessId;
+}
+
+static void update_cygpath_env(void) {
+ static volatile bool done = false;
+
+ if(done) return;
+ done = true;
+
+ char win_path[MAX_PATH];
+
+ // Convert Cygwin root path to Windows path
+ cygwin_conv_path(CCP_POSIX_TO_WIN_A, "/", win_path, sizeof(win_path));
+
+ nd_setenv("NETDATA_CYGWIN_BASE_PATH", win_path, 1);
+
+ nd_log(NDLS_COLLECTORS, NDLP_INFO, "Cygwin/MSYS2 base path set to '%s'", win_path);
+}
+
+SPAWN_SERVER* spawn_server_create(SPAWN_SERVER_OPTIONS options __maybe_unused, const char *name, spawn_request_callback_t cb __maybe_unused, int argc __maybe_unused, const char **argv __maybe_unused) {
+ update_cygpath_env();
+
+ SPAWN_SERVER* server = callocz(1, sizeof(SPAWN_SERVER));
+ if(name)
+ server->name = strdupz(name);
+ else
+ server->name = strdupz("unnamed");
+
+ server->log_forwarder = log_forwarder_start();
+
+ return server;
+}
+
+void spawn_server_destroy(SPAWN_SERVER *server) {
+ if (server) {
+ if (server->log_forwarder) {
+ log_forwarder_stop(server->log_forwarder);
+ server->log_forwarder = NULL;
+ }
+ freez((void *)server->name);
+ freez(server);
+ }
+}
+
+static BUFFER *argv_to_windows(const char **argv) {
+ BUFFER *wb = buffer_create(0, NULL);
+
+ // argv[0] is the path
+ char b[strlen(argv[0]) * 2 + FILENAME_MAX];
+ cygwin_conv_path(CCP_POSIX_TO_WIN_A | CCP_ABSOLUTE, argv[0], b, sizeof(b));
+
+ for(size_t i = 0; argv[i] ;i++) {
+ const char *s = (i == 0) ? b : argv[i];
+ size_t len = strlen(s);
+ buffer_need_bytes(wb, len * 2 + 1);
+
+ bool needs_quotes = false;
+ for(const char *c = s; !needs_quotes && *c ; c++) {
+ switch(*c) {
+ case ' ':
+ case '\v':
+ case '\t':
+ case '\n':
+ case '"':
+ needs_quotes = true;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ if(buffer_strlen(wb)) {
+ if (needs_quotes)
+ buffer_strcat(wb, " \"");
+ else
+ buffer_putc(wb, ' ');
+ }
+ else if (needs_quotes)
+ buffer_putc(wb, '"');
+
+ for(const char *c = s; *c ; c++) {
+ switch(*c) {
+ case '"':
+ buffer_putc(wb, '\\');
+ // fall through
+
+ default:
+ buffer_putc(wb, *c);
+ break;
+ }
+ }
+
+ if(needs_quotes)
+ buffer_strcat(wb, "\"");
+ }
+
+ return wb;
+}
+
+int set_fd_blocking(int fd) {
+ int flags = fcntl(fd, F_GETFL, 0);
+ if (flags == -1) {
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN PARENT: fcntl(F_GETFL) failed");
+ return -1;
+ }
+
+ flags &= ~O_NONBLOCK;
+ if (fcntl(fd, F_SETFL, flags) == -1) {
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN PARENT: fcntl(F_SETFL) failed");
+ return -1;
+ }
+
+ return 0;
+}
+
+//static void print_environment_block(char *env_block) {
+// if (env_block == NULL) {
+// fprintf(stderr, "Environment block is NULL\n");
+// return;
+// }
+//
+// char *env = env_block;
+// while (*env) {
+// fprintf(stderr, "ENVIRONMENT: %s\n", env);
+// // Move to the next string in the block
+// env += strlen(env) + 1;
+// }
+//}
+
+SPAWN_INSTANCE* spawn_server_exec(SPAWN_SERVER *server, int stderr_fd __maybe_unused, int custom_fd __maybe_unused, const char **argv, const void *data __maybe_unused, size_t data_size __maybe_unused, SPAWN_INSTANCE_TYPE type) {
+ static SPINLOCK spinlock = NETDATA_SPINLOCK_INITIALIZER;
+
+ if (type != SPAWN_INSTANCE_TYPE_EXEC)
+ return NULL;
+
+ int pipe_stdin[2] = { -1, -1 }, pipe_stdout[2] = { -1, -1 }, pipe_stderr[2] = { -1, -1 };
+
+ errno_clear();
+
+ SPAWN_INSTANCE *instance = callocz(1, sizeof(*instance));
+ instance->request_id = __atomic_add_fetch(&server->request_id, 1, __ATOMIC_RELAXED);
+
+ CLEAN_BUFFER *wb = argv_to_windows(argv);
+ char *command = (char *)buffer_tostring(wb);
+
+ if (pipe(pipe_stdin) == -1) {
+ nd_log(NDLS_COLLECTORS, NDLP_ERR,
+ "SPAWN PARENT: Cannot create stdin pipe() for request No %zu, command: %s",
+ instance->request_id, command);
+ goto cleanup;
+ }
+
+ if (pipe(pipe_stdout) == -1) {
+ nd_log(NDLS_COLLECTORS, NDLP_ERR,
+ "SPAWN PARENT: Cannot create stdout pipe() for request No %zu, command: %s",
+ instance->request_id, command);
+ goto cleanup;
+ }
+
+ if (pipe(pipe_stderr) == -1) {
+ nd_log(NDLS_COLLECTORS, NDLP_ERR,
+ "SPAWN PARENT: Cannot create stderr pipe() for request No %zu, command: %s",
+ instance->request_id, command);
+ goto cleanup;
+ }
+
+ // Ensure pipes are in blocking mode
+ if (set_fd_blocking(pipe_stdin[PIPE_READ]) == -1 || set_fd_blocking(pipe_stdin[PIPE_WRITE]) == -1 ||
+ set_fd_blocking(pipe_stdout[PIPE_READ]) == -1 || set_fd_blocking(pipe_stdout[PIPE_WRITE]) == -1 ||
+ set_fd_blocking(pipe_stderr[PIPE_READ]) == -1 || set_fd_blocking(pipe_stderr[PIPE_WRITE]) == -1) {
+ nd_log(NDLS_COLLECTORS, NDLP_ERR,
+ "SPAWN PARENT: Failed to set blocking I/O on pipes for request No %zu, command: %s",
+ instance->request_id, command);
+ goto cleanup;
+ }
+
+ // do not run multiple times this section
+ // to prevent handles leaking
+ spinlock_lock(&spinlock);
+
+ // Convert POSIX file descriptors to Windows handles
+ HANDLE stdin_read_handle = (HANDLE)_get_osfhandle(pipe_stdin[PIPE_READ]);
+ HANDLE stdout_write_handle = (HANDLE)_get_osfhandle(pipe_stdout[PIPE_WRITE]);
+ HANDLE stderr_write_handle = (HANDLE)_get_osfhandle(pipe_stderr[PIPE_WRITE]);
+
+ if (stdin_read_handle == INVALID_HANDLE_VALUE || stdout_write_handle == INVALID_HANDLE_VALUE || stderr_write_handle == INVALID_HANDLE_VALUE) {
+ spinlock_unlock(&spinlock);
+ nd_log(NDLS_COLLECTORS, NDLP_ERR,
+ "SPAWN PARENT: Invalid handle value(s) for request No %zu, command: %s",
+ instance->request_id, command);
+ goto cleanup;
+ }
+
+ // Set handle inheritance
+ if (!SetHandleInformation(stdin_read_handle, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT) ||
+ !SetHandleInformation(stdout_write_handle, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT) ||
+ !SetHandleInformation(stderr_write_handle, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT)) {
+ spinlock_unlock(&spinlock);
+ nd_log(NDLS_COLLECTORS, NDLP_ERR,
+ "SPAWN PARENT: Cannot set handle(s) inheritance for request No %zu, command: %s",
+ instance->request_id, command);
+ goto cleanup;
+ }
+
+ // Set up the STARTUPINFO structure
+ STARTUPINFO si;
+ PROCESS_INFORMATION pi;
+ ZeroMemory(&si, sizeof(si));
+ si.cb = sizeof(si);
+ si.dwFlags = STARTF_USESTDHANDLES;
+ si.hStdInput = stdin_read_handle;
+ si.hStdOutput = stdout_write_handle;
+ si.hStdError = stderr_write_handle;
+
+ // Retrieve the current environment block
+ char* env_block = GetEnvironmentStrings();
+// print_environment_block(env_block);
+
+ nd_log(NDLS_COLLECTORS, NDLP_INFO,
+ "SPAWN PARENT: Running request No %zu, command: '%s'",
+ instance->request_id, command);
+
+ int fds_to_keep_open[] = { pipe_stdin[PIPE_READ], pipe_stdout[PIPE_WRITE], pipe_stderr[PIPE_WRITE] };
+ os_close_all_non_std_open_fds_except(fds_to_keep_open, 3, CLOSE_RANGE_CLOEXEC);
+
+ // Spawn the process
+ errno_clear();
+ if (!CreateProcess(NULL, command, NULL, NULL, TRUE, 0, env_block, NULL, &si, &pi)) {
+ spinlock_unlock(&spinlock);
+ nd_log(NDLS_COLLECTORS, NDLP_ERR,
+ "SPAWN PARENT: cannot CreateProcess() for request No %zu, command: %s",
+ instance->request_id, command);
+ goto cleanup;
+ }
+
+ FreeEnvironmentStrings(env_block);
+
+ // When we create a process with the CreateProcess function, it returns two handles:
+ // - one for the process (pi.hProcess) and
+ // - one for the primary thread of the new process (pi.hThread).
+ // Both of these handles need to be explicitly closed when they are no longer needed.
+ CloseHandle(pi.hThread);
+
+ // end of the critical section
+ spinlock_unlock(&spinlock);
+
+ // Close unused pipe ends
+ close(pipe_stdin[PIPE_READ]); pipe_stdin[PIPE_READ] = -1;
+ close(pipe_stdout[PIPE_WRITE]); pipe_stdout[PIPE_WRITE] = -1;
+ close(pipe_stderr[PIPE_WRITE]); pipe_stderr[PIPE_WRITE] = -1;
+
+ // Store process information in instance
+ instance->dwProcessId = pi.dwProcessId;
+ instance->child_pid = cygwin_winpid_to_pid((pid_t)pi.dwProcessId);
+ instance->process_handle = pi.hProcess;
+
+ // Convert handles to POSIX file descriptors
+ instance->write_fd = pipe_stdin[PIPE_WRITE];
+ instance->read_fd = pipe_stdout[PIPE_READ];
+ instance->stderr_fd = pipe_stderr[PIPE_READ];
+
+ // Add stderr_fd to the log forwarder
+ log_forwarder_add_fd(server->log_forwarder, instance->stderr_fd);
+ log_forwarder_annotate_fd_name(server->log_forwarder, instance->stderr_fd, command);
+ log_forwarder_annotate_fd_pid(server->log_forwarder, instance->stderr_fd, spawn_server_instance_pid(instance));
+
+ errno_clear();
+ nd_log(NDLS_COLLECTORS, NDLP_INFO,
+ "SPAWN PARENT: created process for request No %zu, pid %d (winpid %d), command: %s",
+ instance->request_id, (int)instance->child_pid, (int)pi.dwProcessId, command);
+
+ return instance;
+
+ cleanup:
+ if (pipe_stdin[PIPE_READ] >= 0) close(pipe_stdin[PIPE_READ]);
+ if (pipe_stdin[PIPE_WRITE] >= 0) close(pipe_stdin[PIPE_WRITE]);
+ if (pipe_stdout[PIPE_READ] >= 0) close(pipe_stdout[PIPE_READ]);
+ if (pipe_stdout[PIPE_WRITE] >= 0) close(pipe_stdout[PIPE_WRITE]);
+ if (pipe_stderr[PIPE_READ] >= 0) close(pipe_stderr[PIPE_READ]);
+ if (pipe_stderr[PIPE_WRITE] >= 0) close(pipe_stderr[PIPE_WRITE]);
+ freez(instance);
+ return NULL;
+}
+
+static char* GetErrorString(DWORD errorCode) {
+ DWORD lastError = GetLastError();
+
+ LPVOID lpMsgBuf;
+ DWORD bufLen = FormatMessage(
+ FORMAT_MESSAGE_ALLOCATE_BUFFER |
+ FORMAT_MESSAGE_FROM_SYSTEM |
+ FORMAT_MESSAGE_IGNORE_INSERTS,
+ NULL,
+ errorCode,
+ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ (LPTSTR) &lpMsgBuf,
+ 0, NULL );
+
+ SetLastError(lastError);
+
+ if (bufLen) {
+ char* errorString = (char*)LocalAlloc(LMEM_FIXED, bufLen + 1);
+ if (errorString) {
+ strcpy(errorString, (char*)lpMsgBuf);
+ }
+ LocalFree(lpMsgBuf);
+ return errorString;
+ }
+
+ return NULL;
+}
+
+static void TerminateChildProcesses(SPAWN_INSTANCE *si) {
+ HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
+ if (hSnapshot == INVALID_HANDLE_VALUE)
+ return;
+
+ PROCESSENTRY32 pe;
+ pe.dwSize = sizeof(PROCESSENTRY32);
+
+ if (Process32First(hSnapshot, &pe)) {
+ do {
+ if (pe.th32ParentProcessID == si->dwProcessId) {
+ HANDLE hChildProcess = OpenProcess(PROCESS_TERMINATE, FALSE, pe.th32ProcessID);
+ if (hChildProcess) {
+ nd_log(NDLS_COLLECTORS, NDLP_WARNING,
+ "SPAWN PARENT: killing subprocess %u of request No %zu, pid %d (winpid %u)",
+ pe.th32ProcessID, si->request_id, (int)si->child_pid, si->dwProcessId);
+
+ TerminateProcess(hChildProcess, STATUS_CONTROL_C_EXIT);
+ CloseHandle(hChildProcess);
+ }
+ }
+ } while (Process32Next(hSnapshot, &pe));
+ }
+
+ CloseHandle(hSnapshot);
+}
+
+int map_status_code_to_signal(DWORD status_code) {
+ switch (status_code) {
+ case STATUS_ACCESS_VIOLATION:
+ return SIGSEGV;
+ case STATUS_ILLEGAL_INSTRUCTION:
+ return SIGILL;
+ case STATUS_FLOAT_DIVIDE_BY_ZERO:
+ case STATUS_INTEGER_DIVIDE_BY_ZERO:
+ case STATUS_ARRAY_BOUNDS_EXCEEDED:
+ case STATUS_FLOAT_OVERFLOW:
+ case STATUS_FLOAT_UNDERFLOW:
+ case STATUS_FLOAT_INVALID_OPERATION:
+ return SIGFPE;
+ case STATUS_BREAKPOINT:
+ case STATUS_SINGLE_STEP:
+ return SIGTRAP;
+ case STATUS_STACK_OVERFLOW:
+ case STATUS_INVALID_HANDLE:
+ case STATUS_INVALID_PARAMETER:
+ case STATUS_NO_MEMORY:
+ case STATUS_PRIVILEGED_INSTRUCTION:
+ case STATUS_DLL_NOT_FOUND:
+ case STATUS_DLL_INIT_FAILED:
+ case STATUS_ORDINAL_NOT_FOUND:
+ case STATUS_ENTRYPOINT_NOT_FOUND:
+ case STATUS_CONTROL_STACK_VIOLATION:
+ case STATUS_STACK_BUFFER_OVERRUN:
+ case STATUS_ASSERTION_FAILURE:
+ case STATUS_INVALID_CRUNTIME_PARAMETER:
+ case STATUS_HEAP_CORRUPTION:
+ return SIGABRT;
+ case STATUS_CONTROL_C_EXIT:
+ return SIGTERM; // we use this internally as such
+ case STATUS_FATAL_APP_EXIT:
+ return SIGTERM;
+ default:
+ return (status_code & 0xFF) << 8;
+ }
+}
+
+int spawn_server_exec_kill(SPAWN_SERVER *server __maybe_unused, SPAWN_INSTANCE *si) {
+ if(si->child_pid != -1 && kill(si->child_pid, SIGTERM) != 0)
+ nd_log(NDLS_COLLECTORS, NDLP_ERR,
+ "SPAWN PARENT: child of request No %zu, pid %d (winpid %u), failed to be killed",
+ si->request_id, (int)si->child_pid, si->dwProcessId);
+
+ // this gives some warnings at the spawn-tester, but it is generally better
+ // to have them, to avoid abnormal shutdown of the plugins
+ if(si->read_fd != -1) { close(si->read_fd); si->read_fd = -1; }
+ if(si->write_fd != -1) { close(si->write_fd); si->write_fd = -1; }
+ if(si->stderr_fd != -1) {
+ if(!log_forwarder_del_and_close_fd(server->log_forwarder, si->stderr_fd))
+ close(si->stderr_fd);
+
+ si->stderr_fd = -1;
+ }
+
+ errno_clear();
+ if(TerminateProcess(si->process_handle, STATUS_CONTROL_C_EXIT) == 0)
+ nd_log(NDLS_COLLECTORS, NDLP_ERR,
+ "SPAWN PARENT: child of request No %zu, pid %d (winpid %u), failed to be terminated",
+ si->request_id, (int)si->child_pid, si->dwProcessId);
+
+ errno_clear();
+ TerminateChildProcesses(si);
+
+ return spawn_server_exec_wait(server, si);
+}
+
+int spawn_server_exec_wait(SPAWN_SERVER *server __maybe_unused, SPAWN_INSTANCE *si) {
+ if(si->read_fd != -1) { close(si->read_fd); si->read_fd = -1; }
+ if(si->write_fd != -1) { close(si->write_fd); si->write_fd = -1; }
+ if(si->stderr_fd != -1) {
+ if(!log_forwarder_del_and_close_fd(server->log_forwarder, si->stderr_fd))
+ close(si->stderr_fd);
+
+ si->stderr_fd = -1;
+ }
+
+ // wait for the process to end
+ WaitForSingleObject(si->process_handle, INFINITE);
+
+ DWORD exit_code = -1;
+ GetExitCodeProcess(si->process_handle, &exit_code);
+ CloseHandle(si->process_handle);
+
+ char *err = GetErrorString(exit_code);
+
+ nd_log(NDLS_COLLECTORS, NDLP_INFO,
+ "SPAWN PARENT: child of request No %zu, pid %d (winpid %u), exited with code %u (0x%x): %s",
+ si->request_id, (int)si->child_pid, si->dwProcessId,
+ (unsigned)exit_code, (unsigned)exit_code, err ? err : "(no reason text)");
+
+ if(err)
+ LocalFree(err);
+
+ freez(si);
+ return map_status_code_to_signal(exit_code);
+}
+
+#endif