| /* |
| * |
| * BlueZ - Bluetooth protocol stack for Linux |
| * |
| * Copyright (C) 2004-2007 Marcel Holtmann <marcel@holtmann.org> |
| * |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
| * |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include <config.h> |
| #endif |
| |
| #include <stdio.h> |
| #include <errno.h> |
| #include <unistd.h> |
| #include <stdlib.h> |
| #include <dirent.h> |
| #include <signal.h> |
| #include <ctype.h> |
| #include <sys/types.h> |
| #include <sys/wait.h> |
| |
| #include <glib.h> |
| |
| #include <dbus/dbus.h> |
| |
| #include "dbus.h" |
| #include "dbus-helper.h" |
| #include "hcid.h" |
| #include "notify.h" |
| #include "server.h" |
| #include "dbus-common.h" |
| #include "dbus-error.h" |
| #include "dbus-manager.h" |
| #include "dbus-service.h" |
| #include "dbus-hci.h" |
| |
| #define SERVICE_INTERFACE "org.bluez.Service" |
| |
| #define STARTUP_TIMEOUT (10 * 1000) /* 10 seconds */ |
| #define SHUTDOWN_TIMEOUT (2 * 1000) /* 2 seconds */ |
| |
| #define SERVICE_SUFFIX ".service" |
| #define SERVICE_GROUP "Bluetooth Service" |
| |
| #define NAME_MATCH "interface=" DBUS_INTERFACE_DBUS ",member=NameOwnerChanged" |
| |
| static GSList *services = NULL; |
| static GSList *removed = NULL; |
| |
| static void service_free(struct service *service) |
| { |
| if (!service) |
| return; |
| |
| if (service->action) |
| dbus_message_unref(service->action); |
| |
| g_free(service->bus_name); |
| g_free(service->filename); |
| g_free(service->object_path); |
| g_free(service->name); |
| g_free(service->descr); |
| g_free(service->ident); |
| |
| g_free(service); |
| } |
| |
| static void service_exit(const char *name, struct service *service) |
| { |
| DBusConnection *conn = get_dbus_connection(); |
| |
| debug("Service owner exited: %s", name); |
| |
| dbus_connection_emit_signal(conn, service->object_path, |
| SERVICE_INTERFACE, "Stopped", |
| DBUS_TYPE_INVALID); |
| |
| if (service->action) { |
| DBusMessage *reply; |
| reply = dbus_message_new_method_return(service->action); |
| send_message_and_unref(conn, reply); |
| dbus_message_unref(service->action); |
| service->action = NULL; |
| } |
| |
| g_free(service->bus_name); |
| service->bus_name = NULL; |
| } |
| |
| static DBusHandlerResult get_info(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct service *service = data; |
| DBusMessage *reply; |
| DBusMessageIter iter; |
| DBusMessageIter dict; |
| dbus_bool_t running; |
| |
| reply = dbus_message_new_method_return(msg); |
| if (!reply) |
| return DBUS_HANDLER_RESULT_NEED_MEMORY; |
| |
| dbus_message_iter_init_append(reply, &iter); |
| |
| dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, |
| DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING |
| DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING |
| DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); |
| |
| dbus_message_iter_append_dict_entry(&dict, "identifier", |
| DBUS_TYPE_STRING, &service->ident); |
| |
| dbus_message_iter_append_dict_entry(&dict, "name", |
| DBUS_TYPE_STRING, &service->name); |
| |
| dbus_message_iter_append_dict_entry(&dict, "description", |
| DBUS_TYPE_STRING, &service->descr); |
| |
| running = (service->external || service->bus_name) ? TRUE : FALSE; |
| |
| dbus_message_iter_append_dict_entry(&dict, "running", |
| DBUS_TYPE_BOOLEAN, &running); |
| |
| dbus_message_iter_close_container(&iter, &dict); |
| |
| return send_message_and_unref(conn, reply); |
| } |
| |
| static DBusHandlerResult get_identifier(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| |
| struct service *service = data; |
| DBusMessage *reply; |
| const char *identifier = ""; |
| |
| reply = dbus_message_new_method_return(msg); |
| if (!reply) |
| return DBUS_HANDLER_RESULT_NEED_MEMORY; |
| |
| if (service->ident) |
| identifier = service->ident; |
| |
| dbus_message_append_args(reply, |
| DBUS_TYPE_STRING, &identifier, |
| DBUS_TYPE_INVALID); |
| |
| return send_message_and_unref(conn, reply); |
| } |
| |
| static DBusHandlerResult get_name(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| |
| struct service *service = data; |
| DBusMessage *reply; |
| const char *name = ""; |
| |
| reply = dbus_message_new_method_return(msg); |
| if (!reply) |
| return DBUS_HANDLER_RESULT_NEED_MEMORY; |
| |
| if (service->name) |
| name = service->name; |
| |
| dbus_message_append_args(reply, |
| DBUS_TYPE_STRING, &name, |
| DBUS_TYPE_INVALID); |
| |
| return send_message_and_unref(conn, reply); |
| } |
| |
| static DBusHandlerResult get_description(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct service *service = data; |
| DBusMessage *reply; |
| const char *description = ""; |
| |
| reply = dbus_message_new_method_return(msg); |
| if (!reply) |
| return DBUS_HANDLER_RESULT_NEED_MEMORY; |
| |
| if (service->descr) |
| description = service->descr; |
| |
| dbus_message_append_args(reply, |
| DBUS_TYPE_STRING, &description, |
| DBUS_TYPE_INVALID); |
| |
| return send_message_and_unref(conn, reply); |
| } |
| |
| static DBusHandlerResult get_bus_name(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct service *service = data; |
| DBusMessage *reply; |
| |
| if (!service->bus_name) |
| return error_not_available(conn, msg); |
| |
| reply = dbus_message_new_method_return(msg); |
| if (!reply) |
| return DBUS_HANDLER_RESULT_NEED_MEMORY; |
| |
| dbus_message_append_args(reply, |
| DBUS_TYPE_STRING, &service->bus_name, |
| DBUS_TYPE_INVALID); |
| |
| return send_message_and_unref(conn, reply); |
| } |
| |
| static void service_setup(gpointer data) |
| { |
| /* struct service *service = data; */ |
| } |
| |
| static DBusHandlerResult service_filter(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| DBusError err; |
| struct service *service = data; |
| const char *name, *old, *new; |
| unsigned long pid; |
| |
| if (!dbus_message_is_signal(msg, DBUS_INTERFACE_DBUS, "NameOwnerChanged")) |
| return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; |
| |
| if (!dbus_message_get_args(msg, NULL, |
| DBUS_TYPE_STRING, &name, DBUS_TYPE_STRING, &old, |
| DBUS_TYPE_STRING, &new, DBUS_TYPE_INVALID)) { |
| error("Invalid arguments for NameOwnerChanged signal"); |
| return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; |
| } |
| |
| if (*new == '\0' || *old != '\0' || *new != ':') |
| return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; |
| |
| if (!dbus_bus_get_unix_process_id(conn, new, &pid)) { |
| error("Could not get PID of %s", new); |
| return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; |
| } |
| |
| if ((GPid) pid != service->pid) |
| return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; |
| |
| debug("Child PID %d got the unique bus name %s", service->pid, new); |
| |
| service->bus_name = g_strdup(new); |
| |
| dbus_error_init(&err); |
| dbus_bus_remove_match(conn, NAME_MATCH, &err); |
| if (dbus_error_is_set(&err)) { |
| error("Remove match \"%s\" failed: %s" NAME_MATCH, err.message); |
| dbus_error_free(&err); |
| } |
| dbus_connection_remove_filter(conn, service_filter, service); |
| |
| if (service->action) { |
| msg = dbus_message_new_method_return(service->action); |
| if (msg) { |
| if (dbus_message_is_method_call(service->action, MANAGER_INTERFACE, |
| "ActivateService")) |
| dbus_message_append_args(msg, DBUS_TYPE_STRING, &new, |
| DBUS_TYPE_INVALID); |
| send_message_and_unref(conn, msg); |
| } |
| |
| dbus_message_unref(service->action); |
| service->action = NULL; |
| } |
| |
| if (service->startup_timer) { |
| g_source_remove(service->startup_timer); |
| service->startup_timer = 0; |
| } else |
| debug("service_filter: timeout was already removed!"); |
| |
| name_listener_add(conn, new, (name_cb_t) service_exit, service); |
| |
| dbus_connection_emit_signal(conn, service->object_path, |
| SERVICE_INTERFACE, "Started", |
| DBUS_TYPE_INVALID); |
| |
| return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; |
| } |
| |
| static void abort_startup(struct service *service, DBusConnection *conn, int ecode) |
| { |
| DBusError err; |
| |
| if (conn) { |
| dbus_error_init(&err); |
| dbus_bus_remove_match(conn, NAME_MATCH, &err); |
| if (dbus_error_is_set(&err)) { |
| error("Remove match \"%s\" failed: %s" NAME_MATCH, err.message); |
| dbus_error_free(&err); |
| } |
| |
| dbus_connection_remove_filter(conn, service_filter, service); |
| } |
| |
| g_source_remove(service->startup_timer); |
| service->startup_timer = 0; |
| |
| if (service->action) { |
| if (conn) |
| error_failed(conn, service->action, ecode); |
| dbus_message_unref(service->action); |
| service->action = NULL; |
| } |
| |
| if (service->pid > 0 && kill(service->pid, SIGKILL) < 0) |
| error("kill(%d, SIGKILL): %s (%d)", service->pid, |
| strerror(errno), errno); |
| } |
| |
| static void service_died(GPid pid, gint status, gpointer data) |
| { |
| struct service *service = data; |
| |
| if (WIFEXITED(status)) |
| debug("%s (%s) exited with status %d", service->name, |
| service->ident, WEXITSTATUS(status)); |
| else |
| debug("%s (%s) was killed by signal %d", service->name, |
| service->ident, WTERMSIG(status)); |
| |
| g_spawn_close_pid(pid); |
| service->pid = 0; |
| |
| if (service->startup_timer) |
| abort_startup(service, get_dbus_connection(), ECANCELED); |
| |
| if (service->shutdown_timer) { |
| g_source_remove(service->shutdown_timer); |
| service->shutdown_timer = 0; |
| } |
| |
| if (g_slist_find(removed, service)) { |
| removed = g_slist_remove(removed, service); |
| service_free(service); |
| } |
| } |
| |
| static gboolean service_shutdown_timeout(gpointer data) |
| { |
| struct service *service = data; |
| |
| if (service->pid > 0) { |
| debug("SIGKILL for \"%s\" (PID %d) since it didn't exit yet", |
| service->name, service->pid); |
| |
| if (kill(service->pid, SIGKILL) < 0) |
| error("kill(%d, SIGKILL): %s (%d)", service->pid, |
| strerror(errno), errno); |
| } |
| |
| service->shutdown_timer = 0; |
| |
| return FALSE; |
| } |
| |
| static void stop_service(struct service *service, gboolean remove) |
| { |
| if (service->pid > 0 && kill(service->pid, SIGTERM) < 0) |
| error("kill(%d, SIGTERM): %s (%d)", service->pid, |
| strerror(errno), errno); |
| |
| service->shutdown_timer = g_timeout_add(SHUTDOWN_TIMEOUT, |
| service_shutdown_timeout, |
| service); |
| |
| if (remove) { |
| services = g_slist_remove(services, service); |
| removed = g_slist_append(removed, service); |
| } |
| } |
| |
| static gboolean service_startup_timeout(gpointer data) |
| { |
| struct service *service = data; |
| |
| debug("Killing \"%s\" (PID %d) because it did not connect to D-Bus in time", |
| service->name, service->pid); |
| |
| abort_startup(service, get_dbus_connection(), ETIME); |
| |
| return FALSE; |
| } |
| |
| int service_start(struct service *service, DBusConnection *conn) |
| { |
| DBusError derr; |
| char *addr, *argv[2], *envp[2], command[PATH_MAX], address[256]; |
| |
| if (!dbus_connection_add_filter(conn, service_filter, service, NULL)) { |
| error("Unable to add signal filter"); |
| return -1; |
| } |
| |
| dbus_error_init(&derr); |
| dbus_bus_add_match(conn, NAME_MATCH, &derr); |
| if (dbus_error_is_set(&derr)) { |
| error("Add match \"%s\" failed: %s", derr.message); |
| dbus_error_free(&derr); |
| dbus_connection_remove_filter(conn, service_filter, service); |
| return -1; |
| } |
| |
| snprintf(command, sizeof(command) - 1, "%s/bluetoothd-service-%s", |
| SERVICEDIR, service->ident); |
| argv[0] = command; |
| argv[1] = NULL; |
| |
| addr = get_local_server_address(); |
| |
| snprintf(address, sizeof(address) - 1, "BLUETOOTHD_ADDRESS=%s", addr); |
| envp[0] = address; |
| envp[1] = NULL; |
| |
| dbus_free(addr); |
| |
| if (!g_spawn_async(SERVICEDIR, argv, envp, G_SPAWN_DO_NOT_REAP_CHILD, |
| service_setup, service, &service->pid, NULL)) { |
| error("Unable to execute %s", argv[0]); |
| dbus_connection_remove_filter(conn, service_filter, service); |
| dbus_bus_remove_match(conn, NAME_MATCH, NULL); |
| return -1; |
| } |
| |
| g_child_watch_add(service->pid, service_died, service); |
| |
| debug("%s executed with PID %d", argv[0], service->pid); |
| |
| service->startup_timer = g_timeout_add(STARTUP_TIMEOUT, |
| service_startup_timeout, |
| service); |
| |
| return 0; |
| } |
| |
| static DBusHandlerResult start(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct service *service = data; |
| |
| if (service->external || service->pid) |
| return error_failed(conn, msg, EALREADY); |
| |
| if (service_start(service, conn) < 0) |
| return error_failed(conn, msg, ENOEXEC); |
| |
| service->action = dbus_message_ref(msg); |
| |
| return DBUS_HANDLER_RESULT_HANDLED; |
| } |
| |
| static DBusHandlerResult stop(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct service *service = data; |
| |
| if (service->external || !service->bus_name) |
| return error_failed(conn, msg, EPERM); |
| |
| stop_service(service, FALSE); |
| |
| service->action = dbus_message_ref(msg); |
| |
| return DBUS_HANDLER_RESULT_HANDLED; |
| } |
| |
| static DBusHandlerResult is_running(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct service *service = data; |
| DBusMessage *reply; |
| dbus_bool_t running; |
| |
| reply = dbus_message_new_method_return(msg); |
| if (!reply) |
| return DBUS_HANDLER_RESULT_NEED_MEMORY; |
| |
| running = (service->external || service->bus_name) ? TRUE : FALSE; |
| |
| dbus_message_append_args(reply, |
| DBUS_TYPE_BOOLEAN, &running, |
| DBUS_TYPE_INVALID); |
| |
| return send_message_and_unref(conn, reply); |
| } |
| |
| static DBusHandlerResult is_external(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct service *service = data; |
| DBusMessage *reply; |
| |
| reply = dbus_message_new_method_return(msg); |
| if (!reply) |
| return DBUS_HANDLER_RESULT_NEED_MEMORY; |
| |
| dbus_message_append_args(reply, |
| DBUS_TYPE_BOOLEAN, &service->external, |
| DBUS_TYPE_INVALID); |
| |
| return send_message_and_unref(conn, reply); |
| } |
| |
| static DBusHandlerResult list_users(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; |
| } |
| |
| static DBusHandlerResult remove_user(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; |
| } |
| |
| static DBusHandlerResult set_trusted(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct service *service = data; |
| DBusMessage *reply; |
| const char *address; |
| |
| if (!dbus_message_get_args(msg, NULL, |
| DBUS_TYPE_STRING, &address, |
| DBUS_TYPE_INVALID)) |
| return error_invalid_arguments(conn, msg); |
| |
| if (check_address(address) < 0) |
| return error_invalid_arguments(conn, msg); |
| |
| reply = dbus_message_new_method_return(msg); |
| if (!reply) |
| return DBUS_HANDLER_RESULT_NEED_MEMORY; |
| |
| write_trust(BDADDR_ANY, address, service->ident, TRUE); |
| |
| dbus_connection_emit_signal(conn, service->object_path, |
| SERVICE_INTERFACE, "TrustAdded", |
| DBUS_TYPE_STRING, &address, |
| DBUS_TYPE_INVALID); |
| |
| return send_message_and_unref(conn, reply); |
| } |
| |
| static DBusHandlerResult is_trusted(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct service *service = data; |
| DBusMessage *reply; |
| const char *address; |
| dbus_bool_t trusted; |
| |
| if (!dbus_message_get_args(msg, NULL, |
| DBUS_TYPE_STRING, &address, |
| DBUS_TYPE_INVALID)) |
| return error_invalid_arguments(conn, msg); |
| |
| if (check_address(address) < 0) |
| return error_invalid_arguments(conn, msg); |
| |
| trusted = read_trust(BDADDR_ANY, address, service->ident); |
| |
| reply = dbus_message_new_method_return(msg); |
| if (!reply) |
| return DBUS_HANDLER_RESULT_NEED_MEMORY; |
| |
| dbus_message_append_args(reply, |
| DBUS_TYPE_BOOLEAN, &trusted, |
| DBUS_TYPE_INVALID); |
| |
| return send_message_and_unref(conn, reply); |
| } |
| |
| static DBusHandlerResult remove_trust(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct service *service = data; |
| DBusMessage *reply; |
| const char *address; |
| |
| if (!dbus_message_get_args(msg, NULL, |
| DBUS_TYPE_STRING, &address, |
| DBUS_TYPE_INVALID)) |
| return error_invalid_arguments(conn, msg); |
| |
| if (check_address(address) < 0) |
| return error_invalid_arguments(conn, msg); |
| |
| reply = dbus_message_new_method_return(msg); |
| if (!reply) |
| return DBUS_HANDLER_RESULT_NEED_MEMORY; |
| |
| write_trust(BDADDR_ANY, address, service->ident, FALSE); |
| |
| dbus_connection_emit_signal(conn, service->object_path, |
| SERVICE_INTERFACE, "TrustRemoved", |
| DBUS_TYPE_STRING, &address, |
| DBUS_TYPE_INVALID); |
| |
| return send_message_and_unref(conn, reply); |
| } |
| |
| static DBusMethodVTable service_methods[] = { |
| { "GetInfo", get_info, "", "{sv}" }, |
| { "GetIdentifier", get_identifier, "", "s" }, |
| { "GetName", get_name, "", "s" }, |
| { "GetDescription", get_description, "", "s" }, |
| { "GetBusName", get_bus_name, "", "s" }, |
| { "Start", start, "", "" }, |
| { "Stop", stop, "", "" }, |
| { "IsRunning", is_running, "", "b" }, |
| { "IsExternal", is_external, "", "b" }, |
| { "ListUsers", list_users, "", "as" }, |
| { "RemoveUser", remove_user, "s", "" }, |
| { "SetTrusted", set_trusted, "s", "" }, |
| { "IsTrusted", is_trusted, "s", "b" }, |
| { "RemoveTrust", remove_trust, "s", "" }, |
| { NULL, NULL, NULL, NULL } |
| }; |
| |
| static DBusSignalVTable service_signals[] = { |
| { "Started", "" }, |
| { "Stopped", "" }, |
| { "TrustAdded", "s" }, |
| { "TrustRemoved", "s" }, |
| { NULL, NULL } |
| }; |
| |
| static dbus_bool_t service_init(DBusConnection *conn, const char *path) |
| { |
| return dbus_connection_register_interface(conn, path, SERVICE_INTERFACE, |
| service_methods, |
| service_signals, NULL); |
| } |
| |
| static int service_cmp_path(struct service *service, const char *path) |
| { |
| return strcmp(service->object_path, path); |
| } |
| |
| static int service_cmp_ident(struct service *service, const char *ident) |
| { |
| return strcmp(service->ident, ident); |
| } |
| |
| static int register_service(struct service *service) |
| { |
| char obj_path[PATH_MAX], *suffix; |
| DBusConnection *conn = get_dbus_connection(); |
| int i; |
| |
| if (g_slist_find_custom(services, service->ident, |
| (GCompareFunc) service_cmp_ident) |
| || !strcmp(service->ident, GLOBAL_TRUST)) |
| return -EADDRINUSE; |
| |
| if (service->external) { |
| snprintf(obj_path, sizeof(obj_path) - 1, |
| "/org/bluez/external_%s", service->ident); |
| } else { |
| snprintf(obj_path, sizeof(obj_path) - 1, |
| "/org/bluez/service_%s", service->filename); |
| |
| /* Don't include the .service part in the path */ |
| suffix = strstr(obj_path, SERVICE_SUFFIX); |
| *suffix = '\0'; |
| } |
| |
| /* Make the path valid for D-Bus */ |
| for (i = strlen("/org/bluez/"); obj_path[i]; i++) { |
| if (!isalnum(obj_path[i])) |
| obj_path[i] = '_'; |
| } |
| |
| if (g_slist_find_custom(services, obj_path, |
| (GCompareFunc) service_cmp_path)) |
| return -EADDRINUSE; |
| |
| debug("Registering service object: ident=%s, name=%s (%s)", |
| service->ident, service->name, obj_path); |
| |
| |
| if (!dbus_connection_create_object_path(conn, obj_path, |
| service, NULL)) { |
| error("D-Bus failed to register %s object", obj_path); |
| return -1; |
| } |
| |
| if (!service_init(conn, obj_path)) { |
| error("Service init failed"); |
| return -1; |
| } |
| |
| service->object_path = g_strdup(obj_path); |
| |
| services = g_slist_append(services, service); |
| |
| dbus_connection_emit_signal(conn, BASE_PATH, MANAGER_INTERFACE, |
| "ServiceAdded", |
| DBUS_TYPE_STRING, &service->object_path, |
| DBUS_TYPE_INVALID); |
| |
| return 0; |
| } |
| |
| static int unregister_service_for_connection(DBusConnection *connection, |
| struct service *service) |
| { |
| DBusConnection *conn = get_dbus_connection(); |
| |
| debug("Unregistering service object: %s", service->object_path); |
| |
| if (!conn) |
| goto cleanup; |
| |
| if (service->bus_name) |
| name_listener_remove(connection, service->bus_name, |
| (name_cb_t) service_exit, service); |
| |
| dbus_connection_emit_signal(conn, service->object_path, |
| SERVICE_INTERFACE, |
| "Stopped", DBUS_TYPE_INVALID); |
| |
| if (!dbus_connection_destroy_object_path(conn, service->object_path)) { |
| error("D-Bus failed to unregister %s object", service->object_path); |
| return -1; |
| } |
| |
| dbus_connection_emit_signal(conn, BASE_PATH, MANAGER_INTERFACE, |
| "ServiceRemoved", |
| DBUS_TYPE_STRING, &service->object_path, |
| DBUS_TYPE_INVALID); |
| |
| cleanup: |
| if (service->pid) { |
| if (service->startup_timer) { |
| abort_startup(service, conn, ECANCELED); |
| services = g_slist_remove(services, service); |
| removed = g_slist_append(removed, service); |
| } else if (!service->shutdown_timer) |
| stop_service(service, TRUE); |
| } else { |
| services = g_slist_remove(services, service); |
| service_free(service); |
| } |
| |
| return 0; |
| } |
| |
| static int unregister_service(struct service *service) |
| { |
| return unregister_service_for_connection(get_dbus_connection(), service); |
| } |
| |
| void release_services(DBusConnection *conn) |
| { |
| debug("release_services"); |
| |
| g_slist_foreach(services, (GFunc) unregister_service, NULL); |
| g_slist_free(services); |
| services = NULL; |
| } |
| |
| struct service *search_service(DBusConnection *conn, const char *pattern) |
| { |
| GSList *l; |
| |
| for (l = services; l != NULL; l = l->next) { |
| struct service *service = l->data; |
| |
| if (service->ident && !strcmp(service->ident, pattern)) |
| return service; |
| |
| if (service->bus_name && !strcmp(service->bus_name, pattern)) |
| return service; |
| } |
| |
| return NULL; |
| } |
| |
| void append_available_services(DBusMessageIter *array_iter) |
| { |
| GSList *l; |
| |
| for (l = services; l != NULL; l = l->next) { |
| struct service *service = l->data; |
| |
| dbus_message_iter_append_basic(array_iter, |
| DBUS_TYPE_STRING, &service->object_path); |
| } |
| } |
| |
| static struct service *create_service(const char *file) |
| { |
| GKeyFile *keyfile; |
| GError *err = NULL; |
| struct service *service; |
| gboolean autostart; |
| const char *slash; |
| |
| service = g_try_new0(struct service, 1); |
| if (!service) { |
| error("OOM while allocating new service"); |
| return NULL; |
| } |
| |
| service->external = FALSE; |
| |
| keyfile = g_key_file_new(); |
| |
| if (!g_key_file_load_from_file(keyfile, file, 0, &err)) { |
| error("Parsing %s failed: %s", file, err->message); |
| g_error_free(err); |
| goto failed; |
| } |
| |
| service->ident = g_key_file_get_string(keyfile, SERVICE_GROUP, |
| "Identifier", &err); |
| if (err) { |
| debug("%s: %s", file, err->message); |
| g_error_free(err); |
| goto failed; |
| } |
| |
| service->name = g_key_file_get_string(keyfile, SERVICE_GROUP, |
| "Name", &err); |
| if (!service->name) { |
| error("%s: %s", file, err->message); |
| g_error_free(err); |
| goto failed; |
| } |
| |
| slash = strrchr(file, '/'); |
| if (!slash) { |
| error("No slash in service file path!?"); |
| goto failed; |
| } |
| |
| service->filename = g_strdup(slash + 1); |
| |
| service->descr = g_key_file_get_string(keyfile, SERVICE_GROUP, |
| "Description", &err); |
| if (err) { |
| debug("%s: %s", file, err->message); |
| g_error_free(err); |
| err = NULL; |
| } |
| |
| autostart = g_key_file_get_boolean(keyfile, SERVICE_GROUP, |
| "Autostart", &err); |
| if (err) { |
| debug("%s: %s", file, err->message); |
| g_error_free(err); |
| err = NULL; |
| } else |
| service->autostart = autostart; |
| |
| g_key_file_free(keyfile); |
| |
| return service; |
| |
| failed: |
| g_key_file_free(keyfile); |
| service_free(service); |
| return NULL; |
| } |
| |
| static gint service_filename_cmp(struct service *service, const char *filename) |
| { |
| return strcmp(service->filename, filename); |
| } |
| |
| static void service_notify(int action, const char *name, void *user_data) |
| { |
| GSList *l; |
| struct service *service; |
| size_t len; |
| char fullpath[PATH_MAX]; |
| |
| debug("Received notify event %d for %s", action, name); |
| |
| len = strlen(name); |
| if (len < (strlen(SERVICE_SUFFIX) + 1)) |
| return; |
| |
| if (strcmp(name + (len - strlen(SERVICE_SUFFIX)), SERVICE_SUFFIX)) |
| return; |
| |
| switch (action) { |
| case NOTIFY_CREATE: |
| debug("%s was created", name); |
| snprintf(fullpath, sizeof(fullpath) - 1, "%s/%s", CONFIGDIR, name); |
| service = create_service(fullpath); |
| if (!service) { |
| error("Unable to read %s", fullpath); |
| break; |
| } |
| |
| if (register_service(service) < 0) { |
| error("Unable to register service"); |
| service_free(service); |
| break; |
| } |
| |
| if (service->autostart) |
| service_start(service, get_dbus_connection()); |
| |
| break; |
| case NOTIFY_DELETE: |
| debug("%s was deleted", name); |
| l = g_slist_find_custom(services, name, |
| (GCompareFunc) service_filename_cmp); |
| if (l) |
| unregister_service(l->data); |
| break; |
| case NOTIFY_MODIFY: |
| debug("%s was modified", name); |
| break; |
| default: |
| debug("Unknown notify action %d", action); |
| break; |
| } |
| } |
| |
| int init_services(const char *path) |
| { |
| DIR *d; |
| struct dirent *e; |
| |
| d = opendir(path); |
| if (!d) { |
| error("Unable to open service dir %s: %s", path, strerror(errno)); |
| return -1; |
| } |
| |
| while ((e = readdir(d)) != NULL) { |
| char full_path[PATH_MAX]; |
| struct service *service; |
| size_t len = strlen(e->d_name); |
| |
| if (len < (strlen(SERVICE_SUFFIX) + 1)) |
| continue; |
| |
| /* Skip if the file doesn't end in .service */ |
| if (strcmp(&e->d_name[len - strlen(SERVICE_SUFFIX)], SERVICE_SUFFIX)) |
| continue; |
| |
| snprintf(full_path, sizeof(full_path) - 1, "%s/%s", path, e->d_name); |
| |
| service = create_service(full_path); |
| if (!service) { |
| error("Unable to read %s", full_path); |
| continue; |
| } |
| |
| if (register_service(service) < 0) { |
| error("Unable to register service"); |
| service_free(service); |
| continue; |
| } |
| |
| if (service->autostart) |
| service_start(service, get_dbus_connection()); |
| } |
| |
| closedir(d); |
| |
| notify_add(path, service_notify, NULL); |
| |
| return 0; |
| } |
| |
| static struct service *create_external_service(const char *ident, |
| const char *name, const char *description) |
| { |
| struct service *service; |
| |
| service = g_try_new0(struct service, 1); |
| if (!service) { |
| error("OOM while allocating new external service"); |
| return NULL; |
| } |
| |
| service->filename = NULL; |
| service->name = g_strdup(name); |
| service->descr = g_strdup(description); |
| service->ident = g_strdup(ident); |
| |
| service->external = TRUE; |
| |
| return service; |
| } |
| |
| static void external_service_exit(const char *name, struct service *service) |
| { |
| DBusConnection *conn = get_dbus_connection(); |
| |
| service_exit(name, service); |
| |
| if (!conn) |
| return; |
| |
| if (!dbus_connection_destroy_object_path(conn, service->object_path)) |
| return; |
| |
| dbus_connection_emit_signal(conn, BASE_PATH, MANAGER_INTERFACE, |
| "ServiceRemoved", |
| DBUS_TYPE_STRING, &service->object_path, |
| DBUS_TYPE_INVALID); |
| |
| services = g_slist_remove(services, service); |
| service_free(service); |
| } |
| |
| int service_register(DBusConnection *conn, const char *bus_name, const char *ident, |
| const char *name, const char *description) |
| { |
| struct service *service; |
| |
| if (!conn) |
| return -1; |
| |
| service = create_external_service(ident, name, description); |
| if (!service) |
| return -1; |
| |
| service->bus_name = g_strdup(bus_name); |
| |
| if (register_service(service) < 0) { |
| service_free(service); |
| return -1; |
| } |
| |
| name_listener_add(conn, bus_name, (name_cb_t) external_service_exit, |
| service); |
| |
| dbus_connection_emit_signal(get_dbus_connection(), service->object_path, |
| SERVICE_INTERFACE, "Started", |
| DBUS_TYPE_INVALID); |
| |
| return 0; |
| } |
| |
| int service_unregister(DBusConnection *conn, struct service *service) |
| { |
| return unregister_service_for_connection(conn, service); |
| } |