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 }