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), ¤t_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), ¤t_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), ¤t_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), ¤t_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
}