reimplement and fix popen/pclose routines

This commit is contained in:
gdr 1997-09-21 06:04:01 +00:00
parent 1554cedb32
commit b94726dc62

View File

@ -1,91 +1,168 @@
/*
* Copyright (c) 1988 The Regents of the University of California.
* All rights reserved.
* This is a complete rewrite of the popen/pclose routines.
* Devin Reade, 1997.
*
* This code is derived from software written by Ken Arnold and
* published in UNIX Review, Vol. 6, No. 8.
* $Id: popen.c,v 1.2 1997/09/21 06:04:01 gdr Exp $
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the University of
* California, Berkeley and its contributors.
* 4. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* This file is formatted for tab stops every 8 columns.
*/
#ifdef __ORCAC__
segment "libc_gen__";
#endif
#pragma optimize 0
#pragma debug 0
#pragma memorymodel 0
#if defined(LIBC_SCCS) && !defined(lint)
static char *sccsid = "from: @(#)popen.c 5.15 (Berkeley) 2/23/91";
static char *rcsid = "popen.c,v 1.5 1993/08/26 00:44:55 jtc Exp";
#endif /* LIBC_SCCS and not lint */
#include <sys/param.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <signal.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <paths.h>
#include <assert.h>
#include <sys/queue.h>
#ifdef __GNO__
#include <gno/gno.h>
#endif
static pid_t *pids;
#include <assert.h> /* shouldn't be necessary in final code */
/*
* This #define makes us create a temporary file containing the
* commands to execute rather than giving them on the gsh command
* line. Gsh has a problem with accepting command line arguments ...
*/
#define USE_SCRIPT 1
/*
* We maintain state information by having a linked list of these
* structs.
*/
static SLIST_HEAD(popenStateHead, popenEntry_t) popenHead;
static struct popenStateHead *popenHeadPtr = NULL;
typedef struct popenEntry_t {
SLIST_ENTRY(popenEntry_t) pr_link; /* link */
FILE *pr_fp; /* the FILE pointer */
pid_t pr_pid; /* pid of the child */
#ifdef USE_SCRIPT
char *pr_name; /* name of the "script" file */
#endif
} popenEntry_t;
#ifdef USE_SCRIPT
/*
* _popen_script -- create a temporary file of type EXEC that contains
* the set of commands that we will execute. This
* is a Real Kludge to get around a problem with gsh
* ignoring it's command line flags (-c in particular).
*/
static char *
_popen_script (const char *command) {
/* the number of chars in prefix comes from "popen<pid>":
* "popen" 5
* <pid> up to 6 (for a 16-bit pid)
* null 1
*/
char prefix[12];
char *result;
FILE *fp;
int i;
#ifdef __GNO__
int oldEmulate;
#endif
sprintf(prefix, "popen%u", getpid());
if ((result = tempnam(NULL, prefix)) == NULL) {
return NULL;
}
if ((fp = fopen(result, "w")) == NULL) {
free(result);
return NULL;
}
fprintf(fp, "%s\n", command);
fclose(fp);
/* change file/aux type to SRC/EXEC */
#ifdef __GNO__
oldEmulate = _setModeEmulation (1);
#endif
i = chmod(result, S_IRWXU);
#ifdef __GNO__
_setModeEmulation(oldEmulate);
#endif
if (i == -1) {
i = errno;
free(result);
errno = i;
result = NULL;
}
return result;
}
#endif /* USE_SCRIPT */
#pragma optimize 78 /* bits 3 and 6, minimum */
#pragma debug 0
/* change this when we have an sh(1) for GNO */
#ifdef __GNO__
#define SHELL_PATH _PATH_GSHELL
#define SHELL_NAME "gsh"
#else
#define SHELL_PATH _PATH_BSHELL
#define SHELL_NAME "sh"
#endif
#ifdef USE_SCRIPT
#define SH_CFLAG
#else
#define SH_CFLAG "-c",
#error this is broken
#endif
#ifdef __GNO__
#pragma databank 1
static void
_popen_child(int *pdes, char type, char *command)
_popen_child(int *pdes, char *type, char *command)
{
if (type == 'r') {
if (pdes[1] != STDOUT_FILENO) {
(void) dup2(pdes[1], STDOUT_FILENO);
(void) close(pdes[1]);
}
(void) close(pdes[0]);
} else {
if (pdes[0] != STDIN_FILENO) {
(void) dup2(pdes[0], STDIN_FILENO);
(void) close(pdes[0]);
}
(void) close(pdes[1]);
}
char buffer[128];
strcpy(buffer, "/bin/gsh ");
strcat(buffer, command);
/* change this when we have an sh(1) for GNO */
#if 1
execl(_PATH_GSHELL, "gsh", "-c", command, (char *) 0);
if (*type == 'r') {
if (pdes[1] != STDOUT_FILENO) {
if (dup2(pdes[1], STDOUT_FILENO) == -1) {
_exit(126);
}
close(pdes[1]);
}
close(pdes[0]);
} else {
if (pdes[0] != STDIN_FILENO) {
if (dup2(pdes[0], STDIN_FILENO) == -1 ) {
_exit(126);
}
close(pdes[0]);
}
close(pdes[1]);
}
#if 0
execl(SHELL_PATH, SHELL_NAME, SH_CFLAG command, (char *) 0);
#else
execl(_PATH_BSHELL, "sh", "-c", command, (char *) 0);
_execve("/bin/gsh", buffer);
#endif
_exit(127);
{
char *p;
p = strerror(errno);
write(STDERR_FILENO, p, strlen(p));
write(STDERR_FILENO, "\r", 1);
}
_exit(127);
}
#pragma databank 0
@ -94,108 +171,181 @@ _popen_child(int *pdes, char type, char *command)
FILE *
popen(const char *command, const char *type)
{
FILE *iop;
int fds, pid;
int pdes[2];
static int pdes[2];
popenEntry_t *entry = NULL;
int err;
if (*type != 'r' && *type != 'w' || type[1]) {
/* initialize the status pointer if necessary */
if (popenHeadPtr == NULL) {
popenHeadPtr = &popenHead;
SLIST_INIT(popenHeadPtr);
}
/* verify legality of access mode */
if ((*type != 'r' && *type != 'w') || type[1]) {
errno = EINVAL;
return (NULL);
}
if (pids == NULL) {
if ((fds = getdtablesize()) <= 0) {
return (NULL);
}
if ((pids = malloc(fds * sizeof(int))) == NULL) {
return NULL;
}
bzero((char *) pids, fds * sizeof(pid_t));
}
if (pipe(pdes) < 0) {
return (NULL);
goto fail;
}
#ifdef __GNO__
pid = fork2(_popen_child, 1024, 0, "forked child of popen", 5,
pdes, *type, command);
switch (pid) {
#else
switch (pid = vfork()) {
/* get a new status element */
if ((entry = malloc(sizeof(popenEntry_t))) == NULL) {
goto fail;
}
entry->pr_fp = NULL;
entry->pr_pid = 0;
#ifdef USE_SCRIPT
entry->pr_name = NULL;
#endif
/* create the pipes */
if (pipe(pdes) < 0) {
goto fail;
}
#ifdef USE_SCRIPT
if ((command = _popen_script(command)) == NULL) {
goto fail;
}
entry->pr_name = (char *) command;
#endif
#ifdef __GNO__
entry->pr_pid = fork2(_popen_child, 4096, 0,
"forked child of popen", 6, pdes, type,
command);
switch (entry->pr_pid) {
#else
switch (entry->pr_pid = vfork()) {
#endif
case -1: /* error */
(void) close(pdes[0]);
(void) close(pdes[1]);
return (NULL);
err = errno;
close(pdes[0]);
close(pdes[1]);
errno = err;
goto fail;
/* NOTREACHED */
#ifndef __GNO__
case 0: /* child */
if (*type == 'r') {
if (pdes[1] != STDOUT_FILENO) {
(void) dup2(pdes[1], STDOUT_FILENO);
(void) close(pdes[1]);
if (dup2(pdes[1], STDOUT_FILENO) == -1) {
_exit(126);
}
close(pdes[1]);
}
(void) close(pdes[0]);
close(pdes[0]);
} else {
if (pdes[0] != STDIN_FILENO) {
(void) dup2(pdes[0], STDIN_FILENO);
(void) close(pdes[0]);
if (dup2(pdes[0], STDIN_FILENO) == -1 ) {
_exit(126);
}
close(pdes[0]);
}
(void) close(pdes[1]);
close(pdes[1]);
}
execl(_PATH_BSHELL, "sh", "-c", command, (char *) 0);
execl(SHELL_PATH, SHELL_NAME, SH_CFLAG command, (char *) 0);
_exit(127);
/* NOTREACHED */
#endif /* ! __GNO__ */
}
/* parent; assume fdopen can't fail... */
/* parent */
if (*type == 'r') {
iop = fdopen(pdes[0], type);
(void) close(pdes[1]);
if ((entry->pr_fp = fdopen(pdes[0], type)) == NULL) {
close(pdes[0]);
}
close(pdes[1]);
} else {
iop = fdopen(pdes[1], type);
(void) close(pdes[0]);
if ((entry->pr_fp = fdopen(pdes[1], type)) == NULL) {
close(pdes[1]);
}
close(pdes[0]);
}
/*
* this assert may be removed when getdtablesize is replaced by
* a kernel system call rather than just a stub returning OPEN_MAX
*/
assert(fileno(iop) < getdtablesize());
if (entry->pr_fp != NULL) {
SLIST_INSERT_HEAD(popenHeadPtr, entry, pr_link);
return entry->pr_fp;
}
/*FALLTHROUGH*/
pids[fileno(iop)] = pid;
return (iop);
fail:
/*
* OK, so goto's are passe' -- but it reduces the size of the
* generated code.
*/
if (entry) {
err = errno;
if (entry->pr_fp == NULL) {
if (entry->pr_pid > 0) {
kill(entry->pr_pid, SIGKILL);
}
}
if (entry->pr_name) {
unlink(entry->pr_name);
free(entry->pr_name);
}
free(entry);
errno = err;
}
return NULL;
}
#ifdef __GNO__
#define WAITSTAT_TYPE union wait *
#else
#define WAITSTAT_TYPE int *
#endif
/*
* pclose returns -1 if stream is not associated with a
* `popened' command, if already `pclosed', or waitpid
* returns an error.
*/
int
pclose(FILE * iop)
{
register int fdes;
int omask;
union wait pstat;
pid_t pid;
popenEntry_t *np;
if (iop == NULL || popenHeadPtr == NULL) {
errno = EINVAL;
return -1;
}
/*
* this assert may be removed when getdtablesize is replaced by
* a kernel system call rather than just a stub returning OPEN_MAX
*/
assert(fileno(iop) < getdtablesize());
/*
* pclose returns -1 if stream is not associated with a
* `popened' command, if already `pclosed', or waitpid
* returns an error.
*/
fdes = fileno(iop);
if (pids == NULL || pids[fdes] == 0) {
return (-1);
for (np = popenHeadPtr->slh_first; np != NULL;
np = np->pr_link.sle_next) {
if (np->pr_fp == iop) {
break;
}
}
(void) fclose(iop);
if (np == NULL) {
/* iop not opened by popen(3) */
errno = EINVAL;
return -1;
}
assert(np->pr_name != NULL);
assert(np->pr_name[0] != '\0');
SLIST_REMOVE(popenHeadPtr, np, popenEntry_t, pr_link);
/* Close it, and wait for the child to exit. */
fclose(iop);
#if 1
pid = wait((WAITSTAT_TYPE) &pstat);
#else
do {
pid = waitpid(pids[fdes], &pstat, 0);
pid = waitpid(np->pr_pid, (WAITSTAT_TYPE) &pstat, 0);
} while (pid == -1 && errno == EINTR);
pids[fdes] = 0;
#endif
/* clean up the structures */
#ifdef USE_SCRIPT
unlink(np->pr_name);
free(np->pr_name);
#endif
free(np);
return (pid == -1 ? -1 : pstat.w_retcode);
}