diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bd9a26a..bf7dbc2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -64,7 +64,7 @@ jobs: # that exists on one machine: a consumer resolving from the index is handed # a manifest pointing at a directory that exists nowhere. # - # ⚠️ THAT HAS HAPPENED IN THIS ECOSYSTEM, in openkal-musl, and the working + # THAT HAS HAPPENED IN THIS ECOSYSTEM, in openkal-musl, and the working # tree here has carried the same rewrite more than once since. This step # runs first, so what it examines is what the commit contains. - name: The committed manifest names no local directory @@ -100,7 +100,7 @@ jobs: - name: Install mcpp run: | xlings update - # ⚠️ THE PIN MAY NAME THE RELEASE THIS RUN IS VALIDATING, which does + # THE PIN MAY NAME THE RELEASE THIS RUN IS VALIDATING, which does # not exist yet — that is the whole point of MCPP_SOURCE_REF. Bootstrap # from whatever the index has; the step below replaces it with the # build under review, and the pin is what an ordinary run tests. @@ -111,13 +111,13 @@ jobs: fi mcpp --version mcpp self config --mirror GLOBAL - # ⭐⭐ CROSS-VALIDATION: BUILD THE mcpp UNDER REVIEW AND USE THAT ONE. + # CROSS-VALIDATION: BUILD THE mcpp UNDER REVIEW AND USE THAT ONE. # # Empty in the ordinary run, so this job keeps testing the RELEASED # mcpp the pin above names. Set it — `workflow_dispatch` input, or the # repository variable — and the same job runs against that source. # - # ⚠️ THIS EXISTS BECAUSE THE ORDER USED TO BE WRONG. Several mcpp + # THIS EXISTS BECAUSE THE ORDER USED TO BE WRONG. Several mcpp # releases went out green and only then turned this ecosystem red: the # engine's own CI cannot see a defect that appears only in a real # dependency graph, and this repository could not see the engine until @@ -130,7 +130,7 @@ jobs: [ -d "$src" ] || git clone --quiet --depth 1 \ --branch "$MCPP_SOURCE_REF" \ https://github.com/mcpp-community/mcpp.git "$src" - # ⚠️ THE CLONE'S OWN WORKSPACE PIN MUST NOT DECIDE WHICH mcpp + # THE CLONE'S OWN WORKSPACE PIN MUST NOT DECIDE WHICH mcpp # BUILDS IT. `.xlings.json` at mcpp's root pins the mcpp that # compiles mcpp, and that pin does not move when mcpp is released — # so a build inside the checkout obeys it and tries to install a @@ -143,7 +143,7 @@ jobs: # above, which is exactly what removing the file leaves. rm -f "$src/.xlings.json" ( cd "$src" && mcpp build --release ) - # ⚠️ BOTH SPELLINGS, AND NO `-perm`. The matrix reaches Windows and + # BOTH SPELLINGS, AND NO `-perm`. The matrix reaches Windows and # macOS runners too: on Windows the artefact is `mcpp.exe`, and # `-perm -u+x` is not a question that filesystem answers the way this # expects. Measured: `Finished release [optimized] in 173.44s` @@ -157,7 +157,7 @@ jobs: \( -name mcpp -o -name mcpp.exe \) | head -1) [ -n "$built" ] || { echo "::error::mcpp did not build from $MCPP_SOURCE_REF"; exit 1; } echo "$(cd "$(dirname "$built")" && pwd)" >> "$GITHUB_PATH" - # ⚠️ Reported, because a PATH entry that does not win looks exactly + # Reported, because a PATH entry that does not win looks exactly # like one that does until something built with the wrong engine. echo "under review: $("$built" --version) (from $MCPP_SOURCE_REF)" fi @@ -178,7 +178,7 @@ jobs: - name: Every interface, every kind of examination run: | - # ⚠️⚠️ `optional` IS PART OF THE SET AND WAS NOT, WHICH MADE THE STEP'S + # `optional` IS PART OF THE SET AND WAS NOT, WHICH MADE THE STEP'S # OWN NAME UNTRUE. # # `full` expands to `standard,abi,stability,cost`, and `standard` is @@ -229,7 +229,7 @@ jobs: # fails to compile and the diagnostic names it. list="$(grep -vE '^[[:space:]]*(#|$)' .spec/SURFACE.txt | sort -u)" { - # ⚠️ THE MODULE LIST IS DERIVED, NOT WRITTEN OUT. + # THE MODULE LIST IS DERIVED, NOT WRITTEN OUT. # # It used to be written out, and it fell out of step the first time # the specification gained an interface: SURFACE.txt listed four @@ -272,7 +272,7 @@ jobs: # by construction. permitted='^(memcpy|memmove|memset|memcmp|__libc_start_main|main|_GLOBAL_OFFSET_TABLE_|kal_[a-z_]+|__init_array_start|__init_array_end|__preinit_array_start|__preinit_array_end|_ZN3okl.*)$' - # ⭐⭐ ONE NAME IS PERMITTED ONLY IF IT IS WEAK, AND THE WEAKNESS IS + # ONE NAME IS PERMITTED ONLY IF IT IS WEAK, AND THE WEAKNESS IS # THE WHOLE OF THE PERMISSION. # # `environ' is how src/env.cpp recovers the vectors the kernel placed diff --git a/.gitignore b/.gitignore index f70e796..8cc910a 100644 --- a/.gitignore +++ b/.gitignore @@ -20,7 +20,7 @@ Thumbs.db # The specification tree tools/run-conformance.sh clones beside the sources. # -# ⚠️ NO TRAILING SLASH. `.spec/' matches a directory and does not match a +# NO TRAILING SLASH. `.spec/' matches a directory and does not match a # SYMBOLIC LINK to one, which is what a working checkout naturally has; the link # was consequently committed once, pointing at a path that exists on one # machine. diff --git a/README.md b/README.md index db1f227..87199a6 100644 --- a/README.md +++ b/README.md @@ -5,10 +5,10 @@ for Linux, written on the kernel's own system-call interface. ```toml [dependencies] -openkal = "0.9.0" +openkal = "0.13.0" [target.'cfg(os = "linux")'.dependencies] -openkal-linux = "0.9.0" +openkal-linux = "0.13.0" ``` ## Why it does not use a C library diff --git a/mcpp.toml b/mcpp.toml index e3c8db6..b2c1c20 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -1,7 +1,7 @@ [package] namespace = "mcpplibs" name = "openkal-linux" -version = "0.12.0" +version = "0.13.0" description = "The reference implementation of openkal for Linux, written on the kernel's own system-call interface so that it can be placed beneath a C library as well as above one." license = "Apache-2.0" @@ -18,7 +18,7 @@ authors = ["mcpplibs"] repo = "https://github.com/mcpplibs/openkal-linux" [dependencies] -openkal = "0.12.0" +openkal = "0.13.0" # The package contributes definitions and no modules. The interface it # implements is declared by the specification package, which this package diff --git a/src/env.cpp b/src/env.cpp index 3480733..90dd6ed 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -35,14 +35,14 @@ okl_ulong auxval(okl_ulong key) { namespace { -// ⚠️⚠️ ONE C LIBRARY PASSES THESE AND ANOTHER DOES NOT, AND THE ONE THAT DOES +// 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. +// 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: // @@ -65,7 +65,7 @@ bool plausible(int argc, char* const* argv, char* const* envp) { return true; } -// ⚠️ WEAK, AND DATA RATHER THAN A CALL. The independence check in this package +// 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 @@ -100,7 +100,7 @@ bool recover(char*** argv_out, int* argc_out, char*** envp_out) { return false; } -// ⚠️⚠️ A PROGRAM ABOVE openkal SHALL NOT BE ENDED BY SOMETHING openkal NEVER +// 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 @@ -110,7 +110,7 @@ bool recover(char*** argv_out, int* argc_out, char*** envp_out) { // 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 +// 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 @@ -121,7 +121,7 @@ bool recover(char*** argv_out, int* argc_out, char*** envp_out) { // 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 +// 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. [[gnu::constructor(101)]] void quiet_the_signal_openkal_cannot_report() { diff --git a/src/exec.cpp b/src/exec.cpp index 4fdec45..eb2f8fa 100644 --- a/src/exec.cpp +++ b/src/exec.cpp @@ -44,7 +44,7 @@ int kal_exec_publish(void* p, kal_uintptr size) { okl::prot_read | okl::prot_exec); if (okl::failed(r)) return okl::translate(r); - // ⚠️⚠️ THE INSTRUCTION CACHE IS NOT INVALIDATED HERE, AND ONE OF THE THREE + // THE INSTRUCTION CACHE IS NOT INVALIDATED HERE, AND ONE OF THE THREE // IMPLEMENTATIONS DOES INVALIDATE IT. THE ASYMMETRY HAS A RULE. // // A processor with separate caches for data and instructions has just had @@ -52,13 +52,13 @@ int kal_exec_publish(void* p, kal_uintptr size) { // instruction path, and nothing in the protection call makes the second path // observe the first's writes. // - // ⭐ THE SPECIFICATION PLACES THE MAINTENANCE UPON THE PROGRAM, and the + // THE SPECIFICATION PLACES THE MAINTENANCE UPON THE PROGRAM, and the // conformance suite performs it itself and says why: the program is the // party that knows which bytes it wrote. So an implementation that performs // it is being helpful rather than conforming, and one that does not is not // deficient. // - // ⚠️ AND THE ONLY MEANS AVAILABLE HERE IS ONE THIS IMPLEMENTATION MAY NOT + // AND THE ONLY MEANS AVAILABLE HERE IS ONE THIS IMPLEMENTATION MAY NOT // USE. `__builtin___clear_cache' expands to nothing on x86_64 and becomes a // CALL into the compiler's support library on the other two architectures // --- `__riscv_flush_icache' on riscv64. This implementation is linked into @@ -66,7 +66,7 @@ int kal_exec_publish(void* p, kal_uintptr size) { // perform an operation the specification does not require of it is not a // trade worth making. // - // ⭐⭐ MEASURED, AND NOT ON THIS SYSTEM. openkal-macos added the builtin on + // MEASURED, AND NOT ON THIS SYSTEM. openkal-macos added the builtin on // the reading that aarch64 would expand it inline, and its own independence // check reported within the hour: // diff --git a/src/fs.cpp b/src/fs.cpp index 940c38e..725084c 100644 --- a/src/fs.cpp +++ b/src/fs.cpp @@ -58,6 +58,62 @@ int kind_of(okl_u32 mode) { } } +// The properties of the volume a descriptor is on. kal_fs_props answers with +// this, and so does the one field of an enquiry that is a volume's property. +kal_uintptr volume_props(int fd) { + // LOCKS AND CAPACITY ARE IN THE CONSERVATIVE SET, and that is a claim + // about this kernel rather than about the volume: an open-file lock and + // `fstatfs' are answered by the VFS for every format beneath it, including + // the read-only ones --- a lock excludes writers a read-only volume does not + // have, which is a true answer and not a useful one. A format that could not + // would have to be excluded by name here, and this kernel has none. + const kal_uintptr conservative = + KAL_FS_PROP_MODIFIED_TIME | KAL_FS_PROP_ATOMIC_RENAME + | KAL_FS_PROP_LOCKS | KAL_FS_PROP_CAPACITY; + if (fd < 0) return 0; + + okl::kstatfs sf{}; + const okl_long r = okl::sys(okl::nr_fstatfs, fd, reinterpret_cast(&sf)); + if (okl::failed(r)) return conservative; + + switch (sf.f_type) { + // Formats with a case-sensitive namespace and nodes that name others. + case okl::fs_ext234: case okl::fs_btrfs: case okl::fs_xfs: + case okl::fs_f2fs: case okl::fs_tmpfs: case okl::fs_overlay: + case okl::fs_zfs: case okl::fs_bcachefs: + return conservative | KAL_FS_PROP_CASE_SENSITIVE + | KAL_FS_PROP_LINKS | KAL_FS_PROP_MAKE_LINKS + | KAL_FS_PROP_EXECUTABLE; + + // Read-only formats: the nodes are there and none can be made, and a + // rename cannot be atomic because there is no rename. + case okl::fs_squashfs: case okl::fs_erofs: + return KAL_FS_PROP_MODIFIED_TIME | KAL_FS_PROP_CASE_SENSITIVE + | KAL_FS_PROP_LINKS | KAL_FS_PROP_LOCKS | KAL_FS_PROP_CAPACITY + | KAL_FS_PROP_EXECUTABLE; + case okl::fs_iso9660: + return KAL_FS_PROP_MODIFIED_TIME | KAL_FS_PROP_CASE_SENSITIVE + | KAL_FS_PROP_LOCKS | KAL_FS_PROP_CAPACITY; + + // The FAT family stores neither a case distinction nor a node that + // names another. `symlink' on such a volume reports EPERM, and this is + // where a caller learns that before it tries. + case okl::fs_msdos: case okl::fs_exfat: + return conservative; + + // A case-insensitive namespace, with nodes that name others. + case okl::fs_ntfs: case okl::fs_ntfs3: + return conservative | KAL_FS_PROP_LINKS | KAL_FS_PROP_MAKE_LINKS; + // The same, on a format that stores a mode. + case okl::fs_hfsplus: + return conservative | KAL_FS_PROP_LINKS | KAL_FS_PROP_MAKE_LINKS + | KAL_FS_PROP_EXECUTABLE; + + default: + return conservative; + } +} + // Writes no more of the structure than the caller says exists on its side, and // reports which fields it filled. // @@ -66,12 +122,17 @@ int kind_of(okl_u32 mode) { // this kernel, so selecting would cost a branch and save nothing. An // implementation whose environment answers them separately is the one `wanted' // exists for. -void fill_info(const okl::kstat& st, kal_u32 wanted, kal_node_info* out) { - (void)wanted; +// +// The exception is KAL_INFO_EXECUTABLE, which is a property of the volume as +// well as of the node: a format that stores no mode reports execute bits the +// mount chose, and reporting those would be answering for the volume. The +// volume is asked only when the caller wants the field, and only for a file. +void fill_info(const okl::kstat& st, kal_u32 wanted, kal_node_info* out, + int volume_fd) { const kal_u32 self = out->self_size; kal_node_info v{}; v.self_size = self; - v.present = KAL_INFO_ALL; + v.present = KAL_INFO_ALL & ~KAL_INFO_EXECUTABLE; v.size = static_cast(st.size); v.modified_ns = static_cast(st.mtime_sec) * 1000000000u + static_cast(st.mtime_nsec); @@ -83,6 +144,16 @@ void fill_info(const okl::kstat& st, kal_u32 wanted, kal_node_info* out) { v.kind = kind_of(st.mode); v.writable = (st.mode & 0200u) != 0 ? 1 : 0; + // A caller whose structure ends before the field is not given the position. + const kal_u32 reaches = static_cast( + __builtin_offsetof(kal_node_info, executable) + sizeof v.executable); + if ((wanted & KAL_INFO_EXECUTABLE) != 0 && self >= reaches + && v.kind == kal_node_file + && (volume_props(volume_fd) & KAL_FS_PROP_EXECUTABLE) != 0) { + v.present |= KAL_INFO_EXECUTABLE; + v.executable = (st.mode & 0111u) != 0 ? 1 : 0; + } + const kal_u32 n = self < sizeof v ? self : (kal_u32)sizeof v; okl::copy(reinterpret_cast(out), reinterpret_cast(&v), n); } @@ -242,7 +313,7 @@ int kal_fs_info(kal_dir base, const char* name, kal_uintptr len, } return okl::translate(r); } - fill_info(st, wanted, out); + fill_info(st, wanted, out, b); return kal_ok; } @@ -252,7 +323,7 @@ int kal_fs_file_info(kal_file f, kal_u32 wanted, kal_node_info* out) { okl::kstat st{}; const okl_long r = okl::sys(okl::nr_fstat, fd, reinterpret_cast(&st)); if (okl::failed(r)) return okl::translate(r); - fill_info(st, wanted, out); + fill_info(st, wanted, out, fd); return kal_ok; } @@ -277,7 +348,7 @@ int kal_fs_set_modified(kal_file f, kal_u64 modified_ns) { // The same, upon a NAME. Version 0.10. // -// ⚠️⚠️ ADDED BECAUSE THE FORM ABOVE CANNOT REACH A DIRECTORY, AND A CONSUMER +// ADDED BECAUSE THE FORM ABOVE CANNOT REACH A DIRECTORY, AND A CONSUMER // PAID FOR THAT. `kal_fs_set_modified' takes a `kal_file'; a directory is opened // as a `kal_dir'; there was no third thing. openkal-musl reached a lock // directory's timestamp by opening the directory for READING and setting the @@ -302,6 +373,35 @@ int kal_fs_set_modified_at(kal_dir base, const char* name, kal_uintptr len, return okl::failed(r) ? okl::translate(r) : kal_ok; } +// Whether a node may be started, version 0.13. This kernel stores the property +// as three bits, one per class of caller; the interface states one property, +// so a class that may read the node may start it, and clearing clears all +// three. Nothing else about the mode changes. +int kal_fs_set_executable_at(kal_dir base, const char* name, kal_uintptr len, + int executable) { + const int b = okl::unpack(base.h); + if (b < 0 || !okl::acceptable(name, len)) return kal_err_invalid; + okl::terminated t(name, len); if (!t.ok) return kal_err_invalid; + if ((volume_props(b) & KAL_FS_PROP_EXECUTABLE) == 0) return kal_err_not_supported; + + okl::kstat st{}; + okl_long r = okl::sys(okl::nr_newfstatat, b, reinterpret_cast(t.buf), + reinterpret_cast(&st), 0); + if (okl::failed(r)) return okl::translate(r); + const int k = kind_of(st.mode); + if (k == kal_node_directory) return kal_err_is_directory; + if (k != kal_node_file) return kal_err_invalid; + + const unsigned mode = static_cast(st.mode) & 07777u; + const unsigned next = executable != 0 ? (mode | ((mode & 0444u) >> 2)) + : (mode & ~0111u); + if (next == mode) return kal_ok; + // Resolves, as the enquiry above did, so the node changed is the node read. + r = okl::sys(okl::nr_fchmodat, b, reinterpret_cast(t.buf), + static_cast(next)); + return okl::failed(r) ? okl::translate(r) : kal_ok; +} + int kal_fs_mkdir(kal_dir base, const char* name, kal_uintptr len) { const int b = okl::unpack(base.h); if (b < 0 || !okl::acceptable(name, len)) return kal_err_invalid; @@ -405,57 +505,12 @@ int kal_fs_list_next(kal_dir, kal_uintptr* iter, // Case sensitivity and links are claimed only where the format is known to have // them. kal_uintptr kal_fs_props(kal_dir d) { - const int fd = okl::unpack(d.h); - // ⭐ LOCKS AND CAPACITY ARE IN THE CONSERVATIVE SET, and that is a claim - // about this kernel rather than about the volume: an open-file lock and - // `fstatfs' are answered by the VFS for every format beneath it, including - // the read-only ones --- a lock excludes writers a read-only volume does not - // have, which is a true answer and not a useful one. A format that could not - // would have to be excluded by name here, and this kernel has none. - const kal_uintptr conservative = - KAL_FS_PROP_MODIFIED_TIME | KAL_FS_PROP_ATOMIC_RENAME - | KAL_FS_PROP_LOCKS | KAL_FS_PROP_CAPACITY; - if (fd < 0) return 0; - - okl::kstatfs sf{}; - const okl_long r = okl::sys(okl::nr_fstatfs, fd, reinterpret_cast(&sf)); - if (okl::failed(r)) return conservative; - - switch (sf.f_type) { - // Formats with a case-sensitive namespace and nodes that name others. - case okl::fs_ext234: case okl::fs_btrfs: case okl::fs_xfs: - case okl::fs_f2fs: case okl::fs_tmpfs: case okl::fs_overlay: - case okl::fs_zfs: case okl::fs_bcachefs: - return conservative | KAL_FS_PROP_CASE_SENSITIVE - | KAL_FS_PROP_LINKS | KAL_FS_PROP_MAKE_LINKS; - - // Read-only formats: the nodes are there and none can be made, and a - // rename cannot be atomic because there is no rename. - case okl::fs_squashfs: case okl::fs_erofs: - return KAL_FS_PROP_MODIFIED_TIME | KAL_FS_PROP_CASE_SENSITIVE - | KAL_FS_PROP_LINKS | KAL_FS_PROP_LOCKS | KAL_FS_PROP_CAPACITY; - case okl::fs_iso9660: - return KAL_FS_PROP_MODIFIED_TIME | KAL_FS_PROP_CASE_SENSITIVE - | KAL_FS_PROP_LOCKS | KAL_FS_PROP_CAPACITY; - - // The FAT family stores neither a case distinction nor a node that - // names another. `symlink' on such a volume reports EPERM, and this is - // where a caller learns that before it tries. - case okl::fs_msdos: case okl::fs_exfat: - return conservative; - - // A case-insensitive namespace, with nodes that name others. - case okl::fs_ntfs: case okl::fs_ntfs3: case okl::fs_hfsplus: - return conservative | KAL_FS_PROP_LINKS | KAL_FS_PROP_MAKE_LINKS; - - default: - return conservative; - } + return volume_props(okl::unpack(d.h)); } // --- exclusion upon a range of a file -------------------------------------- // -// ⭐⭐ THE OPEN-FILE FORM, AND THE DIFFERENCE IS THE WHOLE REASON THIS IS +// THE OPEN-FILE FORM, AND THE DIFFERENCE IS THE WHOLE REASON THIS IS // WORTH SPECIFYING. // // This kernel's oldest record lock is held by the PROCESS and is released as @@ -466,7 +521,7 @@ kal_uintptr kal_fs_props(kal_dir d) { // open file description and ends when the last descriptor for it closes, and // when the program ends however it ends. // -// ⚠️ Releasing on death is the half a caller cannot build for itself. Exclusion +// Releasing on death is the half a caller cannot build for itself. Exclusion // it can: `KAL_OPEN_EXCLUSIVE' and a name beside the file. What nothing above // this line can do is release that name when its holder dies, so a program that // ended abnormally while holding one would be locked out of its own file for @@ -491,7 +546,7 @@ static int lock_range(kal_file f, kal_u64 start, kal_u64 len, } while (okl::interrupted(r)); if (!okl::failed(r)) return kal_ok; - // ⚠️⚠️ TWO VALUES MEAN ONE THING HERE, AND openkal NAMES ONE OF THEM. + // TWO VALUES MEAN ONE THING HERE, AND openkal NAMES ONE OF THEM. // // The standard this call comes from says a range another holder has is // reported as EITHER of two values, and leaves the choice to the system --- @@ -500,7 +555,7 @@ static int lock_range(kal_file f, kal_u64 start, kal_u64 len, // caller polls upon; the other value translates to `permission', which a // caller reads as "asking again will not help" and acts upon by stopping. // - // ⭐ Narrowed to the attempt that does NOT wait, because that is the only + // Narrowed to the attempt that does NOT wait, because that is the only // one for which the two values carry this meaning. A permission failure on // any other path keeps its own answer. if (!wait && (-r == okl::e_acces || -r == okl::e_again)) return kal_err_again; @@ -536,7 +591,7 @@ int kal_fs_capacity(kal_dir d, kal_u64* total, kal_u64* available) { // In bytes, because that is what the interface says and what a caller of it // wants; this kernel reports blocks and the size of one. const kal_u64 unit = static_cast(sf.f_bsize); - // ⚠️ `f_bavail' AND NOT `f_bfree'. The second counts blocks the volume has, + // `f_bavail' AND NOT `f_bfree'. The second counts blocks the volume has, // including those only a privileged writer may reach; the first counts the // ones THIS program could actually use, which is the question asked. if (total) *total = static_cast(sf.f_blocks) * unit; diff --git a/src/process.cpp b/src/process.cpp index 33434e8..ab2e174 100644 --- a/src/process.cpp +++ b/src/process.cpp @@ -48,7 +48,7 @@ struct vector { // --- reporting a replacement that failed ----------------------------------- // -// ⚠️⚠️ THE REPLACEMENT HAPPENS IN THE DUPLICATE, SO ITS FAILURE WAS REPORTED TO +// THE REPLACEMENT HAPPENS IN THE DUPLICATE, SO ITS FAILURE WAS REPORTED TO // NOBODY. // // A program is started here by duplicating this image and replacing the @@ -59,7 +59,7 @@ struct vector { // something was wrong only by waiting and reading 127, which is exactly what a // program that RAN and exited 127 reports. // -// ⭐ WHAT THAT COST, MEASURED BY A CONSUMER RATHER THAN HERE. openkal-musl +// WHAT THAT COST, MEASURED BY A CONSUMER RATHER THAN HERE. openkal-musl // expresses `execve' as starting a program and ending with its status, so a // name that could not be started ended the CALLING program with 127 instead of // returning -1. musl's `execvp' issues one `execve' per PATH entry and needs @@ -80,7 +80,7 @@ struct exec_report { int fd[2] = { -1, -1 }; bool armed = false; - // ⚠️ THE PIPE MUST NOT SIT WHERE THE DUPLICATE IS ABOUT TO PLACE SOMETHING. + // THE PIPE MUST NOT SIT WHERE THE DUPLICATE IS ABOUT TO PLACE SOMETHING. // The duplicate places streams at 0, 1 and 2 and granted directories at 3 // and upwards, so a pipe that happened to hold one of those numbers would be // closed by the very placement whose failure it exists to report --- and the @@ -159,7 +159,7 @@ inline void reap(okl_long child) { extern "C" { -// Starting a program. ⭐ ONE FUNCTION SINCE 0.11, AND THE SAVING IS NOT ONLY IN +// Starting a program. ONE FUNCTION SINCE 0.11, AND THE SAVING IS NOT ONLY IN // THE HEADER: this file used to hold THREE bodies of sixty lines that differed // by four. Every fix to the shared part --- and there have been several, the // exec-report pipe among them --- had to be made three times or be made once and @@ -183,7 +183,7 @@ int kal_process_spawn(const kal_spawn* how, if (!okl::acceptable(path, path_len)) return kal_err_invalid; if (how->grant_count > 0 && how->grants == nullptr) return kal_err_invalid; - // ⚠️ REFUSED BEFORE ANYTHING IS STARTED, not after. A caller that asked for a + // REFUSED BEFORE ANYTHING IS STARTED, not after. A caller that asked for a // bound lifetime and received a program without one has been given a program // that outlives it --- which is the failure the flag exists to remove --- so an // unclaimed position is an error and not a thing to proceed without. @@ -212,7 +212,7 @@ int kal_process_spawn(const kal_spawn* how, const bool bind = (how->flags & KAL_SPAWN_BOUND_LIFETIME) != 0; - // ⭐ THE UNIT, WHOSE IDENTITY HERE IS A PROCESS GROUP'S --- which is to say, + // THE UNIT, WHOSE IDENTITY HERE IS A PROCESS GROUP'S --- which is to say, // the identifier of whichever program formed it first. `join' is zero for the // first member, and the child then makes the group its own; a later member is // given the number to join. @@ -233,7 +233,7 @@ int kal_process_spawn(const kal_spawn* how, if (ou != 0) okl::sys(okl::nr_dup3, ou, 1, 0); if (er != 0) okl::sys(okl::nr_dup3, er, 2, 0); - // ⚠️ dup3 REFUSES A DUPLICATION ONTO ITSELF, which the ordinary case + // dup3 REFUSES A DUPLICATION ONTO ITSELF, which the ordinary case // reaches whenever a granted directory already occupies the number it // is destined for. Refusing there is correct of dup3 --- the flags could // not be applied --- and here it means the descriptor is already in @@ -244,7 +244,7 @@ int kal_process_spawn(const kal_spawn* how, okl::sys(okl::nr_dup3, granted[i], want, 0); } - // ⭐ THE DIRECTORY THE PROGRAM RUNS IN, AND THIS LINE IS THE WHOLE OF IT. + // THE DIRECTORY THE PROGRAM RUNS IN, AND THIS LINE IS THE WHOLE OF IT. // // `execveat' below takes `b' as a dirfd, but that only RESOLVES the // name --- resolving a name is not entering a directory, which is what @@ -252,7 +252,7 @@ int kal_process_spawn(const kal_spawn* how, // directory to enter, and a started program ran wherever this // implementation happened to be. // - // ⚠️ A FAILURE HERE MUST NOT REACH `execveat'. Running the right program + // A FAILURE HERE MUST NOT REACH `execveat'. Running the right program // in the wrong directory is precisely the silent wrongness this exists to // remove, so it is reported through the same pipe an exec failure uses. if (const okl_long e = okl::sys(okl::nr_fchdir, w); okl::failed(e)) { @@ -261,7 +261,7 @@ int kal_process_spawn(const kal_spawn* how, for (;;) { } } - // ⭐ THE UNIT, ENTERED HERE AND NOT FROM THE PARENT: the parent's own + // THE UNIT, ENTERED HERE AND NOT FROM THE PARENT: the parent's own // `setpgid' on this child races the replacement below and loses once the // program has been replaced. Zero means "your own", which is how a group // comes into existence at all --- there is nothing to create beforehand, @@ -279,7 +279,7 @@ int kal_process_spawn(const kal_spawn* how, okl::sys(okl::nr_exit_group, 127); } - // ⚠️⚠️ THE BASE IS DUPLICATED SO THAT IT SURVIVES THE REPLACEMENT, AND + // THE BASE IS DUPLICATED SO THAT IT SURVIVES THE REPLACEMENT, AND // WITHOUT THIS A WHOLE CLASS OF PROGRAMS COULD NOT BE STARTED AT ALL. // // `execveat' with a dirfd and a relative name gives the program's name to @@ -292,18 +292,18 @@ int kal_process_spawn(const kal_spawn* how, // replacement, by which time a close-on-exec dirfd is gone. The // interpreter is told the script does not exist. // - // ⭐ Measured in twenty lines of plain C, with everything else identical: + // Measured in twenty lines of plain C, with everything else identical: // // dirfd WITH O_CLOEXEC execveat -> ENOENT // dirfd WITHOUT O_CLOEXEC STARTED ok // - // ⚠️ It is not a property of one architecture. It was FOUND on aarch64, + // It is not a property of one architecture. It was FOUND on aarch64, // where every foreign binary needs the binfmt interpreter and so every // start failed --- and it was mistaken there for a limit of the emulator. // It reproduces natively on x86_64 with a `#!' script, which is what a // consumer meets on any machine. // - // ⚠️ Duplicated HERE, in the started image, and not where the preopens are + // Duplicated HERE, in the started image, and not where the preopens are // made: the caller's own descriptors stay close-on-exec, which is what // every other operation of this implementation relies upon. `dup' clears // the flag by definition, so the copy is the exec-visible one. @@ -326,7 +326,7 @@ int kal_process_spawn(const kal_spawn* how, return okl::translate(why); } - // ⚠️ WRITTEN ONLY AFTER THE START HAS SUCCEEDED, and only when the unit was + // WRITTEN ONLY AFTER THE START HAS SUCCEEDED, and only when the unit was // new. The first member's identifier IS the group's, so this is where the // caller learns it; a later member joins one the caller already holds and // there is nothing to report. @@ -404,7 +404,7 @@ int kal_process_wait(kal_process h, int* status, int* terminated_by_environment) return kal_ok; } -// ⭐ ONE PROGRAM, WHATEVER UNIT IT IS IN. +// ONE PROGRAM, WHATEVER UNIT IT IS IN. // // An earlier draft made this reach the whole group when the started program had // formed one, recovering that fact with `getpgid(pid) == pid'. It worked, and it @@ -435,7 +435,7 @@ int kal_process_job_enter(kal_job* j) { // Every program in the unit, including ones this implementation never held a // handle to --- which is the whole reason a unit exists. // -// ⚠️⚠️ AND IT IS THE SIGNAL THAT CANNOT BE DECLINED, WHICH IS A DECISION AND NOT +// AND IT IS THE SIGNAL THAT CANNOT BE DECLINED, WHICH IS A DECISION AND NOT // A DETAIL. // // `kal_process_terminate' upon ONE program uses the polite one: a caller holds @@ -445,18 +445,18 @@ int kal_process_job_enter(kal_job* j) { // one of them may ignore does not terminate the unit, it terminates the part of // it that agreed. // -// ⭐ Measured with a consumer's own test: a shell that traps the polite signal +// Measured with a consumer's own test: a shell that traps the polite signal // and loops. Asked politely, the unit outlived every deadline; the caller's // escalation could not help, because openkal has no vocabulary for "and this // time I mean it" --- it has no signals at all. // -// ⇒ So the operation does what its name says. ⚠️ WHAT THIS COSTS IS REAL: a +// ⇒ So the operation does what its name says. WHAT THIS COSTS IS REAL: a // member gets no chance to clean up, where on a system programmed directly a // caller would send the polite signal first and wait. A caller that wants that // still has it --- `kal_process_terminate' upon the member it holds --- and what it // cannot do is ask a unit politely. // -// ⚠️ A GROUP IS NAMED BY A PROCESS IDENTIFIER, AND THOSE ARE REUSED. Once the +// A GROUP IS NAMED BY A PROCESS IDENTIFIER, AND THOSE ARE REUSED. Once the // program that formed the group has ended and the numbers have wrapped, this can // reach a different group. That is what this system does --- every program that // calls `killpg' lives with it --- and the interface records it rather than @@ -467,7 +467,7 @@ int kal_process_job_terminate(kal_job j) { return okl::failed(r) ? okl::translate(r) : kal_ok; } -// ⚠️ RELEASES NOTHING AND ENDS NOTHING. A group here is a number, not a resource, +// RELEASES NOTHING AND ENDS NOTHING. A group here is a number, not a resource, // so there is no handle to close --- and the operation exists so that a caller // need not know that. Where the unit IS a resource, releasing it must still not // end its members; the interface says so at the declaration. @@ -477,9 +477,9 @@ void kal_process_job_close(kal_job) { } // waited for continues, and this environment collects it when the caller exits. void kal_process_close(kal_process) { } -// ⭐⭐ A WORD THE ENVIRONMENT SETS WHEN SOMEBODY HAS ASKED THIS PROGRAM TO END. +// A WORD THE ENVIRONMENT SETS WHEN SOMEBODY HAS ASKED THIS PROGRAM TO END. // -// ⚠️ A HANDLER AND NOT A WAITING CONTEXT, AND THE REASON IS WHICH ONE CAN BE +// A HANDLER AND NOT A WAITING CONTEXT, AND THE REASON IS WHICH ONE CAN BE // ARMED WITHOUT DISTURBING A PROGRAM THAT NEVER ASKS. Consuming these signals // from a context of its own would require them BLOCKED IN EVERY context, and // blocking is per-context and inherited: a program that already had contexts @@ -492,7 +492,7 @@ void kal_process_close(kal_process) { } // the wake is the same operation `kal_task_wake' performs --- issued here as the // raw call, because a handler may not enter code that takes a lock. // -// ⚠️ THE RESTORER IS SUPPLIED HERE ON ONE ARCHITECTURE AND BY THE KERNEL ON THE +// THE RESTORER IS SUPPLIED HERE ON ONE ARCHITECTURE AND BY THE KERNEL ON THE // OTHER. On x86_64 a disposition installed without SA_RESTORER faults on return // from the handler --- the C library normally supplies the three instructions, // and this implementation has no C library beneath it. On aarch64 the kernel @@ -517,7 +517,7 @@ void stop_handler(int) { 1 /* FUTEX_WAKE */, 0x7fffffff, 0, 0, 0); } -// ⚠️ THE RESULT IS EXAMINED, AND IT WAS NOT WHEN THIS SHIPPED IN 0.11. An +// THE RESULT IS EXAMINED, AND IT WAS NOT WHEN THIS SHIPPED IN 0.11. An // installation that failed would leave a word that can never change, and // answering the caller with one is `reporting success having done nothing' in // its exact form: the program asks whether its end has been requested, is told @@ -537,12 +537,12 @@ bool arm_one(int signo) { } // namespace -// ⚠️ ARMED ON THE FIRST ENQUIRY AND NOT AT STARTUP. A program that never asks +// ARMED ON THE FIRST ENQUIRY AND NOT AT STARTUP. A program that never asks // keeps the default action, which is what every program that has never heard of // this operation expects --- and it is the only arrangement under which adding // this operation changes nothing for anyone who does not use it. const kal_u32* kal_process_stop_requested(void) { - // ⚠️ THREE STATES AND NOT TWO: not yet tried, armed, refused. A second + // THREE STATES AND NOT TWO: not yet tried, armed, refused. A second // caller is told what the first found rather than arming again. int state = __atomic_load_n(&g_stop_armed, __ATOMIC_ACQUIRE); if (state == 0) { @@ -559,7 +559,7 @@ kal_uintptr kal_process_props(void) { | KAL_PROCESS_PROP_CHANNEL | KAL_PROCESS_PROP_GRANT_DIR | KAL_PROCESS_PROP_BOUND_LIFETIME | KAL_PROCESS_PROP_JOB - // ⚠️ AGREES WITH `kal_process_stop_requested', because the header + // AGREES WITH `kal_process_stop_requested', because the header // defines null there as the absence this position reports. Read and // never armed: asking what an implementation can do must not install a // disposition, so the position is claimed until an installation has diff --git a/src/random.cpp b/src/random.cpp index 4d14f65..0468c6b 100644 --- a/src/random.cpp +++ b/src/random.cpp @@ -1,6 +1,6 @@ // openkal.random on Linux --- getrandom(2). // -// ⭐ THE KERNEL'S OWN CALL AND NOT `/dev/urandom`. The device would need a +// THE KERNEL'S OWN CALL AND NOT `/dev/urandom`. The device would need a // descriptor, which needs a path, which a capability-oriented filesystem // deliberately does not hand out; and a program early enough in its life not to // have a filesystem yet still has this call. `getrandom` is the interface the @@ -10,7 +10,7 @@ namespace { -// ⚠️ `GRND_NONBLOCK` IS NOT SET, AND THAT IS WHAT `BLOCKING` REPORTS. +// `GRND_NONBLOCK` IS NOT SET, AND THAT IS WHAT `BLOCKING` REPORTS. // // Without it the call waits until the pool has been initialised, which on a // machine seconds into its first boot can be a real wait. Setting it instead @@ -32,7 +32,7 @@ extern "C" int kal_random_fill(void* out, kal_uintptr len) { static_cast(len - filled), flags_blocking); if (r < 0) { - // ⚠️ THE BUFFER IS NOT RESTORED, AND THE CONTRACT SAYS IT NEED NOT + // THE BUFFER IS NOT RESTORED, AND THE CONTRACT SAYS IT NEED NOT // BE: a failed fill leaves the buffer unspecified rather than // unchanged. Restoring it would oblige this function to keep a copy // of what it was handed, which is a cost every successful call diff --git a/src/stream.cpp b/src/stream.cpp index 95575ce..b2d24a4 100644 --- a/src/stream.cpp +++ b/src/stream.cpp @@ -7,7 +7,7 @@ extern "C" { // is what `openkal.stream`'s transfer operations take and what makes a stream // this implementation hands out interchangeable with one it received. // -// ⚠️⚠️ ONE CONSEQUENCE, AND IT IS NOT VISIBLE FROM THIS FILE. Standard input is +// ONE CONSEQUENCE, AND IT IS NOT VISIBLE FROM THIS FILE. Standard input is // therefore the handle ZERO, and `kal_spawn_streams` reserves zero to mean // "the stream the parent has" (openkal/process.h). The two readings agree at // position `in` --- placing standard input at standard input and inheriting it diff --git a/src/sys.h b/src/sys.h index 82c4078..2f7ad0a 100644 --- a/src/sys.h +++ b/src/sys.h @@ -99,6 +99,8 @@ enum : okl_long { nr_renameat = 264, nr_readlinkat = 267, nr_dup3 = 292, nr_execveat = 322, nr_dup2 = 33, nr_utimensat = 280, nr_symlinkat = 266, nr_fstatfs = 138, nr_getrandom = 318, + // openkal 0.13: whether a node may be started + nr_fchmodat = 268, // openkal 0.11: the directory a started program runs in, and the unit it joins nr_fchdir = 81, nr_setpgid = 109, nr_rt_sigaction = 13, // openkal.net and openkal.datagram @@ -185,6 +187,8 @@ enum : okl_long { nr_prctl = 167, nr_sched_getaffinity = 123, nr_getppid = 173, nr_arch_prctl = -1, nr_utimensat = 88, nr_symlinkat = 36, nr_fstatfs = 44, nr_getrandom = 278, + // openkal 0.13: whether a node may be started + nr_fchmodat = 53, // openkal 0.11: the directory a started program runs in, and the unit it joins nr_fchdir = 50, nr_setpgid = 154, nr_rt_sigaction = 134, // openkal.net and openkal.datagram @@ -207,7 +211,8 @@ enum : okl_long { // appear here rather than being taken from a header: the header belongs to a // C library and this implementation has none. enum : int { - e_perm = 1, e_noent = 2, e_intr = 4, e_io = 5, e_badf = 9, e_child = 10, + e_perm = 1, e_noent = 2, e_intr = 4, e_io = 5, e_noexec = 8, e_badf = 9, + e_child = 10, e_again = 11, e_nomem = 12, e_acces = 13, e_fault = 14, e_busy = 16, e_exist = 17, e_xdev = 18, e_nodev = 19, e_notdir = 20, e_isdir = 21, e_inval = 22, e_nfile = 23, e_mfile = 24, e_notty = 25, e_fbig = 27, @@ -218,7 +223,7 @@ enum : int { // --- constants the kernel defines ------------------------------------------ // -// ⚠️⚠️ THREE OF THESE ARE NOT THE SAME NUMBER ON BOTH ARCHITECTURES, AND WERE +// THREE OF THESE ARE NOT THE SAME NUMBER ON BOTH ARCHITECTURES, AND WERE // WRITTEN AS THOUGH THEY WERE. // // `O_DIRECTORY', `O_NOFOLLOW' and `O_DIRECT' have one set of values on x86_64 @@ -235,7 +240,7 @@ enum : int { // THIS IMPLEMENTATION TRIED TO OPEN FAILED --- including the two preopens it // supplies at inception, which is every directory a program above it can reach. // -// ⭐ MEASURED, and the reading is unambiguous: on aarch64 `kal_fs_preopen_count' +// MEASURED, and the reading is unambiguous: on aarch64 `kal_fs_preopen_count' // answered two and both entries reported `kal_err_permission' with a handle of // zero, while the same program on x86_64 reported both directories and opened a // file in one. A C library above it then had no directory to resolve a name @@ -243,7 +248,7 @@ enum : int { // reads as a program started somewhere odd rather than as an implementation // that opened nothing. // -// ⚠️ Nothing caught it. The conformance suite is run on x86_64; this package is +// Nothing caught it. The conformance suite is run on x86_64; this package is // built for aarch64 and the build succeeds, because a wrong constant is a // number and not a type error. enum : okl_long { @@ -251,21 +256,21 @@ enum : okl_long { o_creat = 0100, o_excl = 0200, o_trunc = 01000, o_append = 02000, o_cloexec = 02000000, - // ⭐ THE LOWEST FREE DESCRIPTOR AT OR ABOVE A BOUND, which is the one + // THE LOWEST FREE DESCRIPTOR AT OR ABOVE A BOUND, which is the one // primitive that moves a descriptor out of the way WITHOUT NAMING the // number it moves to --- and therefore without closing whatever a caller // already had there. `dup3' cannot do this: it is told the number, and it // closes what is on it. f_dupfd_cloexec = 1030, - // ⭐ The same primitive WITHOUT the flag, which is the point of having both. + // The same primitive WITHOUT the flag, which is the point of having both. // A descriptor duplicated this way survives a replacement, and starting a // program that needs an interpreter depends on exactly that --- see the // duplication in `kal_process_spawn'. `dup' would do as well and this // architecture pair does not agree on whether it exists. f_dupfd = 0, - // ⭐⭐ THE OPEN-FILE FORM AND NOT THE PROCESS FORM, WHICH IS THE WHOLE + // THE OPEN-FILE FORM AND NOT THE PROCESS FORM, WHICH IS THE WHOLE // DIFFERENCE. // // This kernel's oldest record lock is held by the PROCESS, and it is @@ -317,6 +322,7 @@ inline int translate(okl_long r) { case e_notempty: return 11; // kal_err_not_empty case e_isdir: return 12; // kal_err_is_directory case e_notdir: return 13; // kal_err_not_directory + case e_noexec: return 14; // kal_err_not_program default: return 3; // kal_err_io } } @@ -401,7 +407,7 @@ struct kstat { okl_i64 blksize; okl_i64 blocks; #else - // ⚠️⚠️ THE FIELDS OF THIS ARCHITECTURE'S RECORD WERE IN THE WRONG ORDER, AND + // THE FIELDS OF THIS ARCHITECTURE'S RECORD WERE IN THE WRONG ORDER, AND // THE BUILD COULD NOT SAY SO. // // The kernel's architecture-independent `struct stat' --- which aarch64 uses @@ -410,7 +416,7 @@ struct kstat { // neither: the mode was read from offset 60 where the kernel writes a // group, and the size from 32 where it writes a device number. // - // ⭐ MEASURED, the same program on both architectures: + // MEASURED, the same program on both architectures: // // x86_64 file: kind=1 size=10 writable=1 link: kind=3 // aarch64 file: kind=4 size=0 writable=0 link: kind=4 @@ -420,7 +426,7 @@ struct kstat { // written ten bytes to was not a regular file --- and every operation that // decides upon a kind, which is most of `std::filesystem', decided wrongly. // - // ⚠️ NOTHING IN THIS ECOSYSTEM COULD HAVE CAUGHT IT. The conformance suite + // NOTHING IN THIS ECOSYSTEM COULD HAVE CAUGHT IT. The conformance suite // runs on the machine that builds it, and every hosted machine in this // ecosystem's continuous integration is x86_64 or an arm64 Mac --- which // uses openkal-macos and a different record again. The aarch64 leg of THIS diff --git a/src/task.cpp b/src/task.cpp index c770f3a..856da40 100644 --- a/src/task.cpp +++ b/src/task.cpp @@ -224,13 +224,13 @@ kal_uintptr kal_task_current(void) { // How many contexts can run at the same moment. Version 0.10. // -// ⚠️⚠️ ADDED BECAUSE ITS ABSENCE WAS A WRONG ANSWER RATHER THAN A REFUSAL. +// ADDED BECAUSE ITS ABSENCE WAS A WRONG ANSWER RATHER THAN A REFUSAL. // `KAL_TASK_PROP_PARALLEL' says WHETHER and not HOW MANY, so a C library above // had nowhere to look and `hardware_concurrency()' answered 1 with no error --- // a program sizing a pool of workers got one worker and no way to know. // Measured: 1 through openkal-musl against 32 on the same machine. // -// ⭐ THE SET THIS CONTEXT MAY RUN ON, not the set the machine has. A program +// THE SET THIS CONTEXT MAY RUN ON, not the set the machine has. A program // confined to two processors is asked to size itself against two; asking the // machine would have it size against a number it cannot use. kal_uintptr kal_task_parallelism(void) { diff --git a/tests/conformance_additions.cpp b/tests/conformance_additions.cpp index 7c77e06..1f459cc 100644 --- a/tests/conformance_additions.cpp +++ b/tests/conformance_additions.cpp @@ -145,6 +145,42 @@ int main() { static_assert(!__is_same(kal::task::props, kal::process::props)); static_assert(!__is_same(kal::fs::props, kal::fs::open_flags)); + // --- version 0.13: a node that may be started, and one that is not a program + // + // The mode is read back from the kernel's own record rather than through + // the enquiry, so that the enquiry and the operation cannot agree with each + // other while both are wrong. + { + const char* prog = "okl-not-a-program.tmp"; + const kal_uintptr pn = std::strlen(prog); + check(put(prog, "this is text\n"), "a text file is written"); + check((kal_fs_props(here()) & kal::fs::executable.bits) != 0, + "the volume the tests run on records whether a node may be started"); + check(kal_fs_set_executable_at(here(), prog, pn, 1) == kal_ok, + "the node is recorded as startable"); + kal_node_info info = kal::fs::info_for_caller(); + check(kal_fs_info(here(), prog, pn, 0, kal::fs::field::executable, &info) == kal_ok + && (info.present & kal::fs::field::executable) != 0 && info.executable == 1, + "the enquiry reports it"); + + kal_process p{}; + const char* argv[1] = { prog }; + const kal_uintptr lens[1] = { pn }; + const kal_spawn how{ here(), here(), nullptr, nullptr, 0, 0 }; + const int e = kal_process_spawn(&how, prog, pn, argv, lens, 1, + nullptr, nullptr, 0, nullptr, &p); + check(e == kal_err_not_program, + "the kernel's ENOEXEC reaches the caller as kal_err_not_program"); + + check(kal_fs_set_executable_at(here(), prog, pn, 0) == kal_ok, + "the record is cleared"); + const kal_spawn again{ here(), here(), nullptr, nullptr, 0, 0 }; + check(kal_process_spawn(&again, prog, pn, argv, lens, 1, + nullptr, nullptr, 0, nullptr, &p) == kal_err_permission, + "and a node not recorded as startable is refused as a permission"); + kal_fs_remove(here(), prog, pn); + } + std::printf("openkal-linux: the operations version 0.5 added\n"); return failures == 0 ? 0 : 1; } diff --git a/tests/conformance_env_time.cpp b/tests/conformance_env_time.cpp index 7dbf27e..e8ddc32 100644 --- a/tests/conformance_env_time.cpp +++ b/tests/conformance_env_time.cpp @@ -20,7 +20,7 @@ int main() { // A program always receives the name it was started with, even where the // environment has none, in which case it is empty rather than absent. check(kal::env::arg_count() >= 1, "at least the program name is present"); - // ⭐ THE VALUE IS COPIED AND THE LENGTH REPORTED IS THE VALUE'S OWN, so a + // THE VALUE IS COPIED AND THE LENGTH REPORTED IS THE VALUE'S OWN, so a // capacity of zero asks for the length without writing --- which is what // lets a caller size a buffer before it has one. char buf[1024]; diff --git a/tests/conformance_fs.cpp b/tests/conformance_fs.cpp index b612d29..0213f32 100644 --- a/tests/conformance_fs.cpp +++ b/tests/conformance_fs.cpp @@ -82,7 +82,7 @@ int main() { } check(found, "enumeration finds the entry"); - // ⚠️ The property the interface exists to have: a name that ascends is + // The property the interface exists to have: a name that ascends is // refused, so a program cannot leave the directory it was given. kal_dir escape{}; check(kal_fs_open_dir(d, "..", 2, &escape) != kal_ok, "an ascending name is refused"); diff --git a/tests/conformance_process_task.cpp b/tests/conformance_process_task.cpp index ce255a2..8ed85e5 100644 --- a/tests/conformance_process_task.cpp +++ b/tests/conformance_process_task.cpp @@ -37,7 +37,7 @@ int main() { // The program to start is reached through a directory the environment // supplied, which is the whole reason the set exists: a program and the // program it starts are commonly not beneath one root. - // ⭐ THE NAME IS COPIED INTO A BUFFER HERE, which is what the operation now + // THE NAME IS COPIED INTO A BUFFER HERE, which is what the operation now // does: it answered with a pointer into the implementation's own storage, // which is meaningful only while the implementation shares this address // space. diff --git a/tests/conformance_stream.cpp b/tests/conformance_stream.cpp index 85d9ed9..6e5beff 100644 --- a/tests/conformance_stream.cpp +++ b/tests/conformance_stream.cpp @@ -33,7 +33,7 @@ int main() { // specification excludes a successful partial transfer, so a conforming // result reports either the full count or a non-zero error. const char msg[] = "openkal-linux: conformance\n"; - // ⭐ ONE SIGNED WORD: the count, or the negated condition when no byte + // ONE SIGNED WORD: the count, or the negated condition when no byte // moved. A caller never inspects two things to learn one thing. const kal_intptr r = kal::write(kal::out(), msg, sizeof(msg) - 1); check(r >= 0, "write reports success");