right_popen 1.0.1-x86-mswin32-60

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 RightScale, Inc.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ 'Software'), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
18
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,119 @@
1
+ = RightPopen
2
+
3
+ == DESCRIPTION
4
+
5
+ === Synopsis
6
+
7
+ RightPopen allows running external processes aynchronously while still
8
+ capturing their standard and error outputs. It relies on EventMachine for most
9
+ of its internal mechanisms. The Linux implementation is valid for any Linux
10
+ platform but there is also a native implementation for Windows platforms.
11
+
12
+ Refer to the wiki (https://github.com/rightscale/right_popen/wikis) for up-to-date
13
+ documentation.
14
+
15
+ Also use the built-in issues tracker (https://github.com/rightscale/right_popen/issues)
16
+ to report issues.
17
+
18
+
19
+ == USAGE
20
+
21
+ === Simple Example
22
+
23
+ require 'rubygems'
24
+ require 'right_popen'
25
+
26
+ @stdout_text = ""
27
+ @stderr_text = ""
28
+ @exit_status = nil
29
+
30
+ def on_read_stdout(data)
31
+ @stdout_text << data
32
+ end
33
+
34
+ def on_read_stderr(data)
35
+ @stderr_text << data
36
+ end
37
+
38
+ def on_exit(status)
39
+ @exit_status = status
40
+ end
41
+
42
+ EM.run do
43
+ EM.next_tick do
44
+ cmd = "ruby -e \"puts 'some stdout text'; $stderr.puts 'some stderr text'\; exit 99\""
45
+ RightScale.popen3(cmd, self, :on_read_stdout, :on_read_stderr, :on_exit)
46
+ end
47
+ timer = EM::PeriodicTimer.new(0.1) do
48
+ if @exit_status
49
+ timer.cancel
50
+ EM.stop
51
+ end
52
+ end
53
+ end
54
+
55
+ puts "@stdout_text = #{@stdout_text}"
56
+ puts "@stderr_text = #{@stderr_text}"
57
+ puts "@exit_status.exitstatus = #{@exit_status.exitstatus}"
58
+
59
+
60
+ == INSTALLATION
61
+
62
+ RightPopen can be installed by entering the following at the command prompt:
63
+
64
+ gem install right_popen
65
+
66
+
67
+ == BUILDING
68
+
69
+ Install the following RubyGems required for building:
70
+ * rake
71
+
72
+ The Windows implementation relies on a native C module which must currently be
73
+ built using the MSVC 6.0 compiler due to a dependency on the standard libraries
74
+ FILE struct provided by the "msvcrt.dll".
75
+
76
+ The gem can be built on Linux or Windows platforms and will produce separate gem
77
+ files depending on current platform. Run the following command from the
78
+ directory containing the "Rakefile":
79
+
80
+ rake build_binary_gem
81
+
82
+
83
+ == TESTING
84
+
85
+ Install the following RubyGems required for testing:
86
+ * rspec
87
+
88
+ The build can be tested using the RSpec gem. Create a link to the installed
89
+ "spec" in your Ruby/bin directory (or ensure the bin directory is on the PATH
90
+ under Windows) and run the following command from the gem directory to execute
91
+ the RightPopen tests:
92
+
93
+ spec spec/right_popen_spec.rb
94
+
95
+
96
+ == LICENSE
97
+
98
+ <b>RightPopen</b>
99
+
100
+ Copyright:: Copyright (c) 2010 RightScale, Inc.
101
+
102
+ Permission is hereby granted, free of charge, to any person obtaining
103
+ a copy of this software and associated documentation files (the
104
+ 'Software'), to deal in the Software without restriction, including
105
+ without limitation the rights to use, copy, modify, merge, publish,
106
+ distribute, sublicense, and/or sell copies of the Software, and to
107
+ permit persons to whom the Software is furnished to do so, subject to
108
+ the following conditions:
109
+
110
+ The above copyright notice and this permission notice shall be
111
+ included in all copies or substantial portions of the Software.
112
+
113
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
114
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
115
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
116
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
117
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
118
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
119
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,48 @@
1
+ require 'rubygems'
2
+ require 'fileutils'
3
+ require 'rake'
4
+ require 'rake/clean'
5
+ require 'rake/testtask'
6
+ require 'rbconfig'
7
+
8
+ include Config
9
+
10
+ desc "Clean any build files for right_popen"
11
+ task :clean do
12
+ if File.exists?('ext/Makefile')
13
+ Dir.chdir('ext') do
14
+ sh 'nmake distclean'
15
+ end
16
+ end
17
+ rm 'lib/win32/right_popen.so' if File.file?('lib/win32/right_popen.so')
18
+ end
19
+
20
+ desc "Build right_popen (but don't install it)"
21
+ task :build => [:clean] do
22
+ if RUBY_PLATFORM =~ /mswin/
23
+ Dir.chdir('ext') do
24
+ ruby 'extconf.rb'
25
+ sh 'nmake'
26
+ end
27
+ FileUtils::mkdir_p 'lib/win32'
28
+ mv 'ext/right_popen.so', 'lib/win32'
29
+ end
30
+ end
31
+
32
+ desc "Build a binary gem"
33
+ task :build_binary_gem => [:build] do
34
+ Dir["*.gem"].each { |gem| rm gem }
35
+ ruby 'right_popen.gemspec'
36
+ end
37
+
38
+ desc 'Install the right_popen library as a gem'
39
+ task :install_gem => [:build_binary_gem] do
40
+ file = Dir["*.gem"].first
41
+ sh "gem install #{file}"
42
+ end
43
+
44
+ desc 'Uninstalls and reinstalls the right_popen library as a gem'
45
+ task :reinstall_gem do
46
+ sh "gem uninstall right_popen"
47
+ sh "rake install_gem"
48
+ end
data/ext/extconf.rb ADDED
@@ -0,0 +1,3 @@
1
+ require 'mkmf'
2
+
3
+ create_makefile('right_popen', 'win32')
@@ -0,0 +1,996 @@
1
+ ///////////////////////////////////////////////////////////////////////////////
2
+ // Copyright (c) 2010 RightScale Inc
3
+ //
4
+ // Permission is hereby granted, free of charge, to any person obtaining
5
+ // a copy of this software and associated documentation files (the
6
+ // "Software"), to deal in the Software without restriction, including
7
+ // without limitation the rights to use, copy, modify, merge, publish,
8
+ // distribute, sublicense, and/or sell copies of the Software, and to
9
+ // permit persons to whom the Software is furnished to do so, subject to
10
+ // the following conditions:
11
+ //
12
+ // The above copyright notice and this permission notice shall be
13
+ // included in all copies or substantial portions of the Software.
14
+ //
15
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ ///////////////////////////////////////////////////////////////////////////////
23
+
24
+ #include "right_popen.h"
25
+
26
+ #define MAX_ERROR_DESCRIPTION_LENGTH 512
27
+ #define ASYNC_IO_BUFFER_SIZE (1 << 12) // 4KB
28
+
29
+ typedef struct IOHandlePairType
30
+ {
31
+ HANDLE hRead;
32
+ HANDLE hWrite;
33
+ } IOHandlePair;
34
+
35
+ typedef struct AsyncIODataType
36
+ {
37
+ DWORD nBytesRead;
38
+ BOOL bPending;
39
+ OVERLAPPED overlapped;
40
+ char buffer[ASYNC_IO_BUFFER_SIZE + 1]; // buffer size plus nul guard byte
41
+ } AsyncIOData;
42
+
43
+ typedef struct Open3ProcessDataType
44
+ {
45
+ struct Open3ProcessDataType* pNext;
46
+ AsyncIOData* pStdoutAsyncIOData;
47
+ AsyncIOData* pStderrAsyncIOData;
48
+ DWORD nOpenFileCount;
49
+ HANDLE hProcess;
50
+ rb_pid_t pid;
51
+ VALUE vStdinWrite;
52
+ VALUE vStdoutRead;
53
+ VALUE vStderrRead;
54
+ IOHandlePair childStdinPair;
55
+ IOHandlePair childStdoutPair;
56
+ IOHandlePair childStderrPair;
57
+ } Open3ProcessData;
58
+
59
+ static const DWORD CHILD_PROCESS_EXIT_WAIT_MSECS = 500; // 0.5 secs
60
+
61
+ static Open3ProcessData* win32_process_data_list = NULL;
62
+ static DWORD win32_named_pipe_serial_number = 1;
63
+
64
+ // Summary:
65
+ // allocates a new Ruby I/O object.
66
+ //
67
+ // Returns:
68
+ // partially initialized I/O object.
69
+ static VALUE allocate_ruby_io_object()
70
+ {
71
+ VALUE klass = rb_cIO;
72
+ NEWOBJ(io, struct RFile);
73
+ OBJSETUP(io, klass, T_FILE);
74
+
75
+ io->fptr = 0;
76
+
77
+ return (VALUE)io;
78
+ }
79
+
80
+ // Summary:
81
+ // parses the given mode string for Ruby mode flags.
82
+ //
83
+ // Returns:
84
+ // integer representation of mode flags
85
+ static int parse_ruby_io_mode_flags(const char* szMode)
86
+ {
87
+ int flags = 0;
88
+ BOOL bValid = TRUE;
89
+
90
+ switch (szMode[0])
91
+ {
92
+ case 'r':
93
+ flags |= FMODE_READABLE;
94
+ break;
95
+ case 'w':
96
+ case 'a':
97
+ flags |= FMODE_WRITABLE;
98
+ break;
99
+ default:
100
+ bValid = FALSE;
101
+ }
102
+ if (bValid)
103
+ {
104
+ if (szMode[1] == 'b')
105
+ {
106
+ flags |= FMODE_BINMODE;
107
+ szMode++;
108
+ }
109
+ if (szMode[1] == '+')
110
+ {
111
+ if (szMode[2] == 0)
112
+ {
113
+ flags |= FMODE_READWRITE;
114
+ }
115
+ else
116
+ {
117
+ bValid = FALSE;
118
+ }
119
+ }
120
+ else if (szMode[1] != 0)
121
+ {
122
+ bValid = FALSE;
123
+ }
124
+ }
125
+ if (FALSE == bValid)
126
+ {
127
+ rb_raise(rb_eArgError, "illegal access mode %s", szMode);
128
+ }
129
+
130
+ return flags;
131
+ }
132
+
133
+ // Summary:
134
+ // gets text for the given error code.
135
+ //
136
+ // Parameters:
137
+ // dwErrorCode
138
+ // win32 error code
139
+ //
140
+ // Returns:
141
+ // formatted error string
142
+ static char* win32_error_description(DWORD dwErrorCode)
143
+ {
144
+ static char ErrStr[MAX_ERROR_DESCRIPTION_LENGTH];
145
+ HLOCAL hLocal = NULL;
146
+ DWORD dwFlags = FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS;
147
+ int length = FormatMessage(dwFlags,
148
+ NULL,
149
+ dwErrorCode,
150
+ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
151
+ (char*)&hLocal,
152
+ 0,
153
+ NULL);
154
+ if (0 == length)
155
+ {
156
+ sprintf(ErrStr, "Unable to format message for Windows error #%d", (int)dwErrorCode);
157
+ }
158
+ else
159
+ {
160
+ strncpy(ErrStr, (LPTSTR)hLocal, length - 2); // remove \r\n
161
+ LocalFree(hLocal);
162
+ }
163
+
164
+ return ErrStr;
165
+ }
166
+
167
+ // Summary:
168
+ // closes the Ruby I/O objects in the given array.
169
+ //
170
+ // Parameters:
171
+ // vRubyIoObjectArray
172
+ // array containing three I/O objects to be closed.
173
+ //
174
+ // Returns:
175
+ // Qnil
176
+ static VALUE right_popen_close_io_array(VALUE vRubyIoObjectArray)
177
+ {
178
+ const int iRubyIoObjectCount = 3;
179
+ int i = 0;
180
+
181
+ for (; i < iRubyIoObjectCount; ++i)
182
+ {
183
+ VALUE vRubyIoObject = RARRAY(vRubyIoObjectArray)->ptr[i];
184
+
185
+ if (rb_funcall(vRubyIoObject, rb_intern("closed?"), 0) == Qfalse)
186
+ {
187
+ rb_funcall(vRubyIoObject, rb_intern("close"), 0);
188
+ }
189
+ }
190
+
191
+ return Qnil;
192
+ }
193
+
194
+ // Summary:
195
+ // creates a process using the popen pipe handles for standard I/O.
196
+ //
197
+ // Parameters:
198
+ // szCommand
199
+ // command to execute
200
+ //
201
+ // hStdin
202
+ // stdin pipe handle
203
+ //
204
+ // hStdin
205
+ // stdin read handle
206
+ //
207
+ // hStdout
208
+ // stdout write handle
209
+ //
210
+ // hStderr
211
+ // stderr write handle
212
+ //
213
+ // phProcess
214
+ // returned process handle
215
+ //
216
+ // pPid
217
+ // returned pid
218
+ //
219
+ // bShowWindow
220
+ // true if process window is initially visible, false if process has no UI or is invisible
221
+ //
222
+ // Returns:
223
+ // true if successful, false otherwise (call GetLastError() for more information)
224
+ static BOOL win32_create_process(char* szCommand,
225
+ HANDLE hStdin,
226
+ HANDLE hStdout,
227
+ HANDLE hStderr,
228
+ HANDLE* phProcess,
229
+ rb_pid_t* pPid,
230
+ BOOL bShowWindow)
231
+ {
232
+ PROCESS_INFORMATION pi;
233
+ STARTUPINFO si;
234
+
235
+ ZeroMemory(&si, sizeof(STARTUPINFO));
236
+
237
+ si.cb = sizeof(STARTUPINFO);
238
+ si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
239
+ si.hStdInput = hStdin;
240
+ si.hStdOutput = hStdout;
241
+ si.hStdError = hStderr;
242
+ si.wShowWindow = bShowWindow ? SW_SHOW : SW_HIDE;
243
+ if (CreateProcess(NULL,
244
+ szCommand,
245
+ NULL,
246
+ NULL,
247
+ TRUE,
248
+ 0,
249
+ NULL,
250
+ NULL,
251
+ &si,
252
+ &pi))
253
+ {
254
+ // Close the handles now so anyone waiting is woken.
255
+ CloseHandle(pi.hThread);
256
+
257
+ // Return process handle
258
+ *phProcess = pi.hProcess;
259
+ *pPid = (rb_pid_t)pi.dwProcessId;
260
+ return TRUE;
261
+ }
262
+
263
+ return FALSE;
264
+ }
265
+
266
+ // Summary:
267
+ // closes the given pipe handle pair, if necessary.
268
+ //
269
+ // Parameters:
270
+ // pair of handles to close.
271
+ static void win32_pipe_close(IOHandlePair* pPair)
272
+ {
273
+ if (NULL != pPair->hRead)
274
+ {
275
+ CloseHandle(pPair->hRead);
276
+ pPair->hRead = NULL;
277
+ }
278
+ if (NULL != pPair->hWrite)
279
+ {
280
+ CloseHandle(pPair->hWrite);
281
+ pPair->hWrite = NULL;
282
+ }
283
+ }
284
+
285
+ // Summary:
286
+ // creates a new asynchronous I/O data structure.
287
+ //
288
+ // Returns:
289
+ // initialized asynchronous I/O structure.
290
+ static AsyncIOData* win32_create_async_io_data()
291
+ {
292
+ AsyncIOData* pAsyncIOData = (AsyncIOData*)malloc(sizeof(AsyncIOData));
293
+
294
+ memset(pAsyncIOData, 0, sizeof(AsyncIOData));
295
+ pAsyncIOData->overlapped.hEvent = CreateEvent(NULL, TRUE, TRUE, NULL);
296
+
297
+ return pAsyncIOData;
298
+ }
299
+
300
+ // Summary:
301
+ // frees an existing asynchronous I/O data structure after cancelling any
302
+ // pending I/O operations.
303
+ //
304
+ // Parameters:
305
+ // hRead
306
+ // pipe handle opened for asynchronous reading
307
+ //
308
+ // pAsyncIOData
309
+ // data to free
310
+ static void win32_free_async_io_data(HANDLE hRead, AsyncIOData* pAsyncIOData)
311
+ {
312
+ if (pAsyncIOData->bPending)
313
+ {
314
+ BOOL bResult = CancelIo(hRead);
315
+
316
+ if (FALSE != bResult || ERROR_NOT_FOUND != GetLastError())
317
+ {
318
+ // Wait for the I/O subsystem to acknowledge our cancellation.
319
+ // Depending on the timing of the calls, the I/O might complete
320
+ // with a cancellation status, or it might complete normally (if
321
+ // the ReadFile() was in the process of completing at the time
322
+ // CancelIo() was called, or if the device does not support
323
+ // cancellation). This call specifies TRUE for the bWait parameter,
324
+ // which will block until the I/O either completes or is canceled,
325
+ // thus resuming execution, provided the underlying device driver
326
+ // and associated hardware are functioning properly. If there is a
327
+ // problem with the driver it is better to stop responding, or
328
+ // "hang," here than to try to continue while masking the problem.
329
+ DWORD nBytesRead = 0;
330
+
331
+ GetOverlappedResult(hRead, &pAsyncIOData->overlapped, &nBytesRead, TRUE);
332
+ }
333
+ pAsyncIOData->bPending = FALSE;
334
+ }
335
+ if (NULL != pAsyncIOData->overlapped.hEvent)
336
+ {
337
+ CloseHandle(pAsyncIOData->overlapped.hEvent);
338
+ pAsyncIOData->overlapped.hEvent = NULL;
339
+ }
340
+ free(pAsyncIOData);
341
+ }
342
+
343
+ // Summary:
344
+ // waits, closes and frees the given process data elements.
345
+ //
346
+ // Parameters:
347
+ // data representing a monitored child process.
348
+ static void win32_free_process_data(Open3ProcessData* pData)
349
+ {
350
+ if (NULL != pData->pStdoutAsyncIOData)
351
+ {
352
+ win32_free_async_io_data(pData->childStdoutPair.hRead, pData->pStdoutAsyncIOData);
353
+ pData->pStdoutAsyncIOData = NULL;
354
+ }
355
+ if (NULL != pData->pStderrAsyncIOData)
356
+ {
357
+ win32_free_async_io_data(pData->childStderrPair.hRead, pData->pStderrAsyncIOData);
358
+ pData->pStderrAsyncIOData = NULL;
359
+ }
360
+ win32_pipe_close(&pData->childStdinPair);
361
+ win32_pipe_close(&pData->childStdoutPair);
362
+ win32_pipe_close(&pData->childStderrPair);
363
+ free(pData);
364
+ }
365
+
366
+ // Summary:
367
+ // finalizer for the given Ruby file object.
368
+ static void win32_pipe_finalize(OpenFile *file, int noraise)
369
+ {
370
+ if (file->f)
371
+ {
372
+ fclose(file->f);
373
+ file->f = NULL;
374
+ }
375
+
376
+ if (file->f2)
377
+ {
378
+ fclose(file->f2);
379
+ file->f2 = NULL;
380
+ }
381
+
382
+ // update exit status for child process, etc.
383
+ {
384
+ Open3ProcessData* pPrevious = NULL;
385
+ Open3ProcessData* pData = win32_process_data_list;
386
+
387
+ for (; NULL != pData; pData = pData->pNext)
388
+ {
389
+ if (pData->pid == file->pid)
390
+ {
391
+ break;
392
+ }
393
+ pPrevious = pData;
394
+ }
395
+ if (NULL != pData)
396
+ {
397
+ if (pData->nOpenFileCount <= 1)
398
+ {
399
+ // remove data from linked list.
400
+ pData->nOpenFileCount = 0;
401
+ if (NULL == pPrevious)
402
+ {
403
+ win32_process_data_list = pData->pNext;
404
+ }
405
+ else
406
+ {
407
+ pPrevious->pNext = pData->pNext;
408
+ }
409
+
410
+ // note that the caller has the option to wait out the child process
411
+ // externally using the returned PID instead of relying on this code
412
+ // to ensure it is finished and return the correct exit code.
413
+ CloseHandle(pData->hProcess);
414
+ pData->hProcess = NULL;
415
+
416
+ // forget the pipe handles owned by the Ruby I/O objects to avoid
417
+ // attempting to again close the already-closed handles.
418
+ pData->childStdinPair.hWrite = NULL;
419
+ pData->childStdoutPair.hRead = NULL;
420
+ pData->childStderrPair.hRead = NULL;
421
+
422
+ // free process data.
423
+ win32_free_process_data(pData);
424
+ }
425
+ else
426
+ {
427
+ --pData->nOpenFileCount;
428
+ }
429
+ }
430
+ }
431
+ }
432
+
433
+ // Summary:
434
+ // creates an asynchronous pipe which allows for a single-threaded process
435
+ // (e.g. Ruby v1.8) to read from multiple open pipes without deadlocking.
436
+ // the write handle can be opened for any combination of synchronous/
437
+ // asynchronous read/write. in the case of reading stdout and stderr from a
438
+ // child process, open the pipe with synchronous write and asynchronous read
439
+ // which means that only the caller and not the child process needs to be
440
+ // aware that the pipe uses asynchronous read calls.
441
+ //
442
+ // Parameters:
443
+ // pReadPipeHandle
444
+ // receives created synchronous read pipe handle
445
+ //
446
+ // pWritePipeHandle
447
+ // receives opened asynchronous write pipe handle
448
+ //
449
+ // pPipeAttributes
450
+ // security attributes or NULL
451
+ //
452
+ // nSize
453
+ // suggested pipe buffer size or zero
454
+ //
455
+ // dwReadMode
456
+ // read mode, which can be FILE_FLAG_OVERLAPPED or zero
457
+ //
458
+ // dwWriteMode
459
+ // write mode, which can be FILE_FLAG_OVERLAPPED or zero
460
+ //
461
+ // Returns:
462
+ // true if successful, false on failure (call GetLastError() for more info)
463
+ static BOOL win32_create_asynchronous_pipe(HANDLE* pReadPipeHandle,
464
+ HANDLE* pWritePipeHandle,
465
+ SECURITY_ATTRIBUTES* pPipeAttributes,
466
+ DWORD nSize,
467
+ DWORD dwReadMode,
468
+ DWORD dwWriteMode)
469
+ {
470
+ HANDLE hRead = NULL;
471
+ HANDLE hWrite = NULL;
472
+ char pipeNameBuffer[MAX_PATH];
473
+
474
+ // reset.
475
+ *pReadPipeHandle = NULL;
476
+ *pWritePipeHandle = NULL;
477
+
478
+ // only one valid mode flag - FILE_FLAG_OVERLAPPED
479
+ if ((dwReadMode | dwWriteMode) & (~FILE_FLAG_OVERLAPPED))
480
+ {
481
+ SetLastError(ERROR_INVALID_PARAMETER);
482
+ return FALSE;
483
+ }
484
+
485
+ // default buffer size to 4 KB.
486
+ {
487
+ const DWORD nMinBufferSize = 1 << 12;
488
+
489
+ nSize = max(nSize, nMinBufferSize);
490
+ }
491
+
492
+ // generate unique pipe name.
493
+ sprintf(pipeNameBuffer,
494
+ "\\\\.\\Pipe\\Ruby_Win32_Open3_gem.%d.%d",
495
+ (int)GetCurrentProcessId(),
496
+ (int)win32_named_pipe_serial_number++);
497
+
498
+ // create read-end of pipe.
499
+ hRead = CreateNamedPipeA(pipeNameBuffer,
500
+ PIPE_ACCESS_INBOUND | dwReadMode,
501
+ PIPE_TYPE_BYTE | PIPE_WAIT,
502
+ 1, // allowed named pipe instances
503
+ nSize, // out buffer size
504
+ nSize, // in buffer size
505
+ 0, // default timeout = 50 ms
506
+ pPipeAttributes);
507
+
508
+ if (NULL == hRead || INVALID_HANDLE_VALUE == hRead)
509
+ {
510
+ return FALSE;
511
+ }
512
+
513
+ // open write-end of existing pipe.
514
+ hWrite = CreateFileA(pipeNameBuffer,
515
+ GENERIC_WRITE,
516
+ 0, // No sharing
517
+ pPipeAttributes,
518
+ OPEN_EXISTING,
519
+ FILE_ATTRIBUTE_NORMAL | dwWriteMode,
520
+ NULL);
521
+
522
+ if (NULL == hWrite || INVALID_HANDLE_VALUE == hWrite)
523
+ {
524
+ DWORD dwErrorCode = GetLastError();
525
+ CloseHandle(hRead);
526
+ SetLastError(dwErrorCode);
527
+
528
+ return FALSE;
529
+ }
530
+ *pReadPipeHandle = hRead;
531
+ *pWritePipeHandle = hWrite;
532
+
533
+ return TRUE;
534
+ }
535
+
536
+ // Summary:
537
+ // creates a pipe and manages the requested inheritance for read/write. this
538
+ // allows the inheritable handles to be passed to a created child process.
539
+ //
540
+ // Parameters:
541
+ // bInheritRead
542
+ // true if read handle (pair.hRead) will be inheritable.
543
+ //
544
+ // bInheritWrite
545
+ // true if write handle (pair.hWrite) will be inheritable.
546
+ //
547
+ // bAsynchronousOutput
548
+ // true if read handle supports overlapped IO API calls, false if reads
549
+ // are synchronous. the write handle is always synchronous so that the
550
+ // child process can perform simple writes to stdout/stderr.
551
+ //
552
+ // Returns:
553
+ // read,write handle pair
554
+ static IOHandlePair win32_pipe_create(BOOL bInheritRead, BOOL bInheritWrite, BOOL bAsynchronousOutput)
555
+ {
556
+ // create pipe without inheritance, if requested.
557
+ IOHandlePair pair;
558
+
559
+ memset(&pair, 0, sizeof pair);
560
+ if (0 == bInheritRead && 0 == bInheritWrite)
561
+ {
562
+ BOOL bResult = bAsynchronousOutput
563
+ ? win32_create_asynchronous_pipe(&pair.hRead, &pair.hWrite, NULL, 0, FILE_FLAG_OVERLAPPED, 0)
564
+ : CreatePipe(&pair.hRead, &pair.hWrite, NULL, 0);
565
+
566
+ if (0 == bResult)
567
+ {
568
+ rb_raise(rb_eRuntimeError, "CreatePipe() failed: %s", win32_error_description(GetLastError()));
569
+ }
570
+ }
571
+ else
572
+ {
573
+ HANDLE hCurrentProcess = GetCurrentProcess();
574
+ SECURITY_ATTRIBUTES sa;
575
+
576
+ // create pipe with inheritable flag set to TRUE.
577
+ memset(&sa, 0, sizeof sa);
578
+ sa.nLength = sizeof(SECURITY_ATTRIBUTES);
579
+ sa.bInheritHandle = TRUE;
580
+ {
581
+ BOOL bResult = bAsynchronousOutput
582
+ ? win32_create_asynchronous_pipe(&pair.hRead, &pair.hWrite, &sa, 0, FILE_FLAG_OVERLAPPED, 0)
583
+ : CreatePipe(&pair.hRead, &pair.hWrite, &sa, 0);
584
+
585
+ if (0 == bResult)
586
+ {
587
+ rb_raise(rb_eRuntimeError, "CreatePipe() failed: %s", win32_error_description(GetLastError()));
588
+ }
589
+ }
590
+
591
+ // duplicate the uninheritable handle (if any) by setting inheritance to FALSE.
592
+ // otherwise, the child inherits the these handles which results in
593
+ // non-closeable handles to the pipes being created.
594
+ if (0 == bInheritRead)
595
+ {
596
+ HANDLE hDuplicate = NULL;
597
+ BOOL bSuccess = DuplicateHandle(hCurrentProcess,
598
+ pair.hRead,
599
+ hCurrentProcess,
600
+ &hDuplicate,
601
+ 0,
602
+ FALSE,
603
+ DUPLICATE_SAME_ACCESS);
604
+ CloseHandle(pair.hRead);
605
+ if (0 == bSuccess)
606
+ {
607
+ CloseHandle(pair.hWrite);
608
+ rb_raise(rb_eRuntimeError, "DuplicateHandle() failed: %s", win32_error_description(GetLastError()));
609
+ }
610
+ pair.hRead = hDuplicate;
611
+ }
612
+ if (0 == bInheritWrite)
613
+ {
614
+ HANDLE hDuplicate = NULL;
615
+ BOOL bSuccess = DuplicateHandle(hCurrentProcess,
616
+ pair.hWrite,
617
+ hCurrentProcess,
618
+ &hDuplicate,
619
+ 0,
620
+ FALSE,
621
+ DUPLICATE_SAME_ACCESS);
622
+ CloseHandle(pair.hWrite);
623
+ if (0 == bSuccess)
624
+ {
625
+ CloseHandle(pair.hRead);
626
+ rb_raise(rb_eRuntimeError, "DuplicateHandle() failed: %s", win32_error_description(GetLastError()));
627
+ }
628
+ pair.hWrite = hDuplicate;
629
+ }
630
+ }
631
+
632
+ return pair;
633
+ }
634
+
635
+ // creates a Ruby I/O object from a file (pipe) handle opened for read or write.
636
+ //
637
+ // Parameters:
638
+ // pid
639
+ // child process id using the other end of pipe
640
+ //
641
+ // mode
642
+ // standard I/O file mode
643
+ //
644
+ // hFile
645
+ // pipe I/O connector to wrap with Ruby object
646
+ //
647
+ // bReadMode
648
+ // TRUE to create a readonly file object, FALSE to create a writeonly file object.
649
+ //
650
+ // Returns:
651
+ // a Ruby I/O object
652
+ static VALUE ruby_create_io_object(rb_pid_t pid, int iFileMode, HANDLE hFile, BOOL bReadMode)
653
+ {
654
+ BOOL bTextMode = 0 != (iFileMode & _O_TEXT);
655
+ char* szMode = bReadMode
656
+ ? (bTextMode ? "r" : "rb")
657
+ : (bTextMode ? "w" : "wb");
658
+ int fd = _open_osfhandle((long)hFile, iFileMode);
659
+ FILE* pFile = _fdopen(fd, szMode);
660
+ int iRubyModeFlags = parse_ruby_io_mode_flags(szMode);
661
+ VALUE pRubyIOObject = allocate_ruby_io_object();
662
+ OpenFile* pRubyOpenFile = NULL;
663
+
664
+ MakeOpenFile(pRubyIOObject, pRubyOpenFile);
665
+ pRubyOpenFile->finalize = win32_pipe_finalize;
666
+ pRubyOpenFile->mode = iRubyModeFlags;
667
+ pRubyOpenFile->pid = pid;
668
+
669
+ if (iRubyModeFlags & FMODE_READABLE)
670
+ {
671
+ pRubyOpenFile->f = pFile;
672
+ }
673
+ if (iRubyModeFlags & FMODE_WRITABLE)
674
+ {
675
+ if (pRubyOpenFile->f)
676
+ {
677
+ pRubyOpenFile->f2 = pFile;
678
+ }
679
+ else
680
+ {
681
+ pRubyOpenFile->f = pFile;
682
+ }
683
+ pRubyOpenFile->mode |= FMODE_SYNC;
684
+ }
685
+
686
+ return pRubyIOObject;
687
+ }
688
+
689
+ // Summary:
690
+ // creates a child process using the given command string and creates pipes for
691
+ // use by the child's standard I/O methods. the pipes can be read either
692
+ // synchronously or asynchronously, the latter being recommended for child
693
+ // processes which potentially produce a large amount of output. reading
694
+ // asynchronously also prevents a deadlock condition where the child is blocked
695
+ // writing to a full pipe cache because the other pipe has not been flushed and
696
+ // therefore cannot be read by the calling process, which is blocked reading.
697
+ //
698
+ // Parameters:
699
+ // variable arguments, as follows:
700
+ // szCommand
701
+ // command to execute including any command-line arguments (required).
702
+ //
703
+ // iMode
704
+ // standard I/O file mode (e.g. _O_TEXT or _O_BINARY)
705
+ //
706
+ // bShowWindow
707
+ // false to hide child process, true to show
708
+ //
709
+ // bAsynchronousOutput
710
+ // false to read synchronously, true to read asynchronously. see
711
+ // also RightPopen::async_read() (defaults to Qfalse).
712
+ //
713
+ // Returns:
714
+ // a Ruby array containing [stdin write, stdout read, stderr read, pid]
715
+ //
716
+ // Throws:
717
+ // raises a Ruby RuntimeError on failure
718
+ static VALUE win32_popen4(char* szCommand, int iMode, BOOL bShowWindow, BOOL bAsynchronousOutput)
719
+ {
720
+ VALUE vReturnArray = Qnil;
721
+ HANDLE hProcess = NULL;
722
+ rb_pid_t pid = 0;
723
+ Open3ProcessData* pData = (Open3ProcessData*)malloc(sizeof(Open3ProcessData));
724
+
725
+ memset(pData, 0, sizeof(Open3ProcessData));
726
+ pData->childStdinPair = win32_pipe_create(TRUE, FALSE, FALSE);
727
+ pData->childStdoutPair = win32_pipe_create(FALSE, TRUE, bAsynchronousOutput);
728
+ pData->childStderrPair = win32_pipe_create(FALSE, TRUE, bAsynchronousOutput);
729
+
730
+ if (0 == win32_create_process(szCommand,
731
+ pData->childStdinPair.hRead,
732
+ pData->childStdoutPair.hWrite,
733
+ pData->childStderrPair.hWrite,
734
+ &hProcess,
735
+ &pid,
736
+ bShowWindow))
737
+ {
738
+ DWORD dwLastError = GetLastError();
739
+ win32_free_process_data(pData);
740
+ rb_raise(rb_eRuntimeError, "CreateProcess() failed: %s", win32_error_description(dwLastError));
741
+ }
742
+
743
+ // wrap piped I/O handles as ruby I/O objects in an array for return.
744
+ pData->vStdinWrite = ruby_create_io_object(pid, iMode, pData->childStdinPair.hWrite, FALSE);
745
+ pData->vStdoutRead = ruby_create_io_object(pid, iMode, pData->childStdoutPair.hRead, TRUE);
746
+ pData->vStderrRead = ruby_create_io_object(pid, iMode, pData->childStderrPair.hRead, TRUE);
747
+ pData->nOpenFileCount = 3;
748
+
749
+ // allocate asynchronous I/O buffers, etc., if necessary.
750
+ if (bAsynchronousOutput)
751
+ {
752
+ pData->pStdoutAsyncIOData = win32_create_async_io_data();
753
+ pData->pStderrAsyncIOData = win32_create_async_io_data();
754
+ }
755
+
756
+ // create array for returning open3/open4 values.
757
+ {
758
+ const int iArraySize = 4;
759
+
760
+ vReturnArray = rb_ary_new2(iArraySize);
761
+ rb_ary_push(vReturnArray, pData->vStdinWrite);
762
+ rb_ary_push(vReturnArray, pData->vStdoutRead);
763
+ rb_ary_push(vReturnArray, pData->vStderrRead);
764
+ rb_ary_push(vReturnArray, UINT2NUM((DWORD)pid));
765
+ }
766
+
767
+ // Child is launched. Close the parents copy of those pipe handles that only
768
+ // the child should have open. You need to make sure that no handles to the
769
+ // write end of the output pipe are maintained in this process or else the
770
+ // pipe will not close when the child process exits and the ReadFile() will
771
+ // hang.
772
+ CloseHandle(pData->childStdinPair.hRead);
773
+ pData->childStdinPair.hRead = NULL;
774
+ CloseHandle(pData->childStdoutPair.hWrite);
775
+ pData->childStdoutPair.hWrite = NULL;
776
+ CloseHandle(pData->childStderrPair.hWrite);
777
+ pData->childStderrPair.hWrite = NULL;
778
+
779
+ // insert data into static linked list.
780
+ pData->pNext = win32_process_data_list;
781
+ win32_process_data_list = pData;
782
+
783
+ return vReturnArray;
784
+ }
785
+
786
+ // Summary:
787
+ // creates a child process using the given command string and creates pipes for
788
+ // use by the child's standard I/O methods. the pipes can be read either
789
+ // synchronously or asynchronously, the latter being recommended for child
790
+ // processes which potentially produce a large amount of output. reading
791
+ // asynchronously also prevents a deadlock condition where the child is blocked
792
+ // writing to a full pipe cache because the other pipe has not been flushed and
793
+ // therefore cannot be read by the calling process, which is blocked reading.
794
+ //
795
+ // Parameters:
796
+ // variable arguments, as follows:
797
+ // vCommand
798
+ // command to execute including any command-line arguments (required).
799
+ //
800
+ // vMode
801
+ // text ("t") or binary ("b") mode (defaults to "t").
802
+ //
803
+ // vShowWindowFlag
804
+ // Qfalse to hide child process, Qtrue to show (defaults to Qfalse)
805
+ //
806
+ // vAsynchronousOutputFlag
807
+ // Qfalse to read synchronously, Qtrue to read asynchronously. see
808
+ // also RightPopen::async_read() (defaults to Qfalse).
809
+ //
810
+ // Returns:
811
+ // a Ruby array containing [stdin write, stdout read, stderr read, pid]
812
+ //
813
+ // Throws:
814
+ // raises a Ruby exception on failure
815
+ static VALUE right_popen_popen4(int argc, VALUE *argv, VALUE klass)
816
+ {
817
+ VALUE vCommand = Qnil;
818
+ VALUE vMode = Qnil;
819
+ VALUE vReturnArray = Qnil;
820
+ VALUE vShowWindowFlag = Qfalse;
821
+ VALUE vAsynchronousOutputFlag = Qfalse;
822
+ int iMode = 0;
823
+ char* mode = "t";
824
+
825
+ rb_scan_args(argc, argv, "13", &vCommand, &vMode, &vShowWindowFlag, &vAsynchronousOutputFlag);
826
+
827
+ if (!NIL_P(vMode))
828
+ {
829
+ mode = StringValuePtr(vMode);
830
+ }
831
+ if (*mode == 't')
832
+ {
833
+ iMode = _O_TEXT;
834
+ }
835
+ else if (*mode != 'b')
836
+ {
837
+ rb_raise(rb_eArgError, "RightPopen::popen4() arg 2 must be 't' or 'b'");
838
+ }
839
+ else
840
+ {
841
+ iMode = _O_BINARY;
842
+ }
843
+
844
+ vReturnArray = win32_popen4(StringValuePtr(vCommand),
845
+ iMode,
846
+ Qfalse != vShowWindowFlag,
847
+ Qfalse != vAsynchronousOutputFlag);
848
+
849
+ // ensure handles are closed in block form.
850
+ if (rb_block_given_p())
851
+ {
852
+ return rb_ensure(rb_yield_splat, vReturnArray, right_popen_close_io_array, vReturnArray);
853
+ }
854
+
855
+ return vReturnArray;
856
+ }
857
+
858
+ // Summary:
859
+ // reads asynchronously from a pipe opened for overlapped I/O.
860
+ //
861
+ // Parameters:
862
+ // vSelf
863
+ // should be Qnil since this is a module method.
864
+ //
865
+ // vRubyIoObject
866
+ // Ruby I/O object created by previous call to one of this module's popen
867
+ // methods. I/O object should be opened for asynchronous reading or else
868
+ // the behavior of this method is undefined.
869
+ //
870
+ // Returns:
871
+ // Ruby string object representing a completed asynchronous read OR
872
+ // the empty string to indicate the read is pending OR
873
+ // Qnil to indicate data is not available and no further attempt to read should be made
874
+ static VALUE right_popen_async_read(VALUE vSelf, VALUE vRubyIoObject)
875
+ {
876
+ if (NIL_P(vRubyIoObject))
877
+ {
878
+ rb_raise(rb_eRuntimeError, "RightPopen::async_read() parameter cannot be nil.");
879
+ }
880
+
881
+ // attempt to find corresponding asynchronous I/O data.
882
+ {
883
+ HANDLE hRead = NULL;
884
+ AsyncIOData* pAsyncIOData = NULL;
885
+ Open3ProcessData* pData = win32_process_data_list;
886
+
887
+ for (; NULL != pData; pData = pData->pNext)
888
+ {
889
+ if (pData->vStdoutRead == vRubyIoObject)
890
+ {
891
+ hRead = pData->childStdoutPair.hRead;
892
+ pAsyncIOData = pData->pStdoutAsyncIOData;
893
+ break;
894
+ }
895
+ if (pData->vStderrRead == vRubyIoObject)
896
+ {
897
+ hRead = pData->childStderrPair.hRead;
898
+ pAsyncIOData = pData->pStderrAsyncIOData;
899
+ break;
900
+ }
901
+ }
902
+ if (NULL == hRead)
903
+ {
904
+ rb_raise(rb_eRuntimeError, "RightPopen::async_read() parameter refers to an I/O object which was not created by this class.");
905
+ }
906
+
907
+ // perform asynchronous read.
908
+ if (WAIT_OBJECT_0 == WaitForSingleObject(pAsyncIOData->overlapped.hEvent, 0))
909
+ {
910
+ // attempt to complete last read without waiting if pending.
911
+ if (pAsyncIOData->bPending)
912
+ {
913
+ if (0 == GetOverlappedResult(hRead, &pAsyncIOData->overlapped, &pAsyncIOData->nBytesRead, FALSE))
914
+ {
915
+ DWORD dwErrorCode = GetLastError();
916
+
917
+ switch (dwErrorCode)
918
+ {
919
+ case ERROR_IO_INCOMPLETE:
920
+ break;
921
+ default:
922
+ // doesn't matter why read failed; read is no longer
923
+ // pending and was probably cancelled.
924
+ pAsyncIOData->bPending = FALSE;
925
+ return Qnil;
926
+ }
927
+ }
928
+ else
929
+ {
930
+ // delayed read completed, set guard byte to nul.
931
+ pAsyncIOData->bPending = FALSE;
932
+ pAsyncIOData->buffer[pAsyncIOData->nBytesRead] = 0;
933
+ }
934
+ }
935
+ else if (0 == ReadFile(hRead,
936
+ pAsyncIOData->buffer,
937
+ sizeof(pAsyncIOData->buffer) - 1,
938
+ &pAsyncIOData->nBytesRead,
939
+ &pAsyncIOData->overlapped))
940
+ {
941
+ DWORD dwErrorCode = GetLastError();
942
+
943
+ switch (dwErrorCode)
944
+ {
945
+ case ERROR_IO_PENDING:
946
+ pAsyncIOData->bPending = TRUE;
947
+ break;
948
+ default:
949
+ // doesn't matter why read failed; data is no longer available.
950
+ return Qnil;
951
+ }
952
+ }
953
+ else
954
+ {
955
+ // read completed immediately, set guard byte to nul.
956
+ pAsyncIOData->buffer[pAsyncIOData->nBytesRead] = 0;
957
+ }
958
+ }
959
+
960
+ // the overlapped I/O appears to pass \r\n literally from the child
961
+ // process' output stream whereas the synchronous stdio alternative
962
+ // replaces \r\n with \n. for the sake of homogeneosity of text data
963
+ // and the fact that Ruby code rarely uses the \r\n idiom, quickly
964
+ // remove all \r characters from the text.
965
+ if (pAsyncIOData->nBytesRead > 0)
966
+ {
967
+ char* pszInsert = pAsyncIOData->buffer;
968
+ char* pszParse = pAsyncIOData->buffer;
969
+ char* pszStop = pAsyncIOData->buffer + pAsyncIOData->nBytesRead;
970
+
971
+ while (pszParse < pszStop)
972
+ {
973
+ char chNext = *pszParse++;
974
+ if ('\r' != chNext)
975
+ {
976
+ *pszInsert++ = chNext;
977
+ }
978
+ }
979
+ pAsyncIOData->nBytesRead = (DWORD)(pszInsert - pAsyncIOData->buffer);
980
+ }
981
+
982
+ // create string for return value, if necessary. the empty string signals
983
+ // that the caller should keep trying (i.e. pending).
984
+ return rb_str_new(pAsyncIOData->buffer, (long)pAsyncIOData->nBytesRead);
985
+ }
986
+ }
987
+
988
+ // Summary:
989
+ // 'RightPopen' module entry point
990
+ void Init_right_popen()
991
+ {
992
+ VALUE vModule = rb_define_module("RightPopen");
993
+
994
+ rb_define_module_function(vModule, "popen4", (VALUE(*)(ANYARGS))right_popen_popen4, -1);
995
+ rb_define_module_function(vModule, "async_read", (VALUE(*)(ANYARGS))right_popen_async_read, 1);
996
+ }