chiark / gitweb /
f60cc1d9126ce4ebca93c70bd6081c95e3dcef5c
[elogind.git] / src / fsck / fsck.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2010 Lennart Poettering
7
8   systemd is free software; you can redistribute it and/or modify it
9   under the terms of the GNU Lesser General Public License as published by
10   the Free Software Foundation; either version 2.1 of the License, or
11   (at your option) any later version.
12
13   systemd is distributed in the hope that it will be useful, but
14   WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16   Lesser General Public License for more details.
17
18   You should have received a copy of the GNU Lesser General Public License
19   along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <stdio.h>
23 #include <stdbool.h>
24 #include <string.h>
25 #include <errno.h>
26 #include <unistd.h>
27 #include <fcntl.h>
28 #include <sys/file.h>
29
30 #include "sd-bus.h"
31 #include "libudev.h"
32
33 #include "util.h"
34 #include "special.h"
35 #include "bus-util.h"
36 #include "bus-error.h"
37 #include "bus-errors.h"
38 #include "fileio.h"
39 #include "udev-util.h"
40
41 static bool arg_skip = false;
42 static bool arg_force = false;
43 static bool arg_show_progress = false;
44
45 static void start_target(const char *target) {
46         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
47         _cleanup_bus_unref_ sd_bus *bus = NULL;
48         int r;
49
50         assert(target);
51
52         r = bus_open_system_systemd(&bus);
53         if (r < 0) {
54                 log_error("Failed to get D-Bus connection: %s", strerror(-r));
55                 return;
56         }
57
58         log_info("Running request %s/start/replace", target);
59
60         /* Start these units only if we can replace base.target with it */
61         r = sd_bus_call_method(bus,
62                                "org.freedesktop.systemd1",
63                                "/org/freedesktop/systemd1",
64                                "org.freedesktop.systemd1.Manager",
65                                "StartUnitReplace",
66                                &error,
67                                NULL,
68                                "sss", "basic.target", target, "replace");
69
70         /* Don't print a warning if we aren't called during startup */
71         if (r < 0 && !sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_JOB))
72                 log_error("Failed to start unit: %s", bus_error_message(&error, -r));
73 }
74
75 static int parse_proc_cmdline_item(const char *key, const char *value) {
76
77         if (streq(key, "fsck.mode") && value) {
78
79                 if (streq(value, "auto"))
80                         arg_force = arg_skip = false;
81                 else if (streq(value, "force"))
82                         arg_force = true;
83                 else if (streq(value, "skip"))
84                         arg_skip = true;
85                 else
86                         log_warning("Invalid fsck.mode= parameter. Ignoring.");
87         } else if (startswith(key, "fsck."))
88                 log_warning("Invalid fsck parameter. Ignoring.");
89 #ifdef HAVE_SYSV_COMPAT
90         else if (streq(key, "fastboot") && !value) {
91                 log_warning("Please pass 'fsck.mode=skip' rather than 'fastboot' on the kernel command line.");
92                 arg_skip = true;
93         } else if (streq(key, "forcefsck") && !value) {
94                 log_warning("Please pass 'fsck.mode=force' rather than 'forcefsck' on the kernel command line.");
95                 arg_force = true;
96         }
97 #endif
98
99         return 0;
100 }
101
102 static void test_files(void) {
103 #ifdef HAVE_SYSV_COMPAT
104         if (access("/fastboot", F_OK) >= 0) {
105                 log_error("Please pass 'fsck.mode=skip' on the kernel command line rather than creating /fastboot on the root file system.");
106                 arg_skip = true;
107         }
108
109         if (access("/forcefsck", F_OK) >= 0) {
110                 log_error("Please pass 'fsck.mode=force' on the kernel command line rather than creating /forcefsck on the root file system.");
111                 arg_force = true;
112         }
113 #endif
114
115         if (access("/run/systemd/show-status", F_OK) >= 0 || plymouth_running())
116                 arg_show_progress = true;
117 }
118
119 static double percent(int pass, unsigned long cur, unsigned long max) {
120         /* Values stolen from e2fsck */
121
122         static const int pass_table[] = {
123                 0, 70, 90, 92, 95, 100
124         };
125
126         if (pass <= 0)
127                 return 0.0;
128
129         if ((unsigned) pass >= ELEMENTSOF(pass_table) || max == 0)
130                 return 100.0;
131
132         return (double) pass_table[pass-1] +
133                 ((double) pass_table[pass] - (double) pass_table[pass-1]) *
134                 (double) cur / (double) max;
135 }
136
137 static int process_progress(int fd) {
138         _cleanup_fclose_ FILE *console = NULL, *f = NULL;
139         usec_t last = 0;
140         bool locked = false;
141         int clear = 0;
142
143         f = fdopen(fd, "r");
144         if (!f) {
145                 safe_close(fd);
146                 return -errno;
147         }
148
149         console = fopen("/dev/console", "we");
150         if (!console)
151                 return -ENOMEM;
152
153         while (!feof(f)) {
154                 int pass, m;
155                 unsigned long cur, max;
156                 _cleanup_free_ char *device = NULL;
157                 double p;
158                 usec_t t;
159
160                 if (fscanf(f, "%i %lu %lu %ms", &pass, &cur, &max, &device) != 4)
161                         break;
162
163                 /* Only show one progress counter at max */
164                 if (!locked) {
165                         if (flock(fileno(console), LOCK_EX|LOCK_NB) < 0)
166                                 continue;
167
168                         locked = true;
169                 }
170
171                 /* Only update once every 50ms */
172                 t = now(CLOCK_MONOTONIC);
173                 if (last + 50 * USEC_PER_MSEC > t)
174                         continue;
175
176                 last = t;
177
178                 p = percent(pass, cur, max);
179                 fprintf(console, "\r%s: fsck %3.1f%% complete...\r%n", device, p, &m);
180                 fflush(console);
181
182                 if (m > clear)
183                         clear = m;
184         }
185
186         if (clear > 0) {
187                 unsigned j;
188
189                 fputc('\r', console);
190                 for (j = 0; j < (unsigned) clear; j++)
191                         fputc(' ', console);
192                 fputc('\r', console);
193                 fflush(console);
194         }
195
196         return 0;
197 }
198
199 int main(int argc, char *argv[]) {
200         const char *cmdline[9];
201         int i = 0, r = EXIT_FAILURE, q;
202         pid_t pid;
203         siginfo_t status;
204         _cleanup_udev_unref_ struct udev *udev = NULL;
205         _cleanup_udev_device_unref_ struct udev_device *udev_device = NULL;
206         const char *device, *type;
207         bool root_directory;
208         int progress_pipe[2] = { -1, -1 };
209         char dash_c[2+10+1];
210         struct stat st;
211
212         if (argc > 2) {
213                 log_error("This program expects one or no arguments.");
214                 return EXIT_FAILURE;
215         }
216
217         log_set_target(LOG_TARGET_AUTO);
218         log_parse_environment();
219         log_open();
220
221         umask(0022);
222
223         parse_proc_cmdline(parse_proc_cmdline_item);
224         test_files();
225
226         if (!arg_force && arg_skip)
227                 return 0;
228
229         udev = udev_new();
230         if (!udev) {
231                 log_oom();
232                 return EXIT_FAILURE;
233         }
234
235         if (argc > 1) {
236                 device = argv[1];
237                 root_directory = false;
238
239                 if (stat(device, &st) < 0) {
240                         log_error("Failed to stat '%s': %m", device);
241                         return EXIT_FAILURE;
242                 }
243
244                 udev_device = udev_device_new_from_devnum(udev, 'b', st.st_rdev);
245                 if (!udev_device) {
246                         log_error("Failed to detect device %s", device);
247                         return EXIT_FAILURE;
248                 }
249         } else {
250                 struct timespec times[2];
251
252                 /* Find root device */
253
254                 if (stat("/", &st) < 0) {
255                         log_error("Failed to stat() the root directory: %m");
256                         return EXIT_FAILURE;
257                 }
258
259                 /* Virtual root devices don't need an fsck */
260                 if (major(st.st_dev) == 0)
261                         return EXIT_SUCCESS;
262
263                 /* check if we are already writable */
264                 times[0] = st.st_atim;
265                 times[1] = st.st_mtim;
266                 if (utimensat(AT_FDCWD, "/", times, 0) == 0) {
267                         log_info("Root directory is writable, skipping check.");
268                         return EXIT_SUCCESS;
269                 }
270
271                 udev_device = udev_device_new_from_devnum(udev, 'b', st.st_dev);
272                 if (!udev_device) {
273                         log_error("Failed to detect root device.");
274                         return EXIT_FAILURE;
275                 }
276
277                 device = udev_device_get_devnode(udev_device);
278                 if (!device) {
279                         log_error("Failed to detect device node of root directory.");
280                         return EXIT_FAILURE;
281                 }
282
283                 root_directory = true;
284         }
285
286         type = udev_device_get_property_value(udev_device, "ID_FS_TYPE");
287         if (type) {
288                 const char *checker = strappenda("/sbin/fsck.", type);
289                 r = access(checker, X_OK);
290                 if (r < 0) {
291                         if (errno == ENOENT) {
292                                 log_info("%s doesn't exist, not checking file system.", checker);
293                                 return EXIT_SUCCESS;
294                         } else
295                                 log_warning("%s cannot be used: %m", checker);
296                 }
297         }
298
299         if (arg_show_progress)
300                 if (pipe(progress_pipe) < 0) {
301                         log_error("pipe(): %m");
302                         return EXIT_FAILURE;
303                 }
304
305         cmdline[i++] = "/sbin/fsck";
306         cmdline[i++] = "-a";
307         cmdline[i++] = "-T";
308         cmdline[i++] = "-l";
309
310         if (!root_directory)
311                 cmdline[i++] = "-M";
312
313         if (arg_force)
314                 cmdline[i++] = "-f";
315
316         if (progress_pipe[1] >= 0) {
317                 snprintf(dash_c, sizeof(dash_c), "-C%i", progress_pipe[1]);
318                 char_array_0(dash_c);
319                 cmdline[i++] = dash_c;
320         }
321
322         cmdline[i++] = device;
323         cmdline[i++] = NULL;
324
325         pid = fork();
326         if (pid < 0) {
327                 log_error("fork(): %m");
328                 goto finish;
329         } else if (pid == 0) {
330                 /* Child */
331                 if (progress_pipe[0] >= 0)
332                         safe_close(progress_pipe[0]);
333                 execv(cmdline[0], (char**) cmdline);
334                 _exit(8); /* Operational error */
335         }
336
337         progress_pipe[1] = safe_close(progress_pipe[1]);
338
339         if (progress_pipe[0] >= 0) {
340                 process_progress(progress_pipe[0]);
341                 progress_pipe[0] = -1;
342         }
343
344         q = wait_for_terminate(pid, &status);
345         if (q < 0) {
346                 log_error("waitid(): %s", strerror(-q));
347                 goto finish;
348         }
349
350         if (status.si_code != CLD_EXITED || (status.si_status & ~1)) {
351
352                 if (status.si_code == CLD_KILLED || status.si_code == CLD_DUMPED)
353                         log_error("fsck terminated by signal %s.", signal_to_string(status.si_status));
354                 else if (status.si_code == CLD_EXITED)
355                         log_error("fsck failed with error code %i.", status.si_status);
356                 else
357                         log_error("fsck failed due to unknown reason.");
358
359                 if (status.si_code == CLD_EXITED && (status.si_status & 2) && root_directory)
360                         /* System should be rebooted. */
361                         start_target(SPECIAL_REBOOT_TARGET);
362                 else if (status.si_code == CLD_EXITED && (status.si_status & 6))
363                         /* Some other problem */
364                         start_target(SPECIAL_EMERGENCY_TARGET);
365                 else {
366                         r = EXIT_SUCCESS;
367                         log_warning("Ignoring error.");
368                 }
369
370         } else
371                 r = EXIT_SUCCESS;
372
373         if (status.si_code == CLD_EXITED && (status.si_status & 1))
374                 touch("/run/systemd/quotacheck");
375
376 finish:
377         close_pipe(progress_pipe);
378
379         return r;
380 }