diff --git a/README.md b/README.md index fc0c3f2..76e396c 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.21.0" +openkal = "0.22.0" [target.'cfg(os = "linux")'.dependencies] -openkal-linux = "0.22.0" +openkal-linux = "0.23.0" ``` ## Why it does not use a C library @@ -102,6 +102,14 @@ A program that carries a runtime of its own answers from that runtime instead (`pthread_getattr_np`), because in that arrangement the runtime is what owns the stacks and is what a program above openkal would ask for itself. +## What 0.23 adds (openkal 0.22) + +**The null device at a position.** `KAL_SPAWN_NULL_IN`, `KAL_SPAWN_NULL_OUT` and +`KAL_SPAWN_NULL_ERR` place `/dev/null` at the started program's standard input, +output or error; `KAL_PROCESS_PROP_NULL_STREAMS` is claimed. The device is opened +in the started image before it is replaced, so the caller holds no descriptor for +it. A flag beside a stream at the same position is `kal_err_invalid`. + ## What 0.22 adds (openkal 0.21) **Instructions from a file.** `kal_exec_map_part` maps the file's pages over part diff --git a/mcpp.toml b/mcpp.toml index 433efda..7eb3bad 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -1,7 +1,7 @@ [package] namespace = "mcpplibs" name = "openkal-linux" -version = "0.22.0" +version = "0.23.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" @@ -65,7 +65,7 @@ provides-interfaces = [ ] [dependencies] -openkal = "0.21.0" +openkal = "0.22.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/process.cpp b/src/process.cpp index 632a223..eef7d0a 100644 --- a/src/process.cpp +++ b/src/process.cpp @@ -533,9 +533,25 @@ int start_image(void* arg) { } return r; }; - const okl_long sin = in != 0 ? lift(in) : 0; - const okl_long sou = ou != 0 ? lift(ou) : 0; - const okl_long ser = er != 0 ? lift(er) : 0; + // THE NULL DEVICE (openkal 0.22), opened here, in the started image, so + // that the caller holds no descriptor for it; it is placed as any other + // source is. Its descriptor is marked to close, so the replacement keeps + // only the positions it was placed at. + const kal_uintptr nulls = how->flags & (KAL_SPAWN_NULL_IN | KAL_SPAWN_NULL_OUT | KAL_SPAWN_NULL_ERR); + okl_long device = 0; + if (nulls != 0) { + const okl_long fd = okl::sys(okl::nr_openat, okl::at_fdcwd, reinterpret_cast("/dev/null"), + okl::o_rdwr | okl::o_cloexec, 0); + if (okl::failed(fd)) { + report.say(fd); + okl::sys(okl::nr_exit_group, 127); + for (;;) { } + } + device = lift(fd); + } + const okl_long sin = (how->flags & KAL_SPAWN_NULL_IN) ? device : in != 0 ? lift(in) : 0; + const okl_long sou = (how->flags & KAL_SPAWN_NULL_OUT) ? device : ou != 0 ? lift(ou) : 0; + const okl_long ser = (how->flags & KAL_SPAWN_NULL_ERR) ? device : er != 0 ? lift(er) : 0; for (kal_uintptr i = 0; i < how->grant_count; ++i) granted[i] = lift(granted[i]); const okl_long base = lift(b); const okl_long work = lift(w); @@ -734,7 +750,15 @@ int kal_process_spawn(const kal_spawn* how, // 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. - if (how->flags & ~KAL_SPAWN_BOUND_LIFETIME) return kal_err_not_supported; + constexpr kal_uintptr nulls = KAL_SPAWN_NULL_IN | KAL_SPAWN_NULL_OUT | KAL_SPAWN_NULL_ERR; + if (how->flags & ~(KAL_SPAWN_BOUND_LIFETIME | nulls)) return kal_err_not_supported; + // THE NULL DEVICE BESIDE A STREAM AT ONE POSITION is two answers to one + // question (openkal 0.22, clause 7.15). + if (streams != nullptr + && (((how->flags & KAL_SPAWN_NULL_IN) && streams->in.h != 0) + || ((how->flags & KAL_SPAWN_NULL_OUT) && streams->out.h != 0) + || ((how->flags & KAL_SPAWN_NULL_ERR) && streams->err.h != 0))) + return kal_err_invalid; okl::terminated p(path, path_len); if (!p.ok) return kal_err_invalid; @@ -1358,6 +1382,7 @@ kal_uintptr kal_process_props(void) { | KAL_PROCESS_PROP_BOUND_LIFETIME | KAL_PROCESS_PROP_JOB | KAL_PROCESS_PROP_JOB_USAGE | KAL_PROCESS_PROP_JOB_LIMIT_SUPERVISED + | KAL_PROCESS_PROP_NULL_STREAMS | (open_id_available() || stat_state(0) != 0 ? KAL_PROCESS_PROP_OPEN_ID : 0u) // AGREES WITH `kal_process_stop_requested', because the header // defines null there as the absence this position reports. Read and diff --git a/tests/openkal_0_22.cpp b/tests/openkal_0_22.cpp new file mode 100644 index 0000000..19cd75e --- /dev/null +++ b/tests/openkal_0_22.cpp @@ -0,0 +1,76 @@ +// What openkal-linux 0.23 changes beside openkal 0.22, as this kernel does it. +// +// THE NULL DEVICE AT A POSITION IS THE SYSTEM'S OWN: a started program finds a +// character device at standard input and standard error, reads the end of input +// there and has every write accepted (openkal 0.22, clause 7.15). The device is +// opened in the started image, so the caller holds no descriptor for it +// afterwards. +#include +#include +#include +#include +#include +import openkal.types; +import openkal.env; +import openkal.fs; +import openkal.process; + +namespace { +int failures = 0; +void check(bool held, const char* what) { + if (!held) { std::printf("FAIL: %s\n", what); ++failures; } +} + +kal_dir root() { + for (kal_uintptr i = 0; i < kal_fs_preopen_count(); ++i) { + kal_dir d{}; char n[8]; kal_uintptr l = 0; + if (kal_fs_preopen(i, &d, n, sizeof n, &l) == kal_ok && l == 1 && n[0] == '/') return d; + } + return kal_dir{}; +} + +int descriptors() { + int n = 0; + for (int fd = 0; fd < 1024; ++fd) n += ::fcntl(fd, F_GETFD) != -1; + return n; +} + +char g_self[4096]; +} // namespace + +int main(int argc, char** argv) { + if (argc > 1 && std::strcmp(argv[1], "--null") == 0) { + struct stat in{}, err{}; + if (::fstat(0, &in) != 0 || !S_ISCHR(in.st_mode)) return 41; + if (::fstat(2, &err) != 0 || !S_ISCHR(err.st_mode)) return 42; + if (in.st_rdev != err.st_rdev) return 43; + char b[8]; + if (::read(0, b, sizeof b) != 0) return 44; + if (::write(2, "discarded", 9) != 9) return 45; + return 0; + } + const kal_intptr n = kal_env_image(g_self, sizeof g_self - 1); + if (n <= 0 || g_self[0] != '/') { std::printf("FAIL: the program's own image is named\n"); return 1; } + g_self[n] = 0; + + const kal_dir r = root(); + const char* args[] = { g_self, "--null" }; + const kal_uintptr lens[] = { std::strlen(g_self), 6 }; + const kal_spawn how{ r, r, nullptr, nullptr, 0, (kal::process::null_in_flag | kal::process::null_err_flag).bits }; + const int before = descriptors(); + kal_process p{}; + const int e = kal_process_spawn(&how, g_self + 1, std::strlen(g_self + 1), args, lens, 2, + nullptr, nullptr, 0, nullptr, &p); + check(e == kal_ok, "a program is started with the null device at two positions"); + check(descriptors() == before, "and the caller holds no descriptor for the device"); + int status = -1, terminated = 0; + if (e == kal_ok) { + kal_process_wait(p, &status, &terminated); + kal_process_close(p); + } + check(terminated == 0 && status == 0, + "the started program finds one character device there, reads its end and writes to it"); + if (status > 0) std::printf(" the started program reported %d\n", status); + if (failures == 0) std::printf("openkal_0_22: ok\n"); + return failures == 0 ? 0 : 1; +}