1 module dIOpipe.IOpipe;
2 
3 /* TODO:
4  * - improved unittests
5  * - support for more operating systems (OS X, BSD)
6  * - better error-handling
7  */
8 
9 import std.conv;
10 
11 version (linux)
12 {
13     
14     import core.stdc.stdlib;
15 	import core.sys.posix.unistd;
16     import std.file;
17 }
18 version (Windows)
19 {
20     import core.sys.windows.windows;
21 	import std.stdio;
22 }
23 
24 class PipeOpenError : Exception
25 {
26 	this(string msg, string file = __FILE__, size_t line = __LINE__)
27 	{
28 		super(msg, file, line);
29 	}
30 }
31 
32 class PipeReadError : Exception
33 {
34 	this(string msg, string file = __FILE__, size_t line = __LINE__)
35 	{
36 		super(msg, file, line);
37 	}
38 }
39 
40 class PipeWriteError : Exception
41 {
42 	this(string msg, string file = __FILE__, size_t line = __LINE__)
43 	{
44 		super(msg, file, line);
45 	}
46 }
47 
48 class IOpipe
49 {
50     public:
51         this (string executable, string workingDir = "")
52         {
53             version(linux)
54             {
55                 int[2] pipeStdin;
56                 int[2] pipeStdout;
57 
58                 if ((pipe(pipeStdin) != 0) || (pipe(pipeStdout) != 0))
59                 {
60                     // error
61 					throw new PipeOpenError("");
62                 }
63 
64                 if (fork() == 0)
65                 {
66                     // child process
67                     close(pipeStdin[1]);
68                     close(pipeStdout[0]);
69 
70                     dup2(pipeStdin[0], 0);
71                     dup2(pipeStdout[1], 1);
72 
73                     if (workingDir != "")
74                         chdir(workingDir);
75 
76                     execl("/bin/sh".ptr, "sh".ptr, "-c".ptr, executable.ptr, cast(char*)0);
77                     exit(1);
78                 }
79 
80                 this.pipeStdin = pipeStdin[1];
81                 this.pipeStdout = pipeStdout[0];
82                 close(pipeStdin[0]);
83                 close(pipeStdout[1]);
84             }
85 
86 			version(Windows)
87 			{
88 				// set up pipe handle inheritance
89 				SECURITY_ATTRIBUTES sa;
90 				sa.nLength = SECURITY_ATTRIBUTES.sizeof;
91 				sa.lpSecurityDescriptor = null;
92 				sa.bInheritHandle = true;
93 
94 				HANDLE hReadChildPipe, hWriteChildPipe;
95 				if (CreatePipe(&hReadChildPipe, &this.pipeStdin, &sa, 0) == 0)
96 				{
97 					// error, use GetLastError & friends and throw exception
98 				}
99 					
100 				if (CreatePipe(&this.pipeStdout, &hWriteChildPipe, &sa, 0) == 0)
101 				{
102 					// error, use GetLastError & friends and throw exception
103 				}
104 
105 				// ensure that the stdin write pipe handle is not inherited
106 				if (SetHandleInformation(this.pipeStdin, HANDLE_FLAG_INHERIT, 0) == 0)
107 				{
108 					// error, use GetLastError & friends and throw exception
109 				}
110 
111 				// ensure that the stdout read pipe handle is not inherited
112 				if (SetHandleInformation(this.pipeStdout, HANDLE_FLAG_INHERIT, 0) == 0)
113 				{
114 					// error, use GetLastError & friends and throw exception
115 				}
116 
117 
118 				STARTUPINFOA si;
119 				GetStartupInfoA(&si);
120 				//si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
121 				si.dwFlags |= STARTF_USESTDHANDLES;
122 				si.wShowWindow = SW_HIDE;
123 				si.hStdOutput = hWriteChildPipe;
124 				si.hStdError = hWriteChildPipe;
125 				si.hStdInput = hReadChildPipe;
126 
127 				PROCESS_INFORMATION pi;
128 				if (CreateProcessA(cast(const(char)*)0, cast(char*)executable.ptr, null, null, true, CREATE_NEW_CONSOLE, null, null, &si, &pi) == 0)
129 				{
130 					// error, use GetLastError & friends and throw exception
131 				}
132 
133 				CloseHandle(hWriteChildPipe);
134 				CloseHandle(hReadChildPipe);
135 			}
136         }
137 
138         ~this ()
139         {
140             version(linux)
141             {
142                 close(this.pipeStdin);
143                 close(this.pipeStdout);
144             }
145 			
146 			version(Windows)
147 			{
148 				//TerminateProcess(hProcessHandle, 0);
149 				CloseHandle(this.pipeStdin);
150 				CloseHandle(this.pipeStdout);
151 			}
152         }
153 
154         string read ()
155         {
156             version(linux)
157             {
158                 char[4096] buf;
159 				auto amount = cast(int)core.sys.posix.unistd.read(this.pipeStdout, buf.ptr, 512);
160 				if (amount < 0)
161 					throw new PipeReadError("");
162 
163 				return to!string(buf[0 .. amount]);
164             }
165 
166 			version(Windows)
167 			{
168 				char[512] buf;
169 				uint bytesRead;
170 				if (ReadFile(this.pipeStdout, cast(void*)buf.ptr, 512, &bytesRead, cast(LPOVERLAPPED)0) == 0)
171 				{
172 					// a real error occured - throw exception
173 				}
174 				
175 				if (bytesRead > 0)
176 					return to!string(buf);
177 
178 				return "";
179 			}
180         }
181 
182         size_t write (string wr)
183         {
184             version(linux)
185             {
186                 //alias core.sys.posix.unistd.write writePipe;
187 				size_t totalBytesWritten = 0;
188 				size_t totalLength = wr.length;
189 
190 				while (totalBytesWritten < totalLength)
191 				{
192 					size_t bytesWritten = core.sys.posix.unistd.write(this.pipeStdin, wr.ptr, wr.length);
193 					if (bytesWritten == -1)
194 					{
195 						throw new PipeWriteError("");
196 					}
197 
198 					totalBytesWritten += bytesWritten;
199 					wr = wr[totalBytesWritten .. $];
200 				}
201 
202 				return totalBytesWritten;
203             }
204 			
205 			version(Windows)
206 			{
207 				uint bytesWritten;
208 				if (WriteFile(this.pipeStdin, wr.ptr, wr.length, &bytesWritten, cast(LPOVERLAPPED)0) == 0)
209 				{
210 					// a real error occured - throw exception
211 				}
212 			}
213         }
214 
215     private:
216         version(linux)
217         {
218             int pipeStdin, pipeStdout;
219         }
220 		version(Windows)
221 		{
222 			HANDLE pipeStdin, pipeStdout;
223 		}
224 }
225 
226 unittest
227 {
228     version(linux)
229 	{
230 		import std.stdio;
231 	    auto io = new IOpipe("uname -a");
232 
233 	    for (;;)
234 	    {
235 	        string msg = io.read();
236 	        if (msg == "") break;
237 	        writeln("-> " ~ msg);
238 	    }
239 	}
240 
241 	version(Windows)
242 	{
243 
244 		import std.stdio;
245 		import core.thread;
246 		auto io = new IOpipe("cmd.exe");
247 
248 		io.write("help\r\n");
249 
250 		Thread.sleep(dur!("seconds")(2));
251 
252 		for (;;)
253 		{
254 			string l = io.read();
255 			writeln("-> " ~ l);
256 
257 			if (l == "") break;
258 		}
259 	}
260 }