/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/*
** Copyright (c) 2010 D. Richard Hipp
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the Simplified BSD License (also
** known as the "2-Clause License" or "FreeBSD License".)
**
** This program 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.
**
** Author contact information:
** drh@hwaci.com
** http://www.hwaci.com/drh/
**
*******************************************************************************
**
** This file contains an implementation of a bi-directional popen().
*/
/*************************************************************************
This copy has been modified slightly for use in the libfossil project.
*/
#undef __STRICT_ANSI__
#include "libfossil.h" /* MUST come first b/c of config macros */
#include <errno.h>
#if FSL_PLATFORM_IS_WINDOWS
#include <windows.h>
#include <fcntl.h>
#else
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#endif
/*
The following macros are used to cast pointers to integers and
integers to pointers. The way you do this varies from one compiler
to the next, so we have developed the following set of #if statements
to generate appropriate macros for a wide range of compilers.
The correct "ANSI" way to do this is to use the intptr_t type.
Unfortunately, that typedef is not available on all compilers, or
if it is available, it requires an #include of specific headers
that vary from one machine to the next.
This code is copied out of SQLite.
*/
#if defined(__PTRDIFF_TYPE__) /* This case should work for GCC */
# define INT_TO_PTR(X) ((void*)(__PTRDIFF_TYPE__)(X))
# define PTR_TO_INT(X) ((int)(__PTRDIFF_TYPE__)(X))
#elif !defined(__GNUC__) /* Works for compilers other than LLVM */
# define INT_TO_PTR(X) ((void*)&((char*)0)[X])
# define PTR_TO_INT(X) ((int)(((char*)X)-(char*)0))
#elif defined(HAVE_STDINT_H) /* Use this case if we have ANSI headers */
# define INT_TO_PTR(X) ((void*)(intptr_t)(X))
# define PTR_TO_INT(X) ((int)(intptr_t)(X))
#else /* Generates a warning - but it always works */
# define INT_TO_PTR(X) ((void*)(X))
# define PTR_TO_INT(X) ((int)(X))
#endif
#if FSL_PLATFORM_IS_WINDOWS
/*
On windows, create a child process and specify the stdin, stdout,
and stderr channels for that process to use.
Return the number of errors.
*/
static int win32_create_child_process(
wchar_t *zCmd, /* The command that the child process will run */
HANDLE hIn, /* Standard input */
HANDLE hOut, /* Standard output */
HANDLE hErr, /* Standard error */
DWORD *pChildPid /* OUT: Child process handle */
){
STARTUPINFOW si;
PROCESS_INFORMATION pi;
BOOL rc;
memset(&si, 0, sizeof(si));
si.cb = sizeof(si);
si.dwFlags = STARTF_USESTDHANDLES;
SetHandleInformation(hIn, HANDLE_FLAG_INHERIT, TRUE);
si.hStdInput = hIn;
SetHandleInformation(hOut, HANDLE_FLAG_INHERIT, TRUE);
si.hStdOutput = hOut;
SetHandleInformation(hErr, HANDLE_FLAG_INHERIT, TRUE);
si.hStdError = hErr;
rc = CreateProcessW(
NULL, /* Application Name */
zCmd, /* Command-line */
NULL, /* Process attributes */
NULL, /* Thread attributes */
TRUE, /* Inherit Handles */
0, /* Create flags */
NULL, /* Environment */
NULL, /* Current directory */
&si, /* Startup Info */
&pi /* Process Info */
);
if( rc ){
CloseHandle( pi.hProcess );
CloseHandle( pi.hThread );
*pChildPid = pi.dwProcessId;
}else{
//fsl__fatal(FSL_RC_ERROR, "cannot create child process");
rc = FSL_RC_ERROR;
}
return rc!=0;
}
#endif
/**
Create a child process running shell command "zCmd". *ppOut gets
assigned to a FILE that becomes the standard input of the child process.
(The caller writes to *ppOut in order to send text to the child.)
*pfdIn gets assigned to the stdout from the child process. (The caller
reads from *pfdIn in order to receive input from the child.)
Note that *pfdIn is an unbuffered file descriptor, not a FILE.
The process ID of the child is written into *pChildPid.
On success the values returned via *pfdIn, *ppOut, and *pChildPid
must be passed to fsl_pclose2() to properly clean up.
Return 0 on success, non-0 on error.
*/
int fsl_popen2(const char *zCmd, int *pfdIn, FILE **ppOut, int *pChildPid){
#if FSL_PLATFORM_IS_WINDOWS
/* FIXME: port these win32_fatal_error() bits to error codes. */
HANDLE hStdinRd, hStdinWr, hStdoutRd, hStdoutWr, hStderr;
SECURITY_ATTRIBUTES saAttr;
DWORD childPid = 0;
int fd;
saAttr.nLength = sizeof(saAttr);
saAttr.bInheritHandle = TRUE;
saAttr.lpSecurityDescriptor = NULL;
hStderr = GetStdHandle(STD_ERROR_HANDLE);
if( !CreatePipe(&hStdoutRd, &hStdoutWr, &saAttr, 4096) ){
//win32_fatal_error("cannot create pipe for stdout");
return FSL_RC_IO;
}
SetHandleInformation( hStdoutRd, HANDLE_FLAG_INHERIT, FALSE);
if( !CreatePipe(&hStdinRd, &hStdinWr, &saAttr, 4096) ){
//win32_fatal_error("cannot create pipe for stdin");
return FSL_RC_IO;
}
SetHandleInformation( hStdinWr, HANDLE_FLAG_INHERIT, FALSE);
win32_create_child_process(fsl_utf8_to_unicode(zCmd),
hStdinRd, hStdoutWr, hStderr,&childPid);
*pChildPid = childPid;
*pfdIn = _open_osfhandle(PTR_TO_INT(hStdoutRd), 0);
fd = _open_osfhandle(PTR_TO_INT(hStdinWr), 0);
*ppOut = _fdopen(fd, "w");
CloseHandle(hStdinRd);
CloseHandle(hStdoutWr);
return 0;
#else
int rc;
int pin[2], pout[2];
*pfdIn = 0;
*ppOut = 0;
*pChildPid = 0;
if( pipe(pin)<0 ){
return fsl_errno_to_rc(errno, FSL_RC_ERROR);
}
if( pipe(pout)<0 ){
rc = fsl_errno_to_rc(errno, FSL_RC_ERROR);
close(pin[0]);
close(pin[1]);
return rc;
}
*pChildPid = fork();
if( *pChildPid<0 ){
rc = fsl_errno_to_rc(errno, FSL_RC_ERROR);
close(pin[0]);
close(pin[1]);
close(pout[0]);
close(pout[1]);
*pChildPid = 0;
return rc;
}
signal(SIGPIPE,SIG_IGN);
if( *pChildPid==0 ){
int fd;
/* This is the child process */
close(0);
fd = dup(pout[0]);
if( fd!=0 ) { /* nop */ };
close(pout[0]);
close(pout[1]);
close(1);
fd = dup(pin[1]);
if( fd!=1 ) { /* nop */ };
close(pin[0]);
close(pin[1]);
execl("/bin/sh", "/bin/sh", "-c", zCmd, (char*)0)
/* doesn't return on success */;
return fsl_errno_to_rc(errno, FSL_RC_ERROR);
}else{
/* This is the parent process */
close(pin[1]);
*pfdIn = pin[0];
close(pout[0]);
*ppOut = fdopen(pout[1], "w");
return 0;
}
#endif
}
/**
Close the connection to a child process previously created using
fsl_popen2(). All 3 arguments are assumed to values returned via
fsl_popen2()'s output parameters: the input file descriptor,
output FILE handle, and child process PID.
If childPid is not zero, that process is killed with SIGINT before
the I/O channels are closed.
On Windows platforms, killing of the child process is not
implemented. (Patches are welcomed.)
*/
void fsl_pclose2(int fdIn, FILE *pOut, int childPid){
#if FSL_PLATFORM_IS_WINDOWS
/* Not implemented, yet */
close(fdIn);
fclose(pOut);
#else
if(childPid>0) kill(childPid, SIGINT);
close(fdIn);
fclose(pOut);
while( waitpid(childPid>0 ? (pid_t)childPid : (pid_t)0,
NULL, WNOHANG)>0 ) {}
#endif
}
#undef PTR_TO_INT
#undef INT_TO_PTR