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 }