/*
** 1998-05-25 -	This module contains various file and directory related operations, that
**		are needed here and there in the application.
** 1998-09-02 -	Debugged and improved the fut_dir_length() function somewhat.
*/

#include "gentoo.h"

#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <regex.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

#include "strutil.h"
#include "fileutil.h"
#include "userinfo.h"
#include "progress.h"

/* ----------------------------------------------------------------------------------------- */

#define	USER_NAME_MAX	(64)		/* Just an assumption, really. */

#define	MIN_COPY_CHUNK	(1 << 17)

/* Path components are relative to this if they're not absolute. */
static char	base_path[PATH_MAX];
/* The length of the base. Saves some calls to strlen() in the middle of everything. :) */
static int	base_len;

/* ----------------------------------------------------------------------------------------- */

/* 1998-05-21 -	Enter a directory, saving the old path for restoration later.
**		If <old> is NULL, no storing is done. Returns boolean success.
*/
gboolean fut_cd(const gchar *new_dir, gchar *old, gsize old_max)
{
	if(old != NULL)
		getcwd(old, old_max);

	return chdir(new_dir) == 0 ? TRUE : FALSE;
}

/* 1998-09-23 -	Return 1 if the named file exists, 0 otherwise. Since this is Unix
**		(I think), this matter is somewhat complex. But I choose to ignore
**		that for now, and pretend it's nice and simple. :)
*/
gboolean fut_exists(const gchar *name)
{
	struct stat	s;
	gint		old_errno = errno, ret;

	ret = lstat(name, &s) == 0;
	errno = old_errno;
	return ret ? TRUE : FALSE;
}

/* 1998-12-18 -	Sets <size> to the size of file <name>, in bytes. Returns 1 on success,
**		0 on failure (in case <size> is never touched).
*/
gboolean fut_size(const gchar *name, gsize *size)
{
	struct stat	s;
	gint		old_errno = errno, ret = 0;

	if(name != NULL && size != NULL && (ret = (stat(name, &s)) == 0))
		*size = (gsize) s.st_size;
	errno = old_errno;
	return ret ? TRUE : FALSE;
}

/* 1999-02-03 -	Works just like fut_size above, but takes an open file descriptor
**		instead of a filename. Sometimes handy.
*/
gboolean fut_fsize(gint fd, gsize *size)
{
	struct stat	s;
	gint		old_errno = errno, ret = 0;

	if(fd >= 0 && size != NULL && (ret = (fstat(fd, &s)) == 0))
		*size = (gsize) s.st_size;
	errno = old_errno;
	return ret ? TRUE : FALSE;
}

/* 1999-01-29 -	Development debugging routine that helps find fd leaks. */
void fut_check_fd(const gchar *label)
{
	gint	fd;

	if((fd = open(".", O_RDONLY)) >= 0)
	{
		fprintf(stderr, "%s fd: %d\n", label, fd);
		close(fd);
	}
}

/* ----------------------------------------------------------------------------------------- */

/* 1998-05-17 -	Return number of entries in given directory.
** 1998-07-13 -	If <nsize> is non-NULL, it is filled-in with the number of bytes required to
**		hold all file names (including '\0'-terminations). This saves **heaps** of memory.
** 1998-09-02 -	If the directory couldn't be read, we now return -1 (which is what the dirpane
**		module expected all along).
** 1998-12-14 -	Changed old <min> arg into <dp>, for compatibility with the fut_check_hide()
**		function's changed prototype.
** 1999-05-29 -	The old <nsize> (for name size) argument has been extended. It's now called
**		<auxsize>, and includes size requirements for symbolic links (link contents and
**		extra stat buffer).
** 1999-06-01 -	If <num_links> is non-NULL, it will be filled-in with the number of symlinks found.
*/
gint fut_dir_length(DirPane *dp, const gchar *path, gsize *auxsize, guint *num_links)
{
	DIR		*dir;
	MainInfo	*min = dp->main;
	struct dirent	*de;
	struct stat	stbuf;
	gint		num = -1;
	guint		nlink = 0;
	gsize		nl = 0;

	if((dir = opendir(path)) != NULL)
	{
		for(num = 0; (de = readdir(dir)) != NULL; num++)
		{
			if(min->cfg.dir_filter(de->d_name) && fut_check_hide(dp, de->d_name))
			{
				nl += strlen(de->d_name) + 1;
				if((auxsize != NULL) && (lstat(de->d_name, &stbuf) == 0))
				{
					if(S_ISLNK(stbuf.st_mode))
					{
						gchar	lbuf[PATH_MAX];
						gint	len;

						nlink++;
						nl += sizeof stbuf + 1;
						if((len = readlink(de->d_name, lbuf, sizeof lbuf)) > 0)
						{
							lbuf[len] = '\0';
							nl += len;
						}
					}
				}
			}
			else
				num--;		/* Compensate for ++ in loop head. Ugly. */
		}
		closedir(dir);
	}

	if(auxsize != NULL)
		*auxsize = nl;
	if(num_links != NULL)
		*num_links = nlink;
	return num;
}

/* ----------------------------------------------------------------------------------------- */

/* 1998-12-20 -	This is under the hood of the fut_dir_size() function. */
static gint recurse_dir_size(MainInfo *min, const gchar *path, guint64 *bytes, FUCount *fc, gboolean with_progress)
{
	gchar		old_dir[PATH_MAX];
	DIR		*dir;
	struct dirent	*de;
	struct stat	stat;

	if(path == NULL)
		return 0;

	if(fut_cd(path, old_dir, sizeof old_dir))
	{
		if((dir = opendir(".")) != NULL)
		{
			while((de = readdir(dir)) != NULL)
			{
				if(with_progress)
				{
					pgs_progress_item_begin(min, de->d_name, 0);
					if(pgs_progress_item_update(min, 0) == PGS_CANCEL)
					{
						errno = EINTR;
						break;
					}
				}
				if(!min->cfg.dir_filter(de->d_name))
					continue;
				if(lstat(de->d_name, &stat) == 0)
				{
					if(bytes != NULL)
						(*bytes) += stat.st_size;
					if(fc != NULL)
					{
						fc->num_bytes  += (guint64) stat.st_size;
						fc->num_blocks += (guint64) stat.st_blocks;
					}
					if(S_ISDIR(stat.st_mode))
					{
						if(fc != NULL)
							fc->num_dirs++;
						recurse_dir_size(min, de->d_name, bytes, fc, with_progress);
					}
					else if(!S_ISDIR(stat.st_mode) && fc != NULL)
					{
						if(S_ISREG(stat.st_mode))
							fc->num_files++;
						else if(S_ISLNK(stat.st_mode))
							fc->num_links++;
						else if(S_ISCHR(stat.st_mode))
							fc->num_cdevs++;
						else if(S_ISBLK(stat.st_mode))
							fc->num_bdevs++;
						else if(S_ISFIFO(stat.st_mode))
							fc->num_fifos++;
						else if(S_ISSOCK(stat.st_mode))
							fc->num_socks++;
					}
				}
				if(with_progress)
					pgs_progress_item_end(min);
			}
			closedir(dir);
		}
		fut_cd(old_dir, NULL, 0);
	}
	return errno == 0;
}

static gint do_dir_size(MainInfo *min, const gchar *path, guint64 *bytes, FUCount *fc, gboolean with_progress)
{
	gint	ret;

	if(fc != NULL)		/* If there is a fc present, wipe it clean. */
	{
		fc->num_dirs  = 0;
		fc->num_files = 0;
		fc->num_links = 0;
		fc->num_cdevs = 0;
		fc->num_bdevs = 0;
		fc->num_fifos = 0;
		fc->num_socks = 0;

		fc->num_total = 0;

		fc->num_bytes  = 0;
		fc->num_blocks = 0;
	}
	if(bytes != NULL)
		*bytes = 0;
	if((ret = recurse_dir_size(min, path, bytes, fc, with_progress)) != 0 && fc != NULL)
		fc->num_total = fc->num_dirs + fc->num_files + fc->num_links + fc->num_cdevs +
				fc->num_bdevs + fc->num_fifos + fc->num_socks;

	return ret;
}

/* 1998-12-19 -	Scan recursively at <path>, accumulating total number of bytes seen, as well as counting
**		how many different "objects" (dirs, files, links etc) were seen. Will never follow a link
**		to a dir or count it as anything but a soft link. <fc> can be NULL if you just don't care.
**		Returns 1 on success, 0 on failure (such as permission denied).
** 1999-03-22 -	Now zeros the <bytes> value before beginning. This fixes a bug in the GetSize command.
*/
gint fut_dir_size(MainInfo *min, const gchar *path, guint64 *bytes, FUCount *fc)
{
	return do_dir_size(min, path, bytes, fc, FALSE);
}

/* 1999-03-28 -	Size up a directory, with GUI progress indication. */
gint fut_dir_size_progress(MainInfo *min, const gchar *path, guint64 *bytes, FUCount *fc)
{
	return do_dir_size(min, path, bytes, fc, TRUE);
}

/* ----------------------------------------------------------------------------------------- */

/* 1998-09-22 -	Evaluate (look up) any environment variable(s) used in <component>. Replaces
**		the entire string with a new version, where all environment variable names
**		have been replaced by the corresponding variable's value. Undefined variables
**		are treated as if their value were "" (empty string). Considers anything with
**		letters, digits and underscores to be a valid variable name. You can also use
**		braces {around} any combination of characters to force a look-up on that.
*/
static void eval_env(char *component)
{ 
	GString	*temp;
	char	*ptr, vname[256], *vptr, *vval;

	if((temp = g_string_new(NULL)) != NULL)
	{
		for(ptr = component; *ptr != '\0'; ptr++)
		{
			if(*ptr == '$')		/* Environment variable? */
			{
				ptr++;
				if(*ptr == '{')		/* Braced name? */
				{
					for(vptr = vname, ptr++; *ptr && *ptr != '}' &&
						(gsize) (vptr - vname) < sizeof vname - 1;)
					{
						*vptr++ = *ptr++;
					}
					ptr++;
				}
				else
				{
					for(vptr = vname; *ptr && (isalnum((int) *ptr) || *ptr == '_') &&
						(gsize) (vptr - vname) < sizeof vname - 1;)
					{
						*vptr++ = *ptr++;
					}
				}
				*vptr = '\0';
				if((vval = getenv(vname)) != NULL)
					g_string_append(temp, vval);
				ptr--;
			}
			else
				g_string_append_c(temp, *ptr);
		}
		str_strncpy(component, temp->str, PATH_MAX);
		g_string_free(temp, TRUE);
	}
}

static void eval_home(char *component)
{
	GString	*temp;
	char	*ptr, ubuf[USER_NAME_MAX], *uptr, *uname = ubuf, *dir;

	if((temp = g_string_new(NULL)) != NULL)
	{
		for(ptr = component + 1, uptr = ubuf; *ptr && *ptr != '/' &&
			(gsize) (uptr - ubuf) < sizeof ubuf - 1;)
		{
			*uptr++ = *ptr++;
		}
		*uptr = '\0';
		if((dir = (char *) usr_lookup_uhome(uname)) != NULL)
			g_string_assign(temp, dir);
		else
			g_string_assign(temp, "");
		g_string_append(temp, ptr);
		str_strncpy(component, temp->str, PATH_MAX);
		g_string_free(temp, TRUE);
	}
}

/* 1998-08-23 -	Here's a strtok()-workalike. Almost. The arg <paths> should be either a string
**		of colon-separated path components (such as "/usr/local:/home/emil:/root"), or
**		NULL. The function fills in <component> with either the first component, or, when
**		<paths> is NULL, with the _next_ component (relative to the last one). If this sounds
**		confusing, read the man page for strtok(3) and associate freely from there. The main
**		difference between this and strtok(3) is that the latter modifies the string being
**		tokenized, whiile this function does not. The <component> buffer is assumed to have
**		room for PATH_MAX bytes.
**		Returns 0 if no more components were found, 1 otherwise.
** 1998-08-24 -	Now supports use of environment variable lookups as a "prefix" in a path component,
**		thus allowing things like "$HOME/.gentoo/icons". Neat.
** 1998-08-30 -	Added support for a 'base' path, which is global to this module (set to the current
**		working dir by a call to fut_initialize()). The base is prepended to any relative
**		path component, thus making this more predictable and generally nicer.
** 1998-09-22 -	Rewrote the support for environment variables; now you can embed them and stuff.
*/
gint fut_path_component(const gchar *paths, gchar *component)
{
	static const gchar	*src = NULL;
	gchar			here, *dst = component;
	gint			comp_len, use_env = 0;

	if(paths != NULL)
		src = paths;
	else if(src == NULL)
		return 0;

	while(*src == ':')			/* Ignore empty components. */
		src++;
	for(; (here = *src) != '\0'; src++)
	{
		if(here == ':')
			break;
		else
			*dst++ = here;
		use_env |= (here == '$');
	}
	*dst = '\0';
	if(here == '\0')			/* End of path list reached? */
		src = NULL;			/* Makes us stop next time. */
	comp_len = dst - component;

	if(use_env)				/* Do we need to look up environmet variables? */
		eval_env(component);
	if(component[0] == '~')			/* Do we need to look up a home directory? */
		eval_home(component);

	if(component[0] != '/')			/* Relative path? */
	{
		if(base_path[0] == '/')		/* Yes, absolute base available? */
		{
			if(comp_len + base_len + 2 < PATH_MAX)	/* Room to prepend it? */
			{
				memmove(component + base_len + 1, component, comp_len + 1);
				memcpy(component, base_path, base_len);
				component[base_len] = '/';
			}
		}
	}

	return dst > component;
}

/* ----------------------------------------------------------------------------------------- */

/* 1998-08-15 -	Attempt to locate file called <name> in any of the colon-separated locations
**		specified by <paths>. When found, returns a pointer to the complete name. If
**		nothing has been found when all paths have been tried, returns a sullen NULL.
**		Very useful for loading icons.
** 1998-08-23 -	Now uses the fut_path_component() function. Feels more robust.
** 1998-12-01 -	Now uses the fut_exists() call rather than a kludgy open().
** BUG BUG BUG	Calls to this function DO NOT nest very well, due to the static buffer pointer
**		being returned. Beware.
*/
gchar * fut_locate(const gchar *paths, const gchar *name)
{
	static gchar	buf[PATH_MAX];
	const gchar	*ptr = paths;

	while(fut_path_component(ptr, buf))
	{
		str_strncat(buf, "/",  sizeof buf);
		str_strncat(buf, name, sizeof buf);
		if(fut_exists(buf))
			return buf;
		ptr = NULL;
	}
	return NULL;
}

/* ----------------------------------------------------------------------------------------- */

/* Just free a path. */
static gboolean kill_path(gpointer key, gpointer data, gpointer user)
{
	g_free(key);

	return TRUE;
}

/* 1998-08-23 -	Scan through a list of colon-separated paths, and return a (possibly HUGE)
**		sorted list of all _files_ found. Very useful for selecting icon pixmaps... The
**		scan is non-recursive; we don't want to go nuts here.
** 1998-09-05 -	Now actually sorts the list, too. That bit had been mysteriously forgotten.
** 1998-12-23 -	Now ignores directories already visited. Uses a string comparison rather than a
**		file-system level check, so you can still fool it. It's a bit harder, though.
*/
GList * fut_scan_path(const gchar *paths, gint (*filter)(const gchar *path, const gchar *name), gboolean with_path)
{
	gchar		buf[PATH_MAX], *item;
	const gchar	*ptr = paths;
	DIR		*dir;
	struct dirent	*de;
	GList		*list = NULL;
	GHashTable	*set;
	GString		*temp;

	if((paths == NULL) || (filter == NULL))
		return NULL;

	if((temp = g_string_new("")) == NULL)
		return NULL;

	if((set = g_hash_table_new(g_str_hash, g_str_equal)) == NULL)
		return NULL;

	for(ptr = paths; fut_path_component(ptr, buf); ptr = NULL)
	{
		if(g_hash_table_lookup(set, buf))
			continue;
		g_hash_table_insert(set, g_strdup(buf), GINT_TO_POINTER(TRUE));
		if((dir = opendir(buf)) != NULL)
		{
			while((de = readdir(dir)) != NULL)
			{
				if(filter(buf, de->d_name))
				{
					if(with_path)
					{
						g_string_assign(temp, buf);
						g_string_append_c(temp, '/');
						g_string_append(temp, de->d_name);
						item = g_strdup(temp->str);
					}
					else
						item = g_strdup(de->d_name);
					if(item != NULL)
						list = g_list_insert_sorted(list, (gpointer) item, (GCompareFunc) strcmp);
				}
			}
			closedir(dir);
		}
	}
	g_hash_table_foreach_remove(set, kill_path, NULL);
	g_hash_table_destroy(set);
	g_string_free(temp, TRUE);
	return list;
}

/* 1998-08-23 -	Free the list created during a previous call to fut_scan_paths(). */
void fut_free_path(GList *list)
{
	GList	*head = list;

	if(list != NULL)
	{
		for(; list != NULL; list = g_list_next(list))
		{
			if(list->data != NULL)
				g_free(list->data);		/* Free the file name. */
		}
		g_list_free(head);		/* Free all list items. */
	}
}

/* ----------------------------------------------------------------------------------------- */

/* 1998-09-01 -	Copy <length> bytes from the file descriptor <in>, to the file descriptor <out>.
**		Returns number of bytes successfully written. Doesn't close any file descriptor
**		after the copy. Never uses more than <max_chunk> bytes, independently of <length>.
**		Beware when copying EMPTY files; you cannot use the return as a boolean!
** 1998-09-30 -	Now does progress reporting using the new 'progress' module. Quite neat.
*/
gsize fut_copy(gint in, gint out, gsize length, gsize max_chunk)
{
	gchar	*buf;
	gsize	put = 0, chunk;
	gsize	want;

	if(in < 0 || out < 0 || length < 1)
		return 0;

	if(max_chunk < MIN_COPY_CHUNK)
		max_chunk = MIN_COPY_CHUNK;

	chunk = (length > max_chunk) ? max_chunk : length;

	buf = g_malloc(chunk);
	while(length > 0)
	{
		want = (gsize) ((length > chunk) ? chunk : length);
		if(read(in, buf, want) == want)
		{
			if(write(out, buf, want) == want)
				put += want;
			else
				break;
		}
		else
			break;
		if(!pgs_progress_item_update(NULL, (guint32) put))
		{
			errno = EINTR;
			break;
		}
		length -= want;
	}
	g_free(buf);

	return put;
}

/* ----------------------------------------------------------------------------------------- */

/* 1998-09-07 -	Check if the user described by (<uid>,<gid>), which is the user currently running
**		gentoo, is allowed to READ from a file described by <stat>. Note that this is a
**		complex check.
** 1998-09-22 -	Moved this function (and friends) into the fileutil module.
*/
gboolean fut_can_read(struct stat *stat, uid_t uid, gid_t gid)
{
	if(uid == 0)			/* Root rules! */
		return TRUE;

	if(stat->st_uid == uid)		/* Current user's file? */
		return (stat->st_mode & S_IRUSR) ? TRUE : FALSE;
	if(stat->st_gid == gid)		/* User belongs to file's group? */
		return (stat->st_mode & S_IRGRP) ? TRUE : FALSE;

	/* User doesn't own file or belong to file owner's group. Check others. */
	return (stat->st_mode & S_IROTH) ? TRUE : FALSE;
}

/* 1998-09-23 -	Just a convenience interface for the above. Name must be absolute, or you
**		must be very lucky (the current dir while running gentoo is a highly
**		stochastic variable).
*/
gboolean fut_can_read_named(const gchar *name)
{
	struct stat	s;

	if(stat(name, &s) == 0)
		return fut_can_read(&s, geteuid(), getegid());
	return FALSE;
}

/* 1998-09-07 -	Do complex WRITE permission check. */
gboolean fut_can_write(struct stat *stat, uid_t uid, gid_t gid)
{
	if(uid == 0)
		return TRUE;

	if(stat->st_uid == uid)
		return (stat->st_mode & S_IWUSR) ? TRUE : FALSE;
	if(stat->st_gid == gid)
		return (stat->st_mode & S_IWGRP) ? TRUE : FALSE;

	return (stat->st_mode & S_IWOTH) ? TRUE : FALSE;
}

/* 1998-09-07 -	Do complex EXECUTE permission check. */
gboolean fut_can_execute(struct stat *stat, uid_t uid, gid_t gid)
{
	if(stat->st_uid == uid)
		return (stat->st_mode & S_IXUSR) ? TRUE : FALSE;
	if(stat->st_gid == gid)
		return (stat->st_mode & S_IXGRP) ? TRUE : FALSE;

	return (stat->st_mode & S_IXOTH) ? TRUE : FALSE;
}

/* ----------------------------------------------------------------------------------------- */

/* 1999-05-29 -	Check if there is a file named <name> in <path>. If such a file exists, check
**		if the current user can execute it. This is handy as a prelude to actually doing
**		a fork()/exec() pair on <name>, since it allows simpler error handling. If execution
**		is not possible, set errno to something suitable and return FALSE. If everything
**		looks fins (not a guarantee exec() will succeed), clear errno and return TRUE.
*/
gboolean fut_locate_executable(const gchar *path, const gchar *name)
{
	errno = EINVAL;		/* Assume the worst. */

	if((path != NULL) && (name != NULL))
	{
		gchar	*pname;

		if((pname = fut_locate(path, name)) != NULL)
		{
			if(access(pname, R_OK | X_OK) == 0)
				return TRUE;
		}
		else
			errno = ENOENT;
	}
	return FALSE;
}

/* ----------------------------------------------------------------------------------------- */

/* 1998-10-26 -	Check if the user doesn't want to see <name>. Returns TRUE if the file is
**		NOT hidden, FALSE if it is. If <name> is NULL, we free any allocated resources
**		and then return TRUE. This is very useful when the RE source is changing.
** 1998-12-14 -	The check is now done with respect to pane <dp>. This has relevance, since
**		hiding can now be enabled/disabled on a per-pane basis. If <name> is NULL,
**		any pane can be used (but it must not be NULL!).
*/
gboolean fut_check_hide(DirPane *dp, const gchar *name)
{
	MainInfo	*min = dp->main;
	HideInfo	*hi = &min->cfg.path.hideinfo;
	guint		cflags = REG_EXTENDED | REG_NOSUB;

	if(name == NULL)	/* The NULL-name special case is pane-independant. */
	{
		if(hi->hide_re != NULL)
		{
			free(hi->hide_re);
			hi->hide_re = NULL;
		}
		return TRUE;
	}
	if(!(min->cfg.dp_format[dp->index].hide_allowed))	/* Hiding disabled? */
		return TRUE;
	switch(hi->mode)
	{
		case HIDE_NONE:
			return TRUE;
		case HIDE_DOT:
			return name[0] != '.';
		case HIDE_REGEXP:
			if(hi->hide_re == NULL)			/* RE not compiled yet? */
			{
				if((hi->hide_re = malloc(sizeof *hi->hide_re)) != NULL)
				{
					if(hi->no_case)
						cflags |= REG_ICASE;
					if(regcomp(hi->hide_re, hi->hide_re_src, cflags) != 0)
					{
						free(hi->hide_re);
						hi->hide_re = NULL;
					}
				}
			}
			if(hi->hide_re != NULL)
				return regexec(hi->hide_re, name, 0, NULL, 0) == REG_NOMATCH;
			break;
	}
	return TRUE;					/* When in doubt, hide nothing. */
}

/* ----------------------------------------------------------------------------------------- */

/* 1998-08-30 -	Initialize the file utility module. Grabs the current working directory for
**		use as the global 'base' for paths. Run this early!
*/
void fut_initialize(void)
{
	if(getcwd(base_path, sizeof base_path) == NULL)
	{
		fprintf(stderr, "FILEUTIL: Couldn't get current working dir - defaulting to /\n");
		str_strncpy(base_path, "/", sizeof base_path);
	}
	base_len = strlen(base_path);
}
