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 logger = std.experimental.logger; 13 import std.stdio : File; 14 15 /** Pipes to use to communicate with a process. 16 * 17 * Can be used to directly communicate via stdin/stdout if so is desired. 18 */ 19 struct Pipe { 20 FileReadChannel input; 21 FileWriteChannel output; 22 23 this(File input, File output) @safe { 24 this.input = FileReadChannel(input); 25 this.output = FileWriteChannel(output); 26 } 27 28 bool hasData() @safe { 29 return input.hasData; 30 } 31 32 /// If there is data to read. 33 bool hasPendingData() @safe { 34 return input.hasPendingData; 35 } 36 37 const(ubyte)[] read(const size_t s) return scope @safe { 38 return input.read(s); 39 } 40 41 ubyte[] read(ref ubyte[] buf) @safe { 42 return input.read(buf); 43 } 44 45 void write(scope const(ubyte)[] data) @safe { 46 output.write(data); 47 } 48 49 void flush() @safe { 50 output.flush; 51 } 52 53 void closeWrite() @safe { 54 output.closeWrite; 55 } 56 } 57 58 /** A read channel over a `File` object. 59 */ 60 struct FileReadChannel { 61 private { 62 File in_; 63 bool eof; 64 } 65 66 this(File in_) @trusted { 67 this.in_ = in_; 68 } 69 70 // TODO: rename to isOpen. 71 /// If the channel is open. 72 bool hasData() @safe { 73 return !eof; 74 } 75 76 // TODO: rename to hasData. 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 pollfd[1] fds; 86 fds[0].fd = in_.fileno; 87 fds[0].events = POLLIN; 88 auto ready = () @trusted { return poll(&fds[0], 1, 0); }(); 89 90 // timeout triggered 91 if (ready == 0) { 92 return false; 93 } 94 95 if (ready < 0) { 96 eof = true; 97 return false; 98 } 99 100 if (fds[0].revents & (POLLNVAL | POLLERR)) { 101 eof = true; 102 } 103 104 return (fds[0].revents & (POLLIN | POLLPRI | POLLHUP)) != 0; 105 } 106 107 /** Read at most `s` bytes from the channel. 108 * 109 * Note that this is slow because the data is copied to keep the interface 110 * memory safe. Prefer the one that takes a buffer 111 */ 112 const(ubyte)[] read(const size_t size) return scope @safe { 113 auto buffer = new ubyte[size]; 114 return cast(const(ubyte)[]) this.read(buffer); 115 } 116 117 /** Read at most `s` bytes from the channel. 118 * 119 * The data is written directly to buf. 120 * The lengt of buf determines how much is read. 121 * 122 * buf is not resized. Use the returned value. 123 */ 124 ubyte[] read(ref ubyte[] buf) return scope @trusted { 125 static import core.sys.posix.unistd; 126 127 if (eof || buf.length == 0 || !hasPendingData) { 128 return null; 129 } 130 131 auto res = core.sys.posix.unistd.read(in_.fileno, &buf[0], buf.length); 132 if (res <= 0) { 133 eof = true; 134 return null; 135 } 136 137 return buf[0 .. res]; 138 } 139 } 140 141 /** IO channel via `File` objects. 142 * 143 * Useful when e.g. communicating over pipes. 144 */ 145 struct FileWriteChannel { 146 private File out_; 147 148 this(File out__) @safe { 149 out_ = out__; 150 } 151 152 /** Write data to the output channel. 153 * 154 * Throws: 155 * ErrnoException if the file is not opened or if the call to fwrite fails. 156 */ 157 void write(scope const(ubyte)[] data) @safe { 158 out_.rawWrite(data); 159 } 160 161 /// Flush the output. 162 void flush() @safe { 163 out_.flush(); 164 } 165 166 /// Close the write channel. 167 void closeWrite() @safe { 168 out_.close; 169 } 170 } 171 172 /// Returns: a `File` object reading from `/dev/null`. 173 File nullIn() @safe { 174 return File("/dev/null", "r"); 175 } 176 177 /// Returns: a `File` object writing to `/dev/null`. 178 File nullOut() @safe { 179 return File("/dev/null", "w"); 180 }