Repository navigation
Expand file tree
/
Copy pathenv.cpp
More file actions
297 lines (272 loc) · 13.3 KB
/
Copy pathenv.cpp
File metadata and controls
297 lines (272 loc) · 13.3 KB
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
295
296
297
#include "sys.h"
#include <openkal/env.h>
namespace okl { int resource_preference(); }
namespace okl {
// The vectors the kernel places on the stack at inception. They are recorded
// by whichever of the two entrances the program has: this implementation's own
// program entry, when the program is linked without a C library, and otherwise
// an initialiser, which every environment that starts a hosted program calls
// with the same three arguments.
//
// Reading the pseudo-file the kernel provides was the alternative. It requires
// a file system before the program has one, and reports the arguments as they
// were at inception rather than as the program received them.
int g_argc = 0;
char** g_argv = nullptr;
char** g_envp = nullptr;
// Whether this program was started with SIGPIPE ignored: what a program it
// starts is given back (process.cpp).
bool g_pipe_started_ignored = false;
okl_ulong* g_auxv = nullptr;
void record(int argc, char** argv, char** envp) {
g_argc = argc; g_argv = argv; g_envp = envp;
if (envp) {
char** e = envp;
while (*e) ++e;
g_auxv = reinterpret_cast<okl_ulong*>(e + 1);
}
}
okl_ulong auxval(okl_ulong key) {
for (okl_ulong* a = g_auxv; a && a[0]; a += 2) if (a[0] == key) return a[1];
return 0;
}
} // namespace okl
namespace {
// ONE C LIBRARY PASSES THESE AND ANOTHER DOES NOT, AND THE ONE THAT DOES
// NOT IS THE ONE THIS PACKAGE EXISTS TO SIT BENEATH.
//
// glibc calls every `.init_array' entry with (argc, argv, envp). musl calls
// them with NO ARGUMENTS. A function declared to take three therefore receives
// whatever the argument registers happened to hold, and this one recorded it.
//
// MEASURED 2026-08-29, WITH THE CONTROL THAT SEPARATES THE TWO EXPLANATIONS.
// It was found by running the tests for aarch64, where the first enquiry after
// the count faulted --- which reads as an architecture defect. It is not:
//
// target argc argv envp
// x86_64-linux-gnu 1 <stack> <stack>
// x86_64-linux-musl 0x4004c2 1 <stack>
// aarch64-linux-musl 0x405ee4 1 <stack>
//
// Both musl rows are shifted by one and the glibc row is not, so the axis is
// the C library. Running only aarch64 would have attributed it to the machine.
//
// So the arguments are CHECKED rather than believed, and where they do not hold
// the vectors are recovered from the one handle both libraries publish.
bool plausible(int argc, char* const* argv, char* const* envp) {
if (argc < 0 || argc > 65536) return false;
if (argv == nullptr) return false;
if (envp == nullptr) return false;
if (argv[argc] != nullptr) return false; // argv is terminated at argc
if (argc > 0 && argv[0] == nullptr) return false;
return true;
}
// WEAK, AND DATA RATHER THAN A CALL. The independence check in this package
// forbids reaching for the C library's names, because a CALL into the runtime a
// program supplied would resolve to the program's and could re-enter this
// implementation without bound. A pointer cannot: it is read once, it executes
// nothing, and being weak it is null in a program that has no C library --- in
// which case this implementation supplied `_start' and recorded the vectors
// there, and this path is not taken.
extern "C" char** environ __attribute__((weak));
// The initial stack, whose shape is the ELF ABI's rather than any library's:
//
// argc argv[0] .. argv[argc-1] NULL envp[0] .. NULL auxv...
//
// so from `envp' the argument vector is reached by walking back over its
// terminator. The walk is CHECKED and not trusted: the count found in the slot
// below argv[0] must equal the number of entries actually there, which a run of
// unrelated stack words does not satisfy. Where it does not hold, nothing is
// recorded and the program is told it has no arguments -- which is an answer,
// and is what clause 7.7 asks of an implementation that cannot know.
bool recover(char*** argv_out, int* argc_out, char*** envp_out) {
char** e = environ;
if (e == nullptr || e[-1] != nullptr) return false;
for (long k = 0; k <= 65536; ++k) {
auto* slot = reinterpret_cast<long*>(e - 2 - k);
if (*slot != k) continue;
char** candidate = e - 1 - k;
bool holds = true;
for (long i = 0; i < k && holds; ++i) if (candidate[i] == nullptr) holds = false;
if (!holds || candidate[k] != nullptr) continue;
*argv_out = candidate; *argc_out = static_cast<int>(k); *envp_out = e;
return true;
}
return false;
}
// A PROGRAM ABOVE openkal SHALL NOT BE ENDED BY SOMETHING openkal NEVER
// TOLD IT ABOUT, AND WITHOUT THIS LINE ONE WAS.
//
// openkal defines no signals. `kal_stream_write' is required to REPORT that the
// far end of a stream is gone --- there is a condition for it --- and this kernel
// instead delivers SIGPIPE, whose default action ends the program. So a program
// that wrote to a closed stream did not receive the error the interface promises;
// it stopped, with a status no operation here produced and no wording anywhere in
// the specification.
//
// MEASURED THROUGH A CONSUMER, AND THE SHAPE IS WHY IT TOOK SO LONG TO SEE. A
// C library above this one answers `signal(SIGPIPE, SIG_IGN)' --- openkal has no
// signals, so the library has nothing to set and truthfully reports success. The
// program is then killed anyway, four layers below the call it made to prevent
// exactly that. Exit 141 in a test whose own assertions never printed.
//
// ⇒ Ignored HERE and not there, because here is the only place that can: the
// interface has no operation a C library could use to say it. The write then
// fails with EPIPE, which `kal_stream_write' translates and reports, which is
// what the interface said would happen all along.
//
// NOT A POLICY CHOICE ABOUT SIGNALS IN GENERAL. This is the one signal an
// ordinary openkal operation provokes; the rest are left exactly as this program
// was started with.
//
// AND IT IS THIS PROGRAM'S, NOT PASSED ON. An ignored disposition survives
// `execve', so every program started from here would otherwise begin with
// SIGPIPE ignored --- `yes | head' inside a started shell then reports a write
// error instead of ending, and a shell cannot undo an ignore it was started
// with. What was found here is recorded, and `kal_process_start' gives a
// started program the disposition this one was started with (process.cpp).
[[gnu::constructor(101)]] void quiet_the_signal_openkal_cannot_report() {
// struct k_sigaction as this kernel takes it: handler, flags, restorer, mask.
struct k_sigaction { void* handler; unsigned long flags; void* restorer; unsigned long mask; };
k_sigaction ignore{ reinterpret_cast<void*>(1) /* SIG_IGN */, 0, nullptr, 0 };
k_sigaction found{};
if (!okl::failed(okl::sys(okl::nr_rt_sigaction, 13 /* SIGPIPE */,
reinterpret_cast<okl_long>(&ignore),
reinterpret_cast<okl_long>(&found), sizeof ignore.mask)))
okl::g_pipe_started_ignored = found.handler == reinterpret_cast<void*>(1);
}
// THE PUBLISHED HANDLE FIRST, THE REGISTERS ONLY WHERE THERE IS NONE. The
// check above tells a shifted vector from a real one, not every set of
// register contents from one: a static program over musl (an ok image's M3
// program) passed it with whatever the registers held, and its image saw no
// environment at all. `environ' is the C library's own record of the vectors
// it was started with, so it is read wherever the library has one.
[[gnu::constructor(101)]] void capture(int argc, char** argv, char** envp) {
if (okl::g_argv != nullptr) return;
char** rargv = nullptr; char** renvp = nullptr; int rargc = 0;
if (recover(&rargv, &rargc, &renvp)) { okl::record(rargc, rargv, renvp); return; }
if (plausible(argc, argv, envp)) okl::record(argc, argv, envp);
}
} // namespace
extern "C" {
// EVERY VALUE IS COPIED INTO THE CALLER'S BUFFER. These answered with a pointer
// into this implementation's own storage, which is meaningful only while the
// implementation shares the caller's address space --- so the interface said
// something different depending on how it was reached, which is the one thing a
// contract must not do (clause 4.4).
//
// Each returns the length the value HAS, so a caller with a large enough buffer
// is done in one call, a caller that wants to size first passes a capacity of
// zero, and a caller whose buffer was too small learns it by comparing.
namespace {
kal_intptr give(const char* v, kal_uintptr n, char* out, kal_uintptr cap) {
if (out != nullptr && cap != 0) okl::copy(out, v, n < cap ? n : cap);
return static_cast<kal_intptr>(n);
}
} // namespace
kal_uintptr kal_env_arg_count(void) { return static_cast<kal_uintptr>(okl::g_argc); }
kal_intptr kal_env_arg(kal_uintptr index, char* out, kal_uintptr cap) {
if (index >= static_cast<kal_uintptr>(okl::g_argc)) return -kal_err_not_found;
const char* s = okl::g_argv[index];
return give(s, okl::length(s), out, cap);
}
kal_intptr kal_env_var(const char* name, kal_uintptr name_len,
char* out, kal_uintptr cap) {
if (name == nullptr) return -kal_err_invalid;
for (char** e = okl::g_envp; e && *e; ++e) {
const char* entry = *e;
kal_uintptr i = 0;
while (i < name_len && entry[i] != '\0' && entry[i] == name[i]) ++i;
if (i == name_len && entry[i] == '=') {
const char* v = entry + name_len + 1;
return give(v, okl::length(v), out, cap);
}
}
// A name that is not there is distinct from one whose value is empty, and
// reporting a length of zero for both would lose that.
return -kal_err_not_found;
}
kal_uintptr kal_env_var_count(void) {
kal_uintptr n = 0; for (char** e = okl::g_envp; e && *e; ++e) ++n; return n;
}
// The NAME at a position. The value is then obtained by kal_env_var: an
// operation answering both needs two buffers, two capacities and two lengths,
// and its second half is kal_env_var written again. The set does not change
// while the program runs, so an index may be held across the two calls.
kal_intptr kal_env_var_at(kal_uintptr index, char* out, kal_uintptr cap) {
kal_uintptr n = 0;
for (char** e = okl::g_envp; e && *e; ++e, ++n) {
if (n != index) continue;
const char* entry = *e;
kal_uintptr i = 0; while (entry[i] != '\0' && entry[i] != '=') ++i;
return give(entry, i, out, cap);
}
return -kal_err_not_found;
}
// THE IMAGE THE KERNEL STARTED (0.17). `/proc/self/exe' names the file the
// kernel mapped, resolved; where the pseudo-file system is not mounted, the
// name the kernel was given (AT_EXECFN), which is the starter's spelling and
// may be relative to where the starter was --- the environment's answer
// either way, which is what the declaration asks for.
// A program built for its environment was loaded by nothing the interface
// names (clause 12: only an image has a loader to name).
kal_intptr kal_env_loader(char* out, kal_uintptr cap) {
(void)out; (void)cap;
return -kal_err_not_found;
}
kal_intptr kal_env_image(char* out, kal_uintptr cap) {
char buf[4096];
const okl_long n = okl::sys(okl::nr_readlinkat, okl::at_fdcwd,
reinterpret_cast<okl_long>("/proc/self/exe"),
reinterpret_cast<okl_long>(buf), sizeof buf);
if (!okl::failed(n) && n > 0 && n < static_cast<okl_long>(sizeof buf))
return give(buf, static_cast<kal_uintptr>(n), out, cap);
if (const okl_ulong fn = okl::auxval(31 /* AT_EXECFN */); fn != 0) {
const char* name = reinterpret_cast<const char*>(fn);
return give(name, okl::length(name), out, cap);
}
return -kal_err_not_found;
}
// For conventions only, as the declaration restricts it.
kal_intptr kal_env_system(char* out, kal_uintptr cap) {
return give("linux", 5, out, cap);
}
// THE RESOURCE PREFERENCE (0.23), which a start conveys in KAL_RESOURCES
// (process.cpp) and a person sets the same way.
int kal_env_resources(void) { return okl::resource_preference(); }
}
namespace okl {
int resource_preference() {
static int known = -1;
int v = __atomic_load_n(&known, __ATOMIC_RELAXED);
if (v >= 0) return v;
v = 0;
constexpr char key[] = "KAL_RESOURCES=";
constexpr okl_uptr key_len = sizeof key - 1;
for (char** e = okl::g_envp; e && *e; ++e) {
okl_uptr i = 0;
while (i < key_len && (*e)[i] == key[i]) ++i;
if (i != key_len) continue;
const char* val = *e + key_len;
auto is = [&](const char* w) {
okl_uptr k = 0;
while (w[k] && val[k] == w[k]) ++k;
return w[k] == '\0' && val[k] == '\0';
};
v = is("performance") ? 1 : is("economy") ? 2 : 0;
break;
}
__atomic_store_n(&known, v, __ATOMIC_RELAXED);
return v;
}
} // namespace okl
namespace {
// THE ENVIRONMENT'S OWN POLICY FOR MEMORY, AS THE PREFERENCE ASKS (0.23): a
// program started for economy has no transparent huge pages, which hold a whole
// two mebibytes resident for the first byte touched. After `capture', which
// reads the environment.
[[gnu::constructor(102)]] void apply_resource_preference() {
if (okl::resource_preference() == 2)
okl::sys(okl::nr_prctl, 41 /* PR_SET_THP_DISABLE */, 1, 0, 0, 0);
}
} // namespace