1201 lines
33 KiB
C++
1201 lines
33 KiB
C++
/*++
|
||
|
||
Copyright (c) 1991-2000 Microsoft Corporation
|
||
|
||
Module Name:
|
||
|
||
DRIVER.CXX
|
||
|
||
Abstract:
|
||
|
||
Functions for Loading, Maintaining, and Unloading Device Drivers.
|
||
ScLoadDeviceDriver
|
||
ScControlDriver
|
||
ScGetDriverStatus
|
||
ScGetObjectName
|
||
ScUnloadDriver
|
||
ScIsPnPDriver
|
||
|
||
Author:
|
||
|
||
Dan Lafferty (danl) 27-Apr-1991
|
||
|
||
Environment:
|
||
|
||
User Mode -Win32
|
||
|
||
Notes:
|
||
|
||
Revision History:
|
||
|
||
22-Feb-1999 jschwart
|
||
Prevent stopping of PnP drivers via ControlService (since it bluescreens
|
||
the machine and is easier to fix here than the REAL fix in NtUnloadDriver)
|
||
02-Oct-1997 AnirudhS
|
||
Removed IOCTL to NDIS for NDIS driver arrivals.
|
||
08-Jan-1997 AnirudhS
|
||
ScGetDriverStatus, ScGetObjectName, etc: Instead of expecting a
|
||
shared lock on the service database and upgrading it to exclusive,
|
||
which is broken, just expect an exclusive lock.
|
||
03-Jan-1997 AnirudhS
|
||
Temporary fix for QFE bug 66887: When a driver stops, don't free its
|
||
object name. Free it only when the driver is reconfigured or deleted.
|
||
The changes are marked by "QFE 66887" comments.
|
||
19-Jan-1996 AnirudhS
|
||
Add IOCTLs to NDIS for TDI and NDIS driver arrivals.
|
||
30-Oct-1995 AnirudhS
|
||
ScLoadDeviceDriver: Turn STATUS_IMAGE_ALREADY_LOADED into
|
||
ERROR_SERVICE_ALREADY_RUNNING.
|
||
05-Aug-1993 Danl
|
||
ScGetObjectName: It is possible to read an empty-string object
|
||
name from the registry. If we do, we need to treat this the same
|
||
as if the ObjectName value were not in the registry.
|
||
01-Jun-1993 Danl
|
||
GetDriverStatus: When state moves from STOPPED to RUNNING,
|
||
then the service record is updated so that STOP is accepted as
|
||
a control.
|
||
10-Jul-1992 Danl
|
||
Changed RegCloseKey to ScRegCloseKey
|
||
27-Apr-1991 danl
|
||
created
|
||
|
||
--*/
|
||
|
||
//
|
||
// INCLUDES
|
||
//
|
||
|
||
#include "precomp.hxx"
|
||
#include <ntddndis.h> // NDIS IOCTL codes
|
||
#include <stdlib.h> // wide character c runtimes.
|
||
#include <cfgmgr32.h> // PNP manager functions
|
||
#include <pnp.h> // PNP manager functions, server side
|
||
#include "scconfig.h" // ScReadStartName
|
||
#include "driver.h" // ScGetDriverStatus()
|
||
#include "depend.h" // ScGetDependentsStopped()
|
||
#include "scsec.h" // ScGetPrivilege, ScReleasePrivilege
|
||
#include <debugfmt.h> // FORMAT_LPWSTR
|
||
|
||
//
|
||
// DEFINES
|
||
//
|
||
|
||
#define OBJ_DIR_INFO_SIZE 4096L
|
||
|
||
#define SERVICE_PATH L"\\Registry\\Machine\\System\\CurrentControlSet\\Services\\"
|
||
|
||
#define FILE_SYSTEM_OBJ_NAME L"\\FileSystem\\"
|
||
|
||
#define DRIVER_OBJ_NAME L"\\Driver\\"
|
||
|
||
//
|
||
// This is a string version of GUID_DEVCLASS_LEGACYDRIVER in devguid.h
|
||
//
|
||
#define LEGACYDRIVER_STRING L"{8ECC055D-047F-11D1-A537-0000F8753ED1}"
|
||
|
||
//
|
||
// LOCAL FUNCTION PROTOTYPES
|
||
//
|
||
|
||
DWORD
|
||
ScGetObjectName(
|
||
LPSERVICE_RECORD ServiceRecord
|
||
);
|
||
|
||
|
||
BOOL
|
||
ScIsPnPDriver(
|
||
IN LPSERVICE_RECORD Service
|
||
);
|
||
|
||
|
||
DWORD
|
||
ScLoadDeviceDriver(
|
||
LPSERVICE_RECORD ServiceRecord
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function attempts to load a device driver. If the NtLoadDriver
|
||
call is successful, we know that the driver is running (since this
|
||
is a synchronous operation). If the call fails, the appropriate
|
||
windows error code is returned.
|
||
|
||
NOTE: It is expected that the Database Lock will be held with
|
||
exclusive access upon entry to this routine.
|
||
|
||
WARNING: This routine releases and acquires the Database Lock.
|
||
|
||
Arguments:
|
||
|
||
ServiceRecord - This is pointer to a service record for the Device
|
||
Driver that is being started.
|
||
|
||
Return Value:
|
||
|
||
|
||
|
||
--*/
|
||
|
||
{
|
||
DWORD status = NO_ERROR;
|
||
NTSTATUS ntStatus;
|
||
LPWSTR regKeyPath;
|
||
UNICODE_STRING regKeyPathString;
|
||
ULONG privileges[1];
|
||
|
||
#if DBG
|
||
DWORD dwLoadTime;
|
||
#endif
|
||
|
||
SC_LOG1(TRACE,"In ScLoadDeviceDriver for "FORMAT_LPWSTR" Driver\n",
|
||
ServiceRecord->ServiceName);
|
||
|
||
SC_ASSERT(ScServiceListLock.Have());
|
||
SC_ASSERT(ScServiceRecordLock.HaveExclusive());
|
||
|
||
//
|
||
// If the ObjectName does not exist yet, create one.
|
||
//
|
||
if (ServiceRecord->ObjectName == NULL) {
|
||
status = ScGetObjectName(ServiceRecord);
|
||
if (status != NO_ERROR) {
|
||
goto CleanExit;
|
||
}
|
||
}
|
||
|
||
//
|
||
// Create the Registry Key Path for this driver name.
|
||
//
|
||
regKeyPath = (LPWSTR)LocalAlloc(
|
||
LMEM_FIXED,
|
||
sizeof(SERVICE_PATH) +
|
||
WCSSIZE(ServiceRecord->ServiceName));
|
||
|
||
if (regKeyPath == NULL) {
|
||
status = ERROR_NOT_ENOUGH_MEMORY;
|
||
goto CleanExit;
|
||
}
|
||
wcscpy(regKeyPath, SERVICE_PATH);
|
||
wcscat(regKeyPath, ServiceRecord->ServiceName);
|
||
|
||
//
|
||
// Load the Driver
|
||
// (but first get SeLoadDriverPrivilege)
|
||
//
|
||
RtlInitUnicodeString(®KeyPathString, regKeyPath);
|
||
|
||
privileges[0] = SE_LOAD_DRIVER_PRIVILEGE;
|
||
status = ScGetPrivilege(1,privileges);
|
||
if (status != NO_ERROR) {
|
||
goto CleanExit;
|
||
}
|
||
|
||
SC_ASSERT(ServiceRecord->ServiceStatus.dwCurrentState == SERVICE_STOPPED);
|
||
|
||
//
|
||
// Release and reacquire the lock around the NtLoadDriver call so that
|
||
// a driver can generate a PnP event during its load routine (in NT5,
|
||
// the ClusDisk driver can synchronously generate a dismount event while
|
||
// it's loading)
|
||
//
|
||
ScServiceRecordLock.Release();
|
||
|
||
#if DBG
|
||
dwLoadTime = GetTickCount();
|
||
#endif
|
||
|
||
ntStatus = NtLoadDriver(®KeyPathString);
|
||
|
||
#if DBG
|
||
dwLoadTime = GetTickCount() - dwLoadTime;
|
||
if (dwLoadTime > 5000)
|
||
{
|
||
SC_LOG2(ERROR,
|
||
" **** NtLoadDriver(%ws) took %lu ms!\n",
|
||
ServiceRecord->ServiceName,
|
||
dwLoadTime);
|
||
}
|
||
#endif
|
||
|
||
ScServiceRecordLock.GetExclusive();
|
||
|
||
ScReleasePrivilege();
|
||
|
||
LocalFree(regKeyPath);
|
||
|
||
if (NT_SUCCESS(ntStatus)) {
|
||
SC_LOG1(TRACE,"ScLoadDeviceDriver: NtLoadDriver Success for "
|
||
FORMAT_LPWSTR " \n",ServiceRecord->ServiceName);
|
||
}
|
||
else if (ntStatus == STATUS_IMAGE_ALREADY_LOADED) {
|
||
SC_LOG1(TRACE,"ScLoadDeviceDriver: Driver " FORMAT_LPWSTR
|
||
" is already running\n",ServiceRecord->ServiceName);
|
||
|
||
status = ERROR_SERVICE_ALREADY_RUNNING;
|
||
}
|
||
else {
|
||
SC_LOG2(WARNING,"ScLoadDeviceDriver: NtLoadDriver(%ws) Failed 0x%lx\n",
|
||
ServiceRecord->ServiceName,
|
||
ntStatus);
|
||
|
||
if (ntStatus == STATUS_NO_SUCH_DEVICE) {
|
||
status = ERROR_BAD_UNIT;
|
||
}
|
||
else {
|
||
status = RtlNtStatusToDosError(ntStatus);
|
||
}
|
||
goto CleanExit;
|
||
}
|
||
|
||
//
|
||
// Update the Service Record with this driver's start information.
|
||
//
|
||
|
||
ServiceRecord->ServiceStatus.dwCurrentState = SERVICE_RUNNING;
|
||
ServiceRecord->ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
|
||
ServiceRecord->ServiceStatus.dwWin32ExitCode = NO_ERROR;
|
||
ServiceRecord->ServiceStatus.dwServiceSpecificExitCode = 0;
|
||
ServiceRecord->ServiceStatus.dwCheckPoint = 0;
|
||
ServiceRecord->ServiceStatus.dwWaitHint = 0;
|
||
ServiceRecord->UseCount++;
|
||
SC_LOG2(USECOUNT, "ScLoadDeviceDriver: " FORMAT_LPWSTR
|
||
" increment USECOUNT=%lu\n", ServiceRecord->ServiceName, ServiceRecord->UseCount);
|
||
|
||
|
||
//
|
||
// Tell NDIS to issue the PNP notifications about this driver's arrival,
|
||
// if necessary
|
||
//
|
||
ScNotifyNdis(ServiceRecord);
|
||
|
||
CleanExit:
|
||
return(status);
|
||
}
|
||
|
||
|
||
VOID
|
||
ScNotifyNdis(
|
||
LPSERVICE_RECORD ServiceRecord
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function issues Plug-and-Play notifications to NDIS about the
|
||
arrival of certain types of drivers and services.
|
||
|
||
Arguments:
|
||
|
||
ServiceRecord - Service or driver that just started successfully.
|
||
|
||
Return Value:
|
||
|
||
None. Errors are written to the event log.
|
||
|
||
--*/
|
||
|
||
{
|
||
HANDLE hDevice;
|
||
BOOL fResult;
|
||
DWORD cb;
|
||
DWORD IoControlCode;
|
||
|
||
|
||
//
|
||
// TDI GROUP SPECIAL: Drivers in group TDI are assumed to be PNP-unaware
|
||
// network transport drivers. Win32 services in group TDI are assumed to
|
||
// be in that group because they start PNP-unaware transport drivers.
|
||
// (PNP-aware transports are in group PNP_TDI.)
|
||
// Such a transport doesn't notify the higher-level network components
|
||
// (TDI clients) that it has arrived. So we issue an IOCTL to NDIS to ask
|
||
// it to read the transport's bindings from the registry and notify the
|
||
// clients on this transport's behalf.
|
||
//
|
||
if (ServiceRecord->MemberOfGroup == ScGlobalTDIGroup)
|
||
{
|
||
IoControlCode = IOCTL_NDIS_ADD_TDI_DEVICE;
|
||
}
|
||
else
|
||
{
|
||
return;
|
||
}
|
||
|
||
hDevice = CreateFile(
|
||
L"\\\\.\\NDIS",
|
||
GENERIC_READ | GENERIC_WRITE,
|
||
0, // sharing mode - not significant
|
||
NULL, // security attributes
|
||
OPEN_EXISTING,
|
||
0, // file attributes and flags
|
||
NULL // handle to template file
|
||
);
|
||
|
||
if (hDevice == INVALID_HANDLE_VALUE)
|
||
{
|
||
SC_LOG(WARNING, "Couldn't open handle to NDIS for IOCTL, error %lu\n",
|
||
GetLastError());
|
||
return;
|
||
}
|
||
|
||
fResult = DeviceIoControl(
|
||
hDevice,
|
||
IoControlCode,
|
||
ServiceRecord->ServiceName, // input buffer
|
||
(DWORD) WCSSIZE(ServiceRecord->ServiceName), // input buffer size
|
||
NULL, // output buffer
|
||
0, // output buffer size
|
||
&cb, // bytes returned
|
||
NULL); // OVERLAPPED structure
|
||
|
||
CloseHandle(hDevice);
|
||
|
||
if (!fResult)
|
||
{
|
||
SC_LOG3(WARNING, "IOCTL %#lx to NDIS for %ws failed, error %lu\n",
|
||
IoControlCode, ServiceRecord->ServiceName, GetLastError());
|
||
}
|
||
}
|
||
|
||
|
||
DWORD
|
||
ScControlDriver(
|
||
DWORD ControlCode,
|
||
LPSERVICE_RECORD ServiceRecord,
|
||
LPSERVICE_STATUS lpServiceStatus
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function checks controls that are passed to device drivers. Only
|
||
two controls are accepted.
|
||
stop - This function attemps to unload the driver. The driver
|
||
state is set to STOP_PENDING since unload is an
|
||
asynchronous operation. We have to wait until another
|
||
call is made that will return the status of this driver
|
||
before we can query the driver object to see if it is
|
||
still there.
|
||
|
||
interrogate - This function attempts to query the driver object
|
||
to see if it is still there.
|
||
|
||
WARNING: This function should only be called with a pointer to
|
||
a ServiceRecord that belongs to a DRIVER.
|
||
|
||
Arguments:
|
||
|
||
ControlCode - This is the control request that is being sent to
|
||
control the driver.
|
||
|
||
ServiceRecord - This is a pointer to the service record for the
|
||
driver that is to be controlled.
|
||
|
||
lpServiceStatus - This is a pointer to a buffer that upon exit will
|
||
contain the latest service status.
|
||
|
||
Return Value:
|
||
|
||
|
||
|
||
--*/
|
||
|
||
{
|
||
DWORD status;
|
||
|
||
SC_LOG1(TRACE,"In ScControlDriver for "FORMAT_LPWSTR" Driver\n",
|
||
ServiceRecord->ServiceName);
|
||
|
||
CServiceRecordExclusiveLock Lock;
|
||
|
||
switch(ControlCode) {
|
||
case SERVICE_CONTROL_INTERROGATE:
|
||
|
||
//
|
||
// On interrogate, we need to see if the service is still there.
|
||
// Then we update the status accordingly.
|
||
//
|
||
status = ScGetDriverStatus(ServiceRecord, lpServiceStatus);
|
||
|
||
if (status == NO_ERROR) {
|
||
|
||
//
|
||
// Based on the state, return the appropriate error code
|
||
// so driver return codes match with those of services
|
||
//
|
||
switch(lpServiceStatus->dwCurrentState) {
|
||
|
||
case SERVICE_STOPPED:
|
||
status = ERROR_SERVICE_NOT_ACTIVE;
|
||
break;
|
||
|
||
case SERVICE_STOP_PENDING:
|
||
status = ERROR_SERVICE_CANNOT_ACCEPT_CTRL;
|
||
break;
|
||
}
|
||
}
|
||
|
||
break;
|
||
|
||
case SERVICE_CONTROL_STOP:
|
||
|
||
//
|
||
// This operation is invalid on PnP drivers
|
||
//
|
||
if (ScIsPnPDriver(ServiceRecord)) {
|
||
status = ERROR_INVALID_SERVICE_CONTROL;
|
||
break;
|
||
}
|
||
|
||
//
|
||
// Find out if the driver is still running.
|
||
//
|
||
status = ScGetDriverStatus(ServiceRecord, lpServiceStatus);
|
||
|
||
if (status != NO_ERROR) {
|
||
break;
|
||
}
|
||
|
||
if (ServiceRecord->ServiceStatus.dwCurrentState != SERVICE_RUNNING) {
|
||
|
||
//
|
||
// If the driver is not running, then it cannot accept the
|
||
// STOP control request. Drivers do not accept STOP requests
|
||
// when in the START_PENDING state. Make these return codes
|
||
// match with those of services based on the driver's state
|
||
//
|
||
|
||
switch(lpServiceStatus->dwCurrentState) {
|
||
|
||
case SERVICE_STOPPED:
|
||
status = ERROR_SERVICE_NOT_ACTIVE;
|
||
break;
|
||
|
||
case SERVICE_STOP_PENDING:
|
||
status = ERROR_SERVICE_CANNOT_ACCEPT_CTRL;
|
||
break;
|
||
|
||
default:
|
||
status = ERROR_INVALID_SERVICE_CONTROL;
|
||
break;
|
||
}
|
||
|
||
goto CleanExit;
|
||
}
|
||
|
||
//
|
||
// Check for dependent services still running
|
||
//
|
||
|
||
if (! ScDependentsStopped(ServiceRecord)) {
|
||
status = ERROR_DEPENDENT_SERVICES_RUNNING;
|
||
goto CleanExit;
|
||
}
|
||
|
||
status = ScUnloadDriver(ServiceRecord);
|
||
|
||
if (status == ERROR_INVALID_SERVICE_CONTROL) {
|
||
|
||
//
|
||
// If the driver fails to unload with this error,
|
||
// then it must be one that cannot be stopped.
|
||
// We want to mark it as such, and return an error.
|
||
//
|
||
SC_LOG0(TRACE,"ScControlDriver: Marking driver as non-stoppable\n");
|
||
|
||
ServiceRecord->ServiceStatus.dwControlsAccepted = 0L;
|
||
|
||
goto CleanExit;
|
||
}
|
||
|
||
//
|
||
// Set the Current State to STOP_PENDING, and get the
|
||
// current status (again);
|
||
//
|
||
ServiceRecord->ServiceStatus.dwCurrentState = SERVICE_STOP_PENDING;
|
||
status = ScGetDriverStatus(ServiceRecord, lpServiceStatus);
|
||
|
||
break;
|
||
|
||
default:
|
||
status = ERROR_INVALID_SERVICE_CONTROL;
|
||
}
|
||
|
||
CleanExit:
|
||
return(status);
|
||
}
|
||
|
||
|
||
DWORD
|
||
ScGetDriverStatus(
|
||
IN OUT LPSERVICE_RECORD ServiceRecord,
|
||
OUT LPSERVICE_STATUS lpServiceStatus OPTIONAL
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function determines the correct current status for a device driver.
|
||
The updated status is only returned if NO_ERROR is returned.
|
||
|
||
WARNING: This function expects the EXCLUSIVE database lock to be held.
|
||
CODEWORK: For greater parallelism while enumerating driver status, get
|
||
the exclusive lock only if the driver status has changed.
|
||
|
||
NOTE: The ServiceRecord passed in MUST be for a DeviceDriver.
|
||
|
||
|
||
|
||
Arguments:
|
||
|
||
ServiceRecord - This is a pointer to the Service Record for the
|
||
Device Driver for which the status is desired.
|
||
|
||
lpServiceStatus - This is a pointer to a buffer that upon exit will
|
||
contain the latest service status.
|
||
|
||
Return Value:
|
||
|
||
NO_ERROR - The operation completed successfully.
|
||
|
||
ERROR_NOT_ENOUGH_MEMORY - If the local alloc failed.
|
||
|
||
Or any unexpected errors from the NtOpenDirectoryObject or
|
||
NtQueryDirectoryObject.
|
||
|
||
--*/
|
||
{
|
||
|
||
NTSTATUS ntStatus;
|
||
DWORD status;
|
||
HANDLE DirectoryHandle;
|
||
OBJECT_ATTRIBUTES Obja;
|
||
ULONG Context;
|
||
ULONG ReturnLength;
|
||
BOOLEAN restartScan;
|
||
UNICODE_STRING ObjectPathString;
|
||
UNICODE_STRING ObjectNameString;
|
||
|
||
LPWSTR pObjectPath;
|
||
LPWSTR pDeviceName;
|
||
BOOL found = FALSE;
|
||
|
||
|
||
SC_LOG1(TRACE,"In ScGetDriverStatus for "FORMAT_LPWSTR" Driver\n",
|
||
ServiceRecord->ServiceName);
|
||
|
||
SC_ASSERT(ScServiceRecordLock.HaveExclusive());
|
||
SC_ASSERT(ServiceRecord->ServiceStatus.dwServiceType & SERVICE_DRIVER);
|
||
|
||
//
|
||
// If the ObjectName does not exist yet, create one.
|
||
//
|
||
if (ServiceRecord->ObjectName == NULL) {
|
||
status = ScGetObjectName(ServiceRecord);
|
||
if (status != NO_ERROR) {
|
||
return(status);
|
||
}
|
||
}
|
||
|
||
|
||
//
|
||
// Take the ObjectPathName apart such that the path is in one
|
||
// string, and the device name is in another string.
|
||
|
||
//
|
||
// First copy the Object Path string into a new buffer.
|
||
//
|
||
pObjectPath = (LPWSTR)LocalAlloc(
|
||
LMEM_FIXED,
|
||
WCSSIZE(ServiceRecord->ObjectName));
|
||
|
||
if(pObjectPath == NULL) {
|
||
return(ERROR_NOT_ENOUGH_MEMORY);
|
||
}
|
||
|
||
wcscpy(pObjectPath, ServiceRecord->ObjectName);
|
||
|
||
//
|
||
// Find the last occurrence of '\'. The Device name follows that.
|
||
// replace the '\' with a NULL terminator. Now we have two strings.
|
||
//
|
||
pDeviceName = wcsrchr(pObjectPath, L'\\');
|
||
if (pDeviceName == NULL) {
|
||
SC_LOG0(ERROR,"ScGetDriverStatus: DeviceName not in object path name\n");
|
||
LocalFree(pObjectPath);
|
||
return(ERROR_PATH_NOT_FOUND);
|
||
}
|
||
|
||
*pDeviceName = L'\0';
|
||
pDeviceName++;
|
||
|
||
|
||
//
|
||
// Open the directory object by name
|
||
//
|
||
|
||
RtlInitUnicodeString(&ObjectPathString,pObjectPath);
|
||
|
||
InitializeObjectAttributes(&Obja,&ObjectPathString,0,NULL,NULL);
|
||
|
||
ntStatus = NtOpenDirectoryObject (
|
||
&DirectoryHandle,
|
||
DIRECTORY_TRAVERSE | DIRECTORY_QUERY,
|
||
&Obja);
|
||
|
||
if (!NT_SUCCESS(ntStatus)) {
|
||
LocalFree(pObjectPath);
|
||
if (ntStatus == STATUS_OBJECT_PATH_NOT_FOUND) {
|
||
//
|
||
// If a driver uses a non-standard object path, the path may
|
||
// not exist if the driver is not running. We want to treat
|
||
// this as if the driver is not running.
|
||
//
|
||
goto CleanExit;
|
||
}
|
||
SC_LOG1(ERROR,"ScGetDriverStatus: NtOpenDirectoryObject failed 0x%lx\n",
|
||
ntStatus);
|
||
return(RtlNtStatusToDosError(ntStatus));
|
||
}
|
||
|
||
RtlInitUnicodeString(&ObjectNameString,pDeviceName);
|
||
|
||
restartScan = TRUE;
|
||
do {
|
||
BYTE Buffer[OBJ_DIR_INFO_SIZE];
|
||
POBJECT_DIRECTORY_INFORMATION pObjInfo;
|
||
|
||
//
|
||
// Query the Directory Object to enumerate all object names
|
||
// in that object directory.
|
||
//
|
||
ntStatus = NtQueryDirectoryObject (
|
||
DirectoryHandle,
|
||
Buffer,
|
||
OBJ_DIR_INFO_SIZE,
|
||
FALSE,
|
||
restartScan,
|
||
&Context,
|
||
&ReturnLength);
|
||
|
||
if (!NT_SUCCESS(ntStatus)) {
|
||
SC_LOG1(ERROR,"ScGetDriverStatus:NtQueryDirectoryObject Failed 0x%lx\n",
|
||
ntStatus);
|
||
|
||
LocalFree(pObjectPath);
|
||
NtClose(DirectoryHandle);
|
||
return(RtlNtStatusToDosError(ntStatus));
|
||
}
|
||
|
||
//
|
||
// Now check to see if the device name that we are interested in is
|
||
// in the enumerated data.
|
||
//
|
||
|
||
for (pObjInfo = (POBJECT_DIRECTORY_INFORMATION) Buffer;
|
||
pObjInfo->Name.Length != 0;
|
||
pObjInfo++) {
|
||
|
||
if (RtlCompareUnicodeString( &(pObjInfo->Name), &ObjectNameString, TRUE) == 0) {
|
||
found = TRUE;
|
||
break;
|
||
}
|
||
}
|
||
restartScan = FALSE;
|
||
} while ((ntStatus == STATUS_MORE_ENTRIES) && (found == FALSE));
|
||
|
||
NtClose(DirectoryHandle);
|
||
LocalFree(pObjectPath);
|
||
|
||
CleanExit:
|
||
|
||
if (found) {
|
||
|
||
DWORD PreviousState;
|
||
|
||
|
||
PreviousState = ServiceRecord->ServiceStatus.dwCurrentState;
|
||
|
||
if (PreviousState != SERVICE_STOP_PENDING) {
|
||
|
||
//
|
||
// The driver IS running.
|
||
//
|
||
|
||
ServiceRecord->ServiceStatus.dwCurrentState = SERVICE_RUNNING;
|
||
|
||
if (PreviousState == SERVICE_STOPPED) {
|
||
//
|
||
// It used to be stopped but now it is running.
|
||
//
|
||
ServiceRecord->ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
|
||
ServiceRecord->ServiceStatus.dwWin32ExitCode = NO_ERROR;
|
||
ServiceRecord->ServiceStatus.dwServiceSpecificExitCode = 0;
|
||
ServiceRecord->ServiceStatus.dwCheckPoint = 0;
|
||
ServiceRecord->ServiceStatus.dwWaitHint = 0;
|
||
ServiceRecord->UseCount++;
|
||
SC_LOG2(USECOUNT, "ScGetDriverStatus: " FORMAT_LPWSTR
|
||
" increment USECOUNT=%lu\n", ServiceRecord->ServiceName, ServiceRecord->UseCount);
|
||
}
|
||
|
||
if (ServiceRecord->ServiceStatus.dwWin32ExitCode ==
|
||
ERROR_SERVICE_NEVER_STARTED) {
|
||
ServiceRecord->ServiceStatus.dwWin32ExitCode = NO_ERROR;
|
||
}
|
||
|
||
SC_LOG1(TRACE,"ScGetDriverStatus: "FORMAT_LPWSTR" Driver is "
|
||
"RUNNING\n", ServiceRecord->ServiceName);
|
||
}
|
||
}
|
||
else {
|
||
|
||
//
|
||
// The driver is NOT running.
|
||
//
|
||
|
||
SC_LOG1(TRACE,"ScGetDriverStatus: "FORMAT_LPWSTR" Driver is "
|
||
"NOT RUNNING\n", ServiceRecord->ServiceName);
|
||
|
||
switch(ServiceRecord->ServiceStatus.dwCurrentState) {
|
||
case SERVICE_STOP_PENDING:
|
||
//
|
||
// If the old state was STOP_PENDING, then we can consider
|
||
// it stopped.
|
||
//
|
||
LocalFree(ServiceRecord->ObjectName);
|
||
|
||
ServiceRecord->ServiceStatus.dwCurrentState = SERVICE_STOPPED;
|
||
ServiceRecord->ServiceStatus.dwControlsAccepted = 0;
|
||
ServiceRecord->ServiceStatus.dwCheckPoint = 0;
|
||
ServiceRecord->ServiceStatus.dwWaitHint = 0;
|
||
ServiceRecord->ServiceStatus.dwWin32ExitCode = NO_ERROR;
|
||
ServiceRecord->ObjectName = NULL;
|
||
|
||
//
|
||
// Since the service is no longer running, we need to decrement
|
||
// the use count. If the count is decremented to zero, and
|
||
// the service is marked for deletion, it will get deleted.
|
||
//
|
||
ScDecrementUseCountAndDelete(ServiceRecord);
|
||
|
||
break;
|
||
|
||
case SERVICE_STOPPED:
|
||
//
|
||
// We are not likely to query this driver's status again soon,
|
||
// so free its object name.
|
||
//
|
||
LocalFree(ServiceRecord->ObjectName);
|
||
ServiceRecord->ObjectName = NULL;
|
||
break;
|
||
|
||
default:
|
||
//
|
||
// The driver stopped without being requested to do so.
|
||
//
|
||
LocalFree(ServiceRecord->ObjectName);
|
||
|
||
ServiceRecord->ServiceStatus.dwCurrentState = SERVICE_STOPPED;
|
||
ServiceRecord->ServiceStatus.dwControlsAccepted = 0;
|
||
ServiceRecord->ServiceStatus.dwCheckPoint = 0;
|
||
ServiceRecord->ServiceStatus.dwWaitHint = 0;
|
||
ServiceRecord->ServiceStatus.dwWin32ExitCode = ERROR_GEN_FAILURE;
|
||
ServiceRecord->ObjectName = NULL;
|
||
|
||
//
|
||
// Since the service is no longer running, we need to decrement
|
||
// the use count. If the count is decremented to zero, and
|
||
// the service is marked for deletion, it will get deleted.
|
||
//
|
||
ScDecrementUseCountAndDelete(ServiceRecord);
|
||
|
||
break;
|
||
}
|
||
|
||
}
|
||
|
||
if (ARGUMENT_PRESENT(lpServiceStatus)) {
|
||
RtlCopyMemory(
|
||
lpServiceStatus,
|
||
&(ServiceRecord->ServiceStatus),
|
||
sizeof(SERVICE_STATUS));
|
||
}
|
||
|
||
return(NO_ERROR);
|
||
|
||
}
|
||
|
||
DWORD
|
||
ScGetObjectName(
|
||
LPSERVICE_RECORD ServiceRecord
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function gets a directory object path name for a driver by looking
|
||
it up in the registry, or, if it isn't specified in the registry, by
|
||
computing it from the driver name and type. It allocates storage
|
||
for this name, and passes back the pointer to it. The Pointer to
|
||
the object name string is stored in the ServiceRecord->ObjectName
|
||
location.
|
||
|
||
WARNING: This function expects the EXCLUSIVE database lock to be held.
|
||
|
||
|
||
Arguments:
|
||
|
||
ServiceRecord - This is a pointer to the ServiceRecord for the Driver.
|
||
|
||
Return Value:
|
||
|
||
NO_ERROR - The operation was successful.
|
||
|
||
ERROR_NOT_ENOUGH_MEMORY - If there wasn't enough memory available for
|
||
the ObjectName.
|
||
|
||
or any error from ScOpenServiceConfigKey.
|
||
|
||
--*/
|
||
{
|
||
DWORD status;
|
||
DWORD bufferSize;
|
||
LPWSTR objectNamePath;
|
||
HKEY serviceKey;
|
||
LPWSTR pObjectName;
|
||
|
||
SC_ASSERT(ScServiceRecordLock.HaveExclusive());
|
||
|
||
if (ServiceRecord->ObjectName != NULL) {
|
||
//
|
||
// Some other thread beat us to it
|
||
//
|
||
return(NO_ERROR);
|
||
}
|
||
|
||
//
|
||
// Open the Registry Key for this driver name.
|
||
//
|
||
status = ScOpenServiceConfigKey(
|
||
ServiceRecord->ServiceName,
|
||
KEY_READ,
|
||
FALSE,
|
||
&serviceKey);
|
||
|
||
if (status != NO_ERROR) {
|
||
SC_LOG1(ERROR,"ScGetObjectName: ScOpenServiceConfigKey Failed %d\n",
|
||
status);
|
||
return(status);
|
||
}
|
||
|
||
//
|
||
// Get the NT Object Name from the registry.
|
||
//
|
||
status = ScReadStartName(
|
||
serviceKey,
|
||
&pObjectName);
|
||
|
||
|
||
ScRegCloseKey(serviceKey);
|
||
|
||
//
|
||
// Make sure we read a value with length greater than 0
|
||
//
|
||
if (status == NO_ERROR && pObjectName != NULL)
|
||
{
|
||
if (*pObjectName != '\0') {
|
||
ServiceRecord->ObjectName = pObjectName;
|
||
return(NO_ERROR);
|
||
}
|
||
else {
|
||
LocalFree(pObjectName);
|
||
}
|
||
}
|
||
|
||
//
|
||
// There must not be a name in the ObjectName value field.
|
||
// In this case, we must build the name from the type info and
|
||
// the ServiceName. Names will take the following form:
|
||
// "\\FileSystem\\Rdr" example of a file system driver
|
||
// "\\Driver\\Parallel" example of a kernel driver
|
||
//
|
||
//
|
||
SC_LOG1(TRACE,"ScGetObjectName: ScReadStartName Failed(%d). Build the"
|
||
"name instead\n",status);
|
||
|
||
if (ServiceRecord->ServiceStatus.dwServiceType == SERVICE_FILE_SYSTEM_DRIVER) {
|
||
|
||
bufferSize = sizeof(FILE_SYSTEM_OBJ_NAME);
|
||
objectNamePath = FILE_SYSTEM_OBJ_NAME;
|
||
}
|
||
else {
|
||
|
||
bufferSize = sizeof(DRIVER_OBJ_NAME);
|
||
objectNamePath = DRIVER_OBJ_NAME;
|
||
|
||
}
|
||
|
||
bufferSize += (DWORD) WCSSIZE(ServiceRecord->ServiceName);
|
||
|
||
pObjectName = (LPWSTR)LocalAlloc(LMEM_FIXED, (UINT) bufferSize);
|
||
if (pObjectName == NULL) {
|
||
SC_LOG0(ERROR,"ScGetObjectName: LocalAlloc Failed\n");
|
||
return(ERROR_NOT_ENOUGH_MEMORY);
|
||
}
|
||
|
||
wcscpy(pObjectName, objectNamePath);
|
||
wcscat(pObjectName, ServiceRecord->ServiceName);
|
||
|
||
ServiceRecord->ObjectName = pObjectName;
|
||
|
||
return(NO_ERROR);
|
||
|
||
}
|
||
|
||
DWORD
|
||
ScUnloadDriver(
|
||
LPSERVICE_RECORD ServiceRecord
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function attempts to unload the driver whose service record
|
||
is passed in.
|
||
|
||
NOTE: Make sure the ServiceRecord is for a driver and not a service
|
||
before calling this routine.
|
||
|
||
|
||
Arguments:
|
||
|
||
ServiceRecord - This is a pointer to the service record for a driver.
|
||
This routine assumes that the service record is for a driver and
|
||
not a service.
|
||
|
||
Return Value:
|
||
|
||
NO_ERROR - if successful.
|
||
|
||
ERROR_INVALID_SERVICE_CONTROL - This is returned if the driver is
|
||
not unloadable.
|
||
|
||
otherwise, an error code is returned.
|
||
|
||
Note:
|
||
|
||
|
||
--*/
|
||
{
|
||
NTSTATUS ntStatus = STATUS_SUCCESS;
|
||
DWORD status;
|
||
LPWSTR regKeyPath;
|
||
UNICODE_STRING regKeyPathString;
|
||
ULONG privileges[1];
|
||
|
||
//
|
||
// Create the Registry Key Path for this driver name.
|
||
//
|
||
regKeyPath = (LPWSTR)LocalAlloc(
|
||
LMEM_FIXED,
|
||
sizeof(SERVICE_PATH) +
|
||
WCSSIZE(ServiceRecord->ServiceName));
|
||
|
||
if (regKeyPath == NULL) {
|
||
status = ERROR_NOT_ENOUGH_MEMORY;
|
||
return(status);
|
||
}
|
||
wcscpy(regKeyPath, SERVICE_PATH);
|
||
wcscat(regKeyPath, ServiceRecord->ServiceName);
|
||
|
||
|
||
//
|
||
// Unload the Driver
|
||
// (but first get SeLoadDriverPrivilege)
|
||
//
|
||
|
||
RtlInitUnicodeString(®KeyPathString, regKeyPath);
|
||
|
||
privileges[0] = SE_LOAD_DRIVER_PRIVILEGE;
|
||
status = ScGetPrivilege(1,privileges);
|
||
if (status != NO_ERROR) {
|
||
LocalFree(regKeyPath);
|
||
return(status);
|
||
}
|
||
|
||
ntStatus = NtUnloadDriver (®KeyPathString);
|
||
|
||
(VOID)ScReleasePrivilege();
|
||
|
||
LocalFree(regKeyPath);
|
||
|
||
if (!NT_SUCCESS(ntStatus)) {
|
||
|
||
if (ntStatus == STATUS_INVALID_DEVICE_REQUEST) {
|
||
|
||
status = ERROR_INVALID_SERVICE_CONTROL;
|
||
return(status);
|
||
}
|
||
|
||
SC_LOG1(ERROR,"ScControlDriver: NtUnloadDriver Failed 0x%lx\n",ntStatus);
|
||
|
||
status = RtlNtStatusToDosError(ntStatus);
|
||
return(status);
|
||
}
|
||
|
||
SC_LOG1(TRACE,"ScLoadDeviceDriver: NtUnloadDriver Success for "
|
||
""FORMAT_LPWSTR "\n",ServiceRecord->ServiceName);
|
||
|
||
return(NO_ERROR);
|
||
|
||
}
|
||
|
||
|
||
BOOL
|
||
ScIsPnPDriver(
|
||
IN LPSERVICE_RECORD Service
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function checks whether a specified driver is a PnP driver
|
||
|
||
Arguments:
|
||
|
||
Service - Specifies the driver of interest.
|
||
|
||
Return Value:
|
||
|
||
TRUE - if the driver is a PnP driver or if this cannot be determined.
|
||
|
||
FALSE - if the service is not a PnP driver.
|
||
|
||
--*/
|
||
{
|
||
CONFIGRET Status;
|
||
BOOL fRetStatus = TRUE;
|
||
WCHAR * pBuffer;
|
||
ULONG cchLen, cchTransferLen, ulRegDataType;
|
||
WCHAR szClassGuid[MAX_GUID_STRING_LEN];
|
||
|
||
//
|
||
// Allocate a buffer for the list of device instances associated with
|
||
// this service
|
||
//
|
||
Status = PNP_GetDeviceListSize(
|
||
NULL, // hBinding
|
||
Service->ServiceName, // pszFilter
|
||
&cchLen, // list length in wchars
|
||
CM_GETIDLIST_FILTER_SERVICE); // filter is a service name
|
||
|
||
if (Status != CR_SUCCESS)
|
||
{
|
||
SC_LOG2(WARNING, "PNP_GetDeviceListSize failed %#lx for service %ws\n",
|
||
Status, Service->ServiceName);
|
||
return TRUE;
|
||
}
|
||
|
||
pBuffer = (WCHAR *) LocalAlloc(0, cchLen * sizeof(WCHAR));
|
||
if (pBuffer == NULL)
|
||
{
|
||
SC_LOG(ERROR, "Couldn't allocate buffer for device list, error %lu\n",
|
||
GetLastError());
|
||
return TRUE;
|
||
}
|
||
|
||
//
|
||
// Initialize parameters for PNP_GetDeviceList, the same way as is
|
||
// normally done in the client side of the API
|
||
//
|
||
pBuffer[0] = L'\0';
|
||
|
||
//
|
||
// Get the list of device instances that are associated with this service
|
||
//
|
||
// (For legacy and PNP-aware services, we could get an empty device list.)
|
||
//
|
||
Status = PNP_GetDeviceList(
|
||
NULL, // binding handle
|
||
Service->ServiceName, // pszFilter
|
||
pBuffer, // buffer for device list
|
||
&cchLen, // buffer length in wchars
|
||
CM_GETIDLIST_FILTER_SERVICE | // filter is a service name
|
||
CM_GETIDLIST_DONOTGENERATE // do not generate an instance if none exists
|
||
);
|
||
|
||
if (Status != CR_SUCCESS)
|
||
{
|
||
SC_LOG2(ERROR, "PNP_GetDeviceList failed %#lx for service %ws\n",
|
||
Status, Service->ServiceName);
|
||
LocalFree(pBuffer);
|
||
return TRUE;
|
||
}
|
||
|
||
//
|
||
// If there are no devnodes, this is not a PnP driver
|
||
//
|
||
if (pBuffer[0] == L'\0' || cchLen == 0)
|
||
{
|
||
SC_LOG1(TRACE, "ScIsPnPDriver: %ws is not a PnP driver (no devnodes)\n",
|
||
Service->ServiceName);
|
||
LocalFree(pBuffer);
|
||
return FALSE;
|
||
}
|
||
|
||
//
|
||
// If there's more than one devnode, this is a PnP driver
|
||
//
|
||
if (*(pBuffer + wcslen(pBuffer) + 1) != L'\0')
|
||
{
|
||
SC_LOG1(TRACE, "ScIsPnPDriver: %ws is a PnP driver (more than 1 devnode)\n",
|
||
Service->ServiceName);
|
||
LocalFree(pBuffer);
|
||
return TRUE;
|
||
}
|
||
|
||
//
|
||
// Get the class GUID of this driver
|
||
//
|
||
cchLen = cchTransferLen = sizeof(szClassGuid);
|
||
|
||
Status = PNP_GetDeviceRegProp(
|
||
NULL, // binding handle
|
||
pBuffer, // device instance
|
||
CM_DRP_CLASSGUID, // property to get
|
||
&ulRegDataType, // pointer to REG_* type
|
||
(LPBYTE) szClassGuid, // buffer for property
|
||
&cchTransferLen, // transfer length
|
||
&cchLen, // buffer length in bytes
|
||
0 // flags
|
||
);
|
||
|
||
if (Status != CR_SUCCESS)
|
||
{
|
||
SC_ASSERT(Status != CR_BUFFER_SMALL);
|
||
|
||
SC_LOG2(ERROR, "PNP_GetDeviceRegProp failed %#lx for service %ws\n",
|
||
Status, Service->ServiceName);
|
||
LocalFree(pBuffer);
|
||
return TRUE;
|
||
}
|
||
|
||
//
|
||
// If the single devnode's class is LegacyDriver,
|
||
// this is not a PnP driver
|
||
//
|
||
fRetStatus = (_wcsicmp(szClassGuid, LEGACYDRIVER_STRING) != 0);
|
||
|
||
SC_LOG2(TRACE, "ScIsPnPDriver: %ws %ws a PnP driver\n",
|
||
Service->ServiceName, fRetStatus ? L"is" : L"is not");
|
||
|
||
LocalFree(pBuffer);
|
||
return fRetStatus;
|
||
}
|