236 lines
7.6 KiB
C
236 lines
7.6 KiB
C
// Native file metadata smoke test.
|
|
// Verifies path stat, handle stat, directory metadata, missing paths, and invalid handles without mutating the filesystem.
|
|
|
|
#include <stdint.h>
|
|
#include <stddef.h>
|
|
#include <string.h>
|
|
#include <ntlite/syscall.h>
|
|
|
|
static char gSelfPath[SYS_PATH_MAX];
|
|
static char gSelfDir[SYS_PATH_MAX];
|
|
|
|
// 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(int code) {
|
|
while (true) SysExitProcess(code);
|
|
}
|
|
|
|
// Fails the test with one plain message.
|
|
static void fail(const char* msg) {
|
|
print("file_stat_smoke: ");
|
|
print(msg);
|
|
print("\n");
|
|
exit_now(1);
|
|
}
|
|
|
|
// Fails the test with one status value.
|
|
static void fail_status(const char* what, uint32_t status) {
|
|
print("file_stat_smoke: ");
|
|
print(what);
|
|
print(" failed status=");
|
|
print_u64(status);
|
|
print("\n");
|
|
exit_now(1);
|
|
}
|
|
|
|
// Copies one string view into a fixed NUL-terminated buffer.
|
|
static int copy_view(char* out, size_t outSize, SYS_STRING_VIEW view) {
|
|
if (!out || outSize == 0 || !view.Data || view.Length + 1 > outSize)
|
|
return 0;
|
|
memcpy(out, view.Data, view.Length);
|
|
out[view.Length] = '\0';
|
|
return 1;
|
|
}
|
|
|
|
// Builds the containing directory path for one process image path.
|
|
static void build_parent_dir(char* out, size_t outSize, const char* path) {
|
|
size_t len = strlen(path);
|
|
size_t slash = (size_t)-1;
|
|
for (size_t i = 0; i < len; ++i) {
|
|
if (path[i] == '/' || path[i] == '\\')
|
|
slash = i;
|
|
}
|
|
|
|
if (slash == (size_t)-1) {
|
|
if (outSize < 2) fail("parent directory buffer too small");
|
|
out[0] = '.';
|
|
out[1] = '\0';
|
|
return;
|
|
}
|
|
|
|
if (slash == 0) {
|
|
if (outSize < 2) fail("parent directory buffer too small");
|
|
out[0] = path[0];
|
|
out[1] = '\0';
|
|
return;
|
|
}
|
|
|
|
if (slash + 1 >= outSize)
|
|
fail("parent directory path too long");
|
|
memcpy(out, path, slash);
|
|
out[slash] = '\0';
|
|
}
|
|
|
|
// Opens this process image for read/query metadata checks.
|
|
static uint32_t open_self_image(uint32_t* fileOut, uint64_t* sizeOut) {
|
|
SYS_PEB* peb = SysGetPeb();
|
|
if (!peb || peb->Magic != SYS_PEB_MAGIC || peb->Version != SYS_ENV_VERSION)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
if (!copy_view(gSelfPath, sizeof(gSelfPath), peb->ImagePath))
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
build_parent_dir(gSelfDir, sizeof(gSelfDir), gSelfPath);
|
|
|
|
uint32_t h = 0;
|
|
SYS_OPEN_FILE_REQUEST req = SysMakeOpenFileRequest(SysSvFromCstr(gSelfPath),
|
|
SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_QUERY,
|
|
SYS_FILE_OPEN_EXISTING,
|
|
SYS_FILE_FLAG_NONE,
|
|
&h);
|
|
uint32_t s = SysOpenFile(&req);
|
|
if (s != NTLSTATUS_SUCCESS)
|
|
return s;
|
|
|
|
uint64_t size = 0;
|
|
s = SysGetFileSize(h, &size);
|
|
if (s != NTLSTATUS_SUCCESS) {
|
|
SysCloseHandle(h);
|
|
return s;
|
|
}
|
|
if (size == 0) {
|
|
SysCloseHandle(h);
|
|
return NTLSTATUS_NOT_FOUND;
|
|
}
|
|
|
|
*fileOut = h;
|
|
*sizeOut = size;
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
// Checks one regular-file metadata result.
|
|
static void check_regular_stat(const SYS_FILE_STAT* stat, uint64_t expectedSize, const char* what) {
|
|
if (stat->Size != sizeof(*stat)) fail("stat abi size mismatch");
|
|
if (stat->Type != SYS_FILE_TYPE_REGULAR) {
|
|
print("file_stat_smoke: ");
|
|
print(what);
|
|
print(" type=");
|
|
print_u64(stat->Type);
|
|
print("\n");
|
|
exit_now(1);
|
|
}
|
|
if ((stat->Attributes & SYS_FILE_ATTR_DIRECTORY) != 0) fail("regular file has directory attribute");
|
|
if (stat->LinkCount == 0) fail("regular file has zero links");
|
|
if (stat->FileSize != expectedSize) {
|
|
print("file_stat_smoke: ");
|
|
print(what);
|
|
print(" size=");
|
|
print_u64(stat->FileSize);
|
|
print(" expected=");
|
|
print_u64(expectedSize);
|
|
print("\n");
|
|
exit_now(1);
|
|
}
|
|
}
|
|
|
|
// Checks one directory metadata result.
|
|
static void check_directory_stat(const SYS_FILE_STAT* stat) {
|
|
if (stat->Size != sizeof(*stat)) fail("directory stat abi size mismatch");
|
|
if (stat->Type != SYS_FILE_TYPE_DIRECTORY) fail("directory stat type mismatch");
|
|
if ((stat->Attributes & SYS_FILE_ATTR_DIRECTORY) == 0) fail("directory attribute missing");
|
|
if (stat->LinkCount == 0) fail("directory has zero links");
|
|
}
|
|
|
|
// Exercises handle and path stat for this process image.
|
|
static void test_file_stat(uint32_t file, uint64_t expectedSize) {
|
|
SYS_FILE_STAT byHandle;
|
|
memset(&byHandle, 0xA5, sizeof(byHandle));
|
|
uint32_t s = SysGetFileStat(file, &byHandle);
|
|
if (s != NTLSTATUS_SUCCESS) fail_status("get file stat", s);
|
|
check_regular_stat(&byHandle, expectedSize, "handle stat");
|
|
|
|
uint64_t size = 0;
|
|
s = SysGetFileSize(file, &size);
|
|
if (s != NTLSTATUS_SUCCESS) fail_status("get file size", s);
|
|
if (size != byHandle.FileSize) fail("file size and file stat disagree");
|
|
|
|
SYS_FILE_STAT byPath;
|
|
memset(&byPath, 0x5A, sizeof(byPath));
|
|
SYS_STAT_FILE_REQUEST statReq = { SysSvFromCstr(gSelfPath), &byPath };
|
|
s = SysStatFile(&statReq);
|
|
if (s != NTLSTATUS_SUCCESS) fail_status("path stat", s);
|
|
check_regular_stat(&byPath, expectedSize, "path stat");
|
|
|
|
if (byHandle.VolumeId != 0 && byPath.VolumeId != 0 && byHandle.VolumeId != byPath.VolumeId)
|
|
fail("handle and path volume ids differ");
|
|
if (byHandle.FileId != 0 && byPath.FileId != 0 && byHandle.FileId != byPath.FileId)
|
|
fail("handle and path file ids differ");
|
|
}
|
|
|
|
// Exercises path stat for the executable's containing directory.
|
|
static void test_directory_stat(void) {
|
|
SYS_FILE_STAT stat;
|
|
memset(&stat, 0xCC, sizeof(stat));
|
|
SYS_STAT_FILE_REQUEST statReq = { SysSvFromCstr(gSelfDir), &stat };
|
|
uint32_t s = SysStatFile(&statReq);
|
|
if (s != NTLSTATUS_SUCCESS) fail_status("directory stat", s);
|
|
check_directory_stat(&stat);
|
|
}
|
|
|
|
// Checks failure status paths that stat callers depend on.
|
|
static void test_failure_paths(void) {
|
|
SYS_FILE_STAT stat;
|
|
SYS_STAT_FILE_REQUEST missingReq = { SysSvFromCstr("file_stat_smoke_missing.nope"), &stat };
|
|
uint32_t s = SysStatFile(&missingReq);
|
|
if (s != NTLSTATUS_NOT_FOUND) fail_status("missing path status", s);
|
|
|
|
s = SysGetFileStat(0xFFFFFFFFu, &stat);
|
|
if (s != NTLSTATUS_INVALID_HANDLE) fail_status("invalid handle status", s);
|
|
|
|
SYS_STAT_FILE_REQUEST badReq = { SysSvFromCstr("file_stat_smoke_missing.nope"), 0 };
|
|
s = SysStatFile(&badReq);
|
|
if (s != NTLSTATUS_INVALID_PARAMETER) fail_status("null stat output status", s);
|
|
}
|
|
|
|
// Runs the native file metadata smoke test.
|
|
int main(void) {
|
|
print("file_stat_smoke: start\n");
|
|
|
|
uint32_t file = 0;
|
|
uint64_t size = 0;
|
|
uint32_t s = open_self_image(&file, &size);
|
|
if (s != NTLSTATUS_SUCCESS) fail_status("open self image", s);
|
|
|
|
test_file_stat(file, size);
|
|
SysCloseHandle(file);
|
|
|
|
test_directory_stat();
|
|
test_failure_paths();
|
|
|
|
print("file_stat_smoke: PASS\n");
|
|
exit_now(0);
|
|
return 0;
|
|
}
|