// RSSRV native namespace tree, symbolic-link traversal, bindings, and enumeration. #include "rssrv_internal.h" #include #include #include 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] != '/') 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; }