Skip to content

Commit 250f002

Browse files
0.7.0 — the crash a consumer could not localise, and openkal 0.9 (#18)
* 0.7.0 --- the crash a consumer could not localise, and openkal 0.9 ⚠️⚠️ `signal(SIGABRT, …)` DESTROYED ITS CALLER'S RETURN ADDRESS. `SYS_rt_sigprocmask` cleared `sizeof(sigset_t)` --- 128 bytes --- into whatever the caller passed, while `a4` is the SIGSETSIZE the caller declared, which is eight on this architecture and is what the kernel writes. Sixteen of musl's seventeen callers pass a 128-byte `sigset_t` and saw nothing. The seventeenth is `src/signal/sigaction.c`, which declares one word and reaches this only when the signal is SIGABRT; that local sits at -0x20 in a frame of 0x30, so 120 bytes past it lay the saved frame pointer and the return address, and `__sigaction` returned to zero. ⭐ The whole of the reproduction is three lines: int main(void) { signal(SIGABRT, h); return 0; } SIGSEGV, instruction pointer at zero, stack top zero rather than a return address. Reported in openkal-linux#13 with a dozen forks and four threads in front of it, because that is what the program that met it was doing; none of it was necessary. Both earlier exclusions in that report were correct. ⚠️ AND IT WAS NOT ONLY INSTALLING A HANDLER. `signal(SIGABRT, SIG_IGN)` and a plain enquiry, `sigaction(SIGABRT, NULL, &old)`, died the same way --- so a program that only READ what SIGABRT was set to could not survive doing so, which a terminal library, a test framework's death tests and any crash reporter all do before they do anything else. `SYS_rt_sigaction` had the same defect reversed: it cleared the size of a structure declared here, three fields, where `struct k_sigaction` is four, so eight bytes of the caller's structure were left holding the stack and were copied out to the program. ⭐ EACH IS THE SAME MISTAKE: A SIZE TAKEN FROM THE TYPE AT HAND RATHER THAN FROM THE CONTRACT BEING ANSWERED. `examples/posix` now touches every signal's disposition in all three forms --- enquiry, ignore, handler. It had thirty-six observations, three about `abort`, and no call to `signal` anywhere: it examined whether `abort` ends the program and never whether a program may ask what a signal is set to. --- and four more the same consumer would have met ------------------------ * `getpgrp()` answered -38. There was no case for the number, and musl's `getpgrp` deliberately does not pass the result through `__syscall_ret` because POSIX says the call cannot fail --- so a negated error was handed over as a process group, with no errno and nothing to check. * `st_dev` and `st_ino` were constants, so every file was the same file: `std::filesystem::equivalent` on two separately created files answered TRUE with no error. openkal 0.9 carries an identity and this reports it, and reports nothing rather than a constant where the implementation has none. * `stat` and `lstat` were one call. The flag was ignored while `open` resolved, so a program was told a name referred to a link when opening it would have reached a file --- and one such node made a whole tree uncopyable for the C++ library above. * `symlink` and `readlink` answer, over `openkal.fs`'s new operations, after asking `kal_fs_props` whether the volume has such nodes. * `SYS_membarrier` has a case of its own so the trace does not report it. It comes from musl's own `pthread_create`, and its result is assigned to nothing --- one of the six numbers the first user of that switch reported was a false alarm, and the cost of that fell on them. * The started-program bound is stated in the README and raised to 256. The sixty-fifth `posix_spawn` of a program that waited for none reported EAGAIN, which is what POSIX says --- what was wrong is that the number was invisible, so the failure landed on an operation with no evident relation to the ones that caused it. --- openkal 0.9 ------------------------------------------------------------ Transfers return one signed word, which is what every site here computed from the earlier two-word result by hand. Values are copied into this library's own buffers rather than pointed at. `kal_node_info` states its size and reports what was filled. The page size is asked for rather than fixed at build time --- it was 4096 in two places, and is what this library reports as `_SC_PAGESIZE`, rounds mappings to, and reports as `st_blksize`. ⭐ And the one weak reference that needed a different spelling no longer does: `kal_process_props` was an object, so testing it the way every other weak reference is tested WAS the null dereference the test existed to prevent. Every report is an operation now. Built and verified for x86_64-linux-musl and aarch64-linux-musl. * README: the versions it names are the versions that exist Every README here opens by showing what a program writes in its manifest, which is the first thing a reader copies and the last thing anyone edits. These lines had drifted --- the specification's own README asked for a version four minor releases old --- and nothing checked them. `openkal/tools/check-readme-versions.sh` now does. * fix: O_NOFOLLOW on a link is ELOOP, and answering ENOENT made a tree unremovable openkal states that opening resolves and offers no form that declines to -- deliberately, since a program that opens a link to read its bytes is asking what kal_fs_link_read answers. So do_openat resolved, and for a link whose target is absent it answered ENOENT. That is a different answer to a different question. O_NOFOLLOW does not ask to open the link and does not ask to open its target: it asks whether the name is a link, and POSIX says ELOOP when it is. Nothing nearby looked wrong. Every operation this port offers still held -- symlink, readlink, stat, lstat, unlink, getdents all behaved as the host does. What failed was three layers up: libc++'s remove_all descends by opening each entry O_DIRECTORY|O_NOFOLLOW and reads ENOENT as "the entry is already gone", so it unlinked nothing and then reported ENOTEMPTY for a directory it had just declined to empty. The host toolchain removed the same tree. Measured on a tree holding a dangling link, a live link and a link to a directory: 6 removed on both, none left behind. The answer comes from the enquiry openkal 0.9 added -- ask about the name itself -- on a path taken only when the caller passed the flag. Also here: the traceprobe step asserted that an absent operation reports itself by naming symlinkat, which this port now implements. It reported zero diagnostics for a hundred attempts, which is the same reading a broken diagnostic channel gives. It now names mknodat and asserts that the dispatcher does not handle it, so the day that changes this step says so instead of passing while measuring nothing. And openkal-windows was pinned at 0.3.0 here against a package now at 0.4.0. * fix: the link operations go through the seam, like everything else okm_opt.h exists so that the decision "is this interface present?" is made once rather than at forty call sites, and its opening comment says why: "a forty-first added later would be the one that was missed -- and missed silently, because the way it shows is a link failure on a target nobody was building at the time." do_readlink and do_symlink were the forty-first. They named kal_fs_link_* di- rectly and tested the weak symbol themselves, which is correct where openkal.fs is present and is not a declaration at all where it is absent: the weak declarations live inside that branch, so in the OKM_HAS_FS == 0 configuration the names came from <openkal/fs.h> strong, and a backend with no filesystem failed to link. ld.lld: error: undefined symbol: kal_fs_link_read >>> referenced by okm_syscall.c:340 ... (do_readlink) Found by openkal-opensbi's bare-metal row, which is the only row that builds that configuration. The null test now lives in the seam, once, and the callers name okm_fs_link_*. Verified by building examples/same-source for riscv64-none-elf over openkal-opensbi. Also: the identity assertion in the posix probe now says where to look when it fails. This port puts zero in st_dev/st_ino when the implementation reports no identity, so every node compares equal to every other -- and the defect is in that implementation, not here. It was measured against openkal-windows. * fix: openkal's granularity is a floor to respect, not this library's page size They are different quantities wearing the same name. openkal's granularity is the coarsest quantum a caller must respect, and an implementation for a machine with no memory management unit answers ONE -- correctly: there is no page, and nothing needs rounding. openkal-opensbi does. libc.page_size is what this library rounds heap growth to, reports as sysconf(_SC_PAGESIZE) and as st_blksize, and whose arithmetic its allocator assumes is a power of two no smaller than its own quantum. Given one, the allocator asked the environment for one-byte extents and the program stopped inside the first allocation large enough to need a new one. Measured, and only on the machine that answers that way. Over openkal-opensbi the same-source example printed three of its four lines and stopped: containers, exceptions and unwinding all held, and the fourth line was the first to format a string. Over openkal-linux, whose answer is 4096, nothing was wrong. Confirmed by putting the defect back and running it again -- the fourth line disappears and returns with the fix. The probe's own assertion did not catch it and could not have: it said "a positive power of two", and one is both. It now says what the allocator requires, and asks for several pages in one allocation and writes every byte of them -- which is the property that actually broke. * README: O_NOFOLLOW is answered by an enquiry, not by an opening A property of the arrangement rather than an omission, and one a reader meets: openkal offers no opening that declines to resolve, so this port asks first and reports ELOOP. Answering ENOENT is a different answer to a different question, and libc++'s remove_all reads it as "the entry has already gone". --------- Co-authored-by: speak-agent <x.d2learn.org@gmail.com>
1 parent c64ad1d commit 250f002

12 files changed

Lines changed: 701 additions & 150 deletions

File tree

.github/workflows/ci.yml

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -615,22 +615,41 @@ jobs:
615615
cxx_runtime = "host-coupled"
616616
TOML
617617
sed -i 's/^ //' "$d/mcpp.toml"
618-
# ⭐ `symlinkat` IS THE OPERATION AND THE PROBE PRINTS ITS OWN NUMBER.
618+
# ⚠️⚠️ THE OPERATION THIS PROBE NAMES MUST STILL BE ABSENT, AND THE
619+
# ONE IT NAMED STOPPED BEING SO.
619620
#
620-
# openkal has no atom that creates a link --- the manifest and README
621-
# both record that --- so it reaches the default arm rather than a case
622-
# with a decision of its own. The number is printed by the probe rather
623-
# than computed here, so the expectation comes from the same headers
624-
# the dispatcher was compiled with; a number worked out on the host
625-
# would be answering for the host's C library.
621+
# It was `symlinkat`, chosen because openkal had no atom that creates a
622+
# link. openkal 0.9 added `kal_fs_link_create`, this dispatcher gained
623+
# a case for it, and the probe --- unchanged, still green in intent ---
624+
# began exercising an operation that now SUCCEEDS. It reported zero
625+
# diagnostics for a hundred attempts, which is the same reading a
626+
# broken diagnostic channel gives.
627+
#
628+
# ⭐ SO THE ABSENCE IS NOW ASSERTED RATHER THAN ASSUMED. `mknodat`
629+
# creates a device node, which openkal does not express and is not
630+
# going to; if a case for it ever appears, the grep below fails and
631+
# says to pick another operation --- instead of this step passing
632+
# while measuring nothing.
633+
if grep -q 'SYS_mknodat' port/src/okm_syscall.c; then
634+
echo "::error::the dispatcher now handles mknodat, so this probe no"
635+
echo "::error::longer exercises an absent operation. Pick another"
636+
echo "::error::operation openkal has no atom for and name it here."
637+
exit 1
638+
fi
639+
# The number is printed by the probe rather than computed here, so the
640+
# expectation comes from the same headers the dispatcher was compiled
641+
# with; a number worked out on the host would answer for the host's C
642+
# library.
626643
printf '%s\n' \
627644
'#include <sys/syscall.h>' \
645+
'#include <sys/stat.h>' \
628646
'#include <fcntl.h>' \
629647
'#include <unistd.h>' \
630648
'#include <stdio.h>' \
631649
'int main(void) {' \
632-
' for (int i = 0; i < 100; i++) (void)!symlinkat("a", AT_FDCWD, "b");' \
633-
' printf("%ld\n", (long)SYS_symlinkat);' \
650+
' for (int i = 0; i < 100; i++)' \
651+
' (void)!mknodat(AT_FDCWD, "node", S_IFIFO | 0600, 0);' \
652+
' printf("%ld\n", (long)SYS_mknodat);' \
634653
' return 0;' \
635654
'}' > "$d/src/main.c"
636655
( cd "$d" && mcpp build --toolchain '${{ matrix.toolchain }}' )

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,8 @@ crash.log
3030
# What the object-level checks in continuous integration write beside the
3131
# sources they examine.
3232
syms.txt
33+
34+
# A working tree of the specification or of an implementation placed beside the
35+
# sources. No trailing slash: the pattern must match a symbolic link as well.
36+
.spec
37+
.impl

README.md

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ the claim can be checked rather than repeated.
88

99
```toml
1010
[dependencies]
11-
openkal-musl = "0.6.0"
11+
openkal-musl = "0.7.0"
1212
```
1313

1414
It names no implementation and no platform: a C library is the one consumer that
@@ -108,8 +108,10 @@ answer that leaves a program wrong without telling it.
108108
| memory protection | `mprotect` reports `ENOSYS` | openkal has no operation upon a mapping's protection. musl asks for a guard page below a thread's stack and proceeds without one when told this, so the honest answer is also the one it is prepared for. |
109109
| out-of-band data | `MSG_OOB`, `MSG_PEEK`, and `POLLPRI` are never reported and `recv` refuses the flags | openkal's transfer operations move bytes and have no second channel and no non-destructive read. |
110110
| readiness *sets* | `epoll` is not built at all, so the link names it | a set held by the environment is a facility of one kernel rather than a capability. `poll` and `select` ask each descriptor in turn, which is what an interface without a set permits. |
111-
| symbolic links | `symlink` reports `ENOSYS`; `readlink` reports `EINVAL` for a name that is not one and `ENOSYS` for one that is | `SURFACE.txt` has no operation that creates or reads a link. It *does* have `kal_node_link` and `KAL_FS_PROP_LINKS`, so an implementation can report a link it encounters and cannot make one; the asymmetry is the specification's and is recorded rather than worked around. |
112-
| ownership and permission bits | `chmod` and `chown` report `ENOSYS`; `stat` reports a mode assembled from what openkal knows | `kal_node_info` carries `writable` — one boolean, not a mode word — and `kal_fs_open_file` takes flags rather than a mode. Mapping the owner-write bit onto it would make `chmod(0600)` succeed and `stat` report something else, which is the shape this port exists to avoid. |
111+
| ~~symbolic links~~ | **answered since 0.7.0**`symlink`, `readlink`, and `stat`/`lstat` telling the two questions apart | openkal 0.9 carries `kal_fs_link_create` and `kal_fs_link_read` as operations of `openkal.fs`, and `kal_fs_props` takes the directory, so this port asks whether the volume has such nodes before it uses them. Where it does not, the refusal is what the enquiry already said. |
112+
| permission bits | `chmod` reports `ENOSYS`; `stat` reports a mode assembled from what openkal knows | `kal_node_info` carries `writable` — one boolean, not a mode word — and `kal_fs_open` takes flags rather than a mode. Mapping the owner-write bit onto it would make `chmod(0600)` succeed and `stat` report something else, which is the shape this port exists to avoid. |
113+
| the identity of a node | `st_dev` and `st_ino` are the implementation's answer where it has one, and **zero for both where it has none** | ⚠️ They were the constants 0 and 1, so every file compared equal to every other: `std::filesystem::equivalent` on two separately created files answered `true` **with no error**. openkal 0.9 carries an identity and reports whether it knows one; a caller must not read two zeroes as sameness, which is why nothing is invented for an implementation that cannot distinguish nodes. |
114+
| ownership | `chown` reports `ENOSYS`; `stat` reports 1000 for both | as the row above: a capability-oriented environment has no principal for an owner to name. |
113115
| a mode given at creation | `open(…, O_CREAT, 0600)` and `mkdir(path, 0700)` **succeed** and `stat` afterwards reports 0666 and 0777 | the row above, in the one place where it does not read as a refusal. openkal opens a file for a purpose and not for an audience, so the argument has nowhere to go. Refusing every mode but the one `stat` will report would refuse nearly every program; what a caller can rely on instead is stated below. |
114116
| entropy | `getrandom` reports `ENOSYS` where the backend declines `openkal.random` | openkal has no source of one to require, and this port does not invent one. The allocator's cookie and the stack canary are derived from the clock and from an address; neither is a security property here. |
115117
| a signal delivered anywhere | `raise` and `kill` perform a signal's **default action** and nothing else: terminating signals end the program, ignored ones succeed, stopping ones report `ENOSYS`, and musl's own three (32, 33, 34) are refused, so `pthread_cancel` reports `ENOSYS` | there is no delivery, so there is no handler to reach; what remains of a signal is what it does when no handler exists. `abort` reaches `kal_abort`, which raises the signal on Linux and ends with a distinguished status elsewhere — a parent can tell an abnormal end from an ordinary one on every system. |
@@ -141,10 +143,30 @@ was given would not be confined by having been given it. `/a/b/../c` is
141143
therefore reduced to `/a/c` before openkal sees it, which is what the program
142144
means in every case except one that passes through a symbolic link.
143145

146+
**`O_NOFOLLOW` is answered by an enquiry, not by an opening.** openkal states
147+
that opening resolves and offers no form that declines to, deliberately: a
148+
program that opens a link in order to read its bytes is asking what
149+
`kal_fs_link_read` answers. So this port asks `kal_fs_info` with
150+
`KAL_FS_NO_RESOLVE` first and reports `ELOOP` when the name is a link, which is
151+
what POSIX says and what a caller passing the flag is distinguishing. Answering
152+
`ENOENT` instead — which is what resolving a link to an absent target produces —
153+
is a different answer to a different question, and libc++'s `remove_all` reads it
154+
as "the entry has already gone" and leaves the tree standing.
155+
144156
**The tables are bounded.** A program may hold 1024 descriptors and 512 open
145157
descriptions; beyond that it is told so. Allocating the tables instead would
146158
place them on the allocator, and the allocator obtains its memory through them.
147159

160+
⚠️ **And a program may have started 256 programs it has not waited for.** An
161+
entry is taken when a program is started and released when it is waited for,
162+
which is what a process table is; a program that never waits holds entries for
163+
ever, and the next start reports `EAGAIN` — which is what POSIX says `fork`
164+
does when the table is full. This bound is stated here because it was not, and
165+
a caller that met it saw a failure on an operation with no evident relation to
166+
the ones that caused it: measured, the sixty-fifth `posix_spawn` of a program
167+
that waited for none, and of one that polled each once with `WNOHANG` and did
168+
not come back.
169+
148170
## Asking which operation was missing
149171

150172
`ENOSYS` says that a facility is not here. It does not say which one, and until

examples/posix/src/main.c

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#include <stdio.h>
33
#include <stdlib.h>
44
#include <string.h>
5+
#include <signal.h>
56
#include <unistd.h>
67
#include <fcntl.h>
78
#include <errno.h>
@@ -164,6 +165,177 @@ int main(int argc, char **argv, char **envp) {
164165
failures += 2;
165166
}
166167

168+
/* ⭐⭐ THE DISPOSITION OF A SIGNAL IS TOUCHED, WHICH NOTHING HERE DID.
169+
*
170+
* This file had thirty-six observations and three of them were about
171+
* `abort'. It contained no call to `signal' or `sigaction' anywhere --- so
172+
* it examined whether `abort' ENDS the program and never whether a program
173+
* may ASK what a signal is set to. A defect that killed any program doing
174+
* the second passed every one of the thirty-six.
175+
*
176+
* ⚠️ ALL THREE FORMS, AND SIGABRT AMONG THEM. The C library takes a lock for
177+
* any change to that one disposition and blocks signals to take it, so
178+
* SIGABRT reaches code the others do not --- and the enquiry, which changes
179+
* nothing, reached it too. Two of the three forms below would have passed
180+
* while the third killed the process. */
181+
{
182+
int survived = 1;
183+
for (int sig = 1; sig < 32; sig++) {
184+
if (sig == SIGKILL || sig == SIGSTOP) continue;
185+
struct sigaction seen;
186+
memset(&seen, 0, sizeof seen);
187+
/* An enquiry, which changes nothing. */
188+
const int q = sigaction(sig, NULL, &seen);
189+
if (q != 0 && errno != ENOSYS) survived = 0;
190+
/* Ignoring, which every environment can express. */
191+
errno = 0;
192+
signal(sig, SIG_IGN);
193+
if (errno != 0 && errno != ENOSYS) survived = 0;
194+
/* A handler, which this environment cannot deliver and refuses. */
195+
errno = 0;
196+
if (signal(sig, SIG_DFL) == SIG_ERR && errno != ENOSYS) survived = 0;
197+
}
198+
check(survived, "every signal's disposition may be read and written or refused");
199+
}
200+
201+
/* Nodes whose content is another name, where the volume has them. */
202+
{
203+
/* The names this block uses are its own: the file the earlier
204+
* observations made has been removed by the time this runs, and a probe
205+
* that depended on another probe's leftovers would report an absence as
206+
* a defect. */
207+
{ FILE *t = fopen("okm-link-target.tmp", "w"); if (t) { fputs("0123456789", t); fclose(t); } }
208+
unlink("okm-probe-link");
209+
const int made = symlink("okm-link-target.tmp", "okm-probe-link");
210+
if (made == 0) {
211+
char target[64] = { 0 };
212+
const ssize_t got = readlink("okm-probe-link", target, sizeof target - 1);
213+
check(got == (ssize_t)strlen("okm-link-target.tmp")
214+
&& strcmp(target, "okm-link-target.tmp") == 0,
215+
"a node's content reads back as it was written");
216+
217+
/* ⭐ THE OBSERVATION THE PORT MOST NEEDED. Asking resolves and
218+
* opening resolves, so the two agree; asking with the flag reports
219+
* the node itself. They disagreed, and a C++ library above reported
220+
* a link where a caller would have reached a file. */
221+
struct stat followed, itself;
222+
check(stat("okm-probe-link", &followed) == 0 && S_ISREG(followed.st_mode),
223+
"stat resolves, and reports what the name finally refers to");
224+
check(lstat("okm-probe-link", &itself) == 0 && S_ISLNK(itself.st_mode),
225+
"lstat reports the node itself");
226+
227+
/* ⭐⭐ AND THE THIRD QUESTION, WHICH IS NEITHER OF THOSE TWO.
228+
*
229+
* O_NOFOLLOW does not ask to open the link and does not ask to
230+
* open its target: it asks `is this name a link?' and expects
231+
* ELOOP when it is. openkal offers no opening that declines to
232+
* resolve --- by design --- so this port resolved, and for a link
233+
* to a name that is absent it answered ENOENT.
234+
*
235+
* ⚠️ THAT IS A DIFFERENT ANSWER TO A DIFFERENT QUESTION, AND
236+
* NOTHING NEARBY LOOKED WRONG. Every operation above still held.
237+
* What failed was three layers up: libc++'s `remove_all' descends
238+
* by opening each entry O_DIRECTORY|O_NOFOLLOW and reads ENOENT as
239+
* `it is already gone', so it unlinked nothing and then reported
240+
* ENOTEMPTY for a directory it had just declined to empty. The
241+
* host toolchain removed the same tree.
242+
*
243+
* The answer comes from the enquiry openkal 0.9 added: ask about
244+
* the name itself. Both cases are checked because they fail
245+
* differently --- a live target resolved to a FILE and returned a
246+
* descriptor, which is not an error at all. */
247+
int nf = open("okm-probe-link", O_RDONLY | O_NOFOLLOW);
248+
check(nf < 0 && errno == ELOOP,
249+
"opening a link with O_NOFOLLOW reports that it is a link");
250+
if (nf >= 0) close(nf);
251+
252+
unlink("okm-probe-target-gone");
253+
unlink("okm-probe-dangling");
254+
if (symlink("okm-probe-target-gone", "okm-probe-dangling") == 0) {
255+
nf = open("okm-probe-dangling", O_RDONLY | O_NOFOLLOW);
256+
check(nf < 0 && errno == ELOOP,
257+
"and does so for a link whose target is absent, rather than ENOENT");
258+
if (nf >= 0) close(nf);
259+
check(unlink("okm-probe-dangling") == 0,
260+
"a link whose target is absent is still removable");
261+
}
262+
263+
unlink("okm-probe-link");
264+
} else if (errno == ENOSYS || errno == EPERM) {
265+
printf("ok: this volume has no nodes that name others, which it reported\n");
266+
} else {
267+
printf("FAIL: making a node that names another (errno %d)\n", errno);
268+
failures++;
269+
}
270+
unlink("okm-link-target.tmp");
271+
}
272+
273+
/* ⚠️ Two different files are two different files. `st_dev' and `st_ino'
274+
* were constants, so every file compared equal to every other and a C++
275+
* library's `equivalent' answered true with no error.
276+
*
277+
* ⚠️⚠️ WHEN THIS FAILS, THE DEFECT IS USUALLY NOT IN THIS PACKAGE. This
278+
* port copies the identity out of `kal_node_info' and puts zero there when
279+
* the implementation does not report one --- which is permitted, and which
280+
* makes every node compare equal to every other. So a failure here says
281+
* "the openkal implementation beneath this one declined to report an
282+
* identity", and the place to look is its `kal_fs_info'.
283+
*
284+
* Measured: it failed on Windows, and openkal-windows was reading a volume
285+
* serial number the object manager had written and then discarding it,
286+
* because the enquiry reported STATUS_BUFFER_OVERFLOW for a volume label
287+
* that did not fit and the implementation read that as a failure. The
288+
* conformance suite could not have said so: an implementation is allowed
289+
* to decline the field, so the suite reports the observation as one it did
290+
* not make. This is the criterion that notices, and it is two packages
291+
* away from the defect. */
292+
{
293+
struct stat x, y;
294+
FILE *fx = fopen("okm-probe-x.tmp", "w"); if (fx) fclose(fx);
295+
FILE *fy = fopen("okm-probe-y.tmp", "w"); if (fy) fclose(fy);
296+
check(stat("okm-probe-x.tmp", &x) == 0 && stat("okm-probe-y.tmp", &y) == 0
297+
&& !(x.st_dev == y.st_dev && x.st_ino == y.st_ino),
298+
"two different files have different identities");
299+
unlink("okm-probe-x.tmp");
300+
unlink("okm-probe-y.tmp");
301+
}
302+
303+
/* A value POSIX says cannot fail is not a negated error. */
304+
check(getpgrp() > 0, "the process group is a number and not a negated error");
305+
306+
/* The page is the machine's and not the build's.
307+
*
308+
* ⚠️⚠️ AND "POSITIVE POWER OF TWO" WAS TRUE OF THE VALUE THAT BROKE IT.
309+
* This library took `kal_memory_granularity()' as its page size, and an
310+
* implementation for a machine with no memory management unit answers ONE
311+
* --- correctly, since nothing there needs rounding. One is positive and
312+
* one is a power of two, so this assertion held while the allocator asked
313+
* the environment for one-byte extents and the program stopped inside the
314+
* first allocation that needed a new one.
315+
*
316+
* ⭐ SO THE CRITERION IS WHAT THE ALLOCATOR REQUIRES, NOT WHAT THE NUMBER
317+
* LOOKS LIKE. A page smaller than this library's own quantum is not a page
318+
* this library can use, whatever openkal reports. */
319+
{
320+
const long page = sysconf(_SC_PAGESIZE);
321+
check(page >= 4096 && (page & (page - 1)) == 0,
322+
"the page size is a power of two no smaller than the allocator's quantum");
323+
324+
/* And the property the number exists to have. Several pages, written
325+
* end to end: the allocation this library rounds to `page' and the
326+
* memory it hands back are the same memory. */
327+
const size_t span = (size_t)page * 4 + 17;
328+
unsigned char *big = malloc(span);
329+
int whole = big != NULL;
330+
if (big) {
331+
for (size_t i = 0; i < span; i++) big[i] = (unsigned char)(i * 31u);
332+
for (size_t i = 0; i < span; i++)
333+
if (big[i] != (unsigned char)(i * 31u)) { whole = 0; break; }
334+
free(big);
335+
}
336+
check(whole, "several pages are obtained in one allocation and every byte of it holds");
337+
}
338+
167339
printf("-- failures: %d --\n", failures);
168340
return failures ? 1 : 0;
169341
}

0 commit comments

Comments
 (0)