chiark / gitweb /
c2e1c741d571382ba7f593986cd1bb0e57a96161
[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(void) {
76         char *line, *w, *state;
77         size_t l;
78         int r;
79
80         r = proc_cmdline(&line);
81         if (r < 0)
82                 log_warning("Failed to read /proc/cmdline, ignoring: %s", strerror(-r));
83         if (r <= 0)
84                 return 0;
85
86         FOREACH_WORD_QUOTED(w, l, line, state) {
87
88                 if (strneq(w, "fsck.mode=auto", l))
89                         arg_force = arg_skip = false;
90                 else if (strneq(w, "fsck.mode=force", l))
91                         arg_force = true;
92                 else if (strneq(w, "fsck.mode=skip", l))
93                         arg_skip = true;
94                 else if (startswith(w, "fsck"))
95                         log_warning("Invalid fsck parameter. Ignoring.");
96 #ifdef HAVE_SYSV_COMPAT
97                 else if (strneq(w, "fastboot", l)) {
98                         log_error("Please pass 'fsck.mode=skip' rather than 'fastboot' on the kernel command line.");
99                         arg_skip = true;
100                 } else if (strneq(w, "forcefsck", l)) {
101                         log_error("Please pass 'fsck.mode=force' rather than 'forcefsck' on the kernel command line.");
102                         arg_force = true;
103                 }
104 #endif
105         }
106
107         free(line);
108         return 0;
109 }
110
111 static void test_files(void) {
112 #ifdef HAVE_SYSV_COMPAT
113         if (access("/fastboot", F_OK) >= 0) {
114                 log_error("Please pass 'fsck.mode=skip' on the kernel command line rather than creating /fastboot on the root file system.");
115                 arg_skip = true;
116         }
117
118         if (access("/forcefsck", F_OK) >= 0) {
119                 log_error("Please pass 'fsck.mode=force' on the kernel command line rather than creating /forcefsck on the root file system.");
120                 arg_force = true;
121         }
122 #endif
123
124         if (access("/run/systemd/show-status", F_OK) >= 0 || plymouth_running())
125                 arg_show_progress = true;
126 }
127
128 static double percent(int pass, unsigned long cur, unsigned long max) {
129         /* Values stolen from e2fsck */
130
131         static const int pass_table[] = {
132                 0, 70, 90, 92, 95, 100
133         };
134
135         if (pass <= 0)
136                 return 0.0;
137
138         if ((unsigned) pass >= ELEMENTSOF(pass_table) || max == 0)
139                 return 100.0;
140
141         return (double) pass_table[pass-1] +
142                 ((double) pass_table[pass] - (double) pass_table[pass-1]) *
143                 (double) cur / (double) max;
144 }
145
146 static int process_progress(int fd) {
147         _cleanup_fclose_ FILE *console = NULL, *f = NULL;
148         usec_t last = 0;
149         bool locked = false;
150         int clear = 0;
151
152         f = fdopen(fd, "r");
153         if (!f) {
154                 close_nointr_nofail(fd);
155                 return -errno;
156         }
157
158         console = fopen("/dev/console", "w");
159         if (!console)
160                 return -ENOMEM;
161
162         while (!feof(f)) {
163                 int pass, m;
164                 unsigned long cur, max;
165                 _cleanup_free_ char *device = NULL;
166                 double p;
167                 usec_t t;
168
169                 if (fscanf(f, "%i %lu %lu %ms", &pass, &cur, &max, &device) != 4)
170                         break;
171
172                 /* Only show one progress counter at max */
173                 if (!locked) {
174                         if (flock(fileno(console), LOCK_EX|LOCK_NB) < 0)
175                                 continue;
176
177                         locked = true;
178                 }
179
180                 /* Only update once every 50ms */
181                 t = now(CLOCK_MONOTONIC);
182                 if (last + 50 * USEC_PER_MSEC > t)
183                         continue;
184
185                 last = t;
186
187                 p = percent(pass, cur, max);
188                 fprintf(console, "\r%s: fsck %3.1f%% complete...\r%n", device, p, &m);
189                 fflush(console);
190
191                 if (m > clear)
192                         clear = m;
193         }
194
195         if (clear > 0) {
196                 unsigned j;
197
198                 fputc('\r', console);
199                 for (j = 0; j < (unsigned) clear; j++)
200                         fputc(' ', console);
201                 fputc('\r', console);
202                 fflush(console);
203         }
204
205         return 0;
206 }
207
208 int main(int argc, char *argv[]) {
209         const char *cmdline[9];
210         int i = 0, r = EXIT_FAILURE, q;
211         pid_t pid;
212         siginfo_t status;
213         _cleanup_udev_unref_ struct udev *udev = NULL;
214         _cleanup_udev_device_unref_ struct udev_device *udev_device = NULL;
215         const char *device;
216         bool root_directory;
217         int progress_pipe[2] = { -1, -1 };
218         char dash_c[2+10+1];
219
220         if (argc > 2) {
221                 log_error("This program expects one or no arguments.");
222                 return EXIT_FAILURE;
223         }
224
225         log_set_target(LOG_TARGET_AUTO);
226         log_parse_environment();
227         log_open();
228
229         umask(0022);
230
231         parse_proc_cmdline();
232         test_files();
233
234         if (!arg_force && arg_skip)
235                 return 0;
236
237         if (argc > 1) {
238                 device = argv[1];
239                 root_directory = false;
240         } else {
241                 struct stat st;
242                 struct timespec times[2];
243
244                 /* Find root device */
245
246                 if (stat("/", &st) < 0) {
247                         log_error("Failed to stat() the root directory: %m");
248                         return EXIT_FAILURE;
249                 }
250
251                 /* Virtual root devices don't need an fsck */
252                 if (major(st.st_dev) == 0)
253                         return EXIT_SUCCESS;
254
255                 /* check if we are already writable */
256                 times[0] = st.st_atim;
257                 times[1] = st.st_mtim;
258                 if (utimensat(AT_FDCWD, "/", times, 0) == 0) {
259                         log_info("Root directory is writable, skipping check.");
260                         return EXIT_SUCCESS;
261                 }
262
263                 udev = udev_new();
264                 if (!udev) {
265                         log_oom();
266                         return EXIT_FAILURE;
267                 }
268
269                 udev_device = udev_device_new_from_devnum(udev, 'b', st.st_dev);
270                 if (!udev_device) {
271                         log_error("Failed to detect root device.");
272                         return EXIT_FAILURE;
273                 }
274
275                 device = udev_device_get_devnode(udev_device);
276                 if (!device) {
277                         log_error("Failed to detect device node of root directory.");
278                         return EXIT_FAILURE;
279                 }
280
281                 root_directory = true;
282         }
283
284         if (arg_show_progress)
285                 if (pipe(progress_pipe) < 0) {
286                         log_error("pipe(): %m");
287                         return EXIT_FAILURE;
288                 }
289
290         cmdline[i++] = "/sbin/fsck";
291         cmdline[i++] = "-a";
292         cmdline[i++] = "-T";
293         cmdline[i++] = "-l";
294
295         if (!root_directory)
296                 cmdline[i++] = "-M";
297
298         if (arg_force)
299                 cmdline[i++] = "-f";
300
301         if (progress_pipe[1] >= 0) {
302                 snprintf(dash_c, sizeof(dash_c), "-C%i", progress_pipe[1]);
303                 char_array_0(dash_c);
304                 cmdline[i++] = dash_c;
305         }
306
307         cmdline[i++] = device;
308         cmdline[i++] = NULL;
309
310         pid = fork();
311         if (pid < 0) {
312                 log_error("fork(): %m");
313                 goto finish;
314         } else if (pid == 0) {
315                 /* Child */
316                 if (progress_pipe[0] >= 0)
317                         close_nointr_nofail(progress_pipe[0]);
318                 execv(cmdline[0], (char**) cmdline);
319                 _exit(8); /* Operational error */
320         }
321
322         if (progress_pipe[1] >= 0) {
323                 close_nointr_nofail(progress_pipe[1]);
324                 progress_pipe[1] = -1;
325         }
326
327         if (progress_pipe[0] >= 0) {
328                 process_progress(progress_pipe[0]);
329                 progress_pipe[0] = -1;
330         }
331
332         q = wait_for_terminate(pid, &status);
333         if (q < 0) {
334                 log_error("waitid(): %s", strerror(-q));
335                 goto finish;
336         }
337
338         if (status.si_code != CLD_EXITED || (status.si_status & ~1)) {
339
340                 if (status.si_code == CLD_KILLED || status.si_code == CLD_DUMPED)
341                         log_error("fsck terminated by signal %s.", signal_to_string(status.si_status));
342                 else if (status.si_code == CLD_EXITED)
343                         log_error("fsck failed with error code %i.", status.si_status);
344                 else
345                         log_error("fsck failed due to unknown reason.");
346
347                 if (status.si_code == CLD_EXITED && (status.si_status & 2) && root_directory)
348                         /* System should be rebooted. */
349                         start_target(SPECIAL_REBOOT_TARGET);
350                 else if (status.si_code == CLD_EXITED && (status.si_status & 6))
351                         /* Some other problem */
352                         start_target(SPECIAL_EMERGENCY_TARGET);
353                 else {
354                         r = EXIT_SUCCESS;
355                         log_warning("Ignoring error.");
356                 }
357
358         } else
359                 r = EXIT_SUCCESS;
360
361         if (status.si_code == CLD_EXITED && (status.si_status & 1))
362                 touch("/run/systemd/quotacheck");
363
364 finish:
365         close_pipe(progress_pipe);
366
367         return r;
368 }