/*****************************************************************************
 *
 * File ..................: mbcico/filelist.c
 * Purpose ...............: fidonet mailer 
 * Last modification date : 18-Oct-1999
 *
 *****************************************************************************
 * Copyright (C) 1997-1999
 *   
 * Michiel Broek		FIDO:		2:2801/16
 * Beekmansbos 10		Internet:	mbroek@ux123.pttnwb.nl
 * 1971 BV IJmuiden
 * the Netherlands
 *
 * This file is part of MBSE BBS.
 *
 * This BBS 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.
 *
 * MBSE BBS 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 MBSE BBS; see the file COPYING.  If not, write to the Free
 * Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
 *****************************************************************************/

#include "../include/libs.h"
#include "../include/structs.h"
#include "../include/records.h"
#include "../common/common.h"
#include "config.h"
#include "session.h"
#include "packet.h"
#include "filelist.h"


#ifndef PATH_MAX
#define PATH_MAX 512
#endif

extern int master;
int made_request;



static char *tmpkname(void);
static char *tmpkname(void)
{
	static char buf[16];

	sprintf(buf,"%08lx.pkt", sequencer());
	return buf;
}



char *xtodos(char *orig)
{
	int	i;
	char	buf[8+1+3+1],*copy,*p,*q,*r;

	if (orig == NULL) 
		return NULL;

	if ((remote_flags & SESSION_FNC) == 0) {
		Syslog('o', "No filename conversion for \"%s\"",S(orig));
		return xstrcpy(orig);
	}

	copy=xstrcpy(orig);
	if ((p=strrchr(copy,'/'))) 
		p++;
	else 
		p=copy;

	if (strcmp(q=copy+strlen(copy)-strlen(".tar.gz"),".tar.gz") == 0) {
		*q='\0';
		q=(char *)"tgz";
	} else if (strcmp(q=copy+strlen(copy)-strlen(".tar.z"),".tar.z") == 0) {
		*q='\0';
		q=(char *)"tgz";
	} else if (strcmp(q=copy+strlen(copy)-strlen(".tar.Z"),".tar.Z") == 0) {
		*q='\0';
		q=(char *)"taz";
	} else if ((q=strrchr(p,'.'))) 
		*q++='\0';
	else 
		q=NULL;

	r=buf;
	for (i=0;(i<8) && (*p);i++,p++,r++)
		switch (*p) {
			case '.':
			case '\\':	*r='_'; break;
			default:	*r=toupper(*p);
		}

	if (q) {
		*r++='.';
		for (i=0;(i<3) && (*q);i++,q++,r++)
			switch (*q) {
				case '.':
				case '\\':	*r='_'; break;
				default:	*r=toupper(*q);
			}
	}
	*r++='\0';

	Syslog('o', "name \"%s\" converted to \"%s\"",S(orig),S(buf));

	free(copy);
	return xstrcpy(buf);
}



/*
 * Add the specified entry to the list of files which should be sent
 * to the remote system.
 * 1. lst		file list to add entry to
 * 2. local		local filename
 * 3. remote		remote filename
 * 4. disposition	disposition
 * 5. floff		offset of entry in flo-file (-1 if this is a flo file)
 * 6. flofp		FILE * of flo file
 * 7. toend		append to end of list
 */
void add_list(file_list **lst, char *local, char *Remote, int disposition, off_t floff, FILE *flofp, int toend)
{
	file_list **tmpl;
	file_list *tmp;

	Syslog('o', "add_list(\"%s\",\"%s\",%d,%s)",
		S(local),S(Remote),disposition,toend?"to end":"to beg");

	if (toend) 
		for (tmpl = lst; *tmpl; tmpl =&((*tmpl)->next));
	else 
		tmpl = &tmp;
	*tmpl = (file_list*)xmalloc(sizeof(file_list));
	if (toend) {
		(*tmpl)->next = NULL;
	} else {
		(*tmpl)->next = *lst;
		*lst = *tmpl;
	}

	(*tmpl)->remote      = xtodos(Remote);
	(*tmpl)->local       = xstrcpy(local);
	(*tmpl)->disposition = disposition;
	(*tmpl)->floff       = floff;
	(*tmpl)->flofp       = flofp;
	return;
}



static void check_flo(file_list **, char *);
static void check_flo(file_list **lst, char *nm)
{
	FILE	*fp;
	off_t	off;
	struct	flock fl;
	char	buf[PATH_MAX],buf2[PATH_MAX],*p,*q;
	int	disposition;
	struct	stat stbuf;

	Syslog('o', "check_flo(\"%s\")",S(nm));

	if ((fp = fopen(nm,"r+")) == NULL) {
		Syslog('o',"no flo file");
		return;
	}
	fl.l_type   = F_RDLCK;
	fl.l_whence = 0;
	fl.l_start  = 0L;
	fl.l_len    = 0L;
	if (fcntl(fileno(fp), F_SETLK, &fl) != 0) {
		if (errno != EAGAIN)
			WriteError("$cannot read-lock \"%s\"",S(nm));
		else 
			Syslog('o',"flo file busy");
		fclose(fp);
		return;
	}

	if (stat(nm, &stbuf) != 0) {
		WriteError("$cannot access \"%s\"",S(nm));
		fclose(fp);
		return;
	}

	while (!feof(fp) && !ferror(fp)) {
		off = ftell(fp);
		if (fgets(buf, sizeof(buf)-1, fp) == NULL) 
			continue;
		if (buf[0] == '~') 
			continue; /* skip sent files */
		if (*(p=buf + strlen(buf) -1) == '\n') 
			*p-- = '\0';
		if (*p == '\r') 
			*p = '\0';

		switch (buf[0]) {
			case '#':	p=buf+1; disposition=TFS; break;
			case '-':
			case '^':	p=buf+1; disposition=KFS; break;
			case '@':	p=buf+1; disposition=LEAVE; break;
			case 0:		continue;
			default:	p=buf; disposition=LEAVE; break;
		}

		memset(&buf2, 0, sizeof(buf2));
		if (strlen(CFG.dospath)) {
			if (strncasecmp(p, CFG.dospath, strlen(CFG.dospath)) == 0) {
				strcpy(buf2,uxoutbound);
				for (p+=strlen(CFG.dospath), q=buf2+strlen(buf2); *p; p++, q++)
					*q = ((*p) == '\\')?'/':tolower(*p);
				*q = '\0';
				p = buf2;
			}
		} else {
			if (strncasecmp(p, CFG.uxpath, strlen(CFG.uxpath)) == 0) {
				for (p=p, q=buf2+strlen(buf2); *p; p++, q++)
					*q = ((*p) == '\\')?'/':tolower(*p);
				*q = '\0';
				p = buf2;
			}
		}

		if ((q=strrchr(p,'/')))
			q++;
		else
			q = p;
		add_list(lst, p, q, disposition, off, fp, 1);
	}

	/*
	 * Add flo file to file list
	 */
	add_list(lst, nm, NULL, KFS, -1L, fp, 1);

	/* here, we leave .flo file open, it will be closed by */
	/* execute_disposition */

	return;
}



file_list *create_filelist(fa_list *al, char *fl, int create)
{
	file_list	*st=NULL;
	file_list	*tmpf;
	fa_list		*tmpa;
	char		flavor, *tmpfl;
	char		*nm;
	char		tmpreq[13];
	struct stat	stbuf;
	int		packets = 0;
	FILE		*fp;

	Syslog('o',"Create_filelist(%s,\"%s\",%d)",
		al?ascfnode(al->addr,0x1f):"<none>",
		S(fl),create);
	made_request = 0;

	for (tmpa = al; tmpa; tmpa = tmpa->next) {
		Syslog('o', "Check address %s", ascfnode(tmpa->addr, 0x1f));

		/*
		 * Check spool files.
		 */
		if (strchr(fl, 'o')) {
			nm=splname(tmpa->addr);
			if ((fp=fopen(nm,"w"))) 
				fclose(fp);
			if ((nm != NULL) && (stat(nm,&stbuf) == 0))
				add_list(&st,nm,NULL,DSF,0L,NULL,1);
		}

		/*
		 * Check .pol files
		 */
		nm = polname(tmpa->addr);
		if ((nm != NULL) && (stat(nm,&stbuf) == 0))
			add_list(&st,nm,NULL,DSF,0L,NULL,1);

		/*
		 * Check other files, all flavors
		 */
		tmpfl = fl;
		while ((flavor = *tmpfl++)) {
			/*
			 * Check normal mail packets
			 */
			nm=pktname(tmpa->addr,flavor);
			if ((nm != NULL) && (stat(nm,&stbuf) == 0)) {
				packets++;
				add_list(&st,nm,tmpkname(),KFS,0L,NULL,1);
			}

			/*
			 * Check .flo files for file attaches
			 */
			nm=floname(tmpa->addr,flavor);
			check_flo(&st,nm);
		}

		if ((session_flags & SESSION_WAZOO) &&
		    ((session_flags & SESSION_HYDRA) == 0) &&
		    (master || ((session_flags & SESSION_IFNA) == 0))) {
			/*
			 * we don't distinguish flavoured reqs
			 */
			nm=reqname(tmpa->addr);
			if ((nm != NULL) && (stat(nm,&stbuf) == 0)) {
				sprintf(tmpreq,"%04X%04X.REQ",
					tmpa->addr->net,tmpa->addr->node);
				add_list(&st,nm,tmpreq,DSF,0L,NULL,1);
				made_request = 1;
			}
		}
	}

	if (((st == NULL) && (create > 1)) ||
	    ((st != NULL) && (packets == 0) && (create > 0))) {
		Syslog('o',"Create packet for %s",ascfnode(al->addr,0x1f));
		if ((fp = openpkt(NULL, al->addr, 'o'))) {
			fwrite('\0', 2, 1, fp);
			fclose(fp);
		}
		add_list(&st,pktname(al->addr,'o'),tmpkname(),KFS,0L,NULL,0);
	}

	for (tmpf = st; tmpf; tmpf = tmpf->next)
		Syslog('o',"flist: \"%s\" -> \"%s\" dsp:%d flofp:%lu floff:%lu",
			S(tmpf->local), S(tmpf->remote), tmpf->disposition,
			(unsigned long)tmpf->flofp, (unsigned long)tmpf->floff);

	return st;
}



/*
 * Create file request list for the Hydra protocol.
 */
file_list *create_freqlist(fa_list *al)
{
	file_list	*st = NULL, *tmpf;
	fa_list		*tmpa;
	char		*nm;
	char		tmpreq[13];
	struct stat	stbuf;

	Syslog('o', "create_freqlist(%s)", al?ascfnode(al->addr, 0x1f):"<none>");
	made_request = 0;

	for (tmpa = al; tmpa; tmpa = tmpa->next) {
		nm = reqname(tmpa->addr);
		if ((nm != NULL) && (stat(nm, &stbuf) == 0)) {
			sprintf(tmpreq, "%04X%04X.REQ", tmpa->addr->net, tmpa->addr->node);
			add_list(&st, nm, tmpreq, DSF, 0L, NULL, 1);
			made_request = 1;
		}
	}

	for (tmpf = st; tmpf; tmpf = tmpf->next)
		Syslog('o', "flist: \"%s\" -> \"%s\" dsp:%d flofp:%lu floff:%lu",
			S(tmpf->local), S(tmpf->remote), tmpf->disposition,
			tmpf->flofp, tmpf->floff);

	return st;
}



void tidy_filelist(file_list *fl, int dsf)
{
	file_list *tmp;

	for (tmp=fl;fl;fl=tmp) {
		tmp=fl->next;
		if (dsf && (fl->disposition == DSF)) {
			Syslog('o',"Removing sent file \"%s\"",S(fl->local));
			if (unlink(fl->local) != 0) {
				if (errno == ENOENT)
					Syslog('o',"Cannot unlink nonexistent file \"%s\"",
						S(fl->local));
				else
					WriteError("$Cannot unlink file \"%s\"",
						S(fl->local));
			}
		}
		if (fl->local) 
			free(fl->local);
		if (fl->remote) 
			free(fl->remote);
		else if (fl->flofp) 
			fclose(fl->flofp);
		free(fl);
	}
	return;
}



void execute_disposition(file_list *fl)
{
	FILE	*fp=NULL;
	char	*nm;
	char	tpl='~';

	Syslog('o', "execute_disposition(%s)", fl->local);
	nm = fl->local;
	if (fl->flofp) {
		/*
		 * Check for special case: flo-file 
		 */
		if (fl->floff == -1) {
			/*
			 * We check if there are any files left for transmission
			 * in the flo-file to decide whether to remove or leave
			 * it on disk.
			 */
			char buf[PATH_MAX];
			int files_remain = 0;

			if (fseek(fl->flofp, 0L, 0) == 0) {
				while (!feof(fl->flofp) && !ferror(fl->flofp)) {
					if (fgets(buf, sizeof(buf)-1, fl->flofp) == NULL)
						continue;

					/*
					 * Count nr of files which haven't been
					 * sent yet
					 */
					if (buf[0] != '~')
						files_remain++;
				}

			} else {
				WriteError("$Error seeking in .flo to 0");
				files_remain = -1; /* Keep flo-file */
			}

			if (files_remain) {
				Syslog('o', "Leaving flo-file \"%s\", %d files remaining", S(nm), files_remain);
				fl->disposition = LEAVE;
			} else {
				fl->disposition = KFS;
			}
		} else {
			/*
			 * Mark files as sent in flo-file
			 */
			if (fseek(fl->flofp, fl->floff, 0) == 0) {
				if (fwrite(&tpl,1,1,fl->flofp) != 1) {
					WriteError("$Error writing '~' to .flo at %lu", (unsigned long)fl->floff);
				}
				fflush(fl->flofp);
				fdatasync(fileno(fl->flofp));
			} else 
				WriteError("$error seeking in .flo to %lu", (unsigned long)fl->floff);
		}
	}

	switch (fl->disposition) {
	case DSF:
	case LEAVE:	
			break;
	case TFS:	
			Syslog('o', "Truncating sent file \"%s\"",S(nm));
			if ((fp=fopen(nm,"w"))) 
				fclose(fp);
			else 
				WriteError("$Cannot truncate file \"%s\"",S(nm));
			break;
	case KFS:	
			Syslog('o', "Removing sent file \"%s\"",S(nm));
			if (unlink(nm) != 0) {
				if (errno == ENOENT)
					Syslog('o', "Cannot unlink nonexistent file \"%s\"", S(nm));
				else
					WriteError("$Cannot unlink file \"%s\"", S(nm));
			}
			break;
	default:	WriteError("execute_disposition: unknown disp %d for \"%s\"",
				fl->disposition,S(nm));
			break;
	}

	return;
}


