chiark / gitweb /
f24715cee31d7a87c608e93bac1eee1601cc7eb5
[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, *l10n_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                 l10n_cancel_message = _("Press Ctrl+C to cancel all filesystem checks in progress");
292                 plymouth_cancel_message = strjoina("fsckd-cancel-msg:", l10n_cancel_message);
293
294                 r = plymouth_send_message(m->plymouth_fd, plymouth_cancel_message, false);
295                 if (r < 0)
296                         log_warning_errno(r, "Can't send filesystem cancel message to plymouth: %m");
297
298         } else if (m->numdevices == 0) {
299
300                 m->plymouth_cancel_sent = false;
301
302                 r = plymouth_send_message(m->plymouth_fd, "", false);
303                 if (r < 0)
304                         log_warning_errno(r, "Can't clear plymouth filesystem cancel message: %m");
305         }
306
307         r = plymouth_send_message(m->plymouth_fd,  message, true);
308         if (r < 0)
309                 return log_warning_errno(r, "Couldn't send \"%s\" to plymouth: %m", message);
310
311         return 0;
312 }
313
314 static int manager_update_global_progress(Manager *m) {
315         Client *current = NULL;
316         _cleanup_free_ char *console_message = NULL;
317         _cleanup_free_ char *fsck_message = NULL;
318         int current_numdevices = 0, r;
319         double current_percent = 100;
320
321         /* get the overall percentage */
322         LIST_FOREACH(clients, current, m->clients) {
323                 current_numdevices++;
324
325                 /* right now, we only keep the minimum % of all fsckd processes. We could in the future trying to be
326                    linear, but max changes and corresponds to the pass. We have all the informations into fsckd
327                    already if we can treat that in a smarter way. */
328                 current_percent = MIN(current_percent, current->percent);
329         }
330
331         /* update if there is anything user-visible to update */
332         if (fabs(current_percent - m->percent) > 0.001 || current_numdevices != m->numdevices) {
333                 m->numdevices = current_numdevices;
334                 m->percent = current_percent;
335
336                 if (asprintf(&console_message,
337                              ngettext("Checking in progress on %d disk (%3.1f%% complete)",
338                                       "Checking in progress on %d disks (%3.1f%% complete)", m->numdevices),
339                                       m->numdevices, m->percent) < 0)
340                         return -ENOMEM;
341
342                 if (asprintf(&fsck_message, "fsckd:%d:%3.1f:%s", m->numdevices, m->percent, console_message) < 0)
343                         return -ENOMEM;
344
345                 r = manager_write_console(m, console_message);
346                 if (r < 0)
347                         return r;
348
349                 /* try to connect to plymouth and send message */
350                 r = manager_send_plymouth_message(m, fsck_message);
351                 if (r < 0)
352                         return r;
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                         manager_update_global_progress(m);
382                 }
383                 return 0;
384         }
385
386         /* read actual data */
387         r = recv(fd, &fsck_data, sizeof(FsckProgress), 0);
388         if (r == 0) {
389                 log_debug("Fsck client connected to fd %d disconnected", client->fd);
390                 client_free(client);
391         } else if (r > 0 && r != sizeof(FsckProgress))
392                 log_warning("Unexpected data structure sent to fsckd socket from fd: %d. Ignoring", client->fd);
393         else if (r > 0 && r == sizeof(FsckProgress)) {
394                 client->devnum = fsck_data.devnum;
395                 client->cur = fsck_data.cur;
396                 client->max = fsck_data.max;
397                 client->pass = fsck_data.pass;
398                 client->percent = compute_percent(client->pass, client->cur, client->max);
399                 log_debug("Getting progress for %u:%u (%lu, %lu, %d) : %3.1f%%",
400                           major(client->devnum), minor(client->devnum),
401                           client->cur, client->max, client->pass, client->percent);
402         } else
403                 log_error_errno(r, "Unknown error while trying to read fsck data: %m");
404
405         manager_update_global_progress(m);
406
407         return 0;
408 }
409
410 static int manager_new_connection_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
411         _cleanup_(client_freep) Client *c = NULL;
412         _cleanup_close_ int new_client_fd = -1;
413         Manager *m = userdata;
414         int r;
415
416         assert(m);
417
418         /* Initialize and list new clients */
419         new_client_fd = accept4(m->connection_fd, NULL, NULL, SOCK_CLOEXEC);
420         if (new_client_fd < 0)
421                 return log_error_errno(errno, "Couldn't accept a new connection: %m");
422
423         if (m->n_clients >= CLIENTS_MAX) {
424                 log_error("Too many clients, refusing connection.");
425                 return 0;
426         }
427
428         log_debug("New fsck client connected to fd: %d", new_client_fd);
429
430         c = new0(Client, 1);
431         if (!c) {
432                 log_oom();
433                 return 0;
434         }
435
436         c->fd = new_client_fd;
437         new_client_fd = -1;
438
439         r = sd_event_add_io(m->event, &c->event_source, c->fd, EPOLLIN, client_progress_handler, c);
440         if (r < 0) {
441                 log_oom();
442                 return 0;
443         }
444
445         LIST_PREPEND(clients, m->clients, c);
446         m->n_clients++;
447         c->manager = m;
448
449         /* only request the client to cancel now in case the request is dropped by the client (chance to recancel) */
450         if (m->cancel_requested)
451                 client_request_cancel(c);
452
453         c = NULL;
454         return 0;
455 }
456
457 static void manager_free(Manager *m) {
458         if (!m)
459                 return;
460
461         /* clear last line */
462         manager_write_console(m, NULL);
463
464         sd_event_source_unref(m->connection_event_source);
465         safe_close(m->connection_fd);
466
467         while (m->clients)
468                 client_free(m->clients);
469
470         manager_disconnect_plymouth(m);
471
472         sd_event_unref(m->event);
473
474         free(m);
475 }
476
477 static int manager_new(Manager **ret, int fd) {
478         _cleanup_(manager_freep) Manager *m = NULL;
479         int r;
480
481         assert(ret);
482
483         m = new0(Manager, 1);
484         if (!m)
485                 return -ENOMEM;
486
487         m->plymouth_fd = -1;
488         m->connection_fd = fd;
489         m->percent = 100;
490
491         r = sd_event_default(&m->event);
492         if (r < 0)
493                 return r;
494
495         if (access("/run/systemd/show-status", F_OK) >= 0)
496                 m->show_status_console = true;
497
498         r = sd_event_add_io(m->event, &m->connection_event_source, fd, EPOLLIN, manager_new_connection_handler, m);
499         if (r < 0)
500                 return r;
501
502         *ret = m;
503         m = NULL;
504
505         return 0;
506 }
507
508 static int run_event_loop_with_timeout(sd_event *e, usec_t timeout) {
509         int r, code;
510
511         assert(e);
512
513         for (;;) {
514                 r = sd_event_get_state(e);
515                 if (r < 0)
516                         return r;
517                 if (r == SD_EVENT_FINISHED)
518                         break;
519
520                 r = sd_event_run(e, timeout);
521                 if (r < 0)
522                         return r;
523
524                 /* timeout reached */
525                 if (r == 0) {
526                         sd_event_exit(e, 0);
527                         break;
528                 }
529         }
530
531         r = sd_event_get_exit_code(e, &code);
532         if (r < 0)
533                 return r;
534
535         return code;
536 }
537
538 static void help(void) {
539         printf("%s [OPTIONS...]\n\n"
540                "Capture fsck progress and forward one stream to plymouth\n\n"
541                "  -h --help             Show this help\n"
542                "     --version          Show package version\n",
543                program_invocation_short_name);
544 }
545
546 static int parse_argv(int argc, char *argv[]) {
547
548         enum {
549                 ARG_VERSION = 0x100,
550                 ARG_ROOT,
551         };
552
553         static const struct option options[] = {
554                 { "help",      no_argument,       NULL, 'h'           },
555                 { "version",   no_argument,       NULL, ARG_VERSION   },
556                 {}
557         };
558
559         int c;
560
561         assert(argc >= 0);
562         assert(argv);
563
564         while ((c = getopt_long(argc, argv, "hv", options, NULL)) >= 0)
565                 switch (c) {
566
567                 case 'h':
568                         help();
569                         return 0;
570
571                 case ARG_VERSION:
572                         puts(PACKAGE_STRING);
573                         puts(SYSTEMD_FEATURES);
574                         return 0;
575
576                 case '?':
577                         return -EINVAL;
578
579                 default:
580                         assert_not_reached("Unhandled option");
581                 }
582
583         if (optind < argc) {
584                 log_error("Extraneous arguments");
585                 return -EINVAL;
586         }
587
588         return 1;
589 }
590
591 int main(int argc, char *argv[]) {
592         _cleanup_(manager_freep) Manager *m = NULL;
593         int fd = -1;
594         int r, n;
595
596         log_set_target(LOG_TARGET_AUTO);
597         log_parse_environment();
598         log_open();
599         init_gettext();
600
601         r = parse_argv(argc, argv);
602         if (r <= 0)
603                 goto finish;
604
605         n = sd_listen_fds(0);
606         if (n > 1) {
607                 log_error("Too many file descriptors received.");
608                 r = -EINVAL;
609                 goto finish;
610         } else if (n == 1)
611                 fd = SD_LISTEN_FDS_START + 0;
612         else {
613                 fd = make_socket_fd(LOG_DEBUG, FSCKD_SOCKET_PATH, SOCK_STREAM | SOCK_CLOEXEC);
614                 if (fd < 0) {
615                         r = log_error_errno(fd, "Couldn't create listening socket fd on %s: %m", FSCKD_SOCKET_PATH);
616                         goto finish;
617                 }
618         }
619
620         r = manager_new(&m, fd);
621         if (r < 0) {
622                 log_error_errno(r, "Failed to allocate manager: %m");
623                 goto finish;
624         }
625
626         r = run_event_loop_with_timeout(m->event, IDLE_TIME_SECONDS * USEC_PER_SEC);
627         if (r < 0) {
628                 log_error_errno(r, "Failed to run event loop: %m");
629                 goto finish;
630         }
631
632         sd_event_get_exit_code(m->event, &r);
633
634 finish:
635         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
636 }