/* 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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; }