#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <time.h>
#include <errno.h>
#include <sys/stat.h>
#include <unistd.h>
#include <gtk/gtk.h>

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

#include "guiutils.h"
#include "cdialog.h"
#include "progressdialog.h"

#include "cfg.h"
#include "edvtypes.h"
#include "edvdate.h"
#include "edvobj.h"
#include "endeavour.h"
#include "edvfop.h"
#include "edvutils.h"
#include "edvutilsgtk.h"
#include "edvcfglist.h"
#include "config.h"

#include "images/pdi_file01_20x20.xpm"
#include "images/pdi_file02_20x20.xpm"
#include "images/pdi_file03_20x20.xpm"
#include "images/pdi_file04_20x20.xpm"
#include "images/pdi_file05_20x20.xpm"
#include "images/pdi_file06_20x20.xpm"
#include "images/pdi_folder_32x32.xpm"
#include "images/pdi_folderfile_32x32.xpm"
#include "images/icon_replace_file_32x32.xpm"


/*
 *	Return values for functions that return a gint have the
 *	following meanings:
 *
 *	0	Success (no error)
 *	-1	General error
 *	-2	Ambiguous, bad value, not found, or other error
 *	-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 *EDVFOPGetError();
static gboolean EDVFOPObjectsSame(
	const struct stat *src_stat_buf, const struct stat *tar_stat_buf
);

static gint EDVFOPConfirmOverwrite(
	edv_core_struct *core_ptr, GtkWidget *toplevel,
	const gchar *src_path, const gchar *tar_path,
	const struct stat *src_lstat_buf, const struct stat *tar_lstat_buf
);
static void EDVFOPMapProgressDialogMove(
	const gchar *label, GtkWidget *toplevel, gboolean force_remap
);
static void EDVFOPMapProgressDialogCopy(
	const gchar *label, GtkWidget *toplevel, gboolean force_remap
);

static gint EDVFOPDoUnlink(const gchar *path);
static gint EDVFOPDoRemoveDirectoryIterate(const gchar *path);
static gint EDVFOPDoRemoveDirectory(const gchar *path);

static gint EDVFOPCopyRegular(
	const gchar *src_obj, const gchar *tar_obj,
	const struct stat *src_lstat_buf,
	const struct stat *src_stat_buf
);
static gint EDVFOPCopyLink(
	const gchar *src_obj, const gchar *tar_obj,
	const struct stat *src_lstat_buf,
	const struct stat *src_stat_buf
);
static gint EDVFOPCopyFIFO(
	const gchar *src_obj, const gchar *tar_obj,
	const struct stat *src_lstat_buf,
	const struct stat *src_stat_buf
);
static gint EDVFOPCopyDeviceBlock(
	const gchar *src_obj, const gchar *tar_obj,
	const struct stat *src_lstat_buf,
	const struct stat *src_stat_buf
);
static gint EDVFOPCopyDeviceCharacter(
	const gchar *src_obj, const gchar *tar_obj,
	const struct stat *src_lstat_buf,
	const struct stat *src_stat_buf
);
static gint EDVFOPCopySocket(
	const gchar *src_obj, const gchar *tar_obj,
	const struct stat *src_lstat_buf,
	const struct stat *src_stat_buf
);
static gint EDVFOPCopyNexus(
	const gchar *src_obj, const gchar *tar_obj,
	const struct stat *src_lstat_buf,
	const struct stat *src_stat_buf,
	gboolean archive
);

static gint EDVFOPDoMoveObjectsLocal(
	edv_core_struct *core_ptr,
	const gchar *src_obj, const gchar *tar_obj,
	const struct stat *src_lstat_buf, const struct stat *src_stat_buf,
	GtkWidget *toplevel,
	gboolean show_progress, gboolean interactive, gboolean archive,
	gint *status, gboolean *yes_to_all,
	gboolean *need_copy
);
static gint EDVFOPDoCopyObjectsLocal(
	edv_core_struct *core_ptr,
	const gchar *src_obj, const gchar *tar_obj,
	const struct stat *src_lstat_buf, const struct stat *src_stat_buf,
	GtkWidget *toplevel,
	gboolean show_progress, gboolean interactive, gboolean archive,
	gint *status, gboolean *yes_to_all, gboolean originally_move
);

static gint EDVFOPCopyDirectoryIterate(
	edv_core_struct *core_ptr,
	const gchar *src_dir, const gchar *tar_dir,
	const struct stat *src_lstat_buf, const struct stat *src_stat_buf,
	GtkWidget *toplevel,
	gboolean show_progress, gboolean interactive, gboolean archive,
	gint *status, gboolean *yes_to_all, gboolean originally_move
);

/* Copy & Move */
static gint EDVFOPCopyMove(
	gboolean is_copy,
	edv_core_struct *core_ptr,
	const gchar *src_obj, const gchar *tar_obj,
	gchar **new_obj_rtn, GtkWidget *toplevel,
	gboolean show_progress, gboolean interactive,
	gboolean *yes_to_all
);
gint EDVFOPCopy(
	edv_core_struct *core_ptr,
	const gchar *src_obj, const gchar *tar_obj,
	gchar **new_obj_rtn, GtkWidget *toplevel,
	gboolean show_progress, gboolean interactive,
	gboolean *yes_to_all
);
gint EDVFOPMove(
	edv_core_struct *core_ptr,
	const gchar *src_obj, const gchar *tar_obj,
	gchar **new_obj_rtn, GtkWidget *toplevel,
	gboolean show_progress, gboolean interactive,
	gboolean *yes_to_all
);

/* Link */
static gchar *EDVFOPGenerateNewLinkName(
	const gchar *src_name, const gchar *tar_path
);
static gchar *EDVFOPGenerateLinkDestination(
	const gchar *src_obj, const gchar *tar_path
);
gint EDVFOPLink(
	edv_core_struct *core_ptr,
	const gchar *src_obj, const gchar *tar_obj,
	gchar **new_obj_rtn, GtkWidget *toplevel,
	gboolean show_progress, gboolean interactive,
	gboolean *yes_to_all
);
gint EDVFOPRelink(
	edv_core_struct *core_ptr,
	const gchar *new_dest, const gchar *tar_obj,
	GtkWidget *toplevel,
	gboolean show_progress, gboolean interactive,
	gboolean *yes_to_all
);

/* Rename */
gint EDVFOPRename(
	edv_core_struct *core_ptr,
	const gchar *src_obj, const gchar *new_name,
	gchar **new_obj_rtn, GtkWidget *toplevel,
	gboolean show_progress, gboolean interactive,
	gboolean *yes_to_all
);

/* CHMod */
gint EDVFOPChmod(
	edv_core_struct *core_ptr,
	const gchar *src_obj, guint permissions,
	GtkWidget *toplevel,
	gboolean show_progress, gboolean interactive,
	gboolean *yes_to_all
);

/* Chown */
gint EDVFOPChown(
	edv_core_struct *core_ptr,
	const gchar *src_obj, gint owner_id, gint group_id,
	GtkWidget *toplevel,
	gboolean show_progress, gboolean interactive,
	gboolean *yes_to_all
);


static gchar *last_error = NULL;


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

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


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


/*
 *	Returns the last error message or NULL if there was no error.
 *
 *	The returned pointer must not be deleted.
 */
const gchar *EDVFOPGetError()
{
	return(last_error);
}


/*
 *	Checks if the stats of the two objects are the same by checking
 *	both their devices and inodes.
 */
static gboolean EDVFOPObjectsSame(
	const struct stat *src_stat_buf,
	const struct stat *tar_stat_buf
)
{
	if((src_stat_buf == NULL) || (tar_stat_buf == NULL))
	    return(FALSE);

	if((src_stat_buf->st_dev == tar_stat_buf->st_dev) &&
	   (src_stat_buf->st_ino == tar_stat_buf->st_ino)
	)
	    return(TRUE);
	else
	    return(FALSE);
}


/*
 *	Maps the confirmation dialog and queries user for replacing the
 *	given src_path with the tar_path.
 *
 *	Returns one of CDIALOG_RESPONSE_*.
 */
static gint EDVFOPConfirmOverwrite(
	edv_core_struct *core_ptr, GtkWidget *toplevel,
	const gchar *src_path, const gchar *tar_path,
	const struct stat *src_lstat_buf, const struct stat *tar_lstat_buf
)
{
	gchar *buf, *src_date, *tar_date;
	gint result;
	edv_date_relativity relativity;
	const gchar *format;
	gulong src_size, tar_size;
	gchar src_size_s[256], tar_size_s[256];
	cfg_item_struct *cfg_list;

	if(core_ptr == NULL)
	    return(CDIALOG_RESPONSE_NOT_AVAILABLE);

	cfg_list = core_ptr->cfg_list;

	/* Get date relativity and format */
	relativity = (edv_date_relativity)CFGItemListGetValueI(
	    cfg_list, EDV_CFG_PARM_DATE_RELATIVITY
	);
	format = CFGItemListGetValueS(
	    cfg_list, EDV_CFG_PARM_DATE_FORMAT
	);


	/* Get date strings for source and target objects */
	src_date = STRDUP(EDVDateFormatString(
	    (src_lstat_buf != NULL) ? (gulong)src_lstat_buf->st_mtime : 0l,
	    format, relativity
	));
	tar_date = STRDUP(EDVDateFormatString(
	    (tar_lstat_buf != NULL) ? (gulong)tar_lstat_buf->st_mtime : 0l,
	    format, relativity
	));


	/* Get sizes of source and target objects in units of bytes */
	src_size = (src_lstat_buf != NULL) ?
	    (gulong)src_lstat_buf->st_size : 0l;
	strcpy(
	    (char *)src_size_s,
	    (const char *)EDVGetObjectSizeStr(core_ptr, src_size)
	);

	tar_size = (tar_lstat_buf != NULL) ?
	    (gulong)tar_lstat_buf->st_size : 0l;
	strcpy(
	    (char *)tar_size_s,
	    (const char *)EDVGetObjectSizeStr(core_ptr, tar_size)
	);

	/* Generate message */
	buf = g_strdup_printf(
#if defined(PROG_LANGUAGE_SPANISH)
"Reemplace:\n\
\n\
    %s (%s byte%s) %s\n\
\n\
La Anchura:\n\
\n\
    %s (%s byte%s) %s\n"
#elif defined(PROG_LANGUAGE_FRENCH)
"Remplacer:\n\
\n\
    %s (%s byte%s) %s\n\
\n\
Largeur:\n\
\n\
    %s (%s byte%s) %s\n"
#elif defined(PROG_LANGUAGE_GERMAN)
"Ersetzen:\n\
\n\
    %s (%s byte%s) %s\n\
\n\
Breite:\n\
\n\
    %s (%s byte%s) %s\n"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Sostituire:\n\
\n\
    %s (%s byte%s) %s\n\
\n\
La Larghezza:\n\
\n\
    %s (%s byte%s) %s\n"
#elif defined(PROG_LANGUAGE_DUTCH)
"Vervang:\n\
\n\
    %s (%s byte%s) %s\n\
\n\
Breedte:\n\
\n\
    %s (%s byte%s) %s\n"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Reponha:\n\
\n\
    %s (%s byte%s) %s\n\
\n\
A Largura:\n\
\n\
    %s (%s byte%s) %s\n"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Erstatt:\n\
\n\
    %s (%s byte%s) %s\n\
\n\
Bredde:\n\
\n\
    %s (%s byte%s) %s\n"
#else
"Replace:\n\
\n\
    %s (%s byte%s) %s\n\
\n\
With:\n\
\n\
    %s (%s byte%s) %s\n"
#endif
	    ,
	    tar_path, tar_size_s, (tar_size == 1) ? "" : "s", tar_date,
	    src_path, src_size_s, (src_size == 1) ? "" : "s", src_date
	);

	EDVPlaySoundWarning(core_ptr);
	CDialogSetTransientFor(toplevel);
	result = CDialogGetResponseIconData(
#if defined(PROG_LANGUAGE_SPANISH)
"Confirme Escriba Para Reemplazar"
#elif defined(PROG_LANGUAGE_FRENCH)
"Confirmer Superposer"
#elif defined(PROG_LANGUAGE_GERMAN)
"Bestätigen Sie Überschreibt"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Confermare Sovrascrivere"
#elif defined(PROG_LANGUAGE_DUTCH)
"Bevestiig Beschrijft"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Confirme Overwrite"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Bekreft Overskriver"
#else
"Confirm Overwrite"
#endif
	    , buf, NULL,
	    (guint8 **)icon_replace_file_32x32_xpm,
	    CDIALOG_BTNFLAG_YES | CDIALOG_BTNFLAG_YES_TO_ALL |
	    CDIALOG_BTNFLAG_NO | CDIALOG_BTNFLAG_CANCEL,
	    CDIALOG_BTNFLAG_NO
	);
	CDialogSetTransientFor(NULL);

	g_free(buf);
	g_free(tar_date);
	g_free(src_date);

	return(result);
}


/*
 *      Maps the progress dialog as needed in animation mode for moving.
 */
static void EDVFOPMapProgressDialogMove(
	const gchar *label, GtkWidget *toplevel, gboolean force_remap
)
{
	guint8	**start_icon_data[3],
		**icon_data[6],
		**end_icon_data[3];

	/* Already mapped? */
	if(ProgressDialogIsQuery())
	{
	    /* Check if the progress dialog needs to be unmapped and 
	     * remapped again
	     */
	    if(force_remap)
	    {
		ProgressDialogBreakQuery(FALSE);
	    }
	    else
	    {
		/* Already mapped and does not need unmapping, so just
		 * update the progress message
		 */
		ProgressDialogUpdate(
		    NULL, label, NULL, NULL,
		    0.0f, EDV_DEF_PROGRESS_BAR_TICKS, TRUE
		);
		return;
	    }
	}

	ProgressDialogSetTransientFor(toplevel);

	start_icon_data[0] = (guint8 **)pdi_folderfile_32x32_xpm;
	start_icon_data[1] = (guint8 **)pdi_folder_32x32_xpm;
	start_icon_data[2] = (guint8 **)pdi_folder_32x32_xpm;
	icon_data[0] = (guint8 **)pdi_file01_20x20_xpm;
	icon_data[1] = (guint8 **)pdi_file02_20x20_xpm;
	icon_data[2] = (guint8 **)pdi_file03_20x20_xpm;
	icon_data[3] = (guint8 **)pdi_file04_20x20_xpm;
	icon_data[4] = (guint8 **)pdi_file05_20x20_xpm;
	icon_data[5] = (guint8 **)pdi_file06_20x20_xpm;
	end_icon_data[0] = (guint8 **)pdi_folder_32x32_xpm;
	end_icon_data[1] = (guint8 **)pdi_folder_32x32_xpm;
	end_icon_data[2] = (guint8 **)pdi_folderfile_32x32_xpm;

	ProgressDialogMapAnimation(
#if defined(PROG_LANGUAGE_SPANISH)
	    "Mover",
	    label,
	    "Parada",
#elif defined(PROG_LANGUAGE_FRENCH)
	    "Déménagement",
	    label,
	    "Arrêt",
#elif defined(PROG_LANGUAGE_GERMAN)
	    "Bewegen",
	    label,
	    "Halt",
#elif defined(PROG_LANGUAGE_ITALIAN)
	    "Il Trasloco",
	    label,
	    "Fermata",
#elif defined(PROG_LANGUAGE_DUTCH)
	    "Bewegen",
	    label,
	    "Einde",
#elif defined(PROG_LANGUAGE_PORTUGUESE)
	    "Mover",
	    label,
	    "Parada",
#elif defined(PROG_LANGUAGE_NORWEGIAN)
	    "Flytting",
	    label,
	    "Stans",
#else
	    "Moving",
	    label,
	    "Stop",
#endif
	    start_icon_data, 3,
	    icon_data, 6,
	    end_icon_data, 3,
	    500l,
	    10000
	);
	ProgressDialogUpdate(
	    NULL, NULL, NULL, NULL,
	    0.0f, EDV_DEF_PROGRESS_BAR_TICKS, TRUE
	);

	/* Flush output so dialog gets mapped and we catch the beginning
	 * of the operation (some WM need this)
	 */
	gdk_flush();
}

/*
 *	Maps the progress dialog as needed in animation mode for copying.
 */
static void EDVFOPMapProgressDialogCopy(
	const gchar *label, GtkWidget *toplevel, gboolean force_remap
)
{
	guint8	**start_icon_data[3],
		**icon_data[6],
		**end_icon_data[3];

	/* Already mapped? */
	if(ProgressDialogIsQuery())
	{
	    /* Check if the progress dialog needs to be unmapped and
	     * remapped again
	     */
	    if(force_remap)
	    {
		ProgressDialogBreakQuery(FALSE);
	    }
	    else
	    {
		/* Already mapped and does not need unmapping, so just
		 * update the progress message
		 */
		ProgressDialogUpdate(
		    NULL, label, NULL, NULL,
		    0.0f, EDV_DEF_PROGRESS_BAR_TICKS, TRUE
		);
		return;
	    }
	}

	ProgressDialogSetTransientFor(toplevel);

	start_icon_data[0] = (guint8 **)pdi_folderfile_32x32_xpm;
	start_icon_data[1] = (guint8 **)pdi_folderfile_32x32_xpm;
	start_icon_data[2] = (guint8 **)pdi_folderfile_32x32_xpm;
	icon_data[0] = (guint8 **)pdi_file01_20x20_xpm;
	icon_data[1] = (guint8 **)pdi_file02_20x20_xpm;
	icon_data[2] = (guint8 **)pdi_file03_20x20_xpm;
	icon_data[3] = (guint8 **)pdi_file04_20x20_xpm;
	icon_data[4] = (guint8 **)pdi_file05_20x20_xpm;
	icon_data[5] = (guint8 **)pdi_file06_20x20_xpm;
	end_icon_data[0] = (guint8 **)pdi_folder_32x32_xpm;
	end_icon_data[1] = (guint8 **)pdi_folder_32x32_xpm;
	end_icon_data[2] = (guint8 **)pdi_folderfile_32x32_xpm;

	ProgressDialogMapAnimation(
#if defined(PROG_LANGUAGE_SPANISH)
	    "Copiar",
	    label,
	    "Parada",
#elif defined(PROG_LANGUAGE_FRENCH)
	    "Copier",
	    label,
	    "Arrêt",
#elif defined(PROG_LANGUAGE_GERMAN)
	    "Kopieren",
	    label,
	    "Halt",
#elif defined(PROG_LANGUAGE_ITALIAN)
	    "Copiare",
	    label,
	    "Fermata",
#elif defined(PROG_LANGUAGE_DUTCH)
	    "Kopiëren",
	    label,
	    "Einde",
#elif defined(PROG_LANGUAGE_PORTUGUESE)
	    "Copiar",
	    label,
	    "Parada",
#elif defined(PROG_LANGUAGE_NORWEGIAN)
	    "Kopiering",
	    label,
	    "Stans",
#else
	    "Copying",
	    label,
	    "Stop",
#endif
	    start_icon_data, 3,
	    icon_data, 6,
	    end_icon_data, 3,
	    500l,
	    10000
	);
	ProgressDialogUpdate(
	    NULL, NULL, NULL, NULL,
	    0.0f, EDV_DEF_PROGRESS_BAR_TICKS, TRUE
	);

	/* Flush output so dialog gets mapped and we catch the beginning
	 * of the operation (some WM need this)
	 */
	gdk_flush();
}



/*
 *	Unlinks the object specified by path.
 */
static gint EDVFOPDoUnlink(const gchar *path)
{
	if(STRISEMPTY(path))
	    return(-1);

	if(unlink((const char *)path))
	{
	    switch(errno)
	    {
	      case EFAULT:
		last_error =
"Target object name string memory points outside of accessable address space";
		return(-3);
		break;

	      case EACCES:
		last_error =
"Write access not permitted on the object for this process";
		return(-2);
		break;

	      case EPERM:
		last_error =
"Write access not permitted, object is S_ISVTX but process does not own it";
		return(-2);
		break;

	      case ENAMETOOLONG:
		last_error = "Object path name is too long";
		return(-2);
		break;

	      case ENOENT:	/* Compoent in path does not exist */
	      case ENOTDIR:	/* Compoent in path is not a directory */
		/* These are okay, do not report error for these
		 * since they indicate the object never existed
		 */
		break;

	      case EISDIR:
		last_error = "Unable to remove object of type directory";
		return(-2);
		break;

	      case ENOMEM:
		last_error = "System is out of memory";
		return(-3);
		break;

	      case EROFS:
		last_error =
"Unable to remove target object on a read-only filesystem";
		return(-2);

	      case ELOOP:
		last_error =
"Too many symbolic links encountered while resolving target object";
		return(-2);
		break;

	      case EIO:
		last_error =
"IO error occured while removing the target object";
		return(-1);
		break;

	      default:
		/* Ignore all other errors */
		break;
	    }
	}

	return(0);
}

/*
 *	Called by EDVFOPDoRemoveDirectory() or itself to remove all
 *	contents locally in the given directory specified by path.
 *
 *      Returns non-zero if the directory or one of its contents could not
 *	be removed locally.
 */
static gint EDVFOPDoRemoveDirectoryIterate(const gchar *path)
{
	gchar **strv;
	gint strc, status = 0;

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

	/* Get listing of contents in source directory and begin copying
	 * them to the target directory
	 */
	strv = (gchar **)GetDirEntNames2((const char *)path, (int *)&strc);
	if(strv != NULL)
	{
	    gint i;
	    const gchar *s;
	    gchar *child_path;
	    struct stat lstat_buf;

	    /* Sort strings */
	    strv = (gchar **)StringQSort((char **)strv, (int)strc);
	    if(strv == NULL)
	    {
		last_error = "Unable to sort directory entries";
		return(-1);
	    }

	    /* Iterate through each string */
	    child_path = NULL;
	    for(i = 0; i < strc; i++)
	    {
#define FREE_AND_CONTINUE	{	\
 g_free(child_path);			\
 child_path = NULL;			\
					\
 g_free(strv[i]);			\
 strv[i] = NULL;			\
					\
 continue;				\
}
		s = strv[i];
		if(s == NULL)
		    FREE_AND_CONTINUE

		/* Error encountered? */
		if(status)
		    FREE_AND_CONTINUE

		/* Skip special dir notations */
		if(!strcmp((const char *)s, "..") ||
		   !strcmp((const char *)s, ".")
		)
		    FREE_AND_CONTINUE

		/* Formulate full path to child */
		child_path = STRDUP(PrefixPaths(path, s));
		if(child_path == NULL)
		    FREE_AND_CONTINUE

		/* Get local stats for child */
		if(lstat((const char *)child_path, &lstat_buf))
		    FREE_AND_CONTINUE

		/* Check if child object is locally a directory */
		if(S_ISDIR(lstat_buf.st_mode))
		{
		    /* Recurse into child directory, removing all its
		     * contents and the child directory itself.
		     */
		    status = EDVFOPDoRemoveDirectoryIterate(child_path);
		}
	 	else
		{
		    /* Object is not locally a directory, so just remove
		     * the object
		     */
		    status = EDVFOPDoUnlink(child_path);
		}

		/* Delete coppies of source and target child paths,
		 * along with the current directory entry name and
		 * continue
		 */
		FREE_AND_CONTINUE
#undef FREE_AND_CONTINUE
	    }

	    /* Delete pointer array to directory entry strings */
	    g_free(strv);
	    strv = NULL;
	}

	/* Remove the specified directory */
	if(rmdir((const char *)path))
	{
	    switch(errno)
	    {
	      case EFAULT:
		last_error =
"Directory name string memory points outside of accessable address space";
		status = -3;
		break;

	      case EACCES:
		last_error =
"Removal of directory not permitted for this process";
		status = -3;
		break;

	      case EPERM:
		last_error =
"Write access not permitted, target is S_ISVTX but process does not own it";
		status = -2;
		break;

	      case ENAMETOOLONG:
		last_error = "Directory path name is too long";
		status = -2;
		break;

	      case ENOENT:
		last_error =
"Directory path compoent does not exist";
		status = -2;
		break;

	      case ENOTDIR:
		last_error =
"Directory path compoent is not a directory";
		status = -2;
		break;

	      case ENOTEMPTY:
		last_error =
"Removal of directory failed, directory is not empty";
		status = -2;
		break;

	      case EBUSY:
		last_error =
"Removal of directory failed, another process is referencing this directory";
		status = -3;
		break;

	      case ENOMEM:
		last_error = "System is out of memory";
		status = -3;
		break;

	      case EROFS:
		last_error =
"Unable to remove directory on a read-only filesystem";
		status = -2;
		break;

	      case ELOOP:
		last_error =
"Too many symbolic links encountered while resolving directory";
		status = -2;
		break;

	      default:
		last_error =
"Error encountered while attempting to remove directory";
		status = -1;
		break;
	    }
	}

	return(status);
}

/*
 *      Attempts to remove the directory specified by path, updating
 *      last_error if an error occured.
 *
 *      Returns non-zero if the directory or one of its contents could not
 *      be removed locally.
 */
static gint EDVFOPDoRemoveDirectory(const gchar *path)
{
	gint status;

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

	status = EDVFOPDoRemoveDirectoryIterate(path);

	return(status);
}



/*
 *	Copies the regular object specified by src_obj to tar_obj.
 *
 *	The tar_obj must not exist and the src_obj must be a regular
 *	file.
 *
 *	2003 Sep 1 - Tara Milana (learfox@twu.net) - This is version
 *	3.0 of this function which now uses the filesystem io block
 *	size obtained from the the tar_obj or src_obj stats
 *
 */
static gint EDVFOPCopyRegular(
	const gchar *src_obj, const gchar *tar_obj,
	const struct stat *src_lstat_buf,
	const struct stat *src_stat_buf
)
{
	gint status, write_error_code = 0;
	gulong file_size, io_buf_len;
	guint8 *io_buf;
	FILE *tar_fp, *src_fp;
	struct stat tar_stat_buf;

	/* Open target file for writing */
	tar_fp = FOpen((const char *)tar_obj, "wb");
	if(tar_fp == NULL)
	{
	    last_error = "Unable to open target object for writing";
	    return(-1);
	}

	/* Open source file for reading */
	src_fp = FOpen((const char *)src_obj, "rb");
	if(src_fp == NULL)
	{
	    last_error = "Unable to open source object for reading";
	    FClose(tar_fp);
	    return(-1);
	}

	/* Get source file size and io block size */
	if(src_lstat_buf != NULL)
	{
	    gint tar_fd = (gint)fileno(tar_fp);

	    file_size = (gulong)src_lstat_buf->st_size;
	    io_buf_len = (gulong)src_lstat_buf->st_blksize;

	    /* Set properties on the target file to match the source
	     * file (if possible, ignore errors)
	     */
	    fchmod((int)tar_fd, src_lstat_buf->st_mode);
	    fchown((int)tar_fd, src_lstat_buf->st_uid, src_lstat_buf->st_gid);
	}
	else
	{
	    file_size = 0l;
	    io_buf_len = 0l;
	}

	/* Get target file io block size */
	if(!fstat(fileno(tar_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 block size is known) */
	if(io_buf_len > 0)
	    io_buf = (guint8 *)g_malloc(io_buf_len * sizeof(guint8));
	else
	    io_buf = NULL;

	status = 0;		/* Reset status */

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

	    /* Read first block from source file */
	    gulong bytes_read = (gulong)fread(
		io_buf, sizeof(guint8), (size_t)io_buf_len, src_fp
	    );
	    while(bytes_read > 0)
	    {
		/* Write block to target file */
		if(fwrite(io_buf, sizeof(guint8), (size_t)bytes_read, tar_fp)
		    != (size_t)(bytes_read * sizeof(guint8))
		)
		{
		    /* Update write error code */
		    write_error_code = (gint)ferror(tar_fp);
		    status = -1;
		    break;
		}

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

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

		/* Report progress */
		if(ProgressDialogIsQuery() && (file_size > 0l))
		{
		    ProgressDialogUpdate(
			NULL, NULL,
			NULL, NULL,
			(gfloat)bp / (gfloat)file_size,
			EDV_DEF_PROGRESS_BAR_TICKS,
			TRUE
		    );

		    /* User aborted? */
		    if(ProgressDialogStopCount() > 0)
		    {
			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(src_fp);
	    while((int)c != EOF)
	    {
		/* Write character to target file */
		if(fputc((int)c, tar_fp) == EOF)
		{
		    /* Update write error code */
		    write_error_code = (gint)ferror(tar_fp);
		    status = -1;
		    break;
		}

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

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

		/* Time to report progress? */
		if(bc >= 10000l)
		{
		    /* Report progress */
		    if(ProgressDialogIsQuery() && (file_size > 0l))
		    {
			ProgressDialogUpdate(
			    NULL, NULL,
			    NULL, NULL,
			    (gfloat)bp / (gfloat)file_size,
			    EDV_DEF_PROGRESS_BAR_TICKS,
			    TRUE
			);

			/* User aborted? */
			if(ProgressDialogStopCount() > 0)
			{
			    status = -4;
			    break;
			}
		    }

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

	/* Close target and source files */
	FClose(tar_fp);
	FClose(src_fp);

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

	return(status);
}

/*
 *      Copies the link object specified by src_obj to tar_obj.
 *
 *      The tar_obj is assumed to not exist and src_obj is assumed to
 *	be a link.
 *
 *      Returns non-zero on error.
 */
static gint EDVFOPCopyLink(
	const gchar *src_obj, const gchar *tar_obj,
	const struct stat *src_lstat_buf,
	const struct stat *src_stat_buf
)
{
	gint result;
	gchar *buf;
	gint buf_len = PATH_MAX + NAME_MAX;
	gint bytes_read;

	/* Report initial progress */
	if(ProgressDialogIsQuery())
	    ProgressDialogUpdate(
		NULL, NULL,
		NULL, NULL,
		0.0f, EDV_DEF_PROGRESS_BAR_TICKS, TRUE
	    );

	/* Allocate memory for link destination value */
	buf = (gchar *)g_malloc((buf_len + 1) * sizeof(gchar));
	if(buf == NULL)
	{
	    last_error = "Memory allocation error";
	    return(-3);
	}

	/* Get link destination of source object */
	bytes_read = readlink(
	    (const char *)src_obj,
	    (char *)buf, (size_t)buf_len
	);
	if(bytes_read < 0)
	{
	    /* Error reading link destination */
	    switch(errno)
	    {
	      case ENOTDIR:
		last_error =
"A compoent of the source link is not a valid directory";
		break;
	      case ENAMETOOLONG:
		last_error = "Source link path name is too long";
		break;
	      case ENOENT:
		last_error = "Unable to find source link";
		break;
	      case EACCES:
		last_error =
"Search permission was denied for a compoent of the source link path";
		break;
	      case ELOOP:
		last_error =
"Too many symbolic links encountered while resolving source link";
		break;
	      case EINVAL:
		last_error = "Source link is not really a link";
		break;
	      case ENOMEM:
		last_error = "System is out of memory";
		break;
	      default:
		last_error = "Unable to obtain source link destination";
		break;
	    }
	    g_free(buf);
	    return(-2);
	}

	/* Put null byte at end of link destination */
	if(bytes_read <= buf_len)
	    buf[bytes_read] = '\0';
	else
	    buf[buf_len] = '\0';

	/* Report final progress */
	if(ProgressDialogIsQuery())
	    ProgressDialogUpdate(
		NULL, NULL,
		NULL, NULL,
		0.5f, EDV_DEF_PROGRESS_BAR_TICKS, TRUE
	    );

	/* Create new link */
	result = (gint)symlink((const char *)buf, (const char *)tar_obj);

	/* Delete link destination buffer */
	g_free(buf);

	/* Set original owner if possible, check if there was no error
	 * creating the object and that the local source stats are 
	 * available
	 */
	if(!result && (src_lstat_buf != NULL))
	{
#if 0
/* Never chmod() a link, it always gets applied to its destination
 * Symbolic links really do not have permissions anyways
 */
	    chmod(tar_obj, src_lstat_buf->st_mode);
#endif

	    lchown(
		(const char *)tar_obj,
		src_lstat_buf->st_uid, src_lstat_buf->st_gid
	    );
	}

	/* Report final progress */
	if(ProgressDialogIsQuery())
	    ProgressDialogUpdate(
		NULL, NULL,
		NULL, NULL,
		1.0f, EDV_DEF_PROGRESS_BAR_TICKS, TRUE
	    );

	/* Error creating link? */
	if(result)
	{
	    switch(errno)
	    {
	      case EPERM:
		last_error =
"Target object filesystem does not support objects of type link";
		break;
	      case EFAULT:
		last_error =
"Target object name string memory points outside of accessable address space";
		break;
	      case EACCES:
		last_error =
"Write access not permitted on target object for this process";
		break;
	      case ENAMETOOLONG:
		last_error = "Target object path name is too long";
		break;
	      case ENOENT:
		last_error =
"A directory compoent of the target object does not exist";
		break;
	      case ENOTDIR:
		last_error =
"A compoent of the target object is not a directory";
		break;
	      case ENOMEM:
		last_error = "System is out of memory";
		break;
	      case EROFS:
		last_error = "Unable to create link on a read-only filesystem";
		break;
	      case EEXIST:
		last_error = "Target object path already exists";
		break;
	      case ELOOP:
		last_error =
"Too many symbolic links encountered while resolving target object";
		break;
	      case ENOSPC:
		last_error = "Unsufficient disk space to create new link";
		break;
	      default:
		last_error = "Error creating link";
		break;
	    }
	    return(-1);
	}
	else
	{
	    return(0);
	}
}

/*
 *      Coppies the FIFO pipe object specified by src_obj to tar_obj.
 *
 *      The tar_obj is assumed to not exist and src_obj is assumed to
 *      be a FIFO pipe.
 *
 *      Returns non-zero on error.
 */
static gint EDVFOPCopyFIFO(
	const gchar *src_obj, const gchar *tar_obj,
	const struct stat *src_lstat_buf,
	const struct stat *src_stat_buf
)
{
	gint result;

	/* Report initial progress */
	if(ProgressDialogIsQuery())
	    ProgressDialogUpdate(
		NULL, NULL,
		NULL, NULL,
		0.0f, EDV_DEF_PROGRESS_BAR_TICKS, TRUE
	    );

	/* Create new FIFO pipe */
	result = (gint)mkfifo(
	    (const char *)tar_obj,
	    (src_lstat_buf != NULL) ?
		src_lstat_buf->st_mode : (S_IFIFO | S_IRUSR | S_IWUSR)
	);

	/* Set original owner if possible, check if there was no error
	 * creating the object and that the local source stats are
	 * available
	 */
	if(!result && (src_lstat_buf != NULL))
	{
	    chmod((const char *)tar_obj, src_lstat_buf->st_mode);
	    lchown(
		(const char *)tar_obj,
		src_lstat_buf->st_uid, src_lstat_buf->st_gid
	    );
	}

	/* Report final progress */
	if(ProgressDialogIsQuery())
	    ProgressDialogUpdate(
		NULL, NULL,
		NULL, NULL,
		1.0f, EDV_DEF_PROGRESS_BAR_TICKS, TRUE
	    );

	/* Error creating FIFO pipe? */
	if(result)
	{
	    switch(errno)
	    {
	      case EACCES:
		last_error =
"Compoent of target FIFO pipe path does not permit searching";
		break;

	      case EEXIST:
		last_error = "Target object already exists";
		break;

	      case ENAMETOOLONG:
		last_error = "Target FIFO pipe path name is too long";
		break;

	      case ENOENT:
		last_error =
"A directory compoent of the target FIFO pipe does not exist";
		break;

	      case ENOSPC:
		last_error = "Unsufficient disk space to create new FIFO pipe";
		break;

	      case ENOTDIR:
		last_error =
"A compoent of the target FIFO pipe path is not a directory";
		break;

	      case EROFS:
		last_error =
"Unable to create FIFO pipe on a read-only filesystem";
		break;

	      default:
		last_error = "Error creating FIFO pipe";
		break;
	    }
	    return(-1);
	}
	else
	{
	    return(0);
	}
}

/*
 *	Coppies the block device node specified by src_obj to tar_obj.
 *
 *      The tar_obj is assumed to not exist and src_obj is assumed to
 *      be a block device.
 *
 *      Returns non-zero on error.
 */
static gint EDVFOPCopyDeviceBlock(
	const gchar *src_obj, const gchar *tar_obj,
	const struct stat *src_lstat_buf,
	const struct stat *src_stat_buf
)
{
	gint result;

	/* Report initial progress */
	if(ProgressDialogIsQuery())
	    ProgressDialogUpdate(
		NULL, NULL,
		NULL, NULL,
		0.0f, EDV_DEF_PROGRESS_BAR_TICKS, TRUE
	    );

	/* Create new block device */
	result = (gint)mknod(
	    (const char *)tar_obj,
	    (src_lstat_buf != NULL) ?
		src_lstat_buf->st_mode : (S_IFBLK | S_IRUSR | S_IWUSR),
	    (src_lstat_buf != NULL) ?
		src_lstat_buf->st_rdev : 0
	);

	/* Set original owner if possible, check if there was no error
	 * creating the object and that the local source stats are
	 * available
	 */
	if(!result && (src_lstat_buf != NULL))
	{
	    chmod((const char *)tar_obj, src_lstat_buf->st_mode);
	    lchown(
		(const char *)tar_obj,
		src_lstat_buf->st_uid, src_lstat_buf->st_gid
	    );
	}

	/* Report final progress */
	if(ProgressDialogIsQuery())
	    ProgressDialogUpdate(
		NULL, NULL,
		NULL, NULL,
		1.0f, EDV_DEF_PROGRESS_BAR_TICKS, TRUE
	    );

	/* Error creating block device? */
	if(result)
	{
	    switch(errno)
	    {
	      case EPERM:
		last_error =
"Filesystem does not support objects of type block device or user is not super user";
		break;

	      case EINVAL:
		last_error = "Invalid mode value specified to create block device";
		break;

	      case EEXIST:
		last_error = "Target object already exists";
		break;

	      case EFAULT:
		last_error =
"Block device name string memory points outside of accessable address space";
		break;

	      case ENAMETOOLONG:
		last_error = "Target block device path name is too long";
		break;

	      case ENOENT:
		last_error =
"A directory compoent of the target block device does not exist";
		break;

	      case ENOMEM:
		last_error = "System is out of memory";
		break;

	      case EROFS:
		last_error =
"Unable to create block device on a read-only filesystem";
		break;

	      case ELOOP:
		last_error =
"Too many symbolic links encountered while resolving target path";
		break;

	      case ENOSPC:
		last_error =
"Unsufficient disk space to create new block device";
		break;

	      default:
		last_error = "Error creating block device";
		break;
	    }
	    return(-1);
	}
	else
	{
	    return(0);
	}
}

/*
 *      Coppies the character device node specified by src_obj to tar_obj.
 *
 *      The tar_obj is assumed to not exist and src_obj is assumed to
 *      be a character device.
 *
 *      Returns non-zero on error.
 */
static gint EDVFOPCopyDeviceCharacter(
	const gchar *src_obj, const gchar *tar_obj,
	const struct stat *src_lstat_buf,
	const struct stat *src_stat_buf
)
{
	gint result;

	/* Report initial progress */
	if(ProgressDialogIsQuery())
	    ProgressDialogUpdate(
		NULL, NULL,
		NULL, NULL,
		0.0f, EDV_DEF_PROGRESS_BAR_TICKS, TRUE
	    );

	/* Create new character device */
	result = (gint)mknod(
	    (const char *)tar_obj,
	    (src_lstat_buf != NULL) ?
		src_lstat_buf->st_mode : (S_IFCHR | S_IRUSR | S_IWUSR),
	    (src_lstat_buf != NULL) ?
		src_lstat_buf->st_rdev : 0
	);

	/* Set original owner if possible, check if there was no error
	 * creating the object and that the local source stats are
	 * available
	 */
	if(!result && (src_lstat_buf != NULL))
	{
	    chmod((const char *)tar_obj, src_lstat_buf->st_mode);
	    lchown(
		(const char *)tar_obj,
		src_lstat_buf->st_uid, src_lstat_buf->st_gid
	    );
	}

	/* Report final progress as needed */
	if(ProgressDialogIsQuery())
	    ProgressDialogUpdate(
		NULL, NULL,
		NULL, NULL,
		1.0f, EDV_DEF_PROGRESS_BAR_TICKS, TRUE
	    );

	/* Error creating character device? */
	if(result)
	{
	    switch(errno)
	    {
	      case EPERM:
		last_error =
"Filesystem does not support objects of type character device or user is not super user";
		break;

	      case EINVAL:
		last_error = "Invalid mode value specified to create character device";
		break;

	      case EEXIST:
		last_error = "Target object already exists";
		break;

	      case EFAULT:
		last_error =
"Character device name string memory points outside of accessable address space";
		break;

	      case ENAMETOOLONG:
		last_error = "Target character device path name is too long";
		break;

	      case ENOENT:
		last_error =
"A directory compoent of the target character device does not exist";
		break;

	      case ENOMEM:
		last_error = "System is out of memory";
		break;

	      case EROFS:
		last_error =
"Ynable to create character device on a read-only filesystem";
		break;

	      case ELOOP:
		last_error =
"Too many symbolic links encountered while resolving target path";
		break;

	      case ENOSPC:
		last_error =
"Unsufficient disk space to create new character device";
		break;

	      default:
		last_error = "Error creating character device";
		break;
	    }
	    return(-1);
	}
	else
	{
	    return(0);
	}
}

/*
 *	Coppies the socket specified by src_obj to tar_obj.
 *
 *      The tar_obj is assumed to not exist and src_obj is assumed to
 *      be a socket.
 *
 *      Returns non-zero on error.
 */
static gint EDVFOPCopySocket(
	const gchar *src_obj, const gchar *tar_obj,
	const struct stat *src_lstat_buf,
	const struct stat *src_stat_buf
)
{
	gint result;
	FILE *tar_fp;

	/* Report initial progress */
	if(ProgressDialogIsQuery())
	    ProgressDialogUpdate(
		NULL, NULL,
		NULL, NULL,
		0.0f, EDV_DEF_PROGRESS_BAR_TICKS, TRUE
	    );

/* TODO not sure how to create a socket object so just create an
 * empty regular file object for now
 */
	tar_fp = FOpen((const char *)tar_obj, "wb");
	if(tar_fp == NULL)
	{
	    last_error = "Unable to open target object for writing";
	    return(-1);
	}

	/* Do not write anything */

	FClose(tar_fp);
	tar_fp = NULL;

	result = 0;


	/* Set original owner if possible, check if there was no error
	 * creating the object and that the local source stats are
	 * available
	 */
	if(!result && (src_lstat_buf != NULL))
	{
	    chmod((const char *)tar_obj, src_lstat_buf->st_mode);
	    lchown(
		(const char *)tar_obj,
		src_lstat_buf->st_uid, src_lstat_buf->st_gid
	    );
	}

	/* Report final progress */
	if(ProgressDialogIsQuery())
	    ProgressDialogUpdate(
		NULL, NULL,
		NULL, NULL,
		1.0f, EDV_DEF_PROGRESS_BAR_TICKS, TRUE
	    );

	/* Error creating socket? */
	if(result)
	{
	    switch(errno)
	    {
	      default:
		last_error = "Error creating socket";
		break;
	    }
	    return(-1);
	}
	else
	{
	    return(0);
	}
}


/*
 *	Front end to copy the object specified by src_obj to tar_obj.
 *
 *	The src_obj can be any object type except directory, however the
 *	src_obj can be a link to a directory.
 *
 *      The tar_obj is assumed to not exist.
 *
 *      Returns non-zero on error.
 */
static gint EDVFOPCopyNexus(
	const gchar *src_obj, const gchar *tar_obj,
	const struct stat *src_lstat_buf,
	const struct stat *src_stat_buf,
	gboolean archive
)
{
	mode_t m;
	const struct stat *stat_buf_ptr;


	if(STRISEMPTY(src_obj) || STRISEMPTY(tar_obj))
	    return(-1);

	/* Must have local stats of source object if archiving or
	 * destination stats if not archiving
	 */
	stat_buf_ptr = archive ? src_lstat_buf : src_stat_buf;
	if(stat_buf_ptr == NULL)
	{
	    last_error = "Statistics of source object not provided";
	    return(-1);
	}

	/* Copy the source object by its type
	 *
	 * The source object may not be a directory
	 */
	m = stat_buf_ptr->st_mode;
	if(S_ISREG(m))
	{
	    return(EDVFOPCopyRegular(
		src_obj, tar_obj, src_lstat_buf, src_stat_buf
	    ));
	}
	else if(S_ISLNK(m))
	{
	    return(EDVFOPCopyLink(
		src_obj, tar_obj, src_lstat_buf, src_stat_buf
	    ));
	}
	else if(S_ISFIFO(m))
	{
	    return(EDVFOPCopyFIFO(
		src_obj, tar_obj, src_lstat_buf, src_stat_buf
	    ));
	}
	else if(S_ISBLK(m))
	{
	    return(EDVFOPCopyDeviceBlock(
		src_obj, tar_obj, src_lstat_buf, src_stat_buf
	    ));
	}
	else if(S_ISCHR(m))
	{
	    return(EDVFOPCopyDeviceCharacter(
		src_obj, tar_obj, src_lstat_buf, src_stat_buf
	    ));
	}
	else if(S_ISSOCK(m))
	{
	    return(EDVFOPCopySocket(
		src_obj, tar_obj, src_lstat_buf, src_stat_buf
	    ));
	}
/* Add support for other objects here (except directories) */
	else
	{
	    /* Unsupported object, ignore */
	    last_error =
"Copying of unsupported object type failed";
	    return(-2);
	}
}


/*
 *      Moves the source object to the target object.
 *
 *      The source object can locally be a directory.
 *      If the target object exists, then the user will be prompted as
 *      instructed and the target object may be overwritten. If the target
 *	object is a directory or a link to a directory then this function
 *	will return -2 and set *need_copy to TRUE.
 *
 *	If the source and target objects are on different devices then
 *	-1 will be returned and *need_copy will be set to TRUE.
 *
 *      Inputs assumed valid.
 */
static gint EDVFOPDoMoveObjectsLocal(
	edv_core_struct *core_ptr,
	const gchar *src_obj, const gchar *tar_obj,
	const struct stat *src_lstat_buf, const struct stat *src_stat_buf,
	GtkWidget *toplevel,
	gboolean show_progress, gboolean interactive, gboolean archive,
	gint *status, gboolean *yes_to_all,
	gboolean *need_copy
)
{
	gint result;
	struct stat tar_lstat_buf;
	const struct stat *src_stat_ptr;

	if((core_ptr == NULL) || STRISEMPTY(src_obj) || STRISEMPTY(tar_obj) ||
	   (status == NULL) || (yes_to_all == NULL) || (need_copy == NULL)
	)
	{
	    if(status != NULL)
		*status = -1;
	    return(-1);
	}

	/* Status already indicates error but is not a user "no" or
	 * "not available" response?
	 */
	if(*status && (*status != -5))
	    return(*status);

	/* Reset need_copy */
	*need_copy = FALSE;


	/* Check if paths are identical */
	if(!strcmp((const char *)src_obj, (const char *)tar_obj))
	{
	    last_error = "Source and target objects are the same";
	    *status = -2;
	    return(*status);
	}


	/* Get pointer to source object statistics */
	src_stat_ptr = archive ? src_lstat_buf : src_stat_buf;
	if(src_stat_ptr == NULL)
	{
	    last_error = "Source object statistics not available";
	    *status = -2;
	    return(*status);
	}


	/* Show Progress Dialog? */
	if(show_progress)
	{
	    gchar *p1 = EDVCopyShortenPath(
		src_obj, EDV_DEF_PROGRESS_BAR_PATH_DISPLAY_MAX
	    );
	    gchar *p2 = EDVCopyShortenPath(
		tar_obj, EDV_DEF_PROGRESS_BAR_PATH_DISPLAY_MAX
	    );
	    gchar *buf = g_strdup_printf(
#if defined(PROG_LANGUAGE_SPANISH)
"Mover:\n\
\n\
    %s\n\
\n\
A:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_FRENCH)
"Déménagement:\n\
\n\
    %s\n\
\n\
A:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_GERMAN)
"Bewegen:\n\
\n\
    %s\n\
\n\
Zu:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_ITALIAN)
"Il Trasloco:\n\
\n\
    %s\n\
\n\
A:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_DUTCH)
"Bewegen:\n\
\n\
    %s\n\
\n\
Te:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"Mover:\n\
\n\
    %s\n\
\n\
A:\n\
\n\
    %s\n"
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"Flytting:\n\
\n\
    %s\n\
\n\
Til:\n\
\n\
    %s\n"
#else
"Moving:\n\
\n\
    %s\n\
\n\
To:\n\
\n\
    %s\n"
#endif
		, p1, p2
	    );

	    /* Map progress dialog as needed and set message */
	    EDVFOPMapProgressDialogMove(buf, toplevel, FALSE);

	    g_free(buf);
	    g_free(p1);
	    g_free(p2);

	    /* User aborted? */
	    if(ProgressDialogStopCount() > 0)
	    {
		*status = -4;
		return(*status);
	    }
	}


	/* Check if target object locally exists */
	if(!lstat((const char *)tar_obj, &tar_lstat_buf))
	{
	    struct stat tar_stat_buf;

	    /* Target object exists locally, try to get target object
	     * destination stats.
	     */
	    result = (gint)stat((const char *)tar_obj, &tar_stat_buf);
	    if(!result)
	    {
		/* Check if source and target objects are the same */
		if(EDVFOPObjectsSame(src_stat_ptr, &tar_stat_buf))
		{
		    last_error = "Source and target objects are the same";
		    *status = -2;
		    return(*status);
		}
	    }
	    /* Check if source and target objects are the same locally */
	    if(EDVFOPObjectsSame(src_lstat_buf, &tar_lstat_buf))
	    {
		last_error = "Source and target objects are the same";
		*status = -2;
		return(*status);
	    }
	

	    /* Is the target object destination a directory? */
	    if(result ? FALSE : S_ISDIR(tar_stat_buf.st_mode))
	    {
		/* Target object already exists locally and its
		 * destination is a directory.
		 *
		 * If the source object is also a directory then mark that
		 * this operation needs to be changed to a copy operation.
		 */
		if(S_ISDIR(src_stat_ptr->st_mode))
		{
		    *need_copy = TRUE;
		    *status = -2;
		}
		else
		{
		    last_error =
 "Target object already exists and is a directory";
		    *status = -2;
		}
		return(*status);
	    }


	    /* It exists but is not a directory, ask user for
	     * confirmation on overwriting it?
	     */
	    if(interactive && !(*yes_to_all))
	    {
		result = EDVFOPConfirmOverwrite(
		    core_ptr, toplevel,
		    src_obj, tar_obj,
		    src_lstat_buf, &tar_lstat_buf
		);
		switch(result)
		{
		  case CDIALOG_RESPONSE_YES_TO_ALL:
		    *yes_to_all = TRUE;
		  case CDIALOG_RESPONSE_YES:
		  case CDIALOG_RESPONSE_OK:
		    break;

		  case CDIALOG_RESPONSE_CANCEL:
		    /* Respond with user abort error code, this will
		     * cause the calling function to detect an error and
		     * stop any looping operations.
		     */
		    *status = -4;
		    return(-4);
		    break;

		  default:
		    /* Respond with no or not available code, the calling
		     * function will see this as an error but should still
		     * continue with futher loop operations.
		     */
		    *status = -5;
		    return(-5);
		    break;
		}
	    }
	}

	/* Target object does not exist or user has given permission to
	 * remove it. So remove it just in case
	 */
	result = EDVFOPDoUnlink(tar_obj);
	if(result)
	{
	    /* Failed to remove object (note that EDVFOPDoUnlink() will
	     * not return an error if the object did not exist in the
	     * first place)
	     */
	    *status = result;
	    return(*status);
	}

	/* Move the source object to the target object */
	if(rename((const char *)src_obj, (const char *)tar_obj))
	{
	    switch(errno)
	    {
	      case EISDIR:
		last_error =
"Target object is a directory and source object is not";
		*status = -2;
		break;

	      case EXDEV:
		/* This is an error but we need to set *need_copy to TRUE
		 * so that the calling function knows that it failed but
		 * needs to copy the object instead
		 */
		*need_copy = TRUE;
		*status = -2;
		break;

	      case EEXIST:
		last_error =
"Target object directory is not empty";
		*status = -2;
		break;

	      case EBUSY:
		last_error =
"Source or target object is currently in use";
		*status = -3;
		break;

	      case EINVAL:
		last_error = "Unable to move parent into a child";
		*status = -2;
		break;

	      case EMLINK:
		last_error = "Too many links in source or target directory";
		*status = -2;
		break;

	      case ENOTDIR:
		last_error =
"A source or target path compoent is not a directory";
		*status = -2;
		break;

	      case EFAULT:
		last_error =
"Directory name string memory points outside of accessable address space";
		*status = -3;
		break;

	      case EACCES:
		last_error =
"Source or target does not allow write permission to this process";
		*status = -2;
		break;

	      case ENAMETOOLONG:
		last_error = "Source or target path name is too long";
		*status = -2;
		break;

	      case ENOENT:
		last_error =
"Source or target path directory compoent does not exist";
		*status = -1;
		break;

	      case ENOMEM:
		last_error = "System is out of memory";
		*status = -3;
		break;

	      case EROFS:
		last_error =
"Unable to move object on to a read-only filesystem";
		*status = -2;
		break;

	      case ELOOP:
		last_error =
"Too many symbolic links encountered while resolving target directory";
		*status = -3;
		break;

	      case ENOSPC:
		last_error =
"Insufficient disk space to move object to target location";
		*status = -3;
		break;
	    }
	}

	return(*status);
}

/*
 *	Coppies the source object to the target object.
 *
 *	The source object must not locally be a directory.
 *	If the target object exists, then the user will be prompted as
 *	instructed and the target object may be overwritten.
 *
 *	This operation will fail if the target object is a directory.
 *
 *      Inputs assumed valid.
 */
static gint EDVFOPDoCopyObjectsLocal(
	edv_core_struct *core_ptr,
	const gchar *src_obj, const gchar *tar_obj,
	const struct stat *src_lstat_buf, const struct stat *src_stat_buf,
	GtkWidget *toplevel,
	gboolean show_progress, gboolean interactive, gboolean archive,
	gint *status, gboolean *yes_to_all, gboolean originally_move
)
{
	gint result;
	struct stat tar_lstat_buf;
	const struct stat *src_stat_ptr;

	if((core_ptr == NULL) || STRISEMPTY(src_obj) || STRISEMPTY(tar_obj) ||
	   (status == NULL) || (yes_to_all == NULL)
	)
	{
	    if(status != NULL)
		*status = -1;
	    return(-1);
	}

	/* Status already indicates error but is not a user "no" or
	 * "not available" response?
	 */
	if(*status && (*status != -5))
	    return(*status);


	/* Check if paths are identical */
	if(!strcmp((const char *)src_obj, (const char *)tar_obj))
	{
	    last_error = "Source and target objects are the same";
	    *status = -2;
	    return(*status);
	}


	/* Get pointer to source object statistics */
	src_stat_ptr = archive ? src_lstat_buf : src_stat_buf;
	if(src_stat_ptr == NULL)
	{
	    last_error = "Source object statistics not available";
	    *status = -2;
	    return(*status);
	}


	/* Show Progress Dialog? */
	if(show_progress)
	{
	    gchar *p1 = EDVCopyShortenPath(
		src_obj, EDV_DEF_PROGRESS_BAR_PATH_DISPLAY_MAX
	    );
	    gchar *p2 = EDVCopyShortenPath(
		tar_obj, EDV_DEF_PROGRESS_BAR_PATH_DISPLAY_MAX
	    );
	    gchar *buf = g_strdup_printf(
#if defined(PROG_LANGUAGE_SPANISH)
"%s:\n\
\n\
    %s\n\
\n\
A:\n\
\n\
    %s\n",
		originally_move ? "Mover" : "Copiar",
#elif defined(PROG_LANGUAGE_FRENCH)
"%s:\n\
\n\
    %s\n\
\n\
A:\n\
\n\
    %s\n",
		originally_move ? "Déménagement" : "Copier",
#elif defined(PROG_LANGUAGE_GERMAN)
"%s:\n\
\n\
    %s\n\
\n\
Zu:\n\
\n\
    %s\n",
		originally_move ? "Bewegen" : "Kopieren",
#elif defined(PROG_LANGUAGE_ITALIAN)
"%s:\n\
\n\
    %s\n\
\n\
A:\n\
\n\
    %s\n",
		originally_move ? "Il Trasloco" : "Copiare",
#elif defined(PROG_LANGUAGE_DUTCH)
"%s:\n\
\n\
    %s\n\
\n\
Te:\n\
\n\
    %s\n",
		originally_move ? "Bewegen" : "Kopiëren",
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"%s:\n\
\n\
    %s\n\
\n\
A:\n\
\n\
    %s\n",
		originally_move ? "Mover" : "Copiar",
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"%s:\n\
\n\
    %s\n\
\n\
Til:\n\
\n\
    %s\n",
		originally_move ? "Flytting" : "Kopiering",
#else
"%s:\n\
\n\
    %s\n\
\n\
To:\n\
\n\
    %s\n",
		originally_move ? "Moving" : "Copying",
#endif
		p1, p2
	    );

	    /* Map progress dialog as needed and set message */
	    if(originally_move)
		EDVFOPMapProgressDialogMove(buf, toplevel, FALSE);
	    else
		EDVFOPMapProgressDialogCopy(buf, toplevel, FALSE);

	    g_free(buf);
	    g_free(p1);
	    g_free(p2);

	    /* User aborted? */
	    if(ProgressDialogStopCount() > 0)
	    {
		*status = -4;
		return(*status);
	    }
	}


	/* Check if local target object already exists */
	if(!lstat((const char *)tar_obj, &tar_lstat_buf))
	{
	    struct stat tar_stat_buf;

	    /* Target object exists locally, try to get target object
	     * destination stats
	     */
	    result = (gint)stat((const char *)tar_obj, &tar_stat_buf);
	    if(!result)
	    {
		/* Check if source and target object destinations are
		 * the same
		 */
		if(EDVFOPObjectsSame(src_stat_ptr, &tar_stat_buf))
		{
		    last_error = "Source and target objects are the same";
		    *status = -2;
		    return(*status);
		}
	    }
	    /* Check if source and target objects are the same locally */
	    if(EDVFOPObjectsSame(src_lstat_buf, &tar_lstat_buf))
	    {
		last_error = "Source and target objects are the same";
		*status = -2;
		return(*status);
	    }


	    /* Is target object locally a directory? */
	    if(S_ISDIR(tar_lstat_buf.st_mode))
	    {
		last_error = "Target object already exists and is a directory";
		*status = -2;
		return(*status);
	    }
	    /* Target object exists and is not a directory, ask user for
	     * confirmation on overwriting it?
	     */
	    if(interactive && !(*yes_to_all))
	    {
		result = EDVFOPConfirmOverwrite(
		    core_ptr, toplevel,
		    src_obj, tar_obj,
		    src_lstat_buf, &tar_lstat_buf
		);
		switch(result)
		{
		  case CDIALOG_RESPONSE_YES_TO_ALL:
		    *yes_to_all = TRUE;
		  case CDIALOG_RESPONSE_YES:
		  case CDIALOG_RESPONSE_OK:

		    break;

		  case CDIALOG_RESPONSE_CANCEL:
		    /* Respond with user abort error code, this will
		     * cause the calling function to detect an error and
		     * stop any looping operations.
		     */
		    *status = -4;
		    return(-4);
		    break;

		  default:
		    /* Respond with no or not available code, the calling
		     * function will see this as an error but should still
		     * continue with futher loop operations.
		     */
		    *status = -5;
		    return(-5);
		    break;
		}
	    }
	}

	/* Target object does not exist or user has given permission to
	 * remove it. So remove it just in case
	 */
	result = EDVFOPDoUnlink(tar_obj);
	if(result)
	{
	    /* Failed to remove object (note that EDVFOPDoUnlink() will
	     * not return an error if the object did not exist in the 
	     * first place)
	     */
	    *status = result;
	    return(*status);
	}

	/* Copy the source object to the target object. This will check
	 * the source object's type and copy it accordingly to tar_obj.
	 * The tar_obj must not exist
	 */
	result = EDVFOPCopyNexus(
	    src_obj, tar_obj,
	    src_lstat_buf, src_stat_buf,
	    archive
	);
	if(result)
	    *status = result;

	return(*status);
}




/*
 *	Creates a directory specified as tar_dir that is identical to
 *	the directory specified by src_dir and coppies all contents
 *	from the src_dir to the tar_dir, recursing if additional 
 *	directories are found.
 *
 *	Inputs assumed valid.
 *
 *	Status will be set to an error code if an error is encountered,
 *	the status pointer is assumed valid.
 */
static gint EDVFOPCopyDirectoryIterate(
	edv_core_struct *core_ptr,
	const gchar *src_dir, const gchar *tar_dir,
	const struct stat *src_lstat_buf, const struct stat *src_stat_buf,
	GtkWidget *toplevel,
	gboolean show_progress, gboolean interactive, gboolean archive,
	gint *status, gboolean *yes_to_all, gboolean originally_move
)
{
	gint result, strc;
	gchar **strv;
	struct stat tar_stat_buf;

	if((core_ptr == NULL) || STRISEMPTY(src_dir) || STRISEMPTY(tar_dir) ||
	   (status == NULL) || (yes_to_all == NULL)
	)
	{
	    if(status != NULL)
		*status = -1;
	    return(-1);
	}

	/* Status already indicates error and error was not a user
	 * response of "no" or "not available"?
	 */
	if((*status) && (*status != -5))
	    return(*status);


	/* Check if paths are identical */
	if(!strcmp((const char *)src_dir, (const char *)tar_dir))
	{
	    last_error = "Source and target directories are the same";
	    *status = -2;
	    return(*status);
	}


	/* Must have stats of source object destination */
	if(src_stat_buf == NULL)
	{
	    last_error =
"Source directory statistics not available";
	    *status = -2;
	    return(*status);
	}


	/* Show Progress Dialog? */
	if(show_progress)
	{
	    gchar *p1 = EDVCopyShortenPath(
		src_dir, EDV_DEF_PROGRESS_BAR_PATH_DISPLAY_MAX
	    );
	    gchar *p2 = EDVCopyShortenPath(
		tar_dir, EDV_DEF_PROGRESS_BAR_PATH_DISPLAY_MAX
	    );
	    gchar *buf = g_strdup_printf(
#if defined(PROG_LANGUAGE_SPANISH)
"%s:\n\
\n\
    %s\n\
\n\
A:\n\
\n\
    %s\n",
		originally_move ? "Mover" : "Copiar",
#elif defined(PROG_LANGUAGE_FRENCH)
"%s:\n\
\n\
    %s\n\
\n\
A:\n\
\n\
    %s\n",
		originally_move ? "Déménagement" : "Copier",
#elif defined(PROG_LANGUAGE_GERMAN)
"%s:\n\
\n\
    %s\n\
\n\
Zu:\n\
\n\
    %s\n",
		originally_move ? "Bewegen" : "Kopieren",
#elif defined(PROG_LANGUAGE_ITALIAN)
"%s:\n\
\n\
    %s\n\
\n\
A:\n\
\n\
    %s\n",
		originally_move ? "Il Trasloco" : "Copiare",
#elif defined(PROG_LANGUAGE_DUTCH)
"%s:\n\
\n\
    %s\n\
\n\
Te:\n\
\n\
    %s\n",
		originally_move ? "Bewegen" : "Kopiëren",
#elif defined(PROG_LANGUAGE_PORTUGUESE)
"%s:\n\
\n\
    %s\n\
\n\
A:\n\
\n\
    %s\n",
		originally_move ? "Mover" : "Copiar",
#elif defined(PROG_LANGUAGE_NORWEGIAN)
"%s:\n\
\n\
    %s\n\
\n\
Til:\n\
\n\
    %s\n",
		originally_move ? "Flytting" : "Kopiering",
#else
"%s:\n\
\n\
    %s\n\
\n\
To:\n\
\n\
    %s\n",
		originally_move ? "Moving" : "Copying",
#endif
		p1, p2
	    );

	    /* Map progress dialog as needed and set message */
	    if(originally_move)
		EDVFOPMapProgressDialogMove(buf, toplevel, FALSE);
	    else
		EDVFOPMapProgressDialogCopy(buf, toplevel, FALSE);

	    g_free(buf);
	    g_free(p1);
	    g_free(p2);

	    /* User aborted? */
	    if(ProgressDialogStopCount() > 0)
	    {
		*status = -4;
		return(*status);
	    }
	}


	/* Get destination of target object if possible and check if
	 * the source and target are the same destination
	 */
	if(!stat((const char *)tar_dir, &tar_stat_buf))
	{
	    /* Check if source and target objects are the same */
	    if(EDVFOPObjectsSame(src_stat_buf, &tar_stat_buf))
	    {
		last_error = "Source and target directories are the same";
		*status = -2;
		return(*status);
	    }
	}
	/* The specified source destination is a directory so we do not
	 * need to check if source and target are the same locally
	 */


	/* Create target directory as needed */
	result = (gint)mkdir((const char *)tar_dir, src_stat_buf->st_mode);
	if(result)
	{
	    switch(errno)
	    {
	      case EEXIST:
		/* Target object exists, check if it is a directory or
		 * a link that reffers to a valid directory
		 */
		if(!ISPATHDIR(tar_dir))
		{
		    last_error =
"Unable to create existing target directory who is not a directory";
		    *status = -1;
		    return(*status);
		}
		else
		{
		    /* Target object is an existing directory or a
		     * link that reffers to a valid directory, do not
		     * indicate any error and continue through
		     */
		}
		break;

	      case EFAULT:
		last_error =
"Directory name string memory points outside of accessable address space";
		*status = -3;
		return(*status);
		break;

	      case EACCES:
		last_error =
"Parent directory does not allow write permission to this process";
		*status = -2;
		return(*status);
		break;

	      case ENAMETOOLONG:
		last_error =
"Target directory path name is too long";
		*status = -2;
		return(*status);
		break;

	      case ENOENT:
	      case ENOTDIR:
		last_error =
"A target directory compoent is not a valid directory";
		*status = -2;
		return(*status);
		break;

	      case ENOMEM:
		last_error = "System is out of memory";
		*status = -3;
		return(*status);
		break;

	      case EROFS:
		last_error =
"Unable to create target directory on a read-only filesystem";
		*status = -2;
		return(*status);
		break;

	      case ELOOP:
		last_error =
"Too many symbolic links encountered while resolving target directory";
		*status = -3;
		return(*status);
		break;

	      case ENOSPC:
		last_error =
"Insufficient disk space to create target directory";
		*status = -3;
		return(*status);

	      default:
		last_error =
"Unable to create target directory, error unknown";
		*status = -1;
		return(*status);
		break;
	    }
	}

	if(show_progress)
	{
	    /* User aborted? */
	    if(ProgressDialogStopCount() > 0)
	    {
		*status = -4;
		return(*status);
	    }
	}



	/* Get listing of contents in source directory and begin copying
	 * them to the target directory
	 */
	strv = (gchar **)GetDirEntNames2((const char *)src_dir, (int *)&strc);
	if(strv != NULL)
	{
	    gint i;
	    const gchar *s;
	    gchar *src_child, *tar_child;
	    struct stat lstat_buf, stat_buf;

	    /* Sort strings */
	    strv = (gchar **)StringQSort((char **)strv, (int)strc);
	    if(strv == NULL)
	    {
		last_error = "Unable to sort directory entries";
		*status = -1;
		return(*status);
	    }

	    /* Iterate through each string */
	    src_child = NULL;
	    tar_child = NULL;
	    for(i = 0; i < strc; i++)
	    {
#define FREE_AND_CONTINUE	{	\
 g_free(src_child);			\
 src_child = NULL;			\
					\
 g_free(tar_child);			\
 tar_child = NULL;			\
					\
 g_free(strv[i]);			\
 strv[i] = NULL;			\
					\
 continue;				\
}
		s = strv[i];
		if(s == NULL)
		    FREE_AND_CONTINUE

		/* Error encountered and it was not a user "no" or
		 * "not available" response?
		 */
		if((*status) && (*status != -5))
		    FREE_AND_CONTINUE

		/* Skip special dir notations */
		if(!strcmp((const char *)s, "..") ||
		   !strcmp((const char *)s, ".")
		)
		    FREE_AND_CONTINUE

		/* Get coppies of source and target child paths */
		src_child = STRDUP(PrefixPaths(src_dir, s));
		if(src_child == NULL)
		    FREE_AND_CONTINUE

		tar_child = STRDUP(PrefixPaths(tar_dir, s));
		if(tar_child == NULL)
		    FREE_AND_CONTINUE

		/* Get local and destination stats for source child */
		if(lstat(src_child, &lstat_buf))
		    FREE_AND_CONTINUE
		if(stat(src_child, &stat_buf))
		    FREE_AND_CONTINUE

		/* Check locally if source object is a directory or if
		 * the destination is a directory and archive is FALSE.
		 *
		 * If so then we will recurse into this directory.
		 */
		if(S_ISDIR(lstat_buf.st_mode) ||
		   (S_ISDIR(stat_buf.st_mode) && !archive)
		)
		{
		    /* Copy source directory and all of its contents to
		     * target
		     */
		    EDVFOPCopyDirectoryIterate(
			core_ptr, src_child, tar_child,
			&lstat_buf, &stat_buf,
			toplevel,
			show_progress, interactive, archive,
			status, yes_to_all, originally_move
		    );
		}
		else
		{
		    /* Copy source object to target */
		    EDVFOPDoCopyObjectsLocal(
			core_ptr, src_child, tar_child,
			&lstat_buf, &stat_buf,
			toplevel,
			show_progress, interactive, archive,
			status, yes_to_all, originally_move
		    );
		}

		/* Delete coppies of source and target child paths,
		 * along with the current directory entry name and
		 * continue
		 */
		FREE_AND_CONTINUE
#undef FREE_AND_CONTINUE
	    }

	    /* Delete pointer array to directory entry strings */
	    g_free(strv);
	    strv = NULL;
	}

	return(0);
}


/*
 *	Procedure to move or copy the source object to the target object.
 *
 *	If is_copy is TRUE then the operation will be a copy, however
 *	sometimes a move is not possible when moving across devices so
 *	if such a situation is detected then is_copy will be set back to
 *	TRUE by this function and the object will be copied and removed
 *	instead.
 *
 *	If new_obj_rtn is not NULL then a dynamically allocated string
 *	containing the full path of the new object will be returned.
 *	The calling function needs to delete it.
 *
 *	If the src_obj is a directory then the entire directory and all
 *	its contents will be recursivly coppied.
 *
 *	If show_progress is TRUE then the progress dialog will be updated
 *	(and mapped as needed). The calling function needs to unmap the 
 *	progress dialog.
 *
 *	Returns non-zero on error.
 */
static gint EDVFOPCopyMove(
	gboolean is_copy,
	edv_core_struct *core_ptr,
	const gchar *src_obj, const gchar *tar_obj,
	gchar **new_obj_rtn,		/* Dynamically allocated */
	GtkWidget *toplevel,		/* Can be NULL */
	gboolean show_progress, gboolean interactive,
	gboolean *yes_to_all
)
{
	static gboolean reenterent = FALSE;
	static gint status;
	gboolean archive = TRUE;
	gboolean originally_move = !is_copy;
	gulong time_start = (gulong)time(NULL);
	gchar *lsrc_obj, *ltar_obj;
	struct stat src_lstat_buf, src_stat_buf, tar_stat_buf;
	gint src_lstat_result, src_stat_result;


	/* Reset returns */
	if(new_obj_rtn != NULL)
	    *new_obj_rtn = NULL;
	/* Leave yes_to_all as whatever value the calling function set it */

	if(reenterent)
	{
	    last_error =
"System is busy, an operation is already in progress";
	    return(-6);
	}
	else
	{
	    reenterent = TRUE;
	}

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


	if((core_ptr == NULL) || STRISEMPTY(src_obj) || STRISEMPTY(tar_obj) ||
	   (yes_to_all == NULL)
	)
	{
	    reenterent = FALSE;
	    return(-1);
	}

#define DO_FREE_LOCALS	{	\
 g_free(lsrc_obj);		\
 lsrc_obj = NULL;		\
 g_free(ltar_obj);		\
 ltar_obj = NULL;		\
}
	/* Get coppies of source and target object paths */
	lsrc_obj = STRDUP(src_obj);
	ltar_obj = STRDUP(tar_obj);
	if((lsrc_obj == NULL) || (ltar_obj == NULL))
	{
	    DO_FREE_LOCALS
	    last_error = "Memory allocation error";
	    reenterent = FALSE;
	    return(-3);
	}

	/* Simplify local coppies of the paths */
	EDVSimplifyPath(lsrc_obj);
	EDVSimplifyPath(ltar_obj);


	/* Make sure paths are not exactly the same, this will be
	 * checked again later when the paths are prefixed as needed
	 * but an initial check needs to be done first to user does not
	 * see any of the more exotic errors.
	 */
	if(!strcmp((const char *)lsrc_obj, (const char *)ltar_obj))
	{
	    DO_FREE_LOCALS
	    last_error = "Source and target objects are the same";
	    reenterent = FALSE;
	    return(-2);
	}

	/* Make sure the source object is not an ancestor of the target
	 * object, otherwise that would be copying/moving a parent
	 * directory into a child object and that is invalid.
	 */
	if(EDVIsParentPath(lsrc_obj, ltar_obj))
	{
	    DO_FREE_LOCALS
	    if(is_copy)
		last_error = "Unable to copy a parent into its child";
	    else
		last_error = "Unable to move a parent into its child";
	    reenterent = FALSE;
	    return(-2);
	}


	/* Get source object statistics, both local and destination */
	src_lstat_result = lstat((const char *)lsrc_obj, &src_lstat_buf);
	src_stat_result = stat((const char *)lsrc_obj, &src_stat_buf);
	if(archive ? src_lstat_result : src_stat_result)
	{
	    /* Unable to get statistics for source object */
	    switch(errno)
	    {
	      case ENOENT:
		last_error = "Unable to find source object";
		break;

	      case ENOTDIR:
		last_error =
"A compoent of the source object is not a valid directory";
		break;

	      case ELOOP:
		last_error =
"Too many symbolic links encountered while resolving source object";
		break;

	      case EACCES:
		last_error =
"Read access not permitted on source object for the current process";
		break;

	      default:
		last_error = "Unable to get statistics for source object";
		break;
	    }
	    DO_FREE_LOCALS
	    reenterent = FALSE;
	    return(-2);
	}

	/* Reset status, it will be updated to indicate any errors as
	 * the correct operation is performed
	 */
	status = 0;

	/* Got source object statistics, now perform copy/move by
	 * the source object's type
	 *
	 * Check if the source object is a directory
	 */
	if(archive ?
	    S_ISDIR(src_lstat_buf.st_mode) : S_ISDIR(src_stat_buf.st_mode)
	)
	{
	    /* Source object is a directory */
	    gboolean need_remove_source = FALSE;
	    const gchar *name = EDVGetPathName(lsrc_obj);
	    gchar *tar_child_obj;


	    /* Copy child object path taken from name portion of lsrc_obj
	     * path prefixed with ltar_obj
	     */
	    tar_child_obj = STRDUP(PrefixPaths(ltar_obj, name));

	    /* Begin copying/moving source directory and all its contents
	     * to the location specified by tar_child_obj
	     */
	    if(!is_copy)
	    {
		EDVFOPDoMoveObjectsLocal(
		    core_ptr, lsrc_obj, tar_child_obj,
		    src_lstat_result ? NULL : &src_lstat_buf,
		    src_stat_result ? NULL : &src_stat_buf,
		    toplevel,
		    show_progress, interactive, archive,
		    &status, yes_to_all,
		    &is_copy
		);
		/* If operation changed from a move to a copy then we need
		 * to mark that the source needs to be removed
		 */
		if(is_copy)
		    need_remove_source = TRUE;
	    }
	    /* Check if either is_copy is TRUE or it was set TRUE above
	     * which indicates the objects need to be coppied istead of
	     * moved
	     */
	    if(is_copy)
	    {
		/* Reset status incase this was originally a move and a
		 * copy needs to be performed instead
		 */
		if(status)
		    status = 0;
		EDVFOPCopyDirectoryIterate(
		    core_ptr, lsrc_obj, tar_child_obj,
		    src_lstat_result ? NULL : &src_lstat_buf,
		    src_stat_result ? NULL : &src_stat_buf,
		    toplevel,
		    show_progress, interactive, archive,
		    &status, yes_to_all, originally_move
	        );
	    }
	    /* Source directory needs to be removed (if operation was
	     * originally a move and a copy across devices took place
	     * instead)?
	     *
	     * Also do not remove if there was an error detected
	     */
	    if(need_remove_source && !status)
	    {
		EDVFOPDoRemoveDirectory(lsrc_obj);
	    }

	    /* Transfer tar_child_obj to the return, the calling function
	     * will delete it
	     */
	    if(new_obj_rtn != NULL)
	    {
		g_free(*new_obj_rtn);
		*new_obj_rtn = tar_child_obj;
	    }
	    else
	    {
		g_free(tar_child_obj);
	    }
	    tar_child_obj = NULL;
	}
	else
	{
	    /* Source object is not a directory */
	    const gchar *s, *s2;
	    gchar *tar_child_obj;
	    gboolean need_remove_source = FALSE;


	    /* Copy target child object path from the target path
	     * initially, change it later if copying to a directory
	     */
	    tar_child_obj = STRDUP(ltar_obj);

	    /* When source object is not a directory then the target
	     * object needs to be checked before appending the source
	     * object's name to the target path
	     *
	     * If and only if the target path exists and is a directory
	     * should the source object's name be appended to the target
	     * path. This is to allow objects being moved/coppied to a
	     * destination with a new name
	     */
	    if(!stat(ltar_obj, &tar_stat_buf))
	    {
		if(S_ISDIR(tar_stat_buf.st_mode))
		{
		    /* Copy child object path taken from name portion of
		     * lsrc_obj path prefixed with ltar_obj
		     */
		    s = EDVGetPathName(lsrc_obj);
		    s2 = (gchar *)PrefixPaths(
			(const char *)ltar_obj, (const char *)s
		    );

		    g_free(tar_child_obj);
		    tar_child_obj = STRDUP(s2);
		    if(tar_child_obj == NULL)
			tar_child_obj = STRDUP(ltar_obj);
		}
	    }

	    /* Begin copying/moving the source object to the location
	     * specified by tar_child_obj
	     */
	    if(!is_copy)
	    {
		EDVFOPDoMoveObjectsLocal(
		    core_ptr, lsrc_obj, tar_child_obj,
		    src_lstat_result ? NULL : &src_lstat_buf,
		    src_stat_result ? NULL : &src_stat_buf,
		    toplevel,
		    show_progress, interactive, archive,
		    &status, yes_to_all,
		    &is_copy
		);
		/* If operation changed from a move to a copy then we need
		 * to mark that the source needs to be removed
		 */
		if(is_copy)
		    need_remove_source = TRUE;
	    }
	    /* Check if either is_copy is TRUE or it was set TRUE above
	     * which indicates the objects need to be coppied istead of
	     * moved
	     */
	    if(is_copy)
	    {
		/* Reset status incase this was originally a move and a
		 * copy needs to be performed instead
		 */
		if(status)
		    status = 0;
		EDVFOPDoCopyObjectsLocal(
		    core_ptr, lsrc_obj, tar_child_obj,
		    src_lstat_result ? NULL : &src_lstat_buf,
		    src_stat_result ? NULL : &src_stat_buf,
		    toplevel,
		    show_progress, interactive, archive,
		    &status, yes_to_all, originally_move
	        );
	    }
	    /* Source object needs to be removed (if operation was
	     * originally a move and a copy across devices took place
	     * instead)?
	     *
	     * Also do not remove if there was an error detected
	     */
	    if(need_remove_source && !status)
	    {
		EDVFOPDoUnlink(lsrc_obj);
	    }

	    /* Transfer tar_child_obj to the return, the calling function
	     * will delete it
	     */
	    if(new_obj_rtn != NULL)
	    {
		g_free(*new_obj_rtn);
		*new_obj_rtn = tar_child_obj;
	    }
	    else
	    {
		g_free(tar_child_obj);
	    }
	    tar_child_obj = NULL;
	}

	/* Record history */
	EDVAppendHistory(
	    core_ptr,
	    originally_move ?
		EDV_HISTORY_DISK_OBJECT_MOVE : EDV_HISTORY_DISK_OBJECT_COPY,
	    time_start, (gulong)time(NULL),
	    status,
	    lsrc_obj,		/* Source */
	    (new_obj_rtn != NULL) ? *new_obj_rtn : ltar_obj,
	    last_error		/* Comment */
	);

	DO_FREE_LOCALS

	reenterent = FALSE;
	return(status);
#undef DO_FREE_LOCALS
}



/*
 *	Front end to copy.
 */
gint EDVFOPCopy(
	edv_core_struct *core_ptr,
	const gchar *src_obj, const gchar *tar_obj,
	gchar **new_obj_rtn,            /* Dynamically allocated */
	GtkWidget *toplevel,            /* Can be NULL */
	gboolean show_progress, gboolean interactive,
	gboolean *yes_to_all
)
{
	return(EDVFOPCopyMove(
	    TRUE,		/* Is copy */
	    core_ptr, src_obj, tar_obj,
	    new_obj_rtn, toplevel,
	    show_progress, interactive,
	    yes_to_all
	));
}

/*
 *	Front end to move.
 */
gint EDVFOPMove(
	edv_core_struct *core_ptr,
	const gchar *src_obj, const gchar *tar_obj,
	gchar **new_obj_rtn,            /* Dynamically allocated */
	GtkWidget *toplevel,            /* Can be NULL */
	gboolean show_progress, gboolean interactive,
	gboolean *yes_to_all
)
{
	return(EDVFOPCopyMove(
	    FALSE,		/* Is not copy */
	    core_ptr, src_obj, tar_obj,
	    new_obj_rtn, toplevel,
	    show_progress, interactive,
	    yes_to_all
	));
}


/*
 *	Generates a full path to a new link name based on the given
 *	src_name located in the directory specified by tar_path if
 *	tar_path is an existing directory or located as tar_path exactly
 *	if it does not exist.
 *
 *	The new link name is gauranteed to not exist and is dynamically
 *	allocated.
 *
 *	Can return NULL on error.
 */
static gchar *EDVFOPGenerateNewLinkName(
	const gchar *src_name, const gchar *tar_path
)
{
	gint counter = 0, counter_max = 10000;
	gchar *full_path, *ltar_path, *lsrc_name;
	gchar *new_path = NULL;
	gchar num_str[40];
	struct stat lstat_buf;


	if(STRISEMPTY(src_name) || STRISEMPTY(tar_path))
	    return(new_path);


	/* Check the target path does not exist */
	if(lstat((const char *)tar_path, &lstat_buf))
	{
	    /* Target path does not exist so return it as is */
	    new_path = STRDUP(tar_path);
	    return(new_path);
	}

	/* Target path exists, check if it is locally a directory
	 * and make coppies of the source object's name and target
	 * path
	 *
	 * The copy of the source object's name will have .lnk
	 * postfixed to it
	 */
	if(S_ISDIR(lstat_buf.st_mode))
	{   
	    /* Target path is locally a directory */
	    ltar_path = STRDUP(tar_path);
	    lsrc_name = g_strdup_printf("%s.lnk", src_name);
	}
	else
	{
	    gchar *parent_path;

	    /* Target path exists and is not locally a directory so
	     * get the source object's name as the target path
	     */
	    lsrc_name = g_strdup_printf(
		"%s.lnk",
		EDVGetPathName(tar_path)
	    );

	    /* Get parent of the target path */
	    parent_path = g_dirname(tar_path);
	    if(parent_path == NULL)
		parent_path = STRDUP(tar_path);
	    ltar_path = parent_path;
	}


	/* Target path is a directory, so look for an available
	 * non-existant link name in that directory dirived from the
	 * lsrc_name
	 */
	for(counter = 1; counter < counter_max; counter++)
	{
	    g_snprintf(
		num_str, sizeof(num_str),
		"%i", counter
	    );

	    full_path = STRDUP(PrefixPaths(ltar_path, lsrc_name));
	    if(full_path == NULL)
		continue;

	    /* If this is not the first try then append a number after
	     * the link's name
	     */
	    if(counter > 1)
	    {
		full_path = G_STRCAT(full_path, num_str);
		if(full_path == NULL)
		    continue;
	    }

	    /* Check if this potential new link name exists locally */
	    if(!lstat((const char *)full_path, &lstat_buf))
	    {
		g_free(full_path);
		continue;
	    }

	    /* All checks passed, transfer full_path to new_path */
	    g_free(new_path);
	    new_path = full_path;

	    break;
	}

	/* Unable to find available link name? */
	if(counter >= counter_max)
	{
	    g_free(new_path);
	    new_path = NULL;
	}

	g_free(lsrc_name);
	g_free(ltar_path);

	return(new_path);
}

/*
 *      Generates a path to the src_obj from the tar_dir, where src_obj
 *	is the path of the source object and tar_path is the location of
 *	the link.
 *
 *      Can return NULL on error.
 */
static gchar *EDVFOPGenerateLinkDestination(
	const gchar *src_obj, const gchar *tar_path
)
{
	const gchar *src_cstrptr, *tar_cstrptr, *tar_cstrptr2;
	gint i, tar_deliminators = 0;
	gchar	*new_path = NULL,
		*ltar_path;


	if(STRISEMPTY(src_obj) || STRISEMPTY(tar_path))
	    return(new_path);

	/* Get copy of parent of the specified target path as ltar_path */
	ltar_path = g_dirname(tar_path);
	if(ltar_path == NULL)
	    ltar_path = STRDUP(tar_path);

	/* Special check, if directories are the same then just return
	 * "." indicating current directory
	 */
	if(!strcmp((const char *)src_obj, (const char *)ltar_path))
	{
	    new_path = STRDUP(".");
	    g_free(ltar_path);
	    return(new_path);
	}


	/* Set source and target starting positions past the toplevel
	 * character
	 */
	src_cstrptr = src_obj + 1;
	tar_cstrptr = ltar_path + 1;


	/* Seek src_cstrptr and tar_cstrptr to the first character
	 * of which they differ
	 */
	while((*src_cstrptr != '\0') && (*tar_cstrptr != '\0'))
	{
	    if(*src_cstrptr != *tar_cstrptr)
		break;

	    src_cstrptr++;
	    tar_cstrptr++;
	}
	/* Deliminator at source position where difference was
	 * encountered? If so then we need to decrement deliminator
	 * count by one
	 *
	 * This will get added up to or past 0 further below
	 */
	if(*src_cstrptr == G_DIR_SEPARATOR)
	    tar_deliminators--;

	/* Seek source position backwards to last deliminator, but keep
	 * it one position ahead of the last deliminator
	 */
	while(src_cstrptr > src_obj)
	{
	    if(*src_cstrptr == G_DIR_SEPARATOR)
	    {
		src_cstrptr++;
		break;
	    }

	    src_cstrptr--;
	}
	/* If source position seeked all the way back to the beginning
	 * then increment it one past the first character to skip the
	 * toplevel character and thus keep source position infront of
	 * the last deliminator
	 */
	if(src_cstrptr <= src_obj)
	    src_cstrptr = src_obj + 1;


	/* Count deliminators in target path from where target position
	 * differed from the corresponding source position
	 */
	tar_deliminators++;
	tar_cstrptr2 = tar_cstrptr;
	while(*tar_cstrptr2 != '\0')
	{
	    if(*tar_cstrptr2 == G_DIR_SEPARATOR)
		tar_deliminators++;

	    tar_cstrptr2++;
	}

	/* Special check, if target happens to be just toplevel then do
	 * not count any deliminators
	 */
	if(!strcmp((const char *)ltar_path, "/"))
	    tar_deliminators = 0;

	/* Begin generating new path */
	g_free(new_path);
	new_path = STRDUP("");
	for(i = 0; i < tar_deliminators; i++)
	    new_path = G_STRCAT(new_path, "../");

	new_path = G_STRCAT(new_path, src_cstrptr);
	if(new_path == NULL)
	{
	    g_free(ltar_path);
	    return(new_path);
	}

	/* If new path was generated as an empty string (perhaps if the
	 * source and target directories were the same), then just set
	 * the new path to "." to indicate current directory
	 */
	if(*new_path == '\0')
	{
	    new_path = G_STRCAT(new_path, ".");
	    if(new_path == NULL)
	    {
		g_free(ltar_path);
		return(new_path);
	    }
	}

	/* Chop off tailing deliminator if any */
	StripPath((char *)new_path);

	g_free(ltar_path);

	return(new_path);
}

/*
 *      Procedure to create a link at the location specified by tar_obj
 *	to the object specified by src_obj.
 *
 *      If new_obj_rtn is not NULL then a dynamically allocated string
 *      containing the full path of the new link will be returned.
 *      The calling function needs to delete it.
 *
 *      If show_progress is TRUE then the progress dialog will be updated
 *      (and mapped as needed). The calling function needs to unmap the
 *      progress dialog.
 *
 *      Returns non-zero on error.
 */
gint EDVFOPLink(
	edv_core_struct *core_ptr,
	const gchar *src_obj, const gchar *tar_obj,
	gchar **new_obj_rtn, GtkWidget *toplevel,
	gboolean show_progress, gboolean interactive,
	gboolean *yes_to_all
)
{
	static gboolean reenterent = FALSE;
	static gint status;
	gboolean archive = TRUE;
	gulong time_start = (gulong)time(NULL);
	gchar *lsrc_obj, *ltar_obj;
	gint src_lstat_result, src_stat_result;
	struct stat src_lstat_buf, src_stat_buf;


	/* Reset returns */
	if(new_obj_rtn != NULL)
	    *new_obj_rtn = NULL;
	/* Leave yes_to_all as whatever value the calling function set it */

	if(reenterent)
	{
	    last_error =
"System is busy, an operation is already in progress";
	    return(-6);
	}
	else
	{
	    reenterent = TRUE;
	}

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

	if((core_ptr == NULL) || STRISEMPTY(src_obj) || STRISEMPTY(tar_obj) ||
	   (yes_to_all == NULL)
	)
	{
	    reenterent = FALSE;
	    return(-1);
	}

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

	/* Get coppies of source and target object paths */
	lsrc_obj = STRDUP(src_obj);
	ltar_obj = STRDUP(tar_obj);
	if((lsrc_obj == NULL) || (ltar_obj == NULL))
	{
	    DO_FREE_LOCALS
	    last_error = "Memory allocation error";
	    reenterent = FALSE;
	    return(-3);
	}

	/* Simplify local coppies of the paths */
	EDVSimplifyPath(lsrc_obj);
	EDVSimplifyPath(ltar_obj);

	/* Skip name and parent path compares, they do not need to be
	 * checked when creating a link
	 */


	/* Get source object statistics, both local and destination */
	src_lstat_result = lstat((const char *)lsrc_obj, &src_lstat_buf);
	src_stat_result = stat((const char *)lsrc_obj, &src_stat_buf);
	if(archive ? src_lstat_result : src_stat_result)
	{
	    /* Unable to get statistics for source object */
	    switch(errno)
	    {
	      case ENOENT:
		last_error = "Unable to find source object";
		break;

	      case ENOTDIR:
		last_error =
"A compoent of the source object is not a valid directory";
		break;

	      case ELOOP:
		last_error =
"Too many symbolic links encountered while resolving source object";
		break;

	      case EACCES:
		last_error =
"Read access not permitted on source object for the current process";
		break;

	      default:
		last_error = "Unable to get statistics for source object";
		break;
	    }
	    DO_FREE_LOCALS
	    reenterent = FALSE;
	    return(-2);
	}

	/* Reset status, it will be updated to indicate any errors
	 * as the correct operation is performed
	 */
	status = 0;

	if(TRUE)
	{
	    const gchar *name = EDVGetPathName(lsrc_obj);
	    gchar *tar_child_obj, *link_destination;
	    gint result;

	    /* Generate full path for a new link object name given the
	     * target path (assumed to be a directory) and the name of
	     * source object
	     *
	     * The generated name is dynamically allocated and is
	     * gauranteed to not reffer to an existing object
	     */
	    tar_child_obj = EDVFOPGenerateNewLinkName(name, ltar_obj);
	    if(tar_child_obj == NULL)
	    {
		status = -1;
		last_error = "Unable to generate new link name";
		DO_FREE_LOCALS
		reenterent = FALSE;
		return(status);
	    }

	    /* Generate path to the given source object from the given
	     * source object path and the new link path tar_child_obj
	     *
	     * The generated path is dynamically allocated
	     */
	    link_destination = EDVFOPGenerateLinkDestination(
		lsrc_obj, tar_child_obj
	    );
	    if(link_destination == NULL)
	    {
		status = -1;
		last_error = "Unable to generate link destination";
		g_free(tar_child_obj);
		DO_FREE_LOCALS
		reenterent = FALSE;
		return(status);
	    }

	    /* Create new link */
	    result = (gint)symlink(
		(const char *)link_destination,	/* Destination Value */
		(const char *)tar_child_obj	/* Link */
	    );

	    /* Successfully created new link and original link local stats
	     * available?
	     */
	    if(!result && !src_lstat_result)
	    {
#if 0
		/* Do not set owner and group, allow symlink() call to
		 * set that automatically
		 */
		lchown(
		    (const char *)tar_obj,
		    src_lstat_buf.st_uid, src_lstat_buf.st_gid
		);
#endif
	    }

	    /* Error creating link? */
	    if(result)
	    {
		switch(errno)
		{
		  case EPERM:
		    last_error =
"Target object filesystem does not support objects of type link";
		    status = -2;
		    break;

		  case EFAULT:
		    last_error =
"Target object name string memory points outside of accessable address space";
		    status = -3;
		    break;

		  case EACCES:
		    last_error =
"Write access not permitted on target object for this process";
		    status = -2;
		    break;

		  case ENAMETOOLONG:
		    last_error =
"Target object path name is too long";
		    status = -2;
		    break;

		  case ENOENT:
		    last_error =
"A directory compoent of the target object does not exist";
		    status = -2;
		    break;

		  case ENOTDIR:
		    last_error =
"A compoent of the target object is not a directory";
		    status = -2;
		    break;

		  case ENOMEM:
		    last_error =
"System is out of memory";
		    status = -3;
		    break;

		  case EROFS:
		    last_error =
"Unable to create link on a read-only filesystem";
		    status = -2;
		    break;

		  case EEXIST:
		    last_error =
"Target object path already exists";
		    status = -2;
		    break;

		  case ELOOP:
		    last_error =
"Too many symbolic links encountered while resolving target object";
		    status = -2;
		    break;

		  case ENOSPC:
		    last_error =
"Unsufficient disk space to create new link";
		    status = -3;
		    break;

		  default:
		    last_error =
"Error creating link";
		    status = -1;
		    break;
		}
	    }

	    /* Delete link destination */
	    g_free(link_destination);
	    link_destination = NULL;

	    /* Transfer tar_child_obj to the return, the calling
	     * function will delete it
	     */
	    if(new_obj_rtn != NULL)
	    {
		g_free(*new_obj_rtn);
		*new_obj_rtn = tar_child_obj;
	    }
	    else
	    {
		g_free(tar_child_obj);
	    }
	    tar_child_obj = NULL;
	}

	/* Record history */
	EDVAppendHistory(
	    core_ptr, EDV_HISTORY_DISK_OBJECT_LINK,
	    time_start, (gulong)time(NULL),
	    status,
	    (new_obj_rtn != NULL) ? *new_obj_rtn : ltar_obj,
	    lsrc_obj,		/* Target */
	    last_error          /* Comment */
	);

	DO_FREE_LOCALS

	reenterent = FALSE;
	return(status);
#undef DO_FREE_LOCALS
}

/*
 *	Updates the link value for an existing link specified by tar_obj,
 *	the new value will be set to new_dest. If the tar_obj exists but
 *	is not a link then this operation will fail.
 *
 *      If show_progress is TRUE then the progress dialog will be updated
 *      (and mapped as needed). The calling function needs to unmap the
 *      progress dialog.
 *
 *      Returns non-zero on error.
 */
gint EDVFOPRelink(
	edv_core_struct *core_ptr,
	const gchar *new_dest, const gchar *tar_obj,
	GtkWidget *toplevel,
	gboolean show_progress, gboolean interactive,
	gboolean *yes_to_all
)
{
	static gboolean reenterent = FALSE;
	static gint status;
/*	gboolean archive = TRUE; */
	gulong time_start = (gulong)time(NULL);
	gchar *ltar_obj;
	struct stat tar_lstat_buf;
	gint tar_lstat_result;


	/* Leave yes_to_all as whatever value the calling function set it */

	if(reenterent)
	{
	    last_error =
"System is busy, an operation is already in progress";
	    return(-6);
	}
	else
	{
	    reenterent = TRUE;
	}

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

	if((core_ptr == NULL) || STRISEMPTY(new_dest) || STRISEMPTY(tar_obj) ||
 	   (yes_to_all == NULL)
	)
	{
	    reenterent = FALSE;
	    return(-1);
	}

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


	/* Get copy of target object path */
	ltar_obj = STRDUP(tar_obj);
	if(ltar_obj == NULL)
	{
	    DO_FREE_LOCALS
	    last_error = "Memory allocation error";
	    reenterent = FALSE;
	    return(-3);
	}

	/* Simplify target path */
	EDVSimplifyPath(ltar_obj);


	/* Get local stats of target object */
	tar_lstat_result = (gint)lstat((const char *)ltar_obj, &tar_lstat_buf);
	if(!tar_lstat_result)
	{
	    /* Target object exists locally, now check if it is of
	     * type link
	     */
	    if(S_ISLNK(tar_lstat_buf.st_mode))
	    {
		/* Remove the existing link object */
		status = EDVFOPDoUnlink(ltar_obj);
		/* Unable to remove link? */
		if(status)
		{
		    DO_FREE_LOCALS
		    reenterent = FALSE;
		    return(status);
		}
	    }
	    else
	    {
		DO_FREE_LOCALS
		last_error = "Object to relink is not a link";
		reenterent = FALSE;
		return(-2);
	    }
	}

	/* Begin relinking by creating new link with the given
	 * destination new_dest
	 */
	status = 0;
	if(TRUE)
	{
	    /* Create new link */
	    gint result = (gint)symlink(
		(const char *)new_dest,	/* Destination Value */
		(const char *)ltar_obj	/* Link */
	    );

	    /* Successfully created new link and original link local
	     * stats available?
	     */
	    if(!result && !tar_lstat_result)
	    {
		/* Set original owner and group if possible */
		lchown(
		    (const char *)tar_obj,
		    tar_lstat_buf.st_uid, tar_lstat_buf.st_gid
		);
	    }

	    /* Error creating link? */
	    if(result)
	    {
		switch(errno)
		{
		  case EPERM:
		    last_error =
"Target object filesystem does not support objects of type link";
		    status = -2;
		    break;

		  case EFAULT:
		    last_error =
"Target object name string memory points outside of accessable address space";
		    status = -3;
		    break;

		  case EACCES:
		    last_error =
"Write access not permitted on target object for this process";
		    status = -2;
		    break;

		  case ENAMETOOLONG:
		    last_error =
"Target object path name is too long";
		    status = -2;
		    break;

		  case ENOENT:
		    last_error =
"A directory compoent of the target object does not exist";
		    status = -2;
		    break;

		  case ENOTDIR:
		    last_error =
"A compoent of the target object is not a directory";
		    status = -2;
		    break;

		  case ENOMEM:
		    last_error =
"System is out of memory";
		    status = -3;
		    break;

		  case EROFS:
		    last_error =
"Unable to create link on a read-only filesystem";
		    status = -2;
		    break;

		  case EEXIST:
		    last_error =
"Target object path already exists";
		    status = -2;
		    break;

		  case ELOOP:
		    last_error =
"Too many symbolic links encountered while resolving target object";
		    status = -2;
		    break;

		  case ENOSPC:
		    last_error =
"Unsufficient disk space to create new link";
		    status = -3;
		    break;

		  default:
		    last_error =
"Error creating link";
		    status = -1;
		    break;
		}
	    }
	}


	/* Record history */
	EDVAppendHistory(
	    core_ptr, EDV_HISTORY_DISK_OBJECT_LINK,
	    time_start, (gulong)time(NULL),
	    status,
	    ltar_obj,		/* Source */
	    new_dest,		/* Target */
	    last_error          /* Comment */
	);

	DO_FREE_LOCALS

	reenterent = FALSE;
	return(status);
#undef DO_FREE_LOCALS
}

/*
 *      Procedure to rename the source object to the name specified by
 *	new_name.
 *
 *      If new_obj_rtn is not NULL then a dynamically allocated string
 *      containing the full path of the new object will be returned.
 *      The calling function needs to delete it.
 *
 *      If show_progress is TRUE then the progress dialog will be updated
 *      (and mapped as needed). The calling function needs to unmap the
 *      progress dialog.
 *
 *      Returns non-zero on error.
 */
gint EDVFOPRename(
	edv_core_struct *core_ptr,
	const gchar *src_obj, const gchar *new_name,
	gchar **new_obj_rtn, GtkWidget *toplevel,
	gboolean show_progress, gboolean interactive,
	gboolean *yes_to_all
)
{
	static gboolean reenterent = FALSE;
	static gint status;
	gchar *parent_path;
	gulong time_start = (gulong)time(NULL);
	gchar *lsrc_obj = NULL, *ltar_obj = NULL;
	struct stat tar_lstat_buf;

	/* Reset returns */
	if(new_obj_rtn != NULL)
	    *new_obj_rtn = NULL;
	/* Leave yes_to_all as whatever value the calling function set it */

	if(reenterent)
	{
	    last_error =
"System is busy, an operation is already in progress";
	    return(-6);
	}
	else
	{
	    reenterent = TRUE;
	}

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

	if((core_ptr == NULL) || STRISEMPTY(src_obj) || STRISEMPTY(new_name) ||
 	   (yes_to_all == NULL)
	)
	{
	    reenterent = FALSE;
	    return(-1);
	}

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

	/* Check if the target object name is valid */
	if(!EDVIsObjectNameValid(new_name))
	{
	    DO_FREE_LOCALS
	    last_error = "Invalid name";
	    reenterent = FALSE;
	    return(-1);
	}

	/* Get copy of source object path */
	lsrc_obj = STRDUP(src_obj);
	if(lsrc_obj == NULL)
	{
	    DO_FREE_LOCALS
	    last_error = "Memory allocation error";
	    reenterent = FALSE;
	    return(-3);
	}
	EDVSimplifyPath(lsrc_obj);

	/* Formulate new ltar_obj path by taking the parent of lsrc_obj
	 * and prefixing it to new_name
	 */
	parent_path = g_dirname(lsrc_obj);
	if(parent_path == NULL)
	{
	    DO_FREE_LOCALS
	    last_error = "Unable to parse original object name";
	    reenterent = FALSE;
	    return(-3);
	}

	/* Format new object name */
	ltar_obj = STRDUP(PrefixPaths(parent_path, new_name));
	g_free(parent_path);
	if(ltar_obj == NULL)
	{
	    DO_FREE_LOCALS
	    last_error = "Unable to parse new object name";
	    reenterent = FALSE;
	    return(-3);
	}
	EDVSimplifyPath(ltar_obj);


	/* Names are the same? */
	if(!strcmp((const char *)lsrc_obj, (const char *)ltar_obj))
	{
	    /* This is not an error, just return 0 and pretend
	     * something happened
	     */
	    if(new_obj_rtn != NULL)
	    {
		g_free(*new_obj_rtn);
		*new_obj_rtn = ltar_obj;
		ltar_obj = NULL;
	    }
	    DO_FREE_LOCALS
	    reenterent = FALSE;
	    return(0);
	}


	/* Check if new name already exists locally? */
	if(!lstat((const char *)ltar_obj, &tar_lstat_buf))
	{
	    DO_FREE_LOCALS
	    last_error = "An object by that name already exists";
	    reenterent = FALSE;
	    return(-2);
	}


	/* Rename the object */
	status = 0;
	if(rename((const char *)lsrc_obj, (const char *)ltar_obj))
	{
	    switch(errno)
	    {
	      case EISDIR:
		last_error = "Unable to rename to existing directory";
		status = -2;
		break;

	      case EXDEV:
		last_error = "Unable to rename across devices";
		status = -2;
		break;

	      case EEXIST:
		last_error = "Unable to rename to non-empty directory";
		status = -2;
		break;

	      case EBUSY:
		last_error = "Unable to rename, the object is currently in use";
		status = -3;
		break;

	      case EINVAL:
		last_error = "Unable to move parent into a child";
		status = -2;
		break;

	      case EMLINK:
		last_error = "Too many links in object path";
		status = -2;
		break;

	      case ENOTDIR:
		last_error = "A path compoent is not a directory";
		status = -2;
		break;

	      case EFAULT:
		last_error =
"Object name string memory points outside of accessable address space";
		status = -3;
		break;

	      case EACCES:
		last_error =
"Directory does not allow write permission to this process";
		status = -2;
		break;

	      case ENAMETOOLONG:
		last_error = "Object path name is too long";
		status = -2;
		break;

	      case ENOENT:
		last_error =
"Object path directory compoent does not exist";
		status = -1;
		break;

	      case ENOMEM:
		last_error = "System is out of memory";
		status = -3;
		break;

	      case EROFS:
		last_error =
"Unable to rename object on a read-only filesystem";
		status = -2;
		break;

	      case ELOOP:
		last_error =
"Too many symbolic links encountered while resolving directory";
		status = -3;
		break;

	      case ENOSPC:
		last_error =
"Insufficient disk space to move object to target location";
		status = -3;
		break;

	      default:
		last_error = "Error renaming object";
		status = -1;
		break;
	    }
	}
	else
	{
	    /* Renamed successfully, now transfer the ltar_obj to the
	     * return value if possible so calling function will
	     * delete it
	     */
	    if(new_obj_rtn != NULL)
	    {
		g_free(*new_obj_rtn);
		*new_obj_rtn = ltar_obj;
		ltar_obj = NULL;
	    }
	}


	/* Record history */
	EDVAppendHistory(
	    core_ptr, EDV_HISTORY_DISK_OBJECT_MOVE,
	    time_start, (gulong)time(NULL),
	    status,
	    lsrc_obj,		/* Source */
	    (new_obj_rtn != NULL) ? *new_obj_rtn : ltar_obj,
	    last_error		/* Comment */
	);

	DO_FREE_LOCALS

	reenterent = FALSE;
	return(status);
#undef DO_FREE_LOCALS
}


/*
 *      Procedure to change permissions the source object.
 *
 *      If show_progress is TRUE then the progress dialog will be updated
 *      (and mapped as needed). The calling function needs to unmap the
 *      progress dialog.
 *
 *      Returns non-zero on error.
 */
gint EDVFOPChmod(
	edv_core_struct *core_ptr,
	const gchar *src_obj, guint permissions,
	GtkWidget *toplevel,
	gboolean show_progress, gboolean interactive,
	gboolean *yes_to_all
)
{
	static gboolean reenterent = FALSE;
	static gint status;
	gulong time_start = (gulong)time(NULL);
	mode_t m = 0;
	gchar *tar_str = NULL;
	gchar *lsrc_obj = NULL;


	if(reenterent)
	{
	    last_error =
"System is busy, an operation is already in progress";
	    return(-6);
	}
	else
	{
	    reenterent = TRUE;
	}

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

	if((core_ptr == NULL) || STRISEMPTY(src_obj) || (yes_to_all == NULL))
	{
	    reenterent = FALSE;
	    return(-1);
	}

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

	/* Copy source object path */
	lsrc_obj = STRDUP(src_obj);


	/* Convert the given permissions mask to stat() mode_t
	 * format
	 */
	m = EDVObjectGetPermissionsFromEDVPermissions(permissions);

	/* Change permissions on object */
	status = 0;
	if(chmod((const char *)lsrc_obj, m))
	{
	    switch(errno)
	    {
	      case EPERM:
		last_error =
"Effective user id of this process is not the owner of the object";
		status = -2;
		break;

	      case EROFS:
		last_error =
"Unable to rename object on a read-only filesystem";
		status = -2;
		break;

	      case EFAULT:
		last_error =
"Object name string memory points outside of accessable address space";
		status = -3;
		break;

	      case ENAMETOOLONG:
		last_error =
"Object path name is too long";
		status = -2;
		break;

	      case ENOENT:
		last_error =
"Object does not exist";
		status = -2;
		break;

	      case ENOMEM:
		last_error =
"System is out of memory";
		status = -3;
		break;

	      case ENOTDIR:
		last_error =
"A compoent of the object path is not a valid directory";
		status = -2;
		break;

	      case EACCES:
		last_error =
"Search permission was denied for a compoent of the object path";
		status = -2;
		break;

	      case ELOOP:
		last_error =
"Too many symbolic links encountered while resolving target object";
		status = -2;
		break;

	      case EIO:
		last_error =
"IO error occured while setting object permissions";
		status = -3;
		break;

	      default:
		last_error =
"Error setting object permissions";
		status = -1;
		break;
	    }
	}

	/* Format permissions string for logging as target */
	tar_str = g_strdup_printf(
	    "%c%c%c%c%c%c%c%c%c",
	    (permissions & EDV_PERMISSION_UREAD) ? 'r' : '-',
	    (permissions & EDV_PERMISSION_UWRITE) ? 'w' : '-',
	    (permissions & EDV_PERMISSION_SETUID) ?
		'S' :
		((permissions & EDV_PERMISSION_UEXECUTE) ? 'x' : '-'),
	    (permissions & EDV_PERMISSION_GREAD) ? 'r' : '-',
	    (permissions & EDV_PERMISSION_GWRITE) ? 'w' : '-',
	    (permissions & EDV_PERMISSION_SETGID) ?
		'G' :
		((permissions & EDV_PERMISSION_GEXECUTE) ? 'x' : '-'),
	    (permissions & EDV_PERMISSION_AREAD) ? 'r' : '-',
	    (permissions & EDV_PERMISSION_AWRITE) ? 'w' : '-',
	    (permissions & EDV_PERMISSION_STICKY) ?
		'T' :
		((permissions & EDV_PERMISSION_AEXECUTE) ? 'x' : '-')
	);

	/* Record history */
	EDVAppendHistory(
	    core_ptr, EDV_HISTORY_DISK_OBJECT_CHMOD,
	    time_start, (gulong)time(NULL),
	    status,
	    lsrc_obj,		/* Source */
	    tar_str,		/* Target */
	    last_error		/* Comment */
	);

	DO_FREE_LOCALS

	reenterent = FALSE;
	return(status);
#undef DO_FREE_LOCALS
}

/*
 *      Procedure to change owner and group of the source object.
 *
 *      If show_progress is TRUE then the progress dialog will be updated
 *      (and mapped as needed). The calling function needs to unmap the
 *      progress dialog.
 *
 *      Returns non-zero on error.
 */
gint EDVFOPChown(
	edv_core_struct *core_ptr,
	const gchar *src_obj, gint owner_id, gint group_id,
	GtkWidget *toplevel,
	gboolean show_progress, gboolean interactive,
	gboolean *yes_to_all
)
{
	static gboolean reenterent = FALSE;
	static gint status;
	gulong time_start = (gulong)time(NULL);
	gchar *tar_str = NULL;
	gchar *lsrc_obj = NULL;


	if(reenterent)
	{
	    last_error =
"System is busy, an operation is already in progress";
	    return(-6);
	}
	else
	{
	    reenterent = TRUE;
	}

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

	if((core_ptr == NULL) || STRISEMPTY(src_obj) || (yes_to_all == NULL))
	{
	    reenterent = FALSE;
	    return(-1);
	}

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

	/* Copy source object path */
	lsrc_obj = STRDUP(src_obj);


	/* Change owner and group of the object locally */
	status = 0;
	if(lchown((const char *)lsrc_obj, (uid_t)owner_id, (gid_t)group_id))
	{
	    switch(errno)
	    {
	      case EPERM:
		last_error =
"Insufficient permission to change ownership and/or group of the object";
		status = -2;
		break;

	      case EROFS:
		last_error =
"Unable to set owner and group of an object on a read-only filesystem";
		status = -2;
		break;

	      case EFAULT:
		last_error =
"Object name string memory points outside of accessable address space";
		status = -3;
		break;

	      case ENAMETOOLONG:
		last_error =
"Object path name is too long";
		status = -2;
		break;

	      case ENOENT:
		last_error =
"Object does not exist";
		status = -2;
		break;

	      case ENOMEM:
		last_error =
"System is out of memory";
		status = -3;
		break;

	      case ENOTDIR:
		last_error =
"A compoent of the object path is not a valid directory";
		status = -2;
		break;

	      case EACCES:
		last_error =
"Search permission was denied for a compoent of the object path";
		status = -2;
		break;

	      case ELOOP:
		last_error =
"Too many symbolic links encountered while resolving target object";
		status = -2;
		break;

	      case EIO:
		last_error =
"IO error occured while setting object owner and group";
		status = -3;
		break;

	      default:
		last_error =
"Error setting object owner and group";
		status = -1;
		break;
	    }
	}

	/* Format permissions string for logging as target */
	tar_str = g_strdup_printf(
	    "%s/%s",
	    EDVUIDGetNameFromUID(
		core_ptr->uid, core_ptr->total_uids,
		owner_id, NULL
	    ),
	    EDVGIDGetNameFromGID(
		core_ptr->gid, core_ptr->total_gids,
		group_id, NULL
	    )
	);

	/* Record history */
	EDVAppendHistory(
	    core_ptr, EDV_HISTORY_DISK_OBJECT_CHOWN,
	    time_start, (gulong)time(NULL),
	    status,
	    lsrc_obj,		/* Source */
	    tar_str,		/* Target */
	    last_error		/* Comment */
	);

	DO_FREE_LOCALS

	reenterent = FALSE;
	return(status);
#undef DO_FREE_LOCALS
}
