diff options
author | Justus Winter <4winter@informatik.uni-hamburg.de> | 2013-07-30 11:59:24 +0200 |
---|---|---|
committer | Samuel Thibault <samuel.thibault@ens-lyon.org> | 2013-09-15 23:28:04 +0200 |
commit | a9a800dbaa0e79ecba232e477291a38e119e2df9 (patch) | |
tree | eaf22ba2b8e4fb4d6e0769c500d2ad6b6ae69491 /trans | |
parent | bdd2c077ee64a236881f6a9de2bb2ab540aeea55 (diff) |
trans: add mtab translator
The mtab translator provides an mtab file that is dynamically created
on demand. It is populated with information about active translators
bound below the given path that is accumulated by traversing the
translator tree. It can also be invoked as normal program that prints
the requested information to stdout.
* trans/mtab.c: New file.
* trans/Makefile: Build mtab.
Diffstat (limited to 'trans')
-rw-r--r-- | trans/Makefile | 8 | ||||
-rw-r--r-- | trans/mtab.c | 800 |
2 files changed, 805 insertions, 3 deletions
diff --git a/trans/Makefile b/trans/Makefile index b3210b67..6eb51d0d 100644 --- a/trans/Makefile +++ b/trans/Makefile @@ -20,14 +20,15 @@ dir := trans makemode := servers targets = symlink firmlink ifsock magic null fifo new-fifo fwd crash \ - password hello hello-mt streamio fakeroot proxy-defpager remap + password hello hello-mt streamio fakeroot proxy-defpager remap \ + mtab SRCS = ifsock.c symlink.c magic.c null.c fifo.c new-fifo.c fwd.c \ crash.c firmlink.c password.c hello.c hello-mt.c streamio.c \ - fakeroot.c proxy-defpager.c remap.c + fakeroot.c proxy-defpager.c remap.c mtab.c OBJS = $(SRCS:.c=.o) fsysServer.o ifsockServer.o passwordServer.o \ crashServer.o crash_replyUser.o msgServer.o \ default_pagerServer.o default_pagerUser.o \ - device_replyServer.o elfcore.o + device_replyServer.o elfcore.o fsysUser.o HURDLIBS = ports netfs trivfs iohelp fshelp pipe ihash shouldbeinlibc LDLIBS += -lpthread password-LDLIBS = $(LIBCRYPT) @@ -51,6 +52,7 @@ magic: ../libiohelp/libiohelp.a hello: ../libtrivfs/libtrivfs.a ../libfshelp/libfshelp.a ../libports/libports.a ../libihash/libihash.a fakeroot: ../libnetfs/libnetfs.a ../libfshelp/libfshelp.a ../libiohelp/libiohelp.a ../libports/libports.a ../libihash/libihash.a remap: ../libtrivfs/libtrivfs.a ../libfshelp/libfshelp.a ../libports/libports.a ../libihash/libihash.a +mtab: ../libtrivfs/libtrivfs.a ../libfshelp/libfshelp.a ../libports/libports.a ../libihash/libihash.a fsysUser.o $(targets): ../libshouldbeinlibc/libshouldbeinlibc.a $(targets): %: %.o diff --git a/trans/mtab.c b/trans/mtab.c new file mode 100644 index 00000000..2973a0b7 --- /dev/null +++ b/trans/mtab.c @@ -0,0 +1,800 @@ +/* This is an mtab translator. + + Copyright (C) 2013 Free Software Foundation, Inc. + + Written by Justus Winter <4winter@informatik.uni-hamburg.de> + + 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 the GNU Hurd. If not, see <http://www.gnu.org/licenses/>. */ + +#include <argp.h> +#include <argz.h> +#include <error.h> +#include <fcntl.h> +#include <hurd.h> +#include <hurd/trivfs.h> +#include <inttypes.h> +#include <mntent.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <unistd.h> +#include <version.h> + +#include "fsys_U.h" + +static char *path = NULL; +static int insecure = 0; + +/* Our control port. */ +struct trivfs_control *control; + +/* These kind of objects are created and populated in the open_hook. + They keep track of the content and file position of the client. */ +struct mtab +{ + char *contents; + size_t contents_len; + off_t offs; +}; + +const char *argp_program_version = STANDARD_HURD_VERSION (mtab); + +static const struct argp_option options[] = +{ + {"insecure", 'I', 0, 0, + "Follow translators not bound to nodes owned by you or root"}, + {} +}; + +/* Parse a command line option. */ +error_t parse_opt (int key, char *arg, struct argp_state *state) +{ + switch (key) + { + case 'I': + insecure = 1; + break; + + case ARGP_KEY_ARG: + path = realpath (arg, NULL); + if (! path) + argp_error (state, "Error while canonicalizing path"); + break; + + case ARGP_KEY_NO_ARGS: + argp_usage (state); + return EINVAL; + + default: + return ARGP_ERR_UNKNOWN; + } + return 0; +} + +static struct argp argp = + { + options, + parse_opt, + "TARGET\tFile name of a node with an active translator", + "A translator providing mtab compatible information about active " + "and passive translators below TARGET.", + }; + +/* This will be called from libtrivfs to help construct the answer + to an fsys_get_options RPC. */ +error_t +trivfs_append_args (struct trivfs_control *fsys, + char **argz, size_t *argz_len) +{ + error_t err; + + if (insecure) + { + err = argz_add (argz, argz_len, path); + if (err) + return err; + } + + err = argz_add (argz, argz_len, path); + return err; +} + +/* Setting this variable makes libtrivfs use our argp to + parse options passed in an fsys_set_options RPC. */ +struct argp *trivfs_runtime_argp = &argp; + +/* Authentication of the current process. */ +uid_t *uids; +gid_t *gids; +size_t uids_len, gids_len; + +/* Initialize and populate the uids and gids vectors. */ +error_t +get_credentials (void) +{ + /* Fetch uids... */ + uids_len = geteuids (0, 0); + if (uids_len < 0) + return errno; + + uids = malloc (uids_len * sizeof (uid_t)); + if (! uids) + return ENOMEM; + + uids_len = geteuids (uids_len, uids); + if (uids_len < 0) + return errno; + + /* ... and gids. */ + gids_len = getgroups (0, 0); + if (gids_len < 0) + return errno; + + gids = malloc (gids_len * sizeof (gid_t)); + if (! uids) + return ENOMEM; + + gids_len = getgroups (gids_len, gids); + if (gids_len < 0) + return errno; + + return 0; +} + +/* Check if the given struct stat describes a node owned by the + current user. */ +int +is_owner (io_statbuf_t *st) +{ + int found = 0; + for (size_t i = 0; i < uids_len; i++) + if (uids[i] == st->st_uid) + { + found = 1; + break; + } + + if (! found) + return 0; + + found = 0; + for (size_t i = 0; i < gids_len; i++) + if (gids[i] == st->st_gid) + { + found = 1; + break; + } + + return found; +} + +error_t +mtab_populate (struct mtab *mtab, const char *path, int insecure); + +error_t +argz_add_device (char **options, size_t *options_len, const char *device); + +error_t +map_device_to_path (const char *device, char **path); + +int +main (int argc, char *argv[]) +{ + error_t err; + + err = argp_parse (&argp, argc, argv, ARGP_IN_ORDER, 0, 0); + if (err) + error (1, err, "argument parsing"); + + err = get_credentials (); + if (err) + error (2, err, "getting credentials"); + + mach_port_t bootstrap; + task_get_bootstrap_port (mach_task_self (), &bootstrap); + if (bootstrap != MACH_PORT_NULL) + { + /* Started as a translator. */ + + auth_t nullauth; + err = auth_makeauth (getauth (), + NULL, MACH_MSG_TYPE_COPY_SEND, 0, + NULL, 0, + NULL, 0, + NULL, 0, + NULL, 0, + &nullauth); + if (err) + error (3, err, "dropping credentials"); + + err = setauth (nullauth); + if (err) + error (3, err, "dropping credentials"); + + /* Reply to our parent. */ + err = trivfs_startup (bootstrap, 0, 0, 0, 0, 0, &control); + mach_port_deallocate (mach_task_self (), bootstrap); + if (err) + error (4, err, "trivfs_startup"); + + /* Launch. */ + ports_manage_port_operations_one_thread (control->pi.bucket, + trivfs_demuxer, + 0); + } + else + { + /* One-shot mode. */ + struct mtab mtab = { NULL, 0, 0 }; + err = mtab_populate (&mtab, path, insecure); + if (err) + error (5, err, "%s", path); + + if (mtab.contents) + printf ("%s", mtab.contents); + } + + return 0; +} + +error_t +mtab_add_entry (struct mtab *mtab, const char *entry, size_t length) +{ + char *p = realloc (mtab->contents, mtab->contents_len + length + 1); + if (! p) + return ENOMEM; + + memcpy (&p[mtab->contents_len], entry, length); + + mtab->contents = p; + mtab->contents_len += length; + + /* Zero-terminate contents so that we can also interpret it as + string. */ + mtab->contents[mtab->contents_len] = '\0'; + + return 0; +} + +/* Populates the given MTAB object with the information for PATH. If + INSECURE is given, also follow translators bound to nodes not owned + by root or the current user. */ +/* XXX split up */ +error_t +mtab_populate (struct mtab *mtab, const char *path, int insecure) +{ + error_t err = 0; + + /* These resources are freed in the epilogue. */ + file_t node = MACH_PORT_NULL; + fsys_t fsys = MACH_PORT_NULL; + char *argz = NULL; + size_t argz_len = 0; + char **argv = NULL; + char *type = NULL; + char *options = NULL; + size_t options_len = 0; + char *src = NULL; + char *entry = NULL; + size_t entry_len = 0; + char *children = NULL; + size_t children_len = 0; + + /* Get the underlying node. */ + node = file_name_lookup (path, O_NOTRANS, 0666); + if (node == MACH_PORT_NULL) + { + err = errno; + goto errout; + } + + if (! insecure) + { + /* Check who owns the node the translator is bound to. */ + io_statbuf_t st; + err = io_stat (node, &st); + if (err) + goto errout; + + if (st.st_uid != 0 && st.st_gid != 0 && ! is_owner (&st)) + { + err = EPERM; + goto errout; + } + } + + err = file_get_translator_cntl (node, &fsys); + if (err == EPERM) + /* If we do not have permission to do that, it cannot be a node + bound to our control port, so ignore this error. */ + err = 0; + + if (err == ENXIO && strcmp (path, "/") == 0) + /* The root translator fails differently, but this can't be bound + to our control port either, so ignore this error. */ + err = 0; + + if (err) + return err; + + if (control && control->pi.port_right == fsys) + /* This node is bound to our control port, ignore it. */ + goto errout; + + /* Re-do the lookup without O_NOTRANS to get the root node. */ + mach_port_deallocate (mach_task_self (), node); + node = file_name_lookup (path, 0, 0666); + if (node == MACH_PORT_NULL) + { + err = errno; + goto errout; + } + + /* Query its options. */ + err = file_get_fs_options (node, &argz, &argz_len); + if (err) + { + if (err == EOPNOTSUPP) + err = 0; /* There's not much we could do then. */ + + goto errout; + } + + size_t count = argz_count (argz, argz_len); + argv = malloc ((count + 1) * sizeof (char *)); + if (! argv) + { + err = ENOMEM; + goto errout; + } + + argz_extract (argz, argz_len, argv); + + type = strdup (argv[0]); + if (! type) + { + err = ENOMEM; + goto errout; + } + + for (int i = 1; i < count - 1; i++) + { + char *v = argv[i]; + + if (*v == '-') + v++; + if (*v == '-') + v++; + + err = argz_add (&options, &options_len, v); + if (err) + goto errout; + } + + err = argz_add_device (&options, &options_len, argv[count - 1]); + if (err) + goto errout; + + argz_stringify (options, options_len, ','); + + string_t source; + err = fsys_get_source (node, source); + if (err) + { + if (err == EOPNOTSUPP) + { + /* Guess based on the last argument. */ + err = map_device_to_path (argv[count - 1], &src); + if (err) + goto errout; + } + else + goto errout; + } + else + src = source; + + entry_len = asprintf (&entry, "%s %s %s %s 0 0\n", src, path, type, + options? options: MNTOPT_DEFAULTS); + if (! entry) + { + err = ENOMEM; + goto errout; + } + + err = mtab_add_entry (mtab, entry, entry_len); + if (err) + goto errout; + + /* path has an active translator, query its children. */ + err = fsys_get_children (node, &children, &children_len); + if (err == EOPNOTSUPP) + { + err = 0; + children_len = 0; + } + + if (err) + goto errout; + + if (children_len) + for (char *c = children; c; c = argz_next (children, children_len, c)) + { + char *p = NULL; + asprintf (&p, "%s%s%s", + path, + path[strlen (path) - 1] == '/'? "": "/", + c); + if (! p) + { + err = ENOMEM; + goto errout; + } + + err = mtab_populate (mtab, p, insecure); + if (err) + { + /* There is really not much we can do about errors here. */ + error (0, err, "%s", p); + err = 0; + } + + free (p); + } + + errout: + if (node != MACH_PORT_NULL) + mach_port_deallocate (mach_task_self (), node); + + if (fsys != MACH_PORT_NULL) + mach_port_deallocate (mach_task_self (), fsys); + + if (argz) + vm_deallocate (mach_task_self (), (vm_address_t) argz, argz_len); + + free (argv); + free (type); + free (options); + + if (src != source) + free (src); + + free (entry); + + if (children) + vm_deallocate (mach_task_self (), (vm_address_t) children, children_len); + + return err; +} + +/* Decodes the DEVICE string into appropriate OPTIONS. Currently only + tmpfs-style size declarations are supported. */ +error_t +argz_add_device (char **options, size_t *options_len, const char *device) +{ + error_t err; + char *end = NULL; + intmax_t size = strtoimax (device, &end, 0); + if (end == NULL || end == device) + return 0; + + if (size < 0) + return 0; + + switch (*end) + { + case 'g': + case 'G': + case 'm': + case 'M': + case 'k': + case 'K': + break; + default: + return 0; + } + + /* device specifies a size. */ + char *arg = NULL; + asprintf (&arg, "size=%s", device); + if (! arg) + return ENOMEM; + + err = argz_add (options, options_len, arg); + + free (arg); + return err; +} + +/* Matches [hs]d\ds\d\d?. */ +int +looks_like_block_device (const char *s) +{ + size_t len = strlen (s); + if (len != 3 && len != 5 && len != 6) + return 0; + + return ((s[0] == 'h' || s[0] == 's') && s[1] == 'd' && isdigit (s[2]) && + (len == 3 || (s[3] == 's' && isdigit (s[4]) && + (len == 5 || isdigit (s[5]))))); +} + +/* Map a device string to a file name referencing the appropriate + device file. */ +error_t +map_device_to_path (const char *device, char **path) +{ + if (strncmp (device, "device:", 7) == 0) + asprintf (path, "/dev/%s", &device[7]); + else if (strncmp (device, "/dev/", 5) == 0) + *path = strdup (device); + else if (looks_like_block_device (device)) + asprintf (path, "/dev/%s", device); + else + *path = strdup ("none"); + + if (! *path) + return ENOMEM; + + return 0; +} + +/* Trivfs hooks. */ +int trivfs_fstype = FSTYPE_MISC; +int trivfs_fsid = 0; + +int trivfs_allow_open = O_READ; + +int trivfs_support_read = 1; +int trivfs_support_write = 0; +int trivfs_support_exec = 0; + +void +trivfs_modify_stat (struct trivfs_protid *cred, struct stat *st) +{ + /* Mark the node as a read-only plain file. */ + st->st_mode &= ~(S_IFMT | ALLPERMS); + st->st_mode |= (S_IFREG | S_IRUSR | S_IRGRP | S_IROTH); + st->st_size = ((struct mtab *) cred->po->hook)->contents_len; +} + +error_t +trivfs_goaway (struct trivfs_control *cntl, int flags) +{ + exit (EXIT_SUCCESS); +} + +static error_t +open_hook (struct trivfs_peropen *peropen) +{ + struct mtab *mtab = malloc (sizeof (struct mtab)); + if (mtab == NULL) + return ENOMEM; + + /* Hook! */ + peropen->hook = mtab; + + /* Initialize the fields. */ + mtab->offs = 0; + mtab->contents = NULL; + mtab->contents_len = 0; + + return mtab_populate (mtab, path, insecure); +} + +static void +close_hook (struct trivfs_peropen *peropen) +{ + free (((struct mtab *) peropen->hook)->contents); + free (peropen->hook); +} + +/* Read data from an IO object. If offset is -1, read from the object + maintained file pointer. If the object is not seekable, offset is + ignored. The amount desired to be read is in AMOUNT. */ +error_t +trivfs_S_io_read (struct trivfs_protid *cred, + mach_port_t reply, mach_msg_type_name_t reply_type, + char **data, mach_msg_type_number_t *data_len, + loff_t offs, mach_msg_type_number_t amount) +{ + struct mtab *op; + + /* Deny access if they have bad credentials. */ + if (! cred) + return EOPNOTSUPP; + + if (! (cred->po->openmodes & O_READ)) + return EBADF; + + /* Get the offset. */ + op = cred->po->hook; + if (offs == -1) + offs = op->offs; + + /* Prune the amount they want to read. */ + if (offs > op->contents_len) + offs = op->contents_len; + if (offs + amount > op->contents_len) + amount = op->contents_len - offs; + + if (amount > 0) + { + /* Possibly allocate a new buffer. */ + if (*data_len < amount) + { + *data = mmap (0, amount, PROT_READ|PROT_WRITE, MAP_ANON, 0, 0); + if (*data == MAP_FAILED) + return ENOMEM; + } + + /* Copy the constant data into the buffer. */ + memcpy ((char *) *data, op->contents + offs, amount); + + /* Update the saved offset. */ + op->offs += amount; + } + + *data_len = amount; + return 0; +} + + +/* Change current read/write offset */ +error_t +trivfs_S_io_seek (struct trivfs_protid *cred, + mach_port_t reply, mach_msg_type_name_t reply_type, + off_t offs, int whence, off_t *new_offs) +{ + if (! cred) + return EOPNOTSUPP; + + struct mtab *op = cred->po->hook; + + switch (whence) + { + case SEEK_CUR: + offs += op->offs; + goto check; + case SEEK_END: + offs += op->contents_len; + case SEEK_SET: + check: + if (offs >= 0) + { + *new_offs = op->offs = offs; + break; + } + default: + return EINVAL; + } + + return 0; +} + +/* If this variable is set, it is called every time a new peropen + structure is created and initialized. */ +error_t (*trivfs_peropen_create_hook)(struct trivfs_peropen *) = open_hook; + +/* If this variable is set, it is called every time a peropen structure + is about to be destroyed. */ +void (*trivfs_peropen_destroy_hook) (struct trivfs_peropen *) = close_hook; + +/* Tell how much data can be read from the object without blocking for + a "long time" (this should be the same meaning of "long time" used + by the nonblocking flag. */ +kern_return_t +trivfs_S_io_readable (struct trivfs_protid *cred, + mach_port_t reply, mach_msg_type_name_t replytype, + mach_msg_type_number_t *amount) +{ + if (!cred) + return EOPNOTSUPP; + + if (!(cred->po->openmodes & O_READ)) + return EINVAL; + + struct mtab *op = cred->po->hook; + + *amount = op->contents_len - op->offs; + return 0; +} + +/* SELECT_TYPE is the bitwise OR of SELECT_READ, SELECT_WRITE, and SELECT_URG. + Block until one of the indicated types of i/o can be done "quickly", and + return the types that are then available. ID_TAG is returned as passed; it + is just for the convenience of the user in matching up reply messages with + specific requests sent. */ +kern_return_t +trivfs_S_io_select (struct trivfs_protid *cred, + mach_port_t reply, mach_msg_type_name_t replytype, + int *type) +{ + if (!cred) + return EOPNOTSUPP; + + if (((*type & SELECT_READ) && !(cred->po->openmodes & O_READ)) + || ((*type & SELECT_WRITE) && !(cred->po->openmodes & O_WRITE))) + return EBADF; + + *type &= ~SELECT_URG; + return 0; +} + +kern_return_t +trivfs_S_io_select_timeout (struct trivfs_protid *cred, + mach_port_t reply, mach_msg_type_name_t replytype, + struct timespec ts, + int *type) +{ + return trivfs_S_io_select (cred, reply, replytype, type); +} + +/* These four routines modify the O_APPEND, O_ASYNC, O_FSYNC, and + O_NONBLOCK bits for the IO object. In addition, io_get_openmodes + will tell you which of O_READ, O_WRITE, and O_EXEC the object can + be used for. The O_ASYNC bit affects icky async I/O; good async + I/O is done through io_async which is orthogonal to these calls. */ + +kern_return_t +trivfs_S_io_get_openmodes (struct trivfs_protid *cred, + mach_port_t reply, mach_msg_type_name_t replytype, + int *bits) +{ + if (!cred) + return EOPNOTSUPP; + + *bits = cred->po->openmodes; + return 0; +} + +error_t +trivfs_S_io_set_all_openmodes(struct trivfs_protid *cred, + mach_port_t reply, + mach_msg_type_name_t replytype, + int mode) +{ + if (!cred) + return EOPNOTSUPP; + + return 0; +} + +kern_return_t +trivfs_S_io_set_some_openmodes (struct trivfs_protid *cred, + mach_port_t reply, + mach_msg_type_name_t replytype, + int bits) +{ + if (!cred) + return EOPNOTSUPP; + + return 0; +} + +kern_return_t +trivfs_S_io_clear_some_openmodes (struct trivfs_protid *cred, + mach_port_t reply, + mach_msg_type_name_t replytype, + int bits) +{ + if (!cred) + return EOPNOTSUPP; + + return 0; +} |