/*-
 * Copyright (c) 1998-2005 Joao Cabral
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 *      DHIS(c)  Dynamic Host Information System Release 5
 */

#include "dhisd.h"
#include "misc.h"
#include "db.h"
#include "network.h"

#include<errno.h>

struct service_s *sbase=NULL;

void fork_cmd(unsigned char *cmd,int id,unsigned char *addr) {
	
	
	unsigned char line[1024];
	unsigned char path[1024];

	strcpy(path,line_entry(1,cmd));
	sprintf(line,"%s %d %s %s",path,id,addr,line_ptr(2,cmd));

	if(!fork()) {
		system(line);
		exit(0);
	}
	return;
}

 
void mark_update(int id,int addr,int oaddr) {

	struct in_addr sa,osa;
	unsigned char addrs[32],oaddrs[32];
	unsigned char *cp;

	sa.s_addr=addr;
	osa.s_addr=oaddr;

	strcpy(addrs,inet_ntoa((struct in_addr)sa));
	strcpy(oaddrs,inet_ntoa((struct in_addr)osa));

	DSYSLOG(1,(LOG_DEBUG,"mark_update() %d %s %s\n",id,addrs,oaddrs));

	service_issue_command(SUPDATE,id,addrs,oaddrs);

	cp=db_get_oncmd(id);
	if(cp!=NULL) {
	if(cp[0]!='\0') 
	fork_cmd(cp,id,addrs);
	}

	cp=db_get_offcmd(id);
	if(cp!=NULL) {
	if(cp[0]!='\0') 
	fork_cmd(cp,id,addrs);
	}

}

void mark_online(int id,int addr) {

	struct in_addr sa;
	unsigned char addrs[32];
	unsigned char *cp;

	sa.s_addr=addr;
	strcpy(addrs,inet_ntoa((struct in_addr)sa));

	DSYSLOG(1,(LOG_DEBUG,"mark_online() %d %s\n",id,addrs));

	service_issue_command(SADD,id,addrs,"");

	cp=db_get_oncmd(id);
	if(cp!=NULL) {
	if(cp[0]!='\0') 
	fork_cmd(cp,id,addrs);
	}
}

void mark_offline(int id,int addr) {

	struct in_addr sa;
	unsigned char addrs[32];
	unsigned char *cp;

	sa.s_addr=addr;
	strcpy(addrs,inet_ntoa((struct in_addr)sa));

	DSYSLOG(1,(LOG_DEBUG,"mark_offline(): %d\n",id));

	service_issue_command(SDELETE,id,addrs,"");

	cp=db_get_offcmd(id);
	if(cp!=NULL) {
	if(cp[0]!='\0') 
	fork_cmd(cp,id,addrs);
	}

}


int read_services(unsigned char *path) {

	FILE *fp;
	unsigned char line[1024];
	
	DSYSLOG(1,(LOG_DEBUG,"read_services(): Reading services database\n"));

	fp=fopen(path,"r");
	if(fp==NULL) {
		DSYSLOG(1,(LOG_DEBUG,"read_services(): Failed to open database\n"));

		return(0);
	}
	
	while(fgets(line,1024,fp)!=NULL) {

	unsigned char stag[128];
	unsigned char scmd[256];
	int nprocs;
	struct service_s *sp;

	off_nl(line);
	strcpy(stag,line_entry(1,line));
	nprocs=atoi(line_entry(2,line));
	strcpy(scmd,line_entry(3,line));
	if(scmd[0]==';' || nprocs<0 || stag[0]==';') continue;
	if(stag[0]=='\0') continue;
	if(sbase==NULL) {
		sbase=(struct service_s *)malloc(sizeof(struct service_s));
		if(sbase==NULL) continue;
		sp=sbase;
	} else {
		sp=sbase;
		while(sp->next!=NULL) sp=sp->next;
		sp->next=(struct service_s *)malloc(sizeof(struct service_s));
		if(sp->next==NULL) continue;
		sp=sp->next;
	}

	DSYSLOG(1,(LOG_DEBUG,"read_services(): Memory allocated for service %s\n",stag));

	sp->next=NULL;
	sp->fdp=NULL;
	sp->rrfdp=NULL;
	sp->nprocs=nprocs;
	strcpy(sp->service,stag);
	strcpy(sp->command,scmd);

	DSYSLOG(1,(LOG_DEBUG,"read_services(): Service %s added to list\n", stag));

	}
	fclose(fp);

	DSYSLOG(1,(LOG_DEBUG,"read_services(): Finished reading services database\n"));

	if(sbase!=NULL) return(1); 

	return(0);

}
	
int init_services(void) {

	struct service_s *sp;

	DSYSLOG(1,(LOG_DEBUG,"init_services(): Initialising services\n"));

	sp=sbase;
	if(sp==NULL) return(0);
	while(sp!=NULL) {
		int j;

		DSYSLOG(1,(LOG_DEBUG,"init_services(): Processing %s\n",sp->service));

		for(j=0;j<sp->nprocs;j++) { 

		int fieldes[2];
		struct service_fds_s *fp;
		fp=sp->fdp;
		if(fp==NULL) {
		sp->fdp=(struct service_fds_s *)malloc(sizeof(struct service_fds_s));
		if(sp->fdp==NULL) return(0);
		fp=sp->fdp;
		sp->rrfdp=fp;
		} else {
		while(fp->next!=NULL) fp=fp->next;
		fp->next=(struct service_fds_s *)malloc(sizeof(struct service_fds_s));
		if(fp->next==NULL) return(0);
		fp=fp->next;
		}
		fp->next=NULL;
		
		DSYSLOG(1,(LOG_DEBUG,"init_services(): Creating process instance %d\n",j));

		if(pipe(fieldes)<0) return(0);	/* Create a pipe for external 
                                                   communication */	

		DSYSLOG(1,(LOG_DEBUG,"init_services(): Pipe created R:%d W:%d\n",
			 fieldes[0],fieldes[1]));
		
		signal(SIGCHLD,sigchld);
		fp->pid=fork();
		if(!(fp->pid)) {	/* we are child */

		int fo;
		int f0,f1,f2;
	
		net_close();

		DSYSLOG(1,(LOG_DEBUG,"init_services(): I am the child for instance %d\n",j));

		close(fieldes[1]);	/* child doesn't write */
		
		/* lets get a null device for output */
		fo=open("/dev/null",O_WRONLY,0);
		if(fo<0) return(0);
		
		DSYSLOG(1,(LOG_DEBUG,"init_services(): child: /dev/null open on %d\n",fo));
		DSYSLOG(1,(LOG_DEBUG,"init_services(): child: read pipe descriptior %d\n",
			   fieldes[0]));

		/* set stdin from pipe */
		close(0);
		f0=dup2(fieldes[0],0);
		if(f0) {
			DSYSLOG(1,(LOG_DEBUG,"init_services(): child: stdin redirect failed\n"));
			DSYSLOG(1,(LOG_DEBUG,"init_services(): child: %s\n", strerror(errno)));
			return(0);
		}
		close(fieldes[0]);	/* server doesn't read */

		DSYSLOG(1,(LOG_DEBUG,"init_services(): child: stdin redirected (%d)\n",fo));

		/* set stdout to /dev/null */
		close(1);
		f1=dup2(fo,1);
		if(f1!=1) return(0);

		DSYSLOG(1,(LOG_DEBUG,"init_services(): child: stdout redirected (%d)\n",fo));

		/* set stderr to /dev/null */
		close(2);
		f2=dup2(fo,2);
		if(f2!=2) return(0);

		close(fo);

		/* we are ready to go */
		execlp(sp->command,sp->command,NULL);
		exit(0);
		
		}

		DSYSLOG(1,(LOG_DEBUG,"init_services(): Parent got child %d\n",fp->pid));

		fp->fd=fieldes[1];
		close(fieldes[0]);
		
		} /* close for */
		sp=sp->next;
	}

	DSYSLOG(1,(LOG_DEBUG,"init_services(): Finished initialising services\n"));

	return(1);
}


void sigchld() {
	
	int pid;
	int s;
	struct service_s *sp;
	struct service_fds_s *fp;
	int fieldes[2];

	pid=wait(&s);

	DSYSLOG(1,(LOG_DEBUG,"Received child %d termination signal\n",pid)); 

	sp=sbase;
	while(sp!=NULL) {
	fp=sp->fdp;
	while(fp!=NULL) {
	if(fp->pid==pid) {

	DSYSLOG(1,(LOG_DEBUG,"Restarting service process for %s\n",sp->service)); 

	close(fp->fd);	/* close previous pipe */

	DSYSLOG(1,(LOG_DEBUG,"init_services(): Creating process instance\n"));

		if(pipe(fieldes)<0) {         	/* Create a pipe for external 
                                                   communication */	
			signal(SIGCHLD,sigchld);
			return;
		}

	DSYSLOG(1,(LOG_DEBUG,"init_services(): Pipe created R:%d W:%d\n",
		fieldes[0],fieldes[1]));

	fp->fd=fieldes[1];
	fp->pid=fork();
	if(!(fp->pid)) { /* we are child */
		int fo;
		int f0,f1,f2;
	
		net_close();

		DSYSLOG(1,(LOG_DEBUG,"init_services(): I am the child %d\n",getpid()));

		close(fieldes[1]);	/* child doesn't write */
		
		/* lets get a null device for output */
		fo=open("/dev/null",O_WRONLY,0);
		if(fo<0) 
			exit(1);

		DSYSLOG(1,(LOG_DEBUG,"init_services(): child: /dev/null open on %d\n",fo));
		DSYSLOG(1,(LOG_DEBUG,"init_services(): child: read pipe descriptior %d\n",
			   fieldes[0]));

		/* set stdin from pipe */
		close(0);
		f0=dup2(fieldes[0],0);
		if(f0) {

			DSYSLOG(1,(LOG_DEBUG,"init_services(): child: stdin redirect failed\n"));
			DSYSLOG(1,(LOG_DEBUG,"init_services(): child: %s\n", strerror(errno)));

			exit(1);
		}
		close(fieldes[0]);	/* server doesn't read */

		DSYSLOG(1,(LOG_DEBUG,"init_services(): child: stdin redirected (%d)\n",fo));

		/* set stdout to /dev/null */
		close(1);
		f1=dup2(fo,1);
		if(f1!=1) { 
			exit(1);
		}
		DSYSLOG(1,(LOG_DEBUG,"init_services(): child: stdout redirected\n"));

		/* set stderr to /dev/null */
		close(2);
		f2=dup2(fo,2);
		if(f2!=2) exit(1);

		close(fo);

		/* we are ready to go */
		execlp(sp->command,sp->command,NULL);
		exit(0);
		
		
	}

	DSYSLOG(1,(LOG_DEBUG,"init_services(): Parent got child %d\n",fp->pid));

	close(fieldes[0]); /* server doesn't read */
	signal(SIGCHLD,sigchld);
	return;
	}
	fp=fp->next;
	}
	sp=sp->next;
	}
	signal(SIGCHLD,sigchld);
}

struct service_s *service_get_by_name(unsigned char *s) {
	
	struct service_s *sp;
	
	sp=sbase;
	while(sp!=NULL) {
		if(!strcmp(s,sp->service)) return(sp);
		sp=sp->next;
	}
	return(NULL);
}

void service_issue_command(unsigned char *cmd,int id,unsigned char *addr,
unsigned char *oaddr) {

	db_service_t *dbp=NULL;
/*
	DSYSLOG(1,(LOG_DEBUG,"service_issue_command: CMD=%s ID=%d ADDR=%s\n",cmd,id,addr));
*/
	dbp=db_get_service_by_id(id);

	DSYSLOG(1,(LOG_DEBUG,"service_issue_command: Got service list for client.\n"));

	if(dbp==NULL) return;
	
	while(dbp!=NULL) {
		unsigned char str[256];
		sprintf(str,"%s %s %d %s %s\n",cmd,dbp->sp->service,
			id,addr,oaddr);

		DSYSLOG(1,(LOG_DEBUG,"service_issue_command: Sending command to %d\n",
			   dbp->sp->rrfdp->pid));

		write(dbp->sp->rrfdp->fd,str,strlen(str));
		
		/* round robin */
		dbp->sp->rrfdp=dbp->sp->rrfdp->next;
		if(dbp->sp->rrfdp==NULL) dbp->sp->rrfdp=dbp->sp->fdp;

		dbp=dbp->next;	/* next service */
	}
	return;
}

void service_free(void) {
	
	struct service_s *sp;
	struct service_fds_s *fsp;

	sp=sbase;
	while(sp!=NULL) {
	fsp=sp->fdp;
	while(fsp!=NULL) {
		sp->fdp=sp->fdp->next;
		close(fsp->fd);
		free(fsp);
		fsp=sp->fdp;
	}
	sbase=sp->next;
	free(sp);
	sp=sbase;
	}
}

void service_send_term(void) {
	
	struct service_s *sp;
	struct service_fds_s *fsp;

	sp=sbase;
	while(sp!=NULL) {
	fsp=sp->fdp;
	while(fsp!=NULL) {
	unsigned char str[32];
	sprintf(str,"%s\n",SEXIT);
	write(fsp->fd,str,strlen(str));
	close(fsp->fd);
	fsp->pid=-1;
	fsp=fsp->next;
	}
	sp=sp->next;
	}
}

void service_send_reload(void) {
	
	struct service_s *sp;
	struct service_fds_s *fsp;

	sp=sbase;
	while(sp!=NULL) {
	fsp=sp->fdp;
	while(fsp!=NULL) {
	unsigned char str[32];
	sprintf(str,"%s\n",SRELOAD);
	write(fsp->fd,str,strlen(str));
	fsp=fsp->next;
	}
	sp=sp->next;
	}
}
