/*  swicol.c -- Write script and data into a shell's stdin.
 */

/*
  Copyright (C) 2004, 2005, 2006 Jim Lowe
  All Rights Reserved.
  
  COPYING TERMS AND CONDITIONS:
  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, 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., 675 Mass Ave, Cambridge, MA 02139, USA.  */


#define FILENEEDDEBUG 1 /* controls the E_DEBUG macros */
#undef FILENEEDDEBUG
#include "swuser_config.h"
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "swheaderline.h"
#include "swheader.h"
#include "ugetopt_help.h"
#include "to_oct.h"
#include "tarhdr.h"
#include "atomicio.h"
#include "swi.h"
#include "swgp.h"
#include "swevents.h"
#include "swlib.h"
#include "swicol.h"
#include "swutilname.h"

#include "debug_config.h"
#ifdef SWICOLNEEDDEBUG
#define SWICOL_E_DEBUG(format) SWBISERROR("SWI DEBUG: ", format)
#define SWICOL_E_DEBUG2(format, arg) SWBISERROR2("SWI DEBUG: ", format, arg)
#define SWICOL_E_DEBUG3(format, arg, arg1) \
			SWBISERROR3("SWI DEBUG: ", format, arg, arg1)
#else
#define SWICOL_E_DEBUG(arg)
#define SWICOL_E_DEBUG2(arg, arg1)
#define SWICOL_E_DEBUG3(arg, arg1, arg2)
#endif 

static
int
set_selected_index(SWICOL * swicol, int first_current_event)
{
	if (first_current_event < 0) 
		return swicol->event_indexM;
	else
		return first_current_event;
}
	
static
void
set_buf(char ** p_dst, char * buf)
{
	char * dst = *p_dst;
	if (dst) free(dst);
	dst = strdup(buf);	
}

static
int
convert_result_to_event_list(char * event_string, STRAR * event_list)
{
	char * s;
	char * current;
	int ret;

	/* fprintf(stderr, "JLX event string = [%s]\n", event_string);  */

	ret = strar_max_index(event_list);
	s = strchr(event_string, '\n');
	current = event_string;
	while(s && *s) {
		*s = '\0';
		strar_add(event_list, current);	
		s++;
		current = s;
		s = strchr(s, '\n');
	}

	/*
	 * Add a newline in the list as a separator
	 */
	strar_add(event_list, "\n");	

	/*
	 * return the position of the start of these current events
	 */
	return ret /* JLx + 1 */;
}

static
int
print_task_group(SWICOL * swicol, STROB * group_script,
		char * script_name, int num_shells)
{
	STROB * subsh;
	STROB * event_msg;
	int i;

	subsh = strob_open(32);
	event_msg = strob_open(32);

	strob_sprintf(group_script, STROB_NO_APPEND,
	"	%s\n" /* Task Group Begins */
	"	%s"
	"	sw_retval=0\n"
	"	%s"
	"	umask %s\n"
	"	blocksize=\"%s\"\n"
	"	xtarget=\"%s\"\n"
	"	wcwd=`pwd`\n"
	"	swexec_status=0\n"
	,
	TEVENT(2, swicol->verbose_levelM, SWI_GROUP_BEGINS, script_name),
	swicol_subshell_marks(subsh, "target", 'L', swicol->nhopsM, swicol->verbose_levelM),
	swicol->setvxM,
	swicol->umaskM,
	swicol->blocksizeM,
	swicol->targetpathM
	);

	/*
	 * now write the working shells that are in this group
	 */
	for (i=0; i<num_shells; i++) {
		strob_sprintf(group_script, STROB_DO_APPEND,
		"	case $swexec_status in	0) %s ;; *) false_ ;; esac\n" 
		"	sw_retval=$?\n"
		"	swexec_status=$sw_retval\n"
		);
	}

	/*
	 * Now write the final pieces of this group
	 */
	strob_sprintf(event_msg, STROB_NO_APPEND, "%s: status=$sw_retval\n", script_name);
	strob_sprintf(group_script, STROB_DO_APPEND,
	"	sleep %d\n"
	"	%s\n" 
	"%s\n",
	swicol->delaytimeM,
	TEVENT(2, swicol->verbose_levelM, SWI_GROUP_ENDS, strob_str(event_msg)),
	swicol_subshell_marks(subsh, "install_target", 'R', swicol->nhopsM, swicol->verbose_levelM)
	);

	strob_close(event_msg);
	strob_close(subsh);
	return 0;
}

static
int
print_task_script(SWICOL * swicol, STROB * command, unsigned long int size, char * dir, char * task_script, char * task_desc)
{
	char * verbosestring;
	char * tonullstring;
	STROB * enddesc = strob_open(32);

	int blocks = (int)(size / 512);
	
	if (size % 512) {
		/*
		 * size must be a whole number of 512 blocks.
		 */
		blocks ++;
		fprintf(stderr, "Internal error: in swicol.c:print_task_script\n");
	}

	/*
	 * make sure directory is not tainted and is atleast 1 char long.
	 */

	if (swlib_is_ascii_noaccept(dir, SWBIS_TAINTED_CHARS "\a\b\n\r\t \v\\", 1)) {
		return 1;
	}

	if (swicol->verbose_levelM > SWC_VERBOSE_8) {
		verbosestring = "set -vx";
	} else {
		verbosestring = "";
	}
	
	if (swicol->verbose_levelM >= SWC_VERBOSE_7) {
		tonullstring = "";
	} else {
		tonullstring = "2>/dev/null";
	}

	strob_sprintf(enddesc, 0, "%s: status=$sw_retval", task_desc);

	strob_sprintf(command, 1,
		"(\n"
		"	%s\n"			/* SWI_TASK_BEGINS */
		"	swxdir=\"%s\"\n"
		"	cd \"$swxdir\"\n"
		"	swret=$?; export swret\n"
		"	%s\n"
		"	dd bs=512 count=\"%d\" %s | (\n"
		"		case $swret in\n"
		"			0)\n"
		" 				;;\n"
		"			*)\n"
		"				dd count=\"%d\" of=/dev/null\n"
		"				%s\n"
		"				exit \"$swret\"\n"
		"				;;\n"
		"		esac\n"
		"		%s"		/* Task main script */
		"		exit \"$sw_retval\"\n"
		"	); sw_retval=$?\n"
		"	%s\n"			/* SWI_TASK_ENDS */
		"	exit $sw_retval\n"	/* exit the task shell */
		"); exit $sw_retval\n"
		,
		TEVENT(2, -1, SWI_TASK_BEGINS, task_desc),
		dir,
		verbosestring,
		blocks,
		tonullstring,
		blocks,
		TEVENT(2, -1, SW_INTERNAL_ERROR, "No such directory or no access: $swxdir"),
		task_script,
		TEVENT(2, -1, SWI_TASK_ENDS, strob_str(enddesc))
		);
	strob_close(enddesc);
	return 0;
}

SWICOL *
swicol_create(void)
{
	SWICOL * swicol;
	swicol = (SWICOL*)malloc(sizeof(SWICOL));
	swicol->tmpM = strob_open(10);
	swicol->logspecM = (struct sw_logspec *)(NULL);
	swicol->verbose_levelM = 0;
	swicol->event_listM = strar_open();
	swicol->umaskM = NULL;
	swicol->blocksizeM = NULL;
	swicol->setvxM = NULL;
	swicol->delaytimeM = 0;
	swicol->targetpathM = (char*)NULL;
	swicol->nhopsM = 1;
	swicol->event_indexM = 1;
	return swicol;
}

void
swicol_delete(SWICOL * swicol)
{
	free(swicol);
}

void
swicol_set_delaytime(SWICOL * swicol, int delay)
{
	swicol->delaytimeM = delay;
}

void
swicol_set_nhops(SWICOL * swicol, int nhops)
{
	swicol->nhopsM = nhops;
}

void
swicol_set_verbose_level(SWICOL * swicol, int level)
{
	swicol->verbose_levelM = level;
}

void
swicol_set_umask(SWICOL * swicol, char * buf)
{
	set_buf(&(swicol->umaskM), buf);
}

void
swicol_set_setvx(SWICOL * swicol, char * buf)
{
	set_buf(&(swicol->setvxM), buf);
}

void
swicol_set_blocksize(SWICOL * swicol, char * buf)
{
	set_buf(&(swicol->blocksizeM), buf);
}

void
swicol_set_targetpath(SWICOL * swicol, char * buf)
{
	set_buf(&(swicol->targetpathM), buf);
}

char *
swicol_get_umask(SWICOL * swicol)
{
	return swicol->umaskM;
}

char *
swicol_get_setvxk(SWICOL * swicol)
{
	return swicol->setvxM;
}

char *
swicol_get_blocksize(SWICOL * swicol)
{
	return swicol->blocksizeM;
}

int
swicol_rpsh_task_send_group_script(SWICOL * swicol, int fd,
			char * script_name, int num_shells)
{
	int ret;
	int eret;
	STROB * tmp = strob_open(1);

	if (print_task_group(swicol, tmp, script_name, num_shells)) {
		return -2;
	}
	ret = atomicio(
			((ssize_t (*)(int, void *, size_t))write),
			fd, 
			(void*)(strob_str(tmp)),
			(size_t)(strob_strlen(tmp))
		);

	eret = strob_strlen(tmp);
	strob_close(tmp);
	if (ret == eret)
		return 0;
	else 
		return -1;
}

int
swicol_rpsh_task_send_script(SWICOL * swicol, int fd,
		unsigned long int data_size, char * dir, char * script, char * desc)
{
	int ret;
	int eret;
	STROB * tmp = strob_open(1);
	
	if (data_size == 0) {
		fprintf(stderr, "%s: Error: a non-zero length data payload is required.\n",
				swlib_utilname_get());
	}

	if (print_task_script(swicol, tmp, data_size, dir, script, desc)) {
		return -2;
	}

	ret = atomicio((ssize_t (*)(int, void *, size_t))write,
		fd, (void*)(strob_str(tmp)), (size_t)(strob_strlen(tmp)));

	eret = strob_strlen(tmp);
	strob_close(tmp);
	if (ret == eret)
		return 0;
	else 
		return -1;
}

int
swicol_rpsh_task_wait(SWICOL * swicol, STROB * retbuf, 
			int event_fd, int timelimit)
{
	int retval = -1;
	int ret = 0;
	int cret;
	int do_stop;
	int readReturn;
	STROB * buf = strob_open(180);
	char * s;
	char * stop_string = "305" ":";   /* SWI_TASK_ENDS */
	time_t start = time(NULL);
	time_t now = start;
	struct timespec req;
              
	req.tv_sec = 0;  	/* seconds */
	req.tv_nsec = 30000000;  /* 1/30th second in nanoseconds */

	strob_strcpy(retbuf, "");

	cret = 0;
	do_stop = 0;
	while(
		(cret == 0) || 
		(do_stop == 0 && ((int)(now - start) < timelimit))
	     )
	{
		nanosleep(&req, (struct timespec *)(NULL));
		now = time(NULL);
		ret = swgpReadLine(buf, event_fd, &readReturn);
		if (cret == 0) {
			cret = ret;
		}
	
		s = strstr(strob_str(buf), stop_string);
		if (s) {
			/*
			 * Got the terminating event.
			 * The terminating event is 305 "SWI_TASK_ENDS"
			 */

			if (
				strlen(s) == 6 /* e.g. "305:0\n" */ &&
				*(s + 5) == '\n' 
			) {
				/*
				 * Normal stop
				 */
				retval = 0;
				do_stop = 1;
			} else if (strlen(s) > 8) {
				/*
				 * error
				 */
				retval = 1;
				do_stop = 1;
				fprintf(stderr, "internal error in swicol_task_wait at line %d\n", __LINE__);
				fprintf(stderr, "expect string [%s]\n", s);
			} else {
				/*
				* OK
				* partial read on stop event
				*/
				;	
			}
		} else {
			/* fprintf(stderr, "JLXX [%s]\n", strob_str(buf)); */
			/*
			 * OK
			 */
			;
		}
		if (ret) 
			strob_strcat(retbuf, strob_str(buf));
	}
	if (do_stop == 0) {
		/*
		 * time limit exceeded.
		 */
		retval = 2;
		swutil_doif_writef(swicol->verbose_levelM, 1, swicol->logspecM,
				STDERR_FILENO,
				"SW_RESOURCE_ERROR: time limit of %d seconds exceeded\n",
							timelimit);

	}
	strob_close(buf);
	return retval;
}

char *
swicol_rpsh_get_event_message(SWICOL * swicol, int event_value,
		int first_current_event, int * p_index)
{
	char * retval = (char*)NULL;
	STROB * tmp = strob_open(10);
	STRAR * event_list;
	char * s;
	int ix;

	ix = set_selected_index(swicol, first_current_event);
	event_list = swicol->event_listM;

	strob_sprintf(tmp, 0, "%d:", event_value);

	E_DEBUG2("event value=[%s]", strob_str(tmp));

	s = strar_get(event_list, ix++);
	while(s) {
		if (strstr(s, strob_str(tmp))) {
			s = strchr(s, ':');
			if (!s) return NULL;
			s++;
			retval = s;
			break;
		}
		s = strar_get(event_list, ix++);
	}
	strob_close(tmp);
	if (p_index) *p_index = --ix;
	return retval;
}

int
swicol_rpsh_get_event_status(SWICOL * swicol, char * msg,
		int event_value, int first_current_event, int * p_index)
{
	int retval;
	int atoi_ret;

	if (!msg)
		msg = swicol_rpsh_get_event_message(swicol, event_value, first_current_event, p_index);

	/*
	 * the message is the status
	 */
	if (!msg) return -1;
	if (isdigit((int)(*msg))) {
		retval = swlib_atoi(msg, &atoi_ret);
		if (atoi_ret) {
			retval = -127;
		}
	} else {
		fprintf(stderr,
			"%s: event %d does not return a status\n",
			swlib_utilname_get(), event_value);
		retval = -128;
	}
	return retval;
}

int
swicol_rpsh_task_expect(SWICOL * swicol, int event_fd, int timelimit)
{
	char * ev;
	int ret;
	int retval = 0;
	STROB * buf = strob_open(10);
	STRAR * event_list = swicol->event_listM;
	int first_current_event;

	swicol->event_indexM = -1;
	/*
	 * wait for the SWI_TASK_ENDS event
	 */
	ret = swicol_rpsh_task_wait(swicol, buf, event_fd, timelimit);
	retval = ret;
	
	E_DEBUG2("expect buffer=[%s]", strob_str(buf));

	/*
	 * Assert a user expectation for a single event.
	 */
	if ((ev=strstr(strob_str(buf), "305:")) == NULL) {
		/*
		* SWI_TASK_ENDS event not found.
		*/
		fprintf(stderr, "%s: Event error: SWI_TASK_END event not received.\n", swlib_utilname_get());
		if (retval == 0) retval = -1;
	} else {
		ev += 4;
		if (isdigit((int)(*ev)) == 0) {
			fprintf(stderr, "%s: Event error: SWI_TASK_END event has invalid status. [%s]\n",
				swlib_utilname_get(),  strob_str(buf));
			retval = -1;
		} else {
			retval = swlib_atoi(ev, NULL);
		}
	}

	/* fprintf(stderr, "start\n%s\nend\n", strob_str(buf)); */
	/*
	 * Store all the events.
	 */ 
	first_current_event = convert_result_to_event_list(strob_str(buf), event_list);

	/*
	 *  Print the events if the verbose level is high enough
	 */
	if (swicol->verbose_levelM >= SWC_VERBOSE_8) {
		/*
		fprintf(stderr, "%s\n", strob_str(buf));
		*/
		swicol_show_events_to_fd(swicol, STDERR_FILENO, first_current_event);
	}

	swicol->event_indexM = first_current_event;
	strob_close(buf);
	return retval;
}

int
swicol_show_events_to_fd(SWICOL * swicol, int fd, int first_current_event)
{
	int ret;
	size_t len;
	int ix;

	if (first_current_event < 0) 
		ix = swicol->event_indexM;
	else
		ix = first_current_event;
	swicol_print_events(swicol, swicol->tmpM, ix);
	len = strob_strlen(swicol->tmpM);
	ret = atomicio(
			((ssize_t (*)(int, void *, size_t))write),
			fd, 
			(void*)(strob_str(swicol->tmpM)),
			len
		);	
	return !(ret == (int)len);
}

void
swicol_print_event(SWICOL * swicol, STROB * buf, char * ev)
{		
	strob_sprintf(buf, STROB_DO_APPEND,  "%s: swicol: %s\n", swlib_utilname_get(), ev);
}

void
swicol_print_events(SWICOL * swicol, STROB * buf, int index)
{
	char * ev;
	int i;

	i = set_selected_index(swicol, index);
	
	strob_strcpy(buf, "");
	strob_sprintf(buf, STROB_DO_APPEND,  "%s: swicol: event stack start\n", swlib_utilname_get());
	while(
		(ev=strar_get(swicol->event_listM, i++)) &&
		(
			1
			/*
			(strcmp(ev, "\n") && index >=0) ||
			(index <= 0)
			*/
		)
	) {

		if (strcmp(ev, "\n") == 0) {
			swicol_print_event(swicol, buf, "");
		} else {
			swicol_print_event(swicol, buf, ev);
		}

		if (strcmp(ev, "\n") == 0 && index < 0) {
			break;
		}
	}
	strob_sprintf(buf, STROB_DO_APPEND,  "%s: swicol: event stack end\n", swlib_utilname_get());
}

char * 
swicol_subshell_marks(STROB * subsh, char * type, int wh, 
			int nhops, int verbose_level)
{
	strob_strcpy(subsh, "");
	if (wh == (int)'L') {
		/*
		 * Left
		 */
		strob_strcat(subsh, "(\n");
	} else if (wh == (int)'R') {
		/*
		 * Right
		 */
		strob_strcat(subsh, "exit $sw_retval;\n) ");
		if ( strcmp(type, "source") == 0) {
			/*
			* Source
			*/
			if (nhops >= 1) {
				if (verbose_level == 0) {
					strob_strcat(subsh, 
						"0</dev/null 2>/dev/null");
				} else {
					strob_strcat(subsh, "0</dev/null");
				}
			} else {
				if (verbose_level == 0) {
					strob_strcat(subsh, "2>/dev/null");
				} else {
					strob_strcat(subsh, "0</dev/null");
				}
			}
		} else if ( strcmp(type, "target") == 0) {
			/*
			* Target
			*/
			if (nhops >= 1) {
				if (verbose_level == 0) {
					strob_strcat(subsh, 
						"1>/dev/null 2>/dev/null");
				} else {
					strob_strcat(subsh, "1>/dev/null");
				}
			} else {
				if (verbose_level == 0) {
					strob_strcat(subsh, 
						"1>/dev/null 2>/dev/null");
				} else {
					strob_strcat(subsh, "1>/dev/null");
				}
			}
		} else if (strcmp(type, "install_target") == 0) {
			if (nhops >= 1) {
				if (verbose_level == 0) {
					strob_strcat(subsh, "");
				} else {
					strob_strcat(subsh, "");
				}
			} else {
				if (verbose_level == 0) {
					strob_strcat(subsh, 
						"2>/dev/null");
				} else {
					strob_strcat(subsh, "");
				}
			}
		} else {
			fprintf(stderr, "internal error in swicol_task_wait at line %d\n", __LINE__);
			exit(1);
		}
		strob_strcat(subsh, "; exit $?");
	} else {
		;
	}
	return strob_str(subsh);
}

int
swicol_initiate_fall_thru(SWICOL * swicol)
{
	return 0;
}

int
swicol_NOT_USED_get_event_status(SWICOL * swicol, int event_value, int first_current_event)
{
	int ret = -1;
	int ix;
	char * v;
	STROB * buf;
	STROB * tmp;

	tmp = strob_open(48);
	buf = strob_open(48);

	ix = set_selected_index(swicol, first_current_event);

	/*
	 * print the events of the selected task script
	 */
	swicol_print_events(swicol, buf, ix);

	/*
	 * buf now has something similar to this
	 *
	 * swinstall: swicol: event stack start
	 * swinstall: swicol: 304:analysis phase 2
	 * swinstall: swicol: 73:2
	 * swinstall: swicol: 305:0
	 * swinstall: swicol: event stack end
	 */

	strob_sprintf(tmp, STROB_NO_APPEND, ": %d:", event_value);

	/*
	 * now find this substr in buf
	 */
	v = strstr(strob_str(buf), strob_str(tmp));

	if (!v) ret = -1; /* not found */

	v+=(strob_strlen(tmp));

	/*
	 * the next byte is the status
         * make sure it is there
	 */

	if (isdigit((int)(*v)) == 0) {
		/*
		 * this event has no status
		 */
		 ret = -2;
	} else {
		/*
		 * convert the status
		 */
		ret = swlib_atoi(v, NULL);
	}

	strob_close(tmp);
	strob_close(buf);
	return ret;
}
