diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-08-26 08:15:24 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-08-26 08:15:35 +0000 |
commit | f09848204fa5283d21ea43e262ee41aa578e1808 (patch) | |
tree | c62385d7adf209fa6a798635954d887f718fb3fb /src/libnetdata | |
parent | Releasing debian version 1.46.3-2. (diff) | |
download | netdata-f09848204fa5283d21ea43e262ee41aa578e1808.tar.xz netdata-f09848204fa5283d21ea43e262ee41aa578e1808.zip |
Merging upstream version 1.47.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
33 files changed, 2815 insertions, 1046 deletions
diff --git a/src/libnetdata/clocks/clocks.c b/src/libnetdata/clocks/clocks.c index e1a3e64c..5da450a2 100644 --- a/src/libnetdata/clocks/clocks.c +++ b/src/libnetdata/clocks/clocks.c @@ -343,7 +343,7 @@ usec_t heartbeat_next(heartbeat_t *hb, usec_t tick) { } if(unlikely(now < next)) { - errno = 0; + errno_clear(); nd_log_limit_static_global_var(erl, 10, 0); nd_log_limit(&erl, NDLS_DAEMON, NDLP_NOTICE, "heartbeat clock: woke up %"PRIu64" microseconds earlier than expected " @@ -351,7 +351,7 @@ usec_t heartbeat_next(heartbeat_t *hb, usec_t tick) { next - now); } else if(unlikely(now - next > tick / 2)) { - errno = 0; + errno_clear(); nd_log_limit_static_global_var(erl, 10, 0); nd_log_limit(&erl, NDLS_DAEMON, NDLP_NOTICE, "heartbeat clock: woke up %"PRIu64" microseconds later than expected " @@ -368,6 +368,35 @@ usec_t heartbeat_next(heartbeat_t *hb, usec_t tick) { return dt; } +#ifdef OS_WINDOWS + +#include "windows.h" + +void sleep_usec_with_now(usec_t usec, usec_t started_ut) +{ + if (!started_ut) + started_ut = now_realtime_usec(); + + usec_t end_ut = started_ut + usec; + usec_t remaining_ut = usec; + + timeBeginPeriod(1); + + while (remaining_ut >= 1000) + { + DWORD sleep_ms = (DWORD) (remaining_ut / USEC_PER_MS); + Sleep(sleep_ms); + + usec_t now_ut = now_realtime_usec(); + if (now_ut >= end_ut) + break; + + remaining_ut = end_ut - now_ut; + } + + timeEndPeriod(1); +} +#else void sleep_usec_with_now(usec_t usec, usec_t started_ut) { // we expect microseconds (1.000.000 per second) // but timespec is nanoseconds (1.000.000.000 per second) @@ -411,6 +440,7 @@ void sleep_usec_with_now(usec_t usec, usec_t started_ut) { } } } +#endif static inline collected_number uptime_from_boottime(void) { #ifdef CLOCK_BOOTTIME_IS_AVAILABLE diff --git a/src/libnetdata/json/json-c-parser-inline.h b/src/libnetdata/json/json-c-parser-inline.h index 543612a2..c1d60ca4 100644 --- a/src/libnetdata/json/json-c-parser-inline.h +++ b/src/libnetdata/json/json-c-parser-inline.h @@ -136,7 +136,7 @@ return false; \ } \ } else if(required) { \ - buffer_sprintf(error, "missing or invalid type (expected double value or null) for '%s.%s'", path, member); \ + buffer_sprintf(error, "missing or invalid type (expected int value or null) for '%s.%s'", path, member); \ return false; \ } \ } while(0) diff --git a/src/libnetdata/libnetdata.c b/src/libnetdata/libnetdata.c index 909bb71d..b36a139d 100644 --- a/src/libnetdata/libnetdata.c +++ b/src/libnetdata/libnetdata.c @@ -493,7 +493,7 @@ char *strndupz(const char *s, size_t len) { // If ptr is NULL, no operation is performed. void freez(void *ptr) { - free(ptr); + if(likely(ptr)) free(ptr); } void *mallocz(size_t size) { @@ -1248,7 +1248,7 @@ cleanup: close(fd); } if(mem == MAP_FAILED) return NULL; - errno = 0; + errno_clear(); return mem; } @@ -1364,7 +1364,7 @@ int verify_netdata_host_prefix(bool log_msg) { char buffer[FILENAME_MAX + 1]; char *path = netdata_configured_host_prefix; char *reason = "unknown reason"; - errno = 0; + errno_clear(); struct stat sb; if (stat(path, &sb) == -1) { @@ -1679,19 +1679,17 @@ char *find_and_replace(const char *src, const char *find, const char *replace, c return value; } - BUFFER *run_command_and_get_output_to_buffer(const char *command, int max_line_length) { BUFFER *wb = buffer_create(0, NULL); - pid_t pid; - FILE *fp = netdata_popen(command, &pid, NULL); - - if(fp) { + POPEN_INSTANCE *pi = spawn_popen_run(command); + if(pi) { char buffer[max_line_length + 1]; - while (fgets(buffer, max_line_length, fp)) { + while (fgets(buffer, max_line_length, pi->child_stdout_fp)) { buffer[max_line_length] = '\0'; buffer_strcat(wb, buffer); } + spawn_popen_kill(pi); } else { buffer_free(wb); @@ -1699,103 +1697,27 @@ BUFFER *run_command_and_get_output_to_buffer(const char *command, int max_line_l return NULL; } - netdata_pclose(NULL, fp, pid); return wb; } bool run_command_and_copy_output_to_stdout(const char *command, int max_line_length) { - pid_t pid; - FILE *fp = netdata_popen(command, &pid, NULL); - - if(fp) { + POPEN_INSTANCE *pi = spawn_popen_run(command); + if(pi) { char buffer[max_line_length + 1]; - while (fgets(buffer, max_line_length, fp)) + + while (fgets(buffer, max_line_length, pi->child_stdout_fp)) fprintf(stdout, "%s", buffer); + + spawn_popen_kill(pi); } else { netdata_log_error("Failed to execute command '%s'.", command); return false; } - netdata_pclose(NULL, fp, pid); return true; } - -static int fd_is_valid(int fd) { - return fcntl(fd, F_GETFD) != -1 || errno != EBADF; -} - -void for_each_open_fd(OPEN_FD_ACTION action, OPEN_FD_EXCLUDE excluded_fds){ - int fd; - - switch(action){ - case OPEN_FD_ACTION_CLOSE: - if(!(excluded_fds & OPEN_FD_EXCLUDE_STDIN)) (void)close(STDIN_FILENO); - if(!(excluded_fds & OPEN_FD_EXCLUDE_STDOUT)) (void)close(STDOUT_FILENO); - if(!(excluded_fds & OPEN_FD_EXCLUDE_STDERR)) (void)close(STDERR_FILENO); -#if defined(HAVE_CLOSE_RANGE) - if(close_range(STDERR_FILENO + 1, ~0U, 0) == 0) return; - nd_log(NDLS_DAEMON, NDLP_DEBUG, "close_range() failed, will try to close fds one by one"); -#endif - break; - case OPEN_FD_ACTION_FD_CLOEXEC: - if(!(excluded_fds & OPEN_FD_EXCLUDE_STDIN)) (void)fcntl(STDIN_FILENO, F_SETFD, FD_CLOEXEC); - if(!(excluded_fds & OPEN_FD_EXCLUDE_STDOUT)) (void)fcntl(STDOUT_FILENO, F_SETFD, FD_CLOEXEC); - if(!(excluded_fds & OPEN_FD_EXCLUDE_STDERR)) (void)fcntl(STDERR_FILENO, F_SETFD, FD_CLOEXEC); -#if defined(HAVE_CLOSE_RANGE) && defined(CLOSE_RANGE_CLOEXEC) // Linux >= 5.11, FreeBSD >= 13.1 - if(close_range(STDERR_FILENO + 1, ~0U, CLOSE_RANGE_CLOEXEC) == 0) return; - nd_log(NDLS_DAEMON, NDLP_DEBUG, "close_range() failed, will try to mark fds for closing one by one"); -#endif - break; - default: - break; // do nothing - } - - DIR *dir = opendir("/proc/self/fd"); - if (dir == NULL) { - struct rlimit rl; - int open_max = -1; - - if(getrlimit(RLIMIT_NOFILE, &rl) == 0 && rl.rlim_max != RLIM_INFINITY) open_max = rl.rlim_max; -#ifdef _SC_OPEN_MAX - else open_max = sysconf(_SC_OPEN_MAX); -#endif - - if (open_max == -1) open_max = 65535; // 65535 arbitrary default if everything else fails - - for (fd = STDERR_FILENO + 1; fd < open_max; fd++) { - switch(action){ - case OPEN_FD_ACTION_CLOSE: - if(fd_is_valid(fd)) (void)close(fd); - break; - case OPEN_FD_ACTION_FD_CLOEXEC: - (void)fcntl(fd, F_SETFD, FD_CLOEXEC); - break; - default: - break; // do nothing - } - } - } else { - struct dirent *entry; - while ((entry = readdir(dir)) != NULL) { - fd = str2i(entry->d_name); - if(unlikely((fd == STDIN_FILENO ) || (fd == STDOUT_FILENO) || (fd == STDERR_FILENO) )) continue; - switch(action){ - case OPEN_FD_ACTION_CLOSE: - if(fd_is_valid(fd)) (void)close(fd); - break; - case OPEN_FD_ACTION_FD_CLOEXEC: - (void)fcntl(fd, F_SETFD, FD_CLOEXEC); - break; - default: - break; // do nothing - } - } - closedir(dir); - } -} - struct timing_steps { const char *name; usec_t time; diff --git a/src/libnetdata/libnetdata.h b/src/libnetdata/libnetdata.h index 859f54cc..b4bddb70 100644 --- a/src/libnetdata/libnetdata.h +++ b/src/libnetdata/libnetdata.h @@ -326,6 +326,9 @@ size_t judy_aral_structures(void); #define GUID_LEN 36 +#define PIPE_READ 0 +#define PIPE_WRITE 1 + #include "linked-lists.h" #include "storage-point.h" @@ -425,7 +428,7 @@ char *find_and_replace(const char *src, const char *find, const char *replace, c #define UNUSED_FUNCTION(x) UNUSED_##x #endif -#define error_report(x, args...) do { errno = 0; netdata_log_error(x, ##args); } while(0) +#define error_report(x, args...) do { errno_clear(); netdata_log_error(x, ##args); } while(0) // Taken from linux kernel #define BUILD_BUG_ON(condition) ((void)sizeof(char[1 - 2*!!(condition)])) @@ -440,18 +443,12 @@ char *find_and_replace(const char *src, const char *find, const char *replace, c bool run_command_and_copy_output_to_stdout(const char *command, int max_line_length); struct web_buffer *run_command_and_get_output_to_buffer(const char *command, int max_line_length); -typedef enum { - OPEN_FD_ACTION_CLOSE, - OPEN_FD_ACTION_FD_CLOEXEC -} OPEN_FD_ACTION; -typedef enum { - OPEN_FD_EXCLUDE_STDIN = 0x01, - OPEN_FD_EXCLUDE_STDOUT = 0x02, - OPEN_FD_EXCLUDE_STDERR = 0x04 -} OPEN_FD_EXCLUDE; -void for_each_open_fd(OPEN_FD_ACTION action, OPEN_FD_EXCLUDE excluded_fds); - +#ifdef OS_WINDOWS +void netdata_cleanup_and_exit(int ret, const char *action, const char *action_result, const char *action_data); +#else void netdata_cleanup_and_exit(int ret, const char *action, const char *action_result, const char *action_data) NORETURN; +#endif + extern char *netdata_configured_host_prefix; #include "os/os.h" @@ -478,7 +475,9 @@ extern char *netdata_configured_host_prefix; #include "datetime/rfc3339.h" #include "datetime/rfc7231.h" #include "completion/completion.h" -#include "popen/popen.h" +#include "log/log.h" +#include "spawn_server/spawn_server.h" +#include "spawn_server/spawn_popen.h" #include "simple_pattern/simple_pattern.h" #ifdef ENABLE_HTTPS # include "socket/security.h" @@ -486,7 +485,6 @@ extern char *netdata_configured_host_prefix; #include "socket/socket.h" #include "config/appconfig.h" #include "log/journal.h" -#include "log/log.h" #include "buffered_reader/buffered_reader.h" #include "procfile/procfile.h" #include "string/string.h" diff --git a/src/libnetdata/locks/locks.c b/src/libnetdata/locks/locks.c index d01ee29f..424b86ce 100644 --- a/src/libnetdata/locks/locks.c +++ b/src/libnetdata/locks/locks.c @@ -224,14 +224,24 @@ int __netdata_rwlock_trywrlock(netdata_rwlock_t *rwlock) { // spinlock implementation // https://www.youtube.com/watch?v=rmGJc9PXpuE&t=41s -void spinlock_init(SPINLOCK *spinlock) { +#ifdef SPINLOCK_IMPL_WITH_MUTEX +void spinlock_init(SPINLOCK *spinlock) +{ + netdata_mutex_init(&spinlock->inner); +} +#else +void spinlock_init(SPINLOCK *spinlock) +{ memset(spinlock, 0, sizeof(SPINLOCK)); } +#endif -static inline void spinlock_lock_internal(SPINLOCK *spinlock) { -#ifdef NETDATA_INTERNAL_CHECKS +#ifndef SPINLOCK_IMPL_WITH_MUTEX +static inline void spinlock_lock_internal(SPINLOCK *spinlock) +{ + #ifdef NETDATA_INTERNAL_CHECKS size_t spins = 0; -#endif + #endif for(int i = 1; __atomic_load_n(&spinlock->locked, __ATOMIC_RELAXED) || @@ -239,9 +249,10 @@ static inline void spinlock_lock_internal(SPINLOCK *spinlock) { ; i++ ) { -#ifdef NETDATA_INTERNAL_CHECKS + #ifdef NETDATA_INTERNAL_CHECKS spins++; -#endif + #endif + if(unlikely(i == 8)) { i = 0; tinysleep(); @@ -250,23 +261,29 @@ static inline void spinlock_lock_internal(SPINLOCK *spinlock) { // we have the lock -#ifdef NETDATA_INTERNAL_CHECKS + #ifdef NETDATA_INTERNAL_CHECKS spinlock->spins += spins; spinlock->locker_pid = gettid_cached(); -#endif + #endif nd_thread_spinlock_locked(); } +#endif // SPINLOCK_IMPL_WITH_MUTEX -static inline void spinlock_unlock_internal(SPINLOCK *spinlock) { -#ifdef NETDATA_INTERNAL_CHECKS +#ifndef SPINLOCK_IMPL_WITH_MUTEX +static inline void spinlock_unlock_internal(SPINLOCK *spinlock) +{ + #ifdef NETDATA_INTERNAL_CHECKS spinlock->locker_pid = 0; -#endif + #endif + __atomic_clear(&spinlock->locked, __ATOMIC_RELEASE); nd_thread_spinlock_unlocked(); } +#endif // SPINLOCK_IMPL_WITH_MUTEX +#ifndef SPINLOCK_IMPL_WITH_MUTEX static inline bool spinlock_trylock_internal(SPINLOCK *spinlock) { if(!__atomic_load_n(&spinlock->locked, __ATOMIC_RELAXED) && !__atomic_test_and_set(&spinlock->locked, __ATOMIC_ACQUIRE)) { @@ -277,36 +294,79 @@ static inline bool spinlock_trylock_internal(SPINLOCK *spinlock) { return false; } +#endif // SPINLOCK_IMPL_WITH_MUTEX +#ifdef SPINLOCK_IMPL_WITH_MUTEX +void spinlock_lock(SPINLOCK *spinlock) +{ + netdata_mutex_lock(&spinlock->inner); +} +#else void spinlock_lock(SPINLOCK *spinlock) { spinlock_lock_internal(spinlock); } +#endif +#ifdef SPINLOCK_IMPL_WITH_MUTEX +void spinlock_unlock(SPINLOCK *spinlock) +{ + netdata_mutex_unlock(&spinlock->inner); +} +#else void spinlock_unlock(SPINLOCK *spinlock) { spinlock_unlock_internal(spinlock); } +#endif +#ifdef SPINLOCK_IMPL_WITH_MUTEX +bool spinlock_trylock(SPINLOCK *spinlock) +{ + return netdata_mutex_trylock(&spinlock->inner) == 0; +} +#else bool spinlock_trylock(SPINLOCK *spinlock) { return spinlock_trylock_internal(spinlock); } +#endif +#ifdef SPINLOCK_IMPL_WITH_MUTEX +void spinlock_lock_cancelable(SPINLOCK *spinlock) +{ + netdata_mutex_lock(&spinlock->inner); +} +#else void spinlock_lock_cancelable(SPINLOCK *spinlock) { spinlock_lock_internal(spinlock); } +#endif +#ifdef SPINLOCK_IMPL_WITH_MUTEX +void spinlock_unlock_cancelable(SPINLOCK *spinlock) +{ + netdata_mutex_unlock(&spinlock->inner); +} +#else void spinlock_unlock_cancelable(SPINLOCK *spinlock) { spinlock_unlock_internal(spinlock); } +#endif +#ifdef SPINLOCK_IMPL_WITH_MUTEX +bool spinlock_trylock_cancelable(SPINLOCK *spinlock) +{ + return netdata_mutex_trylock(&spinlock->inner) == 0; +} +#else bool spinlock_trylock_cancelable(SPINLOCK *spinlock) { return spinlock_trylock_internal(spinlock); } +#endif // ---------------------------------------------------------------------------- // rw_spinlock implementation diff --git a/src/libnetdata/locks/locks.h b/src/libnetdata/locks/locks.h index d3873c29..c05c65fe 100644 --- a/src/libnetdata/locks/locks.h +++ b/src/libnetdata/locks/locks.h @@ -6,19 +6,34 @@ #include "../libnetdata.h" #include "../clocks/clocks.h" +// #ifdef OS_WINDOWS +// #define SPINLOCK_IMPL_WITH_MUTEX +// #endif + typedef pthread_mutex_t netdata_mutex_t; #define NETDATA_MUTEX_INITIALIZER PTHREAD_MUTEX_INITIALIZER -typedef struct netdata_spinlock { - bool locked; -#ifdef NETDATA_INTERNAL_CHECKS - pid_t locker_pid; - size_t spins; +#ifdef SPINLOCK_IMPL_WITH_MUTEX + typedef struct netdata_spinlock + { + netdata_mutex_t inner; + } SPINLOCK; +#else + typedef struct netdata_spinlock + { + bool locked; + #ifdef NETDATA_INTERNAL_CHECKS + pid_t locker_pid; + size_t spins; + #endif + } SPINLOCK; #endif -} SPINLOCK; -#define NETDATA_SPINLOCK_INITIALIZER \ - { .locked = false } +#ifdef SPINLOCK_IMPL_WITH_MUTEX +#define NETDATA_SPINLOCK_INITIALIZER { .inner = PTHREAD_MUTEX_INITIALIZER } +#else +#define NETDATA_SPINLOCK_INITIALIZER { .locked = false } +#endif void spinlock_init(SPINLOCK *spinlock); void spinlock_lock(SPINLOCK *spinlock); diff --git a/src/libnetdata/log/log.c b/src/libnetdata/log/log.c index 501b6632..a31127c4 100644 --- a/src/libnetdata/log/log.c +++ b/src/libnetdata/log/log.c @@ -6,6 +6,10 @@ #include "../libnetdata.h" +#if defined(OS_WINDOWS) +#include <windows.h> +#endif + #ifdef __FreeBSD__ #include <sys/endian.h> #endif @@ -36,6 +40,16 @@ struct nd_log_source; static bool nd_log_limit_reached(struct nd_log_source *source); // ---------------------------------------------------------------------------- + +void errno_clear(void) { + errno = 0; + +#if defined(OS_WINDOWS) + SetLastError(ERROR_SUCCESS); +#endif +} + +// ---------------------------------------------------------------------------- // logging method typedef enum __attribute__((__packed__)) { @@ -514,6 +528,13 @@ int nd_log_health_fd(void) { return STDERR_FILENO; } +int nd_log_collectors_fd(void) { + if(nd_log.sources[NDLS_COLLECTORS].method == NDLM_FILE && nd_log.sources[NDLS_COLLECTORS].fd != -1) + return nd_log.sources[NDLS_COLLECTORS].fd; + + return STDERR_FILENO; +} + void nd_log_set_user_settings(ND_LOG_SOURCES source, const char *setting) { char buf[FILENAME_MAX + 100]; if(setting && *setting) @@ -971,14 +992,38 @@ void nd_log_initialize(void) { nd_log_open(&nd_log.sources[i], i); } -void nd_log_reopen_log_files(void) { - netdata_log_info("Reopening all log files."); +void nd_log_reopen_log_files(bool log) { + if(log) + netdata_log_info("Reopening all log files."); nd_log.std_output.initialized = false; nd_log.std_error.initialized = false; nd_log_initialize(); - netdata_log_info("Log files re-opened."); + if(log) + netdata_log_info("Log files re-opened."); +} + +void nd_log_reopen_log_files_for_spawn_server(void) { + if(nd_log.syslog.initialized) { + closelog(); + nd_log.syslog.initialized = false; + nd_log_syslog_init(); + } + + if(nd_log.journal_direct.initialized) { + close(nd_log.journal_direct.fd); + nd_log.journal_direct.fd = -1; + nd_log.journal_direct.initialized = false; + nd_log_journal_direct_init(NULL); + } + + nd_log.sources[NDLS_UNSET].method = NDLM_DISABLED; + nd_log.sources[NDLS_ACCESS].method = NDLM_DISABLED; + nd_log.sources[NDLS_ACLK].method = NDLM_DISABLED; + nd_log.sources[NDLS_DEBUG].method = NDLM_DISABLED; + nd_log.sources[NDLS_HEALTH].method = NDLM_DISABLED; + nd_log_reopen_log_files(false); } void chown_open_file(int fd, uid_t uid, gid_t gid) { @@ -1011,6 +1056,10 @@ static void errno_annotator(BUFFER *wb, const char *key, struct log_field *lf); static void priority_annotator(BUFFER *wb, const char *key, struct log_field *lf); static void timestamp_usec_annotator(BUFFER *wb, const char *key, struct log_field *lf); +#if defined(OS_WINDOWS) +static void winerror_annotator(BUFFER *wb, const char *key, struct log_field *lf); +#endif + // ---------------------------------------------------------------------------- typedef void (*annotator_t)(BUFFER *wb, const char *key, struct log_field *lf); @@ -1058,6 +1107,13 @@ static __thread struct log_field thread_log_fields[_NDF_MAX] = { .logfmt = "errno", .logfmt_annotator = errno_annotator, }, +#if defined(OS_WINDOWS) + [NDF_WINERROR] = { + .journal = "WINERROR", + .logfmt = "winerror", + .logfmt_annotator = winerror_annotator, + }, +#endif [NDF_INVOCATION_ID] = { .journal = "INVOCATION_ID", // standard journald field .logfmt = NULL, @@ -1563,6 +1619,45 @@ static void errno_annotator(BUFFER *wb, const char *key, struct log_field *lf) { buffer_fast_strcat(wb, "\"", 1); } +#if defined(OS_WINDOWS) +static void winerror_annotator(BUFFER *wb, const char *key, struct log_field *lf) { + DWORD errnum = log_field_to_uint64(lf); + + if(errnum == 0) + return; + + char buf[1024]; + DWORD size = FormatMessageA( + FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + errnum, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + buf, + (DWORD)(sizeof(buf) - 1), + NULL + ); + if(size > 0) { + // remove \r\n at the end + while(size > 0 && (buf[size - 1] == '\r' || buf[size - 1] == '\n')) + buf[--size] = '\0'; + } + else + size = snprintf(buf, sizeof(buf) - 1, "unknown error code"); + + buf[size] = '\0'; + + if(buffer_strlen(wb)) + buffer_fast_strcat(wb, " ", 1); + + buffer_strcat(wb, key); + buffer_fast_strcat(wb, "=\"", 2); + buffer_print_int64(wb, errnum); + buffer_fast_strcat(wb, ", ", 2); + buffer_json_strcat(wb, buf); + buffer_fast_strcat(wb, "\"", 1); +} +#endif + static void priority_annotator(BUFFER *wb, const char *key, struct log_field *lf) { uint64_t pri = log_field_to_uint64(lf); @@ -2099,8 +2194,8 @@ static void nd_logger_merge_log_stack_to_thread_fields(void) { } static void nd_logger(const char *file, const char *function, const unsigned long line, - ND_LOG_SOURCES source, ND_LOG_FIELD_PRIORITY priority, bool limit, int saved_errno, - const char *fmt, va_list ap) { + ND_LOG_SOURCES source, ND_LOG_FIELD_PRIORITY priority, bool limit, + int saved_errno, size_t saved_winerror __maybe_unused, const char *fmt, va_list ap) { SPINLOCK *spinlock; FILE *fp; @@ -2168,6 +2263,11 @@ static void nd_logger(const char *file, const char *function, const unsigned lon if(saved_errno != 0 && !thread_log_fields[NDF_ERRNO].entry.set) thread_log_fields[NDF_ERRNO].entry = ND_LOG_FIELD_I64(NDF_ERRNO, saved_errno); +#if defined(OS_WINDOWS) + if(saved_winerror != 0 && !thread_log_fields[NDF_WINERROR].entry.set) + thread_log_fields[NDF_WINERROR].entry = ND_LOG_FIELD_U64(NDF_WINERROR, saved_winerror); +#endif + CLEAN_BUFFER *wb = NULL; if(fmt && !thread_log_fields[NDF_MESSAGE].entry.set) { wb = buffer_create(1024, NULL); @@ -2215,7 +2315,7 @@ static void nd_logger(const char *file, const char *function, const unsigned lon nd_log.sources[source].pending_msg = NULL; } - errno = 0; + errno_clear(); } static ND_LOG_SOURCES nd_log_validate_source(ND_LOG_SOURCES source) { @@ -2234,6 +2334,12 @@ static ND_LOG_SOURCES nd_log_validate_source(ND_LOG_SOURCES source) { void netdata_logger(ND_LOG_SOURCES source, ND_LOG_FIELD_PRIORITY priority, const char *file, const char *function, unsigned long line, const char *fmt, ... ) { int saved_errno = errno; + + size_t saved_winerror = 0; +#if defined(OS_WINDOWS) + saved_winerror = GetLastError(); +#endif + source = nd_log_validate_source(source); if (source != NDLS_DEBUG && priority > nd_log.sources[source].min_priority) @@ -2243,12 +2349,18 @@ void netdata_logger(ND_LOG_SOURCES source, ND_LOG_FIELD_PRIORITY priority, const va_start(args, fmt); nd_logger(file, function, line, source, priority, source == NDLS_DAEMON || source == NDLS_COLLECTORS, - saved_errno, fmt, args); + saved_errno, saved_winerror, fmt, args); va_end(args); } void netdata_logger_with_limit(ERROR_LIMIT *erl, ND_LOG_SOURCES source, ND_LOG_FIELD_PRIORITY priority, const char *file __maybe_unused, const char *function __maybe_unused, const unsigned long line __maybe_unused, const char *fmt, ... ) { int saved_errno = errno; + + size_t saved_winerror = 0; +#if defined(OS_WINDOWS) + saved_winerror = GetLastError(); +#endif + source = nd_log_validate_source(source); if (source != NDLS_DEBUG && priority > nd_log.sources[source].min_priority) @@ -2272,7 +2384,7 @@ void netdata_logger_with_limit(ERROR_LIMIT *erl, ND_LOG_SOURCES source, ND_LOG_F va_start(args, fmt); nd_logger(file, function, line, source, priority, source == NDLS_DAEMON || source == NDLS_COLLECTORS, - saved_errno, fmt, args); + saved_errno, saved_winerror, fmt, args); va_end(args); erl->last_logged = now; erl->count = 0; @@ -2280,12 +2392,18 @@ void netdata_logger_with_limit(ERROR_LIMIT *erl, ND_LOG_SOURCES source, ND_LOG_F void netdata_logger_fatal( const char *file, const char *function, const unsigned long line, const char *fmt, ... ) { int saved_errno = errno; + + size_t saved_winerror = 0; +#if defined(OS_WINDOWS) + saved_winerror = GetLastError(); +#endif + ND_LOG_SOURCES source = NDLS_DAEMON; source = nd_log_validate_source(source); va_list args; va_start(args, fmt); - nd_logger(file, function, line, source, NDLP_ALERT, true, saved_errno, fmt, args); + nd_logger(file, function, line, source, NDLP_ALERT, true, saved_errno, saved_winerror, fmt, args); va_end(args); char date[LOG_DATE_LENGTH]; diff --git a/src/libnetdata/log/log.h b/src/libnetdata/log/log.h index 338a5d53..015c02eb 100644 --- a/src/libnetdata/log/log.h +++ b/src/libnetdata/log/log.h @@ -46,6 +46,9 @@ typedef enum __attribute__((__packed__)) { NDF_LOG_SOURCE, // DAEMON, COLLECTORS, HEALTH, ACCESS, ACLK - set at the log call NDF_PRIORITY, // the syslog priority (severity) - set at the log call NDF_ERRNO, // the ERRNO at the time of the log call - added automatically +#if defined(OS_WINDOWS) + NDF_WINERROR, // Windows GetLastError() +#endif NDF_INVOCATION_ID, // the INVOCATION_ID of Netdata - added automatically NDF_LINE, // the source code file line number - added automatically NDF_FILE, // the source code filename - added automatically @@ -141,15 +144,17 @@ typedef enum __attribute__((__packed__)) { NDFT_CALLBACK, } ND_LOG_STACK_FIELD_TYPE; +void errno_clear(void); void nd_log_set_user_settings(ND_LOG_SOURCES source, const char *setting); void nd_log_set_facility(const char *facility); void nd_log_set_priority_level(const char *setting); void nd_log_initialize(void); -void nd_log_reopen_log_files(void); +void nd_log_reopen_log_files(bool log); void chown_open_file(int fd, uid_t uid, gid_t gid); void nd_log_chown_log_files(uid_t uid, gid_t gid); void nd_log_set_flood_protection(size_t logs, time_t period); void nd_log_initialize_for_external_plugins(const char *name); +void nd_log_reopen_log_files_for_spawn_server(void); bool nd_log_journal_socket_available(void); ND_LOG_FIELD_ID nd_log_field_id_by_name(const char *field, size_t len); int nd_log_priority2id(const char *priority); @@ -157,6 +162,7 @@ const char *nd_log_id2priority(ND_LOG_FIELD_PRIORITY priority); const char *nd_log_method_for_external_plugins(const char *s); int nd_log_health_fd(void); +int nd_log_collectors_fd(void); typedef bool (*log_formatter_callback_t)(BUFFER *wb, void *data); struct log_stack_entry { diff --git a/src/libnetdata/maps/local-sockets.h b/src/libnetdata/maps/local-sockets.h index d407e6be..6f2ffd81 100644 --- a/src/libnetdata/maps/local-sockets.h +++ b/src/libnetdata/maps/local-sockets.h @@ -5,10 +5,8 @@ #include "libnetdata/libnetdata.h" -// disable libmnl for the moment -#undef HAVE_LIBMNL - #ifdef HAVE_LIBMNL +#include <linux/rtnetlink.h> #include <linux/inet_diag.h> #include <linux/sock_diag.h> #include <linux/unix_diag.h> @@ -67,30 +65,41 @@ struct local_port; struct local_socket_state; typedef void (*local_sockets_cb_t)(struct local_socket_state *state, struct local_socket *n, void *data); +struct local_sockets_config { + bool listening; + bool inbound; + bool outbound; + bool local; + bool tcp4; + bool tcp6; + bool udp4; + bool udp6; + bool pid; + bool cmdline; + bool comm; + bool uid; + bool namespaces; + bool tcp_info; + + size_t max_errors; + size_t max_concurrent_namespaces; + + local_sockets_cb_t cb; + void *data; + + const char *host_prefix; + + // internal use + uint64_t net_ns_inode; +}; + typedef struct local_socket_state { - struct { - bool listening; - bool inbound; - bool outbound; - bool local; - bool tcp4; - bool tcp6; - bool udp4; - bool udp6; - bool pid; - bool cmdline; - bool comm; - bool uid; - bool namespaces; - size_t max_errors; - - local_sockets_cb_t cb; - void *data; - - const char *host_prefix; - } config; + struct local_sockets_config config; struct { + size_t mnl_sends; + size_t namespaces_found; + size_t tcp_info_received; size_t pid_fds_processed; size_t pid_fds_opendir_failed; size_t pid_fds_readlink_failed; @@ -98,6 +107,9 @@ typedef struct local_socket_state { size_t errors_encountered; } stats; + bool spawn_server_is_mine; + SPAWN_SERVER *spawn_server; + #ifdef HAVE_LIBMNL bool use_nl; struct mnl_socket *nl; @@ -106,6 +118,7 @@ typedef struct local_socket_state { ARAL *local_socket_aral; ARAL *pid_socket_aral; + SPINLOCK spinlock; // for namespaces uint64_t proc_self_net_ns_inode; @@ -181,12 +194,21 @@ typedef struct local_socket { SOCKET_DIRECTION direction; uint8_t timer; - uint8_t retransmits; + uint8_t retransmits; // the # of packets currently queued for retransmission (not yet acknowledged) uint32_t expires; uint32_t rqueue; uint32_t wqueue; uid_t uid; + struct { + bool checked; + bool ipv46; + } ipv6ony; + + union { + struct tcp_info tcp; + } info; + char comm[TASK_COMM_LEN]; STRING *cmdline; @@ -201,16 +223,18 @@ typedef struct local_socket { #endif } LOCAL_SOCKET; +static inline void local_sockets_spawn_server_callback(SPAWN_REQUEST *request); + // -------------------------------------------------------------------------------------------------------------------- static inline void local_sockets_log(LS_STATE *ls, const char *format, ...) PRINTFLIKE(2, 3); static inline void local_sockets_log(LS_STATE *ls, const char *format, ...) { - if(++ls->stats.errors_encountered == ls->config.max_errors) { + if(ls && ++ls->stats.errors_encountered == ls->config.max_errors) { nd_log(NDLS_COLLECTORS, NDLP_ERR, "LOCAL-SOCKETS: max number of logs reached. Not logging anymore"); return; } - if(ls->stats.errors_encountered > ls->config.max_errors) + if(ls && ls->stats.errors_encountered > ls->config.max_errors) return; char buf[16384]; @@ -224,6 +248,133 @@ static inline void local_sockets_log(LS_STATE *ls, const char *format, ...) { // -------------------------------------------------------------------------------------------------------------------- +static bool local_sockets_is_ipv4_mapped_ipv6_address(const struct in6_addr *addr) { + // An IPv4-mapped IPv6 address starts with 80 bits of zeros followed by 16 bits of ones + static const unsigned char ipv4_mapped_prefix[12] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF }; + return memcmp(addr->s6_addr, ipv4_mapped_prefix, 12) == 0; +} + +static bool local_sockets_is_loopback_address(struct socket_endpoint *se) { + if (se->family == AF_INET) { + // For IPv4, loopback addresses are in the 127.0.0.0/8 range + return (ntohl(se->ip.ipv4) >> 24) == 127; // Check if the first byte is 127 + } else if (se->family == AF_INET6) { + // Check if the address is an IPv4-mapped IPv6 address + if (local_sockets_is_ipv4_mapped_ipv6_address(&se->ip.ipv6)) { + // Extract the last 32 bits (IPv4 address) and check if it's in the 127.0.0.0/8 range + uint8_t *ip6 = (uint8_t *)&se->ip.ipv6; + const uint32_t ipv4_addr = *((const uint32_t *)(ip6 + 12)); + return (ntohl(ipv4_addr) >> 24) == 127; + } + + // For IPv6, loopback address is ::1 + return memcmp(&se->ip.ipv6, &in6addr_loopback, sizeof(se->ip.ipv6)) == 0; + } + + return false; +} + +static inline bool local_sockets_is_ipv4_reserved_address(uint32_t ip) { + // Check for the reserved address ranges + ip = ntohl(ip); + return ( + (ip >> 24 == 10) || // Private IP range (A class) + (ip >> 20 == (172 << 4) + 1) || // Private IP range (B class) + (ip >> 16 == (192 << 8) + 168) || // Private IP range (C class) + (ip >> 24 == 127) || // Loopback address (127.0.0.0) + (ip >> 24 == 0) || // Reserved (0.0.0.0) + (ip >> 24 == 169 && (ip >> 16) == 254) || // Link-local address (169.254.0.0) + (ip >> 16 == (192 << 8) + 0) // Test-Net (192.0.0.0) + ); +} + +static inline bool local_sockets_is_private_address(struct socket_endpoint *se) { + if (se->family == AF_INET) { + return local_sockets_is_ipv4_reserved_address(se->ip.ipv4); + } + else if (se->family == AF_INET6) { + uint8_t *ip6 = (uint8_t *)&se->ip.ipv6; + + // Check if the address is an IPv4-mapped IPv6 address + if (local_sockets_is_ipv4_mapped_ipv6_address(&se->ip.ipv6)) { + // Extract the last 32 bits (IPv4 address) and check if it's in the 127.0.0.0/8 range + const uint32_t ipv4_addr = *((const uint32_t *)(ip6 + 12)); + return local_sockets_is_ipv4_reserved_address(ipv4_addr); + } + + // Check for link-local addresses (fe80::/10) + if ((ip6[0] == 0xFE) && ((ip6[1] & 0xC0) == 0x80)) + return true; + + // Check for Unique Local Addresses (ULA) (fc00::/7) + if ((ip6[0] & 0xFE) == 0xFC) + return true; + + // Check for multicast addresses (ff00::/8) + if (ip6[0] == 0xFF) + return true; + + // For IPv6, loopback address is :: or ::1 + return memcmp(&se->ip.ipv6, &in6addr_any, sizeof(se->ip.ipv6)) == 0 || + memcmp(&se->ip.ipv6, &in6addr_loopback, sizeof(se->ip.ipv6)) == 0; + } + + return false; +} + +static bool local_sockets_is_multicast_address(struct socket_endpoint *se) { + if (se->family == AF_INET) { + // For IPv4, check if the address is 0.0.0.0 + uint32_t ip = htonl(se->ip.ipv4); + return (ip >= 0xE0000000 && ip <= 0xEFFFFFFF); // Multicast address range (224.0.0.0/4) + } + else if (se->family == AF_INET6) { + // For IPv6, check if the address is ff00::/8 + uint8_t *ip6 = (uint8_t *)&se->ip.ipv6; + return ip6[0] == 0xff; + } + + return false; +} + +static bool local_sockets_is_zero_address(struct socket_endpoint *se) { + if (se->family == AF_INET) { + // For IPv4, check if the address is 0.0.0.0 + return se->ip.ipv4 == 0; + } + else if (se->family == AF_INET6) { + // For IPv6, check if the address is :: + return memcmp(&se->ip.ipv6, &in6addr_any, sizeof(se->ip.ipv6)) == 0; + } + + return false; +} + +static inline const char *local_sockets_address_space(struct socket_endpoint *se) { + if(local_sockets_is_zero_address(se)) + return "zero"; + else if(local_sockets_is_loopback_address(se)) + return "loopback"; + else if(local_sockets_is_multicast_address(se)) + return "multicast"; + else if(local_sockets_is_private_address(se)) + return "private"; + else + return "public"; +} + +// -------------------------------------------------------------------------------------------------------------------- + +static inline bool is_local_socket_ipv46(LOCAL_SOCKET *n) { + return n->local.family == AF_INET6 && + n->direction == SOCKET_DIRECTION_LISTEN && + local_sockets_is_zero_address(&n->local) && + n->ipv6ony.checked && + n->ipv6ony.ipv46; +} + +// -------------------------------------------------------------------------------------------------------------------- + static void local_sockets_foreach_local_socket_call_cb(LS_STATE *ls) { for(SIMPLE_HASHTABLE_SLOT_LOCAL_SOCKET *sl = simple_hashtable_first_read_only_LOCAL_SOCKET(&ls->sockets_hashtable); sl; @@ -425,123 +576,6 @@ static inline bool local_sockets_find_all_sockets_in_proc(LS_STATE *ls, const ch // -------------------------------------------------------------------------------------------------------------------- -static bool local_sockets_is_ipv4_mapped_ipv6_address(const struct in6_addr *addr) { - // An IPv4-mapped IPv6 address starts with 80 bits of zeros followed by 16 bits of ones - static const unsigned char ipv4_mapped_prefix[12] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF }; - return memcmp(addr->s6_addr, ipv4_mapped_prefix, 12) == 0; -} - -static bool local_sockets_is_loopback_address(struct socket_endpoint *se) { - if (se->family == AF_INET) { - // For IPv4, loopback addresses are in the 127.0.0.0/8 range - return (ntohl(se->ip.ipv4) >> 24) == 127; // Check if the first byte is 127 - } else if (se->family == AF_INET6) { - // Check if the address is an IPv4-mapped IPv6 address - if (local_sockets_is_ipv4_mapped_ipv6_address(&se->ip.ipv6)) { - // Extract the last 32 bits (IPv4 address) and check if it's in the 127.0.0.0/8 range - uint8_t *ip6 = (uint8_t *)&se->ip.ipv6; - const uint32_t ipv4_addr = *((const uint32_t *)(ip6 + 12)); - return (ntohl(ipv4_addr) >> 24) == 127; - } - - // For IPv6, loopback address is ::1 - return memcmp(&se->ip.ipv6, &in6addr_loopback, sizeof(se->ip.ipv6)) == 0; - } - - return false; -} - -static inline bool local_sockets_is_ipv4_reserved_address(uint32_t ip) { - // Check for the reserved address ranges - ip = ntohl(ip); - return ( - (ip >> 24 == 10) || // Private IP range (A class) - (ip >> 20 == (172 << 4) + 1) || // Private IP range (B class) - (ip >> 16 == (192 << 8) + 168) || // Private IP range (C class) - (ip >> 24 == 127) || // Loopback address (127.0.0.0) - (ip >> 24 == 0) || // Reserved (0.0.0.0) - (ip >> 24 == 169 && (ip >> 16) == 254) || // Link-local address (169.254.0.0) - (ip >> 16 == (192 << 8) + 0) // Test-Net (192.0.0.0) - ); -} - -static inline bool local_sockets_is_private_address(struct socket_endpoint *se) { - if (se->family == AF_INET) { - return local_sockets_is_ipv4_reserved_address(se->ip.ipv4); - } - else if (se->family == AF_INET6) { - uint8_t *ip6 = (uint8_t *)&se->ip.ipv6; - - // Check if the address is an IPv4-mapped IPv6 address - if (local_sockets_is_ipv4_mapped_ipv6_address(&se->ip.ipv6)) { - // Extract the last 32 bits (IPv4 address) and check if it's in the 127.0.0.0/8 range - const uint32_t ipv4_addr = *((const uint32_t *)(ip6 + 12)); - return local_sockets_is_ipv4_reserved_address(ipv4_addr); - } - - // Check for link-local addresses (fe80::/10) - if ((ip6[0] == 0xFE) && ((ip6[1] & 0xC0) == 0x80)) - return true; - - // Check for Unique Local Addresses (ULA) (fc00::/7) - if ((ip6[0] & 0xFE) == 0xFC) - return true; - - // Check for multicast addresses (ff00::/8) - if (ip6[0] == 0xFF) - return true; - - // For IPv6, loopback address is :: or ::1 - return memcmp(&se->ip.ipv6, &in6addr_any, sizeof(se->ip.ipv6)) == 0 || - memcmp(&se->ip.ipv6, &in6addr_loopback, sizeof(se->ip.ipv6)) == 0; - } - - return false; -} - -static bool local_sockets_is_multicast_address(struct socket_endpoint *se) { - if (se->family == AF_INET) { - // For IPv4, check if the address is 0.0.0.0 - uint32_t ip = htonl(se->ip.ipv4); - return (ip >= 0xE0000000 && ip <= 0xEFFFFFFF); // Multicast address range (224.0.0.0/4) - } - else if (se->family == AF_INET6) { - // For IPv6, check if the address is ff00::/8 - uint8_t *ip6 = (uint8_t *)&se->ip.ipv6; - return ip6[0] == 0xff; - } - - return false; -} - -static bool local_sockets_is_zero_address(struct socket_endpoint *se) { - if (se->family == AF_INET) { - // For IPv4, check if the address is 0.0.0.0 - return se->ip.ipv4 == 0; - } - else if (se->family == AF_INET6) { - // For IPv6, check if the address is :: - return memcmp(&se->ip.ipv6, &in6addr_any, sizeof(se->ip.ipv6)) == 0; - } - - return false; -} - -static inline const char *local_sockets_address_space(struct socket_endpoint *se) { - if(local_sockets_is_zero_address(se)) - return "zero"; - else if(local_sockets_is_loopback_address(se)) - return "loopback"; - else if(local_sockets_is_multicast_address(se)) - return "multicast"; - else if(local_sockets_is_private_address(se)) - return "private"; - else - return "public"; -} - -// -------------------------------------------------------------------------------------------------------------------- - static inline void local_sockets_index_listening_port(LS_STATE *ls, LOCAL_SOCKET *n) { if(n->direction & SOCKET_DIRECTION_LISTEN) { // for the listening sockets, keep a hashtable with all the local ports @@ -636,28 +670,31 @@ static inline bool local_sockets_add_socket(LS_STATE *ls, LOCAL_SOCKET *tmp) { #ifdef HAVE_LIBMNL -static inline void local_sockets_netlink_init(LS_STATE *ls) { - ls->use_nl = true; +static inline void local_sockets_libmnl_init(LS_STATE *ls) { ls->nl = mnl_socket_open(NETLINK_INET_DIAG); - if (!ls->nl) { - local_sockets_log(ls, "cannot open netlink socket"); + if (ls->nl == NULL) { + local_sockets_log(ls, "cannot open libmnl netlink socket"); ls->use_nl = false; } - - if (mnl_socket_bind(ls->nl, 0, MNL_SOCKET_AUTOPID) < 0) { - local_sockets_log(ls, "cannot bind netlink socket"); + else if (mnl_socket_bind(ls->nl, 0, MNL_SOCKET_AUTOPID) < 0) { + local_sockets_log(ls, "cannot bind libmnl netlink socket"); + mnl_socket_close(ls->nl); + ls->nl = NULL; ls->use_nl = false; } + else + ls->use_nl = true; } -static inline void local_sockets_netlink_cleanup(LS_STATE *ls) { +static inline void local_sockets_libmnl_cleanup(LS_STATE *ls) { if(ls->nl) { mnl_socket_close(ls->nl); ls->nl = NULL; + ls->use_nl = false; } } -static inline int local_sockets_netlink_cb_data(const struct nlmsghdr *nlh, void *data) { +static inline int local_sockets_libmnl_cb_data(const struct nlmsghdr *nlh, void *data) { LS_STATE *ls = data; struct inet_diag_msg *diag_msg = mnl_nlmsg_get_payload(nlh); @@ -666,15 +703,19 @@ static inline int local_sockets_netlink_cb_data(const struct nlmsghdr *nlh, void .inode = diag_msg->idiag_inode, .direction = SOCKET_DIRECTION_NONE, .state = diag_msg->idiag_state, + .ipv6ony = { + .checked = false, + .ipv46 = false, + }, .local = { .protocol = ls->tmp_protocol, .family = diag_msg->idiag_family, - .port = diag_msg->id.idiag_sport, + .port = ntohs(diag_msg->id.idiag_sport), }, .remote = { .protocol = ls->tmp_protocol, .family = diag_msg->idiag_family, - .port = diag_msg->id.idiag_dport, + .port = ntohs(diag_msg->id.idiag_dport), }, .timer = diag_msg->idiag_timer, .retransmits = diag_msg->idiag_retrans, @@ -693,12 +734,37 @@ static inline int local_sockets_netlink_cb_data(const struct nlmsghdr *nlh, void memcpy(&n.remote.ip.ipv6, diag_msg->id.idiag_dst, sizeof(n.remote.ip.ipv6)); } + struct rtattr *attr = (struct rtattr *)(diag_msg + 1); + int rtattrlen = nlh->nlmsg_len - NLMSG_LENGTH(sizeof(*diag_msg)); + for (; !n.ipv6ony.checked && RTA_OK(attr, rtattrlen); attr = RTA_NEXT(attr, rtattrlen)) { + switch (attr->rta_type) { + case INET_DIAG_INFO: { + if(ls->tmp_protocol == IPPROTO_TCP) { + struct tcp_info *info = (struct tcp_info *)RTA_DATA(attr); + n.info.tcp = *info; + ls->stats.tcp_info_received++; + } + } + break; + + case INET_DIAG_SKV6ONLY: { + n.ipv6ony.checked = true; + int ipv6only = *(int *)RTA_DATA(attr); + n.ipv6ony.ipv46 = !ipv6only; + } + break; + + default: + break; + } + } + local_sockets_add_socket(ls, &n); return MNL_CB_OK; } -static inline bool local_sockets_netlink_get_sockets(LS_STATE *ls, uint16_t family, uint16_t protocol) { +static inline bool local_sockets_libmnl_get_sockets(LS_STATE *ls, uint16_t family, uint16_t protocol) { ls->tmp_protocol = protocol; char buf[MNL_SOCKET_BUFFER_SIZE]; @@ -710,14 +776,22 @@ static inline bool local_sockets_netlink_get_sockets(LS_STATE *ls, uint16_t fami req.sdiag_family = family; req.sdiag_protocol = protocol; req.idiag_states = -1; + req.idiag_ext = 0; + + if(family == AF_INET6) + req.idiag_ext |= 1 << (INET_DIAG_SKV6ONLY - 1); + + if(protocol == IPPROTO_TCP && ls->config.tcp_info) + req.idiag_ext |= 1 << (INET_DIAG_INFO - 1); nlh = mnl_nlmsg_put_header(buf); nlh->nlmsg_type = SOCK_DIAG_BY_FAMILY; - nlh->nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST; + nlh->nlmsg_flags = NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST; nlh->nlmsg_seq = seq = time(NULL); mnl_nlmsg_put_extra_header(nlh, sizeof(req)); memcpy(mnl_nlmsg_get_payload(nlh), &req, sizeof(req)); + ls->stats.mnl_sends++; if (mnl_socket_sendto(ls->nl, nlh, nlh->nlmsg_len) < 0) { local_sockets_log(ls, "mnl_socket_send failed"); return false; @@ -725,7 +799,7 @@ static inline bool local_sockets_netlink_get_sockets(LS_STATE *ls, uint16_t fami ssize_t ret; while ((ret = mnl_socket_recvfrom(ls->nl, buf, sizeof(buf))) > 0) { - ret = mnl_cb_run(buf, ret, seq, portid, local_sockets_netlink_cb_data, ls); + ret = mnl_cb_run(buf, ret, seq, portid, local_sockets_libmnl_cb_data, ls); if (ret <= MNL_CB_STOP) break; } @@ -774,6 +848,10 @@ static inline bool local_sockets_read_proc_net_x(LS_STATE *ls, const char *filen LOCAL_SOCKET n = { .direction = SOCKET_DIRECTION_NONE, + .ipv6ony = { + .checked = false, + .ipv46 = false, + }, .local = { .family = family, .protocol = protocol, @@ -904,6 +982,10 @@ static inline void local_sockets_detect_directions(LS_STATE *ls) { // -------------------------------------------------------------------------------------------------------------------- static inline void local_sockets_init(LS_STATE *ls) { + ls->config.host_prefix = netdata_configured_host_prefix; + + spinlock_init(&ls->spinlock); + simple_hashtable_init_NET_NS(&ls->ns_hashtable, 1024); simple_hashtable_init_PID_SOCKET(&ls->pid_sockets_hashtable, 65535); simple_hashtable_init_LOCAL_SOCKET(&ls->sockets_hashtable, 65535); @@ -923,9 +1005,36 @@ static inline void local_sockets_init(LS_STATE *ls) { 65536, 65536, NULL, NULL, NULL, false, true); + + memset(&ls->stats, 0, sizeof(ls->stats)); + +#ifdef HAVE_LIBMNL + ls->use_nl = false; + ls->nl = NULL; + ls->tmp_protocol = 0; + local_sockets_libmnl_init(ls); +#endif + + if(ls->config.namespaces && ls->spawn_server == NULL) { + ls->spawn_server = spawn_server_create(SPAWN_SERVER_OPTION_CALLBACK, NULL, local_sockets_spawn_server_callback, 0, NULL); + ls->spawn_server_is_mine = true; + } + else + ls->spawn_server_is_mine = false; } static inline void local_sockets_cleanup(LS_STATE *ls) { + + if(ls->spawn_server_is_mine) { + spawn_server_destroy(ls->spawn_server); + ls->spawn_server = NULL; + ls->spawn_server_is_mine = false; + } + +#ifdef HAVE_LIBMNL + local_sockets_libmnl_cleanup(ls); +#endif + // free the sockets hashtable data for(SIMPLE_HASHTABLE_SLOT_LOCAL_SOCKET *sl = simple_hashtable_first_read_only_LOCAL_SOCKET(&ls->sockets_hashtable); sl; @@ -963,8 +1072,8 @@ static inline void local_sockets_cleanup(LS_STATE *ls) { static inline void local_sockets_do_family_protocol(LS_STATE *ls, const char *filename, uint16_t family, uint16_t protocol) { #ifdef HAVE_LIBMNL - if(ls->use_nl) { - ls->use_nl = local_sockets_netlink_get_sockets(ls, family, protocol); + if(ls->nl && ls->use_nl) { + ls->use_nl = local_sockets_libmnl_get_sockets(ls, family, protocol); if(ls->use_nl) return; @@ -974,7 +1083,7 @@ static inline void local_sockets_do_family_protocol(LS_STATE *ls, const char *fi local_sockets_read_proc_net_x(ls, filename, family, protocol); } -static inline void local_sockets_read_sockets_from_proc(LS_STATE *ls) { +static inline void local_sockets_read_all_system_sockets(LS_STATE *ls) { char path[FILENAME_MAX + 1]; if(ls->config.namespaces) { @@ -1036,7 +1145,52 @@ static inline void local_sockets_send_to_parent(struct local_socket_state *ls __ local_sockets_log(ls, "failed to write cmdline to pipe"); } -static inline bool local_sockets_get_namespace_sockets(LS_STATE *ls, struct pid_socket *ps, pid_t *pid) { +static inline void local_sockets_spawn_server_callback(SPAWN_REQUEST *request) { + LS_STATE ls = { 0 }; + ls.config = *((struct local_sockets_config *)request->data); + + // we don't need these inside namespaces + ls.config.cmdline = false; + ls.config.comm = false; + ls.config.pid = false; + ls.config.namespaces = false; + + // initialize local sockets + local_sockets_init(&ls); + + ls.config.host_prefix = ""; // we need the /proc of the container + + struct local_sockets_child_work cw = { + .net_ns_inode = ls.proc_self_net_ns_inode, + .fd = request->fds[1], // stdout + }; + + ls.config.cb = local_sockets_send_to_parent; + ls.config.data = &cw; + ls.proc_self_net_ns_inode = ls.config.net_ns_inode; + + // switch namespace using the custom fd passed via the spawn server + if (setns(request->fds[3], CLONE_NEWNET) == -1) { + local_sockets_log(&ls, "failed to switch network namespace at child process using fd %d", request->fds[3]); + exit(EXIT_FAILURE); + } + + // read all sockets from /proc + local_sockets_read_all_system_sockets(&ls); + + // send all sockets to parent + local_sockets_foreach_local_socket_call_cb(&ls); + + // send the terminating socket + struct local_socket zero = { + .net_ns_inode = ls.config.net_ns_inode, + }; + local_sockets_send_to_parent(&ls, &zero, &cw); + + exit(EXIT_SUCCESS); +} + +static inline bool local_sockets_get_namespace_sockets_with_pid(LS_STATE *ls, struct pid_socket *ps) { char filename[1024]; snprintfz(filename, sizeof(filename), "%s/proc/%d/ns/net", ls->config.host_prefix, ps->pid); @@ -1060,80 +1214,32 @@ static inline bool local_sockets_get_namespace_sockets(LS_STATE *ls, struct pid_ return false; } - int pipefd[2]; - if (pipe(pipefd) != 0) { - local_sockets_log(ls, "cannot create pipe"); + if(ls->spawn_server == NULL) { close(fd); + local_sockets_log(ls, "spawn server is not available"); return false; } - *pid = fork(); - if (*pid == 0) { - // Child process - close(pipefd[0]); - - // local_sockets_log(ls, "child is here for inode %"PRIu64" and namespace %"PRIu64, ps->inode, ps->net_ns_inode); - - struct local_sockets_child_work cw = { - .net_ns_inode = ps->net_ns_inode, - .fd = pipefd[1], - }; - - ls->config.host_prefix = ""; // we need the /proc of the container - ls->config.cb = local_sockets_send_to_parent; - ls->config.data = &cw; - ls->config.cmdline = false; // we have these already - ls->config.comm = false; // we have these already - ls->config.pid = false; // we have these already - ls->config.namespaces = false; - ls->proc_self_net_ns_inode = ps->net_ns_inode; - - - // switch namespace - if (setns(fd, CLONE_NEWNET) == -1) { - local_sockets_log(ls, "failed to switch network namespace at child process"); - exit(EXIT_FAILURE); - } - -#ifdef HAVE_LIBMNL - local_sockets_netlink_cleanup(ls); - local_sockets_netlink_init(ls); -#endif - - // read all sockets from /proc - local_sockets_read_sockets_from_proc(ls); - - // send all sockets to parent - local_sockets_foreach_local_socket_call_cb(ls); + struct local_sockets_config config = ls->config; + config.net_ns_inode = ps->net_ns_inode; + SPAWN_INSTANCE *si = spawn_server_exec(ls->spawn_server, STDERR_FILENO, fd, NULL, &config, sizeof(config), SPAWN_INSTANCE_TYPE_CALLBACK); + close(fd); fd = -1; - // send the terminating socket - struct local_socket zero = { - .net_ns_inode = ps->net_ns_inode, - }; - local_sockets_send_to_parent(ls, &zero, &cw); - -#ifdef HAVE_LIBMNL - local_sockets_netlink_cleanup(ls); -#endif - - close(pipefd[1]); // Close write end of pipe - exit(EXIT_SUCCESS); + if(si == NULL) { + local_sockets_log(ls, "cannot create spawn instance"); + return false; } - // parent - - close(fd); - close(pipefd[1]); size_t received = 0; struct local_socket buf; - while(read(pipefd[0], &buf, sizeof(buf)) == sizeof(buf)) { + while(read(spawn_server_instance_read_fd(si), &buf, sizeof(buf)) == sizeof(buf)) { size_t len = 0; - if(read(pipefd[0], &len, sizeof(len)) != sizeof(len)) + if(read(spawn_server_instance_read_fd(si), &len, sizeof(len)) != sizeof(len)) local_sockets_log(ls, "failed to read cmdline length from pipe"); if(len) { char cmdline[len + 1]; - if(read(pipefd[0], cmdline, len) != (ssize_t)len) + if(read(spawn_server_instance_read_fd(si), cmdline, len) != (ssize_t)len) local_sockets_log(ls, "failed to read cmdline from pipe"); else { cmdline[len] = '\0'; @@ -1153,15 +1259,15 @@ static inline bool local_sockets_get_namespace_sockets(LS_STATE *ls, struct pid_ break; } + spinlock_lock(&ls->spinlock); + SIMPLE_HASHTABLE_SLOT_LOCAL_SOCKET *sl = simple_hashtable_get_slot_LOCAL_SOCKET(&ls->sockets_hashtable, buf.inode, &buf, true); LOCAL_SOCKET *n = SIMPLE_HASHTABLE_SLOT_DATA(sl); if(n) { string_freez(buf.cmdline); - // local_sockets_log(ls, // "ns inode %" PRIu64" (comm: '%s', pid: %u, ns: %"PRIu64") already exists in hashtable (comm: '%s', pid: %u, ns: %"PRIu64") - ignoring duplicate", // buf.inode, buf.comm, buf.pid, buf.net_ns_inode, n->comm, n->pid, n->net_ns_inode); - continue; } else { n = aral_mallocz(ls->local_socket_aral); @@ -1170,75 +1276,109 @@ static inline bool local_sockets_get_namespace_sockets(LS_STATE *ls, struct pid_ local_sockets_index_listening_port(ls, n); } - } - close(pipefd[0]); + spinlock_unlock(&ls->spinlock); + } + spawn_server_exec_kill(ls->spawn_server, si); return received > 0; } -static inline void local_socket_waitpid(LS_STATE *ls, pid_t pid) { - if(!pid) return; +struct local_sockets_namespace_worker { + LS_STATE *ls; + uint64_t inode; +}; + +static inline void *local_sockets_get_namespace_sockets(void *arg) { + struct local_sockets_namespace_worker *data = arg; + LS_STATE *ls = data->ls; + const uint64_t inode = data->inode; + + spinlock_lock(&ls->spinlock); + + // find a pid_socket that has this namespace + for(SIMPLE_HASHTABLE_SLOT_PID_SOCKET *sl_pid = simple_hashtable_first_read_only_PID_SOCKET(&ls->pid_sockets_hashtable) ; + sl_pid ; + sl_pid = simple_hashtable_next_read_only_PID_SOCKET(&ls->pid_sockets_hashtable, sl_pid)) { + struct pid_socket *ps = SIMPLE_HASHTABLE_SLOT_DATA(sl_pid); + if(!ps || ps->net_ns_inode != inode) continue; - int status; - waitpid(pid, &status, 0); + // now we have a pid that has the same namespace inode - if (WIFEXITED(status) && WEXITSTATUS(status) != 0) - local_sockets_log(ls, "Child exited with status %d", WEXITSTATUS(status)); - else if (WIFSIGNALED(status)) - local_sockets_log(ls, "Child terminated by signal %d", WTERMSIG(status)); + spinlock_unlock(&ls->spinlock); + const bool worked = local_sockets_get_namespace_sockets_with_pid(ls, ps); + spinlock_lock(&ls->spinlock); + + if(worked) + break; + } + + spinlock_unlock(&ls->spinlock); + + return NULL; } static inline void local_sockets_namespaces(LS_STATE *ls) { - pid_t children[5] = { 0 }; - size_t last_child = 0; + size_t threads = ls->config.max_concurrent_namespaces; + if(threads == 0) threads = 5; + if(threads > 100) threads = 100; + + size_t last_thread = 0; + ND_THREAD *workers[threads]; + struct local_sockets_namespace_worker workers_data[threads]; + memset(workers, 0, sizeof(workers)); + memset(workers_data, 0, sizeof(workers_data)); + + spinlock_lock(&ls->spinlock); for(SIMPLE_HASHTABLE_SLOT_NET_NS *sl = simple_hashtable_first_read_only_NET_NS(&ls->ns_hashtable); sl; sl = simple_hashtable_next_read_only_NET_NS(&ls->ns_hashtable, sl)) { - uint64_t inode = (uint64_t)SIMPLE_HASHTABLE_SLOT_DATA(sl); + const uint64_t inode = (uint64_t)SIMPLE_HASHTABLE_SLOT_DATA(sl); if(inode == ls->proc_self_net_ns_inode) continue; - // find a pid_socket that has this namespace - for(SIMPLE_HASHTABLE_SLOT_PID_SOCKET *sl_pid = simple_hashtable_first_read_only_PID_SOCKET(&ls->pid_sockets_hashtable) ; - sl_pid ; - sl_pid = simple_hashtable_next_read_only_PID_SOCKET(&ls->pid_sockets_hashtable, sl_pid)) { - struct pid_socket *ps = SIMPLE_HASHTABLE_SLOT_DATA(sl_pid); - if(!ps || ps->net_ns_inode != inode) continue; + spinlock_unlock(&ls->spinlock); - if(++last_child >= 5) - last_child = 0; + ls->stats.namespaces_found++; - local_socket_waitpid(ls, children[last_child]); - children[last_child] = 0; + if(workers[last_thread] != NULL) { + if(++last_thread >= threads) + last_thread = 0; - // now we have a pid that has the same namespace inode - if(local_sockets_get_namespace_sockets(ls, ps, &children[last_child])) - break; + if(workers[last_thread]) { + nd_thread_join(workers[last_thread]); + workers[last_thread] = NULL; + } } + + workers_data[last_thread].ls = ls; + workers_data[last_thread].inode = inode; + workers[last_thread] = nd_thread_create( + "local-sockets-worker", NETDATA_THREAD_OPTION_JOINABLE, + local_sockets_get_namespace_sockets, &workers_data[last_thread]); + + spinlock_lock(&ls->spinlock); } - for(size_t i = 0; i < 5 ;i++) - local_socket_waitpid(ls, children[i]); + spinlock_unlock(&ls->spinlock); + + // wait all the threads running + for(size_t i = 0; i < threads ;i++) { + if(workers[i]) + nd_thread_join(workers[i]); + } } // -------------------------------------------------------------------------------------------------------------------- static inline void local_sockets_process(LS_STATE *ls) { - -#ifdef HAVE_LIBMNL - local_sockets_netlink_init(ls); -#endif - - ls->config.host_prefix = netdata_configured_host_prefix; - // initialize our hashtables local_sockets_init(ls); // read all sockets from /proc - local_sockets_read_sockets_from_proc(ls); + local_sockets_read_all_system_sockets(ls); // check all socket namespaces if(ls->config.namespaces) @@ -1253,10 +1393,6 @@ static inline void local_sockets_process(LS_STATE *ls) { // free all memory local_sockets_cleanup(ls); - -#ifdef HAVE_LIBMNL - local_sockets_netlink_cleanup(ls); -#endif } static inline void ipv6_address_to_txt(struct in6_addr *in6_addr, char *dst) { diff --git a/src/libnetdata/maps/system-services.h b/src/libnetdata/maps/system-services.h new file mode 100644 index 00000000..123f4f10 --- /dev/null +++ b/src/libnetdata/maps/system-services.h @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_SYSTEM_SERVICES_H +#define NETDATA_SYSTEM_SERVICES_H + +#include "libnetdata/libnetdata.h" +#include <netdb.h> + +// -------------------------------------------------------------------------------------------------------------------- +// hashtable for caching port and protocol to service name mappings +// key is the combination of protocol and port packed into a uint64_t, value is service name (STRING) + +#define SIMPLE_HASHTABLE_VALUE_TYPE STRING +#define SIMPLE_HASHTABLE_NAME _SERVICENAMES_CACHE +#include "libnetdata/simple_hashtable.h" + +typedef struct servicenames_cache { + SPINLOCK spinlock; + SIMPLE_HASHTABLE_SERVICENAMES_CACHE ht; +} SERVICENAMES_CACHE; + +static inline uint64_t system_servicenames_key(uint16_t port, uint16_t ipproto) { + return ((uint64_t)ipproto << 16) | (uint64_t)port; +} + +static inline const char *system_servicenames_ipproto2str(uint16_t ipproto) { + return (ipproto == IPPROTO_TCP) ? "tcp" : "udp"; +} + +static inline const char *static_portnames(uint16_t port, uint16_t ipproto) { + if(port == 19999 && ipproto == IPPROTO_TCP) + return "netdata"; + + if(port == 8125) + return "statsd"; + + return NULL; +} + +static inline STRING *system_servicenames_cache_lookup(SERVICENAMES_CACHE *sc, uint16_t port, uint16_t ipproto) { + uint64_t key = system_servicenames_key(port, ipproto); + spinlock_lock(&sc->spinlock); + + SIMPLE_HASHTABLE_SLOT_SERVICENAMES_CACHE *sl = simple_hashtable_get_slot_SERVICENAMES_CACHE(&sc->ht, key, &key, true); + STRING *s = SIMPLE_HASHTABLE_SLOT_DATA(sl); + if (!s) { + const char *st = static_portnames(port, ipproto); + if(st) { + s = string_strdupz(st); + } + else { + struct servent *se = getservbyport(htons(port), system_servicenames_ipproto2str(ipproto)); + + if (!se || !se->s_name) { + char name[50]; + snprintfz(name, sizeof(name), "%u/%s", port, system_servicenames_ipproto2str(ipproto)); + s = string_strdupz(name); + } + else + s = string_strdupz(se->s_name); + } + + simple_hashtable_set_slot_SERVICENAMES_CACHE(&sc->ht, sl, key, s); + } + + s = string_dup(s); + spinlock_unlock(&sc->spinlock); + return s; +} + +static inline SERVICENAMES_CACHE *system_servicenames_cache_init(void) { + SERVICENAMES_CACHE *sc = callocz(1, sizeof(*sc)); + spinlock_init(&sc->spinlock); + simple_hashtable_init_SERVICENAMES_CACHE(&sc->ht, 100); + return sc; +} + +static inline void system_servicenames_cache_destroy(SERVICENAMES_CACHE *sc) { + spinlock_lock(&sc->spinlock); + + for (SIMPLE_HASHTABLE_SLOT_SERVICENAMES_CACHE *sl = simple_hashtable_first_read_only_SERVICENAMES_CACHE(&sc->ht); + sl; + sl = simple_hashtable_next_read_only_SERVICENAMES_CACHE(&sc->ht, sl)) { + STRING *s = SIMPLE_HASHTABLE_SLOT_DATA(sl); + string_freez(s); + } + + simple_hashtable_destroy_SERVICENAMES_CACHE(&sc->ht); + freez(sc); +} + +#endif //NETDATA_SYSTEM_SERVICES_H diff --git a/src/libnetdata/os/close_range.c b/src/libnetdata/os/close_range.c new file mode 100644 index 00000000..56d5c252 --- /dev/null +++ b/src/libnetdata/os/close_range.c @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "../libnetdata.h" + +static int fd_is_valid(int fd) { + errno_clear(); + return fcntl(fd, F_GETFD) != -1 || errno != EBADF; +} + +int os_get_fd_open_max(void) { + static int fd_open_max = CLOSE_RANGE_FD_MAX; + + if(fd_open_max != CLOSE_RANGE_FD_MAX) + return fd_open_max; + + if(fd_open_max == CLOSE_RANGE_FD_MAX || fd_open_max == -1) { + struct rlimit rl; + if (getrlimit(RLIMIT_NOFILE, &rl) == 0 && rl.rlim_max != RLIM_INFINITY) + fd_open_max = rl.rlim_max; + } + +#ifdef _SC_OPEN_MAX + if(fd_open_max == CLOSE_RANGE_FD_MAX || fd_open_max == -1) { + fd_open_max = sysconf(_SC_OPEN_MAX); + } +#endif + + if(fd_open_max == CLOSE_RANGE_FD_MAX || fd_open_max == -1) { + // Arbitrary default if everything else fails + fd_open_max = 65535; + } + + return fd_open_max; +} + +void os_close_range(int first, int last) { +#if defined(HAVE_CLOSE_RANGE) + if(close_range(first, last, 0) == 0) return; +#endif + +#if defined(OS_LINUX) + DIR *dir = opendir("/proc/self/fd"); + if (dir != NULL) { + struct dirent *entry; + while ((entry = readdir(dir)) != NULL) { + int fd = str2i(entry->d_name); + if (fd >= first && (last == CLOSE_RANGE_FD_MAX || fd <= last) && fd_is_valid(fd)) + (void)close(fd); + } + closedir(dir); + return; + } +#endif + + // Fallback to looping through all file descriptors if necessary + if (last == CLOSE_RANGE_FD_MAX) + last = os_get_fd_open_max(); + + for (int fd = first; fd <= last; fd++) { + if (fd_is_valid(fd)) (void)close(fd); + } +} + +static int compare_ints(const void *a, const void *b) { + int int_a = *((int*)a); + int int_b = *((int*)b); + return (int_a > int_b) - (int_a < int_b); +} + +void os_close_all_non_std_open_fds_except(const int fds[], size_t fds_num) { + if (fds_num == 0 || fds == NULL) { + os_close_range(STDERR_FILENO + 1, CLOSE_RANGE_FD_MAX); + return; + } + + // copy the fds array to ensure we will not alter them + int fds_copy[fds_num]; + memcpy(fds_copy, fds, sizeof(fds_copy)); + + qsort(fds_copy, fds_num, sizeof(int), compare_ints); + + int start = STDERR_FILENO + 1; + size_t i = 0; + + // filter out all fds with a number smaller than our start + for (; i < fds_num; i++) + if(fds_copy[i] >= start) break; + + // call os_close_range() as many times as needed + for (; i < fds_num; i++) { + if (fds_copy[i] > start) + os_close_range(start, fds_copy[i] - 1); + + start = fds_copy[i] + 1; + } + + os_close_range(start, CLOSE_RANGE_FD_MAX); +} diff --git a/src/libnetdata/os/close_range.h b/src/libnetdata/os/close_range.h new file mode 100644 index 00000000..e3cb9379 --- /dev/null +++ b/src/libnetdata/os/close_range.h @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef CLOSE_RANGE_H +#define CLOSE_RANGE_H + +#define CLOSE_RANGE_FD_MAX (int)(~0U) + +int os_get_fd_open_max(void); +void os_close_range(int first, int last); +void os_close_all_non_std_open_fds_except(const int fds[], size_t fds_num); + +#endif //CLOSE_RANGE_H diff --git a/src/libnetdata/os/get_pid_max.c b/src/libnetdata/os/get_pid_max.c index 45027961..70372a74 100644 --- a/src/libnetdata/os/get_pid_max.c +++ b/src/libnetdata/os/get_pid_max.c @@ -2,13 +2,27 @@ #include "../libnetdata.h" -pid_t pid_max = 32768; +pid_t pid_max = 4194304; + pid_t os_get_system_pid_max(void) { + static bool read = false; + if(read) return pid_max; + read = true; + #if defined(OS_MACOS) + int mib[2]; + int maxproc; + size_t len = sizeof(maxproc); + + mib[0] = CTL_KERN; + mib[1] = KERN_MAXPROC; + + if (sysctl(mib, 2, &maxproc, &len, NULL, 0) == -1) { + pid_max = 99999; // Fallback value + nd_log(NDLS_DAEMON, NDLP_ERR, "Cannot find system max pid. Assuming %d.", pid_max); + } + else pid_max = (pid_t)maxproc; - // As we currently do not know a solution to query pid_max from the os - // we use the number defined in bsd/sys/proc_internal.h in XNU sources - pid_max = 99999; return pid_max; #elif defined(OS_FREEBSD) @@ -17,41 +31,40 @@ pid_t os_get_system_pid_max(void) { if (unlikely(GETSYSCTL_BY_NAME("kern.pid_max", tmp_pid_max))) { pid_max = 99999; - netdata_log_error("Assuming system's maximum pid is %d.", pid_max); - } else { - pid_max = tmp_pid_max; + nd_log(NDLS_DAEMON, NDLP_ERR, "Cannot get system max pid. Assuming %d.", pid_max); } + else + pid_max = tmp_pid_max; return pid_max; #elif defined(OS_LINUX) - static char read = 0; - if(unlikely(read)) return pid_max; - read = 1; - char filename[FILENAME_MAX + 1]; snprintfz(filename, FILENAME_MAX, "%s/proc/sys/kernel/pid_max", netdata_configured_host_prefix?netdata_configured_host_prefix:""); unsigned long long max = 0; if(read_single_number_file(filename, &max) != 0) { - netdata_log_error("Cannot open file '%s'. Assuming system supports %d pids.", filename, pid_max); + nd_log(NDLS_DAEMON, NDLP_ERR, "Cannot open file '%s'. Assuming system supports %d pids.", filename, pid_max); return pid_max; } if(!max) { - netdata_log_error("Cannot parse file '%s'. Assuming system supports %d pids.", filename, pid_max); + nd_log(NDLS_DAEMON, NDLP_ERR, "Cannot parse file '%s'. Assuming system supports %d pids.", filename, pid_max); return pid_max; } pid_max = (pid_t) max; return pid_max; -#else +#elif defined(OS_WINDOWS) - // just a big default + pid_max = (pid_t)0x7FFFFFFF; + return pid_max; + +#else - pid_max = 4194304; + // return the default return pid_max; #endif diff --git a/src/libnetdata/os/os-windows-wrappers.c b/src/libnetdata/os/os-windows-wrappers.c new file mode 100644 index 00000000..64076eae --- /dev/null +++ b/src/libnetdata/os/os-windows-wrappers.c @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "../libnetdata.h" + +#if defined(OS_WINDOWS) +#include <windows.h> + +long netdata_registry_get_dword_from_open_key(unsigned int *out, void *lKey, char *name) +{ + DWORD length = 260; + return RegQueryValueEx(lKey, name, NULL, NULL, (LPBYTE) out, &length); +} + +bool netdata_registry_get_dword(unsigned int *out, void *hKey, char *subKey, char *name) +{ + HKEY lKey; + bool status = true; + long ret = RegOpenKeyEx(hKey, + subKey, + 0, + KEY_READ, + &lKey); + if (ret != ERROR_SUCCESS) + return false; + + ret = netdata_registry_get_dword_from_open_key(out, lKey, name); + if (ret != ERROR_SUCCESS) + status = false; + + RegCloseKey(lKey); + + return status; +} + +long netdata_registry_get_string_from_open_key(char *out, unsigned int length, void *lKey, char *name) +{ + return RegQueryValueEx(lKey, name, NULL, NULL, (LPBYTE) out, &length); +} + +bool netdata_registry_get_string(char *out, unsigned int length, void *hKey, char *subKey, char *name) +{ + HKEY lKey; + bool status = true; + long ret = RegOpenKeyEx(hKey, + subKey, + 0, + KEY_READ, + &lKey); + if (ret != ERROR_SUCCESS) + return false; + + ret = netdata_registry_get_string_from_open_key(out, length, lKey, name); + if (ret != ERROR_SUCCESS) + status = false; + + RegCloseKey(lKey); + + return status; +} + +#endif diff --git a/src/libnetdata/os/os-windows-wrappers.h b/src/libnetdata/os/os-windows-wrappers.h new file mode 100644 index 00000000..5ae73043 --- /dev/null +++ b/src/libnetdata/os/os-windows-wrappers.h @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_OS_WINDOWS_WRAPPERS_H +#define NETDATA_OS_WINDOWS_WRAPPERS_H + +#include "../libnetdata.h" + +#if defined(OS_WINDOWS) +#define NETDATA_WIN_DETECTION_METHOD "Windows API/Registry" + +long netdata_registry_get_dword_from_open_key(unsigned int *out, void *lKey, char *name); +bool netdata_registry_get_dword(unsigned int *out, void *hKey, char *subKey, char *name); + +long netdata_registry_get_string_from_open_key(char *out, unsigned int length, void *lKey, char *name); +bool netdata_registry_get_string(char *out, unsigned int length, void *hKey, char *subKey, char *name); + +#endif // OS_WINDOWS +#endif //NETDATA_OS_WINDOWS_WRAPPERS_H diff --git a/src/libnetdata/os/os.h b/src/libnetdata/os/os.h index 35009615..15e74faa 100644 --- a/src/libnetdata/os/os.h +++ b/src/libnetdata/os/os.h @@ -7,12 +7,13 @@ #include <sys/syscall.h> #endif +#include "setproctitle.h" +#include "close_range.h" #include "setresuid.h" #include "setresgid.h" #include "getgrouplist.h" #include "adjtimex.h" #include "gettid.h" -#include "waitid.h" #include "get_pid_max.h" #include "get_system_cpus.h" #include "tinysleep.h" @@ -20,6 +21,7 @@ #include "setenv.h" #include "os-freebsd-wrappers.h" #include "os-macos-wrappers.h" +#include "os-windows-wrappers.h" // ===================================================================================================================== // common defs for Apple/FreeBSD/Linux diff --git a/src/libnetdata/os/setproctitle.c b/src/libnetdata/os/setproctitle.c new file mode 100644 index 00000000..d9315820 --- /dev/null +++ b/src/libnetdata/os/setproctitle.c @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "../libnetdata.h" +#include "setproctitle.h" + +void os_setproctitle(const char *new_name, const int argc, const char **argv) { +#ifdef HAVE_SYS_PRCTL_H + // Set the process name (comm) + prctl(PR_SET_NAME, new_name, 0, 0, 0); +#endif + +#ifdef __FreeBSD__ + // Set the process name on FreeBSD + setproctitle("%s", new_name); +#endif + + if(argc && argv) { + // replace with spaces all parameters found (except argv[0]) + for(int i = 1; i < argc ;i++) { + char *s = (char *)&argv[i][0]; + while(*s != '\0') *s++ = ' '; + } + + // overwrite argv[0] + size_t len = strlen(new_name); + const size_t argv0_len = strlen(argv[0]); + strncpyz((char *)argv[0], new_name, MIN(len, argv0_len)); + while(len < argv0_len) + ((char *)argv[0])[len++] = ' '; + } +} diff --git a/src/libnetdata/os/setproctitle.h b/src/libnetdata/os/setproctitle.h new file mode 100644 index 00000000..0e7211b2 --- /dev/null +++ b/src/libnetdata/os/setproctitle.h @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef SETPROCTITLE_H +#define SETPROCTITLE_H + +void os_setproctitle(const char *new_name, int argc, const char **argv); + +#endif //SETPROCTITLE_H diff --git a/src/libnetdata/os/waitid.c b/src/libnetdata/os/waitid.c deleted file mode 100644 index b78d704e..00000000 --- a/src/libnetdata/os/waitid.c +++ /dev/null @@ -1,72 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later - -#include "../libnetdata.h" - -int os_waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options) { -#if defined(HAVE_WAITID) - return waitid(idtype, id, infop, options); -#else - // emulate waitid() using waitpid() - - // a cache for WNOWAIT - static const struct pid_status empty = { 0, 0 }; - static __thread struct pid_status last = { 0, 0 }; // the cache - struct pid_status current = { 0, 0 }; - - // zero the infop structure - memset(infop, 0, sizeof(*infop)); - - // from the infop structure we use only 3 fields: - // - si_pid - // - si_code - // - si_status - // so, we update only these 3 - - switch(idtype) { - case P_ALL: - current.pid = waitpid((pid_t)-1, ¤t.status, options); - if(options & WNOWAIT) - last = current; - else - last = empty; - break; - - case P_PID: - if(last.pid == (pid_t)id) { - current = last; - last = empty; - } - else - current.pid = waitpid((pid_t)id, ¤t.status, options); - - break; - - default: - errno = ENOSYS; - return -1; - } - - if (current.pid > 0) { - if (WIFEXITED(current.status)) { - infop->si_code = CLD_EXITED; - infop->si_status = WEXITSTATUS(current.status); - } else if (WIFSIGNALED(current.status)) { - infop->si_code = WTERMSIG(current.status) == SIGABRT ? CLD_DUMPED : CLD_KILLED; - infop->si_status = WTERMSIG(current.status); - } else if (WIFSTOPPED(current.status)) { - infop->si_code = CLD_STOPPED; - infop->si_status = WSTOPSIG(current.status); - } else if (WIFCONTINUED(current.status)) { - infop->si_code = CLD_CONTINUED; - infop->si_status = SIGCONT; - } - infop->si_pid = current.pid; - return 0; - } else if (current.pid == 0) { - // No change in state, depends on WNOHANG - return 0; - } - - return -1; -#endif -} diff --git a/src/libnetdata/os/waitid.h b/src/libnetdata/os/waitid.h deleted file mode 100644 index 9e1fd6be..00000000 --- a/src/libnetdata/os/waitid.h +++ /dev/null @@ -1,48 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later - -#ifndef NETDATA_WAITID_H -#define NETDATA_WAITID_H - -#include "config.h" -#include <sys/types.h> -#include <signal.h> - -#ifdef HAVE_SYS_WAIT_H -#include <sys/wait.h> -#endif - -#ifndef WNOWAIT -#define WNOWAIT 0x01000000 -#endif - -#ifndef WEXITED -#define WEXITED 4 -#endif - -#if !defined(HAVE_WAITID) -typedef enum -{ - P_ALL, /* Wait for any child. */ - P_PID, /* Wait for specified process. */ - P_PGID, /* Wait for members of process group. */ - P_PIDFD, /* Wait for the child referred by the PID file descriptor. */ -} idtype_t; - -struct pid_status { - pid_t pid; - int status; -}; - -#if defined(OS_WINDOWS) && !defined(__CYGWIN__) -typedef uint32_t id_t; -typedef struct { - int si_code; /* Signal code. */ - int si_status; /* Exit value or signal. */ - pid_t si_pid; /* Sending process ID. */ -} siginfo_t; -#endif -#endif - -int os_waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options); - -#endif //NETDATA_WAITID_H diff --git a/src/libnetdata/popen/README.md b/src/libnetdata/popen/README.md deleted file mode 100644 index ca4877c1..00000000 --- a/src/libnetdata/popen/README.md +++ /dev/null @@ -1,15 +0,0 @@ -<!-- -title: "popen" -custom_edit_url: https://github.com/netdata/netdata/edit/master/src/libnetdata/popen/README.md -sidebar_label: "popen" -learn_status: "Published" -learn_topic_type: "Tasks" -learn_rel_path: "Developers/libnetdata" ---> - -# popen - -Process management library - - - diff --git a/src/libnetdata/popen/popen.c b/src/libnetdata/popen/popen.c deleted file mode 100644 index c1721e9b..00000000 --- a/src/libnetdata/popen/popen.c +++ /dev/null @@ -1,446 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later - -#include "../libnetdata.h" - -// ---------------------------------------------------------------------------- -// popen with tracking - -static pthread_mutex_t netdata_popen_tracking_mutex = NETDATA_MUTEX_INITIALIZER; - -struct netdata_popen { - pid_t pid; - bool reaped; - siginfo_t infop; - int waitid_ret; - struct netdata_popen *next; - struct netdata_popen *prev; -}; - -static struct netdata_popen *netdata_popen_root = NULL; - -// myp_add_lock takes the lock if we're tracking. -static void netdata_popen_tracking_lock(void) { - netdata_mutex_lock(&netdata_popen_tracking_mutex); -} - -// myp_add_unlock release the lock if we're tracking. -static void netdata_popen_tracking_unlock(void) { - netdata_mutex_unlock(&netdata_popen_tracking_mutex); -} - -// myp_add_locked adds pid if we're tracking. -// myp_add_lock must have been called previously. -static void netdata_popen_tracking_add_pid_unsafe(pid_t pid) { - struct netdata_popen *mp; - - mp = callocz(1, sizeof(struct netdata_popen)); - mp->pid = pid; - - DOUBLE_LINKED_LIST_PREPEND_ITEM_UNSAFE(netdata_popen_root, mp, prev, next); -} - -// myp_del deletes pid if we're tracking. -static void netdata_popen_tracking_del_pid(pid_t pid) { - struct netdata_popen *mp; - - netdata_popen_tracking_lock(); - - DOUBLE_LINKED_LIST_FOREACH_FORWARD(netdata_popen_root, mp, prev, next) { - if(unlikely(mp->pid == pid)) - break; - } - - if(mp) { - DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(netdata_popen_root, mp, prev, next); - freez(mp); - } - else - netdata_log_error("POPEN: Cannot find pid %d.", pid); - - netdata_popen_tracking_unlock(); -} - -// myp_free cleans up any resources allocated for process -// tracking. -void netdata_popen_tracking_cleanup(void) { - netdata_popen_tracking_lock(); - - while(netdata_popen_root) { - struct netdata_popen *mp = netdata_popen_root; - DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(netdata_popen_root, mp, prev, next); - freez(mp); - } - - netdata_popen_tracking_unlock(); -} - -int netdata_waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options) { - struct netdata_popen *mp = NULL; - - if(idtype == P_PID && id != 0) { - // the caller is asking to waitid() for a specific child pid - - netdata_popen_tracking_lock(); - DOUBLE_LINKED_LIST_FOREACH_FORWARD(netdata_popen_root, mp, prev, next) { - if(unlikely(mp->pid == (pid_t)id)) - break; - } - - if(!mp) - netdata_popen_tracking_unlock(); - } - - int ret; - if(mp && mp->reaped) { - // we have already reaped this child - ret = mp->waitid_ret; - *infop = mp->infop; - } - else { - // we haven't reaped this child yet - ret = os_waitid(idtype, id, infop, options); - - if(mp && !mp->reaped) { - mp->reaped = true; - mp->infop = *infop; - mp->waitid_ret = ret; - } - } - - if(mp) - netdata_popen_tracking_unlock(); - - return ret; -} - -// ---------------------------------------------------------------------------- -// helpers - -static inline void convert_argv_to_string(char *dst, size_t size, const char *spawn_argv[]) { - int i; - for(i = 0; spawn_argv[i] ;i++) { - if(i == 0) snprintfz(dst, size, "%s", spawn_argv[i]); - else { - size_t len = strlen(dst); - snprintfz(&dst[len], size - len, " '%s'", spawn_argv[i]); - } - } -} - -// ---------------------------------------------------------------------------- -// the core of netdata popen - -/* - * Returns -1 on failure, 0 on success. When POPEN_FLAG_CREATE_PIPE is set, on success set the FILE *fp pointer. - */ -#define PIPE_READ 0 -#define PIPE_WRITE 1 - -static int popene_internal(volatile pid_t *pidptr, char **env, uint8_t flags, FILE **fpp_child_stdin, FILE **fpp_child_stdout, const char *command, const char *spawn_argv[]) { - // create a string to be logged about the command we are running - char command_to_be_logged[2048]; - convert_argv_to_string(command_to_be_logged, sizeof(command_to_be_logged), spawn_argv); - // netdata_log_info("custom_popene() running command: %s", command_to_be_logged); - - int ret = 0; // success by default - int attr_rc = 1; // failure by default - - FILE *fp_child_stdin = NULL, *fp_child_stdout = NULL; - int pipefd_stdin[2] = { -1, -1 }; - int pipefd_stdout[2] = { -1, -1 }; - - pid_t pid; - posix_spawnattr_t attr; - posix_spawn_file_actions_t fa; - - unsigned int fds_to_exclude_from_closing = OPEN_FD_EXCLUDE_STDERR; - - if(posix_spawn_file_actions_init(&fa)) { - netdata_log_error("POPEN: posix_spawn_file_actions_init() failed."); - ret = -1; - goto set_return_values_and_return; - } - - if(fpp_child_stdin) { - if (pipe(pipefd_stdin) == -1) { - netdata_log_error("POPEN: stdin pipe() failed"); - ret = -1; - goto cleanup_and_return; - } - - if ((fp_child_stdin = fdopen(pipefd_stdin[PIPE_WRITE], "w")) == NULL) { - netdata_log_error("POPEN: fdopen() stdin failed"); - ret = -1; - goto cleanup_and_return; - } - - if(posix_spawn_file_actions_adddup2(&fa, pipefd_stdin[PIPE_READ], STDIN_FILENO)) { - netdata_log_error("POPEN: posix_spawn_file_actions_adddup2() on stdin failed."); - ret = -1; - goto cleanup_and_return; - } - } - else { - if (posix_spawn_file_actions_addopen(&fa, STDIN_FILENO, "/dev/null", O_RDONLY, 0)) { - netdata_log_error("POPEN: posix_spawn_file_actions_addopen() on stdin to /dev/null failed."); - // this is not a fatal error - fds_to_exclude_from_closing |= OPEN_FD_EXCLUDE_STDIN; - } - } - - if (fpp_child_stdout) { - if (pipe(pipefd_stdout) == -1) { - netdata_log_error("POPEN: stdout pipe() failed"); - ret = -1; - goto cleanup_and_return; - } - - if ((fp_child_stdout = fdopen(pipefd_stdout[PIPE_READ], "r")) == NULL) { - netdata_log_error("POPEN: fdopen() stdout failed"); - ret = -1; - goto cleanup_and_return; - } - - if(posix_spawn_file_actions_adddup2(&fa, pipefd_stdout[PIPE_WRITE], STDOUT_FILENO)) { - netdata_log_error("POPEN: posix_spawn_file_actions_adddup2() on stdout failed."); - ret = -1; - goto cleanup_and_return; - } - } - else { - if (posix_spawn_file_actions_addopen(&fa, STDOUT_FILENO, "/dev/null", O_WRONLY, 0)) { - netdata_log_error("POPEN: posix_spawn_file_actions_addopen() on stdout to /dev/null failed."); - // this is not a fatal error - fds_to_exclude_from_closing |= OPEN_FD_EXCLUDE_STDOUT; - } - } - - if(flags & POPEN_FLAG_CLOSE_FD) { - // Mark all files to be closed by the exec() stage of posix_spawn() - for_each_open_fd(OPEN_FD_ACTION_FD_CLOEXEC, fds_to_exclude_from_closing); - } - - attr_rc = posix_spawnattr_init(&attr); - if(attr_rc) { - // failed - netdata_log_error("POPEN: posix_spawnattr_init() failed."); - } - else { - // success - // reset all signals in the child - - if (posix_spawnattr_setflags(&attr, POSIX_SPAWN_SETSIGMASK | POSIX_SPAWN_SETSIGDEF)) - netdata_log_error("POPEN: posix_spawnattr_setflags() failed."); - - sigset_t mask; - sigemptyset(&mask); - - if (posix_spawnattr_setsigmask(&attr, &mask)) - netdata_log_error("POPEN: posix_spawnattr_setsigmask() failed."); - } - - // Take the lock while we fork to ensure we don't race with SIGCHLD - // delivery on a process which exits quickly. - netdata_popen_tracking_lock(); - if (!posix_spawn(&pid, command, &fa, &attr, (char * const*)spawn_argv, env)) { - // success - *pidptr = pid; - netdata_popen_tracking_add_pid_unsafe(pid); - netdata_popen_tracking_unlock(); - } - else { - // failure - netdata_popen_tracking_unlock(); - netdata_log_error("POPEN: failed to spawn command: \"%s\" from parent pid %d.", command_to_be_logged, getpid()); - ret = -1; - goto cleanup_and_return; - } - - // the normal cleanup will run - // but ret == 0 at this point - -cleanup_and_return: - if(!attr_rc) { - // posix_spawnattr_init() succeeded - if (posix_spawnattr_destroy(&attr)) - netdata_log_error("POPEN: posix_spawnattr_destroy() failed"); - } - - if (posix_spawn_file_actions_destroy(&fa)) - netdata_log_error("POPEN: posix_spawn_file_actions_destroy() failed"); - - // the child end - close it - if(pipefd_stdin[PIPE_READ] != -1) - close(pipefd_stdin[PIPE_READ]); - - // our end - if(ret == -1 || !fpp_child_stdin) { - if (fp_child_stdin) - fclose(fp_child_stdin); - else if (pipefd_stdin[PIPE_WRITE] != -1) - close(pipefd_stdin[PIPE_WRITE]); - - fp_child_stdin = NULL; - } - - // the child end - close it - if (pipefd_stdout[PIPE_WRITE] != -1) - close(pipefd_stdout[PIPE_WRITE]); - - // our end - if (ret == -1 || !fpp_child_stdout) { - if (fp_child_stdout) - fclose(fp_child_stdout); - else if (pipefd_stdout[PIPE_READ] != -1) - close(pipefd_stdout[PIPE_READ]); - - fp_child_stdout = NULL; - } - -set_return_values_and_return: - if(fpp_child_stdin) - *fpp_child_stdin = fp_child_stdin; - - if(fpp_child_stdout) - *fpp_child_stdout = fp_child_stdout; - - return ret; -} - -int netdata_popene_variadic_internal_dont_use_directly(volatile pid_t *pidptr, char **env, uint8_t flags, FILE **fpp_child_input, FILE **fpp_child_output, const char *command, ...) { - // convert the variable list arguments into what posix_spawn() needs - // all arguments are expected strings - va_list args; - int args_count; - - // count the number variable parameters - // the variable parameters are expected NULL terminated - { - const char *s; - - va_start(args, command); - args_count = 0; - while ((s = va_arg(args, const char *))) args_count++; - va_end(args); - } - - // create a string pointer array as needed by posix_spawn() - // variable array in the stack - const char *spawn_argv[args_count + 1]; - { - const char *s; - va_start(args, command); - int i; - for (i = 0; i < args_count; i++) { - s = va_arg(args, const char *); - spawn_argv[i] = s; - } - spawn_argv[args_count] = NULL; - va_end(args); - } - - return popene_internal(pidptr, env, flags, fpp_child_input, fpp_child_output, command, spawn_argv); -} - -// See man environ -extern char **environ; - -FILE *netdata_popen(const char *command, volatile pid_t *pidptr, FILE **fpp_child_input) { - FILE *fp_child_output = NULL; - const char *spawn_argv[] = { - "sh", - "-c", - command, - NULL - }; - (void)popene_internal(pidptr, environ, POPEN_FLAG_CLOSE_FD, fpp_child_input, &fp_child_output, "/bin/sh", spawn_argv); - return fp_child_output; -} - -FILE *netdata_popene(const char *command, volatile pid_t *pidptr, char **env, FILE **fpp_child_input) { - FILE *fp_child_output = NULL; - const char *spawn_argv[] = { - "sh", - "-c", - command, - NULL - }; - (void)popene_internal(pidptr, env, POPEN_FLAG_CLOSE_FD, fpp_child_input, &fp_child_output, "/bin/sh", spawn_argv); - return fp_child_output; -} - -// returns 0 on success, -1 on failure -int netdata_spawn(const char *command, volatile pid_t *pidptr) { - const char *spawn_argv[] = { - "sh", - "-c", - command, - NULL - }; - return popene_internal(pidptr, environ, POPEN_FLAG_NONE, NULL, NULL, "/bin/sh", spawn_argv); -} - -int netdata_pclose(FILE *fp_child_input, FILE *fp_child_output, pid_t pid) { - int ret; - siginfo_t info; - - netdata_log_debug(D_EXIT, "Request to netdata_pclose() on pid %d", pid); - - if (fp_child_input) - fclose(fp_child_input); - - if (fp_child_output) - fclose(fp_child_output); - - errno = 0; - - ret = netdata_waitid(P_PID, (id_t) pid, &info, WEXITED); - netdata_popen_tracking_del_pid(pid); - - if (ret != -1) { - switch (info.si_code) { - case CLD_EXITED: - if(info.si_status) - netdata_log_error("child pid %d exited with code %d.", info.si_pid, info.si_status); - return(info.si_status); - - case CLD_KILLED: - if(info.si_status == SIGTERM) { - netdata_log_info("child pid %d killed by SIGTERM", info.si_pid); - return(0); - } - else if(info.si_status == SIGPIPE) { - netdata_log_info("child pid %d killed by SIGPIPE.", info.si_pid); - return(0); - } - else { - netdata_log_error("child pid %d killed by signal %d.", info.si_pid, info.si_status); - return(-1); - } - - case CLD_DUMPED: - netdata_log_error("child pid %d core dumped by signal %d.", info.si_pid, info.si_status); - return(-2); - - case CLD_STOPPED: - netdata_log_error("child pid %d stopped by signal %d.", info.si_pid, info.si_status); - return(0); - - case CLD_TRAPPED: - netdata_log_error("child pid %d trapped by signal %d.", info.si_pid, info.si_status); - return(-4); - - case CLD_CONTINUED: - netdata_log_error("child pid %d continued by signal %d.", info.si_pid, info.si_status); - return(0); - - default: - netdata_log_error("child pid %d gave us a SIGCHLD with code %d and status %d.", info.si_pid, info.si_code, info.si_status); - return(-5); - } - } - else - netdata_log_error("Cannot waitid() for pid %d", pid); - - return 0; -} diff --git a/src/libnetdata/popen/popen.h b/src/libnetdata/popen/popen.h deleted file mode 100644 index 8f46abbc..00000000 --- a/src/libnetdata/popen/popen.h +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later - -#ifndef NETDATA_POPEN_H -#define NETDATA_POPEN_H 1 - -#include "../os/waitid.h" -int netdata_waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options); - -#include "../libnetdata.h" - -#define PIPE_READ 0 -#define PIPE_WRITE 1 - -/* custom_popene_variadic_internal_dont_use_directly flag definitions */ -#define POPEN_FLAG_NONE 0 -#define POPEN_FLAG_CLOSE_FD (1 << 0) // Close all file descriptors other than STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO - -// the flags to be used by default -#define POPEN_FLAGS_DEFAULT (POPEN_FLAG_CLOSE_FD) - -// mypopen_raw is the interface to use instead of custom_popene_variadic_internal_dont_use_directly() -// mypopen_raw will add the terminating NULL at the arguments list -// we append the parameter 'command' twice - this is because the underlying call needs the command to execute and the argv[0] to pass to it -#define netdata_popen_raw_default_flags_and_environment(pidptr, fpp_child_input, fpp_child_output, command, args...) netdata_popene_variadic_internal_dont_use_directly(pidptr, environ, POPEN_FLAGS_DEFAULT, fpp_child_input, fpp_child_output, command, command, ##args, NULL) -#define netdata_popen_raw_default_flags(pidptr, env, fpp_child_input, fpp_child_output, command, args...) netdata_popene_variadic_internal_dont_use_directly(pidptr, env, POPEN_FLAGS_DEFAULT, fpp_child_input, fpp_child_output, command, command, ##args, NULL) -#define netdata_popen_raw(pidptr, env, flags, fpp_child_input, fpp_child_output, command, args...) netdata_popene_variadic_internal_dont_use_directly(pidptr, env, flags, fpp_child_input, fpp_child_output, command, command, ##args, NULL) - -FILE *netdata_popen(const char *command, volatile pid_t *pidptr, FILE **fp_child_input); -FILE *netdata_popene(const char *command, volatile pid_t *pidptr, char **env, FILE **fp_child_input); -int netdata_popene_variadic_internal_dont_use_directly(volatile pid_t *pidptr, char **env, uint8_t flags, FILE **fpp_child_input, FILE **fpp_child_output, const char *command, ...); -int netdata_pclose(FILE *fp_child_input, FILE *fp_child_output, pid_t pid); - -int netdata_spawn(const char *command, volatile pid_t *pidptr); - -#endif /* NETDATA_POPEN_H */ diff --git a/src/libnetdata/procfile/procfile.c b/src/libnetdata/procfile/procfile.c index d9ebf4c9..2b7eeeb5 100644 --- a/src/libnetdata/procfile/procfile.c +++ b/src/libnetdata/procfile/procfile.c @@ -336,7 +336,7 @@ __attribute__((constructor)) void procfile_initialize_default_separators(void) { if(unlikely(i == '\n' || i == '\r')) procfile_default_separators[i] = PF_CHAR_IS_NEWLINE; - else if(unlikely(isspace(i) || !isprint(i))) + else if(unlikely(isspace(i) || (!isprint(i) && !IS_UTF8_BYTE(i)))) procfile_default_separators[i] = PF_CHAR_IS_SEPARATOR; else diff --git a/src/libnetdata/socket/socket.c b/src/libnetdata/socket/socket.c index 0ba24b74..7170a396 100644 --- a/src/libnetdata/socket/socket.c +++ b/src/libnetdata/socket/socket.c @@ -194,11 +194,9 @@ int sock_setreuse(int fd, int reuse) { void sock_setcloexec(int fd) { UNUSED(fd); -#ifndef SOCK_CLOEXEC int flags = fcntl(fd, F_GETFD); if (flags != -1) (void) fcntl(fd, F_SETFD, flags | FD_CLOEXEC); -#endif } int sock_setreuse_port(int fd __maybe_unused, int reuse __maybe_unused) { @@ -290,7 +288,7 @@ int create_listen_socket_unix(const char *path, int listen_backlog) { name.sun_family = AF_UNIX; strncpy(name.sun_path, path, sizeof(name.sun_path)-1); - errno = 0; + errno_clear(); if (unlink(path) == -1 && errno != ENOENT) nd_log(NDLS_DAEMON, NDLP_ERR, "LISTENER: failed to remove existing (probably obsolete or left-over) file on UNIX socket path '%s'.", @@ -918,7 +916,7 @@ int connect_to_this_ip46(int protocol, int socktype, const char *host, uint32_t } sock_setcloexec(fd); - errno = 0; + errno_clear(); if(connect(fd, ai->ai_addr, ai->ai_addrlen) < 0) { if(errno == EALREADY || errno == EINPROGRESS) { nd_log(NDLS_DAEMON, NDLP_DEBUG, @@ -1200,7 +1198,7 @@ inline int wait_on_socket_or_cancel_with_timeout( const int wait_ms = (timeout_ms >= ND_CHECK_CANCELLABILITY_WHILE_WAITING_EVERY_MS || forever) ? ND_CHECK_CANCELLABILITY_WHILE_WAITING_EVERY_MS : timeout_ms; - errno = 0; + errno_clear(); // check every wait_ms const int ret = poll(&pfd, 1, wait_ms); @@ -1482,7 +1480,7 @@ int accept_socket(int fd, int flags, char *client_ip, size_t ipsize, char *clien break; } if (!connection_allowed(nfd, client_ip, client_host, hostsize, access_list, "connection", allow_dns)) { - errno = 0; + errno_clear(); nd_log(NDLS_DAEMON, NDLP_WARNING, "Permission denied for client '%s', port '%s'", client_ip, client_port); diff --git a/src/libnetdata/spawn_server/spawn_popen.c b/src/libnetdata/spawn_server/spawn_popen.c new file mode 100644 index 00000000..f354b1f2 --- /dev/null +++ b/src/libnetdata/spawn_server/spawn_popen.c @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "spawn_popen.h" + +SPAWN_SERVER *netdata_main_spawn_server = NULL; +static SPINLOCK netdata_main_spawn_server_spinlock = NETDATA_SPINLOCK_INITIALIZER; + +bool netdata_main_spawn_server_init(const char *name, int argc, const char **argv) { + if(netdata_main_spawn_server == NULL) { + spinlock_lock(&netdata_main_spawn_server_spinlock); + if(netdata_main_spawn_server == NULL) + netdata_main_spawn_server = spawn_server_create(SPAWN_SERVER_OPTION_EXEC, name, NULL, argc, argv); + spinlock_unlock(&netdata_main_spawn_server_spinlock); + } + + return netdata_main_spawn_server != NULL; +} + +void netdata_main_spawn_server_cleanup(void) { + if(netdata_main_spawn_server) { + spinlock_lock(&netdata_main_spawn_server_spinlock); + if(netdata_main_spawn_server) { + spawn_server_destroy(netdata_main_spawn_server); + netdata_main_spawn_server = NULL; + } + spinlock_unlock(&netdata_main_spawn_server_spinlock); + } +} + +POPEN_INSTANCE *spawn_popen_run_argv(const char **argv) { + netdata_main_spawn_server_init(NULL, 0, NULL); + + SPAWN_INSTANCE *si = spawn_server_exec(netdata_main_spawn_server, nd_log_collectors_fd(), + 0, argv, NULL, 0, SPAWN_INSTANCE_TYPE_EXEC); + + if(si == NULL) return NULL; + + POPEN_INSTANCE *pi = mallocz(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, ...) { + va_list args; + va_list args_copy; + int argc = 0; + + // Start processing variadic arguments + va_start(args, cmd); + + // Make a copy of args to count the number of arguments + va_copy(args_copy, args); + while (va_arg(args_copy, char *) != NULL) argc++; + va_end(args_copy); + + // Allocate memory for argv array (+2 for cmd and NULL terminator) + const char *argv[argc + 2]; + + // Populate the argv array + argv[0] = cmd; + + for (int i = 1; i <= argc; i++) + argv[i] = va_arg(args, const char *); + + argv[argc + 1] = NULL; // NULL-terminate the array + + // End processing variadic arguments + va_end(args); + + return spawn_popen_run_argv(argv); +} + +POPEN_INSTANCE *spawn_popen_run(const char *cmd) { + if(!cmd || !*cmd) return NULL; + + const char *argv[] = { + "/bin/sh", + "-c", + cmd, + NULL + }; + return spawn_popen_run_argv(argv); +} + +static int spawn_popen_status_rc(int status) { + if(WIFEXITED(status)) + return WEXITSTATUS(status); + + if(WIFSIGNALED(status)) { + int sig = WTERMSIG(status); + switch(sig) { + case SIGTERM: + case SIGPIPE: + return 0; + + default: + return -1; + } + } + + return -1; +} + +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); + int status = spawn_server_exec_wait(netdata_main_spawn_server, pi->si); + freez(pi); + return spawn_popen_status_rc(status); +} + +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); + int status = spawn_server_exec_kill(netdata_main_spawn_server, pi->si); + freez(pi); + return spawn_popen_status_rc(status); +} diff --git a/src/libnetdata/spawn_server/spawn_popen.h b/src/libnetdata/spawn_server/spawn_popen.h new file mode 100644 index 00000000..253d1f34 --- /dev/null +++ b/src/libnetdata/spawn_server/spawn_popen.h @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef SPAWN_POPEN_H +#define SPAWN_POPEN_H + +#include "../libnetdata.h" + +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; + +POPEN_INSTANCE *spawn_popen_run(const char *cmd); +POPEN_INSTANCE *spawn_popen_run_argv(const char **argv); +POPEN_INSTANCE *spawn_popen_run_variadic(const char *cmd, ...); +int spawn_popen_wait(POPEN_INSTANCE *pi); +int spawn_popen_kill(POPEN_INSTANCE *pi); + +#endif //SPAWN_POPEN_H diff --git a/src/libnetdata/spawn_server/spawn_server.c b/src/libnetdata/spawn_server/spawn_server.c new file mode 100644 index 00000000..ef6755c3 --- /dev/null +++ b/src/libnetdata/spawn_server/spawn_server.c @@ -0,0 +1,1533 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "../libnetdata.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 +}; + +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; } +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; } + +#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 + +#ifdef __APPLE__ +#include <crt_externs.h> +#define environ (*_NSGetEnviron()) +#else +extern char **environ; +#endif + +static size_t spawn_server_id = 0; +static volatile bool spawn_server_exit = false; +static volatile bool spawn_server_sigchld = false; +static SPAWN_REQUEST *spawn_server_requests = NULL; + +// -------------------------------------------------------------------------------------------------------------------- + +static int connect_to_spawn_server(const char *path, bool log) { + int sock = -1; + + if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { + if(log) + nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN PARENT: cannot create socket() to connect to spawn server."); + return -1; + } + + struct sockaddr_un server_addr = { + .sun_family = AF_UNIX, + }; + strcpy(server_addr.sun_path, path); + + 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."); + close(sock); + return -1; + } + + return sock; +} + +// -------------------------------------------------------------------------------------------------------------------- +// 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 +static void* argv_encode(const char **argv, size_t *out_size) { + size_t buffer_size = 1024; // Initial buffer size + size_t buffer_used = 0; + char *buffer = mallocz(buffer_size); + + if(argv) { + for (const char **p = argv; *p != NULL; p++) { + if (strlen(*p) == 0) + continue; // Skip empty strings + + size_t len = strlen(*p) + 1; + size_t wanted_size = buffer_used + len + 1; + + if (wanted_size >= buffer_size) { + buffer_size *= 2; + + if(buffer_size < wanted_size) + buffer_size = wanted_size; + + buffer = reallocz(buffer, buffer_size); + } + + memcpy(&buffer[buffer_used], *p, len); + buffer_used += len; + } + } + + buffer[buffer_used++] = '\0'; // Final empty string + *out_size = buffer_used; + + return buffer; +} + +// Function to decode argv or envp +static const char** argv_decode(const char *buffer, size_t size) { + size_t count = 0; + const char *ptr = buffer; + while (ptr < buffer + size) { + if(ptr && *ptr) { + count++; + ptr += strlen(ptr) + 1; + } + else + break; + } + + const char **argv = mallocz((count + 1) * sizeof(char *)); + + ptr = buffer; + for (size_t i = 0; i < count; i++) { + argv[i] = ptr; + ptr += strlen(ptr) + 1; + } + argv[count] = NULL; // Null-terminate the array + + 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 + +typedef enum __attribute__((packed)) { + STATUS_REPORT_NONE = 0, + STATUS_REPORT_STARTED, + STATUS_REPORT_FAILED, + STATUS_REPORT_EXITED, + STATUS_REPORT_PING, +} STATUS_REPORT; + +#define STATUS_REPORT_MAGIC 0xBADA55EE + +struct status_report { + uint32_t magic; + STATUS_REPORT status; + union { + struct { + pid_t pid; + } started; + + struct { + int err_no; + } failed; + + struct { + int waitpid_status; + } exited; + }; +}; + +static void spawn_server_send_status_ping(int sock) { + struct status_report sr = { + .magic = STATUS_REPORT_MAGIC, + .status = STATUS_REPORT_PING, + }; + + if(write(sock, &sr, sizeof(sr)) != sizeof(sr)) + nd_log(NDLS_COLLECTORS, NDLP_ERR, + "SPAWN SERVER: Cannot send ping reply."); +} + +static void spawn_server_send_status_success(SPAWN_REQUEST *rq) { + const struct status_report sr = { + .magic = STATUS_REPORT_MAGIC, + .status = STATUS_REPORT_STARTED, + .started = { + .pid = rq->pid, + }, + }; + + if(write(rq->sock, &sr, sizeof(sr)) != sizeof(sr)) + nd_log(NDLS_COLLECTORS, NDLP_ERR, + "SPAWN SERVER: Cannot send success status report for pid %d, request %zu: %s", + rq->pid, rq->request_id, rq->cmdline); +} + +static void spawn_server_send_status_failure(SPAWN_REQUEST *rq) { + struct status_report sr = { + .magic = STATUS_REPORT_MAGIC, + .status = STATUS_REPORT_FAILED, + .failed = { + .err_no = errno, + }, + }; + + if(write(rq->sock, &sr, sizeof(sr)) != sizeof(sr)) + nd_log(NDLS_COLLECTORS, NDLP_ERR, + "SPAWN SERVER: Cannot send failure status report for request %zu: %s", + rq->request_id, rq->cmdline); +} + +static void spawn_server_send_status_exit(SPAWN_REQUEST *rq, int waitpid_status) { + struct status_report sr = { + .magic = STATUS_REPORT_MAGIC, + .status = STATUS_REPORT_EXITED, + .exited = { + .waitpid_status = waitpid_status, + }, + }; + + if(write(rq->sock, &sr, sizeof(sr)) != sizeof(sr)) + nd_log(NDLS_COLLECTORS, NDLP_ERR, + "SPAWN SERVER: Cannot send exit status (%d) report for pid %d, request %zu: %s", + waitpid_status, rq->pid, rq->request_id, rq->cmdline); +} + +// -------------------------------------------------------------------------------------------------------------------- +// execute a received request + +static void request_free(SPAWN_REQUEST *rq) { + if(rq->fds[0] != -1) close(rq->fds[0]); + if(rq->fds[1] != -1) close(rq->fds[1]); + if(rq->fds[2] != -1) close(rq->fds[2]); + 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->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; } + + // create the cmdline for logs + if(rq->argv) { + CLEAN_BUFFER *wb = argv_to_cmdline_buffer(rq->argv); + rq->cmdline = strdupz(buffer_tostring(wb)); + } + break; + + 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; + + default: + errno = EINVAL; + spawn_server_send_status_failure(rq); + request_free(rq); + return; + } + + 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; + } + else if (pid == 0) { + // the child + + spawn_server_run_child(server, rq); + exit(63); + } + + // the parent + rq->pid = pid; + + // 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->argv); rq->argv = NULL; + freez((void *)rq->data); rq->data = NULL; + rq->data_size = 0; + + // do not keep fds we don't need at the parent + if(rq->fds[0] != -1) { close(rq->fds[0]); rq->fds[0] = -1; } + if(rq->fds[1] != -1) { close(rq->fds[1]); rq->fds[1] = -1; } + if(rq->fds[2] != -1) { close(rq->fds[2]); rq->fds[2] = -1; } + if(rq->fds[3] != -1) { close(rq->fds[3]); rq->fds[3] = -1; } + + // keep it in the list + DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(spawn_server_requests, rq, prev, next); +} + +// -------------------------------------------------------------------------------------------------------------------- +// Sending and receiving requests + +typedef enum __attribute__((packed)) { + SPAWN_SERVER_MSG_INVALID = 0, + SPAWN_SERVER_MSG_REQUEST, + SPAWN_SERVER_MSG_PING, +} SPAWN_SERVER_MSG; + +static bool spawn_server_is_running(const char *path) { + struct msghdr msg = {0}; + struct iovec iov[7]; + SPAWN_SERVER_MSG msg_type = SPAWN_SERVER_MSG_PING; + size_t dummy_size = 0; + SPAWN_INSTANCE_TYPE dummy_type = 0; + ND_UUID magic = UUID_ZERO; + char cmsgbuf[CMSG_SPACE(sizeof(int))]; + + iov[0].iov_base = &msg_type; + iov[0].iov_len = sizeof(msg_type); + + iov[1].iov_base = magic.uuid; + iov[1].iov_len = sizeof(magic.uuid); + + iov[2].iov_base = &dummy_size; + iov[2].iov_len = sizeof(dummy_size); + + iov[3].iov_base = &dummy_size; + iov[3].iov_len = sizeof(dummy_size); + + iov[4].iov_base = &dummy_size; + iov[4].iov_len = sizeof(dummy_size); + + iov[5].iov_base = &dummy_size; + iov[5].iov_len = sizeof(dummy_size); + + iov[6].iov_base = &dummy_type; + iov[6].iov_len = sizeof(dummy_type); + + msg.msg_iov = iov; + msg.msg_iovlen = 7; + msg.msg_control = cmsgbuf; + msg.msg_controllen = sizeof(cmsgbuf); + + int sock = connect_to_spawn_server(path, false); + if(sock == -1) + return false; + + int rc = sendmsg(sock, &msg, 0); + if (rc < 0) { + // cannot send the message + close(sock); + return false; + } + + // Receive response + struct status_report sr = { 0 }; + if (read(sock, &sr, sizeof(sr)) != sizeof(sr)) { + // cannot receive a ping reply + close(sock); + return false; + } + + close(sock); + return sr.status == STATUS_REPORT_PING; +} + +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); + if (!encoded_env) + goto cleanup; + + size_t argv_size = 0; + void *encoded_argv = argv_encode(request->argv, &argv_size); + if (!encoded_argv) + goto cleanup; + + struct msghdr msg = {0}; + struct cmsghdr *cmsg; + SPAWN_SERVER_MSG msg_type = SPAWN_SERVER_MSG_REQUEST; + char cmsgbuf[CMSG_SPACE(sizeof(int) * SPAWN_SERVER_TRANSFER_FDS)]; + struct iovec iov[11]; + + + // We send 1 request with 10 iovec in it + // The request will be received in 2 parts + // 1. the first 6 iovec which include the sizes of the memory allocations required + // 2. the last 4 iovec which require the memory allocations to be received + + iov[0].iov_base = &msg_type; + iov[0].iov_len = sizeof(msg_type); + + iov[1].iov_base = magic->uuid; + iov[1].iov_len = sizeof(magic->uuid); + + iov[2].iov_base = &request->request_id; + iov[2].iov_len = sizeof(request->request_id); + + iov[3].iov_base = &env_size; + iov[3].iov_len = sizeof(env_size); + + iov[4].iov_base = &argv_size; + iov[4].iov_len = sizeof(argv_size); + + iov[5].iov_base = &request->data_size; + iov[5].iov_len = sizeof(request->data_size); + + iov[6].iov_base = &request->type; // Added this line + iov[6].iov_len = sizeof(request->type); + + iov[7].iov_base = encoded_env; + iov[7].iov_len = env_size; + + iov[8].iov_base = encoded_argv; + iov[8].iov_len = argv_size; + + iov[9].iov_base = (char *)request->data; + iov[9].iov_len = request->data_size; + + iov[10].iov_base = NULL; + iov[10].iov_len = 0; + + msg.msg_iov = iov; + msg.msg_iovlen = 11; + msg.msg_control = cmsgbuf; + msg.msg_controllen = CMSG_SPACE(sizeof(int) * SPAWN_SERVER_TRANSFER_FDS); + + cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(sizeof(int) * SPAWN_SERVER_TRANSFER_FDS); + + memcpy(CMSG_DATA(cmsg), request->fds, sizeof(int) * SPAWN_SERVER_TRANSFER_FDS); + + int rc = sendmsg(request->sock, &msg, 0); + + if (rc < 0) { + nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN PARENT: Failed to sendmsg() request to spawn server using socket %d.", request->sock); + goto cleanup; + } + else { + ret = true; + // fprintf(stderr, "PARENT: sent request %zu on socket %d (fds: %d, %d, %d, %d) from tid %d\n", + // request->request_id, request->socket, request->fds[0], request->fds[1], request->fds[2], request->fds[3], os_gettid()); + } + +cleanup: + freez(encoded_env); + freez(encoded_argv); + return ret; +} + +static void spawn_server_receive_request(int sock, SPAWN_SERVER *server) { + struct msghdr msg = {0}; + struct iovec iov[7]; + SPAWN_SERVER_MSG msg_type = SPAWN_SERVER_MSG_INVALID; + size_t request_id; + size_t env_size; + size_t argv_size; + size_t data_size; + ND_UUID magic = UUID_ZERO; + SPAWN_INSTANCE_TYPE type; + char cmsgbuf[CMSG_SPACE(sizeof(int) * SPAWN_SERVER_TRANSFER_FDS)]; + char *envp_encoded = NULL, *argv_encoded = NULL, *data = NULL; + int stdin_fd = -1, stdout_fd = -1, stderr_fd = -1, custom_fd = -1; + + // First recvmsg() to read sizes and control message + iov[0].iov_base = &msg_type; + iov[0].iov_len = sizeof(msg_type); + + iov[1].iov_base = magic.uuid; + iov[1].iov_len = sizeof(magic.uuid); + + iov[2].iov_base = &request_id; + iov[2].iov_len = sizeof(request_id); + + iov[3].iov_base = &env_size; + iov[3].iov_len = sizeof(env_size); + + iov[4].iov_base = &argv_size; + iov[4].iov_len = sizeof(argv_size); + + iov[5].iov_base = &data_size; + iov[5].iov_len = sizeof(data_size); + + iov[6].iov_base = &type; + iov[6].iov_len = sizeof(type); + + msg.msg_iov = iov; + msg.msg_iovlen = 7; + msg.msg_control = cmsgbuf; + msg.msg_controllen = sizeof(cmsgbuf); + + if (recvmsg(sock, &msg, 0) < 0) { + nd_log(NDLS_COLLECTORS, NDLP_ERR, + "SPAWN SERVER: failed to recvmsg() the first part of the request."); + close(sock); + return; + } + + if(msg_type == SPAWN_SERVER_MSG_PING) { + spawn_server_send_status_ping(sock); + close(sock); + return; + } + + if(!UUIDeq(magic, server->magic)) { + nd_log(NDLS_COLLECTORS, NDLP_ERR, + "SPAWN SERVER: Invalid authorization key for request %zu. " + "Rejecting request.", + request_id); + close(sock); + return; + } + + if(type == SPAWN_INSTANCE_TYPE_EXEC && !(server->options & SPAWN_SERVER_OPTION_EXEC)) { + nd_log(NDLS_COLLECTORS, NDLP_ERR, + "SPAWN SERVER: Request %zu wants to exec, but exec is not allowed for this spawn server. " + "Rejecting request.", + request_id); + close(sock); + return; + } + + if(type == SPAWN_INSTANCE_TYPE_CALLBACK && !(server->options & SPAWN_SERVER_OPTION_CALLBACK)) { + nd_log(NDLS_COLLECTORS, NDLP_ERR, + "SPAWN SERVER: Request %zu wants to run a callback, but callbacks are not allowed for this spawn server. " + "Rejecting request.", + request_id); + close(sock); + return; + } + + // Extract file descriptors from control message + struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); + if (cmsg == NULL || cmsg->cmsg_len != CMSG_LEN(sizeof(int) * SPAWN_SERVER_TRANSFER_FDS)) { + nd_log(NDLS_COLLECTORS, NDLP_ERR, + "SPAWN SERVER: Received invalid control message (expected %zu bytes, received %zu bytes)", + CMSG_LEN(sizeof(int) * SPAWN_SERVER_TRANSFER_FDS), cmsg?cmsg->cmsg_len:0); + close(sock); + return; + } + + if (cmsg->cmsg_level != SOL_SOCKET || cmsg->cmsg_type != SCM_RIGHTS) { + nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN SERVER: Received unexpected control message type."); + close(sock); + return; + } + + int *fds = (int *)CMSG_DATA(cmsg); + stdin_fd = fds[0]; + stdout_fd = fds[1]; + stderr_fd = fds[2]; + custom_fd = fds[3]; + + if (stdin_fd < 0 || stdout_fd < 0 || stderr_fd < 0) { + nd_log(NDLS_COLLECTORS, NDLP_ERR, + "SPAWN SERVER: invalid file descriptors received, stdin = %d, stdout = %d, stderr = %d", + stdin_fd, stdout_fd, stderr_fd); + goto cleanup; + } + + // Second recvmsg() to read buffer contents + iov[0].iov_base = envp_encoded = mallocz(env_size); + iov[0].iov_len = env_size; + iov[1].iov_base = argv_encoded = mallocz(argv_size); + iov[1].iov_len = argv_size; + iov[2].iov_base = data = mallocz(data_size); + iov[2].iov_len = data_size; + + msg.msg_iov = iov; + msg.msg_iovlen = 3; + msg.msg_control = NULL; + msg.msg_controllen = 0; + + ssize_t total_bytes_received = recvmsg(sock, &msg, 0); + if (total_bytes_received < 0) { + nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN SERVER: failed to recvmsg() the second part of the request."); + goto cleanup; + } + + // fprintf(stderr, "SPAWN SERVER: received request %zu (fds: %d, %d, %d, %d)\n", request_id, + // stdin_fd, stdout_fd, stderr_fd, custom_fd); + + SPAWN_REQUEST *rq = mallocz(sizeof(*rq)); + *rq = (SPAWN_REQUEST){ + .pid = 0, + .request_id = request_id, + .sock = sock, + .fds = { + [0] = stdin_fd, + [1] = stdout_fd, + [2] = stderr_fd, + [3] = custom_fd, + }, + .environment = argv_decode(envp_encoded, env_size), + .argv = argv_decode(argv_encoded, argv_size), + .data = data, + .data_size = data_size, + .type = type + }; + + // all allocations given to the request are now handled by this + spawn_server_execute_request(server, rq); + + // since we make rq->argv and rq->environment NULL when we keep it, + // we don't need these anymore. + freez(envp_encoded); + freez(argv_encoded); + return; + +cleanup: + close(sock); + if(stdin_fd != -1) close(stdin_fd); + if(stdout_fd != -1) close(stdout_fd); + if(stderr_fd != -1) close(stderr_fd); + if(custom_fd != -1) close(custom_fd); + freez(envp_encoded); + freez(argv_encoded); + freez(data); +} + +// -------------------------------------------------------------------------------------------------------------------- +// the spawn server main event loop + +static void spawn_server_sigchld_handler(int signo __maybe_unused) { + spawn_server_sigchld = true; +} + +static void spawn_server_sigterm_handler(int signo __maybe_unused) { + spawn_server_exit = true; +} + +static SPAWN_REQUEST *find_request_by_pid(pid_t pid) { + for(SPAWN_REQUEST *rq = spawn_server_requests; rq ;rq = rq->next) + if(rq->pid == pid) + return rq; + + return NULL; +} + +static void spawn_server_process_sigchld(void) { + // nd_log(NDLS_COLLECTORS, NDLP_INFO, "SPAWN SERVER: checking for exited children"); + + int status; + pid_t pid; + + // Loop to check for exited child processes + while ((pid = waitpid((pid_t)(-1), &status, WNOHANG)) != 0) { + if(pid == -1) + break; + + errno_clear(); + + SPAWN_REQUEST *rq = find_request_by_pid(pid); + size_t request_id = rq ? rq->request_id : 0; + bool send_report_remove_request = false; + + 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, 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, + "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, + "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, + "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, + "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, + "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; + } + + if(send_report_remove_request && rq) { + spawn_server_send_status_exit(rq, status); + DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(spawn_server_requests, rq, prev, next); + request_free(rq); + } + } +} + +static void signals_unblock(void) { + sigset_t sigset; + sigfillset(&sigset); + + if(pthread_sigmask(SIG_UNBLOCK, &sigset, NULL) == -1) { + netdata_log_error("SPAWN SERVER: Could not unblock signals for threads"); + } +} + +static void spawn_server_event_loop(SPAWN_SERVER *server) { + int pipe_fd = server->pipe[1]; + close(server->pipe[0]); server->pipe[0] = -1; + + signals_unblock(); + + // Set up the signal handler for SIGCHLD and SIGTERM + struct sigaction sa; + sa.sa_handler = spawn_server_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 SERVER: sigaction() failed for SIGCHLD"); + exit(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); + } + + struct status_report sr = { + .status = STATUS_REPORT_STARTED, + .started = { + .pid = getpid(), + }, + }; + 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); + } + + struct pollfd fds[2]; + fds[0].fd = server->sock; + fds[0].events = POLLIN; + fds[1].fd = pipe_fd; + fds[1].events = POLLHUP | POLLERR; + + while(!spawn_server_exit) { + int ret = poll(fds, 2, -1); + if (spawn_server_sigchld) { + spawn_server_sigchld = false; + spawn_server_process_sigchld(); + errno_clear(); + + if(ret == -1) + continue; + } + + if (ret == -1) { + nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN SERVER: poll() failed"); + break; + } + + 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"); + break; + } + + if (fds[0].revents & POLLIN) { + int sock = accept(server->sock, NULL, NULL); + if (sock == -1) { + nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN SERVER: accept() failed"); + continue; + } + + // do not fork this socket + sock_setcloexec(sock); + + // receive the request and process it + spawn_server_receive_request(sock, server); + } + } + + // Cleanup before exiting + unlink(server->path); + + // stop all children + if(spawn_server_requests) { + // nd_log(NDLS_COLLECTORS, NDLP_INFO, "SPAWN SERVER: killing all children..."); + size_t killed = 0; + for(SPAWN_REQUEST *rq = spawn_server_requests; rq ; rq = rq->next) { + kill(rq->pid, SIGTERM); + killed++; + } + while(spawn_server_requests) { + spawn_server_process_sigchld(); + tinysleep(); + } + // nd_log(NDLS_COLLECTORS, NDLP_INFO, "SPAWN SERVER: all %zu children finished", killed); + } + + exit(1); +} + +// -------------------------------------------------------------------------------------------------------------------- +// management of the spawn server + +void spawn_server_destroy(SPAWN_SERVER *server) { + if(server->pipe[0] != -1) close(server->pipe[0]); + if(server->pipe[1] != -1) close(server->pipe[1]); + if(server->sock != -1) close(server->sock); + + if(server->server_pid) { + kill(server->server_pid, SIGTERM); + waitpid(server->server_pid, NULL, 0); + } + + if(server->path) { + unlink(server->path); + freez(server->path); + } + + freez((void *)server->name); + freez(server); +} + +static bool spawn_server_create_listening_socket(SPAWN_SERVER *server) { + if(spawn_server_is_running(server->path)) { + nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN SERVER: Server is already listening on path '%s'", server->path); + return false; + } + + if ((server->sock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { + nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN SERVER: Failed to create socket()"); + return false; + } + + struct sockaddr_un server_addr = { + .sun_family = AF_UNIX, + }; + strcpy(server_addr.sun_path, server->path); + unlink(server->path); + errno = 0; + + if (bind(server->sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) { + nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN SERVER: Failed to bind()"); + return false; + } + + if (listen(server->sock, 5) == -1) { + nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN SERVER: Failed to listen()"); + return false; + } + + return true; +} + +static void replace_stdio_with_dev_null() { + 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)); + 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)); + 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)); + close(dev_null_fd); + return; + } + + // Close the original /dev/null file descriptor + close(dev_null_fd); +} + +SPAWN_SERVER* spawn_server_create(SPAWN_SERVER_OPTIONS options, const char *name, spawn_request_callback_t child_callback, int argc, const char **argv) { + SPAWN_SERVER *server = callocz(1, sizeof(SPAWN_SERVER)); + server->pipe[0] = -1; + server->pipe[1] = -1; + server->sock = -1; + server->cb = child_callback; + server->argc = argc; + server->argv = argv; + server->options = options; + server->id = __atomic_add_fetch(&spawn_server_id, 1, __ATOMIC_RELAXED); + os_uuid_generate_random(server->magic.uuid); + + char *runtime_directory = getenv("NETDATA_CACHE_DIR"); + if(runtime_directory && !*runtime_directory) runtime_directory = NULL; + if (runtime_directory) { + struct stat statbuf; + + if(!*runtime_directory) + // it is empty + runtime_directory = NULL; + + else if (stat(runtime_directory, &statbuf) == 0 && S_ISDIR(statbuf.st_mode)) { + // it exists and it is a directory + + if (access(runtime_directory, W_OK) != 0) { + // it is not writable by us + nd_log(NDLS_COLLECTORS, NDLP_ERR, "Runtime directory '%s' is not writable, falling back to '/tmp'", runtime_directory); + runtime_directory = NULL; + } + } + else { + // it does not exist + nd_log(NDLS_COLLECTORS, NDLP_ERR, "Runtime directory '%s' does not exist, falling back to '/tmp'", runtime_directory); + runtime_directory = NULL; + } + } + if(!runtime_directory) + runtime_directory = "/tmp"; + + char path[1024]; + if(name && *name) { + server->name = strdupz(name); + snprintf(path, sizeof(path), "%s/.netdata-spawn-%s.sock", runtime_directory, name); + } + else { + server->name = strdupz("unnamed"); + snprintf(path, sizeof(path), "%s/.netdata-spawn-%d-%zu.sock", runtime_directory, getpid(), server->id); + } + + server->path = strdupz(path); + + if (!spawn_server_create_listening_socket(server)) + goto cleanup; + + if (pipe(server->pipe) == -1) { + nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN SERVER: Cannot create status pipe()"); + goto cleanup; + } + + pid_t pid = fork(); + 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); + } + + 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); + } + else if (pid > 0) { + // the parent + server->server_pid = pid; + close(server->sock); server->sock = -1; + close(server->pipe[1]); server->pipe[1] = -1; + + struct status_report sr = { 0 }; + if (read(server->pipe[0], &sr, sizeof(sr)) != sizeof(sr)) { + nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN SERVER: cannot read() initial status report from spawn server"); + goto cleanup; + } + + if(sr.status != STATUS_REPORT_STARTED) { + nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN SERVER: server did not respond with success."); + goto cleanup; + } + + if(sr.started.pid != server->server_pid) { + nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN SERVER: server sent pid %d but we have created %d.", sr.started.pid, server->server_pid); + goto cleanup; + } + + return server; + } + + nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN SERVER: Cannot fork()"); + +cleanup: + spawn_server_destroy(server); + return NULL; +} + +// -------------------------------------------------------------------------------------------------------------------- +// creating spawn server instances + +void spawn_server_exec_destroy(SPAWN_INSTANCE *instance) { + if(instance->child_pid) kill(instance->child_pid, SIGTERM); + if(instance->write_fd != -1) close(instance->write_fd); + if(instance->read_fd != -1) close(instance->read_fd); + if(instance->sock != -1) close(instance->sock); + freez(instance); +} + +int spawn_server_exec_wait(SPAWN_SERVER *server __maybe_unused, SPAWN_INSTANCE *instance) { + int rc = -1; + + // close the child pipes, to make it exit + if(instance->write_fd != -1) { close(instance->write_fd); instance->write_fd = -1; } + if(instance->read_fd != -1) { close(instance->read_fd); instance->read_fd = -1; } + + // get the result + struct status_report sr = { 0 }; + if(read(instance->sock, &sr, sizeof(sr)) != sizeof(sr)) + nd_log(NDLS_COLLECTORS, NDLP_ERR, + "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; + + 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; + spawn_server_exec_destroy(instance); + return rc; +} + +int spawn_server_exec_kill(SPAWN_SERVER *server, SPAWN_INSTANCE *instance) { + // kill the child, if it is still running + if(instance->child_pid) kill(instance->child_pid, SIGTERM); + return spawn_server_exec_wait(server, instance); +} + +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 pipe_stdin[2] = { -1, -1 }, pipe_stdout[2] = { -1, -1 }; + + SPAWN_INSTANCE *instance = callocz(1, sizeof(SPAWN_INSTANCE)); + instance->read_fd = -1; + instance->write_fd = -1; + + instance->sock = connect_to_spawn_server(server->path, true); + if(instance->sock == -1) + goto cleanup; + + if (pipe(pipe_stdin) == -1) { + nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN PARENT: Cannot create stdin pipe()"); + goto cleanup; + } + + if (pipe(pipe_stdout) == -1) { + nd_log(NDLS_COLLECTORS, NDLP_ERR, "SPAWN PARENT: Cannot create stdout pipe()"); + goto cleanup; + } + + SPAWN_REQUEST request = { + .request_id = __atomic_add_fetch(&server->request_id, 1, __ATOMIC_RELAXED), + .sock = instance->sock, + .fds = { + [0] = pipe_stdin[0], + [1] = pipe_stdout[1], + [2] = stderr_fd, + [3] = custom_fd, + }, + .environment = (const char **)environ, + .argv = argv, + .data = data, + .data_size = data_size, + .type = type + }; + + if(!spawn_server_send_request(&server->magic, &request)) + goto cleanup; + + close(pipe_stdin[0]); pipe_stdin[0] = -1; + instance->write_fd = pipe_stdin[1]; pipe_stdin[1] = -1; + + close(pipe_stdout[1]); pipe_stdout[1] = -1; + instance->read_fd = pipe_stdout[0]; pipe_stdout[0] = -1; + + // copy the request id to the instance + instance->request_id = request.request_id; + + struct status_report sr = { 0 }; + if(read(instance->sock, &sr, sizeof(sr)) != sizeof(sr)) { + nd_log(NDLS_COLLECTORS, NDLP_ERR, + "SPAWN PARENT: Failed to exec spawn request %zu (cannot get initial status report)", + request.request_id); + goto cleanup; + } + + if(sr.magic != STATUS_REPORT_MAGIC) { + nd_log(NDLS_COLLECTORS, NDLP_ERR, + "SPAWN PARENT: Failed to exec spawn request %zu (invalid magic %#x in response)", + request.request_id, sr.magic); + goto cleanup; + } + + switch(sr.status) { + case STATUS_REPORT_STARTED: + instance->child_pid = sr.started.pid; + return instance; + + case STATUS_REPORT_FAILED: + errno = sr.failed.err_no; + nd_log(NDLS_COLLECTORS, NDLP_ERR, + "SPAWN PARENT: Failed to exec spawn request %zu (server reports failure, errno is updated)", + request.request_id); + errno = 0; + break; + + case STATUS_REPORT_EXITED: + errno = ENOEXEC; + nd_log(NDLS_COLLECTORS, NDLP_ERR, + "SPAWN PARENT: Failed to exec spawn request %zu (server reports exit, errno is updated)", + request.request_id); + errno = 0; + break; + + default: + errno = 0; + nd_log(NDLS_COLLECTORS, NDLP_ERR, + "SPAWN PARENT: Invalid status report to exec spawn request %zu (received invalid data)", + request.request_id); + break; + } + +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]); + spawn_server_exec_destroy(instance); + return NULL; +} + +#endif // !OS_WINDOWS diff --git a/src/libnetdata/spawn_server/spawn_server.h b/src/libnetdata/spawn_server/spawn_server.h new file mode 100644 index 00000000..5ba66ae3 --- /dev/null +++ b/src/libnetdata/spawn_server/spawn_server.h @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef SPAWN_SERVER_H +#define SPAWN_SERVER_H + +#define SPAWN_SERVER_TRANSFER_FDS 4 + +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 +// which is not available in Windows +typedef struct spawn_request { + const char *cmdline; // the cmd line of the command we should run + size_t request_id; // the incremental request id + 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 **argv; // the command line and its parameters + const void *data; // the data structure for the callback + size_t data_size; // the data structure size + SPAWN_INSTANCE_TYPE type; // the type of the 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 struct spawm_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); + +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_instance_read_fd(SPAWN_INSTANCE *si); +int spawn_server_instance_write_fd(SPAWN_INSTANCE *si); +pid_t spawn_server_instance_pid(SPAWN_INSTANCE *si); +void spawn_server_instance_read_fd_unset(SPAWN_INSTANCE *si); +void spawn_server_instance_write_fd_unset(SPAWN_INSTANCE *si); + +#endif //SPAWN_SERVER_H diff --git a/src/libnetdata/string/string.c b/src/libnetdata/string/string.c index 94c11f4b..257a3cc4 100644 --- a/src/libnetdata/string/string.c +++ b/src/libnetdata/string/string.c @@ -702,3 +702,8 @@ int string_unittest(size_t entries) { fprintf(stderr, "\n%zu errors found\n", errors); return errors ? 1 : 0; } + +void string_init(void) { + for (size_t i = 0; i != STRING_PARTITIONS; i++) + rw_spinlock_init(&string_base[i].spinlock); +} diff --git a/src/libnetdata/string/string.h b/src/libnetdata/string/string.h index f2ff9666..c44696be 100644 --- a/src/libnetdata/string/string.h +++ b/src/libnetdata/string/string.h @@ -34,4 +34,6 @@ void string_statistics(size_t *inserts, size_t *deletes, size_t *searches, size_ int string_unittest(size_t entries); +void string_init(void); + #endif diff --git a/src/libnetdata/threads/threads.c b/src/libnetdata/threads/threads.c index 0e12d173..36c63f4e 100644 --- a/src/libnetdata/threads/threads.c +++ b/src/libnetdata/threads/threads.c @@ -418,12 +418,14 @@ bool nd_thread_signaled_to_cancel(void) { // ---------------------------------------------------------------------------- // nd_thread_join -void nd_thread_join(ND_THREAD *nti) { - if(!nti) return; +int nd_thread_join(ND_THREAD *nti) { + if(!nti) + return ESRCH; int ret = pthread_join(nti->thread, NULL); - if(ret != 0) - nd_log(NDLS_DAEMON, NDLP_WARNING, "cannot join thread. pthread_join() failed with code %d.", ret); + if(ret != 0) { + nd_log(NDLS_DAEMON, NDLP_WARNING, "cannot join thread. pthread_join() failed with code %d. (tag=%s)", ret, nti->tag); + } else { nd_thread_status_set(nti, NETDATA_THREAD_STATUS_JOINED); @@ -434,4 +436,6 @@ void nd_thread_join(ND_THREAD *nti) { freez(nti); } + + return ret; } diff --git a/src/libnetdata/threads/threads.h b/src/libnetdata/threads/threads.h index a7204e2a..0b54a5fc 100644 --- a/src/libnetdata/threads/threads.h +++ b/src/libnetdata/threads/threads.h @@ -70,7 +70,7 @@ void netdata_threads_init_after_fork(size_t stacksize); void netdata_threads_init_for_external_plugins(size_t stacksize); ND_THREAD *nd_thread_create(const char *tag, NETDATA_THREAD_OPTIONS options, void *(*start_routine) (void *), void *arg); -void nd_thread_join(ND_THREAD * nti); +int nd_thread_join(ND_THREAD * nti); ND_THREAD *nd_thread_self(void); bool nd_thread_is_me(ND_THREAD *nti); |