summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--libc/stdio/popen.c218
1 files changed, 171 insertions, 47 deletions
diff --git a/libc/stdio/popen.c b/libc/stdio/popen.c
index 2fd1fa832..ad9b84cf7 100644
--- a/libc/stdio/popen.c
+++ b/libc/stdio/popen.c
@@ -1,73 +1,197 @@
-/*
- * Modified 3/03/2001 Manuel Novoa III
+/* Copyright (C) 2004 Manuel Novoa III
*
- * Added check for legal mode arg.
- * Call fdopen and check return value before forking.
- * Reduced code size by using variables pr and pnr instead of array refs.
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the Free
+ * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/* Jan 1, 2004
+ *
+ * Rewrite popen for SUSv3 compliance.
+ * Added a list of popen()'d to store pids and use waitpid() in pclose().
+ * Loop on waitpid() failure due to EINTR as required.
+ * Close parent's popen()'d FILEs in the {v}fork()'d child.
+ * Fix failure exit code for failed execve().
*/
+
#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
#include <unistd.h>
-#include <sys/types.h>
#include <sys/wait.h>
-#include <errno.h>
/* uClinux-2.0 has vfork, but Linux 2.0 doesn't */
#include <sys/syscall.h>
#if ! defined __NR_vfork
-#define vfork fork
+# define vfork fork
+# define VFORK_LOCK ((void) 0)
+# define VFORK_UNLOCK ((void) 0)
+#endif
+
+#ifdef __UCLIBC_HAS_THREADS__
+#include <pthread.h>
+static pthread_mutex_t mylock = PTHREAD_MUTEX_INITIALIZER;
+# define LOCK __pthread_mutex_lock(&mylock)
+# define UNLOCK __pthread_mutex_unlock(&mylock);
+#else
+# define LOCK ((void) 0)
+# define UNLOCK ((void) 0)
+#endif
+
+#ifndef VFORK_LOCK
+# define VFORK_LOCK LOCK
+# define VFORK_UNLOCK UNLOCK
#endif
-FILE *popen (const char *command, const char *mode)
+struct popen_list_item {
+ struct popen_list_item *next;
+ FILE *f;
+ pid_t pid;
+};
+
+static struct popen_list_item *popen_list /* = NULL (bss initialized) */;
+
+FILE *popen(const char *command, const char *modes)
{
FILE *fp;
+ struct popen_list_item *pi;
+ struct popen_list_item *po;
int pipe_fd[2];
- int pid, reading;
- int pr, pnr;
-
- reading = (mode[0] == 'r');
- if ((!reading && (mode[0] != 'w')) || mode[1]) {
- __set_errno(EINVAL); /* Invalid mode arg. */
- } else if (pipe(pipe_fd) == 0) {
- pr = pipe_fd[reading];
- pnr = pipe_fd[1-reading];
- if ((fp = fdopen(pnr, mode)) != NULL) {
- if ((pid = vfork()) == 0) { /* vfork -- child */
- close(pnr);
- if (pr != reading) {
- close(reading);
- dup2(pr, reading);
- close(pr);
- }
- execl("/bin/sh", "sh", "-c", command, (char *) 0);
- _exit(255); /* execl failed! */
- } else { /* vfork -- parent or failed */
- close(pr);
- if (pid > 0) { /* vfork -- parent */
- return fp;
- } else { /* vfork -- failed! */
- fclose(fp);
- }
- }
- } else { /* fdopen failed */
- close(pr);
- close(pnr);
+ int parent_fd;
+ int child_fd;
+ int child_writing; /* Doubles as the desired child fildes. */
+ pid_t pid;
+
+ child_writing = 0; /* Assume child is writing. */
+ if (modes[0] != 'w') { /* Parent not writing... */
+ ++child_writing; /* so child must be writing. */
+ if (modes[0] != 'r') { /* Oops! Parent not reading either! */
+ __set_errno(EINVAL);
+ goto RET_NULL;
+ }
+ }
+
+ if (!(pi = malloc(sizeof(struct popen_list_item)))) {
+ goto RET_NULL;
+ }
+
+ if (pipe(pipe_fd)) {
+ goto FREE_PI;
+ }
+
+ child_fd = pipe_fd[child_writing];
+ parent_fd = pipe_fd[1-child_writing];
+
+ if (!(fp = fdopen(parent_fd, modes))) {
+ close(parent_fd);
+ close(child_fd);
+ goto FREE_PI;
+ }
+
+ VFORK_LOCK;
+ if ((pid = vfork()) == 0) { /* Child of vfork... */
+ close(parent_fd);
+ if (child_fd != child_writing) {
+ dup2(child_fd, child_writing);
+ close(child_fd);
+ }
+
+ /* SUSv3 requires that any previously popen()'d streams in the
+ * parent shall be closed in the child. */
+ for (po = popen_list ; po ; po = po->next) {
+ close(po->f->__filedes);
}
+
+ execl("/bin/sh", "sh", "-c", command, (char *)0);
+
+ /* SUSv3 mandates an exit code of 127 for the child if the
+ * command interpreter can not be invoked. */
+ _exit(127);
+ }
+ VFORK_UNLOCK;
+
+ /* We need to close the child filedes whether vfork failed or
+ * it succeeded and we're in the parent. */
+ close(child_fd);
+
+ if (pid > 0) { /* Parent of vfork... */
+ pi->pid = pid;
+ pi->f = fp;
+ LOCK;
+ pi->next = popen_list;
+ popen_list = pi;
+ UNLOCK;
+
+ return fp;
}
+
+ /* If we get here, vfork failed. */
+ fclose(fp); /* Will close parent_fd. */
+
+ FREE_PI:
+ free(pi);
+
+ RET_NULL:
return NULL;
}
-int pclose(FILE *fd)
+int pclose(FILE *stream)
{
- int waitstat;
+ struct popen_list_item *p;
+ int stat;
+ pid_t pid;
- if (fclose(fd) != 0) {
- return EOF;
+ /* First, find the list entry corresponding to stream and remove it
+ * from the list. Set p to the list item (NULL if not found). */
+ LOCK;
+ if ((p = popen_list) != NULL) {
+ if (p->f == stream) {
+ popen_list = p->next;
+ } else {
+ struct popen_list_item *t;
+ do {
+ t = p;
+ if (!(p = t->next)) {
+ __set_errno(EINVAL); /* Not required by SUSv3. */
+ break;
+ }
+ if (p->f == stream) {
+ t->next = p->next;
+ break;
+ }
+ } while (1);
+ }
}
- if (wait(&waitstat) == -1)
- return -1;
- return waitstat;
-}
+ UNLOCK;
+ if (p) {
+ pid = p->pid; /* Save the pid we need */
+ free(p); /* and free the list item. */
+ fclose(stream); /* The SUSv3 example code ignores the return. */
+ /* SUSv3 specificly requires that pclose not return before the child
+ * terminates, in order to disallow pclose from returning on EINTR. */
+ do {
+ if (waitpid(pid, &stat, 0) >= 0) {
+ return stat;
+ }
+ if (errno != EINTR) {
+ break;
+ }
+ } while (1);
+ }
+
+ return -1;
+}