/*++ Copyright (c) 1994 Microsoft Corporation Module Name : instance.cxx Abstract: Defines the functions for TCP services Info class. This module is intended to capture the common scheduler code for the tcp services ( especially internet services) which involves the Service Controller dispatch functions. Also this class provides an interface for common dll of servers. Author: Murali R. Krishnan ( MuraliK ) 15-Nov-1994 Project: Internet Servers Common DLL --*/ #include "tcpdllp.hxx" #include #include #include #include #include "inetreg.h" #include "tcpcons.h" #include "apiutil.h" #include #include #include /************************************************************ * Symbolic Constants ************************************************************/ // // LOCAL Functions // static ULONGLONG InetServiceIdForService( IN DWORD serviceId); #define MAX_ADDRESSES_SUPPORTED 20 #define SIZEOF_IP_SEC_LIST( IPList ) (sizeof(INET_INFO_IP_SEC_LIST) + \ (IPList)->cEntries * \ sizeof(INET_INFO_IP_SEC_ENTRY)) IIS_SERVER_INSTANCE::IIS_SERVER_INSTANCE( IN PIIS_SERVICE pService, IN DWORD dwInstanceId, IN USHORT sPort, IN LPCSTR lpszRegParamKey, IN LPWSTR lpwszAnonPasswordSecretName, IN LPWSTR lpwszVirtualRootsSecretName, IN BOOL fMigrateVroots ) /*++ Desrcription: Contructor for IIS_SERVER_INSTANCE class. This constructs a new service info object for the service specified. Arguments: pService - pointer to the service object. dwInstanceId - Instance number of this instance. sPort - Default port number lpszRegParamKey fully qualified name of the registry key that contains the common service data for this server lpszAnonPasswordSecretName The name of the LSA secret the anonymous password is stored under lpszVirtualRootsSecretName The name of the LSA secret the virtual root passwords are stored under On success it initializes all the members of the object, inserts itself to the global list of service info objects and returns with success. Note: The caller of this function should check the validity by invoking the member function IsValid() after constructing this object. --*/ : m_tslock ( ), m_SecureBinding ( NULL ), m_fZapRegKey ( FALSE), m_fDoServerNameCheck ( FALSE), m_reference ( 0), m_sSecurePort ( 0), m_sDefaultPort ( sPort ), m_dwServerState ( MD_SERVER_STATE_STOPPED), m_dwSavedState ( MD_SERVER_STATE_STOPPED), m_Service ( pService), m_instanceId ( dwInstanceId), m_strParametersKey ( lpszRegParamKey), m_dwAnonAcctDescLen ( 0), m_cReadLocks ( 0), m_strMDPath ( ), m_strMDVirtualRootPath( ), m_dwMaxConnections ( INETA_DEF_MAX_CONNECTIONS), m_dwMaxEndpointConnections( INETA_DEF_MAX_ENDPOINT_CONNECTIONS ), m_dwCurrentConnections( 0), m_dwConnectionTimeout ( INETA_DEF_CONNECTION_TIMEOUT), m_dwServerSize ( INETA_DEF_SERVER_SIZE), m_nAcceptExOutstanding( INETA_DEF_ACCEPTEX_OUTSTANDING), m_AcceptExTimeout ( INETA_DEF_ACCEPTEX_TIMEOUT), m_Logging( pService->QueryServiceName(), dwInstanceId ), m_dwLevelsToScan ( INETA_DEF_LEVELS_TO_SCAN ), m_fAddedToServerInstanceList( FALSE ) { DBG_ASSERT( lpszRegParamKey != NULL ); IF_DEBUG(INSTANCE) { DBGPRINTF( ( DBG_CONTEXT,"Creating iis instance %x[%u]. \n", this, dwInstanceId)); } // // Limit PWS connections // if ( !TsIsNtServer() ) { m_dwMaxConnections = INETA_DEF_MAX_CONNECTIONS_PWS; } // // initialize locks // InitializeCriticalSection(&m_csLock); // // initialize binding support // InitializeListHead( &m_BindingListHead ); // // reference the service // if ( !pService->CheckAndReference( )) { goto error_exit; } m_Service = pService; // // Set cache parameters // m_tsCache.SetParameters( pService->QueryServiceId(), dwInstanceId, this ); // // Set the metadatabase path // if ( QueryInstanceId() == INET_INSTANCE_ROOT ) { DBG_ASSERT( FALSE ); } else { CHAR szTemp[32]; wsprintf(szTemp,"/%s/%s/%d", IIS_MD_LOCAL_MACHINE_PATH, pService->QueryServiceName(), QueryInstanceId()); m_strMDPath.Copy(szTemp); wsprintf(szTemp,"/%s/%s/%d/%s/", IIS_MD_LOCAL_MACHINE_PATH, pService->QueryServiceName(), QueryInstanceId(), IIS_MD_INSTANCE_ROOT); m_strMDVirtualRootPath.Copy(szTemp); if ( fMigrateVroots ) { MoveVrootFromRegToMD(); } if ( QueryInstanceId() == 1 ) { if ( !MoveMDVroots2Registry() ) { PdcHackVRReg2MD( ); } } } // // Read common parameters. // if ( !RegReadCommonParams() ) { goto error_exit; } // // Start logging // m_Logging.InitializeInstance( m_strMDPath.QueryStr(), m_Service->QueryMDObject() ); m_Logging.CreateLog(); m_Logging.Active(); // // link this to the service // if ( dwInstanceId != INET_INSTANCE_ROOT ) { if ( !pService->AddServerInstance( this ) ) { DBG_ASSERT(m_reference == 0); goto error_exit; } DBG_ASSERT(m_reference == 1); } m_fAddedToServerInstanceList = TRUE; return; error_exit: m_dwServerState = MD_SERVER_STATE_INVALID; DBG_ASSERT(m_reference == 0); return; } // IIS_SERVER_INSTANCE::IIS_SERVER_INSTANCE() IIS_SERVER_INSTANCE::~IIS_SERVER_INSTANCE( VOID) /*++ Description: Cleanup the instance object. If the service is not already terminated, it terminates the service before cleanup. Arguments: None Returns: None --*/ { DBG_ASSERT(m_dwServerState != MD_SERVER_STATE_STARTED); DBG_ASSERT(m_reference == 0); // // start cleanup // DBG_REQUIRE( m_Logging.TerminateLogging()); // // If we failed to create this instance or it's getting deleted, remove // the configuration tree // if ( m_fZapRegKey ) { DBGPRINTF((DBG_CONTEXT,"Zapping reg key for %x\n",this)); ZapRegistryKey( NULL, QueryRegParamKey() ); ZapInstanceMBTree( ); } // // endpoints should have been dereferenced // DBG_ASSERT(m_SecureBinding == NULL); DBG_ASSERT(IsListEmpty( &m_BindingListHead )); // // dereference the service // if ( m_fAddedToServerInstanceList && m_Service != NULL ) { m_Service->Dereference( ); } DeleteCriticalSection(&m_csLock); } // IIS_SERVER_INSTANCE::~IIS_SERVER_INSTANCE() # if DBG VOID IIS_SERVER_INSTANCE::Print( VOID) const { IIS_SERVER_INSTANCE::Print(); DBGPRINTF( ( DBG_CONTEXT, " Printing IIS_SERVER_INSTANCE object ( %08x) \n" " State = %u.\n" , this, m_dwServerState )); DBGPRINTF(( DBG_CONTEXT, " Server Admin Params: \n" " Log Anon = %u. Log NonAnon = %u.\n" , m_fLogAnonymous, m_fLogNonAnonymous )); DBGPRINTF(( DBG_CONTEXT, " Printing IIS_SERVER_INSTANCE object (%08x)\n" " Readers # = %u.\n" " Reg Parameters Key = %s\n" " MaxConn = %d. ConnTimeout = %u secs.\n" , this, m_cReadLocks, m_strParametersKey.QueryStr(), m_dwMaxConnections, m_dwConnectionTimeout )); return; } // IIS_SERVER_INSTANCE::Print() #endif // DBG VOID IIS_SERVER_INSTANCE::ZapInstanceMBTree( VOID ) { MB mb( (IMDCOM*) m_Service->QueryMDObject() ); // // Do the metabase // IF_DEBUG(METABASE) { DBGPRINTF((DBG_CONTEXT,"Deleting metabase node %s\n", QueryMDPath())); } if ( !mb.Open( "/", METADATA_PERMISSION_READ | METADATA_PERMISSION_WRITE )) { IF_DEBUG(METABASE) { DBGPRINTF((DBG_CONTEXT,"Open MD instance root %s returns %d\n", "/", GetLastError() )); } return; } // // Delete the instance tree // if ( !mb.DeleteObject( QueryMDPath() )) { IF_DEBUG(METABASE) { DBGPRINTF((DBG_CONTEXT, "Deleting instance node %s returns %d\n", QueryMDPath(), GetLastError())); } } else { mb.Save(); } return; } // IIS_SERVER_INSTANCE::ZapInstanceMBTree DWORD IIS_SERVER_INSTANCE::BindInstance( VOID ) /*++ Routine Description: Binds an instance to all configured endpoints (normal & secure). Arguments: None. Return Value: DWORD - Completion status, 0 if successful, !0 otherwise. --*/ { DWORD err; // // Update the "normal" (i.e. non-secure) bindings. // err = UpdateNormalBindings(); if( err != NO_ERROR ) { IF_DEBUG(INSTANCE) { DBGPRINTF((DBG_CONTEXT, "UpdateNormalBindings() failed, %lu\n", err)); } return err; } // // Update the secure bindings. // err = UpdateSecureBindings(); if( err != NO_ERROR ) { IF_DEBUG(INSTANCE) { DBGPRINTF((DBG_CONTEXT, "UpdateSecureBindings() failed, %lu\n", err)); } // // The main port(s) are OK, but the SSL port(s) failed, // so start anyway. // err = NO_ERROR; } // // Success! // DBG_ASSERT( err == NO_ERROR ); return NO_ERROR; } // IIS_SERVER_INSTANCE::BindInstance DWORD IIS_SERVER_INSTANCE::UnbindInstance( VOID ) /*++ Routine Description: Removes all bindings from an instance. Arguments: None. Return Value: DWORD - Completion status, 0 if successful, !0 otherwise. --*/ { PLIST_ENTRY listEntry; PIIS_SERVER_BINDING binding; LockThisForWrite(); // // Walk the list of normal bindings and destroy them. // while( !IsListEmpty( &m_BindingListHead ) ) { listEntry = RemoveHeadList( &m_BindingListHead ); binding = CONTAINING_RECORD( listEntry, IIS_SERVER_BINDING, m_BindingListEntry ); IF_DEBUG( INSTANCE ) { DBGPRINTF(( DBG_CONTEXT, "unbinding %lx from %lx, binding %lx (%lx:%d:%s)\n", binding->QueryEndpoint(), this, binding, binding->QueryIpAddress(), binding->QueryEndpoint()->QueryPort(), binding->QueryHostName() )); } binding->QueryEndpoint()->RemoveInstance( this, binding->QueryIpAddress(), binding->QueryHostName() ); binding->QueryEndpoint()->Dereference(); delete binding; } // // Remove from secure endpoint list if necessary. // if( m_SecureBinding != NULL ) { m_SecureBinding->QueryEndpoint()->RemoveInstance( this, m_SecureBinding->QueryIpAddress(), m_SecureBinding->QueryHostName() ); m_SecureBinding->QueryEndpoint()->Dereference(); delete m_SecureBinding; m_SecureBinding = NULL; } // // Success! // UnlockThis(); return NO_ERROR; } // IIS_SERVER_INSTANCE::UnbindInstance DWORD IIS_SERVER_INSTANCE::UpdateNormalBindings( VOID ) /*++ Routine Description: Reads the normal binding list from the metabase and incorporates any changes into the current binding configuration. Arguments: None. Return Value: DWORD - Completion status, 0 if successful, !0 otherwise. --*/ { MB mb( (IMDCOM*)m_Service->QueryMDObject() ); MULTISZ msz; DWORD status = NO_ERROR; const CHAR * scan; DWORD ipAddress; USHORT ipPort; const CHAR * hostName; PIIS_SERVER_BINDING binding; LIST_ENTRY createdBindings; PLIST_ENTRY listEntry; // // Setup locals. // InitializeListHead( &createdBindings ); // // Open the metabase and get the current binding list. // if( mb.Open( QueryMDPath() ) ) { if( !mb.GetMultisz( "", MD_SERVER_BINDINGS, IIS_MD_UT_SERVER, &msz ) ) { status = GetLastError(); if( status == MD_ERROR_DATA_NOT_FOUND ) { CHAR defaultBinding[sizeof(":65535:\0")]; INT length; // // The bindings are not in the registry, so create // a default wildcard binding based on the default // port. // length = wsprintf( defaultBinding, ":%u:", m_sDefaultPort ); length++; // account for terminator defaultBinding[length++] = '\0'; // add another terminator if( msz.Copy( defaultBinding, (DWORD)length ) ) { status = NO_ERROR; } else { status = GetLastError(); } } } // // Close the metabase before continuing, as anyone that needs // to update the service status will need write access. // mb.Close(); } else { status = GetLastError(); } // // Lock the instance. // LockThisForWrite(); // // Scan the multisz and look for instances we'll need to create. // if( status == NO_ERROR ) { for( scan = msz.First() ; scan != NULL ; scan = msz.Next( scan ) ) { // // Parse the descriptor (in "ip_address:port:host_name" form) // into its component parts. // status = IIS_SERVER_BINDING::ParseDescriptor( scan, &ipAddress, &ipPort, &hostName ); if( status == NO_ERROR ) { // // See if the descriptor is in our current binding list. // if( !IsInCurrentBindingList( ipAddress, ipPort, hostName ) ) { // // It's not, so we need to create a new binding. // IF_DEBUG( INSTANCE ) { DBGPRINTF(( DBG_CONTEXT, "Adding %lx:%d:%s\n", ipAddress, ipPort, hostName )); } DBG_CODE( binding = NULL ); status = CreateNewBinding( ipAddress, ipPort, hostName, FALSE, // IsSecure &binding ); if( status == NO_ERROR ) { // // Add the new binding to the local list of // newly created bindings. // DBG_ASSERT( binding != NULL ); InsertTailList( &createdBindings, &binding->m_BindingListEntry ); } else { // // Could not create the new binding. // goto fatal; } } } else { // // Could not parse the descriptor. We should probably // write something to the event log here. // DBGPRINTF(( DBG_CONTEXT, "UpdateNormalBindings: could not parse %s, error %lu\n", scan, status )); // // Press on regardless. // status = NO_ERROR; } } } // // Scan the existing bindings and look for those that need to // be deleted. // if( status == NO_ERROR ) { listEntry = m_BindingListHead.Flink; while( listEntry != &m_BindingListHead ) { binding = CONTAINING_RECORD( listEntry, IIS_SERVER_BINDING, m_BindingListEntry ); listEntry = listEntry->Flink; if( !IsBindingInMultiSz( binding, msz ) ) { // // Got one. Remove it from the instance list, dereference // the corresponding endpoint, then delete the binding. // IF_DEBUG( INSTANCE ) { DBGPRINTF(( DBG_CONTEXT, "zapping %lx from %lx, binding %lx (%lx:%d:%s)\n", binding->QueryEndpoint(), this, binding, binding->QueryIpAddress(), binding->QueryEndpoint()->QueryPort(), binding->QueryHostName() )); } binding->QueryEndpoint()->RemoveInstance( this, binding->QueryIpAddress(), binding->QueryHostName() ); RemoveEntryList( &binding->m_BindingListEntry ); binding->QueryEndpoint()->Dereference(); delete binding; } } } if( status == NO_ERROR ) { // // Move the newly created bindings over to the current binding // list. // m_BindingListHead.Blink->Flink = createdBindings.Flink; createdBindings.Flink->Blink = m_BindingListHead.Blink; createdBindings.Blink->Flink = &m_BindingListHead; m_BindingListHead.Blink = createdBindings.Blink; } UnlockThis(); return status; fatal: // // An unrecoverable error occurred, so loop through the local list // of newly created bindings and delete them. // DBG_ASSERT( status != NO_ERROR ); while( !IsListEmpty( &createdBindings ) ) { listEntry = RemoveHeadList( &createdBindings ); binding = CONTAINING_RECORD( listEntry, IIS_SERVER_BINDING, m_BindingListEntry ); IF_DEBUG( INSTANCE ) { DBGPRINTF(( DBG_CONTEXT, "zapping %lx from %lx, binding %lx (%lx:%d:%s) (ERROR)\n", binding->QueryEndpoint(), this, binding, binding->QueryIpAddress(), binding->QueryEndpoint()->QueryPort(), binding->QueryHostName() )); } binding->QueryEndpoint()->RemoveInstance( this, binding->QueryIpAddress(), binding->QueryHostName() ); binding->QueryEndpoint()->Dereference(); delete binding; } UnlockThis(); return status; } // IIS_SERVER_INSTANCE::UpdateNormalBindings DWORD IIS_SERVER_INSTANCE::UpdateSecureBindings( VOID ) /*++ Routine Description: Reads the secure binding list from the metabase and incorporates any changes into the current binding configuration. Arguments: None. Return Value: DWORD - Completion status, 0 if successful, !0 otherwise. --*/ { MB mb( (IMDCOM*)m_Service->QueryMDObject() ); DWORD securePort = 0; DWORD status = NO_ERROR; PIIS_SERVER_BINDING binding; // // Open the metabase and get the secure port. // if( mb.Open( QueryMDPath() ) ) { if( !mb.GetDword( "", MD_SECURE_PORT, IIS_MD_UT_SERVER, &securePort ) ) { securePort = 0; } // // Close the metabase before continuing, as anyone that needs // to update the service status will need write access. // mb.Close(); } else { status = GetLastError(); } // // Lock the instance. // LockThisForWrite(); // // See if the secure port has changed. // if( status == NO_ERROR ) { if( ( m_SecureBinding == NULL && securePort != 0 ) || ( m_SecureBinding != NULL && !m_SecureBinding->Compare( DEF_SECURE_IP_ADDRESS, (USHORT)securePort, DEF_SECURE_HOST_NAME ) ) ) { // // Special case for new port == 0, meaning that the // we're disabling secure access to the instance. // if( securePort == 0 ) { binding = NULL; goto RemoveBinding; } // // Mismatch. Create a new binding. // status = CreateNewBinding( DEF_SECURE_IP_ADDRESS, (USHORT)securePort, DEF_SECURE_HOST_NAME, TRUE, // IsSecure &binding ); if( status == NO_ERROR ) { RemoveBinding: if( m_SecureBinding != NULL ) { m_SecureBinding->QueryEndpoint()->RemoveInstance( this, m_SecureBinding->QueryIpAddress(), m_SecureBinding->QueryHostName() ); m_SecureBinding->QueryEndpoint()->Dereference(); delete m_SecureBinding; } m_SecureBinding = binding; } } } UnlockThis(); return status; } // IIS_SERVER_INSTANCE::UpdateSecureBindings DWORD IIS_SERVER_INSTANCE::CreateNewBinding( IN DWORD IpAddress, IN USHORT IpPort, IN const CHAR * HostName, IN BOOL IsSecure, OUT IIS_SERVER_BINDING ** NewBinding ) /*++ Routine Description: Creates a new binding object for the specified ip address, port, and host name, and creates/references the appropriate endpoint object. Arguments: IpAddress - The binding IP address. May be INADDR_ANY. IpPort - The binding IP port. Required. HostName - The binding host name. May be empty (""). IsSecure - TRUE for secure endpoints. Only used if a new endpoint is created. NewBinding - Receives a pointer to the new binding object if successful. Return Value: DWORD - Completion status, 0 if successful, !0 otherwise. --*/ { PIIS_ENDPOINT endpoint; PIIS_SERVER_BINDING binding; DWORD status; // // Sanity check. // DBG_ASSERT( IpPort != 0 ); DBG_ASSERT( HostName != NULL ); DBG_ASSERT( NewBinding != NULL ); // // Setup locals so we know how to cleanup on exit. // endpoint = NULL; binding = NULL; // // Try to find an endpoint for the specified port. // endpoint = m_Service->FindAndReferenceEndpoint( IpPort, TRUE, // CreateIfNotFound IsSecure ); if( endpoint != NULL ) { // // Create a new binding. // binding = new IIS_SERVER_BINDING( IpAddress, IpPort, HostName, endpoint ); if( binding != NULL ) { if( endpoint->AddInstance( this, IpAddress, HostName ) ) { endpoint->Reference(); *NewBinding = binding; status = NO_ERROR; } else { // // Could not associate the instance with the endpoint. // status = GetLastError(); } } else { // // Could not create new binding object. // status = GetLastError(); } } else { // // Could not find & reference endpoint. // status = GetLastError(); } // // Remove the reference added in FindAndReferenceEndpoint(). // if( endpoint != NULL ) { endpoint->Dereference(); } // // Cleanup if necessary. // if( status != NO_ERROR ) { if( binding != NULL ) { delete binding; } } return status; } // IIS_SERVER_INSTANCE::CreateNewBinding BOOL IIS_SERVER_INSTANCE::IsInCurrentBindingList( IN DWORD IpAddress OPTIONAL, IN USHORT IpPort, IN const CHAR * HostName OPTIONAL ) /*++ Routine Description: Scans the current binding list looking for the specified IP address, port, and host name. Arguments: IpAddress - The IP address to search for. May be INADDR_ANY. IpPort - The IP port to search for. Required. HostName - The host name to search for. May be empty (""). Return Value: BOOL - TRUE if the binding was found, FALSE otherwise. --*/ { PLIST_ENTRY listEntry; PIIS_SERVER_BINDING binding; // // Sanity check. // DBG_ASSERT( IpPort != 0 ); DBG_ASSERT( HostName != NULL ); // // Scan the bindings. // for( listEntry = m_BindingListHead.Flink ; listEntry != &m_BindingListHead ; listEntry = listEntry->Flink ) { binding = CONTAINING_RECORD( listEntry, IIS_SERVER_BINDING, m_BindingListEntry ); if( binding->Compare( IpAddress, IpPort, HostName ) ) { return TRUE; } } return FALSE; } // IIS_SERVER_INSTANCE::IsInCurrentBindingList BOOL IIS_SERVER_INSTANCE::IsBindingInMultiSz( IN PIIS_SERVER_BINDING Binding, IN const MULTISZ &msz ) /*++ Routine Description: Scans the specified MULTISZ object to see if it contains a descriptor matching the specified binding object. Arguments: Binding - The binding to search for. msz - The MULTISZ to search. Return Value: DWORD - Completion status, 0 if successful, !0 otherwise. --*/ { const CHAR * scan; DWORD status; BOOL result; // // Sanity check. // DBG_ASSERT( Binding != NULL ); // // Scan the MULTISZ. // for( scan = msz.First() ; scan != NULL ; scan = msz.Next( scan ) ) { status = Binding->Compare( scan, &result ); if( status == NO_ERROR && result ) { return TRUE; } } return FALSE; } // IIS_SERVER_INSTANCE::IsBindingInMultiSz DWORD IIS_SERVER_INSTANCE::PerformStateChange( VOID ) /*++ Routine Description: Reads the server instance state from the metabase and performs any necessary state changes. Arguments: None. Return Value: DWORD - Completion status, 0 if successful, !0 otherwise. --*/ { MB mb( (IMDCOM *)m_Service->QueryMDObject() ); DWORD status; DWORD requestedState; DWORD currentState; DWORD serviceState; BOOL needToUpdateState; // // Setup locals. // status = NO_ERROR; needToUpdateState = TRUE; serviceState = m_Service->QueryCurrentServiceState(); currentState = QueryServerState(); // // Open the metabase and query the new state. // if( mb.Open( QueryMDPath(), METADATA_PERMISSION_READ ) ) { if( !mb.GetDword( "", MD_SERVER_STATE, IIS_MD_UT_SERVER, &requestedState ) ) { status = GetLastError(); IF_DEBUG( INSTANCE ) { DBGPRINTF(( DBG_CONTEXT, "PerformStateChange: cannot read server state, error %lu\n", status )); } } // // Close it so that code needed to update the metabase when // changing state can indeed open the metabase. // mb.Close(); } else { status = GetLastError(); IF_DEBUG( INSTANCE ) { DBGPRINTF(( DBG_CONTEXT, "PerformStateChange: cannot open metabase for READ, error %lu\n", status )); } } // // Lock the instance. // LockThisForWrite(); // // Interpret the state change. // if( status == NO_ERROR ) { switch( requestedState ) { case MD_SERVER_STATE_STARTING : // // Starting the service. If it's already running, there's // nothing to do. If it's stopped, then start it. If it's // in any other state, this is an invalid state transition. // // Note that the *service* must be running before an instance // can be started. // if( currentState == MD_SERVER_STATE_STARTED ) { break; } else if( serviceState == SERVICE_RUNNING && currentState == MD_SERVER_STATE_STOPPED ) { status = StartInstance( ¤tState ); if( status != NO_ERROR ) { DBGPRINTF(( DBG_CONTEXT, "PerformStateChange: cannot start instance, error %lu\n", status )); } } else { DBGPRINTF(( DBG_CONTEXT, "PerformStateChange: invalid state transition: %lu to %lu\n", currentState, requestedState )); status = ERROR_INVALID_SERVICE_CONTROL; } break; case MD_SERVER_STATE_STOPPING : // // Stopping the service. If it's already stopped, there's // nothing to do. If it's running or paused, then start it. // If it's in any other state, this is an invalid state // transition. // // Note that the *service* must be either running or paused // before an instance can be paused. // if( currentState == MD_SERVER_STATE_STOPPED ) { break; } else if( ( serviceState == SERVICE_RUNNING || serviceState == SERVICE_PAUSED ) && ( currentState == MD_SERVER_STATE_STARTED || currentState == MD_SERVER_STATE_PAUSED ) ) { status = StopInstance( ¤tState ); if( status != NO_ERROR ) { DBGPRINTF(( DBG_CONTEXT, "PerformStateChange: cannot stop instance, error %lu\n", status )); } } else { DBGPRINTF(( DBG_CONTEXT, "PerformStateChange: invalid state transition: %lu to %lu\n", currentState, requestedState )); status = ERROR_INVALID_SERVICE_CONTROL; } break; case MD_SERVER_STATE_PAUSING : // // Pausing the service. If it's already paused, there's // nothing to do. If it's running, then pause it. If it's // in any other state, this is an invalid state transition. // // Note that the *service* must be running before an instance // can be paused. // if( currentState == MD_SERVER_STATE_PAUSED ) { break; } else if( serviceState == SERVICE_RUNNING && currentState == MD_SERVER_STATE_STARTED ) { status = PauseInstance( ¤tState ); if( status != NO_ERROR ) { DBGPRINTF(( DBG_CONTEXT, "PerformStateChange: cannot pause instance, error %lu\n", status )); } } else { DBGPRINTF(( DBG_CONTEXT, "PerformStateChange: invalid state transition: %lu to %lu\n", currentState, requestedState )); status = ERROR_INVALID_SERVICE_CONTROL; } break; case MD_SERVER_STATE_CONTINUING : // // Continuing the service. If it's already running, there's // nothing to do. If it's paused, then continue it. If it's // in any other state, this is an invalid state transition. // // Note that the *service* must be running before an instance // can be continued. // if( currentState == MD_SERVER_STATE_STARTED ) { break; } else if( serviceState == SERVICE_RUNNING && currentState == MD_SERVER_STATE_PAUSED ) { status = ContinueInstance( ¤tState ); if( status != NO_ERROR ) { DBGPRINTF(( DBG_CONTEXT, "PerformStateChange: cannot continue instance, error %lu\n", status )); } } else { DBGPRINTF(( DBG_CONTEXT, "PerformStateChange: invalid state transition: %lu to %lu\n", currentState, requestedState )); status = ERROR_INVALID_SERVICE_CONTROL; } break; case MD_SERVER_STATE_STARTED : case MD_SERVER_STATE_STOPPED : case MD_SERVER_STATE_PAUSED : if( currentState == requestedState ) { // // This is a false notification; ignore it. // needToUpdateState = FALSE; } else { DBGPRINTF(( DBG_CONTEXT, "PerformStateChange: invalid state transition: %lu to %lu\n", currentState, requestedState )); status = ERROR_INVALID_SERVICE_CONTROL; } break; default : DBGPRINTF(( DBG_CONTEXT, "PerformStateChange: invalid state %lu\n", requestedState )); status = ERROR_INVALID_SERVICE_CONTROL; break; } } else { DBGPRINTF(( DBG_CONTEXT, "PerformStateChange: cannot read metabase, error %lu\n", status )); } // // Unlock the instance before trying to reopen the metabase. // UnlockThis(); if( needToUpdateState ) { // // Update the server state and completion status. // SetServerState( currentState, status ); } return status; } // IIS_SERVER_INSTANCE::PerformStateChange VOID IIS_SERVER_INSTANCE::SetServerState( IN DWORD NewState, IN DWORD Win32Error ) /*++ Routine Description: Sets the new server state, storing it locally and also storing the new state in the metabase. Arguments: NewState - The new server state. Win32Error - New Win32 error value. Return Value: None. --*/ { DWORD status = NO_ERROR; MB mb( (IMDCOM *)m_Service->QueryMDObject() ); // // Open the metabase and save the new state. Note that we map // MD_SERVER_STATE_INVALID to MD_SERVER_STATE_STOPPED in the metabase. // Client applications would probably be confused by the _INVALID state. // if( mb.Open( QueryMDPath(), METADATA_PERMISSION_READ | METADATA_PERMISSION_WRITE ) ) { if( !mb.SetDword( "", MD_WIN32_ERROR, IIS_MD_UT_SERVER, Win32Error ) || !mb.SetDword( "", MD_SERVER_STATE, IIS_MD_UT_SERVER, NewState == MD_SERVER_STATE_INVALID ? MD_SERVER_STATE_STOPPED : NewState ) ) { status = GetLastError(); } } else { status = GetLastError(); } if( status != NO_ERROR ) { DBGPRINTF(( DBG_CONTEXT, "SetServerState: cannot write metabase (%lu), error %lu\n", NewState, status )); } // // Save it in the object also. // m_dwServerState = NewState; } // IIS_SERVER_INSTANCE::SetServerState BOOL IIS_SERVER_INSTANCE::CloseInstance( VOID ) /*++ Routine Description: Shuts down instance Arguments: None Return Value: TRUE if Shutdown successful, FALSE otherwise --*/ { IF_DEBUG(INSTANCE) { DBGPRINTF(( DBG_CONTEXT, "IIS_SERVER_INSTANCE::Close called for %x\n", this )); } (VOID)m_Service->DisassociateInstance( this ); return TRUE; } // IIS_SERVER_INSTANCE::CloseInstance DWORD IIS_SERVER_INSTANCE::StartInstance( LPDWORD NewState ) /*++ Routine Description: Sets instance to RUNNING Arguments: NewState - Receives the new state. Return Value: DWORD - 0 if successful, !0 otherwise. --*/ { DWORD status; IF_DEBUG(INSTANCE) { DBGPRINTF(( DBG_CONTEXT, "IIS_SERVER_INSTANCE::StartInstance called for %x. Current state %d\n", this, QueryServerState() )); } DBG_ASSERT( QueryServerState() == MD_SERVER_STATE_STOPPED ); // // Bind the instance. // status = BindInstance(); if( status == NO_ERROR ) { // // Update the server state. // *NewState = MD_SERVER_STATE_STARTED; } return status; } // IIS_SERVER_INSTANCE::StartInstance DWORD IIS_SERVER_INSTANCE::StopInstance( LPDWORD NewState ) /*++ Routine Description: Sets instance to STOPPED Arguments: NewState - Receives the new state. Return Value: DWORD - 0 if successful, !0 otherwise. --*/ { DWORD status; IF_DEBUG(INSTANCE) { DBGPRINTF(( DBG_CONTEXT, "IIS_SERVER_INSTANCE::StopInstance called for %x. Current state %d\n", this, QueryServerState() )); } DBG_ASSERT( QueryServerState() == MD_SERVER_STATE_STARTED || QueryServerState() == MD_SERVER_STATE_PAUSED ); // // Set the state to STOPPED *before* unbinding to prevent a // race condition with incoming connections. // // Note that we call DisconnectUsersByInstance() before *and* after // unbinding the instance. This is to prevent a potential race condition // that can occur if another thread is already in IIS_ENDPOINT:: // FindAndReferenceInstance(), has found the instance, checked its state, // and found it to be MD_SERVER_STATE_STARTED. The call to UnbindInstance() // will lock any affected endpoints, ensuring that there are no other // threads in the midst of a FindAndReferenceInstance(). The second // (seemingly redundant) call to DisconnectUsersByInstance() will catch // any threads that "snuck in" under these conditions. // m_dwServerState = MD_SERVER_STATE_STOPPED; status = m_Service->DisconnectUsersByInstance( this ); if( status == NO_ERROR ) { status = UnbindInstance(); } if( status == NO_ERROR ) { status = m_Service->DisconnectUsersByInstance( this ); } if( status == NO_ERROR ) { *NewState = MD_SERVER_STATE_STOPPED; m_dwSavedState = MD_SERVER_STATE_STOPPED; } return status; } // IIS_SERVER_INSTANCE::StopInstance DWORD IIS_SERVER_INSTANCE::PauseInstance( LPDWORD NewState ) /*++ Routine Description: Sets instance to PAUSE Arguments: NewState - Receives the new state. Return Value: DWORD - 0 if successful, !0 otherwise. --*/ { IF_DEBUG(INSTANCE) { DBGPRINTF(( DBG_CONTEXT, "IIS_SERVER_INSTANCE::Pause called for %x. Current state %d\n", this, QueryServerState() )); } DBG_ASSERT( QueryServerState() == MD_SERVER_STATE_STARTED ); *NewState = MD_SERVER_STATE_PAUSED; return NO_ERROR; } // IIS_SERVER_INSTANCE::PauseInstance DWORD IIS_SERVER_INSTANCE::ContinueInstance( LPDWORD NewState ) /*++ Routine Description: Sets instance to STARTED. Arguments: NewState - Receives the new state. Return Value: DWORD - 0 if successful, !0 otherwise. --*/ { IF_DEBUG(INSTANCE) { DBGPRINTF(( DBG_CONTEXT, "IIS_SERVER_INSTANCE::Continue called for %x. Current state %d\n", this, QueryServerState() )); } DBG_ASSERT( QueryServerState() == MD_SERVER_STATE_PAUSED ); *NewState = MD_SERVER_STATE_STARTED; return NO_ERROR; } // IIS_SERVER_INSTANCE::ContinueInstance VOID IIS_SERVER_INSTANCE::SetWin32Error( DWORD err ) { MB mb( (IMDCOM *)m_Service->QueryMDObject() ); DWORD status = NO_ERROR; // // Open the metabase and save the error code. // if( mb.Open( QueryMDPath(), METADATA_PERMISSION_READ | METADATA_PERMISSION_WRITE ) ) { if( !mb.SetDword( "", MD_WIN32_ERROR, IIS_MD_UT_SERVER, err ) ) { status = GetLastError(); } } else { status = GetLastError(); } if( status != NO_ERROR ) { DBGPRINTF(( DBG_CONTEXT, "SetWin32Error: cannot save error %lu (%lx), error %lx\n", err, err, status )); } } // IIS_SERVER_INSTANCE::SetWin32Error