/* 
 * $Id$
 *
    Copyright (c) 2006-2019 Chung, Hyung-Hwan. All rights reserved.

    Redistribution and use in source and binary forms, with or without
    modification, are permitted provided that the following conditions
    are met:
    1. Redistributions of source code must retain the above copyright
       notice, this list of conditions and the following disclaimer.
    2. Redistributions in binary form must reproduce the above copyright
       notice, this list of conditions and the following disclaimer in the
       documentation and/or other materials provided with the distribution.

    THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR
    IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
    OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
    IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
    NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
    DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
    THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "fs-prv.h"
#include <qse/cmn/str.h>
#include <qse/cmn/mbwc.h>
#include <qse/cmn/path.h>
#include "../cmn/mem-prv.h"

#if defined(_WIN32)
	/* nothing else */
#elif defined(__OS2__)
	/* nothing else */
#elif defined(__DOS__)
	/* nothing else */
#else
#	include <dirent.h>
#	include <errno.h>
#endif

typedef struct info_t info_t;
struct info_t
{
	qse_cstr_t name;

#if defined(_WIN32)
	HANDLE handle;
	WIN32_FIND_DATA wfd;
	int just_changed_fs;
#elif defined(__OS2__)
#elif defined(__DOS__)
#else
	DIR* handle;
	qse_mchar_t* mcurdir;
#endif
};

qse_fs_t* qse_fs_open (qse_mmgr_t* mmgr, qse_size_t xtnsize)
{
	qse_fs_t* fs;

	fs = QSE_MMGR_ALLOC (mmgr, QSE_SIZEOF(*fs) + xtnsize);
	if (fs == QSE_NULL) return QSE_NULL;

	if (qse_fs_init (fs, mmgr) <= -1) 
	{
		QSE_MMGR_FREE (mmgr, fs);
		return QSE_NULL;
	}

	QSE_MEMSET (fs + 1, 0, xtnsize);
	return fs;
}

void qse_fs_close (qse_fs_t* fs)
{
	qse_fs_fini (fs);
	QSE_MMGR_FREE (fs->mmgr, fs);
}

int qse_fs_init (qse_fs_t* fs, qse_mmgr_t* mmgr)
{
	QSE_MEMSET (fs, 0, QSE_SIZEOF(*fs));
	fs->mmgr = mmgr;
	fs->cmgr = qse_getdflcmgr();
	return 0;
}

void qse_fs_fini (qse_fs_t* fs)
{
	info_t* info;

	info = fs->info;
	if (info)
	{
		if (info->name.ptr)
		{
			QSE_ASSERT (info->name.len > 0);
			QSE_MMGR_FREE (fs->mmgr, info->name.ptr);
			info->name.ptr = QSE_NULL;
			info->name.len = 0;
		}

#if defined(_WIN32)
		if (info->handle != INVALID_HANDLE_VALUE) 
		{
			FindClose (info->handle);
			info->handle = INVALID_HANDLE_VALUE;
		}
#elif defined(__OS2__)
		/* TODO: implement this */
#elif defined(__DOS__)
		/* TODO: implement this */
#else
		if (info->mcurdir && info->mcurdir != fs->curdir)
			QSE_MMGR_FREE (fs->mmgr, info->mcurdir);
		info->mcurdir = QSE_NULL;
			
		if (info->handle)
		{
			closedir (info->handle);
			info->handle = QSE_NULL;
		}
#endif

		QSE_MMGR_FREE (fs->mmgr, info);
		fs->info = QSE_NULL;
	}

	if (fs->curdir) 
	{
		QSE_MMGR_FREE (fs->mmgr, fs->curdir);
		fs->curdir = QSE_NULL;
	}
}

qse_mmgr_t* qse_fs_getmmgr (qse_fs_t* fs)
{
	return fs->mmgr;
}

void* qse_fs_getxtn (qse_fs_t* fs)
{
	return QSE_XTN (fs);
}

int qse_fs_getopt (qse_fs_t* fs, qse_fs_opt_t id, void* value)
{
	switch (id)
	{
		case QSE_FS_TRAIT:
			*(int*)value = fs->trait;
			return 0;

		case QSE_FS_CBS:
			*(qse_fs_cbs_t*)value = fs->cbs;
			return 0;
	}

	fs->errnum = QSE_FS_EINVAL;
	return -1;
}

int qse_fs_setopt (qse_fs_t* fs, qse_fs_opt_t id, const void* value)
{
	switch (id)
	{
		case QSE_FS_TRAIT:
			fs->trait = *(const int*)value;
			return 0;


		case QSE_FS_CBS:
			fs->cbs = *(qse_fs_cbs_t*)value;
			return 0;

	}

	fs->errnum = QSE_FS_EINVAL;
	return -1;
}

static QSE_INLINE info_t* get_info (qse_fs_t* fs)
{
	info_t* info;

	info = fs->info;
	if (info == QSE_NULL)
	{
		info = QSE_MMGR_ALLOC (fs->mmgr, QSE_SIZEOF(*info));
		if (info == QSE_NULL) 
		{
			fs->errnum = QSE_FS_ENOMEM;
			return QSE_NULL;
		}

		QSE_MEMSET (info, 0, QSE_SIZEOF(*info));
#if defined(_WIN32)
		info->handle = INVALID_HANDLE_VALUE;
#endif
		fs->info = info;
	}

	return info;
}

int qse_fs_chdir (qse_fs_t* fs, const qse_char_t* name)
{
	qse_char_t* fsname;
	info_t* info;

#if defined(_WIN32)
	HANDLE handle;
	WIN32_FIND_DATA wfd;
	const qse_char_t* tmp_name[4];
	qse_size_t idx;
#elif defined(__OS2__)
	/* TODO: implement this */
#elif defined(__DOS__)
	/* TODO: implement this */
#else
	DIR* handle;
	qse_mchar_t* mfsname;
	const qse_char_t* tmp_name[4];
	qse_size_t idx;
#endif

	if (name[0] == QSE_T('\0'))
	{
		fs->errnum = QSE_FS_EINVAL;
		return -1;
	}

	info = get_info (fs);
	if (info == QSE_NULL) return -1;

#if defined(_WIN32)
	idx = 0;
	if (!qse_isabspath(name) && fs->curdir)
		tmp_name[idx++] = fs->curdir;
	tmp_name[idx++] = name;

	if (qse_isdrivecurpath(name)) 
		tmp_name[idx++] = QSE_T(" ");
	else
		tmp_name[idx++] = QSE_T("\\ ");

	tmp_name[idx] = QSE_NULL;

	fsname = qse_stradup (tmp_name, QSE_NULL, fs->mmgr);
	if (fsname == QSE_NULL) 
	{
		fs->errnum = QSE_FS_ENOMEM; 
		return -1;
	}

	idx = qse_canonpath (fsname, fsname, 0);
	/* Put an asterisk after canonicalization to prevent side-effects.
	 * otherwise, .\* would be transformed to * by qse_canonpath() */
	fsname[idx-1] = QSE_T('*'); 

	/* Using FindExInfoBasic won't resolve cAlternatFileName.
	 * so it can get faster a little bit. The problem is that
	 * it is not supported on old windows. just stick to the
	 * simple API instead. */
	#if 0
	handle = FindFirstFileEx (
		fsname, FindExInfoBasic,
		&wfd, FindExSearchNameMatch, 
		NULL, 0/*FIND_FIRST_EX_CASE_SENSITIVE*/);
	#endif
	handle = FindFirstFile (fsname, &wfd);
	if (handle == INVALID_HANDLE_VALUE) 
	{
		fs->errnum = qse_fs_syserrtoerrnum (fs, GetLastError());
		QSE_MMGR_FREE (fs->mmgr, fsname);
		return -1;
	}

	if (info->handle != INVALID_HANDLE_VALUE) 
		FindClose (info->handle);

	QSE_MEMSET (info, 0, QSE_SIZEOF(*info));
	info->handle = handle;
	info->wfd = wfd;
	info->just_changed_fs = 1;

	if (fs->curdir) QSE_MMGR_FREE (fs->mmgr, fs->curdir);
	fsname[idx-1] = QSE_T('\0'); /* drop the asterisk */
	fs->curdir = fsname;

	return 0;

#elif defined(__OS2__)
	/* TODO: implement this */
	return 0;
#elif defined(__DOS__)
	/* TODO: implement this */
	return 0;
#else

	idx = 0;
	if (!qse_isabspath(name) && fs->curdir)
	{
		tmp_name[idx++] = fs->curdir;
		tmp_name[idx++] = QSE_T("/");
	}
	tmp_name[idx++] = name;
	tmp_name[idx] = QSE_NULL;

	fsname = qse_stradup (tmp_name, QSE_NULL, fs->mmgr);
	if (fsname == QSE_NULL)
	{	
		fs->errnum = QSE_FS_ENOMEM;
		return -1;
	}

	qse_canonpath (fsname, fsname, 0);

#if defined(QSE_CHAR_IS_MCHAR)
	mfsname = fsname;
#else
	mfsname = qse_wcstombsdup (fsname, QSE_NULL, fs->mmgr);
	if (mfsname == QSE_NULL)
	{
		fs->errnum = QSE_FS_ENOMEM;
		QSE_MMGR_FREE (fs->mmgr, fsname);
		return -1;
	}
#endif

	handle = opendir (mfsname);

	if (handle == QSE_NULL)
	{
		fs->errnum = qse_fs_syserrtoerrnum (fs, errno);
		if (mfsname != fsname) 
			QSE_MMGR_FREE (fs->mmgr, mfsname);
		QSE_MMGR_FREE (fs->mmgr, fsname);
		return -1;
	}

	if (info->handle) closedir (info->handle);
	info->handle = handle;

	if (info->mcurdir && info->mcurdir != fs->curdir)
		QSE_MMGR_FREE (fs->mmgr, info->mcurdir);
	info->mcurdir = mfsname;

	if (fs->curdir) QSE_MMGR_FREE (fs->mmgr, fs->curdir);
	fs->curdir = fsname;

	return 0;
#endif
}

#if defined(QSE_CHAR_IS_MCHAR) || defined(_WIN32)
static int set_entry_name (qse_fs_t* fs, const qse_char_t* name)
#else
static int set_entry_name (qse_fs_t* fs, const qse_mchar_t* name)
#endif
{
	info_t* info;
	qse_size_t len;

#if defined(QSE_CHAR_IS_MCHAR) || defined(_WIN32)
	/* nothing more to declare */
#else
	qse_size_t mlen;
#endif

	info = fs->info;
	QSE_ASSERT (info != QSE_NULL);

#if defined(QSE_CHAR_IS_MCHAR) || defined(_WIN32)
	len = qse_strlen (name);
#else
	/* TODO: ignore MBWCERR */
	if (qse_mbstowcs (name, &mlen, QSE_NULL, &len) <= -1)
	{
		/* invalid name ??? */
		return -1;
	}
#endif

	if (len > info->name.len)
	{
		qse_char_t* tmp;

/* TOOD: round up len to the nearlest multiples of something (32, 64, ??)*/
		tmp = QSE_MMGR_REALLOC (
			fs->mmgr, 
			info->name.ptr, 
			(len + 1) * QSE_SIZEOF(*tmp)
		);
		if (tmp == QSE_NULL)
		{
			fs->errnum = QSE_FS_ENOMEM;
			return -1;
		}

		info->name.len = len;
		info->name.ptr = tmp;
	}

#if defined(QSE_CHAR_IS_MCHAR) || defined(_WIN32)
	qse_strcpy (info->name.ptr, name);
#else
	len++; /* for terminating null */
	qse_mbstowcs (name, &mlen, info->name.ptr, &len);
#endif

	fs->ent.name.base = info->name.ptr;
	fs->ent.flags |= QSE_FS_ENT_NAME;
	return 0;
}

#if defined(_WIN32)
static QSE_INLINE void filetime_to_ntime (const FILETIME* ft, qse_ntime_t* nt)
{
	/* reverse of http://support.microsoft.com/kb/167296/en-us */
	ULARGE_INTEGER li;

	li.LowPart = ft->dwLowDateTime;
	li.HighPart = ft->dwHighDateTime;

#if (QSE_SIZEOF_LONG_LONG>=8)
	li.QuadPart -= 116444736000000000ull;
#elif (QSE_SIZEOF___INT64>=8)
	li.QuadPart -= 116444736000000000ui64;
#else
#	error Unsupported 64bit integer type
#endif
	/*li.QuadPart /= 10000000;*/
	/*li.QuadPart /= 10000;
	return li.QuadPart;*/

	/* li.QuadPart is in the 100-nanosecond intervals */
	nt->sec =  li.QuadPart / (QSE_NSECS_PER_SEC / 100);
	nt->nsec = (li.QuadPart % (QSE_NSECS_PER_SEC / 100)) * 100;
}
#endif

qse_fs_ent_t* qse_fs_read (qse_fs_t* fs, int flags)
{
#if defined(_WIN32)
	info_t* info;

	info = fs->info;
	if (info == QSE_NULL) 
	{
		fs->errnum = QSE_FS_ENOENT; /* TODO: is this correct? */
		return QSE_NULL;
	}

	if (info->just_changed_fs)
	{
		info->just_changed_fs = 0;
	}
	else
	{
		if (FindNextFile (info->handle, &info->wfd) == FALSE) 
		{
			DWORD e = GetLastError();
			if (e == ERROR_NO_MORE_FILES)
			{
				fs->errnum = QSE_FS_ENOERR;
				return QSE_NULL;
			}
			else
			{
				fs->errnum = qse_fs_syserrtoerrnum (fs, e);
				return QSE_NULL;
			}
		}
	}

	/* call set_entry_name before changing other fields
	 * in fs->ent not to pollute it in case set_entry_name fails */
	QSE_MEMSET (&fs->ent, 0, QSE_SIZEOF(fs->ent));
	if (set_entry_name (fs, info->wfd.cFileName) <= -1) return QSE_NULL;

	if (flags & QSE_FS_ENT_TYPE)
	{
#if !defined(IO_REPARSE_TAG_SYMLINK)
#	define IO_REPARSE_TAG_SYMLINK 0xA000000C
#endif
#if !defined(FILE_ATTRIBUTE_REPARSE_POINT)
#	define FILE_ATTRIBUTE_REPARSE_POINT 0x00000400
#endif
		if (info->wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
		{
			fs->ent.type = QSE_FS_ENT_SUBDIR;
		}
		else if ((info->wfd.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) &&
			 (info->wfd.dwReserved0 == IO_REPARSE_TAG_SYMLINK))
		{
			fs->ent.type = QSE_FS_ENT_SYMLINK;
		}
		else
		{
			HANDLE h;
			qse_char_t* tmp_name[4];
			qse_char_t* fname;

/* TODO: use a buffer in info... instead of allocating an deallocating every time */
			tmp_name[0] = fs->curdir;
			tmp_name[1] = QSE_T("\\");
			tmp_name[2] = info->wfd.cFileName;
			tmp_name[3] = QSE_NULL;
			fname = qse_stradup (tmp_name, QSE_NULL, fs->mmgr);
			if (fname == QSE_NULL)
			{
				fs->errnum = QSE_FS_ENOMEM;
				return QSE_NULL;
			}

			h = CreateFile (
				fname,
				GENERIC_READ, 
				FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 
				QSE_NULL, 
				OPEN_EXISTING,
				FILE_ATTRIBUTE_NORMAL, 
				0
			);

			QSE_MMGR_FREE (fs->mmgr, fname);

			if (h != INVALID_HANDLE_VALUE)
			{
				DWORD t = GetFileType (h);
				switch (t)
				{
					case FILE_TYPE_CHAR:
						fs->ent.type = QSE_FS_ENT_CHRDEV;
						break;
					case FILE_TYPE_DISK:
						fs->ent.type = QSE_FS_ENT_BLKDEV;
						break;
					case FILE_TYPE_PIPE:
						fs->ent.type = QSE_FS_ENT_PIPE;
						break;
					default:
						fs->ent.type = QSE_FS_ENT_UNKNOWN;
						break;
				}
				CloseHandle (h);
			}
			else
			{
				fs->ent.type = QSE_FS_ENT_UNKNOWN;
			}
		}
		fs->ent.type |= QSE_FS_ENT_TYPE;
	}

	if (flags & QSE_FS_ENT_SIZE)
	{
		LARGE_INTEGER li;
		li.LowPart = info->wfd.nFileSizeLow;
		li.HighPart = info->wfd.nFileSizeHigh;
		fs->ent.size = li.QuadPart;
		fs->ent.type |= QSE_FS_ENT_SIZE;
	}

	if (flags & QSE_FS_ENT_TIME)
	{
		filetime_to_ntime (&info->wfd.ftCreationTime, &fs->ent.time.create);
		filetime_to_ntime (&info->wfd.ftLastAccessTime, &fs->ent.time.access);
		filetime_to_ntime (&info->wfd.ftLastWriteTime, &fs->ent.time.modify);
		fs->ent.type |= QSE_FS_ENT_TIME;
	}

#elif defined(__OS2__)
	/* TODO: implement this */
#elif defined(__DOS__)
	/* TODO: implement this */
#else

	info_t* info;
	struct dirent* ent;
	int x;

	int stat_needed;
	qse_lstat_t st;

	info = fs->info;
	if (info == QSE_NULL) 
	{
		fs->errnum = QSE_FS_ENOTDIR;
		return QSE_NULL;
	}

	errno = 0;
	ent = readdir (info->handle);
	if (ent == QSE_NULL)
	{
		if (errno != 0) fs->errnum = qse_fs_syserrtoerrnum (fs, errno);
		return QSE_NULL;
	}

	QSE_MEMSET (&fs->ent, 0, QSE_SIZEOF(fs->ent));
	if (set_entry_name (fs, ent->d_name) <= -1) return QSE_NULL;

	stat_needed =
	#if !defined(HAVE_STRUCT_DIRENT_D_TYPE)
		(flags & QSE_FS_ENT_TYPE) ||
	#endif
		(flags & QSE_FS_ENT_SIZE) ||
		(flags & QSE_FS_ENT_TIME);
	if (stat_needed)
	{
		const qse_mchar_t* tmp_name[4];
		qse_mchar_t* mfname;

/* TODO: use a buffer in info... instead of allocating an deallocating every time */
		tmp_name[0] = info->mcurdir;
		tmp_name[1] = QSE_MT("/");
		tmp_name[2] = ent->d_name;
		tmp_name[3] = QSE_NULL;
		mfname = qse_mbsadup(tmp_name, QSE_NULL, fs->mmgr);
		if (mfname == QSE_NULL)
		{
			fs->errnum = QSE_FS_ENOMEM;
			return QSE_NULL;
		}
	
	#if defined(HAVE_LSTAT)
		x = QSE_LSTAT (mfname, &st);
	#else
		x = QSE_STAT (mfname, &st);
	#endif
		QSE_MMGR_FREE (fs->mmgr, mfname);

		if (x == -1)
		{
			fs->errnum = qse_fs_syserrtoerrnum (fs, errno);
			return QSE_NULL;
		}
	}

	if (flags & QSE_FS_ENT_TYPE)
	{
	#if defined(HAVE_STRUCT_DIRENT_D_TYPE) && defined(DT_DIR) && defined(DT_REG) /* and more */
		switch (ent->d_type)
		{
			case DT_DIR:
				fs->ent.type = QSE_FS_ENT_SUBDIR;
				break;

			case DT_REG:
				fs->ent.type = QSE_FS_ENT_REGULAR;
				break;

			case DT_LNK:
				fs->ent.type = QSE_FS_ENT_SYMLINK;
				break;

			case DT_BLK: 
				fs->ent.type = QSE_FS_ENT_BLKDEV;
				break;

			case DT_CHR:
				fs->ent.type = QSE_FS_ENT_CHRDEV;
				break;

			case DT_FIFO:
	#if defined(DT_SOCK)
			case DT_SOCK:
	#endif
				fs->ent.type = QSE_FS_ENT_PIPE;
				break;

			default:
				fs->ent.type = QSE_FS_ENT_UNKNOWN;
				break;
		}

	#else
		#if defined(__S_IFMT) && !defined(S_IFMT)
		#	define S_IFMT __S_IFMT
		#endif
		#if defined(__S_IFDIR) && !defined(S_IFDIR)
		#	define S_IFDIR __S_IFDIR
		#endif
		#define IS_TYPE(st,type) ((st.st_mode & S_IFMT) == S_IFDIR)
		fs->ent.type = IS_TYPE(st,S_IFDIR)?  QSE_FS_ENT_SUBDIR:
		               IS_TYPE(st,S_IFREG)?  QSE_FS_ENT_REGULAR:
		               IS_TYPE(st,S_IFLNK)?  QSE_FS_ENT_SYMLINK:
		               IS_TYPE(st,S_IFCHR)?  QSE_FS_ENT_CHRDEV:
		               IS_TYPE(st,S_IFBLK)?  QSE_FS_ENT_BLKDEV:
		               IS_TYPE(st,S_IFIFO)?  QSE_FS_ENT_PIPE:
		#if defined(S_IFSOCK)
		               IS_TYPE(st,S_IFSOCK)? QSE_FS_ENT_PIPE:
		#endif
		                                     QSE_FS_ENT_UNKNOWN;
		#undef IS_TYPE
	#endif
		fs->ent.flags |= QSE_FS_ENT_TYPE;
	}

	if (flags & QSE_FS_ENT_SIZE)
	{
		fs->ent.size = st.st_size;
		fs->ent.flags |= QSE_FS_ENT_SIZE;
	}

	if (flags & QSE_FS_ENT_TIME)
	{
	#if defined(HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC)
		#if defined(HAVE_STRUCT_STAT_ST_BIRTHTIM_TV_NSEC)
		fs->ent.time.create.sec = st.st_birthtim.tv_sec;
		fs->ent.time.create.nsec = st.st_birthtim.tv_nsec;
		#endif

		fs->ent.time.access.sec = st.st_atim.tv_sec;
		fs->ent.time.access.nsec = st.st_atim.tv_nsec;
		fs->ent.time.modify.sec = st.st_mtim.tv_sec;
		fs->ent.time.modify.nsec = st.st_mtim.tv_nsec;
		fs->ent.time.change.sec = st.st_ctim.tv_sec;
		fs->ent.time.change.nsec = st.st_ctim.tv_nsec;
	#elif defined(HAVE_STRUCT_STAT_ST_MTIMESPEC_TV_NSEC)
		#if defined(HAVE_STRUCT_STAT_ST_BIRTHTIMESPEC_TV_NSEC)
		fs->ent.time.create.sec = st.st_birthtimespec.tv_sec;
		fs->ent.time.create.nsec = st.st_birthtimespec.tv_nsec;
		#endif

		fs->ent.time.access.sec = st.st_atimespec.tv_sec;
		fs->ent.time.access.nsec = st.st_atimespec.tv_nsec;
		fs->ent.time.modify.sec = st.st_mtimespec.tv_sec;
		fs->ent.time.modify.nsec = st.st_mtimespec.tv_nsec;
		fs->ent.time.change.sec = st.st_ctimespec.tv_sec;
		fs->ent.time.change.nsec = st.st_ctimespec.tv_nsec;
	#else
		#if defined(HAVE_STRUCT_STAT_ST_BIRTHTIME)
		fs->ent.time.create.sec = st.st_birthtime;
		#endif
		fs->ent.time.access.sec = st.st_atime;
		fs->ent.time.modify.sec = st.st_mtime;
		fs->ent.time.change.sec = st.st_ctime;
	#endif
		fs->ent.flags |= QSE_FS_ENT_TIME;
	}
#endif

	return &fs->ent;
}

int qse_fs_rewind (qse_fs_t* fs)
{
	return 0;
}


qse_fs_char_t* qse_fs_makefspathformbs (qse_fs_t* fs, const qse_mchar_t* path)
{
	qse_fs_char_t* fspath;

#if defined(QSE_FS_CHAR_IS_MCHAR)
	fspath = (qse_mchar_t*)path;
#else
	fspath = qse_mbstowcsdupwithcmgr (path, QSE_NULL, fs->mmgr, fs->cmgr);
	if (!fspath) fs->errnum = QSE_FS_ENOMEM;
#endif

	return fspath;
}

qse_fs_char_t* qse_fs_makefspathforwcs (qse_fs_t* fs, const qse_wchar_t* path)
{
	qse_fs_char_t* fspath;

#if defined(QSE_FS_CHAR_IS_MCHAR)
	fspath = qse_wcstombsdupwithcmgr (path, QSE_NULL, fs->mmgr, fs->cmgr);
	if (!fspath) fs->errnum = QSE_FS_ENOMEM;
#else
	fspath = path;
#endif

	return fspath;
}

qse_fs_char_t* qse_fs_dupfspathformbs (qse_fs_t* fs, const qse_mchar_t* path)
{
	qse_fs_char_t* fspath;

#if defined(QSE_FS_CHAR_IS_MCHAR)
	fspath = qse_mbsdup (path, fs->mmgr);
#else
	fspath = qse_mbstowcsdupwithcmgr (path, QSE_NULL, fs->mmgr, fs->cmgr);
	if (!fspath) fs->errnum = QSE_FS_ENOMEM;
#endif

	return fspath;
}

qse_fs_char_t* qse_fs_dupfspathforwcs (qse_fs_t* fs, const qse_wchar_t* path)
{
	qse_fs_char_t* fspath;

#if defined(QSE_FS_CHAR_IS_MCHAR)
	fspath = qse_wcstombsdupwithcmgr (path, QSE_NULL, fs->mmgr, fs->cmgr);
	if (!fspath) fs->errnum = QSE_FS_ENOMEM;
#else
	fspath = qse_wcsdup (path, fs->mmgr);
#endif

	return fspath;
}

void qse_fs_freefspathformbs (qse_fs_t* fs, const qse_mchar_t* path, qse_fs_char_t* fspath)
{
	if (path != (const qse_mchar_t*)fspath) QSE_MMGR_FREE (fs->mmgr, fspath);
}

void qse_fs_freefspathforwcs (qse_fs_t* fs, const qse_wchar_t* path, qse_fs_char_t* fspath)
{
	if (path != (const qse_wchar_t*)fspath) QSE_MMGR_FREE (fs->mmgr, fspath);
}


int qse_fs_invokeactcb (qse_fs_t* fs, qse_fs_action_t action, qse_fs_char_t* src_fspath, qse_fs_char_t* dst_fspath, qse_uintmax_t bytes_total, qse_uintmax_t bytes_done)
{
	qse_char_t* srcpath = QSE_NULL, * dstpath = QSE_NULL;
	int x = 1;

	if (src_fspath) 
	{
		srcpath = (qse_char_t*)make_str_with_fspath (fs, src_fspath);
		if (!srcpath) 
		{
			x = -1;
			goto done;
		}
	}
	if (dst_fspath) 
	{
		dstpath = (qse_char_t*)make_str_with_fspath (fs, dst_fspath);
		if (!dstpath)
		{
			x = -1;
			goto done;
		}
	}

	x = fs->cbs.actcb (fs, action, srcpath, dstpath, bytes_total, bytes_done);

done:
	if (srcpath) free_str_with_fspath (fs, cpfile->src_fspath, srcpath);
	if (dstpath) free_str_with_fspath (fs, cpfile->dst_fspath, dstpath);

	return x;
}
