#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <time.h>
#include <sys/stat.h>
#include <utime.h>
#include <gtk/gtk.h>
#include <unistd.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 "edv_types.h"
#include "edv_date.h"
#include "edv_obj.h"
#include "endeavour2.h"
#include "edv_obj_op.h"
#include "edv_utils.h"
#include "edv_utils_gtk.h"
#include "edv_cfg_list.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"
#include "images/icon_chmod_32x32.xpm"
#include "images/icon_owned_32x32.xpm"
#include "images/icon_time_stamp_32x32.xpm"


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


/* Error Message */
const gchar *EDVObjectOPGetError(void);
static void EDVObjectOPCopyErrorMessage(const gchar *msg);

/* Checks */
static gboolean EDVObjectOPAreObjectsSame(
	const struct stat *src_stat_buf, const struct stat *tar_stat_buf
);

/* Confirmation */
static gint EDVObjectOPConfirmOverwrite(
	edv_core_struct *core, GtkWidget *toplevel,
	const gchar *src_path, const gchar *tar_path,
	const struct stat *src_lstat_buf, const struct stat *tar_lstat_buf
);

/* Progress Dialog Mapping */
static void EDVObjectOPMapProgressDialogMove(
	const gchar *label, GtkWidget *toplevel,
	const gboolean force_remap
);
static void EDVObjectOPMapProgressDialogCopy(
	const gchar *label, GtkWidget *toplevel,
	const gboolean force_remap
);

/* Counting */
static gint EDVObjectOPCountObjects(
	const gchar *path,
	const gboolean recursive,
	const gboolean archive
);

/* Deleting */
static gint EDVObjectOPUnlink(const gchar *path);
static gint EDVObjectOPDeleteDirectory(const gchar *path);

/* Object Copying */
static gint EDVObjectOPCopyRegular(
	const gchar *src_path, const gchar *tar_path,
	const struct stat *src_lstat_buf,
	const struct stat *src_stat_buf
);
static gint EDVObjectOPCopyLink(
	const gchar *src_path, const gchar *tar_path,
	const struct stat *src_lstat_buf,
	const struct stat *src_stat_buf
);
static gint EDVObjectOPCopyFIFO(
	const gchar *src_path, const gchar *tar_path,
	const struct stat *src_lstat_buf,
	const struct stat *src_stat_buf
);
static gint EDVObjectOPCopyDeviceBlock(
	const gchar *src_path, const gchar *tar_path,
	const struct stat *src_lstat_buf,
	const struct stat *src_stat_buf
);
static gint EDVObjectOPCopyDeviceCharacter(
	const gchar *src_path, const gchar *tar_path,
	const struct stat *src_lstat_buf,
	const struct stat *src_stat_buf
);
static gint EDVObjectOPCopySocket(
	const gchar *src_path, const gchar *tar_path,
	const struct stat *src_lstat_buf,
	const struct stat *src_stat_buf
);
static gint EDVObjectOPCopyNexus(
	const gchar *src_path, const gchar *tar_path,
	const struct stat *src_lstat_buf,
	const struct stat *src_stat_buf,
	const gboolean archive
);

/* Object Moving */
static gint EDVObjectOPMoveObjectsLocal(
	edv_core_struct *core,
	const gchar *src_path, const gchar *tar_path,
	const struct stat *src_lstat_buf, const struct stat *src_stat_buf,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	const gboolean archive,
	gint *status, gboolean *yes_to_all,
	gboolean *need_copy
);

/* Object Copying */
static void EDVObjectOPCopyObjectsLocal(
	edv_core_struct *core,
	const gchar *src_path, const gchar *tar_path,
	const struct stat *src_lstat_buf, const struct stat *src_stat_buf,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	const gboolean archive,	gint *status, gboolean *yes_to_all,
	const gboolean originally_move
);

/* Directory Copying */
static void EDVObjectOPCopyDirectoryIterate(
	edv_core_struct *core,
	const gchar *src_dir, const gchar *tar_dir,
	const struct stat *src_lstat_buf, const struct stat *src_stat_buf,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	const gboolean archive,
	gint *status, gboolean *yes_to_all,
	const gboolean originally_move
);

/* Copy & Move */
static gint EDVObjectOPCopyMove(
	const gboolean is_copy,
	edv_core_struct *core,
	const gchar *src_path, const gchar *tar_path,
	gchar **new_path_rtn, GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all
);
gint EDVObjectOPCopy(
	edv_core_struct *core,
	const gchar *src_path, const gchar *tar_path,
	gchar **new_path_rtn, GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all
);
gint EDVObjectOPMove(
	edv_core_struct *core,
	const gchar *src_path, const gchar *tar_path,
	gchar **new_path_rtn, GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all
);

/* Link */
static gchar *EDVObjectOPGenerateNewLinkPath(
	const gchar *path, const gchar *dest_name
);
static gchar *EDVObjectOPGenerateLinkDestination(
	const gchar *path, const gchar *dest_path
);
gint EDVObjectOPLink(
	edv_core_struct *core,
	const gchar *path,
	const gchar *dest_path,
	gchar **new_path_rtn,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all
);
gint EDVObjectOPRelink(
	edv_core_struct *core,
	const gchar *path,
	const gchar *new_dest_value,
	GList **modified_paths_list,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all
);

/* Rename */
gint EDVObjectOPRename(
	edv_core_struct *core,
	const gchar *path,
	const gchar *new_name,
	GList **modified_paths_list,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all
);

/* Change Permissions */
static gint EDVObjectOPChModIterate(
	edv_core_struct *core,
	const gchar *path,
	const mode_t m,
	GList **modified_paths_list,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all,
	const gboolean recursive, const gboolean archive,
	gint *nobjs_processed, const gint nobjs
);
gint EDVObjectOPChMod(
	edv_core_struct *core,
	GList *paths_list,
	const edv_permission_flags permissions,
	GList **modified_paths_list,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all,
	const gboolean recursive, const gboolean archive
);

/* Change Ownership */
static gint EDVObjectOPChOwnIterate(
	edv_core_struct *core,
	const gchar *path,
	const gint owner_id, const gint group_id,
	GList **modified_paths_list,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all,
	const gboolean recursive, const gboolean archive,
	gint *nobjs_processed, const gint nobjs
);
gint EDVObjectOPChOwn(
	edv_core_struct *core,
	GList *paths_list,
	const gint owner_id, const gint group_id,
	GList **modified_paths_list,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all,
	const gboolean recursive, const gboolean archive
);

/* Change Time Stamps */
static gint EDVObjectOPChTimeIterate(
	edv_core_struct *core,
	const gchar *path, struct utimbuf *ut_buf,
	GList **modified_paths_list,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all,
	const gboolean recursive, const gboolean archive,
	gint *nobjs_processed, const gint nobjs
);
gint EDVObjectOPChTime(
	edv_core_struct *core,
	GList *paths_list,
	const gulong atime, const gulong mtime,
	GList **modified_paths_list,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all,
	const gboolean recursive, const gboolean archive
);


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


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

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

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


/*
 *	Checks if the two object's statistics specify that they are
 *	the same device and inode.
 */
static gboolean EDVObjectOPAreObjectsSame(
	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 EDVObjectOPConfirmOverwrite(
	edv_core_struct *core, GtkWidget *toplevel,
	const gchar *src_path, const gchar *tar_path,
	const struct stat *src_lstat_buf, const struct stat *tar_lstat_buf
)
{
	gint result;
	edv_date_relativity relativity;
	const gchar *format;
	gchar *msg, *src_date, *tar_date, *src_size_s, *tar_size_s;
	gulong src_size, tar_size;
	cfg_item_struct *cfg_list;

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

	cfg_list = core->cfg_list;

	/* Get the date relativity and format */
	relativity = (edv_date_relativity)EDV_GET_I(
	    EDV_CFG_PARM_DATE_RELATIVITY
	);
	format = EDV_GET_S(EDV_CFG_PARM_DATE_FORMAT);

	/* Get the date strings for the 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 the sizes of source and target objects in units of bytes */
	src_size = (src_lstat_buf != NULL) ?
	    (gulong)src_lstat_buf->st_size : 0l;
	src_size_s = STRDUP(EDVGetObjectSizeStr(core, src_size));

	tar_size = (tar_lstat_buf != NULL) ?
	    (gulong)tar_lstat_buf->st_size : 0l;
	tar_size_s = STRDUP(EDVGetObjectSizeStr(core, tar_size));

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

	EDVPlaySoundWarning(core);
	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
	    , msg, 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(src_size_s);
	g_free(tar_size_s);
	g_free(msg);
	g_free(tar_date);
	g_free(src_date);

	return(result);
}


/*
 *	Maps the progress dialog as needed in animation mode for
 *	moving an object.
 */
static void EDVObjectOPMapProgressDialogMove(
	const gchar *label, GtkWidget *toplevel,
	const 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,
	    EDV_DEF_PROGRESS_DLG_ANIM_INT,
	    EDV_DEF_PROGRESS_DLG_ANIM_INC
	);
	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 an object.
 */
static void EDVObjectOPMapProgressDialogCopy(
	const gchar *label, GtkWidget *toplevel,
	const 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,
	    EDV_DEF_PROGRESS_DLG_ANIM_INT,
	    EDV_DEF_PROGRESS_DLG_ANIM_INC
	);
	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();
}


/*
 *	Counts the number of objects.
 *
 *	The path specifies the object to count.
 *
 *	If recursive is TRUE and path is a directory then all the
 *	objects in that directory will be counted as well.
 *
 *	If archive is TRUE and path is a link that leads to a
 *	directory then only the link will be counted and none of
 *	the objects in the destination directory will be counted
 *	regardless of if recursive is TRUE.
 */
static gint EDVObjectOPCountObjects(
	const gchar *path,
	const gboolean recursive,
	const gboolean archive
)
{
	gint nobjs = 0;
	mode_t m;
	struct stat stat_buf;

	if(STRISEMPTY(path))
	    return(nobjs);

	if(lstat((const char *)path, &stat_buf))
	    return(nobjs);

	m = stat_buf.st_mode;

	/* Count this object */
	nobjs++;

	/* Directory? */
#ifdef S_ISDIR
	if(S_ISDIR(m) && recursive)
#else
	if(FALSE)
#endif
	{
	    gint i, strc;
	    const gchar *name;
	    gchar *full_path;
	    gchar **strv = GetDirEntNames2(path, &strc);
	    for(i = 0; i < strc; i++)
	    {
		name = strv[i];
		if(name == NULL)
		    continue;

		/* Ignore current and parent directory notations */
		if(!strcmp((const char *)name, ".") ||
		   !strcmp((const char *)name, "..")
		)
		{
		    g_free(strv[i]);
		    continue;
		}

		full_path = STRDUP(PrefixPaths(
		    (const char *)path, (const char *)name
		));
		nobjs += EDVObjectOPCountObjects(
		    full_path, recursive, archive
		);
		g_free(full_path);
		g_free(strv[i]);   
	    }
	    g_free(strv);
	}
	/* Link? */
#ifdef S_ISLNK
	else if(S_ISLNK(m))   
#else
	else if(FALSE)
#endif
	{
	    /* Count links as links instead of their destination? */
	    if(!archive)
	    {
		/* Get the destination object's stats */
		if(!stat((const char *)path, &stat_buf))
		{
		    const mode_t m = stat_buf.st_mode;

		    /* Destination is a directory? */
#ifdef S_ISDIR
		    if(S_ISDIR(m))
#else
		    if(FALSE)
#endif
		    {
			gint i, strc;
			const gchar *name;
			gchar *full_path;
			gchar **strv = GetDirEntNames2(path, &strc);
			for(i = 0; i < strc; i++)
			{
			    name = strv[i];
			    if(name == NULL)
				continue;

			    /* Ignore current and parent directory notations */
			    if(!strcmp((const char *)name, ".") ||
			       !strcmp((const char *)name, "..")
			    ) 
			    {
				g_free(strv[i]);
				continue;
			    }

			    full_path = STRDUP(PrefixPaths(
				(const char *)path, (const char *)name
			    ));
			    nobjs += EDVObjectOPCountObjects(
				full_path, recursive, archive
			    );
			    g_free(full_path);
			    g_free(strv[i]);
			}
			g_free(strv);
		    }
		}
	    }
	}

	return(nobjs);
}

/*
 *	Unlinks the object.
 *
 *	The path specifies the object to unlink.
 *
 *	If an error occures then last_error will be set to the error
 *	message that describes what error occured.
 *
 *	Returns 0 on success or non-zero on error. Can also return
 *	0 if the object did not exist.
 */
static gint EDVObjectOPUnlink(const gchar *path)
{
	if(STRISEMPTY(path))
	    return(-1);

#define SET_LAST_ERROR(_path_,_error_code_) {	\
 if(last_error == NULL) {			\
  gchar *s = g_strconcat(			\
   "Unable to delete \"",			\
   g_basename(_path_),				\
   "\", ",					\
   g_strerror(_error_code_),			\
   NULL						\
  );						\
  EDVObjectOPCopyErrorMessage(s);		\
  g_free(s);					\
 }						\
}

	if(unlink((const char *)path))
	{
	    const gint error_code = (gint)errno;
	    switch(error_code)
	    {
#ifdef EFAULT
	      /* Segmentation fault */
	      case EFAULT:
		SET_LAST_ERROR(path, error_code);
		return(-3);
		break;
#endif
#ifdef EACCES
	      /* Access denied */
	      case EACCES:
		SET_LAST_ERROR(path, error_code);
		return(-1);
		break;
#endif
#ifdef EPERM
	      /* Permission denied */
	      case EPERM:
		SET_LAST_ERROR(path, error_code);
		return(-1);
		break;
#endif
#ifdef ENAMETOOLONG
	      /* Invalid name */
	      case ENAMETOOLONG:
		SET_LAST_ERROR(path, error_code);
		return(-2);
		break;
#endif
#ifdef ENOENT
	      /* Object does not exist */
	      case ENOENT:
		/* Do not process this as an error */
		break;
#endif
#ifdef ENOTDIR
	      /* Object does not exist */
	      case ENOTDIR:
		/* Do not process this as an error */
		break;
#endif
#ifdef EISDIR
	      /* The object is a directory */
	      case EISDIR:
		SET_LAST_ERROR(path, error_code);
		return(-2);
		break;
#endif
#ifdef ENOMEM
	      /* Out of memory */
	      case ENOMEM:
		SET_LAST_ERROR(path, error_code);
		return(-3);
		break;
#endif
#ifdef EROFS
	      /* The object is on a read only filesystem */
	      case EROFS:
		SET_LAST_ERROR(path, error_code);
		return(-1);
		break;
#endif
#ifdef ELOOP
	      /* Too many symbolic links encountered */
	      case ELOOP:
		SET_LAST_ERROR(path, error_code);
		return(-3);
		break;
#endif
#ifdef EIO
	      /* IO error */
	      case EIO:
		SET_LAST_ERROR(path, error_code);
		return(-1);
		break;
#endif
#ifdef EBUSY
	      /* The system is currently busy */
	      case EBUSY:
		SET_LAST_ERROR(path, error_code);
		return(-3);
		break;
#endif
#ifdef ETXTBUSY
	      /* The file is currently in use */
	      case ETXTBUSY:
		SET_LAST_ERROR(path, error_code);
		return(-3);
		break;
#endif
#ifdef EINTR
	      /* Operation interrupted */
	      case EINTR:
		return(-4);
		break;
#endif
#ifdef EMULTIHOP
	      /* Multihop */
	      case EMULTIHOP:
		SET_LAST_ERROR(path, error_code);
		return(-1);
		break;
#endif
#ifdef ENOLINK
	      /* No link */
	      case ENOLINK:
		SET_LAST_ERROR(path, error_code);
		return(-1);
		break;
#endif
	      /* All other errors */
	      default:
		SET_LAST_ERROR(path, error_code);
		return(-1);
		break;
	    }
	}
#undef SET_LAST_ERROR

	return(0);
}

/*
 *	Deletes the directory and all the objects in that directory.
 *
 *	The path specifies the directory to delete.
 *
 *	If an error occures then last_error will be set to the error
 *	message that describes what error occured.
 *
 *	Returns 0 on success or non-zero if the directory or any
 *	object in the directory could not be deleted.
 */
static gint EDVObjectOPDeleteDirectory(const gchar *path)
{
	gint status, strc;
	gchar **strv;

	/* If no path was specified then return success */
	if(path == NULL)
	    return(0);

	status = 0;

	/* Get the list of objects in the directory */
	strv = (gchar **)GetDirEntNames2(
	    (const char *)path, (int *)&strc
	);
	if(strv != NULL)
	{
	    struct stat lstat_buf;
	    gint i;
	    const gchar *s;
	    gchar *child_path = NULL;

	    /* Delete each object in the directory */
	    for(i = 0; i < strc; i++)
	    {
		s = strv[i];
		if(s == NULL)
		    continue;

		/* Error encountered? */
		if(status != 0)
		{
		    g_free(strv[i]);
		    continue;
		}

		/* Skip special dir notations */
		if(!strcmp((const char *)s, "..") ||
		   !strcmp((const char *)s, ".")
		)
		{
		    g_free(strv[i]);
		    continue;
		}

		/* Formulate full path to child */
		child_path = STRDUP(PrefixPaths(path, s));
		if(child_path == NULL)
		{
		    g_free(strv[i]);
		    continue;
		}

		/* Get this object's local statistics */
		if(lstat((const char *)child_path, &lstat_buf))
		{
		    g_free(child_path);
		    g_free(strv[i]);
		    continue;
		}

		/* Is this object a directory? */
#ifdef S_ISDIR
		if(S_ISDIR(lstat_buf.st_mode))
#else
		if(FALSE)
#endif
		{
		    /* Delete all objects in this directory and the
		     * directory itself
		     */
		    status = EDVObjectOPDeleteDirectory(child_path);
		}
	 	else
		{
		    /* Delete this object */
		    status = EDVObjectOPUnlink(child_path);
		}

		g_free(child_path);
		g_free(strv[i]);
	    }

	    g_free(strv);
	}

	/* Stop if an error occured */
	if(status != 0)
	    return(status);

	/* Remove this directory */
	if(rmdir((const char *)path))
	{
	    const gint error_code = (gint)errno;
	    gchar *s = g_strconcat(
		"Unable to delete the directory \"",
		g_basename(path),
		"\", ",
		g_strerror(error_code),
		NULL
	    );
	    EDVObjectOPCopyErrorMessage(s);
	    g_free(s);
	    switch(error_code)
	    {
#ifdef EFAULT
	      /* Segmentation fault */
	      case EFAULT:
		status = -3;
		break;
#endif
#ifdef EACCES
	      /* Access denied */
	      case EACCES:
		status = -1;
		break;
#endif
#ifdef EPERM
	      /* Permission denied */
	      case EPERM:
		status = -1;
		break;
#endif
#ifdef ENAMETOOLONG
	      /* Name too long */
	      case ENAMETOOLONG:
		status = -2;
		break;
#endif
#ifdef ENOENT
	      /* No such object */
	      case ENOENT:
		status = -1;
		break;
#endif
#ifdef ENOTDIR
	      /* The directory to remove is not, in fact, a directory */
	      case ENOTDIR:
		status = -2;
		break;
#endif
#ifdef ENOTEMPTY
	      /* The directory is not empty */
	      case ENOTEMPTY:
		status = -1;
		break;
#endif
#ifdef EBUSY
	      /* The system is currently busy */
	      case EBUSY:
		status = -3;
		break;
#endif
#ifdef ENOMEM
	      /* Out of memory */
	      case ENOMEM:
		status = -3;
		break;
#endif
#ifdef EROFS
	      /* The directory is on a read-only filesystem */
	      case EROFS:
		status = -1;
		break;
#endif
#ifdef ELOOP
	      /* Too many symbolic links encountered */
	      case ELOOP:
		status = -3;
		break;
#endif
	      default:
		status = -1;
		break;
	    }
	}

	return(status);
}


/*
 *	Coppies the file.
 *
 *	The src_path specifies the full path of the file to be
 *	coppied. The src_path must be a file.
 *
 *	The tar_path specifies the full path to where to copy to,
 *	there must not be an object already existing at this location.
 *
 *	If an error occures then last_error will be set to the error
 *	message that describes what error occured.
 *
 *	Returns 0 on success or non-zero on error.
 */
static gint EDVObjectOPCopyRegular(
	const gchar *src_path, const gchar *tar_path,
	const struct stat *src_lstat_buf,
	const struct stat *src_stat_buf
)
{
	gint status;
	gulong file_size, read_buf_len;
	guint8 *read_buf;
	FILE *tar_fp, *src_fp;
	struct stat tar_stat_buf;

	/* Open the target file for writing */
	tar_fp = fopen((const char *)tar_path, "wb");
	if(tar_fp == NULL)
	{
	    const gint error_code = (gint)errno;
	    gchar *s = g_strconcat(
		"Unable to open the file \"",
		g_basename(tar_path),
		"\" for writing, ",
		g_strerror(error_code),
		NULL
	    );
	    EDVObjectOPCopyErrorMessage(s);
	    g_free(s);
	    return(-1);
	}

	/* Open the source file for reading */
	src_fp = fopen((const char *)src_path, "rb");
	if(src_fp == NULL)
	{
	    const gint error_code = (gint)errno;
	    gchar *s = g_strconcat(
		"Unable to open the file \"",
		g_basename(src_path),
		"\" for reading, ",
		g_strerror(error_code),
		NULL
	    );
	    EDVObjectOPCopyErrorMessage(s);
	    g_free(s);
	    fclose(tar_fp);
	    return(-1);
	}

	/* Get the source file's statistics and the read buffer size */
	if(src_lstat_buf != NULL)
	{
	    const gint tar_fd = (gint)fileno(tar_fp);

	    file_size = (gulong)src_lstat_buf->st_size;
	    read_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);
	}
	else
	{
	    file_size = 0l;
	    read_buf_len = 0l;
	}

	/* Get the target file's statistics */
	if(!fstat(fileno(tar_fp), &tar_stat_buf))
	{
	    /* Need to use a smaller read buffer? */
	    if((gulong)tar_stat_buf.st_blksize < read_buf_len)
		read_buf_len = (gulong)tar_stat_buf.st_blksize;
	}

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

	status = 0;

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

	    while(!feof(src_fp))
	    {
		/* Read this block from the source file */
		units_read = (gint)fread(
		    read_buf, sizeof(guint8), (size_t)read_buf_len, src_fp
		);
		if(units_read <= 0)
		{
		    if(units_read < 0)
		    {
			/* Read error */
			const gint error_code = (gint)errno;
			gchar *s = g_strconcat(
			    "Unable to read from the file \"",
			    g_basename(src_path),
			    "\", ",
			    g_strerror(error_code),
			    NULL
			);
			EDVObjectOPCopyErrorMessage(s);
			g_free(s);
			status = -1;
		    }
		    break;
		}

		/* Write this block to the target file */
		if(fwrite(
		    read_buf, sizeof(guint8), (size_t)units_read, tar_fp
		) != (size_t)units_read)
		{
		    /* Write error */
		    const gint error_code = (gint)errno;
		    gchar *s = g_strconcat(
			"Unable to write to the file \"",
			g_basename(tar_path),
			"\", ",
			g_strerror(error_code),
			NULL
		    );
		    EDVObjectOPCopyErrorMessage(s);
		    g_free(s);
		    status = -1;
		    break;
		}

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

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

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

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

	    /* Read the first character from the source file */
	    gint c = (gint)fgetc(src_fp);
	    while((int)c != EOF)
	    {
		/* Write the character to the target file */
		if(fputc((int)c, tar_fp) == EOF)
		{
		    const gint error_code = (gint)errno;
		    gchar *s = g_strconcat(
			"Unable to write to the file \"",
			g_basename(tar_path),
			"\", ",
			g_strerror(error_code),
			NULL
		    );
		    EDVObjectOPCopyErrorMessage(s);
		    g_free(s);
		    status = -1;
		    break;
		}

		copy_cnt++;
		cur_size++;

		/* Read the next character from the source file */
		c = (gint)fgetc(src_fp);

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

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

		    copy_cnt = 0l;
		}
	    }
	}

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

	return(status);
}

/*
 *	Coppies the link.
 *
 *	The src_path specifies the full path of the link to be
 *	coppied. The src_path must be a link.
 *
 *	The tar_path specifies the full path to where to copy to,
 *	there must not be an object already existing at this location.
 *
 *	If an error occures then last_error will be set to the error
 *	message that describes what error occured.
 *
 *	Returns 0 on success or non-zero on error.
 */
static gint EDVObjectOPCopyLink(
	const gchar *src_path, const gchar *tar_path,
	const struct stat *src_lstat_buf,
	const struct stat *src_stat_buf
)
{
	gchar *v;

	/* Report the initial progress */
	if(ProgressDialogIsQuery())
	{
	    ProgressDialogUpdate(
		NULL, NULL,
		NULL, NULL,
		0.0f, EDV_DEF_PROGRESS_BAR_TICKS, TRUE
	    );
	    if(ProgressDialogStopCount() > 0)
		return(-4);
	}

	/* Get the link's value */
	v = EDVGetLinkValue(src_path);
	if(v == NULL)
	{
	    const gint error_code = (gint)errno;
	    gchar *s = g_strconcat(
		"Unable to read from the link, ",
		g_strerror(error_code),
		NULL
	    );
	    EDVObjectOPCopyErrorMessage(s);
	    g_free(s);
	    return(-1);
	}

	/* Report progress */
	if(ProgressDialogIsQuery())
	{
	    ProgressDialogUpdate(
		NULL, NULL,
		NULL, NULL,
		0.5f, EDV_DEF_PROGRESS_BAR_TICKS, TRUE
	    );
	    if(ProgressDialogStopCount() > 0)
	    {
		g_free(v);
		return(-4);
	    }
	}


	/* Create the new link */
	if(symlink(
	    (const char *)v,		/* Value */
	    (const char *)tar_path	/* Path */
	))
	{
	    const gint error_code = (gint)errno;
	    gchar *s = g_strconcat(
		"Unable to copy the link, ",
		g_strerror(error_code),
		NULL
	    );
	    EDVObjectOPCopyErrorMessage(s);
	    g_free(s);
	    g_free(v);
	    return(-1);
	}

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

	/* Links do not have any permissions to restore */

	/* Report the final progress */
	if(ProgressDialogIsQuery())
	{
	    ProgressDialogUpdate(
		NULL, NULL,
		NULL, NULL,
		1.0f, EDV_DEF_PROGRESS_BAR_TICKS, TRUE
	    );
	    if(ProgressDialogStopCount() > 0)
		return(-4);
	}    

	return(0);
}

/*
 *	Coppies the FIFO pipe.
 *
 *	The src_path specifies the full path of the FIFO pipe to be
 *	coppied. The src_path must be a FIFO pipe.
 *
 *	The tar_path specifies the full path to where to copy to,
 *	there must not be an object already existing at this location.
 *
 *	If an error occures then last_error will be set to the error
 *	message that describes what error occured.
 *
 *	Returns 0 on success or non-zero on error.
 */
static gint EDVObjectOPCopyFIFO(
	const gchar *src_path, const gchar *tar_path,
	const struct stat *src_lstat_buf,
	const struct stat *src_stat_buf
)
{
#if defined(S_IFFIFO) || defined(S_IFIFO)

	/* Report the initial progress */
	if(ProgressDialogIsQuery())
	{
	    ProgressDialogUpdate(
		NULL, NULL,
		NULL, NULL,
		0.0f, EDV_DEF_PROGRESS_BAR_TICKS, TRUE
	    );
	    if(ProgressDialogStopCount() > 0)
		return(-4);
	}    

	/* Create the new FIFO pipe */
	if(mknod(
	    (const char *)tar_path,
#ifdef S_IFFIFO
	    (src_lstat_buf != NULL) ?
		src_lstat_buf->st_mode : (S_IFFIFO | S_IRUSR | S_IWUSR),
#else
	    (src_lstat_buf != NULL) ?
		src_lstat_buf->st_mode : (S_IFIFO | S_IRUSR | S_IWUSR),
#endif
	    0
	))
	{
	    const gint error_code = (gint)errno;
	    gchar *s = g_strconcat(
		"Unable to copy the FIFO pipe, ",
		g_strerror(error_code),
		NULL
	    );
	    EDVObjectOPCopyErrorMessage(s);
	    g_free(s);
	    return(-1);
	}

	/* Report progress */
	if(ProgressDialogIsQuery())
	{
	    ProgressDialogUpdate(
		NULL, NULL,
		NULL, NULL,
		0.5f, EDV_DEF_PROGRESS_BAR_TICKS, TRUE
	    );
	    if(ProgressDialogStopCount() > 0)
		return(-4);
	}    

	/* Restore the original permissions */
	if(src_lstat_buf != NULL)
	{
	    chmod((const char *)tar_path, src_lstat_buf->st_mode);
	}

	/* Report the final progress */
	if(ProgressDialogIsQuery())
	{
	    ProgressDialogUpdate(
		NULL, NULL,
		NULL, NULL,
		1.0f, EDV_DEF_PROGRESS_BAR_TICKS, TRUE
	    );
	    if(ProgressDialogStopCount() > 0)
		return(-4);
	}    

	return(0);
#else
	last_error = "Unsupported object type";
	return(-2);
#endif
}

/*
 *	Coppies the block device.
 *
 *	The src_path specifies the full path of the block device to be
 *	coppied. The src_path must be a block device.
 *
 *	The tar_path specifies the full path to where to copy to,
 *	there must not be an object already existing at this location.
 *
 *	If an error occures then last_error will be set to the error
 *	message that describes what error occured.
 *
 *	Returns 0 on success or non-zero on error.
 */
static gint EDVObjectOPCopyDeviceBlock(
	const gchar *src_path, const gchar *tar_path,
	const struct stat *src_lstat_buf,
	const struct stat *src_stat_buf
)
{
#if defined(S_IFBLK)
	/* Report the initial progress */
	if(ProgressDialogIsQuery())
	{
	    ProgressDialogUpdate(
		NULL, NULL,
		NULL, NULL,
		0.0f, EDV_DEF_PROGRESS_BAR_TICKS, TRUE
	    );
	    if(ProgressDialogStopCount() > 0)
		return(-4);
	}    

	/* Create the new block device */
	if(mknod(
	    (const char *)tar_path,
	    (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
	))
	{
	    const gint error_code = (gint)errno;
	    gchar *s = g_strconcat(
		"Unable to copy the block device, ",
		g_strerror(error_code),
		NULL
	    );
	    EDVObjectOPCopyErrorMessage(s);
	    g_free(s);
	    return(-1);
	}

	/* Report progress */
	if(ProgressDialogIsQuery())
	{
	    ProgressDialogUpdate(
		NULL, NULL,
		NULL, NULL,
		0.5f, EDV_DEF_PROGRESS_BAR_TICKS, TRUE
	    );
	    if(ProgressDialogStopCount() > 0)
		return(-4);
	}    

	/* Restore the original permissions */
	if(src_lstat_buf != NULL)
	{
	    chmod((const char *)tar_path, src_lstat_buf->st_mode);
	}

	/* Report the final progress */
	if(ProgressDialogIsQuery())
	{
	    ProgressDialogUpdate(
		NULL, NULL,
		NULL, NULL,
		1.0f, EDV_DEF_PROGRESS_BAR_TICKS, TRUE
	    );
	    if(ProgressDialogStopCount() > 0)
		return(-4);
	}    

	return(0);
#else
	last_error = "Unsupported object type";
	return(-2);
#endif
}

/*
 *	Coppies the character device.
 *
 *	The src_path specifies the full path of the character device
 *	to be coppied. The src_path must be a character device.
 *
 *	The tar_path specifies the full path to where to copy to,
 *	there must not be an object already existing at this location.
 *
 *	If an error occures then last_error will be set to the error
 *	message that describes what error occured.
 *
 *	Returns 0 on success or non-zero on error.
 */
static gint EDVObjectOPCopyDeviceCharacter(
	const gchar *src_path, const gchar *tar_path,
	const struct stat *src_lstat_buf,
	const struct stat *src_stat_buf
)
{
#if defined(S_IFCHR)
	/* Report the initial progress */
	if(ProgressDialogIsQuery())
	{
	    ProgressDialogUpdate(
		NULL, NULL,
		NULL, NULL,
		0.0f, EDV_DEF_PROGRESS_BAR_TICKS, TRUE
	    );
	    if(ProgressDialogStopCount() > 0)
		return(-4);
	}

	/* Create the new character device */
	if(mknod(
	    (const char *)tar_path,
	    (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
	))
	{
	    const gint error_code = (gint)errno;
	    gchar *s = g_strconcat(
		"Unable to copy the character device, ",
		g_strerror(error_code),
		NULL
	    );
	    EDVObjectOPCopyErrorMessage(s);
	    g_free(s);
	    return(-1);
	}

	/* Report progress */
	if(ProgressDialogIsQuery())
	{
	    ProgressDialogUpdate(
		NULL, NULL,
		NULL, NULL,
		0.5f, EDV_DEF_PROGRESS_BAR_TICKS, TRUE
	    );
	    if(ProgressDialogStopCount() > 0)
		return(-4);
	}

	/* Restore the original permissions */
	if(src_lstat_buf != NULL)
	{
	    chmod((const char *)tar_path, src_lstat_buf->st_mode);
	}

	/* Report the final progress */
	if(ProgressDialogIsQuery())
	{
	    ProgressDialogUpdate(
		NULL, NULL,
		NULL, NULL,
		1.0f, EDV_DEF_PROGRESS_BAR_TICKS, TRUE
	    );
	    if(ProgressDialogStopCount() > 0)
		return(-4);
	}

	return(0);
#else
	last_error = "Unsupported object type";
	return(-2);
#endif
}

/*
 *	Coppies the socket.
 *
 *	The src_path specifies the full path of the socket to be
 *	coppied. The src_path must be a socket.
 *
 *	The tar_path specifies the full path to where to copy to,
 *	there must not be an object already existing at this location.
 *
 *	If an error occures then last_error will be set to the error
 *	message that describes what error occured.
 *
 *	Returns 0 on success or non-zero on error.
 */
static gint EDVObjectOPCopySocket(
	const gchar *src_path, const gchar *tar_path,
	const struct stat *src_lstat_buf,
	const struct stat *src_stat_buf
)
{
#if defined(S_IFSOCK)
	/* Report the initial progress */
	if(ProgressDialogIsQuery())
	{
	    ProgressDialogUpdate(
		NULL, NULL,
		NULL, NULL,
		0.0f, EDV_DEF_PROGRESS_BAR_TICKS, TRUE
	    );
	    if(ProgressDialogStopCount() > 0)
		return(-4);
	}

	/* Create the new socket */
	if(mknod(
	    (const char *)tar_path,
	    (src_lstat_buf != NULL) ?
		src_lstat_buf->st_mode : (S_IFSOCK | S_IRUSR | S_IWUSR),
	    0
	))
	{
	    const gint error_code = (gint)errno;
	    gchar *s = g_strconcat(
		"Unable to copy the socket, ",
		g_strerror(error_code),
		NULL
	    );
	    EDVObjectOPCopyErrorMessage(s);
	    g_free(s);
	    return(-1);
	}

	/* Report progress */
	if(ProgressDialogIsQuery())
	{
	    ProgressDialogUpdate(
		NULL, NULL,
		NULL, NULL,
		0.5f, EDV_DEF_PROGRESS_BAR_TICKS, TRUE
	    );
	    if(ProgressDialogStopCount() > 0)
		return(-4);
	}

	/* Restore the original permissions */
	if(src_lstat_buf != NULL)
	{
	    chmod((const char *)tar_path, src_lstat_buf->st_mode);
	}

	/* Report the final progress */
	if(ProgressDialogIsQuery())
	{
	    ProgressDialogUpdate(
		NULL, NULL,
		NULL, NULL,
		1.0f, EDV_DEF_PROGRESS_BAR_TICKS, TRUE
	    );
	    if(ProgressDialogStopCount() > 0)
		return(-4);
	}

	return(0);
#else
	last_error = "Unsupported object type";
	return(-2);
#endif
}


/*
 *	Determines the object type and coppies it accordingly (the
 *	object may not be a directory, though).
 *
 *	The src_path specifies the full path to the object to copy.
 *
 *	The tar_path specifies the full path to where to copy to,
 *	there must not be an object already existing at this location.
 *
 *	If an error occures then last_error will be set to the error
 *	message that describes what error occured.
 *
 *	Returns 0 on success or non-zero on error.
 */
static gint EDVObjectOPCopyNexus(
	const gchar *src_path, const gchar *tar_path,
	const struct stat *src_lstat_buf,
	const struct stat *src_stat_buf,
	const gboolean archive
)
{
	mode_t m;
	const struct stat *stat_buf_ptr;

	if(STRISEMPTY(src_path) || STRISEMPTY(tar_path))
	{
	    last_error = "Path not specified";
	    return(-2);
	}

	/* 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 =
"Source object statistics not available";
	    return(-1);
	}

	/* Copy the source object by its type
	 *
	 * The source object may not be a directory
	 */
	m = stat_buf_ptr->st_mode;
#ifdef S_ISREG
	if(S_ISREG(m))
#else
	if(TRUE)
#endif
	{
	    return(EDVObjectOPCopyRegular(
		src_path, tar_path, src_lstat_buf, src_stat_buf
	    ));
	}
#ifdef S_ISLNK
	else if(S_ISLNK(m))
	{
	    return(EDVObjectOPCopyLink(
		src_path, tar_path, src_lstat_buf, src_stat_buf
	    ));
	}
#endif
#ifdef S_ISFIFO
	else if(S_ISFIFO(m))
	{
	    return(EDVObjectOPCopyFIFO(
		src_path, tar_path, src_lstat_buf, src_stat_buf
	    ));
	}
#endif
#ifdef S_ISBLK
	else if(S_ISBLK(m))
	{
	    return(EDVObjectOPCopyDeviceBlock(
		src_path, tar_path, src_lstat_buf, src_stat_buf
	    ));
	}
#endif
#ifdef S_ISCHR
	else if(S_ISCHR(m))
	{
	    return(EDVObjectOPCopyDeviceCharacter(
		src_path, tar_path, src_lstat_buf, src_stat_buf
	    ));
	}
#endif
#ifdef S_ISSOCK
	else if(S_ISSOCK(m))
	{
	    return(EDVObjectOPCopySocket(
		src_path, tar_path, src_lstat_buf, src_stat_buf
	    ));
	}
#endif
/* Add support for other objects here (except directories) */
	else
	{
	    /* Unsupported object, ignore */
	    last_error = "Unsupported object type";
	    return(-2);
	}
}


/*
 *	Moves the object.
 *
 *	The src_path specifies the full path of the object to be moved.
 *
 *	The tar_path specifies the full path of where the object is
 *	to be moved to. If it refers to an existing object then the
 *	user will be prompted to overwrite it. If if refers to an
 *	existing directory or a link to a directory then -2 will
 *	be returned and *need_copy will be set 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.
 *
 *	If an error occures then last_error will be set to the error
 *	message that describes what error occured.
 *
 *	Returns 0 on success or non-zero on error. If the operation
 *	requires a copy instead of a move then -2 will be returned
 *	and *need_copy will be set to TRUE.
 */
static gint EDVObjectOPMoveObjectsLocal(
	edv_core_struct *core,
	const gchar *src_path, const gchar *tar_path,
	const struct stat *src_lstat_buf, const struct stat *src_stat_buf,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	const gboolean archive,
	gint *status, gboolean *yes_to_all,
	gboolean *need_copy
)
{
	struct stat tar_lstat_buf;
	const struct stat *src_stat_ptr;
	gboolean tar_path_exists_locally;

	if((core == NULL) || STRISEMPTY(src_path) ||
	   STRISEMPTY(tar_path) || (status == NULL) ||
	   (yes_to_all == NULL) || (need_copy == NULL)
	)
	{
	    if(status != NULL)
		*status = -2;
	    return(-2);
	}

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

	/* Reset need_copy */
	*need_copy = FALSE;

	/* Check if the source and target object paths are identical */
	if(!strcmp((const char *)src_path, (const char *)tar_path))
	{
	    last_error = "The source object and the target object are the same";
	    *status = -2;
	    return(*status);
	}

	/* Get the pointer to the source object's statistics */
	src_stat_ptr = archive ? src_lstat_buf : src_stat_buf;
	if(src_stat_ptr == NULL)
	{
	    last_error = "The source object's statistics was not specified";
	    *status = -2;
	    return(*status);
	}

	/* Map the Progress Dialog? */
	if(show_progress)
	{
	    gchar *p1 = EDVShortenPath(
		src_path, EDV_DEF_PROGRESS_BAR_PATH_DISPLAY_MAX
	    );
	    gchar *p2 = EDVShortenPath(
		tar_path, 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
	    );
	    EDVObjectOPMapProgressDialogMove(buf, toplevel, FALSE);

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

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


	/* Get the target object's local statistics */
	if(lstat((const char *)tar_path, &tar_lstat_buf))
	{
	    /* Unable to get the target object's local statistics,
	     * check why
	     */
	    const gint error_code = (gint)errno;
	    switch(error_code)
	    {
#ifdef ENOENT
	      /* Does not exist */
	      case ENOENT:
		tar_path_exists_locally = FALSE;
		break;
#endif
#ifdef ENOTDIR
	      /* Does not exist */
	      case ENOTDIR:
		tar_path_exists_locally = FALSE;
		break;
#endif
#ifdef EACCES
	      /* Access denied */
	      case EACCES:
		if(last_error == NULL)
		{
		    gchar *s = g_strconcat(
			"Access denied to the target location \"",
			g_basename(tar_path),
			"\"",
			NULL
		    );
		    EDVObjectOPCopyErrorMessage(s);
		    g_free(s);
		}
		*status = -1;
		return(*status);
		break;
#endif
#ifdef EPERM
	      /* Permission denied */
	      case EPERM:
		if(last_error == NULL)
		{
		    gchar *s = g_strconcat(
			"Permission denied to the target location \"",
			g_basename(tar_path),
			"\"",
			NULL
		    );
		    EDVObjectOPCopyErrorMessage(s);
		    g_free(s);
		}
		*status = -1;
		return(*status);
		break;
#endif
	      /* All else assume that it might exist */
	      default:
		tar_path_exists_locally = TRUE;
		break;
	    }
	}
	else
	{
	    /* The target object exists locally */
	    struct stat tar_stat_buf;
	    gint result;

	    tar_path_exists_locally = TRUE;

	    /* Get the target object's destination statistics */
	    result = (gint)stat((const char *)tar_path, &tar_stat_buf);
	    if(result == 0)
	    {
		/* Check if source and target objects are the same */
		if(EDVObjectOPAreObjectsSame(src_stat_ptr, &tar_stat_buf))
		{
		    last_error = "The source object and the target object are the same";
		    *status = -2;
		    return(*status);
		}
	    }
	    /* Check if the source and target objects are the same locally */
	    if(EDVObjectOPAreObjectsSame(src_lstat_buf, &tar_lstat_buf))
	    {
		last_error = "The source object and the target object are the same";
		*status = -2;
		return(*status);
	    }

	    /* Is the target object's destination a directory? */
#ifdef S_ISDIR
	    if((result == 0) ? S_ISDIR(tar_stat_buf.st_mode) : FALSE)
#else
	    if(FALSE)
#endif
	    {
		/* 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
		 */
#ifdef S_ISDIR
		if(S_ISDIR(src_stat_ptr->st_mode))
#else
		if(FALSE)
#endif
		{
		    *need_copy = TRUE;
		    *status = -2;
		}
		else
		{
		    last_error =
"The target object already exists and is a directory";
		    *status = -2;
		}
		return(*status);
	    }

	    /* It exists but is not a directory, confirm overwrite? */
	    if(interactive && !(*yes_to_all))
	    {
		const gint response = EDVObjectOPConfirmOverwrite(
		    core, toplevel,
		    src_path, tar_path,
		    src_lstat_buf, &tar_lstat_buf
		);
		switch(response)
		{
		  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;
		}
	    }
	}

	/* If the target object exists locally then it must be
	 * deleted before we can move the source there
	 */
	if(tar_path_exists_locally)
	{
	    const gint result = EDVObjectOPUnlink(tar_path);
	    if(result != 0)
	    {
		*status = result;
		return(*status);
	    }
	}

	/* Move the source object to the target object */
	if(rename((const char *)src_path, (const char *)tar_path))
	{
	    const gint error_code = (gint)errno;
#define SET_LAST_ERROR(_error_code_)	{	\
 if(last_error == NULL) {			\
  gchar *s = g_strconcat(			\
   "Unable to move the object, ",		\
   g_strerror(_error_code_),			\
   NULL						\
  );						\
  EDVObjectOPCopyErrorMessage(s);		\
  g_free(s);					\
 }						\
}
	    switch(error_code)
	    {
#ifdef EISDIR
	      /* The target is a directory but the source is not */
	      case EISDIR:
		SET_LAST_ERROR(error_code);
		*status = -2;
		break;
#endif
#ifdef EXDEV
	      /* The source and the target locations are on different devices */
	      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;
		last_error = NULL;
		*status = -2;
		break;
#endif
#if defined(ENOTEMPTY)
	      /* The target location is not an empty directory */
	      case ENOTEMPTY:
		SET_LAST_ERROR(error_code);
		*status = -1;
		break;
#elif defined(EEXIST)
	      /* The target location is not an empty directory */
	      case EEXIST:
		SET_LAST_ERROR(error_code);
		*status = -1;
		break;
#endif
#ifdef EBUSY
	      /* The object is currently in use */
	      case EBUSY:
		SET_LAST_ERROR(error_code);
		*status = -1;
		break;
#endif
#ifdef EINVAL
	      /* Invalid target location */
	      case EINVAL:
		SET_LAST_ERROR(error_code);
		*status = -2;
		break;
#endif
#ifdef EMLINK
	      /* The source object already has the maximum number of links to it */
	      case EMLINK:
		SET_LAST_ERROR(error_code);
		*status = -2;
		break;
#endif
#ifdef ENOTDIR
	      /* A compoent of the path is not, in fact, a directory or
	       * the source object is a directory and the target object
	       * is not
	       */
	      case ENOTDIR:
		SET_LAST_ERROR(error_code);
		*status = -1;
		break;
#endif
#ifdef EFAULT
	      /* Segmentation fault */
	      case EFAULT:
		SET_LAST_ERROR(error_code);
		*status = -3;
		break;
#endif
#ifdef EACCES
	      /* Access denied */
	      case EACCES:
		SET_LAST_ERROR(error_code);
		*status = -1;
		break;
#endif
#ifdef EPERM
	      /* Permission denied */
	      case EPERM:
		SET_LAST_ERROR(error_code);
		*status = -1;
		break;
#endif
#ifdef ENAMETOOLONG
	      /* Name too long */
	      case ENAMETOOLONG:
		SET_LAST_ERROR(error_code);
		*status = -2;
		break;
#endif
#ifdef ENOENT
	      /* The source object or a compoent of the target object
	       * does not exist
	       */
	      case ENOENT:
		SET_LAST_ERROR(error_code);
		*status = -1;
		break;
#endif
#ifdef ENOMEM
	      /* Out of memory */
	      case ENOMEM:
		SET_LAST_ERROR(error_code);
		*status = -3;
		break;
#endif
#ifdef EROFS
	      /* The source object or the target object is on a read-only filesystem */
	      case EROFS:
		SET_LAST_ERROR(error_code);
		*status = -1;
		break;
#endif
#ifdef ELOOP
	      /* Too many symbolic links encountered */
	      case ELOOP:
		SET_LAST_ERROR(error_code);
		*status = -3;
		break;
#endif
#ifdef ENOSPC
	      /* Out of disk space */
	      case ENOSPC:
		SET_LAST_ERROR(error_code);
		*status = -3;
		break;
#endif
	      default:
		SET_LAST_ERROR(error_code);
		*status = -1;
		break;
	    }
#undef SET_LAST_ERROR
	}

	return(*status);
}

/*
 *	Coppies the object.
 *
 *	The src_path specifies the full path to the object to be coppied.
 *	It must not locally be a directory.
 *
 *	The tar_path specifies the full path to where the object is to
 *	be coppied to. If this location already exists and interactive
 *	is TRUE then tue user will be prompted to overwrite the
 *	existing object.
 *
 *	If the tar_path already exists locally as a directory then -2
 *	will be returned.
 *
 *	If an error occures then last_error will be set to the error
 *	message that describes what error occured.
 */
static void EDVObjectOPCopyObjectsLocal(
	edv_core_struct *core,
	const gchar *src_path, const gchar *tar_path,
	const struct stat *src_lstat_buf, const struct stat *src_stat_buf,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	const gboolean archive, gint *status, gboolean *yes_to_all,
	const gboolean originally_move
)
{
	struct stat tar_lstat_buf;
	const struct stat *src_stat_ptr;
	gboolean tar_path_exists_locally;
	gint result;

	if((core == NULL) || STRISEMPTY(src_path) || STRISEMPTY(tar_path) ||
	   (status == NULL) || (yes_to_all == NULL)
	)
	{
	    if(status != NULL)
		*status = -1;
	    return;
	}

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

	/* Check if paths are identical */
	if(!strcmp((const char *)src_path, (const char *)tar_path))
	{
	    last_error = "The source object and the target object are the same";
	    *status = -2;
	    return;
	}

	/* Get the pointer to source object's statistics */
	src_stat_ptr = archive ? src_lstat_buf : src_stat_buf;
	if(src_stat_ptr == NULL)
	{
	    last_error = "The source object's statistics was not specified";
	    *status = -2;
	    return;
	}

	/* Update the progress dialog? */
	if(show_progress)
	{
	    gchar *p1 = EDVShortenPath(
		src_path, EDV_DEF_PROGRESS_BAR_PATH_DISPLAY_MAX
	    );
	    gchar *p2 = EDVShortenPath(
		tar_path, 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)
		EDVObjectOPMapProgressDialogMove(buf, toplevel, FALSE);
	    else
		EDVObjectOPMapProgressDialogCopy(buf, toplevel, FALSE);

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

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

	/* Get the target object's local statistics */
	if(lstat((const char *)tar_path, &tar_lstat_buf))
	{
	    /* Unable to get the target object's local statistics,
	     * check why
	     */
	    const gint error_code = (gint)errno;
	    switch(error_code)
	    {
#ifdef ENOENT
	      /* Does not exist */
	      case ENOENT:
		tar_path_exists_locally = FALSE;
		break;
#endif
#ifdef ENOTDIR
	      /* Does not exist */
	      case ENOTDIR:
		tar_path_exists_locally = FALSE;
		break;
#endif
#ifdef EACCES
	      /* Access denied */
	      case EACCES:
		if(last_error == NULL)
		{
		    gchar *s = g_strconcat(
			"Access denied to the target location \"",
			g_basename(tar_path),
			"\"",
			NULL
		    );
		    EDVObjectOPCopyErrorMessage(s);
		    g_free(s);
		}
		*status = -1;
		return;
		break;
#endif
#ifdef EPERM
	      /* Permission denied */
	      case EPERM:
		if(last_error == NULL)
		{
		    gchar *s = g_strconcat(
			"Permission denied to the target location \"",
			g_basename(tar_path),
			"\"",
			NULL
		    );
		    EDVObjectOPCopyErrorMessage(s);
		    g_free(s);
		}
		*status = -1;
		return;
		break;
#endif
	      /* All else assume that it might exist */
	      default:
		tar_path_exists_locally = TRUE;
		break;
	    }
	}
	else
	{
	    /* The target object exists locally */
	    struct stat tar_stat_buf;
	    gint result;

	    tar_path_exists_locally = TRUE;

	    /* Get the target object's destination statistics */
	    result = (gint)stat((const char *)tar_path, &tar_stat_buf);
	    if(result == 0)
	    {
		/* Check if source and target object destinations are
		 * the same
		 */
		if(EDVObjectOPAreObjectsSame(src_stat_ptr, &tar_stat_buf))
		{
		    last_error = "The source object and the target object are the same";
		    *status = -2;
		    return;
		}
	    }
	    /* Check if source and target objects are the same locally */
	    if(EDVObjectOPAreObjectsSame(src_lstat_buf, &tar_lstat_buf))
	    {
		last_error = "The source object and the target object are the same";
		*status = -2;
		return;
	    }

	    /* Is target object locally a directory? */
#ifdef S_ISDIR
	    if(S_ISDIR(tar_lstat_buf.st_mode))
#else
	    if(FALSE)
#endif
	    {
		last_error = "The target object already exists and is a directory";
		*status = -2;
		return;
	    }
	    /* Target object exists and is not a directory, ask user for
	     * confirmation on overwriting it?
	     */
	    if(interactive && !(*yes_to_all))
	    {
		const gint response = EDVObjectOPConfirmOverwrite(
		    core, toplevel,
		    src_path, tar_path,
		    src_lstat_buf, &tar_lstat_buf
		);
		switch(response)
		{
		  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 the user abort error code, this
		     * will cause the calling function to detect an
		     * error and stop any looping operations
		     */
		    *status = -4;
		    return;
		    break;

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

	/* If the target object exists locally then it must be
	 * deleted before we can move the source there
	 */
	if(tar_path_exists_locally)
	{
	    const gint result = EDVObjectOPUnlink(tar_path);
	    if(result != 0)
	    {
		*status = result;
		return;
	    }
	}

	/* Copy the source object to the target object
	 *
	 * This will check the source object's type and copy it
	 * accordingly
	 *
	 * The tar_path must not exist
	 */
	result = EDVObjectOPCopyNexus(
	    src_path, tar_path,
	    src_lstat_buf, src_stat_buf,
	    archive
	);
	if(result != 0)
	{
	    *status = result;
	}
	else
	{
	    /* If originally a move then restore the original ownership
	     * and time stamps
	     */
	    if(originally_move)
	    {
		lchown(
		    (const char *)tar_path,
		    src_stat_ptr->st_uid,
		    src_stat_ptr->st_gid
		);

		/* Restore the time stamps only if the object was not
		 * a link
		 */
		if(
#ifdef S_ISLNK
			!S_ISLNK(src_stat_ptr->st_mode)
#else
			TRUE
#endif
	        )
		{
		    struct utimbuf utime_buf;
		    utime_buf.actime = src_stat_ptr->st_atime;
		    utime_buf.modtime = src_stat_ptr->st_mtime;
		    utime((const char *)tar_path, &utime_buf);
		}
	    }
	}
}


/*
 *	Coppies the directory.
 *
 *	The src_dir specifies the full path to the directory to copy.
 *	All contents within it will be coppied.
 *
 *	The tar_dir specifies the full path to where the directory is
 *	to be coppied to.
 *
 *	If an error occures then last_error will be set to the error
 *	message that describes what error occured.
 */
static void EDVObjectOPCopyDirectoryIterate(
	edv_core_struct *core,
	const gchar *src_dir, const gchar *tar_dir,
	const struct stat *src_lstat_buf, const struct stat *src_stat_buf,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	const gboolean archive, gint *status,
	gboolean *yes_to_all,
	const gboolean originally_move
)
{
	gint result, strc;
	gchar **strv;
	struct stat tar_stat_buf;

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

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

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

	/* Must have the stats of the source object's destination */
	if(src_stat_buf == NULL)
	{
	    last_error =
"The source directory's statistics was not specified";
	    *status = -2;
	    return;
	}

	/* Map the Progress Dialog? */
	if(show_progress)
	{
	    gchar *p1 = EDVShortenPath(
		src_dir, EDV_DEF_PROGRESS_BAR_PATH_DISPLAY_MAX
	    );
	    gchar *p2 = EDVShortenPath(
		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)
		EDVObjectOPMapProgressDialogMove(buf, toplevel, FALSE);
	    else
		EDVObjectOPMapProgressDialogCopy(buf, toplevel, FALSE);

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

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


	/* Get the stats of the target object's destination if possible
	 * and check if the source and target destinations are the
	 * same
	 */
	if(!stat((const char *)tar_dir, &tar_stat_buf))
	{
	    /* Check if source and target objects are the same */
	    if(EDVObjectOPAreObjectsSame(src_stat_buf, &tar_stat_buf))
	    {
		last_error = "The source directory and the target directory are the same";
		*status = -2;
		return;
	    }
	}

	/* The specified source destination is a directory so we do not
	 * need to check if the source and target are the same locally
	 */


	/* Create the target directory as needed */
	result = (gint)mkdir(
	    (const char *)tar_dir,
#if defined(S_IRUSR) && defined(S_IWUSR) && defined(S_IXUSR)
	    S_IRUSR | S_IWUSR | S_IXUSR |
#endif
#if defined(S_IRGRP) && defined(S_IWGRP) && defined(S_IXGRP)
	    S_IRGRP | S_IWGRP | S_IXGRP |
#endif
#if defined(S_IROTH) && defined(S_IWOTH) && defined(S_IXOTH)
	    S_IROTH | S_IWOTH | S_IXOTH |
#endif
	    0
	);
	if(result != 0)
	{
	    const gint error_code = (gint)errno;
	    gchar *s = g_strconcat(
		"Unable to create the directory \"",
		g_basename(tar_dir),
		"\", ",
		g_strerror(error_code),
		NULL
	    );
	    EDVObjectOPCopyErrorMessage(s);
	    g_free(s);
	    switch(error_code)
	    {
	      case EEXIST:
		/* The target object exists, check if it is a
		 * directory or a link who's destination is a valid
		 * directory
 		 */
		if(ISPATHDIR(tar_dir))
		{
		    /* The target object is an existing directory or
		     * a link who's destination is a valid directory,
		     * so do not indicate any error and continue
		     * through
		     */
		    last_error = NULL;
		}
		else
		{
		    last_error =
"The target object exists and is not a directory";
		    *status = -1;
		    return;
		}
		break;
	      case EFAULT:
		*status = -3;
		return;
		break;
	      case EACCES:
		*status = -2;
		return;
		break;
	      case ENAMETOOLONG:
		*status = -2;
		return;
		break;
	      case ENOENT:
	      case ENOTDIR:
		*status = -2;
		return;
		break;
	      case ENOMEM:
		*status = -3;
		return;
		break;
	      case EROFS:
		*status = -2;
		return;
		break;
	      case ELOOP:
		*status = -3;
		return;
		break;
	      case ENOSPC:
		*status = -3;
		return;
	      default:
		*status = -1;
		return;
		break;
	    }
	}
	else
	{
	    /* A new target directory was created, set its permissions
	     * to match the permissions of the original directory
	     */
	    chmod(
		(const char *)tar_dir,
		src_stat_buf->st_mode
	    );
	}

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


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

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

	    /* Iterate through each object in the source directory  */
	    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;			\
 s = 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 != 0) && (*status != -5))
		    FREE_AND_CONTINUE

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

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

		/* Get local and destination stats for source child,
		 * fail only if the local stats cannot be obtained
		 */
		if(lstat((const char *)src_child, &lstat_buf))
		    FREE_AND_CONTINUE

		src_child_stat_result = (gint)stat(
		    (const char *)src_child, &stat_buf
		);
		if(src_child_stat_result)
		    memset(&stat_buf, 0x00, sizeof(struct stat));

		/* Check if the source object is locally a directory
		 * or if the destination is a directory and archive
		 * is FALSE, if so then we will recurse into this
		 * directory
		 */
#ifdef S_ISDIR
		if(S_ISDIR(lstat_buf.st_mode) ||
		   (S_ISDIR(stat_buf.st_mode) && !archive)
		)
#else
		if(FALSE)
#endif
		{
		    /* Copy this directory and all of its contents
		     * to the target directory
		     */
		    EDVObjectOPCopyDirectoryIterate(
			core, src_child, tar_child,
			&lstat_buf,
			(src_child_stat_result == 0) ? &stat_buf : NULL,
			toplevel,
			show_progress, interactive, archive,
			status, yes_to_all, originally_move
		    );
		}
		else
		{
		    /* Copy this source object to the target
		     * directory
		     */
		    EDVObjectOPCopyObjectsLocal(
			core, src_child, tar_child,
			&lstat_buf,
			(src_child_stat_result == 0) ? &stat_buf : NULL,
			toplevel,
			show_progress, interactive, archive,
			status, yes_to_all, originally_move
		    );
		}

		FREE_AND_CONTINUE
#undef FREE_AND_CONTINUE
	    }

	    g_free(strv);
	}

	/* Restore the ownership and time stamps on the target directory
	 * if the original operation was a move
	 */
	if(originally_move)
	{
	    struct utimbuf utime_buf;

	    chown(
		(const char *)tar_dir,
		src_stat_buf->st_uid,
		src_stat_buf->st_gid
	    );

	    utime_buf.actime = src_stat_buf->st_atime;
	    utime_buf.modtime = src_stat_buf->st_mtime;
	    utime((const char *)tar_dir, &utime_buf);
	}
}


/*
 *	Coppies or moves the object.
 *
 *	If is_copy is TRUE then the operation will be a copy, otherwise
 *	the operation will be a move unless src_path and tar_path are on
 *	different devices in which case the operation will become a
 *	copy but with messages stating that it is a move and src_path
 *	will be removed upon success.
 *
 *	The src_path specifies the full path to the object to be coppied
 *	or moved.
 *
 *	The tar_path specifies the full path to where the object is to
 *	be coppied or moved to.
 *
 *	If new_path_rtn is not NULL then a dynamically allocated string
 *	describing the full path of the new object will be returned.
 *
 *	If the src_path is a directory then that directory and of its
 *	contents will be recursivly copied or moved.
 *
 *	If new_path_rtn is not NULL then it will be set with a
 *	dynamically allocated string describing the newly added
 *	object's path.
 *
 *	If show_progress is TRUE then the progress dialog will be
 *	mapped as needed and updated. The calling function needs to 
 *	unmap the progress dialog.
 *
 *	If interactive is TRUE then the user will be prompted for
 *	overwrites as needed.
 *	
 *	Returns 0 on success and non-zero on error.
 */
static gint EDVObjectOPCopyMove(
	const gboolean is_copy,
	edv_core_struct *core,
	const gchar *src_path, const gchar *tar_path,
	gchar **new_path_rtn,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all
)
{
	static gboolean reenterent = FALSE;
	gulong		time_start = (gulong)time(NULL);
	gboolean	archive = TRUE,
			need_copy = is_copy,
			originally_move = !is_copy;
	gchar *lsrc_path, *ltar_path;
	gint		status = 0,
			src_lstat_result,
			src_stat_result;
	struct stat	src_lstat_buf,
			src_stat_buf,
			tar_stat_buf;

	if(new_path_rtn != NULL)
	    *new_path_rtn = NULL;

	/* Leave the value of yes_to_all as is */

	if(reenterent)
	{
	    last_error =
"An operation is already in progress, please try again later";
	    return(-6);
	}
	else
	{
	    reenterent = TRUE;
	}

	last_error = NULL;

	if((core == NULL) || STRISEMPTY(src_path) ||
	   STRISEMPTY(tar_path) || (yes_to_all == NULL)
	)
	{
	    last_error = "Invalid value";
	    reenterent = FALSE;
	    return(-2);
	}

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

	/* Copy the source and target paths */
	lsrc_path = STRDUP(src_path);
	ltar_path = STRDUP(tar_path);
	if((lsrc_path == NULL) || (ltar_path == NULL))
	{
	    last_error = "Memory allocation error";
	    DO_FREE_LOCALS
	    reenterent = FALSE;
	    return(-3);
	}

	/* Simplify the source and target paths */
	EDVSimplifyPath(lsrc_path);
	EDVSimplifyPath(ltar_path);


	/* Make sure that the 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_path, (const char *)ltar_path))
	{
	    last_error = "The source object and the target object are the same";
	    DO_FREE_LOCALS
	    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_path, ltar_path))
	{
	    if(need_copy)
		last_error = "Unable to copy a parent into its child";
	    else
		last_error = "Unable to move a parent into its child";
	    DO_FREE_LOCALS
	    reenterent = FALSE;
	    return(-2);
	}


	/* Get source object statistics, both local and destination */
	src_lstat_result = (gint)lstat((const char *)lsrc_path, &src_lstat_buf);
	src_stat_result = (gint)stat((const char *)lsrc_path, &src_stat_buf);
	if(archive ? (src_lstat_result != 0) : (src_stat_result != 0))
	{
	    /* Unable to get statistics for the source object */
	    last_error = "Unable to get the source object's statistics";
	    DO_FREE_LOCALS
	    reenterent = FALSE;
	    return(-2);
	}

	/* Got the source object statistics, now perform the copy/move
	 * by the source object's type
	 *
	 * Check if the source object is a directory
	 */
#ifdef S_ISDIR
	if(archive ?
	    S_ISDIR(src_lstat_buf.st_mode) : S_ISDIR(src_stat_buf.st_mode)
	)
#else
	if(FALSE)
#endif
	{
	    /* The source object is a directory, set the target child
	     * object's path by prefixing the target object's path with
	     * the source object's name
	     */
	    gchar *tar_child_obj = STRDUP(PrefixPaths(
		(const char *)ltar_path,
		(const char *)g_basename(lsrc_path)
	    ));
	    gboolean need_remove_src = FALSE;

	    /* Begin copying or moving the source directory and all
	     * of its contents to the location specified by
	     * tar_child_obj
	     *
	     * Is this a move?
	     */
	    if(!need_copy)
	    {
		EDVObjectOPMoveObjectsLocal(
		    core, lsrc_path, tar_child_obj,
		    (src_lstat_result == 0) ? &src_lstat_buf : NULL,
		    (src_stat_result == 0) ? &src_stat_buf : NULL,
		    toplevel,
		    show_progress, interactive, archive,
		    &status, yes_to_all,
		    &need_copy
		);

		/* If the above operation changed from a move to a
		 * copy then the source object needs to be removed
		 */
		if(need_copy)
		    need_remove_src = TRUE;
	    }

	    /* Is a copy requested or needed?
	     *
	     * The need_copy can either be specified as TRUE or set
	     * to TRUE when EDVObjectOPMoveObjectsLocal() was called
	     * to indicate that the source object needs to be coppied
	     * instead of moved
	     */
	    if(need_copy)
	    {
		/* Reset status incase this was originally a move and
		 * a copy needs to be performed instead
		 */
		if(status != 0)
		    status = 0;
		EDVObjectOPCopyDirectoryIterate(
		    core, lsrc_path, tar_child_obj,
		    (src_lstat_result == 0) ? &src_lstat_buf : NULL,
		    (src_stat_result == 0) ? &src_stat_buf : NULL,
		    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_src && (status == 0))
		EDVObjectOPDeleteDirectory(lsrc_path);

	    /* Set the new object return */
	    if(new_path_rtn != NULL)
	    {
		g_free(*new_path_rtn);
		*new_path_rtn = tar_child_obj;
	    }
	    else
	    {
		g_free(tar_child_obj);
	    }
	    tar_child_obj = NULL;
	}
	else
	{
	    /* The source object is not a directory, set the target
	     * child object's path as the target object's path, change
	     * it later if the target object is a directory
	     */
	    gchar *tar_child_obj = STRDUP(ltar_path);
	    gboolean need_remove_src = FALSE;

	    /* When the source object is not a directory then the target
	     * object's existance 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((const char *)ltar_path, &tar_stat_buf))
	    {
#ifdef S_ISDIR
		if(S_ISDIR(tar_stat_buf.st_mode))
#else
		if(FALSE)
#endif
		{
		    /* Get the target child object's path as the target
		     * object's path prefixed to the source object's
		     * name
		     */
		    g_free(tar_child_obj);
		    tar_child_obj = STRDUP(PrefixPaths(
			(const char *)ltar_path,
			(const char *)g_basename(lsrc_path)
		    ));
		    if(tar_child_obj == NULL)
			tar_child_obj = STRDUP(ltar_path);
		}
	    }

	    /* Begin copying or moving the source object to the
	     * location specified by tar_child_obj
	     *
	     * Is this a move?
	     */
	    if(!need_copy)
	    {
		EDVObjectOPMoveObjectsLocal(
		    core, lsrc_path, 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,
		    &need_copy
		);

		/* If the above operation changed from a move to a
		 * copy then the source object needs to be removed
		 */
		if(need_copy)
		    need_remove_src = TRUE;
	    }

	    /* Is a copy requested or needed?
	     *
	     * The need_copy can either be specified as TRUE or set
	     * to TRUE when EDVObjectOPMoveObjectsLocal() was called
	     * to indicate that the source object needs to be coppied
	     * instead of moved
	     */
	    if(need_copy)
	    {
		/* Reset status incase this was originally a move and a
		 * copy needs to be performed instead
		 */
		if(status != 0)
		    status = 0;
		EDVObjectOPCopyObjectsLocal(
		    core, lsrc_path, 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_src && (status == 0))
		EDVObjectOPUnlink(lsrc_path);

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

	/* Record history */
	EDVAppendHistory(
	    core,
	    originally_move ?
		EDV_HISTORY_DISK_OBJECT_MOVE : EDV_HISTORY_DISK_OBJECT_COPY,
	    time_start, (gulong)time(NULL),
	    status,
	    lsrc_path,		/* Source */
	    (new_path_rtn != NULL) ? *new_path_rtn : ltar_path,
	    last_error		/* Comment */
	);

	DO_FREE_LOCALS

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


/*
 *	Coppies the object.
 */
gint EDVObjectOPCopy(
	edv_core_struct *core,
	const gchar *src_path, const gchar *tar_path,
	gchar **new_path_rtn,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all
)
{
	return(EDVObjectOPCopyMove(
	    TRUE,			/* Is copy */
	    core, src_path, tar_path,
	    new_path_rtn, toplevel,
	    show_progress, interactive,
	    yes_to_all
	));
}

/*
 *	Moves the object.
 */
gint EDVObjectOPMove(
	edv_core_struct *core,
	const gchar *src_path, const gchar *tar_path,
	gchar **new_path_rtn,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all
)
{
	return(EDVObjectOPCopyMove(
	    FALSE,			/* Is move */
	    core, src_path, tar_path,
	    new_path_rtn, toplevel,
	    show_progress, interactive,
	    yes_to_all
	));
}


/*
 *	Generates a new link name.
 *
 *	The path specifies the full path to the link or a directory
 *	where the link is to be created in.
 *
 *	The dest_name specifies the name of the destination object
 *	(without the path).
 *
 *	Returns a dynamically allocated string describing the absolute
 *	path to a new link. If path refers to a non-existant object
 *	then a copy of path is returned, otherwise if path refers to
 *	an existing object then it must be a directory in which case
 *	a copy of path postfixed with dest_name and possibily a number
 *	will be returned.
 *
 *	If the path refers to a non-existant directory then the
 *	returned string is just a copy of the path.
 */
static gchar *EDVObjectOPGenerateNewLinkPath(
	const gchar *path, const gchar *dest_name
)
{
	gint counter = 0, counter_max = 10000;
	gchar	*full_path,
		*parent = NULL,
		*name = NULL,
		*new_path = NULL;
	struct stat lstat_buf;

	if(STRISEMPTY(path) || STRISEMPTY(dest_name))
	    return(new_path);

	/* If the path does not exist locally then just return a
	 * copy of it
	 */
	if(lstat((const char *)path, &lstat_buf))
	{
	    const gint error_code = (gint)errno;
	    if(error_code == ENOENT)
		return(STRDUP(path));
	    else
		return(NULL);
	}

	/* Does the path refer to a directory? */
#ifdef S_ISDIR
	if(S_ISDIR(lstat_buf.st_mode))
#else
	if(FALSE)
#endif
	{   
	    /* The path is a directory */
	    parent = STRDUP(path);
	    name = STRDUP(dest_name);
	}
	else
	{
	    /* Not a directory, so always return NULL */
	    g_free(parent);
	    g_free(name);
	    return(NULL);
	}
	if((parent == NULL) || (name == NULL))
	{
	    g_free(parent);
	    g_free(name);
	    return(NULL);
	}

	/* The copy of the target path is now a directory, now find
	 * an available non-existant link name in that directory
	 * derived from the copy of the source object's name
	 */
	for(counter = 1; counter < counter_max; counter++)
	{
	    full_path = STRDUP(PrefixPaths(parent, 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)
	    {
		gchar num_str[40];
		g_snprintf(
		    num_str, sizeof(num_str),
		    "%u", counter
		);
		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))
	    {
		/* Object exists but unable to obtain stats? */
		const gint error_code = (gint)errno;
		if(error_code != ENOENT)
		{
		    g_free(full_path);
		    continue;
		}
	    }
	    else
	    {
		/* This new new link refers to an existing object */
		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(parent);
	g_free(name);

	return(new_path);
}

/*
 *      Generates a relative path from the link's location (parent
 *	directory) to its destination.
 *
 *	The path specifies the full path to the link.
 *
 *	The dest_path specifies the full path to the link's
 *	destination.
 *
 *	Returns a dynamically allocated string describing the relative
 *	path from the link's location (parent directory) to its
 *	destination.
 */
static gchar *EDVObjectOPGenerateLinkDestination(
	const gchar *path, const gchar *dest_path
)
{
	const gchar *src_cstrptr, *tar_cstrptr, *tar_cstrptr2;
	gint i, tar_deliminators = 0;
	gchar	*new_path = NULL,
		*lpath;

	if(STRISEMPTY(path) || STRISEMPTY(dest_path))
	    return(new_path);

	/* Get the link's parent directory as lpath */
	lpath = g_dirname(path);
	if(lpath == NULL)
	    lpath = STRDUP(path);

	/* If the directories are the same then just return "." for
	 * current directory
	 */
	if(!strcmp((const char *)dest_path, (const char *)lpath))
	{
	    new_path = STRDUP(".");
	    g_free(lpath);
	    return(new_path);
	}

	/* Set the starting path positions past the toplevel
	 * character
	 */
	src_cstrptr = (gchar *)strchr((char *)dest_path, G_DIR_SEPARATOR);
	if(src_cstrptr != NULL)
	    src_cstrptr++;
	else
	    src_cstrptr = dest_path + 1;

	tar_cstrptr = (gchar *)strchr((char *)lpath, G_DIR_SEPARATOR);
	if(tar_cstrptr != NULL)
	    tar_cstrptr++;
	else
	    tar_cstrptr = lpath + 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 > dest_path)
	{
	    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 <= dest_path)
	    src_cstrptr = dest_path + 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 *)lpath, "/"))
	    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(lpath);
	    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(lpath);
		return(new_path);
	    }
	}

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

	g_free(lpath);

	return(new_path);
}

/*
 *	Creates a link.
 *
 *	The path specifies the full path to the link or a directory
 *	where the link is to be created in. If path refers to an
 *	existing object then it must be a directory and the new link
 *	will be created there, otherwise if path refers to a
 *	non-existing object then it will be treated as the full path
 *	to the link that is to be created.
 *
 *	The dest_path specifies the full path to the object that is
 *	to be the link's destination.
 *
 *	If new_path_rtn is not NULL then a dynamically allocated string
 *	describing the full path of the new link will be returned.
 *
 *	Returns 0 on success and non-zero on error.
 */
gint EDVObjectOPLink(
	edv_core_struct *core,
	const gchar *path,
	const gchar *dest_path,
	gchar **new_path_rtn,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all
)
{
	static gboolean reenterent = FALSE;
	const gulong time_start = (gulong)time(NULL);
	gboolean archive = TRUE;
	struct stat dest_lstat_buf, dest_stat_buf;
	gint status, dest_lstat_result, dest_stat_result;
	gchar *lpath, *ldest_path;

	if(new_path_rtn != NULL)
	    *new_path_rtn = NULL;

	/* Leave the value of yes_to_all as is */

	if(reenterent)
	{
	    last_error =
"An operation is already in progress, please try again later";
	    return(-6);
	}
	else
	{
	    reenterent = TRUE;
	}

	last_error = NULL;

	if((core == NULL) || STRISEMPTY(path) ||
	   STRISEMPTY(dest_path) || (yes_to_all == NULL)
	)
	{
	    last_error = "Invalid value";
	    reenterent = FALSE;
	    return(-1);
	}

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

	/* Copy the path and destination path */
	lpath = STRDUP(path);
	ldest_path = STRDUP(dest_path);
	if((lpath == NULL) || (ldest_path == NULL))
	{
	    last_error = "Memory allocation error";
	    DO_FREE_LOCALS
	    reenterent = FALSE;
	    return(-3);
	}

	EDVSimplifyPath(lpath);
	EDVSimplifyPath(ldest_path);

	/* Get the statistics of the destination */
	dest_lstat_result = lstat((const char *)ldest_path, &dest_lstat_buf);
	dest_stat_result = stat((const char *)ldest_path, &dest_stat_buf);
	if(archive ? dest_lstat_result : dest_stat_result)
	{
	    const gint error_code = (gint)errno;
	    gchar *s = g_strconcat(
		"Unable to link to \"",
		g_basename(ldest_path),
		"\", ",
		g_strerror(error_code),
		NULL
	    );
	    EDVObjectOPCopyErrorMessage(s);
	    g_free(s);
	    DO_FREE_LOCALS
	    reenterent = FALSE;
	    return(-1);
	}

	status = 0;

	if(TRUE)
	{
	    gint result;
	    const gchar *dest_name = g_basename(ldest_path);
	    gchar *child_path, *dest_value;

	    /* Generate the full path for a new link */
	    child_path = EDVObjectOPGenerateNewLinkPath(
		lpath, dest_name
	    );
	    if(child_path == NULL)
	    {
		status = -1;
		last_error = "Unable to generate a new link name";
		DO_FREE_LOCALS
		reenterent = FALSE;
		return(status);
	    }

	    /* Generate the destination value for the link */
	    dest_value = EDVObjectOPGenerateLinkDestination(
		child_path, ldest_path
	    );
	    if(dest_value == NULL)
	    {
		status = -1;
		last_error = "Unable to generate the link's destination value";
		g_free(child_path);
		DO_FREE_LOCALS
		reenterent = FALSE;
		return(status);
	    }

	    /* Create link */
	    result = (gint)symlink(
		(const char *)dest_value,	/* Destination Value */
		(const char *)child_path	/* Link */
	    );
	    if(result)
	    {
		const gint error_code = (gint)errno;
		gchar *s = g_strconcat(
		    "Unable to create the link \"",
		    g_basename(child_path),
		    "\", ",
		    g_strerror(error_code),
		    NULL
		);
		EDVObjectOPCopyErrorMessage(s);
		g_free(s);
	    }

	    /* Delete the link's destination value */
	    g_free(dest_value);
	    dest_value = NULL;

	    /* Set the full path of the new link return */
	    if(new_path_rtn != NULL)
	    {
		g_free(*new_path_rtn);
		*new_path_rtn = STRDUP(child_path);
	    }
	}

	/* Record history */
	EDVAppendHistory(
	    core, EDV_HISTORY_DISK_OBJECT_LINK,
	    time_start, (gulong)time(NULL),
	    status,
	    (new_path_rtn != NULL) ? *new_path_rtn : ldest_path,
	    ldest_path,		/* Target */
	    last_error		/* Comment */
	);

	DO_FREE_LOCALS

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

/*
 *	Set the link's value.
 *
 *	The path specifies the full path to the link who's value is
 *	to be set.
 *
 *	The new_dest_value specifies the new link value.
 *
 *	Returns 0 on success and non-zero on error.
 */
gint EDVObjectOPRelink(
	edv_core_struct *core,
	const gchar *path,
	const gchar *new_dest_value,
	GList **modified_paths_list,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all
)
{
	static gboolean reenterent = FALSE;
	const gulong time_start = (gulong)time(NULL);
	gint status, lstat_result;
/*	gboolean archive = TRUE; */
	gchar *lpath;
	struct stat lstat_buf;

	/* Leave the value of yes_to_all as is */

	if(modified_paths_list != NULL)
	    *modified_paths_list = NULL;

	if(reenterent)
	{
	    last_error =
"An operation is already in progress, please try again later";
	    return(-6);
	}
	else
	{
	    reenterent = TRUE;
	}

	last_error = NULL;

	if((core == NULL) || STRISEMPTY(path) ||
	   STRISEMPTY(new_dest_value) || (yes_to_all == NULL)
	)
	{
	    last_error = "Invalid value";
	    reenterent = FALSE;
	    return(-1);
	}

#define DO_FREE_LOCALS	{	\
 g_free(lpath);			\
}


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

	EDVSimplifyPath(lpath);

	/* Get the link's stats */
	lstat_result = (gint)lstat(
	    (const char *)lpath, &lstat_buf
	);
	if(!lstat_result)
	{
	    /* Is it a link? */
#ifdef S_ISLNK
	    if(S_ISLNK(lstat_buf.st_mode))
#else
	    if(FALSE)
#endif
	    {
		/* Remove the existing link */
		if(EDVObjectOPUnlink(lpath))
		{
		    DO_FREE_LOCALS
		    reenterent = FALSE;
		    return(-1);
		}
	    }
	    else
	    {
		last_error = "The object to relink is not a link";
		DO_FREE_LOCALS
		reenterent = FALSE;
		return(-2);
	    }
	}

	status = 0;

	if(TRUE)
	{
	    /* Create the link */
	    if(symlink(
		(const char *)new_dest_value,	/* Destination Value */
		(const char *)lpath		/* Link */
	    ))
	    {
		/* Error creating link? */
		const gint error_code = (gint)errno;
		gchar *s = g_strconcat(
		    "Unable to relink, ",
		    g_strerror(error_code),
		    NULL
		);
		EDVObjectOPCopyErrorMessage(s);
		g_free(s);
		status = -1;
	    }
	    else
	    {
		/* Successfully created the new link */
		if(lstat_result == 0)
		{
		    /* Restore the original ownership */
		    lchown(
			(const char *)path,
			lstat_buf.st_uid, lstat_buf.st_gid
		    );
		}

		/* Add this object to the list of modified paths */
		if(modified_paths_list != NULL)
		    *modified_paths_list = g_list_append(
			*modified_paths_list, STRDUP(lpath)
		    );
	    }
	}

	/* Record history */
	EDVAppendHistory(
	    core, EDV_HISTORY_DISK_OBJECT_LINK,
	    time_start, (gulong)time(NULL),
	    status,
	    lpath,			/* Source */
	    new_dest_value,		/* Target */
	    last_error			/* Comment */
	);

	DO_FREE_LOCALS

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

/*
 *	Rename.
 *
 *	The path specifies the full path to the object to be renamed.
 *
 *	The new_name specifies the new name of the object (without
 *	path).
 *
 *	If new_path_rtn is not NULL then a dynamically allocated string
 *	describing the full path to the new object will be returned.
 *
 *	Returns 0 on success and non-zero on error.
 */
gint EDVObjectOPRename(
	edv_core_struct *core,
	const gchar *path,
	const gchar *new_name,
	GList **modified_paths_list,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all
)
{
	static gboolean reenterent = FALSE;
	const gulong time_start = (gulong)time(NULL);
	struct stat tar_lstat_buf;
	gint status;
	gchar *parent, *lpath = NULL, *ltar_path = NULL;

	if(modified_paths_list != NULL)
	    *modified_paths_list = NULL;

	if(reenterent)
	{
	    last_error =
"An operation is already in progress, please try again later";
	    return(-6);
	}
	else
	{
	    reenterent = TRUE;
	}

	last_error = NULL;

	if((core == NULL) || STRISEMPTY(path) || STRISEMPTY(new_name) ||
 	   (yes_to_all == NULL)
	)
	{
	    last_error = "Invalid value";
	    reenterent = FALSE;
	    return(-1);
	}

#define DO_FREE_LOCALS	{	\
 g_free(lpath);			\
 g_free(ltar_path);		\
}

	/* Check if the new name contains any invalid characters */
	if(!EDVIsObjectNameValid(new_name))
	{
	    last_error = "Invalid name";
	    DO_FREE_LOCALS
	    reenterent = FALSE;
	    return(-1);
	}

	/* Copy the object's path */
	lpath = STRDUP(path);
	if(lpath == NULL)
	{
	    last_error = "Memory allocation error";
	    DO_FREE_LOCALS
	    reenterent = FALSE;
	    return(-3);
	}
	EDVSimplifyPath(lpath);

	/* Get the object's parent */
	parent = g_dirname(lpath);
	if(parent == NULL)
	{
	    DO_FREE_LOCALS
	    last_error = "Unable to obtain the object's parent path";
	    reenterent = FALSE;
	    return(-3);
	}

	/* Format the full path to the new object */
	ltar_path = STRDUP(PrefixPaths(parent, new_name));

	g_free(parent);

	if(ltar_path == NULL)
	{
	    last_error = "Unable to format the full path to the new object";
	    DO_FREE_LOCALS
	    reenterent = FALSE;
	    return(-3);
	}

	EDVSimplifyPath(ltar_path);

	/* Are the names are the same? */
	if(!strcmp((const char *)lpath, (const char *)ltar_path))
	{
	    /* If the names are the same then do not rename and
	     * return success
	     *
	     * Add this object to the modified paths list
	     */
	    if(modified_paths_list != NULL)
		*modified_paths_list = g_list_append(
		    *modified_paths_list, STRDUP(ltar_path)
		);

	    DO_FREE_LOCALS
	    reenterent = FALSE;
	    return(0);
	}


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

	/* Rename the object */
	status = 0;
	if(rename((const char *)lpath, (const char *)ltar_path))
	{
	    /* Rename error */
	    const gint error_code = (gint)errno;
	    gchar *s = g_strconcat(
		"Unable to rename \"",
		g_basename(lpath),
		"\" to \"",
		g_basename(ltar_path),
		"\", ",
		g_strerror(error_code),
		NULL
	    );
	    EDVObjectOPCopyErrorMessage(s);
	    g_free(s);
	    status = -1;
	}
	else
	{
	    /* Renamed successfully
	     *
	     * Add this object to the modified paths list
	     */
	    if(modified_paths_list != NULL)
		*modified_paths_list = g_list_append(
		    *modified_paths_list, STRDUP(ltar_path)
		);
	}

	/* Record history */
	EDVAppendHistory(
	    core, EDV_HISTORY_DISK_OBJECT_MOVE,
	    time_start, (gulong)time(NULL),
	    status,
	    lpath,		/* Source */
	    ltar_path,		/* Target */
	    last_error		/* Comment */
	);

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


/*
 *	Change permissions.
 */
static gint EDVObjectOPChModIterate(
	edv_core_struct *core,
	const gchar *path,
	const mode_t m,
	GList **modified_paths_list,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all,
	const gboolean recursive, const gboolean archive,
	gint *nobjs_processed, const gint nobjs
)
{
	struct stat lstat_buf;
	gint status;

	if(path == NULL)
	{
	    last_error = "Invalid value";
	    return(-2);
	}

	/* Update the progress dialog message? */
	if(show_progress)
	{
	    const gfloat progress = (nobjs > 0) ?
		((gfloat)(*nobjs_processed) / (gfloat)nobjs) : 0.0f;
	    gchar	*p1 = EDVShortenPath(
		path, EDV_DEF_PROGRESS_BAR_PATH_DISPLAY_MAX
	    ),		*msg = g_strdup_printf(
"Changing permissions of:\n\
\n\
    %s\n",
		p1
	    );
	    g_free(p1);
	    if(ProgressDialogIsQuery())
	    {
		ProgressDialogUpdate(
		    NULL, msg, NULL, NULL,
		    progress, EDV_DEF_PROGRESS_BAR_TICKS, TRUE
		);
	    }
	    else
	    {
		ProgressDialogSetTransientFor(toplevel);
		ProgressDialogMap(
		    "Changing Permissions",
		    msg,
		    (const guint8 **)icon_chmod_32x32_xpm,
		    "Stop"
		);
		ProgressDialogUpdate(
		    NULL, NULL, NULL, NULL,
		    progress, EDV_DEF_PROGRESS_BAR_TICKS, TRUE
		);
		gdk_flush();
	    }
	    g_free(msg);

	    if(ProgressDialogStopCount() > 0)
		return(-4);
	}

	/* Set the permissions of this object
	 *
	 * Note that the archive value is ignored because there is
	 * no lchown since links themselves do not have permissions
	 */
	if(chmod((const char *)path, m))
	{
	    const gint error_code = (gint)errno;
	    gchar *s = g_strconcat(
		"Unable to change the permissions of \"",
		g_basename(path),
		"\", ",
		g_strerror(error_code),
		NULL
	    );
	    EDVObjectOPCopyErrorMessage(s);
	    g_free(s);
	    return(-1);
	}

	/* Get this object's local statistics */
	if(!lstat((const char *)path, &lstat_buf))
	{
	    /* Is this object a link? */
#ifdef S_ISLNK
	    if(S_ISLNK(lstat_buf.st_mode))
#else
	    if(FALSE)
#endif
	    {
		/* Report the link's destination as being modified */
		if(modified_paths_list != NULL)
                    *modified_paths_list = g_list_append(
                        *modified_paths_list,
			EDVGetLinkValueFull(path)
                    );
	    }
	    else
	    {
		/* Add this object to the modified paths list */
	 	if(modified_paths_list != NULL)
		    *modified_paths_list = g_list_append(
			*modified_paths_list, STRDUP(path)
		    );
	    }
	}

	/* Count this object as processed */
	*nobjs_processed = (*nobjs_processed) + 1;

	if(show_progress && ProgressDialogIsQuery())
	{
	    const gfloat progress = (nobjs > 0) ?
		((gfloat)(*nobjs_processed) / (gfloat)nobjs) : 0.0f;
	    ProgressDialogUpdate(
		NULL, NULL, NULL, NULL,
		progress, EDV_DEF_PROGRESS_BAR_TICKS, TRUE
	    );
	    if(ProgressDialogStopCount() > 0)
		return(-4);
	}

	status = 0;

	/* Recurse? */
	if(recursive)
	{
	    /* Is this object a directory? */
	    if(archive ? ISLPATHDIR(path) : ISPATHDIR(path))
	    {
		/* Set permissions of each object in the directory */
		gint i, strc;
		const gchar *name;
		gchar *full_path;
		gchar **strv = GetDirEntNames2(path, &strc);
		StringQSort(strv, strc);           
		for(i = 0; i < strc; i++)
		{
		    name = strv[i];
		    if(name == NULL)
			continue;

		    if(status != 0)
		    {
			g_free(strv[i]);
			continue;
		    }

		    if(!strcmp((const char *)name, ".") ||
		       !strcmp((const char *)name, "..")
		    )
		    {
			g_free(strv[i]);
			continue;
		    }

		    full_path = STRDUP(PrefixPaths(
			(const char *)path, (const char *)name
		    ));
		    status = EDVObjectOPChModIterate(
			core,
			full_path, m,
			modified_paths_list,
			toplevel,
			show_progress, interactive,
			yes_to_all,
			recursive, archive,
			nobjs_processed, nobjs
		    );
		    g_free(full_path);         
		    g_free(strv[i]);
		}
		g_free(strv);
	    }
	}    

	return(status);
}

/*
 *	Change permissions.
 *
 *	The path specifies the full path to the object to chmod.
 *
 *	The permissions specifies the permissions to set.
 *
 *	Returns 0 on success and non-zero on error.
 */
gint EDVObjectOPChMod(
	edv_core_struct *core,
	GList *paths_list,
	const edv_permission_flags permissions,
	GList **modified_paths_list,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all,
	const gboolean recursive, const gboolean archive
)
{
	static gboolean reenterent = FALSE;
	gulong time_start;
	gint status, nobjs_processed, nobjs;
	mode_t m = 0;
	gchar *permissions_str = NULL;
	GList *glist;

	if(modified_paths_list != NULL)
	    *modified_paths_list = NULL;

	if(reenterent)
	{
	    last_error =
"An operation is already in progress, please try again later";
	    return(-6);
	}
	else
	{
	    reenterent = TRUE;
	}

	last_error = NULL;

	if((core == NULL) || (paths_list == NULL) || (yes_to_all == NULL))
	{
	    last_error = "Invalid value";
	    reenterent = FALSE;
	    return(-1);
	}

#define DO_FREE_LOCALS	{	\
 g_free(permissions_str);	\
}

	/* Count the number of objects to be processed */
	nobjs_processed = 0;
	nobjs = 0;
	for(glist = paths_list; glist != NULL; glist = g_list_next(glist))
	    nobjs += EDVObjectOPCountObjects(
		(const gchar *)glist->data, recursive, archive
	    );

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

	/* Format the permissions string for logging */
	permissions_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' : '-')
	);

	/* Iterate through the paths list and change the permissions
	 * of each object
	 */
	status = 0;
	for(glist = paths_list; glist != NULL; glist = g_list_next(glist))
	{
	    time_start = (gulong)time(NULL);

	    /* Change the permissions of this object */
	    status = EDVObjectOPChModIterate(
		core,
		(const gchar *)glist->data,
		m,
		modified_paths_list,
		toplevel,
		show_progress, interactive,
		yes_to_all,
		recursive, archive,
		&nobjs_processed, nobjs
	    );

	    /* Record history */
	    EDVAppendHistory(
		core, EDV_HISTORY_DISK_OBJECT_CHMOD,
		time_start, (gulong)time(NULL),
		status,
		(const gchar *)paths_list->data,
		permissions_str,
		last_error
	    );

	    if(status != 0)
		break;
	}

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


/*
 *	Change ownership.
 */
static gint EDVObjectOPChOwnIterate(
	edv_core_struct *core,
	const gchar *path,
	const gint owner_id, const gint group_id,
	GList **modified_paths_list,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all,
	const gboolean recursive, const gboolean archive,
	gint *nobjs_processed, const gint nobjs
)
{
	gint status;

	if(path == NULL)
	{
	    last_error = "Invalid value";
	    return(-2);
	}

	/* Update the progress dialog message? */
	if(show_progress)
	{
	    const gfloat progress = (nobjs > 0) ?
		((gfloat)(*nobjs_processed) / (gfloat)nobjs) : 0.0f;
	    gchar	*p1 = EDVShortenPath(
		path, EDV_DEF_PROGRESS_BAR_PATH_DISPLAY_MAX
	    ),		*msg = g_strdup_printf(
"Changing ownership of:\n\
\n\
    %s\n",
		p1
	    );
	    g_free(p1);
	    if(ProgressDialogIsQuery())
	    {
		ProgressDialogUpdate(
		    NULL, msg, NULL, NULL,
		    progress, EDV_DEF_PROGRESS_BAR_TICKS, TRUE
		);
	    }
	    else
	    {
		ProgressDialogSetTransientFor(toplevel);
		ProgressDialogMap(
		    "Changing Ownership",
		    msg,
		    (const guint8 **)icon_owned_32x32_xpm,
		    "Stop"
		);
		ProgressDialogUpdate(
		    NULL, NULL, NULL, NULL,
		    progress, EDV_DEF_PROGRESS_BAR_TICKS, TRUE
		);
		gdk_flush();
	    }
	    g_free(msg);

	    if(ProgressDialogStopCount() > 0)
		return(-4);
	}

	/* Set the ownership of this object */
	if(archive)
	{
	    if(lchown(
		(const char *)path,
		(uid_t)owner_id, (gid_t)group_id
	    ))
	    {
		const gint error_code = (gint)errno;
		gchar *s = g_strconcat(
		    "Unable to change the ownership of \"",
		    g_basename(path),
		    "\", ",
		    g_strerror(error_code),
		    NULL
		);
		EDVObjectOPCopyErrorMessage(s);
		g_free(s);
		return(-1);
	    }
	}
	else
	{
	    if(chown(
		(const char *)path,
		(uid_t)owner_id, (gid_t)group_id
	    ))
	    {
		const gint error_code = (gint)errno;
		gchar *s = g_strconcat(
		    "Unable to change the ownership of \"",
		    g_basename(path),
		    "\", ",
		    g_strerror(error_code),
		    NULL
		);
		EDVObjectOPCopyErrorMessage(s);
		g_free(s);
		return(-1);
	    }
	}

	if(archive)
	{
	    /* Add this object to the modified paths list */
	    if(modified_paths_list != NULL)
		*modified_paths_list = g_list_append(
		    *modified_paths_list, STRDUP(path)
		);
	}
	else
	{
	    /* Get this object's local statistics */
	    struct stat lstat_buf;
	    if(!lstat((const char *)path, &lstat_buf))
	    {
		/* Is this object a link? */
#ifdef S_ISLNK
		if(S_ISLNK(lstat_buf.st_mode))
#else
		if(FALSE)
#endif
		{
		    /* Report the link's destination as being modified */
		    if(modified_paths_list != NULL)
			*modified_paths_list = g_list_append(
			    *modified_paths_list,
			    EDVGetLinkValueFull(path)
			);
		}
		else
		{
		    /* Add this object to the modified paths list */
		    if(modified_paths_list != NULL)
			*modified_paths_list = g_list_append(
			    *modified_paths_list, STRDUP(path)
			);
		}
	    }
	}

	/* Count this object as processed */
	*nobjs_processed = (*nobjs_processed) + 1;

	if(show_progress && ProgressDialogIsQuery())
	{
	    const gfloat progress = (nobjs > 0) ?
		((gfloat)(*nobjs_processed) / (gfloat)nobjs) : 0.0f;
	    ProgressDialogUpdate(
		NULL, NULL, NULL, NULL,
		progress, EDV_DEF_PROGRESS_BAR_TICKS, TRUE
	    );
	    if(ProgressDialogStopCount() > 0)
		return(-4);
	}

	status = 0;

	/* Recurse? */
	if(recursive)
	{
	    /* Is this object a directory? */
	    if(archive ? ISLPATHDIR(path) : ISPATHDIR(path))
	    {
		/* Set ownership of each object in the directory */
		gint i, strc;
		const gchar *name;
		gchar *full_path;
		gchar **strv = GetDirEntNames2(path, &strc);
		StringQSort(strv, strc);           
		for(i = 0; i < strc; i++)
		{
		    name = strv[i];
		    if(name == NULL)
			continue;

		    if(status != 0)
		    {
			g_free(strv[i]);
			continue;
		    }

		    if(!strcmp((const char *)name, ".") ||
		       !strcmp((const char *)name, "..")
		    )
		    {
			g_free(strv[i]);
			continue;
		    }

		    full_path = STRDUP(PrefixPaths(
			(const char *)path, (const char *)name
		    ));
		    status = EDVObjectOPChOwnIterate(
			core,
			full_path, owner_id, group_id,
			modified_paths_list,
			toplevel,
			show_progress, interactive,
			yes_to_all,
			recursive, archive,
			nobjs_processed, nobjs
		    );
		    g_free(full_path);         
		    g_free(strv[i]);
		}
		g_free(strv);
	    }
	}    

	return(status);
}

/*
 *	Change ownership.
 *
 *	The path specifies the full path to the object to chown.
 *
 *	The owner_id specifies the user id of the owner to chown to.
 *
 *	The group_id specifies the group id of the group to chown to.
 *
 *	Returns 0 on success and non-zero on error.
 */
gint EDVObjectOPChOwn(
	edv_core_struct *core,
	GList *paths_list,
	const gint owner_id, const gint group_id,
	GList **modified_paths_list,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all,
	const gboolean recursive, const gboolean archive
)
{
	static gboolean reenterent = FALSE;
	gulong time_start;
	gint status, nobjs_processed, nobjs;
	gchar *owner_group_str = NULL;
	GList *glist;

	if(modified_paths_list != NULL)
	    *modified_paths_list = NULL;

	if(reenterent)
	{
	    last_error =
"An operation is already in progress, please try again later";
	    return(-6);
	}
	else
	{
	    reenterent = TRUE;
	}

	last_error = NULL;

	if((core == NULL) || (paths_list == NULL) || (yes_to_all == NULL))
	{
	    last_error = "Invalid value";
	    reenterent = FALSE;
	    return(-1);
	}

#define DO_FREE_LOCALS	{	\
 g_free(owner_group_str);	\
}

	/* Count the number of objects to be processed */
	nobjs_processed = 0;
	nobjs = 0;
	for(glist = paths_list; glist != NULL; glist = g_list_next(glist))
	    nobjs += EDVObjectOPCountObjects(
		(const gchar *)glist->data, recursive, archive
	    );

	/* Format the ownership string for logging */
	owner_group_str = g_strdup_printf(
	    "%s/%s",
	    EDVUIDGetNameFromUID(
		core->uid, core->total_uids,
		owner_id, NULL
	    ),
	    EDVGIDGetNameFromGID(
		core->gid, core->total_gids,
		group_id, NULL
	    )
	);

	/* Iterate through the paths list and change the ownership of
	 * each object
	 */
	status = 0;
	for(glist = paths_list; glist != NULL; glist = g_list_next(glist))
	{
	    time_start = (gulong)time(NULL);

	    /* Change the ownership of this object */
	    status = EDVObjectOPChOwnIterate(
		core,
		(const gchar *)glist->data,
		owner_id, group_id,
		modified_paths_list,
		toplevel,
		show_progress, interactive,
		yes_to_all,
		recursive, archive,
		&nobjs_processed, nobjs
	    );

	    /* Record history */
	    EDVAppendHistory(
		core, EDV_HISTORY_DISK_OBJECT_CHOWN,
		time_start, (gulong)time(NULL),
		status,
		(const gchar *)paths_list->data,
		owner_group_str,
		last_error
	    );

	    if(status != 0)
		break;
	}

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


/*
 *	Change time stamps.
 */
static gint EDVObjectOPChTimeIterate(
	edv_core_struct *core,
	const gchar *path, struct utimbuf *ut_buf,
	GList **modified_paths_list,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all,
	const gboolean recursive, const gboolean archive,
	gint *nobjs_processed, const gint nobjs
)
{
	struct stat lstat_buf;
	gint status;

	if(path == NULL)
	{
	    last_error = "Invalid value";
	    return(-2);
	}

	/* Update the progress dialog message? */
	if(show_progress)
	{
	    const gfloat progress = (nobjs > 0) ?
		((gfloat)(*nobjs_processed) / (gfloat)nobjs) : 0.0f;
	    gchar	*p1 = EDVShortenPath(
		path, EDV_DEF_PROGRESS_BAR_PATH_DISPLAY_MAX
	    ),		*msg = g_strdup_printf(
"Changing time stamps of:\n\
\n\
    %s\n",
		p1
	    );
	    g_free(p1);
	    if(ProgressDialogIsQuery())
	    {
		ProgressDialogUpdate(
		    NULL, msg, NULL, NULL,
		    progress, EDV_DEF_PROGRESS_BAR_TICKS, TRUE
		);
	    }
	    else
	    {
		ProgressDialogSetTransientFor(toplevel);
		ProgressDialogMap(
		    "Changing Time Stamps",
		    msg,
		    (const guint8 **)icon_time_stamp_32x32_xpm,
		    "Stop"
		);
		ProgressDialogUpdate(
		    NULL, NULL, NULL, NULL,
		    progress, EDV_DEF_PROGRESS_BAR_TICKS, TRUE
		);
		gdk_flush();
	    }
	    g_free(msg);

	    if(ProgressDialogStopCount() > 0)
		return(-4);
	}

	/* Change the time stamps of this object
	 *
	 * Note that the archive value is ignored since there is
	 * no lutime() call
	 */
	if(utime((const char *)path, ut_buf))
	{
	    const gint error_code = (gint)errno;
	    gchar *s = g_strconcat(
		"Unable to change the time stamps of \"",
		g_basename(path),
		"\", ",
		g_strerror(error_code),
		NULL
	    );
	    EDVObjectOPCopyErrorMessage(s);
	    g_free(s);
	    return(-1);
	}

	/* Get this object's local statistics */
	if(!lstat((const char *)path, &lstat_buf))
	{
	    /* Is this object a link? */
#ifdef S_ISLNK
	    if(S_ISLNK(lstat_buf.st_mode))
#else
	    if(FALSE)
#endif
	    {
		/* Report the link's destination as being modified */
		if(modified_paths_list != NULL)
                    *modified_paths_list = g_list_append(
                        *modified_paths_list,
			EDVGetLinkValueFull(path)
                    );
	    }
	    else
	    {
		/* Add this object to the modified paths list */
	 	if(modified_paths_list != NULL)
		    *modified_paths_list = g_list_append(
			*modified_paths_list, STRDUP(path)
		    );
	    }
	}

	/* Count this object as processed */
	*nobjs_processed = (*nobjs_processed) + 1;

	if(show_progress && ProgressDialogIsQuery())
	{
	    const gfloat progress = (nobjs > 0) ?
		((gfloat)(*nobjs_processed) / (gfloat)nobjs) : 0.0f;
	    ProgressDialogUpdate(
		NULL, NULL, NULL, NULL,
		progress, EDV_DEF_PROGRESS_BAR_TICKS, TRUE
	    );
	    if(ProgressDialogStopCount() > 0)
		return(-4);
	}

	status = 0;

	/* Recurse? */
	if(recursive)
	{
	    /* Is this object a directory? */
	    if(archive ? ISLPATHDIR(path) : ISPATHDIR(path))
	    {
		/* Set time stamps of each object in the directory */
		gint i, strc;
		const gchar *name;
		gchar *full_path;
		gchar **strv = GetDirEntNames2(path, &strc);
		StringQSort(strv, strc);           
		for(i = 0; i < strc; i++)
		{
		    name = strv[i];
		    if(name == NULL)
			continue;

		    if(status != 0)
		    {
			g_free(strv[i]);
			continue;
		    }

		    if(!strcmp((const char *)name, ".") ||
		       !strcmp((const char *)name, "..")
		    )
		    {
			g_free(strv[i]);
			continue;
		    }

		    full_path = STRDUP(PrefixPaths(
			(const char *)path, (const char *)name
		    ));
		    status = EDVObjectOPChTimeIterate(
			core,
			full_path, ut_buf,
			modified_paths_list,
			toplevel,
			show_progress, interactive,
			yes_to_all,
			recursive, archive,
			nobjs_processed, nobjs
		    );
		    g_free(full_path);         
		    g_free(strv[i]);
		}
		g_free(strv);
	    }
	}    

	return(status);
}

/*
 *	Change time stamps.
 *
 *	The path specifies the full path to the object to set the time.
 *
 *	The atime specifies the access time stamp in seconds since
 *	EPOCH.
 *
 *	The mtime specifies the modify time stamp in seconds since
 *	EPOCH.
 *
 *      Returns 0 on success and non-zero on error.
 */
gint EDVObjectOPChTime(
	edv_core_struct *core,       
	GList *paths_list,
	const gulong atime, const gulong mtime,
	GList **modified_paths_list,
	GtkWidget *toplevel,
	const gboolean show_progress, const gboolean interactive,
	gboolean *yes_to_all,
	const gboolean recursive, const gboolean archive
)
{
	static gboolean reenterent = FALSE;
	struct utimbuf *ut_buf;
	gint status, nobjs_processed, nobjs;
	gulong time_start;
	const gchar *format;
	gchar *time_str = NULL;
	GList *glist;
	cfg_item_struct *cfg_list;
	edv_date_relativity relativity;

	if(modified_paths_list != NULL)
	    *modified_paths_list = NULL;

	if(reenterent)
	{
	    last_error =
"An operation is already in progress, please try again later";
	    return(-6);
	}
	else
	{
	    reenterent = TRUE;
	}

	last_error = NULL;

	if((core == NULL) || (paths_list == NULL) || (yes_to_all == NULL))
	{
	    last_error = "Invalid value";
	    reenterent = FALSE;
	    return(-1);
	}

	cfg_list = core->cfg_list;
	relativity = (edv_date_relativity)EDV_GET_I(
	    EDV_CFG_PARM_DATE_RELATIVITY
	);
	format = EDV_GET_S(EDV_CFG_PARM_DATE_FORMAT);

#define DO_FREE_LOCALS	{	\
 g_free(time_str);		\
}

	/* Count the number of objects to be processed */
	nobjs_processed = 0;
	nobjs = 0;
	for(glist = paths_list; glist != NULL; glist = g_list_next(glist))
	    nobjs += EDVObjectOPCountObjects(
		(const gchar *)glist->data, recursive, archive
	    );

	/* Format the time string */
	time_str = STRDUP(EDVDateFormatString(mtime, format, relativity));

	/* Allocate the time stamps buffer */
	ut_buf = (struct utimbuf *)g_malloc0(sizeof(struct utimbuf));
	if(ut_buf == NULL)
	{
	    last_error = "Memory allocation error";
	    DO_FREE_LOCALS
	    reenterent = FALSE;
	    return(-3);
	}

	/* Set the time stamps buffer */
	ut_buf->actime = atime;
	ut_buf->modtime = mtime;

	/* Iterate through the paths list and change the time stamps
	 * of each object
	 */
	status = 0;
	for(glist = paths_list; glist != NULL; glist = g_list_next(glist))
	{
	    time_start = (gulong)time(NULL);

	    /* Change the time stamps of this object */
	    status = EDVObjectOPChTimeIterate(
		core,
		(const gchar *)glist->data,
		ut_buf,
		modified_paths_list,
		toplevel, show_progress, interactive,
		yes_to_all,
		recursive, archive,
		&nobjs_processed, nobjs
	    );

	    /* Record history */
	    EDVAppendHistory(
		core, EDV_HISTORY_DISK_OBJECT_CHTIME,
		time_start, (gulong)time(NULL),
		status,
		(const gchar *)paths_list->data,
		time_str,
		last_error
	    );

	    if(status != 0)
		break;
	}

	/* Delete the time stamps buffer */
	g_free(ut_buf);

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