/* $Id: e2p_dircmp.c 755 2007-11-26 22:02:18Z tpgww $

Portions copyright (C) 2006-2007 tooar <tooar@gmx.net>

This file is part of emelFM2.
emelFM2 is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.

emelFM2 is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with emelFM2; see the file GPL. If not, contact the Free Software
Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/

/**
@file plugins/e2p_dircmp.c
@brief plugin for showing differences between the contents of active and inactive directories
*/

#include "emelfm2.h"
#include "e2p_dircmp.h"
#include "e2_plugins.h"
#include "e2_fileview.h"
#include "e2_filelist.h"
#include "e2_task.h"

typedef struct _E2_CmpData
{
#ifdef E2_VFS
	GError **operr;
	PlaceInfo *otherspace;
#endif
	gint oldroot_len;
	gchar *newroot;
} E2_CmpData;

/********* start of borrowed code *********/

/*
This code implements the MD5 message-digest algorithm, by Ron Rivest.
This code was written by Colin Plumb in 1993, our understanding is that no
copyright is claimed and that this code is in the public domain.

Equivalent code is available from RSA Data Security, Inc.
This code has been tested against that, and is functionally equivalent.

To compute the message digest of a chunk of bytes, declare an
MD5Context structure, pass it to MD5Init, call MD5Update as
needed on buffers full of bytes, and then call MD5Final, which
will fill a supplied 16-byte array with the digest.
*/

#ifndef HIGHFIRST
#define byteReverse(buf, len)	/* Nothing */
#else
void byteReverse(unsigned char *buf, unsigned longs);

#ifndef ASM_MD5
/*
Note: this code is harmless on little-endian machines.
*/
void byteReverse(unsigned char *buf, unsigned longs)
{
    u_int32_t t;
    do {
	t = (u_int32_t) ((unsigned) buf[3] << 8 | buf[2]) << 16 |
	    ((unsigned) buf[1] << 8 | buf[0]);
	*(u_int32_t *) buf = t;
	buf += 4;
    } while (--longs);
}
#endif
#endif

/*
Start MD5 accumulation.  Set bit count to 0 and buffer to mysterious
initialization constants.
*/
void MD5Init(struct MD5Context *ctx)
{
    ctx->buf[0] = 0x67452301;
    ctx->buf[1] = 0xefcdab89;
    ctx->buf[2] = 0x98badcfe;
    ctx->buf[3] = 0x10325476;

    ctx->bits[0] = 0;
    ctx->bits[1] = 0;
}

/*
Update context to reflect the concatenation of another buffer full
of bytes.
*/
void MD5Update(struct MD5Context *ctx, unsigned char const *buf, unsigned len)
{
    u_int32_t t;

    /* Update bitcount */

    t = ctx->bits[0];
    if ((ctx->bits[0] = t + ((u_int32_t) len << 3)) < t)
	ctx->bits[1]++;		/* Carry from low to high */
    ctx->bits[1] += len >> 29;

    t = (t >> 3) & 0x3f;	/* Bytes already in shsInfo->data */

    /* Handle any leading odd-sized chunks */

    if (t) {
	unsigned char *p = (unsigned char *) ctx->in + t;

	t = 64 - t;
	if (len < t) {
	    memcpy(p, buf, len);
	    return;
	}
	memcpy(p, buf, t);
	byteReverse(ctx->in, 16);
	MD5Transform(ctx->buf, (u_int32_t *) ctx->in);
	buf += t;
	len -= t;
    }
    /* Process data in 64-byte chunks */

    while (len >= 64) {
	memcpy(ctx->in, buf, 64);
	byteReverse(ctx->in, 16);
	MD5Transform(ctx->buf, (u_int32_t *) ctx->in);
	buf += 64;
	len -= 64;
    }

    /* Handle any remaining bytes of data. */

    memcpy(ctx->in, buf, len);
}

/*
Final wrapup - pad to 64-byte boundary with the bit pattern
1 0* (64-bit count of bits processed, MSB-first)
*/
void MD5Final(unsigned char digest[16], struct MD5Context *ctx)
{
    unsigned count;
    unsigned char *p;

    /* Compute number of bytes mod 64 */
    count = (ctx->bits[0] >> 3) & 0x3F;

    /* Set the first char of padding to 0x80.  This is safe since there is
       always at least one byte free */
    p = ctx->in + count;
    *p++ = 0x80;

    /* Bytes of padding needed to make 64 bytes */
    count = 64 - 1 - count;

    /* Pad out to 56 mod 64 */
    if (count < 8) {
	/* Two lots of padding:  Pad the first block to 64 bytes */
	memset(p, 0, count);
	byteReverse(ctx->in, 16);
	MD5Transform(ctx->buf, (u_int32_t *) ctx->in);

	/* Now fill the next block with 56 bytes */
	memset(ctx->in, 0, 56);
    } else {
	/* Pad block to 56 bytes */
	memset(p, 0, count - 8);
    }
    byteReverse(ctx->in, 14);

    /* Append length in bits and transform */
    ((u_int32_t *) ctx->in)[14] = ctx->bits[0];
    ((u_int32_t *) ctx->in)[15] = ctx->bits[1];

    MD5Transform(ctx->buf, (u_int32_t *) ctx->in);
    byteReverse((unsigned char *) ctx->buf, 4);
    memcpy(digest, ctx->buf, 16);

    memset(ctx, 0, sizeof(* ctx));	/* In case it's sensitive */
    /* The original version of this code omitted the asterisk. In
       effect, only the first part of ctx was wiped with zeros, not
       the whole thing. Bug found by Derek Jones. Original line: */
    // memset(ctx, 0, sizeof(ctx));	/* In case it's sensitive */
}

#ifndef ASM_MD5

/* The four core functions - F1 is optimized somewhat */

/* #define F1(x, y, z) (x & y | ~x & z) */
#define F1(x, y, z) (z ^ (x & (y ^ z)))
#define F2(x, y, z) F1(z, x, y)
#define F3(x, y, z) (x ^ y ^ z)
#define F4(x, y, z) (y ^ (x | ~z))

/* This is the central step in the MD5 algorithm. */
#ifdef __PUREC__
#define MD5STEP(f, w, x, y, z, data, s) \
	( w += f /*(x, y, z)*/ + data,  w = w<<s | w>>(32-s),  w += x )
#else
#define MD5STEP(f, w, x, y, z, data, s) \
	( w += f(x, y, z) + data,  w = w<<s | w>>(32-s),  w += x )
#endif

/*
The core of the MD5 algorithm, this alters an existing MD5 hash to
reflect the addition of 16 longwords of new data.  MD5Update blocks
the data and converts bytes into longwords for this routine.
*/
void MD5Transform(u_int32_t buf[4], u_int32_t const in[16])
{
    register u_int32_t a, b, c, d;

    a = buf[0];
    b = buf[1];
    c = buf[2];
    d = buf[3];

#ifdef __PUREC__	/* PureC Weirdness... (GG) */
    MD5STEP(F1(b,c,d), a, b, c, d, in[0] + 0xd76aa478L, 7);
    MD5STEP(F1(a,b,c), d, a, b, c, in[1] + 0xe8c7b756L, 12);
    MD5STEP(F1(d,a,b), c, d, a, b, in[2] + 0x242070dbL, 17);
    MD5STEP(F1(c,d,a), b, c, d, a, in[3] + 0xc1bdceeeL, 22);
    MD5STEP(F1(b,c,d), a, b, c, d, in[4] + 0xf57c0fafL, 7);
    MD5STEP(F1(a,b,c), d, a, b, c, in[5] + 0x4787c62aL, 12);
    MD5STEP(F1(d,a,b), c, d, a, b, in[6] + 0xa8304613L, 17);
    MD5STEP(F1(c,d,a), b, c, d, a, in[7] + 0xfd469501L, 22);
    MD5STEP(F1(b,c,d), a, b, c, d, in[8] + 0x698098d8L, 7);
    MD5STEP(F1(a,b,c), d, a, b, c, in[9] + 0x8b44f7afL, 12);
    MD5STEP(F1(d,a,b), c, d, a, b, in[10] + 0xffff5bb1L, 17);
    MD5STEP(F1(c,d,a), b, c, d, a, in[11] + 0x895cd7beL, 22);
    MD5STEP(F1(b,c,d), a, b, c, d, in[12] + 0x6b901122L, 7);
    MD5STEP(F1(a,b,c), d, a, b, c, in[13] + 0xfd987193L, 12);
    MD5STEP(F1(d,a,b), c, d, a, b, in[14] + 0xa679438eL, 17);
    MD5STEP(F1(c,d,a), b, c, d, a, in[15] + 0x49b40821L, 22);

    MD5STEP(F2(b,c,d), a, b, c, d, in[1] + 0xf61e2562L, 5);
    MD5STEP(F2(a,b,c), d, a, b, c, in[6] + 0xc040b340L, 9);
    MD5STEP(F2(d,a,b), c, d, a, b, in[11] + 0x265e5a51L, 14);
    MD5STEP(F2(c,d,a), b, c, d, a, in[0] + 0xe9b6c7aaL, 20);
    MD5STEP(F2(b,c,d), a, b, c, d, in[5] + 0xd62f105dL, 5);
    MD5STEP(F2(a,b,c), d, a, b, c, in[10] + 0x02441453L, 9);
    MD5STEP(F2(d,a,b), c, d, a, b, in[15] + 0xd8a1e681L, 14);
    MD5STEP(F2(c,d,a), b, c, d, a, in[4] + 0xe7d3fbc8L, 20);
    MD5STEP(F2(b,c,d), a, b, c, d, in[9] + 0x21e1cde6L, 5);
    MD5STEP(F2(a,b,c), d, a, b, c, in[14] + 0xc33707d6L, 9);
    MD5STEP(F2(d,a,b), c, d, a, b, in[3] + 0xf4d50d87L, 14);
    MD5STEP(F2(c,d,a), b, c, d, a, in[8] + 0x455a14edL, 20);
    MD5STEP(F2(b,c,d), a, b, c, d, in[13] + 0xa9e3e905L, 5);
    MD5STEP(F2(a,b,c), d, a, b, c, in[2] + 0xfcefa3f8L, 9);
    MD5STEP(F2(d,a,b), c, d, a, b, in[7] + 0x676f02d9L, 14);
    MD5STEP(F2(c,d,a), b, c, d, a, in[12] + 0x8d2a4c8aL, 20);

    MD5STEP(F3(b,c,d), a, b, c, d, in[5] + 0xfffa3942L, 4);
    MD5STEP(F3(a,b,c), d, a, b, c, in[8] + 0x8771f681L, 11);
    MD5STEP(F3(d,a,b), c, d, a, b, in[11] + 0x6d9d6122L, 16);
    MD5STEP(F3(c,d,a), b, c, d, a, in[14] + 0xfde5380cL, 23);
    MD5STEP(F3(b,c,d), a, b, c, d, in[1] + 0xa4beea44L, 4);
    MD5STEP(F3(a,b,c), d, a, b, c, in[4] + 0x4bdecfa9L, 11);
    MD5STEP(F3(d,a,b), c, d, a, b, in[7] + 0xf6bb4b60L, 16);
    MD5STEP(F3(c,d,a), b, c, d, a, in[10] + 0xbebfbc70L, 23);
    MD5STEP(F3(b,c,d), a, b, c, d, in[13] + 0x289b7ec6L, 4);
    MD5STEP(F3(a,b,c), d, a, b, c, in[0] + 0xeaa127faL, 11);
    MD5STEP(F3(d,a,b), c, d, a, b, in[3] + 0xd4ef3085L, 16);
    MD5STEP(F3(c,d,a), b, c, d, a, in[6] + 0x04881d05L, 23);
    MD5STEP(F3(b,c,d), a, b, c, d, in[9] + 0xd9d4d039L, 4);
    MD5STEP(F3(a,b,c), d, a, b, c, in[12] + 0xe6db99e5L, 11);
    MD5STEP(F3(d,a,b), c, d, a, b, in[15] + 0x1fa27cf8L, 16);
    MD5STEP(F3(c,d,a), b, c, d, a, in[2] + 0xc4ac5665L, 23);

    MD5STEP(F4(b,c,d), a, b, c, d, in[0] + 0xf4292244L, 6);
    MD5STEP(F4(a,b,c), d, a, b, c, in[7] + 0x432aff97L, 10);
    MD5STEP(F4(d,a,b), c, d, a, b, in[14] + 0xab9423a7L, 15);
    MD5STEP(F4(c,d,a), b, c, d, a, in[5] + 0xfc93a039L, 21);
    MD5STEP(F4(b,c,d), a, b, c, d, in[12] + 0x655b59c3L, 6);
    MD5STEP(F4(a,b,c), d, a, b, c, in[3] + 0x8f0ccc92L, 10);
    MD5STEP(F4(d,a,b), c, d, a, b, in[10] + 0xffeff47dL, 15);
    MD5STEP(F4(c,d,a), b, c, d, a, in[1] + 0x85845dd1L, 21);
    MD5STEP(F4(b,c,d), a, b, c, d, in[8] + 0x6fa87e4fL, 6);
    MD5STEP(F4(a,b,c), d, a, b, c, in[15] + 0xfe2ce6e0L, 10);
    MD5STEP(F4(d,a,b), c, d, a, b, in[6] + 0xa3014314L, 15);
    MD5STEP(F4(c,d,a), b, c, d, a, in[13] + 0x4e0811a1L, 21);
    MD5STEP(F4(b,c,d), a, b, c, d, in[4] + 0xf7537e82L, 6);
    MD5STEP(F4(a,b,c), d, a, b, c, in[11] + 0xbd3af235L, 10);
    MD5STEP(F4(d,a,b), c, d, a, b, in[2] + 0x2ad7d2bbL, 15);
    MD5STEP(F4(c,d,a), b, c, d, a, in[9] + 0xeb86d391L, 21);
#else
    MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7);
    MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12);
    MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17);
    MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22);
    MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7);
    MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12);
    MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17);
    MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22);
    MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7);
    MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12);
    MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
    MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);
    MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7);
    MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);
    MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);
    MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);

    MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5);
    MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9);
    MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);
    MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20);
    MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5);
    MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9);
    MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
    MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20);
    MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5);
    MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9);
    MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14);
    MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20);
    MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
    MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9);
    MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14);
    MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);

    MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4);
    MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11);
    MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
    MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);
    MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4);
    MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11);
    MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16);
    MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
    MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
    MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11);
    MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16);
    MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23);
    MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4);
    MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
    MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
    MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23);

    MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6);
    MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10);
    MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);
    MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21);
    MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6);
    MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10);
    MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);
    MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21);
    MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6);
    MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
    MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15);
    MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
    MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6);
    MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);
    MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15);
    MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21);
#endif

    buf[0] += a;
    buf[1] += b;
    buf[2] += c;
    buf[3] += d;
}

#endif

/*************** end of borrowed code ***************/

/**
@brief blockwise read and hash the regular file @a filepath
Uses file descriptors, which means files must be real, local
Any error message here expects BGL to be open
@param filepath localised string, absolute path of item to read
@return pointer to newly-allocated string containing the computed hash, or NULL on error
*/
static guchar *_e2p_diff_dohash (VPATH *filepath)
{
	HASH_CONTEXT md;
	guchar buffer [BUFSIZ];
	guchar sum [HASH_LENGTH+1]; //computed hash + trailing \0
	gint fdesc;
	E2_ERR_DECLARE

#if defined(__USE_GNU) && defined(O_NOATIME)
	gint flags = O_RDONLY;
	if (!e2_fs_access (filepath, W_OK E2_ERR_PTR()))
		flags |= O_NOATIME;
	else
	{
		//try to proceed without atime preservation
		E2_ERR_CLEAR
	}
	fdesc = e2_fs_safeopen (VPSTR (filepath), flags, 0);
#else	//be strict about open-flags
	fdesc = e2_fs_safeopen (filepath, O_RDONLY, 0);
#endif
	if (fdesc < 0)
	{
#ifdef E2_VFS
		e2_fs_set_error_from_errno (&E2_ERR_NAME);
#endif
		e2_fs_error_local (_("Cannot open '%s' for reading"), filepath E2_ERR_MSGL());
		E2_ERR_CLEAR
		return NULL;
	}

	guint64 bytes_read = 0;	//used for error recovery

	HASH_Init (&md);

	while (TRUE)
	{
		// clear buffer in case we need to pad the hash
		memset (buffer, 0, BUFSIZ);
		ssize_t n_read = e2_fs_read (fdesc, buffer, BUFSIZ E2_ERR_PTR());
		if (n_read == BUFSIZ)
		{
			HASH_Update (&md, buffer, BUFSIZ);
			bytes_read += n_read;
		}
		else if (n_read == 0)
			break;
		else if (n_read < 0)
		{
//			if (1)	//FIXME fatal error handling
			//these are the fatal errors used in md5deep
			if (   E2_ERR_IS (EACCES)	//permission denied (changed since opened ?)
				|| E2_ERR_IS (ENODEV)	//operation not supported (can't happen?)
				|| E2_ERR_IS (EBADF)	//bad file descriptor (can't happen?)
				|| E2_ERR_IS (EFBIG)	//file too big
				|| E2_ERR_IS (ETXTBSY)	//text file busy
				)
			{
				e2_fs_error_local (_("Error reading file %s"), filepath E2_ERR_MSGL());
				E2_ERR_CLEAR
				return NULL;
			}
			//non-fatal, we can continue
			E2_ERR_CLEAR
			HASH_Update(&md, buffer, BUFSIZ);
			bytes_read += BUFSIZ;
		 /* but the file pointer's now undefined. So manually advance it to the
			next read position */
			lseek (fdesc, bytes_read, SEEK_SET);
		}
		else if (n_read < BUFSIZ)
		{
		  //end of the file, but not a full buffer
		  //update the hash accordingly
			HASH_Update (&md, buffer, n_read);
			break;
		}
	}

	HASH_Final (sum, &md);

/* if we want to return result in human-readable form
	static gchar hex [] = "0123456789abcdef";
//	static
	gchar result [2 * HASH_LENGTH + 1];
	gint i;
	for (i = 0; i < HASH_LENGTH; ++i)
	{
		result[2 * i] = hex[(sum[i] >> 4) & 0xf];
		result[2 * i + 1] = hex[sum[i] & 0xf];
	}
	result [2 * HASH_LENGTH] = '\0';
	hash = strdup (result);
else */
	sum [HASH_LENGTH] = '\0';
	//casting silliness to prevent warnings ...
	guchar *hash = (guchar *) g_strdup ((gchar *) sum);

/* overkill
	if (hash == NULL)
	{
		gchar *utf = F_DISPLAYNAME_FROM_LOCALE (filename);
		gchar *msg = g_strdup_printf (
			_("Md5 calculation failed for %s - out of memory"), utf);
		gdk_threads_enter ();
		e2_output_print_error (msg, TRUE);
		gdk_threads_leave ();
		F_FREE (utf);
	} */

	TEMP_FAILURE_RETRY (close (fdesc));	//FIXME vfs

	return hash;
}
/**
@brief determine equality of non-dir items described by data in @a data
BGL is expected to be open/off
@param localpath absolute path of non-directory item to be compared, localised string
@param statptr pointer to info from lstat() for @a localpath
@param localotherpath absolute path of item to be compared, localised string

@return TRUE if the items are equal, (also FALSE after memory shortage)
*/
static gboolean _e2p_diff1 (VPATH *localpath, const struct stat *statptr,
	VPATH *localotherpath)
{
	gboolean retval;
	struct stat othersb;

	if (e2_fs_lstat (localotherpath, &othersb E2_ERR_NONE()))
		retval = FALSE;	//lstat() failed, assume this means we can't find a match
	else if ((statptr->st_mode & S_IFMT) != (othersb.st_mode & S_IFMT))
		retval = FALSE;	//unequal type = quick check
	else if (statptr->st_size != othersb.st_size)
		retval = FALSE;	//unequal size = quick check
	else if (S_ISREG (othersb.st_mode) && othersb.st_size > 0)
	{	//determine equality of items' md5 sums
		guchar *hash1 = _e2p_diff_dohash (localpath);
		if (hash1 == NULL)
			retval = FALSE;
		else
		{
			guchar *hash2 = _e2p_diff_dohash (localotherpath);
			if (hash2 == NULL)
			{
				g_free (hash1);
				retval = FALSE;
			}
			else
			{
				retval = ! strcmp ((gchar *) hash1, (gchar *) hash2);
				g_free (hash1);
				g_free (hash2);
			}
		}
	}
	else if (S_ISLNK (othersb.st_mode))
	{
		//first, check for equality of link targets
		gchar *target1, *target2;
#ifdef USE_GLIB2_10
		target1 = g_slice_alloc (PATH_MAX);
#else
		target1 = g_try_malloc (PATH_MAX);
#endif
		CHECKALLOCATEDWARNT (target1, );
		if (target1 != NULL)
		{
			gint len = e2_fs_readlink (localpath, target1, PATH_MAX E2_ERR_NONE());	//FIXME vfs
			if (len < 0) len = 0;	//hack to deal with error
			*(target1 + len) = '\0';
#ifdef USE_GLIB2_10
			target2 = g_slice_alloc (PATH_MAX);
#else
			target2 = g_try_malloc (PATH_MAX);
#endif
			CHECKALLOCATEDWARNT (target2, );
			if (target2 != NULL)
			{
				len = e2_fs_readlink (localotherpath, target2, PATH_MAX E2_ERR_NONE());	//FIXME vfs
				if (len < 0) len = 0;
				*(target2 + len) = '\0';
				retval = !strcmp (target1, target2);
/*
//the targets actually match ?
				if (!strcmp (target1, target2))
				{
					E2_ERR_DECLARE
					gchar *target1 = g_try_malloc (PATH_MAX);
					CHECKALLOCATEDWARNT (target1, return FALSE;)
					if (target1 != NULL)
					{
						g_strlcpy (target1, localpath, PATH_MAX);
						if (!e2_fs_walk_link (&target1 E2_ERR_ARG()))
						{
							//FIXME report error
							E2_ERR_CLEAR
							return FALSE;
						}
						gchar *target2 = g_try_malloc (PATH_MAX);
						CHECKALLOCATEDWARNT (target2, FIXMEg_free (target1); return FALSE;)
						if (target2 != NULL)
						{
							g_strlcpy (target2, localotherpath, PATH_MAX);
							if (!e2_fs_walk_link (&target2 E2_ERR_ARG()))
							{
								//FIXME report error
								E2_ERR_CLEAR
								g_free (target1);
								g_free (target2);
								return FALSE;
							}
							if (e2_fs_lstat (target1, &othersb E2_ERR_NONE()))
								retval = FALSE;
							else
							{
							CHECK FOR DIR
								retval = _e2p_diff1 (target1, &othersb, target2);
							}
							g_free (target2);
						}
						g_free (target1);
					}
				}
				else
					retval = FALSE;
*/
#ifdef USE_GLIB2_10
				g_slice_free1 (PATH_MAX, target2);
#else
				g_free (target2);
#endif
			}
			else
				retval = FALSE;
#ifdef USE_GLIB2_10
			g_slice_free1 (PATH_MAX, target1);
#else
			g_free (target1);
#endif
		}
		else
			retval = FALSE;
	}
	else
	//at this point, empty regular files, or other types (various types of special file)
	//must be equal
		retval = TRUE;

	return retval;
}
/**
@brief helper function for checking directories item-count
This is a callback for a treewalk function.
Each dir should bump @a count by 2 more than the no. of items (due to the
DP or DRR and DP reports).
@param local_name path of item reported by the walker, localised string
@param statbuf pointer to struct stat with data about @a local_name
@param status code from the walker, indicating what type of item is being reported
@param count pointer to store for items count
@return E2TW_CONTINUE always
*/
static E2_TwResult _e2p_diff_count_twcb (VPATH *local_name,
	const struct stat *statbuf, E2_TwStatus status, guint *count)
{
	(*count)++;
	return E2TW_CONTINUE;
}
/**
@brief determine equality of dir items described by data in @a data
@param data ptr to data struct for items to be compared
This is a callback for a treewalk function
Error message expects BGL to be open/off
@param localpath absolute path of item reported by the walker, localised string
@param statbuf pointer to struct stat with data about @a localpath
@param status code from the walker, indicating what type of report it is
@param twdata pointer to tw data struct

@return E2TW_CONTINUE when items match, E2TW_STOP when they don't or can't decide
*/
static E2_TwResult _e2p_diff_twcb (VPATH *localpath,
	const struct stat *statptr, E2_TwStatus status, E2_CmpData *twdata)
{
	gchar *localotherpath;
	E2_TwResult retval = E2TW_STOP;
#ifdef E2_VFS
	VPATH ddata;
#endif

	switch (status)
	{
		case E2TW_F:	//not directory or link
		case E2TW_SL:	//symbolic link
		case E2TW_SLN:	//symbolic link naming non-existing file
			localotherpath = e2_utils_strcat (twdata->newroot,
				(gchar *)localpath + twdata->oldroot_len);
#ifdef E2_VFS
			ddata.localpath = localotherpath;
			ddata.spacedata = twdata->otherspace;
			if (_e2p_diff1 (localpath, statptr, &ddata))
#else
			if (_e2p_diff1 (localpath, statptr, localotherpath))
#endif
				retval = E2TW_CONTINUE;
			g_free (localotherpath);
			break;
		case E2TW_D:	//directory
		case E2TW_DRR:	//directory now readable
		{
			struct stat othrstatbuf;
			localotherpath = e2_utils_strcat (twdata->newroot,
				(gchar *)localpath + twdata->oldroot_len);
#ifdef E2_VFS
			ddata.localpath = localotherpath;
			ddata.spacedata = twdata->otherspace;
			if (e2_fs_stat (&ddata, &othrstatbuf E2_ERR_NONE())
#else
			if (e2_fs_stat (localotherpath, &othrstatbuf E2_ERR_NONE())
#endif
				|| !S_ISDIR (othrstatbuf.st_mode)
				|| S_ISLNK (othrstatbuf.st_mode))
				break;
			//maybe there are more items in the other dir, or if this dir is empty
			//there will be no other check that the other even exists
			guint count = 0;
			e2_fs_tw (localpath, _e2p_diff_count_twcb, &count, 1,
				E2TW_XQT | E2TW_PHYS E2_ERR_NONE());
			guint count2 = 0;
			localotherpath = e2_utils_strcat (twdata->newroot,
				(gchar *)localpath + twdata->oldroot_len);
#ifdef E2_VFS
			e2_fs_tw (&ddata, _e2p_diff_count_twcb, &count2, 1,
#else
			e2_fs_tw (localotherpath, _e2p_diff_count_twcb, &count2, 1,
#endif
				E2TW_XQT | E2TW_PHYS E2_ERR_NONE());
			g_free (localotherpath);
			if (count != count2)
				break;
		}
		case E2TW_DP:	//directory, finished
			retval = E2TW_CONTINUE;	//effectively ignore these
			break;
//		case E2TW_DL:	//directory, not opened due to tree-depth limit
//		case E2TW_DM:	//directory, not opened due to different file system
//		case E2TW_DNR:	//unreadable directory (for which, error is reported upstream)
//		case E2TW_NS:	//un-statable item (for which, error is reported upstream)
		default:
			break;
	}
	return retval;
}
/**
@brief thread function to iterate over active pane file list to check for matches
BGL will be open
@param thread_data UNUSED NULL pointer specified when thread was established

@return NULL
*/
static gpointer _e2p_diff_all (gpointer thread_data)
{
	GtkTreeIter iter;
	GtkTreeModel *model = curr_view->model;
	if (gtk_tree_model_get_iter_first (model, &iter));
	{	//it's not empty
		e2_filelist_disable_refresh ();
		gdk_threads_enter (); //prevent thread-related hang!
		e2_window_set_cursor (GDK_WATCH);
		gdk_threads_leave ();
		while (TRUE)
		{
			LISTS_LOCK
			//CHECKME abort if cd detected ?
			gboolean busy = curr_view->listcontrols.refresh_working
						 || curr_view->listcontrols.cd_working;
			LISTS_UNLOCK
			if (!busy)
				break;
			usleep (100000);
		}

		gchar *curr_local, *other_local, *currpath;
		FileInfo *info;
		struct stat othersb;
		E2_CmpData data;
#ifdef E2_VFS
		data.operr = NULL;	//or something ...
		data.otherspace = other_view->spacedata;
		VPATH sdata;
		VPATH ddata;
		sdata.spacedata = curr_view->spacedata;
		ddata.spacedata = other_view->spacedata;
#endif
		curr_local = F_FILENAME_TO_LOCALE (curr_view->dir);
		other_local = F_FILENAME_TO_LOCALE (other_view->dir);
		gboolean matched;
		GtkTreeSelection *sel = curr_view->selection;
		gdk_threads_enter ();
		gtk_tree_selection_unselect_all (sel);	//start with clean slate
		gdk_threads_leave ();
		do
		{
#ifdef E2_VFSTMP
			//CLEAR any error data fom last loop
#endif
			gtk_tree_model_get (model, &iter, FINFO, &info, -1);
			data.newroot = e2_utils_strcat (other_local, info->filename);
#ifdef E2_VFS
			ddata.localpath = data.newroot;
			if (e2_fs_lstat (&ddata, &othersb E2_ERR_NONE()))
#else
			if (e2_fs_lstat (data.newroot, &othersb E2_ERR_NONE()))
#endif
				matched = FALSE;	//lstat() failed, crudely assume can't find anything to match
			else
			{
				currpath = e2_utils_strcat (curr_local, info->filename);
				if (S_ISDIR (info->statbuf.st_mode) && S_ISDIR (othersb.st_mode))
				{
					data.oldroot_len = strlen (currpath);
#ifdef E2_VFS
					sdata.localpath = currpath;
					matched = e2_fs_tw (&sdata, _e2p_diff_twcb, &data, -1,
#else
					matched = e2_fs_tw (currpath, _e2p_diff_twcb, &data, -1,
#endif
						E2TW_PHYS E2_ERR_NONE());
				}
				else if (!S_ISDIR (info->statbuf.st_mode) && !S_ISDIR (othersb.st_mode))
#ifdef E2_VFS
				{
					sdata.localpath = currpath;
					matched = _e2p_diff1 (&sdata, &info->statbuf, &ddata);
				}
#else
					matched = _e2p_diff1 (currpath, &info->statbuf, data.newroot);
#endif
				else
					matched = FALSE;	//mixture of dir and non-dir

				g_free (currpath);
			}

			if (matched)
			{
				gdk_threads_enter ();
				gtk_tree_selection_select_iter (sel, &iter);
				gdk_threads_leave ();
			}

			g_free (data.newroot);
		} while (gtk_tree_model_iter_next (model, &iter));

		F_FREE (curr_local);
		F_FREE (other_local);

#ifdef E2_VFS
		//cleanup data.operr	//or something ...
#endif
		gdk_threads_enter ();
		e2_window_set_cursor (GDK_LEFT_PTR);
		gdk_threads_leave ();
		e2_filelist_enable_refresh ();
	}
	return NULL;
}

/**
@brief directory-comparison plugin action
This creates a thread to iterate over active pane file list to check for matches

@param from the button, menu item etc which was activated
@param art action runtime data

@return TRUE
*/
static gboolean _e2p_diff (gpointer from, E2_ActionRuntime *art)
{
	g_thread_create (_e2p_diff_all, NULL, FALSE, NULL);
	return TRUE;
}

//aname must be confined to this module
static gchar *aname;
/**
@brief plugin initialization function, called by main program

@param p ptr to plugin data struct

@return TRUE if the initialization succeeds, else FALSE
*/
gboolean init_plugin (Plugin *p)
{
#define ANAME "dircmp"
  aname = _("compare");

  p->signature = ANAME VERSION;
  p->menu_name = _("C_ompare");
  p->description = _("Select active-pane items which are duplicated in the other pane");
  p->icon = "plugin_"ANAME"_"E2IP".png";  //prepend path if appropriate

  if (p->action == NULL)
 {
	//no need to free this
	gchar *action_name = g_strconcat (_A(13),".",aname,NULL);
	p->action = e2_plugins_action_register
	 (action_name, E2_ACTION_TYPE_ITEM, _e2p_diff, NULL, FALSE, 0, NULL);
	  return TRUE;
  }
  return FALSE;
}
/**
@brief cleanup transient things for this plugin

@param p pointer to data struct for the plugin

@return TRUE if all cleanups were completed
*/
gboolean clean_plugin (Plugin *p)
{
  gchar *action_name = g_strconcat (_A(13),".",aname,NULL);
  gboolean ret = e2_plugins_action_unregister (action_name);
  g_free (action_name);
  return ret;
}
