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