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