Files
nt-lite/code/user/personalities/posix/posix_io.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

555 lines
19 KiB
C

// POSIX compatibility support for byte-stream file I/O.
#include <stddef.h>
#include <string.h>
#include <intrin.h>
#include <ntlite/syscall.h>
#include <ntlite/console_service.h>
#include <ntlite/path.h>
#include "posix_internal.h"
#include "posix_fd.h"
#include "posix_path.h"
// Returns whether one open description permits reading.
static bool PosixDescriptionReadable(const POSIX_OPEN_DESCRIPTION* description) {
return description && (PosixFdGetStatusFlags(description) & O_ACCMODE) != O_WRONLY;
}
// Returns whether one open description permits writing.
static bool PosixDescriptionWritable(const POSIX_OPEN_DESCRIPTION* description) {
return description && (PosixFdGetStatusFlags(description) & O_ACCMODE) != O_RDONLY;
}
// Returns the server-owned shared ring behind one pipe open description.
static SYS_PIPE_SHARED_STATE* PosixPipeShared(POSIX_OPEN_DESCRIPTION* description) {
if (!description || !description->Shared || description->Shared->SharedObjectData == 0) {
errno = EIO;
return (SYS_PIPE_SHARED_STATE*)0;
}
SYS_PIPE_SHARED_STATE* shared =
(SYS_PIPE_SHARED_STATE*)(uintptr_t)description->Shared->SharedObjectData;
if (shared->Magic != SYS_PIPE_SHARED_MAGIC || shared->Version != SYS_PIPE_SHARED_VERSION ||
shared->Capacity != SYS_PIPE_BUFFER_CAPACITY ||
shared->HeaderBytes != SYS_PIPE_SHARED_HEADER_BYTES) {
errno = EIO;
return (SYS_PIPE_SHARED_STATE*)0;
}
return shared;
}
// Acquires one shared pipe-side serialization lock without entering the kernel.
static void PosixPipeLock(volatile long* lock) {
while (_InterlockedCompareExchange(lock, 1, 0) != 0)
_mm_pause();
_ReadWriteBarrier();
}
// Releases one shared pipe-side serialization lock.
static void PosixPipeUnlock(volatile long* lock) {
_ReadWriteBarrier();
_InterlockedExchange(lock, 0);
}
// Returns buffered bytes, rejecting corrupted shared counters.
static int PosixPipeUsed(SYS_PIPE_SHARED_STATE* shared, uint64_t* usedOut) {
uint64_t readPosition = shared->ReadPosition;
_ReadWriteBarrier();
uint64_t writePosition = shared->WritePosition;
uint64_t used = writePosition - readPosition;
if (used > SYS_PIPE_BUFFER_CAPACITY) {
errno = EIO;
return -1;
}
*usedOut = used;
return 0;
}
// Copies bytes out of the shared circular pipe buffer.
static void PosixPipeCopyOut(SYS_PIPE_SHARED_STATE* shared, uint64_t readPosition,
void* buffer, size_t length) {
size_t offset = (size_t)(readPosition % SYS_PIPE_BUFFER_CAPACITY);
size_t first = SYS_PIPE_BUFFER_CAPACITY - offset;
if (first > length) first = length;
memcpy(buffer, shared->Buffer + offset, first);
if (first < length)
memcpy((uint8_t*)buffer + first, shared->Buffer, length - first);
}
// Copies bytes into the shared circular pipe buffer.
static void PosixPipeCopyIn(SYS_PIPE_SHARED_STATE* shared, uint64_t writePosition,
const void* buffer, size_t length) {
size_t offset = (size_t)(writePosition % SYS_PIPE_BUFFER_CAPACITY);
size_t first = SYS_PIPE_BUFFER_CAPACITY - offset;
if (first > length) first = length;
memcpy(shared->Buffer + offset, buffer, first);
if (first < length)
memcpy(shared->Buffer, (const uint8_t*)buffer + first, length - first);
}
// Reads one pipe entirely through the shared user mapping except when blocking.
static ssize_t PosixPipeRead(POSIX_OPEN_DESCRIPTION* description, void* buffer, size_t length) {
SYS_PIPE_SHARED_STATE* shared = PosixPipeShared(description);
if (!shared) return -1;
for (;;) {
PosixPipeLock(&shared->ReadLock);
uint64_t used = 0;
if (PosixPipeUsed(shared, &used) != 0) {
PosixPipeUnlock(&shared->ReadLock);
return -1;
}
if (used != 0) {
size_t transfer = length < used ? length : (size_t)used;
uint64_t readPosition = shared->ReadPosition;
PosixPipeCopyOut(shared, readPosition, buffer, transfer);
_ReadWriteBarrier();
shared->ReadPosition = readPosition + transfer;
_ReadWriteBarrier();
long writeWaiters = shared->WriteWaiters;
PosixPipeUnlock(&shared->ReadLock);
if (writeWaiters != 0)
(void)SysNotifyPipe(description->KHandle);
return (ssize_t)transfer;
}
if (shared->WriterOpen == 0) {
PosixPipeUnlock(&shared->ReadLock);
return 0;
}
if (PosixFdGetStatusFlags(description) & O_NONBLOCK) {
PosixPipeUnlock(&shared->ReadLock);
errno = EAGAIN;
return -1;
}
PosixPipeUnlock(&shared->ReadLock);
uint32_t status = SysWait(description->KHandle, SYS_WAIT_FLAG_NONE);
if (status != NTLSTATUS_SUCCESS) {
errno = PosixStatusToErrno(status);
return -1;
}
}
}
// Writes one pipe entirely through the shared user mapping except when blocking/waking.
static ssize_t PosixPipeWrite(POSIX_OPEN_DESCRIPTION* description, const void* buffer, size_t length) {
SYS_PIPE_SHARED_STATE* shared = PosixPipeShared(description);
if (!shared) return -1;
for (;;) {
PosixPipeLock(&shared->WriteLock);
if (shared->ReaderOpen == 0) {
PosixPipeUnlock(&shared->WriteLock);
errno = EPIPE;
return -1;
}
uint64_t used = 0;
if (PosixPipeUsed(shared, &used) != 0) {
PosixPipeUnlock(&shared->WriteLock);
return -1;
}
size_t available = SYS_PIPE_BUFFER_CAPACITY - (size_t)used;
size_t transfer = 0;
if (length <= SYS_PIPE_ATOMIC_WRITE_MAX) {
if (available >= length)
transfer = length;
} else if (available != 0) {
transfer = length < available ? length : available;
}
if (transfer != 0) {
uint64_t writePosition = shared->WritePosition;
PosixPipeCopyIn(shared, writePosition, buffer, transfer);
_ReadWriteBarrier();
shared->WritePosition = writePosition + transfer;
_ReadWriteBarrier();
long readWaiters = shared->ReadWaiters;
PosixPipeUnlock(&shared->WriteLock);
if (readWaiters != 0)
(void)SysNotifyPipe(description->KHandle);
return (ssize_t)transfer;
}
if (PosixFdGetStatusFlags(description) & O_NONBLOCK) {
PosixPipeUnlock(&shared->WriteLock);
errno = EAGAIN;
return -1;
}
PosixPipeUnlock(&shared->WriteLock);
uint32_t status = SysWait(description->KHandle, SYS_WAIT_FLAG_NONE);
if (status != NTLSTATUS_SUCCESS) {
errno = PosixStatusToErrno(status);
return -1;
}
}
}
// Clears the native console display.
__declspec(dllexport) void clearscreen(void) {
(void)NtlConsoleClear(SYS_HANDLE_INVALID, 0x012456);
}
// Flushes pending native console keyboard input.
__declspec(dllexport) void clearkeyboard(void) {
(void)NtlConsoleFlushInput(SYS_HANDLE_INVALID);
}
// Opens one native file or synthetic POSIX device and returns a process-local descriptor.
__declspec(dllexport) int open(const char* path, int flags, ...) {
if (!path || (flags & O_ACCMODE) == O_ACCMODE) {
errno = EINVAL;
return -1;
}
char native[512];
const char* p = PosixTranslatePath(path, native, sizeof(native));
int statusFlags = flags & (O_ACCMODE | O_APPEND | O_NONBLOCK);
if (strcmp(p, SYNTH_DEVNULL) == 0)
return PosixFdCreate(PosixFdDevNull, SYS_HANDLE_INVALID, statusFlags);
if (strcmp(p, SYNTH_DEVZERO) == 0)
return PosixFdCreate(PosixFdDevZero, SYS_HANDLE_INVALID, statusFlags);
uint32_t kh = SYS_HANDLE_INVALID;
uint32_t access = SYS_FILE_ACCESS_QUERY | SYS_FILE_ACCESS_LOCK;
uint32_t disposition = SYS_FILE_OPEN_EXISTING;
if ((flags & O_ACCMODE) == O_RDWR)
access |= SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_WRITE;
else if ((flags & O_ACCMODE) == O_WRONLY)
access |= SYS_FILE_ACCESS_WRITE;
else
access |= SYS_FILE_ACCESS_READ;
if (flags & O_APPEND)
access |= SYS_FILE_ACCESS_APPEND;
if ((flags & O_CREAT) && (flags & O_TRUNC))
disposition = SYS_FILE_CREATE_ALWAYS;
else if (flags & O_CREAT)
disposition = SYS_FILE_OPEN_ALWAYS;
else if (flags & O_TRUNC)
disposition = SYS_FILE_TRUNCATE_EXISTING;
uint32_t status = NtlOpenFile(SysSvFromCstr(p), access, disposition,
SYS_FILE_FLAG_NONE, &kh);
if (status != NTLSTATUS_SUCCESS) {
errno = PosixStatusToErrno(status);
return -1;
}
int fd = PosixFdCreate(PosixFdFile, kh, statusFlags);
if (fd < 0)
SysCloseHandle(kh);
return fd;
}
// Reads bytes from one descriptor through its stable open-description reference.
__declspec(dllexport) ssize_t read(int fd, void* buf, size_t count) {
if (count == 0)
return 0;
if (!buf) {
errno = EINVAL;
return -1;
}
POSIX_OPEN_DESCRIPTION* description = PosixFdAcquire(fd);
if (!description)
return -1;
if (!PosixDescriptionReadable(description)) {
PosixFdRelease(description);
errno = EBADF;
return -1;
}
ssize_t result = -1;
switch (description->Type) {
case PosixFdConsoleRead: {
char* dst = (char*)buf;
result = (ssize_t)count;
for (size_t i = 0; i < count; i++) {
uint32_t status = NtlConsoleReadCharacter(SYS_HANDLE_INVALID, &dst[i]);
if (status != NTLSTATUS_SUCCESS) {
errno = PosixStatusToErrno(status);
result = -1;
break;
}
if (dst[i] == '\n' || dst[i] == '\r') {
result = (ssize_t)(i + 1u);
break;
}
}
break;
}
case PosixFdDevNull:
result = 0;
break;
case PosixFdDevZero:
memset(buf, 0, count);
result = (ssize_t)count;
break;
case PosixFdFile: {
size_t got = 0;
SYS_FILE_IO_REQUEST request =
SysMakeFileIoRequest(description->KHandle, buf, count, &got);
uint32_t status = SysReadFile(&request);
if (status != NTLSTATUS_SUCCESS && got == 0)
errno = PosixStatusToErrno(status);
else
result = (ssize_t)got;
break;
}
case PosixFdPipeRead:
result = PosixPipeRead(description, buf, count);
break;
case PosixFdSocket: {
size_t got = 0;
SYS_SOCKET_RECV_REQUEST request = {
sizeof(SYS_SOCKET_RECV_REQUEST), description->KHandle, 0,
buf, count, &got
};
uint32_t status = SysRecvSocket(&request);
if (status != NTLSTATUS_SUCCESS)
errno = PosixStatusToErrno(status);
else
result = (ssize_t)got;
break;
}
default:
errno = EBADF;
break;
}
PosixFdRelease(description);
return result;
}
// Writes bytes through one descriptor's shared open description.
__declspec(dllexport) ssize_t write(int fd, const void* buf, size_t count) {
if (count == 0)
return 0;
if (!buf) {
errno = EINVAL;
return -1;
}
POSIX_OPEN_DESCRIPTION* description = PosixFdAcquire(fd);
if (!description)
return -1;
if (!PosixDescriptionWritable(description)) {
PosixFdRelease(description);
errno = EBADF;
return -1;
}
ssize_t result = -1;
switch (description->Type) {
case PosixFdConsoleWrite: {
size_t written = 0;
uint32_t status = NtlConsoleWrite(SYS_HANDLE_INVALID, buf, count, &written);
if (status != NTLSTATUS_SUCCESS && written == 0)
errno = PosixStatusToErrno(status);
else
result = (ssize_t)written;
break;
}
case PosixFdDevNull:
case PosixFdDevZero:
result = (ssize_t)count;
break;
case PosixFdFile: {
size_t written = 0;
SYS_FILE_IO_REQUEST request =
SysMakeFileIoRequest(description->KHandle, (void*)buf, count, &written);
uint32_t status = SysWriteFile(&request);
if (status != NTLSTATUS_SUCCESS && written == 0)
errno = PosixStatusToErrno(status);
else
result = (ssize_t)written;
break;
}
case PosixFdPipeWrite:
result = PosixPipeWrite(description, buf, count);
break;
case PosixFdSocket: {
size_t sent = 0;
SYS_SOCKET_SEND_REQUEST request = {
sizeof(SYS_SOCKET_SEND_REQUEST), description->KHandle, 0,
buf, count, &sent
};
uint32_t status = SysSendSocket(&request);
if (status != NTLSTATUS_SUCCESS)
errno = PosixStatusToErrno(status);
else
result = (ssize_t)sent;
break;
}
default:
errno = EBADF;
break;
}
PosixFdRelease(description);
return result;
}
// Closes one descriptor without invalidating concurrent open-description users.
__declspec(dllexport) int close(int fd) {
return PosixFdClose(fd);
}
// Creates one anonymous pipe whose backing and master endpoint capabilities are owned by posixsrv.
__declspec(dllexport) int pipe(int pipefd[2]) {
if (!pipefd) {
errno = EINVAL;
return -1;
}
NTL_POSIX_REQUEST request;
memset(&request, 0, sizeof(request));
request.Size = sizeof(request);
request.Version = NTL_POSIX_PROTOCOL_VERSION;
request.Operation = NTL_POSIX_OP_CREATE_PIPE;
request.Flags = NTL_POSIX_REQUEST_FLAG_NONE;
SYS_HANDLE handles[2] = { SYS_HANDLE_INVALID, SYS_HANDLE_INVALID };
NTL_POSIX_REPLY reply;
KSTATUS status = PosixRuntimeCallServerExchange(
&request, (const SYS_PORT_HANDLE_TRANSFER*)0, 0, handles, 2u, 2u, &reply);
if (!KSUCCESS(status) || !KSUCCESS(reply.Status)) {
for (uint32_t i = 0; i < 2u; ++i) {
if (handles[i] != SYS_HANDLE_INVALID)
(void)SysCloseHandle(handles[i]);
}
errno = PosixStatusToErrno(KSUCCESS(status) ? reply.Status : status);
return -1;
}
NTL_POSIX_SHARED_OPEN_DESCRIPTION* readShared =
(NTL_POSIX_SHARED_OPEN_DESCRIPTION*)(uintptr_t)reply.SharedOpenDescription;
NTL_POSIX_SHARED_OPEN_DESCRIPTION* writeShared =
(NTL_POSIX_SHARED_OPEN_DESCRIPTION*)(uintptr_t)reply.SharedOpenDescription2;
SYS_PIPE_SHARED_STATE* pipeShared = readShared ?
(SYS_PIPE_SHARED_STATE*)(uintptr_t)readShared->SharedObjectData :
(SYS_PIPE_SHARED_STATE*)0;
int valid = handles[0] != SYS_HANDLE_INVALID && handles[1] != SYS_HANDLE_INVALID &&
reply.OpenDescriptionId != 0 && reply.OpenDescriptionId2 != 0 &&
reply.OpenDescriptionId != reply.OpenDescriptionId2 &&
reply.OpenDescriptionType == NTL_POSIX_OPEN_TYPE_PIPE_READ &&
reply.OpenDescriptionType2 == NTL_POSIX_OPEN_TYPE_PIPE_WRITE &&
reply.OpenStatusFlags == NTL_POSIX_OPEN_STATUS_RDONLY &&
reply.OpenStatusFlags2 == NTL_POSIX_OPEN_STATUS_WRONLY &&
readShared && writeShared &&
readShared->Magic == NTL_POSIX_SHARED_OPEN_MAGIC &&
writeShared->Magic == NTL_POSIX_SHARED_OPEN_MAGIC &&
readShared->Version == NTL_POSIX_SHARED_OPEN_VERSION &&
writeShared->Version == NTL_POSIX_SHARED_OPEN_VERSION &&
readShared->Type == NTL_POSIX_OPEN_TYPE_PIPE_READ &&
writeShared->Type == NTL_POSIX_OPEN_TYPE_PIPE_WRITE &&
readShared->DescriptionId == reply.OpenDescriptionId &&
writeShared->DescriptionId == reply.OpenDescriptionId2 &&
readShared->Reserved == 0 && writeShared->Reserved == 0 &&
readShared->Reserved2 == 0 && writeShared->Reserved2 == 0 &&
readShared->SharedObjectData != 0 &&
readShared->SharedObjectData == writeShared->SharedObjectData &&
pipeShared && pipeShared->Magic == SYS_PIPE_SHARED_MAGIC &&
pipeShared->Version == SYS_PIPE_SHARED_VERSION &&
pipeShared->Capacity == SYS_PIPE_BUFFER_CAPACITY &&
pipeShared->HeaderBytes == SYS_PIPE_SHARED_HEADER_BYTES;
if (!valid) {
(void)SysCloseHandle(handles[0]);
(void)SysCloseHandle(handles[1]);
PosixFdReleaseServerDescription(reply.OpenDescriptionId);
PosixFdReleaseServerDescription(reply.OpenDescriptionId2);
errno = EIO;
return -1;
}
int readFd = PosixFdImport(PosixFdPipeRead, handles[0], reply.OpenDescriptionId,
readShared, 0);
handles[0] = SYS_HANDLE_INVALID;
if (readFd < 0) {
(void)SysCloseHandle(handles[1]);
PosixFdReleaseServerDescription(reply.OpenDescriptionId2);
return -1;
}
int writeFd = PosixFdImport(PosixFdPipeWrite, handles[1], reply.OpenDescriptionId2,
writeShared, 0);
handles[1] = SYS_HANDLE_INVALID;
if (writeFd < 0) {
(void)PosixFdClose(readFd);
return -1;
}
pipefd[0] = readFd;
pipefd[1] = writeFd;
return 0;
}
// Deletes one translated native path.
__declspec(dllexport) int unlink(const char* path) {
if (!path) { errno = EINVAL; return -1; }
char native[512];
const char* p = PosixTranslatePath(path, native, sizeof(native));
if (strcmp(p, SYNTH_DEVNULL) == 0 || strcmp(p, SYNTH_DEVZERO) == 0) {
errno = EINVAL;
return -1;
}
uint32_t status = NtlDeleteFile(SysSvFromCstr(p));
if (status != NTLSTATUS_SUCCESS) {
errno = PosixStatusToErrno(status);
return -1;
}
return 0;
}
// Repositions the shared file offset owned by an open description.
__declspec(dllexport) off_t lseek(int fd, off_t offset, int whence) {
POSIX_OPEN_DESCRIPTION* description = PosixFdAcquire(fd);
if (!description)
return -1;
if (description->Type != PosixFdFile) {
PosixFdRelease(description);
errno = ESPIPE;
return -1;
}
int64_t target = 0;
uint32_t status = NTLSTATUS_SUCCESS;
if (whence == SEEK_SET) {
target = (int64_t)offset;
} else if (whence == SEEK_CUR) {
uint64_t current = 0;
status = SysGetFilePos(description->KHandle, &current);
if (status == NTLSTATUS_SUCCESS && current <= 0x7FFFFFFFULL)
target = (int64_t)current + (int64_t)offset;
else if (status == NTLSTATUS_SUCCESS)
status = NTLSTATUS_INVALID_PARAMETER;
} else if (whence == SEEK_END) {
uint64_t size = 0;
status = SysGetFileSize(description->KHandle, &size);
if (status == NTLSTATUS_SUCCESS && size <= 0x7FFFFFFFULL)
target = (int64_t)size + (int64_t)offset;
else if (status == NTLSTATUS_SUCCESS)
status = NTLSTATUS_INVALID_PARAMETER;
} else {
status = NTLSTATUS_INVALID_PARAMETER;
}
if (status == NTLSTATUS_SUCCESS && (target < 0 || target > 0x7FFFFFFFLL))
status = NTLSTATUS_INVALID_PARAMETER;
if (status == NTLSTATUS_SUCCESS)
status = SysSeekFile(description->KHandle, (uint64_t)target);
PosixFdRelease(description);
if (status != NTLSTATUS_SUCCESS) {
errno = PosixStatusToErrno(status);
return -1;
}
return (off_t)target;
}