win32-open3 0.3.1-x86-mswin32-60 → 0.3.2-x86-mswin32-60

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,34 @@
1
+ ##############################################################################
2
+ # win32_open3_example.rb
3
+ #
4
+ # Test script for the win32-open3 library. You should run this via the
5
+ # 'rake example' Rakefile task.
6
+ #
7
+ # Modify as you see fit.
8
+ ##############################################################################
9
+ require 'win32/open3'
10
+
11
+ puts "VERSION: " + Open3::WIN32_OPEN3_VERSION
12
+
13
+ cmd1 = 'ver' # valid
14
+ cmd2 = 'verb' # invalid
15
+ cmd3 = "ruby -e 'exit 1'" # valid, with explicit exit status
16
+
17
+ # Try each command as you like...
18
+ Open3.popen3(cmd3){ |io_in, io_out, io_err|
19
+ error = io_err.read
20
+ if error && error.length > 0
21
+ puts 'Error: ' + error
22
+ break
23
+ else
24
+ output = io_out.read
25
+ puts 'Output: ' + output if output
26
+ end
27
+ }
28
+
29
+ p $?.class
30
+ p $? >> 8
31
+ p $?.stopped?
32
+ p $?.exited?
33
+ p $?.exitstatus
34
+ p $?.pid
data/ext/win32/open3.c CHANGED
@@ -1,546 +1,567 @@
1
- /***************************************************
2
- * open3.c
3
- *
4
- * Source for the win32-open3 extension.
5
- ***************************************************/
6
- #include "ruby.h"
7
- #include "rubysig.h"
8
- #include "rubyio.h"
9
- #include "open3.h"
10
-
11
- #include <malloc.h>
12
- #include <io.h>
13
- #include <string.h>
14
- #include <fcntl.h>
15
- #include <sys/stat.h>
16
-
17
- /* Necessary to work with Ruby 1.8.3 or later */
18
- #ifdef HAVE_TYPE_RB_PID_T
19
- #define pid_t rb_pid_t
20
- #endif
21
-
22
- static VALUE win32_last_status = Qnil;
23
- static HANDLE pid_handle = NULL;
24
-
25
- static VALUE ruby_popen(char *, int, VALUE);
26
-
27
- static int rb_io_mode_flags2(int mode){
28
- int flags;
29
-
30
- switch (mode & (O_RDONLY|O_WRONLY|O_RDWR)) {
31
- case O_RDONLY:
32
- flags = FMODE_READABLE;
33
- break;
34
- case O_WRONLY:
35
- flags = FMODE_WRITABLE;
36
- break;
37
- case O_RDWR:
38
- flags = FMODE_WRITABLE|FMODE_READABLE;
39
- break;
40
- }
41
-
42
- #ifdef O_BINARY
43
- if(mode & O_BINARY)
44
- flags |= FMODE_BINMODE;
45
- #endif
46
-
47
- return flags;
48
- }
49
-
50
- static char* rb_io_modenum_mode(int flags, char* mode){
51
- char *p = mode;
52
-
53
- switch(flags & (O_RDONLY|O_WRONLY|O_RDWR)){
54
- case O_RDONLY:
55
- *p++ = 'r';
56
- break;
57
- case O_WRONLY:
58
- *p++ = 'w';
59
- break;
60
- case O_RDWR:
61
- *p++ = 'r';
62
- *p++ = '+';
63
- break;
64
- }
65
-
66
- *p++ = '\0';
67
-
68
- #ifdef O_BINARY
69
- if(flags & O_BINARY){
70
- if(mode[1] == '+'){
71
- mode[1] = 'b';
72
- mode[2] = '+';
73
- mode[3] = '\0';
74
- }
75
- else{
76
- mode[1] = 'b'; mode[2] = '\0';
77
- }
78
- }
79
- #endif
80
- return mode;
81
- }
82
-
83
- /* Used to close io handle */
84
- static VALUE io_close(VALUE val) {
85
- int i;
86
-
87
- for(i = 0; i < 3; i++){
88
- if(rb_funcall(RARRAY(val)->ptr[i], rb_intern("closed?"), 0) == Qfalse)
89
- rb_funcall(RARRAY(val)->ptr[i], rb_intern("close"), 0);
90
- }
91
-
92
- return Qnil;
93
- }
94
-
95
- /*
96
- * call-seq:
97
- * Open3.popen3(cmd, mode='t', show=false)
98
- * Open3.popen3(cmd, mode='t', show=false){ |io_in, io_out, io_err| ... }
99
- *
100
- * Executes 'command', returning an array of three IO handles representing
101
- * STDIN, STDOUT and STDERR, respectively. In block form these IO handles
102
- * are yielded back to the block and automatically closed at the end of the
103
- * block.
104
- *
105
- * You may optionally pass a mode flag of 't' (text, the default) or 'b'
106
- * (binary) to this method.
107
- *
108
- * If the 'show' variable is set to true, then a console window is shown.
109
- */
110
- static VALUE win32_popen3(int argc, VALUE *argv, VALUE klass)
111
- {
112
- VALUE v_name, v_mode, v_port;
113
- VALUE v_show_window = Qfalse;
114
- char mbuf[4];
115
- int tm = 0;
116
- char *mode = "t";
117
-
118
- rb_scan_args(argc, argv, "12", &v_name, &v_mode, &v_show_window);
119
-
120
- // Mode can be either a string or a number
121
- if(!NIL_P(v_mode)){
122
- if(FIXNUM_P(v_mode))
123
- mode = rb_io_modenum_mode(FIX2INT(v_mode), mbuf);
124
- else
125
- mode = StringValuePtr(v_mode);
126
- }
127
-
128
- if(*mode == 't')
129
- tm = _O_TEXT;
130
- else if(*mode != 'b')
131
- rb_raise(rb_eArgError, "popen3() arg 2 must be 't' or 'b'");
132
- else
133
- tm = _O_BINARY;
134
-
135
- v_port = ruby_popen(StringValuePtr(v_name), tm, v_show_window);
136
-
137
- // Ensure handles are closed in block form
138
- if(rb_block_given_p()){
139
- return rb_ensure(rb_yield_splat, v_port, io_close, v_port);
140
- }
141
-
142
- return v_port;
143
- }
144
-
145
- static BOOL RubyCreateProcess(char *cmdstring, HANDLE hStdin, HANDLE hStdout,
146
- HANDLE hStderr, HANDLE *hProcess, pid_t *pid, VALUE v_show_window)
147
- {
148
- PROCESS_INFORMATION piProcInfo;
149
- STARTUPINFO siStartInfo;
150
- char *s1,*s2, *s3 = " /c ";
151
- int i, x;
152
-
153
- if(i = GetEnvironmentVariable("COMSPEC", NULL, 0)){
154
- char *comshell;
155
- s1 = (char *)_alloca(i);
156
-
157
- if(!(x = GetEnvironmentVariable("COMSPEC", s1, i)))
158
- return x;
159
-
160
- /* Explicitly check if we are using COMMAND.COM. If we are
161
- * then use the w9xpopen hack.
162
- */
163
- comshell = s1 + x;
164
-
165
- while(comshell >= s1 && *comshell != '\\')
166
- --comshell;
167
-
168
- ++comshell;
169
-
170
- // Windows 95, 98 and ME are not supported
171
- if(GetVersion() < 0x80000000 && _stricmp(comshell, "command.com") != 0){
172
- x = i + strlen(s3) + strlen(cmdstring) + 1;
173
- s2 = ALLOCA_N(char, x);
174
- sprintf(s2, "%s%s%s", s1, s3, cmdstring);
175
- }
176
- else{
177
- rb_raise(rb_eRuntimeError,"not supported on this platform");
178
- }
179
- }
180
- else{
181
- // Could be an else here to try cmd.exe / command.com in the path.
182
- // Now we'll just error out..
183
- rb_raise(rb_eRuntimeError,
184
- "Cannot locate a COMSPEC environment variable to use as the shell"
185
- );
186
- return FALSE;
187
- }
188
-
189
- ZeroMemory(&siStartInfo, sizeof(STARTUPINFO));
190
-
191
- siStartInfo.cb = sizeof(STARTUPINFO);
192
- siStartInfo.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
193
- siStartInfo.hStdInput = hStdin;
194
- siStartInfo.hStdOutput = hStdout;
195
- siStartInfo.hStdError = hStderr;
196
- siStartInfo.wShowWindow = SW_HIDE;
197
-
198
- if(v_show_window == Qtrue)
199
- siStartInfo.wShowWindow = SW_SHOW;
200
-
201
- // Try the command first without COMSPEC
202
- if(CreateProcess(
203
- NULL,
204
- cmdstring,
205
- NULL,
206
- NULL,
207
- TRUE,
208
- 0,
209
- NULL,
210
- NULL,
211
- &siStartInfo,
212
- &piProcInfo)
213
- ){
214
- // Close the handles now so anyone waiting is woken.
215
- CloseHandle(piProcInfo.hThread);
216
-
217
- // Return process handle
218
- *hProcess = piProcInfo.hProcess;
219
- *pid = (pid_t)piProcInfo.dwProcessId;
220
- return TRUE;
221
- }
222
-
223
- // If that failed, try again with COMSPEC
224
- if(CreateProcess(
225
- NULL,
226
- s2,
227
- NULL,
228
- NULL,
229
- TRUE,
230
- CREATE_NEW_CONSOLE,
231
- NULL,
232
- NULL,
233
- &siStartInfo,
234
- &piProcInfo)
235
- ){
236
- // Close the handles now so anyone waiting is woken.
237
- CloseHandle(piProcInfo.hThread);
238
-
239
- // Return process handle
240
- *hProcess = piProcInfo.hProcess;
241
- *pid = (pid_t)piProcInfo.dwProcessId;
242
- return TRUE;
243
- }
244
-
245
- rb_raise(rb_eRuntimeError, "CreateProcess() failed: %s",
246
- ErrorDescription(GetLastError())
247
- );
248
-
249
- return FALSE;
250
- }
251
-
252
- /* Set the Process::Status. This code is based on patches by Samuel Tesla and
253
- * John-Mason Shackelford.
254
- */
255
- static void win32_set_last_status(const int status, const int pid)
256
- {
257
- // rb_last_status is defined in process.c in the main ruby.exe
258
- __declspec (dllimport) extern VALUE rb_last_status;
259
- VALUE klass = rb_path2class("Process::Status");
260
- VALUE process_status = rb_obj_alloc(klass);
261
- rb_iv_set(process_status, "status", INT2FIX(status << 8));
262
- rb_iv_set(process_status, "pid", INT2FIX(pid));
263
- rb_last_status = process_status;
264
- win32_last_status = process_status;
265
- }
266
-
267
- static void win32_pipe_finalize(OpenFile *file, int noraise)
268
- {
269
- int status;
270
-
271
- if(file->f){
272
- fclose(file->f);
273
- file->f = NULL;
274
- }
275
-
276
- if(file->f2){
277
- fclose(file->f2);
278
- file->f2 = NULL;
279
- }
280
-
281
- if(pid_handle != NULL){
282
- GetExitCodeProcess(pid_handle, &status);
283
-
284
- if(status != STILL_ACTIVE){
285
- CloseHandle(pid_handle);
286
- pid_handle = NULL;
287
- win32_set_last_status(status, file->pid);
288
- }
289
- }
290
- }
291
-
292
- // The following code is based off of KB: Q190351
293
- static VALUE ruby_popen(char *cmdstring, int mode, VALUE v_show_window)
294
- {
295
- HANDLE hChildStdinRd, hChildStdinWr, hChildStdoutRd, hChildStdoutWr,
296
- hChildStderrRd, hChildStderrWr, hChildStdinWrDup, hChildStdoutRdDup,
297
- hChildStderrRdDup, hProcess;
298
-
299
- pid_t pid;
300
-
301
- SECURITY_ATTRIBUTES saAttr;
302
- BOOL fSuccess;
303
- int fd1, fd2, fd3;
304
- FILE *f1, *f2, *f3;
305
- long file_count;
306
- VALUE port;
307
- VALUE p1,p2,p3;
308
- int modef;
309
- OpenFile *fptr;
310
- char *m1, *m2;
311
-
312
- saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
313
- saAttr.bInheritHandle = TRUE;
314
- saAttr.lpSecurityDescriptor = NULL;
315
-
316
- if(!CreatePipe(&hChildStdinRd, &hChildStdinWr, &saAttr, 0)) {
317
- rb_raise(rb_eRuntimeError, "CreatePipe() failed: %s",
318
- ErrorDescription(GetLastError())
319
- );
320
- }
321
-
322
- /* Create new output read handle and the input write handle. Set
323
- * the inheritance properties to FALSE. Otherwise, the child inherits
324
- * the these handles; resulting in non-closeable handles to the pipes
325
- * being created.
326
- */
327
- fSuccess = DuplicateHandle(
328
- GetCurrentProcess(),
329
- hChildStdinWr,
330
- GetCurrentProcess(),
331
- &hChildStdinWrDup,
332
- 0,
333
- FALSE,
334
- DUPLICATE_SAME_ACCESS
335
- );
336
-
337
- if(!fSuccess){
338
- rb_raise(rb_eRuntimeError, "DuplicateHandle() failed: %s",
339
- ErrorDescription(GetLastError())
340
- );
341
- }
342
-
343
- // Close the inheritable version of ChildStdin that we're using
344
- CloseHandle(hChildStdinWr);
345
-
346
- if(!CreatePipe(&hChildStdoutRd, &hChildStdoutWr, &saAttr, 0)) {
347
- rb_raise(rb_eRuntimeError, "CreatePipe() failed: %s",
348
- ErrorDescription(GetLastError())
349
- );
350
- }
351
-
352
- fSuccess = DuplicateHandle(
353
- GetCurrentProcess(),
354
- hChildStdoutRd,
355
- GetCurrentProcess(),
356
- &hChildStdoutRdDup,
357
- 0,
358
- FALSE,
359
- DUPLICATE_SAME_ACCESS
360
- );
361
-
362
- if(!fSuccess){
363
- rb_raise(rb_eRuntimeError, "DuplicateHandle() failed: %s",
364
- ErrorDescription(GetLastError())
365
- );
366
- }
367
-
368
- // Close the inheritable version of ChildStdout that we're using.
369
- CloseHandle(hChildStdoutRd);
370
-
371
- if(!CreatePipe(&hChildStderrRd, &hChildStderrWr, &saAttr, 0)) {
372
- rb_raise(rb_eRuntimeError, "CreatePipe() failed: %s",
373
- ErrorDescription(GetLastError())
374
- );
375
- }
376
-
377
- fSuccess = DuplicateHandle(
378
- GetCurrentProcess(),
379
- hChildStderrRd,
380
- GetCurrentProcess(),
381
- &hChildStderrRdDup,
382
- 0,
383
- FALSE,
384
- DUPLICATE_SAME_ACCESS
385
- );
386
-
387
- if (!fSuccess) {
388
- rb_raise(rb_eRuntimeError, "DuplicateHandle() failed: %s",
389
- ErrorDescription(GetLastError())
390
- );
391
- }
392
-
393
- // Close the inheritable version of ChildStdErr that we're using.
394
- CloseHandle(hChildStderrRd);
395
-
396
- if(mode & _O_TEXT){
397
- m1 = "r";
398
- m2 = "w";
399
- }
400
- else{
401
- m1 = "rb";
402
- m2 = "wb";
403
- }
404
-
405
- // Convert HANDLE's into file descriptors and, ultimately, Ruby IO objects.
406
- fd1 = _open_osfhandle((long)hChildStdinWrDup, mode);
407
- f1 = _fdopen(fd1, m2);
408
-
409
- fd2 = _open_osfhandle((long)hChildStdoutRdDup, mode);
410
- f2 = _fdopen(fd2, m1);
411
-
412
- fd3 = _open_osfhandle((long)hChildStderrRdDup, mode);
413
- f3 = _fdopen(fd3, m1);
414
-
415
- file_count = 3;
416
-
417
- if(!RubyCreateProcess(cmdstring,hChildStdinRd,hChildStdoutWr,
418
- hChildStderrWr, &hProcess, &pid, v_show_window))
419
- {
420
- return Qnil;
421
- }
422
-
423
- // I think this is only possible on Win9x, but we'll leave it here
424
- if(pid < 0){
425
- pid = -pid;
426
- }
427
-
428
- modef = rb_io_mode_flags(m2);
429
- p1 = io_alloc(rb_cIO);
430
- MakeOpenFile(p1, fptr);
431
-
432
- fptr->finalize = win32_pipe_finalize;
433
- fptr->mode = modef;
434
- fptr->pid = pid;
435
-
436
- if(modef & FMODE_READABLE){
437
- fptr->f = f1;
438
- }
439
-
440
- if(modef & FMODE_WRITABLE){
441
- if(fptr->f){
442
- fptr->f2 = f1;
443
- }
444
- else{
445
- fptr->f = f1;
446
- }
447
- fptr->mode |= FMODE_SYNC;
448
- }
449
-
450
- modef = rb_io_mode_flags(m1);
451
- p2 = io_alloc(rb_cIO);
452
- MakeOpenFile(p2, fptr);
453
-
454
- pid_handle = hProcess;
455
- fptr->finalize = win32_pipe_finalize;
456
- fptr->mode = modef;
457
- fptr->pid = pid;
458
-
459
- if(modef & FMODE_READABLE){
460
- fptr->f = f2;
461
- }
462
- if(modef & FMODE_WRITABLE){
463
- if(fptr->f){
464
- fptr->f2 = f2;
465
- }
466
- else{
467
- fptr->f = f2;
468
- }
469
- fptr->mode |= FMODE_SYNC;
470
- }
471
-
472
- modef = rb_io_mode_flags(m1);
473
- p3 = io_alloc(rb_cIO);
474
- MakeOpenFile(p3, fptr);
475
-
476
- fptr->finalize = win32_pipe_finalize;
477
- fptr->mode = modef;
478
- fptr->pid = pid;
479
-
480
- if(modef & FMODE_READABLE){
481
- fptr->f = f3;
482
- }
483
-
484
- if(modef & FMODE_WRITABLE){
485
- if(fptr->f){
486
- fptr->f2 = f3;
487
- }
488
- else{
489
- fptr->f = f3;
490
- }
491
- fptr->mode |= FMODE_SYNC;
492
- }
493
-
494
- port = rb_ary_new2(4);
495
- rb_ary_push(port,(VALUE)p1);
496
- rb_ary_push(port,(VALUE)p2);
497
- rb_ary_push(port,(VALUE)p3);
498
- rb_ary_push(port,UINT2NUM((DWORD)pid));
499
-
500
- /* Child is launched. Close the parents copy of those pipe
501
- * handles that only the child should have open. You need to
502
- * make sure that no handles to the write end of the output pipe
503
- * are maintained in this process or else the pipe will not close
504
- * when the child process exits and the ReadFile() will hang.
505
- */
506
-
507
- if(!CloseHandle(hChildStdinRd)){
508
- rb_raise(rb_eRuntimeError, "CloseHandle() failed: %s",
509
- ErrorDescription(GetLastError())
510
- );
511
- }
512
-
513
- if(!CloseHandle(hChildStdoutWr)){
514
- rb_raise(rb_eRuntimeError, "CloseHandle() failed: %s",
515
- ErrorDescription(GetLastError())
516
- );
517
- }
518
-
519
- if(!CloseHandle(hChildStderrWr)){
520
- rb_raise(rb_eRuntimeError, "CloseHandle() failed: %s",
521
- ErrorDescription(GetLastError())
522
- );
523
- }
524
-
525
- return port;
526
- }
527
-
528
- /*
529
- * Spawn an external program and retrieve the stdin, stdout and stderr IO
530
- * handles, as well as the process ID. Use in block or non-block form.
531
- */
532
- void Init_open3()
533
- {
534
- VALUE mOpen3 = rb_define_module("Open3");
535
-
536
- VALUE mOpen4 = rb_define_module("Open4");
537
-
538
- rb_define_module_function(mOpen3, "popen3", win32_popen3, -1);
539
- rb_define_module_function(mOpen4, "popen4", win32_popen3, -1);
540
-
541
- /* 0.3.1: The version of this library */
542
- rb_define_const(mOpen3, "WIN32_OPEN3_VERSION", rb_str_new2(WIN32_OPEN3_VERSION));
543
-
544
- /* 0.3.1: The version of this library */
545
- rb_define_const(mOpen4, "WIN32_OPEN3_VERSION", rb_str_new2(WIN32_OPEN3_VERSION));
546
- }
1
+ /***************************************************
2
+ * open3.c
3
+ *
4
+ * Source for the win32-open3 extension.
5
+ ***************************************************/
6
+ #include "ruby.h"
7
+
8
+ #ifdef HAVE_RUBY_IO_H
9
+ #include "ruby/io.h"
10
+ #else
11
+ #include "rubysig.h"
12
+ #include "rubyio.h"
13
+ #endif
14
+
15
+ #include "open3.h"
16
+
17
+ #include <malloc.h>
18
+ #include <io.h>
19
+ #include <string.h>
20
+ #include <fcntl.h>
21
+ #include <sys/stat.h>
22
+
23
+ // Necessary to work with Ruby 1.8.3 or later
24
+ #ifdef HAVE_TYPE_RB_PID_T
25
+ #define pid_t rb_pid_t
26
+ #endif
27
+
28
+ // Ruby 1.9.x
29
+ #ifndef RSTRING_PTR
30
+ #define RSTRING_PTR(s) (RSTRING(s)->ptr)
31
+ #endif
32
+ #ifndef RSTRING_LEN
33
+ #define RSTRING_LEN(s) (RSTRING(s)->len)
34
+ #endif
35
+
36
+ #ifndef RARRAY_PTR
37
+ #define RARRAY_PTR(a) (RARRAY(a)->ptr)
38
+ #endif
39
+ #ifndef RARRAY_LEN
40
+ #define RARRAY_LEN(a) (RARRAY(a)->len)
41
+ #endif
42
+
43
+ static VALUE win32_last_status = Qnil;
44
+ static HANDLE pid_handle = NULL;
45
+
46
+ static VALUE ruby_popen(char *, int, VALUE);
47
+
48
+ static int rb_io_mode_flags2(int mode){
49
+ int flags;
50
+
51
+ switch (mode & (O_RDONLY|O_WRONLY|O_RDWR)) {
52
+ case O_RDONLY:
53
+ flags = FMODE_READABLE;
54
+ break;
55
+ case O_WRONLY:
56
+ flags = FMODE_WRITABLE;
57
+ break;
58
+ case O_RDWR:
59
+ flags = FMODE_WRITABLE|FMODE_READABLE;
60
+ break;
61
+ }
62
+
63
+ #ifdef O_BINARY
64
+ if(mode & O_BINARY)
65
+ flags |= FMODE_BINMODE;
66
+ #endif
67
+
68
+ return flags;
69
+ }
70
+
71
+ static char* rb_io_modenum_mode(int flags, char* mode){
72
+ char *p = mode;
73
+
74
+ switch(flags & (O_RDONLY|O_WRONLY|O_RDWR)){
75
+ case O_RDONLY:
76
+ *p++ = 'r';
77
+ break;
78
+ case O_WRONLY:
79
+ *p++ = 'w';
80
+ break;
81
+ case O_RDWR:
82
+ *p++ = 'r';
83
+ *p++ = '+';
84
+ break;
85
+ }
86
+
87
+ *p++ = '\0';
88
+
89
+ #ifdef O_BINARY
90
+ if(flags & O_BINARY){
91
+ if(mode[1] == '+'){
92
+ mode[1] = 'b';
93
+ mode[2] = '+';
94
+ mode[3] = '\0';
95
+ }
96
+ else{
97
+ mode[1] = 'b'; mode[2] = '\0';
98
+ }
99
+ }
100
+ #endif
101
+ return mode;
102
+ }
103
+
104
+ /* Used to close io handle */
105
+ static VALUE io_close(VALUE val) {
106
+ int i;
107
+
108
+ for(i = 0; i < 3; i++){
109
+ if(rb_funcall(RARRAY_PTR(val)[i], rb_intern("closed?"), 0) == Qfalse)
110
+ rb_funcall(RARRAY_PTR(val)[i], rb_intern("close"), 0);
111
+ }
112
+
113
+ return Qnil;
114
+ }
115
+
116
+ /*
117
+ * call-seq:
118
+ * Open3.popen3(cmd, mode='t', show=false)
119
+ * Open3.popen3(cmd, mode='t', show=false){ |io_in, io_out, io_err| ... }
120
+ *
121
+ * Executes 'command', returning an array of three IO handles representing
122
+ * STDIN, STDOUT and STDERR, respectively. In block form these IO handles
123
+ * are yielded back to the block and automatically closed at the end of the
124
+ * block.
125
+ *
126
+ * You may optionally pass a mode flag of 't' (text, the default) or 'b'
127
+ * (binary) to this method.
128
+ *
129
+ * If the 'show' variable is set to true, then a console window is shown.
130
+ */
131
+ static VALUE win32_popen3(int argc, VALUE *argv, VALUE klass)
132
+ {
133
+ VALUE v_name, v_mode, v_port;
134
+ VALUE v_show_window = Qfalse;
135
+ char mbuf[4];
136
+ int tm = 0;
137
+ char *mode = "t";
138
+
139
+ rb_scan_args(argc, argv, "12", &v_name, &v_mode, &v_show_window);
140
+
141
+ // Mode can be either a string or a number
142
+ if(!NIL_P(v_mode)){
143
+ if(FIXNUM_P(v_mode))
144
+ mode = rb_io_modenum_mode(FIX2INT(v_mode), mbuf);
145
+ else
146
+ mode = StringValuePtr(v_mode);
147
+ }
148
+
149
+ if(*mode == 't')
150
+ tm = _O_TEXT;
151
+ else if(*mode != 'b')
152
+ rb_raise(rb_eArgError, "popen3() arg 2 must be 't' or 'b'");
153
+ else
154
+ tm = _O_BINARY;
155
+
156
+ v_port = ruby_popen(StringValuePtr(v_name), tm, v_show_window);
157
+
158
+ // Ensure handles are closed in block form
159
+ if(rb_block_given_p()){
160
+ return rb_ensure(rb_yield_splat, v_port, io_close, v_port);
161
+ }
162
+
163
+ return v_port;
164
+ }
165
+
166
+ static BOOL RubyCreateProcess(char *cmdstring, HANDLE hStdin, HANDLE hStdout,
167
+ HANDLE hStderr, HANDLE *hProcess, pid_t *pid, VALUE v_show_window)
168
+ {
169
+ PROCESS_INFORMATION piProcInfo;
170
+ STARTUPINFO siStartInfo;
171
+ char *s1,*s2, *s3 = " /c ";
172
+ int i, x;
173
+
174
+ if(i = GetEnvironmentVariable("COMSPEC", NULL, 0)){
175
+ char *comshell;
176
+ s1 = (char *)_alloca(i);
177
+
178
+ if(!(x = GetEnvironmentVariable("COMSPEC", s1, i)))
179
+ return x;
180
+
181
+ /* Explicitly check if we are using COMMAND.COM. If we are
182
+ * then use the w9xpopen hack.
183
+ */
184
+ comshell = s1 + x;
185
+
186
+ while(comshell >= s1 && *comshell != '\\')
187
+ --comshell;
188
+
189
+ ++comshell;
190
+
191
+ // Windows 95, 98 and ME are not supported
192
+ if(GetVersion() < 0x80000000 && _stricmp(comshell, "command.com") != 0){
193
+ x = i + strlen(s3) + strlen(cmdstring) + 1;
194
+ s2 = ALLOCA_N(char, x);
195
+ sprintf(s2, "%s%s%s", s1, s3, cmdstring);
196
+ }
197
+ else{
198
+ rb_raise(rb_eRuntimeError,"not supported on this platform");
199
+ }
200
+ }
201
+ else{
202
+ // Could be an else here to try cmd.exe / command.com in the path.
203
+ // Now we'll just error out..
204
+ rb_raise(rb_eRuntimeError,
205
+ "Cannot locate a COMSPEC environment variable to use as the shell"
206
+ );
207
+ return FALSE;
208
+ }
209
+
210
+ ZeroMemory(&siStartInfo, sizeof(STARTUPINFO));
211
+
212
+ siStartInfo.cb = sizeof(STARTUPINFO);
213
+ siStartInfo.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
214
+ siStartInfo.hStdInput = hStdin;
215
+ siStartInfo.hStdOutput = hStdout;
216
+ siStartInfo.hStdError = hStderr;
217
+ siStartInfo.wShowWindow = SW_HIDE;
218
+
219
+ if(v_show_window == Qtrue)
220
+ siStartInfo.wShowWindow = SW_SHOW;
221
+
222
+ // Try the command first without COMSPEC
223
+ if(CreateProcess(
224
+ NULL,
225
+ cmdstring,
226
+ NULL,
227
+ NULL,
228
+ TRUE,
229
+ 0,
230
+ NULL,
231
+ NULL,
232
+ &siStartInfo,
233
+ &piProcInfo)
234
+ ){
235
+ // Close the handles now so anyone waiting is woken.
236
+ CloseHandle(piProcInfo.hThread);
237
+
238
+ // Return process handle
239
+ *hProcess = piProcInfo.hProcess;
240
+ *pid = (pid_t)piProcInfo.dwProcessId;
241
+ return TRUE;
242
+ }
243
+
244
+ // If that failed, try again with COMSPEC
245
+ if(CreateProcess(
246
+ NULL,
247
+ s2,
248
+ NULL,
249
+ NULL,
250
+ TRUE,
251
+ CREATE_NEW_CONSOLE,
252
+ NULL,
253
+ NULL,
254
+ &siStartInfo,
255
+ &piProcInfo)
256
+ ){
257
+ // Close the handles now so anyone waiting is woken.
258
+ CloseHandle(piProcInfo.hThread);
259
+
260
+ // Return process handle
261
+ *hProcess = piProcInfo.hProcess;
262
+ *pid = (pid_t)piProcInfo.dwProcessId;
263
+ return TRUE;
264
+ }
265
+
266
+ rb_raise(rb_eRuntimeError, "CreateProcess() failed: %s",
267
+ ErrorDescription(GetLastError())
268
+ );
269
+
270
+ return FALSE;
271
+ }
272
+
273
+ /* Set the Process::Status. This code is based on patches by Samuel Tesla and
274
+ * John-Mason Shackelford.
275
+ */
276
+ static void win32_set_last_status(const int status, const int pid)
277
+ {
278
+ // rb_last_status is defined in process.c in the main ruby.exe
279
+ __declspec (dllimport) extern VALUE rb_last_status;
280
+ VALUE klass = rb_path2class("Process::Status");
281
+ VALUE process_status = rb_obj_alloc(klass);
282
+ rb_iv_set(process_status, "status", INT2FIX(status << 8));
283
+ rb_iv_set(process_status, "pid", INT2FIX(pid));
284
+ rb_last_status = process_status;
285
+ win32_last_status = process_status;
286
+ }
287
+
288
+ static void win32_pipe_finalize(OpenFile *file, int noraise)
289
+ {
290
+ DWORD status;
291
+
292
+ if(file->f){
293
+ fclose(file->f);
294
+ file->f = NULL;
295
+ }
296
+
297
+ if(file->f2){
298
+ fclose(file->f2);
299
+ file->f2 = NULL;
300
+ }
301
+
302
+ if(pid_handle != NULL){
303
+ GetExitCodeProcess(pid_handle, &status);
304
+
305
+ if(status != STILL_ACTIVE){
306
+ CloseHandle(pid_handle);
307
+ pid_handle = NULL;
308
+ win32_set_last_status(status, file->pid);
309
+ }
310
+ }
311
+ }
312
+
313
+ // The following code is based off of KB: Q190351
314
+ static VALUE ruby_popen(char *cmdstring, int mode, VALUE v_show_window)
315
+ {
316
+ HANDLE hChildStdinRd, hChildStdinWr, hChildStdoutRd, hChildStdoutWr,
317
+ hChildStderrRd, hChildStderrWr, hChildStdinWrDup, hChildStdoutRdDup,
318
+ hChildStderrRdDup, hProcess;
319
+
320
+ pid_t pid;
321
+
322
+ SECURITY_ATTRIBUTES saAttr;
323
+ BOOL fSuccess;
324
+ int fd1, fd2, fd3;
325
+ FILE *f1, *f2, *f3;
326
+ long file_count;
327
+ VALUE port;
328
+ VALUE p1,p2,p3;
329
+ int modef;
330
+ OpenFile *fptr;
331
+ char *m1, *m2;
332
+
333
+ saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
334
+ saAttr.bInheritHandle = TRUE;
335
+ saAttr.lpSecurityDescriptor = NULL;
336
+
337
+ if(!CreatePipe(&hChildStdinRd, &hChildStdinWr, &saAttr, 0)) {
338
+ rb_raise(rb_eRuntimeError, "CreatePipe() failed: %s",
339
+ ErrorDescription(GetLastError())
340
+ );
341
+ }
342
+
343
+ /* Create new output read handle and the input write handle. Set
344
+ * the inheritance properties to FALSE. Otherwise, the child inherits
345
+ * the these handles; resulting in non-closeable handles to the pipes
346
+ * being created.
347
+ */
348
+ fSuccess = DuplicateHandle(
349
+ GetCurrentProcess(),
350
+ hChildStdinWr,
351
+ GetCurrentProcess(),
352
+ &hChildStdinWrDup,
353
+ 0,
354
+ FALSE,
355
+ DUPLICATE_SAME_ACCESS
356
+ );
357
+
358
+ if(!fSuccess){
359
+ rb_raise(rb_eRuntimeError, "DuplicateHandle() failed: %s",
360
+ ErrorDescription(GetLastError())
361
+ );
362
+ }
363
+
364
+ // Close the inheritable version of ChildStdin that we're using
365
+ CloseHandle(hChildStdinWr);
366
+
367
+ if(!CreatePipe(&hChildStdoutRd, &hChildStdoutWr, &saAttr, 0)) {
368
+ rb_raise(rb_eRuntimeError, "CreatePipe() failed: %s",
369
+ ErrorDescription(GetLastError())
370
+ );
371
+ }
372
+
373
+ fSuccess = DuplicateHandle(
374
+ GetCurrentProcess(),
375
+ hChildStdoutRd,
376
+ GetCurrentProcess(),
377
+ &hChildStdoutRdDup,
378
+ 0,
379
+ FALSE,
380
+ DUPLICATE_SAME_ACCESS
381
+ );
382
+
383
+ if(!fSuccess){
384
+ rb_raise(rb_eRuntimeError, "DuplicateHandle() failed: %s",
385
+ ErrorDescription(GetLastError())
386
+ );
387
+ }
388
+
389
+ // Close the inheritable version of ChildStdout that we're using.
390
+ CloseHandle(hChildStdoutRd);
391
+
392
+ if(!CreatePipe(&hChildStderrRd, &hChildStderrWr, &saAttr, 0)) {
393
+ rb_raise(rb_eRuntimeError, "CreatePipe() failed: %s",
394
+ ErrorDescription(GetLastError())
395
+ );
396
+ }
397
+
398
+ fSuccess = DuplicateHandle(
399
+ GetCurrentProcess(),
400
+ hChildStderrRd,
401
+ GetCurrentProcess(),
402
+ &hChildStderrRdDup,
403
+ 0,
404
+ FALSE,
405
+ DUPLICATE_SAME_ACCESS
406
+ );
407
+
408
+ if (!fSuccess) {
409
+ rb_raise(rb_eRuntimeError, "DuplicateHandle() failed: %s",
410
+ ErrorDescription(GetLastError())
411
+ );
412
+ }
413
+
414
+ // Close the inheritable version of ChildStdErr that we're using.
415
+ CloseHandle(hChildStderrRd);
416
+
417
+ if(mode & _O_TEXT){
418
+ m1 = "r";
419
+ m2 = "w";
420
+ }
421
+ else{
422
+ m1 = "rb";
423
+ m2 = "wb";
424
+ }
425
+
426
+ // Convert HANDLE's into file descriptors and, ultimately, Ruby IO objects.
427
+ fd1 = _open_osfhandle((long)hChildStdinWrDup, mode);
428
+ f1 = _fdopen(fd1, m2);
429
+
430
+ fd2 = _open_osfhandle((long)hChildStdoutRdDup, mode);
431
+ f2 = _fdopen(fd2, m1);
432
+
433
+ fd3 = _open_osfhandle((long)hChildStderrRdDup, mode);
434
+ f3 = _fdopen(fd3, m1);
435
+
436
+ file_count = 3;
437
+
438
+ if(!RubyCreateProcess(cmdstring,hChildStdinRd,hChildStdoutWr,
439
+ hChildStderrWr, &hProcess, &pid, v_show_window))
440
+ {
441
+ return Qnil;
442
+ }
443
+
444
+ // I think this is only possible on Win9x, but we'll leave it here
445
+ if(pid < 0){
446
+ pid = -pid;
447
+ }
448
+
449
+ modef = rb_io_mode_flags(m2);
450
+ p1 = io_alloc(rb_cIO);
451
+ MakeOpenFile(p1, fptr);
452
+
453
+ fptr->finalize = win32_pipe_finalize;
454
+ fptr->mode = modef;
455
+ fptr->pid = pid;
456
+
457
+ if(modef & FMODE_READABLE){
458
+ fptr->f = f1;
459
+ }
460
+
461
+ if(modef & FMODE_WRITABLE){
462
+ if(fptr->f){
463
+ fptr->f2 = f1;
464
+ }
465
+ else{
466
+ fptr->f = f1;
467
+ }
468
+ fptr->mode |= FMODE_SYNC;
469
+ }
470
+
471
+ modef = rb_io_mode_flags(m1);
472
+ p2 = io_alloc(rb_cIO);
473
+ MakeOpenFile(p2, fptr);
474
+
475
+ pid_handle = hProcess;
476
+ fptr->finalize = win32_pipe_finalize;
477
+ fptr->mode = modef;
478
+ fptr->pid = pid;
479
+
480
+ if(modef & FMODE_READABLE){
481
+ fptr->f = f2;
482
+ }
483
+ if(modef & FMODE_WRITABLE){
484
+ if(fptr->f){
485
+ fptr->f2 = f2;
486
+ }
487
+ else{
488
+ fptr->f = f2;
489
+ }
490
+ fptr->mode |= FMODE_SYNC;
491
+ }
492
+
493
+ modef = rb_io_mode_flags(m1);
494
+ p3 = io_alloc(rb_cIO);
495
+ MakeOpenFile(p3, fptr);
496
+
497
+ fptr->finalize = win32_pipe_finalize;
498
+ fptr->mode = modef;
499
+ fptr->pid = pid;
500
+
501
+ if(modef & FMODE_READABLE){
502
+ fptr->f = f3;
503
+ }
504
+
505
+ if(modef & FMODE_WRITABLE){
506
+ if(fptr->f){
507
+ fptr->f2 = f3;
508
+ }
509
+ else{
510
+ fptr->f = f3;
511
+ }
512
+ fptr->mode |= FMODE_SYNC;
513
+ }
514
+
515
+ port = rb_ary_new2(4);
516
+ rb_ary_push(port,(VALUE)p1);
517
+ rb_ary_push(port,(VALUE)p2);
518
+ rb_ary_push(port,(VALUE)p3);
519
+ rb_ary_push(port,UINT2NUM((DWORD)pid));
520
+
521
+ /* Child is launched. Close the parents copy of those pipe
522
+ * handles that only the child should have open. You need to
523
+ * make sure that no handles to the write end of the output pipe
524
+ * are maintained in this process or else the pipe will not close
525
+ * when the child process exits and the ReadFile() will hang.
526
+ */
527
+
528
+ if(!CloseHandle(hChildStdinRd)){
529
+ rb_raise(rb_eRuntimeError, "CloseHandle() failed: %s",
530
+ ErrorDescription(GetLastError())
531
+ );
532
+ }
533
+
534
+ if(!CloseHandle(hChildStdoutWr)){
535
+ rb_raise(rb_eRuntimeError, "CloseHandle() failed: %s",
536
+ ErrorDescription(GetLastError())
537
+ );
538
+ }
539
+
540
+ if(!CloseHandle(hChildStderrWr)){
541
+ rb_raise(rb_eRuntimeError, "CloseHandle() failed: %s",
542
+ ErrorDescription(GetLastError())
543
+ );
544
+ }
545
+
546
+ return port;
547
+ }
548
+
549
+ /*
550
+ * Spawn an external program and retrieve the stdin, stdout and stderr IO
551
+ * handles, as well as the process ID. Use in block or non-block form.
552
+ */
553
+ void Init_open3()
554
+ {
555
+ VALUE mOpen3 = rb_define_module("Open3");
556
+
557
+ VALUE mOpen4 = rb_define_module("Open4");
558
+
559
+ rb_define_module_function(mOpen3, "popen3", win32_popen3, -1);
560
+ rb_define_module_function(mOpen4, "popen4", win32_popen3, -1);
561
+
562
+ /* 0.3.1: The version of this library */
563
+ rb_define_const(mOpen3, "WIN32_OPEN3_VERSION", rb_str_new2(WIN32_OPEN3_VERSION));
564
+
565
+ /* 0.3.1: The version of this library */
566
+ rb_define_const(mOpen4, "WIN32_OPEN3_VERSION", rb_str_new2(WIN32_OPEN3_VERSION));
567
+ }