@cryptotaxi247 / netdata-1 / commits / 39339bf57

status file 24 (#19996)

* prevent crash when sqlite has null uuid * simplify parsing rfc3339 timestamps * prepare for separating dedup from status file * split status file dedup to a separate file - no code changes * split status-file-io * save deduplication hashes to a separate file * keep a hash of the deduplication slots, to ensure the file cannot be tampered * added machine guid timestamp to status file * do not sent zero machine guid timestamp * check for NULL rd on retention * fix worker_job_id parsing

Costa Tsaousis committed Mar 30, 2025 at 19:35 UTC 39339bf572bf1853c2cc965318b69ddf90862857
17 files changed +563 -455
CMakeLists.txt
+6 -2
@@ -1271,14 +1271,18 @@ set(DAEMON_FILES
1271 src/daemon/pulse/pulse-db-dbengine-retention.h
1272 src/daemon/pulse/pulse-parents.c
1273 src/daemon/pulse/pulse-parents.h
1274 - src/daemon/daemon-status-file.c
1275 - src/daemon/daemon-status-file.h
1274 + src/daemon/status-file.c
1275 + src/daemon/status-file.h
1276 src/daemon/config/netdata-conf-ssl.c
1277 src/daemon/config/netdata-conf-ssl.h
1278 src/daemon/daemon-systemd-watcher.c
1279 src/daemon/daemon-systemd-watcher.h
1280 src/daemon/machine-guid.c
1281 src/daemon/machine-guid.h
1282 + src/daemon/status-file-dedup.c
1283 + src/daemon/status-file-dedup.h
1284 + src/daemon/status-file-io.c
1285 + src/daemon/status-file-io.h
1286 )
1287
1288 set(H2O_FILES
src/daemon/daemon-shutdown-watcher.c
+1 -1
@@ -1,7 +1,7 @@
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "daemon-shutdown-watcher.h"
4 -#include "daemon-status-file.h"
4 +#include "status-file.h"
5
6 #ifdef ENABLE_SENTRY
7 #include "sentry-native/sentry-native.h"
src/daemon/daemon-shutdown.c
+1 -1
@@ -2,7 +2,7 @@
2
3 #include "daemon-shutdown.h"
4 #include "daemon-service.h"
5 -#include "daemon-status-file.h"
5 +#include "status-file.h"
6 #include "daemon/daemon-shutdown-watcher.h"
7 #include "static_threads.h"
8 #include "common.h"
src/daemon/machine-guid.c
+1 -1
@@ -164,7 +164,7 @@ static ND_MACHINE_GUID machine_guid_get_or_create(void) {
164 nd_log(NDLS_DAEMON, NDLP_ERR, "MACHINE_GUID: failed to read GUID from file '%s'", filename);
165
166 // Attempt to retrieve GUID from daemon status file.
167 - h.uuid = daemon_status_file_get_host_id();
167 + h = daemon_status_file_get_host_id();
168 if (UUIDiszero(h.uuid)) {
169 // If the status file does not contain a GUID, generate a new one.
170 nd_log(NDLS_DAEMON, NDLP_INFO, "MACHINE_GUID: generating a new GUID");
src/daemon/main.c
+1 -1
@@ -3,7 +3,7 @@
3 #include "common.h"
4 #include "buildinfo.h"
5 #include "daemon-shutdown-watcher.h"
6 -#include "daemon-status-file.h"
6 +#include "status-file.h"
7 #include "static_threads.h"
8 #include "web/api/queries/backfill.h"
9
src/daemon/signal-handler.c
+1 -1
@@ -1,7 +1,7 @@
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "common.h"
4 -#include "daemon/daemon-status-file.h"
4 +#include "daemon/status-file.h"
5
6 #ifdef ENABLE_SENTRY
7 #include "sentry-native/sentry-native.h"
src/daemon/status-file-dedup.c new
+221
@@ -0,0 +1,221 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "status-file-dedup.h"
4 +#include "status-file-io.h"
5 +
6 +#define DEDUP_FILENAME "dedup-netdata.dat"
7 +#define DEDUP_VERSION 1
8 +#define DEDUP_MAGIC 0x1DEDA9F17EDA7150 // 1(x) DEDUPFILEDAT (v)1 50(entries)
9 +
10 +#define REPORT_EVENTS_EVERY (86400 - 3600) // -1 hour to tolerate cron randomness
11 +
12 +typedef struct {
13 + uint64_t magic;
14 + size_t v;
15 + uint64_t hash;
16 + struct {
17 + bool sentry;
18 + uint64_t hash;
19 + usec_t timestamp_ut;
20 + } slot[50];
21 +} DAEMON_STATUS_DEDUP;
22 +
23 +static DAEMON_STATUS_DEDUP dedup = { 0 };
24 +
25 +static void stack_trace_anonymize(char *s) {
26 + // IMPORTANT: NO LOCKS OR ALLOCATIONS HERE, THIS FUNCTION IS CALLED FROM SIGNAL HANDLERS
27 + // THIS FUNCTION MUST USE ONLY ASYNC-SIGNAL-SAFE OPERATIONS
28 +
29 + char *p = s;
30 + while (*p && (p = strstr(p, "0x"))) {
31 + p[1] = '0';
32 + p += 2;
33 + while(isxdigit((uint8_t)*p)) *p++ = '0';
34 + }
35 +}
36 +
37 +uint64_t daemon_status_file_hash(DAEMON_STATUS_FILE *ds, const char *msg, const char *cause) {
38 + // IMPORTANT: NO LOCKS OR ALLOCATIONS HERE, THIS FUNCTION IS CALLED FROM SIGNAL HANDLERS
39 + // THIS FUNCTION MUST USE ONLY ASYNC-SIGNAL-SAFE OPERATIONS
40 +
41 + struct {
42 + uint32_t v;
43 + DAEMON_STATUS status;
44 + SIGNAL_CODE signal_code;
45 + ND_PROFILE profile;
46 + EXIT_REASON exit_reason;
47 + RRD_DB_MODE db_mode;
48 + uint32_t worker_job_id;
49 + uint8_t db_tiers;
50 + bool kubernetes;
51 + bool sentry_available;
52 + bool sentry_fatal;
53 + ND_MACHINE_GUID host_id;
54 + ND_UUID machine_id;
55 + long line;
56 + char version[sizeof(ds->version)];
57 + char filename[sizeof(ds->fatal.filename)];
58 + char function[sizeof(ds->fatal.function)];
59 + char stack_trace[sizeof(ds->fatal.stack_trace)];
60 + char thread[sizeof(ds->fatal.thread)];
61 + char msg[128];
62 + char cause[32];
63 + } to_hash;
64 +
65 + // this is important to remove any random bytes from the structure
66 + memset(&to_hash, 0, sizeof(to_hash));
67 +
68 + dsf_acquire(*ds);
69 +
70 + to_hash.v = ds->v,
71 + to_hash.status = ds->status,
72 + to_hash.signal_code = ds->fatal.signal_code,
73 + to_hash.profile = ds->profile,
74 + to_hash.exit_reason = ds->exit_reason,
75 + to_hash.db_mode = ds->db_mode,
76 + to_hash.db_tiers = ds->db_tiers,
77 + to_hash.kubernetes = ds->kubernetes,
78 + to_hash.sentry_available = ds->sentry_available,
79 + to_hash.sentry_fatal = ds->fatal.sentry,
80 + to_hash.host_id = ds->host_id,
81 + to_hash.machine_id = ds->machine_id,
82 + to_hash.worker_job_id = ds->fatal.worker_job_id,
83 +
84 + strncpyz(to_hash.version, ds->version, sizeof(to_hash.version) - 1);
85 + strncpyz(to_hash.filename, ds->fatal.filename, sizeof(to_hash.filename) - 1);
86 + strncpyz(to_hash.filename, ds->fatal.function, sizeof(to_hash.function) - 1);
87 + strncpyz(to_hash.stack_trace, ds->fatal.stack_trace, sizeof(to_hash.stack_trace) - 1);
88 + strncpyz(to_hash.thread, ds->fatal.thread, sizeof(to_hash.thread) - 1);
89 +
90 + if(msg)
91 + strncpyz(to_hash.msg, msg, sizeof(to_hash.msg) - 1);
92 +
93 + if(cause)
94 + strncpyz(to_hash.cause, cause, sizeof(to_hash.cause) - 1);
95 +
96 + stack_trace_anonymize(to_hash.stack_trace);
97 +
98 + uint64_t hash = fnv1a_hash_bin64(&to_hash, sizeof(to_hash));
99 +
100 + dsf_release(*ds);
101 + return hash;
102 +}
103 +
104 +// --------------------------------------------------------------------------------------------------------------------
105 +// read and write the dedup hashes
106 +
107 +static bool status_file_dedup_load_and_parse(const char *filename, void *data __maybe_unused) {
108 + // IMPORTANT: NO LOCKS OR ALLOCATIONS HERE, THIS FUNCTION IS CALLED FROM SIGNAL HANDLERS
109 + // THIS FUNCTION MUST USE ONLY ASYNC-SIGNAL-SAFE OPERATIONS
110 +
111 + int fp = open(filename, O_RDONLY);
112 + if(fp == -1)
113 + goto failed;
114 +
115 + memset(&dedup, 0, sizeof(dedup));
116 + ssize_t r = read(fp, &dedup, sizeof(dedup));
117 + close(fp);
118 +
119 + if(r != sizeof(dedup))
120 + goto failed;
121 +
122 + if(dedup.magic != DEDUP_MAGIC)
123 + goto failed;
124 +
125 + if(dedup.v != DEDUP_VERSION)
126 + goto failed;
127 +
128 + uint64_t hash = fnv1a_hash_bin64(&dedup.slot, sizeof(dedup.slot));
129 + if(dedup.hash != hash)
130 + goto failed;
131 +
132 + return true;
133 +
134 +failed:
135 + memset(&dedup, 0, sizeof(dedup));
136 + return false;
137 +}
138 +
139 +bool daemon_status_dedup_load(void) {
140 + // IMPORTANT: NO LOCKS OR ALLOCATIONS HERE, THIS FUNCTION IS CALLED FROM SIGNAL HANDLERS
141 + // THIS FUNCTION MUST USE ONLY ASYNC-SIGNAL-SAFE OPERATIONS
142 +
143 + return status_file_io_load(DEDUP_FILENAME, status_file_dedup_load_and_parse, NULL);
144 +}
145 +
146 +static bool daemon_status_dedup_save(void) {
147 + // IMPORTANT: NO LOCKS OR ALLOCATIONS HERE, THIS FUNCTION IS CALLED FROM SIGNAL HANDLERS
148 + // THIS FUNCTION MUST USE ONLY ASYNC-SIGNAL-SAFE OPERATIONS
149 +
150 + dedup.magic = DEDUP_MAGIC;
151 + dedup.v = DEDUP_VERSION;
152 + dedup.hash = fnv1a_hash_bin64(&dedup.slot, sizeof(dedup.slot));
153 + return status_file_io_save(DEDUP_FILENAME, &dedup, sizeof(dedup), false);
154 +}
155 +
156 +// --------------------------------------------------------------------------------------------------------------------
157 +// deduplication hashes management
158 +
159 +bool dedup_already_posted(DAEMON_STATUS_FILE *ds __maybe_unused, uint64_t hash, bool sentry) {
160 + // IMPORTANT: NO LOCKS OR ALLOCATIONS HERE, THIS FUNCTION IS CALLED FROM SIGNAL HANDLERS
161 + // THIS FUNCTION MUST USE ONLY ASYNC-SIGNAL-SAFE OPERATIONS
162 +
163 + daemon_status_dedup_load();
164 +
165 + usec_t now_ut = now_realtime_usec();
166 +
167 + for(size_t i = 0; i < _countof(dedup.slot); i++) {
168 + if(dedup.slot[i].timestamp_ut == 0)
169 + continue;
170 +
171 + if(hash == dedup.slot[i].hash &&
172 + sentry == dedup.slot[i].sentry &&
173 + now_ut - dedup.slot[i].timestamp_ut < REPORT_EVENTS_EVERY * USEC_PER_SEC) {
174 + // we have already posted this crash
175 + return true;
176 + }
177 + }
178 +
179 + return false;
180 +}
181 +
182 +void dedup_keep_hash(DAEMON_STATUS_FILE *ds __maybe_unused, uint64_t hash, bool sentry) {
183 + // IMPORTANT: NO LOCKS OR ALLOCATIONS HERE, THIS FUNCTION IS CALLED FROM SIGNAL HANDLERS
184 + // THIS FUNCTION MUST USE ONLY ASYNC-SIGNAL-SAFE OPERATIONS
185 +
186 + daemon_status_dedup_load();
187 +
188 + // find the same hash
189 + for(size_t i = 0; i < _countof(dedup.slot); i++) {
190 + if(dedup.slot[i].hash == hash && dedup.slot[i].sentry == sentry) {
191 + dedup.slot[i].hash = hash;
192 + dedup.slot[i].sentry = sentry;
193 + dedup.slot[i].timestamp_ut = now_realtime_usec();
194 + goto save;
195 + }
196 + }
197 +
198 + // find an empty slot
199 + for(size_t i = 0; i < _countof(dedup.slot); i++) {
200 + if(!dedup.slot[i].hash) {
201 + dedup.slot[i].hash = hash;
202 + dedup.slot[i].sentry = sentry;
203 + dedup.slot[i].timestamp_ut = now_realtime_usec();
204 + goto save;
205 + }
206 + }
207 +
208 + // find the oldest slot
209 + size_t store_at_slot = 0;
210 + for(size_t i = 1; i < _countof(dedup.slot); i++) {
211 + if(dedup.slot[i].timestamp_ut < dedup.slot[store_at_slot].timestamp_ut)
212 + store_at_slot = i;
213 + }
214 +
215 + dedup.slot[store_at_slot].hash = hash;
216 + dedup.slot[store_at_slot].sentry = sentry;
217 + dedup.slot[store_at_slot].timestamp_ut = now_realtime_usec();
218 +
219 +save:
220 + daemon_status_dedup_save();
221 +}
src/daemon/status-file-dedup.h new
+14
@@ -0,0 +1,14 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#ifndef NETDATA_STATUS_FILE_DEDUP_H
4 +#define NETDATA_STATUS_FILE_DEDUP_H
5 +
6 +#include "libnetdata/libnetdata.h"
7 +#include "status-file.h"
8 +
9 +uint64_t daemon_status_file_hash(DAEMON_STATUS_FILE *ds, const char *msg, const char *cause);
10 +
11 +bool dedup_already_posted(DAEMON_STATUS_FILE *ds, uint64_t hash, bool sentry);
12 +void dedup_keep_hash(DAEMON_STATUS_FILE *ds, uint64_t hash, bool sentry);
13 +
14 +#endif //NETDATA_STATUS_FILE_DEDUP_H
src/daemon/status-file-io.c new
+227
@@ -0,0 +1,227 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "common.h"
4 +#include "status-file-io.h"
5 +
6 +// List of fallback directories to try
7 +static const char *status_file_io_fallback_dirs[] = {
8 + CACHE_DIR,
9 + "/tmp",
10 + "/run",
11 + "/var/run",
12 + ".",
13 +};
14 +
15 +static void status_file_io_fallback_dirs_update(void) {
16 + status_file_io_fallback_dirs[0] = netdata_configured_cache_dir;
17 +}
18 +
19 +static bool status_file_io_check(const char *directory, const char *filename, char *dst, size_t dst_size, time_t *mtime) {
20 + // IMPORTANT: NO LOCKS OR ALLOCATIONS HERE, THIS FUNCTION IS CALLED FROM SIGNAL HANDLERS
21 + // THIS FUNCTION MUST USE ONLY ASYNC-SIGNAL-SAFE OPERATIONS
22 +
23 + if(!directory || !*directory || !filename || !*filename || !dst || !dst_size || !mtime)
24 + return false;
25 +
26 + size_t len = 0;
27 + len = strcatz(dst, len, dst_size, directory);
28 + if(!len || dst[len - 1] != '/')
29 + len = strcatz(dst, len, dst_size, "/");
30 + len = strcatz(dst, len, dst_size, filename);
31 +
32 + // Get file metadata
33 + OS_FILE_METADATA metadata = os_get_file_metadata(dst);
34 + if (!OS_FILE_METADATA_OK(metadata)) {
35 + *mtime = 0;
36 + return false;
37 + }
38 +
39 + *mtime = metadata.modified_time;
40 + return true;
41 +}
42 +
43 +static void status_file_io_remove_obsolete(const char *protected_dir, const char *filename) {
44 + // IMPORTANT: NO LOCKS OR ALLOCATIONS HERE, THIS FUNCTION IS CALLED FROM SIGNAL HANDLERS
45 + // THIS FUNCTION MUST USE ONLY ASYNC-SIGNAL-SAFE OPERATIONS
46 +
47 + FUNCTION_RUN_ONCE();
48 +
49 + char dst[FILENAME_MAX];
50 +
51 + status_file_io_fallback_dirs_update();
52 + for(size_t i = 0; i < _countof(status_file_io_fallback_dirs); i++) {
53 + if(strcmp(status_file_io_fallback_dirs[i], protected_dir) == 0)
54 + continue;
55 +
56 + size_t len = 0;
57 + len = strcatz(dst, len, sizeof(dst), status_file_io_fallback_dirs[i]);
58 + if(!len || dst[len - 1] != '/')
59 + len = strcatz(dst, len, sizeof(dst), "/");
60 + len = strcatz(dst, len, sizeof(dst), filename);
61 +
62 + unlink(dst);
63 + }
64 +
65 + errno_clear();
66 +}
67 +
68 +bool status_file_io_load(const char *filename, bool (*cb)(const char *, void *), void *data) {
69 + // IMPORTANT: NO LOCKS OR ALLOCATIONS HERE, THIS FUNCTION IS CALLED FROM SIGNAL HANDLERS
70 + // THIS FUNCTION MUST USE ONLY ASYNC-SIGNAL-SAFE OPERATIONS
71 +
72 + char newest[FILENAME_MAX] = "";
73 + char current[FILENAME_MAX];
74 + time_t newest_mtime = 0, current_mtime;
75 +
76 + // Check the primary directory first
77 + if(status_file_io_check(netdata_configured_varlib_dir, filename, current, sizeof(current), &current_mtime)) {
78 + strncpyz(newest, current, sizeof(newest) - 1);
79 + newest_mtime = current_mtime;
80 + }
81 +
82 + // Check each fallback location
83 + status_file_io_fallback_dirs_update();
84 + for(size_t i = 0; i < _countof(status_file_io_fallback_dirs); i++) {
85 + if(status_file_io_check(status_file_io_fallback_dirs[i], filename, current, sizeof(current), &current_mtime) &&
86 + (!*newest || current_mtime > newest_mtime)) {
87 + strncpyz(newest, current, sizeof(newest) - 1);
88 + newest_mtime = current_mtime;
89 + }
90 + }
91 +
92 + // Load the newest file found
93 + if(*newest && cb(newest, data))
94 + return true;
95 +
96 + nd_log(NDLS_DAEMON, NDLP_ERR, "Cannot find a status file in any location");
97 + return false;
98 +}
99 +
100 +static bool status_file_io_save_this(const char *directory, const char *filename, const uint8_t *data, size_t size) {
101 + // IMPORTANT: NO LOCKS OR ALLOCATIONS HERE, THIS FUNCTION IS CALLED FROM SIGNAL HANDLERS
102 + // THIS FUNCTION MUST USE ONLY ASYNC-SIGNAL-SAFE OPERATIONS
103 +
104 + // Linux: https://man7.org/linux/man-pages/man7/signal-safety.7.html
105 + // memcpy(), strlen(), open(), write(), fsync(), close(), fchmod(), rename(), unlink()
106 +
107 + // MacOS: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/sigaction.2.html#//apple_ref/doc/man/2/sigaction
108 + // open(), write(), fsync(), close(), rename(), unlink()
109 + // does not explicitly mention fchmod, memcpy(), and strlen(), but they are safe
110 +
111 + if(!directory || !*directory)
112 + return false;
113 +
114 + static uint64_t tmp_attempt_counter = 0;
115 +
116 + char final[FILENAME_MAX];
117 + char temp[FILENAME_MAX];
118 + char tid_str[UINT64_MAX_LENGTH];
119 +
120 + print_uint64(tid_str, __atomic_add_fetch(&tmp_attempt_counter, 1, __ATOMIC_RELAXED));
121 + size_t dir_len = strlen(directory);
122 + size_t fil_len = strlen(filename);
123 + size_t tid_len = strlen(tid_str);
124 +
125 + if (dir_len + 1 + fil_len + 1 + tid_len + 1 >= sizeof(final))
126 + return false; // cannot fit the filename
127 +
128 + // create the filename
129 + size_t pos = 0;
130 + memcpy(&final[pos], directory, dir_len); pos += dir_len;
131 + final[pos] = '/'; pos++;
132 + memcpy(&final[pos], filename, fil_len); pos += fil_len;
133 + final[pos] = '\0';
134 +
135 + // create the temp filename
136 + memcpy(temp, final, pos);
137 + temp[pos] = '-'; pos++;
138 + memcpy(&temp[pos], tid_str, tid_len); pos += tid_len;
139 + temp[pos] = '\0';
140 +
141 + // Open file with O_WRONLY, O_CREAT, and O_TRUNC flags
142 + int fd = open(temp, O_WRONLY | O_CREAT | O_TRUNC, 0664);
143 + if (fd == -1)
144 + return false;
145 +
146 + /* Write content to file using write() */
147 + size_t total_written = 0;
148 +
149 + while (total_written < size) {
150 + ssize_t bytes_written = write(fd, data + total_written, size - total_written);
151 +
152 + if (bytes_written <= 0) {
153 + if (errno == EINTR)
154 + continue; /* Retry if interrupted by signal */
155 +
156 + close(fd);
157 + unlink(temp); /* Remove the temp file */
158 + return false;
159 + }
160 +
161 + total_written += bytes_written;
162 + }
163 +
164 + /* Fsync to ensure data is written to disk */
165 + if (fsync(fd) == -1) {
166 + close(fd);
167 + unlink(temp);
168 + return false;
169 + }
170 +
171 + /* Set permissions using chmod() */
172 + if (fchmod(fd, 0664) != 0) {
173 + close(fd);
174 + unlink(temp);
175 + return false;
176 + }
177 +
178 + /* Close file */
179 + if (close(fd) == -1) {
180 + unlink(temp);
181 + return false;
182 + }
183 +
184 + /* Rename temp file to target file */
185 + if (rename(temp, final) != 0) {
186 + unlink(temp);
187 + return false;
188 + }
189 +
190 + return true;
191 +}
192 +
193 +bool status_file_io_save(const char *filename, const void *data, size_t size, bool log) {
194 + // IMPORTANT: NO LOCKS OR ALLOCATIONS HERE, THIS FUNCTION IS CALLED FROM SIGNAL HANDLERS
195 + // THIS FUNCTION MUST USE ONLY ASYNC-SIGNAL-SAFE OPERATIONS
196 +
197 + // wb should have enough space to hold the JSON content, to avoid any allocations
198 +
199 + // Try primary directory first
200 + bool saved = false;
201 + if (status_file_io_save_this(netdata_configured_varlib_dir, filename, data, size)) {
202 + status_file_io_remove_obsolete(netdata_configured_varlib_dir, filename);
203 + saved = true;
204 + }
205 + else {
206 + if(log)
207 + nd_log(NDLS_DAEMON, NDLP_DEBUG, "Failed to save status file in primary directory %s",
208 + netdata_configured_varlib_dir);
209 +
210 + // Try each fallback directory until successful
211 + status_file_io_fallback_dirs_update();
212 + for(size_t i = 0; i < _countof(status_file_io_fallback_dirs); i++) {
213 + if (status_file_io_save_this(status_file_io_fallback_dirs[i], filename, data, size)) {
214 + if(log)
215 + nd_log(NDLS_DAEMON, NDLP_DEBUG, "Saved status file in fallback %s", status_file_io_fallback_dirs[i]);
216 +
217 + saved = true;
218 + break;
219 + }
220 + }
221 + }
222 +
223 + if (!saved && log)
224 + nd_log(NDLS_DAEMON, NDLP_ERR, "Failed to save status file in any location");
225 +
226 + return saved;
227 +}
src/daemon/status-file-io.h new
+12
@@ -0,0 +1,12 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#ifndef NETDATA_STATUS_FILE_IO_H
4 +#define NETDATA_STATUS_FILE_IO_H
5 +
6 +#include "libnetdata/libnetdata.h"
7 +#include "status-file.h"
8 +
9 +bool status_file_io_load(const char *filename, bool (*cb)(const char *, void *), void *data);
10 +bool status_file_io_save(const char *filename, const void *data, size_t size, bool log);
11 +
12 +#endif //NETDATA_STATUS_FILE_IO_H
src/daemon/status-file.c renamed
+32 -432
@@ -1,7 +1,7 @@
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "common.h"
4 -#include "daemon-status-file.h"
4 +#include "status-file.h"
5 #include "buildinfo.h"
6
7 #include <curl/curl.h>
@@ -9,12 +9,13 @@
9 #include <openssl/pem.h>
10 #include <openssl/err.h>
11
12 -#define REPORT_EVENTS_EVERY (86400 - 3600) // -1 hour to tolerate cron randomness
13 -
12 #ifdef ENABLE_SENTRY
13 #include "sentry-native/sentry-native.h"
14 #endif
15
16 +#include "status-file-dedup.h"
17 +#include "status-file-io.h"
18 +
19 #define STATUS_FILENAME "status-netdata.json"
20
21 ENUM_STR_MAP_DEFINE(DAEMON_STATUS) = {
@@ -59,10 +60,6 @@ static DAEMON_STATUS_FILE session_status = {
60
61 static void daemon_status_file_out_of_memory(void);
62
62 -// these are used instead of locks when locks cannot be used (signal handler, out of memory, etc)
63 -#define dsf_acquire(ds) __atomic_load_n(&(ds).v, __ATOMIC_ACQUIRE)
64 -#define dsf_release(ds) __atomic_store_n(&(ds).v, (ds).v, __ATOMIC_RELEASE)
65 -
63 static void copy_and_clean_thread_name_if_empty(DAEMON_STATUS_FILE *ds, const char *name) {
64 if(ds->fatal.thread[0] && strcmp(ds->fatal.thread, "NO_NAME") != 0)
65 return;
@@ -90,82 +87,6 @@ static void set_stack_trace_message_if_empty(DAEMON_STATUS_FILE *ds, const char
87 // --------------------------------------------------------------------------------------------------------------------
88 // json generation
89
93 -static void stack_trace_anonymize(char *s) {
94 - char *p = s;
95 - while (*p && (p = strstr(p, "0x"))) {
96 - p[1] = '0';
97 - p += 2;
98 - while(isxdigit((uint8_t)*p)) *p++ = '0';
99 - }
100 -}
101 -
102 -static uint64_t daemon_status_file_hash(DAEMON_STATUS_FILE *ds, const char *msg, const char *cause) {
103 - // IMPORTANT: NO LOCKS OR ALLOCATIONS HERE, THIS FUNCTION IS CALLED FROM SIGNAL HANDLERS
104 - // THIS FUNCTION MUST USE ONLY ASYNC-SIGNAL-SAFE OPERATIONS
105 -
106 - struct {
107 - uint32_t v;
108 - DAEMON_STATUS status;
109 - SIGNAL_CODE signal_code;
110 - ND_PROFILE profile;
111 - EXIT_REASON exit_reason;
112 - RRD_DB_MODE db_mode;
113 - uint32_t worker_job_id;
114 - uint8_t db_tiers;
115 - bool kubernetes;
116 - bool sentry_available;
117 - bool sentry_fatal;
118 - ND_UUID host_id;
119 - ND_UUID machine_id;
120 - long line;
121 - char version[sizeof(ds->version)];
122 - char filename[sizeof(ds->fatal.filename)];
123 - char function[sizeof(ds->fatal.function)];
124 - char stack_trace[sizeof(ds->fatal.stack_trace)];
125 - char thread[sizeof(ds->fatal.thread)];
126 - char msg[128];
127 - char cause[32];
128 - } to_hash;
129 -
130 - // this is important to remove any random bytes from the structure
131 - memset(&to_hash, 0, sizeof(to_hash));
132 -
133 - dsf_acquire(*ds);
134 -
135 - to_hash.v = ds->v,
136 - to_hash.status = ds->status,
137 - to_hash.signal_code = ds->fatal.signal_code,
138 - to_hash.profile = ds->profile,
139 - to_hash.exit_reason = ds->exit_reason,
140 - to_hash.db_mode = ds->db_mode,
141 - to_hash.db_tiers = ds->db_tiers,
142 - to_hash.kubernetes = ds->kubernetes,
143 - to_hash.sentry_available = ds->sentry_available,
144 - to_hash.sentry_fatal = ds->fatal.sentry,
145 - to_hash.host_id = ds->host_id,
146 - to_hash.machine_id = ds->machine_id,
147 - to_hash.worker_job_id = ds->fatal.worker_job_id,
148 -
149 - strncpyz(to_hash.version, ds->version, sizeof(to_hash.version) - 1);
150 - strncpyz(to_hash.filename, ds->fatal.filename, sizeof(to_hash.filename) - 1);
151 - strncpyz(to_hash.filename, ds->fatal.function, sizeof(to_hash.function) - 1);
152 - strncpyz(to_hash.stack_trace, ds->fatal.stack_trace, sizeof(to_hash.stack_trace) - 1);
153 - strncpyz(to_hash.thread, ds->fatal.thread, sizeof(to_hash.thread) - 1);
154 -
155 - if(msg)
156 - strncpyz(to_hash.msg, msg, sizeof(to_hash.msg) - 1);
157 -
158 - if(cause)
159 - strncpyz(to_hash.cause, cause, sizeof(to_hash.cause) - 1);
160 -
161 - stack_trace_anonymize(to_hash.stack_trace);
162 -
163 - uint64_t hash = fnv1a_hash_bin64(&to_hash, sizeof(to_hash));
164 -
165 - dsf_release(*ds);
166 - return hash;
167 -}
168 -
90 static void daemon_status_file_to_json(BUFFER *wb, DAEMON_STATUS_FILE *ds) {
91 // IMPORTANT: NO LOCKS OR ALLOCATIONS HERE, THIS FUNCTION IS CALLED FROM SIGNAL HANDLERS
92 // THIS FUNCTION MUST USE ONLY ASYNC-SIGNAL-SAFE OPERATIONS
@@ -177,7 +98,11 @@ static void daemon_status_file_to_json(BUFFER *wb, DAEMON_STATUS_FILE *ds) {
98
99 buffer_json_member_add_object(wb, "agent");
100 {
180 - buffer_json_member_add_uuid(wb, "id", ds->host_id.uuid);
101 + buffer_json_member_add_uuid(wb, "id", ds->host_id.uuid.uuid);
102 +
103 + if(ds->v >= 24 && ds->host_id.last_modified_ut)
104 + buffer_json_member_add_datetime_rfc3339(wb, "since", ds->host_id.last_modified_ut, true);
105 +
106 buffer_json_member_add_uuid_compact(wb, "ephemeral_id", ds->invocation.uuid);
107 buffer_json_member_add_string(wb, "version", ds->version);
108
@@ -312,23 +237,6 @@ static void daemon_status_file_to_json(BUFFER *wb, DAEMON_STATUS_FILE *ds) {
237 }
238 buffer_json_object_close(wb);
239
315 - buffer_json_member_add_array(wb, "dedup");
316 - {
317 - for(size_t i = 0; i < _countof(ds->dedup.slot); i++) {
318 - if (ds->dedup.slot[i].timestamp_ut == 0)
319 - continue;
320 -
321 - buffer_json_add_array_item_object(wb);
322 - {
323 - buffer_json_member_add_datetime_rfc3339(wb, "@timestamp", ds->dedup.slot[i].timestamp_ut, true);
324 - buffer_json_member_add_uint64(wb, "hash", ds->dedup.slot[i].hash);
325 - buffer_json_member_add_boolean(wb, "sentry", ds->dedup.slot[i].sentry);
326 - }
327 - buffer_json_object_close(wb);
328 - }
329 - }
330 - buffer_json_array_close(wb);
331 -
240 dsf_release(*ds);
241 }
242
@@ -339,7 +247,6 @@ static bool daemon_status_file_from_json(json_object *jobj, void *data, BUFFER *
247 char path[1024]; path[0] = '\0';
248
249 DAEMON_STATUS_FILE *ds = data;
342 - char datetime[RFC3339_MAX_LENGTH]; datetime[0] = '\0';
250
251 // change management, version to know which fields to expect
252 uint64_t version = 0;
@@ -360,11 +267,10 @@ static bool daemon_status_file_from_json(json_object *jobj, void *data, BUFFER *
267 bool required_v21 = version >= 21 ? strict : false;
268 bool required_v22 = version >= 22 ? strict : false;
269 bool required_v23 = version >= 23 ? strict : false;
270 + bool required_v24 = version >= 24 ? strict : false;
271
272 // Parse timestamp
365 - JSONC_PARSE_TXT2CHAR_OR_ERROR_AND_RETURN(jobj, path, "@timestamp", datetime, error, required_v1);
366 - if(datetime[0])
367 - ds->timestamp_ut = rfc3339_parse_ut(datetime, NULL);
273 + JSONC_PARSE_TXT2RFC3339_USEC_OR_ERROR_AND_RETURN(jobj, path, "@timestamp", ds->timestamp_ut, error, required_v1);
274
275 const char *profile_key = version >= 18 ? "profile" : "ND_profile";
276 const char *status_key = version >= 18 ? "status" : "ND_status";
@@ -381,7 +287,11 @@ static bool daemon_status_file_from_json(json_object *jobj, void *data, BUFFER *
287
288 // Parse agent object
289 JSONC_PARSE_SUBOBJECT(jobj, path, "agent", error, required_v1, {
384 - JSONC_PARSE_TXT2UUID_OR_ERROR_AND_RETURN(jobj, path, "id", ds->host_id.uuid, error, required_v1);
290 + JSONC_PARSE_TXT2UUID_OR_ERROR_AND_RETURN(jobj, path, "id", ds->host_id.uuid.uuid, error, required_v1);
291 +
292 + if(version >= 24)
293 + JSONC_PARSE_TXT2RFC3339_USEC_OR_ERROR_AND_RETURN(jobj, path, "since", ds->host_id.last_modified_ut, error, required_v24);
294 +
295 JSONC_PARSE_TXT2UUID_OR_ERROR_AND_RETURN(jobj, path, "ephemeral_id", ds->invocation.uuid, error, required_v1);
296 JSONC_PARSE_TXT2CHAR_OR_ERROR_AND_RETURN(jobj, path, "version", ds->version, error, required_v1);
297 JSONC_PARSE_UINT64_OR_ERROR_AND_RETURN(jobj, path, "uptime", ds->uptime, error, required_v1);
@@ -506,38 +416,9 @@ static bool daemon_status_file_from_json(json_object *jobj, void *data, BUFFER *
416 }
417
418 if(version >= 23)
509 - JSONC_PARSE_TXT2ENUM_OR_ERROR_AND_RETURN(jobj, path, "worker_job_id", SIGNAL_CODE_2id_h, ds->fatal.worker_job_id, error, required_v23);
419 + JSONC_PARSE_UINT64_OR_ERROR_AND_RETURN(jobj, path, "worker_job_id", ds->fatal.worker_job_id, error, required_v23);
420 });
421
512 - // Parse the last posted object
513 - if(version == 3) {
514 - JSONC_PARSE_SUBOBJECT(jobj, path, "dedup", error, required_v3, {
515 - datetime[0] = '\0';
516 - JSONC_PARSE_TXT2CHAR_OR_ERROR_AND_RETURN(jobj, path, "@timestamp", datetime, error, required_v3);
517 - if (datetime[0])
518 - ds->dedup.slot[0].timestamp_ut = rfc3339_parse_ut(datetime, NULL);
519 -
520 - JSONC_PARSE_UINT64_OR_ERROR_AND_RETURN(jobj, path, "hash", ds->dedup.slot[0].hash, error, required_v3);
521 - JSONC_PARSE_UINT64_OR_ERROR_AND_RETURN(jobj, path, "restarts", ds->restarts, error, required_v3);
522 - });
523 - }
524 - else if(version >= 4) {
525 - JSONC_PARSE_ARRAY(jobj, path, "dedup", error, required_v4, {
526 - size_t i = 0;
527 - JSONC_PARSE_ARRAY_ITEM_OBJECT(jobj, path, i, required_v4, {
528 - if(i < _countof(ds->dedup.slot)) {
529 - datetime[0] = '\0';
530 - JSONC_PARSE_TXT2CHAR_OR_ERROR_AND_RETURN(jobj, path, "@timestamp", datetime, error, required_v4);
531 - if (datetime[0])
532 - ds->dedup.slot[i].timestamp_ut = rfc3339_parse_ut(datetime, NULL);
533 -
534 - JSONC_PARSE_UINT64_OR_ERROR_AND_RETURN(jobj, path, "hash", ds->dedup.slot[i].hash, error, required_v4);
535 - JSONC_PARSE_BOOL_OR_ERROR_AND_RETURN(jobj, path, "sentry", ds->dedup.slot[i].sentry, error, required_v17);
536 - }
537 - });
538 - });
539 - }
540 -
422 return true;
423 }
424
@@ -581,11 +462,11 @@ static void daemon_status_file_migrate_once(void) {
462 session_status.claim_id = last_session_status.claim_id;
463 session_status.node_id = last_session_status.node_id;
464 session_status.host_id = last_session_status.host_id;
584 - if(UUIDiszero(session_status.host_id)) {
585 - if(!UUIDiszero(last_session_status.host_id))
465 + if(UUIDiszero(session_status.host_id.uuid)) {
466 + if(!UUIDiszero(last_session_status.host_id.uuid))
467 session_status.host_id = last_session_status.host_id;
468 else
588 - session_status.host_id = machine_guid_get()->uuid;
469 + session_status.host_id = *machine_guid_get();
470 }
471
472 strncpyz(session_status.architecture, last_session_status.architecture, sizeof(session_status.architecture) - 1);
@@ -614,11 +495,6 @@ static void daemon_status_file_migrate_once(void) {
495 session_status.reliability++;
496 }
497
617 - if(last_session_status.v == STATUS_FILE_VERSION) {
618 - for (size_t i = 0; i < _countof(session_status.dedup.slot); i++)
619 - session_status.dedup.slot[i] = last_session_status.dedup.slot[i];
620 - }
621 -
498 strncpyz(session_status.stack_traces, capture_stack_trace_backend(), sizeof(session_status.stack_traces) - 1);
499
500 dsf_release(last_session_status);
@@ -653,7 +529,7 @@ static void daemon_status_file_refresh(DAEMON_STATUS status) {
529 if(session_status.status == DAEMON_STATUS_EXITING)
530 session_status.timings.exit = (time_t)((now_ut - session_status.timings.exit_started_ut + USEC_PER_SEC/2) / USEC_PER_SEC);
531
656 - session_status.host_id = machine_guid_get()->uuid;
532 + session_status.host_id = *machine_guid_get();
533 session_status.boottime = now_boottime_sec();
534 session_status.uptime = now_realtime_sec() - netdata_start_time;
535 session_status.timestamp_ut = now_ut;
@@ -669,7 +545,7 @@ static void daemon_status_file_refresh(DAEMON_STATUS status) {
545
546 if(localhost) {
547 if(!UUIDiszero(localhost->host_id))
672 - session_status.host_id = localhost->host_id;
548 + session_status.host_id.uuid = localhost->host_id;
549
550 if(!UUIDiszero(localhost->node_id))
551 session_status.node_id = localhost->node_id;
@@ -693,196 +569,25 @@ static void daemon_status_file_refresh(DAEMON_STATUS status) {
569 dsf_release(session_status);
570 }
571
696 -// --------------------------------------------------------------------------------------------------------------------
697 -// file helpers
698 -
699 -// List of fallback directories to try
700 -static const char *status_file_fallbacks[] = {
701 - CACHE_DIR,
702 - "/tmp",
703 - "/run",
704 - "/var/run",
705 - ".",
706 -};
707 -
708 -static void set_dynamic_fallbacks(void) {
709 - status_file_fallbacks[0] = netdata_configured_cache_dir;
710 -}
711 -
712 -static bool check_status_file(const char *directory, char *filename, size_t filename_size, time_t *mtime) {
713 - if(!directory || !*directory)
714 - return false;
715 -
716 - snprintfz(filename, filename_size, "%s/%s", directory, STATUS_FILENAME);
717 -
718 - // Get file metadata
719 - OS_FILE_METADATA metadata = os_get_file_metadata(filename);
720 - if (!OS_FILE_METADATA_OK(metadata)) {
721 - *mtime = 0;
722 - return false;
723 - }
724 -
725 - *mtime = metadata.modified_time;
726 - return true;
727 -}
728 -
572 // --------------------------------------------------------------------------------------------------------------------
573 // load a saved status
574
732 -static bool load_status_file(const char *filename, DAEMON_STATUS_FILE *status) {
733 - FILE *fp = fopen(filename, "r");
734 - if (!fp)
735 - return false;
575 +static bool status_file_load_and_parse(const char *filename, void *data) {
576 + DAEMON_STATUS_FILE *status = data;
577
578 CLEAN_BUFFER *wb = buffer_create(0, NULL);
579 CLEAN_BUFFER *error = buffer_create(0, NULL);
580
740 - // Get file size
741 - fseek(fp, 0, SEEK_END);
742 - long file_size = ftell(fp);
743 - fseek(fp, 0, SEEK_SET);
744 -
745 - // Read the file
746 - buffer_need_bytes(wb, file_size + 1);
747 - ssize_t read_bytes = fread(wb->buffer, 1, file_size, fp);
748 - fclose(fp);
749 -
750 - if (read_bytes == 0)
581 + if(!read_txt_file_to_buffer(filename, wb, 65536))
582 return false;
583
753 - wb->buffer[read_bytes] = '\0';
754 - wb->len = read_bytes;
755 -
584 // Parse the JSON
585 return json_parse_payload_or_error(wb, error, daemon_status_file_from_json, status) == HTTP_RESP_OK;
586 }
587
760 -void daemon_status_file_load(DAEMON_STATUS_FILE *ds) {
761 - char newest_filename[FILENAME_MAX] = "";
762 - char current_filename[FILENAME_MAX];
763 - time_t newest_mtime = 0, current_mtime;
764 -
765 - // Check the primary directory first
766 - if(check_status_file(netdata_configured_varlib_dir, current_filename, sizeof(current_filename), &current_mtime)) {
767 - strncpyz(newest_filename, current_filename, sizeof(newest_filename) - 1);
768 - newest_mtime = current_mtime;
769 - }
770 -
771 - // Check each fallback location
772 - set_dynamic_fallbacks();
773 - for(size_t i = 0; i < _countof(status_file_fallbacks); i++) {
774 - if(check_status_file(status_file_fallbacks[i], current_filename, sizeof(current_filename), &current_mtime) &&
775 - (!*newest_filename || current_mtime > newest_mtime)) {
776 - strncpyz(newest_filename, current_filename, sizeof(newest_filename) - 1);
777 - newest_mtime = current_mtime;
778 - }
779 - }
780 -
781 - // Load the newest file found
782 - if(*newest_filename) {
783 - if(!load_status_file(newest_filename, ds))
784 - nd_log(NDLS_DAEMON, NDLP_ERR, "Failed to load newest status file: %s", newest_filename);
785 - }
786 - else
787 - nd_log(NDLS_DAEMON, NDLP_ERR, "Cannot find a status file in any location");
788 -}
789 -
588 // --------------------------------------------------------------------------------------------------------------------
589 // save the current status
590
793 -static bool save_status_file(const char *directory, const char *content, size_t content_size) {
794 - // IMPORTANT: NO LOCKS OR ALLOCATIONS HERE, THIS FUNCTION IS CALLED FROM SIGNAL HANDLERS
795 - // THIS FUNCTION MUST USE ONLY ASYNC-SIGNAL-SAFE OPERATIONS
796 -
797 - // Linux: https://man7.org/linux/man-pages/man7/signal-safety.7.html
798 - // memcpy(), strlen(), open(), write(), fsync(), close(), fchmod(), rename(), unlink()
799 -
800 - // MacOS: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/sigaction.2.html#//apple_ref/doc/man/2/sigaction
801 - // open(), write(), fsync(), close(), rename(), unlink()
802 - // does not explicitly mention fchmod, memcpy(), and strlen(), but they are safe
803 -
804 - if(!directory || !*directory)
805 - return false;
806 -
807 - static uint64_t tmp_attempt_counter = 0;
808 -
809 - char filename[FILENAME_MAX];
810 - char temp_filename[FILENAME_MAX];
811 - char tid_str[UINT64_MAX_LENGTH];
812 -
813 - print_uint64(tid_str, __atomic_add_fetch(&tmp_attempt_counter, 1, __ATOMIC_RELAXED));
814 - size_t dir_len = strlen(directory);
815 - size_t fil_len = strlen(STATUS_FILENAME);
816 - size_t tid_len = strlen(tid_str);
817 -
818 - if (dir_len + 1 + fil_len + 1 + tid_len + 1 >= sizeof(filename))
819 - return false; // cannot fit the filename
820 -
821 - // create the filename
822 - size_t pos = 0;
823 - memcpy(&filename[pos], directory, dir_len); pos += dir_len;
824 - filename[pos] = '/'; pos++;
825 - memcpy(&filename[pos], STATUS_FILENAME, fil_len); pos += fil_len;
826 - filename[pos] = '\0';
827 -
828 - // create the temp filename
829 - memcpy(temp_filename, filename, pos);
830 - temp_filename[pos] = '-'; pos++;
831 - memcpy(&temp_filename[pos], tid_str, tid_len); pos += tid_len;
832 - temp_filename[pos] = '\0';
833 -
834 - // Open file with O_WRONLY, O_CREAT, and O_TRUNC flags
835 - int fd = open(temp_filename, O_WRONLY | O_CREAT | O_TRUNC, 0664);
836 - if (fd == -1)
837 - return false;
838 -
839 - /* Write content to file using write() */
840 - size_t total_written = 0;
841 -
842 - while (total_written < content_size) {
843 - ssize_t bytes_written = write(fd, content + total_written, content_size - total_written);
844 -
845 - if (bytes_written <= 0) {
846 - if (errno == EINTR)
847 - continue; /* Retry if interrupted by signal */
848 -
849 - close(fd);
850 - unlink(temp_filename); /* Remove the temp file */
851 - return false;
852 - }
853 -
854 - total_written += bytes_written;
855 - }
856 -
857 - /* Fsync to ensure data is written to disk */
858 - if (fsync(fd) == -1) {
859 - close(fd);
860 - unlink(temp_filename);
861 - return false;
862 - }
863 -
864 - /* Set permissions using chmod() */
865 - if (fchmod(fd, 0664) != 0) {
866 - close(fd);
867 - unlink(temp_filename);
868 - return false;
869 - }
870 -
871 - /* Close file */
872 - if (close(fd) == -1) {
873 - unlink(temp_filename);
874 - return false;
875 - }
876 -
877 - /* Rename temp file to target file */
878 - if (rename(temp_filename, filename) != 0) {
879 - unlink(temp_filename);
880 - return false;
881 - }
882 -
883 - return true;
884 -}
885 -
591 static BUFFER *static_save_buffer = NULL;
592 static void static_save_buffer_init(void) {
593 if (!static_save_buffer)
@@ -891,23 +596,6 @@ static void static_save_buffer_init(void) {
596 buffer_flush(static_save_buffer);
597 }
598
894 -static void remove_old_status_files(const char *protected_dir) {
895 - FUNCTION_RUN_ONCE();
896 -
897 - char filename[FILENAME_MAX];
898 -
899 - set_dynamic_fallbacks();
900 - for(size_t i = 0; i < _countof(status_file_fallbacks); i++) {
901 - if(strcmp(status_file_fallbacks[i], protected_dir) == 0)
902 - continue;
903 -
904 - snprintfz(filename, sizeof(filename), "%s/%s", status_file_fallbacks[i], STATUS_FILENAME);
905 - unlink(filename);
906 - }
907 -
908 - errno_clear();
909 -}
910 -
599 static bool daemon_status_file_saved = false;
600 static void daemon_status_file_save(BUFFER *wb, DAEMON_STATUS_FILE *ds, bool log) {
601 // IMPORTANT: NO LOCKS OR ALLOCATIONS HERE, THIS FUNCTION IS CALLED FROM SIGNAL HANDLERS
@@ -920,100 +608,10 @@ static void daemon_status_file_save(BUFFER *wb, DAEMON_STATUS_FILE *ds, bool log
608 daemon_status_file_to_json(wb, ds);
609 buffer_json_finalize(wb);
610
923 - const char *content = buffer_tostring(wb);
924 - size_t content_size = buffer_strlen(wb);
925 -
926 - // Try primary directory first
927 - bool saved = false;
928 - if (save_status_file(netdata_configured_varlib_dir, content, content_size)) {
929 - remove_old_status_files(netdata_configured_varlib_dir);
930 - saved = true;
931 - }
932 - else {
933 - if(log)
934 - nd_log(NDLS_DAEMON, NDLP_DEBUG, "Failed to save status file in primary directory %s",
935 - netdata_configured_varlib_dir);
936 -
937 - // Try each fallback directory until successful
938 - set_dynamic_fallbacks();
939 - for(size_t i = 0; i < _countof(status_file_fallbacks); i++) {
940 - if (save_status_file(status_file_fallbacks[i], content, content_size)) {
941 - if(log)
942 - nd_log(NDLS_DAEMON, NDLP_DEBUG, "Saved status file in fallback %s", status_file_fallbacks[i]);
943 -
944 - saved = true;
945 - break;
946 - }
947 - }
948 - }
949 -
950 - if (!saved && log)
951 - nd_log(NDLS_DAEMON, NDLP_ERR, "Failed to save status file in any location");
952 -
953 - if (saved)
611 + if(status_file_io_save(STATUS_FILENAME, buffer_tostring(wb), buffer_strlen(wb), log))
612 daemon_status_file_saved = true;
613 }
614
957 -// --------------------------------------------------------------------------------------------------------------------
958 -// deduplication hashes management
959 -
960 -static bool dedup_already_posted(DAEMON_STATUS_FILE *ds, uint64_t hash, bool sentry) {
961 - // IMPORTANT: NO LOCKS OR ALLOCATIONS HERE, THIS FUNCTION IS CALLED FROM SIGNAL HANDLERS
962 - // THIS FUNCTION MUST USE ONLY ASYNC-SIGNAL-SAFE OPERATIONS
963 -
964 - usec_t now_ut = now_realtime_usec();
965 -
966 - for(size_t i = 0; i < _countof(ds->dedup.slot); i++) {
967 - if(ds->dedup.slot[i].timestamp_ut == 0)
968 - continue;
969 -
970 - if(hash == ds->dedup.slot[i].hash &&
971 - sentry == ds->dedup.slot[i].sentry &&
972 - now_ut - ds->dedup.slot[i].timestamp_ut < REPORT_EVENTS_EVERY * USEC_PER_SEC) {
973 - // we have already posted this crash
974 - return true;
975 - }
976 - }
977 -
978 - return false;
979 -}
980 -
981 -static void dedup_keep_hash(DAEMON_STATUS_FILE *ds, uint64_t hash, bool sentry) {
982 - // IMPORTANT: NO LOCKS OR ALLOCATIONS HERE, THIS FUNCTION IS CALLED FROM SIGNAL HANDLERS
983 - // THIS FUNCTION MUST USE ONLY ASYNC-SIGNAL-SAFE OPERATIONS
984 -
985 - // find the same hash
986 - for(size_t i = 0; i < _countof(ds->dedup.slot); i++) {
987 - if(ds->dedup.slot[i].hash == hash && ds->dedup.slot[i].sentry == sentry) {
988 - ds->dedup.slot[i].hash = hash;
989 - ds->dedup.slot[i].sentry = sentry;
990 - ds->dedup.slot[i].timestamp_ut = now_realtime_usec();
991 - return;
992 - }
993 - }
994 -
995 - // find an empty slot
996 - for(size_t i = 0; i < _countof(ds->dedup.slot); i++) {
997 - if(!ds->dedup.slot[i].hash) {
998 - ds->dedup.slot[i].hash = hash;
999 - ds->dedup.slot[i].sentry = sentry;
1000 - ds->dedup.slot[i].timestamp_ut = now_realtime_usec();
1001 - return;
1002 - }
1003 - }
1004 -
1005 - // find the oldest slot
1006 - size_t store_at_slot = 0;
1007 - for(size_t i = 1; i < _countof(ds->dedup.slot); i++) {
1008 - if(ds->dedup.slot[i].timestamp_ut < ds->dedup.slot[store_at_slot].timestamp_ut)
1009 - store_at_slot = i;
1010 - }
1011 -
1012 - ds->dedup.slot[store_at_slot].hash = hash;
1013 - ds->dedup.slot[store_at_slot].sentry = sentry;
1014 - ds->dedup.slot[store_at_slot].timestamp_ut = now_realtime_usec();
1015 -}
1016 -
615 // --------------------------------------------------------------------------------------------------------------------
616 // POST the last status to agent-events
617
@@ -1085,10 +683,10 @@ static void post_status_file(struct post_status_file_thread_data *d) {
683 CURLcode rc = curl_easy_perform(curl);
684 if(rc == CURLE_OK) {
685 daemon_status_file_startup_step("startup(crash reports dedup)");
686 + session_status.posts++;
687 nd_log(NDLS_DAEMON, NDLP_INFO, "Posted last status to agent-events successfully.");
688 uint64_t hash = daemon_status_file_hash(d->status, d->msg, d->cause);
689 dedup_keep_hash(&session_status, hash, false);
1091 - session_status.posts++;
690 daemon_status_file_save(wb, &session_status, true);
691 }
692 else
@@ -1170,7 +768,7 @@ static enum crash_report_t check_crash_reports_config(void) {
768 void daemon_status_file_init(void) {
769 static_save_buffer_init();
770 mallocz_register_out_of_memory_cb(daemon_status_file_out_of_memory);
1173 - daemon_status_file_load(&last_session_status);
771 + status_file_io_load(STATUS_FILENAME, status_file_load_and_parse, &last_session_status);
772 daemon_status_file_migrate_once();
773 }
774
@@ -1449,6 +1047,8 @@ static void daemon_status_file_save_twice_if_we_can_get_stack_trace(BUFFER *wb,
1047
1048 daemon_status_file_save(wb, ds, false);
1049 }
1050 +
1051 + errno_clear();
1052 }
1053
1054 // --------------------------------------------------------------------------------------------------------------------
@@ -1758,8 +1358,8 @@ ssize_t daemon_status_file_get_reliability(void) {
1358 return session_status.reliability;
1359 }
1360
1761 -ND_UUID daemon_status_file_get_host_id(void) {
1762 - if(!UUIDiszero(session_status.host_id))
1361 +ND_MACHINE_GUID daemon_status_file_get_host_id(void) {
1362 + if(!UUIDiszero(session_status.host_id.uuid))
1363 return session_status.host_id;
1364 else
1365 return last_session_status.host_id;
src/daemon/status-file.h renamed
+12 -14
@@ -1,14 +1,15 @@
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 -#ifndef NETDATA_DAEMON_STATUS_FILE_H
4 -#define NETDATA_DAEMON_STATUS_FILE_H
3 +#ifndef NETDATA_STATUS_FILE_H
4 +#define NETDATA_STATUS_FILE_H
5
6 #include "libnetdata/libnetdata.h"
7 #include "daemon/config/netdata-conf-profile.h"
8 #include "database/rrd-database-mode.h"
9 #include "claim/cloud-status.h"
10 +#include "machine-guid.h"
11
11 -#define STATUS_FILE_VERSION 23
12 +#define STATUS_FILE_VERSION 24
13
14 typedef enum {
15 DAEMON_STATUS_NONE,
@@ -50,9 +51,10 @@ typedef struct daemon_status_file {
51 size_t posts; // the number of posts to the backend
52 ssize_t reliability; // consecutive restarts: > 0 reliable, < 0 crashing
53
54 + ND_MACHINE_GUID host_id; // the machine guid of the system
55 +
56 ND_UUID boot_id; // the boot id of the system
57 ND_UUID invocation; // the netdata invocation id generated the file
55 - ND_UUID host_id; // the machine guid of the agent
58 ND_UUID node_id; // the Netdata Cloud node id of the agent
59 ND_UUID claim_id; // the Netdata Cloud claim id of the agent
60 ND_UUID machine_id; // the unique machine id of the system
@@ -101,16 +103,12 @@ typedef struct daemon_status_file {
103 uint32_t worker_job_id;
104 bool sentry; // true when the error was also reported to sentry
105 } fatal;
104 -
105 - struct {
106 - struct {
107 - bool sentry;
108 - uint64_t hash;
109 - usec_t timestamp_ut;
110 - } slot[15];
111 - } dedup;
106 } DAEMON_STATUS_FILE;
107
108 +// these are used instead of locks when locks cannot be used (signal handler, out of memory, etc)
109 +#define dsf_acquire(ds) __atomic_load_n(&(ds).v, __ATOMIC_ACQUIRE)
110 +#define dsf_release(ds) __atomic_store_n(&(ds).v, (ds).v, __ATOMIC_RELEASE)
111 +
112 // saves the current status
113 void daemon_status_file_update_status(DAEMON_STATUS status);
114
@@ -155,7 +153,7 @@ long daemon_status_file_get_fatal_line(void);
153 DAEMON_STATUS daemon_status_file_get_status(void);
154 size_t daemon_status_file_get_restarts(void);
155 ssize_t daemon_status_file_get_reliability(void);
158 -ND_UUID daemon_status_file_get_host_id(void);
156 +ND_MACHINE_GUID daemon_status_file_get_host_id(void);
157 size_t daemon_status_file_get_fatal_worker_job_id(void);
158
161 -#endif //NETDATA_DAEMON_STATUS_FILE_H
159 +#endif //NETDATA_STATUS_FILE_H
src/database/rrddim.c
+12
@@ -417,6 +417,9 @@ inline int rrddim_set_divisor(RRDSET *st, RRDDIM *rd, int32_t divisor) {
417 // ----------------------------------------------------------------------------
418
419 time_t rrddim_last_entry_s_of_tier(RRDDIM *rd, size_t tier) {
420 + if(unlikely(!rd))
421 + return 0;
422 +
423 if(unlikely(tier > nd_profile.storage_tiers || !rd->tiers[tier].smh))
424 return 0;
425
@@ -425,6 +428,9 @@ time_t rrddim_last_entry_s_of_tier(RRDDIM *rd, size_t tier) {
428
429 // get the timestamp of the last entry in the round-robin database
430 time_t rrddim_last_entry_s(RRDDIM *rd) {
431 + if(unlikely(!rd))
432 + return 0;
433 +
434 time_t latest_time_s = rrddim_last_entry_s_of_tier(rd, 0);
435
436 for(size_t tier = 1; tier < nd_profile.storage_tiers;tier++) {
@@ -439,6 +445,9 @@ time_t rrddim_last_entry_s(RRDDIM *rd) {
445 }
446
447 time_t rrddim_first_entry_s_of_tier(RRDDIM *rd, size_t tier) {
448 + if(unlikely(!rd))
449 + return 0;
450 +
451 if(unlikely(tier > nd_profile.storage_tiers || !rd->tiers[tier].smh))
452 return 0;
453
@@ -446,6 +455,9 @@ time_t rrddim_first_entry_s_of_tier(RRDDIM *rd, size_t tier) {
455 }
456
457 time_t rrddim_first_entry_s(RRDDIM *rd) {
458 + if(unlikely(!rd))
459 + return 0;
460 +
461 time_t oldest_time_s = 0;
462
463 for(size_t tier = 0; tier < nd_profile.storage_tiers;tier++) {
src/database/rrdhost-system-info.h
+1 -1
@@ -5,7 +5,7 @@
5
6 #include "libnetdata/libnetdata.h"
7 #include "rrdlabels.h"
8 -#include "daemon/daemon-status-file.h"
8 +#include "daemon/status-file.h"
9
10 #ifdef RRDHOST_SYSTEM_INFO_INTERNALS
11 struct rrdhost_system_info {
src/database/sqlite/sqlite_metadata.c
+4 -1
@@ -968,6 +968,9 @@ done:
968
969 static void delete_dimension_uuid(nd_uuid_t *dimension_uuid, sqlite3_stmt **action_res __maybe_unused, bool flag __maybe_unused)
970 {
971 + if(!dimension_uuid)
972 + return;
973 +
974 sqlite3_stmt *res = NULL;
975 int rc;
976
@@ -1153,7 +1156,7 @@ done:
1156 static bool dimension_can_be_deleted(nd_uuid_t *dim_uuid __maybe_unused, sqlite3_stmt **res __maybe_unused, bool flag __maybe_unused)
1157 {
1158 #ifdef ENABLE_DBENGINE
1156 - if(dbengine_enabled) {
1159 + if(dbengine_enabled && dim_uuid) {
1160 bool no_retention = true;
1161 for (size_t tier = 0; tier < nd_profile.storage_tiers; tier++) {
1162 if (!multidb_ctx[tier])
src/libnetdata/json/json-c-parser-inline.h
+16
@@ -39,6 +39,22 @@
39 } \
40 } while(0)
41
42 +#define JSONC_PARSE_TXT2RFC3339_USEC_OR_ERROR_AND_RETURN(jobj, path, member, dst, error, required) do { \
43 + char _datetime[RFC3339_MAX_LENGTH]; _datetime[0] = '\0'; \
44 + json_object *_j; \
45 + if (json_object_object_get_ex(jobj, member, &_j) && json_object_is_type(_j, json_type_string)) { \
46 + strncpyz(_datetime, json_object_get_string(_j), sizeof(_datetime) - 1); \
47 + dst = rfc3339_parse_ut(_datetime, NULL); \
48 + } \
49 + else { \
50 + dst = 0; \
51 + if (required) { \
52 + buffer_sprintf(error, "missing or invalid type for '%s.%s' string", path, member); \
53 + return false; \
54 + } \
55 + } \
56 +} while(0)
57 +
58 #define JSONC_PARSE_TXT2STRDUPZ_OR_ERROR_AND_RETURN(jobj, path, member, dst, error, required) do { \
59 json_object *_j; \
60 if (json_object_object_get_ex(jobj, member, &_j) && json_object_is_type(_j, json_type_string)) { \
src/libnetdata/os/run_dir.c
+1
@@ -124,5 +124,6 @@ const char *os_run_dir(bool rw) {
124
125 spinlock_unlock(&spinlock);
126
127 + errno_clear();
128 return cached_run_dir;
129 }