/* * Copyright (c) 2002 Apple Computer, Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /*! * @header CNetInfoPlugin */ #include #include #include // for syslog() to log calls #include #include #include #include #include #include "CNetInfoPlugin.h" #include "DSUtils.h" #include "CNiNodeList.h" #include "CNiPlugIn.h" #include "SharedConsts.h" #include "CString.h" #include "DSCThread.h" #include "DSEventSemaphore.h" #include "my_ni_pwdomain.h" #include "CSharedData.h" #include "CLog.h" #include "NiLib2.h" //PFIXextern dsBool gLocalNodeNotAvailable; /* * PRIVATE! * notifiation center SPI */ #ifdef __cplusplus extern "C" { #endif extern uint32_t notify_get_state(int token, int *state); extern uint32_t notify_register_plain(const char *name, int *out_token); #ifdef __cplusplus } #endif #define NETINFO_BINDING_KEY "com.apple.system.netinfo.local.binding_change" #define BINDING_STATE_UNBOUND 0 #define BINDING_STATE_BOUND 1 #define BINDING_STATE_NETROOT 2 // -------------------------------------------------------------------------------- // Globals DSEventSemaphore *gKickNodeRequests = nil; DSMutexSemaphore *gNetInfoMutex = nil; CNiNodeList *CNetInfoPlugin::fNiNodeList = nil; FourCharCode CNetInfoPlugin::fToken = 0; static void DoNIPINetworkChange(CFRunLoopTimerRef timer, void *info); void DoNIPINetworkChange(CFRunLoopTimerRef timer, void *info) { if ( info != nil ) { ((CNetInfoPlugin *)info)->ReDiscoverNetwork(); } }// DoNIPINetworkChange CFStringRef NetworkChangeNIPICopyStringCallback( const void *item ); CFStringRef NetworkChangeNIPICopyStringCallback( const void *item ) { return CFSTR("NetworkChangeinNetInfoModule"); } // -------------------------------------------------------------------------------- // * CNetInfoPlugin () // -------------------------------------------------------------------------------- CNetInfoPlugin::CNetInfoPlugin ( FourCharCode inSig, const char *inName ) : CServerPlugin(inSig, inName) { fNiNodeList = nil; fState = kUnknownState; fRegisterNodePtr = nil; fServerRunLoop = nil; //could be obtained directly now since a module not plugin fTransitionCheckTime = 0; //need the signature for some static functions CNetInfoPlugin::fToken = inSig; if ( gKickNodeRequests == nil ) { gKickNodeRequests = new DSEventSemaphore(); if ( gKickNodeRequests == nil ) throw((sInt32)eMemoryAllocError); } if ( gNetInfoMutex == nil ) { gNetInfoMutex = new DSMutexSemaphore(); if ( gNetInfoMutex == nil ) throw((sInt32)eMemoryAllocError); } } // CNetInfoPlugin // -------------------------------------------------------------------------------- // * ~CNetInfoPlugin () // -------------------------------------------------------------------------------- CNetInfoPlugin::~CNetInfoPlugin ( void ) { } // ~CNetInfoPlugin // -------------------------------------------------------------------------------- // * Validate () // -------------------------------------------------------------------------------- sInt32 CNetInfoPlugin::Validate ( const char *inVersionStr, const uInt32 inSignature ) { CNetInfoPlugin::fToken = fPlugInSignature = inSignature; return( eDSNoErr ); } // Validate // -------------------------------------------------------------------------------- // * Initialize () // -------------------------------------------------------------------------------- sInt32 CNetInfoPlugin::Initialize ( void ) { sInt32 siResult = eDSOpenNodeFailed; tDataList *pDataList = nil; tDataList *aDataList = nil; gNetInfoMutex->Wait(); try { { void *domain = nil; int tokcheck = 0; int tokplain = 0; int status = eDSNoErr; int binding = 0; int retcheck = 0; int numRetries = 0; DSSemaphore aCheckNIMutex; DBGLOG( kLogPlugin, "CNetInfoPlugin::Initialize - kick start netinfod initiated." ); //kick start netinfod here to ensure netinfod starts early upon boot/reboot? //status = (int)do_open( nil, "local", &domain, true, 1, NULL, NULL ); status = (int)do_open( nil, "127.0.0.1/local", &domain, true, 1, NULL, NULL ); if (status == NI_OK) { DBGLOG( kLogPlugin, "CNetInfoPlugin::Initialize - do_open on tag succeeded." ); } else { DBGLOG( kLogPlugin, "CNetInfoPlugin::Initialize - do_open on tag failed." ); } DBGLOG( kLogPlugin, "CNetInfoPlugin::Initialize - netinfo notification check attempted." ); status = notify_register_check(NETINFO_BINDING_KEY, &tokcheck); if (status == NOTIFY_STATUS_OK) { status = notify_check(tokcheck, &retcheck); int checkCount = 0; while ( (status == NOTIFY_STATUS_OK) && (retcheck == 0) && (checkCount < 3) ) { checkCount++; aCheckNIMutex.Wait( 1 * kMilliSecsPerSec ); status = notify_check(tokcheck, &retcheck); } if (retcheck == 1) { DBGLOG( kLogPlugin, "CNetInfoPlugin::Initialize - netinfo notification check succeeded." ); } if (tokcheck != 0) { notify_cancel(tokcheck); tokcheck = 0; } } DBGLOG( kLogPlugin, "CNetInfoPlugin::Initialize - netinfo binding discovery attempted." ); status = notify_register_plain(NETINFO_BINDING_KEY, &tokplain); if (status != NOTIFY_STATUS_OK) { DBGLOG1( kLogPlugin, "CNetInfoPlugin::Initialize - notify_register_plain failed for netinfo binding (status %d).", status ); syslog(LOG_INFO,"CNetInfoPlugin::Initialize - notify_register_plain failed for netinfo binding (status %d)\n", status); } else { binding = BINDING_STATE_UNBOUND; DBGLOG1( kLogPlugin, "CNetInfoPlugin::Initialize - notify_register_plain succeeded for netinfo binding (status %d).", status ); while (binding == BINDING_STATE_UNBOUND) { status = notify_get_state(tokplain, &binding); if (status != NOTIFY_STATUS_OK) { DBGLOG1( kLogPlugin, "CNetInfoPlugin::Initialize - notify_get_state failed for netinfo binding (status %d).", status ); syslog(LOG_INFO,"CNetInfoPlugin::Initialize - notify_get_state failed for netinfo binding (status %d)\n", status); status = -1; break; } if (binding != BINDING_STATE_UNBOUND) { status = binding; DBGLOG1( kLogPlugin, "CNetInfoPlugin::Initialize - netinfo binding bound after %d queries on netinfod status.", numRetries ); break; } numRetries++; if (numRetries > 10) { status = -1; DBGLOG1( kLogPlugin, "CNetInfoPlugin::Initialize - notify_get_state failed over last minute (status %d).", status ); syslog(LOG_INFO,"CNetInfoPlugin::Initialize - notify_get_state failed over last minute (status %d)\n", status); break; } aCheckNIMutex.Wait( 2 * kMilliSecsPerSec ); if ((numRetries % 5) == 0) { notify_cancel(tokplain); tokplain = 0; DBGLOG2( kLogPlugin, "CNetInfoPlugin::Initialize - retry attempt %d on notify_register_plain for netinfo binding (status %d).", numRetries, status ); status = notify_register_plain(NETINFO_BINDING_KEY, &tokplain); if (status != NOTIFY_STATUS_OK) { DBGLOG2( kLogPlugin, "CNetInfoPlugin::Initialize - retry %d on notify_register_plain failed for netinfo binding (status %d)\n", numRetries, status ); syslog(LOG_INFO,"CNetInfoPlugin::Initialize - retry %d on notify_register_plain failed for netinfo binding (status %d)\n", numRetries, status); status = -1; break; } } } if (tokplain != 0) { notify_cancel(tokplain); } switch (status) { case BINDING_STATE_BOUND: case BINDING_STATE_NETROOT: siResult = eDSNoErr; break; default: siResult = eDSOpenNodeFailed; break; } } if (domain != nil) { ni_free(domain); domain = nil; } } if (siResult != eDSNoErr) { throw(siResult); } if ( fNiNodeList == nil ) { fNiNodeList = new CNiNodeList(); } if ( fNiNodeList == nil ) throw( (sInt32)eMemoryAllocError ); //Register the default local node immediately pDataList = ::dsBuildFromPathPriv( kstrDefaultLocalNodeName, "/" ); aDataList = ::dsBuildFromPathPriv( kstrDefaultLocalNodeName, "/" ); if ( pDataList != nil ) { //AddNode consumes the pDataList fNiNodeList->AddNode( ".", pDataList, false ); //will be registered but not treated the same as others pDataList = nil; } if ( aDataList != nil ) { DBGLOG1( kLogPlugin, "Registering node %s.", kstrDefaultLocalNodeName ); CServerPlugin::_RegisterNode( fPlugInSignature, aDataList, kLocalNodeType ); ::dsDataListDeallocatePriv( aDataList ); free(aDataList); aDataList = nil; //need to set global that local node will be available shortly //PFIX gLocalNodeNotAvailable = false; } fRegisterMutex.Wait(); if (fRegisterNodePtr == nil) { fRegisterNodePtr = new CNodeRegister( fPlugInSignature, fNiNodeList, false, this ); if ( fRegisterNodePtr != nil ) { //this call could throw fRegisterNodePtr->StartThread(); } } fRegisterMutex.Signal(); // Turn off the failed flag and turn on active and inited flag fState &= ~kFailedToInit; fState += kActive; fState += kInitialized; // Tell everyone that we are ready to go now CNetInfoPlugin::WakeUpRequests(); } catch( sInt32 err ) { fState += kFailedToInit; siResult = err; } gNetInfoMutex->Signal(); return( siResult ); } // Initialize // -------------------------------------------------------------------------------- // * PeriodicTask () // -------------------------------------------------------------------------------- sInt32 CNetInfoPlugin::PeriodicTask ( void ) { return( eDSNoErr ); } // PeriodicTask //-------------------------------------------------------------------------------------------------- // * WakeUpRequests() (static) // //-------------------------------------------------------------------------------------------------- void CNetInfoPlugin::WakeUpRequests ( void ) { gKickNodeRequests->Signal(); } // WakeUpRequests // --------------------------------------------------------------------------- // * WaitForInit // // --------------------------------------------------------------------------- void CNetInfoPlugin::WaitForInit ( void ) { volatile uInt32 uiAttempts = 0; while ( !(fState & kInitialized) && !(fState & kFailedToInit) ) { try { // Try for 2 minutes before giving up if ( uiAttempts++ >= 240 ) { return; } // Now wait until we are told that there is work to do or // we wake up on our own and we will look for ourselves gKickNodeRequests->Wait( (uInt32)(.5 * kMilliSecsPerSec) ); // try // { // gKickNodeRequests->Reset(); // } // // catch( sInt32 err ) // { // } } catch( sInt32 err1 ) { } // uiAttempts++; } } // WaitForInit // --------------------------------------------------------------------------- // * ProcessRequest // // --------------------------------------------------------------------------- sInt32 CNetInfoPlugin::ProcessRequest ( void *inData ) { sInt32 siResult = eDSNoErr; CNiPlugIn cNiPlugin; char *pathStr = nil; try { if ( inData == nil ) { return( ePlugInDataError ); } if (((sHeader *)inData)->fType == kOpenDirNode) { if (((sOpenDirNode *)inData)->fInDirNodeName != nil) { pathStr = ::dsGetPathFromListPriv( ((sOpenDirNode *)inData)->fInDirNodeName, "/" ); if ( (pathStr != nil) && (strncmp(pathStr,"/NetInfo",8) != 0) ) { throw( (sInt32)eDSOpenNodeFailed ); } } } if ( ((sHeader *)inData)->fType == kServerRunLoop ) { if ( (((sHeader *)inData)->fContextData) != nil ) { fServerRunLoop = (CFRunLoopRef)(((sHeader *)inData)->fContextData); return (siResult); } } WaitForInit(); if (fState == kUnknownState) { throw( (sInt32)ePlugInCallTimedOut ); } if ( (fState & kFailedToInit) || !(fState & kInitialized) ) { throw( (sInt32)ePlugInFailedToInitialize ); } if ( (fState & kInactive) || !(fState & kActive) ) { throw( (sInt32)ePlugInNotActive ); } if ( ((sHeader *)inData)->fType == kHandleNetworkTransition ) { HandleMultipleNetworkTransitions(); } else { siResult = cNiPlugin.HandleRequest( inData ); } } catch( sInt32 err ) { siResult = err; } if (pathStr != nil) { free(pathStr); pathStr = nil; } return( siResult ); } // ProcessRequest //------------------------------------------------------------------------------------ // * HandleMultipleNetworkTransitions //------------------------------------------------------------------------------------ void CNetInfoPlugin::HandleMultipleNetworkTransitions ( void ) { void *ptInfo = nil; //let us be smart about doing the recheck //we would like to wait a short period for NetInfo to come back fully //we also don't want to re-init multiple times during this wait period //however we do go ahead and fire off timers each time //each call in here we update the delay time by 5 seconds fTransitionCheckTime = time(nil) + 5; if (fServerRunLoop != nil) { ptInfo = (void *)this; CFRunLoopTimerContext c = {0, (void*)ptInfo, NULL, NULL, NetworkChangeNIPICopyStringCallback}; CFRunLoopTimerRef timer = CFRunLoopTimerCreate( NULL, CFAbsoluteTimeGetCurrent() + 5, 0, 0, 0, DoNIPINetworkChange, (CFRunLoopTimerContext*)&c); CFRunLoopAddTimer(fServerRunLoop, timer, kCFRunLoopDefaultMode); if (timer) CFRelease(timer); } } // HandleMultipleNetworkTransitions //------------------------------------------------------------------------------------ // * ReDiscoverNetwork //------------------------------------------------------------------------------------ void CNetInfoPlugin::ReDiscoverNetwork(void) { sInt32 siResult = eDSNoErr; //do something if the wait period has passed if (time(nil) >= fTransitionCheckTime) { if ( fNiNodeList != nil ) { fRegisterMutex.Wait(); if (fRegisterNodePtr == nil) { fRegisterNodePtr = new CNodeRegister( fPlugInSignature, fNiNodeList, true, this ); if ( fRegisterNodePtr != nil ) { try { //set all the node flags to dirty fNiNodeList->SetAllDirty(); //this call could throw fRegisterNodePtr->StartThread(); } catch( sInt32 err ) { siResult = err; } } } else { //set all the node flags to dirty //fNiNodeList->SetAllDirty(); // this is taken care of inside CNodeRegister fRegisterNodePtr->Restart(); } fRegisterMutex.Signal(); } } } // ReDiscoverNetwork // --------------------------------------------------------------------------- // * SafeOpen // // --------------------------------------------------------------------------- sInt32 CNetInfoPlugin::SafeOpen ( const char *inName, sInt32 inTimeoutSecs, ni_id *outNiDirID, void **outDomain, char **outDomainName ) { sInt32 siResult = eDSNoErr; ni_status niStatus = NI_OK; void *domain = nil; char *domName = nil; ni_id domainID; uInt32 localOrParent = 0; time_t delayedNI = 0; tDataList *pDataList = nil; try { if ( fNiNodeList->IsOpen( inName, outDomain, outDomainName, outNiDirID ) == false ) { localOrParent = fNiNodeList->CheckForLocalOrParent( inName ); gNetInfoMutex->Wait(); delayedNI = time(nil) + 2; //normally ni_open will complete in under 2 secs if (localOrParent == 1) { niStatus = ::ni_open( nil, ".", &domain ); if ( delayedNI < time(nil) ) { syslog(LOG_INFO,"SafeOpen::Call to ni_open was with fixed domain name: . and lasted %d seconds.", (uInt32)(2 + time(nil) - delayedNI)); } } else if (localOrParent == 2) { niStatus = ::ni_open( nil, "..", &domain ); if ( delayedNI < time(nil) ) { syslog(LOG_INFO,"SafeOpen::Call to ni_open was with fixed domain name: .. and lasted %d seconds.", (uInt32)(2 + time(nil) - delayedNI)); } } else { niStatus = ::ni_open( nil, inName, &domain ); if ( delayedNI < time(nil) ) { syslog(LOG_INFO,"SafeOpen::Call to ni_open was with argument domain name: %s and lasted %d seconds.", inName, (uInt32)(2 + time(nil) - delayedNI)); } } if ( niStatus != NI_OK ) { throw( (sInt32)eDSOpenNodeFailed ); } // Ping the domain to see if it's really alive ::ni_setreadtimeout( domain, inTimeoutSecs ); ::ni_setabort( domain, 1 ); NI_INIT( &domainID ); domainID.nii_object = 0; niStatus = ::ni_self( domain, &domainID ); if ( niStatus != NI_OK ) { //check for nil and free if not if (domain != nil) { ::ni_free( domain ); } throw( (sInt32)eDSOpenNodeFailed ); } gNetInfoMutex->Signal(); if ( ::strcmp( inName, "/" ) == 0 ) { domName = (char *)::calloc( ::strlen( "/NetInfo/root" ) + 1, sizeof( char ) ); ::strcpy( domName, "/NetInfo/root" ); } else if ( ::strcmp( inName, "." ) == 0 ) { domName = strdup( kstrDefaultLocalNodeName ); } else if ( ::strcmp( inName, ".." ) == 0 ) { char * niDomName = nil; gNetInfoMutex->Wait(); delayedNI = time(nil) + 2; //normally my_ni_pwdomain will complete in under 2 secs niStatus = ::my_ni_pwdomain( domain, &niDomName ); if ( delayedNI < time(nil) ) { syslog(LOG_INFO,"SafeOpen::Call to my_ni_pwdomain was with argument domain name: %s and lasted %d seconds.", inName, (uInt32)(2 + time(nil) - delayedNI)); } gNetInfoMutex->Signal(); if ( niStatus == NI_OK ) { if ( ::strcmp( niDomName, "/" ) == 0 ) { domName = (char *)::calloc( ::strlen( "/NetInfo/root" ) + 1, sizeof( char ) ); ::strcpy( domName, "/NetInfo/root" ); } else { domName = (char *)::calloc( ::strlen( niDomName ) + ::strlen( "/NetInfo/root" ) + 1, sizeof( char ) ); ::strcpy( domName, "/NetInfo/root" ); ::strcat( domName, niDomName ); } if (niDomName != nil) { free(niDomName); niDomName = nil; } } else { domName = (char *)::calloc( ::strlen( "Unknown Locations" ) + 1, sizeof( char ) ); ::strcpy( domName, "Unknown Locations" ); } } else { domName = (char *)::calloc( ::strlen( "/NetInfo/root" ) + ::strlen( inName ) + 1, sizeof( char ) ); ::strcpy( domName, "/NetInfo/root" ); ::strcat( domName, inName ); } fNiNodeList->Lock(); if ( fNiNodeList->IsOpen( inName, outDomain, outDomainName, outNiDirID ) == false ) { //we are forcing the SetDomainInfo code to always create a node via AddNode //this gets around the refCount problem fNiNodeList->SetDomainInfo( inName, domain, domName, &domainID ); fNiNodeList->UnLock(); //we also register the node here since it was not already found in our node list pDataList = ::dsBuildFromPathPriv( domName, "/" ); if ( pDataList != nil ) { if (CServerPlugin::_RegisterNode( CNetInfoPlugin::fToken, pDataList, kDirNodeType ) == eDSNoErr) { DBGLOG1( kLogPlugin, "CNetInfoPlugin::SafeOpen: Registering node that was not already registered %s.", domName ); } ::dsDataListDeallocatePriv( pDataList ); free( pDataList ); pDataList = nil; } *outDomain = domain; *outDomainName = strdup(domName); ::memcpy( outNiDirID, &domainID, sizeof( ni_id ) ); } else { fNiNodeList->UnLock(); if (domain != nil) { gNetInfoMutex->Wait(); ni_free(domain); gNetInfoMutex->Signal(); domain = nil; } if (domName != nil) { free(domName); domName = nil; } } } } catch( sInt32 err ) { gNetInfoMutex->Signal(); siResult = err; } catch ( ... ) { gNetInfoMutex->Signal(); siResult = -12020; } return( siResult ); } // SafeOpen // --------------------------------------------------------------------------- // * SafeClose // // --------------------------------------------------------------------------- sInt32 CNetInfoPlugin::SafeClose ( const char *inName ) { sInt32 siResult = eDSNoErr; void *domain = nil; gNetInfoMutex->Wait(); domain = fNiNodeList->DeleteNode( inName ); if (domain != nil) { ni_free(domain); domain = nil; } gNetInfoMutex->Signal(); return( siResult ); } // SafeClose // --------------------------------------------------------------------------- // * UnregisterNode // // --------------------------------------------------------------------------- sInt32 CNetInfoPlugin::UnregisterNode ( const uInt32 inToken, tDataList *inNode ) { sInt32 siResult = eDSNoErr; siResult = CServerPlugin::_UnregisterNode( inToken, inNode ); return (siResult); } // UnregisterNode // --------------------------------------------------------------------------- // * NodeRegisterComplete // // --------------------------------------------------------------------------- void CNetInfoPlugin::NodeRegisterComplete ( CNodeRegister *aRegisterThread ) { fRegisterMutex.Wait(); if (fRegisterNodePtr == aRegisterThread) { fRegisterNodePtr = nil; } fRegisterMutex.Signal(); } // NodeRegisterComplete // -------------------------------------------------------------------------------- // * SetPluginState () // -------------------------------------------------------------------------------- sInt32 CNetInfoPlugin::SetPluginState ( const uInt32 inState ) { //does nothing yet return( eDSNoErr ); } // SetPluginState