1 /** 2 Copyright: Copyright (c) 2019, Joakim Brännström. All rights reserved. 3 License: MPL-2 4 Author: Joakim Brännström (joakim.brannstrom@gmx.com) 5 6 This Source Code Form is subject to the terms of the Mozilla Public License, 7 v.2.0. If a copy of the MPL was not distributed with this file, You can obtain 8 one at http://mozilla.org/MPL/2.0/. 9 */ 10 module process.channel; 11 12 import std.stdio : stdin, stdout, File; 13 14 class ChannelException : Exception { 15 ChannelStatus status; 16 17 this(ChannelStatus s) @safe pure nothrow @nogc { 18 super(null); 19 status = s; 20 } 21 22 string toString(ChannelStatus s) @safe pure nothrow const @nogc { 23 final switch (s) with (ChannelStatus) { 24 case ok: 25 return "ok"; 26 } 27 } 28 } 29 30 enum ChannelStatus { 31 ok, 32 } 33 34 interface ReadChannel { 35 alias OutRange = void delegate(const(ubyte)[] data); 36 37 // TODO: rename to isOpen. 38 /// If the channel is open. 39 bool hasData() @safe; 40 41 // TODO: rename to hasData. 42 /** If there is data to read, non blocking. 43 * 44 * If this is called before read then it is guaranteed that read will not 45 * block. 46 */ 47 bool hasPendingData() @safe; 48 49 /** Read at most `s` bytes from the channel. 50 * 51 * Note that this is slow because the data is copied to keep the interface 52 * memory safe. Prefer the one that takes an OutputRange. 53 */ 54 const(ubyte)[] read(const size_t s) @safe; 55 56 /// ditto 57 //void read(OutRange r, const size_t s); 58 59 /// Destroy the channel. 60 void destroy() @safe; 61 } 62 63 interface WriteChannel { 64 /** Writes as much data as possible to the output. 65 * 66 * Returns: a slice of the data that is left to write. 67 */ 68 void write(scope const(ubyte)[] data) @safe; 69 70 /// Flush the output. 71 void flush() @safe; 72 73 /// Destroy the channel. 74 void destroy() @safe; 75 76 /// Close the write channel. 77 void closeWrite() @safe; 78 } 79 80 interface Channel : ReadChannel, WriteChannel { 81 /// Destroy the channel. 82 void destroy() @safe; 83 } 84 85 /** Holds stdin/stdout/stderr channels open. 86 * 87 * Can be used to directly communicate via stdin/stdout if so is desired. 88 */ 89 class Stdio : Channel { 90 ReadChannel input; 91 WriteChannel output; 92 WriteChannel outputError; 93 94 this() { 95 import std.stdio : stdin, stdout, stderr; 96 97 input = new FileReadChannel(stdin); 98 output = new FileWriteChannel(stdout); 99 outputError = new FileWriteChannel(stderr); 100 } 101 102 override void destroy() @safe { 103 input.destroy; 104 output.destroy; 105 outputError.destroy; 106 } 107 108 override bool hasData() @safe { 109 return input.hasData; 110 } 111 112 /// If there is data to read. 113 override bool hasPendingData() @safe { 114 return input.hasPendingData; 115 } 116 117 /** Read at most `s` bytes from the channel. 118 * 119 * Note that this is slow because the data is copied to keep the interface 120 * memory safe. Prefer the one that takes an OutputRange. 121 */ 122 override const(ubyte)[] read(const size_t s) return scope @safe { 123 return input.read(s); 124 } 125 126 override void write(scope const(ubyte)[] data) @safe { 127 output.write(data); 128 } 129 130 /// Flush the output. 131 override void flush() @safe { 132 output.flush; 133 } 134 135 override void closeWrite() @safe { 136 output.closeWrite; 137 } 138 } 139 140 /** Pipes to use to communicate with a process. 141 * 142 * Can be used to directly communicate via stdin/stdout if so is desired. 143 */ 144 class Pipe : Channel { 145 import std.process : Pipe; 146 147 ReadChannel input; 148 WriteChannel output; 149 150 this(File input, File output) @safe { 151 this.input = new FileReadChannel(input); 152 this.output = new FileWriteChannel(output); 153 } 154 155 override void destroy() @trusted { 156 input.destroy; 157 output.destroy; 158 } 159 160 override bool hasData() @safe { 161 return input.hasData; 162 } 163 164 /// If there is data to read. 165 override bool hasPendingData() @safe { 166 return input.hasPendingData; 167 } 168 169 /** Read at most `s` bytes from the channel. 170 * 171 * Note that this is slow because the data is copied to keep the interface 172 * memory safe. Prefer the one that takes an OutputRange. 173 */ 174 override const(ubyte)[] read(const size_t s) return scope @safe { 175 return input.read(s); 176 } 177 178 override void write(scope const(ubyte)[] data) @safe { 179 output.write(data); 180 } 181 182 /// Flush the output. 183 override void flush() @safe { 184 output.flush; 185 } 186 187 override void closeWrite() @safe { 188 output.closeWrite; 189 } 190 } 191 192 /** A read channel over a `File` object. 193 */ 194 class FileReadChannel : ReadChannel { 195 private { 196 File in_; 197 bool eof; 198 } 199 200 this(File in_) @trusted { 201 this.in_ = in_; 202 } 203 204 override void destroy() @safe { 205 in_.detach; 206 } 207 208 override bool hasData() @safe { 209 return !eof; 210 } 211 212 override bool hasPendingData() @safe { 213 import core.sys.posix.poll; 214 215 pollfd[1] fds; 216 fds[0].fd = in_.fileno; 217 fds[0].events = POLLIN; 218 auto ready = () @trusted { return poll(&fds[0], 1, 0); }(); 219 220 if (ready <= 0) { 221 return false; 222 } 223 return (fds[0].revents | POLLIN) != 0; 224 } 225 226 override const(ubyte)[] read(const size_t size) return scope @trusted { 227 static import core.sys.posix.unistd; 228 229 if (size == 0 || !hasPendingData) { 230 return null; 231 } 232 233 auto buffer = new ubyte[size]; 234 auto res = core.sys.posix.unistd.read(in_.fileno, &buffer[0], size); 235 if (res <= 0) { 236 eof = true; 237 return null; 238 } 239 240 return cast(const(ubyte)[]) buffer[0 .. res]; 241 } 242 } 243 244 /** IO channel via `File` objects. 245 * 246 * Useful when e.g. communicating over pipes. 247 */ 248 class FileWriteChannel : WriteChannel { 249 private File out_; 250 251 this(File out__) @safe { 252 out_ = out__; 253 } 254 255 override void destroy() @safe { 256 out_.detach; 257 } 258 259 /** Write data to the output channel. 260 * 261 * Throws: 262 * ErrnoException if the file is not opened or if the call to fwrite fails. 263 */ 264 override void write(scope const(ubyte)[] data) @safe { 265 out_.rawWrite(data); 266 } 267 268 override void flush() @safe { 269 out_.flush(); 270 } 271 272 override void closeWrite() @safe { 273 out_.close; 274 } 275 } 276 277 /// Returns: a `File` object reading from `/dev/null`. 278 File nullIn() @safe { 279 return File("/dev/null", "r"); 280 } 281 282 /// Returns: a `File` object writing to `/dev/null`. 283 File nullOut() @safe { 284 return File("/dev/null", "w"); 285 }