--- /dev/null
+/*
+ * This file is part of epc.
+ *
+ * Copyright © 2019 Samsung Electronics
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <systemd/sd-bus.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/timerfd.h>
+#include <vconf.h>
+
+#include "common.h"
+#include "systemd_dbus.h"
+
+#define DBUS_PROPERTIES_INTERFACE "org.freedesktop.DBus.Properties"
+#define SERVICE_NAME "activationd-acceptance-test.service"
+#define VCONF_ACTIVATION_KEY "file/activationd/acceptance"
+
+enum unit_state {
+ UNKNOWN,
+ ACTIVE,
+ INACTIVE
+};
+
+struct action {
+ const char *action;
+ enum unit_state expected_states[2];
+ int state_count;
+ int current_state;
+};
+
+struct test_sequence {
+ struct action actions[3];
+ int current_action;
+ int ok;
+};
+
+
+struct test_data {
+ sd_bus *bus;
+ sd_event *loop;
+ struct test_sequence test;
+ enum unit_state state;
+ enum unit_state prev_state;
+ char *path;
+};
+
+static const char *from_unit_state(enum unit_state state)
+{
+ switch (state) {
+ case UNKNOWN:
+ return "unknown";
+ case ACTIVE:
+ return "active";
+ default:
+ return "inactive";
+ }
+}
+
+static enum unit_state to_unit_state(const char *state)
+{
+ if (!strncmp (state, "active", 6))
+ return ACTIVE;
+ else if (!strncmp (state, "inactive", 8) || !strncmp (state, "deactivating", 12))
+ return INACTIVE;
+ else
+ return UNKNOWN;
+}
+
+static int set_vconf_key(const char *val)
+{
+ keylist_t *kl = NULL;
+ kl = vconf_keylist_new();
+ if (!kl) {
+ fprintf(stderr, "error creating vconf keylists\n");
+ return -1;
+ }
+
+ vconf_keylist_add_str(kl, VCONF_ACTIVATION_KEY, val);
+ if (vconf_set(kl) < 0) {
+ fprintf(stderr, "error saving vconf key\n");
+ vconf_keylist_free(kl);
+ return -1;
+ }
+
+ vconf_keylist_free(kl);
+ return 0;
+}
+
+static int test_step(struct test_data *data)
+{
+ struct test_sequence *test = &data->test;
+ if (test->current_action == ARRAY_SIZE(test->actions)) {
+ sd_event_exit(data->loop, EXIT_SUCCESS);
+ test->ok = 1;
+ return 0;
+ }
+
+ if (set_vconf_key(test->actions[test->current_action].action))
+ return -1;
+ return 0;
+}
+
+static int check_test_result(struct test_data *data)
+{
+ struct action *a = &data->test.actions[data->test.current_action];
+ enum unit_state exp_state = a->expected_states[a->current_state];
+
+ if (data->test.ok == 1)
+ return 0;
+
+ if (data->state != exp_state) {
+ data->test.ok = 1;
+ fprintf(stderr, "test failed (%s instead of %s at step %d of %s)\n",
+ from_unit_state(data->state),
+ from_unit_state(exp_state),
+ a->current_state,
+ a->action);
+
+ sd_event_exit(data->loop, EXIT_FAILURE);
+ return -1;
+ }
+ a->current_state++;
+ if (a->current_state == a->state_count) {
+ data->test.current_action++;
+ if (test_step(data) < 0)
+ return -1;
+ }
+ return 0;
+}
+
+
+static int check_unit_state(struct test_data *data, const char *state)
+{
+ enum unit_state new_state;
+
+ new_state = to_unit_state(state);
+ if (data->state != new_state) {
+ data->prev_state = data->state;
+ data->state = new_state;
+ check_test_result(data);
+ }
+ return 0;
+}
+
+
+static int on_properties_changed(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) {
+ int r;
+ struct test_data *data = (struct test_data*)userdata;
+ const char *iface;
+ const char *member;
+ const char *value = NULL;
+ const char *contents;
+
+ r = sd_bus_message_read(m, "s", &iface);
+ if (r < 0) {
+ fprintf(stderr, "Failed to parse PropertiesChanged\n");
+ return r;
+ }
+
+ r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "{sv}");
+ if (r < 0)
+ return r;
+
+ while ((r = sd_bus_message_enter_container(m, SD_BUS_TYPE_DICT_ENTRY, "sv")) > 0) {
+ char type;
+
+ r = sd_bus_message_read(m, "s", &member);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_peek_type(m, &type, &contents);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_enter_container(m, SD_BUS_TYPE_VARIANT, contents);
+ if (r < 0)
+ return r;
+
+ if (strcmp(member, "ActiveState") == 0) {
+ r = sd_bus_message_read(m, "s", &value);
+ if (r < 0)
+ return r;
+ } else {
+ r = sd_bus_message_skip(m, contents);
+ if (r < 0)
+ return r;
+ }
+
+ r = sd_bus_message_exit_container(m);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_exit_container(m);
+ if (r < 0)
+ return r;
+ }
+
+ r = sd_bus_message_exit_container(m);
+ if (r < 0)
+ return r;
+
+ if (value)
+ check_unit_state(data, value);
+ return 0;
+}
+
+static void cleanup(struct test_data *data)
+{
+ free(data->path);
+ if (!data->bus)
+ return;
+ sd_bus_flush(data->bus);
+ sd_bus_unref(data->bus);
+}
+
+static int signal_handler(sd_event_source *s,
+ const struct signalfd_siginfo *si,
+ void *userdata)
+{
+ sd_event *loop = userdata;
+ sd_event_exit(loop, EXIT_FAILURE);
+ return 0;
+}
+
+static int start_test(sd_event_source *s, uint64_t usec, void *userdata)
+{
+ struct test_data *data = (struct test_data*)userdata;
+ return test_step(data);
+}
+
+static int timeout_handler(sd_event_source *s, uint64_t usec, void *userdata)
+{
+ struct test_data *data = (struct test_data*)userdata;
+ fprintf(stderr, "test timed out\n");
+ sd_event_exit(data->loop, EXIT_FAILURE);
+ return 0;
+}
+
+static int call_subscribe(struct test_data *data)
+{
+ __attribute__((cleanup(sd_bus_message_unrefp))) sd_bus_message *reply = NULL;
+ __attribute__((cleanup(sd_bus_error_free))) sd_bus_error error = SD_BUS_ERROR_NULL;
+ int ret;
+
+ ret = sd_bus_call_method(data->bus,
+ SYSTEMD_SERVICE,
+ SYSTEMD_OBJ,
+ SYSTEMD_MANAGER_INTERFACE,
+ "Subscribe",
+ &error,
+ &reply,
+ "");
+
+ if (ret < 0) {
+ fprintf(stderr, "Failed to issue Subscribe() call: %s\n", error.message);
+ return -1;
+ }
+ return 0;
+}
+
+static int call_load_unit(struct test_data *data)
+{
+ __attribute__((cleanup(sd_bus_message_unrefp))) sd_bus_message *reply = NULL;
+ __attribute__((cleanup(sd_bus_error_free))) sd_bus_error error = SD_BUS_ERROR_NULL;
+ int ret;
+ const char *path = NULL;
+ char err[512];
+
+ ret = sd_bus_call_method(data->bus,
+ SYSTEMD_SERVICE,
+ SYSTEMD_OBJ,
+ SYSTEMD_MANAGER_INTERFACE,
+ "LoadUnit",
+ &error,
+ &reply,
+ "s",
+ SERVICE_NAME);
+
+ if (ret < 0) {
+ fprintf(stderr, "Failed to issue LoadUnit() call: %s\n", error.message);
+ return -1;
+ }
+
+ ret = sd_bus_message_read(reply, "o", &path);
+ if (ret < 0) {
+ if (strerror_r(-ret, err, sizeof err))
+ fprintf(stderr, "Failed to read reply: %s\n", err);
+ return -1;
+ }
+
+ if (!path) {
+ fprintf(stderr, "ould not get object path\n");
+ return -1;
+ }
+ data->path = strdup(path);
+ return 0;
+}
+int main(int argc, char *argv[])
+{
+ struct test_data data = {
+ .bus = NULL,
+ .loop = NULL,
+ .state = UNKNOWN,
+ .prev_state = UNKNOWN,
+ .path = NULL,
+ .test = {
+ .actions = {
+ {"START", {ACTIVE, UNKNOWN}, 1, 0},
+ {"RELOAD", {INACTIVE, ACTIVE}, 2, 0},
+ {"STOP", {INACTIVE, UNKNOWN}, 1, 0}
+ },
+ .current_action = 0,
+ .ok = 0
+ }
+ };
+
+ int r;
+ uint64_t now;
+ sigset_t ss;
+ char buf[512];
+ char err[512];
+
+ r = sd_bus_open_system(&data.bus);
+ if (r < 0) {
+ if (strerror_r(-r, err, sizeof err))
+ fprintf(stderr, "Failed to read reply: %s\n", err);
+ goto fail;
+ }
+
+ r = call_subscribe(&data);
+ if (r < 0)
+ goto fail;
+
+ r = call_load_unit(&data);
+ if (r < 0)
+ goto fail;
+
+ r = snprintf(buf, sizeof buf, "type='signal',interface='org.freedesktop.DBus.Properties',member='PropertiesChanged', path='%s'", data.path);
+ if (r < 0) {
+ if (strerror_r(-r, err, sizeof err))
+ fprintf(stderr, "Failed to create match rule: %s\n", err);
+ goto fail;
+ }
+
+ r = sd_bus_add_match(data.bus, NULL, buf, on_properties_changed, &data);
+ if (r < 0) {
+ if (strerror_r(-r, err, sizeof err))
+ fprintf(stderr, "Failed to add PropertiesChanged match: %s\n", err);
+ goto fail;
+ }
+
+ r = sd_event_new(&data.loop);
+ if (r < 0) {
+ if (strerror_r(-r, err, sizeof err))
+ fprintf(stderr, "Failed to create main loop: %s\n", err);
+ goto fail;
+ }
+
+ sigemptyset(&ss);
+ sigaddset(&ss, SIGINT);
+ sigaddset(&ss, SIGTERM);
+ r = sigprocmask(SIG_BLOCK, &ss, NULL);
+ if (r < 0) {
+ if (strerror_r(-r, err, sizeof err))
+ fprintf(stderr, "Failed to change signal mask: %s\n", err);
+ goto fail;
+ }
+
+ r = sd_event_add_signal(data.loop, NULL, SIGINT, signal_handler, data.loop);
+ if (r < 0) {
+ if (strerror_r(-r, err, sizeof err))
+ fprintf(stderr, "Failed to register SIGINT handler: %s\n", err);
+ goto fail;
+ }
+
+ r = sd_event_add_signal(data.loop, NULL, SIGTERM, signal_handler, data.loop);
+ if (r < 0) {
+ if (strerror_r(-r, err, sizeof err))
+ fprintf(stderr, "Failed to register SIGTERM handler: %s\n", err);
+ goto fail;
+ }
+
+ r = sd_bus_attach_event(data.bus, data.loop, 0);
+ if (r < 0) {
+ if (strerror_r(-r, err, sizeof err))
+ fprintf(stderr, "Failed to attach bus to event loop: %s\n", err);
+ goto fail;
+ }
+
+ r = sd_event_add_time(data.loop, NULL, CLOCK_REALTIME, 0, 0, start_test, &data);
+ if (r < 0) {
+ if (strerror_r(-r, err, sizeof err))
+ fprintf(stderr, "Failed to attach timer to event loop: %s\n", err);
+ goto fail;
+ }
+
+ sd_event_now(data.loop, CLOCK_REALTIME, &now);
+
+ r = sd_event_add_time(data.loop, NULL, CLOCK_REALTIME, now+10000000, 0, timeout_handler, &data);
+ if (r < 0) {
+ if (strerror_r(-r, err, sizeof err))
+ fprintf(stderr, "Failed to attach timer to event loop: %s\n", err);
+ goto fail;
+ }
+
+ r = sd_event_loop(data.loop);
+ if (r < 0) {
+ if (strerror_r(-r, err, sizeof err))
+ fprintf(stderr, "Failed to run main loop: %s\n", err);
+ goto fail;
+ }
+
+ cleanup(&data);
+ return r;
+
+fail:
+ cleanup(&data);
+ return EXIT_FAILURE;
+}