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 }