Files
nt-lite/code/user/tests/file_lock_smoke/file_lock_smoke.c
T
maksw 73abd6f90d isolate POSIX builds from the host UCRT, wire lwext4 and ntfs-3g as ext4.sys and ntfs.sys, split the FAT driver into directory/file/async/driver/mount units, add posixsrv and dwm plus input/console/desktop syscall clients and a pile of new smoke targets, rename serial.sys to cereal.sys, name HDA/USB/lwIP/AHCI workers, harden ATA probing/reset/removal around actual hardware deadlines, absent-channel detection, diagnostics and explicit worker lifetime, redo ACPI S5 shutdown around GAS/MMIO and HW-reduced firmware, scan SSDTs for _S5 and deal with Intel SMI sleep trapping, make serial and SMP timeouts use real monotonic deadlines, add thread names and fix terminal thread teardown, scheduler and APC prepare/commit races, make advisory file locks queryable and preserve split ranges and add mutable file append state, add shared-memory pipes with kernel-assisted wait/wake, introduce capability-scoped input and display managers, split surface map rights into read/write, add explicit unmap and replace present with commit/generation/damage tracking while keeping physical presentation under the display manager, decouple native exception classes from Win32 exception numbers and translate them in kernel32, add opaque process startup data, bump the resource protocol to v3 with namespace directories, symlinks and enumeration and service control to v2 with generic READY handle exports, add the new POSIX errno/status surface and rebuild fds around posixsrv-owned shared open descriptions, descriptor-local flags, exact inheritance snapshots, dup/fcntl/record locks and shared-ring pipes, add waitpid, POSIX credentials/groups/sessions/passwd/group lookups and move sockets onto stable open descriptions, port the shells and apps to the new console/input services and surface commit API, teach init to load services.ini and start/supervise RSSRV, DWM and generic services under restricted capabilities while publishing their endpoints through RSSRV, split RSSRV into namespace, volume, display, session, input, console and resource components and make /SystemRoot a real symlink to the independently published system volume, shift the syscall table again, expand lock/resource/surface/POSIX/service/thread smoke coverage, make docs scream ASK QUESTIONS and DISCUSS at LLMs, replace MIT No Attribution with WTFPL+.
2026-09-16 10:33:20 +02:00

414 lines
14 KiB
C

// Advisory file-lock smoke test.
// Verifies process-owned byte-range conflicts, timeout behavior, and exit cleanup.
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include <ntlite/syscall.h>
#include <ntlite/path.h>
#include <ntlite/process_loader.h>
#define LOCK_OFFSET 0u
#define LOCK_LENGTH 128u
#define NONOVERLAP_OFFSET 512u
#define CHILD_PREFIX_MAX 32u
#define CHILD_PATH_MAX 256u
// Prints one NUL-terminated string to the kernel console.
static void print(const char* s) {
SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromCstr(s) };
SysWriteConsole(&req);
}
// Prints one unsigned integer to the kernel console.
static void print_u64(uint64_t v) {
char buf[32];
int i = 0;
if (v == 0) {
print("0");
return;
}
while (v && i < (int)sizeof(buf)) {
buf[i++] = (char)('0' + (v % 10));
v /= 10;
}
while (i--) {
char c = buf[i];
SYS_WRITE_CONSOLE_REQUEST req = { sizeof(SYS_WRITE_CONSOLE_REQUEST), SysSvFromParts(&c, 1) };
SysWriteConsole(&req);
}
}
// Terminates the current test process.
static void exit_now(int32_t code) {
while (true) SysExitProcess(code);
}
// Fails the test with one status value.
static void fail_status(const char* what, uint32_t status) {
print("file_lock_smoke: ");
print(what);
print(" status=");
print_u64(status);
print("\n");
exit_now(1);
}
// Fails the test with one plain message.
static void fail(const char* what) {
print("file_lock_smoke: ");
print(what);
print("\n");
exit_now(1);
}
// Deletes one path if it already exists.
static void delete_path(const char* path) {
(void)NtlDeleteFile(SysSvFromCstr(path));
}
// Opens one path with lock rights.
static uint32_t open_existing_lockable(const char* path, uint32_t* fileOut) {
uint32_t h = 0;
uint32_t s = NtlOpenFile(SysSvFromCstr(path),
SYS_FILE_ACCESS_QUERY | SYS_FILE_ACCESS_LOCK,
SYS_FILE_OPEN_EXISTING,
SYS_FILE_FLAG_NONE,
&h);
if (s == NTLSTATUS_SUCCESS)
*fileOut = h;
return s;
}
// Creates a temporary file on the first writable path that works.
static uint32_t create_temp_file(uint32_t* fileOut, const char** pathOut) {
static const char* paths[] = {
"file_lock_smoke.tmp",
};
for (size_t i = 0; i < sizeof(paths) / sizeof(paths[0]); i++) {
uint32_t h = 0;
delete_path(paths[i]);
uint32_t s = NtlOpenFile(SysSvFromCstr(paths[i]),
SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_WRITE | SYS_FILE_ACCESS_QUERY | SYS_FILE_ACCESS_LOCK,
SYS_FILE_CREATE_NEW,
SYS_FILE_FLAG_NONE,
&h);
if (s == NTLSTATUS_SUCCESS) {
*fileOut = h;
*pathOut = paths[i];
return NTLSTATUS_SUCCESS;
}
}
return NTLSTATUS_NOT_FOUND;
}
// Locks a range through the public request ABI.
static uint32_t lock_range(uint32_t file, uint32_t mode, uint64_t offset, uint64_t length, uint64_t timeoutMs) {
SYS_LOCK_FILE_REQUEST req = {
sizeof(req),
file,
mode,
SYS_FILE_LOCK_FLAG_NONE,
offset,
length,
timeoutMs,
};
return SysLockFile(&req);
}
// Unlocks a range through the public request ABI.
static uint32_t unlock_range(uint32_t file, uint64_t offset, uint64_t length) {
SYS_UNLOCK_FILE_REQUEST req = {
sizeof(req),
file,
SYS_FILE_LOCK_FLAG_NONE,
offset,
length,
};
return SysUnlockFile(&req);
}
// Queries the first conflicting lock through the public request ABI.
static uint32_t query_range(uint32_t file, uint32_t mode, uint64_t offset, uint64_t length,
SYS_FILE_LOCK_INFO* infoOut) {
SYS_QUERY_FILE_LOCK_REQUEST req = {
sizeof(req),
file,
mode,
SYS_FILE_LOCK_FLAG_NONE,
offset,
length,
infoOut,
};
return SysQueryFileLock(&req);
}
// Splits the child command line into mode and path.
static int parse_child_command(SYS_STRING_VIEW view, char* mode, size_t modeCap, char* path, size_t pathCap) {
if (!view.Data || view.Length == 0)
return 0;
size_t sep = 0;
while (sep < view.Length && view.Data[sep] != ':')
sep++;
if (sep == view.Length || sep + 1 >= view.Length)
return 0;
if (sep + 1 > modeCap || view.Length - sep > pathCap)
return 0;
memcpy(mode, view.Data, sep);
mode[sep] = '\0';
memcpy(path, view.Data + sep + 1, view.Length - sep - 1);
path[view.Length - sep - 1] = '\0';
return 1;
}
// Returns whether two NUL-terminated strings are equal.
static int str_eq(const char* a, const char* b) {
return strcmp(a, b) == 0;
}
// Returns whether one mode name belongs to the child command protocol.
static int is_child_mode(const char* mode) {
return str_eq(mode, "excl-blocked") ||
str_eq(mode, "excl-timeout") ||
str_eq(mode, "excl-wait") ||
str_eq(mode, "shared-ok") ||
str_eq(mode, "excl-nonoverlap") ||
str_eq(mode, "query-exclusive") ||
str_eq(mode, "hold-exit");
}
// Child process entry for cross-process lock checks.
static void run_child(SYS_STRING_VIEW commandLine) {
char mode[CHILD_PREFIX_MAX];
char path[CHILD_PATH_MAX];
if (!parse_child_command(commandLine, mode, sizeof(mode), path, sizeof(path)))
exit_now(2);
uint32_t file = 0;
uint32_t s = open_existing_lockable(path, &file);
if (s != NTLSTATUS_SUCCESS)
exit_now(3);
if (str_eq(mode, "excl-blocked")) {
s = lock_range(file, SYS_FILE_LOCK_EXCLUSIVE, LOCK_OFFSET, LOCK_LENGTH, 0);
SysCloseHandle(file);
exit_now(s == NTLSTATUS_WOULD_BLOCK ? 0 : 4);
}
if (str_eq(mode, "excl-timeout")) {
s = lock_range(file, SYS_FILE_LOCK_EXCLUSIVE, LOCK_OFFSET, LOCK_LENGTH, 20);
SysCloseHandle(file);
exit_now(s == NTLSTATUS_TIMEOUT ? 0 : 5);
}
if (str_eq(mode, "excl-wait")) {
s = lock_range(file, SYS_FILE_LOCK_EXCLUSIVE, LOCK_OFFSET, LOCK_LENGTH, SYS_WAIT_TIMEOUT_INFINITE);
if (s == NTLSTATUS_SUCCESS)
(void)unlock_range(file, LOCK_OFFSET, LOCK_LENGTH);
SysCloseHandle(file);
exit_now(s == NTLSTATUS_SUCCESS ? 0 : 6);
}
if (str_eq(mode, "shared-ok")) {
s = lock_range(file, SYS_FILE_LOCK_SHARED, LOCK_OFFSET, LOCK_LENGTH, 0);
if (s == NTLSTATUS_SUCCESS)
(void)unlock_range(file, LOCK_OFFSET, LOCK_LENGTH);
SysCloseHandle(file);
exit_now(s == NTLSTATUS_SUCCESS ? 0 : 7);
}
if (str_eq(mode, "excl-nonoverlap")) {
s = lock_range(file, SYS_FILE_LOCK_EXCLUSIVE, NONOVERLAP_OFFSET, LOCK_LENGTH, 0);
if (s == NTLSTATUS_SUCCESS)
(void)unlock_range(file, NONOVERLAP_OFFSET, LOCK_LENGTH);
SysCloseHandle(file);
exit_now(s == NTLSTATUS_SUCCESS ? 0 : 8);
}
if (str_eq(mode, "query-exclusive")) {
SYS_FILE_LOCK_INFO info;
memset(&info, 0, sizeof(info));
s = query_range(file, SYS_FILE_LOCK_EXCLUSIVE, LOCK_OFFSET, LOCK_LENGTH, &info);
SysCloseHandle(file);
exit_now(s == NTLSTATUS_SUCCESS &&
info.Mode == SYS_FILE_LOCK_EXCLUSIVE &&
info.Offset == LOCK_OFFSET && info.Length == LOCK_LENGTH &&
info.OwnerProcessId != 0 ? 0 : 11);
}
if (str_eq(mode, "hold-exit")) {
s = lock_range(file, SYS_FILE_LOCK_EXCLUSIVE, LOCK_OFFSET, LOCK_LENGTH, 0);
SysCloseHandle(file);
exit_now(s == NTLSTATUS_SUCCESS ? 0 : 9);
}
SysCloseHandle(file);
exit_now(10);
}
// Builds a child command line of the form mode:path.
static void build_child_command(char* out, size_t outCap, const char* mode, const char* path) {
size_t modeLen = strlen(mode);
size_t pathLen = strlen(path);
if (modeLen + 1 + pathLen + 1 > outCap)
fail("child command too long");
memcpy(out, mode, modeLen);
out[modeLen] = ':';
memcpy(out + modeLen + 1, path, pathLen + 1);
}
// Runs this image as a child and requires a zero exit code.
static void run_child_expect_success(const char* mode, const char* path) {
SYS_PEB* peb = SysGetPeb();
if (!peb || !peb->ImagePath.Data || peb->ImagePath.Length == 0)
fail("missing image path");
char command[CHILD_PREFIX_MAX + CHILD_PATH_MAX];
build_child_command(command, sizeof(command), mode, path);
uint32_t child = 0;
NTL_CREATE_SYSTEM_PROCESS_REQUEST req;
memset(&req, 0, sizeof(req));
req.Size = sizeof(req);
req.Flags = NTL_PROCESS_LOAD_FLAG_NONE;
req.Path = peb->ImagePath;
req.CommandLine = SysSvFromCstr(command);
req.SecurityContext = SYS_SECURITY_CONTEXT_CURRENT;
req.ResolveModuleName = NtlResolveSystemModuleName;
req.ProcessHandleOut = &child;
uint32_t s = NtlCreateSystemProcess(&req);
if (s != NTLSTATUS_SUCCESS) fail_status("create child", s);
s = SysWait(child, SYS_WAIT_FLAG_NONE);
if (s != NTLSTATUS_SUCCESS) fail_status("wait child", s);
int32_t exitCode = 0;
s = SysGetProcessExitCode(child, &exitCode);
if (s != NTLSTATUS_SUCCESS) fail_status("query child", s);
SysCloseHandle(child);
if (exitCode != 0) {
print("file_lock_smoke: child ");
print(mode);
print(" exited ");
print_u64((uint32_t)exitCode);
print("\n");
exit_now(1);
}
}
// Verifies validation paths that should not depend on filesystem state.
static void test_validation(uint32_t file) {
SYS_LOCK_FILE_REQUEST lockReq = {
0,
file,
SYS_FILE_LOCK_EXCLUSIVE,
SYS_FILE_LOCK_FLAG_NONE,
LOCK_OFFSET,
LOCK_LENGTH,
0,
};
uint32_t s = SysLockFile(&lockReq);
if (s != NTLSTATUS_INVALID_PARAMETER) fail_status("bad lock size", s);
lockReq.Size = sizeof(lockReq);
lockReq.Length = 0;
s = SysLockFile(&lockReq);
if (s != NTLSTATUS_INVALID_PARAMETER) fail_status("zero lock length", s);
SYS_UNLOCK_FILE_REQUEST unlockReq = {
sizeof(unlockReq),
file,
SYS_FILE_LOCK_FLAG_NONE,
LOCK_OFFSET,
LOCK_LENGTH,
};
s = SysUnlockFile(&unlockReq);
if (s != NTLSTATUS_NOT_FOUND) fail_status("unlock missing range", s);
lockReq.Length = LOCK_LENGTH;
lockReq.FileHandle = 0xeeeeeeeeu;
s = SysLockFile(&lockReq);
if (s != NTLSTATUS_INVALID_HANDLE) fail_status("bad lock handle", s);
}
// Program entry point.
int main(void) {
SYS_PEB* peb = SysGetPeb();
if (peb && peb->CommandLine.Data && peb->CommandLine.Length != 0) {
char mode[CHILD_PREFIX_MAX];
char path[CHILD_PATH_MAX];
if (parse_child_command(peb->CommandLine, mode, sizeof(mode), path, sizeof(path)) &&
is_child_mode(mode)) {
run_child(peb->CommandLine);
}
}
print("file_lock_smoke: start\n");
uint32_t file = 0;
const char* path = 0;
uint32_t s = create_temp_file(&file, &path);
if (s != NTLSTATUS_SUCCESS) fail_status("create temp", s);
test_validation(file);
s = lock_range(file, SYS_FILE_LOCK_EXCLUSIVE, LOCK_OFFSET, LOCK_LENGTH, 0);
if (s != NTLSTATUS_SUCCESS) fail_status("exclusive parent lock", s);
run_child_expect_success("excl-blocked", path);
run_child_expect_success("query-exclusive", path);
run_child_expect_success("excl-timeout", path);
run_child_expect_success("excl-nonoverlap", path);
s = unlock_range(file, LOCK_OFFSET, LOCK_LENGTH);
if (s != NTLSTATUS_SUCCESS) fail_status("exclusive parent unlock", s);
s = lock_range(file, SYS_FILE_LOCK_SHARED, LOCK_OFFSET, LOCK_LENGTH, 0);
if (s != NTLSTATUS_SUCCESS) fail_status("shared parent lock", s);
run_child_expect_success("shared-ok", path);
run_child_expect_success("excl-blocked", path);
s = unlock_range(file, LOCK_OFFSET, LOCK_LENGTH);
if (s != NTLSTATUS_SUCCESS) fail_status("shared parent unlock", s);
s = lock_range(file, SYS_FILE_LOCK_EXCLUSIVE, LOCK_OFFSET, LOCK_LENGTH, 0);
if (s != NTLSTATUS_SUCCESS) fail_status("blocking setup lock", s);
SYS_PEB* selfPeb = SysGetPeb();
if (!selfPeb || !selfPeb->ImagePath.Data || selfPeb->ImagePath.Length == 0) fail("missing self image");
char command[CHILD_PREFIX_MAX + CHILD_PATH_MAX];
build_child_command(command, sizeof(command), "excl-wait", path);
uint32_t child = 0;
NTL_CREATE_SYSTEM_PROCESS_REQUEST createReq;
memset(&createReq, 0, sizeof(createReq));
createReq.Size = sizeof(createReq);
createReq.Flags = NTL_PROCESS_LOAD_FLAG_NONE;
createReq.Path = selfPeb->ImagePath;
createReq.CommandLine = SysSvFromCstr(command);
createReq.SecurityContext = SYS_SECURITY_CONTEXT_CURRENT;
createReq.ResolveModuleName = NtlResolveSystemModuleName;
createReq.ProcessHandleOut = &child;
s = NtlCreateSystemProcess(&createReq);
if (s != NTLSTATUS_SUCCESS) fail_status("create blocking child", s);
SysSleep(30);
s = unlock_range(file, LOCK_OFFSET, LOCK_LENGTH);
if (s != NTLSTATUS_SUCCESS) fail_status("wake unlock", s);
s = SysWait(child, SYS_WAIT_FLAG_NONE);
if (s != NTLSTATUS_SUCCESS) fail_status("wait blocking child", s);
int32_t exitCode = 0;
s = SysGetProcessExitCode(child, &exitCode);
if (s != NTLSTATUS_SUCCESS) fail_status("query blocking child", s);
SysCloseHandle(child);
if (exitCode != 0) fail("blocking child failed");
run_child_expect_success("hold-exit", path);
s = lock_range(file, SYS_FILE_LOCK_EXCLUSIVE, LOCK_OFFSET, LOCK_LENGTH, 0);
if (s != NTLSTATUS_SUCCESS) fail_status("exit released lock", s);
s = unlock_range(file, LOCK_OFFSET, LOCK_LENGTH);
if (s != NTLSTATUS_SUCCESS) fail_status("exit release cleanup unlock", s);
SysCloseHandle(file);
delete_path(path);
print("file_lock_smoke: PASS\n");
exit_now(0);
return 0;
}