diff options
Diffstat (limited to 'sutils/fstab.c')
-rw-r--r-- | sutils/fstab.c | 591 |
1 files changed, 591 insertions, 0 deletions
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; +} |