diff options
Diffstat (limited to 'src/libnetdata/spawn_server')
-rw-r--r-- | src/libnetdata/spawn_server/log-forwarder.c | 322 | ||||
-rw-r--r-- | src/libnetdata/spawn_server/log-forwarder.h | 17 | ||||
-rw-r--r-- | src/libnetdata/spawn_server/spawn-tester.c | 493 | ||||
-rw-r--r-- | src/libnetdata/spawn_server/spawn_library.c | 51 | ||||
-rw-r--r-- | src/libnetdata/spawn_server/spawn_library.h | 10 | ||||
-rw-r--r-- | src/libnetdata/spawn_server/spawn_popen.c | 115 | ||||
-rw-r--r-- | src/libnetdata/spawn_server/spawn_popen.h | 12 | ||||
-rw-r--r-- | src/libnetdata/spawn_server/spawn_server.h | 15 | ||||
-rw-r--r-- | src/libnetdata/spawn_server/spawn_server_internals.h | 90 | ||||
-rw-r--r-- | src/libnetdata/spawn_server/spawn_server_libuv.c | 395 | ||||
-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.c | 299 | ||||
-rw-r--r-- | src/libnetdata/spawn_server/spawn_server_windows.c | 456 |
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 |