chiark / gitweb /
manager: properly parse finish timestamp
[elogind.git] / src / manager.c
index bc589bacc7ffd8e1c17f0d6ac7d2e8b8b2741a1a..e90757eacd76873a4e844cef11762463f19816aa 100644 (file)
@@ -92,7 +92,7 @@ static int manager_setup_notify(Manager *m) {
         else
                 strncpy(sa.un.sun_path+1, NOTIFY_SOCKET, sizeof(sa.un.sun_path)-1);
 
-        if (bind(m->notify_watch.fd, &sa.sa, sizeof(sa_family_t) + 1 + strlen(sa.un.sun_path+1)) < 0) {
+        if (bind(m->notify_watch.fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + 1 + strlen(sa.un.sun_path+1)) < 0) {
                 log_error("bind() failed: %m");
                 return -errno;
         }
@@ -164,11 +164,16 @@ static int manager_setup_signals(Manager *m) {
                         SIGWINCH,    /* Kernel sends us this on kbrequest (alt-arrowup) */
                         SIGPWR,      /* Some kernel drivers and upsd send us this on power failure */
                         SIGRTMIN+0,  /* systemd: start default.target */
-                        SIGRTMIN+1,  /* systemd: start rescue.target */
+                        SIGRTMIN+1,  /* systemd: isolate rescue.target */
                         SIGRTMIN+2,  /* systemd: isolate emergency.target */
                         SIGRTMIN+3,  /* systemd: start halt.target */
                         SIGRTMIN+4,  /* systemd: start poweroff.target */
                         SIGRTMIN+5,  /* systemd: start reboot.target */
+                        SIGRTMIN+6,  /* systemd: start kexec.target */
+                        SIGRTMIN+13, /* systemd: Immediate halt */
+                        SIGRTMIN+14, /* systemd: Immediate poweroff */
+                        SIGRTMIN+15, /* systemd: Immediate reboot */
+                        SIGRTMIN+16, /* systemd: Immediate kexec */
                         -1);
         assert_se(sigprocmask(SIG_SETMASK, &mask, NULL) == 0);
 
@@ -211,7 +216,7 @@ int manager_new(ManagerRunningAs running_as, Manager **_m) {
         m->audit_fd = -1;
 #endif
 
-        m->signal_watch.fd = m->mount_watch.fd = m->udev_watch.fd = m->epoll_fd = m->dev_autofs_fd = -1;
+        m->signal_watch.fd = m->mount_watch.fd = m->udev_watch.fd = m->epoll_fd = m->dev_autofs_fd = m->swap_watch.fd = -1;
         m->current_job_id = 1; /* start as id #1, so that we can leave #0 around as "null-like" value */
 
         if (!(m->environment = strv_copy(environ)))
@@ -982,14 +987,14 @@ static int transaction_verify_order_one(Manager *m, Job *j, Job *from, unsigned
 
 
                 if (delete) {
-                        log_warning("Breaking ordering cycle by deleting job %s/%s", k->unit->meta.id, job_type_to_string(k->type));
+                        log_warning("Breaking ordering cycle by deleting job %s/%s", delete->unit->meta.id, job_type_to_string(delete->type));
                         transaction_delete_unit(m, delete->unit);
                         return -EAGAIN;
                 }
 
                 log_error("Unable to break cycle");
 
-                dbus_set_error(e, BUS_ERROR_TRANSACTION_ORDER_IS_CYCLIC, "Transaction order is cyclic. See logs for details.");
+                dbus_set_error(e, BUS_ERROR_TRANSACTION_ORDER_IS_CYCLIC, "Transaction order is cyclic. See system logs for details.");
                 return -ENOEXEC;
         }
 
@@ -1182,6 +1187,7 @@ static int transaction_apply(Manager *m) {
 
                 j->unit->meta.job = j;
                 j->installed = true;
+                m->n_installed_jobs ++;
 
                 /* We're fully installed. Now let's free data we don't
                  * need anymore. */
@@ -1397,18 +1403,27 @@ static int transaction_add_job_and_dependencies(
         assert(type < _JOB_TYPE_MAX);
         assert(unit);
 
-        if (unit->meta.load_state != UNIT_LOADED && unit->meta.load_state != UNIT_ERROR) {
+        if (unit->meta.load_state != UNIT_LOADED &&
+            unit->meta.load_state != UNIT_ERROR &&
+            unit->meta.load_state != UNIT_MASKED) {
                 dbus_set_error(e, BUS_ERROR_LOAD_FAILED, "Unit %s is not loaded properly.", unit->meta.id);
                 return -EINVAL;
         }
 
         if (type != JOB_STOP && unit->meta.load_state == UNIT_ERROR) {
-                dbus_set_error(e, BUS_ERROR_LOAD_FAILED, "Unit %s failed to load: %s. You might find more information in the logs.",
+                dbus_set_error(e, BUS_ERROR_LOAD_FAILED,
+                               "Unit %s failed to load: %s. "
+                               "You might find more information in the system logs.",
                                unit->meta.id,
                                strerror(-unit->meta.load_error));
                 return -EINVAL;
         }
 
+        if (type != JOB_STOP && unit->meta.load_state == UNIT_MASKED) {
+                dbus_set_error(e, BUS_ERROR_MASKED, "Unit %s is masked.", unit->meta.id);
+                return -EINVAL;
+        }
+
         if (!unit_job_is_applicable(unit, type)) {
                 dbus_set_error(e, BUS_ERROR_JOB_TYPE_NOT_APPLICABLE, "Job type %s is not applicable for unit %s.", job_type_to_string(type), unit->meta.id);
                 return -EBADR;
@@ -1535,7 +1550,7 @@ int manager_add_job(Manager *m, JobType type, Unit *unit, JobMode mode, bool ove
                 return -EPERM;
         }
 
-        log_debug("Trying to enqueue job %s/%s", unit->meta.id, job_type_to_string(type));
+        log_debug("Trying to enqueue job %s/%s/%s", unit->meta.id, job_type_to_string(type), job_mode_to_string(mode));
 
         if ((r = transaction_add_job_and_dependencies(m, type, unit, NULL, true, override, false, e, &ret)) < 0) {
                 transaction_abort(m);
@@ -1631,7 +1646,7 @@ int manager_load_unit_prepare(Manager *m, const char *name, const char *path, DB
         if (!name)
                 name = file_name_from_path(path);
 
-        if (!unit_name_is_valid(name)) {
+        if (!unit_name_is_valid(name, false)) {
                 dbus_set_error(e, BUS_ERROR_INVALID_NAME, "Unit name %s is not valid.", name);
                 return -EINVAL;
         }
@@ -1977,7 +1992,7 @@ static int manager_process_signal_fd(Manager *m) {
                         }
 
                         /* Run the exit target if there is one, if not, just exit. */
-                        if (manager_start_target(m, SPECIAL_EXIT_SERVICE, JOB_REPLACE) < 0) {
+                        if (manager_start_target(m, SPECIAL_EXIT_TARGET, JOB_REPLACE) < 0) {
                                 m->exit_code = MANAGER_EXIT;
                                 return 0;
                         }
@@ -2048,22 +2063,38 @@ static int manager_process_signal_fd(Manager *m) {
                         break;
 
                 default: {
-                        static const char * const table[] = {
+                        /* Starting SIGRTMIN+0 */
+                        static const char * const target_table[] = {
                                 [0] = SPECIAL_DEFAULT_TARGET,
                                 [1] = SPECIAL_RESCUE_TARGET,
                                 [2] = SPECIAL_EMERGENCY_TARGET,
                                 [3] = SPECIAL_HALT_TARGET,
                                 [4] = SPECIAL_POWEROFF_TARGET,
-                                [5] = SPECIAL_REBOOT_TARGET
+                                [5] = SPECIAL_REBOOT_TARGET,
+                                [6] = SPECIAL_KEXEC_TARGET
+                        };
+
+                        /* Starting SIGRTMIN+13, so that target halt and system halt are 10 apart */
+                        static const ManagerExitCode code_table[] = {
+                                [0] = MANAGER_HALT,
+                                [1] = MANAGER_POWEROFF,
+                                [2] = MANAGER_REBOOT,
+                                [3] = MANAGER_KEXEC
                         };
 
                         if ((int) sfsi.ssi_signo >= SIGRTMIN+0 &&
-                            (int) sfsi.ssi_signo < SIGRTMIN+(int) ELEMENTSOF(table)) {
-                                manager_start_target(m, table[sfsi.ssi_signo - SIGRTMIN],
+                            (int) sfsi.ssi_signo < SIGRTMIN+(int) ELEMENTSOF(target_table)) {
+                                manager_start_target(m, target_table[sfsi.ssi_signo - SIGRTMIN],
                                                      (sfsi.ssi_signo == 1 || sfsi.ssi_signo == 2) ? JOB_ISOLATE : JOB_REPLACE);
                                 break;
                         }
 
+                        if ((int) sfsi.ssi_signo >= SIGRTMIN+13 &&
+                            (int) sfsi.ssi_signo < SIGRTMIN+13+(int) ELEMENTSOF(code_table)) {
+                                m->exit_code = code_table[sfsi.ssi_signo - SIGRTMIN - 13];
+                                break;
+                        }
+
                         log_warning("Got unhandled signal <%s>.", strna(signal_to_string(sfsi.ssi_signo)));
                 }
                 }
@@ -2140,6 +2171,11 @@ static int process_event(Manager *m, struct epoll_event *ev) {
                 mount_fd_event(m, ev->events);
                 break;
 
+        case WATCH_SWAP:
+                /* Some swap table change, intended for the swap subsystem */
+                swap_fd_event(m, ev->events);
+                break;
+
         case WATCH_UDEV:
                 /* Some notification from udev, intended for the device subsystem */
                 device_fd_event(m, ev->events);
@@ -2172,6 +2208,8 @@ int manager_loop(Manager *m) {
         set_free_free(m->unit_path_cache);
         m->unit_path_cache = NULL;
 
+        manager_check_finished(m);
+
         /* There might still be some zombies hanging around from
          * before we were exec()'ed. Leat's reap them */
         if ((r = manager_dispatch_sigchld(m)) < 0)
@@ -2205,6 +2243,9 @@ int manager_loop(Manager *m) {
                 if (manager_dispatch_dbus_queue(m) > 0)
                         continue;
 
+                if (swap_dispatch_reload(m) > 0)
+                        continue;
+
                 if ((n = epoll_wait(m->epoll_fd, &event, 1, -1)) < 0) {
 
                         if (errno == EINTR)
@@ -2296,6 +2337,75 @@ void manager_send_unit_audit(Manager *m, Unit *u, int type, bool success) {
 
 }
 
+void manager_send_unit_plymouth(Manager *m, Unit *u) {
+        int fd = -1;
+        union sockaddr_union sa;
+        int n = 0;
+        char *message = NULL;
+        ssize_t r;
+
+        /* Don't generate plymouth events if the service was already
+         * started and we're just deserializing */
+        if (m->n_deserializing > 0)
+                return;
+
+        if (m->running_as != MANAGER_SYSTEM)
+                return;
+
+        if (u->meta.type != UNIT_SERVICE &&
+            u->meta.type != UNIT_MOUNT &&
+            u->meta.type != UNIT_SWAP)
+                return;
+
+        /* We set SOCK_NONBLOCK here so that we rather drop the
+         * message then wait for plymouth */
+        if ((fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0)) < 0) {
+                log_error("socket() failed: %m");
+                return;
+        }
+
+        zero(sa);
+        sa.sa.sa_family = AF_UNIX;
+        strncpy(sa.un.sun_path+1, "/ply-boot-protocol", sizeof(sa.un.sun_path)-1);
+        if (connect(fd, &sa.sa, sizeof(sa.un)) < 0) {
+
+                if (errno != EPIPE &&
+                    errno != EAGAIN &&
+                    errno != ENOENT &&
+                    errno != ECONNREFUSED &&
+                    errno != ECONNRESET &&
+                    errno != ECONNABORTED)
+                        log_error("connect() failed: %m");
+
+                goto finish;
+        }
+
+        if (asprintf(&message, "U\002%c%s%n", (int) (strlen(u->meta.id) + 1), u->meta.id, &n) < 0) {
+                log_error("Out of memory");
+                goto finish;
+        }
+
+        errno = 0;
+        if ((r = write(fd, message, n + 1)) != n + 1) {
+
+                if (errno != EPIPE &&
+                    errno != EAGAIN &&
+                    errno != ENOENT &&
+                    errno != ECONNREFUSED &&
+                    errno != ECONNRESET &&
+                    errno != ECONNABORTED)
+                        log_error("Failed to write Plymouth message: %m");
+
+                goto finish;
+        }
+
+finish:
+        if (fd >= 0)
+                close_nointr_nofail(fd);
+
+        free(message);
+}
+
 void manager_dispatch_bus_name_owner_changed(
                 Manager *m,
                 const char *name,
@@ -2380,10 +2490,17 @@ int manager_serialize(Manager *m, FILE *f, FDSet *fds) {
         assert(f);
         assert(fds);
 
-        fprintf(f, "startup-timestamp=%llu %llu\n\n",
+        fprintf(f, "startup-timestamp=%llu %llu\n",
                 (unsigned long long) m->startup_timestamp.realtime,
                 (unsigned long long) m->startup_timestamp.monotonic);
 
+        if (dual_timestamp_is_set(&m->finish_timestamp))
+                fprintf(f, "finish-timestamp=%llu %llu\n",
+                        (unsigned long long) m->finish_timestamp.realtime,
+                        (unsigned long long) m->finish_timestamp.monotonic);
+
+        fputc('\n', f);
+
         HASHMAP_FOREACH_KEY(u, t, m->units, i) {
                 if (u->meta.id != t)
                         continue;
@@ -2442,6 +2559,15 @@ int manager_deserialize(Manager *m, FILE *f, FDSet *fds) {
                                 m->startup_timestamp.realtime = a;
                                 m->startup_timestamp.monotonic = b;
                         }
+                } else if (startswith(l, "finish-timestamp=")) {
+                        unsigned long long a, b;
+
+                        if (sscanf(l+17, "%lli %llu", &a, &b) != 2)
+                                log_debug("Failed to parse finish timestamp value %s", l+17);
+                        else {
+                                m->finish_timestamp.realtime = a;
+                                m->finish_timestamp.monotonic = b;
+                        }
                 } else
                         log_debug("Unknown serialization item '%s'", l);
         }
@@ -2584,6 +2710,47 @@ int manager_set_console(Manager *m, const char *console) {
         return 0;
 }
 
+bool manager_unit_pending_inactive(Manager *m, const char *name) {
+        Unit *u;
+
+        assert(m);
+        assert(name);
+
+        /* Returns true if the unit is inactive or going down */
+        if (!(u = manager_get_unit(m, name)))
+                return true;
+
+        return unit_pending_inactive(u);
+}
+
+void manager_check_finished(Manager *m) {
+        char userspace[FORMAT_TIMESPAN_MAX], kernel[FORMAT_TIMESPAN_MAX], sum[FORMAT_TIMESPAN_MAX];
+
+        assert(m);
+
+        if (dual_timestamp_is_set(&m->finish_timestamp))
+                return;
+
+        if (hashmap_size(m->jobs) > 0)
+                return;
+
+        dual_timestamp_get(&m->finish_timestamp);
+
+        if (m->running_as == MANAGER_SYSTEM)
+                log_info("Startup finished in %s (kernel) + %s (userspace) = %s.",
+                         format_timespan(kernel, sizeof(kernel),
+                                         m->startup_timestamp.monotonic),
+                         format_timespan(userspace, sizeof(userspace),
+                                         m->finish_timestamp.monotonic - m->startup_timestamp.monotonic),
+                         format_timespan(sum, sizeof(sum),
+                                         m->finish_timestamp.monotonic));
+        else
+                log_debug("Startup finished in %s.",
+                          format_timespan(userspace, sizeof(userspace),
+                                          m->finish_timestamp.monotonic - m->startup_timestamp.monotonic));
+
+}
+
 static const char* const manager_running_as_table[_MANAGER_RUNNING_AS_MAX] = {
         [MANAGER_SYSTEM] = "system",
         [MANAGER_SESSION] = "session"