#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <sys/types.h>
#include <time.h>
#include <sys/stat.h>
#if defined(HAVE_REGEX)
# include <regex.h>
#else
# include <fnmatch.h>
#endif
#ifdef HAVE_LIBXAR
# include <xar/xar.h>
#endif
#ifdef HAVE_LIBZIP
# include <zip.h>
#endif
#include <glib.h>
#include <unistd.h>

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

#include "cfg.h"
#include "edv_types.h"
#include "edv_date.h"
#include "edv_archive_obj.h"
#include "endeavour2.h"
#include "edv_archive_stat.h"
#include "edv_utils.h"
#include "edv_cfg_list.h"
#include "config.h"


/*
 *	Return values legend:
 *
 *	0	Success.
 *	-1	General error.
 *	-2	Invalid value.
 *	-3	Systems error; out of memory or out of disk space.
 *	-4	User responded with "Cancel".
 *	-5	User responded with "No" or response was not available.
 *	-6	Call would cause reentry.
 */


static gchar *G_STRCAT(gchar *s, const gchar *s2);

const gchar *EDVArchStatGetError(void);
static void EDVArchStatCopyErrorMessage(const gchar *msg);

static void EDVArchStatCapNewline(gchar *s);
#if 0
static void EDVArchStatCapSpace(gchar *s);
#endif
static gboolean EDVArchStatFilter(
	GList *paths_list,
#if defined(HAVE_REGEX)
	regex_t *regex_filter,
#else
	const gchar *filter,
#endif
	const gchar *path
);

/* Line Parsing */
static void EDVArchStatParseLineARJ(
	edv_archive_object_struct *obj, const gchar *buf
);
static void EDVArchStatParseLineLHA(
	edv_archive_object_struct *obj, const gchar *buf,
	const struct tm *cur_mtime_buf
);
static void EDVArchStatParseLineRAR(
	edv_archive_object_struct *obj, const gchar *buf
);
static void EDVArchStatParseLineRPM(
	edv_archive_object_struct *obj, const gchar *buf
);
static void EDVArchStatParseLineTar(
	edv_archive_object_struct *obj, const gchar *buf
);
#ifndef HAVE_LIBZIP
static void EDVArchStatParseLineZip(
	edv_archive_object_struct *obj, const gchar *buf
);
#endif

/* Get Stats For A Object In Archive */
static edv_archive_object_struct *EDVArchStatARJ(
	edv_core_struct *core,
	const gchar *arch_obj, const gchar *path
);
static edv_archive_object_struct *EDVArchStatLHA(
	edv_core_struct *core,
	const gchar *arch_obj, const gchar *path
);
static edv_archive_object_struct *EDVArchStatRAR(
	edv_core_struct *core,
	const gchar *arch_obj, const gchar *path
);
static edv_archive_object_struct *EDVArchStatRPM(
	edv_core_struct *core,
	const gchar *arch_obj, const gchar *path
);
static edv_archive_object_struct *EDVArchStatTar(
	edv_core_struct *core,
	const gchar *arch_obj, const gchar *path,
	gboolean is_compress_compressed,
	gboolean is_gzip_compressed,
	gboolean is_bzip2_compressed
);
static edv_archive_object_struct *EDVArchStatZip(
	edv_core_struct *core,
	const gchar *arch_obj,
	const gchar *path
);
edv_archive_object_struct *EDVArchStat(
	edv_core_struct *core,
	const gchar *arch_obj,
	const gchar *path,
	const gchar *password
);

/* Get Stats For All Objects Found In The Archive */
#if defined(HAVE_REGEX)
#define EDV_ARCH_STAT_LIST_ANY_PROTOTYPE	\
	edv_core_struct *core,			\
	const gchar *arch_obj,			\
	GList *paths_list,			\
	regex_t *regex_filter,			\
	gint (*progress_cb)(			\
		const gchar *,			\
		edv_archive_object_struct *,	\
		const gulong, const gulong,	\
		gpointer			\
	),					\
	gpointer progress_data
#else
#define EDV_ARCH_STAT_LIST_ANY_PROTOTYPE	\
	edv_core_struct *core,			\
	const gchar *arch_obj,			\
	GList *paths_list,			\
	const gchar *filter,			\
	gint (*progress_cb)(			\
		const gchar *,			\
		edv_archive_object_struct *,	\
		const gulong, const gulong,	\
		gpointer			\
	),					\
	gpointer progress_data
#endif
static GList *EDVArchStatListARJ(EDV_ARCH_STAT_LIST_ANY_PROTOTYPE);
static GList *EDVArchStatListLHA(EDV_ARCH_STAT_LIST_ANY_PROTOTYPE);
static GList *EDVArchStatListRAR(EDV_ARCH_STAT_LIST_ANY_PROTOTYPE);
static GList *EDVArchStatListRPM(EDV_ARCH_STAT_LIST_ANY_PROTOTYPE);
static GList *EDVArchStatListTar(
	EDV_ARCH_STAT_LIST_ANY_PROTOTYPE,
	const gboolean is_compress_compressed,
	const gboolean is_gzip_compressed,
	const gboolean is_bzip2_compressed
);
static GList *EDVArchStatListXAr(EDV_ARCH_STAT_LIST_ANY_PROTOTYPE);
static GList *EDVArchStatListZip(EDV_ARCH_STAT_LIST_ANY_PROTOTYPE);
GList *EDVArchStatList(
	edv_core_struct *core,
	const gchar *arch_obj,
	GList *paths_list,
	const gchar *filter,
	const gchar *password,
	gint (*progress_cb)(
		const gchar *,
		edv_archive_object_struct *,
		const gulong, const gulong,
		gpointer
	),
	gpointer progress_data
);


static const gchar *last_error = NULL;
static gchar last_error_buf[80];


#define ISCR(c)		(((c) == '\n') || ((c) == '\r'))

#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)

#define UNLINK(p)	(((p) != NULL) ? (gint)unlink((const char *)(p)) : -1)


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);
}


/*
 *	Returns the last error message as a statically allocated
 *	string or NULL if there was no previous error.
 */
const gchar *EDVArchStatGetError(void)
{
	return(last_error);
}

/*
 *	Coppies the error message specified by msg to the last error
 *	message buffer and sets last_error to point to that buffer.
 */
static void EDVArchStatCopyErrorMessage(const gchar *msg)
{
	if(msg == NULL)
	    return;

	strncpy(last_error_buf, msg, sizeof(last_error_buf));
	last_error_buf[sizeof(last_error_buf) - 1] = '\0';
	last_error = last_error_buf;
}


/*
 *	Replaces the first newline character with a null byte.
 */
static void EDVArchStatCapNewline(gchar *s)
{
	while(*s != '\0')
	{
	    if(ISCR(*s))
	    {
		*s = '\0';
		return;
	    }
	    s++;
	}
}

#if 0
/*
 *	Replaces the first space character with a null byte.
 */
static void EDVArchStatCapSpace(gchar *s)
{
	while(*s != '\0')
	{
	    if(ISSPACE(*s))
	    {
		*s = '\0';
		return;
	    }
	    s++;
	}
}
#endif

/*
 *	Checks if the path passes the filter test.
 *
 *	The paths_list specifies the list of object paths. If the
 *	specified path matches a path in paths_list then TRUE
 *	will be returned. If paths_list is NULL then filter will
 *	be used instead.
 *
 *	The regex_filter or filter specifies the filter. If HAVE_REGEX
 *	is defined then it is the regex_filter and otherwise it is
 *	the filter. In either case, this is only used if paths_list is
 *	NULL.
 *
 *	The path specifies the path. If paths_list was NULL then only
 *	the name portion of the path will be used when comparing it
 *	to the filter.
 *
 *	Returns TRUE if the specified path passes the filter test.
 */
static gboolean EDVArchStatFilter(
	GList *paths_list,
#if defined(HAVE_REGEX)
	regex_t *regex_filter,
#else
	const gchar *filter,
#endif
	const gchar *path
)
{
	/* If no path is specified then always return FALSE */
	if(STRISEMPTY(path))
	    return(FALSE);

	/* Is the paths list specified? */
	if(paths_list != NULL)
	{
	    const gchar *fpath;
	    GList *glist;

	    /* Iterate through the list of paths */
	    for(glist = paths_list; glist != NULL; glist = g_list_next(glist))
	    {
		fpath = (const gchar *)glist->data;
		if(STRISEMPTY(fpath))
		    continue;

		/* Paths match? (case sensitive) */
		if(!strcmp((const char *)fpath, (const char *)path))
		    return(TRUE);
	    }
	}
	else
	{
	    /* No paths list was specified, use the filter instead */
	    const gchar *name;

	    /* If the filter is NULL then it is always a match */
#if defined(HAVE_REGEX)
	    if(regex_filter == NULL)
		return(TRUE);
#else
	    if(STRISEMPTY(filter))
		return(TRUE);
#endif

	    /* Get the name portion of the path */
	    name = (const gchar *)strrchr((const char *)path, G_DIR_SEPARATOR);
	    if(name != NULL)
		name++;
	    else
		name = path;

	    /* Filter check */
#if defined(HAVE_REGEX)
	    if(regexec(
		regex_filter,
		name,
		0, NULL,
		0
	    ) == 0)
		return(TRUE);
#else
	    if(fnmatch(filter, name, 0) == 0)
		return(TRUE);
#endif
	}

	return(FALSE);
}


/*
 *	Parses the null terminated string specified by buf which is
 *	from an ARJ Packager list output.
 *
 *	The given string contain four lines per object description
 *	per ARJ Packager list output format.
 */
static void EDVArchStatParseLineARJ(
	edv_archive_object_struct *obj, const gchar *buf
)
{
	gint c;
	const gchar *s = buf;
	struct tm mtime_buf;

	if((obj == NULL) || (s == NULL))
	    return;

	memset(&mtime_buf, 0x00, sizeof(struct tm));
	mtime_buf.tm_isdst = -1;

	/* Sample format line:

002) dir/file.ext
 11 UNIX           1293        638 0.493 03-08-21 00:16:06 -rw-r--r-- ---  +1
				   DTA   03-08-25 20:00:36
				   DTC   03-08-21 00:16:06

	*/

	/* Index (ignored) */
	while(!ISSPACE(*s) && (*s != '\0'))
	    s++;
	while(ISSPACE(*s))
	    s++;

	/* Full Path & Name */
	if(*s != '\0')
	{
	    gint len;
	    const gchar *s_start = s;

	    while(!ISSPACE(*s) && (*s != '\0'))
		s++;

	    len = (gint)(s - s_start);

	    /* Get full path */
	    g_free(obj->full_path);
	    obj->full_path = (gchar *)g_malloc(
		(len + 1) * sizeof(gchar)
	    );
	    if(len > 0)
		memcpy(obj->full_path, s_start, len);
	    obj->full_path[len] = '\0';

	    /* Strip tailing deliminators */
	    StripPath((char *)obj->full_path);

	    /* Get name from path */
	    g_free(obj->name);
	    obj->name = STRDUP(g_basename(obj->full_path));
	}
	while(ISSPACE(*s))
	    s++;

	/* Rev (ignored) */
	while(!ISSPACE(*s) && (*s != '\0'))
	    s++;
	while(ISSPACE(*s))
	    s++;

	/* Platform (ignored) */
	while(!ISSPACE(*s) && (*s != '\0'))
	    s++;
	while(ISSPACE(*s))
	    s++;

	/* Size */
	obj->size = (gulong)ATOL(s);
	while(!ISSPACE(*s) && (*s != '\0'))
	    s++;
	while(ISSPACE(*s))
	    s++;

	/* Compressed Size */
	obj->compressed_size = (gulong)ATOL(s);
	while(!ISSPACE(*s) && (*s != '\0'))
	    s++;
	while(ISSPACE(*s))
	    s++;

	/* Compression Ratio */
	obj->compression_ratio = 1.0f - ATOF(s);
	if(obj->compression_ratio > 1.0f)
	    obj->compression_ratio = 1.0f;
	while(!ISSPACE(*s) && (*s != '\0'))
	    s++;
	while(ISSPACE(*s))
	    s++;

	/* Year-month-date */
	if(*s != '\0')
	{
	    gint year_2digit;

	    /* Get 2-digit year (use "imperfect" Y2K fix) */
	    if(*s == '0')
		s++;
	    year_2digit = ATOI(s);
	    mtime_buf.tm_year = (int)((year_2digit < 60) ?
		(year_2digit + 100) : year_2digit
	    );
	    while((*s != '-') && (*s != '\0'))
		s++;
	    if(*s == '-')
		s++;

	    /* Month */
	    if(*s == '0')
		s++;
	    mtime_buf.tm_mon = (int)ATOI(s) - 1;
	    while((*s != '-') && (*s != '\0'))
		s++;
	    if(*s == '-')
		s++;

	    /* Day */
	    if(*s == '0')
		s++;
	    mtime_buf.tm_mday = (int)ATOI(s);
	    while(!ISSPACE(*s) && (*s != '\0'))
		s++;
	}
	while(ISSPACE(*s))
	    s++;

	/* Hour:minutes:seconds */
	if(*s != '\0')
	{
	    /* Hour */
	    if(*s == '0')
		s++;
	    mtime_buf.tm_hour = (int)ATOI(s) - 1;
	    while((*s != ':') && (*s != '\0'))
		s++;
	    if(*s == ':')
		s++;

	    /* Minutes */
	    if(*s == '0')
		s++;
	    mtime_buf.tm_min = (int)ATOI(s);
	    while((*s != ':') && (*s != '\0'))
		s++;
	    if(*s == ':')
		s++;

	    /* Seconds */
	    if(*s == '0')
		s++;
	    mtime_buf.tm_sec = (int)ATOI(s);
	    while(!ISSPACE(*s) && (*s != '\0'))
		s++;
	}

	/* Parse time compoents and generate system time since we now
	 * have enough information to do that
	 */
	obj->access_time = (gulong)mktime(&mtime_buf);
	obj->modify_time = obj->access_time;
	obj->change_time = obj->access_time;

	while(ISSPACE(*s))
	    s++;


	/* Type */
	c = *s;
	if(c != '\0')
	{
	    if(c == 'd')
		obj->type = EDV_OBJECT_TYPE_DIRECTORY;
	    else if(c == 'l')
		obj->type = EDV_OBJECT_TYPE_LINK;
	    else if(c == 'p')
		obj->type = EDV_OBJECT_TYPE_FIFO;
	    else if(c == 'b')
		obj->type = EDV_OBJECT_TYPE_DEVICE_BLOCK;
	    else if(c == 'c')
		obj->type = EDV_OBJECT_TYPE_DEVICE_CHARACTER;
	    else if(c == 's')
		obj->type = EDV_OBJECT_TYPE_SOCKET;
	    else
		obj->type = EDV_OBJECT_TYPE_FILE;

	    s++;
	}

	/* Permissions */
	obj->permissions = 0x00000000;

	/* Owner read/write/execute */
	c = *s;
	if(c != '\0')
	{
	    obj->permissions |= (c == 'r') ? EDV_PERMISSION_UREAD : 0;
	    s++;
	}
	c = *s;
	if(c != '\0')
	{
	    obj->permissions |= (c == 'w') ? EDV_PERMISSION_UWRITE : 0;
	    s++;
	}
	c = *s;
	if(c != '\0')
	{
	    obj->permissions |= (c == 'x') ? EDV_PERMISSION_UEXECUTE : 0;
	    s++;
	}

	/* Group read/write/execute */
	c = *s;
	if(c != '\0')
	{
	    obj->permissions |= (c == 'r') ? EDV_PERMISSION_GREAD : 0;
	    s++;
	}
	c = *s;
	if(c != '\0')
	{
	    obj->permissions |= (c == 'w') ? EDV_PERMISSION_GWRITE : 0;
	    s++;
	}
	c = *s;
	if(c != '\0')
	{
	    obj->permissions |= (c == 'x') ? EDV_PERMISSION_GEXECUTE : 0;
	    s++;
	}

	/* Anonymous read/write/execute */
	c = *s;
	if(c != '\0')
	{
	    obj->permissions |= (c == 'r') ? EDV_PERMISSION_AREAD : 0;
	    s++;
	}
	c = *s;
	if(c != '\0')
	{
	    obj->permissions |= (c == 'w') ? EDV_PERMISSION_AWRITE : 0;
	    s++;
	}
	c = *s;
	if(c != '\0')
	{
	    obj->permissions |= (c == 'x') ? EDV_PERMISSION_AEXECUTE : 0;
	    s++;
	}

	while(ISSPACE(*s))
	    s++;

	/* GUI (ignored) */
	while(!ISSPACE(*s) && (*s != '\0'))
	    s++;
	while(ISSPACE(*s))
	    s++;

	/* BPMGS (use as method) */
	if(*s != '\0')
	{
	    gint len;
	    const gchar *s_start = s;

	    while(!ISSPACE(*s) && (*s != '\0'))
		s++;

	    len = (gint)(s - s_start);

	    g_free(obj->method);
	    obj->method = (gchar *)g_malloc((len + 1) * sizeof(gchar));
	    if(len > 0)
		memcpy(obj->method, s_start, len);
	    obj->method[len] = '\0';
	}
	while(ISSPACE(*s))
	    s++;

	/* Ignore other dates and a whole bunch of other stuff */
}

/*
 *      Parses the null terminated string specified by buf which is
 *	from a LHA Archive list output.
 */
static void EDVArchStatParseLineLHA(
	edv_archive_object_struct *obj, const gchar *buf,
	const struct tm *cur_mtime_buf
)
{
	gint c;
	const gchar *s = buf;
	struct tm mtime_buf;

	if((obj == NULL) || (s == NULL))
	    return;

	if(cur_mtime_buf != NULL)
	    memcpy(&mtime_buf, cur_mtime_buf, sizeof(struct tm));
	else
	    memset(&mtime_buf, 0x00, sizeof(struct tm));

	/* Sample format line:

-rw-r--r-- 500/500 1058 2643 40.0% -lh5- bf09 Aug 26 18:32 dir/file.ext

	 */

	/* Type */
	c = *s;
	if(c != '\0')
	{
	    if(c == 'd')
		obj->type = EDV_OBJECT_TYPE_DIRECTORY;
	    else if(c == 'l')
		obj->type = EDV_OBJECT_TYPE_LINK;
	    else if(c == 'p')
		obj->type = EDV_OBJECT_TYPE_FIFO;
	    else if(c == 'b')
		obj->type = EDV_OBJECT_TYPE_DEVICE_BLOCK;
	    else if(c == 'c')
		obj->type = EDV_OBJECT_TYPE_DEVICE_CHARACTER;
	    else if(c == 's')
		obj->type = EDV_OBJECT_TYPE_SOCKET;
	    else
		obj->type = EDV_OBJECT_TYPE_FILE;

	    s++;
	}

	/* Permissions */
	obj->permissions = 0x00000000;

	/* Owner read/write/execute */
	c = *s;
	if(c != '\0')
	{
	    obj->permissions |= (c == 'r') ? EDV_PERMISSION_UREAD : 0;
	    s++;
	}
	c = *s;
	if(c != '\0')
	{
	    obj->permissions |= (c == 'w') ? EDV_PERMISSION_UWRITE : 0;
	    s++;
	}
	c = *s;
	if(c != '\0')
	{
	    obj->permissions |= (c == 'x') ? EDV_PERMISSION_UEXECUTE : 0;
	    s++;
	}

	/* Group read/write/execute */
	c = *s;
	if(c != '\0')
	{
	    obj->permissions |= (c == 'r') ? EDV_PERMISSION_GREAD : 0;
	    s++;
	}
	c = *s;
	if(c != '\0')
	{
	    obj->permissions |= (c == 'w') ? EDV_PERMISSION_GWRITE : 0;
	    s++;
	}
	c = *s;
	if(c != '\0')
	{
	    obj->permissions |= (c == 'x') ? EDV_PERMISSION_GEXECUTE : 0;
	    s++;
	}

	/* Anonymous read/write/execute */
	c = *s;
	if(c != '\0')
	{
	    obj->permissions |= (c == 'r') ? EDV_PERMISSION_AREAD : 0;
	    s++;
	}
	c = *s;
	if(c != '\0')
	{
	    obj->permissions |= (c == 'w') ? EDV_PERMISSION_AWRITE : 0;
	    s++;
	}
	c = *s;
	if(c != '\0')
	{
	    obj->permissions |= (c == 'x') ? EDV_PERMISSION_AEXECUTE : 0;
	    s++;
	}
	while(ISBLANK(*s))
	    s++;

	/* Owner/group */
	if(*s != '\0')
	{
	    gchar *s_delim;
	    const gchar *s_end = (const gchar *)strpbrk(
		(const char *)s, " \t"
	    );
	    const gint len = (s_end != NULL) ? (gint)(s_end - s) : STRLEN(s);
	    gchar *owner_group_str = (gchar *)g_malloc(
		(len + 1) * sizeof(gchar)
	    );
	    if(len > 0)
		memcpy(owner_group_str, s, len);
	    owner_group_str[len] = '\0';

	    s_delim = (gchar *)strchr((char *)owner_group_str, '/');
	    if(s_delim != NULL)
	    {
		*s_delim = '\0';

		/* Get the owner */
		g_free(obj->owner_name);
		obj->owner_name = STRDUP(owner_group_str);

		/* Get the group */
		g_free(obj->group_name);
		obj->group_name = STRDUP(s_delim + 1);
	    }
	    else
	    {
		/* Get the owner */
		g_free(obj->owner_name);
		obj->owner_name = STRDUP(owner_group_str);
	    }

	    g_free(owner_group_str);

	    s += len;
	}
	while(ISBLANK(*s))
	    s++;

	/* Compressed Size */
	obj->compressed_size = (gulong)ATOL(s);
	while(!ISBLANK(*s) && (*s != '\0'))
	    s++;
	while(ISBLANK(*s))
	    s++;

	/* Size */
	obj->size = (gulong)ATOL(s);
	while(!ISBLANK(*s) && (*s != '\0'))
	    s++;
	while(ISBLANK(*s))
	    s++;

	/* Compression Ratio */
	obj->compression_ratio = CLIP(ATOF(s) / 100.0f, 0.0f, 1.0f);
	while(!ISBLANK(*s) && (*s != '\0'))
	    s++;
	while(ISBLANK(*s))
	    s++;

	/* Method */
	if(*s != '\0')
	{
	    gint len;
	    const gchar *s_start = s;

	    while(!ISBLANK(*s) && (*s != '\0'))
		s++;

	    len = (gint)(s - s_start);

	    g_free(obj->method);
	    obj->method = (gchar *)g_malloc((len + 1) * sizeof(gchar));
	    if(len > 0)
		memcpy(obj->method, s_start, len);
	    obj->method[len] = '\0';
	}
	while(ISBLANK(*s))
	    s++;

	/* CRC */
	if(*s != '\0')
	{
	    gint len;
	    const gchar *s_start = s;

	    while(!ISBLANK(*s) && (*s != '\0'))
		s++;

	    len = (gint)(s - s_start);

	    g_free(obj->crc);
	    obj->crc = (gchar *)g_malloc((len + 1) * sizeof(gchar));
	    if(len > 0)
		memcpy(obj->crc, s_start, len);
	    obj->crc[len] = '\0';
	    strtoupper(obj->crc);
	}
	while(ISBLANK(*s))
	    s++;

	/* Month name */
	if(strcasepfx((const char *)s, "jan"))
	    mtime_buf.tm_mon = 0;
	else if(strcasepfx((const char *)s, "feb"))
	    mtime_buf.tm_mon = 1;
	else if(strcasepfx((const char *)s, "mar"))
	    mtime_buf.tm_mon = 2;
	else if(strcasepfx((const char *)s, "apr"))
	    mtime_buf.tm_mon = 3;
	else if(strcasepfx((const char *)s, "may"))
	    mtime_buf.tm_mon = 4;
	else if(strcasepfx((const char *)s, "jun"))
	    mtime_buf.tm_mon = 5;
	else if(strcasepfx((const char *)s, "jul"))
	    mtime_buf.tm_mon = 6;
	else if(strcasepfx((const char *)s, "aug"))
	    mtime_buf.tm_mon = 7;
	else if(strcasepfx((const char *)s, "sep"))
	    mtime_buf.tm_mon = 8;
	else if(strcasepfx((const char *)s, "oct"))
	    mtime_buf.tm_mon = 9;
	else if(strcasepfx((const char *)s, "nov"))
	    mtime_buf.tm_mon = 10;
	else if(strcasepfx((const char *)s, "dec"))
	    mtime_buf.tm_mon = 11;
	while(!ISBLANK(*s) && (*s != '\0'))
	    s++;
	while(ISBLANK(*s))
	    s++;

	/* Day */
	mtime_buf.tm_mday = (int)ATOI(s);
	while(!ISBLANK(*s) && (*s != '\0'))
	    s++;
	while(ISBLANK(*s))
	    s++;

	/* Year or time */
	if(*s != '\0')
	{
	    gchar *s2 = STRDUP(s), *s3;
	    for(s3 = s2; *s3 != '\0'; s3++)
	    {
		if(ISBLANK(*s3))
		{
		    *s3 = '\0';
		    break;
		}
	    }

	    s3 = s2;

	    /* Time? */
	    if(strchr((const char *)s3, ':') != NULL)
	    {
		/* Hour */
		if(*s3 == '0')
		    s3++;
		mtime_buf.tm_hour = (int)ATOI(s3);
		while((*s3 != ':') && (*s3 != '\0'))
		    s3++;
		if(*s3 == ':')
		    s3++;

		/* Minutes */
		if(*s3 == '0')
		    s3++;
		mtime_buf.tm_min = (int)ATOI(s3);
	    }
	    else
	    {
		/* Year */
		mtime_buf.tm_year = (int)ATOI(s3) - 1900;
	    }
	    g_free(s2);

	    while(!ISBLANK(*s) && (*s != '\0'))
		s++;
	}
	while(ISBLANK(*s))
	    s++;

	/* Parse time compoents and generate system time since we now
	 * have enough information to do that
	 */
	obj->access_time = (gulong)mktime(&mtime_buf);
	obj->modify_time = obj->access_time;
	obj->change_time = obj->access_time;

	/* Full Path & Name */
	if(*s != '\0')
	{
	    gint len;
	    const gchar *s_start = s;

	    while(!ISSPACE(*s) && (*s != '\0'))
		s++;

	    len = (gint)(s - s_start);

	    /* Get full path */
	    g_free(obj->full_path);
	    obj->full_path = (gchar *)g_malloc(
		(len + 1) * sizeof(gchar)
	    );
	    if(len > 0)
		memcpy(obj->full_path, s_start, len);
	    obj->full_path[len] = '\0';

	    /* Strip tailing deliminators */
	    StripPath((char *)obj->full_path);

	    /* Get name from path */
	    g_free(obj->name);
	    obj->name = STRDUP(g_basename(obj->full_path));
	}
	while(ISSPACE(*s))
	    s++;
}

/*
 *	Parses the null terminated string specified by buf which is
 *	from a RAR Archive list output.
 *
 *	The given string must contain two lines per object description
 *	per RAR Archive list output format.
 */
static void EDVArchStatParseLineRAR(
	edv_archive_object_struct *obj, const gchar *buf
)
{
	const gchar *s = buf;
	struct tm mtime_buf;

	if((obj == NULL) || (s == NULL))
	    return;

	memset(&mtime_buf, 0x00, sizeof(struct tm));
	mtime_buf.tm_isdst = -1;

	/* Sample format line:

 dir/file.ext   1604      780  48% 12-03-03 14:50 -rw-r--r-- 5005A169 m3b 2.9

	    Or

 dir/file.ext   1604      780  48% 12-03-03 14:50 .....A 5005A169 m3b 2.9

	 */

	/* Full Path & Name */
	if(*s != '\0')
	{
	    gint len;

	    /* Find the end deliminator for the full path and name as
	     * follows; ' ' '\t' '\n' or '\r'
	     */
	    const gchar *s_end = (const gchar *)strchr(
		(const char *)s, '\n'
	    );
	    if(s_end == NULL)
		s_end = (const gchar *)strchr(
		    (const char *)s, '\r'
		);
	    if(s_end == NULL)
		s_end = (const gchar *)strchr(
		    (const char *)s, ' '
		);
	    if(s_end == NULL)
		s_end = (const gchar *)strchr(
		    (const char *)s, '\t'
		);

	    len = (s_end != NULL) ? (gint)(s_end - s) : STRLEN(s);

	    g_free(obj->full_path);
	    obj->full_path = (gchar *)g_malloc((len + 1) * sizeof(gchar));
	    if(len > 0)
		memcpy(obj->full_path, s, len);
	    obj->full_path[len] = '\0';

	    /* Strip tailing deliminators */
	    StripPath((char *)obj->full_path);

	    /* Get name from path */
	    g_free(obj->name);
	    obj->name = STRDUP(g_basename(obj->full_path));

	    s += len;
	}
	while(ISSPACE(*s))
	    s++;

	/* Size */
	obj->size = (gulong)ATOL(s);
	while(!ISSPACE(*s) && (*s != '\0'))
	    s++;
	while(ISSPACE(*s))
	    s++;

	/* Compressed Size */
	obj->compressed_size = (gulong)ATOL(s);
	while(!ISSPACE(*s) && (*s != '\0'))
	    s++;
	while(ISSPACE(*s))
	    s++;

	/* Compression Ratio */
	obj->compression_ratio = (gfloat)ATOI(s) / 100.0f;
	while(!ISSPACE(*s) && (*s != '\0'))
	    s++;
	while(ISSPACE(*s))
	    s++;

	/* Day-month-year */
	if(*s != '\0')
	{
	    gint year_2digit;

	    /* Day */
	    if(*s == '0')
		s++;
	    mtime_buf.tm_mday = (int)ATOI(s);
	    while((*s != '-') && (*s != '\0'))
		s++;
	    if(*s == '-')
		s++;

	    /* Month */
	    if(*s == '0')
		s++;
	    mtime_buf.tm_mon = (int)ATOI(s) - 1;
	    while((*s != '-') && (*s != '\0'))
		s++;
	    if(*s == '-')
		s++;

	    /* Get 2-digit year (use "imperfect" Y2K fix) */
	    if(*s == '0')
		s++;
	    year_2digit = ATOI(s);
	    mtime_buf.tm_year = (int)((year_2digit < 60) ?
		(year_2digit + 100) : year_2digit
	    );
	    while(!ISSPACE(*s) && (*s != '\0'))
		s++;
	}
	while(ISSPACE(*s))
	    s++;

	/* Hour:minutes */
	if(*s != '\0')
	{
	    /* Hour */
	    if(*s == '0')
		s++;
	    mtime_buf.tm_hour = (int)(ATOI(s) - 1);
	    while((*s != ':') && (*s != '\0'))
		s++;
	    if(*s == ':')
		s++;

	    /* Minutes */
	    if(*s == '0')
		s++;
	    mtime_buf.tm_min = (int)ATOI(s);
	    while(!ISSPACE(*s) && (*s != '\0'))
		s++;
	}

	/* Parse time compoents and generate system time since we now
	 * have enough information to do that
	 */
	obj->access_time = (gulong)mktime(&mtime_buf);
	obj->modify_time = obj->access_time;
	obj->change_time = obj->access_time;

	while(ISSPACE(*s))
	    s++;

	/* Attributes */
	if(s != '\0')
	{
	    const gchar *s_end = (const gchar *)strpbrk(
		(const char *)s, " \t"
	    );
	    const gint len = (s_end != NULL) ? (gint)(s_end - s) : STRLEN(s);

	    /* Determine the attributes string by its length */
	    /* UNIX? */
	    if(len == 10)
	    {
		/* Type */
		gchar c = s[0];
		if(c == 'd')
		    obj->type = EDV_OBJECT_TYPE_DIRECTORY;
		else if(c == 'l')
		    obj->type = EDV_OBJECT_TYPE_LINK;
		else if(c == 'p')
		    obj->type = EDV_OBJECT_TYPE_FIFO;
		else if(c == 'b')
		    obj->type = EDV_OBJECT_TYPE_DEVICE_BLOCK;
		else if(c == 'c')
		    obj->type = EDV_OBJECT_TYPE_DEVICE_CHARACTER;
		else if(c == 's')
		    obj->type = EDV_OBJECT_TYPE_SOCKET;
		else
		    obj->type = EDV_OBJECT_TYPE_FILE;

		/* Permissions */
		obj->permissions = 0x00000000;

		/* Owner read/write/execute */
		if(s[1] == 'r')
		    obj->permissions |= EDV_PERMISSION_UREAD;
		if(s[2] == 'w')
		    obj->permissions |= EDV_PERMISSION_UWRITE;
		if(s[3] == 'x')
		    obj->permissions |= EDV_PERMISSION_UEXECUTE;

		/* Group read/write/execute */
		if(s[4] == 'r')
		    obj->permissions |= EDV_PERMISSION_GREAD;
		if(s[5] == 'w')
		    obj->permissions |= EDV_PERMISSION_GWRITE;
		if(s[6] == 'x')
		    obj->permissions |= EDV_PERMISSION_GEXECUTE;

		/* Anonymous read/write/execute */
		if(s[7] == 'r')
		    obj->permissions |= EDV_PERMISSION_AREAD;
		if(s[8] == 'w')
		    obj->permissions |= EDV_PERMISSION_AWRITE;
		if(s[9] == 'x')
		    obj->permissions |= EDV_PERMISSION_AEXECUTE;
	    }
	    /* DOS */
	    else if(len == 6)
	    {
		obj->type = EDV_OBJECT_TYPE_FILE;
		obj->permissions = EDV_PERMISSION_UREAD |
				   EDV_PERMISSION_UWRITE |
				   EDV_PERMISSION_GREAD |
				   EDV_PERMISSION_GWRITE |
				   EDV_PERMISSION_AREAD |
				   EDV_PERMISSION_AWRITE;
	    }
	    /* Other */
	    else
	    {
		obj->type = EDV_OBJECT_TYPE_FILE;
		obj->permissions = EDV_PERMISSION_UREAD |
				   EDV_PERMISSION_UWRITE |
				   EDV_PERMISSION_GREAD |
				   EDV_PERMISSION_GWRITE |
				   EDV_PERMISSION_AREAD |
				   EDV_PERMISSION_AWRITE;
	    }

	    s += len;
	}
	while(ISSPACE(*s))
	    s++;

	/* CRC */
	if(*s != '\0')
	{
	    const gchar *s_end = (const gchar *)strpbrk(
		(const char *)s, " \t"
	    );
	    const gint len = (s_end != NULL) ? (gint)(s_end - s) : STRLEN(s);

	    g_free(obj->crc);
	    obj->crc = (gchar *)g_malloc((len + 1) * sizeof(gchar));
	    if(len > 0)
		memcpy(obj->crc, s, len);
	    obj->crc[len] = '\0';

	    s += len;
	}
	while(ISSPACE(*s))
	    s++;

	/* Method & Version */
	if(*s != '\0')
	{
	    gint len;
	    const gchar *s_end = s;

	    while(!ISSPACE(*s_end) && (*s_end != '\0'))
		s_end++;
	    while(ISSPACE(*s_end))
		s_end++;
	    while(!ISSPACE(*s_end) && (*s_end != '\0'))
		s_end++;

	    len = (gint)(s_end - s);

	    g_free(obj->method);
	    obj->method = (gchar *)g_malloc((len + 1) * sizeof(gchar));
	    if(len > 0)
		memcpy(obj->method, s, len);
	    obj->method[len] = '\0';

	    s = s_end;
	}
	while(ISSPACE(*s))
	    s++;
}

/*
 *	Parses the null terminated string specified by buf which is
 *	from a RedHat Package Manager list output.
 */
static void EDVArchStatParseLineRPM(
	edv_archive_object_struct *obj, const gchar *buf
)
{
	gint c;
	const gchar *buf_ptr = buf;
	struct tm mtime_buf;


	if((obj == NULL) || (buf_ptr == NULL))
	    return;

	memset(&mtime_buf, 0x00, sizeof(struct tm));
	mtime_buf.tm_isdst = -1;

	/* Sample format line:

-rw-r--r--    1 root    root            27666 Jan  4  2000 /dir/file.ext

	 */

	/* Type and permissions string (this is one string with no
	 * deliminators
	 */

	/* Type */
	c = *buf_ptr;
	if(c != '\0')
	{
	    if(c == 'd')
		obj->type = EDV_OBJECT_TYPE_DIRECTORY;
	    else if(c == 'l')
		obj->type = EDV_OBJECT_TYPE_LINK;
	    else if(c == 'p')
		obj->type = EDV_OBJECT_TYPE_FIFO;
	    else if(c == 'b')
		obj->type = EDV_OBJECT_TYPE_DEVICE_BLOCK;
	    else if(c == 'c')
		obj->type = EDV_OBJECT_TYPE_DEVICE_CHARACTER;
	    else if(c == 's')
		obj->type = EDV_OBJECT_TYPE_SOCKET;
	    else
		obj->type = EDV_OBJECT_TYPE_FILE;

	    buf_ptr++;
	}

	/* Permissions */
	obj->permissions = 0x00000000;

	/* Owner read/write/execute */
	c = *buf_ptr;
	if(c != '\0')
	{
	    obj->permissions |= (c == 'r') ? EDV_PERMISSION_UREAD : 0;
	    buf_ptr++;
	}
	c = *buf_ptr;
	if(c != '\0')
	{
	    obj->permissions |= (c == 'w') ? EDV_PERMISSION_UWRITE : 0;
	    buf_ptr++;
	}
	c = *buf_ptr;
	if(c != '\0')
	{
	    obj->permissions |= (c == 'x') ? EDV_PERMISSION_UEXECUTE : 0;
	    buf_ptr++;
	}

	/* Group read/write/execute */
	c = *buf_ptr;
	if(c != '\0')
	{
	    obj->permissions |= (c == 'r') ? EDV_PERMISSION_GREAD : 0;
	    buf_ptr++;
	}
	c = *buf_ptr;
	if(c != '\0')
	{
	    obj->permissions |= (c == 'w') ? EDV_PERMISSION_GWRITE : 0;
	    buf_ptr++;
	}
	c = *buf_ptr;
	if(c != '\0')
	{
	    obj->permissions |= (c == 'x') ? EDV_PERMISSION_GEXECUTE : 0;
	    buf_ptr++;
	}

	/* Anonymous read/write/execute */
	c = *buf_ptr;
	if(c != '\0')
	{
	    obj->permissions |= (c == 'r') ? EDV_PERMISSION_AREAD : 0;
	    buf_ptr++;
	}
	c = *buf_ptr;
	if(c != '\0')
	{
	    obj->permissions |= (c == 'w') ? EDV_PERMISSION_AWRITE : 0;
	    buf_ptr++;
	}
	c = *buf_ptr;
	if(c != '\0')
	{
	    obj->permissions |= (c == 'x') ? EDV_PERMISSION_AEXECUTE : 0;
	    buf_ptr++;
	}

	while(ISBLANK(*buf_ptr))
	    buf_ptr++;


	/* This is number, not sure what it is but just seek past it */
	while(!ISBLANK(*buf_ptr) && (*buf_ptr != '\0'))
	    buf_ptr++;
	while(ISBLANK(*buf_ptr))
	    buf_ptr++;


	/* Owner and group strings, deliminated by a space in between */
	if(*buf_ptr != '\0')
	{
	    gchar *s, *s2;

	    /* Get owner */
	    g_free(obj->owner_name);
	    obj->owner_name = s = STRDUP(buf_ptr);
	    s2 = strchr(s, ' ');
	    if(s2 != NULL)
		*s2 = '\0';
	    s2 = strchr(s, '\t');
	    if(s2 != NULL)
		*s2 = '\0';

	    /* Seek to group */
	    while(!ISBLANK(*buf_ptr) && (*buf_ptr != '\0'))
		buf_ptr++;
	    while(ISBLANK(*buf_ptr))
		buf_ptr++;

	    /* Get group */
	    g_free(obj->group_name);
	    obj->group_name = s = STRDUP(buf_ptr);
	    s2 = strchr(s, ' ');
	    if(s2 != NULL)
		*s2 = '\0';
	    s2 = strchr(s, '\t');
	    if(s2 != NULL)
		*s2 = '\0';

	    /* Seek past group to first blank character */
	    while(!ISBLANK(*buf_ptr) && (*buf_ptr != '\0'))
		buf_ptr++;
	}


	while(ISBLANK(*buf_ptr))
	    buf_ptr++;

	/* Size */
	obj->size = (gulong)ATOL(buf_ptr);

	while(!ISBLANK(*buf_ptr) && (*buf_ptr != '\0'))
	    buf_ptr++;
	while(ISBLANK(*buf_ptr))
	    buf_ptr++;


	/* These next three strings are month, day, and year
	 * separated by blank characters but the names are verbose and
	 * thus too difficult to parse
	 */
	/* Skip month */
	while(!ISBLANK(*buf_ptr) && (*buf_ptr != '\0'))
	    buf_ptr++;
	while(ISBLANK(*buf_ptr))
	    buf_ptr++;

	/* Skip day */
	while(!ISBLANK(*buf_ptr) && (*buf_ptr != '\0'))
	    buf_ptr++;
	while(ISBLANK(*buf_ptr))
	    buf_ptr++;

	/* Skip year */
	while(!ISBLANK(*buf_ptr) && (*buf_ptr != '\0'))
	    buf_ptr++;
	while(ISBLANK(*buf_ptr))
	    buf_ptr++;

	/* Full Path & Name */
	if(*buf_ptr != '\0')
	{
	    gint len;
	    const gchar *s = buf_ptr;

	    while(!ISSPACE(*buf_ptr) && (*buf_ptr != '\0'))
		buf_ptr++;

	    len = (gint)(buf_ptr - s);

	    /* Get full path */
	    g_free(obj->full_path);
	    obj->full_path = (gchar *)g_malloc(
		(len + 1) * sizeof(gchar)
	    );
	    if(len > 0)
		memcpy(obj->full_path, s, len);
	    obj->full_path[len] = '\0';

	    /* Strip tailing deliminators */
	    StripPath((char *)obj->full_path);

	    /* Get name from path */
	    g_free(obj->name);
	    obj->name = STRDUP(g_basename(obj->full_path));
	}
	while(ISSPACE(*buf_ptr))
	    buf_ptr++;

	/* If this object is a link then we need to parse the link
	 * destination
	 */
	if(obj->type == EDV_OBJECT_TYPE_LINK)
	{
	    gint len;
	    const gchar *s;

	    /* Seek past "->" deliminator */
	    while(!ISSPACE(*buf_ptr) && (*buf_ptr != '\0'))
		buf_ptr++;
	    while(ISSPACE(*buf_ptr))
		buf_ptr++;

	    /* Now at link destination value */
	    s = buf_ptr;
	    while(!ISSPACE(*buf_ptr) && (*buf_ptr != '\0'))
		buf_ptr++;

	    len = (gint)(buf_ptr - s);

	    g_free(obj->link_value);
	    obj->link_value = (gchar *)g_malloc(
		(len + 1) * sizeof(gchar)
	    );
	    if(len > 0)
		memcpy(obj->link_value, s, len);
	    obj->link_value[len] = '\0';

	    while(ISSPACE(*buf_ptr))
		buf_ptr++;
	}
}


/*
 *	Parses the null terminated string specified by buf which is
 *	from a Tape Archiver list output.
 */
static void EDVArchStatParseLineTar(
	edv_archive_object_struct *obj, const gchar *buf
)
{
	gchar c;
	const gchar *s = buf;
	struct tm mtime_buf;

	if((obj == NULL) || (s == NULL))
	    return;

	memset(&mtime_buf, 0x00, sizeof(struct tm));
	mtime_buf.tm_isdst = -1;

	/* Sample format line:

-rw-r--r-- learfox/learfox 279 2006-06-17 02:40 file.txt

	   or

lrwxrwxrwx root/root   0 2001-11-19 02:57 file.ext -> link.dest

	 */

	/* Type and permissions string (this is one string with no
	 * deliminators
	 */

	/* Type */
	c = *s;
	if(!ISBLANK(c) && (c != '\0'))
	{
	    if(c == 'd')
		obj->type = EDV_OBJECT_TYPE_DIRECTORY;
	    else if(c == 'l')
		obj->type = EDV_OBJECT_TYPE_LINK;
	    else if(c == 'p')
		obj->type = EDV_OBJECT_TYPE_FIFO;
	    else if(c == 'b')
		obj->type = EDV_OBJECT_TYPE_DEVICE_BLOCK;
	    else if(c == 'c')
		obj->type = EDV_OBJECT_TYPE_DEVICE_CHARACTER;
	    else if(c == 's')
		obj->type = EDV_OBJECT_TYPE_SOCKET;
	    else
		obj->type = EDV_OBJECT_TYPE_FILE;

	    s++;
	}

	/* Permissions */
	obj->permissions = 0x00000000;

	/* Owner read/write/execute */
	c = *s;
	if(!ISBLANK(c) && (c != '\0'))
	{
	    obj->permissions |= (c == 'r') ? EDV_PERMISSION_UREAD : 0;
	    s++;
	}
	c = *s;
	if(!ISBLANK(c) && (c != '\0'))
	{
	    obj->permissions |= (c == 'w') ? EDV_PERMISSION_UWRITE : 0;
	    s++;
	}
	c = *s;
	if(!ISBLANK(c) && (c != '\0'))
	{
	    obj->permissions |= (c == 'x') ? EDV_PERMISSION_UEXECUTE : 0;
	    s++;
	}

	/* Group read/write/execute */
	c = *s;
	if(!ISBLANK(c) && (c != '\0'))
	{
	    obj->permissions |= (c == 'r') ? EDV_PERMISSION_GREAD : 0;
	    s++;
	}
	c = *s;
	if(!ISBLANK(c) && (c != '\0'))
	{
	    obj->permissions |= (c == 'w') ? EDV_PERMISSION_GWRITE : 0;
	    s++;
	}
	c = *s;
	if(!ISBLANK(c) && (c != '\0'))
	{
	    obj->permissions |= (c == 'x') ? EDV_PERMISSION_GEXECUTE : 0;
	    s++;
	}

	/* Anonymous read/write/execute */
	c = *s;
	if(!ISBLANK(c) && (c != '\0'))
	{
	    obj->permissions |= (c == 'r') ? EDV_PERMISSION_AREAD : 0;
	    s++;
	}
	c = *s;
	if(!ISBLANK(c) && (c != '\0'))
	{
	    obj->permissions |= (c == 'w') ? EDV_PERMISSION_AWRITE : 0;
	    s++;
	}
	c = *s;
	if(!ISBLANK(c) && (c != '\0'))
	{
	    obj->permissions |= (c == 'x') ? EDV_PERMISSION_AEXECUTE : 0;
	    s++;
	}

	/* Seek past any subsequent characters as needed */
	while(!ISBLANK(*s) && (*s != '\0'))
	    s++;

	while(ISBLANK(*s))
	    s++;

	/* Owner and group string, separated by a '/' in between */
	if(*s != '\0')
	{
	    gchar *s_delim;
	    const gchar *s_end = (const gchar *)strpbrk(
		(const char *)s, " \t"
	    );
	    const int len = (s_end != NULL) ? (gint)(s_end - s) : STRLEN(s);
	    gchar *owner_group_str = (gchar *)g_malloc(
		(len + 1) * sizeof(gchar)
	    );
	    if(len > 0)
		memcpy(owner_group_str, s, len);
	    owner_group_str[len] = '\0';

	    /* Get position of owner and group name deliminator */
	    s_delim = (gchar *)strchr((char *)owner_group_str, '/');
	    if(s_delim != NULL)
	    {
		*s_delim = '\0';	/* Put null char at end of owner name */

		/* Get the owner's name */
		g_free(obj->owner_name);
		obj->owner_name = STRDUP(owner_group_str);

		/* Get the group's name (it is valid after the null char) */
		g_free(obj->group_name);
		obj->group_name = STRDUP(s_delim + 1);
	    }
	    else
	    {
		/* Get the owner's name */
		g_free(obj->owner_name);
		obj->owner_name = STRDUP(owner_group_str);
	    }

	    g_free(owner_group_str);

	    s += len;
	}
	while(ISBLANK(*s))
	    s++;


	/* Size */
	obj->size = (gulong)ATOL(s);

	while(!ISBLANK(*s) && (*s != '\0'))
	    s++;
	while(ISBLANK(*s))
	    s++;


	/* Year-month-day */
	if(*s != '\0')
	{
	    /* Get year (input value starts from 1900) */
	    if(*s == '0')
		s++;
	    mtime_buf.tm_year = (int)(ATOI(s) - 1900);
	    while((*s != '-') && (*s != '\0'))
		s++;
	    if(*s == '-')
		s++;

	    /* Get month */
	    if(*s == '0')
		s++;
	    mtime_buf.tm_mon = (int)(ATOI(s) - 1);
	    while((*s != '-') && (*s != '\0'))
		s++;
	    if(*s == '-')
		s++;

	    /* Get day */
	    if(*s == '0')
		s++;
	    mtime_buf.tm_mday = (int)ATOI(s);
	    while(!ISBLANK(*s) && (*s != '\0'))
		s++;
	}

	while(ISBLANK(*s))
	    s++;


	/* Hour:minutes */
	if(*s != '\0')
	{
	    /* Get hour */
	    if(*s == '0')
		s++;
	    mtime_buf.tm_hour = (int)(ATOI(s) - 1);
	    while((*s != ':') && (*s != '\0'))
		s++;
	    if(*s == ':')
		s++;

	    /* Get minutes */
	    if(*s == '0')
		s++;
	    mtime_buf.tm_min = (int)ATOI(s);
	    while(!ISBLANK(*s) && (*s != '\0'))
		s++;
	}

	/* Parse time compoents and generate system time since we now
	 * have enough information to do that
	 */
	obj->access_time = (gulong)mktime(&mtime_buf);
	obj->modify_time = obj->access_time;
	obj->change_time = obj->access_time;

	while(ISBLANK(*s))
	    s++;

	/* Full Path & Name */
	if(*s != '\0')
	{
	    /* Link? */
	    if(obj->type == EDV_OBJECT_TYPE_LINK)
	    {
		const gchar *s_delim = (const gchar *)strstr(
		    (const char *)s, " -> "
		);
		if(s_delim != NULL)
		{
		    const gint len = (gint)(s_delim - s);

		    g_free(obj->full_path);
		    obj->full_path = (gchar *)g_malloc(
			(len + 1) * sizeof(gchar)
		    );
		    if(len > 0)
			memcpy(obj->full_path, s, len);
		    obj->full_path[len] = '\0';

		    /* Strip tailing deliminators */
		    StripPath((char *)obj->full_path);

		    /* Get the name from the path */
		    g_free(obj->name);
		    obj->name = STRDUP(g_basename(obj->full_path));

		    /* Seek past the deliminator and get the link's
		     * destination
		     */
		    s = s_delim + STRLEN(" -> ");
		    if(*s != '\0')
		    {
			const gchar *s_end = (const gchar *)strpbrk(
			    (const char *)s, "\n\r"
			);
			const gint len = (s_end != NULL) ?
			    (gint)(s_end - s) : STRLEN(s);

			g_free(obj->link_value);
			obj->link_value = (gchar *)g_malloc(
			    (len + 1) * sizeof(gchar)
			);
			if(len > 0)
			    memcpy(obj->link_value, s, len);
			obj->link_value[len] = '\0';

			s += len;
		    }
		}
		else
		{
		    /* Link with a missing deliminator, so just get
		     * the full path & name
		     */
		    const gchar *s_end = (const gchar *)strpbrk(
			(const char *)s, "\n\r"
		    );
		    const gint len = (s_end != NULL) ?
			(gint)(s_end - s) : STRLEN(s);

		    g_free(obj->full_path);
		    obj->full_path = (gchar *)g_malloc(
			(len + 1) * sizeof(gchar)
		    );
		    if(len > 0)
			memcpy(obj->full_path, s, len);
		    obj->full_path[len] = '\0';

		    /* Strip tailing deliminators */
		    StripPath((char *)obj->full_path);

		    /* Get the name from the path */
		    g_free(obj->name);
		    obj->name = STRDUP(g_basename(obj->full_path));

		    s += len;
		}
	    }
	    /* All else assume file */
	    else
	    {
		const gchar *s_end = (const gchar *)strpbrk(
		    (const char *)s, "\n\r"
		);
		const gint len = (s_end != NULL) ?
		    (gint)(s_end - s) : STRLEN(s);

		g_free(obj->full_path);
		obj->full_path = (gchar *)g_malloc(
		    (len + 1) * sizeof(gchar)
		);
		if(len > 0)
		    memcpy(obj->full_path, s, len);
		obj->full_path[len] = '\0';

		/* Strip tailing deliminators */
		StripPath((char *)obj->full_path);

		/* Get the name from the path */
		g_free(obj->name);
		obj->name = STRDUP(g_basename(obj->full_path));

		s += len;
	    }
	}
}

#ifndef HAVE_LIBZIP
/*
 *	Parses the null terminated string specified by buf which is
 *	from a PKZip list output
 */
static void EDVArchStatParseLineZip(
	edv_archive_object_struct *obj, const gchar *buf
)
{
	const gchar *s = buf;
	struct tm mtime_buf;

	if((obj == NULL) || (s == NULL))
	    return;

	memset(&mtime_buf, 0x00, sizeof(struct tm));
	mtime_buf.tm_isdst = -1;

	/* Sample format line:

    2371  Defl:X      688  71%  08-24-03 20:33  9c2d86e6  file.ext

	 */

	/* Size */
	obj->size = (gulong)ATOL(s);
	while(!ISBLANK(*s) && (*s != '\0'))
	    s++;
	while(ISBLANK(*s))
	    s++;

	/* Compression Method */
	if(*s != '\0')
	{
	    gint len;
	    const gchar *s_start = s;

	    while(!ISBLANK(*s) && (*s != '\0'))
		s++;

	    len = (gint)(s - s_start);

	    g_free(obj->method);
	    obj->method = (gchar *)g_malloc((len + 1) * sizeof(gchar));
	    if(len > 0)
		memcpy(obj->method, s_start, len);
	    obj->method[len] = '\0';
	}
	while(ISBLANK(*s))
	    s++;

	/* Compressed Size */
	obj->compressed_size = (gulong)ATOL(s);
	while(!ISBLANK(*s) && (*s != '\0'))
	    s++;
	while(ISBLANK(*s))
	    s++;

	/* Compression Ratio */
	obj->compression_ratio = (gfloat)ATOI(s) / 100.0f;
	while(!ISBLANK(*s) && (*s != '\0'))
	    s++;
	while(ISBLANK(*s))
	    s++;

	/* Month-day-year */
	if(*s != '\0')
	{
	    gint year_2digit;

	    /* Get month */
	    if(*s == '0')
		s++;
	    mtime_buf.tm_mon = (int)(ATOI(s) - 1);
	    while((*s != '-') && (*s != '\0'))
		s++;
	    if(*s == '-')
		s++;

	    /* Get day */
	    if(*s == '0')
		s++;
	    mtime_buf.tm_mday = (int)ATOI(s);
	    while((*s != '-') && (*s != '\0'))
		s++;
	    if(*s == '-')
		s++;

	    /* The year is relative to 1900, note that there is a Y2K
	     * problem here but we are compensating for it
	     */
	    if(*s == '0')
		s++;
	    year_2digit = (int)ATOI(s);
	    mtime_buf.tm_year = (int)((year_2digit < 60) ?
		(year_2digit + 100) : year_2digit
	    );
	    while(!ISBLANK(*s) && (*s != '\0'))
		s++;
	}
	while(ISBLANK(*s))
	    s++;

	/* Hour:minutes */
	if(*s != '\0')
	{
	    /* Get hour */
	    if(*s == '0')
		s++;
	    mtime_buf.tm_hour = (int)(ATOI(s) - 1);
	    while((*s != ':') && (*s != '\0'))
		s++;
	    if(*s == ':')
		s++;

	    /* Get minutes */
	    if(*s == '0')
		s++;
	    mtime_buf.tm_min = (int)ATOI(s);
	    while(!ISBLANK(*s) && (*s != '\0'))
		s++;
	}

	/* Parse time compoents and generate system time since we now
	 * have enough information to do that
	 */
	obj->access_time = (gulong)mktime(&mtime_buf);
	obj->modify_time = obj->access_time;
	obj->change_time = obj->access_time;

	while(ISBLANK(*s))
	    s++;

	/* CRC */
	if(*s != '\0')
	{
	    gint len;
	    const gchar *s_start = s;

	    while(!ISBLANK(*s) && (*s != '\0'))
		s++;

	    len = (gint)(s - s_start);

	    g_free(obj->crc);
	    obj->crc = (gchar *)g_malloc((len + 1) * sizeof(gchar));
	    if(len > 0)
		memcpy(obj->crc, s_start, len);
	    obj->crc[len] = '\0';
	}
	while(ISBLANK(*s))
	    s++;

	/* Full Path & Name */
	if(*s != '\0')
	{
	    gint len;
	    const gchar *s_start = s;

	    while(!ISSPACE(*s) && (*s != '\0'))
		s++;

	    len = (gint)(s - s_start);

	    /* Get full path */
	    g_free(obj->full_path);
	    obj->full_path = (gchar *)g_malloc(
		(len + 1) * sizeof(gchar)
	    );
	    if(len > 0)
		memcpy(obj->full_path, s_start, len);
	    obj->full_path[len] = '\0';

	    /* Determine object type from the full path, if it has a
	     * tailing deliminator then it is a directory, otherwise it
	     * is a file
	     */
	    if(len > 1)
	    {
		switch(obj->full_path[len - 1])
		{
		  case G_DIR_SEPARATOR:
		    obj->type = EDV_OBJECT_TYPE_DIRECTORY;
		    break;
		  case '@':
		    obj->type = EDV_OBJECT_TYPE_LINK;
		    break;
		  default:
		    obj->type = EDV_OBJECT_TYPE_FILE;
		    break;
		}
	    }
	    else
	    {
		obj->type = EDV_OBJECT_TYPE_FILE;
	    }

	    /* Strip tailing deliminators */
	    StripPath((char *)obj->full_path);

	    /* Get name from path */
	    g_free(obj->name);
	    obj->name = STRDUP(g_basename(obj->full_path));
	}
	while(ISSPACE(*s))
	    s++;

	/* The PKZip format does not support permissions */
	obj->permissions = 0;

}
#endif	/* !HAVE_LIBZIP */


/*
 *	Get the stats for the object specified by path in the ARJ
 *	archive.
 *
 *	Inputs assumed valid.
 */
static edv_archive_object_struct *EDVArchStatARJ(
	edv_core_struct *core,
	const gchar *arch_obj, const gchar *path
)
{
	const gchar *prog_arj = CFGItemListGetValueS(
	    core->cfg_list, EDV_CFG_PARM_PROG_ARJ
	);
	FILE *fp;
	gint p;
	gchar *cmd, *stdout_path, *stderr_path;
	edv_archive_object_struct *obj = NULL;

	/* Format the archive list output command */
	cmd = g_strdup_printf(
	    "\"%s\" v -i -y \"%s\" \"%s\"",
	    prog_arj, arch_obj, path
	);
	if(cmd == NULL)
	    return(obj);

	/* Generate the output file paths */
	stdout_path = EDVTmpName(NULL);
	stderr_path = EDVTmpName(NULL);

	/* Execute the archive list output command */
	p = (gint)ExecBOE(
	    (const char *)cmd,
	    (const char *)stdout_path,
	    (const char *)stderr_path
	);
	g_free(cmd);
	if(p <= 0)
	{
	    g_free(stdout_path);
	    g_free(stderr_path);
	    return(obj);
	}

	/* Open the output file for reading */
	fp = fopen((const char *)stdout_path, "rb");
	if(fp != NULL)
	{
	    gboolean	got_header = FALSE;
	    gint i;
	    const gchar *delim_header = "------------";
	    gchar *s, buf[10000];

	    /* Read past the header or to the end of file */
	    for(i = 0; TRUE; i++)
	    {
		if(fgets(buf, sizeof(buf), fp) != NULL)
		    buf[sizeof(buf) - 1] = '\0';
		else
		    break;

		if(strpfx((const char *)buf, (const char *)delim_header))
		{
		    got_header = TRUE;
		    break;
		}
	    }

	    /* Able to read past the header? */
	    if(got_header ? (fgets(buf, sizeof(buf), fp) != NULL) : FALSE)
	    {
		gchar *complete_line;

		buf[sizeof(buf) - 1] = '\0';

		s = buf;
		while(ISBLANK(*s))
		    s++;

		/* Read a total of 4 lines (3 more) and concat them
		 * togeather
		 */
		complete_line = STRDUP(s);
		for(i = 0; i < 3; i++)
		{
		    if(fgets(buf, sizeof(buf), fp) != NULL)
		    {
			buf[sizeof(buf) - 1] = '\0';
			complete_line = G_STRCAT(complete_line, buf);
		    }
		    else
		    {
			break;
		    }
		}

		/* Create a new archive object structure and parse the
		 * loaded line to it
		 */
		obj = EDVArchObjectNew();
		EDVArchStatParseLineARJ(obj, complete_line);

		g_free(complete_line);
	    }

	    fclose(fp);
	}

	/* Delete the output files */
	UNLINK(stdout_path);
	UNLINK(stderr_path);

	g_free(stdout_path);
	g_free(stderr_path);

	return(obj);
}

/*
 *	Get stats for the object in the LHA archive.
 *
 *	Inputs assumed valid.
 */
static edv_archive_object_struct *EDVArchStatLHA(
	edv_core_struct *core,
	const gchar *arch_obj, const gchar *path
)
{
	const gchar *prog_lha = CFGItemListGetValueS(
	    core->cfg_list, EDV_CFG_PARM_PROG_LHA
	);
	FILE *fp;
	gint p;
	gchar *cmd, *stdout_path, *stderr_path;
	edv_archive_object_struct *obj = NULL;

	/* Format the archive list output command */
	cmd = g_strdup_printf(
	    "\"%s\" v \"%s\"",
	    prog_lha, arch_obj
	);
	if(cmd == NULL)
	    return(obj);

	/* Generate the output file paths */
	stdout_path = EDVTmpName(NULL);
	stderr_path = EDVTmpName(NULL);

	/* Execute the archive list output command */
	p = (gint)ExecBOE(
	    (const char *)cmd,
	    (const char *)stdout_path,
	    (const char *)stderr_path
	);
	g_free(cmd);
	if(p <= 0)
	{
	    g_free(stdout_path);
	    g_free(stderr_path);
	    return(obj);
	}

	/* Open the output file for reading */
	fp = fopen((const char *)stdout_path, "rb");
	if(fp != NULL)
	{
	    gboolean got_header = FALSE;
	    gint i;
	    time_t cur_time = time(NULL);
	    gchar *s;
	    const gchar *delim_header = "----------";
	    gchar buf[10000];
	    const struct tm *tm_ptr;
	    struct tm cur_mtime_buf;


	    /* Get current time */
	    tm_ptr = localtime(&cur_time);
	    if(tm_ptr != NULL)
		memcpy(&cur_mtime_buf, tm_ptr, sizeof(struct tm));
	    else
		memset(&cur_mtime_buf, 0x00, sizeof(struct tm));


	    /* Read past the header or to the end of file */
	    for(i = 0; TRUE; i++)
	    {
		if(fgets(buf, sizeof(buf), fp) != NULL)
		    buf[sizeof(buf) - 1] = '\0';
		else
		    break;

		if(strpfx((const char *)buf, (const char *)delim_header))
		{
		    got_header = TRUE;
		    break;
		}
	    }

	    /* Able to read past the header? */
	    if(got_header ? (fgets(buf, sizeof(buf), fp) != NULL) : FALSE)
	    {
		buf[sizeof(buf) - 1] = '\0';

		s = buf;

		/* Seek paste spaces */
		while(ISBLANK(*s))
		    s++;

		/* Create a new archive object structure and parse the
		 * loaded line to it
		 */
		obj = EDVArchObjectNew();
		EDVArchStatParseLineLHA(obj, s, &cur_mtime_buf);
	    }

	    fclose(fp);
	}

	/* Delete the output files */
	UNLINK(stdout_path);
	UNLINK(stderr_path);

	g_free(stdout_path);
	g_free(stderr_path);

	return(obj);
}

/*
 *	Get stats for the object in the RAR archive.
 *
 *	Inputs assumed valid.
 */
static edv_archive_object_struct *EDVArchStatRAR(
	edv_core_struct *core,
	const gchar *arch_obj, const gchar *path
)
{
	const gchar *prog_rar = CFGItemListGetValueS(
	    core->cfg_list, EDV_CFG_PARM_PROG_RAR
	);
	FILE *fp;
	gint p;
	gchar *cmd, *stdout_path, *stderr_path;
	edv_archive_object_struct *obj = NULL;

	/* Format the archive list output command */
	cmd = g_strdup_printf(
	    "\"%s\" v -y -c- \"%s\" \"%s\"",
	    prog_rar, arch_obj, path
	);
	if(cmd == NULL)
	    return(obj);

	/* Generate the output file paths */
	stdout_path = EDVTmpName(NULL);
	stderr_path = EDVTmpName(NULL);

	/* Execute the archive list output command */
	p = (gint)ExecBOE(
	    (const char *)cmd,
	    (const char *)stdout_path,
	    (const char *)stderr_path
	);
	g_free(cmd);
	if(p <= 0)
	{
	    g_free(stdout_path);
	    g_free(stderr_path);
	    return(obj);
	}

	/* Open the output file for reading */
	fp = fopen((const char *)stdout_path, "rb");
	if(fp != NULL)
	{
	    gboolean got_header = FALSE;
	    gint i;
	    const gchar *delim_header = "------------";
	    gchar *s, buf[10000];

	    /* Read past the header or to the end of file */
	    for(i = 0; TRUE; i++)
	    {
		if(fgets(buf, sizeof(buf), fp) != NULL)
		    buf[sizeof(buf) - 1] = '\0';
		else
		    break;

		if(strpfx((const char *)buf, (const char *)delim_header))
		{
		    got_header = TRUE;
		    break;
		}
	    }

	    /* Able to read past the header? */
	    if(got_header ? (fgets(buf, sizeof(buf), fp) != NULL) : FALSE)
	    {
		gchar *complete_line;

		buf[sizeof(buf) - 1] = '\0';

		s = buf;
		while(ISBLANK(*s))
		    s++;

		/* Read a total of 2 lines (1 more) and concat them
		 * togeather
		 */
		complete_line = STRDUP(s);
		for(i = 0; i < 1; i++)
		{
		    if(fgets(buf, sizeof(buf), fp) != NULL)
		    {
			buf[sizeof(buf) - 1] = '\0';
			complete_line = G_STRCAT(complete_line, buf);
		    }
		    else
		    {
			break;
		    }
		}

		/* Create a new archive object structure and parse the
		 * loaded line to it
		 */
		obj = EDVArchObjectNew();
		EDVArchStatParseLineRAR(obj, complete_line);

		g_free(complete_line);
	    }

	    fclose(fp);
	}

	/* Delete the output files */
	UNLINK(stdout_path);
	UNLINK(stderr_path);

	g_free(stdout_path);
	g_free(stderr_path);

	return(obj);
}

/*
 *	Get stats for the object in the RedHat Package Manager
 *	package.
 *
 *	Inputs assumed valid.
 */
static edv_archive_object_struct *EDVArchStatRPM(
	edv_core_struct *core,
	const gchar *arch_obj, const gchar *path
)
{
	const gchar *prog_rpm = CFGItemListGetValueS(
	    core->cfg_list, EDV_CFG_PARM_PROG_RPM
	);
	FILE *fp;
	gint p;
	gchar *cmd, *stdout_path, *stderr_path;
	edv_archive_object_struct *obj = NULL;

	/* Format the archive list output command */
	cmd = g_strdup_printf(
	    "\"%s\" -q -l -p -v \"%s\"",
	    prog_rpm, arch_obj
	);
	if(cmd == NULL)
	    return(obj);

	/* Generate the output file paths */
	stdout_path = EDVTmpName(NULL);
	stderr_path = EDVTmpName(NULL);

	/* Execute the archive list output command */
	p = (gint)ExecBOE(
	    (const char *)cmd,
	    (const char *)stdout_path,
	    (const char *)stderr_path
	);
	g_free(cmd);
	if(p <= 0)
	{
	    g_free(stdout_path);
	    g_free(stderr_path);
	    return(obj);
	}

	/* Open the output file for reading */
	fp = fopen((const char *)stdout_path, "rb");
	if(fp != NULL)
	{
	    gint i;
	    gchar *s, *s2;
	    gchar buf[10000];

	    /* Begin reading each line and get stats of the line that
	     * contains the given path
	     */
	    while(fgets(buf, sizeof(buf), fp) != NULL)
	    {
		buf[sizeof(buf) - 1] = '\0';

		s = buf;

		/* Skip initial spaces */
		while(ISBLANK(*s))
		    s++;

		/* Skip no more than 8 fields to position s2 at the
		 * file name argument
		 */
		s2 = s;
		for(i = 0; i < 8; i++)
		{
		    while(!ISBLANK(*s2) && (*s2 != '\0'))
			s2++;
		    while(ISBLANK(*s2))
			s2++;
		}

		/* Is this the object we want to obtain the stats for? */
		if(strpfx(s2, path))
		{
		    /* Create a new archive object structure and parse
		     * the loaded line to it
		     */
		    obj = EDVArchObjectNew();
		    EDVArchStatParseLineRPM(obj, s);
		    break;
		}
	    }

	    fclose(fp);
	}

	/* Delete the output files */
	UNLINK(stdout_path);
	UNLINK(stderr_path);

	g_free(stdout_path);
	g_free(stderr_path);

	return(obj);
}

/*
 *	Get stats for the object in the Tape Archive.
 *
 *	Inputs assumed valid.
 */
static edv_archive_object_struct *EDVArchStatTar(
	edv_core_struct *core,
	const gchar *arch_obj, const gchar *path,
	gboolean is_compress_compressed,
	gboolean is_gzip_compressed,
	gboolean is_bzip2_compressed
)
{
	const gchar *prog_tar = CFGItemListGetValueS(
	    core->cfg_list, EDV_CFG_PARM_PROG_TAR
	);
	const gchar *prog_bunzip2 = CFGItemListGetValueS(
	    core->cfg_list, EDV_CFG_PARM_PROG_BUNZIP2
	);
	FILE *fp;
	gint p;
	gchar *cmd, *stdout_path, *stderr_path;
	edv_archive_object_struct *obj = NULL;

	/* Format the archive list output command */
	if(is_compress_compressed)
	    cmd = g_strdup_printf(
		"\"%s\" -Z -t -v -f \"%s\" \"%s\"",
		prog_tar, arch_obj, path
	    );
	else if(is_gzip_compressed)
	    cmd = g_strdup_printf(
		"\"%s\" -z -t -v -f \"%s\" \"%s\"",
		prog_tar, arch_obj, path
	    );
	else if(is_bzip2_compressed)
	    cmd = g_strdup_printf(
		"\"%s\" \"--use-compress-program=%s\" -t -v -f \"%s\" \"%s\"",
		prog_tar, prog_bunzip2, arch_obj, path
	    );
	else
	    cmd = g_strdup_printf(
		"\"%s\" -t -v -f \"%s\" \"%s\"",
		prog_tar, arch_obj, path
	    );
	if(cmd == NULL)
	    return(obj);

	/* Generate the output file paths */
	stdout_path = EDVTmpName(NULL);
	stderr_path = EDVTmpName(NULL);

	/* Execute the archive list output command */
	p = (gint)ExecBOE(
	    (const char *)cmd,
	    (const char *)stdout_path,
	    (const char *)stderr_path
	);
	g_free(cmd);
	if(p <= 0)
	{
	    g_free(stdout_path);
	    g_free(stderr_path);
	    return(obj);
	}

	/* Open the output file for reading */
	fp = fopen((const char *)stdout_path, "rb");
	if(fp != NULL)
	{
	    gchar *s, buf[10000];

	    if(fgets(buf, sizeof(buf), fp) != NULL)
	    {
		buf[sizeof(buf) - 1] = '\0';

		s = buf;

		/* Skip initial spaces */
		while(ISBLANK(*s))
		    s++;

		/* Create a new archive object and parse the loaded
		 * line to it
		 */
		obj = EDVArchObjectNew();
		EDVArchStatParseLineTar(obj, buf);
	    }

	    fclose(fp);
	}

	/* Delete the output files */
	UNLINK(stdout_path);
	UNLINK(stderr_path);

	g_free(stdout_path);
	g_free(stderr_path);

	return(obj);
}

/*
 *	Get stats for the object in the PKZip archive.
 *
 *	Inputs assumed valid.
 */
static edv_archive_object_struct *EDVArchStatZip(
	edv_core_struct *core,
	const gchar *arch_obj, const gchar *path
)
{
#ifdef HAVE_LIBZIP
	int zip_error;
	struct zip *archive;
	struct zip_stat zip_stat_buf;
	gint i;
	edv_archive_object_struct *obj;

	/* Open the PKZip archive */
	archive = zip_open(arch_obj, 0, &zip_error);
	if(archive == NULL)
	{
	    const gint error_code = (gint)errno;
	    zip_error_to_str(
		last_error_buf, sizeof(last_error_buf),
		zip_error, error_code
	    );
	    last_error = last_error_buf;
	    return(NULL);
	}

	/* Find the object in the PKZip archive by the specified path */
	i = zip_name_locate(archive, path, 0);
	if(i < 0)
	{
	    /* Object does not exist in the PKZip archive */
	    zip_close(archive);
	    return(NULL);
	}

	/* Get the stats for this object in the PKZip archive */
	if(zip_stat_index(archive, i, 0, &zip_stat_buf))
	{
	    int error_code;
	    zip_error_get(archive, &zip_error, &error_code);
	    zip_error_to_str(
		last_error_buf, sizeof(last_error_buf),
		zip_error, error_code
	    );
	    last_error = last_error_buf;
	    zip_close(archive);
	    return(NULL);
	}

	/* Create a new archive object stats structure and set its
	 * values to the values obtained from the PKZip archive's
	 * object stats
	 */
	obj = EDVArchObjectNew();
	if(obj != NULL)
	{
	    gchar *full_path = STRDUP(zip_stat_buf.name);
	    const gint len = STRLEN(full_path);

	    /* Determine the type by the last character of the name */
	    switch((len > 1) ? full_path[len - 1] : '\0')
	    {
	      case G_DIR_SEPARATOR:
		obj->type = EDV_OBJECT_TYPE_DIRECTORY;
		full_path[len - 1] = '\0';
		break;
	      case '@':
		obj->type = EDV_OBJECT_TYPE_LINK;
		full_path[len - 1] = '\0';
		break;
	      default:
		obj->type = EDV_OBJECT_TYPE_FILE;
		break;
	    }

	    obj->full_path = STRDUP(full_path);
	    obj->name = STRDUP(g_basename(full_path));

	    obj->permissions = 0;	/* Not supported by the PKZip format */

	    obj->access_time = (gulong)zip_stat_buf.mtime;
	    obj->modify_time = (gulong)zip_stat_buf.mtime;
	    obj->change_time = (gulong)zip_stat_buf.mtime;

	    obj->size = (gulong)zip_stat_buf.size;
	    obj->compressed_size = (gulong)zip_stat_buf.comp_size;

	    obj->compression_ratio = (obj->size > 0l) ?
		(1.0f - ((gfloat)obj->compressed_size / (gfloat)obj->size)) :
		0.0f;
	    if(obj->compression_ratio < 0.0f)
		obj->compression_ratio = 0.0f;

	    switch(zip_stat_buf.comp_method)
	    {
#ifdef ZIP_CM_STORE
	      case ZIP_CM_STORE:
		obj->method = STRDUP("Store");
		break;
#endif
#ifdef ZIP_CM_SHRINK
	      case ZIP_CM_SHRINK:
		obj->method = STRDUP("Shrink");
		break;
#endif
#ifdef ZIP_CM_REDUCE_1
	      case ZIP_CM_REDUCE_1:
		obj->method = STRDUP("Reduce 1");
		break;
#endif
#ifdef ZIP_CM_REDUCE_2
	      case ZIP_CM_REDUCE_2:
		obj->method = STRDUP("Reduce 2");
		break;
#endif
#ifdef ZIP_CM_REDUCE_3
	      case ZIP_CM_REDUCE_3:
		obj->method = STRDUP("Reduce 3");
		break;
#endif
#ifdef ZIP_CM_REDUCE_4
	      case ZIP_CM_REDUCE_4:
		obj->method = STRDUP("Reduce 4");
		break;
#endif
#ifdef ZIP_CM_IMPLODE
	      case ZIP_CM_IMPLODE:
		obj->method = STRDUP("Implode");
		break;
#endif
#ifdef ZIP_CM_DEFLATE
	      case ZIP_CM_DEFLATE:
		obj->method = STRDUP("Deflate");
		break;
#endif
#ifdef ZIP_CM_DEFLATE64
	      case ZIP_CM_DEFLATE64:
		obj->method = STRDUP("Deflate 64");
		break;
#endif
#ifdef ZIP_CM_PKWARE_IMPLODE
	      case ZIP_CM_PKWARE_IMPLODE:
		obj->method = STRDUP("PKWare Implode");
		break;
#endif
	      default:
		obj->method = STRDUP("Other");
		break;
	    }

	    if(zip_stat_buf.encryption_method != ZIP_EM_NONE)
	    {
		switch(zip_stat_buf.encryption_method)
		{
#ifdef ZIP_EM_TRAD_PKWARE
		  case ZIP_EM_TRAD_PKWARE:
		    obj->encryption = STRDUP("PKWare Encryption");
		    break;
#endif
#ifdef ZIP_EM_DES
		  case ZIP_EM_DES:
		    obj->encryption = STRDUP("DES Encryption");
		    break;
#endif
#ifdef ZIP_EM_RC2_OLD
		  case ZIP_EM_RC2_OLD:
		    obj->encryption = STRDUP("RC2 (Old) Encryption");
		    break;
#endif
#ifdef ZIP_EM_3DES_168
		  case ZIP_EM_3DES_168:
		    obj->encryption = STRDUP("3DES 168 Encryption");
		    break;
#endif
#ifdef ZIP_EM_3DES_112
		  case ZIP_EM_3DES_112:
		    obj->encryption = STRDUP("3DES 112 Encryption");
		    break;
#endif
#ifdef ZIP_EM_AES_128
		  case ZIP_EM_AES_128:
		    obj->encryption = STRDUP("AES 128 Encryption");
		    break;
#endif
#ifdef ZIP_EM_AES_192
		  case ZIP_EM_AES_192:
		    obj->encryption = STRDUP("AES 192 Encryption");
		    break;
#endif
#ifdef ZIP_EM_AES_256
		  case ZIP_EM_AES_256:
		    obj->encryption = STRDUP("AES 256 Encryption");
		    break;
#endif
#ifdef ZIP_EM_RC2
		  case ZIP_EM_RC2:
		    obj->encryption = STRDUP("RC2 Encryption");
		    break;
#endif
#ifdef ZIP_EM_RC4
		  case ZIP_EM_RC4:
		    obj->encryption = STRDUP("RC4 Encryption");
		    break;
#endif
		  default:
		    obj->encryption = STRDUP("Encryption");
		    break;
		}
	    }

	    obj->crc = g_strdup_printf(
		"%.8X",
		(guint32)zip_stat_buf.crc
	    );

	    g_free(full_path);
	}

	/* Close the PKZip archive */
	zip_close(archive);

	/* Check the PKZip archive for errors */
	archive = zip_open(arch_obj, ZIP_CHECKCONS, &zip_error);
	if(archive == NULL)
	{
	    const gint error_code = (gint)errno;
	    zip_error_to_str(
		last_error_buf, sizeof(last_error_buf),
		zip_error, error_code
	    );
	    last_error = last_error_buf;
	    return(obj);
	}
	zip_close(archive);

	return(obj);
#else
	const gchar *prog_unzip = CFGItemListGetValueS(
	    core->cfg_list, EDV_CFG_PARM_PROG_UNZIP
	);
	FILE *fp;
	gint p;
	gchar *cmd, *stdout_path, *stderr_path;
	edv_archive_object_struct *obj = NULL;

	/* Format the archive list output command */
	cmd = g_strdup_printf(
	    "\"%s\" -l -v \"%s\" \"%s\"",
	    prog_unzip, arch_obj, path
	);
	if(cmd == NULL)
	    return(obj);

	/* Generate the output file paths */
	stdout_path = EDVTmpName(NULL);
	stderr_path = EDVTmpName(NULL);

	/* Execute the archive list output command */
	p = (gint)ExecBOE(
	    (const char *)cmd,
	    (const char *)stdout_path,
	    (const char *)stderr_path
	);
	g_free(cmd);
	if(p <= 0)
	{
	    g_free(stdout_path);
	    g_free(stderr_path);
	    return(obj);
	}

	/* Open the output file for reading */
	fp = fopen((const char *)stdout_path, "rb");
	if(fp != NULL)
	{
	    gboolean got_header = FALSE;
	    gint i;
	    const gchar *delim_header = "--------";
	    gchar *s, buf[10000];

	    /* Read past the header or to the end of file */
	    for(i = 0; TRUE; i++)
	    {
		if(fgets(buf, sizeof(buf), fp) != NULL)
		    buf[sizeof(buf) - 1] = '\0';
		else
		    break;

		if(strpfx((const char *)buf, (const char *)delim_header))
		{
		    got_header = TRUE;
		    break;
		}
	    }

	    /* Able to read past the header? */
	    if(got_header ? (fgets(buf, sizeof(buf), fp) != NULL) : FALSE)
	    {
		buf[sizeof(buf) - 1] = '\0';

		s = buf;

		/* Seek past spaces */
		while(ISBLANK(*s))
		    s++;

		/* Create a new archive object structure and parse the
		 * loaded line to it
		 */
		obj = EDVArchObjectNew();
		EDVArchStatParseLineZip(obj, s);
	    }

	    fclose(fp);
	}

	/* Delete the output files */
	UNLINK(stdout_path);
	UNLINK(stderr_path);

	g_free(stdout_path);
	g_free(stderr_path);

	return(obj);
#endif	/* HAVE_LIBZIP */
}

/*
 *      Fetches statistics from the archive specified by arch_obj for
 *	the object in the archive specified by path.
 *
 *	The returned stats must be deallocated by the calling function.
 *
 *	Can return NULL on error.
 */
edv_archive_object_struct *EDVArchStat(
	edv_core_struct *core,
	const gchar *arch_obj, const gchar *path,
	const gchar *password
)
{
	const gchar *arch_name;
	edv_archive_object_struct *obj = NULL;
	struct stat stat_buf;

	last_error = NULL;
	last_error = NULL;

	if((core == NULL) || STRISEMPTY(arch_obj) || STRISEMPTY(path))
	    return(obj);

	arch_name = g_basename(arch_obj);

	/* Get archive stats and making sure it is a file */
	if(stat((const char *)arch_obj, &stat_buf))
	    return(obj);
#ifdef S_ISREG
	if(!S_ISREG(stat_buf.st_mode))
	    return(obj);
#endif

	/* Get stats by the archive's extension */

	/* ARJ Archive */
	if(EDVIsExtension(arch_name, ".arj"))
	{
	    obj = EDVArchStatARJ(
		core, arch_obj, path
	    );
	}
	/* LHA Archive */
	else if(EDVIsExtension(arch_name, ".lha"))
	{
	    obj = EDVArchStatLHA(
		core, arch_obj, path
	    );
	}
	/* RAR Archive */
	else if(EDVIsExtension(arch_name, ".rar"))
	{
	    obj = EDVArchStatRAR(
		core, arch_obj, path
	    );
	}
	/* RedHat Package Manager Package */
	else if(EDVIsExtension(arch_name, ".rpm"))
	{
	    obj = EDVArchStatRPM(
		core, arch_obj, path
	    );
	}
	/* Tape Archive (Compressed) */
	else if(EDVIsExtension(arch_name, ".tar.Z"))
	{
	    obj = EDVArchStatTar(
		core, arch_obj, path,
		TRUE,		/* Is compress compressed */
		FALSE,		/* Not gzip compressed */
		FALSE		/* Not bzip2 compressed */
	    );
	}
	/* Tape Archive (GZip) */
	else if(EDVIsExtension(arch_name, ".tgz .tar.gz"))
	{
	    obj = EDVArchStatTar(
		core, arch_obj, path,
		FALSE,		/* Not compress compressed */
		TRUE,		/* Is gzip compressed */
		FALSE		/* Not bzip2 compressed */
	    );
	}
	/* Tape Archive (BZip2) */
	else if(EDVIsExtension(arch_name, ".tar.bz2"))
	{
	    obj = EDVArchStatTar(
		core, arch_obj, path,
		FALSE,		/* Not compress compressed */
		FALSE,		/* Not gzip compressed */
		TRUE		/* Is bzip2 compressed */
	    );
	}
	/* Tape Archive */
	else if(EDVIsExtension(arch_name, ".tar"))
	{
	    obj = EDVArchStatTar(
		core, arch_obj, path,
		FALSE,		/* Not compress compressed */
		FALSE,		/* Not gzip compressed */
		FALSE		/* Not bzip2 compressed */
	    );
	}
	/* PKZip Archive */
	else if(EDVIsExtension(arch_name, ".xpi .zip"))
	{
	    obj = EDVArchStatZip(
		core, arch_obj, path
	    );
	}

	return(obj);
}


/*
 *      Returns a listing of all objects in the Arj archive.
 */
static GList *EDVArchStatListARJ(EDV_ARCH_STAT_LIST_ANY_PROTOTYPE)
{
	const gchar *prog_arj = CFGItemListGetValueS(
	    core->cfg_list, EDV_CFG_PARM_PROG_ARJ
	);
	FILE *fp;
	gint p;
	gchar *cmd, *stdout_path, *stderr_path;
	GList *obj_list = NULL;
	edv_archive_object_struct *obj;

	/* Format the archive list output command */
	cmd = g_strdup_printf(
	    "\"%s\" v -i -y \"%s\"",
	    prog_arj, arch_obj
	);
	if(cmd == NULL)
	    return(obj_list);

	/* Generate the output file paths */
	stdout_path = EDVTmpName(NULL);
	stderr_path = EDVTmpName(NULL);

	/* Execute the archive list output command */
	p = (gint)ExecBOE(
	    (const char *)cmd,
	    (const char *)stdout_path,
	    (const char *)stderr_path
	);
	g_free(cmd);
	if(p <= 0)
	{
	    g_free(stdout_path);
	    g_free(stderr_path);
	    return(obj_list);
	}

	/* Open the output file for reading */
	fp = fopen((const char *)stdout_path, "rb");
	if(fp != NULL)
	{
	    gint i;
	    gchar *s, *vpath;
	    const gchar *delim_header = "------------";
	    gchar buf[10000];


	    /* Read past the header or to the end of file */
	    for(i = 0; TRUE; i++)
	    {
		if(fgets(buf, sizeof(buf), fp) != NULL)
		    buf[sizeof(buf) - 1] = '\0';
		else
		    break;

		if(strpfx((const char *)buf, (const char *)delim_header))
		    break;
	    }

	    /* Begin reading each set of four lines describing each
	     * object in the ARJ archive
	     */
	    while(fgets(buf, sizeof(buf), fp) != NULL)
	    {
		/* Report progress? */
		if(progress_cb != NULL)
		{
		    if(progress_cb(
			arch_obj,
			NULL,
			0l, 0l,
			progress_data
		    ))
			break;
		}

		buf[sizeof(buf) - 1] = '\0';

		s = buf;

		/* Skip initial spaces */
		while(ISBLANK(*s))
		    s++;

		/* Ending deliminator reached? If so do not parse this
		 * line or any subsequent lines
		 */
		if(*s == '-')
		    break;

		/* Seek vpath to the full path value segment in s,
		 * skipping 1 value in s
		 */
		vpath = s;
		for(i = 0; i < 1; i++)
		{
		    while(!ISBLANK(*vpath) && (*vpath != '\0'))
			vpath++;
		    while(ISBLANK(*vpath))
			vpath++;
		}

		/* Copy the path value and cap the first newline
		 * character
		 */
		vpath = STRDUP(vpath);
		EDVArchStatCapNewline(vpath);

		/* Check if the path matches any of the paths in
		 * the paths list or filter
		 */
		if(EDVArchStatFilter(
		    paths_list,
#if defined(HAVE_REGEX)
		    regex_filter,
#else
		    filter,
#endif
		    vpath
		))
		{
		    /* Read a total of 4 lines (3 more) and concat
		     * them togeather
		     */
		    gchar *complete_line = STRDUP(s);
		    for(i = 0; i < 3; i++)
		    {
			if(fgets(buf, sizeof(buf), fp) != NULL)
			{
			    buf[sizeof(buf) - 1] = '\0';
			    complete_line = G_STRCAT(complete_line, buf);
			}
			else
			{
			    break;
			}
		    }

		    /* Create a new archive object structure and parse
		     * the loaded line to it
		     */
		    obj = EDVArchObjectNew();
		    EDVArchStatParseLineARJ(obj, complete_line);

		    g_free(complete_line);

		    /* Append this object to the list */
		    obj_list = g_list_append(obj_list, obj);
		}

		g_free(vpath);
	    }

	    fclose(fp);
	}

	/* Check for errors */
	fp = fopen((const char *)stderr_path, "rb");
	if(fp != NULL)
	{
	    gchar *s, *s2, buf[10000];

	    while(fgets(buf, sizeof(buf), fp) != NULL)
	    {
		buf[sizeof(buf) - 1] = '\0';

		s = buf;
		while(ISSPACE(*s))
		    s++;

		s2 = (gchar *)strpbrk((char *)s, "\n\r");
		if(s2 != NULL)
		    *s2 = '\0';

		if(!STRISEMPTY(s))
		{
		    EDVArchStatCopyErrorMessage(s);
		    break;
		}
	    }

	    fclose(fp);
	}

	/* Delete the output files */
	UNLINK(stdout_path);
	UNLINK(stderr_path);

	g_free(stdout_path);
	g_free(stderr_path);

	return(obj_list);
}

/*
 *      Returns a listing of all objects in the LHA Archive.
 */
static GList *EDVArchStatListLHA(EDV_ARCH_STAT_LIST_ANY_PROTOTYPE)
{
	const gchar *prog_lha = CFGItemListGetValueS(
	    core->cfg_list, EDV_CFG_PARM_PROG_LHA
	);
	FILE *fp;
	gint p;
	gchar *cmd, *stdout_path, *stderr_path;
	GList *obj_list = NULL;
	edv_archive_object_struct *obj;

	/* Format the archive list output command */
	cmd = g_strdup_printf(
	    "\"%s\" v \"%s\"",
	    prog_lha, arch_obj
	);
	if(cmd == NULL)
	    return(obj_list);

	/* Generate the output file paths */
	stdout_path = EDVTmpName(NULL);
	stderr_path = EDVTmpName(NULL);

	/* Execute the archive list output command */
	p = (gint)ExecBOE(
	    (const char *)cmd,
	    (const char *)stdout_path,
	    (const char *)stderr_path
	);
	g_free(cmd);
	if(p <= 0)
	{
	    g_free(stdout_path);
	    g_free(stderr_path);
	    return(obj_list);
	}

	/* Open the output file for reading */
	fp = fopen((const char *)stdout_path, "rb");
	if(fp != NULL)
	{
	    gint i;
	    time_t cur_time = time(NULL);
	    gchar *s, *vpath;
	    const gchar *delim_header = "----------",
			*delim_footer = "----------";
	    gchar buf[10000];
	    const struct tm *tm_ptr;
	    struct tm cur_mtime_buf;


	    /* Get current time */
	    tm_ptr = localtime(&cur_time);
	    if(tm_ptr != NULL)
		memcpy(&cur_mtime_buf, tm_ptr, sizeof(struct tm));
	    else
		memset(&cur_mtime_buf, 0x00, sizeof(struct tm));


	    /* Read past the header or to the end of file */
	    for(i = 0; TRUE; i++)
	    {
		if(fgets(buf, sizeof(buf), fp) != NULL)
		    buf[sizeof(buf) - 1] = '\0';
		else
		    break;

		if(strpfx((const char *)buf, (const char *)delim_header))
		    break;
	    }


	    /* Begin reading each line describing each object in the
	     * LHA archive
	     */
	    while(fgets(buf, sizeof(buf), fp) != NULL)
	    {
		/* Report progress? */
		if(progress_cb != NULL)
		{
		    if(progress_cb(
			arch_obj,
			NULL,
			0l, 0l,
			progress_data
		    ))
			break;
		}

		buf[sizeof(buf) - 1] = '\0';

		/* Footer deliminator reached? */
		if(strpfx((const char *)buf, (const char *)delim_footer))
		    break;

		s = buf;

		/* Skip initial spaces */
		while(ISBLANK(*s))
		    s++;

		/* Seek vpath to the full path value segment in s,
		 * skipping 10 values in s
		 */
		vpath = s;
		for(i = 0; i < 10; i++)
		{
		    while(!ISBLANK(*vpath) && (*vpath != '\0'))
			vpath++;
		    while(ISBLANK(*vpath))
			vpath++;
		}

		/* Copy the path value and cap the first newline
		 * character
		 */
		vpath = STRDUP(vpath);
		EDVArchStatCapNewline(vpath);

		/* Check if the path matches any of the paths in
		 * the paths list or filter
		 */
		if(EDVArchStatFilter(
		    paths_list,
#if defined(HAVE_REGEX)
		    regex_filter,
#else                            
		    filter,
#endif
		    vpath
		))
		{
		    /* Create a new archive object structure and parse
		     * the loaded line to it
		     */
		    obj = EDVArchObjectNew();
		    EDVArchStatParseLineLHA(obj, s, &cur_mtime_buf);

		    /* Append this object to the list */
		    obj_list = g_list_append(obj_list, obj);
		}

		g_free(vpath);
	    }

	    fclose(fp);
	}

	/* Check for errors */
	fp = fopen((const char *)stderr_path, "rb");
	if(fp != NULL)
	{
	    gchar *s, *s2, buf[10000];

	    while(fgets(buf, sizeof(buf), fp) != NULL)
	    {
		buf[sizeof(buf) - 1] = '\0';

		s = buf;
		while(ISSPACE(*s))
		    s++;

		s2 = (gchar *)strpbrk((char *)s, "\n\r");
		if(s2 != NULL)
		    *s2 = '\0';

		if(!STRISEMPTY(s))
		{
		    EDVArchStatCopyErrorMessage(s);
		    break;
		}
	    }

	    fclose(fp);
	}

	/* Delete the output files */
	UNLINK(stdout_path);
	UNLINK(stderr_path);

	g_free(stdout_path);
	g_free(stderr_path);

	return(obj_list);
}

/*
 *      Returns a listing of all objects in the RAR Archive.
 */
static GList *EDVArchStatListRAR(EDV_ARCH_STAT_LIST_ANY_PROTOTYPE)
{
	const gchar *prog_rar = CFGItemListGetValueS(
	    core->cfg_list, EDV_CFG_PARM_PROG_RAR
	);
	FILE *fp;
	gint p;
	gchar *cmd, *stdout_path, *stderr_path;
	GList *obj_list = NULL;
	edv_archive_object_struct *obj;

	/* Format the archive list output command */
	cmd = g_strdup_printf(
	    "\"%s\" v -y -c- \"%s\"",
	    prog_rar, arch_obj
	);
	if(cmd == NULL)
	    return(obj_list);

	/* Generate the output file paths */
	stdout_path = EDVTmpName(NULL);
	stderr_path = EDVTmpName(NULL);

	/* Execute the archive list output command */
	p = (gint)ExecBOE(
	    (const char *)cmd,
	    (const char *)stdout_path,
	    (const char *)stderr_path
	);
	g_free(cmd);
	if(p <= 0)
	{
	    g_free(stdout_path);
	    g_free(stderr_path);
	    return(obj_list);
	}

	/* Open the output file for reading */
	fp = fopen((const char *)stdout_path, "rb");
	if(fp != NULL)
	{
	    gint i;
	    gchar *s, *vpath;
	    const gchar	*delim_header = "------------",
			*delim_footer = "------------";
	    gchar buf[10000];

	    /* Read past the header or to the end of file */
	    for(i = 0; TRUE; i++)
	    {
		if(fgets(buf, sizeof(buf), fp) != NULL)
		    buf[sizeof(buf) - 1] = '\0';
		else
		    break;

		if(strpfx((const char *)buf, (const char *)delim_header))
		    break;
	    }

	    /* Begin reading each set of two lines describing each
	     * object in the RAR archive
	     */
	    while(fgets(buf, sizeof(buf), fp) != NULL)
	    {
		/* Report progress? */
		if(progress_cb != NULL)
		{
		    if(progress_cb(
			arch_obj,
			NULL,
			0l, 0l,
			progress_data
		    ))
			break;
		}

		buf[sizeof(buf) - 1] = '\0';

		/* Footer deliminator reached? */
		if(strpfx((const char *)buf, (const char *)delim_footer))
		    break;

		s = buf;

		/* Skip initial spaces */
		while(ISBLANK(*s))
		    s++;

		/* Seek vpath to the full path value segment in s,
		 * skipping 0 values in s
		 */
		vpath = s;
		for(i = 0; i < 0; i++)
		{
		    while(!ISBLANK(*vpath) && (*vpath != '\0'))
			vpath++;
		    while(ISBLANK(*vpath))
			vpath++;
		}

		/* Copy the path value and cap the first newline
		 * character
		 */
		vpath = STRDUP(vpath);
		EDVArchStatCapNewline(vpath);

		/* Check if the path matches any of the paths in
		 * the paths list or filter
		 */
		if(EDVArchStatFilter(
		    paths_list,
#if defined(HAVE_REGEX)
		    regex_filter,
#else                            
		    filter,
#endif
		    vpath
		))
		{
		    /* Read a total of 2 lines (1 more) and concat
		     * them togeather
		     */
		    gchar *complete_line = STRDUP(s);
		    for(i = 0; i < 1; i++)
		    {
			if(fgets(buf, sizeof(buf), fp) != NULL)
			{
			    buf[sizeof(buf) - 1] = '\0';
			    complete_line = G_STRCAT(complete_line, buf);
			}
			else
			{
			    break;
			}
		    }

		    /* Create a new archive object structure and parse
		     * the loaded line to it
		     */
		    obj = EDVArchObjectNew();
		    EDVArchStatParseLineRAR(obj, complete_line);

		    g_free(complete_line);

		    /* Append this object to the list */
		    obj_list = g_list_append(obj_list, obj);
		}

		g_free(vpath);
	    }

	    fclose(fp);
	}

	/* Check for errors */
	fp = fopen((const char *)stderr_path, "rb");
	if(fp != NULL)
	{
	    gchar *s, *s2, buf[10000];

	    while(fgets(buf, sizeof(buf), fp) != NULL)
	    {
		buf[sizeof(buf) - 1] = '\0';

		s = buf;
		while(ISSPACE(*s))
		    s++;

		s2 = (gchar *)strpbrk((char *)s, "\n\r");
		if(s2 != NULL)
		    *s2 = '\0';

		if(!STRISEMPTY(s))
		{
		    EDVArchStatCopyErrorMessage(s);
		    break;
		}
	    }

	    fclose(fp);
	}

	/* Delete the output files */
	UNLINK(stdout_path);
	UNLINK(stderr_path);

	g_free(stdout_path);
	g_free(stderr_path);

	return(obj_list);
}

/*
 *      Returns a listing of all objects in the RPM archive.
 */
static GList *EDVArchStatListRPM(EDV_ARCH_STAT_LIST_ANY_PROTOTYPE)
{
	const gchar *prog_rpm = CFGItemListGetValueS(
	    core->cfg_list, EDV_CFG_PARM_PROG_RPM
	);
	FILE *fp;
	gint p;
	gchar *cmd, *stdout_path, *stderr_path;
	GList *obj_list = NULL;
	edv_archive_object_struct *obj;


	/* Format the archive list output command */
	cmd = g_strdup_printf(
	    "\"%s\" -q -l -p -v \"%s\"",
	    prog_rpm, arch_obj
	);
	if(cmd == NULL)
	    return(obj_list);

	/* Generate the output file paths */
	stdout_path = EDVTmpName(NULL);
	stderr_path = EDVTmpName(NULL);

	/* Execute the archive list output command */
	p = (gint)ExecBOE(
	    (const char *)cmd,
	    (const char *)stdout_path,
	    (const char *)stderr_path
	);
	g_free(cmd);
	if(p <= 0)
	{
	    g_free(stdout_path);
	    g_free(stderr_path);
	    return(obj_list);
	}

	/* Open the output file for reading */
	fp = fopen((const char *)stdout_path, "rb");
	if(fp != NULL)
	{
	    gint i;
	    gchar *s, *vpath;
	    gchar buf[10000];

	    /* Begin reading each line describing each object in the
	     * RedHat Package Manager package
	     */
	    while(fgets(buf, sizeof(buf), fp) != NULL)
	    {
		/* Report progress? */
		if(progress_cb != NULL)
		{
		    if(progress_cb(
			arch_obj,
			NULL,
			0l, 0l,
			progress_data
		    ))
			break;
		}

		buf[sizeof(buf) - 1] = '\0';

		s = buf;

		/* Skip initial spaces */
		while(ISBLANK(*s))
		    s++;

		/* Seek vpath to the full path value segment in s,
		 * skipping 8 values in s
		 */
		vpath = s;
		for(i = 0; i < 8; i++)
		{
		    while(!ISBLANK(*vpath) && (*vpath != '\0'))
			vpath++;
		    while(ISBLANK(*vpath))
			vpath++;
		}

		/* Copy the path value and cap the first newline
		 * character
		 */
		vpath = STRDUP(vpath);
		EDVArchStatCapNewline(vpath);

		/* Check if the path matches any of the paths in
		 * the paths list or filter
		 */
		if(EDVArchStatFilter(
		    paths_list,
#if defined(HAVE_REGEX)
		    regex_filter,
#else                            
		    filter,
#endif
		    vpath
		))
		{
		    /* Create a new archive object structure and parse
		     * the loaded line to it
		     */
		    obj = EDVArchObjectNew();
		    EDVArchStatParseLineRPM(obj, s);

		    /* Append this object to the list */
		    obj_list = g_list_append(obj_list, obj);
		}

		g_free(vpath);
	    }

	    fclose(fp);
	}

	/* Check for errors */
	fp = fopen((const char *)stderr_path, "rb");
	if(fp != NULL)
	{
	    gchar *s, *s2, buf[10000];

	    while(fgets(buf, sizeof(buf), fp) != NULL)
	    {
		buf[sizeof(buf) - 1] = '\0';

		s = buf;
		while(ISSPACE(*s))
		    s++;

		s2 = (gchar *)strpbrk((char *)s, "\n\r");
		if(s2 != NULL)
		    *s2 = '\0';

		if(!STRISEMPTY(s))
		{
		    EDVArchStatCopyErrorMessage(s);
		    break;
		}
	    }

	    fclose(fp);
	}

	/* Delete the output files */
	UNLINK(stdout_path);
	UNLINK(stderr_path);

	g_free(stdout_path);
	g_free(stderr_path);

	return(obj_list);
}

/*
 *	Returns a listing of all objects in the Tar archive.
 */
static GList *EDVArchStatListTar(
	EDV_ARCH_STAT_LIST_ANY_PROTOTYPE,
	const gboolean is_compress_compressed,
	const gboolean is_gzip_compressed,
	const gboolean is_bzip2_compressed
)
{
	const gchar *prog_tar = CFGItemListGetValueS(
	    core->cfg_list, EDV_CFG_PARM_PROG_TAR
	);
	const gchar *prog_bunzip2 = CFGItemListGetValueS(
	    core->cfg_list, EDV_CFG_PARM_PROG_BUNZIP2
	);
	FILE *fp;
	gint p;
	gchar *cmd, *stdout_path, *stderr_path;
	GList *obj_list = NULL;
	edv_archive_object_struct *obj;


	/* Format the archive list output command */
	if(is_compress_compressed)
	    cmd = g_strdup_printf(
		"\"%s\" -Z -t -v -f \"%s\"",
		prog_tar, arch_obj
	    );
	else if(is_gzip_compressed)
	    cmd = g_strdup_printf(
		"\"%s\" -z -t -v -f \"%s\"",
		prog_tar, arch_obj
	    );
	else if(is_bzip2_compressed)
	    cmd = g_strdup_printf(
		"\"%s\" \"--use-compress-program=%s\" -t -v -f \"%s\"",
		prog_tar, prog_bunzip2, arch_obj
	    );
	else
	    cmd = g_strdup_printf(
		"\"%s\" -t -v -f \"%s\"",
		prog_tar, arch_obj
	    );
	if(cmd == NULL)
	    return(obj_list);

	/* Generate the output file paths */
	stdout_path = EDVTmpName(NULL);
	stderr_path = EDVTmpName(NULL);

	/* Execute the archive list output command */
	p = (gint)ExecBOE(
	    (const char *)cmd,
	    (const char *)stdout_path,
	    (const char *)stderr_path
	);
	g_free(cmd);
	if(p <= 0)
	{
	    g_free(stdout_path);
	    g_free(stderr_path);
	    return(obj_list);
	}

/* If listings don't get updated, we may need EDVSyncDisks() here */

	/* Open the output file for reading */
	fp = fopen((const char *)stdout_path, "rb");
	if(fp != NULL)
	{
	    gint i;
	    gchar *s, *vpath;
	    gchar buf[10000];

	    /* Begin reading each line describing each object in the
	     * Tape Archive
	     */
	    while(fgets(buf, sizeof(buf), fp) != NULL)
	    {
		/* Report progress? */
		if(progress_cb != NULL)
		{
		    if(progress_cb(
			arch_obj,
			NULL,
			0l, 0l,
			progress_data
		    ))
			break;
		}

		buf[sizeof(buf) - 1] = '\0';

		s = buf;

		/* Skip initial spaces */
		while(ISBLANK(*s))
		    s++;

		/* Seek vpath to the full path value segment in s,
		 * skipping 5 value in s
		 */
		vpath = s;
		for(i = 0; i < 5; i++)
		{
		    while(!ISBLANK(*vpath) && (*vpath != '\0'))
			vpath++;
		    while(ISBLANK(*vpath))
			vpath++;
		}

		/* Copy the path value and cap the " -> " or the
		 * newline character.
		 */
		vpath = STRDUP(vpath);
		if(vpath != NULL)
		{
		    gchar *s = (gchar *)strstr((char *)vpath, " -> ");
		    if(s == NULL)
			s = (gchar *)strpbrk((char *)vpath, "\n\r");
		    if(s != NULL)
			*s = '\0';
		}

		/* Check if the path matches any of the paths in
		 * the paths list or filter
		 */
		if(EDVArchStatFilter(
		    paths_list,
#if defined(HAVE_REGEX)
		    regex_filter,
#else                            
		    filter,
#endif
		    vpath
		))
		{
		    /* Create a new archive object structure and parse
		     * the loaded line to it
		     */
		    obj = EDVArchObjectNew();
		    EDVArchStatParseLineTar(obj, s);

		    /* Append this object to the list */
		    obj_list = g_list_append(obj_list, obj);
		}

		g_free(vpath);
	    }

	    fclose(fp);
	}

	/* Check for errors */
	fp = fopen((const char *)stderr_path, "rb");
	if(fp != NULL)
	{
	    gchar *s, *s2, buf[10000];

	    while(fgets(buf, sizeof(buf), fp) != NULL)
	    {
		buf[sizeof(buf) - 1] = '\0';

		s = buf;
		while(ISSPACE(*s))
		    s++;

		s2 = (gchar *)strpbrk((char *)s, "\n\r");
		if(s2 != NULL)
		    *s2 = '\0';

		if(!STRISEMPTY(s))
		{
		    EDVArchStatCopyErrorMessage(s);
		    break;
		}
	    }

	    fclose(fp);
	}

	/* Delete the output files */
	UNLINK(stdout_path);
	UNLINK(stderr_path);

	g_free(stdout_path);
	g_free(stderr_path);

	return(obj_list);
}

/*
 *	Returns a listing of all objects in the X Archive.
 */
static GList *EDVArchStatListXAr(EDV_ARCH_STAT_LIST_ANY_PROTOTYPE)
{
#ifdef HAVE_LIBXAR
	time_t t;
	struct tm *tm_local;
	xar_t xar;
	xar_iter_t i1, i2;
	xar_file_t xar_fp;
	gint i, nobjs;
	glong gmt_offset_dst;
	const gchar *key, *val;
	gchar *path;
	GList *obj_list = NULL;
	edv_archive_object_struct *obj;

	/* Open the X Archive for reading */
	xar = xar_open(arch_obj, READ);
	if(xar == NULL)
	{
	    last_error = "Unable to open the X Archive for reading";
	    return(obj_list);
	}

	/* Create a new X Archive object iterator */
	i1 = xar_iter_new(xar);
	if(i1 == 0)
	{
	    xar_close(xar);
	    last_error = "Unable to create a new X Archive iterator";
	    return(obj_list);
	}

	/* Count the number of objects in the X Archive */
	for(xar_fp = xar_file_first(xar, i1), nobjs = 0;
	    xar_fp != NULL;
	    xar_fp = xar_file_next(i1), nobjs++
	);

	/* Initialize time globals timezone & daylight */
	t = time(NULL);
	tm_local = localtime(&t);

	/* Calculate the GMT offset with DST */
	gmt_offset_dst = (glong)(timezone - ((daylight == 1) ? (60 * 60) : 0));

	/* Iterate through each object in the X Archive */
	for(xar_fp = xar_file_first(xar, i1), i = 0;
	    xar_fp != NULL;
	    xar_fp = xar_file_next(i1), i++
	)
	{
	    /* Create a new X Archive property iterator */
	    i2 = xar_iter_new(xar);
	    if(i2 == 0)
		break;

	    /* Get the path of this object within the X Archive */
	    path = (gchar *)xar_get_path(xar_fp);

	    /* Is this object's path not in the list of paths? */
	    if(!EDVArchStatFilter(
		paths_list,
#if defined(HAVE_REGEX)
		regex_filter,
#else
		filter,
#endif
		path
	    ))
	    {
		g_free(path);
		continue;
	    }

	    /* Create a new archive object */
	    obj = EDVArchObjectNew();
	    if(obj == NULL)
		break;

	    obj->full_path = STRDUP(path);
	    obj->name = STRDUP(g_basename(path));

	    g_free(path);

	    /* Iterate through each property and add each property's
	     * value to the archive object
	     */
	    for(key = (const gchar *)xar_prop_first(xar_fp, i2);
		key != NULL;
	        key = (const gchar *)xar_prop_next(i2)
	    )
	    {
		val = NULL;
		xar_prop_get(xar_fp, (const char *)key, (const char **)&val);

		/* Name */
		if(!g_strcasecmp(key, "name"))
		{
		    const gchar *name = (val != NULL) ? val : "";
		    g_free(obj->name);
		    obj->name = STRDUP(name);
		}
		/* Type */
		else if(!g_strcasecmp(key, "type"))
		{
		    const gchar *type = (val != NULL) ? val : "";
		    if(!g_strcasecmp(type, "file"))
		    {
			obj->type = EDV_OBJECT_TYPE_FILE;
		    }
		    else if(!g_strcasecmp(type, "directory"))
		    {
			obj->type = EDV_OBJECT_TYPE_DIRECTORY;
		    }
		    else if(!g_strcasecmp(type, "symlink") ||
			    !g_strcasecmp(type, "hard link") ||
			    !g_strcasecmp(type, "hardlink") ||
			    !g_strcasecmp(type, "link")
		    )
		    {
			obj->type = EDV_OBJECT_TYPE_LINK;
		    }
		    else if(!g_strcasecmp(type, "block special") ||
			    !g_strcasecmp(type, "block device") ||
			    !g_strcasecmp(type, "blockspecial") ||
			    !g_strcasecmp(type, "blockdevice")
		    )
		    {
			obj->type = EDV_OBJECT_TYPE_DEVICE_BLOCK;
		    }
		    else if(!g_strcasecmp(type, "character special") ||
			    !g_strcasecmp(type, "character device") ||
			    !g_strcasecmp(type, "characterspecial") ||
			    !g_strcasecmp(type, "characterdevice")
		    )
		    {
			obj->type = EDV_OBJECT_TYPE_DEVICE_CHARACTER;
		    }
		    else if(!g_strcasecmp(type, "fifo"))
		    {
			obj->type = EDV_OBJECT_TYPE_FIFO;
		    }
		    else if(!g_strcasecmp(type, "socket"))
		    {
			obj->type = EDV_OBJECT_TYPE_SOCKET;
		    }
		    else
		    {
			obj->type = EDV_OBJECT_TYPE_UNKNOWN;
		    }
		}
		/* Size */
		else if(!g_strcasecmp(key, "data/size") ||
			!g_strcasecmp(key, "size")
		)
		{
		    obj->size = (gulong)ATOL(val);
		}
		/* Compressed Size */
		else if(!g_strcasecmp(key, "data/length") ||
			!g_strcasecmp(key, "compressed size")
		)
		{
		    obj->compressed_size = (gulong)ATOL(val);
		}
		/* Link */
		else if(!g_strcasecmp(key, "link"))
		{
		    const gchar *link = (val != NULL) ? val : "";
		    g_free(obj->link_value);
		    obj->link_value = STRDUP(link);
		}
		/* Mode */
		else if(!g_strcasecmp(key, "mode") ||
			!g_strcasecmp(key, "permissions")
		)
		{
		    const gchar *permissions = (val != NULL) ? val : "";
		    const gint len = STRLEN(permissions);

		    /* Is the value's format is numeric? */
		    if((len > 0) ? isdigit(*permissions) : FALSE)
		    {
			/* Numeric
			 *
			 * Check if the format is nnnn
			 */
			if(len >= 4)
			{
			    /* nnnn */
			    /* SetUID, SetGID, and Sticky */
			    guint8 v = (guint8)(
                                (gchar)permissions[0] - '0'
                            );
			    if(v & (1 << 0))
				obj->permissions |= EDV_PERMISSION_STICKY;
			    if(v & (1 << 1))
				obj->permissions |= EDV_PERMISSION_SETGID;
			    if(v & (1 << 2))
				obj->permissions |= EDV_PERMISSION_SETUID;

			    /* User */
			    v = (guint8)(
                                (gchar)permissions[1] - '0'
                            );
			    if(v & (1 << 0))
				obj->permissions |= EDV_PERMISSION_UEXECUTE;
			    if(v & (1 << 1))
			        obj->permissions |= EDV_PERMISSION_UWRITE;
			    if(v & (1 << 2))
				obj->permissions |= EDV_PERMISSION_UREAD;

			    /* Group */
			    v = (guint8)(
				(gchar)permissions[2] - '0'
			    );
			    if(v & (1 << 0))
				obj->permissions |= EDV_PERMISSION_GEXECUTE;
			    if(v & (1 << 1))
				obj->permissions |= EDV_PERMISSION_GWRITE;
			    if(v & (1 << 2))
				obj->permissions |= EDV_PERMISSION_GREAD;

			    /* Other */
			    v = (guint8)(
				(gchar)permissions[3] - '0'
			    );
			    if(v & (1 << 0))
				obj->permissions |= EDV_PERMISSION_AEXECUTE;
			    if(v & (1 << 1))
				obj->permissions |= EDV_PERMISSION_AWRITE;
			    if(v & (1 << 2))
				obj->permissions |= EDV_PERMISSION_AREAD;
			}
			/* nnn */
			else if(len == 3)
			{
			    /* User */
			    guint8 v = (guint8)(
                                (gchar)permissions[1] - '0'
                            );
			    if(v & (1 << 0))
				obj->permissions |= EDV_PERMISSION_UEXECUTE;
			    if(v & (1 << 1))
			        obj->permissions |= EDV_PERMISSION_UWRITE;
			    if(v & (1 << 2))
				obj->permissions |= EDV_PERMISSION_UREAD;

			    /* Group */
			    v = (guint8)(
				(gchar)permissions[2] - '0'
			    );
			    if(v & (1 << 0))
				obj->permissions |= EDV_PERMISSION_GEXECUTE;
			    if(v & (1 << 1))
				obj->permissions |= EDV_PERMISSION_GWRITE;
			    if(v & (1 << 2))
				obj->permissions |= EDV_PERMISSION_GREAD;

			    /* Other */
			    v = (guint8)(
				(gchar)permissions[3] - '0'
			    );
			    if(v & (1 << 0))
				obj->permissions |= EDV_PERMISSION_AEXECUTE;
			    if(v & (1 << 1))
				obj->permissions |= EDV_PERMISSION_AWRITE;
			    if(v & (1 << 2))
				obj->permissions |= EDV_PERMISSION_AREAD;
			}
			/* n */
			else if(len >= 1)
			{
			    const guint8 v = (guint8)(
                                (gchar)permissions[1] - '0'
                            );
			    if(v & (1 << 0))
			    {
				obj->permissions |= EDV_PERMISSION_UEXECUTE;
				obj->permissions |= EDV_PERMISSION_GEXECUTE;
				obj->permissions |= EDV_PERMISSION_AEXECUTE;
			    }
			    if(v & (1 << 1))
			    {
			        obj->permissions |= EDV_PERMISSION_UWRITE;
			        obj->permissions |= EDV_PERMISSION_GWRITE;
			        obj->permissions |= EDV_PERMISSION_AWRITE;
			    }
			    if(v & (1 << 2))
			    {
				obj->permissions |= EDV_PERMISSION_UREAD;
				obj->permissions |= EDV_PERMISSION_GREAD;
				obj->permissions |= EDV_PERMISSION_AREAD;
			    }
			}
		    }
		    else
		    {
			/* Alphabet
			 *
			 * Check if the format is trwxrwxrwx
			 */
			if(len >= 10)
			{
			    if(permissions[1] == 'r')
				obj->permissions |= EDV_PERMISSION_UREAD;
			    if(permissions[2] == 'w')
				obj->permissions |= EDV_PERMISSION_UWRITE;
			    if(permissions[3] == 'x')
				obj->permissions |= EDV_PERMISSION_UEXECUTE;
			    if(permissions[4] == 'r')
				obj->permissions |= EDV_PERMISSION_GREAD;
			    if(permissions[5] == 'w')
				obj->permissions |= EDV_PERMISSION_GWRITE;
			    if(permissions[6] == 'x')
				obj->permissions |= EDV_PERMISSION_GEXECUTE;
			    if(permissions[7] == 'r')
				obj->permissions |= EDV_PERMISSION_AREAD;
			    if(permissions[8] == 'w')
				obj->permissions |= EDV_PERMISSION_AWRITE;
			    if(permissions[9] == 'x')
				obj->permissions |= EDV_PERMISSION_AEXECUTE;
			}
			/* rwxrwxrwx */
			else if(len == 9)
			{
			    if(permissions[0] == 'r')
				obj->permissions |= EDV_PERMISSION_UREAD;
			    if(permissions[1] == 'w')
				obj->permissions |= EDV_PERMISSION_UWRITE;
			    if(permissions[2] == 'x')
				obj->permissions |= EDV_PERMISSION_UEXECUTE;
			    if(permissions[3] == 'r')
				obj->permissions |= EDV_PERMISSION_GREAD;
			    if(permissions[4] == 'w')
				obj->permissions |= EDV_PERMISSION_GWRITE;
			    if(permissions[5] == 'x')
				obj->permissions |= EDV_PERMISSION_GEXECUTE;
			    if(permissions[6] == 'r')
				obj->permissions |= EDV_PERMISSION_AREAD;
			    if(permissions[7] == 'w')
				obj->permissions |= EDV_PERMISSION_AWRITE;
			    if(permissions[8] == 'x')
				obj->permissions |= EDV_PERMISSION_AEXECUTE;
			}
			/* rwx */
			else if(len >= 3)
			{
			    if(permissions[0] == 'r')
			    {
				obj->permissions |= EDV_PERMISSION_UREAD;
				obj->permissions |= EDV_PERMISSION_GREAD;
				obj->permissions |= EDV_PERMISSION_AREAD;
			    }
			    if(permissions[1] == 'w')
			    {
				obj->permissions |= EDV_PERMISSION_UWRITE;
				obj->permissions |= EDV_PERMISSION_GWRITE;
				obj->permissions |= EDV_PERMISSION_AWRITE;
			    }
			    if(permissions[2] == 'x')
			    {
				obj->permissions |= EDV_PERMISSION_UEXECUTE;
				obj->permissions |= EDV_PERMISSION_GEXECUTE;
				obj->permissions |= EDV_PERMISSION_AEXECUTE;
			    }
			}
		    }
		}
		/* User */
		else if(!g_strcasecmp(key, "user"))
		{
		    const gchar *user = (val != NULL) ? val : "";
		    g_free(obj->owner_name);
		    obj->owner_name = STRDUP(user);
		}
		/* Group */
		else if(!g_strcasecmp(key, "group"))
		{
		    const gchar *group = (val != NULL) ? val : "";
		    g_free(obj->group_name);
		    obj->group_name = STRDUP(group);
		}
		/* ATime */
		else if(!g_strcasecmp(key, "atime"))
		{
		    const gchar *atime = val;
		    struct tm tm_buf;

		    memset(&tm_buf, 0x00, sizeof(struct tm));
		    tm_buf.tm_isdst = -1;

		    /* Year */
		    if(atime != NULL)
		    {
			tm_buf.tm_year = (int)ATOI(atime) - 1900;
			atime = (const gchar *)strchr(atime, '-');
			if(atime != NULL)
			    atime++;
		    }
		    /* Month */
		    if(atime != NULL)
		    {
			tm_buf.tm_mon = (int)ATOI(atime) - 1;
			atime = (const gchar *)strchr(atime, '-');
			if(atime != NULL)
			    atime++;
		    }
		    /* Day */
		    if(atime != NULL)
		    {
			tm_buf.tm_mday = (int)ATOI(atime);
			atime = (const gchar *)strchr(atime, 'T');
			if(atime != NULL)
			    atime++;
		    }
		    /* Hours */
		    if(atime != NULL)
		    {
			tm_buf.tm_hour = (int)ATOI(atime);
			atime = (const gchar *)strchr(atime, ':');
			if(atime != NULL)
			    atime++;
		    }
		    /* Minutes */
		    if(atime != NULL)
		    {
			tm_buf.tm_min = (int)ATOI(atime);
			atime = (const gchar *)strchr(atime, ':');
			if(atime != NULL)
			    atime++;
		    }
		    /* Seconds */
		    if(atime != NULL)
		    {
			tm_buf.tm_sec = (int)ATOI(atime);
		    }

		    obj->access_time = (gulong)((glong)mktime(&tm_buf) -
			gmt_offset_dst);
		}
		/* MTime */
		else if(!g_strcasecmp(key, "mtime"))
		{
		    const gchar *mtime = val;
		    struct tm tm_buf;

		    memset(&tm_buf, 0x00, sizeof(struct tm));
		    tm_buf.tm_isdst = -1;

		    /* Year */
		    if(mtime != NULL)
		    {
			tm_buf.tm_year = (int)ATOI(mtime) - 1900;
			mtime = (const gchar *)strchr(mtime, '-');
			if(mtime != NULL)
			    mtime++;
		    }
		    /* Month */
		    if(mtime != NULL)
		    {
			tm_buf.tm_mon = (int)ATOI(mtime) - 1;
			mtime = (const gchar *)strchr(mtime, '-');
			if(mtime != NULL)
			    mtime++;
		    }
		    /* Day */
		    if(mtime != NULL)
		    {
			tm_buf.tm_mday = (int)ATOI(mtime);
			mtime = (const gchar *)strchr(mtime, 'T');
			if(mtime != NULL)
			    mtime++;
		    }
		    /* Hours */
		    if(mtime != NULL)
		    {
			tm_buf.tm_hour = (int)ATOI(mtime);
			mtime = (const gchar *)strchr(mtime, ':');
			if(mtime != NULL)
			    mtime++;
		    }
		    /* Minutes */
		    if(mtime != NULL)
		    {
			tm_buf.tm_min = (int)ATOI(mtime);
			mtime = (const gchar *)strchr(mtime, ':');
			if(mtime != NULL)
			    mtime++;
		    }
		    /* Seconds */
		    if(mtime != NULL)
		    {
			tm_buf.tm_sec = (int)ATOI(mtime);
		    }

		    obj->modify_time = (gulong)((glong)mktime(&tm_buf) -
			gmt_offset_dst);
		}
		/* CTime */
		else if(!g_strcasecmp(key, "ctime"))
		{
		    const gchar *ctime = val;
		    struct tm tm_buf;

		    memset(&tm_buf, 0x00, sizeof(struct tm));
		    tm_buf.tm_isdst = -1;

		    /* Year */
		    if(ctime != NULL)
		    {
			tm_buf.tm_year = (int)ATOI(ctime) - 1900;
			ctime = (const gchar *)strchr(ctime, '-');
			if(ctime != NULL)
			    ctime++;
		    }
		    /* Month */
		    if(ctime != NULL)
		    {
			tm_buf.tm_mon = (int)ATOI(ctime) - 1;
			ctime = (const gchar *)strchr(ctime, '-');
			if(ctime != NULL)
			    ctime++;
		    }
		    /* Day */
		    if(ctime != NULL)
		    {
			tm_buf.tm_mday = (int)ATOI(ctime);
			ctime = (const gchar *)strchr(ctime, 'T');
			if(ctime != NULL)
			    ctime++;
		    }
		    /* Hours */
		    if(ctime != NULL)
		    {
			tm_buf.tm_hour = (int)ATOI(ctime);
			ctime = (const gchar *)strchr(ctime, ':');
			if(ctime != NULL)
			    ctime++;
		    }
		    /* Minutes */
		    if(ctime != NULL)
		    {
			tm_buf.tm_min = (int)ATOI(ctime);
			ctime = (const gchar *)strchr(ctime, ':');
			if(ctime != NULL)
			    ctime++;
		    }
		    /* Seconds */
		    if(ctime != NULL)
		    {
			tm_buf.tm_sec = (int)ATOI(ctime);
		    }

		    obj->change_time = (gulong)((glong)mktime(&tm_buf) -
			gmt_offset_dst);
		}
		/* Encoding */
		else if(!g_strcasecmp(key, "data/encoding") ||
			!g_strcasecmp(key, "encoding") ||
			!g_strcasecmp(key, "method")
		)
		{
		    const gchar *encoding = (val != NULL) ? val : "";
		    g_free(obj->method);
		    obj->method = STRDUP(encoding);
		}
		/* Archived Checksum */
		else if(!g_strcasecmp(key, "data/archived-checksum") ||
			!g_strcasecmp(key, "data/checksum") ||
			!g_strcasecmp(key, "checksum") ||
			!g_strcasecmp(key, "crc")
		)
		{
		    const gchar *checksum = (val != NULL) ? val : "";
		    g_free(obj->crc);
		    obj->crc = STRDUP(checksum);
		}
	    }

	    /* Delete the X Archive property iterator */
	    xar_iter_free(i2);

	    /* Calculate the compression ratio */
	    obj->compression_ratio = (obj->size > 0l) ?
		(1.0f - ((gfloat)obj->compressed_size / (gfloat)obj->size)) :
		0.0f;
	    if(obj->compression_ratio < 0.0f)
		obj->compression_ratio = 0.0f;

	    /* Append this object to the list */
	    obj_list = g_list_append(obj_list, obj);

	    /* Report progress? */
	    if(progress_cb != NULL)
	    {
		if(progress_cb(
		    arch_obj,
		    obj,
		    (gulong)(i + 1), (gulong)nobjs,
		    progress_data
		))
		    break;
	    }
	}

	/* Delete the iterator and close the X Archive */
	xar_iter_free(i1);
	xar_close(xar);

	return(obj_list);
#else
	return(NULL);
#endif
}

/*
 *	Returns a listing of all objects in the PKZip archive.
 */
static GList *EDVArchStatListZip(EDV_ARCH_STAT_LIST_ANY_PROTOTYPE)
{
#ifdef HAVE_LIBZIP
	int zip_error;
	struct zip *archive;
	struct zip_stat zip_stat_buf;
	gint i, len, nobjs;
	gchar *clean_path;
	const gchar *path;
	GList *obj_list = NULL;
	edv_object_type type;
	edv_archive_object_struct *obj;

	/* Open the PKZip archive */
	archive = zip_open(arch_obj, 0, &zip_error);
	if(archive == NULL)
	{
	    const gint error_code = (gint)errno;
	    zip_error_to_str(
		last_error_buf, sizeof(last_error_buf),
		zip_error, error_code
	    );
	    last_error = last_error_buf;
	    return(obj_list);
	}

	/* Get the number of objects in the PKZip archive */
	nobjs = zip_get_num_files(archive);
	if(nobjs <= 0)
	{
	    int error_code;
	    zip_error_get(archive, &zip_error, &error_code);
	    zip_error_to_str(
		last_error_buf, sizeof(last_error_buf),
		zip_error, error_code
	    );
	    last_error = last_error_buf;
	    zip_close(archive);
	    return(obj_list);
	}

	/* Iterate through each object in the PKZip archive */
	for(i = 0; i < nobjs; i++)
	{
	    /* Get the stats for this object in the PKZip archive, if
	     * unable to obtain the stats then just continue on to the
	     * next object (because sometimes an index may refer to a
	     * deleted or non-existant object)
	     */
	    if(zip_stat_index(archive, i, 0, &zip_stat_buf))
		continue;

	    path = zip_stat_buf.name;
	    if(STRISEMPTY(path))
		continue;

	    /* Get the "clean path" (the path without the tailing
	     * directory deliminator or tailing type character)
	     */
	    clean_path = STRDUP(path);
	    len = STRLEN(clean_path);
	    switch((len > 1) ? clean_path[len - 1] : '\0')
	    {
	      case G_DIR_SEPARATOR:	/* Directory */
		clean_path[len - 1] = '\0';
		type = EDV_OBJECT_TYPE_DIRECTORY;
		break;
	      case '@':			/* Link */
		clean_path[len - 1] = '\0';
		type = EDV_OBJECT_TYPE_LINK;
		break;
	      default:			/* File */
		type = EDV_OBJECT_TYPE_FILE;
		break;
	    }

	    /* Is this object's path not in the list of paths (use the
	     * "clean path" for proper matching)?
	     */
	    if(!EDVArchStatFilter(
		paths_list,
#if defined(HAVE_REGEX)
		regex_filter,
#else
		filter,
#endif
		clean_path
	    ))
	    {
		g_free(clean_path);
		continue;
	    }

	    /* Create a new archive object stats structure and set its
	     * values to the values obtained from the PKZip archive's
	     * object stats
	     */
	    obj = EDVArchObjectNew();
	    if(obj == NULL)
	    {
		g_free(clean_path);
		continue;
	    }

	    obj->type = type;

	    obj->full_path = STRDUP(clean_path);
	    obj->name = STRDUP(g_basename(clean_path));

	    obj->permissions = 0;	/* Not supported by the PKZip format */

	    obj->access_time = (gulong)zip_stat_buf.mtime;
	    obj->modify_time = (gulong)zip_stat_buf.mtime;
	    obj->change_time = (gulong)zip_stat_buf.mtime;

	    obj->size = (gulong)zip_stat_buf.size;
	    obj->compressed_size = (gulong)zip_stat_buf.comp_size;

	    obj->compression_ratio = (obj->size > 0l) ?
		(1.0f - ((gfloat)obj->compressed_size / (gfloat)obj->size)) :
		0.0f;
	    if(obj->compression_ratio < 0.0f)
		obj->compression_ratio = 0.0f;

	    switch(zip_stat_buf.comp_method)
	    {
#ifdef ZIP_CM_STORE
	      case ZIP_CM_STORE:
		obj->method = STRDUP("Store");
		break;
#endif
#ifdef ZIP_CM_SHRINK
	      case ZIP_CM_SHRINK:
		obj->method = STRDUP("Shrink");
		break;
#endif
#ifdef ZIP_CM_REDUCE_1
	      case ZIP_CM_REDUCE_1:
		obj->method = STRDUP("Reduce 1");
		break;
#endif
#ifdef ZIP_CM_REDUCE_2
	      case ZIP_CM_REDUCE_2:
		obj->method = STRDUP("Reduce 2");
		break;
#endif
#ifdef ZIP_CM_REDUCE_3
	      case ZIP_CM_REDUCE_3:
		obj->method = STRDUP("Reduce 3");
		break;
#endif
#ifdef ZIP_CM_REDUCE_4
	      case ZIP_CM_REDUCE_4:
		obj->method = STRDUP("Reduce 4");
		break;
#endif
#ifdef ZIP_CM_IMPLODE
	      case ZIP_CM_IMPLODE:
		obj->method = STRDUP("Implode");
		break;
#endif
#ifdef ZIP_CM_DEFLATE
	      case ZIP_CM_DEFLATE:
		obj->method = STRDUP("Deflate");
		break;
#endif
#ifdef ZIP_CM_DEFLATE64
	      case ZIP_CM_DEFLATE64:
		obj->method = STRDUP("Deflate 64");
		break;
#endif
#ifdef ZIP_CM_PKWARE_IMPLODE
	      case ZIP_CM_PKWARE_IMPLODE:
		obj->method = STRDUP("PKWare Implode");
		break;
#endif
	      default:
		obj->method = STRDUP("Other");
		break;
	    }

	    obj->crc = g_strdup_printf(
		"%.8X",
		(guint32)zip_stat_buf.crc
	    );

	    g_free(clean_path);

	    /* Append this object to the list */
	    obj_list = g_list_append(obj_list, obj);

	    /* Report progress? */
	    if(progress_cb != NULL)
	    {
		if(progress_cb(
		    arch_obj,
		    obj,
		    (gulong)(i + 1), (gulong)nobjs,
		    progress_data
		))
		    break;
	    }
	}

	/* Close the PKZip archive */
	zip_close(archive);

	/* Check the PKZip archive for errors */
	archive = zip_open(arch_obj, ZIP_CHECKCONS, &zip_error);
	if(archive == NULL)
	{
	    const gint error_code = (gint)errno;
	    zip_error_to_str(
		last_error_buf, sizeof(last_error_buf),
		zip_error, error_code
	    );
	    last_error = last_error_buf;
	    return(obj_list);
	}
	zip_close(archive);

	return(obj_list);
#else
	const gchar *prog_unzip = CFGItemListGetValueS(
	    core->cfg_list, EDV_CFG_PARM_PROG_UNZIP
	);
	FILE *fp;
	gint p;
	gchar *cmd, *stdout_path, *stderr_path;
	GList *obj_list = NULL;
	edv_archive_object_struct *obj;

	/* Format the archive list output command */
	cmd = g_strdup_printf(
	    "\"%s\" -l -v \"%s\"",
	    prog_unzip, arch_obj
	);
	if(cmd == NULL)
	    return(obj_list);

	/* Generate the output file paths */
	stdout_path = EDVTmpName(NULL);
	stderr_path = EDVTmpName(NULL);

	/* Execute the archive list output command */
	p = (gint)ExecBOE(
	    (const char *)cmd,
	    (const char *)stdout_path,
	    (const char *)stderr_path
	);
	g_free(cmd);
	if(p <= 0)
	{
	    g_free(stdout_path);
	    g_free(stderr_path);
	    return(obj_list);
	}

	/* Open the output file for reading */
	fp = fopen((const char *)stdout_path, "rb");
	if(fp != NULL)
	{
	    gint i;
	    gchar *s, *vpath;
	    const gchar *delim_header = "--------";
	    gchar buf[10000];

	    /* Read past the header or to the end of file */
	    for(i = 0; TRUE; i++)
	    {
		if(fgets(buf, sizeof(buf), fp) != NULL)
		    buf[sizeof(buf) - 1] = '\0';
		else
		    break;

		if(strpfx((const char *)buf, (const char *)delim_header))
		    break;
	    }

	    /* Begin reading each line describing each object in the
	     * PKZip archive
	     */
	    while(fgets(buf, sizeof(buf), fp) != NULL)
	    {
		/* Report progress? */
		if(progress_cb != NULL)
		{
		    if(progress_cb(
			arch_obj,
			NULL,
			0l, 0l,
			progress_data
		    ))
			break;
		}

		buf[sizeof(buf) - 1] = '\0';

		s = buf;

		/* Skip initial spaces */
		while(ISBLANK(*s))
		    s++;

		/* Ending deliminator reached? If so do not parse this
		 * line or any subsequent lines
		 */
		if(*s == '-')
		    break;

		/* Seek vpath to the full path value segment in s,
		 * skipping 7 value in s
		 */
		vpath = s;
		for(i = 0; i < 7; i++)
		{
		    while(!ISBLANK(*vpath) && (*vpath != '\0'))
			vpath++;
		    while(ISBLANK(*vpath))
			vpath++;
		}

		/* Copy the path value and cap the first newline
		 * character
		 */
		vpath = STRDUP(vpath);
		EDVArchStatCapNewline(vpath);

		/* Check if the path matches any of the paths in
		 * the paths list or filter
		 */
		if(EDVArchStatFilter(
		    paths_list,
#if defined(HAVE_REGEX)
		    regex_filter,
#else
		    filter,
#endif
		    vpath
		))
		{
		    /* Create a new archive object structure and parse
		     * the loaded line to it
		     */
		    obj = EDVArchObjectNew();
		    EDVArchStatParseLineZip(obj, s);

		    /* Append this object to the list */
		    obj_list = g_list_append(obj_list, obj);
		}

		g_free(vpath);
	    }

	    fclose(fp);
	}

	/* Check for errors */
	fp = fopen((const char *)stderr_path, "rb");
	if(fp != NULL)
	{
	    gchar *s, *s2, buf[10000];

	    while(fgets(buf, sizeof(buf), fp) != NULL)
	    {
		buf[sizeof(buf) - 1] = '\0';

		s = buf;
		while(ISSPACE(*s))
		    s++;

		s2 = (gchar *)strpbrk((char *)s, "\n\r");
		if(s2 != NULL)
		    *s2 = '\0';

		if(!STRISEMPTY(s))
		{
		    EDVArchStatCopyErrorMessage(s);
		    break;
		}
	    }

	    fclose(fp);
	}

	/* Delete the output files */
	UNLINK(stdout_path);
	UNLINK(stderr_path);

	g_free(stdout_path);
	g_free(stderr_path);

	return(obj_list);
#endif
}

/*
 *	Returns a list of archive object stats for the objects found in
 *	the archive arch_obj.
 *
 *	The paths_list specifies a list of (const gchar *) paths that
 *	represent objects within the archive to obtain stats for. If
 *	paths_list is NULL then the filter will be used to determine
 *	which objects in the archive to obtain the stats for.
 *
 *	The filter specifies the object name filter to obtain the
 *	object stats for. Only the object's name (not its path within
 *	the archive) will be compared with the filter. The filter is
 *	only used if paths_list is NULL.
 *
 *	The password specifies the password to use in order to obtain
 *	stats from encrypted archives.
 *
 *	The returned list of archive object stats must be deleted by the
 *	calling function.
 *
 *	Can return NULL if the archive is not supported, error, or no
 *	objects were found in the archive.
 */
GList *EDVArchStatList(
	edv_core_struct *core,
	const gchar *arch_obj,
	GList *paths_list,
	const gchar *filter,
	const gchar *password,
	gint (*progress_cb)(
		const gchar *,
		edv_archive_object_struct *,
		const gulong, const gulong,
		gpointer
	),
	gpointer progress_data
)
{
#ifdef HAVE_REGEX
	regex_t *regex_filter;
#endif
	const gchar *arch_name;
	struct stat stat_buf;
	GList *obj_list = NULL;

	last_error = NULL;

	if((core == NULL) || STRISEMPTY(arch_obj))
	    return(obj_list);

	arch_name = g_basename(arch_obj);

	/* Get archive stats and make sure it is a file */
	if(stat((const char *)arch_obj, &stat_buf))
	    return(obj_list);
#ifdef S_ISREG
	if(!S_ISREG(stat_buf.st_mode))
	    return(obj_list);
#endif

#if defined(HAVE_REGEX)
	/* Compile the regex search criteria */
	if(STRISEMPTY(filter) ?
	    FALSE : strcmp(filter, "*")
	)
	{
	    regex_filter = (regex_t *)g_malloc(sizeof(regex_t));
	    if(regcomp(
		regex_filter,
		filter,
#ifdef REG_EXTENDED
		REG_EXTENDED |		/* Use POSIX extended regex */
#endif
		REG_NOSUB		/* Do not report subpattern matches */
	    ))
	    {
		g_free(regex_filter);
		regex_filter = NULL;
	    }
	}
	else
	{
	    regex_filter = NULL;
	}
#else
	if(STRISEMPTY(filter) ?
	    TRUE : !strcmp(filter, "*")
	) 
	    filter = NULL;
#endif

	/* Get listing by the archive's extension */

	/* ARJ Archive */
	if(EDVIsExtension(arch_name, ".arj"))
	{
	    obj_list = EDVArchStatListARJ(
		core,
		arch_obj,
		paths_list,
#if defined(HAVE_REGEX)
		regex_filter,
#else
		filter,
#endif
		progress_cb, progress_data
	    );
	}
	/* LHA Archive */
	else if(EDVIsExtension(arch_name, ".lha"))
	{
	    obj_list = EDVArchStatListLHA(
		core,
		arch_obj,
		paths_list,
#if defined(HAVE_REGEX)
		regex_filter,
#else
		filter,
#endif
		progress_cb, progress_data
	    );
	}
	/* RAR Archive */
	else if(EDVIsExtension(arch_name, ".rar"))
	{
	    obj_list = EDVArchStatListRAR(
		core,
		arch_obj,
		paths_list,
#if defined(HAVE_REGEX)
		regex_filter,
#else
		filter,
#endif
		progress_cb, progress_data
	    );
	}
	/* RedHat Package Manager Package */
	else if(EDVIsExtension(arch_name, ".rpm"))
	{
	    obj_list = EDVArchStatListRPM(
		core,
		arch_obj,
		paths_list,
#if defined(HAVE_REGEX)
		regex_filter,
#else
		filter,
#endif
		progress_cb, progress_data
	    );
	}
	/* Tape Archive (Compressed) */
	else if(EDVIsExtension(arch_name, ".tar.Z"))
	{
	    obj_list = EDVArchStatListTar(
		core,
		arch_obj,
		paths_list,
#if defined(HAVE_REGEX)
		regex_filter,
#else
		filter,
#endif
		progress_cb, progress_data,
		TRUE,		/* Not compress compressed */
		FALSE,		/* Not gzip compressed */
		FALSE		/* Not bzip2 compressed */
	    );
	}
	/* Tape Archive (GZip) */
	else if(EDVIsExtension(arch_name, ".tgz .tar.gz"))
	{
	    obj_list = EDVArchStatListTar(
		core,
		arch_obj,
		paths_list,
#if defined(HAVE_REGEX)
		regex_filter,
#else
		filter,
#endif
		progress_cb, progress_data,
		FALSE,		/* Not compress compressed */
		TRUE,		/* Is gzip compressed */
		FALSE		/* Not bzip2 compressed */
	    );
	}
	/* Tape Archive (BZip2) */
	else if(EDVIsExtension(arch_name, ".tar.bz2"))
	{
	    obj_list = EDVArchStatListTar(
		core,
		arch_obj,
		paths_list,
#if defined(HAVE_REGEX)
		regex_filter,
#else
		filter,
#endif
		progress_cb, progress_data,
		FALSE,		/* Not compress compressed */
		FALSE,		/* Not gzip compressed */
		TRUE		/* Is bzip2 compressed */
	    );
	}
	/* Tape Archive */
	else if(EDVIsExtension(arch_name, ".tar"))
	{
	    obj_list = EDVArchStatListTar(
		core,
		arch_obj,
		paths_list,
#if defined(HAVE_REGEX)
		regex_filter,
#else
		filter,
#endif
		progress_cb, progress_data,
		FALSE,		/* Not compress compressed */
		FALSE,		/* Not gzip compressed */
		FALSE		/* Not bzip2 compressed */
	    );
	}
	/* X Archive */
	else if(EDVIsExtension(arch_name, ".xar"))
	{
	    obj_list = EDVArchStatListXAr(
		core,
		arch_obj,
		paths_list,
#if defined(HAVE_REGEX)
		regex_filter,
#else
		filter,
#endif
		progress_cb, progress_data
	    );
	}
	/* PKZip Archive */
	else if(EDVIsExtension(arch_name, ".xpi .zip"))
	{
	    obj_list = EDVArchStatListZip(
		core,
		arch_obj,
		paths_list,
#if defined(HAVE_REGEX)
		regex_filter,
#else
		filter,
#endif
		progress_cb, progress_data
	    );
	}

#ifdef HAVE_REGEX
	if(regex_filter != NULL)
	{
	    regfree(regex_filter);
	    g_free(regex_filter);
	}
#endif

	return(obj_list);
}
