@cryptotaxi247 / netdata-1 / commits / 16b6916bf

Run periodical refresh of timezone (#21944)

* Refactor timezone handling for thread safety and runtime updates. - Introduced thread-safe access to system and host timezone data. - Added atomic update mechanism for DST and runtime timezone refreshes. - Migrated all direct timezone field accesses to use thread-safe getters. - Updated database and streaming logic for consistent timezone handling. * Update timezone logging to use thread-safe accessor functions. * Corrected timezone detection * Refactor timezone handling: add tzdb name validation, enhance detection fallbacks, and update `refresh_system_timezone` for safer updates. * Improve timezone handling: address empty timezone configuration and log mismatched system vs. user-configured timezones. * Handle empty timezone configuration: fallback to auto-detected timezone when unset. * Fix timezone handling: reorder cleanup calls, improve in-place sanitization, and update thread-safety comments. * Refactor timezone handling: replace `const char*` with `char*` for better memory management and ownership clarity. * Extend and refactor timezone handling: - Fixed typo in function name (`ioanna` → `iana`). - Added detection of timezone info from TZ files. - Introduced validation for safe TZDB paths. - Enhanced thread safety and fallback mechanisms in `refresh_system_timezone`. - Updated CMake to check for `tzalloc`, `localtime_rz`, and `tzfree`. * Update CMake: add checks for `tzalloc`, `localtime_rz`, and `tzfree` * - Introduced validation for TZif header counts to enforce RFC 8536 compliance. - Enhanced safety by validating all transition type indices before usage. - Updated `setenv` for timezone variable with overwrite enabled. - Improved comments and fallback logic for timezone resolution. * Update `setenv` for timezone variable: disable overwrite flag for safer handling * Fix `setenv` for timezone variable: enforce overwrite only when `TZ` is unset or empty * Fix `setenv` for timezone: ensure `TZ` is updated only when unset or empty * Refactor timezone handling: adjust `timezone_is_tzdb_name` logic for improved accuracy and fallback consistency. * Initialize `rrdhost_update_lock` spinlock for thread safety. * Fix `strstr` usage in analytics: ensure exact window ID matches by wrapping with quotes

Stelios Fragkakis committed Mar 17, 2026 at 14:53 UTC 16b6916bfa3a8686bab1ee1225d5bce324f787b9
21 files changed +681 -135
CMakeLists.txt
+3
@@ -531,6 +531,9 @@ check_function_exists(getrandom HAVE_GETRANDOM)
531 check_function_exists(sysinfo HAVE_SYSINFO)
532
533 check_function_exists(timegm HAVE_TIMEGM)
534 +check_function_exists(tzalloc HAVE_TZALLOC)
535 +check_function_exists(localtime_rz HAVE_LOCALTIME_RZ)
536 +check_function_exists(tzfree HAVE_TZFREE)
537
538 if(CMAKE_C_COMPILER_ID MATCHES "GNU|Clang")
539 # -fno-omit-frame-pointer = add frame pointers to all functions
packaging/cmake/config.cmake.h.in
+3
@@ -85,6 +85,9 @@
85 #cmakedefine HAVE_GETRANDOM
86 #cmakedefine HAVE_SYSINFO
87 #cmakedefine HAVE_TIMEGM
88 +#cmakedefine HAVE_TZALLOC
89 +#cmakedefine HAVE_LOCALTIME_RZ
90 +#cmakedefine HAVE_TZFREE
91 #cmakedefine HAVE_TM_GMTOFF
92
93 #cmakedefine HAVE_LIBBACKTRACE
src/daemon/analytics.c
+430 -92
@@ -749,7 +749,7 @@ static void get_win_geoiso(char *geo_name, int length) {
749 geo_name[0] = '\0';
750 }
751
752 -static int map_windows_tz_to_ioanna(char *out, char *win_id, char *geo_name) {
752 +static int map_windows_tz_to_iana(char *out, char *win_id, char *geo_name) {
753 if (*win_id == '\0')
754 return -1;
755
@@ -758,11 +758,15 @@ static int map_windows_tz_to_ioanna(char *out, char *win_id, char *geo_name) {
758 return -1;
759
760 char buffer[CONFIG_FILE_LINE_MAX + 1];
761 + char win_id_match[512];
762 bool copied = 0;
763 +
764 + snprintfz(win_id_match, sizeof(win_id_match), "\"%s\"", win_id);
765 +
766 while (fgets(buffer, CONFIG_FILE_LINE_MAX, fp) != NULL) {
767 buffer[CONFIG_FILE_LINE_MAX] = '\0';
768
765 - char *s = strstr(buffer, win_id);
769 + char *s = strstr(buffer, win_id_match);
770 if (!s) {
771 if (!copied)
772 continue;
@@ -793,60 +797,108 @@ static int map_windows_tz_to_ioanna(char *out, char *win_id, char *geo_name) {
797 }
798 #endif
799
796 -void get_system_timezone(void)
797 -{
798 - char buffer[FILENAME_MAX + 1] = "";
800 +// Detect the current IANA timezone name from the system.
801 +// Returns a pointer into the provided buffer, or NULL if detection fails.
802 +const char *detect_system_timezone_name(char *buffer, size_t buffer_size) {
803 const char *timezone = NULL;
800 - const char *tz = NULL;
804 +
805 #ifdef OS_WINDOWS
806 char geo_name[128];
807 char win_zone[256];
808 get_timezone_win_id(win_zone, 256);
809 get_win_geoiso(geo_name, 128);
806 - if (!map_windows_tz_to_ioanna(buffer, win_zone, geo_name))
810 + if (!map_windows_tz_to_iana(buffer, win_zone, geo_name))
811 timezone = buffer;
812 #else
809 - // avoid flood calls to stat(/etc/localtime)
810 - // http://stackoverflow.com/questions/4554271/how-to-avoid-excessive-stat-etc-localtime-calls-in-strftime-on-linux
811 - tz = getenv("TZ");
812 - if (!tz || !*tz)
813 - setenv("TZ", inicfg_get(&netdata_config, CONFIG_SECTION_ENV_VARS, "TZ", ":/etc/localtime"), 0);
814 -#endif
813 + // read the /etc/localtime symlink first — this is the authoritative source
814 + // on modern Linux (timedatectl always updates it, but /etc/timezone can lag)
815 + {
816 + ssize_t ret = readlink("/etc/localtime", buffer, buffer_size - 1);
817 + if (ret > 0) {
818 + buffer[ret] = '\0';
819
816 - ssize_t ret;
820 + const char *cmp = "/usr/share/zoneinfo/";
821 + size_t cmp_len = strlen(cmp);
822
818 - // use the TZ variable
819 - if (tz && *tz && *tz != ':') {
820 - timezone = tz;
821 - netdata_log_info("TIMEZONE: using TZ variable '%s'", timezone);
823 + char *s = strstr(buffer, cmp);
824 + if (s && s[cmp_len])
825 + timezone = &s[cmp_len];
826 + }
827 }
828
824 - // use the contents of /etc/timezone
825 - if (!timezone && !read_txt_file("/etc/timezone", buffer, sizeof(buffer))) {
829 + // fall back to /etc/timezone (Debian/Ubuntu)
830 + if (!timezone && !read_txt_file("/etc/timezone", buffer, buffer_size)) {
831 timezone = buffer;
827 - netdata_log_info("TIMEZONE: using the contents of /etc/timezone");
832 }
833 +#endif
834
830 - // read the link /etc/localtime
831 - if (!timezone) {
832 - ret = readlink("/etc/localtime", buffer, FILENAME_MAX);
835 + if (timezone && *timezone) {
836 + // sanitize in-place: keep only alnum, '_', '/', '-', '+'
837 + char *d = buffer;
838 + const char *src = timezone;
839 + const char *end = buffer + buffer_size - 1;
840 + while (*src && d < end) {
841 + if (isalnum((uint8_t)*src) || *src == '_' || *src == '/' || *src == '-' || *src == '+')
842 + *d++ = *src;
843 + src++;
844 + }
845 + *d = '\0';
846 + timezone = buffer;
847 + }
848
834 - if (ret > 0) {
835 - buffer[ret] = '\0';
849 + return (timezone && *timezone) ? timezone : NULL;
850 +}
851
837 - char *cmp = "/usr/share/zoneinfo/";
838 - size_t cmp_len = strlen(cmp);
852 +// Set at startup: true when the user explicitly set "timezone" in netdata.conf.
853 +static bool timezone_user_configured = false;
854
840 - char *s = strstr(buffer, cmp);
841 - if (s && s[cmp_len]) {
842 - timezone = &s[cmp_len];
843 - netdata_log_info("TIMEZONE: using the link of /etc/localtime: '%s'", timezone);
844 - }
845 - } else
846 - buffer[0] = '\0';
855 +// True when the timezone name came from a proper source (config,
856 +// /etc/localtime, /etc/timezone, TZ env var) rather than from the strftime("%Z")
857 +// fallback which only produces bare abbreviations like "CEST" or "PST".
858 +static bool timezone_is_tzdb_name = false;
859 +
860 +bool system_timezone_is_user_configured(void) {
861 + return timezone_user_configured;
862 +}
863 +
864 +bool system_timezone_is_tzdb_name(void) {
865 + return timezone_is_tzdb_name;
866 +}
867 +
868 +void get_system_timezone(void)
869 +{
870 + char buffer[FILENAME_MAX + 1] = "";
871 + const char *timezone = NULL;
872 +
873 +#ifndef OS_WINDOWS
874 + // avoid flood calls to stat(/etc/localtime)
875 + // http://stackoverflow.com/questions/4554271/how-to-avoid-excessive-stat-etc-localtime-calls-in-strftime-on-linux
876 + const char *tz = getenv("TZ");
877 + if (!tz || !*tz) {
878 + setenv("TZ", inicfg_get(&netdata_config, CONFIG_SECTION_ENV_VARS, "TZ", ":/etc/localtime"), 1);
879 + tz = getenv("TZ");
880 }
881
849 - // find the timezone from strftime()
882 + // use the TZ variable if it's an explicit IANA name (not a path starting with ':')
883 + if (tz && *tz && *tz != ':') {
884 + timezone = tz;
885 + timezone_is_tzdb_name = true;
886 + netdata_log_info("TIMEZONE: using TZ variable '%s'", timezone);
887 + }
888 +#endif
889 +
890 + // Detect from system sources (/etc/localtime symlink, /etc/timezone, Windows API)
891 + if (!timezone) {
892 + timezone = detect_system_timezone_name(buffer, sizeof(buffer));
893 + if (timezone) {
894 + timezone_is_tzdb_name = true;
895 + netdata_log_info("TIMEZONE: detected '%s'", timezone);
896 + }
897 + }
898 +
899 + // Last resort: use strftime %Z (gives abbreviation, not IANA name).
900 + // timezone_is_tzdb_name stays false so refresh_system_timezone() won't
901 + // try to resolve it via tzalloc() or the tzfile parser.
902 if (!timezone) {
903 time_t t;
904 struct tm *tmp, tmbuf;
@@ -855,9 +907,7 @@ void get_system_timezone(void)
907 tmp = localtime_r(&t, &tmbuf);
908
909 if (tmp != NULL) {
858 - if (strftime(buffer, FILENAME_MAX, "%Z", tmp) == 0)
859 - buffer[0] = '\0';
860 - else {
910 + if (strftime(buffer, FILENAME_MAX, "%Z", tmp) != 0) {
911 buffer[FILENAME_MAX] = '\0';
912 timezone = buffer;
913 netdata_log_info("TIMEZONE: using strftime(): '%s'", timezone);
@@ -865,69 +915,357 @@ void get_system_timezone(void)
915 }
916 }
917
868 - if (timezone && *timezone) {
869 - // make sure it does not have illegal characters
870 - // netdata_log_info("TIMEZONE: fixing '%s'", timezone);
918 + if (!timezone || !*timezone)
919 + timezone = "unknown";
920
872 - size_t len = strlen(timezone);
873 - char tmp[len + 1];
874 - char *d = tmp;
875 - *d = '\0';
921 + // Check if the user explicitly set "timezone" in netdata.conf BEFORE
922 + // inicfg_get auto-populates it with the detected default.
923 + timezone_user_configured = inicfg_exists(&netdata_config, CONFIG_SECTION_GLOBAL, "timezone");
924 +
925 + // inicfg_get returns a config-system-owned pointer, stable for the process lifetime
926 + const char *configured_tz = inicfg_get(&netdata_config, CONFIG_SECTION_GLOBAL, "timezone", timezone);
927 +
928 + // Treat "timezone =" (empty value) as not user-configured,
929 + // and fall back to the auto-detected timezone.
930 + if (timezone_user_configured && (!configured_tz || !*configured_tz)) {
931 + timezone_user_configured = false;
932 + configured_tz = timezone;
933 + }
934 +
935 + // If the user explicitly configured a timezone, treat it as a valid tzdb name
936 + // (the user is responsible for providing a valid value).
937 + if (timezone_user_configured)
938 + timezone_is_tzdb_name = true;
939 +
940 + // Compute abbreviation and UTC offset, then set all three atomically
941 + refresh_system_timezone(configured_tz, timezone_is_tzdb_name);
942 +}
943 +
944 +static inline uint32_t tzif_read_be32(const unsigned char *src) {
945 + return ((uint32_t)src[0] << 24) |
946 + ((uint32_t)src[1] << 16) |
947 + ((uint32_t)src[2] << 8) |
948 + (uint32_t)src[3];
949 +}
950 +
951 +static inline int64_t tzif_read_be64(const unsigned char *src) {
952 + return ((int64_t)(uint64_t)src[0] << 56) |
953 + ((int64_t)(uint64_t)src[1] << 48) |
954 + ((int64_t)(uint64_t)src[2] << 40) |
955 + ((int64_t)(uint64_t)src[3] << 32) |
956 + ((int64_t)(uint64_t)src[4] << 24) |
957 + ((int64_t)(uint64_t)src[5] << 16) |
958 + ((int64_t)(uint64_t)src[6] << 8) |
959 + (int64_t)(uint64_t)src[7];
960 +}
961 +
962 +static bool timezone_name_is_safe_tzdb_path(const char *timezone) {
963 + if (!timezone || !*timezone || *timezone == '/')
964 + return false;
965 +
966 + for (const char *p = timezone; *p; p++) {
967 + if (!(isalnum((uint8_t)*p) || *p == '_' || *p == '/' || *p == '-' || *p == '+'))
968 + return false;
969 + }
970 +
971 + return true;
972 +}
973
877 - while (*timezone) {
878 - if (isalnum((uint8_t)*timezone) || *timezone == '_' || *timezone == '/')
879 - *d++ = *timezone++;
880 - else
881 - timezone++;
974 +static bool timezone_info_from_tm(struct tm *tmp, char *abbrev, size_t abbrev_size, int32_t *offset) {
975 + if (!tmp)
976 + return false;
977 +
978 + int32_t new_offset = 0;
979 + char offset_str[16];
980 +
981 + if (strftime(abbrev, abbrev_size, "%Z", tmp) == 0)
982 + strncpyz(abbrev, "UTC", abbrev_size - 1);
983 +
984 + if (strftime(offset_str, sizeof(offset_str), "%z", tmp) != 0) {
985 + char sign = offset_str[0] == '-' || offset_str[0] == '+' ? offset_str[0] : '+';
986 + int hours = (isdigit((uint8_t)offset_str[1]) ? (offset_str[1] - '0') : 0) * 10 +
987 + (isdigit((uint8_t)offset_str[2]) ? (offset_str[2] - '0') : 0);
988 + int minutes = (isdigit((uint8_t)offset_str[3]) ? (offset_str[3] - '0') : 0) * 10 +
989 + (isdigit((uint8_t)offset_str[4]) ? (offset_str[4] - '0') : 0);
990 +
991 + new_offset = (hours * 3600) + (minutes * 60);
992 + if (sign == '-')
993 + new_offset = -new_offset;
994 + }
995 +
996 + *offset = new_offset;
997 + return true;
998 +}
999 +
1000 +static bool current_process_timezone_info(time_t t, char *abbrev, size_t abbrev_size, int32_t *offset) {
1001 + struct tm *tmp, tmbuf;
1002 + tmp = localtime_r(&t, &tmbuf);
1003 + return timezone_info_from_tm(tmp, abbrev, abbrev_size, offset);
1004 +}
1005 +
1006 +#ifndef OS_WINDOWS
1007 +struct tzif_header {
1008 + char magic[4];
1009 + char version;
1010 + char reserved[15];
1011 + unsigned char ttisgmtcnt[4];
1012 + unsigned char ttisstdcnt[4];
1013 + unsigned char leapcnt[4];
1014 + unsigned char timecnt[4];
1015 + unsigned char typecnt[4];
1016 + unsigned char charcnt[4];
1017 +};
1018 +
1019 +struct tzif_type {
1020 + int32_t gmtoff;
1021 + uint8_t isdst;
1022 + uint8_t abbrind;
1023 +};
1024 +
1025 +static bool tzif_skip_bytes(FILE *fp, size_t bytes) {
1026 + char buffer[256];
1027 + while (bytes) {
1028 + size_t chunk = bytes > sizeof(buffer) ? sizeof(buffer) : bytes;
1029 + if (fread(buffer, 1, chunk, fp) != chunk)
1030 + return false;
1031 + bytes -= chunk;
1032 + }
1033 + return true;
1034 +}
1035 +
1036 +// RFC 8536 sane upper bounds — real tzfiles are far smaller,
1037 +// but we allow headroom for unusual / future data.
1038 +#define TZIF_MAX_TIMECNT 2048
1039 +#define TZIF_MAX_TYPECNT 256
1040 +#define TZIF_MAX_CHARCNT 2048
1041 +#define TZIF_MAX_LEAPCNT 50
1042 +#define TZIF_MAX_ISSTDCNT 256
1043 +#define TZIF_MAX_ISGMTCNT 256
1044 +
1045 +static bool tzif_validate_header_counts(uint32_t timecnt, uint32_t typecnt, uint32_t charcnt,
1046 + uint32_t leapcnt, uint32_t ttisstdcnt, uint32_t ttisgmtcnt) {
1047 + if (timecnt > TZIF_MAX_TIMECNT || typecnt > TZIF_MAX_TYPECNT ||
1048 + charcnt > TZIF_MAX_CHARCNT || leapcnt > TZIF_MAX_LEAPCNT ||
1049 + ttisstdcnt > TZIF_MAX_ISSTDCNT || ttisgmtcnt > TZIF_MAX_ISGMTCNT)
1050 + return false;
1051 +
1052 + // ttisstdcnt and ttisgmtcnt must be 0 or equal to typecnt per RFC 8536
1053 + if ((ttisstdcnt != 0 && ttisstdcnt != typecnt) ||
1054 + (ttisgmtcnt != 0 && ttisgmtcnt != typecnt))
1055 + return false;
1056 +
1057 + return true;
1058 +}
1059 +
1060 +static bool tzif_skip_block(FILE *fp, const struct tzif_header *hdr, size_t time_size) {
1061 + uint32_t ttisgmtcnt = tzif_read_be32(hdr->ttisgmtcnt);
1062 + uint32_t ttisstdcnt = tzif_read_be32(hdr->ttisstdcnt);
1063 + uint32_t leapcnt = tzif_read_be32(hdr->leapcnt);
1064 + uint32_t timecnt = tzif_read_be32(hdr->timecnt);
1065 + uint32_t typecnt = tzif_read_be32(hdr->typecnt);
1066 + uint32_t charcnt = tzif_read_be32(hdr->charcnt);
1067 +
1068 + if (!tzif_validate_header_counts(timecnt, typecnt, charcnt, leapcnt, ttisstdcnt, ttisgmtcnt))
1069 + return false;
1070 +
1071 + size_t bytes = (size_t)timecnt * time_size +
1072 + (size_t)timecnt +
1073 + (size_t)typecnt * 6 +
1074 + (size_t)charcnt +
1075 + (size_t)leapcnt * (time_size + 4) +
1076 + (size_t)ttisstdcnt +
1077 + (size_t)ttisgmtcnt;
1078 +
1079 + return tzif_skip_bytes(fp, bytes);
1080 +}
1081 +
1082 +static int tzif_default_type_index(const struct tzif_type *types, uint32_t typecnt) {
1083 + if (!types || !typecnt)
1084 + return -1;
1085 +
1086 + for (uint32_t i = 0; i < typecnt; i++) {
1087 + if (!types[i].isdst)
1088 + return (int)i;
1089 + }
1090 +
1091 + return 0;
1092 +}
1093 +
1094 +static bool timezone_info_from_tzfile(const char *timezone, time_t t, char *abbrev, size_t abbrev_size, int32_t *offset) {
1095 + if (!timezone_name_is_safe_tzdb_path(timezone))
1096 + return false;
1097 +
1098 + const char *tzdir = getenv("TZDIR");
1099 + if (!tzdir || !*tzdir)
1100 + tzdir = "/usr/share/zoneinfo";
1101 +
1102 + char path[FILENAME_MAX + 1];
1103 + snprintfz(path, sizeof(path), "%s/%s", tzdir, timezone);
1104 +
1105 + FILE *fp = fopen(path, "rb");
1106 + if (!fp)
1107 + return false;
1108 +
1109 + bool ok = false;
1110 + struct tzif_header hdr;
1111 +
1112 + if (fread(&hdr, 1, sizeof(hdr), fp) != sizeof(hdr))
1113 + goto cleanup;
1114 +
1115 + if (memcmp(hdr.magic, "TZif", 4) != 0)
1116 + goto cleanup;
1117 +
1118 + if (hdr.version >= '2') {
1119 + if (!tzif_skip_block(fp, &hdr, 4))
1120 + goto cleanup;
1121 +
1122 + if (fread(&hdr, 1, sizeof(hdr), fp) != sizeof(hdr))
1123 + goto cleanup;
1124 +
1125 + if (memcmp(hdr.magic, "TZif", 4) != 0)
1126 + goto cleanup;
1127 + }
1128 +
1129 + uint32_t timecnt = tzif_read_be32(hdr.timecnt);
1130 + uint32_t typecnt = tzif_read_be32(hdr.typecnt);
1131 + uint32_t charcnt = tzif_read_be32(hdr.charcnt);
1132 + uint32_t leapcnt = tzif_read_be32(hdr.leapcnt);
1133 + uint32_t ttisstdcnt = tzif_read_be32(hdr.ttisstdcnt);
1134 + uint32_t ttisgmtcnt = tzif_read_be32(hdr.ttisgmtcnt);
1135 + size_t time_size = (hdr.version >= '2') ? 8 : 4;
1136 +
1137 + if (!typecnt || !charcnt)
1138 + goto cleanup;
1139 +
1140 + if (!tzif_validate_header_counts(timecnt, typecnt, charcnt, leapcnt, ttisstdcnt, ttisgmtcnt))
1141 + goto cleanup;
1142 +
1143 + int64_t *transition_times = callocz(timecnt ? timecnt : 1, sizeof(*transition_times));
1144 + uint8_t *transition_types = callocz(timecnt ? timecnt : 1, sizeof(*transition_types));
1145 + struct tzif_type *types = callocz(typecnt, sizeof(*types));
1146 + char *abbrs = callocz(charcnt + 1, sizeof(*abbrs));
1147 +
1148 + unsigned char timebuf[8];
1149 + unsigned char typebuf[6];
1150 +
1151 + for (uint32_t i = 0; i < timecnt; i++) {
1152 + if (fread(timebuf, 1, time_size, fp) != time_size)
1153 + goto free_and_cleanup;
1154 + transition_times[i] = (time_size == 8) ? tzif_read_be64(timebuf) : (int32_t)tzif_read_be32(timebuf);
1155 + }
1156 +
1157 + if (timecnt && fread(transition_types, 1, timecnt, fp) != timecnt)
1158 + goto free_and_cleanup;
1159 +
1160 + // validate all transition type indices before using them
1161 + for (uint32_t i = 0; i < timecnt; i++) {
1162 + if (transition_types[i] >= typecnt)
1163 + goto free_and_cleanup;
1164 + }
1165 +
1166 + for (uint32_t i = 0; i < typecnt; i++) {
1167 + if (fread(typebuf, 1, sizeof(typebuf), fp) != sizeof(typebuf))
1168 + goto free_and_cleanup;
1169 +
1170 + types[i].gmtoff = (int32_t)tzif_read_be32(typebuf);
1171 + types[i].isdst = typebuf[4];
1172 + types[i].abbrind = typebuf[5];
1173 + }
1174 +
1175 + if (fread(abbrs, 1, charcnt, fp) != charcnt)
1176 + goto free_and_cleanup;
1177 + abbrs[charcnt] = '\0';
1178 +
1179 + int type_index = -1;
1180 + if (timecnt == 0) {
1181 + type_index = tzif_default_type_index(types, typecnt);
1182 + } else {
1183 + for (uint32_t i = 0; i < timecnt; i++) {
1184 + if ((int64_t)t < transition_times[i])
1185 + break;
1186 + type_index = transition_types[i];
1187 }
883 - *d = '\0';
884 - strncpyz(buffer, tmp, len);
885 - timezone = buffer;
886 - netdata_log_info("TIMEZONE: fixed as '%s'", timezone);
1188 +
1189 + if (type_index < 0)
1190 + type_index = tzif_default_type_index(types, typecnt);
1191 }
1192
889 - if (!timezone || !*timezone)
890 - timezone = "unknown";
1193 + if (type_index < 0 || (uint32_t)type_index >= typecnt)
1194 + goto free_and_cleanup;
1195
892 - netdata_configured_timezone = inicfg_get(&netdata_config, CONFIG_SECTION_GLOBAL, "timezone", timezone);
1196 + if (types[type_index].abbrind >= charcnt)
1197 + goto free_and_cleanup;
1198
894 - //get the utc offset, and the timezone as returned by strftime
895 - //will be sent to the cloud
896 - //Note: This will need an agent restart to get new offset on time change (dst, etc).
897 - {
898 - time_t t;
899 - struct tm *tmp, tmbuf;
900 - char zone[FILENAME_MAX + 1];
901 - char sign[2], hh[3], mm[3];
1199 + const char *tz_abbrev = &abbrs[types[type_index].abbrind];
1200 + if (!*tz_abbrev)
1201 + tz_abbrev = "UTC";
1202
903 - t = now_realtime_sec();
904 - tmp = localtime_r(&t, &tmbuf);
1203 + strncpyz(abbrev, tz_abbrev, abbrev_size - 1);
1204 + *offset = types[type_index].gmtoff;
1205 + ok = true;
1206
906 - if (tmp != NULL) {
907 - if (strftime(zone, FILENAME_MAX, "%Z", tmp) == 0) {
908 - netdata_configured_abbrev_timezone = strdupz("UTC");
909 - } else
910 - netdata_configured_abbrev_timezone = strdupz(zone);
911 -
912 - if (strftime(zone, FILENAME_MAX, "%z", tmp) == 0) {
913 - netdata_configured_utc_offset = 0;
914 - } else {
915 - sign[0] = zone[0] == '-' || zone[0] == '+' ? zone[0] : '0';
916 - sign[1] = '\0';
917 - hh[0] = isdigit((uint8_t)zone[1]) ? zone[1] : '0';
918 - hh[1] = isdigit((uint8_t)zone[2]) ? zone[2] : '0';
919 - hh[2] = '\0';
920 - mm[0] = isdigit((uint8_t)zone[3]) ? zone[3] : '0';
921 - mm[1] = isdigit((uint8_t)zone[4]) ? zone[4] : '0';
922 - mm[2] = '\0';
923 -
924 - netdata_configured_utc_offset = (str2i(hh) * 3600) + (str2i(mm) * 60);
925 - netdata_configured_utc_offset =
926 - sign[0] == '-' ? -netdata_configured_utc_offset : netdata_configured_utc_offset;
1207 +free_and_cleanup:
1208 + freez(abbrs);
1209 + freez(types);
1210 + freez(transition_types);
1211 + freez(transition_times);
1212 +
1213 +cleanup:
1214 + fclose(fp);
1215 + return ok;
1216 +}
1217 +#endif
1218 +
1219 +void refresh_system_timezone(const char *timezone, bool is_tzdb_name) {
1220 + time_t t;
1221 + char abbrev[64];
1222 + const char *new_abbrev = "UTC";
1223 + int32_t new_offset = 0;
1224 +
1225 + // Update global flag when we have confirmed tzdb knowledge.
1226 + // When is_tzdb_name is false, preserve the existing global flag — a prior
1227 + // successful detection may have already promoted it to true.
1228 + if (is_tzdb_name)
1229 + timezone_is_tzdb_name = true;
1230 +
1231 + t = now_realtime_sec();
1232 + bool ok = false;
1233 +
1234 +#if defined(HAVE_TZALLOC) && defined(HAVE_LOCALTIME_RZ) && defined(HAVE_TZFREE)
1235 + if (is_tzdb_name) {
1236 + timezone_t tz = tzalloc(timezone);
1237 + if (tz) {
1238 + struct tm *tmp, tmbuf;
1239 + tmp = localtime_rz(tz, &t, &tmbuf);
1240 + ok = timezone_info_from_tm(tmp, abbrev, sizeof(abbrev), &new_offset);
1241 + tzfree(tz);
1242 + }
1243 + }
1244 +#elif !defined(OS_WINDOWS)
1245 + if (is_tzdb_name)
1246 + ok = timezone_info_from_tzfile(timezone, t, abbrev, sizeof(abbrev), &new_offset);
1247 +#endif
1248 +
1249 + if (!ok)
1250 + ok = current_process_timezone_info(t, abbrev, sizeof(abbrev), &new_offset);
1251 +
1252 + if (ok && *abbrev)
1253 + new_abbrev = abbrev;
1254 +
1255 + // Atomically update the system timezone triplet
1256 + system_tz_set(timezone, new_abbrev, new_offset);
1257 +
1258 + // Update localhost if it exists
1259 + if (localhost) {
1260 + if (rrdhost_update_timezone(localhost, timezone, new_abbrev, new_offset)) {
1261 + // Timezone changed — update the two labels directly, persist, and notify.
1262 + if (localhost->rrdlabels) {
1263 + rrdlabels_add(localhost->rrdlabels, "_timezone", timezone, RRDLABEL_SRC_AUTO);
1264 + rrdlabels_add(localhost->rrdlabels, "_abbrev_timezone", new_abbrev, RRDLABEL_SRC_AUTO);
1265 + rrdhost_flag_set(localhost, RRDHOST_FLAG_METADATA_LABELS | RRDHOST_FLAG_METADATA_UPDATE);
1266 + stream_send_host_labels(localhost);
1267 }
928 - } else {
929 - netdata_configured_abbrev_timezone = strdupz("UTC");
930 - netdata_configured_utc_offset = 0;
1268 + aclk_queue_node_info(localhost, false);
1269 }
1270 }
1271 }
src/daemon/analytics.h
+4
@@ -88,6 +88,10 @@ void analytics_log_dashboard(void);
88 void analytics_gather_mutable_meta_data(void);
89 void analytics_report_oom_score(long long int score);
90 void get_system_timezone(void);
91 +bool system_timezone_is_user_configured(void);
92 +bool system_timezone_is_tzdb_name(void);
93 +const char *detect_system_timezone_name(char *buffer, size_t buffer_size);
94 +void refresh_system_timezone(const char *timezone, bool is_tzdb_name);
95 void analytics_reset(void);
96 void analytics_init(void);
97
src/daemon/common.c
+52 -3
@@ -13,8 +13,57 @@ const char *netdata_configured_varlib_dir = VARLIB_DIR;
13 const char *netdata_configured_cloud_dir = VARLIB_DIR "/cloud.d";
14 const char *netdata_configured_home_dir = VARLIB_DIR;
15 const char *netdata_configured_host_prefix = NULL;
16 -const char *netdata_configured_timezone = NULL;
17 -const char *netdata_configured_abbrev_timezone = NULL;
18 -int32_t netdata_configured_utc_offset = 0;
16
17 bool netdata_ready = false;
18 +
19 +// ============================================================================
20 +// system timezone - thread-safe access
21 +
22 +static struct {
23 + SPINLOCK spinlock;
24 + char *timezone;
25 + char *abbrev_timezone;
26 + int32_t utc_offset;
27 +} system_tz = {
28 + .spinlock = SPINLOCK_INITIALIZER,
29 + .timezone = NULL,
30 + .abbrev_timezone = NULL,
31 + .utc_offset = 0,
32 +};
33 +
34 +void system_tz_set(const char *timezone, const char *abbrev_timezone, int32_t utc_offset) {
35 + // Own copies of both strings
36 + char *new_tz = strdupz(timezone ? timezone : "unknown");
37 + char *new_abbrev = strdupz(abbrev_timezone ? abbrev_timezone : "UTC");
38 +
39 + spinlock_lock(&system_tz.spinlock);
40 + // All readers use system_tz_get() which holds this same spinlock and copies,
41 + // so no reader can be using these pointers after we release the lock.
42 + char *old_tz = system_tz.timezone;
43 + char *old_abbrev = system_tz.abbrev_timezone;
44 + system_tz.timezone = new_tz;
45 + system_tz.abbrev_timezone = new_abbrev;
46 + system_tz.utc_offset = utc_offset;
47 + spinlock_unlock(&system_tz.spinlock);
48 +
49 + freez(old_tz);
50 + freez(old_abbrev);
51 +}
52 +
53 +SYSTEM_TZ system_tz_get(void) {
54 + SYSTEM_TZ tz;
55 + spinlock_lock(&system_tz.spinlock);
56 + tz.timezone = strdupz(system_tz.timezone ? system_tz.timezone : "unknown");
57 + tz.abbrev_timezone = strdupz(system_tz.abbrev_timezone ? system_tz.abbrev_timezone : "UTC");
58 + tz.utc_offset = system_tz.utc_offset;
59 + spinlock_unlock(&system_tz.spinlock);
60 + return tz;
61 +}
62 +
63 +void system_tz_free(SYSTEM_TZ *tz) {
64 + freez(tz->timezone);
65 + freez(tz->abbrev_timezone);
66 + tz->timezone = NULL;
67 + tz->abbrev_timezone = NULL;
68 + tz->utc_offset = 0;
69 +}
src/daemon/common.h
+14 -3
@@ -82,11 +82,22 @@ extern const char *netdata_configured_varlib_dir;
82 extern const char *netdata_configured_cloud_dir;
83 extern const char *netdata_configured_home_dir;
84 extern const char *netdata_configured_host_prefix;
85 -extern const char *netdata_configured_timezone;
86 -extern const char *netdata_configured_abbrev_timezone;
87 -extern int32_t netdata_configured_utc_offset;
85 extern bool netdata_anonymous_statistics_enabled;
86
87 +// Thread-safe system timezone access.
88 +// Use system_tz_get() to read; the returned struct owns strdup'd copies
89 +// that must be released with system_tz_free().
90 +// Use system_tz_set() to write (called by timezone detection and periodic refresh).
91 +typedef struct {
92 + char *timezone; // IANA timezone name, e.g. "America/New_York" (owned, strdup'd)
93 + char *abbrev_timezone; // abbreviated timezone, e.g. "EDT" (owned, strdup'd)
94 + int32_t utc_offset; // offset from UTC in seconds
95 +} SYSTEM_TZ;
96 +
97 +SYSTEM_TZ system_tz_get(void);
98 +void system_tz_set(const char *timezone, const char *abbrev_timezone, int32_t utc_offset);
99 +void system_tz_free(SYSTEM_TZ *tz);
100 +
101 extern bool netdata_ready;
102 extern time_t netdata_start_time;
103
src/daemon/pulse/pulse-daemon.c
+44
@@ -72,8 +72,52 @@ static void pulse_daemon_uptime_do(bool extended __maybe_unused) {
72 }
73 }
74
75 +// Called from a single pulse daemon thread, so last_refresh_ut needs no synchronization.
76 +static void pulse_daemon_timezone_do(bool extended __maybe_unused) {
77 + static usec_t last_refresh_ut = 0;
78 +
79 + usec_t now_ut = now_monotonic_usec();
80 +
81 + // refresh every 30 minutes to pick up DST changes
82 + if (now_ut - last_refresh_ut >= 30 * 60 * USEC_PER_SEC) {
83 + if (system_timezone_is_user_configured()) {
84 + // User explicitly configured a timezone in netdata.conf — respect it,
85 + // just refresh the abbreviation and offset (handles DST transitions).
86 + static bool mismatch_logged = false;
87 + if (!mismatch_logged) {
88 + mismatch_logged = true;
89 + char sys_buf[FILENAME_MAX + 1];
90 + const char *sys_tz = detect_system_timezone_name(sys_buf, sizeof(sys_buf));
91 + SYSTEM_TZ tz = system_tz_get();
92 + if (sys_tz && tz.timezone && strcmp(sys_tz, tz.timezone) != 0)
93 + nd_log(NDLS_DAEMON, NDLP_NOTICE,
94 + "TIMEZONE: configured '%s' differs from system '%s'",
95 + tz.timezone, sys_tz);
96 + system_tz_free(&tz);
97 + }
98 + SYSTEM_TZ tz = system_tz_get();
99 + refresh_system_timezone(tz.timezone, true);
100 + system_tz_free(&tz);
101 + } else {
102 + // Auto-detected timezone — re-detect to pick up system changes.
103 + char buf[FILENAME_MAX + 1];
104 + const char *detected = detect_system_timezone_name(buf, sizeof(buf));
105 + if (detected) {
106 + refresh_system_timezone(detected, true);
107 + } else {
108 + // Detection failed — use the stored name with its original tzdb flag.
109 + SYSTEM_TZ tz = system_tz_get();
110 + refresh_system_timezone(tz.timezone, system_timezone_is_tzdb_name());
111 + system_tz_free(&tz);
112 + }
113 + }
114 + last_refresh_ut = now_ut;
115 + }
116 +}
117 +
118 void pulse_daemon_do(bool extended) {
119 pulse_daemon_cpu_usage_do(extended);
120 pulse_daemon_uptime_do(extended);
121 pulse_daemon_memory_do(extended);
122 + pulse_daemon_timezone_do(extended);
123 }
src/daemon/status-file.c
+5 -2
@@ -789,8 +789,11 @@ static void daemon_status_file_refresh(DAEMON_STATUS status) {
789 if(get_daemon_status_fields_from_system_info(&session_status))
790 product_name_vendor_type(&session_status);
791
792 - if(netdata_configured_timezone)
793 - safecpy(session_status.timezone, netdata_configured_timezone);
792 + {
793 + SYSTEM_TZ tz = system_tz_get();
794 + safecpy(session_status.timezone, tz.timezone);
795 + system_tz_free(&tz);
796 + }
797
798 session_status.exit_reason = exit_initiated_get();
799 session_status.profile = nd_profile_detect_and_configure(false);
src/database/engine/dbengine-stresstest.c
+7 -4
@@ -8,14 +8,15 @@ static RRDHOST *dbengine_rrdhost_find_or_create(char *name) {
8 /* We don't want to drop metrics when generating load,
9 * we prefer to block data generation itself */
10
11 - return rrdhost_find_or_create(
11 + SYSTEM_TZ tz = system_tz_get();
12 + RRDHOST *host = rrdhost_find_or_create(
13 name,
14 name,
15 name,
16 os_type,
16 - netdata_configured_timezone,
17 - netdata_configured_abbrev_timezone,
18 - netdata_configured_utc_offset,
17 + tz.timezone,
18 + tz.abbrev_timezone,
19 + tz.utc_offset,
20 program_name,
21 NETDATA_VERSION,
22 nd_profile.update_every,
@@ -32,6 +33,8 @@ static RRDHOST *dbengine_rrdhost_find_or_create(char *name) {
33 NULL,
34 0
35 );
36 + system_tz_free(&tz);
37 + return host;
38 }
39
40 static inline void rrddim_set_by_pointer_fake_time(RRDDIM *rd, collected_number value, time_t now) {
src/database/engine/dbengine-unittest.c
+7 -4
@@ -94,14 +94,15 @@ static RRDHOST *dbengine_rrdhost_find_or_create(char *name) {
94 /* We don't want to drop metrics when generating load,
95 * we prefer to block data generation itself */
96
97 - return rrdhost_find_or_create(
97 + SYSTEM_TZ tz = system_tz_get();
98 + RRDHOST *host = rrdhost_find_or_create(
99 name,
100 name,
101 name,
102 os_type,
102 - netdata_configured_timezone,
103 - netdata_configured_abbrev_timezone,
104 - netdata_configured_utc_offset,
103 + tz.timezone,
104 + tz.abbrev_timezone,
105 + tz.utc_offset,
106 program_name,
107 NETDATA_VERSION,
108 nd_profile.update_every,
@@ -118,6 +119,8 @@ static RRDHOST *dbengine_rrdhost_find_or_create(char *name) {
119 NULL,
120 0
121 );
122 + system_tz_free(&tz);
123 + return host;
124 }
125
126 static void test_dbengine_create_charts(RRDHOST *host, RRDSET *st[CHARTS], RRDDIM *rd[CHARTS][DIMS],
src/database/rrd.c
+5 -3
@@ -124,14 +124,15 @@ int rrd_init(const char *hostname, struct rrdhost_system_info *system_info, bool
124 health_load_config_defaults();
125 }
126
127 + SYSTEM_TZ tz = system_tz_get();
128 localhost = rrdhost_create(
129 hostname
130 , registry_get_this_machine_hostname()
131 , machine_guid_get_txt()
132 , os_type
132 - , netdata_configured_timezone
133 - , netdata_configured_abbrev_timezone
134 - , netdata_configured_utc_offset
133 + , tz.timezone
134 + , tz.abbrev_timezone
135 + , tz.utc_offset
136 , program_name
137 , NETDATA_VERSION
138 , nd_profile.update_every, default_rrd_history_entries
@@ -148,6 +149,7 @@ int rrd_init(const char *hostname, struct rrdhost_system_info *system_info, bool
149 , 1
150 , 0
151 );
152 + system_tz_free(&tz);
153 rrdhost_system_info_free(system_info);
154
155 if (unlikely(!localhost))
src/database/rrdhost-labels.c
+6 -2
@@ -162,8 +162,12 @@ static void rrdhost_load_auto_labels(void) {
162 if (localhost->stream.snd.destination)
163 rrdlabels_add(labels, "_streams_to", string2str(localhost->stream.snd.destination), RRDLABEL_SRC_AUTO);
164
165 - rrdlabels_add(labels, "_timezone", rrdhost_timezone(localhost), RRDLABEL_SRC_AUTO);
166 - rrdlabels_add(labels, "_abbrev_timezone", rrdhost_abbrev_timezone(localhost), RRDLABEL_SRC_AUTO);
165 + {
166 + RRDHOST_TZ host_tz = rrdhost_tz_get(localhost);
167 + rrdlabels_add(labels, "_timezone", host_tz.timezone, RRDLABEL_SRC_AUTO);
168 + rrdlabels_add(labels, "_abbrev_timezone", host_tz.abbrev_timezone, RRDLABEL_SRC_AUTO);
169 + rrdhost_tz_free(&host_tz);
170 + }
171 }
172
173 void reload_host_labels(void) {
src/database/rrdhost.c
+44 -5
@@ -139,10 +139,16 @@ static inline void rrdhost_init_os(RRDHOST *host, const char *os) {
139 string_freez(old);
140 }
141
142 -static inline void rrdhost_init_timezone(RRDHOST *host, const char *timezone, const char *abbrev_timezone, int32_t utc_offset) {
143 - if (host->timezone && timezone && !strcmp(rrdhost_timezone(host), timezone) && host->abbrev_timezone && abbrev_timezone &&
144 - !strcmp(rrdhost_abbrev_timezone(host), abbrev_timezone) && host->utc_offset == utc_offset)
145 - return;
142 +// Caller must hold rrdhost_update_lock (or be in single-threaded host creation).
143 +// Returns true if anything actually changed.
144 +static inline bool rrdhost_init_timezone(RRDHOST *host, const char *timezone, const char *abbrev_timezone, int32_t utc_offset) {
145 + const char *cur_tz = host->timezone ? string2str(host->timezone) : NULL;
146 + const char *cur_abbrev = host->abbrev_timezone ? string2str(host->abbrev_timezone) : NULL;
147 +
148 + if (cur_tz && timezone && !strcmp(cur_tz, timezone) &&
149 + cur_abbrev && abbrev_timezone && !strcmp(cur_abbrev, abbrev_timezone) &&
150 + host->utc_offset == utc_offset)
151 + return false;
152
153 STRING *old = host->timezone;
154 host->timezone = string_strdupz((timezone && *timezone)?timezone:"unknown");
@@ -153,6 +159,7 @@ static inline void rrdhost_init_timezone(RRDHOST *host, const char *timezone, co
159 string_freez(old);
160
161 host->utc_offset = utc_offset;
162 + return true;
163 }
164
165 void set_host_properties(RRDHOST *host, int update_every,
@@ -173,6 +180,35 @@ void set_host_properties(RRDHOST *host, int update_every,
180 host->registry_hostname = string_strdupz((registry_hostname && *registry_hostname) ? registry_hostname : rrdhost_hostname(host));
181 }
182
183 +bool rrdhost_update_timezone(RRDHOST *host, const char *timezone, const char *abbrev_timezone, int32_t utc_offset) {
184 + spinlock_lock(&host->rrdhost_update_lock);
185 + bool changed = rrdhost_init_timezone(host, timezone, abbrev_timezone, utc_offset);
186 + spinlock_unlock(&host->rrdhost_update_lock);
187 +
188 + if (changed)
189 + rrdhost_flag_set(host, RRDHOST_FLAG_METADATA_INFO | RRDHOST_FLAG_METADATA_UPDATE);
190 +
191 + return changed;
192 +}
193 +
194 +RRDHOST_TZ rrdhost_tz_get(RRDHOST *host) {
195 + RRDHOST_TZ tz;
196 + spinlock_lock(&host->rrdhost_update_lock);
197 + tz.timezone = strdupz(host->timezone ? string2str(host->timezone) : "unknown");
198 + tz.abbrev_timezone = strdupz(host->abbrev_timezone ? string2str(host->abbrev_timezone) : "UTC");
199 + tz.utc_offset = host->utc_offset;
200 + spinlock_unlock(&host->rrdhost_update_lock);
201 + return tz;
202 +}
203 +
204 +void rrdhost_tz_free(RRDHOST_TZ *tz) {
205 + freez(tz->timezone);
206 + freez(tz->abbrev_timezone);
207 + tz->timezone = NULL;
208 + tz->abbrev_timezone = NULL;
209 + tz->utc_offset = 0;
210 +}
211 +
212 // ----------------------------------------------------------------------------
213 // RRDHOST - add a host
214
@@ -321,6 +357,7 @@ RRDHOST *rrdhost_create(
357 host->health.enabled = ((memory_mode == RRD_DB_MODE_NONE)) ? false : health;
358
359 spinlock_init(&host->receiver_lock);
360 + spinlock_init(&host->rrdhost_update_lock);
361
362 if (likely(!archived)) {
363 rrd_functions_host_init(host);
@@ -428,6 +465,7 @@ RRDHOST *rrdhost_create(
465
466 // ------------------------------------------------------------------------
467
468 + RRDHOST_TZ host_tz = rrdhost_tz_get(host);
469 nd_log(NDLS_DAEMON, NDLP_INFO,
470 "Host '%s' (at registry as '%s') with guid '%s' initialized"
471 ", os '%s'"
@@ -447,7 +485,7 @@ RRDHOST *rrdhost_create(
485 , rrdhost_registry_hostname(host)
486 , host->machine_guid
487 , rrdhost_os(host)
450 - , rrdhost_timezone(host)
488 + , host_tz.timezone
489 , rrdhost_program_name(host)
490 , rrdhost_program_version(host)
491 , host->rrd_update_every
@@ -462,6 +500,7 @@ RRDHOST *rrdhost_create(
500 , string2str(host->health.default_exec)
501 , string2str(host->health.default_recipient)
502 );
503 + rrdhost_tz_free(&host_tz);
504
505 if(!archived) {
506 rrdhost_flag_set(host, RRDHOST_FLAG_METADATA_INFO | RRDHOST_FLAG_METADATA_UPDATE);
src/database/rrdhost.h
+15 -2
@@ -347,8 +347,8 @@ extern RRDHOST *localhost;
347 #define rrdhost_hostname(host) string2str((host)->hostname)
348 #define rrdhost_registry_hostname(host) string2str((host)->registry_hostname)
349 #define rrdhost_os(host) string2str((host)->os)
350 -#define rrdhost_timezone(host) string2str((host)->timezone)
351 -#define rrdhost_abbrev_timezone(host) string2str((host)->abbrev_timezone)
350 +// Timezone fields are mutable at runtime (DST refresh); use rrdhost_tz_get() for thread-safe access.
351 +// Do NOT access host->timezone or host->abbrev_timezone directly outside of rrdhost_update_lock.
352 #define rrdhost_program_name(host) string2str((host)->program_name)
353 #define rrdhost_program_version(host) string2str((host)->program_version)
354
@@ -467,6 +467,19 @@ void set_host_properties(
467 const char *os, const char *tzone, const char *abbrev_tzone, int32_t utc_offset,
468 const char *prog_name, const char *prog_version);
469
470 +bool rrdhost_update_timezone(RRDHOST *host, const char *timezone, const char *abbrev_timezone, int32_t utc_offset);
471 +
472 +// Thread-safe timezone snapshot from an RRDHOST.
473 +// The returned struct owns strdup'd copies; release with rrdhost_tz_free().
474 +typedef struct {
475 + char *timezone; // IANA timezone name (owned, strdup'd)
476 + char *abbrev_timezone; // abbreviated timezone (owned, strdup'd)
477 + int32_t utc_offset; // offset from UTC in seconds
478 +} RRDHOST_TZ;
479 +
480 +RRDHOST_TZ rrdhost_tz_get(RRDHOST *host);
481 +void rrdhost_tz_free(RRDHOST_TZ *tz);
482 +
483 static inline void rrdhost_retention(RRDHOST *host, time_t now, bool online, time_t *from, time_t *to) {
484 time_t first_time_s = 0, last_time_s = 0;
485 spinlock_lock(&host->retention.spinlock);
src/database/sqlite/sqlite_aclk_alert.c
+8 -2
@@ -407,8 +407,14 @@ void health_alarm_log_populate(
407
408 alarm_log->config_hash = sqlite3_uuid_unparse_strdupz(res, CONFIG_HASH_ID);
409
410 - alarm_log->utc_offset = host->utc_offset;
411 - alarm_log->timezone = strdupz(rrdhost_abbrev_timezone(host));
410 + {
411 + RRDHOST_TZ host_tz = rrdhost_tz_get(host);
412 + alarm_log->utc_offset = host_tz.utc_offset;
413 + // Transfer ownership of the strdup'd copy instead of duplicating again
414 + alarm_log->timezone = host_tz.abbrev_timezone;
415 + host_tz.abbrev_timezone = NULL;
416 + rrdhost_tz_free(&host_tz);
417 + }
418 alarm_log->exec_path = sqlite3_column_bytes(res, EXEC) ?
419 strdupz((char *)sqlite3_column_text(res, EXEC)) :
420 strdupz((char *)string2str(host->health.default_exec));
src/database/sqlite/sqlite_aclk_node.c
+4 -1
@@ -71,11 +71,13 @@ static void build_node_info(RRDHOST *host, struct aclk_sync_completion *sync_com
71 if (host != localhost && !is_virtual_host)
72 host_version = stream_receiver_program_version_strdupz(host);
73
74 + RRDHOST_TZ host_tz = rrdhost_tz_get(host);
75 +
76 node_info.data.name = rrdhost_hostname(host);
77 node_info.data.os = rrdhost_os(host);
78 node_info.data.version = host_version ? host_version : NETDATA_VERSION;
79 node_info.data.release_channel = get_release_channel();
78 - node_info.data.timezone = rrdhost_abbrev_timezone(host);
80 + node_info.data.timezone = host_tz.abbrev_timezone;
81 node_info.data.custom_info = inicfg_get(&netdata_config, CONFIG_SECTION_WEB, "custom dashboard_info.js", "");
82 node_info.data.machine_guid = host->machine_guid;
83 node_info.node_capabilities = (struct capability *)aclk_get_agent_capas();
@@ -94,6 +96,7 @@ static void build_node_info(RRDHOST *host, struct aclk_sync_completion *sync_com
96 host == localhost ? "parent" : "child");
97
98 rrd_rdunlock();
99 + rrdhost_tz_free(&host_tz);
100 freez(node_info.node_instance_capabilities);
101 freez(host_version);
102
src/database/sqlite/sqlite_health.c
+6 -2
@@ -1069,8 +1069,12 @@ void sql_health_alarm_log2json(RRDHOST *host, BUFFER *wb, time_t after, const ch
1069 buffer_json_add_array_item_object(wb); // this node
1070
1071 buffer_json_member_add_string_or_empty(wb, "hostname", rrdhost_hostname(host));
1072 - buffer_json_member_add_int64(wb, "utc_offset", (int64_t)host->utc_offset);
1073 - buffer_json_member_add_string_or_empty(wb, "timezone", rrdhost_abbrev_timezone(host));
1072 + {
1073 + RRDHOST_TZ host_tz = rrdhost_tz_get(host);
1074 + buffer_json_member_add_int64(wb, "utc_offset", (int64_t)host_tz.utc_offset);
1075 + buffer_json_member_add_string_or_empty(wb, "timezone", host_tz.abbrev_timezone);
1076 + rrdhost_tz_free(&host_tz);
1077 + }
1078 buffer_json_member_add_int64(wb, "unique_id", (int64_t) sqlite3_column_int64(stmt_query, 0));
1079 buffer_json_member_add_int64(wb, "alarm_id", (int64_t) sqlite3_column_int64(stmt_query, 1));
1080 buffer_json_member_add_int64(wb, "alarm_event_id", (int64_t) sqlite3_column_int64(stmt_query, 2));
src/database/sqlite/sqlite_metadata.c
+7 -3
@@ -1088,6 +1088,7 @@ done:
1088 static int store_host_metadata(RRDHOST *host)
1089 {
1090 sqlite3_stmt *res = NULL;
1091 + RRDHOST_TZ host_tz = { 0 };
1092
1093 if (!PREPARE_STATEMENT(db_meta, SQL_STORE_HOST_INFO, &res))
1094 return false;
@@ -1098,12 +1099,13 @@ static int store_host_metadata(RRDHOST *host)
1099 SQLITE_BIND_FAIL(bind_fail, bind_text_null(res, ++param, rrdhost_registry_hostname(host), 1));
1100 SQLITE_BIND_FAIL(bind_fail, sqlite3_bind_int(res, ++param, host->rrd_update_every));
1101 SQLITE_BIND_FAIL(bind_fail, bind_text_null(res, ++param, rrdhost_os(host), 1));
1101 - SQLITE_BIND_FAIL(bind_fail, bind_text_null(res, ++param, rrdhost_timezone(host), 1));
1102 + host_tz = rrdhost_tz_get(host);
1103 + SQLITE_BIND_FAIL(bind_fail, bind_text_null(res, ++param, host_tz.timezone, 1));
1104 SQLITE_BIND_FAIL(bind_fail, bind_text_null(res, ++param, "", 1));
1105 SQLITE_BIND_FAIL(bind_fail, sqlite3_bind_int(res, ++param, rrdhost_ingestion_hops(host)));
1106 SQLITE_BIND_FAIL(bind_fail, sqlite3_bind_int(res, ++param, host->rrd_memory_mode));
1105 - SQLITE_BIND_FAIL(bind_fail, bind_text_null(res, ++param, rrdhost_abbrev_timezone(host), 1));
1106 - SQLITE_BIND_FAIL(bind_fail, sqlite3_bind_int(res, ++param, host->utc_offset));
1107 + SQLITE_BIND_FAIL(bind_fail, bind_text_null(res, ++param, host_tz.abbrev_timezone, 1));
1108 + SQLITE_BIND_FAIL(bind_fail, sqlite3_bind_int(res, ++param, host_tz.utc_offset));
1109 SQLITE_BIND_FAIL(bind_fail, bind_text_null(res, ++param, rrdhost_program_name(host), 1));
1110 SQLITE_BIND_FAIL(bind_fail, bind_text_null(res, ++param, rrdhost_program_version(host), 1));
1111 SQLITE_BIND_FAIL(bind_fail, sqlite3_bind_int64(res, ++param, host->rrd_history_entries));
@@ -1116,12 +1118,14 @@ static int store_host_metadata(RRDHOST *host)
1118 error_report("Failed to store host %s, rc = %d", rrdhost_hostname(host), store_rc);
1119
1120 SQLITE_FINALIZE(res);
1121 + rrdhost_tz_free(&host_tz);
1122
1123 return store_rc != SQLITE_DONE;
1124
1125 bind_fail:
1126 REPORT_BIND_FAIL(res, param);
1127 SQLITE_FINALIZE(res);
1128 + rrdhost_tz_free(&host_tz);
1129 return 1;
1130 }
1131
src/plugins.d/pluginsd_parser.c
+5 -3
@@ -205,14 +205,15 @@ static inline PARSER_RC pluginsd_host_define_end(char **words __maybe_unused, si
205
206 struct rrdhost_system_info *system_info = rrdhost_system_info_from_host_labels(parser->user.host_define.rrdlabels);
207
208 + SYSTEM_TZ tz = system_tz_get();
209 RRDHOST *host = rrdhost_find_or_create(
210 string2str(parser->user.host_define.hostname),
211 string2str(parser->user.host_define.hostname),
212 parser->user.host_define.machine_guid_str,
213 NETDATA_VIRTUAL_HOST,
213 - netdata_configured_timezone,
214 - netdata_configured_abbrev_timezone,
215 - netdata_configured_utc_offset,
214 + tz.timezone,
215 + tz.abbrev_timezone,
216 + tz.utc_offset,
217 program_name,
218 NETDATA_VERSION,
219 nd_profile.update_every,
@@ -228,6 +229,7 @@ static inline PARSER_RC pluginsd_host_define_end(char **words __maybe_unused, si
229 stream_receive.replication.step,
230 system_info,
231 false);
232 + system_tz_free(&tz);
233
234 rrdhost_system_info_free(system_info);
235
src/streaming/stream-connector.c
+7 -3
@@ -308,9 +308,13 @@ bool stream_connect(struct sender_state *s, uint16_t default_port, time_t timeou
308 buffer_key_value_urlencode(wb, "&machine_guid", host->machine_guid);
309 buffer_sprintf(wb, "&update_every=%d", (int)nd_profile.update_every);
310 buffer_key_value_urlencode(wb, "&os", rrdhost_os(host));
311 - buffer_key_value_urlencode(wb, "&timezone", rrdhost_timezone(host));
312 - buffer_key_value_urlencode(wb, "&abbrev_timezone", rrdhost_abbrev_timezone(host));
313 - buffer_sprintf(wb, "&utc_offset=%d", host->utc_offset);
311 + {
312 + RRDHOST_TZ host_tz = rrdhost_tz_get(host);
313 + buffer_key_value_urlencode(wb, "&timezone", host_tz.timezone);
314 + buffer_key_value_urlencode(wb, "&abbrev_timezone", host_tz.abbrev_timezone);
315 + buffer_sprintf(wb, "&utc_offset=%d", host_tz.utc_offset);
316 + rrdhost_tz_free(&host_tz);
317 + }
318 buffer_sprintf(wb, "&hops=%d", s->hops);
319 buffer_sprintf(wb, "&ver=%u", s->capabilities);
320 rrdhost_system_info_to_url_encode_stream(wb, host->system_info);
src/web/api/formatters/charts2json.c
+5 -1
@@ -52,7 +52,11 @@ void charts2json(RRDHOST *host, BUFFER *wb) {
52 buffer_json_member_add_string(wb, "version", rrdhost_program_version(host));
53 buffer_json_member_add_string(wb, "release_channel", get_release_channel());
54 buffer_json_member_add_string(wb, "os", rrdhost_os(host));
55 - buffer_json_member_add_string(wb, "timezone", rrdhost_timezone(host));
55 + {
56 + RRDHOST_TZ host_tz = rrdhost_tz_get(host);
57 + buffer_json_member_add_string(wb, "timezone", host_tz.timezone);
58 + rrdhost_tz_free(&host_tz);
59 + }
60 buffer_json_member_add_int64(wb, "update_every", host->rrd_update_every);
61 buffer_json_member_add_int64(wb, "history", host->rrd_history_entries);
62 buffer_json_member_add_string(wb, "memory_mode", rrd_memory_mode_name(host->rrd_memory_mode));