/* $Id: e2_fs_FAM_dnotify.c 2064 2010-03-12 13:15:36Z tpgww $

Copyright (C) 2005-2010 tooar <tooar@emelfm2.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, see http://www.gnu.org/licenses.
*/

/**
@file src/filesystem/e2_fs_FAM_dnotify.c
@brief functions related to file-alteration monitoring using dnotify on linux
*/

#include "emelfm2.h"
#ifdef E2_FAM_DNOTIFY

#include <string.h>
#include <fcntl.h>
#include <signal.h>

//#define EXTRA_MESSAGES
//#define SIMPLECOUNT
#ifdef SIMPLECOUNT
static guint debugcount = 0;
#endif

//SIGRTMIN is "often blocked"
//#define DNOTIFY_SIGNAL SIGRTMIN
#define DNOTIFY_SIGNAL SIGRTMIN+1
sigset_t dnotify_signal_set;

//the types of changes about which dnotify will report
//NOTE these relate to items in the dir, seems like some
//eg DN_RENAME do not apply to the dir itself
#define DNOTIFY_FLAGS \
	DN_ACCESS | DN_MODIFY | DN_CREATE | DN_DELETE | \
	DN_RENAME | DN_ATTRIB | DN_MULTISHOT
#define DNSHORT_FLAGS \
	DN_MODIFY | DN_CREATE | DN_DELETE | \
	DN_RENAME | DN_ATTRIB | DN_MULTISHOT

//value to be stored in array to signal an unused column
#define FD_UNUSED 0
enum { REPORT_FD, REPORT_REF, REPORT_COUNT, REPORT_ROWS };
/*store for logging reports and associated stuff for up to 3 fd's
(1 or 2 pane-dirs and config parent)
Rows are used as per the enum above*/
static gint reports [3][REPORT_ROWS] = { {FD_UNUSED}, {FD_UNUSED}, {FD_UNUSED}};
//utf-8 pathnames for each monitored item
static gchar *paths[3];

//these are for polling config file changes
static gchar *localconfig;
static struct stat cfgstatbuf;

//define this when doing client-determined polling for received reports
#define DNOTIFY_POLLING
//otherwise, these support server-determined report-processing
#ifndef DNOTIFY_POLLING
/*Max. no of dnotify reports that we can handle here, in any batch
If polling is not in force, then the received reports will be
processed when there's no more pending, or when this
many reports are received, whichever is sooner.
Note that multiple reports can be generated for each changed
item in a dir*/
#define MAX_REPORTS 1024

static GIOChannel *dnotify_read_ioc;
static GIOChannel *dnotify_write_ioc;
//FIXME make this something pre-emptive
//G_LOCK_DEFINE (e2_dnotify);
static GSource *dnotify_src;
static guint dnotify_src_id;
#endif

/**
@brief Read data piped by dnotify
This is a callback for the pipe-watch source
Batchwise retrieves a stream of the accumulated strings piped
to dnotify by the signal handler. It is called repeatedly until the
stream is finished.
@param user_data UNUSED pointer to data specified when callback was connected
@return TRUE, so the source remains intact
*/
#ifndef DNOTIFY_POLLING
static gboolean _e2_fs_FAM_dnotify_pipe_handler (gpointer user_data)
{
	/*buffer receives a stream comprising the accumulated strings
	piped back to dnotify, at the end of each signal-handler
	function call
	buffer size determines the user-report batch-frequency.
	Presently relies on the fact that piped feedback is 1 byte per report */
	gchar buffer[MAX_REPORTS];
	gsize buffer_bytes;
	//G_LOCK (e2_dnotify);
	g_io_channel_read_chars
		(dnotify_read_ioc, buffer, sizeof(buffer), &buffer_bytes, NULL);
	//G_UNLOCK (e2_dnotify);

//	printd (DEBUG, "pipe handler read %d bytes", buffer_bytes);
	//FIXME process the batch of received reports here, and issue reports to client

	return TRUE;
}
#endif
/**
@brief Handle dnotify-related signal
Logs a report for the fd, for later use

@param sig the signal to handle
@param si pointer to signal data struct
@param sig_data pointer to signal data
*/
static void _e2_fs_FAM_dnotify_mainsignal_handler
	(gint sig, siginfo_t *si, void *sig_data)
{
#ifdef SIMPLECOUNT
	debugcount++;
#else
	//log the report, (atomic, signal-proof)
	register gint i, r = si->si_fd;
	E2_BLOCK
	for (i=0; i<3; i++)
	{
		if (reports[i][REPORT_FD] == r)
		{
			reports[i][REPORT_COUNT]++;
			break;
		}
	}
	E2_UNBLOCK
#if defined(DEBUG_MESSAGES) && defined(EXTRA_MESSAGES)
	gchar *dir;
# ifdef E2_VFSTMP
		//dir when path is non-mounted
# else
	if (si->si_fd == curr_pane->FAMreq)
		dir = curr_pane->path;
	else if (si->si_fd == other_pane->FAMreq)
		dir = other_pane->path;
# endif
	else
		dir = "config file";
	printd (DEBUG, "dnotify signal for %s", dir);
#endif
#endif //def SIMPLECOUNT
#ifndef DNOTIFY_POLLING
/*	Update the queue which causes the pipe-handler to be called
	CHECKME any merit in sending back a more-informative
	string? Later on, we get it piped back (as part of a stream)
	into the read buffer ... So if we change the string here,
	remember to change the read buffer size accordingly,
	to preserve the desired batch size */
	g_io_channel_write_chars (dnotify_write_ioc, "+", 1, NULL, NULL);
	g_io_channel_flush (dnotify_write_ioc, NULL);
#endif
}
/**
@brief Handle dnotify-related signal-queue-full signal
@param sig the signal to handle
@param si pointer to signal data struct
@param sig_data pointer to signal data
*/
static void _e2_fs_FAM_dnotify_overflowsignal_handler
	(gint sig, siginfo_t * si, void *sig_data)
{
	//set all monitored items dirty
	register gint i, r = FD_UNUSED;
	E2_BLOCK
	for (i=0; i<3; i++)
	{
		if (reports[i][REPORT_FD] != r)
			reports[i][REPORT_COUNT]++;
	}
	E2_UNBLOCK
	printd (WARN, "OOPS !! dnotify signal queue overflow");
}
/**
@brief register directory @a path with dnotify
This func can be called from within a threaded refresh
@param path utf-8 string with path of item to be monitored
@return the file descriptor for the dir, <0 for an error
*/
static gint _e2_fs_FAM_dnotify_monitor_dir (gchar *path)
{
	//find if path is already monitored
	gint i, retval = -1;
	E2_BLOCK
	for (i=0; i<3; i++)
	{
		if (reports[i][REPORT_FD] != FD_UNUSED	//e.g. not cancelled
			&& paths[i] != NULL
			&& !strcmp (paths[i], path))
		{
			//if so, just ref it
			reports[i][REPORT_REF]++;
			printd (DEBUG, "reffed dnotify monitoring of %s", path);
			retval = reports[i][REPORT_FD];
			break;
		}
	}
	E2_UNBLOCK
	if (retval != -1)
		return retval;

	//otherwise register it with dnotify
	gchar *local = F_FILENAME_TO_LOCALE (path);
	gint fd = e2_fs_safeopen (local, O_RDONLY | O_NONBLOCK, 0);
	F_FREE (local, path);

	if (fd >= 0)
	{
		fcntl (fd, F_SETSIG, DNOTIFY_SIGNAL);
		if (!fcntl (fd, F_NOTIFY, DNOTIFY_FLAGS))
		{
			//setup for report-logging
			E2_BLOCK
			for (i=0; i<3; i++)
			{
				if (reports[i][REPORT_FD] == FD_UNUSED)
				{
					reports[i][REPORT_FD] = fd;
					reports[i][REPORT_REF] = 1;
					reports[i][REPORT_COUNT] = 0;
					if (paths[i] != NULL)
						g_free (paths[i]);
					paths[i] = g_strdup (path);
					printd (DEBUG, "added dnotify monitoring of %s", path);
					retval = fd;
					break;
				}
			}
			E2_UNBLOCK
			if (retval != -1)
				return retval;
		}
		else	//connection failed for some reason
			e2_fs_safeclose (fd);
	}
	printd (WARN, "failed to add dnotify monitoring of %s\n%s", path, strerror (errno));
	return -1;
}
/**
@brief de-register directory @a path with dnotify
This func can be called from within a threaded refresh
@param fd file descriptor number that is to be closed
@return TRUE if cancellation succeeds
*/
static gboolean _e2_fs_FAM_dnotify_demonitor_dir (gint fd)
{
	gboolean retval = FALSE;
	gint i;
	//check if dir not needed anymore
	E2_BLOCK
	for (i=0; i<3; i++)
	{
		if (reports[i][REPORT_FD] == fd)
		{
			if (reports[i][REPORT_REF] == 1)
			{
				fcntl (fd, F_NOTIFY, 0);
				if (!close (fd))
				{
					reports[i][REPORT_FD] = FD_UNUSED;
					reports[i][REPORT_COUNT] = 0;	//overkill ? cleared when monitoring starts
					printd (DEBUG, "removed dnotify monitoring of %s", paths[i]);
					retval = TRUE;
					break;
				}
				else
				{	//close failed, need to re-register
					fcntl (fd, F_NOTIFY, DNOTIFY_FLAGS);
					printd (WARN, "failed to remove dnotify monitoring of %s", paths[i]);
					break;
				}
			}
			else
			{	//just unref it
				reports[i][REPORT_REF]--;
				retval = TRUE;
				break;
			}
		}
	}
	E2_UNBLOCK
	return retval;
}
/**
@brief Initialize dnotify monitoring.

@return TRUE if initialization succeeded
*/
static gboolean _e2_fs_FAM_dnotify_init (void)
{
	struct sigaction act;
#ifndef DNOTIFY_POLLING
	//setup to get piped signal when it's time to process the received reports
	gint fds[2];

	if (pipe (fds) < 0)
	{
		printd (WARN,"Could not create dnotify pipe");
		return FALSE;
	}

	dnotify_read_ioc = g_io_channel_unix_new (fds[0]);
	g_io_channel_set_encoding (dnotify_read_ioc, NULL, NULL);
	g_io_channel_set_flags (dnotify_read_ioc, G_IO_FLAG_NONBLOCK, NULL);

	dnotify_write_ioc = g_io_channel_unix_new (fds[1]);
	g_io_channel_set_encoding (dnotify_write_ioc, NULL, NULL);
	g_io_channel_set_flags (dnotify_write_ioc, G_IO_FLAG_NONBLOCK, NULL);

	//FIXME make this something pre-emptive
	dnotify_src = g_io_create_watch (dnotify_read_ioc,
						G_IO_IN | G_IO_HUP | G_IO_ERR);
	g_source_set_callback (dnotify_src, _e2_fs_FAM_dnotify_pipe_handler, NULL, NULL);
	g_source_set_priority (dnotify_src, 100);
	g_source_set_can_recurse (dnotify_src, FALSE);
	dnotify_src_id = g_source_attach (dnotify_src, NULL);
#endif

	//setup related signal-stuff
	act.sa_sigaction = _e2_fs_FAM_dnotify_mainsignal_handler;
	sigemptyset (&act.sa_mask);
//	sigaddset (&act.sa_mask, DNOTIFY_SIGNAL);
	act.sa_flags = SA_RESTART | SA_SIGINFO;
	sigaction (DNOTIFY_SIGNAL, &act, NULL);

	//catch SIGIO (which is issued when the queue fills) as well
	act.sa_sigaction = _e2_fs_FAM_dnotify_overflowsignal_handler;
//	sigemptyset (&act.sa_mask);
	sigaction (SIGIO, &act, NULL);

	//setup for signal [un]blocking
//	sigemptyset (&dnotify_signal_set);
	dnotify_signal_set = act.sa_mask;
	sigaddset (&dnotify_signal_set, DNOTIFY_SIGNAL);	//don't block SIGIO
	return TRUE;
}
/**
@brief cleanup after ending dnotify monitoring.

@return
*/
static void _e2_fs_FAM_dnotify_abandon (void)
{
	//don't bother cleaning up actual data, we're ending now
	//shutdown connections before closing channels
	gint i, fd;
	E2_BLOCK
	for (i=0; i<3; i++)
	{
		if ((fd = reports[i][REPORT_FD]) != FD_UNUSED)
		{
			fcntl (fd, F_NOTIFY, 0);
			close (fd);
		}
	}
	E2_UNBLOCK
#ifndef DNOTIFY_POLLING
	GError *err = NULL;
	g_io_channel_shutdown (dnotify_read_ioc, FALSE, &err);
	g_io_channel_shutdown (dnotify_write_ioc, FALSE, &err);
	g_source_destroy (dnotify_src);
#endif
}

  /*************************/
 /**** public functions ****/
/*************************/

/**
@brief establish dnotify connection

@return
*/
void e2_fs_FAM_connect (void)
{
	app.monitor_type = (_e2_fs_FAM_dnotify_init ()) ?
		E2_MONITOR_FAM : E2_MONITOR_DEFAULT ;
}
/**
@brief terminate dnotify connection

session-end, no data freeing

@return
*/
void e2_fs_FAM_disconnect (void)
{
	if (app.monitor_type == E2_MONITOR_FAM)
		_e2_fs_FAM_dnotify_abandon ();
}
/**
@brief change monitored directory

if old-dir not also in the other pane, cancel old-dir monitoring
if new-dir not also in other pane, start monitoring new-dir
If new-dir is not monitored (FAM error or dir is already monitored in other pane)
the FAM request for the pane is set to -1

@a olddir needs trailing '/', for valid comparing with rt->view.dir etc

@param olddir utf-8 string with absolute path of dir to stop monitoring
@param rt data struct for the pane to which the cd applies, including the new dir in rt->view.dir

@return
*/
void e2_fs_FAM_change (gchar *olddir, E2_PaneRuntime *rt)
{
	if (app.monitor_type == E2_MONITOR_DEFAULT)
		return;
	//cancel monitoring of current dir if that was happening
	//at session-start, both panes have rt->FAMreq == -1
	if (rt->FAMreq != -1)
	{
		//use the request for this pane
		_e2_fs_FAM_dnotify_demonitor_dir (rt->FAMreq);
		//default flag = no-monitoring this pane
		rt->FAMreq = -1;
	}
	E2_PaneRuntime *ort = (rt == curr_pane) ? other_pane : curr_pane;
#ifdef E2_VFSTMP
		//dir when path is non-mounted
#else
	if (!strcmp (olddir, ort->view.dir))
#endif
	{	//panes are showing same dir now
		//connect to the other pane if need be, to get correct pane id
		//when monitoring
		if (ort->FAMreq == -1)
		{
#ifdef E2_VFSTMP
		//dir when path is non-mounted
#else
			ort->FAMreq = _e2_fs_FAM_dnotify_monitor_dir (ort->view.dir);
//			printd (DEBUG, "started monitoring of other dir %s", ort->view.dir);
#endif
		}
	}
	//now hoookup to the new dir, if it's not going already
#ifdef E2_VFSTMP
		//dirs when path is non-mounted
#else
	if (strcmp (ort->view.dir, rt->view.dir))	//NB panes may be same at session start
	{	//new dir not already monitored
		rt->FAMreq = _e2_fs_FAM_dnotify_monitor_dir (rt->view.dir);
//		printd (DEBUG, "started monitoring of this dir %s", rt->view.dir);
	}
#endif
	else
	{
		rt->FAMreq = -1;
//		printd (DEBUG, "skipped monitoring of this dir %s", rt->view.dir);
	}
}
/**
@brief get rid of last pending change-report for directory @a path
Used as part of filelist refreshing
There is no actual change if the fd for the relevant pane is -1
which means there is no monitoring anyway
@param path utf-8 string with path of dir to b processed
@return
*/
void e2_fs_FAM_clean_reports (gchar *path)
{
	if (app.monitor_type == E2_MONITOR_DEFAULT)
		return;
	usleep (20000);	//wait for some possible interrupt(s)
#ifdef E2_VFSTMP
	//FIXME dir when not mounted local
#else
	gint fd = (!strcmp (curr_view->dir, path)) ?
#endif
		curr_pane->FAMreq : other_pane->FAMreq;
	if (fd != -1)
	{	//dir was being monitored before
		//decrement its reports log
		gint i;
		E2_BLOCK
		for (i=0; i<3; i++)
		{
			if (reports[i][REPORT_FD] == fd)
			{
				if (reports[i][REPORT_COUNT] > 0)
					reports[i][REPORT_COUNT]--;
				break;
			}
		}
		E2_UNBLOCK
	}
}
/* *
@brief Maybe get rid of last pending change-report for directory NOT @a path
Adjusts the report-count for the other pane, if that's the parent of @a path
Used as part of filelist refreshing
There is no actual change if the fd for the relevant pane is -1
which means there is no monitoring anyway
@param path utf-8 string with path of dir to b processed
@return
*/
/*
void e2_fs_FAM_clean_other_reports (gchar *path)
{
	if (app.monitor_type == E2_MONITOR_DEFAULT)
		return;
	usleep (20000);	//wait for some possible interrupt(s)

#ifdef E2_VFSTMP
	//FIXME dir when not mounted local
#else
	gchar *otherdir = (!strcmp (curr_view->dir, path)) ?
			other_view->dir : curr_view->dir;
#endif
	if (g_str_has_prefix (path, otherdir))
	{
		//make sure it's a direct parent
		gchar *s = path + strlen (otherdir);
		if (*s == '\0')
			otherdir = NULL;	//paths are the same
		else
		{
			s = strchr (s, G_DIR_SEPARATOR);
			if (*(s+1) != '\0')
				otherdir = NULL;	//there's more than 1 level between the 2
		}
	}
	else
		otherdir = NULL;
	if (otherdir != NULL)
	{
#ifdef E2_VFSTMP
	//FIXME dir when not mounted local
#else
		fd = (!strcmp (curr_view->dir, otherdir)) ?
#endif
			curr_pane->FAMreq : other_pane->FAMreq;
		if (fd != -1)
		{	//dir was being monitored before
			//clear its reports log
			gint i;
			E2_BLOCK
			for (i=0; i<3; i++)
			{
				if (reports[i][REPORT_FD] == fd)
				{
					if (reports[i][REPORT_COUNT] > 0)
						reports[i][REPORT_COUNT]--;
					break;
				}
			}
			E2_UNBLOCK
		}
	}
} */
/* *
@brief begin dnotify monitoring of directory @a path
Used as part of filelist refreshing (possibly threaded),
as part of a pair with e2_fs_FAM_cancel_monitor_dir ()
Any prior reports for the dir are cleared
There is no actual change if the fd for the relevant pane is -1
which means there was no monitoring before the cancellation
@param path utf-8 string with path of dir to be resumed
@return
*/
/*void e2_fs_FAM_monitor_dir (gchar *path)
{
	E2_PaneRuntime *rt =
#ifdef E2_VFSTMP
		//dir when path is non-mounted
#else
		(!strcmp (curr_pane->path, path)) ?
#endif
		curr_pane : other_pane;
	if (rt->FAMreq != -1)
	{	//dir was being monitored before
		//so start it again
		rt->FAMreq = _e2_fs_FAM_dnotify_monitor_dir (path);
//		printd (DEBUG, "(as part of a resumption)");
	}
} */
/* *
@brief cancel dnotify monitoring of directory @a path
Used as part of filelist refreshing (possibly threaded),
as part of a pair with e2_fs_FAM_monitor_dir ()
There is no actual change if the fd for the relevant pane is -1
which means there is no monitoring anyway
@param path utf-8 string with path of dir to be suspended
@return
*/
/*void e2_fs_FAM_cancel_monitor_dir (gchar *path)
{
	E2_PaneRuntime *rt =
#ifdef E2_VFSTMP
		//dir when path is non-mounted
#else
		(!strcmp (curr_pane->path, path)) ?
#endif
		curr_pane : other_pane;
	if (rt->FAMreq != -1)
	{	//dir was being monitored before
		_e2_fs_FAM_dnotify_demonitor_dir (rt->FAMreq);
//		printd (DEBUG, "(as part of a suspension)");
	}
} */
/**
@brief reduce the scope of dnotify monitoring of directory @a path
There is no actual change if the fd for the relevant pane is -1 which means
there is no monitoring anyway
Any prior reports for the dir are not affected
@param path utf-8 string with path of dir whose watch is to be altered
@return
*/
void e2_fs_FAM_less_monitor_dir (gchar *path)
{
	E2_PaneRuntime *rt =
#ifdef E2_VFSTMP
		//dir when path is non-mounted
#else
		(!strcmp (curr_pane->path, path)) ?
#endif
		curr_pane : other_pane;
	if (rt->FAMreq != -1)
	{	//dir was being monitored before
		fcntl (rt->FAMreq, F_SETSIG, DNOTIFY_SIGNAL);
		if (fcntl (rt->FAMreq, F_NOTIFY, DNSHORT_FLAGS))
			printd (ERROR, "Error in limiting dnotify monitoring");
	}
}
/**
@brief resume normal scope of dnotify monitoring of directory @a path after
 a e2_fs_FAM_less_monitor_dir()
There is no actual change if the fd for the relevant pane is -1 which means
there is no monitoring anyway
Any prior reports for the dir are not affected
@param path utf-8 string with path of dir whose watch is to be altered
@return
*/
void e2_fs_FAM_more_monitor_dir (gchar *path)
{
	E2_PaneRuntime *rt =
#ifdef E2_VFSTMP
		//dir when path is non-mounted
#else
		(!strcmp (curr_pane->path, path)) ?
#endif
		curr_pane : other_pane;
	if (rt->FAMreq != -1)
	{	//dir was being monitored before
		fcntl (rt->FAMreq, F_SETSIG, DNOTIFY_SIGNAL);
		if (fcntl (rt->FAMreq, F_NOTIFY, DNOTIFY_FLAGS))
			printd (ERROR, "Error in reinstating full dnotify monitoring");
	}
}
/**
@brief setup monitoring of config file

@return TRUE if the connection was successfully established
*/
gboolean e2_fs_FAM_monitor_config (void)
{
	gchar *config_file = g_build_filename (e2_cl_options.config_dir, default_config_file, NULL);
	gchar *config_parent =  g_path_get_dirname (config_file);
	gboolean retval = FALSE;

	if (strcmp (config_parent, "."))
	{
		app.FAMreq = _e2_fs_FAM_dnotify_monitor_dir (config_parent);
		if (app.FAMreq >= 0)
			retval = TRUE;
	}

	//setup to check for irrelevant reports about config file
	if (localconfig == NULL)
		localconfig = D_FILENAME_TO_LOCALE (config_file);
	stat (localconfig, &cfgstatbuf);	//ok to traverse link, ok to fail FIXME vfs

	if (retval)
		printd (DEBUG, "dnotify init for %s succeeded", config_file);
	else
		printd (WARN, "dnotify init for %s failed", config_file);

	g_free (config_file);
	g_free (config_parent);
	return retval;
}
/**
@brief cancel monitoring of config file

@return TRUE if the connection was successfully removed
*/
gboolean e2_fs_FAM_cancel_monitor_config (void)
{
	gchar *config_file = g_build_filename (e2_cl_options.config_dir, default_config_file, NULL);
	gchar *config_parent =  g_path_get_dirname (config_file);
	gboolean retval = (strcmp (config_parent, ".")
		 && _e2_fs_FAM_dnotify_demonitor_dir (app.FAMreq));

	if (retval)
		printd (DEBUG, "dnotify cancel for %s succeeded", config_file);
	else
		printd (WARN, "dnotify cancel for %s failed", config_file);

	g_free (config_file);
	g_free (config_parent);
	return retval;
}
/**
@brief poll monitor to check if any monitored thing has changed

This just updates flags, to signal that refresh is needed
For a pane, update is needed if any directory content has
changed, or if parent dir is not accessible/readable or gone
A missing config file is not reported (as we can't reload it anyway)

@param p1altered pointer to location to store T/F for whether pane 1 needs refresh
@param p2altered pointer to location to store T/F for whether pane 2 needs refresh
@param cfgaltered pointer to location to store T/F for whether config needs refresh

@return
*/
void e2_fs_FAM_poll (gboolean *p1altered, gboolean *p2altered, gboolean *cfgaltered)
{
#ifdef SIMPLECOUNT
	if (debugcount > 0)
	{
		printd (DEBUG, "%d reports received", debugcount);
		debugcount = 0;
	}
	*p1altered = *p2altered = *cfgaltered = FALSE;
	return;
#else

	//make sure the monitored dir(s) are still valid
	//dnotify will not have reported inaccessible/renamed/deleted dir(s)
	//access () traverses links (ok) and does not change atime (ok)
#ifdef E2_VFSTMP
	//FIXME dir when not mounted local
#else
	gchar *local = D_FILENAME_TO_LOCALE (app.pane1.path); //always dup, to avoid dirchange race
#endif
	*p1altered = e2_fs_access (local, R_OK | X_OK E2_ERR_PTR());
	g_free (local);
#ifdef E2_VFSTMP
	//FIXME dir when not mounted local
#else
	local = D_FILENAME_TO_LOCALE (app.pane2.path); //always dup, to avoid dirchange race
#endif
	*p2altered = e2_fs_access (local, R_OK | X_OK E2_ERR_PTR());
	g_free (local);
	//not interested whether config is still present
	*cfgaltered = FALSE;

	//block DNOTIFY_SIGNAL
	sigprocmask (SIG_BLOCK, &dnotify_signal_set, NULL);

	//interrogate and clear reports logs
	gint i;
	E2_BLOCK
	for (i=0; i<3; i++)
	{
		if (reports[i][REPORT_COUNT] > 0
			&& reports[i][REPORT_FD] != FD_UNUSED)
		{
//			gchar *path = g_hash_table_lookup (fd_hash,
//				GINT_TO_POINTER (reports[i][REPORT_FD]));
#ifdef EXTRA_MESSAGES
			printd (DEBUG, "%d report(s) for %s", reports[i][REPORT_COUNT], paths[i]);
#endif
			reports[i][REPORT_COUNT] = 0;
			gint fd = reports[i][REPORT_FD];
			if (fd == app.pane1.FAMreq)
			{
				*p1altered = TRUE;
//				printd (DEBUG, "pane 1 to be refreshed");
				//mirrored panes need to get the same status
				if (app.pane2.FAMreq == -1)
				{
					*p2altered = TRUE;
//					printd (DEBUG, "pane 2 to be refreshed");
				}
			}
			else if (fd == app.pane2.FAMreq)
			{
				*p2altered = TRUE;
//				printd (DEBUG, "pane 2 to be refreshed");
				//mirrored panes need to get the same status
				if (app.pane1.FAMreq == -1)
				{
					*p1altered = TRUE;
//					printd (DEBUG, "pane 1 to be refreshed");
				}
			}
			else if (fd == app.FAMreq)
			{
				struct stat sb;
				//we do NOT want a dirty-report if the config file is gone now !
				if (!stat (localconfig, &sb))	//look thru link FIXME vfs
				{
					if (  sb.st_mtime != cfgstatbuf.st_mtime
//						|| sb.st_size != cfgstatbuf.st_size
//						|| sb.st_ino != cfgstatbuf.st_ino
						)
					{
						cfgstatbuf = sb;
						*cfgaltered = TRUE;
						printd (DEBUG, "changed config file");
					}
				}
			}
		}
	}
	E2_UNBLOCK
	//unblock DNOTIFY_SIGNAL
	sigprocmask (SIG_UNBLOCK, &dnotify_signal_set, NULL);

	return;
#endif //def SIMPLECOUNT
}

#endif //def E2_FAM_DNOTIFY
