diff --git a/README.md b/README.md index 5da0bb3..c0227df 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ the claim can be checked rather than repeated. ```toml [dependencies] -openkal-musl = "0.22.8" +openkal-musl = "0.22.9" ``` > **Engine floor (mcpp 2026.9.18.3):** this version of this package declares diff --git a/examples/posix/src/main.c b/examples/posix/src/main.c index 65ef19f..f3c2597 100644 --- a/examples/posix/src/main.c +++ b/examples/posix/src/main.c @@ -97,6 +97,12 @@ int main(int argc, char **argv, char **envp) { check(lseek(fd, 3, SEEK_SET) == 3, "the position is set"); char rb[8] = {0}; check(read(fd, rb, 4) == 4 && memcmp(rb, "3456", 4) == 0, "four bytes are read from the position"); + /* At an offset, and the position stays where the read above left it. */ + memset(rb, 0, sizeof rb); + check(pread(fd, rb, 3, 1) == 3 && memcmp(rb, "123", 3) == 0, "three bytes are read at an offset"); + check(pwrite(fd, "x", 1, 0) == 1, "a byte is written at an offset"); + check(lseek(fd, 0, SEEK_CUR) == 7, "neither moves the position"); + check(pread(fd, rb, 2, 0) == 2 && memcmp(rb, "x1", 2) == 0, "the byte written at an offset is read back"); struct stat st; check(fstat(fd, &st) == 0 && st.st_size == 10, "the open file reports its length"); check(ftruncate(fd, 4) == 0, "the length is set"); diff --git a/mcpp.toml b/mcpp.toml index 0e8424b..14817f0 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -1,7 +1,7 @@ [package] namespace = "mcpplibs" name = "openkal-musl" -version = "0.22.8" +version = "0.22.9" description = "musl 1.2.5 redirected onto openkal: one C library, ported once, above every implementation of the specification rather than above one kernel." license = "Apache-2.0" diff --git a/musl/PATCHES.md b/musl/PATCHES.md index 7851013..3ccec66 100644 --- a/musl/PATCHES.md +++ b/musl/PATCHES.md @@ -652,6 +652,21 @@ to a directory's time at all. A file still asks for exactly what is required, and an implementation that cannot open a directory returns an error that is passed on unchanged. Asked of the specification. +## A transfer at an offset, made of the position (0.22.9) + +`pread`, `pwrite`, `preadv` and `pwritev` are a seek to the offset, the +transfer, and a seek back to the descriptor's position, under the descriptor +table's lock (`do_transfer_at` in `port/src/okm_syscall.c`). openkal reads and +writes a file at its position and has no transfer at an offset. Before 0.22.9 +the four answered `ENOSYS`, which a configure check cannot see because the +functions link; LLVM reads every file through `pread`. + +A caller can tell one difference. Two positional transfers on one descriptor do +not interleave, but a plain `read` or `write` on the same descriptor in another +thread, made between the two seeks, moves from the positional offset; POSIX +leaves the position of such a call unaffected. A descriptor that is not a file +answers `ESPIPE`, as POSIX states. + ## What the architecture does not decide alone `long double`. diff --git a/port/src/okm_syscall.c b/port/src/okm_syscall.c index 89ab831..4ae6efd 100644 --- a/port/src/okm_syscall.c +++ b/port/src/okm_syscall.c @@ -247,6 +247,47 @@ static syscall_arg_t do_read(int fd, void* buf, size_t len) return r; } +/* A TRANSFER AT AN OFFSET, MADE OF THE POSITION. + * + * openkal reads and writes a file at its position and offers no transfer at + * an offset, so `pread' and `pwrite' are a seek, the transfer, and a seek back + * to the position the descriptor had. Until 0.22.9 they were answered ENOSYS, + * which no configure check can see: the functions link. LLVM reads every file + * through `pread', and clangd built as an ok image could open none (measured + * 2026-10-29). + * + * The three steps hold the descriptor table's lock, so two positional transfers + * do not interleave. What the lock does not give is POSIX's other promise: a + * plain `read' or `write' on the same descriptor, in another thread, between + * the two seeks, moves from the positional offset. Recorded in musl/PATCHES.md. */ +static syscall_arg_t do_transfer_at(int fd, const struct iovec* v, int count, + int64_t at, int writing) +{ + struct okm_desc* d = okm_desc_of(fd); + if (!d) return -EBADF; + if (d->kind != OKM_FILE) return -ESPIPE; + if (at < 0 || count < 0) return -EINVAL; + + okm_lock(); + kal_u64 keep = 0, now = 0; + int e = okm_fs_seek(d->file, 0, SEEK_CUR, &keep); + if (e == kal_ok) e = okm_fs_seek(d->file, at, SEEK_SET, &now); + if (e != kal_ok) { okm_unlock(); return -okm_errno(e); } + + syscall_arg_t total = 0; + for (int i = 0; i < count; i++) { + if (v[i].iov_len == 0) continue; + const syscall_arg_t r = writing ? do_write(fd, v[i].iov_base, v[i].iov_len) + : do_read(fd, v[i].iov_base, v[i].iov_len); + if (r < 0) { if (!total) total = r; break; } + total += r; + if ((size_t)r < v[i].iov_len) break; + } + okm_fs_seek(d->file, (int64_t)keep, SEEK_SET, &now); + okm_unlock(); + return total; +} + /* --- opening --------------------------------------------------------------- */ static syscall_arg_t do_openat(int dirfd, const char* path, int flags, int mode) @@ -1348,6 +1389,21 @@ syscall_arg_t __okm_syscall(syscall_arg_t n, syscall_arg_t a1, syscall_arg_t a2, return total; } + /* The offset is one argument on every target this library builds (LP64); + * see do_transfer_at for how the transfer is made. */ + case SYS_pread64: { + struct iovec one = { (void*)a2, (size_t)a3 }; + return do_transfer_at((int)a1, &one, 1, (int64_t)a4, 0); + } + case SYS_pwrite64: { + struct iovec one = { (void*)a2, (size_t)a3 }; + return do_transfer_at((int)a1, &one, 1, (int64_t)a4, 1); + } + case SYS_preadv: + return do_transfer_at((int)a1, (const struct iovec*)a2, (int)a3, (int64_t)a4, 0); + case SYS_pwritev: + return do_transfer_at((int)a1, (const struct iovec*)a2, (int)a3, (int64_t)a4, 1); + /* --- naming ----------------------------------------------------------- */ case SYS_openat: return do_openat((int)a1, (const char*)a2, (int)a3, (int)a4); #ifdef SYS_open