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