diff --git a/README.md b/README.md index 326ded8..f4c4bce 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ for Linux, written on the kernel's own system-call interface. openkal = "0.17.0" [target.'cfg(os = "linux")'.dependencies] -openkal-linux = "0.18.0" +openkal-linux = "0.18.1" ``` ## Why it does not use a C library diff --git a/mcpp.toml b/mcpp.toml index fd3961b..f3bfdcb 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -1,7 +1,7 @@ [package] namespace = "mcpplibs" name = "openkal-linux" -version = "0.18.0" +version = "0.18.1" 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" @@ -63,7 +63,7 @@ provides-interfaces = [ ] [dependencies] -openkal = "0.17.0" +openkal = "0.17.1" # 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 623f58f..70f9c90 100644 --- a/src/process.cpp +++ b/src/process.cpp @@ -415,6 +415,16 @@ bool absolute_under(int fd, const char* name, okl_uptr len, char* out, okl_uptr return true; } +// Whether `name' reaches the file `fd' holds: the same device and inode. +bool same_file(okl_long fd, const char* name) { + okl::kstat held{}, named{}; + if (okl::failed(okl::sys(okl::nr_fstat, fd, reinterpret_cast(&held)))) return false; + if (okl::failed(okl::sys(okl::nr_newfstatat, okl::at_fdcwd, reinterpret_cast(name), + reinterpret_cast(&named), 0))) + return false; + return held.dev == named.dev && held.ino == named.ino; +} + } // namespace // EVERY ENVIRONMENT ENTRY IS `NAME=value' (0.17, process.h). Refused before @@ -643,11 +653,18 @@ int kal_process_spawn(const kal_spawn* how, // the caller had no standard input and supplied none, the lowest free // number is 0, and a script would read its own descriptor as input. // - // What is given up: a script now sees `$0' as `/dev/fd/' and not as - // a path under its directory, so one that locates its neighbours with - // `dirname "$0"' no longer finds them. That is the price of leaving a - // descriptor for the file and not for the directory, the same one - // `fexecve' charges. The descriptor still names the program: it can be + // A SCRIPT IS GIVEN ITS NAME WHERE THE NAME REACHES THE SAME FILE + // (0.18.1). Through the descriptor a script sees `$0' as `/dev/fd/', + // and one that locates its neighbours with `dirname "$0"' --- a wrapper + // installed beside the files it runs, a test's stub provider --- finds + // none of them: measured in mcpp, whose provider stub wrote beside + // itself into `/dev/fd/ticks'. So a program that needs an interpreter is + // started by its absolute name, as every shell and `posix_spawn' start + // it, when that name is known and names the file the descriptor holds + // (the same device and inode, so the name grants nothing the descriptor + // did not). Only where it does not --- a base the kernel cannot name, a + // name rebound since it was opened --- is the descriptor kept, with the + // price stated above: the descriptor still names the program and can be // reopened through `/proc/self/fd/', as `/proc/self/exe' can. // // Opened HERE, in the started image, and not where the preopens are made: @@ -679,6 +696,10 @@ int kal_process_spawn(const kal_spawn* how, reinterpret_cast(envs.slots), okl::at_empty_path); }; okl_long why = start(); + if (why == -okl::e_noent && named && same_file(exe, resolved)) + why = okl::sys(okl::nr_execve, reinterpret_cast(resolved), + reinterpret_cast(args.slots), + reinterpret_cast(envs.slots)); if (why == -okl::e_noent) { okl::sys(okl::nr_fcntl, exe, okl::f_setfd, 0); why = start(); @@ -951,7 +972,11 @@ const kal_u32* kal_process_stop_requested(void) { // caller is told what the first found rather than arming again. int state = __atomic_load_n(&g_stop_armed, __ATOMIC_ACQUIRE); if (state == 0) { - const bool ok = arm_one(15) && arm_one(2); // SIGTERM, SIGINT + // SIGTERM, SIGINT and SIGHUP (0.18.1): the terminal that started a + // program closing is a request to end it as much as the other two, and + // a program that took the requests over and not this one was ended by + // it before it could finish in order. + const bool ok = arm_one(15) && arm_one(2) && arm_one(1); state = ok ? 1 : -1; __atomic_store_n(&g_stop_armed, state, __ATOMIC_RELEASE); } diff --git a/tests/script_sees_its_name.cpp b/tests/script_sees_its_name.cpp new file mode 100644 index 0000000..a1d8bc2 --- /dev/null +++ b/tests/script_sees_its_name.cpp @@ -0,0 +1,71 @@ +// A `#!' script is given its name (0.18.1). +// +// Started through the descriptor, a script saw `$0' as `/dev/fd/', and one +// that locates its neighbours with `dirname "$0"' found none of them. A +// program that needs an interpreter is now started by its absolute name where +// that name reaches the file the descriptor holds. +// +// The script writes beside itself, under the name its `$0' gives; the test +// reads the file back from the directory it placed the script in. +#include +#include +#include +#include +#include +#include +import openkal.types; +import openkal.fs; +import openkal.process; + +namespace { +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 main() { + char dir[] = "/tmp/okl-script-XXXXXX"; + if (::mkdtemp(dir) == nullptr) { std::printf("FAIL: a scratch directory\n"); return 1; } + char script[256], beside[256]; + std::snprintf(script, sizeof script, "%s/stub", dir); + std::snprintf(beside, sizeof beside, "%s/beside", dir); + { + const int fd = ::open(script, O_WRONLY | O_CREAT | O_TRUNC, 0755); + const char text[] = "#!/bin/sh\nprintf '%s' \"$0\" > \"${0%/*}/beside\"\n"; + const bool written = fd >= 0 && ::write(fd, text, sizeof text - 1) == static_cast(sizeof text - 1); + if (fd >= 0) ::close(fd); + if (!written) { std::printf("FAIL: the script is written\n"); return 1; } + } + + const kal_dir r = root(); + const char* argv[] = { script }; + const kal_uintptr lens[] = { std::strlen(script) }; + const kal_spawn how{ r, r, nullptr, nullptr, 0, 0 }; + kal_process p{}; + const int e = kal_process_spawn(&how, script + 1, std::strlen(script + 1), argv, lens, 1, + nullptr, nullptr, 0, nullptr, &p); + int status = -1, terminated = 0; + if (e == kal_ok) kal_process_wait(p, &status, &terminated); + + char seen[256] = {}; + if (FILE* f = std::fopen(beside, "r")) { std::fgets(seen, sizeof seen, f); std::fclose(f); } + ::unlink(beside); + ::unlink(script); + ::rmdir(dir); + + int failures = 0; + if (e != kal_ok || terminated != 0 || status != 0) { + std::printf("FAIL: the script is started (rc %d, status %d)\n", e, status); + ++failures; + } + if (std::strcmp(seen, script) != 0) { + std::printf("FAIL: the script sees its own name as $0 (saw '%s')\n", seen); + ++failures; + } + if (failures == 0) std::printf("openkal-linux: a script is given its name\n"); + return failures == 0 ? 0 : 1; +} diff --git a/tests/stop_word_take_over.cpp b/tests/stop_word_take_over.cpp index 1134b70..a515108 100644 --- a/tests/stop_word_take_over.cpp +++ b/tests/stop_word_take_over.cpp @@ -25,6 +25,17 @@ void check(bool held, const char* what) { } int main(int argc, char** argv) { + if (argc > 1 && std::strcmp(argv[1], "--hang-up") == 0) { + // 0.18.1: the closing of the terminal is a request to end, taken over + // with the others, and the program ends as it would have. + const kal_u32* word = kal_process_stop_requested(); + if (word == nullptr) return 90; + ::kill(::getpid(), SIGHUP); + for (int i = 0; i < 1000 && *word == 0; ++i) ::usleep(1000); + if (*word == 0) return 91; + kal_process_end_as_requested(); + return 92; + } if (argc > 1 && std::strcmp(argv[1], "--end-as-requested") == 0) { const kal_u32* word = kal_process_stop_requested(); if (word == nullptr) return 90; @@ -58,6 +69,16 @@ int main(int argc, char** argv) { check(WIFSIGNALED(st) && WTERMSIG(st) == SIGTERM, "a program that ends as requested is reported to its starter as ended by the request"); + const pid_t hung = ::fork(); + if (hung == 0) { + ::execl("/proc/self/exe", argv[0], "--hang-up", nullptr); + ::_exit(93); + } + st = 0; + ::waitpid(hung, &st, 0); + check(WIFSIGNALED(st) && WTERMSIG(st) == SIGHUP, + "a hang-up is taken over as a request, and the program ends as it would have"); + if (failures == 0) std::printf("openkal-linux: the stop word is taken over and given back\n"); return failures == 0 ? 0 : 1; }