/* -------------------------------------------------------------------- */
/* SMS Client, send messages to mobile phones and pagers		*/
/*									*/
/* sms_client.c								*/
/*									*/
/*  Copyright (C) 1997,1998,1999 Angelo Masci				*/
/*									*/
/*  This library is free software; you can redistribute it and/or	*/
/*  modify it under the terms of the GNU Library General Public		*/
/*  License as published by the Free Software Foundation; either	*/
/*  version 2 of the License, or (at your option) any later version.	*/
/*									*/
/*  This library 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	*/
/*  Library General Public License for more details.			*/
/*									*/
/*  You should have received a copy of the GNU Library General Public	*/
/*  License along with this library; if not, write to the Free		*/
/*  Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.	*/
/*									*/
/*  You can contact the author at this e-mail address:			*/
/*									*/
/*  angelo@styx.demon.co.uk						*/
/*									*/
/* -------------------------------------------------------------------- */
/* $Id$
   -------------------------------------------------------------------- */

#include <stdio.h>
#include <errno.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
#include <time.h>
#include <sys/wait.h>

#if defined(LINUX)
#include <getopt.h>
#endif
#if defined(NEXT)
#include <libc.h>
#endif

#include "error.h"
#include "sms_list.h"
#include "logfile/logfile.h"
#include "expand.h"
#include "driver/driver.h"
#include "comms/comms.h"
#include "common/common.h"
#include "lock/lock.h"
#include "resource/resource.h"
#include "version.h"

/* -------------------------------------------------------------------- */

#if !defined(MVERSION)
#error "MVERSION undefined"
#else
#define VERSION MVERSION
#endif

#if !defined(MLOGFILE)
#error "MLOGFILE undefined"
#else
#define LOGFILE         MLOGFILE
#endif

#if !defined(MLOGLEVEL)
#error "MLOGLEVEL undefined" 
#else
#define LOGLEVEL	MLOGLEVEL
#endif

#if !defined(MSERVICEDIR)
#error "MSERVICEDIR undefined"
#else
#define SERVICEDIR      MSERVICEDIR
#endif

#define CONFIG_FILE  (MSERVICEDIR "/sms_config")
#define SMSLOCK_FILE (MSERVICEDIR "/smslock")
 
/* -------------------------------------------------------------------- */

static 	char 
	*SMS_lock_action,
	*SMS_lockfile,
	*SMS_default_service;

static	char
	*current_message = NULL;

static 	long 
	SMS_lock_retry_delay,
	SMS_service_timeout;
		
/* -------------------------------------------------------------------- */

#define MAXMESSAGELEN	150
#define MAXMESSAGES	256
#define SERVICETIMEOUT  SMS_service_timeout

/* -------------------------------------------------------------------- */

static 	RESOURCE resource_list[] = 
	{
		{ RESOURCE_STRING,  "SMS_default_service", 	0, 1, NULL, 0,       "CELLNET",    0,        &SMS_default_service	},
		{ RESOURCE_STRING,  "SMS_lock_action", 	        0, 1, NULL, 0,       "BLOCK",      0,        &SMS_lock_action		},
		{ RESOURCE_NUMERIC, "SMS_lock_retry_delay", 	0, 0, NULL, 0,       NULL,         5000000,  &SMS_lock_retry_delay 	},
		{ RESOURCE_STRING,  "SMS_lockfile", 		0, 1, NULL, 0,       SMSLOCK_FILE, 0,        &SMS_lockfile 		},
		{ RESOURCE_NUMERIC, "SMS_service_timeout", 	0, 1, NULL, 0,       NULL, 	   300,      &SMS_service_timeout 	},
		{ RESOURCE_NULL,     NULL, 			0, 1, NULL, 0,       NULL,         0,        NULL  			}
	};

/* -------------------------------------------------------------------- */

void get_message(char *message);
void usage(char *file);
int main(int, char *[]);

/* -------------------------------------------------------------------- */

static 	volatile sig_atomic_t
	child_flag,
	alarm_flag;
int	child_status;

void trap_child(int signo)
{
	child_flag = TRUE;
}

void trap_alarm(int signo)
{
	alarm_flag = TRUE;
}

/* -------------------------------------------------------------------- */
/* -------------------------------------------------------------------- */
void get_message(char *message)
{
	int	i,
		c;

	if (isatty(fileno(stdin)))
	{	printf("Enter your message and end with Control-D\n");
	}
		
	for (i=0; i<MAXMESSAGELEN; i++)
	{
		c = getchar();
		if (c == EOF)
		{	break;
		}

		message[i] = c;
	}

	if (i == MAXMESSAGELEN)
	{
		lprintf(LOG_ERROR, "Message too long\n");
		exit(EMESSAGETOOLONG);
	}

	message[i] = '\0';
}

/* -------------------------------------------------------------------- */
/* -------------------------------------------------------------------- */
void usage(char *file)
{
	char 	blank[128], 
		*src, 
		*dst;


	lprintf(LOG_STANDARD, "Usage: %s -v\n", file);
	lprintf(LOG_STANDARD, "       %s -d\n", file);
	lprintf(LOG_STANDARD, "       %s [-q][-l loglevel]\n", file);

	src = file;
	dst = blank;
	while(*src != '\0')
	{
		*dst = ' ';
		
		dst++;
		src++;
	}

	*dst = '\0';

	lprintf(LOG_STANDARD, "       %s [service:]number|name[,name|[service:]number...]\n", blank);
	lprintf(LOG_STANDARD, "       %s [message] ...\n", blank);
}



/* -------------------------------------------------------------------- */
/* -------------------------------------------------------------------- */
static void SMS_release_lock(void)
{	resource_unlock(SMS_lockfile);
}


/* -------------------------------------------------------------------- */
/* -------------------------------------------------------------------- */
static int SMS_obtain_lock(char *sms_lockfile)
{
	int 	no_block,
		notify;
	

	if (resource_test_lockdir(sms_lockfile) == -1)
	{	return -1;
	}


	if (strcmp(SMS_lock_action, "NO_BLOCK") == 0)
	{	no_block = TRUE;
	}
	else if (strcmp(SMS_lock_action, "BLOCK") == 0)
	{	no_block = FALSE;	
	}
	else
	{	lprintf(LOG_WARNING, "SMS_lock_action invalid, defaulting to NO_BLOCK\n");
		no_block = TRUE;
	}


	notify = TRUE;
	while(resource_lock(sms_lockfile))
	{
		if (no_block)
		{	lprintf(LOG_ERROR, "Could not obtain lock for sms_client\n"); 
			lprintf(LOG_STANDARD, "Another SMS Client is already running.\n"); 

			return -1;
		}

		if (notify)
		{	lprintf(LOG_VERBOSE, "Blocking on lockfile '%s'\n", sms_lockfile); 
			lprintf(LOG_STANDARD, "Another SMS Client is already running.\n"); 
			lprintf(LOG_STANDARD, "Waiting...\n"); 
			notify = FALSE;
		}

		resource_wait(sms_lockfile, SMS_lock_retry_delay);
	}

	if (!notify)
	{	lprintf(LOG_STANDARD, "The other SMS Client has finished.\n"); 
		lprintf(LOG_STANDARD, "Continuing...\n"); 
	}


	atexit(SMS_release_lock);	/* Establish EXIT handler	*/
					/* to release the lockfile if	*/
					/* we leave prematurely		*/

	return 0;
}



/* -------------------------------------------------------------------- */
/* -------------------------------------------------------------------- */

static void *access_get_first(void *list);
static void *access_get_next(void *node);
static char *access_get_number(void *node);
static char *access_get_message(void *node);


static void *access_get_first(void *list)
{
	return (void *)get_first((SMS_list *)list);
}

static void *access_get_next(void *node)
{
	return (void *)get_next((SMS_list *)node);
}

static char *access_get_number(void *node)
{
	return get_number((SMS_list *)node);
}

static char *access_get_message(void *node)
{
	return current_message;
}

static void access_set_message(void *node, char *message)
{
	current_message = message;
}

static void access_set_delivery(void *node, int result)
{
	set_delivery(node, result);
}


/* -------------------------------------------------------------------- */
/* -------------------------------------------------------------------- */
int deliver_message(DEVICE_ENTRY *device, char *mservice, SMS_list *list, char *message, int message_num, int delivery_error)
{
	time_t	driver_start_time,
		driver_end_time;

	SMS_list
		*delivered;




	/* ---------------------------------------- */

	time(&driver_start_time);

	if ((*device->init)(mservice, device) == -1)
	{
		lprintf(LOG_ERROR, "Driver Initialization Failed\n");
		delivery_error = EDELIVERY;
	}
	else
	{	access_set_message(NULL, message);

		(*device->main)(list, 
		                access_get_first,
		                access_get_next,
		                access_get_number,
		                access_get_message,
		                access_set_delivery,
		                device->env);


		time(&driver_end_time);
		lprintf(LOG_STANDARD, "%s Service Time: %d Seconds\n", mservice, (int)(driver_end_time - driver_start_time));

		delivered = list;
		while (delivered != NULL)
		{
			if (get_delivery(delivered))
			{
				lprintf(LOG_WARNING, "Could not deliver message %d to %s on %s delivery code %d\n", 
					message_num,
			        	get_name(delivered), 
				        get_number(delivered),
			        	get_delivery(delivered));

				delivery_error = EDELIVERY;
			}
	
			lprintf(LOG_STANDARD, "[%03d] %s:%s \"%s\"\n", 
			       get_delivery(delivered),
			       get_service(delivered),
			       get_number(delivered),
		               access_get_message(delivered));

			delivered = get_next(delivered);
		}
	}

	return delivery_error;
}


/* -------------------------------------------------------------------- */
/* -------------------------------------------------------------------- */
int main(int argc, char *argv[])
{
	DEVICE_ENTRY
		*device;

	char 	*mservice,
		message[MAXMESSAGES][MAXMESSAGELEN +1],
		*ptr,
		*protocol,
		*progname;
		
	int	i, num_messages;

	SMS_parent_list 
		*parent_node,
		*parent_list;

	SMS_list
		*list,
		*numbers;
				
	TOKEN_HEAP
		*global,
		*local;
		
	int	num_args,
		nind,
		mind,
		c,
		delivery_error = 0;

	time_t	start_time,
		end_time;
		

	pid_t	pid;
	sigset_t 
		newmask, 
		zeromask,
		origmask,
		parentmask;

	void (* prev_sigalrm)(int);
	void (* prev_sigchld)(int);


	progname = strrchr(argv[0], '/');
	if (progname == NULL)
	{	progname = argv[0];
	}
	else
	{	progname++;
	}

	/* ---------------------------- */

	set_logfile(LOGFILE);
	set_loglevel(LOGLEVEL);
	set_consolelog(TRUE);

	/* ---------------------------- */

	while ((c = getopt (argc, argv, "vql:m:d")) != -1)
        {
                switch (c)
                {
                        case 'q':
				set_consolelog(FALSE);
                                break;

                        case 'd':
				display_drivers();
				exit(0);

                        case 'v':
				lprintf(LOG_STANDARD, "%s %s\n", progname, VERSION);
                                exit(0);

                        case 'l':  
				set_loglevel((int)strtol(optarg, &ptr, 10));
				if (ptr == optarg)
				{
					lprintf(LOG_ERROR, "Option l requires an argument\n");
	     	                        usage(progname);
                	                exit(EUSAGE);
				}
                                
                                break;

                        case '?':
#if !defined(NEXT)
                                lprintf(LOG_ERROR, "Unknown option `-%c'\n", optopt);
#endif
                                usage(progname);
                                exit(EUSAGE);

                        default:
                                abort ();
                }
        }
                        

	/* ---------------------------- */

	num_args = argc - optind;

	if (num_args < 1)
	{
		usage(progname);
		exit(EUSAGE);
	}

	nind = optind;
	mind = optind +1;

	/* ---------------------------- */

        lprintf(LOG_VERYVERBOSE, "Version Information: '%s'\n", VERSION);

	/* ---------------------------- */

	if (read_resource_file(CONFIG_FILE, resource_list, TRUE) != RESOURCE_FILE_OK)
	{	lprintf(LOG_ERROR, "Unrecoverable Failure Parsing file '%s'\n", CONFIG_FILE);
		exit(1);
	}


	/* ---------------------------- */
	/* Get and expand NAMES|NUMBERS	*/
	
	if (SMS_dual_openrc(&global, &local) == -1)
	{	exit(-1);
	}

	numbers = SMS_expandnumber(global, local, "<NULL>", argv[nind], SMS_default_service);
	SMS_dual_closerc(global, local);


	/* ---------------------------- */
	/* Check NAMES|NUMBERS		*/

	if (SMS_validate_expanded_numbers(numbers))
	{
		lprintf(LOG_ERROR, "Expanding names\n"); 
		exit(ENAMEEXPANSION);
	}

	/* ---------------------------- */


	if (num_args >= 2)
	{
		for (i=mind; i<argc; i++)
		{
			if (strlen(argv[i]) > MAXMESSAGELEN)
			{
				lprintf(LOG_ERROR, "Message %d too long\n", (i - mind +1));
				exit(EMESSAGETOOLONG);
			}

			strcpy(message[i - mind], argv[i]);
		}

		num_messages = argc - mind;
	}
	else
	{	get_message(message[0]);
		num_messages = 1;
	}


	/* ------------------------------------------------------------ */
	/* If sms_lock_action is set to BLOCK any other instances of 	*/
	/* sms_client which are running	will cause this one to block.	*/
	/* When the other instances have finished executing and the 	*/
	/* lockfile has been released this instance can attemp to 	*/
	/* get the lockfile and run					*/
	/* ------------------------------------------------------------ */

	if (SMS_obtain_lock(SMS_lockfile) == -1)
	{	exit(-1);
	}

	/* ------------------------------------------------------------ */

	time(&start_time);

	parent_list = gather(numbers);

	for (parent_node = get_first_parent(parent_list);
	     parent_node != NULL;
	     parent_node = get_next_parent(parent_node))
	{
		list = get_child(parent_node);		

		mservice = get_service(list);

		protocol = get_protocol(mservice);
		if (protocol == NULL)
		{	lprintf(LOG_ERROR, "Service '%s' does not have a valid protocol entry\n", mservice);
			continue;
		}

		device = get_device(protocol);
		if (device == NULL)
		{	lprintf(LOG_ERROR, "Driver for service '%s' NOT found\n", mservice);
		}
		else
		{
			/* ------------------------------------ */
			/* Add SIGCHLD to set of blocked	*/
			/* signals.				*/
			/* ------------------------------------ */

			sigemptyset(&newmask);
			sigaddset(&newmask, SIGCHLD);

			lprintf(LOG_VERYVERBOSE, "SIGCHLD is being blocked\n");
			if (sigprocmask(SIG_BLOCK, &newmask, &origmask) == -1)
			{	exit(-1);
			}

			lprintf(LOG_VERYVERBOSE, "Installing signal handler for SIGCHLD\n");
			prev_sigchld = signal(SIGCHLD, trap_child);
			if (prev_sigchld == SIG_ERR)
			{	exit(-1);
			}

			/* ------------------------------------ */

			lprintf(LOG_VERBOSE, "Forking process for service '%s'\n", mservice);

			pid = fork();
			switch (pid)
			{
				case 0:	/* Child */
				{
					/* -------------------------------------------- */
					/* Return child process' signal mask back	*/
					/* to it's original state.			*/
					/* -------------------------------------------- */

					if (signal(SIGCHLD, prev_sigchld) == SIG_ERR)
					{	exit(-1);
					}

					if (sigprocmask(SIG_SETMASK, &origmask, NULL) == -1)
					{	exit(-1);
					}

					/* -------------------------------------------- */


					lprintf(LOG_VERBOSE, "Started process for service '%s'\n", mservice);

					for (i=0; i<num_messages; i++)
					{	delivery_error = deliver_message(device, mservice, list, message[i], i, delivery_error);
					}

					lprintf(LOG_VERBOSE, "Finished process for service '%s'\n", mservice);
					exit(delivery_error);
				}
				case -1:	/* Error */
				{
					/* ------------------------------------ */
					/* Restore orignal signal mask and	*/
					/* signal handlers for SIGALRM and	*/
					/* SIGCHLD.				*/
					/* ------------------------------------ */

					if (signal(SIGCHLD, prev_sigchld) == SIG_ERR)
					{	exit(-1);
					}

					if (sigprocmask(SIG_SETMASK, &origmask, NULL) == -1)
					{	exit(-1);
					}

					/* ------------------------------------ */

					lprintf(LOG_ERROR, "Failed to start process for service '%s'\n", mservice);
					delivery_error = -1;
					break;
				}
				default:		/* Parent */
				{
					/* ------------------------------------ */
					/* Add SIGALRM to set of blocked	*/
					/* signals.				*/
					/* ------------------------------------ */

					sigemptyset(&parentmask);
					sigaddset(&parentmask, SIGALRM);

					lprintf(LOG_VERYVERBOSE, "SIGALRM is being blocked\n");
					if (sigprocmask(SIG_BLOCK, &parentmask, NULL) == -1)
					{	exit(-1);
					}

					lprintf(LOG_VERYVERBOSE, "Installing signal handler for SIGALRM\n");
					prev_sigalrm = signal(SIGALRM, trap_alarm);
					if (prev_sigalrm == SIG_ERR)
					{	exit(-1);
					}

					/* ------------------------------------ */
					/* Set TIMEOUT for child. The signal	*/
					/* handler will not be executed until	*/
					/* we issue the sigsuspend() call.	*/
					/* ------------------------------------ */

					alarm(SERVICETIMEOUT); 	

					/* ------------------------------------ */

					alarm_flag = 0;
					child_flag = 0;
					child_status = 0;

					/* ------------------------------------ */

					while ((!alarm_flag) && 
					       (!child_flag))
					{
						sigemptyset(&zeromask);
						sigsuspend(&zeromask);
					}

					/* ------------------------------------ */
					/* We must reset the alarm to avoid	*/
					/* the alarm signal we're no longer	*/
					/* interested in.			*/
					/* ------------------------------------ */

					alarm(0);

					/* ------------------------------------ */
					/* We've been woken up by SIGALRM or	*/
					/* SIGCHLD.				*/
					/*					*/
					/* Restore orignal signal mask and	*/
					/* signal handlers for SIGALRM and	*/
					/* SIGCHLD.				*/
					/* ------------------------------------ */

					if (signal(SIGALRM, prev_sigalrm) == SIG_ERR)
					{	exit(-1);
					}

					if (signal(SIGCHLD, prev_sigchld) == SIG_ERR)
					{	exit(-1);
					}

					if (sigprocmask(SIG_SETMASK, &origmask, NULL) == -1)
					{	exit(-1);
					}

					/* ------------------------------------ */

					if (alarm_flag)
					{	
						lprintf(LOG_ERROR, "Timeout of %ld Seconds expired for service '%s'\n", SERVICETIMEOUT, mservice);

						if (!child_flag)
						{
							/* ------------------------------------ */
							/* We are still waiting for the		*/
							/* child to return.			*/
							/* As we have timedout, we must 	*/
							/* terminate child. 			*/
							/* ------------------------------------ */

							lprintf(LOG_VERYVERBOSE, "Sending Process '%d' SIGKILL...\n", (int)pid);

							while (kill(pid, SIGKILL) == -1)
							{
								if (errno == ESRCH)
								{
									/* ------------------------------------ */
									/* The call to kill has returned with	*/
									/* an error indicating that the		*/
									/* process does not exist. We have	*/
									/* sucessfully killed it.		*/
									/* ------------------------------------ */

									break;
								}

								lprintf(LOG_VERYVERBOSE, "Re-sending child SIGKILL...\n");
								sleep(1);
							}

							lprintf(LOG_ERROR, "Forcibly Terminated Process for service '%s'\n", mservice);
						}

						/* ------------------------------------ */
						/* We should now wait on it so that	*/
						/* the process entry is cleared.	*/
						/* ------------------------------------ */

						while (waitpid(pid, &child_status, 0) == -1)
						{
							/* Ignore Errors generated by 	*/
							/* signals.			*/

							if (errno != EINTR)
							{	break;
							}
						}

						lprintf(LOG_ERROR, "Process '%d' terminated\n", (int)pid);
					}
					else
					{
						/* ------------------------------------ */
						/* As the child process has finished.	*/
						/* We should now wait on it so that	*/
						/* the process entry is cleared.	*/
						/* ------------------------------------ */

						while(waitpid(pid, &child_status, 0) == -1)
						{
							/* Ignore Errors generated by 	*/
							/* signals.			*/

							if (errno != EINTR)
							{	break;
							}
						}

						/* ------------------------------------ */
						/* We should determine the exit status	*/
						/* for the child for use later.		*/
						/* ------------------------------------ */


						lprintf(LOG_VERBOSE, "Process completed for service '%s'\n", mservice);

						if (WIFEXITED(child_status))
						{	if (WEXITSTATUS(child_status) != 0)
							{	delivery_error = WEXITSTATUS(child_status);
							}
						}
						else
						{	delivery_error = -1;
						}
					}
				}
			}
		}

		free(protocol);
	}


	time(&end_time);
	lprintf(LOG_STANDARD, "Total Elapsed Time: %d Seconds\n", (int)(end_time - start_time));	

	free_list(numbers);


	SMS_release_lock();
	return delivery_error;
}
