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