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