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;
64 LIST_FIELDS(struct Client, clients);
67 typedef struct Manager {
76 bool plymouth_cancel_sent;
77 bool cancel_requested;
80 static int connect_plymouth(Manager *m);
81 static int update_global_progress(Manager *m);
82 static void manager_free(Manager *m);
83 DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free);
84 #define _cleanup_manager_free_ _cleanup_(manager_freep)
86 static double compute_percent(int pass, size_t cur, size_t max) {
87 /* Values stolen from e2fsck */
89 static const double pass_table[] = {
90 0, 70, 90, 92, 95, 100
96 if ((unsigned) pass >= ELEMENTSOF(pass_table) || max == 0)
99 return pass_table[pass-1] +
100 (pass_table[pass] - pass_table[pass-1]) *
104 static int request_cancel_client(Client *current) {
105 FsckdMessage cancel_msg = {
111 n = send(current->fd, &cancel_msg, sizeof(FsckdMessage), 0);
113 return log_warning_errno(errno, "Cannot send cancel to fsck on (%u:%u): %m", major(current->devnum), minor(current->devnum));
114 if ((size_t) n < sizeof(FsckdMessage)) {
115 log_warning("Short send when sending cancel to fsck on (%u:%u).", major(current->devnum), minor(current->devnum));
119 current->cancelled = true;
123 static void remove_client(Client **first, Client *item) {
124 LIST_REMOVE(clients, *first, item);
125 safe_close(item->fd);
129 static void on_plymouth_disconnect(Manager *m) {
130 m->plymouth_fd = safe_close(m->plymouth_fd);
131 m->plymouth_cancel_sent = false;
134 static int plymouth_feedback_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
135 Manager *m = userdata;
142 r = read(m->plymouth_fd, buffer, sizeof(buffer));
144 on_plymouth_disconnect(m);
146 if (buffer[0] == '\15')
147 log_error("Message update to plymouth wasn't delivered successfully");
149 /* the only answer support type we requested is a key interruption */
150 if (buffer[0] == '\2' && buffer[5] == '\3') {
151 m->cancel_requested = true;
152 /* cancel all connected clients */
153 LIST_FOREACH(clients, current, m->clients)
154 request_cancel_client(current);
161 static int send_message_plymouth_socket(int plymouth_fd, const char *message, bool update) {
162 _cleanup_free_ char *packet = NULL;
169 if (asprintf(&packet, "%c\002%c%s%n", mode, (int) (strlen(message) + 1), message, &n) < 0)
172 return loop_write(plymouth_fd, packet, n + 1, true);
175 static int send_message_plymouth(Manager *m, const char *message) {
176 const char *plymouth_cancel_message = NULL;
179 r = connect_plymouth(m);
183 if (!m->plymouth_cancel_sent) {
185 /* Indicate to plymouth that we listen to Ctrl+C */
186 r = loop_write(m->plymouth_fd, PLYMOUTH_REQUEST_KEY, sizeof(PLYMOUTH_REQUEST_KEY), true);
188 return log_warning_errno(r, "Can't send to plymouth cancel key: %m");
190 m->plymouth_cancel_sent = true;
192 plymouth_cancel_message = strjoina("fsckd-cancel-msg:", _("Press Ctrl+C to cancel all filesystem checks in progress"));
194 r = send_message_plymouth_socket(m->plymouth_fd, plymouth_cancel_message, false);
196 log_warning_errno(r, "Can't send filesystem cancel message to plymouth: %m");
198 } else if (m->numdevices == 0) {
200 m->plymouth_cancel_sent = false;
202 r = send_message_plymouth_socket(m->plymouth_fd, "", false);
204 log_warning_errno(r, "Can't clear plymouth filesystem cancel message: %m");
207 r = send_message_plymouth_socket(m->plymouth_fd, message, true);
209 return log_warning_errno(r, "Couldn't send \"%s\" to plymouth: %m", message);
214 static int update_global_progress(Manager *m) {
215 Client *current = NULL;
216 _cleanup_free_ char *console_message = NULL;
217 _cleanup_free_ char *fsck_message = NULL;
218 int current_numdevices = 0, l = 0, r;
219 double current_percent = 100;
221 /* get the overall percentage */
222 LIST_FOREACH(clients, current, m->clients) {
223 current_numdevices++;
225 /* right now, we only keep the minimum % of all fsckd processes. We could in the future trying to be
226 linear, but max changes and corresponds to the pass. We have all the informations into fsckd
227 already if we can treat that in a smarter way. */
228 current_percent = MIN(current_percent, current->percent);
231 /* update if there is anything user-visible to update */
232 if (fabs(current_percent - m->percent) > 0.001 || current_numdevices != m->numdevices) {
233 m->numdevices = current_numdevices;
234 m->percent = current_percent;
236 if (asprintf(&console_message,
237 ngettext("Checking in progress on %d disk (%3.1f%% complete)",
238 "Checking in progress on %d disks (%3.1f%% complete)", m->numdevices),
239 m->numdevices, m->percent) < 0)
242 if (asprintf(&fsck_message, "fsckd:%d:%3.1f:%s", m->numdevices, m->percent, console_message) < 0)
245 /* write to console */
247 fprintf(m->console, "\r%s\r%n", console_message, &l);
251 /* try to connect to plymouth and send message */
252 r = send_message_plymouth(m, fsck_message);
254 log_debug("Couldn't send message to plymouth");
262 static int connect_plymouth(Manager *m) {
263 union sockaddr_union sa = PLYMOUTH_SOCKET;
266 /* try to connect or reconnect if sending a message */
267 if (m->plymouth_fd >= 0)
270 m->plymouth_fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
271 if (m->plymouth_fd < 0)
272 return log_warning_errno(errno, "Connection to plymouth socket failed: %m");
274 if (connect(m->plymouth_fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + 1 + strlen(sa.un.sun_path+1)) < 0) {
275 r = log_warning_errno(errno, "Couldn't connect to plymouth: %m");
279 r = sd_event_add_io(m->event, NULL, m->plymouth_fd, EPOLLIN, plymouth_feedback_handler, m);
281 log_warning_errno(r, "Can't listen to plymouth socket: %m");
288 on_plymouth_disconnect(m);
292 static int progress_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
293 Client *client = userdata;
295 FsckProgress fsck_data;
302 /* check first if we need to cancel this client */
303 if (m->cancel_requested) {
304 if (!client->cancelled)
305 request_cancel_client(client);
308 /* ensure we have enough data to read */
309 r = ioctl(fd, FIONREAD, &buflen);
310 if (r == 0 && buflen != 0 && (size_t) buflen < sizeof(FsckProgress)) {
311 if (client->buflen != buflen)
312 client->buflen = buflen;
313 /* we got twice the same size from a bad behaving client, kick it off the list */
315 log_warning("Closing bad behaving fsck client connection at fd %d", client->fd);
316 remove_client(&(m->clients), client);
317 r = update_global_progress(m);
319 log_warning_errno(r, "Couldn't update global progress: %m");
324 /* read actual data */
325 r = recv(fd, &fsck_data, sizeof(FsckProgress), 0);
327 log_debug("Fsck client connected to fd %d disconnected", client->fd);
328 remove_client(&(m->clients), client);
329 } else if (r > 0 && r != sizeof(FsckProgress))
330 log_warning("Unexpected data structure sent to fsckd socket from fd: %d. Ignoring", client->fd);
331 else if (r > 0 && r == sizeof(FsckProgress)) {
332 client->devnum = fsck_data.devnum;
333 client->cur = fsck_data.cur;
334 client->max = fsck_data.max;
335 client->pass = fsck_data.pass;
336 client->percent = compute_percent(client->pass, client->cur, client->max);
337 log_debug("Getting progress for %u:%u (%lu, %lu, %d) : %3.1f%%",
338 major(client->devnum), minor(client->devnum),
339 client->cur, client->max, client->pass, client->percent);
341 log_error_errno(r, "Unknown error while trying to read fsck data: %m");
343 r = update_global_progress(m);
345 log_warning_errno(r, "Couldn't update global progress: %m");
350 static int new_connection_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
351 Manager *m = userdata;
352 Client *client = NULL;
353 int new_client_fd, r;
357 /* Initialize and list new clients */
358 new_client_fd = accept4(m->connection_fd, NULL, NULL, SOCK_CLOEXEC);
359 if (new_client_fd < 0)
360 return log_error_errno(errno, "Couldn't accept a new connection: %m");
362 log_debug("New fsck client connected to fd: %d", new_client_fd);
364 client = new0(Client, 1);
367 client->fd = new_client_fd;
369 LIST_PREPEND(clients, m->clients, client);
370 r = sd_event_add_io(m->event, NULL, client->fd, EPOLLIN, progress_handler, client);
372 remove_client(&(m->clients), client);
375 /* only request the client to cancel now in case the request is dropped by the client (chance to recancel) */
376 if (m->cancel_requested)
377 request_cancel_client(client);
382 static void manager_free(Manager *m) {
383 Client *current = NULL, *l = NULL;
387 /* clear last line */
388 if (m->console && m->clear > 0) {
391 fputc('\r', m->console);
392 for (j = 0; j < (unsigned) m->clear; j++)
393 fputc(' ', m->console);
394 fputc('\r', m->console);
398 safe_close(m->connection_fd);
399 safe_close(m->plymouth_fd);
403 LIST_FOREACH_SAFE(clients, current, l, m->clients)
404 remove_client(&(m->clients), current);
406 sd_event_unref(m->event);
411 static int manager_new(Manager **ret, int fd) {
412 _cleanup_manager_free_ Manager *m = NULL;
417 m = new0(Manager, 1);
421 r = sd_event_default(&m->event);
425 m->connection_fd = fd;
426 if (access("/run/systemd/show-status", F_OK) >= 0) {
427 m->console = fopen("/dev/console", "we");
429 return log_warning_errno(errno, "Can't connect to /dev/console: %m");
440 static int run_event_loop_with_timeout(sd_event *e, usec_t timeout) {
446 r = sd_event_get_state(e);
449 if (r == SD_EVENT_FINISHED)
452 r = sd_event_run(e, timeout);
456 /* timeout reached */
463 r = sd_event_get_exit_code(e, &code);
470 static void help(void) {
471 printf("%s [OPTIONS...]\n\n"
472 "Capture fsck progress and forward one stream to plymouth\n\n"
473 " -h --help Show this help\n"
474 " --version Show package version\n",
475 program_invocation_short_name);
478 static int parse_argv(int argc, char *argv[]) {
485 static const struct option options[] = {
486 { "help", no_argument, NULL, 'h' },
487 { "version", no_argument, NULL, ARG_VERSION },
496 while ((c = getopt_long(argc, argv, "hv", options, NULL)) >= 0)
504 puts(PACKAGE_STRING);
505 puts(SYSTEMD_FEATURES);
512 assert_not_reached("Unhandled option");
516 log_error("Extraneous arguments");
523 int main(int argc, char *argv[]) {
524 _cleanup_manager_free_ Manager *m = NULL;
528 log_set_target(LOG_TARGET_AUTO);
529 log_parse_environment();
533 r = parse_argv(argc, argv);
537 n = sd_listen_fds(0);
539 log_error("Too many file descriptors received.");
543 fd = SD_LISTEN_FDS_START + 0;
545 fd = make_socket_fd(LOG_DEBUG, FSCKD_SOCKET_PATH, SOCK_STREAM | SOCK_CLOEXEC);
547 r = log_error_errno(fd, "Couldn't create listening socket fd on %s: %m", FSCKD_SOCKET_PATH);
552 r = manager_new(&m, fd);
554 log_error_errno(r, "Failed to allocate manager: %m");
558 r = sd_event_add_io(m->event, NULL, fd, EPOLLIN, new_connection_handler, m);
560 log_error_errno(r, "Can't listen to connection socket: %m");
564 r = run_event_loop_with_timeout(m->event, IDLE_TIME_SECONDS * USEC_PER_SEC);
566 log_error_errno(r, "Failed to run event loop: %m");
570 sd_event_get_exit_code(m->event, &r);
573 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;