/* $Id: e2p_crypt.c 848 2008-04-04 22:57:42Z tpgww $

Copyright (C) 2007-2008 tooar <tooar@gmx.net>

This file is part of emelFM2.
emelFM2 is free software; for the most part 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.

However this file, a plugin for emelFM2, may be configured to include
mini-LZO code, about which there are mixed messages. It might be
licensed only under version 2 of the General Public License, and if
so and that code is included, then this plugin must entirely remain
under that same version of the General Public Licence.

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/optional/e2p_crypt.c
@brief plugin for encrypting or decrypting selected items
The encryption code is derived from tinycrypt v.0.4. It's based on ARC4 (see
http://en.wikipedia.org/wiki/RC4) with enchancements by the tinycrypt author:
 - reduced nonce value to about 20 bytes - 160 bits should be enough
 - reduced discarded bytes to 512 - there's no evidence on the net that more
   are needed
 - simplified mixing of the password and nonce values with the key, since
   throwing away the first 512 bytes mixes them up anyway
As for tinycrypt, any [de]compression is peformed using minilzo, the mini
subset of the fast LZO real-time data compression library
*/

/*TODO
en/de-compression
  single
  handle counter in new custom name
  ?m[un]map (void *address, size_t length, int protect, int flags, int filedes, off_t offset)
  buffered I/O if not processing whole file at once, can't be done with compression ?
  pthread_testcancel ();	//swap threads, cancel if instructed WHERE RELEVANT
  library access for [de]compression
  links - ok ?

LZO supports in-place decompression, so sniff or reallocate loaded buffer NO - fails
in-place decompressing instead of duplicate buffers seems to NOT work
max size of lzo buffer lzo_unt = size_t? = ?
LZO streaming?

UI
error handling - warnings
entry latency sometimes
crashes sometimes during dialog setup (maybe more to do with de-cryption?)
entry text scrolled out of entry window sometimes
"active" entry text selected but not focused @ startup
some dialog-button choices have a BGL problem, if hide dialog after user-choice ?
*/

#include "emelfm2.h"
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <pthread.h>
#include <dlfcn.h>
//#include "e2p_crypt.h"
#include "e2_plugins.h"
#include "e2_password_dialog.h"
#include "e2_task.h"
#include "e2_filelist.h"

//enable internal mini-LZO for en/de-cryption
//#define E2_MINICRYPT
#ifdef E2_MINICRYPT
//get mini-lzo headers and code
# include "e2p_crypt_lzo.source"
#endif

#ifdef TC_COMPATIBLE
//if we want to be compatible with tinycrypt
# ifndef E2_MINICRYPT
#  define E2_MINICRYPT
# endif
# define csize_t gulong //32 bits, probably?
# define SIZESHIFT 24 //(sizeof (csize_t) - 1) * 8, for handling endian-independent stored csize_t
# define NONCE_LENGTH 20
# define HEADER_LENGTH1 20	//no flags in TC files
#elif defined(_LARGEFILE_SOURCE)//if we cater for "large" files
# define csize_t guint64
# define SIZESHIFT 56
# define NONCE_LENGTH 24  //HEADER_LENGTH1 - csize_t (>=20)
# define HEADER_LENGTH1 32 //multiple of 8 >= (csize_t + 20)
#else //if we cater for files up to 2 GB
# define csize_t guint32
# define SIZESHIFT 24
# define NONCE_LENGTH 20
# define HEADER_LENGTH1 24 //NONCE_LENGTH + csize_t for flags
#endif

#define KEY_LENGTH 256 //hash table size KEY_LENGTH MUST be 256
//#define DISCARD_BYTES 768 // KEY_LENGTH * 3
#define DISCARD_BYTES 512 // KEY_LENGTH * 2
#define SWAP(a,b) temp=a;a=b;b=temp;

//bitflags in csize_t stored in after nonce in encrypted file
//(don't change or remove any existing flag!)
enum
{
	E2_CFLAGNONE     = 0,
	E2_CFLAGCOMPRESS = 1,		//file is compressed (and one of the type-flags is set)
	E2_CFLAGSIZE     = 1 << 4,	//original file size stored as csize_t
	E2_CFLAGNAME     = 1 << 5,	//original file name stored as text
	E2_CFLAGINFO     = 1 << 6,	//original file data stored in FileInfo
	E2_CFLAGINTLZ    = 1 << 16,	//file compressed with internal mini lzo
		//these need to be in ascending usage-preference-order
	E2_CFLAGLZO      = 1 << 17,	//file compressed with external liblzo
	E2_CFLAGZ        = 1 << 18,	//file compressed with external libz
	E2_CFLAGBZ2      = 1 << 19	//file compressed with external libbz2
};
//for library checking
#define E2_CFLAGLIBMASK 0xf0000

//FOR LZO [DE]COMPRESSION
#define LZO1X_1_MEM_COMPRESS 16384*sizeof(guchar *)

//FOR LIBZ [DE]COMPRESSION
//compression levels
enum
{
	Z_DEFAULT_COMPRESSION = -1,
	Z_NO_COMPRESSION,
	Z_BEST_SPEED,
	Z_BEST_COMPRESSION = 9
};

//FOR LIBBZIP2 [DE]COMPRESSION

typedef struct _E2P_CryptOpts
{
	gboolean en_name_same;	//encrypted file name = same as onencrypted name
	gboolean en_name_suffix;//encrypted file name has user-specified suffix (if any)
	gboolean en_name_custom;//encrypted file name = user-specified
//	gboolean en_name_embed;	//store filenama in encrypted file
	gboolean en_properties_embed;	//store filenama and statbuf in encrypted file

	gboolean de_name_same;		//decrypted file name = same as encrypted name
	gboolean de_name_stored;	//decrypted file name = embedded original name
	gboolean de_name_suffix;	//decrypted file name omits user-specified suffix (if any)
	gboolean de_name_custom;	//decrypted file name = user-specified

	gboolean de_props_stored;	//decrypted file other properties = original file
	gboolean compress;	//compress file before encryption
	gboolean backup;	//preserve any file with same name as specified for the [de]crypted file
	gboolean preserve;	//preserve the file to be [de]crypted, with alternate name if appropriate
	gboolean recurse;	//recursively process all files in any selected dir
	gboolean walklinks;	//process link targets

	gboolean decryptmode;//TRUE = decrypt, FALSE = encrypt
	gboolean permission;//for transferring whether it's ok to make the change
	gboolean multisrc;	//TRUE when processing > 1 item in loop
	gboolean ignore_suffix; //don't worry about incorrect suffix when decompressing
	gboolean owrite;	//don't ask to confirm overwrites

	gchar *en_suffix;	//user-specified suffix, freeable utf-8
	gchar *en_name;		//user-specified name, freeable utf-8
	gchar *de_suffix;	//user-specified suffix, freeable utf-8
	gchar *de_name;		//user-specified name, freeable utf-8
	gchar *plain_pw;	//store for plaintext password ptr
	const gchar *localpath;	//copy of dialog-function arg, or substitute during treewalk
	struct stat *statptr;
#ifdef E2_VFS
	PlaceInfo *spacedata;
#endif
	GList *dirdata;		//list of E2_Dirent's for dirs yet to be processed
} E2P_CryptOpts;

typedef struct _E2P_CryptDlgRuntime
{
	GtkWidget *dialog;	//main dialog widget
	E2P_CryptOpts *opts;
	E2_PWDataRuntime *pwrt;	//data struct for password-related widgets
	gboolean dlgopen;	//the following widgets exist
	GtkWidget *mode_btn;		//radio button for en/de-crypt mode

	GtkWidget *encryptbox;		//vbox with encryption-specific widgets
	GtkWidget *en_name_btn_same;
	GtkWidget *en_name_btn_suffix;
	GtkWidget *en_name_btn_custom;
	GtkWidget *en_name_suffix_entry;
	GtkWidget *en_name_custom_entry;
//	GtkWidget *en_name_embed_btn;
	GtkWidget *confirmbox;
	GtkWidget *en_properties_embed_btn;
	GtkWidget *compress_btn;

	GtkWidget *decryptbox;		//vbox with decryption-specific widgets
	GtkWidget *de_name_btn_same;
	GtkWidget *de_name_btn_stored;
	GtkWidget *de_name_btn_suffix;
	GtkWidget *de_name_btn_custom;
	GtkWidget *de_name_suffix_entry;
	GtkWidget *de_name_custom_entry;

	GtkWidget *recurse_btn;
	GtkWidget *backup_btn;
	GtkWidget *preserve_btn;
	GtkWidget *linktarget_btn;
	GtkWidget *properties_btn;

//	GtkWidget *all_btn;	//for [de]sensitisizing apply-to-all choice
	DialogButtons result;
} E2P_CryptDlgRuntime;

static gboolean _e2p_task_docryptQ (E2_ActionTaskData *qed);
static csize_t _e2pcr_compress_buffer (gpointer filebuffer,
	/*ssize_t*/ gulong filebuffersize, gpointer *compressedbuffer);	//CHECKME gulong size ?
static gboolean _e2pcr_write_buffer (VPATH *localpath, gint descriptor,
	gpointer filebuffer, /*ssize_t*/ gulong filebuffersize);	//CHECKME gulong size ?
static gboolean _e2pcr_read_file (VPATH *localpath, gpointer *filebuffer,
	/*ssize_t*/ gulong filebuffersize);
static csize_t _e2pcr_decompress_buffer (gpointer filebuffer,
	/*ssize_t*/ gulong filebuffersize, csize_t originalfilesize,
#ifndef E2_MINICRYPT
	//FIXME make altdecompress_buf a function arg, non-static
	csize_t libflags, //gint (*altdecompress_buf) (),
#endif
	gpointer *decompressedbuffer);
static gboolean _e2pcr_flush_file (VPATH *localpath, guint8 hashes[KEY_LENGTH]);

//session-static parameters
static E2P_CryptOpts session_opts =
{
	FALSE,	//encrypted file name = same as onencrypted name
	TRUE,	//encrypted file name has user-specified suffix (if any)
	FALSE,	//encrypted file name = user-specified
//	FALSE,	//store filenama in encrypted file
	FALSE,	//store filename and statbuf in encrypted file
	FALSE,	//decrypted file name = same as encrypted name
	FALSE,	//decrypted file name = embedded original name
	TRUE,	//decrypted file name omits user-specified suffix (if any)
	FALSE,	//decrypted file name = user-specified

	FALSE,	//reinstate properties of original file other than its name
	TRUE,	//compress file before encryption
	TRUE,	//preserve any file with same name as specified for the [de]crypted file
	TRUE,	//preserve the file to be [de]crypted, with alternate name if appropriate
	FALSE,	//recursively process all files in any selected dir
	TRUE,	//process link targets
/*rest are all default values, FALSE or NULL

	FALSE,	//decrypt mode
	FALSE,	//permission
	FALSE,	//>1 item
	FALSE,	//ignore decomp suffix
	FALSE,	//overwrite

	NULL,	//encryption suffix, initialised to ".enc"
	NULL,	//custom name for encryption
	NULL,	//decryption suffix, initialised to ".enc"
	NULL,	//custom name for decryption
	NULL,	//password

	NULL,	//item path
	NULL	//stat buffer ptr
*/
};
//these are ok to be static for all usage in session ??
csize_t compresslib = E2_CFLAGNONE;
csize_t altdecompresslib = E2_CFLAGNONE;
//pointers to library functions for compressing and decompressing a buffer
//these will be set to relevant functions from LZO lib, libz or libbz2,
//according to which lib is detected (scanniing in that order). Or NULL if no
//compression lib is available at runtime
gint (*init_compress) ();
gint (*compress_buf) ();
gint (*decompress_buf) ();
//for decompressing using a lib other than the default, in accord with in-file flag
gint (*altdecompress_buf) ();

  /***************/
 /** utilities **/
/***************/
/**
@brief get a random value in range 0 .. 255

@param retval pointer to store for value

@return TRUE if the value was filled
*/
static gboolean _e2pcr_getrandom (guint8 *retval)
{
	E2_FILE *randFile = fopen	//no vfs needed here
#if defined(__linux__) || defined(__solaris__) || defined(darwin) || defined(__OpenBSD__) || defined(AIX)
	//CHECKME which other OS's ?
	("/dev/urandom", "r");
#else
	("/dev/random", "r");
#endif
	if (randFile == NULL)
	{
		printd (DEBUG, "cannot open random number source");
		//FIXME handle error
		*retval = 0;
		return FALSE;
	}
	*retval = getc (randFile);
	fclose (randFile);
	return TRUE;
}
/**
@brief construct a temporary itemname by adding a suffix to @a localpath

@param localpath absolute path of item to be processed, localised string
@param custom string to append to @a localpath, localised string

@return newly-allocated, localised, path string comprising the temp name
*/
static gchar *_e2pcr_get_tempname (VPATH *localpath, gchar *custom)
{
	gchar *temppath, *s;
	guint i = 0;
	E2_ERR_DECLARE
#ifdef E2_VFS
	VPATH tdata;
	tdata.spacedata = localpath->spacedata;
#endif
	while (TRUE)
	{
		temppath = g_strdup_printf ("%s%s~%d", VPSTR(localpath), custom, i);
		if (i == 0)
		{	//first try without any "~N" suffix
			s = strrchr (temppath, '~');
			*s = '\0';
		}
#ifdef E2_VFS
		tdata.localpath = temppath;
		if (e2_fs_access2 (&tdata E2_ERR_PTR()) && E2_ERR_IS (ENOENT))
#else
		if (e2_fs_access2 (temppath E2_ERR_PTR()) && E2_ERR_IS (ENOENT))
#endif
		{
			E2_ERR_CLEAR
			break;
		}
		E2_ERR_CLEAR
		g_free (temppath);
		i++;
	}
	return temppath;
}
/**
@brief in an endian-independant fashion, store unencrypted data corresponding to @a value starting from @a datastart

@param datastart pointer to first of a series of unencrypted bytes to store

@return
*/
static void _e2pcr_store (csize_t value, guchar *datastart)
{
	csize_t s = value;
	guint i;
	//stored byte-order is low ... high
    for (i = 0; i < sizeof (csize_t); i++)
	{
		*datastart++ = (guint8) s;
		s >>= 8;
    }
}
/**
@brief in an endian-independant fashion, construct a csize_t from data stored starting from @a datastart

@param datastart pointer to first of a series of decrypted stored bytes to process

@return the stored value
*/
static csize_t _e2pcr_read_store (guchar *datastart)
{
	csize_t grabber, value = 0;
	guint i;
    for (i = 0; i < sizeof (csize_t); i++)
	{
		grabber = *datastart++;
		value = (value >> 8) | (grabber << SIZESHIFT);
    }
	return value;
}
/**
@brief check whether the user wlll allow an existing item to be over-written
This is not done in main task loop, as we need to handle custom names set in
the dialog or stored in a file being decompressed
This expects BGL to be open/off
@param localpath absolute path of item to check, localised string
@param multisrc TRUE if the current item is part of a multi-item selection

@return OK if there is no conflict, or code corresponding to the user's choice in the o/w dialog
*/
static DialogButtons _e2pcr_ow_check (VPATH *localpath, gboolean multisrc)
{
	DialogButtons result;
	if (e2_fs_access2 (localpath E2_ERR_NONE()) == 0)
	{
#ifdef E2_REFRESH_DEBUG
		printd (DEBUG, "enable refresh, copy as dialog");
#endif
		e2_filelist_enable_refresh ();  //allow updates while we wait
		gdk_threads_enter ();
		result = e2_dialog_ow_check (NULL, VPSTR (localpath), (multisrc) ? BOTHALL : NONE);
		gdk_threads_leave ();
		e2_filelist_disable_refresh ();
	}
	else
		result = OK;
	return result;
}
/**
@brief run warning dialog for confirmation
@param prompt prompt string
@param multi this is part of a multi-file task
@return button code returned by the dialog
*/
static DialogButtons _e2pcr_dialog_warning (gchar *prompt, gboolean multi)
{
	DialogButtons retval;
	GtkWidget *dialog = e2_dialog_create (GTK_STOCK_DIALOG_WARNING,
		prompt, _("confirm"), e2_dialog_response_decode_cb, &retval);
	//set default button to 'no'
	E2_BUTTON_NO.showflags |= E2_BTN_DEFAULT;
	gdk_threads_enter ();
	if (multi)
		e2_dialog_show (dialog, NULL,	//parent stays sensitive
			0, &E2_BUTTON_NOTOALL, &E2_BUTTON_YESTOALL,
			&E2_BUTTON_YES, &E2_BUTTON_NO, NULL);
	else
		e2_dialog_show (dialog, NULL,	//parent stays sensitive
			0, &E2_BUTTON_YES, &E2_BUTTON_NO, NULL);
	gtk_main ();	//because the decoder has gtk_main_quit();
	gtk_widget_destroy (dialog);
	gdk_threads_leave ();
	return retval;
}

  /****************************/
 /* en/de-cryption functions */
/****************************/

#ifndef E2_MINICRYPT
/**
@brief setup things for decompression, according to @a flags

@param localpath localised path string, for use in error messages
@param flags the flags recorded in a file being decompressed

@return TRUE if no error happens
*/
static gboolean _e2pcr_check_lib (VPATH *localpath, csize_t flags)
{
	printd (DEBUG, "masked file lib-flags %0x", flags & E2_CFLAGLIBMASK);
	gpointer libhandle;
	//FIXME use non-static func pointer, and
	gchar *message = NULL;	//no error yet
	if ((flags & E2_CFLAGLIBMASK) == (compresslib & E2_CFLAGLIBMASK))
	{
		altdecompress_buf = decompress_buf;
		printd (DEBUG, "using default lib");
	}
	else
	{
		if (flags & E2_CFLAGLZO)	//(E2_CFLAGLZO | E2_CFLAGINTLZ));
		{
			if (!(altdecompresslib & E2_CFLAGLZO) //need to get this lib
				&& (compresslib & E2_CFLAGLIBMASK) > E2_CFLAGLZO) //couldn't find it at start of session
			{
				printd (DEBUG, "LZO decompression library missing");
				message = GINT_TO_POINTER (1);
			}
//					else if (altdecompresslib & E2_CFLAGLZO) //got it before
//					{
				//FIXME
//					}
			else
			{
				libhandle = dlopen ("liblzo2.so.2", RTLD_LAZY);
				if (libhandle == NULL)
				{
					printd (DEBUG, "LZO decompression library missing");
					message = GINT_TO_POINTER (1);
				}
				init_compress = dlsym (libhandle, "__lzo_init_v2");	//a #define in lzoconf.h
				if (init_compress != NULL)
				{
					altdecompress_buf = dlsym (libhandle, "lzo1x_decompress_safe");
					if (altdecompress_buf != NULL)
					{
						altdecompresslib |= E2_CFLAGLZO;
						printd (DEBUG, "using LZO lib");
					}
					else
					{
						dlclose (libhandle);
						message = GINT_TO_POINTER (1);
					}
				}
				else
				{
					dlclose (libhandle);
					message = GINT_TO_POINTER (1);
				}
			}
			if (message != NULL)
				message = _("No LZO compression-library for file %s");
		}
		else if (flags & E2_CFLAGZ)
		{
			if (!(altdecompresslib & E2_CFLAGZ) //need to change
				&& (compresslib & E2_CFLAGLIBMASK) > E2_CFLAGZ) //couldn't fine the lib at start of session
			{
				printd (DEBUG, "ZLIB decompression library missing");
				message = GINT_TO_POINTER (1);
			}
//					else if (altdecompresslib & E2_CFLAGZ) //got it before
//					{
				//FIXME
//					}
			else
			{
				libhandle = dlopen ("libz.so.1", RTLD_LAZY);
				if (libhandle == NULL)
				{
					printd (DEBUG, "ZLIB decompression library missing");
					message = GINT_TO_POINTER (1);
				}
				altdecompress_buf = dlsym (libhandle, "uncompress");
				if (altdecompress_buf != NULL)
				{
					altdecompresslib |= E2_CFLAGZ;
					printd (DEBUG, "using ZLIB lib");
				}
				else
				{
					dlclose (libhandle);
					message = GINT_TO_POINTER (1);
				}
			}
			if (message != NULL)
				message = _("No ZLIB compression-library for file %s");
		}
		else if (flags & E2_CFLAGBZ2)
		{
			if (!(altdecompresslib & E2_CFLAGBZ2) //need to change
				&& (compresslib & E2_CFLAGLIBMASK) > E2_CFLAGBZ2) //couldn't find the lib at start of session
			{
				printd (DEBUG, "BZ2LIB decompression library missing");
				message = GINT_TO_POINTER (1);
			}
//					else if (altdecompresslib & E2_CFLAGBZ2) //got it before
//					{
				//FIXME
//					}
			else
			{
				libhandle = dlopen ("libbz2.so.1", RTLD_LAZY);
				if (libhandle == NULL)
				{
					printd (DEBUG, "BZ2LIB decompression library missing");
					message = GINT_TO_POINTER (1);
				}
				altdecompress_buf = dlsym (libhandle, "BZ2_bzBuffToBuffCompress");
				if (altdecompress_buf != NULL)
				{
					altdecompresslib |= E2_CFLAGBZ2;
					printd (DEBUG, "using BZ2 lib");
				}
				else
				{
					dlclose (libhandle);
					message = GINT_TO_POINTER (1);
				}
			}
			if (message != NULL)
				message = _("No BZIP compression-library for file %s");
		}
		else
		{
			printd (DEBUG, "unknown decompression library");
			message = _("Unknown compression-library for file %s");
		}

		if (message != NULL)
		{
			e2_fs_error_simple (message, localpath);
			return FALSE;
		}
	}
	return TRUE;
}
#endif
/**
@brief initialize hash key
Unlike with tinycrypt, the "discard bytes" process starts at hashes[0], not hashes[1],
and at the end of that process tha i-index is 255, not 0
@param hashes array of hash-bytes
@param password the en/de-cryption password string
@param nonce pointer to start of "nonce" string
@param noncelength length of @a nonce

@return the "seed" (j-index) of the hashkey to use for the next operation
*/
static guint8 _e2pcr_init_key (guint8 hashes[KEY_LENGTH], gchar *password,
	guchar *nonce, guint noncelength)
{
	guint indx;
	guint8 i, j, temp;
	gchar *p;

	//initialize the key;
    for (indx = 0; indx < KEY_LENGTH; indx++)
        hashes[indx] = (guint8)indx;
	//mangle the key from the password, as done in ARC4
	i = j = 0;
	p = password;
	for (indx = 0; indx < KEY_LENGTH; indx++)
	{
		if (*p == '\0')
			p = password;

		j += hashes[(guint8)indx] + *p++;
		SWAP(hashes[(guint8)indx], hashes[j]);
	}
	//mangle the key from the random bytes in the nonce
	for (indx = 0; indx < noncelength; indx++)
	{
		j += hashes[i] + *nonce++;
		SWAP(hashes[i], hashes[j]);
		i++;
	}
	//"discard bytes" = mangle some more
	i = 255;	//don't skip the first value
	j = 0;
	for (indx = 0; indx < DISCARD_BYTES; indx++)
	{
		i++;
		j += hashes[i];
		SWAP(hashes[i], hashes[j]);
	}
	//DISCARD_BYTES is a multiple of KEY_LENGTH, so now i = 255, j = whatever
	return j;
}
/**
@brief setup nonce and hash table to use for encryption
This uses with a random key XOR-ed with the user's munged key.
Prepend the key, and the uncompressed file length, then encrypt it.

@param hashes array of hash values to en/decrypt each byte of file
@param password plaintext password to use
@param noncebuffer array of chars in which to store the nonce, must be sized >= NONCE_LENGTH

@return 0 on error, or the "j-index" resulting from the key mangling (which may be 0)
*/
static guint8 _e2pcr_encrypt_setup (guint8 hashes[KEY_LENGTH], gchar *password,
	guchar noncebuffer[NONCE_LENGTH])
{
	//fill nonce with random bytes
	E2_FILE *randFile = fopen
#if defined(__linux__) || defined(__solaris__) || defined(darwin) || defined(__OpenBSD__) || defined(AIX)
	//CHECKME which other OS's ?
	("/dev/urandom", "r");
#else
	("/dev/random", "r");
#endif
	if (randFile == NULL)
	{
		printd (DEBUG, "cannot open random number source");
		//FIXME handle error
		return 0;
	}
	guchar *bufferp = noncebuffer;
	guint8 i = 0;
	for (i = 0; i < NONCE_LENGTH; i++)
		*bufferp++ = getc (randFile);

	fclose (randFile);

	return (_e2pcr_init_key (hashes, password, noncebuffer, NONCE_LENGTH));
}
/**
@brief encrypt or decrypt @a filebuffer
Buffer contents are XOR-ed with the user's munged key
@param hashes array of key bytes
@param iseed pointer to store for i-index for manipulating @a hashes
@param jseed pointer to store for j-index for manipulating @a hashes
@param filebuffer the buffer to be processed
@param filebuffersize size of @a filebuffer

@return
*/
static void _e2pcr_crypt_buffer (guint8 hashes[KEY_LENGTH],
	guint8 *iseed, guint8 *jseed, gpointer filebuffer, csize_t filebuffersize)
{
	guchar *encryptedbufferp, *filebufferp;
	csize_t indx;
	guint8 i, j, temp;

	encryptedbufferp = filebufferp = filebuffer;
	i = *iseed;
	j = *jseed;

	for (indx = 0; indx < filebuffersize; indx++)
	{
		j += hashes[i];
		*encryptedbufferp++ = *filebufferp ^ hashes[(guint8)(hashes[i] + hashes[j])];
		SWAP(hashes[i], hashes[j]);
		i++;
		filebufferp++;
	}
	*iseed = i;
	*jseed = j;
}
/**
@brief finalise naming of en/de-crypted file
This is same for en- and de-cryption, except that in former case, redundant file
is wiped, not just deleted

@param localpath localised path of the item that's been processed
@param temppath localised path of interim temp file holding the processed results
@param newpath localised path of final name that's different from @a localpath
@param same_name TRUE when the processed file ends up as @a localpath
@param preserve TRUE to backup anything that would be overwritten, including @a localpath (options->preserve or options->backup)
@param wipe TRUE to wipe, FALSE to delete, the original @a localpath

@return TRUE if all was done as required
*/
static gboolean _e2pcr_finalise_item (VPATH *localpath, VPATH *temppath, VPATH *newpath,
	gboolean same_name, gboolean preserve, gboolean wipe, guint8 hashes[KEY_LENGTH])
{
#ifdef E2_VFS
	VPATH otherpath;
	otherpath.spacedata = localpath->spacedata;
#endif
	gboolean success;
	gchar *tmp;
	if (same_name)
	{
		if (preserve)
		{
			//no need to check for permission - that's done in the dialog
			tmp = _e2pcr_get_tempname (localpath, "-original");	//ascii, & don't bother with translation
#ifdef E2_VFS
			otherpath.localpath = tmp;
			success = e2_task_backend_rename (localpath, &otherpath);
#else
			success = e2_task_backend_rename (localpath, tmp);
#endif
			g_free (tmp);
			if (!success)
				return FALSE;
		}
		else
		{
			success = (wipe) ?
				//wipe & delete original file (this fluffs up the hashes array)
				_e2pcr_flush_file (localpath, hashes) :
				//delete original file
				e2_task_backend_delete (localpath);
			if (!success)
				return FALSE;
		}

		if (!e2_task_backend_rename (temppath, localpath))
			return FALSE;
	}
	else	//final name != original name
	{
		if (!e2_fs_access (newpath, F_OK E2_ERR_NONE()))
		{
			if (preserve)
			{
				if (!e2_fs_access (newpath, W_OK E2_ERR_NONE()))
				{
					tmp = _e2pcr_get_tempname (newpath, "-original");	//ascii, & don't bother to translate
#ifdef E2_VFS
					otherpath.localpath = tmp;
					success = e2_task_backend_rename (newpath, &otherpath);
#else
					success = e2_task_backend_rename (newpath, tmp);
#endif
					g_free (tmp);
					if (!success)
					{
						//can't change original file
						//CHECKME get & use a tempname for localpath?
						return FALSE;
					}
				}
			}
			else
			{
				if (e2_option_bool_get ("confirm-overwrite"))
				{
					DialogButtons choice = _e2pcr_ow_check (newpath, FALSE);
					if (choice != OK)
						return FALSE;
				}
				e2_task_backend_delete (newpath);
			}
			if (!e2_task_backend_rename (temppath, newpath))
				return FALSE;
		}
		else	//newpath doesn't exist
			if (!preserve)
		{
			if (!e2_task_backend_rename (temppath, newpath))
				return FALSE;
			success = (wipe) ?
				//wipe & delete original file (this fluffs up the hashes array)
				_e2pcr_flush_file (localpath, hashes) :
				//delete original file
				e2_task_backend_delete (localpath);
			if (!success)
				return FALSE;
		}
		else	//leave original as is
			if (!e2_task_backend_rename (temppath, newpath))
			return FALSE;
	}
	return TRUE;
}
/**
@brief encrypt file @a localpath
Any error message expects BGL open
@a newname is freed, if non-NULL
@param localpath localised path of item being processed, also reflected in @a dir and @a oldname
@param dir directory in which the item is located, absolute localised path
@param oldname name of item being processed, localised string
@param newname new name of item after processing, NULL if @a use_same_name is TRUE
@param use_same_name TRUE to give encrypted file same name as original
@param check whether to confirm overwrites when renaming
@param options pointer to crypt options data

@return code indicating choice or success
*/
static DialogButtons _e2pcr_encrypt1 (VPATH *localpath,
	const gchar *dir, const gchar *oldname, gchar *newname,
	gboolean use_same_name, gboolean check, E2P_CryptOpts *options)
{
	guint8 iseed;
	guint8 jseed;
	guint8 hashes[KEY_LENGTH];
	gint fdesc;
	gulong filebuffersize, compressedbuffersize;
	gboolean onwards;
	gpointer filebuffer, compressedbuffer;
	gchar *temppath;
	E2_ERR_DECLARE
	struct stat sb;

	if (e2_fs_stat (localpath, &sb E2_ERR_NONE()))
	{
		printd (DEBUG, "cannpt stat the file");
#ifdef E2_VFS
		e2_fs_set_error_from_errno (&E2_ERR_NAME);
#endif
		e2_fs_error_local (_("Cannot open '%s' for reading"),
			localpath E2_ERR_MSGL());
		E2_ERR_CLEAR
		return NO;
	}

	//file reading is size-limited to gulong (was ssize_t)
	filebuffersize = (gulong) sb.st_size;
	if (filebuffersize == 0)
	{	//error or empty file, probably not error as file was opened ok
		return CANCEL;
	}

	if (!_e2pcr_read_file (localpath, &filebuffer, filebuffersize))
	{
		return NO;
	}

	if (options->compress)
	{
		printd (DEBUG, "go to compress file buffer");
		compressedbuffersize = _e2pcr_compress_buffer (filebuffer,
			filebuffersize, &compressedbuffer);
		if (compressedbuffersize == 0)
		{
			g_free (filebuffer);
			return NO;	//or CANCEL ?
		}
	}
	else	//warnings prevention only
	{
		compressedbuffersize = 0;
		compressedbuffer = NULL;
	}

	temppath = e2_utils_get_tempname (VPSTR (localpath));
#ifdef E2_VFS
	VPATH tdata = { temppath, localpath->spacedata };
#endif
	//descriptor for blockwize writing encryped file
	fdesc = e2_fs_safeopen (temppath, O_CREAT | O_WRONLY, S_IWUSR | S_IRUSR);
	if (fdesc < 0)
	{
#ifdef E2_VFS
		e2_fs_set_error_from_errno (&E2_ERR_NAME);
#endif
		e2_fs_error_local (_("Cannot open '%s' for writing"),
#ifdef E2_VFS
		&tdata E2_ERR_MSGL());
#else
		temppath E2_ERR_MSGL());
#endif
		E2_ERR_CLEAR
		g_free (filebuffer);
		g_free (temppath);
		return NO;
	}

	csize_t flags = (options->compress) ?
#ifdef E2_MINICRYPT
		E2_CFLAGCOMPRESS : E2_CFLAGNONE;
#else
		E2_CFLAGCOMPRESS | (compresslib & E2_CFLAGLIBMASK) : E2_CFLAGNONE;
#endif
	printd (DEBUG, "masked file lib-flags %0x", flags & E2_CFLAGLIBMASK);
	guint len = HEADER_LENGTH1;	//NONCE_LENGTH + bitflags
/*		if (options->en_name_embed)
	{
		flags |= E2_CFLAGNAME;
		len += (NAME_MAX+1);
	}
	else */
	if (options->en_properties_embed)
	{
		flags |= E2_CFLAGINFO;
		len += sizeof (FileInfo);
	}
	else
	{
		flags |= E2_CFLAGSIZE;
		len += sizeof (csize_t);
	}
	guchar headerbuffer[len];
	memset (headerbuffer, 0, len);

	iseed = 0;
	jseed = _e2pcr_encrypt_setup (hashes, options->plain_pw, headerbuffer);	//nonce at start of header

//		*((csize_t *) (headerbuffer + NONCE_LENGTH)) = flags;
	_e2pcr_store (flags, headerbuffer + NONCE_LENGTH);

/*		if (options->en_name_embed)
	{
		g_strlcpy (headerbuffer + HEADER_LENGTH1, oldname, NAME_MAX+1);
		FIXME need sixe too
	}
	else */
	if (options->en_properties_embed)
	{
		FileInfo *info = (FileInfo *) (headerbuffer + HEADER_LENGTH1);
		g_strlcpy (info->filename, oldname, sizeof (info->filename));
		//freshen the times to approx. this access
		if (e2_fs_lstat (localpath, &info->statbuf E2_ERR_NONE()))
			//or else just set old times
			memcpy (&info->statbuf, options->statptr, sizeof (struct stat));
	}
	else
	{
//			sptr = (csize_t *) (headerBuffer + HEADER_LENGTH1);
//			*sptr = filebuffersize;
		_e2pcr_store (filebuffersize, headerbuffer + HEADER_LENGTH1);
	}

	//encrypt header after the nonce
	_e2pcr_crypt_buffer (hashes, &iseed, &jseed, headerbuffer + NONCE_LENGTH,
		len - NONCE_LENGTH);

	//write header
#ifdef E2_VFS
	onwards = _e2pcr_write_buffer (&tdata, fdesc, headerbuffer, len);
#else
	onwards = _e2pcr_write_buffer (temppath, fdesc, headerbuffer, len);
#endif

	if (onwards)
	{
		//encrypt and write the main data
		if (flags & E2_CFLAGCOMPRESS)
		{
			_e2pcr_crypt_buffer (hashes, &iseed, &jseed, compressedbuffer, compressedbuffersize);
#ifdef E2_VFS
			onwards = _e2pcr_write_buffer (&tdata,
#else
			onwards = _e2pcr_write_buffer (temppath,
#endif
				fdesc, compressedbuffer, compressedbuffersize);
		}
		else
		{
			_e2pcr_crypt_buffer (hashes, &iseed, &jseed, filebuffer, filebuffersize);
#ifdef E2_VFS
			onwards = _e2pcr_write_buffer (&tdata,
#else
			onwards = _e2pcr_write_buffer (temppath,
#endif
				fdesc, filebuffer, filebuffersize);
		}
	}

	if (onwards)
	{
		//create trailer for signature-check when de-compressing
		//because it's 0, endian-ness doesn't matter
		csize_t *sptr = (csize_t *) headerbuffer;
		*sptr = 0;
		_e2pcr_crypt_buffer (hashes, &iseed, &jseed, headerbuffer, sizeof (csize_t));
#ifdef E2_VFS
		onwards = _e2pcr_write_buffer (&tdata,
#else
		onwards = _e2pcr_write_buffer (temppath,
#endif
			fdesc, headerbuffer, sizeof (csize_t));
	}

	if (onwards)
	{
		gchar *newpath = (newname == NULL) ? NULL : g_build_filename (dir, newname, NULL);
#ifdef E2_VFS
		VPATH ddata = { newpath, localpath->spacedata };
#endif
		onwards = _e2pcr_finalise_item (localpath,
#ifdef E2_VFS
		&tdata, &ddata,
#else
		temppath, newpath,
#endif
			use_same_name, options->preserve || options->backup, TRUE, hashes);
		if (newpath != NULL)
			g_free (newpath);
	}

	g_free (temppath);
	if (newname != NULL)
		g_free (newname);

	return ((onwards) ? OK : NO);
}
/**
@brief decrypt file @a localpath

Any error message expects BGL open

@param localpath localised path of item being processed, also reflected in @a dir and @a oldname
@param dir directory in which the item is located, absolute localised path
@param oldname name of item being processed, localised string
@param newname new name of item after processing, NULL if @a use_same_name is TRUE
@param use_same_name TRUE to give decrypted file same name as original
@param check whether to confirm overwrites when renaming
@param options pointer to crypt options data

@return code indicating choice or NO for error
*/
static DialogButtons _e2pcr_decrypt1 (VPATH *localpath,
	const gchar *dir, const gchar *oldname, gchar *newname,
	gboolean use_same_name, gboolean check, E2P_CryptOpts *options)
{
	guint8 iseed;
	guint8 jseed;
	guint8 hashes[KEY_LENGTH];
	gboolean onwards;
	FileInfo *info;
//	FileInfo stored_info;	//for remembering a streamed info
	E2_ERR_DECLARE
	struct stat sb;

	if (e2_fs_stat (localpath, &sb E2_ERR_NONE()))
	{
		printd (DEBUG, "cannpt stat the file");
#ifdef E2_VFS
		e2_fs_set_error_from_errno (&E2_ERR_NAME);
#endif
		e2_fs_error_local (_("Cannot open '%s' for reading"),
			localpath E2_ERR_MSGL());
		E2_ERR_CLEAR
		return NO;
	}
	//file reading is size-limited to gulong (was ssize_t)
	gulong filebuffersize = (gulong) sb.st_size;
	if (filebuffersize == 0)
	{	//error or empty file, probably not error as file was opened ok
		return CANCEL;
	}

//*		for debugging ...
/* NO MERIT IN 2-STAGE PROCESS, UNLESS SOME HEADER-TESTING IS DONE
	//sniff the file
	//sniff size must be >= NONCE_LENGTH + sizeof(c_size_t)(for flags) + sizeof(FileInfo)
	guchar headerbuffer[512];
	memset (headerbuffer, 0, );
	ssize_t headersize = MIN (sizeof (headerbuffer), filebuffersize);
	gpointer headerbuffer;
	gulong headersize = filebuffersize;

	if (!_e2pcr_read_file (localpath, &headerbuffer, headersize))
*/
	gpointer filebuffer;
	if (!_e2pcr_read_file (localpath, &filebuffer, filebuffersize))
	{
		return NO;
	}
	//setup hashes using nonce from header
	iseed = 0;
	jseed = _e2pcr_init_key (hashes, options->plain_pw, filebuffer, NONCE_LENGTH); //nonce is at start of file buffer
	//decrypt rest of header
	_e2pcr_crypt_buffer (hashes, &iseed, &jseed, filebuffer + NONCE_LENGTH,
			filebuffersize - NONCE_LENGTH);

	csize_t flags;
	//if reading the whole file, check the trailing signature
	//since it's supposed to be 0, endian-ness doesn't matter
	flags = *((csize_t *) (filebuffer + filebuffersize - sizeof (csize_t)));
	if (flags != 0)
	{
		printd (DEBUG, "bad password");
		e2_fs_error_simple (_("Wrong password for %s"), localpath);
		return NO;
	}

#ifdef E2_VFS
	VPATH ddata;
	ddata.spacedata = localpath->spacedata;
#endif

	guint skiplen;
	csize_t originalFileLength;

//	flags = *((csize_t *) (filebuffer + NONCE_LENGTH));
	flags = _e2pcr_read_store (filebuffer + NONCE_LENGTH);
	gboolean compressed = flags & E2_CFLAGCOMPRESS;
//	gboolean name = ;
	gboolean properties = flags & E2_CFLAGINFO;
	gboolean use_stored_props = properties;
	gboolean use_stored_name;
	if (properties)
	{
		info = (FileInfo *)(filebuffer + HEADER_LENGTH1);
		if (options->de_name_stored)
		{
			use_stored_name = !g_str_equal (oldname, info->filename);
			if (use_stored_name)
			{
				newname = g_strdup (info->filename);
				if (check)	//no point in warning about re-use of same name
				{
					gchar *checkpath = g_build_filename (dir, newname, NULL);
#ifdef E2_VFS
					ddata.localpath = checkpath;
					DialogButtons choice = _e2pcr_ow_check (&ddata, options->multisrc);
#else
					DialogButtons choice = _e2pcr_ow_check (checkpath, options->multisrc);
#endif
					g_free (checkpath);
					if (choice == YES_TO_ALL)
						options->owrite = FALSE;
					else if (choice == CANCEL || choice == NO_TO_ALL)
					{
						g_free (newname);
						return choice;
					}
				}
				use_same_name = FALSE;
			}
		}
		else
			use_stored_name = FALSE;

		skiplen = HEADER_LENGTH1 + sizeof (FileInfo);
		originalFileLength = (csize_t) info->statbuf.st_size;
		if (options->de_props_stored)
			use_stored_props = TRUE;	//signal for later attention
	}
	else
	{
		info = NULL;	//warning prevention only
		use_stored_name = FALSE;
		skiplen = HEADER_LENGTH1 + sizeof (csize_t);
//			originalFileLength = *((csize_t *) (headerBuffer + HEADER_LENGTH1));
		originalFileLength = _e2pcr_read_store (filebuffer + HEADER_LENGTH1);
	}

/*		if (filebuffersize > headersize)
	{	//decrypt rest of file
		filebuffer = malloc (filebuffersize);	//not g_try_malloc, that's limited to gulong
		CHECKALLOCATEDWARNT (filebuffer, )	//FIXME
		memcpy (filebuffer, headerbuffer, headersize);
		if (!_e2pcr_read_file ((gchar *)localpath, in_desc,
			filebuffer + headersize, filebuffersize - headersize))
		{
			e2_fs_safeclose (in_desc);
			retval = NO;
			goto cleanup;
		}
		_e2pcr_crypt_buffer (hashes, &iseed, &jseed, filebuffer + headersize,
			filebuffersize - headersize);
	}
	//check the trailing signature
	//since it's supposed to be 0, endian-ness doesn't matter
	flags = *((csize_t *) (filebuffer + filebuffersize - sizeof (csize_t)));
	if (flags != 0)
	{
		printd (DEBUG, "bad password");
		e2_fs_error_simple (_("Wrong password for %s"), (gchar *)localpath);
		e2_fs_safeclose (in_desc);
		retval = CANCEL;
		goto cleanup;
	}
*/
	gpointer uncompressedbuffer;
	csize_t uncompressedbuffersize;
	if (compressed)
	{
#ifndef E2_MINICRYPT
		if (!_e2pcr_check_lib (localpath, flags))
		{
			if (use_stored_name)
				g_free (newname);
			return CANCEL;
		}
#endif
		uncompressedbuffersize =
			_e2pcr_decompress_buffer (filebuffer + skiplen,
				filebuffersize - skiplen - sizeof (csize_t),
				originalFileLength,
#ifndef E2_MINICRYPT
				flags & E2_CFLAGLIBMASK,
#endif
				&uncompressedbuffer);	//same buffer used for decomp
		if (uncompressedbuffersize == 0)
		{
			printd (DEBUG, "decompression failed");
			e2_fs_error_simple (_("Error decompressing file %s"), localpath);
			if (use_stored_name)
				g_free (newname);
			return CANCEL;
		}
	}

	gchar *temppath = e2_utils_get_tempname (VPSTR (localpath));
#ifdef E2_VFS
	VPATH tdata = { temppath, localpath->spacedata };
#endif
	if (compressed)
	{
#ifdef E2_VFS
		onwards = e2_fs_set_file_contents (&tdata,
#else
		onwards = e2_fs_set_file_contents (temppath,
#endif
			uncompressedbuffer, uncompressedbuffersize,
			S_IWUSR | S_IRUSR E2_ERR_PTR());
	}
	else
	{
#ifdef E2_VFS
		onwards = e2_fs_set_file_contents (&tdata,
#else
		onwards = e2_fs_set_file_contents (temppath,
#endif
			filebuffer + skiplen, filebuffersize - skiplen - sizeof (csize_t),
			S_IWUSR | S_IRUSR E2_ERR_PTR());
	}

	gchar *newpath = (newname == NULL) ? NULL : g_build_filename (dir, newname, NULL);
	if (onwards)
	{
#ifdef E2_VFS
		ddata.localpath = newpath;
#endif
		onwards = _e2pcr_finalise_item (localpath,
#ifdef E2_VFS
		&tdata, &ddata,
#else
		temppath, newpath,
#endif
			use_same_name, options->preserve || options->backup, FALSE, hashes);
	}

	if (onwards && use_stored_props)
	{	//decrypting with stored data
#ifndef E2_VFS
		const gchar *p;
#endif
		if (use_same_name)
		{
#ifdef E2_VFS
			ddata.localpath = localpath->localpath;
#else
			p = VPCSTR (localpath);
#endif
		}
		else
		{
//			ddata.localpath = newpath already
#ifndef E2_VFS
			p = newpath;
#endif
		}
		if (onwards &&
#ifdef E2_VFS
			!e2_task_backend_chown (&ddata, info->statbuf.st_uid, info->statbuf.st_gid, FALSE))
#else
			!e2_task_backend_chown (p, info->statbuf.st_uid, info->statbuf.st_gid, FALSE))
#endif
				onwards = FALSE;
		if (onwards &&
#ifdef E2_VFS
			e2_fs_chmod (&ddata, info->statbuf.st_mode & ALLPERMS E2_ERR_NONE()))
#else
			e2_fs_chmod (p, info->statbuf.st_mode & ALLPERMS E2_ERR_NONE()))
#endif
				onwards = FALSE;
		if (onwards)
		{
			struct utimbuf tb;
			tb.modtime = info->statbuf.st_mtime;
			tb.actime = info->statbuf.st_atime;
#ifdef E2_VFS
			if (e2_fs_utime (&ddata, &tb E2_ERR_NONE()))
#else
			if (e2_fs_utime (p, &tb E2_ERR_NONE()))
#endif
				onwards = FALSE;
		}
	}

	g_free (temppath);
	if (newpath != NULL)
		g_free (newpath);
	if (newname != NULL)
		g_free (newname);	//CAREFUL in caller too

	return ((onwards) ? OK : NO);
}

  /*****************************/
 /** buffer & file functions **/
/*****************************/
/**
@brief compress @a filebuffer using using mini-lzo or some other supported library
Any error message expects BGL open
Size of the buffer is implicitly limited to ULONG_MAX
@param filebuffer pointer to buffer to compress
@param filebuffersize no. of bytes to compress in @a filebuffer
@param compressedbuffer store for pointer newly-allocated buffer holding compressed bytes

@return size of compressed buffer, 0 on error
*/
static csize_t _e2pcr_compress_buffer (gpointer filebuffer, /*size_t*/ gulong filebuffersize,
	gpointer *compressedbuffer)
{
#ifdef E2_MINICRYPT
	csize_t compressedbuffersize = filebuffersize + (filebuffersize >> 6) + 19;
	*compressedbuffer = malloc (compressedbuffersize);	//not g_try_malloc, that's limited to gulong
	CHECKALLOCATEDWARNT (*compressedbuffer, return 0;)
	if (*compressedbuffer == NULL)
	{
		//FIXME handle error
		return 0;
	}
	gpointer workmem = g_try_malloc (LZO1X_1_MEM_COMPRESS);
	CHECKALLOCATEDWARNT (workmem, )
	if (workmem != NULL)
	{
		lzo_uint newbuffersize;
		gint result = lzo1x_1_compress
			(filebuffer, filebuffersize, *compressedbuffer, &newbuffersize, workmem);

		g_free (workmem);

		if (result == LZO_E_OK)
			return (csize_t) newbuffersize;
	}
#else
	printd (DEBUG, "compress file buffer");
	csize_t compressedbuffersize;
	if (compresslib & E2_CFLAGLZO)
		compressedbuffersize = filebuffersize + (filebuffersize >> 6) + 19;
	else if (compresslib & E2_CFLAGZ)
		compressedbuffersize = (filebuffersize * 1.001 + 20 + 8) / 8 * 8;
	else if (compresslib & E2_CFLAGBZ2)
		compressedbuffersize = (filebuffersize * 1.01 + 600 + 8) / 8 * 8;
	else
		return 0;

	*compressedbuffer = malloc (compressedbuffersize);	//not g_try_malloc, that's limited to gulong
	CHECKALLOCATEDWARNT (*compressedbuffer, return 0;)

	if (compresslib & E2_CFLAGLZO)
	{
		printd (DEBUG, "compress using LZO");
		gpointer workmem = g_try_malloc (LZO1X_1_MEM_COMPRESS);
		CHECKALLOCATEDWARNT (workmem, )
		if (workmem != NULL)
		{
			init_compress ();
			guint compressedlen;
			gint res = compress_buf (filebuffer, (guint) filebuffersize, *compressedbuffer,
				&compressedlen, workmem);
			g_free (workmem);
			if (res == 0)
			{
				return (csize_t) compressedlen;
			}
		}
	}
	else if (compresslib & E2_CFLAGZ)
	{
		printd (DEBUG, "compress using ZLIB");
		gulong compressedlen = (gulong) compressedbuffersize;
		if (compress_buf (*compressedbuffer, &compressedlen, filebuffer,
			(gulong) filebuffersize, Z_BEST_SPEED) == 0)
		{
			return (csize_t) compressedlen;
		}
	}
	else if (compresslib & E2_CFLAGBZ2)
	{
		printd (DEBUG, "compress using BZ2");
		guint compressedlen = (guint) compressedbuffersize;
		if (compress_buf (*compressedbuffer, &compressedlen, filebuffer,
			(guint) filebuffersize, 2, 0, 30) == 0)
		{
			return (csize_t) compressedlen;
		}
	}
#endif
	//FIXME handle error, warn user
	printd (DEBUG, "compression failed");
	g_free (*compressedbuffer);
	*compressedbuffer = NULL;
	return 0;
}
/**
@brief decompress buffer using mini-lzo or some other supported library
Size of the buffer is implicitly limited to ULONG_MAX
@param filebuffer pointer to buffer to decompress
@param filebuffersize no. of bytes in @a filebuffer
@param originalfilesize expected length of decompressed buffer
#ifndef E2_MINICRYPT
@param csize_t libflags which decompression mode to use
#endif
@param decompressedbuffer store for pointer to newly-allocated buffer holding @a originalFileLength decompressed bytes

@return size of uncompressed buffer, 0 on error
*/
static csize_t _e2pcr_decompress_buffer (gpointer filebuffer,
	/*size_t*/ gulong filebuffersize, csize_t originalfilesize,
#ifndef E2_MINICRYPT
	//FIXME make altdecompress_buf a function arg, non-static
	csize_t libflags, //gint (*altdecompress_buf) (),
#endif
	gpointer *decompressedbuffer)
{
	*decompressedbuffer = malloc (originalfilesize);	//not g_try_malloc
	CHECKALLOCATEDWARNT (*decompressedbuffer, return 0;)
	if (*decompressedbuffer == NULL)
		return 0;

#ifdef E2_MINICRYPT
	lzo_uint decompressedlen;
	gint result = lzo1x_decompress
		(filebuffer, filebuffersize, *decompressedbuffer, &decompressedlen, NULL);

	if (result == LZO_E_OK && decompfilesize == originalfilesize)
		return (csize_t) decompfilesize;
#else
	if (libflags & E2_CFLAGLZO)	//(E2_CFLAGLZO | E2_CFLAGINTLZ));
	{
		printd (DEBUG, "de-compressing using LZO lib");
		init_compress ();
		guint decompressedlen = (guint) originalfilesize;
		if (altdecompress_buf (filebuffer, (guint) filebuffersize,
			*decompressedbuffer, &decompressedlen, NULL //gpointer wrkmem NOT USED
			) == 0 && (csize_t) decompressedlen == originalfilesize)
		{
			return (csize_t) decompressedlen;
		}
		printd (DEBUG, "but that failed - original size %d decompressed to %d", originalfilesize, decompressedlen);
	}
	else if (libflags & E2_CFLAGZ)
	{
		printd (DEBUG, "de-compressing using ZLIB lib");
		gulong decompressedlen = (gulong) originalfilesize;
		if (altdecompress_buf (*decompressedbuffer, &decompressedlen, filebuffer,
			(gulong) filebuffersize) == 0 && (csize_t) decompressedlen == originalfilesize)
		{
			return (csize_t) decompressedlen;
		}
		printd (DEBUG, "but that failed - original size %d decompressed to %d", originalfilesize, decompressedlen);
	}
	else if (libflags & E2_CFLAGBZ2)
	{
		printd (DEBUG, "de-compressing using BZ2 lib");
		guint decompressedlen = (guint) originalfilesize;
		if (altdecompress_buf (*decompressedbuffer, &decompressedlen, filebuffer,
			(guint) filebuffersize, 0, 0) == 0 && (csize_t) decompressedlen == originalfilesize)
		{
			return (csize_t) decompressedlen;
		}
		printd (DEBUG, "but that failed - original size %d decompressed to %d", originalfilesize, decompressedlen);
	}
#endif
	//FIXME handle error
	g_free (*decompressedbuffer);
	*decompressedbuffer = NULL;
	return 0;
}
/**
@brief fill @a buffer with the contents of some file from $PATH
This is an alternative to storing some sequence of data that is readily
recognisable as over-written data
Expects BGL to be open on arrival here
@param buffer pointer to buffer to be overwritten
@param buffersize size of @a buffer

@return TRUE if the process was completed
*/
static gboolean _e2pcr_wipe_buffer (gpointer buffer, size_t buffersize)
{
	gboolean retval = FALSE;
	gchar *sep;
	gchar *execpath = (gchar *)g_getenv ("PATH");
	if (execpath == NULL)
	{
		sep = NULL;
		execpath = "/bin";
	}
	else
	{
		sep = strchr (execpath, ':');	//ascii scan ok
		if (sep != NULL)
			execpath = g_strndup (execpath, sep-execpath);
		//FIXME preserve execpath so that later members can be used
	}
#ifdef E2_VFS
	VPATH ddata = { execpath, NULL };	//files in $PATH must be local
	GList *entries = (GList *)e2_fs_dir_foreach (&ddata,
#else
	GList *entries = (GList *)e2_fs_dir_foreach (execpath,
#endif
		E2_DIRWATCH_NO,	//local = fast read
		NULL, NULL, NULL E2_ERR_NONE());

	if (E2DREAD_FAILED (entries))
	{
		//FIXME try another dir in PATH, or ...
		//FIXME warn user
//		e2_fs_error_simple (
//			_("You do not have authority to read %s"), execpath);
		if (sep != NULL)
			g_free (execpath);
		return FALSE;
	}
	guint count = g_list_length (entries);
	guint8 c;
restart:
	if (!_e2pcr_getrandom (&c))
	{
		//CHECKME recover ? or ...
		//FIXME warn user
//		e2_fs_error_simple (
//			_("You do not have authority to modify %s"), (*iterator)->filename);
		goto cleanup;
	}
	guint first = count * c / 256;
	guint i = 0;
	gchar *filename, *filepath = NULL;
	GList *member;
reloop:
	for (member = g_list_nth (entries, first); member != NULL; member = member->next)
	{
		filename = (gchar *)member->data;
		if (!g_str_equal (filename, ".."))
		{
			filepath = g_build_filename (execpath, filename, NULL);
#ifdef E2_VFS
			ddata.localpath = filepath;
			if (!e2_fs_access (&ddata, R_OK E2_ERR_NONE()))
#else
			if (!e2_fs_access (filepath, R_OK E2_ERR_NONE()))
#endif
				break;
			g_free (filepath);
		}
		filepath = NULL;

		if (++i == count);
		{
			//try with next dir from PATH or ...
			printd (DEBUG, "cannot find a file for data source");
			//FIXME warn user
//			e2_fs_error_simple (
//				_("You do not have authority to read anything in %s"), execpath);
			goto cleanup;
		}
	}
	if (member == NULL && i < count)
	{	//reached end of list, cycle back to start
		first = 0;
		goto reloop;
	}
	if (filepath == NULL)
		goto cleanup;

	E2_ERR_DECLARE
	gint fdesc = e2_fs_safeopen (filepath, O_RDONLY, 0);
	if (fdesc < 0)
	{
		printd (DEBUG, "cannot open data source file");
		goto restart;	//try with another file from list
/*  or ...
#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
		goto cleanup;
*/
	}

	struct stat sb;
#ifdef E2_VFS
	e2_fs_stat (&ddata, &sb E2_ERR_NONE());
#else
	e2_fs_stat (filepath, &sb E2_ERR_NONE());
#endif
	csize_t masksize = (csize_t) sb.st_size;
	ssize_t n_read;

	if (masksize >= buffersize)
	{
		n_read = e2_fs_read (fdesc, buffer, buffersize E2_ERR_PTR());
		if (n_read < buffersize)
		{
//#ifdef E2_VFS
//			e2_fs_set_error_from_errno (&E2_ERR_NAME);
//#endif
//			e2_fs_error_local (_("Error reading file %s"), localpath E2_ERR_MSGL());
			E2_ERR_CLEAR
			//FIXME handle shortfall
		}
	}
	else
	{	//mask-file is smaller than the buffer, read repeatedly until buffer is full
		csize_t readsofar = 0;
		guchar *readPtr = buffer;
		while (readsofar < buffersize)
		{
			n_read = e2_fs_read (fdesc, readPtr, masksize E2_ERR_PTR());
			if (n_read < masksize)
			{
//#ifdef E2_VFS
//				e2_fs_set_error_from_errno (&E2_ERR_NAME);
//#endif
//				e2_fs_error_local (_("Error reading file %s"), localpath E2_ERR_MSGL());
				E2_ERR_CLEAR
				//FIXME handle shortfall
			}
			lseek (fdesc, 0, SEEK_SET);	//FIXME vfs
			readsofar += masksize;
			readPtr += masksize;
			if (readsofar > (buffersize - masksize))
				masksize = buffersize - readsofar;
		}
	}

	//FIXME page buffer to disk, to mask any swap storage

	e2_fs_safeclose (fdesc);
	retval = TRUE;
cleanup:
	if (sep != NULL)
		g_free (execpath);
	e2_list_free_with_data (&entries);

	return retval;
}
/**
@brief read some or all of a file into @a filebuffer
This allows full or partial writing of a file, to support streaming and
otherwise-segemented input
Any error message expects BGL to be open
@param localpath localised name of item being read, used only for error messages
@param filebuffer pointer to store for address of allocated buffer
@param filebuffersize no. of bytes to read into @a filebuffer

@return TRUE if requested no. of bytes were read
*/
static gboolean _e2pcr_read_file (VPATH *localpath, gpointer *filebuffer,
	/*size_t*/ gulong filebuffersize)
{
	if (filebuffersize > 0)
	{
		E2_ERR_DECLARE
		/*ssize_t*/ gulong nread;
		if (!e2_fs_get_file_contents (localpath, filebuffer, &nread,
			FALSE E2_ERR_PTR()) || nread < filebuffersize)
		{
			printd (DEBUG, "cannot read whole file");
			//FIXME handle error
#ifdef E2_VFS
			e2_fs_set_error_from_errno (&E2_ERR_NAME);
#endif
			e2_fs_error_local (_("Error reading file %s"), localpath E2_ERR_MSGL());
			E2_ERR_CLEAR
			return FALSE;
		}
	}
	return TRUE;
}
/**
@brief write @a buffer out to storage
This allows full or partial writing of a file, to support streaming and
otherwise-segemented output
Any error message expects BGL to be open
@param localpath localised name of item being read, used only for error messages
@param descriptor file descriptor
@param buffer store for pointer to buffer holding data to write
@param buffersize size of @a filebuffer, > 0

@return TRUE if the write was completed
*/
static gboolean _e2pcr_write_buffer (VPATH *localpath, gint descriptor,
	gpointer buffer, /*size_t*/ gulong buffersize)
{
	if (buffersize > 0)
	{
		E2_ERR_DECLARE
		ssize_t bytes_written = e2_fs_write (descriptor, buffer,
			buffersize E2_ERR_PTR());
		if ((gulong)bytes_written < buffersize)
		{
#ifdef E2_VFS
			e2_fs_set_error_from_errno (&E2_ERR_NAME);
#endif
			e2_fs_error_local (_("Error writing file %s"), localpath E2_ERR_MSGL());
			E2_ERR_CLEAR
			return FALSE;
		}
	}
	return TRUE;
}
/**
@brief overwrite and delete @a localpath with the contents of some file from /bin
Any error message here expects BGL to be open
@param localpath absolute path of item to be processed, localised string
@param hashes the hash array used for en/de-cryption

@return TRUE if the process was completed
*/
static gboolean _e2pcr_flush_file (VPATH *localpath, guint8 hashes[KEY_LENGTH])
{
	E2_ERR_DECLARE
	struct stat sb;
	if (e2_fs_stat (localpath, &sb E2_ERR_PTR()))
	{
		e2_fs_error_local (_("Cannot get current data for %s"),
			localpath E2_ERR_MSGL());
		E2_ERR_CLEAR
		return FALSE;
	}
	if (sb.st_size == 0)
		return TRUE;

	guint8 randomval = 112;
	_e2pcr_getrandom (&randomval);//fudge the size
	csize_t wipesize = (csize_t) sb.st_size + (csize_t) randomval;

	//find a buffer up to 64 times file's block-size
	csize_t buffersize = sb.st_blksize * 64;
	while (buffersize > wipesize)
		buffersize /= 2;
	if (buffersize < wipesize && buffersize < sb.st_blksize)
		buffersize = wipesize;
	gpointer buffer;
	while ((buffer = malloc (buffersize)) == NULL)
	{
		if (buffersize < sb.st_blksize)
		{
			gdk_threads_enter ();
			e2_utils_show_memory_message ();
			gdk_threads_leave ();
			return FALSE;
		}
		buffersize /= 2;
	}
	//open file for writing without truncation
	gint fdesc = e2_fs_safeopen (VPCSTR (localpath), O_RDWR | O_NONBLOCK, 0);
	if (fdesc < 0)
	{
		g_free (buffer);
#ifdef E2_VFS
		e2_fs_set_error_from_errno (&E2_ERR_NAME);
#endif
		e2_fs_error_local (_("Cannot open '%s' for writing"), localpath E2_ERR_MSGL());
		E2_ERR_CLEAR
		return FALSE;
	}

	gboolean retval = FALSE;
//	flockfile (outputFile);
	if (buffersize == wipesize)
	{
		if (!_e2pcr_wipe_buffer (buffer, buffersize)
			|| !_e2pcr_write_buffer (localpath, fdesc, buffer, buffersize))
		{
			//FIXME error message
//			e2_fs_error_simple (
//				_("You do not have authority to modify %s"), (*iterator)->filename);
			goto cleanup;
		}
	}
	else
	{
		csize_t writesofar = 0;
		csize_t bsize = buffersize;
		while (writesofar < wipesize)
		{
			if (_e2pcr_wipe_buffer (buffer, bsize)
				&& _e2pcr_write_buffer (localpath, fdesc, buffer, bsize))
			{
				writesofar += bsize;
				if (writesofar > (wipesize - buffersize))
					bsize = wipesize - writesofar;
			}
			else
			{
				//FIXME error message
//				e2_fs_error_simple (
//					_("You do not have authority to modify %s"), (*iterator)->filename);
				goto cleanup;
			}
		}
	}

	fsync (fdesc);

	retval = TRUE;
cleanup:
	g_free (buffer);
//	funlockfile (outputFile);
	e2_fs_safeclose (fdesc);

	if (retval)
	{
		//rename it (which changes ctime to now)
		gchar *s = _e2pcr_get_tempname (localpath, "ABCDE");
		gchar *t = strrchr (s, G_DIR_SEPARATOR);
		t += sizeof(gchar);
		guint8 iseed = randomval;
		guint8 jseed = (guint8) randomval * 2;
		_e2pcr_crypt_buffer (hashes, &iseed, &jseed, t, (csize_t) strlen (t));
		guchar *p = (guchar *) t;
		while (*p != '\0')
		{
			if (*p < '0')
				*p += '0';
			else
			{
				while (*p > 0x7e)
					*p -= 0x10;
			}
			p += sizeof(gchar);
		}
#ifdef E2_VFS
		VPATH ddata = { s, localpath->spacedata };
		e2_task_backend_move (localpath, &ddata);
#else
		e2_task_backend_move (localpath, s);
#endif
		//mask file m, atimes - random dates in past year
		time_t now = time (NULL);
		struct utimbuf tb;
		tb.modtime = now - 365 * 24 * 3600 * (time_t) randomval / 256;
		_e2pcr_getrandom (&randomval);
		tb.actime = now - 365 * 24 * 3600 * (time_t) randomval / 256;
		while (tb.actime < tb.modtime)
			tb.actime += 3600;
#ifdef E2_VFS
		e2_fs_utime (&ddata, &tb E2_ERR_NONE());
#else
		e2_fs_utime (s, &tb E2_ERR_NONE());
#endif
		//delete
#ifdef E2_VFS
		e2_task_backend_delete (&ddata);
#else
		e2_task_backend_delete (s);
#endif
		g_free (s);
	}

	return retval;
}
/**
@brief encrypt or decrypt file @a localpath
@a localpath may be target of a link, whose path is given in options
Any error message here expects BGL to be open
@param localpath absolute path of item to process, localised string
@param options pointer to process-parameters data

@return OK or YES_TO_ALL if successful, CANCEL or NO_TO_ALL if user chooses those, NO after error
*/
static DialogButtons _e2pcr_crypt1 (VPATH *localpath, E2P_CryptOpts *options)
{
	gchar *s, *newname = NULL;	//warning prevention
	gchar *dir = g_path_get_dirname (VPSTR (localpath));
	gchar *oldname = g_path_get_basename (VPSTR (localpath));
	DialogButtons retval;
	gboolean use_same_name = FALSE;
	gboolean check = (options->backup || options->owrite) ?
		FALSE : e2_option_bool_get ("confirm-overwrite");
#ifdef E2_VFS
	VPATH ddata;
	ddata.spacedata = localpath->spacedata;
#endif

	if (options->decryptmode)	//doing decryption
	{
		//check naming arrangements specified by dialog flags & widgets
		gboolean use_stored_name = FALSE;
		if (options->de_name_same)	//decrypted file name = same as encrypted name
			use_same_name = TRUE;
		else
		{
			if (options->de_name_suffix)
			{
				newname = F_FILENAME_TO_LOCALE (options->de_suffix);
				if (*newname != '\0' && g_str_has_suffix (oldname, newname))
				{
					use_same_name = FALSE;
					gint len = strlen (newname);
					F_FREE (newname);
					newname = g_strdup (oldname);
					*(newname + strlen (newname) - len) = '\0';
				}
				else
				{
					if (*newname != '\0' && !options->ignore_suffix)
					{
						//ask user what to do
						gchar *utf = F_FILENAME_FROM_LOCALE (VPSTR (localpath));
						s = g_strdup_printf (
							_("%s does not end with \"%s\".\nProcess this file anyway?"),
							utf, options->de_suffix);
						retval = _e2pcr_dialog_warning (s, options->multisrc);
						F_FREE (utf);
						g_free (s);
						switch (retval)
						{
							case YES_TO_ALL:
								options->ignore_suffix = TRUE;
							case OK:
								break;
							//case NO_TO_ALL:
//CHECKME consider a flag for stopping, instead of the current spaghetti to provoke a stop
							//case CANCEL:
							default:
								F_FREE (newname);
								g_free (dir);
								g_free (oldname);
								return retval;
						}
					}
					use_same_name = TRUE;
					F_FREE (newname);
				}
			}
			else if (options->de_name_custom)
			{
				if (*options->de_name != '\0')
				{
					newname = D_FILENAME_TO_LOCALE (options->de_name);
					use_same_name = g_str_equal (oldname, newname);
					if (use_same_name)
						g_free (newname);
				}
				else
					use_same_name = TRUE;
			}
			else	//last choice is stored name (which may, later, turn out to be N/A or same)
			{
				use_stored_name = TRUE;
//				use_same_name = TRUE;	//nothing to cleanup afterwards
			}
			//newname now clear or g_freeable
		}

		if (check
			&& !use_same_name	//no point in warning about re-use of same name
			&& !use_stored_name)	//we already know the name of the processed item
		{
			s = g_build_filename (dir, newname, NULL);
#ifdef E2_VFS
			ddata.localpath = s;
			retval = _e2pcr_ow_check (&ddata, options->multisrc);
#else
			retval = _e2pcr_ow_check (s, options->multisrc);
#endif
			g_free (s);
			if (retval == YES_TO_ALL)
				options->owrite = FALSE;
			else if (retval == CANCEL || retval == NO_TO_ALL)
			{
				g_free (dir);
				g_free (oldname);
//				if (!use_same_name)
					g_free (newname);
				return retval;
			}
		}

		retval = _e2pcr_decrypt1 (localpath, dir, oldname, newname,
				use_same_name, check, options);
	}
	else	//doing encryption
	{
		//check naming arrangements specified by dialog flags & widgets
		if (options->en_name_same)	//encrypted file name = same as original name
			use_same_name = TRUE;
		else
		{
			if (options->en_name_suffix)
			{
				if (*options->en_suffix != '\0')
				{
					use_same_name = FALSE;
					s = F_FILENAME_TO_LOCALE (options->en_suffix);
					newname = e2_utils_strcat (oldname, s);
					F_FREE (s);
				}
				else
					use_same_name = TRUE;
			}
			else if (options->en_name_custom)
			{
				if (*options->en_name != '\0')
				{
					newname = D_FILENAME_TO_LOCALE (options->en_name);
					use_same_name = g_str_equal (oldname, newname);
					if (use_same_name)
						g_free (newname);
				}
				else
					use_same_name = TRUE;
			}
			else
				use_same_name = TRUE;	//should never get here
		}

		if (check
			&& !use_same_name)	//no point in warning about re-use of same name
		{
			s = g_build_filename (dir, newname, NULL);
#ifdef E2_VFS
			ddata.localpath = s;
			retval = _e2pcr_ow_check (&ddata, options->multisrc);
#else
			retval = _e2pcr_ow_check (s, options->multisrc);
#endif
			g_free (s);
			if (retval == YES_TO_ALL)
				options->owrite = FALSE;
			else if (retval == CANCEL || retval == NO_TO_ALL)
			{
				g_free (dir);
				g_free (oldname);
//				if (!use_same_name)
					g_free (newname);
				return retval;
			}
		}

		retval = _e2pcr_encrypt1 (localpath, dir, oldname, newname,
			use_same_name, check, options);

	}	//end of encryption-specific section

	g_free (dir);
	g_free (oldname);
//	if (!use_same_name) cleared downstream
//		g_free (newname);

	return retval;
}
/**
@brief callback function for recursive directory processing
This is called for each non-directory item in the directory to be processed.
Treewalk is breadth-first, not physical
@param localpath absolute path of item to copy, localised string
@param statptr pointer to struct stat with info about @a localpath
@param status code from the walker, indicating what type of report it is
@param user_data pointer to user-specified data

@return E2TW_CONTINUE on success, others as appropriate
*/
static E2_TwResult _e2pcr_task_twcb_crypt (VPATH *localpath,
	const struct stat *statptr, E2_TwStatus status, E2P_CryptOpts *user_data)
{
	E2_TwResult retval = E2TW_CONTINUE;
	switch (status)
	{
		DialogButtons cryptresult;
		mode_t mode;
		E2_DirEnt *dirfix;
		GList *member;
#ifdef E2_VFS
		VPATH ddata;
#endif

		case E2TW_DP:	//dir completed
			//revert any altered dir permissions
#ifdef E2_VFS
			ddata.spacedata = localpath->spacedata;
#endif
			mode = statptr->st_mode & ALLPERMS;
			for (member = g_list_last (user_data->dirdata); member != NULL; member = member->prev)
			{
				dirfix = member->data;
				if (dirfix != NULL)
				{
					if (g_str_equal (dirfix->path, VPSTR (localpath)))
					{
#ifdef E2_VFS
						ddata.localpath = dirfix->path;
#endif
						if ((mode & ALLPERMS) != dirfix->mode &&
#ifdef E2_VFS
							e2_fs_chmod (&ddata, dirfix->mode E2_ERR_NONE()))
#else
							e2_fs_chmod (localpath, dirfix->mode E2_ERR_NONE()))
#endif
								retval = E2TW_STOP;	//CHECKME - want cleanup of copied file to continue
						g_free (dirfix->path);
						DEALLOCATE (E2_DirEnt, dirfix);
						user_data->dirdata = g_list_delete_link (user_data->dirdata, member);
						break;
					}
				}
//				else //should never happen CHECKME ok when walking list ?
//					user_data->dirdata = g_list_delete_link (user_data->dirdata, member);
			}
			break;
		case E2TW_DM:	//dir, not opened due to different file system (reported upstream)
		case E2TW_DL:	//dir, not opened due to depth limit (reported upstream)
		case E2TW_DNR:	//unreadable dir (for which, error is reported upstream)
						//eventual chmod for this will probably fail, but try anyhow
//CHECKME report continue after problem
//			retval = E2TW_FIXME;
			break;
		case E2TW_DRR:	//dir now readable
		case E2TW_D:
			//ensure dir is writable, if we can
			if (e2_fs_tw_adjust_dirmode (localpath, statptr, (W_OK | X_OK)) == 0)
				//failed to set missing W and/or X perm
				retval = E2TW_SKIPSUB;	//can't process any item in the dir
				//CHECKME does DP report arrive ?
			else
			{
				//add this dir to list of items to revert afterwards
				//CHECKME what if newmode == oldmode ? (omit == scan full list in DP cb)
				dirfix = ALLOCATE (E2_DirEnt);
				CHECKALLOCATEDWARNT (dirfix, result = E2TW_STOP; break;)
				dirfix->path = g_strdup (VPSTR (localpath));
				dirfix->mode = statptr->st_mode & ALLPERMS;
				user_data->dirdata = g_list_append (user_data->dirdata, dirfix);
			}
			break;
		case E2TW_F:	//not directory or link
			if (S_ISREG (statptr->st_mode))
			{
				struct stat sb;
				user_data->localpath = VPSTR (localpath);	//ok to throw away the original
				sb = *statptr;	//get a non-const statbuf
				user_data->statptr = &sb;
				cryptresult = _e2pcr_crypt1 (localpath, user_data);
				if (cryptresult == NO_TO_ALL || cryptresult == NO) //NO == error
					retval = E2TW_STOP;
			}
			break;
		case E2TW_SL:	//symbolic link
			if (user_data->walklinks)
			{
				//get ultimate target of link
				gchar *target = g_strdup (VPSTR (localpath));
				if (e2_fs_walk_link (&target E2_ERR_NONE()))
				{
					struct stat sb;
#ifdef E2_VFS
					ddata.localpath = target;
					ddata.spacedata = localpath->spacedata;
					if (!e2_fs_stat (&ddata, &sb E2_ERR_NONE()))
#else
					if (!e2_fs_stat (target, &sb E2_ERR_NONE()))
#endif
					{
						user_data->localpath = VPSTR (localpath);	//ok to throw away the original
						user_data->statptr = &sb;
#ifdef E2_VFS
						cryptresult = _e2pcr_crypt1 (&ddata, user_data);
#else
						cryptresult = _e2pcr_crypt1 (target, user_data);
#endif
					}
					else
						cryptresult = NO;
				}
				else
					cryptresult = NO;
				g_free (target);
				if (cryptresult == NO_TO_ALL || cryptresult == NO) //NO == error
					retval = E2TW_STOP;
			}
//		case E2TW_SLN:	//symbolic link targeting non-existent item
//			error message ??
//			retval = E2TW_STOP;
		default:
			break;
	}
	return retval;
}
/**
@brief apply the user's choice to @a localpath
Error messages here and downstream expect BGL open
@param options pointer to process parameters data

@return TRUE if successful
*/
static DialogButtons _e2pcr_apply (E2P_CryptOpts *options)
{
#ifdef E2_VFS
	VPATH ddata;
	ddata.spacedata = options->spacedata;
#endif
	if (S_ISDIR (options->statptr->st_mode))
	{
		if (!options->recurse)
			return CANCEL;
		if ((options->decryptmode && options->de_name_same) ||
			(!options->decryptmode && options->en_name_same))
			return CANCEL;	//CHECKME warning ? ok if only 1 file/link in dir ?
		//the path may be changed in the treewalk
		const gchar *savepath = options->localpath;
		//recursively process dir contents
		//walk-flags for: fix-DNR, no thru-links if appropriate
		E2_TwFlags exec_flags = E2TW_FIXDIR;
		if (!options->walklinks)
			exec_flags |= E2TW_PHYS;

		E2_ERR_DECLARE;

		//CHECKME allow continue after error?
#ifdef E2_VFS
		ddata.localpath = options->localpath;
		gboolean retval = e2_fs_tw (&ddata,
#else
		gboolean retval = e2_fs_tw ((gchar *)options->localpath,
#endif
			_e2pcr_task_twcb_crypt, options, -1, exec_flags E2_ERR_PTR());

		//normally no leftover dir data, but ensure cleanup ...
		GList *member;
		for (member = g_list_last (options->dirdata); member != NULL; member = member->prev)
		{
			E2_DirEnt *dirfix = member->data;
			if (dirfix != NULL)
			{
#ifdef E2_VFS
				ddata.localpath = dirfix->path;
				if (e2_fs_chmod (&ddata, dirfix->mode E2_ERR_PTR()))
#else
				if (e2_fs_chmod (dirfix->path, dirfix->mode E2_ERR_PTR()))
#endif
					retval = FALSE;
				g_free (dirfix->path);
				DEALLOCATE (E2_DirEnt, dirfix);
			}
		}

		//handle errors
		if (!retval
			&& E2_ERR_ISNOT(0))	//might be a user-abort, not an error
		{
#ifdef E2_VFS
			ddata.localpath = savepath;
#endif
			e2_fs_error_local (_("Cannot process all of %s"),
#ifdef E2_VFS
				&ddata E2_ERR_MSGL());
#else
				(gchar *) savepath E2_ERR_MSGL());
#endif
			E2_ERR_CLEAR
		}
		return (retval) ? OK : NO_TO_ALL;	//no need for a simple CANCEL
	}
	else //not a dir
		if (S_ISLNK (options->statptr->st_mode))
	{	//handle links separately, to manage 'look-through'
		if (!options->walklinks)
			return FALSE;
		//get ultimate target and remove any relativity
		DialogButtons cryptresult;
		gchar *target = g_strdup (options->localpath);
		if (e2_fs_walk_link (&target E2_ERR_NONE()))
		{
			struct stat sb;
#ifdef E2_VFS
			ddata.localpath = target;
			if (!e2_fs_stat (&ddata, &sb E2_ERR_NONE()))
#else
			if (!e2_fs_stat (target, &sb E2_ERR_NONE()))
#endif
			{
				options->localpath = target;	//ok to throw away the original
				options->statptr = &sb;
#ifdef E2_VFS
				cryptresult = _e2pcr_crypt1 (&ddata, options);
#else
				cryptresult = _e2pcr_crypt1 (target, options);
#endif
			}
			else
				cryptresult = NO;
		}
		else
			cryptresult = NO;
		g_free (target);
		return cryptresult;
//		return (_e2pcr_crypt1 (target, options) == OK);
	}
	else	//not dir or link
#ifdef E2_VFS
	{
		ddata.localpath = options->localpath;
		return (_e2pcr_crypt1 (&ddata, options));
	}
#else
		return (_e2pcr_crypt1 (options->localpath, options));
#endif
}
/**
@brief determine whether intended change is permitted

@param rt pointer to dialog runtime data struct

@return TRUE if change is permitted, or if current item is a dir with W,X permissions
*/
static gboolean _e2pcr_check_permission (E2P_CryptDlgRuntime *rt)
{
	gchar target[PATH_MAX];
	gchar *newpath, *localname, *dir;
	const gchar *localpath;
	struct stat *sp;
#ifdef E2_VFS
	VPATH ddata;
	ddata.spacedata = rt->opts->spacedata;
#endif
	localpath = rt->opts->localpath;
	sp = rt->opts->statptr;
restart:
#ifdef E2_VFS
	ddata.localpath = localpath;
	if (e2_fs_lstat (&ddata, sp E2_ERR_NONE()))
#else
	if (e2_fs_lstat (localpath, sp E2_ERR_NONE()))
#endif
	{
		rt->opts->permission = FALSE;
		return FALSE;
	}
	if (S_ISLNK (sp->st_mode))
	{
		if ((rt->dlgopen &&
			!gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (rt->linktarget_btn)))
			|| !rt->opts->walklinks)
		{
			rt->opts->permission = FALSE;
			return FALSE;
		}
		gchar *checktarget = g_strdup (localpath);
		if (!e2_fs_walk_link (&checktarget E2_ERR_NONE()))
		{
			g_free (checktarget);
			rt->opts->permission = FALSE;
			return FALSE;
		}

		g_strlcpy (target, checktarget, sizeof (target));
		g_free (checktarget);
		localpath = target;
		goto restart;
	}
	if (S_ISDIR (sp->st_mode))
	{
#ifdef E2_VFS
		rt->opts->permission = !e2_fs_access (&ddata, X_OK | W_OK E2_ERR_NONE());
#else
		rt->opts->permission = !e2_fs_access (localpath, X_OK | W_OK E2_ERR_NONE());
#endif
		return rt->opts->permission;
	}

	dir = g_path_get_dirname (localpath);
	//access _always_ traverses links
#ifdef E2_VFS
	ddata.localpath = dir;
	if (!e2_fs_access (&ddata, X_OK | W_OK E2_ERR_NONE()))
#else
	if (!e2_fs_access (dir, X_OK | W_OK E2_ERR_NONE()))
#endif
	{
		if (rt->opts->decryptmode)
		{
			if (rt->opts->de_name_same)	//decrypted file name = same as encrypted name
#ifdef E2_VFS
			{
				ddata.localpath = localpath;
				rt->opts->permission = !e2_fs_access (&ddata, W_OK E2_ERR_NONE());
			}
#else
				rt->opts->permission = !e2_fs_access (localpath, W_OK E2_ERR_NONE());
#endif
			else
			{
				if (rt->opts->de_name_suffix)
				{
					localname = (rt->dlgopen) ?
						F_FILENAME_TO_LOCALE (
							gtk_entry_get_text (GTK_ENTRY (rt->de_name_suffix_entry))):
						F_FILENAME_TO_LOCALE (rt->opts->de_suffix);
					newpath = g_strdup (localpath);
					if (*localname != '\0' && g_str_has_suffix (newpath, localname))
						*(newpath + strlen (newpath) - strlen (localname)) = '\0';
#ifdef E2_VFS
					ddata.localpath = newpath;
					rt->opts->permission = e2_fs_access (&ddata, F_OK E2_ERR_NONE())
					 || !e2_fs_access (&ddata, W_OK E2_ERR_NONE());
#else
					rt->opts->permission = e2_fs_access (newpath, F_OK E2_ERR_NONE())
					 || !e2_fs_access (newpath, W_OK E2_ERR_NONE());
#endif
					F_FREE (localname);
					g_free (newpath);
				}
				else if (rt->opts->de_name_custom)
				{
					localname = (rt->dlgopen) ?
						F_FILENAME_TO_LOCALE (
							gtk_entry_get_text (GTK_ENTRY (rt->de_name_custom_entry))):
						F_FILENAME_TO_LOCALE (rt->opts->de_name);
					newpath = g_build_filename (dir, localname, NULL);
#ifdef E2_VFS
					ddata.localpath = newpath;
					rt->opts->permission = e2_fs_access (&ddata, F_OK E2_ERR_NONE())
					 || !e2_fs_access (&ddata, W_OK E2_ERR_NONE());
#else
					rt->opts->permission = e2_fs_access (newpath, F_OK E2_ERR_NONE())
					 || !e2_fs_access (newpath, W_OK E2_ERR_NONE());
#endif
					F_FREE (localname);
					g_free (newpath);
				}
				else
					rt->opts->permission = TRUE;
			}
		}
		else	//encrypting
		{
			if (rt->opts->en_name_same)	//encrypted file name = same as original
#ifdef E2_VFS
			{
				ddata.localpath = localpath;
				rt->opts->permission = !e2_fs_access (&ddata, W_OK E2_ERR_NONE());
			}
#else
				rt->opts->permission = !e2_fs_access (localpath, W_OK E2_ERR_NONE());
#endif
			else
			{
				if (rt->opts->en_name_suffix)
				{
					localname = (rt->dlgopen) ?
						F_FILENAME_TO_LOCALE (
							gtk_entry_get_text (GTK_ENTRY (rt->en_name_suffix_entry))):
						F_FILENAME_TO_LOCALE (rt->opts->en_suffix);
					newpath = g_strconcat (localpath, localname, NULL);
#ifdef E2_VFS
					ddata.localpath = newpath;
					rt->opts->permission = e2_fs_access (&ddata, F_OK E2_ERR_NONE())
					 || !e2_fs_access (&ddata, W_OK E2_ERR_NONE());
#else
					rt->opts->permission = e2_fs_access (newpath, F_OK E2_ERR_NONE())
					 || !e2_fs_access (newpath, W_OK E2_ERR_NONE());
#endif
					F_FREE (localname);
					g_free (newpath);
				}
				else if (rt->opts->en_name_custom)
				{
					localname = (rt->dlgopen) ?
						F_FILENAME_TO_LOCALE (
							gtk_entry_get_text (GTK_ENTRY (rt->en_name_custom_entry))):
						F_FILENAME_TO_LOCALE (rt->opts->en_name);
					newpath = g_build_filename (dir, localname, NULL);
#ifdef E2_VFS
					ddata.localpath = newpath;
					rt->opts->permission = e2_fs_access (&ddata, F_OK E2_ERR_NONE())
					 || !e2_fs_access (&ddata, W_OK E2_ERR_NONE());
#else
					rt->opts->permission = e2_fs_access (newpath, F_OK E2_ERR_NONE())
					 || !e2_fs_access (newpath, W_OK E2_ERR_NONE());
#endif
					F_FREE (localname);
					g_free (newpath);
				}
				else
					rt->opts->permission = TRUE;
			}
		}
	}
	else
		rt->opts->permission = FALSE;

	g_free (dir);
	return rt->opts->permission;
}
/**
@brief determine change-permission and set dialog button-sensitivities accordingly

@param rt pointer to dialog data struct

@return
*/
static void _e2pcr_set_buttons (E2P_CryptDlgRuntime *rt)
{
	gboolean ok, encmode, custom;
	ok = _e2pcr_check_permission (rt);
	//enable or disable button(s)
	if (rt->opts->multisrc)
	{
		if (ok)
		{
			//disable yes-to-all button (if any) when a custom name is to be used
			encmode = //(rt->dlgopen) ?
				gtk_toggle_button_get_active
				(GTK_TOGGLE_BUTTON (rt->mode_btn));// : !rt->opts->decryptmode;
			if (encmode)
				custom = //(rt->dlgopen) ?
				gtk_toggle_button_get_active
					(GTK_TOGGLE_BUTTON (rt->en_name_btn_custom));// : rt->opts->en_name_custom ;
			else
				custom =// (rt->dlgopen) ?
				gtk_toggle_button_get_active
					(GTK_TOGGLE_BUTTON (rt->de_name_btn_custom));// : rt->opts->de_name_custom ;
		}
		else
			custom = FALSE;	//warning prevention
		gtk_dialog_set_response_sensitive (GTK_DIALOG (rt->dialog), E2_RESPONSE_YESTOALL, ok & !custom);
	}
	gtk_dialog_set_response_sensitive (GTK_DIALOG (rt->dialog), GTK_RESPONSE_OK, ok);

	ok = !((!rt->opts->decryptmode && rt->opts->en_name_custom)
		|| (rt->opts->decryptmode && rt->opts->de_name_custom));
	gtk_widget_set_sensitive (rt->recurse_btn, ok);
}
/**
@brief key-release callback

@param entry UNUSED the entry widget where the key was pressed
@param event pointer to event data struct
@param rt pointer to data struct for the search

@return FALSE always
*/
static gboolean _e2pcr_keyrel_cb (GtkWidget *entry, GdkEventKey *event,
	E2P_CryptDlgRuntime *rt)
{
	_e2pcr_set_buttons (rt);
	return FALSE;
}
/**
@brief callback for toggle encryption (mode) button

@param widget toggled encrypt button
@param rt pointer to dialog data struct

@return
*/
static void _e2pcr_toggle_mode_cb (GtkWidget *widget, E2P_CryptDlgRuntime *rt)
{
	gboolean state = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget));
	if (state)
	{
		gtk_widget_hide (rt->decryptbox);
		gtk_widget_show (rt->encryptbox);
		gtk_widget_show (rt->confirmbox);
		gtk_widget_show (rt->en_properties_embed_btn);
		gtk_widget_show (rt->compress_btn);
		gtk_widget_hide (rt->properties_btn);
		gtk_widget_set_sensitive (rt->recurse_btn,
			!gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (rt->en_name_btn_custom)));
	}
	else
	{
		gtk_widget_hide (rt->encryptbox);
		gtk_widget_show (rt->decryptbox);
		gtk_widget_hide (rt->confirmbox);
		gtk_widget_hide (rt->en_properties_embed_btn);
		gtk_widget_hide (rt->compress_btn);
		gtk_widget_show (rt->properties_btn);
		gtk_widget_set_sensitive (rt->recurse_btn,
			!gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (rt->de_name_btn_custom)));
	}
//	gtk_widget_set_sensitive (rt->pwrt->pwentry2, state);
	rt->opts->decryptmode = !state;
	rt->pwrt->confirm = state; //make the password dialog check/not for matches

	//determine and handle permission
	_e2pcr_set_buttons (rt);
}
/**
@brief callback for encryped name toggle buttons

@param widget clicked button
@param rt pointer to dialog data struct

@return
*/
static void _e2pcr_toggle_encname_cb (GtkWidget *widget, E2P_CryptDlgRuntime *rt)
{
	if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)))
	{
		if (widget == rt->en_name_btn_suffix)
		{
			gtk_widget_set_sensitive (rt->en_name_suffix_entry, TRUE);
			gtk_widget_set_sensitive (rt->en_name_custom_entry, FALSE);
		}
		else if (widget == rt->en_name_btn_custom)
		{
			gtk_widget_set_sensitive (rt->en_name_custom_entry, TRUE);
			gtk_widget_set_sensitive (rt->en_name_suffix_entry, FALSE);
		}
		else //same name
		{
			gtk_widget_set_sensitive (rt->en_name_suffix_entry, FALSE);
			gtk_widget_set_sensitive (rt->en_name_custom_entry, FALSE);
		}
		gtk_widget_set_sensitive (rt->recurse_btn, widget != rt->en_name_btn_custom);
		//determine and handle permission
		_e2pcr_set_buttons (rt);
	}
}
/**
@brief callback for decryped name toggle buttons
Assumes BGL is off/open
@param widget clicked button
@param rt pointer to dialog data struct

@return
*/
static void _e2pcr_toggle_decname_cb (GtkWidget *widget, E2P_CryptDlgRuntime *rt)
{
	if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)))
	{
		if (widget == rt->de_name_btn_suffix)
		{
			gtk_widget_set_sensitive (rt->de_name_suffix_entry, TRUE);
			gtk_widget_set_sensitive (rt->de_name_custom_entry, FALSE);
		}
		else if (widget == rt->de_name_btn_custom)
		{
			gtk_widget_set_sensitive (rt->de_name_custom_entry, TRUE);
			gtk_widget_set_sensitive (rt->de_name_suffix_entry, FALSE);
		}
		else //same name
		{
			gtk_widget_set_sensitive (rt->de_name_suffix_entry, FALSE);
			gtk_widget_set_sensitive (rt->de_name_custom_entry, FALSE);
		}
		gtk_widget_set_sensitive (rt->recurse_btn, widget != rt->de_name_btn_custom);
		//determine and handle permission
		_e2pcr_set_buttons (rt);
	}
}
/**
@brief handle button click, window-close etc for crypt dialog
This is the callback for "response" signals emitted from @a dialog
@param dialog UNUSED the dialog where the response was generated
@param response the response returned from the dialog
@param rt pointer to data struct for the dialog

@return
*/
static void _e2pcr_response_cb (GtkDialog *dialog, gint response,
	E2P_CryptDlgRuntime *rt)
{
	gboolean finished;
	switch (response)
	{
		case E2_RESPONSE_YESTOALL:
		case GTK_RESPONSE_OK:
			//only end if the password(s) are ok
			finished = e2_password_dialog_confirm (rt->pwrt);
			break;
		default:
			finished = TRUE;
			break;
	}

	if (finished)
	{
		e2_password_dialog_backup (rt->pwrt);	//backup static stuff
		switch (response)
		{
			case E2_RESPONSE_YESTOALL:
				rt->result = YES_TO_ALL;
				break;
			case GTK_RESPONSE_OK:
				rt->result = OK;
				break;
			case E2_RESPONSE_NOTOALL:
				rt->result = NO_TO_ALL;
				break;
			default:
				rt->result = CANCEL;
				break;
		}
		//do not cleanup widgets here - done in main code
		gtk_main_quit ();
	}
}
/**
@brief create and run an encryption-change dialog

@param options pointer to options data struct

@return enumerator corresponding to user's choice of action
*/
static DialogButtons _e2pcr_crypt_dialog_run (E2P_CryptOpts *options)
{
	const gchar *localpath = options->localpath;
	struct stat *sp = options->statptr;
#ifdef E2_VFS
	VPATH ddata = { localpath, options->spacedata };
	if (e2_fs_lstat (&ddata, sp E2_ERR_NONE()))
#else
	if (e2_fs_lstat (localpath, sp E2_ERR_NONE()))
#endif
		return CANCEL;
	if (!(S_ISREG (sp->st_mode) || S_ISDIR (sp->st_mode) || S_ISLNK (sp->st_mode)))
		return CANCEL;

	printd (DEBUG, "create crypt dialog");

	E2P_CryptDlgRuntime crt;
	gchar *pw = NULL;
	//create a temporary container from which we can later plunder some widgets
	GtkWidget *tempbox = gtk_vbox_new (FALSE, 0);
	//create P/W widgets, with both entries, we'll hide one as appropriate
	//CHECKME make it tabular ?
	crt.pwrt = e2_password_dialog_setup (tempbox, TRUE, /*FALSE,*/ NULL, &pw);
	if (crt.pwrt == NULL)
	{
		//FIXME warn about memory error
		gtk_widget_destroy (tempbox);
		return NO_TO_ALL;
	}
	crt.dialog = e2_dialog_create (NULL, NULL, _("en/decrypt file"),
		_e2pcr_response_cb, &crt);

	crt.opts = options;
	gdk_threads_enter ();	//seems more stable with a single lock

	GtkWidget *dialog_vbox = GTK_DIALOG (crt.dialog)->vbox;
	//things that go before the password entries
	GString *label_text = g_string_sized_new (NAME_MAX+20);
	gchar *type;
	switch (sp->st_mode & S_IFMT)
	{
		case S_IFDIR:
			type = _("Directory");
			break;
		case S_IFLNK:
			type = _("Symbolic link");
			break;
//		case S_IFREG:
		default:
			type = _("Filename");
			break;
	}
	gchar *name = g_path_get_basename (localpath);
	gchar *utf = F_FILENAME_FROM_LOCALE (name);	//needed for naming
	g_string_printf (label_text, "%s: <b>%s</b>", type, utf);
	if (S_ISLNK (sp->st_mode))
	{
		gchar *target = g_strdup (localpath);
		if (e2_fs_walk_link (&target E2_ERR_NONE()))
		{
			gchar *utfname = F_DISPLAYNAME_FROM_LOCALE (target);
			g_string_append_printf (label_text, _(" to %s"), utfname);
			F_FREE (utfname);
		}
		g_free (target);
	}
	e2_widget_add_mid_label (dialog_vbox, label_text->str, 0, TRUE, E2_PADDING); //L, R padding
	GtkWidget *hbox = e2_widget_add_box (dialog_vbox, FALSE, 0, FALSE, TRUE, 0);
	//FIXME set initial mode according to extension of item
	crt.mode_btn = e2_button_add_radio (hbox, _("encrypt"), NULL,
		!options->decryptmode, FALSE, 0, NULL, NULL);	//set callback later, after widgets created
	GSList *group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (crt.mode_btn));
	e2_button_add_radio (hbox, _("decrypt"), group, options->decryptmode, FALSE, 0, NULL, NULL);

	crt.encryptbox = gtk_vbox_new (FALSE, 0);
	crt.en_name_btn_same = e2_button_add_radio (crt.encryptbox,
		_("encrypted file will have same name"), NULL, options->en_name_same,
		FALSE, 0, NULL, NULL);	//set callback later, after widgets created;
	hbox = e2_widget_add_box (crt.encryptbox, TRUE, 0, FALSE, FALSE, E2_PADDING);
	group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (crt.en_name_btn_same));
	crt.en_name_btn_suffix = e2_button_add_radio (hbox,
		_("append this to encrypted file name"), group, options->en_name_suffix,
		FALSE, 0, NULL, NULL);

//	printd (DEBUG, "create crypt dialog 3");
//	gdk_threads_enter ();	//latency reduction ? PROBABLY A CRASHER
	crt.en_name_suffix_entry = e2_widget_add_entry (hbox, options->en_suffix,
		TRUE, FALSE);	//no use selecting it
//	gdk_threads_leave ();
	gtk_widget_set_size_request (crt.en_name_suffix_entry, 80, -1);
	gtk_widget_set_sensitive (crt.en_name_suffix_entry, options->en_name_suffix);
//#ifdef E2_ASSISTED
//	e2_widget_set_label_relations (GTK_LABEL (label), crt.en_name_suffix_entry);
//#endif
	//enable permissions change after any change
	g_signal_connect_after (G_OBJECT (crt.en_name_suffix_entry), "key-release-event",
		G_CALLBACK (_e2pcr_keyrel_cb), &crt);

	hbox = e2_widget_add_box (crt.encryptbox, TRUE, 0, FALSE, TRUE, 0);
	group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (crt.en_name_btn_same));
	crt.en_name_btn_custom = e2_button_add_radio (hbox,
		_("encrypted file name will be"), group, options->en_name_custom,
		FALSE, 0, NULL, NULL);
//	printd (DEBUG, "create crypt dialog 4");
//	gdk_threads_enter ();	//latency reduction ? crasher ?
	crt.en_name_custom_entry = e2_widget_add_entry (hbox, utf, TRUE, FALSE);	//no use selecting it
//	gdk_threads_leave ();
//	gtk_widget_set_size_request (crt.en_name_custom_entry, 120, -1);
	gtk_widget_set_sensitive (crt.en_name_custom_entry, options->en_name_custom);
//	printd (DEBUG, "create crypt dialog 4A");
//#ifdef E2_ASSISTED
//	e2_widget_set_label_relations (GTK_LABEL (label), crt.en_name_custom_entry);
//#endif
	g_signal_connect_after (G_OBJECT (crt.en_name_custom_entry), "key-release-event",
		G_CALLBACK (_e2pcr_keyrel_cb), &crt);

	gtk_box_pack_start (GTK_BOX (dialog_vbox), crt.encryptbox, TRUE, TRUE, 0); //top, bottom padding

	crt.decryptbox = gtk_vbox_new (FALSE, 0);
	crt.de_name_btn_same = e2_button_add_radio (crt.decryptbox,
		_("decrypted file will have same name"), NULL, options->de_name_same,
		FALSE, 0, NULL, NULL);	//set callback later, after widgets created;
	group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (crt.de_name_btn_same));
	crt.de_name_btn_stored = e2_button_add_radio (crt.decryptbox,
		_("decrypted file will have embedded name"), group, options->de_name_stored,
		FALSE, 0, NULL, NULL);
	hbox = e2_widget_add_box (crt.decryptbox, TRUE, 0, FALSE, FALSE, 0);
	group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (crt.de_name_btn_same));
	crt.de_name_btn_suffix = e2_button_add_radio (hbox,
		_("strip this from end of decrypted file name"), group, options->de_name_suffix,
		FALSE, 0, NULL, NULL);

//	printd (DEBUG, "create crypt dialog 5");
//	gdk_threads_enter ();	//latency reduction ? crasher ?
	crt.de_name_suffix_entry = e2_widget_add_entry (hbox, options->de_suffix, TRUE, FALSE);
//	gdk_threads_leave ();
	gtk_widget_set_size_request (crt.de_name_suffix_entry, 80, -1);
	gtk_widget_set_sensitive (crt.de_name_suffix_entry, options->de_name_suffix);
//#ifdef E2_ASSISTED
//	e2_widget_set_label_relations (GTK_LABEL (label), crt.de_name_suffix_entry);
//#endif
//	printd (DEBUG, "create crypt dialog 5A");
	g_signal_connect_after (G_OBJECT (crt.de_name_suffix_entry), "key-release-event",
		G_CALLBACK (_e2pcr_keyrel_cb), &crt);

	hbox = e2_widget_add_box (crt.decryptbox, TRUE, 0, FALSE, TRUE, 0);
	group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (crt.de_name_btn_same));
	crt.de_name_btn_custom = e2_button_add_radio (hbox,
		_("decrypted file name will be"), group, options->de_name_custom,
		FALSE, 0, NULL, NULL);
//	printd (DEBUG, "create crypt dialog 6");
//	gdk_threads_enter ();	//latency reduction ?
	crt.de_name_custom_entry = e2_widget_add_entry (hbox, utf, TRUE, FALSE);
//	gdk_threads_leave ();
//	gtk_widget_set_size_request (crt.de_name_custom_entry, 120, -1);
	gtk_widget_set_sensitive (crt.de_name_custom_entry, options->de_name_custom);
//#ifdef E2_ASSISTED
//	e2_widget_set_label_relations (GTK_LABEL (label), crt.de_name_custom_entry);
//#endif
//	printd (DEBUG, "create crypt dialog 6A");
	g_signal_connect_after (G_OBJECT (crt.de_name_custom_entry), "key-release-event",
		G_CALLBACK (_e2pcr_keyrel_cb), &crt);
	gtk_box_pack_start (GTK_BOX (dialog_vbox), crt.decryptbox, TRUE, TRUE, E2_PADDING); //top, bottom padding

	hbox = e2_widget_add_box (dialog_vbox, FALSE, 0, FALSE, TRUE, 0);
	//the existing label in tempbox is centred, might as well set a new one
	e2_widget_add_mid_label (hbox, _("Enter password:"), 0, FALSE, E2_PADDING_SMALL); //L, R padding
//	printd (DEBUG, "create crypt dialog 6B");
	//supply a previously-entered password (but not the confirmation one when encrypting?)

	if (options->plain_pw != NULL)
	{
		gtk_entry_set_text (GTK_ENTRY (crt.pwrt->pwentry1), options->plain_pw);
		gtk_editable_select_region (GTK_EDITABLE (crt.pwrt->pwentry1), 0, -1);
	}
	gtk_widget_reparent (crt.pwrt->pwentry1, hbox);

	crt.confirmbox = e2_widget_add_box (dialog_vbox, FALSE, 0, FALSE, TRUE, 0);
	e2_widget_add_mid_label (crt.confirmbox, _("Confirm password:"), 0, FALSE, E2_PADDING_SMALL); //L, R padding
//	printd (DEBUG, "create crypt dialog 7");
	if (options->plain_pw != NULL)
	{
		gtk_entry_set_text (GTK_ENTRY (crt.pwrt->pwentry2), options->plain_pw);
//		gtk_editable_select_region (GTK_EDITABLE (rt->pwentry2), 0, -1);
	}
	gtk_widget_reparent (crt.pwrt->pwentry2, crt.confirmbox);
	//clear original dialog's box
//	hbox = g_object_get_data (G_OBJECT (rt->dialog), "e2-dialog-hbox");
//	gtk_container_remove (GTK_CONTAINER (dialog_vbox), hbox);
	gtk_widget_destroy (tempbox);	//finished with that now

	crt.properties_btn = e2_button_add_toggle (dialog_vbox, TRUE, options->de_props_stored,
		_("restore properties"), _("decrypted file will have stored owners, permissions and dates"),
		FALSE, 0, NULL, NULL);
	hbox = e2_widget_add_box (dialog_vbox, FALSE, 0, FALSE, TRUE, E2_PADDING_SMALL);
#ifdef E2_MINICRYPT
	crt.compress_btn = e2_button_add_toggle (hbox, TRUE, options->compress,
		_("compress"), _("compress file before encryption"), FALSE, 0, NULL, NULL);
#else
	crt.compress_btn = e2_button_add_toggle (hbox, TRUE,
		(options->compress && (compresslib & E2_CFLAGCOMPRESS)),
		_("compress"), _("compress file before encryption"), FALSE, 0, NULL, NULL);
	if (!(compresslib & E2_CFLAGCOMPRESS))
	{
		options->compress = FALSE;
		gtk_widget_set_sensitive (crt.compress_btn, FALSE);
	}
#endif
	crt.en_properties_embed_btn = e2_button_add_toggle (hbox, TRUE, options->en_properties_embed,
		_("store properties"), _("store current name, permissions etc in the encrypted file"), FALSE, 0, NULL, NULL);
	hbox = e2_widget_add_box (dialog_vbox, FALSE, 0, FALSE, TRUE, E2_PADDING_SMALL);
	crt.backup_btn = e2_button_add_toggle (hbox, TRUE, options->backup,
		_("backup"), _("backup an existing file with the same name as the processed file"), FALSE, 0, NULL, NULL);
	crt.preserve_btn = e2_button_add_toggle (hbox, TRUE, options->preserve,
		_("keep original"), _("do not remove the original file, after processing it"), FALSE, 0, NULL, NULL);
	hbox = e2_widget_add_box (dialog_vbox, FALSE, 0, FALSE, TRUE, E2_PADDING_SMALL);
	crt.linktarget_btn = e2_button_add_toggle (hbox, TRUE, options->walklinks,
		_("through links"), _("if file is a symlink, process its target"), FALSE, 0, NULL, NULL);
	crt.recurse_btn = e2_button_add_toggle (hbox, TRUE, options->recurse,
		_("recurse directories"), NULL, FALSE, 0, NULL, NULL);

	//now it's safe to connect toggle button callbacks
	g_signal_connect (G_OBJECT (crt.mode_btn), "toggled",
		G_CALLBACK (_e2pcr_toggle_mode_cb), &crt);
	g_signal_connect (G_OBJECT (crt.en_name_btn_same), "toggled",
		G_CALLBACK (_e2pcr_toggle_encname_cb), &crt);
	g_signal_connect (G_OBJECT (crt.en_name_btn_suffix), "toggled",
		G_CALLBACK (_e2pcr_toggle_encname_cb), &crt);
	g_signal_connect (G_OBJECT (crt.en_name_btn_custom), "toggled",
		G_CALLBACK (_e2pcr_toggle_encname_cb), &crt);
	g_signal_connect (G_OBJECT (crt.de_name_btn_same), "toggled",
		G_CALLBACK (_e2pcr_toggle_decname_cb), &crt);
	g_signal_connect (G_OBJECT (crt.de_name_btn_suffix), "toggled",
		G_CALLBACK (_e2pcr_toggle_decname_cb), &crt);
	g_signal_connect (G_OBJECT (crt.de_name_btn_custom), "toggled",
		G_CALLBACK (_e2pcr_toggle_decname_cb), &crt);
	g_signal_connect (G_OBJECT (crt.de_name_btn_stored), "toggled",
		G_CALLBACK (_e2pcr_toggle_decname_cb), &crt);

	if (options->decryptmode)
	{
		gtk_widget_show (crt.decryptbox);
		gtk_widget_hide (crt.confirmbox);
		gtk_widget_hide (crt.compress_btn);
		gtk_widget_hide (crt.en_properties_embed_btn);
	}
	else
	{
		gtk_widget_show (crt.encryptbox);
		gtk_widget_hide (crt.properties_btn);
	}

	if (options->multisrc)
	{
		e2_dialog_add_defined_button (crt.dialog, &E2_BUTTON_NOTOALL);
		e2_dialog_add_defined_button (crt.dialog, &E2_BUTTON_YESTOALL);
	}
//	E2_BUTTON_CANCEL.showflags |= E2_BTN_DEFAULT;
	E2_BUTTON_OK.showflags &= ~E2_BTN_DEFAULT;

	e2_dialog_add_defined_button (crt.dialog, &E2_BUTTON_CANCEL);
	e2_dialog_add_defined_button (crt.dialog, &E2_BUTTON_OK);

	_e2pcr_set_buttons (&crt);

	crt.pwrt->confirm = !options->decryptmode; //make the password dialog check/not for matches
	crt.dlgopen = TRUE;

	e2_dialog_setup (crt.dialog, app.main_window);

	//gboolean QQQQcheckmutexok2;
	gdk_threads_leave();

//		pthread_mutex_lock (&pw_mutex);
/*		if (window_width == -1)
		window_width = MIN(400, app.window.panes_paned->allocation.width*2/3);
	gtk_window_resize (GTK_WINDOW(rt->dialog), window_width, -1);
*/
//		g_static_rec_mutex_unlock (&pw_mutex);
//		pthread_mutex_unlock (&pw_mutex);
	gdk_threads_enter ();
	e2_dialog_run (crt.dialog, app.main_window, E2_DIALOG_DONT_SHOW_ALL);
	gtk_widget_grab_focus (crt.pwrt->pwentry1);
	gtk_main ();
//		gtk_widget_hide (rt->dialog); CRASHER
	gdk_threads_leave ();

//		gdk_threads_enter ();
//		gtk_widget_hide (rt->dialog);
//		gdk_threads_leave ();

	DialogButtons retval = crt.result;
	//remember most of the current dialog data
	options->decryptmode =
		!gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (crt.mode_btn));
	options->en_name_same =		//encrypted file name = same as onencrypted name
		gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (crt.en_name_btn_same));
	options->en_name_suffix =	//encrypted file name has user-specified suffix (if any)
		gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (crt.en_name_btn_suffix));
	options->en_name_custom =	//encrypted file name = user-specified
		gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (crt.en_name_btn_custom));
//	options->en_name_embed =	//store filenama in encrypted file
//		gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (crt.en_name_embed_btn));
	options->en_properties_embed =	//store filenama and statbuf in encrypted file
		gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (crt.en_properties_embed_btn));

	options->de_name_same =		//decrypted file name = same as encrypted name
		gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (crt.de_name_btn_same));
	options->de_name_stored =	//decrypted file name = embedded original name
		gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (crt.de_name_btn_stored));
	options->de_name_suffix =	//decrypted file name omits user-specified suffix (if any)
		gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (crt.de_name_btn_suffix));
	options->de_name_custom =	//decrypted file name = user-specified
		gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (crt.de_name_btn_custom));
	options->de_props_stored =	//reinstate other properties of original file
		gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (crt.properties_btn));

	options->compress =	//compress file before encryption
		gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (crt.compress_btn));
	options->backup =	//preserve any file with same name as specified for the [de]crypted file
		gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (crt.backup_btn));
	options->preserve =	//preserve the file to be [de]crypted, with alternate name if appropriate
		gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (crt.preserve_btn));
	options->recurse =	//recursively process all files in any selected dir
		gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (crt.recurse_btn));
	options->walklinks =//process link targets
		gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (crt.linktarget_btn));

	if (options->en_name != NULL)
		g_free (options->en_name);
	options->en_name = g_strdup (gtk_entry_get_text (GTK_ENTRY (crt.en_name_custom_entry)));	//user-specified suffix, freeable utf-8
	if (options->en_suffix != NULL)
		g_free (options->en_suffix);
	options->en_suffix = g_strdup (gtk_entry_get_text (GTK_ENTRY (crt.en_name_suffix_entry)));	//user-specified suffix, freeable utf-8
	if (options->de_name != NULL)
		g_free (options->de_name);
	options->de_name = g_strdup (gtk_entry_get_text (GTK_ENTRY (crt.de_name_custom_entry)));	//user-specified suffix, freeable utf-8
	if (options->de_suffix != NULL)
		g_free (options->de_suffix);
	options->de_suffix = g_strdup (gtk_entry_get_text (GTK_ENTRY (crt.de_name_suffix_entry)));	//user-specified suffix, freeable utf-8

	//remember password only if a single-item is to be processed,
	//and permission is ok and password is not empty
	if (retval == OK || retval == YES_TO_ALL || (retval == CANCEL && options->multisrc))
	{
		pw = *(crt.pwrt->passwd);
		if (pw == NULL || *pw == '\0')
			retval = CANCEL;
		else
		{
			if (retval != CANCEL)
			{
				_e2pcr_check_permission (&crt);
				if (!options->permission)
					retval = CANCEL;
			}
			if (options->plain_pw != NULL)
				g_free (options->plain_pw);
			options->plain_pw = pw;
		}
	}
	if (!(retval == OK || retval == YES_TO_ALL || (retval == CANCEL && options->multisrc)))
	{
		if (options->plain_pw != NULL)
		{
			g_free (options->plain_pw);
			options->plain_pw = NULL;
		}
	}

	F_FREE (utf);
	g_free (name);

	gdk_threads_enter ();
	gtk_widget_destroy (crt.dialog);
	gdk_threads_leave ();
	//do not free p/w string - it's saved at options->plain_pw
	DEALLOCATE (E2_PWDataRuntime, crt.pwrt);
	return retval;
}

/**
@brief encryt or decrypt selected item(s) in active pane
If > 1 item is selected, a dialog is created for each such item in turn (or
until the user chooses stop or apply-to-all)
@param from the button, menu item etc which was activated
@param art action runtime data

@return TRUE if action completed successfully, else FALSE
*/
static gboolean _e2p_task_docrypt (gpointer from, E2_ActionRuntime *art)
{
	return (e2_task_enqueue_task (E2_TASK_CRYPT, art, from,
		_e2p_task_docryptQ, e2_task_refresh_lists));
}
static gboolean _e2p_task_docryptQ (E2_ActionTaskData *qed)
{
	//printd (DEBUG, "task: crypt");
#ifdef E2_VFSTMP
	if (qed->currspace != NULL)	//not a local space
		return FALSE;
#endif

	struct stat sb;	//statbuf for general use
	E2P_CryptOpts options = session_opts;
	options.permission = FALSE;
	options.ignore_suffix = FALSE;
	options.owrite = FALSE;
	options.en_suffix = g_strdup (session_opts.en_suffix);
	options.de_suffix = g_strdup (session_opts.de_suffix);
	options.plain_pw = NULL;	//probably redundant
	options.statptr = &sb;
	options.dirdata = NULL;
#ifdef E2_VFS
	options.spacedata = qed->currspace;
#endif

	GPtrArray *names = qed->names;
	options.multisrc = names->len > 1;
	gchar *curr_local = qed->currdir;
	guint count;
	gboolean all = FALSE;
	E2_SelectedItemInfo **iterator = (E2_SelectedItemInfo **) names->pdata;
	//guess the initial mode
	//too bad if 1st item is a dir with the expected suffix
	gchar *utf = F_FILENAME_TO_LOCALE ((*iterator)->filename);
	options.decryptmode = (*session_opts.de_suffix != '\0' && g_str_has_suffix (utf, session_opts.de_suffix));
	F_FREE (utf);
	GString *path = g_string_sized_new (PATH_MAX);

#ifdef E2_REFRESH_DEBUG
	printd (DEBUG, "disable refresh, crypt task");
#endif
	e2_filelist_disable_refresh ();
	e2_task_advise ();

#ifdef E2_VFS
	VPATH ddata;
	ddata.spacedata = qed->currspace;
#endif

	for (count = 0; count < names->len; count++, iterator++)
	{
		DialogButtons choice;
		//".." entries filtered when names compiled
//FIXME for single-setup: instead of the following, adjust file details in dialog, reset default button
//		gchar *itempath = e2_utils_dircat (curr_view, (*iterator)->filename, TRUE);
		g_string_printf (path, "%s%s", curr_local, (*iterator)->filename); //separator comes with dir
		options.localpath = path->str;
		if (all)
		{
			//check if we have permission to change this item
			E2P_CryptDlgRuntime crt;
			crt.opts = &options;
			crt.dlgopen = FALSE;
			if (_e2pcr_check_permission (&crt))
				choice = OK;
			else
			{
#ifdef E2_VFS
				ddata.localpath = (*iterator)->filename;
#endif
				e2_fs_error_simple (
					_("You do not have authority to modify %s"),
#ifdef E2_VFS
					&ddata);
#else
					(*iterator)->filename);
#endif
				choice = CANCEL;
			}
		}
		else
		{
#ifdef E2_REFRESH_DEBUG
			printd (DEBUG, "enable refresh, encrypt dialog");
#endif
			e2_filelist_enable_refresh ();  //allow updates while we wait
			*qed->status = E2_TASK_PAUSED;
			choice = _e2pcr_crypt_dialog_run (&options);
			*qed->status = E2_TASK_RUNNING;
#ifdef E2_REFRESH_DEBUG
			printd (DEBUG, "disable refresh, encrypt dialog");
#endif
			e2_filelist_disable_refresh ();
		}

		switch (choice)
		{
		  case YES_TO_ALL:
			all = TRUE;
//			myuid = getuid ();  //do this once, for speed
			choice = OK;
		  case OK:
			if (options.permission)	// && (axs_changes != NULL || def_changes != NULL))
			{
#ifdef E2_INCLIST
				if ((choice = _e2pcr_apply (&options)) == OK)
				{
					//FIXME update line in treeview
				}
#else
# ifdef E2_FAM
				choice = _e2pcr_apply (&options);
# else
				if ((choice = _e2pcr_apply (&options)) == OK)
#  ifdef E2_VFS
				{
					if (ddata.spacedata == NULL)
					{
						ddata.localpath = qed->currdir;
						e2_fs_touchnow (&ddata E2_ERR_NONE());
					}
				}
#  else
				//make the file-list refresher notice successful change
					e2_fs_touchnow (qed->currdir E2_ERR_NONE());
#  endif
# endif
#endif
/*				if (!all)
				{
					CLEANUPS
				}
*/
			}
		  case CANCEL:
			break;
		  default:
			choice = NO_TO_ALL;  // break flag;
			break;
		}
		if (choice == NO_TO_ALL)
			break;
	}

	//backup relevant last-used option data
	g_free (session_opts.en_suffix);
	g_free (session_opts.de_suffix);
	if (options.en_name != NULL)
	{
		g_free (options.en_name);
		options.en_name = NULL;
	}
	if (options.de_name != NULL)
	{
		g_free (options.de_name);
		options.de_name = NULL;
	}
	if (options.plain_pw != NULL)
	{
		g_free (options.plain_pw);
		options.plain_pw = NULL;
	}
	session_opts = options;

	g_string_free (path, TRUE);
	e2_window_clear_status_message ();
#ifdef E2_REFRESH_DEBUG
	printd (DEBUG, "enable refresh, acl task");
#endif
	e2_filelist_enable_refresh ();

	return TRUE;
}

//aname must be confined to this module
static gchar *aname;
static gpointer libhandle;

/**
@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 "crypt"
	aname = _("crypt");

	p->signature = ANAME VERSION;
	p->menu_name = _("_En/decrypt..");
	p->description = _("Encrypt or decrypt selected items");
	p->icon = "plugin_"ANAME"_"E2IP".png";  //use icon file pathname if appropriate

	if (p->action == NULL)
	{
		//no need to free this
		gchar *action_name = g_strconcat (_A(5),".",aname,NULL);
		p->action = e2_plugins_action_register (action_name, E2_ACTION_TYPE_ITEM,
			_e2p_task_docrypt, NULL, FALSE, 0, NULL);

		session_opts.en_suffix = g_strdup (".enc");	//no translation
		session_opts.de_suffix = g_strdup (".enc");	//no translation

		//setup for default compression (and incidentally, decompression)
		//first try, fastest performer, lzo
		if ((libhandle = dlopen ("liblzo2.so.2", RTLD_LAZY)) != NULL)
		{
			init_compress = dlsym (libhandle, "__lzo_init_v2");	//a #define in lzoconf.h
			if (init_compress != NULL)
			{
				compress_buf = dlsym (libhandle, "lzo1x_1_compress");
				if (compress_buf != NULL)
				{
					decompress_buf = dlsym (libhandle, "lzo1x_decompress_safe");
					if (decompress_buf != NULL)
						compresslib = E2_CFLAGCOMPRESS | E2_CFLAGLZO;
					else
					{
						init_compress = NULL;
						compress_buf = NULL;
					}
				}
				else
					init_compress = NULL;
			}
			if (compresslib == E2_CFLAGNONE)
				dlclose (libhandle);
		}

		//second, intermediate speed, libz
		if (compresslib == E2_CFLAGNONE &&
			(libhandle = dlopen ("libz.so.1", RTLD_LAZY)) != NULL)
		{
			compress_buf = dlsym (libhandle, "compress2");
			if (compress_buf != NULL)
			{
				decompress_buf = dlsym (libhandle, "uncompress");
				if (decompress_buf != NULL)
					compresslib = E2_CFLAGCOMPRESS | E2_CFLAGZ;
				else
					compress_buf = NULL;
			}
			if (compresslib == E2_CFLAGNONE)
				dlclose (libhandle);
		}

		//last, libbz2
		if (compresslib == E2_CFLAGNONE &&
			(libhandle = dlopen ("libbz2.so.1", RTLD_LAZY)) != NULL)
		{
			compress_buf = dlsym (libhandle, "BZ2_bzBuffToBuffCompress");
			if (compress_buf != NULL)
			{
				decompress_buf = dlsym (libhandle, "BZ2_bzBuffToBuffDecompress");
				if (decompress_buf != NULL)
					compresslib = E2_CFLAGCOMPRESS | E2_CFLAGBZ2;
				else
					compress_buf = NULL;
			}
			if (compresslib == E2_CFLAGNONE)
			{
				dlclose (libhandle);
				libhandle = NULL;	//don't do anything when plugin closes
			}
		}
		//no flags set in compresslib if nothing found
		printd (DEBUG, "masked lib-flags %0x", compresslib & E2_CFLAGLIBMASK);
		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(5),".",aname,NULL);
	gboolean ret =
	 e2_plugins_action_unregister (action_name);
	g_free (action_name);
	if (ret)
	{
		g_free (session_opts.en_suffix);
		g_free (session_opts.de_suffix);

		if (libhandle != NULL)	//disconnect from the compression library
			dlclose (libhandle);
	}
	return ret;
}
