Skip to content

Commit 0922007

Browse files
committed
0.5.0 --- openkal 0.10's five operations, four implemented and one recorded
⭐ THIS SYSTEM EXCLUDES PER HANDLE, WHICH IS WHAT openkal STATES. The other two kernels carry an older record lock held by the PROCESS --- released as soon as that process closes any descriptor for the node --- and have to reach past it to the open-file form. Here there is nothing to reach past: `NtLockFile' is the handle's. ⚠️ AND THIS SYSTEM'S EXCLUSION IS MANDATORY RATHER THAN ADVISORY: a write that crosses a locked range is refused by the system, where elsewhere it is refused only to a program that asked. That is a difference a caller can observe and it is the environment's own; simulating the weaker one is not this implementation's to do. `kal_fs_capacity' reports the units this CALLER may use rather than the volume's free total --- a quota makes the two differ, and the larger is not a number a program can act upon. `kal_fs_set_modified_at' does NOT open through `kal_fs_open': that one names `FILE_NON_DIRECTORY_FILE', correctly, since it opens a file --- and a directory is exactly what this declaration exists to reach. It opens for the attribute alone, so a caller need not be able to write the contents to stamp them. `kal_task_parallelism' counts the ACTIVE MASK and not the count field. This record carries both and they differ whenever a program is confined to part of the machine, which is the case a program sizing itself most needs to get right. --- and the one that is not claimed ----------------------------------------- ⚠️⚠️ `kal_process_spawn_bound' IS REFUSED, AND NOT BECAUSE THIS SYSTEM CANNOT. It can: a job object with `JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE' ends every program in the job when the last handle closes, which this system does when a process dies however it dies. That is exactly the binding openkal describes. It is not claimed because it has not been MEASURED here. The one consumer that needs it composes `execve', and this system already declines `openkal.space', so nothing on this system reaches the operation today --- and claiming a binding that has never been exercised is the shape of answer openkal exists to refuse. It is recorded as the next thing this implementation should do rather than as an absence, and a caller that asks `kal_process_props' first is told.
1 parent 0c8537c commit 0922007

6 files changed

Lines changed: 163 additions & 4 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ An implementation of [openkal](https://github.com/mcpplibs/openkal) for Windows.
77
openkal = "0.9.0"
88

99
[target.'cfg(windows)'.dependencies]
10-
openkal-windows = "0.4.0"
10+
openkal-windows = "0.5.0"
1111
```
1212

1313
Its purpose is as much to test the specification as to be used. openkal was

mcpp.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22
namespace = "mcpplibs"
33
name = "openkal-windows"
4-
version = "0.4.0"
4+
version = "0.5.0"
55
description = "An implementation of openkal for Windows, written on the Win32 interfaces and the object manager beneath them, using no C runtime symbol."
66
license = "Apache-2.0"
77

@@ -18,7 +18,7 @@ authors = ["mcpplibs"]
1818
repo = "https://github.com/mcpplibs/openkal-windows"
1919

2020
[dependencies]
21-
openkal = "0.9.0"
21+
openkal = "0.10.0"
2222

2323
# The package contributes definitions and no modules. The interface it
2424
# implements is declared by the specification package, which this package

src/fs.cpp

Lines changed: 96 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,100 @@ int kal_fs_set_modified(kal_file f, kal_u64 modified_ns) {
415415
return okw::ok(r) ? kal_ok : okw::translate_nt(r);
416416
}
417417

418+
// The modification time of a NAME, including a directory. Version 0.10.
419+
//
420+
// ⚠️ AND THE OPEN IS NOT `kal_fs_open''S. That one names `FILE_NON_DIRECTORY_FILE'
421+
// --- correctly, since it opens a FILE --- and a directory is exactly what this
422+
// declaration exists to reach. Opening for the attribute alone also means a
423+
// caller need not be able to write the contents to stamp them, which is what
424+
// `utimensat' means everywhere else.
425+
int kal_fs_set_modified_at(kal_dir base, const char* name, kal_uintptr len,
426+
kal_u64 modified_ns) {
427+
void* root = dir_handle(base);
428+
if (!root || !okw::acceptable(name, len)) return kal_err_invalid;
429+
430+
void* h = nullptr;
431+
const long r = open_relative(root, name, len, FILE_WRITE_ATTRIBUTES,
432+
okw::file_open,
433+
okw::file_open_for_backup_intent, &h);
434+
if (!okw::ok(r)) return okw::translate_nt(r);
435+
436+
okw::file_basic_information basic{};
437+
basic.last_write_time =
438+
static_cast<okw_i64>(modified_ns / 100ull + kEpochDifference);
439+
okw::io_status_block iosb{};
440+
const long w = okw::NtSetInformationFile(h, &iosb, &basic, sizeof basic,
441+
okw::file_basic_information_class);
442+
okw::NtClose(h);
443+
return okw::ok(w) ? kal_ok : okw::translate_nt(w);
444+
}
445+
446+
// --- exclusion upon a range of a file ---------------------------------------
447+
//
448+
// ⭐ THIS SYSTEM EXCLUDES PER HANDLE, WHICH IS WHAT openkal STATES. The other
449+
// two kernels carry an older form held by the PROCESS and have to reach past it;
450+
// here there is nothing to reach past.
451+
//
452+
// ⚠️ AND THIS SYSTEM'S EXCLUSION IS MANDATORY RATHER THAN ADVISORY: a write that
453+
// crosses a locked range is refused by the system, where elsewhere it is refused
454+
// only to a program that asked. That is a difference a caller can observe, and
455+
// it is the environment's own; nothing here can or should simulate the weaker
456+
// one.
457+
static int lock_range(kal_file f, kal_u64 start, kal_u64 len,
458+
bool exclusive, bool wait, bool release) {
459+
void* h = file_handle(f);
460+
if (!h) return kal_err_invalid;
461+
462+
okw_i64 offset = static_cast<okw_i64>(start);
463+
// openkal spells "to the end, however far that comes to be" as zero; this
464+
// system has no such spelling and takes a count, so the largest one stands
465+
// for it --- which is what every C library on this system does for the same
466+
// reason.
467+
okw_i64 length = len ? static_cast<okw_i64>(len)
468+
: static_cast<okw_i64>(0x7fffffffffffffffll);
469+
470+
okw::io_status_block iosb{};
471+
const long r = release
472+
? okw::NtUnlockFile(h, &iosb, &offset, &length, 0)
473+
: okw::NtLockFile(h, nullptr, nullptr, nullptr, &iosb, &offset, &length,
474+
0, static_cast<unsigned char>(wait ? 0 : 1),
475+
static_cast<unsigned char>(exclusive ? 1 : 0));
476+
return okw::ok(r) ? kal_ok : okw::translate_nt(r);
477+
}
478+
479+
int kal_fs_lock(kal_file f, kal_u64 start, kal_u64 len, kal_uintptr mode) {
480+
const bool shared = (mode & KAL_LOCK_SHARED) != 0;
481+
const bool exclusive = (mode & KAL_LOCK_EXCLUSIVE) != 0;
482+
if (shared == exclusive) return kal_err_invalid;
483+
return lock_range(f, start, len, exclusive, (mode & KAL_LOCK_WAIT) != 0, false);
484+
}
485+
486+
int kal_fs_unlock(kal_file f, kal_u64 start, kal_u64 len) {
487+
return lock_range(f, start, len, false, false, true);
488+
}
489+
490+
// How much the volume holds, in bytes.
491+
//
492+
// ⚠️ `available' AND NOT `total free'. This system reports the units this
493+
// CALLER may use, which is the question openkal asks; a quota makes the two
494+
// differ and the larger of them is not an answer a program can act upon.
495+
int kal_fs_capacity(kal_dir d, kal_u64* total, kal_u64* available) {
496+
void* h = dir_handle(d);
497+
if (!h) return kal_err_invalid;
498+
499+
okw::io_status_block s{};
500+
okw::file_fs_size_information info{};
501+
const long r = okw::NtQueryVolumeInformationFile(h, &s, &info, sizeof info,
502+
okw::fs_size_information_class);
503+
if (!okw::ok(r)) return okw::translate_nt(r);
504+
505+
const kal_u64 unit = static_cast<kal_u64>(info.sectors_per_unit)
506+
* static_cast<kal_u64>(info.bytes_per_sector);
507+
if (total) *total = static_cast<kal_u64>(info.total_allocation_units) * unit;
508+
if (available) *available = static_cast<kal_u64>(info.available_allocation_units) * unit;
509+
return kal_ok;
510+
}
511+
418512
int kal_fs_mkdir(kal_dir base, const char* name, kal_uintptr len) {
419513
void* root = dir_handle(base);
420514
if (!root || !okw::acceptable(name, len)) return kal_err_invalid;
@@ -566,7 +660,8 @@ int kal_fs_list_next(kal_dir, kal_uintptr* iter,
566660
kal_uintptr kal_fs_props(kal_dir d) {
567661
void* h = dir_handle(d);
568662
const kal_uintptr conservative =
569-
KAL_FS_PROP_MODIFIED_TIME | KAL_FS_PROP_ATOMIC_RENAME;
663+
KAL_FS_PROP_MODIFIED_TIME | KAL_FS_PROP_ATOMIC_RENAME
664+
| KAL_FS_PROP_LOCKS | KAL_FS_PROP_CAPACITY;
570665
if (!h) return 0;
571666

572667
okw::io_status_block s{};

src/process.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,28 @@ void kal_process_close(kal_process p) {
290290
// KAL_PROCESS_PROP_GRANT_DIR is deliberately absent: kal_process_spawn_with
291291
// refuses a non-empty set of grants here, and a word claiming a facility the
292292
// next call refuses is the disagreement clause 6.2 exists to prevent.
293+
// Starting a program whose lifetime is bound to this one's. Version 0.10.
294+
//
295+
// ⚠️⚠️ REFUSED HERE, AND NOT BECAUSE THIS SYSTEM CANNOT --- IT CAN. A job object
296+
// with `JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE' ends every program in the job when
297+
// the last handle to it closes, which this system does when a process dies
298+
// however it dies. That is exactly the binding openkal describes.
299+
//
300+
// ⚠️ IT IS NOT CLAIMED IN THIS RELEASE BECAUSE IT HAS NOT BEEN MEASURED HERE.
301+
// The one consumer that needs it composes `execve', and this system already
302+
// declines `openkal.space' --- so nothing on this system reaches the operation
303+
// today, and claiming a binding that has never been exercised is the shape of
304+
// answer openkal exists to refuse. It is the next thing this implementation
305+
// should do, and it is recorded as that rather than as an absence.
306+
//
307+
// A caller that asks `kal_process_props' first is told before it depends on it.
308+
int kal_process_spawn_bound(kal_dir, const char*, kal_uintptr,
309+
const char**, const kal_uintptr*, kal_uintptr,
310+
const char**, const kal_uintptr*, kal_uintptr,
311+
const kal_spawn_streams*, kal_process*) {
312+
return kal_err_not_supported;
313+
}
314+
293315
kal_uintptr kal_process_props(void) { return
294316
KAL_PROCESS_PROP_TERMINATE | KAL_PROCESS_PROP_STREAM_PASSING
295317
| KAL_PROCESS_PROP_EXIT_STATUS

src/task.cpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,27 @@ int kal_task_wait(const kal_u32* word, kal_u32 expected,
102102
}
103103
}
104104

105+
// How many contexts can run at the same moment. Version 0.10.
106+
//
107+
// ⚠️ Added because its absence was a WRONG ANSWER and not a refusal: the
108+
// property word says whether contexts run at once and not how many can, so a C
109+
// library above answered 1 with no error and a program sizing a pool of workers
110+
// got one worker.
111+
//
112+
// ⭐ THE ACTIVE MASK AND NOT THE COUNT FIELD. This record carries both, and they
113+
// differ whenever a program is confined to part of the machine --- which is the
114+
// case a program sizing itself most needs to get right.
115+
kal_uintptr kal_task_parallelism(void) {
116+
SYSTEM_INFO info{};
117+
GetSystemInfo(&info);
118+
kal_uintptr count = 0;
119+
for (unsigned long long bit = info.dwActiveProcessorMask; bit; bit &= bit - 1)
120+
++count;
121+
if (count) return count;
122+
// Zero is "cannot say", and openkal distinguishes it from one on purpose.
123+
return static_cast<kal_uintptr>(info.dwNumberOfProcessors);
124+
}
125+
105126
int kal_task_wake(const kal_u32* word, kal_uintptr count, kal_uintptr* woken) {
106127
void* address = const_cast<void*>(static_cast<const void*>(word));
107128
if (count == 0) { if (woken) *woken = 0; return kal_ok; }

src/win.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,17 @@ enum : int {
154154
enum : int {
155155
fs_volume_information_class = 1,
156156
fs_attribute_information_class = 5,
157+
fs_size_information_class = 3,
158+
};
159+
160+
// How much the volume holds. The counts are in allocation units and the record
161+
// says how many bytes one is, which is why capacity is two multiplications and
162+
// not a field.
163+
struct file_fs_size_information {
164+
okw_i64 total_allocation_units;
165+
okw_i64 available_allocation_units;
166+
unsigned long sectors_per_unit;
167+
unsigned long bytes_per_sector;
157168
};
158169

159170
// The dispositions NtCreateFile takes. They are the whole of what
@@ -210,6 +221,16 @@ __declspec(dllimport) long __stdcall NtQueryDirectoryFile(void* handle, void* ev
210221
int cls, unsigned char single, unicode_string* pattern,
211222
unsigned char restart);
212223
__declspec(dllimport) long __stdcall NtFlushBuffersFile(void* handle, io_status_block* status);
224+
225+
// ⭐ EXCLUSION IS PER-HANDLE ON THIS SYSTEM, which is exactly what openkal
226+
// states: the holder is the `kal_file'. There is no second, process-held form
227+
// to avoid here --- the thing the other two kernels have to reach past.
228+
__declspec(dllimport) long __stdcall NtLockFile(void* handle, void* event, void* apc, void* apc_context,
229+
io_status_block* status, okw_i64* offset, okw_i64* length,
230+
unsigned long key, unsigned char fail_immediately,
231+
unsigned char exclusive);
232+
__declspec(dllimport) long __stdcall NtUnlockFile(void* handle, io_status_block* status,
233+
okw_i64* offset, okw_i64* length, unsigned long key);
213234
__declspec(dllimport) unsigned long __stdcall RtlNtStatusToDosError(long status);
214235
}
215236

0 commit comments

Comments
 (0)