@cryptotaxi247 / netdata-1 / commits / 283900dee

Translate Path (Windows) (#22194)

thiagoftsm committed May 21, 2026 at 14:12 UTC 283900dee677ae7a8480be4516cfa2a0a9bf3947
26 files changed +821 -56
CMakeLists.txt
+41
@@ -95,6 +95,47 @@ include(NetdataCompilerFlags)
95 set(CONFIG_H_DIR ${CMAKE_BINARY_DIR})
96 set(CONFIG_H ${CONFIG_H_DIR}/config.h)
97
98 +function(netdata_windows_path_to_runtime_path output_var input_path)
99 + set(_converted_path "")
100 + set(_cygpath_result 1)
101 +
102 + find_program(_cygpath_executable NAMES cygpath)
103 + if(_cygpath_executable)
104 + execute_process(
105 + COMMAND ${_cygpath_executable} -u "${input_path}"
106 + RESULT_VARIABLE _cygpath_result
107 + OUTPUT_VARIABLE _converted_path
108 + OUTPUT_STRIP_TRAILING_WHITESPACE
109 + ERROR_QUIET)
110 + endif()
111 +
112 + if(NOT _converted_path OR NOT _cygpath_result EQUAL 0)
113 + string(REPLACE "\\" "/" _converted_path "${input_path}")
114 +
115 + if(_converted_path MATCHES "^([A-Za-z]):(.*)$")
116 + string(TOLOWER "${CMAKE_MATCH_1}" _drive_letter)
117 + set(_converted_path "/${_drive_letter}${CMAKE_MATCH_2}")
118 + endif()
119 + endif()
120 +
121 + if(NOT _converted_path STREQUAL "")
122 + string(REGEX REPLACE "/$" "" _converted_path "${_converted_path}")
123 + endif()
124 +
125 + set(${output_var} "${_converted_path}" PARENT_SCOPE)
126 +endfunction()
127 +
128 +if(OS_WINDOWS)
129 + set(NETDATA_WINDOWS_PATH_PREFIX "C:\\Program Files\\Netdata" CACHE STRING
130 + "Native Windows install prefix used to derive runtime paths")
131 + string(REGEX REPLACE "[/\\\\]+$" "" NETDATA_WINDOWS_PATH_PREFIX "${NETDATA_WINDOWS_PATH_PREFIX}")
132 + string(REPLACE "\\" "\\\\" NETDATA_WINDOWS_PATH_PREFIX_ESCAPED "${NETDATA_WINDOWS_PATH_PREFIX}")
133 +
134 + if(NOT BUILD_FOR_PACKAGING)
135 + netdata_windows_path_to_runtime_path(NETDATA_RUNTIME_PREFIX "${NETDATA_WINDOWS_PATH_PREFIX}")
136 + endif()
137 +endif()
138 +
139 if(NOT NETDATA_RUNTIME_PREFIX STREQUAL "")
140 string(REGEX REPLACE "/$" "" NETDATA_RUNTIME_PREFIX "${NETDATA_RUNTIME_PREFIX}")
141 endif()
netdata-installer.sh
+46 -2
@@ -195,6 +195,7 @@ USAGE: ${PROGRAM} [options]
195 where options include:
196
197 --install-prefix <path> Install netdata in <path>. Ex. --install-prefix /opt will put netdata in /opt/netdata.
198 + --windows-path-prefix <path> Override NETDATA_WINDOWS_PATH_PREFIX for the CMake build.
199 --dont-start-it Do not (re)start netdata after installation.
200 --dont-wait Run installation in non-interactive mode.
201 --stable-channel Use packages from GitHub release pages instead of nightly updates.
@@ -259,6 +260,7 @@ fi
260 DONOTSTART=0
261 DONOTWAIT=0
262 NETDATA_PREFIX=
263 +NETDATA_WINDOWS_PATH_PREFIX=
264 LIBS_ARE_HERE=0
265 NETDATA_ENABLE_ML=""
266 ENABLE_DBENGINE=1
@@ -360,6 +362,10 @@ while [ -n "${1}" ]; do
362 NETDATA_PREFIX="${2}/netdata"
363 shift 1
364 ;;
365 + "--windows-path-prefix")
366 + NETDATA_WINDOWS_PATH_PREFIX="${2}"
367 + shift 1
368 + ;;
369 "--install-no-prefix")
370 NETDATA_PREFIX="${2}"
371 shift 1
@@ -652,9 +658,47 @@ echo >&2 "Netdata user and group set to: ${NETDATA_USER}/${NETDATA_GROUP}"
658
659 prepare_cmake_options
660
661 +print_cmake_configure_command() {
662 + printf "Would have used the following CMake command line for configuration: "
663 + # shellcheck disable=SC2086
664 + case "${NETDATA_CMAKE_INSTALL_PREFIX_OPTION:+I}${NETDATA_WINDOWS_PATH_PREFIX_OPTION:+W}" in
665 + "IW")
666 + escaped_print "${cmake}" ${NETDATA_CMAKE_OPTIONS} "${NETDATA_CMAKE_INSTALL_PREFIX_OPTION}" "${NETDATA_WINDOWS_PATH_PREFIX_OPTION}"
667 + ;;
668 + "I")
669 + escaped_print "${cmake}" ${NETDATA_CMAKE_OPTIONS} "${NETDATA_CMAKE_INSTALL_PREFIX_OPTION}"
670 + ;;
671 + "W")
672 + escaped_print "${cmake}" ${NETDATA_CMAKE_OPTIONS} "${NETDATA_WINDOWS_PATH_PREFIX_OPTION}"
673 + ;;
674 + *)
675 + escaped_print "${cmake}" ${NETDATA_CMAKE_OPTIONS}
676 + ;;
677 + esac
678 + printf "\n"
679 +}
680 +
681 +run_cmake_configure() {
682 + # shellcheck disable=SC2086
683 + case "${NETDATA_CMAKE_INSTALL_PREFIX_OPTION:+I}${NETDATA_WINDOWS_PATH_PREFIX_OPTION:+W}" in
684 + "IW")
685 + run ${cmake} ${NETDATA_CMAKE_OPTIONS} "${NETDATA_CMAKE_INSTALL_PREFIX_OPTION}" "${NETDATA_WINDOWS_PATH_PREFIX_OPTION}"
686 + ;;
687 + "I")
688 + run ${cmake} ${NETDATA_CMAKE_OPTIONS} "${NETDATA_CMAKE_INSTALL_PREFIX_OPTION}"
689 + ;;
690 + "W")
691 + run ${cmake} ${NETDATA_CMAKE_OPTIONS} "${NETDATA_WINDOWS_PATH_PREFIX_OPTION}"
692 + ;;
693 + *)
694 + run ${cmake} ${NETDATA_CMAKE_OPTIONS}
695 + ;;
696 + esac
697 +}
698 +
699 if [ -n "${NETDATA_PREPARE_ONLY}" ]; then
700 progress "Exiting before building Netdata as requested."
657 - printf "Would have used the following CMake command line for configuration: %s\n" "${cmake} ${NETDATA_CMAKE_OPTIONS}"
701 + print_cmake_configure_command
702 trap - EXIT
703 exit 0
704 fi
@@ -665,7 +709,7 @@ if [ "${IS_NETDATA_STATIC_BINARY}" = "yes" ]; then
709 fi
710
711 # shellcheck disable=SC2086
668 -if ! run ${cmake} ${NETDATA_CMAKE_OPTIONS}; then
712 +if ! run_cmake_configure; then
713 fatal "Failed to configure Netdata sources." I000A
714 fi
715
packaging/cmake/config.cmake.h.in
+1
@@ -174,6 +174,7 @@
174 // directory paths
175
176 #define NETDATA_RUNTIME_PREFIX "@NETDATA_RUNTIME_PREFIX@"
177 +#cmakedefine NETDATA_WINDOWS_PATH_PREFIX "@NETDATA_WINDOWS_PATH_PREFIX_ESCAPED@"
178 #cmakedefine CACHE_DIR "@CACHE_DIR@"
179 #cmakedefine CONFIG_DIR "@CONFIG_DIR@"
180 #cmakedefine LIBCONFIG_DIR "@LIBCONFIG_DIR@"
packaging/installer/functions.sh
+17 -1
@@ -273,7 +273,23 @@ check_for_feature() {
273 }
274
275 prepare_cmake_options() {
276 - NETDATA_CMAKE_OPTIONS="-S ./ -B ${NETDATA_BUILD_DIR} ${CMAKE_OPTS} ${NETDATA_PREFIX+-DCMAKE_INSTALL_PREFIX="${NETDATA_PREFIX}"} ${NETDATA_USER:+-DNETDATA_USER=${NETDATA_USER}} ${NETDATA_CMAKE_OPTIONS} "
276 + NETDATA_CMAKE_OPTIONS="-S ./ -B ${NETDATA_BUILD_DIR} ${CMAKE_OPTS} ${NETDATA_USER:+-DNETDATA_USER=${NETDATA_USER}} ${NETDATA_CMAKE_OPTIONS} "
277 +
278 + # Keep forwarding the install prefix even when it is empty.
279 + # This installer uses an empty prefix for /usr/... destinations instead of
280 + # CMake's /usr/local default.
281 + NETDATA_CMAKE_INSTALL_PREFIX_OPTION="-DCMAKE_INSTALL_PREFIX=${NETDATA_PREFIX-}"
282 + NETDATA_WINDOWS_PATH_PREFIX_OPTION=
283 +
284 + #
285 + # Do not append either prefix-related option to NETDATA_CMAKE_OPTIONS,
286 + # because that variable is later expanded as a space-delimited string and
287 + # path values may contain spaces. Keep them as dedicated variables so
288 + # callers can pass them as separately quoted arguments.
289 +
290 + if [ -n "${NETDATA_WINDOWS_PATH_PREFIX:-}" ]; then
291 + NETDATA_WINDOWS_PATH_PREFIX_OPTION="-DNETDATA_WINDOWS_PATH_PREFIX=${NETDATA_WINDOWS_PATH_PREFIX}"
292 + fi
293
294 NEED_OLD_CXX=0
295
packaging/utils/coverity-scan.sh
+7 -1
@@ -129,7 +129,13 @@ scanit() {
129 ENABLE_GO=0
130 prepare_cmake_options
131
132 - run cmake ${NETDATA_CMAKE_OPTIONS}
132 + # shellcheck disable=SC2086
133 + case "${NETDATA_CMAKE_INSTALL_PREFIX_OPTION:+I}${NETDATA_WINDOWS_PATH_PREFIX_OPTION:+W}" in
134 + "IW") run cmake ${NETDATA_CMAKE_OPTIONS} "${NETDATA_CMAKE_INSTALL_PREFIX_OPTION}" "${NETDATA_WINDOWS_PATH_PREFIX_OPTION}" ;;
135 + "I") run cmake ${NETDATA_CMAKE_OPTIONS} "${NETDATA_CMAKE_INSTALL_PREFIX_OPTION}" ;;
136 + "W") run cmake ${NETDATA_CMAKE_OPTIONS} "${NETDATA_WINDOWS_PATH_PREFIX_OPTION}" ;;
137 + *) run cmake ${NETDATA_CMAKE_OPTIONS} ;;
138 + esac
139
140 progress "Analyzing netdata..."
141 run "${covbuild}" --dir cov-int cmake --build "${NETDATA_BUILD_DIR}" --parallel ${JOBS} -- ${BUILD_OPTS}
packaging/windows/compile-on-windows.sh
+40
@@ -8,6 +8,45 @@ CMAKE_BUILD_TYPE="${CMAKE_BUILD_TYPE:-RelWithDebInfo}"
8
9 set -eu -o pipefail
10
11 +windows_path_prefix=
12 +windows_path_prefix_arg=()
13 +
14 +usage() {
15 + cat <<EOF
16 +Usage: $0 [options]
17 +
18 +Options:
19 + --windows-path-prefix <path> Override NETDATA_WINDOWS_PATH_PREFIX for this build.
20 + --help Show this help message.
21 +EOF
22 +}
23 +
24 +while [ $# -gt 0 ]; do
25 + case "$1" in
26 + --windows-path-prefix)
27 + if [ $# -lt 2 ]; then
28 + echo "Missing value for --windows-path-prefix" >&2
29 + exit 1
30 + fi
31 + windows_path_prefix="$2"
32 + shift 2
33 + ;;
34 + --help|-h)
35 + usage
36 + exit 0
37 + ;;
38 + *)
39 + echo "Unrecognized option '$1'" >&2
40 + usage >&2
41 + exit 1
42 + ;;
43 + esac
44 +done
45 +
46 +if [ -n "${windows_path_prefix}" ]; then
47 + windows_path_prefix_arg=("-DNETDATA_WINDOWS_PATH_PREFIX=${windows_path_prefix}")
48 +fi
49 +
50 if [ -d "${build}" ]; then
51 rm -rf "${build}"
52 fi
@@ -47,6 +86,7 @@ CFLAGS="${BUILD_CFLAGS}" /usr/bin/cmake \
86 -DENABLE_BUNDLED_JSONC=On \
87 -DENABLE_BUNDLED_PROTOBUF=Off \
88 -DRust_COMPILER=/ucrt64/bin/rustc \
89 + "${windows_path_prefix_arg[@]}" \
90 ${EXTRA_CMAKE_OPTIONS:-}
91 ${GITHUB_ACTIONS+echo "::endgroup::"}
92
src/daemon/buildinfo.c
+34 -3
@@ -1562,12 +1562,38 @@ static void populate_directories(void) {
1562
1563 // ----------------------------------------------------------------------------
1564
1565 +static const char *build_info_value_for_display(size_t i, char **allocated) {
1566 + const char *value = BUILD_INFO[i].value;
1567 +
1568 + if(allocated)
1569 + *allocated = NULL;
1570 +
1571 +#if defined(OS_WINDOWS)
1572 + if(BUILD_INFO[i].category == BIC_DIRECTORIES && BUILD_INFO[i].type == BIT_STRING && value) {
1573 + if(!allocated)
1574 + return value;
1575 +
1576 + *allocated = os_translate_msys_to_windows_path(value);
1577 + return *allocated;
1578 + }
1579 +#endif
1580 +
1581 + return value;
1582 +}
1583 +
1584 static void print_build_info_category_to_json(BUFFER *b, BUILD_INFO_CATEGORY category, const char *key) {
1585 buffer_json_member_add_object(b, key);
1586 for(size_t i = 0; i < BIB_TERMINATOR ;i++) {
1587 if(BUILD_INFO[i].category == category && BUILD_INFO[i].json) {
1569 - if(BUILD_INFO[i].value)
1570 - buffer_json_member_add_string(b, BUILD_INFO[i].json, BUILD_INFO[i].value);
1588 +#if defined(OS_WINDOWS)
1589 + CLEAN_CHAR_P *display_path = NULL;
1590 + const char *value = build_info_value_for_display(i, &display_path);
1591 +#else
1592 + const char *value = build_info_value_for_display(i, NULL);
1593 +#endif
1594 +
1595 + if(value)
1596 + buffer_json_member_add_string(b, BUILD_INFO[i].json, value);
1597 else
1598 buffer_json_member_add_boolean(b, BUILD_INFO[i].json, BUILD_INFO[i].status);
1599 }
@@ -1581,7 +1607,12 @@ static void print_build_info_category_to_console(BUILD_INFO_CATEGORY category, c
1607 if(BUILD_INFO[i].category == category && BUILD_INFO[i].print) {
1608 const char *v = BUILD_INFO[i].status ? "YES" : "NO";
1609 const char *k = BUILD_INFO[i].print;
1584 - const char *d = BUILD_INFO[i].value;
1610 +#if defined(OS_WINDOWS)
1611 + CLEAN_CHAR_P *display_path = NULL;
1612 + const char *d = build_info_value_for_display(i, &display_path);
1613 +#else
1614 + const char *d = build_info_value_for_display(i, NULL);
1615 +#endif
1616
1617 int padding_length = 60 - strlen(k) - 1;
1618 if (padding_length < 0) padding_length = 0;
src/daemon/config/netdata-conf-directories.c
+8 -8
@@ -6,7 +6,7 @@
6 static const char *get_varlib_subdir_from_config(const char *prefix, const char *dir) {
7 char filename[FILENAME_MAX + 1];
8 snprintfz(filename, FILENAME_MAX, "%s/%s", prefix, dir);
9 - return inicfg_get(&netdata_config, CONFIG_SECTION_DIRECTORIES, dir, filename);
9 + return inicfg_get_path(&netdata_config, CONFIG_SECTION_DIRECTORIES, dir, filename);
10 }
11
12 void netdata_conf_section_directories(void) {
@@ -15,13 +15,13 @@ void netdata_conf_section_directories(void) {
15 // ------------------------------------------------------------------------
16 // get system paths
17
18 - netdata_configured_user_config_dir = inicfg_get(&netdata_config, CONFIG_SECTION_DIRECTORIES, "config", netdata_configured_user_config_dir);
19 - netdata_configured_stock_config_dir = inicfg_get(&netdata_config, CONFIG_SECTION_DIRECTORIES, "stock config", netdata_configured_stock_config_dir);
20 - netdata_configured_stock_data_dir = inicfg_get(&netdata_config, CONFIG_SECTION_DIRECTORIES, "stock data", netdata_configured_stock_data_dir);
21 - netdata_configured_log_dir = inicfg_get(&netdata_config, CONFIG_SECTION_DIRECTORIES, "log", netdata_configured_log_dir);
22 - netdata_configured_web_dir = inicfg_get(&netdata_config, CONFIG_SECTION_DIRECTORIES, "web", netdata_configured_web_dir);
23 - netdata_configured_cache_dir = inicfg_get(&netdata_config, CONFIG_SECTION_DIRECTORIES, "cache", netdata_configured_cache_dir);
24 - netdata_configured_varlib_dir = inicfg_get(&netdata_config, CONFIG_SECTION_DIRECTORIES, "lib", netdata_configured_varlib_dir);
18 + netdata_configured_user_config_dir = inicfg_get_path(&netdata_config, CONFIG_SECTION_DIRECTORIES, "config", netdata_configured_user_config_dir);
19 + netdata_configured_stock_config_dir = inicfg_get_path(&netdata_config, CONFIG_SECTION_DIRECTORIES, "stock config", netdata_configured_stock_config_dir);
20 + netdata_configured_stock_data_dir = inicfg_get_path(&netdata_config, CONFIG_SECTION_DIRECTORIES, "stock data", netdata_configured_stock_data_dir);
21 + netdata_configured_log_dir = inicfg_get_path(&netdata_config, CONFIG_SECTION_DIRECTORIES, "log", netdata_configured_log_dir);
22 + netdata_configured_web_dir = inicfg_get_path(&netdata_config, CONFIG_SECTION_DIRECTORIES, "web", netdata_configured_web_dir);
23 + netdata_configured_cache_dir = inicfg_get_path(&netdata_config, CONFIG_SECTION_DIRECTORIES, "cache", netdata_configured_cache_dir);
24 + netdata_configured_varlib_dir = inicfg_get_path(&netdata_config, CONFIG_SECTION_DIRECTORIES, "lib", netdata_configured_varlib_dir);
25
26 netdata_configured_cloud_dir = get_varlib_subdir_from_config(netdata_configured_varlib_dir, "cloud.d");
27
src/daemon/config/netdata-conf-logs.c
+6 -6
@@ -61,19 +61,19 @@ void netdata_conf_section_logs(void) {
61 snprintfz(filename, FILENAME_MAX, "%s/debug.log", netdata_configured_log_dir);
62 #endif
63
64 - nd_log_set_user_settings(NDLS_DEBUG, inicfg_get(&netdata_config, CONFIG_SECTION_LOGS, "debug", filename));
64 + nd_log_set_user_settings(NDLS_DEBUG, inicfg_get_log_path_setting(&netdata_config, CONFIG_SECTION_LOGS, "debug", filename));
65
66 if(os_default_method)
67 snprintfz(filename, FILENAME_MAX, "%s", os_default_method);
68 else
69 snprintfz(filename, FILENAME_MAX, "%s/daemon.log", netdata_configured_log_dir);
70 - nd_log_set_user_settings(NDLS_DAEMON, inicfg_get(&netdata_config, CONFIG_SECTION_LOGS, "daemon", filename));
70 + nd_log_set_user_settings(NDLS_DAEMON, inicfg_get_log_path_setting(&netdata_config, CONFIG_SECTION_LOGS, "daemon", filename));
71
72 if(os_default_method)
73 snprintfz(filename, FILENAME_MAX, "%s", os_default_method);
74 else
75 snprintfz(filename, FILENAME_MAX, "%s/collector.log", netdata_configured_log_dir);
76 - nd_log_set_user_settings(NDLS_COLLECTORS, inicfg_get(&netdata_config, CONFIG_SECTION_LOGS, "collector", filename));
76 + nd_log_set_user_settings(NDLS_COLLECTORS, inicfg_get_log_path_setting(&netdata_config, CONFIG_SECTION_LOGS, "collector", filename));
77
78 #if defined(OS_WINDOWS)
79 // on windows, access log goes to windows events
@@ -81,13 +81,13 @@ void netdata_conf_section_logs(void) {
81 #else
82 snprintfz(filename, FILENAME_MAX, "%s/access.log", netdata_configured_log_dir);
83 #endif
84 - nd_log_set_user_settings(NDLS_ACCESS, inicfg_get(&netdata_config, CONFIG_SECTION_LOGS, "access", filename));
84 + nd_log_set_user_settings(NDLS_ACCESS, inicfg_get_log_path_setting(&netdata_config, CONFIG_SECTION_LOGS, "access", filename));
85
86 if(os_default_method)
87 snprintfz(filename, FILENAME_MAX, "%s", os_default_method);
88 else
89 snprintfz(filename, FILENAME_MAX, "%s/health.log", netdata_configured_log_dir);
90 - nd_log_set_user_settings(NDLS_HEALTH, inicfg_get(&netdata_config, CONFIG_SECTION_LOGS, "health", filename));
90 + nd_log_set_user_settings(NDLS_HEALTH, inicfg_get_log_path_setting(&netdata_config, CONFIG_SECTION_LOGS, "health", filename));
91
92 aclklog_enabled = inicfg_get_boolean(&netdata_config, CONFIG_SECTION_CLOUD, "conversation log", CONFIG_BOOLEAN_NO);
93 if (aclklog_enabled) {
@@ -97,7 +97,7 @@ void netdata_conf_section_logs(void) {
97 #else
98 snprintfz(filename, FILENAME_MAX, "%s/aclk.log", netdata_configured_log_dir);
99 #endif
100 - nd_log_set_user_settings(NDLS_ACLK, inicfg_get(&netdata_config, CONFIG_SECTION_CLOUD, "conversation log file", filename));
100 + nd_log_set_user_settings(NDLS_ACLK, inicfg_get_log_path_setting(&netdata_config, CONFIG_SECTION_CLOUD, "conversation log file", filename));
101 }
102
103 debug_flags_initialize();
src/daemon/config/netdata-conf-web.c
+2 -3
@@ -146,12 +146,11 @@ void netdata_conf_web_security_init(void) {
146
147 char filename[FILENAME_MAX + 1];
148 snprintfz(filename, FILENAME_MAX, "%s/ssl/key.pem", netdata_configured_user_config_dir);
149 - netdata_ssl_security_key = inicfg_get(&netdata_config, CONFIG_SECTION_WEB, "ssl key", filename);
149 + netdata_ssl_security_key = inicfg_get_filename(&netdata_config, CONFIG_SECTION_WEB, "ssl key", filename);
150
151 snprintfz(filename, FILENAME_MAX, "%s/ssl/cert.pem", netdata_configured_user_config_dir);
152 - netdata_ssl_security_cert = inicfg_get(&netdata_config, CONFIG_SECTION_WEB, "ssl certificate", filename);
152 + netdata_ssl_security_cert = inicfg_get_filename(&netdata_config, CONFIG_SECTION_WEB, "ssl certificate", filename);
153
154 tls_version = inicfg_get(&netdata_config, CONFIG_SECTION_WEB, "tls version", "1.3");
155 tls_ciphers = inicfg_get(&netdata_config, CONFIG_SECTION_WEB, "tls ciphers", "none");
156 }
157 -
src/daemon/environment.c
+2 -2
@@ -113,12 +113,12 @@ void set_environment_for_plugins_and_scripts(void) {
113 const char *p = getenv("PATH");
114 if (!p) p = "/bin:/usr/bin";
115 snprintfz(path, sizeof(path), "%s:%s", p, "/sbin:/usr/sbin:/usr/local/bin:/usr/local/sbin");
116 - setenv("PATH", inicfg_get(&netdata_config, CONFIG_SECTION_ENV_VARS, "PATH", path), 1);
116 + setenv("PATH", inicfg_get_path_list(&netdata_config, CONFIG_SECTION_ENV_VARS, "PATH", path), 1);
117
118 // python options
119 p = getenv("PYTHONPATH");
120 if (!p) p = "";
121 - setenv("PYTHONPATH", inicfg_get(&netdata_config, CONFIG_SECTION_ENV_VARS, "PYTHONPATH", p), 1);
121 + setenv("PYTHONPATH", inicfg_get_path_list(&netdata_config, CONFIG_SECTION_ENV_VARS, "PYTHONPATH", p), 1);
122
123 // disable buffering for python plugins
124 setenv("PYTHONUNBUFFERED", "1", 1);
src/daemon/main.c
+2 -2
@@ -1101,10 +1101,10 @@ int netdata_main(int argc, char **argv) {
1101 // The "HOME" env var points to the root's home dir because Netdata starts as root. Can't use "HOME".
1102 struct passwd *pw = getpwuid(getuid());
1103 if (inicfg_exists(&netdata_config, CONFIG_SECTION_DIRECTORIES, "home") || !pw || !pw->pw_dir) {
1104 - netdata_configured_home_dir = inicfg_get(&netdata_config, CONFIG_SECTION_DIRECTORIES, "home", netdata_configured_home_dir);
1104 + netdata_configured_home_dir = inicfg_get_path(&netdata_config, CONFIG_SECTION_DIRECTORIES, "home", netdata_configured_home_dir);
1105 }
1106 else
1107 - netdata_configured_home_dir = inicfg_get(&netdata_config, CONFIG_SECTION_DIRECTORIES, "home", pw->pw_dir);
1107 + netdata_configured_home_dir = inicfg_get_path(&netdata_config, CONFIG_SECTION_DIRECTORIES, "home", pw->pw_dir);
1108
1109 nd_setenv("HOME", netdata_configured_home_dir, 1);
1110
src/health/health.c
+3 -3
@@ -74,7 +74,7 @@ void health_load_config_defaults(void) {
74
75 snprintfz(filename, FILENAME_MAX, "%s/alarm-notify.sh", netdata_configured_primary_plugins_dir);
76 health_globals.config.default_exec =
77 - string_strdupz(inicfg_get(&netdata_config, CONFIG_SECTION_HEALTH, "script to execute on alarm", filename));
77 + string_strdupz(inicfg_get_filename(&netdata_config, CONFIG_SECTION_HEALTH, "script to execute on alarm", filename));
78
79 health_globals.config.enabled_alerts =
80 simple_pattern_create(inicfg_get(&netdata_config, CONFIG_SECTION_HEALTH, "enabled alarms", "*"),
@@ -136,13 +136,13 @@ void health_load_config_defaults(void) {
136 inline const char *health_user_config_dir(void) {
137 char buffer[FILENAME_MAX + 1];
138 snprintfz(buffer, FILENAME_MAX, "%s/health.d", netdata_configured_user_config_dir);
139 - return inicfg_get(&netdata_config, CONFIG_SECTION_DIRECTORIES, "health config", buffer);
139 + return inicfg_get_path(&netdata_config, CONFIG_SECTION_DIRECTORIES, "health config", buffer);
140 }
141
142 inline const char *health_stock_config_dir(void) {
143 char buffer[FILENAME_MAX + 1];
144 snprintfz(buffer, FILENAME_MAX, "%s/health.d", netdata_configured_stock_config_dir);
145 - return inicfg_get(&netdata_config, CONFIG_SECTION_DIRECTORIES, "stock health config", buffer);
145 + return inicfg_get_path(&netdata_config, CONFIG_SECTION_DIRECTORIES, "stock health config", buffer);
146 }
147
148 void health_plugin_init(void) {
src/health/health_silencers.c
+1 -1
@@ -400,7 +400,7 @@ void health_set_silencers_filename(void) {
400 snprintfz(filename, FILENAME_MAX, "%s/health.silencers.json", netdata_configured_varlib_dir);
401
402 health_globals.config.silencers_filename =
403 - string_strdupz(inicfg_get(&netdata_config, CONFIG_SECTION_HEALTH, "silencers file", filename));
403 + string_strdupz(inicfg_get_filename(&netdata_config, CONFIG_SECTION_HEALTH, "silencers file", filename));
404 }
405
406 void health_silencers_init(void) {
src/libnetdata/inicfg/inicfg.h
+6
@@ -185,6 +185,12 @@ void inicfg_free(struct config *root);
185
186 const char *inicfg_get(struct config *root, const char *section, const char *name, const char *default_value);
187 const char *inicfg_set(struct config *root, const char *section, const char *name, const char *value);
188 +const char *inicfg_get_filename(struct config *root, const char *section, const char *name, const char *default_value);
189 +const char *inicfg_get_path(struct config *root, const char *section, const char *name, const char *default_value);
190 +const char *inicfg_get_path_list(struct config *root, const char *section, const char *name, const char *default_value);
191 +const char *inicfg_get_quoted_path_list(struct config *root, const char *section, const char *name, const char *default_value);
192 +const char *inicfg_get_log_path_setting(struct config *root, const char *section, const char *name, const char *default_value);
193 +const char *inicfg_log_path_setting_for_display(const char *value, char *dst, size_t dst_size);
194
195 long long inicfg_get_number(struct config *root, const char *section, const char *name, long long value);
196 long long inicfg_get_number_range(struct config *root, const char *section, const char *name, long long value, long long min, long long max);
src/libnetdata/inicfg/inicfg_api.c
+231
@@ -16,6 +16,237 @@ const char *inicfg_set(struct config *root, const char *section, const char *nam
16 return string2str(opt->value);
17 }
18
19 +static STRING *reformat_path(STRING *value) {
20 +#if defined(OS_WINDOWS)
21 + CLEAN_CHAR_P *converted = os_translate_windows_to_msys_path(string2str(value));
22 + if(string_strcmp(value, converted) != 0) {
23 + string_freez(value);
24 + return string_strdupz(converted);
25 + }
26 + // value unchanged: fall through and return as-is
27 +#else
28 + // no-op on non-Windows: paths are always POSIX-style
29 + (void)value;
30 +#endif
31 +
32 + return value;
33 +}
34 +
35 +#if defined(OS_WINDOWS)
36 +static bool log_setting_output_is_special(const char *output) {
37 + return !output || !*output ||
38 + strcmp(output, "none") == 0 ||
39 + strcmp(output, "off") == 0 ||
40 + strcmp(output, "journal") == 0 ||
41 + strcmp(output, "syslog") == 0 ||
42 + strcmp(output, "system") == 0 ||
43 + strcmp(output, "stderr") == 0 ||
44 + strcmp(output, "stdout") == 0 ||
45 + strcmp(output, "/dev/null") == 0
46 +#if defined(HAVE_ETW)
47 + || strcmp(output, "etw") == 0
48 +#endif
49 +#if defined(HAVE_WEL)
50 + || strcmp(output, "wel") == 0
51 +#endif
52 + ;
53 +}
54 +
55 +static char *transform_log_path_setting(const char *setting, bool for_display) {
56 + if(!setting)
57 + return strdupz("");
58 +
59 + CLEAN_CHAR_P *copy = strdupz(setting);
60 + char *output = strrchr(copy, '@');
61 + size_t prefix_len = 0;
62 +
63 + if(output) {
64 + prefix_len = (size_t)(output - copy);
65 + *output = '\0';
66 + output++;
67 + }
68 + else
69 + output = copy;
70 +
71 + if(log_setting_output_is_special(output))
72 + return strdupz(setting);
73 +
74 + CLEAN_CHAR_P *translated = for_display
75 + ? os_translate_msys_to_windows_path(output)
76 + : os_translate_windows_to_msys_path(output);
77 + // os_translate_*_path() returns an allocated non-NULL string, using ""
78 + // for empty input, so no NULL fallback is needed here.
79 + size_t translated_len = strnlen(translated, CONFIG_MAX_VALUE + 1);
80 +
81 + if(output == copy)
82 + return strdupz(translated);
83 +
84 + BUFFER *wb = buffer_create(prefix_len + translated_len + 2, NULL);
85 + buffer_sprintf(wb, "%s@%s", copy, translated);
86 + char *result = strdupz(buffer_tostring(wb));
87 + buffer_free(wb);
88 +
89 + return result;
90 +}
91 +
92 +static bool windows_native_path_p(const char *value) {
93 + if(!value || !*value)
94 + return false;
95 +
96 + return (isalpha((unsigned char)value[0]) && value[1] == ':') ||
97 + ((value[0] == '\\' && value[1] == '\\') || (value[0] == '/' && value[1] == '/'));
98 +}
99 +
100 +static bool windows_path_list_needs_reformat_p(const char *value) {
101 + if(!value || !*value)
102 + return false;
103 +
104 + return strchr(value, ';') || strchr(value, '\\') || windows_native_path_p(value);
105 +}
106 +
107 +static STRING *reformat_path_list(STRING *value) {
108 + const char *src = string2str(value);
109 + size_t src_len = string_strlen(value);
110 + if(!windows_path_list_needs_reformat_p(src))
111 + return value;
112 +
113 + BUFFER *wb = buffer_create(src_len + 1, NULL);
114 + bool first = true;
115 + const char *segment_start = src;
116 +
117 + while(true) {
118 + const char *separator = strchr(segment_start, ';');
119 + size_t segment_len = separator
120 + ? (size_t)(separator - segment_start)
121 + : src_len - (size_t)(segment_start - src);
122 +
123 + CLEAN_CHAR_P *segment = strndupz(segment_start, segment_len);
124 + char *trimmed = trim(segment);
125 + CLEAN_CHAR_P *converted = os_translate_windows_to_msys_path(trimmed);
126 +
127 + if(!first)
128 + buffer_strcat(wb, ":");
129 + buffer_strcat(wb, converted);
130 + first = false;
131 +
132 + if(!separator)
133 + break;
134 +
135 + segment_start = separator + 1;
136 + }
137 +
138 + if(string_strcmp(value, buffer_tostring(wb)) != 0) {
139 + string_freez(value);
140 + value = string_strdupz(buffer_tostring(wb));
141 + }
142 +
143 + buffer_free(wb);
144 + return value;
145 +}
146 +
147 +static STRING *reformat_quoted_path_list(STRING *value) {
148 + CLEAN_CHAR_P *copy = strdupz(string2str(value));
149 + // 256 slots is far more than any realistic plugins list; entries beyond this are silently ignored.
150 + char *words[256] = { 0 };
151 + size_t num_words = quoted_strings_splitter_config(copy, words, _countof(words));
152 + if(!num_words)
153 + return value;
154 +
155 + BUFFER *wb = buffer_create(string_strlen(value) + 1, NULL);
156 + for(size_t i = 0; i < num_words; i++) {
157 + CLEAN_CHAR_P *converted = os_translate_windows_to_msys_path(words[i]);
158 + if(i)
159 + buffer_strcat(wb, " ");
160 + buffer_sprintf(wb, "\"%s\"", converted);
161 + }
162 +
163 + if(string_strcmp(value, buffer_tostring(wb)) != 0) {
164 + string_freez(value);
165 + value = string_strdupz(buffer_tostring(wb));
166 + }
167 +
168 + buffer_free(wb);
169 + return value;
170 +}
171 +
172 +static STRING *reformat_log_path_setting(STRING *value) {
173 + CLEAN_CHAR_P *converted = transform_log_path_setting(string2str(value), false);
174 + if(string_strcmp(value, converted) != 0) {
175 + string_freez(value);
176 + return string_strdupz(converted);
177 + }
178 +
179 + return value;
180 +}
181 +#else
182 +static STRING *reformat_path_list(STRING *value) {
183 + return value;
184 +}
185 +
186 +static STRING *reformat_quoted_path_list(STRING *value) {
187 + return value;
188 +}
189 +
190 +static STRING *reformat_log_path_setting(STRING *value) {
191 + return value;
192 +}
193 +#endif
194 +
195 +const char *inicfg_get_filename(struct config *root, const char *section, const char *name, const char *default_value) {
196 + struct config_option *opt = inicfg_get_raw_value(root, section, name, default_value, CONFIG_VALUE_TYPE_FILENAME, reformat_path);
197 + if(!opt)
198 + return NULL;
199 +
200 + return string2str(opt->value);
201 +}
202 +
203 +const char *inicfg_get_path(struct config *root, const char *section, const char *name, const char *default_value) {
204 + struct config_option *opt = inicfg_get_raw_value(root, section, name, default_value, CONFIG_VALUE_TYPE_PATH, reformat_path);
205 + if(!opt)
206 + return NULL;
207 +
208 + return string2str(opt->value);
209 +}
210 +
211 +const char *inicfg_get_path_list(struct config *root, const char *section, const char *name, const char *default_value) {
212 + struct config_option *opt = inicfg_get_raw_value(root, section, name, default_value, CONFIG_VALUE_TYPE_TEXT, reformat_path_list);
213 + if(!opt)
214 + return NULL;
215 +
216 + return string2str(opt->value);
217 +}
218 +
219 +const char *inicfg_get_quoted_path_list(struct config *root, const char *section, const char *name, const char *default_value) {
220 + struct config_option *opt = inicfg_get_raw_value(root, section, name, default_value, CONFIG_VALUE_TYPE_TEXT, reformat_quoted_path_list);
221 + if(!opt)
222 + return NULL;
223 +
224 + return string2str(opt->value);
225 +}
226 +
227 +const char *inicfg_get_log_path_setting(struct config *root, const char *section, const char *name, const char *default_value) {
228 + struct config_option *opt = inicfg_get_raw_value(root, section, name, default_value, CONFIG_VALUE_TYPE_TEXT, reformat_log_path_setting);
229 + if(!opt)
230 + return NULL;
231 +
232 + return string2str(opt->value);
233 +}
234 +
235 +const char *inicfg_log_path_setting_for_display(const char *value, char *dst, size_t dst_size) {
236 +#if defined(OS_WINDOWS)
237 + if(!dst || dst_size == 0)
238 + return value ? value : "";
239 +
240 + CLEAN_CHAR_P *converted = transform_log_path_setting(value, true);
241 + snprintfz(dst, dst_size, "%s", converted);
242 + return dst;
243 +#else
244 + (void)dst;
245 + (void)dst_size;
246 + return value ? value : "";
247 +#endif
248 +}
249 +
250 bool inicfg_test_boolean_value(const char *s) {
251 if(!strcasecmp(s, "yes") || !strcasecmp(s, "true") || !strcasecmp(s, "on")
252 || !strcasecmp(s, "auto") || !strcasecmp(s, "on demand"))
src/libnetdata/inicfg/inicfg_conf_file.c
+144 -3
@@ -26,6 +26,134 @@ ENUM_STR_MAP_DEFINE(CONFIG_VALUE_TYPES) = {
26
27 ENUM_STR_DEFINE_FUNCTIONS(CONFIG_VALUE_TYPES, CONFIG_VALUE_TYPE_UNKNOWN, "unknown");
28
29 +#if defined(OS_WINDOWS)
30 +static bool inicfg_windows_is_path_list_env_var(const struct config_section *sect, const struct config_option *opt) {
31 + return !string_strcmp(sect->name, CONFIG_SECTION_ENV_VARS) &&
32 + (!string_strcmp(opt->name, "PATH") || !string_strcmp(opt->name, "PYTHONPATH"));
33 +}
34 +
35 +static bool inicfg_windows_is_quoted_path_list_dir_var(const struct config_section *sect, const struct config_option *opt) {
36 + return !string_strcmp(sect->name, CONFIG_SECTION_DIRECTORIES) &&
37 + !string_strcmp(opt->name, "plugins");
38 +}
39 +
40 +static bool inicfg_windows_is_log_path_setting(const struct config_section *sect, const struct config_option *opt) {
41 + return ((!string_strcmp(sect->name, CONFIG_SECTION_LOGS) &&
42 + (!string_strcmp(opt->name, "debug") ||
43 + !string_strcmp(opt->name, "daemon") ||
44 + !string_strcmp(opt->name, "collector") ||
45 + !string_strcmp(opt->name, "access") ||
46 + !string_strcmp(opt->name, "health"))) ||
47 + (!string_strcmp(sect->name, CONFIG_SECTION_CLOUD) &&
48 + !string_strcmp(opt->name, "conversation log file")));
49 +}
50 +
51 +static const char *inicfg_windows_path_list_for_display(const char *value, char *dst, size_t dst_size) {
52 + if (!value || !*value || !dst || dst_size == 0)
53 + return value ? value : "";
54 +
55 + // Internal storage always uses ':' as separator (normalized on read).
56 + // A ';' here means the value was never normalized, so treat it as already Windows-format.
57 + if (strchr(value, ';')) {
58 + snprintfz(dst, dst_size, "%s", value);
59 + return dst;
60 + }
61 +
62 + dst[0] = '\0';
63 + size_t len = 0;
64 + size_t value_len = strnlen(value, CONFIG_MAX_VALUE);
65 + const char *value_end = value + value_len;
66 + bool first = true;
67 +
68 + const char *segment_start = value;
69 + while (segment_start <= value_end) {
70 + if (segment_start == value_end)
71 + break;
72 +
73 + const char *separator = memchr(segment_start, ':', (size_t)(value_end - segment_start));
74 + size_t segment_len = separator
75 + ? (size_t)(separator - segment_start)
76 + : value_len - (size_t)(segment_start - value);
77 +
78 + char segment[CONFIG_MAX_VALUE + 1];
79 + if (segment_len > CONFIG_MAX_VALUE)
80 + segment_len = CONFIG_MAX_VALUE;
81 +
82 + memcpy(segment, segment_start, segment_len);
83 + segment[segment_len] = '\0';
84 +
85 + CLEAN_CHAR_P *translated = os_translate_msys_to_windows_path(segment);
86 + if (!first)
87 + len = strcatz(dst, len, ";", dst_size);
88 + len = strcatz(dst, len, translated, dst_size);
89 + first = false;
90 +
91 + if (!separator)
92 + break;
93 +
94 + segment_start = separator + 1;
95 + }
96 +
97 + return dst;
98 +}
99 +
100 +static const char *inicfg_windows_quoted_path_list_for_display(const char *value, char *dst, size_t dst_size) {
101 + if (!value || !*value || !dst || dst_size == 0)
102 + return value ? value : "";
103 +
104 + CLEAN_CHAR_P *copy = strdupz(value);
105 + // 256 slots matches the limit in reformat_quoted_path_list; entries beyond this are silently ignored.
106 + char *words[256] = { 0 };
107 + size_t num_words = quoted_strings_splitter_config(copy, words, _countof(words));
108 + if (!num_words) {
109 + snprintfz(dst, dst_size, "%s", value);
110 + return dst;
111 + }
112 +
113 + dst[0] = '\0';
114 + size_t len = 0;
115 + for(size_t i = 0; i < num_words; i++) {
116 + CLEAN_CHAR_P *translated = os_translate_msys_to_windows_path(words[i]);
117 + if (i)
118 + len = strcatz(dst, len, " ", dst_size);
119 + len = strcatz(dst, len, "\"", dst_size);
120 + len = strcatz(dst, len, translated, dst_size);
121 + len = strcatz(dst, len, "\"", dst_size);
122 + }
123 +
124 + return dst;
125 +}
126 +
127 +static const char *inicfg_windows_value_for_display(
128 + const struct config_section *sect,
129 + const struct config_option *opt,
130 + const char *value,
131 + char *dst,
132 + size_t dst_size)
133 +{
134 + if (!value || !*value)
135 + return value ? value : "";
136 +
137 + if (inicfg_windows_is_path_list_env_var(sect, opt))
138 + return inicfg_windows_path_list_for_display(value, dst, dst_size);
139 +
140 + if (inicfg_windows_is_quoted_path_list_dir_var(sect, opt))
141 + return inicfg_windows_quoted_path_list_for_display(value, dst, dst_size);
142 +
143 + if (inicfg_windows_is_log_path_setting(sect, opt))
144 + return inicfg_log_path_setting_for_display(value, dst, dst_size);
145 +
146 + // Translate all PATH/FILENAME-typed options, plus all remaining DIRECTORIES keys.
147 + // The list-type keys handled above are ENV_VARS.PATH/PYTHONPATH and DIRECTORIES.plugins.
148 + // If a new list-type key is added to either section, add a dedicated check before this fallback.
149 + if ((opt->type != CONFIG_VALUE_TYPE_PATH && opt->type != CONFIG_VALUE_TYPE_FILENAME) &&
150 + string_strcmp(sect->name, CONFIG_SECTION_DIRECTORIES) != 0)
151 + return value;
152 +
153 + return os_translate_path(dst, value, dst_size);
154 +}
155 +#endif
156 +
157
158 // ----------------------------------------------------------------------------
159 // config load/save
@@ -62,7 +190,7 @@ int inicfg_load(struct config *root, char *filename, int overwrite_used, const c
190 line++;
191
192 s = trim(buffer);
65 - if(!s || *s == '#') {
193 + if(!s || !*s || *s == '#') {
194 netdata_log_debug(D_CONFIG, "CONFIG: ignoring line %d of file '%s', it is empty.", line, filename);
195 continue;
196 }
@@ -296,10 +424,23 @@ void inicfg_generate(struct config *root, BUFFER *wb, int only_changed, bool net
424 string2str(opt->value_original));
425 }
426
427 + const char *current_value = string2str(opt->value);
428 + const char *default_value = opt->value_default ? string2str(opt->value_default) : "";
429 +#if defined(OS_WINDOWS)
430 + char current_value_windows[CONFIG_MAX_VALUE + 1];
431 + char default_value_windows[CONFIG_MAX_VALUE + 1];
432 +
433 + current_value = inicfg_windows_value_for_display(
434 + sect, opt, current_value, current_value_windows, sizeof(current_value_windows));
435 + if(opt->value_default)
436 + default_value = inicfg_windows_value_for_display(
437 + sect, opt, default_value, default_value_windows, sizeof(default_value_windows));
438 +#endif
439 +
440 if(show_default)
441 buffer_sprintf(wb, "\t#| datatype: %s, default value: %s\n",
442 CONFIG_VALUE_TYPES_2str(opt->type),
302 - string2str(opt->value_default));
443 + default_value);
444
445 buffer_sprintf(wb, "\t%s%s = %s\n",
446 (
@@ -308,7 +449,7 @@ void inicfg_generate(struct config *root, BUFFER *wb, int only_changed, bool net
449 (opt->flags & CONFIG_VALUE_USED)
450 ) ? "# " : "",
451 string2str(opt->name),
311 - string2str(opt->value));
452 + current_value);
453
454 options_added++;
455 }
src/libnetdata/os/os.c
+118
@@ -35,4 +35,122 @@ const char *os_type = "macos";
35
36 #if defined(OS_WINDOWS)
37 const char *os_type = "windows";
38 +
39 +#define OS_WINDOWS_PATH_TRANSLATION_MAX 8191
40 +
41 +char *os_translate_msys_to_windows_path(const char *src) {
42 + if (!src)
43 + return strdupz("");
44 +
45 + if (!*src)
46 + return strdupz("");
47 +
48 + if (src[0] == '/') {
49 + ssize_t converted_size = cygwin_conv_path(CCP_POSIX_TO_WIN_A, src, NULL, 0);
50 + if (converted_size > 0) {
51 + char *converted_path = mallocz((size_t)converted_size);
52 + if (cygwin_conv_path(CCP_POSIX_TO_WIN_A, src, converted_path, (size_t)converted_size) == 0)
53 + return converted_path;
54 +
55 + freez(converted_path);
56 + }
57 + }
58 +
59 + size_t src_len = strnlen(src, OS_WINDOWS_PATH_TRANSLATION_MAX);
60 + char *converted_path = mallocz(src_len + 3);
61 + size_t i = 0;
62 + size_t j = 0;
63 +
64 + if (src_len >= 2 && isalpha((unsigned char)src[0]) && src[1] == ':') {
65 + converted_path[j++] = (char)toupper((unsigned char)src[0]);
66 + converted_path[j++] = ':';
67 + i = 2;
68 +
69 + if (src[i] == '\\' || src[i] == '/') {
70 + converted_path[j++] = '\\';
71 + i++;
72 + }
73 + }
74 + else if (src_len >= 2 && src[0] == '/' && isalpha((unsigned char)src[1]) && (src_len == 2 || src[2] == '/')) {
75 + converted_path[j++] = (char)toupper((unsigned char)src[1]);
76 + converted_path[j++] = ':';
77 + i = 2;
78 +
79 + if (src[i] == '/') {
80 + converted_path[j++] = '\\';
81 + i++;
82 + }
83 + }
84 + else if (src_len >= 2 && ((src[0] == '\\' && src[1] == '\\') || (src[0] == '/' && src[1] == '/'))) {
85 + converted_path[j++] = '\\';
86 + converted_path[j++] = '\\';
87 + i = 2;
88 + }
89 +
90 + for (; i < src_len && j < src_len + 2; i++)
91 + converted_path[j++] = (src[i] == '/') ? '\\' : src[i];
92 +
93 + converted_path[j] = '\0';
94 + return converted_path;
95 +}
96 +
97 +char *os_translate_path(char *dst, const char *src, size_t dst_size) {
98 + if (!dst || !dst_size)
99 + return dst;
100 +
101 + if (!src) {
102 + dst[0] = '\0';
103 + return dst;
104 + }
105 +
106 + CLEAN_CHAR_P *translated = os_translate_msys_to_windows_path(src);
107 + snprintfz(dst, dst_size, "%s", translated);
108 + return dst;
109 +}
110 +
111 +char *os_translate_windows_to_msys_path(const char *src) {
112 + if (!src)
113 + return strdupz("");
114 +
115 + // Keep already POSIX-style paths unchanged.
116 + if (src[0] == '/')
117 + return strdupz(src);
118 +
119 + ssize_t converted_size = cygwin_conv_path(CCP_WIN_A_TO_POSIX, src, NULL, 0);
120 + if (converted_size > 0) {
121 + char *converted_path = mallocz((size_t)converted_size);
122 + if (cygwin_conv_path(CCP_WIN_A_TO_POSIX, src, converted_path, (size_t)converted_size) == 0)
123 + return converted_path;
124 +
125 + freez(converted_path);
126 + }
127 +
128 + size_t src_len = strnlen(src, OS_WINDOWS_PATH_TRANSLATION_MAX);
129 + char *converted_path = mallocz(src_len + 3);
130 + size_t converted_size_fallback = src_len + 3;
131 + size_t i = 0;
132 + size_t j = 0;
133 +
134 + if (src_len >= 2 && isalpha((unsigned char)src[0]) && src[1] == ':') {
135 + converted_path[j++] = '/';
136 + converted_path[j++] = (char)tolower((unsigned char)src[0]);
137 +
138 + i = 2;
139 + if (src[i] == '\\' || src[i] == '/') {
140 + converted_path[j++] = '/';
141 + i++; // consume the separator so the loop below doesn't emit it again
142 + }
143 + }
144 + else if (src_len >= 2 && ((src[0] == '\\' && src[1] == '\\') || (src[0] == '/' && src[1] == '/'))) {
145 + converted_path[j++] = '/';
146 + converted_path[j++] = '/';
147 + i = 2;
148 + }
149 +
150 + for (; i < src_len && j < converted_size_fallback - 1; i++)
151 + converted_path[j++] = (src[i] == '\\') ? '/' : src[i];
152 +
153 + converted_path[j] = '\0';
154 + return converted_path;
155 +}
156 #endif
src/libnetdata/os/os.h
+22
@@ -56,4 +56,26 @@ extern const char *os_type;
56 extern unsigned int system_hz;
57 void os_get_system_HZ(void);
58
59 +#if defined(OS_WINDOWS)
60 +char *os_translate_path(char *dst, const char *src, size_t dst_size);
61 +char *os_translate_msys_to_windows_path(const char *src);
62 +// Returns newly allocated POSIX-style storage; caller must free.
63 +char *os_translate_windows_to_msys_path(const char *src);
64 +#else
65 +// No translation needed on non-Windows; copy src into dst for consistent semantics.
66 +static inline char *os_translate_path(char *dst, const char *src, size_t dst_size) {
67 + if (!dst || !dst_size)
68 + return dst;
69 + if (!src) {
70 + dst[0] = '\0';
71 + return dst;
72 + }
73 + strncpyz(dst, src, dst_size - 1);
74 + return dst;
75 +}
76 +static inline char *os_translate_windows_to_msys_path(const char *src) {
77 + return strdupz(src ? src : "");
78 +}
79 +#endif
80 +
81 #endif //NETDATA_OS_H
src/plugins.d/plugins_d.c
+1 -1
@@ -24,7 +24,7 @@ inline size_t pluginsd_initialize_plugin_directories()
24 // Get the configuration entry
25 if (likely(!plugins_dir_list)) {
26 snprintfz(plugins_dirs, FILENAME_MAX * 2, "\"%s\" \"%s/custom-plugins.d\"", PLUGINS_DIR, CONFIG_DIR);
27 - plugins_dir_list = strdupz(inicfg_get(&netdata_config, CONFIG_SECTION_DIRECTORIES, "plugins", plugins_dirs));
27 + plugins_dir_list = strdupz(inicfg_get_quoted_path_list(&netdata_config, CONFIG_SECTION_DIRECTORIES, "plugins", plugins_dirs));
28 }
29
30 // Parse it and store it to plugin directories
src/registry/registry_init.c
+3 -3
@@ -82,14 +82,14 @@ void netdata_conf_section_registry(void) {
82
83 // path names
84 snprintfz(filename, FILENAME_MAX, "%s/registry", netdata_configured_varlib_dir);
85 - registry.pathname = inicfg_get(&netdata_config, CONFIG_SECTION_DIRECTORIES, "registry", filename);
85 + registry.pathname = inicfg_get_path(&netdata_config, CONFIG_SECTION_DIRECTORIES, "registry", filename);
86
87 // filenames
88 snprintfz(filename, FILENAME_MAX, "%s/registry.db", registry.pathname);
89 - registry.db_filename = inicfg_get(&netdata_config, CONFIG_SECTION_REGISTRY, "registry db file", filename);
89 + registry.db_filename = inicfg_get_filename(&netdata_config, CONFIG_SECTION_REGISTRY, "registry db file", filename);
90
91 snprintfz(filename, FILENAME_MAX, "%s/registry-log.db", registry.pathname);
92 - registry.log_filename = inicfg_get(&netdata_config, CONFIG_SECTION_REGISTRY, "registry log file", filename);
92 + registry.log_filename = inicfg_get_filename(&netdata_config, CONFIG_SECTION_REGISTRY, "registry log file", filename);
93
94 // configuration options
95 registry.save_registry_every_entries = (unsigned long long)inicfg_get_number(&netdata_config, CONFIG_SECTION_REGISTRY, "registry save db every new entries", 1000000);
src/streaming/stream-conf.c
+2 -2
@@ -234,8 +234,8 @@ void stream_conf_load() {
234 if(!netdata_ssl_validate_certificate_sender)
235 nd_log_daemon(NDLP_NOTICE, "SSL: streaming senders will skip SSL certificates verification.");
236
237 - stream_send.parents.ssl_ca_path = string_strdupz(inicfg_get(&stream_config, CONFIG_SECTION_STREAM, "CApath", NULL));
238 - stream_send.parents.ssl_ca_file = string_strdupz(inicfg_get(&stream_config, CONFIG_SECTION_STREAM, "CAfile", NULL));
237 + stream_send.parents.ssl_ca_path = string_strdupz(inicfg_get_path(&stream_config, CONFIG_SECTION_STREAM, "CApath", NULL));
238 + stream_send.parents.ssl_ca_file = string_strdupz(inicfg_get_filename(&stream_config, CONFIG_SECTION_STREAM, "CAfile", NULL));
239
240 if(stream_send.enabled && (!stream_send.parents.destination || !stream_send.api_key)) {
241 nd_log_daemon(
src/web/api/mcp_auth.c
+6
@@ -114,7 +114,13 @@ void mcp_api_key_initialize(void) {
114
115 char path[PATH_MAX];
116 snprintf(path, sizeof(path), "%s/%s", netdata_configured_varlib_dir, MCP_DEV_PREVIEW_API_KEY_FILENAME);
117 +#if defined(OS_WINDOWS)
118 + char display_path[PATH_MAX];
119 + netdata_log_info("MCP: Developer preview API key initialized. Location: %s",
120 + os_translate_path(display_path, path, sizeof(display_path)));
121 +#else
122 netdata_log_info("MCP: Developer preview API key initialized. Location: %s", path);
123 +#endif
124 }
125
126 bool mcp_api_key_verify(const char *api_key, bool silent) {
src/web/api/v1/api_v1_manage.c
+64 -11
@@ -7,25 +7,34 @@ char *api_secret;
7 static char *get_mgmt_api_key(void) {
8 char filename[FILENAME_MAX + 1];
9 snprintfz(filename, FILENAME_MAX, "%s/netdata.api.key", netdata_configured_varlib_dir);
10 - const char *api_key_filename = inicfg_get(&netdata_config, CONFIG_SECTION_REGISTRY, "netdata management api key file", filename);
10 + const char *api_key_filename = inicfg_get_filename(&netdata_config, CONFIG_SECTION_REGISTRY, "netdata management api key file", filename);
11 static char guid[GUID_LEN + 1] = "";
12
13 if(likely(guid[0]))
14 return guid;
15
16 // read it from disk
17 +#ifdef O_NOFOLLOW
18 + int fd = open(api_key_filename, O_RDONLY | O_CLOEXEC | O_NOFOLLOW);
19 +#else
20 int fd = open(api_key_filename, O_RDONLY | O_CLOEXEC);
21 +#endif
22 if(fd != -1) {
19 - char buf[GUID_LEN + 1];
20 - if(read(fd, buf, GUID_LEN) != GUID_LEN)
21 - netdata_log_error("Failed to read management API key from '%s'", api_key_filename);
23 + struct stat st;
24 + if(fstat(fd, &st) != 0 || !S_ISREG(st.st_mode))
25 + netdata_log_error("Management API key file '%s' is not a regular file, regenerating.", api_key_filename);
26 else {
23 - buf[GUID_LEN] = '\0';
24 - if(regenerate_guid(buf, guid) == -1) {
25 - netdata_log_error("Failed to validate management API key '%s' from '%s'.",
26 - buf, api_key_filename);
27 -
28 - guid[0] = '\0';
27 + char buf[GUID_LEN + 1];
28 + if(read(fd, buf, GUID_LEN) != GUID_LEN)
29 + netdata_log_error("Failed to read management API key from '%s'", api_key_filename);
30 + else {
31 + buf[GUID_LEN] = '\0';
32 + if(regenerate_guid(buf, guid) == -1) {
33 + netdata_log_error("Failed to validate management API key '%s' from '%s'.",
34 + buf, api_key_filename);
35 +
36 + guid[0] = '\0';
37 + }
38 }
39 }
40 close(fd);
@@ -40,12 +49,28 @@ static char *get_mgmt_api_key(void) {
49 guid[GUID_LEN] = '\0';
50
51 // save it
43 - fd = open(api_key_filename, O_WRONLY|O_CREAT|O_TRUNC | O_CLOEXEC, 0600);
52 +#ifdef O_NOFOLLOW
53 + struct stat st;
54 + // O_RDWR avoids blocking on FIFOs (O_WRONLY blocks until a reader arrives).
55 + // O_TRUNC is omitted so truncation only happens after fstat() confirms a regular file.
56 + fd = open(api_key_filename, O_RDWR|O_CREAT|O_CLOEXEC|O_NOFOLLOW, 0600);
57 if(fd == -1) {
58 netdata_log_error("Cannot create unique management API key file '%s'. Please adjust config parameter 'netdata management api key file' to a proper path and file.", api_key_filename);
59 goto temp_key;
60 }
61
62 + if(fstat(fd, &st) != 0 || !S_ISREG(st.st_mode)) {
63 + netdata_log_error("Management API key file '%s' is not a regular file.", api_key_filename);
64 + close(fd);
65 + goto temp_key;
66 + }
67 +
68 + if(ftruncate(fd, 0) != 0) {
69 + netdata_log_error("Cannot truncate management API key file '%s'.", api_key_filename);
70 + close(fd);
71 + goto temp_key;
72 + }
73 +
74 if(write(fd, guid, GUID_LEN) != GUID_LEN) {
75 netdata_log_error("Cannot write the unique management API key file '%s'. Please adjust config parameter 'netdata management api key file' to a proper path and file with enough space left.", api_key_filename);
76 close(fd);
@@ -53,6 +78,34 @@ static char *get_mgmt_api_key(void) {
78 }
79
80 close(fd);
81 +#else
82 + // Without O_NOFOLLOW: write to a uniquely named temp file then rename atomically.
83 + // Use mkstemp() so the temporary filename is not predictable.
84 + // rename() replaces the destination entry without following symlinks there,
85 + // so a symlink planted at api_key_filename cannot redirect truncation to another file.
86 + char tmp_filename[FILENAME_MAX + 1];
87 + snprintfz(tmp_filename, FILENAME_MAX, "%s.tmp.XXXXXX", api_key_filename);
88 +
89 + fd = mkstemp(tmp_filename);
90 + if(fd == -1) {
91 + netdata_log_error("Cannot create temporary management API key file '%s'. Please adjust config parameter 'netdata management api key file' to a proper path and file.", tmp_filename);
92 + goto temp_key;
93 + }
94 +
95 + if(write(fd, guid, GUID_LEN) != GUID_LEN) {
96 + netdata_log_error("Cannot write the unique management API key file '%s'. Please adjust config parameter 'netdata management api key file' to a proper path and file with enough space left.", tmp_filename);
97 + close(fd);
98 + unlink(tmp_filename);
99 + goto temp_key;
100 + }
101 + close(fd);
102 +
103 + if(rename(tmp_filename, api_key_filename) != 0) {
104 + netdata_log_error("Cannot rename temporary API key file '%s' to '%s'. Please adjust config parameter 'netdata management api key file' to a proper path and file.", tmp_filename, api_key_filename);
105 + unlink(tmp_filename);
106 + goto temp_key;
107 + }
108 +#endif
109 }
110
111 return guid;
src/web/api/v2/api_v2_claim.c
+5 -3
@@ -113,9 +113,11 @@ static void claim_add_user_info_command(BUFFER *wb) {
113 const char *os_message;
114
115 #if defined(OS_WINDOWS)
116 - char win_path[MAX_PATH];
117 - cygwin_conv_path(CCP_POSIX_TO_WIN_A, filename, win_path, sizeof(win_path));
118 - os_filename = win_path;
116 + char win_path[FILENAME_MAX];
117 + if(cygwin_conv_path(CCP_POSIX_TO_WIN_A, filename, win_path, sizeof(win_path)) == 0)
118 + os_filename = win_path;
119 + else
120 + os_filename = os_translate_path(win_path, filename, sizeof(win_path));
121 os_prefix = "more";
122 os_message = "We need to verify this Windows server is yours. So, open a Command Prompt on this server to run the command. It will give you a UUID. Copy and paste this UUID to this box:";
123 #else
src/web/server/web_client.c
+9 -1
@@ -444,7 +444,15 @@ static bool find_filename_to_serve(const char *filename, char *dst, size_t dst_l
444 }
445
446 static int web_server_static_file(struct web_client *w, char *filename) {
447 - netdata_log_debug(D_WEB_CLIENT, "%llu: Looking for file '%s/%s'", w->id, netdata_configured_web_dir, filename);
447 + char web_path[FILENAME_MAX];
448 + snprintfz(web_path, sizeof(web_path), "%s/%s", netdata_configured_web_dir, filename);
449 +#if defined(OS_WINDOWS)
450 + char display_path[FILENAME_MAX];
451 + netdata_log_debug(D_WEB_CLIENT, "%llu: Looking for file '%s'", w->id,
452 + os_translate_path(display_path, web_path, sizeof(display_path)));
453 +#else
454 + netdata_log_debug(D_WEB_CLIENT, "%llu: Looking for file '%s'", w->id, web_path);
455 +#endif
456
457 if(!http_can_access_dashboard(w))
458 return web_client_permission_denied_acl(w);