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