summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--sutils/fsck.c504
-rw-r--r--sutils/fstab.c591
-rw-r--r--sutils/fstab.h152
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, &currently_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__ */