chiark / gitweb /
fsckd: Reduce the SAK window when writing to console
[elogind.git] / src / fsckd / fsckd.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2015 Canonical
7
8   Author:
9     Didier Roche <didrocks@ubuntu.com>
10
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.
15
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.
20
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/>.
23 ***/
24
25 #include <getopt.h>
26 #include <errno.h>
27 #include <libintl.h>
28 #include <math.h>
29 #include <stdbool.h>
30 #include <stdlib.h>
31 #include <stdio.h>
32 #include <sys/socket.h>
33 #include <sys/types.h>
34 #include <sys/un.h>
35 #include <unistd.h>
36
37 #include "sd-daemon.h"
38 #include "build.h"
39 #include "def.h"
40 #include "event-util.h"
41 #include "log.h"
42 #include "list.h"
43 #include "macro.h"
44 #include "socket-util.h"
45 #include "util.h"
46 #include "fsckd.h"
47
48 #define IDLE_TIME_SECONDS 30
49 #define PLYMOUTH_REQUEST_KEY "K\2\2\3"
50 #define CLIENTS_MAX 128
51
52 struct Manager;
53
54 typedef struct Client {
55         struct Manager *manager;
56         int fd;
57         dev_t devnum;
58
59         size_t cur;
60         size_t max;
61         int pass;
62
63         double percent;
64
65         size_t buflen;
66         bool cancelled;
67
68         sd_event_source *event_source;
69
70         LIST_FIELDS(struct Client, clients);
71 } Client;
72
73 typedef struct Manager {
74         sd_event *event;
75
76         LIST_HEAD(Client, clients);
77         unsigned n_clients;
78
79         size_t clear;
80
81         int connection_fd;
82         sd_event_source *connection_event_source;
83
84         bool show_status_console;
85
86         double percent;
87         int numdevices;
88
89         int plymouth_fd;
90         sd_event_source *plymouth_event_source;
91         bool plymouth_cancel_sent;
92
93         bool cancel_requested;
94 } Manager;
95
96 static void client_free(Client *c);
97 static void manager_free(Manager *m);
98
99 DEFINE_TRIVIAL_CLEANUP_FUNC(Client*, client_free);
100 DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free);
101
102 static int manager_write_console(Manager *m, const char *message) {
103         _cleanup_fclose_ FILE *console = NULL;
104         int l;
105         size_t j;
106
107         assert(m);
108
109         if (!m->show_status_console)
110                 return 0;
111
112         /* Reduce the SAK window by opening and closing console on every request */
113         console = fopen("/dev/console", "we");
114         if (!console)
115                 return -errno;
116
117         if (message) {
118                 fprintf(console, "\r%s\r%n", message, &l);
119                 if (m->clear  < (size_t)l)
120                         m->clear = (size_t)l;
121         } else {
122                 fputc('\r', console);
123                 for (j = 0; j < m->clear; j++)
124                         fputc(' ', console);
125                 fputc('\r', console);
126         }
127         fflush(console);
128
129         return 0;
130 }
131
132 static double compute_percent(int pass, size_t cur, size_t max) {
133         /* Values stolen from e2fsck */
134
135         static const double pass_table[] = {
136                 0, 70, 90, 92, 95, 100
137         };
138
139         if (pass <= 0)
140                 return 0.0;
141
142         if ((unsigned) pass >= ELEMENTSOF(pass_table) || max == 0)
143                 return 100.0;
144
145         return pass_table[pass-1] +
146                 (pass_table[pass] - pass_table[pass-1]) *
147                 (double) cur / max;
148 }
149
150 static int client_request_cancel(Client *c) {
151         FsckdMessage cancel_msg = {
152                 .cancel = 1,
153         };
154
155         ssize_t n;
156
157         assert(c);
158
159         if (c->cancelled)
160                 return 0;
161
162         n = send(c->fd, &cancel_msg, sizeof(FsckdMessage), 0);
163         if (n < 0)
164                 return log_warning_errno(errno, "Cannot send cancel to fsck on (%u:%u): %m", major(c->devnum), minor(c->devnum));
165         if ((size_t) n < sizeof(FsckdMessage)) {
166                 log_warning("Short send when sending cancel to fsck on (%u:%u).", major(c->devnum), minor(c->devnum));
167                 return -EIO;
168         }
169
170         c->cancelled = true;
171         return 1;
172 }
173
174 static void client_free(Client *c) {
175         assert(c);
176
177         if (c->manager) {
178                 LIST_REMOVE(clients, c->manager->clients, c);
179                 c->manager->n_clients--;
180         }
181
182         sd_event_source_unref(c->event_source);
183
184         safe_close(c->fd);
185         free(c);
186 }
187
188 static void manager_disconnect_plymouth(Manager *m) {
189         assert(m);
190
191         m->plymouth_event_source = sd_event_source_unref(m->plymouth_event_source);
192         m->plymouth_fd = safe_close(m->plymouth_fd);
193         m->plymouth_cancel_sent = false;
194 }
195
196 static int manager_plymouth_feedback_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
197         Manager *m = userdata;
198         Client *current;
199         char buffer[6];
200         ssize_t l;
201
202         assert(m);
203
204         l = read(m->plymouth_fd, buffer, sizeof(buffer));
205         if (l < 0) {
206                 log_warning_errno(errno, "Got error while reading from plymouth: %m");
207                 manager_disconnect_plymouth(m);
208                 return -errno;
209         }
210         if (l == 0) {
211                 manager_disconnect_plymouth(m);
212                 return 0;
213         }
214
215         if (l > 1 && buffer[0] == '\15')
216                 log_error("Message update to plymouth wasn't delivered successfully");
217
218         /* the only answer support type we requested is a key interruption */
219         if (l > 2 && buffer[0] == '\2' && buffer[5] == '\3') {
220                 m->cancel_requested = true;
221
222                 /* cancel all connected clients */
223                 LIST_FOREACH(clients, current, m->clients)
224                         client_request_cancel(current);
225         }
226
227         return 0;
228 }
229
230 static int manager_connect_plymouth(Manager *m) {
231         union sockaddr_union sa = PLYMOUTH_SOCKET;
232         int r;
233
234         /* try to connect or reconnect if sending a message */
235         if (m->plymouth_fd >= 0)
236                 return 0;
237
238         m->plymouth_fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
239         if (m->plymouth_fd < 0)
240                 return log_warning_errno(errno, "Connection to plymouth socket failed: %m");
241
242         if (connect(m->plymouth_fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + 1 + strlen(sa.un.sun_path+1)) < 0) {
243                 r = log_warning_errno(errno, "Couldn't connect to plymouth: %m");
244                 goto fail;
245         }
246
247         r = sd_event_add_io(m->event, &m->plymouth_event_source, m->plymouth_fd, EPOLLIN, manager_plymouth_feedback_handler, m);
248         if (r < 0) {
249                 log_warning_errno(r, "Can't listen to plymouth socket: %m");
250                 goto fail;
251         }
252
253         return 1;
254
255 fail:
256         manager_disconnect_plymouth(m);
257         return r;
258 }
259
260 static int plymouth_send_message(int plymouth_fd, const char *message, bool update) {
261         _cleanup_free_ char *packet = NULL;
262         int n;
263         char mode = 'M';
264
265         if (update)
266                 mode = 'U';
267
268         if (asprintf(&packet, "%c\002%c%s%n", mode, (int) (strlen(message) + 1), message, &n) < 0)
269                 return log_oom();
270
271         return loop_write(plymouth_fd, packet, n + 1, true);
272 }
273
274 static int manager_send_plymouth_message(Manager *m, const char *message) {
275         const char *plymouth_cancel_message = NULL;
276         int r;
277
278         r = manager_connect_plymouth(m);
279         if (r < 0)
280                 return r;
281
282         if (!m->plymouth_cancel_sent) {
283
284                 /* Indicate to plymouth that we listen to Ctrl+C */
285                 r = loop_write(m->plymouth_fd, PLYMOUTH_REQUEST_KEY, sizeof(PLYMOUTH_REQUEST_KEY), true);
286                 if (r < 0)
287                         return log_warning_errno(r, "Can't send to plymouth cancel key: %m");
288
289                 m->plymouth_cancel_sent = true;
290
291                 plymouth_cancel_message = strjoina("fsckd-cancel-msg:", _("Press Ctrl+C to cancel all filesystem checks in progress"));
292
293                 r = plymouth_send_message(m->plymouth_fd, plymouth_cancel_message, false);
294                 if (r < 0)
295                         log_warning_errno(r, "Can't send filesystem cancel message to plymouth: %m");
296
297         } else if (m->numdevices == 0) {
298
299                 m->plymouth_cancel_sent = false;
300
301                 r = plymouth_send_message(m->plymouth_fd, "", false);
302                 if (r < 0)
303                         log_warning_errno(r, "Can't clear plymouth filesystem cancel message: %m");
304         }
305
306         r = plymouth_send_message(m->plymouth_fd,  message, true);
307         if (r < 0)
308                 return log_warning_errno(r, "Couldn't send \"%s\" to plymouth: %m", message);
309
310         return 0;
311 }
312
313 static int manager_update_global_progress(Manager *m) {
314         Client *current = NULL;
315         _cleanup_free_ char *console_message = NULL;
316         _cleanup_free_ char *fsck_message = NULL;
317         int current_numdevices = 0, r;
318         double current_percent = 100;
319
320         /* get the overall percentage */
321         LIST_FOREACH(clients, current, m->clients) {
322                 current_numdevices++;
323
324                 /* right now, we only keep the minimum % of all fsckd processes. We could in the future trying to be
325                    linear, but max changes and corresponds to the pass. We have all the informations into fsckd
326                    already if we can treat that in a smarter way. */
327                 current_percent = MIN(current_percent, current->percent);
328         }
329
330         /* update if there is anything user-visible to update */
331         if (fabs(current_percent - m->percent) > 0.001 || current_numdevices != m->numdevices) {
332                 m->numdevices = current_numdevices;
333                 m->percent = current_percent;
334
335                 if (asprintf(&console_message,
336                              ngettext("Checking in progress on %d disk (%3.1f%% complete)",
337                                       "Checking in progress on %d disks (%3.1f%% complete)", m->numdevices),
338                                       m->numdevices, m->percent) < 0)
339                         return -ENOMEM;
340
341                 if (asprintf(&fsck_message, "fsckd:%d:%3.1f:%s", m->numdevices, m->percent, console_message) < 0)
342                         return -ENOMEM;
343
344                 r = manager_write_console(m, console_message);
345                 if (r < 0)
346                         return r;
347
348                 /* try to connect to plymouth and send message */
349                 r = manager_send_plymouth_message(m, fsck_message);
350                 if (r < 0)
351                         log_debug("Couldn't send message to plymouth");
352
353         }
354         return 0;
355 }
356
357 static int client_progress_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
358         Client *client = userdata;
359         FsckProgress fsck_data;
360         size_t buflen;
361         Manager *m;
362         int r;
363
364         assert(client);
365
366         m = client->manager;
367
368         /* check first if we need to cancel this client */
369         if (m->cancel_requested)
370                 client_request_cancel(client);
371
372         /* ensure we have enough data to read */
373         r = ioctl(fd, FIONREAD, &buflen);
374         if (r == 0 && buflen != 0 && (size_t) buflen < sizeof(FsckProgress)) {
375                 if (client->buflen != buflen)
376                         client->buflen = buflen;
377                 /* we got twice the same size from a bad behaving client, kick it off the list */
378                 else {
379                         log_warning("Closing bad behaving fsck client connection at fd %d", client->fd);
380                         client_free(client);
381                         r = manager_update_global_progress(m);
382                         if (r < 0)
383                                 log_warning_errno(r, "Couldn't update global progress: %m");
384                 }
385                 return 0;
386         }
387
388         /* read actual data */
389         r = recv(fd, &fsck_data, sizeof(FsckProgress), 0);
390         if (r == 0) {
391                 log_debug("Fsck client connected to fd %d disconnected", client->fd);
392                 client_free(client);
393         } else if (r > 0 && r != sizeof(FsckProgress))
394                 log_warning("Unexpected data structure sent to fsckd socket from fd: %d. Ignoring", client->fd);
395         else if (r > 0 && r == sizeof(FsckProgress)) {
396                 client->devnum = fsck_data.devnum;
397                 client->cur = fsck_data.cur;
398                 client->max = fsck_data.max;
399                 client->pass = fsck_data.pass;
400                 client->percent = compute_percent(client->pass, client->cur, client->max);
401                 log_debug("Getting progress for %u:%u (%lu, %lu, %d) : %3.1f%%",
402                           major(client->devnum), minor(client->devnum),
403                           client->cur, client->max, client->pass, client->percent);
404         } else
405                 log_error_errno(r, "Unknown error while trying to read fsck data: %m");
406
407         r = manager_update_global_progress(m);
408         if (r < 0)
409                 log_warning_errno(r, "Couldn't update global progress: %m");
410
411         return 0;
412 }
413
414 static int manager_new_connection_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
415         _cleanup_(client_freep) Client *c = NULL;
416         _cleanup_close_ int new_client_fd = -1;
417         Manager *m = userdata;
418         int r;
419
420         assert(m);
421
422         /* Initialize and list new clients */
423         new_client_fd = accept4(m->connection_fd, NULL, NULL, SOCK_CLOEXEC);
424         if (new_client_fd < 0)
425                 return log_error_errno(errno, "Couldn't accept a new connection: %m");
426
427         if (m->n_clients >= CLIENTS_MAX) {
428                 log_error("Too many clients, refusing connection.");
429                 return 0;
430         }
431
432         log_debug("New fsck client connected to fd: %d", new_client_fd);
433
434         c = new0(Client, 1);
435         if (!c) {
436                 log_oom();
437                 return 0;
438         }
439
440         c->fd = new_client_fd;
441         new_client_fd = -1;
442
443         r = sd_event_add_io(m->event, &c->event_source, c->fd, EPOLLIN, client_progress_handler, c);
444         if (r < 0) {
445                 log_oom();
446                 return 0;
447         }
448
449         LIST_PREPEND(clients, m->clients, c);
450         m->n_clients++;
451         c->manager = m;
452
453         /* only request the client to cancel now in case the request is dropped by the client (chance to recancel) */
454         if (m->cancel_requested)
455                 client_request_cancel(c);
456
457         c = NULL;
458         return 0;
459 }
460
461 static void manager_free(Manager *m) {
462         if (!m)
463                 return;
464
465         /* clear last line */
466         manager_write_console(m, NULL);
467
468         sd_event_source_unref(m->connection_event_source);
469         safe_close(m->connection_fd);
470
471         while (m->clients)
472                 client_free(m->clients);
473
474         manager_disconnect_plymouth(m);
475
476         sd_event_unref(m->event);
477
478         free(m);
479 }
480
481 static int manager_new(Manager **ret, int fd) {
482         _cleanup_(manager_freep) Manager *m = NULL;
483         int r;
484
485         assert(ret);
486
487         m = new0(Manager, 1);
488         if (!m)
489                 return -ENOMEM;
490
491         m->plymouth_fd = -1;
492         m->connection_fd = fd;
493         m->percent = 100;
494
495         r = sd_event_default(&m->event);
496         if (r < 0)
497                 return r;
498
499         if (access("/run/systemd/show-status", F_OK) >= 0)
500                 m->show_status_console = true;
501
502         r = sd_event_add_io(m->event, &m->connection_event_source, fd, EPOLLIN, manager_new_connection_handler, m);
503         if (r < 0)
504                 return r;
505
506         *ret = m;
507         m = NULL;
508
509         return 0;
510 }
511
512 static int run_event_loop_with_timeout(sd_event *e, usec_t timeout) {
513         int r, code;
514
515         assert(e);
516
517         for (;;) {
518                 r = sd_event_get_state(e);
519                 if (r < 0)
520                         return r;
521                 if (r == SD_EVENT_FINISHED)
522                         break;
523
524                 r = sd_event_run(e, timeout);
525                 if (r < 0)
526                         return r;
527
528                 /* timeout reached */
529                 if (r == 0) {
530                         sd_event_exit(e, 0);
531                         break;
532                 }
533         }
534
535         r = sd_event_get_exit_code(e, &code);
536         if (r < 0)
537                 return r;
538
539         return code;
540 }
541
542 static void help(void) {
543         printf("%s [OPTIONS...]\n\n"
544                "Capture fsck progress and forward one stream to plymouth\n\n"
545                "  -h --help             Show this help\n"
546                "     --version          Show package version\n",
547                program_invocation_short_name);
548 }
549
550 static int parse_argv(int argc, char *argv[]) {
551
552         enum {
553                 ARG_VERSION = 0x100,
554                 ARG_ROOT,
555         };
556
557         static const struct option options[] = {
558                 { "help",      no_argument,       NULL, 'h'           },
559                 { "version",   no_argument,       NULL, ARG_VERSION   },
560                 {}
561         };
562
563         int c;
564
565         assert(argc >= 0);
566         assert(argv);
567
568         while ((c = getopt_long(argc, argv, "hv", options, NULL)) >= 0)
569                 switch (c) {
570
571                 case 'h':
572                         help();
573                         return 0;
574
575                 case ARG_VERSION:
576                         puts(PACKAGE_STRING);
577                         puts(SYSTEMD_FEATURES);
578                         return 0;
579
580                 case '?':
581                         return -EINVAL;
582
583                 default:
584                         assert_not_reached("Unhandled option");
585                 }
586
587         if (optind < argc) {
588                 log_error("Extraneous arguments");
589                 return -EINVAL;
590         }
591
592         return 1;
593 }
594
595 int main(int argc, char *argv[]) {
596         _cleanup_(manager_freep) Manager *m = NULL;
597         int fd = -1;
598         int r, n;
599
600         log_set_target(LOG_TARGET_AUTO);
601         log_parse_environment();
602         log_open();
603         init_gettext();
604
605         r = parse_argv(argc, argv);
606         if (r <= 0)
607                 goto finish;
608
609         n = sd_listen_fds(0);
610         if (n > 1) {
611                 log_error("Too many file descriptors received.");
612                 r = -EINVAL;
613                 goto finish;
614         } else if (n == 1)
615                 fd = SD_LISTEN_FDS_START + 0;
616         else {
617                 fd = make_socket_fd(LOG_DEBUG, FSCKD_SOCKET_PATH, SOCK_STREAM | SOCK_CLOEXEC);
618                 if (fd < 0) {
619                         r = log_error_errno(fd, "Couldn't create listening socket fd on %s: %m", FSCKD_SOCKET_PATH);
620                         goto finish;
621                 }
622         }
623
624         r = manager_new(&m, fd);
625         if (r < 0) {
626                 log_error_errno(r, "Failed to allocate manager: %m");
627                 goto finish;
628         }
629
630         r = run_event_loop_with_timeout(m->event, IDLE_TIME_SECONDS * USEC_PER_SEC);
631         if (r < 0) {
632                 log_error_errno(r, "Failed to run event loop: %m");
633                 goto finish;
634         }
635
636         sd_event_get_exit_code(m->event, &r);
637
638 finish:
639         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
640 }