Subversion Repositories Games.Chess Giants

Rev

Rev 177 | Blame | Compare with Previous | Last modification | View Log | Download | RSS feed

  1. // pipe.cpp -- somewhat portable pipe implementation that resembles POSIX popen()/pclose()
  2.  
  3. #include "common.h"
  4.  
  5.  
  6. // definitions used in this module only
  7. #define RD 0
  8. #define WR 1
  9.  
  10.  
  11. #ifdef _WIN32
  12. typedef struct pipe_s
  13. {
  14.    PROCESS_INFORMATION procinfo;
  15.    int descriptors[2]; // RD and WR
  16. } pipe_t;
  17. #endif // _WIN32
  18.  
  19.  
  20. // function prototypes
  21. FILE *pipe_open (const wchar_t *shell_command, const wchar_t *mode);
  22. int pipe_close (FILE *stream);
  23. int pipe_isalive (FILE *stream, int64_t *exit_code);
  24. int pipe_hasdata (FILE *stream);
  25. int pipe_read (FILE *stream, void *dstbuf, int nbytes);
  26. int pipe_write (FILE *stream, void *srcbuf, int nbytes);
  27.  
  28.  
  29. FILE *pipe_open (const wchar_t *shell_command, const wchar_t *mode)
  30. {
  31.    // wrapper around popen(), and simple reimplementation of it for systems that don't have it
  32.    // mode shall be "r", "w", or "r+"
  33.  
  34. #ifdef _WIN32
  35.    #define IT_TO_ME 0
  36.    #define ME_TO_IT 1
  37.    SECURITY_ATTRIBUTES security_attributes;
  38.    wchar_t current_path[MAX_PATH];
  39.    wchar_t *command_pathname;
  40.    STARTUPINFO startup_info;
  41.    HANDLE hStdIO[2][2];
  42.    pipe_t *pipe;
  43.  
  44.    (void) mode; // all pipes are open read-write
  45.  
  46.    command_pathname = (wchar_t *) malloc ((MAX_PATH + 4096) * sizeof (wchar_t)); // should be enough
  47.    if (command_pathname == NULL)
  48.       return (NULL);
  49.  
  50.    // figure out command pathname out of full command string
  51.    wcscpy_s (command_pathname, MAX_PATH + 4096, shell_command);
  52.    if ((command_pathname[0] == L'"') && (wcschr (&command_pathname[1], L'"') != NULL))
  53.    {
  54.       memmove (command_pathname, &command_pathname[1], (MAX_PATH + 4096 - 1) * sizeof (wchar_t));
  55.       *wcschr (&command_pathname[1], L'"') = 0;
  56.    }
  57.    else if (wcschr (command_pathname, L' ') != NULL)
  58.       *wcschr (command_pathname, L' ') = 0;
  59.  
  60.    // build the bidirectional I/O pipe
  61.    pipe = (pipe_t *) malloc (sizeof (pipe_t));
  62.    memset (pipe, 0, sizeof (pipe_t));
  63.    memset (&security_attributes, 0, sizeof (security_attributes)); // prepare the pipes' security attributes
  64.    security_attributes.nLength = sizeof (SECURITY_ATTRIBUTES);
  65.    security_attributes.bInheritHandle = true; // set the bInheritHandle flag so pipe handles are inherited
  66.    hStdIO[0][0] = hStdIO[0][1] = hStdIO[1][0] = hStdIO[1][1] = NULL;
  67.    CreatePipe (&hStdIO[ME_TO_IT][RD], &hStdIO[ME_TO_IT][WR], &security_attributes, 0); // create a pipe for the child process's stdin
  68.    CreatePipe (&hStdIO[IT_TO_ME][RD], &hStdIO[IT_TO_ME][WR], &security_attributes, 0); // create a pipe for the child process's stdout
  69.    SetHandleInformation (hStdIO[ME_TO_IT][WR], HANDLE_FLAG_INHERIT, 0); // ensure the write handle to the pipe for STDIN is not inherited
  70.    SetHandleInformation (hStdIO[IT_TO_ME][RD], HANDLE_FLAG_INHERIT, 0); // ensure the read handle to the pipe for STDOUT is not inherited
  71.    pipe->descriptors[RD] = _open_osfhandle ((intptr_t) hStdIO[IT_TO_ME][RD], _O_BINARY);
  72.    pipe->descriptors[WR] = _open_osfhandle ((intptr_t) hStdIO[ME_TO_IT][WR], _O_BINARY | _O_APPEND);
  73.  
  74.    // spawn the child process with redirected input and output
  75.    memset (&startup_info, 0, sizeof (startup_info));
  76.    startup_info.cb = sizeof (STARTUPINFO);
  77.    startup_info.dwFlags = STARTF_USESTDHANDLES;
  78.    startup_info.hStdInput = hStdIO[ME_TO_IT][RD];
  79.    startup_info.hStdOutput = hStdIO[IT_TO_ME][WR];
  80.    startup_info.hStdError = hStdIO[IT_TO_ME][WR];
  81.    GetCurrentDirectory (WCHAR_SIZEOF (current_path), current_path);
  82.    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))
  83.    {
  84.       pipe_close ((FILE *) pipe);
  85.       free (command_pathname);
  86.       return (NULL);
  87.    }
  88.  
  89.    // close the handles we won't use, free the command pathname memory space and return a pointer to our pipe object
  90.    CloseHandle (hStdIO[IT_TO_ME][WR]);
  91.    CloseHandle (hStdIO[ME_TO_IT][RD]);
  92.    #undef IT_TO_ME
  93.    #undef ME_TO_IT
  94.    free (command_pathname);
  95.    return ((FILE *) pipe);
  96. #else // !_WIN32
  97.    return (popen (shell_command, mode));
  98. #endif // _WIN32
  99. }
  100.  
  101.  
  102. int pipe_close (FILE *stream)
  103. {
  104.    // wrapper around pclose(), and simple reimplementation of it for systems that don't have it
  105.  
  106. #ifdef _WIN32
  107.    HANDLE hRemoteThread;
  108.    HANDLE hProcessDup;
  109.    DWORD handle_flags;
  110.    DWORD thread_id;
  111.    DWORD exit_code;
  112.    pipe_t *pipe;
  113.    bool was_duplicated;
  114.  
  115.    pipe = (pipe_t *) stream; // cast FILE * back into our own structure
  116.  
  117.    // close all handles
  118.    if (pipe->descriptors[RD] != -1) _close (pipe->descriptors[RD]); pipe->descriptors[RD] = -1;
  119.    if (pipe->descriptors[WR] != -1) _close (pipe->descriptors[WR]); pipe->descriptors[WR] = -1;
  120.  
  121.    // terminate process if it hasn't terminated by itself yet
  122.    exit_code = 0;
  123.    if (GetExitCodeProcess (pipe->procinfo.hProcess, &exit_code) && (exit_code == STILL_ACTIVE))
  124.    {
  125.       // taken from Dr. Dobbs. How to terminate any process cleanly: create a remote thread in it,
  126.       // and make its start address point right into kernel32's ExitProcess() function...
  127.       was_duplicated = (DuplicateHandle (GetCurrentProcess (), pipe->procinfo.hProcess, GetCurrentProcess (), &hProcessDup, PROCESS_ALL_ACCESS, FALSE, 0) != 0);
  128.       if (!was_duplicated)
  129.          hProcessDup = pipe->procinfo.hProcess;
  130.       hRemoteThread = CreateRemoteThread (hProcessDup, NULL, 0, (PTHREAD_START_ROUTINE) GetProcAddress (GetModuleHandle (L"Kernel32"), "ExitProcess"), (void *) 0, 0, &thread_id);
  131.       if (hRemoteThread != NULL)
  132.       {
  133.          WaitForSingleObject (hProcessDup, INFINITE); // must wait process to terminate to guarantee that it has exited
  134.          CloseHandle (hRemoteThread);
  135.       }
  136.       else
  137.          TerminateProcess (pipe->procinfo.hProcess, 0); // if process refuses our remote thread, kill it
  138.       if (was_duplicated)
  139.          CloseHandle (hProcessDup);
  140.    }
  141.  
  142.    // close process handles
  143.    if (pipe->procinfo.hProcess != NULL)
  144.       CloseHandle (pipe->procinfo.hProcess);
  145.    if (GetHandleInformation (pipe->procinfo.hThread, &handle_flags))
  146.       CloseHandle (pipe->procinfo.hThread);
  147.  
  148.    // and finally free the pipe structure
  149.    free (pipe);
  150.    return ((int) exit_code);
  151. #else // !_WIN32
  152.    return (pclose (stream));
  153. #endif // _WIN32
  154. }
  155.  
  156.  
  157. int pipe_isalive (FILE *stream, int64_t *exit_code)
  158. {
  159.    // wrapper around ioctl() on pipes for systems that don't have it
  160.  
  161. #ifdef _WIN32
  162.    DWORD w32exitcode;
  163.    DWORD handle_flags;
  164.    if (((pipe_t *) stream)->procinfo.hProcess == 0)
  165.       return (1); // if the child process has not been fully started yet, assume the pipe is still alive
  166.    else if (((pipe_t *) stream)->procinfo.hProcess == INVALID_HANDLE_VALUE)
  167.       return (0); // if the child process handle has been explicitly marked invalid, assume it's already dead
  168.    else if (GetHandleInformation (((pipe_t *) stream)->procinfo.hProcess, &handle_flags) == 0)
  169.    {
  170.       ((pipe_t *) stream)->procinfo.hProcess = INVALID_HANDLE_VALUE;
  171.       return (0); // if the process handle is no longer valid but its handle is not marked as such, assume the child is dead
  172.    }
  173.    else if (GetExitCodeProcess (((pipe_t *) stream)->procinfo.hProcess, &w32exitcode) == 0)
  174.       return (1); // if GetExitCodeProcess() failed, *conservatively* assume the child is still alive
  175.    *exit_code = (int64_t) w32exitcode;
  176.    return (w32exitcode == STILL_ACTIVE);
  177. #else // !_WIN32
  178.    *exit_code = 0;
  179.    return (1); // UNIX doesn't provide a portable way for this
  180. #endif // _WIN32
  181. }
  182.  
  183.  
  184. int pipe_hasdata (FILE *stream)
  185. {
  186.    // wrapper around ioctl() on pipes for systems that don't have it
  187.  
  188. #ifdef _WIN32
  189.    return (!_eof (((pipe_t *) stream)->descriptors[RD]));
  190. #else // !_WIN32
  191.    return (feof (stream));
  192. #endif // _WIN32
  193. }
  194.  
  195.  
  196. int pipe_read (FILE *stream, void *dstbuf, int nbytes)
  197. {
  198.    // wrapper around fread() on pipes for systems that don't support it
  199.  
  200. #ifdef _WIN32
  201.    return (_read (((pipe_t *) stream)->descriptors[RD], dstbuf, nbytes));
  202. #else // !_WIN32
  203.    return (fread (dstbuf, 1, nbytes, stream));
  204. #endif // _WIN32
  205. }
  206.  
  207.  
  208. int pipe_write (FILE *stream, void *srcbuf, int nbytes)
  209. {
  210.    // wrapper around fwrite() on pipes for systems that don't support it
  211.  
  212. #ifdef _WIN32
  213.    return (_write (((pipe_t *) stream)->descriptors[WR], srcbuf, nbytes));
  214. #else // !_WIN32
  215.    return (fwrite (srcbuf, 1, nbytes, stream));
  216. #endif // _WIN32
  217. }
  218.