0
0
Fork 0
mirror of https://github.com/netdata/netdata.git synced 2025-04-16 18:37:50 +00:00
netdata_netdata/daemon/commands.c
Andrew Moss fe722cb2a4
Improve the behavior of claiming ()
The default cloud url has been updated to app.netdata.cloud ready for the release. The claiming process now checks the current user executing claiming and refuses to perform the claim for the wrong user. If the current UID is 0 then claiming proceeds but the file ownership is adjusted to be the correct netdata user. The default expected user is `netdata` unless the script can identify the user from the current configuration. After the claiming script is executed the CLI is used to reload the claiming state.
2020-03-31 13:07:24 +02:00

633 lines
18 KiB
C

// SPDX-License-Identifier: GPL-3.0-or-later
#include "common.h"
#include "../database/engine/rrdenginelib.h"
static uv_thread_t thread;
static uv_loop_t* loop;
static uv_async_t async;
static struct completion completion;
static uv_pipe_t server_pipe;
char cmd_prefix_by_status[] = {
CMD_PREFIX_INFO,
CMD_PREFIX_ERROR,
CMD_PREFIX_ERROR
};
static int command_server_initialized = 0;
static int command_thread_error;
static int command_thread_shutdown;
static unsigned clients = 0;
struct command_context {
/* embedded client pipe structure at address 0 */
uv_pipe_t client;
uv_work_t work;
uv_write_t write_req;
cmd_t idx;
char *args;
char *message;
cmd_status_t status;
char command_string[MAX_COMMAND_LENGTH];
unsigned command_string_size;
};
/* Forward declarations */
static cmd_status_t cmd_help_execute(char *args, char **message);
static cmd_status_t cmd_reload_health_execute(char *args, char **message);
static cmd_status_t cmd_save_database_execute(char *args, char **message);
static cmd_status_t cmd_reopen_logs_execute(char *args, char **message);
static cmd_status_t cmd_exit_execute(char *args, char **message);
static cmd_status_t cmd_fatal_execute(char *args, char **message);
static cmd_status_t cmd_reload_claiming_state_execute(char *args, char **message);
static cmd_status_t cmd_reload_labels_execute(char *args, char **message);
static command_info_t command_info_array[] = {
{"help", cmd_help_execute, CMD_TYPE_HIGH_PRIORITY}, // show help menu
{"reload-health", cmd_reload_health_execute, CMD_TYPE_ORTHOGONAL}, // reload health configuration
{"save-database", cmd_save_database_execute, CMD_TYPE_ORTHOGONAL}, // save database for memory mode save
{"reopen-logs", cmd_reopen_logs_execute, CMD_TYPE_ORTHOGONAL}, // Close and reopen log files
{"shutdown-agent", cmd_exit_execute, CMD_TYPE_EXCLUSIVE}, // exit cleanly
{"fatal-agent", cmd_fatal_execute, CMD_TYPE_HIGH_PRIORITY}, // exit with fatal error
{"reload-claiming-state", cmd_reload_claiming_state_execute, CMD_TYPE_ORTHOGONAL}, // reload claiming state
{"reload-labels", cmd_reload_labels_execute, CMD_TYPE_ORTHOGONAL}, // reload the labels
};
/* Mutexes for commands of type CMD_TYPE_ORTHOGONAL */
static uv_mutex_t command_lock_array[CMD_TOTAL_COMMANDS];
/* Commands of type CMD_TYPE_EXCLUSIVE are writers */
static uv_rwlock_t exclusive_rwlock;
/*
* Locking order:
* 1. exclusive_rwlock
* 2. command_lock_array[]
*/
/* Forward declarations */
static void cmd_lock_exclusive(unsigned index);
static void cmd_lock_orthogonal(unsigned index);
static void cmd_lock_idempotent(unsigned index);
static void cmd_lock_high_priority(unsigned index);
static command_lock_t *cmd_lock_by_type[] = {
cmd_lock_exclusive,
cmd_lock_orthogonal,
cmd_lock_idempotent,
cmd_lock_high_priority
};
/* Forward declarations */
static void cmd_unlock_exclusive(unsigned index);
static void cmd_unlock_orthogonal(unsigned index);
static void cmd_unlock_idempotent(unsigned index);
static void cmd_unlock_high_priority(unsigned index);
static command_lock_t *cmd_unlock_by_type[] = {
cmd_unlock_exclusive,
cmd_unlock_orthogonal,
cmd_unlock_idempotent,
cmd_unlock_high_priority
};
static cmd_status_t cmd_help_execute(char *args, char **message)
{
(void)args;
*message = mallocz(MAX_COMMAND_LENGTH);
strncpyz(*message,
"\nThe commands are (arguments are in brackets):\n"
"help\n"
" Show this help menu.\n"
"reload-health\n"
" Reload health configuration.\n"
"reload-labels\n"
" Reload all labels.\n"
"save-database\n"
" Save internal DB to disk for memory mode save.\n"
"reopen-logs\n"
" Close and reopen log files.\n"
"shutdown-agent\n"
" Cleanup and exit the netdata agent.\n"
"fatal-agent\n"
" Log the state and halt the netdata agent.\n"
"reload-claiming-state\n"
" Reload agent claiming state from disk.\n",
MAX_COMMAND_LENGTH - 1);
return CMD_STATUS_SUCCESS;
}
static cmd_status_t cmd_reload_health_execute(char *args, char **message)
{
(void)args;
(void)message;
error_log_limit_unlimited();
info("COMMAND: Reloading HEALTH configuration.");
health_reload();
error_log_limit_reset();
return CMD_STATUS_SUCCESS;
}
static cmd_status_t cmd_save_database_execute(char *args, char **message)
{
(void)args;
(void)message;
error_log_limit_unlimited();
info("COMMAND: Saving databases.");
rrdhost_save_all();
info("COMMAND: Databases saved.");
error_log_limit_reset();
return CMD_STATUS_SUCCESS;
}
static cmd_status_t cmd_reopen_logs_execute(char *args, char **message)
{
(void)args;
(void)message;
error_log_limit_unlimited();
info("COMMAND: Reopening all log files.");
reopen_all_log_files();
error_log_limit_reset();
return CMD_STATUS_SUCCESS;
}
static cmd_status_t cmd_exit_execute(char *args, char **message)
{
(void)args;
(void)message;
error_log_limit_unlimited();
info("COMMAND: Cleaning up to exit.");
netdata_cleanup_and_exit(0);
exit(0);
return CMD_STATUS_SUCCESS;
}
static cmd_status_t cmd_fatal_execute(char *args, char **message)
{
(void)args;
(void)message;
fatal("COMMAND: netdata now exits.");
return CMD_STATUS_SUCCESS;
}
static cmd_status_t cmd_reload_claiming_state_execute(char *args, char **message)
{
(void)args;
(void)message;
#ifndef ENABLE_CLOUD
info("The claiming feature has been disabled");
return CMD_STATUS_FAILURE;
#endif
#ifndef ENABLE_ACLK
info("Cloud functionality is not enabled because of missing dependencies at build-time.");
return CMD_STATUS_FAILURE;
#endif
error_log_limit_unlimited();
info("COMMAND: Reloading Agent Claiming configuration.");
load_claiming_state();
error_log_limit_reset();
return CMD_STATUS_SUCCESS;
}
static cmd_status_t cmd_reload_labels_execute(char *args, char **message)
{
(void)args;
info("COMMAND: reloading host labels.");
reload_host_labels();
BUFFER *wb = buffer_create(10);
rrdhost_rdlock(localhost);
netdata_rwlock_rdlock(&localhost->labels_rwlock);
struct label *l=localhost->labels;
while (l != NULL) {
buffer_sprintf(wb,"Label [source id=%s]: \"%s\" -> \"%s\"\n", translate_label_source(l->label_source), l->key, l->value);
l = l->next;
}
netdata_rwlock_unlock(&localhost->labels_rwlock);
rrdhost_unlock(localhost);
(*message)=strdupz(buffer_tostring(wb));
buffer_free(wb);
return CMD_STATUS_SUCCESS;
}
static void cmd_lock_exclusive(unsigned index)
{
(void)index;
uv_rwlock_wrlock(&exclusive_rwlock);
}
static void cmd_lock_orthogonal(unsigned index)
{
uv_rwlock_rdlock(&exclusive_rwlock);
uv_mutex_lock(&command_lock_array[index]);
}
static void cmd_lock_idempotent(unsigned index)
{
(void)index;
uv_rwlock_rdlock(&exclusive_rwlock);
}
static void cmd_lock_high_priority(unsigned index)
{
(void)index;
}
static void cmd_unlock_exclusive(unsigned index)
{
(void)index;
uv_rwlock_wrunlock(&exclusive_rwlock);
}
static void cmd_unlock_orthogonal(unsigned index)
{
uv_rwlock_rdunlock(&exclusive_rwlock);
uv_mutex_unlock(&command_lock_array[index]);
}
static void cmd_unlock_idempotent(unsigned index)
{
(void)index;
uv_rwlock_rdunlock(&exclusive_rwlock);
}
static void cmd_unlock_high_priority(unsigned index)
{
(void)index;
}
static void pipe_close_cb(uv_handle_t* handle)
{
/* Also frees command context */
freez(handle);
}
static void pipe_write_cb(uv_write_t* req, int status)
{
(void)status;
uv_pipe_t *client = req->data;
uv_close((uv_handle_t *)client, pipe_close_cb);
--clients;
info("Command Clients = %u\n", clients);
}
static inline void add_char_to_command_reply(char *reply_string, unsigned *reply_string_size, char character)
{
reply_string[(*reply_string_size)++] = character;
}
static inline void add_string_to_command_reply(char *reply_string, unsigned *reply_string_size, char *str)
{
unsigned len;
len = strlen(str);
strncpyz(reply_string + *reply_string_size, str, len);
*reply_string_size += len;
}
static void send_command_reply(struct command_context *cmd_ctx, cmd_status_t status, char *message)
{
int ret;
char reply_string[MAX_COMMAND_LENGTH] = {'\0', };
char exit_status_string[MAX_EXIT_STATUS_LENGTH + 1] = {'\0', };
unsigned reply_string_size = 0;
uv_buf_t write_buf;
uv_stream_t *client = (uv_stream_t *)(uv_pipe_t *)cmd_ctx;
snprintfz(exit_status_string, MAX_EXIT_STATUS_LENGTH, "%u", status);
add_char_to_command_reply(reply_string, &reply_string_size, CMD_PREFIX_EXIT_CODE);
add_string_to_command_reply(reply_string, &reply_string_size, exit_status_string);
add_char_to_command_reply(reply_string, &reply_string_size, '\0');
if (message) {
add_char_to_command_reply(reply_string, &reply_string_size, cmd_prefix_by_status[status]);
add_string_to_command_reply(reply_string, &reply_string_size, message);
}
cmd_ctx->write_req.data = client;
write_buf.base = reply_string;
write_buf.len = reply_string_size;
ret = uv_write(&cmd_ctx->write_req, (uv_stream_t *)client, &write_buf, 1, pipe_write_cb);
if (ret) {
error("uv_write(): %s", uv_strerror(ret));
}
info("COMMAND: Sending reply: \"%s\"", reply_string);
}
cmd_status_t execute_command(cmd_t idx, char *args, char **message)
{
cmd_status_t status;
cmd_type_t type = command_info_array[idx].type;
cmd_lock_by_type[type](idx);
status = command_info_array[idx].func(args, message);
cmd_unlock_by_type[type](idx);
return status;
}
static void after_schedule_command(uv_work_t *req, int status)
{
struct command_context *cmd_ctx = req->data;
(void)status;
send_command_reply(cmd_ctx, cmd_ctx->status, cmd_ctx->message);
if (cmd_ctx->message)
freez(cmd_ctx->message);
}
static void schedule_command(uv_work_t *req)
{
struct command_context *cmd_ctx = req->data;
cmd_ctx->status = execute_command(cmd_ctx->idx, cmd_ctx->args, &cmd_ctx->message);
}
static void parse_commands(struct command_context *cmd_ctx)
{
char *message = NULL, *pos;
cmd_t i;
cmd_status_t status;
status = CMD_STATUS_FAILURE;
/* Skip white-space characters */
for (pos = cmd_ctx->command_string ; isspace(*pos) && ('\0' != *pos) ; ++pos) {;}
for (i = 0 ; i < CMD_TOTAL_COMMANDS ; ++i) {
if (!strncmp(pos, command_info_array[i].cmd_str, strlen(command_info_array[i].cmd_str))) {
cmd_ctx->work.data = cmd_ctx;
cmd_ctx->idx = i;
cmd_ctx->args = pos + strlen(command_info_array[i].cmd_str);
cmd_ctx->message = NULL;
assert(0 == uv_queue_work(loop, &cmd_ctx->work, schedule_command, after_schedule_command));
break;
}
}
if (CMD_TOTAL_COMMANDS == i) {
/* no command found */
message = strdupz("Illegal command. Please type \"help\" for instructions.");
send_command_reply(cmd_ctx, status, message);
freez(message);
}
}
static void pipe_read_cb(uv_stream_t *client, ssize_t nread, const uv_buf_t *buf)
{
struct command_context *cmd_ctx = (struct command_context *)client;
if (0 == nread) {
info("%s: Zero bytes read by command pipe.", __func__);
} else if (UV_EOF == nread) {
info("EOF found in command pipe.");
parse_commands(cmd_ctx);
} else if (nread < 0) {
error("%s: %s", __func__, uv_strerror(nread));
}
if (nread < 0) { /* stop stream due to EOF or error */
(void)uv_read_stop((uv_stream_t *)client);
} else if (nread) {
size_t to_copy;
to_copy = MIN(nread, MAX_COMMAND_LENGTH - 1 - cmd_ctx->command_string_size);
memcpy(cmd_ctx->command_string + cmd_ctx->command_string_size, buf->base, to_copy);
cmd_ctx->command_string_size += to_copy;
cmd_ctx->command_string[cmd_ctx->command_string_size] = '\0';
}
if (buf && buf->len) {
freez(buf->base);
}
if (nread < 0 && UV_EOF != nread) {
uv_close((uv_handle_t *)client, pipe_close_cb);
--clients;
info("Command Clients = %u\n", clients);
}
}
static void alloc_cb(uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf)
{
(void)handle;
buf->base = mallocz(suggested_size);
buf->len = suggested_size;
}
static void connection_cb(uv_stream_t *server, int status)
{
int ret;
uv_pipe_t *client;
struct command_context *cmd_ctx;
assert(status == 0);
/* combined allocation of client pipe and command context */
cmd_ctx = mallocz(sizeof(*cmd_ctx));
client = (uv_pipe_t *)cmd_ctx;
ret = uv_pipe_init(server->loop, client, 1);
if (ret) {
error("uv_pipe_init(): %s", uv_strerror(ret));
freez(cmd_ctx);
return;
}
ret = uv_accept(server, (uv_stream_t *)client);
if (ret) {
error("uv_accept(): %s", uv_strerror(ret));
uv_close((uv_handle_t *)client, pipe_close_cb);
return;
}
++clients;
info("Command Clients = %u\n", clients);
/* Start parsing a new command */
cmd_ctx->command_string_size = 0;
cmd_ctx->command_string[0] = '\0';
ret = uv_read_start((uv_stream_t*)client, alloc_cb, pipe_read_cb);
if (ret) {
error("uv_read_start(): %s", uv_strerror(ret));
uv_close((uv_handle_t *)client, pipe_close_cb);
--clients;
info("Command Clients = %u\n", clients);
return;
}
}
static void async_cb(uv_async_t *handle)
{
uv_stop(handle->loop);
}
static void command_thread(void *arg)
{
int ret;
uv_fs_t req;
(void) arg;
loop = mallocz(sizeof(uv_loop_t));
ret = uv_loop_init(loop);
if (ret) {
error("uv_loop_init(): %s", uv_strerror(ret));
command_thread_error = ret;
goto error_after_loop_init;
}
loop->data = NULL;
ret = uv_async_init(loop, &async, async_cb);
if (ret) {
error("uv_async_init(): %s", uv_strerror(ret));
command_thread_error = ret;
goto error_after_async_init;
}
async.data = NULL;
ret = uv_pipe_init(loop, &server_pipe, 0);
if (ret) {
error("uv_pipe_init(): %s", uv_strerror(ret));
command_thread_error = ret;
goto error_after_pipe_init;
}
(void)uv_fs_unlink(loop, &req, PIPENAME, NULL);
uv_fs_req_cleanup(&req);
ret = uv_pipe_bind(&server_pipe, PIPENAME);
if (ret) {
error("uv_pipe_bind(): %s", uv_strerror(ret));
command_thread_error = ret;
goto error_after_pipe_bind;
}
ret = uv_listen((uv_stream_t *)&server_pipe, SOMAXCONN, connection_cb);
if (ret) {
/* Fallback to backlog of 1 */
info("uv_listen() failed with backlog = %d, falling back to backlog = 1.", SOMAXCONN);
ret = uv_listen((uv_stream_t *)&server_pipe, 1, connection_cb);
}
if (ret) {
error("uv_listen(): %s", uv_strerror(ret));
command_thread_error = ret;
goto error_after_uv_listen;
}
command_thread_error = 0;
command_thread_shutdown = 0;
/* wake up initialization thread */
complete(&completion);
while (command_thread_shutdown == 0) {
uv_run(loop, UV_RUN_DEFAULT);
}
/* cleanup operations of the event loop */
info("Shutting down command event loop.");
uv_close((uv_handle_t *)&async, NULL);
uv_close((uv_handle_t*)&server_pipe, NULL);
uv_run(loop, UV_RUN_DEFAULT); /* flush all libuv handles */
info("Shutting down command loop complete.");
assert(0 == uv_loop_close(loop));
freez(loop);
return;
error_after_uv_listen:
error_after_pipe_bind:
uv_close((uv_handle_t*)&server_pipe, NULL);
error_after_pipe_init:
uv_close((uv_handle_t *)&async, NULL);
error_after_async_init:
uv_run(loop, UV_RUN_DEFAULT); /* flush all libuv handles */
assert(0 == uv_loop_close(loop));
error_after_loop_init:
freez(loop);
/* wake up initialization thread */
complete(&completion);
}
static void sanity_check(void)
{
/* The size of command_info_array must be CMD_TOTAL_COMMANDS elements */
BUILD_BUG_ON(CMD_TOTAL_COMMANDS != sizeof(command_info_array) / sizeof(command_info_array[0]));
}
void commands_init(void)
{
cmd_t i;
int error;
sanity_check();
if (command_server_initialized)
return;
info("Initializing command server.");
for (i = 0 ; i < CMD_TOTAL_COMMANDS ; ++i) {
uv_mutex_init(&command_lock_array[i]);
}
assert(0 == uv_rwlock_init(&exclusive_rwlock));
init_completion(&completion);
error = uv_thread_create(&thread, command_thread, NULL);
if (error) {
error("uv_thread_create(): %s", uv_strerror(error));
goto after_error;
}
/* wait for worker thread to initialize */
wait_for_completion(&completion);
destroy_completion(&completion);
uv_thread_set_name_np(thread, "DAEMON_COMMAND");
if (command_thread_error) {
error = uv_thread_join(&thread);
if (error) {
error("uv_thread_create(): %s", uv_strerror(error));
}
goto after_error;
}
command_server_initialized = 1;
return;
after_error:
error("Failed to initialize command server. The netdata cli tool will be unable to send commands.");
}
void commands_exit(void)
{
cmd_t i;
if (!command_server_initialized)
return;
command_thread_shutdown = 1;
info("Shutting down command server.");
/* wake up event loop */
assert(0 == uv_async_send(&async));
assert(0 == uv_thread_join(&thread));
for (i = 0 ; i < CMD_TOTAL_COMMANDS ; ++i) {
uv_mutex_destroy(&command_lock_array[i]);
}
uv_rwlock_destroy(&exclusive_rwlock);
info("Command server has stopped.");
command_server_initialized = 0;
}