/* $Id: e2_fs_walk.c 744 2007-11-22 21:36:34Z tpgww $

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

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

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

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

/**
@file src/filesystem/e2_fs_walk.c
@brief tree-walk functionality, like nftw() but with user-data and additional flags
*/

/**
\page treewalks recursive file operations

Function e2_fs_tw() is comparable to nftw(), but with user data (to avoid global
variables in callback functions, so threads can be used) and extra parameters
(types of status-report provided to callback functions and types of return flags
from callback functions).

e2_fs_tw() may be called recursively, from any downstream function.

To instruct e2_fs_tw() how to perform its function, you may supply any one or more
of these flags:
E2TW_PHYS     perform physical walk, don't look though symlinks
E2TW_MOUNT    report only items on same file system as the starting item
E2TW_DEPTH    open and process any subdir as soon as it's found, instead of after
				all non-subdirs have been processed (UNUSED)
E2TW_ONLYDIR  no F, SL or SLN reports - report only directories and stat() fails
E2TW_NODIR    no D, DRR or DP reports - report only non-directories and
				stat() fails (NS) and dirs not-opened (DL, DM, DNR)
E2TW_FIXDIR   if possible, repair DNR situations and then report DRR instead of DNR
E2TW_QT       suppress in-walker error messages e.g. after intentional DL
E2TW_XQT      suppress ALL in-walker error messages
E2TW_XERR     (when E2_VFS is defined) the first item in user_data is a GError**
				for returning downstream error data
E2TW_DC       issue a DP report after some sorts of in-walker errors, and after
			  a callback returns STOP or a nested call returns STOP

Only the first 3 of these are in nftw(). Unlike nftw(), there is no CHDIR option,
as that's bad for multi-app and multi-thread contexts. So absolute paths must
always be used.

The effect of E2TW_DEPTH is really only on the order of "discovery", and so, on which
items have been processed after an error causes premature termination of a walk.
At this time, no walk is depth-first, and the code for that is disabled.

As appropriate, status report code(s) provided to the specified callback function
will be:
E2TW_F item is not a directory or link
E2TW_SL symbolic link to a file other than a directory
E2TW_SLN symbolic link whose target file is non-existent
E2TW_D	directory
E2TW_DL directory, not opened due to tree-depth limit
E2TW_DM directory, not opened due to different file system
E2TW_DNR unreadable directory
E2TW_DP directory processed (all its contents have been reported)
E2TW_NS un-stattable item
E2TW_DRR formerly-unreadable directory, now repaired

E2TW_D or E2TW_DRR, and E2TW_DP, will both be reported for each _opened_ directory
(unless processing is aborted by a STOP or SKIP_SUBTREE message from the callback,
from the D or DRR report, or aborted by some error in the walker itself).
E2TW_DP will not be reported after E2TW_DL, E2TW_DM or E2TW_DNR reports.
After a E2TW_DRR report, a DRRKEEP message has the same effect as CONTINUE

If a walk is aborted without a E2TW_DP report, any data parked by the callback
will leak unless it's cleaned by the parent routine, after the walk is finished.
Callbacks can report E2TW_CLEAN, which (after a F, SL, SLN, D or DRR report) will
trigger a DP callback and then stop. In a depth-first walk at least, that will
probably still leak unless there is a post-walk cleanup.
[Any??] in-walker error during a breadth-first walk will trigger a [DP] report
to allow local cleanup, and then stop.

Since the order of opening items in a dir is arbitrary, in breadth-first mode the
the walker defers processing of all subdirs until all non-subdirs are done.

e2_fs_tw() assumes that gtk's BGL is open/off, for the purposes of managing
display updating (error messages etc). Downstream callbacks must assume that too.

Error messages are displayed by the walker when E2TW_DL, E2TW_DM, E2TW_DNR,
E2TW_NS conditions arise, among other things, a relevant "quiet" flag applies
to the walk.
*/

#include "e2_fs.h"
#include <string.h>

/* twstat pointer to function to be used for stat oprations
(e2_fs_lstat() for E2TW_PHYS walks, e2_fs_stat() for others)
cbfunc pointer to the function to be called for each located item
user_data void pointer to user-specified data to be supplied to cbunc each
   time it's called (with GError** as its first member if E2TW_XERR is set)
max_depth the largest number of tree levels to descend, or -1 for no limit
exec_flags flags dictating the way the walk is to be performed
device the system on which the starting item resides
original_path newly-allocated localised path string, = CWD when the walk is started
*/
typedef struct _E2_TwData
{
#ifdef E2_VFS
	gint (*twstat) (VPATH *, struct stat *, GError **);
#else
	gint (*twstat) (const gchar *, struct stat *);
#endif
	E2_TwResult (*cbfunc) (VPATH *, const struct stat *, E2_TwStatus, gpointer);
	gpointer user_data;
	gint max_depth;	//-1 for unlimited
	E2_FsReadWatch watchdir;
	E2_TwFlags exec_flags;
	dev_t device;
} E2_TwData;

/**
@brief add to @a localpath any permission(s) in @a howflags that do not already apply

Intended mainly for dirs but will work for non-dirs too
Any error message expects BGL open
NOTE for GRP and OTH this does not work, for dirs at least
@param localpath absolute path of item to process, localised string
@param statptr pointer to statbuf for @a localpath, as provided to the treewalk cb func
@param howflags desired permission flags, R_OK W_OK and/or X_OK

@return mode of @a localpath, not masked with ALLPERMS, maybe same as current mode, 0 after error
*/
mode_t e2_fs_tw_adjust_dirmode (VPATH *localpath, const struct stat *statptr,
	gint howflags)
{
	E2_ERR_DECLARE
	if (!e2_fs_access3 (localpath, howflags E2_ERR_PTR()))
		return statptr->st_mode;
	E2_ERR_CLEAR

	mode_t pflags = 0;
	uid_t id = getuid ();
	if (id == 0 || id == statptr->st_uid)
	{
		if (howflags & R_OK) pflags |= S_IRUSR;
		if (howflags & W_OK) pflags |= S_IWUSR;
		if (howflags & X_OK) pflags |= S_IXUSR;
	}
	//NB for GRP and OTH this does not work, for dirs at least
	else if ((statptr->st_mode & S_IWGRP) //must already be group-writable for a member to change it
		&& e2_fs_ingroup (statptr->st_gid))
	{
		if (howflags & R_OK) pflags |= S_IRGRP;
		if (howflags & X_OK) pflags |= S_IXGRP;
	}
	else if (statptr->st_mode & S_IWOTH)//must already be other-writable for another to change it
	{
		if (howflags & R_OK) pflags |= S_IROTH;
		if (howflags & X_OK) pflags |= S_IXOTH;
	}

	mode_t newmode = 0;	//assignment for warning prevention
	if (pflags != 0)
		newmode = statptr->st_mode | pflags;
	if (pflags == 0	//nothing to change
		|| e2_fs_chmod (localpath, newmode & ALLPERMS E2_ERR_PTR()))
	{
		//X permission on dir when trying to process its contents
		gchar *fmt = ((howflags & X_OK) && S_ISDIR (statptr->st_mode)) ?
			_("Cannot change anything in %s") : _("Cannot change permissions of %s");
		e2_fs_error_local (fmt, localpath E2_ERR_MSGL());
		E2_ERR_CLEAR
		newmode = 0;
	}
	return newmode;
}
/**
@brief determine the appropriate status report code for link @a local

With relative symlinks, stat() generally fails, and anyway, can't distinguish
valid links from hanging ones. Nor can lstat().
So this uses a kludgy but robust alternative approach

@param localpath absolute path of link-item to process, localised string

@return callbck status code in accordance with whether the link is valid
*/
static E2_TwResult _e2_fs_tw_link (VPATH *localpath)
{
	E2_TwStatus code;
	gchar buf[PATH_MAX];
	gint read = e2_fs_readlink (localpath, buf, sizeof (buf) E2_ERR_NONE());
	if (read == -1)
	{
		code = E2TW_SLN;
	}
	else
	{
		buf[read] = '\0';
		gchar *converted1 = F_FILENAME_FROM_LOCALE (VPSTR(localpath));
		gchar *converted2 = F_FILENAME_FROM_LOCALE (buf);
		gchar *targetpath = e2_utils_translate_relative_path (converted1, converted2);
		F_FREE (converted1);
		F_FREE (converted2);
		//targetpath has a trailing /, with which access() can't cope
		*(targetpath + strlen (targetpath) - sizeof (gchar)) = '\0';

		converted1 = F_FILENAME_TO_LOCALE (targetpath);
		//access() looks through links
#ifdef E2_VFS
		VPATH data = { converted1, localpath->spacedata };
		code = (e2_fs_access (&data, F_OK E2_ERR_NONE())) ? E2TW_SLN : E2TW_SL;
#else
		code = (e2_fs_access (converted1, F_OK E2_ERR_NONE())) ? E2TW_SLN : E2TW_SL;
#endif
		g_free (targetpath);
		F_FREE (converted1);
	}
	return code;
}
/**
@brief process 'non-directory' item @a localpath, as part of a treewalk

This determines the appropriate status report code for the callback, then calls it
@a localpath may be the path of a link, regular file, etc
@a localpath will have been stat'd or lstat'd (depending on walk-flag E2TW_PHYS)
into @a statptr, before this function is called

@param localpath absolute path of item to process, localised string
@param statptr pointer to struct stat used for stating @a local
@param twdata pointer to struct with 'constant' data for each call here

@return result code in accordance with callback-function response
*/
static E2_TwResult _e2_fs_tw_nondir (VPATH *localpath, const struct stat *statptr,
	E2_TwData *twdata)
{
	E2_TwStatus code;
	if (twdata->exec_flags & E2TW_PHYS)
	{
		//the prior lstat did not look through a link
		//or the local lstat failed
		//in general, relative links (valid or not) will end up here
		code = (S_ISLNK (statptr->st_mode)) ? _e2_fs_tw_link (localpath) : E2TW_F;
	}
	else
	{
		//the prior stat() processed a non-link,
		//or might, or might not, have successfully processed a link
		//check if it was a link
		struct stat statbuf2;	//don't disturb statptr
		if (e2_fs_lstat (localpath, &statbuf2 E2_ERR_NONE()))
			code = E2TW_NS; //lstat failed
		else
			code = (S_ISLNK (statbuf2.st_mode)) ? _e2_fs_tw_link (localpath) : E2TW_F;
	}

	return ((*twdata->cbfunc) (localpath, statptr, code, twdata->user_data));
}
/**
@brief process directory @a localpath, as part of a treewalk

if possible, and in accord with the walk-mode determined by flags in @a twdata,
@a localpath is opened and its contents are listed and each listed item is
'reported' to the callback function recorded in @a twdata.
Any subdir found among the contents will recurse into this function.
Non-dirs are handled by another function, before coming here or when found here.

@param localpath absolute path of _directory_ to process, localised string, with or without trailing "/"
@param depth enumator of current parent's level in the tree (relative
     to the original parent), 0 to max_depth (if the latter is not -1)
@param twdata pointer to struct with walk-parameters including callback and its data

@return result code in accordance with callback-function returns, E2TW_STOP or E2TW_CONTINUE if all was well
*/
static E2_TwResult _e2_fs_tw_dir (VPATH *localpath, gint depth, E2_TwData *twdata)
{
	//FIXME support an exit-code for cleanup-this-level then stop
	E2_TwResult result;
	gint newdepth, localpathlen;
	guint pathbuflen, bufspace;
	//CHECKME want these flags to be variable by any callback function ?
	gboolean reportdirs = !(twdata->exec_flags & E2TW_NODIR);
	gboolean reportnondirs = !(twdata->exec_flags & E2TW_ONLYDIR);

	gboolean repaired = FALSE;	//unreadable dir has been fixed
	mode_t firstmode = 0;	//original mode of an un-processable dir
	mode_t newmode = 0;	//fixed mode of an un-processable dir, for possible reinstatement

	gchar *itempath, *itemname;
	GList *entries, *parked_dirs, *member;
#ifdef E2_VFS
	VPATH ddata;
	GError **callerr;
	E2_ERR_DECLARE
#endif
	struct stat statbuf;	//assume it's ok to recursively stack this much data

	//populate a local statbuf, if we can
	if ((*twdata->twstat) (localpath, &statbuf E2_ERR_PTR()))
	{	//failed (statbuf will be garbage for this call)
		//since cbfunc doesn't have access to the error data, as a
		//convenience we report to the user unless instructed otherwise
		if (!(twdata->exec_flags & E2TW_XQT))
			e2_fs_error_local (_("Cannot get information about %s"),
				localpath E2_ERR_MSGL());

		result = (*twdata->cbfunc) (localpath, &statbuf, E2TW_NS, twdata->user_data);

#ifdef E2_VFS
		if (twdata->exec_flags & E2TW_XERR)
		{
			callerr = (GError **)twdata->user_data;
			if (callerr != NULL && *callerr == NULL)	//valid, unused error data
				*callerr = E2_ERR_NAME;
		}
		else
			E2_ERR_CLEAR
#endif
		//for an un-stattable item, bias to STOP
		return ((result == E2TW_CONTINUE) ? E2TW_CONTINUE : E2TW_STOP);
	}
	//check whether it's ok to open the dir
	if ((twdata->exec_flags & E2TW_MOUNT) && twdata->device != statbuf.st_dev)
	{
		if (!(twdata->exec_flags & (E2TW_QT | E2TW_XQT)))
			e2_fs_error_simple (_("Directory %s not opened"), localpath);
		result = (*twdata->cbfunc) (localpath, &statbuf, E2TW_DM, twdata->user_data);
		return ((result & E2TW_STOP) ? E2TW_STOP : E2TW_CONTINUE); //unwanted filesystem, would expect normally to continue
	}
	newdepth = depth + 1;
	if (twdata->max_depth != -1 && newdepth > twdata->max_depth)
	{
		if (!(twdata->exec_flags & (E2TW_QT | E2TW_XQT)))
			e2_fs_error_simple (_("Directory %s not opened"), localpath);
		result = (*twdata->cbfunc) (localpath, &statbuf, E2TW_DL, twdata->user_data);
		return ((result & E2TW_STOP) ? E2TW_STOP : E2TW_CONTINUE); //max depth reached, would expect normally to continue
	}

	//create a re-usable path-string buffer
	//since this function may recurse indefinitely, buffer is on heap, not stack
	localpathlen = strlen (VPSTR(localpath));
	//space to append a FileInfo->filename (NAME_MAX+1), plus a few chars (/ or 0)
	pathbuflen = (localpathlen + NAME_MAX + 1)/8*8 + 8;
#ifdef USE_GLIB2_10
	itempath = g_slice_alloc ((gulong) pathbuflen);
#else
	itempath = g_try_malloc ((gulong) pathbuflen);
#endif
//	CHECKALLOCATEDWARNT (itempath, return E2TW_STOP;)
//CHECKME support more-complex response(s) to this condition ?
	if (itempath == NULL)
	{
		gdk_threads_enter ();
		e2_utils_show_memory_message ();
		gdk_threads_leave ();
		return E2TW_STOP;
	}

	//get list of item-names in the dir
	entries = (GList *)e2_fs_dir_foreach (localpath, twdata->watchdir,
		NULL, NULL, NULL E2_ERR_PTR());
	//check whether it was possible to open the dir
	if (E2DREAD_FAILED (entries))
	{
		if (twdata->exec_flags & E2TW_FIXDIR)
		{	//we want to try fixing DNR instances
			firstmode = statbuf.st_mode;
			newmode = e2_fs_tw_adjust_dirmode (localpath, &statbuf, (R_OK | X_OK));
			if (newmode != firstmode && newmode != 0)
			{	//try again
				entries = (GList *)e2_fs_dir_foreach (localpath, twdata->watchdir,
					NULL, NULL, NULL E2_ERR_PTR());
			}
		}
		//was 1st or possible 2nd attempted read successful ?
		if (E2DREAD_FAILED (entries))
		{
			if (!(twdata->exec_flags & E2TW_XQT))
				e2_fs_error_local (_("Cannot open directory %s"),
					localpath E2_ERR_MSGL());

			result = (*twdata->cbfunc) (localpath, &statbuf, E2TW_DNR, twdata->user_data);

#ifdef E2_VFS
			if (twdata->exec_flags & E2TW_XERR)
			{
				callerr = (GError **)twdata->user_data;
				if (callerr != NULL && *callerr == NULL)	//valid, unused error data
					*callerr = E2_ERR_NAME;
			}
			else
				E2_ERR_CLEAR
#endif
#ifdef USE_GLIB2_10
			g_slice_free1 (pathbuflen, itempath);
#else
			g_free (itempath);
#endif
			return ((result & E2TW_STOP) ? E2TW_STOP : E2TW_CONTINUE);
		}
		repaired = TRUE;
	}

	if (reportdirs)
	{
		result = (*twdata->cbfunc) (localpath, &statbuf,
			(repaired) ? E2TW_DRR : E2TW_D, twdata->user_data);
/*		if (result & E2TW_CLEAN)	//CHECKME is this reasonable after a D/DRR ?
		{
			result |=
			((*twdata->cbfunc) (localpath, &statbuf, E2TW_DP, twdata->user_data));
			result |= E2TW_STOP;	//ensure a STOP
		}
*/
		if (repaired && (result & E2TW_DRKEEP))
			repaired = FALSE;	//prevent any later mode reversion

		if (result & (E2TW_STOP | E2TW_SKIPSUB))
		{
			if (repaired)
				e2_fs_chmod (localpath, firstmode E2_ERR_NONE());
			e2_list_free_with_data (&entries);
			return (result & E2TW_STOP) ? E2TW_STOP : E2TW_CONTINUE;
		}
	}
//==== DIR HAS BEEN READ & REPORTED AS SUCH

#ifdef __USE_GNU
	itemname = mempcpy (itempath, VPSTR (localpath), localpathlen);
#else
	memcpy (itempath, VPSTR (localpath), localpathlen);
	itemname = itempath + localpathlen;
#endif
	bufspace = pathbuflen - localpathlen;
	if (*(itemname - sizeof (gchar)) != G_DIR_SEPARATOR)	//ascii ok
	{
		*itemname = G_DIR_SEPARATOR;	//ascii ok
		itemname++;
		bufspace--;
	}
	//for logging dirs in breadth-first mode, to be processed when we're ready
	//to open them, after all non-dirs at this level have been processed
	parked_dirs = NULL;
#ifdef E2_VFS
	ddata.localpath = itempath;
	ddata.spacedata = localpath->spacedata;
#endif
	//walk the list of entries in the dir (tree-depth now = newdepth)
	for (member = entries; member != NULL ; member = member->next)
	{
		if (strcmp ((gchar *)member->data, "..") == 0)
			continue;

		g_strlcpy (itemname, (gchar *) member->data, bufspace);

#ifdef E2_VFS
		if ((*twdata->twstat) (&ddata, &statbuf E2_ERR_PTR()))
#else
		if ((*twdata->twstat) (itempath, &statbuf E2_ERR_PTR()))
#endif
		{	//process stat failure
			//NOTE statting a hanging link to a dir does NOT fail here, glib BUG ?
			if (!(twdata->exec_flags & E2TW_XQT))
				e2_fs_error_local (_("Cannot get information about %s"),
#ifdef E2_VFS
					&ddata E2_ERR_MSGL());
#else
					itempath E2_ERR_MSGL());
#endif
#ifdef E2_VFS
			result = (*twdata->cbfunc) (&ddata, &statbuf, E2TW_NS, twdata->user_data);
			if (twdata->exec_flags & E2TW_XERR)
			{
				callerr = (GError **)twdata->user_data;
				if (callerr != NULL && *callerr == NULL)	//valid, unused error data
					*callerr = E2_ERR_NAME;
			}
			else
				E2_ERR_CLEAR
#else
			result = (*twdata->cbfunc) (itempath, &statbuf, E2TW_NS, twdata->user_data);
#endif
			if ((result & E2TW_CLEAN) && reportdirs)
			{
				result |=
				((*twdata->cbfunc) (localpath, &statbuf, E2TW_DP, twdata->user_data));
				result |= E2TW_STOP;	//ensure a STOP
			}
			if (repaired && (result & E2TW_DRKEEP))
				repaired = FALSE;	//prevent any later mode reversion
			if (result & (E2TW_STOP | E2TW_SKIPSUB))
			{
				if (repaired)
					e2_fs_chmod (localpath, firstmode E2_ERR_NONE());
				if (parked_dirs != NULL)
					g_list_free (parked_dirs);
				e2_list_free_with_data (&entries);
#ifdef USE_GLIB2_10
				g_slice_free1 (pathbuflen, itempath);
#else
				g_free (itempath);
#endif
				return (result & E2TW_STOP) ? E2TW_STOP : E2TW_CONTINUE;
			}
		}
		else if (S_ISDIR (statbuf.st_mode))
		{	//stattable dir or (present) link-target that's a dir
/*			if (twdata->exec_flags & E2TW_DEPTH)
			{	//depth-first walk, open the dir now
				//(start- and end-reports issued in the recursed function)
				if (_e2_fs_tw (itempath, newdepth, twdata) != E2TW_CONTINUE)
				{
					//NOTE no DP report for this dir if aborted downstream
					//i.e. NO CLEAN CURRENT LEVEL
					//no parks to cleanup if depth-1st walk
					e2_list_free_with_data (&entries);
#ifdef USE_GLIB2_10
//					g_slice_free1 (itempathbuflen, itempath);
					g_slice_free1 (buflen, itempath);
#else
					g_free (itempath);
#endif
			FIXME more flags processing
					return E2TW_STOP;
				}
			}
			else
			{
*/
				//breadth-first walk
				//log the subdir for processing after all non-subdirs
				parked_dirs = g_list_prepend (parked_dirs,
					GINT_TO_POINTER (g_list_position (entries, member)));
//			}
		}
		else	//not a dir (it could be a hanging link to a dir)
			if (reportnondirs)
		{
#ifdef E2_VFS
			result = _e2_fs_tw_nondir (&ddata, &statbuf, twdata);
#else
			result = _e2_fs_tw_nondir (itempath, &statbuf, twdata);
#endif
			if ((result & E2TW_CLEAN) && reportdirs)
			{
				result |=
				((*twdata->cbfunc) (localpath, &statbuf, E2TW_DP, twdata->user_data));
				result |= E2TW_STOP;	//ensure a STOP
			}
			if (repaired && (result & E2TW_DRKEEP))
				repaired = FALSE;	//prevent later mode reversion
			if (result & E2TW_STOP)	//no SKIPSUB recognised here
			{
				if (repaired)
					e2_fs_chmod (localpath, firstmode E2_ERR_NONE());
				if (parked_dirs != NULL)
					g_list_free (parked_dirs);
				e2_list_free_with_data (&entries);
#ifdef USE_GLIB2_10
				g_slice_free1 (pathbuflen, itempath);
#else
				g_free (itempath);
#endif
				return E2TW_STOP;
			}
		}
	}
	//in a breadth-first walk, process the dirs that haven't yet been opened
	//(all of them, as their order is arbitrary)
	result = 0;
	for (member = parked_dirs ; member != NULL ; member = member->next)
	{
		gint indx = GPOINTER_TO_INT (member->data);
		gchar *parkname = g_list_nth_data (entries, indx);
		g_strlcpy (itemname, parkname, bufspace);
#ifdef E2_VFS
		result |= (_e2_fs_tw_dir (&ddata, newdepth, twdata));
#else
		result |= (_e2_fs_tw_dir (itempath, newdepth, twdata));
#endif
	}
	if (parked_dirs != NULL)
		g_list_free (parked_dirs);
	e2_list_free_with_data (&entries);
#ifdef USE_GLIB2_10
	g_slice_free1 (pathbuflen, itempath);
#else
	g_free (itempath);
#endif
	gboolean downfailed = (result & E2TW_STOP);
/*	//FIXME support cleanup then stop
	if (result & ?)
		....
	if (result & E2TW_STOP)
	{
		if (repaired)
			e2_fs_chmod (localpath, firstmode E2_ERR_NONE());
		return E2TW_STOP;
	}
*/
//==== FINISHED DIR-WALK
	//freshen the statbuf
	//(all downstream uses of the buffer are const, so shouldn't be necessary
	//except as another existence check ?)
	if ((*twdata->twstat) (localpath, &statbuf E2_ERR_PTR()))
	{	//stat failed (and advised as such)
		//can't issue valid DP report CHECKME do an invalid one ?
		if (reportdirs && (twdata->exec_flags & E2TW_DC))
			(*twdata->cbfunc) (localpath, &statbuf, E2TW_DP, twdata->user_data);

		if (repaired)	//perms changed prior to DRR report and cb did not want this ignored
		{
			//after stat() failure, this will probably fail too
			e2_fs_chmod (localpath, firstmode E2_ERR_NONE());
		}
#ifdef E2_VFS
		if (twdata->exec_flags & E2TW_XERR)
		{
			callerr = (GError **)twdata->user_data;
			if (callerr != NULL && *callerr == NULL)	//valid, unused error data
				*callerr = E2_ERR_NAME;
		}
		else
			E2_ERR_CLEAR
#endif
		return E2TW_STOP;
	}
	else	//final stat succeeded
	{
		if (reportdirs)
		{
			//issue dir-end report
			result = (*twdata->cbfunc) (localpath, &statbuf, E2TW_DP, twdata->user_data);
			if (repaired && (result & E2TW_DRKEEP))
				repaired = FALSE;	//prevent later mode reversion
		}
		else
			result = (downfailed) ? E2TW_STOP : E2TW_CONTINUE;

		if (repaired)	//perms changed prior to DRR report and no cb wanted this ignored
		{
			if (e2_fs_chmod (localpath, firstmode E2_ERR_PTR()))
			{
				if (!(twdata->exec_flags & E2TW_XQT))
					e2_fs_error_local (_("Cannot change permissions of %s"),
						localpath E2_ERR_MSGL());
#ifdef E2_VFS
				if (twdata->exec_flags & E2TW_XERR)
				{
					callerr = (GError **)twdata->user_data;
					if (callerr != NULL && *callerr == NULL)	//valid, unused error data
						*callerr = E2_ERR_NAME;
				}
				else
					E2_ERR_CLEAR
#endif
				return E2TW_STOP;
			}
		}
	}
	return (result & E2TW_STOP) ? E2TW_STOP : E2TW_CONTINUE;
}
/**
@brief locate and report all items in directory tree under @a start_dir

Potential error message expects gtk's BGL to be open. Downstream callbacks
must assume that too.

@param start_item absolute path of item (dir or non-dir) on|in which to start the walk, localised string
@param callback pointer to the function to be called for each located item
@param user_data pointer to user-specified data to be supplied to each call to @a callback
@param max_depth the largest number of tree levels to descend, or -1 for no limit
@param exec_flags flags indicating the way the walk is to be performed

@return TRUE if @a start_item (and any descendants) was processed in way(s) that @a callback considered ok
*/
gboolean e2_fs_tw (VPATH *start_item, E2_TwResult (*callback) (),
	gpointer user_data, gint max_depth, E2_TwFlags exec_flags E2_ERR_ARG())
{
	E2_TwResult result;
	struct stat statbuf;
	gint (*twstat) (VPATH *, struct stat *
#ifdef E2_VFS
		, GError **
#endif
	);
	E2_ERR_BACKUP (localerr);

	twstat = (exec_flags & E2TW_PHYS) ? e2_fs_lstat : e2_fs_stat;
	if ((twstat) (start_item, &statbuf E2_ERR_SAMEARG()))
	{
		if (!(exec_flags & E2TW_XQT))
			e2_fs_error_local (_("Cannot get information about %s"),
				start_item E2_ERR_MSGC());
		result = E2TW_STOP;
	}
	else
	{
		E2_FsReadWatch watchdir =
			(S_ISDIR (statbuf.st_mode) && e2_fs_mount_is_fusepoint (start_item)) ?
			E2_DIRWATCH_YES : E2_DIRWATCH_NO;
		//setup walk-data for downstream functions
		E2_TwData data =
		{ twstat, callback, user_data, max_depth, watchdir, exec_flags, statbuf.st_dev };

		result = (S_ISDIR (statbuf.st_mode)) ?
			_e2_fs_tw_dir (start_item, 0, &data):
			_e2_fs_tw_nondir (start_item, &statbuf, &data); //should never be needed, but ...
	}

#ifdef E2_VFS
	if (exec_flags & E2TW_XERR)
	{
		GError **callerr = (GError **)user_data;
		if (callerr != NULL && *callerr != NULL)	//valid, unused error data
			*E2_ERR_NAME = *callerr;
	}
#endif
	E2_ERR_CLEARBACKUP (localerr);

	return (result == E2TW_CONTINUE);
}
