Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ An implementation of [openkal](https://github.com/mcpplibs/openkal) for Windows.
openkal = "0.9.0"

[target.'cfg(windows)'.dependencies]
openkal-windows = "0.4.0"
openkal-windows = "0.5.0"
```

Its purpose is as much to test the specification as to be used. openkal was
Expand Down
4 changes: 2 additions & 2 deletions mcpp.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
namespace = "mcpplibs"
name = "openkal-windows"
version = "0.4.0"
version = "0.5.0"
description = "An implementation of openkal for Windows, written on the Win32 interfaces and the object manager beneath them, using no C runtime symbol."
license = "Apache-2.0"

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

[dependencies]
openkal = "0.9.0"
openkal = "0.10.0"

# The package contributes definitions and no modules. The interface it
# implements is declared by the specification package, which this package
Expand Down
2 changes: 2 additions & 0 deletions port/ntdll.def
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ EXPORTS
NtClose
NtCreateFile
NtFlushBuffersFile
NtLockFile
NtQueryDirectoryFile
NtQueryInformationFile
NtQueryVolumeInformationFile
NtReadFile
NtSetInformationFile
NtUnlockFile
NtWriteFile
RtlNtStatusToDosError
145 changes: 144 additions & 1 deletion src/fs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,100 @@ int kal_fs_set_modified(kal_file f, kal_u64 modified_ns) {
return okw::ok(r) ? kal_ok : okw::translate_nt(r);
}

// The modification time of a NAME, including a directory. Version 0.10.
//
// ⚠️ AND THE OPEN IS NOT `kal_fs_open''S. That one names `FILE_NON_DIRECTORY_FILE'
// --- correctly, since it opens a FILE --- and a directory is exactly what this
// declaration exists to reach. Opening for the attribute alone also means a
// caller need not be able to write the contents to stamp them, which is what
// `utimensat' means everywhere else.
int kal_fs_set_modified_at(kal_dir base, const char* name, kal_uintptr len,
kal_u64 modified_ns) {
void* root = dir_handle(base);
if (!root || !okw::acceptable(name, len)) return kal_err_invalid;

void* h = nullptr;
const long r = open_relative(root, name, len, FILE_WRITE_ATTRIBUTES,
okw::file_open,
okw::file_open_for_backup_intent, &h);
if (!okw::ok(r)) return okw::translate_nt(r);

okw::file_basic_information basic{};
basic.last_write_time =
static_cast<okw_i64>(modified_ns / 100ull + kEpochDifference);
okw::io_status_block iosb{};
const long w = okw::NtSetInformationFile(h, &iosb, &basic, sizeof basic,
okw::file_basic_information_class);
okw::NtClose(h);
return okw::ok(w) ? kal_ok : okw::translate_nt(w);
}

// --- exclusion upon a range of a file ---------------------------------------
//
// ⭐ THIS SYSTEM EXCLUDES PER HANDLE, WHICH IS WHAT openkal STATES. The other
// two kernels carry an older form held by the PROCESS and have to reach past it;
// here there is nothing to reach past.
//
// ⚠️ 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; nothing here can or should simulate the weaker
// one.
static int lock_range(kal_file f, kal_u64 start, kal_u64 len,
bool exclusive, bool wait, bool release) {
void* h = file_handle(f);
if (!h) return kal_err_invalid;

okw_i64 offset = static_cast<okw_i64>(start);
// openkal spells "to the end, however far that comes to be" as zero; this
// system has no such spelling and takes a count, so the largest one stands
// for it --- which is what every C library on this system does for the same
// reason.
okw_i64 length = len ? static_cast<okw_i64>(len)
: static_cast<okw_i64>(0x7fffffffffffffffll);

okw::io_status_block iosb{};
const long r = release
? okw::NtUnlockFile(h, &iosb, &offset, &length, 0)
: okw::NtLockFile(h, nullptr, nullptr, nullptr, &iosb, &offset, &length,
0, static_cast<unsigned char>(wait ? 0 : 1),
static_cast<unsigned char>(exclusive ? 1 : 0));
return okw::ok(r) ? kal_ok : okw::translate_nt(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, exclusive, (mode & KAL_LOCK_WAIT) != 0, false);
}

int kal_fs_unlock(kal_file f, kal_u64 start, kal_u64 len) {
return lock_range(f, start, len, false, false, true);
}

// How much the volume holds, in bytes.
//
// ⚠️ `available' AND NOT `total free'. This system reports the units this
// CALLER may use, which is the question openkal asks; a quota makes the two
// differ and the larger of them is not an answer a program can act upon.
int kal_fs_capacity(kal_dir d, kal_u64* total, kal_u64* available) {
void* h = dir_handle(d);
if (!h) return kal_err_invalid;

okw::io_status_block s{};
okw::file_fs_size_information info{};
const long r = okw::NtQueryVolumeInformationFile(h, &s, &info, sizeof info,
okw::fs_size_information_class);
if (!okw::ok(r)) return okw::translate_nt(r);

const kal_u64 unit = static_cast<kal_u64>(info.sectors_per_unit)
* static_cast<kal_u64>(info.bytes_per_sector);
if (total) *total = static_cast<kal_u64>(info.total_allocation_units) * unit;
if (available) *available = static_cast<kal_u64>(info.available_allocation_units) * unit;
return kal_ok;
}

int kal_fs_mkdir(kal_dir base, const char* name, kal_uintptr len) {
void* root = dir_handle(base);
if (!root || !okw::acceptable(name, len)) return kal_err_invalid;
Expand Down Expand Up @@ -563,10 +657,59 @@ int kal_fs_list_next(kal_dir, kal_uintptr* iter,
// guessed: names on the volume this system is ordinarily installed on are
// compared without regard to case, and a volume attached to the same machine
// may be otherwise --- and a word per implementation could state neither.
// Whether the environment beneath actually performs a lock.
//
// ⚠️ ASKED ON A DIRECTORY, WHICH IS NOT A THING THIS SYSTEM LOCKS --- and that is
// what makes the question answerable without disturbing anything. A system that
// implements the operation refuses a directory as a wrong request; one that has
// not implemented it says so with a different value, and that difference is the
// whole of the enquiry. Nothing is locked either way.
//
// Answered once. It is a property of what is beneath this program rather than of
// a volume, so it does not vary between the directories one program holds.
static bool locking_available() {
static int cached = -1;
if (cached >= 0) return cached != 0;
const kal_uintptr count = kal_fs_preopen_count();
cached = 1;
if (count > 0) {
kal_dir probe{};
char name[8]; kal_uintptr len = 0;
if (kal_fs_preopen(0, &probe, name, sizeof name, &len) == kal_ok) {
void* h = dir_handle(probe);
if (h) {
okw::io_status_block iosb{};
okw_i64 off = 0, len2 = 1;
const long r = okw::NtLockFile(h, nullptr, nullptr, nullptr, &iosb,
&off, &len2, 0, 1, 1);
if (okw::ok(r)) okw::NtUnlockFile(h, &iosb, &off, &len2, 0);
else if (r == okw::status_not_implemented) cached = 0;
}
}
}
return cached != 0;
}

kal_uintptr kal_fs_props(kal_dir d) {
void* h = dir_handle(d);
// ⚠️⚠️ LOCKING IS ASKED ABOUT RATHER THAN ASSUMED, AND THE REASON IS NOT
// THE VOLUME.
//
// This system locks a byte range, and the three continuous-integration rows
// that run on it measure that it does. A FOURTH row cross-builds and runs
// the result under an emulator of this system --- which EXPORTS the call and
// answers `STATUS_NOT_IMPLEMENTED' when it is made.
//
// ⭐ So the property is not a property of the volume here, nor of the
// format: it is a property of what is beneath the program at the moment it
// asks. A word that claimed the position regardless would be describing the
// INTERFACE rather than the environment --- and the whole purpose of a
// capability word is that a caller may ask before it calls and be told the
// truth about where it is.
const kal_uintptr lockable = locking_available() ? KAL_FS_PROP_LOCKS : 0;
const kal_uintptr conservative =
KAL_FS_PROP_MODIFIED_TIME | KAL_FS_PROP_ATOMIC_RENAME;
KAL_FS_PROP_MODIFIED_TIME | KAL_FS_PROP_ATOMIC_RENAME
| lockable | KAL_FS_PROP_CAPACITY;
if (!h) return 0;

okw::io_status_block s{};
Expand Down
22 changes: 22 additions & 0 deletions src/process.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,28 @@ void kal_process_close(kal_process p) {
// KAL_PROCESS_PROP_GRANT_DIR is deliberately absent: kal_process_spawn_with
// refuses a non-empty set of grants here, and a word claiming a facility the
// next call refuses is the disagreement clause 6.2 exists to prevent.
// Starting a program whose lifetime is bound to this one's. Version 0.10.
//
// ⚠️⚠️ REFUSED HERE, 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 to it closes, which this system does when a process dies
// however it dies. That is exactly the binding openkal describes.
//
// ⚠️ IT IS NOT CLAIMED IN THIS RELEASE 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 the next thing this implementation
// should do, and it is recorded as that rather than as an absence.
//
// A caller that asks `kal_process_props' 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
Expand Down
21 changes: 21 additions & 0 deletions src/task.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,27 @@ 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: the
// property word says whether contexts run at once and not how many can, so a C
// library above answered 1 with no error and a program sizing a pool of workers
// got one worker.
//
// ⭐ 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.
kal_uintptr kal_task_parallelism(void) {
SYSTEM_INFO info{};
GetSystemInfo(&info);
kal_uintptr count = 0;
for (unsigned long long bit = info.dwActiveProcessorMask; bit; bit &= bit - 1)
++count;
if (count) return count;
// Zero is "cannot say", and openkal distinguishes it from one on purpose.
return static_cast<kal_uintptr>(info.dwNumberOfProcessors);
}

int kal_task_wake(const kal_u32* word, kal_uintptr count, kal_uintptr* woken) {
void* address = const_cast<void*>(static_cast<const void*>(word));
if (count == 0) { if (woken) *woken = 0; return kal_ok; }
Expand Down
14 changes: 14 additions & 0 deletions src/win.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,20 @@ int translate_win32(unsigned long e) {
case ERROR_ALREADY_EXISTS: return kal_err_exists;
case ERROR_DIR_NOT_EMPTY: return kal_err_not_empty;
case ERROR_DIRECTORY: return kal_err_not_directory;
// ⚠️⚠️ A LOCK THAT ANOTHER HOLDER HAS IS `AGAIN' AND NOT AN
// INPUT-OUTPUT FAILURE, and this line is missing from every earlier
// release because nothing here took a lock until openkal 0.10.
//
// `kal_fs_lock' without KAL_LOCK_WAIT reports kal_err_again where the
// range is held, which is the answer a caller POLLS UPON. Falling to
// the arm below would have reported kal_err_io --- a failure of the
// device rather than a conflict with another holder --- and a caller
// reading that would stop rather than retry.
//
// ⭐ It is distinct from ERROR_SHARING_VIOLATION above, which stays
// `permission': that one is a conflict over how a file was OPENED and
// is not resolved by asking again.
case ERROR_LOCK_VIOLATION: return kal_err_again;
case ERROR_IO_PENDING: return kal_err_again;
default: return kal_err_io;
}
Expand Down
28 changes: 28 additions & 0 deletions src/win.h
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,17 @@ enum : int {
enum : int {
fs_volume_information_class = 1,
fs_attribute_information_class = 5,
fs_size_information_class = 3,
};

// How much the volume holds. The counts are in allocation units and the record
// says how many bytes one is, which is why capacity is two multiplications and
// not a field.
struct file_fs_size_information {
okw_i64 total_allocation_units;
okw_i64 available_allocation_units;
unsigned long sectors_per_unit;
unsigned long bytes_per_sector;
};

// The dispositions NtCreateFile takes. They are the whole of what
Expand Down Expand Up @@ -210,6 +221,16 @@ __declspec(dllimport) long __stdcall NtQueryDirectoryFile(void* handle, void* ev
int cls, unsigned char single, unicode_string* pattern,
unsigned char restart);
__declspec(dllimport) long __stdcall NtFlushBuffersFile(void* handle, io_status_block* status);

// ⭐ EXCLUSION IS PER-HANDLE ON THIS SYSTEM, which is exactly what openkal
// states: the holder is the `kal_file'. There is no second, process-held form
// to avoid here --- the thing the other two kernels have to reach past.
__declspec(dllimport) long __stdcall NtLockFile(void* handle, void* event, void* apc, void* apc_context,
io_status_block* status, okw_i64* offset, okw_i64* length,
unsigned long key, unsigned char fail_immediately,
unsigned char exclusive);
__declspec(dllimport) long __stdcall NtUnlockFile(void* handle, io_status_block* status,
okw_i64* offset, okw_i64* length, unsigned long key);
__declspec(dllimport) unsigned long __stdcall RtlNtStatusToDosError(long status);
}

Expand All @@ -224,6 +245,13 @@ inline bool ok(long status) { return status >= 0; }
// reported to have the same identity.
inline constexpr long status_buffer_overflow = static_cast<long>(0x80000005ul);

// ⚠️ AN ENVIRONMENT MAY EXPORT A NAME AND NOT IMPLEMENT WHAT IT NAMES, and the
// two are distinguishable only by this value. It is what a capability word has
// to consult before claiming a position: an operation whose export resolves and
// whose call answers this cannot be performed here, and a word claiming it would
// be describing the interface rather than the environment.
inline constexpr long status_not_implemented = static_cast<long>(0xC0000002ul);

// --- translation -------------------------------------------------------------
//
// The environment's error values are mapped onto the closed set the
Expand Down
1 change: 1 addition & 0 deletions src/win32.h
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@ enum : DWORD {
ERROR_OUTOFMEMORY = 14,
ERROR_WRITE_PROTECT = 19,
ERROR_SHARING_VIOLATION = 32,
ERROR_LOCK_VIOLATION = 33,
ERROR_HANDLE_EOF = 38,
ERROR_HANDLE_DISK_FULL = 39,
ERROR_NOT_SUPPORTED = 50,
Expand Down
Loading