]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/fsck.c
relicense to LGPLv2.1 (with exceptions)
[thirdparty/systemd.git] / src / 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 }