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