1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2015 Canonical
9 Didier Roche <didrocks@ubuntu.com>
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.
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.
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/>.
31 #include <sys/socket.h>
32 #include <sys/types.h>
37 #include "event-util.h"
42 #include "sd-daemon.h"
43 #include "socket-util.h"
46 #define IDLE_TIME_SECONDS 30
50 typedef struct Client {
51 struct Manager *manager;
60 LIST_FIELDS(struct Client, clients);
63 typedef struct Manager {
73 static void manager_free(Manager *m);
74 DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free);
75 #define _cleanup_manager_free_ _cleanup_(manager_freep)
77 static double compute_percent(int pass, size_t cur, size_t max) {
78 /* Values stolen from e2fsck */
80 static const double pass_table[] = {
81 0, 70, 90, 92, 95, 100
87 if ((unsigned) pass >= ELEMENTSOF(pass_table) || max == 0)
90 return pass_table[pass-1] +
91 (pass_table[pass] - pass_table[pass-1]) *
96 static void remove_client(Client **first, Client *item) {
97 LIST_REMOVE(clients, *first, item);
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;
108 /* get the overall percentage */
109 LIST_FOREACH(clients, current, m->clients) {
110 current_numdevices++;
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);
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;
123 if (asprintf(&console_message, "Checking in progress on %d disks (%3.1f%% complete)",
124 m->numdevices, m->percent) < 0)
127 /* write to console */
129 fprintf(m->console, "\r%s\r%n", console_message, &l);
139 static int progress_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
140 Client *client = userdata;
142 FsckProgress fsck_data;
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 */
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);
160 log_warning_errno(r, "Couldn't update global progress: %m");
165 /* read actual data */
166 r = recv(fd, &fsck_data, sizeof(FsckProgress), 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);
182 log_error_errno(r, "Unknown error while trying to read fsck data: %m");
184 r = update_global_progress(m);
186 log_warning_errno(r, "Couldn't update global progress: %m");
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;
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);
205 client->fd = new_client_fd;
207 LIST_PREPEND(clients, m->clients, client);
208 r = sd_event_add_io(m->event, NULL, client->fd, EPOLLIN, progress_handler, client);
210 remove_client(&(m->clients), client);
214 return log_error_errno(errno, "Couldn't accept a new connection: %m");
219 static void manager_free(Manager *m) {
220 Client *current = NULL, *l = NULL;
224 /* clear last line */
225 if (m->console && m->clear > 0) {
228 fputc('\r', m->console);
229 for (j = 0; j < (unsigned) m->clear; j++)
230 fputc(' ', m->console);
231 fputc('\r', m->console);
235 safe_close(m->connection_fd);
239 LIST_FOREACH_SAFE(clients, current, l, m->clients)
240 remove_client(&(m->clients), current);
242 sd_event_unref(m->event);
247 static int manager_new(Manager **ret, int fd) {
248 _cleanup_manager_free_ Manager *m = NULL;
253 m = new0(Manager, 1);
257 r = sd_event_default(&m->event);
261 m->connection_fd = fd;
262 m->console = fopen("/dev/console", "we");
264 return log_warning_errno(errno, "Can't connect to /dev/console: %m");
273 static int run_event_loop_with_timeout(sd_event *e, usec_t timeout) {
279 r = sd_event_get_state(e);
282 if (r == SD_EVENT_FINISHED)
285 r = sd_event_run(e, timeout);
289 /* timeout reached */
296 r = sd_event_get_exit_code(e, &code);
303 static void help(void) {
304 printf("%s [OPTIONS...]\n\n"
305 "Capture fsck progress and forward one stream to plymouth\n\n"
306 " -h --help Show this help\n"
307 " --version Show package version\n",
308 program_invocation_short_name);
311 static int parse_argv(int argc, char *argv[]) {
318 static const struct option options[] = {
319 { "help", no_argument, NULL, 'h' },
320 { "version", no_argument, NULL, ARG_VERSION },
329 while ((c = getopt_long(argc, argv, "hv", options, NULL)) >= 0)
337 puts(PACKAGE_STRING);
338 puts(SYSTEMD_FEATURES);
345 assert_not_reached("Unhandled option");
349 log_error("Extraneous arguments");
356 int main(int argc, char *argv[]) {
357 _cleanup_manager_free_ Manager *m = NULL;
361 log_set_target(LOG_TARGET_AUTO);
362 log_parse_environment();
365 r = parse_argv(argc, argv);
367 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
369 n = sd_listen_fds(0);
371 log_error("Too many file descriptors received.");
374 fd = SD_LISTEN_FDS_START + 0;
376 fd = make_socket_fd(LOG_DEBUG, FSCKD_SOCKET_PATH, SOCK_STREAM | SOCK_CLOEXEC);
378 log_error_errno(r, "Couldn't create listening socket fd on %s: %m", FSCKD_SOCKET_PATH);
383 r = manager_new(&m, fd);
385 log_error_errno(r, "Failed to allocate manager: %m");
389 r = sd_event_add_io(m->event, NULL, fd, EPOLLIN, new_connection_handler, m);
391 log_error_errno(r, "Can't listen to connection socket: %m");
395 r = run_event_loop_with_timeout(m->event, IDLE_TIME_SECONDS * USEC_PER_SEC);
397 log_error_errno(r, "Failed to run event loop: %m");
401 sd_event_get_exit_code(m->event, &r);
403 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;