Skip to content

Commit 7aabd33

Browse files
committed
0.4.0: the argument vector is passed unaltered, and the suite observes it
The implementation prepended the path to the argument vector, so a started program received the caller's argv[0] at index 1 and could not be given the name it observes as its own. Clause 7.6 of openkal 0.4 now states that the vector is complete and is passed unaltered, and this is that change. The suite did not detect it, and the reason is worth recording. It started /bin/true and read the status, and /bin/true ignores its arguments — so the same result was produced whether the vector arrived intact or shifted by one. A test that starts a program without observing what the program received tests the starting and not the vector. The assertion added here starts a shell instead. `sh -c <script>` takes $0 from its own argv[0] when no further argument is given, so the script observes the name the caller chose; an implementation that prepended the path would give the shell an extra argument, which it would read as a script file to open. The assertion was confirmed to fail against the previous behaviour before the behaviour was changed. Three defects in continuous integration are also fixed, and they share a shape. The specification version was stated twice, in the manifest and again in the workflow, and the two drifted: the manifest moved to 0.3.0 while the workflow compared against the 0.2.0 surface, so four names this implementation is required to export were reported as unspecified. It is now read from the manifest, which is the one place it is declared. The assertion that the suites ran named two of the five present. Its purpose is to catch a suite that discovers nothing and reports success, and a hand-written list defeats that purpose the moment a suite is added. The list is now derived from the files present. The step that ran the example asserted a line the example does not print, in support of an operation that no longer exists. It had never run: the step before it failed first, and a step that never runs asserts nothing while appearing to. It is replaced by the portable program, taken from the specification at the version the manifest names rather than copied here. The surface comparison is now --complete: this implementation claims every interface, so a name it fails to export is a failure rather than an interface it declines to provide. It exports all 47.
1 parent 018ef5e commit 7aabd33

4 files changed

Lines changed: 106 additions & 22 deletions

File tree

‎.github/workflows/ci.yml‎

Lines changed: 67 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ jobs:
1616
# package uses modules, exported extern "C" declarations and ordinary
1717
# dependencies, none of which is recent; the pin exists for reproducibility
1818
# rather than because an older mcpp is known to fail.
19-
MCPP_VERSION: 2026.8.19.3
19+
MCPP_VERSION: 2026.8.19.4
2020
XLINGS_VERSION: v2026.8.17.2
2121
XLINGS_NON_INTERACTIVE: '1'
2222
steps:
@@ -35,30 +35,78 @@ jobs:
3535
mcpp --version
3636
mcpp self config --mirror GLOBAL
3737
38+
# The version of the specification this implementation is written against
39+
# is stated once, in the manifest, and read from there. Stating it a second
40+
# time in this file is what allowed the two to drift: the manifest moved to
41+
# 0.3.0 and this file compared against the 0.2.0 surface, so four names
42+
# this implementation is required to export were reported as unspecified.
43+
- name: Read the specification version from the manifest
44+
run: |
45+
v="$(sed -n 's/^openkal[[:space:]]*=[[:space:]]*"\([^"]*\)".*/\1/p' mcpp.toml | head -1)"
46+
test -n "$v" || { echo "the manifest does not name openkal" >&2; exit 1; }
47+
echo "OPENKAL_VERSION=$v" >> "$GITHUB_ENV"
48+
echo "written against openkal $v"
49+
3850
- name: Conformance
3951
run: |
4052
mcpp test 2>&1 | tee conformance.log
41-
# A suite that discovered nothing reports success. Both cases are
42-
# asserted to have run.
43-
grep -q 'conformance_stream ... ok' conformance.log
44-
grep -q 'conformance_memory ... ok' conformance.log
53+
54+
# A suite that discovered nothing reports success, so every suite is
55+
# asserted to have run. The list is derived from the files present
56+
# rather than written out here: a hand-written list names the suites
57+
# that existed when it was written, and a suite added afterwards
58+
# escapes the assertion silently. This assertion named two of five.
59+
missing=0
60+
for f in tests/*.cpp; do
61+
name="$(basename "$f" .cpp)"
62+
if ! grep -q "^$name \.\.\. ok" conformance.log; then
63+
echo "suite did not run, or did not pass: $name" >&2
64+
missing=1
65+
fi
66+
done
67+
test "$missing" -eq 0
68+
69+
- name: The specification, at the version the manifest names
70+
uses: actions/checkout@v4
71+
with:
72+
repository: mcpplibs/openkal
73+
ref: ${{ env.OPENKAL_VERSION }}
74+
path: .spec
4575

4676
- name: The exported surface matches the specification
47-
env:
48-
OPENKAL_VERSION: 0.2.0
4977
run: |
50-
# Clause 9.3. The list is fetched from the specification repository at
51-
# the version this implementation is written against, so that the
52-
# comparison has one source rather than a copy that can drift.
53-
base="https://raw.githubusercontent.com/mcpplibs/openkal/$OPENKAL_VERSION"
54-
curl -fsSL --retry 3 --retry-all-errors -o SURFACE.txt "$base/SURFACE.txt"
55-
curl -fsSL --retry 3 --retry-all-errors -o check-surface.sh "$base/tools/check-surface.sh"
56-
bash check-surface.sh SURFACE.txt $(find target -name '*.o' | tr '\n' ' ')
78+
# Clause 9.3. The list and the checker come from the specification
79+
# itself rather than from a copy kept here, so that the comparison has
80+
# one source. --complete because this implementation claims every
81+
# interface: a name it fails to export is a failure, not an interface
82+
# it declines to provide.
83+
bash .spec/tools/check-surface.sh --complete .spec/SURFACE.txt \
84+
$(find target -name '*.o' | tr '\n' ' ')
5785
58-
- name: The example runs
59-
working-directory: examples/hello
86+
# The program is taken from the specification rather than copied here. A
87+
# copy in each implementation is a copy that can diverge, and the value of
88+
# the program is precisely that every implementation runs the same one.
89+
#
90+
# The manifest is written here because naming the implementation is the
91+
# manifest's job and not the program's, which is the arrangement the
92+
# program exists to demonstrate.
93+
- name: The portable program runs
6094
run: |
95+
sed -i 's|^openkal = ".*"$|openkal = { path = ".spec" }|' mcpp.toml
96+
cat > .spec/examples/portable/mcpp.toml <<'TOML'
97+
[package]
98+
name = "portable"
99+
version = "0.1.0"
100+
101+
[dependencies]
102+
openkal = { path = "../.." }
103+
openkal-linux = { path = "../../.." }
104+
TOML
105+
cd .spec/examples/portable
61106
mcpp run 2>&1 | tee run.log
62-
grep -q 'openkal: hello' run.log
63-
grep -q 'openkal: allocation succeeded' run.log
64-
grep -q 'openkal: vectored writes unavailable' run.log
107+
# Both directions: that the program reported, and that nothing it
108+
# observed failed to hold. Asserting only the first would pass for a
109+
# program that printed its failures.
110+
grep -q 'openkal: the portable program, above eight interfaces' run.log
111+
grep -q 'openkal: observations that did not hold: 0' run.log
112+
! grep -q 'NOT HELD' run.log

‎mcpp.toml‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
[package]
22
namespace = "mcpplibs"
33
name = "openkal-linux"
4-
version = "0.3.0"
4+
version = "0.4.0"
55
description = "The reference implementation of openkal for Linux. It is both a usable backend and the worked example other implementations follow."
66
license = "Apache-2.0"
77
authors = ["mcpplibs"]
88
repo = "https://github.com/mcpplibs/openkal-linux"
99

1010
[dependencies]
11-
openkal = "0.3.0"
11+
openkal = "0.4.0"
1212

1313
# The package contributes definitions and no modules. The interface it
1414
# implements is declared by the specification package, which this package

‎src/process.cpp‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,10 @@ int kal_process_spawn(kal_dir base,
6868
if (!p.ok) return kal_err_invalid;
6969

7070
vector args, envs;
71-
args.add(p.buf, path_len); // the started program's own name
71+
// The vector is passed unaltered. Clause 7.6: argv[0] is the name the
72+
// started program observes as its own, and it is the caller's to choose —
73+
// the started program reads it through kal_env_arg(0), so a caller that did
74+
// not supply it could not predict what the program would read.
7275
for (kal_uintptr i = 0; i < argc; ++i) args.add(argv[i], argv_lens[i]);
7376
for (kal_uintptr i = 0; i < envc; ++i) envs.add(envp[i], envp_lens[i]);
7477
if (!args.ok || !envs.ok) return kal_err_no_memory;

‎tests/conformance_process_task.cpp‎

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,39 @@ int main() {
8585
check(status != 0, "a non-zero status is reported as such");
8686
kal_process_close(q);
8787
}
88+
89+
// Clause 7.6: the vector is passed unaltered, and argv[0] is the name
90+
// the started program observes as its own.
91+
//
92+
// The programs started above ignore their arguments, so they cannot
93+
// distinguish an implementation that passes the vector from one that
94+
// prepends the path — which is how that defect survived a suite that
95+
// started programs and read their statuses. A shell does distinguish
96+
// them: `sh -c <script>` takes $0 from its own argv[0] when no further
97+
// argument is given, so the script observes the name the caller chose.
98+
// An implementation that prepended the path would give the shell an
99+
// extra argument, which it would read as a script file to open, and the
100+
// status would be non-zero.
101+
const char* sh_paths[] = { "bin/sh", "usr/bin/sh" };
102+
const kal_uintptr sh_lens[] = { 6, 10 };
103+
const char* script = "test \"$0\" = openkal-observed-argv0";
104+
kal_uintptr script_len = 0; while (script[script_len]) ++script_len;
105+
106+
kal_process r{};
107+
const char* rargv[] = { "openkal-observed-argv0", "-c", script };
108+
const kal_uintptr rlens[] = { 22, 2, script_len };
109+
int rrc = kal_err_invalid;
110+
for (int i = 0; i < 2 && rrc != kal_ok; ++i)
111+
rrc = kal_process_spawn(slash, sh_paths[i], sh_lens[i], rargv, rlens, 3,
112+
nullptr, nullptr, 0, nullptr, &r);
113+
check(rrc == kal_ok, "a shell is started");
114+
if (rrc == kal_ok) {
115+
int status = -1, terminated = -1;
116+
kal_process_wait(r, &status, &terminated);
117+
check(status == 0 && terminated == 0,
118+
"the started program observes the argument vector the caller supplied, unaltered");
119+
kal_process_close(r);
120+
}
88121
}
89122

90123
// A name that ascends is refused here as it is in the file system.

0 commit comments

Comments
 (0)