diff --git a/README.md b/README.md index d7eb4d7..d839f03 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ written on the kernel's own calls. openkal = "0.9.0" [target.'cfg(os = "macos")'.dependencies] -openkal-macos = "0.6.1" +openkal-macos = "0.7.0" ``` Its purpose is as much to test the specification as to be used. A specification diff --git a/mcpp.toml b/mcpp.toml index 4d5bdae..64bb9af 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -1,7 +1,7 @@ [package] namespace = "mcpplibs" name = "openkal-macos" -version = "0.6.1" +version = "0.7.0" description = "An implementation of openkal for macOS, written on the kernel's own calls. Its purpose is as much to test the specification as to be used." license = "Apache-2.0" @@ -18,7 +18,7 @@ authors = ["mcpplibs"] repo = "https://github.com/mcpplibs/openkal-macos" [dependencies] -openkal = "0.9.0" +openkal = "0.10.0" [build] # The flags are attached to this package's own sources rather than to the whole diff --git a/src/fs.cpp b/src/fs.cpp index b5ea7c6..d731f03 100644 --- a/src/fs.cpp +++ b/src/fs.cpp @@ -275,6 +275,103 @@ int kal_fs_set_modified(kal_file f, kal_u64 modified_ns) { return okm::failed(r) ? okm::translate(r) : kal_ok; } +// The modification time of a NAME, including a directory. Version 0.10. +// +// ⚠️ Expressed as opening the name and using the operation above's own call, +// because this system's time-setting call takes a descriptor. Opening for +// READING is enough for it and is what lets a DIRECTORY be reached --- which is +// the whole reason this declaration exists: the file-taking form takes a +// `kal_file' and a directory is a `kal_dir', so this interface had no route to +// a directory's time at all. +int kal_fs_set_modified_at(kal_dir base, const char* name, kal_uintptr len, + kal_u64 modified_ns) { + const int b = okm::unpack(base.h); + if (b < 0 || !okm::acceptable(name, len)) return kal_err_invalid; + okm::terminated t(name, len); if (!t.ok) return kal_err_invalid; + + const okm_long fd = okm::sys(okm::nr_openat, b, + reinterpret_cast(t.buf), + okm::o_rdonly | okm::o_cloexec, 0); + if (okm::failed(fd)) return okm::translate(fd); + + okm::kstat64 st{}; + okm_long r = okm::sys(okm::nr_fstat64, fd, reinterpret_cast(&st)); + if (!okm::failed(r)) { + okm::ktimeval times[2]; + times[0].sec = st.atime_sec; + times[0].usec = static_cast(st.atime_nsec / 1000); + times[0].pad = 0; + times[1].sec = static_cast(modified_ns / 1000000000u); + times[1].usec = static_cast((modified_ns % 1000000000u) / 1000u); + times[1].pad = 0; + r = okm::sys(okm::nr_futimes, fd, reinterpret_cast(times)); + } + okm::sys(okm::nr_close, fd); + return okm::failed(r) ? okm::translate(r) : kal_ok; +} + +// --- exclusion upon a range of a file --------------------------------------- +// +// ⭐ THE OPEN-FILE FORM. This system's oldest record lock is held by the process +// and is released when that process closes any descriptor for the node; openkal +// states the holder as the `kal_file', and `F_OFD_*' is exactly that. +static int lock_range(kal_file f, kal_u64 start, kal_u64 len, + short type, bool wait) { + const int fd = okm::unpack(f.h); + if (fd < 0) return kal_err_invalid; + + okm::kflock fl{}; + fl.l_start = static_cast(start); + fl.l_len = static_cast(len); // zero means to the end, as here + fl.l_pid = 0; + fl.l_type = type; + fl.l_whence = okm::seek_set; + + const okm_long cmd = wait ? okm::f_ofd_setlkw : okm::f_ofd_setlk; + okm_long r; + do { + r = okm::sys(okm::nr_fcntl, fd, cmd, reinterpret_cast(&fl)); + } while (r == -okm::e_intr); + if (!okm::failed(r)) return kal_ok; + + // ⚠️⚠️ TWO VALUES MEAN ONE THING HERE, AND openkal NAMES ONE OF THEM. The + // standard this call comes from reports a range another holder has as + // EITHER of two values and leaves the choice to the system. openkal says + // `kal_err_again', which a caller polls upon; the other translates to + // `permission', which a caller reads as "asking again will not help". + // Narrowed to the attempt that does not wait, where alone the two carry + // this meaning. + if (!wait && (-r == okm::e_acces || -r == okm::e_again)) return kal_err_again; + return okm::translate(r); +} + +int kal_fs_lock(kal_file f, kal_u64 start, kal_u64 len, kal_uintptr mode) { + const bool shared = (mode & KAL_LOCK_SHARED) != 0; + const bool exclusive = (mode & KAL_LOCK_EXCLUSIVE) != 0; + if (shared == exclusive) return kal_err_invalid; + return lock_range(f, start, len, + shared ? okm::lock_read : okm::lock_write, + (mode & KAL_LOCK_WAIT) != 0); +} + +int kal_fs_unlock(kal_file f, kal_u64 start, kal_u64 len) { + return lock_range(f, start, len, okm::lock_unlock, false); +} + +// How much the volume holds, in bytes. `f_bavail' rather than `f_bfree': the +// question is what THIS program could use. +int kal_fs_capacity(kal_dir d, kal_u64* total, kal_u64* available) { + const int fd = okm::unpack(d.h); + if (fd < 0) return kal_err_invalid; + okm::kstatfs64 sf{}; + const okm_long r = okm::sys(okm::nr_fstatfs64, fd, reinterpret_cast(&sf)); + if (okm::failed(r)) return okm::translate(r); + const kal_u64 unit = static_cast(sf.f_bsize); + if (total) *total = static_cast(sf.f_blocks) * unit; + if (available) *available = static_cast(sf.f_bavail) * unit; + return kal_ok; +} + int kal_fs_mkdir(kal_dir base, const char* name, kal_uintptr len) { const int b = okm::unpack(base.h); if (b < 0 || !okm::acceptable(name, len)) return kal_err_invalid; @@ -380,8 +477,12 @@ int kal_fs_list_next(kal_dir, kal_uintptr* iter, // mounts, and `renameat' within one directory is atomic by POSIX. kal_uintptr kal_fs_props(kal_dir d) { const int fd = okm::unpack(d.h); + // Locks and capacity are answered by this system's VFS for every format + // beneath it, so they are in the conservative set rather than switched on + // by name below. const kal_uintptr conservative = - KAL_FS_PROP_MODIFIED_TIME | KAL_FS_PROP_ATOMIC_RENAME; + KAL_FS_PROP_MODIFIED_TIME | KAL_FS_PROP_ATOMIC_RENAME + | KAL_FS_PROP_LOCKS | KAL_FS_PROP_CAPACITY; if (fd < 0) return 0; okm::kstatfs64 sf{}; diff --git a/src/process.cpp b/src/process.cpp index fac60c2..54defc1 100644 --- a/src/process.cpp +++ b/src/process.cpp @@ -247,6 +247,31 @@ int kal_process_terminate(kal_process h) { // waited for continues, and this environment collects it when the caller exits. void kal_process_close(kal_process) { } +// Starting a program whose lifetime is bound to this one's. Version 0.10. +// +// ⚠️⚠️ REFUSED HERE, AND THE REFUSAL IS THE HONEST ANSWER RATHER THAN A GAP TO +// FILL LATER WITH SOMETHING THAT LOOKS LIKE IT. +// +// The binding openkal describes has to hold however the caller ends, including +// when it is killed outright --- and this system has no primitive that arms it +// from inside the started image. The other kernel does, in one call. +// +// ⚠️ What this system offers instead is a WATCH: a context here can be told when +// another ends and can then act. That is not the same thing and must not be +// offered as it. A watch needs a live context to notice, so a caller that is +// killed outright notices nothing and the started program survives --- which is +// precisely the failure the operation exists to remove. Composing it would move +// the defect from "refused" to "works except when it matters". +// +// ⇒ `KAL_PROCESS_PROP_BOUND_LIFETIME' is not claimed, and a caller that asks +// first is told before it depends on it. +int kal_process_spawn_bound(kal_dir, const char*, kal_uintptr, + const char**, const kal_uintptr*, kal_uintptr, + const char**, const kal_uintptr*, kal_uintptr, + const kal_spawn_streams*, kal_process*) { + return kal_err_not_supported; +} + kal_uintptr kal_process_props(void) { return KAL_PROCESS_PROP_TERMINATE | KAL_PROCESS_PROP_STREAM_PASSING | KAL_PROCESS_PROP_EXIT_STATUS diff --git a/src/sys.h b/src/sys.h index 9a70117..d5e50aa 100644 --- a/src/sys.h +++ b/src/sys.h @@ -261,6 +261,21 @@ enum : int { enum : okm_long { o_rdonly = 0, o_wronly = 1, o_rdwr = 2, + + // ⚠️ THIS SYSTEM'S VALUES, WHICH ARE NOT THE OTHER ONE'S. A read lock is 1 + // here and 0 there, and a write lock is 3 here and 1 there --- the BSD + // numbering rather than the one the other kernel took. A table copied from + // the sibling implementation would compile, run, and take the wrong kind of + // lock. + lock_read = 1, lock_unlock = 2, lock_write = 3, + seek_set = 0, + + // The open-file form, which this system has had since 10.10. The holder is + // the open file rather than the process, which is what openkal states. + f_ofd_getlk = 92, f_ofd_setlk = 90, f_ofd_setlkw = 91, + + // sysctl: how many processors this machine runs at once. + ctl_hw = 6, hw_ncpu = 3, o_creat = 0x0200, o_excl = 0x0800, o_trunc = 0x0400, o_append = 0x0008, o_directory = 0x100000, o_cloexec = 0x1000000, o_nofollow = 0x0100, at_fdcwd = -2, at_removedir = 0x0080, at_symlink_nofollow = 0x0020, @@ -308,6 +323,17 @@ inline int translate(okm_long r) { // consults --- a property that varies between the RESOURCES of an interface is // answered by an enquiry taking the resource, and here the resource's format is // what the enquiry has to look at. +// ⚠️ THIS SYSTEM'S `struct flock' PUTS THE POSITIONS FIRST, and the other +// kernel's puts the kinds first. The two layouts are not interchangeable, and a +// structure copied across would place a 64-bit offset where two shorts belong. +struct kflock { + okm_i64 l_start; + okm_i64 l_len; + int l_pid; + short l_type; + short l_whence; +}; + struct kstatfs64 { okm_u32 f_bsize; okm_u32 f_iosize; // int32; the pair fills the first eight bytes diff --git a/src/task.cpp b/src/task.cpp index b090b33..d6aeeca 100644 --- a/src/task.cpp +++ b/src/task.cpp @@ -170,6 +170,24 @@ int kal_task_wait(const kal_u32* word, kal_u32 expected, } } +// How many contexts can run at the same moment. Version 0.10. +// +// ⚠️ Added because its absence was a WRONG ANSWER and not a refusal: +// `KAL_TASK_PROP_PARALLEL' says whether and not how many, so a C library above +// answered 1 with no error and a program sizing a pool of workers got one. +kal_uintptr kal_task_parallelism(void) { + int mib[2] = { okm::ctl_hw, okm::hw_ncpu }; + int value = 0; + okm_uptr length = sizeof value; + const okm_long r = okm::sys(okm::nr_sysctl, + reinterpret_cast(mib), 2, + reinterpret_cast(&value), + reinterpret_cast(&length), 0, 0); + // Zero is "cannot say", and openkal distinguishes it from one on purpose. + if (okm::failed(r) || value <= 0) return 0; + return static_cast(value); +} + int kal_task_wake(const kal_u32* word, kal_uintptr count, kal_uintptr* woken) { if (count == 0) { if (woken) *woken = 0; return kal_ok; } okm_long operation = okm::ul_compare_and_wait | okm::ulf_no_errno;