summaryrefslogtreecommitdiffstats
path: root/src/libnetdata
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-08-26 08:15:24 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-08-26 08:15:35 +0000
commitf09848204fa5283d21ea43e262ee41aa578e1808 (patch)
treec62385d7adf209fa6a798635954d887f718fb3fb /src/libnetdata
parentReleasing debian version 1.46.3-2. (diff)
downloadnetdata-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 '')
-rw-r--r--src/libnetdata/clocks/clocks.c34
-rw-r--r--src/libnetdata/json/json-c-parser-inline.h2
-rw-r--r--src/libnetdata/libnetdata.c104
-rw-r--r--src/libnetdata/libnetdata.h26
-rw-r--r--src/libnetdata/locks/locks.c82
-rw-r--r--src/libnetdata/locks/locks.h31
-rw-r--r--src/libnetdata/log/log.c136
-rw-r--r--src/libnetdata/log/log.h8
-rw-r--r--src/libnetdata/maps/local-sockets.h664
-rw-r--r--src/libnetdata/maps/system-services.h92
-rw-r--r--src/libnetdata/os/close_range.c98
-rw-r--r--src/libnetdata/os/close_range.h12
-rw-r--r--src/libnetdata/os/get_pid_max.c45
-rw-r--r--src/libnetdata/os/os-windows-wrappers.c61
-rw-r--r--src/libnetdata/os/os-windows-wrappers.h18
-rw-r--r--src/libnetdata/os/os.h4
-rw-r--r--src/libnetdata/os/setproctitle.c31
-rw-r--r--src/libnetdata/os/setproctitle.h8
-rw-r--r--src/libnetdata/os/waitid.c72
-rw-r--r--src/libnetdata/os/waitid.h48
-rw-r--r--src/libnetdata/popen/README.md15
-rw-r--r--src/libnetdata/popen/popen.c446
-rw-r--r--src/libnetdata/popen/popen.h35
-rw-r--r--src/libnetdata/procfile/procfile.c2
-rw-r--r--src/libnetdata/socket/socket.c10
-rw-r--r--src/libnetdata/spawn_server/spawn_popen.c142
-rw-r--r--src/libnetdata/spawn_server/spawn_popen.h24
-rw-r--r--src/libnetdata/spawn_server/spawn_server.c1533
-rw-r--r--src/libnetdata/spawn_server/spawn_server.h57
-rw-r--r--src/libnetdata/string/string.c5
-rw-r--r--src/libnetdata/string/string.h2
-rw-r--r--src/libnetdata/threads/threads.c12
-rw-r--r--src/libnetdata/threads/threads.h2
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, &current.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, &current.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);