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"
50 #define CLIENTS_MAX 128
54 typedef struct Client {
55 struct Manager *manager;
68 sd_event_source *event_source;
70 LIST_FIELDS(struct Client, clients);
73 typedef struct Manager {
76 LIST_HEAD(Client, clients);
82 sd_event_source *connection_event_source;
90 sd_event_source *plymouth_event_source;
91 bool plymouth_cancel_sent;
93 bool cancel_requested;
96 static void client_free(Client *c);
97 static void manager_free(Manager *m);
99 DEFINE_TRIVIAL_CLEANUP_FUNC(Client*, client_free);
100 DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free);
102 static double compute_percent(int pass, size_t cur, size_t max) {
103 /* Values stolen from e2fsck */
105 static const double pass_table[] = {
106 0, 70, 90, 92, 95, 100
112 if ((unsigned) pass >= ELEMENTSOF(pass_table) || max == 0)
115 return pass_table[pass-1] +
116 (pass_table[pass] - pass_table[pass-1]) *
120 static int client_request_cancel(Client *c) {
121 FsckdMessage cancel_msg = {
132 n = send(c->fd, &cancel_msg, sizeof(FsckdMessage), 0);
134 return log_warning_errno(errno, "Cannot send cancel to fsck on (%u:%u): %m", major(c->devnum), minor(c->devnum));
135 if ((size_t) n < sizeof(FsckdMessage)) {
136 log_warning("Short send when sending cancel to fsck on (%u:%u).", major(c->devnum), minor(c->devnum));
144 static void client_free(Client *c) {
148 LIST_REMOVE(clients, c->manager->clients, c);
149 c->manager->n_clients--;
152 sd_event_source_unref(c->event_source);
158 static void manager_disconnect_plymouth(Manager *m) {
161 m->plymouth_event_source = sd_event_source_unref(m->plymouth_event_source);
162 m->plymouth_fd = safe_close(m->plymouth_fd);
163 m->plymouth_cancel_sent = false;
166 static int manager_plymouth_feedback_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
167 Manager *m = userdata;
174 l = read(m->plymouth_fd, buffer, sizeof(buffer));
176 log_warning_errno(errno, "Got error while reading from plymouth: %m");
177 manager_disconnect_plymouth(m);
181 manager_disconnect_plymouth(m);
185 if (l > 1 && buffer[0] == '\15')
186 log_error("Message update to plymouth wasn't delivered successfully");
188 /* the only answer support type we requested is a key interruption */
189 if (l > 2 && buffer[0] == '\2' && buffer[5] == '\3') {
190 m->cancel_requested = true;
192 /* cancel all connected clients */
193 LIST_FOREACH(clients, current, m->clients)
194 client_request_cancel(current);
200 static int manager_connect_plymouth(Manager *m) {
201 union sockaddr_union sa = PLYMOUTH_SOCKET;
204 /* try to connect or reconnect if sending a message */
205 if (m->plymouth_fd >= 0)
208 m->plymouth_fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
209 if (m->plymouth_fd < 0)
210 return log_warning_errno(errno, "Connection to plymouth socket failed: %m");
212 if (connect(m->plymouth_fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + 1 + strlen(sa.un.sun_path+1)) < 0) {
213 r = log_warning_errno(errno, "Couldn't connect to plymouth: %m");
217 r = sd_event_add_io(m->event, &m->plymouth_event_source, m->plymouth_fd, EPOLLIN, manager_plymouth_feedback_handler, m);
219 log_warning_errno(r, "Can't listen to plymouth socket: %m");
226 manager_disconnect_plymouth(m);
230 static int plymouth_send_message(int plymouth_fd, const char *message, bool update) {
231 _cleanup_free_ char *packet = NULL;
238 if (asprintf(&packet, "%c\002%c%s%n", mode, (int) (strlen(message) + 1), message, &n) < 0)
241 return loop_write(plymouth_fd, packet, n + 1, true);
244 static int manager_send_plymouth_message(Manager *m, const char *message) {
245 const char *plymouth_cancel_message = NULL;
248 r = manager_connect_plymouth(m);
252 if (!m->plymouth_cancel_sent) {
254 /* Indicate to plymouth that we listen to Ctrl+C */
255 r = loop_write(m->plymouth_fd, PLYMOUTH_REQUEST_KEY, sizeof(PLYMOUTH_REQUEST_KEY), true);
257 return log_warning_errno(r, "Can't send to plymouth cancel key: %m");
259 m->plymouth_cancel_sent = true;
261 plymouth_cancel_message = strjoina("fsckd-cancel-msg:", _("Press Ctrl+C to cancel all filesystem checks in progress"));
263 r = plymouth_send_message(m->plymouth_fd, plymouth_cancel_message, false);
265 log_warning_errno(r, "Can't send filesystem cancel message to plymouth: %m");
267 } else if (m->numdevices == 0) {
269 m->plymouth_cancel_sent = false;
271 r = plymouth_send_message(m->plymouth_fd, "", false);
273 log_warning_errno(r, "Can't clear plymouth filesystem cancel message: %m");
276 r = plymouth_send_message(m->plymouth_fd, message, true);
278 return log_warning_errno(r, "Couldn't send \"%s\" to plymouth: %m", message);
283 static int manager_update_global_progress(Manager *m) {
284 Client *current = NULL;
285 _cleanup_free_ char *console_message = NULL;
286 _cleanup_free_ char *fsck_message = NULL;
287 int current_numdevices = 0, l = 0, r;
288 double current_percent = 100;
290 /* get the overall percentage */
291 LIST_FOREACH(clients, current, m->clients) {
292 current_numdevices++;
294 /* right now, we only keep the minimum % of all fsckd processes. We could in the future trying to be
295 linear, but max changes and corresponds to the pass. We have all the informations into fsckd
296 already if we can treat that in a smarter way. */
297 current_percent = MIN(current_percent, current->percent);
300 /* update if there is anything user-visible to update */
301 if (fabs(current_percent - m->percent) > 0.001 || current_numdevices != m->numdevices) {
302 m->numdevices = current_numdevices;
303 m->percent = current_percent;
305 if (asprintf(&console_message,
306 ngettext("Checking in progress on %d disk (%3.1f%% complete)",
307 "Checking in progress on %d disks (%3.1f%% complete)", m->numdevices),
308 m->numdevices, m->percent) < 0)
311 if (asprintf(&fsck_message, "fsckd:%d:%3.1f:%s", m->numdevices, m->percent, console_message) < 0)
314 /* write to console */
316 fprintf(m->console, "\r%s\r%n", console_message, &l);
320 /* try to connect to plymouth and send message */
321 r = manager_send_plymouth_message(m, fsck_message);
323 log_debug("Couldn't send message to plymouth");
331 static int client_progress_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
332 Client *client = userdata;
333 FsckProgress fsck_data;
342 /* check first if we need to cancel this client */
343 if (m->cancel_requested)
344 client_request_cancel(client);
346 /* ensure we have enough data to read */
347 r = ioctl(fd, FIONREAD, &buflen);
348 if (r == 0 && buflen != 0 && (size_t) buflen < sizeof(FsckProgress)) {
349 if (client->buflen != buflen)
350 client->buflen = buflen;
351 /* we got twice the same size from a bad behaving client, kick it off the list */
353 log_warning("Closing bad behaving fsck client connection at fd %d", client->fd);
355 r = manager_update_global_progress(m);
357 log_warning_errno(r, "Couldn't update global progress: %m");
362 /* read actual data */
363 r = recv(fd, &fsck_data, sizeof(FsckProgress), 0);
365 log_debug("Fsck client connected to fd %d disconnected", client->fd);
367 } else if (r > 0 && r != sizeof(FsckProgress))
368 log_warning("Unexpected data structure sent to fsckd socket from fd: %d. Ignoring", client->fd);
369 else if (r > 0 && r == sizeof(FsckProgress)) {
370 client->devnum = fsck_data.devnum;
371 client->cur = fsck_data.cur;
372 client->max = fsck_data.max;
373 client->pass = fsck_data.pass;
374 client->percent = compute_percent(client->pass, client->cur, client->max);
375 log_debug("Getting progress for %u:%u (%lu, %lu, %d) : %3.1f%%",
376 major(client->devnum), minor(client->devnum),
377 client->cur, client->max, client->pass, client->percent);
379 log_error_errno(r, "Unknown error while trying to read fsck data: %m");
381 r = manager_update_global_progress(m);
383 log_warning_errno(r, "Couldn't update global progress: %m");
388 static int manager_new_connection_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
389 _cleanup_(client_freep) Client *c = NULL;
390 _cleanup_close_ int new_client_fd = -1;
391 Manager *m = userdata;
396 /* Initialize and list new clients */
397 new_client_fd = accept4(m->connection_fd, NULL, NULL, SOCK_CLOEXEC);
398 if (new_client_fd < 0)
399 return log_error_errno(errno, "Couldn't accept a new connection: %m");
401 if (m->n_clients >= CLIENTS_MAX) {
402 log_error("Too many clients, refusing connection.");
406 log_debug("New fsck client connected to fd: %d", new_client_fd);
414 c->fd = new_client_fd;
417 r = sd_event_add_io(m->event, &c->event_source, c->fd, EPOLLIN, client_progress_handler, c);
423 LIST_PREPEND(clients, m->clients, c);
427 /* only request the client to cancel now in case the request is dropped by the client (chance to recancel) */
428 if (m->cancel_requested)
429 client_request_cancel(c);
435 static void manager_free(Manager *m) {
439 /* clear last line */
440 if (m->console && m->clear > 0) {
443 fputc('\r', m->console);
444 for (j = 0; j < (unsigned) m->clear; j++)
445 fputc(' ', m->console);
446 fputc('\r', m->console);
450 sd_event_source_unref(m->connection_event_source);
451 safe_close(m->connection_fd);
454 client_free(m->clients);
456 manager_disconnect_plymouth(m);
461 sd_event_unref(m->event);
466 static int manager_new(Manager **ret, int fd) {
467 _cleanup_(manager_freep) Manager *m = NULL;
472 m = new0(Manager, 1);
477 m->connection_fd = fd;
480 r = sd_event_default(&m->event);
484 if (access("/run/systemd/show-status", F_OK) >= 0) {
485 m->console = fopen("/dev/console", "we");
490 r = sd_event_add_io(m->event, &m->connection_event_source, fd, EPOLLIN, manager_new_connection_handler, m);
500 static int run_event_loop_with_timeout(sd_event *e, usec_t timeout) {
506 r = sd_event_get_state(e);
509 if (r == SD_EVENT_FINISHED)
512 r = sd_event_run(e, timeout);
516 /* timeout reached */
523 r = sd_event_get_exit_code(e, &code);
530 static void help(void) {
531 printf("%s [OPTIONS...]\n\n"
532 "Capture fsck progress and forward one stream to plymouth\n\n"
533 " -h --help Show this help\n"
534 " --version Show package version\n",
535 program_invocation_short_name);
538 static int parse_argv(int argc, char *argv[]) {
545 static const struct option options[] = {
546 { "help", no_argument, NULL, 'h' },
547 { "version", no_argument, NULL, ARG_VERSION },
556 while ((c = getopt_long(argc, argv, "hv", options, NULL)) >= 0)
564 puts(PACKAGE_STRING);
565 puts(SYSTEMD_FEATURES);
572 assert_not_reached("Unhandled option");
576 log_error("Extraneous arguments");
583 int main(int argc, char *argv[]) {
584 _cleanup_(manager_freep) Manager *m = NULL;
588 log_set_target(LOG_TARGET_AUTO);
589 log_parse_environment();
593 r = parse_argv(argc, argv);
597 n = sd_listen_fds(0);
599 log_error("Too many file descriptors received.");
603 fd = SD_LISTEN_FDS_START + 0;
605 fd = make_socket_fd(LOG_DEBUG, FSCKD_SOCKET_PATH, SOCK_STREAM | SOCK_CLOEXEC);
607 r = log_error_errno(fd, "Couldn't create listening socket fd on %s: %m", FSCKD_SOCKET_PATH);
612 r = manager_new(&m, fd);
614 log_error_errno(r, "Failed to allocate manager: %m");
618 r = run_event_loop_with_timeout(m->event, IDLE_TIME_SECONDS * USEC_PER_SEC);
620 log_error_errno(r, "Failed to run event loop: %m");
624 sd_event_get_exit_code(m->event, &r);
627 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;