src/libnetdata/paths/paths.c
// SPDX-License-Identifier: GPL-3.0-or-later
#include "paths.h"
static int is_procfs(const char *path, char **reason) {
#if defined(__APPLE__) || defined(__FreeBSD__)
(void)path;
(void)reason;
#else
struct statfs stat;
if (statfs(path, &stat) == -1) {
if (reason)
*reason = "failed to statfs()";
return -1;
}
#if defined PROC_SUPER_MAGIC
if (stat.f_type != PROC_SUPER_MAGIC) {
if (reason)
*reason = "type is not procfs";
return -1;
}
#endif
#endif
return 0;
}
static int is_sysfs(const char *path, char **reason) {
#if defined(__APPLE__) || defined(__FreeBSD__)
(void)path;
(void)reason;
#else
struct statfs stat;
if (statfs(path, &stat) == -1) {
if (reason)
*reason = "failed to statfs()";
return -1;
}
#if defined SYSFS_MAGIC
if (stat.f_type != SYSFS_MAGIC) {
if (reason)
*reason = "type is not sysfs";
return -1;
}
#endif
#endif
return 0;
}
int verify_netdata_host_prefix(bool log_msg) {
if(!netdata_configured_host_prefix)
netdata_configured_host_prefix = "";
if(!*netdata_configured_host_prefix)
return 0;
char path[FILENAME_MAX];
char *reason = "unknown reason";
errno_clear();
strncpyz(path, netdata_configured_host_prefix, sizeof(path) - 1);
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;
}
snprintfz(path, sizeof(path), "%s/proc", netdata_configured_host_prefix);
if(is_procfs(path, &reason) == -1)
goto failed;
snprintfz(path, sizeof(path), "%s/sys", netdata_configured_host_prefix);
if(is_sysfs(path, &reason) == -1)
goto failed;
if (netdata_configured_host_prefix && *netdata_configured_host_prefix) {
if (log_msg)
netdata_log_info("Using host prefix directory '%s'", netdata_configured_host_prefix);
}
return 0;
failed:
if (log_msg)
netdata_log_error("Ignoring host prefix '%s': path '%s' %s", netdata_configured_host_prefix, path, reason);
netdata_configured_host_prefix = "";
return -1;
}
size_t filename_from_path_entry(char out[FILENAME_MAX], const char *path, const char *entry, const char *extension) {
if(unlikely(!path || !*path)) path = ".";
if(unlikely(!entry)) entry = "";
// skip trailing slashes in path
size_t len = strlen(path);
while(len > 0 && path[len - 1] == '/') len--;
// skip leading slashes in subpath
while(entry[0] == '/') entry++;
// 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] == '/' && (*entry || len == 0)) {
slash = "";
len++;
}
else if(!*entry) {
// there is no entry
// no need for trailing slash
slash = "";
}
return snprintfz(out, FILENAME_MAX, "%.*s%s%s%s%s", (int)len, path, slash, entry,
extension && *extension ? "." : "",
extension && *extension ? extension : "");
}
char *filename_from_path_entry_strdupz(const char *path, const char *entry) {
char filename[FILENAME_MAX];
filename_from_path_entry(filename, path, entry, NULL);
return strdupz(filename);
}
bool filename_is_dir(const char *filename, bool create_it) {
CLEAN_CHAR_P *buffer = NULL;
size_t max_links = 100;
bool is_dir = false;
struct stat st;
while(max_links && stat(filename, &st) == 0) {
if ((st.st_mode & S_IFMT) == S_IFDIR)
is_dir = true;
else if ((st.st_mode & S_IFMT) == S_IFLNK) {
max_links--;
if(!buffer)
buffer = mallocz(FILENAME_MAX);
char link_dst[FILENAME_MAX];
ssize_t l = readlink(filename, link_dst, FILENAME_MAX - 1);
if (l > 0) {
link_dst[l] = '\0';
strncpyz(buffer, link_dst, FILENAME_MAX - 1);
filename = buffer;
continue;
}
}
break;
}
if(!is_dir && create_it && max_links == 100 && mkdir(filename, 0750) == 0)
is_dir = true;
return is_dir;
}
bool path_entry_is_dir(const char *path, const char *entry, bool create_it) {
char filename[FILENAME_MAX];
filename_from_path_entry(filename, path, entry, NULL);
return filename_is_dir(filename, create_it);
}
bool filename_is_file(const char *filename) {
CLEAN_CHAR_P *buffer = NULL;
size_t max_links = 100;
bool is_file = false;
struct stat st;
while(max_links && stat(filename, &st) == 0) {
if((st.st_mode & S_IFMT) == S_IFREG)
is_file = true;
else if((st.st_mode & S_IFMT) == S_IFLNK) {
max_links--;
if(!buffer)
buffer = mallocz(FILENAME_MAX);
char link_dst[FILENAME_MAX];
ssize_t l = readlink(filename, link_dst, FILENAME_MAX - 1);
if(l > 0) {
link_dst[l] = '\0';
strncpyz(buffer, link_dst, FILENAME_MAX - 1);
filename = buffer;
continue;
}
}
break;
}
return is_file;
}
bool path_entry_is_file(const char *path, const char *entry) {
char filename[FILENAME_MAX];
filename_from_path_entry(filename, path, entry, NULL);
return filename_is_file(filename);
}
void recursive_config_double_dir_load(const char *user_path, const char *stock_path, const char *entry, int (*callback)(const char *filename, void *data, bool stock_config), void *data, size_t depth) {
if(depth > 3) {
netdata_log_error("CONFIG: Max directory depth reached while reading user path '%s', stock path '%s', subpath '%s'", user_path, stock_path,
entry);
return;
}
if(!stock_path)
stock_path = user_path;
char *udir = filename_from_path_entry_strdupz(user_path, entry);
char *sdir = filename_from_path_entry_strdupz(stock_path, entry);
netdata_log_debug(D_HEALTH, "CONFIG traversing user-config directory '%s', stock config directory '%s'", udir, sdir);
DIR *dir = opendir(udir);
if (!dir) {
netdata_log_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')
) {
netdata_log_debug(D_HEALTH, "CONFIG ignoring user-config directory '%s/%s'", udir, de->d_name);
continue;
}
if(path_entry_is_dir(udir, de->d_name, false)) {
recursive_config_double_dir_load(udir, sdir, de->d_name, callback, data, depth + 1);
continue;
}
}
if(de->d_type == DT_UNKNOWN || de->d_type == DT_REG || de->d_type == DT_LNK) {
size_t len = strlen(de->d_name);
if(path_entry_is_file(udir, de->d_name) &&
len > 5 && !strcmp(&de->d_name[len - 5], ".conf")) {
char *filename = filename_from_path_entry_strdupz(udir, de->d_name);
netdata_log_debug(D_HEALTH, "CONFIG calling callback for user file '%s'", filename);
callback(filename, data, false);
freez(filename);
continue;
}
}
netdata_log_debug(D_HEALTH, "CONFIG ignoring user-config file '%s/%s' of type %d", udir, de->d_name, (int)de->d_type);
}
closedir(dir);
}
netdata_log_debug(D_HEALTH, "CONFIG traversing stock config directory '%s', user config directory '%s'", sdir, udir);
dir = opendir(sdir);
if (!dir) {
netdata_log_error("CONFIG cannot open stock config directory '%s'.", sdir);
}
else {
if (strcmp(udir, sdir)) {
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')
) {
netdata_log_debug(D_HEALTH, "CONFIG ignoring stock config directory '%s/%s'", sdir, de->d_name);
continue;
}
if(path_entry_is_dir(sdir, de->d_name, false)) {
// we recurse in stock subdirectory, only when there is no corresponding
// user subdirectory - to avoid reading the files twice
if(!path_entry_is_dir(udir, de->d_name, false))
recursive_config_double_dir_load(udir, sdir, de->d_name, callback, data, depth + 1);
continue;
}
}
if(de->d_type == DT_UNKNOWN || de->d_type == DT_REG || de->d_type == DT_LNK) {
size_t len = strlen(de->d_name);
if(path_entry_is_file(sdir, de->d_name) && !path_entry_is_file(udir, de->d_name) &&
len > 5 && !strcmp(&de->d_name[len - 5], ".conf")) {
char *filename = filename_from_path_entry_strdupz(sdir, de->d_name);
netdata_log_debug(D_HEALTH, "CONFIG calling callback for stock file '%s'", filename);
callback(filename, data, true);
freez(filename);
continue;
}
}
netdata_log_debug(D_HEALTH, "CONFIG ignoring stock-config file '%s/%s' of type %d", udir, de->d_name, (int)de->d_type);
}
}
closedir(dir);
}
netdata_log_debug(D_HEALTH, "CONFIG done traversing user-config directory '%s', stock config directory '%s'", udir, sdir);
freez(udir);
freez(sdir);
}