/*++

Copyright (C) 1997-1999  Microsoft Corporation

Module Name:

    cnode.cpp

Abstract:

    This module implements CDevice, CClass, CResource and CComputer classes.

Author:

    William Hsieh (williamh) created

Revision History:


--*/
#include "devmgr.h"
#include "cdriver.h"
#include "hwprof.h"
#include "sysinfo.h"
#include <initguid.h>
#include <mountmgr.h>
#include <devguid.h>
#include <wdmguid.h>


//
// CClass implementation
//
CClass::CClass(
    CMachine* pMachine,
    GUID* pGuid
    )
{
    m_Guid = *pGuid;
    ASSERT(pMachine);
    ASSERT(pGuid);

    HKEY hKey = NULL;

    m_NoDisplay = FALSE;
    m_pMachine = pMachine;
    m_TotalDevices = 0;
    m_TotalHiddenDevices = 0;
    m_pDevInfoList = NULL;
    m_pos = NULL;

    if (!m_pMachine->DiGetClassFriendlyNameString(pGuid, m_strDisplayName)) 
    {
        m_strDisplayName.LoadString(g_hInstance, IDS_UNKNOWN);
    }
    
    m_pMachine->DiGetClassImageIndex(pGuid, &m_iImage);

    hKey = m_pMachine->DiOpenClassRegKey(pGuid, KEY_READ, DIOCR_INSTALLER);

    if (INVALID_HANDLE_VALUE != hKey) 
    {
        if (ERROR_SUCCESS == RegQueryValueEx(hKey, REGSTR_VAL_NODISPLAYCLASS, NULL, NULL, NULL, NULL)) 
        {
            m_NoDisplay = TRUE;
        }
        
        RegCloseKey(hKey);
    }
}

CDevInfoList*
CClass::GetDevInfoList(
    HWND hwndParent
    )
{
    if (!m_pDevInfoList) 
    {
        HDEVINFO hDevInfo = m_pMachine->DiCreateDeviceInfoList(&m_Guid, hwndParent);

        if (hDevInfo && INVALID_HANDLE_VALUE != hDevInfo) 
        {
            m_pDevInfoList = new CDevInfoList(hDevInfo, hwndParent);
        }
    }
    
    return m_pDevInfoList;
}

inline
CItemIdentifier*
CClass::CreateIdentifier()
{
    return new CClassIdentifier(*this);
}

CClass::~CClass()
{
    m_listDevice.RemoveAll();

    if (m_pDevInfoList)
    {
        delete m_pDevInfoList;
    }
}

HICON
CClass::LoadIcon()
{
    HICON hClassIcon;

    if (!m_pMachine->DiLoadClassIcon(&m_Guid, &hClassIcon, NULL))
    {
        return NULL;
    }
    
    return hClassIcon;
}

void
CClass::AddDevice(CDevice* pDevice)
{
    ASSERT(pDevice);

    m_listDevice.AddTail(pDevice);
    m_TotalDevices++;

    // Every device under a NoDisplay class is a hidden device
    if (m_NoDisplay || pDevice->IsHidden()) {
    
        m_TotalHiddenDevices++;
    }
}

BOOL
CClass::GetFirstDevice(
    CDevice** ppDevice,
    PVOID&    Context
    )
{
    ASSERT(ppDevice);

    if (!m_listDevice.IsEmpty()) 
    {
        POSITION pos;

        pos = m_listDevice.GetHeadPosition();
        *ppDevice = m_listDevice.GetNext(pos);
        Context = pos;

        return TRUE;
    }
    
    *ppDevice = NULL;

    return FALSE;
}

BOOL
CClass::GetNextDevice(
    CDevice** ppDevice,
    PVOID&    Context
    )
{
    ASSERT(ppDevice);

    POSITION pos = (POSITION)(Context);

    if(NULL != pos) 
    {
        *ppDevice = m_listDevice.GetNext(pos);
        Context = pos;
        return TRUE;
    }
    
    *ppDevice = NULL;

    return FALSE;
}

void
CClass::PropertyChanged()
{
    HKEY hKey = NULL;

    if (!m_pMachine->DiGetClassFriendlyNameString(&m_Guid, m_strDisplayName))
    {
        m_strDisplayName.LoadString(g_hInstance, IDS_UNKNOWN);
    }
    
    m_pMachine->DiGetClassImageIndex(&m_Guid, &m_iImage);

    if (m_pDevInfoList) 
    {
        delete m_pDevInfoList;
        m_pDevInfoList = NULL;
    }
    
    hKey = m_pMachine->DiOpenClassRegKey(&m_Guid, KEY_READ, DIOCR_INSTALLER);

    if (INVALID_HANDLE_VALUE != hKey) 
    {
        if (ERROR_SUCCESS == RegQueryValueEx(hKey, REGSTR_VAL_NODISPLAYCLASS, NULL, NULL, NULL, NULL))
        {
            m_NoDisplay = TRUE;
        }
        
        RegCloseKey(hKey);
    }
}

// CDevice implementation
//
CDevice::CDevice(
    CMachine* pMachine,
    CClass* pClass,
    PSP_DEVINFO_DATA pDevData
    )
{
    ASSERT(pMachine && pDevData && pClass);
    
    m_DevData = *pDevData;
    m_pClass = pClass;
    m_pMachine = pMachine;
    m_pSibling = NULL;
    m_pParent = NULL;
    m_pChild = NULL;

    if (!m_pMachine->CmGetDescriptionString(m_DevData.DevInst, m_strDisplayName))
    {
        m_strDisplayName.LoadString(g_hInstance, IDS_UNKNOWN_DEVICE);
    }
    
    m_pMachine->CmGetDeviceIDString(m_DevData.DevInst, m_strDeviceID);
    m_iImage = m_pClass->GetImageIndex();
}

inline
CItemIdentifier*
CDevice::CreateIdentifier()
{
    return new CDeviceIdentifier(*this);
}

void
CDevice::PropertyChanged()
{
    if (!m_pMachine->CmGetDescriptionString(m_DevData.DevInst, m_strDisplayName))
    {
        m_strDisplayName.LoadString(g_hInstance, IDS_UNKNOWN_DEVICE);
    }
    
    m_pMachine->CmGetDeviceIDString(m_DevData.DevInst, m_strDeviceID);
    m_iImage = m_pClass->GetImageIndex();
}

HICON
CDevice::LoadClassIcon()
{
    HICON hClassIcon;
    hClassIcon = NULL;

    if (m_pMachine->DiLoadClassIcon(&m_DevData.ClassGuid, &hClassIcon, NULL))
    {
        return hClassIcon;
    }

    return NULL;
}

BOOL
CDevice::GetStatus(
    DWORD* pStatus,
    DWORD* pProblem
    )
{
    return  m_pMachine->CmGetStatus(m_DevData.DevInst, pProblem, pStatus);
}

BOOL
CDevice::GetCapabilities(
    DWORD* pCapabilities
    )
{
    return m_pMachine->CmGetCapabilities(m_DevData.DevInst, pCapabilities);
}

BOOL
CDevice::GetPowerCapabilities(
    DWORD* pCapabilities
    )
{
    CM_POWER_DATA CmPowerData;
    ULONG Size;

    Size = sizeof(CmPowerData);
    if (m_pMachine->CmGetRegistryProperty(m_DevData.DevInst,
                                          CM_DRP_DEVICE_POWER_DATA,
                                          &CmPowerData,
                                          &Size
                                          ) == CR_SUCCESS) {

        *pCapabilities = CmPowerData.PD_Capabilities;
        return TRUE;
    }

    *pCapabilities = 0;
    return FALSE;
}

BOOL
CDevice::IsRAW()
{
    DWORD Capabilities;
    
    return (m_pMachine->CmGetCapabilities(m_DevData.DevInst, &Capabilities) &&
            (CM_DEVCAP_RAWDEVICEOK & Capabilities));
}

BOOL
CDevice::IsHidden()
{
    CClass *pClass = GetClass();

    //
    // A device is hidden if one of the following are TRUE:
    //
    // - It's class is a NoDisplayClass
    // - It has the DN_NO_SHOW_IN_DM Status flag set
    // - It is a Phantom devnode
    //
    return (NoShowInDM() || IsPhantom() || pClass->NoDisplay());
}

BOOL
CDevice::IsPhantom()
{
    DWORD Status, Problem;

    return !m_pMachine->CmGetStatus(m_DevData.DevInst, &Problem, &Status) &&
            (CR_NO_SUCH_VALUE == m_pMachine->GetLastCR() ||
            CR_NO_SUCH_DEVINST == m_pMachine->GetLastCR());
}

BOOL
CDevice::NoShowInDM()
{
    DWORD Status, Problem;
    Status = 0;
    GetStatus(&Status, &Problem);
    return (Status & DN_NO_SHOW_IN_DM);
}

BOOL
CDevice::IsUninstallable(
    )
/*++

    This function determins whether a device can be uninstalled. A device
    cannot be uninstalled if it is a ROOT device and it does not have
    the DN_DISABLEABLE DevNode status bit set.
    
Return Value:
    TRUE if the device can be uninstalled.
    FALSE if the device cannot be uninstalled.    
    
--*/
{
    DWORD Status, Problem;

    if (GetStatus(&Status, &Problem) &&
        !(Status & DN_DISABLEABLE) &&
         (Status & DN_ROOT_ENUMERATED)) {

        return FALSE;
    }
    
    return TRUE;
}

BOOL
CDevice::IsDisableable(
    )
/*++

    This function determins whether a device can be disabled or not by 
    checking the DN_DISABLEABLE DevNode status bit.
    
    A device that is currently Hardware Disabled cannot be software disabled.
    
Return Value:
    TRUE if the device can be disabled.
    FALSE if the device cannot be disabled.    

--*/
{
    DWORD Status, Problem;

    if (GetStatus(&Status, &Problem) &&
        (Status & DN_DISABLEABLE) &&
        (CM_PROB_HARDWARE_DISABLED != Problem)) {

        return TRUE;
    }

    return FALSE;
}

BOOL
CDevice::IsDisabled(
    )
/*++

    A device is disabled if it has the problem CM_PROB_DISABLED.  
    
Return Value:    
    TRUE if device is disabled.
    FALSE if device is NOT disabled.
    
--*/
{
    DWORD Status, Problem;

    if (GetStatus(&Status, &Problem)) 
    {
        return ((Status & DN_HAS_PROBLEM) && (CM_PROB_DISABLED == Problem));
    }

    return FALSE;
}

BOOL
CDevice::IsStateDisabled(
    )
/*++

    A device state is disabled if it has the CONFIGFLAG_DISABLED ConfigFlag
    set or the CSCONFIGFLAG_DISABLED Config Specific ConfigFlag disabled in 
    the current profile.
    
    Note that a device disabled State has nothing to do with whether the device
    is currently physically disabled or not.  The disabled state is just a registry
    flag that tells Plug and Play what to do with the device the next time it is 
    started.
    
Return Value:    
    TRUE if device's state is disabled.
    FALSE if device's state is NOT disabled.
    
--*/
{
    ULONG hwpfCurrent;
    DWORD Flags;

    //
    // Check if the device state is globally disabled by checking it's ConfigFlags
    //
    GetConfigFlags(&Flags);
    if (Flags & CONFIGFLAG_DISABLED) {
        return TRUE;
    }

    //
    // Check if the device state is disabled in the current hardware profile by 
    // checking it's Config Specific ConfigFlags.
    //
    if (m_pMachine->CmGetCurrentHwProfile(&hwpfCurrent) &&
        m_pMachine->CmGetHwProfileFlags(m_DevData.DevInst, hwpfCurrent, &Flags) &&
        (Flags & CSCONFIGFLAG_DISABLED)) {
        return TRUE;
    }

    return FALSE;
}

BOOL
CDevice::IsStarted()
{
    DWORD Status, Problem;    
    
    //
    // Check to see if the DN_STARTED devnode status flag is set.
    //
    if (GetStatus(&Status, &Problem) &&
        (Status & DN_STARTED))
    {
        return TRUE;
    }
    
    return FALSE;
}

BOOL
CDevice::HasProblem(
    )
/*++

    This function returns whether a device has a problem or not.
    
Return Value:    
    TRUE if device has a problem.
    FALSE if device does not have a problem.
    
--*/
{
    DWORD Status, Problem;    
    
    if (GetStatus(&Status, &Problem))
    {
        //
        // If the DN_HAS_PROBLEM or DN_PRIVATE_PROBLEM status bits are set
        // then this device has a problem, unless the problem is CM_PROB_MOVED.
        //
        if ((Status & DN_PRIVATE_PROBLEM) ||
            ((Status & DN_HAS_PROBLEM) && (Problem != CM_PROB_MOVED)))
        {
            return TRUE;
        }

        //
        // If the device is not started and RAW capable then it also has a problem
        //
        if (!(Status & DN_STARTED) && IsRAW()) 
        {
            return TRUE;
        }
    }

    return FALSE;
}

BOOL
CDevice::NeedsRestart(
    )
/*++

    This function returns whether a device needs a restart or not.  It checks the
    DN_NEED_RESTART Status flag.  
    
Return Value:    
    TRUE if device needs the computer to be restarted for it to work properly.
    FALSE if device does not need the computer to be restarted.
    
--*/
{
    DWORD Status, Problem;

    if (GetStatus(&Status, &Problem)) 
    {
        return (Status & DN_NEED_RESTART);
    }

    return FALSE;
}

CDevice*
CDevice::FindMFParent()
{
    if (!IsMFChild())
    {
        return NULL;
    }
    
    ASSERT(m_pParent);

    CDevice* pDevice = m_pParent;

    while (pDevice->IsMFChild())
    {
        pDevice = pDevice->GetParent();
    }
    
    return pDevice;
}

BOOL
CDevice::IsMFChild(
    )
{
    DWORD Status, Problem;
    Status = 0;
    GetStatus(&Status, &Problem);
    return (Status & DN_MF_CHILD);
}

BOOL
CDevice::IsSpecialMFChild(
    )
{
    if (IsMFChild()) 
    {
        DWORD Flags;

        if (GetConfigFlags(&Flags))
        {
            return (Flags & CONFIGFLAG_CANTSTOPACHILD);
        }
    }
    
    return FALSE;
}

// This function determines if the device is a pnp device
// INPUT:
//  None
// OUTPUT:
//  TRUE if the device is a pnp device
//  FLASE if the device is not a pnp device or undetermined
BOOL
CDevice::IsPnpDevice()
{
    // A device is pnp device if the device id is not led
    // by "root" and DN_ROOT_ENUMERATED is not set
    // in its status.
    if (!m_strDeviceID.IsEmpty()) 
    {
        // unfortunately, we do not have lstrcmpin library function
        TCHAR DeviceId[MAX_DEVICE_ID_LEN + 1];
        int len = lstrlen(REGSTR_KEY_ROOTENUM);

        lstrcpyn(DeviceId, (LPCTSTR)m_strDeviceID, len);
        DeviceId[len] = _T('\0');

        if (lstrcmpi(DeviceId, REGSTR_KEY_ROOTENUM)) 
        {
            // the device id does not start with "root"
            // check the status
            DWORD Problem, Status;

            if (GetStatus(&Status, &Problem))
            {
                return !(Status & DN_ROOT_ENUMERATED);
            }
        }
    }
    
    return FALSE;
}

BOOL
CDevice::IsBiosDevice()
{
    // A device is bios enumerated if its device id
    // is led by "root" and its status does not
    // have DN_ROOT_ENUMERATED
    if (!m_strDeviceID.IsEmpty()) 
    {
        TCHAR DeviceId[MAX_DEVICE_ID_LEN + 1];
        int len = lstrlen(REGSTR_KEY_ROOTENUM);
        
        lstrcpyn(DeviceId, (LPCTSTR)m_strDeviceID, len);
        DeviceId[len] = _T('\0');

        if (!lstrcmpi(DeviceId, REGSTR_KEY_ROOTENUM)) 
        {
            // the device id starts with "root"
            // check the status
            DWORD Problem, Status;

            if (GetStatus(&Status, &Problem)) 
            {
                return !(Status & DN_ROOT_ENUMERATED);
            }
        }
    }
    
    return FALSE;
}

BOOL
CDevice::IsPCMCIA()
{
    ASSERT(m_pClass);

    // if the device's class is PCMCIA, it is a PCMCIA device
    if (IsEqualGUID(*m_pClass, GUID_DEVCLASS_PCMCIA))
    {
        return TRUE;
    }
    
    // if the device has ancestor(s), it is a PCMCIA if one of
    // its ancestor is PCMCIA
    if (m_pParent)
    {
        return m_pParent->IsPCMCIA();
    }
    
    return FALSE;
}

BOOL
CDevice::IsPCIDevice()
{
    GUID BusGuid;
    CONFIGRET ConfigRet;

    if (m_pMachine->CmGetBusGuid(GetDevNode(), &BusGuid) &&
        IsEqualGUID(BusGuid, GUID_BUS_TYPE_PCI)) {

        return TRUE;
    }

    return FALSE;
}

BOOL
CDevice::GetConfigFlags(
    DWORD* pFlags
    )
{
    return m_pMachine->CmGetConfigFlags(m_DevData.DevInst, pFlags);
}

BOOL
CDevice::GetConfigSpecificConfigFlags(
    DWORD* pCSStatus
    )
{
    ULONG hwpfCurrent;

    if (m_pMachine->CmGetCurrentHwProfile(&hwpfCurrent) &&
        m_pMachine->CmGetHwProfileFlags(m_DevData.DevInst, hwpfCurrent, pCSStatus)) {
        
        return TRUE;
    }

    return FALSE;
}

BOOL
CDevice::GetKnownLogConf(LOG_CONF* plc, DWORD* plcType)
{
    return m_pMachine->CmGetKnownLogConf(m_DevData.DevInst, plc, plcType);
}

BOOL
CDevice::HasResources()
{
    return m_pMachine->CmHasResources(m_DevData.DevInst);
}

void
CDevice::GetMFGString(
    String& strMFG
    )
{
    m_pMachine->CmGetMFGString(m_DevData.DevInst, strMFG);

    if (strMFG.IsEmpty()) 
    {
        strMFG.LoadString(g_hInstance, IDS_UNKNOWN);
    }
}

void
CDevice::GetProviderString(
    String& strProvider
    )
{
    m_pMachine->CmGetProviderString(m_DevData.DevInst, strProvider);
    
    if (strProvider.IsEmpty()) {
    
        strProvider.LoadString(g_hInstance, IDS_UNKNOWN);
    }
}

void
CDevice::GetDriverDateString(
    String& strDriverDate
    )
{
    FILETIME ft;

    strDriverDate.Empty();

    //
    // First try to get the driver date FileTime data from the registry,
    // this way we can localize the date.
    //
    if (m_pMachine->CmGetDriverDateData(m_DevData.DevInst, &ft)) {

        SYSTEMTIME SystemTime;
        TCHAR DriverDate[MAX_PATH];

        DriverDate[0] = TEXT('\0');

        if (FileTimeToSystemTime(&ft, &SystemTime)) {

            if (GetDateFormat(LOCALE_USER_DEFAULT,
                          DATE_SHORTDATE,
                          &SystemTime,
                          NULL,
                          DriverDate,
                          sizeof(DriverDate)/sizeof(TCHAR)
                          ) != 0) {

                strDriverDate = DriverDate;
            }
        }

    } else {
    
        //
        // We couldn't get the FileTime data so just get the DriverDate string
        // from the registry.
        //
        m_pMachine->CmGetDriverDateString(m_DevData.DevInst, strDriverDate);
    }

    if (strDriverDate.IsEmpty()) {
    
        strDriverDate.LoadString(g_hInstance, IDS_NOT_AVAILABLE);
    }
}

void
CDevice::GetDriverVersionString(
    String& strDriverVersion
    )
{
    m_pMachine->CmGetDriverVersionString(m_DevData.DevInst, strDriverVersion);

    if (strDriverVersion.IsEmpty()) {
    
        strDriverVersion.LoadString(g_hInstance, IDS_NOT_AVAILABLE);
    }
}

LPCTSTR
CDevice::GetClassDisplayName()
{
    if (m_pClass)
    {
        return m_pClass->GetDisplayName();
    }
    
    else
    {
        return NULL;
    }
}

BOOL
CDevice::NoChangeUsage()
{
    SP_DEVINSTALL_PARAMS dip;
    dip.cbSize = sizeof(dip);

    if (m_pMachine->DiGetDeviceInstallParams(&m_DevData, &dip))
    {
        return (dip.Flags & DI_PROPS_NOCHANGEUSAGE);
    }
    
    else
    {
        return TRUE;
    }
}

CDriver*
CDevice::CreateDriver()
{
    SP_DRVINFO_DATA DrvInfoData;
    PSP_DRVINFO_DATA pDrvInfoData;
    CDriver* pDriver = NULL;

    DrvInfoData.cbSize = sizeof(DrvInfoData);

    // If the device has a selected driver, use it. Otherwise,
    // we pass a NULL drvinfodata so that CDriver will search
    // for the device driver key or service to create
    // a list of driver files.
    if (m_pMachine->DiGetSelectedDriver(&m_DevData, &DrvInfoData))
    {
        pDrvInfoData = &DrvInfoData;
    }
    
    else
    {
        pDrvInfoData = NULL;
    }

    pDriver = new CDriver();

    if (!pDriver) {

        SetLastError(ERROR_NOT_ENOUGH_MEMORY);
        return NULL;
    }

    // if we have a selected driver and we failed to
    // to create the driver, retry it without the selected driver
    //
    if (pDrvInfoData && pDriver->Create(this, pDrvInfoData)) {

        return pDriver;
    }

    pDriver->Create(this, NULL);

    return pDriver;
}

BOOL
CDevice::HasDrivers()
{
    HKEY hKey;
    BOOL HasDriverKey, HasServiceKey;
    CDriver DeviceDriver;
    BOOL Result;

    HasDriverKey = FALSE;

    if (m_pMachine->IsLocal()) 
    {
        // open drvice's driver registry key
        hKey = m_pMachine->DiOpenDevRegKey(&m_DevData, DICS_FLAG_GLOBAL,
                        0, DIREG_DRV, KEY_ALL_ACCESS);
                        
        HasDriverKey = INVALID_HANDLE_VALUE != hKey;

        if (INVALID_HANDLE_VALUE != hKey) 
        {
            RegCloseKey(hKey);
        }
    }
    
    DWORD Size = 0;

    m_pMachine->DiGetDeviceRegistryProperty(&m_DevData, SPDRP_SERVICE,
                        NULL, NULL, 0, &Size);

    HasServiceKey = (0 != Size);

    if (HasServiceKey || HasDriverKey) 
    {
        // either we have a driver or a service key.
        // try to find out if we can find any valid driver files.
        Result = DeviceDriver.Create(this);
    }
    
    else 
    {
        if (m_pMachine->IsLocal() && g_HasLoadDriverNamePrivilege) 
        {
            // If the target machine is local and the user has the
            // administrator privilege, we need the driver page
            // for users to update the drivers.
            //
            Result = TRUE;
        }
        
        else 
        {
            Result = FALSE;
        }
    }
    
    return Result;
}

DWORD
CDevice::EnableDisableDevice(
    HWND hDlg,
    BOOL Enabling
    )
{
    BOOL Disabling = !Enabling;
    BOOL Canceled;
    Canceled = FALSE;
    DWORD RestartFlags = 0;
    DWORD ConfigFlags;
    HCURSOR hCursorOld = NULL;
    BOOL Refresh = FALSE;

    //
    // Disable refreshing the TREE while we are enabling/disabling this device
    //
    m_pMachine->EnableRefresh(FALSE);

    if (!GetConfigFlags(&ConfigFlags)) {
        ConfigFlags = 0;
    }

    //
    // Only want the disabled bit
    //
    ConfigFlags &= CONFIGFLAG_DISABLED;

    CHwProfileList* pHwProfileList = new CHwProfileList();
    
    if (!pHwProfileList) {
        goto clean0;
    }

    pHwProfileList->Create(this, ConfigFlags);

    //
    // Get the current profile
    //
    CHwProfile* phwpf;

    if (!(pHwProfileList->GetCurrentHwProfile(&phwpf))) {
        goto clean0;
    }

    //
    // Can only enable a device that is currently disabled
    //
    if (IsStateDisabled() && Enabling) {
        phwpf->SetEnablePending();
    }

    //
    // Can only disable a device that is currently enabled
    //
    else if (!IsStateDisabled() && Disabling) {
        phwpf->SetDisablePending();
    }

    //
    // If we don't have a valid enable or disable then exit
    //
    if (!(phwpf->IsEnablePending()) && !(phwpf->IsDisablePending())) {
        goto clean0;
    }

    //
    // This device is not a boot device so just display the normal disable
    // warning to the user.
    //
    if (Disabling) {
        int MsgBoxResult;
        TCHAR szText[MAX_PATH];
        DWORD Size;
        
        Size = LoadResourceString(IDS_WARN_NORMAL_DISABLE, szText, ARRAYLEN(szText));
        MsgBoxResult = MessageBox(hDlg, 
                                  szText,
                                  GetDisplayName(),
                                  MB_ICONEXCLAMATION | MB_YESNO | MB_DEFBUTTON2
                                  );

        if (IDYES != MsgBoxResult) {
            goto clean0;
        }
    }

    hCursorOld = SetCursor(LoadCursor(NULL, IDC_WAIT));

    //
    // If this isn't a live devnode then we need to do a manual refresh if we
    // are diabling the device.
    // 
    Refresh = (!Enabling &&
               (IsPhantom() || 
                HasProblem() || 
                !IsStarted()));

    m_pMachine->DiTurnOnDiFlags(*this, DI_NODI_DEFAULTACTION);

    SP_PROPCHANGE_PARAMS pcp;
    pcp.ClassInstallHeader.cbSize = sizeof(SP_CLASSINSTALL_HEADER);
    pcp.ClassInstallHeader.InstallFunction = DIF_PROPERTYCHANGE;

    //
    // Now ask the class installer if the device can be specifically enabled/disabled
    //
    pcp.Scope = DICS_FLAG_CONFIGSPECIFIC;
    pcp.StateChange = DICS_DISABLE;
    
    if (phwpf->IsEnablePending()) {
        pcp.StateChange = DICS_ENABLE;
    }
    
    pcp.HwProfile = phwpf->GetHwProfile();

    m_pMachine->DiSetClassInstallParams(*this,
                                        &pcp.ClassInstallHeader,
                                        sizeof(pcp)
                                        );
            
    m_pMachine->DiCallClassInstaller(DIF_PROPERTYCHANGE, *this);
    Canceled = (ERROR_CANCELLED == GetLastError());
    
    //
    // class installer has not objection of our enabling/disabling,
    // do real enabling/disabling.
    //
    if (!Canceled) {
        if (phwpf->IsDisablePending()) {
            pcp.StateChange = DICS_DISABLE;
            pcp.Scope = DICS_FLAG_CONFIGSPECIFIC;
            pcp.HwProfile = phwpf->GetHwProfile();
            m_pMachine->DiSetClassInstallParams(*this,
                                                &pcp.ClassInstallHeader,
                                                sizeof(pcp)
                                                );
                    
            m_pMachine->DiChangeState(*this);
        }
                    
        else {
            //
            // We are enabling the device,
            // do a specific enabling then a globally enabling.
            // the globally enabling will start the device
            // The implementation here is different from
            // Win9x which does a global enabling, a config
            // specific enabling and then a start.
            //
            pcp.Scope = DICS_FLAG_CONFIGSPECIFIC;
            pcp.HwProfile = phwpf->GetHwProfile();
            m_pMachine->DiSetClassInstallParams(*this,
                                                &pcp.ClassInstallHeader,
                                                sizeof(pcp)
                                                );
                        
            m_pMachine->DiChangeState(*this);

            //
            // This call will start the device is it not started.
            //
            pcp.Scope = DICS_FLAG_GLOBAL;
            m_pMachine->DiSetClassInstallParams(*this,
                                                &pcp.ClassInstallHeader,
                                                sizeof(pcp)
                                                );
                        
            m_pMachine->DiChangeState(*this);
        }
                    
        if (phwpf->IsEnablePending()) {
            phwpf->ResetEnablePending();
        }
        
        else if (phwpf->IsDisablePending()) {
            phwpf->ResetDisablePending();
        }

        //
        // signal that the property of the device is changed.
        //
        m_pMachine->DiTurnOnDiFlags(*this, DI_PROPERTIES_CHANGE);

        //
        // See if we need a restart.
        //
        RestartFlags |= (m_pMachine->DiGetFlags(*this)) & (DI_NEEDRESTART | DI_NEEDREBOOT);

        if (NeedsRestart()) {
            RestartFlags |= DI_NEEDRESTART;
        }
    }

    //
    // Remove class install parameters, this also reset
    // DI_CLASSINATLLPARAMS
    //
    m_pMachine->DiSetClassInstallParams(*this, NULL, 0);

    m_pMachine->DiTurnOffDiFlags(*this, DI_NODI_DEFAULTACTION);

clean0:
    if (pHwProfileList) {
        delete pHwProfileList;
    }

    //
    // Enable the tree for refreshing.
    // We will only schedule a refresh ourselves if the device was not started 
    // before we tried to disable it, and we are not going to prompt for a reboot.
    // In all other cases we should get a WM_DEVICECHANGE which will cause us
    // to refresh our tree.
    //
    if (Refresh && !NeedsRestart()) {
        m_pMachine->ScheduleRefresh();
    }

    m_pMachine->EnableRefresh(TRUE);

    if (hCursorOld != NULL) {
        SetCursor(hCursorOld);
    }

    return RestartFlags;
}

BOOL
CDevice::operator ==(
    CDevice& OtherDevice
    )
{
    return \
        (*m_pClass == *(OtherDevice.GetClass())) &&
        !lstrcmpi(m_strDeviceID, OtherDevice.GetDeviceID()) &&
        !lstrcmpi(m_strDisplayName, OtherDevice.GetDisplayName());
}

//
// CComputer implementation
//

CComputer::CComputer(
    CMachine* pMachine,
    DEVNODE dnRoot
    )
{
    ASSERT(pMachine);
    ASSERT(!GetChild() && !GetParent() && !GetSibling());
    
    m_pMachine = pMachine;
    m_strDisplayName.Empty();
    m_strDisplayName = pMachine->GetMachineDisplayName();
    m_iImage = pMachine->GetComputerIconIndex();
    m_dnRoot = dnRoot;
}

inline
CItemIdentifier*
CComputer::CreateIdentifier()
{
    return new CComputerIdentifier(*this);
}


CResource::CResource(
    CDevice* pDevice,
    RESOURCEID ResType,
    DWORDLONG dlBase,
    DWORDLONG dlLen,
    BOOL Forced,
    BOOL Free
    )
{
    m_pChild = NULL;
    m_pSibling = NULL;
    m_pParent =  NULL;
    m_ResType = ResType;
    m_dlBase = dlBase;
    m_dlLen = dlLen;
    m_Forced = Forced;
    m_dlEnd = m_dlBase + m_dlLen - 1;
    m_Allocated = !Free;
    ASSERT(pDevice);
    m_pDevice = pDevice;
    m_iImage = pDevice->GetImageIndex();
    ASSERT(ResType >= ResType_Mem && ResType <= ResType_IRQ);

    m_strDisplayName.Empty();
    m_strDisplayName = pDevice->GetDisplayName();

    if (ResType_IRQ == m_ResType)
    {
        String strBus;

        strBus.LoadString(g_hInstance, 
                          pDevice->IsPCIDevice() ? IDS_PCI : IDS_ISA
                          );

        m_strViewName.Format(TEXT("%2d    "), m_dlBase);
        m_strViewName = strBus + m_strViewName;
    }
    
    else if (ResType_DMA == m_ResType)
    {
        m_strViewName.Format(TEXT("%2d    " ), m_dlBase);
    }
    
    else 
    {
#ifdef _WIN64
        m_strViewName.Format(TEXT("[%016I64X - %016I64X]  "), m_dlBase, m_dlEnd);
#else
        m_strViewName.Format(TEXT("[%08lX - %08lX]  "), (ULONG)m_dlBase, (ULONG)m_dlEnd);
#endif
    }
    
    if (m_Allocated) 
    {
        m_strViewName += pDevice->GetDisplayName();
    }
}

void
CResource::GetRangeString(
    String& strRange
    )
{
    if (ResType_IRQ == m_ResType ||
        ResType_DMA == m_ResType)
    {
        strRange.Format(TEXT("%2d"), m_dlBase);
    }
    
    else 
    {
        strRange.Format(TEXT("[%08lX - %08lX]"), (ULONG)m_dlBase, (ULONG)m_dlEnd);
    }
}

BOOL
CResource::operator <=(
    const CResource& resSrc
    )
{
    DWORDLONG dlBase, dlLen;

    resSrc.GetValue(&dlBase, &dlLen);

    if (m_dlBase < dlBase)
        return TRUE;
    
    // if this resource contain the given resource,
    // we are smaller!
    if (m_dlBase == dlBase)
        return (m_dlBase + m_dlLen > dlBase + dlLen);
    
    return FALSE;
}

BOOL
CResource::EnclosedBy(
    const CResource& resSrc
    )
{
    DWORDLONG dlBase, dlLen;
    resSrc.GetValue(&dlBase, &dlLen);
    return m_dlBase >= dlBase && m_dlBase + m_dlLen <= dlBase + dlLen;
}


CResourceType::CResourceType(
    CMachine* pMachine,
    RESOURCEID ResType
    )
{
    int iStringID;

    m_ResType = ResType;
    m_pChild = NULL;
    m_pSibling = NULL;
    m_pParent =  NULL;
    m_pMachine = pMachine;
    ASSERT(ResType >= ResType_Mem && ResType <= ResType_IRQ);

    switch (ResType)
    {
    case ResType_IRQ:
        iStringID = IDS_VIEW_RESOURCE_IRQ;
        break;
    
    case ResType_IO:
        iStringID = IDS_VIEW_RESOURCE_IO;
        break;
    
    case ResType_DMA:
        iStringID = IDS_VIEW_RESOURCE_DMA;
        break;
    
    case ResType_Mem:
        iStringID = IDS_VIEW_RESOURCE_MEM;
        break;
    
    default:
        iStringID = IDS_UNKNOWN;
        break;
    }

    m_strDisplayName.Empty();
    m_strDisplayName.LoadString(g_hInstance, iStringID);
    m_iImage = pMachine->GetResourceIconIndex();
}

inline
CItemIdentifier*
CResourceType::CreateIdentifier()
{
    return new CResourceTypeIdentifier(*this);
}


// This function creates CResourceList object to contain the designated
// resources for the given device.
// INPUT:
//      pDevice -- the device
//      ResType  -- what type of resource
//      LogConfType -- what type of logconf
// OUTPUT:
//      NONE.
//
// This function may throw CMemoryException
//
CResourceList::CResourceList(
    CDevice* pDevice,
    RESOURCEID ResType,
    ULONG LogConfType,
    ULONG AltLogConfType
    )
{
    ASSERT(ResType_All != ResType);
    ASSERT(BOOT_LOG_CONF == LogConfType ||
           FORCED_LOG_CONF == LogConfType ||
           ALLOC_LOG_CONF == LogConfType);
    ASSERT(pDevice);

    LOG_CONF lc;
    RES_DES rd, rdPrev;
    rdPrev;
    RESOURCEID ResId;
    BOOL Forced;
    CMachine* pMachine = pDevice->m_pMachine;
    ASSERT(pMachine);

    rdPrev = 0;

    // even though we have a valid logconf, it does not mean
    // GetNextResDes would succeed because the ResType is not
    // ResType_All.
    if (pMachine->CmGetFirstLogConf(pDevice->GetDevNode(), &lc, LogConfType)) 
    {
        if (pMachine->CmGetNextResDes(&rd, lc, ResType, &ResId)) 
        {
            ULONG DataSize;
            DWORDLONG dlBase, dlLen;
            
            do 
            {
                DataSize = pMachine->CmGetResDesDataSize(rd);

                if (DataSize) 
                {
                    BufferPtr<BYTE> DataPtr(DataSize);

                    if (pMachine->CmGetResDesData(rd, DataPtr, DataSize)) 
                    {
                        // need this to use a different image overlay for
                        // forced allocated resource
                        Forced = pMachine->CmGetFirstLogConf(pDevice->GetDevNode(),
                                NULL, FORCED_LOG_CONF);
                        
                        if (ExtractResourceValue(ResType, DataPtr, &dlBase, &dlLen)) 
                        {
                            SafePtr<CResource> ResPtr;
                            CResource* pRes;
    
                            pRes = new CResource(pDevice, ResType, dlBase,
                                                 dlLen, Forced, FALSE);
                                                 
                            if (pRes) 
                            {
                                ResPtr.Attach(pRes);
                                InsertResourceToList(pRes);
                                ResPtr.Detach();
                            }
                        }
                    }
                }
                
                if (rdPrev)
                {
                    pMachine->CmFreeResDesHandle(rdPrev);
                }
                
                rdPrev = rd;

            } while (pMachine->CmGetNextResDes(&rd, rdPrev, ResType, &ResId));
            
            //free the last resource descriptor handle
            pMachine->CmFreeResDesHandle(rd);
        }

        pMachine->CmFreeLogConfHandle(lc);
    }
}



// This function creates CResourceList object to contain the designated
// resources for the given machine.
// INPUT:
//  pMachine -- the machine
//  ResType  -- what type of resource
//  LogConfType -- what type of logconf
// OUTPUT:
//  NONE.
//
// This function may throw CMemoryException
//
CResourceList::CResourceList(
    CMachine* pMachine,
    RESOURCEID ResType,
    ULONG LogConfType,
    ULONG AltLogConfType
    )
{
    ASSERT(ResType_All != ResType);
    ASSERT(BOOT_LOG_CONF == LogConfType ||
           FORCED_LOG_CONF == LogConfType ||
           ALLOC_LOG_CONF == LogConfType ||
           ALL_LOG_CONF == LogConfType);

    ASSERT(pMachine);

    if (pMachine->GetNumberOfDevices()) 
    {
        ASSERT(pMachine->m_pComputer && pMachine->m_pComputer->GetChild());

        CDevice* pFirstDevice;

        pFirstDevice = pMachine->m_pComputer->GetChild();
        CreateSubtreeResourceList(pFirstDevice, ResType, LogConfType, AltLogConfType);
    }
}

//
// This function extracts resource value from the provided buffer
//
// INPUT:
//  ResType     -- resource type the data contain
//  pData       -- the raw data
//  pdlBase     -- buffer to hold the base of the value
//  pdlLen      -- buffer to hold the length of the value
//
// OUTPUT:
//  TRUE if this is a valid resource descriptor or FALSE if we should ignore it.
//  
// NOTE:
//  If the return value is FALSE then pdlBase and pdlLen are not filled in.
//
BOOL
CResourceList::ExtractResourceValue(
    RESOURCEID ResType,
    PVOID pData,
    DWORDLONG* pdlBase,
    DWORDLONG* pdlLen
    )
{
    BOOL bValidResDes = TRUE;

    ASSERT(pData && pdlBase && pdlLen);

    switch (ResType)
    {
    case ResType_Mem:
        if (pMemResData(pData)->MEM_Header.MD_Alloc_Base <= pMemResData(pData)->MEM_Header.MD_Alloc_End) {
        
            *pdlBase = pMemResData(pData)->MEM_Header.MD_Alloc_Base;
            *pdlLen = pMemResData(pData)->MEM_Header.MD_Alloc_End -
                *pdlBase + 1;
        } else {
            //
            // If base > end then ignore this resource descriptor
            //
            *pdlBase = 0;
            *pdlLen = 0;
            bValidResDes = FALSE;
        }
        break;
            
    case ResType_IRQ:
        *pdlBase = pIRQResData(pData)->IRQ_Header.IRQD_Alloc_Num;
        // IRQ len is always 1
        *pdlLen = 1;
        break;
            
    case ResType_DMA:
        *pdlBase = pDMAResData(pData)->DMA_Header.DD_Alloc_Chan;
        // DMA len is always 1
        *pdlLen = 1;
        break;
            
    case ResType_IO:
        if (pIOResData(pData)->IO_Header.IOD_Alloc_Base <= pIOResData(pData)->IO_Header.IOD_Alloc_End) {
        
            *pdlBase = pIOResData(pData)->IO_Header.IOD_Alloc_Base;
            *pdlLen = pIOResData(pData)->IO_Header.IOD_Alloc_End -
                *pdlBase + 1;
        } else {
            //
            // If base > end then ignore this resource descriptor
            //
            *pdlBase = 0;
            *pdlLen = 0;
            bValidResDes = FALSE;
        }
        break;
          
    default:
        ASSERT(FALSE);
        *pdlBase = 0;
        *pdlLen = 0;
        break;
    }

    return bValidResDes;
}


//
//This function creates resources for the given subtree rooted at
//the given device
//
//INPUT:
//  pDevice -- the root device of the subtree
//  ResType -- resource type to be created
//  LogConfType -- logconf type to be created from
//
//OUTPUT:
//  NONE
//
// This function may throw CMemoryException
//
void
CResourceList::CreateSubtreeResourceList(
    CDevice* pDeviceStart,
    RESOURCEID ResType,
    ULONG LogConfType,
    ULONG AltLogConfType
    )
{
    LOG_CONF lc;
    RES_DES rd, rdPrev;
    RESOURCEID ResId;
    BOOL Forced;
    CMachine* pMachine = pDeviceStart->m_pMachine;
    ASSERT(pMachine);

    while (pDeviceStart) 
    {
        //
        // We will try to get a LogConf for either the LogConfType (which defaults to
        // ALLOC_LOG_CONF) or the AltLogConfType (which defaults to BOOT_LOG_CONF).
        // We need to do this because on Win2000 a device that only has a BOOT_LOG_CONF
        // will still consume those resources, even if it does not have an ALLOC_LOG_CONF.
        // So we need to first check the ALLOC_LOG_CONF and if that fails check the
        // BOOT_LOG_CONF.
        //
        if (pMachine->CmGetFirstLogConf(pDeviceStart->GetDevNode(), &lc, LogConfType) ||
            pMachine->CmGetFirstLogConf(pDeviceStart->GetDevNode(), &lc, AltLogConfType)) 
        {
            rdPrev = 0;

            if (pMachine->CmGetNextResDes(&rd, lc, ResType, &ResId)) 
            {
                ULONG DataSize;
                DWORDLONG dlBase, dlLen;

                do 
                {
                    DataSize = pMachine->CmGetResDesDataSize(rd);

                    if (DataSize) 
                    {
                        // need this to use a different image overlay for
                        // forced allocated resource
                        Forced = pMachine->CmGetFirstLogConf(pDeviceStart->GetDevNode(),
                                NULL, FORCED_LOG_CONF);
                                
                        BufferPtr<BYTE> DataPtr(DataSize);
                        
                        if (pMachine->CmGetResDesData(rd, DataPtr, DataSize)) 
                        {
                            if (ExtractResourceValue(ResType, DataPtr, &dlBase, &dlLen))
                            {
                                SafePtr<CResource> ResPtr;
                                
                                CResource* pRes;
                                
                                pRes = new CResource(pDeviceStart, ResType, dlBase,
                                        dlLen, Forced, FALSE);
                                ResPtr.Attach(pRes);
                                InsertResourceToList(pRes);
                                ResPtr.Detach();
                            }
                        }
                    }
                    
                    if (rdPrev)
                        pMachine->CmFreeResDesHandle(rdPrev);
                    
                    rdPrev = rd;
                    
                }while (pMachine->CmGetNextResDes(&rd, rdPrev, ResType, &ResId));
                
                //free the last resource descriptor handle
                pMachine->CmFreeResDesHandle(rd);
            }
            
            pMachine->CmFreeLogConfHandle(lc);
        }
        
        if (pDeviceStart->GetChild())
            CreateSubtreeResourceList(pDeviceStart->GetChild(), ResType, LogConfType, AltLogConfType);
        
        pDeviceStart = pDeviceStart->GetSibling();
    }
}


// This function creates a resource tree
// INPUT:
//  ppResRoot   -- buffer to receive the tree root
//
BOOL
CResourceList::CreateResourceTree(
    CResource** ppResRoot
    )
{
    ASSERT(ppResRoot);

    *ppResRoot = NULL;

    if (!m_listRes.IsEmpty()) 
    {
        POSITION pos = m_listRes.GetHeadPosition();
        CResource* pResFirst;

        pResFirst = m_listRes.GetNext(pos);
        *ppResRoot = pResFirst;

        while (NULL != pos) 
        {
            CResource* pRes = m_listRes.GetNext(pos);
            InsertResourceToTree(pRes, pResFirst, TRUE);
        }
    }
    
    return TRUE;
}

BOOL
CResourceList::InsertResourceToTree(
    CResource* pRes,
    CResource* pResRoot,
    BOOL       ForcedInsert
    )
{
    CResource* pResLast;

    while (pResRoot) 
    {
        if (pRes->EnclosedBy(*pResRoot)) 
        {
            // this resource is either the pResRoot child or grand child
            // figure out which one it is
            if (!pResRoot->GetChild()) 
            {
                pResRoot->SetChild(pRes);
                pRes->SetParent(pResRoot);
            }
            
            else if (!InsertResourceToTree(pRes, pResRoot->GetChild(), FALSE)) 
            {
                // the Resource is not a grand child of pResRoot.
                // search for the last child of pResRoot
                CResource* pResSibling;
                pResSibling = pResRoot->GetChild();

                while (pResSibling->GetSibling()) 
                    pResSibling = pResSibling->GetSibling();
                
                pResSibling->SetSibling(pRes);
                pRes->SetParent(pResRoot);
            }
            
            return TRUE;
        }
        
        pResLast = pResRoot;
        pResRoot = pResRoot->GetSibling();
    }
    
    if (ForcedInsert) 
    {
        // when we reach here, pResLast is the last child
        pResLast->SetSibling(pRes);
        pRes->SetParent(pResLast->GetParent());
        return TRUE;
    }
    
    return FALSE;
}


CResourceList::~CResourceList()
{
    if (!m_listRes.IsEmpty()) 
    {
        POSITION pos = m_listRes.GetHeadPosition();

        while (NULL != pos) 
        {
            delete m_listRes.GetNext(pos);
        }
        
        m_listRes.RemoveAll();
    }
}

BOOL
CResourceList::GetFirst(
    CResource** ppRes,
    PVOID&      Context
    )
{
    ASSERT(ppRes);

    if (!m_listRes.IsEmpty()) 
    {
        POSITION pos = m_listRes.GetHeadPosition();
        *ppRes = m_listRes.GetNext(pos);
        Context = pos;
        return TRUE;
    }
    
    Context = NULL;
    *ppRes = NULL;

    return FALSE;
}

BOOL
CResourceList::GetNext(
    CResource** ppRes,
    PVOID&      Context
    )
{
    ASSERT(ppRes);

    POSITION pos = (POSITION)Context;

    if (NULL != pos) 
    {
        *ppRes = m_listRes.GetNext(pos);
        Context = pos;
        return TRUE;
    }
    
    *ppRes = NULL;
    return FALSE;
}

//
// This function inserts the given resource to class's resource list
// The resources are kept in accending sorted order
void
CResourceList::InsertResourceToList(
    CResource* pRes
    )
{
    POSITION pos;
    CResource* pSrc;
    DWORDLONG dlBase, dlLen;
    pRes->GetValue(&dlBase, &dlLen);

    pos = m_listRes.GetHeadPosition();

    while (NULL != pos) 
    {
        POSITION posSave = pos;
        pSrc = m_listRes.GetNext(pos);

        if (*pRes <= *pSrc) 
        {
            m_listRes.InsertBefore(posSave, pRes);
            return;
        }
    }
    
    m_listRes.AddTail(pRes);
}


inline
CItemIdentifier*
CResource::CreateIdentifier()
{
    return new CResourceIdentifier(*this);
}
