#include "ProperStorage.h"
#include "Logger.h"

#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <stdio.h>
#include <algorithm>

#ifdef __FreeBSD__
	#include <sys/param.h>
	#include <sys/ucred.h>
	#include <sys/mount.h>
#else	// Linux
	#include <mntent.h>
	#include <sys/vfs.h>
#endif


#define LIMIT_STORAGE_SIZE  10737418240LL      // 최소 Storage 용량.
#define DISABLE_FLAG	       ".disable"      // Mount disable 플래그


// 생성자
CProperStorage::CProperStorage( const std::string & strStorageRoot, const int nCheckInterval )
{
	// 멤버 변수 초기화	
	pthread_mutex_init( &m_mutexLock, NULL );	// Mutex Lock init.
	m_nCheckInterval = nCheckInterval;
	m_strProperPath.clear();
	m_nAvailableSize = 0;

	// 최상위 경로값에서 마지막 문자가 '/' 인 경우.. 제거 처리한다.
	//  - 업무 Flow 관련 경로 처리 문제 사전 해결
	//  - 시스템에서 추출된 mount 정보와 비교 처리시 정상 비교가 될 수 있도록..
	if( strStorageRoot.length() >  1 )
	{
		if( strStorageRoot.at( strStorageRoot.length() -1 ) == '/' )
		{
			m_strStorageRoot = strStorageRoot.substr( 0, strStorageRoot.length() -1 );
		}
		else
			m_strStorageRoot = strStorageRoot;
	}
	else
		m_strStorageRoot = strStorageRoot;
}

// 소멸자
CProperStorage::~CProperStorage()
{
	// 동작 중인 Thread 정지 처리.
	// - 만약 Thread 가 이미 종료된 경우 m_threadHandle 가 다른 Thread Handle 일 수 있으므로 업무 Flow 수정시 주의할 것
	pthread_cancel( m_threadHandle );

	// Mutex Lock 제거
	pthread_mutex_destroy( &m_mutexLock );
}

// Content Write 관련 최적 Mount 경로를 반환한다.
bool CProperStorage::GetProperPath( std::string& strProperPath )
{
	bool bResult;

	// Thread 와 동시 접근을 제어하기 위해 Lock 처리
	pthread_mutex_lock( &m_mutexLock );
	if( m_strProperPath.empty() == true )
	{
		// 최적 경로를 추출하지 못한 경우...
		bResult = false;	
		LOG( LERR, "proper mount path empty. return false.");
	}
	else
	{
		// 최적 경로가 존재하는 경우..
		strProperPath = m_strProperPath;
		bResult = true;		
		LOG( LDBG, "proper mount path return. [%s]", strProperPath.c_str() );
	}

	pthread_mutex_unlock( &m_mutexLock );
	return bResult;
}


// Content Write 관련 최적 Mount 경로 및 사용 가능 Free Size 를 반환한다.
bool CProperStorage::GetProperPath( std::string& strProperPath, unsigned long long & nAvailableSzie )
{
	bool bResult;

	// Thread 와 동시 접근을 제어하기 위해 Lock 처리
	pthread_mutex_lock( &m_mutexLock );
	if( m_strProperPath.empty() == true )
	{
		// 최적 경로를 추출하지 못한 경우...
		nAvailableSzie = 0;
		bResult = false;
		
		LOG( LERR, "proper mount path empty. return false.");
	}
	else
	{
		// 최적 경로가 존재하는 경우..
		strProperPath = m_strProperPath;
		nAvailableSzie = m_nAvailableSize;
		bResult = true;
		
		LOG( LDBG, "proper mount path return. [%s] free[%llu Byte]", strProperPath.c_str(), nAvailableSzie );
	}

	pthread_mutex_unlock( &m_mutexLock );
	return bResult;
}


// Content Write 관련 최적 Mount 경로의 사용 가능 Free Size 를 반환한다.
//  - GetProperPath() 함수를 통해 경로 정보 수신 후 본 함수를 호출할 경우...
//  - Thread 에 의해 경로 정보 및 Free 용량 정보가 변경되어 다른 경로에 대한 Free 용량 정보가 전달될 수 있으니... 사용시 주의한다.
void CProperStorage::GetAvailableSize( unsigned long long & nAvailableSzie )
{
	// Thread 와 동시 접근을 제어하기 위해 Lock 처리
	pthread_mutex_lock( &m_mutexLock );

	nAvailableSzie = m_nAvailableSize;
	LOG( LDBG, "proper mount free size [%llu] return. path[%s]", nAvailableSzie, m_strProperPath.c_str() );

	pthread_mutex_unlock( &m_mutexLock );
	
	return;
}


// Thread 를 생성하여 Mount 정보에 대한 검사를 주기적으로 수행한다.
bool CProperStorage::Start()
{
	// 생성자를 통해 전달받은 최상위 경로 정보가 유효하지 않은 경우..
	if( m_strStorageRoot.empty() == true )
	{
		LOG( LERR, "storage root path empty. ");
		return false;
	}

	// 생성자를 통해 전달받은 최상위 경로 정보가 '/' 정보인 경우에도 유효하지 않으므로 오류로 처리한다.
	if( strcmp( m_strStorageRoot.c_str(), "/") == 0 )
	{
		LOG( LERR, "storage root path not vaild. [%s]", m_strStorageRoot.c_str() );
		return false;
	}
	
	// Thread 를 생성 시킨다.
	int nResult = pthread_create( &m_threadHandle, 0, CProperStorage::threadFunc, this );
	if( nResult != 0 )
	{
		// Thread 생성 실패시...
		int errorNum = errno;
		LOG( LERR, "thread create failed.[%d][%s]", errorNum, strerror(errorNum) );

		return false;
	}

	return true;
}


// Thread 함수
void * CProperStorage::threadFunc( void * arg )
{
	CProperStorage * pObject = reinterpret_cast<CProperStorage *>(arg);    
	pthread_detach( pthread_self() );

	while(1)
	{
		pthread_testcancel();
		pObject->Execute();
		pthread_testcancel();

		// 지정된 시간동안 sleep..
		sleep( pObject->m_nCheckInterval );
	}

	return NULL;
}


// sort 처리를 수행하기 위한 함수.
bool SortMountAvailableSize( struct systme_mount_info fisrt, struct systme_mount_info second)
{
	// first 의 available_byte 용량 순으로 정렬.
	return ( fisrt.available_byte > second.available_byte ) ? true : false;
}


// 실제 시스템 Disk Mount 정보를 조회하여... 최적 Mount 경로 정보를 추출하기 위한 함수.
// 본 함수 Flow 로직은 코드를 단순하게 하여 라인 수를 줄여 compact 하게 할 수 있으나... 함수 Flow 이해가 어렵게 된다.
// 따라서.. 중복되는 상황이 나오더라도.. 업무 Flow 순서에 명확하도록 개발한다.
void CProperStorage::Execute()
{
	// Start() 함수에서 최상위 경로 인자값에 대한 검사를 했으므로.... 최상위 경로값을 정상이라고 판단하고 검사를 수행한다.
	// 최상위 경로값은 다음과 같이 세팅될 수 있다.
	// - /stg , /stg/, /stg/node0 , /stg/node0/ 
	// - /user2 , /user2/, /user2/dav_storage, /user2/dav_storage/

	// 1. 최상위 경로값에서 마지막 문자가 '/' 인 경우.. 제거 처리한다.
	//  - 업무 Flow 관련 경로 처리 문제 사전 해결
	//  - 시스템에서 추출된 mount 정보와 비교 처리시 정상 비교가 될 수 있도록..	
	// => 본 처리는 생성자에서 처리를 수행하므로.. pass....	
	
	// 2. 장비의 Mount 정보 와 available 용량 정보를 추출한다.	
	if( GetSystmeMountInfo() == false )
	{
		// 시스템의 Mount 정보 추출 실패 또는 Mount 정보가 존재하지 않는 경우...
		// Mount 정보 추출 실패로 인해 최적 경로 정보를 invalid 정보로 설정 처리한다.
		SetProperPathError();
		return;
	}
	
	// Mount 정보 저장 관련 임시 객체
	struct systme_mount_info stMountInfo;
	
	// 3. 수정된 최상위 경로값과 동일한 장비 Mount 정보가 존재하는지 비교한다.
	//  - 비교시 문자열 Full 비교 수행.. ( /stg/node0 등으로 설정된 경우를 위해 )

	// 각 조건에 맞아 추출된 Mount 정보를 저장하기 위한 임시 변수.
	std::vector<struct systme_mount_info> vecTempMount;
	std::vector<struct systme_mount_info>::iterator it;

	//_LOG( LDBG, "Worker[%d]::ProperStorage: mount full scan start. path[%s]", getpid(), m_strStorageRoot.c_str() );

	for( it = m_vecMount.begin(); it < m_vecMount.end(); it++ )
	{
		if( strcmp( m_strStorageRoot.c_str(), it->mount_name.c_str() ) == 0 )
		{
			stMountInfo.mount_name     = it->mount_name;
			stMountInfo.available_byte = it->available_byte;

			vecTempMount.push_back( stMountInfo );
		
			_LOG( LDBG, "Worker[%d]::ProperStorage: mount name full scan. [%s] available byte[%llu]"
					, getpid(), stMountInfo.mount_name.c_str(), stMountInfo.available_byte );
		}
	}

	// 4. 만약 위의 3번 항목에서 Mount 장비를 찾지 못한 경우....
	//  - conf 상에 설정된 경로 정보가 실제 Mount 정보를 나타내는 것이 아니다.
	//  - 수정된 최상위 경로부터 부분 비교를 시작해서 없을 경우.. sub path 부분을 제외한 후 다시 장비 Mount 정보와 비교한다. 
	//  - 장비 mount 정보를 찾을 때까지 본 비교를 반복하여 수행한다.
	if( vecTempMount.empty() == true )
	{
		// 최초 비교시에는 원래 정보를 가지고 부분 비교처리.. (/stg 등으로 설정된 경우를 위해)
		std::string strPreviousPath = m_strStorageRoot;
		std::string strCurrentPath  = m_strStorageRoot;

		do
		{
			//_LOG( LDBG, "Worker[%d]::ProperStorage: mount partical scan start.. path[%s]", getpid(), strCurrentPath.c_str() );

			// 수정된 경로 정보만을 가지고 부분 비교를 수행....
			for( it = m_vecMount.begin(); it < m_vecMount.end(); it++ )
			{
				// 부분 비교 처리
				if( strncmp( strCurrentPath.c_str(), it->mount_name.c_str(), strlen(strCurrentPath.c_str()) ) == 0 )
				{
					stMountInfo.mount_name     = it->mount_name;
					stMountInfo.available_byte = it->available_byte;

					vecTempMount.push_back( stMountInfo );

					_LOG( LDBG, "Worker[%d]::ProperStorage: mount partical scan. path[%s], mount[%s] available byte[%llu]"
							, getpid(), strCurrentPath.c_str(), stMountInfo.mount_name.c_str(), stMountInfo.available_byte );
				}
			}

			// 위에서 정보 추출이 실패한 경우..상위 Path 정보를 추출한다. ( /user2/dav_storage, /stg/node0/aa/bb 등으로 설정된 경우를 위해 )
			if( vecTempMount.empty() == true )
			{
				GetUpperPath( strPreviousPath, strCurrentPath );
				if( strCurrentPath.empty() == true )
				{
					break;
				}

				strPreviousPath = strCurrentPath;
			}	

		} while( vecTempMount.empty() == true );
	}

	// 5. 3,4 항목에서 장비 mount 를 찾은 경우....
	//  
	// 5.1 1개 Mount 만 검색된 경우...
	//     - 최종 마운트 경로 정보는 1 항목에서 수정된 경로이며.....
	//     - 여유용량이 임계치를 이상이면 정상, 임계치 미만이면 오류 처리한다.
	//     - 1개 이므로.. mount 에 대한 disable 검사는 수행하지 않는다. (왜냐면 소용 없으므로... )
	//     
	// 5.2 다중 Mount 가 검색된 경우...
	//     - 각 mount 에 대한 여유용량 임계치를 검사한다.
	//       만약 여유용량이 임계치 미만이면 목록에서 제거한다.
	//     - 수정된 mount 목록에서 각 Mount 경로에 대한 disable 검사를 수행한다. 
	//     - 최종... disable 되지 않고.. 여유 용량이 가장 많은 mount 를 선택한다.
	//       만약 전부 disable 된 경우... disable 을 무시하고 여유용량이 가장 많은 mount 를 선택한다.
	//     - 최종 마운트 경로 정보는 시스템에서 추출된 Mount 경로를 사용한다. ( 5.1 과는 다른 선택임 )

	if( vecTempMount.empty() == true )
	{
		// 적합한 Mount 경로를 찾지 못한 경우...
		// Mount 정보 추출 실패로 인해 최적 경로 정보를 invalid 정보로 설정 처리한다.
		SetProperPathError();
		LOG( LERR, "proper mount path find failed. conf path[%s]", m_strStorageRoot.c_str() );
	}
	else
	{
		// 시스템에서 1개 Mount 정보만 검색된 경우.
		if( vecTempMount.size() == 1 )
		{
			// 여유용량에 대한 임계치를 검사 후...
			// 최적 Mount 경로 정보는 운영자가 설정한 경로 정보로 설정한다.. (/user2/dav_storage , /stg/node0, /stg/node0/aa/bb 등의 경우)
			if( vecTempMount[0].available_byte < LIMIT_STORAGE_SIZE )
			{				
				LOG( LERR, "proper mount not found. mount[%s] available size[%llu] limit[%llu]"
						, m_strStorageRoot.c_str(), vecTempMount[0].available_byte, LIMIT_STORAGE_SIZE );

				SetProperPathError();
			}
			else
			{
				// CHG 2015-03-27 huibong
				// 설정값이 /stg 이고.. mount 정보가 1개 /stg/node0 인 경우...
				// 아래 로직에 의해 경로값이 설정값인 /stg 가 선택되는 문제 발생. ( #21917 )
				// 이를 해결하기 위해.. 설정값과 추출된 mount 이름 중 길이가 큰 값을 사용하도록 수정 처리한다.
				// SetProperPath(  m_strStorageRoot, vecTempMount[0].available_byte );

				if( m_strStorageRoot.size() > vecTempMount[0].mount_name.size() )
					SetProperPath( m_strStorageRoot, vecTempMount[0].available_byte );
				else
					SetProperPath( vecTempMount[0].mount_name, vecTempMount[0].available_byte );
			}
		}
		else
		{
			// 시스템에서 1개 초과 Mount 정보만 검색된 경우. -> 시스템에서 추출된 Mount 경로를 최적 Mount 경로를 사용한다.
			// 여유용량에 대한 임계치를 검사 후... -> 임계치 이하 항목은 제거..
			// 여유 용량이 가장 많은 Mount 순으로 정렬한 후...
			// Mount disable 검사....
			// 만약 전체 mount disable 되어 있다면... 가장 용량이 많은 Mount 선택 처리..

			// 임계치 이상의 여유 공간을 가진 Mount 정보를 임시로 저장할 Vector.
			std::vector<struct systme_mount_info> vecAvailMount;
			
			for( it = vecTempMount.begin(); it < vecTempMount.end(); it++ )
			{
				// 여유 용량에 대한 임계치 검사.
				if( it->available_byte >= LIMIT_STORAGE_SIZE )
				{
					stMountInfo.mount_name     = it->mount_name;
					stMountInfo.available_byte = it->available_byte;

					vecAvailMount.push_back( stMountInfo );
				}
				else
				{
					_LOG( LWAR,  "mount available size not enough. mount[%s] available size[%llu] limit[%llu]"
						, it->mount_name.c_str(), it->available_byte, LIMIT_STORAGE_SIZE );
				}
			}

			// 임계치 이상의 용량을 가진 Mount 가 없는 경우...
			if( vecAvailMount.empty() == true )
			{
				LOG( LERR, "proper mount not found. mount available size not enough. limit[%llu]", LIMIT_STORAGE_SIZE );
				SetProperPathError();
			}
			else
			{
				// 최소 임계치 이상의 용량을 가진 Mount 가 존재하는 경우..
				// Available 용량이 큰 순서대로 sort 처리...			
				// 루프를 돌며서 각 Mount 별로 disable 검사.. 만약 disable 처리가 안된 경우. 해당 Mount 를 Proper Mount 롤 선정.
				std::sort( vecAvailMount.begin(), vecAvailMount.end(), SortMountAvailableSize );
				
				for( it = vecAvailMount.begin(); it < vecAvailMount.end(); it++ )
				{
					//_LOG( LDBG,  "ProperStorage: Sort result. mount[%s] available size[%llu]" , it->mount_name.c_str(), it->available_byte );
					
					// Mount 별로 Mount disable 되어 있는지 검사...
					if( CheckMountDisable( it->mount_name ) == false )
					{
						SetProperPath( it->mount_name, it->available_byte );

						return;
					}
				}

				// 위의 루프에서 Mount 가 전부 disable 되어 있어 적합한 Mount 를 찾지 못한 경우...
				// 용량별로 sort 되어 있으므로.. 첫번째 Mount 를 선정한다.
				_LOG( LWAR,  "Worker[%d]::ProperStorage: all mount disable. forced selected by available size order.[%s] size[%llu]"
					, getpid(), vecAvailMount[0].mount_name.c_str(), vecAvailMount[0].available_byte );

				SetProperPath( vecAvailMount[0].mount_name, vecAvailMount[0].available_byte );
				
				return;
			}
		}
	}

	return;
}


// FHS 장비의 Mount 정보를 추출하여... 멤버 변수인 m_vecMount 에 저장한다.
// return true  : Mount 정보 정상 추출시...
//        false : Mount 정보 추출 실패 또는 추출된 Mount 정보가 없는 경우...
bool CProperStorage::GetSystmeMountInfo()
{
	// 추출된 Mount 정보 저장 객체 초기화.
	struct systme_mount_info stMountInfo;
	m_vecMount.clear();
	
	// statfs 구조체 정보.
	//  struct statfs {
	//  char     f_mntonname[88];       /* directory on which mounted */
	//  int64_t  f_bavail;              /* free blocks avail to non-superuser */
	//  uint64_t f_bsize;               /* filesystem fragment size */
	//  ..
	//  }
	//  => uint64_t = unsigned long long => 0 ~ 18,446,744,073,709,551,615 범위 가짐.
	
// FreeBSD 에서만.. getmntinfo 함수를 지원..
// 향후 Linux Migration 을 대비하기 위해 OS 별 코드 추가한다.
#ifdef __FreeBSD__

	// Mount 정보를 추출하기 위한 변수 초기화.
	int nMountCount = 0;
	struct statfs * pStatFsBuf = NULL;

	// Mount 정보 추출...
	// getmntinfo 특성상.. 할당된 메모리를 application 에서 해제시키면 안되므로 주의할 것.
	nMountCount = getmntinfo( &pStatFsBuf, MNT_WAIT );
	if( nMountCount == 0 )
	{
		// Mount 정보 추출 실패시.. 오류 처리한다.
		int errorNum = errno;
		LOG( LERR, "system getmntinfo function return failed. [%d][%s]", errorNum, strerror(errorNum) );

		return false;
	}

	for( int i = 0 ; i < nMountCount ; i++ )
	{
		stMountInfo.mount_name     = pStatFsBuf[i].f_mntonname;
		stMountInfo.available_byte = pStatFsBuf[i].f_bavail * (unsigned long long)pStatFsBuf[i].f_bsize ;
	
		m_vecMount.push_back( stMountInfo );

		//LOG( LDBG, "system mount name[%s] available byte[%llu]", stMountInfo.mount_name.c_str(), stMountInfo.available_byte );

		// 시스템에서 Mount 정보 추출시 유효 용량에 대한 임계치 검사를 수행할 경우....
		// 아래 코드에서 운영자가 설정한 Mount 정보 추출이 실패.. 잘못된 경로상에 Content 저장이 발생할 수 있으므로.. 임계치 검사를 수해하지 않는다.
	}

#else      // Linux 인 경우...
	
	FILE * fp = NULL;
	
	struct mntent   stMnt;
	struct mntent * pMnt;
	char tempBuf[512];

	fp = setmntent( "/etc/mtab", "r");
	if( fp != NULL )
	{
		while( (pMnt = getmntent_r( fp, &stMnt, tempBuf, sizeof(tempBuf))) != NULL )
		{
			struct statfs statFsBuf;

			if( (stMnt.mnt_dir != NULL) && (statfs( stMnt.mnt_dir, &statFsBuf ) == 0 ) )
			{
				stMountInfo.mount_name     = stMnt.mnt_dir;
				stMountInfo.available_byte = statFsBuf.f_bavail * (unsigned long long)statFsBuf.f_bsize ;

				m_vecMount.push_back( stMountInfo );
				
				//LOG( LDBG, "system mount name[%s] available byte[%llu]", stMountInfo.mount_name.c_str(), stMountInfo.available_byte );	
			}
		}

		// open 된 fp close 처리.
		endmntent( fp );
	}

#endif
	
	// 추출된 결과 확인...
	if( m_vecMount.empty() == true )
	{
		// 추출된 system Mount 정보가 없는 경우..
		LOG( LERR, "system mount info empty. check mount info." );
		return false;
	}

	return true;
}

// 인자로 전달받은 Mount 에 대해 disable flag 가 등록되어 있는지 검사
// true  : mount disable flag 가 걸려 있는 경우.
// false : mount disable flag 가 없는 경우..
bool CProperStorage::CheckMountDisable( const std::string & strMountName)
{
	char szDisableName[512];
	struct stat flagStat;

	sprintf( szDisableName, "%s%s", strMountName.c_str(), DISABLE_FLAG );

	// Mount disable flag 가 존재하고....
	if( lstat( szDisableName, &flagStat) == 0 )
	{
		// 해당 파일이 정규 파일인 경우.. ( 폴더, 링크 등은 제외 처리 )
		if( S_ISREG(flagStat.st_mode) != 0 )
		{
			return true;
		}
	}

	return false;
}


// 전달받은 경로로부터.. 상위 Path 정보를 추출하여 전달한다.
// 오류 발생시 [out] 값인 strUpperPath 가 empty 된다.
void CProperStorage::GetUpperPath( const std::string & strSourcePath, std::string & strUpperPath )
{
	// 인자로 전달되는 strSourcePath 끝에.. '/' 가 이미 제거되었다고 판단하고 작업을 수행한다.
	std::string::size_type pos = strSourcePath.rfind( '/' );
	if( pos == std::string::npos )
	{
		// 찾지 못한 경우...
		// out 값을 empty 처리시킨다.
		LOG( LNOT, "upper path not found. source[%s]", strSourcePath.c_str());
		strUpperPath.clear();
		
		return;
	}

	// 찾은 경우..
	strUpperPath = strSourcePath.substr(0, pos);

	// 추출된 경로가... 최상위 '/' 인 경우.. 유효하지 않으므로 오류로 처리한다.
	if( strcmp( strUpperPath.c_str(), "/") == 0 )
	{
		LOG( LNOT, "upper path not vaild that root path. [%s]", strUpperPath.c_str() );
		strUpperPath.clear();
	}

	return;
}

// Content Write 관련 최적 Mount 경로 및 사용 가능 Free Size 를 설정한다.
void CProperStorage::SetProperPath( std::string& strProperPath, unsigned long long & nAvailableSzie )
{
	// Thread 와 동시 접근을 제어하기 위해 Lock 처리
	pthread_mutex_lock( &m_mutexLock );

	m_strProperPath  = strProperPath;
	m_nAvailableSize = nAvailableSzie;

	pthread_mutex_unlock( &m_mutexLock );
	
	_LOG( LDBG, "Worker[%d]::ProperStorage: proper path set. [%s] free[%llu]byte", getpid(), m_strProperPath.c_str(), m_nAvailableSize );
	
	return;
}

// Content Write 관련 최적 Mount 경로 및 사용 가능 Free Size 추출 실패시..
void CProperStorage::SetProperPathError()
{
	// Thread 와 동시 접근을 제어하기 위해 Lock 처리
	pthread_mutex_lock( &m_mutexLock );

	m_strProperPath.clear();
	m_nAvailableSize = 0;

	pthread_mutex_unlock( &m_mutexLock );
	
	LOG( LERR, "Worker[%d]: proper mount path set invalid value.", getpid() );
	return;
}
