unit control: add unit instances and wildcards support 21/204821/10
authorMaciej Slodczyk <m.slodczyk2@partner.samsung.com>
Fri, 26 Apr 2019 13:23:45 +0000 (15:23 +0200)
committerPaweł Szewczyk <p.szewczyk@samsung.com>
Tue, 30 Apr 2019 14:35:49 +0000 (16:35 +0200)
Change-Id: Iac25e794764d79d6560ab1af39d99b71f50ffc58
Signed-off-by: Maciej Slodczyk <m.slodczyk2@partner.samsung.com>
include/unit_control_event.h
src/decision_makers/unit_control_dm.c
src/event_types/unit_control_event.c
src/listeners/unit_control_api.c

index 40d40e9e2775c2c0b2170be6cc2022c70ea251ea..584b270c9b5a0051e7764b484ca2e8d514d32923 100644 (file)
@@ -20,6 +20,7 @@
 #define UNIT_CONTROL_EVENT_H
 
 #include <time.h>
+#include <systemd/sd-bus.h>
 #include "event.h"
 
 #define UC_EVENT_ID "unit_control"
 
 struct unit_control_event {
        struct epc_event event;
+       sd_bus_message *m;
        char *method;
        char *unit;
        time_t event_time;
 };
 
 struct unit_control_event_data {
+       sd_bus_message *m;
        char *method;
        char *unit;
        time_t event_time;
index 6d8477d4248c4ef7b92fba90608fbae38ce7f313..077c792a36462d5cdc6601b73014f435fd60315b 100644 (file)
@@ -18,6 +18,7 @@
 #include <malloc.h>
 #include <errno.h>
 #include <string.h>
+#include <systemd/sd-bus.h>
 
 #include "decision_made_event.h"
 #include "action.h"
@@ -27,6 +28,7 @@
 #include "common.h"
 #include "database.h"
 #include "json-config.h"
+#include "systemd_dbus.h"
 
 #include "unit_control_event.h"
 
@@ -42,6 +44,11 @@ struct whitelist_entry {
        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)
 {
@@ -50,8 +57,148 @@ static int unit_control_event_match(struct epc_event_handler *handler,
        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,
@@ -59,20 +206,16 @@ static int unit_control_execute(const char *unit, const char *command, struct ep
        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;
        }
 
@@ -84,7 +227,7 @@ static int unit_control_execute(const char *unit, const char *command, struct ep
                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;
        }
 
@@ -97,7 +240,7 @@ static int unit_control_execute(const char *unit, const char *command, struct ep
        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;
        }
 
@@ -106,8 +249,51 @@ 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, '.');
@@ -126,36 +312,120 @@ static int match_unit(const char *unit_name, const char *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) {
@@ -166,15 +436,32 @@ static void cleanup_whitelist(struct unit_control_decision_maker *dm)
 
 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);
@@ -183,6 +470,9 @@ static int add_whitelist_entry(struct unit_control_decision_maker *dm, const cha
 
 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;
@@ -196,12 +486,12 @@ static int unit_control_init(struct epc_event_handler *handler, struct epc_confi
                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;
        }
 
@@ -210,11 +500,10 @@ static int unit_control_init(struct epc_event_handler *handler, struct epc_confi
                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:
@@ -224,6 +513,8 @@ 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);
 
index 5da0da7693e4b5ecc06d38ef32a5df836371042a..b004c8a502e4a3bafb1f03f6f91a3383ae5ec97b 100644 (file)
@@ -40,6 +40,7 @@ static int allocate_uc_event(struct epc_event_type *type,
        uc_ev->event_time = uc_ev_data->event_time;
        uc_ev->method = uc_ev_data->method;
        uc_ev->unit = uc_ev_data->unit;
+       uc_ev->m = uc_ev_data->m;
 
        *ev = &uc_ev->event;
        return 0;
index 860d88e68f8e5a561d0b3b24adcb4b4608d40500..d5abc5c7e213afa69631832b150e11842411b665 100644 (file)
@@ -46,7 +46,7 @@ static int method_generic_callback(sd_bus_message *m, void *userdata, sd_bus_err
                goto fail;
        }
 
-       /* Read the parameters */
+       /* Read parameters */
        r = sd_bus_message_read(m, "s", &s);
        if (r < 0) {
                fprintf(stderr, "Failed to parse parameters: %s\n", strerror(-r));
@@ -73,6 +73,10 @@ static int method_generic_callback(sd_bus_message *m, void *userdata, sd_bus_err
                goto fail;
        }
 
+       /*we'll use it later for the response */
+       sd_bus_message_ref(m);
+       uc_ev_data.m = m;
+
        r = epc_event_create(UC_EVENT_ID, &uc_ev_data, &ev);
        if (r) {
                log_error_errno(r, "Unable to allocate an event: %m.");
@@ -87,9 +91,11 @@ static int method_generic_callback(sd_bus_message *m, void *userdata, sd_bus_err
                r = -EINVAL;
                goto fail;
        }
-
-       return sd_bus_reply_method_return(m, "s", "ok");
+       return 1;
 fail:
+       if (uc_ev_data.m)
+               sd_bus_message_unref(uc_ev_data.m);
+
        free(uc_ev_data.method);
        free(uc_ev_data.unit);
        return sd_bus_error_set_errno(ret_error, -r);