1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
//! **Writing an executable file is a CHILD's job, never this process's**
//! (bl-fd28, generalized to production by bl-e6c9) — the whole of the ETXTBSY
//! discipline, in the one place the crate spells it.
//!
//! `fs::write` on a file holds a write fd for the length of the write. A `fork`
//! on ANY thread copies that fd into a child that keeps it until its own `exec`
//! completes, and an `exec` of that file inside the window is **ETXTBSY**. The
//! window cannot be closed from the fork side: [`super`]'s `cfg(test)` lock
//! covered only yog's own forks, and yog links `balls`, `litany` and `brazen`,
//! each of which forks `git` on its own account (measured, and measured out —
//! [`super`]'s module doc carries both numbers).
//!
//! bl-fd28 closed it on the side that owns it, for test fixtures. **The engine
//! had the same hazard and kept it**: `world::tools::ensure_shim` wrote the
//! world's shims with `fs::write` + `set_permissions` and a caller exec'd one
//! immediately after — yog composes a world's shims and then runs them, and yog
//! forks from every thread. Reproduced at bl-fd28's own recipe with the
//! `world::tools`/`world::tests` beats folded into the filter, 16 workers x 70
//! iterations: **7 ETXTBSY failures**, every one of them a shim exec.
//!
//! So there is one helper and it is production's: [`write_exec`]. The fd lives
//! in `sh`, which holds it for as long as `cat` runs and never for a moment in
//! this process, so a peer fork — in yog or in any crate yog links, at any
//! moment — has nothing of ours to copy. `rules/no-hand-chmod.yml` refuses the
//! hand-rolled spelling everywhere in `src`, so the discipline is structural
//! rather than a convention the next executable forgets.
//!
//! Two shapes were rejected. A **retry on ETXTBSY** turns a hazard into a
//! production loop and leaves the window open for whoever does not spin. A
//! **write-then-rename** does not work at all: a rename does not change the
//! inode, so the copied write fd still refers to the file the caller execs.
//!
//! The body goes down a pipe rather than an argv word, so nothing here has an
//! `ARG_MAX`. The pipe is [`io::pipe`] rather than `Stdio::piped()` for a
//! smaller reason that is worth the two `drop`s: `Child::stdin` is an `Option`
//! that can only be `Some` here, and an owned pipe has no unreachable arm to
//! answer for. A body large enough to fill the pipe buffer before `cat` drains
//! it would deadlock; a shim is a few hundred bytes and a fixture is a script.
//! `sh`'s stderr is captured rather than inherited, so the reason a write
//! failed rides the error instead of the caller's terminal.
//!
//! **A failed child outranks a broken pipe** (bl-4b71). The child is waited for
//! whatever the write did, and the write's own error is answered with only when
//! the child exited cleanly — because `EPIPE` on the way in *means* the child is
//! already gone, so its status and stderr are the honest reason and the raw
//! errno is a symptom. Reporting the write first made the outcome a scheduler
//! race between the parent's `write_all` and the child's death, which reddened
//! roughly one close gate in five repo-wide; this ordering gives both
//! interleavings one answer.
use ;
use Path;
use Stdio;
/// The one recipe: read the body from stdin, then set the executable bits. Both
/// acts are the child's, which is the point — this process opens nothing.
const RECIPE: &str = r#"cat > "$1" && chmod 755 "$1""#;
/// Write `body` to `path` and mark it `0755` — **executable by all, writable
/// only by the owner** — entirely inside a child process. The crate's one way
/// to create an executable file, in production and in tests alike
/// (`crate::test_support::write_exec` is this function with the error turned
/// into a panic, which is all a fixture wants).
pub