diff options
-rw-r--r-- | sutils/fsck.c | 504 | ||||
-rw-r--r-- | sutils/fstab.c | 591 | ||||
-rw-r--r-- | sutils/fstab.h | 152 |
3 files changed, 1247 insertions, 0 deletions
diff --git a/sutils/fsck.c b/sutils/fsck.c new file mode 100644 index 00000000..18e44bba --- /dev/null +++ b/sutils/fsck.c @@ -0,0 +1,504 @@ +/* Hurd-aware fsck wrapper + + Copyright (C) 1996 Free Software Foundation, Inc. + + Written by Miles Bader <miles@gnu.ai.mit.edu> + + 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 "fstab.h" + +#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 +#define FSCK_F_VERBOSE 0x20 /* Not passed down. */ + +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 was_readonly :1; /* The fs was readonly before we made it so. */ + 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_VERBOSE) + { + 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_VERBOSE) + { + char *argz; + size_t argz_len; + argz_create (argv, &argz, &argz_len); + argz_stringify (argz, argz_len, ' '); + puts (argz); + free (argz); + } + + 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. */ + } + + 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, was_readonly; + struct fsck *fsck; + + if (got_sigint) + /* We got SIGINT, so we pretend that all fscks got a signal without even + attempting to run them. */ + return FSCK_EX_SIGNAL; + +#define CK(err, fmt, args...) \ + do { if (err) { error (0, err, fmt , ##args); return FSCK_EX_ERROR; } } while (0) + + err = fs_mounted (fs, &mounted); + CK (err, "%s: Cannot check mounted state", fs->mntent.mnt_dir); + + if (mounted) + { + err = fs_readonly (fs, &was_readonly); + CK (err, "%s: Cannot check readonly state", fs->mntent.mnt_dir); + if (! was_readonly) + { + err = fs_set_readonly (fs, 1); + CK (err, "%s: Cannot make readonly", fs->mntent.mnt_dir); + } + } + +#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->was_readonly = was_readonly; + 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 + RESTORE_WRITABLE is true, then if the filesystem was mounted writable + prior to fscking, make it writable once more (after remounting if + applicable). */ +static void +fsck_cleanup (struct fsck *fsck, int remount, int restore_writable) +{ + error_t err = 0; + struct fs *fs = fsck->fs; + + *fsck->self = fsck->next; /* Remove from chain. */ + + 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) + { + err = fs_remount (fs); + if (err) + error (0, err, "%s: Cannot remount", fs->mntent.mnt_dir); + } + if (!err && !fsck->was_readonly && restore_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) + fsck_cleanup (fsck, 0, 1); + } + + 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 restore_writable = (status == 0 || FSCK_EX_IS_FIXED (status)); + fsck_cleanup (fsck, remount, restore_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 summary_status = 0; + struct fscks *fscks = malloc (sizeof (struct fscks)); + + void merge_status (int status) + { + if (status > summary_status) + summary_status = status; + } + + if (! fscks) + error (FSCK_EX_ERROR, ENOMEM, "malloc"); + fscks->running = 0; + fscks->flags = flags; + fscks->free_slots = max_parallel; + + /* Do in pass order (pass 0 is skipped). */ + 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. */ + 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. */ + { + 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)); + } + } + + free (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"}, + {"fstab", 't', "FILE", 0, "File to use instead of " _PATH_MNTTAB}, + {"max-parallel", 'l', "NUM", 0, "Limit the number of parallel checks to NUM"}, + {"verbose", 'v', 0, 0, "Print informational messages"}, + {"search-fmts",'S', "FMTS", 0, + "`:' separated list of formats to use for finding fsck programs"}, + {0, 0, 0, 0, "In --preen mode, the following also apply:", 2}, + {"force", 'f', 0, 0, "Check even if clean"}, + {"silent", 's', 0, 0, "Only print diagostic messages"}, + {0, 0} +}; +static const char *args_doc = "DEVICE"; +static const char *doc = 0; + +int +main (int argc, char **argv) +{ + error_t err; + struct fstab *fstab, *check; + struct fstypes *types; + int flags = 0; + char *names = 0; + size_t names_len = 0; + char *search_fmts = FSCK_SEARCH_FMTS; + size_t search_fmts_len = sizeof FSCK_SEARCH_FMTS; + char *fstab_path = _PATH_MNTTAB; + int max_parallel = -1; + + error_t parse_opt (int key, char *arg, struct argp_state *state) + { + switch (key) + { + 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 't': fstab_path = arg; break; + case 'l': + max_parallel = atoi (arg); + if (! max_parallel) + argp_error (state, "%s: Invalid value for --max-parellel", arg); + break; + case 'S': + argz_create_sep (arg, ':', &search_fmts, &search_fmts_len); + break; + case ARGP_KEY_ARG: + err = argz_add (&names, &names_len, arg); + if (err) + argp_failure (state, 100, ENOMEM, "%s", arg); + break; + default: + return ARGP_ERR_UNKNOWN; + } + return 0; + } + struct argp argp = {options, parse_opt, args_doc, doc}; + + argp_parse (&argp, argc, argv, 0, 0, 0); + + err = fstypes_create (search_fmts, search_fmts_len, &types); + if (err) + error (102, err, "fstypes_create"); + + err = fstab_create (types, &fstab); + if (err) + error (101, err, "fstab_create"); + + err = fstab_read (fstab, _PATH_MNTTAB); + if (err) + error (103, err, "%s", _PATH_MNTTAB); + + if (names) + /* Fsck specified filesystems; also look at /var/run/mtab. */ + { + char *name; + + err = fstab_read (fstab, _PATH_MOUNTED); + if (err) + error (104, err, "%s", _PATH_MOUNTED); + + err = fstab_create (types, &check); + if (err) + error (105, err, "fstab_create"); + + for (name = names; name; name = argz_next (names, names_len, name)) + { + struct fs *fs = fstab_find (fstab, name); + if (! fs) + error (106, 0, "%s: Unknown device or filesystem", name); + fstab_add_fs (check, fs, 0); + } + } + else + /* Fsck everything in /etc/fstab. */ + check = fstab; + + 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); + + status = fsck (check, flags, max_parallel); + if (got_sigquit && status < FSCK_EX_QUIT) + status = FSCK_EX_QUIT; + + exit (status); +} diff --git a/sutils/fstab.c b/sutils/fstab.c new file mode 100644 index 00000000..91e730d2 --- /dev/null +++ b/sutils/fstab.c @@ -0,0 +1,591 @@ +/* Fstab filesystem frobbing + + Copyright (C) 1996 Free Software Foundation, Inc. + + Written by Miles Bader <miles@gnu.ai.mit.edu> + + 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. */ + +#include <stdlib.h> +#include <unistd.h> +#include <stdio.h> +#include <errno.h> +#include <fcntl.h> +#include <error.h> +#include <argz.h> + +#include <hurd/fsys.h> + +#include "fstab.h" + +/* Return a new fstab in FSTAB. */ +error_t +fstab_create (struct fstypes *types, struct fstab **fstab) +{ + struct fstab *new = malloc (sizeof (struct fstab)); + if (new) + { + new->entries = 0; + new->types = types; + *fstab = new; + return 0; + } + else + return ENOMEM; +} + +/* Free FSTAB and all of its entries. */ +void +fstab_free (struct fstab *fstab) +{ + while (fstab->entries) + fs_free (fstab->entries); + free (fstab); +} + +/* Return a new fstypes structure in TYPES. SEARCH_FMTS is copied. */ +error_t +fstypes_create (char *search_fmts, size_t search_fmts_len, + struct fstypes **types) +{ + struct fstypes *new = malloc (sizeof (struct fstypes)); + if (new) + { + new->entries = 0; + new->program_search_fmts = malloc (search_fmts_len); + new->program_search_fmts_len = search_fmts_len; + if (! new->program_search_fmts) + { + free (types); + return ENOMEM; + } + *types = new; + return 0; + } + else + return ENOMEM; +} + +/* Return an fstype entry in TYPES called NAME, in FSTYPE. If there is no + existing entry, an attempt to find a fsck program with the given type, + using the alternatives in the FSCK_SEARCH_FMTS field in TYPES. If + one is found, it is added to TYPES, otherwise an new entry is created + with a NULL PROGRAM field. */ +error_t +fstypes_get (struct fstypes *types, char *name, struct fstype **fstype) +{ + char *fmts, *fmt; + size_t fmts_len; + struct fstype *type; + + for (type = types->entries; type; type = type->next) + if (strcasecmp (type->name, name) == 0) + { + *fstype = type; + return 0; + } + + /* No existing entry, make a new one. */ + type = malloc (sizeof (struct fstype)); + if (! type) + return ENOMEM; + + type->name = strdup (name); + type->program = 0; + type->next = types->entries; + types->entries = type; + + fmts = types->program_search_fmts; + fmts_len = types->program_search_fmts_len; + + for (fmt = fmts; fmt; fmt = argz_next (fmts, fmts_len, fmt)) + { + int fd; + char *program; + + asprintf (&program, fmt, name); + fd = open (program, O_EXEC); + if (fd < 0) + { + free (program); /* Failed. */ + if (errno != ENOENT && errno != EACCES) + /* The program's there but something went wrong; fail. */ + return errno; + } + else + /* We can open for exec, but check the stat info too (e.g. root can + open everything). */ + { + struct stat stat; + int rv = fstat (fd, &stat); + + close (fd); + + if (rv < 0) + return errno; + + if (stat.st_mode & S_IXUSR) + /* Yup execute bit is set. This must a program... */ + { + type->program = program; + break; + } + } + } + + return 0; +} + +/* Copy MNTENT into FS, copying component strings as well. */ +error_t +fs_set_mntent (struct fs *fs, struct mntent *mntent) +{ + char *end; + size_t needed = 0; + + if (fs->storage) + free (fs->storage); + + /* Allocate space for all string mntent fields in FS. */ +#define COUNT(field) if (mntent->field) needed += strlen (mntent->field) + 1; + COUNT (mnt_fsname); + COUNT (mnt_dir); + COUNT (mnt_type); + COUNT (mnt_opts); +#undef COUNT + + fs->storage = malloc (needed); + if (! fs->storage) + return ENOMEM; + + /* Copy MNTENT into FS; string-valued fields will be fixed up next. */ + fs->mntent = *mntent; + + /* Copy each mntent field from MNTENT into FS's version. */ + end = fs->storage; +#define STORE(field) \ + fs->mntent.field = end; end = stpcpy (end, mntent->field) + 1 + STORE (mnt_fsname); + STORE (mnt_dir); + STORE (mnt_type); + STORE (mnt_opts); +#undef STORE + + if (fs->type && strcasecmp (fs->type->name, mntent->mnt_type) != 0) + fs->type = 0; /* Type is different. */ + + return 0; +} + +/* Returns an fstype for FS in TYPE, trying to fillin FS's type field if + necessary. */ +error_t +fs_type (struct fs *fs, struct fstype **type) +{ + error_t err = 0; + if (! fs->type) + err = fstypes_get (fs->fstab->types, fs->mntent.mnt_type, &fs->type); + if (! err) + *type = fs->type; + return err; +} + +/* Looks to see if FS is currently mounted, being very careful to avoid + mounting anything that's not already, and fills in the fsys & mounted + fields in FS. */ +static error_t +_fs_check_mounted (struct fs *fs) +{ + error_t err = 0; + + if (fs->mounted < 0) + /* The mounted field in FS is -1 if we're not sure. */ + { + file_t mount_point; + error_t err = careful_lookup (fs->mntent.mnt_dir, &mount_point); + + if (! err) + { + if (fs->fsys != MACH_PORT_NULL) + mach_port_deallocate (mach_task_self (), fs->fsys); + if (mount_point != MACH_PORT_NULL) + /* The node exists. Is it the root of an active translator? + [Note that it could be a different translator than the one in + the mntent, but oh well, nothing we can do about that.] */ + err = file_get_translator_cntl (mount_point, &fs->fsys); + else + /* Either the mount point doesn't exist, or wasn't mounted. */ + fs->fsys = MACH_PORT_NULL; + } + + if (! err) + fs->mounted = (fs->fsys != MACH_PORT_NULL); + } + + return err; +} + +/* Looks to see if FS is currently mounted, being very careful to avoid + mounting anything that's not already, and returns the control port for the + mounted filesystem (MACH_PORT_NULL if not mounted). */ +error_t +fs_fsys (struct fs *fs, fsys_t *fsys) +{ + error_t err = _fs_check_mounted (fs); + if (!err && fsys) + *fsys = fs->fsys; + return err; +} + +/* Looks to see if FS is currently mounted, being very careful to avoid + mounting anything that's not already, and returns the boolean MOUNTED. */ +error_t +fs_mounted (struct fs *fs, int *mounted) +{ + error_t err = _fs_check_mounted (fs); + if (!err && mounted) + *mounted = fs->mounted; + return err; +} + +/* Looks to see if FS is currently mounted readonly, being very careful to + avoid mounting anything that's not already, and returns the boolean + READONLY. If FS isn't mounted at all, READONLY is set to 1 (it's not + going to write anything!). */ +error_t +fs_readonly (struct fs *fs, int *readonly) +{ + error_t err = 0; + + if (fs->readonly < 0) + /* Unknown. */ + { + fsys_t fsys; + + err = fs_fsys (fs, &fsys); + if (! err) + if (fsys == MACH_PORT_NULL) + fs->readonly = 1; + else + /* Ask the filesystem whether it's readonly; we don't really have a + good method for this, other than asking for it's options and + looking for `--readonly' or `--writable'. If we see neither, + return EOPNOTSUPP. */ + { + char _opts[200], *opts = _opts; + size_t opts_len = sizeof opts; + + err = fsys_get_options (fsys, &opts, &opts_len); + if (! err) + { + char *opt; + + for (opt = opts + ; fs->readonly < 0 && opt && opt < opts + opts_len + ; opt = argz_next (opts, opts_len, opt)) + if (strcasecmp (opt, "--readonly") == 0) + fs->readonly = 1; + else if (strcasecmp (opt, "--writable") == 0) + fs->readonly = 0; + + if (fs->readonly < 0) + err = EOPNOTSUPP; /* So far as we know... */ + + if (opts != _opts) + /* Free out-of-line memory returned by fsys_get_options. */ + vm_deallocate (mach_task_self (), + (vm_address_t)opts, opts_len); + } + } + } + + if (!err && readonly) + *readonly = fs->readonly; + + return err; +} + +/* If FS is currently mounted writable, try to make it readonly. XXX If FS + is not mounted at all, then nothing is done. */ +error_t +fs_set_readonly (struct fs *fs, int readonly) +{ + int currently_readonly; + error_t err = fs_readonly (fs, ¤tly_readonly); + + readonly = !!readonly; + + if (!err && readonly != currently_readonly) + /* We have to try and change the readonly state. */ + { + fsys_t fsys; + + err = fs_fsys (fs, &fsys); + if (!err && fsys != MACH_PORT_NULL) /* XXX What to do if not mounted? */ + { + char *opts = readonly ? "--readonly" : "--writable"; + size_t opts_len = strlen (opts) + 1; + err = fsys_set_options (fsys, opts, opts_len, 0); + if (! err) + fs->readonly = readonly; + } + } + + return err; +} + +/* If FS is currently mounted tell lit to remount the device. XXX If FS is + not mounted at all, then nothing is done. */ +error_t +fs_remount (struct fs *fs) +{ + fsys_t fsys; + error_t err = fs_fsys (fs, &fsys); + if (!err && fsys != MACH_PORT_NULL) /* XXX What to do if not mounted? */ + { + char *opts = "--remount"; + size_t opts_len = strlen (opts) + 1; + err = fsys_set_options (fsys, opts, opts_len, 0); + } + return err; +} + +/* Returns the FS entry in FSTAB with the device field NAME (there can only + be one such entry). */ +inline struct fs * +fstab_find_device (struct fstab *fstab, char *name) +{ + struct fs *fs; + for (fs = fstab->entries; fs; fs = fs->next) + if (strcmp (fs->mntent.mnt_fsname, name) == 0) + return fs; + return 0; +} + +/* Returns the FS entry in FSTAB with the mount point NAME (there can only + be one such entry). */ +inline struct fs * +fstab_find_mount (struct fstab *fstab, char *name) +{ + struct fs *fs; + for (fs = fstab->entries; fs; fs = fs->next) + if (strcmp (fs->mntent.mnt_dir, name) == 0) + return fs; + return 0; +} + +/* Returns the FS entry in FSTAB with the device or mount point NAME (there + can only be one such entry). */ +inline struct fs * +fstab_find (struct fstab *fstab, char *name) +{ + return fstab_find_device (fstab, name) ?: fstab_find_mount (fstab, name); +} + +/* Cons FS onto the beginning of FSTAB's entry list. */ +static void +_fstab_add (struct fstab *fstab, struct fs *fs) +{ + fs->fstab = fstab; + fs->next = fstab->entries; + fs->self = &fstab->entries; + if (fstab->entries) + fstab->entries->self = &fs->next; + fstab->entries = fs; +} + +/* Destroy FS, removing it from its containing FSTAB. */ +void +fs_free (struct fs *fs) +{ + *fs->self = fs->next; /* unlink from chain */ + if (fs->storage) + free (fs->storage); + if (fs->fsys != MACH_PORT_NULL) + mach_port_deallocate (mach_task_self (), fs->fsys); + free (fs); +} + +/* Add an entry for MNTENT to FSTAB, removing any existing entries that + conflict (in either the device or mount point). If RESULT is non-zero, the + new entry is returne in it. */ +error_t +fstab_add_mntent (struct fstab *fstab, struct mntent *mntent, + struct fs **result) +{ + int new = 0; /* True if we didn't overwrite an old entry. */ + error_t err = 0; + struct fs *fs = fstab_find_device (fstab, mntent->mnt_fsname); + struct fs *mounted_fs = fstab_find_mount (fstab, mntent->mnt_dir); + + if (! fs) + /* No old entry with the same device; see if there's one with the same + mount point. */ + { + fs = mounted_fs; + mounted_fs = 0; + } + + if (! fs) + /* No old entry, make a new one. */ + { + fs = malloc (sizeof (struct fs)); + if (fs) + { + bzero (fs, sizeof (struct fs)); + new = 1; + } + else + err = ENOMEM; + } + + if (! err) + /* Try and fill in FS's mntent. */ + err = fs_set_mntent (fs, mntent); + + if (new) + if (! err) + _fstab_add (fstab, fs); + else if (fs) + free (fs); + + if (!err && mounted_fs) + /* Get rid of the conflicting entry MOUNTED_FS. */ + fs_free (mounted_fs); + + if (!err && result) + *result = fs; + + return err; +} + +/* Copy the entry FS (which should belong to another fstab than DST) into + DST. If DST & SRC have different TYPES fields, EINVAL is returned. If + COPY is non-zero, the copy is returned in it. */ +error_t +fstab_add_fs (struct fstab *dst, struct fs *fs, struct fs **copy) +{ + error_t err; + struct fstab *src = fs->fstab; + + if (dst->types != src->types) + return EINVAL; + + err = fstab_add_mntent (dst, &fs->mntent, copy); + if (err) + return err; + + (*copy)->type = fs->type; + + return 0; +} + +/* Merge SRC into DST, as if by calling fstab_add_fs on DST with every + entry in SRC, and then deallocating SRC. If DST & SRC have different + TYPES fields, EINVAL is returned. */ +error_t +fstab_merge (struct fstab *dst, struct fstab *src) +{ + struct fs *fs; + + if (dst->types != src->types) + return EINVAL; + + /* Remove entries in DST which conflict with those in SRC. */ + for (fs = src->entries; fs; fs = fs->next) + { + struct fs *old_fs; + + old_fs = fstab_find_device (dst, fs->mntent.mnt_fsname); + if (old_fs) + fs_free (old_fs); + old_fs = fstab_find_mount (dst, fs->mntent.mnt_dir); + if (old_fs) + fs_free (old_fs); + } + + /* Now that we know there are no conflicts, steal all SRC's entries and + cons them onto DST. */ + for (fs = src->entries; fs; fs = fs->next) + _fstab_add (dst, fs); + + /* Now all entries from SRC should be in DST, so just deallocate SRC. */ + free (src); + + return 0; +} + +/* Reads fstab-format entries into FSTAB from the file NAME. Any entries + duplicating one already in FS_LIST supersede the existing entry. */ +error_t +fstab_read (struct fstab *fstab, char *name) +{ + error_t err; + /* Used to hold entries from the file, before merging with FSTAB at the + end. */ + struct fstab *contents; + FILE *stream = setmntent (name, "r"); + + if (! stream) + return errno; + + err = fstab_create (fstab->types, &contents); + if (! err) + { + while (!err && !feof (stream)) + { + struct mntent *mntent = getmntent (stream); + + if (! mntent) + err = errno; + else if (fstab_find_device (fstab, mntent->mnt_fsname)) + error (0, 0, "%s: Warning: duplicate entry for device %s (%s)", + name, mntent->mnt_fsname, mntent->mnt_dir); + else if (fstab_find_mount (fstab, mntent->mnt_dir)) + error (0, 0, "%s: Warning: duplicate entry for mount point %s (%s)", + name, mntent->mnt_dir, mntent->mnt_fsname); + else + err = fstab_add_mntent (fstab, mntent, 0); + } + + if (! err) + fstab_merge (fstab, contents); + else + fstab_free (contents); + } + + endmntent (stream); + + return err; +} + +/* Return the next pass number that applies to any filesystem in FSTAB that + is greater than PASS, or -1 if there isn't any. */ +int fstab_next_pass (struct fstab *fstab, int pass) +{ + int next_pass = -1; + struct fs *fs; + for (fs = fstab->entries; fs; fs = fs->next) + if (fs->mntent.mnt_passno > pass) + if (next_pass < 0 || fs->mntent.mnt_passno < next_pass) + { + next_pass = fs->mntent.mnt_passno; + if (next_pass == pass + 1) + break; /* Only possible answer. */ + } + return next_pass; +} diff --git a/sutils/fstab.h b/sutils/fstab.h new file mode 100644 index 00000000..b8616185 --- /dev/null +++ b/sutils/fstab.h @@ -0,0 +1,152 @@ +/* Fstab filesystem frobbing + + Copyright (C) 1996 Free Software Foundation, Inc. + + Written by Miles Bader <miles@gnu.ai.mit.edu> + + 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. */ + +#ifndef __FSTAB_H__ +#define __FSTAB_H__ + +#include <mntent.h> +#include <hurd.h> + +struct fs +{ + struct fstab *fstab; /* Containg fstab. */ + struct mntent mntent; /* Mount entry from fstab file. */ + char *storage; /* Storage for strings in MNTENT. */ + struct fstype *type; /* Only set if fs_type called. */ + int readonly, mounted; /* Only set if fs_{readonly,mounted} called. + 0 or 1 if known; -1 if unknown. */ + fsys_t fsys; /* Only set if fs_fsys called. */ + struct fs *next, **self; +}; + +struct fstab +{ + struct fs *entries; + struct fstypes *types; +}; + +struct fstype +{ + char *name; /* Malloced. */ + char *program; /* Malloced. */ + struct fstype *next; +}; + +struct fstypes +{ + struct fstype *entries; + + /* How to search for programs. A '\0' separated list of strings. Each + should be a printf-style format string, which will be passed the + filesystem type. The first such expansion that results in an executable + file will be chosen as the fsck program for that type. */ + char *program_search_fmts; + size_t program_search_fmts_len; /* Length of PROGRAM_SEARCH_FMTS. */ +}; + +/* Return a new fstab in FSTAB. */ +error_t fstab_create (struct fstypes *types, struct fstab **fstab); + +/* Free FSTAB and all of its entries. */ +void fstab_free (struct fstab *fstab); + +/* Return a new fstypes structure in TYPES. SEARCH_FMTS is copied. */ +error_t fstypes_create (char *search_fmts, size_t search_fmts_len, + struct fstypes **types); + +/* Return an fstype entry in TYPES called NAME, in FSTYPE. If there is no + existing entry, an attempt to find a fsck program with the given type, + using the alternatives in the FSCK_SEARCH_FMTS field in TYPES. If + one is found, it is added to TYPES, otherwise an new entry is created + with a NULL PROGRAM field. */ +error_t fstypes_get (struct fstypes *types, char *name, struct fstype **fstype); + +/* Copy MNTENT into FS, copying component strings as well. */ +error_t fs_set_mntent (struct fs *fs, struct mntent *mntent); + +/* Returns an fstype for FS in TYPE, trying to fillin FS's type field if + necessary. */ +error_t fs_type (struct fs *fs, struct fstype **type); + +/* Looks to see if FS is currently mounted, being very careful to avoid + mounting anything that's not already, and returns the control port for the + mounted filesystem (MACH_PORT_NULL if not mounted). */ +error_t fs_fsys (struct fs *fs, fsys_t *fsys); + +/* Looks to see if FS is currently mounted, being very careful to avoid + mounting anything that's not already, and returns the boolean MOUNTED. */ +error_t fs_mounted (struct fs *fs, int *mounted); + +/* Looks to see if FS is currently mounted readonly, being very careful to + avoid mounting anything that's not already, and returns the boolean + READONLY. If FS isn't mounted at all, READONLY is set to 1 (it's not + going to write anything!). */ +error_t fs_readonly (struct fs *fs, int *readonly); + +/* If FS is currently mounted writable, try to make it readonly. XXX If FS + is not mounted at all, then nothing is done. */ +error_t fs_set_readonly (struct fs *fs, int readonly); + +/* If FS is currently mounted tell lit to remount the device. XXX If FS is + not mounted at all, then nothing is done. */ +error_t fs_remount (struct fs *fs); + +/* Destroy FS, removing it from its containing FSTAB. */ +void fs_free (struct fs *fs); + +/* Returns the FS entry in FSTAB with the device field NAME (there can only + be one such entry). */ +struct fs *fstab_find_device (struct fstab *fstab, char *name); + +/* Returns the FS entry in FSTAB with the mount point NAME (there can only + be one such entry). */ +struct fs *fstab_find_mount (struct fstab *fstab, char *name); + +/* Returns the FS entry in FSTAB with the device or mount point NAME (there + can only be one such entry). */ +struct fs *fstab_find (struct fstab *fstab, char *name); + +/* Add an entry for MNTENT to FSTAB, removing any existing entries that + conflict (in either the device or mount point). If RESULT is non-zero, the + new entry is returne in it. */ +error_t fstab_add_mntent (struct fstab *fstab, struct mntent *mntent, + struct fs **result); + +/* Copy the entry FS (which should belong to another fstab than DST) into + DST. If DST & SRC have different TYPES fields, EINVAL is returned. If + COPY is non-zero, the copy is returned in it. */ +error_t fstab_add_fs (struct fstab *dst, struct fs *fs, struct fs **copy); + +/* Merge SRC into DST, as if by calling fstab_add_fs on DST with every + entry in SRC, and then deallocating SRC. If DST & SRC have different + TYPES fields, EINVAL is returned. */ +error_t fstab_merge (struct fstab *dst, struct fstab *src); + +/* Reads fstab-format entries into FSTAB from the file NAME. Any entries + duplicating one already in FS_LIST supersede the existing entry. */ +error_t fstab_read (struct fstab *fstab, char *name); + +/* Return the next pass number that applies to any filesystem in FSTAB that + is greater than PASS, or -1 if there isn't any. */ +int fstab_next_pass (struct fstab *fstab, int pass); + +#endif /* __FSTAB_H__ */ |