/* dirutil.c - MS-DOS directory reading routines
 *
 * Bdale Garbee, N3EUA, Dave Trulli, NN2Z, and Phil Karn, KA9Q
 * Directory sorting by Mike Chepponis, K3MC
 * New version using regs.h by Russell Nelson.
 * Rewritten for Turbo-C 2.0 routines by Phil Karn, KA9Q 25 March 89
 *
 * Added path filter functions and applied to dodir
 * also used by ftpcli.c, added current directory  
 * storage capability (11/92 WA3DSP)
 * Bugfixes in the above by WG7J
 */

#include <stdio.h>
#ifndef UNIX
#include <dir.h>
#include <dos.h>
#else
#include <sys/stat.h>
/* There are at least FOUR variants of statfs()... we handle SCO and Linux */
#ifdef M_UNIX
#include <sys/statfs.h>
#define f_bavail f_bfree
#else
#include <sys/vfs.h>
#endif /* M_UNIX */
#endif /* UNIX */
#include <ctype.h>
#include <stdlib.h>
#ifndef UNIX
#include <io.h>
#endif

#include <stdlib.h>
#include <time.h>
#include "global.h"
#include "config.h"
#include "proc.h"
#include "session.h"
#include "dirutil.h"
#include "commands.h"
#include "smtp.h"

#ifdef CALLSERVER
#include <string.h>
#include <alloc.h>
extern char *CDROM; /* buckbook.c: defines CDROM drive letter e.g. "s:"  */
#endif

struct dirsort {
	struct dirsort *next;
	struct ffblk de;
};
#define	NULLSORT (struct dirsort *)0

static void commas __ARGS((char *dest));
static int fncmp __ARGS((char *a, char *b));
static void format_fname_full __ARGS((FILE *file,struct ffblk *sbuf,int full,
	int n));
static void free_clist __ARGS((struct dirsort *this));

#ifdef	notdef
static int getdir_nosort __ARGS((char *path,int full,FILE *file));
#endif
static int nextname __ARGS((int command, char *name, struct ffblk *sbuf));
static void print_free_space __ARGS((char *path,FILE *file,int n));
void undosify __ARGS((char *s));
extern void crunch __ARGS((char *buf,char *path));
static char *wildcardize __ARGS((char *path));
char *defpath __ARGS((struct cur_dirs *curdirs,char *path));

#ifdef TNOS_68K
#define REGFILE	(FA_DIREC)
#define ALLFILES "*"
#include <modes.h>
#else
#define REGFILE	(FA_HIDDEN|FA_SYSTEM|FA_DIREC)
#define ALLFILES "*.*"
#endif

#define	insert_ptr(list,new)	(new->next = list,list = new)


/* Create a directory listing in a temp file and return the resulting file
 * descriptor. If full == 1, give a full listing; else return just a list
 * of names.
 */
FILE *
dir(path,full)
char *path;
int full;
{
FILE *fp;
char *path2;

	if((fp = tmpfile()) != NULLFILE){
		if (!path)
			path = Command->curdirs->dir;
		if (full > 0)
			fprintf(fp, "Directory of %s at %s\n\n", path, Hostname);
		path2 = strdup(make_dir_path(2,path,Command->curdirs->dir));
		getdir(path2,full,fp);
		free (path2);
		rewind(fp);
	}
	return fp;
}

/* find the first or next file and lowercase it. */
static int
nextname(command, name, sbuf)
int command;
char *name;
struct ffblk *sbuf;
{
	int found;

	switch(command){
	case 0:
		found = findfirst(name,sbuf,REGFILE);
		break;
	default:
		found = findnext(sbuf);
	}
	found = (found == 0);
#ifndef UNIX
	if(found)
		strlwr(sbuf->ff_name);
#endif

	return found;
}


/* wildcard filename lookup */
int
filedir(name,times,ret_str)
char *name;
int times;
char *ret_str;
{
static struct ffblk sbuf;
int rval;

	switch(times){
		case 0:		rval = findfirst(name,&sbuf,REGFILE);
				break;
		default:	rval = findnext(&sbuf);
				break;
	}
	if(rval == -1)
		ret_str[0] = '\0';
	else 	/* Copy result to output */
		strcpy(ret_str, sbuf.ff_name);
	return rval;
}


/* do a directory list to the stream 
 * full = 0 -> short form, 1 is long, 2 is long with "|" down center, and
 * -1 is short with two entries per line.
*/
int
getdir(path,full,file)
char *path;
int full;
FILE *file;
{
	struct ffblk sbuf;
	int command = 0;
	int n = 0;
	struct dirsort *head, *here, *new;

	path = wildcardize(path);

	head = NULLSORT;	/* No head of chain yet... */
	for(;;){
		pwait (NULL);
		if (!nextname(command, path, &sbuf))
			break;
		command = 1;	/* Got first one already... */
		if (sbuf.ff_name[0] == '.')	/* drop "." and ".." */
#if defined(UNIX) || defined(TNOS_68K)
		    if (sbuf.ff_name[1] == '\0' || (sbuf.ff_name[1] == '.' &&
						     sbuf.ff_name[2] == '\0'))
#endif
			continue;

		new = (struct dirsort *) mallocw(sizeof(struct dirsort));
		new->de = sbuf;	/* Copy contents of directory entry struct */

		/* insert it into the list */
		if (!head || fncmp(new->de.ff_name, head->de.ff_name) < 0) {
			insert_ptr(head, new);
		} else {
			register struct dirsort *this;
			for (this = head;
			    this->next != NULLSORT;
			    this = this->next)
				if (fncmp(new->de.ff_name, this->next->de.ff_name) < 0)
					break;
			insert_ptr(this->next, new);
		}
	} /* infinite FOR loop */

	for (here = head; here; here = here->next)
		format_fname_full(file,&here->de,full,++n);

	/* Give back all the memory we temporarily needed... */
	free_clist(head);

	if(full > 0)
		print_free_space(path, file, n);

	if ((full == -1) && (n & 1))
		fputs("\n",file);

	return 0;
}

static int
fncmp(a,b)
register char *a, *b;
{
        int i;

	for(;;){
#ifndef TNOS_68K
		if (*a == '.')
			return -1;
		if (*b == '.')
			return 1;
#endif
		if ((i = *a - *b++) != 0)
			return i;
		if (!*a++)
			return -1;
	}
}

#ifdef ALLCMD
/* Change working directory */
int
docd(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	if(argc > 1){
		if (!dir_ok(argv[1],Command->curdirs)) {
			tprintf("Invalid Drive/Directory - %s\n",argv[1]);
			return 1;
		}
	}
	tprintf("Local Directory - %s\n", Command->curdirs->dir);
	return 0;
}


#ifdef MSDOS
/* Change working drive */
int
dodrive(argc,argv,p)
int argc;
char *argv[];
void *p;
{
char **margv;

	margv = (char **)callocw(2,sizeof(char *));
	margv[1] = strdup(argv[0]);
	docd(2,margv,p);
	free(margv[1]);
	free(margv);
	return 0;
}
#endif


char *
defpath (curdirs,path)
struct cur_dirs *curdirs;
char *path;
{
#if !defined(TNOS_68K) && !defined(UNIX)
int drive;

	drive = tolower(path[0]) - '`';
	if (strlen (path) == 2 && path[1] == ':' && curdirs->curdir[drive] != NULLCHAR)
		return curdirs->curdir[drive];
	else
#endif
		return path;
}


#if (defined(ALLCMD) || defined(ALLSESSIONS))
/* List directory to console */
int
dircmd(argc,argv,p)
int argc;
char *argv[];
void *p;
{
	char *path, *file;
	FILE *fp;
	char tmpname[80];
	char **margv;

	if (argc > 1)		{
		file = strdup (make_fname (Command->curdirs->dir, defpath (Command->curdirs, argv[1])));
		path=strdup(make_dir_path(argc,file,Command->curdirs->dir));
		free (file);
	} else
		path = strdup (Command->curdirs->dir);
	margv = (char **)callocw(2,sizeof(char *));
	tmpnam(tmpname);
	fp = fopen(tmpname,WRITE_TEXT);
	getdir(path,1,fp);
	free(path);
	fclose(fp);
	margv[1] = strdup(tmpname);
	morecmd(2,margv,p);
	free(margv[1]);
	free(margv);
	unlink(tmpname);
	return 0;
}

int
dodir(argc,argv,p)
int argc;
char *argv[];
void *p;
{
    char **pargv;
    int i;

    if(Curproc->input == Command->input) {
        /* Make private copy of argv and args,
         * spawn off subprocess and return.
         */
        pargv = (char **)callocw((unsigned)argc + 1,sizeof(char *));
        for(i=0;i<argc;i++)
            pargv[i] = strdup(argv[i]);
        pargv[i] = NULL;
        newproc("dir",512,(void (*)__ARGS((int,void*,void*)))dircmd,argc,(void *)pargv,p,1);
    } else
        dircmd(argc,argv,p);
    return 0;
}

#endif


/* Create directory */
int
domkd(argc,argv,p)
int argc;
char *argv[];
void *p;
{
char path[128];
	
	strcpy(path,make_fname(Command->curdirs->dir,argv[1]));
	
#ifdef UNIX
	if(mkdir(path, 0777) == -1)
#else
	if(mkdir(path) == -1)
#endif
		tprintf("Can't make %s: %s\n",path,sys_errlist[errno]);
	return 0;
}
/* Remove directory */
int
dormd(argc,argv,p)
int argc;
char *argv[];
void *p;
{
char path[128];
	
	strcpy(path,make_fname(Command->curdirs->dir,argv[1]));
	if(rmdir(path) == -1)
		tprintf("Can't remove %s: %s\n",path,sys_errlist[errno]);
	return 0;
}
#endif /*ALLCMD*/

/*
 * Return a string with commas every 3 positions.
 * the original string is replace with the string with commas.
 *
 * The caller must be sure that there is enough room for the resultant
 * string.
 *
 *
 * k3mc 4 Dec 87
 */
static void
commas(dest)
char *dest;
{
	char *src, *core;	/* Place holder for malloc */
	unsigned cc;		/* The comma counter */
	unsigned len;

	len = strlen(dest);
	/* Make a copy, so we can muck around */
	core = src = strdup(dest);

	cc = (len-1)%3 + 1;	/* Tells us when to insert a comma */

	while(*src != '\0'){
		*dest++ = *src++;
		if( ((--cc) == 0) && *src ){
			*dest++ = ','; cc = 3;
		}
	}
	free(core);
	*dest = '\0';
}
/* fix up the filename so that it contains the proper wildcard set */
static char *
wildcardize(path)
char *path;
{
#ifndef TNOS_68K
#define THISSIZE 64
#else
#ifdef UNIX
#define THISSIZE 1024
#else
#define THISSIZE 128
#endif
#endif
struct ffblk sbuf;
static char ourpath[THISSIZE];

	/* Root directory is a special case */
	if(path == NULLCHAR ||
	   *path == '\0'
#ifndef TNOS_68K
	   || strcmp(path,"\\") == 0 ||
	   strcmp(path,"/") == 0)	{
		path = "\\*.*";
#else
		)	{
		path = "*";
#endif
		return path;
		}
#ifdef MSDOS
    /* MSDOS Root directory can also have a drive letter prefix */
    if(isalpha(*path) &&
        (strcmp(path+1,":\\") == 0 || strcmp(path+1,":/") == 0) ) {
            sprintf(ourpath,"%c:/*.*", *path);
            return ourpath;
    }
#endif
   
#ifdef CALLSERVER
    if  (CDROM != NULLCHAR &&  strcmp(path, CDROM) == 0)  {
      path = (char *) mallocw(7); /* THIS causes a memory leak ! - WG7J */
      sprintf(path, "%s/*.*", CDROM);
    }
#endif

	/* if they gave the name of a subdirectory, append \*.* to it */
	if (
#ifdef TNOS_68K
	    (*path == '/' && !(strchr(path+1, '/'))) ||
#endif
	    (nextname(0, path, &sbuf) &&
	    (sbuf.ff_attrib & FA_DIREC) &&
	    !nextname(1, path, &sbuf))) {

		/* if there isn't enough room, give up -- it's invalid anyway */
		if (strlen(path) + 4 > (THISSIZE - 1)) return path;
		strcpy(ourpath, path);
#ifdef TNOS_68K
		strcat(ourpath, "/*");
#else
#ifdef UNIX
		strcat(ourpath, "/*");
#else
		strcat(ourpath, "\\*.*");
#endif
#endif
		return ourpath;
	}
	return path;
}

static void
format_fname_full(file, sbuf, full, n)
FILE *file;
struct ffblk *sbuf;
int full, n;
{
#ifdef UNIX
static char line_buf[1025];
static char cbuf[1025];
#else
char line_buf[50];		/* for long dirlist */
char cbuf[20];			/* for making line_buf */
#endif

	strcpy(cbuf,sbuf->ff_name);
	if(sbuf->ff_attrib & FA_DIREC) strcat(cbuf, "/");
	if (full > 0) {
		/* Long form, give other info too */
#if !defined(TNOS_68K) && !defined(UNIX)
		sprintf(line_buf,"%-13s",cbuf);
#else
		sprintf(line_buf,"%-14s",cbuf);
		if (strlen (cbuf) > 14)
			strcpy (&line_buf[12], "..");
#endif
		if(sbuf->ff_attrib & FA_DIREC)	{
#if !defined(TNOS_68K) && !defined(UNIX)
			strcat(line_buf,"           ");/* 11 spaces */
#else
			strcat(line_buf,"          ");/* 10 spaces */
#endif
		} else {
			sprintf(cbuf,"%ld",sbuf->ff_fsize);
			commas(cbuf);
#if !defined(TNOS_68K) && !defined(UNIX)
			sprintf(line_buf+strlen(line_buf),"%10s ",cbuf);
#else
			sprintf(line_buf+strlen(line_buf),"%9s ",cbuf);
#endif
		}
		sprintf(line_buf+strlen(line_buf),"%2d:%02d %2d/%02d/%02d%s",
#ifdef UNIX
		  sbuf->ff_ftime.tm_hour, sbuf->ff_ftime.tm_min,
		  sbuf->ff_ftime.tm_mon + 1, sbuf->ff_ftime.tm_mday,
		  sbuf->ff_ftime.tm_year,
#else
		  (sbuf->ff_ftime >> 11) & 0x1f,	/* hour */
		  (sbuf->ff_ftime >> 5) & 0x3f,	/* minute */
		  (sbuf->ff_fdate >> 5) & 0xf,	/* month */
		  (sbuf->ff_fdate ) & 0x1f,		/* day */
		  (sbuf->ff_fdate >> 9) + 80,	/* year */
#endif
		  (full == 2) ? " | \n" : (n & 1) ? "   " : "\n");
		fputs(line_buf,file);
	} else {
		if (!full)	{
			fputs(cbuf,file);
			fputs("\n",file);
		} else	{
			fprintf(file, "%-37.37s", cbuf);
			fputs ((n & 1) ? "   " : "\n", file);
		}
	}
}
/* Provide additional information only on DIR */
static void
print_free_space(path, file, n)
char *path;
FILE *file;
int n;
{
	unsigned long free_bytes, total_bytes;
	char s_free[20], s_total[20];
	char cbuf[20];
#ifdef UNIX
	char *pbuf;
	struct statfs vfsb;
	char *cp;

	pbuf = mallocw(1024);
	strcpy(pbuf, path);
	if ((cp = strrchr(pbuf, '/')) == 0)
	    strcat(pbuf, "/.");
	else
	{
	    *++cp = '.';
	    *++cp = '\0';
	}
#ifdef M_UNIX
	statfs(pbuf, &vfsb, sizeof vfsb, 0);
#else
	statfs(pbuf, &vfsb);
#endif
	free_bytes = vfsb.f_bsize * vfsb.f_bavail;
	total_bytes = vfsb.f_bsize * vfsb.f_blocks;
#else
	struct dfree dtable;
	unsigned long bpcl;
#ifndef TNOS_68K
	char resolved[80];	/* may need as little as 67 */
	union REGS regs;
	struct SREGS sregs;
	int drive;

	/* do an undocumented call to find wich drive this mane resolves to */
	regs.x.si = FP_OFF(path);
	sregs.ds = FP_SEG(path);
	regs.x.di = FP_OFF(resolved);
	sregs.es = FP_SEG(resolved);
	regs.h.ah = 0x60;
	intdosx(&regs,&regs,&sregs);
	drive = resolved[0] - '@';

#endif
	/* Find disk free space */
#ifndef TNOS_68K
	getdfree(drive,&dtable);
#else
	getdfree(path,&dtable);
#endif

	bpcl = dtable.df_bsec * dtable.df_sclus;
	free_bytes  = dtable.df_avail * bpcl;
	total_bytes = dtable.df_total * bpcl;
#endif

	if(n & 1)
		fputs("\n",file);

	sprintf(s_free,"%ld",free_bytes);
	commas(s_free);
	sprintf(s_total,"%ld",total_bytes);
	commas(s_total);

	if(n)
		sprintf(cbuf,"%d",n);
	else
		strcpy(cbuf,"No");

	fprintf(file,"%s file%s. %s bytes free. Disk size %s bytes.\n",
		cbuf,(n==1? "":"s"),s_free,s_total);
}
static void
free_clist(this)
struct dirsort *this;
{
	struct dirsort *next;

	while (this != NULLSORT) {
		next = this->next;
		free(this);
		this = next;
	}
}
#ifdef	notdef
static int
getdir_nosort(path,full,file)
char *path;
int full;
FILE *file;
{
	struct ffblk sbuf;
	int command;
	int n = 0;	/* Number of directory entries */

/*	path = wildcardize(path); */
	command = 0;
	while(nextname(command, path, &sbuf)){
		command = 1;	/* Got first one already... */
		if (sbuf.ff_name[0] == '.')	/* drop "." and ".." */
			continue;
		format_fname_full(file, &sbuf, full, ++n);
	}
	if(full)
		print_free_space(path, file, n);
	return 0;
}
#endif

/* Translate those %$#@!! backslashes to proper form */
void
undosify(s)
char *s;
{
	if (s)		{
		while(*s != '\0')	{
			if(*s == '\\')
				*s = '/';
			s++;
		}
	}
}


char *
make_dir_path (count,arg,curdir)
int count;
char *arg, *curdir;
{
#ifdef UNIX
	char path[1024], *q;
#else
	char path[128], *q;
#endif

	undosify(arg);
	undosify(curdir);
	if (count>=2) {
	   q=arg;
	   q+=strlen(arg)-1;
#ifndef TNOS_68K
	   if (*q=='/' || *q==':') {
#else
	   if (*q=='/') {
#endif
	      strcpy(path,arg);
	      strcat(path,ALLFILES);
	   } else
	      strcpy(path,arg);
	} else {
	   strcpy(path,ALLFILES);
	}
	return (make_fname(curdir,path));
}        

char*
make_fname(curdir,fname)
char *curdir, *fname;
{
char *p;
#ifdef UNIX
static char new_name[1024];
#else
static char new_name[128];
#endif

	strcpy(new_name,curdir);
	undosify(fname);
	if (fname && *fname)	{
#if !defined(TNOS_68K) && !defined(UNIX)
		if (fname[0]=='/' || (strchr(fname,':') != NULLCHAR) )
#else
		if (fname[0]=='/')
#endif
			return fname;
		else	{
			p=new_name;
			p+=strlen(p)-1;
			if (*p=='/')
				*p='\0';
			crunch(new_name,fname);
#ifdef MSDOS
			if (new_name[strlen(new_name)-1] == ':')
				strcat (new_name, "/");
#endif
		}
	}
	return new_name;
}

/* Check Drive/Directory for validity - 1=OK, 0=NOGOOD */

int
dir_ok(newpath,dirs)
char *newpath;
struct cur_dirs *dirs;
{
#ifndef TNOS_68K
#ifdef UNIX
    char *a, curpath[1024];
#else
    char *a, curpath[128];
#endif
    char buf[128],fullpath[128];
    int result;
#ifndef UNIX
    int drive = dirs->drv;
#endif

    undosify(newpath);
#ifdef UNIX
    strcpy(buf, newpath);
    strcpy(curpath, dirs->dir);
#else
    strlwr(newpath);
    a=newpath;
    if ((*(a+1)==':') && (isalpha(*a))){
        drive=tolower(*a)-'`';
        strcpy(buf,a+2);
        if (dirs->curdir[drive]==NULLCHAR) {
            if(!getcurdir(drive,curpath)) {
                undosify(curpath);
                sprintf(fullpath,"%c:/%s",drive+'`',curpath);
                dirs->curdir[drive]=strdup(fullpath);
                dirs->drv=drive;
                dirs->dir=dirs->curdir[drive];
            }
        }
    } else {
        strcpy(buf,newpath);
    }

    if((a=dirs->curdir[drive])!=NULLCHAR) {
        if ((*(a+1)==':') && (isalpha(*a))){
            if (*(a+2)=='/')
		       strcpy(curpath,a+3);
		    else
		       strcpy(curpath,a+2);
        } else {
		    strcpy(curpath,a);
        }
    } else {
	  strcpy(curpath,"");
    }
#endif

    if (*buf!='/') {
	  crunch(curpath,buf);
    } else {
	  strcpy(curpath,buf+1);
    }
    a=curpath;
#ifdef UNIX
    sprintf(fullpath,"%s%s",(*a!='/' ? "/" : ""),curpath);
#else
    sprintf(fullpath,"%c:%s%s",drive+'`',(*a!='/' ? "/" : ""),curpath);
#endif
    if((result=access(fullpath,0)+1)==1) {
#ifdef UNIX
	free(dirs->dir);
	dirs->dir = strdup(fullpath);
#else
        if(dirs->curdir[drive])
            free(dirs->curdir[drive]);
        dirs->curdir[drive]=strdup(fullpath);
        dirs->drv=drive;
        dirs->dir=dirs->curdir[drive];
#endif
    }
    return result;
#else
int result, new = 0;
char fullpath[1024];

	undosify(newpath);
	strlwr(newpath);
	result = ((access (newpath, S_IFDIR) == -1) ? 0 : 1);
	if (result)	{
/*		chdir (newpath);	*/
		new = (*newpath == '/');
		sprintf(fullpath,"%s%s%s",!new ? dirs->dir : "",!new ? "/" : "",newpath);
		free (dirs->dir);
		dirs->dir = strdup (fullpath);
	}
	return (result);
#endif
}

char *
init_dirs(dirs)
struct cur_dirs *dirs;
{
#ifdef UNIX
    dirs->dir = getcwd(NULL, 1024);
    return dirs->dir;
#else
#ifndef TNOS_68K
    char buf[128],fullpath[128];
    int x,drive;

    for(x=0;x<=26;x++)
       dirs->curdir[x]='\0';

    drive=getdisk()+1;
    getcurdir(drive,buf);
    undosify(buf);
    strlwr(buf);
    sprintf(fullpath,"%c:/%s",drive+'`',buf);
    dirs->curdir[drive]=strdup(fullpath);
    dirs->drv=drive;
    dirs->dir=dirs->curdir[drive];
    return dirs->curdir[drive];
#else
char fullpath[1024];

    dirs->dir = strdup (getcwd (fullpath, 1024));
    return (dirs->dir);
#endif
#endif
}

void
free_dirs(dirs)
struct cur_dirs *dirs;
{
#ifdef UNIX
	free(dirs->dir);
#else
#ifndef TNOS_68K
	int x;

	for(x=0;x<=26;x++) {
		if (dirs->curdir[x])
			free(dirs->curdir[x]);
	}
#else
	free (dirs->dir);
#endif
#endif
}



