Files
nt-lite/code/user/tests/file_lock_smoke/file_lock_smoke.c
T

378 lines
13 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>
#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 = { 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 = { SysSvFromParts(&c, 1) };
SysWriteConsole(&req);
}
}
// Terminates the current test process.
static void exit_now(int32_t code) {
for (;;) 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) {
SYS_PATH_REQUEST req = { SysSvFromCstr(path) };
(void)SysDeleteFile(&req);
}
// Opens one path with lock rights.
static uint32_t open_existing_lockable(const char* path, uint32_t* fileOut) {
uint32_t h = 0;
SYS_OPEN_FILE_REQUEST req = SysMakeOpenFileRequest(SysSvFromCstr(path),
SYS_FILE_ACCESS_QUERY | SYS_FILE_ACCESS_LOCK,
SYS_FILE_OPEN_EXISTING,
SYS_FILE_FLAG_NONE,
&h);
uint32_t s = SysOpenFile(&req);
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",
"/file_lock_smoke.tmp",
"1:/file_lock_smoke.tmp",
"0:/file_lock_smoke.tmp",
};
for (size_t i = 0; i < sizeof(paths) / sizeof(paths[0]); i++) {
uint32_t h = 0;
delete_path(paths[i]);
SYS_OPEN_FILE_REQUEST req = SysMakeOpenFileRequest(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);
uint32_t s = SysOpenFile(&req);
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);
}
// 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, "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, "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;
SYS_CREATE_PROCESS_REQUEST req = {
peb->ImagePath,
SysSvFromCstr(command),
&child,
};
uint32_t s = SysCreateProcess(&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("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;
SYS_CREATE_PROCESS_REQUEST createReq = { selfPeb->ImagePath, SysSvFromCstr(command), &child };
s = SysCreateProcess(&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;
}