#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <paths.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <err.h>
#include <sys/mman.h>
#include <sys/time.h>
#ifdef __FreeBSD__
#include <sys/resource.h>
#include <devstat.h>
#include <sys/devicestat.h>

#include <sys/param.h>
#include <sys/ucred.h>
#include <sys/mount.h>

#else // linux
#include <mntent.h>
#include <sys/vfs.h>
#endif  // __FreeBSD__

#include <sys/types.h>
#include <sys/stat.h>

#include <httpd.h>
#include <http_config.h>
#include <http_protocol.h>
#include <http_log.h>

#include "dav_repos.h"

#define DISABLE_FLAG	".disable"


// CHG 2019-09-24 huibong Bigbox FHS 장비 사용에 따른 mount 별 운영여유공간 계산방식 개선 (#32807)
// - 285 TB 급 bigbox FHS 장비 사용으로 인해
// - 기존과 같이 각 mount 별 1% 여유공간을 남길경우... 2.8 TB 가 남겨져 저장공간 낭비가 심해
// - 다음과 같이 개선 처리함.
//  -- 각 mount total 공간 ( used + avail ) > 3TB 이면... avail 공간은 30 GByte 초과할 경우 write 가능
//  -- 각 mount total 공간 ( used + avail ) <= 3TB 이면... 기존과 같이 사용률이 99% 미만인 경우 write 가능
// - 기존 conf 상의 DavProviderStorageLimit 설정은 제거 처리

#define MOUNT_WRITE_CHECK_PERCENT_LIMIT		3298534883328	// 3 TB
#define MOUNT_WRITE_LIMIT_SIZE				32212254720		// 30 GB
#define MOUNT_WRITE_LIMIT_PERCENT			99				// 99% 

typedef struct
{
	char *			f_mntonname;
	apr_int64_t		f_bavail;		// 해당 mount 의 여유용량, 8Byte long 최대값은 9,223,372,036,854,775,807 이므로.. 9,223 PB 까지 저장 가능.
	apr_int64_t		total_size;		// 해당 mount 의 total size, UFS 의 8% 예약공간 이슈로... used size + avail size 로 계산한다. (2019-09-24 #32807 로 추가)
	int				capacity;		// 사용량 정보 (%), int 형변환 때문에 99.99 % -> 99 로 표시됨.

} diskinfo;

int get_writable_path(apr_pool_t *p, const char *homepath, char *subpath)
{
	// 2015.06.15 dadamin
	// 램덤 처리 시 동일한 타입을 사용않을 시 
	// 특정 볼륨(node0)로 몰리는 현상이 발견되어
	// 램덤 함수에서 사용하는 타입으로 수정함
	unsigned int current = 0;
	diskinfo *info;
	apr_array_header_t *storgelist = NULL;
	long used, availblks;

#ifdef __FreeBSD__

	int mntsize = 0, i = 0;
	struct statfs *mntbuf = NULL;

	apr_global_mutex_lock(local_disk_lock);
	mntsize = getmntinfo(&mntbuf, MNT_WAIT);

	if( mntsize == 0 )
	{
		apr_global_mutex_unlock(local_disk_lock);
		ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, "get_writable_path::getmntinfo error [%d]", errno);
		return -1; // error
	}

	storgelist = apr_array_make(p, 0, sizeof(diskinfo));
	if(storgelist  == NULL)
	{
		apr_global_mutex_unlock(local_disk_lock);
		ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, "get_writable_path::apr_array_make error [%d]", errno);
		return -1; // error
	}
	for (i = 0; i < mntsize; i++)
	{
		// storage root 
		if (strncmp(homepath, mntbuf[i].f_mntonname, strlen(homepath)))
			continue;
		
		info =  (diskinfo *) apr_array_push(storgelist);

		// multi volume
		info->f_mntonname = mntbuf[i].f_mntonname;
		info->f_bavail = mntbuf[i].f_bavail * mntbuf[i].f_bsize;
		
		used = mntbuf[i].f_blocks - mntbuf[i].f_bfree;
		availblks = mntbuf[i].f_bavail + used;

		// 각 mount total size 정보는 UFS 예약 8% 를 제거하기 위해 실제 사용량 + free 용량 정보를 더해서 계산한다.
		info->total_size = availblks * mntbuf[i].f_bsize;
		info->capacity = (int)(availblks == 0 ? 100.0 : (double)used / (double)availblks * 100.0);
	}

	apr_global_mutex_unlock(local_disk_lock);

#else // linux

	FILE *f;
	struct mntent mnt;
	struct mntent *mnte;
	char buf[512];
	if ((f = setmntent ("/etc/mtab", "r")) != NULL) 
	{
		storgelist = apr_array_make(p, 0, sizeof(diskinfo));
		while ((mnte = getmntent_r (f, &mnt, buf, sizeof(buf))) != NULL)
		{
			// storage root 
			if (strncmp(homepath, mnt.mnt_dir, strlen(homepath)))
				continue;

			struct statfs statfsbuf;
			if(statfs(mnt.mnt_dir, &statfsbuf) == 0)
			{
				info =  (diskinfo *) apr_array_push(storgelist);
				// multi volume
				info->f_mntonname = mnt.mnt_dir;
				info->f_bavail = statfsbuf.f_bavail * statfsbuf.f_bsize;

				used = statfsbuf.f_blocks - statfsbuf.f_bfree;
				availblks = statfsbuf.f_bavail + used;
			
				// 각 mount total size 정보는 특정 시스템의 예약 8% 를 제거하기 위해 실제 사용량 + free 용량 정보를 더해서 계산한다.
				info->total_size = availblks * statfsbuf.f_bsize;
				info->capacity = (int)(availblks == 0 ? 100.0 : (double)used / (double)availblks * 100.0);
			}
			else 
				ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, "get_writable_path:: multi volume statfs error [%d]", errno);
		}
		endmntent(f);
	}
	else 
	{
		ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, "get_writable_path::setmntent error [%d]", errno);
		return -1; // error
	}

#endif // 

	if(storgelist->nelts == 0)
	{
		// single volume
		struct statfs statfsbuf;
		if(statfs(homepath, &statfsbuf) == 0)
		{
			info =  (diskinfo *) apr_array_push(storgelist);
			info->f_mntonname = (char *)homepath;
			info->f_bavail = statfsbuf.f_bavail * statfsbuf.f_bsize;

			used = statfsbuf.f_blocks - statfsbuf.f_bfree;
			availblks = statfsbuf.f_bavail + used;

			// 각 mount total size 정보는 UFS 예약 8% 를 제거하기 위해 실제 사용량 + free 용량 정보를 더해서 계산한다.
			info->total_size = availblks * statfsbuf.f_bsize;
			info->capacity = (int)(availblks == 0 ? 100.0 : (double)used / (double)availblks * 100.0);
		}
		else 
			ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, "get_writable_path::statfs error [%d]", errno);
	}

	if(storgelist->nelts > 0)
	{		
		char disable_flag[1024] = {0};
		struct stat sb;
		int not_exist = -1;

		apr_generate_random_bytes((unsigned char *)&current, sizeof(unsigned int));

		current = current % storgelist->nelts;

		info = &((diskinfo *) storgelist->elts)[current];

		sprintf(disable_flag, "%s%s", info->f_mntonname, DISABLE_FLAG);		
		not_exist = stat(disable_flag, &sb);

		// CHG 2019-09-24 huibong Bigbox FHS 장비 사용에 따른 mount 별 운영여유공간 계산방식 개선 (#32807)
		// - 285 TB 급 bigbox FHS 장비 사용으로 인해
		// - 기존과 같이 각 mount 별 1% 여유공간을 남길경우... 2.8 TB 가 남겨져 저장공간 낭비가 심해
		// - 다음과 같이 개선 처리함.
		//  -- 각 mount total 공간 ( used + avail ) > 3TB 이면... avail 공간은 30 GByte 초과할 경우 write 가능
		//  -- 각 mount total 공간 ( used + avail ) <= 3TB 이면... 기존과 같이 사용률이 99% 미만인 경우 write 가능
		// - 기존 conf 상의 DavProviderStorageLimit 설정은 제거 처리
		if( info->total_size > MOUNT_WRITE_CHECK_PERCENT_LIMIT )
		{
			if( not_exist && info->f_bavail > MOUNT_WRITE_LIMIT_SIZE )
			{
				strcpy( subpath, info->f_mntonname );
		
				// for debug
				//ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, "get_writable_path: random, total size greater 3TB, free size greater 30GB, return [%s]", subpath );
				return 0;
			}
		}
		else
		{
			if( not_exist && info->capacity < MOUNT_WRITE_LIMIT_PERCENT )
			{
				strcpy( subpath, info->f_mntonname );
				
				// for debug
				//ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, "get_writable_path: random, total size less than or equal 3TB, used percent less than 99%%, return [%s]", subpath );
				return 0;
			}
		}
	}


	// 2014-02-28 dadamin
	// 램덤 실패 시 해당 결과 순회 후 정상 디스크를 선택
	int first = 0, diskcnt = storgelist->nelts;
	first = current;
	for ( current = first ; current < diskcnt; ++current)
	{
		char disable_flag[1024] = {0};
		struct stat sb;
		int not_exist = -1;

		info = &((diskinfo *) storgelist->elts)[current];

		sprintf(disable_flag, "%s%s", info->f_mntonname, DISABLE_FLAG);		
		not_exist = stat(disable_flag, &sb);

		// CHG 2019-09-24 huibong Bigbox FHS 장비 사용에 따른 mount 별 운영여유공간 계산방식 개선 (#32807)
		// - 285 TB 급 bigbox FHS 장비 사용으로 인해
		// - 기존과 같이 각 mount 별 1% 여유공간을 남길경우... 2.8 TB 가 남겨져 저장공간 낭비가 심해
		// - 다음과 같이 개선 처리함.
		//  -- 각 mount total 공간 ( used + avail ) > 3TB 이면... avail 공간은 30 GByte 초과할 경우 write 가능
		//  -- 각 mount total 공간 ( used + avail ) <= 3TB 이면... 기존과 같이 사용률이 99% 미만인 경우 write 가능
		// - 기존 conf 상의 DavProviderStorageLimit 설정은 제거 처리
		if( info->total_size > MOUNT_WRITE_CHECK_PERCENT_LIMIT )
		{
			if( not_exist && info->f_bavail > MOUNT_WRITE_LIMIT_SIZE )
			{
				strcpy( subpath, info->f_mntonname );

				// for debug
				//ap_log_error( APLOG_MARK, APLOG_ERR, 0, NULL, "get_writable_path: sequence, total size greater 3TB, free size greater 30GB, return [%s]", subpath );
				return 0;
			}
		}
		else
		{
			if( not_exist && info->capacity < MOUNT_WRITE_LIMIT_PERCENT )
			{
				strcpy( subpath, info->f_mntonname );

				// for debug
				//ap_log_error( APLOG_MARK, APLOG_ERR, 0, NULL, "get_writable_path: sequence, total size less than or equal 3TB, used percent less than 99%%, return [%s]", subpath );
				return 0;
			}
		}

		if( first > 0 && (current+1) >= diskcnt )
		{
			current = -1;
			diskcnt = first;
			first = 0;
		}
	}

	ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, "get_writable_path::Empty writable");
	return -1; // error
}
