chiark / gitweb /
service: optionally, trie dbus name cycle to service cycle
[elogind.git] / service.c
index 9ab8ec1a430833cfa2232dc5d816244bbc815101..bd248a07881c387f01c93365a2384239965d4f96 100644 (file)
--- a/service.c
+++ b/service.c
@@ -85,6 +85,16 @@ static void service_unwatch_main_pid(Service *s) {
         s->main_pid = 0;
 }
 
+static void service_close_socket_fd(Service *s) {
+        assert(s);
+
+        if (s->socket_fd < 0)
+                return;
+
+        close_nointr_nofail(s->socket_fd);
+        s->socket_fd = -1;
+}
+
 static void service_done(Unit *u) {
         Service *s = SERVICE(u);
 
@@ -108,6 +118,14 @@ 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);
 }
 
@@ -700,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);
 
@@ -728,6 +762,9 @@ static void service_init(Unit *u) {
         s->main_pid_known = false;
         s->failure = false;
 
+        s->socket_fd = -1;
+        s->bus_name_good = false;
+
         RATELIMIT_INIT(s->ratelimit, 10*USEC_PER_SEC, 5);
 }
 
@@ -742,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;
 }
 
@@ -779,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);
@@ -825,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++) {
@@ -944,7 +1001,6 @@ fail:
         return r;
 }
 
-
 static int service_notify_sockets_dead(Service *s) {
         Iterator i;
         Set *set;
@@ -954,7 +1010,6 @@ static int service_notify_sockets_dead(Service *s) {
         assert(s);
 
         /* Notifies all our sockets when we die */
-
         if ((r = service_get_sockets(s, &set)) < 0)
                 return r;
 
@@ -1020,6 +1075,11 @@ static void service_set_state(Service *s, ServiceState state) {
             state == SERVICE_AUTO_RESTART)
                 service_notify_sockets_dead(s);
 
+        if (state != SERVICE_START_PRE &&
+            state != SERVICE_START &&
+            !(state == SERVICE_DEAD && UNIT(s)->meta.job))
+                service_close_socket_fd(s);
+
         if (old_state != state)
                 log_debug("%s changed %s → %s", UNIT(s)->meta.id, service_state_to_string(old_state), service_state_to_string(state));
 
@@ -1106,9 +1166,13 @@ static int service_spawn(
         assert(c);
         assert(_pid);
 
-        if (pass_fds)
-                if ((r = service_collect_fds(s, &fds, &n_fds)) < 0)
+        if (pass_fds) {
+                if (s->socket_fd >= 0) {
+                        fds = &s->socket_fd;
+                        n_fds = 1;
+                } else if ((r = service_collect_fds(s, &fds, &n_fds)) < 0)
                         goto fail;
+        }
 
         if (timeout) {
                 if ((r = unit_watch_timer(UNIT(s), s->timeout_usec, &s->timer_watch)) < 0)
@@ -1135,11 +1199,17 @@ static int service_spawn(
         if (r < 0)
                 goto fail;
 
+        if (fds) {
+                if (s->socket_fd >= 0)
+                        service_close_socket_fd(s);
+                else
+                        free(fds);
+        }
+
         if ((r = unit_watch_pid(UNIT(s), pid)) < 0)
                 /* FIXME: we need to do something here */
                 goto fail;
 
-        free(fds);
         *_pid = pid;
 
         return 0;
@@ -1346,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);
@@ -1398,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,
@@ -1424,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");
@@ -2019,6 +2094,93 @@ 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);
+
+        /* This is called by the socket code when instantiating a new
+         * service for a stream socket and the socket needs to be
+         * configured. */
+
+        if (UNIT(s)->meta.load_state != UNIT_LOADED)
+                return -EINVAL;
+
+        if (s->socket_fd >= 0)
+                return -EBUSY;
+
+        if (s->state != SERVICE_DEAD)
+                return -EAGAIN;
+
+        s->socket_fd = fd;
+        return 0;
+}
+
 static const char* const service_state_table[_SERVICE_STATE_MAX] = {
         [SERVICE_DEAD] = "dead",
         [SERVICE_START_PRE] = "start-pre",
@@ -2050,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);
@@ -2089,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
 };