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 }