1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2015 Canonical
9 Didier Roche <didrocks@ubuntu.com>
11 systemd is free software; you can redistribute it and/or modify it
12 under the terms of the GNU Lesser General Public License as published by
13 the Free Software Foundation; either version 2.1 of the License, or
14 (at your option) any later version.
16 systemd is distributed in the hope that it will be useful, but
17 WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 Lesser General Public License for more details.
21 You should have received a copy of the GNU Lesser General Public License
22 along with systemd; If not, see <http://www.gnu.org/licenses/>.
32 #include <sys/socket.h>
33 #include <sys/types.h>
37 #include "sd-daemon.h"
40 #include "event-util.h"
44 #include "socket-util.h"
48 #define IDLE_TIME_SECONDS 30
49 #define PLYMOUTH_REQUEST_KEY "K\2\2\3"
53 typedef struct Client {
54 struct Manager *manager;
67 LIST_FIELDS(struct Client, clients);
70 typedef struct Manager {
73 LIST_HEAD(Client, clients);
83 sd_event_source *plymouth_event_source;
84 bool plymouth_cancel_sent;
86 bool cancel_requested;
89 static void manager_free(Manager *m);
90 DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free);
92 static double compute_percent(int pass, size_t cur, size_t max) {
93 /* Values stolen from e2fsck */
95 static const double pass_table[] = {
96 0, 70, 90, 92, 95, 100
102 if ((unsigned) pass >= ELEMENTSOF(pass_table) || max == 0)
105 return pass_table[pass-1] +
106 (pass_table[pass] - pass_table[pass-1]) *
110 static int client_request_cancel(Client *c) {
111 FsckdMessage cancel_msg = {
122 n = send(c->fd, &cancel_msg, sizeof(FsckdMessage), 0);
124 return log_warning_errno(errno, "Cannot send cancel to fsck on (%u:%u): %m", major(c->devnum), minor(c->devnum));
125 if ((size_t) n < sizeof(FsckdMessage)) {
126 log_warning("Short send when sending cancel to fsck on (%u:%u).", major(c->devnum), minor(c->devnum));
134 static void client_free(Client *c) {
138 LIST_REMOVE(clients, c->manager->clients, c);
144 static void manager_disconnect_plymouth(Manager *m) {
147 m->plymouth_event_source = sd_event_source_unref(m->plymouth_event_source);
148 m->plymouth_fd = safe_close(m->plymouth_fd);
149 m->plymouth_cancel_sent = false;
152 static int manager_plymouth_feedback_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
153 Manager *m = userdata;
160 l = read(m->plymouth_fd, buffer, sizeof(buffer));
162 log_warning_errno(errno, "Got error while reading from plymouth: %m");
163 manager_disconnect_plymouth(m);
167 manager_disconnect_plymouth(m);
171 if (l > 1 && buffer[0] == '\15')
172 log_error("Message update to plymouth wasn't delivered successfully");
174 /* the only answer support type we requested is a key interruption */
175 if (l > 2 && buffer[0] == '\2' && buffer[5] == '\3') {
176 m->cancel_requested = true;
178 /* cancel all connected clients */
179 LIST_FOREACH(clients, current, m->clients)
180 client_request_cancel(current);
186 static int manager_connect_plymouth(Manager *m) {
187 union sockaddr_union sa = PLYMOUTH_SOCKET;
190 /* try to connect or reconnect if sending a message */
191 if (m->plymouth_fd >= 0)
194 m->plymouth_fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
195 if (m->plymouth_fd < 0)
196 return log_warning_errno(errno, "Connection to plymouth socket failed: %m");
198 if (connect(m->plymouth_fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + 1 + strlen(sa.un.sun_path+1)) < 0) {
199 r = log_warning_errno(errno, "Couldn't connect to plymouth: %m");
203 r = sd_event_add_io(m->event, &m->plymouth_event_source, m->plymouth_fd, EPOLLIN, manager_plymouth_feedback_handler, m);
205 log_warning_errno(r, "Can't listen to plymouth socket: %m");
212 manager_disconnect_plymouth(m);
216 static int plymouth_send_message(int plymouth_fd, const char *message, bool update) {
217 _cleanup_free_ char *packet = NULL;
224 if (asprintf(&packet, "%c\002%c%s%n", mode, (int) (strlen(message) + 1), message, &n) < 0)
227 return loop_write(plymouth_fd, packet, n + 1, true);
230 static int manager_send_plymouth_message(Manager *m, const char *message) {
231 const char *plymouth_cancel_message = NULL;
234 r = manager_connect_plymouth(m);
238 if (!m->plymouth_cancel_sent) {
240 /* Indicate to plymouth that we listen to Ctrl+C */
241 r = loop_write(m->plymouth_fd, PLYMOUTH_REQUEST_KEY, sizeof(PLYMOUTH_REQUEST_KEY), true);
243 return log_warning_errno(r, "Can't send to plymouth cancel key: %m");
245 m->plymouth_cancel_sent = true;
247 plymouth_cancel_message = strjoina("fsckd-cancel-msg:", _("Press Ctrl+C to cancel all filesystem checks in progress"));
249 r = plymouth_send_message(m->plymouth_fd, plymouth_cancel_message, false);
251 log_warning_errno(r, "Can't send filesystem cancel message to plymouth: %m");
253 } else if (m->numdevices == 0) {
255 m->plymouth_cancel_sent = false;
257 r = plymouth_send_message(m->plymouth_fd, "", false);
259 log_warning_errno(r, "Can't clear plymouth filesystem cancel message: %m");
262 r = plymouth_send_message(m->plymouth_fd, message, true);
264 return log_warning_errno(r, "Couldn't send \"%s\" to plymouth: %m", message);
269 static int manager_update_global_progress(Manager *m) {
270 Client *current = NULL;
271 _cleanup_free_ char *console_message = NULL;
272 _cleanup_free_ char *fsck_message = NULL;
273 int current_numdevices = 0, l = 0, r;
274 double current_percent = 100;
276 /* get the overall percentage */
277 LIST_FOREACH(clients, current, m->clients) {
278 current_numdevices++;
280 /* right now, we only keep the minimum % of all fsckd processes. We could in the future trying to be
281 linear, but max changes and corresponds to the pass. We have all the informations into fsckd
282 already if we can treat that in a smarter way. */
283 current_percent = MIN(current_percent, current->percent);
286 /* update if there is anything user-visible to update */
287 if (fabs(current_percent - m->percent) > 0.001 || current_numdevices != m->numdevices) {
288 m->numdevices = current_numdevices;
289 m->percent = current_percent;
291 if (asprintf(&console_message,
292 ngettext("Checking in progress on %d disk (%3.1f%% complete)",
293 "Checking in progress on %d disks (%3.1f%% complete)", m->numdevices),
294 m->numdevices, m->percent) < 0)
297 if (asprintf(&fsck_message, "fsckd:%d:%3.1f:%s", m->numdevices, m->percent, console_message) < 0)
300 /* write to console */
302 fprintf(m->console, "\r%s\r%n", console_message, &l);
306 /* try to connect to plymouth and send message */
307 r = manager_send_plymouth_message(m, fsck_message);
309 log_debug("Couldn't send message to plymouth");
317 static int client_progress_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
318 Client *client = userdata;
319 FsckProgress fsck_data;
328 /* check first if we need to cancel this client */
329 if (m->cancel_requested)
330 client_request_cancel(client);
332 /* ensure we have enough data to read */
333 r = ioctl(fd, FIONREAD, &buflen);
334 if (r == 0 && buflen != 0 && (size_t) buflen < sizeof(FsckProgress)) {
335 if (client->buflen != buflen)
336 client->buflen = buflen;
337 /* we got twice the same size from a bad behaving client, kick it off the list */
339 log_warning("Closing bad behaving fsck client connection at fd %d", client->fd);
341 r = manager_update_global_progress(m);
343 log_warning_errno(r, "Couldn't update global progress: %m");
348 /* read actual data */
349 r = recv(fd, &fsck_data, sizeof(FsckProgress), 0);
351 log_debug("Fsck client connected to fd %d disconnected", client->fd);
353 } else if (r > 0 && r != sizeof(FsckProgress))
354 log_warning("Unexpected data structure sent to fsckd socket from fd: %d. Ignoring", client->fd);
355 else if (r > 0 && r == sizeof(FsckProgress)) {
356 client->devnum = fsck_data.devnum;
357 client->cur = fsck_data.cur;
358 client->max = fsck_data.max;
359 client->pass = fsck_data.pass;
360 client->percent = compute_percent(client->pass, client->cur, client->max);
361 log_debug("Getting progress for %u:%u (%lu, %lu, %d) : %3.1f%%",
362 major(client->devnum), minor(client->devnum),
363 client->cur, client->max, client->pass, client->percent);
365 log_error_errno(r, "Unknown error while trying to read fsck data: %m");
367 r = manager_update_global_progress(m);
369 log_warning_errno(r, "Couldn't update global progress: %m");
374 static int manager_new_connection_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
375 Manager *m = userdata;
376 Client *client = NULL;
377 int new_client_fd, r;
381 /* Initialize and list new clients */
382 new_client_fd = accept4(m->connection_fd, NULL, NULL, SOCK_CLOEXEC);
383 if (new_client_fd < 0)
384 return log_error_errno(errno, "Couldn't accept a new connection: %m");
386 log_debug("New fsck client connected to fd: %d", new_client_fd);
388 client = new0(Client, 1);
391 client->fd = new_client_fd;
393 LIST_PREPEND(clients, m->clients, client);
394 r = sd_event_add_io(m->event, NULL, client->fd, EPOLLIN, client_progress_handler, client);
399 /* only request the client to cancel now in case the request is dropped by the client (chance to recancel) */
400 if (m->cancel_requested)
401 client_request_cancel(client);
406 static void manager_free(Manager *m) {
410 /* clear last line */
411 if (m->console && m->clear > 0) {
414 fputc('\r', m->console);
415 for (j = 0; j < (unsigned) m->clear; j++)
416 fputc(' ', m->console);
417 fputc('\r', m->console);
421 safe_close(m->connection_fd);
424 client_free(m->clients);
426 manager_disconnect_plymouth(m);
431 sd_event_unref(m->event);
436 static int manager_new(Manager **ret, int fd) {
437 _cleanup_(manager_freep) Manager *m = NULL;
442 m = new0(Manager, 1);
447 m->connection_fd = fd;
450 r = sd_event_default(&m->event);
454 if (access("/run/systemd/show-status", F_OK) >= 0) {
455 m->console = fopen("/dev/console", "we");
460 r = sd_event_add_io(m->event, NULL, fd, EPOLLIN, manager_new_connection_handler, m);
470 static int run_event_loop_with_timeout(sd_event *e, usec_t timeout) {
476 r = sd_event_get_state(e);
479 if (r == SD_EVENT_FINISHED)
482 r = sd_event_run(e, timeout);
486 /* timeout reached */
493 r = sd_event_get_exit_code(e, &code);
500 static void help(void) {
501 printf("%s [OPTIONS...]\n\n"
502 "Capture fsck progress and forward one stream to plymouth\n\n"
503 " -h --help Show this help\n"
504 " --version Show package version\n",
505 program_invocation_short_name);
508 static int parse_argv(int argc, char *argv[]) {
515 static const struct option options[] = {
516 { "help", no_argument, NULL, 'h' },
517 { "version", no_argument, NULL, ARG_VERSION },
526 while ((c = getopt_long(argc, argv, "hv", options, NULL)) >= 0)
534 puts(PACKAGE_STRING);
535 puts(SYSTEMD_FEATURES);
542 assert_not_reached("Unhandled option");
546 log_error("Extraneous arguments");
553 int main(int argc, char *argv[]) {
554 _cleanup_(manager_freep) Manager *m = NULL;
558 log_set_target(LOG_TARGET_AUTO);
559 log_parse_environment();
563 r = parse_argv(argc, argv);
567 n = sd_listen_fds(0);
569 log_error("Too many file descriptors received.");
573 fd = SD_LISTEN_FDS_START + 0;
575 fd = make_socket_fd(LOG_DEBUG, FSCKD_SOCKET_PATH, SOCK_STREAM | SOCK_CLOEXEC);
577 r = log_error_errno(fd, "Couldn't create listening socket fd on %s: %m", FSCKD_SOCKET_PATH);
582 r = manager_new(&m, fd);
584 log_error_errno(r, "Failed to allocate manager: %m");
588 r = run_event_loop_with_timeout(m->event, IDLE_TIME_SECONDS * USEC_PER_SEC);
590 log_error_errno(r, "Failed to run event loop: %m");
594 sd_event_get_exit_code(m->event, &r);
597 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;