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