]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/fsck/fsck.c
Introduce udev object cleanup functions
[thirdparty/systemd.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 }