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);