/* Copyright (C) 1991, 1992, 1993 Free Software Foundation, Inc.
This file is part of the GNU C Library.

The GNU C 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.

The GNU C 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 the GNU C Library; see the file COPYING.LIB.  If
not, write to the Free Software Foundation, Inc., 675 Mass Ave,
Cambridge, MA 02139, USA.  */

#include <ansidecl.h>
#include <errno.h>
#include <stddef.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <fcntl.h>

#define	SH_PATH	"/bin/sh"	/* Shell to run.  */
#define	SH_NAME	"sh"		/* Name to give it.  */

/* Structure describing a popen child.  */
struct child
  {
    pid_t pid;			/* PID of the child.  */
    __ptr_t cookie;		/* Original cookie from fdopen.  */
    __io_functions funcs;	/* Original functions from fdopen.  */
  };

/* io_functions for pipe streams.
   These all simply call the corresponding
   original function with the original cookie.  */

#define FUNC(type, name, args)						      \
  static type DEFUN(__CONCAT(child_,name), args, __CONCAT(name,decl))	      \
  {									      \
    struct child *c = (struct child *) cookie;				      \
    {									      \
      __ptr_t cookie = c->cookie;					      \
      return (*c->funcs.__CONCAT(__,name)) args;			      \
    }									      \
  }

#define readdecl PTR cookie AND register char *buf AND register size_t n
FUNC (int, read, (cookie, buf, n))
#define writedecl PTR cookie AND register CONST char *buf AND register size_t n
FUNC (int, write, (cookie, buf, n))
#define seekdecl PTR cookie AND fpos_t *pos AND int whence
FUNC (int, seek, (cookie, pos, whence))
#define closedecl PTR cookie
FUNC (int, close, (cookie))
#define filenodecl PTR cookie
FUNC (int, fileno, (cookie))

static const __io_functions child_funcs
  = { child_read, child_write, child_seek, child_close, child_fileno };

/* Open a new stream that is a one-way pipe to a
   child process running the given shell command.  */
FILE *
DEFUN(popen, (command, mode), CONST char *command AND CONST char *mode)
{
  pid_t pid;
  int pipedes[2];
  FILE *stream;
  struct child *child;

  if (command == NULL || mode == NULL || (*mode != 'r' && *mode != 'w'))
    {
      errno = EINVAL;
      return NULL;
    }

  /* Create the pipe.  */
  if (pipe(pipedes) < 0)
    return NULL;

  /* Fork off the child.  */
  pid = __vfork ();
  if (pid == (pid_t) -1)
    {
      /* The fork failed.  */
      (void) close (pipedes[0]);
      (void) close (pipedes[1]);
      return NULL;
    }
  else if (pid == (pid_t) 0)
    {
      /* We are the child side.  Make the write side of
	 the pipe be stdin or the read side be stdout.  */

      CONST char *new_argv[4];

      if ((*mode == 'w' ? dup2(pipedes[STDIN_FILENO], STDIN_FILENO) :
	  dup2(pipedes[STDOUT_FILENO], STDOUT_FILENO)) < 0)
	_exit(127);

      /* Close the pipe descriptors.  */
      (void) close(pipedes[STDIN_FILENO]);
      (void) close(pipedes[STDOUT_FILENO]);

      /* Exec the shell.  */
      new_argv[0] = SH_NAME;
      new_argv[1] = "-c";
      new_argv[2] = command;
      new_argv[3] = NULL;
      (void) execve(SH_PATH, (char *CONST *) new_argv, environ);
      /* Die if it failed.  */
      _exit(127);
    }

  /* We are the parent side.  */

  /* Close the irrelevant side of the pipe and open the relevant side as a
     new stream.  Mark our side of the pipe to close on exec, so new children
     won't see it.  */
  if (*mode == 'r')
    {
      (void) close (pipedes[STDOUT_FILENO]);
      (void) fcntl (pipedes[STDIN_FILENO], F_SETFD, FD_CLOEXEC);
      stream = fdopen (pipedes[STDIN_FILENO], mode);
    }
  else
    {
      (void) close (pipedes[STDIN_FILENO]);
      (void) fcntl (pipedes[STDOUT_FILENO], F_SETFD, FD_CLOEXEC);
      stream = fdopen (pipedes[STDOUT_FILENO], mode);
    }

  if (stream == NULL)
    goto error;

  child = (struct child *) malloc (sizeof (struct child));
  if (child == NULL)
    goto error;

  {
    /* Make sure STREAM has its functions set before
       we try to squirrel them away in CHILD.  */
    extern void __stdio_check_funcs __P ((FILE *));
    __stdio_check_funcs (stream);
  }

  child->pid = pid;
  child->cookie = stream->__cookie;
  child->funcs = stream->__io_funcs;
  stream->__cookie = (PTR) child;
  stream->__io_funcs = child_funcs;
  stream->__ispipe = 1;
  return stream;

 error:
  {
    /* The stream couldn't be opened or the child structure couldn't be
       allocated.  Kill the child and close the other side of the pipe.  */
    int save = errno;
    (void) kill (pid, SIGKILL);
    if (stream == NULL)
      (void) close (pipedes[*mode == 'r' ? STDOUT_FILENO : STDIN_FILENO]);
    else
      (void) fclose (stream);
#ifndef	NO_WAITPID
    (void) waitpid (pid, (int *) NULL, 0);
#else
    {
      pid_t dead;
      do
	dead = wait ((int *) NULL);
      while (dead > 0 && dead != pid);
    }
#endif
    errno = save;
    return NULL;
  }
}

/* Close a stream opened by popen and return its status.
   Returns -1 if the stream was not opened by popen.  */
int
DEFUN(pclose, (stream), register FILE *stream)
{
  struct child *c;
  pid_t pid, dead;
  int status;

  if (!__validfp(stream) || !stream->__ispipe)
    {
      errno = EINVAL;
      return -1;
    }

  c = (struct child *) stream->__cookie;
  pid = c->pid;
  stream->__cookie = c->cookie;
  stream->__io_funcs = c->funcs;
  free ((PTR) c);
  stream->__ispipe = 0;
  if (fclose (stream))
    return -1;

#ifndef	NO_WAITPID
  dead = waitpid (pid, &status, 0);
#else
  do
    dead = wait (&status);
  while (dead > 0 && dead != pid);
#endif
  if (dead != pid)
    status = -1;

  return status;
}