/* Threading stuff for incoming feeds */
/*

  Safety check array indexing for:

	

	static int chan_len[MAX_CHAN];

  Safety check:
	chan_bf[chan] = mymalloc(CHBFSZ);
	chan_item[chan] = mymalloc(maxlen+1);
String
	memmove, memcpy, strcpy, strncpy
	string lengths;
	------------------------
  
    
      
	
	  memcpy, memmove




Add status 'nthread' counter and display 
	Use test function to test these input routines with
	partial packets, and long message id's 
	Add 'check' processing after we have debugged takethis.
	Test for speed with feedtest (could download?)
	Modify item_suck to reject messages with naked 'cr' characters
	in the headers.
	IF thread creation fails, continue without threads!!!

In chan.c
	stat_start(X_DOREST);
	if (xn[c] > 0) chan_dorest(c);

	if (xn[c]==0) if (chan_xisihave[c]) {
		th_start(c);
	}
	stat_end(X_DOREST);
	vtrace();

*/

#ifdef __MWERKS__ /*RHP we will take this to mean we are making a Mac version*/
#include "mac.h"
#endif

#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <assert.h>
#include <errno.h>

#include "mutex.h"


#ifdef VMS
#include <unixio.h>
#define unlink remove
#endif
#ifndef NO_FCNTL
#include <fcntl.h>
#else
/*RHP not for mac */
#ifndef Mac
#include <file.h>
#endif
#endif
#include "emsg.h"
#include "sary.h"
#include "str.h"
#include "lib.h"
#include "group.h"
#include "hist.h"
#include "suck.h"
#include "nntp.h"
#include "chan.h"
#include "feed.h"
#include "expire.h"
#include "control.h"
#include "item.h"
#include "db.h"
#include "ncmd.h"
#include "thin.h"

#undef mymalloc
#undef myfree
#define mymalloc(x) mymalloc2(x,M_THIN)
#define myfree(x) myfree2(x,M_THIN)

void nntp_addidle(int x);
void suck_setexit(void);
int thin_suck(int chan, char *start, int len, char *mid, char *s, char *out, int toobig, int start_time, int start_clock, int skt, int skc, int isiahve);
int thin_test(int chan, char *bf, int len);
int thin_finish(int chan);
int thin_perror(char *s);
int thin_abort(char *rsea);
int ncmd_ihave_result(int chan, char *mid, char *rs);

int tmsg(const char * arg_list, ...);
static Thread *chan_thread[MAX_CHAN];
static String *chan_str[MAX_CHAN];
static int chan_chan[MAX_CHAN];
static int maxlen;
static char *chan_item[MAX_CHAN];
static char *chan_bf[MAX_CHAN];
static int chan_upto[MAX_CHAN];
static int chan_len[MAX_CHAN];
static int isdebug = FALSE;
#define CHBFSZ 40000
#define xxxxnntp_socket_read thin_test

#ifdef DOTHREADS

#ifndef TESTING
int thin_start(int chan)
{
	static int done;
	if ((chan<0) || (chan>=MAX_CHAN)) return TRUE;
 	done = TRUE;
	chan_chan[chan] = chan;
	tmsg("Creating Input thread for ihave feed %d\n",chan);
	nntp_setpending(chan,TRUE);
	nntp_setblock(chan,TRUE);
	chan_thread[chan] = th_new(thin_main,&chan_chan[chan]);
	if (chan_thread[chan]==NULL) {
		emsg("Thread creation failed\n");
		return FALSE;
	}
	lib_thread_add(1);
	return TRUE;
}
int thin_main(void *arg)
{
	int chan = *(int *) arg;

	tmsg("Input thread running 1 chan=%d\n",chan);
	thin_init(chan);
	for (;TRUE;) {
		if (isdebug) tmsg("thin: Input thread running 2\n");
		if (!thin_read(chan)) break;
	}
	tmsg("thin: Input thread exiting\n");
	thin_finish(chan);
	return TRUE;
}
int thin_finish(int chan)
{
	if ((chan<0) || (chan>=MAX_CHAN)) return TRUE;
	tmsg("thin: Input thread finishing\n");
	mutex_wait(M_MAIN);
	chan_close(chan);
	myfree(chan_item[chan]); chan_item[chan] = NULL;	
	myfree(chan_bf[chan]); chan_bf[chan] = NULL;	
	if (chan_str[chan]!=NULL) str_free(chan_str[chan]); chan_str[chan] = NULL;
	lib_thread_add(-1);
	mutex_release(M_MAIN);
	return TRUE;
}
int thin_init(int chan)
{
	if ((chan<0) || (chan>=MAX_CHAN)) return TRUE;
	mutex_wait(M_MAIN);
	maxlen = conf_item_max();
	if (maxlen<40000) maxlen = 40000;
	chan_bf[chan] = mymalloc(CHBFSZ);
	chan_item[chan] = mymalloc(maxlen+1);
	mutex_release(M_MAIN);
	return TRUE;
}
int thin_read(int chan)
{
	char bf[CHBFSZ];
	char *p[40];
	int n;

	if (chan<0) return TRUE;
	if (!thin_read_line(chan,bf,CHBFSZ-30)) return FALSE;
	if (isdebug) tmsg("Read line (%.300s)\n",bf);
	n = token_split(bf,p," \t\r\n",30);	
	if (isdebug) tmsg("Tokens %d\n",n);
	if (n<1) return TRUE;
	strlwr(p[0]);
	if (isdebug) tmsg("Got command (%.30s)\n",p[0]);
	if (strcmp(p[0],"takethis")==0) {if (!thin_cmd_takethis(chan,p[1],FALSE)) return FALSE;}
	else if (strcmp(p[0],"check")==0) {if (!thin_cmd_check(chan,p[1])) return FALSE;}
	else if (strcmp(p[0],"ihave")==0) {if (!thin_cmd_ihave(chan,p[1])) return FALSE;}
	else if (strcmp(p[0],"quit")==0) {if (!thin_cmd_quit(chan)) return FALSE;}
	else if (strcmp(p[0],"xdrealip2")==0) /*Ignore this no response */;
	else thin_cmd_error(chan,p[0]);
	return TRUE;
}
int thin_read_line(int chan, char *xrslt,int bfsz)
{
	char *at;
	char *start;
	char *rslt;
	int want;
	int n;
	int len;
	int tlen;
	char *s;
	
	if ((chan<0) || (chan>=MAX_CHAN)) return TRUE;
again:
	len=chan_len[chan]-chan_upto[chan];
	tlen = bfsz;
	at=chan_bf[chan]+chan_upto[chan];
	start = at;
	rslt = xrslt;
	for (;(len>0) ;tlen--,len--) {
		if (*at==0) *at = ' ';
		if ((*at=='\n') || (tlen<=1)) {
			*rslt = 0;
			chan_upto[chan] += (at-start+1);
			return TRUE;
		}
		*rslt++ = *at++;
	}
	
	/* Shuffle it and read some more if needed */
	len = chan_len[chan]-chan_upto[chan];
	if (len<0) {
		tmsg("FATAL ERROR Len less zero %d\n",len);
		thin_abort("len less than zero");
	}
	if (chan_upto[chan]>0) {
		if (len>=CHBFSZ) {
			tmsg("FATAL ERROR Len exceeded %d\n",len);
			thin_abort("len exceeded");
		}
		if (len>0) memmove(chan_bf[chan],chan_bf[chan]+chan_upto[chan],len);
		if (isdebug)  tmsg("Shuffled %d bytes (%.30s) \n",len,chan_bf[chan]);
		chan_upto[chan] = 0;
		chan_len[chan] = len;
	}
	s = chan_bf[chan]; s[len] = 0;

	want = bfsz-len-100;
	if (want<0) {
		tmsg("Chucking away part of long line\n");
		len = 0;
	}
	want = bfsz-len-100;
	n = nntp_socket_read(chan,s+len,want);
	if (n<=0) {
		tmsg("thin: Cannot read from chan %d\n",chan);
		thin_perror("socket_read");
		return FALSE;
	}
	if (isdebug) tmsg("thin: Read %d bytes (%.30s) \n",n,chan_bf[chan]);
	chan_len[chan] += n;
	goto again;
}
int thin_perror(char *s)
{
	mutex_wait(M_MAIN);
        nntp_lasterror();               
	nntp_perror(s);
	mutex_release(M_MAIN);
	return TRUE;
}
int thin_abort(char *reason)
{
	tmsg("FATAL ERROR OCCURED %.90s\n",reason);
	mutex_wait(M_MAIN);
	emsg("FATAL ERROR IN THIN THREADING MODULE\n");
	suck_setexit();
	mutex_release(M_MAIN);
	abort();
	return 0;
}
int thin_getrest(int chan, char *out)
{
	int len = chan_len[chan]-chan_upto[chan];

	if ((chan<0) || (chan>=MAX_CHAN)) return TRUE;
	if (len>=maxlen) thin_abort("len>maxlen");
	if (len<0) thin_abort("len<0");
	memcpy(out,chan_bf[chan]+chan_upto[chan],len);
	chan_upto[chan] = 0;
	chan_len[chan] = 0;
	return len;
}
int thin_putrest(int chan, char *out, int len)
{
	if ((chan<0) || (chan>=MAX_CHAN)) return TRUE;
	if ((len >= CHBFSZ) || (len<0)) {
		tmsg("FATAL Thin_putrest failed [%d] %p %d %.20s\n",chan,out,len,out);	
		thin_abort("putrest");
	}
	memcpy(chan_bf[chan],out,len);
	chan_len[chan] = len;
	return TRUE;
}
int thin_cmd_check(int chan, char *mid)
{
	char response[BFSZ];
	int n;
	int xsec;
	if (chan<0) return TRUE;
	if ((mid==NULL) || (strlen(mid)>199)) {
		char bf[CHBFSZ];
		/* Immediately reject it */
		if (mid==NULL) mid="";
		sprintf(bf,"438 %.34000s not wanted, mid too long\r\n",mid);
		if (nntp_socket_write(chan,bf,strlen(bf))<=0) return FALSE;
		return TRUE;
	}
	mutex_wait(M_MAIN);
	xsec = lib_msec();
	if (isdebug) tmsg("thin: check %.200s\n",mid);
	ncmd_check_result(chan,mid,response);
	chan_setused(chan);
	nntp_addidle(xsec-lib_msec());
	mutex_release(M_MAIN);
	n = nntp_socket_write(chan,response,strlen(response));
	if (n<=0) return FALSE;
	return TRUE;
}
int thin_cmd_ihave(int chan, char *mid)
{
	char response[BFSZ];
	int n;
	int xsec;

	if (chan<0) return TRUE;
	if ((mid==NULL) || (strlen(mid)>199)) {
		char bf[CHBFSZ];
		/* Immediately reject it */
		sprintf(bf,"435 article not wanted, mid too long\r\n",mid);
		if (nntp_socket_write(chan,bf,strlen(bf))<=0) return FALSE;
		return TRUE;
	}
	mutex_wait(M_MAIN);
	xsec = lib_msec();
	if (isdebug) tmsg("thin: ihave %.200s\n",mid);
	ncmd_ihave_result(chan,mid,response);
	chan_setused(chan);
	nntp_addidle(xsec-lib_msec());
	mutex_release(M_MAIN);
	n = nntp_socket_write(chan,response,strlen(response));
	if (n<=0) return FALSE;
	thin_cmd_takethis(chan,mid,TRUE);
	return TRUE;
}
int thin_cmd_error(int chan, char *cmd)
{
	char *s;
	int n;

	if (chan<0) return TRUE;
	s = "500 Invalid command in thread_input feeding mode\r\n";
	tmsg("thin: error: Invalid command in thread_input feeding mode (%.90s)\n",cmd);
	n = nntp_socket_write(chan,s,strlen(s));
	if (n<=0) return FALSE;
	return TRUE;
}
int thin_cmd_quit(int chan)
{
	char *s;
	int n;
	if (chan<0) return TRUE;
	s = "205 Closing connection goodbye!\r\n";
	n = nntp_socket_write(chan,s,strlen(s));
	if (n<=0) return FALSE;
	return FALSE;
}
int thin_cmd_takethis(int chan, char *mid, int isihave)
{
	/*	Read in rest of input from buffer
		Read in missing data
		Put left over data into input buffer.
		go read data in.
	*/
	char *out = chan_item[chan];
	char *start=out;
	int want = maxlen-1;
	int sz = CHBFSZ-1;
	char *s,*s2;
	int n;
	int w;
	int toobig = FALSE;
	int first=TRUE;
	int start_time,start_clock;
	int sk_time,sk_clock;

	if ((chan<0) || (chan>=MAX_CHAN)) return TRUE;
	if (mid==NULL) mid="null";
        start_time = lib_msec();
        start_clock = stats_clock();
	sk_time = sk_clock = 0;
	n = thin_getrest(chan,out);
	if (n<=0) first = FALSE;
	out += n;
	want -= n;
	for (;TRUE;) {
		if (want<=10) { /* Ran out of space, must be a long item we need to reject */
			tmsg("thin: Input message over-flow, throwing away this item (item_max exceeded)\n");
			toobig = TRUE;
			memmove(chan_item[chan],out-10,10);
			out = chan_item[chan]+10;
			want = maxlen-11;
		}
		if (!first) { 
			w = want;
			if (w>=((CHBFSZ/2)-100)) w = (CHBFSZ/2)-100;
			if (isdebug)  tmsg("thin: takethis, read more %d %d %d\n",w,want,maxlen);
			if (w<0) {
				emsg("w is %d\n",w);
				thin_abort("w<0");
			}
		        sk_time -= lib_msec();
			sk_clock -= stats_clock();
			n = nntp_socket_read(chan,out,w);
		        sk_time += lib_msec();
			sk_clock += stats_clock();
			if (n<=0) return FALSE;
			if (isdebug) tmsg("thin: takethis, read %d bytes (%.30s)\n",n,out);
			want -= n;
			out += n;
			/* Find end of item, if found then process item
			and put rest onto input buffer */
		}
		first = FALSE;
		s = out-n - 4;
		if (s<chan_item[chan]) s = chan_item[chan];
		s2 = find_eom(s,out-s+1); /* Points to char after end of line feed */
		if (isdebug)  tmsg("thin: eom if found len %d %p (%.20s) \n",n,s2,s);
		s = s2;
		if (out-s<0) s = NULL; /* Shouldn't happen but this will stop it*/
		if (s!=NULL) {
			/* Block, copy into string, and send in */
			if (isdebug)  tmsg("Found end of message\n");
			thin_suck(chan,start,s-start,mid,s,out,toobig,start_time,start_clock,sk_time,sk_clock,isihave);
			return TRUE;
		}
	}
	return TRUE;
}
int thin_trim_dot(String *xitem)
{
	/* If the end is, lf.lf or   lf.crlf, then strip it */
	int len = str_len(xitem);
	char *s;

	s = str_s(xitem) + len - 3;
	if (strncmp(s,"\n.\n",3)==0) len -=2;
	else {
		s--;
		if (strncmp(s,"\n.\r\n",4)==0) len -=3;
	}
	str_setlen(xitem,len);
	return TRUE;
}

int thin_suck(int chan, char *start, int len, char *mid, char *s, char *out, int toobig, int start_time, int start_clock, int sk_time, int sk_clock, int isihave)
{
	char response[10000];
	int xsec;
	int n;
	String *xitem;

	
	if ((chan<0) || (chan>=MAX_CHAN)) return TRUE;
	mutex_wait(M_MAIN);
	xsec = lib_msec();
	stats_add("thin_sk",lib_msec()-sk_time,stats_clock()-sk_clock);
	stats_add("thin_read",start_time,start_clock);

	if (chan_str[chan]==NULL) chan_str[chan] = str_new();
	xitem = chan_str[chan];
	str_copys(xitem,start,len);
	gstat_xadd("Total In bytes/s",len); /* So the graph makes sense */
	nntp_speedin(chan,len);
	thin_trim_dot(xitem);
#ifndef NORMALTHING
	if (strlen(mid)>400) {
		sprintf(response,"439 %.4000s mid is too long\015\012",mid);
	} else {
		ncmd_item_suck(chan,mid,xitem,response,toobig,isihave);
	}
#else
	sprintf(response,"239 %.900s article transferred ok\015\012",mid);
#endif
	chan_setused(chan);
	str_clearbig(xitem);
	nntp_addidle(xsec-lib_msec());
	mutex_release(M_MAIN);
	
	/* Send response */
	n = nntp_socket_write(chan,response,strlen(response));
	if (n<=0) return FALSE;
		
	/* Put rest of input buffer into cmd buffer. */
	thin_putrest(chan, s,out-s);
	return TRUE;
}
char *find_eom(char *part, int nn)
{
	register int n=nn;
	register char *s=part;

	for (;n>0;n--,s++) {
		if (*s==0) *s = 32;
		if (*s=='\n') {
			s++; 
			n--; if (n<=0) break;
			if (*s!='.') continue;

			s++;
			n--; if (n<=0) break;
			if (*s=='\r') s++;
			if (*s=='\n') {
				return s+1; 
			}
		}
	}
	return NULL;
}
#endif
/* Thread safe logging */
int thin_test(int chan, char *bf, int len)
{
	/* Generate various test packets */
	static int u;
	static char mid[BFSZ];
	char *o;
	static int upto;
	char big[60000];
	char bigmid[60000];
	int i;
	if (chan<0) return TRUE;
	if (u==0) {
		u++;
		sprintf(mid,"<a%d@a.com>",time(NULL));
		sprintf(bf,"takethis %s\r\n",mid);
		return strlen(bf);
	}
	if (u==1) {
		u++;
		o = bf;
		sprintf(o,"From: me@here.com\r\n"); o += strlen(o);
		sprintf(o,"Subject: this is the subject\r\n"); o += strlen(o);
		sprintf(o,"Newsgroups: test.1\r\n"); o += strlen(o);
		sprintf(o,"Message-ID: %s\r\n",mid); o += strlen(o);
		sprintf(o,"Date: Wed, 10 May 2000 14:43:58 +0200\r\n"); o += strlen(o);
		sprintf(o,"\r\n"); o += strlen(o);
		sprintf(o,"Test message\r\n"); o += strlen(o);
		sprintf(o,".\r\n"); o += strlen(o);
		return strlen(bf);
	}
	if (u==2) {
		u++;
		o = bf;
		for (i=0; i<10; i++) {
			sprintf(mid,"<a%d%d@a.com>",time(NULL),i);
			sprintf(o,"check %s\r\n",mid); o += strlen(o);
		}
		return strlen(bf);
	}
	if (u==3) {
		u++;
		o = bf;
		sprintf(mid,"<eol_ba%d%d@a.com>",time(NULL),i);
		sprintf(o,"check %s\r\n",mid); o += strlen(o);
		return strlen(bf)-1;
	}
	if (u==4) {
		strcpy(bf,"\n");
		u++;
		return 1;
	}
	if (u==5) {
		u++;
		o = bf;
		sprintf(o,"check <aa@bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb>\r\n",mid); o += strlen(o);
		return strlen(bf);
	}
	if (u==6) {
		memset(bigmid,'x',50000);
		sprintf(big,"check <%s>\r\n",bigmid);
		strncpy(bf,big,len);
		upto = len;
		u++;
		return len;
	}
	if (upto<(int) strlen(big)) if (u==7) {
		if (len>((int) strlen(big)-upto)) len = strlen(big) - upto;
		strncpy(bf,big+upto,len);
		upto += len;
		return len;
	}
	lib_msleep(1000*60);
	return -1; /*  Socket close */
}
#endif

int tmsg(const char * arg_list, ...)
{
        va_list arg_ptr;
        char *format;
        int len;
	char output[4000];
	
        va_start(arg_ptr, arg_list);
        format = (char *) arg_list;
        len = vsprintf(output, format, arg_ptr);
        if (len>=2000) imsg("FATAL, IMSG ERROR TOO LONG %.100s\n",output);
#ifdef DOTHREADS
	mutex_wait(M_MAIN);
#endif
        imsg("%.900s",output);
#ifdef DOTHREADS
	mutex_release(M_MAIN);
#endif
	return 0;
}
static int ntest_done;
#ifndef DOTHREADS
int thin_trim_dot(String *xitem)
{
	return TRUE;
}
int thin_test_threads(int n)
{
	printf("Threads not compiled in this version of dnews, sorry\n");
	printf("This may be an oversight, or it may be due to a problem with the thread library on this system\n");
	printf("Email support@netwinsite.com to find out more details, give exact details of your OS type and version\n");
	return FALSE;
}
#else
int thread_test_main(void *x);
int thin_test_threads(int n)
{
	/* Create 'n' threads, running test function */
	int i;
	Thread *h[30000];

	mutex_init();
	if (n>20000) n = 20000;
	for (i=0; i<n; i++) {
		h[i] = th_new(thread_test_main,(void *) i);
		if (h[i]==NULL) break;
	}
	printf("Successfully created %d threads\n",n);
	for (i=0; i<600; i++) {
		lib_msleep(1000);
		if (ntest_done>=n) break;
	}
	printf("Completed %d/%d tests\n",ntest_done,n);
	for (i=0; i<n; i++) {
		th_free(&h[i]); /* Closes the handle, not the thread itself */
	}
	return TRUE;
}
int test_fail(char *reason)
{
	printf("test failed, Reason (%.90s)\n",reason);
	abort();
	return 0;
}
static int glob;
int thread_test_main(void *x)
{
	char bf[BFSZ];
	FILE *f;
	static int skip;
	char *s;
	int i;
	int n = (int) x;
	/* Open a file, do some strtok's */


	mutex_wait(M_MAIN); 
	mutex_wait(M_MAIN); 
/*	printf("re-entrant mutex's work ok\n");*/
	mutex_release(M_MAIN);
	mutex_release(M_MAIN);


	errno = th_id();
	sleep(2);
	if (errno!=th_id())  test_fail("errno"); 
	f = fopen("test.dat","r");
	if (f==NULL) {
		f = fopen("test.dat","a");
	}
	if (f==NULL) {
		printf("Could not open file handle %s\n",strerror(errno));
	}

	for (i=0; i<30; i++) {
#ifdef WIN32
		if (!skip) {
			strcpy(bf,"a b c d e f");
			s = strtok(bf," ");
			lib_msleep(1);
			if (strcmp(s,"a")!=0) {printf("strtok is faulty\n"); skip = TRUE;} 
			s = strtok(NULL," ");
			lib_msleep(1);
			if (strcmp(s,"b")!=0) {printf("strtok is faulty\n"); skip = TRUE;}
		}
#endif
		mutex_wait(M_MAIN); 
		glob = i;
		lib_msleep(1);
		if (glob!=i) {
			mutex_release(M_MAIN);
			test_fail("Mutex fails");
		}
		mutex_release(M_MAIN);
	}
	if (f!=NULL) fclose(f);	
	printf("Test %d finished\n",n);
	mutex_wait(M_MAIN); 
	ntest_done++;
	mutex_release(M_MAIN);

	return 0;
}
#endif
