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 hasData() @safe { 25 return input.hasData; 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 bool eof; 60 } 61 62 this(File in_) @trusted { 63 this.in_ = in_; 64 } 65 66 // TODO: rename to isOpen. 67 /// If the channel is open. 68 bool hasData() @safe { 69 return !eof; 70 } 71 72 // TODO: rename to hasData. 73 /** If there is data to read, non blocking. 74 * 75 * If this is called before read then it is guaranteed that read will not 76 * block. 77 */ 78 bool hasPendingData() @safe { 79 import core.sys.posix.poll; 80 81 pollfd[1] fds; 82 fds[0].fd = in_.fileno; 83 fds[0].events = POLLIN; 84 auto ready = () @trusted { return poll(&fds[0], 1, 0); }(); 85 86 // timeout triggered 87 if (ready == 0) { 88 return false; 89 } 90 91 if (ready < 0) { 92 eof = true; 93 return false; 94 } 95 96 if (fds[0].revents & (POLLNVAL | POLLERR)) { 97 eof = true; 98 } 99 100 return (fds[0].revents & (POLLIN | POLLPRI | POLLHUP)) != 0; 101 } 102 103 /** Read at most `s` bytes from the channel. 104 * 105 * Note that this is slow because the data is copied to keep the interface 106 * memory safe. Prefer the one that takes a buffer 107 */ 108 const(ubyte)[] read(const size_t size) return scope @safe { 109 auto buffer = new ubyte[size]; 110 return cast(const(ubyte)[]) this.read(buffer); 111 } 112 113 /** Read at most `s` bytes from the channel. 114 * 115 * The data is written directly to buf. 116 * The lengt of buf determines how much is read. 117 * 118 * buf is not resized. Use the returned value. 119 */ 120 ubyte[] read(ref ubyte[] buf) return scope @trusted { 121 static import core.sys.posix.unistd; 122 123 if (eof || buf.length == 0 || !hasPendingData) { 124 return null; 125 } 126 127 auto res = core.sys.posix.unistd.read(in_.fileno, &buf[0], buf.length); 128 if (res <= 0) { 129 eof = true; 130 return null; 131 } 132 133 return buf[0 .. res]; 134 } 135 } 136 137 /** IO channel via `File` objects. 138 * 139 * Useful when e.g. communicating over pipes. 140 */ 141 struct FileWriteChannel { 142 private File out_; 143 144 this(File out__) @safe { 145 out_ = out__; 146 } 147 148 /** Write data to the output channel. 149 * 150 * Throws: 151 * ErrnoException if the file is not opened or if the call to fwrite fails. 152 */ 153 void write(scope const(ubyte)[] data) @safe { 154 out_.rawWrite(data); 155 } 156 157 /// Flush the output. 158 void flush() @safe { 159 out_.flush(); 160 } 161 162 /// Close the write channel. 163 void closeWrite() @safe { 164 out_.close; 165 } 166 } 167 168 /// Returns: a `File` object reading from `/dev/null`. 169 File nullIn() @safe { 170 return File("/dev/null", "r"); 171 } 172 173 /// Returns: a `File` object writing to `/dev/null`. 174 File nullOut() @safe { 175 return File("/dev/null", "w"); 176 }