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