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 sd_event_source *event_source;
69 LIST_FIELDS(struct Client, clients);
72 typedef struct Manager {
75 LIST_HEAD(Client, clients);
85 sd_event_source *plymouth_event_source;
86 bool plymouth_cancel_sent;
88 bool cancel_requested;
91 static void client_free(Client *c);
92 static void manager_free(Manager *m);
94 DEFINE_TRIVIAL_CLEANUP_FUNC(Client*, client_free);
95 DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free);
97 static double compute_percent(int pass, size_t cur, size_t max) {
98 /* Values stolen from e2fsck */
100 static const double pass_table[] = {
101 0, 70, 90, 92, 95, 100
107 if ((unsigned) pass >= ELEMENTSOF(pass_table) || max == 0)
110 return pass_table[pass-1] +
111 (pass_table[pass] - pass_table[pass-1]) *
115 static int client_request_cancel(Client *c) {
116 FsckdMessage cancel_msg = {
127 n = send(c->fd, &cancel_msg, sizeof(FsckdMessage), 0);
129 return log_warning_errno(errno, "Cannot send cancel to fsck on (%u:%u): %m", major(c->devnum), minor(c->devnum));
130 if ((size_t) n < sizeof(FsckdMessage)) {
131 log_warning("Short send when sending cancel to fsck on (%u:%u).", major(c->devnum), minor(c->devnum));
139 static void client_free(Client *c) {
143 LIST_REMOVE(clients, c->manager->clients, c);
145 sd_event_source_unref(c->event_source);
151 static void manager_disconnect_plymouth(Manager *m) {
154 m->plymouth_event_source = sd_event_source_unref(m->plymouth_event_source);
155 m->plymouth_fd = safe_close(m->plymouth_fd);
156 m->plymouth_cancel_sent = false;
159 static int manager_plymouth_feedback_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
160 Manager *m = userdata;
167 l = read(m->plymouth_fd, buffer, sizeof(buffer));
169 log_warning_errno(errno, "Got error while reading from plymouth: %m");
170 manager_disconnect_plymouth(m);
174 manager_disconnect_plymouth(m);
178 if (l > 1 && buffer[0] == '\15')
179 log_error("Message update to plymouth wasn't delivered successfully");
181 /* the only answer support type we requested is a key interruption */
182 if (l > 2 && buffer[0] == '\2' && buffer[5] == '\3') {
183 m->cancel_requested = true;
185 /* cancel all connected clients */
186 LIST_FOREACH(clients, current, m->clients)
187 client_request_cancel(current);
193 static int manager_connect_plymouth(Manager *m) {
194 union sockaddr_union sa = PLYMOUTH_SOCKET;
197 /* try to connect or reconnect if sending a message */
198 if (m->plymouth_fd >= 0)
201 m->plymouth_fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
202 if (m->plymouth_fd < 0)
203 return log_warning_errno(errno, "Connection to plymouth socket failed: %m");
205 if (connect(m->plymouth_fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + 1 + strlen(sa.un.sun_path+1)) < 0) {
206 r = log_warning_errno(errno, "Couldn't connect to plymouth: %m");
210 r = sd_event_add_io(m->event, &m->plymouth_event_source, m->plymouth_fd, EPOLLIN, manager_plymouth_feedback_handler, m);
212 log_warning_errno(r, "Can't listen to plymouth socket: %m");
219 manager_disconnect_plymouth(m);
223 static int plymouth_send_message(int plymouth_fd, const char *message, bool update) {
224 _cleanup_free_ char *packet = NULL;
231 if (asprintf(&packet, "%c\002%c%s%n", mode, (int) (strlen(message) + 1), message, &n) < 0)
234 return loop_write(plymouth_fd, packet, n + 1, true);
237 static int manager_send_plymouth_message(Manager *m, const char *message) {
238 const char *plymouth_cancel_message = NULL;
241 r = manager_connect_plymouth(m);
245 if (!m->plymouth_cancel_sent) {
247 /* Indicate to plymouth that we listen to Ctrl+C */
248 r = loop_write(m->plymouth_fd, PLYMOUTH_REQUEST_KEY, sizeof(PLYMOUTH_REQUEST_KEY), true);
250 return log_warning_errno(r, "Can't send to plymouth cancel key: %m");
252 m->plymouth_cancel_sent = true;
254 plymouth_cancel_message = strjoina("fsckd-cancel-msg:", _("Press Ctrl+C to cancel all filesystem checks in progress"));
256 r = plymouth_send_message(m->plymouth_fd, plymouth_cancel_message, false);
258 log_warning_errno(r, "Can't send filesystem cancel message to plymouth: %m");
260 } else if (m->numdevices == 0) {
262 m->plymouth_cancel_sent = false;
264 r = plymouth_send_message(m->plymouth_fd, "", false);
266 log_warning_errno(r, "Can't clear plymouth filesystem cancel message: %m");
269 r = plymouth_send_message(m->plymouth_fd, message, true);
271 return log_warning_errno(r, "Couldn't send \"%s\" to plymouth: %m", message);
276 static int manager_update_global_progress(Manager *m) {
277 Client *current = NULL;
278 _cleanup_free_ char *console_message = NULL;
279 _cleanup_free_ char *fsck_message = NULL;
280 int current_numdevices = 0, l = 0, r;
281 double current_percent = 100;
283 /* get the overall percentage */
284 LIST_FOREACH(clients, current, m->clients) {
285 current_numdevices++;
287 /* right now, we only keep the minimum % of all fsckd processes. We could in the future trying to be
288 linear, but max changes and corresponds to the pass. We have all the informations into fsckd
289 already if we can treat that in a smarter way. */
290 current_percent = MIN(current_percent, current->percent);
293 /* update if there is anything user-visible to update */
294 if (fabs(current_percent - m->percent) > 0.001 || current_numdevices != m->numdevices) {
295 m->numdevices = current_numdevices;
296 m->percent = current_percent;
298 if (asprintf(&console_message,
299 ngettext("Checking in progress on %d disk (%3.1f%% complete)",
300 "Checking in progress on %d disks (%3.1f%% complete)", m->numdevices),
301 m->numdevices, m->percent) < 0)
304 if (asprintf(&fsck_message, "fsckd:%d:%3.1f:%s", m->numdevices, m->percent, console_message) < 0)
307 /* write to console */
309 fprintf(m->console, "\r%s\r%n", console_message, &l);
313 /* try to connect to plymouth and send message */
314 r = manager_send_plymouth_message(m, fsck_message);
316 log_debug("Couldn't send message to plymouth");
324 static int client_progress_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
325 Client *client = userdata;
326 FsckProgress fsck_data;
335 /* check first if we need to cancel this client */
336 if (m->cancel_requested)
337 client_request_cancel(client);
339 /* ensure we have enough data to read */
340 r = ioctl(fd, FIONREAD, &buflen);
341 if (r == 0 && buflen != 0 && (size_t) buflen < sizeof(FsckProgress)) {
342 if (client->buflen != buflen)
343 client->buflen = buflen;
344 /* we got twice the same size from a bad behaving client, kick it off the list */
346 log_warning("Closing bad behaving fsck client connection at fd %d", client->fd);
348 r = manager_update_global_progress(m);
350 log_warning_errno(r, "Couldn't update global progress: %m");
355 /* read actual data */
356 r = recv(fd, &fsck_data, sizeof(FsckProgress), 0);
358 log_debug("Fsck client connected to fd %d disconnected", client->fd);
360 } else if (r > 0 && r != sizeof(FsckProgress))
361 log_warning("Unexpected data structure sent to fsckd socket from fd: %d. Ignoring", client->fd);
362 else if (r > 0 && r == sizeof(FsckProgress)) {
363 client->devnum = fsck_data.devnum;
364 client->cur = fsck_data.cur;
365 client->max = fsck_data.max;
366 client->pass = fsck_data.pass;
367 client->percent = compute_percent(client->pass, client->cur, client->max);
368 log_debug("Getting progress for %u:%u (%lu, %lu, %d) : %3.1f%%",
369 major(client->devnum), minor(client->devnum),
370 client->cur, client->max, client->pass, client->percent);
372 log_error_errno(r, "Unknown error while trying to read fsck data: %m");
374 r = manager_update_global_progress(m);
376 log_warning_errno(r, "Couldn't update global progress: %m");
381 static int manager_new_connection_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
382 _cleanup_(client_freep) Client *c = NULL;
383 Manager *m = userdata;
384 int new_client_fd, r;
388 /* Initialize and list new clients */
389 new_client_fd = accept4(m->connection_fd, NULL, NULL, SOCK_CLOEXEC);
390 if (new_client_fd < 0)
391 return log_error_errno(errno, "Couldn't accept a new connection: %m");
393 log_debug("New fsck client connected to fd: %d", new_client_fd);
397 safe_close(new_client_fd);
402 c->fd = new_client_fd;
403 r = sd_event_add_io(m->event, &c->event_source, c->fd, EPOLLIN, client_progress_handler, c);
409 LIST_PREPEND(clients, m->clients, c);
412 /* only request the client to cancel now in case the request is dropped by the client (chance to recancel) */
413 if (m->cancel_requested)
414 client_request_cancel(c);
420 static void manager_free(Manager *m) {
424 /* clear last line */
425 if (m->console && m->clear > 0) {
428 fputc('\r', m->console);
429 for (j = 0; j < (unsigned) m->clear; j++)
430 fputc(' ', m->console);
431 fputc('\r', m->console);
435 safe_close(m->connection_fd);
438 client_free(m->clients);
440 manager_disconnect_plymouth(m);
445 sd_event_unref(m->event);
450 static int manager_new(Manager **ret, int fd) {
451 _cleanup_(manager_freep) Manager *m = NULL;
456 m = new0(Manager, 1);
461 m->connection_fd = fd;
464 r = sd_event_default(&m->event);
468 if (access("/run/systemd/show-status", F_OK) >= 0) {
469 m->console = fopen("/dev/console", "we");
474 r = sd_event_add_io(m->event, NULL, fd, EPOLLIN, manager_new_connection_handler, m);
484 static int run_event_loop_with_timeout(sd_event *e, usec_t timeout) {
490 r = sd_event_get_state(e);
493 if (r == SD_EVENT_FINISHED)
496 r = sd_event_run(e, timeout);
500 /* timeout reached */
507 r = sd_event_get_exit_code(e, &code);
514 static void help(void) {
515 printf("%s [OPTIONS...]\n\n"
516 "Capture fsck progress and forward one stream to plymouth\n\n"
517 " -h --help Show this help\n"
518 " --version Show package version\n",
519 program_invocation_short_name);
522 static int parse_argv(int argc, char *argv[]) {
529 static const struct option options[] = {
530 { "help", no_argument, NULL, 'h' },
531 { "version", no_argument, NULL, ARG_VERSION },
540 while ((c = getopt_long(argc, argv, "hv", options, NULL)) >= 0)
548 puts(PACKAGE_STRING);
549 puts(SYSTEMD_FEATURES);
556 assert_not_reached("Unhandled option");
560 log_error("Extraneous arguments");
567 int main(int argc, char *argv[]) {
568 _cleanup_(manager_freep) Manager *m = NULL;
572 log_set_target(LOG_TARGET_AUTO);
573 log_parse_environment();
577 r = parse_argv(argc, argv);
581 n = sd_listen_fds(0);
583 log_error("Too many file descriptors received.");
587 fd = SD_LISTEN_FDS_START + 0;
589 fd = make_socket_fd(LOG_DEBUG, FSCKD_SOCKET_PATH, SOCK_STREAM | SOCK_CLOEXEC);
591 r = log_error_errno(fd, "Couldn't create listening socket fd on %s: %m", FSCKD_SOCKET_PATH);
596 r = manager_new(&m, fd);
598 log_error_errno(r, "Failed to allocate manager: %m");
602 r = run_event_loop_with_timeout(m->event, IDLE_TIME_SECONDS * USEC_PER_SEC);
604 log_error_errno(r, "Failed to run event loop: %m");
608 sd_event_get_exit_code(m->event, &r);
611 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;