Files

576 lines
20 KiB
C

// POSIX compatibility support for byte-stream file I/O.
#include <ntlite/atomic.h>
#include <stdbool.h>
#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 int32_t* lock) {
while (NtlAtomicCompareExchange32(lock, 1, 0) != 0)
_mm_pause();
_ReadWriteBarrier();
}
// Releases one shared pipe-side serialization lock.
static void PosixPipeUnlock(volatile int32_t* lock) {
_ReadWriteBarrier();
NtlAtomicExchange32(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();
int32_t 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);
POSIX_THREAD_STATE* thread = PosixRuntimeThreadState();
uint64_t deliveryCount = thread ? thread->SignalDeliveryCount : 0;
KSTATUS status = SysWait(description->KHandle, SYS_WAIT_FLAG_ALERTABLE);
if (status == NTLSTATUS_USER_APC || status == NTLSTATUS_KERNEL_APC ||
status == NTLSTATUS_ALERTED) {
if (!thread || thread->SignalDeliveryCount == deliveryCount)
continue;
errno = EINTR;
return -1;
}
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);
(void)raise(SIGPIPE);
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();
int32_t 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);
POSIX_THREAD_STATE* thread = PosixRuntimeThreadState();
uint64_t deliveryCount = thread ? thread->SignalDeliveryCount : 0;
KSTATUS status = SysWait(description->KHandle, SYS_WAIT_FLAG_ALERTABLE);
if (status == NTLSTATUS_USER_APC || status == NTLSTATUS_KERNEL_APC ||
status == NTLSTATUS_ALERTED) {
if (!thread || thread->SignalDeliveryCount == deliveryCount)
continue;
errno = EINTR;
return -1;
}
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;
KSTATUS 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++) {
KSTATUS 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);
KSTATUS 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
};
KSTATUS 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;
KSTATUS 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);
KSTATUS 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
};
KSTATUS 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;
bool 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;
}
KSTATUS 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;
KSTATUS 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;
}