1 /** 2 Copyright: Copyright (c) 2020, Joakim Brännström. All rights reserved. 3 License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0) 4 Author: Joakim Brännström (joakim.brannstrom@gmx.com) 5 */ 6 module proc.channel; 7 8 import logger = std.experimental.logger; 9 import std.stdio : File; 10 11 /** Pipes to use to communicate with a process. 12 * 13 * Can be used to directly communicate via stdin/stdout if so is desired. 14 */ 15 struct Pipe { 16 FileReadChannel input; 17 FileWriteChannel output; 18 19 this(File input, File output) @safe { 20 this.input = FileReadChannel(input); 21 this.output = FileWriteChannel(output); 22 } 23 24 bool isOpen() @safe { 25 return input.isOpen; 26 } 27 28 /// If there is data to read. 29 bool hasPendingData() @safe { 30 return input.hasPendingData; 31 } 32 33 const(ubyte)[] read(const size_t s) return scope @safe { 34 return input.read(s); 35 } 36 37 ubyte[] read(ref ubyte[] buf) @safe { 38 return input.read(buf); 39 } 40 41 void write(scope const(ubyte)[] data) @safe { 42 output.write(data); 43 } 44 45 void flush() @safe { 46 output.flush; 47 } 48 49 void closeWrite() @safe { 50 output.closeWrite; 51 } 52 } 53 54 /** A read channel over a `File` object. 55 */ 56 struct FileReadChannel { 57 private { 58 File in_; 59 enum State { 60 active, 61 hup, 62 eof 63 } 64 65 State st; 66 } 67 68 this(File in_) @trusted { 69 this.in_ = in_; 70 } 71 72 /// If the channel is open. 73 bool isOpen() @safe { 74 return st != State.eof; 75 } 76 77 /** If there is data to read, non blocking. 78 * 79 * If this is called before read then it is guaranteed that read will not 80 * block. 81 */ 82 bool hasPendingData() @safe { 83 import core.sys.posix.poll; 84 85 if (st == State.eof) { 86 return false; 87 } else if (st == State.hup) { 88 // will never block and transition to eof when out of data. 89 return true; 90 } 91 92 pollfd[1] fds; 93 fds[0].fd = in_.fileno; 94 fds[0].events = POLLIN; 95 auto ready = () @trusted { return poll(&fds[0], 1, 0); }(); 96 97 // timeout triggered 98 if (ready == 0) { 99 return false; 100 } 101 102 if (ready < 0) { 103 import core.stdc.errno : errno, EINTR; 104 105 if (errno == EINTR) { 106 // poll just interrupted. try again. 107 return false; 108 } 109 110 // an errnor occured. 111 st = State.eof; 112 return false; 113 } 114 115 if (fds[0].revents & POLLHUP) { 116 // POLLHUP mean that the other side has been closed. A read will 117 // always succeed. If the read returns zero length then it means 118 // that the pipe is out of data. Thus the worst thing that happens 119 // is that we get nothing, an empty slice. 120 st = State.hup; 121 return true; 122 } 123 124 if (fds[0].revents & (POLLNVAL | POLLERR)) { 125 st = State.eof; 126 return false; 127 } 128 129 return (fds[0].revents & POLLIN) != 0; 130 } 131 132 /** Read at most `s` bytes from the channel. 133 * 134 * Note that this is slow because the data is copied to keep the interface 135 * memory safe. Prefer the one that takes a buffer 136 */ 137 const(ubyte)[] read(const size_t size) return scope @safe { 138 auto buffer = new ubyte[size]; 139 return cast(const(ubyte)[]) this.read(buffer); 140 } 141 142 /** Read at most `s` bytes from the channel. 143 * 144 * The data is written directly to buf. 145 * The lengt of buf determines how much is read. 146 * 147 * buf is not resized. Use the returned value. 148 */ 149 ubyte[] read(ref ubyte[] buf) return scope @trusted { 150 static import core.sys.posix.unistd; 151 152 if (st == State.eof || buf.length == 0) { 153 return null; 154 } 155 156 const res = core.sys.posix.unistd.read(in_.fileno, &buf[0], buf.length); 157 if (res <= 0) { 158 st = State.eof; 159 return null; 160 } 161 162 return buf[0 .. res]; 163 } 164 } 165 166 /** IO channel via `File` objects. 167 * 168 * Useful when e.g. communicating over pipes. 169 */ 170 struct FileWriteChannel { 171 private File out_; 172 173 this(File out__) @safe { 174 out_ = out__; 175 } 176 177 /** Write data to the output channel. 178 * 179 * Throws: 180 * ErrnoException if the file is not opened or if the call to fwrite fails. 181 */ 182 void write(scope const(ubyte)[] data) @safe { 183 out_.rawWrite(data); 184 } 185 186 /// Flush the output. 187 void flush() @safe { 188 out_.flush(); 189 } 190 191 /// Close the write channel. 192 void closeWrite() @safe { 193 out_.close; 194 } 195 } 196 197 /// Returns: a `File` object reading from `/dev/null`. 198 File nullIn() @safe { 199 return File("/dev/null", "r"); 200 } 201 202 /// Returns: a `File` object writing to `/dev/null`. 203 File nullOut() @safe { 204 return File("/dev/null", "w"); 205 }