#include <malloc.h>
#include <errno.h>
#include <string.h>
+#include <systemd/sd-bus.h>
#include "decision_made_event.h"
#include "action.h"
#include "common.h"
#include "database.h"
#include "json-config.h"
+#include "systemd_dbus.h"
#include "unit_control_event.h"
struct list_head node;
};
+struct unit_info {
+ char *unit;
+ struct list_head node;
+};
+
static int unit_control_event_match(struct epc_event_handler *handler,
struct epc_event *ev)
{
return 0;
}
+static int list_unit_by_pattern(const char *pattern, sd_bus_message **reply)
+{
+ assert(pattern);
+
+ __attribute__((cleanup(sd_bus_message_unrefp))) sd_bus_message *m = NULL;
+ __attribute__((cleanup(sd_bus_error_free))) sd_bus_error error = SD_BUS_ERROR_NULL;
+ int ret;
+ sd_bus *bus = NULL;
+ char *prefix = NULL;
+ char *joined = NULL;
+
+ char *patterns[2] = {};
+
+ char *unit_states[] = {
+ "running",
+ NULL
+ };
+
+ ret = epc_acquire_systemd_bus(&bus);
+ if (ret < 0) {
+ log_error_errno(ret, "failed to acquire the default system bus connection: %m");
+ return -EINVAL;
+ }
+
+ ret = sd_bus_message_new_method_call(
+ bus,
+ &m,
+ SYSTEMD_SERVICE,
+ SYSTEMD_OBJ,
+ SYSTEMD_MANAGER_INTERFACE,
+ "ListUnitsByPatterns");
+
+ if (ret < 0) {
+ log_error_errno(ret, "failed to issue ListUnitsByPatterns() call - %m");
+ return ret;
+ }
+
+ ret = sd_bus_message_append_strv(m, (char **)unit_states);
+ if (ret < 0) {
+ log_error_errno(ret, "failed to to setup arg 1 for ListUnitsByPatterns() call - %m");
+ return ret;
+ }
+
+ patterns[0] = (char *)pattern;
+ ret = sd_bus_message_append_strv(m, patterns);
+ if (ret < 0) {
+ log_error_errno(ret, "failed to to setup arg 2 for ListUnitsByPatterns() call - %m");
+ goto cleanup;
+ }
+
+ ret = sd_bus_call(bus, m, 0, &error, reply);
+ if (ret < 0) {
+ log_error("failed to issue ListUnitsByPatterns() call: %s\n", error.message);
+ goto cleanup;
+ }
+
+ return 0;
+cleanup:
+ free(prefix);
+ free(joined);
+ return ret;
+}
+
+static int parse_unit_info(sd_bus_message *m, char **name)
+{
+ assert(m);
+
+ char *id;
+ int ret;
+
+ ret = sd_bus_message_read(
+ m,
+ "(ssssssouso)",
+ &id,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL
+ );
+
+ if (ret < 0) {
+ log_error_errno(ret, " failed to fetch message contents - %m");
+ return ret;
+ }
+
+ *name = id;
+ return ret;
+}
+
+static int parse_unit_list(sd_bus_message *reply, struct list_head *units)
+{
+ assert(reply);
+ assert(units);
+
+ int ret = 0;
+ char *name;
+ struct unit_info *u;
+
+ ret = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssssssouso)");
+ if (ret < 0) {
+ log_error_errno(ret, "failed to parse ListUnitsByPatterns() response - %m");
+ return ret;
+ }
+
+ while ((ret = parse_unit_info(reply, &name)) > 0) {
+ u = calloc(1, sizeof(*u));
+ if (!u) {
+ log_error("Could not allocate unit_info object");
+ return -ENOMEM;
+ }
+ u->unit = strdup(name);
+ if (!u->unit)
+ return -ENOMEM;
+
+ list_add_tail(&u->node, units);
+ }
+
+ if (ret < 0) {
+ log_error_errno(ret, "error parsing unit info - %m");
+ return ret;
+ }
+
+ ret = sd_bus_message_exit_container(reply);
+ if (ret < 0) {
+ log_error_errno(ret, "failed to clanup parsing unit info - %m");
+ return ret;
+ }
+
+ return ret;
+}
+
static int unit_control_execute(const char *unit, const char *command, struct epc_event *ev)
{
+ assert(unit);
+ assert(command);
+ assert(ev);
+
struct dm_event_data ev_data = {
.reason = ev,
.who_made = MODULE_NAME,
struct epc_event *new_ev;
int ret;
- assert(unit);
- assert(command);
- assert(ev);
-
ev_data.action = EPC_ACTION_UNIT_START_ID;
ret = epc_object_new(&ev_data.action_data);
if (ret < 0) {
- log_error("Could not create data object for action");
+ log_error_errno(ret, "could not create data object for action - %m");
goto out;
}
ret = epc_fill_for_unit_start(ev_data.action_data, unit);
if (ret) {
- log_error("Unable to create event data");
+ log_error_errno(ret, "unable to create event data - %m");
goto out;
}
epc_fill_for_unit_action(ev_data.action_data, "RestartUnit");
else {
ret = -EINVAL;
- log_error("Could not dispatch action: %s", command);
+ log_error_errno(ret, "could not dispatch action: %s - %m", command);
goto out;
}
ret = event_processor_report_event(new_ev);
epc_event_unref(new_ev);
if (ret) {
- log_error("Unable to report event");
+ log_error_errno(ret, "unable to report event - %m");
goto out;
}
return ret;
}
+static int unit_control_query_and_execute(const char *pattern, const char *command, struct epc_event *ev)
+{
+ assert(pattern);
+ assert(command);
+ assert(ev);
+
+ __attribute__((cleanup(sd_bus_message_unrefp))) sd_bus_message *reply = NULL;
+ int ret, error_code = 0;
+ struct list_head unit_list;
+ struct unit_info *u, *next;
+
+ INIT_LIST_HEAD(&unit_list);
+
+ ret = list_unit_by_pattern(pattern, &reply);
+ if (ret < 0)
+ return ret;
+ ret = parse_unit_list(reply, &unit_list);
+ if (ret < 0)
+ goto cleanup;
+
+ list_for_each_entry_safe(u, next, &unit_list, node) {
+ log_debug("execute action %s on unit %s", command, u->unit);
+ ret = unit_control_execute(u->unit, command, ev);
+ if (ret < 0)
+ error_code = ret;
+ free(u->unit);
+ list_del(&u->node);
+ }
+ if (error_code != 0)
+ return error_code;
+
+ return 0;
+cleanup:
+ list_for_each_entry_safe(u, next, &unit_list, node) {
+ free(u->unit);
+ list_del(&u->node);
+ }
+ return ret;
+}
+
static int match_unit(const char *unit_name, const char *unit_whitelist_rule)
{
+ assert(unit_name);
+ assert(unit_whitelist_rule);
+
char *rule_suf, *unit_suf;
rule_suf = strrchr(unit_whitelist_rule, '.');
return 0;
}
+/*
+ * Assuming method is not 'start', in both cases:
+ * Request: Whitelist entry:
+ * ---------------------------------------
+ * name@instance.service name@.service
+ * name@.service name@instance.service
+ *
+ * the right action to execute is equivalent to:
+ * systemctl stop|restart name@instance.service
+ *
+ * So in any case we need to pick the most precise name.
+ * Comparing string lengths seems quick and easy way to do this.
+ *
+ * Next thing is, if the proper name is a wildcard, e.g. name@.service,
+ * the way of sending a method call is to add a '*' to the service name:
+ * name@*.service
+ *
+ * User needs to free returned pointer.
+ * */
+char *construct_proper_service_name(char *requested_name, char *whitelist_name)
+{
+ assert(requested_name);
+ assert(whitelist_name);
+
+ char *name, *ret, *at;
+ if (strlen(requested_name) > strlen(whitelist_name))
+ name = requested_name;
+ else
+ name = whitelist_name;
+
+ at = strstr(name, "@.");
+
+ /* add '*' if wildcard */
+ if (at) {
+ at++; /* move the pointer after '@' */
+ ret = calloc(1, strlen(name)+ 2); /* one for \0 and one for additional asterisk*/
+ if (!ret)
+ return NULL;
+ strncpy(ret, name, at - name);
+ sprintf(ret + (at - name), "*%s", at);
+ } else {
+ ret = strdup(name);
+ }
+
+ return ret;
+}
+
static int unit_control_make_decision(struct epc_event_handler *handler)
{
+ assert(handler);
+
struct epc_event *event = pop_epc_event(&handler->event_queue);
struct unit_control_decision_maker *dm = container_of(handler,
struct unit_control_decision_maker, eh);
struct unit_control_event *ev = to_unit_control_event(event);
- int ret;
+ int ret = -EACCES;
+ int error_code = 0;
struct whitelist_entry *w;
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ char *service_name;
+
+ /* cannot use start with wildcards */
+ if (!strcmp(ev->method, "start") && strstr(ev->unit, "@.")) {
+ ret = -ENOTSUP;
+ goto cleanup;
+ }
list_for_each_entry(w, &dm->whitelist, node) {
if (!match_unit(ev->unit, w->unit))
continue;
- ret = unit_control_execute(ev->unit, ev->method, event);
- if (ret < 0)
+ service_name = construct_proper_service_name(ev->unit, w->unit);
+
+ if (!service_name) {
+ error_code = -ENOMEM;
break;
+ }
+
+ log_debug("execute action %s on pattern %s", ev->method, service_name);
+
+ if (!strcmp(ev->method, "start")) /* no need to query when starting */
+ ret = unit_control_execute(service_name, ev->method, event);
+ else
+ ret = unit_control_query_and_execute(service_name, ev->method, event);
+ if (ret < 0) {
+ log_error_errno(ret, "action %s on unit %s failed (%m)", ev->method, service_name);
+ error_code = ret;
+ }
+ free(service_name);
}
+cleanup:
epc_object_unref(event);
- return 0;
+ if (error_code != 0) {
+ sd_bus_error_set_errno(&error, error_code);
+ ret = sd_bus_reply_method_error(ev->m, &error);
+ } else {
+ ret = sd_bus_reply_method_return(ev->m, "s", "ok");
+ }
+ sd_bus_message_unref(ev->m);
+ return ret;
}
static void cleanup_whitelist_entry(struct whitelist_entry *w)
{
+ assert(w);
free(w->unit);
free(w);
}
static void cleanup_whitelist(struct unit_control_decision_maker *dm)
{
+ assert(dm);
+
struct whitelist_entry *w, *next;
list_for_each_entry_safe(w, next, &dm->whitelist, node) {
static int add_whitelist_entry(struct unit_control_decision_maker *dm, const char *unit)
{
- struct whitelist_entry *w;
+ assert(dm);
+ assert(unit);
+
+ struct whitelist_entry *w, *next;
+
+ /* check for duplicates and generalizations */
+ list_for_each_entry_safe(w, next, &dm->whitelist, node) {
+ if (match_unit(unit, w->unit)) {
+ log_debug("not adding %s to the whitelist - duplicate/more general rule %s exists", unit, w->unit);
+ return 0;
+ }
+ if (match_unit(w->unit, unit)) {
+ log_debug("add %s to the whitelist - more general rule than %s - removing %s", unit, w->unit, w->unit);
+ /* remove w */
+ list_del_init(&w->node);
+ cleanup_whitelist_entry(w);
+ }
+ }
w = calloc(1, sizeof(*w));
if (!w) {
- log_error("Could not allocate whitelist entry object");
+ log_error("could not allocate whitelist entry object");
return -ENOMEM;
}
- log_debug("Adding whitelist etry for unit %s", unit);
+ log_debug("adding whitelist entry for unit %s", unit);
w->unit = strdup(unit);
list_add_tail(&w->node, &dm->whitelist);
static int unit_control_init(struct epc_event_handler *handler, struct epc_config *config)
{
+ assert(handler);
+ assert(config);
+
struct unit_control_decision_maker *dm = container_of(handler,
struct unit_control_decision_maker, eh);
int len;
return 0;
if (!json_object_object_get_ex(config->root, "whitelist", &array)) {
- log_error("There is no 'whitelist' array");
+ log_error("there is no 'whitelist' array");
return -EINVAL;
}
if (!json_object_is_type(array, json_type_array)) {
- log_error("Config value is not an array");
+ log_error("config value is not an array");
return -EINVAL;
}
obj = json_object_array_get_idx(array, i);
ret = add_whitelist_entry(dm, json_object_get_string(obj));
if (ret < 0) {
- log_error("Could not add whitelist entry");
+ log_error_errno(ret, "could not add whitelist entry - %m");
goto cleanup;
}
}
-
return 0;
cleanup:
static void unit_control_cleanup(struct epc_event_handler *handler)
{
+ assert(handler);
+
struct unit_control_decision_maker *dm = container_of(handler,
struct unit_control_decision_maker, eh);