1630 lines
		
	
	
		
			34 KiB
		
	
	
	
		
			C
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			1630 lines
		
	
	
		
			34 KiB
		
	
	
	
		
			C
		
	
	
		
			Executable File
		
	
	
	
	
/*  $Id$
 | 
						|
 | 
						|
    Part of SWI-Prolog
 | 
						|
 | 
						|
    Author:        Jan Wielemaker
 | 
						|
    E-mail:        J.Wielemaker@uva.nl
 | 
						|
    WWW:           http://www.swi-prolog.org
 | 
						|
    Copyright (C): 2008-2009, University of Amsterdam
 | 
						|
 | 
						|
    This library is free software; you can redistribute it and/or
 | 
						|
    modify it under the terms of the GNU Lesser General Public
 | 
						|
    License as published by the Free Software Foundation; either
 | 
						|
    version 2.1 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
 | 
						|
    Lesser General Public License for more details.
 | 
						|
 | 
						|
    You should have received a copy of the GNU Lesser General Public
 | 
						|
    License along with this library; if not, write to the Free Software
 | 
						|
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 | 
						|
*/
 | 
						|
 | 
						|
#ifdef HAVE_CONFIG_H
 | 
						|
#include <config.h>
 | 
						|
#endif
 | 
						|
 | 
						|
/*#define O_DEBUG 1*/
 | 
						|
#include <SWI-Stream.h>
 | 
						|
#include <SWI-Prolog.h>
 | 
						|
#include "error.h"
 | 
						|
#include <stdio.h>
 | 
						|
#include <string.h>
 | 
						|
#include <assert.h>
 | 
						|
#include <errno.h>
 | 
						|
#ifdef HAVE_SYS_TYPES_H
 | 
						|
#include <sys/types.h>
 | 
						|
#endif
 | 
						|
#ifdef HAVE_SYS_WAIT_H
 | 
						|
#include <sys/wait.h>
 | 
						|
#endif
 | 
						|
#ifdef HAVE_SYS_STAT_H
 | 
						|
#include <sys/stat.h>
 | 
						|
#endif
 | 
						|
#ifdef HAVE_FCNTL_H
 | 
						|
#include <fcntl.h>
 | 
						|
#endif
 | 
						|
 | 
						|
static atom_t ATOM_stdin;
 | 
						|
static atom_t ATOM_stdout;
 | 
						|
static atom_t ATOM_stderr;
 | 
						|
static atom_t ATOM_std;
 | 
						|
static atom_t ATOM_null;
 | 
						|
static atom_t ATOM_process;
 | 
						|
static atom_t ATOM_detached;
 | 
						|
static atom_t ATOM_cwd;
 | 
						|
static atom_t ATOM_env;
 | 
						|
static atom_t ATOM_window;
 | 
						|
static atom_t ATOM_timeout;
 | 
						|
static atom_t ATOM_release;
 | 
						|
static atom_t ATOM_infinite;
 | 
						|
static functor_t FUNCTOR_error2;
 | 
						|
static functor_t FUNCTOR_type_error2;
 | 
						|
static functor_t FUNCTOR_domain_error2;
 | 
						|
static functor_t FUNCTOR_resource_error1;
 | 
						|
static functor_t FUNCTOR_process_error2;
 | 
						|
static functor_t FUNCTOR_system_error2;
 | 
						|
static functor_t FUNCTOR_pipe1;
 | 
						|
static functor_t FUNCTOR_exit1;
 | 
						|
static functor_t FUNCTOR_killed1;
 | 
						|
static functor_t FUNCTOR_eq2;		/* =/2 */
 | 
						|
 | 
						|
#define MAYBE 2
 | 
						|
 | 
						|
#if O_DEBUG
 | 
						|
#define DEBUG(g) g
 | 
						|
#else
 | 
						|
#define DEBUG(g) (void)0
 | 
						|
#endif
 | 
						|
 | 
						|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | 
						|
ISSUES:
 | 
						|
	- Deal with child errors (no cwd, cannot execute, etc.)
 | 
						|
	- Windows version
 | 
						|
	- Complete test suite
 | 
						|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
 | 
						|
 | 
						|
 | 
						|
		 /*******************************
 | 
						|
		 *	      ERRORS		*
 | 
						|
		 *******************************/
 | 
						|
 | 
						|
static int
 | 
						|
type_error(term_t actual, const char *expected)
 | 
						|
{ term_t ex;
 | 
						|
 | 
						|
  if ( (ex=PL_new_term_ref()) &&
 | 
						|
       PL_unify_term(ex,
 | 
						|
		     PL_FUNCTOR, FUNCTOR_error2,
 | 
						|
		       PL_FUNCTOR, FUNCTOR_type_error2,
 | 
						|
		         PL_CHARS, expected,
 | 
						|
		         PL_TERM, actual,
 | 
						|
		       PL_VARIABLE) )
 | 
						|
    return PL_raise_exception(ex);
 | 
						|
 | 
						|
  return FALSE;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static int
 | 
						|
domain_error(term_t actual, const char *expected)
 | 
						|
{ term_t ex;
 | 
						|
 | 
						|
  if ( (ex=PL_new_term_ref()) &&
 | 
						|
       PL_unify_term(ex,
 | 
						|
		     PL_FUNCTOR, FUNCTOR_error2,
 | 
						|
		       PL_FUNCTOR, FUNCTOR_domain_error2,
 | 
						|
		         PL_CHARS, expected,
 | 
						|
		         PL_TERM, actual,
 | 
						|
		       PL_VARIABLE) )
 | 
						|
    return PL_raise_exception(ex);
 | 
						|
 | 
						|
  return FALSE;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static int
 | 
						|
resource_error(const char *resource)
 | 
						|
{ term_t ex;
 | 
						|
 | 
						|
  if ( (ex=PL_new_term_ref()) &&
 | 
						|
       PL_unify_term(ex,
 | 
						|
		     PL_FUNCTOR, FUNCTOR_error2,
 | 
						|
		       PL_FUNCTOR, FUNCTOR_resource_error1,
 | 
						|
		         PL_CHARS, resource,
 | 
						|
		       PL_VARIABLE) )
 | 
						|
    return PL_raise_exception(ex);
 | 
						|
 | 
						|
  return FALSE;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
		 /*******************************
 | 
						|
		 *	       ADMIN		*
 | 
						|
		 *******************************/
 | 
						|
 | 
						|
#ifdef __WINDOWS__
 | 
						|
#include <windows.h>
 | 
						|
#include <stdio.h>
 | 
						|
#include <fcntl.h>
 | 
						|
#include <io.h>
 | 
						|
#ifndef __MINGW32__
 | 
						|
typedef DWORD  pid_t;
 | 
						|
#endif
 | 
						|
typedef wchar_t echar;			/* environment character */
 | 
						|
#else
 | 
						|
typedef char echar;
 | 
						|
#endif
 | 
						|
 | 
						|
typedef enum std_type
 | 
						|
{ std_std,
 | 
						|
  std_null,
 | 
						|
  std_pipe
 | 
						|
} std_type;
 | 
						|
 | 
						|
 | 
						|
typedef struct p_stream
 | 
						|
{ term_t   term;			/* P in pipe(P) */
 | 
						|
  std_type type;			/* type of stream */
 | 
						|
#ifdef __WINDOWS__
 | 
						|
  HANDLE   fd[2];			/* pipe handles */
 | 
						|
#else
 | 
						|
  int      fd[2];			/* pipe handles */
 | 
						|
#endif
 | 
						|
} p_stream;
 | 
						|
 | 
						|
 | 
						|
typedef struct ecbuf
 | 
						|
{ echar *buffer;
 | 
						|
  size_t size;
 | 
						|
  size_t allocated;
 | 
						|
} ecbuf;
 | 
						|
 | 
						|
 | 
						|
typedef struct p_options
 | 
						|
{ atom_t exe_name;			/* exe as atom */
 | 
						|
#ifdef __WINDOWS__
 | 
						|
  wchar_t *exe;				/* Executable */
 | 
						|
  wchar_t *cmdline;			/* Command line */
 | 
						|
  wchar_t *cwd;				/* CWD of new process */
 | 
						|
#else
 | 
						|
  char *exe;				/* Executable */
 | 
						|
  char **argv;				/* argument vector */
 | 
						|
  char *cwd;				/* CWD of new process */
 | 
						|
  char **envp;				/* New environment */
 | 
						|
#endif
 | 
						|
  ecbuf  envbuf;			/* environment buffer */
 | 
						|
  term_t pid;				/* process(PID) */
 | 
						|
  int pipes;				/* #pipes found */
 | 
						|
  p_stream streams[3];
 | 
						|
  int   detached;			/* create as detached */
 | 
						|
  int   window;				/* Show a window? */
 | 
						|
} p_options;
 | 
						|
 | 
						|
 | 
						|
typedef struct wait_options
 | 
						|
{ double timeout;
 | 
						|
  int	 has_timeout;
 | 
						|
  int	 release;
 | 
						|
} wait_options;
 | 
						|
 | 
						|
 | 
						|
#ifdef __WINDOWS__
 | 
						|
static int win_command_line(term_t t, int arity,
 | 
						|
			    const wchar_t *exepath, wchar_t **cmdline);
 | 
						|
#endif
 | 
						|
 | 
						|
		 /*******************************
 | 
						|
		 *	  STRING BUFFER		*
 | 
						|
		 *******************************/
 | 
						|
 | 
						|
static void
 | 
						|
free_ecbuf(ecbuf *b)
 | 
						|
{ if ( b->buffer )
 | 
						|
  { PL_free(b->buffer);
 | 
						|
    b->buffer = NULL;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static int
 | 
						|
add_ecbuf(ecbuf *b, echar *data, size_t len)
 | 
						|
{ if ( b->size + len > b->allocated )
 | 
						|
  { size_t newsize = (b->allocated ? b->allocated * 2 : 2048);
 | 
						|
 | 
						|
    while( b->size + len > newsize )
 | 
						|
      newsize *= 2;
 | 
						|
 | 
						|
    if ( b->buffer )
 | 
						|
    { b->buffer = PL_realloc(b->buffer, newsize*sizeof(echar));
 | 
						|
    } else
 | 
						|
    { b->buffer = PL_malloc(newsize*sizeof(echar));
 | 
						|
    }
 | 
						|
 | 
						|
    b->allocated = newsize;
 | 
						|
  }
 | 
						|
 | 
						|
  memcpy(b->buffer+b->size, data, len*sizeof(echar));
 | 
						|
  b->size += len;
 | 
						|
 | 
						|
  return TRUE;
 | 
						|
}
 | 
						|
 | 
						|
		 /*******************************
 | 
						|
		 *	ENVIRONMENT PARSING	*
 | 
						|
		 *******************************/
 | 
						|
 | 
						|
static int
 | 
						|
get_echars_arg_ex(int i, term_t from, term_t arg, echar **sp, size_t *lenp)
 | 
						|
{ const echar *s, *e;
 | 
						|
 | 
						|
  if ( !PL_get_arg(i, from, arg) )
 | 
						|
    return FALSE;
 | 
						|
 | 
						|
#ifdef __WINDOWS__
 | 
						|
  if ( !PL_get_wchars(arg, lenp, sp,
 | 
						|
		      CVT_ATOMIC|CVT_EXCEPTION) )
 | 
						|
#else
 | 
						|
  if ( !PL_get_nchars(arg, lenp, sp,
 | 
						|
		      CVT_ATOMIC|CVT_EXCEPTION|REP_FN) )
 | 
						|
#endif
 | 
						|
    return FALSE;
 | 
						|
 | 
						|
  for(s = *sp, e = s+*lenp; s<e; s++)
 | 
						|
  { if ( !*s )
 | 
						|
      return domain_error(arg, "text_non_zero_code");
 | 
						|
  }
 | 
						|
 | 
						|
  return TRUE;
 | 
						|
}
 | 
						|
 | 
						|
#ifdef __WINDOWS__
 | 
						|
#define ECHARS(s) L##s
 | 
						|
#else
 | 
						|
#define ECHARS(s) s
 | 
						|
#endif
 | 
						|
 | 
						|
static int
 | 
						|
parse_environment(term_t t, p_options *info)
 | 
						|
{ term_t tail = PL_copy_term_ref(t);
 | 
						|
  term_t head = PL_new_term_ref();
 | 
						|
  term_t tmp  = PL_new_term_ref();
 | 
						|
  ecbuf *eb   = &info->envbuf;
 | 
						|
  int count = 0, c = 0;
 | 
						|
#ifndef __WINDOWS__
 | 
						|
  echar *q;
 | 
						|
  char **ep;
 | 
						|
#endif
 | 
						|
 | 
						|
  assert(eb->size == 0);
 | 
						|
  assert(eb->allocated == 0);
 | 
						|
  assert(eb->buffer == NULL);
 | 
						|
 | 
						|
  while( PL_get_list(tail, head, tail) )
 | 
						|
  { echar *s;
 | 
						|
    size_t len;
 | 
						|
 | 
						|
    if ( !PL_is_functor(head, FUNCTOR_eq2) )
 | 
						|
      return type_error(head, "environment_variable");
 | 
						|
 | 
						|
    if ( !get_echars_arg_ex(1, head, tmp, &s, &len) )
 | 
						|
      return FALSE;
 | 
						|
    add_ecbuf(eb, s, len);
 | 
						|
    add_ecbuf(eb, ECHARS("="), 1);
 | 
						|
    if ( !get_echars_arg_ex(2, head, tmp, &s, &len) )
 | 
						|
      return FALSE;
 | 
						|
    add_ecbuf(eb, s, len);
 | 
						|
    add_ecbuf(eb, ECHARS("\0"), 1);
 | 
						|
 | 
						|
    count++;
 | 
						|
  }
 | 
						|
 | 
						|
  if ( !PL_get_nil(tail) )
 | 
						|
    return type_error(tail, "list");
 | 
						|
 | 
						|
#ifdef __WINDOWS__
 | 
						|
  add_ecbuf(eb, ECHARS("\0"), 1);
 | 
						|
#else
 | 
						|
  info->envp = PL_malloc((count+1)*sizeof(char*));
 | 
						|
 | 
						|
  for(ep=info->envp, c=0, q=eb->buffer; c<count; c++, ep++)
 | 
						|
  { *ep = q;
 | 
						|
    q += strlen(q)+1;
 | 
						|
  }
 | 
						|
  assert((size_t)(q-eb->buffer) == eb->size);
 | 
						|
  *ep = NULL;
 | 
						|
#endif
 | 
						|
 | 
						|
  return TRUE;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static int
 | 
						|
get_stream(term_t t, p_options *info, p_stream *stream)
 | 
						|
{ atom_t a;
 | 
						|
 | 
						|
  if ( PL_get_atom(t, &a) )
 | 
						|
  { if ( a == ATOM_null )
 | 
						|
    { stream->type = std_null;
 | 
						|
      return TRUE;
 | 
						|
    } else if ( a == ATOM_std )
 | 
						|
    { stream->type = std_std;
 | 
						|
      return TRUE;
 | 
						|
    } else
 | 
						|
    { return domain_error(t, "process_stream");
 | 
						|
    }
 | 
						|
  } else if ( PL_is_functor(t, FUNCTOR_pipe1) )
 | 
						|
  { stream->term = PL_new_term_ref();
 | 
						|
    _PL_get_arg(1, t, stream->term);
 | 
						|
    stream->type = std_pipe;
 | 
						|
    info->pipes++;
 | 
						|
    return TRUE;
 | 
						|
  } else
 | 
						|
    return type_error(t, "process_stream");
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static int
 | 
						|
parse_options(term_t options, p_options *info)
 | 
						|
{ term_t tail = PL_copy_term_ref(options);
 | 
						|
  term_t head = PL_new_term_ref();
 | 
						|
  term_t arg = PL_new_term_ref();
 | 
						|
 | 
						|
  info->window = MAYBE;
 | 
						|
 | 
						|
  while(PL_get_list(tail, head, tail))
 | 
						|
  { atom_t name;
 | 
						|
    int arity;
 | 
						|
 | 
						|
    if ( !PL_get_name_arity(head, &name, &arity) || arity != 1 )
 | 
						|
      return type_error(head, "option");
 | 
						|
    _PL_get_arg(1, head, arg);
 | 
						|
 | 
						|
    if ( name == ATOM_stdin )
 | 
						|
    { if ( !get_stream(arg, info, &info->streams[0]) )
 | 
						|
	return FALSE;
 | 
						|
    } else if ( name == ATOM_stdout )
 | 
						|
    { if ( !get_stream(arg, info, &info->streams[1]) )
 | 
						|
	return FALSE;
 | 
						|
    } else if ( name == ATOM_stderr )
 | 
						|
    { if ( !get_stream(arg, info, &info->streams[2]) )
 | 
						|
	return FALSE;
 | 
						|
    } else if ( name == ATOM_process )
 | 
						|
    { info->pid = PL_copy_term_ref(arg);
 | 
						|
    } else if ( name == ATOM_detached )
 | 
						|
    { if ( !PL_get_bool(arg, &info->detached) )
 | 
						|
	return type_error(arg, "boolean");
 | 
						|
    } else if ( name == ATOM_cwd )
 | 
						|
    {
 | 
						|
#ifdef __WINDOWS__
 | 
						|
      if ( !PL_get_wchars(arg, NULL, &info->cwd,
 | 
						|
			 CVT_ATOM|CVT_STRING|CVT_EXCEPTION|BUF_MALLOC) )
 | 
						|
	return FALSE;
 | 
						|
#else
 | 
						|
      if ( !PL_get_chars(arg, &info->cwd,
 | 
						|
			 CVT_ATOM|CVT_STRING|CVT_EXCEPTION|BUF_MALLOC|REP_FN) )
 | 
						|
	return FALSE;
 | 
						|
#endif
 | 
						|
    } else if ( name == ATOM_window )
 | 
						|
    { if ( !PL_get_bool(arg, &info->window) )
 | 
						|
	return type_error(arg, "boolean");
 | 
						|
    } else if ( name == ATOM_env )
 | 
						|
    { if ( !parse_environment(arg, info) )
 | 
						|
	return FALSE;
 | 
						|
    } else
 | 
						|
      return domain_error(head, "process_option");
 | 
						|
  }
 | 
						|
 | 
						|
  if ( !PL_get_nil(tail) )
 | 
						|
    return type_error(tail, "list");
 | 
						|
 | 
						|
  return TRUE;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static int
 | 
						|
get_exe(term_t exe, p_options *info)
 | 
						|
{ int arity;
 | 
						|
  term_t arg = PL_new_term_ref();
 | 
						|
 | 
						|
  if ( !PL_get_name_arity(exe, &info->exe_name, &arity) )
 | 
						|
    return type_error(exe, "callable");
 | 
						|
 | 
						|
  PL_put_atom(arg, info->exe_name);
 | 
						|
 | 
						|
#ifdef __WINDOWS__
 | 
						|
  if ( !PL_get_wchars(arg, NULL, &info->exe, CVT_ATOM|CVT_EXCEPTION|BUF_MALLOC) )
 | 
						|
    return FALSE;
 | 
						|
  if ( !win_command_line(exe, arity, info->exe, &info->cmdline) )
 | 
						|
    return FALSE;
 | 
						|
#else /*__WINDOWS__*/
 | 
						|
  if ( !PL_get_chars(arg, &info->exe, CVT_ATOM|CVT_EXCEPTION|BUF_MALLOC|REP_FN) )
 | 
						|
    return FALSE;
 | 
						|
 | 
						|
  info->argv = PL_malloc((arity+2)*sizeof(char*));
 | 
						|
  memset(info->argv, 0, (arity+2)*sizeof(char*));
 | 
						|
  info->argv[0] = strdup(info->exe);
 | 
						|
  { int i;
 | 
						|
 | 
						|
    for(i=1; i<=arity; i++)
 | 
						|
    { _PL_get_arg(i, exe, arg);
 | 
						|
 | 
						|
      if ( !PL_get_chars(arg, &info->argv[i],
 | 
						|
			 CVT_ATOMIC|CVT_EXCEPTION|BUF_MALLOC|REP_FN) )
 | 
						|
	return FALSE;
 | 
						|
    }
 | 
						|
    info->argv[i] = NULL;
 | 
						|
  }
 | 
						|
#endif /*__WINDOWS__*/
 | 
						|
 | 
						|
  return TRUE;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static void
 | 
						|
free_options(p_options *info)		/* TBD: close streams */
 | 
						|
{ if ( info->exe )
 | 
						|
  { PL_free(info->exe);
 | 
						|
    info->exe = NULL;
 | 
						|
  }
 | 
						|
  if ( info->cwd )
 | 
						|
  { PL_free(info->cwd);
 | 
						|
    info->cwd = NULL;
 | 
						|
  }
 | 
						|
#ifndef __WINDOWS__
 | 
						|
  if ( info->envp )
 | 
						|
  { PL_free(info->envp);
 | 
						|
    info->envp = NULL;
 | 
						|
  }
 | 
						|
#endif
 | 
						|
  free_ecbuf(&info->envbuf);
 | 
						|
#ifdef __WINDOWS__
 | 
						|
  if ( info->cmdline )
 | 
						|
  { PL_free(info->cmdline);
 | 
						|
    info->cmdline = NULL;
 | 
						|
  }
 | 
						|
 | 
						|
#else /*__WINDOWS__*/
 | 
						|
 | 
						|
  if ( info->argv )
 | 
						|
  { char **a;
 | 
						|
    for(a=info->argv; *a; a++)
 | 
						|
    { if ( *a )
 | 
						|
	PL_free(*a);
 | 
						|
    }
 | 
						|
    PL_free(info->argv);
 | 
						|
 | 
						|
    info->argv = NULL;
 | 
						|
  }
 | 
						|
 | 
						|
#endif /*__WINDOWS__*/
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
		 /*******************************
 | 
						|
		 *	   PROCESS READS	*
 | 
						|
		 *******************************/
 | 
						|
 | 
						|
#define	PROCESS_MAGIC	0x29498001
 | 
						|
 | 
						|
typedef struct process_context
 | 
						|
{ int	magic;				/* PROCESS_MAGIC */
 | 
						|
#ifdef __WINDOWS__
 | 
						|
  HANDLE handle;			/* process handle */
 | 
						|
#else
 | 
						|
  pid_t	pid;				/* the process id */
 | 
						|
#endif
 | 
						|
  int   open_mask;			/* Open streams */
 | 
						|
  int   pipes[3];			/* stdin/stdout/stderr */
 | 
						|
} process_context;
 | 
						|
 | 
						|
static int wait_for_process(process_context *pc);
 | 
						|
 | 
						|
static int
 | 
						|
process_fd(void *handle, process_context **PC)
 | 
						|
{ process_context *pc = (process_context*) ((uintptr_t)handle&~(uintptr_t)0x3);
 | 
						|
  int pipe = (int)(uintptr_t)handle & 0x3;
 | 
						|
 | 
						|
  if ( pc->magic == PROCESS_MAGIC )
 | 
						|
  { if ( PC )
 | 
						|
      *PC = pc;
 | 
						|
    return pc->pipes[pipe];
 | 
						|
  }
 | 
						|
 | 
						|
  return -1;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static ssize_t
 | 
						|
Sread_process(void *handle, char *buf, size_t size)
 | 
						|
{ int fd = process_fd(handle, NULL);
 | 
						|
 | 
						|
  return (*Sfilefunctions.read)((void*)(uintptr_t)fd, buf, size);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static ssize_t
 | 
						|
Swrite_process(void *handle, char *buf, size_t size)
 | 
						|
{ int fd = process_fd(handle, NULL);
 | 
						|
 | 
						|
  return (*Sfilefunctions.write)((void*)(uintptr_t)fd, buf, size);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static int
 | 
						|
Sclose_process(void *handle)
 | 
						|
{ process_context *pc;
 | 
						|
  int fd = process_fd(handle, &pc);
 | 
						|
 | 
						|
  if ( fd >= 0 )
 | 
						|
  { int which = (int)(uintptr_t)handle & 0x3;
 | 
						|
    int rc;
 | 
						|
 | 
						|
    rc = (*Sfilefunctions.close)((void*)(uintptr_t)fd);
 | 
						|
    pc->open_mask &= ~(1<<which);
 | 
						|
 | 
						|
    DEBUG(Sdprintf("Closing fd[%d]; mask = 0x%x\n", which, pc->open_mask));
 | 
						|
 | 
						|
    if ( !pc->open_mask )
 | 
						|
    { int rcw = wait_for_process(pc);
 | 
						|
 | 
						|
      return rcw ? 0 : -1;
 | 
						|
    }
 | 
						|
 | 
						|
    return rc;
 | 
						|
  }
 | 
						|
 | 
						|
  return -1;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static int
 | 
						|
Scontrol_process(void *handle, int action, void *arg)
 | 
						|
{ process_context *pc;
 | 
						|
  int fd = process_fd(handle, &pc);
 | 
						|
 | 
						|
  switch(action)
 | 
						|
  { case SIO_GETFILENO:
 | 
						|
    { int *fdp = arg;
 | 
						|
      *fdp = fd;
 | 
						|
      return 0;
 | 
						|
    }
 | 
						|
    default:
 | 
						|
      return (*Sfilefunctions.control)((void*)(uintptr_t)fd, action, arg);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static IOFUNCTIONS Sprocessfunctions =
 | 
						|
{ Sread_process,
 | 
						|
  Swrite_process,
 | 
						|
  NULL,					/* seek */
 | 
						|
  Sclose_process,
 | 
						|
  Scontrol_process,
 | 
						|
  NULL					/* seek64 */
 | 
						|
};
 | 
						|
 | 
						|
 | 
						|
static IOSTREAM *
 | 
						|
#ifdef __WINDOWS__
 | 
						|
open_process_pipe(process_context *pc, int which, HANDLE fd)
 | 
						|
#else
 | 
						|
open_process_pipe(process_context *pc, int which, int fd)
 | 
						|
#endif
 | 
						|
{ void *handle;
 | 
						|
  int flags;
 | 
						|
 | 
						|
  pc->open_mask |= (1<<which);
 | 
						|
#ifdef __WINDOWS__
 | 
						|
  pc->pipes[which] = _open_osfhandle((long)fd, _O_BINARY);
 | 
						|
#else
 | 
						|
  pc->pipes[which] = fd;
 | 
						|
#endif
 | 
						|
 | 
						|
#define ISO_FLAGS (SIO_RECORDPOS|SIO_FBUF|SIO_TEXT)
 | 
						|
 | 
						|
  if ( which == 0 )
 | 
						|
    flags = SIO_OUTPUT|ISO_FLAGS;
 | 
						|
  else
 | 
						|
    flags = SIO_INPUT|ISO_FLAGS;
 | 
						|
 | 
						|
  handle = (void *)((uintptr_t)pc | (uintptr_t)which);
 | 
						|
 | 
						|
  return Snew(handle, flags, &Sprocessfunctions);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
		 /*******************************
 | 
						|
		 *	       OS STUFF		*
 | 
						|
		 *******************************/
 | 
						|
 | 
						|
 | 
						|
#ifdef __WINDOWS__
 | 
						|
 | 
						|
CRITICAL_SECTION process_lock;
 | 
						|
#define LOCK()   EnterCriticalSection(&process_lock);
 | 
						|
#define UNLOCK() LeaveCriticalSection(&process_lock);
 | 
						|
 | 
						|
static void
 | 
						|
win_init()
 | 
						|
{ InitializeCriticalSection(&process_lock);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static atom_t
 | 
						|
WinError()
 | 
						|
{ int id = GetLastError();
 | 
						|
  char *msg;
 | 
						|
  static WORD lang;
 | 
						|
  static lang_initialised = 0;
 | 
						|
 | 
						|
  if ( !lang_initialised )
 | 
						|
    lang = MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_UK);
 | 
						|
 | 
						|
again:
 | 
						|
  if ( FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|
 | 
						|
		     FORMAT_MESSAGE_IGNORE_INSERTS|
 | 
						|
		     FORMAT_MESSAGE_FROM_SYSTEM,
 | 
						|
		     NULL,			/* source */
 | 
						|
		     id,			/* identifier */
 | 
						|
		     lang,
 | 
						|
		     (LPTSTR) &msg,
 | 
						|
		     0,				/* size */
 | 
						|
		     NULL) )			/* arguments */
 | 
						|
  { atom_t a = PL_new_atom(msg);
 | 
						|
 | 
						|
    LocalFree(msg);
 | 
						|
    lang_initialised = 1;
 | 
						|
 | 
						|
    return a;
 | 
						|
  } else
 | 
						|
  { if ( lang_initialised == 0 )
 | 
						|
    { lang = MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT);
 | 
						|
      lang_initialised = 1;
 | 
						|
      goto again;
 | 
						|
    }
 | 
						|
 | 
						|
    return PL_new_atom("Unknown Windows error");
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static int
 | 
						|
win_error(const char *op)
 | 
						|
{ atom_t msg = WinError();
 | 
						|
  term_t ex = PL_new_term_ref();
 | 
						|
 | 
						|
  PL_unify_term(ex, PL_FUNCTOR, FUNCTOR_error2,
 | 
						|
		      PL_FUNCTOR, FUNCTOR_system_error2,
 | 
						|
		        PL_CHARS, op,
 | 
						|
		        PL_ATOM, msg,
 | 
						|
		      PL_VARIABLE);
 | 
						|
 | 
						|
  return PL_raise_exception(ex);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
typedef struct arg_string
 | 
						|
{ size_t  len;
 | 
						|
  wchar_t *text;
 | 
						|
  wchar_t quote;
 | 
						|
} arg_string;
 | 
						|
 | 
						|
#define QMISC	0x1
 | 
						|
#define QDBLQ	0x2
 | 
						|
#define QSBLQ	0x4
 | 
						|
 | 
						|
static int
 | 
						|
set_quote(arg_string *as)
 | 
						|
{ int needq = 0;
 | 
						|
  const wchar_t *s = as->text;
 | 
						|
 | 
						|
  for(; *s; s++)
 | 
						|
  { if ( !iswalnum(*s) )
 | 
						|
    { if ( *s == '"' )
 | 
						|
	needq |= QDBLQ;
 | 
						|
      else if ( *s == '\'' )
 | 
						|
	needq |= QSBLQ;
 | 
						|
      else
 | 
						|
	needq |= QMISC;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  if ( !needq )
 | 
						|
  { as->quote = 0;
 | 
						|
    return TRUE;
 | 
						|
  }
 | 
						|
  needq &= ~QMISC;
 | 
						|
  switch( needq )
 | 
						|
  { case QDBLQ:
 | 
						|
      as->quote = '\'';
 | 
						|
      return TRUE;
 | 
						|
    case 0:
 | 
						|
    case QSBLQ:
 | 
						|
      as->quote = '"';
 | 
						|
      return TRUE;
 | 
						|
    default:
 | 
						|
      return FALSE;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static int
 | 
						|
win_command_line(term_t t, int arity, const wchar_t *exe, wchar_t **cline)
 | 
						|
{ if ( arity > 0 )
 | 
						|
  { arg_string *av = PL_malloc((arity+1)*sizeof(*av));
 | 
						|
    term_t arg = PL_new_term_ref();
 | 
						|
    size_t cmdlen;
 | 
						|
    wchar_t *cmdline, *o;
 | 
						|
    const wchar_t *b;
 | 
						|
    int i;
 | 
						|
 | 
						|
    if ( (b=wcsrchr(exe, '\\')) )
 | 
						|
      b++;
 | 
						|
    else
 | 
						|
      b = exe;
 | 
						|
    av[0].text = (wchar_t*)b;
 | 
						|
    av[0].len = wcslen(av[0].text);
 | 
						|
    set_quote(&av[0]);
 | 
						|
    cmdlen = av[0].len+(av[0].quote?2:0)+1;
 | 
						|
 | 
						|
    for( i=1; i<=arity; i++)
 | 
						|
    { PL_get_arg(i, t, arg);
 | 
						|
 | 
						|
      if ( !PL_get_wchars(arg, &av[i].len, &av[i].text,
 | 
						|
			  CVT_ATOMIC|CVT_EXCEPTION|BUF_MALLOC) )
 | 
						|
	return FALSE;
 | 
						|
 | 
						|
      if ( wcslen(av[i].text) != av[i].len )
 | 
						|
	return domain_error(arg, "no_zero_code_atom");
 | 
						|
 | 
						|
      if ( !set_quote(&av[i]) )
 | 
						|
	return domain_error(arg, "dos_quotable_atom");
 | 
						|
 | 
						|
      cmdlen += av[i].len+(av[i].quote?2:0)+1;
 | 
						|
    }
 | 
						|
 | 
						|
    cmdline = PL_malloc(cmdlen*sizeof(wchar_t));
 | 
						|
    for( o=cmdline,i=0; i<=arity; )
 | 
						|
    { wchar_t *s = av[i].text;
 | 
						|
 | 
						|
      if ( av[i].quote )
 | 
						|
	*o++ = av[i].quote;
 | 
						|
      wcsncpy(o, s, av[i].len);
 | 
						|
      o += av[i].len;
 | 
						|
      if ( i > 0 )
 | 
						|
	PL_free(s);			/* do not free shared exename */
 | 
						|
      if ( av[i].quote )
 | 
						|
	*o++ = av[i].quote;
 | 
						|
 | 
						|
      if (++i <= arity)
 | 
						|
	*o++ = ' ';
 | 
						|
    }
 | 
						|
    *o = 0;
 | 
						|
    PL_free(av);
 | 
						|
 | 
						|
    *cline = cmdline;
 | 
						|
  } else
 | 
						|
  { *cline = NULL;
 | 
						|
  }
 | 
						|
 | 
						|
  return TRUE;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
typedef struct win_process
 | 
						|
{ DWORD pid;
 | 
						|
  HANDLE handle;
 | 
						|
  struct win_process *next;
 | 
						|
} win_process;
 | 
						|
 | 
						|
 | 
						|
static win_process *processes;
 | 
						|
 | 
						|
static void
 | 
						|
register_process(DWORD pid, HANDLE h)
 | 
						|
{ win_process *wp = PL_malloc(sizeof(*wp));
 | 
						|
 | 
						|
  wp->pid = pid;
 | 
						|
  wp->handle = h;
 | 
						|
  LOCK();
 | 
						|
  wp->next = processes;
 | 
						|
  processes = wp;
 | 
						|
  UNLOCK();
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static int
 | 
						|
unregister_process(DWORD pid)
 | 
						|
{ win_process **wpp, *wp;
 | 
						|
 | 
						|
  LOCK();
 | 
						|
  for(wpp=&processes, wp=*wpp; wp; wpp=&wp->next, wp=*wpp)
 | 
						|
  { if ( wp->pid == pid )
 | 
						|
    { *wpp = wp->next;
 | 
						|
      PL_free(wp);
 | 
						|
      UNLOCK();
 | 
						|
      return TRUE;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  UNLOCK();
 | 
						|
  return FALSE;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static HANDLE
 | 
						|
find_process_from_pid(DWORD pid, const char *pred)
 | 
						|
{ win_process *wp;
 | 
						|
 | 
						|
  LOCK();
 | 
						|
  for(wp=processes; wp; wp=wp->next)
 | 
						|
  { if ( wp->pid == pid )
 | 
						|
    { HANDLE h = wp->handle;
 | 
						|
      UNLOCK();
 | 
						|
      return h;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  UNLOCK();
 | 
						|
 | 
						|
  if ( pred )
 | 
						|
  { term_t ex = PL_new_term_ref();
 | 
						|
 | 
						|
    PL_put_integer(ex, pid);
 | 
						|
    pl_error(NULL, 2, NULL, ERR_EXISTENCE,
 | 
						|
	     "process", ex);
 | 
						|
  }
 | 
						|
 | 
						|
  return (HANDLE)0;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
#define WP_TIMEOUT 2
 | 
						|
 | 
						|
static int
 | 
						|
wait_process_handle(HANDLE process, ULONG *rc, DWORD timeout)
 | 
						|
{ DWORD wc;
 | 
						|
 | 
						|
retry:
 | 
						|
  wc = MsgWaitForMultipleObjects(1,
 | 
						|
				 &process,
 | 
						|
				 FALSE,	/* return on any event */
 | 
						|
				 timeout,
 | 
						|
				 QS_ALLINPUT);
 | 
						|
 | 
						|
  switch(wc)
 | 
						|
  { case WAIT_OBJECT_0:
 | 
						|
      if ( !GetExitCodeProcess(process, rc) )
 | 
						|
      { win_error("GetExitCodeProcess");
 | 
						|
	CloseHandle(process);
 | 
						|
	return FALSE;
 | 
						|
      }
 | 
						|
      CloseHandle(process);
 | 
						|
      return TRUE;
 | 
						|
    case WAIT_OBJECT_0+1:
 | 
						|
    { MSG msg;
 | 
						|
 | 
						|
      while( PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) )
 | 
						|
      { TranslateMessage(&msg);
 | 
						|
	DispatchMessage(&msg);
 | 
						|
	if ( PL_handle_signals() < 0 )
 | 
						|
	  return FALSE;
 | 
						|
      }
 | 
						|
      goto retry;
 | 
						|
    }
 | 
						|
    case WAIT_TIMEOUT:
 | 
						|
      return WP_TIMEOUT;
 | 
						|
    default:
 | 
						|
      win_error("WaitForSingleObject");
 | 
						|
      CloseHandle(process);
 | 
						|
      return FALSE;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static int
 | 
						|
wait_for_pid(pid_t pid, term_t code, wait_options *opts)
 | 
						|
{ HANDLE *h;
 | 
						|
 | 
						|
  if ( (h=find_process_from_pid(pid, "process_wait")) )
 | 
						|
  { ULONG rc;
 | 
						|
    DWORD timeout;
 | 
						|
    int wc;
 | 
						|
 | 
						|
    if ( opts->has_timeout )
 | 
						|
      timeout = (DWORD)(opts->timeout * 1000.0);
 | 
						|
    else
 | 
						|
      timeout = INFINITE;
 | 
						|
 | 
						|
    if ( !(wc=wait_process_handle(h, &rc, timeout)) )
 | 
						|
      return FALSE;
 | 
						|
    if ( wc == WP_TIMEOUT )
 | 
						|
      return PL_unify_atom(code, ATOM_timeout);
 | 
						|
 | 
						|
    unregister_process(pid);
 | 
						|
 | 
						|
    return PL_unify_term(code,
 | 
						|
			 PL_FUNCTOR, FUNCTOR_exit1,
 | 
						|
			   PL_LONG, rc);
 | 
						|
  } else
 | 
						|
  { return FALSE;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static int
 | 
						|
wait_for_process(process_context *pc)
 | 
						|
{ int rc;
 | 
						|
  ULONG prc;
 | 
						|
 | 
						|
  rc = wait_process_handle(pc->handle, &prc, INFINITE);
 | 
						|
  CloseHandle(pc->handle);
 | 
						|
  PL_free(pc);
 | 
						|
 | 
						|
  return rc;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static int
 | 
						|
win_wait_success(atom_t exe, HANDLE process)
 | 
						|
{ ULONG rc;
 | 
						|
 | 
						|
  if ( !wait_process_handle(process, &rc, INFINITE) )
 | 
						|
    return FALSE;
 | 
						|
 | 
						|
  if ( rc != 0 )
 | 
						|
  { term_t code = PL_new_term_ref();
 | 
						|
    term_t ex = PL_new_term_ref();
 | 
						|
 | 
						|
    PL_unify_term(ex,
 | 
						|
		  PL_FUNCTOR, FUNCTOR_error2,
 | 
						|
		    PL_FUNCTOR, FUNCTOR_process_error2,
 | 
						|
		      PL_ATOM, exe,
 | 
						|
		      PL_FUNCTOR, FUNCTOR_exit1,
 | 
						|
		        PL_LONG, rc,
 | 
						|
		    PL_VARIABLE);
 | 
						|
    return PL_raise_exception(ex);
 | 
						|
  }
 | 
						|
 | 
						|
  return TRUE;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static int
 | 
						|
create_pipes(p_options *info)
 | 
						|
{ int i;
 | 
						|
  SECURITY_ATTRIBUTES sa;
 | 
						|
 | 
						|
  sa.nLength = sizeof(sa);          /* Length in bytes */
 | 
						|
  sa.bInheritHandle = 1;            /* the child must inherit these handles */
 | 
						|
  sa.lpSecurityDescriptor = NULL;
 | 
						|
 | 
						|
  for(i=0; i<3; i++)
 | 
						|
  { p_stream *s = &info->streams[i];
 | 
						|
 | 
						|
    if ( s->term )
 | 
						|
    { if ( !CreatePipe(&s->fd[0], &s->fd[1], &sa, 1<<13) )
 | 
						|
      { return win_error("CreatePipe");
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  return TRUE;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static IOSTREAM *
 | 
						|
Sopen_handle(HANDLE h, const char *mode)
 | 
						|
{ return Sfdopen(_open_osfhandle((long)h, _O_BINARY), mode);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static HANDLE
 | 
						|
open_null_stream(DWORD access)
 | 
						|
{ SECURITY_ATTRIBUTES sa;
 | 
						|
 | 
						|
  sa.nLength = sizeof(sa);          /* Length in bytes */
 | 
						|
  sa.bInheritHandle = 1;            /* the child must inherit these handles */
 | 
						|
  sa.lpSecurityDescriptor = NULL;
 | 
						|
 | 
						|
  return CreateFile("nul",
 | 
						|
		    access,
 | 
						|
		    FILE_SHARE_READ|FILE_SHARE_WRITE,
 | 
						|
		    &sa,		/* security */
 | 
						|
		    OPEN_EXISTING,
 | 
						|
		    0,
 | 
						|
		    NULL);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static int
 | 
						|
console_app(void)
 | 
						|
{ HANDLE h;
 | 
						|
 | 
						|
  if ( (h = GetStdHandle(STD_OUTPUT_HANDLE)) != INVALID_HANDLE_VALUE )
 | 
						|
  { DWORD mode;
 | 
						|
 | 
						|
    if ( GetConsoleMode(h, &mode) )
 | 
						|
      return TRUE;
 | 
						|
  }
 | 
						|
 | 
						|
  return FALSE;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static int
 | 
						|
do_create_process(p_options *info)
 | 
						|
{ int flags = 0;
 | 
						|
  PROCESS_INFORMATION pi;
 | 
						|
  STARTUPINFOW si;
 | 
						|
 | 
						|
  switch(info->window)
 | 
						|
  { case MAYBE:
 | 
						|
      if ( !console_app() )
 | 
						|
	flags |= CREATE_NO_WINDOW;
 | 
						|
      break;
 | 
						|
    case TRUE:
 | 
						|
      break;
 | 
						|
    case FALSE:
 | 
						|
      flags |= CREATE_NO_WINDOW;
 | 
						|
      break;
 | 
						|
  }
 | 
						|
 | 
						|
  memset(&si, 0, sizeof(si));
 | 
						|
  si.cb = sizeof(si);
 | 
						|
  si.dwFlags = STARTF_USESTDHANDLES;
 | 
						|
 | 
						|
				      /* stdin */
 | 
						|
  switch( info->streams[0].type )
 | 
						|
  { case std_pipe:
 | 
						|
      si.hStdInput = info->streams[0].fd[0];
 | 
						|
      SetHandleInformation(info->streams[0].fd[1],
 | 
						|
			   HANDLE_FLAG_INHERIT, FALSE);
 | 
						|
      break;
 | 
						|
    case std_null:
 | 
						|
      si.hStdInput = open_null_stream(GENERIC_READ);
 | 
						|
      break;
 | 
						|
    case std_std:
 | 
						|
      si.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
 | 
						|
      break;
 | 
						|
  }
 | 
						|
				      /* stdout */
 | 
						|
  switch( info->streams[1].type )
 | 
						|
  { case std_pipe:
 | 
						|
      si.hStdOutput = info->streams[1].fd[1];
 | 
						|
      SetHandleInformation(info->streams[1].fd[0],
 | 
						|
			   HANDLE_FLAG_INHERIT, FALSE);
 | 
						|
      break;
 | 
						|
    case std_null:
 | 
						|
      si.hStdOutput = open_null_stream(GENERIC_WRITE);
 | 
						|
      break;
 | 
						|
    case std_std:
 | 
						|
      si.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
 | 
						|
      break;
 | 
						|
  }
 | 
						|
				      /* stderr */
 | 
						|
  switch( info->streams[2].type )
 | 
						|
  { case std_pipe:
 | 
						|
      si.hStdError = info->streams[2].fd[1];
 | 
						|
      SetHandleInformation(info->streams[2].fd[0],
 | 
						|
			   HANDLE_FLAG_INHERIT, FALSE);
 | 
						|
      break;
 | 
						|
    case std_null:
 | 
						|
      si.hStdError = open_null_stream(GENERIC_WRITE);
 | 
						|
      break;
 | 
						|
    case std_std:
 | 
						|
      si.hStdError = GetStdHandle(STD_ERROR_HANDLE);
 | 
						|
      break;
 | 
						|
  }
 | 
						|
 | 
						|
  if ( CreateProcessW(info->exe,
 | 
						|
		      info->cmdline,
 | 
						|
		      NULL,		/* Process security */
 | 
						|
		      NULL,		/* Thread security */
 | 
						|
		      TRUE,		/* Inherit handles */
 | 
						|
		      flags,		/* Creation flags */
 | 
						|
		      info->envbuf.buffer, /* Environment */
 | 
						|
		      info->cwd,	/* Directory */
 | 
						|
		      &si,		/* Startup info */
 | 
						|
		      &pi) )		/* Process information */
 | 
						|
  { CloseHandle(pi.hThread);
 | 
						|
 | 
						|
    if ( info->pipes > 0 && info->pid == 0 )
 | 
						|
    { IOSTREAM *s;
 | 
						|
      process_context *pc = PL_malloc(sizeof(*pc));
 | 
						|
 | 
						|
      DEBUG(Sdprintf("Wait on pipes\n"));
 | 
						|
 | 
						|
      memset(pc, 0, sizeof(*pc));
 | 
						|
      pc->magic  = PROCESS_MAGIC;
 | 
						|
      pc->handle = pi.hProcess;
 | 
						|
 | 
						|
      if ( info->streams[0].type == std_pipe )
 | 
						|
      { CloseHandle(info->streams[0].fd[0]);
 | 
						|
	s = open_process_pipe(pc, 0, info->streams[0].fd[1]);
 | 
						|
	PL_unify_stream(info->streams[0].term, s);
 | 
						|
      }
 | 
						|
      if ( info->streams[1].type == std_pipe )
 | 
						|
      { CloseHandle(info->streams[1].fd[1]);
 | 
						|
	s = open_process_pipe(pc, 1, info->streams[1].fd[0]);
 | 
						|
	PL_unify_stream(info->streams[1].term, s);
 | 
						|
      }
 | 
						|
      if ( info->streams[2].type == std_pipe )
 | 
						|
      { CloseHandle(info->streams[2].fd[1]);
 | 
						|
	s = open_process_pipe(pc, 2, info->streams[2].fd[0]);
 | 
						|
	PL_unify_stream(info->streams[2].term, s);
 | 
						|
      }
 | 
						|
 | 
						|
      return TRUE;
 | 
						|
    } else if ( info->pipes > 0 )
 | 
						|
    { IOSTREAM *s;
 | 
						|
 | 
						|
      if ( info->streams[0].type == std_pipe )
 | 
						|
      { CloseHandle(info->streams[0].fd[0]);
 | 
						|
	s = Sopen_handle(info->streams[0].fd[1], "w");
 | 
						|
	PL_unify_stream(info->streams[0].term, s);
 | 
						|
      }
 | 
						|
      if ( info->streams[1].type == std_pipe )
 | 
						|
      { CloseHandle(info->streams[1].fd[1]);
 | 
						|
	s = Sopen_handle(info->streams[1].fd[0], "r");
 | 
						|
	PL_unify_stream(info->streams[1].term, s);
 | 
						|
      }
 | 
						|
      if ( info->streams[2].type == std_pipe )
 | 
						|
      { CloseHandle(info->streams[2].fd[1]);
 | 
						|
	s = Sopen_handle(info->streams[2].fd[0], "r");
 | 
						|
	PL_unify_stream(info->streams[2].term, s);
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    if ( info->pid )
 | 
						|
    { register_process(pi.dwProcessId, pi.hProcess);
 | 
						|
      return PL_unify_integer(info->pid, pi.dwProcessId);
 | 
						|
    }
 | 
						|
 | 
						|
    return win_wait_success(info->exe_name, pi.hProcess);
 | 
						|
  } else
 | 
						|
  { return win_error("CreateProcess");
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
#else /*__WINDOWS__*/
 | 
						|
 | 
						|
static int
 | 
						|
create_pipes(p_options *info)
 | 
						|
{ int i;
 | 
						|
 | 
						|
  for(i=0; i<3; i++)
 | 
						|
  { p_stream *s = &info->streams[i];
 | 
						|
 | 
						|
    if ( s->term )
 | 
						|
    { if ( pipe(s->fd) )
 | 
						|
      { assert(errno = EMFILE);
 | 
						|
	return resource_error("open_files");
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  return TRUE;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static int
 | 
						|
unify_exit_status(term_t code, int status)
 | 
						|
{ if ( WIFEXITED(status) )
 | 
						|
  { return PL_unify_term(code,
 | 
						|
			 PL_FUNCTOR, FUNCTOR_exit1,
 | 
						|
			   PL_INT, (int)WEXITSTATUS(status));
 | 
						|
  } else if ( WIFSIGNALED(status) )
 | 
						|
  { return PL_unify_term(code,
 | 
						|
			 PL_FUNCTOR, FUNCTOR_killed1,
 | 
						|
			   PL_INT, (int)WTERMSIG(status));
 | 
						|
  } else
 | 
						|
  { assert(0);
 | 
						|
    return FALSE;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static int
 | 
						|
wait_for_pid(pid_t pid, term_t code, wait_options *opts)
 | 
						|
{ pid_t p2;
 | 
						|
  int status;
 | 
						|
 | 
						|
  if ( opts->has_timeout && opts->timeout == 0.0 )
 | 
						|
  { if ( (p2=waitpid(pid, &status, WNOHANG)) == pid )
 | 
						|
      return unify_exit_status(code, status);
 | 
						|
    else if ( p2 == 0 )
 | 
						|
      return PL_unify_atom(code, ATOM_timeout);
 | 
						|
    else
 | 
						|
    { term_t PID;
 | 
						|
 | 
						|
    error:
 | 
						|
      return ((PID = PL_new_term_ref()) &&
 | 
						|
	      PL_put_integer(PID, pid) &&
 | 
						|
	      pl_error(NULL, 0, "waitpid", ERR_ERRNO,
 | 
						|
		       errno, "wait", "process", PID));
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  for(;;)
 | 
						|
  { if ( (p2=waitpid(pid, &status, 0)) == pid )
 | 
						|
      return unify_exit_status(code, status);
 | 
						|
 | 
						|
    if ( p2 == -1 && errno == EINTR )
 | 
						|
    { if ( PL_handle_signals() < 0 )
 | 
						|
	return FALSE;
 | 
						|
    } else
 | 
						|
    { goto error;
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static int
 | 
						|
wait_for_process(process_context *pc)
 | 
						|
{ for(;;)
 | 
						|
  { int status;
 | 
						|
    pid_t p2;
 | 
						|
 | 
						|
    if ( (p2=waitpid(pc->pid, &status, 0)) == pc->pid )
 | 
						|
    { PL_free(pc);
 | 
						|
      return TRUE;
 | 
						|
    }
 | 
						|
 | 
						|
    if ( errno == EINTR && PL_handle_signals() >= 0 )
 | 
						|
      continue;
 | 
						|
 | 
						|
    PL_free(pc);
 | 
						|
    return FALSE;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static int
 | 
						|
wait_success(atom_t name, pid_t pid)
 | 
						|
{ pid_t p2;
 | 
						|
 | 
						|
  for(;;)
 | 
						|
  { int status;
 | 
						|
 | 
						|
    if ( (p2=waitpid(pid, &status, 0)) == pid )
 | 
						|
    { if ( WIFEXITED(status) && WEXITSTATUS(status) == 0 )
 | 
						|
      { return TRUE;
 | 
						|
      } else
 | 
						|
      { term_t code, ex;
 | 
						|
 | 
						|
	if ( (code = PL_new_term_ref()) &&
 | 
						|
	     (ex = PL_new_term_ref()) &&
 | 
						|
	     unify_exit_status(code, status) &&
 | 
						|
	     PL_unify_term(ex,
 | 
						|
			   PL_FUNCTOR, FUNCTOR_error2,
 | 
						|
			     PL_FUNCTOR, FUNCTOR_process_error2,
 | 
						|
			       PL_ATOM, name,
 | 
						|
			       PL_TERM, code,
 | 
						|
			     PL_VARIABLE) )
 | 
						|
	  return PL_raise_exception(ex);
 | 
						|
	return FALSE;
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    if ( p2 == -1 && errno == EINTR )
 | 
						|
    { if ( PL_handle_signals() < 0 )
 | 
						|
	return FALSE;
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static int
 | 
						|
do_create_process(p_options *info)
 | 
						|
{ int pid;
 | 
						|
 | 
						|
  if ( !(pid=fork()) )			/* child */
 | 
						|
  { int fd;
 | 
						|
    int rc;
 | 
						|
 | 
						|
    PL_cleanup_fork();
 | 
						|
 | 
						|
    if ( info->detached )
 | 
						|
      setsid();
 | 
						|
 | 
						|
    if ( info->cwd )
 | 
						|
    { if ( chdir(info->cwd) )
 | 
						|
      { perror(info->cwd);
 | 
						|
	exit(1);
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
					/* stdin */
 | 
						|
    switch( info->streams[0].type )
 | 
						|
    { case std_pipe:
 | 
						|
	dup2(info->streams[0].fd[0], 0);
 | 
						|
	close(info->streams[0].fd[1]);
 | 
						|
	break;
 | 
						|
      case std_null:
 | 
						|
	if ( (fd = open("/dev/null", O_RDONLY)) >= 0 )
 | 
						|
	  dup2(fd, 0);
 | 
						|
        break;
 | 
						|
      case std_std:
 | 
						|
	break;
 | 
						|
    }
 | 
						|
					/* stdout */
 | 
						|
    switch( info->streams[1].type )
 | 
						|
    { case std_pipe:
 | 
						|
	dup2(info->streams[1].fd[1], 1);
 | 
						|
        close(info->streams[1].fd[0]);
 | 
						|
	break;
 | 
						|
      case std_null:
 | 
						|
	if ( (fd = open("/dev/null", O_WRONLY)) >= 0 )
 | 
						|
	  dup2(fd, 1);
 | 
						|
        break;
 | 
						|
      case std_std:
 | 
						|
	break;
 | 
						|
    }
 | 
						|
					/* stderr */
 | 
						|
    switch( info->streams[2].type )
 | 
						|
    { case std_pipe:
 | 
						|
	dup2(info->streams[2].fd[1], 2);
 | 
						|
        close(info->streams[2].fd[0]);
 | 
						|
	break;
 | 
						|
      case std_null:
 | 
						|
	if ( (fd = open("/dev/null", O_WRONLY)) >= 0 )
 | 
						|
	  dup2(fd, 2);
 | 
						|
        break;
 | 
						|
      case std_std:
 | 
						|
	break;
 | 
						|
    }
 | 
						|
 | 
						|
    if ( info->envp )
 | 
						|
      rc = execve(info->exe, info->argv, info->envp);
 | 
						|
    else
 | 
						|
      rc = execv(info->exe, info->argv);
 | 
						|
 | 
						|
    if ( rc )
 | 
						|
    { perror(info->exe);
 | 
						|
      exit(1);
 | 
						|
    }
 | 
						|
 | 
						|
    { term_t exe = PL_new_term_ref();
 | 
						|
      PL_put_atom_chars(exe, info->exe);
 | 
						|
 | 
						|
      return pl_error(NULL, 0, "execv", ERR_ERRNO, errno, "exec", "process", exe);
 | 
						|
    }
 | 
						|
  } else				/* parent */
 | 
						|
  { if ( info->pipes > 0 && info->pid == 0 )
 | 
						|
    { IOSTREAM *s;
 | 
						|
      process_context *pc = PL_malloc(sizeof(*pc));
 | 
						|
 | 
						|
      DEBUG(Sdprintf("Wait on pipes\n"));
 | 
						|
 | 
						|
      memset(pc, 0, sizeof(*pc));
 | 
						|
      pc->magic = PROCESS_MAGIC;
 | 
						|
      pc->pid = pid;
 | 
						|
 | 
						|
      if ( info->streams[0].type == std_pipe )
 | 
						|
      { close(info->streams[0].fd[0]);
 | 
						|
	s = open_process_pipe(pc, 0, info->streams[0].fd[1]);
 | 
						|
	PL_unify_stream(info->streams[0].term, s);
 | 
						|
      }
 | 
						|
      if ( info->streams[1].type == std_pipe )
 | 
						|
      { close(info->streams[1].fd[1]);
 | 
						|
	s = open_process_pipe(pc, 1, info->streams[1].fd[0]);
 | 
						|
	PL_unify_stream(info->streams[1].term, s);
 | 
						|
      }
 | 
						|
      if ( info->streams[2].type == std_pipe )
 | 
						|
      { close(info->streams[2].fd[1]);
 | 
						|
	s = open_process_pipe(pc, 2, info->streams[2].fd[0]);
 | 
						|
	PL_unify_stream(info->streams[2].term, s);
 | 
						|
      }
 | 
						|
 | 
						|
      return TRUE;
 | 
						|
    } else if ( info->pipes > 0 )
 | 
						|
    { IOSTREAM *s;
 | 
						|
 | 
						|
      if ( info->streams[0].type == std_pipe )
 | 
						|
      { close(info->streams[0].fd[0]);
 | 
						|
	s = Sfdopen(info->streams[0].fd[1], "w");
 | 
						|
	PL_unify_stream(info->streams[0].term, s);
 | 
						|
      }
 | 
						|
      if ( info->streams[1].type == std_pipe )
 | 
						|
      { close(info->streams[1].fd[1]);
 | 
						|
	s = Sfdopen(info->streams[1].fd[0], "r");
 | 
						|
	PL_unify_stream(info->streams[1].term, s);
 | 
						|
      }
 | 
						|
      if ( info->streams[2].type == std_pipe )
 | 
						|
      { close(info->streams[2].fd[1]);
 | 
						|
	s = Sfdopen(info->streams[2].fd[0], "r");
 | 
						|
	PL_unify_stream(info->streams[2].term, s);
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    if ( info->pid )
 | 
						|
      return PL_unify_integer(info->pid, pid);
 | 
						|
 | 
						|
    return wait_success(info->exe_name, pid);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
#endif /*__WINDOWS__*/
 | 
						|
 | 
						|
 | 
						|
		 /*******************************
 | 
						|
		 *	      BINDING		*
 | 
						|
		 *******************************/
 | 
						|
 | 
						|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | 
						|
Basic process creation interface takes
 | 
						|
 | 
						|
	* Exe file
 | 
						|
	* List of arguments
 | 
						|
	* standard streams		% std, null, pipe(S)
 | 
						|
	* Working directory
 | 
						|
	* detached			% Unix
 | 
						|
	* window			% Windows
 | 
						|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
 | 
						|
 | 
						|
static foreign_t
 | 
						|
process_create(term_t exe, term_t options)
 | 
						|
{ p_options info;
 | 
						|
  int rc = FALSE;
 | 
						|
 | 
						|
  memset(&info, 0, sizeof(info));
 | 
						|
 | 
						|
  if ( !get_exe(exe, &info) )
 | 
						|
    goto out;
 | 
						|
  if ( !parse_options(options, &info) )
 | 
						|
    goto out;
 | 
						|
  if ( !create_pipes(&info) )
 | 
						|
    goto out;
 | 
						|
 | 
						|
  rc = do_create_process(&info);
 | 
						|
 | 
						|
out:
 | 
						|
  free_options(&info);
 | 
						|
 | 
						|
  return rc;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static int
 | 
						|
get_pid(term_t pid, pid_t *p)
 | 
						|
{ int n;
 | 
						|
 | 
						|
  if ( !PL_get_integer(pid, &n) )
 | 
						|
    return type_error(pid, "integer");
 | 
						|
  if ( n < 0 )
 | 
						|
    return domain_error(pid, "not_less_than_zero");
 | 
						|
 | 
						|
  *p = n;
 | 
						|
  return TRUE;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static foreign_t
 | 
						|
process_wait(term_t pid, term_t code, term_t options)
 | 
						|
{ pid_t p;
 | 
						|
  wait_options opts;
 | 
						|
  term_t tail = PL_copy_term_ref(options);
 | 
						|
  term_t head = PL_new_term_ref();
 | 
						|
  term_t arg  = PL_new_term_ref();
 | 
						|
 | 
						|
  if ( !get_pid(pid, &p) )
 | 
						|
    return FALSE;
 | 
						|
 | 
						|
  memset(&opts, 0, sizeof(opts));
 | 
						|
  while(PL_get_list(tail, head, tail))
 | 
						|
  { atom_t name;
 | 
						|
    int arity;
 | 
						|
 | 
						|
    if ( !PL_get_name_arity(head, &name, &arity) || arity != 1 )
 | 
						|
      return type_error(head, "option");
 | 
						|
    _PL_get_arg(1, head, arg);
 | 
						|
    if ( name == ATOM_timeout )
 | 
						|
    { atom_t a;
 | 
						|
 | 
						|
      if ( !(PL_get_atom(arg, &a) && a == ATOM_infinite) )
 | 
						|
      { if ( !PL_get_float(arg, &opts.timeout) )
 | 
						|
	  return type_error(arg, "timeout");
 | 
						|
	opts.has_timeout = TRUE;
 | 
						|
      }
 | 
						|
    } else if ( name == ATOM_release )
 | 
						|
    { if ( !PL_get_bool(arg, &opts.release) )
 | 
						|
	return type_error(arg, "boolean");
 | 
						|
      if ( opts.release == FALSE )
 | 
						|
	return domain_error(arg, "true");
 | 
						|
    } else
 | 
						|
      return domain_error(head, "process_wait_option");
 | 
						|
  }
 | 
						|
  if ( !PL_get_nil(tail) )
 | 
						|
    return type_error(tail, "list");
 | 
						|
 | 
						|
  return wait_for_pid(p, code, &opts);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static foreign_t
 | 
						|
process_kill(term_t pid, term_t signal)
 | 
						|
{ int p;
 | 
						|
 | 
						|
  if ( !get_pid(pid, &p) )
 | 
						|
    return FALSE;
 | 
						|
 | 
						|
{
 | 
						|
#ifdef __WINDOWS__
 | 
						|
  HANDLE h;
 | 
						|
 | 
						|
  if ( !(h=find_process_from_pid(p, "process_kill")) )
 | 
						|
    return FALSE;
 | 
						|
 | 
						|
  if ( TerminateProcess(h, 255) )
 | 
						|
    return TRUE;
 | 
						|
 | 
						|
  return win_error("TerminateProcess");
 | 
						|
#else /*__WINDOWS__*/
 | 
						|
  int sig;
 | 
						|
 | 
						|
  if ( !PL_get_signum_ex(signal, &sig) )
 | 
						|
    return FALSE;
 | 
						|
 | 
						|
  if ( kill(p, sig) == 0 )
 | 
						|
    return TRUE;
 | 
						|
 | 
						|
  switch(errno)
 | 
						|
  { case EPERM:
 | 
						|
      return pl_error("process_kill", 2, NULL, ERR_PERMISSION,
 | 
						|
		      pid, "kill", "process");
 | 
						|
    case ESRCH:
 | 
						|
      return pl_error("process_kill", 2, NULL, ERR_EXISTENCE,
 | 
						|
		      "process", pid);
 | 
						|
    default:
 | 
						|
      return pl_error("process_kill", 2, "kill", ERR_ERRNO, errno, "kill", "process", pid);
 | 
						|
  }
 | 
						|
#endif /*__WINDOWS__*/
 | 
						|
}
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
#define MKATOM(n) ATOM_ ## n = PL_new_atom(#n)
 | 
						|
#define MKFUNCTOR(n,a) FUNCTOR_ ## n ## a = PL_new_functor(PL_new_atom(#n), a)
 | 
						|
 | 
						|
install_t
 | 
						|
install_process()
 | 
						|
{
 | 
						|
#ifdef __WINDOWS__
 | 
						|
  win_init();
 | 
						|
#endif
 | 
						|
 | 
						|
  MKATOM(stdin);
 | 
						|
  MKATOM(stdout);
 | 
						|
  MKATOM(stderr);
 | 
						|
  MKATOM(std);
 | 
						|
  MKATOM(null);
 | 
						|
  MKATOM(process);
 | 
						|
  MKATOM(detached);
 | 
						|
  MKATOM(cwd);
 | 
						|
  MKATOM(env);
 | 
						|
  MKATOM(window);
 | 
						|
  MKATOM(timeout);
 | 
						|
  MKATOM(release);
 | 
						|
  MKATOM(infinite);
 | 
						|
 | 
						|
  MKFUNCTOR(pipe, 1);
 | 
						|
  MKFUNCTOR(error, 2);
 | 
						|
  MKFUNCTOR(type_error, 2);
 | 
						|
  MKFUNCTOR(domain_error, 2);
 | 
						|
  MKFUNCTOR(process_error, 2);
 | 
						|
  MKFUNCTOR(system_error, 2);
 | 
						|
  MKFUNCTOR(resource_error, 1);
 | 
						|
  MKFUNCTOR(exit, 1);
 | 
						|
  MKFUNCTOR(killed, 1);
 | 
						|
 | 
						|
  FUNCTOR_eq2 = PL_new_functor(PL_new_atom("="), 2);
 | 
						|
 | 
						|
  PL_register_foreign("process_create", 2, process_create, 0);
 | 
						|
  PL_register_foreign("process_wait", 3, process_wait, 0);
 | 
						|
  PL_register_foreign("process_kill", 2, process_kill, 0);
 | 
						|
}
 |