Landin library reference discuss source

platform/posix/processes.ldn

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