chiark / gitweb /
3c587a3f2025358f424fbe0025691658933ce10a
[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                         return r;
352         }
353         return 0;
354 }
355
356 static int client_progress_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
357         Client *client = userdata;
358         FsckProgress fsck_data;
359         size_t buflen;
360         Manager *m;
361         int r;
362
363         assert(client);
364
365         m = client->manager;
366
367         /* check first if we need to cancel this client */
368         if (m->cancel_requested)
369                 client_request_cancel(client);
370
371         /* ensure we have enough data to read */
372         r = ioctl(fd, FIONREAD, &buflen);
373         if (r == 0 && buflen != 0 && (size_t) buflen < sizeof(FsckProgress)) {
374                 if (client->buflen != buflen)
375                         client->buflen = buflen;
376                 /* we got twice the same size from a bad behaving client, kick it off the list */
377                 else {
378                         log_warning("Closing bad behaving fsck client connection at fd %d", client->fd);
379                         client_free(client);
380                         manager_update_global_progress(m);
381                 }
382                 return 0;
383         }
384
385         /* read actual data */
386         r = recv(fd, &fsck_data, sizeof(FsckProgress), 0);
387         if (r == 0) {
388                 log_debug("Fsck client connected to fd %d disconnected", client->fd);
389                 client_free(client);
390         } else if (r > 0 && r != sizeof(FsckProgress))
391                 log_warning("Unexpected data structure sent to fsckd socket from fd: %d. Ignoring", client->fd);
392         else if (r > 0 && r == sizeof(FsckProgress)) {
393                 client->devnum = fsck_data.devnum;
394                 client->cur = fsck_data.cur;
395                 client->max = fsck_data.max;
396                 client->pass = fsck_data.pass;
397                 client->percent = compute_percent(client->pass, client->cur, client->max);
398                 log_debug("Getting progress for %u:%u (%lu, %lu, %d) : %3.1f%%",
399                           major(client->devnum), minor(client->devnum),
400                           client->cur, client->max, client->pass, client->percent);
401         } else
402                 log_error_errno(r, "Unknown error while trying to read fsck data: %m");
403
404         manager_update_global_progress(m);
405
406         return 0;
407 }
408
409 static int manager_new_connection_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
410         _cleanup_(client_freep) Client *c = NULL;
411         _cleanup_close_ int new_client_fd = -1;
412         Manager *m = userdata;
413         int r;
414
415         assert(m);
416
417         /* Initialize and list new clients */
418         new_client_fd = accept4(m->connection_fd, NULL, NULL, SOCK_CLOEXEC);
419         if (new_client_fd < 0)
420                 return log_error_errno(errno, "Couldn't accept a new connection: %m");
421
422         if (m->n_clients >= CLIENTS_MAX) {
423                 log_error("Too many clients, refusing connection.");
424                 return 0;
425         }
426
427         log_debug("New fsck client connected to fd: %d", new_client_fd);
428
429         c = new0(Client, 1);
430         if (!c) {
431                 log_oom();
432                 return 0;
433         }
434
435         c->fd = new_client_fd;
436         new_client_fd = -1;
437
438         r = sd_event_add_io(m->event, &c->event_source, c->fd, EPOLLIN, client_progress_handler, c);
439         if (r < 0) {
440                 log_oom();
441                 return 0;
442         }
443
444         LIST_PREPEND(clients, m->clients, c);
445         m->n_clients++;
446         c->manager = m;
447
448         /* only request the client to cancel now in case the request is dropped by the client (chance to recancel) */
449         if (m->cancel_requested)
450                 client_request_cancel(c);
451
452         c = NULL;
453         return 0;
454 }
455
456 static void manager_free(Manager *m) {
457         if (!m)
458                 return;
459
460         /* clear last line */
461         manager_write_console(m, NULL);
462
463         sd_event_source_unref(m->connection_event_source);
464         safe_close(m->connection_fd);
465
466         while (m->clients)
467                 client_free(m->clients);
468
469         manager_disconnect_plymouth(m);
470
471         sd_event_unref(m->event);
472
473         free(m);
474 }
475
476 static int manager_new(Manager **ret, int fd) {
477         _cleanup_(manager_freep) Manager *m = NULL;
478         int r;
479
480         assert(ret);
481
482         m = new0(Manager, 1);
483         if (!m)
484                 return -ENOMEM;
485
486         m->plymouth_fd = -1;
487         m->connection_fd = fd;
488         m->percent = 100;
489
490         r = sd_event_default(&m->event);
491         if (r < 0)
492                 return r;
493
494         if (access("/run/systemd/show-status", F_OK) >= 0)
495                 m->show_status_console = true;
496
497         r = sd_event_add_io(m->event, &m->connection_event_source, fd, EPOLLIN, manager_new_connection_handler, m);
498         if (r < 0)
499                 return r;
500
501         *ret = m;
502         m = NULL;
503
504         return 0;
505 }
506
507 static int run_event_loop_with_timeout(sd_event *e, usec_t timeout) {
508         int r, code;
509
510         assert(e);
511
512         for (;;) {
513                 r = sd_event_get_state(e);
514                 if (r < 0)
515                         return r;
516                 if (r == SD_EVENT_FINISHED)
517                         break;
518
519                 r = sd_event_run(e, timeout);
520                 if (r < 0)
521                         return r;
522
523                 /* timeout reached */
524                 if (r == 0) {
525                         sd_event_exit(e, 0);
526                         break;
527                 }
528         }
529
530         r = sd_event_get_exit_code(e, &code);
531         if (r < 0)
532                 return r;
533
534         return code;
535 }
536
537 static void help(void) {
538         printf("%s [OPTIONS...]\n\n"
539                "Capture fsck progress and forward one stream to plymouth\n\n"
540                "  -h --help             Show this help\n"
541                "     --version          Show package version\n",
542                program_invocation_short_name);
543 }
544
545 static int parse_argv(int argc, char *argv[]) {
546
547         enum {
548                 ARG_VERSION = 0x100,
549                 ARG_ROOT,
550         };
551
552         static const struct option options[] = {
553                 { "help",      no_argument,       NULL, 'h'           },
554                 { "version",   no_argument,       NULL, ARG_VERSION   },
555                 {}
556         };
557
558         int c;
559
560         assert(argc >= 0);
561         assert(argv);
562
563         while ((c = getopt_long(argc, argv, "hv", options, NULL)) >= 0)
564                 switch (c) {
565
566                 case 'h':
567                         help();
568                         return 0;
569
570                 case ARG_VERSION:
571                         puts(PACKAGE_STRING);
572                         puts(SYSTEMD_FEATURES);
573                         return 0;
574
575                 case '?':
576                         return -EINVAL;
577
578                 default:
579                         assert_not_reached("Unhandled option");
580                 }
581
582         if (optind < argc) {
583                 log_error("Extraneous arguments");
584                 return -EINVAL;
585         }
586
587         return 1;
588 }
589
590 int main(int argc, char *argv[]) {
591         _cleanup_(manager_freep) Manager *m = NULL;
592         int fd = -1;
593         int r, n;
594
595         log_set_target(LOG_TARGET_AUTO);
596         log_parse_environment();
597         log_open();
598         init_gettext();
599
600         r = parse_argv(argc, argv);
601         if (r <= 0)
602                 goto finish;
603
604         n = sd_listen_fds(0);
605         if (n > 1) {
606                 log_error("Too many file descriptors received.");
607                 r = -EINVAL;
608                 goto finish;
609         } else if (n == 1)
610                 fd = SD_LISTEN_FDS_START + 0;
611         else {
612                 fd = make_socket_fd(LOG_DEBUG, FSCKD_SOCKET_PATH, SOCK_STREAM | SOCK_CLOEXEC);
613                 if (fd < 0) {
614                         r = log_error_errno(fd, "Couldn't create listening socket fd on %s: %m", FSCKD_SOCKET_PATH);
615                         goto finish;
616                 }
617         }
618
619         r = manager_new(&m, fd);
620         if (r < 0) {
621                 log_error_errno(r, "Failed to allocate manager: %m");
622                 goto finish;
623         }
624
625         r = run_event_loop_with_timeout(m->event, IDLE_TIME_SECONDS * USEC_PER_SEC);
626         if (r < 0) {
627                 log_error_errno(r, "Failed to run event loop: %m");
628                 goto finish;
629         }
630
631         sd_event_get_exit_code(m->event, &r);
632
633 finish:
634         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
635 }