1-- The blocking provider's `io.programs` (D479): a child is started by the
2-- system handle this provider is built over, which does not wait, and
3-- its pipes, files of that handle, are read and written as this
4-- provider's `read` and `write` read and write any other, blocking the
5-- thread, as waiting for the child does, POSIX's `waitpid`.
6
7import core/io
8import core/text
9import hosted/system
10import platform/posix
11
12blocking_spawn: (self: host, path: text.host_string,
13 arguments: []text.host_string, environment: []io.variable,
14 streams: io.child_streams)
15 -> (started: io.child)
16 ! io.not_found | io.no_access | io.io_failed =
17 started = try system.start(self.services, path, arguments, environment,
18 streams)
19end blocking_spawn
20
21-- Close one of a child's pipes, which is closed even when the system
22-- reports a failure.
23close_pipe: (self: host, opened: io.file) -> none ! io.io_failed =
24 posix.close(system.descriptor_of(opened)) else (problem)
25 record(self, problem)
26 fail io.io_failed
27 end
28end close_pipe
29
30-- Write all of BYTES to the child's input pipe, blocking the thread while
31-- the pipe is full, as any other write does.
32blocking_write_child: (self: host, inout started: io.child, bytes: []u8)
33 -> none ! io.io_failed =
34 cleared(self)
35 fail io.io_failed when not started.input_open
36 try write_all(self, started.input, bytes)
37end blocking_write_child
38
39blocking_close_input: (self: host, inout started: io.child)
40 -> none ! io.io_failed =
41 cleared(self)
42 fail io.io_failed when not started.input_open
43 started.input_open = false
44 try close_pipe(self, started.input)
45end blocking_close_input
46
47-- Whether the child's errors, and not its output, is what to read next:
48-- the output when it has anything, its end included, and the errors only
49-- when they alone do. A wait a signal interrupted is made again.
50errors_first: (self: host, started: io.child) -> (yes: bool)
51 ! io.io_failed =
52 mut entries: [2]posix.poll_entry =
53 [posix.watch(system.descriptor_of(started.output), posix.poll_input),
54 posix.watch(system.descriptor_of(started.errors), posix.poll_input)]
55 loop do
56 _ = posix.poll(entries[0..<2], -1) else (problem)
57 continue when problem == posix.eintr
58 record(self, problem)
59 fail io.io_failed
60 end
61 break
62 end loop
63 yes = posix.happened(entries[0]) == posix.poll_no_events
64end errors_first
65
66blocking_read_child: (self: host, inout started: io.child, into: []mut u8)
67 -> (taken: io.child_read) ! io.io_failed =
68 cleared(self)
69 taken = (count: 0, stream: io.child_output,
70 finished: not started.output_open and not started.errors_open)
71 return when lenof into == 0
72 loop do
73 if not started.output_open and not started.errors_open then
74 taken = (count: 0, stream: io.child_output, finished: true)
75 return
76 end if
77 mut from_errors: bool = not started.output_open
78 if started.output_open and started.errors_open then
79 from_errors = try errors_first(self, started)
80 end if
81 opened: io.file = if from_errors then started.errors
82 else started.output end if
83 count: usize = try blocking_read(self, opened, into)
84 if count > 0 then
85 taken = (count: count,
86 stream: if from_errors then io.child_errors
87 else io.child_output end if,
88 finished: false)
89 return
90 end if
91 -- That stream has ended, and its pipe is done with.
92 if from_errors then
93 started.errors_open = false
94 else
95 started.output_open = false
96 end if
97 try close_pipe(self, opened)
98 end loop
99end blocking_read_child
100
101-- Close every pipe the child still has, keeping the first failure.
102close_pipes: (self: host, started: io.child) -> (closed: bool) =
103 closed = true
104 if started.input_open then
105 close_pipe(self, started.input) else (problem)
106 _ = problem
107 closed = false
108 end
109 end if
110 if started.output_open then
111 close_pipe(self, started.output) else (problem)
112 _ = problem
113 closed = false
114 end
115 end if
116 if started.errors_open then
117 close_pipe(self, started.errors) else (problem)
118 _ = problem
119 closed = false
120 end
121 end if
122end close_pipes
123
124-- Wait for the child, again when a signal interrupts the wait.
125waited: (self: host, child: posix.process_id)
126 -> (status: posix.child_status) ! io.io_failed =
127 loop do
128 status = posix.wait(child) else (problem)
129 continue when problem == posix.eintr
130 record(self, problem)
131 fail io.io_failed
132 end
133 return
134 end loop
135end waited
136
137-- The pipes are closed before the wait, so a child still writing to one
138-- meets a closed pipe rather than waiting for a reader that never comes.
139blocking_wait: (self: host, sink started: io.child)
140 -> (ended: io.ending) ! io.io_failed =
141 cleared(self)
142 closed: bool = close_pipes(self, started)
143 status: posix.child_status =
144 try waited(self, system.process_of(started))
145 ended = system.ended(self.services, status)
146 fail io.io_failed when not closed
147end blocking_wait
148
149host is io.programs
150 (spawn: blocking_spawn, write_child: blocking_write_child,
151 close_input: blocking_close_input, read_child: blocking_read_child,
152 wait: blocking_wait)