diff --git a/README.md b/README.md index 0ee574f..1855dfc 100644 --- a/README.md +++ b/README.md @@ -77,16 +77,16 @@ conditional on the target. ```toml [dependencies] -openkal = "0.16.0" +openkal = "0.17.0" [target.'cfg(os = "linux")'.dependencies] -openkal-linux = "0.15.1" +openkal-linux = "0.18.0" [target.'cfg(os = "macos")'.dependencies] -openkal-macos = "0.12.1" +openkal-macos = "0.15.0" [target.'cfg(windows)'.dependencies] -openkal-windows = "0.10.2" +openkal-windows = "0.13.0" ``` The program imports the interface and names no implementation. @@ -115,6 +115,7 @@ its own. The layer names are those of the build tool (`mcpp`, *The Target Side*) | `c-abi = musl` | `openkal-musl` | a POSIX-shaped C environment built on `kal_*`; what it cannot provide it refuses and lists | the platform's own C runtime headers (`io.h`, `TargetConditionals.h`); facilities openkal does not define (`epoll`, signal handlers) | | `c++-abi = libc++` | `openkal-llvm-runtime` | a C++ runtime configured for that C library | | | build tool | `mcpp` | a layer supplied by the dependency graph is supplied wholly by it: the host's headers and libraries are not searched | whether a package's source compiles against that C library | +| image (`-openkal`, clause 12) | `openkal-image`'s loader, holding one implementation | the same bytes run in every environment of the architecture that has a loader; a required interface the implementation lacks is refused at load and named | a platform format, signature or installer (a program carrying the image as a native executable provides those) | Four rules follow, and they bind packages rather than this specification. diff --git a/SPEC.md b/SPEC.md index 97a06a0..20c7bbd 100644 --- a/SPEC.md +++ b/SPEC.md @@ -1,4 +1,4 @@ -# openkal Specification, version 0.16 +# openkal Specification, version 0.17 ## 1. Scope @@ -955,6 +955,28 @@ system from inside a confinement. Each implementation observes the first paragraph in its own tests, since positions and handle tables are not portable; the conformance suite observes the third. +### 7.14 Removing a name that is held open + +`kal_fs_remove` of a file another handle holds open removes the name when the +call returns, on a volume for which the implementation claims +`KAL_FS_PROP_REMOVE_OPEN`. The open file remains usable through its handles +until the last of them is closed; the directory that held the name is then +empty and may be removed, and the name may be created again. + +The behaviour is the one a C library's `unlink` states, and a program relies on +it without naming it: a lock file held for the program's lifetime, a temporary +file kept open after its name is removed, a tree removed while one of its files +is still open. One environment defers the removal by default --- the name +remains, refusing every open, until the last handle is closed --- and offers +the immediate form on the volumes that record it. Measured above an +implementation that used the deferred form: a C++ program's `remove_all` of a +directory holding its own lock file failed with "directory not empty" on that +environment alone, while the same program built for the environment removed it. + +A volume that cannot remove a held name at once is answered before the call +(clause 6.2): the position is not claimed, and a caller that needs the +directory gone closes what it holds first. + ## 8. Evolution Each interface is versioned independently. A revision may add declarations and @@ -1038,6 +1060,36 @@ A device supplied through openhal may serve as the implementation of an openkal interface. The relationship is one of connection rather than of layering, and the connection is made by the package that describes the board. +### 10.1 One implementation in one process + +**A process holds one implementation of openkal.** The table above says one per +interface; this states the unit it is counted in. An implementation keeps state +that belongs to the process and not to a caller --- the word of +`kal_process_stop_requested`, the table its owned handles index (clause 6.7), the +treatment it installed for the environment's requests --- and two +implementations in one process would each believe they own it. A handle issued +by one is not a handle of the other (clause 7.2), so two parts of one program +that each linked an implementation of their own cannot pass a file between them, +and two treatments of one request to end leave it delivered to whichever was +installed last. + +A program linked as one artifact satisfies this by construction. A program +composed of several --- a program and the libraries it loads, a loader and the +images it starts (clause 12) --- satisfies it when the implementation is in one +of them and the others reach it, which is the **shared form** of an +implementation: an artifact the others load, whose exported names are exactly +`SURFACE.txt` (clause 9's second half applies to it unchanged), and whose name +carries the major version of the specification it implements, so that two +artifacts written against incompatible versions do not bind to one. An +implementation that offers a shared form states it in its package; it is not +required to offer one, and a program that is one artifact needs none. + +What a program cannot do is load a second implementation beside the first and +use both. Nothing in this specification detects it, because nothing can: the two +are each conforming, and the defect is in the composition. It is recorded here +so that a composition is examined for it rather than discovering it as a handle +that one half of a program refuses. + ## 11. Matters this version does not settle The following are recorded so that they are not mistaken for oversights. @@ -1487,3 +1539,241 @@ The following are recorded so that they are not mistaken for oversights. has released. Each implementation observes the release in its own tests, because a count of open handles is not portable — clause 7.13 observes its first paragraph the same way, for the same reason. + +25. **A rename that does not replace.** **Settled in 0.17.** + `kal_fs_rename_flags` with `KAL_RENAME_NO_REPLACE`, answered per volume by + `KAL_FS_PROP_RENAME_NO_REPLACE`. Three consumers above this interface + publish a file under a name only if nobody else has --- a package store, a + build cache several programs fill, an installer --- and each composed it + from an enquiry and a rename, which loses the name to a second publisher + between the two. The composition through `KAL_OPEN_EXCLUSIVE` does not + help: it creates the name and the rename then replaces what was created. + Every environment this specification targets performs the operation + natively where the volume can, so it is a word that was missing (entry + 10's criterion), and a volume that cannot is answered before the call + (clause 6.2). A second declaration rather than a flags argument added to + `kal_fs_rename`, because clause 8 forbids altering the first. +26. **Ending as the request to end would have.** **Settled in 0.17.** + `kal_process_end_as_requested`. A program that takes the request over with + `kal_process_stop_requested` and finishes its work had no way to end as the + request would have ended it: `kal_exit` with any status reports a program + that chose to end, a shell running a loop then starts the next program, and + a starter that waits cannot tell a cancelled build from a failed one. The + report it wanted is the environment's own report of an end the environment + caused, which nothing above this interface can produce. Measured by the + consumer that met it: a build tool's cancelled run was reported to its + shell as an exit status, and the shell's loop continued. + + **The take-over is stated with it**, in `process.h`: from the first call of + `kal_process_stop_requested`, a request reaches the program only through the + word, and a request its starter arranged not to deliver stays undelivered. + Two implementations restored the delivery of a request their starter had + declined, which ended background programs that were meant to survive it. +27. **A rendezvous at a name.** **Settled in 0.17.** `kal_net_listen_at` and + `kal_net_connect_at`, answered per directory by `KAL_FS_PROP_LOCAL_ENDPOINT`. + Two programs on one machine --- an editor and the server it starts, two + instances of one program deciding which is the first --- met at an address + on the loopback, which every program on the machine can reach, so each + protocol carried its own access control. A name in a directory is reached by + a program that holds the directory, which is the confinement of clause 7.13 + applied to a connection. Every environment this specification targets binds + such a connection to a name natively. It is admitted as two operations of + `openkal.net` rather than an interface of its own because the resources are + the ones the interface already has --- a listener and a connection --- and + only where they meet is new. Passing a handle across such a connection is + not admitted, for the reason entry 9 gives. +28. **Publishing part of a region.** **Settled in 0.17.** + `kal_exec_publish_part` and `kal_exec_granularity`. `exec.h` declined a + partial form on the ground that it would report the environment's + granularity through the shape of the interface, and said in the same header + that a loader belongs above openkal and needs only this interface. The two + statements could not both hold: an image's instructions and data lie at + fixed distances in one region, so a loader must make the first executable + and leave the second writable. Measured by writing the loader (a C library's + `dlopen` above three implementations). The granularity is reported by its own + operation because it is not `kal_memory_granularity`: one environment + reserves in sixty-four kibibytes and protects in four. +29. **What is known of a running program.** **Settled in 0.17.** + `kal_process_self`, `kal_process_open_id` and `kal_process_info`, with + `kal_program_info` in the shape of `kal_node_info`. A language server is + told its client's identifier and must notice when the client is gone; a + lease names the program that holds it; a bound on the processor time a + program may use is checked against what it has used. None could be + answered above this interface, and the consumer that needed all three kept + seven declarations of one environment's functions and read another's + process table by name. The identifier is opaque and is given again, so it is + reported with the moment the program started, and the pair is what names + one program. A program the caller did not start can be asked about and not + waited for or ended: the second would end a program by a number that may + since name another. +30. **The image a program was started from, and the system it runs upon.** + **Settled in 0.17.** `kal_env_image` and `kal_env_system`, and + `kal_env_loader` for an image (clause 12): what starts the same program + again is a loader and an image there, and the loader is named nowhere else. The first is a + fact every environment that starts named images tells the program and that + the program cannot recover otherwise; argument zero is the starter's choice + (clause 7.6). The second exists for clause 12: a program built once and run + upon several systems must still follow each system's conventions --- where + a user's configuration is kept, the suffix of a program, the separator of a + list of directories --- and those are the system's decisions rather than + resources of this interface. **It is restricted to conventions in its + declaration**, because a name of a class of environment used to decide what + an implementation can do is the error clause 3.3 records; capability words + answer that, about the implementation in front of the program. +31. **Whether written control sequences are obeyed.** **Settled in 0.17.** + `KAL_TERM_SEQUENCES`, a mode position of the stream a program writes. A + program that colours its output could not tell an environment that obeys + ECMA-48 from one that prints it, and one implementation wrote bytes to its + console that the console then displayed. ECMA-48 is a standard of its own + and not the shape of an environment; one environment of the three obeys it + only once asked, and the position is how a program asks. +32. **The direction of a channel, and the form of an environment entry.** + **Stated in 0.17.** `kal_process_channel` named no direction and both + implementations made `mine` the reading end; an environment entry with no + `=` was refused by one implementation and passed on by another. Both are + now stated in `process.h`, and the second is refused by all. +33. **One implementation in one process.** **Stated in 0.17**, clause 10.1, + with the shared form that lets a program of several artifacts satisfy it. + It was assumed rather than stated, and became a question when a loader above + this interface began starting images that themselves call `kal_*` + (clause 12). +34. **Images.** **Specified in 0.17**, clause 12. Until this version a program + above openkal was portable in its source: the same source, built once per + environment. The clause that follows makes it portable in its bytes for one + architecture, and states why that needed no operation that did not already + exist except the three of entries 28 and 30. + +35. **A directory supplied after inception.** **Stated in 0.17**, at + `kal_fs_preopen`. The set of supplied directories was fixed when the program + started, and on the environment whose volumes are separate roots a volume + mounted later could not be reached by any name. An environment may now + append such a directory; a position once given keeps its directory and its + name, so nothing a program holds changes meaning. The same entry records the + root of a network's names, which that environment now supplies as a + directory of its own (`//`). + +36. **An image's entry, and an import it can do without.** **Stated in 0.17**, + clause 12.2 and 12.4. An image's entry takes nothing and returns its status, + and the loader ends with that status after the finalisers: the arguments + are `kal_env_arg`'s, as in every other arrangement. A weak import is bound + when it is provided and is zero otherwise, and it does not enter the + requirements note --- so a C library can keep a table of operations it uses + when present (a `dlopen` beside the image, an interface a later version + adds) without making every image refuse to load where they are absent. +37. **Removing a name that is held open.** **Stated in 0.17**, clause 7.14, + answered per volume by `KAL_FS_PROP_REMOVE_OPEN`. The specification said + what a removal removes and not when; two environments removed the name at + once and the third when the last handle closed, and a tree holding an open + file was removable on two of them. The immediate form is native on every + volume that records it, so it is a statement of what was already the + common behaviour, and a volume without it is answered before the call. +38. **Ending a read that waits.** **Stated in 0.17**, at `kal_net_shutdown`. + Ending the read direction of a connection ends a read waiting in it, with + the end of input. Two environments did so and the third left the read + pending, so a program that ended a connection from another context waited + for its reader there alone; the release of the connection is not the + remedy, since a handle in use is not to be released (clause 7.2). + +## 12. Images + +A program written against this specification is built for an environment: its +implementation is linked into it (clause 4.4, **S**), or loaded beside it +(**L**). This clause specifies a third arrangement, in which the program is an +**image** --- one artifact per processor architecture --- and a **loader** +running in the environment holds the implementation, maps the image, binds the +names it imports to that implementation, and transfers control to it. The same +bytes then run in every environment of that architecture for which a loader +exists. + +It is an arrangement and not a requirement: an implementation is not required to +be reachable from an image, and a program built for its environment is +unaffected by this clause. The format is called the **ok format** (openkal +image format); an executable image is conventionally named `*.oke`, and an image +that is loaded by another `*.okso`. + +### 12.1 What makes it possible, and what does not + +**An environment's kernel executes only its own format** --- each recognises a +different header at the start of the file --- **and a file cannot be all of +them.** What is above the kernel is a loader in every environment, and a loader +can be written for any format. An image is therefore never started by the +environment directly. It is started by a loader that is itself a program of the +environment, or by a program of the environment that carries the image within +it and starts it the same way. + +Nothing else is environment-specific. An image calls only `kal_*`, so it needs +from the loader only what this specification already declares, together with +three operations a loader needs and a program could not otherwise reach +(clause 11, entries 28 and 30). + +### 12.2 The image + +| Item | Requirement | +| --- | --- | +| container | ELF, position-independent (`ET_DYN`), little-endian, of the architecture's class; no text relocation | +| alignment | every loadable segment aligned to at least 65536 bytes, which is the largest protection or reservation granularity among the environments this specification targets; a loader publishes each executable segment with `kal_exec_publish_part` | +| relocations | only the architecture's relative, absolute, global-data and procedure-linkage relocations, and the packed relative form; a loader refuses any other | +| imports | only names this specification declares, and names other images export; a name of a C library or of an environment is refused. A weak import is optional: it is bound when the implementation or an image the image needs provides it, and is zero otherwise | +| entry | an executable image's ELF entry is a function of no parameters returning `int`, in the image's convention; its value is the image's status. An image that another loads has no entry | +| calling convention | the architecture's, fixed for every environment: the System V convention on x86-64, the procedure call standard on AArch64; an implementation whose environment uses another presents entries in this one to the image (12.3) | +| platform register | an AArch64 image does not use `x18`, which two environments reserve | +| stack | a frame larger than a page is touched page by page, in order: one environment commits a context's stack as its guard page is touched, and a frame that steps over the guard ends the program | +| thread-local storage | through the emulated form, in which a variable is found by a function the image itself supplies; an image does not address the thread pointer, which one environment does not let a program set and another does not let it change | +| unwinding | exceptions and unwinding do not cross from an image into a `kal_*` operation; the interface is a C interface (clause 1) | +| note | an `OPENKAL` note (12.4) | + +The thread-local storage of an image is kept by the image's own C library, +above `kal_task_current`. `kal_task_current` names a context only while it runs, +so the library releases what it kept for a context when a context it started +ends; a context the library did not start --- the first, and any an embedding +program started --- keeps its storage for the life of the image. No operation is +added for it. The cost is a function call where a register would have been read; +it is stated rather than hidden, and a program for which it matters is built for +its environment. + +### 12.3 The loader's side + +A loader holds the process's one implementation (clause 10.1). It maps the image +into a region from `kal_exec_alloc`, applies the relocations, binds each imported +name to that implementation's entry for it, publishes the executable segments, +runs the image's initialisers and transfers control to its entry; when the entry +returns, the loader runs the image's finalisers and ends with the status the entry +returned (`kal_exit`). The image's +`kal_env_*` operations answer for the image: arguments as the loader received +them after its own, `kal_env_image` the image's name, and `kal_env_loader` the +loader's own, so that the program can be started again (a loader, then an +image). + +Where an environment's own convention differs from the image's --- on x86-64, +one environment passes arguments in other registers --- the loader presents each +entry in the image's convention, and a function the image passes to the +implementation (`kal_task_start`'s entry, `kal_space_start`'s) is entered in the +image's convention. The presentation is mechanical and is generated from +`SURFACE.txt` and the declarations, so that it cannot omit a name. It is the +loader's and not the implementation's: an implementation is written in its +environment's convention, as every program of that environment is, and a +convention of images is a fact about images. + +### 12.4 The note + +An image carries notes whose owner is `OPENKAL`, in a section `.note.openkal`: + +| type | descriptor | +| --- | --- | +| 1, requirements | `kal_u32` major and `kal_u32` minor version of this clause (1 and 0), then a `kal_u64` of `KAL_IFACE_*` positions: **the interfaces the image requires**, derived from the names it imports other than weakly | +| 2, signature | 32 bytes of an Ed25519 public key, then 64 bytes of the signature, over the whole file with these 96 bytes read as zero | + +The first is required and the second is not. A loader compares the required interfaces with `kal_interfaces` and +refuses to load an image that requires one the implementation does not provide, +naming the interface. That is clause 6.1 in this arrangement: the loader binds +at load what a linker binds at link, so it reports at load what a linker reports +at link, and no operation that reports a lack of support at run time is +introduced. A loader refuses an image of a higher major version of this clause. +Whether a signature is required, and whose, is the loader's policy. + +### 12.5 What is not specified + +The loader, the format's tools, and the C library and C++ run-time inside an +image are not specified: they are programs above this interface, and an image may +carry any of them, or none. The reference loader and tools are published beside +this specification (`mcpplibs/openkal-image`) and are not normative. diff --git a/SURFACE.txt b/SURFACE.txt index 2fb6e16..4261a5e 100644 --- a/SURFACE.txt +++ b/SURFACE.txt @@ -1,4 +1,4 @@ -# The C surface of openkal 0.16, one name per line. +# The C surface of openkal 0.17, one name per line. # # This file is normative and is the single source consulted by clause 9. A # conforming implementation exports the names of the interfaces it provides @@ -37,6 +37,9 @@ kal_memory_granularity # openkal.env kal_env_arg kal_env_arg_count +kal_env_image +kal_env_loader +kal_env_system kal_env_var kal_env_var_at kal_env_var_count @@ -69,6 +72,7 @@ kal_fs_preopen_count kal_fs_props kal_fs_remove kal_fs_rename +kal_fs_rename_flags kal_fs_seek kal_fs_set_executable_at kal_fs_set_modified @@ -80,12 +84,16 @@ kal_fs_unlock kal_process_channel kal_process_channel_close kal_process_close +kal_process_end_as_requested +kal_process_info kal_process_job_close kal_process_job_enter kal_process_job_terminate +kal_process_open_id kal_process_props -kal_process_stop_requested +kal_process_self kal_process_spawn +kal_process_stop_requested kal_process_terminate kal_process_wait # openkal.task @@ -102,8 +110,10 @@ kal_task_yield # openkal.exec kal_exec_alloc kal_exec_free +kal_exec_granularity kal_exec_props kal_exec_publish +kal_exec_publish_part # openkal.terminal kal_terminal_get_mode kal_terminal_props @@ -114,7 +124,9 @@ kal_net_accept kal_net_close kal_net_close_listener kal_net_connect +kal_net_connect_at kal_net_listen +kal_net_listen_at kal_net_listener_local kal_net_local kal_net_peer diff --git a/conformance/src/declarations.c b/conformance/src/declarations.c index 305aae3..5758524 100644 --- a/conformance/src/declarations.c +++ b/conformance/src/declarations.c @@ -37,13 +37,18 @@ void okc_declarations_c(void) (void)sizeof(&kal_datagram_send_to); (void)sizeof(&kal_env_arg); (void)sizeof(&kal_env_arg_count); + (void)sizeof(&kal_env_image); + (void)sizeof(&kal_env_loader); + (void)sizeof(&kal_env_system); (void)sizeof(&kal_env_var); (void)sizeof(&kal_env_var_at); (void)sizeof(&kal_env_var_count); (void)sizeof(&kal_exec_alloc); (void)sizeof(&kal_exec_free); + (void)sizeof(&kal_exec_granularity); (void)sizeof(&kal_exec_props); (void)sizeof(&kal_exec_publish); + (void)sizeof(&kal_exec_publish_part); (void)sizeof(&kal_exit); (void)sizeof(&kal_free); (void)sizeof(&kal_fs_capacity); @@ -65,6 +70,7 @@ void okc_declarations_c(void) (void)sizeof(&kal_fs_props); (void)sizeof(&kal_fs_remove); (void)sizeof(&kal_fs_rename); + (void)sizeof(&kal_fs_rename_flags); (void)sizeof(&kal_fs_seek); (void)sizeof(&kal_fs_set_executable_at); (void)sizeof(&kal_fs_set_modified); @@ -78,7 +84,9 @@ void okc_declarations_c(void) (void)sizeof(&kal_net_close); (void)sizeof(&kal_net_close_listener); (void)sizeof(&kal_net_connect); + (void)sizeof(&kal_net_connect_at); (void)sizeof(&kal_net_listen); + (void)sizeof(&kal_net_listen_at); (void)sizeof(&kal_net_listener_local); (void)sizeof(&kal_net_local); (void)sizeof(&kal_net_peer); @@ -88,10 +96,14 @@ void okc_declarations_c(void) (void)sizeof(&kal_process_channel); (void)sizeof(&kal_process_channel_close); (void)sizeof(&kal_process_close); + (void)sizeof(&kal_process_end_as_requested); + (void)sizeof(&kal_process_info); (void)sizeof(&kal_process_job_close); (void)sizeof(&kal_process_job_enter); (void)sizeof(&kal_process_job_terminate); + (void)sizeof(&kal_process_open_id); (void)sizeof(&kal_process_props); + (void)sizeof(&kal_process_self); (void)sizeof(&kal_process_spawn); (void)sizeof(&kal_process_stop_requested); (void)sizeof(&kal_process_terminate); diff --git a/conformance/src/sections/child.cpp b/conformance/src/sections/child.cpp index 9618911..270e05e 100644 --- a/conformance/src/sections/child.cpp +++ b/conformance/src/sections/child.cpp @@ -35,6 +35,7 @@ const char* argument_for(errand e) { case errand::wait_to_be_terminated: return "--child-wait"; case errand::report_grants: return "--child-grants"; case errand::report_no_preopens: return "--child-no-preopens"; + case errand::end_as_requested: return "--child-end-as-requested"; case errand::none: break; } return ""; @@ -54,6 +55,7 @@ errand child_errand() { if (same(a, len, argument_for(errand::wait_to_be_terminated))) return errand::wait_to_be_terminated; if (same(a, len, argument_for(errand::report_grants))) return errand::report_grants; if (same(a, len, argument_for(errand::report_no_preopens))) return errand::report_no_preopens; + if (same(a, len, argument_for(errand::end_as_requested))) return errand::end_as_requested; } #endif return errand::none; @@ -132,6 +134,21 @@ after g_after; kal_exit(34); case errand::abort_with_message: kal_abort("openkal-conformance: the message kal_abort was given\n", 52); + case errand::end_as_requested: + // The word is taken over, awaited for two seconds, and the copy then + // ends as the request would have. 98: no request arrived as one --- + // the environment ended this copy outright, or never asked. 99: the + // operation returned although a request had arrived. +#if defined(MCPP_FEATURE_PROCESS) && defined(MCPP_FEATURE_TIME) + if (const kal_u32* word = kal_process_stop_requested()) { + kal_stream_write(kal_stdout(), "ARMED\n", 6); + for (int i = 0; i < 400 && *word == 0; ++i) kal_time_sleep(5u * 1000u * 1000u); + if (*word == 0) kal_exit(98); + kal_process_end_as_requested(); + kal_exit(99); + } +#endif + kal_exit(98); case errand::wait_to_be_terminated: #ifdef MCPP_FEATURE_TIME for (;;) kal_time_sleep(5u * 1000u * 1000u); diff --git a/conformance/src/sections/child.cppm b/conformance/src/sections/child.cppm index 454ccf7..281c319 100644 --- a/conformance/src/sections/child.cppm +++ b/conformance/src/sections/child.cppm @@ -25,6 +25,7 @@ enum class errand { wait_to_be_terminated, // so that a request to terminate has something to reach report_grants, // the directories a starter granted are its preopens report_no_preopens, // ... and a count of zero leaves it none (clause 7.13) + end_as_requested, // takes the request over, then ends as it would have (0.17) }; // The names the starter grants and the copy expects, in order, and the marker diff --git a/conformance/src/sections/env.cpp b/conformance/src/sections/env.cpp index 9cf7f1a..854cfd1 100644 --- a/conformance/src/sections/env.cpp +++ b/conformance/src/sections/env.cpp @@ -31,6 +31,35 @@ void run() { char buf[room]; const kal_uintptr count = kal_env_arg_count(); + // The image and the system (version 0.17): a name, or not found; and the + // length the value has, as every enquiry here reports it. + { + const kal_intptr n = kal_env_image(buf, room); + observe(kind::behaviour, n > 0 || n == -kal_err_not_found, + "the image a program was started from is named, or not found"); + observe(kind::behaviour, kal_env_image(nullptr, 0) == n, + "a capacity of zero reports the image's length without writing"); + } + { + // Built for its environment, a program has no loader to name; an image + // is told the program that runs it. + const kal_intptr n = kal_env_loader(buf, room); + observe(kind::behaviour, n > 0 || n == -kal_err_not_found, + "the loader of an image is named, and a program built for its environment has none"); + observe(kind::behaviour, kal_env_loader(nullptr, 0) == n, + "a capacity of zero reports the loader's length without writing"); + } + { + const kal_intptr n = kal_env_system(buf, room); + bool lower = n > 0 && n < static_cast(room); + for (kal_intptr i = 0; lower && i < n; ++i) + lower = (buf[i] >= 'a' && buf[i] <= 'z') || (buf[i] >= '0' && buf[i] <= '9'); + observe(kind::behaviour, lower || n == -kal_err_not_found, + "the system is a lower-case name, or not found"); + observe(kind::behaviour, kal_env_system(nullptr, 0) == n, + "a capacity of zero reports the system's length without writing"); + } + // Position zero is the name by which the program was started, and an // environment that has no such name reports an empty string rather than // omitting it --- so the count is at least one on every environment. diff --git a/conformance/src/sections/exec.cpp b/conformance/src/sections/exec.cpp index 157a61c..2b98769 100644 --- a/conformance/src/sections/exec.cpp +++ b/conformance/src/sections/exec.cpp @@ -145,6 +145,42 @@ void run() { unobserved(kind::behaviour, "openkal.exec", "the implementation does not report republishing"); } + + // PART OF A REGION (version 0.17). Two granules: the first published and + // executed, the second written AFTER the first was published --- which is + // what a loader does with an image's data beside its instructions. + const kal_uintptr g = kal_exec_granularity(); + observe(kind::behaviour, g != 0 && (g & (g - 1)) == 0, + "the protection granularity is a power of two"); + if (g != 0) { + observe(kind::behaviour, kal_exec_publish_part(nullptr, 0, g) != kal_ok, + "publishing part of no region is refused"); + void* r = kal_exec_alloc(2 * g); + observe(kind::behaviour, r != nullptr, "a region of two granules is reserved"); + if (r != nullptr) { + auto* c = static_cast(r); + for (unsigned long i = 0; i < kReturns42Size; ++i) c[i] = kReturns42[i]; + observe(kind::behaviour, kal_exec_publish_part(r, 0, g / 2 == 0 ? 1 : g / 2) == kal_err_invalid, + "a part that is not a multiple of the granularity is refused"); + observe(kind::behaviour, kal_exec_publish_part(r, 0, g) == kal_ok, + "the first granule is published"); +#if defined(__GNUC__) || defined(__clang__) + __builtin___clear_cache(static_cast(r), static_cast(r) + kReturns42Size); +#endif + c[g] = 7; + c[g + 1] = static_cast(c[g] + 1); + observe(kind::behaviour, c[g + 1] == 8, + "the part not published is still writable"); + fn h = nullptr; + { + auto* d = reinterpret_cast(&h); + const auto* b = reinterpret_cast(&r); + for (unsigned long i = 0; i < sizeof h; ++i) d[i] = b[i]; + } + observe(kind::behaviour, h() == 42, "the published part is executed"); + kal_exec_free(r, 2 * g); + } + } #endif } diff --git a/conformance/src/sections/fs.cpp b/conformance/src/sections/fs.cpp index f4704a4..b5ea93f 100644 --- a/conformance/src/sections/fs.cpp +++ b/conformance/src/sections/fs.cpp @@ -314,6 +314,41 @@ void run() { "the directory is removed"); } + // A name held open is removed at once (clause 7.14, version 0.17), where + // the volume claims it: the name is absent, the file stays usable, and the + // directory that held it is empty. An implementation that deferred the + // removal to the close left a C++ program's `remove_all' unable to remove + // a tree holding its own lock file. + if ((kal_fs_props(here()) & kal::fs::remove_open.bits) != 0) { + const char* kHeld = "okc-conformance-held.tmp"; + kal_fs_remove(here(), kHeld, length(kHeld)); + kal_fs_mkdir(here(), kHeld, length(kHeld)); + kal_dir d{}; + if (kal_fs_open_dir(here(), kHeld, length(kHeld), &d) == kal_ok) { + kal_file f{}; + const auto flags = kal::fs::open::read | kal::fs::open::write | kal::fs::open::create; + if (kal::fs::open_file(d, "h", 1, flags, &f) == kal_ok) { + const int removed = kal_fs_remove(d, "h", 1); + kal_node_info gone = fresh(); + const int ge = kal_fs_info(d, "h", 1, kal::fs::no_resolve, kal::fs::field::all, &gone); + observe(kind::behaviour, removed == kal_ok && ge == kal_ok && gone.kind == kal_node_absent, + "a file removed while it is open loses its name at once"); + observe(kind::behaviour, write_bytes(f, "held", 4), + "and stays usable through the handle that holds it"); + kal_fs_close_dir(d); + d = {}; + observe(kind::behaviour, kal_fs_remove(here(), kHeld, length(kHeld)) == kal_ok, + "and the directory that held it is removed"); + kal_fs_close_file(f); + } + if (d.h) kal_fs_close_dir(d); + } + kal_fs_remove(here(), kHeld, length(kHeld)); + } else { + unobserved(kind::behaviour, "a name held open is removed at once", + "the volume does not claim KAL_FS_PROP_REMOVE_OPEN"); + } + // Renaming, and that the old name is then absent. { put_file(kName, "x"); @@ -329,6 +364,41 @@ void run() { kal_fs_remove(here(), kOther, length(kOther)); } + // A rename that does not replace (version 0.17): refused with + // kal_err_exists and changing nothing when the name is there; a rename + // when it is not. Asked of the volume first, as clause 6.2 says. + if ((kal_fs_props(here()) & kal::fs::rename_no_replace.bits) != 0) { + put_file(kName, "first"); + put_file(kOther, "second"); + observe(kind::behaviour, + kal_fs_rename_flags(here(), kName, length(kName), here(), kOther, length(kOther), + kal::fs::no_replace.bits) == kal_err_exists, + "a rename that does not replace is refused when the name is there"); + char buf[16] = {}; + observe(kind::behaviour, + read_file(kOther, buf, sizeof buf) == 6 && buf[0] == 's' + && read_file(kName, buf, sizeof buf) == 5, + "and changes neither name"); + kal_fs_remove(here(), kOther, length(kOther)); + observe(kind::behaviour, + kal_fs_rename_flags(here(), kName, length(kName), here(), kOther, length(kOther), + kal::fs::no_replace.bits) == kal_ok, + "a rename that does not replace renames when the name is free"); + kal_fs_remove(here(), kOther, length(kOther)); + } else { + unobserved(kind::behaviour, "a rename that does not replace", + "the volume does not claim KAL_FS_PROP_RENAME_NO_REPLACE"); + } + { + put_file(kName, "x"); + observe(kind::behaviour, + kal_fs_rename_flags(here(), kName, length(kName), here(), kOther, length(kOther), + (kal_uintptr)1u << 30) == kal_err_invalid, + "a rename flag the specification has not assigned is refused"); + kal_fs_remove(here(), kName, length(kName)); + kal_fs_remove(here(), kOther, length(kOther)); + } + // --- what an enquiry answers, and what it does not -------------------- // // THREE MECHANISMS, THREE OBSERVATIONS. The size the caller states, the @@ -462,7 +532,42 @@ void run() { observe(kind::behaviour, sized == static_cast(length(kName)), "a capacity of zero reports the content's length without writing"); - kal_fs_remove(here(), kLink, length(kLink)); + // THE NAME IS RENAMED AND REMOVED, NOT WHAT IT LEADS TO. An + // implementation that followed the link removed the file it named + // and left the link, and could not remove a link to a directory + // at all; a C++ program's `remove_all' then failed on the one + // environment that did so. + const char* kMoved = "okc-conformance-link-moved.tmp"; + kal_fs_remove(here(), kMoved, length(kMoved)); + const int moved = kal_fs_rename(here(), kLink, length(kLink), here(), kMoved, length(kMoved)); + kal_node_info after = fresh(); + kal_fs_info(here(), kMoved, length(kMoved), kal::fs::no_resolve, kal::fs::field::all, &after); + observe(kind::behaviour, moved == kal_ok && after.kind == kal_node_link, + "renaming a link renames the link"); + const int removed = kal_fs_remove(here(), kMoved, length(kMoved)); + kal_node_info target_after = fresh(); + const int te = kal_fs_info(here(), kName, length(kName), 0, kal::fs::field::all, &target_after); + observe(kind::behaviour, removed == kal_ok && te == kal_ok && target_after.kind == kal_node_file, + "removing a link removes the link and leaves the file it named"); + + // And a link to a directory, which one environment makes as a + // different kind of node and which is removed as its name too. + const char* kDir = "okc-conformance-linked-dir.tmp"; + const char* kDirLink = "okc-conformance-dir-link.tmp"; + kal_fs_remove(here(), kDirLink, length(kDirLink)); + kal_fs_mkdir(here(), kDir, length(kDir)); + { + if (kal_fs_link_create(here(), kDirLink, length(kDirLink), kDir, length(kDir), + kal::fs::link_directory) == kal_ok) { + const int gone = kal_fs_remove(here(), kDirLink, length(kDirLink)); + kal_node_info dir_after = fresh(); + const int de = kal_fs_info(here(), kDir, length(kDir), 0, kal::fs::field::all, &dir_after); + observe(kind::behaviour, gone == kal_ok && de == kal_ok && dir_after.kind == kal_node_directory, + "removing a link to a directory removes the link and leaves the directory"); + } + kal_fs_remove(here(), kDir, length(kDir)); + } + kal_fs_remove(here(), kName, length(kName)); } else { unobserved(kind::behaviour, @@ -658,7 +763,8 @@ void run() { | kal::fs::modified_time | kal::fs::atomic_rename | kal::fs::make_links | kal::fs::locks | kal::fs::capacity - | kal::fs::executable).bits; + | kal::fs::executable | kal::fs::rename_no_replace + | kal::fs::local_endpoint | kal::fs::remove_open).bits; observe(kind::abi, (kal_fs_props(here()) & ~assigned) == 0, "the capability word contains no position the specification has not assigned"); diff --git a/conformance/src/sections/net.cpp b/conformance/src/sections/net.cpp index 6e7d0a3..81d33a4 100644 --- a/conformance/src/sections/net.cpp +++ b/conformance/src/sections/net.cpp @@ -5,8 +5,10 @@ import openkal.stream; import openkal.net; import openkal.task; import openkal.time; +import openkal.fs; // a rendezvous at a name is in a directory (0.17) import okc.report; import okc.spec; +import okc.atomic; namespace okc::net { @@ -44,6 +46,20 @@ void releases_the_reader(void*) { const char x = 'r'; kal_stream_write(g_releaser_stream, &x, 1); } + +// The context of the observation that ending the read direction ends a read +// waiting in it: it reads once, and records what the read answered. +kal_stream g_waiting_stream{}; +volatile kal_intptr g_waiting_answer = -1; +volatile kal_u32 g_waiting_done = 0; + +void waits_for_the_end(void*) { + char b[8]; + g_waiting_answer = kal_stream_read(g_waiting_stream, b, sizeof b); + okc::store_release(&g_waiting_done, 1u); + kal_uintptr woken = 0; + kal_task_wake(const_cast(&g_waiting_done), 1, &woken); +} #endif } // namespace @@ -193,6 +209,57 @@ void run() { } } + // ENDING THE READ DIRECTION ENDS A READ WAITING IN IT (0.17). A program ends + // a connection that another of its contexts reads from by ending the + // direction; the waiting read reports the end of input, as every later + // read does. One system left the read pending, and a program waited two + // minutes for its reader there alone. On a connection of its own, whose + // peer has sent nothing and ended nothing, so the read does wait; bounded: + // after two seconds both ends are released whatever the implementation did. +#if defined(MCPP_FEATURE_TASK) && defined(MCPP_FEATURE_TIME) + if (kal::net::has(kal::net::halfclose)) { + kal_net_conn near{}, far{}; + if (kal_net_connect(&to, &near) == kal_ok && kal_net_accept(listener, &far) == kal_ok) { + g_waiting_stream = kal_net_stream(far); + kal_task waiter{}; + if (kal_task_start(waits_for_the_end, nullptr, &waiter) == kal_ok) { + kal_time_sleep(200ull * 1000ull * 1000ull); + const bool waited = okc::load_acquire(&g_waiting_done) == 0; + const kal_duration t0 = kal_time_monotonic(); + const int rc = kal::net::shutdown(far, kal::net::shut::read); + while (okc::load_acquire(&g_waiting_done) == 0 && kal_time_monotonic() - t0 < kReleaseAfterNs) + kal_task_wait(const_cast(&g_waiting_done), 0, 50ull * 1000ull * 1000ull); + const bool ended = okc::load_acquire(&g_waiting_done) != 0; + const kal_duration took = kal_time_monotonic() - t0; + if (!ended) kal::net::shutdown(near, kal::net::shut::both); // the peer's end releases it + kal_task_join(waiter); + const bool held = waited && rc == kal_ok && ended && g_waiting_answer == 0; + observe(kind::behaviour, held, + "ending the read direction ends a read waiting in it with the end of input"); + if (!held) { + put(waited ? " the read waited; " : " the read did not wait; "); + put("the shutdown answered "); put_signed(rc); + put(ended ? ", the read ended after " : ", the read was still waiting after "); + put_signed(static_cast(took / 1000000ull)); put(" ms"); + if (ended) { put(" and answered "); put_signed(static_cast(g_waiting_answer)); } + put("\n"); + } + char b[4]; + observe(kind::behaviour, kal_stream_read(kal_net_stream(far), b, sizeof b) == 0, + "and a later read reports the end of input too"); + } else { + unobserved(kind::behaviour, "ending the read direction ends a read waiting in it", + "an execution context could not be started"); + } + } else { + unobserved(kind::behaviour, "ending the read direction ends a read waiting in it", + "a second connection could not be made"); + } + if (near.h) kal_net_close(near); + if (far.h) kal_net_close(far); + } +#endif + kal_net_close(server); kal_net_close(client); kal_net_close_listener(listener); @@ -211,6 +278,51 @@ void run() { "an endpoint of unknown length is refused, not misread"); if (rc == kal_ok) kal_net_close(c); } + + // A RENDEZVOUS AT A NAME (version 0.17), where the working directory's + // volume claims it. The listener does not remove its name, so the name is + // removed before and after. +#ifdef MCPP_FEATURE_FS + { + const kal_dir here = kal::fs::working(); + const char name[] = "okc-rendezvous"; + const kal_uintptr len = sizeof name - 1; + if ((kal_fs_props(here) & kal::fs::local_endpoint.bits) != 0) { + kal_fs_remove(here, name, len); + kal_net_listener l{}; + const int lr = kal_net_listen_at(here, name, len, &l); + observe(kind::behaviour, lr == kal_ok, "a listener is opened at a name"); + if (lr == kal_ok) { + kal_net_listener second{}; + observe(kind::behaviour, kal_net_listen_at(here, name, len, &second) == kal_err_exists, + "a second listener at the same name is refused with kal_err_exists"); + kal_net_conn c{}, s{}; + const int cr = kal_net_connect_at(here, name, len, &c); + observe(kind::behaviour, cr == kal_ok, "a connection is made at the name"); + if (cr == kal_ok && kal_net_accept(l, &s) == kal_ok) { + const kal_stream cs = kal_net_stream(c), sst = kal_net_stream(s); + char b[4] = {}; + const bool moved = kal_stream_write(cs, "okc", 3) == 3 + && kal_stream_read(sst, b, 3) == 3 && b[0] == 'o' && b[2] == 'c'; + observe(kind::behaviour, moved, "bytes cross a connection made at a name"); + kal_endpoint ep{}; + observe(kind::behaviour, kal_net_peer(s, &ep) == kal_err_not_supported, + "a connection made at a name has no address to report"); + kal_net_close(s); + } + if (cr == kal_ok) kal_net_close(c); + kal_net_close_listener(l); + } + kal_fs_remove(here, name, len); + kal_net_conn none{}; + observe(kind::behaviour, kal_net_connect_at(here, name, len, &none) == kal_err_not_found, + "a connection at a name nothing listens at is kal_err_not_found"); + } else { + unobserved(kind::behaviour, "a rendezvous at a name", + "the working directory does not claim KAL_FS_PROP_LOCAL_ENDPOINT"); + } + } +#endif #endif } diff --git a/conformance/src/sections/process.cpp b/conformance/src/sections/process.cpp index 23e0e28..88e9fd8 100644 --- a/conformance/src/sections/process.cpp +++ b/conformance/src/sections/process.cpp @@ -90,6 +90,90 @@ void run() { "the implementation does not claim prop_terminate"); } + // ENDING AS THE REQUEST WOULD HAVE (version 0.17). With no request there is + // nothing to reproduce, and the operation returns. + observe(kind::behaviour, kal_process_end_as_requested() == kal_err_invalid, + "ending as requested, with no request, returns kal_err_invalid"); +#ifdef MCPP_FEATURE_TIME + if (kal::process::has(kal::process::stop_requested) && kal::process::has(kal::process::terminate)) { + kal_process child{}; + if (start_copy_running("openkal-conformance-child", + argument_for(errand::end_as_requested), child)) { + kal_time_sleep(300u * 1000u * 1000u); + kal_process_terminate(child); + int status = 0, terminated = 0; + kal_process_wait(child, &status, &terminated); + if (terminated == 0 && status == 98) + unobserved(kind::behaviour, "a program ends as the request would have ended it", + "a request to terminate did not arrive as a request on this environment"); + else + observe(kind::behaviour, terminated != 0 && status != 99, + "a program that took the request over ends as the request would have ended it"); + kal_process_close(child); + } + } +#endif + + // WHAT IS KNOWN OF A RUNNING PROGRAM (version 0.17). + { + kal_process self{}; + const int e = kal_process_self(&self); + observe(kind::behaviour, e == kal_ok, "a program has a handle to itself"); + if (e == kal_ok) { + kal_program_info info{}; + info.self_size = sizeof info; + observe(kind::behaviour, kal_process_info(self, kal::process::info::all, &info) == kal_ok, + "a program is asked about itself"); + observe(kind::behaviour, (info.present & ~kal::process::info::all) == 0, + "no field the specification has not assigned is reported"); + observe(kind::behaviour, (info.present & kal::process::info::ended) == 0 || info.ended == 0, + "a program asking about itself has not ended"); + int status = 0, terminated = 0; + observe(kind::behaviour, kal_process_wait(self, &status, &terminated) == kal_err_invalid, + "a program does not wait for itself"); + kal_program_info tiny{}; + tiny.self_size = 4; + observe(kind::behaviour, kal_process_info(self, kal::process::info::all, &tiny) == kal_err_invalid, + "a structure too small for its fields is refused"); + if (kal::process::has(kal::process::open_id) && (info.present & kal::process::info::id) != 0) { + kal_process again{}; + const int o = kal_process_open_id(info.id, &again); + observe(kind::behaviour, o == kal_ok, "a program is found by its identifier"); + if (o == kal_ok) { + kal_program_info other{}; + other.self_size = sizeof other; + kal_process_info(again, kal::process::info::all, &other); + observe(kind::behaviour, other.id == info.id + && ((info.present & other.present & kal::process::info::started) == 0 + || other.started_ns == info.started_ns), + "and is the same program, started at the same moment"); + observe(kind::behaviour, kal_process_terminate(again) == kal_err_not_supported, + "a program found by its identifier is not the caller's to end"); + kal_process_close(again); + } + } else { + unobserved(kind::behaviour, "a program is found by its identifier", + "the implementation does not claim KAL_PROCESS_PROP_OPEN_ID"); + } + kal_process_close(self); + } + } + { + int status = 0, terminated = 0; + kal_process child{}; +#ifdef MCPP_FEATURE_TIME + if (start_copy_running("openkal-conformance-child", argument_for(errand::exit_with_33), child)) { + kal_process_wait(child, &status, &terminated); + kal_program_info info{}; + info.self_size = sizeof info; + const int e = kal_process_info(child, kal::process::info::ended, &info); + observe(kind::behaviour, e == kal_ok && ((info.present & kal::process::info::ended) == 0 || info.ended != 0), + "a started program that was awaited is reported as ended"); + kal_process_close(child); + } +#endif + } + // A START THAT DID NOT HAPPEN IS REPORTED, AND BY ITS REASON. Version 0.13. // // A name that does not exist needs no file to be created, so this is @@ -212,7 +296,8 @@ void run() { | kal::process::grant_dir | kal::process::bound_lifetime | kal::process::job - | kal::process::stop_requested).bits; + | kal::process::stop_requested + | kal::process::open_id).bits; observe(kind::abi, sizeof(kal_preopen) == 3 * sizeof(kal_uintptr), "a directory grant occupies three machine words"); observe(kind::abi, sizeof(kal_spawn) == 6 * sizeof(kal_uintptr), diff --git a/include/openkal/env.h b/include/openkal/env.h index a0431b5..957ddb4 100644 --- a/include/openkal/env.h +++ b/include/openkal/env.h @@ -54,6 +54,55 @@ kal_intptr kal_env_var(const char* name, kal_uintptr name_len, kal_uintptr kal_env_var_count(void); kal_intptr kal_env_var_at(kal_uintptr index, char* out, kal_uintptr cap); +/* The name of the image this program was started from, as the environment + * states it to the program: the name the environment resolved when it started + * the program, not the name in argument zero, which the starter chooses (clause + * 7.6). Version 0.17. + * + * Every environment that starts programs from named images states it --- the + * name the kernel resolved, the path the loader was given, the name recorded in + * the process's own parameters --- and a program cannot recover it otherwise: + * argument zero is whatever its starter wrote, and a search of the directories + * a starter searched is a guess about a decision already made. A program that + * finds its own data beside itself needs it. + * + * The name is in the environment's own spelling and is meaningful to the + * environment rather than to this interface: it is not resolved against a + * preopen, and a program uses it as it uses any name it was given from outside. + * An environment that starts programs from no named image reports + * kal_err_not_found, negated. Where a program is an image started by a loader + * above openkal (clause 12), the loader's implementation states the image's + * name, not its own. */ +kal_intptr kal_env_image(char* out, kal_uintptr cap); + +/* The name of the program that loaded this image and runs it, where this + * program is an image (clause 12): the loader's own image, in the environment's + * spelling. With the image's name (kal_env_image) it is what starts the same + * program again --- a loader, then an image --- as a program's own image name + * alone does when it is built for its environment. Version 0.17. + * + * A program built for its environment was loaded by nothing this interface + * names, and its implementation reports kal_err_not_found, negated. */ +kal_intptr kal_env_loader(char* out, kal_uintptr cap); + +/* The name of the environment's system: `linux', `macos', `windows', and + * further names as implementations are written, lower case, from an open set. + * Version 0.17. + * + * IT EXISTS FOR CONVENTIONS AND FOR NOTHING ELSE. The conventions of a system + * --- where it keeps a user's configuration, how it separates the entries of a + * list of directories, the suffix of a program it starts --- are decided by the + * system and are not resources of this interface, and a program built once and + * run upon several systems (clause 12) learns which system's conventions to + * follow here. A program shall not use the name to change how it calls an + * operation of this specification, or to infer what an operation can do: what + * an implementation can do is answered by its capability words (clause 6.2), + * which are true of the implementation in front of the program, and a name is + * true of a class of environment, which is the thing clause 3.3 records a name + * cannot be. An implementation whose environment has no such name reports + * kal_err_not_found, negated. */ +kal_intptr kal_env_system(char* out, kal_uintptr cap); + #ifdef __cplusplus } #endif diff --git a/include/openkal/exec.h b/include/openkal/exec.h index db599b6..811fa25 100644 --- a/include/openkal/exec.h +++ b/include/openkal/exec.h @@ -42,12 +42,38 @@ void* kal_exec_alloc(kal_uintptr size); /* Makes a region obtained from kal_exec_alloc executable and no longer * writable. Returns kal_ok, or an error if the environment refuses. * - * The whole of the region is published; there is no partial form. An - * implementation that had to publish in parts would be reporting its page - * granularity through the shape of the interface, and a caller cannot act upon - * that. */ + * The whole of the region is published. Version 0.17 adds the partial form + * below, and clause 11 entry 28 records why the reason this paragraph used to + * give against it --- that a partial form reports the environment's granularity + * through the shape of the interface --- did not survive a loader. */ int kal_exec_publish(void* p, kal_uintptr size); +/* Makes PART of a region obtained from kal_exec_alloc executable and no longer + * writable, and leaves the rest as it was. Version 0.17. + * + * A loader needs it, and this header has said since the interface was added + * that a loader belongs above openkal and needs only this interface. An image's + * instructions and its data lie at fixed distances from each other in one + * region --- the instructions address the data relative to themselves --- so + * the instructions must become executable while the data stays writable, and a + * whole-region publication cannot express that. + * + * `offset' and `size' are measured from the start of the region and are + * multiples of kal_exec_granularity; a part that is not is kal_err_invalid, and + * nothing is published. A part may be published once; parts that do not + * overlap may be published in any order. */ +int kal_exec_publish_part(void* region, kal_uintptr offset, kal_uintptr size); + +/* The quantum in which kal_exec_publish_part publishes: the environment's + * protection granularity. Version 0.17. + * + * It is not kal_memory_granularity, which is the quantum of reservation: on one + * environment that is sixty-four kibibytes while protection is per four, and an + * image laid out for the second would be refused by the first. An image that + * aligns its parts to the largest granularity among the environments it will + * run upon (clause 12) is published unchanged on each. */ +kal_uintptr kal_exec_granularity(void); + /* Releases a region obtained from kal_exec_alloc, published or not. The size is * the one passed to the reservation. */ void kal_exec_free(void* p, kal_uintptr size); diff --git a/include/openkal/fs.h b/include/openkal/fs.h index e8ef932..53be136 100644 --- a/include/openkal/fs.h +++ b/include/openkal/fs.h @@ -164,6 +164,15 @@ struct kal_node_info { * be started, and * kal_fs_set_executable_at * is answered here. 0.13 */ +#define KAL_FS_PROP_RENAME_NO_REPLACE ((kal_uintptr)1u << 8) /* KAL_RENAME_NO_REPLACE + * is answered here. 0.17 */ +#define KAL_FS_PROP_LOCAL_ENDPOINT ((kal_uintptr)1u << 9) /* a name here may be a + * rendezvous of + * openkal.net. 0.17 */ +#define KAL_FS_PROP_REMOVE_OPEN ((kal_uintptr)1u << 10) /* a name is removed at + * once although a file + * of that name is open. + * 0.17 */ /* Positions in the flags word of kal_fs_open. */ #define KAL_OPEN_READ ((kal_uintptr)1u << 0) @@ -192,6 +201,14 @@ extern "C" { * names are the environment's; this specification requires only that they be * distinct. * + * A DIRECTORY MAY BE APPENDED WHILE THE PROGRAM RUNS, AND A POSITION ONCE + * GIVEN KEEPS ITS DIRECTORY AND ITS NAME. Version 0.17. An environment that + * gains a directory it would have supplied at inception --- a volume mounted + * since --- appends it, so a program that asks again finds it, and a program + * that holds a position is not moved. Before 0.17 the set was fixed at + * inception, and a name on a volume mounted after the program started could + * not be reached at all. + * * THE NAME IS COPIED, AND `name_len' REPORTS THE LENGTH IT HAS. An operation * that produces a RESOURCE returns `int' and writes the resource; the length of * a name it also produces goes in an out-parameter, because the return is @@ -305,10 +322,43 @@ int kal_fs_info(struct kal_dir base, const char* name, kal_uintptr len, * private is one whose PREOPEN was not handed to anybody else, and that is the * decision of the party that starts a program rather than of the program. */ int kal_fs_mkdir (struct kal_dir base, const char* name, kal_uintptr len); +/* Removing a name: a file, an empty directory, or a link, which is removed + * itself and not what it leads to. + * + * AT ONCE, WHERE THE VOLUME CLAIMS KAL_FS_PROP_REMOVE_OPEN, although a file of + * that name is open: the name is absent when the call returns, the open file + * stays usable until it is closed, and the directory that held it may be + * removed. Clause 7.14. On a volume that does not claim the position a held + * file's name may remain until its last handle is closed. */ int kal_fs_remove(struct kal_dir base, const char* name, kal_uintptr len); int kal_fs_rename(struct kal_dir from, const char* a, kal_uintptr alen, struct kal_dir to, const char* b, kal_uintptr blen); +/* Positions in the flags of kal_fs_rename_flags. */ +#define KAL_RENAME_NO_REPLACE ((kal_uintptr)1u << 0) + +/* kal_fs_rename with a flags word. Version 0.17. + * + * KAL_RENAME_NO_REPLACE: the rename fails with kal_err_exists, and changes + * nothing, when `b' already names a node; the test and the rename are one act. + * It cannot be composed above the interface: asking whether `b' exists and then + * renaming leaves a moment in which another program creates `b' and loses it, + * and opening `b' exclusively and then renaming over it replaces what was just + * created. A program that publishes a file under a name only if nobody else has + * --- an installer, a cache that several programs fill --- needs exactly this. + * + * Whether a volume can do it is a property of its format, so kal_fs_props + * answers it with KAL_FS_PROP_RENAME_NO_REPLACE on the directory `to'; a + * request upon a volume that does not claim it is kal_err_not_supported and + * renames nothing. Every environment this specification targets has the + * operation natively where the volume does (`renameat2(RENAME_NOREPLACE)', + * `renamex_np(RENAME_EXCL)', a rename that does not replace). A flag this + * version has not assigned is kal_err_invalid. A flags word of zero is + * kal_fs_rename. Clause 11 entry 25. */ +int kal_fs_rename_flags(struct kal_dir from, const char* a, kal_uintptr alen, + struct kal_dir to, const char* b, kal_uintptr blen, + kal_uintptr flags); + /* Enquiry about an open file. It is not expressible through kal_fs_info: the * name a file was opened by may since have been removed or reused, and a C * library answering fstat from the name would answer about a different file. diff --git a/include/openkal/net.h b/include/openkal/net.h index 7756994..d2632a5 100644 --- a/include/openkal/net.h +++ b/include/openkal/net.h @@ -19,6 +19,8 @@ #ifndef OPENKAL_NET_H #define OPENKAL_NET_H #include "types.h" +/* For `kal_dir', which names where a local endpoint is (version 0.17). */ +#include "fs.h" #include "stream.h" /* Opaque handles occupying one machine word, per clause 7.2. @@ -56,6 +58,44 @@ int kal_net_connect(const struct kal_endpoint* to, struct kal_net_conn* out); int kal_net_listen (const struct kal_endpoint* local, struct kal_net_listener* out); int kal_net_accept (struct kal_net_listener l, struct kal_net_conn* out); +/* A listener and a connection that meet at a NAME in a directory rather than + * at an address and a port. Version 0.17. + * + * Two programs on one machine that speak to each other need a rendezvous that + * only they can reach. An address on the loopback is reachable by every program + * on the machine, and access to it is then a matter for each protocol; a name in + * a directory is reachable by a program that holds the directory, which is the + * confinement this interface already has (clause 7.13) applied to a connection. + * Every environment this specification targets provides it: a socket bound to + * a name in the file system, resolved relative to a directory where the + * environment can and through the directory's own name where it cannot. + * + * The name is one component, as `kal_fs_open' takes. `kal_net_listen_at' + * creates it and fails with kal_err_exists, negated nothing, when the name is + * there --- including a name a listener that has ended left behind, which a + * caller removes with `kal_fs_remove' if it knows the listener has ended; a + * listener does not remove its name when it is closed, because no environment + * does so for a program that ends without closing it and an interface that did + * it sometimes would be one a caller could not rely upon. `kal_net_connect_at' + * reports kal_err_not_found when nothing is listening at the name. + * + * The connection and the listener are the ones this interface already has: + * accepted by `kal_net_accept', read and written through `kal_net_stream', + * half-closed, bounded by `openkal.timeout', released by `kal_net_close'. + * `kal_net_peer' and `kal_net_local' report kal_err_not_supported for them, + * because an endpoint is an address and a port. + * + * Whether a directory can hold such a name is a property of the volume and of + * the environment together, answered by KAL_FS_PROP_LOCAL_ENDPOINT on the + * directory; a call upon one that does not claim it is kal_err_not_supported. + * A name the environment cannot place --- every environment bounds the length of + * such a name, more tightly than the length of a file's --- is kal_err_invalid. + * Clause 11 entry 27. */ +int kal_net_listen_at (struct kal_dir base, const char* name, kal_uintptr len, + struct kal_net_listener* out); +int kal_net_connect_at(struct kal_dir base, const char* name, kal_uintptr len, + struct kal_net_conn* out); + /* A connection is read and written through openkal.stream. The stream remains * valid while the connection is open and is not separately released; the * connection owns it. The wording is `openkal.fs's, because the arrangement is. @@ -84,6 +124,14 @@ int kal_net_local (struct kal_net_conn c, struct kal_endpoint* out); int kal_net_listener_local(struct kal_net_listener l, struct kal_endpoint* out); /* Ends transfer in one direction while the other continues. + * + * ENDING THE READ DIRECTION ENDS A READ WAITING IN IT. Version 0.17. The read + * reports the end of input --- zero --- as every later read in that direction + * does, whichever context it waits in. It is how a program ends a connection + * that another of its contexts reads from, and the release of the connection + * cannot be: the handle is not to be released while an operation uses it + * (clause 7.2). Measured: one implementation left the read pending, and a + * program waited two minutes for its reader on that system alone. * * This is the operation that distinguishes a connection from a file, and it is * why the two are separate interfaces. An implementation that cannot express it diff --git a/include/openkal/process.h b/include/openkal/process.h index cd67047..a5e41bc 100644 --- a/include/openkal/process.h +++ b/include/openkal/process.h @@ -81,6 +81,8 @@ struct kal_preopen { #define KAL_PROCESS_PROP_JOB ((kal_uintptr)1u << 6) /* kal_process_stop_requested answers a word rather than null. Version 0.11. */ #define KAL_PROCESS_PROP_STOP_REQUESTED ((kal_uintptr)1u << 7) +/* kal_process_open_id finds a program by its identifier. Version 0.17. */ +#define KAL_PROCESS_PROP_OPEN_ID ((kal_uintptr)1u << 8) /* HOW A PROGRAM IS STARTED, AND IT IS ONE OPERATION BECAUSE 0.11 STOPPED * MAKING IT A FAMILY. @@ -233,6 +235,13 @@ extern "C" { * Everything that varies between one start and another is in `kal_spawn', which * is why this is one declaration and not a family --- see the comment there. * + * EACH ENVIRONMENT ENTRY IS `NAME=value', and an entry that holds no `=', or + * whose name is empty, is kal_err_invalid and starts nothing. Version 0.17 + * states it: one implementation refused such an entry and another passed it to + * the started program, so a caller could not know which program it was + * starting. `kal_env_var_at' answers a NAME, so a caller copying its own + * environment composes the entry from `kal_env_var_at' and `kal_env_var'. + * * An implementation reports kal_err_not_supported, and starts nothing, when the * request names something it cannot do: a flag whose position it does not claim * in `kal_process_props', or a non-empty `grants' without @@ -267,6 +276,16 @@ int kal_process_spawn(const struct kal_spawn* how, * which openkal otherwise has no way to express, because the standard streams * are borrowed and cannot be manufactured. * + * THE PAIR HAS ONE DIRECTION, AND IT IS STATED BECAUSE IT WAS NOT. Version + * 0.17. `mine' is the end that READS and `theirs' the end that WRITES: placed + * as a started program's `out' or `err', `theirs' carries what the program + * writes to `mine'. A caller that writes to a started program's input asks for + * a second pair and uses it the other way about --- places `mine' at `in' and + * writes to `theirs'. Before 0.17 the text named no direction, both + * implementations of the time made `mine' the reading end, and a caller that + * guessed the other way wrote into an end that refused (measured: a consumer's + * first program to feed a child's input read nothing). + * * BOTH ENDS ARE OWNED, AND THE RELEASE IS HERE. A parent that does not release * `theirs' after the spawn never observes the end of input on `mine' --- the * classic deadlock of this arrangement --- so an interface that hands out these @@ -329,6 +348,19 @@ void kal_process_close(struct kal_process); * distinguish causes may add an enquiry later; this one does not need altering * for that. * + * ONE OWNER FROM THE FIRST CALL. Version 0.17. From the first call of this + * operation in a program, a request to end that reaches the program is + * delivered through the word and through nothing else: the implementation + * replaces whatever its environment would have done upon the request, + * including an arrangement the program itself made earlier by means outside + * this interface. A request that the party which STARTED the program arranged + * not to be delivered at all --- an environment lets a starter say so, and a + * program started in the background is commonly started that way --- remains + * undelivered: taking it over would end a program its starter meant to be + * immune, and the word would read zero for ever, which is true. Before 0.17 + * the text said neither, and two implementations restored delivery of a + * request their starter had declined. + * * AND IT SAYS NOTHING ABOUT AN END THAT CANNOT BE OBSERVED. Where a program is * ended outright --- the un-declinable signal here, the abrupt termination there, * `kal_process_job_terminate' through this interface --- nothing is set, because @@ -336,6 +368,91 @@ void kal_process_close(struct kal_process); * implementable on both kinds of system rather than on one. */ const kal_u32* kal_process_stop_requested(void); +/* ENDS THIS PROGRAM AS THE REQUEST TO END WOULD HAVE ENDED IT, had the program + * not taken the request over. Version 0.17. + * + * A program that has been asked to end, and has finished what it does before + * ending, should end in the way that tells its starter it was asked to: a + * starter that waits observes `terminated' from `kal_process_wait', and a shell + * running a loop stops the loop rather than starting the next program. Ending + * through `kal_exit' with any status tells the starter something else --- + * that the program chose to end --- and nothing above this interface can + * produce the other report, because it is the environment's own report of an + * end it caused. + * + * Returns only if the program has not been asked to end, with + * kal_err_invalid: there is no request whose manner could be reproduced. Where + * the program has been asked to end, it does not return. Every environment that + * provides `kal_process_stop_requested' can do it --- restoring the request's + * default treatment and delivering it again, or ending with the status the + * environment records for such a request. Clause 11 entry 26. */ +int kal_process_end_as_requested(void); + +/* WHAT IS KNOWN OF A PROGRAM THAT HAS BEEN STARTED. Version 0.17. + * + * The shape is `kal_node_info's (clause 5.3): the caller sets `self_size' and + * names in `wanted' the fields it needs; the implementation fills what it can + * and reports in `present' which, and writes no more of the structure than + * `self_size' states. Fields may be appended. + * + * `id' is the identifier the environment gives the program, an opaque value + * that MAY BE GIVEN AGAIN to another program once this one has ended and been + * waited for. `started_ns' is when the program started, on a clock of the + * environment's choosing that does not repeat, so the pair (id, started_ns) + * names one program for as long as the environment runs; a caller that keeps an + * identifier keeps the pair, and compares both. `cpu_ns' is the processor time + * the program has used, in every context it ran. `ended' is non-zero once the + * program has ended. */ +struct kal_program_info { + kal_u32 self_size; + kal_u32 present; + kal_u64 id; + kal_u64 started_ns; + kal_u64 cpu_ns; + kal_u32 ended; + kal_u32 reserved; +}; + +/* Positions in `wanted' and `present'. */ +#define KAL_PINFO_ID ((kal_u32)1u << 0) +#define KAL_PINFO_STARTED ((kal_u32)1u << 1) +#define KAL_PINFO_CPU ((kal_u32)1u << 2) +#define KAL_PINFO_ENDED ((kal_u32)1u << 3) +#define KAL_PINFO_ALL (KAL_PINFO_ID | KAL_PINFO_STARTED | KAL_PINFO_CPU | KAL_PINFO_ENDED) + +/* A handle to THIS program, owned and released by kal_process_close. It may be + * asked about with kal_process_info, and is refused by kal_process_wait and by + * kal_process_terminate with kal_err_invalid: a program does not wait for + * itself, and ending itself is kal_exit. Version 0.17. */ +int kal_process_self(struct kal_process* out); + +/* A handle to the program whose identifier is `id', owned and released by + * kal_process_close. Version 0.17. + * + * A program learns another's identifier from outside this interface --- a + * client states its own in a message, a file names the program that holds it + * --- and wants to know whether that program is still there and what it has + * used. The handle may be asked about with kal_process_info, and is refused by + * kal_process_wait and kal_process_terminate with kal_err_not_supported: a + * program the caller did not start is not the caller's to wait for or to end, + * and on environments where it could be, the caller would be ending a program + * by a number that may since name another (the reason `started_ns' exists). + * + * No such program is kal_err_not_found; one the caller may not ask about is + * kal_err_permission. An implementation that does not claim + * KAL_PROCESS_PROP_OPEN_ID reports kal_err_not_supported. Where the environment + * can hold the program itself --- a handle that keeps its identifier from being + * given again --- the handle does; where it cannot, the implementation records + * `started_ns' at the open and reports `ended' set once the identifier names a + * program that started at another time. */ +int kal_process_open_id(kal_u64 id, struct kal_process* out); + +/* Fills what is known of a program. Any handle this interface issued may be + * asked about: one from kal_process_spawn, kal_process_self, or + * kal_process_open_id. `out->self_size' shall be set by the caller; a size + * smaller than the fields that precede `id' is kal_err_invalid. */ +int kal_process_info(struct kal_process p, kal_u32 wanted, struct kal_program_info* out); + /* Puts THE CALLING program into a unit, on the same terms as `kal_spawn.job': * a word of zero forms a new unit and receives its identity, and a word that * names one joins it. diff --git a/include/openkal/terminal.h b/include/openkal/terminal.h index 5f86f70..3e87a9a 100644 --- a/include/openkal/terminal.h +++ b/include/openkal/terminal.h @@ -36,6 +36,24 @@ #define KAL_TERM_ECHO ((kal_uintptr)1u << 1) /* the environment shows what is typed */ #define KAL_TERM_PASS_CONTROL ((kal_uintptr)1u << 2) /* the environment reserves no keystroke */ #define KAL_TERM_PASS_INPUT ((kal_uintptr)1u << 3) /* the environment alters nothing typed */ +#define KAL_TERM_SEQUENCES ((kal_uintptr)1u << 4) /* control sequences written are obeyed */ + +/* KAL_TERM_SEQUENCES, VERSION 0.17, IS A POSITION OF WHAT IS WRITTEN AND NOT OF + * WHAT IS TYPED, AND IT IS READ AND ESTABLISHED ON THE STREAM A PROGRAM WRITES. + * Set, the environment interprets the control sequences of ECMA-48 written to + * the stream --- colour, emphasis, the position of the cursor, erasure --- and + * does not display them. Clear, it may display them as the bytes they are. + * + * ECMA-48 is an international standard, and not the shape of any one + * environment: every terminal this specification has been implemented on + * interprets it, one of the three only once a program has asked its console to. + * Before this position a program could not tell an environment that obeys the + * sequences from one that prints them, and asked the environment's own switch + * outside this interface or printed escape characters into a log. Zero is the + * weaker claim (clause 6.2): an implementation released before this position + * reports it clear and has thereby said nothing false. An environment whose + * terminals always interpret the sequences reports the position set and ignores + * a request to clear it, which kal_terminal_get_mode then reports. */ /* KAL_TERM_PASS_CONTROL IS A GUARANTEE IN ONE DIRECTION AND A PERMISSION IN THE * OTHER. Set, every keystroke reaches the program, including the ones an diff --git a/include/openkal/version.h b/include/openkal/version.h index 03cf123..cb40c44 100644 --- a/include/openkal/version.h +++ b/include/openkal/version.h @@ -42,7 +42,7 @@ * for the reason tools/check-readme-versions.sh exists --- the one fact a * consumer actually asks for was checked by nobody. */ #define KAL_VERSION_MAJOR 0u -#define KAL_VERSION_MINOR 16u +#define KAL_VERSION_MINOR 17u #define KAL_VERSION_PATCH 0u #define KAL_VERSION_MAKE(major, minor, patch) \ diff --git a/mcpp.toml b/mcpp.toml index 0b700e4..571dcee 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -1,7 +1,7 @@ [package] namespace = "mcpplibs" name = "openkal" -version = "0.16.0" +version = "0.17.0" description = "openkal: a portable kernel ABI specification. This package carries the normative declarations; implementations are separate packages." license = "Apache-2.0" authors = ["mcpplibs"] diff --git a/src/env.cppm b/src/env.cppm index 700a281..b41d1f0 100644 --- a/src/env.cppm +++ b/src/env.cppm @@ -22,6 +22,9 @@ export using ::kal_env_arg; export using ::kal_env_var; export using ::kal_env_var_count; export using ::kal_env_var_at; +export using ::kal_env_image; +export using ::kal_env_loader; +export using ::kal_env_system; export namespace kal::env { diff --git a/src/exec.cppm b/src/exec.cppm index 04a77cc..5a30272 100644 --- a/src/exec.cppm +++ b/src/exec.cppm @@ -24,6 +24,8 @@ export using ::kal_exec_alloc; export using ::kal_exec_publish; export using ::kal_exec_free; export using ::kal_exec_props; +export using ::kal_exec_publish_part; +export using ::kal_exec_granularity; export namespace kal::exec { @@ -47,6 +49,10 @@ inline bool has(props p) { return properties().has(p); } inline void* alloc(kal_uintptr size) { return kal_exec_alloc(size); } inline int publish(void* p, kal_uintptr size) { return kal_exec_publish(p, size); } +inline int publish_part(void* region, kal_uintptr offset, kal_uintptr size) { + return kal_exec_publish_part(region, offset, size); +} +inline kal_uintptr granularity() { return kal_exec_granularity(); } inline void free(void* p, kal_uintptr size) { kal_exec_free(p, size); } } diff --git a/src/fs.cppm b/src/fs.cppm index c0d8d18..c66a87f 100644 --- a/src/fs.cppm +++ b/src/fs.cppm @@ -54,6 +54,7 @@ export using ::kal_fs_capacity; export using ::kal_fs_mkdir; export using ::kal_fs_remove; export using ::kal_fs_rename; +export using ::kal_fs_rename_flags; export using ::kal_fs_list_begin; export using ::kal_fs_list_next; export using ::kal_fs_props; @@ -113,6 +114,19 @@ inline constexpr props make_links {KAL_FS_PROP_MAKE_LINKS}; inline constexpr props locks {KAL_FS_PROP_LOCKS}; inline constexpr props capacity {KAL_FS_PROP_CAPACITY}; inline constexpr props executable {KAL_FS_PROP_EXECUTABLE}; +// A rename that fails rather than replaces (KAL_RENAME_NO_REPLACE) is answered +// on this volume. Version 0.17. +inline constexpr props rename_no_replace{KAL_FS_PROP_RENAME_NO_REPLACE}; +// A name in this directory may be a rendezvous of openkal.net +// (kal_net_listen_at). Version 0.17. +inline constexpr props local_endpoint{KAL_FS_PROP_LOCAL_ENDPOINT}; +// A name is removed at once although a file of that name is open (clause +// 7.14). Version 0.17. +inline constexpr props remove_open{KAL_FS_PROP_REMOVE_OPEN}; + +struct rename_flag_tag; +using rename_flags = kal::props; +inline constexpr rename_flags no_replace{KAL_RENAME_NO_REPLACE}; enum : int { seek_set = KAL_SEEK_SET, seek_current = KAL_SEEK_CURRENT, seek_end = KAL_SEEK_END }; diff --git a/src/macros.cppm b/src/macros.cppm index a967ff1..1f64cf8 100644 --- a/src/macros.cppm +++ b/src/macros.cppm @@ -42,9 +42,12 @@ inline constexpr kal_uintptr KAL_FS_PROP_CAPACITY_M = (kal_uintptr)( inline constexpr kal_uintptr KAL_FS_PROP_CASE_SENSITIVE_M = (kal_uintptr)(KAL_FS_PROP_CASE_SENSITIVE); inline constexpr kal_uintptr KAL_FS_PROP_EXECUTABLE_M = (kal_uintptr)(KAL_FS_PROP_EXECUTABLE); inline constexpr kal_uintptr KAL_FS_PROP_LINKS_M = (kal_uintptr)(KAL_FS_PROP_LINKS); +inline constexpr kal_uintptr KAL_FS_PROP_LOCAL_ENDPOINT_M = (kal_uintptr)(KAL_FS_PROP_LOCAL_ENDPOINT); inline constexpr kal_uintptr KAL_FS_PROP_LOCKS_M = (kal_uintptr)(KAL_FS_PROP_LOCKS); inline constexpr kal_uintptr KAL_FS_PROP_MAKE_LINKS_M = (kal_uintptr)(KAL_FS_PROP_MAKE_LINKS); inline constexpr kal_uintptr KAL_FS_PROP_MODIFIED_TIME_M = (kal_uintptr)(KAL_FS_PROP_MODIFIED_TIME); +inline constexpr kal_uintptr KAL_FS_PROP_REMOVE_OPEN_M = (kal_uintptr)(KAL_FS_PROP_REMOVE_OPEN); +inline constexpr kal_uintptr KAL_FS_PROP_RENAME_NO_REPLACE_M = (kal_uintptr)(KAL_FS_PROP_RENAME_NO_REPLACE); inline constexpr kal_uintptr KAL_IFACE_ABORT_M = (kal_uintptr)(KAL_IFACE_ABORT); inline constexpr kal_uintptr KAL_IFACE_DATAGRAM_M = (kal_uintptr)(KAL_IFACE_DATAGRAM); inline constexpr kal_uintptr KAL_IFACE_ENV_M = (kal_uintptr)(KAL_IFACE_ENV); @@ -78,16 +81,23 @@ inline constexpr kal_uintptr KAL_OPEN_EXCLUSIVE_M = (kal_uintptr)( inline constexpr kal_uintptr KAL_OPEN_READ_M = (kal_uintptr)(KAL_OPEN_READ); inline constexpr kal_uintptr KAL_OPEN_TRUNCATE_M = (kal_uintptr)(KAL_OPEN_TRUNCATE); inline constexpr kal_uintptr KAL_OPEN_WRITE_M = (kal_uintptr)(KAL_OPEN_WRITE); +inline constexpr kal_uintptr KAL_PINFO_ALL_M = (kal_uintptr)(KAL_PINFO_ALL); +inline constexpr kal_uintptr KAL_PINFO_CPU_M = (kal_uintptr)(KAL_PINFO_CPU); +inline constexpr kal_uintptr KAL_PINFO_ENDED_M = (kal_uintptr)(KAL_PINFO_ENDED); +inline constexpr kal_uintptr KAL_PINFO_ID_M = (kal_uintptr)(KAL_PINFO_ID); +inline constexpr kal_uintptr KAL_PINFO_STARTED_M = (kal_uintptr)(KAL_PINFO_STARTED); inline constexpr kal_uintptr KAL_PROCESS_PROP_BOUND_LIFETIME_M = (kal_uintptr)(KAL_PROCESS_PROP_BOUND_LIFETIME); inline constexpr kal_uintptr KAL_PROCESS_PROP_CHANNEL_M = (kal_uintptr)(KAL_PROCESS_PROP_CHANNEL); inline constexpr kal_uintptr KAL_PROCESS_PROP_EXIT_STATUS_M = (kal_uintptr)(KAL_PROCESS_PROP_EXIT_STATUS); inline constexpr kal_uintptr KAL_PROCESS_PROP_GRANT_DIR_M = (kal_uintptr)(KAL_PROCESS_PROP_GRANT_DIR); inline constexpr kal_uintptr KAL_PROCESS_PROP_JOB_M = (kal_uintptr)(KAL_PROCESS_PROP_JOB); +inline constexpr kal_uintptr KAL_PROCESS_PROP_OPEN_ID_M = (kal_uintptr)(KAL_PROCESS_PROP_OPEN_ID); inline constexpr kal_uintptr KAL_PROCESS_PROP_STOP_REQUESTED_M = (kal_uintptr)(KAL_PROCESS_PROP_STOP_REQUESTED); inline constexpr kal_uintptr KAL_PROCESS_PROP_STREAM_PASSING_M = (kal_uintptr)(KAL_PROCESS_PROP_STREAM_PASSING); inline constexpr kal_uintptr KAL_PROCESS_PROP_TERMINATE_M = (kal_uintptr)(KAL_PROCESS_PROP_TERMINATE); inline constexpr kal_uintptr KAL_RANDOM_PROP_BLOCKING_M = (kal_uintptr)(KAL_RANDOM_PROP_BLOCKING); inline constexpr kal_uintptr KAL_RANDOM_PROP_HARDWARE_M = (kal_uintptr)(KAL_RANDOM_PROP_HARDWARE); +inline constexpr kal_uintptr KAL_RENAME_NO_REPLACE_M = (kal_uintptr)(KAL_RENAME_NO_REPLACE); inline constexpr kal_uintptr KAL_SEEK_CURRENT_M = (kal_uintptr)(KAL_SEEK_CURRENT); inline constexpr kal_uintptr KAL_SEEK_END_M = (kal_uintptr)(KAL_SEEK_END); inline constexpr kal_uintptr KAL_SEEK_SET_M = (kal_uintptr)(KAL_SEEK_SET); @@ -108,6 +118,7 @@ inline constexpr kal_uintptr KAL_TERM_PASS_CONTROL_M = (kal_uintptr)( inline constexpr kal_uintptr KAL_TERM_PASS_INPUT_M = (kal_uintptr)(KAL_TERM_PASS_INPUT); inline constexpr kal_uintptr KAL_TERM_PROP_MODE_M = (kal_uintptr)(KAL_TERM_PROP_MODE); inline constexpr kal_uintptr KAL_TERM_PROP_SIZE_M = (kal_uintptr)(KAL_TERM_PROP_SIZE); +inline constexpr kal_uintptr KAL_TERM_SEQUENCES_M = (kal_uintptr)(KAL_TERM_SEQUENCES); inline constexpr kal_uintptr KAL_TIME_PROP_MONOTONIC_SUSPENDS_M = (kal_uintptr)(KAL_TIME_PROP_MONOTONIC_SUSPENDS); inline constexpr kal_uintptr KAL_TIME_PROP_SLEEP_PRECISE_M = (kal_uintptr)(KAL_TIME_PROP_SLEEP_PRECISE); inline constexpr kal_uintptr KAL_TIME_PROP_WALL_AVAILABLE_M = (kal_uintptr)(KAL_TIME_PROP_WALL_AVAILABLE); diff --git a/src/net.cppm b/src/net.cppm index ada2a4d..e6c9552 100644 --- a/src/net.cppm +++ b/src/net.cppm @@ -17,6 +17,7 @@ module; export module openkal.net; export import openkal.types; export import openkal.stream; +export import openkal.fs; export using ::kal_net_conn; export using ::kal_net_listener; @@ -24,6 +25,8 @@ export using ::kal_net_listener; export using ::kal_net_connect; export using ::kal_net_listen; export using ::kal_net_accept; +export using ::kal_net_listen_at; +export using ::kal_net_connect_at; export using ::kal_net_stream; export using ::kal_net_peer; export using ::kal_net_local; diff --git a/src/process.cppm b/src/process.cppm index fc168e6..296ddc8 100644 --- a/src/process.cppm +++ b/src/process.cppm @@ -37,10 +37,22 @@ export using ::kal_process_job_terminate; export using ::kal_process_job_close; export using ::kal_process_stop_requested; export using ::kal_process_props; +export using ::kal_process_end_as_requested; +export using ::kal_program_info; +export using ::kal_process_self; +export using ::kal_process_open_id; +export using ::kal_process_info; static_assert(sizeof(kal_process) == sizeof(kal_uintptr), "clause 7.2"); static_assert(sizeof(kal_spawn_streams) == 3 * sizeof(kal_uintptr), "clause 5.3"); static_assert(sizeof(kal_preopen) == 3 * sizeof(kal_uintptr), "clause 5.3"); +// kal_program_info grows by appending, as kal_node_info does; the offsets of +// the fields that exist are what is frozen. +static_assert(__builtin_offsetof(kal_program_info, id) == 8, "clause 5.3"); +static_assert(__builtin_offsetof(kal_program_info, started_ns) == 16, "clause 5.3"); +static_assert(__builtin_offsetof(kal_program_info, cpu_ns) == 24, "clause 5.3"); +static_assert(__builtin_offsetof(kal_program_info, ended) == 32, "clause 5.3"); +static_assert(sizeof(kal_program_info) == 40, "clause 5.3"); export namespace kal::process { @@ -58,6 +70,16 @@ inline constexpr props grant_dir {KAL_PROCESS_PROP_GRANT_DIR}; inline constexpr props bound_lifetime{KAL_PROCESS_PROP_BOUND_LIFETIME}; inline constexpr props job {KAL_PROCESS_PROP_JOB}; inline constexpr props stop_requested{KAL_PROCESS_PROP_STOP_REQUESTED}; +inline constexpr props open_id {KAL_PROCESS_PROP_OPEN_ID}; + +// The fields of kal_program_info, for `wanted' and `present' (0.17). +namespace info { +inline constexpr kal_u32 id = KAL_PINFO_ID; +inline constexpr kal_u32 started = KAL_PINFO_STARTED; +inline constexpr kal_u32 cpu = KAL_PINFO_CPU; +inline constexpr kal_u32 ended = KAL_PINFO_ENDED; +inline constexpr kal_u32 all = KAL_PINFO_ALL; +} // WHAT A CALLER ASKS FOR, WHICH IS A DIFFERENT WORD FROM WHAT AN // IMPLEMENTATION CAN DO. `props' above answers the second; these set the first. diff --git a/src/terminal.cppm b/src/terminal.cppm index a50ed2f..ac2cc01 100644 --- a/src/terminal.cppm +++ b/src/terminal.cppm @@ -43,6 +43,9 @@ inline constexpr mode pass_control{KAL_TERM_PASS_CONTROL}; // carriage return and is not rewritten into a line feed. Independent of // pass_control, which says whether a keystroke is kept rather than altered. inline constexpr mode pass_input {KAL_TERM_PASS_INPUT}; +// Control sequences of ECMA-48 written to the stream are obeyed rather than +// shown. A position of the stream a program writes. Version 0.17. +inline constexpr mode sequences {KAL_TERM_SEQUENCES}; struct props_tag; using props = kal::props;