master
c 935 lines 29.9 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "common.h"
4
5 static uv_thread_t thread;
6 static uv_loop_t* loop;
7 static uv_async_t async;
8 static struct completion completion;
9 static uv_pipe_t server_pipe;
10
11 char cmd_prefix_by_status[] = {
12 CMD_PREFIX_INFO,
13 CMD_PREFIX_ERROR,
14 CMD_PREFIX_ERROR
15 };
16
17 static cmd_init_status_t command_server_initialized = CMD_INIT_STATUS_OFF;
18 static int command_thread_error;
19 static int command_thread_shutdown;
20 static unsigned clients = 0;
21
22 struct command_context {
23 /* embedded client pipe structure at address 0 */
24 uv_pipe_t client;
25
26 uv_work_t work;
27 uv_write_t write_req;
28 cmd_t idx;
29 char *args;
30 char *message;
31 cmd_status_t status;
32 char command_string[MAX_COMMAND_LENGTH];
33 unsigned command_string_size;
34 };
35
36 static inline char command_reply_prefix(const struct command_context *cmd_ctx, cmd_status_t status)
37 {
38 if (cmd_ctx->idx == CMD_PING)
39 return CMD_PREFIX_INFO;
40
41 return cmd_prefix_by_status[status];
42 }
43
44 /* Forward declarations */
45 static cmd_status_t cmd_help_execute(char *args, char **message);
46 static cmd_status_t cmd_reload_health_execute(char *args, char **message);
47 static cmd_status_t cmd_reopen_logs_execute(char *args, char **message);
48 static cmd_status_t cmd_exit_execute(char *args, char **message);
49 static cmd_status_t cmd_fatal_execute(char *args, char **message);
50 static cmd_status_t cmd_reload_claiming_state_execute(char *args, char **message);
51 static cmd_status_t cmd_reload_labels_execute(char *args, char **message);
52 static cmd_status_t cmd_read_config_execute(char *args, char **message);
53 static cmd_status_t cmd_write_config_execute(char *args, char **message);
54 static cmd_status_t cmd_ping_execute(char *args, char **message);
55 static cmd_status_t cmd_aclk_state(char *args, char **message);
56 static cmd_status_t cmd_version(char *args, char **message);
57 static cmd_status_t cmd_dumpconfig(char *args, char **message);
58 static cmd_status_t cmd_remove_stale_node(char *args, char **message);
59 static cmd_status_t cmd_mark_stale_nodes_ephemeral(char *args, char **message);
60 static cmd_status_t cmd_update_node_info(char *args, char **message);
61
62 static command_info_t command_info_array[] = {
63 {"help", "", "Show this help menu.", cmd_help_execute, CMD_TYPE_HIGH_PRIORITY, CMD_INIT_STATUS_INIT}, // show help menu
64 {"reload-health", "", "Reload health configuration.", cmd_reload_health_execute, CMD_TYPE_ORTHOGONAL, CMD_INIT_STATUS_FULL}, // reload health configuration
65 {"reopen-logs", "", "Close and reopen log files.", cmd_reopen_logs_execute, CMD_TYPE_ORTHOGONAL, CMD_INIT_STATUS_FULL}, // Close and reopen log files
66 {"shutdown-agent", "", "Cleanup and exit the netdata agent.", cmd_exit_execute, CMD_TYPE_EXCLUSIVE, CMD_INIT_STATUS_FULL}, // exit cleanly
67 {"fatal-agent", "", "Log the state and halt the netdata agent.", cmd_fatal_execute, CMD_TYPE_HIGH_PRIORITY, CMD_INIT_STATUS_FULL}, // exit with fatal error
68 {"reload-claiming-state", "", "Reload agent claiming state from disk.", cmd_reload_claiming_state_execute, CMD_TYPE_ORTHOGONAL, CMD_INIT_STATUS_FULL}, // reload claiming state
69 {"reload-labels", "", "Reload all localhost labels.", cmd_reload_labels_execute, CMD_TYPE_ORTHOGONAL, CMD_INIT_STATUS_FULL}, // reload the labels
70 {"read-config", "", "", cmd_read_config_execute, CMD_TYPE_CONCURRENT, CMD_INIT_STATUS_FULL},
71 {"write-config", "", "", cmd_write_config_execute, CMD_TYPE_ORTHOGONAL, CMD_INIT_STATUS_FULL},
72 {"ping", "", "Return with 'pong'; exit 0 when ready, 1 while initializing, 255 if the agent cannot be contacted.", cmd_ping_execute, CMD_TYPE_ORTHOGONAL, CMD_INIT_STATUS_INIT}, // ping command
73 {"aclk-state", "[json]", "Returns current state of ACLK and Netdata Cloud connection. (optionally in json).", cmd_aclk_state, CMD_TYPE_ORTHOGONAL, CMD_INIT_STATUS_FULL},
74 {"version", "", "Returns the netdata version.", cmd_version, CMD_TYPE_ORTHOGONAL, CMD_INIT_STATUS_INIT},
75 {"dumpconfig", "", "Returns the current netdata.conf on stdout.", cmd_dumpconfig, CMD_TYPE_ORTHOGONAL, CMD_INIT_STATUS_FULL},
76 {"mark-stale-nodes-ephemeral", "<node_id | machine_guid | hostname | ALL_NODES>",
77 "Marks one or all disconnected nodes as ephemeral, while keeping their retention\n available for queries on both this Netdata Agent dashboard and Netdata Cloud", cmd_mark_stale_nodes_ephemeral, CMD_TYPE_ORTHOGONAL, CMD_INIT_STATUS_FULL},
78 {"remove-stale-node", "<node_id | machine_guid | hostname | ALL_NODES>",
79 "Marks one or all disconnected nodes as ephemeral, and removes them\n so that they are no longer available for queries, from both this\n Netdata Agent dashboard and Netdata Cloud.", cmd_remove_stale_node, CMD_TYPE_ORTHOGONAL, CMD_INIT_STATUS_FULL},
80 {"update-node-info", "", "Schedules an node update message for localhost to Netdata Cloud.", cmd_update_node_info, CMD_TYPE_ORTHOGONAL, CMD_INIT_STATUS_FULL},
81 };
82
83 /* Mutexes for commands of type CMD_TYPE_ORTHOGONAL */
84 static netdata_mutex_t command_lock_array[CMD_TOTAL_COMMANDS];
85 /* Commands of type CMD_TYPE_EXCLUSIVE are writers */
86 static netdata_rwlock_t exclusive_rwlock;
87 /*
88 * Locking order:
89 * 1. exclusive_rwlock
90 * 2. command_lock_array[]
91 */
92
93 /* Forward declarations */
94 static void cmd_lock_exclusive(unsigned index);
95 static void cmd_lock_orthogonal(unsigned index);
96 static void cmd_lock_idempotent(unsigned index);
97 static void cmd_lock_high_priority(unsigned index);
98
99 static command_lock_t *cmd_lock_by_type[] = {
100 cmd_lock_exclusive,
101 cmd_lock_orthogonal,
102 cmd_lock_idempotent,
103 cmd_lock_high_priority
104 };
105
106 /* Forward declarations */
107 static void cmd_unlock_exclusive(unsigned index);
108 static void cmd_unlock_orthogonal(unsigned index);
109 static void cmd_unlock_idempotent(unsigned index);
110 static void cmd_unlock_high_priority(unsigned index);
111
112 static command_lock_t *cmd_unlock_by_type[] = {
113 cmd_unlock_exclusive,
114 cmd_unlock_orthogonal,
115 cmd_unlock_idempotent,
116 cmd_unlock_high_priority
117 };
118
119 static cmd_status_t cmd_help_execute(char *args, char **message)
120 {
121 (void)args;
122 CLEAN_BUFFER *wb = buffer_create(0, NULL);
123
124 buffer_strcat(wb, "The commands are:\n\n");
125 for(size_t i = 0; i < _countof(command_info_array); i++) {
126 const command_info_t *t = &command_info_array[i];
127 if(!t->help || !t->help[0]) continue;
128
129 buffer_strcat(wb, " ");
130 buffer_strcat(wb, t->cmd_str);
131 if(t->params && t->params[0]) {
132 buffer_putc(wb, ' ');
133 buffer_strcat(wb, t->params);
134 }
135 buffer_putc(wb, '\n');
136 buffer_strcat(wb, " ");
137 buffer_strcat(wb, t->help);
138 buffer_strcat(wb, "\n\n");
139 }
140
141 *message = strdupz(buffer_tostring(wb));
142 return CMD_STATUS_SUCCESS;
143 }
144
145 static cmd_status_t cmd_reload_health_execute(char *args, char **message)
146 {
147 (void)args;
148 (void)message;
149
150 nd_log_limits_unlimited();
151 netdata_log_info("COMMAND: Reloading HEALTH configuration.");
152 health_plugin_reload();
153 nd_log_limits_reset();
154
155 return CMD_STATUS_SUCCESS;
156 }
157
158 static cmd_status_t cmd_reopen_logs_execute(char *args, char **message)
159 {
160 (void)args;
161 (void)message;
162
163 nd_log_limits_unlimited();
164 nd_log_reopen_log_files(true);
165 nd_log_limits_reset();
166
167 return CMD_STATUS_SUCCESS;
168 }
169
170 static cmd_status_t cmd_exit_execute(char *args, char **message)
171 {
172 (void)args;
173 (void)message;
174
175 nd_log_limits_unlimited();
176 netdata_log_info("COMMAND: Cleaning up to exit.");
177 netdata_exit_gracefully(EXIT_REASON_CMD_EXIT, true);
178 return CMD_STATUS_SUCCESS;
179 }
180
181 static cmd_status_t cmd_fatal_execute(char *args, char **message)
182 {
183 (void)args;
184 (void)message;
185
186 fatal("COMMAND: netdata now exits.");
187
188 return CMD_STATUS_SUCCESS;
189 }
190
191 static cmd_status_t cmd_reload_claiming_state_execute(char *args __maybe_unused, char **message) {
192 char msg[1024];
193
194 CLOUD_STATUS status = claim_reload_and_wait_online();
195 switch(status) {
196 case CLOUD_STATUS_ONLINE:
197 snprintfz(msg, sizeof(msg),
198 "Netdata Agent is claimed to Netdata Cloud and is currently online.");
199 break;
200
201 case CLOUD_STATUS_BANNED:
202 snprintfz(msg, sizeof(msg),
203 "Netdata Agent is claimed to Netdata Cloud, but it is banned.");
204 break;
205
206 default:
207 case CLOUD_STATUS_AVAILABLE:
208 snprintfz(msg, sizeof(msg),
209 "Netdata Agent is not claimed to Netdata Cloud: %s",
210 claim_agent_failure_reason_get());
211 break;
212
213 case CLOUD_STATUS_OFFLINE:
214 snprintfz(msg, sizeof(msg),
215 "Netdata Agent is claimed to Netdata Cloud, but it is currently offline: %s",
216 cloud_status_aclk_offline_reason());
217 break;
218
219 case CLOUD_STATUS_INDIRECT:
220 snprintfz(msg, sizeof(msg),
221 "Netdata Agent is not claimed to Netdata Cloud, but it is currently online via parent.");
222 break;
223 }
224
225 *message = strdupz(msg);
226
227 return CMD_STATUS_SUCCESS;
228 }
229
230 static cmd_status_t cmd_reload_labels_execute(char *args, char **message)
231 {
232 (void)args;
233 netdata_log_info("COMMAND: reloading host labels.");
234 reload_host_labels();
235 aclk_queue_node_info(localhost, 1);
236
237 BUFFER *wb = buffer_create(10, NULL);
238 rrdlabels_log_to_buffer(localhost->rrdlabels, wb);
239 (*message)=strdupz(buffer_tostring(wb));
240 buffer_free(wb);
241
242 return CMD_STATUS_SUCCESS;
243 }
244
245 static cmd_status_t cmd_read_config_execute(char *args, char **message)
246 {
247 size_t n = strlen(args);
248 char *separator = strchr(args,'|');
249 if (separator == NULL)
250 return CMD_STATUS_FAILURE;
251 char *separator2 = strchr(separator + 1,'|');
252 if (separator2 == NULL)
253 return CMD_STATUS_FAILURE;
254
255 char *temp = callocz(n + 1, 1);
256 strcpy(temp, args);
257 size_t offset = separator - args;
258 temp[offset] = 0;
259 size_t offset2 = separator2 - args;
260 temp[offset2] = 0;
261
262 const char *conf_file = temp; /* "cloud" is cloud.conf, otherwise netdata.conf */
263 struct config *tmp_config = strcmp(conf_file, "cloud") ? &netdata_config : &cloud_config;
264
265 const char *value = inicfg_get(tmp_config, temp + offset + 1, temp + offset2 + 1, NULL);
266 if (value == NULL) {
267 netdata_log_error("Cannot execute read-config conf_file=%s section=%s / key=%s because no value set",
268 conf_file,
269 temp + offset + 1,
270 temp + offset2 + 1);
271 freez(temp);
272 return CMD_STATUS_FAILURE;
273 }
274 else {
275 (*message) = strdupz(value);
276 freez(temp);
277 return CMD_STATUS_SUCCESS;
278 }
279 }
280
281 static cmd_status_t cmd_write_config_execute(char *args, char **message)
282 {
283 UNUSED(message);
284 netdata_log_info("write-config %s", args);
285 size_t n = strlen(args);
286 char *separator = strchr(args,'|');
287 if (separator == NULL)
288 return CMD_STATUS_FAILURE;
289 char *separator2 = strchr(separator + 1,'|');
290 if (separator2 == NULL)
291 return CMD_STATUS_FAILURE;
292 char *separator3 = strchr(separator2 + 1,'|');
293 if (separator3 == NULL)
294 return CMD_STATUS_FAILURE;
295 char *temp = callocz(n + 1, 1);
296 strcpy(temp, args);
297 size_t offset = separator - args;
298 temp[offset] = 0;
299 size_t offset2 = separator2 - args;
300 temp[offset2] = 0;
301 size_t offset3 = separator3 - args;
302 temp[offset3] = 0;
303
304 const char *conf_file = temp; /* "cloud" is cloud.conf, otherwise netdata.conf */
305 struct config *tmp_config = strcmp(conf_file, "cloud") ? &netdata_config : &cloud_config;
306
307 inicfg_set(tmp_config, temp + offset + 1, temp + offset2 + 1, temp + offset3 + 1);
308 netdata_log_info("write-config conf_file=%s section=%s key=%s value=%s",conf_file, temp + offset + 1, temp + offset2 + 1,
309 temp + offset3 + 1);
310 freez(temp);
311 return CMD_STATUS_SUCCESS;
312 }
313
314 static cmd_status_t cmd_ping_execute(char *args, char **message)
315 {
316 (void)args;
317
318 *message = strdupz("pong");
319
320 return netdata_ready_load() ? CMD_STATUS_SUCCESS : CMD_STATUS_FAILURE;
321 }
322
323 static cmd_status_t cmd_aclk_state(char *args, char **message)
324 {
325 netdata_log_info("COMMAND: Reopening aclk/cloud state.");
326 if (strstr(args, "json"))
327 *message = aclk_state_json();
328 else
329 *message = aclk_state();
330
331 return CMD_STATUS_SUCCESS;
332 }
333
334 static cmd_status_t cmd_version(char *args, char **message)
335 {
336 (void)args;
337
338 char version[MAX_COMMAND_LENGTH];
339 snprintfz(version, MAX_COMMAND_LENGTH -1, "%s %s", program_name, NETDATA_VERSION);
340
341 *message = strdupz(version);
342
343 return CMD_STATUS_SUCCESS;
344 }
345
346 static cmd_status_t cmd_dumpconfig(char *args, char **message)
347 {
348 (void)args;
349
350 BUFFER *wb = buffer_create(1024, NULL);
351 inicfg_generate(&netdata_config, wb, 0, true);
352 *message = strdupz(buffer_tostring(wb));
353 buffer_free(wb);
354 return CMD_STATUS_SUCCESS;
355 }
356
357 static int remove_ephemeral_host(BUFFER *wb, RRDHOST *host, bool report_error, bool unregister)
358 {
359 if (host == localhost) {
360 if (report_error)
361 buffer_sprintf(wb, "Node '%s' (machine guid: %s) is our localhost - not changing it",
362 rrdhost_hostname(host), host->machine_guid);
363 return 0;
364 }
365
366 if (rrdhost_is_online(host)) {
367 if (report_error)
368 buffer_sprintf(wb, "Node '%s' (machine guid: %s) is online - not changing it",
369 rrdhost_hostname(host), host->machine_guid);
370 return 0;
371 }
372
373 bool marked = false;
374 if (!rrdhost_option_check(host, RRDHOST_OPTION_EPHEMERAL_HOST)) {
375 rrdhost_option_set(host, RRDHOST_OPTION_EPHEMERAL_HOST);
376 marked = true;
377 }
378
379 sql_set_host_label(&host->host_id.uuid, "_is_ephemeral", "true");
380 pulse_host_status(host, 0, 0);
381
382 if (marked)
383 send_node_info_with_wait(host);
384
385 if (unregister) {
386 send_node_update_with_wait(host, 0, 0);
387
388 unregister_node(host->machine_guid);
389 host->node_id = UUID_ZERO;
390 buffer_sprintf(wb, "Node '%s' (machine guid: %s) has been unregistered",
391 rrdhost_hostname(host), host->machine_guid);
392 rrdhost_free___without_having_rrd_wrlock(host);
393 return 1;
394 }
395
396 if (marked) {
397 buffer_sprintf(wb, "Node '%s' (machine guid: %s) has been marked ephemeral",
398 rrdhost_hostname(host), host->machine_guid);
399 return 1;
400 }
401
402 if (report_error) {
403 buffer_sprintf(wb, "Node '%s' (machine guid: %s) is already ephemeral - not changing it",
404 rrdhost_hostname(host), host->machine_guid);
405 }
406
407 return 0;
408 }
409
410 #define SQL_HOSTNAME_TO_REMOVE "SELECT host_id FROM host WHERE (hostname = @hostname OR @hostname = 'ALL_NODES')"
411
412 static cmd_status_t cmd_remove_stale_node_internal(char *args, char **message, bool unregister)
413 {
414 (void)args;
415
416 BUFFER *wb = buffer_create(1024, NULL);
417 if (strlen(args) == 0) {
418 buffer_sprintf(wb, "Please specify a machine or node UUID or hostname");
419 goto done;
420 }
421
422 RRDHOST *host = NULL;
423 host = rrdhost_find_by_guid(args);
424 if (!host)
425 host = rrdhost_find_by_node_id(args);
426
427 if (!host) {
428 sqlite3_stmt *res = NULL;
429
430 bool report_error = strcmp(args, "ALL_NODES") != 0;
431
432 if (!PREPARE_STATEMENT(db_meta, SQL_HOSTNAME_TO_REMOVE, &res)) {
433 buffer_sprintf(wb, "Failed to prepare database statement to check for stale nodes");
434 goto done;
435 }
436
437 int param = 0;
438 SQLITE_BIND_FAIL(done0, sqlite3_bind_text(res, ++param, args, -1, SQLITE_STATIC));
439
440 param = 0;
441 int cnt = 0;
442 while (sqlite3_step_monitored(res) == SQLITE_ROW) {
443 char guid[UUID_STR_LEN];
444 uuid_unparse_lower(*(nd_uuid_t *)sqlite3_column_blob(res, 0), guid);
445 host = rrdhost_find_by_guid(guid);
446 if (host) {
447 int rc = remove_ephemeral_host(wb, host, report_error, unregister);
448 if(rc) {
449 cnt += rc;
450 buffer_fast_strcat(wb, "\n", 1);
451 }
452 }
453 }
454 if (!cnt && buffer_strlen(wb) == 0) {
455 if (report_error)
456 buffer_sprintf(wb, "No match for \"%s\"", args);
457 else
458 buffer_sprintf(wb, "No stale nodes found");
459 }
460 done0:
461 REPORT_BIND_FAIL(res, param);
462 SQLITE_FINALIZE(res);
463 }
464 else
465 (void) remove_ephemeral_host(wb, host, true, unregister);
466
467 done:
468 *message = strdupz(buffer_tostring(wb));
469 buffer_free(wb);
470 return CMD_STATUS_SUCCESS;
471 }
472
473 static cmd_status_t cmd_update_node_info(char *args __maybe_unused, char **message)
474 {
475 if (aclk_online()) {
476 schedule_node_state_update(localhost, 1000);
477 CLEAN_BUFFER *wb = buffer_create(1024, NULL);
478 buffer_sprintf(wb, "Node info update scheduled for \"%s\"", rrdhost_hostname(localhost));
479 *message = strdupz(buffer_tostring(wb));
480 }
481 else
482 *message = strdupz("Agent is not connected to Netdata Cloud");
483 return CMD_STATUS_SUCCESS;
484 }
485
486 static cmd_status_t cmd_remove_stale_node(char *args, char **message)
487 {
488 return cmd_remove_stale_node_internal(args, message, true);
489 }
490
491 static cmd_status_t cmd_mark_stale_nodes_ephemeral(char *args, char **message)
492 {
493 return cmd_remove_stale_node_internal(args, message, false);
494 }
495
496 static void cmd_lock_exclusive(unsigned index)
497 {
498 (void)index;
499
500 netdata_rwlock_wrlock(&exclusive_rwlock);
501 }
502
503 static void cmd_lock_orthogonal(unsigned index)
504 {
505 netdata_rwlock_rdlock(&exclusive_rwlock);
506 netdata_mutex_lock(&command_lock_array[index]);
507 }
508
509 static void cmd_lock_idempotent(unsigned index)
510 {
511 (void)index;
512
513 netdata_rwlock_rdlock(&exclusive_rwlock);
514 }
515
516 static void cmd_lock_high_priority(unsigned index)
517 {
518 (void)index;
519 }
520
521 static void cmd_unlock_exclusive(unsigned index)
522 {
523 (void)index;
524
525 netdata_rwlock_wrunlock(&exclusive_rwlock);
526 }
527
528 static void cmd_unlock_orthogonal(unsigned index)
529 {
530 netdata_rwlock_rdunlock(&exclusive_rwlock);
531 netdata_mutex_unlock(&command_lock_array[index]);
532 }
533
534 static void cmd_unlock_idempotent(unsigned index)
535 {
536 (void)index;
537
538 netdata_rwlock_rdunlock(&exclusive_rwlock);
539 }
540
541 static void cmd_unlock_high_priority(unsigned index)
542 {
543 (void)index;
544 }
545
546 static void pipe_close_cb(uv_handle_t* handle)
547 {
548 /* Also frees command context */
549 freez(handle);
550 }
551
552 static void pipe_write_cb(uv_write_t* req, int status)
553 {
554 (void)status;
555 uv_pipe_t *client = req->data;
556
557 uv_close((uv_handle_t *)client, pipe_close_cb);
558 --clients;
559 buffer_free(client->data);
560 // netdata_log_info("Command Clients = %u", clients);
561 }
562
563 static inline void add_char_to_command_reply(BUFFER *reply_string, unsigned *reply_string_size, char character)
564 {
565 buffer_putc(reply_string, character);
566 *reply_string_size +=1;
567 }
568
569 static inline void add_string_to_command_reply(BUFFER *reply_string, unsigned *reply_string_size, char *str)
570 {
571 unsigned len;
572
573 len = strlen(str);
574 buffer_fast_strcat(reply_string, str, len);
575 *reply_string_size += len;
576 }
577
578 static void send_command_reply(struct command_context *cmd_ctx, cmd_status_t status, char *message)
579 {
580 int ret;
581 BUFFER *reply_string = buffer_create(128, NULL);
582
583 char exit_status_string[MAX_EXIT_STATUS_LENGTH + 1] = {'\0', };
584 unsigned reply_string_size = 0;
585 uv_buf_t write_buf;
586 uv_stream_t *client = (uv_stream_t *)(uv_pipe_t *)cmd_ctx;
587
588 snprintfz(exit_status_string, MAX_EXIT_STATUS_LENGTH, "%u", status);
589 add_char_to_command_reply(reply_string, &reply_string_size, CMD_PREFIX_EXIT_CODE);
590 add_string_to_command_reply(reply_string, &reply_string_size, exit_status_string);
591 add_char_to_command_reply(reply_string, &reply_string_size, '\0');
592
593 if (message) {
594 add_char_to_command_reply(reply_string, &reply_string_size, command_reply_prefix(cmd_ctx, status));
595 add_string_to_command_reply(reply_string, &reply_string_size, message);
596 }
597
598 cmd_ctx->write_req.data = client;
599 client->data = reply_string;
600 write_buf.base = reply_string->buffer;
601 write_buf.len = reply_string_size;
602 ret = uv_write(&cmd_ctx->write_req, (uv_stream_t *)client, &write_buf, 1, pipe_write_cb);
603 if (ret) {
604 netdata_log_error("uv_write(): %s", uv_strerror(ret));
605 }
606 }
607
608 cmd_status_t execute_command(cmd_t idx, char *args, char **message)
609 {
610 cmd_status_t status;
611 cmd_type_t type = command_info_array[idx].type;
612
613 cmd_lock_by_type[type](idx);
614 if (command_server_initialized >= command_info_array[idx].init_status)
615 status = command_info_array[idx].func(args, message);
616 else {
617 if (message)
618 *message = strdupz("Agent is initializing");
619 status = CMD_STATUS_SUCCESS;
620 }
621 cmd_unlock_by_type[type](idx);
622
623 return status;
624 }
625
626 static void after_schedule_command(uv_work_t *req, int status)
627 {
628 struct command_context *cmd_ctx = req->data;
629
630 (void)status;
631
632 send_command_reply(cmd_ctx, cmd_ctx->status, cmd_ctx->message);
633 if (cmd_ctx->message)
634 freez(cmd_ctx->message);
635 }
636
637 static void schedule_command(uv_work_t *req)
638 {
639 register_libuv_worker_jobs();
640 worker_is_busy(UV_EVENT_SCHEDULE_CMD);
641
642 struct command_context *cmd_ctx = req->data;
643 cmd_ctx->status = execute_command(cmd_ctx->idx, cmd_ctx->args, &cmd_ctx->message);
644
645 worker_is_idle();
646 }
647
648 /* This will alter the state of the command_info_array.cmd_str
649 */
650 static void parse_commands(struct command_context *cmd_ctx)
651 {
652 char *message = NULL, *pos, *lstrip, *rstrip;
653 cmd_t i;
654 cmd_status_t status;
655
656 status = CMD_STATUS_FAILURE;
657
658 /* Skip white-space characters */
659 for (pos = cmd_ctx->command_string ; isspace((uint8_t)*pos) && ('\0' != *pos) ; ++pos) ;
660 for (i = 0 ; i < CMD_TOTAL_COMMANDS ; ++i) {
661 if (!strncmp(pos, command_info_array[i].cmd_str, strlen(command_info_array[i].cmd_str))) {
662 if (CMD_EXIT == i) {
663 /* musl C does not like libuv workqueues calling exit() */
664 execute_command(CMD_EXIT, NULL, NULL);
665 }
666 for (lstrip=pos + strlen(command_info_array[i].cmd_str); isspace((uint8_t)*lstrip) && ('\0' != *lstrip); ++lstrip) ;
667 for (rstrip=lstrip+strlen(lstrip)-1; rstrip>lstrip && isspace((uint8_t)*rstrip); *(rstrip--) = 0 ) ;
668
669 cmd_ctx->work.data = cmd_ctx;
670 cmd_ctx->idx = i;
671 cmd_ctx->args = lstrip;
672 cmd_ctx->message = NULL;
673
674 fatal_assert(0 == uv_queue_work(loop, &cmd_ctx->work, schedule_command, after_schedule_command));
675 break;
676 }
677 }
678 if (CMD_TOTAL_COMMANDS == i) {
679 /* no command found */
680 message = strdupz("Illegal command. Please type \"help\" for instructions.");
681 send_command_reply(cmd_ctx, status, message);
682 freez(message);
683 }
684 }
685
686 static void pipe_read_cb(uv_stream_t *client, ssize_t nread, const uv_buf_t *buf)
687 {
688 struct command_context *cmd_ctx = (struct command_context *)client;
689
690 if (0 == nread) {
691 netdata_log_info("%s: Zero bytes read by command pipe.", __func__);
692 } else if (UV_EOF == nread) {
693 parse_commands(cmd_ctx);
694 } else if (nread < 0) {
695 netdata_log_error("%s: %s", __func__, uv_strerror(nread));
696 }
697
698 if (nread < 0) { /* stop stream due to EOF or error */
699 (void)uv_read_stop((uv_stream_t *)client);
700 } else if (nread) {
701 size_t to_copy;
702
703 to_copy = MIN((size_t) nread, MAX_COMMAND_LENGTH - 1 - cmd_ctx->command_string_size);
704 memcpy(cmd_ctx->command_string + cmd_ctx->command_string_size, buf->base, to_copy);
705 cmd_ctx->command_string_size += to_copy;
706 cmd_ctx->command_string[cmd_ctx->command_string_size] = '\0';
707 }
708 if (buf && buf->len) {
709 freez(buf->base);
710 }
711
712 if (nread < 0 && UV_EOF != nread) {
713 uv_close((uv_handle_t *)client, pipe_close_cb);
714 --clients;
715 // netdata_log_info("Command Clients = %u", clients);
716 }
717 }
718
719 static void alloc_cb(uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf)
720 {
721 (void)handle;
722
723 buf->base = mallocz(suggested_size);
724 buf->len = suggested_size;
725 }
726
727 static void connection_cb(uv_stream_t *server, int status)
728 {
729 int ret;
730 uv_pipe_t *client;
731 struct command_context *cmd_ctx;
732 fatal_assert(status == 0);
733
734 /* combined allocation of client pipe and command context */
735 cmd_ctx = mallocz(sizeof(*cmd_ctx));
736 cmd_ctx->idx = CMD_HELP;
737 client = (uv_pipe_t *)cmd_ctx;
738 ret = uv_pipe_init(server->loop, client, 1);
739 if (ret) {
740 netdata_log_error("uv_pipe_init(): %s", uv_strerror(ret));
741 freez(cmd_ctx);
742 return;
743 }
744 ret = uv_accept(server, (uv_stream_t *)client);
745 if (ret) {
746 netdata_log_error("uv_accept(): %s", uv_strerror(ret));
747 uv_close((uv_handle_t *)client, pipe_close_cb);
748 return;
749 }
750
751 ++clients;
752 // netdata_log_info("Command Clients = %u", clients);
753 /* Start parsing a new command */
754 cmd_ctx->command_string_size = 0;
755 cmd_ctx->command_string[0] = '\0';
756
757 ret = uv_read_start((uv_stream_t*)client, alloc_cb, pipe_read_cb);
758 if (ret) {
759 netdata_log_error("uv_read_start(): %s", uv_strerror(ret));
760 uv_close((uv_handle_t *)client, pipe_close_cb);
761 --clients;
762 // netdata_log_info("Command Clients = %u", clients);
763 return;
764 }
765 }
766
767 static void async_cb(uv_async_t *handle)
768 {
769 uv_stop(handle->loop);
770 }
771
772 static void command_thread(void *arg) {
773 uv_thread_set_name_np("DAEMON_COMMAND");
774
775 int ret;
776 uv_fs_t req;
777
778 (void) arg;
779 loop = mallocz(sizeof(uv_loop_t));
780 ret = uv_loop_init(loop);
781 if (ret) {
782 netdata_log_error("uv_loop_init(): %s", uv_strerror(ret));
783 command_thread_error = ret;
784 goto error_after_loop_init;
785 }
786 loop->data = NULL;
787
788 ret = uv_async_init(loop, &async, async_cb);
789 if (ret) {
790 netdata_log_error("uv_async_init(): %s", uv_strerror(ret));
791 command_thread_error = ret;
792 goto error_after_async_init;
793 }
794 async.data = NULL;
795
796 ret = uv_pipe_init(loop, &server_pipe, 0);
797 if (ret) {
798 netdata_log_error("uv_pipe_init(): %s", uv_strerror(ret));
799 command_thread_error = ret;
800 goto error_after_pipe_init;
801 }
802
803 const char *pipename = daemon_pipename();
804
805 (void)uv_fs_unlink(loop, &req, pipename, NULL);
806 uv_fs_req_cleanup(&req);
807 ret = uv_pipe_bind(&server_pipe, pipename);
808 if (ret) {
809 netdata_log_error("uv_pipe_bind(): %s", uv_strerror(ret));
810 command_thread_error = ret;
811 goto error_after_pipe_bind;
812 }
813
814 ret = uv_listen((uv_stream_t *)&server_pipe, SOMAXCONN, connection_cb);
815 if (ret) {
816 /* Fallback to backlog of 1 */
817 netdata_log_info("uv_listen() failed with backlog = %d, falling back to backlog = 1.", SOMAXCONN);
818 ret = uv_listen((uv_stream_t *)&server_pipe, 1, connection_cb);
819 }
820 if (ret) {
821 netdata_log_error("uv_listen(): %s", uv_strerror(ret));
822 command_thread_error = ret;
823 goto error_after_uv_listen;
824 }
825
826 command_thread_error = 0;
827 command_thread_shutdown = 0;
828 /* wake up initialization thread */
829 completion_mark_complete(&completion);
830
831 while (command_thread_shutdown == 0) {
832 uv_run(loop, UV_RUN_DEFAULT);
833 }
834 /* cleanup operations of the event loop */
835 netdata_log_info("Shutting down command event loop.");
836 uv_close((uv_handle_t *)&async, NULL);
837 uv_close((uv_handle_t*)&server_pipe, NULL);
838 uv_run(loop, UV_RUN_DEFAULT); /* flush all libuv handles */
839
840 netdata_log_info("Shutting down command loop complete.");
841 fatal_assert(0 == uv_loop_close(loop));
842 freez(loop);
843
844 return;
845
846 error_after_uv_listen:
847 error_after_pipe_bind:
848 uv_close((uv_handle_t*)&server_pipe, NULL);
849 error_after_pipe_init:
850 uv_close((uv_handle_t *)&async, NULL);
851 error_after_async_init:
852 uv_run(loop, UV_RUN_DEFAULT); /* flush all libuv handles */
853 fatal_assert(0 == uv_loop_close(loop));
854 error_after_loop_init:
855 freez(loop);
856
857 /* wake up initialization thread */
858 completion_mark_complete(&completion);
859 }
860
861 static void sanity_check(void)
862 {
863 /* The size of command_info_array must be CMD_TOTAL_COMMANDS elements */
864 BUILD_BUG_ON(CMD_TOTAL_COMMANDS != sizeof(command_info_array) / sizeof(command_info_array[0]));
865 }
866
867 void commands_init(void)
868 {
869 cmd_t i;
870 int error;
871
872 sanity_check();
873 if (command_server_initialized == CMD_INIT_STATUS_FULL)
874 return;
875
876 if (command_server_initialized == CMD_INIT_STATUS_OFF) {
877 netdata_log_info("Initializing command server for liveness CHECK");
878 command_server_initialized = CMD_INIT_STATUS_INIT;
879 }
880 else {
881 netdata_log_info("Initializing full command server.");
882 command_server_initialized = CMD_INIT_STATUS_FULL;
883 return;
884 }
885
886 for (i = 0 ; i < CMD_TOTAL_COMMANDS ; ++i) {
887 fatal_assert(0 == netdata_mutex_init(&command_lock_array[i]));
888 }
889 fatal_assert(0 == netdata_rwlock_init(&exclusive_rwlock));
890
891 completion_init(&completion);
892 error = uv_thread_create(&thread, command_thread, NULL);
893 if (error) {
894 netdata_log_error("uv_thread_create(): %s", uv_strerror(error));
895 goto after_error;
896 }
897 /* wait for worker thread to initialize */
898 completion_wait_for(&completion);
899 completion_destroy(&completion);
900
901 if (command_thread_error) {
902 error = uv_thread_join(&thread);
903 if (error) {
904 netdata_log_error("uv_thread_create(): %s", uv_strerror(error));
905 }
906 goto after_error;
907 }
908
909 return;
910
911 after_error:
912 netdata_log_error("Failed to initialize command server. The netdata cli tool will be unable to send commands.");
913 command_server_initialized = CMD_INIT_STATUS_OFF;
914 }
915
916 void commands_exit(void)
917 {
918 cmd_t i;
919
920 if (command_server_initialized == CMD_INIT_STATUS_OFF)
921 return;
922
923 command_thread_shutdown = 1;
924 netdata_log_info("Shutting down command server.");
925 /* wake up event loop */
926 fatal_assert(0 == uv_async_send(&async));
927 fatal_assert(0 == uv_thread_join(&thread));
928
929 for (i = 0 ; i < CMD_TOTAL_COMMANDS ; ++i) {
930 netdata_mutex_destroy(&command_lock_array[i]);
931 }
932 netdata_rwlock_destroy(&exclusive_rwlock);
933 netdata_log_info("Command server has stopped.");
934 command_server_initialized = CMD_INIT_STATUS_OFF;
935 }