master
c 414 lines 13.1 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "../libnetdata.h"
4 #include "log-forwarder.h"
5
6 typedef struct LOG_FORWARDER_ENTRY {
7 int fd;
8 char *cmd;
9 pid_t pid;
10 BUFFER *wb;
11 size_t pfds_idx;
12 bool delete;
13
14 struct LOG_FORWARDER_ENTRY *prev;
15 struct LOG_FORWARDER_ENTRY *next;
16 } LOG_FORWARDER_ENTRY;
17
18 typedef struct LOG_FORWARDER {
19 LOG_FORWARDER_ENTRY *entries;
20 ND_THREAD *thread;
21 SPINLOCK spinlock;
22 int pipe_fds[2]; // Pipe for notifications
23 bool running;
24 volatile bool initialized; // Thread has fully initialized (atomic)
25 } LOG_FORWARDER;
26
27 static void log_forwarder_thread_func(void *arg);
28
29 static inline size_t log_forwarder_max_pfds(void) {
30 size_t max_nfds = SIZE_MAX / sizeof(struct pollfd);
31 size_t max_poll_nfds = (size_t)(nfds_t)-1;
32
33 if(max_poll_nfds < max_nfds)
34 max_nfds = max_poll_nfds;
35
36 return max_nfds;
37 }
38
39 // --------------------------------------------------------------------------------------------------------------------
40 // helper functions
41
42 static inline LOG_FORWARDER_ENTRY *log_forwarder_find_entry_unsafe(LOG_FORWARDER *lf, int fd) {
43 for (LOG_FORWARDER_ENTRY *entry = lf->entries; entry; entry = entry->next) {
44 if (entry->fd == fd)
45 return entry;
46 }
47
48 return NULL;
49 }
50
51 static inline void log_forwarder_del_entry_unsafe(LOG_FORWARDER *lf, LOG_FORWARDER_ENTRY *entry) {
52 if(!entry) return;
53
54 DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(lf->entries, entry, prev, next);
55 buffer_free(entry->wb);
56 freez(entry->cmd);
57 close(entry->fd);
58 freez(entry);
59 }
60
61 static inline void log_forwarder_wake_up_worker(LOG_FORWARDER *lf) {
62 char ch = 0;
63 ssize_t bytes_written = write(lf->pipe_fds[PIPE_WRITE], &ch, 1);
64 if (bytes_written != 1)
65 nd_log(NDLS_COLLECTORS, NDLP_ERR, "Log forwarder: Failed to write to notification pipe");
66 }
67
68 // --------------------------------------------------------------------------------------------------------------------
69 // starting / stopping
70
71 LOG_FORWARDER *log_forwarder_start(void) {
72 LOG_FORWARDER *lf = callocz(1, sizeof(LOG_FORWARDER));
73
74 spinlock_init(&lf->spinlock);
75 if (pipe(lf->pipe_fds) != 0) {
76 freez(lf);
77 return NULL;
78 }
79
80 // make sure read() will not block on this pipe
81 if(sock_setnonblock(lf->pipe_fds[PIPE_READ], true) != 1)
82 nd_log(NDLS_COLLECTORS, NDLP_ERR, "Log forwarder: Failed to set non-blocking mode");
83
84 lf->running = true;
85 __atomic_store_n(&lf->initialized, false, __ATOMIC_RELEASE);
86
87 lf->thread = nd_thread_create("log-fw", NETDATA_THREAD_OPTION_DEFAULT, log_forwarder_thread_func, lf);
88
89 if(!lf->thread) {
90 nd_log(NDLS_COLLECTORS, NDLP_ERR, "Log forwarder: nd_thread_create() failed!");
91 close(lf->pipe_fds[PIPE_READ]);
92 close(lf->pipe_fds[PIPE_WRITE]);
93 freez(lf);
94 return NULL;
95 }
96
97 // Wait for the thread to signal it's initialized
98 size_t retries = 0;
99 while (!__atomic_load_n(&lf->initialized, __ATOMIC_ACQUIRE) && retries < 100) { // 100 * 10ms = 1 second max
100 sleep_usec(10 * USEC_PER_MS); // 1ms
101 retries++;
102 }
103
104 if (!__atomic_load_n(&lf->initialized, __ATOMIC_ACQUIRE))
105 nd_log(NDLS_COLLECTORS, NDLP_WARNING, "Log forwarder: thread initialization timeout");
106
107 return lf;
108 }
109
110 static inline void mark_all_entries_for_deletion_unsafe(LOG_FORWARDER *lf) {
111 for(LOG_FORWARDER_ENTRY *entry = lf->entries; entry ;entry = entry->next)
112 entry->delete = true;
113 }
114
115 void log_forwarder_stop(LOG_FORWARDER *lf) {
116 if(!lf || !lf->running)
117 return;
118
119 // Signal the thread to stop
120 spinlock_lock(&lf->spinlock);
121
122 if(!lf->running) {
123 spinlock_unlock(&lf->spinlock);
124 return;
125 }
126
127 lf->running = false;
128 mark_all_entries_for_deletion_unsafe(lf);
129 spinlock_unlock(&lf->spinlock);
130
131 // Wake up the thread by writing to the pipe (don't close it yet - let the thread clean up)
132 char ch = 0;
133 ssize_t written = write(lf->pipe_fds[PIPE_WRITE], &ch, 1);
134 (void)written;
135
136 // Wait for the thread to finish
137 // Note: nd_thread_join() handles the Windows/MSYS2 EINVAL case internally
138 int join_result = nd_thread_join(lf->thread);
139 if(join_result != 0) {
140 nd_log(NDLS_COLLECTORS, NDLP_ERR,
141 "Log forwarder: nd_thread_join() failed with error %d", join_result);
142 }
143
144 // Always clean up - if join failed, the thread has still exited
145 lf->thread = NULL;
146 close(lf->pipe_fds[PIPE_WRITE]);
147 freez(lf);
148 }
149
150 // --------------------------------------------------------------------------------------------------------------------
151 // managing entries
152
153 void log_forwarder_add_fd(LOG_FORWARDER *lf, int fd) {
154 if(!lf || !lf->running || fd < 0) return;
155
156 LOG_FORWARDER_ENTRY *entry = callocz(1, sizeof(LOG_FORWARDER_ENTRY));
157 entry->fd = fd;
158 entry->cmd = NULL;
159 entry->pid = 0;
160 entry->pfds_idx = 0;
161 entry->delete = false;
162 entry->wb = buffer_create(0, NULL);
163
164 spinlock_lock(&lf->spinlock);
165
166 // Append to the entries list
167 DOUBLE_LINKED_LIST_PREPEND_ITEM_UNSAFE(lf->entries, entry, prev, next);
168
169 // Send a byte to the pipe to wake up the thread
170 log_forwarder_wake_up_worker(lf);
171
172 spinlock_unlock(&lf->spinlock);
173 }
174
175 bool log_forwarder_del_and_close_fd(LOG_FORWARDER *lf, int fd) {
176 if(!lf || !lf->running || fd < 0) return false;
177
178 bool ret = false;
179
180 spinlock_lock(&lf->spinlock);
181
182 LOG_FORWARDER_ENTRY *entry = log_forwarder_find_entry_unsafe(lf, fd);
183 if(entry) {
184 entry->delete = true;
185
186 // Send a byte to the pipe to wake up the thread
187 log_forwarder_wake_up_worker(lf);
188
189 ret = true;
190 }
191
192 spinlock_unlock(&lf->spinlock);
193
194 return ret;
195 }
196
197 void log_forwarder_annotate_fd_name(LOG_FORWARDER *lf, int fd, const char *cmd) {
198 if(!lf || !lf->running || fd < 0 || !cmd || !*cmd) return;
199
200 spinlock_lock(&lf->spinlock);
201
202 LOG_FORWARDER_ENTRY *entry = log_forwarder_find_entry_unsafe(lf, fd);
203 if (entry) {
204 freez(entry->cmd);
205 entry->cmd = strdupz(cmd);
206 }
207
208 spinlock_unlock(&lf->spinlock);
209 }
210
211 void log_forwarder_annotate_fd_pid(LOG_FORWARDER *lf, int fd, pid_t pid) {
212 if(!lf || !lf->running || fd < 0) return;
213
214 spinlock_lock(&lf->spinlock);
215
216 LOG_FORWARDER_ENTRY *entry = log_forwarder_find_entry_unsafe(lf, fd);
217 if (entry)
218 entry->pid = pid;
219
220 spinlock_unlock(&lf->spinlock);
221 }
222
223 // --------------------------------------------------------------------------------------------------------------------
224 // log forwarder thread
225
226 static inline void log_forwarder_log(LOG_FORWARDER *lf __maybe_unused, LOG_FORWARDER_ENTRY *entry, const char *msg) {
227 if(!msg || !*msg || !entry || !lf) return;
228
229 const char *s = msg;
230 while(*s && isspace((uint8_t)*s)) s++;
231 if(*s == '\0') return; // do not log empty lines
232
233 ND_LOG_STACK lgs[] = {
234 ND_LOG_FIELD_TXT(NDF_SYSLOG_IDENTIFIER, entry->cmd ? entry->cmd : "unknown"),
235 ND_LOG_FIELD_I64(NDF_TID, entry->pid),
236 ND_LOG_FIELD_END(),
237 };
238 ND_LOG_STACK_PUSH(lgs);
239
240 nd_log(NDLS_COLLECTORS, NDLP_WARNING, "STDERR: %s", msg);
241 }
242
243 // returns the number of entries active
244 static inline size_t log_forwarder_remove_deleted_unsafe(LOG_FORWARDER *lf) {
245 size_t entries = 0;
246
247 LOG_FORWARDER_ENTRY *entry = lf->entries;
248 while(entry) {
249 LOG_FORWARDER_ENTRY *next = entry->next;
250
251 if(entry->delete) {
252 if (entry->wb && buffer_strlen(entry->wb))
253 // there is something not logged in it - log it
254 log_forwarder_log(lf, entry, buffer_tostring(entry->wb));
255
256 log_forwarder_del_entry_unsafe(lf, entry);
257 }
258 else
259 entries++;
260
261 entry = next;
262 }
263
264 return entries;
265 }
266
267 static void log_forwarder_thread_func(void *arg) {
268 LOG_FORWARDER *lf = (LOG_FORWARDER *)arg;
269 struct pollfd *pfds = NULL;
270 size_t pfds_capacity = 0;
271 const size_t max_pfds = log_forwarder_max_pfds();
272
273 while (1) {
274 spinlock_lock(&lf->spinlock);
275
276 // Signal initialization on first iteration after acquiring spinlock
277 // This ensures the thread is truly ready and in its main loop
278 if(!__atomic_load_n(&lf->initialized, __ATOMIC_ACQUIRE)) {
279 __atomic_store_n(&lf->initialized, true, __ATOMIC_RELEASE);
280 }
281
282 if (!lf->running) {
283 spinlock_unlock(&lf->spinlock);
284 break;
285 }
286
287 // Count the number of fds
288 size_t entries = log_forwarder_remove_deleted_unsafe(lf);
289 internal_fatal(entries > max_pfds - 1,
290 "Log forwarder: too many file descriptors to poll (%zu > %zu)",
291 entries + 1, max_pfds);
292 size_t nfds = 1 + entries;
293
294 // Reuse the pollfd array across iterations to avoid heap churn in the worker loop.
295 if (unlikely(nfds > pfds_capacity)) {
296 size_t new_capacity = pfds_capacity ? pfds_capacity : 1;
297 while (new_capacity < nfds) {
298 internal_fatal(new_capacity > max_pfds / 2,
299 "Log forwarder: pollfd capacity overflow while growing to %zu fds",
300 nfds);
301 new_capacity *= 2;
302 }
303
304 pfds = reallocz(pfds, new_capacity * sizeof(*pfds));
305 pfds_capacity = new_capacity;
306 }
307
308 // First, the notification pipe
309 pfds[0].fd = lf->pipe_fds[PIPE_READ];
310 pfds[0].events = POLLIN;
311
312 size_t idx = 1;
313 for(LOG_FORWARDER_ENTRY *entry = lf->entries; entry ; entry = entry->next, idx++) {
314 pfds[idx].fd = entry->fd;
315 pfds[idx].events = POLLIN;
316 entry->pfds_idx = idx;
317 }
318
319 spinlock_unlock(&lf->spinlock);
320
321 int timeout = 200; // 200ms
322 int ret = poll(pfds, nfds, timeout);
323
324 if (ret > 0) {
325 // Check the notification pipe
326 if (pfds[0].revents & (POLLIN | POLLERR | POLLHUP | POLLNVAL)) {
327 if (pfds[0].revents & (POLLERR | POLLHUP | POLLNVAL)) {
328 // Pipe error - check if we should exit
329 spinlock_lock(&lf->spinlock);
330 bool should_exit = !lf->running;
331 spinlock_unlock(&lf->spinlock);
332
333 if (should_exit) {
334 // Expected during shutdown
335 break;
336 }
337
338 nd_log(NDLS_COLLECTORS, NDLP_ERR,
339 "Log forwarder: pipe error (revents=0x%x) but still running",
340 (unsigned int) pfds[0].revents);
341 }
342
343 if (pfds[0].revents & POLLIN) {
344 // Read and discard the data
345 char buf[256];
346 ssize_t bytes_read = read(lf->pipe_fds[PIPE_READ], buf, sizeof(buf));
347 // Ignore the data; proceed regardless of the result
348 if (bytes_read == -1) {
349 if (errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) {
350 // Handle read error if necessary
351 nd_log(NDLS_COLLECTORS, NDLP_ERR, "Log forwarder: Failed to read from notification pipe");
352 break;
353 }
354 }
355 }
356 }
357
358 spinlock_lock(&lf->spinlock);
359
360 // read or mark them for deletion
361 for(LOG_FORWARDER_ENTRY *entry = lf->entries; entry ; entry = entry->next) {
362 if (entry->pfds_idx < 1 || entry->pfds_idx >= nfds || !(pfds[entry->pfds_idx].revents & POLLIN) || entry->delete || !entry->wb)
363 continue;
364
365 BUFFER *wb = entry->wb;
366 buffer_need_bytes(wb, 1024);
367
368 ssize_t bytes_read = read(entry->fd, &wb->buffer[wb->len], wb->size - wb->len - 1);
369 if(bytes_read > 0)
370 wb->len += bytes_read;
371 else if(bytes_read == 0 || (bytes_read == -1 && errno != EINTR && errno != EAGAIN)) {
372 // EOF or error
373 entry->delete = true;
374 }
375
376 // log as many lines are they have been received
377 char *start = (char *)buffer_tostring(wb);
378 char *newline = strchr(start, '\n');
379 while(newline) {
380 *newline = '\0';
381 log_forwarder_log(lf, entry, start);
382
383 start = ++newline;
384 newline = strchr(newline, '\n');
385 }
386
387 if(start != wb->buffer) {
388 wb->len = strlen(start);
389 if (wb->len)
390 memmove(wb->buffer, start, wb->len);
391 }
392
393 entry->pfds_idx = 0;
394 }
395
396 spinlock_unlock(&lf->spinlock);
397 }
398 else if (ret == 0) {
399 // Timeout, nothing to do
400 continue;
401
402 }
403 else
404 nd_log(NDLS_COLLECTORS, NDLP_ERR, "Log forwarder: poll() error");
405 }
406
407 freez(pfds);
408
409 spinlock_lock(&lf->spinlock);
410 mark_all_entries_for_deletion_unsafe(lf);
411 log_forwarder_remove_deleted_unsafe(lf);
412 spinlock_unlock(&lf->spinlock);
413 close(lf->pipe_fds[PIPE_READ]);
414 }