#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include <sys/types.h>
#include <limits.h>
#include <utime.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <glib.h>
#include <unistd.h>

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

#include "edv_types.h"
#include "edv_obj.h"
#include "edv_recycled_obj.h"
#include "edv_recbin_index.h"
#include "edv_recbin_stat.h"
#include "edv_utils.h"
#include "edv_utils_gtk.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.
 */


const gchar *EDVRecBinIndexGetError(void);
static void EDVRecBinIndexCopyErrorMessage(const gchar *msg);

/* Get Index List & Total */
gint EDVRecBinIndexGetTotal(const gchar *index_path);
GList *EDVRecBinIndexGetList(const gchar *index_path);

static void EDVRecBinIndexWriteObjectIterate(
	FILE *fp,
	const guint index,
	const edv_recycled_object_struct *obj
);

/* Index File Open, Next, and Close */
edv_recbin_index_struct *EDVRecBinIndexOpen(const gchar *index_path);
gint EDVRecBinIndexNext(edv_recbin_index_struct *rbi_ptr);
void EDVRecBinIndexClose(edv_recbin_index_struct *rbi_ptr);

/* Index Adding & Removing */
guint EDVRecBinIndexAdd(
	const gchar *index_path, const edv_recycled_object_struct *obj
);
gint EDVRecBinIndexRemove(
	const gchar *index_path, const guint index
);

/* Get Recycled Object From Index */
static gint EDVRecBinObjectGetFromIndexFileIterate(
	FILE *fp, edv_recycled_object_struct *obj
);
gint EDVRecBinObjectGetFromIndexFile(
	const gchar *index_path,
	const guint index,		/* Index of the recycled object */
	edv_recycled_object_struct *obj	/* storage */
);

/* Delete, Recover, and Purge */
gint EDVRecBinDiskObjectDelete(
	const gchar *index_path,
	const guint index,		/* Put into recycle bin index file under this index */
	const gchar *path,		/* Object to delete */
	gint (*progress_cb)(gpointer, const gulong, const gulong),
	gpointer data
);
gint EDVRecBinDiskObjectRecover(
	const gchar *index_path,
	const guint index,		/* Index of the recycled object */
	const gchar *path,		/* Alternate recovery location (can
					 * be NULL) */
	gint (*progress_cb)(gpointer, const gulong, const gulong),
	gpointer data
);
gint EDVRecBinDiskObjectPurge(
	const gchar *index_path,
	const guint index,		/* Index of the recycled object */
 	gint (*progress_cb)(gpointer, const gulong, const gulong),
	gpointer data
);
gint EDVRecBinDiskObjectPurgeAll(
	const gchar *index_path,
	gint (*progress_cb)(gpointer, const gulong, const gulong),
	gpointer data
);


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


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


/*
 *	Returns a statically allocated string specifying the the last 
 *	error (or NULL if there was no error) since the last call to
 *	a EDVRecBinFIO*() function.
 */
const gchar *EDVRecBinIndexGetError(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 EDVRecBinIndexCopyErrorMessage(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;
}

/*
 *	Gets the total number of recycled objects in the recycle bin.
 */
gint EDVRecBinIndexGetTotal(const gchar *index_path)
{
	gint total;
	gchar *recbin_dir;

	last_error = NULL;

	if(STRISEMPTY(index_path))
	{
	    last_error =
"Recycled objects index file was not specified";
	    return(0);
	}

	/* Get the recycle bin directory */
	recbin_dir = g_dirname(index_path);
	if(recbin_dir == NULL)
	{
	    last_error =
"Unable to obtain the recycle bin directory";
	    return(0);
	}

	/* Get the total number of objects in the recycle bin
	 * directory
	 */
	total = NUMDIRCONTENTS(recbin_dir);

	g_free(recbin_dir);

	/* Subtract the recycled objects index file from the total */
	total--;
	if(total < 0)
	    total = 0;

	return(total);
}

/*
 *	Gets a list of recycled object indices.
 *
 *	Returns a list of indices of type guint from the recycled
 *	objects index file specified by index_path. The calling function
 *	must delete the returned list.
 */
GList *EDVRecBinIndexGetList(const gchar *index_path)
{
	FILE *fp;
	gint vi[1];
	GList *indices_list = NULL;

	last_error = NULL;

	if(STRISEMPTY(index_path))
	{
	    last_error =
"Recycled objects index file was not specified";
	    return(indices_list);
	}

	/* Open the recycled objects index file for reading */
	fp = fopen((const char *)index_path, "rb");
	if(fp == NULL)
	{
	    /* No recycled objects (do not treat this as an error) */
	    return(indices_list);
	}

	/* Begin reading the recycled objects index file */
	while(!FSeekToParm(
	    fp,
	    "BeginRecycledObject",
	    EDV_CFG_COMMENT_CHAR, EDV_CFG_DELIMINATOR_CHAR
	))
	{
	    /* Get the value of the start of the recycled object
	     * block parameter which is the index of the
	     * recycled object
	     */
	    FGetValuesI(fp, vi, 1);

	    indices_list = g_list_append(
		indices_list,
		(gpointer)vi[0]			/* guint */
	    );
	}

	/* Close the recycled objects index file */
	fclose(fp);

	return(indices_list);
}


/*
 *	Writes the recycled object to the index file.
 *
 *	The fp specifies the opened file pointer to the recycle bin
 *	index file at the position in the file where the recycled
 *	object's information should be written to.
 *
 *	The index specifies the recycled object's index which will
 *	be written to the index file.
 *
 *	The obj specifies the recycled object who's information will
 *	be written to the index file.
 *
 *	This function is used by EDVRecBinIndexAdd() and
 *	EDVRecBinIndexRemove().
 */
static void EDVRecBinIndexWriteObjectIterate(
	FILE *fp,
	const guint index,
	const edv_recycled_object_struct *obj
)
{
	/* BeginRecycledObject */
	fprintf(
	    fp,
	    "BeginRecycledObject = %u\n",
	    index
	);
	/* Name */
	if(!STRISEMPTY(obj->name))
	    fprintf(
		fp,
		"\tName = %s\n",
		(const char *)obj->name
	    );
	/* OriginalPath */
	if(!STRISEMPTY(obj->original_path))
	    fprintf(
		fp,
		"\tOriginalPath = %s\n",
		(const char *)obj->original_path
	    );
	/* DateDeleted */
	fprintf(
	    fp,
	    "\tDateDeleted = %ld\n",
	    (unsigned long)obj->deleted_time
	);
	/* Type */
	fprintf(
	    fp,
	    "\tType = %i\n",
	    (int)obj->type
	);
	/* LinkedTo */
	if(!STRISEMPTY(obj->link_value))
	{
	    /* Make a copy of the link destination and remove any non
	     * printable characters from it
	     *
	     * This link destination is a simplified version and is not
	     * to be considered the actual (unmodified) link destination
	     * value (which is obtained from the recycled file of the
	     * link)
	     */
	    gchar *s = STRDUP(obj->link_value), *s2;
	    for(s2 = s; *s2 != '\0'; s2++)
	    {
		if(!isprint((char)*s2))
		    *s2 = ' ';
	    }
	    if((s2 - s) >= (PATH_MAX + NAME_MAX))
		s[PATH_MAX + NAME_MAX - 1] = '\0';
	    fprintf(
		fp,
		"\tLinkedTo = %s\n",
		(const char *)s
	    );
	    g_free(s);
	}
#if 0
	else
	{
	    fprintf(
		fp,
		"\tLinkedTo = \n"
	    );
	}
#endif
	/* Permissions */
	fprintf(
	    fp,
	    "\tPermissions = %i\n",
	    (unsigned int)obj->permissions
	);
	/* AccessTime */
	fprintf(
	    fp,
	    "\tAccessTime = %ld\n",
	    (unsigned long)obj->access_time
	);
	/* ModifyTime */
	fprintf(
	    fp,
	    "\tModifyTime = %ld\n",
	    (unsigned long)obj->modify_time
	);
	/* ChangeTime */
	fprintf(
	    fp,
	    "\tChangeTime = %ld\n",
	    (unsigned long)obj->change_time
	);
	/* OwnerID */
	fprintf(
	    fp,
	    "\tOwnerID = %i\n",
	    (int)obj->owner_id
	);
	/* GroupIO */
	fprintf(
	    fp,
	    "\tGroupID = %i\n",
	    (int)obj->group_id
	);
	/* Size */
	fprintf(
	    fp,
	    "\tSize = %ld\n",
	    (unsigned long)obj->size
	);



	fprintf(
	    fp,
	    "EndRecycledObject\n"
	);
}

/*
 *	Opens the recycle bin index file for reading.
 *
 *	The index_path specifies the recycle bin index file.
 *
 *	EDVRecBinIndexNext() must be called to get the very first
 *	and subsequent recycled objects.
 *
 *	The returned recycle bin index context must be deleted by a
 *	call to EDVRecBinIndexClose().
 *
 *	Can return NULL if there are no recycled objects.
 */
edv_recbin_index_struct *EDVRecBinIndexOpen(const gchar *index_path)
{
	FILE *fp;
	edv_recbin_index_struct *rbi_ptr;

	last_error = NULL;

	if(STRISEMPTY(index_path))
	{
	    last_error =
"Recycled objects index file was not specified";
	    return(NULL);
	}

	/* Open the recycled objects index file for reading */
	fp = fopen((const char *)index_path, "rb");
	if(fp == NULL)
	{
	    const gint error_code = (gint)errno;
	    if(error_code != ENOENT)
	    {
		EDVRecBinIndexCopyErrorMessage(g_strerror(error_code));
	    }
	    return(NULL);
	}

	/* Create a new recycle bin index context */
	rbi_ptr = EDV_RECBIN_INDEX(g_malloc(
	    sizeof(edv_recbin_index_struct)
	));
	if(rbi_ptr == NULL)
	{
	    last_error = "Memory allocation error";
	    fclose(fp);
	    return(NULL);
	}

	/* Initialize all the values */
	rbi_ptr->fp = fp;
	rbi_ptr->index = 0;
	rbi_ptr->obj = EDVRecycledObjectNew();

	return(rbi_ptr);
}

/*
 *	Updates the given recbin index handle's index to the next recycled
 *	object index found in the recycle bin index file.
 *
 *	Returns non-zero on error or end of file.
 */
gint EDVRecBinIndexNext(edv_recbin_index_struct *rbi_ptr)
{
	gint status;
	FILE *fp;
	edv_recycled_object_struct *obj;
	gchar *parm = NULL;

	last_error = NULL;

	if(rbi_ptr == NULL)
	{
	    /* No recycle bin index context, this may indicate that
	     * there are no recycled objects (do not treat this as an
	     * error)
	     */
	    return(-1);
	}

	/* Get the values from the recycle bin index context */
	fp = rbi_ptr->fp;
	obj = rbi_ptr->obj;
	if((fp == NULL) || (obj == NULL))
	{
	    last_error =
"Recycle bin index context was not properly initialized";
	    return(-1);
	}

	/* Begin reading the recycle bin index file */
	status = 0;
	while(TRUE)
	{
	    /* Read the next parameter */
	    parm = FSeekNextParm(
		fp, parm,
		EDV_CFG_COMMENT_CHAR,
		EDV_CFG_DELIMINATOR_CHAR
	    );
	    if(parm == NULL)
	    {
		status = -1;
		break;
	    }

	    /* Begin handling by parameter */

	    /* Start of a recycled object block? */
	    if(!g_strcasecmp(parm, "BeginRecycledObject"))
	    {
		gint vi[1];

		FGetValuesI(fp, (int *)vi, 1);

		/* Update the current index */
		rbi_ptr->index = (guint)vi[0];

		/* Read all subsequent parameters from the current
		 * file position store them on the recycled object
		 *
		 * Stopping just after the next end of object block
		 */
		obj->index = (guint)vi[0];
		EDVRecBinObjectGetFromIndexFileIterate(fp, obj);

		/* Stop reading once this object has been obtained */
		break;
	    }
	    else
	    {
		/* Other parameter, skip */
		FSeekNextLine(fp);
	    }
	}

	/* Delete the parameter */
	g_free(parm);

	return(status);
}

/*
 *	Closes the recycle bin index file and deletes the recbin index
 *	handle.
 */
void EDVRecBinIndexClose(edv_recbin_index_struct *rbi_ptr)
{
	last_error = NULL;

	if(rbi_ptr == NULL)
	{
	    /* No recycle bin index context, this may indicate that
	     * there are no recycled objects (do not treat this as an
	     * error)
	     */
	    return;
	}

	/* Close the recycle bin index file */
	if(rbi_ptr->fp != NULL)
	    fclose(rbi_ptr->fp);

	/* Delete the recycled object buffer */
	EDVRecycledObjectDelete(rbi_ptr->obj);

	g_free(rbi_ptr);
}


/*
 *	Adds the recycled object to the recycled objects index file.
 *
 *	The index_path specifies the recycled objects index file.
 *
 *	The obj specifies the recycled object who's values will be
 *	used as the new entry in the recycled objects index file.
 *	The specified obj itself will not be modified or deleted.
 *
 *	Returns the index of the new recycled object or 0 on error.
 */
guint EDVRecBinIndexAdd(
	const gchar *index_path, const edv_recycled_object_struct *obj
)
{
	FILE *fp;
	guint cur_index, new_index = 0;
	gchar *recbin_dir;
	GList *glist, *indices_list;

	last_error = NULL;

	if(STRISEMPTY(index_path))
	{
	    last_error =
"Recycled objects index file was not specified";
	    return(0);
	}

	/* Get list of indices in the recycled objects index file */
	indices_list = EDVRecBinIndexGetList(index_path);

	/* Iterate through index list, looking for an index that is not
	 * in the list so that we can use it
	 *
	 * The new_index is initially set to 0, if an available index
	 * is found then new_index will be set to that index value
	 */
	for(cur_index = 1; cur_index != 0; cur_index++)
	{
	    /* Iterate through index list, checking if cur_index
	     * matches any index in the list and breaking the loop
	     * prematurly if there is a match
	     */
	    for(glist = indices_list; glist != NULL; glist = g_list_next(glist))
	    {
		if(cur_index == (guint)glist->data)
		    break;
	    }
	    /* Was the cur_index not in the indices list? */
	    if(glist == NULL)
	    {
		new_index = cur_index;
		break;
	    }
	}

	/* Delete the indices list */
	g_list_free(indices_list);

	/* No more indices available? */
	if(new_index == 0)
	{
	    last_error =
"Unable to generate a new index for the recycled object";
	    return(0);
	}

	/* Add a new entry to the recycled objects index file */

	/* Get the recycle bin directory and create it as needed */
	recbin_dir = g_dirname(index_path);
	if(recbin_dir != NULL)
	{
	    if(rmkdir((const char *)recbin_dir, S_IRUSR | S_IWUSR | S_IXUSR))
	    {
		last_error =
"Unable to create the recycle bin directory";
		g_free(recbin_dir);
		return(0);
	    }
 	    g_free(recbin_dir);
	}

	/* Open the recycle bin index file for write append */
	fp = fopen((const char *)index_path, "ab");
	if(fp != NULL)
	{
	    /* Write the recycled object to the recycled objects
	     * index file
	     */
	    if(obj != NULL)
		EDVRecBinIndexWriteObjectIterate(
		    fp, new_index, obj
		);

	    fclose(fp);
	}
	else
	{
	    last_error =
"Unable to open the recycled objects index file for writing";
	}

	return(new_index);
}

/*
 *	Removes the entry in the specified recycle bin index file.
 *
 *	Returns the number of occurances removed.
 */
gint EDVRecBinIndexRemove(
	const gchar *index_path, const guint index
)
{
	FILE *fp;
	gint objects_removed = 0;
	gchar	*recbin_dir,
		*in_file,
		*out_file;
	edv_recbin_index_struct *rbi_ptr;

	last_error = NULL;

	if(STRISEMPTY(index_path))
	{
	    last_error =
"Recycled objects index file was not specified";
	    return(objects_removed);
	}

	/* Get the recycle bin directory and create it as needed */
	recbin_dir = g_dirname(index_path);
	if(recbin_dir != NULL)
	{
	    if(rmkdir((const char *)recbin_dir, S_IRUSR | S_IWUSR | S_IXUSR))
 	    {
		last_error =
"Unable to create the recycle bin directory";
		g_free(recbin_dir);
		return(objects_removed);
	    }
	    g_free(recbin_dir);
	}

	/* Make coppies of input and output recycle bin index file names */
	in_file = STRDUP(index_path);
	out_file = g_strdup_printf("%s_", index_path);

	/* Open the output file for writing */
	fp = fopen((const char *)out_file, "wb");
	if(fp == NULL)
	{
	    last_error =
"Unable to open the output recycled objects index file for writing";
	    g_free(in_file);
	    g_free(out_file);
	    return(objects_removed);
	}

	/* Open the input recycle bin index file */
	rbi_ptr = EDVRecBinIndexOpen(in_file);

	/* Iterate through all all the indices in the input file */
	while(!EDVRecBinIndexNext(rbi_ptr))
	{
	    if(rbi_ptr->index != index)
		EDVRecBinIndexWriteObjectIterate(
		    fp, rbi_ptr->index, rbi_ptr->obj
		);
	    else
		objects_removed++;
	}
	EDVRecBinIndexClose(rbi_ptr);

	/* Close the output recycle bin index file */
	fclose(fp);

	/* Remove the old input recycle bin index file (if any) */
	UNLINK(in_file);

	/* Rename the output recycle bin index file to the input (true)
	 * recycle bin index file
	 */
	if(rename((const char *)out_file, (const char *)in_file))
	{
	    last_error =
"Unable to rename the output recycle bin index file to the input recycle bin index file";
	    g_free(in_file);
	    g_free(out_file);
	    return(objects_removed);
	}

	g_free(in_file);
	g_free(out_file);

	return(objects_removed);
}


/*
 *	Used by EDVRecBinIndexNext() and EDVRecBinObjectGetFromIndexFile().
 *
 *	Reads all subsequent parameters from the opened recycle bin
 *	index file specified by fp and loads them to the specified
 *	recycled object.
 *
 *	The fp will be positioned just after the next end of object
 *	block.
 *
 *	Inputs assumed valid.
 */
static gint EDVRecBinObjectGetFromIndexFileIterate(
	FILE *fp, edv_recycled_object_struct *obj
)
{
	gint status = 0;
	gchar *parm = NULL;

	/* Begin reading the file */
	while(TRUE)
	{
	    /* Read the next parameter */
	    parm = (gchar *)FSeekNextParm(
		fp, (char *)parm,
		EDV_CFG_COMMENT_CHAR,
		EDV_CFG_DELIMINATOR_CHAR
	    );
	    if(parm == NULL)
	    {
		status = -1;
		break;
	    }

	    /* Begin handling by parameter */

	    /* Name */
	    else if(!g_strcasecmp(parm, "Name"))
	    {
		g_free(obj->name);
		obj->name = (gchar *)FGetString(fp);
	    }
	    /* OriginalPath */
	    else if(!g_strcasecmp(parm, "OriginalPath"))
	    {
		g_free(obj->original_path);
		obj->original_path = (gchar *)FGetString(fp);
	    }
	    /* DateDeleted */
	    else if(!g_strcasecmp(parm, "DateDeleted"))
	    {
		glong vl[1];
		FGetValuesL(fp, (long *)vl, 1);
		obj->deleted_time = (gulong)vl[0];
	    }
	    /* Type */
	    else if(!g_strcasecmp(parm, "Type"))
	    {
		gint vi[1];
		FGetValuesI(fp, (int *)vi, 1);
		obj->type = (edv_object_type)vi[0];
	    }
	    /* LinkedTo */
	    else if(!g_strcasecmp(parm, "LinkedTo"))
	    {
	        /* This link destination is a simplified version and is
		 * not to be considered the actual (unmodified) link
		 * destination value (which is obtained from the
		 * recycled file of the link)
		 */
		g_free(obj->link_value);
		obj->link_value = (gchar *)FGetString(fp);
	    }
	    /* Permissions */
	    else if(!g_strcasecmp(parm, "Permissions"))
	    {
		gint vi[1];
		FGetValuesI(fp, (int *)vi, 1);
		obj->permissions = (edv_permission_flags)vi[0];
	    }
	    /* AccessTime */
	    else if(!g_strcasecmp(parm, "AccessTime"))
	    {
		glong vl[1];
		FGetValuesL(fp, (long *)vl, 1);
		obj->access_time = (gulong)vl[0];
	    }
	    /* ModifyTime */
	    else if(!g_strcasecmp(parm, "ModifyTime"))
	    {
		glong vl[1];
		FGetValuesL(fp, (long *)vl, 1);
		obj->modify_time = (gulong)vl[0];
	    }
	    /* ChangeTime */
	    else if(!g_strcasecmp(parm, "ChangeTime"))
	    {
		glong vl[1];
		FGetValuesL(fp, (long *)vl, 1);
		obj->change_time = (gulong)vl[0];
	    }
	    /* OwnerID */
	    else if(!g_strcasecmp(parm, "OwnerID"))
	    {
		gint vi[1];
		FGetValuesI(fp, (int *)vi, 1);
		obj->owner_id = vi[0];
	    }
	    /* GroupID */
	    else if(!g_strcasecmp(parm, "GroupID"))
	    {
		gint vi[1];
		FGetValuesI(fp, (int *)vi, 1);
		obj->group_id = vi[0];
	    }
	    /* Size */
	    else if(!g_strcasecmp(parm, "Size"))
	    {
		glong vl[1];
		FGetValuesL(fp, (long *)vl, 1);
		obj->size = (gulong)vl[0];
	    }

	    /* EndRecycledObject */
	    else if(!g_strcasecmp(parm, "EndRecycledObject"))
	    {
		/* Skip this value and seek to the next line and
		 * then stop reading
		 */
		FSeekNextLine(fp);
		break;
	    }
	    else
	    {
		/* Unsupported parameter (ignore this) */
		FSeekNextLine(fp);
	    }
	}

	/* Delete the parameter */
	g_free(parm);

	return(status);
}

/*
 *	Gets the recycled object values.
 *
 *	The index_path specifies the recycled objects index file.
 *
 *	The index specifies the index of recycled object who stats are
 *	to be obtained.
 *
 *	The obj specifies the buffer in which to store the recycled
 *	object values in to.
 *
 *	Returns 0 on success or non-zero if the recycled object
 *	specified by index does not exist or an error occured.
 */
gint EDVRecBinObjectGetFromIndexFile(
	const gchar *index_path,		/* Recycle bin index file */
	const guint index,		/* Index of the recycled object */
	edv_recycled_object_struct *obj	/* Storage */
)
{
	gint status;
	FILE *fp;
	gchar *parm = NULL;

	last_error = NULL;

	if(STRISEMPTY(index_path))
	{
	    last_error =
"Recycled objects index file was not specified";
	    return(-2);
	}

	if(obj == NULL)
	{
	    last_error =
"Buffer to store the recycled object's values to was not specified";
	    return(-2);
	}

	/* Open the recycle bin index file for reading */
	fp = fopen((const char *)index_path, "rb");
	if(fp == NULL)
	    return(-1);

	/* Begin reading the recycle bin index file */
	status = -1;
	while(TRUE)
	{
	    /* Read the next parameter */
	    parm = (gchar *)FSeekNextParm(
		fp, (char *)parm,
		EDV_CFG_COMMENT_CHAR,
		EDV_CFG_DELIMINATOR_CHAR
	    );
	    if(parm == NULL)
		break;

	    /* Begin handling by parameter */

	    /* BeginRecycledObject */
	    if(!g_strcasecmp(parm, "BeginRecycledObject"))
	    {
		gint vi[1];

		FGetValuesI(fp, (int *)vi, 1);

		/* This index value matches the specified index? */
		if((guint)vi[0] == index)
		{
		    /* Read all subsequent parameters from the current
		     * fp position loading them to the recycled object
		     *
		     * Stopping just after the next end of object block
 		     */
		    obj->index = (guint)vi[0];
		    EDVRecBinObjectGetFromIndexFileIterate(fp, obj);

		    /* Mark that we matched this object by its index */
		    status = 0;

		    /* Stop reading once this object has been loaded */
		    break;
		}
	    }
	    else
	    {
		/* Other parameter (ignore) */
		FSeekNextLine(fp);
	    }
	}

	/* Delete the parameter */
	g_free(parm);

	/* Close the recycle bin index file */
	fclose(fp);

	return(status);
}


/*
 *	Puts the object into the recycle bin.
 *
 *	The index_path specifies the recycled objects index file, the
 *	recycle bin index file will not be updated.
 *
 *	The index specifies the index of the recycled object to put
 *	the specified object under. If a recycled object already exists
 *	for the specified index then it will be overwritten.
 *
 *	If progress_cb is not NULL then it will be called during the
 *	operation to report the progress. If it returns non-zero then
 *	the operation will be aborted.
 *
 *	The data specifies the user data which will be passed to the
 *	progress_cb function.
 */
gint EDVRecBinDiskObjectDelete(
	const gchar *index_path,
	const guint index,		/* Put into recycle bin index file under this index */
	const gchar *path,		/* Object to delete */
	gint (*progress_cb)(gpointer, const gulong, const gulong),
	gpointer data
)
{
	gint status;
	gchar	*in_path = NULL,
		*out_path = NULL,
		*recbin_dir = NULL;
	struct stat in_lstat_buf;

	last_error = NULL;

	if(STRISEMPTY(index_path))
	{
	    last_error =
"Recycled objects index file was not specified";
	    return(-1);
	}

	if(index == 0)
	{
	    last_error =
"Invalid recycled object index";
	    return(-2);
	}

	if(STRISEMPTY(path))
	{
	    last_error =
"Object to delete was not specified";
	    return(-1);
	}

#define DO_FREE_LOCALS	{	\
 g_free(in_path);		\
 g_free(out_path);		\
 g_free(recbin_dir);		\
}


	/* Get the recycle bin directory */
	recbin_dir = g_dirname(index_path);
	if(recbin_dir == NULL)
	{
	    last_error =
"Unable to obtain the recycle bin directory";
	    DO_FREE_LOCALS
	    return(-2);
	}

	/* Create the recycle bin directory as needed */
	if(rmkdir((const char *)recbin_dir, S_IRUSR | S_IWUSR | S_IXUSR)) 
	{
	    last_error =
"Unable to create the recycle bin directory";
	    DO_FREE_LOCALS
	    return(-1);
	}

	/* Generate the full path to the object to delete */
	in_path = STRDUP(path);
	if(in_path == NULL)
	{
	    last_error =
"Unable to generate the full path of the object to delete";
	    DO_FREE_LOCALS
	    return(-2);
	}

	/* Generate path to output object */
	out_path = g_strdup_printf(
	    "%s%c%i",
	    recbin_dir, G_DIR_SEPARATOR, index
	);
	if(out_path == NULL)
	{
	    last_error =
"Unable to generate the path of the recycled object";
	    DO_FREE_LOCALS
	    return(-2);
	}

	/* Make sure that the object to delete exists and get its
	 * statistics
	 */
	if(lstat((const char *)in_path, &in_lstat_buf))
	{
	    last_error =
"Unable to get the statistics of the object to delete";
	    DO_FREE_LOCALS
	    return(-1);
	}

	status = 0;

	/* Copy in_path to out_path by in_path's type */

	/* Regular file? */
#ifdef S_ISREG
	if(S_ISREG(in_lstat_buf.st_mode))
#else
	if(TRUE)
#endif
	{
	    gint out_fd;
	    FILE *in_fp, *out_fp;
	    struct stat tar_stat_buf;
	    const gulong file_size = (gulong)in_lstat_buf.st_size;
	    gulong read_buf_len = (gulong)in_lstat_buf.st_blksize;
	    guint8 *read_buf;

	    /* Report the initial progress */
	    if((progress_cb != NULL) && (status == 0))
	    {
		if(progress_cb(data, 0l, file_size))
		{
		    DO_FREE_LOCALS
		    return(-4);
		}
	    }

	    /* Open the file for reading */
	    in_fp = fopen((const char *)in_path, "rb");
	    if(in_fp == NULL)
	    {
		last_error = "Unable to open the file for reading";
		DO_FREE_LOCALS
		return(-1);
	    }

	    /* Report progress */
	    if((progress_cb != NULL) && (status == 0))
	    {
		if(progress_cb(data, 0l, file_size))
		{
		    fclose(in_fp);
		    DO_FREE_LOCALS
		    return(-4);
		}
	    }

	    /* Open the recycled file for writing */
	    out_fp = fopen((const char *)out_path, "wb");
	    if(out_fp == NULL)
	    {
		last_error = "Unable to open the recycled file for writing";
		fclose(in_fp);
		DO_FREE_LOCALS
		return(-1);
	    }

	    /* Report progress */
	    if((progress_cb != NULL) && (status == 0))
	    {
		if(progress_cb(data, 0l, file_size))
		{
		    fclose(in_fp);
		    fclose(out_fp);
		    DO_FREE_LOCALS
		    return(-4);
		}
	    }

	    out_fd = fileno(out_fp);

	    /* Get the recycled file's statistics */
	    if(!fstat(out_fd, &tar_stat_buf))
	    {
		/* Use a smaller read buffer? */
		if((gulong)tar_stat_buf.st_blksize < read_buf_len)
		    read_buf_len = (gulong)tar_stat_buf.st_blksize;
	    }

	    /* Set the recycled file's permissions so that only
	     * the owner can read and write to it
	     */
	    if(fchmod(out_fd, S_IRUSR | S_IWUSR))
	    {
		last_error = "Unable to set the recycled file's permissoins";
		fclose(in_fp);
		fclose(out_fp);
		DO_FREE_LOCALS
		return(-1);
	    }

	    /* Allocate the read buffer */
	    read_buf = (read_buf_len > 0) ?
		(guint8 *)g_malloc(read_buf_len) : NULL;

	    /* Copy one block at a time? */
	    if(read_buf != NULL)
	    {
		gint units_read;
		gulong cur_size = 0l;

		while(!feof(in_fp))
		{
		    /* Read the next block from the source file */
		    units_read = (gint)fread(
			read_buf, sizeof(guint8), (size_t)read_buf_len, in_fp
		    );
		    if(units_read <= 0)
		    {
			if(units_read < 0)
			{
			    /* Read error */
			    last_error =
"An error occured while reading from the file to be deleted";
			    status = -1;
			}
			break;
		    }

		    /* Write this block to the recycled file */
		    if(fwrite(
			read_buf, sizeof(guint8), (size_t)units_read, out_fp
		    ) != (size_t)units_read)
		    {
			last_error =
"An error occured while writing to the recycled file";
			status = -1;
			break;
		    }

		    cur_size += (gulong)(units_read * sizeof(guint8));

		    /* Report progress? */
		    if(progress_cb != NULL)
		    {
			if(progress_cb(data, cur_size, file_size))
			{
			    status = -4;
			    break;
			}
		    }
		}

		/* Delete the read buffer */
		g_free(read_buf);
	    }
	    else
	    {
		/* Copy one character at a time */
		gint c;
		gulong copy_cnt = 0l, cur_size = 0l;

		while(!feof(in_fp))
		{
		    /* Read the next character from the source file */
		    c = (gint)fgetc(in_fp);
		    if((int)c == EOF)
			break;

		    /* Write this character to the target file */
		    if(fputc((int)c, out_fp) == EOF)
		    {
			last_error =
"An error occured while writing to the recycled file";
			status = -1;
			break;
		    }

		    copy_cnt++;
		    cur_size++;

		    /* Time to report progress? */
		    if(copy_cnt >= 10000l)
		    {
			if(progress_cb != NULL)
			{
			    if(progress_cb(data, cur_size, file_size))
			    {
				status = -4;
				break;
			    }
			}
			copy_cnt = 0l;
		    }
		}
	    }

	    /* Close the files */
	    fclose(in_fp);
	    fclose(out_fp);

	    /* Report the final progress */
	    if((progress_cb != NULL) && (status == 0))
	    {
		if(progress_cb(data, file_size, file_size))
		    status = -4;
	    }

	    /* If the user aborted then the recycled file needs to
	     * be removed
	     */
	    if(status == -4)
		UNLINK(out_path);

	    /* Remove the file to be deleted */
	    if(status == 0)
	    {
		if(UNLINK(in_path))
		{
		    last_error = "Unable to delete the file";
		    UNLINK(out_path);
		    DO_FREE_LOCALS
		    return(-1);
		}
	    }
	}
#ifdef S_ISDIR
	/* Directory? */
	else if(S_ISDIR(in_lstat_buf.st_mode))
	{
	    FILE *out_fp;

	    /* Report the initial progress */
	    if((progress_cb != NULL) && (status == 0))
	    {
		if(progress_cb(data, 0l, 1l))
		{
		    DO_FREE_LOCALS
		    return(-4);
		}
	    }

	    /* Open the recycled file for writing */
	    out_fp = fopen((const char *)out_path, "wb");
	    if(out_fp == NULL)
	    {
		last_error = "Unable to open the recycled file for writing";
		DO_FREE_LOCALS
		return(-1);
	    }

	    /* Do not store any data in the output file for
	     * directories
	     */

	    /* Close the recycled file */
	    fclose(out_fp);

	    /* Report the final progress */
	    if((progress_cb != NULL) && (status == 0))
	    {
		if(progress_cb(data, 1l, 1l))
		    status = -4;
	    }

	    /* If the user aborted then the recycled file needs to
	     * be removed
	     */
	    if(status == -4)
		UNLINK(out_path);

	    /* Remove the directory */
	    if(status == 0)
	    {
		if(rmdir((const char *)in_path))
		{
		    last_error = "Unable to delete the directory";
		    UNLINK(out_path);
		    DO_FREE_LOCALS
		    return(-1);
		}
	    }
	}
#endif
#ifdef S_ISLNK
	/* Link? */
	else if(S_ISLNK(in_lstat_buf.st_mode))
	{
	    gchar *v;

	    /* Report the initial progress */
	    if((progress_cb != NULL) && (status == 0))
	    {
		if(progress_cb(data, 0l, 2l))
		{
		    DO_FREE_LOCALS
		    return(-4);
		}
	    }

	    /* Get the link's destination value */
	    v = EDVGetLinkValue(in_path);
	    if(v == NULL)
	    {
		last_error = "Unable to get the link's destination value";
		DO_FREE_LOCALS
                return(-1);
	    }

	    /* Report progress */
	    if((progress_cb != NULL) && (status == 0))
	    {
		if(progress_cb(data, 1l, 2l))
		    status = -4;
	    }

	    if(status == 0)
	    {
		/* Open the recycled file for writing */
		FILE *out_fp = fopen((const char *)out_path, "wb");
		if(out_fp == NULL)
		{
		    last_error = "Unable to open the recycled file for writing";
		    g_free(v);
		    DO_FREE_LOCALS
		    return(-1);
		}

		/* Write the link's destination */
		if(fputs((const char *)v, out_fp) == EOF)
		{
		    last_error =
"An error occured while writing to the recycled file";
		    fclose(out_fp);
		    g_free(v);
		    DO_FREE_LOCALS
		    return(-1);
		}

		/* Close the output file */
		fclose(out_fp);
	    }

	    /* Delete the link's value */
	    g_free(v);

	    /* Report the final progress */
	    if((progress_cb != NULL) && (status == 0))
	    {
		if(progress_cb(data, 2l, 2l))
		    status = -4;
	    }

	    /* If the user aborted then the recycled file needs to
	     * be removed
	     */
	    if(status == -4)
		UNLINK(out_path);

	    /* Remove the link */
	    if(status == 0)
	    {
		if(UNLINK(in_path))
		{
		    last_error = "Unable to remove the link";
		    UNLINK(out_path);
		    DO_FREE_LOCALS
		    return(-1);
		}
	    }
	}
#endif
#ifdef S_ISFIFO
	/* FIFO pipe? */
	else if(S_ISFIFO(in_lstat_buf.st_mode))
	{
	    /* Report the initial progress */
	    if((progress_cb != NULL) && (status == 0))
	    {
		if(progress_cb(data, 0l, 2l))
		{
		    DO_FREE_LOCALS
		    return(-4);
		}
	    }


	    /* Report progress */
	    if((progress_cb != NULL) && (status == 0))
	    {
		if(progress_cb(data, 1l, 2l))
		    status = -4;
	    }

	    if(status == 0)
	    {
		/* Open the recycled file for writing */
		FILE *out_fp = fopen((const char *)out_path, "wb");
		if(out_fp == NULL)
		{
		    last_error = "Unable to open the recycled file for writing";
		    DO_FREE_LOCALS
		    return(-1);
		}

		/* Do not write any data to the output file for FIFO
		 * pipes
		 */

		/* Close the output file */
		fclose(out_fp);
	    }

	    /* Report the final progress */
	    if((progress_cb != NULL) && (status == 0))
	    {
		if(progress_cb(data, 2l, 2l))
		    status = -4;
	    }

	    /* If the user aborted then the recycled file needs to
	     * be removed
	     */
	    if(status == -4)
		UNLINK(out_path);

	    /* Remove the FIFO pipe */
	    if(status == 0)
	    {
		if(UNLINK(in_path))
		{
		    last_error = "Unable to delete the FIFO pipe";
		    UNLINK(out_path);
		    DO_FREE_LOCALS
		    return(-1);
		}
	    }
	}
#endif
#ifdef S_ISBLK
	/* Block device? */
	else if(S_ISBLK(in_lstat_buf.st_mode))
	{
	    gint major, minor;

	    /* Report the initial progress */
	    if((progress_cb != NULL) && (status == 0))
	    {
		if(progress_cb(data, 0l, 2l))
		{
		    DO_FREE_LOCALS
		    return(-4);
		}
	    }

	    /* Get block device's major and minor numbers */
	    EDVGetDeviceNumbers(
		(gint)in_lstat_buf.st_rdev, &major, &minor
	    );

	    /* Report progress */
	    if((progress_cb != NULL) && (status == 0))
	    {
		if(progress_cb(data, 1l, 2l))
		    status = -4;
	    }

	    if(status == 0)
	    {
		/* Open the recycled file for writing */
		FILE *out_fp = fopen((const char *)out_path, "wb");
		if(out_fp == NULL)
		{
		    last_error = "Unable to open the recycled file for writing";
		    DO_FREE_LOCALS
		    return(-1);
		}

		/* Write the major and minor numbers to recycled file */
		if(fwrite(&major, sizeof(major), 1, out_fp) < 1)
		{
		    last_error =
"An error occured while writing to the recycled file";
		    fclose(out_fp);
		    DO_FREE_LOCALS
		    return(-1);
		}
		if(fwrite(&minor, sizeof(minor), 1, out_fp) < 1)
		{
		    last_error =
"An error occured while writing to the recycled file";
		    fclose(out_fp);
		    DO_FREE_LOCALS
		    return(-1);
		}

		/* Close the output file */
		fclose(out_fp);
	    }

	    /* Report the final progress */
	    if((progress_cb != NULL) && (status == 0))
	    {
		if(progress_cb(data, 2l, 2l))
		    status = -4;
	    }

	    /* If the user aborted then the recycled file needs to
	     * be removed
	     */
	    if(status == -4)
		UNLINK(out_path);

	    /* Remove the block device */
	    if(status == 0)
	    {
		if(UNLINK(in_path))
		{
		    last_error = "Unable to delete the block device";
		    UNLINK(out_path);
		    DO_FREE_LOCALS
		    return(-1);
		}
	    }
	}
#endif
#ifdef S_ISCHR
	/* Character device? */
	else if(S_ISCHR(in_lstat_buf.st_mode))
	{
	    gint major, minor;

	    /* Report the initial progress */
	    if((progress_cb != NULL) && (status == 0))
	    {
		if(progress_cb(data, 0l, 2l))
		{
		    DO_FREE_LOCALS
		    return(-4);
		}
	    }

	    /* Get the character device's major and minor numbers */
	    EDVGetDeviceNumbers(
		(gint)in_lstat_buf.st_rdev, &major, &minor
	    );

	    /* Report progress */
	    if((progress_cb != NULL) && (status == 0))
	    {
		if(progress_cb(data, 1l, 2l))
		    status = -4;
	    }

	    if(status == 0)
	    {
		/* Open the recycled file for writing */
		FILE *out_fp = fopen((const char *)out_path, "wb");
		if(out_fp == NULL)
		{
		    last_error = "Unable to open the recycled file for writing";
		    DO_FREE_LOCALS
		    return(-1);
		}

		/* Write the major and minor numbers to recycled file */
		if(fwrite(&major, sizeof(major), 1, out_fp) < 1)
		{
		    last_error =
"An error occured while writing to the recycled file";
		    fclose(out_fp);
		    DO_FREE_LOCALS
		    return(-1);
		}
		if(fwrite(&minor, sizeof(minor), 1, out_fp) < 1)
		{
		    last_error =
"An error occured while writing to the recycled file";
		    fclose(out_fp);
		    DO_FREE_LOCALS
		    return(-1);
		}

		/* Close the output file */
		fclose(out_fp);
	    }

	    /* Report the final progress */
	    if((progress_cb != NULL) && (status == 0))
	    {
		if(progress_cb(data, 2l, 2l))
		    status = -4;
	    }

	    /* If the user aborted then the recycled file needs to
	     * be removed
	     */
	    if(status == -4)
		UNLINK(out_path);

	    /* Remove the character device */
	    if(status == 0)
	    {
		if(UNLINK(in_path))
		{
		    last_error = "Unable to delete the character device";
		    UNLINK(out_path);
		    DO_FREE_LOCALS
		    return(-1);
		}
	    }
	}
#endif
#ifdef S_ISSOCK
	/* Socket? */
	else if(S_ISSOCK(in_lstat_buf.st_mode))
	{
	    /* Report the initial progress */
	    if((progress_cb != NULL) && (status == 0))
	    {
		if(progress_cb(data, 0l, 1l))
		{
		    DO_FREE_LOCALS
		    return(-4);
		}
	    }

	    if(status == 0)
	    {
		/* Open the recycled file for writing */
		FILE *out_fp = fopen((const char *)out_path, "wb");
		if(out_fp == NULL)
		{
		    last_error = "Unable to open the recycled file for writing";
		    DO_FREE_LOCALS
		    return(-1);
		}

		/* Do not write any data to for sockets */

		/* Close the recycled file */
		fclose(out_fp);
	    }

	    /* Report the final progress */
	    if((progress_cb != NULL) && (status == 0))
	    {
		if(progress_cb(data, 1l, 1l))
		    status = -4;
	    }

	    /* If the user aborted then the recycled file needs to
	     * be removed
	     */
	    if(status == -4)
		UNLINK(out_path);

	    /* Remove the socket */
	    if(status == 0)
	    {
		if(UNLINK(in_path))
		{
		    last_error = "Unable to delete the socket";
		    UNLINK(out_path);
		    DO_FREE_LOCALS
		    return(-1);
		}
	    }
	}
#endif
	/* Unsupported type? */
	else
	{
	    last_error = "Unsupported object type";
	    DO_FREE_LOCALS
	    return(-2);
	}

	DO_FREE_LOCALS
	return(status);
#undef DO_FREE_LOCALS
}

/*
 *	Recovers the recycled object.
 *
 *	The index_path specifies the recycled objects index file, the
 *	recycle bin index file will not be updated.
 *
 *	The index specifies the recycled object to recover.
 *
 *	The path specifies the full path of the recovered object. This
 *	object must not already exist or else the recovery will fail.
 *	If path is NULL then the recycled object's original location
 *	and name will be used.
 *
 *	If progress_cb is not NULL then it will be called during the
 *	operation to report the progress. If it returns non-zero then
 *	the operation will be aborted.
 *
 *	The data specifies the user data which will be passed to the
 *	progress_cb function.
 */
gint EDVRecBinDiskObjectRecover(
	const gchar *index_path,
	const guint index,		/* Index of the recycled object */
	const gchar *path,		/* Alternate recovery location (can
					 * be NULL) */
	gint (*progress_cb)(gpointer, const gulong, const gulong),
	gpointer data
)
{
	gint status;
	gboolean	restore_permissions,
			restore_ownership,
			restore_time;
	edv_recycled_object_struct *obj = NULL;
	gchar	*in_path = NULL,
		*out_path = NULL,
		*recbin_dir = NULL;
	struct stat in_lstat_buf, out_lstat_buf;

	last_error = NULL;

	if(STRISEMPTY(index_path))
	{
	    last_error =
"Recycled objects index file was not specified";
	    return(-2);
	}

	if(index == 0)
	{
	    last_error =
"Invalid recycled object index";
	    return(-2);
	}

#define DO_FREE_LOCALS	{		\
 g_free(in_path);			\
 g_free(out_path);			\
 g_free(recbin_dir);			\
 EDVRecycledObjectDelete(obj);		\
}

	/* Get the recycled object's stats */
	obj = EDVRecBinObjectStat(index_path, index);
	if(obj == NULL)
	{
	    last_error = "Unable to stat recycled object by the given index";
	    DO_FREE_LOCALS
	    return(-1);
	}

	/* Get the recycle bin directory */
	recbin_dir = g_dirname(index_path);
	if(recbin_dir == NULL)
	{
	    last_error =
"Unable to obtain the recycle bin directory";
	    DO_FREE_LOCALS
	    return(-2);
	}

	/* Generate the full path to the recycled object */
	in_path = g_strdup_printf(
	    "%s%c%i",
	    recbin_dir, G_DIR_SEPARATOR, index
	);
	if(in_path == NULL)
	{
	    last_error =
"Unable to generate the recycled object path";
	    DO_FREE_LOCALS
	    return(-1);
	}

	/* Generate the full path to the recovered object */
	if(path != NULL)
	{
	    /* Use the specified alternate location */
	    out_path = STRDUP(path);
	}
	else
	{
	    /* Use the original location */
	    out_path = STRDUP(PrefixPaths(
		obj->original_path, obj->name
	    ));
	}
	if(out_path == NULL)
	{
	    last_error =
"Unable to generate the path to the recovered object";
	    DO_FREE_LOCALS
	    return(-1);
	}

	/* Make sure that the recycled object exists and that the
	 * recovered object does not exist
	 */
	if(lstat((const char *)in_path, &in_lstat_buf))
	{
	    last_error = "Unable to stat recycled object";
	    DO_FREE_LOCALS
	    return(-1);
	}
	if(!lstat((const char *)out_path, &out_lstat_buf))
	{
	    last_error = "An object already exists at the recovery location";
	    DO_FREE_LOCALS
 	    return(-1);
	}

	status = 0;
	restore_permissions = FALSE;
	restore_ownership = FALSE;
	restore_time = FALSE;

	/* Copy in_path to out_path by object type */

	/* Regular file? */
	if(obj->type == EDV_OBJECT_TYPE_FILE)
	{
	    FILE *in_fp, *out_fp;
	    struct stat tar_stat_buf;
	    const gulong file_size = (gulong)in_lstat_buf.st_size;
	    gulong read_buf_len = (gulong)in_lstat_buf.st_blksize;
	    guint8 *read_buf;

	    /* Report the initial progress */
	    if(progress_cb != NULL)
	    {
		if(progress_cb(data, 0l, file_size))
		{
		    DO_FREE_LOCALS
		    return(-4);
		}
	    }

	    /* Open the recycled file for reading */
	    in_fp = fopen((const char *)in_path, "rb");
	    if(in_fp == NULL)
	    {
		last_error = "Unable to open the recycled file for reading";
		DO_FREE_LOCALS
		return(-1);
	    }

	    /* Report progress */
	    if((progress_cb != NULL) && (status == 0))
	    {
		if(progress_cb(data, 0l, file_size))
		{
		    fclose(in_fp);
		    DO_FREE_LOCALS
		    return(-4);
		}
	    }

	    /* Open the recovered file for writing */
	    out_fp = fopen((const char *)out_path, "wb");
	    if(out_fp == NULL)
	    {
		last_error = "Unable to open the recovered file for writing";
		fclose(in_fp);
		DO_FREE_LOCALS
		return(-1);
	    }

	    /* Report progress */
	    if((progress_cb != NULL) && (status == 0))
	    {
		if(progress_cb(data, 0l, file_size))
		{
		    fclose(in_fp);
		    fclose(out_fp);
		    DO_FREE_LOCALS
		    return(-4);
		}
	    }

	    /* Get the recovered file's statistics */
	    if(!fstat(fileno(out_fp), &tar_stat_buf))
	    {
		/* If target file is on a device that has a smaller
		 * block size io then the io buffer must be shrinked to
		 * match this block size io
		 */
		if((gulong)tar_stat_buf.st_blksize < read_buf_len)
		    read_buf_len = (gulong)tar_stat_buf.st_blksize;
	    }

	    /* Allocate the read buffer */
	    read_buf = (read_buf_len > 0l) ?
		(guint8 *)g_malloc(read_buf_len) : NULL;

	    /* Copy one block at a time? */
	    if(read_buf != NULL)
	    {
		gint units_read;
		gulong cur_size = 0l;

		while(!feof(in_fp))
		{
		    /* Read the next block from the recycled file */
		    units_read = (gint)fread(
			read_buf, sizeof(guint8), (size_t)read_buf_len, in_fp
		    );
		    if(units_read <= 0)
		    {
			if(units_read < 0)
			{
			    /* Read error */
			    last_error =
"An error occured while reading from the recycled file";
			    status = -1;
			}
			break;
		    }

		    /* Write this block to the recovered file */
		    if(fwrite(
			read_buf, sizeof(guint8), (size_t)units_read, out_fp
		    ) != (size_t)units_read)
		    {
			/* Write error */
			last_error =
"An error occured while writing to the recovered file";
			status = -1;
			break;
		    }

		    cur_size += (gulong)(units_read * sizeof(guint8));

		    /* Report progress */
		    if(progress_cb != NULL)
		    {
			if(progress_cb(data, cur_size, file_size))
			{
			    status = -4;
			    break;
			}
		    }
		}

		/* Delete the read buffer */
		g_free(read_buf);
	    }
	    else
	    {
		/* Copy one character at a time */
		gint c;
		gulong copy_cnt = 0l, cur_size = 0l;

		while(!feof(in_fp))
		{
		    /* Read the next character from the source file */
		    c = (gint)fgetc(in_fp);
		    if((int)c == EOF)
			break;

		    /* Write this character to the target file */
		    if(fputc((int)c, out_fp) == EOF)
		    {
			last_error =
"An error occured while writing to the recovered file";
			status = -1;
			break;
		    }

		    copy_cnt++;
		    cur_size++;

		    /* Time to report progress? */
		    if(copy_cnt >= 10000l)
		    {
			/* Report progress */
			if(progress_cb != NULL)
			{
			    if(progress_cb(data, cur_size, file_size))
			    {
				status = -4;
				break;
			    }
			}
			copy_cnt = 0l;
		    }
		}
	    }

	    /* Close the files */
	    fclose(in_fp);
	    fclose(out_fp);

	    /* Report the final progress */
	    if((progress_cb != NULL) && (status == 0))
	    {
		if(progress_cb(data, file_size, file_size))
		    status = -4;
	    }

	    /* If the user aborted then remove the recovered file */
	    if(status == -4)
		UNLINK(out_path);

	    /* Remove the recycled file if there were no errors and
	     * no user abort
	     */
	    if(status == 0)
	    {
		if(UNLINK(in_path))
		{
		    last_error = "Unable to remove the recycled object";
		    DO_FREE_LOCALS
		    return(-1);
		}
	    }

	    restore_permissions = TRUE;
	    restore_ownership = TRUE;
	    restore_time = TRUE;
	}
	/* Directory? */
	else if(obj->type == EDV_OBJECT_TYPE_DIRECTORY)
	{
	    /* Report the initial progress */
	    if(progress_cb != NULL)
	    {
		if(progress_cb(data, 0l, 1l))
		{
		    DO_FREE_LOCALS
		    return(-4);
		}
	    }

	    /* Create the recovered directory */
	    if(mkdir((const char *)out_path, 0))
	    {
		last_error = "Unable to create the recovered directory";
		DO_FREE_LOCALS
		return(-1);
	    }

	    /* Report the final progress */
	    if((progress_cb != NULL) && (status == 0))
	    {
		if(progress_cb(data, 1l, 1l))
		    status = -4;
	    }

	    /* If the user aborted then remove the recovered file */
	    if(status == -4)
		rmdir((const char *)out_path);

	    /* Remove the recycled file if there were no errors and
	     * no user abort
	     */
	    if(status == 0)
	    {
		if(UNLINK(in_path))
		{
		    last_error = "Unable to remove the recycled object";
		    DO_FREE_LOCALS
		    return(-1);
		}
	    }

	    restore_permissions = TRUE;
	    restore_ownership = TRUE;
	    restore_time = TRUE;
	}
	/* Link? */
	else if(obj->type == EDV_OBJECT_TYPE_LINK)
	{
	    FILE *fp;
	    gint bytes_read;
	    const gulong file_size = (gulong)in_lstat_buf.st_size;
	    gchar *dest;

	    /* Report the initial progress */
	    if(progress_cb != NULL)
	    {
		if(progress_cb(data, 0l, file_size))
		{
		    DO_FREE_LOCALS
		    return(-4);
		}
	    }

	    /* Open the recycled file for reading */
	    fp = fopen((const char *)in_path, "rb");
	    if(fp == NULL)
	    {
		last_error = "Unable to open the recycled file for reading";
		DO_FREE_LOCALS
		return(-1);
	    }

	    /* Allocate the link destination buffer */
	    dest = (gchar *)g_malloc(
		(file_size + 1) * sizeof(gchar)
	    );
	    if(dest == NULL)
	    {
		last_error = "Memory allocation error";
		fclose(fp);
		DO_FREE_LOCALS
		return(-3);
	    }

	    /* Read the input file as the link destination value */
	    bytes_read = (gint)fread(
		dest, sizeof(gchar), (size_t)file_size, fp
	    );
	    if(bytes_read < (gint)file_size)
	    {
		last_error = "Unable to read the recycled file";
		fclose(fp);
		g_free(dest);
		DO_FREE_LOCALS
		return(-1);
	    }
	    if(bytes_read < (gint)file_size)
		dest[bytes_read] = '\0';
	    else
		dest[file_size] = '\0';

	    /* Close the recycled file */
	    fclose(fp);

	    /* Report progress */
	    if((progress_cb != NULL) && (status == 0))
	    {
		if(progress_cb(data, file_size, file_size))
		    status = -4;
	    }

	    /* Create link containing the destination value read
	     * from the input file
	     */
	    if(symlink((const char *)dest, (const char *)out_path))
	    {
		last_error = "Unable to create the recovered link";
		g_free(dest);
		DO_FREE_LOCALS
		return(-1);
	    }

	    g_free(dest);

	    /* Report the final progress */
	    if((progress_cb != NULL) && (status == 0))
	    {
		if(progress_cb(data, file_size, file_size))
		    status = -4;
	    }

	    /* If the user aborted then remove the recovered file */
	    if(status == -4)
		UNLINK(out_path);

	    /* Remove the recycled file if there were no errors and
	     * no user abort
	     */
	    if(status == 0)
	    {
		if(UNLINK(in_path))
		{
		    last_error = "Unable to remove the recycled object";
		    DO_FREE_LOCALS
		    return(-1);
		}
	    }

	    /* Do not restore permissions for links */
	    restore_ownership = TRUE;
	    /* Do not restore time for links */
	}
	/* FIFO pipe? */
	else if(obj->type == EDV_OBJECT_TYPE_FIFO)
	{
#if defined(S_IFFIFO) || defined(S_IFIFO)
#ifdef S_IFFIFO
	    const mode_t m = S_IFFIFO | S_IRUSR | S_IWUSR;
#else
	    const mode_t m = S_IFIFO | S_IRUSR | S_IWUSR;
#endif

	    /* Report the initial progress */
	    if(progress_cb != NULL)
	    {
		if(progress_cb(data, 0l, 1l))
		{
		    DO_FREE_LOCALS
		    return(-4);
		}
	    }

	    /* Create the FIFO pipe */
	    if(mknod((const char *)out_path, m, 0))
	    {
		last_error = "Unable to create the recovered FIFO pipe";
		DO_FREE_LOCALS
		return(-1);
	    }

	    /* Report the final progress */
	    if((progress_cb != NULL) && (status == 0))
	    {
		if(progress_cb(data, 1l, 1l))
		    status = -4;
	    }

	    /* If the user aborted then remove the recovered file */
	    if(status == -4)
		UNLINK(out_path);

	    /* Remove the recycled file if there were no errors and
	     * no user abort
	     */
	    if(status == 0)
	    {
		if(UNLINK(in_path))
		{
		    last_error = "Unable to remove the recycled object";
		    DO_FREE_LOCALS
		    return(-1);
		}
	    }

	    restore_permissions = TRUE;
	    restore_ownership = TRUE;
	    restore_time = TRUE;
#else
	    last_error = "Unsupported object type";
	    DO_FREE_LOCALS
	    return(-2);
#endif
	}
	/* Block device? */
	else if(obj->type == EDV_OBJECT_TYPE_DEVICE_BLOCK)
	{
#if defined(S_IFBLK)
	    const mode_t m = S_IFBLK | S_IRUSR | S_IWUSR;
	    FILE *fp;
	    gint major, minor;

	    /* Report the initial progress */
	    if(progress_cb != NULL)
	    {
		if(progress_cb(data, 0l, 2l))
		{
		    DO_FREE_LOCALS
		    return(-4);
		}
	    }

	    /* Open the recycled file for reading */
	    fp = fopen((const char *)in_path, "rb");
	    if(fp == NULL)
	    {
		last_error = "Unable to open the recycled file for reading";
		DO_FREE_LOCALS
		return(-1);
	    }

	    /* Read the major number */
	    if(fread(&major, sizeof(major), 1, fp) < 1)
		major = 0;

	    /* Read the minor number */
	    if(fread(&minor, sizeof(minor), 1, fp) < 1)
		minor = 0;

	    /* Close the input file */
	    fclose(fp);

	    /* Report progress */
	    if((progress_cb != NULL) && (status == 0))
	    {
		if(progress_cb(data, 1l, 2l))
		    status = -4;
	    }

	    if(status == 0)
	    {
		/* Get the device number */
		const gint rdev = EDVFormatDeviceNumbers(major, minor);

		/* Create the block device */
		if(mknod((const char *)out_path, m, (dev_t)rdev))
		{
		    last_error = "Unable to create the recovered block device";
		    DO_FREE_LOCALS
		    return(-1);
		}
	    }

	    /* Report the final progress */
	    if((progress_cb != NULL) && (status == 0))
	    {
		if(progress_cb(data, 2l, 2l))
		    status = -4;
	    }

	    /* If the user aborted then remove the recovered file */
	    if(status == -4)
		UNLINK(out_path);

	    /* Remove the recycled file if there were no errors and
	     * no user abort
	     */
	    if(status == 0)
	    {
		if(UNLINK(in_path))
		{
		    last_error = "Unable to remove the recycled object";
		    DO_FREE_LOCALS
		    return(-1);
		}
	    }

	    restore_permissions = TRUE;
	    restore_ownership = TRUE;
	    restore_time = TRUE;
#else
	    last_error = "Unsupported object type";
	    DO_FREE_LOCALS
	    return(-2);
#endif
	}
	/* Character device? */
	else if(obj->type == EDV_OBJECT_TYPE_DEVICE_CHARACTER)
	{
#if defined(S_IFCHR)
	    const mode_t m = S_IFCHR | S_IRUSR | S_IWUSR;
	    FILE *fp;
	    gint major, minor;

	    /* Report the initial progress */
	    if(progress_cb != NULL)
	    {
		if(progress_cb(data, 0l, 2l))
		{
		    DO_FREE_LOCALS
		    return(-4);
		}
	    }

	    /* Open the recycled file for reading */
	    fp = fopen((const char *)in_path, "rb");
	    if(fp == NULL)
	    {
		last_error = "Unable to open the recycled file for reading";
		DO_FREE_LOCALS
		return(-1);
	    }

	    /* Read the major number */
	    if(fread(&major, sizeof(major), 1, fp) < 1)
		major = 0;

	    /* Read the minor number */
	    if(fread(&minor, sizeof(minor), 1, fp) < 1)
		minor = 0;

	    /* Close the recycled file */
	    fclose(fp);

	    /* Report progress */
	    if((progress_cb != NULL) && (status == 0))
	    {
		if(progress_cb(data, 1l, 2l))
		    status = -4;
	    }

	    if(status == 0)
	    {
		/* Get the device number */
		const gint rdev = EDVFormatDeviceNumbers(major, minor);

		/* Create the character device */
		if(mknod((const char *)out_path, m, (dev_t)rdev))
		{
		    last_error = "Unable to create the recovered character device";
		    DO_FREE_LOCALS
		    return(-1);
		}
	    }

	    /* Report the final progress */
	    if((progress_cb != NULL) && (status == 0))
	    {
		if(progress_cb(data, 2l, 2l))
		    status = -4;
	    }

	    /* If the user aborted then remove the recovered file */
	    if(status == -4)
		UNLINK(out_path);

	    /* Remove the recycled file if there were no errors and
	     * no user abort
	     */
	    if(status == 0)
	    {
		if(UNLINK(in_path))
		{
		    last_error = "Unable to remove the recycled object";
		    DO_FREE_LOCALS
		    return(-1);
		}
	    }

	    restore_permissions = TRUE;
	    restore_ownership = TRUE;
	    restore_time = TRUE;
#else
	    last_error = "Unsupported object type";
	    DO_FREE_LOCALS
	    return(-2);
#endif
	}
	/* Socket? */
	else if(obj->type == EDV_OBJECT_TYPE_SOCKET)
	{
#if defined(S_IFSOCK)
	    const mode_t m = S_IFSOCK | S_IRUSR | S_IWUSR;

	    /* Report the initial progress */
	    if(progress_cb != NULL)
	    {
		if(progress_cb(data, 0l, 1l))
		{
		    DO_FREE_LOCALS
		    return(-4);
		}
	    }

	    /* Create the socket */
	    if(mknod((const char *)out_path, m, 0))
	    {
		last_error = "Unable to create the recovered socket";
		DO_FREE_LOCALS
		return(-1);
	    }

	    /* Report the final progress */
	    if((progress_cb != NULL) && (status == 0))
	    {
		if(progress_cb(data, 1l, 1l))
		    status = -4;
	    }

	    /* If the user aborted then remove the recovered file */
	    if(status == -4)
		UNLINK(out_path);

	    /* Remove the recycled file if there were no errors and
	     * no user abort
	     */
	    if(status == 0)
	    {
		if(UNLINK(in_path))
		{
		    last_error = "Unable to remove the recycled object";
		    DO_FREE_LOCALS
		    return(-1);
		}
	    }

	    restore_permissions = TRUE;
	    restore_ownership = TRUE;
	    restore_time = TRUE;
#else
	    last_error = "Unsupported object type";
	    DO_FREE_LOCALS
	    return(-2);
#endif
	}
	/* Unsupported type? */
	else
	{
	    last_error = "Unsupported object type";
	    DO_FREE_LOCALS
	    return(-2);
	}


	/* Begin restoring properties on the recovered object */

	/* Restore the ownership? */
	if(restore_ownership)
	{
	    lchown(
		(const char *)out_path,
		(uid_t)obj->owner_id, (gid_t)obj->group_id
	    );
	}

	/* Restore the permissions? */
	if(restore_permissions)
	{
	    mode_t m = 0;
	    if(obj->permissions & EDV_PERMISSION_UEXECUTE)
		m |= S_IXUSR;
	    if(obj->permissions & EDV_PERMISSION_UREAD)
		m |= S_IRUSR;
	    if(obj->permissions & EDV_PERMISSION_UWRITE)
		m |= S_IWUSR;
	    if(obj->permissions & EDV_PERMISSION_GEXECUTE)
		m |= S_IXGRP;
	    if(obj->permissions & EDV_PERMISSION_GREAD)
		m |= S_IRGRP;
	    if(obj->permissions & EDV_PERMISSION_GWRITE)
		m |= S_IWGRP;
	    if(obj->permissions & EDV_PERMISSION_AEXECUTE)
		m |= S_IXOTH;
	    if(obj->permissions & EDV_PERMISSION_AREAD)
		m |= S_IROTH;
	    if(obj->permissions & EDV_PERMISSION_AWRITE)
		m |= S_IWOTH;
	    if(obj->permissions & EDV_PERMISSION_SETUID)
		m |= S_ISUID;
	    if(obj->permissions & EDV_PERMISSION_SETGID)
		m |= S_ISGID;
	    if(obj->permissions & EDV_PERMISSION_STICKY)
		m |= S_ISVTX;
	    chmod((const char *)out_path, m);
	}

	/* Restore the time stamps? */
	if(restore_time)
	{
	    struct utimbuf utime_buf;
	    utime_buf.actime = (time_t)obj->access_time;
	    utime_buf.modtime = (time_t)obj->modify_time;
	    utime((const char *)out_path, &utime_buf);
	}

	DO_FREE_LOCALS
	return(status);
#undef DO_FREE_LOCALS
}

/*
 *	Purges the recycled object in the recycle bin.
 *
 *	The index_path specifies the recycled objects index file, the
 *	recycle bin index file will not be updated.
 *
 *	The index specifies the recycled object to purge.
 *
 *	If progress_cb is not NULL then it will be called during the
 *	operation to report the progress. If it returns non-zero then
 *	the operation will be aborted.
 *
 *	The data specifies the user data which will be passed to the
 *	progress_cb function.
 */
gint EDVRecBinDiskObjectPurge(
	const gchar *index_path,	/* Recycle bin index file */
	const guint index,		/* Index of the recycled object */
 	gint (*progress_cb)(gpointer, const gulong, const gulong),
	gpointer data
)
{
	gint status;
	gchar	*in_path = NULL,
		*recbin_dir = NULL;

	last_error = NULL;

	if(STRISEMPTY(index_path))
	{
	    last_error =
"Recycled objects index file not specified";
	    return(-2);
	}

	if(index == 0)
	{
	    last_error =
"Invalid recycled object index";
	    return(-2);
	}

#define DO_FREE_LOCALS	{	\
 g_free(in_path);		\
 in_path = NULL;		\
 g_free(recbin_dir);		\
 recbin_dir = NULL;		\
}

	/* Get the recycle bin directory */
	recbin_dir = g_dirname(index_path);
	if(recbin_dir == NULL)
	{
	    last_error =
"Unable to obtain the recycle bin directory";
	    DO_FREE_LOCALS
	    return(-2);
	}

	/* Create the recycled objects directory as needed */
	if(rmkdir(recbin_dir, S_IRUSR | S_IWUSR | S_IXUSR))
	{
	    last_error =
"Unable to create the recycled objects directory";
	    DO_FREE_LOCALS
	    return(-1);
	}

	/* Generate the full path to the recycled object */
	in_path = g_strdup_printf(
	    "%s%c%i",
	    recbin_dir, G_DIR_SEPARATOR, index
	);
	if(in_path == NULL)
	{
	    last_error =
"Unable to generate recycled object path";
	    DO_FREE_LOCALS
	    return(-1);
	}

	status = 0;

	/* Remove the recycled object */
	if(TRUE)
	{
	    /* Report initial progress? */
	    if((progress_cb != NULL) && (status == 0))
	    {
		/* Update progress and check for user abort */
		if(progress_cb(data, 0, 1))
		    status = -4;
	    }

	    /* Remove original file specified by in_path if user did not
	     * abort and there were no errors
	     */
	    if(status == 0)
	    {
		/* Remove recycled object, ignore any errors */
		UNLINK(in_path);
	    }

	    /* Report completed progress? */
	    if((progress_cb != NULL) && (status == 0))
	    {
		/* Update progress and check for user abort */
		if(progress_cb(data, 1, 1))
		    status = -4;
	    }
	}

	DO_FREE_LOCALS
	return(status);
#undef DO_FREE_LOCALS
}

/*
 *	Purges all the recycled objects in the recycle bin.
 *
 *	This is similar to EDVRecBinDiskObjectPurge() except that it
 *	purges of the all recycled objects in a single and more
 *	efficient pass.
 *
 *	The index_path specifies the recycled objects index file, the
 *	recycle bin index file will not be updated.
 *
 *	If progress_cb is not NULL then it will be called during the
 *	operation to report the progress. If it returns non-zero then
 *	the operation will be aborted.
 *
 *	The data specifies the user data which will be passed to the
 *	progress_cb function.
 */
gint EDVRecBinDiskObjectPurgeAll(
	const gchar *index_path,
	gint (*progress_cb)(gpointer, const gulong, const gulong),
	gpointer data
)
{
	gint status, strc;
	gchar **strv, *recbin_dir;

	last_error = NULL;

	if(STRISEMPTY(index_path))
	{
	    last_error =
"Recycled objects index file not specified";
	    return(-2);
	}

#define DO_FREE_LOCALS	{		\
 g_free(recbin_dir);			\
 recbin_dir = NULL;			\
}

	/* Get the recycle bin directory */
	recbin_dir = g_dirname(index_path);
	if(recbin_dir == NULL)
	{
	    last_error =
"Unable to obtain the recycle bin directory";
	    DO_FREE_LOCALS
	    return(-2);
	}

	/* Create the recycled objects directory as needed */
	if(rmkdir(recbin_dir, S_IRUSR | S_IWUSR | S_IXUSR))
	{
	    last_error =
"Unable to create the recycled objects directory";
	    DO_FREE_LOCALS
	    return(-1);
	}

	status = 0;

	/* Get all contents of the recycled objects directory, which
	 * should include the recycled objects index file
	 */
	strv = GetDirEntNames2(recbin_dir, &strc);
	if(strv != NULL)
	{
	    gint i;
	    const gchar *name;
	    gchar *full_path = NULL;

	    /* Report initial progress? */
	    if((progress_cb != NULL) && (status == 0))
	    {
		/* Report progress and check for user abort */
		if(progress_cb(data, 0, strc))
		    status = -4;
	    }

	    /* Iterate through each entry in the recycled objects
	     * directory and remove it
	     */
	    for(i = 0; i < strc; i++)
	    {
#define DO_FREE_CONTINUE	{	\
 g_free(full_path);			\
 full_path = NULL;			\
 g_free(strv[i]);			\
 strv[i] = NULL;			\
 continue;				\
}
		name = strv[i];
		if(name == NULL)
		    DO_FREE_CONTINUE

		/* If there was an error or user aborted then just
		 * deallocate this directory entry name and all
		 * subsequent ones
		 */
		if(status != 0)
		    DO_FREE_CONTINUE

		/* Skip special directory notations */
		if(!strcmp(name, "..") ||
		   !strcmp(name, ".")
		)
		    DO_FREE_CONTINUE

		/* Generate full path to current directory entry */
		full_path = STRDUP(PrefixPaths(recbin_dir, name));
		if(full_path == NULL)
		    DO_FREE_CONTINUE

		/* Report progress? */
		if(progress_cb != NULL)
		{
		    /* Report progress and check for user abort */
		    if(progress_cb(data, i, strc))
		    {
			status = -4;
			DO_FREE_CONTINUE
		    }
		}

		/* Remove object specified by the full path of the
		 * directory entry name
		 */
		if(UNLINK(full_path))
		{
		    last_error = "Unable to remove recycled object";
		    status = -1;
		    DO_FREE_CONTINUE
		}

		DO_FREE_CONTINUE
#undef DO_FREE_CONTINUE
	    }

	    /* Report final progress? */
	    if((progress_cb != NULL) && (status == 0))
	    {
		/* Report progress and check for user abort */
		if(progress_cb(data, strc, strc))
		    status = -4;
	    }

	    /* Delete directory entry names pointer array, but not
	     * each name in the array since they have already been
	     * deleted
	     */
	    g_free(strv);
	}

	DO_FREE_LOCALS
	return(status);
#undef DO_FREE_LOCALS
}
