#include "ServiceCheck.h"

#include "Logger.h"
#include "ProcessConfig.h"

#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <net/if_media.h>
#include <ifaddrs.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>

#include <sstream>




// Service out file name
#define FILE_SERVICE_OUT      "/user/service/fhs.out"

// Service read-only file name
#define FILE_SERVICE_RONLY   "/user/service/fhs.ronly"
#define FILE_VMASD_DISABLE   "/user/service/vmasd.disable"


// Apache alive check 에 사용할 Port 정보.
#define PORT_APACHE_LISTEN   80

// fsck 관련 프로세스 동작 여부 체크에 사용할 process name
// - 기존 vmasd 에서는 fsck_ufs 를 확인했지만...
// - FreeBSD 에서 zfs, ufs 등을 사용하게 되어.. fsck 가 동작 중인지만 체크한다.
#define PROCESS_NAME_FSCK    "fsck"


// fimngd alive check 에서 fimngd 가 dead 상태로 판단된 경우...
// 허용 가능한 최대 dead 상태 횟수
// 해당 횟수 초과시 fimngd 가 죽은 것으로 판단하여 서비스 상태를 변경 처리한다.
// - fimngd 가 업데이트 등의 목적으로 종료된 경우....
// - 이를 바로 반영하여 서비스 상태를 불가로 만들 경우... 전체 서비스 운영에 문제가 될 수 있으므로..
// - fimngd 재시작 처리를 위한 여유 시간을 확보하기 위함.
// - 허용 가능 시간 : MAX_DEAD_COUNT_FIMNGD * SERVICE_CHECK_INTERVAL(WorkerServiceCheck.cpp에 정의 ) = 3 * 4 = 12 sec
#define MAX_DEAD_COUNT_FIMNGD    3



////////////////////////////////////////////////////////////////////

// 생성자
CServiceCheck::CServiceCheck( CFhsShm * pFhsShm )
	: m_pFhsShm( pFhsShm )	
{
	m_uCheckFimngdPort = 0;
	m_iFimngdDeadCount = -1;  // 최초 기동시 -1 로 초기화.
}

// 소멸자
CServiceCheck::~CServiceCheck()
{

}

// 초기화 함수
bool CServiceCheck::Init( std::vector<std::string> vecNic, std::vector<std::string> vecDns, unsigned int uFimngdPort )
{
	if( m_pFhsShm == NULL )
		return false;
	
	// 인자로 전달받은 공인 NIC 및 DNS 정보를 멤버 변수에 저장.
	// - 각 인자에 대한 유효성은 config 처리 관련 모듈에서 수행했으므로.. 하지 않는다.
	m_vecServiceNic    = vecNic;
	m_vecCheckDns      = vecDns;
	m_uCheckFimngdPort = uFimngdPort;

	m_vecServiceIp.clear();
	
	return true;
}


// Service 상태 Check 시작..
// - 오류 발생시.. false 반환.
bool CServiceCheck::Check()
{
	struct stat stFileStat;

	// check start time 
	clock_gettime( CLOCK_MONOTONIC, &m_timeStart );

	
	// 1. /user/service/fhs.out
	// - 존재할 경우... SERVICE_STAT_OUT 상태로 설정.
	memset( &stFileStat, 0x00, sizeof( struct stat ) );
	if( lstat( FILE_SERVICE_OUT, &stFileStat ) == 0 )
	{
		// 해당 파일이 정규 파일인 경우.. ( 폴더, 링크 등은 제외 처리 )
		if( S_ISREG( stFileStat.st_mode ) != 0 )
		{
			// 해당 파일이 존재하는 경우...
			// - shared memory 설정을 변경 하고...
			// - check 작업을 종료한다. ( 더 이상 체크할 필요 없으므로.. )
			m_pFhsShm->SetServiceStat( SERVICE_STAT_OUT );

			_LOG( LINF, "[Service] %s found. service stat set: %d (%s)"
				  , FILE_SERVICE_OUT, m_pFhsShm->GetServiceStat(), m_pFhsShm->GetServiceStatString() );
			return true;
		}
	}
	_LOG( LINF, "[Service] %s check passed.", FILE_SERVICE_OUT );

	
	// 2. NIC down check
	if( IsAliveServiceNic() == false )
	{
		// NIC 체크에 오류 또는 1개라도 active 상태가 아닌 경우...
		// - 서비스 불가 상태로 공유메모리 설정.		
		// - check 작업을 종료한다. ( 더 이상 체크할 필요 없으므로.. )
		m_pFhsShm->SetServiceStat( SERVICE_STAT_DISABLE );

		_LOG( LINF, "[Service] service nic alive check fail. service stat set: %d (%s)"
			  , m_pFhsShm->GetServiceStat(), m_pFhsShm->GetServiceStatString() );

		return true;
	}
	_LOG( LINF, "[Service] service nic alive check passed." );


	// 2020-04-08 CHG huibong IP 정보 추출시 hang 발생 회피를 위해 기동시 최초 1회만 IP 정보 추출 (#32967)
	//                - getifaddrs() 호출시 간혹 hang 발생으로 serive out 상황 발생
	//                - 이를 회피하기 위해 추출된 IP 정보를 저장하는 m_vecServiceIp 객체가 유효하지 않은 경우만...
	//                - IP 정보를 추출 한다.
	if( m_vecServiceIp.empty() == true )
	{
		// 서비스 NIC 정보로 부터 서비스 IP 정보를 사전에 추출한다.
		// - 아래 Apache alive, DNS resolve 체크에 필요.
		if( GetServiceIp() == false )
		{
			// 서비스 NIC 정보로 부터 서비스 IP 정보 추출 실패시..
			// - 아래 하위 check 를 수행할 수 없으므로...
			// - 서비스 불가 상태로 처리.		
			// - check 작업을 종료한다. ( 더 이상 체크할 필요 없으므로.. )
			m_pFhsShm->SetServiceStat( SERVICE_STAT_DISABLE );

			_LOG( LINF, "[Service] service nic ip check fail. service stat set: %d (%s)"
				, m_pFhsShm->GetServiceStat(), m_pFhsShm->GetServiceStatString() );

			return true;
		}
		_LOG( LINF, "[Service] service nic ip check passed." );
	}

	// 3. Storage total 용량 Check.
	// - Storage total 용량 정보가 0 으로 유효하지 않은 경우 SERVICE_STAT_DISABLE 상태로 처리한다.
	if( m_pFhsShm->GetStorageTotal() <= 0 )
	{
		m_pFhsShm->SetServiceStat( SERVICE_STAT_DISABLE );

		_LOG( LINF, "[Service] storage total size[%ju] not available. service stat set: %d (%s)"
			  , m_pFhsShm->GetStorageTotal(), m_pFhsShm->GetServiceStat(), m_pFhsShm->GetServiceStatString() );

		return true;
	}
	_LOG( LINF, "[Service] storage total size check passed." );
	
	// 4. Apache 80 port alive check
	// - 위에서 추출한 서비스 IP 정보를 이용하여 80 port 접속 테스트 수행.
	// - 2015-05-11 현재 opendav 가 80 port listen 처리시 IPv4 에 대해서만 Listen 처리하므로...
	// - 추출된 서비스 IP 중 IPv4 에 대해서만 테스트를 수행한다.
	// - false : 접속 실패 또는 오류 발생시에는 서비스 불가 상태로 처리.		
	if( IsAliveApache() == false )
	{
		_LOG( LINF, "[Service] apache connect fail. sleep & retry." );

		// 2016-04-07 CHG huibong (#27149)
		// - FreeBSD 6.2 장비에서 apache alive check 실패로 인한 잦은 disable 상황 발생.
		// - apache alive check 관련 timeout 을 1 sec -> 2 sec 로 변경해 봤으나 효과 없음.
		// - 따라서 .. 실패 발생시.. sleep 후 다시 check 하도록 Flow 변경한다.
		sleep(1);
		if( IsAliveApache() == false )
		{
			// Apache 80 port 접속 테스트 실패시..
			// - 아래 하위 check 를 수행할 수 없으므로... 서비스 불가 상태로 처리.		
			// - check 작업을 종료한다. ( 더 이상 체크할 필요 없으므로.. )
			m_pFhsShm->SetServiceStat(SERVICE_STAT_DISABLE);

			_LOG(LINF, "[Service] apache connect fail. service stat set: %d (%s)"
				, m_pFhsShm->GetServiceStat(), m_pFhsShm->GetServiceStatString());

			return true;
		}
	}	
	_LOG( LINF, "[Service] apache connect check passed." );

	// 5. DNS resolve check.
	// - 이전 상태가 SERVICE_STAT_OUT, SERVICE_STAT_DISABLE 인 경우에만 DNS resolve 체크 수행.
	// -- 만약 DNS 일부가 죽은 경우.. resolve 되지 않아 실패 처리되므로.. 정상 상태에서 주기적인 감시는 수행 안함.
	// - 장비의 hostname 을 기준으로 m_vecCheckDns 에 정의된 DNS 서버에 resolve 질의
	// - resolve 결과가.. m_vecServiceIp 에 존재하는 경우.. 정상.
	// - 만약 resolve 결과가 m_vecServiceIp 에 존재하지 않는 경우... 
	// -- 해당 domain 이 다른 장비를 가르키는 것일 수 있으므로.. 관련 내역 로깅 및 서비스 불가 처리.
	int iServiceStat = m_pFhsShm->GetServiceStat();
	if( iServiceStat == SERVICE_STAT_OUT || iServiceStat == SERVICE_STAT_DISABLE )
	{
		// DNS resolve 체크 수행.
		if( IsResolveDns() == false )
		{
			// resolve 실패, 대상 IP 미존재, 오류 등의 상황 발생시.
			// - 아래 하위 check 를 수행할 수 없으므로... 서비스 불가 상태로 처리.		
			// - check 작업을 종료한다. ( 더 이상 체크할 필요 없으므로.. )
			m_pFhsShm->SetServiceStat( SERVICE_STAT_DISABLE );

			_LOG( LINF, "[Service] dns resolve check fail. service stat set: %d (%s)"
				  , m_pFhsShm->GetServiceStat(), m_pFhsShm->GetServiceStatString() );

			return true;
		}
		_LOG( LINF, "[Service] dns resolve check passed." );
	}
	
	// 6. fimngd alive check
	// - 위에서 추출된 서비스 IP 1개를 사용하여 접속 테스트 수행.
	// - 접속 테스트 실패한 경우...
	// -- 최초 기동시에는 바로 서비스 불가(2) 로 처리.
	// -- 서비스 중인 경우 지정된 횟수 동안 접속이 불가능한 경우 서비스 불가(2)로 처리한다.
	if( IsAliveFimngd() == false )
	{
		// 최초 기동시 발생한 것인지 체크.		
		if( m_iFimngdDeadCount == -1 )
		{
			// 최초 기동시 fimngd 가 미동작 중인 경우...
			// - 바로 서비스 불가 상태로 처리한다.
			// - check 작업을 종료한다. ( 더 이상 체크할 필요 없으므로.. )
			m_pFhsShm->SetServiceStat( SERVICE_STAT_DISABLE );

			_LOG( LINF, "[Service] fimngd dead in starting. service stat set: %d (%s)"
				  , m_pFhsShm->GetServiceStat(), m_pFhsShm->GetServiceStatString() );

			return true;
		}
		else
		{
			// 최초 기동시에는 정상.
			// 서비스 중에 fimngd 가 종료된 경우...
			// dead count 횟수 증가.
			m_iFimngdDeadCount++;
			if( m_iFimngdDeadCount > 9999 )
				m_iFimngdDeadCount = 9999;
			
			if( m_iFimngdDeadCount > MAX_DEAD_COUNT_FIMNGD )
			{
				// 지정된 횟수를 초과하여 접속이 불가능한 경우
				// - 서비스 불가(2)로 처리한다.
				m_pFhsShm->SetServiceStat( SERVICE_STAT_DISABLE );

				_LOG( LINF, "[Service] fimngd dead. count[%d]. service stat set: %d (%s)"
					  , m_iFimngdDeadCount, m_pFhsShm->GetServiceStat(), m_pFhsShm->GetServiceStatString() );

				return true;
			}
			else
			{
				// fimngd 가 종료되었으나.. 지정된 횟수를 초과하지 않은 경우.
				// - 관련 사항 로깅 처리.
				_LOG( LINF, "[Service] fimngd dead. but not enough dead count. count[%d]", m_iFimngdDeadCount );
			}
		}
	}
	else
	{
		// fimngd 가 정상 동작 중인 경우...
		m_iFimngdDeadCount = 0;
	}
	_LOG( LINF, "[Service] fimngd alive check passed." );


	// 7. Storage write/read 테스트 실패 또는 Hang 상황 발생시
	// 2018-01-31 NEW huibong Storage 에 대한 오류 상황 또는 Hang 감지 기능 추가 (#31701)
	if( IsNormalStatusStorageCheck() == false )
	{
		// Storage 에 대한 write / read 테스트 실패 또는 Hang 으로 인한 timeout 발생한 상황
		// - 서비스 불가 상태로 공유메모리 설정.		
		// - check 작업을 종료한다. ( 더 이상 체크할 필요 없으므로.. )
		m_pFhsShm->SetServiceStat( SERVICE_STAT_DISABLE );

		_LOG( LINF, "[Service] storage status check fail. service stat set: %d (%s)"
			  , m_pFhsShm->GetServiceStat(), m_pFhsShm->GetServiceStatString() );
		
		return true;
	}
	_LOG( LINF, "[Service] storage write/read hang check passed." );


	// 8. System Collect worker 프로세스 Hang 상황 발생시
	// 2018-02-01 NEW huibong 시스템 hang 감지 기능 추가 (#31701)
	if( IsNormalStatusSystemCollect() == false )
	{
		// System Collect worker 프로세스가 Hang 걸린 상황
		// - 서비스 불가 상태로 공유메모리 설정.		
		// - check 작업을 종료한다. ( 더 이상 체크할 필요 없으므로.. )
		m_pFhsShm->SetServiceStat( SERVICE_STAT_DISABLE );

		_LOG( LINF, "[Service] system collect worker status hang. service stat set: %d (%s)"
			  , m_pFhsShm->GetServiceStat(), m_pFhsShm->GetServiceStatString() );
		
		return true;
	}
	_LOG( LINF, "[Service] system collect hang check passed." );


	// 9. ZFS 사용 중인 경우 ZFS 상태 정보 check
	// 2018-02-05 NEW huibong ZFS 상태 감지 기능 추가 (#31701)
	ZfsStatus iZfsStatus = GetZfsStatus();
	if( iZfsStatus == ZFS_ERROR )
	{
		// Storage 관련 정보 추출 실패 또는
		// ZFS 상태가 Error 인 경우
		// - 서비스 불가 상태로 공유메모리 설정.		
		// - check 작업을 종료한다. ( 더 이상 체크할 필요 없으므로.. )
		m_pFhsShm->SetServiceStat( SERVICE_STAT_DISABLE );

		_LOG( LINF, "[Service] zfs status check result: error,  service stat set: %d (%s)"
			  , m_pFhsShm->GetServiceStat(), m_pFhsShm->GetServiceStatString() );

		return true;
	}
	else if( iZfsStatus == ZFS_READONLY )
	{
		// ZFS 상태가 DEGRADED 등의 Write 불가 상태인 경우
		// - read only 상태로 공유메모리 설정.		
		// - check 작업을 종료한다. ( 더 이상 체크할 필요 없으므로.. )
		m_pFhsShm->SetServiceStat( SERVICE_STAT_RONLY );

		_LOG( LINF, "[Service] zfs status check result: read-only,  service stat set: %d (%s)"
			  , m_pFhsShm->GetServiceStat(), m_pFhsShm->GetServiceStatString() );
		
		return true;
	}
	_LOG( LINF, "[Service] zfs online check passed." );


	// check end time 
	clock_gettime( CLOCK_MONOTONIC, &m_timeEnd );

	// 작업 소요 시간 계산
	m_dElapsedTime = m_timeEnd.tv_sec - m_timeStart.tv_sec;
	m_dElapsedTime += ( m_timeEnd.tv_nsec - m_timeStart.tv_nsec ) * 1e-9;

	_LOG( LINF, "[Service] service disable check passed. total elapsed time: %.3lf sec.", m_dElapsedTime );


	// 10. /user/service/fhs.ronly
	// - 존재할 경우... SERVICE_STAT_RONLY 상태로 설정.
	memset( &stFileStat, 0x00, sizeof( struct stat ) );
	if( lstat( FILE_SERVICE_RONLY, &stFileStat ) == 0 )
	{
		// 해당 파일이 정규 파일인 경우.. ( 폴더, 링크 등은 제외 처리 )
		if( S_ISREG( stFileStat.st_mode ) != 0 )
		{
			// 해당 파일이 존재하는 경우...
			// - shared memory 설정을 변경 하고...
			// - check 작업을 종료한다. ( 더 이상 체크할 필요 없으므로.. )
			m_pFhsShm->SetServiceStat( SERVICE_STAT_RONLY );

			_LOG( LINF, "[Service] %s found. service stat set: %d (%s)"
				  , FILE_SERVICE_RONLY, m_pFhsShm->GetServiceStat(), m_pFhsShm->GetServiceStatString() );

			return true;
		}
	}
	_LOG( LINF, "[Service] %s check passed.", FILE_SERVICE_RONLY );


	// 11. vmasd 와 호환성을 고려하여.. vmasd.disable 이 설정된 경우... SERVICE_STAT_RONLY 상태로 설정.
	// - 본 코드는 향후 전체 RC 패치 및 vmasd 소스 삭제 처리 후 제거 예정.
	memset( &stFileStat, 0x00, sizeof( struct stat ) );
	if( lstat( FILE_VMASD_DISABLE, &stFileStat ) == 0 )
	{
		// 해당 파일이 정규 파일인 경우.. ( 폴더, 링크 등은 제외 처리 )
		if( S_ISREG( stFileStat.st_mode ) != 0 )
		{
			// 해당 파일이 존재하는 경우...
			// - shared memory 설정을 변경 하고...
			// - check 작업을 종료한다. ( 더 이상 체크할 필요 없으므로.. )
			m_pFhsShm->SetServiceStat( SERVICE_STAT_RONLY );

			_LOG( LINF, "[Service] %s found. service stat set: %d (%s)"
				  , FILE_VMASD_DISABLE, m_pFhsShm->GetServiceStat(), m_pFhsShm->GetServiceStatString() );

			return true;
		}
	}
	_LOG( LINF, "[Service] %s check passed.", FILE_VMASD_DISABLE );


	// 2020-04-08 CHG huibong fsck process check 기능을 사용하지 않도록 수정 (#32973)
	//                - fsck process check 관련 popen() - fgets() 함수에서 장비 부하시 blocking으로 인한 지연 발생
	//                - nonblocking 방식, vmasd 에서 사용한 sysctl 등의 방식을 테스트했으나...장비 부하시 지연은 계속 발생
	//                - fsck 가 서비스 중에 실제 동작하지 않으며... ZFS 사용 등으로 인해 더이상 check 할 필요가 없으므로...
	//                - fsck check 절차는 비활성화 처리한다.
	// 12. fsck 동작 check.
	//if( IsFsckRun() == true )
	//{
	//	// fsck 프로세스가 동작 중인 경우...
	//	// - shared memory 설정을 변경 하고...
	//	// - check 작업을 종료한다.
	//	m_pFhsShm->SetServiceStat( SERVICE_STAT_RONLY );
	//			
	//	_LOG( LINF, "[Service] %s process found. service stat set: %d (%s)"
	//		  , PROCESS_NAME_FSCK, m_pFhsShm->GetServiceStat(), m_pFhsShm->GetServiceStatString() );
	//	
	//	return true;
	//}
	//_LOG( LINF, "[Service] %s process check passed.", PROCESS_NAME_FSCK );


	// 13. Disk Busy 상황인 경우 Read Only 상태로 변경
	// 2018-02-07 NEW huibong disk busy 상황시 Read Only 상태로 변경 처리 (#31701)
	// - Disk Busy 상황시 응답 지연 발생 또는 장비 Hang 상황인 것으로 판단됨
	// - 2018-01 SK 에서 발생한 응답 지연으로 인한 SolProxy 오류 확인 결과... 
	// - queue 가 7 이상 & disk busy 50 % 이상인 경우 응답 지연 발생됨
	// - 따라서.. 해당 상황인 경우.. Read Only 상태로 일시 변경 처리한다.
	if( m_pFhsShm->GetDiskMaxQueue() >= DISK_BUSY_MIN_QUEUE_COUNT &&
		m_pFhsShm->GetDiskMaxBusy() >= DISK_BUSY_MIN_BUSY_PERCENT )
	{
		// disk queue, busy 값이 기준값 이상인 경우
		// - service 상태를 Read only 상태로 변경 처리
		m_pFhsShm->SetServiceStat( SERVICE_STAT_RONLY );

		_LOG( LINF, "[Service] disk busy. queue[%ju / %d], busy[%.1lf / %.1lf]. service stat set: %d (%s)" 
			  , m_pFhsShm->GetDiskMaxQueue(), DISK_BUSY_MIN_QUEUE_COUNT
			  , m_pFhsShm->GetDiskMaxBusy() , DISK_BUSY_MIN_BUSY_PERCENT
			  , m_pFhsShm->GetServiceStat(), m_pFhsShm->GetServiceStatString() );

		return true;
	}
	_LOG( LINF, "[Service] disk busy check passed." );


	// 위의 모든 조건에 해당되지 않을 경우.. 정상 상태로 처리한다.
	m_pFhsShm->SetServiceStat( SERVICE_STAT_OK );

	// check end time 
	clock_gettime( CLOCK_MONOTONIC, &m_timeEnd );

	// 작업 소요 시간 계산
	m_dElapsedTime = m_timeEnd.tv_sec - m_timeStart.tv_sec;
	m_dElapsedTime += ( m_timeEnd.tv_nsec - m_timeStart.tv_nsec ) * 1e-9;
	
	_LOG( LINF, "[Service] all check passed. total elapsed time: %.3lf sec.", m_dElapsedTime );

	return true;
}


// fsck 프로세스 동작 여부 체크.
// - true  : 해당 프로세스 동작 중
// - false : 동작 안함.
bool CServiceCheck::IsFsckRun()
{
	char tempBuffer[64];
	FILE * fd = NULL;
	bool bRun = false;

	snprintf( tempBuffer, sizeof( tempBuffer ), "pgrep -x %s | sort", PROCESS_NAME_FSCK );

	fd = popen( tempBuffer, "r" );
	if( fd == NULL )
	{
		int errorNum = errno;
		LOG( LERR, "%s process check fail.[%d][%s]", PROCESS_NAME_FSCK, errorNum, strerror( errorNum ) );
		
		// 오류 발생시 false 반환하여.. 해당 프로세스가 동작 중이지 않는 것으로 처리.
		return false;
	}
	else
	{
		memset( tempBuffer, 0x00, sizeof( tempBuffer ) );

		// 2020-04-08 popen - fgets 사용시 fgets() 처리 지연 발생
		//            자세한 사항은 https://jarvis.solbox.com/redmine/issues/32973 참고
		if( fgets( tempBuffer, sizeof( tempBuffer ) - 1, fd ) != NULL )
		{
			// 프로세스 ID 가 존재하는 경우...
			_LOG( LINF, "[Service] %s process found. pid [%d]", PROCESS_NAME_FSCK, atoi( tempBuffer ) );
			bRun = true;
		}

		pclose( fd );
		return bRun;
	}
}

// 멤버 변수 m_vecSvcNic 에 저장된 공인 NIC 에 대한 상태 체크 수행
// - true  : 모두 정상
// - false : 오류 발생 또는 1개라도 active 상태가 아닌 경우.
// - 본 코드는 기존 vmasd 의 kvm 사용 코드를 배제하고.. ifconfig 에서 사용한 코드를 사용하여 개발함.
bool CServiceCheck::IsAliveServiceNic()
{
	// 서비스 NIC 정보가 비어 있는 경우...
	// - 일부러 설정 안할 수도 있으나.. 공인 IP 추출, DNS resolve 체크 등이 불가하므로.. 오류로 처리한다.
	if( m_vecServiceNic.empty() == true )
	{
		_LOG( LINF, "[Service] service nic check. service nic not exist.");
		return false;
	}
	
	// 임시 socket 생성
	int tempSocket = socket( AF_INET, SOCK_DGRAM, 0 );
	if( tempSocket == -1 )
	{
		// 임시 socket 생성 실패시..
		int errorNum = errno;
		LOG( LERR, "service nic check. socket create error. [%d][%s]", errorNum, strerror( errorNum ) );

		// 오류 발생 return
		return false;
	}

	struct ifmediareq stNicInfo;
	std::vector< std::string >::const_iterator it;

	for( it = m_vecServiceNic.begin(); it != m_vecServiceNic.end(); it++ )
	{
		// 저장 객체 초기화.
		memset( &stNicInfo, 0, sizeof( struct ifmediareq ) );

		// 추출할 NIC 정보 설정.
		// CHG 2020-10-29 huibong FreeBSD 12.x 의 gcc 9.3 사용시 warning 발생 코드 수정 (#33233)
		//     - gcc 9.3 에서 strncpy 관련 warning 발생으로 인해 코드 검수 후 수정 처리.
		//strncpy( stNicInfo.ifm_name, it->c_str(), sizeof( stNicInfo.ifm_name ) );
		strncpy( stNicInfo.ifm_name, it->c_str(), sizeof( stNicInfo.ifm_name ) - 1 );
		
		if( ioctl( tempSocket, SIOCGIFMEDIA, (caddr_t)&stNicInfo ) < 0 ) 
		{
			/*
			* Interface doesn't support SIOC{G,S}IFMEDIA.
			*/
			int errorNum = errno;
			LOG( LERR, "service nic check. ioctl error. [%s][%d][%s]", it->c_str(), errorNum, strerror( errorNum ) );

			// 임시 socket close 처리.
			close( tempSocket );

			// 오류 발생 return
			return false;
		}
		
		// 추출된 NIC 상태 코드 정보가 유효한지 판단.
		if( stNicInfo.ifm_status & IFM_AVALID )
		{
			if( stNicInfo.ifm_status & IFM_ACTIVE )
			{
				// 정상 상태인 경우.
				//_LOG( LDBG, "[Service] service nic check ok.[%s]", it->c_str() );
			}
			else
			{
				// 정상 상태가 아닌 경우..
				// - 관련 내역 로깅 후
				// - false 반환 처리.
				_LOG( LINF, "[Service] service nic not active.[%s][%d]", it->c_str(), stNicInfo.ifm_status );

				// 임시 socket close 처리.
				close( tempSocket );

				// 오류 발생 return
				return false;
			}
		}
		else
		{
			// 추출된 NIC 상태 코드 정보가 유효하지 않은 경우... ( 이런 경우 거의 없으나.. 혹시... )
			// - 관련 내역 로깅 후
			// - false 반환 처리.
			_LOG( LINF, "[Service] service nic check. status code not vaild.[%s][%d]", it->c_str(), stNicInfo.ifm_status );

			// 임시 socket close 처리.
			close( tempSocket );

			// 오류 발생 return
			return false;
		}
	}

	// 임시 socket close 처리.
	close( tempSocket );

	return true;
}

// 멤버 변수 m_vecSvcNic 에 저장된 서비스 NIC 에 대한 IP 정보를 추출하여 멤버 변수 m_vecServiceIp 에 저장
// - true  : 추출 성공시.
// - false : 추출된 service IP 정보가 존재하지 않는 경우. ( 오류 발생 등으로 인해 추출 불가시 포함)
// - 실제 conf 에는 N 개의 서비스 NIC 을 설정 했더라도.. 실제 N 개 이하의 NIC 만 서비스에 사용할 수 있으므로..
// - service IP 정보가 없는 경우에만.. 오류로 처리한다.
bool CServiceCheck::GetServiceIp()
{
	// 서비스 IP 정보를 저장할 멤버 변수 초기화
	m_vecServiceIp.clear();
	
	// 서비스 NIC 정보가 비어 있는 경우...
	// - 공인 IP 정보 추출이 불가능 하므로... 오류로 처리.
	if( m_vecServiceNic.empty() == true )
	{
		_LOG( LINF, "[Service] service nic ip check. service nic not exist." );		
		return false;
	}

	// 참고사항
	// socket 과 ioctl SIOCGIFADDR 플래그를 이용하여 IP 정보를 추출할 수 있지만..
	// IPv4, IPv6 지원을 위해 각각 socket 생성 후 루프를 돌려야 함.
	// 따라서.. ifconfig binary 에서 사용된 getifaddrs() 함수를 이용하여 한번에 추출 처리함.

	struct ifaddrs * pInterfaceAll   = NULL;   // 전체 Interface 정보를 저장하기 위한 포인터.
	struct ifaddrs * pInterfaceEntry = NULL;   // 각 개별 정보를 가르키는 구조체 포인터.

	// 주소 관련 구조체에 대한 포인터
	// - IPv4 인 경우 struct sockaddr_in , IPv6 인 경우 struct sockaddr_in6 을 가르켜야 하므로... 
	// - void 형 포인터로 선언.
	void * pAddress = NULL;
	
	// 추출된 IP 정보를 저장하기 위한 임시 변수.
	char szAddrBuffer[INET6_ADDRSTRLEN];

	struct IpAddressInfo stIpAddr;
	std::vector< std::string >::const_iterator it;
	
	// Interface 정보 추출.
	// - 동적 생성된 Memory 를 반환하므로.. freeifaddrs() 함수를 이용하여 free 처리 해야 함.
	if( getifaddrs( &pInterfaceAll ) == 0 )
	{
		// interface 정보 추출 성공시
		// - 추출된 정보에 대해 루프를 돌면서 유효성 검증 후
		// - 서비스 NIC 에 등록된 interface 인 경우.. IP 정보를 추출하여 저장 처리한다.
		for( pInterfaceEntry = pInterfaceAll; pInterfaceEntry != NULL; pInterfaceEntry = pInterfaceEntry->ifa_next )
		{
			// 주소 정보가 유효하지 않은 경우 제외. ( 즉 NIC 은 있는데.. 주소를 할당하지 않은 경우. )
			if( pInterfaceEntry->ifa_addr->sa_data == NULL )
				continue;

			// interface name 정보가 유효하지 않은 경우 제외.
			if( pInterfaceEntry->ifa_name == NULL )
				continue;
						
			if( pInterfaceEntry->ifa_addr->sa_family == AF_INET )
			{
				// IPv4 인 경우.
				pAddress = &( ( struct sockaddr_in * )pInterfaceEntry->ifa_addr )->sin_addr;
			}
			else if( pInterfaceEntry->ifa_addr->sa_family == AF_INET6 )
			{
				// IPv6 인 경우.				
				pAddress = &( ( struct sockaddr_in6 * )pInterfaceEntry->ifa_addr )->sin6_addr;				
			}
			else 
			{
				//It isn't IPv4 or IPv6
				continue;
			}

			// 추출된 Interface 정보가 서비스 NIC 으로 등록된 경우에만.. IP 정보를 저장 처리한다.
			for( it = m_vecServiceNic.begin(); it != m_vecServiceNic.end(); it++ )
			{
				if( strcmp( pInterfaceEntry->ifa_name, it->c_str() ) == 0 )
				{
					// 추출된 interface name 이 서비스 NIC 으로 등록되어 있는 경우...
					// ip 정보를 m_vecServiceIp 변수에 저장 처리한다.
					
					// inet_ntoa() 함수가 static 메모리 사용으로 인해 Thread-safe 하지 않으므로..
					// inet_ntop() 함수 사용.
					memset( szAddrBuffer, 0x00, sizeof( szAddrBuffer ) );
					if( inet_ntop( pInterfaceEntry->ifa_addr->sa_family, pAddress, szAddrBuffer, sizeof( szAddrBuffer )) != NULL )					
					{
						stIpAddr.type = pInterfaceEntry->ifa_addr->sa_family;
						stIpAddr.ip   = szAddrBuffer;
						m_vecServiceIp.push_back( stIpAddr );

						_LOG( LDBG, "[Service] service nic ip [%s][%s]", pInterfaceEntry->ifa_name, szAddrBuffer );
					}
					else
					{
						// 오류 발생시..
						// - 관련 내역 로깅만 하고.. 계속 진행.
						int errorNum = errno;
						LOG( LERR, "service nic ip check. inet_ntop error. [%s][%d][%s]", it->c_str(), errorNum, strerror( errorNum ) );
					}
				}
			}
		}
	}
	else
	{
		// interface 정보 추출 실패시...
		// - 실제 추출 결과는 맨 마지막에 체크하므로...  오류 내역 로깅만 처리.
		int errorNum = errno;
		LOG( LERR, "service nic ip check. getifaddrs error. [%d][%s]", errorNum, strerror( errorNum ) );
	}

	// getifaddrs() 함수에 의해 동적 생성된 메모리 해제 처리.
	if( pInterfaceAll != NULL )
		freeifaddrs( pInterfaceAll );
		
	
	// 공인 IP 정보가 존재하지 않는 경우..
	if( m_vecServiceIp.empty() == true )
	{
		_LOG( LINF, "[Service] service nic ip not found." );
		return false;
	}
	else
		return true;
}

// Apache 의 80 port 에 접속 가능한지 check.
// return  true  : 정상
//         false : 추출된 서비스 IP 로 접속 테스트 수행시.. 1개라도 접속이 실패한 경우(Timeout 발생 포함)
// - 2015-05-11 현재 opendav 가 80 port listen 처리시 IPv4 에 대해서만 Listen 처리하므로...
// - 추출된 서비스 IP 중 IPv4 에 대해서만 테스트를 수행한다.
bool CServiceCheck::IsAliveApache()
{
	// IPv6 인 IP 에 대해서는 테스트를 수행하지 않음.
	// 따라서.. IPv6 만 존재하는 경우.. 실패로 처리한다.
	bool bAlive = false;

	int sock = -1;
	
	int errorNum = 0;
		
	struct linger opt_linger;
	struct sockaddr_in  stTarget4;
	int flags;
	int result = 0;
		
	//struct sockaddr_in6 stTarget6;
	//stTarget6.sin6_family = AF_INET6;
	//stTarget6.sin6_port   = htons( PORT_APACHE_LISTEN );
	
	std::vector< struct IpAddressInfo >::const_iterator it;	
	for( it = m_vecServiceIp.begin(); it != m_vecServiceIp.end(); it++ )
	{
		// IPv4 인 경우...
		if( it->type == AF_INET )
		{
			sock = socket( AF_INET, SOCK_STREAM, 0 );
			if( sock == -1 )
			{
				errorNum = errno;
				LOG( LERR, "apache alive check. socket create error. [%s][%d][%s]", it->ip.c_str(), errorNum, strerror( errorNum ) );

				// socket 생성 실패시 접속 실패로 처리.
				return false;
			}

			// Connnection 종료시 많은 TIME_WAIT 상태 발생 방지를 위해 SO_LINGER 옵션을 설정처리한다.			
			opt_linger.l_onoff  = 1; /* LINGER ON */

			// 0 : No Wait for abortive disconnect
			// - FreeBSD 6.2 apache 로그 (54)Connection reset by peer: apr_socket_addr_get(APR_LOCAL) 다수 발생.
			// - 0 으로 설정할 경우. close 처리시.. 상대편에서 정상 종료를 제대로 인지 못할 수 있음
			// - 0 이외 정수값으로 설정할 경우.. TCP TIME_WAIT 이 발생하며... 60 sec 후 정리됨. ( 4 sec 마다 체크할 경우.. 15 개의 TIME_WAIT 존재)
			// - 따라서 ..  0 이 아닌 1 sec 로 유지 처리.
			opt_linger.l_linger = 1; 
			
			if( setsockopt( sock, SOL_SOCKET, SO_LINGER, &opt_linger, sizeof( opt_linger )) != 0 )			
			{
				// 오류가 발생한 경우..
				// - 관련 내역 로깅만 처리하고 check 작업은 그대로 진행한다.
				errorNum = errno;
				LOG( LWAR, "apache alive check. setsockopt SO_LINGER set error [%d][%s]", errorNum, strerror( errorNum ) );
			}
			
			memset( &stTarget4, 0x00, sizeof( struct sockaddr_in ) );
			stTarget4.sin_family      = AF_INET;
			stTarget4.sin_port        = htons( PORT_APACHE_LISTEN );
			stTarget4.sin_addr.s_addr = inet_addr( it->ip.c_str() );

			// connection timeout 관련 처리
			// - 접속 대상 장비가 부하 상황인 경우...  connect() 함수의 block 발생 (#9276)
			// - 이를 해결하기 위해 connection timeout 를 구현
			// - non block socket 으로 만든 후.. connect 호출. select 함수를 호출하여 정상 처리 여부 확인.
			
			/* Non Block Mode Set */
			if( ( flags = fcntl( sock, F_GETFL, 0 ) ) >= 0 )
			{
				//flags |= O_NONBLOCK;
				if( fcntl( sock, F_SETFL, flags | O_NONBLOCK ) < 0 )
				{
					errorNum = errno;
					LOG( LWAR, "apache alive check. socket NONBLOCK set error [%d][%s]", errorNum, strerror( errorNum ) );
					
					close( sock );

					// 접속 실패로 처리.
					return false;
				}
			}
			else
			{
				// fcntl 호출 오류시
				errorNum = errno;
				LOG( LWAR, "apache alive check. socket fcntl get error [%d][%s]", errorNum, strerror( errorNum ) );

				// 접속 실패로 처리.
				close( sock );
				return false;
			}


			// 접속 시도
			result = connect( sock, ( struct sockaddr * )&stTarget4, sizeof( stTarget4 ) );
			if( result < 0 )
			{
				// 중요 코드
				// - connect 시도 후 결과를 바로 확인하지 않고... select 함수에서 결과 확인시..
				// - 오류 코드 clear 발생 등의 문제로 인해...
				// - 실제 접속이 되지 않았는데.. 접속된 것으로 처리 될 수 있음.
				// - 이를 방지하기 위해 본 코드가 중요함.

				errorNum = errno;

				// non-block 모드이므로.. 처리 중인 경우 제외하고 오류로 처리.
				if( errorNum != EINPROGRESS )
				{
					_LOG( LINF, "[Service] apache alive check. connect fail.[%s][%d]->[%d][%s]", it->ip.c_str(), PORT_APACHE_LISTEN, errorNum, strerror( errorNum ) );

					close( sock );
					return false;
				}
			}


			// 바로 접속이 된 경우...
			if( result == 0 )
			{
				// 관련 내역 로깅
				bAlive = true;
				_LOG( LDBG, "[Service] apache alive check ok. ipv4[%s][%d]", it->ip.c_str(), PORT_APACHE_LISTEN );
			}
			else
			{
				// 바로 접속이 되지 않은 경우....
				// select 함수를 이용하여 timeout 여부 확인.

				// connect timeout 설정 관련 변수
				fd_set selectReadFds, selectWriteFds;
				struct timeval timeOver;

				FD_ZERO( &selectReadFds );
				FD_SET( sock, &selectReadFds );
				selectWriteFds = selectReadFds;

				// 2016-04-06 FreeBSD 6.2 에서 잦은 Timout 발생
				// 이에 따라 timeout 값을 1 -> 2 sec 로 변경했으나 효과 없음.
				timeOver.tv_sec  = 1;             /* 1 second timeout */
				timeOver.tv_usec = 0;


				// select 함수를 이용한 접속 결과 관련 처리.
				if( select( sock + 1, &selectReadFds, &selectWriteFds, NULL, &timeOver ) == 1 )
				{
					errorNum = 0;
					socklen_t errorLength = sizeof( errorNum );

					if( getsockopt( sock, SOL_SOCKET, SO_ERROR, &errorNum, &errorLength ) == -1 )
					{
						// 오류 발생시.
						errorNum = errno;
						LOG( LERR, "apache alive check fail. getsockopt SO_ERROR get error [%d][%s] ipv4[%s][%d]"
							 , errorNum, strerror( errorNum ), it->ip.c_str(), PORT_APACHE_LISTEN );

						close( sock );
						return false;
					}
					else
					{
						if( errorNum == 0 )
						{
							// 접속 성공시.
							bAlive = true;
							_LOG( LDBG, "[Service] apache alive check[wait] ok. ipv4[%s][%d]", it->ip.c_str(), PORT_APACHE_LISTEN );
						}
						else
						{
							// 접속 실패시.
							_LOG( LINF, "[Service] apache alive check. connect[wait] fail.[%s][%d]->[%d][%s]"
								  , it->ip.c_str(), PORT_APACHE_LISTEN, errorNum, strerror( errorNum ) );

							close( sock );
							return false;
						}
					}
				}
				else
				{
					// connect timeout or select 함수 오류 발생시
					// - 접속 실패로 처리.
					// - 실제 Apache 가 죽어 있는 경우... 접속을 시도하면 대부분 위의 select 함수 반환을 통해.. 접속 실패 처리되지만...
					// - 종종 Timeout 처리 되는 경우가 발생.
					// - 따라서.. Timeout 의 경우.. 부하 등으로 인한 접속 실패 경우도 있지만... Apache 가 죽은 경우에도 Timeout 발생 가능함.
					// - 최종.. Timeout 발생시... Apache 가 죽은 것으로 판단하는 것이 합당함.
					errorNum = errno;
					_LOG( LINF, "[Service] apache alive check. connect timeout.[%s][%d] [%d][%s]", it->ip.c_str(), PORT_APACHE_LISTEN, errorNum, strerror(errorNum));
					
					close( sock );
					return false;
				}
			}
					
			// 사용한 socket 종료 처리.
			//fcntl( sock, F_SETFL, flags );
			close( sock );
		}
		// IPv6 인 경우..
		// - check 수행하지 않고 pass 처리.
		//else if( it->type == AF_INET6 )
	}


	if( bAlive == false )
	{
		// IPv6 인 IP 만 존재하는 경우.
		_LOG( LINF, "[Service] apache alive check. service ipv4 not found.");

		// 접속 실패로 처리.
		return false;
	}
	else
	{
		// 정상인 경우..
		return true;
	}
}


// DNS resolve 체크 수행.
// - 장비의 hostname 을 기준으로 m_vecCheckDns 에 정의된 DNS 서버에 resolve 질의
// - resolve 결과가.. m_vecServiceIp 에 존재하는 경우.. true.
// - 만약 resolve 결과가 m_vecServiceIp 에 존재하지 않는 경우... 
// -- 해당 domain 이 다른 장비를 가르키는 것일 수 있으므로.. fasle 반환.

// CHG 2019-09-04 huibong DNS resolve 기능 개선 (#32786)
// - Alive DNS 에 대해서만 resolve check 기능 추가
// - DNS 설정 정보 또는 Alive DNS 존재 하지 않을 경우.. 시스템에 설정된 default nameserver 를 이용하여 resolve check 하도록 flow 수정
// - 로깅 보완.
bool CServiceCheck::IsResolveDns()
{
	// DNS 정보가 존재하지 않는 경우..
	// - 2019-09-03 원래는 그냥 통과였으나... default DNS check 하도록 flow 수정
	if( m_vecCheckDns.empty() == true )
	{
		LOG( LINF, "[Service] missing CHECK_DNS_LIST info in conf. perform the default dns check.");
		return IsResolveDefaultDns();
	}

	char tempBuffer[128];
	std::string strTemp;
	FILE * fd = NULL;

	std::vector<std::string> vecAliveCheckDns;
	std::vector< std::string >::const_iterator it;
	std::vector< struct IpAddressInfo >::const_iterator pos;
	
	bool bExistResult;	   // dig 조회 결과가 존재하는지 확인 처리를 위한 플래그.
	bool bExistlocal;      // dig 로 조회된 IP 가 서비스 IP 상에 존재하는지 판단을 위한 플래그.


	// Alive DNS 정보 추출한다.
	vecAliveCheckDns.clear();
	for( it = m_vecCheckDns.begin(); it != m_vecCheckDns.end(); it++ )
	{
		// dig 명령 수행.
		snprintf( tempBuffer, sizeof( tempBuffer ), "dig %s +short", it->c_str());

		fd = popen( tempBuffer, "r" );
		if( fd == NULL )
		{
			// 오류 발생시 alive DNS 목록에 등록하지 않는다.
			int errorNum = errno;
			LOG( LERR, "[Service] dns alive check. dig error [%s] [%d][%s]", tempBuffer, errorNum, strerror( errorNum ) );
		}
		else
		{
			bExistResult = false;
			memset( tempBuffer, 0x00, sizeof( tempBuffer ) );

			// dig 결과 존재시
			while( fgets( tempBuffer, sizeof( tempBuffer ) - 1, fd ) != NULL )
			{
				strTemp = tempBuffer;
				Trim( strTemp );

				if( strTemp.empty() == false )
				{
					// data 가 존재하는 경우....
					// inet_aton() 함수를 이용하여 숫자 변환을 시도. 성공하는 경우.. 정상 IP 로 판단.
					struct sockaddr_in addr_inet;
					if( inet_aton( strTemp.c_str(), &addr_inet.sin_addr ) == 1 )
					{
						// 결과값이 정상인 경우... flag 변경
						bExistResult = true;
						_LOG( LDBG, "[Service] dns alive check ok. [%s][%s]", it->c_str(), strTemp.c_str() );
					}
					else
					{
						// inet_aton() 함수 호출 실패시.
						_LOG( LERR, "[Service] dns alive check fail. inet_aton() conversion error. [%s][%s]", it->c_str(), strTemp.c_str() );
					}
				}
				else
				{
					// 빈 응답인 경우.... dns resolve 실패로 판단한다.
					_LOG( LERR, "[Service] dns alive check error. [%s][empty response]", it->c_str() );
				}
			}

			if( bExistResult == true )
			{
				// 정상 결과값이 존재하는 경우... 해당 DNS 를 alive DNS 에 등록한다.
				vecAliveCheckDns.push_back( *it );
				_LOG( LDBG, "[Service] alive dns add. [%s]", it->c_str());
			}
			else
			{
				_LOG( LERR, "[Service] dns alive check failed. dns removed in alive list. [%s]", it->c_str() );
			}
		}
	}

	// Alive DNS 정보가 존재하지 않는 경우..
	// - default DNS check 
	if( vecAliveCheckDns.empty() == true )
	{
		LOG( LINF, "[Service] alive dns not exist. perform the default dns check." );
		return IsResolveDefaultDns();
	}

	// Alive DNS 가 존재하는 경우....
	for( it = vecAliveCheckDns.begin(); it != vecAliveCheckDns.end(); it++ )
	{
		// dig 명령 수행.
		snprintf( tempBuffer, sizeof( tempBuffer ), "dig @%s %s +short", it->c_str(), m_pFhsShm->GetHostName() );
		_LOG( LDBG, "[Service] dns resolve check.[%s]", tempBuffer );

		fd = popen( tempBuffer, "r" );
		if( fd == NULL )
		{
			int errorNum = errno;
			LOG( LERR, "[Service] dns resolve check. dig error[%s][%d][%s]", tempBuffer, errorNum, strerror( errorNum ) );
			// 오류 발생시 false 반환
			return false;
		}
		else
		{
			// 최초 결과값이 존재하지 않는 것으로 설정.
			bExistResult = false;
			memset( tempBuffer, 0x00, sizeof( tempBuffer ) );

			// dig 결과 존재시
			while( fgets( tempBuffer, sizeof( tempBuffer ) - 1, fd ) != NULL )
			{
				strTemp = tempBuffer;
				Trim( strTemp );
				
				bExistResult = true;
				_LOG( LDBG, "[Service] dns resolved ip [%s][%s]", it->c_str(), strTemp.c_str() );

				// 추출된 IP 정보가 m_vecServiceIp 상에 존재하는지 확인한다.				
				bExistlocal = false;
				for( pos = m_vecServiceIp.begin(); pos != m_vecServiceIp.end(); pos++ )
				{
					if( strTemp == pos->ip )
					{
						bExistlocal = true;
						break;
					}
				}

				// dig 조회된 IP 정보가 NIC 에 할당된 서비스 IP 정보가 아닌 경우..
				if( bExistlocal == false )
				{
					pclose( fd );

					_LOG( LERR, "[Service] dns resolved ip not found in nic [%s][%s]", it->c_str(), strTemp.c_str() );
					return false;
				}
			}

			// dig 결과가 존재하지 않는 경우.. 실패로 처리.
			// - 해당 DNS 에 IP가 등록되지 않았거나...
			// - 해당 DNS 정보가 잘못 설정되었거나....
			// - 해당 DNS 가 죽은 경우....			
			if( bExistResult == false )
			{
				pclose( fd );				

				_LOG( LERR, "[Service] dns resolved ip nothing. check dns or conf [%s][%s]", it->c_str(), m_pFhsShm->GetHostName() );
				return false;
			}
		}
		pclose( fd );
		fd = NULL;
	}
	
	return true;
}


// Default DNS resolve check
// - 장비의 hostname 을 기준으로 시스템에 설정된 기본 DNS 서버에 resolve 질의
// - resolve 결과가.. m_vecServiceIp 에 존재하는 경우.. true.
// - 위의 DNS check 관련 설정 정보가 없거나.. Alive DNS 가 없는 경우 처리를 수행하기 위한 보조 방법임
bool CServiceCheck::IsResolveDefaultDns()
{
	bool bExistResult;	   // dig 조회 결과가 존재하는지 확인 처리를 위한 플래그.
	bool bExistlocal;      // dig 로 조회된 IP 가 서비스 IP 상에 존재하는지 판단을 위한 플래그.

	char tempBuffer[128];
	std::string strTemp;
	FILE * fd = NULL;

	// dig 명령 수행.
	snprintf( tempBuffer, sizeof( tempBuffer ), "dig %s +short", m_pFhsShm->GetHostName() );
	//_LOG( LDBG, "[Service] local dns resolve check.[%s]", tempBuffer );

	fd = popen( tempBuffer, "r" );
	if( fd == NULL )
	{
		int errorNum = errno;
		LOG( LERR, "[Service] default dns resolve check. dig error[%s][%d][%s]", tempBuffer, errorNum, strerror( errorNum ) );
		// 오류 발생시 false 반환
		return false;
	}
	else
	{
		// 최초 결과값이 존재하지 않는 것으로 설정.
		bExistResult = false;
		memset( tempBuffer, 0x00, sizeof( tempBuffer ) );

		// dig 결과 존재시
		while( fgets( tempBuffer, sizeof( tempBuffer ) - 1, fd ) != NULL )
		{
			strTemp = tempBuffer;
			Trim( strTemp );

			bExistResult = true;
			_LOG( LDBG, "[Service] default dns resolved ip [%s][%s]", m_pFhsShm->GetHostName(), strTemp.c_str() );

			// 추출된 IP 정보가 m_vecServiceIp 상에 존재하는지 확인한다.				
			bExistlocal = false;
			std::vector< struct IpAddressInfo >::const_iterator pos;

			for( pos = m_vecServiceIp.begin(); pos != m_vecServiceIp.end(); pos++ )
			{
				if( strTemp == pos->ip )
				{
					bExistlocal = true;
					break;
				}
			}

			// dig 조회된 IP 정보가 NIC 에 할당된 서비스 IP 정보가 아닌 경우..
			if( bExistlocal == false )
			{
				pclose( fd );

				_LOG( LERR, "[Service] default dns resolved ip not found in local [%s][%s]", m_pFhsShm->GetHostName(), strTemp.c_str() );
				return false;
			}
			else
			{
				_LOG( LDBG, "[Service] default dns resolved ip check ok. [%s][%s]", m_pFhsShm->GetHostName(), strTemp.c_str() );
			}
		}

		// dig 결과가 존재하지 않는 경우.. 실패로 처리.
		// - default DNS 에 IP가 등록되지 않았거나...
		// - default DNS 정보가 잘못 설정되었거나....
		// - default DNS 가 죽은 경우....			
		if( bExistResult == false )
		{
			pclose( fd );

			_LOG( LWAR, "[Service] default dns resolved ip nothing. check nameserver in /etc/resolv.conf ");
			return false;
		}
	}

	pclose( fd );
	fd = NULL;

	return true;
}



// std 상에 trim 함수가 없어서 직접 구현. 아니면 boost/algorithm/string.hpp 상의 boost::trim 함수 사용
// @return void
void CServiceCheck::Trim( std::string & str )
{
	if( str.length() == 0 )
		return;

	// 문자열 뒤의 공백, TAB, CR 등의 문자 제거처리.
	std::string::size_type pos = str.find_last_not_of( " \a\b\f\n\r\t\v" );
	if( pos != std::string::npos )
		str.erase( pos + 1 );

	// 문자열 앞의 공백, TAB, CR 등의 문자 제거처리.
	pos = str.find_first_not_of( " \a\b\f\n\r\t\v" );
	if( pos != std::string::npos )
		str.erase( 0, pos );
}

// FHS 의 fimngd 프로세스 동작 여부 check.
// - init() 에서 설정된 m_uCheckFimngdPort 정보와 추출된 서비스 IP 를 이용하여 TCP 접속 테스트 수행.
// @return true  접속 성공시
//         false 접속 실패 또는 오류 발생시.
bool CServiceCheck::IsAliveFimngd()
{
	std::string strIp;
	strIp.clear();
	
	// 서비스 IP 정보 추출
	// - 앞의 업무 flow 에서 서비스 IP 존재 유무룰 판단했으므로.. 추가로 할 필요 없음.
	// - IPv4 인 정보 추출.
	std::vector< struct IpAddressInfo >::const_iterator it;
	for( it = m_vecServiceIp.begin(); it != m_vecServiceIp.end(); it++ )
	{
		// IPv4 인 경우...
		if( it->type == AF_INET )
		{
			strIp = it->ip;
			break;
		}
	}
	
	// IP 정보가 없는 경우...
	// - 127.0.0.1 을 사용.
	if( strIp.empty() == true )
		strIp = "127.0.0.1";
	
	// socket 관련 변수.
	int sock = -1;
	struct sockaddr_in  stTarget;
	int flags;

	int result = 0;
	int errorNum = 0;
		

	// socket 생성.	
	sock = socket( AF_INET, SOCK_STREAM, 0 );
	if( sock == -1 )
	{
		errorNum = errno;
		LOG( LERR, "fimngd alive check. socket create error. [%d][%s]", errorNum, strerror( errorNum ) );
		return false;
	}

	// Connnection 종료시 많은 TIME_WAIT 상태 발생 방지를 위해 SO_LINGER 옵션을 설정처리한다.
	struct linger opt_linger;
	opt_linger.l_onoff  = 1; /* LINGER ON */

	// 0 : No Wait for abortive disconnect	
	// - 0 으로 설정할 경우. close 처리시.. 상대편에서 정상 종료를 제대로 인지 못할 수 있음
	// - 0 이외 정수값으로 설정할 경우.. TCP TIME_WAIT 이 발생하며... 60 sec 후 정리됨. ( 4 sec 마다 체크할 경우.. 15 개의 TIME_WAIT 존재)
	// - fimngd 가 accept 처리 1 sec 후 무조건 close 처리하므로... 0 으로 설정해도 무방.
	opt_linger.l_linger = 0; 

	if( setsockopt( sock, SOL_SOCKET, SO_LINGER, &opt_linger, sizeof( opt_linger ) ) != 0 )
	{
		// 오류가 발생한 경우..
		// - 관련 내역 로깅만 처리하고 check 작업은 그대로 진행한다.
		errorNum = errno;
		LOG( LWAR, "fimngd alive check. setsockopt SO_LINGER set error [%d][%s]", errorNum, strerror( errorNum ) );
	}

	memset( &stTarget, 0x00, sizeof( struct sockaddr_in ) );
	stTarget.sin_family = AF_INET;
	stTarget.sin_port = htons( m_uCheckFimngdPort );
	stTarget.sin_addr.s_addr = inet_addr( strIp.c_str() );

	// connection timeout 관련 처리
	// - 접속 대상 장비가 부하 상황인 경우...  connect() 함수의 block 발생
	// - 이를 해결하기 위해 connection timeout 를 구현
	// - non block socket 으로 만든 후.. connect 호출. select 함수를 호출하여 정상 처리 여부 확인.
	
	/* Non Block Mode Set */
	if( ( flags = fcntl( sock, F_GETFL, 0 ) ) >= 0 )
	{		
		if( fcntl( sock, F_SETFL, flags | O_NONBLOCK ) < 0 )
		{
			errorNum = errno;
			LOG( LWAR, "fimngd alive check. socket NONBLOCK set error [%d][%s]", errorNum, strerror( errorNum ) );

			close( sock );
			return false;
		}
	}
	else
	{
		// fcntl 호출 오류시
		errorNum = errno;
		LOG( LWAR, "fimngd alive check. socket fcntl get error [%d][%s]", errorNum, strerror( errorNum ) );

		close( sock );
		return false;
	}
	
	// 접속 시도	
	result = connect( sock, ( struct sockaddr * )&stTarget, sizeof( stTarget ) );
	if( result < 0 )
	{
		// 중요 코드
		// - connect 시도 후 결과를 바로 확인하지 않고... select 함수에서 결과 확인시..
		// - 오류 코드 clear 발생 등의 문제로 인해...
		// - 실제 접속이 되지 않았는데.. 접속된 것으로 처리 될 수 있음.
		// - 이를 방지하기 위해 본 코드가 중요함.

		errorNum = errno;

		// non-block 모드이므로.. 처리 중인 경우 제외하고 오류로 처리.
		if( errorNum != EINPROGRESS )
		{
			_LOG( LINF, "[Service] fimngd alive check. connect fail.[%s][%d]->[%d][%s]", strIp.c_str(), m_uCheckFimngdPort, errorNum, strerror( errorNum ) );

			close( sock );
			return false;
		}
	}


	// 바로 접속이 된 경우...
	if( result == 0 )
	{
		// 관련 내역 로깅
		_LOG( LDBG, "[Service] fimngd alive check ok. ipv4[%s][%d]", strIp.c_str(), m_uCheckFimngdPort );
	}
	else
	{
		// 바로 접속이 되지 않은 경우....
		// select 함수를 이용하여 timeout 여부 확인.

		// connect timeout 설정 관련 변수
		fd_set selectReadFds, selectWriteFds;
		struct timeval timeOver;

		FD_ZERO( &selectReadFds );
		FD_SET( sock, &selectReadFds );
		selectWriteFds = selectReadFds;
		
		timeOver.tv_sec = 1;             /* 1 second timeout */
		timeOver.tv_usec = 0;
		
		// select 함수를 이용한 접속 결과 관련 처리.
		if( select( sock + 1, &selectReadFds, &selectWriteFds, NULL, &timeOver ) == 1 )
		{
			errorNum = 0;
			socklen_t errorLength = sizeof( errorNum );

			if( getsockopt( sock, SOL_SOCKET, SO_ERROR, &errorNum, &errorLength ) == -1 )
			{
				// 오류 발생시.
				errorNum = errno;
				LOG( LERR, "fimngd alive check fail. getsockopt SO_ERROR get error [%d][%s] ipv4[%s][%d]"
					 , errorNum, strerror( errorNum ), strIp.c_str(), m_uCheckFimngdPort );

				close( sock );
				return false;
			}
			else
			{
				if( errorNum == 0 )
				{
					// 접속 성공시.			
					_LOG( LDBG, "[Service] fimngd alive check[wait] ok. ipv4[%s][%d]", strIp.c_str(), m_uCheckFimngdPort );
				}
				else
				{
					// 접속 실패시.
					_LOG( LINF, "[Service] fimngd alive check. connect[wait] fail.[%s][%d]->[%d][%s]", strIp.c_str(), m_uCheckFimngdPort, errorNum, strerror( errorNum ) );

					close( sock );
					return false;
				}
			}
		}
		else
		{
			// connect timeout or select 함수 오류 발생시
			// - 접속 실패로 처리.				
			_LOG( LINF, "[Service] fimngd alive check. connect timeout.[%s][%d]", strIp.c_str(), m_uCheckFimngdPort );

			close( sock );
			return false;
		}
	}
		
	// 사용한 socket 종료 처리.
	close( sock );
	return true;
}

// Storage Write/Read 테스트 결과가 정상인지 Check
// - Storage 저장 공간에 대한 file write / read 테스트가 worker 프로세스에 의해 주기적으로 수행
// - 만약 test 실패 발생시 공유 메모리 상의 관련 값이 -1 로 설정됨
// - 만약 test 중 hang 발생시 공유 메모리 상의 값을 확인하여 timeout 여부 판단 필요.
// @return true  storage check 정상인 경우
//         false storage check 실패 또는 timeout 상황인 경우
bool CServiceCheck::IsNormalStatusStorageCheck()
{
	// 공유 메모리에 저장된 정보 추출
	time_t timeLastCheck = m_pFhsShm->GetTimeStorageCheck();

	if( timeLastCheck == -1 )
	{
		// Storage write/read 테스트 실패인 경우
		return false;
	}
	else if( timeLastCheck == 0 )
	{
		// 아직 프로세스 초기화 상태인 경우, 거의 발생하지 않지만..
		// 발생할 가능성 존재하여 판단 처리하며.. 정상으로 판단한다.
		return true;
	}
	else
	{
		// 그 외의 경우는 현재 시간을 추출하여 timeout 발생 여부 check 한다.
		struct timespec currentClock;
		memset( &currentClock, 0x00, sizeof( struct timespec ) );
		clock_gettime( CLOCK_MONOTONIC, &currentClock );

		if( timeLastCheck < currentClock.tv_sec - TIMEOUT_STORAGE_CHECK )
		{
			// timeout 발생시
			_LOG( LINF, "[Service] storage status check timeout[%d]. last success[%ld], current[%ld]"
				  , TIMEOUT_STORAGE_CHECK, timeLastCheck, currentClock.tv_sec );

			return false;
		}
	}

	// Storage write/read 테스트가 정상인 경우
	return true;
}

// System Collecotr 관련 Worker 프로세스가 정상 동작하는지 Check
// - system 상태 정보 수집을 위한 system collector worker 프로세스는 주기적으로 수행되며
// - 수집 시작시간, 수집된 정보를 공유메모리에 저장 
// - 만약 여러 요인에 의해 해당 worker 프로세스가 hang 상황인 경우 서비스 상태를 disable 로 변경 처리해야 함
// - 이를 판단하기 위한 함수로 공유메모리 정보를 이용하여 timeout 판단함.
// @return true  정상인 경우
//         false timeout 상황인 경우
bool CServiceCheck::IsNormalStatusSystemCollect()
{
	// 공유 메모리에 저장된 정보 추출
	time_t timeLastCheck = m_pFhsShm->GetTimeSystemCheck();

	if( timeLastCheck == 0 )
	{
		// 아직 프로세스 초기화 상태인 경우 
		// 발생할 가능성 존재하며.. 정상으로 판단한다.
		return true;
	}
	else
	{
		// 그 외의 경우는 현재 시간을 추출하여 timeout 발생 여부 check 한다.
		struct timespec currentClock;
		memset( &currentClock, 0x00, sizeof( struct timespec ) );
		clock_gettime( CLOCK_MONOTONIC, &currentClock );

		if( timeLastCheck < currentClock.tv_sec - TIMEOUT_SYSTEM_COLLECT )
		{
			// timeout 발생시
			_LOG( LINF, "[Service] system collect status timeout[%d]. last success[%ld], current[%ld]"
				  , TIMEOUT_SYSTEM_COLLECT, timeLastCheck, currentClock.tv_sec );

			return false;
		}
	}

	// 정상인 경우
	return true;
}




// ZFS 상태 관련 정보 추출하여 정상인지 Check
ZfsStatus CServiceCheck::GetZfsStatus()
{
	std::string strStorageRoot = CProcessConfig::GetInstance()->GetStorageRoot();

	// Storage 관련 mount 정보 추출 실패시
	// - 서비스 불가 상태로 처리 될 수 있도록 ERROR 정보 반환 
	if( m_storageInfo.Init( strStorageRoot.c_str() ) == false )
	{
		_LOG( LINF, "[Service] storage info object init faild." );
		return ZFS_ERROR;
	}

	// Storage 정보 추출이 정상적인 경우, Storage 에 ZFS file system 이 존재하는지 Check
	// - ZFS file system 이 없는 경우.. 정상 상태 return
	if( m_storageInfo.IsExistZFS() == false )
	{
		return ZFS_ONLINE;
	}


	// ZFS file system 이 존재하는 경우
	char tempBuffer[128];
	FILE * fd = NULL;

	// ZFS health check 는 check 과정상 오류 발생 가능성이 높으므로...
	// 명시적인 FAULTED, DEGRADED  등의 상태 정보만을 상태 판단의 근거로 하고..
	// 중간 과정 상의 오류는 로깅만 하고.. 그냥 정상으로 처리한다.
	snprintf( tempBuffer, sizeof( tempBuffer ), "zpool list -Ho name,health");
	fd = popen( tempBuffer, "r" );
	if( fd == NULL )
	{
		// ZFS health check 명령어 수행 실패시
		int errorNum = errno;
		LOG( LERR, "zfs health check popen error.[%d][%s]", errorNum, strerror( errorNum ) );

		// popen 오류는 경로 등의 문제로 발생할 수 있으므로.. 그냥 정상으로 처리한다.
		return ZFS_ONLINE;
	}

	// zpool 명령어가 실행된 경우
	std::string strTemp;
	std::stringstream ss;
	std::string strName;
	std::string strHealth;
	
	std::vector< ZfsStatus > vecHealth;		// 최종 결과를 저장할 vector

	memset( tempBuffer, 0x00, sizeof( tempBuffer ) );

	// 실행 결과는 다음과 같아 String 파싱 처리 필요
	// stg     ONLINE
	// zroot   ONLINE
	while( fgets( tempBuffer, sizeof( tempBuffer ) - 1, fd ) != NULL )
	{
		strTemp = tempBuffer;
		Trim( strTemp );

		// Tokenize
		ss.str( "" );		// 재사용전 초기화 처리, 무조건 빈값을 다시 넣어줘야 정상 동작
		ss.clear();			// 재사용전 초기화 처리, 무조건 clear 처리 해야 정상 동작.
		ss.str( strTemp );
		ss >> strName;
		ss >> strHealth;

		// strName (stg, zroot) 정보가 
		// CProcessConfig::GetInstance()->GetStorageRoot() 정보 ( /stg, /stg/node0 ) 상에 존재하는지 Check
		// Content 저장 경로를 N 개의 ZFS pool 로 묶을 수 있으므로.. 1개 아닌.. N 개 정보가 나올 수 있어
		// health 결과는 vector 에 저장 처리.
		if( strStorageRoot.find( strName ) != std::string::npos )
		{
			// vector 에 저장시... 문자열 정보를 숫자로 변환해서 저장한다.
			// 그냥 문자열로 저장할 경우.. 나중에 꺼내서 다시 Check 하는 비용이 더 듬.
			if( strcasecmp( strHealth.c_str(), "ONLINE" ) == 0 )
			{
				vecHealth.push_back( ZFS_ONLINE );
			}
			else if( strcasecmp( strHealth.c_str(), "DEGRADED" ) == 0 )
			{
				vecHealth.push_back( ZFS_READONLY );
			}
			else
			{
				// 나머지 상태는 FAULTED, OFFLINE, UNAVAIL, REMOVED 등이 있으며.. 대부분 서비스 불가 상태 처리해야 함.
				vecHealth.push_back( ZFS_ERROR );
			}

			_LOG( LINF, "[Service] zfs health check [%s : %s]", strName.c_str(), strHealth.c_str() );
		}
	}

	// open fd close
	pclose( fd );

	// 추출된 정보가 없는 경우...
	// - 명시적인 오류가 아니면.. 정상으로 처리한다.
	if( vecHealth.empty() == true )
	{
		_LOG( LWAR, "[Service] zfs file ststem exist [%s]. but zpool name not match. zfs health check process failed.", strStorageRoot.c_str() );
		return ZFS_ONLINE;
	}
	
	// 추출된 health check 결과를 보고 판단한다.
	// - N 개의 결과가 나올 수 있으므로...
	// - 우선 순위를 ERROR - READ ONLY - 그외 정상 순서로 체크한다.
	std::vector< ZfsStatus >::iterator it;
	for( it = vecHealth.begin(); it < vecHealth.end(); it++ )
	{
		if( *it == ZFS_ERROR )
		{
			_LOG( LINF, "[Service] zfs health check result: error (service disable)." );
			return ZFS_ERROR;
		}
	}
	for( it = vecHealth.begin(); it < vecHealth.end(); it++ )
	{
		if( *it == ZFS_READONLY )
		{
			_LOG( LINF, "[Service] zfs health check result: read only (service readonly)." );
			return ZFS_READONLY;
		}
	}


	// 명시적인 오류가 아니면.. 정상으로 처리한다.
	return ZFS_ONLINE;

}
