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 | } |