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 {
77 sd_event_source *plymouth_event_source;
79 bool plymouth_cancel_sent;
80 bool cancel_requested;
83 static int connect_plymouth(Manager *m);
84 static int update_global_progress(Manager *m);
85 static void manager_free(Manager *m);
86 DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free);
87 #define _cleanup_manager_free_ _cleanup_(manager_freep)
89 static double compute_percent(int pass, size_t cur, size_t max) {
90 /* Values stolen from e2fsck */
92 static const double pass_table[] = {
93 0, 70, 90, 92, 95, 100
99 if ((unsigned) pass >= ELEMENTSOF(pass_table) || max == 0)
102 return pass_table[pass-1] +
103 (pass_table[pass] - pass_table[pass-1]) *
107 static int request_cancel_client(Client *current) {
108 FsckdMessage cancel_msg = {
114 n = send(current->fd, &cancel_msg, sizeof(FsckdMessage), 0);
116 return log_warning_errno(errno, "Cannot send cancel to fsck on (%u:%u): %m", major(current->devnum), minor(current->devnum));
117 if ((size_t) n < sizeof(FsckdMessage)) {
118 log_warning("Short send when sending cancel to fsck on (%u:%u).", major(current->devnum), minor(current->devnum));
122 current->cancelled = true;
126 static void remove_client(Client **first, Client *item) {
127 LIST_REMOVE(clients, *first, item);
128 safe_close(item->fd);
132 static void plymouth_disconnect(Manager *m) {
135 m->plymouth_event_source = sd_event_source_unref(m->plymouth_event_source);
136 m->plymouth_fd = safe_close(m->plymouth_fd);
137 m->plymouth_cancel_sent = false;
140 static int plymouth_feedback_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
141 Manager *m = userdata;
148 l = read(m->plymouth_fd, buffer, sizeof(buffer));
150 log_warning_errno(errno, "Got error while reading from plymouth: %m");
151 plymouth_disconnect(m);
155 plymouth_disconnect(m);
159 if (buffer[0] == '\15')
160 log_error("Message update to plymouth wasn't delivered successfully");
162 /* the only answer support type we requested is a key interruption */
163 if (buffer[0] == '\2' && buffer[5] == '\3') {
164 m->cancel_requested = true;
166 /* cancel all connected clients */
167 LIST_FOREACH(clients, current, m->clients)
168 request_cancel_client(current);
174 static int send_message_plymouth_socket(int plymouth_fd, const char *message, bool update) {
175 _cleanup_free_ char *packet = NULL;
182 if (asprintf(&packet, "%c\002%c%s%n", mode, (int) (strlen(message) + 1), message, &n) < 0)
185 return loop_write(plymouth_fd, packet, n + 1, true);
188 static int send_message_plymouth(Manager *m, const char *message) {
189 const char *plymouth_cancel_message = NULL;
192 r = connect_plymouth(m);
196 if (!m->plymouth_cancel_sent) {
198 /* Indicate to plymouth that we listen to Ctrl+C */
199 r = loop_write(m->plymouth_fd, PLYMOUTH_REQUEST_KEY, sizeof(PLYMOUTH_REQUEST_KEY), true);
201 return log_warning_errno(r, "Can't send to plymouth cancel key: %m");
203 m->plymouth_cancel_sent = true;
205 plymouth_cancel_message = strjoina("fsckd-cancel-msg:", _("Press Ctrl+C to cancel all filesystem checks in progress"));
207 r = send_message_plymouth_socket(m->plymouth_fd, plymouth_cancel_message, false);
209 log_warning_errno(r, "Can't send filesystem cancel message to plymouth: %m");
211 } else if (m->numdevices == 0) {
213 m->plymouth_cancel_sent = false;
215 r = send_message_plymouth_socket(m->plymouth_fd, "", false);
217 log_warning_errno(r, "Can't clear plymouth filesystem cancel message: %m");
220 r = send_message_plymouth_socket(m->plymouth_fd, message, true);
222 return log_warning_errno(r, "Couldn't send \"%s\" to plymouth: %m", message);
227 static int update_global_progress(Manager *m) {
228 Client *current = NULL;
229 _cleanup_free_ char *console_message = NULL;
230 _cleanup_free_ char *fsck_message = NULL;
231 int current_numdevices = 0, l = 0, r;
232 double current_percent = 100;
234 /* get the overall percentage */
235 LIST_FOREACH(clients, current, m->clients) {
236 current_numdevices++;
238 /* right now, we only keep the minimum % of all fsckd processes. We could in the future trying to be
239 linear, but max changes and corresponds to the pass. We have all the informations into fsckd
240 already if we can treat that in a smarter way. */
241 current_percent = MIN(current_percent, current->percent);
244 /* update if there is anything user-visible to update */
245 if (fabs(current_percent - m->percent) > 0.001 || current_numdevices != m->numdevices) {
246 m->numdevices = current_numdevices;
247 m->percent = current_percent;
249 if (asprintf(&console_message,
250 ngettext("Checking in progress on %d disk (%3.1f%% complete)",
251 "Checking in progress on %d disks (%3.1f%% complete)", m->numdevices),
252 m->numdevices, m->percent) < 0)
255 if (asprintf(&fsck_message, "fsckd:%d:%3.1f:%s", m->numdevices, m->percent, console_message) < 0)
258 /* write to console */
260 fprintf(m->console, "\r%s\r%n", console_message, &l);
264 /* try to connect to plymouth and send message */
265 r = send_message_plymouth(m, fsck_message);
267 log_debug("Couldn't send message to plymouth");
275 static int connect_plymouth(Manager *m) {
276 union sockaddr_union sa = PLYMOUTH_SOCKET;
279 /* try to connect or reconnect if sending a message */
280 if (m->plymouth_fd >= 0)
283 m->plymouth_fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
284 if (m->plymouth_fd < 0)
285 return log_warning_errno(errno, "Connection to plymouth socket failed: %m");
287 if (connect(m->plymouth_fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + 1 + strlen(sa.un.sun_path+1)) < 0) {
288 r = log_warning_errno(errno, "Couldn't connect to plymouth: %m");
292 r = sd_event_add_io(m->event, &m->plymouth_event_source, m->plymouth_fd, EPOLLIN, plymouth_feedback_handler, m);
294 log_warning_errno(r, "Can't listen to plymouth socket: %m");
301 plymouth_disconnect(m);
305 static int progress_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
306 Client *client = userdata;
308 FsckProgress fsck_data;
315 /* check first if we need to cancel this client */
316 if (m->cancel_requested) {
317 if (!client->cancelled)
318 request_cancel_client(client);
321 /* ensure we have enough data to read */
322 r = ioctl(fd, FIONREAD, &buflen);
323 if (r == 0 && buflen != 0 && (size_t) buflen < sizeof(FsckProgress)) {
324 if (client->buflen != buflen)
325 client->buflen = buflen;
326 /* we got twice the same size from a bad behaving client, kick it off the list */
328 log_warning("Closing bad behaving fsck client connection at fd %d", client->fd);
329 remove_client(&(m->clients), client);
330 r = update_global_progress(m);
332 log_warning_errno(r, "Couldn't update global progress: %m");
337 /* read actual data */
338 r = recv(fd, &fsck_data, sizeof(FsckProgress), 0);
340 log_debug("Fsck client connected to fd %d disconnected", client->fd);
341 remove_client(&(m->clients), client);
342 } else if (r > 0 && r != sizeof(FsckProgress))
343 log_warning("Unexpected data structure sent to fsckd socket from fd: %d. Ignoring", client->fd);
344 else if (r > 0 && r == sizeof(FsckProgress)) {
345 client->devnum = fsck_data.devnum;
346 client->cur = fsck_data.cur;
347 client->max = fsck_data.max;
348 client->pass = fsck_data.pass;
349 client->percent = compute_percent(client->pass, client->cur, client->max);
350 log_debug("Getting progress for %u:%u (%lu, %lu, %d) : %3.1f%%",
351 major(client->devnum), minor(client->devnum),
352 client->cur, client->max, client->pass, client->percent);
354 log_error_errno(r, "Unknown error while trying to read fsck data: %m");
356 r = update_global_progress(m);
358 log_warning_errno(r, "Couldn't update global progress: %m");
363 static int new_connection_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
364 Manager *m = userdata;
365 Client *client = NULL;
366 int new_client_fd, r;
370 /* Initialize and list new clients */
371 new_client_fd = accept4(m->connection_fd, NULL, NULL, SOCK_CLOEXEC);
372 if (new_client_fd < 0)
373 return log_error_errno(errno, "Couldn't accept a new connection: %m");
375 log_debug("New fsck client connected to fd: %d", new_client_fd);
377 client = new0(Client, 1);
380 client->fd = new_client_fd;
382 LIST_PREPEND(clients, m->clients, client);
383 r = sd_event_add_io(m->event, NULL, client->fd, EPOLLIN, progress_handler, client);
385 remove_client(&(m->clients), client);
388 /* only request the client to cancel now in case the request is dropped by the client (chance to recancel) */
389 if (m->cancel_requested)
390 request_cancel_client(client);
395 static void manager_free(Manager *m) {
396 Client *current = NULL, *l = NULL;
400 /* clear last line */
401 if (m->console && m->clear > 0) {
404 fputc('\r', m->console);
405 for (j = 0; j < (unsigned) m->clear; j++)
406 fputc(' ', m->console);
407 fputc('\r', m->console);
411 plymouth_disconnect(m);
413 safe_close(m->connection_fd);
418 LIST_FOREACH_SAFE(clients, current, l, m->clients)
419 remove_client(&(m->clients), current);
421 sd_event_unref(m->event);
426 static int manager_new(Manager **ret, int fd) {
427 _cleanup_manager_free_ Manager *m = NULL;
432 m = new0(Manager, 1);
436 r = sd_event_default(&m->event);
440 m->connection_fd = fd;
441 if (access("/run/systemd/show-status", F_OK) >= 0) {
442 m->console = fopen("/dev/console", "we");
444 return log_warning_errno(errno, "Can't connect to /dev/console: %m");
455 static int run_event_loop_with_timeout(sd_event *e, usec_t timeout) {
461 r = sd_event_get_state(e);
464 if (r == SD_EVENT_FINISHED)
467 r = sd_event_run(e, timeout);
471 /* timeout reached */
478 r = sd_event_get_exit_code(e, &code);
485 static void help(void) {
486 printf("%s [OPTIONS...]\n\n"
487 "Capture fsck progress and forward one stream to plymouth\n\n"
488 " -h --help Show this help\n"
489 " --version Show package version\n",
490 program_invocation_short_name);
493 static int parse_argv(int argc, char *argv[]) {
500 static const struct option options[] = {
501 { "help", no_argument, NULL, 'h' },
502 { "version", no_argument, NULL, ARG_VERSION },
511 while ((c = getopt_long(argc, argv, "hv", options, NULL)) >= 0)
519 puts(PACKAGE_STRING);
520 puts(SYSTEMD_FEATURES);
527 assert_not_reached("Unhandled option");
531 log_error("Extraneous arguments");
538 int main(int argc, char *argv[]) {
539 _cleanup_manager_free_ Manager *m = NULL;
543 log_set_target(LOG_TARGET_AUTO);
544 log_parse_environment();
548 r = parse_argv(argc, argv);
552 n = sd_listen_fds(0);
554 log_error("Too many file descriptors received.");
558 fd = SD_LISTEN_FDS_START + 0;
560 fd = make_socket_fd(LOG_DEBUG, FSCKD_SOCKET_PATH, SOCK_STREAM | SOCK_CLOEXEC);
562 r = log_error_errno(fd, "Couldn't create listening socket fd on %s: %m", FSCKD_SOCKET_PATH);
567 r = manager_new(&m, fd);
569 log_error_errno(r, "Failed to allocate manager: %m");
573 r = sd_event_add_io(m->event, NULL, fd, EPOLLIN, new_connection_handler, m);
575 log_error_errno(r, "Can't listen to connection socket: %m");
579 r = run_event_loop_with_timeout(m->event, IDLE_TIME_SECONDS * USEC_PER_SEC);
581 log_error_errno(r, "Failed to run event loop: %m");
585 sd_event_get_exit_code(m->event, &r);
588 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;