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