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 }