X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=blobdiff_plain;f=src%2Fcore%2Fmanager.c;h=42423985bc8fe11ee61950beaa317660b3d4a08e;hp=aaf66970cf11e7a6e94d83fdd2919ad9ee01aaec;hb=9e9e2b722cf796b58e959cd174d87ce0ec0bc996;hpb=2e3d069236777cd62f755a02f4a239306b4ad21a diff --git a/src/core/manager.c b/src/core/manager.c index aaf66970c..42423985b 100644 --- a/src/core/manager.c +++ b/src/core/manager.c @@ -71,6 +71,7 @@ #include "path-util.h" #include "audit-fd.h" #include "efivars.h" +#include "env-util.h" /* As soon as 16 units are in our GC queue, make sure to run a gc sweep */ #define GC_QUEUE_ENTRIES_MAX 16 @@ -78,9 +79,16 @@ /* 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" +#define TIME_T_MAX (time_t)((1UL << ((sizeof(time_t) << 3) - 1)) - 1) + static int manager_setup_notify(Manager *m) { union { struct sockaddr sa; @@ -123,7 +131,7 @@ static int manager_setup_notify(Manager *m) { ev.data.ptr = &m->notify_watch; if (epoll_ctl(m->epoll_fd, EPOLL_CTL_ADD, m->notify_watch.fd, &ev) < 0) { - log_error("Failed to add timer change fd to epoll: %m"); + log_error("Failed to add notification socket fd to epoll: %m"); return -errno; } @@ -137,6 +145,149 @@ static int manager_setup_notify(Manager *m) { return 0; } +static int manager_jobs_in_progress_mod_timer(Manager *m) { + struct itimerspec its; + + 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) { + memset(p, ' ', pos-2); + p += pos-2; + } + memcpy(p, ANSI_RED_ON, strlen(ANSI_RED_ON)); + p += strlen(ANSI_RED_ON); + *p++ = '*'; + } + + if (pos > 0 && pos <= width) { + memcpy(p, ANSI_HIGHLIGHT_RED_ON, strlen(ANSI_HIGHLIGHT_RED_ON)); + p += strlen(ANSI_HIGHLIGHT_RED_ON); + *p++ = '*'; + } + + memcpy(p, ANSI_HIGHLIGHT_OFF, strlen(ANSI_HIGHLIGHT_OFF)); + p += strlen(ANSI_HIGHLIGHT_OFF); + + if (pos < width) { + memcpy(p, ANSI_RED_ON, strlen(ANSI_RED_ON)); + p += strlen(ANSI_RED_ON); + *p++ = '*'; + if (pos < width-1) { + memset(p, ' ', width-1-pos); + p += width-1-pos; + } + memcpy(p, ANSI_HIGHLIGHT_OFF, strlen(ANSI_HIGHLIGHT_OFF)); + p += strlen(ANSI_HIGHLIGHT_OFF); + } + *p = 0; +} + +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; + + if (!j) + return; + + 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; @@ -287,6 +438,9 @@ static void manager_strip_environment(Manager *m) { * the initrd interface: * http://www.freedesktop.org/wiki/Software/systemd/InitrdInterface */ strv_remove_prefix(m->environment, "RD_"); + + /* Drop invalid entries */ + strv_env_clean(m->environment); } int manager_new(SystemdRunningAs running_as, Manager **_m) { @@ -303,7 +457,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; @@ -316,6 +472,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 */ @@ -521,6 +678,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) { @@ -556,6 +716,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); @@ -619,14 +781,15 @@ int manager_coldplug(Manager *m) { static void manager_build_unit_path_cache(Manager *m) { char **i; - DIR *d = NULL; + DIR _cleanup_free_ *d = NULL; int r; assert(m); set_free_free(m->unit_path_cache); - if (!(m->unit_path_cache = set_new(string_hash_func, string_compare_func))) { + m->unit_path_cache = set_new(string_hash_func, string_compare_func); + if (!m->unit_path_cache) { log_error("Failed to allocate unit path cache."); return; } @@ -639,7 +802,8 @@ static void manager_build_unit_path_cache(Manager *m) { d = opendir(*i); if (!d) { - log_error("Failed to open directory: %m"); + if (errno != ENOENT) + log_error("Failed to open directory %s: %m", *i); continue; } @@ -655,7 +819,8 @@ static void manager_build_unit_path_cache(Manager *m) { goto fail; } - if ((r = set_put(m->unit_path_cache, p)) < 0) { + r = set_put(m->unit_path_cache, p); + if (r < 0) { free(p); goto fail; } @@ -672,9 +837,6 @@ fail: set_free_free(m->unit_path_cache); m->unit_path_cache = NULL; - - if (d) - closedir(d); } int manager_startup(Manager *m, FILE *serialization, FDSet *fds) { @@ -758,7 +920,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; @@ -980,6 +1142,10 @@ unsigned manager_dispatch_run_queue(Manager *m) { } m->dispatching_run_queue = false; + + if (hashmap_size(m->jobs) > 0) + manager_watch_jobs_in_progress(m); + return n; } @@ -1519,6 +1685,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."); @@ -1850,6 +2026,7 @@ int manager_serialize(Manager *m, FILE *f, FDSet *fds, bool serialize_jobs) { Iterator i; Unit *u; const char *t; + char **e; int r; assert(m); @@ -1873,6 +2050,14 @@ int manager_serialize(Manager *m, FILE *f, FDSet *fds, bool serialize_jobs) { dual_timestamp_serialize(f, "finish-timestamp", &m->finish_timestamp); } + STRV_FOREACH(e, m->environment) { + _cleanup_free_ char *ce; + + ce = cescape(*e); + if (ce) + fprintf(f, "env=%s\n", *e); + } + fputc('\n', f); HASHMAP_FOREACH_KEY(u, t, m->units, i) { @@ -1973,7 +2158,25 @@ int manager_deserialize(Manager *m, FILE *f, FDSet *fds) { dual_timestamp_deserialize(l+20, &m->userspace_timestamp); else if (startswith(l, "finish-timestamp=")) dual_timestamp_deserialize(l+17, &m->finish_timestamp); - else + else if (startswith(l, "env=")) { + _cleanup_free_ char *uce = NULL; + char **e; + + uce = cunescape(l+4); + if (!uce) { + r = -ENOMEM; + goto finish; + } + + e = strv_env_set(m->environment, uce); + if (!e) { + r = -ENOMEM; + goto finish; + } + + strv_free(m->environment); + m->environment = e; + } else log_debug("Unknown serialization item '%s'", l); } @@ -2117,7 +2320,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); @@ -2164,8 +2367,10 @@ void manager_check_finished(Manager *m) { assert(m); - if (hashmap_size(m->jobs) > 0) + 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); @@ -2173,6 +2378,8 @@ void manager_check_finished(Manager *m) { /* Turn off confirm spawn now */ m->confirm_spawn = false; + manager_unwatch_jobs_in_progress(m); + if (dual_timestamp_is_set(&m->finish_timestamp)) return; @@ -2261,7 +2468,8 @@ static int create_generator_dir(Manager *m, char **generator, const char *name) r = mkdir_p_label(p, 0755); if (r < 0) { - log_error("Failed to create generator directory: %s", strerror(-r)); + log_error("Failed to create generator directory %s: %s", + p, strerror(-r)); free(p); return r; } @@ -2272,7 +2480,8 @@ static int create_generator_dir(Manager *m, char **generator, const char *name) if (!mkdtemp(p)) { free(p); - log_error("Failed to create generator directory: %m"); + log_error("Failed to create generator directory %s: %m", + p); return -errno; } } @@ -2311,7 +2520,8 @@ void manager_run_generators(Manager *m) { if (errno == ENOENT) return; - log_error("Failed to enumerate generator directory: %m"); + log_error("Failed to enumerate generator directory %s: %m", + generator_path); return; } @@ -2441,7 +2651,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) @@ -2456,6 +2666,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);