948 lines
37 KiB
C
948 lines
37 KiB
C
// Native resource namespace service. RSSRV owns global names and transfers capabilities; it is not a file data path.
|
|
|
|
#include <ntlite/exception.h>
|
|
#include <ntlite/resource.h>
|
|
#include <ntlite/service.h>
|
|
#include <ntlite/service_control.h>
|
|
#include <ntlite/syscall.h>
|
|
#include <stdint.h>
|
|
#include <stddef.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#define RSSRV_LISTENER_BACKLOG 16u
|
|
|
|
// One namespace binding. Path storage follows the fixed header in the same allocation.
|
|
typedef struct RSSRV_ENTRY {
|
|
struct RSSRV_ENTRY* Next;
|
|
SYS_HANDLE Handle;
|
|
uint32_t Kind;
|
|
SYS_RIGHTS ExportRights;
|
|
uint32_t PathLength;
|
|
char Path[1];
|
|
} RSSRV_ENTRY;
|
|
|
|
typedef struct RSSRV_SESSION {
|
|
SYS_HANDLE Endpoint;
|
|
uint32_t Administrative;
|
|
} RSSRV_SESSION;
|
|
|
|
typedef struct RSSRV_LISTENER_WORKER {
|
|
SYS_HANDLE Listener;
|
|
uint32_t Administrative;
|
|
} RSSRV_LISTENER_WORKER;
|
|
|
|
typedef struct RSSRV_VOLUME_PUBLICATION {
|
|
struct RSSRV_VOLUME_PUBLICATION* Next;
|
|
uint32_t InstanceId;
|
|
uint32_t NamespaceId;
|
|
uint32_t PathLength;
|
|
char Path[48];
|
|
} RSSRV_VOLUME_PUBLICATION;
|
|
|
|
static SYS_HANDLE gNamespaceMutex = SYS_HANDLE_INVALID;
|
|
static RSSRV_ENTRY* gNamespace = (RSSRV_ENTRY*)0;
|
|
static SYS_HANDLE gPublicListener = SYS_HANDLE_INVALID;
|
|
static SYS_HANDLE gAdminListener = SYS_HANDLE_INVALID;
|
|
static RSSRV_LISTENER_WORKER gPublicWorker;
|
|
static RSSRV_LISTENER_WORKER gAdminWorker;
|
|
static RSSRV_VOLUME_PUBLICATION* gPublishedVolumes = (RSSRV_VOLUME_PUBLICATION*)0;
|
|
static uint32_t gNextHarddiskVolumeId = 1u;
|
|
|
|
// Exits without depending on a compiler runtime return path.
|
|
static void RssrvExit(int code) {
|
|
for (;;) SysExitProcess(code);
|
|
}
|
|
|
|
// Acquires the namespace mutex.
|
|
static KSTATUS RssrvLockNamespace(void) {
|
|
return SysWait(gNamespaceMutex, SYS_WAIT_FLAG_NONE);
|
|
}
|
|
|
|
// Releases the namespace mutex.
|
|
static void RssrvUnlockNamespace(void) {
|
|
(void)SysReleaseMutex(gNamespaceMutex);
|
|
}
|
|
|
|
// Validates one canonical absolute native resource path.
|
|
static KSTATUS RssrvValidatePath(const char* path, uint32_t length) {
|
|
if (!path || length < 2 || length > NTL_RESOURCE_PATH_MAX || path[0] != '/' || 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 == 1 && path[componentStart] == '.') ||
|
|
(componentLength == 2 && path[componentStart] == '.' && path[componentStart + 1] == '.'))
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
componentStart = i + 1;
|
|
}
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
// Returns whether a path belongs to the RSSRV-owned automatic mounted-volume naming family.
|
|
static int RssrvIsManagedVolumePath(const char* path, uint32_t length) {
|
|
static const char prefix[] = "/Device/HarddiskVolume";
|
|
uint32_t prefixLength = (uint32_t)(sizeof(prefix) - 1u);
|
|
return path && length >= prefixLength && memcmp(path, prefix, prefixLength) == 0;
|
|
}
|
|
|
|
// Finds one exact binding while the namespace mutex is held.
|
|
static RSSRV_ENTRY* RssrvFindLocked(const char* path, uint32_t length, RSSRV_ENTRY*** linkOut) {
|
|
RSSRV_ENTRY** link = &gNamespace;
|
|
while (*link) {
|
|
RSSRV_ENTRY* entry = *link;
|
|
if (entry->PathLength == length && memcmp(entry->Path, path, length) == 0) {
|
|
if (linkOut) *linkOut = link;
|
|
return entry;
|
|
}
|
|
link = &entry->Next;
|
|
}
|
|
if (linkOut) *linkOut = link;
|
|
return (RSSRV_ENTRY*)0;
|
|
}
|
|
|
|
// Finds the longest namespace binding that is an exact component prefix of path.
|
|
static RSSRV_ENTRY* RssrvFindPrefixLocked(const char* path, uint32_t length) {
|
|
RSSRV_ENTRY* best = (RSSRV_ENTRY*)0;
|
|
for (RSSRV_ENTRY* entry = gNamespace; entry; entry = entry->Next) {
|
|
if (entry->PathLength > length)
|
|
continue;
|
|
if (memcmp(entry->Path, path, entry->PathLength) != 0)
|
|
continue;
|
|
if (entry->PathLength != length && path[entry->PathLength] != '/')
|
|
continue;
|
|
if (!best || entry->PathLength > best->PathLength)
|
|
best = entry;
|
|
}
|
|
return best;
|
|
}
|
|
|
|
// Takes ownership of one handle and binds it to a new exact resource name.
|
|
static KSTATUS RssrvBindOwned(const char* path,
|
|
uint32_t length,
|
|
uint32_t kind,
|
|
SYS_HANDLE handle,
|
|
SYS_RIGHTS exportRights) {
|
|
if (kind == NTL_RESOURCE_KIND_INVALID || handle == SYS_HANDLE_INVALID || !exportRights)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
KSTATUS status = RssrvValidatePath(path, length);
|
|
if (!KSUCCESS(status)) return status;
|
|
|
|
size_t bytes = offsetof(RSSRV_ENTRY, Path) + (size_t)length + 1u;
|
|
RSSRV_ENTRY* entry = (RSSRV_ENTRY*)malloc(bytes);
|
|
if (!entry) return NTLSTATUS_NO_MEMORY;
|
|
memset(entry, 0, bytes);
|
|
entry->Handle = handle;
|
|
entry->Kind = kind;
|
|
entry->ExportRights = exportRights;
|
|
entry->PathLength = length;
|
|
memcpy(entry->Path, path, length);
|
|
entry->Path[length] = '\0';
|
|
|
|
status = RssrvLockNamespace();
|
|
if (!KSUCCESS(status)) {
|
|
free(entry);
|
|
return status;
|
|
}
|
|
if (RssrvFindLocked(path, length, (RSSRV_ENTRY***)0)) {
|
|
RssrvUnlockNamespace();
|
|
free(entry);
|
|
return NTLSTATUS_OBJECT_NAME_COLLISION;
|
|
}
|
|
entry->Next = gNamespace;
|
|
gNamespace = entry;
|
|
RssrvUnlockNamespace();
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
// Removes one binding and closes the namespace's retained handle.
|
|
static KSTATUS RssrvUnbind(const char* path, uint32_t length) {
|
|
KSTATUS status = RssrvValidatePath(path, length);
|
|
if (!KSUCCESS(status)) return status;
|
|
status = RssrvLockNamespace();
|
|
if (!KSUCCESS(status)) return status;
|
|
|
|
RSSRV_ENTRY** link = (RSSRV_ENTRY**)0;
|
|
RSSRV_ENTRY* entry = RssrvFindLocked(path, length, &link);
|
|
if (!entry) {
|
|
RssrvUnlockNamespace();
|
|
return NTLSTATUS_NOT_FOUND;
|
|
}
|
|
*link = entry->Next;
|
|
RssrvUnlockNamespace();
|
|
(void)SysCloseHandle(entry->Handle);
|
|
free(entry);
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
// Finds one published mounted volume by kernel instance identity.
|
|
static RSSRV_VOLUME_PUBLICATION* RssrvFindPublishedVolume(uint32_t instanceId) {
|
|
for (RSSRV_VOLUME_PUBLICATION* publication = gPublishedVolumes; publication; publication = publication->Next) {
|
|
if (publication->InstanceId == instanceId)
|
|
return publication;
|
|
}
|
|
return (RSSRV_VOLUME_PUBLICATION*)0;
|
|
}
|
|
|
|
// Returns whether one kernel volume instance is present in the current manager snapshot.
|
|
static int RssrvSnapshotContainsVolume(const SYS_VOLUME_INFO* volumes, uint32_t count, uint32_t instanceId) {
|
|
for (uint32_t i = 0; i < count; ++i) {
|
|
if (volumes[i].InstanceId == instanceId)
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// Builds one RSSRV-local /Device/HarddiskVolumeN name without introducing a formatting-runtime dependency.
|
|
static KSTATUS RssrvBuildHarddiskVolumePath(uint32_t namespaceId, char* path, uint32_t capacity, uint32_t* lengthOut) {
|
|
static const char prefix[] = "/Device/HarddiskVolume";
|
|
if (!namespaceId || !path || !lengthOut)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
uint32_t prefixLength = (uint32_t)(sizeof(prefix) - 1u);
|
|
char digits[10];
|
|
uint32_t digitCount = 0;
|
|
uint32_t value = namespaceId;
|
|
do {
|
|
digits[digitCount++] = (char)('0' + (value % 10u));
|
|
value /= 10u;
|
|
} while (value && digitCount < (uint32_t)sizeof(digits));
|
|
|
|
if (prefixLength + digitCount + 1u > capacity)
|
|
return NTLSTATUS_BUFFER_TOO_SMALL;
|
|
memcpy(path, prefix, prefixLength);
|
|
for (uint32_t i = 0; i < digitCount; ++i)
|
|
path[prefixLength + i] = digits[digitCount - 1u - i];
|
|
path[prefixLength + digitCount] = '\0';
|
|
*lengthOut = prefixLength + digitCount;
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
// Takes a stable manager snapshot, growing the userspace buffer if the mounted-volume count changes.
|
|
static KSTATUS RssrvQueryVolumes(SYS_HANDLE manager,
|
|
SYS_VOLUME_INFO** volumesOut,
|
|
uint32_t* countOut,
|
|
uint64_t* generationOut) {
|
|
if (!volumesOut || !countOut || !generationOut)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
*volumesOut = (SYS_VOLUME_INFO*)0;
|
|
*countOut = 0;
|
|
*generationOut = 0;
|
|
|
|
SYS_VOLUME_INFO* volumes = (SYS_VOLUME_INFO*)0;
|
|
uint32_t capacity = 0;
|
|
for (;;) {
|
|
uint32_t count = 0;
|
|
uint64_t generation = 0;
|
|
SYS_QUERY_VOLUMES_REQUEST query;
|
|
memset(&query, 0, sizeof(query));
|
|
query.Size = sizeof(query);
|
|
query.ManagerHandle = manager;
|
|
query.Flags = SYS_VOLUME_QUERY_FLAG_NONE;
|
|
query.Capacity = capacity;
|
|
query.Volumes = volumes;
|
|
query.CountOut = &count;
|
|
query.GenerationOut = &generation;
|
|
KSTATUS status = SysQueryVolumes(&query);
|
|
if (status == NTLSTATUS_BUFFER_TOO_SMALL) {
|
|
if (volumes) free(volumes);
|
|
volumes = (SYS_VOLUME_INFO*)0;
|
|
capacity = count;
|
|
if (capacity) {
|
|
volumes = (SYS_VOLUME_INFO*)malloc((size_t)capacity * sizeof(*volumes));
|
|
if (!volumes)
|
|
return NTLSTATUS_NO_MEMORY;
|
|
memset(volumes, 0, (size_t)capacity * sizeof(*volumes));
|
|
}
|
|
continue;
|
|
}
|
|
if (!KSUCCESS(status)) {
|
|
if (volumes) free(volumes);
|
|
return status;
|
|
}
|
|
for (uint32_t i = 0; i < count; ++i) {
|
|
if (volumes[i].Size != sizeof(SYS_VOLUME_INFO) ||
|
|
volumes[i].Version != SYS_VOLUME_ENUM_VERSION || !volumes[i].InstanceId ||
|
|
volumes[i].Flags != 0) {
|
|
if (volumes) free(volumes);
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
}
|
|
}
|
|
*volumesOut = volumes;
|
|
*countOut = count;
|
|
*generationOut = generation;
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
}
|
|
|
|
// Reconciles the RSSRV object namespace with the kernel's mounted-volume snapshot.
|
|
static KSTATUS RssrvSyncVolumes(SYS_HANDLE manager, uint64_t* generationOut) {
|
|
if (!generationOut)
|
|
return NTLSTATUS_INVALID_PARAMETER;
|
|
|
|
SYS_VOLUME_INFO* volumes = (SYS_VOLUME_INFO*)0;
|
|
uint32_t count = 0;
|
|
uint64_t generation = 0;
|
|
KSTATUS status = RssrvQueryVolumes(manager, &volumes, &count, &generation);
|
|
if (!KSUCCESS(status))
|
|
return status;
|
|
|
|
RSSRV_VOLUME_PUBLICATION** link = &gPublishedVolumes;
|
|
while (*link) {
|
|
RSSRV_VOLUME_PUBLICATION* publication = *link;
|
|
if (RssrvSnapshotContainsVolume(volumes, count, publication->InstanceId)) {
|
|
link = &publication->Next;
|
|
continue;
|
|
}
|
|
status = RssrvUnbind(publication->Path, publication->PathLength);
|
|
if (!KSUCCESS(status) && status != NTLSTATUS_NOT_FOUND) {
|
|
free(volumes);
|
|
return status;
|
|
}
|
|
*link = publication->Next;
|
|
free(publication);
|
|
}
|
|
|
|
for (uint32_t i = 0; i < count; ++i) {
|
|
if (RssrvFindPublishedVolume(volumes[i].InstanceId))
|
|
continue;
|
|
|
|
SYS_HANDLE volume = SYS_HANDLE_INVALID;
|
|
status = SysOpenVolume(manager, volumes[i].InstanceId,
|
|
SYS_RIGHT_QUERY | SYS_RIGHT_TRANSFER |
|
|
SYS_VOLUME_RIGHT_OPEN | SYS_VOLUME_RIGHT_MODIFY,
|
|
&volume);
|
|
if (status == NTLSTATUS_NOT_FOUND || status == NTLSTATUS_OBJECT_DEAD)
|
|
continue;
|
|
if (!KSUCCESS(status)) {
|
|
free(volumes);
|
|
return status;
|
|
}
|
|
|
|
RSSRV_VOLUME_PUBLICATION* publication =
|
|
(RSSRV_VOLUME_PUBLICATION*)malloc(sizeof(*publication));
|
|
if (!publication) {
|
|
(void)SysCloseHandle(volume);
|
|
free(volumes);
|
|
return NTLSTATUS_NO_MEMORY;
|
|
}
|
|
memset(publication, 0, sizeof(*publication));
|
|
publication->InstanceId = volumes[i].InstanceId;
|
|
publication->NamespaceId = gNextHarddiskVolumeId;
|
|
status = RssrvBuildHarddiskVolumePath(publication->NamespaceId,
|
|
publication->Path,
|
|
(uint32_t)sizeof(publication->Path),
|
|
&publication->PathLength);
|
|
if (!KSUCCESS(status)) {
|
|
free(publication);
|
|
(void)SysCloseHandle(volume);
|
|
free(volumes);
|
|
return status;
|
|
}
|
|
|
|
status = RssrvBindOwned(publication->Path, publication->PathLength,
|
|
NTL_RESOURCE_KIND_VOLUME, volume,
|
|
SYS_RIGHT_QUERY | SYS_VOLUME_RIGHT_OPEN | SYS_VOLUME_RIGHT_MODIFY);
|
|
if (!KSUCCESS(status)) {
|
|
free(publication);
|
|
(void)SysCloseHandle(volume);
|
|
free(volumes);
|
|
return status;
|
|
}
|
|
publication->Next = gPublishedVolumes;
|
|
gPublishedVolumes = publication;
|
|
gNextHarddiskVolumeId++;
|
|
if (!gNextHarddiskVolumeId) {
|
|
free(volumes);
|
|
return NTLSTATUS_LIMIT_REACHED;
|
|
}
|
|
}
|
|
|
|
if (volumes) free(volumes);
|
|
*generationOut = generation;
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
// Keeps automatic /Device/HarddiskVolumeN bindings synchronized with mounted IO_VOLUME instances.
|
|
static void RssrvVolumeWorker(uintptr_t argument) {
|
|
SYS_HANDLE manager = (SYS_HANDLE)argument;
|
|
for (;;) {
|
|
uint64_t generation = 0;
|
|
KSTATUS status = RssrvSyncVolumes(manager, &generation);
|
|
if (!KSUCCESS(status))
|
|
RssrvExit(12);
|
|
status = SysWaitVolumeChange(manager, generation, SYS_WAIT_TIMEOUT_INFINITE);
|
|
if (!KSUCCESS(status))
|
|
RssrvExit(12);
|
|
}
|
|
}
|
|
|
|
// Starts the mounted-volume namespace publication worker.
|
|
static KSTATUS RssrvStartVolumeWorker(SYS_HANDLE manager) {
|
|
uint32_t threadId = 0;
|
|
SYS_CREATE_THREAD_REQUEST create;
|
|
memset(&create, 0, sizeof(create));
|
|
create.Size = sizeof(create);
|
|
create.Entry = (uintptr_t)RssrvVolumeWorker;
|
|
create.Argument = (uintptr_t)manager;
|
|
create.Priority = 1;
|
|
create.Flags = SYS_CREATE_THREAD_FLAG_NONE;
|
|
create.ThreadIdOut = &threadId;
|
|
return SysCreateThread(&create);
|
|
}
|
|
|
|
// Replies with one fixed resource-protocol reply and no handle transfer.
|
|
static KSTATUS RssrvReplyStatus(SYS_HANDLE endpoint,
|
|
uint32_t messageId,
|
|
uint32_t operation,
|
|
KSTATUS status,
|
|
uint32_t kind) {
|
|
NTL_RESOURCE_REPLY reply;
|
|
memset(&reply, 0, sizeof(reply));
|
|
reply.Size = sizeof(reply);
|
|
reply.Version = NTL_RESOURCE_PROTOCOL_VERSION;
|
|
reply.Operation = operation;
|
|
reply.Flags = NTL_RESOURCE_FLAG_NONE;
|
|
reply.Status = status;
|
|
reply.Kind = kind;
|
|
|
|
SYS_REPLY_PORT_REQUEST request;
|
|
memset(&request, 0, sizeof(request));
|
|
request.Size = sizeof(request);
|
|
request.EndpointHandle = endpoint;
|
|
request.MessageId = messageId;
|
|
request.Flags = SYS_PORT_FLAG_NONE;
|
|
request.Payload = &reply;
|
|
request.PayloadLength = sizeof(reply);
|
|
return SysReplyPort(&request);
|
|
}
|
|
|
|
// Handles one lookup while retaining the namespace mutex through capability transfer.
|
|
static KSTATUS RssrvHandleLookup(SYS_HANDLE endpoint,
|
|
uint32_t messageId,
|
|
const NTL_RESOURCE_REQUEST* request,
|
|
const char* path) {
|
|
if (!request->Rights)
|
|
return RssrvReplyStatus(endpoint, messageId, request->Operation,
|
|
NTLSTATUS_INVALID_PARAMETER, NTL_RESOURCE_KIND_INVALID);
|
|
|
|
KSTATUS status = RssrvLockNamespace();
|
|
if (!KSUCCESS(status))
|
|
return RssrvReplyStatus(endpoint, messageId, request->Operation, status, NTL_RESOURCE_KIND_INVALID);
|
|
RSSRV_ENTRY* entry = RssrvFindLocked(path, request->PathLength, (RSSRV_ENTRY***)0);
|
|
if (!entry) {
|
|
RssrvUnlockNamespace();
|
|
return RssrvReplyStatus(endpoint, messageId, request->Operation,
|
|
NTLSTATUS_NOT_FOUND, NTL_RESOURCE_KIND_INVALID);
|
|
}
|
|
|
|
uint32_t kind = entry->Kind;
|
|
NTL_RESOURCE_REPLY reply;
|
|
memset(&reply, 0, sizeof(reply));
|
|
reply.Size = sizeof(reply);
|
|
reply.Version = NTL_RESOURCE_PROTOCOL_VERSION;
|
|
reply.Operation = NTL_RESOURCE_OP_LOOKUP;
|
|
reply.Status = NTLSTATUS_SUCCESS;
|
|
reply.Kind = kind;
|
|
|
|
SYS_PORT_HANDLE_TRANSFER transfer;
|
|
transfer.Handle = entry->Handle;
|
|
if ((request->Rights & ~entry->ExportRights) != 0) {
|
|
RssrvUnlockNamespace();
|
|
return RssrvReplyStatus(endpoint, messageId, request->Operation,
|
|
NTLSTATUS_ACCESS_DENIED, kind);
|
|
}
|
|
|
|
transfer.DesiredRights = request->Rights;
|
|
transfer.Flags = SYS_PORT_TRANSFER_FLAG_NONE;
|
|
|
|
SYS_REPLY_PORT_REQUEST out;
|
|
memset(&out, 0, sizeof(out));
|
|
out.Size = sizeof(out);
|
|
out.EndpointHandle = endpoint;
|
|
out.MessageId = messageId;
|
|
out.Flags = SYS_PORT_FLAG_NONE;
|
|
out.Payload = &reply;
|
|
out.PayloadLength = sizeof(reply);
|
|
out.Handles = &transfer;
|
|
out.HandleCount = 1;
|
|
status = SysReplyPort(&out);
|
|
RssrvUnlockNamespace();
|
|
|
|
// A rights mismatch leaves the synchronous call pending, so return the precise error without a handle.
|
|
if (!KSUCCESS(status))
|
|
return RssrvReplyStatus(endpoint, messageId, request->Operation, status, kind);
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
// Resolves the longest bound resource prefix and transfers traversal authority.
|
|
static KSTATUS RssrvHandleResolve(SYS_HANDLE endpoint,
|
|
uint32_t messageId,
|
|
const NTL_RESOURCE_REQUEST* request,
|
|
const char* path) {
|
|
if ((request->Flags & ~NTL_RESOURCE_RESOLVE_FLAG_MODIFY) != 0)
|
|
return RssrvReplyStatus(endpoint, messageId, request->Operation,
|
|
NTLSTATUS_INVALID_PARAMETER, NTL_RESOURCE_KIND_INVALID);
|
|
|
|
KSTATUS status = RssrvLockNamespace();
|
|
if (!KSUCCESS(status))
|
|
return RssrvReplyStatus(endpoint, messageId, request->Operation, status, NTL_RESOURCE_KIND_INVALID);
|
|
|
|
RSSRV_ENTRY* entry = RssrvFindPrefixLocked(path, request->PathLength);
|
|
if (!entry) {
|
|
RssrvUnlockNamespace();
|
|
return RssrvReplyStatus(endpoint, messageId, request->Operation,
|
|
NTLSTATUS_NOT_FOUND, NTL_RESOURCE_KIND_INVALID);
|
|
}
|
|
|
|
SYS_RIGHTS desiredRights = 0;
|
|
if (entry->Kind == NTL_RESOURCE_KIND_VOLUME) {
|
|
desiredRights = SYS_VOLUME_RIGHT_OPEN;
|
|
if (request->Flags & NTL_RESOURCE_RESOLVE_FLAG_MODIFY)
|
|
desiredRights |= SYS_VOLUME_RIGHT_MODIFY;
|
|
} else if (entry->Kind == NTL_RESOURCE_KIND_DIRECTORY) {
|
|
desiredRights = SYS_DIR_RIGHT_OPEN;
|
|
if (request->Flags & NTL_RESOURCE_RESOLVE_FLAG_MODIFY)
|
|
desiredRights |= SYS_DIR_RIGHT_MODIFY;
|
|
} else {
|
|
uint32_t kind = entry->Kind;
|
|
RssrvUnlockNamespace();
|
|
return RssrvReplyStatus(endpoint, messageId, request->Operation,
|
|
NTLSTATUS_NOT_SUPPORTED, kind);
|
|
}
|
|
|
|
if ((desiredRights & ~entry->ExportRights) != 0) {
|
|
uint32_t kind = entry->Kind;
|
|
RssrvUnlockNamespace();
|
|
return RssrvReplyStatus(endpoint, messageId, request->Operation,
|
|
NTLSTATUS_ACCESS_DENIED, kind);
|
|
}
|
|
|
|
uint32_t kind = entry->Kind;
|
|
NTL_RESOURCE_REPLY reply;
|
|
memset(&reply, 0, sizeof(reply));
|
|
reply.Size = sizeof(reply);
|
|
reply.Version = NTL_RESOURCE_PROTOCOL_VERSION;
|
|
reply.Operation = NTL_RESOURCE_OP_RESOLVE;
|
|
reply.Flags = request->Flags;
|
|
reply.Status = NTLSTATUS_SUCCESS;
|
|
reply.Kind = kind;
|
|
reply.ResolvedLength = entry->PathLength;
|
|
|
|
SYS_PORT_HANDLE_TRANSFER transfer;
|
|
transfer.Handle = entry->Handle;
|
|
transfer.DesiredRights = desiredRights;
|
|
transfer.Flags = SYS_PORT_TRANSFER_FLAG_NONE;
|
|
|
|
SYS_REPLY_PORT_REQUEST out;
|
|
memset(&out, 0, sizeof(out));
|
|
out.Size = sizeof(out);
|
|
out.EndpointHandle = endpoint;
|
|
out.MessageId = messageId;
|
|
out.Flags = SYS_PORT_FLAG_NONE;
|
|
out.Payload = &reply;
|
|
out.PayloadLength = sizeof(reply);
|
|
out.Handles = &transfer;
|
|
out.HandleCount = 1;
|
|
status = SysReplyPort(&out);
|
|
RssrvUnlockNamespace();
|
|
if (!KSUCCESS(status))
|
|
return RssrvReplyStatus(endpoint, messageId, request->Operation, status, kind);
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
// Handles one metadata query.
|
|
static KSTATUS RssrvHandleQuery(SYS_HANDLE endpoint,
|
|
uint32_t messageId,
|
|
const NTL_RESOURCE_REQUEST* request,
|
|
const char* path) {
|
|
KSTATUS status = RssrvLockNamespace();
|
|
if (!KSUCCESS(status))
|
|
return RssrvReplyStatus(endpoint, messageId, request->Operation, status, NTL_RESOURCE_KIND_INVALID);
|
|
RSSRV_ENTRY* entry = RssrvFindLocked(path, request->PathLength, (RSSRV_ENTRY***)0);
|
|
uint32_t found = entry != (RSSRV_ENTRY*)0;
|
|
uint32_t kind = found ? entry->Kind : NTL_RESOURCE_KIND_INVALID;
|
|
RssrvUnlockNamespace();
|
|
return RssrvReplyStatus(endpoint, messageId, request->Operation,
|
|
found ? NTLSTATUS_SUCCESS : NTLSTATUS_NOT_FOUND, kind);
|
|
}
|
|
|
|
// Handles one administrative bind. On success the namespace takes ownership of receivedHandle.
|
|
static KSTATUS RssrvHandleBind(SYS_HANDLE endpoint,
|
|
uint32_t messageId,
|
|
const NTL_RESOURCE_REQUEST* request,
|
|
const char* path,
|
|
uint32_t administrative,
|
|
SYS_HANDLE receivedHandle,
|
|
uint32_t* consumedOut) {
|
|
if (!consumedOut) return NTLSTATUS_INVALID_PARAMETER;
|
|
*consumedOut = 0;
|
|
if (!administrative || RssrvIsManagedVolumePath(path, request->PathLength))
|
|
return RssrvReplyStatus(endpoint, messageId, request->Operation,
|
|
NTLSTATUS_ACCESS_DENIED, NTL_RESOURCE_KIND_INVALID);
|
|
KSTATUS bindStatus = RssrvBindOwned(path, request->PathLength, request->Kind, receivedHandle, request->ExportRights);
|
|
if (KSUCCESS(bindStatus)) *consumedOut = 1;
|
|
KSTATUS replyStatus = RssrvReplyStatus(endpoint, messageId, request->Operation, bindStatus,
|
|
KSUCCESS(bindStatus) ? request->Kind : NTL_RESOURCE_KIND_INVALID);
|
|
return KSUCCESS(replyStatus) ? bindStatus : replyStatus;
|
|
}
|
|
|
|
// Dispatches one received resource request and returns whether a received handle was consumed.
|
|
static uint32_t RssrvDispatchRequest(SYS_HANDLE endpoint,
|
|
uint32_t administrative,
|
|
const uint8_t* payload,
|
|
uint32_t payloadLength,
|
|
const SYS_PORT_MESSAGE_INFO* info,
|
|
SYS_HANDLE receivedHandle) {
|
|
uint32_t operation = 0;
|
|
if (payloadLength < sizeof(NTL_RESOURCE_REQUEST)) {
|
|
(void)RssrvReplyStatus(endpoint, info->MessageId, operation,
|
|
NTLSTATUS_INVALID_PARAMETER, NTL_RESOURCE_KIND_INVALID);
|
|
return 0;
|
|
}
|
|
|
|
NTL_RESOURCE_REQUEST request;
|
|
memcpy(&request, payload, sizeof(request));
|
|
operation = request.Operation;
|
|
if (request.Size != sizeof(request) || request.Version != NTL_RESOURCE_PROTOCOL_VERSION ||
|
|
request.PathLength == 0 || payloadLength != sizeof(request) + request.PathLength) {
|
|
(void)RssrvReplyStatus(endpoint, info->MessageId, operation,
|
|
NTLSTATUS_INVALID_PARAMETER, NTL_RESOURCE_KIND_INVALID);
|
|
return 0;
|
|
}
|
|
const char* path = (const char*)(payload + sizeof(request));
|
|
KSTATUS status = RssrvValidatePath(path, request.PathLength);
|
|
if (!KSUCCESS(status)) {
|
|
(void)RssrvReplyStatus(endpoint, info->MessageId, operation, status, NTL_RESOURCE_KIND_INVALID);
|
|
return 0;
|
|
}
|
|
|
|
switch (operation) {
|
|
case NTL_RESOURCE_OP_LOOKUP:
|
|
if (request.Flags != NTL_RESOURCE_FLAG_NONE || info->HandleCount != 0 ||
|
|
request.ExportRights != 0 || request.Kind != NTL_RESOURCE_KIND_INVALID) {
|
|
(void)RssrvReplyStatus(endpoint, info->MessageId, operation,
|
|
NTLSTATUS_INVALID_PARAMETER, NTL_RESOURCE_KIND_INVALID);
|
|
return 0;
|
|
}
|
|
(void)RssrvHandleLookup(endpoint, info->MessageId, &request, path);
|
|
return 0;
|
|
case NTL_RESOURCE_OP_QUERY:
|
|
if (request.Flags != NTL_RESOURCE_FLAG_NONE || info->HandleCount != 0 || request.Rights != 0 ||
|
|
request.ExportRights != 0 || request.Kind != NTL_RESOURCE_KIND_INVALID) {
|
|
(void)RssrvReplyStatus(endpoint, info->MessageId, operation,
|
|
NTLSTATUS_INVALID_PARAMETER, NTL_RESOURCE_KIND_INVALID);
|
|
return 0;
|
|
}
|
|
(void)RssrvHandleQuery(endpoint, info->MessageId, &request, path);
|
|
return 0;
|
|
case NTL_RESOURCE_OP_RESOLVE:
|
|
if (info->HandleCount != 0 || request.Rights != 0 || request.ExportRights != 0 ||
|
|
request.Kind != NTL_RESOURCE_KIND_INVALID ||
|
|
(request.Flags & ~NTL_RESOURCE_RESOLVE_FLAG_MODIFY) != 0) {
|
|
(void)RssrvReplyStatus(endpoint, info->MessageId, operation,
|
|
NTLSTATUS_INVALID_PARAMETER, NTL_RESOURCE_KIND_INVALID);
|
|
return 0;
|
|
}
|
|
(void)RssrvHandleResolve(endpoint, info->MessageId, &request, path);
|
|
return 0;
|
|
case NTL_RESOURCE_OP_BIND:
|
|
if (request.Flags != NTL_RESOURCE_FLAG_NONE || info->HandleCount != 1 ||
|
|
!(request.Rights & SYS_RIGHT_TRANSFER) ||
|
|
request.ExportRights == 0 || (request.ExportRights & ~request.Rights) != 0 ||
|
|
request.Kind == NTL_RESOURCE_KIND_INVALID || receivedHandle == SYS_HANDLE_INVALID) {
|
|
(void)RssrvReplyStatus(endpoint, info->MessageId, operation,
|
|
NTLSTATUS_INVALID_PARAMETER, NTL_RESOURCE_KIND_INVALID);
|
|
return 0;
|
|
}
|
|
{
|
|
uint32_t consumed = 0;
|
|
(void)RssrvHandleBind(endpoint, info->MessageId, &request, path, administrative,
|
|
receivedHandle, &consumed);
|
|
return consumed;
|
|
}
|
|
case NTL_RESOURCE_OP_UNBIND:
|
|
if (request.Flags != NTL_RESOURCE_FLAG_NONE || info->HandleCount != 0 || request.Rights != 0 ||
|
|
request.ExportRights != 0 || request.Kind != NTL_RESOURCE_KIND_INVALID) {
|
|
(void)RssrvReplyStatus(endpoint, info->MessageId, operation,
|
|
NTLSTATUS_INVALID_PARAMETER, NTL_RESOURCE_KIND_INVALID);
|
|
return 0;
|
|
}
|
|
status = administrative && !RssrvIsManagedVolumePath(path, request.PathLength)
|
|
? RssrvUnbind(path, request.PathLength)
|
|
: NTLSTATUS_ACCESS_DENIED;
|
|
(void)RssrvReplyStatus(endpoint, info->MessageId, operation, status, NTL_RESOURCE_KIND_INVALID);
|
|
return 0;
|
|
default:
|
|
(void)RssrvReplyStatus(endpoint, info->MessageId, operation,
|
|
NTLSTATUS_NOT_SUPPORTED, NTL_RESOURCE_KIND_INVALID);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// Serves one connected resource session until the peer disconnects.
|
|
static void RssrvSessionThread(uintptr_t argument) {
|
|
RSSRV_SESSION* session = (RSSRV_SESSION*)argument;
|
|
if (!session) {
|
|
SysExitThread(1);
|
|
return;
|
|
}
|
|
SYS_HANDLE endpoint = session->Endpoint;
|
|
uint32_t administrative = session->Administrative;
|
|
free(session);
|
|
|
|
uint8_t payload[SYS_PORT_MAX_INLINE_BYTES];
|
|
SYS_HANDLE handles[SYS_PORT_MAX_TRANSFER_HANDLES];
|
|
for (;;) {
|
|
memset(handles, 0, sizeof(handles));
|
|
SYS_PORT_MESSAGE_INFO info;
|
|
memset(&info, 0, sizeof(info));
|
|
info.Size = sizeof(info);
|
|
SYS_RECEIVE_PORT_REQUEST receive;
|
|
memset(&receive, 0, sizeof(receive));
|
|
receive.Size = sizeof(receive);
|
|
receive.EndpointHandle = endpoint;
|
|
receive.Flags = SYS_PORT_FLAG_NONE;
|
|
receive.Payload = payload;
|
|
receive.PayloadCapacity = sizeof(payload);
|
|
receive.HandlesOut = handles;
|
|
receive.HandleCapacity = SYS_PORT_MAX_TRANSFER_HANDLES;
|
|
receive.InfoOut = &info;
|
|
receive.TimeoutMilliseconds = SYS_PORT_TIMEOUT_INFINITE;
|
|
KSTATUS status = SysReceivePort(&receive);
|
|
if (status == NTLSTATUS_PORT_DISCONNECTED)
|
|
break;
|
|
if (!KSUCCESS(status))
|
|
break;
|
|
|
|
uint32_t consumed = 0;
|
|
if (info.Type == SYS_PORT_MESSAGE_REQUEST)
|
|
consumed = RssrvDispatchRequest(endpoint, administrative, payload, info.PayloadLength,
|
|
&info, info.HandleCount == 1 ? handles[0] : SYS_HANDLE_INVALID);
|
|
for (uint32_t i = consumed ? 1u : 0u; i < info.HandleCount; ++i) {
|
|
if (handles[i] != SYS_HANDLE_INVALID) (void)SysCloseHandle(handles[i]);
|
|
}
|
|
}
|
|
|
|
(void)SysCloseHandle(endpoint);
|
|
SysExitThread(0);
|
|
}
|
|
|
|
// Accepts sessions from one listener and gives every connection its own worker thread.
|
|
static void RssrvListenerThread(uintptr_t argument) {
|
|
RSSRV_LISTENER_WORKER* worker = (RSSRV_LISTENER_WORKER*)argument;
|
|
if (!worker) {
|
|
SysExitThread(1);
|
|
return;
|
|
}
|
|
for (;;) {
|
|
SYS_HANDLE endpoint = SYS_HANDLE_INVALID;
|
|
SYS_ACCEPT_PORT_REQUEST accept;
|
|
memset(&accept, 0, sizeof(accept));
|
|
accept.Size = sizeof(accept);
|
|
accept.ListenerHandle = worker->Listener;
|
|
accept.Flags = SYS_PORT_ACCEPT_FLAG_NONE;
|
|
accept.TimeoutMilliseconds = SYS_PORT_TIMEOUT_INFINITE;
|
|
accept.EndpointHandleOut = &endpoint;
|
|
KSTATUS status = SysAcceptPort(&accept);
|
|
if (!KSUCCESS(status))
|
|
break;
|
|
|
|
RSSRV_SESSION* session = (RSSRV_SESSION*)malloc(sizeof(*session));
|
|
if (!session) {
|
|
(void)SysDisconnectPort(endpoint);
|
|
(void)SysCloseHandle(endpoint);
|
|
continue;
|
|
}
|
|
session->Endpoint = endpoint;
|
|
session->Administrative = worker->Administrative;
|
|
|
|
uint32_t threadId = 0;
|
|
SYS_CREATE_THREAD_REQUEST create;
|
|
memset(&create, 0, sizeof(create));
|
|
create.Size = sizeof(create);
|
|
create.Entry = (uintptr_t)RssrvSessionThread;
|
|
create.Argument = (uintptr_t)session;
|
|
create.Priority = 1;
|
|
create.Flags = SYS_CREATE_THREAD_FLAG_NONE;
|
|
create.ThreadIdOut = &threadId;
|
|
status = SysCreateThread(&create);
|
|
if (!KSUCCESS(status)) {
|
|
free(session);
|
|
(void)SysDisconnectPort(endpoint);
|
|
(void)SysCloseHandle(endpoint);
|
|
}
|
|
}
|
|
SysExitThread(0);
|
|
}
|
|
|
|
// Starts one resource listener worker thread.
|
|
static KSTATUS RssrvStartListenerWorker(RSSRV_LISTENER_WORKER* worker,
|
|
SYS_HANDLE listener,
|
|
uint32_t administrative) {
|
|
worker->Listener = listener;
|
|
worker->Administrative = administrative;
|
|
uint32_t threadId = 0;
|
|
SYS_CREATE_THREAD_REQUEST create;
|
|
memset(&create, 0, sizeof(create));
|
|
create.Size = sizeof(create);
|
|
create.Entry = (uintptr_t)RssrvListenerThread;
|
|
create.Argument = (uintptr_t)worker;
|
|
create.Priority = 1;
|
|
create.Flags = SYS_CREATE_THREAD_FLAG_NONE;
|
|
create.ThreadIdOut = &threadId;
|
|
return SysCreateThread(&create);
|
|
}
|
|
|
|
// Creates one listener using the service's bounded default backlog.
|
|
static KSTATUS RssrvCreateListener(SYS_HANDLE* listenerOut) {
|
|
SYS_CREATE_PORT_REQUEST create;
|
|
memset(&create, 0, sizeof(create));
|
|
create.Size = sizeof(create);
|
|
create.Flags = SYS_PORT_FLAG_NONE;
|
|
create.Backlog = RSSRV_LISTENER_BACKLOG;
|
|
create.HandleOut = listenerOut;
|
|
return SysCreatePort(&create);
|
|
}
|
|
|
|
// Accepts the one private service-manager control connection.
|
|
static KSTATUS RssrvAcceptControl(SYS_HANDLE controlListener, SYS_HANDLE* endpointOut) {
|
|
SYS_ACCEPT_PORT_REQUEST accept;
|
|
memset(&accept, 0, sizeof(accept));
|
|
accept.Size = sizeof(accept);
|
|
accept.ListenerHandle = controlListener;
|
|
accept.Flags = SYS_PORT_ACCEPT_FLAG_NONE;
|
|
accept.TimeoutMilliseconds = SYS_PORT_TIMEOUT_INFINITE;
|
|
accept.EndpointHandleOut = endpointOut;
|
|
return SysAcceptPort(&accept);
|
|
}
|
|
|
|
// Sends service readiness and delegates the private administrative resource listener to init.
|
|
static KSTATUS RssrvSendReady(SYS_HANDLE controlEndpoint) {
|
|
NTL_SERVICE_CONTROL_MESSAGE message;
|
|
memset(&message, 0, sizeof(message));
|
|
message.Size = sizeof(message);
|
|
message.Version = NTL_SERVICE_CONTROL_VERSION;
|
|
message.Operation = NTL_SERVICE_CONTROL_READY;
|
|
message.Flags = NTL_SERVICE_READY_FLAG_RESOURCE_ADMIN_LISTENER;
|
|
message.Status = NTLSTATUS_SUCCESS;
|
|
|
|
SYS_PORT_HANDLE_TRANSFER transfer;
|
|
transfer.Handle = gAdminListener;
|
|
transfer.DesiredRights = SYS_RIGHT_QUERY | SYS_PORT_RIGHT_CONNECT;
|
|
transfer.Flags = SYS_PORT_TRANSFER_FLAG_NONE;
|
|
|
|
SYS_SEND_PORT_REQUEST send;
|
|
memset(&send, 0, sizeof(send));
|
|
send.Size = sizeof(send);
|
|
send.EndpointHandle = controlEndpoint;
|
|
send.Flags = SYS_PORT_FLAG_NONE;
|
|
send.Payload = &message;
|
|
send.PayloadLength = sizeof(message);
|
|
send.Handles = &transfer;
|
|
send.HandleCount = 1;
|
|
send.TimeoutMilliseconds = SYS_PORT_TIMEOUT_INFINITE;
|
|
return SysSendPort(&send);
|
|
}
|
|
|
|
// Waits for the service manager's stop notification or control-port disconnect.
|
|
static void RssrvControlLoop(SYS_HANDLE controlEndpoint) {
|
|
for (;;) {
|
|
NTL_SERVICE_CONTROL_MESSAGE message;
|
|
SYS_PORT_MESSAGE_INFO info;
|
|
memset(&message, 0, sizeof(message));
|
|
memset(&info, 0, sizeof(info));
|
|
info.Size = sizeof(info);
|
|
SYS_RECEIVE_PORT_REQUEST receive;
|
|
memset(&receive, 0, sizeof(receive));
|
|
receive.Size = sizeof(receive);
|
|
receive.EndpointHandle = controlEndpoint;
|
|
receive.Payload = &message;
|
|
receive.PayloadCapacity = sizeof(message);
|
|
receive.InfoOut = &info;
|
|
receive.TimeoutMilliseconds = SYS_PORT_TIMEOUT_INFINITE;
|
|
KSTATUS status = SysReceivePort(&receive);
|
|
if (status == NTLSTATUS_PORT_DISCONNECTED)
|
|
return;
|
|
if (!KSUCCESS(status))
|
|
return;
|
|
if (info.Type != SYS_PORT_MESSAGE_NOTIFICATION || info.HandleCount != 0 ||
|
|
info.PayloadLength != sizeof(message) || message.Size != sizeof(message) ||
|
|
message.Version != NTL_SERVICE_CONTROL_VERSION)
|
|
continue;
|
|
if (message.Operation == NTL_SERVICE_CONTROL_STOP)
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Starts RSSRV and owns the global native resource namespace until init requests shutdown.
|
|
void _start(void) {
|
|
KSTATUS status = NtlInitializeExceptionRuntime();
|
|
if (!KSUCCESS(status)) RssrvExit(1);
|
|
|
|
SYS_SECURITY_CONTEXT_INFO security;
|
|
memset(&security, 0, sizeof(security));
|
|
security.Size = sizeof(security);
|
|
status = SysQuerySecurityContext(SYS_SECURITY_CONTEXT_CURRENT, &security);
|
|
if (!KSUCCESS(status) || security.EffectivePrivileges != SYS_PRIV_SYSTEM_SERVICE_REGISTER ||
|
|
security.DelegablePrivileges != 0 || security.ServiceClaims != SYS_SERVICE_CLAIM_RESOURCE ||
|
|
security.DelegableServiceClaims != 0)
|
|
RssrvExit(2);
|
|
|
|
SYS_HANDLE systemVolume = SysGetStartupCapability(SYS_STARTUP_CAP_SYSTEM_VOLUME_NAMESPACE);
|
|
SYS_HANDLE controlListener = SysGetStartupCapability(SYS_STARTUP_CAP_SERVICE_CONTROL);
|
|
SYS_HANDLE volumeManager = SysGetStartupCapability(SYS_STARTUP_CAP_VOLUME_MANAGER);
|
|
if (systemVolume == SYS_HANDLE_INVALID || controlListener == SYS_HANDLE_INVALID ||
|
|
volumeManager == SYS_HANDLE_INVALID)
|
|
RssrvExit(3);
|
|
|
|
status = SysCreateMutex(&gNamespaceMutex);
|
|
if (!KSUCCESS(status)) RssrvExit(3);
|
|
|
|
// RSSRV takes ownership of its startup system-volume handle as the first namespace binding.
|
|
static const char systemRoot[] = "/SystemRoot";
|
|
status = RssrvBindOwned(systemRoot, sizeof(systemRoot) - 1, NTL_RESOURCE_KIND_VOLUME, systemVolume,
|
|
SYS_RIGHT_QUERY | SYS_VOLUME_RIGHT_OPEN | SYS_VOLUME_RIGHT_MODIFY);
|
|
if (!KSUCCESS(status)) RssrvExit(4);
|
|
|
|
SYS_HANDLE controlEndpoint = SYS_HANDLE_INVALID;
|
|
status = RssrvAcceptControl(controlListener, &controlEndpoint);
|
|
(void)SysCloseHandle(controlListener);
|
|
if (!KSUCCESS(status)) RssrvExit(5);
|
|
|
|
status = RssrvCreateListener(&gPublicListener);
|
|
if (!KSUCCESS(status)) RssrvExit(6);
|
|
status = RssrvCreateListener(&gAdminListener);
|
|
if (!KSUCCESS(status)) RssrvExit(7);
|
|
status = RssrvStartListenerWorker(&gPublicWorker, gPublicListener, 0);
|
|
if (!KSUCCESS(status)) RssrvExit(8);
|
|
status = RssrvStartListenerWorker(&gAdminWorker, gAdminListener, 1);
|
|
if (!KSUCCESS(status)) RssrvExit(9);
|
|
|
|
uint64_t volumeGeneration = 0;
|
|
status = RssrvSyncVolumes(volumeManager, &volumeGeneration);
|
|
if (!KSUCCESS(status)) RssrvExit(10);
|
|
status = RssrvStartVolumeWorker(volumeManager);
|
|
if (!KSUCCESS(status)) RssrvExit(11);
|
|
|
|
status = SysRegisterSystemService(SYS_SERVICE_RESOURCE, gPublicListener);
|
|
if (!KSUCCESS(status)) RssrvExit(12);
|
|
status = RssrvSendReady(controlEndpoint);
|
|
if (!KSUCCESS(status)) RssrvExit(13);
|
|
|
|
RssrvControlLoop(controlEndpoint);
|
|
(void)SysUnregisterSystemService(SYS_SERVICE_RESOURCE, gPublicListener);
|
|
(void)SysDisconnectPort(controlEndpoint);
|
|
(void)SysCloseHandle(controlEndpoint);
|
|
RssrvExit(0);
|
|
}
|