/* -*-c-*- */
/* This program 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 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "config.h"

#include <errno.h>
#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif

/* for F_CMD_ARGS */
#include "fvwm/fvwm.h"
#include "execcontext.h"
/* end of for CMD_ARGS */

/*for debug message*/
#include "fvwm.h"
#include "misc.h"
/* end of for debug message */

/* for get_current_read_file */
#include "read.h"
/* for busy cursor */
#include "cursor.h"

/* for Scr global */
#include "screen.h"

#include "externs.h"

/* for module syncronous */
#include "libs/ftime.h"
#include "libs/System.c"
#include "libs/envvar.h"
#include "libs/Parse.h"
#include "libs/Strings.h"
#include "libs/wild.h"
#include "libs/fvwmsignal.h"
#include "events.h"
#include "bindings.h"

/* for positive write */

#include "module_list.h"
#include "module_interface.h"
/*
 * Use POSIX behaviour if we can, otherwise use SysV instead
 * should this be here?
 */
#ifndef O_NONBLOCK
#  define O_NONBLOCK  O_NDELAY
#endif

#define MOD_NOGRABMASK(m) ((m)->xNoGrabMask)
#define MOD_SYNCMASK(m) ((m)->xSyncMask)

typedef struct
{
	unsigned long *data;
	int size;
	int done;
} mqueue_object_type;


/* the linked list pointers to the first and last modules */
static fmodule_list module_list = NULL;
/* keep to-be-deleted modules in a deathrow until they are deleted safely. */
static fmodule_list death_row = NULL;

/*
 * static functions
 */
static fmodule *module_alloc(void);
static void module_free(fmodule *module);

/*
 * list handling functions
 */
static inline void module_list_insert(fmodule *module, fmodule_list *list);
static inline fmodule *module_list_remove(fmodule *module, fmodule_list *list);
static inline void module_list_destroy(fmodule_list *list);
static inline int module_list_len(fmodule_list *list);

static void KillModuleByName(char *name, char *alias);
static char *get_pipe_name(fmodule *module);
static void DeleteMessageQueueBuff(fmodule *module);

static inline void msg_mask_set(
	msg_masks_t *msg_mask, unsigned long m1, unsigned long m2);
static void set_message_mask(msg_masks_t *mask, unsigned long msg);


void module_kill_all(void)
{
	module_list_destroy(&module_list);

	return;
}

static fmodule *module_alloc(void)
{
	fmodule *module;

	module = (fmodule *)safemalloc(sizeof(fmodule));
	MOD_SET_CMDLINE(module, 0);
	MOD_READFD(module) = -1;
	MOD_WRITEFD(module) = -1;
	fqueue_init(&MOD_PIPEQUEUE(module));
	msg_mask_set(&MOD_PIPEMASK(module), DEFAULT_MASK, DEFAULT_MASK);
	msg_mask_set(&MOD_NOGRABMASK(module), 0, 0);
	msg_mask_set(&MOD_SYNCMASK(module), 0, 0);
	MOD_NAME(module) = NULL;
	MOD_ALIAS(module) = NULL;

	return module;
}

/* closes the pipes and frees every data associated with a module record */
static void module_free(fmodule *module)
{
	if (module == NULL)
	{
		return;
	}
	close(MOD_WRITEFD(module));
	close(MOD_READFD(module));

	if (MOD_NAME(module) != NULL)
	{
		free(MOD_NAME(module));
	}
	if (MOD_ALIAS(module) != NULL)
	{
		free(MOD_ALIAS(module));
	}
	while (!FQUEUE_IS_EMPTY(&(MOD_PIPEQUEUE(module))))
	{
		DeleteMessageQueueBuff(module);
	}
	free(module);

	return;
}

static inline void module_list_insert(fmodule *module, fmodule_list *list)
{
	fmodule_store *new_store;
	DBUG("module_list_insert", "inserting module");
	if (module == NULL)
	{
		return;
	}
	new_store = (fmodule_store*)safemalloc(sizeof(fmodule_store));
	new_store->module = module;
	new_store->next = *list;
	*list = new_store;

	return;
}

static inline fmodule *module_list_remove(fmodule *module, fmodule_list *list)
{
	fmodule_store **position;

	if (module == NULL)
	{
		return NULL;
	}

	for (
		position = list; *position != NULL;
		position = &((*position)->next))
	{
		if ((*position)->module == module)
		{
			/* found it */
			fmodule_store *current;

			DBUG("module_list_remove", "Removing from module list");
			current = *position;
			*position = (*position)->next;
			free(current);

			return module;
		}
	}

	/* module not found */
	DBUG("module_list_remove", "Tried to remove a not listed module!");

	return NULL;
}

static inline void module_list_destroy(fmodule_list *list)
{
	fmodule_store *current;
	fmodule_store *next;

	for (current = *list; current != NULL; current = next)
	{
		next = current->next;
		module_free(current->module);
		free(current);
	}
	*list = NULL;

	return;
}

static fmodule *do_execute_module(
	F_CMD_ARGS, Bool desperate, Bool do_listen_only)
{
	int fvwm_to_app[2], app_to_fvwm[2];
	int i, val, nargs = 0;
	char *cptr = NULL;
	char **args = NULL;
	char *arg1 = NULL;
	char arg2[20];
	char arg3[20];
	char arg5[20];
	char arg6[20];
	char *token;
	extern char *ModulePath;
	Window win;
	FvwmWindow * const fw = exc->w.fw;
	fmodule *module;

	fvwm_to_app[0] = -1;
	fvwm_to_app[1] = -1;
	app_to_fvwm[1] = -1;
	app_to_fvwm[0] = -1;
	args = (char **)safemalloc(7 * sizeof(char *));
	/* Olivier: Why ? */
	/*   if (eventp->type != KeyPress) */
	/*     UngrabEm(); */
	if (action == NULL)
	{
		goto err_exit;
	}
	if (fw)
	{
		win = FW_W(fw);
	}
	else
	{
		win = None;
	}
	action = GetNextToken(action, &cptr);
	if (!cptr)
	{
		goto err_exit;
	}
	arg1 = searchPath(ModulePath, cptr, EXECUTABLE_EXTENSION, X_OK);
	if (arg1 == NULL)
	{
		/* If this function is called in 'desparate' mode this means
		 * fvwm is trying a module name as a last resort.  In this case
		 * the error message is inappropriate because it was most
		 * likely a typo in a command, not a module name. */
		if (!desperate)
		{
			fvwm_msg(
				ERR, "executeModule",
				"No such module '%s' in ModulePath '%s'",
				cptr, ModulePath);
		}
		goto err_exit;
	}
#ifdef REMOVE_EXECUTABLE_EXTENSION
	{
		char *p;

		p = arg1 + strlen(arg1) - strlen(EXECUTABLE_EXTENSION);
		if (strcmp(p, EXECUTABLE_EXTENSION) == 0)
		{
			*p = 0;
		}
	}
#endif

	/* I want one-ended pipes, so I open two two-ended pipes,
	 * and close one end of each. I need one ended pipes so that
	 * I can detect when the module crashes/malfunctions */
	if (do_listen_only == True)
	{
		fvwm_to_app[0] = -1;
		fvwm_to_app[1] = -1;
	}
	else if (pipe(fvwm_to_app) != 0)
	{
		fvwm_msg(ERR, "executeModule", "Failed to open pipe");
		goto err_exit;
	}
	if (pipe(app_to_fvwm) != 0)
	{
		fvwm_msg(ERR, "executeModule", "Failed to open pipe2");
		goto err_exit;
	}
	if (
		fvwm_to_app[0] >= fvwmlib_max_fd ||
		fvwm_to_app[1] >= fvwmlib_max_fd ||
		app_to_fvwm[0] >= fvwmlib_max_fd ||
		app_to_fvwm[1] >= fvwmlib_max_fd)
	{
		fvwm_msg(ERR, "executeModule", "too many open fds");
		goto err_exit;
	}

	/* all ok, create the space and fill up */
	module = module_alloc();

	MOD_NAME(module) = stripcpy(cptr);
	free(cptr);
	sprintf(arg2, "%d", app_to_fvwm[1]);
	sprintf(arg3, "%d", fvwm_to_app[0]);
	sprintf(arg5, "%lx", (unsigned long)win);
	sprintf(arg6, "%lx", (unsigned long)exc->w.wcontext);
	args[0] = arg1;
	args[1] = arg2;
	args[2] = arg3;
	args[3] = (char *)get_current_read_file();
	if (!args[3])
	{
		args[3] = "none";
	}
	args[4] = arg5;
	args[5] = arg6;
	for (nargs = 6; action = GetNextToken(action, &token), token; nargs++)
	{
		args = (char **)saferealloc(
			(void *)args, (nargs + 2) * sizeof(char *));
		args[nargs] = token;
		if (MOD_ALIAS(module) == NULL)
		{
			const char *ptr = skipModuleAliasToken(args[nargs]);

			if (ptr && *ptr == '\0')
			{
				MOD_ALIAS(module) = stripcpy(args[nargs]);
			}
		}
	}
	args[nargs] = NULL;

	/* Try vfork instead of fork. The man page says that vfork is better!
	 */
	/* Also, had to change exit to _exit() */
	/* Not everyone has vfork! */
	val = fork();
	if (val > 0)
	{
		/* This fork remains running fvwm */
		/* close appropriate descriptors from each pipe so
		 * that fvwm will be able to tell when the app dies */
		close(app_to_fvwm[1]);
		/* dont't care that this may be -1 */
		close(fvwm_to_app[0]);

		/* add these pipes to fvwm's active pipe list */
		MOD_WRITEFD(module) = fvwm_to_app[1];
		MOD_READFD(module) = app_to_fvwm[0];
		msg_mask_set(
			&MOD_PIPEMASK(module), DEFAULT_MASK, DEFAULT_MASK);
		free(arg1);
		if (DoingCommandLine)
		{
			/* add to the list of command line modules */
			DBUG("executeModule", "starting commandline module\n");
			MOD_SET_CMDLINE(module, 1);
		}

		/* make the PositiveWrite pipe non-blocking. Don't want to jam
		 * up fvwm because of an uncooperative module */
		if (MOD_WRITEFD(module) >= 0)
		{
			fcntl(MOD_WRITEFD(module), F_SETFL, O_NONBLOCK);
		}
		/* Mark the pipes close-on exec so other programs
		 * won`t inherit them */
		if (fcntl(MOD_READFD(module), F_SETFD, 1) == -1)
		{
			fvwm_msg(
				ERR, "executeModule",
				"module close-on-exec failed");
		}
		if (
			MOD_WRITEFD(module) >= 0 &&
			fcntl(MOD_WRITEFD(module), F_SETFD, 1) == -1)
		{
			fvwm_msg(
				ERR, "executeModule",
				"module close-on-exec failed");
		}
		/* module struct is completed, insert into the list */
		module_list_insert(module, &module_list);

		for (i = 6; i < nargs; i++)
		{
			if (args[i] != 0)
			{
				free(args[i]);
			}
		}
	}
	else if (val ==0)
	{
		/* this is the child */
		/* this fork execs the module */
#ifdef FORK_CREATES_CHILD
		/* dont't care that this may be -1 */
		close(fvwm_to_app[1]);
		close(app_to_fvwm[0]);
#endif
		fvmm_deinstall_signals();
		if (!Pdefault)
		{
			char visualid[32];
			char colormap[32];

			sprintf(
				visualid, "FVWM_VISUALID=%lx",
				XVisualIDFromVisual(Pvisual));
			flib_putenv("FVWM_VISUALID", visualid);
			sprintf(colormap, "FVWM_COLORMAP=%lx", Pcmap);
			flib_putenv("FVWM_COLORMAP", colormap);
		}
		else
		{
			flib_unsetenv("FVWM_VISUALID");
			flib_unsetenv("FVWM_COLORMAP");
		}

		/* Why is this execvp??  We've already searched the module
		 * path! */
		execvp(arg1,args);
		fvwm_msg(
			ERR, "executeModule", "Execution of module failed: %s",
			arg1);
		perror("");
		close(app_to_fvwm[1]);
		/* dont't care that this may be -1 */
		close(fvwm_to_app[0]);
#ifdef FORK_CREATES_CHILD
		exit(1);
#endif
	}
	else
	{
		fvwm_msg(ERR, "executeModule", "Fork failed");
		free(arg1);
		for (i = 6; i < nargs; i++)
		{
			if (args[i] != 0)
			{
				free(args[i]);
			}
		}
		free(args);
		module_free(module);

		return NULL;
	}
	free(args);

	return module;

  err_exit:
	if (arg1 != NULL)
	{
		free(arg1);
	}
	if (cptr != NULL)
	{
		free(cptr);
	}
	if (args != NULL)
	{
		free(args);
	}
	/* dont't care that these may be -1 */
	close(fvwm_to_app[0]);
	close(fvwm_to_app[1]);
	close(app_to_fvwm[0]);
	close(app_to_fvwm[1]);

	return NULL;
}

fmodule *executeModuleDesperate(F_CMD_ARGS)
{
	fmodule *m;

	m = do_execute_module(F_PASS_ARGS, True, False);

	return m;
}

void module_kill(fmodule *module)
{
	module_list_insert(
		module_list_remove(module, &module_list), &death_row);

	return;
}

/* free modules in the deathrow */
void module_cleanup(void)
{
	module_list_destroy(&death_row);

	return;
}

/* void module_send(fmodule *module, unsigned long *ptr, int size) */
/* This used to be marked "fvwm_inline".  I removed this
   when I added the lockonsend logic.  The routine seems too big to
   want to inline.  dje 9/4/98 */
extern int myxgrabcount;                /* defined in libs/Grab.c */
extern char *ModuleUnlock;              /* defined in libs/Module.c */
void PositiveWrite(fmodule *module, unsigned long *ptr, int size)
{
	extern int moduleTimeout;
	msg_masks_t mask;

	if (ptr == NULL)
	{
		return;
	}
	if (MOD_WRITEFD(module) == -1)
	{
		return;
	}
	if (!IS_MESSAGE_IN_MASK(&(MOD_PIPEMASK(module)), ptr[1]))
	{
		return;
	}

	/* a dirty hack to prevent FvwmAnimate triggering during Recapture */
	/* would be better to send RecaptureStart and RecaptureEnd messages. */
	/* If module is lock on send for iconify message and it's an
	 * iconify event and server grabbed, then return */
	mask.m1 = (MOD_NOGRABMASK(module).m1 & MOD_SYNCMASK(module).m1);
	mask.m2 = (MOD_NOGRABMASK(module).m2 & MOD_SYNCMASK(module).m2);
	if (IS_MESSAGE_IN_MASK(&mask, ptr[1]) && myxgrabcount != 0)
	{
		return;
	}

	/* DV: This was once the AddToMessageQueue function.  Since it was only
	 * called once, put it in here for better performance. */
	{
		mqueue_object_type *c;

		c = (mqueue_object_type *)malloc(
			sizeof(mqueue_object_type) + size);
		if (c == NULL)
		{
			fvwm_msg(ERR, "PositiveWrite", "malloc failed\n");
			exit(1);
		}
		c->size = size;
		c->done = 0;
		c->data = (unsigned long *)(c + 1);
		memcpy((void*)c->data, (const void*)ptr, size);
		fqueue_add_at_end(&(MOD_PIPEQUEUE(module)), c);
	}

	/* dje, from afterstep, for FvwmAnimate, allows modules to sync with
	 * fvwm. this is disabled when the server is grabbed, otherwise
	 * deadlocks happen. M_LOCKONSEND has been replaced by a separated
	 * mask which defines on which messages the fvwm-to-module
	 * communication need to be lock on send. olicha Nov 13, 1999 */
	/* migo (19-Aug-2000): removed !myxgrabcount to sync M_DESTROY_WINDOW
	 */
	/* dv (06-Jul-2002): added the !myxgrabcount again.  Deadlocks *do*
	 * happen without it.  There must be another way to fix
	 * M_DESTROY_WINDOW handling in FvwmEvent. */
	/*if (IS_MESSAGE_IN_MASK(&(MOD_SYNCMASK(module)), ptr[1]))*/
	if (
		IS_MESSAGE_IN_MASK(
			&(MOD_SYNCMASK(module)), ptr[1]) && !myxgrabcount)
	{
		fd_set readSet;
		int channel = MOD_READFD(module);
		struct timeval timeout;
		Bool done = False;
		fmodule_input *input;

		FlushMessageQueue(module);

		while (!done)
		{
			int rc = 0;
			/*
			 * We give the read a long timeout; if the module
			 * fails to respond within this time then it deserves
			 * to be KILLED!
			 *
			 * NOTE: rather than impose an arbitrary timeout on the
			 * user, we will make this a configuration parameter.
			 */
			do
			{
				timeout.tv_sec = moduleTimeout;
				timeout.tv_usec = 0;
				FD_ZERO(&readSet);
				FD_SET(channel, &readSet);

				/* Wait for input to arrive on just one
				 * descriptor, with a timeout (fvwmSelect <= 0)
				 * or read() returning wrong size is bad news
				 */
				rc = fvwmSelect(
					channel + 1, &readSet, NULL, NULL,
					&timeout);
				/* retry if select() failed with EINTR */
			} while (rc < 0 && !isTerminated && (errno == EINTR));

			if ( isTerminated )
			{
				break;
			}

			if (rc > 0)
			{
				input = module_receive(module);
				if (
					input == NULL ||
					module_input_expect(input,
						ModuleUnlockResponse))
				{
					module_input_discard(input);
					done = True;
				}
				else
				{
					module_input_execute(input);
				}

			}
			else
			{
				char *name;

				name = get_pipe_name(module);
				/* Doh! Something has gone wrong - get rid of
				 * the offender! */
				fvwm_msg(ERR, "PositiveWrite",
					 "Failed to read descriptor from"
					 " '%s':\n"
					 "- data available=%c\n"
					 "- terminate signal=%c\n",
					 name,
					 (FD_ISSET(channel, &readSet) ?
					  'Y' : 'N'),
					 isTerminated ? 'Y' : 'N');
				module_kill(module);
				break;
			}


			/* Execute all messages from the module until UNLOCK is
			 * received N.B. This may cause recursion if a command
			 * results in a sync message to another module, which
			 * in turn may send a command that results in another
			 * sync message to this module.
			 * Hippo: I don't think this will cause deadlocks, but
			 * the third time we get here the first times UNLOCK
			 * will be read and then on returning up the third
			 * level UNLOCK will be read at the first level. This
			 * could be difficult to fix without turning queueing
			 * on.  Turning queueing on may be bad because it can
			 * be useful for modules to be able to inject commands
			 * from modules in a synchronous manner. e.g.
			 * FvwmIconMan can tell FvwmAnimate to do an animation
			 * when a window is de-iconified from the IconMan,
			 * queueing make s this happen too late. */
		}
	}

	return;
}

fmodule_input *module_receive(fmodule *module)
{
	unsigned long size;
	unsigned long cont;
	Window win;
	int n;
	fmodule_input *input = NULL;

	n = read(MOD_READFD(module), &win, sizeof(Window));
	if (n < sizeof(Window))
	{
		/* exit silently, module should have died.. */
		goto err;
	}

	n = read(MOD_READFD(module), &size, sizeof(size));
	if (n < sizeof(size))
	{
		fvwm_msg(
			ERR, "module_receive",
			"Fail to read command size (Module: %p, read: %i, "
			"size: %i)", module, n, (int)sizeof(size));
		goto err;
	}

	if (size > MAX_MODULE_INPUT_TEXT_LEN)
	{
		fvwm_msg(ERR, "module_receive",
			 "Module(%p) command is too big (%ld), limit is %d",
			 module, size, MAX_MODULE_INPUT_TEXT_LEN);
		/* The rest of the output from this module is going to be
		 * scrambled so let's kill it rather than risk interpreting
		 * garbage */
		goto err;
	}

	/* allocate all storage at once */
	/* also save space for the '\0' termination character */
	input = (fmodule_input *)safemalloc(
		sizeof(fmodule_input) + sizeof(char)*(size + 1));

	input->module = module;
	input->window = win;
	input->command = (char*)input + sizeof(fmodule_input);

	n = read(MOD_READFD(module), input->command, size);
	if (n < size)
	{
		fvwm_msg(
			ERR, "module_receive",
			"Fail to read command (Module: %p, read: %i, size:"
			" %ld)", module, n, size);
		goto err;
	}
	input->command[n] = '\0';
	n = read(MOD_READFD(module), &cont, sizeof(cont));
	if (n < sizeof(cont))
	{
		fvwm_msg(ERR, "module_receive",
			 "Module %p, Size Problems (read: %d, size: %d)",
			 module, n, (int)sizeof(cont));
		goto err;
	}
	if (cont == 0)
	{
		/* this is documented as a valid way for a module to quit
		 * so let's not complain */
		module_kill(module);
	}

	return input;
err:
	module_kill(module);
	module_input_discard(input);
	return NULL;
}

/* frees the module input data struct */
void module_input_discard(fmodule_input *input)
{
	if (input==NULL)
	{
		return;
	}
	free(input);
}

/* returns true if the module command matches "expect", false otherwise */
Bool module_input_expect(fmodule_input *input, char *expect)
{
	if (input == NULL || input->command == NULL || expect == NULL)
	{
		return False;
	}

	if (strncasecmp(input->command, expect, strlen(expect))	== 0)
	{
		/* the module sent the expected string */
		return True;
	}

	return False;
}

void module_list_itr_init(fmodule_list_itr *itr)
{
	*itr = module_list;

	return;
}

fmodule *module_list_itr_next(fmodule_list_itr *itr)
{
	fmodule *module;

	if (*itr == NULL)
	{
		return NULL;
	}
	module = (*itr)->module;
	*itr = (*itr)->next;

	return module;
}

int module_list_len(fmodule_list *list)
{
	int count=0;
	fmodule_store *current=*list;
	while (current != NULL)
	{
		current = current->next;
		count++;
	}

	return count;
}

static void KillModuleByName(char *name, char *alias)
{
	fmodule_list_itr moditr;
	fmodule *module;

	if (name == NULL)
	{
		return;
	}
	module_list_itr_init(&moditr);
	while ( (module = module_list_itr_next(&moditr)) != NULL)
	{
		if (
			MOD_NAME(module) != NULL &&
			matchWildcards(name, MOD_NAME(module)) &&
			(!alias || (
				 MOD_ALIAS(module) &&
				 matchWildcards(alias, MOD_ALIAS(module)))))
		{
			module_kill(module);
		}
	}

	return;
}

static char *get_pipe_name(fmodule *module)
{
	char *name="";

	if (MOD_NAME(module) != NULL)
	{
		if (MOD_ALIAS(module) != NULL)
		{
			name = CatString3(
				MOD_NAME(module), " ", MOD_ALIAS(module));
		}
		else
		{
			name = MOD_NAME(module);
		}
	}
	else
	{
		name = CatString3("(null)", "", "");
	}

	return name;
}

/*
 * returns a pointer inside a string (just after the alias) if ok or NULL
 */
char *skipModuleAliasToken(const char *string)
{
#define is_valid_first_alias_char(ch) (isalpha(ch) || (ch) == '/')
#define is_valid_alias_char(ch) (is_valid_first_alias_char(ch)  \
				|| isalnum(ch) || (ch) == '-' || \
				(ch) == '.' || (ch) == '/')

	if (is_valid_first_alias_char(*string))
	{
		int len = 1;
		string++;
		while (*string && is_valid_alias_char(*string))
		{
			if (++len > MAX_MODULE_ALIAS_LEN)
			{
				return NULL;
			}
			string++;
		}
		return (char *)string;
	}

	return NULL;
#undef is_valid_first_alias_char
#undef is_valid_alias_char
}

/* message mask handling - does this belong here? */

static inline void msg_mask_set(
	msg_masks_t *msg_mask, unsigned long m1, unsigned long m2)
{
	msg_mask->m1 = m1;
	msg_mask->m2 = m2;

	return;
}

/*
 * Sets the mask to the specific value.  If M_EXTENDED_MSG is set in mask, the
 * function operates only on the extended messages, otherwise it operates only
 * on normal messages.
 */
static void set_message_mask(msg_masks_t *mask, unsigned long msg)
{
	if (msg & M_EXTENDED_MSG)
	{
		mask->m2 = (msg & ~M_EXTENDED_MSG);
	}
	else
	{
		mask->m1 = msg;
	}

	return;
}


/* message queues */

static void DeleteMessageQueueBuff(fmodule *module)
{
	mqueue_object_type *obj;

	if (fqueue_get_first(&(MOD_PIPEQUEUE(module)), (void **)&obj) == 1)
	{
		/* remove from queue */
		fqueue_remove_or_operate_from_front(
			&(MOD_PIPEQUEUE(module)), NULL, NULL, NULL, NULL);
		/* we don't need to free the obj->data here because it's in the
		 * same malloced block as the obj itself. */
		free(obj);
	}

	return;
}

void FlushMessageQueue(fmodule *module)
{
	extern int moduleTimeout;
	mqueue_object_type *obj;
	char *dptr;
	int a;

	if (module == NULL)
	{
		return;
	}

	while (fqueue_get_first(&(MOD_PIPEQUEUE(module)), (void **)&obj) == 1)
	{
		dptr = (char *)obj->data;
		while (obj->done < obj->size)
		{
			a = write(MOD_WRITEFD(module), &dptr[obj->done],
				  obj->size - obj->done);
			if (a >=0)
			{
				obj->done += a;
			}
			/* the write returns EWOULDBLOCK or EAGAIN if the pipe
			 * is full. (This is non-blocking I/O). SunOS returns
			 * EWOULDBLOCK, OSF/1 returns EAGAIN under these
			 * conditions. Hopefully other OSes return one of these
			 * values too. Solaris 2 doesn't seem to have a man
			 * page for write(2) (!) */
			else if (errno == EWOULDBLOCK || errno == EAGAIN)
			{
				fd_set writeSet;
				struct timeval timeout;
				int channel = MOD_WRITEFD(module);
				int rc = 0;

				do
				{
					/* Wait until the pipe accepts further
					 * input */
					timeout.tv_sec = moduleTimeout;
					timeout.tv_usec = 0;
					FD_ZERO(&writeSet);
					FD_SET(channel, &writeSet);
					rc = fvwmSelect(
						channel + 1, NULL, &writeSet,
						NULL, &timeout);
					/* retry if select() failed with EINTR
					 */
				} while ((rc < 0) && !isTerminated &&
					 (errno == EINTR));

				if ( isTerminated )
				{
					return;
				}
				if (!FD_ISSET(channel, &writeSet))
				{
					char *name;

					name = get_pipe_name(module);
					/* Doh! Something has gone wrong - get
					 * rid of the offender! */
					fvwm_msg(
						ERR, "FlushMessageQueue",
						"Failed to write descriptor to"
						" '%s':\n"
						"- select rc=%d\n"
						"- terminate signal=%c\n",
						name, rc, isTerminated ?
						'Y' : 'N');
					module_kill(module);

					return;
				}

				/* pipe accepts further input; continue */
				continue;
			}
			else if (errno != EINTR)
			{
				module_kill(module);

				return;
			}
		}
		DeleteMessageQueueBuff(module);
	}

	return;
}

void FlushAllMessageQueues(void)
{
	fmodule_list_itr moditr;
	fmodule *module;

	module_list_itr_init(&moditr);
	while ( (module = module_list_itr_next(&moditr)) != NULL)
	{
		FlushMessageQueue(module);
	}

	return;
}

/* empty, only here so that the signal handling initialization code is the
 * same for modules and fvwm  */
RETSIGTYPE DeadPipe(int sig)
{
	SIGNAL_RETURN;
}

void CMD_Module(F_CMD_ARGS)
{
	do_execute_module(F_PASS_ARGS, False, False);

	return;
}

void CMD_ModuleListenOnly(F_CMD_ARGS)
{
	do_execute_module(F_PASS_ARGS, False, True);

	return;
}

void CMD_KillModule(F_CMD_ARGS)
{
	char *name;
	char *alias = NULL;

	action = GetNextToken(action,&name);
	if (!name)
	{
		return;
	}

	GetNextToken(action, &alias);
	KillModuleByName(name, alias);
	free(name);
	if (alias)
	{
		free(alias);
	}
	return;
}

void CMD_ModuleSynchronous(F_CMD_ARGS)
{
	int sec = 0;
	char *next;
	char *token;
	char *expect = ModuleFinishedStartupResponse;
	fmodule *module;
	fd_set in_fdset;
	fd_set out_fdset;
	time_t start_time;
	Bool done = False;
	Bool need_ungrab = False;
	char *escape = NULL;
	XEvent tmpevent;

	if (!action)
	{
		return;
	}

	token = PeekToken(action, &next);
	if (StrEquals(token, "expect"))
	{
		token = PeekToken(next, &next);
		if (token)
		{
			expect = alloca(strlen(token) + 1);
			strcpy(expect, token);
		}
		action = next;
		token = PeekToken(action, &next);
	}
	if (token && StrEquals(token, "timeout"))
	{
		if (GetIntegerArguments(next, &next, &sec, 1) > 0 && sec > 0)
		{
			/* we have a delay, skip the number */
			action = next;
		}
		else
		{
			fvwm_msg(ERR, "executeModuleSync", "illegal timeout");
			return;
		}
	}

	if (!action)
	{
		/* no module name */
		return;
	}

	module = do_execute_module(F_PASS_ARGS, False, False);
	if (module == NULL)
	{
		/* executing the module failed, just return */
		return;
	}

	/* Busy cursor stuff */
	if (Scr.BusyCursor & BUSY_MODULESYNCHRONOUS)
	{
		if (GrabEm(CRS_WAIT, GRAB_BUSY))
			need_ungrab = True;
	}

	/* wait for module input */
	start_time = time(NULL);

	while (!done)
	{
		struct timeval timeout;
		int num_fd;

		/* A signal here could interrupt the select call. We would
		 * then need to restart our select, unless the signal was
		 * a "terminate" signal. Note that we need to reinitialise
		 * all of select's parameters after it has returned. */
		do
		{
			FD_ZERO(&in_fdset);
			FD_ZERO(&out_fdset);

			FD_SET(MOD_READFD(module), &in_fdset);

			if (!FQUEUE_IS_EMPTY(&MOD_PIPEQUEUE(module)))
			{
				FD_SET(MOD_WRITEFD(module), &out_fdset);
			}
			timeout.tv_sec = 0;
			timeout.tv_usec = 1;
			num_fd = fvwmSelect(
				fvwmlib_max_fd, &in_fdset, &out_fdset, 0,
				&timeout);
		} while (num_fd < 0 && !isTerminated);

		/* Exit if we have received a "terminate" signal */
		if (isTerminated)
		{
			break;
		}

		if (num_fd > 0)
		{
			if (FD_ISSET(MOD_READFD(module), &in_fdset))
			{
				fmodule_input * input;

				/* Check for module input. */
				input = module_receive(module);
				if (
					input == NULL ||
					module_input_expect(input,expect))
				{
					module_input_discard(input);
					done = True;
				}
				else
				{
					module_input_execute(input);
				}

			}

			if ((MOD_WRITEFD(module) >= 0) &&
			    FD_ISSET(MOD_WRITEFD(module), &out_fdset))
			{
				FlushMessageQueue(module);
			}
		}

		usleep(1000);
		if (difftime(time(NULL), start_time) >= sec && sec)
		{
			/* timeout */
			done = True;
		}

		/* Check for "escape function" */
		if (FPending(dpy) &&
		    FCheckMaskEvent(dpy, KeyPressMask, &tmpevent))
		{
			int context;
			XClassHint *class;
			char *name;
			Window w;

			context = GetContext(
				NULL, exc->w.fw, &tmpevent, &w);
			if (exc->w.fw != NULL)
			{
				class = &(exc->w.fw->class);
				name = exc->w.fw->name.name;
			}
			else
			{
				class = NULL;
				name = NULL;
			}
			escape = CheckBinding(
				Scr.AllBindings, STROKE_ARG(0)
				tmpevent.xkey.keycode, tmpevent.xkey.state,
				GetUnusedModifiers(), context, BIND_KEYPRESS,
				class, name);
			if (escape != NULL)
			{
				if (!strcasecmp(escape,"escapefunc"))
				{
					done = True;
				}
			}
		}
	} /* while */

	if (need_ungrab)
	{
		UngrabEm(GRAB_BUSY);
	}

	return;
}

/* mask handling - does this belong here? */

void CMD_set_mask(F_CMD_ARGS)
{
	unsigned long val;

	if (exc->m.module == NULL)
	{
		return;
	}
	if (!action || sscanf(action, "%lu", &val) != 1)
	{
		val = 0;
	}
	set_message_mask(&(MOD_PIPEMASK(exc->m.module)), (unsigned long)val);

	return;
}

void CMD_set_sync_mask(F_CMD_ARGS)
{
	unsigned long val;

	if (exc->m.module == NULL)
	{
		return;
	}
	if (!action || sscanf(action,"%lu",&val) != 1)
	{
		val = 0;
	}
	set_message_mask(&(MOD_SYNCMASK(exc->m.module)), (unsigned long)val);

	return;
}

void CMD_set_nograb_mask(F_CMD_ARGS)
{
	unsigned long val;

	if (exc->m.module == NULL)
	{
		return;
	}
	if (!action || sscanf(action,"%lu",&val) != 1)
	{
		val = 0;
	}
	set_message_mask(&(MOD_NOGRABMASK(exc->m.module)), (unsigned long)val);

	return;
}
