Hi, I have cleaned up my run.c translator for inclusion in the Hurd, please let me know if I can check it in (it's supposed to be a single file in trans/run.c). I think it is a simple translator to learn from by beginners, but it also provides a useful functionality. It's one piece that bridges between the Hurd filesystems and UNIX programs.
It supports reading and writing, through a bidirectional pipe (but it will only set up the directions that correspond to the open() flags). One simple way is to use it to connect a file to the output of a program: $ settrans -ac ~/.signature /hurd/run -r /games/fortune -s Another simple way is to direct what is written into a file to program: settrans -ac /var/log/apache.log /hurd/run -W /bin/sh -c '/bin/logfile-dispatcher > /tmp/dispatcher-status' In this sense, it can also be an alternative to James filemux, because you can connect it with arbitrary programs, like shell scripts (for example, in read mode, you can make a shell script that selects a file at random from a group of files, and outputs it). One instance of the program is started for each open which is followed by a real file action (like a read or write). One special mode that doesn't work too well is using it in read/write mode. If you open it in read/write mode, you can communicate with the program over its stdin and stdout descriptors (almost as with pipes), and use the program as filter. For example, it is supposed to be possible to do things like that: settrans -ac /tmp/foo /hurd/run /bin/tr a-z A-Z However, I have not found an easy way yet to route data through the /tmp/foo node. The shell redirector <> looks promising, but I am not sure if it is possible to incorporate this correctly into a pipe-chained sequence of commands. So I am considering to write a "filter" program that has two threads and takes a filename as an argument. One thread reads on stdin and writes to the opened file, the other thread reads from the file and writes to stdout. This should work rather well, and can be used like this: cat text | filter /tmp/foo > /tmp/text-foo-ed However, there is a fundamental problem in all this because we can't properly signal an EOF condition. After all, we only have one file descriptor open to the run translator, and how can we signal that there is nothing left to read to it? When we could, I would use two pipes instead one bidirectional, and close it. This would be enough to let the running program know about it. But how to get the end of file signal to the foo translator in the first place? The filter program should do it somehow. Do we have some icky (or even proper) RPC that we can use for this? Am I missing something obvious? Another point is that it does not do any buffering. It probably should, although this would make it more complex. Oh, and I have not tested the icky select implementation. I should probably use io_select as I use io_readable, but I changed this back and forth for unrelated reasons. Oh, and I was not sure about the reply port argument and cancellation. For learning by newbies, I also think that having some code that looks familiar is also nice ;) I have tested that reading and writing and all options work properly. With programs like cat, the filter mode works properly. With programs like tr, it seems to hang when trying to read back (see above). If you have any suggestions, please let me know. Thanks, Marcus -- `Rhubarb is no Egyptian god.' Debian http://www.debian.org [EMAIL PROTECTED] Marcus Brinkmann GNU http://www.gnu.org [EMAIL PROTECTED] [EMAIL PROTECTED] http://www.marcus-brinkmann.de
/* run.c - A translator connected to a running program. Copyright (C) 2002 Free Software Foundation, Inc. Written by Marcus Brinkmann. This program 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. This program 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #define _GNU_SOURCE 1 #include <hurd/trivfs.h> #include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <argp.h> #include <argz.h> #include <error.h> #include <string.h> #include <fcntl.h> #include <sys/mman.h> #include <sys/stat.h> #include <sys/wait.h> #include <cthreads.h> #include <rwlock.h> #include <version.h> const char *argp_program_version = STANDARD_HURD_VERSION (run); /* This lock protects access to cmd, cmd_len, cmdp and the trivfs_allow_open variables. */ static struct rwlock config_lock; /* ARGZ version of command line to execute. */ static char *cmd = 0; static size_t cmd_len; /* ARGV version of command line to execute. */ static char **cmdp = 0; /* A hook for us to keep track of the file descriptor state. */ struct open { struct mutex lock; int fd; /* The file descriptor of the pipe to the child, -1 if not yet opened, and -2 if open failed. */ }; /* From the Unix Programming FAQ. */ static int fork2 (void) { pid_t pid; int status; if (!(pid = fork ())) { switch (fork ()) { case 0: /* Child. */ return 0; case -1: /* Assumes all errnos are < 256. */ _exit (errno); default: /* Parent. */ _exit (0); } } if (pid < 0 || waitpid (pid, &status, 0) < 0) return -1; if (WIFEXITED (status)) if (WEXITSTATUS (status) == 0) return 1; else errno = WEXITSTATUS (status); else errno = EINTR; /* well, sort of :-) */ return -1; } /* Start the child process. FLAGS can be O_READ, O_WRITE or bit-wise OR of both. Return a file descriptor for reading the child's output or -1 on error. */ static int start_child (int flags) { int p[2]; int err; /* In the GNU system, pipes are bidirectional, so reading and writing works in both directions, and we only need one pipe. */ if (pipe (p)) return -1; if (!(err = fork2 ())) { /* Translators are started without a terminal, so the first invocation of pipe() returns 0 and 1 as file descriptors. */ int duped = 1; close (p[0]); if (flags & O_READ) { /* If want to allow reading, we need to redirect stdout to our pipe. */ if (p[1] != 1) dup2 (p[1],1); else duped = 0; } if (flags & O_WRITE) { /* If want to allow writing, we need to redirect stdin to our pipe. */ if (p[1] != 0) dup2 (p[1],0); else duped = 0; } /* Close the pipe fd if it wasn't one of those we need. */ if (duped) close (p[1]); execvp (cmdp[0], cmdp); exit (-1); /* Only reached if execvp fails. */ } if (err == -1) { /* fork() failed. */ close (p[0]); close (p[1]); return -1; } close (p[1]); fcntl (p[0], F_SETFD, FD_CLOEXEC); return p[0]; } static int check_child (struct open *op) { if (op->fd == -1) { rwlock_reader_lock (&config_lock); op->fd = start_child (trivfs_allow_open); rwlock_reader_unlock (&config_lock); if (op->fd == -1) op->fd = -2; } if (op->fd == -2) return -1; return 0; } /* Trivfs hooks. */ int trivfs_fstype = FSTYPE_MISC; int trivfs_fsid = 0; int trivfs_allow_open = O_RDWR; int trivfs_support_read = 1; int trivfs_support_write = 1; int trivfs_support_exec = 0; void trivfs_modify_stat (struct trivfs_protid *cred, struct stat *st) { /* Mark the node as a pipe. */ st->st_mode &= ~S_IFMT; st->st_mode |= S_IFIFO; st->st_size = 0; } error_t trivfs_goaway (struct trivfs_control *cntl, int flags) { exit (0); } static error_t open_hook (struct trivfs_peropen *peropen) { struct open *op = malloc (sizeof (struct open)); if (op == NULL) return ENOMEM; mutex_init (&op->lock); op->fd = -1; /* -1: We have not tried to open it yet. */ peropen->hook = op; return 0; } static void close_hook (struct trivfs_peropen *peropen) { struct open *op = peropen->hook; if (op->fd >= 0) close (op->fd); mutex_clear (&op->lock); free (peropen->hook); } /* 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; /* 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, vm_address_t *data, mach_msg_type_number_t *data_len, off_t offs, mach_msg_type_number_t amount) { struct open *op; /* Deny access if they have bad credentials. */ if (! cred) return EOPNOTSUPP; else if (! (cred->po->openmodes & O_READ)) return EBADF; /* Get the open hook. */ op = cred->po->hook; /* Check if the pipe is really open. */ mutex_lock (&op->lock); if (op->fd < 0) { check_child (op); if (op->fd < 0) { mutex_unlock (&op->lock); return EIO; } } mutex_unlock (&op->lock); /* Offset is not supported and ignored. */ if (amount > 0) { /* Possibly allocate a new buffer. */ if (*data_len < amount) *data = (vm_address_t) mmap (0, amount, PROT_READ|PROT_WRITE, MAP_ANON, 0, 0); /* Read the data from the pipe. */ amount = read (op->fd, (char *) *data, amount); if (amount == -1) return errno; } *data_len = amount; return 0; } /* Write data to an IO object. If offset is -1, write at the object maintained file pointer. If the object is not seekable, offset is ignored. The amount successfully written is returned in amount. A given user should not have more than one outstanding io_write on an object at a time; servers implement congestion control by delaying responses to io_write. Servers may drop data (returning ENOBUFS) if they recevie more than one write when not prepared for it. */ kern_return_t trivfs_S_io_write (struct trivfs_protid *cred, mach_port_t reply, mach_msg_type_name_t replytype, vm_address_t data, mach_msg_type_number_t datalen, off_t offs, mach_msg_type_number_t *amt) { int amount = 0; struct open *op; if (!cred) return EOPNOTSUPP; else if (!(cred->po->openmodes & O_WRITE)) return EBADF; /* Get the open hook. */ op = cred->po->hook; /* Check if the pipe is really open. */ mutex_lock (&op->lock); if (op->fd < 0) { check_child (op); if (op->fd < 0) { mutex_unlock (&op->lock); return EIO; } } mutex_unlock (&op->lock); /* Offset is not supported and ignored. */ if (datalen > 0) { amount = write (op->fd, (char *) data, datalen); if (amount == -1) return errno; } *amt = amount; 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. */ kern_return_t trivfs_S_io_select (struct trivfs_protid *cred, mach_port_t reply, mach_msg_type_name_t replytype, int *type) { struct open *op; fd_set rfds; fd_set wfds; fd_set efds; int nr; if (!cred) return EOPNOTSUPP; else if (((*type & SELECT_READ) && !(cred->po->openmodes & O_READ)) || ((*type & SELECT_WRITE) && !(cred->po->openmodes & O_WRITE))) return EBADF; /* Get the open hook. */ op = cred->po->hook; /* Check if the pipe is really open. */ mutex_lock (&op->lock); if (op->fd < 0) { check_child (op); if (op->fd < 0) { mutex_unlock (&op->lock); return EIO; } } mutex_unlock (&op->lock); FD_ZERO (&rfds); FD_ZERO (&wfds); FD_ZERO (&efds); if (*type & SELECT_READ) FD_SET (op->fd, &rfds); if (*type & SELECT_WRITE) FD_SET (op->fd, &wfds); if (*type & SELECT_URG) FD_SET (op->fd, &efds); nr = select (op->fd, (*type & SELECT_READ) ? &rfds : 0, (*type & SELECT_WRITE) ? &wfds : 0, (*type & SELECT_URG) ? &efds : 0, 0); if (nr == -1) return errno; *type = 0; if (FD_ISSET (op->fd, &rfds)) *type = SELECT_READ; if (FD_ISSET (op->fd, &wfds)) *type = SELECT_WRITE; if (FD_ISSET (op->fd, &efds)) *type = SELECT_URG; return 0; } /* 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) { struct open *op; io_t file; error_t err; if (!cred) return EOPNOTSUPP; else if (!(cred->po->openmodes & O_READ)) return EINVAL; /* Get the open hook. */ op = cred->po->hook; /* Check if the pipe is really open. */ mutex_lock (&op->lock); if (op->fd < 0) { check_child (op); if (op->fd < 0) { mutex_unlock (&op->lock); return EIO; } } mutex_unlock (&op->lock); /* For a change, do native I/O instead using the C library. */ file = getdport (op->fd); if (file == MACH_PORT_NULL) return errno; err = io_readable (file, amount); mach_port_deallocate (mach_task_self (), file); return err; } /* 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) { return EOPNOTSUPP; } kern_return_t trivfs_S_file_set_size (struct trivfs_protid *cred, mach_port_t reply, mach_msg_type_name_t reply_type, off_t size) { if (!cred) return EOPNOTSUPP; else if (!(cred->po->openmodes & O_WRITE)) return EINVAL; return 0; } /* These three routines modify the O_APPEND, O_ASYNC, O_FSYNC, and O_NONBLOCK bits for the IO object. The O_ASYNC bit affects icky async I/O; good async I/O is done through io_async which is orthogonal to these calls. */ error_t trivfs_S_io_set_all_openmodes(struct trivfs_protid *cred, mach_port_t reply, mach_msg_type_name_t replytype, int mode) { return EOPNOTSUPP; } 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) { return EOPNOTSUPP; } 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; else return 0; } /* Options processing. We accept the same options on the command line and from fsys_set_options. */ static const struct argp_option options[] = { {"readonly", 'r', 0, 0, "Disallow writing"}, {"rdonly", 0, 0, OPTION_ALIAS | OPTION_HIDDEN}, {"ro", 0, 0, OPTION_ALIAS | OPTION_HIDDEN}, {"writable", 'w', 0, 0, "Allow writing"}, {"rdwr", 0, 0, OPTION_ALIAS | OPTION_HIDDEN}, {"rw", 0, 0, OPTION_ALIAS | OPTION_HIDDEN}, {"writeonly", 'W',0, 0, "Disallow reading"}, {"wronly", 0, 0, OPTION_ALIAS | OPTION_HIDDEN}, {0} }; static const char args_doc[] = "COMMAND [ARG...]"; static const char doc[] = "A translator for invoking a command" "\vThis translator appears like a file which content is the output" " of a program."; static error_t parse_opt (int opt, char *arg, struct argp_state *state) { switch (opt) { default: return ARGP_ERR_UNKNOWN; case ARGP_KEY_INIT: case ARGP_KEY_SUCCESS: case ARGP_KEY_ERROR: break; case 'r': rwlock_writer_lock (&config_lock); trivfs_allow_open = O_READ; rwlock_writer_unlock (&config_lock); return 0; case 'w': rwlock_writer_lock (&config_lock); trivfs_allow_open = O_RDWR; rwlock_writer_unlock (&config_lock); return 0; case 'W': rwlock_writer_lock (&config_lock); trivfs_allow_open = O_WRITE; rwlock_writer_unlock (&config_lock); return 0; case ARGP_KEY_NO_ARGS: rwlock_writer_lock (&config_lock); if (!cmd) { rwlock_writer_unlock (&config_lock); argp_usage(state); return EINVAL; } rwlock_writer_unlock (&config_lock); return 0; case ARGP_KEY_ARGS: /* Steal the entire tail of arg vector for our own use. */ rwlock_writer_lock (&config_lock); if (cmd) free (cmd); if (cmdp) free (cmdp); cmd = 0; cmdp = 0; if (argz_create (state->argv + state->next, &cmd, &cmd_len)) { rwlock_writer_unlock (&config_lock); return ENOMEM; } if (! (cmdp = (char **) malloc (sizeof (char *) * (argz_count (cmd, cmd_len) + 1)))) { rwlock_writer_unlock (&config_lock); return ENOMEM; } argz_extract (cmd, cmd_len, cmdp); rwlock_writer_unlock (&config_lock); return 0; } return 0; } /* 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 = 0; rwlock_reader_lock (&config_lock); if (trivfs_allow_open != O_RDWR) err = argz_add (argz, argz_len, (trivfs_allow_open == O_READ) ? "-r" : "-W"); if (!err) err = argz_append (argz, argz_len, cmd, cmd_len); rwlock_reader_unlock (&config_lock); return err; } static struct argp run_argp = { options, parse_opt, args_doc, doc }; /* Setting this variable makes libtrivfs use our argp to parse options passed in an fsys_set_options RPC. */ struct argp *trivfs_runtime_argp = &run_argp; int main (int argc, char **argv) { error_t err; mach_port_t bootstrap; struct trivfs_control *fsys; /* Initialize the lock that will protect CMD, CMD_LEN and CMDP. We must do this before argp_parse, because parse_opt (above) will use the lock. */ rwlock_init (&config_lock); /* We use the same argp for options available at startup as for options we'll accept in an fsys_set_options RPC. */ argp_parse (&run_argp, argc, argv, ARGP_IN_ORDER, 0, 0); task_get_bootstrap_port (mach_task_self (), &bootstrap); if (bootstrap == MACH_PORT_NULL) error (1, 0, "Must be started as a translator"); /* Reply to our parent. */ err = trivfs_startup (bootstrap, 0, 0, 0, 0, 0, &fsys); mach_port_deallocate (mach_task_self (), bootstrap); if (err) error (3, err, "trivfs_startup"); /* Launch. */ ports_manage_port_operations_multithread (fsys->pi.bucket, trivfs_demuxer, 10 * 1000, /* Idle thread. */ 10 * 60 * 1000, /* Idle server. */ 0); return 0; }