diff options
author | Maciej Slodczyk <m.slodczyk2@partner.samsung.com> | 2019-04-26 15:23:45 +0200 |
---|---|---|
committer | Paweł Szewczyk <p.szewczyk@samsung.com> | 2019-04-30 16:35:49 +0200 |
commit | c760fc6dbc91ce42dfc4774f31d713c4d0734caf (patch) | |
tree | 128d6171b47da4b3542cd84a7d5079180e5b1b60 | |
parent | eeadf741b6381385b793fbbb8aae703b10f1b389 (diff) | |
download | activationd-c760fc6dbc91ce42dfc4774f31d713c4d0734caf.tar.gz activationd-c760fc6dbc91ce42dfc4774f31d713c4d0734caf.tar.bz2 activationd-c760fc6dbc91ce42dfc4774f31d713c4d0734caf.zip |
unit control: add unit instances and wildcards support
Change-Id: Iac25e794764d79d6560ab1af39d99b71f50ffc58
Signed-off-by: Maciej Slodczyk <m.slodczyk2@partner.samsung.com>
-rw-r--r-- | include/unit_control_event.h | 3 | ||||
-rw-r--r-- | src/decision_makers/unit_control_dm.c | 329 | ||||
-rw-r--r-- | src/event_types/unit_control_event.c | 1 | ||||
-rw-r--r-- | src/listeners/unit_control_api.c | 12 |
4 files changed, 323 insertions, 22 deletions
diff --git a/include/unit_control_event.h b/include/unit_control_event.h index 40d40e9..584b270 100644 --- a/include/unit_control_event.h +++ b/include/unit_control_event.h @@ -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" @@ -29,12 +30,14 @@ 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; diff --git a/src/decision_makers/unit_control_dm.c b/src/decision_makers/unit_control_dm.c index 6d8477d..077c792 100644 --- a/src/decision_makers/unit_control_dm.c +++ b/src/decision_makers/unit_control_dm.c @@ -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); diff --git a/src/event_types/unit_control_event.c b/src/event_types/unit_control_event.c index 5da0da7..b004c8a 100644 --- a/src/event_types/unit_control_event.c +++ b/src/event_types/unit_control_event.c @@ -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; diff --git a/src/listeners/unit_control_api.c b/src/listeners/unit_control_api.c index 860d88e..d5abc5c 100644 --- a/src/listeners/unit_control_api.c +++ b/src/listeners/unit_control_api.c @@ -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); |