chiark / gitweb /
fsck: port from libudev to sd-device
[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   Copyright 2014 Holger Hans Peter Freyther
8
9   systemd is free software; you can redistribute it and/or modify it
10   under the terms of the GNU Lesser General Public License as published by
11   the Free Software Foundation; either version 2.1 of the License, or
12   (at your option) any later version.
13
14   systemd is distributed in the hope that it will be useful, but
15   WITHOUT ANY WARRANTY; without even the implied warranty of
16   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17   Lesser General Public License for more details.
18
19   You should have received a copy of the GNU Lesser General Public License
20   along with systemd; If not, see <http://www.gnu.org/licenses/>.
21 ***/
22
23 #include <stdio.h>
24 #include <stdbool.h>
25 #include <errno.h>
26 #include <unistd.h>
27 #include <fcntl.h>
28 #include <sys/file.h>
29 #include <sys/stat.h>
30
31 #include "sd-bus.h"
32 #include "sd-device.h"
33
34 #include "util.h"
35 #include "special.h"
36 #include "bus-util.h"
37 #include "bus-error.h"
38 #include "bus-common-errors.h"
39 #include "device-util.h"
40 #include "path-util.h"
41 #include "socket-util.h"
42 #include "fsckd/fsckd.h"
43
44 static bool arg_skip = false;
45 static bool arg_force = false;
46 static const char *arg_repair = "-a";
47
48 static void start_target(const char *target) {
49         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
50         _cleanup_bus_close_unref_ sd_bus *bus = NULL;
51         int r;
52
53         assert(target);
54
55         r = bus_open_system_systemd(&bus);
56         if (r < 0) {
57                 log_error_errno(r, "Failed to get D-Bus connection: %m");
58                 return;
59         }
60
61         log_info("Running request %s/start/replace", target);
62
63         /* Start these units only if we can replace base.target with it */
64         r = sd_bus_call_method(bus,
65                                "org.freedesktop.systemd1",
66                                "/org/freedesktop/systemd1",
67                                "org.freedesktop.systemd1.Manager",
68                                "StartUnitReplace",
69                                &error,
70                                NULL,
71                                "sss", "basic.target", target, "replace");
72
73         /* Don't print a warning if we aren't called during startup */
74         if (r < 0 && !sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_JOB))
75                 log_error("Failed to start unit: %s", bus_error_message(&error, -r));
76 }
77
78 static int parse_proc_cmdline_item(const char *key, const char *value) {
79
80         if (streq(key, "fsck.mode") && value) {
81
82                 if (streq(value, "auto"))
83                         arg_force = arg_skip = false;
84                 else if (streq(value, "force"))
85                         arg_force = true;
86                 else if (streq(value, "skip"))
87                         arg_skip = true;
88                 else
89                         log_warning("Invalid fsck.mode= parameter '%s'. Ignoring.", value);
90
91         } else if (streq(key, "fsck.repair") && value) {
92
93                 if (streq(value, "preen"))
94                         arg_repair = "-a";
95                 else if (streq(value, "yes"))
96                         arg_repair = "-y";
97                 else if (streq(value, "no"))
98                         arg_repair = "-n";
99                 else
100                         log_warning("Invalid fsck.repair= parameter '%s'. Ignoring.", value);
101         }
102
103 #ifdef HAVE_SYSV_COMPAT
104         else if (streq(key, "fastboot") && !value) {
105                 log_warning("Please pass 'fsck.mode=skip' rather than 'fastboot' on the kernel command line.");
106                 arg_skip = true;
107
108         } else if (streq(key, "forcefsck") && !value) {
109                 log_warning("Please pass 'fsck.mode=force' rather than 'forcefsck' on the kernel command line.");
110                 arg_force = true;
111         }
112 #endif
113
114         return 0;
115 }
116
117 static void test_files(void) {
118
119 #ifdef HAVE_SYSV_COMPAT
120         if (access("/fastboot", F_OK) >= 0) {
121                 log_error("Please pass 'fsck.mode=skip' on the kernel command line rather than creating /fastboot on the root file system.");
122                 arg_skip = true;
123         }
124
125         if (access("/forcefsck", F_OK) >= 0) {
126                 log_error("Please pass 'fsck.mode=force' on the kernel command line rather than creating /forcefsck on the root file system.");
127                 arg_force = true;
128         }
129 #endif
130
131 }
132
133 static int process_progress(int fd, pid_t fsck_pid, dev_t device_num) {
134         _cleanup_fclose_ FILE *f = NULL;
135         usec_t last = 0;
136         _cleanup_close_ int fsckd_fd = -1;
137         static const union sockaddr_union sa = {
138                 .un.sun_family = AF_UNIX,
139                 .un.sun_path = FSCKD_SOCKET_PATH,
140         };
141
142         fsckd_fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
143         if (fsckd_fd < 0)
144                 return log_warning_errno(errno, "Cannot open fsckd socket, we won't report fsck progress: %m");
145         if (connect(fsckd_fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sa.un.sun_path)) < 0)
146                 return log_warning_errno(errno, "Cannot connect to fsckd socket, we won't report fsck progress: %m");
147
148         f = fdopen(fd, "r");
149         if (!f)
150                 return log_warning_errno(errno, "Cannot connect to fsck, we won't report fsck progress: %m");
151
152         while (!feof(f)) {
153                 int pass;
154                 size_t buflen;
155                 size_t cur, max;
156                 ssize_t r;
157                 usec_t t;
158                 _cleanup_free_ char *device = NULL;
159                 FsckProgress progress;
160                 FsckdMessage fsckd_message;
161
162                 if (fscanf(f, "%i %zu %zu %ms", &pass, &cur, &max, &device) != 4)
163                         break;
164
165                 /* Only update once every 50ms */
166                 t = now(CLOCK_MONOTONIC);
167                 if (last + 50 * USEC_PER_MSEC > t)
168                         continue;
169
170                 last = t;
171
172                 /* send progress to fsckd */
173                 progress.devnum = device_num;
174                 progress.cur = cur;
175                 progress.max = max;
176                 progress.pass = pass;
177
178                 r = send(fsckd_fd, &progress, sizeof(FsckProgress), 0);
179                 if (r < 0 || (size_t) r < sizeof(FsckProgress))
180                         log_warning_errno(errno, "Cannot communicate fsck progress to fsckd: %m");
181
182                 /* get fsckd requests, only read when we have coherent size data */
183                 r = ioctl(fsckd_fd, FIONREAD, &buflen);
184                 if (r == 0 && (size_t) buflen >= sizeof(FsckdMessage)) {
185                         r = recv(fsckd_fd, &fsckd_message, sizeof(FsckdMessage), 0);
186                         if (r > 0 && fsckd_message.cancel == 1) {
187                                 log_info("Request to cancel fsck from fsckd");
188                                 kill(fsck_pid, SIGTERM);
189                         }
190                 }
191         }
192
193         return 0;
194 }
195
196 int main(int argc, char *argv[]) {
197         const char *cmdline[9];
198         int i = 0, r = EXIT_FAILURE, q;
199         pid_t pid;
200         int progress_rc;
201         siginfo_t status;
202         _cleanup_device_unref_ sd_device *dev = NULL;
203         const char *device, *type;
204         bool root_directory;
205         _cleanup_close_pair_ int progress_pipe[2] = { -1, -1 };
206         char dash_c[sizeof("-C")-1 + DECIMAL_STR_MAX(int) + 1];
207         struct stat st;
208
209         if (argc > 2) {
210                 log_error("This program expects one or no arguments.");
211                 return EXIT_FAILURE;
212         }
213
214         log_set_target(LOG_TARGET_AUTO);
215         log_parse_environment();
216         log_open();
217
218         umask(0022);
219
220         q = parse_proc_cmdline(parse_proc_cmdline_item);
221         if (q < 0)
222                 log_warning_errno(q, "Failed to parse kernel command line, ignoring: %m");
223
224         test_files();
225
226         if (!arg_force && arg_skip) {
227                 r = 0;
228                 goto finish;
229         }
230
231         if (argc > 1) {
232                 device = argv[1];
233                 root_directory = false;
234
235                 if (stat(device, &st) < 0) {
236                         r = log_error_errno(errno, "Failed to stat '%s': %m", device);
237                         goto finish;
238                 }
239
240                 r = sd_device_new_from_devnum(&dev, 'b', st.st_rdev);
241                 if (r < 0) {
242                         log_error_errno(r, "Failed to detect device %s: %m", device);
243                         goto finish;
244                 }
245         } else {
246                 struct timespec times[2];
247
248                 /* Find root device */
249
250                 if (stat("/", &st) < 0) {
251                         r = log_error_errno(errno, "Failed to stat() the root directory: %m");
252                         goto finish;
253                 }
254
255                 /* Virtual root devices don't need an fsck */
256                 if (major(st.st_dev) == 0) {
257                         log_debug("Root directory is virtual, skipping check.");
258                         r = 0;
259                         goto finish;
260                 }
261
262                 /* check if we are already writable */
263                 times[0] = st.st_atim;
264                 times[1] = st.st_mtim;
265                 if (utimensat(AT_FDCWD, "/", times, 0) == 0) {
266                         log_info("Root directory is writable, skipping check.");
267                         r = 0;
268                         goto finish;
269                 }
270
271                 r = sd_device_new_from_devnum(&dev, 'b', st.st_dev);
272                 if (r < 0) {
273                         log_error_errno(r, "Failed to detect root device: %m");
274                         goto finish;
275                 }
276
277                 r = sd_device_get_devname(dev, &device);
278                 if (r < 0) {
279                         log_error_errno(r, "Failed to detect device node of root directory: %m");
280                         r = -ENXIO;
281                         goto finish;
282                 }
283
284                 root_directory = true;
285         }
286
287         r = sd_device_get_property_value(dev, "ID_FS_TYPE", &type);
288         if (r >= 0) {
289                 r = fsck_exists(type);
290                 if (r == -ENOENT) {
291                         log_info("fsck.%s doesn't exist, not checking file system on %s", type, device);
292                         r = 0;
293                         goto finish;
294                 } else if (r < 0)
295                         log_warning_errno(r, "fsck.%s cannot be used for %s: %m", type, device);
296         }
297
298         if (pipe(progress_pipe) < 0) {
299                 r = log_error_errno(errno, "pipe(): %m");
300                 goto finish;
301         }
302
303         cmdline[i++] = "/sbin/fsck";
304         cmdline[i++] =  arg_repair;
305         cmdline[i++] = "-T";
306
307         /*
308          * Since util-linux v2.25 fsck uses /run/fsck/<diskname>.lock files.
309          * The previous versions use flock for the device and conflict with
310          * udevd, see https://bugs.freedesktop.org/show_bug.cgi?id=79576#c5
311          */
312         cmdline[i++] = "-l";
313
314         if (!root_directory)
315                 cmdline[i++] = "-M";
316
317         if (arg_force)
318                 cmdline[i++] = "-f";
319
320         xsprintf(dash_c, "-C%i", progress_pipe[1]);
321         cmdline[i++] = dash_c;
322
323         cmdline[i++] = device;
324         cmdline[i++] = NULL;
325
326         pid = fork();
327         if (pid < 0) {
328                 r = log_error_errno(errno, "fork(): %m");
329                 goto finish;
330         } else if (pid == 0) {
331                 /* Child */
332                 progress_pipe[0] = safe_close(progress_pipe[0]);
333                 execv(cmdline[0], (char**) cmdline);
334                 _exit(8); /* Operational error */
335         }
336
337         progress_pipe[1] = safe_close(progress_pipe[1]);
338
339         progress_rc = process_progress(progress_pipe[0], pid, st.st_rdev);
340         progress_pipe[0] = -1;
341
342         r = wait_for_terminate(pid, &status);
343         if (r < 0) {
344                 log_error_errno(r, "waitid(): %m");
345                 goto finish;
346         }
347
348         if (status.si_code != CLD_EXITED || (status.si_status & ~1) || progress_rc != 0) {
349
350                 /* cancel will kill fsck (but process_progress returns 0) */
351                 if ((progress_rc != 0 && status.si_code == CLD_KILLED) || status.si_code == CLD_DUMPED)
352                         log_error("fsck terminated by signal %s.", signal_to_string(status.si_status));
353                 else if (status.si_code == CLD_EXITED)
354                         log_error("fsck failed with error code %i.", status.si_status);
355                 else if (progress_rc != 0)
356                         log_error("fsck failed due to unknown reason.");
357
358                 r = -EINVAL;
359
360                 if (status.si_code == CLD_EXITED && (status.si_status & 2) && root_directory)
361                         /* System should be rebooted. */
362                         start_target(SPECIAL_REBOOT_TARGET);
363                 else if (status.si_code == CLD_EXITED && (status.si_status & 6))
364                         /* Some other problem */
365                         start_target(SPECIAL_EMERGENCY_TARGET);
366                 else {
367                         r = 0;
368                         if (progress_rc != 0)
369                                 log_warning("Ignoring error.");
370                 }
371
372         } else
373                 r = 0;
374
375         if (status.si_code == CLD_EXITED && (status.si_status & 1))
376                 touch("/run/systemd/quotacheck");
377
378 finish:
379         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
380 }