#ifndef _USE_BSD
/* For wait3() */
# define _USE_BSD
#endif
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <ctype.h>
#include <errno.h>
#include <signal.h>
#include <sched.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fnmatch.h>
#include <glib.h>
#include <unistd.h>

#include "../include/string.h"
#include "../include/fio.h"
#include "../include/disk.h"

#include "edv_utils.h"
#include "config.h"


extern char **environ;

const gchar *EDVSizeStrDelim(gulong i);
gchar *EDVStrSub(
	const gchar *fmt, const gchar *token, const gchar *val  
);

void EDVSimplifyPath(gchar *path);
gboolean EDVIsParentPath(const gchar *parent, const gchar *child);
static gboolean EDVIsExtensionIterate(
	const gchar *name, const gint name_len,
	const gchar *ext, const gint ext_len
);
gboolean EDVIsExtension(const gchar *name, const gchar *ext);
gchar *EDVShortenPath(const gchar *path, gint max);
gchar *EDVEvaluatePath(const gchar *parent, const gchar *path);
gchar *EDVGetCWD(void);
gint EDVSetCWD(const gchar *path);
guint EDVGetUMask(void);
void EDVSetUMask(const guint m);
gchar *EDVTmpName(const gchar *dir);
gchar *EDVWhich(const gchar *name);
gchar *EDVGetLinkValue(const gchar *path);
gchar *EDVGetLinkValueFull(const gchar *path);
gboolean EDVIsLinkInfinatelyRecursive(const gchar *path);

void EDVGetDeviceNumbers(gint rdev, gint *major, gint *minor);
gint EDVFormatDeviceNumbers(const gint major, const gint minor);

gboolean EDVProcessIsRunning(const gint pid);

static void EDVSystemWaitCB(gint s);
static gpointer EDVSystemAddSignal(gint signum, void (*handler)(gint));
gint EDVSystem(const gchar *cmd);
gint EDVSystemBlock(const gchar *cmd, gint *status);

GList *EDVOpenFileGList(const gchar *path, gint max_lines);
void EDVSaveFileGList(const gchar *path, const GList *glist);


#define ATOI(s)         (((s) != NULL) ? atoi(s) : 0)
#define ATOL(s)         (((s) != NULL) ? atol(s) : 0)
#define ATOF(s)         (((s) != NULL) ? atof(s) : 0.0f)
#define STRDUP(s)       (((s) != NULL) ? g_strdup(s) : NULL)

#define MAX(a,b)        (((a) > (b)) ? (a) : (b))
#define MIN(a,b)        (((a) < (b)) ? (a) : (b))
#define CLIP(a,l,h)     (MIN(MAX((a),(l)),(h)))
#define STRLEN(s)       (((s) != NULL) ? strlen(s) : 0)
#define STRISEMPTY(s)   (((s) != NULL) ? (*(s) == '\0') : TRUE)


static gchar *G_STRCAT(gchar *s, const gchar *s2)
{
	if(s != NULL) {
	    if(s2 != NULL) {
		gchar *sr = g_strconcat(s, s2, NULL);
		g_free(s);
		s = sr;
	    }
	} else {
	    if(s2 != NULL)
		s = STRDUP(s2);
	    else
		s = STRDUP("");
	}
	return(s);
}


/*
 *	Similar to EDVGetObjectSizeStr() except that the string is
 *	always deliminated.
 */
const gchar *EDVSizeStrDelim(gulong i)
{
	gint comma_countdown, slen;
	gchar ss[80], *ss_ptr;
	static gchar ts[80], *ts_ptr;

	g_snprintf(ss, sizeof(ss), "%ld", i);
	slen = STRLEN(ss);

	/* 3 digits or less? (no commas needed) */
	if(slen <= 3)
	{
	    strcpy(ts, ss);
	    return(ts);
	}

	ts_ptr = ts;
	ss_ptr = ss;

	/* Initialize comma counter */
	comma_countdown = slen % 3;
	if(comma_countdown <= 0)
	    comma_countdown = 3;

	/* Iterate through size string until end is reached */
	while(*ss_ptr != '\0')
	{
	    /* Reset comma counter and put in a comma? */
	    if(comma_countdown <= 0)
	    {
		*ts_ptr++ = ',';
		comma_countdown = 3;
	    }

	    *ts_ptr++ = *ss_ptr++;
	    comma_countdown--;
	}

	/* Null terminate return string */
	*ts_ptr = '\0';

	return(ts);
}


/*
 *	Returns a dynamically allocated string with all occurances of
 *	the string token replaced with the string val in the string fmt.
 */
gchar *EDVStrSub(
	const gchar *fmt, const gchar *token, const gchar *val  
)
{
	return(strsub(fmt, token, val));
}


/*
 *	Simplifies the path by removing any tailing path deliminators
 *	reducing any occurances of "/..".
 */
void EDVSimplifyPath(gchar *path)
{
	gboolean is_absolute;

	if(STRISEMPTY(path))
	    return;

	is_absolute = g_path_is_absolute(path);

	/* Remove tailing deliminator characters if any */
	StripPath(path);

	/* Reduce all occurances of "/.." in the path */
	SimplifyPath(path);

	/* If the path was originally an absolute path and now its
	 * become an empty string then set path as toplevel
	 */
	if(is_absolute && (*path == '\0'))
	    strcpy(path, "/");
}

/*
 *	Checks if the specified parent path is a parent or grand parent
 *	of the specified child path.
 *
 *	Both paths must be absolute and simplified.
 */
gboolean EDVIsParentPath(const gchar *parent, const gchar *child)
{
	gboolean status;
	gint len;
	gchar	*lparent,
		*lchild,
		delim_str[2];

#define DO_FREE_LOCALS	{	\
 g_free(lparent);		\
 lparent = NULL;		\
 g_free(lchild);		\
 lchild = NULL;			\
}
	
	if(STRISEMPTY(parent) || STRISEMPTY(child))
	    return(FALSE);

	/* Both paths must be absolute */
	if(!g_path_is_absolute(parent) || !g_path_is_absolute(child))
	    return(FALSE);

	/* Make coppies of the parent and child paths */
	lparent = STRDUP(parent);
	lchild = STRDUP(child);

	/* Need to tack on a tailing G_DIR_SEPARATOR character to the
	 * parent path for proper prefix matching below
	 */
	delim_str[0] = G_DIR_SEPARATOR;
	delim_str[1] = '\0';

	len = STRLEN(lparent);
	if(len > 0)
	{
	    if(lparent[len - 1] != G_DIR_SEPARATOR)
		lparent = G_STRCAT(lparent, delim_str);
	    if(lparent == NULL)
	    {
		DO_FREE_LOCALS
		return(FALSE);
	    }
	}

	len = STRLEN(lchild);
	if(len > 0)
	{
	    if(lchild[len - 1] != G_DIR_SEPARATOR)
		lchild = G_STRCAT(lchild, delim_str);
	    if(lchild == NULL)
	    {
		DO_FREE_LOCALS
		return(FALSE);
	    }
	}

	/* Check if the lparent is a prefix of lchild, if it is then
	 * it suggest that it is a parent or grand parent of the child
	 * path
	 */
	if(strpfx(lchild, lparent))
	    status = TRUE;
	else
	    status = FALSE;

	DO_FREE_LOCALS
	return(status);
#undef DO_FREE_LOCALS
}

/*
 *	Called by EDVIsExtension() to check if ext matches the path.
 *
 *	The name specifies the name without the path and may not be
 *	NULL.
 *
 *	The ext expecifies one extension (no spaces) with wildcards
 *	allowed and may not be NULL.
 */
static gboolean EDVIsExtensionIterate(
	const gchar *name, const gint name_len,
	const gchar *ext, const gint ext_len
)
{
	/* Extension starts with a '.' deliminator? */
	if(*ext == '.')
	{
	    const gchar *name_ext = name + (name_len - ext_len);

	    /* Check if ext is a suffix of name */
	    if(name_len < ext_len)
		return(FALSE);

	    return((g_strcasecmp(name_ext, ext)) ? FALSE : TRUE);
	}
	/* Not an extension, so use fnmatch() */
	else
	{
	    return((fnmatch(ext, name, 0) == 0) ? TRUE : FALSE);
	}
}

/*
 *	Checks if the name's extension matches one in the extensions
 *	list.
 *
 *	The name specifies the name without the path portion, for
 *	example:
 *
 *		"file.txt"
 *
 *	The ext specifies the extensions list, a space-separated list
 *	of extensions with wilcards allowed, for example:
 *
 *		".txt .doc *rc Makefile*"
 *
 */
gboolean EDVIsExtension(const gchar *name, const gchar *ext)
{
	const gchar *ss, *ext_ptr;
	gint name_len, ext_len;
	gchar *st;
	gchar cur_ext[NAME_MAX];

	if(STRISEMPTY(name) || STRISEMPTY(ext))
	    return(FALSE);

	name_len = STRLEN(name);

	/* Set ext_ptr to start of extensions list string, seeking past
	 * any initial spaces
	 */
	ext_ptr = ext;
	while(ISBLANK(*ext_ptr))
	    ext_ptr++;

	/* Iterate through each word in the extensions list */
	while(*ext_ptr != '\0')
	{
	    /* Copy this word in the extensions list string to
	     * cur_ext and calculate this extension's length as
	     * ext_len
	     */
	    for(ext_len = 0,
		st = cur_ext,
		ss = ext_ptr;
		(ext_len < (gint)(sizeof(cur_ext) - 1)) &&
		!ISBLANK(*ss) && (*ss != '\0');
	        ext_len++
	    )
		*st++ = *ss++;
	    *st = '\0';

	    /* Check this extension word matches */
	    if(EDVIsExtensionIterate(name, name_len, cur_ext, ext_len))
		return(TRUE);

	    /* At this point ss should be at the end of the word (at
	     * the first space) or at the end of the extensions list
	     * string
	     */
	    ext_ptr = ss;
	    while(ISBLANK(*ext_ptr))
		ext_ptr++;
	}

	return(FALSE);
}

/*
 *	Returns a dynamically allocated copy of the path that is no
 *	longer than the specified max characters.
 *
 *	If the specified path is longer than max then the shortened
 *	return string will contain a "..." prefix.
 */
gchar *EDVShortenPath(const gchar *path, gint max)
{
	gint len;

	if(path == NULL)
	    return(NULL);

	len = STRLEN(path);
	if((len > max) && (max > 3))
	{
	    /* Need to shorten string */
	    gint i = len - max + 3;

	    return(g_strdup_printf(
		"...%s", &path[i]
	    ));
	}
	else
	{
	    return(STRDUP(path));   
	}
}

/*
 *	Returns a dynamically allocated evaluated path based on the
 *	specified path.
 *
 *	The parent path should be an absolute path indicating the parent
 *	or current path. The parent path is only used if the specified
 *	path is not an absolute path. If the parent path is NULL then
 *	the toplevel directory will be used as the parent path.
 *
 *	The path will be evaulated as follows:
 *
 *	Check for home directory "~" prefix and substitute home
 *	directory needed.
 *
 *	Simplified, all occurances of ".." evaluated.
 *
 *	Tailing directory deliminators removed.
 */
gchar *EDVEvaluatePath(const gchar *parent, const gchar *path)
{
	gchar *eval_path;

	if(path == NULL)
	{
	    errno = EINVAL;
	    return(NULL);
	}

	if(parent == NULL)
#if defined(_WIN32)
	    parent = "\\";
#else
	    parent = "/";
#endif

	/* Current directory? */
	if(!strcmp(path, "."))
	{
	    eval_path = STRDUP(parent);
	}
	/* Parent directory? */
	else if(!strcmp(path, ".."))
	{
	    eval_path = g_dirname(parent);
	}
	/* Check for special prefix substitutions for the input path */
	else if(strcasepfx(path, "~"))
	{
	    /* Prefix the value from the HOME environment variable to
	     * the input path and generate the new path
	     */
	    const gchar	*home = g_getenv(ENV_VAR_NAME_HOME),
			*s = path + STRLEN("~");

	    /* If home directory environment variable value was not set
	     * then assume toplevel
	     */
	    if(home == NULL)
#if defined(_WIN32)
		home = "\\";
#else
		home = "/";
#endif

	    /* Seek s after the prefix and past any deliminator
	     * characters
	     */
	    while(*s == G_DIR_SEPARATOR)
		s++;

	    /* Create the evaluated absolute path with the home dir
	     * prefixed
	     */
	    eval_path = STRDUP(PrefixPaths(home, s));
	}
	else
	{
	    /* Create the evaluated absolute path */
	    eval_path = g_path_is_absolute(path) ?
		STRDUP(path) :
		STRDUP(PrefixPaths(parent, path));
	}

	/* Begin simplifying the generated path */

	/* Simplify path, reducing occurances of "../" */
	SimplifyPath(eval_path);

	/* Remove tailing deliminators */
	StripPath(eval_path);

	return(eval_path);
}

/*
 *	Returns a dynamically allocated string describing the current
 *	working directory.
 */
gchar *EDVGetCWD(void)
{
	return(STRDUP(g_get_current_dir()));
}

/*
 *	Sets the current working directory.
 *
 *	Returns 0 on success or non-zero on error.
 */
gint EDVSetCWD(const gchar *path)
{
	if(STRISEMPTY(path))
	{
	    errno = EINVAL;
	    return(-2);
	}

	return(chdir(path));
}

/*
 *	Returns the current umask.
 */
guint EDVGetUMask(void)
{
	const guint m = (guint)umask(0);
	umask((mode_t)m);
	return(m);
}

/*
 *	Sets the umask.
 */
void EDVSetUMask(const guint m)
{
	umask((mode_t)m);
}

/*
 *	Returns a dynamically allocated string describing a unique file
 *	name in the specified dir that is gauranteed to not exist.
 */
gchar *EDVTmpName(const gchar *dir)
{
	gint fd;
	gchar *path;

	if(STRISEMPTY(dir))
	    dir = g_getenv(ENV_VAR_NAME_TMPDIR);
	if(STRISEMPTY(dir))
#if defined(P_tmpdir)
	    dir = P_tmpdir;
#elif defined(_WIN32)
	    dir = "C:\\TEMP";
#else
	    dir = "/tmp";
#endif

	/* Create template path */
	path = STRDUP(PrefixPaths(
	    dir,
	    PROG_NAME "XXXXXX"
	));
	if(path == NULL)
	{
	    errno = ENOMEM;
	    return(NULL);
	}

	/* Create tempory file and modify the template path */
	fd = (gint)mkstemp((char *)path);
	if(fd > -1)
	    close((int)fd);

	return(path);
}

/*
 *	Returns a dynamically allocated string path describing the
 *	completed absolute path to the program found from the PATH
 *	environment variable for the program specified name or NULL if
 *	there is no match.
 */
gchar *EDVWhich(const gchar *name)
{
	gint i;
	const gchar *path_list;
	gchar *s, *matched_path, **pathv;

	if(STRISEMPTY(name))
	    return(NULL);

	/* Specified name already has an absolute path to it? */
	if(g_path_is_absolute(name))
	    return(STRDUP(name));

	/* Get the value of the path environment */
	path_list = g_getenv(ENV_VAR_NAME_PATH);
	if(path_list == NULL)
	    return(NULL);

	/* Break up the path environment into individual paths */
	pathv = g_strsplit(path_list, G_SEARCHPATH_SEPARATOR_S, -1);
	if(pathv == NULL)
	    return(NULL);

	/* Check each individual path location for the specified name */
	matched_path = NULL;
	for(i = 0; pathv[i] != NULL; i++);
	for(i--; i >= 0; i--)
	{
	    s = STRDUP(PrefixPaths(
		(const char *)pathv[i], (const char *)name
	    ));
	    if(s == NULL)
		continue;

	    if(!access((const char *)s, X_OK))
	    {
		matched_path = s;
		break;
	    }

	    g_free(s);
	}

	g_strfreev(pathv);

	return(matched_path);
}

/*
 *	Gets the link's destination value.
 *
 *	The path specifies the full path to the link.
 *
 *	Returns a dynamically allocated string containing the link's
 *	destination value or NULL on error. The global errno will be        
 *	set if an error has occured.
 */                  
gchar *EDVGetLinkValue(const gchar *path)
{
	struct stat lstat_buf;
	gint len, bytes_read;
	gchar *v;

	if(STRISEMPTY(path))
	{
	    errno = EINVAL;
	    return(NULL);
	}

	if(lstat(path, &lstat_buf))
	    return(NULL);

#ifdef S_ISLNK
	if(!S_ISLNK(lstat_buf.st_mode))
#else
	if(TRUE)
#endif
	{
	    errno = EINVAL;
	    return(NULL);
	}

	len = (gint)lstat_buf.st_size;
	v = (gchar *)g_malloc((len + 1) * sizeof(gchar));
	if(v == NULL)
	{
	    errno = ENOMEM;
	    return(NULL);
	}

	if(len > 0)
	{
	    bytes_read = readlink(
		(const char *)path,		/* Link */
		v,				/* Value return */
		(size_t)len			/* Value allocation */
	    );
	    if(bytes_read != len)
	    {
		g_free(v);
		return(NULL);
	    }
	}

	v[len] = '\0';

	return(v);
}

/*
 *	Same as EDVGetLinkValue() except that it appends the parent
 *	of the path to the link's destination value if the link's
 *	destination value is a relative path.
 */
gchar *EDVGetLinkValueFull(const gchar *path)
{
	gchar	*parent,
		*full_path,
		*v = EDVGetLinkValue(path);
	if(v == NULL)
	    return(NULL);

	if(g_path_is_absolute(v))
	    return(v);

	parent = g_dirname(path);
	if(parent == NULL)
	    return(v); 

	full_path = g_strconcat(
	    parent,
	    G_DIR_SEPARATOR_S,
	    v,
	    NULL
	);

	g_free(parent);
	g_free(v);

	SimplifyPath(full_path);

	return(full_path);
}

/*
 *	Checks if the link specified by path has a destination that
 *	may possibly be infinately recursive.
 */
gboolean EDVIsLinkInfinatelyRecursive(const gchar *path)
{
	struct stat stat_buf;
	gboolean status = FALSE;
	gint len;
	gchar *s, *parent, *cur_path;
	gchar link_value[PATH_MAX + NAME_MAX + 1];

	if(STRISEMPTY(path))
	    return(status);

	/* Is the link's destination not valid? */
	if(stat(path, &stat_buf))
	    return(status);

#ifdef S_ISDIR
	/* Is the link's destination not a directory? */
	if(!S_ISDIR(stat_buf.st_mode))
	    return(status);
#endif

	/* Record the parent of the starting link */
	parent = g_dirname(path);
	if(parent == NULL)
	    return(status);

	/* Begin going through each link to reach the destination */
	cur_path = STRDUP(path);
	while(cur_path != NULL)
	{
	    /* Get the link's destination value, if we get an error it
	     * means we have reached the end and cur_path is the final
	     * destination
	     */
	    len = (gint)readlink(cur_path, link_value, sizeof(link_value));
	    if(len <= 0)
		break;
	    if(len >= (gint)sizeof(link_value))
		len = MAX((gint)(sizeof(link_value) - 1), 0);
	    link_value[len] = '\0';

	    /* Get this link's parent */
	    s = g_dirname(cur_path);
	    if(s == NULL)
		break;

	    /* Get the link's destination as the current path */
	    g_free(cur_path);
	    cur_path = EDVEvaluatePath(s, link_value);
	    g_free(s);
	}

	if(cur_path != NULL)
	{
	    /* Is the destination a grand parent of the starting link? */
	    if(strpfx(parent, cur_path))
		status = TRUE;
	}

	g_free(cur_path);
	g_free(parent);

	return(status);
}


/*
 *	Returns the device major and minor numbers parsed from the
 *	specified (dev_t)rdev value (obtained from stat()).
 */
void EDVGetDeviceNumbers(gint rdev, gint *major, gint *minor)
{
	if(major != NULL)
	    *major = (gint)((rdev >> 8) & 0xff);
	if(minor != NULL)
	    *minor = (gint)((rdev >> 0) & 0xff);
}

/*
 *	Returns the (dev_t)rdev value (obtained from stat()) parsed
 *	from the specified device major and minor numbers.
 */
gint EDVFormatDeviceNumbers(const gint major, const gint minor)
{
	return((gint)(
	    ((dev_t)major << 8) | ((dev_t)minor << 0)
	));
}


/*
 *	Checks if the process speecified by pid is still running.
 */
gboolean EDVProcessIsRunning(const gint pid)
{
#if defined(__linux__)
	gchar path[PATH_MAX + NAME_MAX];
	g_snprintf(path, sizeof(path), "/proc/%i", pid);
	return(access(path, F_OK) ? FALSE : TRUE);
#else
	struct sched_param sp;

	if(pid <= 0)
	{
	    errno = EINVAL;
	    return(FALSE);
	}

	if(sched_getparam(
	    (pid_t)pid,
	    &sp
	) == 0)
	    return(TRUE);
	else
	    return(FALSE);
#endif   
}


/*
 *	SIGCHLD signal callback.
 *
 *	Used by EDVSystem*().
 */
static void EDVSystemWaitCB(gint s)
{
	gint status;
	while(wait3((int *)&status, WNOHANG, NULL) > 0);
}

/*
 *	Adds a signal using the BSD style signal(2).
 *
 *	Returns the old signal action handler of type
 *	void (*handler)(gint).
 */
static gpointer EDVSystemAddSignal(gint signum, void (*handler)(gint))
{
	struct sigaction act, old_act;

	act.sa_handler = handler;
	act.sa_flags = 0;

	if(sigaction((int)signum, &act, &old_act) == -1)
	    return((gpointer)SIG_ERR);
	else
	    return((gpointer)old_act.sa_handler);
}

/*
 *	Executes the command specified by cmd in the background.
 *
 *	Returns the process ID or -1 on error.
 */
gint EDVSystem(const gchar *cmd)
{
	gint pid;

	if(STRISEMPTY(cmd))
	    return(-1);

#ifdef SIGCHLD
	EDVSystemAddSignal(SIGCHLD, EDVSystemWaitCB);
#endif

	/* Fork */
	pid = (gint)fork();
	if(pid == -1)
	    return(-1);

	/* Are we the child? */
	if(pid == 0)
	{
	    /* We are the child, execute command */
	    gchar *argv[4];
	    argv[0] = "sh";
	    argv[1] = "-c";
	    argv[2] = (gchar *)cmd;
	    argv[3] = 0;
	    execve("/bin/sh", (char **)argv, environ);
	    exit(0);
	}
	else
	{
	    /* We are the parent */
	}

	return(pid);
}

/*
 *	Executes the command specified by cmd and blocks until it has
 *	exited.
 *
 *	If status is not NULL then the status of the command
 *	be returned.
 *
 *	Returns the process ID or -1 on error.
 */
gint EDVSystemBlock(const gchar *cmd, gint *status)
{
	gint pid;

	if(STRISEMPTY(cmd))
	    return(-1);

	/* Fork */
	pid = (gint)fork();
	if(pid == -1)
	    return(-1);

	/* Are we the child? */
	if(pid == 0)
	{
	    /* We are the child, execute command */
	    gchar *argv[4];
	    argv[0] = "sh";
	    argv[1] = "-c";
	    argv[2] = (gchar *)cmd;
	    argv[3] = 0;
	    execve("/bin/sh", (char **)argv, environ);
	    exit(0);
	}
	else
	{
	    /* We are the parent, wait for child to exit */
	    const gint wait_status = (gint)waitpid(
		(int)pid, (int *)status, 0
	    );
	    if(wait_status == -1)
	    {
		/* Interrupted? */
		if(errno == EINTR)
		{

		}
	    }
	}

	return(pid);
}


/*
 *	Opens the file specified by path and returns its contents as
 *	a list of dynamically allocated strings in a GList.
 *
 *	If max_lines is positive then no more than max_lines will be
 *	read from the file.
 */
GList *EDVOpenFileGList(const gchar *path, gint max_lines)
{
	gint lines_read;
	gchar *s;
	GList *glist;

	/* Open file for reading */
	FILE *fp = FOpen((const char *)path, "rb");
	if(fp == NULL)
	    return(NULL);

	/* Read each line of the file and store them to a GList */
	lines_read = 0;
	glist = NULL;
	s = (gchar *)FGetStringLiteral(fp);
	while(s != NULL)
	{
	    lines_read++;

	    /* Append line to GList and read next line (if any) */
	    glist = g_list_append(glist, s);
	    s = (gchar *)FGetStringLiteral(fp);

	    /* Is max_lines specified and number of lines read has
	     * reached max_lines?
	     */
	    if((max_lines > 0) && (lines_read >= max_lines))
	    {
		g_free(s);
		break;
	    }
	}

	/* Close file */
	FClose(fp);

	return(glist);
}

/*
 *	Writes each string in the specified GList to the file
 *	specified by path.
 */
void EDVSaveFileGList(const gchar *path, const GList *glist)
{
	gint lines_written;
	const gchar *s;
	/* Open file for writing */
	FILE *fp = FOpen((const char *)path, "wb");
	if(fp == NULL)
	    return;

	/* Write each line in the GList */
	lines_written = 0;
	while(glist != NULL)
	{
	    /* Get current line */
	    s = (gchar *)glist->data;
	    if(s != NULL)
	    {
		/* Write line */
		fwrite(s, sizeof(gchar), STRLEN(s), fp);
		fputc('\n', fp);	/* Add newline character */
		lines_written++;
	    }
	    glist = g_list_next(glist);
	}


	/* Close file */
	FClose(fp);
}

