chiark / gitweb /
fsck: use _cleanup_close_pair_ where appropriate
[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 "libudev.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 "udev-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 %lu %lu %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_udev_unref_ struct udev *udev = NULL;
203         _cleanup_udev_device_unref_ struct udev_device *udev_device = NULL;
204         const char *device, *type;
205         bool root_directory;
206         _cleanup_close_pair_ int progress_pipe[2] = { -1, -1 };
207         char dash_c[sizeof("-C")-1 + DECIMAL_STR_MAX(int) + 1];
208         struct stat st;
209
210         if (argc > 2) {
211                 log_error("This program expects one or no arguments.");
212                 return EXIT_FAILURE;
213         }
214
215         log_set_target(LOG_TARGET_AUTO);
216         log_parse_environment();
217         log_open();
218
219         umask(0022);
220
221         q = parse_proc_cmdline(parse_proc_cmdline_item);
222         if (q < 0)
223                 log_warning_errno(q, "Failed to parse kernel command line, ignoring: %m");
224
225         test_files();
226
227         if (!arg_force && arg_skip)
228                 return 0;
229
230         udev = udev_new();
231         if (!udev) {
232                 log_oom();
233                 return EXIT_FAILURE;
234         }
235
236         if (argc > 1) {
237                 device = argv[1];
238                 root_directory = false;
239
240                 if (stat(device, &st) < 0) {
241                         log_error_errno(errno, "Failed to stat '%s': %m", device);
242                         return EXIT_FAILURE;
243                 }
244
245                 udev_device = udev_device_new_from_devnum(udev, 'b', st.st_rdev);
246                 if (!udev_device) {
247                         log_error("Failed to detect device %s", device);
248                         return EXIT_FAILURE;
249                 }
250         } else {
251                 struct timespec times[2];
252
253                 /* Find root device */
254
255                 if (stat("/", &st) < 0) {
256                         log_error_errno(errno, "Failed to stat() the root directory: %m");
257                         return EXIT_FAILURE;
258                 }
259
260                 /* Virtual root devices don't need an fsck */
261                 if (major(st.st_dev) == 0)
262                         return EXIT_SUCCESS;
263
264                 /* check if we are already writable */
265                 times[0] = st.st_atim;
266                 times[1] = st.st_mtim;
267                 if (utimensat(AT_FDCWD, "/", times, 0) == 0) {
268                         log_info("Root directory is writable, skipping check.");
269                         return EXIT_SUCCESS;
270                 }
271
272                 udev_device = udev_device_new_from_devnum(udev, 'b', st.st_dev);
273                 if (!udev_device) {
274                         log_error("Failed to detect root device.");
275                         return EXIT_FAILURE;
276                 }
277
278                 device = udev_device_get_devnode(udev_device);
279                 if (!device) {
280                         log_error("Failed to detect device node of root directory.");
281                         return EXIT_FAILURE;
282                 }
283
284                 root_directory = true;
285         }
286
287         type = udev_device_get_property_value(udev_device, "ID_FS_TYPE");
288         if (type) {
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                         return EXIT_SUCCESS;
293                 } else if (r < 0)
294                         log_warning_errno(r, "fsck.%s cannot be used for %s: %m", type, device);
295         }
296
297         if (pipe(progress_pipe) < 0) {
298                 log_error_errno(errno, "pipe(): %m");
299                 return EXIT_FAILURE;
300         }
301
302         cmdline[i++] = "/sbin/fsck";
303         cmdline[i++] =  arg_repair;
304         cmdline[i++] = "-T";
305
306         /*
307          * Since util-linux v2.25 fsck uses /run/fsck/<diskname>.lock files.
308          * The previous versions use flock for the device and conflict with
309          * udevd, see https://bugs.freedesktop.org/show_bug.cgi?id=79576#c5
310          */
311         cmdline[i++] = "-l";
312
313         if (!root_directory)
314                 cmdline[i++] = "-M";
315
316         if (arg_force)
317                 cmdline[i++] = "-f";
318
319         xsprintf(dash_c, "-C%i", progress_pipe[1]);
320         cmdline[i++] = dash_c;
321
322         cmdline[i++] = device;
323         cmdline[i++] = NULL;
324
325         pid = fork();
326         if (pid < 0) {
327                 log_error_errno(errno, "fork(): %m");
328                 goto finish;
329         } else if (pid == 0) {
330                 /* Child */
331                 progress_pipe[0] = safe_close(progress_pipe[0]);
332                 execv(cmdline[0], (char**) cmdline);
333                 _exit(8); /* Operational error */
334         }
335
336         progress_pipe[1] = safe_close(progress_pipe[1]);
337
338         progress_rc = process_progress(progress_pipe[0], pid, st.st_rdev);
339         progress_pipe[0] = -1;
340
341         q = wait_for_terminate(pid, &status);
342         if (q < 0) {
343                 log_error_errno(q, "waitid(): %m");
344                 goto finish;
345         }
346
347         if (status.si_code != CLD_EXITED || (status.si_status & ~1) || progress_rc != 0) {
348
349                 /* cancel will kill fsck (but process_progress returns 0) */
350                 if ((progress_rc != 0 && status.si_code == CLD_KILLED) || status.si_code == CLD_DUMPED)
351                         log_error("fsck terminated by signal %s.", signal_to_string(status.si_status));
352                 else if (status.si_code == CLD_EXITED)
353                         log_error("fsck failed with error code %i.", status.si_status);
354                 else if (progress_rc != 0)
355                         log_error("fsck failed due to unknown reason.");
356
357                 if (status.si_code == CLD_EXITED && (status.si_status & 2) && root_directory)
358                         /* System should be rebooted. */
359                         start_target(SPECIAL_REBOOT_TARGET);
360                 else if (status.si_code == CLD_EXITED && (status.si_status & 6))
361                         /* Some other problem */
362                         start_target(SPECIAL_EMERGENCY_TARGET);
363                 else {
364                         r = EXIT_SUCCESS;
365                         if (progress_rc != 0)
366                                 log_warning("Ignoring error.");
367                 }
368
369         } else
370                 r = EXIT_SUCCESS;
371
372         if (status.si_code == CLD_EXITED && (status.si_status & 1))
373                 touch("/run/systemd/quotacheck");
374
375 finish:
376         return r;
377 }