chiark / gitweb /
util: add a bit of syntactic sugar to run short code fragments with a different umask
[elogind.git] / src / core / manager.c
index bd49892cd46a9b08e61b91be3f7d219fb61c5d7e..e26522a4dd0944460c9e3baca37ee05522eace40 100644 (file)
 /* As soon as 5s passed since a unit was added to our GC queue, make sure to run a gc sweep */
 #define GC_QUEUE_USEC_MAX (10*USEC_PER_SEC)
 
+/* Initial delay and the interval for printing status messages about running jobs */
+#define JOBS_IN_PROGRESS_WAIT_SEC 5
+#define JOBS_IN_PROGRESS_PERIOD_SEC 1
+#define JOBS_IN_PROGRESS_PERIOD_DIVISOR 3
+
 /* Where clients shall send notification messages to */
 #define NOTIFY_SOCKET "@/org/freedesktop/systemd1/notify"
 
@@ -90,7 +95,7 @@ static int manager_setup_notify(Manager *m) {
                 struct sockaddr_un un;
         } sa;
         struct epoll_event ev;
-        int one = 1;
+        int one = 1, r;
 
         assert(m);
 
@@ -111,12 +116,15 @@ static int manager_setup_notify(Manager *m) {
 
         sa.un.sun_path[0] = 0;
 
-        if (bind(m->notify_watch.fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + 1 + strlen(sa.un.sun_path+1)) < 0) {
+        r = bind(m->notify_watch.fd, &sa.sa,
+                 offsetof(struct sockaddr_un, sun_path) + 1 + strlen(sa.un.sun_path+1));
+        if (r < 0) {
                 log_error("bind() failed: %m");
                 return -errno;
         }
 
-        if (setsockopt(m->notify_watch.fd, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one)) < 0) {
+        r = setsockopt(m->notify_watch.fd, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one));
+        if (r < 0) {
                 log_error("SO_PASSCRED failed: %m");
                 return -errno;
         }
@@ -125,7 +133,8 @@ static int manager_setup_notify(Manager *m) {
         ev.events = EPOLLIN;
         ev.data.ptr = &m->notify_watch;
 
-        if (epoll_ctl(m->epoll_fd, EPOLL_CTL_ADD, m->notify_watch.fd, &ev) < 0) {
+        r = epoll_ctl(m->epoll_fd, EPOLL_CTL_ADD, m->notify_watch.fd, &ev);
+        if (r < 0) {
                 log_error("Failed to add notification socket fd to epoll: %m");
                 return -errno;
         }
@@ -140,6 +149,143 @@ static int manager_setup_notify(Manager *m) {
         return 0;
 }
 
+static int manager_jobs_in_progress_mod_timer(Manager *m) {
+        struct itimerspec its;
+
+        if (m->jobs_in_progress_watch.type != WATCH_JOBS_IN_PROGRESS)
+                return 0;
+
+        zero(its);
+
+        its.it_value.tv_sec = JOBS_IN_PROGRESS_WAIT_SEC;
+        its.it_interval.tv_sec = JOBS_IN_PROGRESS_PERIOD_SEC;
+
+        if (timerfd_settime(m->jobs_in_progress_watch.fd, 0, &its, NULL) < 0)
+                return -errno;
+
+        return 0;
+}
+
+static int manager_watch_jobs_in_progress(Manager *m) {
+        struct epoll_event ev;
+        int r;
+
+        assert(m);
+
+        if (m->jobs_in_progress_watch.type != WATCH_INVALID)
+                return 0;
+
+        m->jobs_in_progress_watch.type = WATCH_JOBS_IN_PROGRESS;
+        m->jobs_in_progress_watch.fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK|TFD_CLOEXEC);
+        if (m->jobs_in_progress_watch.fd < 0) {
+                log_error("Failed to create timerfd: %m");
+                r = -errno;
+                goto err;
+        }
+
+        r = manager_jobs_in_progress_mod_timer(m);
+        if (r < 0) {
+                log_error("Failed to set up timer for jobs progress watch: %s", strerror(-r));
+                goto err;
+        }
+
+        zero(ev);
+        ev.events = EPOLLIN;
+        ev.data.ptr = &m->jobs_in_progress_watch;
+
+        if (epoll_ctl(m->epoll_fd, EPOLL_CTL_ADD, m->jobs_in_progress_watch.fd, &ev) < 0) {
+                log_error("Failed to add jobs progress timer fd to epoll: %m");
+                r = -errno;
+                goto err;
+        }
+
+        log_debug("Set up jobs progress timerfd.");
+
+        return 0;
+
+err:
+        if (m->jobs_in_progress_watch.fd >= 0)
+                close_nointr_nofail(m->jobs_in_progress_watch.fd);
+        watch_init(&m->jobs_in_progress_watch);
+        return r;
+}
+
+static void manager_unwatch_jobs_in_progress(Manager *m) {
+        if (m->jobs_in_progress_watch.type != WATCH_JOBS_IN_PROGRESS)
+                return;
+
+        assert_se(epoll_ctl(m->epoll_fd, EPOLL_CTL_DEL, m->jobs_in_progress_watch.fd, NULL) >= 0);
+        close_nointr_nofail(m->jobs_in_progress_watch.fd);
+        watch_init(&m->jobs_in_progress_watch);
+        m->jobs_in_progress_iteration = 0;
+
+        log_debug("Closed jobs progress timerfd.");
+}
+
+#define CYLON_BUFFER_EXTRA (2*strlen(ANSI_RED_ON) + strlen(ANSI_HIGHLIGHT_RED_ON) + 2*strlen(ANSI_HIGHLIGHT_OFF))
+static void draw_cylon(char buffer[], size_t buflen, unsigned width, unsigned pos) {
+        char *p = buffer;
+
+        assert(buflen >= CYLON_BUFFER_EXTRA + width + 1);
+        assert(pos <= width+1); /* 0 or width+1 mean that the center light is behind the corner */
+
+        if (pos > 1) {
+                if (pos > 2)
+                        p = mempset(p, ' ', pos-2);
+                p = stpcpy(p, ANSI_RED_ON);
+                *p++ = '*';
+        }
+
+        if (pos > 0 && pos <= width) {
+                p = stpcpy(p, ANSI_HIGHLIGHT_RED_ON);
+                *p++ = '*';
+        }
+
+        p = stpcpy(p, ANSI_HIGHLIGHT_OFF);
+
+        if (pos < width) {
+                p = stpcpy(p, ANSI_RED_ON);
+                *p++ = '*';
+                if (pos < width-1)
+                        p = mempset(p, ' ', width-1-pos);
+                p = stpcpy(p, ANSI_HIGHLIGHT_OFF);
+        }
+}
+
+static void manager_print_jobs_in_progress(Manager *m) {
+        Iterator i;
+        Job *j;
+        char *job_of_n = NULL;
+        unsigned counter = 0, print_nr;
+        char cylon[6 + CYLON_BUFFER_EXTRA + 1];
+        unsigned cylon_pos;
+
+        print_nr = (m->jobs_in_progress_iteration / JOBS_IN_PROGRESS_PERIOD_DIVISOR) % m->n_running_jobs;
+
+        HASHMAP_FOREACH(j, m->jobs, i)
+                if (j->state == JOB_RUNNING && counter++ == print_nr)
+                        break;
+
+        /* m->n_running_jobs must be consistent with the contents of m->jobs,
+         * so the above loop must have succeeded in finding j. */
+        assert(counter == print_nr + 1);
+
+        cylon_pos = m->jobs_in_progress_iteration % 14;
+        if (cylon_pos >= 8)
+                cylon_pos = 14 - cylon_pos;
+        draw_cylon(cylon, sizeof(cylon), 6, cylon_pos);
+
+        if (m->n_running_jobs > 1)
+                if (asprintf(&job_of_n, "(%u of %u) ", counter, m->n_running_jobs) < 0)
+                        job_of_n = NULL;
+
+        manager_status_printf(m, true, cylon, "%sA %s job is running for %s",
+                              strempty(job_of_n), job_type_to_string(j->type), unit_description(j->unit));
+        free(job_of_n);
+
+        m->jobs_in_progress_iteration++;
+}
+
 static int manager_setup_time_change(Manager *m) {
         struct epoll_event ev;
         struct itimerspec its;
@@ -309,7 +455,9 @@ int manager_new(SystemdRunningAs running_as, Manager **_m) {
 
         dual_timestamp_get(&m->userspace_timestamp);
         dual_timestamp_from_monotonic(&m->kernel_timestamp, 0);
+#ifdef ENABLE_EFI
         efi_get_boot_timestamps(&m->userspace_timestamp, &m->firmware_timestamp, &m->loader_timestamp);
+#endif
 
         m->running_as = running_as;
         m->name_data_slot = m->conn_data_slot = m->subscribed_data_slot = -1;
@@ -322,6 +470,7 @@ int manager_new(SystemdRunningAs running_as, Manager **_m) {
         watch_init(&m->swap_watch);
         watch_init(&m->udev_watch);
         watch_init(&m->time_change_watch);
+        watch_init(&m->jobs_in_progress_watch);
 
         m->epoll_fd = m->dev_autofs_fd = -1;
         m->current_job_id = 1; /* start as id #1, so that we can leave #0 around as "null-like" value */
@@ -527,6 +676,9 @@ static void manager_clear_jobs_and_units(Manager *m) {
 
         assert(hashmap_isempty(m->jobs));
         assert(hashmap_isempty(m->units));
+
+        m->n_on_console = 0;
+        m->n_running_jobs = 0;
 }
 
 void manager_free(Manager *m) {
@@ -562,6 +714,8 @@ void manager_free(Manager *m) {
                 close_nointr_nofail(m->notify_watch.fd);
         if (m->time_change_watch.fd >= 0)
                 close_nointr_nofail(m->time_change_watch.fd);
+        if (m->jobs_in_progress_watch.fd >= 0)
+                close_nointr_nofail(m->jobs_in_progress_watch.fd);
 
         free(m->notify_socket);
 
@@ -764,7 +918,7 @@ int manager_add_job(Manager *m, JobType type, Unit *unit, JobMode mode, bool ove
 
         job_type_collapse(&type, unit);
 
-        tr = transaction_new();
+        tr = transaction_new(mode == JOB_REPLACE_IRREVERSIBLY);
         if (!tr)
                 return -ENOMEM;
 
@@ -986,6 +1140,10 @@ unsigned manager_dispatch_run_queue(Manager *m) {
         }
 
         m->dispatching_run_queue = false;
+
+        if (m->n_running_jobs > 0)
+                manager_watch_jobs_in_progress(m);
+
         return n;
 }
 
@@ -1034,7 +1192,7 @@ static int manager_process_notify_fd(Manager *m) {
                         uint8_t buf[CMSG_SPACE(sizeof(struct ucred))];
                 } control;
                 Unit *u;
-                char **tags;
+                char _cleanup_strv_free_ **tags = NULL;
 
                 zero(iovec);
                 iovec.iov_base = buf;
@@ -1087,8 +1245,6 @@ static int manager_process_notify_fd(Manager *m) {
 
                 if (UNIT_VTABLE(u)->notify_message)
                         UNIT_VTABLE(u)->notify_message(u, ucred->pid, tags);
-
-                strv_free(tags);
         }
 
         return 0;
@@ -1513,6 +1669,8 @@ static int process_event(Manager *m, struct epoll_event *ev) {
                            NULL);
 
                 /* Restart the watch */
+                epoll_ctl(m->epoll_fd, EPOLL_CTL_DEL, m->time_change_watch.fd,
+                          NULL);
                 close_nointr_nofail(m->time_change_watch.fd);
                 watch_init(&m->time_change_watch);
                 manager_setup_time_change(m);
@@ -1525,6 +1683,16 @@ static int process_event(Manager *m, struct epoll_event *ev) {
                 break;
         }
 
+        case WATCH_JOBS_IN_PROGRESS: {
+                uint64_t v;
+
+                /* not interested in the data */
+                read(w->fd, &v, sizeof(v));
+
+                manager_print_jobs_in_progress(m);
+                break;
+        }
+
         default:
                 log_error("event type=%i", w->type);
                 assert_not_reached("Unknown epoll event type.");
@@ -1732,7 +1900,8 @@ void manager_send_unit_plymouth(Manager *m, Unit *u) {
 
         /* 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) {
+        fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
+        if (fd < 0) {
                 log_error("socket() failed: %m");
                 return;
         }
@@ -1815,7 +1984,6 @@ void manager_dispatch_bus_query_pid_done(
 
 int manager_open_serialization(Manager *m, FILE **_f) {
         char *path = NULL;
-        mode_t saved_umask;
         int fd;
         FILE *f;
 
@@ -1829,9 +1997,9 @@ int manager_open_serialization(Manager *m, FILE **_f) {
         if (!path)
                 return -ENOMEM;
 
-        saved_umask = umask(0077);
-        fd = mkostemp(path, O_RDWR|O_CLOEXEC);
-        umask(saved_umask);
+        RUN_WITH_UMASK(0077) {
+                fd = mkostemp(path, O_RDWR|O_CLOEXEC);
+        }
 
         if (fd < 0) {
                 free(path);
@@ -2150,7 +2318,7 @@ finish:
         return r;
 }
 
-bool manager_is_booting_or_shutting_down(Manager *m) {
+static bool manager_is_booting_or_shutting_down(Manager *m) {
         Unit *u;
 
         assert(m);
@@ -2167,6 +2335,12 @@ bool manager_is_booting_or_shutting_down(Manager *m) {
         return false;
 }
 
+bool manager_is_reloading_or_reexecuting(Manager *m) {
+        assert(m);
+
+        return m->n_reloading != 0;
+}
+
 void manager_reset_failed(Manager *m) {
         Unit *u;
         Iterator i;
@@ -2197,8 +2371,13 @@ void manager_check_finished(Manager *m) {
 
         assert(m);
 
-        if (hashmap_size(m->jobs) > 0)
+        if (m->n_running_jobs == 0)
+                manager_unwatch_jobs_in_progress(m);
+
+        if (hashmap_size(m->jobs) > 0) {
+                manager_jobs_in_progress_mod_timer(m);
                 return;
+        }
 
         /* Notify Type=idle units that we are done now */
         close_pipe(m->idle_pipe);
@@ -2235,10 +2414,10 @@ void manager_check_finished(Manager *m) {
                                            "INITRD_USEC=%llu", (unsigned long long) initrd_usec,
                                            "USERSPACE_USEC=%llu", (unsigned long long) userspace_usec,
                                            "MESSAGE=Startup finished in %s (kernel) + %s (initrd) + %s (userspace) = %s.",
-                                           format_timespan(kernel, sizeof(kernel), kernel_usec),
-                                           format_timespan(initrd, sizeof(initrd), initrd_usec),
-                                           format_timespan(userspace, sizeof(userspace), userspace_usec),
-                                           format_timespan(sum, sizeof(sum), total_usec),
+                                           format_timespan(kernel, sizeof(kernel), kernel_usec, USEC_PER_MSEC),
+                                           format_timespan(initrd, sizeof(initrd), initrd_usec, USEC_PER_MSEC),
+                                           format_timespan(userspace, sizeof(userspace), userspace_usec, USEC_PER_MSEC),
+                                           format_timespan(sum, sizeof(sum), total_usec, USEC_PER_MSEC),
                                            NULL);
                 } else {
                         kernel_usec = m->userspace_timestamp.monotonic - m->kernel_timestamp.monotonic;
@@ -2250,9 +2429,9 @@ void manager_check_finished(Manager *m) {
                                            "KERNEL_USEC=%llu", (unsigned long long) kernel_usec,
                                            "USERSPACE_USEC=%llu", (unsigned long long) userspace_usec,
                                            "MESSAGE=Startup finished in %s (kernel) + %s (userspace) = %s.",
-                                           format_timespan(kernel, sizeof(kernel), kernel_usec),
-                                           format_timespan(userspace, sizeof(userspace), userspace_usec),
-                                           format_timespan(sum, sizeof(sum), total_usec),
+                                           format_timespan(kernel, sizeof(kernel), kernel_usec, USEC_PER_MSEC),
+                                           format_timespan(userspace, sizeof(userspace), userspace_usec, USEC_PER_MSEC),
+                                           format_timespan(sum, sizeof(sum), total_usec, USEC_PER_MSEC),
                                            NULL);
                 }
         } else {
@@ -2264,7 +2443,7 @@ void manager_check_finished(Manager *m) {
                                    MESSAGE_ID(SD_MESSAGE_STARTUP_FINISHED),
                                    "USERSPACE_USEC=%llu", (unsigned long long) userspace_usec,
                                    "MESSAGE=Startup finished in %s.",
-                                   format_timespan(sum, sizeof(sum), total_usec),
+                                   format_timespan(sum, sizeof(sum), total_usec, USEC_PER_MSEC),
                                    NULL);
         }
 
@@ -2272,7 +2451,7 @@ void manager_check_finished(Manager *m) {
 
         sd_notifyf(false,
                    "READY=1\nSTATUS=Startup finished in %s.",
-                   format_timespan(sum, sizeof(sum), total_usec));
+                   format_timespan(sum, sizeof(sum), total_usec, USEC_PER_MSEC));
 }
 
 static int create_generator_dir(Manager *m, char **generator, const char *name) {
@@ -2305,9 +2484,9 @@ static int create_generator_dir(Manager *m, char **generator, const char *name)
                         return log_oom();
 
                 if (!mkdtemp(p)) {
-                        free(p);
                         log_error("Failed to create generator directory %s: %m",
                                   p);
+                        free(p);
                         return -errno;
                 }
         }
@@ -2335,7 +2514,6 @@ void manager_run_generators(Manager *m) {
         DIR *d = NULL;
         const char *generator_path;
         const char *argv[5];
-        mode_t u;
         int r;
 
         assert(m);
@@ -2369,9 +2547,9 @@ void manager_run_generators(Manager *m) {
         argv[3] = m->generator_unit_path_late;
         argv[4] = NULL;
 
-        u = umask(0022);
-        execute_directory(generator_path, d, (char**) argv);
-        umask(u);
+        RUN_WITH_UMASK(0022) {
+                execute_directory(generator_path, d, (char**) argv);
+        }
 
         trim_generator_dir(m, &m->generator_unit_path);
         trim_generator_dir(m, &m->generator_unit_path_early);
@@ -2477,7 +2655,7 @@ void manager_set_show_status(Manager *m, bool b) {
                 unlink("/run/systemd/show-status");
 }
 
-bool manager_get_show_status(Manager *m) {
+static bool manager_get_show_status(Manager *m) {
         assert(m);
 
         if (m->running_as != SYSTEMD_SYSTEM)
@@ -2492,6 +2670,25 @@ bool manager_get_show_status(Manager *m) {
         return plymouth_running();
 }
 
+void manager_status_printf(Manager *m, bool ephemeral, const char *status, const char *format, ...) {
+        va_list ap;
+
+        if (!manager_get_show_status(m))
+                return;
+
+        /* XXX We should totally drop the check for ephemeral here
+         * and thus effectively make 'Type=idle' pointless. */
+        if (ephemeral && m->n_on_console > 0)
+                return;
+
+        if (!manager_is_booting_or_shutting_down(m))
+                return;
+
+        va_start(ap, format);
+        status_vprintf(status, true, ephemeral, format, ap);
+        va_end(ap);
+}
+
 void watch_init(Watch *w) {
         assert(w);