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 }