#include "StorageCheck.h"

#include "Logger.h"

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>

// Storage Write / Read test 를 수행할 파일명 정의
#define CHECK_FILE_NAME		PROG_NAME"_check.txt"


// 생성자
CStorageCheck::CStorageCheck( CFhsShm * pFhsShm )
	: m_pFhsShm( pFhsShm )
{

}

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

}

// 초기화 함수
// - szRoot : Write/Read 테스트를 수행할 mount 최상위 경로 정보.
bool CStorageCheck::Init( const char * szRoot )
{
	// 공유메모리 접근 포인터가 유효하지 않은 경우
	if( m_pFhsShm == NULL )
		return false;

	// 최초 storage 정보 추출을 수행해서 문제 없는지 체크
	if( m_storageInfo.Init( szRoot ) == false )
	{
		LOG( LERR, "[Storage] storage info class init failed." );
		return false;
	}

	// 소요시간 측정 관련 변수 초기화
	memset( &m_timeTotalStart, 0x00, sizeof( struct timespec ) );
	memset( &m_timeTotalEnd, 0x00, sizeof( struct timespec ) );
	m_dElapsedTotalTime = 0.0;
	
	memset( &m_timeDiskStart, 0x00, sizeof( struct timespec ) );
	memset( &m_timeDiskEnd, 0x00, sizeof( struct timespec ) );
	m_dElapsedDiskTime = 0.0;

	return true;
}


// Storage 상태 Check 시작
// - szRoot : Write/Read 테스트를 수행할 mount 최상위 경로 정보.
// - 오류 발생시 false 반환
// - Hang 발생 가능하므로 본 함수 호출부에서 timeout 처리 필요
bool CStorageCheck::Check( const char * szRoot )
{
	// Storage info 객체 초기화
	// - 전달받은 최상위 경로값 오류시
	// - 정보 추출 실패 또는 szRoot 에 맞는 경로 정보 추출 실패시 false 반환
	// - mount 정보가 중간에 변경될 수 있으므로.. Check 수행시마다.. Storage 정보 추출하도록 한다.
	if( m_storageInfo.Init( szRoot ) == false )
	{
		LOG( LERR, "[Storage] storage info class init failed.");
		return false;
	}

	// write, read check 대상 경로 저장을 위한 vector
	// - 전달받은 root 경로 정보를 이용하여 테스트 수행할 경로 정보 추출 저장
	std::vector< std::string > vecCheckPath;
	if( m_storageInfo.GetStoragePath( vecCheckPath ) == false )
	{
		// storage 정보가 유효하지 않은 경우
		LOG( LERR, "[Storage] storage info not valid.");
		return false;
	}

	// 실제 추출된 경로에 대한 파일 write, read 테스트 수행

	// 변수 선언 & 초기화
	std::vector< std::string >::iterator it;
	std::string szFullFileName;
	FILE * pFile = NULL;

	int errorNum;
	char szErrorBuffer[1024];
	char szDataBuffer[1024];
	char szReadBuffer[1024];

	// write, read, unlink 작업 전 잠시 대기를 위한 sleep 설정
	struct timespec sleepInterval;
	sleepInterval.tv_sec = 0;
	sleepInterval.tv_nsec = 200000000;  // 0.2 sec


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

	// 파일 저장 Data 생성
	snprintf( szDataBuffer, sizeof( szDataBuffer ) - 1, "%ld.%ld", m_timeTotalStart.tv_sec, m_timeTotalStart.tv_nsec );

	_LOG( LINF, "[Storage] check start with data [%s]", szDataBuffer );

	// 설정된 경로별 파일 write, read 테스트 수행
	for( it = vecCheckPath.begin(); it < vecCheckPath.end(); it++ )
	{
		// check start time 
		clock_gettime( CLOCK_MONOTONIC, &m_timeDiskStart );
	
		// 파일명 조립
		szFullFileName = *it + "/" + CHECK_FILE_NAME;

		// 파일 open
		pFile = fopen( szFullFileName.c_str(), "w" );
		if( pFile == NULL )
		{
			// 파일 open 실패시
			errorNum = errno;
			snprintf( szErrorBuffer, sizeof( szErrorBuffer ) - 1, "File open failed to write.[%s] [%d][%s]", szFullFileName.c_str(), errorNum, strerror( errorNum ) );

			LOG( LERR, "[Storage] %s", szErrorBuffer );
			return false;
		}

		// 파일 write 
		// - fwrite 가 지정된 크기만큼 write 실패한 경우.. write error 발생
		if( fwrite( szDataBuffer, 1, strlen( szDataBuffer ), pFile ) != strlen( szDataBuffer ) )
		{
			// write 실패시
			errorNum = errno;
			snprintf( szErrorBuffer, sizeof( szErrorBuffer ) - 1, "File write failed.[%s] [%d][%s]", szFullFileName.c_str(), errorNum, strerror( errorNum ) );
			
			fclose( pFile );
			pFile = NULL;

			LOG( LERR, "[Storage] %s", szErrorBuffer );
			return false;
		}

		// buffer flush 처리 - I/O 오류 발생으로 실패 발생 가능
		if( fflush( pFile ) != 0 )
		{
			// flush 처리 실패시
			errorNum = errno;
			snprintf( szErrorBuffer, sizeof( szErrorBuffer ) - 1, "File write flush failed.[%s] [%d][%s]", szFullFileName.c_str(), errorNum, strerror( errorNum ) );
			
			fclose( pFile );
			pFile = NULL;

			LOG( LERR, "[Storage] %s", szErrorBuffer );
			return false;
		}

		//_LOG( LINF, "[Storage] %s check file write & flush ok. [%s]", szFullFileName.c_str(), tempBuffer ); 

		// 파일 close
		if( pFile != NULL )
		{
			fclose( pFile );
			pFile = NULL;
		}

		// 잠시 대기 
		nanosleep( &sleepInterval, NULL );

		// 파일 open
		pFile = fopen( szFullFileName.c_str(), "r" );
		if( pFile == NULL )
		{
			// 파일 open 실패시
			errorNum = errno;
			snprintf( szErrorBuffer, sizeof( szErrorBuffer ) - 1, "File open failed to read.[%s] [%d][%s]", szFullFileName.c_str(), errorNum, strerror( errorNum ) );

			LOG( LERR, "[Storage] %s", szErrorBuffer );
			return false;
		}

		// 파일 read
		// - 파일 읽은 크기가 실제 저장한 크기보다 작은 경우
		// - end of file 인지 read error 인지 체크해야 하며 (man page)
		// - read buffer 는 작업 전 초기화하지 않으면.. garbage data 가 저장되어 있어 문제됨.
		memset( szReadBuffer, 0x00, sizeof( szReadBuffer ) );
		if( fread( szReadBuffer, 1, sizeof( szReadBuffer ), pFile ) != strlen( szDataBuffer ) )
		{
			// read 실패시 
			// - end of file : 실제 저장한 data 와 크기가 안 맞으므로.. 이거는 오류로 본다
			// - read 실패 : read 오류로 처리
			if( feof( pFile ) != 0 )	// end of file 인 경우
			{
				snprintf( szErrorBuffer, sizeof( szErrorBuffer ) - 1, "File read size not match[%s] src[%s] file[%s]"
						  , szFullFileName.c_str(), szDataBuffer, szReadBuffer );
			}
			else
			{
				errorNum = errno;
				snprintf( szErrorBuffer, sizeof( szErrorBuffer ) - 1, "File read failed.[%s] [%d][%s]", szFullFileName.c_str(), errorNum, strerror( errorNum ) );
			}

			fclose( pFile );
			pFile = NULL;

			LOG( LERR, "[Storage] %s", szErrorBuffer );
			return false;
		}

		// read data check
		if( strcmp( szDataBuffer, szReadBuffer ) != 0 )
		{
			snprintf( szErrorBuffer, sizeof( szErrorBuffer ) - 1, "File read data not match[%s]. src[%s] file[%s]"
					  , szFullFileName.c_str(), szDataBuffer, szReadBuffer );

			fclose( pFile );
			pFile = NULL;

			LOG( LERR, "[Storage] %s", szErrorBuffer );
			return false;
		}

		// 파일 close
		if( pFile != NULL )
		{
			fclose( pFile );
			pFile = NULL;
		}

		// 잠시 대기 
		nanosleep( &sleepInterval, NULL );

		// 파일 unlink
		if( unlink( szFullFileName.c_str() ) != 0 )
		{
			// unlink 실패시
			errorNum = errno;
			snprintf( szErrorBuffer, sizeof( szErrorBuffer ) - 1, "File unlink failed.[%s] [%d][%s]", szFullFileName.c_str(), errorNum, strerror( errorNum ) );

			LOG( LERR, "[Storage] %s", szErrorBuffer );
			return false;
		}

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

		// disk 당 작업 소요 시간 계산
		m_dElapsedDiskTime = m_timeDiskEnd.tv_sec - m_timeDiskStart.tv_sec;
		m_dElapsedDiskTime += ( m_timeDiskEnd.tv_nsec - m_timeDiskStart.tv_nsec ) * 1e-9;

		_LOG( LINF, "[Storage] %s check ok. elapsed time: %.3lf sec.", it->c_str(), m_dElapsedDiskTime );
	}

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

	// 작업 소요 시간 계산
	m_dElapsedTotalTime = m_timeTotalEnd.tv_sec - m_timeTotalStart.tv_sec;
	m_dElapsedTotalTime += ( m_timeTotalEnd.tv_nsec - m_timeTotalStart.tv_nsec ) * 1e-9;

	_LOG( LINF, "[Storage] check end. total elapsed time: %.3lf sec.", m_dElapsedTotalTime );
	return true;
}

