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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
//! zsh's man and info pages, shipped inside the binary.
//!
//! **zshrs-original infrastructure — no C source counterpart.** zsh's
//! `make install` drops `zsh.1`, `zshall.1`, `zshbuiltins.1` … under
//! `<prefix>/share/man/man1` and `zsh.info*` under `<prefix>/share/info`,
//! and `man`/`info` find them because that prefix is already on the
//! system's search path.
//!
//! zshrs is a drop-in binary that is not installed under a zsh prefix, so
//! `man zshall` and `info zsh` worked only on a host that happened to have
//! zsh installed — while the pages document the language zshrs itself
//! implements. `build.rs` packs `vendor/zsh-doc` into the binary and this
//! module writes it to `~/.zshrs/{man,info}` on first run, then puts those
//! directories on `MANPATH` / `INFOPATH`.
//!
//! Both variables are extended, never replaced: an empty trailing entry is
//! how `man` and `info` are told "and then your usual search path", so a
//! shell that had no `MANPATH` still finds every other page on the system.
//!
//! Errors are swallowed on purpose, as in [`crate::bundled_functions`]: a
//! read-only or full `$HOME` must not stop the shell from starting.
use Write;
use ;
/// The packed tree: `u32 name_len | name | u32 body_len | body`, repeated,
/// little-endian, zstd-compressed. Names keep their `man1/` or `info/`
/// prefix. Written by `bundle_zsh_docs` in build.rs.
static BUNDLE: & = include_bytes!;
include!;
/// Written into the directory so a zshrs carrying different pages
/// refreshes them instead of leaving a stale set from an older build.
const STAMP: &str = ".zshrs-docs-version";
/// What [`STAMP`] holds: crate version plus the bundle's content hash.
/// The version alone is not enough — the pages can change within a
/// version, and then a version-only stamp never triggers a rewrite.
/// `~/.zshrs`.
/// `~/.zshrs/man` — the `MANPATH` entry, holding `man1/`.
/// `~/.zshrs/info` — the `INFOPATH` entry.
/// `~/.zshrs/help` — the `HELPDIR` entry, `run-help`'s database.
/// True when the tree is absent or holds a different bundle.
/// Materialise the pages when missing or stale. Returns how many files
/// were written; `Some(0)` means the tree was already current.
///
/// The cost on the common path is one `read_to_string` of a short stamp.
/// Prepend `dir` to a colon-separated search variable, keeping whatever
/// the user already had and leaving an empty trailing entry when the
/// variable was unset.
///
/// The empty entry matters: `man` and `info` read `a::b` / a trailing `:`
/// as "splice the built-in default search path in here". Setting a bare
/// `MANPATH=~/.zshrs/man` would instead REPLACE the system's man path and
/// hide every other page on the machine.
/// The search-path directories to publish, once materialised. `HELPDIR`
/// is not among them: it is a plain scalar naming ONE directory, not a
/// colon list, and it is a shell parameter rather than an environment
/// entry, so it is set outright by [`seed_helpdir_param`].
/// `$HOME` exactly as the process INHERITED it, empty treated as absent.
///
/// Not `std::env::var("HOME")`: by the time [`seed_helpdir_param`] runs,
/// `ShellExecutor::new` has already synthesised a `$HOME` from the
/// password database for a shell launched without one (c:Src/init.c:
/// 1237-1250, ported beside the env-import loop) and `addenv`'d it, so
/// the live environment can no longer tell the two cases apart. The
/// process-entry snapshot can — it is `environ` as `main` froze it, which
/// is the same source c:Src/params.c:893 `createparamtable` imports from.
/// The fallback is for lib tests, which never run `main` and so never
/// fill the snapshot.
/// `HELPDIR`, if the user has not chosen one.
///
/// zsh does not export `HELPDIR`; `run-help` falls back to a default
/// baked in at build time by whichever zsh compiled it — the vendored copy
/// carries `/opt/homebrew/Cellar/zsh/5.9.2/share/zsh/help`, a path that
/// does not exist on a host without that exact install. Pointing it at the
/// bundled tree is what makes `run-help` work with no zsh on the machine.
///
/// Resolved against [`inherited_home`] rather than [`help_dir`], so a
/// shell launched with NO `$HOME` at all gets no `HELPDIR` — see the
/// scrubbed-environment note on [`seed_helpdir_param`].
///
/// A user-set value always wins: this only fills an empty slot.
/// Point `$HELPDIR` at the bundled help tree as a PLAIN SHELL PARAMETER.
///
/// `run-help` is a shell function — `local HELPDIR="${HELPDIR:-<compiled
/// default>}"` at `vendor/zsh/functions/run-help:13` — so it reads the
/// shell's own parameter table and never the process environment. Putting
/// `HELPDIR` in the environment therefore bought the feature nothing and
/// leaked the name into EVERY child process: with `$HOME` set, a child of
/// zshrs saw `HELPDIR`, `MANPATH` and `INFOPATH` where a child of zsh saw
/// only `MANPATH`, and `env rm <TAB>` (which lists exported names via
/// `_parameters -g "*export*"`) offered a name zsh does not have.
/// `MANPATH` / `INFOPATH` stay exported because `man` and `info` are
/// separate PROCESSES and read them from their environment.
///
/// Must be called AFTER the environment-import loop has built paramtab:
/// c:Src/params.c:893 `createparamtable` imports `environ` exactly as it
/// was at process entry, so a user's exported `HELPDIR` is already in the
/// table by then and the `getsparam` guard below leaves it alone —
/// value and `PM_EXPORTED` both intact, since it came in exported.
///
/// Nothing is seeded when the process inherited NO `$HOME`. Running after
/// the import loop also means running after `$HOME` has been synthesised
/// from the password database, and resolving the tree against THAT would
/// give a scrubbed `env -i` shell a `$HELPDIR` — a name the oracle does
/// not have there (`env -i zsh -f -c 'print ${+HELPDIR}'` says 0), and a
/// silent one, since `${(k)parameters}` is what the completion system
/// reads. [`inherited_home`] is the test that keeps the two shells equal
/// in both arms.
/// Materialise the pages and put them on `MANPATH` / `INFOPATH` in the OS
/// environment, so any child process (`man`, `info`) inherits them.
///
/// Called from `ShellExecutor::new`. This alone does NOT make the shell's
/// own `$MANPATH` / `$INFOPATH` show the new value when the variable was
/// already in the inherited environment — paramtab is built from the
/// process-entry `environ` snapshot taken in `main`, which a later
/// `setenv` cannot reach. The binary entry calls [`publish_into`] on that
/// snapshot for the shell-visible half.
///
/// `HELPDIR` is not published here, or anywhere else in the environment:
/// it is a shell parameter only, seeded by [`seed_helpdir_param`] once
/// paramtab exists.
/// Materialise the pages and publish them into a process-entry `environ`
/// snapshot before it is frozen.
///
/// c:Src/params.c:893 — `createparamtable` imports `environ` exactly as it
/// was at process entry, and zshrs preserves that by snapshotting `envp`
/// in `main`. An `$INFOPATH` inherited from the parent therefore keeps its
/// old value in the shell no matter what the shell `setenv`s afterwards;
/// `$MANPATH` only appeared to work because it is a PM_TIED colonarray
/// re-derived from the live environment later. Editing the snapshot fixes
/// both, and keeps the process environment in step for child commands.