/***************************************************************************

  gb_file_temp.h

  (c) 2000-2017 Benoît Minisini <benoit.minisini@gambas-basic.org>

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2, or (at your option)
  any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
  MA 02110-1301, USA.

***************************************************************************/

#define __GB_FILE_C

#include "config.h"

#include "gb_common.h"
#include "gb_error.h"
#include "gb_limit.h"
#include "gb_component.h"

#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <dirent.h>
#include <pwd.h>

#ifdef PROJECT_EXEC

#include <sys/statvfs.h>

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

#endif

#include "gb_file.h"

#define OPT_NLINK 1
#define OPT_FSTATAT 1

#ifdef PROJECT_EXEC

static DIR *_file_dir = NULL;
static char *_file_pattern = NULL;
static char *_file_path = NULL;
static bool _file_dir_arch = FALSE;
static bool _file_dir_filter = FALSE;
static uchar _file_attr;
static bool _temp_used = FALSE;

#endif

static char _file_buffer[PATH_MAX + 16];
static int _file_buffer_length;

#ifdef _DIRENT_HAVE_D_TYPE
#undef _DIRENT_HAVE_D_TYPE
#endif

#ifdef _DIRENT_HAVE_D_TYPE
static bool _last_is_dir;
#endif

static char *_home = NULL;
static uid_t _uid = 0;

#ifdef PROJECT_EXEC

typedef
	struct _path {
		struct _path *next;
		char *path;
		}
	FILE_PATH;

typedef
	struct {
		unsigned int mask;
		unsigned char test[4];
		unsigned int mode[4];
	}
	FILE_MODE_DECODE;

//static int _count = 0;

static void push_path(void **list, const char *path)
{
	FILE_PATH *slot;

	ALLOC(&slot, sizeof(FILE_PATH));
	slot->path = STRING_new_zero(path);

	slot->next = *list;
	*list = slot;

	/*_count++;
	fprintf(stderr, "[%d] push_path: %s\n", _count, path);*/
}


static char *pop_path(void **list)
{
	char *path;
	FILE_PATH *slot;

	if (!*list)
		return NULL;

	path = ((FILE_PATH *)*list)->path;
	slot = *list;
	*list = ((FILE_PATH *)*list)->next;
	FREE(&slot);

	/*_count--;
	fprintf(stderr, "[%d] pop_path: %s\n", _count, path);*/
	return path;
}


static void dir_exit(void)
{
	if (_file_dir != NULL)
	{
		closedir(_file_dir);
		_file_dir = NULL;
	}

	STRING_free(&_file_pattern);
	STRING_free(&_file_path);
}


char *FILE_make_temp(int *len, const char *pattern)
{
	static int count = 0;

	_temp_used = TRUE;

	if (len)
	{
		if (pattern)
			*len = snprintf(_file_buffer, sizeof(_file_buffer), FILE_TEMP_PATTERN, (int)getuid(), (int)getpid(), pattern);
		else
		{
			count++;
			*len = snprintf(_file_buffer, sizeof(_file_buffer), FILE_TEMP_FILE, (int)getuid(), (int)getpid(), count);
		}
	}
	else
		snprintf(_file_buffer, sizeof(_file_buffer), FILE_TEMP_DIR, (int)getuid(), (int)getpid());

	return _file_buffer;
}

static void remove_temp_file(const char *path)
{
	if (FILE_is_dir(path))
	{
		//fprintf(stderr, "rmdir: %s\n", path);
		rmdir(path);
	}
	else
	{
		//fprintf(stderr, "unlink: %s\n", path);
		unlink(path);
	}
}

void FILE_remove_temp_file(void)
{
	if (_temp_used)
		FILE_recursive_dir(FILE_make_temp(NULL, NULL), NULL, remove_temp_file, 0, FALSE);
	rmdir(FILE_make_temp(NULL, NULL));
	_temp_used = FALSE;
}

void FILE_remove_temp_file_pid(pid_t pid)
{
	snprintf(_file_buffer, sizeof(_file_buffer), FILE_TEMP_DIR, (int)getuid(), (int)pid);
	FILE_recursive_dir(_file_buffer, NULL, remove_temp_file, 0, FALSE);
	snprintf(_file_buffer, sizeof(_file_buffer), FILE_TEMP_DIR, (int)getuid(), (int)pid);
	rmdir(_file_buffer);
}

void FILE_init(void)
{
	struct stat info;

	_temp_used = TRUE;
	FILE_remove_temp_file();

	snprintf(_file_buffer, sizeof(_file_buffer), FILE_TEMP_PREFIX, (int)getuid());
	(void)mkdir(_file_buffer, S_IRWXU);

	if (lstat(_file_buffer, &info) == 0 && S_ISDIR(info.st_mode) && chown(_file_buffer, getuid(), getgid()) == 0 && chmod(_file_buffer, S_IRWXU) == 0)
	{
		snprintf(_file_buffer, sizeof(_file_buffer), FILE_TEMP_DIR, (int)getuid(), (int)getpid());
		(void)mkdir(_file_buffer, S_IRWXU);
		if (lstat(_file_buffer, &info) == 0 && S_ISDIR(info.st_mode) && chown(_file_buffer, getuid(), getgid()) == 0 && chmod(_file_buffer, S_IRWXU) == 0)
			return;
	}

	ERROR_fatal("cannot initialize interpreter temporary directory. Do you try to hijack Gambas?");
}

void FILE_exit(void)
{
	STRING_free(&_home);
	FILE_remove_temp_file();
	dir_exit();
}

#else

void FILE_exit(void)
{
	STR_free(_home);
}

#endif

static char *stradd(char *d, const char *s)
{
	for(;;)
	{
		if ((*d = *s) == 0)
			break;
		d++;
		s++;
	}

	return d;
}

/*#define stradd(d, s) \
({ \
	char *_d = (d); \
	const char *_s = (s); \
	\
	for(;;) \
	{ \
		if ((*_d = *_s) == 0) \
			break; \
		\
		_d++; \
		_s++; \
	} \
	\
	_d; \
})*/

const char *FILE_cat(const char *path, ...)
{
	char *p;
	va_list args;
	int len;
	bool end_slash = FALSE;
	bool add_slash = FALSE;

	va_start(args, path);

	p = _file_buffer;

	if (path != _file_buffer)
		*p = 0;

	for(;;)
	{
		if (*path == '/' && p != _file_buffer)
			path++;

		len = strlen(path);
		if (add_slash)
			len++;

		if (len > 0)
		{
			if ((p + len) > &_file_buffer[PATH_MAX])
				THROW(E_TOOLONG);

			if (p != path)
			{
				if (add_slash)
					p = stradd(p, "/");

				p = stradd(p, path);
			}
			else
				p += len;

			end_slash = (p[-1] == '/');
		}

		path = va_arg(args, char *);
		if (path == NULL)
			break;

		add_slash = ((!end_slash) && (*path != 0) && (*path != '/'));
	}

	va_end(args);

	_file_buffer_length = p - _file_buffer;
	return _file_buffer;
}


char *FILE_buffer(void)
{
	return _file_buffer;
}


int FILE_buffer_length(void)
{
	if (_file_buffer_length < 0)
		_file_buffer_length = strlen(_file_buffer);

	return _file_buffer_length;
}

static void init_file_buffer(const char *path)
{
	int len;

	if (path == _file_buffer)
		return;

	len = strlen(path);

	if (len > PATH_MAX)
		THROW(E_TOOLONG);

	strcpy(_file_buffer, path);
	_file_buffer_length = len;
}

//#define INIT_FILE_BUFFER(_path) (init_file_buffer(), _path = _file_buffer)

const char *FILE_get_dir(const char *path)
{
	char *p;

	if (path == NULL || path[0] == 0)
		return NULL;

	if (path[0] == '/' && path[1] == 0)
		return "/";

	init_file_buffer(path);

	p = rindex(_file_buffer, '/');

	if (p == NULL)
		*_file_buffer = 0;
	else
	{
		*p = 0;

		if (_file_buffer[0] == 0 && path[0] == '/')
			strcpy(_file_buffer, "/");
	}

	_file_buffer_length = -1;
	return _file_buffer;
}


const char *FILE_get_name(const char *path)
{
	const char *p;

	p = rindex(path, '/');
	if (p)
		return &p[1];
	else
		return path;
}


const char *FILE_get_ext(const char *path)
{
	const char *p;

	p = rindex(path, '/');
	if (p)
		path = &p[1];

	p = rindex(path, '.');
	if (p == NULL)
		return &path[strlen(path)];
	else
		return p + 1;
}


const char *FILE_set_ext(const char *path, const char *ext)
{
	char *p;

	init_file_buffer(path);

	p = (char *)FILE_get_ext(_file_buffer);

	if (!ext)
	{
		if (p > _file_buffer && p[-1] == '.')
			p[-1] = 0;
		else
			*p = 0;
		return _file_buffer;
	}

	if (&p[strlen(ext)] >= &_file_buffer[PATH_MAX])
		THROW(E_TOOLONG);

	if (p == _file_buffer || p[-1] != '.')
		*p++ = '.';

	if (*ext == '.')
		ext++;

	strcpy(p, ext);

	_file_buffer_length = -1;
	return _file_buffer;
}


const char *FILE_get_basename(const char *path)
{
	char *p;

	path = FILE_get_name(path);

	init_file_buffer(path);

	p = rindex(_file_buffer, '.');
	if (p)
		*p = 0;

	_file_buffer_length = -1;
	return _file_buffer;
}

bool FILE_is_dir(const char *path)
{
	struct stat buf;

#ifdef PROJECT_EXEC

	if (FILE_is_relative(path))
	{
		/*if (!EXEC_arch)
		{
			chdir(PROJECT_path);
			if (lstat(path, &buf) == 0)
				goto __OK;
		}*/

		return ARCHIVE_is_dir(NULL, path);
	}

#endif

	if (stat(path, &buf))
		return FALSE;

/*#ifdef PROJECT_EXEC
__OK:
#endif*/

	return (S_ISDIR(buf.st_mode));
}


#ifdef PROJECT_EXEC

bool FILE_exist_real(const char *path)
{
	struct stat buf;

	if (chdir(PROJECT_path)) return FALSE;
	return (stat(path, &buf) == 0);
}

bool FILE_stat(const char *path, FILE_STAT *info, bool follow)
{
	struct stat buf;
	int ret;

	//fprintf(stderr, "FILE_stat: %s\n", path);

	if (FILE_is_relative(path))
	{
		if (ARCHIVE_stat(NULL, path, info))
		{
			errno = ENOENT;
			return TRUE;
		}
		else
			return FALSE;
	}

	if (follow)
		ret = stat(path, &buf);
	else
		ret = lstat(path, &buf);

	if (ret)
		return TRUE;

//_OK:

	if (S_ISREG(buf.st_mode))
		info->type = GB_STAT_FILE;
	else if (S_ISDIR(buf.st_mode))
		info->type = GB_STAT_DIRECTORY;
	else if (S_ISCHR(buf.st_mode) || S_ISBLK(buf.st_mode))
		info->type = GB_STAT_DEVICE;
	else if (S_ISFIFO(buf.st_mode))
		info->type = GB_STAT_PIPE;
	else if (S_ISSOCK(buf.st_mode))
		info->type = GB_STAT_SOCKET;
	else if (S_ISLNK(buf.st_mode))
		info->type = GB_STAT_LINK;

	info->mode = buf.st_mode & 07777;
	info->size = buf.st_size;
	info->atime = (int)buf.st_atime;
	info->mtime = (int)buf.st_mtime;
	info->ctime = (int)buf.st_ctime;
	info->hidden = (*FILE_get_name(path) == '.');
	info->uid = buf.st_uid;
	info->gid = buf.st_gid;
	info->device = buf.st_dev;
	info->blkdev = S_ISBLK(buf.st_mode);
	info->chrdev = S_ISCHR(buf.st_mode);

	return FALSE;
}

char *FILE_mode_to_string(mode_t mode)
{
	char *str = _file_buffer;

  str[0] = mode & S_IRUSR ? 'r' : '-';
  str[1] = mode & S_IWUSR ? 'w' : '-';
  str[2] = (mode & S_ISUID
            ? (mode & S_IXUSR ? 's' : 'S')
            : (mode & S_IXUSR ? 'x' : '-'));
  str[3] = mode & S_IRGRP ? 'r' : '-';
  str[4] = mode & S_IWGRP ? 'w' : '-';
  str[5] = (mode & S_ISGID
            ? (mode & S_IXGRP ? 's' : 'S')
            : (mode & S_IXGRP ? 'x' : '-'));
  str[6] = mode & S_IROTH ? 'r' : '-';
  str[7] = mode & S_IWOTH ? 'w' : '-';
  str[8] = (mode & S_ISVTX
            ? (mode & S_IXOTH ? 't' : 'T')
            : (mode & S_IXOTH ? 'x' : '-'));
	str[9] = 0;

	_file_buffer_length = 9;

	return str;
}

mode_t FILE_mode_from_string(mode_t mode, const char *str)
{
	static FILE_MODE_DECODE decode[] = {
		{ S_IRUSR, { '-', 'r' }, { 0, S_IRUSR } },
		{ S_IWUSR, { '-', 'w' }, { 0, S_IWUSR } },
		{ S_IXUSR | S_ISUID, { '-', 'x', 'S', 's' }, { 0, S_IXUSR, S_ISUID, S_IXUSR | S_ISUID } },
		{ S_IRGRP, { '-', 'r' }, { 0, S_IRGRP } },
		{ S_IWGRP, { '-', 'w' }, { 0, S_IWGRP } },
		{ S_IXGRP | S_ISGID, { '-', 'x', 'S', 's' }, { 0, S_IXGRP, S_ISGID, S_IXGRP | S_ISGID } },
		{ S_IROTH, { '-', 'r' }, { 0, S_IROTH } },
		{ S_IWOTH, { '-', 'w' }, { 0, S_IWOTH } },
		{ S_IXOTH | S_ISVTX, { '-', 'x', 'T', 't' }, { 0, S_IXOTH, S_ISVTX, S_IXOTH | S_ISVTX } },
		{ 0 }
	};

	unsigned char c, test;
	FILE_MODE_DECODE *d = decode;
	int i;

	while (d->mask)
	{
		c = *str++;
		if (!c)
			break;

		for (i = 0; i <= 3; i++)
		{
			test = d->test[i];
			if (!test)
				break;
			if (c == test)
			{
				mode = (mode & ~(d->mask)) | d->mode[i];
				break;
			}
		}

		d++;
	}

	return mode;
}


void FILE_chmod(const char *path, mode_t mode)
{
	if (FILE_is_relative(path))
		THROW(E_ACCESS);

	if (chmod(path, mode))
		THROW_SYSTEM(errno, path);
}


void FILE_chown(const char *path, const char *user)
{
	struct passwd *pwd;
	uid_t uid;

	if (FILE_is_relative(path))
		THROW(E_ACCESS);

	if (!strcmp(user, "root"))
		uid = (uid_t)0;
	else
	{
		errno = 0;
		pwd = getpwnam(user);
		if (errno)
			THROW_SYSTEM(errno, path);
		if (!pwd)
			THROW(E_USER);
		uid = pwd->pw_uid;
	}

	if (chown(path, uid, (gid_t)-1))
		THROW_SYSTEM(errno, path);
}


void FILE_chgrp(const char *path, const char *group)
{
	struct group *grp;
	gid_t gid;

	if (FILE_is_relative(path))
		THROW(E_ACCESS);

	if (!strcmp(group, "root"))
		gid = (gid_t)0;
	else
	{
		errno = 0;
		grp = getgrnam(group);
		if (errno)
			THROW_SYSTEM(errno, path);
		if (!grp)
			THROW(E_USER);
		gid = grp->gr_gid;
	}

	if (chown(path, (uid_t)-1, gid))
		THROW_SYSTEM(errno, path);
}


void FILE_dir_first(const char *path, const char *pattern, int attr)
{
	//fprintf(stderr, "FILE_dir_first: %s\n", path);
	dir_exit();

	if (!path || *path == 0)
		path = ".";

	_file_attr = (uchar)attr;
	
	if (_file_attr == (GB_STAT_FILE | GB_STAT_DIRECTORY))
		_file_attr = 0;


	if (FILE_is_relative(path))
	{
		/*if (!EXEC_arch)
		{
			chdir(PROJECT_path);
			if (lstat(path, &buf) == 0)
				goto _OK;
		}*/

		_file_dir_arch = TRUE;
		_file_dir_filter = (ARCHIVE_dir_first(NULL, path, pattern, attr));
			
		return;
	}

	_file_dir_arch = FALSE;
	_file_dir_filter = FALSE;
	_file_dir = opendir(path);
	if (_file_dir == NULL)
		THROW_SYSTEM(errno, path);

	_file_pattern = STRING_new_zero(pattern);
	_file_path = STRING_new_zero(path);
}

#if OPT_FSTATAT
#else
	#ifdef FSTATAT
		#undef FSTATAT
	#endif
#endif

bool FILE_dir_next(char **path, int *len)
{
	struct dirent *entry;
	int len_entry;
	bool ret;
	char *name;
	#ifndef _DIRENT_HAVE_D_TYPE
	struct stat info;
		#ifndef HAVE_FSTATAT
		char *p = _file_buffer;
		#endif
	#endif

	//fprintf(stderr, "FILE_dir_next: %p %d %d\n", file_dir, file_dir_arch, file_attr);

	if (_file_dir_arch)
	{
		ret = ARCHIVE_dir_next(path, len, _file_attr);
		if (ret)
			_file_dir_arch = FALSE;
		return ret;
	}

	if (_file_dir == NULL)
		return TRUE;

	#ifndef _DIRENT_HAVE_D_TYPE
		#ifndef HAVE_FSTATAT
		if (_file_attr)
		{
			init_file_buffer(_file_path);
			p += _file_buffer_length;

			if (p[-1] != '/' && (_file_buffer[1] || _file_buffer[0] != '/'))
				*p++ = '/';
		}
		#endif
	#endif

	for(;;)
	{
		entry = readdir(_file_dir);
		if (entry == NULL)
		{
			//fprintf(stderr, "readdir: NULL\n");
			dir_exit();
			return TRUE;
		}

		name = entry->d_name;
		//fprintf(stderr, "readdir: %s\n", name);

		if (name[0] == '.' && (name[1] == 0 || (name[1] == '.' && name[2] == 0)))
			continue;

		len_entry = strlen(name);
		if ((len_entry + _file_buffer_length) > PATH_MAX)
			continue;
		
		if (_file_dir_filter)
		{
			if (name[len_entry - 1] == '~')
				continue;
		}

		if (_file_attr)
		{
			#ifdef _DIRENT_HAVE_D_TYPE
			if ((_file_attr == GB_STAT_DIRECTORY) ^ (entry->d_type == DT_DIR))
				continue;
			#else
				#if HAVE_FSTATAT
				if (fstatat(dirfd(_file_dir), name, &info, 0))
				#else
				strcpy(p, name);
				if (stat(_file_buffer, &info))
				#endif
				{
					if (_file_attr == GB_STAT_DIRECTORY)
						continue;
				}
				else if ((_file_attr == GB_STAT_DIRECTORY) ^ (S_ISDIR(info.st_mode) != 0))
					continue;
			#endif
		}

		if (_file_pattern == NULL)
			break;

		if (REGEXP_match(_file_pattern, STRING_length(_file_pattern), name, len_entry))
			break;
	}

	*path = name;
	*len = len_entry;
	#ifdef _DIRENT_HAVE_D_TYPE
	_last_is_dir = entry->d_type == DT_DIR;
	#endif

	return FALSE;
}

//#undef _DIRENT_HAVE_D_TYPE

void FILE_recursive_dir(const char *dir, void (*found)(const char *), void (*afterfound)(const char *), int attr, bool follow)
{
	char *file_rdir_path;
	int file_rdir_len;
	void *list = NULL;
	void *dir_list = NULL;
	char *file;
	int len;
	char *path;
	//struct stat buf;
	#ifdef _DIRENT_HAVE_D_TYPE
	#else
	FILE_STAT info;
	#endif
	bool is_dir;
	#if OPT_NLINK
	int nsubdir = -1;
	#endif

	if (!dir || *dir == 0)
		dir = ".";
	else if (!FILE_is_dir(dir))
		return;

	FILE_dir_first(dir, NULL, attr != GB_STAT_DIRECTORY ? 0 : GB_STAT_DIRECTORY);

	file_rdir_path = STRING_new_zero(dir);
	file_rdir_len = STRING_length(file_rdir_path);
	if (file_rdir_path[file_rdir_len - 1] == '/')
		file_rdir_len--;

	#if OPT_NLINK
	if (_file_dir && !FILE_is_relative(dir))
	{
		struct stat dinfo;
		fstat(dirfd(_file_dir), &dinfo);
		// If the number of links N to the directory:
		// - Is > 2, then the directory has N - 2 sub-directories
		// - Is = 2, then the directory has no sub-directory
		// - Is < 2, then the file system is not POSIX, so we must scan everything
		nsubdir = dinfo.st_nlink - 2;
	}
	#endif


	while (!FILE_dir_next(&file, &len))
	{
		//fprintf(stderr, "FILE_dir_next: %.*s\n", len, file);

		if ((file_rdir_len + len + 1) > PATH_MAX)
			THROW(E_TOOLONG);
		memcpy(_file_buffer, file_rdir_path, file_rdir_len);
		_file_buffer[file_rdir_len] = '/';
		memcpy(&_file_buffer[file_rdir_len + 1], file, len);
		_file_buffer[file_rdir_len + len + 1] = 0;
		path = _file_buffer;

		#if OPT_NLINK
		if (nsubdir || follow)
		#endif
		{
			#ifdef _DIRENT_HAVE_D_TYPE
			is_dir = _last_is_dir;
			#else
			if (follow)
				is_dir = FILE_is_dir(path);
			else
			{
				if (FILE_stat(path, &info, FALSE))
					is_dir = FALSE;
				else
					is_dir = info.type == GB_STAT_DIRECTORY;
			}
			#endif

			if (is_dir)
			{
				nsubdir--;
				push_path(&dir_list, path);
				continue;
			}
		}

		push_path(&list, path);
	}

	STRING_free(&file_rdir_path);

	while (dir_list)
	{
		path = pop_path(&dir_list);
		//fprintf(stderr, "%s\n", path);

		TRY
		{
			if (found && (!attr || (attr & GB_STAT_DIRECTORY))) (*found)(path);
			FILE_recursive_dir(path, found, afterfound, attr, follow);
			if (afterfound && (!attr || (attr & GB_STAT_DIRECTORY))) (*afterfound)(path);
		}
		CATCH
		{
			//ERROR_print_at(stdout);
		}
		END_TRY

		STRING_free((char **)&path);
	}

	while (list)
	{
		path = pop_path(&list);
		//fprintf(stderr, "%s\n", path);

		TRY
		{
			if (found) (*found)(path);
			if (afterfound) (*afterfound)(path);
		}
		CATCH
		{
			//ERROR_print_at(stdout);
		}
		END_TRY

		STRING_free((char **)&path);
	}
}


void FILE_rmdir(const char *path)
{
	if (FILE_is_relative(path))
		THROW(E_ACCESS);

	if (rmdir(path) != 0)
	{
		if (errno == ENOTEMPTY || errno == EEXIST)
			THROW(E_NEMPTY);
		else
			THROW_SYSTEM(errno, path);
	}
}


void FILE_mkdir_mode(const char *path, mode_t mode)
{
	if (FILE_is_relative(path))
		THROW(E_ACCESS);

	if (mkdir(path, mode) != 0)
		THROW_SYSTEM(errno, path);
}


void FILE_make_path_dir(const char *path)
{
	int i;
	char c;

	if (FILE_is_relative(path))
		return;

	init_file_buffer(path);

	for (i = 1;; i++)
	{
		c = _file_buffer[i];
		if (c == 0)
			break;
		if (c == '/')
		{
			_file_buffer[i] = 0;
			(void)mkdir(_file_buffer, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);
			_file_buffer[i] = c;
		}
	}
}


void FILE_copy(const char *src, const char *dst)
{
	STREAM stream_src;
	STREAM stream_dst;
	int64_t len;
	int64_t n;
	char *buf = NULL;

	CLEAR(&stream_src);
	CLEAR(&stream_dst);

	if (FILE_exist(dst))
		THROW(E_EXIST, dst);

	ALLOC(&buf, MAX_IO);

	TRY
	{
		STREAM_open(&stream_src, src, GB_ST_READ);
		STREAM_open(&stream_dst, dst, GB_ST_CREATE);

		STREAM_lof(&stream_src, &len);

		while (len)
		{
			n = len > MAX_IO ? MAX_IO : len;
			STREAM_read(&stream_src, buf, n);
			STREAM_write(&stream_dst, buf, n);
			len -= n;
		}

		STREAM_close(&stream_src);
		STREAM_close(&stream_dst);

		FREE(&buf);
	}
	CATCH
	{
		if (stream_src.type)
			STREAM_close(&stream_src);
		if (stream_dst.type)
			STREAM_close(&stream_dst);
		FREE(&buf);

		PROPAGATE();
	}
	END_TRY
}


bool FILE_access(const char *path, int mode)
{
	int m;
	
	if (FILE_is_relative(path))
	{
		if (mode & (GB_ST_WRITE | GB_ST_EXEC))
			return FALSE;

		/*if (!EXEC_arch)
		{
			chdir(PROJECT_path);
			if (access(path, mode) == 0)
				return TRUE;
		}*/

		return ARCHIVE_exist(NULL, path);
	}

	m = 0;
	if (mode & GB_ST_READ) m += R_OK;
	if (mode & GB_ST_WRITE) m += W_OK;
	if (mode & GB_ST_EXEC) m += X_OK;
	
	return (access(path, m) == 0);
}


bool FILE_exist_follow(const char *path, bool follow)
{
	struct stat buf;

	if (FILE_is_relative(path))
		return ARCHIVE_exist(NULL, path);

	return follow ? (stat(path, &buf) == 0) : (lstat(path, &buf) == 0);
}


void FILE_link(const char *src, const char *dst)
{
	/* src can be relative */
	if (FILE_is_relative(dst))
		THROW(E_ACCESS);

	if (FILE_exist(dst))
		THROW(E_EXIST, dst);

	if (symlink(src, dst) != 0)
		THROW_SYSTEM(errno, dst);
}

int64_t FILE_free(const char *path)
{
	struct statvfs info;

	if (FILE_is_relative(path))
		return 0;

	statvfs(path, &info);
	return (int64_t)(getuid() == 0 ? info.f_bfree : info.f_bavail) * info.f_bsize;
}

#else

bool FILE_exist(const char *path)
{
	return (access(path, F_OK) == 0);
}

time_t FILE_get_time(const char *path)
{
	struct stat info;

	if (stat(path, &info) == 0)
		return info.st_mtime;
	else
		return (time_t)-1L;
}

size_t FILE_get_size(const char *path)
{
	struct stat info;

	if (stat(path, &info) == 0)
		return info.st_size;
	else
		return -1;
}

bool FILE_copy(const char *src, const char *dst)
{
	int src_fd;
	int dst_fd;
	ssize_t len;
	char *buf = NULL;
	int save_errno;
	struct stat info;

	//fprintf(stderr, "FILE_copy: %s -> %s\n", src, dst);

	if (stat(src, &info))
		return TRUE;

	src_fd = open(src, O_RDONLY);
	if (src_fd < 0)
	{
		//fprintf(stderr, "open src failed\n");
		return TRUE;
	}

	dst_fd = creat(dst, info.st_mode);
	if (dst_fd < 0)
	{
		//fprintf(stderr, "open dst failed\n");
		save_errno = errno;
		close(src_fd);
		errno = save_errno;
		return TRUE;
	}

	ALLOC(&buf, MAX_IO);

	for(;;)
	{
		len = read(src_fd, buf, MAX_IO);
		if (len == 0)
			break;
		if (len < 0 && errno == EINTR)
			continue;
		if (write(dst_fd, buf, len) < 0)
		{
			save_errno = errno;
			close(src_fd);
			close(dst_fd);
			unlink(dst);
			errno = save_errno;
			IFREE(buf);
			return TRUE;
		}
	}

	close(src_fd);
	close(dst_fd);
	IFREE(buf);

	return FALSE;
}

#endif

const char *FILE_getcwd(const char *subdir)
{
	if (getcwd(_file_buffer, PATH_MAX) == NULL)
		return NULL;

	_file_buffer_length = strlen(_file_buffer);

	if (subdir != NULL)
		return FILE_cat(_file_buffer, subdir, NULL);
	else
		return _file_buffer;
}


const char *FILE_readlink(const char *link)
{
	int len = readlink(link, _file_buffer, PATH_MAX);

	if (len < 0)
		return NULL;

	_file_buffer[len] = 0;
	_file_buffer_length = len;
	return _file_buffer;

}

const char *FILE_find_gambas(void)
{
	const char *path;

	path = getenv("GB_PATH");

	if (!path || !*path)
	{
		if (FILE_exist(GAMBAS_LINK_PATH))
		{
			path = FILE_readlink(GAMBAS_LINK_PATH);
			if (!path)
				path = GAMBAS_LINK_PATH;
		}
		else
		{
			path = GAMBAS_PATH "/gbx" GAMBAS_VERSION_STRING;
		}
	}

	return path;
}

void FILE_chdir(const char *path)
{
	#ifdef PROJECT_EXEC
	if (chdir(path))
		THROW_SYSTEM(errno, path);
	#else
	if (chdir(path))
		THROW("Cannot change current directory to '&1': &2", path, strerror(errno));
	#endif
}

void FILE_unlink(const char *path)
{
	#ifdef PROJECT_EXEC
	if (FILE_is_relative(path))
		THROW(E_ACCESS);

	if (unlink(path) != 0)
		THROW_SYSTEM(errno, path);
	#else
	if (unlink(path) != 0 && errno != ENOENT)
		THROW("Cannot remove file '&1': &2", path, strerror(errno));
	#endif
}


void FILE_rename_ext(const char *src, const char *dst, bool kill)
{
	#ifdef PROJECT_EXEC
	if (FILE_is_relative(src) || FILE_is_relative(dst))
		THROW(E_ACCESS);

	if (!kill)
	{
		if (FILE_exist(dst))
			THROW(E_EXIST, dst);
	}

	if (rename(src, dst) != 0)
		THROW_SYSTEM(errno, dst);
	#else
	if (rename(src, dst) != 0)
		THROW("Cannot rename file '&1' to '&2': &3", src, dst, strerror(errno));
	#endif
}

char *FILE_get_home(void)
{
	struct passwd *info;
	uid_t uid = getuid();

	if (!_home || _uid != uid)
	{
#ifdef PROJECT_EXEC
		STRING_free(&_home);
#else
		STR_free(_home);
#endif
		info = getpwuid(uid);
		if (info)
#ifdef PROJECT_EXEC
			_home = STRING_new_zero(info->pw_dir);
#else
			_home = STR_copy(info->pw_dir);
#endif
		_uid = uid;
	}

	return _home;
}

