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 /** A read channel over a `File` object. 12 */ 13 struct FileReadChannel { 14 File file; 15 16 private { 17 enum State { 18 none, 19 active, 20 hup, 21 eof 22 } 23 24 State st; 25 } 26 27 this(File file) @trusted { 28 this.file = file; 29 this.st = State.active; 30 } 31 32 /// If the channel is open. 33 bool isOpen() @safe { 34 return st != State.eof && st != State.none; 35 } 36 37 /** If there is data to read, non blocking. 38 * 39 * If this is called before read then it is guaranteed that read will not 40 * block. 41 */ 42 bool hasPendingData() @safe { 43 import core.sys.posix.poll; 44 45 if (st == State.eof) { 46 return false; 47 } else if (st == State.hup) { 48 // will never block and transition to eof when out of data. 49 return true; 50 } 51 52 pollfd[1] fds; 53 fds[0].fd = file.fileno; 54 fds[0].events = POLLIN; 55 auto ready = () @trusted { return poll(&fds[0], 1, 0); }(); 56 57 // timeout triggered 58 if (ready == 0) { 59 return false; 60 } 61 62 if (ready < 0) { 63 import core.stdc.errno : errno, EINTR; 64 65 if (errno == EINTR) { 66 // poll just interrupted. try again. 67 return false; 68 } 69 70 // an errnor occured. 71 st = State.eof; 72 return false; 73 } 74 75 if (fds[0].revents & POLLHUP) { 76 // POLLHUP mean that the other side has been closed. A read will 77 // always succeed. If the read returns zero length then it means 78 // that the pipe is out of data. Thus the worst thing that happens 79 // is that we get nothing, an empty slice. 80 st = State.hup; 81 return true; 82 } 83 84 if (fds[0].revents & (POLLNVAL | POLLERR)) { 85 st = State.eof; 86 return false; 87 } 88 89 return (fds[0].revents & POLLIN) != 0; 90 } 91 92 /** Read at most `s` bytes from the channel. 93 * 94 * Note that this is slow because the data is copied to keep the interface 95 * memory safe. Prefer the one that takes a buffer 96 */ 97 const(ubyte)[] read(const size_t size) return scope @safe { 98 auto buffer = new ubyte[size]; 99 return cast(const(ubyte)[]) this.read(buffer); 100 } 101 102 /** Read at most `s` bytes from the channel. 103 * 104 * The data is written directly to buf. 105 * The lengt of buf determines how much is read. 106 * 107 * buf is not resized. Use the returned value. 108 */ 109 ubyte[] read(ref ubyte[] buf) return scope @trusted { 110 static import core.sys.posix.unistd; 111 112 if (st == State.eof || buf.length == 0) { 113 return null; 114 } 115 116 const res = core.sys.posix.unistd.read(file.fileno, &buf[0], buf.length); 117 if (res <= 0) { 118 st = State.eof; 119 return null; 120 } 121 122 return buf[0 .. res]; 123 } 124 125 /// Flush the input. 126 void flush() @safe { 127 file.flush(); 128 } 129 } 130 131 /** IO channel via `File` objects. 132 * 133 * Useful when e.g. communicating over pipes. 134 */ 135 struct FileWriteChannel { 136 File file; 137 138 this(File file) @safe { 139 this.file = file; 140 } 141 142 const(ubyte)[] write(scope return const(char)[] data_) @trusted { 143 static import core.sys.posix.unistd; 144 145 auto data = cast(const(ubyte)[]) data_; 146 147 const res = core.sys.posix.unistd.write(file.fileno, &data[0], data.length); 148 if (res <= 0) { 149 return null; 150 } 151 return data[0 .. res]; 152 } 153 154 /** Write data to the output channel. 155 * 156 * Returns: the data that was written 157 */ 158 const(ubyte)[] write(scope return const(ubyte)[] data) @trusted { 159 static import core.sys.posix.unistd; 160 161 const res = core.sys.posix.unistd.write(file.fileno, &data[0], data.length); 162 if (res <= 0) { 163 return null; 164 } 165 return data[0 .. res]; 166 } 167 168 /// Flush the output. 169 void flush() @safe { 170 file.flush(); 171 } 172 173 /// Close the write channel. 174 void closeWrite() @safe { 175 file.close; 176 } 177 } 178 179 /// Returns: a `File` object reading from `/dev/null`. 180 File nullIn() @safe { 181 return File("/dev/null", "r"); 182 } 183 184 /// Returns: a `File` object writing to `/dev/null`. 185 File nullOut() @safe { 186 return File("/dev/null", "w"); 187 }