//+-------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright (C) Microsoft Corporation, 1994 - 2000.
//
//  File:       service.cxx
//
//  Contents:   CI service
//
//  History:    17-Sep-96   dlee   Created
//
//--------------------------------------------------------------------------

#include <pch.cxx>
#pragma hdrstop

#include <dbt.h>
#include <initguid.h>   // so we know the value of GUIDs
#include <ioevent.h>
#include <cievtmsg.h>
#include <cisvcex.hxx>
#include <eventlog.hxx>
#include <ciregkey.hxx>
#include <regacc.hxx>
#include <drvnotif.hxx>
#include <notifary.hxx>

//
// HRESULTTOWIN32() maps an HRESULT to a Win32 error. If the facility code
// of the HRESULT is FACILITY_WIN32, then the code portion (i.e. the
// original Win32 error) is returned. Otherwise, the original HRESULT is
// returned unchagned.
//

#define HRESULT_CODE(hr)     ((hr) & 0xFFFF)
#define HRESULTTOWIN32(hres) ((HRESULT_FACILITY(hres) == FACILITY_WIN32) ? HRESULT_CODE(hres) : (hres))

static const DWORD dwServiceWaitHint = 60000;   // 60 seconds

SERVICE_STATUS_HANDLE g_hTheCiSvc = 0;
static DWORD dwCiSvcStatus = SERVICE_START_PENDING;

#define DEB_CI_MOUNT DEB_ITRACE

// 1: 324666 is fixed
// 0: 324666 is not fixed and we need an extra thread to work around it

#define SYNC_REGISTER 1

BOOL g_fSCMThreadIsGone = FALSE;

//+----------------------------------------------------------------------------
//
//  Function:   UpdateServiceStatus
//
//  Synopsis:   Does a SetServiceStatus() to the service manager.
//
//  History:    06-Jun-94   DwightKr    Created
//
//-----------------------------------------------------------------------------

void UpdateServiceStatus( DWORD dwWin32ExitCode )
{
    // note to accept power events, "OR" SERVICE_ACCEPT_POWER_EVENTS here
    static SERVICE_STATUS CiSvcStatus =
    {
        SERVICE_WIN32_OWN_PROCESS |         // dwServiceType
          SERVICE_INTERACTIVE_PROCESS,      // add this line for interactive
        0,                                  // dwCurrentState
        SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE |
        SERVICE_ACCEPT_SHUTDOWN,
        NO_ERROR,                           // dwWin32ExitCode
        0,                                  // dwServiceSpecificExitCode
        0,                                  // dwCheckPoint
        0                                   // dwWaitHint
    };

    CiSvcStatus.dwCurrentState  = dwCiSvcStatus;

    CiSvcStatus.dwWin32ExitCode = dwWin32ExitCode;

    if ( dwCiSvcStatus == SERVICE_START_PENDING ||
         dwCiSvcStatus == SERVICE_STOP_PENDING )
    {
        CiSvcStatus.dwCheckPoint++;
        CiSvcStatus.dwWaitHint = dwServiceWaitHint;
    }
    else // SERVICE_RUNNING, SERVICE_STOPPED, SERVICE_PAUSED, ...
    {
        CiSvcStatus.dwCheckPoint = 0;
        CiSvcStatus.dwWaitHint   = 0;
    }

    ciDebugOut(( DEB_ITRACE, "service status: %d\n", dwCiSvcStatus ));

    BOOL fOK = SetServiceStatus(g_hTheCiSvc, &CiSvcStatus);

#if CIDBG == 1
    if ( !fOK )
        ciDebugOut(( DEB_ITRACE, "Ci Service: Error from SetServiceStatus = 0x%x\n", GetLastError() ));
#endif
} //UpdateServerStatus

//+-------------------------------------------------------------------------
//
//  Function:   ProcessCustomEvent
//
//  Synopsis:   This is a helper for HandleDevNotification. It processes
//              the device custom events
//
//  Arguments:  [pEventData] -- a PDEV_BROADCAST_HDR object
//              [pContext]   -- a CDrvNotifArray * object.
//
//  Return:     error code from the StartCatalogOnVol/StopCatalogsOnVol
//              proceudres
//
//  History:    18-May-98       kitmanh        Created
//              12-Aug-98       kitmanh        Added return value
//
//--------------------------------------------------------------------------

SCODE ProcessCustomEvent( PVOID pEventData, PVOID pContext )
{
    SCODE sc = S_OK;
    DEV_BROADCAST_HDR UNALIGNED *pBroadcastHdr = (PDEV_BROADCAST_HDR) pEventData;

    ciDebugOut(( DEB_CI_MOUNT, "What is the device type? (%#x)\n", 
                 pBroadcastHdr->dbch_devicetype ));

    // is this a handled event?
    if ( DBT_DEVTYP_HANDLE != pBroadcastHdr->dbch_devicetype)
        return ERROR_CALL_NOT_IMPLEMENTED;

    DEV_BROADCAST_HANDLE UNALIGNED *pDevBroadcastHandle = (PDEV_BROADCAST_HANDLE) pBroadcastHdr;

    ciDebugOut(( DEB_CI_MOUNT, "It is a handled type, handle %#x\n",
                 pDevBroadcastHandle->dbch_hdevnotify ));

    CDrvNotifArray * pDrvNotifArray = (CDrvNotifArray *)pContext;

    CDrvNotificationInfo * pDriveInfo = pDrvNotifArray->FindDriveNotificationByHandle(
                                        (HDEVNOTIFY) pDevBroadcastHandle->dbch_hdevnotify);

    if ( 0 != pDriveInfo )
    {
        if ( GUID_IO_VOLUME_LOCK == pDevBroadcastHandle->dbch_eventguid ) 
        {
            // a volume lock occurred
            ciDebugOut(( DEB_CI_MOUNT, "GUID_IO_VOLUME_LOCK for volume %wc\n", 
                         pDriveInfo->GetDrvLetter() ));
                        
            if ( eVolReady == pDriveInfo->GetVolState() ) 
            {
                ciDebugOut(( DEB_CI_MOUNT, "About to stop catalogs on Vol\n" ));
                sc = StopCiSvcWork( eLockVol, pDriveInfo->GetDrvLetter() );   //stop catalog on volume
                ciDebugOut(( DEB_CI_MOUNT, "Ci Service: Done stopping the catalogs\n" ));

                pDriveInfo->SetVolState( eVolLocked ); 
            }
                        
            ciDebugOut(( DEB_CI_MOUNT, "Increment Lock Attempts\n" ));
            pDriveInfo->IncLockAttempts();
            ciDebugOut(( DEB_CI_MOUNT, "_cLockAttempts is %d\n", pDriveInfo->GetLockAttempts() ));
        }
        else if ( GUID_IO_MEDIA_REMOVAL == pDevBroadcastHandle->dbch_eventguid ) 
        {
            // CD-ROMs aren't giving dismount/unlock notifies,
            // so key off of this instead.  This is "by design" apparently.
            // A media removal occurred.  If we have a CD-ROM catalog
            // open, close it.

            ciDebugOut(( DEB_CI_MOUNT, "GUID_IO_MEDIA_REMOVAL for volume %wc\n", 
                         pDriveInfo->GetDrvLetter() ));

            WCHAR awc[4];
            wcscpy( awc, L"C:\\" );
            awc[0] = pDriveInfo->GetDrvLetter();

            if ( DRIVE_CDROM == GetDriveType( awc ) )
            {
                if ( eVolReady == pDriveInfo->GetVolState() ) 
                {
                    ciDebugOut(( DEB_CI_MOUNT, "About to stop catalogs on Vol\n" ));
                    sc = StopCiSvcWork( eLockVol, pDriveInfo->GetDrvLetter() );   //stop catalog on volume
                    ciDebugOut(( DEB_CI_MOUNT, "Ci Service: Done stopping the catalogs\n" ));
                }
            }
        }
        else if ( GUID_IO_VOLUME_UNLOCK == pDevBroadcastHandle->dbch_eventguid )
        {
            // This assert is not always true for CD-ROMs.  I don't know why

            //Win4Assert( eVolLocked == pDriveInfo->GetVolState() );

            if ( eVolLocked == pDriveInfo->GetVolState() )
            {
                // a volume was unlocked

                ciDebugOut(( DEB_CI_MOUNT, "GUID_IO_VOLUME_UNLOCK for volume %wc, removable %s, automount %s\n", 
                             pDriveInfo->GetDrvLetter(),
                             pDriveInfo->IsRemovable() ? "yes" : "no",
                             pDriveInfo->IsAutoMount() ? "yes" : "no" ));
    
                pDriveInfo->ResetLockAttempts();
                
                // Unregister since the _hVol is obsolete
    
                pDriveInfo->UnregisterNotification();
    
                //
                // We can open catalogs on fixed volumes on the unlock, but not on
                // removable volumes, since we get an unlock once the volume is
                // ejected.  For removable volumes, open the catalog on the mount,
                // except for the case of chkdsk where the volume will be mountable
                // immediately and we won't get the mount notification
                // For Fixed volumes, don't wait for the mount since it may be a
                // long time until the mount happens.
                //
    
                if ( pDriveInfo->Touch() )
                {
                    ciDebugOut(( DEB_CI_MOUNT, "About to start catalogs on Vol %wc\n",
                                 pDriveInfo->GetDrvLetter() ));
                    sc = StopCiSvcWork( eUnLockVol, pDriveInfo->GetDrvLetter() );
                    ciDebugOut(( DEB_CI_MOUNT, "Ci Service: Done starting the catalogs\n" ));
                }
    
                //
                // Asynchronously redo RegisterDeviceNotification with a new
                //  volume handle.
                // If we don't unregister/reregister on Jaz volumes we never get a
                // mount notify.  But on fixed volumes if we unregister/reregister
                // we miss the mount since it happens before the register succeeds
                // on an operation like chkdsk.
                //
    
#if SYNC_REGISTER
                pDriveInfo->RegisterNotification();
#else
                pDrvNotifArray->RegisterDormantEntries();
#endif
    
                pDriveInfo->SetVolState( eVolReady );
            }
        }
        else if ( GUID_IO_VOLUME_LOCK_FAILED == pDevBroadcastHandle->dbch_eventguid )
        {
            ciDebugOut(( DEB_CI_MOUNT, "GUID_IO_VOLUME_LOCK_FAILED for volume %wc\n",
                         pDriveInfo->GetDrvLetter() ));

            if ( pDriveInfo->GetLockAttempts() > 0 )
            { 
                ciDebugOut(( DEB_CI_MOUNT, "Decrement _cLockAttempts\n" ));
                pDriveInfo->DecLockAttempts();
            }
            else
            {
                Win4Assert( eVolReady == pDriveInfo->GetVolState() );
            }

            ciDebugOut(( DEB_CI_MOUNT, "_cLockAttempts is %d\n", pDriveInfo->GetLockAttempts() ));
                                
            if ( ( 0 == pDriveInfo->GetLockAttempts() ) && 
                 ( eVolLocked == pDriveInfo->GetVolState() ) ) 
            {
                // unlock the volume, since all attemps to lock the volume have failed.

                pDriveInfo->UnregisterNotification();

                ciDebugOut(( DEB_CI_MOUNT, "About to start(lock_failed) catalogs on Vol %wc\n", pDriveInfo->GetDrvLetter() ));
                sc = StopCiSvcWork( eUnLockVol, pDriveInfo->GetDrvLetter() );
                ciDebugOut(( DEB_CI_MOUNT, "Ci Service: Done starting the catalogs\n" ));

                // redo RegisterDeviceNotification with a new volume handle
#if SYNC_REGISTER
                pDriveInfo->RegisterNotification();
#else
                pDrvNotifArray->RegisterDormantEntries();
#endif
                pDriveInfo->SetVolState( eVolReady );   
                ciDebugOut(( DEB_CI_MOUNT, "Done Unlocking for lock_failed\n" ));
            }
        }
        else if ( GUID_IO_VOLUME_DISMOUNT == pDevBroadcastHandle->dbch_eventguid &&
                  eVolReady == pDriveInfo->GetVolState() )
        {
            ciDebugOut(( DEB_CI_MOUNT, "GUID_IO_VOLUME_DISMOUNT for volume %wc, removable %s, automount %s\n",
                         pDriveInfo->GetDrvLetter(),
                         pDriveInfo->IsRemovable() ? "yes" : "no",
                         pDriveInfo->IsAutoMount() ? "yes" : "no" ));

            ciDebugOut(( DEB_CI_MOUNT, "About to stop catalogs on Vol %wc\n", pDriveInfo->GetDrvLetter() ));
            sc = StopCiSvcWork( eLockVol, pDriveInfo->GetDrvLetter() );   //stop catalog on volume
            ciDebugOut(( DEB_CI_MOUNT, "Ci Service: Done stopping the catalogs on Vol %wc\n", pDriveInfo->GetDrvLetter() ));

            pDriveInfo->SetVolState( eVolLocked );
        }
        else if ( GUID_IO_VOLUME_DISMOUNT_FAILED == pDevBroadcastHandle->dbch_eventguid &&
                  eVolReady != pDriveInfo->GetVolState() )
        {
            ciDebugOut(( DEB_CI_MOUNT, "GUID_IO_VOLUME_DISMOUNT_FAILED for volume %wc\n",
                         pDriveInfo->GetDrvLetter() ));
            
            pDriveInfo->UnregisterNotification();

            ciDebugOut(( DEB_CI_MOUNT, "About to start(dimount_failed) catalogs on Vol %wc\n", pDriveInfo->GetDrvLetter() ));
            sc = StopCiSvcWork( eUnLockVol, pDriveInfo->GetDrvLetter() );
            ciDebugOut(( DEB_CI_MOUNT, "Ci Service: Done starting the catalogs\n" ));

            // redo RegisterDeviceNotification with a new volume handle
#if SYNC_REGISTER
            pDriveInfo->RegisterNotification();
#else
            pDrvNotifArray->RegisterDormantEntries();
#endif
            pDriveInfo->SetVolState( eVolReady );
        }
        else if ( GUID_IO_VOLUME_MOUNT == pDevBroadcastHandle->dbch_eventguid )
        {
            ciDebugOut(( DEB_CI_MOUNT, "GUID_IO_VOLUME_MOUNT for volume %wc, removable %s, automount %s\n",
                         pDriveInfo->GetDrvLetter(),
                         pDriveInfo->IsRemovable() ? "yes" : "no",
                         pDriveInfo->IsAutoMount() ? "yes" : "no" ));

            //
            // Mount notifications come at the oddest times -- even after an
            // eject of a removable volume!  Make sure the volume really is
            // valid by touching it before trying to open a catalog on the
            // volume.  Only start catalogs on mount for removable drives.
            // Start catalogs for fixed drives on Unlock.  This is because
            // we have to asynchronously re-register for notifications after
            // an unlock, and by the time we register we've missed the mount.
            // Lovely piece of design work by the pnp guys.  Note: this is
            // partially fixed in current builds.  If we don't re-register
            // we get everything but mount notifications on removable
            // drives.
            //

            BOOL fOK = pDriveInfo->Touch();

            ciDebugOut(( DEB_CI_MOUNT, "drive %wc appears healthy? %d\n",
                         pDriveInfo->GetDrvLetter(),
                         fOK ));

            if ( fOK && pDriveInfo->IsRemovable() )
            {
                ciDebugOut(( DEB_CI_MOUNT, "About to start catalogs on Vol\n" ));
                sc = StopCiSvcWork( eUnLockVol, pDriveInfo->GetDrvLetter() );
                ciDebugOut(( DEB_CI_MOUNT, "Ci Service: Done starting the catalogs\n" ));
            }
        }
        else
        {
            ciDebugOut(( DEB_CI_MOUNT, "UNHANDLED but device object was recognized\n" ));

            if ( GUID_IO_VOLUME_LOCK_FAILED   == pDevBroadcastHandle->dbch_eventguid )
                ciDebugOut(( DEB_CI_MOUNT, "   GUID_IO_VOLUME_LOCK_FAILED\n" ));
            else if ( GUID_IO_VOLUME_DISMOUNT_FAILED == pDevBroadcastHandle->dbch_eventguid )
                ciDebugOut(( DEB_CI_MOUNT, "   GUID_IO_VOLUME_DISMOUNT_FAILED\n" ));
            else if ( GUID_IO_VOLUME_LOCK     == pDevBroadcastHandle->dbch_eventguid )
                ciDebugOut(( DEB_CI_MOUNT, "   GUID_IO_VOLUME_LOCK\n" ));
            else if ( GUID_IO_VOLUME_UNLOCK   == pDevBroadcastHandle->dbch_eventguid )
                ciDebugOut(( DEB_CI_MOUNT, "   GUID_IO_VOLUME_UNLOCK\n" ));
            else if ( GUID_IO_VOLUME_DISMOUNT == pDevBroadcastHandle->dbch_eventguid )
                ciDebugOut(( DEB_CI_MOUNT, "   GUID_IO_VOLUME_DISMOUNT\n" ));
            else if ( GUID_IO_VOLUME_MOUNT    == pDevBroadcastHandle->dbch_eventguid )
                ciDebugOut(( DEB_CI_MOUNT, "   GUID_IO_VOLUME_MOUNT\n" ));
            else if ( GUID_IO_MEDIA_ARRIVAL   == pDevBroadcastHandle->dbch_eventguid )
                ciDebugOut(( DEB_CI_MOUNT, "   GUID_IO_MEDIA_ARRIVAL\n" ));
            else if ( GUID_IO_MEDIA_REMOVAL   == pDevBroadcastHandle->dbch_eventguid )
                ciDebugOut(( DEB_CI_MOUNT, "   GUID_IO_MEDIA_REMOVAL\n" ));
            else
                ciDebugOut(( DEB_CI_MOUNT, "   eventguid: {%08lx-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x}\n",
                             pDevBroadcastHandle->dbch_eventguid.Data1,
                             pDevBroadcastHandle->dbch_eventguid.Data2,
                             pDevBroadcastHandle->dbch_eventguid.Data3,
                             pDevBroadcastHandle->dbch_eventguid.Data4[0], pDevBroadcastHandle->dbch_eventguid.Data4[1],
                             pDevBroadcastHandle->dbch_eventguid.Data4[2], pDevBroadcastHandle->dbch_eventguid.Data4[3],
                             pDevBroadcastHandle->dbch_eventguid.Data4[4], pDevBroadcastHandle->dbch_eventguid.Data4[5],
                             pDevBroadcastHandle->dbch_eventguid.Data4[6], pDevBroadcastHandle->dbch_eventguid.Data4[7] ));
        }
    }
    else
    {
        ciDebugOut(( DEB_CI_MOUNT, "   handle: %#x\n", pDevBroadcastHandle->dbch_handle ));
        ciDebugOut(( DEB_CI_MOUNT, "   hdev_notify: %#x\n", pDevBroadcastHandle->dbch_hdevnotify ));
        ciDebugOut(( DEB_CI_MOUNT, "   nameoffset: %#x\n", pDevBroadcastHandle->dbch_nameoffset ));

        if ( GUID_IO_VOLUME_LOCK_FAILED == pDevBroadcastHandle->dbch_eventguid )
            ciDebugOut(( DEB_CI_MOUNT, "    GUID_IO_VOLUME_LOCK_FAILED\n" ));
        else if ( GUID_IO_VOLUME_DISMOUNT_FAILED == pDevBroadcastHandle->dbch_eventguid )
            ciDebugOut(( DEB_CI_MOUNT, "    GUID_IO_VOLUME_DISMOUNT_FAILED\n" ));
        else if ( GUID_IO_VOLUME_LOCK == pDevBroadcastHandle->dbch_eventguid )
            ciDebugOut(( DEB_CI_MOUNT, "    GUID_IO_VOLUME_LOCK\n" ));
        else if ( GUID_IO_VOLUME_UNLOCK == pDevBroadcastHandle->dbch_eventguid )
            ciDebugOut(( DEB_CI_MOUNT, "    GUID_IO_VOLUME_UNLOCK\n" ));
        else if ( GUID_IO_VOLUME_DISMOUNT == pDevBroadcastHandle->dbch_eventguid )
            ciDebugOut(( DEB_CI_MOUNT, "    GUID_IO_VOLUME_DISMOUNT\n" ));
        else if ( GUID_IO_VOLUME_MOUNT == pDevBroadcastHandle->dbch_eventguid )
            ciDebugOut(( DEB_CI_MOUNT, "    GUID_IO_VOLUME_MOUNT\n" ));
        else if ( GUID_IO_MEDIA_ARRIVAL == pDevBroadcastHandle->dbch_eventguid )
            ciDebugOut(( DEB_CI_MOUNT, "   GUID_IO_MEDIA_ARRIVAL\n" ));
        else if ( GUID_IO_MEDIA_REMOVAL == pDevBroadcastHandle->dbch_eventguid )
            ciDebugOut(( DEB_CI_MOUNT, "   GUID_IO_MEDIA_REMOVAL\n" ));
        else
            ciDebugOut(( DEB_CI_MOUNT, "   eventguid: {%08lx-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x}\n",
                         pDevBroadcastHandle->dbch_eventguid.Data1,
                         pDevBroadcastHandle->dbch_eventguid.Data2,
                         pDevBroadcastHandle->dbch_eventguid.Data3,
                         pDevBroadcastHandle->dbch_eventguid.Data4[0], pDevBroadcastHandle->dbch_eventguid.Data4[1],
                         pDevBroadcastHandle->dbch_eventguid.Data4[2], pDevBroadcastHandle->dbch_eventguid.Data4[3],
                         pDevBroadcastHandle->dbch_eventguid.Data4[4], pDevBroadcastHandle->dbch_eventguid.Data4[5],
                         pDevBroadcastHandle->dbch_eventguid.Data4[6], pDevBroadcastHandle->dbch_eventguid.Data4[7] ));
    }

    return sc;
} //ProcessCustomEvent

//+----------------------------------------------------------------------------
//
//  Function:   CiSvcMsgProc
//
//  Synopsis:   Message handler for Ci service
//
//  Arguments:  [dwControl] - the message.
//
//  Returns:    Nothing
//
//  History:    06-Jun-94   DwightKr    Created
//              06-23-98    KitmanH     Updated for RegisterServiceCtrlHandlerEx
//              07-30-98    KitmanH     Return appropriate errors
//
//  Notes:      We need to keep the status between calls to this routine
//              since the service control manager may query the current
//              status at any time.
//
//-----------------------------------------------------------------------------

DWORD WINAPI CiSvcMsgProc( DWORD dwControl, 
                           DWORD dwEventType, 
                           PVOID pEventData, 
                           PVOID pContext )
{
    ciDebugOut(( DEB_ITRACE,
                 "Ci 0Service: Executing service control command 0x%x\n",
                 dwControl ));
    
    Win4Assert( pContext );

    BOOL fShutdown = FALSE;
    DWORD dwError = NO_ERROR;
    
    TRY
    {
        switch (dwControl)
        {
        case SERVICE_CONTROL_STOP:
        case SERVICE_CONTROL_SHUTDOWN:
            UpdateServiceStatus( NO_ERROR );

            if ( SERVICE_STOP_PENDING != dwCiSvcStatus )
            {
                ciDebugOut(( DEB_ITRACE, "About to stop\n" ));
                dwCiSvcStatus = SERVICE_STOP_PENDING;

                if ( ! g_fSCMThreadIsGone )
                    StopCiSvcWork( eNetStop );
            }
            ciDebugOut( (DEB_ITRACE, "Ci Service: Done shutting down service\n" ));

            //
            // Calling UpdateServiceStatus() after doing the shutdown is
            // causing the service to hang. Not calling solved the problem
            // and so I am preventing it from being called.
            //
            fShutdown = TRUE;
            break;

        case SERVICE_CONTROL_PAUSE:
            if ( SERVICE_PAUSED != dwCiSvcStatus &&
                 SERVICE_STOP_PENDING != dwCiSvcStatus )
            {
                ciDebugOut(( DEB_ITRACE, "About to pause\n" ));
                if ( ! g_fSCMThreadIsGone )
                    StopCiSvcWork( eNetPause );
                dwCiSvcStatus = SERVICE_PAUSED;
                ciDebugOut(( DEB_ITRACE, "Ci Service: Done pausing the service\n" ));
            }
            break;

        case SERVICE_CONTROL_CONTINUE:
            if ( SERVICE_PAUSED == dwCiSvcStatus &&
                 SERVICE_STOP_PENDING != dwCiSvcStatus )
            {
                ciDebugOut(( DEB_ITRACE, "About to continue\n" ));
                if ( ! g_fSCMThreadIsGone )
                    StopCiSvcWork( eNetContinue );
                dwCiSvcStatus = SERVICE_RUNNING;
            }
            ciDebugOut(( DEB_ITRACE, "Ci Service: Done continuing the service\n" ));
            break;

        case SERVICE_CONTROL_DEVICEEVENT:
            // a dismount or remount may have occurred

            ciDebugOut(( DEB_ITRACE, "SERVICE_CONTROL_DEVICEEVENT received, event = %#x\n",
                         dwEventType ));

            {
                SCODE sc = S_OK;

                if ( DBT_CUSTOMEVENT == dwEventType )
                {
                    ciDebugOut(( DEB_ITRACE, "It is a custom event\n" ));
                    if ( ! g_fSCMThreadIsGone )
                        sc = ProcessCustomEvent( pEventData, pContext );
                    ciDebugOut(( DEB_ITRACE, "Done processing custom event\n" ));
                }

                // convert sc into a WIN32 error and return it
                ciDebugOut(( DEB_ITRACE, "Process Custom Event returned sc = %#x\n", sc ));
                    
                dwError = HRESULTTOWIN32(sc);
            }
            break;

        case SERVICE_CONTROL_INTERROGATE:
            break;

        default:
            ciDebugOut(( DEB_CI_MOUNT, "service control not implemented %d\n", dwControl ));
            dwError = ERROR_CALL_NOT_IMPLEMENTED;
            break;
        }

        UpdateServiceStatus( NO_ERROR );
    }
    CATCH (CException, e)
    {
        ciDebugOut(( DEB_ITRACE, "Ci Service: Error from callback = 0x%x\n", e.GetErrorCode() ));
    }
    END_CATCH

    ciDebugOut(( DEB_ITRACE, "Getting out of CiSvcMsgProc\n" ));
   
    return dwError;
} //CiSvcMsgProc

//+-------------------------------------------------------------------------
//
//  Function:   CiServiceMain, public
//
//  Purpose:    Service entry point
//
//  Arguments:  [dwNumServiceArgs] - number of arguments passed
//              [awcsServiceArgs]  - arguments
//
//  History:    06-Jun-94   DwightKr    Created
//
//--------------------------------------------------------------------------

extern CEventSem * g_pevtPauseContinue;

void CiSvcMain(
    DWORD    dwNumServiceArgs,
    LPWSTR * awcsServiceArgs )
{
    // The service control manager crofted up this thread, so we have to
    // establish the exception state, etc.

    CTranslateSystemExceptions translate;
    TRY
    {
        CDrvNotifArray DrvNotifArray;

        ciDebugOut( (DEB_ITRACE, "Ci Service: Attempting to register service\n" ));

        // Register service handler with service controller

        g_hTheCiSvc = RegisterServiceCtrlHandlerEx( wcsCiSvcName,
                                                    CiSvcMsgProc,
                                                    &DrvNotifArray );
        if (0 == g_hTheCiSvc)
        {
            ciDebugOut(( DEB_ERROR, "Unable to register ci service\n" ));
            THROW( CException( E_FAIL ) );
        }

        CEventSem evtPauseContinue;
        g_pevtPauseContinue = &evtPauseContinue;

        UpdateServiceStatus( NO_ERROR );

        CRegAccess reg( RTL_REGISTRY_CONTROL, wcsRegAdmin );

        if ( 1 != reg.Read( wcsPreventCisvcParam, (ULONG) 0 ) )
        {
            dwCiSvcStatus = SERVICE_RUNNING;
            UpdateServiceStatus( NO_ERROR );

            #if CIDBG == 1
                BOOL fRun = TRUE;   // FALSE --> Stop

                TRY
                {
                    ULONG ulVal = reg.Read( L"StopCiSvcOnStartup", (ULONG)0 );

                    if ( 1 == ulVal )
                        fRun = FALSE;
                }
                CATCH( CException, e )
                {
                }
                END_CATCH;

                unsigned long OldWin4AssertLevel = SetWin4AssertLevel(ASSRT_MESSAGE | ASSRT_POPUP);

                Win4Assert( fRun );

                SetWin4AssertLevel( OldWin4AssertLevel );
            #endif // CIDBG

            // Register for pnp notifications on drives used by catalogs

            DrvNotifArray.RegisterCatForNotifInRegistry();

            //
            // Register for pnp notifications on removable drives not yet
            // registered if the registry flag says it's appropriate.
            //

            if ( 0 != reg.Read( wcsMountRemovableCatalogs,
                                CI_AUTO_MOUNT_CATALOGS_DEFAULT ) )
                DrvNotifArray.RegisterRemovableDrives();

            StartCiSvcWork( DrvNotifArray );

            DrvNotifArray.UnregisterDeviceNotifications();

            ciDebugOut(( DEB_ITRACE, "service_stopped from CiSvcMain\n" ));
        }
        else
        {
            CEventLog eventLog( NULL, wcsCiEventSource );
            CEventItem item( EVENTLOG_INFORMATION_TYPE,
                             CI_SERVICE_CATEGORY,
                             MSG_CI_SERVICE_SUPPRESSED,
                             0 );
            eventLog.ReportEvent( item );
        }

        dwCiSvcStatus = SERVICE_STOPPED;
        UpdateServiceStatus( NO_ERROR );
        ciDebugOut(( DEB_ITRACE, "Shutdown is done\n" ));

        CoFreeUnusedLibraries();

        ciDebugOut( (DEB_ITRACE, "Ci Service: Leaving CiSvcMain()\n" ));
    }
    CATCH (CException, e)
    {
        ciDebugOut( (DEB_ITRACE, "Ci Service: Detected error 0x%x\n", e.GetErrorCode() ));
    }
    END_CATCH

    g_pevtPauseContinue = 0;
    g_fSCMThreadIsGone = TRUE;
} //CiSvcMain

//+----------------------------------------------------------------------------
//
//  Function:   SvcEntry_CiSvc
//
//  Synopsis:   Entry from services.exe
//
//              This is currently broken since services doesn't unload
//              query.dll when the service is stopped, and our global
//              variables don't expect to be used again when the service
//              is restarted.  It's probably a week of work to fix this!
//              We don't do this anyway so it doesn't matter.
//
//  History:    05-Jan-97   dlee    Created
//
//-----------------------------------------------------------------------------

void SvcEntry_CiSvc(
    DWORD    NumArgs,
    LPWSTR * ArgsArray,
    void *   pSvcsGlobalData,
    HANDLE   SvcRefHandle )
{
    CiSvcMain( NumArgs, ArgsArray );
} //SvcEntry_CiSvc