// pipe.cpp -- somewhat portable pipe implementation that resembles POSIX popen()/pclose()
#include "common.h"
// definitions used in this module only
#define RD 0
#define WR 1
#ifdef _WIN32
typedef struct pipe_s
{
PROCESS_INFORMATION procinfo;
int descriptors[2]; // RD and WR
} pipe_t;
#endif // _WIN32
// function prototypes
FILE *pipe_open (const wchar_t *shell_command, const wchar_t *mode);
int pipe_close (FILE *stream);
int pipe_isalive (FILE *stream);
int pipe_hasdata (FILE *stream);
int pipe_read (FILE *stream, void *dstbuf, int nbytes);
int pipe_write (FILE *stream, void *srcbuf, int nbytes);
FILE *pipe_open (const wchar_t *shell_command, const wchar_t *mode)
{
// wrapper around popen(), and simple reimplementation of it for systems that don't have it
// mode shall be "r", "w", or "r+"
#ifdef _WIN32
#define IT_TO_ME 0
#define ME_TO_IT 1
SECURITY_ATTRIBUTES security_attributes;
wchar_t current_path[MAX_PATH];
wchar_t *command_pathname;
STARTUPINFO startup_info;
HANDLE hStdIO[2][2];
pipe_t *pipe;
(void) mode; // all pipes are open read-write
command_pathname = (wchar_t *) malloc ((MAX_PATH + 4096) * sizeof (wchar_t)); // should be enough
if (command_pathname == NULL)
return (NULL);
// figure out command pathname out of full command string
wcscpy_s (command_pathname, MAX_PATH + 4096, shell_command);
if ((command_pathname[0] == L'"') && (wcschr (&command_pathname[1], L'"') != NULL))
{
memmove (command_pathname, &command_pathname[1], (MAX_PATH + 4096 - 1) * sizeof (wchar_t));
*wcschr (&command_pathname[1], L'"') = 0;
}
else if (wcschr (command_pathname, L' ') != NULL)
*wcschr (command_pathname, L' ') = 0;
// build the bidirectional I/O pipe
pipe = (pipe_t *) malloc (sizeof (pipe_t));
memset (pipe, 0, sizeof (pipe_t));
memset (&security_attributes, 0, sizeof (security_attributes)); // prepare the pipes' security attributes
security_attributes.nLength = sizeof (SECURITY_ATTRIBUTES);
security_attributes.bInheritHandle = true; // set the bInheritHandle flag so pipe handles are inherited
hStdIO[0][0] = hStdIO[0][1] = hStdIO[1][0] = hStdIO[1][1] = NULL;
CreatePipe (&hStdIO[ME_TO_IT][RD], &hStdIO[ME_TO_IT][WR], &security_attributes, 0); // create a pipe for the child process's stdin
CreatePipe (&hStdIO[IT_TO_ME][RD], &hStdIO[IT_TO_ME][WR], &security_attributes, 0); // create a pipe for the child process's stdout
SetHandleInformation (hStdIO[ME_TO_IT][WR], HANDLE_FLAG_INHERIT, 0); // ensure the write handle to the pipe for STDIN is not inherited
SetHandleInformation (hStdIO[IT_TO_ME][RD], HANDLE_FLAG_INHERIT, 0); // ensure the read handle to the pipe for STDOUT is not inherited
pipe->descriptors[RD] = _open_osfhandle ((intptr_t) hStdIO[IT_TO_ME][RD], _O_BINARY);
pipe->descriptors[WR] = _open_osfhandle ((intptr_t) hStdIO[ME_TO_IT][WR], _O_BINARY | _O_APPEND);
// spawn the child process with redirected input and output
memset (&startup_info, 0, sizeof (startup_info));
startup_info.cb = sizeof (STARTUPINFOA);
startup_info.dwFlags = STARTF_USESTDHANDLES;
startup_info.hStdInput = hStdIO[ME_TO_IT][RD];
startup_info.hStdOutput = hStdIO[IT_TO_ME][WR];
startup_info.hStdError = hStdIO[IT_TO_ME][WR];
GetCurrentDirectory (WCHAR_SIZEOF (current_path), current_path);
if (!CreateProcess (command_pathname, (wchar_t *) shell_command, NULL, NULL, true, CREATE_NO_WINDOW | DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP, NULL, current_path, &startup_info, &pipe->procinfo))
{
pipe_close ((FILE *) pipe);
free (command_pathname);
return (NULL);
}
// close the handles we won't use, free the command pathname memory space and return a pointer to our pipe object
CloseHandle (hStdIO[IT_TO_ME][WR]);
CloseHandle (hStdIO[ME_TO_IT][RD]);
#undef IT_TO_ME
#undef ME_TO_IT
free (command_pathname);
return ((FILE *) pipe);
#else // !_WIN32
return (popen (shell_command, mode));
#endif // _WIN32
}
int pipe_close (FILE *stream)
{
// wrapper around pclose(), and simple reimplementation of it for systems that don't have it
#ifdef _WIN32
HANDLE hRemoteThread;
HANDLE hProcessDup;
DWORD handle_flags;
DWORD thread_id;
DWORD exit_code;
pipe_t *pipe;
bool was_duplicated;
pipe = (pipe_t *) stream; // cast FILE * back into our own structure
// close all handles
if (pipe->descriptors[RD] != -1) _close (pipe->descriptors[RD]); pipe->descriptors[RD] = -1;
if (pipe->descriptors[WR] != -1) _close (pipe->descriptors[WR]); pipe->descriptors[WR] = -1;
// terminate process if it hasn't terminated by itself yet
exit_code = 0;
if (GetExitCodeProcess (pipe->procinfo.hProcess, &exit_code) && (exit_code == STILL_ACTIVE))
{
// taken from Dr. Dobbs. How to terminate any process cleanly: create a remote thread in it,
// and make its start address point right into kernel32's ExitProcess() function...
was_duplicated = (DuplicateHandle (GetCurrentProcess (), pipe->procinfo.hProcess, GetCurrentProcess (), &hProcessDup, PROCESS_ALL_ACCESS, FALSE, 0) != 0);
if (!was_duplicated)
hProcessDup = pipe->procinfo.hProcess;
hRemoteThread = CreateRemoteThread (hProcessDup, NULL, 0, (PTHREAD_START_ROUTINE) GetProcAddress (GetModuleHandle (L"Kernel32"), "ExitProcess"), (void *) 0, 0, &thread_id);
if (hRemoteThread != NULL)
{
WaitForSingleObject (hProcessDup, INFINITE); // must wait process to terminate to guarantee that it has exited
CloseHandle (hRemoteThread);
}
else
TerminateProcess (pipe->procinfo.hProcess, 0); // if process refuses our remote thread, kill it
if (was_duplicated)
CloseHandle (hProcessDup);
}
// close process handles
if (pipe->procinfo.hProcess != NULL)
CloseHandle (pipe->procinfo.hProcess);
if (GetHandleInformation (pipe->procinfo.hThread, &handle_flags))
CloseHandle (pipe->procinfo.hThread);
// and finally free the pipe structure
free (pipe);
return ((int) exit_code);
#else // !_WIN32
return (pclose (stream));
#endif // _WIN32
}
int pipe_isalive (FILE *stream)
{
// wrapper around ioctl() on pipes for systems that don't have it
#ifdef _WIN32
DWORD exit_code;
DWORD handle_flags;
if (((pipe_t *) stream)->procinfo.hProcess == 0)
return (1); // if the child process has not been fully started yet, assume the pipe is still alive
else if (((pipe_t *) stream)->procinfo.hProcess == INVALID_HANDLE_VALUE)
return (0); // if the child process handle has been explicitly marked invalid, assume it's already dead
else if (GetHandleInformation (((pipe_t *) stream)->procinfo.hProcess, &handle_flags) == 0)
{
((pipe_t *) stream)->procinfo.hProcess = INVALID_HANDLE_VALUE;
return (0); // if the process handle is no longer valid but its handle is not marked as such, assume the child is dead
}
else if (GetExitCodeProcess (((pipe_t *) stream)->procinfo.hProcess, &exit_code) == 0)
return (1); // if GetExitCodeProcess() failed, *conservatively* assume the child is still alive
return (exit_code == STILL_ACTIVE);
#else // !_WIN32
return (1); // UNIX doesn't provide a portable way for this
#endif // _WIN32
}
int pipe_hasdata (FILE *stream)
{
// wrapper around ioctl() on pipes for systems that don't have it
#ifdef _WIN32
return (!_eof (((pipe_t *) stream)->descriptors[RD]));
#else // !_WIN32
return (feof (stream));
#endif // _WIN32
}
int pipe_read (FILE *stream, void *dstbuf, int nbytes)
{
// wrapper around fread() on pipes for systems that don't support it
#ifdef _WIN32
return (_read (((pipe_t *) stream)->descriptors[RD], dstbuf, nbytes));
#else // !_WIN32
return (fread (dstbuf, 1, nbytes, stream));
#endif // _WIN32
}
int pipe_write (FILE *stream, void *srcbuf, int nbytes)
{
// wrapper around fwrite() on pipes for systems that don't support it
#ifdef _WIN32
return (_write (((pipe_t *) stream)->descriptors[WR], srcbuf, nbytes));
#else // !_WIN32
return (fwrite (srcbuf, 1, nbytes, stream));
#endif // _WIN32
}