chiark / gitweb /
6b2eeb067f07903d1f374a6f3d6338df78e36b4a
[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 <math.h>
28 #include <stdbool.h>
29 #include <stdlib.h>
30 #include <stdio.h>
31 #include <sys/socket.h>
32 #include <sys/types.h>
33 #include <sys/un.h>
34 #include <unistd.h>
35
36 #include "build.h"
37 #include "event-util.h"
38 #include "fsckd.h"
39 #include "log.h"
40 #include "list.h"
41 #include "macro.h"
42 #include "sd-daemon.h"
43 #include "socket-util.h"
44 #include "util.h"
45
46 #define IDLE_TIME_SECONDS 30
47
48 struct Manager;
49
50 typedef struct Client {
51         struct Manager *manager;
52         int fd;
53         dev_t devnum;
54         size_t cur;
55         size_t max;
56         int pass;
57         double percent;
58         size_t buflen;
59
60         LIST_FIELDS(struct Client, clients);
61 } Client;
62
63 typedef struct Manager {
64         sd_event *event;
65         Client *clients;
66         int clear;
67         int connection_fd;
68         FILE *console;
69         double percent;
70         int numdevices;
71 } Manager;
72
73 static void manager_free(Manager *m);
74 DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free);
75 #define _cleanup_manager_free_ _cleanup_(manager_freep)
76
77 static double compute_percent(int pass, size_t cur, size_t max) {
78         /* Values stolen from e2fsck */
79
80         static const double pass_table[] = {
81                 0, 70, 90, 92, 95, 100
82         };
83
84         if (pass <= 0)
85                 return 0.0;
86
87         if ((unsigned) pass >= ELEMENTSOF(pass_table) || max == 0)
88                 return 100.0;
89
90         return pass_table[pass-1] +
91                 (pass_table[pass] - pass_table[pass-1]) *
92                 (double) cur / max;
93 }
94
95
96 static void remove_client(Client **first, Client *item) {
97         LIST_REMOVE(clients, *first, item);
98         safe_close(item->fd);
99         free(item);
100 }
101
102 static int update_global_progress(Manager *m) {
103         Client *current = NULL;
104         _cleanup_free_ char *console_message = NULL;
105         int current_numdevices = 0, l = 0;
106         double current_percent = 100;
107
108         /* get the overall percentage */
109         LIST_FOREACH(clients, current, m->clients) {
110                 current_numdevices++;
111
112                 /* right now, we only keep the minimum % of all fsckd processes. We could in the future trying to be
113                    linear, but max changes and corresponds to the pass. We have all the informations into fsckd
114                    already if we can treat that in a smarter way. */
115                 current_percent = MIN(current_percent, current->percent);
116         }
117
118         /* update if there is anything user-visible to update */
119         if (fabs(current_percent - m->percent) > 0.001 || current_numdevices != m->numdevices) {
120                 m->numdevices = current_numdevices;
121                 m->percent = current_percent;
122
123                 if (asprintf(&console_message, "Checking in progress on %d disks (%3.1f%% complete)",
124                                                 m->numdevices, m->percent) < 0)
125                         return -ENOMEM;
126
127                 /* write to console */
128                 if (m->console) {
129                         fprintf(m->console, "\r%s\r%n", console_message, &l);
130                         fflush(m->console);
131                 }
132
133                 if (l > m->clear)
134                         m->clear = l;
135         }
136         return 0;
137 }
138
139 static int progress_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
140         Client *client = userdata;
141         Manager *m = NULL;
142         FsckProgress fsck_data;
143         size_t buflen;
144         int r;
145
146         assert(client);
147         m = client->manager;
148
149         /* ensure we have enough data to read */
150         r = ioctl(fd, FIONREAD, &buflen);
151         if (r == 0 && buflen != 0 && (size_t) buflen < sizeof(FsckProgress)) {
152                 if (client->buflen != buflen)
153                         client->buflen = buflen;
154                 /* we got twice the same size from a bad behaving client, kick it off the list */
155                 else {
156                         log_warning("Closing bad behaving fsck client connection at fd %d", client->fd);
157                         remove_client(&(m->clients), client);
158                         r = update_global_progress(m);
159                         if (r < 0)
160                                 log_warning_errno(r, "Couldn't update global progress: %m");
161                 }
162                 return 0;
163         }
164
165         /* read actual data */
166         r = recv(fd, &fsck_data, sizeof(FsckProgress), 0);
167         if (r == 0) {
168                 log_debug("Fsck client connected to fd %d disconnected", client->fd);
169                 remove_client(&(m->clients), client);
170         } else if (r > 0 && r != sizeof(FsckProgress))
171                 log_warning("Unexpected data structure sent to fsckd socket from fd: %d. Ignoring", client->fd);
172         else if (r > 0 && r == sizeof(FsckProgress)) {
173                 client->devnum = fsck_data.devnum;
174                 client->cur = fsck_data.cur;
175                 client->max = fsck_data.max;
176                 client->pass = fsck_data.pass;
177                 client->percent = compute_percent(client->pass, client->cur, client->max);
178                 log_debug("Getting progress for %u:%u (%lu, %lu, %d) : %3.1f%%",
179                           major(client->devnum), minor(client->devnum),
180                           client->cur, client->max, client->pass, client->percent);
181         } else
182                 log_error_errno(r, "Unknown error while trying to read fsck data: %m");
183
184         r = update_global_progress(m);
185         if (r < 0)
186                 log_warning_errno(r, "Couldn't update global progress: %m");
187
188         return 0;
189 }
190
191 static int new_connection_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
192         Manager *m = userdata;
193         Client *client = NULL;
194         int new_client_fd, r;
195
196         assert(m);
197
198         /* Initialize and list new clients */
199         new_client_fd = accept4(m->connection_fd, NULL, NULL, SOCK_CLOEXEC);
200         if (new_client_fd > 0) {
201                 log_debug("New fsck client connected to fd: %d", new_client_fd);
202                 client = new0(Client, 1);
203                 if (!client)
204                         return log_oom();
205                 client->fd = new_client_fd;
206                 client->manager = m;
207                 LIST_PREPEND(clients, m->clients, client);
208                 r = sd_event_add_io(m->event, NULL, client->fd, EPOLLIN, progress_handler, client);
209                 if (r < 0) {
210                         remove_client(&(m->clients), client);
211                         return r;
212                 }
213         } else
214                 return log_error_errno(errno, "Couldn't accept a new connection: %m");
215
216         return 0;
217 }
218
219 static void manager_free(Manager *m) {
220         Client *current = NULL, *l = NULL;
221         if (!m)
222                 return;
223
224         /* clear last line */
225         if (m->console && m->clear > 0) {
226                 unsigned j;
227
228                 fputc('\r', m->console);
229                 for (j = 0; j < (unsigned) m->clear; j++)
230                         fputc(' ', m->console);
231                 fputc('\r', m->console);
232                 fflush(m->console);
233         }
234
235         safe_close(m->connection_fd);
236         if (m->console)
237                 fclose(m->console);
238
239         LIST_FOREACH_SAFE(clients, current, l, m->clients)
240                 remove_client(&(m->clients), current);
241
242         sd_event_unref(m->event);
243
244         free(m);
245 }
246
247 static int manager_new(Manager **ret, int fd) {
248         _cleanup_manager_free_ Manager *m = NULL;
249         int r;
250
251         assert(ret);
252
253         m = new0(Manager, 1);
254         if (!m)
255                 return -ENOMEM;
256
257         r = sd_event_default(&m->event);
258         if (r < 0)
259                 return r;
260
261         m->connection_fd = fd;
262         if (access("/run/systemd/show-status", F_OK) >= 0) {
263                 m->console = fopen("/dev/console", "we");
264                 if (!m->console)
265                         return log_warning_errno(errno, "Can't connect to /dev/console: %m");
266         }
267         m->percent = 100;
268
269         *ret = m;
270         m = NULL;
271
272         return 0;
273 }
274
275 static int run_event_loop_with_timeout(sd_event *e, usec_t timeout) {
276         int r, code;
277
278         assert(e);
279
280         for (;;) {
281                 r = sd_event_get_state(e);
282                 if (r < 0)
283                         return r;
284                 if (r == SD_EVENT_FINISHED)
285                         break;
286
287                 r = sd_event_run(e, timeout);
288                 if (r < 0)
289                         return r;
290
291                 /* timeout reached */
292                 if (r == 0) {
293                         sd_event_exit(e, 0);
294                         break;
295                 }
296         }
297
298         r = sd_event_get_exit_code(e, &code);
299         if (r < 0)
300                 return r;
301
302         return code;
303 }
304
305 static void help(void) {
306         printf("%s [OPTIONS...]\n\n"
307                "Capture fsck progress and forward one stream to plymouth\n\n"
308                "  -h --help             Show this help\n"
309                "     --version          Show package version\n",
310                program_invocation_short_name);
311 }
312
313 static int parse_argv(int argc, char *argv[]) {
314
315         enum {
316                 ARG_VERSION = 0x100,
317                 ARG_ROOT,
318         };
319
320         static const struct option options[] = {
321                 { "help",      no_argument,       NULL, 'h'           },
322                 { "version",   no_argument,       NULL, ARG_VERSION   },
323                 {}
324         };
325
326         int c;
327
328         assert(argc >= 0);
329         assert(argv);
330
331         while ((c = getopt_long(argc, argv, "hv", options, NULL)) >= 0)
332                 switch (c) {
333
334                 case 'h':
335                         help();
336                         return 0;
337
338                 case ARG_VERSION:
339                         puts(PACKAGE_STRING);
340                         puts(SYSTEMD_FEATURES);
341                         return 0;
342
343                 case '?':
344                         return -EINVAL;
345
346                 default:
347                         assert_not_reached("Unhandled option");
348                 }
349
350         if (optind < argc) {
351                 log_error("Extraneous arguments");
352                 return -EINVAL;
353         }
354
355         return 1;
356 }
357
358 int main(int argc, char *argv[]) {
359         _cleanup_manager_free_ Manager *m = NULL;
360         int fd = -1;
361         int r, n;
362
363         log_set_target(LOG_TARGET_AUTO);
364         log_parse_environment();
365         log_open();
366
367         r = parse_argv(argc, argv);
368         if (r <= 0)
369                 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
370
371         n = sd_listen_fds(0);
372         if (n > 1) {
373                 log_error("Too many file descriptors received.");
374                 return EXIT_FAILURE;
375         } else if (n == 1) {
376                 fd = SD_LISTEN_FDS_START + 0;
377         } else {
378                 fd = make_socket_fd(LOG_DEBUG, FSCKD_SOCKET_PATH, SOCK_STREAM | SOCK_CLOEXEC);
379                 if (fd < 0) {
380                         log_error_errno(r, "Couldn't create listening socket fd on %s: %m", FSCKD_SOCKET_PATH);
381                         return EXIT_FAILURE;
382                 }
383         }
384
385         r = manager_new(&m, fd);
386         if (r < 0) {
387                 log_error_errno(r, "Failed to allocate manager: %m");
388                 return EXIT_FAILURE;
389         }
390
391         r = sd_event_add_io(m->event, NULL, fd, EPOLLIN, new_connection_handler, m);
392         if (r < 0) {
393                 log_error_errno(r, "Can't listen to connection socket: %m");
394                 return EXIT_FAILURE;
395         }
396
397         r = run_event_loop_with_timeout(m->event, IDLE_TIME_SECONDS * USEC_PER_SEC);
398         if (r < 0) {
399                 log_error_errno(r, "Failed to run event loop: %m");
400                 return EXIT_FAILURE;
401         }
402
403         sd_event_get_exit_code(m->event, &r);
404
405         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
406 }