win32-open3 0.2.5-mswin32
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.
- data/CHANGES +37 -0
- data/MANIFEST +14 -0
- data/README +64 -0
- data/doc/open3.txt +75 -0
- data/ext/extconf.rb +4 -0
- data/ext/open3.c +526 -0
- data/ext/open3.h +73 -0
- data/lib/win32/open3.so +0 -0
- data/test/tc_open3.rb +101 -0
- metadata +58 -0
data/CHANGES
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
== 0.2.5 - 8-Dec-2006
|
2
|
+
* Added a WIN32_OPEN3_VERSION constant.
|
3
|
+
* Fixes and updates to the gemspec.
|
4
|
+
* Some internal reorganization.
|
5
|
+
* Minor documentation updates and corrections.
|
6
|
+
|
7
|
+
== 0.2.4 - 4-Jun-2006
|
8
|
+
* Applied a patch for Windows 2000. Thanks again to John-Mason Shackelford
|
9
|
+
for the patch.
|
10
|
+
* Fixed the version number for the last release.
|
11
|
+
|
12
|
+
== 0.2.3 - 3-Jun-2006
|
13
|
+
* Now sets Process::Status when the block form is used. Thanks go to
|
14
|
+
Samuel Tesla and John-Mason Shackelford for their patches.
|
15
|
+
* Improved error messages.
|
16
|
+
* Documentation updated with regards to the block form of the method calls.
|
17
|
+
* Some internal reorganization that you don't care about.
|
18
|
+
|
19
|
+
== 0.2.2 - 10-Feb-2006
|
20
|
+
* Methods now support block form, and all handles are automatically closed
|
21
|
+
at the end of the block (via ensure).
|
22
|
+
|
23
|
+
== 0.2.1 - 27-Oct-2005
|
24
|
+
* Added a type check in the extconf.rb file and did some type mangling in
|
25
|
+
order to handle rb_pid_t in a backwards compatible way. In other words,
|
26
|
+
you need this version to work with 1.8.3 or later.
|
27
|
+
|
28
|
+
== 0.2.0 - 9-Jan-2005
|
29
|
+
* Added the Open4 module and popen4 method. Thanks go to Aslak Hellesoy for
|
30
|
+
the patch.
|
31
|
+
* Added an optional 3rd parameter - the "show" flag. See docs for details.
|
32
|
+
* Made some documentation more rdoc friendly.
|
33
|
+
* Moved the "examples" directory to the toplevel directory.
|
34
|
+
* Added some information to the README file about possible compiler problems.
|
35
|
+
|
36
|
+
== 0.1.0 - 8-Oct-2004
|
37
|
+
* Initial release
|
data/MANIFEST
ADDED
data/README
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
= Description
|
2
|
+
This package provides an Open3.popen3 implementation for Win32 systems
|
3
|
+
|
4
|
+
= Prerequisites
|
5
|
+
Ruby 1.8.0 or later
|
6
|
+
C++ compiler (if building from source).
|
7
|
+
|
8
|
+
= Installation
|
9
|
+
== Manual Installation
|
10
|
+
cd ext
|
11
|
+
ruby extconf.rb
|
12
|
+
nmake
|
13
|
+
ruby tc_open3.rb (optional, in the 'test' directory)
|
14
|
+
nmake install
|
15
|
+
== Gem Installation
|
16
|
+
ruby win32-open3.gemspec
|
17
|
+
gem install win32-open3-X.Y.Z.gem
|
18
|
+
|
19
|
+
= Synopsis
|
20
|
+
require 'win32/open3'
|
21
|
+
|
22
|
+
Open3.popen3('ver'){ |io_in, io_out, io_err|
|
23
|
+
error = io_err.gets
|
24
|
+
if error
|
25
|
+
puts "Error: " + error.chomp
|
26
|
+
exit
|
27
|
+
end
|
28
|
+
puts "Result: " + io_out.readlines.last.chomp
|
29
|
+
}
|
30
|
+
|
31
|
+
= Notes
|
32
|
+
This package is not supported on Windows 95, 98, or ME.
|
33
|
+
This is a stripped down version of Park Heesob's win32_popen package.
|
34
|
+
|
35
|
+
= Developer's Notes
|
36
|
+
This is a repackaging of Heesob's win32_popen module. The purpose of the
|
37
|
+
repackaging was to create a unified API between the existing Ruby open3
|
38
|
+
package and this package.
|
39
|
+
|
40
|
+
The popen2 and posix_popen methods are not included in this release. They
|
41
|
+
were originally part of the win32_popen module, but have been left out.
|
42
|
+
They may be added back in at a later date.
|
43
|
+
|
44
|
+
There are a couple of differences in the Windows version for open3 (which
|
45
|
+
also apply to Open4.popen4) - the mode flag and the show flag. For the
|
46
|
+
mode, you can specify either 't' (text, the default) or 'b' (binary) as a
|
47
|
+
second argument. For the show flag, you can specify either true or false,
|
48
|
+
which will show the console window, or not, depending on the value you
|
49
|
+
pass. The default is false.
|
50
|
+
|
51
|
+
= Known Issues
|
52
|
+
I have noticed that this package (and others) may segfault if you are using
|
53
|
+
the latest version of the Ruby one-click installer and VC++ 7.0 or later.
|
54
|
+
This appears to be an issue between VC++ 6 (which the installer was built
|
55
|
+
with) and VC++ 7.0. Your best solution is to either upgrade your C
|
56
|
+
compiler or to rebuild Ruby from scratch rather than using the installer.
|
57
|
+
|
58
|
+
You can also download a precompiled binary from the project site. Look for a
|
59
|
+
file called 'open3-x.y.z-vc6.so' on the 'files' page, where 'x.y.z' is a
|
60
|
+
version number, if you want a binary that's compatible with Curt Hibbs one
|
61
|
+
click installer.
|
62
|
+
|
63
|
+
= More Documentation
|
64
|
+
See the doc/open3.txt file for more details.
|
data/doc/open3.txt
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
= Description
|
2
|
+
An open3 library for Win32
|
3
|
+
|
4
|
+
= Synopsis
|
5
|
+
require 'win32/open3'
|
6
|
+
|
7
|
+
Open3.popen3('ver'){ |io_in, io_out, io_err|
|
8
|
+
error = io_err.gets
|
9
|
+
if error
|
10
|
+
puts "Error: " + error.chomp
|
11
|
+
exit
|
12
|
+
end
|
13
|
+
puts "Result: " + io_out.readlines.last.chomp
|
14
|
+
}
|
15
|
+
|
16
|
+
= Module functions
|
17
|
+
Open3.popen3(command, mode='t', show=false)
|
18
|
+
|
19
|
+
Open3.popen3(command, mode='t', show=false){ |io_in, io_out, io_err| ... }
|
20
|
+
|
21
|
+
Executes 'command', returning an array of three IO handles representing
|
22
|
+
STDIN, STDOUT and STDERR, respectively. In block form these IO handles
|
23
|
+
are yielded back to the block and automatically closed at the end of the
|
24
|
+
block.
|
25
|
+
|
26
|
+
You may optionally pass a mode flag of 't' (text, the default) or 'b'
|
27
|
+
(binary) to this method.
|
28
|
+
|
29
|
+
If the 'show' variable is set to true, then a console window is shown.
|
30
|
+
|
31
|
+
Open4.popen4(command, mode='t', show=false)
|
32
|
+
|
33
|
+
Open4.popen4(command, mode='t', show=false){ |io_in, io_out, io_err, pid| ... }
|
34
|
+
|
35
|
+
Executes 'command', returning an array consisting of three IO handles,
|
36
|
+
representing STDIN, STDOUT and STDERR, respectively, as well as the PID
|
37
|
+
of the newly generated process. In block form these IO handles and pid
|
38
|
+
are yielded back to the block and automatically closed at the end of the
|
39
|
+
block.
|
40
|
+
|
41
|
+
You may optionally pass a mode flag of 't' (text, the default) or 'b'
|
42
|
+
(binary) to this method.
|
43
|
+
|
44
|
+
If the 'show' variable is set to true, then a console window is shown.
|
45
|
+
|
46
|
+
= Notes
|
47
|
+
This is a remake of Park Heesob's win32_popen package. It has been reworked
|
48
|
+
in order to make the API (nearly) identical to the Open3 package currently
|
49
|
+
included in the Ruby standard library.
|
50
|
+
|
51
|
+
For now only the popen3 and popen4 methods have been included. In later
|
52
|
+
releases we will probably add the popen2 and posix_popen methods back in.
|
53
|
+
|
54
|
+
= Acknowledgements
|
55
|
+
Thanks go to Samuel Tesla and John-Mason Shackelford for their patches that
|
56
|
+
enabled us to set Process::Status.
|
57
|
+
|
58
|
+
= Known Bugs
|
59
|
+
None that I know of. Please log any other bug reports on the RubyForge
|
60
|
+
project page at http://www.rubyforge.net/projects/win32utils.
|
61
|
+
|
62
|
+
= License
|
63
|
+
Ruby's
|
64
|
+
|
65
|
+
= Copyright
|
66
|
+
(C) 2003-2006 Daniel J. Berger, All Rights Reserved .
|
67
|
+
|
68
|
+
= Warranty
|
69
|
+
This package is provided "as is" and without any express or
|
70
|
+
implied warranties, including, without limitation, the implied
|
71
|
+
warranties of merchantability and fitness for a particular purpose.
|
72
|
+
|
73
|
+
= Authors
|
74
|
+
Park Heesob
|
75
|
+
Daniel J. Berger
|
data/ext/extconf.rb
ADDED
data/ext/open3.c
ADDED
@@ -0,0 +1,526 @@
|
|
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
|
+
}
|
46
|
+
#endif
|
47
|
+
|
48
|
+
return flags;
|
49
|
+
}
|
50
|
+
|
51
|
+
static char* rb_io_modenum_mode(int flags, char* mode){
|
52
|
+
char *p = mode;
|
53
|
+
|
54
|
+
switch(flags & (O_RDONLY|O_WRONLY|O_RDWR)){
|
55
|
+
case O_RDONLY:
|
56
|
+
*p++ = 'r';
|
57
|
+
break;
|
58
|
+
case O_WRONLY:
|
59
|
+
*p++ = 'w';
|
60
|
+
break;
|
61
|
+
case O_RDWR:
|
62
|
+
*p++ = 'r';
|
63
|
+
*p++ = '+';
|
64
|
+
break;
|
65
|
+
}
|
66
|
+
|
67
|
+
*p++ = '\0';
|
68
|
+
#ifdef O_BINARY
|
69
|
+
if(flags & O_BINARY){
|
70
|
+
if(mode[1] == '+'){
|
71
|
+
mode[1] = 'b'; mode[2] = '+'; mode[3] = '\0';
|
72
|
+
}
|
73
|
+
else{
|
74
|
+
mode[1] = 'b'; mode[2] = '\0';
|
75
|
+
}
|
76
|
+
}
|
77
|
+
#endif
|
78
|
+
return mode;
|
79
|
+
}
|
80
|
+
|
81
|
+
/* Used to close io handle */
|
82
|
+
static VALUE io_close(VALUE val) {
|
83
|
+
int i;
|
84
|
+
for(i=0; i<3; i++){
|
85
|
+
if(rb_funcall(RARRAY(val)->ptr[i], rb_intern("closed?"), 0) == Qfalse)
|
86
|
+
rb_funcall(RARRAY(val)->ptr[i], rb_intern("close"), 0);
|
87
|
+
}
|
88
|
+
return Qnil;
|
89
|
+
}
|
90
|
+
|
91
|
+
/*
|
92
|
+
* call-seq:
|
93
|
+
* Open3.popen3(cmd, mode='t', show=false)
|
94
|
+
* Open3.popen3(cmd, mode='t', show=false){ |io_in, io_out, io_err| ... }
|
95
|
+
*
|
96
|
+
* Executes 'command', returning an array of three IO handles representing
|
97
|
+
* STDIN, STDOUT and STDERR, respectively. In block form these IO handles
|
98
|
+
* are yielded back to the block and automatically closed at the end of the
|
99
|
+
* block.
|
100
|
+
*
|
101
|
+
* You may optionally pass a mode flag of 't' (text, the default) or 'b'
|
102
|
+
* (binary) to this method.
|
103
|
+
*
|
104
|
+
* If the 'show' variable is set to true, then a console window is shown.
|
105
|
+
*/
|
106
|
+
static VALUE win32_popen3(int argc, VALUE *argv, VALUE klass)
|
107
|
+
{
|
108
|
+
VALUE pname, pmode, port;
|
109
|
+
VALUE v_show_window = Qfalse;
|
110
|
+
char mbuf[4];
|
111
|
+
int tm = 0;
|
112
|
+
char *mode = "t";
|
113
|
+
|
114
|
+
rb_scan_args(argc, argv, "12", &pname, &pmode, &v_show_window);
|
115
|
+
|
116
|
+
/* Mode can be either a string or a number */
|
117
|
+
if(!NIL_P(pmode)){
|
118
|
+
if(FIXNUM_P(pmode)){
|
119
|
+
mode = rb_io_modenum_mode(FIX2INT(pmode), mbuf);
|
120
|
+
}
|
121
|
+
else{
|
122
|
+
mode = StringValuePtr(pmode);
|
123
|
+
}
|
124
|
+
}
|
125
|
+
|
126
|
+
if(*mode == 't'){
|
127
|
+
tm = _O_TEXT;
|
128
|
+
}
|
129
|
+
else if(*mode != 'b') {
|
130
|
+
rb_raise(rb_eArgError, "popen3() arg 2 must be 't' or 'b'");
|
131
|
+
}
|
132
|
+
else{
|
133
|
+
tm = _O_BINARY;
|
134
|
+
}
|
135
|
+
|
136
|
+
port = ruby_popen(StringValuePtr(pname), tm, v_show_window);
|
137
|
+
|
138
|
+
/* Ensure handles are closed in block form */
|
139
|
+
if(rb_block_given_p()) {
|
140
|
+
rb_ensure(rb_yield_splat, port, io_close, port);
|
141
|
+
return win32_last_status;
|
142
|
+
}
|
143
|
+
|
144
|
+
return port;
|
145
|
+
}
|
146
|
+
|
147
|
+
static BOOL RubyCreateProcess(char *cmdstring, HANDLE hStdin, HANDLE hStdout,
|
148
|
+
HANDLE hStderr, HANDLE *hProcess, pid_t *pid, VALUE v_show_window)
|
149
|
+
{
|
150
|
+
PROCESS_INFORMATION piProcInfo;
|
151
|
+
STARTUPINFO siStartInfo;
|
152
|
+
char *s1,*s2, *s3 = " /c ";
|
153
|
+
int i, x;
|
154
|
+
|
155
|
+
|
156
|
+
if(i = GetEnvironmentVariable("COMSPEC",NULL,0)){
|
157
|
+
char *comshell;
|
158
|
+
s1 = (char *)_alloca(i);
|
159
|
+
if (!(x = GetEnvironmentVariable("COMSPEC", s1, i)))
|
160
|
+
return x;
|
161
|
+
|
162
|
+
/* Explicitly check if we are using COMMAND.COM. If we are
|
163
|
+
* then use the w9xpopen hack.
|
164
|
+
*/
|
165
|
+
comshell = s1 + x;
|
166
|
+
while (comshell >= s1 && *comshell != '\\')
|
167
|
+
--comshell;
|
168
|
+
++comshell;
|
169
|
+
|
170
|
+
if (GetVersion() < 0x80000000 &&
|
171
|
+
_stricmp(comshell, "command.com") != 0) {
|
172
|
+
// NT/2000 and not using command.com.
|
173
|
+
x = i + strlen(s3) + strlen(cmdstring) + 1;
|
174
|
+
s2 = ALLOCA_N(char, x);
|
175
|
+
sprintf(s2, "%s%s%s", s1, s3, cmdstring);
|
176
|
+
}
|
177
|
+
else{
|
178
|
+
// Windows 9x or ME
|
179
|
+
rb_raise(rb_eRuntimeError,"not supported on this platform");
|
180
|
+
}
|
181
|
+
}
|
182
|
+
|
183
|
+
// Could be an else here to try cmd.exe / command.com in the path
|
184
|
+
// Now we'll just error out..
|
185
|
+
else {
|
186
|
+
rb_raise(rb_eRuntimeError,
|
187
|
+
"Cannot locate a COMSPEC environment variable to "
|
188
|
+
"use as the shell");
|
189
|
+
return FALSE;
|
190
|
+
}
|
191
|
+
|
192
|
+
ZeroMemory(&siStartInfo, sizeof(STARTUPINFO));
|
193
|
+
siStartInfo.cb = sizeof(STARTUPINFO);
|
194
|
+
siStartInfo.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
|
195
|
+
siStartInfo.hStdInput = hStdin;
|
196
|
+
siStartInfo.hStdOutput = hStdout;
|
197
|
+
siStartInfo.hStdError = hStderr;
|
198
|
+
siStartInfo.wShowWindow = SW_HIDE;
|
199
|
+
|
200
|
+
if(v_show_window == Qtrue)
|
201
|
+
siStartInfo.wShowWindow = SW_SHOW;
|
202
|
+
|
203
|
+
// Try the command first without COMSPEC
|
204
|
+
if(CreateProcess(
|
205
|
+
NULL,
|
206
|
+
cmdstring,
|
207
|
+
NULL,
|
208
|
+
NULL,
|
209
|
+
TRUE,
|
210
|
+
0,
|
211
|
+
NULL,
|
212
|
+
NULL,
|
213
|
+
&siStartInfo,
|
214
|
+
&piProcInfo)
|
215
|
+
){
|
216
|
+
// Close the handles now so anyone waiting is woken.
|
217
|
+
CloseHandle(piProcInfo.hThread);
|
218
|
+
|
219
|
+
// Return process handle
|
220
|
+
*hProcess = piProcInfo.hProcess;
|
221
|
+
*pid = (pid_t)piProcInfo.dwProcessId;
|
222
|
+
return TRUE;
|
223
|
+
}
|
224
|
+
|
225
|
+
// If that failed, try again with COMSPEC
|
226
|
+
if(CreateProcess(
|
227
|
+
NULL,
|
228
|
+
s2,
|
229
|
+
NULL,
|
230
|
+
NULL,
|
231
|
+
TRUE,
|
232
|
+
CREATE_NEW_CONSOLE,
|
233
|
+
NULL,
|
234
|
+
NULL,
|
235
|
+
&siStartInfo,
|
236
|
+
&piProcInfo)
|
237
|
+
){
|
238
|
+
/* Close the handles now so anyone waiting is woken. */
|
239
|
+
CloseHandle(piProcInfo.hThread);
|
240
|
+
|
241
|
+
/* Return process handle */
|
242
|
+
*hProcess = piProcInfo.hProcess;
|
243
|
+
*pid = (pid_t)piProcInfo.dwProcessId;
|
244
|
+
return TRUE;
|
245
|
+
}
|
246
|
+
|
247
|
+
rb_raise(rb_eRuntimeError, "CreateProcess() failed: %s",
|
248
|
+
ErrorDescription(GetLastError())
|
249
|
+
);
|
250
|
+
|
251
|
+
return FALSE;
|
252
|
+
}
|
253
|
+
|
254
|
+
/* Set the Process::Status. Based on patches by Samuel Tesla and John-Mason
|
255
|
+
* Shackelford.
|
256
|
+
*/
|
257
|
+
static void win32_set_last_status(const int status, const int pid)
|
258
|
+
{
|
259
|
+
/* rb_last_status is defined in process.c in the main ruby.exe */
|
260
|
+
__declspec (dllimport) extern VALUE rb_last_status;
|
261
|
+
VALUE klass = rb_path2class("Process::Status");
|
262
|
+
VALUE process_status = rb_obj_alloc(klass);
|
263
|
+
rb_iv_set(process_status, "status", INT2FIX(status << 8));
|
264
|
+
rb_iv_set(process_status, "pid", INT2FIX(pid));
|
265
|
+
rb_last_status = process_status;
|
266
|
+
win32_last_status = process_status;
|
267
|
+
}
|
268
|
+
|
269
|
+
static void win32_pipe_finalize(OpenFile *file, int noraise)
|
270
|
+
{
|
271
|
+
int status;
|
272
|
+
|
273
|
+
if(pid_handle != NULL){
|
274
|
+
GetExitCodeProcess(pid_handle, &status);
|
275
|
+
if(status != STILL_ACTIVE){
|
276
|
+
win32_set_last_status(status, file->pid);
|
277
|
+
}
|
278
|
+
}
|
279
|
+
}
|
280
|
+
|
281
|
+
/* The following code is based off of KB: Q190351 */
|
282
|
+
static VALUE ruby_popen(char *cmdstring, int mode, VALUE v_show_window)
|
283
|
+
{
|
284
|
+
HANDLE hChildStdinRd, hChildStdinWr, hChildStdoutRd, hChildStdoutWr,
|
285
|
+
hChildStderrRd, hChildStderrWr, hChildStdinWrDup, hChildStdoutRdDup,
|
286
|
+
hChildStderrRdDup, hProcess;
|
287
|
+
|
288
|
+
pid_t pid;
|
289
|
+
|
290
|
+
SECURITY_ATTRIBUTES saAttr;
|
291
|
+
BOOL fSuccess;
|
292
|
+
int fd1, fd2, fd3;
|
293
|
+
FILE *f1, *f2, *f3;
|
294
|
+
long file_count;
|
295
|
+
VALUE port;
|
296
|
+
VALUE p1,p2,p3;
|
297
|
+
int modef;
|
298
|
+
OpenFile *fptr;
|
299
|
+
char *m1, *m2;
|
300
|
+
|
301
|
+
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
|
302
|
+
saAttr.bInheritHandle = TRUE;
|
303
|
+
saAttr.lpSecurityDescriptor = NULL;
|
304
|
+
|
305
|
+
if(!CreatePipe(&hChildStdinRd, &hChildStdinWr, &saAttr, 0)) {
|
306
|
+
rb_raise(rb_eRuntimeError, "CreatePipe() failed: %s",
|
307
|
+
ErrorDescription(GetLastError())
|
308
|
+
);
|
309
|
+
}
|
310
|
+
|
311
|
+
/* Create new output read handle and the input write handle. Set
|
312
|
+
* the inheritance properties to FALSE. Otherwise, the child inherits
|
313
|
+
* the these handles; resulting in non-closeable handles to the pipes
|
314
|
+
* being created.
|
315
|
+
*/
|
316
|
+
fSuccess = DuplicateHandle(
|
317
|
+
GetCurrentProcess(),
|
318
|
+
hChildStdinWr,
|
319
|
+
GetCurrentProcess(),
|
320
|
+
&hChildStdinWrDup,
|
321
|
+
0,
|
322
|
+
FALSE,
|
323
|
+
DUPLICATE_SAME_ACCESS
|
324
|
+
);
|
325
|
+
if(!fSuccess){
|
326
|
+
rb_raise(rb_eRuntimeError, "DuplicateHandle() failed: %s",
|
327
|
+
ErrorDescription(GetLastError())
|
328
|
+
);
|
329
|
+
}
|
330
|
+
|
331
|
+
// Close the inheritable version of ChildStdin that we're using
|
332
|
+
CloseHandle(hChildStdinWr);
|
333
|
+
|
334
|
+
if(!CreatePipe(&hChildStdoutRd, &hChildStdoutWr, &saAttr, 0)) {
|
335
|
+
rb_raise(rb_eRuntimeError, "CreatePipe() failed: %s",
|
336
|
+
ErrorDescription(GetLastError())
|
337
|
+
);
|
338
|
+
}
|
339
|
+
|
340
|
+
fSuccess = DuplicateHandle(
|
341
|
+
GetCurrentProcess(),
|
342
|
+
hChildStdoutRd,
|
343
|
+
GetCurrentProcess(),
|
344
|
+
&hChildStdoutRdDup,
|
345
|
+
0,
|
346
|
+
FALSE,
|
347
|
+
DUPLICATE_SAME_ACCESS
|
348
|
+
);
|
349
|
+
|
350
|
+
if(!fSuccess){
|
351
|
+
rb_raise(rb_eRuntimeError, "DuplicateHandle() failed: %s",
|
352
|
+
ErrorDescription(GetLastError())
|
353
|
+
);
|
354
|
+
}
|
355
|
+
|
356
|
+
// Close the inheritable version of ChildStdout that we're using.
|
357
|
+
CloseHandle(hChildStdoutRd);
|
358
|
+
|
359
|
+
if(!CreatePipe(&hChildStderrRd, &hChildStderrWr, &saAttr, 0)) {
|
360
|
+
rb_raise(rb_eRuntimeError, "CreatePipe() failed: %s",
|
361
|
+
ErrorDescription(GetLastError())
|
362
|
+
);
|
363
|
+
}
|
364
|
+
|
365
|
+
fSuccess = DuplicateHandle(
|
366
|
+
GetCurrentProcess(),
|
367
|
+
hChildStderrRd,
|
368
|
+
GetCurrentProcess(),
|
369
|
+
&hChildStderrRdDup,
|
370
|
+
0,
|
371
|
+
FALSE,
|
372
|
+
DUPLICATE_SAME_ACCESS
|
373
|
+
);
|
374
|
+
|
375
|
+
if (!fSuccess) {
|
376
|
+
rb_raise(rb_eRuntimeError, "DuplicateHandle() failed: %s",
|
377
|
+
ErrorDescription(GetLastError())
|
378
|
+
);
|
379
|
+
}
|
380
|
+
|
381
|
+
// Close the inheritable version of ChildStdErr that we're using.
|
382
|
+
CloseHandle(hChildStderrRd);
|
383
|
+
|
384
|
+
if(mode & _O_TEXT){
|
385
|
+
m1 = "r";
|
386
|
+
m2 = "w";
|
387
|
+
}
|
388
|
+
else{
|
389
|
+
m1 = "rb";
|
390
|
+
m2 = "wb";
|
391
|
+
}
|
392
|
+
|
393
|
+
// Convert our HANDLE's into file descriptors and, ultimately, into
|
394
|
+
// Ruby IO objects.
|
395
|
+
fd1 = _open_osfhandle((long)hChildStdinWrDup, mode);
|
396
|
+
f1 = _fdopen(fd1, m2);
|
397
|
+
fd2 = _open_osfhandle((long)hChildStdoutRdDup, mode);
|
398
|
+
f2 = _fdopen(fd2, m1);
|
399
|
+
fd3 = _open_osfhandle((long)hChildStderrRdDup, mode);
|
400
|
+
f3 = _fdopen(fd3, m1);
|
401
|
+
|
402
|
+
file_count = 3;
|
403
|
+
|
404
|
+
if(!RubyCreateProcess(cmdstring,hChildStdinRd,hChildStdoutWr,
|
405
|
+
hChildStderrWr, &hProcess, &pid, v_show_window))
|
406
|
+
{
|
407
|
+
return Qnil;
|
408
|
+
}
|
409
|
+
|
410
|
+
// I think this is only possible on Win9x, but we'll leave it here
|
411
|
+
if(pid < 0){
|
412
|
+
pid = -pid;
|
413
|
+
}
|
414
|
+
|
415
|
+
modef = rb_io_mode_flags(m2);
|
416
|
+
p1 = io_alloc(rb_cIO);
|
417
|
+
MakeOpenFile(p1, fptr);
|
418
|
+
|
419
|
+
fptr->finalize = win32_pipe_finalize;
|
420
|
+
fptr->mode = modef;
|
421
|
+
fptr->pid = pid;
|
422
|
+
|
423
|
+
if(modef & FMODE_READABLE){
|
424
|
+
fptr->f = f1;
|
425
|
+
}
|
426
|
+
|
427
|
+
if(modef & FMODE_WRITABLE){
|
428
|
+
if(fptr->f){
|
429
|
+
fptr->f2 = f1;
|
430
|
+
}
|
431
|
+
else{
|
432
|
+
fptr->f = f1;
|
433
|
+
}
|
434
|
+
fptr->mode |= FMODE_SYNC;
|
435
|
+
}
|
436
|
+
|
437
|
+
modef = rb_io_mode_flags(m1);
|
438
|
+
p2 = io_alloc(rb_cIO);
|
439
|
+
MakeOpenFile(p2, fptr);
|
440
|
+
|
441
|
+
pid_handle = hProcess;
|
442
|
+
fptr->finalize = win32_pipe_finalize;
|
443
|
+
fptr->mode = modef;
|
444
|
+
fptr->pid = pid;
|
445
|
+
|
446
|
+
if(modef & FMODE_READABLE){
|
447
|
+
fptr->f = f2;
|
448
|
+
}
|
449
|
+
if(modef & FMODE_WRITABLE){
|
450
|
+
if(fptr->f){
|
451
|
+
fptr->f2 = f2;
|
452
|
+
}
|
453
|
+
else{
|
454
|
+
fptr->f = f2;
|
455
|
+
}
|
456
|
+
fptr->mode |= FMODE_SYNC;
|
457
|
+
}
|
458
|
+
|
459
|
+
modef = rb_io_mode_flags(m1);
|
460
|
+
p3 = io_alloc(rb_cIO);
|
461
|
+
MakeOpenFile(p3, fptr);
|
462
|
+
|
463
|
+
fptr->finalize = win32_pipe_finalize;
|
464
|
+
fptr->mode = modef;
|
465
|
+
fptr->pid = pid;
|
466
|
+
|
467
|
+
if(modef & FMODE_READABLE){
|
468
|
+
fptr->f = f3;
|
469
|
+
}
|
470
|
+
|
471
|
+
if(modef & FMODE_WRITABLE){
|
472
|
+
if(fptr->f){
|
473
|
+
fptr->f2 = f3;
|
474
|
+
}
|
475
|
+
else{
|
476
|
+
fptr->f = f3;
|
477
|
+
}
|
478
|
+
fptr->mode |= FMODE_SYNC;
|
479
|
+
}
|
480
|
+
|
481
|
+
port = rb_ary_new2(4);
|
482
|
+
rb_ary_push(port,(VALUE)p1);
|
483
|
+
rb_ary_push(port,(VALUE)p2);
|
484
|
+
rb_ary_push(port,(VALUE)p3);
|
485
|
+
rb_ary_push(port,UINT2NUM((DWORD)pid));
|
486
|
+
|
487
|
+
/* Child is launched. Close the parents copy of those pipe
|
488
|
+
* handles that only the child should have open. You need to
|
489
|
+
* make sure that no handles to the write end of the output pipe
|
490
|
+
* are maintained in this process or else the pipe will not close
|
491
|
+
* when the child process exits and the ReadFile() will hang.
|
492
|
+
*/
|
493
|
+
|
494
|
+
if (!CloseHandle(hChildStdinRd)) {
|
495
|
+
rb_raise(rb_eRuntimeError, "CloseHandle() failed: %s",
|
496
|
+
ErrorDescription(GetLastError())
|
497
|
+
);
|
498
|
+
}
|
499
|
+
|
500
|
+
if (!CloseHandle(hChildStdoutWr)) {
|
501
|
+
rb_raise(rb_eRuntimeError, "CloseHandle() failed: %s",
|
502
|
+
ErrorDescription(GetLastError())
|
503
|
+
);
|
504
|
+
}
|
505
|
+
|
506
|
+
if(!CloseHandle(hChildStderrWr)) {
|
507
|
+
rb_raise(rb_eRuntimeError, "CloseHandle() failed: %s",
|
508
|
+
ErrorDescription(GetLastError())
|
509
|
+
);
|
510
|
+
}
|
511
|
+
|
512
|
+
return port;
|
513
|
+
}
|
514
|
+
|
515
|
+
void
|
516
|
+
Init_open3()
|
517
|
+
{
|
518
|
+
VALUE mOpen3 = rb_define_module("Open3");
|
519
|
+
VALUE mOpen4 = rb_define_module("Open4");
|
520
|
+
|
521
|
+
rb_define_module_function(mOpen3, "popen3", win32_popen3, -1);
|
522
|
+
rb_define_module_function(mOpen4, "popen4", win32_popen3, -1);
|
523
|
+
|
524
|
+
rb_define_const(mOpen3, "WIN32_OPEN3_VERSION", rb_str_new2(WIN32_OPEN3_VERSION));
|
525
|
+
rb_define_const(mOpen4, "WIN32_OPEN3_VERSION", rb_str_new2(WIN32_OPEN3_VERSION));
|
526
|
+
}
|
data/ext/open3.h
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
#define WIN32_OPEN3_VERSION "0.2.5"
|
2
|
+
#define MAX_STRING 512
|
3
|
+
|
4
|
+
static VALUE io_alloc _((VALUE));
|
5
|
+
|
6
|
+
static VALUE io_alloc(VALUE klass){
|
7
|
+
NEWOBJ(io, struct RFile);
|
8
|
+
OBJSETUP(io, klass, T_FILE);
|
9
|
+
|
10
|
+
io->fptr = 0;
|
11
|
+
|
12
|
+
return (VALUE)io;
|
13
|
+
}
|
14
|
+
|
15
|
+
int rb_io_mode_flags(const char* mode){
|
16
|
+
int flags = 0;
|
17
|
+
|
18
|
+
switch (mode[0]){
|
19
|
+
case 'r':
|
20
|
+
flags |= FMODE_READABLE;
|
21
|
+
break;
|
22
|
+
case 'w':
|
23
|
+
flags |= FMODE_WRITABLE;
|
24
|
+
break;
|
25
|
+
case 'a':
|
26
|
+
flags |= FMODE_WRITABLE;
|
27
|
+
break;
|
28
|
+
default:
|
29
|
+
error:
|
30
|
+
rb_raise(rb_eArgError, "illegal access mode %s", mode);
|
31
|
+
}
|
32
|
+
|
33
|
+
if(mode[1] == 'b'){
|
34
|
+
flags |= FMODE_BINMODE;
|
35
|
+
mode++;
|
36
|
+
}
|
37
|
+
|
38
|
+
if(mode[1] == '+'){
|
39
|
+
flags |= FMODE_READWRITE;
|
40
|
+
if(mode[2] != 0){
|
41
|
+
goto error;
|
42
|
+
}
|
43
|
+
}
|
44
|
+
else if(mode[1] != 0) goto error;
|
45
|
+
|
46
|
+
return flags;
|
47
|
+
}
|
48
|
+
|
49
|
+
// Return an error code as a string
|
50
|
+
LPTSTR ErrorDescription(DWORD p_dwError)
|
51
|
+
{
|
52
|
+
HLOCAL hLocal = NULL;
|
53
|
+
static char ErrStr[MAX_STRING];
|
54
|
+
int len;
|
55
|
+
|
56
|
+
if (!(len=FormatMessage(
|
57
|
+
FORMAT_MESSAGE_ALLOCATE_BUFFER |
|
58
|
+
FORMAT_MESSAGE_FROM_SYSTEM |
|
59
|
+
FORMAT_MESSAGE_IGNORE_INSERTS,
|
60
|
+
NULL,
|
61
|
+
p_dwError,
|
62
|
+
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), /* Default language */
|
63
|
+
(LPTSTR)&hLocal,
|
64
|
+
0,
|
65
|
+
NULL)))
|
66
|
+
{
|
67
|
+
rb_raise(rb_eStandardError,"Unable to format error message");
|
68
|
+
}
|
69
|
+
memset(ErrStr, 0, MAX_STRING);
|
70
|
+
strncpy(ErrStr, (LPTSTR)hLocal, len-2); /* remove \r\n */
|
71
|
+
LocalFree(hLocal);
|
72
|
+
return ErrStr;
|
73
|
+
}
|
data/lib/win32/open3.so
ADDED
Binary file
|
data/test/tc_open3.rb
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
###########################################################################
|
2
|
+
# tc_open3.rb
|
3
|
+
#
|
4
|
+
# Test suite for the win32-open3 package. Except for the
|
5
|
+
# 'test_open3_with_arguments' test and Open4 tests, this suite passes
|
6
|
+
# on Unix as well.
|
7
|
+
###########################################################################
|
8
|
+
base = File.basename(Dir.pwd)
|
9
|
+
if base == 'test' || base =~ /win32-open3/i
|
10
|
+
require 'ftools'
|
11
|
+
Dir.chdir('..') if base == 'test'
|
12
|
+
Dir.mkdir('win32') unless File.exists?('win32')
|
13
|
+
File.copy('open3.so', 'win32') if File.exists?('open3.so')
|
14
|
+
$LOAD_PATH.unshift Dir.pwd
|
15
|
+
Dir.chdir('test') rescue nil
|
16
|
+
end
|
17
|
+
|
18
|
+
require 'win32/open3'
|
19
|
+
require 'test/unit'
|
20
|
+
|
21
|
+
class TC_Win32_Open3 < Test::Unit::TestCase
|
22
|
+
def setup
|
23
|
+
@good_cmd = 'ver'
|
24
|
+
@bad_cmd = 'verb'
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_open3_basic
|
28
|
+
assert_respond_to(Open3, :popen3)
|
29
|
+
assert_nothing_raised{ Open3.popen3(@good_cmd) }
|
30
|
+
assert_nothing_raised{ Open3.popen3(@bad_cmd) }
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_open4_basic
|
34
|
+
assert_respond_to(Open4, :popen4)
|
35
|
+
assert_nothing_raised{ Open4.popen4(@good_cmd) }
|
36
|
+
assert_nothing_raised{ Open4.popen4(@bad_cmd) }
|
37
|
+
end
|
38
|
+
|
39
|
+
# This test would fail on other platforms
|
40
|
+
def test_open3_with_arguments
|
41
|
+
assert_nothing_raised{ Open3.popen3(@good_cmd, 't') }
|
42
|
+
assert_nothing_raised{ Open3.popen3(@bad_cmd, 't') }
|
43
|
+
assert_nothing_raised{ Open3.popen3(@good_cmd, 'b') }
|
44
|
+
assert_nothing_raised{ Open3.popen3(@bad_cmd, 'b') }
|
45
|
+
assert_nothing_raised{ Open3.popen3(@good_cmd, 't', false) }
|
46
|
+
assert_nothing_raised{ Open3.popen3(@good_cmd, 't', true) }
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_open3_handles
|
50
|
+
arr = Open3.popen3(@good_cmd)
|
51
|
+
assert_kind_of(Array, arr)
|
52
|
+
assert_kind_of(IO, arr[0])
|
53
|
+
assert_kind_of(IO, arr[1])
|
54
|
+
assert_kind_of(IO, arr[2])
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_open3_block
|
58
|
+
assert_nothing_raised{ Open3.popen3(@good_cmd){ |pin, pout, perr| } }
|
59
|
+
Open3.popen3(@good_cmd){ |pin, pout, perr|
|
60
|
+
assert_kind_of(IO, pin)
|
61
|
+
assert_kind_of(IO, pout)
|
62
|
+
assert_kind_of(IO, perr)
|
63
|
+
}
|
64
|
+
end
|
65
|
+
|
66
|
+
def test_open4_block
|
67
|
+
assert_nothing_raised{ Open4.popen4(@good_cmd){ |pin, pout, perr, pid| } }
|
68
|
+
Open4.popen4(@good_cmd){ |pin, pout, perr, pid|
|
69
|
+
assert_kind_of(IO, pin)
|
70
|
+
assert_kind_of(IO, pout)
|
71
|
+
assert_kind_of(IO, perr)
|
72
|
+
assert_kind_of(Fixnum, pid)
|
73
|
+
}
|
74
|
+
end
|
75
|
+
|
76
|
+
def test_open4_return_values
|
77
|
+
arr = Open4.popen4(@good_cmd)
|
78
|
+
assert_kind_of(Array,arr)
|
79
|
+
assert_kind_of(IO, arr[0])
|
80
|
+
assert_kind_of(IO, arr[1])
|
81
|
+
assert_kind_of(IO, arr[2])
|
82
|
+
assert_kind_of(Fixnum, arr[3])
|
83
|
+
end
|
84
|
+
|
85
|
+
def test_handle_good_content
|
86
|
+
fin, fout, ferr = Open3.popen3(@good_cmd)
|
87
|
+
assert_kind_of(String, fout.gets)
|
88
|
+
assert_nil(ferr.gets)
|
89
|
+
end
|
90
|
+
|
91
|
+
def test_handle_bad_content
|
92
|
+
fin, fout, ferr = Open3.popen3(@bad_cmd)
|
93
|
+
assert_kind_of(String, ferr.gets)
|
94
|
+
assert_nil(fout.gets)
|
95
|
+
end
|
96
|
+
|
97
|
+
def teardown
|
98
|
+
@good_cmd = nil
|
99
|
+
@bad_cmd = nil
|
100
|
+
end
|
101
|
+
end
|
metadata
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
rubygems_version: 0.9.0
|
3
|
+
specification_version: 1
|
4
|
+
name: win32-open3
|
5
|
+
version: !ruby/object:Gem::Version
|
6
|
+
version: 0.2.5
|
7
|
+
date: 2006-12-08 00:00:00 -07:00
|
8
|
+
summary: Provides an Open3.popen3 implementation for MS Windows
|
9
|
+
require_paths:
|
10
|
+
- lib
|
11
|
+
email: djberg96@gmail.com
|
12
|
+
homepage: http://www.rubyforge.org/projects/win32utils
|
13
|
+
rubyforge_project: win32utils
|
14
|
+
description: Provides an Open3.popen3 implementation for MS Windows
|
15
|
+
autorequire:
|
16
|
+
default_executable:
|
17
|
+
bindir: bin
|
18
|
+
has_rdoc: true
|
19
|
+
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 1.8.2
|
24
|
+
version:
|
25
|
+
platform: mswin32
|
26
|
+
signing_key:
|
27
|
+
cert_chain:
|
28
|
+
post_install_message:
|
29
|
+
authors:
|
30
|
+
- Daniel J. Berger
|
31
|
+
files:
|
32
|
+
- doc/open3.txt
|
33
|
+
- test/tc_open3.rb
|
34
|
+
- ext/extconf.rb
|
35
|
+
- ext/open3.c
|
36
|
+
- ext/open3.h
|
37
|
+
- lib/win32/open3.so
|
38
|
+
- CHANGES
|
39
|
+
- README
|
40
|
+
- MANIFEST
|
41
|
+
test_files:
|
42
|
+
- test/tc_open3.rb
|
43
|
+
rdoc_options: []
|
44
|
+
|
45
|
+
extra_rdoc_files:
|
46
|
+
- CHANGES
|
47
|
+
- README
|
48
|
+
- MANIFEST
|
49
|
+
- doc/open3.txt
|
50
|
+
- ext/open3.c
|
51
|
+
executables: []
|
52
|
+
|
53
|
+
extensions: []
|
54
|
+
|
55
|
+
requirements: []
|
56
|
+
|
57
|
+
dependencies: []
|
58
|
+
|