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

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

#include "edvtypes.h"
#include "edvobj.h"
#include "edvrecbin.h"
#include "edvrecbinfio.h"
#include "edvutils.h"
#include "edvutilsgtk.h"
#include "config.h"


static gchar *last_error = NULL;


const gchar *EDVRecBinFIOGetError(void);

/* Recycled Index File IO */
gint EDVRecBinFIOTotalItems(const gchar *filename);
guint *EDVRecBinFIOGetIndexList(
	const gchar *filename, gint *total
);

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

edv_recbin_index_struct *EDVRecBinIndexOpen(
	const gchar *filename
);
gint EDVRecBinIndexNext(edv_recbin_index_struct *rbi_ptr);
void EDVRecBinIndexClose(edv_recbin_index_struct *rbi_ptr);

guint EDVRecBinIndexAdd(
	const gchar *filename, const edv_recbin_object_struct *obj
);
gint EDVRecBinIndexRemove(
	const gchar *filename, guint index
);

/* Get Recycled Object Stats */
static gint EDVRecBinObjectGetFromIndexFileIterate(
	FILE *fp,
	edv_recbin_object_struct *obj
);
static gint EDVRecBinObjectGetFromIndexFile(
	const gchar *filename,		/* recycled.ini */
	guint index,
	edv_recbin_object_struct *obj
);
edv_recbin_object_struct *EDVRecBinObjectStat(
	const gchar *filename,		/* recycled.ini */
	guint index			/* Index of object stats to load */
);

/* Delete, Recover, and Purge */
gint EDVRecBinDiskObjectDelete(
	const gchar *index_filename,	/* recycled.ini */
	guint index,			/* Put into recycled index file under this index */
	const gchar *path,		/* Object to delete */
	gint (*progress_cb)(gpointer, gulong, gulong),
	gpointer client_data
);
gint EDVRecBinDiskObjectRecover(
	const gchar *index_filename,	/* recycled.ini */
	guint index,			/* Recycled object to recover */
	const gchar *path,		/* Alternate recovery dir if not NULL */
	gint (*progress_cb)(gpointer, gulong, gulong),
	gpointer client_data
);
gint EDVRecBinDiskObjectPurge(
	const gchar *index_filename,	/* recycled.ini */
	guint index,			/* Recycled object to recover */
	gint (*progress_cb)(gpointer, gulong, gulong),
	gpointer client_data
);
gint EDVRecBinDiskObjectPurgeAll(
	const gchar *index_filename,	/* recycled.ini */
	gint (*progress_cb)(gpointer, gulong, gulong),
	gpointer client_data
);


#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 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 *EDVRecBinFIOGetError(void)
{
	return(last_error);
}

/*
 *	Quickly gets a directory listing of the recycled objects
 *	directory specified by the recycled objects index file filename
 *	then returns the number of recycled objects.
 */
gint EDVRecBinFIOTotalItems(const gchar *filename)
{
	gint total;
	gchar *parent_path;

	if(STRISEMPTY(filename))
	    return(0);

	/* Get parent directory of the recycled objects index file */
	parent_path = g_dirname(filename);
	if(parent_path == NULL)
	    return(0);

	/* Get total number of entries in the recycled objects
	 * directory
	 */
	total = NUMDIRCONTENTS(parent_path);

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

	g_free(parent_path);

	return(total);
}

/*
 *	Returns a dynamically allocated list of indexes loaded from the
 *	recycled objects index file specified by filename.
 *
 *	The calling function must deallocate the returned list of
 *	recycled object indexes.
 */
guint *EDVRecBinFIOGetIndexList(
	const gchar *filename, gint *total
)
{
	guint *index = NULL;
	gint total_indices = 0;
	FILE *fp;


	if(STRISEMPTY(filename) || (total == NULL))
	    return(index);

	*total = total_indices;		/* Reset total */

	/* Open recycled objects index file */
	fp = FOpen((const char *)filename, "rb");
	if(fp == NULL)
	    return(index);

	/* Begin reading recycled objects index file */
	while(TRUE)
	{
	    /* Seek to next start of recycled object block parameter */
	    if(FSeekToParm(
		fp,
		"BeginRecycledObject",
		EDV_CFG_COMMENT_CHAR, EDV_CFG_DELIMINATOR_CHAR
	    ))
		break;

	    if(TRUE)
	    {
		gint i;
		gint value[1];

		/* Get value of start of recycled object block
		 * parameter which is the index of the recycled object
		 */
		FGetValuesI(fp, value, 1);

		/* Allocate more pointers */
		i = total_indices;
		total_indices = i + 1;
		index = (guint *)g_realloc(
		    index, total_indices * sizeof(guint)
		);
		if(index == NULL)
		{
		    total_indices = 0;
		    break;
		}
		else
		{
		    index[i] = (guint)value[0];
		}
	    }
	}

	/* Close recycled objects index file */
	FClose(fp);

	*total = total_indices;		/* Update total */

	return(index);
}


/*
 *	Used by EDVRecBinIndexAdd() and EDVRecBinIndexRemove().
 *
 *	Writes one recycled object block of the specifed recycled object
 *	obj to the recycled index file opened for writing specified by
 *	fp.
 */
static void EDVRecBinIndexWriteObjectIterate(
	FILE *fp, guint index, const edv_recbin_object_struct *obj
)
{
	/* BeginRecycledObject */
	fprintf(
	    fp,
	    "BeginRecycledObject = %i\n",
	    (int)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->date_deleted
	);
	/* Type */
	fprintf(
	    fp,
	    "\tType = %i\n",
	    (int)obj->type
	);
	/* LinkedTo */
	if(!STRISEMPTY(obj->linked_to))
	{
	    /* 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->linked_to), *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);
	}
	else
	{
	    fprintf(
		fp,
		"\tLinkedTo = \n"
	    );
	}
	/* 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 recycled index file specified by filename and returns
 *	a handle that will be passed to subsequent calls to
 *	EDVRecBinIndexNext() and EDVRecBinIndexClose().
 *
 *	EDVRecBinIndexNext() must be called immediatly after calling
 *	this function to obtain the first recycled object.
 *
 *	Can return NULL on error.
 */
edv_recbin_index_struct *EDVRecBinIndexOpen(
	const gchar *filename
)
{
	FILE *fp;
	edv_recbin_index_struct *rbi_ptr;

	if(STRISEMPTY(filename))
	    return(NULL);

	/* Open the recycled index file for reading */
	fp = FOpen((const char *)filename, "rb");
	if(fp == NULL)
	    return(NULL);

	/* Create a new recycled index handle  */
	rbi_ptr = EDV_RECBIN_INDEX(g_malloc0(
	    sizeof(edv_recbin_index_struct)
	));
	if(rbi_ptr == NULL)
	{
	    FClose(fp);
	    return(rbi_ptr);
	}

	/* Update values */
	rbi_ptr->fp = fp;
	rbi_ptr->index = 0;
	rbi_ptr->obj = EDVRecBinObjectNew();

	return(rbi_ptr);
}

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

	if(rbi_ptr == NULL)
	    return(-1);

	fp = rbi_ptr->fp;
	obj = rbi_ptr->obj;
	if((fp == NULL) || (obj == NULL))
	    return(-1);

	/* Begin reading recycled index file */
	status = 0;
	while(TRUE)
	{
	    /* Read 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 value[1];

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

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

		/* 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)value[0];
		EDVRecBinObjectGetFromIndexFileIterate(fp, obj);

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

	/* Delete parameter */
	g_free(parm);

	return(status);
}

/*
 *	Closes the recycled index file and deletes the recbin index
 *	handle.
 */
void EDVRecBinIndexClose(edv_recbin_index_struct *rbi_ptr)
{
	if(rbi_ptr == NULL)
	    return;

	/* Close recycled index file */
	if(rbi_ptr->fp != NULL)
	    FClose(rbi_ptr->fp);

	/* Delete context recycled object structure */
	EDVRecBinObjectDelete(rbi_ptr->obj);

	g_free(rbi_ptr);
}


/*
 *	Adds the values of the specified recycled object to the recycled
 *	index file. The given recycled object structure will not be
 *	modified.
 *
 *	Returns the index of the new entry or 0 on error.
 */
guint EDVRecBinIndexAdd(
	const gchar *filename, const edv_recbin_object_struct *obj
)
{
	gint i, total;
	guint *index, cur_index, new_index = 0;
	FILE *fp;
	gchar *parent_path;

	if(STRISEMPTY(filename))
	    return(0);

	/* Get list of indices in the recycled objects index file */
	index = EDVRecBinFIOGetIndexList(filename, &total);

	/* 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(i = 0; i < total; i++)
	    {
		if(cur_index == index[i])
		    break;
	    }
	    /* Was cur_index not in the list? */
	    if(i >= total)
	    {
		new_index = cur_index;
		break;
	    }
	}

	/* Delete index list */
	g_free(index);


	/* No more indexes available? */
	if(new_index == 0)
	    return(0);


	/* Begin adding a new recycled object entry to the recycled
	 * objects index file
	 */

	/* Get parent directory and create it as needed */
	parent_path = g_dirname(filename);
	if(parent_path != NULL)
	{
	    rmkdir((const char *)parent_path, S_IRUSR | S_IWUSR | S_IXUSR);
	    g_free(parent_path);
	    parent_path = NULL;
	}

	/* Open recycled index file for write append */
	fp = FOpen((const char *)filename, "ab");
	if(fp != NULL)
	{
	    /* Write given recycled object structure to recycled
	     * index file
	     */
	    if(obj != NULL)
		EDVRecBinIndexWriteObjectIterate(
		    fp, new_index, obj
		);

	    FClose(fp);
	}

	return(new_index);
}

/*
 *	Removes the entry in the given recycled index file.
 *
 *	Returns the number of occurances removed.
 */
gint EDVRecBinIndexRemove(
	const gchar *filename, guint index
)
{
	gchar	*parent_path,
		*out_file,
		*in_file;
	FILE *fp;
	edv_recbin_index_struct *rbi_ptr;
	gint objects_removed = 0;

	if(STRISEMPTY(filename))
	    return(objects_removed);

	/* Get parent directory and create it as needed */
	parent_path = g_dirname(filename);
	if(parent_path != NULL)
	{
	    rmkdir((const char *)parent_path, S_IRUSR | S_IWUSR | S_IXUSR);
	    g_free(parent_path);
	    parent_path = NULL;
	}


	/* Make coppies of input and output recycled index file names */
	in_file = STRDUP(filename);
	out_file = STRDUP(filename);
	out_file = G_STRCAT(out_file, "_");

	/* Open output file for writing */
	fp = FOpen((const char *)out_file, "wb");

	/* Open input file */
	rbi_ptr = EDVRecBinIndexOpen(in_file);
	/* Iterate through all indexes */
	while(!EDVRecBinIndexNext(rbi_ptr))
	{
	    if(rbi_ptr->index != index)
		EDVRecBinIndexWriteObjectIterate(
		    fp, rbi_ptr->index, rbi_ptr->obj
		);
	    else
		objects_removed++;
	}
	EDVRecBinIndexClose(rbi_ptr);
	rbi_ptr = NULL;

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

	/* Remove input file and rename output file to input file */
	UNLINK(in_file);
	rename((const char *)out_file, (const char *)in_file);

	/* Delete coppied recycled index file names */
	g_free(out_file);
	g_free(in_file);

	return(objects_removed);
}


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

	/* Begin reading file */
	while(TRUE)
	{
	    /* Read next parameter */
	    parm = FSeekNextParm(
		fp, 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 = (char *)FGetString(fp);
	    }
	    /* OriginalPath */
	    else if(!g_strcasecmp(parm, "OriginalPath"))
	    {
		g_free(obj->original_path);
		obj->original_path = (char *)FGetString(fp);
	    }
	    /* DateDeleted */
	    else if(!g_strcasecmp(parm, "DateDeleted"))
	    {
		glong value[1];
		FGetValuesL(fp, (long *)value, 1);
		obj->date_deleted = (gulong)value[0];
	    }
	    /* Type */
	    else if(!g_strcasecmp(parm, "Type"))
	    {
		gint value[1];
		FGetValuesI(fp, (int *)value, 1);
		obj->type = (edv_object_type)value[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->linked_to);
		obj->linked_to = (char *)FGetString(fp);
	    }
	    /* Permissions */
	    else if(!g_strcasecmp(parm, "Permissions"))
	    {
		gint value[1];
		FGetValuesI(fp, (int *)value, 1);
		obj->permissions = (edv_permission_flags)value[0];
	    }
	    /* AccessTime */
	    else if(!g_strcasecmp(parm, "AccessTime"))
	    {
		glong value[1];
		FGetValuesL(fp, (long *)value, 1);
		obj->access_time = (gulong)value[0];
	    }
	    /* ModifyTime */
	    else if(!g_strcasecmp(parm, "ModifyTime"))
	    {
		glong value[1];
		FGetValuesL(fp, (long *)value, 1);
		obj->modify_time = (gulong)value[0];
	    }
	    /* ChangeTime */
	    else if(!g_strcasecmp(parm, "ChangeTime"))
	    {
		glong value[1];
		FGetValuesL(fp, (long *)value, 1);
		obj->change_time = (gulong)value[0];
	    }
	    /* OwnerID */
	    else if(!g_strcasecmp(parm, "OwnerID"))
	    {
		gint value[1];
		FGetValuesI(fp, (int *)value, 1);
		obj->owner_id = value[0];
	    }
	    /* GroupID */
	    else if(!g_strcasecmp(parm, "GroupID"))
	    {
		gint value[1];
		FGetValuesI(fp, (int *)value, 1);
		obj->group_id = value[0];
	    }
	    /* Size */
	    else if(!g_strcasecmp(parm, "Size"))
	    {
		glong value[1];
		FGetValuesL(fp, (long *)value, 1);
		obj->size = (gulong)value[0];
	    }

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

	/* Delete parameter */
	g_free(parm);

	return(status);
}


/*
 *	Gets values from the specified recycled objects index file and
 *	stores them on the recycled object specified by obj.
 *
 *	Returns non-zero if the recycled object specified by index does
 *	not exist or an error occured.
 *
 *	Inputs assumed valid.
 */
static gint EDVRecBinObjectGetFromIndexFile(
	const gchar *filename,          /* recycled.ini */
	guint index,
	edv_recbin_object_struct *obj
)
{
	gint status = -1;
	gchar *parm = NULL;

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

	/* Begin reading recycled index file */
	while(TRUE)
	{
	    /* Read 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 value[1];

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

		/* This index value matches the specified index? */
		if((guint)value[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)value[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 parameter */
	g_free(parm);

	/* Close recycled objects index file */
	FClose(fp);

	return(status);
}


/*
 *      Returns a new recbin object structure containing the information
 *      from the recycled object specified by index from the recycled index
 *      file filename.
 */
edv_recbin_object_struct *EDVRecBinObjectStat(
	const gchar *filename,          /* recycled.ini */
	guint index                     /* Index of object stats to load */
)
{
	edv_recbin_object_struct *obj;

	if(STRISEMPTY(filename))
	    return(NULL);

	/* Create new recycled object and get values for it */
	obj = EDVRecBinObjectNew();

	/* Open recycled objects index file and get values for the
	 * recycled object specified by index, storing the values
	 * in the given recycled object
	 */
	if(EDVRecBinObjectGetFromIndexFile(filename, index, obj))
	{
	    /* Error loading object from recycled index file */
	    EDVRecBinObjectDelete(obj);
	    return(NULL);
	}

	return(obj);
}


/*
 *	Puts the object specified by path to the recycled objects 
 *	directory under the index specified by index. If a recycled object
 *	who's name matches the given index already exists then it will be
 *	overwritten.
 *
 *	The index file will not be updated.
 *
 *      Returns the following error codes:
 *
 *      0       Success
 *      -1      General error
 *      -2      Ambiguous or permission denied
 *      -3      Systems error
 *      -4      Progress callback responded with cancel
 */
gint EDVRecBinDiskObjectDelete(
	const gchar *index_filename,    /* recycled.ini */
	guint index,                    /* Put into recycled index file under this index */
	const gchar *path,              /* Object to delete */
	gint (*progress_cb)(gpointer, gulong, gulong),
	gpointer client_data
)
{
	gint status;
	gchar	*in_path = NULL,
		*out_path = NULL,
		*recycled_dir = NULL;
	struct stat in_lstat_buf;

	/* Reset last error message */
	last_error = NULL;

	if(STRISEMPTY(index_filename) || STRISEMPTY(path))
	{
	    last_error = "Bad input value";
	    return(-1);
	}

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


	/* Get parent directory of the recycled index file path, this will
	 * be the recycled objects directory
	 */
	recycled_dir = EDVRecBinGetDirectoryFromIndexPath(index_filename);
	if(recycled_dir == NULL)
	{
	    DO_FREE_LOCALS
	    last_error =
"Unable to obtain recycled objects directory from recycled index file path";
	    return(-2);
	}

	/* Create recycled objects directory as needed */
	rmkdir((const char *)recycled_dir, S_IRUSR | S_IWUSR | S_IXUSR);


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

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


	/* Make sure input object path exists */
	if(lstat((const char *)in_path, &in_lstat_buf))
	{
	    DO_FREE_LOCALS
	    last_error = "Unable to stat object to delete";
	    return(-2);
	}


	status = 0;


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

	/* Regular file? */
	if(S_ISREG(in_lstat_buf.st_mode))
	{
	    guint8 *io_buf;
	    gulong	file_size = in_lstat_buf.st_size,
			io_buf_len = in_lstat_buf.st_blksize;
	    FILE	*in_fp = FOpen((const char *)in_path, "rb"),
			*out_fp = FOpen((const char *)out_path, "wb");
	    struct stat tar_stat_buf;


	    /* Successfully opened files? */
	    if(in_fp == NULL)
	    {
		DO_FREE_LOCALS
		FClose(in_fp);
		FClose(out_fp);
		last_error = "Unable to open file for reading";
		return(-1);
	    }
	    if(out_fp == NULL)
	    {
		DO_FREE_LOCALS
		FClose(in_fp);
		FClose(out_fp);
		last_error = "Unable to open recycled file for writing";
		return(-1);
	    }

	    /* Report initial progress? */
	    if((progress_cb != NULL) && !status)
	    {
		/* Update progress and check for abort */
		if(progress_cb(client_data, 0l, file_size))
		    status = -4;
	    }

	    /* Get target io buffer size */
	    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(tar_stat_buf.st_blksize < io_buf_len)
		    io_buf_len = tar_stat_buf.st_blksize;
	    }

	    /* Allocate io buffer (if io buffer size is known) */
	    if(io_buf_len > 0)
		io_buf = (guint8 *)g_malloc(io_buf_len * sizeof(guint8));
	    else
		io_buf = NULL;

	    /* Copy by blocks (using fread())? */
	    if(io_buf != NULL)
	    {
		/* Copy by blocks (using fread()) */
		gulong bc = 0l, bp = 0l;

		/* Read first block from source file */
		gulong bytes_read = (gulong)fread(
		    io_buf, sizeof(guint8), (size_t)io_buf_len, in_fp
		);
		while((bytes_read > 0) && !status)
		{
		    /* Write block to target file */
		    if(fwrite(io_buf, sizeof(guint8), (size_t)bytes_read, out_fp)
			!= (size_t)(bytes_read * sizeof(guint8))
		    )
		    {
			/* Update write error code */
			last_error = "Error writing recycled file";
			status = -1;
			break;
		    }

		    /* Increment byte count and position */
		    bc += bytes_read;
		    bp += bytes_read;

		    /* Get next block from source file */
		    bytes_read = (gulong)fread(
			io_buf, sizeof(guint8), (size_t)io_buf_len, in_fp
		    );

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

		    bc = 0l;	/* Reset byte count */
		}
	    }
	    else
	    {
		/* Copy one character at a time (using fgetc()) */
		gulong bc = 0l, bp = 0l;

		/* Read first character from source file */
		gint c = (gint)fgetc(in_fp);
		while(((int)c != EOF) && !status)
		{
		    /* Write character to target file */
		    if(fputc((int)c, out_fp) == EOF)
		    {
			/* Update write error code */
			last_error = "Error writing recycled file";
			status = -1;
			break;
		    }

		    /* Increment byte count and position */
		    bc++;
		    bp++;

		    /* Get next character on source file */
		    c = (gint)fgetc(in_fp);

		    /* Time to report progress? */
		    if(bc >= 10000l)
		    {
			if(progress_cb != NULL)
			{
			    if(progress_cb(client_data, bp, file_size))
			    {
				status = -4;
				break;
			    }
			}

			bc = 0l;	/* Reset byte count */
		    }
		}
	    }

	    /* Close files */
	    FClose(in_fp);
	    FClose(out_fp);

	    /* Delete io buffer (if any) */
	    g_free(io_buf);

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

	    /* Remove original file specified by in_path if user did
	     * not abort and there were no errors
	     */
	    if(!status)
	    {
		if(UNLINK(in_path))
		{
		    /* Error removing original file */
		    DO_FREE_LOCALS
		    switch(errno)
		    {
		      case EFAULT:
			last_error = "Segmentation fault";
			status = -3;
			break;
		      case EACCES:
			last_error =
 "Object's permissions or location does not permit it to be deleted";
			status = -2;
			break;
		      case EPERM:
			last_error =
 "You do not own that object in the tempory files location";
			status = -2;
			break;
		      case ENAMETOOLONG:
			last_error = "Object's path name is too long";
			status = -2;
			break;
		      case ENOENT:
			last_error =
 "A compoent of the object's path does not exist";
			status = -2;
			break;
		      case ENOTDIR:
			last_error =
 "A compoent of the object's path is not a directory";
			status = -2;
			break;
		      case EISDIR:
			last_error =
 "Object's path reffers to a directory object";
			status = -2;
			break;
		      case ENOMEM:
			last_error = "System is out of memory";
			status = -3;
			break;
		      case EROFS:
			last_error =
 "The object exists on a read-only filesystem";
			status = -2;
			break;
		      case ELOOP:
			last_error =
 "Too many symbolic links encountered in the object's path";
			status = -2;
			break;
		      case EIO:
			last_error = "An input/output error occured";
			status = -3;
			break;
		      default:
			last_error = "Unable to delete file";
			status = -1;
			break;
		    }
		    return(status);
		}
	    }
	}
	/* Directory? */
	else if(S_ISDIR(in_lstat_buf.st_mode))
	{
	    /* Report initial progress */
	    if((progress_cb != NULL) && !status)
	    {
		if(progress_cb(client_data, 0l, 3l))
		    status = -4;
	    }

	    /* Write output file */
	    if(!status)
	    {
		/* Open output file for writing */
		FILE *out_fp = FOpen((const char *)out_path, "wb");
		if(out_fp == NULL)
		{
		    DO_FREE_LOCALS
		    last_error = "Unable open recycled file for writing";
		    return(-1);
		}

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

		/* Close file */
		FClose(out_fp);

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

	    /* Remove directory */
	    if(!status)
	    {
		if(rmdir((const char *)in_path))
		{
		    /* Error removing directory */
		    DO_FREE_LOCALS
		    switch(errno)
		    {
		      case ENOTEMPTY:
			last_error = "Directory is not empty";
			status = -2;
			break;
		      default:
			last_error = "Unable to remove directory";
			status = -1;
			break;
		    }
		    return(status);
		}

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

	    /* Report final progress */
	    if((progress_cb != NULL) && !status)
	    {
		/* Update progress and check for abort */
		if(progress_cb(client_data, 3l, 3l))
		    status = -4;
	    }
	}
	/* Link? */
	else if(S_ISLNK(in_lstat_buf.st_mode))
	{
	    /* Report initial progress */
	    if((progress_cb != NULL) && !status)
	    {
		if(progress_cb(client_data, 0l, 3l))
		    status = -4;
	    }

	    /* Write output file */
	    if(!status)
	    {
		FILE *out_fp;
		gchar buf[PATH_MAX + NAME_MAX + 1];


		/* Read link destination value */
		gint bytes_read = (gint)readlink(
		    (const char *)in_path, (char *)buf, sizeof(buf)
		);
		if(bytes_read < 0)
		{
		    DO_FREE_LOCALS
		    last_error = "Unable to read link";
		    return(-1);
		}
		if(bytes_read < sizeof(buf))
		    buf[bytes_read] = '\0';
		else
		    buf[sizeof(buf) - 1] = '\0';

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

		/* Write link destination */
		fputs((const char *)buf, out_fp);

		/* Close files */
		FClose(out_fp);

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

	    /* Remove link */
	    if(!status)
	    {
		if(UNLINK(in_path))
		{
		    DO_FREE_LOCALS
		    last_error = "Unable to delete link";
		    return(-1);
		}

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

	    /* Report final progress */
	    if((progress_cb != NULL) && !status)
	    {
		if(progress_cb(client_data, 3l, 3l))
		    status = -4;
	    }
	}
	/* FIFO pipe? */
	else if(S_ISFIFO(in_lstat_buf.st_mode))
	{
	    /* Report initial progress */
	    if((progress_cb != NULL) && !status)
	    {
		if(progress_cb(client_data, 0l, 3l))
		    status = -4;
	    }

	    /* Write output file */
	    if(!status)
	    {
		/* Open output file for writing */
		FILE *out_fp = FOpen((const char *)out_path, "wb");
		if(out_fp == NULL)
		{
		    DO_FREE_LOCALS
		    last_error = "Unable open recycled file for writing";
		    return(-1);
		}

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

		/* Close files */
		FClose(out_fp);

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

	    /* Remove FIFO pipe */
	    if(!status)
	    {
		if(UNLINK(in_path))
		{
		    DO_FREE_LOCALS
		    last_error = "Unable to delete FIFO pipe";
		    return(-1);
		}

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

	    /* Report final progress */
	    if((progress_cb != NULL) && !status)
	    {
		if(progress_cb(client_data, 3l, 3l))
		    status = -4;
	    }
	}
	/* Block device? */
	else if(S_ISBLK(in_lstat_buf.st_mode))
	{
	    /* Report initial progress */
	    if((progress_cb != NULL) && !status)
	    {
		if(progress_cb(client_data, 0l, 3l))
		    status = -4;
	    }

	    /* Write output file */
	    if(!status)
	    {
		FILE *out_fp;
		gint major, minor;

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

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

		/* Write major and minor numbers to output file */
		fwrite(&major, sizeof(gint), 1, out_fp);
		fwrite(&minor, sizeof(gint), 1, out_fp);

		/* Close files */
		FClose(out_fp);

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

	    /* Remove block device */
	    if(!status)
	    {
		if(UNLINK(in_path))
		{
		    DO_FREE_LOCALS
		    last_error = "Unable to delete block device";
		    return(-1);
		}

		/* Report progress */
		if((progress_cb != NULL) && !status)
		{
		    /* Update progress and check for abort */
		    if(progress_cb(client_data, 2l, 3l))
			status = -4;
		}
	    }

	    /* Report final progress */
	    if((progress_cb != NULL) && !status)
	    {
		if(progress_cb(client_data, 3l, 3l))
		    status = -4;
	    }
	}
	/* Character device? */
	else if(S_ISCHR(in_lstat_buf.st_mode))
	{
	    /* Report initial progress */
	    if((progress_cb != NULL) && !status)
	    {
		if(progress_cb(client_data, 0l, 3l))
		    status = -4;
	    }

	    if(!status)
	    {
		FILE *out_fp;
		gint major, minor;

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

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

		/* Write major and minor numbers to output file */
		fwrite(&major, sizeof(gint), 1, out_fp);
		fwrite(&minor, sizeof(gint), 1, out_fp);

		/* Close files */
		FClose(out_fp);

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

	    /* Remove character device */
	    if(!status)
	    {
		if(UNLINK(in_path))
		{
		    DO_FREE_LOCALS
		    last_error = "Unable to delete character device";
		    return(-1);
		}

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

	    /* Report final progress */
	    if((progress_cb != NULL) && !status)
	    {
		if(progress_cb(client_data, 3l, 3l))
		    status = -4;
	    }
	}
	/* Socket? */
	else if(S_ISSOCK(in_lstat_buf.st_mode))
	{
	    /* Report initial progress */
	    if((progress_cb != NULL) && !status)
	    {
		if(progress_cb(client_data, 0l, 3l))
		    status = -4;
	    }

	    /* Write output file */
	    if(!status)
	    {
		/* Open output file for writing */
		FILE *out_fp = FOpen((const char *)out_path, "wb");
		if(out_fp == NULL)
		{
		    DO_FREE_LOCALS
		    last_error = "Unable open recycled file for writing";
		    return(-1);
		}

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

		/* Close files */
		FClose(out_fp);

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

	    /* Remove socket */
	    if(!status)
	    {
		if(UNLINK(in_path))
		{
		    DO_FREE_LOCALS
		    last_error = "Unable to delete socket";
		    return(-1);
		}

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

	    /* Report final progress */
	    if((progress_cb != NULL) && !status)
	    {
		if(progress_cb(client_data, 3l, 3l))
		    status = -4;
	    }
	}
	/* Unsupported type? */
	else
	{
	    DO_FREE_LOCALS
	    last_error = "Unsupported object type";
	    return(-2);
	}

	DO_FREE_LOCALS
	return(status);
#undef DO_FREE_LOCALS
}

/*
 *	Recovers the object specified by index.
 *
 *	If the given path is NULL then the original location will be used,
 *	otherwise the path will specify a alternate location to recover 
 *	to.
 *
 *	The index file will not be updated.
 *
 *	Returns the following error codes:
 *
 *	0	Success
 *	-1	General error
 *	-2	Ambiguous or permission denied
 *	-3	Systems error
 *	-4	Progress callback responded with cancel
 */
gint EDVRecBinDiskObjectRecover(
	const gchar *index_filename,    /* recycled.ini */
	guint index,                    /* Recycled object to recover */
	const gchar *path,              /* Alternate recovery dir if not NULL */
	gint (*progress_cb)(gpointer, gulong, gulong),
	gpointer client_data
)
{
	gint status;
	gboolean	restore_permissions,
			restore_ownership,
			restore_time;
	edv_recbin_object_struct *obj = NULL;
	gchar	*in_path = NULL,
		*out_path = NULL,
		*recycled_dir = NULL;
	struct stat in_lstat_buf, out_lstat_buf;

	/* Reset last error message */
	last_error = NULL;

	if(index_filename == NULL)
	{
	    last_error = "Bad input value";
	    return(-1);
	}

#define DO_FREE_LOCALS	{		\
 g_free(in_path);			\
 in_path = NULL;			\
					\
 g_free(out_path);			\
 out_path = NULL;			\
					\
 g_free(recycled_dir);			\
 recycled_dir = NULL;			\
					\
 EDVRecBinObjectDelete(obj);		\
 obj = NULL;				\
}

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

	/* Get parent directory of the recycled index file path, this will
	 * be the recycled objects directory
	 */
	recycled_dir = EDVRecBinGetDirectoryFromIndexPath(index_filename);
	if(recycled_dir == NULL)
	{
	    DO_FREE_LOCALS
	    last_error =
"Unable to obtain recycled objects directory from recycled index file path";
	    return(-2);
	}

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

	/* Generate output object full path */
	if(path == NULL)
	{
	    /* Given path is NULL, implying that we use the original
	     * path to generate the output path
	     */
	    out_path = STRDUP(PrefixPaths(
		obj->original_path, obj->name
	    ));
	}
	else
	{
	    out_path = STRDUP(path);
	}
	if(out_path == NULL)
	{
	    DO_FREE_LOCALS
	    last_error =
"Unable to generate recovered object path";
	    return(-2);
	}


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


	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)
	{
	    guint8 *io_buf;
	    gulong      file_size = (gulong)in_lstat_buf.st_size,
			io_buf_len = (gulong)in_lstat_buf.st_blksize;
	    FILE        *in_fp = FOpen((const char *)in_path, "rb"),
			*out_fp = FOpen((const char *)out_path, "wb");
	    struct stat tar_stat_buf;


	    /* Successfully opened files? */
	    if(in_fp == NULL)
	    {
		DO_FREE_LOCALS
		FClose(in_fp);
		FClose(out_fp);
		last_error = "Unable to open recycled file for reading";
		return(-1);
	    }
	    if(out_fp == NULL)
	    {
		DO_FREE_LOCALS
		FClose(in_fp);
		FClose(out_fp);
		last_error = "Unable to open recovered file for writing";
		return(-1);
	    }

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

	    /* Get target io buffer size */
	    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 < io_buf_len)
		    io_buf_len = (gulong)tar_stat_buf.st_blksize;
	    }

	    /* Allocate io buffer (if io buffer size is known) */
	    if(io_buf_len > 0l)
		io_buf = (guint8 *)g_malloc(io_buf_len * sizeof(guint8));
	    else
		io_buf = NULL;

	    /* Copy by blocks (using fread())? */
	    if(io_buf != NULL)
	    {
		/* Copy by blocks (using fread()) */
		gulong bc = 0l, bp = 0l;

		/* Read first block from source file */
		gulong bytes_read = (gulong)fread(
		    io_buf, sizeof(guint8), (size_t)io_buf_len, in_fp
		);
		while((bytes_read > 0) && !status)
		{
		    /* Write block to target file */
		    if(fwrite(io_buf, sizeof(guint8), (size_t)bytes_read, out_fp)
			!= (size_t)(bytes_read * sizeof(guint8))
		    )
		    {
			/* Update write error code */
			last_error = "Error writing recovered file";
			status = -1;
			break;
		    }

		    /* Increment byte count and position */
		    bc += bytes_read;
		    bp += bytes_read;

		    /* Get next block from source file */
		    bytes_read = (gulong)fread(
			io_buf, sizeof(guint8), (size_t)io_buf_len, in_fp
		    );

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

		    bc = 0l;		/* Reset byte count */
		}
	    }
	    else
	    {
		/* Begin one character at a time (using fgetc()) */
		gulong bc = 0l, bp = 0l;

		/* Read first character from source file */
		gint c = (gint)fgetc(in_fp);
		while(((int)c != EOF) && !status)
		{
		    /* Write character to target file */
		    if(fputc((int)c, out_fp) == EOF)
		    {
			/* Update write error code */
			last_error = "Error writing recovered file";
			status = -1;
			break;
		    }

		    /* Increment byte count and position */
		    bc++;
		    bp++;

		    /* Get next character on source file */
		    c = (gint)fgetc(in_fp);

		    /* Time to report progress? */
		    if(bc >= 10000l)
		    {
			/* Report progress */
			if(progress_cb != NULL)
			{
			    if(progress_cb(client_data, bp, file_size))
			    {
				status = -4;
				break;
			    }
			}
			bc = 0l;	/* Reset byte count */
		    }
		}
	    }

	    /* Close files */
	    FClose(in_fp);
	    FClose(out_fp);

	    /* Delete io buffer (if any) */
	    g_free(io_buf);

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

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

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

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

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

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

	    restore_permissions = TRUE;
	    restore_ownership = TRUE;
	    restore_time = TRUE;
	}
	/* Link? */
	else if(obj->type == EDV_OBJECT_TYPE_LINK)
	{
	    /* Report initial progress */
	    if((progress_cb != NULL) && !status)
	    {
		if(progress_cb(client_data, 0l, 1l))
		    status = -4;
	    }

	    if(!status)
	    {
		FILE *fp;
		gint bytes_read;
		gulong file_size = MAX(in_lstat_buf.st_size, 0l);
		gchar *dest = (gchar *)g_malloc(
		    (file_size + 1) * sizeof(gchar)
		);
		if(dest == NULL)
		{
		    DO_FREE_LOCALS
		    last_error = "Memory allocation error";
		    return(-1);
		}

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

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

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

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

		g_free(dest);
	    }

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

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

	    restore_permissions = FALSE;	/* Do not restore permissions for link */
	    restore_ownership = TRUE;
	    restore_time = FALSE;
	}
	/* FIFO pipe? */
	else if(obj->type == EDV_OBJECT_TYPE_FIFO)
	{
	    /* Report initial progress */
	    if((progress_cb != NULL) && !status)
	    {
		if(progress_cb(client_data, 0l, 1l))
		    status = -4;
	    }

	    if(!status)
	    {
		mode_t m = S_IFIFO | S_IRUSR | S_IWUSR;

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

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

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

	    restore_permissions = TRUE;
	    restore_ownership = TRUE;
	    restore_time = TRUE;
	}
	/* Block device? */
	else if(obj->type == EDV_OBJECT_TYPE_DEVICE_BLOCK)
	{
	    /* Report initial progress */
	    if((progress_cb != NULL) && !status)
	    {
		if(progress_cb(client_data, 0l, 1l))
		    status = -4;
	    }

	    if(!status)
	    {
		FILE *in_fp;
		gint rdev, major, minor;
		mode_t m = S_IFBLK | S_IRUSR | S_IWUSR;

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

                /* Read major number */
                if(fread(&major, sizeof(gint), 1, in_fp) <
                    (size_t)(1 * sizeof(gint))
                )
                    major = 0;

                /* Read minor number */
                if(fread(&minor, sizeof(gint), 1, in_fp) <
                    (size_t)(1 * sizeof(gint))
                )
                    minor = 0;

		/* Close input file */
		FClose(in_fp);
		in_fp = NULL;

		/* Format dev to contain the major and minor numbers */
		rdev = EDVFormatDeviceNumbers(major, minor);

		/* Block device with the major and minor numbers taken
		 * from the input file
		 */
		if(mknod((const char *)out_path, m, (dev_t)rdev))
		{
		    DO_FREE_LOCALS
		    last_error = "Unable to create recovered block device";
		    return(-1);
		}
	    }

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

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

	    restore_permissions = TRUE;
	    restore_ownership = TRUE;
	}
	/* Character device? */
	else if(obj->type == EDV_OBJECT_TYPE_DEVICE_CHARACTER)
	{
	    /* Report initial progress */
	    if((progress_cb != NULL) && !status)
	    {
		if(progress_cb(client_data, 0l, 1l))
		    status = -4;
	    }

	    if(!status)
	    {
		FILE *in_fp;
		gint rdev, major, minor;
		mode_t m = S_IFCHR | S_IRUSR | S_IWUSR;

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

		/* Read major number */
		if(fread(&major, sizeof(gint), 1, in_fp) <
		    (size_t)(1 * sizeof(gint))
		)
		    major = 0;

		/* Read minor number */
		if(fread(&minor, sizeof(gint), 1, in_fp) <
		    (size_t)(1 * sizeof(gint))
		)
		    minor = 0;

		/* Close input file */
		FClose(in_fp);

		/* Format dev to contain the major and minor numbers */
		rdev = EDVFormatDeviceNumbers(major, minor);

		/* Character device with the major and minor numbers
		 * taken from the input file
		 */
		if(mknod((const char *)out_path, m, (dev_t)rdev))
		{
		    DO_FREE_LOCALS
		    last_error = "Unable to create recovered character device";
		    return(-1);
		}
	    }

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

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

	    restore_permissions = TRUE;
	    restore_ownership = TRUE;
	}
	/* Socket? */
	else if(obj->type == EDV_OBJECT_TYPE_SOCKET)
	{
	    /* Don't know how to recreate socket objects, so just create
	     * an empty file
	     */
	    FILE *out_fp = FOpen((const char *)out_path, "wb");

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

	    /* Unable to open output file for writing? */
	    if(out_fp == NULL)
	    {
		DO_FREE_LOCALS
		last_error = "Unable to open recovered file for writing";
		return(-1);
	    }

	    /* Do not write anything */

	    FClose(out_fp);


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

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

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


	/* Begin restoring properties on the recovered object */

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

	/* Restore 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 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(out_path, &utime_buf);
	}

	DO_FREE_LOCALS
	return(status);
#undef DO_FREE_LOCALS
}

/*
 *      Purges the recycled object specified by index.
 *
 *      The index file will not be updated.
 *
 *      Returns the following error codes:
 *
 *      0       Success
 *      -1      General error
 *      -2      Ambiguous or permission denied
 *      -3      Systems error
 *      -4      Progress callback responded with cancel
 */
gint EDVRecBinDiskObjectPurge(
	const gchar *index_filename,    /* recycled.ini */
	guint index,                    /* Recycled object to purge */
	gint (*progress_cb)(gpointer, gulong, gulong),
	gpointer client_data
)
{
	gint status;
	gchar	*in_path = NULL,
		*recycled_dir = NULL;


	/* Reset last error message */
	last_error = NULL;


	if(index_filename == NULL)
	{
	    last_error = "Bad input value";
	    return(-1);
	}

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

	/* Get parent directory of the recycled index file path, this will
	 * be the recycled objects directory.
	 */
	recycled_dir = EDVRecBinGetDirectoryFromIndexPath(index_filename);
	if(recycled_dir == NULL)
	{
	    DO_FREE_LOCALS
	    last_error =
"Unable to obtain recycled objects directory from recycled index file path";
	    return(-2);
	}

	/* Create recycled objects directory as needed */
	rmkdir(recycled_dir, S_IRUSR | S_IWUSR | S_IXUSR);


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


	status = 0;


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

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

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

	DO_FREE_LOCALS
	return(status);
#undef DO_FREE_LOCALS
}

/*
 *	Similar to EDVRecBinDiskObjectPurge() except that it purges
 *	all contents in recycled objects directory, including the
 *	recycled objects index file.
 *
 *	Calling this function is more efficient than calling
 *	EDVRecBinDiskObjectPurge() for each recycled object.
 *
 *      Returns the following error codes:
 *
 *      0       Success
 *      -1      General error
 *      -2      Ambiguous or permission denied
 *      -3      Systems error
 *      -4      Progress callback responded with cancel
 */
gint EDVRecBinDiskObjectPurgeAll(
	const gchar *index_filename,    /* recycled.ini */
	gint (*progress_cb)(gpointer, gulong, gulong),
	gpointer client_data
)
{
	gint status, strc;
	gchar **strv;
	gchar *recycled_dir;


	/* Reset last error message */
	last_error = NULL;


	if(STRISEMPTY(index_filename))
	{
	    last_error = "Bad input value";
	    return(-1);
	}

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

	/* Get parent directory of the recycled index file path, this will
	 * be the recycled objects directory
	 */
	recycled_dir = EDVRecBinGetDirectoryFromIndexPath(index_filename);
	if(recycled_dir == NULL)
	{
	    DO_FREE_LOCALS
	    last_error =
"Unable to obtain recycled objects directory from recycled index file path";
	    return(-2);
	}

	/* Create recycled objects directory as needed */
	rmkdir(recycled_dir, S_IRUSR | S_IWUSR | S_IXUSR);


	status = 0;

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


	    /* Report initial progress? */
	    if((progress_cb != NULL) && !status)
	    {
		/* Report progress and check for user abort */
		if(progress_cb(client_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(strv[i]);			\
 strv[i] = NULL;			\
 continue;				\
}
		cstrptr = strv[i];
		if(cstrptr == 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)
		    DO_FREE_CONTINUE

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

		/* Generate full path to current directory entry */
		cstrptr2 = PrefixPaths(recycled_dir, cstrptr);
		if(cstrptr2 == NULL)
		    DO_FREE_CONTINUE

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

		/* Remove object specified by the full path of the
		 * directory entry name
		 */
		if(UNLINK(cstrptr2))
		{
		    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)
	    {
		/* Report progress and check for user abort */
		if(progress_cb(client_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
}
