]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/fsckd/fsckd.c
fsck: use _cleanup_close_pair_ where appropriate
[thirdparty/systemd.git] / src / fsckd / fsckd.c
CommitLineData
ac6e2f0d
DR
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>
b0d92464 27#include <libintl.h>
ac6e2f0d
DR
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
d2268a20 37#include "sd-daemon.h"
ac6e2f0d 38#include "build.h"
07f9a21b 39#include "def.h"
ac6e2f0d 40#include "event-util.h"
ac6e2f0d
DR
41#include "log.h"
42#include "list.h"
43#include "macro.h"
ac6e2f0d
DR
44#include "socket-util.h"
45#include "util.h"
d2268a20 46#include "fsckd.h"
ac6e2f0d
DR
47
48#define IDLE_TIME_SECONDS 30
07f9a21b 49#define PLYMOUTH_REQUEST_KEY "K\2\2\3"
e78e0674 50#define CLIENTS_MAX 128
ac6e2f0d
DR
51
52struct Manager;
53
54typedef struct Client {
55 struct Manager *manager;
56 int fd;
57 dev_t devnum;
f8824a51 58
ac6e2f0d
DR
59 size_t cur;
60 size_t max;
61 int pass;
f8824a51 62
ac6e2f0d 63 double percent;
f8824a51 64
ac6e2f0d 65 size_t buflen;
07f9a21b 66 bool cancelled;
ac6e2f0d 67
d42688ef
LP
68 sd_event_source *event_source;
69
ac6e2f0d
DR
70 LIST_FIELDS(struct Client, clients);
71} Client;
72
73typedef struct Manager {
74 sd_event *event;
f8824a51
LP
75
76 LIST_HEAD(Client, clients);
e78e0674 77 unsigned n_clients;
f8824a51 78
ac6e2f0d 79 int clear;
57b394b5 80
ac6e2f0d 81 int connection_fd;
57b394b5 82 sd_event_source *connection_event_source;
f8824a51 83
ac6e2f0d 84 FILE *console;
1952708a 85
ac6e2f0d
DR
86 double percent;
87 int numdevices;
70463136 88
07f9a21b 89 int plymouth_fd;
70463136 90 sd_event_source *plymouth_event_source;
07f9a21b 91 bool plymouth_cancel_sent;
f8824a51 92
07f9a21b 93 bool cancel_requested;
ac6e2f0d
DR
94} Manager;
95
d42688ef 96static void client_free(Client *c);
ac6e2f0d 97static void manager_free(Manager *m);
d42688ef
LP
98
99DEFINE_TRIVIAL_CLEANUP_FUNC(Client*, client_free);
ac6e2f0d 100DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free);
ac6e2f0d
DR
101
102static 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
0b02c7c3 120static int client_request_cancel(Client *c) {
d2268a20
LP
121 FsckdMessage cancel_msg = {
122 .cancel = 1,
123 };
124
07f9a21b 125 ssize_t n;
07f9a21b 126
0b02c7c3
LP
127 assert(c);
128
129 if (c->cancelled)
130 return 0;
131
132 n = send(c->fd, &cancel_msg, sizeof(FsckdMessage), 0);
d2268a20 133 if (n < 0)
0b02c7c3 134 return log_warning_errno(errno, "Cannot send cancel to fsck on (%u:%u): %m", major(c->devnum), minor(c->devnum));
d2268a20 135 if ((size_t) n < sizeof(FsckdMessage)) {
0b02c7c3 136 log_warning("Short send when sending cancel to fsck on (%u:%u).", major(c->devnum), minor(c->devnum));
d2268a20
LP
137 return -EIO;
138 }
139
0b02c7c3
LP
140 c->cancelled = true;
141 return 1;
07f9a21b 142}
ac6e2f0d 143
f8824a51
LP
144static void client_free(Client *c) {
145 assert(c);
146
e78e0674 147 if (c->manager) {
f8824a51 148 LIST_REMOVE(clients, c->manager->clients, c);
e78e0674
LP
149 c->manager->n_clients--;
150 }
f8824a51 151
d42688ef
LP
152 sd_event_source_unref(c->event_source);
153
f8824a51
LP
154 safe_close(c->fd);
155 free(c);
ac6e2f0d
DR
156}
157
88b28381 158static void manager_disconnect_plymouth(Manager *m) {
70463136
LP
159 assert(m);
160
161 m->plymouth_event_source = sd_event_source_unref(m->plymouth_event_source);
c011cc26 162 m->plymouth_fd = safe_close(m->plymouth_fd);
07f9a21b
DR
163 m->plymouth_cancel_sent = false;
164}
165
88b28381 166static int manager_plymouth_feedback_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
07f9a21b
DR
167 Manager *m = userdata;
168 Client *current;
169 char buffer[6];
70463136 170 ssize_t l;
07f9a21b
DR
171
172 assert(m);
173
70463136
LP
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");
88b28381 177 manager_disconnect_plymouth(m);
70463136
LP
178 return -errno;
179 }
180 if (l == 0) {
88b28381 181 manager_disconnect_plymouth(m);
70463136
LP
182 return 0;
183 }
184
df4573e8 185 if (l > 1 && buffer[0] == '\15')
70463136
LP
186 log_error("Message update to plymouth wasn't delivered successfully");
187
188 /* the only answer support type we requested is a key interruption */
df4573e8 189 if (l > 2 && buffer[0] == '\2' && buffer[5] == '\3') {
70463136
LP
190 m->cancel_requested = true;
191
192 /* cancel all connected clients */
193 LIST_FOREACH(clients, current, m->clients)
0b02c7c3 194 client_request_cancel(current);
07f9a21b
DR
195 }
196
197 return 0;
198}
199
88b28381
LP
200static 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
225fail:
226 manager_disconnect_plymouth(m);
227 return r;
228}
229
230static int plymouth_send_message(int plymouth_fd, const char *message, bool update) {
07f9a21b 231 _cleanup_free_ char *packet = NULL;
3963cea3 232 int n;
07f9a21b
DR
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();
07f9a21b 240
3963cea3
LP
241 return loop_write(plymouth_fd, packet, n + 1, true);
242}
07f9a21b 243
88b28381 244static int manager_send_plymouth_message(Manager *m, const char *message) {
07f9a21b 245 const char *plymouth_cancel_message = NULL;
e9d2527f 246 int r;
07f9a21b 247
88b28381 248 r = manager_connect_plymouth(m);
07f9a21b
DR
249 if (r < 0)
250 return r;
251
252 if (!m->plymouth_cancel_sent) {
e9d2527f
LP
253
254 /* Indicate to plymouth that we listen to Ctrl+C */
07f9a21b
DR
255 r = loop_write(m->plymouth_fd, PLYMOUTH_REQUEST_KEY, sizeof(PLYMOUTH_REQUEST_KEY), true);
256 if (r < 0)
e9d2527f
LP
257 return log_warning_errno(r, "Can't send to plymouth cancel key: %m");
258
07f9a21b 259 m->plymouth_cancel_sent = true;
e9d2527f 260
b0d92464 261 plymouth_cancel_message = strjoina("fsckd-cancel-msg:", _("Press Ctrl+C to cancel all filesystem checks in progress"));
e9d2527f 262
88b28381 263 r = plymouth_send_message(m->plymouth_fd, plymouth_cancel_message, false);
07f9a21b
DR
264 if (r < 0)
265 log_warning_errno(r, "Can't send filesystem cancel message to plymouth: %m");
e9d2527f 266
07f9a21b 267 } else if (m->numdevices == 0) {
e9d2527f 268
07f9a21b 269 m->plymouth_cancel_sent = false;
e9d2527f 270
88b28381 271 r = plymouth_send_message(m->plymouth_fd, "", false);
07f9a21b
DR
272 if (r < 0)
273 log_warning_errno(r, "Can't clear plymouth filesystem cancel message: %m");
274 }
275
88b28381 276 r = plymouth_send_message(m->plymouth_fd, message, true);
07f9a21b 277 if (r < 0)
e9d2527f 278 return log_warning_errno(r, "Couldn't send \"%s\" to plymouth: %m", message);
07f9a21b
DR
279
280 return 0;
281}
282
88b28381 283static int manager_update_global_progress(Manager *m) {
ac6e2f0d
DR
284 Client *current = NULL;
285 _cleanup_free_ char *console_message = NULL;
07f9a21b
DR
286 _cleanup_free_ char *fsck_message = NULL;
287 int current_numdevices = 0, l = 0, r;
ac6e2f0d
DR
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
b0d92464
DR
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)
ac6e2f0d 309 return -ENOMEM;
705778ad 310
07f9a21b
DR
311 if (asprintf(&fsck_message, "fsckd:%d:%3.1f:%s", m->numdevices, m->percent, console_message) < 0)
312 return -ENOMEM;
ac6e2f0d
DR
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
07f9a21b 320 /* try to connect to plymouth and send message */
88b28381 321 r = manager_send_plymouth_message(m, fsck_message);
07f9a21b
DR
322 if (r < 0)
323 log_debug("Couldn't send message to plymouth");
324
ac6e2f0d
DR
325 if (l > m->clear)
326 m->clear = l;
327 }
328 return 0;
329}
330
88b28381 331static int client_progress_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
ac6e2f0d 332 Client *client = userdata;
ac6e2f0d
DR
333 FsckProgress fsck_data;
334 size_t buflen;
88b28381 335 Manager *m;
ac6e2f0d
DR
336 int r;
337
338 assert(client);
88b28381 339
ac6e2f0d
DR
340 m = client->manager;
341
07f9a21b 342 /* check first if we need to cancel this client */
0b02c7c3
LP
343 if (m->cancel_requested)
344 client_request_cancel(client);
07f9a21b 345
ac6e2f0d
DR
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);
f8824a51 354 client_free(client);
88b28381 355 r = manager_update_global_progress(m);
ac6e2f0d
DR
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);
f8824a51 366 client_free(client);
ac6e2f0d
DR
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
88b28381 381 r = manager_update_global_progress(m);
ac6e2f0d
DR
382 if (r < 0)
383 log_warning_errno(r, "Couldn't update global progress: %m");
384
385 return 0;
386}
387
88b28381 388static int manager_new_connection_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
d42688ef 389 _cleanup_(client_freep) Client *c = NULL;
e78e0674 390 _cleanup_close_ int new_client_fd = -1;
ac6e2f0d 391 Manager *m = userdata;
e78e0674 392 int r;
ac6e2f0d
DR
393
394 assert(m);
395
396 /* Initialize and list new clients */
397 new_client_fd = accept4(m->connection_fd, NULL, NULL, SOCK_CLOEXEC);
2de68ed9 398 if (new_client_fd < 0)
ac6e2f0d
DR
399 return log_error_errno(errno, "Couldn't accept a new connection: %m");
400
e78e0674
LP
401 if (m->n_clients >= CLIENTS_MAX) {
402 log_error("Too many clients, refusing connection.");
403 return 0;
404 }
405
2de68ed9
LP
406 log_debug("New fsck client connected to fd: %d", new_client_fd);
407
d42688ef
LP
408 c = new0(Client, 1);
409 if (!c) {
d42688ef
LP
410 log_oom();
411 return 0;
412 }
413
414 c->fd = new_client_fd;
e78e0674
LP
415 new_client_fd = -1;
416
d42688ef 417 r = sd_event_add_io(m->event, &c->event_source, c->fd, EPOLLIN, client_progress_handler, c);
2de68ed9 418 if (r < 0) {
d42688ef
LP
419 log_oom();
420 return 0;
2de68ed9 421 }
d42688ef
LP
422
423 LIST_PREPEND(clients, m->clients, c);
e78e0674 424 m->n_clients++;
d42688ef
LP
425 c->manager = m;
426
2de68ed9
LP
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)
d42688ef 429 client_request_cancel(c);
2de68ed9 430
d42688ef 431 c = NULL;
ac6e2f0d
DR
432 return 0;
433}
434
435static void manager_free(Manager *m) {
ac6e2f0d
DR
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
57b394b5 450 sd_event_source_unref(m->connection_event_source);
ac6e2f0d 451 safe_close(m->connection_fd);
70463136 452
f8824a51
LP
453 while (m->clients)
454 client_free(m->clients);
ac6e2f0d 455
88b28381
LP
456 manager_disconnect_plymouth(m);
457
458 if (m->console)
459 fclose(m->console);
460
ac6e2f0d
DR
461 sd_event_unref(m->event);
462
463 free(m);
464}
465
466static int manager_new(Manager **ret, int fd) {
88b28381 467 _cleanup_(manager_freep) Manager *m = NULL;
ac6e2f0d
DR
468 int r;
469
470 assert(ret);
471
472 m = new0(Manager, 1);
473 if (!m)
474 return -ENOMEM;
475
88b28381
LP
476 m->plymouth_fd = -1;
477 m->connection_fd = fd;
478 m->percent = 100;
479
ac6e2f0d
DR
480 r = sd_event_default(&m->event);
481 if (r < 0)
482 return r;
483
19e887e7
DR
484 if (access("/run/systemd/show-status", F_OK) >= 0) {
485 m->console = fopen("/dev/console", "we");
486 if (!m->console)
88b28381 487 return -errno;
19e887e7 488 }
ac6e2f0d 489
57b394b5 490 r = sd_event_add_io(m->event, &m->connection_event_source, fd, EPOLLIN, manager_new_connection_handler, m);
88b28381
LP
491 if (r < 0)
492 return r;
493
ac6e2f0d
DR
494 *ret = m;
495 m = NULL;
496
497 return 0;
498}
499
500static 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
530static 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
538static 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
583int main(int argc, char *argv[]) {
88b28381 584 _cleanup_(manager_freep) Manager *m = NULL;
ac6e2f0d
DR
585 int fd = -1;
586 int r, n;
587
588 log_set_target(LOG_TARGET_AUTO);
589 log_parse_environment();
590 log_open();
b0d92464 591 init_gettext();
ac6e2f0d
DR
592
593 r = parse_argv(argc, argv);
594 if (r <= 0)
a922c18b 595 goto finish;
ac6e2f0d
DR
596
597 n = sd_listen_fds(0);
598 if (n > 1) {
599 log_error("Too many file descriptors received.");
a922c18b
LP
600 r = -EINVAL;
601 goto finish;
adfe5671 602 } else if (n == 1)
ac6e2f0d 603 fd = SD_LISTEN_FDS_START + 0;
adfe5671 604 else {
ac6e2f0d
DR
605 fd = make_socket_fd(LOG_DEBUG, FSCKD_SOCKET_PATH, SOCK_STREAM | SOCK_CLOEXEC);
606 if (fd < 0) {
a922c18b
LP
607 r = log_error_errno(fd, "Couldn't create listening socket fd on %s: %m", FSCKD_SOCKET_PATH);
608 goto finish;
ac6e2f0d
DR
609 }
610 }
611
612 r = manager_new(&m, fd);
613 if (r < 0) {
614 log_error_errno(r, "Failed to allocate manager: %m");
a922c18b 615 goto finish;
ac6e2f0d
DR
616 }
617
ac6e2f0d
DR
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");
a922c18b 621 goto finish;
ac6e2f0d
DR
622 }
623
624 sd_event_get_exit_code(m->event, &r);
625
a922c18b 626finish:
ac6e2f0d
DR
627 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
628}