diff options
Diffstat (limited to 'sutils/fsck.c')
-rw-r--r-- | sutils/fsck.c | 568 |
1 files changed, 568 insertions, 0 deletions
diff --git a/sutils/fsck.c b/sutils/fsck.c new file mode 100644 index 00000000..424e3f17 --- /dev/null +++ b/sutils/fsck.c @@ -0,0 +1,568 @@ +/* Hurd-aware fsck wrapper + + Copyright (C) 1996, 97, 98, 99 Free Software Foundation, Inc. + + Written by Miles Bader <miles@gnu.org> + + This file is part of the GNU Hurd. + + The GNU Hurd is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2, or (at + your option) any later version. + + The GNU Hurd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ + +/* This wrapper runs other file-system specific fsck programs. They are + expected to accept at least the following options: + + -p Terse automatic mode + -y Automatically answer yes to all questions + -n Automatically answer no to all questions + -f Check even if clean + -s Only print diagostic messages + + They should also return exit-status codes as following: + + 0 Filesystem was clean + 1,2 Filesystem fixed (and is now clean) + 4,8 Filesystem was broken, but couldn't be fixed + ... Anything else is assumed be some horrible error + + The exit-status from this wrapper is the greatest status returned from any + individual fsck. + + Although it knows something about the hurd, this fsck still uses + /etc/fstab, and is generally not very integrated. That will have to wait + until the appropiate mechanisms for doing so are decided. */ + +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/wait.h> +#include <error.h> +#include <argp.h> +#include <argz.h> +#include <assert.h> +#include <version.h> + +#include "fstab.h" + +const char *argp_program_version = STANDARD_HURD_VERSION (fsck); + + +/* for debugging */ +static int _debug = 0; +#define debug(fmt, args...) \ + do { if (_debug) { \ + fprintf (stderr, "[%s: ", __FUNCTION__); \ + fprintf (stderr, fmt , ##args); \ + fprintf (stderr, "]\n"); } } while (0) +#define fs_debug(fs, fmt, args...) \ + debug ("%s: " fmt, (fs)->mntent.mnt_dir , ##args) + +#define FSCK_SEARCH_FMTS "/sbin/fsck.%s" + +/* Exit codes we return. */ +#define FSCK_EX_OK 0 /* No errors */ +#define FSCK_EX_FIXED 1 /* File system errors corrected */ +#define FSCK_EX_BROKEN 4 /* File system errors left uncorrected */ +#define FSCK_EX_QUIT 12 /* Got SIGQUIT */ +#define FSCK_EX_SIGNAL 20 /* Signalled (not SIGQUIT) */ +#define FSCK_EX_ERROR 50 +#define FSCK_EX_EXEC 99 /* Exec failed */ +/* Everything else is some sort of fsck problem. */ + +/* Things we know about what child fsck's might return. */ +#define FSCK_EX_IS_FIXED(st) ({ int _st = (st); _st >= 1 || _st <= 2; }) +#define FSCK_EX_IS_BROKEN(st) ({ int _st = (st); _st >= 4 || _st <= 8; }) + +/* Common fsck flags. */ +#define FSCK_F_PREEN 0x1 +#define FSCK_F_YES 0x2 +#define FSCK_F_NO 0x4 +#define FSCK_F_FORCE 0x8 +#define FSCK_F_SILENT 0x10 + +/* The following are only used internally. */ +#define FSCK_F_VERBOSE 0x100 +#define FSCK_F_WRITABLE 0x200 /* Make writable after fscking. */ +#define FSCK_F_AUTO 0x400 /* Do all filesystems in fstab. */ +#define FSCK_F_DRYRUN 0x800 /* Don't actually do anything. */ + +static int got_sigquit = 0, got_sigint = 0; + +static void sigquit () +{ + got_sigquit = 1; +} + +static void sigint () +{ + got_sigint = 1; +} + +struct fsck +{ + struct fs *fs; /* Filesystem being fscked. */ + int pid; /* Pid for process. */ + int make_writable; /* Make writable after fscking if possible. */ + struct fsck *next, **self; +}; + +struct fscks +{ + struct fsck *running; /* Fsck processes now running. */ + int free_slots; /* Number of fsck processes we can start. */ + int flags; +}; + +/* Starts FS's fsck program on FS's device, returning the pid of the process. + If an error is encountered, prints an error message and returns 0. + Filesystems that need not be fscked at all also return 0 (but don't print + an error message). */ +static pid_t +fs_start_fsck (struct fs *fs, int flags) +{ + pid_t pid; + char flags_buf[10]; + char *argv[4], **argp = argv; + struct fstype *type; + error_t err = fs_type (fs, &type); + + assert_perror (err); /* Should already have been checked for. */ + assert (type->program); + + *argp++ = type->program; + + if (flags & (FSCK_F_PREEN|FSCK_F_YES|FSCK_F_NO|FSCK_F_FORCE|FSCK_F_SILENT)) + { + char *p = flags_buf; + *argp++ = flags_buf; + *p++ = '-'; + if (flags & FSCK_F_PREEN) *p++ = 'p'; + if (flags & FSCK_F_YES) *p++ = 'y'; + if (flags & FSCK_F_NO) *p++ = 'n'; + if (flags & FSCK_F_FORCE) *p++ = 'f'; + if (flags & FSCK_F_SILENT) *p++ = 's'; + *p = '\0'; + } + + *argp++ = fs->mntent.mnt_fsname; + *argp = 0; + + if (flags & FSCK_F_DRYRUN) + { + char *argz; + size_t argz_len; + argz_create (argv, &argz, &argz_len); + argz_stringify (argz, argz_len, ' '); + puts (argz); + free (argz); + return 0; + } + + pid = fork (); + if (pid < 0) + { + error (0, errno, "fork"); + return 0; + } + + if (pid == 0) + /* Child. */ + { + execv (type->program, argv); + exit (FSCK_EX_EXEC); /* Exec failed. */ + } + + if ((flags & FSCK_F_VERBOSE) || _debug) + { + char *argz; + size_t argz_len; + argz_create (argv, &argz, &argz_len); + argz_stringify (argz, argz_len, ' '); + fs_debug (fs, "Spawned pid %d: %s", pid, argz); + if (flags & FSCK_F_VERBOSE) + puts (argz); + free (argz); + } + + return pid; +} + +/* Start a fsck process for FS running, and add an entry for it to FSCKS. + This also ensures that if FS is currently mounted, it will be made + readonly first. If the fsck is successfully started, 0 is returned, + otherwise FSCK_EX_ERROR. */ +static int +fscks_start_fsck (struct fscks *fscks, struct fs *fs) +{ + error_t err; + int mounted, make_writable; + struct fsck *fsck; + + if (got_sigint) + /* We got SIGINT, so we pretend that all fscks got a signal without even + attempting to run them. */ + { + fs_debug (fs, "Forcing signal"); + return FSCK_EX_SIGNAL; + } + +#define CK(err, fmt, args...) \ + do { if (err) { error (0, err, fmt , ##args); return FSCK_EX_ERROR; } } while (0) + + fs_debug (fs, "Checking mounted state"); + err = fs_mounted (fs, &mounted); + CK (err, "%s: Cannot check mounted state", fs->mntent.mnt_dir); + + if (mounted) + { + int readonly; + + fs_debug (fs, "Checking readonly state"); + err = fs_readonly (fs, &readonly); + CK (err, "%s: Cannot check readonly state", fs->mntent.mnt_dir); + + if (fscks->flags & FSCK_F_DRYRUN) + { + if (! readonly) + { + printf ("%s: writable filesystem %s would be made read-only\n", + program_invocation_name, fs->mntent.mnt_dir); + readonly = 1; + } + } + + if (! readonly) + { + fs_debug (fs, "Making readonly"); + err = fs_set_readonly (fs, 1); + CK (err, "%s: Cannot make readonly", fs->mntent.mnt_dir); + } + + make_writable = !readonly + || ((fscks->flags & FSCK_F_WRITABLE) && hasmntopt (&fs->mntent, "rw")); + if (make_writable) + { + fs_debug (fs, "Will make writable after fscking if possible"); + make_writable = 1; + } + } + else + make_writable = 0; + +#undef CK + + /* Ok, any mounted filesystem is safely readonly. */ + + fsck = malloc (sizeof (struct fsck)); + if (! fsck) + { + error (0, ENOMEM, "malloc"); + return FSCK_EX_ERROR; + } + + fsck->fs = fs; + fsck->make_writable = make_writable; + fsck->next = fscks->running; + if (fsck->next) + fsck->next->self = &fsck->next; + fsck->self = &fscks->running; + fsck->pid = fs_start_fsck (fs, fscks->flags); + fscks->running = fsck; + + if (fsck->pid) + fscks->free_slots--; + + return 0; +} + +/* Cleanup after fscking with FSCK. If REMOUNT is true, ask the filesystem + to remount itself (to incorporate changes made by the fsck program). If + MAKE_WRITABLE is true, then if the filesystem should be made writable, do + so (after remounting if applicable). */ +static void +fsck_cleanup (struct fsck *fsck, int remount, int make_writable) +{ + error_t err = 0; + struct fs *fs = fsck->fs; + + /* Remove from chain. */ + *fsck->self = fsck->next; + if (fsck->next) + fsck->next->self = fsck->self; + + fs_debug (fs, "Cleaning up after fsck (remount = %d, make_writable = %d)", + remount, make_writable); + + if (fs->mounted > 0) + /* It's currently mounted; if the fsck modified the device, tell the + running filesystem to remount it. Also we may make it writable. */ + { + if (remount) + { + fs_debug (fs, "Remounting"); + err = fs_remount (fs); + if (err) + error (0, err, "%s: Cannot remount", fs->mntent.mnt_dir); + } + if (!err && make_writable && fsck->make_writable) + { + fs_debug (fs, "Making writable"); + err = fs_set_readonly (fs, 0); + if (err) + error (0, err, "%s: Cannot make writable", fs->mntent.mnt_dir); + } + } + + free (fsck); +} + +/* Wait for some fsck process to exit, cleaning up after it, and return its + exit-status. */ +static int +fscks_wait (struct fscks *fscks) +{ + pid_t pid; + int wstatus, status; + struct fsck *fsck, *next; + + /* Cleanup fscks that didn't even start. */ + for (fsck = fscks->running; fsck; fsck = next) + { + next = fsck->next; + if (fsck->pid == 0) + { + fs_debug (fsck->fs, "Pruning failed fsck"); + fsck_cleanup (fsck, 0, 1); + } + } + + debug ("Waiting..."); + + do + pid = wait (&wstatus); + while (pid < 0 && errno == EINTR); + + if (pid > 0) + { + if (WIFEXITED (wstatus)) + status = WEXITSTATUS (wstatus); + else if (WIFSIGNALED (wstatus)) + status = FSCK_EX_SIGNAL; + else + status = FSCK_EX_ERROR; + + for (fsck = fscks->running; fsck; fsck = fsck->next) + if (fsck->pid == pid) + { + int remount = (status != 0); + int make_writable = (status == 0 || FSCK_EX_IS_FIXED (status)); + fs_debug (fsck->fs, "Fsck finished (status = %d)", status); + fsck_cleanup (fsck, remount, make_writable); + fscks->free_slots++; + break; + } + if (! fsck) + error (0, 0, "%d: Unknown process exited", pid); + } + else if (errno == ECHILD) + /* There are apparently no child processes left, and we weren't told of + their demise. This can't happen. */ + { + while (fscks->running) + { + error (0, 0, "%s: Fsck process disappeared!", + fscks->running->fs->mntent.mnt_fsname); + /* Be pessimistic -- remount the filesystem, but leave it + readonly. */ + fsck_cleanup (fscks->running, 1, 0); + fscks->free_slots++; + } + status = FSCK_EX_ERROR; + } + else + status = FSCK_EX_ERROR; /* What happened? */ + + return status; +} + +/* Fsck all the filesystems in FSTAB, with the flags in FLAGS, doing at most + MAX_PARALLEL parallel fscks. The greatest exit code returned by any one + fsck is returned. */ +static int +fsck (struct fstab *fstab, int flags, int max_parallel) +{ + int pass; + struct fs *fs; + int autom = (flags & FSCK_F_AUTO); + int summary_status = 0; + struct fscks fscks = { running: 0, flags: flags }; + + void merge_status (int status) + { + if (status > summary_status) + summary_status = status; + } + + /* Do in pass order; pass 0 is never run, it is reserved for "off". */ + for (pass = 1; pass > 0; pass = fstab_next_pass (fstab, pass)) + /* Submit all filesystems in the given pass, up to MAX_PARALLEL at a + time. There should currently be no fscks running. */ + { + debug ("Pass %d", pass); + + fscks.free_slots = max_parallel; + + /* Try and fsck every filesystem in this pass. */ + for (fs = fstab->entries; fs; fs = fs->next) + if (fs->mntent.mnt_passno == pass) + /* FS is applicable for this pass. */ + { + struct fstype *type; + error_t err = fs_type (fs, &type); + + if (err) + { + error (0, err, "%s: Cannot find fsck program (type %s)", + fs->mntent.mnt_dir, fs->mntent.mnt_type); + merge_status (FSCK_EX_ERROR); + } + else if (type->program) + /* This is a fsckable filesystem. */ + { + fs_debug (fs, "Fsckable; free_slots = %d", fscks.free_slots); + while (fscks.free_slots == 0) + /* No room; wait for another fsck to finish. */ + merge_status (fscks_wait (&fscks)); + merge_status (fscks_start_fsck (&fscks, fs)); + } + else if (autom) + fs_debug (fs, "Not fsckable"); + else + error (0, 0, "%s: %s: Not a fsckable filesystem type", + fs->mntent.mnt_dir, fs->mntent.mnt_type); + } + + /* Now wait for them all to finish. */ + while (fscks.running) + merge_status (fscks_wait (&fscks)); + } + + return summary_status; +} + +static const struct argp_option options[] = +{ + {"preen", 'p', 0, 0, "Terse automatic mode", 1}, + {"yes", 'y', 0, 0, "Automatically answer yes to all questions"}, + {"no", 'n', 0, 0, "Automatically answer no to all questions"}, + {"parallel", 'l', "NUM", 0, "Limit the number of parallel checks to NUM"}, + {"verbose", 'v', 0, 0, "Print informational messages"}, + {"writable", 'w', 0, 0, + "Make RW filesystems writable after fscking, if possible"}, + {"debug", 'D', 0, OPTION_HIDDEN }, + {"force", 'f', 0, 0, "Check even if clean"}, + + {"dry-run", 'N', 0, 0, "Don't check, just show what would be done"}, + {0, 0, 0, 0, "In --preen mode, the following also apply:", 2}, + {"silent", 's', 0, 0, "Print only diagnostic messages"}, + {"quiet", 'q', 0, OPTION_ALIAS | OPTION_HIDDEN }, + {0, 0} +}; +static const char doc[] = "Filesystem consistency check and repair"; +static const char args_doc[] = "[ DEVICE|FSYS... ]"; + + +int +main (int argc, char **argv) +{ + struct fstab *check; + int status; /* exit status */ + int flags = 0; + int max_parallel = -1; /* -1 => use default */ + + error_t parse_opt (int key, char *arg, struct argp_state *state) + { + struct fstab_argp_params *params = state->input; + switch (key) + { + case ARGP_KEY_INIT: + state->child_inputs[0] = params; /* pass down to fstab_argp parser */ + break; + case 'p': flags |= FSCK_F_PREEN; break; + case 'y': flags |= FSCK_F_YES; break; + case 'n': flags |= FSCK_F_NO; break; + case 'f': flags |= FSCK_F_FORCE; break; + case 's': flags |= FSCK_F_SILENT; break; + case 'v': flags |= FSCK_F_VERBOSE; break; + case 'w': flags |= FSCK_F_WRITABLE; break; + case 'N': flags |= FSCK_F_DRYRUN; break; + case 'D': _debug = 1; break; + case 'l': + max_parallel = atoi (arg); + if (max_parallel < 1) + argp_error (state, "%s: Invalid value for --max-parallel", arg); + break; + case ARGP_KEY_NO_ARGS: + if (flags & FSCK_F_PREEN) + params->do_all = 1; + else if (!params->do_all) + { + argp_usage (state); + return EINVAL; + } + break; + default: + return ARGP_ERR_UNKNOWN; + } + return 0; + } + static const struct argp_child kids[] = + { { &fstab_argp, 0, + "Filesystem selection (default is all in " _PATH_MNTTAB "):", 2 }, + { 0 } }; + struct argp argp = { options, parse_opt, args_doc, doc, kids }; + struct fstab_argp_params fstab_params; + + argp_parse (&argp, argc, argv, 0, 0, &fstab_params); + + check = fstab_argp_create (&fstab_params, + FSCK_SEARCH_FMTS, sizeof FSCK_SEARCH_FMTS); + if (fstab_params.do_all) + flags |= FSCK_F_AUTO; + + if (max_parallel <= 0) + { + if (flags & FSCK_F_PREEN) + max_parallel = 100; /* In preen mode, do lots in parallel. */ + else + max_parallel = 1; /* Do one at a time to keep output rational. */ + } + + /* If the user send a SIGQUIT (usually ^\), then do all checks, but + regardless of their outcome, return a status that will cause the + automatic reboot to stop after fscking is complete. */ + signal (SIGQUIT, sigquit); + + /* Let currently running fscks complete (each such program can handle + signals as it sees fit), and cause not-yet-run fscks to act as if they + got a signal. */ + signal (SIGINT, sigint); + + debug ("Fscking..."); + status = fsck (check, flags, max_parallel); + if (got_sigquit && status < FSCK_EX_QUIT) + status = FSCK_EX_QUIT; + + exit (status); +} |