--- /dev/null
+/*
+ * Copyright (c) 2025 Samsung Electronics Co., Ltd.
+ *
+ * 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 <stdio.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdbool.h>
+#include <dlfcn.h>
+#include <dirent.h>
+#include <getopt.h>
+#include <errno.h>
+
+#include <glib.h>
+#include <glib-object.h>
+#include <glib-unix.h>
+
+#include <rpc-port-internal.h>
+#include <bundle_internal.h>
+#include <tizen_core.h>
+
+#include "common.h"
+#include "unified-system-service-common.h"
+
+#define COMMANDLINE_OPTION_SERVICE_PLUGIN "service-plugin"
+
+#ifdef LIB64
+#define PLUGIN_LIB_DIR "/usr/lib64"
+#else
+#define PLUGIN_LIB_DIR "/usr/lib"
+#endif
+
+#define PLUGIN_LIB_PREFIX "libunified-system-service"
+#define PLUGIN_DATA_SYMBOL_PREFIX "unified_system_service"
+#define PLUGIN_DATA_SYMBOL_POSTFIX "data"
+
+struct unified_system_service_data {
+ char *service_name;
+ void *service_handle;
+ unified_system_service *service;
+ tizen_core_task_h task;
+ bool is_initialized;
+};
+
+static tizen_core_task_h g_main_task = NULL;
+static GSList *g_unified_system_service_data_list = NULL;
+
+G_LOCK_DEFINE_STATIC(unified_system_service_lock);
+
+static int unified_system_service_get_backend_service(const char *service_plugin_name,
+ void **service_handle,
+ unified_system_service **service)
+{
+ int ret = 0;
+
+ char plugin_path[PATH_MAX] = { 0, };
+ int plugin_path_len = 0;
+
+ char plugin_data_symbol[PATH_MAX] = { 0, };
+ int plugin_data_symbol_len = 0;
+
+ void *temp_service_handle = NULL;
+ unified_system_service *temp_service = NULL;
+
+ if (service_plugin_name == NULL || service_handle == NULL || service == NULL)
+ return -EINVAL;
+
+ plugin_path_len = snprintf(plugin_path, sizeof(plugin_path),
+ "%s/%s-%s.so",
+ PLUGIN_LIB_DIR, PLUGIN_LIB_PREFIX, service_plugin_name);
+ if (plugin_path_len >= sizeof(plugin_path)) {
+ _E("Plugin path is too long: length(%d)", plugin_path_len);
+ return -EINVAL;
+ }
+
+ temp_service_handle = dlopen(plugin_path, RTLD_LAZY);
+ if (temp_service_handle == NULL) {
+ _E("Failed to load service plugin for %s: path(%s), %s",
+ service_plugin_name, plugin_path, dlerror());
+ return -EINVAL;
+ }
+
+ plugin_data_symbol_len = snprintf(plugin_data_symbol,
+ sizeof(plugin_data_symbol), "%s_%s_%s",
+ PLUGIN_DATA_SYMBOL_PREFIX, service_plugin_name,
+ PLUGIN_DATA_SYMBOL_POSTFIX);
+ if (plugin_data_symbol_len >= sizeof(plugin_data_symbol)) {
+ _E("Plugin data symbol is too long: length(%d)", plugin_data_symbol_len);
+ ret = -EINVAL;
+ goto error;
+ }
+
+ temp_service = dlsym(temp_service_handle, plugin_data_symbol);
+ if (temp_service == NULL) {
+ _E("Failed to find plugin data symbol for %s: symbol(%s), %s",
+ service_plugin_name, plugin_data_symbol,
+ dlerror());
+ ret = -EINVAL;
+ goto error;
+ }
+
+ *service_handle = temp_service_handle;
+ *service = temp_service;
+
+ return 0;
+error:
+ if (temp_service_handle != NULL)
+ dlclose(temp_service_handle);
+
+ return ret;
+}
+
+static int unified_system_service_open_and_add_plugin(char **service_plugin_names)
+{
+ int ret = 0;
+ size_t service_plugin_names_len = 0;
+ struct unified_system_service_data *service_data = NULL;
+ size_t service_data_count = 0;
+
+ for (size_t i = 0; service_plugin_names[i]; ++i)
+ ++service_plugin_names_len;
+
+ for (size_t i = 0; i < service_plugin_names_len; ++i) {
+ service_data = calloc(1, sizeof(*service_data));
+ if (service_data == NULL) {
+ _W("Cannot allocate memory for service plugin: %s", service_plugin_names[i]);
+ continue;
+ }
+ service_data->service_name = strdup(service_plugin_names[i]);
+ if (service_data->service_name == NULL) {
+ _W("Cannot allocate memory for service name: %s", service_plugin_names[i]);
+ free(service_data);
+ service_data = NULL;
+ continue;
+ }
+
+ ret = unified_system_service_get_backend_service(service_plugin_names[i],
+ &(service_data->service_handle),
+ &(service_data->service));
+ if (ret != 0) {
+ _W("Cannot get service plugin: %s", service_plugin_names[i]);
+ free(service_data->service_name);
+ free(service_data);
+ service_data = NULL;
+ continue;
+ }
+
+ g_unified_system_service_data_list = g_slist_prepend(g_unified_system_service_data_list,
+ (gpointer)service_data);
+ service_data = NULL;
+ ++service_data_count;
+ }
+
+ if (service_data_count == 0) {
+ _E("Failed to get service plugin");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void delete_unified_system_service(
+ struct unified_system_service_data *unified_system_service_data,
+ gpointer data)
+{
+ int ret = 0;
+
+ if (unified_system_service_data == NULL) {
+ _E("Invallid parameter: unified_system_service_data(NULL)");
+ return;
+ }
+
+ if (unified_system_service_data->service_handle != NULL) {
+ unified_system_service_data->service = NULL;
+ dlclose(unified_system_service_data->service_handle);
+ unified_system_service_data->service_handle = NULL;
+ }
+
+ free(unified_system_service_data->service_name);
+ free(unified_system_service_data);
+
+ return;
+}
+
+static int unified_system_service_delete_and_close_plugin(void)
+{
+ enum hal_module module;
+ int ret;
+
+ g_slist_foreach(g_unified_system_service_data_list,
+ (GFunc)delete_unified_system_service, NULL);
+
+ g_slist_free(g_unified_system_service_data_list);
+ g_unified_system_service_data_list = NULL;
+
+ return 0;
+}
+
+static bool unified_system_service_initialize_cb(tizen_core_source_h source, int *timeout, void *data)
+{
+ struct unified_system_service_data *service_data = NULL;
+ int ret = 0;
+
+ /* FIXME: This is error situation, task should be terminated? */
+ if (data == NULL) {
+ _E("Invalid unified_system_service(NULL)");
+ return false;
+ }
+ service_data = (struct unified_system_service_data *)data;
+
+ if (service_data->is_initialized)
+ return false;
+
+ G_LOCK(unified_system_service_lock);
+
+ /**
+ * FIXME: If early_init succeed and init failed, should early_init
+ * called?
+ */
+ if (service_data->service->early_init) {
+ ret = service_data->service->early_init(NULL);
+ if (ret != 0) {
+ _E("Failed to early initialize %s: ret(%d)",
+ service_data->service_name, ret);
+ goto error;
+ }
+ }
+
+ if (service_data->service->init) {
+ ret = service_data->service->init(NULL);
+ if (ret != 0) {
+ _E("Failed to initialize %s: ret(%d)",
+ service_data->service_name, ret);
+ goto error;
+ }
+ }
+
+ service_data->is_initialized = true;
+ G_UNLOCK(unified_system_service_lock);
+
+ return false;
+
+error:
+ G_UNLOCK(unified_system_service_lock);
+
+ return false;
+}
+
+static void unified_system_service_finalize_cb(tizen_core_source_h source, void *data)
+{
+ struct unified_system_service_data *service_data = NULL;
+ int ret = 0;
+
+ if (data == NULL) {
+ _E("Invalid unified_system_service(NULL)");
+ return;
+ }
+ service_data = (struct unified_system_service_data *)data;
+
+ G_LOCK(unified_system_service_lock);
+
+ if (service_data->service->exit) {
+ ret = service_data->service->exit(NULL);
+ if (ret != 0) {
+ _E("Cannot exit %s: ret(%d)",
+ service_data->service_name, ret);
+ }
+ }
+
+ if (service_data->service->late_exit) {
+ ret = service_data->service->late_exit(NULL);
+ if (ret != 0) {
+ _E("Cannot late exit %s: ret(%d)",
+ service_data->service_name, ret);
+ }
+ }
+
+ service_data->is_initialized = false;
+ G_UNLOCK(unified_system_service_lock);
+
+ _D("Exit done.");
+}
+
+static void create_unified_system_service_task(gpointer data, gpointer user_data)
+{
+ tizen_core_task_h task = NULL;
+ tizen_core_h core = NULL;
+ tizen_core_source_h source = NULL;
+
+ struct unified_system_service_data *service_data = NULL;
+ size_t *failed_count = NULL;
+ int ret = 0;
+
+ if (data == NULL || user_data == NULL) {
+ _E("Invalid parameter: data(%p), user_data(%p)", data, user_data);
+ return;
+ }
+
+ service_data = (struct unified_system_service_data *)data;
+ failed_count = (size_t *)user_data;
+
+ if (service_data->service_name == NULL) {
+ _E("Service name is not initialized");
+ goto error;
+ }
+
+ ret = tizen_core_task_create(service_data->service_name, true, &task);
+ if (ret != TIZEN_CORE_ERROR_NONE) {
+ _E("Failed to create tizen_core task: ret(%d)", ret);
+ goto error;
+ }
+
+ ret = tizen_core_source_create(&source);
+ if (ret != TIZEN_CORE_ERROR_NONE) {
+ _E("Failed to create tizen_core source: ret(%d)", ret);
+ goto error;
+ }
+
+ ret = tizen_core_task_get_tizen_core(task, &core);
+ if (ret != TIZEN_CORE_ERROR_NONE) {
+ _E("Failed to get tizen_core core from task: ret(%d)", ret);
+ goto error;
+ }
+
+ ret = tizen_core_source_set_prepare_cb(source, unified_system_service_initialize_cb, service_data);
+ if (ret != TIZEN_CORE_ERROR_NONE) {
+ _E("Failed to set prepare callback: ret(%d)", ret);
+ goto error;
+ }
+
+ ret = tizen_core_source_set_finalize_cb(source, unified_system_service_finalize_cb, service_data);
+ if (ret != TIZEN_CORE_ERROR_NONE) {
+ _E("Failed to set finalize callback: ret(%d)", ret);
+ goto error;
+ }
+
+ ret = tizen_core_add_source(core, source);
+ if (ret != TIZEN_CORE_ERROR_NONE) {
+ _E("Failed to add tizen_core source to core: ret(%d)", ret);
+ goto error;
+ }
+
+ service_data->task = task;
+
+ _D("Task created for %s", service_data->service_name);
+
+ ret = tizen_core_task_run(task);
+ if (ret != TIZEN_CORE_ERROR_NONE) {
+ _E("Failed to run tizen_core task for %s: ret(%d)",
+ service_data->service_name, ret);
+ goto error;
+ }
+
+ return;
+
+error:
+ if (source != NULL)
+ tizen_core_source_destroy(source);
+
+ if (task != NULL)
+ tizen_core_task_destroy(task);
+
+ (*failed_count)++;
+
+ return;
+}
+
+static void delete_unified_system_service_task(gpointer data, gpointer user_data)
+{
+ struct unified_system_service_data *service_data = NULL;
+ int ret = 0;
+
+ if (data == NULL) {
+ _E("Invalid parameter: data(NULL)");
+ return;
+ }
+ service_data = (struct unified_system_service_data *)data;
+
+ if (service_data->task == NULL) {
+ _D("No task for %s", service_data->service_name);
+ return;
+ }
+
+ ret = tizen_core_task_quit(service_data->task);
+ if (ret != TIZEN_CORE_ERROR_NONE) {
+ _E("Failed to quit task for %s", service_data->service_name);
+ return;
+ }
+
+ ret = tizen_core_task_destroy(service_data->task);
+ if (ret != TIZEN_CORE_ERROR_NONE) {
+ _E("Failed to destroy task for %s", service_data->service_name);
+ return;
+ }
+
+ service_data->task = NULL;
+
+ _D("Task destroyed for %s", service_data->service_name);
+}
+
+static int unified_system_service_start(void)
+{
+ size_t failed_count = 0;
+ size_t services_len = g_slist_length(g_unified_system_service_data_list);
+
+ /* Create tasks for unified-system-service and then tie them */
+ g_slist_foreach(g_unified_system_service_data_list,
+ (GFunc)create_unified_system_service_task,
+ &failed_count);
+
+ if (failed_count > 0) {
+ _W("Cannot register %zu/%zu service(s)",
+ failed_count, services_len);
+ }
+
+ if (failed_count == services_len) {
+ _E("Failed to start unified system service, no task is created");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void unified_system_service_stop(void)
+{
+ g_slist_foreach(g_unified_system_service_data_list,
+ delete_unified_system_service_task, NULL);
+}
+
+static gboolean handle_signal_glib(gpointer data)
+{
+ int signal_number = GPOINTER_TO_INT(data);
+
+ _D("Received signal(%d)", signal_number);
+ if (g_main_task != NULL)
+ tizen_core_task_quit(g_main_task);
+
+ return G_SOURCE_REMOVE;
+}
+
+static void handle_signal_std(int signal_number)
+{
+ _W("Received signal(%d)", signal_number);
+ raise(SIGTERM);
+}
+
+static void attach_signal_handlers(void)
+{
+ g_unix_signal_add(SIGINT, handle_signal_glib, GINT_TO_POINTER(SIGINT));
+ g_unix_signal_add(SIGHUP, handle_signal_glib, GINT_TO_POINTER(SIGHUP));
+ g_unix_signal_add(SIGTERM, handle_signal_glib, GINT_TO_POINTER(SIGTERM));
+
+ signal(SIGQUIT, handle_signal_std);
+ signal(SIGABRT, handle_signal_std);
+}
+
+static void print_usage(const char *exec_name)
+{
+ _E("Usage: %s --%s <service names>\n",
+ exec_name, COMMANDLINE_OPTION_SERVICE_PLUGIN);
+}
+
+static int parse_arguments(int argc, char **argv, char **service_plugin_names_str)
+{
+ int opt_flag = 0;
+ char *parsed_service_plugin_names = NULL;
+
+ struct option options[] = {
+ {
+ .name = COMMANDLINE_OPTION_SERVICE_PLUGIN,
+ .has_arg = required_argument,
+ .flag = &opt_flag,
+ .val = 1
+ }, {
+ .name = NULL,
+ .has_arg = 0,
+ .flag = NULL,
+ .val = 0
+ },
+ };
+
+ if (service_plugin_names_str == NULL) {
+ _E("Invalid argument: service_plugin_names_str should not be NULL");
+ return -EINVAL;
+ }
+
+ opt_flag = 0;
+ opterr = 0;
+ while (getopt_long(argc, argv, "", options, NULL) != -1) {
+ switch (opt_flag) {
+ case 1:
+ if (parsed_service_plugin_names != NULL) {
+ _E("Invalid commandline argument: <service-plugin> provided twice\n");
+ return -EINVAL;
+ }
+ parsed_service_plugin_names = optarg;
+ break;
+ default:
+ _E("Invalid commandline argument: %s", argv[optind - 1]);
+ return -EINVAL;
+ }
+
+ opt_flag = 0;
+ }
+
+ if (parsed_service_plugin_names == NULL) {
+ _E("Invalid commandline argument: <service-plugin> is not provided\n");
+ return -EINVAL;
+ }
+
+ *service_plugin_names_str = parsed_service_plugin_names;
+
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ int ret = 0;
+ char *service_plugin_names_str = NULL;
+ gchar **service_plugin_names = NULL;
+
+ ret = parse_arguments(argc, argv, &service_plugin_names_str);
+ if (ret != 0) {
+ print_usage(argv[0]);
+ return ret;
+ }
+
+ _D("Initialize unified-system-service");
+
+ tizen_core_init();
+
+ ret = tizen_core_task_create("main", false, &g_main_task);
+ if (ret != TIZEN_CORE_ERROR_NONE) {
+ _E("Failed to create tizen_core main task: ret(%d)", ret);
+ tizen_core_shutdown();
+ return 1;
+ }
+
+ service_plugin_names = g_strsplit(service_plugin_names_str, ",", 0);
+ unified_system_service_open_and_add_plugin(service_plugin_names);
+ g_strfreev(service_plugin_names);
+ service_plugin_names = NULL;
+
+ if (g_slist_length(g_unified_system_service_data_list) == 0) {
+ _W("No available service plugin, exit");
+
+ tizen_core_task_destroy(g_main_task);
+ tizen_core_shutdown();
+
+ return 0;
+ }
+
+ ret = unified_system_service_start();
+ if (ret != 0) {
+ _E("Failed to start unified-system-service: ret(%d)", ret);
+
+ unified_system_service_delete_and_close_plugin();
+ tizen_core_task_destroy(g_main_task);
+ tizen_core_shutdown();
+
+ return 1;
+ }
+
+ attach_signal_handlers();
+
+ tizen_core_task_run(g_main_task);
+
+ /* Un-load unified-system-service plugin */
+ unified_system_service_stop();
+ unified_system_service_delete_and_close_plugin();
+
+ tizen_core_task_destroy(g_main_task);
+
+ tizen_core_shutdown();
+
+ _D("Exit unified-system-service");
+
+ return 0;
+}