From 953fcefc3a0b64e43059c7a447de8aa919052a62 Mon Sep 17 00:00:00 2001 From: Ygal Blum Date: Thu, 15 Jun 2023 14:28:29 +0300 Subject: [PATCH] hirtectl - implement getting remote unit status New hirtectl command - status node unit Add new the new file to the build and include it main - support status and add to usage expose create_message_new_method_call to allow internal usage string-util - add isempty update the man page Signed-off-by: Ygal Blum --- doc/man/hirtectl.1.md | 6 +- src/client/client.c | 10 +- src/client/client.h | 3 + src/client/meson.build | 3 +- src/client/method_status.c | 281 ++++++++++++++++++++++++++++++ src/client/method_status.h | 6 + src/libhirte/common/string-util.h | 4 + 7 files changed, 310 insertions(+), 3 deletions(-) create mode 100644 src/client/method_status.c create mode 100644 src/client/method_status.h diff --git a/doc/man/hirtectl.1.md b/doc/man/hirtectl.1.md index 8cdb24e334..90b6bf7a32 100644 --- a/doc/man/hirtectl.1.md +++ b/doc/man/hirtectl.1.md @@ -40,7 +40,7 @@ Creates a monitor on the given agent to observe changes in the specified units. ### **hirtectl** *monitor* *node-connection* -Creates a monitor to observe connection state changes for all nodes. +Creates a monitor to observe connection state changes for all nodes. **Example:** @@ -54,3 +54,7 @@ hirtectl monitor \\\* dbus.service,apache2.service ### **hirtectl** *daemon-reload* [*agent*] Performs `daemon-reload` for the `hirte-agent`. + +### **hirtectl** [*status*] [*agent*] [*unit1*,*...*] + +Fetches the status of the systemd units for the `hirte-agent`. diff --git a/src/client/client.c b/src/client/client.c index 54933a7df0..31d13d600d 100644 --- a/src/client/client.c +++ b/src/client/client.c @@ -10,6 +10,7 @@ #include "client.h" #include "method_list_units.h" #include "method_monitor.h" +#include "method_status.h" Client *new_client(char *op, int opargc, char **opargv, char *opt_filter_glob) { Client *client = malloc0(sizeof(Client)); @@ -302,7 +303,7 @@ int method_metrics_listen(Client *client) { return r; } -static int create_message_new_method_call( +int create_message_new_method_call( Client *client, const char *node_name, const char *member, sd_bus_message **new_message) { int r = 0; _cleanup_sd_bus_message_ sd_bus_message *outgoing_message = NULL; @@ -614,6 +615,11 @@ int client_call_manager(Client *client) { return -EINVAL; } r = method_daemon_reload_on(client, client->opargv[0]); + } else if (streq(client->op, "status")) { + if (client->opargc < 2) { + return -EINVAL; + } + r = method_status_unit_on(client, client->opargv[0], &client->opargv[1], client->opargc - 1); } else { return -EINVAL; } @@ -651,5 +657,7 @@ int print_client_usage(char *argv) { printf(" usage: monitor node-connection\n"); printf(" - daemon-reload: reload systemd daemon on a specific node\n"); printf(" usage: disable nodename\n"); + printf(" - status: get the status of systemd units on a specific node\n"); + printf(" usage: status nodename unitfilename...\n"); return 0; } diff --git a/src/client/client.h b/src/client/client.h index db07dacb32..c8978d5318 100644 --- a/src/client/client.h +++ b/src/client/client.h @@ -31,3 +31,6 @@ int print_client_usage(char *argv); DEFINE_CLEANUP_FUNC(Client, client_unref) #define _cleanup_client_ _cleanup_(client_unrefp) + +int create_message_new_method_call( + Client *client, const char *node_name, const char *member, sd_bus_message **new_message); diff --git a/src/client/meson.build b/src/client/meson.build index 5f6568168e..f4c7ddf4e0 100644 --- a/src/client/meson.build +++ b/src/client/meson.build @@ -6,7 +6,8 @@ client_src = [ 'main.c', 'client.c', 'method_monitor.c', - 'method_list_units.c' + 'method_list_units.c', + 'method_status.c' ] executable( diff --git a/src/client/method_status.c b/src/client/method_status.c new file mode 100644 index 0000000000..cb44a895b2 --- /dev/null +++ b/src/client/method_status.c @@ -0,0 +1,281 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#include "libhirte/bus/utils.h" +#include "libhirte/common/common.h" + +#include "client.h" +#include "method_status.h" + +typedef struct unit_info_t { + const char *id; + const char *load_state; + const char *active_state; + const char *sub_state; + const char *unit_file_state; +} unit_info_t; + +struct bus_properties_map { + const char *member; + size_t offset; +}; + +static const struct bus_properties_map property_map[] = { + { "Id", offsetof(unit_info_t, id) }, + { "LoadState", offsetof(unit_info_t, load_state) }, + { "ActiveState", offsetof(unit_info_t, active_state) }, + { "SubState", offsetof(unit_info_t, sub_state) }, + { "UnitFileState", offsetof(unit_info_t, unit_file_state) }, + {} +}; + +static int get_property_map(sd_bus_message *m, const struct bus_properties_map **prop) { + int r = 0; + const char *member = NULL; + int i = 0; + + r = sd_bus_message_read_basic(m, SD_BUS_TYPE_STRING, &member); + if (r < 0) { + fprintf(stderr, "Failed to read the name of the property: %s\n", strerror(-r)); + return r; + } + + for (i = 0; property_map[i].member; i++) { + if (streq(property_map[i].member, member)) { + *prop = &property_map[i]; + break; + } + } + + return 0; +} + +static int read_property_value(sd_bus_message *m, const struct bus_properties_map *prop, unit_info_t *unit_info) { + int r = 0; + char type = 0; + const char *contents = NULL; + const char *s = NULL; + const char **v = (const char **) ((uint8_t *) unit_info + prop->offset); + + r = sd_bus_message_peek_type(m, NULL, &contents); + if (r < 0) { + fprintf(stderr, "Failed to peek into the type of the property: %s\n", strerror(-r)); + return r; + } + + r = sd_bus_message_enter_container(m, SD_BUS_TYPE_VARIANT, contents); + if (r < 0) { + fprintf(stderr, "Failed to enter into the container of the property: %s\n", strerror(-r)); + return r; + } + + r = sd_bus_message_peek_type(m, &type, NULL); + if (r < 0) { + fprintf(stderr, "Failed to get the type of the property: %s\n", strerror(-r)); + return r; + } + + if (type != SD_BUS_TYPE_STRING) { + fprintf(stderr, "Currently only string types are expected\n"); + return -EINVAL; + } + + r = sd_bus_message_read_basic(m, type, &s); + if (r < 0) { + fprintf(stderr, "Failed to get the value of the property: %s\n", strerror(-r)); + return r; + } + + if (isempty(s)) { + s = NULL; + } + + *v = s; + + r = sd_bus_message_exit_container(m); + if (r < 0) { + fprintf(stderr, "Failed to exit from the container of the property: %s\n", strerror(-r)); + } + + return r; +} + +static int process_dict_entry(sd_bus_message *m, unit_info_t *unit_info) { + int r = 0; + const struct bus_properties_map *prop = NULL; + + r = get_property_map(m, &prop); + if (r < 0) { + return r; + } + + if (prop) { + r = read_property_value(m, prop, unit_info); + if (r < 0) { + fprintf(stderr, + "Failed to get the value for member %s - %s\n", + prop->member, + strerror(-r)); + } + } else { + r = sd_bus_message_skip(m, "v"); + if (r < 0) { + fprintf(stderr, "Failed to skip the property container: %s\n", strerror(-r)); + } + } + + return r; +} + +static int parse_unit_status_response_from_message(sd_bus_message *m, unit_info_t *unit_info) { + int r = 0; + + r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "{sv}"); + if (r < 0) { + fprintf(stderr, "Failed to open the strings array container: %s\n", strerror(-r)); + return r; + } + + for (;;) { + r = sd_bus_message_enter_container(m, SD_BUS_TYPE_DICT_ENTRY, "sv"); + if (r < 0) { + fprintf(stderr, "Failed to enter the property container: %s\n", strerror(-r)); + return r; + } + + if (r == 0) { + break; + } + + r = process_dict_entry(m, unit_info); + if (r < 0) { + break; + } + + r = sd_bus_message_exit_container(m); + if (r < 0) { + fprintf(stderr, "Failed to exit the property container: %s\n", strerror(-r)); + break; + } + } + + return r; +} + +#define PRINT_TAB_SIZE 8 +static void print_info_header(unsigned long name_col_width) { + unsigned long i = 0; + + fprintf(stderr, "UNIT"); + for (i = name_col_width; i > 0; i -= PRINT_TAB_SIZE) { + fprintf(stderr, "\t"); + } + + fprintf(stderr, "| LOADED\t| ACTIVE\t| SUBSTATE\t| ENABLED\t|\n"); + for (i = name_col_width; i > 0; i -= PRINT_TAB_SIZE) { + fprintf(stderr, "--------"); + } + fprintf(stderr, "----------------"); + fprintf(stderr, "----------------"); + fprintf(stderr, "----------------"); + fprintf(stderr, "----------------\n"); +} + +#define PRINT_AND_ALIGN(x) \ + do { \ + fprintf(stderr, "| %s\t", unit_info->x); \ + if (!unit_info->x || strlen(unit_info->x) < 6) { \ + fprintf(stderr, "\t"); \ + } \ + } while (0) + + +static void print_unit_info(unit_info_t *unit_info, unsigned long name_col_width) { + unsigned long i = 0; + + if (unit_info->load_state && streq(unit_info->load_state, "not-found")) { + fprintf(stderr, "Unit %s could not be found.\n", unit_info->id); + return; + } + + fprintf(stderr, "%s", unit_info->id); + name_col_width -= unit_info->id ? strlen(unit_info->id) : 0; + name_col_width += PRINT_TAB_SIZE; + for (i = name_col_width; i > 0; i -= PRINT_TAB_SIZE) { + fprintf(stderr, "\t"); + } + + PRINT_AND_ALIGN(load_state); + PRINT_AND_ALIGN(active_state); + PRINT_AND_ALIGN(sub_state); + PRINT_AND_ALIGN(unit_file_state); + + fprintf(stderr, "|\n"); +} + +static unsigned long get_max_name_len(char **units, size_t units_count) { + size_t i = 0; + unsigned long max_unit_name_len = 0; + + for (i = 0; i < units_count; i++) { + unsigned long unit_name_len = strlen(units[i]); + max_unit_name_len = max_unit_name_len > unit_name_len ? max_unit_name_len : unit_name_len; + } + + return max_unit_name_len; +} + +static int get_status_unit_on(Client *client, char *node_name, char *unit_name, unsigned long name_col_width) { + int r = 0; + _cleanup_sd_bus_error_ sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_sd_bus_message_ sd_bus_message *result = NULL; + _cleanup_sd_bus_message_ sd_bus_message *outgoing_message = NULL; + unit_info_t unit_info = { 0 }; + + r = create_message_new_method_call(client, node_name, "GetUnitProperties", &outgoing_message); + if (r < 0) { + fprintf(stderr, "Failed to create a new message: %s\n", strerror(-r)); + return r; + } + + r = sd_bus_message_append(outgoing_message, "ss", unit_name, ""); + if (r < 0) { + fprintf(stderr, "Failed to append runtime to the message: %s\n", strerror(-r)); + return r; + } + + r = sd_bus_call(client->api_bus, outgoing_message, HIRTE_DEFAULT_DBUS_TIMEOUT, &error, &result); + if (r < 0) { + fprintf(stderr, "Failed to issue call: %s\n", error.message); + return r; + } + + r = parse_unit_status_response_from_message(result, &unit_info); + if (r < 0) { + fprintf(stderr, "Failed to parse the response strings array: %s\n", error.message); + return r; + } + + print_unit_info(&unit_info, name_col_width); + + return 0; +} + +int method_status_unit_on(Client *client, char *node_name, char **units, size_t units_count) { + unsigned i = 0; + + unsigned long max_name_len = get_max_name_len(units, units_count); + + print_info_header(max_name_len); + for (i = 0; i < units_count; i++) { + int r = get_status_unit_on(client, node_name, units[i], max_name_len); + if (r < 0) { + fprintf(stderr, + "Failed to get status of unit %s on node %s - %s", + units[i], + node_name, + strerror(-r)); + return r; + } + } + + return 0; +} diff --git a/src/client/method_status.h b/src/client/method_status.h new file mode 100644 index 0000000000..31ffefa945 --- /dev/null +++ b/src/client/method_status.h @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#include + +#include "types.h" + +int method_status_unit_on(Client *client, char *node_name, char **units, size_t units_count); \ No newline at end of file diff --git a/src/libhirte/common/string-util.h b/src/libhirte/common/string-util.h index 831b2ee2c9..bf8d30edbe 100644 --- a/src/libhirte/common/string-util.h +++ b/src/libhirte/common/string-util.h @@ -70,3 +70,7 @@ static inline bool match_glob(const char *str, const char *glob) { return !*glob; } // NOLINTEND(misc-no-recursion) + +static inline bool isempty(const char *a) { + return !a || a[0] == '\0'; +}