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