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