1-- Running another program: a seventh concept beside the six, outside
2-- `world`, through which a program starts a child by its path, with the
3-- arguments and the environment it gives it, connects each of the child's
4-- standard streams, reads what the child writes and waits for it to end.
5
6import core/text
7
8--- A child's standard stream is the parent's own: it reads or writes
9--- where the parent does.
10public inherited: atom
11--- A child's standard stream is connected to nothing: its input reads at
12--- once to its end, and what it writes is thrown away.
13public discarded: atom
14--- A child's standard stream is a pipe the parent holds the other end of:
15--- the parent writes the child's input, or reads its output or errors.
16public piped: atom
17--- How one of a child's standard streams is connected: `inherited`,
18--- `discarded` or `piped`.
19public stream_choice: type = inherited | discarded | piped
20
21--- How each of a child's three standard streams is connected.
22public child_streams: type = struct
23 input: stream_choice
24 output: stream_choice
25 errors: stream_choice
26end child_streams
27
28--- Every stream the parent's own: the child reads and writes where the
29--- parent does.
30public inheriting: child_streams = (input: inherited, output: inherited,
31 errors: inherited)
32
33--- Every stream connected to nothing: a child run for how it ends.
34public discarding: child_streams = (input: discarded, output: discarded,
35 errors: discarded)
36
37--- The child's output and errors piped to the parent, which reads them with
38--- `read_child`, and its input discarded.
39public capturing: child_streams = (input: discarded, output: piped,
40 errors: piped)
41
42--- A started child: a value its caller keeps and edits, passing it `inout`
43--- to `write_child`, `close_input` and `read_child`, and `sink` to `wait`,
44--- which consumes it. Its fields are the provider's: `handle` names the
45--- child, and `input`, `output` and `errors` the parent's ends of its piped
46--- streams, each open while its `*_open` flag says so, which it is only
47--- for a piped stream. The child owns its pipes: `close_input` closes its
48--- input, `read_child` closes an output it has read to the end, and `wait`
49--- closes the rest. A provider whose own files they are, as the hosted
50--- one's are its descriptors, may read and write them through `reader` and
51--- `writer` as well, a line reader over a child's output among them; never
52--- close them through `files`. A child that is never waited for keeps
53--- what its provider holds for it until the process ends: there are no
54--- destructors.
55public child: type = struct
56 handle: usize
57 input: file
58 output: file
59 errors: file
60 input_open: bool
61 output_open: bool
62 errors_open: bool
63end child
64
65--- What `read_child` read: `count` bytes, from the child's errors when
66--- `errors` is true and from its output when it is not, or nothing and
67--- `finished`, once every piped output stream of the child has reached its
68--- end.
69public child_read: type = struct
70 count: usize
71 errors: bool
72 finished: bool
73end child_read
74
75--- How a child ended: it `exited` with the code its program returned, or
76--- it was `signalled`, ended from outside before it returned one. What
77--- ended it is the system's detail, which its provider keeps; on POSIX it
78--- is a signal. A POSIX code is the low eight bits of the program's
79--- value, 0 to 255.
80public ending: type = struct
81 how: variant
82 exited: (code: i32) |
83 signalled
84 end how
85end ending
86
87--- Starting programs and waiting for them. A program is named by its
88--- path, a host string, never looked up on a search path nor given to a
89--- shell; a child is given exactly the arguments and the environment its
90--- parent builds, and inherits no other open file. Each of its three
91--- standard streams is inherited, discarded or piped (`child_streams`).
92---
93--- A child that writes more than a pipe holds waits for its parent to read
94--- it, so read a piped output while the child runs, and read output and
95--- errors piped together through `read_child`, which reads whichever has
96--- something: reading one to its end first can wait forever while the
97--- child waits to write the other. Close a piped input before waiting for
98--- a child that reads it to its end.
99public programs: type = concept (provider: type)
100 --- Start the program at `path` with `arguments`, which it sees after
101 --- its own name, and exactly the variables of `environment`. A
102 --- missing program is `not_found`, one the caller may not run
103 --- `no_access`; on any failure no child was started.
104 spawn: (self: provider, path: text.host_string,
105 arguments: []text.host_string, environment: []variable,
106 streams: child_streams)
107 -> (started: child) ! not_found | no_access | io_failed
108 --- Write all of `bytes` to the child's piped input, waiting while its
109 --- pipe is full.
110 write_child: (self: provider, inout started: child, bytes: []u8)
111 -> none ! io_failed
112 --- Close the child's piped input, so that it reads to its end.
113 close_input: (self: provider, inout started: child) -> none ! io_failed
114 --- Read what the child wrote next to whichever of its piped output and
115 --- errors has something, into `into`.
116 read_child: (self: provider, inout started: child, into: []mut u8)
117 -> (taken: child_read) ! io_failed
118 --- Wait for the child to end, close every pipe it still has, and
119 --- report how it ended. The child is consumed even when this fails.
120 wait: (self: provider, sink started: child)
121 -> (ended: ending) ! io_failed
122end programs
123
124--- Start the program at `path` through `programs` evidence, with
125--- `arguments` after its name and exactly the variables of `environment`,
126--- its standard streams connected as `streams` says. Wait for every child
127--- started.
128public spawn: (provider: type is programs, host: provider,
129 path: text.host_string, arguments: []text.host_string,
130 environment: []variable, streams: child_streams)
131 -> (started: child) ! not_found | no_access | io_failed =
132 started = try provider.spawn(host, path, arguments, environment, streams)
133end spawn
134
135--- Write all of `bytes` to a child's piped input. A child that is not
136--- reading waits its parent here once its pipe is full, so write a large
137--- input only to a child that reads it as it comes. Writing to an input
138--- that is not open is `io_failed`.
139public write_child: (provider: type is programs, host: provider,
140 inout started: child, bytes: []u8) -> none ! io_failed =
141 try provider.write_child(host, started, bytes)
142end write_child
143
144--- Close a child's piped input, which it then reads to its end. Closing
145--- an input that is not open is `io_failed`.
146public close_input: (provider: type is programs, host: provider,
147 inout started: child) -> none ! io_failed =
148 try provider.close_input(host, started)
149end close_input
150
151--- Read the next bytes a child wrote to whichever of its piped output and
152--- errors has something, waiting until one has; zero bytes and `finished`
153--- once both have ended.
154public read_child: (provider: type is programs, host: provider,
155 inout started: child, into: []mut u8)
156 -> (taken: child_read) ! io_failed =
157 taken = try provider.read_child(host, started, into)
158end read_child
159
160--- Wait for a child to end and report how it did. The child is consumed,
161--- with every pipe it still had, even when this reports `io_failed`.
162public wait: (provider: type is programs, host: provider,
163 sink started: child) -> (ended: ending) ! io_failed =
164 ended = try provider.wait(host, started)
165end wait