/* $Id: e2_command.c 853 2008-04-07 10:38:17Z tpgww $

Copyright (C) 2004-2008 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/command/e2_command.c
@brief functions for handling e2 commands
*/
/**
\page commline the command line

ToDo

\section alias command aliases

ToDo
*/
/**
\page commands executing external commands

ToDo - description of how this works

\section alias alias operation

ToDo
*/


#include "e2_command.h"
#include <string.h>
#include <sys/wait.h>
#include <signal.h>
#include <pwd.h>
#include <pthread.h>
#include "e2_task.h"
#include "e2_filelist.h"
#include "e2_alias.h"
#include "e2_fs.h"

#define CMDNAMELEN 30

extern pthread_mutex_t task_mutex;
static gint waitflags;	//waitpid() argument, tailored for user's system
static gint block_count;	//refcount for [un]blocking child signals
const gchar *shellcmd;	//the command interpreter to use for external shell
//list of utf8 strings, each nAmE=string-value, no whitespace surrounding "="
static GList *variables;

#ifdef E2_NEW_COMMAND
//static void _e2_command_sigpoll_handler (gint num, siginfo_t *info, void *context);
static gpointer _e2_command_watch (E2_TaskRuntime *rt);
#else
static gboolean _e2_command_watch (GIOChannel *ioc, GIOCondition cond,
	E2_TaskRuntime *rt, gboolean error);
#endif
static void _e2_command_sigchld_handler (gint num, siginfo_t *info, void *context);

//CHECKME need SIGPIPE management ??

/**
@brief set handler for SIGCHLD signals
@return
*/
static void _e2_command_set_sigchild_handler (void)
{
	struct sigaction sigdata;
	sigdata.sa_sigaction = _e2_command_sigchld_handler;
	sigemptyset (&sigdata.sa_mask);
	sigdata.sa_flags = SA_RESTART | SA_SIGINFO; //SA_NOCLDSTOP not allowed;
	sigaction (SIGCHLD, &sigdata, NULL);
}
/**
@brief block incoming SIGCHLD signals
@return
*/
static void _e2_command_block_childsignal (void)
{
	if (++block_count == 1)
	{
		sigset_t set;	//, oldset;
		sigemptyset (&set);
		sigaddset (&set, SIGCHLD);
		sigprocmask (SIG_BLOCK, &set, NULL);	//&oldset);
	}
}
/**
@brief unblock incoming SIGCHLD signals
@return
*/
static void _e2_command_unblock_childsignal (void)
{
	if (--block_count < 0)
	{
		printd (WARN, "child signal block refcount error");
		block_count = 0;
	}
	if (block_count == 0)
	{
		sigset_t set;	//, oldset;
		//re-install the signal handler (some systems need this?) WRONG
//		_e2_command_set_sigchild_handler ();
		//and unblock it
		sigemptyset (&set);
		sigaddset (&set, SIGCHLD);
		sigprocmask (SIG_UNBLOCK, &set, NULL);	//&oldset);
	}
}
/**
@brief handle SIGCHILD signals
Note that opened pipes etc will come here, even though they're not logged as
children
@param num the signal number (17)
@param info pointer to data struct with details about signal, or NULL
@param context UNUSED

@return
*/
static void _e2_command_sigchld_handler (gint num, siginfo_t *info, void *context)
{
	printd (DEBUG, "signal handler (signal num:%d)", num);
	pid_t endpid;
	gint exitstatus;

	if (info != NULL)
	{
		//normally, the child's data is provided in signal data
		endpid = info->si_pid;
//		exitstatus = info->si_status;
		waitpid (endpid, &exitstatus, waitflags);	//must reap
	}
	else
	{
rewait:
		//get stopped, restarted and finished child(ren), if any
		//(any error will abort this loop, ECHILD (10) is normal loop terminator)
		endpid = waitpid (-1, &exitstatus, waitflags);
		printd (DEBUG, "waitpid() for any children returned %d", endpid);
	}

	if (endpid > 0)
	{
		E2_TaskRuntime *rt = e2_task_find_running_task ((glong) endpid);
		if (!(rt == NULL || rt->action))
		{
			printd (DEBUG, "ending after waitpid returned %d", endpid);
			pthread_mutex_lock (&task_mutex);
			if (WIFEXITED (exitstatus))
			{
				rt->pid = -2L;	//prevent further matches for this rt
				rt->status = E2_TASK_COMPLETED;
				rt->ex.command.exit = WEXITSTATUS (exitstatus);
				printd (DEBUG, "signal handler detected '%s' ended normally with exit code %d",
					rt->ex.command.command, WEXITSTATUS (exitstatus));
				printd (DEBUG, "child status stored");
			}
			else if (WIFSTOPPED (exitstatus))
			{
# ifdef WCONTINUED	//linux >= 2.6.10
				rt->status = E2_TASK_PAUSED;
# endif
				printd (DEBUG, "continuing after %d stopped by signal %d",
					(gint)endpid, WSTOPSIG (exitstatus));
			}
			else if (WIFSIGNALED (exitstatus))
			{
				gint signal = WTERMSIG (exitstatus);
				//CHECKME which other signals are acceptable ?
				if (signal == SIGTTOU || signal == SIGTTIN)
				{
# ifdef WCONTINUED	//linux >= 2.6.10
					rt->status = E2_TASK_PAUSED;
# endif
					//CHECKME do a select() and print messages after SIGTTOU/SIGTTIN signal ?
					printd (DEBUG, "continuing after %d stopped by signal %d",
						(gint)endpid, signal);
				}
				else
				{
					rt->pid = -2L;	//prevent further matches for this rt
					rt->status = E2_TASK_INCOMPLETE;
					rt->ex.command.exit = signal;
					printd (DEBUG, "child-signal handler detected command '%s' terminated by signal %d (%s)",
						rt->ex.command.command, signal, g_strsignal (signal));
					printd (DEBUG, "child status (%d) stored", signal);
				}
			}
# ifdef WCONTINUED
			else if (WIFCONTINUED (exitstatus))
			{
				rt->status = E2_TASK_RUNNING;
				printd (DEBUG, "process %d restarted", endpid);
			}
# endif
			pthread_mutex_unlock (&task_mutex);
		}
		else	//no matching running-command data (could be a sync command (old version))
//			if (!rt->action)
		{
			printd (WARN, "received signal from unrecorded child process (%d)", (gint) endpid);
			//in this handler, current signal is blocked, pass it on for other handlers
			//kill (endpid, num);	//CHECKME
		}
		if (info == NULL && !rt->action)
			goto rewait;
	} //end of endpid > 0
	else if (endpid < 0)
	{
		if (errno == EINTR)
		{
			printd (DEBUG, "re-checking after waitpid was interrupted");
			goto rewait;
		}
		else if (errno != ECHILD)
		{
			printd (DEBUG, "aborting after waitpid pid error %d (%s)", errno, g_strerror (errno));
			//FIXME find which task it is and record error exit
//			rt->status = E2_TASK_INCOMPLETE;
//			rt->ex.command.exit = errno;
		}
	}
	//re-install the signal handler (some systems need this?)
	_e2_command_set_sigchild_handler ();
}

#ifdef E2_NEW_COMMAND //=======================
/*DOES NOT WORK PROPERLY YET
select() returns prematurely for linux 2.4
fork again and read from grandchild, like glib ?
use pseudo forkpty ?
sync commands print nothing until command ends
additional thread for sync output to sync child ?
event reinstatement after <Ctrl>c checks with sync child
SIGPOLL handler instead of polling in watch func? maybe only works when detached from session terminal ?
*/
//piping from here to child stdin not properly supported, not least because the
//output fd is always selectable, which causes irrelevant returning from select()
//#define OUTPUT_POLL

enum { INWARDS, OUTWARDS };	//pipe fd enumerator

/* In Linux versions before 2.6.11, the capacity of a pipe (== PIPE_BUF) was the
same as the system page size (e.g., 4096 bytes on x86). Since Linux 2.6.11, the
pipe capacity is 65536 bytes. Other OS's have different values */
#define READBUFSIZE 1024
//this is READBUFSIZE, less space for adding a trailing \0, then rounded down to multiple of 8
#define READMAX 1016

//#include <fcntl.h>
//#include <stropts.h> //for ioctl

/* *
@brief set handler for SIGPOLL/SIGIO signals
@return
*/
/*static void _e2_command_set_sigpoll_handler (void)
{
	struct sigaction sigdata;
	sigdata.sa_sigaction = _e2_command_sigpoll_handler;
	sigemptyset (&sigdata.sa_mask);
	sigdata.sa_flags = SA_RESTART | SA_SIGINFO;
	sigaction (SIGPOLL, &sigdata, NULL);
} */
/**
@brief tolerantly transfer data from file descriptor @a fd into @a buffer
The buffer pointed to by @a buffer must be big enough to hold @a count bytes
@param fd number of file descriptor to read
@param buffer character-buffer pointer
@param count maximum no. of bytes to read or write

@return the no. of bytes read, -1 in case of some errors
*/
static ssize_t _e2_command_piperead (gint fd, gchar *buffer, gint count)
{
	ssize_t i, n = 0;
	while (n < count)
	{
		i = read (fd, buffer + n, count - n);	//vfs?? e2_fs_file_blockread;
		if (i >= 0)
			return (i+n);
		else
		{
			switch (errno)
			{
				//some errors we don't care about
				case EINTR:
				case EAGAIN:
#ifdef ERESTART
				case ERESTART:
#endif
					n += i;
					break;
				default:
					return -1;
					break;
			}
		}
	}
	return count;
}
/**
@brief print @a buffer contents
The printing is segmented into into lines, to prevent too-chunky scrolling
The final line may or may not have a trailing '\n'
@param buffer character-buffer pointer
@param error TRUE to print error stream
@param rt pointer to command data struct

@return
*/
static void _e2_command_display (gchar *buffer, gboolean error, E2_TaskRuntime *rt)
{
	gchar save;
	gchar *sep, *line;
	line = buffer;
	while (TRUE)
	{
		//always ascii \n, don't need utf8 search
		if ((sep = strchr (line, '\n')) != NULL)
		{
			sep++;
			save = *sep;
			*sep = '\0';
			gdk_threads_enter ();
			if (error)
				e2_output_print (rt->current_tab, line, rt->pidstr, FALSE,
					E2_ERRORTAGS, NULL);
			else
				e2_output_print (rt->current_tab, line, rt->pidstr, FALSE, NULL);
			gdk_threads_leave ();
			*sep = save;
			line = sep;
		}
		else
		{
			if (*line != '\0')
			{
				gdk_threads_enter ();
				if (error)
					e2_output_print (rt->current_tab, line, rt->pidstr, FALSE,
						E2_ERRORTAGS, NULL);
				else
					e2_output_print (rt->current_tab, line, rt->pidstr, FALSE, NULL);
				gdk_threads_leave ();
			}
			break;
		}
	}
}
/*
//FOR DEBUGGING
static void _e2_command_sigpoll_handler (gint num, siginfo_t *info, void *context)
{
	printd (DEBUG, "signal handler (signal num:%d)", num);

	if (info != NULL)
	{
		gint code = info->si_code;
		switch (code)
		{
			case POLL_IN:	//data input available
				break;
			case POLL_OUT:	//output buffers available
				break;
			case POLL_MSG:	//input message available
				break;
			case POLL_ERR:	//I/O error
				break;
			case POLL_PRI:	//high priority input available
				break;
			case POLL_HUP:	//device disconnected
				break;
		}
		glong band = info->si_band;	//band event for POLL_IN, POLL_OUT, or POLL_MSG
	}
/ *
	gint fd = 0;
	gint owner = fcntl (fd, F_GETOWN);
	gint oldflags = fcntl (fd, F_GETFL);
	fcntl (fd, F_SETFL, oldflags | O_NONBLOCK | O_ASYNC);

	gint yesplease = 1;
	ioctl (fd, FIOASYNC, &yesplease);
* /
}
*/
#define E2CMDTYPEB
/**
@brief watch for and forward to output pane any stdout or stderr text from running command
This is a thread function.
Assumes BGL is off
@param rt pointer to command data struct

@return the exit code of the command, > 0 if error
*/
static gpointer _e2_command_watch (E2_TaskRuntime *rt)
{
//	struct timeval wait;
//	struct timeval *waitptr;
	struct timespec wait;
	struct timespec *waitptr;
	gchar *buffer;
#ifdef USE_GLIB2_10
	buffer = g_slice_alloc (READBUFSIZE);
#else
	buffer = g_try_malloc (READBUFSIZE);
#endif
	CHECKALLOCATEDWARNT (buffer, return (GINT_TO_POINTER(1)););

	e2_utils_block_thread_signals ();	//block all allowed signals to this thread (what about SIGPIPE?)

	E2_CommandTaskData *cmddata = &rt->ex.command;
	printd (DEBUG, "child output fd = %d, child error fd = %d, max monitorable fd = %d",
		cmddata->child_stdout_fd, cmddata->child_stderr_fd, FD_SETSIZE);
	//FIXME handle assigned fd > FD_SETSIZE
	gint maxfd = MAX(cmddata->child_stdout_fd, cmddata->child_stderr_fd);
#ifdef OUTPUT_POLL
	if (cmddata->child_stdin_fd > maxfd)
		maxfd = cmddata->child_stdin_fd;
#endif
	maxfd++;

	//as the pipes will be selected at least when the child closes it's ok to
	//block until select succeeds
	//and note, ouput pipe (if used) is always selectable
	waitptr = NULL;
#ifdef E2CMDTYPEB
	gboolean outread, errread;
	outread = errread = TRUE;	//we want to pselect() the fd's at least once
#endif

	while (TRUE)
	{
		gint selcount;
		guint maxwait;
		ssize_t bytecount;
		gboolean outread, errread;
		fd_set in_fds;
#ifdef OUTPUT_POLL
		fd_set out_fds;
#endif
reselect:
/*#ifdef E2CMDTYPEB
		//FIXME handle case where outread and errread are both FALSE;
		if (!(outread || errread))
		{
			selcount = 0;
			goto ?
		}
#endif */
		FD_ZERO (&in_fds);
//FIXME find a way to may these tests work without stuffing up the completion reporting
#ifdef E2CMDTYPEB
		if (outread)	//don't select fd's that are closed
#endif
			FD_SET (cmddata->child_stdout_fd, &in_fds);
#ifdef E2CMDTYPEB
		if (errread)
#endif
			FD_SET (cmddata->child_stderr_fd, &in_fds);
#ifdef OUTPUT_POLL
		FD_ZERO (&out_fds);
# ifdef E2CMDTYPEB
		if (?)
# endif
			FD_SET (cmddata->child_stdin_fd, &out_fds);
#endif
		//timer data undefined after last use, re-init if used now
		if (waitptr != NULL)
		{
			wait.tv_sec = maxwait; //0 for immediate return
//			wait.tv_usec = 0;
			wait.tv_nsec = 0;
		}

#ifndef E2CMDTYPEB
		outread = errread = FALSE;	//ensure var's are initialised
#endif

#ifdef OUTPUT_POLL
		selcount = pselect (maxfd, &in_fds, &out_fds, NULL, waitptr, NULL);
#else
		selcount = pselect (maxfd, &in_fds, NULL, NULL, waitptr, NULL);
#endif
		if (selcount == -1 && errno == EINTR)
		{
			usleep (1000);	//wait awhile for any interrrupt processing
			goto reselect;
		}
		else if (selcount < 0)
		{
			printd (DEBUG, "pipe selection error %d (%s)", errno, g_strerror (errno));
//			outread = errread = FALSE;	//ensure no more selects
			selcount = 0;	//fake value to trigger loop exit
		}
		else if (selcount > 0)
		{
//			printd (DEBUG, "pipe selection");
			//at end-of-command, both input fd's report 0 bytes ready to read
			if (FD_ISSET (cmddata->child_stdout_fd, &in_fds))
			{
//				printd (DEBUG, "child stdout ready");
				//read input, starting with normal pipe
#ifdef E2CMDTYPEB
				outread = FALSE;	//in case this one fails
#endif
				while ((bytecount =
					_e2_command_piperead (cmddata->child_stdout_fd, buffer, READMAX)) > 0)
				{
					printd (DEBUG, "child stdout buffer read");
					outread = TRUE;
					*(buffer+bytecount) = '\0';
					_e2_command_display (buffer, FALSE, rt);
					if (bytecount != READMAX)
						break;
				}
				if (!outread)
				{
					//pipe closed or closing
					printd (DEBUG, "FALSE positive child stdout ready");
					usleep (10000);	//allow for some cleanup
				}
			}
//#ifdef E2CMDTYPEB
//			else
//				outread = TRUE;	//ensure reselection happens
//#endif

			//to minimise race-risk, check again for stderr if stdout was processed
			if (!FD_ISSET (cmddata->child_stderr_fd, &in_fds) && outread)
			{
//				printd (DEBUG, "re-check child stderr ready");
				wait.tv_sec = 0;
//				wait.tv_usec = 0;
				wait.tv_nsec = 0;
				FD_ZERO (&in_fds);
				FD_SET (cmddata->child_stderr_fd, &in_fds);
				TEMP_FAILURE_RETRY (pselect (cmddata->child_stderr_fd+1, &in_fds, NULL, NULL, &wait, NULL));
			}
			if (FD_ISSET (cmddata->child_stderr_fd, &in_fds))
			{
//				printd (DEBUG, "child stderr ready");
				//read input, starting with error pipe
#ifdef E2CMDTYPEB
				errread = FALSE;	//in case this one fails
#endif
				while ((bytecount =
					_e2_command_piperead (cmddata->child_stderr_fd, buffer, READMAX)) > 0)
				{
					printd (DEBUG, "child stderr buffer read");
					errread = TRUE;
					*(buffer+bytecount) = '\0';
					_e2_command_display (buffer, TRUE, rt);
					if (bytecount != READMAX)
						break;
				}
				if (!errread)
				{
					//pipe closed or closing
					printd (DEBUG, "FALSE positive child stderr ready");
					usleep (10000);	//allow for some cleanup
				}
			}
//#ifdef E2CMDTYPEB
//			else
//				errread = TRUE;	//ensure reselection happens
//#endif
#ifdef OUTPUT_POLL
//FIXME grab any commandline input and pass direct to running sync command
//MAYBE only if child is stopped ?
//FIXME child-pipe is ALWAYS ready, including at at end-of-command
//reading a line with \0 termination seems not to work
//multi reads can close the fd ?
			if (FD_ISSET (cmddata->child_stdin_fd, &out_fds))
			{
				printd (DEBUG, "child ready for input");
/*				gdk_threads_enter ();
				e2_output_print (rt->current_tab, "Child wants input",
					rt->pidstr, TRUE, E2_ERRORTAGS, NULL);
				gdk_threads_leave ();
				if (0)
				{	//there is something to send ...
					child = fdopen (cmddata->child_stderr_fd, "w");
					do
					{
						//DO SOMETHING TO GET MESSAGE INTO BUFFER
					} while (fputs (buf, child) != EOF);
					fclose (child);
				}
*/
			}
#endif
		}

		pthread_mutex_lock (&task_mutex);
		gboolean done = (rt->status >= E2_TASK_COMPLETED);
		pthread_mutex_unlock (&task_mutex);
#ifdef E2CMDTYPEB
		if (done || selcount == 0 || !(outread || errread))
#else
		if (done)
		{
			if (selcount == 0 || !(outread || errread))
#endif
			{
				printd (DEBUG, "watch function cleanup after end of process %s", rt->pidstr);
				close (cmddata->child_stdin_fd);
				close (cmddata->child_stdout_fd);
				close (cmddata->child_stderr_fd);
				if (cmddata->show && e2_option_bool_get ("fileop-show"))
				{
#ifdef E2CMDTYPEB
					//wait at most 5 secs for completion code to be logged
					guint i = 0;
					while (!done && i < 250)
					{
						usleep (20000);
						pthread_mutex_lock (&task_mutex);
						done = (rt->status >= E2_TASK_COMPLETED);
						pthread_mutex_unlock (&task_mutex);
						i++;
					}
					if (i < 250)
					{
#endif
						gchar *message = g_strdup_printf ("%s>%s (%s) %s '%d'\n",
							(cmddata->extshell) ? "sh" : "", cmddata->command,
						rt->pidstr, _("returned"), cmddata->exit);
						gdk_threads_enter ();
						e2_output_print (rt->current_tab, message, rt->pidstr,
							TRUE, "small", (cmddata->exit == 0) ? "green" : "red", NULL);
						gdk_threads_leave ();
						g_free (message);
#ifdef E2CMDTYPEB
					}
#endif
				}
		//CHECKME sometimes (how?) we can end this func without breaking here
				printd (DEBUG, "Exiting command select loop");
				break;
#ifndef E2CMDTYPEB
			}
			else	//CHECKME maybe not really needed ?
			{
				printd (DEBUG, "scanning child pipes again after watch function detected process %s completion flag", rt->pidstr);
				//do one last quick scan in case of race between SIGCHILD and pipes interrogation
				maxwait = 0;
				waitptr = &wait;
				goto reselect;
			}
#endif
		}
#ifdef DEBUG_MESSAGES
		else
		{
	//		if (selcount == 0 || !(outread || errread))
	//			printd (DEBUG, "no pipe from child was actually read");
			if (selcount == 0)
				printd (DEBUG, "selected 0 fd's");
			if (!outread)
				printd (DEBUG, "flag outread FALSE");
			if (!errread)
				printd (DEBUG, "flag errread FALSE");
		}
#endif
	//CHECKME can we do this without so many loops ? SIGPOLL ?
	//CHECKME for sync commands, omit WNOHANG ?
/*
		if (maxwait < 10)
		{	//this is the end of the first pipes-scan
			maxwait = 10;	//revert to slower polling after 1st pass
//			cmddata->pollcount = -1;	//flag that the pipes have been polled now,
									//(so it's ok for timer to re-enable main-thread SIGIO)
			//ostensibly this should have no impact on main-thread signals, but
			//for some kernels it does seem to speed up termination
			//note that this will probably stuff up the blockage refcount!
//			_e2_command_unblock_childsignal (); //allow any child's status change to be noticed
//			printd (DEBUG, "SIGCHILD re-enabled in watch function");
		}
THIS RELIES ON select() returning regularly ...
		if (cmddata->sync)
		{
			GdkDisplay *display =
			gdk_display_manager_get_default_display (gdk_display_manager_get());
			GdkEvent *event, *event2;
			//check for cancellation request
			//callbacks do not work, we must poll the events queue
			if ((event = gdk_display_get_event (display)) != NULL)	//peek won't work
			{
				if (event->type == GDK_KEY_PRESS)
				{
					if (((GdkEventKey*)event)->keyval == GDK_Control_L
						|| ((GdkEventKey*)event)->keyval == GDK_Control_R)
					{
						event2 = event;
						if ((event = gdk_display_get_event (display)) != NULL
							 && ((GdkEventKey*)event)->keyval == GDK_c
							 && ((GdkEventKey*)event)->state & GDK_CONTROL_MASK)
						{
							printd (DEBUG, "SIGTERM sent to child process group");
							kill (-pid, SIGTERM);
						}
						else
						{
							if (event != NULL)
							{
								gdk_display_put_event (display, event);
								gdk_event_free (event);
							}
							gdk_display_put_event (display, event2);
							gdk_event_free (event2);
						}
					}
					else if (((GdkEventKey*)event)->keyval == GDK_c
						&& (((GdkEventKey*)event)->state & GDK_CONTROL_MASK)
						//&& ((GdkEventKey*)event)->window == ?
					)
					{
						printd (DEBUG, "SIGTERM sent to child process group");
						kill (-pid, SIGTERM);
					}
//FIXME reinstating irrelevant events blocks handling of relevant ones
					else
						gdk_display_put_event (display, event);
				}
				else
					gdk_display_put_event (display, event);

				gdk_event_free (event);
			}
		}
*/
	}

#ifdef USE_GLIB2_10
	g_slice_free1 (READBUFSIZE, buffer);
#else
	g_free (buffer);
#endif
//	printd (DEBUG, "End command watch");
	return (GINT_TO_POINTER (rt->ex.command.exit));
}
/**
@brief run command @a command
Assumes BGL is closed upon entry
@param command the command string, used in messages, utf8
@param args pointer to NULL-terminated command arguments array, each localised
@param cwd path of directory to use as CWD for the command, utf8
@param sync TRUE to run @a command in foreground, FALSE for background
@param show TRUE to show @a command and its exit status in the output pane
@param extshell TRUE if @a command is to be run in external shell

@return the exit code of the command, > 0 if error
*/
static gint _e2_command_fork (gchar *command, gchar **args, const gchar *cwd,
	gboolean sync, gboolean show, gboolean extshell)
{
	//there are no embedded tests for command existence, so we need to do that
	if (!g_path_is_absolute (args[0]))
	{
#ifdef E2_VFSTMP
	if supporting get-and-exec, then local $PATH may be irrelevant
	need relevant PlaceInfo to sort this out, might not be curr_view->spacedata->workplace
	cwd needs to be a VPATH*
#endif
		gchar *path = g_find_program_in_path (args[0]);
		if (path != NULL)
		{
			g_free (path);
		}
		else
		{
			gchar *utf = F_DISPLAYNAME_FROM_LOCALE (args[0]);
			gchar *msg = g_strdup_printf (_("Command '%s' - %s"), utf, g_strerror (errno));
			e2_output_print_error (msg, TRUE);
			F_FREE (utf);
			return 1;
		}
	}

#ifdef E2_VFS
	VPATH ddata;
#endif
	gchar *message;
	pid_t pid;
/*
//#include <termios.h>

	struct termios shell_tmodes;
	pid_t shell_pgid;
	gboolean interactive_shell = isatty (STDIN_FILENO);
	if (interactive_shell)
	{
		//loop until we are in the foreground
		while (tcgetpgrp (STDIN_FILENO) != (shell_pgid = getpgrp ()))
			kill (-shell_pgid, SIGTTIN);

/ *		//ignore interactive and job-control signals
		signal (SIGINT, SIG_IGN);
		signal (SIGQUIT, SIG_IGN);
		signal (SIGTSTP, SIG_IGN);
		signal (SIGTTIN, SIG_IGN);
		signal (SIGTTOU, SIG_IGN);
		signal (SIGCHLD, SIG_IGN);
* /
		//put ourselves in our own process group
		shell_pgid = getpid ();
		if (setpgid (shell_pgid, shell_pgid) < 0)
		{
			printd (DEBUG,"Could not put shell into its own process group");
			return FALSE;
		}
		//grab control of the terminal
		tcsetpgrp (STDIN_FILENO, shell_pgid);
		//save default terminal attributes for shell
		tcgetattr (STDIN_FILENO, &shell_tmodes);
	}
*/
	//setup to write to child's stdin & read child's stdout, stderr
	gint stdin_pipe[2];
	gint stdout_pipe[2];
	gint stderr_pipe[2];
	if (pipe (stdin_pipe) < 0)	//no need to worry about VFS
	{
		goto launch_error;
	}
	if (pipe (stdout_pipe) < 0)
	{
		close (stdin_pipe[INWARDS]);
		close (stdin_pipe[OUTWARDS]);
		goto launch_error;
	}
	if (pipe (stderr_pipe) < 0)
    {
		close (stdin_pipe[INWARDS]);
		close (stdin_pipe[OUTWARDS]);
		close (stdout_pipe[INWARDS]);
		close (stdout_pipe[OUTWARDS]);
		goto launch_error;
	}
	E2_TaskRuntime *rt;
	//keep trying this until data set fn not busy
	//real pid is set later
	while ((rt = e2_task_set_data (0, (sync) ? E2_TASKTYPE_SYNC : E2_TASKTYPE_ASYNC,
		command)) == NULL);
	if (rt == GINT_TO_POINTER (1))
	{
		close (stdin_pipe[INWARDS]);
		close (stdin_pipe[OUTWARDS]);
		close (stdout_pipe[INWARDS]);
		close (stdout_pipe[OUTWARDS]);
		close (stderr_pipe[INWARDS]);
		close (stderr_pipe[OUTWARDS]);
		goto launch_error;
	}

	_e2_command_block_childsignal ();
	gdk_threads_leave ();

	if ((pid = fork ()) == 0)
	{	//child
		close (stdin_pipe[OUTWARDS]);
		close (stdout_pipe[INWARDS]);
		close (stderr_pipe[INWARDS]);

		pid = getpid ();	//get real pid
		if (setpgid (pid, pid) < 0)
		{
			printd (DEBUG,"Could not put child into its own process group");
			//FIXME warn user about this
//			_exit (2);
		}

		//arrange stdio via parent
/* seems to do nothing
	Setting the O_ASYNC flag for the read end of a pipe causes a signal
	(SIGIO by default) to be generated when new input becomes available on the pipe.
	On Linux, O_ASYNC is supported for pipes only since kernel 2.6.
	If the pipe is full, then a write(2) will block or fail, depending on whether
	the O_NONBLOCK flag is set
		gint flags = fcntl (stdout_pipe[OUTWARDS], F_GETFL);
		if (flags != -1)
			fcntl (stdout_pipe[OUTWARDS], F_SETFL, flags | O_NONBLOCK | O_ASYNC);
		flags = fcntl (stderr_pipe[OUTWARDS], F_GETFL);
		if (flags != -1)
			fcntl (stderr_pipe[OUTWARDS], F_SETFL, flags | O_NONBLOCK | O_ASYNC);
*/
		//(these fd assignments redirect child's stdin, stdout, stderr streams too)
		TEMP_FAILURE_RETRY (dup2 (stdin_pipe[INWARDS], STDIN_FILENO));
		//redirect output to parent
		TEMP_FAILURE_RETRY (dup2 (stdout_pipe[OUTWARDS], STDOUT_FILENO));
		TEMP_FAILURE_RETRY (dup2 (stderr_pipe[OUTWARDS], STDERR_FILENO));
//close on exit flags ?
		//setup crash if the parent exits and we later write to a pipe
		signal (SIGPIPE, SIG_DFL);

		_e2_command_unblock_childsignal ();

#ifdef E2_VFS
		if (curr_view->spacedata != NULL)
		{
#ifdef E2_VFSTMP
	if supporting get-and-exec, and getting is slow, some cd-race might occur
	need relevant PlaceInfo which might not be curr_view->spacedata->workplace
#endif
			//there's no native analog for the vdir, so go here...
			//all command args need to have been modified accordingly, by the command interpreter
			ddata.localpath = curr_view->spacedata->workplace;
			ddata.spacedata = NULL;
			if (!e2_fs_chdir_local (&ddata E2_ERR_NONE()))
				execvp (args[0], args);	//should not return
		}
		else	//native execution
#endif
		{
			if (cwd == NULL)
				cwd = curr_view->dir;
			if (e2_fs_chdir ((gchar *)cwd E2_ERR_NONE()))
			{
	//			printd (DEBUG, "Child will run '%s'", args[0]);
				execvp (args[0], args);	//should not return
			}
		}
		_exit (1);
		//end of child process
	}
	gdk_threads_enter ();
	if (pid < 0)
	{	//fork error
		close (stdin_pipe[INWARDS]);
		close (stdin_pipe[OUTWARDS]);
		close (stdout_pipe[INWARDS]);
		close (stdout_pipe[OUTWARDS]);
		close (stderr_pipe[INWARDS]);
		close (stderr_pipe[OUTWARDS]);
		_e2_command_unblock_childsignal ();
		rt->status = E2_TASK_FAILED;
		rt->ex.command.exit = 128;
		goto launch_error;
	}
	//parent process
	rt->status = E2_TASK_RUNNING;
	rt->pid = (glong) pid;
	rt->pidstr = g_strdup_printf ("%ld", rt->pid);
	E2_CommandTaskData *cmddata = &rt->ex.command;
	cmddata->child_stdin_fd = stdin_pipe[OUTWARDS];	//setup for later sending to child
	cmddata->child_stdout_fd = stdout_pipe[INWARDS];	//setup for later receiving from child
	cmddata->child_stderr_fd = stderr_pipe[INWARDS];	//setup for later receiving from child
//	FILE *child_stdin_stream = fdopen (stdin_pipe[OUTWARDS], "w");
//	FILE *child_stdout_stream = fdopen (stdout_pipe[INWARDS], "r");
//	FILE *child_stderr_stream = fdopen (stderr_pipe[INWARDS], "r");
//	cmddata->pollcount = 0;	//not ready to enable SIGCHILD yet NOT USED NOW
	cmddata->sync = sync;
	cmddata->extshell = extshell;
	cmddata->show = show;
#ifdef DEBUG_MESSAGES
	if (sync)
		printd (DEBUG, "synchronously run command %s", args[0]);
	else
		printd (DEBUG, "asynchronously run command %s", args[0]);
#endif
	if (show && e2_option_bool_get ("fileop-show"))
	{
		message = g_strconcat ((extshell) ? "sh>" : ">", command, NULL);
		e2_output_print (rt->current_tab,
			message, rt->pidstr, FALSE, "bold", "blue", NULL);
		g_free (message);
		message = g_strdup_printf ("  (%s)\n", rt->pidstr);
		e2_output_print (rt->current_tab,
			message, rt->pidstr, FALSE, "small", "blue", NULL);
		g_free (message);
		WAIT_FOR_EVENTS;
	}

//	_e2_command_unblock_childsignal (); //allow child exit to be noticed SEE TIMER

	close (stdin_pipe[INWARDS]);
	close (stdout_pipe[OUTWARDS]);
	close (stderr_pipe[OUTWARDS]);

/* seems to do nothing
	Setting the O_ASYNC flag for the read end of a pipe causes a signal
	(SIGIO by default) to be generated when new input becomes available on the pipe.
	On Linux, O_ASYNC is supported for pipes only since kernel 2.6.

	//make pipes non-blocking, so we can read big buffers without having
	//to use fionread() to prevent blocking
	gint flags = fcntl (stdout_pipe[INWARDS], F_GETFL);
	if (flags != -1)
		fcntl (stdout_pipe[INWARDS], F_SETFL, flags | O_NONBLOCK | O_ASYNC);
	flags = fcntl (stderr_pipe[INWARDS], F_GETFL);
	if (flags != -1)
		fcntl (stderr_pipe[INWARDS], F_SETFL, flags | O_NONBLOCK | O_ASYNC);
*/
/*	prefer to poll pipes at least once before unblocking SIGCHILD in this thread
	(ostensibly it's not valid to unblock SIGCHILD in another thread where watching
	happens, but for some kernels that does seem to help, so unblocking also happens
	in _e2_command_watch())
	So for async at least we use a timer which checks a flag set in _e2_command_watch()
	CHECKME can timer persist past rt destruction at session end ? etc ?
	Timer interval is compromise between prompt cleanup when there is little or no
	initial input, and the amount of interruption of the initial input loop when
	that involves a lot of printing - so we use a 2-stage timer
*/
	pthread_t wthreadID;
	if (sync)
	{
		gdk_threads_leave ();	//BGL management happens downstream
		if (pthread_create (&wthreadID, NULL, (gpointer)_e2_command_watch, rt))
		{
			_e2_command_unblock_childsignal ();
			gdk_threads_enter ();
			goto launch_error;
		}
		//FIXME unblock-timer won't work until we go back to gtk
//		usleep (10000);
		_e2_command_unblock_childsignal (); //allow any child's status change to be noticed
		//FIXME allow command output to be printed while waiting for completion
		//probably by running as for async, but blocking start of any other action
		// or command (except send to child)
		pthread_join (wthreadID, NULL);
		gdk_threads_enter ();
		return cmddata->exit;
	}
	else	//async execution
	{
//		g_timeout_add_full (G_PRIORITY_HIGH, 2,
//			(GSourceFunc) _e2_command_checkblock, &cmddata->pollcount, NULL);
		pthread_attr_t attr;
		pthread_attr_init (&attr);
		pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED);
		if (pthread_create (&wthreadID, &attr, (gpointer)_e2_command_watch, rt))
		{
			_e2_command_unblock_childsignal ();
			goto launch_error;
		}
		_e2_command_unblock_childsignal ();
		return 0;
	}

launch_error:
#ifdef E2_VFS
# ifdef E2_VFSTMP
	if supporting get-and-exec, and getting is slow, some cd-race might occur
	need relevant PlaceInfo which might not be curr_view->spacedata->workplace
# endif
	ddata.localpath = args[0];
	ddata.spacedata = curr_view->spacedata;
#endif
	gdk_threads_leave ();	//downstream does mutex management
	e2_fs_error_simple (_("Error while launching '%s'"),
#ifdef E2_VFS
		&ddata);
#else
		args[0]);
#endif
	gdk_threads_enter ();
	return 1;
}
/**
@brief pipe utf-8 string @a message to process @a pid
The specified process must be a child of the current e2 instance
@param pid the id of the destination process, or 0 for the last-created child
@param message, utf8 string, to send to process @a pid
@return TRUE if the string was sent without error
*/
static gboolean _e2_command_send_to_pid (glong pid, gchar *message)
{
	printd (DEBUG, "_e2_command_send_to_pid (pid:%d,messge:%s)", pid, message);
	gchar *msg2 = NULL;
	E2_TaskRuntime *rt;

	if (pid == 0)
	{
		rt = e2_task_find_last_running_child (FALSE);
		if (rt == NULL)
			msg2 = g_strdup (_("Cannot find last child process"));
		else
			pid = rt->pid;	//in case of error message
	}
	else
	{
		rt = e2_task_find_running_task (pid);
		if (rt == NULL)
			msg2 = g_strdup_printf (_("Cannot find child process with pid %ld"), pid);
	}
	if (rt != NULL && rt->action)
		msg2 = g_strdup_printf (_("Cannot communicate to process %ld"), pid);

	if (msg2 != NULL)
	{
		e2_output_print_error (msg2, TRUE);
		return FALSE;
	}

	//CHECKME required message encoding ?
	msg2 = g_strconcat (message, "\n", NULL);
	size_t size = strlen (msg2);
	ssize_t sent = write (rt->ex.command.child_stdin_fd, msg2, size);
	if (sent != size)	//FIXME handle -1 error or keep trying to write it all
	{
		g_free (msg2);
		msg2 = _("Failed writing to child");
		e2_output_print (rt->current_tab, msg2, rt->pidstr, TRUE, E2_ERRORTAGS, NULL);
		e2_utils_beep ();
		return FALSE;
	}
	else if (rt->ex.command.show)
		//report what has been sent FIXME what if it's a password ?
		e2_output_print (rt->current_tab, msg2, rt->pidstr, FALSE, NULL);

	g_free (msg2);
	return TRUE;
}
#else //ndef E2_NEW_COMMAND =======================
/**
@brief process non-error-iochannel data
@param ioc the channel passing the data
@param cond flags indicating the condition(s) that have been satisfied
@param rt pointer to data struct for the command to which the channel applies
@return FALSE if the event source should be removed
*/
static gboolean _e2_command_watch_std (GIOChannel *ioc, GIOCondition cond,
	E2_TaskRuntime *rt)
{
	return _e2_command_watch (ioc, cond, rt, FALSE);
}
/**
@brief process error-iochannel data
@param ioc the channel passing the data
@param cond flags indicating the condition(s) that have been satisfied
@param rt pointer to data struct for the command to which the channel applies
@return FALSE if the event source should be removed
*/
static gboolean _e2_command_watch_err (GIOChannel *ioc, GIOCondition cond,
	E2_TaskRuntime *rt)
{
	return _e2_command_watch (ioc, cond, rt, TRUE);
}
/**
@brief process iochannel data

Data are grabbed in parcels up to 4095 bytes, which are then separated
into lines (\n), and those lines are sent to the output printer
Will shut the channel down upon error

@param ioc the channel event source
@param cond flags indicating the condition(s) that have been satisfied
@param rt pointer to data struct for the command to which the channel applies
@param error TRUE if this call relates to the error channel
@return FALSE if the event source should be removed
*/
static gboolean _e2_command_watch (GIOChannel *ioc, GIOCondition cond,
	E2_TaskRuntime *rt, gboolean error)
{
	printd (DEBUG, "_e2_command_watch (ioc:,cond:%x,rt:,%s channel)", cond, (error) ? "stderr":"stdout");
	E2_CommandTaskData *cmddata = &rt->ex.command;
	if (cond & (G_IO_IN | G_IO_PRI))
	{
		gchar buf[5000];	//space (rounded) to read 4096 and add trailing \0
		//buf[0] = buf[4096] = '\0';
		gsize len = 0;

//		_e2_command_block_childsignal ();

		GIOStatus ret;
reread:
		ret = g_io_channel_read_chars (ioc, buf, 4096 * sizeof(gchar), &len, NULL);
		if (ret == G_IO_STATUS_NORMAL && len > 0)	//len==0 is also a proxy for ret == G_IO_STATUS_EOF
		{
//			if (cmddata->show || error)
//			{
//				if (len < 4096)
					*(buf + len) = '\0';
				gdk_threads_enter ();
				if (error)
					e2_output_print (rt->current_tab, buf, rt->pidstr,
						FALSE, E2_ERRORTAGS, NULL);
				else
					e2_output_print (rt->current_tab, buf, rt->pidstr,
						FALSE, NULL);
				gdk_threads_leave ();
/* line-by-line processing needed if we want to parse and handle CR's BS's etc
				{
					gchar save;	//ignore compiler warning
					gchar *sep;
					gchar *print = buf;
					gboolean more = TRUE;
					while (more)
					{
						//always ascii \n, don't need utf8 search
						if ((sep = strchr (print, '\n')) != NULL)
						{
							sep++;
							save = *sep;
							*sep = '\0';
						}
						else
							more = FALSE;

						//printd (DEBUG, "printing for tab %x", rt->current_tab);
						if (error)
							e2_output_print (rt->current_tab, print, rt->pidstr,
								FALSE, E2_ERRORTAGS, NULL);
						else
							e2_output_print (rt->current_tab, print, rt->pidstr,
								FALSE, NULL);
						if (more)
						{
							*sep = save;
							print = sep;
						}
					}
				}
*/
//			}

//			_e2_command_unblock_childsignal ();
			return TRUE;
		}
		else if (ret == G_IO_STATUS_AGAIN)
			goto reread;

//		_e2_command_unblock_childsignal ();
	}

	if (cond & (G_IO_ERR | G_IO_HUP | G_IO_NVAL))
	{
		gint fd;
		if (error)
		{	//shutting down child's error channel normally with flag G_IO_HUP
//			printd (DEBUG, "stderr iochannel cleanup");
//			rt->status = E2_TASK_COMPLETED;
			//possible race here - SIGCHILD handler may be called after this
			//so store a dummy exit value if that handler has not logged the real value
			if ((cond & (G_IO_ERR | G_IO_NVAL))
				&& rt->status != E2_TASK_COMPLETED)
			{
				rt->status = E2_TASK_INCOMPLETE;	//ensure this
				cmddata->exit = 1;
//				printd (DEBUG, "child status 1 stored");
			}
//			else
//				printd (DEBUG, "child status NOT stored here");

			fd = g_io_channel_unix_get_fd (ioc);
			g_io_channel_shutdown (ioc, TRUE, NULL);
//			g_io_channel_unref (ioc); the return FALSE has this effect
			e2_fs_safeclose (fd);
			//shutdown the output channel
//			if (cmddata->to_child != NULL)
//			{
				fd = g_io_channel_unix_get_fd (cmddata->to_child);
				g_io_channel_shutdown (cmddata->to_child, FALSE, NULL);
				g_io_channel_unref (cmddata->to_child); //like source-removal
				e2_fs_safeclose (fd);
//			}
		}
		else
		{	//shutting down child's stdout channel normally with flag G_IO_HUP
//			printd (DEBUG, "stdout iochannel cleanup");
//			_e2_command_block_childsignal ();

			fd = g_io_channel_unix_get_fd (ioc);
			//shutdown the error input channel
			g_io_channel_shutdown (ioc, TRUE, NULL);
//			g_io_channel_unref (ioc); the return FALSE has this effect
			e2_fs_safeclose (fd);

//irrelevant for *NIX g_spawn_close_pid (rt->pid);
			//possible race here - SIGCHILD handler may be called after this
			//so store a dummy exit value if that handler has not logged the real value
			if (!(cond & (G_IO_ERR | G_IO_NVAL))
				&& rt->status != E2_TASK_COMPLETED)
			{
				cmddata->exit = 0;
//				printd (DEBUG, "child status 0 stored");
			}
//			else
//				printd (DEBUG, "child status NOT stored here");

			if (cmddata->show && e2_option_bool_get ("fileop-show"))
			{
				gchar *message = g_strdup_printf ("%s%s (%s) %s '%d'\n",
					(cmddata->extshell) ? "sh>" : ">", cmddata->command,
					rt->pidstr, _("returned"), cmddata->exit);
				gdk_threads_enter ();
				e2_output_print (rt->current_tab, message, rt->pidstr, TRUE, "small",
					(cmddata->exit == 0) ? "green" : "red", NULL);
				gdk_threads_leave ();
				g_free (message);
				printd (DEBUG, "child status printed");
			}
			else
			{	//this is a hack to get small amounts of command-output text to
				//display when there's no completion message
				gdk_threads_enter ();
				e2_output_print (rt->current_tab, "", rt->pidstr, FALSE, NULL);
				gdk_threads_leave ();
			}

//			_e2_command_unblock_childsignal ();
		}
		return FALSE;	//removes the source
	}
	return TRUE;	//should never get to here
}
/* *
@brief for a sync command, get the real pid before the child command is executed
@param rt pointer to task data struct for the command
@return
*/
/*not called in sync command, dunno why
static void _e2_command_set_childdata (E2_TaskRuntime *rt)
{
	if (1)	//(rt->ex.command<is-sync>)
	{
		rt->pid = (glong) getpid ();
		printd (DEBUG, "command child pid is %ld", rt->pid);
		//FIXME setup to watch descendants stdout, stderr
	}
} */
/**
@brief set up channel for receiving data from a child process
This is called twice for each async child (for child's stdout and stderr channels)
@param fd descriptor for the channel (from g_spawn_async_with_pipes())
@param cond flags setting the mode of channel operation
@param func the function to be called when @a cond is satisfied
@param rt pointer to data struct for the command to which the channel applies
@return the channel
*/
static GIOChannel *_e2_command_setup_watch_channel
	(gint fd, GIOCondition cond, gpointer func, E2_TaskRuntime *rt)
{
	GIOChannel *ioc = g_io_channel_unix_new (fd);
	// Set IOChannel encoding to none, to make it fit for binary data
	//(which seems to mean that _any_ encoding will work)
	g_io_channel_set_encoding (ioc, NULL, NULL);
	g_io_channel_set_buffered (ioc, FALSE);
	g_io_channel_set_close_on_unref (ioc, TRUE);
	g_io_add_watch_full (ioc, e2_option_int_get ("command-watch-priority"),
		cond, (GIOFunc) func, rt, NULL);
	//undo reference added by g_io_add_watch ()
	g_io_channel_unref (ioc);
	return ioc;
}
/*
static gboolean _e2_command_watch_out (GIOChannel *ioc, GIOCondition cond,
	E2_TaskRuntime *rt)
{
	printd (DEBUG, "command can be written to");
	return TRUE;
} */
/**
@brief asynchronously run shell command
This sets the executed command's CWD to curr_view->dir
Expects BGL closed
@param command the command string, used in messages, utf8
@param args pointer to command arguments array, each localised
@param cwd path of directory to use as CWD for the command, utf8
@param show TRUE to show @a command and its pid in the output pane
@param extshell TRUE when command is to be run in external shell

@return the pid of process running the command, or 0 if there's an error
*/
//FIXME when this sort of mechanism is nested in a child process, that process hangs
#if E2_DEBUG_LEVEL > 2
GIOChannel *stdoutchannel;
GIOChannel *stderrchannel;
#endif
static gint _e2_command_run_async (gchar *command, gchar **args, const gchar *cwd,
	gboolean show, gboolean extshell)
{
	printd (DEBUG, "asynchronously run command %s", command);
	_e2_command_block_childsignal ();	//prevent feedback from child until ready to get it

	if (cwd == NULL)
		cwd = curr_view->dir;
	gchar *local;
#ifdef E2_VFS
	//for a non-native place, there is no analog for curr_view->dir, so go elsewhere
	//all args need to have been conformed, by the command interpreter
	local = (curr_view->spacedata == NULL) ?
		F_FILENAME_TO_LOCALE (cwd) : curr_view->spacedata->workplace;
#else
	local = F_FILENAME_TO_LOCALE (cwd);
#endif
	GPid pid = 0;
	gint stdinfd = 0, stdoutfd = 0, stderrfd = 0;	//file descriptors to write to and read from child
	GError *error = NULL;
	g_spawn_async_with_pipes (local, args, NULL,
		G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_SEARCH_PATH,
		NULL, NULL, &pid, &stdinfd, &stdoutfd, &stderrfd, &error);
#ifdef E2_VFS
	if (curr_view->spacedata == NULL)
#endif
		F_FREE (local);

	//CHECKME revert CWD to curr_view->dir if that still exists ?

	if (error != NULL)
	{
		gchar *message = g_strconcat (extshell ? "sh>" : ">", command, " ", NULL);
		//always use the current tab at the start of the command
		e2_output_print (&app.tab, message, NULL, TRUE, "bold", "blue", NULL);
		g_free (message);
		message = g_strconcat (error->message, ".", NULL);
		e2_output_print_error (message, TRUE);
		g_error_free (error);
		_e2_command_unblock_childsignal ();
		return 0;
	}

	E2_TaskRuntime *rt;
	//keep trying this until data set fn not busy
	while ((rt = e2_task_set_data ((glong)pid, E2_TASKTYPE_ASYNC, command)) == NULL);

	if (rt == GINT_TO_POINTER (1))
	{
		//CHECKME cleanup the spawn ?
		_e2_command_unblock_childsignal ();
		return 0;
	}
	rt->ex.command.show = show;
	rt->ex.command.extshell = extshell;

	//channel for getting the child's stdout
#if E2_DEBUG_LEVEL > 2
	stdoutchannel =
#endif
	_e2_command_setup_watch_channel (stdoutfd,
		G_IO_IN | G_IO_PRI | G_IO_HUP,
		_e2_command_watch_std, rt);
	//channel for getting the child's stderr
#if E2_DEBUG_LEVEL > 2
	stderrchannel =
#endif
	_e2_command_setup_watch_channel (stderrfd,
		G_IO_IN | G_IO_PRI | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
		_e2_command_watch_err, rt);

	//channel for sending to child stdin
	//FIXME do this only if/when needed ?
	GIOChannel *ioc = g_io_channel_unix_new (stdinfd);
	g_io_channel_set_encoding (ioc, NULL, NULL);	//binary (=any) encoding
	g_io_channel_set_buffered (ioc, FALSE);
	g_io_channel_set_close_on_unref (ioc, TRUE);
//	g_io_add_watch_full (ioc, e2_option_int_get ("command-watch-priority"),
//		G_IO_OUT, (GIOFunc) _e2_command_watch_out, rt, NULL);
	//undo reference added by g_io_add_watch ()
//	g_io_channel_unref (ioc);
	rt->ex.command.to_child = ioc;

	if (show && e2_option_bool_get ("fileop-show"))
	{
		gchar *message = g_strconcat (extshell ? "sh>" : ">", command, NULL);
		e2_output_print (rt->current_tab, message, rt->pidstr, FALSE,
			"bold", "blue", NULL);
		g_free (message);
		message = g_strdup_printf ("  (%d)\n", pid);
		e2_output_print (rt->current_tab, message, rt->pidstr, FALSE,
			"small", "blue", NULL);
		g_free (message);
	}

	_e2_command_unblock_childsignal ();

	return pid;
}
/**
@brief run command @a command synchronously (block until it's finished)
This sets the executed command's CWD to curr_view->dir
Expects BGL to be closed
@param command the command string, used in messages, utf8
@param args pointer to command arguments array, each localised
@param cwd path of directory to use as CWD for the command, utf8
@param show TRUE to show @a command and its exit status in the output pane
@param extshell TRUE when command is to be run in external shell

@return the exit code of the command, > 0 if error
*/
static gint _e2_command_run_sync (gchar *command, gchar **args, const gchar *cwd,
	gboolean show, gboolean extshell)
{
	printd (DEBUG, "synchronously run command %s", command);
	//we dont get real pid's from static commands, so we fake
	//by using -ve ones in simple sequence
	static glong fakepid = 0;
	fakepid--;

	gchar *message;
	E2_TaskRuntime *rt;

	//keep trying this until data set fn not busy
	//the child-signal handler will not be able to find this data, due to fake pid
	while ((rt = e2_task_set_data (fakepid, E2_TASKTYPE_SYNC, command)) == NULL);

	if (rt == GINT_TO_POINTER (1))
	{
		//CHECKME warn user ?
		fakepid++;
		return 1;
	}
	//but mark-names starting with "-" are confusing so substitute "s"
	*(rt->pidstr) = 's';
	rt->ex.command.show = show;
	rt->ex.command.extshell = extshell;

	if (show && e2_option_bool_get ("fileop-show"))
	{
		gchar *message = g_strconcat (extshell ? "sh>" : ">", command, NULL);
		e2_output_print (rt->current_tab, message, rt->pidstr, FALSE,
			"bold", "blue", NULL);
		g_free (message);
		message = g_strdup_printf ("  (%s)\n", rt->pidstr);
		e2_output_print (rt->current_tab, message, rt->pidstr, FALSE,
			"small", "blue", NULL);
		g_free (message);
		WAIT_FOR_EVENTS;
	}

	if (cwd == NULL)
		cwd = curr_view->dir;
//	GError *error;
	gint exit;
	gchar *sout = NULL, *serr = NULL;
	gchar *local;
#ifdef E2_VFS
	//for a non-native place, there is no analog for curr_view->dir, so go elsewhere
	//all args need to have been conformed, by the command interpreter
	local = (curr_view->spacedata == NULL) ?
		F_FILENAME_TO_LOCALE (cwd) : curr_view->spacedata->workplace;
#else
	local = F_FILENAME_TO_LOCALE (cwd);
#endif
	_e2_command_block_childsignal ();

	rt->status = E2_TASK_RUNNING;
	gboolean success = g_spawn_sync (local, args, NULL, G_SPAWN_SEARCH_PATH,
//DOES NOTHING		(GSpawnChildSetupFunc) _e2_command_set_childdata, rt,
		NULL, NULL,
		&sout, &serr, &exit, NULL);	//&error);
	//no rt cleanup after error
	rt->status = (success) ? E2_TASK_COMPLETED : E2_TASK_FAILED;

	//CHECKME revert CWD to curr_view->dir if that still exists ?

	_e2_command_unblock_childsignal ();

	rt->ex.command.exit = exit;

	if (success && show && sout != NULL && *sout != '\0')
	{
		e2_output_print (rt->current_tab, sout, rt->pidstr, FALSE, NULL);
	}
	if (success && serr != NULL && *serr != '\0')
	{
		e2_output_print_error (serr, FALSE);
	}
	if (sout != NULL)
		g_free (sout);
	if (serr != NULL)
		g_free (serr);
#ifdef E2_VFS
	if (curr_view->spacedata == NULL)
#endif
		F_FREE (local);

	if (success && show && e2_option_bool_get ("fileop-show"))
	{
		message = g_strdup_printf ("%s%s (%s) %s '%d'\n", (extshell) ? "sh>" : ">",
			command, rt->pidstr, _("returned"), WEXITSTATUS (exit));
		e2_output_print (rt->current_tab, message, rt->pidstr, TRUE, "small",
			(WEXITSTATUS (exit) == 0) ? "green" : "red", NULL);
		g_free (message);
	}
	if (!success)
	{
		message = g_strdup_printf (_("Strange error: could not run '%s'"), command);
		e2_output_print_error (message, TRUE);
	}

	return WEXITSTATUS (exit);
}
/**
@brief pipe utf-8 string @a message to process @a pid
The specified process must be a child of the current e2 instance
@param pid the id of the destination process, or 0 for the last-created child
@param message utf string to send to process @a pid
@return TRUE if the string was sent without error
*/
static gboolean _e2_command_send_to_pid (glong pid, gchar *message)
{
	gchar *errmsg;
	printd (DEBUG, "e2_command_send_to_pid (pid:%d,messge:%s)", pid, message);
	E2_TaskRuntime *rt;
	rt = (pid == 0) ?
		e2_task_find_last_running_child (FALSE):
		e2_task_find_running_task (pid);
	if (rt == NULL || rt->action)
	{
		errmsg = g_strdup_printf (_("The process with pid %ld is not our child"), pid);
		e2_output_print_error (errmsg, TRUE);
		return FALSE;
	}

	gsize size;
	GIOStatus stat;
	GError *error = NULL;
	//CHECKME what if the process doesn't understand utf-8 ?
	stat = g_io_channel_write_chars (rt->ex.command.to_child, message, -1, &size, &error);
	if (error != NULL)
	{
		errmsg = g_strdup_printf (_("Failed writing to child: %s"), error->message);
		printd (WARN, errmsg);
		e2_output_print (rt->current_tab, errmsg, rt->pidstr, TRUE, E2_ERRORTAGS, NULL);
		g_error_free (error);
		g_free (errmsg);
		return FALSE;
	}
	error = NULL;
	stat = g_io_channel_write_chars (rt->ex.command.to_child, "\n", 1, &size, &error);
	if (error != NULL)
	{
		errmsg = g_strdup_printf (_("Failed writing to child: %s"), error->message);
		printd (WARN, errmsg);
		e2_output_print (rt->current_tab, errmsg, rt->pidstr, TRUE, E2_ERRORTAGS, NULL);
		g_error_free (error);
		g_free (errmsg);
		return FALSE;
	}
	else if (rt->ex.command.show)
	{	//signal what has been sent
		e2_output_print (rt->current_tab, message, rt->pidstr, TRUE, NULL);
	}
	return TRUE;
}
#endif //def E2_NEW_COMMAND ========================
/**
@brief kill child process with id @a pid
@param pid the id of the process we're looking for
@return TRUE if a child process with that pid was found and killed
*/
gboolean e2_command_kill_child (guint pid)
{
	E2_TaskRuntime *rt = e2_task_find_running_task ((glong)pid);
	if (!(rt == NULL || rt->action))
	{	//it's one of ours
		if (!kill ((pid_t) pid, SIGTERM))	//or SIGKILL
		{
			rt->pid = -2L;	//no more matching
			rt->status = E2_TASK_ABORTED;
			return TRUE;
		}
		e2_output_print_strerrno ();
	}
	return FALSE;
}
/**
@brief check whether process with id @a pid is running
@param pid the id of the process we're looking for
@return TRUE if a process with that pid is running
*/
gboolean e2_command_find_process (guint pid)
{
	pid_t result = waitpid (pid, NULL, WNOHANG);
	return (result == 0);	//will be -1 if finished before, result if finished now
}
/**
@brief update all relevant child foreground-tab pointers after output pane tab change
@param currenttab pointer to data for tab to be replaced
@param replacetab pointer to data for tab to be substituted for @a currenttab
@return
*/
void e2_command_retab_children (E2_OutputTabRuntime *currenttab,
	E2_OutputTabRuntime *replacetab)
{
	E2_TaskRuntime *rt;
	GList *member;
#ifndef E2_NEW_COMMAND
	_e2_command_block_childsignal ();
#endif
	pthread_mutex_lock (&task_mutex);
	member = app.taskhistory;
	pthread_mutex_unlock (&task_mutex);
	while (member != NULL)
	{
		rt = (E2_TaskRuntime *) member->data;
		if (rt != NULL
			&& (rt->status == E2_TASK_RUNNING || rt->status == E2_TASK_PAUSED))
		{
			if (rt->current_tab == currenttab)
				rt->current_tab = rt->background_tab;
			else if (rt->background_tab == replacetab)
				rt->current_tab = currenttab;
		}
		pthread_mutex_lock (&task_mutex);
		member = member->next;
		pthread_mutex_unlock (&task_mutex);
	}
#ifndef E2_NEW_COMMAND
	_e2_command_unblock_childsignal ();
#endif
}
/**
@brief update tab ptrs of children using a deleted tab
@param currenttab the deleted tab's data struct
@param replacetab the replacement tab's data struct
@return
*/
void e2_command_retab2_children (E2_OutputTabRuntime *currenttab,
	E2_OutputTabRuntime *replacetab)
{
	E2_TaskRuntime *rt;
	GList *member;
#ifndef E2_NEW_COMMAND
	_e2_command_block_childsignal ();
#endif
	pthread_mutex_lock (&task_mutex);
	member = app.taskhistory;
	pthread_mutex_unlock (&task_mutex);
	while (member != NULL)
	{
		rt = (E2_TaskRuntime *) member->data;
		if (rt != NULL
			&& (rt->status == E2_TASK_RUNNING || rt->status == E2_TASK_PAUSED)
			&& rt->background_tab == currenttab)
		{
			rt->background_tab = replacetab;
			rt->current_tab = replacetab;
		}
		pthread_mutex_lock (&task_mutex);
		member = member->next;
		pthread_mutex_unlock (&task_mutex);
	}
#ifndef E2_NEW_COMMAND
	_e2_command_unblock_childsignal ();
#endif
}
/**
@brief clear pending commands from queue
This is an action, results shown in the current output tab
@param from UNUSED the activated widget
@param art UNUSED runtime data sent to this action
@return TRUE if anything pending is removed
*/
gboolean e2_command_clear_pending (gpointer from, E2_ActionRuntime *art)
{
	E2_TaskRuntime *rt;
	GList *member;
	gboolean retval = FALSE;
#ifndef E2_NEW_COMMAND
	_e2_command_block_childsignal ();
#endif
	//to avoid iteration issues, a 2-stage process ...
	pthread_mutex_lock (&task_mutex);
	member = app.taskhistory;
	pthread_mutex_unlock (&task_mutex);
	while (member != NULL)
	{
		rt = (E2_TaskRuntime *) member->data;
		if (rt->status <= E2_TASK_QUEUED)	//Q or none
		{
			//rt->pidstr not set yet for queued items
			if (rt->action)
			{
				g_free (rt->ex.action.currdir);
				g_free (rt->ex.action.othrdir);
				if (rt->ex.action.names != NULL)
					e2_fileview_clean_selected (rt->ex.action.names);
				if (rt->ex.action.rt_data != NULL)
					g_free (rt->ex.action.rt_data);
			}
			else
			{
				g_free (rt->ex.command.command);
				g_free (rt->ex.command.currdir);
#ifdef E2_COMMANDQ
				g_free (rt->ex.command.othrdir);
				if (rt->ex.command.names != NULL)	//active pane selected items array
				{
					g_ptr_array_foreach (rt->ex.command.names, (GFunc)g_free, NULL);
					g_ptr_array_free (rt->ex.command.names, TRUE);
				}
				if (rt->ex.command.othernames != NULL)	//inactive pane selected items array
				{
					g_ptr_array_foreach (rt->ex.command.othernames, (GFunc)g_free, NULL);
					g_ptr_array_free (rt->ex.command.othernames, TRUE);
				}
# ifdef E2_VFSTMP
	//FIXME clear "fstab" data for 2 panes if command is queued and needs CWD or has %fFdDpP
# endif
#endif
			}
			DEALLOCATE (E2_TaskRuntime, rt);
			member->data = NULL;
			retval = TRUE;
		}
		pthread_mutex_lock (&task_mutex);
		member = member->next;
		pthread_mutex_unlock (&task_mutex);
	}
	pthread_mutex_lock (&task_mutex);
	app.taskhistory = g_list_remove_all (app.taskhistory, NULL);
	pthread_mutex_unlock (&task_mutex);
#ifndef E2_NEW_COMMAND
	_e2_command_unblock_childsignal ();
#endif
	return retval;
}
/**
@brief list queued pending commands
This is an action, results shown in the current output tab
@param from UNUSED the activated widget
@param art UNUSED runtime data sent to this action
@return TRUE if there is any pending command
*/
static gboolean _e2_command_list_pending (gpointer from, E2_ActionRuntime *art)
{
//	printd (DEBUG, "e2_command_list_pending (data:)");
	//find start of waiting item(s) in Q
	E2_TaskRuntime *rt;
	GList *member;
	//quick check for something relevant
	pthread_mutex_lock (&task_mutex);
	member = app.taskhistory;
	pthread_mutex_unlock (&task_mutex);
	while (member != NULL)
	{
		rt = (E2_TaskRuntime *) member->data;
		if (rt->status <= E2_TASK_QUEUED)
			break;
		pthread_mutex_lock (&task_mutex);
		member = member->next;
		pthread_mutex_unlock (&task_mutex);
	}
	if (member == NULL)
	{
		e2_output_print (&app.tab, _("nothing is waiting"), NULL, TRUE, NULL);
		return FALSE;
	}
	gchar *srcdir, *shortdir, *message;
	message = _("command");
	e2_output_print (&app.tab, message, NULL, TRUE, "bold", "uline", NULL);
#ifndef E2_NEW_COMMAND
	_e2_command_block_childsignal ();
#endif
	while (member != NULL)
	{
		rt = (E2_TaskRuntime *) member->data;
		if (rt != NULL && rt->status <= E2_TASK_QUEUED)
		{
			if (rt->action)
			{
				E2_ActionTaskData *atask = &rt->ex.action;
				srcdir = F_FILENAME_FROM_LOCALE (atask->currdir);
				shortdir = e2_utils_str_shorten (srcdir, CMDNAMELEN, E2_DOTS_START);
				gchar *subject;
				if (g_str_has_prefix (atask->action->name, _A(5)))
				{	//this was a "file-action"
					subject =
					(atask->rt_data == NULL || *((gchar *)atask->rt_data) == '\0') ?
						_("<selected items>") : (gchar *)atask->rt_data;
				}
				else
					subject = "";	//this results in a redundant space in the line
				message = g_strdup_printf ("%s %s || %s",
					atask->action->name, subject, shortdir);
				F_FREE (srcdir);
			}
			else
			{
				shortdir =
				e2_utils_str_shorten (rt->ex.command.currdir, CMDNAMELEN, E2_DOTS_START);
				gchar *shortcmd =
				e2_utils_str_shorten (rt->ex.command.command, CMDNAMELEN, E2_DOTS_END);
				message = g_strdup_printf ("%s || %s", shortcmd, shortdir);
				g_free (shortcmd);

			}
			e2_output_print (&app.tab, message, NULL, TRUE, NULL);
			g_free (shortdir);
			g_free (message);
		}
		pthread_mutex_lock (&task_mutex);
		member = member->next;
		pthread_mutex_unlock (&task_mutex);
	}
#ifndef E2_NEW_COMMAND
	_e2_command_unblock_childsignal ();
#endif
	e2_output_print_end (&app.tab, FALSE);

	return TRUE;
}
/**
@brief count the active child processes of this session
This counts child processes, if @a countcmds is TRUE, and actions (queued or
not) in the task history list and marked as currently-running, or (if
@a countpaused is TRUE, paused
@param countcmds TRUE to include all active commands in the count
@param countpaused TRUE to include paused actions in the count
@return the number of currently-running commands and actions
*/
guint e2_command_count_running_tasks (gboolean countcmds, gboolean countpaused)
{
	guint i = 0;
	E2_TaskRuntime *rt;
	GList *member;
	pthread_mutex_lock (&task_mutex);
	member = app.taskhistory;
	pthread_mutex_unlock (&task_mutex);
	while (member != NULL)
	{
		rt = (E2_TaskRuntime *) member->data;
		if (rt != NULL
			&& (rt->action || (countcmds && !rt->action))
			&& (rt->status == E2_TASK_RUNNING || (countpaused && (rt->status == E2_TASK_PAUSED))))
			i++;
		pthread_mutex_lock (&task_mutex);
		member = member->next;
		pthread_mutex_unlock (&task_mutex);
	}
	return i;
}
/**
@brief list the active child processes (commands and actions) of the current session
This is an action, results shown in the current tab
@param from UNUSED the activated widget
@param art UNUSED runtime data sent to this action
@return TRUE if there are any children
*/
static gboolean _e2_command_list_children (gpointer from, E2_ActionRuntime *art)
{
//	printd (DEBUG, "_e2_command_list_children (data:)");
	if (e2_command_count_running_tasks (TRUE, TRUE) == 0)
	{
		e2_output_print (&app.tab, _("nothing is running"), NULL, TRUE, NULL);
		return FALSE;
	}
	gchar *srcdir, *shortdir, *message;
	message = _("  pid || directory || command");
	e2_output_print (&app.tab, message, NULL, TRUE, "bold", "uline", NULL);

	E2_TaskRuntime *rt;
	GList *member;
#ifndef E2_NEW_COMMAND
	_e2_command_block_childsignal ();
#endif
	pthread_mutex_lock (&task_mutex);
	member = app.taskhistory;
	pthread_mutex_unlock (&task_mutex);
	while (member != NULL)
	{
		rt = (E2_TaskRuntime *) member->data;
		if (rt != NULL
			&& (rt->status == E2_TASK_RUNNING || rt->status == E2_TASK_PAUSED))
		{
			if (rt->action)
			{
				E2_ActionTaskData *atask = &rt->ex.action;
				srcdir= F_FILENAME_FROM_LOCALE (atask->currdir);
				shortdir = e2_utils_str_shorten (srcdir, CMDNAMELEN, E2_DOTS_START);
				message = g_strdup_printf (" %6s || %s || %s %s", rt->pidstr,
					shortdir, atask->action->name,
					(atask->rt_data == NULL) ?
						_("<selected items>") : (gchar *)atask->rt_data);
				F_FREE (srcdir);
			}
			else
			{
				shortdir = e2_utils_str_shorten (rt->ex.command.currdir,
					CMDNAMELEN, E2_DOTS_START);
				message = g_strdup_printf (" %6s || %s || %s", rt->pidstr, shortdir,
					rt->ex.command.command);
			}

			e2_output_print (&app.tab, message, NULL, TRUE, NULL);
			g_free (shortdir);
			g_free (message);
		}
		pthread_mutex_lock (&task_mutex);
		member = member->next;
		pthread_mutex_unlock (&task_mutex);
	}
#ifndef E2_NEW_COMMAND
	_e2_command_unblock_childsignal ();
#endif
	e2_output_print_end (&app.tab, FALSE);

	return TRUE;
}
/**
@brief list the command history of the current session
This is an action, results shown in the current output tab
@param from UNUSED the activated widget
@param art UNUSED data sent to this action
@return TRUE if there is a history
*/
static gboolean _e2_command_list_history (gpointer from, E2_ActionRuntime *art)
{
//	printd (DEBUG, "e2_command_list_history (data:)");
	gchar *srcdir, *shortdir, *message;
	message = _("command || directory || result");
	e2_output_print (&app.tab, message, NULL, TRUE, "bold", "uline", NULL);

	E2_TaskRuntime *rt;
	GList *member;
#ifndef E2_NEW_COMMAND
	_e2_command_block_childsignal ();
#endif
	pthread_mutex_lock (&task_mutex);
	member = app.taskhistory;
	pthread_mutex_unlock (&task_mutex);
	while (member != NULL)
	{
		rt = (E2_TaskRuntime *) member->data;
		if (rt != NULL && rt->status >= E2_TASK_COMPLETED)
		{
			if (rt->action)
			{
				E2_ActionTaskData *atask = &rt->ex.action;
				srcdir = F_FILENAME_FROM_LOCALE (atask->currdir);
				shortdir = e2_utils_str_shorten (srcdir, CMDNAMELEN, E2_DOTS_START);
				gchar *subject;
				if (g_str_has_prefix (atask->action->name, _A(5)))
				{	//this was a "file-action"
					subject =
					(atask->rt_data == NULL || *((gchar *)atask->rt_data) == '\0') ?
						_("<selected items>") : (gchar *)atask->rt_data;
				}
				else
					subject = "";	//this results in a redundant space in the line
				message = g_strdup_printf ("%s %s || %s || %s",
					atask->action->name, subject, shortdir,
					(atask->result) ? _("OK") : _("error"));
				F_FREE (srcdir);
			}
			else
			{
				E2_CommandTaskData *ctask = &rt->ex.command;
				shortdir = e2_utils_str_shorten ((gchar *)ctask->currdir,
					CMDNAMELEN, E2_DOTS_START);
				gchar *shortcmd =
				e2_utils_str_shorten (rt->ex.command.command, CMDNAMELEN, E2_DOTS_END);
				if (ctask->exit == 0)
					message = g_strdup_printf (
					"%s || %s || %s", shortcmd, shortdir, _("OK"));
				else
					message = g_strdup_printf (
					"%s || %s || %s %d", shortcmd, shortdir, _("error"), ctask->exit);
				g_free (shortcmd);
			}

			e2_output_print (&app.tab, message, NULL, TRUE, NULL);
			g_free (shortdir);
			g_free (message);
		}
		pthread_mutex_lock (&task_mutex);
		member = member->next;
		pthread_mutex_unlock (&task_mutex);
	}
#ifndef E2_NEW_COMMAND
	_e2_command_unblock_childsignal ();
#endif
	e2_output_print_end (&app.tab, FALSE);

	return TRUE;
}
/**
@brief set or display value of @a variable stored in variables list
If @a value is "" or NULL, any matching variable is removed from the list
If @a var_name is "", any variable whose value is @a value is displayed.
If @a var_name is and @a valuer are both "", all variables are displayed.
@param var_name utf variable-name string
@param value utf8 variable-value string
@return
*/
static void _e2_command_handle_variable_value (gchar *var_name, gchar *value)
{
	gchar *this;
	GList *member;
	g_strstrip (var_name);
	g_strstrip (value);
	if (*var_name == '\0')	//display
	{
		if (*value == '\0')
		{	//just "="
			for (member = variables; member != NULL; member = member->next)
				e2_output_print (&app.tab, (gchar *)member->data, NULL, FALSE, NULL);
			e2_output_print_end (&app.tab, FALSE);
		}
		else
		{
			this = g_strconcat ("=", value, NULL);
			for (member = variables; member != NULL; member = member->next)
			{
				if (g_str_has_suffix ((gchar*)member->data, this))
				{
					e2_output_print (&app.tab, (gchar *)member->data, NULL, FALSE, NULL);
					break;
				}
			}
			g_free (this);
		}
	}
	else	//set or clear
	{
		gchar *prefix = g_strconcat (var_name, "=", NULL);
		for (member = variables; member != NULL; member = member->next)
		{
			this = (gchar *)member->data;
			if (g_str_has_prefix (this, prefix))
			{
				g_free (this);
				if (*value == '\0')
					variables = g_list_delete_link (variables, member);
				else
					member->data = g_strconcat (prefix, value, NULL);
				return;
			}
		}
		this = g_strconcat (prefix, value, NULL);
		variables = g_list_append (variables, this);
	}
}
/**
@brief get value of @a variable stored in variables list
@param var_name utf variable-name string
@return pointer to value string, or NULL if none found
*/
const gchar *e2_command_get_variable_value (gchar *var_name)
{
	gchar *this, *retval;
	gchar *prefix = g_strconcat (var_name, "=", NULL);
	GList *member;
	for (member = variables; member != NULL; member = member->next)
	{
		this = (gchar *)member->data;
		if (g_str_has_prefix (this, prefix))
		{
			retval = this + strlen (prefix);
			return retval;
		}
	}
	return NULL;
}
/**
@brief check whether @a command has any "modifiers"
@param command utf command string
@param macros pointer to flag, set FALSE if leading '!' found
@param shell pointer to flag, set TRUE if leading '>' found
@param wait pointer to flag, set TRUE if leading '|' found
@param show pointer to flag, set FALSE if trailing '&' found
@return integer, the byte-offset into @a command where the next char to be processed is
*/
static gint _e2_command_run_checks (gchar *command,
	gboolean *macros, gboolean *shell, gboolean *wait, gboolean *show)
{
	gint retval = 0;
	gboolean done = FALSE;
	//checks here are all for single-gchar characters, no need for utf functions
	while (!done)
	{
		switch (command[0])
		{
			case ' ':	//skip leading whitespace
			case '\t':
				break;
			case '!':
				*macros = FALSE;
				break;
			case '>':
				*shell = TRUE;
				break;
			case '|':
				*wait = TRUE;
				break;
			default:
				done = TRUE;
				break;
		}
		if (!done)
		{
			retval++;
			command++;
		}
	}
	if (!*shell)
	{	//force shell for commands that we know must go to shell
		//(redirection chars etc)
		//this will be corrected later for <pseudo-actions>
		gchar *s = e2_utils_bare_strchr (command, '>');
		if (s != NULL && s > command && *(s+1) != '\0')
			*shell = TRUE;
		else if ((s = e2_utils_bare_strchr (command, '|')) != NULL
			&& s > command && *(s+1) != '\0')
			*shell = TRUE;
		else if ((s = e2_utils_bare_strchr (command, '<')) != NULL
			&& s > command && *(s+1) != '\0')
			*shell = TRUE;
		else if ((s = e2_utils_bare_strchr (command, '`')) != NULL
				&& (s = e2_utils_bare_strchr (s+1, '`')) != NULL)
			*shell = TRUE;
	}
	if (!*shell && g_str_has_suffix (command, "&"))
	{
		*show = FALSE;
		command[strlen (command) - sizeof (gchar)] = '\0';
	}
	return retval;
}
/**
@brief run a single command @a raw, after interpreting its contents
Downstream functions expect BGL to be closed
This function does not set or use CWD, but downstream functions for commands
(not actions) use curr_view->dir
Command modifiers (!>&) are interpreted.
$... variables are interpreted
%... macros are expanded unless the "no-expand" modifier '!' is present at
the start of @a raw.
Any 'pid:' directive is interpreted. The 'pid' string must describe a base 10 integer.
Any "\" that doesn't follows another "\" is simply removed.
Any "~" followed by "/" is interpreted as $HOME, as is a "~" by itself.
If * or ? is in any argument, that will be expanded, if possible, into a
series of matching item-names in the active directory.
"cd" commands that are not run in a separate shell just change the
dir shown in the active pane. In that case the new dir must be
separated by one or more ' ' chars (ie no tabs), or "cd" alone is
interpreted as "cd $HOME".
Command arguments for must be separated by one or more ' ' chars (ie no tabs)
NOTE used to return pid of process running the command (if any)
@param raw utf-8 command string
@param cwd utf8 path string, maybe with trailer, to use as (native) CWD when running @a command NULL for curr_view->dir
@param range flags for the scope of the command

@return 0 for success, >0 for error
*/
//FIXME more consistent returned value
static gint _e2_command_run_single (gchar *raw, const gchar *cwd, E2_CommandRange range)
{
	//CHECKME should variables be volatilized for threaded usage ?
#ifdef E2_VFSTMP
//BIG FIXME handling command arguments etc when active pane is vdir
#endif
	static gchar *prior_cmd = NULL;	//for preventing recursive alias substitution
	gchar *command1, *command2, *freeme;
	gint result;
	gboolean expand_macros = TRUE;
	gboolean run_in_shell = FALSE;
	gboolean wait = FALSE;
	gboolean show = TRUE;
//#ifdef E2_NEW_COMMAND
	gboolean free1 = FALSE;
//#endif
	//1st, find how the command is to be run
	command1 = raw + _e2_command_run_checks
		(raw, &expand_macros, &run_in_shell, &wait, &show);
	//(wait and show will be overridden, later, if range == E2_COMMAND_RANGE_FILE_ACTION)

	//replace any alias if so desired
	if (//CHECKME range != E2_COMMAND_RANGE_FILE_ACTION //this is not an action on selected items
		//&& expand_macros	//no instruction to not expand macros
		//&&
		e2_option_bool_get ("command-use-aliases")	//alias use is expected
		&& (prior_cmd == NULL || strstr (command1, prior_cmd) == NULL)	//ok to recurse aliases
	)
	{
		freeme = e2_alias_apply (command1, &app.aliases);
		//alias may have substituted a joined command
		if (e2_utils_bare_strchr (freeme, ';') != NULL)	//if always ascii ;, don't need g_utf8_strchr()
		{
			//one or more elements of the joined command may have the alias that's
			//just been replaced, in which case we must prevent a recursive loop
			command2 = e2_utils_find_whitespace (command1);
			gint len = (command2 == NULL) ? strlen (command1) : (command2-command1);
			if (prior_cmd != NULL)
				g_free (prior_cmd);
			prior_cmd = g_strndup (command1, len);
			//go restart the command process
#ifdef E2_COMMANDQ
			result = e2_command_run (freeme, range, TRUE);
#else
			result = e2_command_run (freeme, range);
#endif
			g_free (freeme);
			g_free (prior_cmd);
			prior_cmd = NULL;
			return result;
		}

		//check command operation mode again, in case alias changed it
		command1 = freeme + _e2_command_run_checks (freeme,
			&expand_macros, &run_in_shell, &wait, &show);
	}
	else
		freeme = NULL;

	E2_Action *action = e2_action_check (command1);
	gboolean command_is_action = (action != NULL);
	if (command_is_action)
	{
		run_in_shell = FALSE;	//over-ride any explicit or applied flag
		if (action->exclude & E2_ACTION_EXCLUDE_ACCEL)
		{
			gchar *msg = g_strdup_printf (_("Cannot run '%s'"), command1);
			e2_output_print_error (msg, TRUE);
			if (freeme != NULL)
				g_free (freeme);
			return 1;
		}
		//set the correct action-range according to whether the
		//name begins with "file" and has no argument
		if (g_str_has_prefix (command1, _A(5)))
		{
			command2 = e2_utils_find_whitespace (command1);
			if (command2 == NULL || e2_utils_pass_whitespace (command2) == NULL)
				range = E2_COMMAND_RANGE_FILE_ACTION;
		}
		else if (range == E2_COMMAND_RANGE_FILE_ACTION)
			range = E2_COMMAND_RANGE_DEFAULT;
	}
//#ifdef E2_NEW_COMMAND
	else	//for commands, relative (and non-absolute?) paths in the command need to be interpreted
	{
		if (g_str_has_prefix (command1, "./") || g_str_has_prefix (command1, "../"))
		{
			command2 = e2_utils_find_whitespace (command1);
			if (command2 != NULL)
				*command2 = '\0';

			gchar *s1 = e2_utils_translate_relative_path (
#ifdef E2_VFSTMP
			//FIXME which CWD ?
			OR should we "get" and run an executable in an active-pane vdir
			or some dir realtive to that?
#else
			curr_view->dir,
#endif
			command1);
			//strip trailer from the expansion
			*(s1 + strlen (s1) - sizeof(gchar)) = '\0';

			if (command2 != NULL)
			{
				*command2 = ' ';	//any whitespace will do
				command2 = g_strconcat (s1, command2, NULL);
				g_free (s1);
			}
			else
				command2 = s1;

			command1 = command2;
			free1 = TRUE;
		}
	}
//#endif
	//set some default operating modes for selected-items commands
	//(overrides show flag in raw or alias, if any)
	if (!command_is_action && range == E2_COMMAND_RANGE_FILE_ACTION)
	{
	//CHECKME is this of any real use ? it's intended mainly for actions
	//	wait = !e2_option_bool_get ("fileop-background");
		show = e2_option_bool_get ("fileop-show");
	}

	//if appropriate, arrange to append the selected item(s),
	//as the (or additional) command argument(s)
	if (range == E2_COMMAND_RANGE_FILE_ACTION	//this is an action on selected items
		&& expand_macros	//no instruction to not expand macros
		&& (!command_is_action || g_str_has_prefix (command1, _A(5))) //command or file action
		//there is no filelist macro already in the command
//		//FIXME support macros other than with english letters
		&& strstr (command1, "%f") == NULL
		&& strstr (command1, "%F") == NULL
		&& strstr (command1, "%p") == NULL
		&& strstr (command1, "%P") == NULL
	   )
		command2 = g_strconcat (command1, " %f", NULL);
	else
		command2 = g_strdup (command1);

	if (freeme != NULL)
		g_free (freeme);
//#ifdef E2_NEW_COMMAND
	if (free1 && command1 != freeme)
		g_free (command1);
//#endif

	//newly-allocated command2 is now the focus ..

	//replace variables etc
	command1 = e2_utils_replace_vars (command2);
	g_free (command2);
	//replace wildcards
	if (!run_in_shell && expand_macros)
		command1 = e2_utils_replace_wildcards (command1);

	//now newly-allocated command1 has focus, command2 is clean

	//if command is: cd [%]%f, when updir '../' line was clicked, that
	//item would not be available as a selected item, so ...
	if (g_str_has_prefix (command1, "cd "))
	{
		//FIXME support macros other than with english letters
		if (strstr (command1, "%f") != NULL || strstr (command1, "%p") != NULL)
		{
			FileInfo *info = e2_fileview_get_selected_first_local (curr_view, TRUE);
			if (info != NULL //something selected
				&& g_str_equal (info->filename, ".."))
			{
				g_free (command1);
				command1 = g_strdup ("cd ..");	//no translation
				expand_macros = FALSE;	//avoid error message
			}
		}
	}
	//expand macros, if wanted
	if (expand_macros)
	{
		command2 = e2_utils_expand_macros (command1, NULL);
		if (command2 == NULL)
		{
			e2_output_print_error (_("Failed to expand macros"), FALSE); //current tab
			result = 1;
			goto cleanup;
		}
		else if (command2 == GINT_TO_POINTER(1))
		{	//the user cancelled in a prompt macro
			result = 1;
			goto cleanup;
		}
		g_free (command1);
		command1 = command2;
	}

	//replace or clear any variable if so commanded
	if (!run_in_shell && (command2 = e2_utils_bare_strchr (command1, '=')) != NULL)
	{
		*command2 = '\0';
		//ensure not "=" in a command parameter
		gchar *s = e2_utils_find_whitespace (command1);
		if (s != NULL)
			s = e2_utils_pass_whitespace (s+1);
		if (s == NULL)
		{
			_e2_command_handle_variable_value (command1, command2+1);
			return 0;
		}
		*command2 = '=';
	}

	//interpret any 'pid:' directive
/*	the 'pid' string must describe a base 10 integer, no
	bigger than the max integer for the current system

	i.e. should be 5 bytes long (16385) for 32-bit systems,
	10 bytes for 64-bit systems
#if sizeof(gint) == 4
# define E2_PID_SIZE 5
#else
# define E2_PID_SIZE 10
#endif
*/
	command2 = NULL;
	if (range & E2_COMMAND_RANGE_TOCHILD)
	{
		command2 = e2_utf8_unescape (command1, ' ');
		result = (_e2_command_send_to_pid (0, command2)) ? 0 : 1;
		g_free (command2);
		goto cleanup;
	}
	if (//range != E2_COMMAND_RANGE_FILE_ACTION	//CHECKME why excluded ?
		//&&
		(command2 = strchr (command1, ':')) != NULL	//if always ascii ':', don't need g_utf8_strchr()
	)
	{
		if (command2 > command1)	// there was something ahead of the ':'
		{
			*command2 = '\0';
			gchar *end;
			glong i = strtol (command1, &end, 10);
			if (end == command2)
				//the whole string was used in conversion, so it was
				//a valid number and nothing else
			{
				command2 = e2_utf8_unescape (command2 + 1, ' ');
				printd (DEBUG, "sending '%s' to process '%s'", command2, command1);
				result = (_e2_command_send_to_pid (i, command2)) ? 0 : 1;	//+1 to skip the '\0'
				g_free (command2);
				goto cleanup;
			}
			*command2 = ':';
		}
	}
	//"cd" commands that are not run in a separate shell
	//just change the dir in the active pane
	if (!run_in_shell && g_str_has_prefix (command1, "cd"))	//no translation
	{	//note: tests here rely on ascii single-byte chars in command string
		gint len = strlen (command1);
		if (len > 3 && command1[2] == ' ')	//FIXME or \t ?
		{
			command2 = e2_utf8_unescape (command1+3, ' ');
			e2_pane_change_dir (NULL, command2);	//command1 is utf-8 or ascii
			g_free (command2);
			result = 0;
			goto cleanup;
		}
		if (len == 2)
		{
/*			gchar *home = (gchar *) g_get_home_dir ();
			e2_pane_change_dir (NULL, home);
*/
			e2_pane_change_dir (NULL, "~");
			result = 0;
			goto cleanup;
		}
	}
	else if /*(g_str_has_prefix (command1, "<"))
	{	//action
		if (strchr (command1, '.') == NULL)	//quick check
		{
			e2_output_print_error (_("You must specify an action to run, like '<file.copy'"), FALSE); //current tab
			result = 1;
			goto cleanup;
		}
		if (strchr (command1, '.') != NULL)	//quick check */
		(command_is_action)
		{
			gchar *gap = e2_utils_find_whitespace (command1);
			if (gap != NULL)
			{
				*gap = '\0';
				gap = e2_utils_pass_whitespace (gap+1);
				if (gap != NULL)
					gap = e2_utf8_unescape (gap, ' ');	//actions don't expect escaped whitespace
			}
			result = (e2_action_run_simple (command1, gap)) ?	//now gap points to args, or NULL
					0 : 1;
			if (gap != NULL)
				g_free (gap);
			goto cleanup;
		}
//	}

//	gboolean free;
	//CHECKME handle commands via Q processor thread ?
	gchar **args;
	gint argc;
	if (run_in_shell)
	{	//run in new shell
//		free = TRUE;	//free args, and separately free command1
		argc = 3;
		//construct freeable vector of args
//		args = g_malloc (sizeof (gchar *) * 4);
		args = NEW (gchar*, 4);
		args[0] = g_strdup (shellcmd);
		args[1] = g_strdup ("-c");
		args[2] = g_strdup (command1);
		args[3] = NULL;
	}
	else
	{	//run in same shell */
		//g_shell_parse_argv() leaves one of the args = command1
		//so don't trigger a double-free
	//	free = FALSE;	//free args, but not doubly-so for command1
		GError *error = NULL;
		//NB the glib 2.6 API doco is wrong about how this works !!
		g_shell_parse_argv (command1, &argc, &args, &error);
		if (error != NULL)
		{
			gchar *msg = g_strdup_printf (_("Failed parsing command '%s' - %s"),
				command1, error->message);
			e2_output_print_error (msg, TRUE);
			g_error_free (error);
			result = 1;
			goto cleanup;
		}
	}

#ifndef E2_FILES_UTF8ONLY
	if (!app.utf8_filenames)
	{
//		free = TRUE;	//free all args anyway
		//localise the actual command strings
		gchar *local;
		gchar **tmp = args;
		while (*tmp != NULL)
		{	//we always take a copy and strfreev later
			local = D_FILENAME_TO_LOCALE (*tmp);
			g_free (*tmp);
			*tmp = local;
			tmp++;
		}
	}
#endif
#ifdef E2_NEW_COMMAND
	result = _e2_command_fork (command1, args, cwd, wait, show, run_in_shell);
#else
	result = (wait) ?
		_e2_command_run_sync (command1, args, cwd, show, run_in_shell):
		_e2_command_run_async (command1, args, cwd, show, run_in_shell);
#endif
//	if (free)
		g_free (command1);
	g_strfreev (args);

#ifdef E2_NEW_COMMAND
	return result;	//((result == 0) ? 0 : 1); //exit code, 0 = ok
#else
	if (wait)
		return result;	//exit code, 0 = ok
	else
		return ((result == 0) ? 1 : 0);	//pid, >0 = ok
#endif

cleanup:
	g_free (command1);
	return result;
}
/**
@brief action to send a string to last-started child process, if any
Various checks are performed, to decide whether it's ok to send the command.
The string may be empty.
@param from the entry which has the string to send
@param art action runtime data

@return TRUE if action completed successfully, else FALSE
*/
static gboolean _e2_command_send_to_lastchild (gpointer from, E2_ActionRuntime *art)
{
	gchar *msg;
	//get the command string
	const gchar *command_raw = gtk_entry_get_text (GTK_ENTRY (from));
	//quick exit
	if (command_raw == NULL)	//cannot happen ?
		return FALSE;
//	if (strchr (command_raw, ';') != NULL)
//		goto error;
	if (e2_task_find_last_running_child (FALSE) == NULL)
		goto error;
	gchar *command = g_strdup (command_raw);
	//assume that an action string is not really meant to send to running command
	if (e2_action_check (command) != NULL)
	{
		g_free (command);
		goto error;
	}
//	tochild = TRUE;
	//this is an action, can only return T/F, so we lose the result of the command
	_e2_command_run_single (command, NULL, E2_COMMAND_RANGE_DEFAULT | E2_COMMAND_RANGE_TOCHILD);
//	tochild = FALSE;
	//we don't want any history, just clear the text
	gtk_entry_set_text (GTK_ENTRY (from), "");
	g_free (command);
	return TRUE;

error:
	msg =
	g_strdup_printf (_("Cannot send \"%s\" to a child process"), command_raw);
	e2_output_print_error (msg, TRUE);
	return FALSE;
}

  /****************/
 /**** public ****/
/****************/

/**
@brief run command @a command after interpreting its contents, and with CWD @a workdir

@a command may be a single command or a series joined by ';'
(any ';' inside single- or double-parentheses is ignored)
Normally, commands are run with CWD set to curr_view->dir. Here, we borrow
that string temporarily
The change of CWD will probably not persist for the duration of command execution
if the command is asynchronous
@param command utf8 string containing the command(s)
@param workdir utf8 path string, maybe with trailer, to use as (native) CWD when running @a command, NULL for curr_view->dir
@param range flags for the scope of the command (may be over-ridden, for actions)
@return integer return code from last command executed, 0 = success, > 0 on error
*/
gint e2_command_run_at (gchar *command, const gchar *workdir, E2_CommandRange range)
{
//E2_VFSTMP workdir probably needs to become a VPATH*
	//CHECKME should these variables be volatilized for threaded usage ?
	if ((command == NULL) || (*command == '\0'))
		return 1;
	g_strchug (command);	//command arg may be a path with intended trailing whitespace
	if (*command == '\0')
		return 1;
	printd (DEBUG, "e2_command_run_at (command:%s,range:%d)", command, range);

//FIXME umount commands sometimes should get eject appended - but what about aliases ?

	//parse & handle joined commands here
	//in particular, ignore separators inside parentheses
	if (strchr (command, ';') == NULL)	//if always ascii ;, don't need g_utf8_strchr()
		//no joined commands
		return _e2_command_run_single (command, workdir, range);
	//don't split joined commands if ">>" modifier-prefix is provided
	gchar *s = command;
	gboolean run_in_shell = FALSE, done = FALSE;
	while (*s != '\0')
	{
		switch (*s++)
		{
			case ' ':
			case '\t':
			case '!':
			case '|':
				break;
			case '>':
				if (*s == '>')
					run_in_shell = TRUE;
			default:
				done = TRUE;
				break;
		}
		if (done)
			break;
	}
	if (run_in_shell)
		return _e2_command_run_single (command, workdir, range);

	gint result = 0;
	gchar *p;
	s = command;
	while ((p = e2_utils_bare_strchr (s, ';')) != NULL)
	{
		//process this command
		*p = '\0';
		result = _e2_command_run_single (s, workdir, range);
		s = p; s++;	//resume from the next spot
		if (*s == '\0') //the ';' was at end of line
			return result;
	}
	//process the last (or only) command
	return (_e2_command_run_single (s, workdir, range));
}
/* *
NOW a #define
@brief run command @a command, after interpreting its contents

@a command may be a single command or a series joined by ';'
(any ';' inside single- or double-parentheses is ignored)
NOTE used to return command's pid
@param command utf string containing the command(s)
@param range flags for the scope of the command (may be over-ridden, for actions)

@return integer return code from last command executed, 0 = success, > 0 on error
*/
/*gint e2_command_run (gchar *command, E2_CommandRange range)
{
	return (e2_command_run_at (command, NULL, range));
} */
/**
@brief display commands help document
The document name is hard-coded with the help-file name
defined at build-time
@return
*/
void e2_command_output_help (void)
{	//check whether there's still an alias for the help command
	//(and BTW get a copy of the string, which needs to be freeable for e2_command_run)
	gchar *help_cmd = _("help");
	gchar *command = e2_alias_apply (help_cmd, &app.aliases);
	if (g_str_equal (help_cmd, command))
	{	//we can't find what the user has set for a help command
		//so use the default instead
		g_free (command);
		command = g_strconcat(_A(5),".",_A(103)," ",
			e2_option_str_get ("usage-help-doc"), " [commands]",NULL);	//file.view_at _I( helpfile titles not (yet?) translated
	}
#ifdef E2_COMMANDQ
	e2_command_run (command, E2_COMMAND_RANGE_DEFAULT, FALSE);
#else
	e2_command_run (command, E2_COMMAND_RANGE_DEFAULT);
#endif
	g_free (command);
}
/**
@brief register command-related actions
@return
*/
void e2_command_actions_register (void)
{
	//not action stuff, but this is a convenient spot for other init
//#ifndef E2_NEW_COMMAND
	_e2_command_set_sigchild_handler ();
//#endif
//#ifdef E2_NEW_COMMAND
//	_e2_command_set_sigpoll_handler ();
//#endif

	struct passwd *pw;
	pw = getpwuid (getuid ());
	if (pw != NULL)
	{
		gchar *baseshell = g_path_get_basename (pw->pw_shell);
		shellcmd = g_find_program_in_path (baseshell);
		g_free (baseshell);
		if (shellcmd == NULL)
			shellcmd = "/bin/sh";
	}
	else
		shellcmd = "/bin/sh";

	//setup waitpid() parameter, with check for glibc/kernel disconnect on WCONTINUED
	waitflags = WNOHANG
#ifdef WCONTINUED
			| WCONTINUED
#endif
			| WUNTRACED;
#ifdef WCONTINUED
	if (waitpid (-1, NULL, waitflags) < 0 && errno == EINVAL)
	{
		printd (DEBUG, "this kernel does not support WCONTINUED");
		waitflags = WNOHANG | WUNTRACED;
	}
#endif

#ifdef __linux__
# ifdef __WALL
//	waitflags = waitflags | __WALL | __WNOTHREAD; causes failure
	waitflags |= __WALL;
# endif
#endif

	gchar *action_name = g_strconcat(_A(7),".",_A(28),NULL);
	e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM,
		_e2_command_list_children, NULL, FALSE);
	action_name = g_strconcat(_A(7),".",_A(51),NULL);
	e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM,
		_e2_command_list_history, NULL, FALSE);
	action_name = g_strconcat(_A(7),".",_A(66),NULL);
	e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM,
		_e2_command_list_pending, NULL, FALSE);
	action_name = g_strconcat(_A(29),".",_A(66),NULL);
	e2_action_register_simple (action_name, E2_ACTION_TYPE_ITEM,
		e2_command_clear_pending, NULL, FALSE);
	action_name = g_strconcat(_A(1),".",_A(77),NULL);
	e2_action_register (action_name, E2_ACTION_TYPE_ITEM,
		_e2_command_send_to_lastchild, NULL, FALSE,
		E2_ACTION_EXCLUDE_TOOLBAR | E2_ACTION_EXCLUDE_MENU, NULL);
}
/**
@brief register command-related options
@return
*/
void e2_command_options_register (void)
{
	//no screen rebuilds needed after any change to these options
	gchar *group_name = g_strconcat(_C(6),":",_C(25),NULL); //_("commands:misc"
	e2_option_str_register ("command-xterm", group_name, _("x terminal emulator:"),
		_("This is the external command/application that will be be run when emelFM2 is asked to open a terminal"),
		NULL, "xterm",
		E2_OPTION_FLAG_BASIC | E2_OPTION_FLAG_FREEGROUP);
	e2_option_bool_register ("use-external-viewer", group_name, _("use external file-viewer"),
		_("If activated, the command entered below will be run to view file content, instead of launching the internal viewer"),
		NULL, FALSE,
		E2_OPTION_FLAG_BASIC);
	e2_option_str_register ("command-viewer", group_name, _("viewer command:"),
		_("This is a command to run an external application for viewing file content.\n"
		"The first selected item will be supplied as the first argument"),    //CHECKME = this is wrong ??
		"use-external-viewer", "gview",
		E2_OPTION_FLAG_BASIC);
	e2_option_bool_register ("use-external-editor", group_name, _("use external file-editor"),
		_("If activated, the command entered below will be run to edit file content, instead of launching the internal editor"),
		NULL, FALSE,
		E2_OPTION_FLAG_BASIC);
	e2_option_str_register ("command-editor", group_name, _("editor command:"),
		_("This is a command to run an external application for editing file content.\n"
		"The first selected item will be supplied as the first argument"),    //CHECKME = this is wrong ??
		"use-external-editor", "leafpad",
		E2_OPTION_FLAG_BASIC);
	e2_option_bool_register ("use-external-encoder", group_name, _("use external encoding converter"),
		_("If activated, the command entered below will be run, instead of using the internal conversion functions, to convert file character encoding when needed"),
		NULL, FALSE,
		E2_OPTION_FLAG_ADVANCED);
	gchar *defcmd = g_strconcat ("enca -L %{(languages)@", _("File encoding:"),"} -x UTF-8 <%p", NULL);
	e2_option_str_register ("command-encoder", group_name, _("converter command:"),
		_("A command which runs an external application to convert text encoding to UTF-8"),
		"use-external-encoder", defcmd,
		E2_OPTION_FLAG_ADVANCED);
	g_free (defcmd);
	e2_option_bool_register ("command-use-aliases", group_name, _("use aliases"),
		_("This is a general switch to turn on/off alias handling for commands"),
		NULL, TRUE,
		E2_OPTION_FLAG_ADVANCED);
	e2_option_bool_register ("transparent-dir-links", group_name, _("interpret 'relative' paths"),
		_("This enables correct interpretation of paths containing '..' etc. These might be typed in, or attached to a button, say"),
		NULL, TRUE,
		E2_OPTION_FLAG_ADVANCED);
	e2_option_bool_register ("command-persist", group_name, _("running commands survive shutdown"),
		_("If activated, commands that are still running will not be terminated at end of emelFM2 session"),
		NULL, TRUE,
		E2_OPTION_FLAG_ADVANCED);
#ifndef E2_NEW_COMMAND
	e2_option_int_register ("command-watch-priority", group_name, _("watch priority"),
		_("The watch priority of commands influences how fast program\n"
		"output is read from i/o channels. A too-high priority might\n"
		"decrease gui responsiveness and break automatic scrolling.\n"
		"(note: negative values mean high priority, positive values mean low priority)"),
		NULL, 300, -200, 400,
		E2_OPTION_FLAG_ADVANCED);
#endif
	//these options relate to actions, not commands
	group_name = g_strconcat(_C(6),":",_C(14),NULL);  //_("commands:file operations"
	e2_option_bool_register ("task-timeout-checks", group_name, _("stop after timeout"),
	_("If activated, each file operation will be terminated if not finished within the time-interval set below"),
	NULL, FALSE,
	E2_OPTION_FLAG_ADVANCED | E2_OPTION_FLAG_FREEGROUP);
	e2_option_int_register ("task-timeout-interval", group_name, _("timeout interval"),
	_("The interval (seconds) allowed to complete any file operation"),
	"task-timeout-checks", 20, 1, 3600,
	E2_OPTION_FLAG_ADVANCED);
	e2_option_bool_register ("confirm-delete", group_name, _("confirm any delete"),
		_("If activated, you will be asked for confirmation before actually deleting anything"), NULL, TRUE,
		E2_OPTION_FLAG_BASIC);
	e2_option_bool_register ("confirm-overwrite", group_name, _("confirm any overwrite"),
		_("If activated, you will be asked for confirmation before actually overwriting anything"), NULL, TRUE,
		E2_OPTION_FLAG_BASIC);
	e2_option_bool_register ("relative-symlinks", group_name, _("relative symlinks"),
		_("This gives each created symlink a relative path to its source, like '../../<otherbranch>/<file>', instead of a full path referenced to /"),
		NULL, TRUE,
		E2_OPTION_FLAG_ADVANCED);
}
