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