mirror of
https://github.com/ApfelTeeSaft/reactos.git
synced 2026-09-01 04:13:35 +00:00
[SERVICES]
Implement service database locking via RLockServiceDatabase / RUnlockServiceDatabase etc... and use this functionality. Patch by Hermès Bélusca. I did not rename the database lock functions because the functions names RLockServiceDatabase and RUnlockServiceDatabase are misleading. They do not lock any database but lock the service start function. The existing functions ScmLockDatabaseExclusive, ScmLockDatabaseShared and ScmUnlockDatabase do protect (lock) the service database (service, group and image lists) form concurrent access. That is also the reason for renaming the SC_LOCK_STRUCT to START_LOCK. See issue #7131 for more details. svn path=/trunk/; revision=56844
This commit is contained in:
@@ -12,6 +12,7 @@ list(APPEND SOURCE
|
||||
database.c
|
||||
driver.c
|
||||
groupdb.c
|
||||
lock.c
|
||||
rpcserver.c
|
||||
services.c
|
||||
services.rc
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
/*
|
||||
* PROJECT: ReactOS Service Control Manager
|
||||
* LICENSE: GPL - See COPYING in the top level directory
|
||||
* FILE: base/system/services/lock.c
|
||||
* PURPOSE: Service lock functions
|
||||
* COPYRIGHT: Copyright 2002-2006 Eric Kohl
|
||||
*/
|
||||
|
||||
/* INCLUDES *****************************************************************/
|
||||
|
||||
#include "services.h"
|
||||
|
||||
#define NDEBUG
|
||||
#include <debug.h>
|
||||
|
||||
|
||||
/* GLOBALS *******************************************************************/
|
||||
|
||||
static PSTART_LOCK pServiceStartLock = NULL;
|
||||
|
||||
|
||||
/* FUNCTIONS *****************************************************************/
|
||||
|
||||
/*
|
||||
* TRUE if locked by the Service Control Manager, FALSE otherwise
|
||||
*/
|
||||
|
||||
DWORD
|
||||
ScmAcquireServiceStartLock(IN BOOL IsServiceController,
|
||||
OUT LPSC_RPC_LOCK lpLock)
|
||||
{
|
||||
DWORD dwRequiredSize;
|
||||
DWORD dwError = ERROR_SUCCESS;
|
||||
|
||||
*lpLock = NULL;
|
||||
|
||||
/* Lock the service database exclusively */
|
||||
ScmLockDatabaseExclusive();
|
||||
|
||||
if (pServiceStartLock != NULL)
|
||||
{
|
||||
dwError = ERROR_SERVICE_DATABASE_LOCKED;
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Allocate a new lock for the database */
|
||||
dwRequiredSize = sizeof(START_LOCK);
|
||||
|
||||
if (!IsServiceController)
|
||||
{
|
||||
/* FIXME: dwRequiredSize += RtlLengthSid(UserSid <-- to be retrieved); */
|
||||
}
|
||||
|
||||
pServiceStartLock = HeapAlloc(GetProcessHeap(),
|
||||
HEAP_ZERO_MEMORY,
|
||||
dwRequiredSize);
|
||||
if (pServiceStartLock == NULL)
|
||||
{
|
||||
dwError = ERROR_NOT_ENOUGH_MEMORY;
|
||||
goto done;
|
||||
}
|
||||
|
||||
pServiceStartLock->Tag = LOCK_TAG;
|
||||
pServiceStartLock->TimeWhenLocked = (DWORD)time(NULL);
|
||||
|
||||
/* FIXME: Retrieve the owner SID. Use IsServiceController. */
|
||||
pServiceStartLock->LockOwnerSid = (PSID)NULL;
|
||||
|
||||
*lpLock = (LPSC_RPC_LOCK)pServiceStartLock;
|
||||
|
||||
done:
|
||||
/* Unlock the service database */
|
||||
ScmUnlockDatabase();
|
||||
|
||||
return dwError;
|
||||
}
|
||||
|
||||
|
||||
DWORD
|
||||
ScmReleaseServiceStartLock(IN OUT LPSC_RPC_LOCK lpLock)
|
||||
{
|
||||
PSTART_LOCK pStartLock;
|
||||
DWORD dwError = ERROR_SUCCESS;
|
||||
|
||||
if (lpLock == NULL)
|
||||
return ERROR_INVALID_SERVICE_LOCK;
|
||||
|
||||
pStartLock = (PSTART_LOCK)*lpLock;
|
||||
|
||||
if (pStartLock->Tag != LOCK_TAG)
|
||||
return ERROR_INVALID_SERVICE_LOCK;
|
||||
|
||||
/* Lock the service database exclusively */
|
||||
ScmLockDatabaseExclusive();
|
||||
|
||||
/* Release the lock handle */
|
||||
if ((pStartLock == pServiceStartLock) &&
|
||||
(pServiceStartLock != NULL))
|
||||
{
|
||||
HeapFree(GetProcessHeap(), 0, pServiceStartLock);
|
||||
pServiceStartLock = NULL;
|
||||
*lpLock = NULL;
|
||||
|
||||
dwError = ERROR_SUCCESS;
|
||||
}
|
||||
else
|
||||
{
|
||||
dwError = ERROR_INVALID_SERVICE_LOCK;
|
||||
}
|
||||
|
||||
/* Unlock the service database */
|
||||
ScmUnlockDatabase();
|
||||
|
||||
return dwError;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Helper functions for RQueryServiceLockStatusW() and
|
||||
* RQueryServiceLockStatusA().
|
||||
* We suppose that lpLockStatus points to a valid
|
||||
* well-sized buffer.
|
||||
*/
|
||||
VOID
|
||||
ScmQueryServiceLockStatusW(OUT LPQUERY_SERVICE_LOCK_STATUSW lpLockStatus)
|
||||
{
|
||||
/* Lock the service database shared */
|
||||
ScmLockDatabaseShared();
|
||||
|
||||
if (pServiceStartLock != NULL)
|
||||
{
|
||||
lpLockStatus->fIsLocked = TRUE;
|
||||
|
||||
/* FIXME: Retrieve the owner name. */
|
||||
lpLockStatus->lpLockOwner = NULL;
|
||||
|
||||
lpLockStatus->dwLockDuration = (DWORD)time(NULL) - pServiceStartLock->TimeWhenLocked;
|
||||
}
|
||||
else
|
||||
{
|
||||
lpLockStatus->fIsLocked = FALSE;
|
||||
|
||||
wcscpy((LPWSTR)(lpLockStatus + 1), L"");
|
||||
lpLockStatus->lpLockOwner = (LPWSTR)(ULONG_PTR)sizeof(QUERY_SERVICE_LOCK_STATUSW);
|
||||
|
||||
lpLockStatus->dwLockDuration = 0;
|
||||
}
|
||||
|
||||
/* Unlock the whole SC manager */
|
||||
ScmUnlockDatabase();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
VOID
|
||||
ScmQueryServiceLockStatusA(OUT LPQUERY_SERVICE_LOCK_STATUSA lpLockStatus)
|
||||
{
|
||||
/* Lock the service database shared */
|
||||
ScmLockDatabaseShared();
|
||||
|
||||
if (pServiceStartLock != NULL)
|
||||
{
|
||||
lpLockStatus->fIsLocked = TRUE;
|
||||
|
||||
/* FIXME: Retrieve the owner name. */
|
||||
lpLockStatus->lpLockOwner = NULL;
|
||||
|
||||
lpLockStatus->dwLockDuration = (DWORD)time(NULL) - pServiceStartLock->TimeWhenLocked;
|
||||
}
|
||||
else
|
||||
{
|
||||
lpLockStatus->fIsLocked = FALSE;
|
||||
|
||||
strcpy((LPSTR)(lpLockStatus + 1), "");
|
||||
lpLockStatus->lpLockOwner = (LPSTR)(ULONG_PTR)sizeof(QUERY_SERVICE_LOCK_STATUSA);
|
||||
|
||||
lpLockStatus->dwLockDuration = 0;
|
||||
}
|
||||
|
||||
/* Unlock the whole SC manager */
|
||||
ScmUnlockDatabase();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/* EOF */
|
||||
@@ -1307,7 +1307,7 @@ DWORD RLockServiceDatabase(
|
||||
|
||||
DPRINT("RLockServiceDatabase() called\n");
|
||||
|
||||
*lpLock = 0;
|
||||
*lpLock = NULL;
|
||||
|
||||
hMgr = ScmGetServiceManagerFromHandle(hSCManager);
|
||||
if (hMgr == NULL)
|
||||
@@ -1320,12 +1320,7 @@ DWORD RLockServiceDatabase(
|
||||
SC_MANAGER_LOCK))
|
||||
return ERROR_ACCESS_DENIED;
|
||||
|
||||
// return ScmLockDatabase(0, hMgr->0xC, hLock);
|
||||
|
||||
/* FIXME: Lock the database */
|
||||
*lpLock = (SC_RPC_LOCK)0x12345678; /* Dummy! */
|
||||
|
||||
return ERROR_SUCCESS;
|
||||
return ScmAcquireServiceStartLock(FALSE, lpLock);
|
||||
}
|
||||
|
||||
|
||||
@@ -1706,8 +1701,8 @@ DWORD RSetServiceStatus(
|
||||
DWORD RUnlockServiceDatabase(
|
||||
LPSC_RPC_LOCK Lock)
|
||||
{
|
||||
UNIMPLEMENTED;
|
||||
return ERROR_SUCCESS;
|
||||
DPRINT("RUnlockServiceDatabase(%p)\n", Lock);
|
||||
return ScmReleaseServiceStartLock(Lock);
|
||||
}
|
||||
|
||||
|
||||
@@ -2857,12 +2852,41 @@ Done:
|
||||
/* Function 18 */
|
||||
DWORD RQueryServiceLockStatusW(
|
||||
SC_RPC_HANDLE hSCManager,
|
||||
LPQUERY_SERVICE_LOCK_STATUSW lpLockStatus,
|
||||
LPBYTE lpBuf, // LPQUERY_SERVICE_LOCK_STATUSW lpLockStatus,
|
||||
DWORD cbBufSize,
|
||||
LPBOUNDED_DWORD_4K pcbBytesNeeded)
|
||||
{
|
||||
UNIMPLEMENTED;
|
||||
return ERROR_CALL_NOT_IMPLEMENTED;
|
||||
LPQUERY_SERVICE_LOCK_STATUSW lpLockStatus = (LPQUERY_SERVICE_LOCK_STATUSW)lpBuf;
|
||||
PMANAGER_HANDLE hMgr;
|
||||
DWORD dwRequiredSize;
|
||||
|
||||
if (!lpLockStatus || !pcbBytesNeeded)
|
||||
return ERROR_INVALID_PARAMETER;
|
||||
|
||||
hMgr = ScmGetServiceManagerFromHandle(hSCManager);
|
||||
if (hMgr == NULL)
|
||||
{
|
||||
DPRINT1("Invalid service manager handle!\n");
|
||||
return ERROR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
if (!RtlAreAllAccessesGranted(hMgr->Handle.DesiredAccess,
|
||||
SC_MANAGER_QUERY_LOCK_STATUS))
|
||||
{
|
||||
DPRINT("Insufficient access rights! 0x%lx\n", hMgr->Handle.DesiredAccess);
|
||||
return ERROR_ACCESS_DENIED;
|
||||
}
|
||||
|
||||
/* HACK: we need to compute instead the real length of the owner name */
|
||||
dwRequiredSize = sizeof(QUERY_SERVICE_LOCK_STATUSW) + sizeof(WCHAR);
|
||||
*pcbBytesNeeded = dwRequiredSize;
|
||||
|
||||
if (cbBufSize < dwRequiredSize)
|
||||
return ERROR_INSUFFICIENT_BUFFER;
|
||||
|
||||
ScmQueryServiceLockStatusW(lpLockStatus);
|
||||
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
@@ -2875,6 +2899,7 @@ DWORD RStartServiceW(
|
||||
DWORD dwError = ERROR_SUCCESS;
|
||||
PSERVICE_HANDLE hSvc;
|
||||
PSERVICE lpService = NULL;
|
||||
SC_RPC_LOCK Lock = NULL;
|
||||
DWORD i;
|
||||
|
||||
DPRINT("RStartServiceW(%p %lu %p) called\n", hService, argc, argv);
|
||||
@@ -2917,9 +2942,17 @@ DWORD RStartServiceW(
|
||||
if (lpService->bDeleted)
|
||||
return ERROR_SERVICE_MARKED_FOR_DELETE;
|
||||
|
||||
/* Acquire the service start lock until the service has been started */
|
||||
dwError = ScmAcquireServiceStartLock(TRUE, &Lock);
|
||||
if (dwError != ERROR_SUCCESS)
|
||||
return dwError;
|
||||
|
||||
/* Start the service */
|
||||
dwError = ScmStartService(lpService, argc, (LPWSTR*)argv);
|
||||
|
||||
/* Release the service start lock */
|
||||
ScmReleaseServiceStartLock(&Lock);
|
||||
|
||||
return dwError;
|
||||
}
|
||||
|
||||
@@ -4090,12 +4123,41 @@ Done:
|
||||
/* Function 30 */
|
||||
DWORD RQueryServiceLockStatusA(
|
||||
SC_RPC_HANDLE hSCManager,
|
||||
LPQUERY_SERVICE_LOCK_STATUSA lpLockStatus,
|
||||
LPBYTE lpBuf, // LPQUERY_SERVICE_LOCK_STATUSA lpLockStatus,
|
||||
DWORD cbBufSize,
|
||||
LPBOUNDED_DWORD_4K pcbBytesNeeded)
|
||||
{
|
||||
UNIMPLEMENTED;
|
||||
return ERROR_CALL_NOT_IMPLEMENTED;
|
||||
LPQUERY_SERVICE_LOCK_STATUSA lpLockStatus = (LPQUERY_SERVICE_LOCK_STATUSA)lpBuf;
|
||||
PMANAGER_HANDLE hMgr;
|
||||
DWORD dwRequiredSize;
|
||||
|
||||
if (!lpLockStatus || !pcbBytesNeeded)
|
||||
return ERROR_INVALID_PARAMETER;
|
||||
|
||||
hMgr = ScmGetServiceManagerFromHandle(hSCManager);
|
||||
if (hMgr == NULL)
|
||||
{
|
||||
DPRINT1("Invalid service manager handle!\n");
|
||||
return ERROR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
if (!RtlAreAllAccessesGranted(hMgr->Handle.DesiredAccess,
|
||||
SC_MANAGER_QUERY_LOCK_STATUS))
|
||||
{
|
||||
DPRINT("Insufficient access rights! 0x%lx\n", hMgr->Handle.DesiredAccess);
|
||||
return ERROR_ACCESS_DENIED;
|
||||
}
|
||||
|
||||
/* FIXME: we need to compute instead the real length of the owner name */
|
||||
dwRequiredSize = sizeof(QUERY_SERVICE_LOCK_STATUSA) + sizeof(CHAR);
|
||||
*pcbBytesNeeded = dwRequiredSize;
|
||||
|
||||
if (cbBufSize < dwRequiredSize)
|
||||
return ERROR_INSUFFICIENT_BUFFER;
|
||||
|
||||
ScmQueryServiceLockStatusA(lpLockStatus);
|
||||
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
@@ -4108,6 +4170,7 @@ DWORD RStartServiceA(
|
||||
DWORD dwError = ERROR_SUCCESS;
|
||||
PSERVICE_HANDLE hSvc;
|
||||
PSERVICE lpService = NULL;
|
||||
SC_RPC_LOCK Lock = NULL;
|
||||
LPWSTR *lpVector = NULL;
|
||||
DWORD i;
|
||||
DWORD dwLength;
|
||||
@@ -4183,6 +4246,17 @@ DWORD RStartServiceA(
|
||||
/* Start the service */
|
||||
dwError = ScmStartService(lpService, argc, lpVector);
|
||||
|
||||
/* Acquire the service start lock until the service has been started */
|
||||
dwError = ScmAcquireServiceStartLock(TRUE, &Lock);
|
||||
if (dwError != ERROR_SUCCESS)
|
||||
goto done;
|
||||
|
||||
/* Start the service */
|
||||
dwError = ScmStartService(lpService, argc, lpVector);
|
||||
|
||||
/* Release the service start lock */
|
||||
ScmReleaseServiceStartLock(&Lock);
|
||||
|
||||
done:
|
||||
/* Free the Unicode argument vector */
|
||||
if (lpVector != NULL)
|
||||
|
||||
@@ -376,7 +376,8 @@ wWinMain(HINSTANCE hInstance,
|
||||
LPWSTR lpCmdLine,
|
||||
int nShowCmd)
|
||||
{
|
||||
HANDLE hScmStartEvent;
|
||||
HANDLE hScmStartEvent = NULL;
|
||||
SC_RPC_LOCK Lock = NULL;
|
||||
DWORD dwError;
|
||||
|
||||
DPRINT("SERVICES: Service Control Manager\n");
|
||||
@@ -385,23 +386,36 @@ wWinMain(HINSTANCE hInstance,
|
||||
if (!ScmCreateStartEvent(&hScmStartEvent))
|
||||
{
|
||||
DPRINT1("SERVICES: Failed to create start event\n");
|
||||
ExitThread(0);
|
||||
goto done;
|
||||
}
|
||||
|
||||
DPRINT("SERVICES: created start event with handle %p.\n", hScmStartEvent);
|
||||
|
||||
/* Create the shutdown event */
|
||||
hScmShutdownEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
|
||||
if (hScmShutdownEvent == NULL)
|
||||
{
|
||||
DPRINT1("SERVICES: Failed to create shutdown event\n");
|
||||
goto done;
|
||||
}
|
||||
|
||||
// ScmInitThreadManager();
|
||||
|
||||
/* FIXME: more initialization */
|
||||
|
||||
/* Read the control set values */
|
||||
if (!ScmGetControlSetValues())
|
||||
{
|
||||
DPRINT1("SERVICES: failed to read the control set values\n");
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Create the service database */
|
||||
dwError = ScmCreateServiceDatabase();
|
||||
if (dwError != ERROR_SUCCESS)
|
||||
{
|
||||
DPRINT1("SERVICES: failed to create SCM database (Error %lu)\n", dwError);
|
||||
CloseHandle(hScmStartEvent);
|
||||
ExitThread(0);
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Update service database */
|
||||
@@ -429,26 +443,37 @@ wWinMain(HINSTANCE hInstance,
|
||||
|
||||
ScmInitNamedPipeCriticalSection();
|
||||
|
||||
/* Acquire the service start lock until autostart services have been started */
|
||||
dwError = ScmAcquireServiceStartLock(TRUE, &Lock);
|
||||
if (dwError != ERROR_SUCCESS)
|
||||
{
|
||||
DPRINT1("SERVICES: failed to acquire the service start lock (Error %lu)\n", dwError);
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Start auto-start services */
|
||||
ScmAutoStartServices();
|
||||
|
||||
/* FIXME: more to do ? */
|
||||
|
||||
/* Release the service start lock */
|
||||
ScmReleaseServiceStartLock(&Lock);
|
||||
|
||||
DPRINT("SERVICES: Running.\n");
|
||||
|
||||
/* Create the shutdown event and wait until it gets set */
|
||||
hScmShutdownEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
|
||||
if (hScmShutdownEvent)
|
||||
WaitForSingleObject(hScmShutdownEvent, INFINITE);
|
||||
/* Wait until the shutdown event gets signaled */
|
||||
WaitForSingleObject(hScmShutdownEvent, INFINITE);
|
||||
|
||||
done:
|
||||
ScmDeleteNamedPipeCriticalSection();
|
||||
|
||||
/* Close the shutdown event */
|
||||
CloseHandle(hScmShutdownEvent);
|
||||
if (hScmShutdownEvent != NULL)
|
||||
CloseHandle(hScmShutdownEvent);
|
||||
|
||||
/* Close the start event */
|
||||
CloseHandle(hScmStartEvent);
|
||||
if (hScmStartEvent != NULL)
|
||||
CloseHandle(hScmStartEvent);
|
||||
|
||||
DPRINT("SERVICES: Finished.\n");
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <time.h>
|
||||
#define WIN32_NO_STATUS
|
||||
#include <windows.h>
|
||||
#include <netevent.h>
|
||||
@@ -67,6 +68,16 @@ typedef struct _SERVICE
|
||||
} SERVICE, *PSERVICE;
|
||||
|
||||
|
||||
#define LOCK_TAG 0x4C697041 /* 'ApiL' */
|
||||
|
||||
typedef struct _START_LOCK
|
||||
{
|
||||
DWORD Tag; /* Must be LOCK_TAG */
|
||||
DWORD TimeWhenLocked; /* Number of seconds since 1970 */
|
||||
PSID LockOwnerSid; /* It is NULL if the SCM aquired the lock */
|
||||
} START_LOCK, *PSTART_LOCK;
|
||||
|
||||
|
||||
/* VARIABLES ***************************************************************/
|
||||
|
||||
extern LIST_ENTRY ServiceListHead;
|
||||
@@ -150,6 +161,14 @@ DWORD ScmSetServiceGroup(PSERVICE lpService,
|
||||
LPCWSTR lpGroupName);
|
||||
|
||||
|
||||
/* lock.c */
|
||||
DWORD ScmAcquireServiceStartLock(IN BOOL IsServiceController,
|
||||
OUT LPSC_RPC_LOCK lpLock);
|
||||
DWORD ScmReleaseServiceStartLock(IN OUT LPSC_RPC_LOCK lpLock);
|
||||
VOID ScmQueryServiceLockStatusW(OUT LPQUERY_SERVICE_LOCK_STATUSW lpLockStatus);
|
||||
VOID ScmQueryServiceLockStatusA(OUT LPQUERY_SERVICE_LOCK_STATUSA lpLockStatus);
|
||||
|
||||
|
||||
/* rpcserver.c */
|
||||
|
||||
VOID ScmStartRpcServer(VOID);
|
||||
|
||||
@@ -2286,7 +2286,7 @@ QueryServiceLockStatusA(SC_HANDLE hSCManager,
|
||||
{
|
||||
/* Call to services.exe using RPC */
|
||||
dwError = RQueryServiceLockStatusA((SC_RPC_HANDLE)hSCManager,
|
||||
lpStatusPtr,
|
||||
(LPBYTE)lpStatusPtr,
|
||||
dwBufferSize,
|
||||
pcbBytesNeeded);
|
||||
}
|
||||
@@ -2348,7 +2348,7 @@ QueryServiceLockStatusW(SC_HANDLE hSCManager,
|
||||
{
|
||||
/* Call to services.exe using RPC */
|
||||
dwError = RQueryServiceLockStatusW((SC_RPC_HANDLE)hSCManager,
|
||||
lpStatusPtr,
|
||||
(LPBYTE)lpStatusPtr,
|
||||
dwBufferSize,
|
||||
pcbBytesNeeded);
|
||||
}
|
||||
|
||||
@@ -443,7 +443,8 @@ interface svcctl
|
||||
/* Function 18 */
|
||||
DWORD RQueryServiceLockStatusW(
|
||||
[in] SC_RPC_HANDLE hSCManager,
|
||||
[out] LPQUERY_SERVICE_LOCK_STATUSW lpLockStatus,
|
||||
[out, size_is(cbBufSize)] LPBYTE lpLockStatus,
|
||||
/* FIXME: should be [out] LPQUERY_SERVICE_LOCK_STATUSW lpLockStatus, */
|
||||
[in, range(0, 1024*4)] DWORD cbBufSize,
|
||||
[out] LPBOUNDED_DWORD_4K pcbBytesNeeded);
|
||||
|
||||
@@ -555,7 +556,8 @@ interface svcctl
|
||||
/* Function 30 */
|
||||
DWORD RQueryServiceLockStatusA(
|
||||
[in] SC_RPC_HANDLE hSCManager,
|
||||
[out] LPQUERY_SERVICE_LOCK_STATUSA lpLockStatus,
|
||||
[out, size_is(cbBufSize)] LPBYTE lpLockStatus,
|
||||
/* FIXME: should be [out] LPQUERY_SERVICE_LOCK_STATUSA lpLockStatus, */
|
||||
[in, range(0, 1024*4)] DWORD cbBufSize,
|
||||
[out] LPBOUNDED_DWORD_4K pcbBytesNeeded);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user