Subversion Repositories Games.Chess Giants

Rev

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

Rev Author Line No. Line
150 pmbaty 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);
177 pmbaty 23
int pipe_isalive (FILE *stream, int64_t *exit_code);
150 pmbaty 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));
175 pmbaty 62
   memset (pipe, 0, sizeof (pipe_t));
150 pmbaty 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
 
174 pmbaty 74
   // spawn the child process with redirected input and output
150 pmbaty 75
   memset (&startup_info, 0, sizeof (startup_info));
194 pmbaty 76
   startup_info.cb = sizeof (STARTUPINFO);
150 pmbaty 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
 
177 pmbaty 157
int pipe_isalive (FILE *stream, int64_t *exit_code)
150 pmbaty 158
{
159
   // wrapper around ioctl() on pipes for systems that don't have it
160
 
161
#ifdef _WIN32
177 pmbaty 162
   DWORD w32exitcode;
175 pmbaty 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
   }
177 pmbaty 173
   else if (GetExitCodeProcess (((pipe_t *) stream)->procinfo.hProcess, &w32exitcode) == 0)
175 pmbaty 174
      return (1); // if GetExitCodeProcess() failed, *conservatively* assume the child is still alive
177 pmbaty 175
   *exit_code = (int64_t) w32exitcode;
176
   return (w32exitcode == STILL_ACTIVE);
150 pmbaty 177
#else // !_WIN32
177 pmbaty 178
   *exit_code = 0;
150 pmbaty 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
}