From 05e343b70453716cc6292b17e7ef175a8c106aad Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 15 Apr 2010 23:16:16 +0200 Subject: [PATCH] service: optionally, trie dbus name cycle to service cycle --- dbus.c | 158 +++++++++++++++++++++++++++++++++++++++++------- dbus.h | 2 + load-fragment.c | 1 + manager.c | 39 ++++++++++++ manager.h | 13 ++-- service.c | 128 +++++++++++++++++++++++++++++++++++++-- service.h | 10 ++- unit.c | 22 ++++++- unit.h | 12 ++++ 9 files changed, 350 insertions(+), 35 deletions(-) diff --git a/dbus.c b/dbus.c index e2f8f3cd496..a1a3606361e 100644 --- a/dbus.c +++ b/dbus.c @@ -316,17 +316,25 @@ static DBusHandlerResult api_bus_message_filter(DBusConnection *connection, DBu bus_done_api(m); } else if (dbus_message_is_signal(message, DBUS_INTERFACE_DBUS, "NameOwnerChanged")) { - const char *name, *old, *new; + const char *name, *old_owner, *new_owner; if (!dbus_message_get_args(message, &error, DBUS_TYPE_STRING, &name, - DBUS_TYPE_STRING, &old, - DBUS_TYPE_STRING, &new, + DBUS_TYPE_STRING, &old_owner, + DBUS_TYPE_STRING, &new_owner, DBUS_TYPE_INVALID)) log_error("Failed to parse NameOwnerChanged message: %s", error.message); else { if (set_remove(m->subscribed, (char*) name)) log_debug("Subscription client vanished: %s (left: %u)", name, set_size(m->subscribed)); + + if (old_owner[0] == 0) + old_owner = NULL; + + if (new_owner[0] == 0) + new_owner = NULL; + + manager_dispatch_bus_name_owner_changed(m, name, old_owner, new_owner); } } @@ -388,7 +396,7 @@ unsigned bus_dispatch(Manager *m) { return 0; } -static void pending_cb(DBusPendingCall *pending, void *userdata) { +static void request_name_pending_cb(DBusPendingCall *pending, void *userdata) { DBusMessage *reply; DBusError error; @@ -432,48 +440,49 @@ static void pending_cb(DBusPendingCall *pending, void *userdata) { } static int request_name(Manager *m) { - DBusMessage *message; const char *name = "org.freedesktop.systemd1"; uint32_t flags = 0; - DBusPendingCall *pending; + DBusMessage *message = NULL; + DBusPendingCall *pending = NULL; if (!(message = dbus_message_new_method_call( DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS, "RequestName"))) - return -ENOMEM; + goto oom; if (!dbus_message_append_args( message, DBUS_TYPE_STRING, &name, DBUS_TYPE_UINT32, &flags, - DBUS_TYPE_INVALID)) { - dbus_message_unref(message); - return -ENOMEM; - } + DBUS_TYPE_INVALID)) + goto oom; - if (!dbus_connection_send_with_reply(m->api_bus, message, &pending, -1)) { - dbus_message_unref(message); - return -ENOMEM; - } + if (!dbus_connection_send_with_reply(m->api_bus, message, &pending, -1)) + goto oom; + if (!dbus_pending_call_set_notify(pending, request_name_pending_cb, m, NULL)) + goto oom; dbus_message_unref(message); - - if (!dbus_pending_call_set_notify(pending, pending_cb, NULL, NULL)) { - dbus_pending_call_cancel(pending); - dbus_pending_call_unref(pending); - return -ENOMEM; - } - - dbus_pending_call_unref(pending); /* We simple ask for the name and don't wait for it. Sooner or * later we'll have it. */ return 0; + +oom: + if (pending) { + dbus_pending_call_cancel(pending); + dbus_pending_call_unref(pending); + } + + if (message) + dbus_message_unref(message); + + return -ENOMEM; } static int bus_setup_loop(Manager *m, DBusConnection *bus) { @@ -557,6 +566,10 @@ int bus_init_api(Manager *m) { if (m->api_bus) return 0; + if (m->name_data_slot < 0) + if (!dbus_pending_call_allocate_data_slot(&m->name_data_slot)) + return -ENOMEM; + if (m->running_as != MANAGER_SESSION && m->system_bus) m->api_bus = m->system_bus; else { @@ -636,6 +649,9 @@ void bus_done_api(Manager *m) { set_free(m->subscribed); m->subscribed = NULL; } + + if (m->name_data_slot >= 0) + dbus_pending_call_free_data_slot(&m->name_data_slot); } void bus_done_system(Manager *m) { @@ -652,6 +668,102 @@ void bus_done_system(Manager *m) { } } +static void query_pid_pending_cb(DBusPendingCall *pending, void *userdata) { + Manager *m = userdata; + DBusMessage *reply; + DBusError error; + const char *name; + + dbus_error_init(&error); + + assert_se(name = dbus_pending_call_get_data(pending, m->name_data_slot)); + assert_se(reply = dbus_pending_call_steal_reply(pending)); + + switch (dbus_message_get_type(reply)) { + + case DBUS_MESSAGE_TYPE_ERROR: + + assert_se(dbus_set_error_from_message(&error, reply)); + log_warning("GetConnectionUnixProcessID() failed: %s", error.message); + break; + + case DBUS_MESSAGE_TYPE_METHOD_RETURN: { + uint32_t r; + + if (!dbus_message_get_args(reply, + &error, + DBUS_TYPE_UINT32, &r, + DBUS_TYPE_INVALID)) { + log_error("Failed to parse GetConnectionUnixProcessID() reply: %s", error.message); + break; + } + + manager_dispatch_bus_query_pid_done(m, name, (pid_t) r); + break; + } + + default: + assert_not_reached("Invalid reply message"); + } + + dbus_message_unref(reply); + dbus_error_free(&error); +} + +int bus_query_pid(Manager *m, const char *name) { + DBusMessage *message = NULL; + DBusPendingCall *pending = NULL; + char *n = NULL; + + assert(m); + assert(name); + + if (!(message = dbus_message_new_method_call( + DBUS_SERVICE_DBUS, + DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS, + "GetConnectionUnixProcessID"))) + goto oom; + + if (!(dbus_message_append_args( + message, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID))) + goto oom; + + if (!dbus_connection_send_with_reply(m->api_bus, message, &pending, -1)) + goto oom; + + if (!(n = strdup(name))) + goto oom; + + if (!dbus_pending_call_set_data(pending, m->name_data_slot, n, free)) + goto oom; + + n = NULL; + + if (!dbus_pending_call_set_notify(pending, query_pid_pending_cb, m, NULL)) + goto oom; + + dbus_message_unref(message); + dbus_pending_call_unref(pending); + + return 0; + +oom: + free(n); + + if (pending) { + dbus_pending_call_cancel(pending); + dbus_pending_call_unref(pending); + } + + if (message) + dbus_message_unref(message); + + return -ENOMEM; +} + DBusHandlerResult bus_default_message_handler(Manager *m, DBusMessage *message, const char*introspection, const BusProperty *properties) { DBusError error; DBusMessage *reply = NULL; diff --git a/dbus.h b/dbus.h index 5b41877e8c3..412c6683197 100644 --- a/dbus.h +++ b/dbus.h @@ -70,6 +70,8 @@ DBusHandlerResult bus_default_message_handler(Manager *m, DBusMessage *message, DBusHandlerResult bus_send_error_reply(Manager *m, DBusMessage *message, DBusError *bus_error, int error); +int bus_query_pid(Manager *m, const char *name); + int bus_property_append_string(Manager *m, DBusMessageIter *i, const char *property, void *data); int bus_property_append_strv(Manager *m, DBusMessageIter *i, const char *property, void *data); int bus_property_append_bool(Manager *m, DBusMessageIter *i, const char *property, void *data); diff --git a/load-fragment.c b/load-fragment.c index 5e98ae83248..8e42188a6b4 100644 --- a/load-fragment.c +++ b/load-fragment.c @@ -1182,6 +1182,7 @@ static int load_from_path(Unit *u, const char *path) { { "SysVStartPriority", config_parse_sysv_priority, &u->service.sysv_start_priority, "Service" }, { "KillMode", config_parse_kill_mode, &u->service.kill_mode, "Service" }, { "NonBlocking", config_parse_bool, &u->service.exec_context.non_blocking, "Service" }, + { "BusName", config_parse_string, &u->service.bus_name, "Service" }, EXEC_CONTEXT_CONFIG_ITEMS(u->service.exec_context, "Service"), { "ListenStream", config_parse_listen, &u->socket, "Socket" }, diff --git a/manager.c b/manager.c index 07b2d4ac9a5..e5dc209bf23 100644 --- a/manager.c +++ b/manager.c @@ -320,6 +320,7 @@ int manager_new(ManagerRunningAs running_as, bool confirm_spawn, Manager **_m) { m->running_as = running_as; m->confirm_spawn = confirm_spawn; + m->name_data_slot = -1; m->signal_watch.fd = m->mount_watch.fd = m->udev_watch.fd = m->epoll_fd = -1; m->current_job_id = 1; /* start as id #1, so that we can leave #0 around as "null-like" value */ @@ -339,6 +340,9 @@ int manager_new(ManagerRunningAs running_as, bool confirm_spawn, Manager **_m) { if (!(m->cgroup_bondings = hashmap_new(string_hash_func, string_compare_func))) goto fail; + if (!(m->watch_bus = hashmap_new(string_hash_func, string_compare_func))) + goto fail; + if ((m->epoll_fd = epoll_create1(EPOLL_CLOEXEC)) < 0) goto fail; @@ -408,6 +412,7 @@ void manager_free(Manager *m) { hashmap_free(m->jobs); hashmap_free(m->transaction_jobs); hashmap_free(m->watch_pids); + hashmap_free(m->watch_bus); if (m->epoll_fd >= 0) close_nointr(m->epoll_fd); @@ -1866,6 +1871,40 @@ void manager_write_utmp_runlevel(Manager *m, Unit *u) { } } +void manager_dispatch_bus_name_owner_changed( + Manager *m, + const char *name, + const char* old_owner, + const char *new_owner) { + + Unit *u; + + assert(m); + assert(name); + + if (!(u = hashmap_get(m->watch_bus, name))) + return; + + UNIT_VTABLE(u)->bus_name_owner_change(u, name, old_owner, new_owner); +} + +void manager_dispatch_bus_query_pid_done( + Manager *m, + const char *name, + pid_t pid) { + + Unit *u; + + assert(m); + assert(name); + assert(pid >= 1); + + if (!(u = hashmap_get(m->watch_bus, name))) + return; + + UNIT_VTABLE(u)->bus_query_pid_done(u, name, pid); +} + static const char* const manager_running_as_table[_MANAGER_RUNNING_AS_MAX] = { [MANAGER_INIT] = "init", [MANAGER_SYSTEM] = "system", diff --git a/manager.h b/manager.h index ae9cd9fe936..cb4af820d9c 100644 --- a/manager.h +++ b/manager.h @@ -178,6 +178,9 @@ struct Manager { DBusConnection *api_bus, *system_bus; Set *subscribed; + Hashmap *watch_bus; /* D-Bus names => Unit object n:1 */ + int32_t name_data_slot; + /* Data specific to the cgroup subsystem */ Hashmap *cgroup_bondings; /* path string => CGroupBonding object 1:n */ char *cgroup_controller; @@ -215,11 +218,13 @@ unsigned manager_dispatch_dbus_queue(Manager *m); int manager_loop(Manager *m); +void manager_write_utmp_reboot(Manager *m); +void manager_write_utmp_runlevel(Manager *m, Unit *t); + +void manager_dispatch_bus_name_owner_changed(Manager *m, const char *name, const char* old_owner, const char *new_owner); +void manager_dispatch_bus_query_pid_done(Manager *m, const char *name, pid_t pid); + const char *manager_running_as_to_string(ManagerRunningAs i); ManagerRunningAs manager_running_as_from_string(const char *s); -void manager_write_utmp_reboot(Manager *m); - -void manager_write_utmp_runlevel(Manager *m, Unit *t); - #endif diff --git a/service.c b/service.c index 96e9d83ad90..bd248a07881 100644 --- a/service.c +++ b/service.c @@ -118,6 +118,12 @@ static void service_done(Unit *u) { service_unwatch_main_pid(s); service_unwatch_control_pid(s); + if (s->bus_name) { + unit_unwatch_bus_name(UNIT(u), s->bus_name); + free(s->bus_name); + s->bus_name = NULL; + } + service_close_socket_fd(s); unit_unwatch_timer(u, &s->timer_watch); @@ -712,6 +718,22 @@ static int service_load_sysv(Service *s) { return 0; } +static int service_add_bus_name(Service *s) { + char *n; + int r; + + assert(s); + assert(s->bus_name); + + if (asprintf(&n, "dbus-%s.service", s->bus_name) < 0) + return 0; + + r = unit_merge_by_name(UNIT(s), n); + free(n); + + return r; +} + static void service_init(Unit *u) { Service *s = SERVICE(u); @@ -741,6 +763,7 @@ static void service_init(Unit *u) { s->failure = false; s->socket_fd = -1; + s->bus_name_good = false; RATELIMIT_INIT(s->ratelimit, 10*USEC_PER_SEC, 5); } @@ -756,6 +779,11 @@ static int service_verify(Service *s) { return -EINVAL; } + if (s->type == SERVICE_DBUS && !s->bus_name) { + log_error("%s is of type D-Bus but no D-Bus service name has been specified. Refusing.", UNIT(s)->meta.id); + return -EINVAL; + } + return 0; } @@ -793,6 +821,14 @@ static int service_load(Unit *u) { if ((r = sysv_chkconfig_order(s)) < 0) return r; + + if (s->bus_name) { + if ((r = service_add_bus_name(s)) < 0) + return r; + + if ((r = unit_watch_bus_name(u, s->bus_name)) < 0) + return r; + } } return service_verify(s); @@ -839,6 +875,13 @@ static void service_dump(Unit *u, FILE *f, const char *prefix) { "%sPIDFile: %s\n", prefix, s->pid_file); + if (s->bus_name) + fprintf(f, + "%sBusName: %s\n" + "%sBus Name Good: %s\n", + prefix, s->bus_name, + prefix, yes_no(s->bus_name_good)); + exec_context_dump(&s->exec_context, f, prefix); for (c = 0; c < _SERVICE_EXEC_COMMAND_MAX; c++) { @@ -1373,7 +1416,9 @@ static void service_enter_running(Service *s, bool success) { if (!success) s->failure = true; - if (main_pid_good(s) != 0 && cgroup_good(s) != 0) + if (main_pid_good(s) != 0 && + cgroup_good(s) != 0 && + (s->bus_name_good || s->type != SERVICE_DBUS)) service_set_state(s, SERVICE_RUNNING); else if (s->valid_no_process) service_set_state(s, SERVICE_EXITED); @@ -1425,7 +1470,7 @@ static void service_enter_start(Service *s) { if ((r = service_spawn(s, s->exec_command[SERVICE_EXEC_START], - s->type == SERVICE_FORKING, + s->type == SERVICE_FORKING || s->type == SERVICE_DBUS, true, true, true, @@ -1451,15 +1496,18 @@ static void service_enter_start(Service *s) { s->control_command = s->exec_command[SERVICE_EXEC_START]; service_set_state(s, SERVICE_START); - } else if (s->type == SERVICE_FINISH) { + } else if (s->type == SERVICE_FINISH || + s->type == SERVICE_DBUS) { /* For finishing services we wait until the start * process exited, too, but it is our main process. */ + /* For D-Bus services we know the main pid right away, + * but wait for the bus name to appear on the bus. */ + s->main_pid = pid; s->main_pid_known = true; - s->control_command = s->exec_command[SERVICE_EXEC_START]; service_set_state(s, SERVICE_START); } else assert_not_reached("Unknown service type"); @@ -2046,6 +2094,72 @@ finish: return r; } +static void service_bus_name_owner_change( + Unit *u, + const char *name, + const char *old_owner, + const char *new_owner) { + + Service *s = SERVICE(u); + + assert(s); + assert(name); + + assert(streq(s->bus_name, name)); + assert(old_owner || new_owner); + + if (old_owner && new_owner) + log_debug("%s's D-Bus name %s changed owner from %s to %s", u->meta.id, name, old_owner, new_owner); + else if (old_owner) + log_debug("%s's D-Bus name %s no longer registered by %s", u->meta.id, name, old_owner); + else + log_debug("%s's D-Bus name %s now registered by %s", u->meta.id, name, new_owner); + + s->bus_name_good = !!new_owner; + + if (s->type == SERVICE_DBUS) { + + /* service_enter_running() will figure out what to + * do */ + if (s->state == SERVICE_RUNNING) + service_enter_running(s, true); + else if (s->state == SERVICE_START && new_owner) + service_enter_start_post(s); + + } else if (new_owner && + s->main_pid <= 0 && + (s->state == SERVICE_START || + s->state == SERVICE_START_POST || + s->state == SERVICE_RUNNING || + s->state == SERVICE_RELOAD)) { + + /* Try to acquire PID from bus service */ + log_debug("Trying to acquire PID from D-Bus name..."); + + bus_query_pid(u->meta.manager, name); + } +} + +static void service_bus_query_pid_done( + Unit *u, + const char *name, + pid_t pid) { + + Service *s = SERVICE(u); + + assert(s); + assert(name); + + log_debug("%s's D-Bus name %s is now owned by process %u", u->meta.id, name, (unsigned) pid); + + if (s->main_pid <= 0 && + (s->state == SERVICE_START || + s->state == SERVICE_START_POST || + s->state == SERVICE_RUNNING || + s->state == SERVICE_RELOAD)) + s->main_pid = pid; +} + int service_set_socket_fd(Service *s, int fd) { assert(s); assert(fd >= 0); @@ -2098,7 +2212,8 @@ DEFINE_STRING_TABLE_LOOKUP(service_restart, ServiceRestart); static const char* const service_type_table[_SERVICE_TYPE_MAX] = { [SERVICE_FORKING] = "forking", [SERVICE_SIMPLE] = "simple", - [SERVICE_FINISH] = "finish" + [SERVICE_FINISH] = "finish", + [SERVICE_DBUS] = "dbus" }; DEFINE_STRING_TABLE_LOOKUP(service_type, ServiceType); @@ -2137,5 +2252,8 @@ const UnitVTable service_vtable = { .cgroup_notify_empty = service_cgroup_notify_event, + .bus_name_owner_change = service_bus_name_owner_change, + .bus_query_pid_done = service_bus_query_pid_done, + .enumerate = service_enumerate }; diff --git a/service.h b/service.h index 5ddc1804239..e603ff74b90 100644 --- a/service.h +++ b/service.h @@ -57,8 +57,9 @@ typedef enum ServiceRestart { typedef enum ServiceType { SERVICE_FORKING, /* forks by itself (i.e. traditional daemons) */ - SERVICE_SIMPLE, /* we fork and go on right-away (i.e. modern socket activated daemons)*/ + SERVICE_SIMPLE, /* we fork and go on right-away (i.e. modern socket activated daemons) */ SERVICE_FINISH, /* we fork and wait until the program finishes (i.e. programs like fsck which run and need to finish before we continue) */ + SERVICE_DBUS, /* we fork and wait until a specific D-Bus name appears on the bus */ _SERVICE_TYPE_MAX, _SERVICE_TYPE_INVALID = -1 } ServiceType; @@ -103,13 +104,18 @@ struct Service { pid_t main_pid, control_pid; bool main_pid_known:1; - bool failure:1; /* if we shut down, remember why */ + /* If we shut down, remember why */ + bool failure:1; + + bool bus_name_good:1; bool sysv_has_lsb:1; char *sysv_path; int sysv_start_priority; char *sysv_runlevels; + char *bus_name; + RateLimit ratelimit; int socket_fd; diff --git a/unit.c b/unit.c index 872abf3e13d..900c76ad66a 100644 --- a/unit.c +++ b/unit.c @@ -1023,6 +1023,9 @@ int unit_watch_pid(Unit *u, pid_t pid) { assert(u); assert(pid >= 1); + /* Watch a specific PID. We only support one unit watching + * each PID for now. */ + return hashmap_put(u->meta.manager->watch_pids, UINT32_TO_PTR(pid), u); } @@ -1030,7 +1033,7 @@ void unit_unwatch_pid(Unit *u, pid_t pid) { assert(u); assert(pid >= 1); - hashmap_remove(u->meta.manager->watch_pids, UINT32_TO_PTR(pid)); + hashmap_remove_value(u->meta.manager->watch_pids, UINT32_TO_PTR(pid), u); } int unit_watch_timer(Unit *u, usec_t delay, Watch *w) { @@ -1606,6 +1609,23 @@ fail: return NULL; } +int unit_watch_bus_name(Unit *u, const char *name) { + assert(u); + assert(name); + + /* Watch a specific name on the bus. We only support one unit + * watching each name for now. */ + + return hashmap_put(u->meta.manager->watch_bus, name, u); +} + +void unit_unwatch_bus_name(Unit *u, const char *name) { + assert(u); + assert(name); + + hashmap_remove_value(u->meta.manager->watch_bus, name, u); +} + static const char* const unit_type_table[_UNIT_TYPE_MAX] = { [UNIT_SERVICE] = "service", [UNIT_TIMER] = "timer", diff --git a/unit.h b/unit.h index c0423354d10..338a58b0098 100644 --- a/unit.h +++ b/unit.h @@ -250,8 +250,17 @@ struct UnitVTable { void (*sigchld_event)(Unit *u, pid_t pid, int code, int status); void (*timer_event)(Unit *u, uint64_t n_elapsed, Watch *w); + /* Called whenever any of the cgroups this unit watches for + * ran empty */ void (*cgroup_notify_empty)(Unit *u); + /* Called whenever a name thus Unit registered for comes or + * goes away. */ + void (*bus_name_owner_change)(Unit *u, const char *name, const char *old_owner, const char *new_owner); + + /* Called whenever a bus PID lookup finishes */ + void (*bus_query_pid_done)(Unit *u, const char *name, pid_t pid); + /* This is called for each unit type and should be used to * enumerate existing devices and load them. However, * everything that is loaded here should still stay in @@ -348,6 +357,9 @@ void unit_unwatch_pid(Unit *u, pid_t pid); int unit_watch_timer(Unit *u, usec_t delay, Watch *w); void unit_unwatch_timer(Unit *u, Watch *w); +int unit_watch_bus_name(Unit *u, const char *name); +void unit_unwatch_bus_name(Unit *u, const char *name); + bool unit_job_is_applicable(Unit *u, JobType j); int set_unit_path(const char *p);