/* Copyright (C) 1992 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 <sys/types.h>
#include <wordexp.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>


/* We do word expansion with a pipe to the shell.
   The shell command `sh [-P] [-u] -w "words ..."' expands words.
   If -P, command substitution is an error.
   If -u, reference to an undefined variable is an error.
   The shell writes on its stdout:
   	%u\0	Number of words.
	%u\0	Number of bytes in all words together (not counting \0s).
	word1\0
	word2\0
	...
	wordN\0
   */

#define	SHELL_PATH	"/bin/sh"
#define	SHELL_NAME	"sh"


int
DEFUN(wordexp, (string, pwordexp, flags),
      CONST char *string AND wordexp_t *pwordexp AND int flags)
{
  int error;
  pid_t pid;
  int d[2];
  int status;

  FILE *f;
  size_t wordc, start, buflen;
  char *buf;

  /* Create the pipe through which we will communicate to the shell.  */
  if (pipe (d) < 0)
    return -1;

  pid = fork ();
  if (pid < 0)
    return -1;

  if (pid == 0)
    {
      /* Child.  Run the shell.  */

      CONST char *argv[5];

      close (d[STDIN_FILENO]);
      dup2 (d[STDOUT_FILENO], STDOUT_FILENO);
      if (!(flags & WRDE_SHOWERR))
	close (STDERR_FILENO);

      i = 0;
      argv[i++] = SHELL_NAME;
      if (flags & WRDE_NOCMD)
	argv[i++] = "-P";
      if (flags & WRDE_UNDEF)
	argv[i++] = "-u";
      argv[i++] = "-w";
      argv[i++] = string;
      argv[i++] = NULL;

      execv (SHELL_PATH, argv);
      _exit (WRDE_NOSPACE);
    }

  /* Parent.  */

  buf = NULL;
  error = WRDE_NOSPACE;

  close (d[STDOUT_FILENO]);
  f = fdopen (d[STDIN_FILENO]);
  if (f == NULL)
    goto lose;

  /* Read the number of words and number of bytes from the shell.  */
  if (fscanf (f, "%u", &wordc) != 1 || getc (f) != '\0' ||
      fscanf (f, "%u", &buflen) != 1 || getc (f) != '\0')
    goto lose;

  /* Read the words from the shell, and wait for it to return.  */
  buflen += wordc;
  buf = malloc (buflen);
  if (buf == NULL ||
      fread (buf, buflen, 1, f) != 1 ||
      waitpid (pid, &status, 0) != pid)
    goto lose;

  if (WIFEXITED (status))
    {
      if (WEXITSTATUS (status) != 0)
	{
	  error = WEXITSTATUS (status);
	  goto lose;
	}
    }
  else
    goto lose;

  /* Pack the structure.  */

  start = 0;
  if (flags & WRDE_DOOFFS)
    start += pwordexp->we_offs;
  if (flags & WRDE_APPEND)
    start += pwordexp->we_wordc;
  wordc = start + wordc + 1;

  if (flags & WRDE_APPEND)
    wordv = (char **) realloc ((PTR) pwordexp->we_wordv,
			       wordc * sizeof (char *));
  else
    wordv = (char **) malloc (wordc * sizeof (char *));
  if (wordv == NULL)
    goto lose;

  if (flags & WRDE_DOOFFS)
    for (i = 0; i < pwordexp->we_offs; ++i)
      wordv[i] = NULL;

  for (i = start; i < wordc; ++i)
    {
      pwordexp->we_wordv[i] = buf;
      buf = strchr (buf, '\0') + 1;
    }
  wordv[i] = NULL;

  if (flags & WRDE_REUSE)
    {
      free (pwordexp->we_wordv[0]);
      if (!(flags & WRDE_APPEND))
	free (pwordexp->we_wordv);
    }

  pwordexp->we_wordc = wordc;
  pwordexp->we_wordv = wordv;

  return 0;

 lose:
  {
    int save;
    save = errno;
    (void) kill (pid, SIGKILL);
    free (buf);
    (void) waitpid (pid, (int *) NULL, 0);
    errno = save;
    return error;
  }
}


void
DEFUN(wordexp, (pwordexp), wordexp_t *pwordexp)
{
  /* All the other elts point into the first.  */
  free (pwordexp->we_wordv[0]);
  free (pwordexp->we_wordv);
}
