1-- The process and the programs it runs: its arguments and environment,
2-- as the entry found them, SIGPIPE's disposition, pipes, spawning a
3-- program and waiting for it, and ending at once.
4
5import core/io
6import core/mem
7import core/text
8import platform/libc
9import platform/linux
10
11--- End the process with `status` at once, POSIX's `_exit`: nothing more
12--- of the program runs, and no C stream is flushed.
13public link(symbol: "_landin_posix_exit") exit:
14 (status: i32) -> noreturn =
15 system_exit(status)
16end exit
17
18-- What the argument root holds before it is established, and what C's
19-- null pointer is in an argv entry: no table, no string.
20no_table: atom
21
22no_string: atom
23
24root_table: type = no_table | ptr ptr u8
25
26argument_string: type = no_string | ptr u8
27
28-- The argument root: C's argv and argc as the process's entry gave them,
29-- retained without copying. The table is the pointer the entry gave,
30-- kept in module storage, so what is lent from it outlives every call.
31-- Beside it the environment the entry gave, `no_table` for C's null.
32mut root_values: root_table = no_table
33
34mut root_count: i32
35
36mut root_environment: root_table = no_table
37
38word: usize = sizeof usize
39
40--- Establish the process's argument root from C's `main` carriers. The
41--- hosted entry calls this with its own `argc`, `argv` and `envp` before
42--- its body runs; a C `main` that drives Landin exports calls it itself,
43--- with its own or with C's `environ`, before it reads an argument or a
44--- variable and before starting a thread that may.
45---
46--- The first call also ignores SIGPIPE when its disposition is the
47--- default, so that a write to a pipe or a socket nobody reads fails with
48--- `epipe` rather than ending the process; a handler a C `main` installed
49--- first is kept. `spawn` gives every child the default back.
50---
51--- The first call needs a nonnegative count and a non-null table, and
52--- keeps that exact triple without copying: the caller keeps both tables
53--- and their strings readable while any argument or variable lent from
54--- them is in use. A null environment is an empty one. Repeating the
55--- identical triple changes nothing; a negative count, a null argument
56--- table or a different triple traps. The published arguments omit
57--- `argv[0]`, so their count is `max(argc - 1, 0)`.
58public extern(c) _landin_host_initialize_arguments:
59 (count: i32, escaping values: ptr ptr u8,
60 escaping environment: ptr ptr u8) -> none =
61 -- Every misuse of the root stops at compiler.unreachable(), which a
62 -- selected panic handler sees as `unreachable` at that site [1560].
63 -- C may pass a null table, which no Landin pointer is, so its address
64 -- is what is checked.
65 given: usize = usize(values)
66 variables: usize = usize(environment)
67 held: usize = match root_values
68 no_table: usize(0)
69 ptr (table): usize(table)
70 end match
71 if held <> 0 then
72 -- An identical repeat changes nothing; another triple would
73 -- replace a root that lent arguments may still use.
74 kept: usize = match root_environment
75 no_table: usize(0)
76 ptr (table): usize(table)
77 end match
78 if count <> root_count or given <> held or variables <> kept then
79 compiler.unreachable()
80 end if
81 return
82 end if
83 if count < 0 or given == 0 then
84 compiler.unreachable()
85 end if
86 root_count = count
87 root_values = values
88 -- A write to a pipe or a socket nobody reads is EPIPE, an error the
89 -- program answers, rather than SIGPIPE ending the process.
90 system_ignore_broken_pipe()
91 if variables <> 0 then
92 root_environment = environment
93 -- On Linux, the auxiliary vector after the initial stack's envp,
94 -- when that is where C's runtime found these tables.
95 system_adopt_start(count, values, environment)
96 end if
97end _landin_host_initialize_arguments
98
99--- Whether SIGPIPE is ignored in this process, as
100--- `_landin_host_initialize_arguments` makes it when it was the default:
101--- a write to a pipe or a socket nobody reads then fails with `epipe`.
102public link(symbol: "_landin_posix_broken_pipe_ignored") broken_pipe_ignored:
103 () -> (ignored: bool) =
104 ignored = system_broken_pipe_ignored()
105end broken_pipe_ignored
106
107--- The process's arguments as its entry gave them: argv after `argv[0]`
108--- and their count. `arguments` gives the table, which lends what it
109--- points to from storage the process keeps, and `argument` reads it.
110public argument_table: type = struct
111 values: ptr ptr u8
112 count: usize
113end argument_table
114
115--- The process's arguments, `argv[0]` not among them. It traps when no
116--- argument root has been established: the hosted entry establishes one
117--- before `main` runs, and a C `main` calls
118--- `_landin_host_initialize_arguments` itself.
119public link(symbol: "_landin_posix_arguments") arguments:
120 () -> (table: argument_table) =
121 held: ptr ptr u8 = match root_values
122 no_table: compiler.unreachable()
123 ptr (found): found
124 end match
125 first: ptr ptr u8 = ptr(usize(held) + word)
126 table = (values: first,
127 count: if root_count > 0 then usize(root_count - 1)
128 else usize(0) end if)
129end arguments
130
131--- The argument `index`, from zero, as a host string lending the
132--- process's own storage, which nothing copies. An index at or past the
133--- table's `count` traps, as does a null entry, which C promises none
134--- below argc is.
135public link(symbol: "_landin_posix_argument") argument:
136 (table: argument_table, index: usize)
137 -> (value: text.host_string from table) =
138 if index >= table.count then
139 compiler.unreachable()
140 end if
141 entry: ptr argument_string = ptr(usize(table.values) + index * word)
142 match entry.val
143 no_string: compiler.unreachable()
144 ptr (data): value = text.host_over(view(data,
145 terminated_length(data)))
146 end match
147end argument
148
149--- The program's name as the process's entry gave it, `argv[0]`: what
150--- its parent called it, often the path it was started by, as a host
151--- string lending the process's own storage. It is empty when the
152--- parent gave no arguments at all, and it traps before the argument root
153--- exists, as `arguments` does.
154public link(symbol: "_landin_posix_program_name") program_name:
155 () -> (name: text.host_string) =
156 held: ptr ptr u8 = match root_values
157 no_table: compiler.unreachable()
158 ptr (found): found
159 end match
160 name = text.host_over(no_bytes[0..<0])
161 return when root_count < 1
162 first: ptr argument_string = ptr(usize(held))
163 match first.val
164 no_string: return
165 ptr (data): name = text.host_over(view(data, terminated_length(data)))
166 end match
167end program_name
168
169no_bytes: [1]u8 = [0]
170
171--- The process's environment as its entry gave it: C's `envp`, null
172--- terminated, and the count of its entries before the null, counted when
173--- it is asked for. `variable` reads it.
174public environment_table: type = struct
175 values: ptr ptr u8
176 count: usize
177end environment_table
178
179-- The table of an empty environment: one null entry.
180empty_environment: [1]usize = [0]
181
182--- The process's environment, read where the entry left it: the initial
183--- stack's on a static Linux program, and what the C runtime gave `main`
184--- otherwise, each `NAME=value` in the order it was given. It is counted
185--- afresh each time, since C code in the same process may change its own
186--- `environ` in place, which this module never does: changing the
187--- environment is not safe beside threads, and a child is given the one
188--- its parent builds instead. It traps before the argument root exists,
189--- as `arguments` does.
190public link(symbol: "_landin_posix_environment") environment:
191 () -> (table: environment_table) =
192 _ = arguments()
193 values: ptr ptr u8 = match root_environment
194 no_table: ptr(usize(addr empty_environment[0]))
195 ptr (found): found
196 end match
197 mut count: usize = 0
198 loop do
199 entry: ptr usize = ptr(usize(values) + count * word)
200 break when entry.val == 0
201 inc count
202 end loop
203 table = (values: values, count: count)
204end environment
205
206--- The variable at `index`, from zero, of the environment `table`: the
207--- bytes of its entry before the first `=` as its name and those after it
208--- as its value, host strings lending the process's own storage, which
209--- nothing copies. An entry with no `=` is a name with an empty value. An
210--- index at or past the table's `count` traps, as does a null entry, which
211--- a table changed since it was counted may hold.
212public link(symbol: "_landin_posix_variable") variable:
213 (table: environment_table, index: usize)
214 -> (item: io.variable from table) =
215 if index >= table.count then
216 compiler.unreachable()
217 end if
218 entry: ptr argument_string = ptr(usize(table.values) + index * word)
219 match entry.val
220 no_string: compiler.unreachable()
221 ptr (data): begin
222 whole: []u8 = view(data, terminated_length(data))
223 at: usize = mem.find(whole, u8('='))
224 value: []u8 = if at < lenof whole then whole[at + 1..<lenof whole]
225 else whole[lenof whole..<lenof whole] end if
226 item = (name: text.host_over(whole[0..<at]),
227 value: text.host_over(value))
228 end
229 end match
230end variable
231
232--- The two ends of a pipe: what is written to `writing` is read from
233--- `reading`.
234public pipe_ends: type = struct
235 reading: descriptor
236 writing: descriptor
237end pipe_ends
238
239--- Make a pipe, POSIX's `pipe`: its two ends, each closed when the process
240--- executes a program, which Linux and FreeBSD set as the pipe is made,
241--- with `pipe2`, and Darwin, which has none, just after; or the errno,
242--- `emfile` when the process has no descriptor to spare. A pipe holds 64
243--- KiB on Linux and between 16 and 64 KiB on Darwin and FreeBSD, and a
244--- write to a full one waits until its reader reads.
245public link(symbol: "_landin_posix_pipe") pipe:
246 () -> (ends: pipe_ends) ! errno =
247 ends = try system_pipe()
248end pipe
249
250--- Whether `opened` is closed when the process executes a program, its
251--- `FD_CLOEXEC` flag, as every descriptor this module makes is: or the
252--- errno, `ebadf` for a descriptor that is not open.
253public link(symbol: "_landin_posix_closed_on_exec") closed_on_exec:
254 (opened: descriptor) -> (yes: bool) ! errno =
255 yes = try system_closed_on_exec(opened)
256end closed_on_exec
257
258process_value: type = distinct i32
259
260--- A child process's ID, as `spawn` gives it and `wait` takes it: a
261--- handle, not a number to count with.
262public process_id: type = process_value
263
264--- The descriptors a child's standard input, output and error are made
265--- from: each is duplicated onto the child's descriptor 0, 1 or 2, so the
266--- child receives it open whether or not it is closed on exec, one that
267--- already is that number among them, and the same descriptor may be given
268--- for more than one. The parent's descriptors and their flags are left as
269--- they are. A stream that is its own number and is not open in the parent
270--- is not open in the child either; any other that is not open is `ebadf`.
271public standard_streams: type = struct
272 input: descriptor
273 output: descriptor
274 errors: descriptor
275end standard_streams
276
277--- The parent's own three standard streams, inherited.
278public inherited_streams: standard_streams = (input: standard_input,
279 output: standard_output,
280 errors: standard_error)
281
282--- Start the program at `path` in a child process, POSIX's `posix_spawn`:
283--- the program is given `path` as its name, `argv[0]`, then `arguments`,
284--- and exactly the variables of `environment`, each as `NAME=value`, and
285--- its standard streams are `streams`. Nothing searches a path for the
286--- program and no shell reads anything. Every other descriptor is closed
287--- in the child, so it receives no file but its three streams. The
288--- child's ID, or the errno, with no child started: `enoent` for a missing
289--- program, `eacces` for one that may not be executed, `enoexec` for one
290--- that is no program, `einval` for an argument or a value holding a NUL
291--- or a name that is empty or holds `=` or a NUL, and `e2big` for more than
292--- the system takes. A child must be waited for, with `wait`, or it keeps
293--- its place in the system's table until this process ends.
294---
295--- The child starts with SIGPIPE's default disposition, whatever this
296--- process's is: this process ignores it, which an exec would otherwise
297--- pass on.
298---
299--- On Linux it is a `clone` that copies this process, as `fork` does,
300--- which in the child moves the streams into place, gives SIGPIPE its
301--- default and executes the program, and reports a failure to the parent
302--- through a pipe closed on exec; on Darwin and FreeBSD it is the C
303--- library's `posix_spawn`, told to reset SIGPIPE with
304--- `posix_spawnattr_setsigdefault`. The
305--- block of strings and tables the child is given is mapped for the call
306--- and unmapped after it.
307public link(symbol: "_landin_posix_spawn") spawn:
308 (path: text.host_string, arguments: []text.host_string,
309 environment: []io.variable, streams: standard_streams)
310 -> (child: process_id) ! errno =
311 mut room: path_room = zeroed
312 terminated: ptr u8 = try terminate(path, room[0..<lenof room])
313 named: []u8 = text.host_bytes(path)
314 -- The tables first, so each pointer is aligned, then every string
315 -- with its NUL: the name, the arguments, and each variable as
316 -- `NAME=value`.
317 entries: usize = (lenof arguments + 2) + (lenof environment + 1)
318 mut size: usize = entries * word + lenof named + 1
319 for argument in arguments do
320 bytes: []u8 = text.host_bytes(argument)
321 fail einval when mem.find(bytes, 0) < lenof bytes
322 size += lenof bytes + 1
323 end for
324 for item in environment do
325 name: []u8 = text.host_bytes(item.name)
326 value: []u8 = text.host_bytes(item.value)
327 fail einval when lenof name == 0 or mem.find(name, u8('=')) < lenof name
328 or mem.find(name, 0) < lenof name or mem.find(value, 0) < lenof value
329 size += lenof name + lenof value + 2
330 end for
331 block: ptr mut u8 = try map(size)
332 defer release(block, size)
333 whole: []mut u8 = writable_view(block, size)
334 mut table: usize = usize(block)
335 mut at: usize = entries * word
336 at = place(whole, at, table, named)
337 table += word
338 for argument in arguments do
339 at = place(whole, at, table, text.host_bytes(argument))
340 table += word
341 end for
342 table += word
343 variables: usize = table
344 for item in environment do
345 start: usize = at
346 at = place(whole, at, table, text.host_bytes(item.name))
347 -- The name's NUL becomes the `=`, and the value follows it.
348 whole[at - 1] = u8('=')
349 at = place(whole, at, table, text.host_bytes(item.value))
350 slot: ptr mut usize = ptr(table)
351 slot.val = usize(block) + start
352 table += word
353 end for
354 (result: input, made: made_input) :=
355 try lifted(streams.input, standard_input)
356 defer forget(input, made_input)
357 (result: output, made: made_output) :=
358 try lifted(streams.output, standard_output)
359 defer forget(output, made_output)
360 (result: errors, made: made_errors) :=
361 try lifted(streams.errors, standard_error)
362 defer forget(errors, made_errors)
363 named_table: ptr ptr u8 = ptr(usize(block))
364 variable_table: ptr ptr u8 = ptr(variables)
365 child = try system_spawn(terminated, named_table, variable_table,
366 (input: input, output: output, errors: errors))
367end spawn
368
369-- Copy BYTES to WHOLE at AT with a NUL after them, which the mapping
370-- already is, and put their address in the table slot at TABLE: where the
371-- next string goes.
372place: (whole: []mut u8, at: usize, table: usize, bytes: []u8)
373 -> (next: usize) =
374 next = at + lenof bytes + 1
375 mem.copy(whole[at..<at + lenof bytes], bytes) else (problem)
376 _ = problem
377 compiler.unreachable()
378 end
379 slot: ptr mut usize = ptr(table)
380 slot.val = usize(addr whole[0]) + at
381end place
382
383-- Give a mapping back, whatever the system says.
384release: (block: ptr mut u8, size: usize) -> none =
385 unmap(block, size) else none
386end release
387
388-- Every stream for the child below 3 is duplicated above 2 for the call,
389-- and the duplicate closed after: one that is not its own number would be
390-- overwritten by another stream moved into place before it is read, and
391-- one that is would keep its `FD_CLOEXEC` flag and be closed by the exec,
392-- where a duplication onto its number clears the flag. The duplicate is
393-- the parent's own, so the parent's flags are left as they are. A
394-- stream that is its own number and is not open stays as it is, so the
395-- child's is not open either.
396lifted: (source: descriptor, target: descriptor)
397 -> (result: descriptor, made: bool) ! errno =
398 result = source
399 made = false
400 return when i32(source) >= 3 or i32(source) < 0
401 result = system_duplicate_above(source, 3) else (problem)
402 return when source == target and problem == ebadf
403 fail problem
404 end
405 made = true
406end lifted
407
408forget: (made_from: descriptor, made: bool) -> none =
409 if made then forgotten(made_from) end if
410end forget
411
412--- How a child ended, as `wait` reports it: it `exited` with the code its
413--- program gave `_exit` or returned from `main`, of which the parent sees
414--- the low eight bits, or it was `signalled`, ended by the signal of
415--- that number, a number each system gives its signals: SIGKILL is 9
416--- and SIGTERM 15 on all three, SIGUSR1 10 on Linux and 30 on Darwin and
417--- FreeBSD.
418public child_status: type = struct
419 how: variant
420 exited: (code: i32) |
421 signalled: (signal: i32)
422 end how
423end child_status
424
425--- Wait for the child `child` to end and report how it did, POSIX's
426--- `waitpid`, after which it is gone: or the errno, `echild` for no child
427--- of this process, `eintr` when a signal came first and the child is
428--- still to be waited for.
429public link(symbol: "_landin_posix_wait") wait:
430 (child: process_id) -> (status: child_status) ! errno =
431 word: i32 = try system_wait(child)
432 -- The status word is the same on all three systems: the low seven
433 -- bits are the signal that ended the child, zero when it exited, and
434 -- the next eight its code.
435 ending: i32 = word & 127
436 status = if ending == 0 then (how: exited(code: (word >> 8) & 255))
437 else (how: signalled(signal: ending)) end if
438end wait
439
440-- The system's own calls beneath the facility above: Linux's system
441-- calls through platform/linux, and the C library through
442-- platform/libc on Darwin and FreeBSD, whose system interface it is.
443
444fixed if compiler.os == linux then
445 system_exit: (status: i32) -> noreturn =
446 linux.exit_group(status)
447 end system_exit
448
449 system_pipe: () -> (ends: pipe_ends) ! errno =
450 mut made: [2]i32 = zeroed
451 result: i32 = linux.pipe2(addr made[0], linux.o_cloexec)
452 fail errno_of(-result) when result < 0
453 ends = (reading: descriptor(made[0]), writing: descriptor(made[1]))
454 end system_pipe
455
456 system_closed_on_exec: (opened: descriptor) -> (yes: bool) ! errno =
457 flags: i32 = linux.fcntl(i32(opened), linux.f_getfd, 0)
458 fail errno_of(-flags) when flags < 0
459 yes = (flags & linux.fd_cloexec) <> 0
460 end system_closed_on_exec
461
462 system_duplicate_above: (source: descriptor, lowest: i32)
463 -> (result: descriptor) ! errno =
464 made: i32 = linux.fcntl(i32(source), linux.f_dupfd_cloexec,
465 i64(lowest))
466 fail errno_of(-made) when made < 0
467 result = descriptor(made)
468 end system_duplicate_above
469
470 -- An end of the report pipe moved above the standard streams, so that
471 -- moving them into place in the child cannot overwrite it.
472 report_end: (given: i32) -> (result: i32) ! errno =
473 result = given
474 return when given >= 3
475 moved: i32 = linux.fcntl(given, linux.f_dupfd_cloexec, 3)
476 _ = linux.close(given)
477 fail errno_of(-moved) when moved < 0
478 result = moved
479 end report_end
480
481 -- What the child runs between the copy and the program: it moves the
482 -- streams into place, each lifted above 2 by `spawn`, so that `dup3`
483 -- clears the close-on-exec flag in the child alone, and one still its
484 -- own number, which the parent has not got open, is left closed; it
485 -- marks every other descriptor closed on exec and executes the
486 -- program, and when any of that fails writes the errno to the report
487 -- pipe and ends. It makes system calls and nothing
488 -- else: no allocation, and nothing another thread of the parent could
489 -- have held.
490 start_child: (path: ptr u8, arguments: ptr ptr u8,
491 environment: ptr ptr u8, streams: standard_streams,
492 report: i32) -> noreturn =
493 mut result: i32 = 0
494 if streams.input <> standard_input then
495 result = linux.dup3(i32(streams.input), 0, 0)
496 end if
497 if result >= 0 and streams.output <> standard_output then
498 result = linux.dup3(i32(streams.output), 1, 0)
499 end if
500 if result >= 0 and streams.errors <> standard_error then
501 result = linux.dup3(i32(streams.errors), 2, 0)
502 end if
503 if result >= 0 then
504 -- Descriptors this layer made are closed on exec already; this
505 -- closes any a C library or an inherited one left open, and a
506 -- kernel older than 5.11 that refuses it leaves only those.
507 _ = linux.close_range(3, 0 -% 1, linux.close_range_cloexec)
508 -- The parent ignores SIGPIPE, which an exec keeps; the child
509 -- starts with the default, as a program expects to.
510 mut restored: linux.signal_action = zeroed
511 restored.handler = linux.sig_dfl
512 result = linux.rt_sigaction(linux.sigpipe, addr restored,
513 linux.no_action)
514 end if
515 if result >= 0 then
516 result = linux.execve(path, arguments, environment)
517 end if
518 problem: u32 = u32(-result)
519 word: [4]u8 = [u8(problem & 255), u8((problem >> 8) & 255),
520 u8((problem >> 16) & 255), u8(problem >> 24)]
521 _ = linux.write(report, addr word[0], 4)
522 linux.exit_group(127)
523 end start_child
524
525 -- The child's report: four bytes of the errno that stopped it, or none
526 -- once its exec closed the pipe. A read interrupted by a signal is
527 -- made again: the child exists either way, and only its report says
528 -- whether it is running the program.
529 read_report: (reading: i32) -> (problem: i32) =
530 mut word: [4]u8 = zeroed
531 mut got: isize = 0
532 loop do
533 got = linux.read(reading, addr word[0], 4)
534 break when got <> -4
535 end loop
536 problem = 0
537 return when got <> 4
538 problem = i32(u32(word[0]) | (u32(word[1]) << 8)
539 | (u32(word[2]) << 16) | (u32(word[3]) << 24))
540 end read_report
541
542 system_spawn: (path: ptr u8, arguments: ptr ptr u8,
543 environment: ptr ptr u8, streams: standard_streams)
544 -> (child: process_id) ! errno =
545 mut made: [2]i32 = zeroed
546 opened: i32 = linux.pipe2(addr made[0], linux.o_cloexec)
547 fail errno_of(-opened) when opened < 0
548 reading: i32 = report_end(made[0]) else (problem)
549 _ = linux.close(made[1])
550 fail problem
551 end
552 writing: i32 = report_end(made[1]) else (problem)
553 _ = linux.close(reading)
554 fail problem
555 end
556 copied: i64 = linux.fork()
557 if copied == 0 then
558 start_child(path, arguments, environment, streams, writing)
559 end if
560 _ = linux.close(writing)
561 if copied < 0 then
562 _ = linux.close(reading)
563 fail errno_of(i32(-copied))
564 end if
565 problem: i32 = read_report(reading)
566 _ = linux.close(reading)
567 child = process_id(i32(copied))
568 return when problem == 0
569 -- The child ended without its program: reap it, so no trace of it
570 -- is left.
571 mut status: i32 = 0
572 loop do
573 reaped: i32 = linux.wait4(i32(copied), addr status, 0)
574 break when reaped <> -4
575 end loop
576 fail errno_of(problem)
577 end system_spawn
578
579 -- A program the C runtime started gives `main` the initial stack's
580 -- tables, and the auxiliary vector after them is platform/linux's
581 -- once it has held them to the kernel's layout.
582 system_adopt_start: (count: i32, values: ptr ptr u8,
583 environment: ptr ptr u8) -> none =
584 _ = linux.adopt_start(count, values, environment)
585 end system_adopt_start
586
587 -- SIGPIPE's disposition, the process's own: ignored when it was the
588 -- default, so that a write to a pipe or a socket nobody reads fails
589 -- with EPIPE rather than ending the process; a handler a C `main`
590 -- installed, and an ignore the process was started with, are kept.
591 system_ignore_broken_pipe: () -> none =
592 mut previous: linux.signal_action = zeroed
593 return when linux.rt_sigaction(linux.sigpipe, linux.no_action,
594 addr previous) < 0
595 return when previous.handler <> linux.sig_dfl
596 mut ignoring: linux.signal_action = zeroed
597 ignoring.handler = linux.sig_ign
598 _ = linux.rt_sigaction(linux.sigpipe, addr ignoring, linux.no_action)
599 end system_ignore_broken_pipe
600
601 -- Whether SIGPIPE is ignored, for a program to ask.
602 system_broken_pipe_ignored: () -> (ignored: bool) =
603 mut previous: linux.signal_action = zeroed
604 ignored = linux.rt_sigaction(linux.sigpipe, linux.no_action,
605 addr previous) >= 0
606 and previous.handler == linux.sig_ign
607 end system_broken_pipe_ignored
608
609 system_wait: (child: process_id) -> (word: i32) ! errno =
610 mut status: i32 = 0
611 result: i32 = linux.wait4(i32(child), addr status, 0)
612 fail errno_of(-result) when result < 0
613 word = status
614 end system_wait
615elsif compiler.os == darwin or compiler.os == freebsd then
616 system_exit: (status: i32) -> noreturn =
617 libc._exit(status)
618 end system_exit
619
620 fixed if compiler.os == darwin then
621 -- Darwin has no pipe2, so each end is marked closed on exec just
622 -- after the pipe is made. A program another thread executes in
623 -- between could inherit the pair; `spawn` closes every descriptor
624 -- it does not give the child either way.
625 system_pipe: () -> (ends: pipe_ends) ! errno =
626 mut made: [2]i32 = zeroed
627 fail failure() when libc.pipe(addr made[0]) <> 0
628 if libc.fcntl(made[0], libc.f_setfd, libc.fd_cloexec) <> 0
629 or libc.fcntl(made[1], libc.f_setfd, libc.fd_cloexec) <> 0
630 then
631 problem: errno = failure()
632 _ = libc.close(made[0])
633 _ = libc.close(made[1])
634 fail problem
635 end if
636 ends = (reading: descriptor(made[0]),
637 writing: descriptor(made[1]))
638 end system_pipe
639 else
640 system_pipe: () -> (ends: pipe_ends) ! errno =
641 mut made: [2]i32 = zeroed
642 fail failure() when libc.pipe2(addr made[0], libc.o_cloexec) <> 0
643 ends = (reading: descriptor(made[0]),
644 writing: descriptor(made[1]))
645 end system_pipe
646 end if
647
648 system_closed_on_exec: (opened: descriptor) -> (yes: bool) ! errno =
649 flags: i32 = libc.fcntl(i32(opened), libc.f_getfd)
650 fail failure() when flags < 0
651 yes = (flags & libc.fd_cloexec) <> 0
652 end system_closed_on_exec
653
654 system_duplicate_above: (source: descriptor, lowest: i32)
655 -> (result: descriptor) ! errno =
656 made: i32 = libc.fcntl(i32(source), libc.f_dupfd_cloexec, lowest)
657 fail failure() when made < 0
658 result = descriptor(made)
659 end system_duplicate_above
660
661 -- The actions that make the child's streams: each stream, above 2 once
662 -- `spawn` has lifted it, is duplicated onto its number, which clears
663 -- the close-on-exec flag in the child alone. A stream still its own
664 -- number is one the parent has not got open, and is left closed.
665 -- Darwin closes every descriptor no action names through its own
666 -- spawn flag; FreeBSD closes every descriptor from 3 up as the last
667 -- action.
668 stream_action: (actions: ptr mut libc.spawn_object,
669 source: descriptor, target: i32) -> (error: i32) =
670 error = 0
671 return when i32(source) == target
672 error = libc.posix_spawn_file_actions_adddup2(actions, i32(source),
673 target)
674 end stream_action
675
676 fixed if compiler.os == darwin then
677 last_actions: (actions: ptr mut libc.spawn_object,
678 attributes: ptr mut libc.spawn_object)
679 -> (error: i32) =
680 _ = actions
681 _ = attributes
682 error = 0
683 end last_actions
684
685 -- Every descriptor the actions do not name is closed, and the
686 -- signals the attributes name start at their default.
687 spawn_flags: i16 = libc.posix_spawn_cloexec_default
688 | libc.posix_spawn_setsigdef
689 else
690 last_actions: (actions: ptr mut libc.spawn_object,
691 attributes: ptr mut libc.spawn_object)
692 -> (error: i32) =
693 _ = attributes
694 error = libc.posix_spawn_file_actions_addclosefrom_np(actions, 3)
695 end last_actions
696
697 -- The signals the attributes name start at their default.
698 spawn_flags: i16 = libc.posix_spawn_setsigdef
699 end if
700
701 -- The parent ignores SIGPIPE, which an exec keeps, so the child is
702 -- told to start with its default, as a program expects to: SIGPIPE,
703 -- 13, is the set's bit 12 on both systems.
704 signal_defaults: (attributes: ptr mut libc.spawn_object)
705 -> (error: i32) =
706 mut signals: libc.signal_set = zeroed
707 signals.bits[0] = u32(1) << u32(libc.sigpipe - 1)
708 error = libc.posix_spawnattr_setsigdefault(attributes, addr signals)
709 if error == 0 then
710 error = libc.posix_spawnattr_setflags(attributes, spawn_flags)
711 end if
712 end signal_defaults
713
714 -- posix_spawn reports its failure as its result rather than in errno.
715 spawn_with: (path: ptr u8, arguments: ptr ptr u8,
716 environment: ptr ptr u8, streams: standard_streams,
717 actions: ptr mut libc.spawn_object,
718 attributes: ptr mut libc.spawn_object)
719 -> (child: process_id) ! errno =
720 mut error: i32 = stream_action(actions, streams.input, 0)
721 if error == 0 then
722 error = stream_action(actions, streams.output, 1)
723 end if
724 if error == 0 then
725 error = stream_action(actions, streams.errors, 2)
726 end if
727 if error == 0 then error = last_actions(actions, attributes) end if
728 if error == 0 then error = signal_defaults(attributes) end if
729 fail errno_of(error) when error <> 0
730 mut made: i32 = 0
731 error = libc.posix_spawn(addr made, path, actions, attributes,
732 arguments, environment)
733 fail errno_of(error) when error <> 0
734 child = process_id(made)
735 end spawn_with
736
737 system_spawn: (path: ptr u8, arguments: ptr ptr u8,
738 environment: ptr ptr u8, streams: standard_streams)
739 -> (child: process_id) ! errno =
740 mut actions: libc.spawn_object = 0
741 mut attributes: libc.spawn_object = 0
742 error: i32 = libc.posix_spawn_file_actions_init(addr actions)
743 fail errno_of(error) when error <> 0
744 given: i32 = libc.posix_spawnattr_init(addr attributes)
745 if given <> 0 then
746 _ = libc.posix_spawn_file_actions_destroy(addr actions)
747 fail errno_of(given)
748 end if
749 child = spawn_with(path, arguments, environment, streams,
750 addr actions, addr attributes) else (problem)
751 _ = libc.posix_spawnattr_destroy(addr attributes)
752 _ = libc.posix_spawn_file_actions_destroy(addr actions)
753 fail problem
754 end
755 _ = libc.posix_spawnattr_destroy(addr attributes)
756 _ = libc.posix_spawn_file_actions_destroy(addr actions)
757 end system_spawn
758
759 -- Darwin and FreeBSD keep no auxiliary vector here to adopt.
760 system_adopt_start: (count: i32, values: ptr ptr u8,
761 environment: ptr ptr u8) -> none =
762 _ = count
763 _ = values
764 _ = environment
765 end system_adopt_start
766
767 -- SIGPIPE's disposition, the process's own: ignored when it was the
768 -- default, so that a write to a pipe or a socket nobody reads fails
769 -- with EPIPE rather than ending the process; a handler a C `main`
770 -- installed, and an ignore the process was started with, are kept.
771 system_ignore_broken_pipe: () -> none =
772 mut previous: libc.signal_action = zeroed
773 return when libc.sigaction(libc.sigpipe, libc.no_address,
774 addr previous) <> 0
775 return when previous.handler <> libc.sig_dfl
776 mut ignoring: libc.signal_action = zeroed
777 ignoring.handler = libc.sig_ign
778 _ = libc.sigaction(libc.sigpipe, addr ignoring, libc.no_address)
779 end system_ignore_broken_pipe
780
781 -- Whether SIGPIPE is ignored, for a program to ask.
782 system_broken_pipe_ignored: () -> (ignored: bool) =
783 mut previous: libc.signal_action = zeroed
784 ignored = libc.sigaction(libc.sigpipe, libc.no_address,
785 addr previous) == 0
786 and previous.handler == libc.sig_ign
787 end system_broken_pipe_ignored
788
789 system_wait: (child: process_id) -> (word: i32) ! errno =
790 mut status: i32 = 0
791 fail failure() when libc.waitpid(i32(child), addr status, 0) < 0
792 word = status
793 end system_wait
794end if