Files

560 lines
22 KiB
C

// RSSRV native namespace tree, symbolic-link traversal, bindings, and enumeration.
#include "rssrv_internal.h"
#include <stddef.h>
#include <ntlite/native_heap.h>
#include <string.h>
typedef enum RSSRV_NODE_TYPE {
RssrvNodeDirectory = 1,
RssrvNodeBinding = 2,
RssrvNodeSymbolicLink = 3,
} RSSRV_NODE_TYPE;
// One hierarchical namespace node. Name storage follows the fixed header in the same allocation.
struct RSSRV_NODE {
RSSRV_NODE* Parent;
RSSRV_NODE* FirstChild;
RSSRV_NODE* NextSibling;
RSSRV_NODE_TYPE Type;
uint32_t NameLength;
union {
struct {
SYS_HANDLE Handle;
uint32_t Kind;
SYS_RIGHTS ExportRights;
} Binding;
struct {
char* Target;
uint32_t TargetLength;
} Link;
} u;
char Name[1];
};
// Acquires the namespace mutex.
static KSTATUS RssrvNamespaceLock(RSSRV_NAMESPACE* namespaceState) {
if (!namespaceState || namespaceState->Mutex == SYS_HANDLE_INVALID)
return NTLSTATUS_INVALID_PARAMETER;
return SysWait(namespaceState->Mutex, SYS_WAIT_FLAG_NONE);
}
// Releases the namespace mutex.
static void RssrvNamespaceUnlock(RSSRV_NAMESPACE* namespaceState) {
if (namespaceState && namespaceState->Mutex != SYS_HANDLE_INVALID)
(void)SysReleaseMutex(namespaceState->Mutex);
}
// Allocates one namespace node with inline component-name storage.
static RSSRV_NODE* RssrvAllocateNode(RSSRV_NODE_TYPE type, const char* name, uint32_t nameLength) {
if (nameLength > NTL_RESOURCE_NAME_MAX || (nameLength && !name))
return (RSSRV_NODE*)0;
size_t bytes = offsetof(RSSRV_NODE, Name) + (size_t)nameLength + 1u;
RSSRV_NODE* node = (RSSRV_NODE*)malloc(bytes);
if (!node) return (RSSRV_NODE*)0;
memset(node, 0, bytes);
node->Type = type;
node->NameLength = nameLength;
if (nameLength) memcpy(node->Name, name, nameLength);
node->Name[nameLength] = '\0';
return node;
}
// Returns the public resource kind exposed for one exact namespace node.
static uint32_t RssrvNodeKind(const RSSRV_NODE* node) {
if (!node) return NTL_RESOURCE_KIND_INVALID;
if (node->Type == RssrvNodeDirectory) return NTL_RESOURCE_KIND_NAMESPACE_DIRECTORY;
if (node->Type == RssrvNodeSymbolicLink) return NTL_RESOURCE_KIND_SYMBOLIC_LINK;
if (node->Type == RssrvNodeBinding) return node->u.Binding.Kind;
return NTL_RESOURCE_KIND_INVALID;
}
// Finds one immediate child while the namespace mutex is held.
static RSSRV_NODE* RssrvFindChildLocked(RSSRV_NODE* parent, const char* name, uint32_t nameLength) {
if (!parent || parent->Type != RssrvNodeDirectory || !name || !nameLength)
return (RSSRV_NODE*)0;
for (RSSRV_NODE* child = parent->FirstChild; child; child = child->NextSibling) {
if (child->NameLength == nameLength && memcmp(child->Name, name, nameLength) == 0)
return child;
}
return (RSSRV_NODE*)0;
}
// Links one unowned node beneath a namespace directory while the namespace mutex is held.
static KSTATUS RssrvInsertChildLocked(RSSRV_NODE* parent, RSSRV_NODE* node) {
if (!parent || parent->Type != RssrvNodeDirectory || !node || !node->NameLength)
return NTLSTATUS_INVALID_PARAMETER;
if (RssrvFindChildLocked(parent, node->Name, node->NameLength))
return NTLSTATUS_OBJECT_NAME_COLLISION;
node->Parent = parent;
node->NextSibling = parent->FirstChild;
parent->FirstChild = node;
return NTLSTATUS_SUCCESS;
}
// Walks an exact namespace path without following symbolic links.
static RSSRV_NODE* RssrvFindExactLocked(RSSRV_NAMESPACE* namespaceState,
const char* path,
uint32_t length) {
if (!namespaceState || !namespaceState->Root || !path || length == 0 || path[0] != '/')
return (RSSRV_NODE*)0;
if (length == 1)
return namespaceState->Root;
RSSRV_NODE* current = namespaceState->Root;
uint32_t componentStart = 1;
for (uint32_t i = 1; i <= length; ++i) {
if (i != length && path[i] != '/')
continue;
uint32_t componentLength = i - componentStart;
current = RssrvFindChildLocked(current, path + componentStart, componentLength);
if (!current)
return (RSSRV_NODE*)0;
if (i != length && current->Type != RssrvNodeDirectory)
return (RSSRV_NODE*)0;
componentStart = i + 1;
}
return current;
}
// Splits one non-root path into an existing namespace parent and final component.
static KSTATUS RssrvFindParentLocked(RSSRV_NAMESPACE* namespaceState,
const char* path,
uint32_t length,
RSSRV_NODE** parentOut,
const char** nameOut,
uint32_t* nameLengthOut) {
if (!namespaceState || !path || length < 2 || !parentOut || !nameOut || !nameLengthOut)
return NTLSTATUS_INVALID_PARAMETER;
uint32_t slash = length;
while (slash > 1 && path[slash - 1u] != '/') slash--;
uint32_t nameStart = slash;
uint32_t nameLength = length - nameStart;
uint32_t parentLength = nameStart == 1u ? 1u : nameStart - 1u;
RSSRV_NODE* parent = RssrvFindExactLocked(namespaceState, path, parentLength);
if (!parent) return NTLSTATUS_NOT_FOUND;
if (parent->Type != RssrvNodeDirectory) return NTLSTATUS_NOT_SUPPORTED;
*parentOut = parent;
*nameOut = path + nameStart;
*nameLengthOut = nameLength;
return NTLSTATUS_SUCCESS;
}
// Frees one detached namespace node and the payload it owns.
static void RssrvFreeDetachedNode(RSSRV_NODE* node) {
if (!node) return;
if (node->Type == RssrvNodeBinding && node->u.Binding.Handle != SYS_HANDLE_INVALID)
(void)SysCloseHandle(node->u.Binding.Handle);
if (node->Type == RssrvNodeSymbolicLink && node->u.Link.Target)
free(node->u.Link.Target);
free(node);
}
// Resolves a symbolic-link target to one exact namespace node while bounding recursive links.
static KSTATUS RssrvResolveExactNodeLocked(RSSRV_NAMESPACE* namespaceState,
RSSRV_NODE* node,
uint32_t depth,
RSSRV_NODE** nodeOut) {
if (!namespaceState || !nodeOut) return NTLSTATUS_INVALID_PARAMETER;
*nodeOut = (RSSRV_NODE*)0;
while (node && node->Type == RssrvNodeSymbolicLink) {
if (depth++ >= RSSRV_SYMBOLIC_LINK_MAX_DEPTH)
return NTLSTATUS_LIMIT_REACHED;
node = RssrvFindExactLocked(namespaceState, node->u.Link.Target, node->u.Link.TargetLength);
if (!node)
return NTLSTATUS_NOT_FOUND;
}
if (!node) return NTLSTATUS_NOT_FOUND;
*nodeOut = node;
return NTLSTATUS_SUCCESS;
}
// Resolves the first bound capability reached by the caller's path, following namespace links.
static KSTATUS RssrvResolveBindingLocked(RSSRV_NAMESPACE* namespaceState,
const char* path,
uint32_t length,
RSSRV_NODE** bindingOut,
uint32_t* consumedOut) {
if (!namespaceState || !path || length < 2 || !bindingOut || !consumedOut)
return NTLSTATUS_INVALID_PARAMETER;
*bindingOut = (RSSRV_NODE*)0;
*consumedOut = 0;
RSSRV_NODE* current = namespaceState->Root;
uint32_t componentStart = 1;
for (uint32_t i = 1; i <= length; ++i) {
if (i != length && path[i] != '/')
continue;
uint32_t componentLength = i - componentStart;
RSSRV_NODE* next = RssrvFindChildLocked(current, path + componentStart, componentLength);
if (!next) return NTLSTATUS_NOT_FOUND;
uint32_t consumed = i;
RSSRV_NODE* resolved = (RSSRV_NODE*)0;
KSTATUS status = RssrvResolveExactNodeLocked(namespaceState, next, 0, &resolved);
if (!KSUCCESS(status)) return status;
next = resolved;
if (next->Type == RssrvNodeBinding) {
*bindingOut = next;
*consumedOut = consumed;
return NTLSTATUS_SUCCESS;
}
if (next->Type != RssrvNodeDirectory)
return NTLSTATUS_NOT_SUPPORTED;
current = next;
componentStart = i + 1u;
}
return NTLSTATUS_NOT_SUPPORTED;
}
// Returns the lexicographically next immediate child after one optional cursor name.
static RSSRV_NODE* RssrvEnumerateNextLocked(RSSRV_NODE* directory,
const char* after,
uint32_t afterLength) {
if (!directory || directory->Type != RssrvNodeDirectory)
return (RSSRV_NODE*)0;
RSSRV_NODE* best = (RSSRV_NODE*)0;
for (RSSRV_NODE* child = directory->FirstChild; child; child = child->NextSibling) {
int afterCompare = 1;
if (afterLength) {
uint32_t common = child->NameLength < afterLength ? child->NameLength : afterLength;
int cmp = memcmp(child->Name, after, common);
if (cmp == 0) {
if (child->NameLength < afterLength) cmp = -1;
else if (child->NameLength > afterLength) cmp = 1;
}
afterCompare = cmp;
}
if (afterLength && afterCompare <= 0)
continue;
if (!best) {
best = child;
continue;
}
uint32_t common = child->NameLength < best->NameLength ? child->NameLength : best->NameLength;
int cmp = memcmp(child->Name, best->Name, common);
if (cmp < 0 || (cmp == 0 && child->NameLength < best->NameLength))
best = child;
}
return best;
}
// Initializes the native namespace root and its synchronization object.
KSTATUS RssrvNamespaceInitialize(RSSRV_NAMESPACE* namespaceState) {
if (!namespaceState) return NTLSTATUS_INVALID_PARAMETER;
memset(namespaceState, 0, sizeof(*namespaceState));
namespaceState->Mutex = SYS_HANDLE_INVALID;
KSTATUS status = SysCreateMutex(&namespaceState->Mutex);
if (!KSUCCESS(status)) return status;
namespaceState->Root = RssrvAllocateNode(RssrvNodeDirectory, (const char*)0, 0);
if (!namespaceState->Root) {
(void)SysCloseHandle(namespaceState->Mutex);
namespaceState->Mutex = SYS_HANDLE_INVALID;
return NTLSTATUS_NO_MEMORY;
}
return NTLSTATUS_SUCCESS;
}
// Validates one canonical absolute native resource path, including the root path "/".
KSTATUS RssrvNamespaceValidatePath(const char* path, uint32_t length) {
if (!path || length == 0 || length > NTL_RESOURCE_PATH_MAX || path[0] != '/' ||
!SysSvIsUtf8Text(SysSvFromParts(path, length)))
return NTLSTATUS_INVALID_PARAMETER;
if (length == 1)
return NTLSTATUS_SUCCESS;
if (path[length - 1] == '/')
return NTLSTATUS_INVALID_PARAMETER;
uint32_t componentStart = 1;
for (uint32_t i = 1; i <= length; ++i) {
if (i < length) {
char c = path[i];
if (c == '\\' || c == '\0')
return NTLSTATUS_INVALID_PARAMETER;
if (c != '/')
continue;
}
uint32_t componentLength = i - componentStart;
if (componentLength == 0 || componentLength > NTL_RESOURCE_NAME_MAX ||
(componentLength == 1 && path[componentStart] == '.') ||
(componentLength == 2 && path[componentStart] == '.' && path[componentStart + 1] == '.'))
return NTLSTATUS_INVALID_PARAMETER;
componentStart = i + 1;
}
return NTLSTATUS_SUCCESS;
}
// Creates one exact namespace directory. Its parent must already exist.
KSTATUS RssrvNamespaceCreateDirectory(RSSRV_NAMESPACE* namespaceState,
const char* path,
uint32_t length) {
KSTATUS status = RssrvNamespaceValidatePath(path, length);
if (!KSUCCESS(status)) return status;
if (!namespaceState || length == 1) return NTLSTATUS_OBJECT_NAME_COLLISION;
RSSRV_NODE* node = (RSSRV_NODE*)0;
status = RssrvNamespaceLock(namespaceState);
if (!KSUCCESS(status)) return status;
RSSRV_NODE* parent = (RSSRV_NODE*)0;
const char* name = (const char*)0;
uint32_t nameLength = 0;
status = RssrvFindParentLocked(namespaceState, path, length, &parent, &name, &nameLength);
if (KSUCCESS(status)) {
node = RssrvAllocateNode(RssrvNodeDirectory, name, nameLength);
if (!node) status = NTLSTATUS_NO_MEMORY;
}
if (KSUCCESS(status))
status = RssrvInsertChildLocked(parent, node);
RssrvNamespaceUnlock(namespaceState);
if (!KSUCCESS(status) && node) free(node);
return status;
}
// Creates one absolute namespace symbolic link. Dangling targets are permitted.
KSTATUS RssrvNamespaceCreateSymbolicLink(RSSRV_NAMESPACE* namespaceState,
const char* path,
uint32_t length,
const char* target,
uint32_t targetLength) {
KSTATUS status = RssrvNamespaceValidatePath(path, length);
if (!KSUCCESS(status)) return status;
status = RssrvNamespaceValidatePath(target, targetLength);
if (!KSUCCESS(status)) return status;
if (!namespaceState || length == 1) return NTLSTATUS_ACCESS_DENIED;
RSSRV_NODE* node = (RSSRV_NODE*)0;
char* targetCopy = (char*)malloc((size_t)targetLength + 1u);
if (!targetCopy) return NTLSTATUS_NO_MEMORY;
memcpy(targetCopy, target, targetLength);
targetCopy[targetLength] = '\0';
status = RssrvNamespaceLock(namespaceState);
if (!KSUCCESS(status)) {
free(targetCopy);
return status;
}
RSSRV_NODE* parent = (RSSRV_NODE*)0;
const char* name = (const char*)0;
uint32_t nameLength = 0;
status = RssrvFindParentLocked(namespaceState, path, length, &parent, &name, &nameLength);
if (KSUCCESS(status)) {
node = RssrvAllocateNode(RssrvNodeSymbolicLink, name, nameLength);
if (!node) status = NTLSTATUS_NO_MEMORY;
}
if (KSUCCESS(status)) {
node->u.Link.Target = targetCopy;
node->u.Link.TargetLength = targetLength;
status = RssrvInsertChildLocked(parent, node);
}
RssrvNamespaceUnlock(namespaceState);
if (!KSUCCESS(status)) {
if (node) free(node);
free(targetCopy);
}
return status;
}
// Takes ownership of one handle and binds it to a new exact resource name.
KSTATUS RssrvNamespaceBindOwned(RSSRV_NAMESPACE* namespaceState,
const char* path,
uint32_t length,
uint32_t kind,
SYS_HANDLE handle,
SYS_RIGHTS exportRights) {
if (!namespaceState || kind == NTL_RESOURCE_KIND_INVALID ||
kind == NTL_RESOURCE_KIND_NAMESPACE_DIRECTORY || kind == NTL_RESOURCE_KIND_SYMBOLIC_LINK ||
handle == SYS_HANDLE_INVALID || !exportRights)
return NTLSTATUS_INVALID_PARAMETER;
KSTATUS status = RssrvNamespaceValidatePath(path, length);
if (!KSUCCESS(status)) return status;
if (length == 1) return NTLSTATUS_ACCESS_DENIED;
RSSRV_NODE* node = (RSSRV_NODE*)0;
status = RssrvNamespaceLock(namespaceState);
if (!KSUCCESS(status)) return status;
RSSRV_NODE* parent = (RSSRV_NODE*)0;
const char* name = (const char*)0;
uint32_t nameLength = 0;
status = RssrvFindParentLocked(namespaceState, path, length, &parent, &name, &nameLength);
if (KSUCCESS(status)) {
node = RssrvAllocateNode(RssrvNodeBinding, name, nameLength);
if (!node) status = NTLSTATUS_NO_MEMORY;
}
if (KSUCCESS(status)) {
node->u.Binding.Handle = handle;
node->u.Binding.Kind = kind;
node->u.Binding.ExportRights = exportRights;
status = RssrvInsertChildLocked(parent, node);
}
RssrvNamespaceUnlock(namespaceState);
if (!KSUCCESS(status) && node) free(node);
return status;
}
// Removes one exact namespace node. Namespace directories must be empty.
KSTATUS RssrvNamespaceUnbind(RSSRV_NAMESPACE* namespaceState,
const char* path,
uint32_t length) {
KSTATUS status = RssrvNamespaceValidatePath(path, length);
if (!KSUCCESS(status)) return status;
if (!namespaceState || length == 1) return NTLSTATUS_ACCESS_DENIED;
status = RssrvNamespaceLock(namespaceState);
if (!KSUCCESS(status)) return status;
RSSRV_NODE* node = RssrvFindExactLocked(namespaceState, path, length);
if (!node) {
RssrvNamespaceUnlock(namespaceState);
return NTLSTATUS_NOT_FOUND;
}
if (node->Type == RssrvNodeDirectory && node->FirstChild) {
RssrvNamespaceUnlock(namespaceState);
return NTLSTATUS_ACCESS_DENIED;
}
RSSRV_NODE* parent = node->Parent;
RSSRV_NODE** link = &parent->FirstChild;
while (*link && *link != node) link = &(*link)->NextSibling;
if (*link != node) {
RssrvNamespaceUnlock(namespaceState);
return NTLSTATUS_FAILURE;
}
*link = node->NextSibling;
node->Parent = (RSSRV_NODE*)0;
node->NextSibling = (RSSRV_NODE*)0;
RssrvNamespaceUnlock(namespaceState);
RssrvFreeDetachedNode(node);
return NTLSTATUS_SUCCESS;
}
// Queries one exact namespace node without following the final symbolic link.
KSTATUS RssrvNamespaceQuery(RSSRV_NAMESPACE* namespaceState,
const char* path,
uint32_t length,
uint32_t* kindOut) {
if (!namespaceState || !kindOut) return NTLSTATUS_INVALID_PARAMETER;
*kindOut = NTL_RESOURCE_KIND_INVALID;
KSTATUS status = RssrvNamespaceLock(namespaceState);
if (!KSUCCESS(status)) return status;
RSSRV_NODE* node = RssrvFindExactLocked(namespaceState, path, length);
if (node) *kindOut = RssrvNodeKind(node);
RssrvNamespaceUnlock(namespaceState);
return node ? NTLSTATUS_SUCCESS : NTLSTATUS_NOT_FOUND;
}
// Returns the lexicographically next immediate child after one optional cursor name.
KSTATUS RssrvNamespaceEnumerate(RSSRV_NAMESPACE* namespaceState,
const char* path,
uint32_t length,
const char* after,
uint32_t afterLength,
RSSRV_NAMESPACE_ENTRY* entryOut) {
if (!namespaceState || !entryOut || (afterLength && !after) || afterLength > NTL_RESOURCE_NAME_MAX)
return NTLSTATUS_INVALID_PARAMETER;
memset(entryOut, 0, sizeof(*entryOut));
entryOut->Kind = NTL_RESOURCE_KIND_INVALID;
KSTATUS status = RssrvNamespaceLock(namespaceState);
if (!KSUCCESS(status)) return status;
RSSRV_NODE* directory = RssrvFindExactLocked(namespaceState, path, length);
if (!directory) {
RssrvNamespaceUnlock(namespaceState);
return NTLSTATUS_NOT_FOUND;
}
if (directory->Type != RssrvNodeDirectory) {
entryOut->Kind = RssrvNodeKind(directory);
RssrvNamespaceUnlock(namespaceState);
return NTLSTATUS_NOT_SUPPORTED;
}
RSSRV_NODE* child = RssrvEnumerateNextLocked(directory, after, afterLength);
if (!child) {
RssrvNamespaceUnlock(namespaceState);
return NTLSTATUS_NO_MORE_FILES;
}
entryOut->Kind = RssrvNodeKind(child);
entryOut->NameLength = child->NameLength;
memcpy(entryOut->Name, child->Name, child->NameLength);
entryOut->Name[child->NameLength] = '\0';
RssrvNamespaceUnlock(namespaceState);
return NTLSTATUS_SUCCESS;
}
// Opens a locked view of one exact bound object, following a final namespace symbolic link.
KSTATUS RssrvNamespaceBeginLookup(RSSRV_NAMESPACE* namespaceState,
const char* path,
uint32_t length,
RSSRV_NAMESPACE_BINDING_VIEW* viewOut,
uint32_t* kindOut) {
if (!namespaceState || !viewOut || !kindOut) return NTLSTATUS_INVALID_PARAMETER;
memset(viewOut, 0, sizeof(*viewOut));
viewOut->Handle = SYS_HANDLE_INVALID;
*kindOut = NTL_RESOURCE_KIND_INVALID;
KSTATUS status = RssrvNamespaceLock(namespaceState);
if (!KSUCCESS(status)) return status;
RSSRV_NODE* node = RssrvFindExactLocked(namespaceState, path, length);
if (!node) {
RssrvNamespaceUnlock(namespaceState);
return NTLSTATUS_NOT_FOUND;
}
RSSRV_NODE* resolved = (RSSRV_NODE*)0;
status = RssrvResolveExactNodeLocked(namespaceState, node, 0, &resolved);
if (!KSUCCESS(status)) {
RssrvNamespaceUnlock(namespaceState);
return status;
}
node = resolved;
*kindOut = RssrvNodeKind(node);
if (node->Type != RssrvNodeBinding) {
RssrvNamespaceUnlock(namespaceState);
return NTLSTATUS_NOT_SUPPORTED;
}
viewOut->Namespace = namespaceState;
viewOut->Handle = node->u.Binding.Handle;
viewOut->Kind = node->u.Binding.Kind;
viewOut->ExportRights = node->u.Binding.ExportRights;
viewOut->Locked = 1u;
return NTLSTATUS_SUCCESS;
}
// Opens a locked view of the first bound object reached while traversing one resource path.
KSTATUS RssrvNamespaceBeginResolve(RSSRV_NAMESPACE* namespaceState,
const char* path,
uint32_t length,
RSSRV_NAMESPACE_BINDING_VIEW* viewOut) {
if (!namespaceState || !viewOut) return NTLSTATUS_INVALID_PARAMETER;
memset(viewOut, 0, sizeof(*viewOut));
viewOut->Handle = SYS_HANDLE_INVALID;
KSTATUS status = RssrvNamespaceLock(namespaceState);
if (!KSUCCESS(status)) return status;
RSSRV_NODE* node = (RSSRV_NODE*)0;
uint32_t resolvedLength = 0;
status = RssrvResolveBindingLocked(namespaceState, path, length, &node, &resolvedLength);
if (!KSUCCESS(status)) {
RssrvNamespaceUnlock(namespaceState);
return status;
}
viewOut->Namespace = namespaceState;
viewOut->Handle = node->u.Binding.Handle;
viewOut->Kind = node->u.Binding.Kind;
viewOut->ExportRights = node->u.Binding.ExportRights;
viewOut->ResolvedLength = resolvedLength;
viewOut->Locked = 1u;
return NTLSTATUS_SUCCESS;
}
// Releases the namespace lock held by a successful binding view.
void RssrvNamespaceEndBinding(RSSRV_NAMESPACE_BINDING_VIEW* view) {
if (!view || !view->Locked || !view->Namespace) return;
RssrvNamespaceUnlock(view->Namespace);
view->Locked = 0;
view->Namespace = (RSSRV_NAMESPACE*)0;
view->Handle = SYS_HANDLE_INVALID;
}