//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
//
// This source code is licensed under Microsoft Shared Source License
// Version 1.0 for Windows CE.
// For a copy of the license visit http://go.microsoft.com/fwlink/?LinkId=3223.
//

#define WINCEMACRO
#define BUILDING_FS_STUBS

#include <windows.h>
#include <devload.h>
#include <guiddef.h>
#include <notify.h>
#include <dbt.h>
#include <console.h>
#include "devmgrp.h"
#include "devzones.h"

typedef struct _DEVICE_CHANGE_THREAD_INIT_INFO {
    LIST_ENTRY ContextChain;        // queue of devices
    CRITICAL_SECTION csNotify;      // serializes access to the queue
    HANDLE hevNewEntries;           // signaled when the queue is updated
} DEVICE_CHANGE_THREAD_INIT_INFO, *PDEVICE_CHANGE_THREAD_INIT_INFO;

DEVICE_CHANGE_THREAD_INIT_INFO g_NotifyThreadState;

// these structures are passed to the notification thread in a queue
typedef struct _DEVICE_CHANGE_CONTEXT {
    LIST_ENTRY link;
    TCHAR DevName[DEVNAME_LEN];
    BOOL bNew;
} DEVICE_CHANGE_CONTEXT, * PDEVICE_CHANGE_CONTEXT;

typedef BOOL (WINAPI *PFN_SendNotifyMessageW)(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam);

static PFN_SendNotifyMessageW gpfnSendNotifyMessageW;

// {6F40791D-300E-44e4-BC38-E0E63CA8375C} -- see DMCLASS_PROTECTEDBUSNAMESPACE in devload.h
static const GUID gDmClassProtectedBusNamespace = { 0x6f40791d, 0x300e, 0x44e4, { 0xbc, 0x38, 0xe0, 0xe6, 0x3c, 0xa8, 0x37, 0x5c } };


//
// Function to format and send a Windows broadcast message announcing the arrival
// or removal of a device in the system.
//
static VOID
BroadcastDeviceChange(
    LPTSTR DevName,
    BOOL bNew
    )
{
    PDEV_BROADCAST_PORT pBCast;
    DWORD len;
    LPTSTR str;

    DEBUGCHK(gpfnSendNotifyMessageW != NULL);

    len = sizeof(DEV_BROADCAST_HDR) + (_tcslen(DevName) + 1)*sizeof(TCHAR);

    pBCast = LocalAlloc(LPTR, len);
    if (pBCast == NULL) {
        return;
    }

    



    pBCast->dbcp_devicetype = DBT_DEVTYP_PORT;
    pBCast->dbcp_reserved = 0;
    str = (LPTSTR)&(pBCast->dbcp_name[0]);
    _tcscpy(str, DevName);
    pBCast->dbcp_size = len;

    DEBUGMSG(ZONE_PNP,
             (TEXT("DEVICE!BroadcastDeviceChange Calling SendNotifyMessage for device %s\r\n"), DevName));

    // Call the function
    gpfnSendNotifyMessageW(
        HWND_BROADCAST,
        WM_DEVICECHANGE,
        (bNew) ? DBT_DEVICEARRIVAL : DBT_DEVICEREMOVECOMPLETE,
        (LPARAM)pBCast);

    LocalFree(pBCast);
}   // BroadcastDeviceChange


//
// Function to signal the app notification system of a device change
//
static VOID
NotifyDevice(
    LPTSTR DevName,
    LPTSTR Op
    )
{
    DWORD len;
    LPTSTR str;


    len = (_tcslen(Op) + _tcslen(DevName) + 2)*sizeof(TCHAR);

    str = LocalAlloc(LPTR, len);
    if (str == NULL) {
        return;
    }

    //
    // Format the end of the command line
    //
    _tcscpy(str, Op);
    _tcscat(str, TEXT(" "));
    _tcscat(str, DevName);

    DEBUGMSG(ZONE_PNP,
        (TEXT("DEVICE!NotifyDevice Calling CeEventHasOccurred(%s)\r\n"), str));
    CeEventHasOccurred(NOTIFICATION_EVENT_DEVICE_CHANGE, str);
    LocalFree(str);
}   // NotifyDevice


//
// Thread function to call CeEventHasOccurred and SendNotifyMessage for a device
// that has been recently installed or removed. Another thread is needed because
// in the context in which the device changes occur, the gwes and the filesystem
// critical sections are taken.
//
static DWORD
DeviceNotifyThread(
   IN PVOID ThreadContext
   )
{
    PDEVICE_CHANGE_THREAD_INIT_INFO pNotifyThreadState = 
        (PDEVICE_CHANGE_THREAD_INIT_INFO) ThreadContext;
    HKEY hk;
    DWORD dwStatus;
    HMODULE hmCoreDll;
    
    DEBUGCHK(pNotifyThreadState != NULL);
    DEBUGCHK(pNotifyThreadState->hevNewEntries != NULL);
    
    // get our thread priority
    dwStatus = RegOpenKeyEx(HKEY_LOCAL_MACHINE, DEVLOAD_DRIVERS_KEY, 0, 0, &hk);
    if(dwStatus == ERROR_SUCCESS) {
        DWORD dwType, dwValue;
        DWORD dwSize = sizeof(dwValue);
        dwStatus = RegQueryValueEx(hk, _T("NotifyPriority256"), NULL, &dwType, 
            (LPBYTE) &dwValue, &dwSize);
        if(dwStatus == ERROR_SUCCESS && dwType == REG_DWORD) {
            CeSetThreadPriority(GetCurrentThread(), (INT) dwValue);
            DEBUGMSG(ZONE_INIT, (_T("DEVICE!DeviceNotifyThread: priority is %u\r\n"),
                dwStatus));
        }
        RegCloseKey(hk);
    }
    

    // get DLL entry points for notification
    hmCoreDll = (HMODULE)LoadLibrary(TEXT("COREDLL.DLL"));
    if(hmCoreDll != NULL) {
        gpfnSendNotifyMessageW = (PFN_SendNotifyMessageW)GetProcAddress(hmCoreDll, TEXT("SendNotifyMessageW"));
        FreeLibrary(hmCoreDll);     // we're explicitly linked with coredll so this is safe
    }

    while(TRUE) {
        dwStatus = WaitForSingleObject(pNotifyThreadState->hevNewEntries, INFINITE);
        if(dwStatus == WAIT_OBJECT_0) {
            BOOL fDone = FALSE;
            
            // process all queued device notifications in order
            while(!fDone) {
                PDEVICE_CHANGE_CONTEXT pdcc = NULL;
                
                // get the next notification, if one is present
                EnterCriticalSection(&pNotifyThreadState->csNotify);
                if(!IsListEmpty(&pNotifyThreadState->ContextChain)) {
                    PLIST_ENTRY pLink = RemoveTailList(&pNotifyThreadState->ContextChain);
                    DEBUGCHK(pLink != NULL);
                    pdcc = CONTAINING_RECORD(pLink, DEVICE_CHANGE_CONTEXT, link);
                    DEBUGCHK(pdcc != NULL);
                }
                LeaveCriticalSection(&pNotifyThreadState->csNotify);
                
                // if we found a queue entry, process it
                if(pdcc != NULL) {
                    if(gpfnSendNotifyMessageW) {
                        if(IsAPIReady(SH_WMGR)) {
                            BroadcastDeviceChange(pdcc->DevName, pdcc->bNew);
                        }
                    }
                    NotifyDevice(pdcc->DevName, (pdcc->bNew) ? NOTIFY_DEVICE_ADD : NOTIFY_DEVICE_REMOVE);
                    LocalFree(pdcc);
                } else {
                    fDone = TRUE;
                }
            }
        } else {
            DEBUGMSG(ZONE_ERROR, 
                (_T("DEVICE!DeviceNotifyThread: WaitForSingleObject() failed %d\r\n"),
                GetLastError()));
        }
    }
    
    return 0;
}   // DeviceNotifyThread


// This routine launches the thread that handles device notification broadcasts.
// It is called once at boot time and returns TRUE if successful, FALSE if
// there's a problem.
static BOOL 
StartDeviceNotifyThread(void)
{
    BOOL fOk = FALSE;
    HANDLE htNotify;
    
    // set up the notification structure
    g_NotifyThreadState.hevNewEntries = CreateEvent(NULL, FALSE, FALSE, NULL);
    InitializeCriticalSection(&g_NotifyThreadState.csNotify);
    InitializeListHead(&g_NotifyThreadState.ContextChain);
    
    // make sure we have everything we need
    if(g_NotifyThreadState.hevNewEntries == NULL) {
        DEBUGMSG(ZONE_WARNING, 
            (_T("DEVICE!StartDeviceNotify: CreateEvent() failed\r\n")));
    } else {
        htNotify = CreateThread(NULL, 0,
            (LPTHREAD_START_ROUTINE)DeviceNotifyThread,
            (LPVOID) &g_NotifyThreadState, 0, NULL);
        if (htNotify == NULL) {
            DEBUGMSG(ZONE_WARNING, 
                (_T("DEVICE!StartDeviceNotify: CreateThread() failed\r\n")));
        } else {
            // close the handle, since we're not waiting on the thread -- but
            // keep the value to record that the thread is running.
            CloseHandle(htNotify);
            fOk = TRUE;
        }
    }
    
    // if there was an error, free resources
    if(!fOk) {
        if(g_NotifyThreadState.hevNewEntries != NULL) {
            CloseHandle(g_NotifyThreadState.hevNewEntries);
            g_NotifyThreadState.hevNewEntries = NULL;
        }
        DeleteCriticalSection(&g_NotifyThreadState.csNotify);
    }

    DEBUGCHK(fOk);

    return fOk;
}

//
// Function to start a thread that signals a device change via the application
// notification system and via a broadcast windows message.
//
VOID
StartDeviceNotify(
    LPTSTR DevName,
    BOOL bNew
    )
{
    PDEVICE_CHANGE_CONTEXT pdcc;

    DEBUGCHK(g_NotifyThreadState.hevNewEntries != NULL);
    DEBUGCHK(_tcslen(DevName) < DEVNAME_LEN);

    if(g_NotifyThreadState.hevNewEntries != NULL) {
        pdcc = LocalAlloc(LPTR, sizeof(DEVICE_CHANGE_CONTEXT));
        if (pdcc == NULL) {
            DEBUGMSG(ZONE_WARNING, (_T("DEVICE!StartDeviceNotify: LocalAlloc() failed\r\n")));
            return;
        }
        
        pdcc->bNew = bNew;
        memcpy(pdcc->DevName, DevName, DEVNAME_LEN*sizeof(TCHAR));
        
        pdcc->DevName[DEVNAME_LEN-1] = '\0';        // Sentinel
        
        // queue the notification if the thread is not running
        EnterCriticalSection(&g_NotifyThreadState.csNotify);
        InsertHeadList(&g_NotifyThreadState.ContextChain, (PLIST_ENTRY) pdcc);
        LeaveCriticalSection(&g_NotifyThreadState.csNotify);
        SetEvent(g_NotifyThreadState.hevNewEntries);
    }
}   // StartDeviceNotify


BOOL 
ConvertStringToGuid (IN LPCTSTR pwszGuid, OUT GUID *pGuid)
{
    UINT Data4[8];
    int  Count;
    const WCHAR GUID_FORMAT_W[] = L"{%08lX-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}";

    DEBUGCHK(pwszGuid != NULL);
    DEBUGCHK(pGuid != NULL);

    if (wcslen(pwszGuid) != 38
        || swscanf(pwszGuid,
                      GUID_FORMAT_W,
                      &pGuid->Data1, 
                      &pGuid->Data2, 
                      &pGuid->Data3, 
                      &Data4[0], 
                      &Data4[1], 
                      &Data4[2], 
                      &Data4[3], 
                      &Data4[4], 
                      &Data4[5], 
                      &Data4[6], 
                      &Data4[7]) != 11) 
    {
        return FALSE;
    }

    for(Count = 0; Count < sizeof(Data4)/sizeof(Data4[0]); Count++) 
    {
        pGuid->Data4[Count] = (UCHAR)Data4[Count];
    }

    return TRUE;
}

// This routine looks for an interface advertised by a device driver. If it
// finds a match, it returns a pointer to its state structure.  Otherwise,
// it returns NULL.  The caller of this routine must hold the device manager
// critical section.
fsinterface_t *
FindDeviceInterface(fsdev_t *lpdev, const GUID *pClass)
{
    fsinterface_t *pif;

    DEBUGCHK(lpdev != NULL);
    DEBUGCHK(pClass != NULL);
    
    for(pif = lpdev->pInterfaces; pif != NULL; pif = pif->pNext) {
        if(IsEqualGUID(&pif->guidClass, pClass)) {
            break;
        }
    }

    return pif;
}

// This routine advertises or deadvertises an interface that is associated
// with a particular device driver.  If successful it will allocate or
// free a structure describing the interface.  Its intrface is modeled
// after AdvertiseInterface(), with an extra parameter for the device state
// structure.  Unlike AdvertiseInterface(), it returns a status DWORD, with
// ERROR_SUCCESS indicating successful completion or a Win32 error code
// indicating a problem.
DWORD
I_AdvertiseDeviceInterface(fsdev_t *lpdev, LPCGUID pClass, LPCWSTR pszName, BOOL fAdd)
{
    DWORD dwStatus = ERROR_SUCCESS;
#ifdef DEBUG
    LPCWSTR pszFname = L"I_AdvertiseDeviceInterface";
#endif  // DEBUG

    DEBUGCHK(lpdev != NULL);
    DEBUGCHK(pClass != NULL);
    DEBUGCHK(pszName != NULL);

    if(fAdd) {
        // allocate a structure to keep track of the interface
        fsinterface_t *pif;
        pif = (fsinterface_t *) LocalAlloc(0, sizeof(*pif) + ((wcslen(pszName) + 1) * sizeof(pszName[0])));
        if(pif == NULL) {
            DEBUGMSG(ZONE_WARNING, 
                (_T("%s: not enough memory to advertise '%s' for device 0x%08x\r\n"),
                pszFname, pszFname, lpdev));
            dwStatus = ERROR_OUTOFMEMORY;
        } else {
            LPWSTR pszTemp = (LPWSTR) ((LPBYTE) pif + sizeof(*pif));
            
            // initialize the structure
            wcscpy(pszTemp, pszName);
            pif->pszName = pszTemp;
            pif->guidClass = *pClass;

            // advertise the interface
            EnterCriticalSection(&g_devcs);
            if(!AdvertiseInterface_Trap(&pif->guidClass, pif->pszName, lpdev->dwId, fAdd)) {
                dwStatus = GetLastError();
                DEBUGMSG(ZONE_WARNING, 
                    (_T("%s: AdvertiseInterface({%08lx-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x}, '%s', %d) failed %d\r\n"), 
                    pszFname,
                    pif->guidClass.Data1, pif->guidClass.Data2, pif->guidClass.Data3, 
                    pif->guidClass.Data4[0], pif->guidClass.Data4[1], pif->guidClass.Data4[2], pif->guidClass.Data4[3], 
                    pif->guidClass.Data4[4], pif->guidClass.Data4[5], pif->guidClass.Data4[6], pif->guidClass.Data4[7], 
                    pif->pszName, fAdd, dwStatus));
                DEBUGCHK(dwStatus != ERROR_SUCCESS);
            } else {
                DEBUGMSG(ZONE_PNP, 
                    (L"PNP interface class {%08lx-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x} (%s) ATTACH\n", 
                    pif->guidClass.Data1, pif->guidClass.Data2, pif->guidClass.Data3, 
                    pif->guidClass.Data4[0], pif->guidClass.Data4[1], pif->guidClass.Data4[2], pif->guidClass.Data4[3], 
                    pif->guidClass.Data4[4], pif->guidClass.Data4[5], pif->guidClass.Data4[6], pif->guidClass.Data4[7], 
                    pif->pszName));
                pif->pNext = lpdev->pInterfaces;
                lpdev->pInterfaces = pif;
            }
            LeaveCriticalSection(&g_devcs);

            // did we add the interface successfully?
            if(dwStatus != ERROR_SUCCESS) {
                // no, release resources
                LocalFree(pif);
            }
        }
    } else {
        // shut down interfaces and free their associated data
        fsinterface_t *pif, *pifPrev;

        EnterCriticalSection(&g_devcs);

        // find the interface
        pifPrev = NULL;
        for(pif = lpdev->pInterfaces; pif != NULL; pif = pif->pNext) {
            if(IsEqualGUID(&pif->guidClass, pClass) && wcscmp(pif->pszName, pszName) == 0) {
                break;
            }
            pifPrev = pif;
        }

        // did we find it?
        if(pif == NULL) {
            DEBUGMSG(ZONE_WARNING, 
                (L"%s: can't find {%08lx-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x} (%s)\n", 
                pszFname,
                pClass->Data1, pClass->Data2, pClass->Data3, 
                pClass->Data4[0], pClass->Data4[1], pClass->Data4[2], pClass->Data4[3], 
                pClass->Data4[4], pClass->Data4[5], pClass->Data4[6], pClass->Data4[7], 
                pszName));
            dwStatus = ERROR_FILE_NOT_FOUND;
        } else {
            BOOL fOk;

            // remove this entry from the list
            if(pifPrev != NULL) {
                pifPrev->pNext = pif->pNext;
            } else {
                lpdev->pInterfaces = pif->pNext;
            }

            // de-advertise the interface
            DEBUGMSG(ZONE_PNP, 
                (L"PNP interface class {%08lx-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x} (%s) DETACH\n", 
                pif->guidClass.Data1, pif->guidClass.Data2, pif->guidClass.Data3, 
                pif->guidClass.Data4[0], pif->guidClass.Data4[1], pif->guidClass.Data4[2], pif->guidClass.Data4[3], 
                pif->guidClass.Data4[4], pif->guidClass.Data4[5], pif->guidClass.Data4[6], pif->guidClass.Data4[7], 
                pif->pszName));
            fOk = AdvertiseInterface_Trap(&pif->guidClass, pif->pszName, lpdev->dwId, fAdd);
            if(!fOk) {
                dwStatus = GetLastError();
                DEBUGMSG(ZONE_WARNING, 
                    (_T("%s: AdvertiseInterface({%08lx-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x}, '%s', %d) failed %d\r\n"), 
                    pszFname,
                    pif->guidClass.Data1, pif->guidClass.Data2, pif->guidClass.Data3, 
                    pif->guidClass.Data4[0], pif->guidClass.Data4[1], pif->guidClass.Data4[2], pif->guidClass.Data4[3], 
                    pif->guidClass.Data4[4], pif->guidClass.Data4[5], pif->guidClass.Data4[6], pif->guidClass.Data4[7], 
                    pif->pszName, fAdd, dwStatus));
                DEBUGCHK(dwStatus != ERROR_SUCCESS);
            }

            // release resources
            LocalFree(pif);
        }

        LeaveCriticalSection(&g_devcs);
    }

    // update the device driver's state structure if necessary
    if(lpdev->pszBusName != NULL && lpdev->fnOpen != NULL) {
        // are we advertising support for protected namespaces and associating it with our bus name?
        if(IsEqualGUID(&gDmClassProtectedBusNamespace, pClass)) {
            WCHAR szNameBuf[MAX_PATH];
            HRESULT hr = StringCchCopyW(szNameBuf, ARRAYSIZE(szNameBuf), L"$bus\\");
            hr = StringCchCatW(szNameBuf, ARRAYSIZE(szNameBuf), lpdev->pszBusName);
            if(wcscmp(szNameBuf, pszName) == 0) {
                EnterCriticalSection(&g_devcs);
                if(fAdd) {
                    lpdev->wFlags |= DF_ENABLE_BUSNAMES;
                } else {
                    lpdev->wFlags &= ~DF_ENABLE_BUSNAMES;
                }
                LeaveCriticalSection(&g_devcs);
            }
        }
    }
    return dwStatus;
}

// Check to see if there is an "IClass" value in the device's active key;
// if not, check for one in the device key.
// If still not, then if there's a "Prefix" value, use the DEVCLASS_STREAM guid;
// otherwise issue a warning - this driver is not PnP-CE compatible.
//
// For each guid in the IClass value (assume 1 if we defaulted):
//   it should be of the form "GUID[=name]" where GUID is in the standard
//   registry form - {8-4-4-4-12} - and name is a string representing the
//   the device address as advertised over the interface designated by the GUID.
//   If the "=name" component is not present then the driver must have specified
//   a Prefix and the name will default to the name of the stream interface.
void 
PnpAdvertiseInterfaces (fsdev_t *lpdev)
{
    HKEY hk = NULL;
    DWORD dwStatus, dwSize;
    LPWSTR pszStreamIClass = L"{f8a6ba98-087a-43ac-a9d8-b7f13c5bae31}\0";   // can't include '=' because we modify the string
    WCHAR szNameBuf[MAX_PATH];
    LPWSTR pszIClass, pszClass;
#ifdef DEBUG
    LPCWSTR pszFname = _T("Device!PnpAdvertiseInterfaces");
#endif  // DEBUG

    DEBUGCHK(lpdev != NULL);
    DEBUGCHK(lpdev->pszDeviceKey != NULL);

    // format the active key path, We sure szNameBuf is big enough to hold the formatted string.
    wsprintf(szNameBuf, L"%s\\%02u", s_ActiveKey, lpdev->dwId);

    // open the active key
    dwStatus = RegOpenKeyEx(HKEY_LOCAL_MACHINE, szNameBuf, 0, 0, &hk);
    if(dwStatus != ERROR_SUCCESS) {
        DEBUGMSG(ZONE_WARNING, 
            (_T("%s: can't open active key '%s' for device 0x%08x\r\n"), 
            pszFname, szNameBuf, lpdev));
    } else {
        dwStatus = RegQueryValueEx(hk, DEVLOAD_ICLASS_VALNAME, NULL, NULL, NULL, &dwSize);
        if(dwStatus != ERROR_SUCCESS) {
            RegCloseKey(hk);
        }
    } 

    // have we found an IClass?
    if(dwStatus != ERROR_SUCCESS) {
        // no, check the device key
        dwStatus = RegOpenKeyEx(HKEY_LOCAL_MACHINE, lpdev->pszDeviceKey, 0, 0, &hk);
        if(dwStatus != ERROR_SUCCESS) {
            DEBUGMSG(ZONE_WARNING, 
                (_T("%s: can't open device key '%s' for device 0x%08x\r\n"), 
                pszFname, lpdev->pszDeviceKey, lpdev));
        } else {
            dwStatus = RegQueryValueEx(hk, DEVLOAD_ICLASS_VALNAME, NULL, NULL, NULL, &dwSize);
            if(dwStatus != ERROR_SUCCESS) {
                RegCloseKey(hk);
            }
        }
    }

    // have we found an IClass?
    if(dwStatus != ERROR_SUCCESS) {
        // no, use the default stream class
        pszIClass = pszStreamIClass;
        dwStatus = ERROR_SUCCESS;
    } else {
        // yes, validate the size
        if((dwSize % sizeof(WCHAR)) != 0) {
            DEBUGMSG(ZONE_WARNING, 
                (_T("%s: IClass size %u for device 0x%08x is not a multiple of WCHAR\r\n"), 
                pszFname, dwSize, lpdev));
            RegCloseKey(hk);
            return;
        }

        // try to read the IClass
        __try {
            pszIClass = _alloca(dwSize + sizeof(WCHAR));
        }
        __except(EXCEPTION_EXECUTE_HANDLER) {
            dwStatus = ERROR_OUTOFMEMORY;
            pszIClass = NULL;
        }
        if(pszIClass != NULL) {
            dwStatus = RegQueryValueEx(hk, DEVLOAD_ICLASS_VALNAME, NULL, NULL, (LPBYTE) pszIClass, &dwSize);
            dwSize /= sizeof(pszIClass[0]);
            pszIClass[dwSize - 1] = 0;  // enforce null termination
            pszIClass[dwSize] = 0;      // enforce multi-sz double termination
        }
        RegCloseKey(hk);
    }

    // have we read the IClass?
    if(dwStatus != ERROR_SUCCESS) {
        DEBUGMSG(ZONE_WARNING, (_T("%s: can't get IClass for device 0x%08x\r\n"), pszFname, lpdev));
        return;
    }

    // send legacy CeEventHasOccurred and WM_DEVICECHANGE notifications
    if(lpdev->pszLegacyName != NULL) {
        StartDeviceNotify(lpdev->pszLegacyName, TRUE);
    }

    // Parse the IClass data, which consists of multiple null-terminated strings
    pszClass = pszIClass;
    while(*pszClass != 0) {
        LPWSTR pszName;
        DWORD dwLen = wcslen(pszClass);

        // decide what name to associate with the GUID
        pszName = wcschr(pszClass, L'=');
        if(pszName != NULL) {
            *pszName = 0;       // null terminate the class
            pszName++;          // get the rest of the name
            if(*pszName == 0) {
                DEBUGMSG(ZONE_WARNING, (_T("%s: empty name associated with IClass '%s' for device 0x%08x\r\n"), 
                    pszFname, pszClass, lpdev));
                pszClass += dwLen + 1;
                continue;
            }
        }

        // was a name specified?
        if(pszName == NULL) {
            if(lpdev->pszLegacyName != NULL) {
                pszName = lpdev->pszLegacyName;
            } else if(lpdev->pszDeviceName != NULL) {
                pszName = lpdev->pszDeviceName;
            } else {
                DEBUGMSG(ZONE_PNP, 
                    (_T("%s: can't advertise interfaces from unnamed device 0x%08x\r\n"),
                    pszFname, lpdev));
            }
        }

        // is the name actually a namespace specifier?
        if(pszName != NULL) {
            if(wcsicmp(pszName, L"%d") == 0) {
                if(lpdev->pszDeviceName == NULL) {
                    DEBUGMSG(ZONE_WARNING, 
                        (_T("%s: device 0x%08x has no $device name to advertise\r\n"), pszFname, lpdev));
                    pszName = NULL;
                } else {
                    HRESULT hr = StringCchCopyW(szNameBuf, ARRAYSIZE(szNameBuf), L"$device\\");
                    hr = StringCchCatW(szNameBuf, ARRAYSIZE(szNameBuf), lpdev->pszDeviceName);
                    pszName = szNameBuf;
                }
            } else if(wcsicmp(pszName, L"%b") == 0) {
                if(lpdev->pszBusName == NULL || lpdev->fnOpen == NULL) {
                    DEBUGMSG(ZONE_WARNING, 
                        (_T("%s: device 0x%08x does not support handle access to $bus names\r\n"), pszFname, lpdev));
                    pszName = NULL;
                } else {
                    HRESULT hr = StringCchCopyW(szNameBuf, ARRAYSIZE(szNameBuf), L"$bus\\");
                    hr = StringCchCatW(szNameBuf, ARRAYSIZE(szNameBuf), lpdev->pszBusName);
                    pszName = szNameBuf;
                }
            } else if(wcsicmp(pszName, L"%l") == 0) {
                if(lpdev->pszLegacyName == NULL) {
                    DEBUGMSG(ZONE_WARNING, 
                        (_T("%s: device 0x%08x has no legacy name to advertise\r\n"), pszFname, lpdev));
                    pszName = NULL;
                } else {
                    HRESULT hr = StringCchCopyW(szNameBuf, ARRAYSIZE(szNameBuf), lpdev->pszLegacyName);
                    pszName = szNameBuf;
                }
            }
        }

        // advertise the interface if we have a name associated with it
        if(pszName != NULL) {
            GUID guidClass;

            if (ConvertStringToGuid(pszClass, &guidClass) == FALSE) {
                // WARN! bad guid syntax
                DEBUGMSG(ZONE_WARNING, (_T("%s: invalid class GUID '%s'\r\n"), pszFname, pszClass));
            } else {
                I_AdvertiseDeviceInterface(lpdev, &guidClass, pszName, TRUE);
            }
        }
        pszClass += dwLen + 1;
    }
}

void 
PnpDeadvertiseInterfaces (fsdev_t *lpdev)
{
    DEBUGCHK(lpdev != NULL);

    // send legacy CeEventHasOccurred and WM_DEVICECHANGE notifications
    if(lpdev->pszLegacyName != NULL) {
        StartDeviceNotify(lpdev->pszLegacyName, FALSE);
    }

    // shut down interfaces and free their associated data
    EnterCriticalSection(&g_devcs);
    while(lpdev->pInterfaces != NULL) {
        I_AdvertiseDeviceInterface(lpdev, &lpdev->pInterfaces->guidClass, lpdev->pInterfaces->pszName, FALSE);
    }
    LeaveCriticalSection(&g_devcs);
}

// this routine sets up global data structures and starts the notification
// thread
void 
InitializePnPNotifications (void)
{
    StartDeviceNotifyThread();
}

