summaryrefslogtreecommitdiffstats
path: root/libnetdata/libnetdata.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2018-11-07 12:19:29 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2018-11-07 12:20:17 +0000
commita64a253794ac64cb40befee54db53bde17dd0d49 (patch)
treec1024acc5f6e508814b944d99f112259bb28b1be /libnetdata/libnetdata.c
parentNew upstream version 1.10.0+dfsg (diff)
downloadnetdata-a64a253794ac64cb40befee54db53bde17dd0d49.tar.xz
netdata-a64a253794ac64cb40befee54db53bde17dd0d49.zip
New upstream version 1.11.0+dfsgupstream/1.11.0+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--libnetdata/libnetdata.c (renamed from src/common.c)696
1 files changed, 391 insertions, 305 deletions
diff --git a/src/common.c b/libnetdata/libnetdata.c
index 94fd5e42..de330cae 100644
--- a/src/common.c
+++ b/libnetdata/libnetdata.c
@@ -1,4 +1,6 @@
-#include "common.h"
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "libnetdata.h"
#ifdef __APPLE__
#define INHERIT_NONE 0
@@ -8,17 +10,6 @@
# define MADV_DONTFORK INHERIT_NONE
#endif /* __FreeBSD__ || __APPLE__*/
-char *netdata_configured_hostname = NULL;
-char *netdata_configured_config_dir = NULL;
-char *netdata_configured_log_dir = NULL;
-char *netdata_configured_plugins_dir = NULL;
-char *netdata_configured_web_dir = NULL;
-char *netdata_configured_cache_dir = NULL;
-char *netdata_configured_varlib_dir = NULL;
-char *netdata_configured_home_dir = NULL;
-char *netdata_configured_host_prefix = NULL;
-char *netdata_configured_timezone = NULL;
-
struct rlimit rlimit_nofile = { .rlim_cur = 1024, .rlim_max = 1024 };
int enable_ksm = 1;
@@ -36,182 +27,172 @@ const char *program_version = VERSION;
// routines.
#ifdef NETDATA_LOG_ALLOCATIONS
-static struct memory_statistics {
- volatile size_t malloc_calls_made;
- volatile size_t calloc_calls_made;
- volatile size_t realloc_calls_made;
- volatile size_t strdup_calls_made;
- volatile size_t free_calls_made;
- volatile size_t memory_calls_made;
- volatile size_t allocated_memory;
- volatile size_t mmapped_memory;
-} memory_statistics;
-
-static inline void print_allocations(const char *file, const char *function, const unsigned long line) {
- static struct memory_statistics old = { 0, 0, 0, 0, 0, 0, 0, 0 };
-
- //if(unlikely(!(memory_statistics.memory_calls_made % 5))) {
- fprintf(stderr, "(%04lu@%-10.10s:%-15.15s): Allocated %zu KB (+%zu B), mmapped %zu KB (+%zu B): malloc %zu (+%zu), calloc %zu (+%zu), realloc %zu (+%zu), strdup %zu (+%zu), free %zu (+%zu)\n",
- line, file, function,
- (memory_statistics.allocated_memory + 512) / 1024, memory_statistics.allocated_memory - old.allocated_memory,
- (memory_statistics.mmapped_memory + 512) / 1024, memory_statistics.mmapped_memory - old.mmapped_memory,
- memory_statistics.malloc_calls_made, memory_statistics.malloc_calls_made - old.malloc_calls_made,
- memory_statistics.calloc_calls_made, memory_statistics.calloc_calls_made - old.calloc_calls_made,
- memory_statistics.realloc_calls_made, memory_statistics.realloc_calls_made - old.realloc_calls_made,
- memory_statistics.strdup_calls_made, memory_statistics.strdup_calls_made - old.strdup_calls_made,
- memory_statistics.free_calls_made, memory_statistics.free_calls_made - old.free_calls_made
- );
-
- memcpy(&old, &memory_statistics, sizeof(struct memory_statistics));
- //}
+static __thread struct memory_statistics {
+ volatile ssize_t malloc_calls_made;
+ volatile ssize_t calloc_calls_made;
+ volatile ssize_t realloc_calls_made;
+ volatile ssize_t strdup_calls_made;
+ volatile ssize_t free_calls_made;
+ volatile ssize_t memory_calls_made;
+ volatile ssize_t allocated_memory;
+ volatile ssize_t mmapped_memory;
+} memory_statistics = { 0, 0, 0, 0, 0, 0, 0, 0 };
+
+__thread size_t log_thread_memory_allocations = 0;
+
+static inline void print_allocations(const char *file, const char *function, const unsigned long line, const char *type, size_t size) {
+ static __thread struct memory_statistics old = { 0, 0, 0, 0, 0, 0, 0, 0 };
+
+ fprintf(stderr, "%s iteration %zu MEMORY TRACE: %lu@%s : %s : %s : %zu\n",
+ netdata_thread_tag(),
+ log_thread_memory_allocations,
+ line, file, function,
+ type, size
+ );
+
+ fprintf(stderr, "%s iteration %zu MEMORY ALLOCATIONS: (%04lu@%-40.40s:%-40.40s): Allocated %zd KB (%+zd B), mmapped %zd KB (%+zd B): %s : malloc %zd (%+zd), calloc %zd (%+zd), realloc %zd (%+zd), strdup %zd (%+zd), free %zd (%+zd)\n",
+ netdata_thread_tag(),
+ log_thread_memory_allocations,
+ line, file, function,
+ (memory_statistics.allocated_memory + 512) / 1024, memory_statistics.allocated_memory - old.allocated_memory,
+ (memory_statistics.mmapped_memory + 512) / 1024, memory_statistics.mmapped_memory - old.mmapped_memory,
+ type,
+ memory_statistics.malloc_calls_made, memory_statistics.malloc_calls_made - old.malloc_calls_made,
+ memory_statistics.calloc_calls_made, memory_statistics.calloc_calls_made - old.calloc_calls_made,
+ memory_statistics.realloc_calls_made, memory_statistics.realloc_calls_made - old.realloc_calls_made,
+ memory_statistics.strdup_calls_made, memory_statistics.strdup_calls_made - old.strdup_calls_made,
+ memory_statistics.free_calls_made, memory_statistics.free_calls_made - old.free_calls_made
+ );
+
+ memcpy(&old, &memory_statistics, sizeof(struct memory_statistics));
}
-static inline void malloc_accounting(const char *file, const char *function, const unsigned long line, size_t size) {
-#if defined(HAVE_C___ATOMIC) && !defined(NETDATA_NO_ATOMIC_INSTRUCTIONS)
- __atomic_fetch_add(&memory_statistics.memory_calls_made, 1, __ATOMIC_SEQ_CST);
- __atomic_fetch_add(&memory_statistics.malloc_calls_made, 1, __ATOMIC_SEQ_CST);
- __atomic_fetch_add(&memory_statistics.allocated_memory, size, __ATOMIC_SEQ_CST);
-#else
- // this is for debugging - we don't care locking it
- memory_statistics.memory_calls_made++;
- memory_statistics.malloc_calls_made++;
- memory_statistics.allocated_memory += size;
-#endif
- print_allocations(file, function, line);
+static inline void mmap_accounting(size_t size) {
+ if(log_thread_memory_allocations) {
+ memory_statistics.memory_calls_made++;
+ memory_statistics.mmapped_memory += size;
+ }
}
-static inline void mmap_accounting(size_t size) {
-#if defined(HAVE_C___ATOMIC) && !defined(NETDATA_NO_ATOMIC_INSTRUCTIONS)
- __atomic_fetch_add(&memory_statistics.malloc_calls_made, 1, __ATOMIC_SEQ_CST);
- __atomic_fetch_add(&memory_statistics.mmapped_memory, size, __ATOMIC_SEQ_CST);
-#else
- // this is for debugging - we don't care locking it
- memory_statistics.memory_calls_made++;
- memory_statistics.mmapped_memory += size;
-#endif
+void *mallocz_int(const char *file, const char *function, const unsigned long line, size_t size) {
+ if(log_thread_memory_allocations) {
+ memory_statistics.memory_calls_made++;
+ memory_statistics.malloc_calls_made++;
+ memory_statistics.allocated_memory += size;
+ print_allocations(file, function, line, "malloc()", size);
+ }
+
+ size_t *n = (size_t *)malloc(sizeof(size_t) + size);
+ if (unlikely(!n)) fatal("mallocz() cannot allocate %zu bytes of memory.", size);
+ *n = size;
+ return (void *)&n[1];
}
-static inline void calloc_accounting(const char *file, const char *function, const unsigned long line, size_t size) {
-#if defined(HAVE_C___ATOMIC) && !defined(NETDATA_NO_ATOMIC_INSTRUCTIONS)
- __atomic_fetch_add(&memory_statistics.memory_calls_made, 1, __ATOMIC_SEQ_CST);
- __atomic_fetch_add(&memory_statistics.calloc_calls_made, 1, __ATOMIC_SEQ_CST);
- __atomic_fetch_add(&memory_statistics.allocated_memory, size, __ATOMIC_SEQ_CST);
-#else
- // this is for debugging - we don't care locking it
- memory_statistics.memory_calls_made++;
- memory_statistics.calloc_calls_made++;
- memory_statistics.allocated_memory += size;
-#endif
- print_allocations(file, function, line);
+void *callocz_int(const char *file, const char *function, const unsigned long line, size_t nmemb, size_t size) {
+ size = nmemb * size;
+
+ if(log_thread_memory_allocations) {
+ memory_statistics.memory_calls_made++;
+ memory_statistics.calloc_calls_made++;
+ memory_statistics.allocated_memory += size;
+ print_allocations(file, function, line, "calloc()", size);
+ }
+
+ size_t *n = (size_t *)calloc(1, sizeof(size_t) + size);
+ if (unlikely(!n)) fatal("callocz() cannot allocate %zu bytes of memory.", size);
+ *n = size;
+ return (void *)&n[1];
}
-static inline void realloc_accounting(const char *file, const char *function, const unsigned long line, void *ptr, size_t size) {
- (void)ptr;
+void *reallocz_int(const char *file, const char *function, const unsigned long line, void *ptr, size_t size) {
+ if(!ptr) return mallocz_int(file, function, line, size);
-#if defined(HAVE_C___ATOMIC) && !defined(NETDATA_NO_ATOMIC_INSTRUCTIONS)
- __atomic_fetch_add(&memory_statistics.memory_calls_made, 1, __ATOMIC_SEQ_CST);
- __atomic_fetch_add(&memory_statistics.realloc_calls_made, 1, __ATOMIC_SEQ_CST);
- __atomic_fetch_add(&memory_statistics.allocated_memory, size, __ATOMIC_SEQ_CST);
-#else
- // this is for debugging - we don't care locking it
- memory_statistics.memory_calls_made++;
- memory_statistics.realloc_calls_made++;
- memory_statistics.allocated_memory += size;
-#endif
- print_allocations(file, function, line);
+ size_t *n = (size_t *)ptr;
+ n--;
+ size_t old_size = *n;
+
+ n = realloc(n, sizeof(size_t) + size);
+ if (unlikely(!n)) fatal("reallocz() cannot allocate %zu bytes of memory (from %zu bytes).", size, old_size);
+
+ if(log_thread_memory_allocations) {
+ memory_statistics.memory_calls_made++;
+ memory_statistics.realloc_calls_made++;
+ memory_statistics.allocated_memory += (size - old_size);
+ print_allocations(file, function, line, "realloc()", size - old_size);
+ }
+
+ *n = size;
+ return (void *)&n[1];
}
-static inline void strdup_accounting(const char *file, const char *function, const unsigned long line, const char *s) {
+char *strdupz_int(const char *file, const char *function, const unsigned long line, const char *s) {
size_t size = strlen(s) + 1;
-#if defined(HAVE_C___ATOMIC) && !defined(NETDATA_NO_ATOMIC_INSTRUCTIONS)
- __atomic_fetch_add(&memory_statistics.memory_calls_made, 1, __ATOMIC_SEQ_CST);
- __atomic_fetch_add(&memory_statistics.strdup_calls_made, 1, __ATOMIC_SEQ_CST);
- __atomic_fetch_add(&memory_statistics.allocated_memory, size, __ATOMIC_SEQ_CST);
-#else
- // this is for debugging - we don't care locking it
- memory_statistics.memory_calls_made++;
- memory_statistics.strdup_calls_made++;
- memory_statistics.allocated_memory += size;
-#endif
- print_allocations(file, function, line);
+ if(log_thread_memory_allocations) {
+ memory_statistics.memory_calls_made++;
+ memory_statistics.strdup_calls_made++;
+ memory_statistics.allocated_memory += size;
+ print_allocations(file, function, line, "strdup()", size);
+ }
+
+ size_t *n = (size_t *)malloc(sizeof(size_t) + size);
+ if (unlikely(!n)) fatal("strdupz() cannot allocate %zu bytes of memory.", size);
+
+ *n = size;
+ char *t = (char *)&n[1];
+ strcpy(t, s);
+ return t;
}
-static inline void free_accounting(const char *file, const char *function, const unsigned long line, void *ptr) {
- (void)file;
- (void)function;
- (void)line;
+void freez_int(const char *file, const char *function, const unsigned long line, void *ptr) {
+ if(unlikely(!ptr)) return;
- if(likely(ptr)) {
-#if defined(HAVE_C___ATOMIC) && !defined(NETDATA_NO_ATOMIC_INSTRUCTIONS)
- __atomic_fetch_add(&memory_statistics.memory_calls_made, 1, __ATOMIC_SEQ_CST);
- __atomic_fetch_add(&memory_statistics.free_calls_made, 1, __ATOMIC_SEQ_CST);
-#else
- // this is for debugging - we don't care locking it
+ size_t *n = (size_t *)ptr;
+ n--;
+ size_t size = *n;
+
+ if(log_thread_memory_allocations) {
memory_statistics.memory_calls_made++;
memory_statistics.free_calls_made++;
-#endif
+ memory_statistics.allocated_memory -= size;
+ print_allocations(file, function, line, "free()", size);
}
-}
-#endif
-#ifdef NETDATA_LOG_ALLOCATIONS
-char *strdupz_int(const char *file, const char *function, const unsigned long line, const char *s) {
- strdup_accounting(file, function, line, s);
+ free(n);
+}
#else
-char *strdupz(const char *s) {
-#endif
+char *strdupz(const char *s) {
char *t = strdup(s);
if (unlikely(!t)) fatal("Cannot strdup() string '%s'", s);
return t;
}
-#ifdef NETDATA_LOG_ALLOCATIONS
-void *mallocz_int(const char *file, const char *function, const unsigned long line, size_t size) {
- malloc_accounting(file, function, line, size);
-#else
-void *mallocz(size_t size) {
-#endif
+void freez(void *ptr) {
+ free(ptr);
+}
+void *mallocz(size_t size) {
void *p = malloc(size);
if (unlikely(!p)) fatal("Cannot allocate %zu bytes of memory.", size);
return p;
}
-#ifdef NETDATA_LOG_ALLOCATIONS
-void *callocz_int(const char *file, const char *function, const unsigned long line, size_t nmemb, size_t size) {
- calloc_accounting(file, function, line, nmemb * size);
-#else
void *callocz(size_t nmemb, size_t size) {
-#endif
-
void *p = calloc(nmemb, size);
if (unlikely(!p)) fatal("Cannot allocate %zu bytes of memory.", nmemb * size);
return p;
}
-#ifdef NETDATA_LOG_ALLOCATIONS
-void *reallocz_int(const char *file, const char *function, const unsigned long line, void *ptr, size_t size) {
- realloc_accounting(file, function, line, ptr, size);
-#else
void *reallocz(void *ptr, size_t size) {
-#endif
-
void *p = realloc(ptr, size);
if (unlikely(!p)) fatal("Cannot re-allocate memory to %zu bytes.", size);
return p;
}
-#ifdef NETDATA_LOG_ALLOCATIONS
-void freez_int(const char *file, const char *function, const unsigned long line, void *ptr) {
- free_accounting(file, function, line, ptr);
-#else
-void freez(void *ptr) {
#endif
- free(ptr);
-}
+// --------------------------------------------------------------------------------------------------------------------
void json_escape_string(char *dst, const char *src, size_t size) {
const char *t;
@@ -244,52 +225,6 @@ void json_fix_string(char *s) {
}
}
-int sleep_usec(usec_t usec) {
-
-#ifndef NETDATA_WITH_USLEEP
- // we expect microseconds (1.000.000 per second)
- // but timespec is nanoseconds (1.000.000.000 per second)
- struct timespec rem, req = {
- .tv_sec = (time_t) (usec / 1000000),
- .tv_nsec = (suseconds_t) ((usec % 1000000) * 1000)
- };
-
- while (nanosleep(&req, &rem) == -1) {
- if (likely(errno == EINTR)) {
- debug(D_SYSTEM, "nanosleep() interrupted (while sleeping for %llu microseconds).", usec);
- req.tv_sec = rem.tv_sec;
- req.tv_nsec = rem.tv_nsec;
- } else {
- error("Cannot nanosleep() for %llu microseconds.", usec);
- break;
- }
- }
-
- return 0;
-#else
- int ret = usleep(usec);
- if(unlikely(ret == -1 && errno == EINVAL)) {
- // on certain systems, usec has to be up to 999999
- if(usec > 999999) {
- int counter = usec / 999999;
- while(counter--)
- usleep(999999);
-
- usleep(usec % 999999);
- }
- else {
- error("Cannot usleep() for %llu microseconds.", usec);
- return ret;
- }
- }
-
- if(ret != 0)
- error("usleep() failed for %llu microseconds.", usec);
-
- return ret;
-#endif
-}
-
unsigned char netdata_map_chart_names[256] = {
[0] = '\0', //
[1] = '_', //
@@ -1183,124 +1118,6 @@ int snprintfz(char *dst, size_t n, const char *fmt, ...) {
return ret;
}
-// ----------------------------------------------------------------------------
-// system functions
-// to retrieve settings of the system
-
-int processors = 1;
-long get_system_cpus(void) {
- processors = 1;
-
- #ifdef __APPLE__
- int32_t tmp_processors;
-
- if (unlikely(GETSYSCTL_BY_NAME("hw.logicalcpu", tmp_processors))) {
- error("Assuming system has %d processors.", processors);
- } else {
- processors = tmp_processors;
- }
-
- return processors;
- #elif __FreeBSD__
- int32_t tmp_processors;
-
- if (unlikely(GETSYSCTL_BY_NAME("hw.ncpu", tmp_processors))) {
- error("Assuming system has %d processors.", processors);
- } else {
- processors = tmp_processors;
- }
-
- return processors;
- #else
-
- char filename[FILENAME_MAX + 1];
- snprintfz(filename, FILENAME_MAX, "%s/proc/stat", netdata_configured_host_prefix);
-
- procfile *ff = procfile_open(filename, NULL, PROCFILE_FLAG_DEFAULT);
- if(!ff) {
- error("Cannot open file '%s'. Assuming system has %d processors.", filename, processors);
- return processors;
- }
-
- ff = procfile_readall(ff);
- if(!ff) {
- error("Cannot open file '%s'. Assuming system has %d processors.", filename, processors);
- return processors;
- }
-
- processors = 0;
- unsigned int i;
- for(i = 0; i < procfile_lines(ff); i++) {
- if(!procfile_linewords(ff, i)) continue;
-
- if(strncmp(procfile_lineword(ff, i, 0), "cpu", 3) == 0) processors++;
- }
- processors--;
- if(processors < 1) processors = 1;
-
- procfile_close(ff);
-
- debug(D_SYSTEM, "System has %d processors.", processors);
- return processors;
-
- #endif /* __APPLE__, __FreeBSD__ */
-}
-
-pid_t pid_max = 32768;
-pid_t get_system_pid_max(void) {
- #ifdef __APPLE__
- // 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 __FreeBSD__
- int32_t tmp_pid_max;
-
- if (unlikely(GETSYSCTL_BY_NAME("kern.pid_max", tmp_pid_max))) {
- pid_max = 99999;
- error("Assuming system's maximum pid is %d.", pid_max);
- } else {
- pid_max = tmp_pid_max;
- }
-
- return pid_max;
- #else
-
- 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);
-
- unsigned long long max = 0;
- if(read_single_number_file(filename, &max) != 0) {
- error("Cannot open file '%s'. Assuming system supports %d pids.", filename, pid_max);
- return pid_max;
- }
-
- if(!max) {
- error("Cannot parse file '%s'. Assuming system supports %d pids.", filename, pid_max);
- return pid_max;
- }
-
- pid_max = (pid_t) max;
- return pid_max;
-
- #endif /* __APPLE__, __FreeBSD__ */
-}
-
-unsigned int hz;
-void get_system_HZ(void) {
- long ticks;
-
- if ((ticks = sysconf(_SC_CLK_TCK)) == -1) {
- error("Cannot get system clock ticks");
- }
-
- hz = (unsigned int) ticks;
-}
-
/*
// poor man cycle counting
static unsigned long tsc;
@@ -1359,3 +1176,272 @@ int recursively_delete_dir(const char *path, const char *reason) {
return ret;
}
+
+static int is_virtual_filesystem(const char *path, char **reason) {
+
+#if defined(__APPLE__) || defined(__FreeBSD__)
+ (void)path;
+ (void)reason;
+#else
+ struct statfs stat;
+ // stat.f_fsid.__val[0] is a file system id
+ // stat.f_fsid.__val[1] is the inode
+ // so their combination uniquely identifies the file/dir
+
+ if (statfs(path, &stat) == -1) {
+ if(reason) *reason = "failed to statfs()";
+ return -1;
+ }
+
+ if(stat.f_fsid.__val[0] != 0 || stat.f_fsid.__val[1] != 0) {
+ errno = EINVAL;
+ if(reason) *reason = "is not a virtual file system";
+ return -1;
+ }
+#endif
+
+ return 0;
+}
+
+int verify_netdata_host_prefix() {
+ if(!netdata_configured_host_prefix)
+ netdata_configured_host_prefix = "";
+
+ if(!*netdata_configured_host_prefix)
+ return 0;
+
+ char buffer[FILENAME_MAX + 1];
+ char *path = netdata_configured_host_prefix;
+ char *reason = "unknown reason";
+ errno = 0;
+
+ struct stat sb;
+ if (stat(path, &sb) == -1) {
+ reason = "failed to stat()";
+ goto failed;
+ }
+
+ if((sb.st_mode & S_IFMT) != S_IFDIR) {
+ errno = EINVAL;
+ reason = "is not a directory";
+ goto failed;
+ }
+
+ path = buffer;
+ snprintfz(path, FILENAME_MAX, "%s/proc", netdata_configured_host_prefix);
+ if(is_virtual_filesystem(path, &reason) == -1)
+ goto failed;
+
+ snprintfz(path, FILENAME_MAX, "%s/sys", netdata_configured_host_prefix);
+ if(is_virtual_filesystem(path, &reason) == -1)
+ goto failed;
+
+ if(netdata_configured_host_prefix && *netdata_configured_host_prefix)
+ info("Using host prefix directory '%s'", netdata_configured_host_prefix);
+
+ return 0;
+
+failed:
+ error("Ignoring host prefix '%s': path '%s' %s", netdata_configured_host_prefix, path, reason);
+ netdata_configured_host_prefix = "";
+ return -1;
+}
+
+char *strdupz_path_subpath(const char *path, const char *subpath) {
+ if(unlikely(!path || !*path)) path = ".";
+ if(unlikely(!subpath)) subpath = "";
+
+ // skip trailing slashes in path
+ size_t len = strlen(path);
+ while(len > 0 && path[len - 1] == '/') len--;
+
+ // skip leading slashes in subpath
+ while(subpath[0] == '/') subpath++;
+
+ // if the last character in path is / and (there is a subpath or path is now empty)
+ // keep the trailing slash in path and remove the additional slash
+ char *slash = "/";
+ if(path[len] == '/' && (*subpath || len == 0)) {
+ slash = "";
+ len++;
+ }
+ else if(!*subpath) {
+ // there is no subpath
+ // no need for trailing slash
+ slash = "";
+ }
+
+ char buffer[FILENAME_MAX + 1];
+ snprintfz(buffer, FILENAME_MAX, "%.*s%s%s", (int)len, path, slash, subpath);
+ return strdupz(buffer);
+}
+
+int path_is_dir(const char *path, const char *subpath) {
+ char *s = strdupz_path_subpath(path, subpath);
+
+ size_t max_links = 100;
+
+ int is_dir = 0;
+ struct stat statbuf;
+ while(max_links-- && stat(s, &statbuf) == 0) {
+ if((statbuf.st_mode & S_IFMT) == S_IFDIR) {
+ is_dir = 1;
+ break;
+ }
+ else if((statbuf.st_mode & S_IFMT) == S_IFLNK) {
+ char buffer[FILENAME_MAX + 1];
+ ssize_t l = readlink(s, buffer, FILENAME_MAX);
+ if(l > 0) {
+ buffer[l] = '\0';
+ freez(s);
+ s = strdupz(buffer);
+ continue;
+ }
+ else {
+ is_dir = 0;
+ break;
+ }
+ }
+ else {
+ is_dir = 0;
+ break;
+ }
+ }
+
+ freez(s);
+ return is_dir;
+}
+
+int path_is_file(const char *path, const char *subpath) {
+ char *s = strdupz_path_subpath(path, subpath);
+
+ size_t max_links = 100;
+
+ int is_file = 0;
+ struct stat statbuf;
+ while(max_links-- && stat(s, &statbuf) == 0) {
+ if((statbuf.st_mode & S_IFMT) == S_IFREG) {
+ is_file = 1;
+ break;
+ }
+ else if((statbuf.st_mode & S_IFMT) == S_IFLNK) {
+ char buffer[FILENAME_MAX + 1];
+ ssize_t l = readlink(s, buffer, FILENAME_MAX);
+ if(l > 0) {
+ buffer[l] = '\0';
+ freez(s);
+ s = strdupz(buffer);
+ continue;
+ }
+ else {
+ is_file = 0;
+ break;
+ }
+ }
+ else {
+ is_file = 0;
+ break;
+ }
+ }
+
+ freez(s);
+ return is_file;
+}
+
+void recursive_config_double_dir_load(const char *user_path, const char *stock_path, const char *subpath, int (*callback)(const char *filename, void *data), void *data, size_t depth) {
+ if(depth > 3) {
+ error("CONFIG: Max directory depth reached while reading user path '%s', stock path '%s', subpath '%s'", user_path, stock_path, subpath);
+ return;
+ }
+
+ char *udir = strdupz_path_subpath(user_path, subpath);
+ char *sdir = strdupz_path_subpath(stock_path, subpath);
+
+ debug(D_HEALTH, "CONFIG traversing user-config directory '%s', stock config directory '%s'", udir, sdir);
+
+ DIR *dir = opendir(udir);
+ if (!dir) {
+ error("CONFIG cannot open user-config directory '%s'.", udir);
+ }
+ else {
+ struct dirent *de = NULL;
+ while((de = readdir(dir))) {
+ if(de->d_type == DT_DIR || de->d_type == DT_LNK) {
+ if( !de->d_name[0] ||
+ (de->d_name[0] == '.' && de->d_name[1] == '\0') ||
+ (de->d_name[0] == '.' && de->d_name[1] == '.' && de->d_name[2] == '\0')
+ ) {
+ debug(D_HEALTH, "CONFIG ignoring user-config directory '%s/%s'", udir, de->d_name);
+ continue;
+ }
+
+ if(path_is_dir(udir, de->d_name)) {
+ recursive_config_double_dir_load(udir, sdir, de->d_name, callback, data, depth + 1);
+ continue;
+ }
+ }
+
+ if(de->d_type == DT_REG || de->d_type == DT_LNK) {
+ size_t len = strlen(de->d_name);
+ if(path_is_file(udir, de->d_name) &&
+ len > 5 && !strcmp(&de->d_name[len - 5], ".conf")) {
+ char *filename = strdupz_path_subpath(udir, de->d_name);
+ callback(filename, data);
+ freez(filename);
+ }
+ else
+ debug(D_HEALTH, "CONFIG ignoring user-config file '%s/%s'", udir, de->d_name);
+ }
+ }
+
+ closedir(dir);
+ }
+
+ debug(D_HEALTH, "CONFIG traversing stock config directory '%s', user config directory '%s'", sdir, udir);
+
+ dir = opendir(sdir);
+ if (!dir) {
+ error("CONFIG cannot open stock config directory '%s'.", sdir);
+ }
+ else {
+ struct dirent *de = NULL;
+ while((de = readdir(dir))) {
+ if(de->d_type == DT_DIR || de->d_type == DT_LNK) {
+ if( !de->d_name[0] ||
+ (de->d_name[0] == '.' && de->d_name[1] == '\0') ||
+ (de->d_name[0] == '.' && de->d_name[1] == '.' && de->d_name[2] == '\0')
+ ) {
+ debug(D_HEALTH, "CONFIG ignoring stock config directory '%s/%s'", sdir, de->d_name);
+ continue;
+ }
+
+ if(path_is_dir(sdir, de->d_name)) {
+ // we recurse in stock subdirectory, only when there is no corresponding
+ // user subdirectory - to avoid reading the files twice
+
+ if(!path_is_dir(udir, de->d_name))
+ recursive_config_double_dir_load(udir, sdir, de->d_name, callback, data, depth + 1);
+
+ continue;
+ }
+ }
+
+ if(de->d_type == DT_REG || de->d_type == DT_LNK) {
+ size_t len = strlen(de->d_name);
+ if(path_is_file(sdir, de->d_name) && !path_is_file(udir, de->d_name) &&
+ len > 5 && !strcmp(&de->d_name[len - 5], ".conf")) {
+ char *filename = strdupz_path_subpath(sdir, de->d_name);
+ callback(filename, data);
+ freez(filename);
+ }
+ else
+ debug(D_HEALTH, "CONFIG ignoring stock config file '%s/%s'", sdir, de->d_name);
+ }
+ }
+
+ closedir(dir);
+ }
+
+ freez(udir);
+ freez(sdir);
+}