/*
 * Copyright (C) 1992 by Rush Record (rhr@clio.rice.edu)
 * 
 * This file is part of ED.
 * 
 * ED 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.
 * 
 * ED 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 ED
 * (see the file COPYING).  If not, write to the Free Software Foundation, 675
 * Mass Ave, Cambridge, MA 02139, USA.
 */
#include "opsys.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>

#ifndef NO_MMAP
#include <sys/types.h>
#include <sys/mman.h>
#endif

#include "unistd.h"
#include "memory.h"
#include "file.h"
#include "ctyp_dec.h"

#define FILENAME_OFFSET 40	/* this is the offset from the beginning of diredit records to the file name */

#ifndef O_BINARY
#define O_BINARY 0
#endif

/* NOTE: BLOCK_SIZE cannot be larger than BUFFER_SIZE */
#define BLOCK_SIZE 512
/* NOTE: BLOCK_SIZE must be an even multiple of BINARY_RECSIZE */
#define BUFFER_SIZE 2048

#include "rec.h"
#include "window.h"
#include "ed_dec.h"

typedef struct mmap_str *mmap_ptr;
typedef struct mmap_str
{
	mmap_ptr next;
	Char *adr;
	Int len;
} mmap_node;

static mmap_ptr base = NULL;
static Char months[] = "JanFebMarAprMayJunJulAugSepOctNovDec";
static Char ftpbin = 0;	/* flags a binary ftp'ed file was just loaded */
static Char foundusr[128];
static Char foundpwd[128];
static Char fileonly = 0;	/* flag to prevent interpretation of fname as dirname */
static Char forcebinary = 0;	/* flag to force binary transfer of file */
static Char maperror = 0;	/* flag that a map_file error other than ENOENT has occurred */

extern Char *filename();
extern Char *ftp_cwd();
extern Char *last_message();
#ifndef VMS
extern char *sys_errlist[];
#endif

/******************************************************************************\
|Routine: filename_offset
|Callby: command edit
|Purpose: Returns the offset to the file name in diredit records.
|Arguments:
|    none
\******************************************************************************/
Int filename_offset()
{
	return(FILENAME_OFFSET);
}

/******************************************************************************\
|Routine: mode_string
|Callby: read_in_diredit
|Purpose: Returns a string expressing the file's permissions, as in: rwxr-xr-x.
|Arguments:
|    mode is the file mode.
\******************************************************************************/
Char *mode_string(mode)
Int mode;
{
	static char rbuf[10];
	
	memset(rbuf,'-',9);
	rbuf[9] = '\0';
	if(mode & 1)
		rbuf[8] = 'x';
	if(mode & 2)
		rbuf[7] = 'w';
	if(mode & 4)
		rbuf[6] = 'r';
	if(mode & 8)
		rbuf[5] = 'x';
	if(mode & 16)
		rbuf[4] = 'w';
	if(mode & 32)
		rbuf[3] = 'r';
	if(mode & 64)
		rbuf[2] = 'x';
	if(mode & 128)
		rbuf[1] = 'w';
	if(mode & 256)
		rbuf[0] = 'r';
	return(rbuf);
}

/******************************************************************************\
|Routine: size_string
|Callby: read_in_diredit
|Purpose: Returns a string expressing the file size in 6 bytes.
|Arguments:
|    size is the size of the file in bytes.
\******************************************************************************/
Char *size_string(size)
Int size;
{
	static char rbuf[9];
	
	if(size > 99999999)
		sprintf(rbuf,"%5dmeg",size / 1000000);
	else
		sprintf(rbuf,"%8d",size);
	return(rbuf);
}

/******************************************************************************\
|Routine: parse_vms_perms
|Callby: parse_vms_mode read_in_diredit
|Purpose: Parses expressions like RWED. Returns unix-style mode.
|Arguments:
|    p is a pointer in the string that presumably contains the permissions. This
|      string may be empty, in which case it is terminated by , or \0 or ).
|    mode is the returned mode, as in rwx.
\******************************************************************************/
void parse_vms_perms(p,mode)
Char *p,*mode;
{
	memset(mode,'-',3);
	while(1)
		switch(*p++)
		{
			case 'R':
				mode[0] = 'r';
				break;
			case 'W':
				mode[1] = 'w';
				break;
			case 'E':
				mode[2] = 'x';
				break;
			case 'D':
				break;
			default:
				return;
		}
}

/******************************************************************************\
|Routine: parse_vms_mode
|Callby: read_in_diredit
|Purpose: Parses expressions like (RWED,RE,RE,). Returns unix-style mode.
|Arguments:
|    p is a pointer in the string that presumably contains the permissions.
|    mode is the returned mode, as in rwxr-xr-w.
\******************************************************************************/
void parse_vms_mode(p,mode)
Char *p,*mode;
{
	Char *q;
	
	mode[9] = '\0';
	if((q = strchr(p,'(')))
	{
		if((p = strchr(q,',')))
		{
			parse_vms_perms(++p,mode);
			if((q = strchr(p,',')))
			{
				parse_vms_perms(++q,mode + 3);
				if((p = strchr(q,',')))
					parse_vms_perms(++p,mode + 6);
				else
					goto badmode;
			}
			else
				goto badmode;
		}
		else
			goto badmode;
		return;
	}
badmode:
	strcpy(mode,"?????????");
}

/******************************************************************************\
|Routine: fix_vms_dir
|Callby: read_in_diredit
|Purpose: Converts *.dir;1 or *.DIR;1 to [.dir] or [.DIR]. Also strips device
|         and directory from the name, and packages it in [].
|Arguments:
|    file is the input file name.
|    ret is the returned file name (same as file if .dir not present).
\******************************************************************************/
void fix_vms_dir(file,ret)
Char *file,*ret;
{
	Char buf[512],*p,*q;
	
	strcpy(buf,file);
	q = buf;
	if((p = strstr(buf,".DIR;1")))
	{
		*p = '\0';
		while(q != p && *p != ']' && *p != '>')
			p--;
		if(q != p)
			p++;
		sprintf(ret,"[.%s]",p);
	}
	else if((p = strstr(buf,".dir;1")))	/* oddball lowercase VMS system */
	{
		*p = '\0';
		while(q != p && *p != ']' && *p != '>')
			p--;
		if(q != p)
			p++;
		sprintf(ret,"[.%s]",++p);
	}
	else	/* just strip device/directory */
	{
		for(p = buf,q = buf + strlen(buf) - 1;p != q && *q != ']' && *q != '>';)
			q--;
		if(q != p)
			q++;
		strcpy(ret,q);
	}
}

/******************************************************************************\
|Routine: fake_date
|Callby: read_in_diredit
|Purpose: Makes up a fake set of information about a file when parsing fails.
|Arguments:
|    none
\******************************************************************************/
void fake_date(size,year,month,day,hour,minute)
Int *size,*year;
Char *month;
Int *day,*hour,*minute;
{
	*size = 0;
	*year = 1955;
	strcpy(month,"Feb");
	*day = 8;
	*hour = 6;
	*minute = 35;
}

/******************************************************************************\
|Routine: parse_monthyear
|Callby: read_in_diredit
|Purpose: Deals with month and year in VMS listings.
|Arguments:
|    monthyear is the string that combines the month and year.
|    size is the (possibly) returned file size in bytes.
|    year is the (possibly) returned year.
|    month is the returned month.
|    day is the (possibly) returned day.
|    ihour is the (possibly) returned hour.
|    minute is the (possibly) returned minute.
|    mode is the returned file mode.
|    p is a pointer to the listing record.
\******************************************************************************/
void parse_monthyear(monthyear,size,year,month,day,ihour,minute,mode,p)
Char *monthyear;
Int *size,*year;
Char *month;
Int *day,*ihour,*minute;
Char *mode;
Char *p;
{
	Char *q;
	
	if((q = strchr(monthyear,'-')))
	{
		*q++ = '\0';
		strcpy(month,monthyear);
		if(my_sscanf(q,"%d",year) == 1)
		{
			parse_vms_mode(p,mode);
			return;
		}
	}
	fake_date(size,year,month,day,ihour,minute);	/* use fake date info */
}

/******************************************************************************\
|Routine: force_binary
|Callby: edit
|Purpose: Flags that the next file opened will be in binary mode.
|Arguments:
|    none
\******************************************************************************/
void force_binary()
{
	forcebinary = 1;
}

/******************************************************************************\
|Routine: ftp_fileonly
|Callby: edit
|Purpose: Flags that the next file opened will skip the directory check.
|Arguments:
|    none
\******************************************************************************/
void ftp_fileonly()
{
	fileonly = 1;
}

/******************************************************************************\
|Routine: netrc_password
|Callby: ftp_open
|Purpose: Returns netrc password or NULL string.
|Arguments:
|    none
\******************************************************************************/
Char *netrc_password()
{
	return(foundpwd);
}

/******************************************************************************\
|Routine: netrc_token
|Callby: find_netrc
|Purpose: Gets a token from the .netrc file.
|Arguments:
|    fp is the file pointer of the .netrc file.
\******************************************************************************/
Char *netrc_token(fp)
FILE *fp;
{
	static Char tok[512],*p;
	Int c;
	
	while(1)
	{
		if((c = fgetc(fp)) < 0)
		{
			fclose(fp);
			return(NULL);
		}
		if(isspace(c))
			continue;
		p = tok;
		*p++ = c;
		while(1)
		{
			if((c = fgetc(fp)) < 0)
			{
				*p++ = '\0';
				return(tok);
			}
			if(isspace(c))
			{
				*p++ = '\0';
				return(tok);
			}
			*p++ = c;
		}
	}
}

/******************************************************************************\
|Routine: find_netrc
|Callby: extract_host
|Purpose: Looks up a host in the user's netrc file. Returns pointer to username,
|         or NULL if no matching host is found.
|Arguments:
|    host is the host we're looking for.
\******************************************************************************/
Char *find_netrc(host)
Char *host;
{
	FILE *fp;
	Char *p,file[512];
	struct stat statbuf;
	
	foundpwd[0] = '\0';	/* we check for netrc password when opening connection */
#ifdef VMS
	strcpy(file,"SYS$LOGIN:.NETRC;");
#else
	strcpy(file,"$HOME/.netrc");
#endif
	envir_subs(file);
	if(!stat(file,&statbuf))
	{
		if((statbuf.st_mode & 0x1ff) == 0x180)	/* require rw------- permission */
		{
			if((fp = fopen(file,"r")))	/* open the .netrc file */
			{
				while((p = netrc_token(fp)))	/* get tokens */
				{
					if(!inscmp(p,"machine"))
					{
						if(!(p = netrc_token(fp)))	/* get machine name */
							return(NULL);
						if(!inscmp(host,p))	/* a hit on the machine name */
						{
							while((p = netrc_token(fp)))
							{
								if(!inscmp(p,"password"))
								{
									if(!(p = netrc_token(fp)))
										return(NULL);
									fclose(fp);
									strcpy(foundpwd,p);
									return(foundusr);
								}
								else if(!inscmp(p,"login"))
								{
									if(!(p = netrc_token(fp)))
										return(NULL);
									strcpy(foundusr,p);
								}
								else if(!inscmp(p,"machine"))
								{
									fclose(fp);
									return(foundusr);	/* user but no password, prompt them later */
								}
							}
							return(foundusr);	/* user but no password, prompt them later */
						}
					}
				}
			}
		}
	}
	return(NULL);
}

/******************************************************************************\
|Routine: was_binary
|Callby: load_file
|Purpose: Tells caller whether the last file loaded was loaded in BINARY mode.
|Arguments:
|    none
\******************************************************************************/
Int was_binary()
{
	if(ftpbin)
	{
		ftpbin = 0;
		return(1);
	}
	return(0);
}

/******************************************************************************\
|Routine: extract_host
|Callby: output_file read_in_diredit
|Purpose: Extracts host and user names from file spec, and pointer to path.
|Arguments:
|    i is the offset in lbuf of the path name.
|    lbuf is the complete file name.
|    host is the returned host name.
|    usr is the returned user name.
\******************************************************************************/
Char *extract_host(i,lbuf,host,usr)
Int i;
Char *lbuf,*host,*usr;
{
	Int l;
	Char *p,*q,*r;
	
	p = lbuf + i;	/* points to directory name */
	memcpy(host,lbuf + 1,(l = p - lbuf - 2));
	host[l] = '\0';
	if((q = strchr(host,'@')))
	{
		foundpwd[0] = '\0';	/* prevent old password from being used for new user/host */
		memcpy(usr,host,(l = q - host));
		usr[l] = '\0';
		for(q++,r = host;(*r++ = *q++););	/* eliminate the username from the host string */
	}
	else	/* no user specified, check .netrc */
	{
		if((q = find_netrc(host)))
			strcpy(usr,q);
		else
			strcpy(usr,"anonymous");	/* no .netrc, use "anonymous" */
	}
	return(p);
}

#ifndef NO_FTP
/******************************************************************************\
|Routine: host_filename
|Callby: output_file read_in_diredit
|Purpose: Parses filenames depending on host type.
|Arguments:
|    i is the offset in lbuf of the path name.
|    lbuf is the complete file name.
|    fname is the returned, processed file name.
\******************************************************************************/
Char *host_filename(i,lbuf,fname)
Int i;
Char *lbuf,*fname;
{
	Int ftpsystem;
	Char *p,*q,*r,buf[512];
	
	ftpsystem = ftp_system(lbuf);
	p = lbuf + i;	/* points to directory name */
	if(ftp_unixy(ftpsystem))	/* if server system is unix or dos or os/2 or wnt... */
	{
		if(!*p)
			strcpy(p,"/");	/* turn /xxx.yyy.zzz: into /xxx.yyy.zzz:/ */
		while((q = strstr(lbuf + i,"/../")))	/* handle '..' present in file name */
		{
			for(r = q - 1;r > lbuf && *r != '/' && *r != ':';r--);
			if(*r == ':')	/* they tried to back up from top level */
			{
				*++r = '/';
				*++r = '\0';
			}
			else if(r != lbuf && *r == '/')	/* eat everything from preceeding / to last . in /../ */
				for(q += 3;(*r++ = *q++););
		}
		if(p[strlen(p) - 1] != '/')	/* be sure dirname ends with '/' */
			strcat(p,"/");
		if(!strncmp(p,"./",2))
		{
			sprintf(buf,"%s/%s",ftp_cwd(),p + 2);
			sprintf(p,"%s",buf);
			while((q = strstr(lbuf,"//")))
				for(r = q + 1;(*q++ = *r++););
		}
	}
	else	/* server is VMS or VM */
	{
		if(!*p)	/* nothing present past /host: */
			strcpy(p,ftp_cwd());
		else if(!strchr(p,'['))
		{
			strcpy(buf,ftp_cwd());
			strcat(buf,p);
			strcpy(p,buf);
		}
		if(p[strlen(p) - 1] == '/')	/* strip trailing '/' from dirname */
			p[strlen(p) - 1] = '\0';
		while((q = strstr(p,"][.")))	/* collapse things like [a][.b] to [a.b] */
			while((*q = *(q + 2)))
				q++;
		if((q = strstr(p,"[-]")))	/* move up in directory hierarchy if indicated */
		{
			if(ftpsystem == 'I' || ftpsystem == 'T')	/* VM and MTS systems don't have 'up' */
				*q = '\0';
			else
			{
				while(q > p)
				{
					if(*--q == '.')
					{
						strcpy(q,"]");
						break;
					}
					if(*q == ':')	/* they tried to move up from top level */
					{
						strcpy(q,ftp_cwd());
						break;
					}
				}
				if(q == p)
					strcpy(p,ftp_cwd());
			}
		}
		else if(!strncmp(p,"[]",2))
		{
			sprintf(buf,"%s%s",ftp_cwd(),p + 2);
			sprintf(p,"%s",buf);
		}
		else if(!strncmp(p,"[.",2))
		{
			strcpy(buf,ftp_cwd());
			strcat(buf,p);
			strcpy(p,buf);
			while((q = strstr(p,"][.")))	/* collapse things like [a][.b] to [a.b] */
				while((*q = *(q + 2)))
					q++;
		}
	}
	strcpy(fname,lbuf);	/* return the actual name */
	return(p);
}

/******************************************************************************\
|Routine: ftp_date
|Callby: load_file
|Purpose: Converts a directory listing date/time entry into yyyy mm-dd hh:mm.
|Arguments:
|    month is the month, one of Jan, Feb,...
|    day is the day of the month.
|    hour is hh:mm or yyyy.
\******************************************************************************/
Char *ftp_date(month,day,hour)
Char *month,*hour;
Int day;
{
	Char *buf;
	static Char buf2[128];
	time_t l;

/* get the year */
	if(hour[2] == ':')
	{
		l = time(0);
		buf = (Char *)ctime(&l);
		memcpy(buf2,buf + 20,4);
	}
	else
		memcpy(buf2,hour,4);
	buf2[4] = ' ';
/* do the month */
	sprintf(buf2 + 5,"%02d-",((strstr(months,month) - months) / 3) + 1);
/* do the day */
	sprintf(buf2 + 8,"%02d",day);
	buf2[10] = ' ';
/* do hours, minutes */
	if(hour[2] != ':')
		strcpy(buf2 + 11,"00:00");
	else
		memcpy(buf2 + 11,hour,5);
	buf2[16] = '\0';
	return(buf2);
}
#endif

/******************************************************************************\
|Routine: proper_date
|Callby: read_in_diredit
|Purpose: Converts a statbuf mtime entry into yyyy mm-dd hh:mm.
|Arguments:
|    mtime is the statbuf mtime entry.
\******************************************************************************/
Char *proper_date(mtime)
time_t mtime;
{
	Char *buf,monbuf[4];
	static Char buf2[64];

	buf = ctime(&mtime);
	memcpy(buf2,buf + 20,4);
	buf2[4] = ' ';
	memcpy(monbuf,buf + 4,3);
	monbuf[3] = '\0';
	sprintf(buf2 + 5,"%02d-",((strstr(months,monbuf) - months) / 3) + 1);
	memcpy(buf2 + 8,buf + 8,8);
	if(buf2[8] == ' ')
		buf2[8] = '0';
	buf2[16] = '\0';
	return(buf2);
}

/******************************************************************************\
|Routine: map_file
|Callby: load_file
|Purpose: Maps a file into memory.
|Arguments:
|    file is the name of the file.
|    bytes is the returned size of the mapped area in bytes.
\******************************************************************************/
Char *map_file(file,bytes)
Char *file;
Int *bytes;
{
	struct stat statbuf;
	Int fd;
	Char *p,*errmsg,buf[512];
	mmap_ptr m;
#ifdef VMS
	Char *q;
	Int remain,nbytes;
#endif
#ifdef NO_MMAP
	Int l;
#endif
	
/* get the file's size in bytes */
	if(stat(file,&statbuf) < 0)
	{
#ifdef VMS
		return(0);	/* VMS doesn't really support errno, nor even vaxc$errno */
#else
report_error:
		if(errno == ENOENT)
			return(0);
		sprintf(buf,"Unable to open file '%s'.",file);
		slip_message(buf);
		errmsg = (Char *)sys_errlist[errno];
		slip_message(errmsg);
		wait_message();
		maperror = 1;
		return(0);
#endif
	}
		
/* open the file */
#ifdef VMS
	if((fd = open(file,O_RDONLY,0,"ctx=rec","rfm=fix","mrs=512","shr=get,put,del,mse,upi,upd")) < 0)
		return(0);
#else
	if((fd = open(file,O_RDONLY | O_BINARY,0)) < 0)
		goto report_error;
#endif
#ifdef NO_MMAP
	if(!(p = (Char *)imalloc(statbuf.st_size)))
	{
		close(fd);
		return(0);
	}
#ifdef VMS
	q = p;
	remain = statbuf.st_size;
	while(remain > 0)
	{
		if((nbytes = read(fd,q,remain)) <= 0)
			break;
		q += nbytes;
		remain -= nbytes;
	}
	statbuf.st_size -= remain;
#else
	if((l = read(fd,p,statbuf.st_size)) != statbuf.st_size)
	{
		slip_message("That file didn't read in correctly.");
		wait_message();
		statbuf.st_size = l;
	}
#endif
	close(fd);
#else
	p = (Char *)mmap(0,statbuf.st_size,PROT_READ | PROT_WRITE,MAP_PRIVATE,fd,0);
	close(fd);
	if(p == (Char *)(-1))
		return(0);
#endif
	for(m = (mmap_ptr)&base;m->next;m = m->next);
	m->next = (mmap_ptr)imalloc(sizeof(mmap_node));
	m = m->next;
	m->next = NULL;
	m->adr = p;
	*bytes = m->len = statbuf.st_size;
	return(p);
}

/******************************************************************************\
|Routine: unmap_file
|Callby: include_file main
|Purpose: Unmaps a file.
|Arguments:
|	adr is the base address of the mapped region.
\******************************************************************************/
void unmap_file(adr)
Char *adr;
{
	mmap_ptr m,p;
	
	for(p = (mmap_ptr)&base,m = base;m;p = m,m = m->next)
		if(m->adr == adr)
		{
#ifdef NO_MMAP
			ifree(adr);
#else
			munmap(adr,m->len);
#endif
			p->next = m->next;
			ifree(m);
			return;
		}
	vtend();
	printf("\nA call to unmap_file passed an unrecognized address.\n");
	cleanup(0);
	exit(0);
}

/******************************************************************************\
|Routine: checkbook
|Callby: load_file
|Purpose: Checks whether a file has a bookmark. Returns the position found in
|         the bookmark file, or 0.
|Arguments:
|    editfile is the name of the file.
|    bookmark is the returned bookmark file name, or null string.
|    byteoffset is the byte offset within the record.
\******************************************************************************/
Int checkbook(editfile,bookmark,byteoffset)
Char *editfile,*bookmark;
Int *byteoffset;
{
#ifdef GNUDOS
	bookmark[0] = '\0';
	return(0);
#else
	FILE *fp;
	Int initline;
	
	strcpy(bookmark,editfile);
	strcat(bookmark,"-EDbookmark");
	initline = 0;
	if((fp = fopen(bookmark,"r")))
	{
		if(my_fscanf(fp,"%d,%d",&initline,byteoffset) == 2)
		{
			if(initline < 1)
				initline = 1;
			fclose(fp);
		}
		else
		{
			fclose(fp);
			bookmark[0] = '\0';
		}
	}
	else
		bookmark[0] = '\0';
	return(initline);
#endif
}

/******************************************************************************\
|Routine: read_stdin
|Callby: load_file
|Purpose: Reads an ASCII file from standard input, creating records.
|Arguments:
|	fname is the buffer that receives the file name.
|	base is the base of the file's record queue.
\******************************************************************************/
Int read_stdin(fname,base)
Char *fname;
rec_ptr base;
{
	Char buffer[BUFFER_SIZE];
	register rec_ptr new;
	register Int i,nrecs;
	
	nrecs = 0;
	strcpy(fname," Standard Input ");
	new = NULL;
	while(gets(buffer))
	{
		if((i = strlen(buffer)) == BUFFER_SIZE)
			i--;
		buffer[i++] = '\n';
		nrecs++;
		new = (rec_ptr)imalloc(sizeof(rec_node));	/* get new record */
		insq(new,base->prev);
		new->data = (Char *)imalloc(i);	/* get buffer for record */
		if(--i > 0)	/* copy record data if present */
			memcpy(new->data,buffer,i);
		new->data[i] = 0;
		new->length = i;
		new->recflags = 1;
	}
	return(nrecs);
}

/******************************************************************************\
|Routine: read_stdin_binary
|Callby: load_file
|Purpose: Reads a binary file from standard input, creating records.
|Arguments:
|	fname is the buffer that receives the file name.
|	base is the base of the file's record queue.
\******************************************************************************/
Int read_stdin_binary(fname,base)
Char *fname;
rec_ptr base;
{
	Char buffer[BUFFER_SIZE];
	register Char *p,*stop;
	register rec_ptr new;
	register Int i,nrecs;

	nrecs = 0;
	strcpy(fname," Standard Input ");
	for(p = buffer,stop = buffer + BINARY_RECSIZE;(i = getchar()) != EOF;)
	{
		*p++ = i;
		if(p == stop)
		{
			nrecs++;
			new = (rec_ptr)imalloc(sizeof(rec_node));
			insq(new,base->prev);
			new->data = (Char *)imalloc(BINARY_RECSIZE);
			memcpy(new->data,(p = buffer),BINARY_RECSIZE);
			new->length = BINARY_RECSIZE;
			new->recflags = 1;
		}
	}
	if((i = p - buffer))
	{
		nrecs++;
		new = (rec_ptr)imalloc(sizeof(rec_node));
		insq(new,base->prev);
		new->data = (Char *)imalloc(i);
		memcpy(new->data,buffer,i);
		new->length = i;
		new->recflags = 1;
	}
	return(nrecs);
}

/******************************************************************************\
|Routine: read_in
|Callby: load_file
|Purpose: Reads an ASCII file, creating records.
|Arguments:
|	mapped is the mapped file pointer.
|   nbytes is the size of the mapped region.
|	base is the base of the file's record queue.
\******************************************************************************/
Int read_in(mapped,nbytes,base)
Char *mapped;
Int nbytes;
rec_ptr base;
{
	rec_ptr new;
	register Int nrecs,inprog;
	register Char *p,*q,*stop;
	
	nrecs = 0;
	new = NULL;
	inprog = 0;
	for(stop = mapped + nbytes,q = p = mapped;p != stop;p++)
		if(*p == '\n')
		{
			nrecs++;
			new = (rec_ptr)imalloc(sizeof(rec_node));	/* get new record */
			insq(new,base->prev);
			new->data = q;	/* use mapped address as record data */
			if((new->length = p - q) > 0)
				if(new->data[new->length - 1] == '\r')	/* NULL-terminate the buffer (mapped data are MAP_PRIVATE) */
					new->length--;
			new->data[new->length] = '\0';
			new->recflags = 0;	/* don't free this buffer, it's mapped file data */
			q = p + 1;
			inprog = 0;
		}
		else
			inprog = 1;
	if(inprog)	/* the last record doesn't have newline termination, handle it specially */
	{
#ifdef GNUDOS
		for(stop = q;stop != p;stop++)
			if(*stop == 26)
				break;
		if(stop != p)
		{
			*stop = '\0';
			if(!strlen(q))
				return(nrecs);
			p = stop;
		}
#endif
		nrecs++;
		new = (rec_ptr)imalloc(sizeof(rec_node));	/* get new record */
		insq(new,base->prev);
		new->length = p - q;
		new->data = (Char *)imalloc(new->length + 1);	/* use mapped address as record data */
		memcpy(new->data,q,new->length);	/* copy data to buffer */
		new->data[new->length] = 0;	/* NULL-terminate the buffer (mapped data are MAP_PRIVATE) */
		new->recflags = 1;	/* this buffer should be freed */
	}
	return(nrecs);
}

/******************************************************************************\
|Routine: read_in_binary
|Callby: load_file
|Purpose: Reads a file in binary mode, creating 32-byte records.
|Arguments:
|	mapped is the mapped file pointer.
|   nbytes is the size of the mapped region.
|	base is the base of the file's record queue.
\******************************************************************************/
Int read_in_binary(mapped,nbytes,base)
Char *mapped;
Int nbytes;
rec_ptr base;
{
	register Int i,nrecs;
	register Char *p,*stop;
	rec_ptr new;
	
	nrecs = 0;
	for(stop = mapped + nbytes - BINARY_RECSIZE,p = mapped;p <= stop;p += BINARY_RECSIZE)
	{
		nrecs++;
		new = (rec_ptr)imalloc(sizeof(rec_node));
		insq(new,base->prev);
		new->data = p;
		new->length = BINARY_RECSIZE;
		new->recflags = 0;
	}
	if((i = (mapped + nbytes) - p))
	{
		nrecs++;
		new = (rec_ptr)imalloc(sizeof(rec_node));
		insq(new,base->prev);
		new->data = p;
		new->length = i;
		new->recflags = 0;
	}
	return(nrecs);
}

/******************************************************************************\
|Routine: host_in_name
|Callby: load_file read_in_diredit
|Purpose: Returns >0 if /xxx.yyy.zzz: is present in file name. Return value is
|         the offset to the character after the colon.
|Arguments:
|	lbuf is the file name.
\******************************************************************************/
Int host_in_name(lbuf)
Char *lbuf;
{
	Char *p,c;
	
#ifdef NO_FTP
	return(0);
#else
	if(lbuf[0] == '/')
	{
		for(p = lbuf + 1;(c = *p);p++)
		{
			if(c == ':')
				break;
			if(!isalpha(c) && !isdigit(c) && c != '+' && c != '.' && c != '-' && c != '_' && c != '@')
			{
				c = '\0';
				break;
			}
		}
		if(c)	/* it's an /xxx.yyy.zzz: filename */
			return(++p - lbuf);
	}
	return(0);
#endif
}

/******************************************************************************\
|Routine: read_in_diredit
|Callby: load_file
|Purpose: Creates a diredit buffer.
|Arguments:
|	fname is the buffer that receives the title string. If a VMS system, this is
|         in the form disk:[dir.dir...]. Returns 1 on success, -2 on ftp failure.
|	buf is the directory name.
|	base is the base of the file's record queue.
\******************************************************************************/
Int read_in_diredit(fname,buf,base)
Char *fname,*buf;
rec_ptr base;
{
	Char retbuf[512],lbuf[512],cwd[512],host[512],record[512],command[512],usr[64],fbuf[512],fbuf1[512],monthyear[512],msgbuf[128];
	rec_ptr new,tmp;
	struct stat statbuf;
	Int i,l,nrecs,ihour,ftpsystem,lasttime,thistime,ticker,mustrestore;
	Long context;
	Char *p,*q,c;
/* variables used for parsing remote listings */
	Char mode[64],group[64],owner[64],month[64],hour[64],*recptr;
	Int links,size,day,pred,year,minute,imonth;
	
	strcpy(lbuf,buf);
/* handle remote host directories */
#ifndef NO_FTP
	if((i = host_in_name(lbuf)))	/* this is true if /xxx.yyy.zzz: is in the file name */
	{
		p = extract_host(i,lbuf,host,usr);
/* get the directory listing from the remote host */
		if(!ftp_open(host,usr))
			return(-2);
		ftpsystem = ftp_system(buf);
		p = host_filename(i,lbuf,fname);
		if(ftpsystem == 'I' || ftpsystem == 'T')	/* VM systems emulate VMS systems, mostly */
		{
			if(strlen(p))
			{
				p++;	/* advance past [ character */
				if((q = strchr(p,']')))	/* find ] character */
				{
					if(!*(q + 1))	/* only a directory is specified, strip [, ] from VM dirname */
						*q = '\0';
					else
						return(-1);	/* there is a file name present, fail */
				}
				sprintf(command,"CWD %s",p);
			}
			else
				command[0] = '\0';
		}
		else
		{
			if(ftpsystem == 'M' || ftpsystem == 'O' || ftpsystem == 'W')
			{
				if(strlen(p) > 1)
				{
					if(!(strlen(p) == 3 && *(p + 1) == ':' && *(p + 2) == '/'))
						if(*(q = p + strlen(p) - 1) == '/')
							*q = '\0';
				}
				strcpy(mode,"rwxrwxrwx");
			}
			sprintf(command,"CWD %s",p);
		}
		if(strlen(command))
			if(!ftp_command(command))	/* the -1 tells caller to try opening it as a file, rather than a directory */
				if(!strstr(last_message(),"ataset"))
					return(-1);
		if(!ftp_data("LIST"))
			return(-2);
		new = (rec_ptr)imalloc(sizeof(rec_node));
		insq(new,base->prev);
/* insert a record allowing user to move up */
		if(ftp_unixy(ftpsystem))
		{
			new->data = (Char *)imalloc(FILENAME_OFFSET + strlen("..") + 1);	/* get buffer for record */
			sprintf(new->data,"d        0                              ..");
		}
		else
		{
			new->data = (Char *)imalloc(FILENAME_OFFSET + strlen("[-]") + 1);	/* get buffer for record */
			sprintf(new->data,"d        0                              [-]");
		}
		new->length = strlen(new->data);
		new->recflags = 1;
		nrecs = 1;
		if(ftpsystem == 'U')
		{
			if(!ftp_record(record))	/* return 1 because ".." or "[-]" is always present */
				return(1);
			if(!strstr(record,"otal"))
				goto nototal;
		}
		lasttime = time(0);
		ticker = 0;
/* get directory listing */
		while(ftp_record(record))
		{
			if(!strlen(record))
				continue;			
/* check for report of files listed */
			ticker = (ticker + 1) & 31;
			if(!ticker)
			{
				thistime = time(0);
				if(thistime - lasttime > 2)
				{
					if(ttychk() == 3)
					{
						ftp_abort();
						break;
					}
					lasttime = thistime;
					sprintf(msgbuf,"Total files so far: %d.",nrecs);
					move(BOTROW,1);
					ers_end();
					reverse();
					putz(msgbuf);
					normal();
					putout();
					mustrestore = 1;
				}
			}
nototal:
/* unix systems */
/*
dr-xr-xr-x   1 owner    group             0 Aug 13 11:13 cs
-r-xr-xr-x   1 owner    group          1420 Aug  3  8:53 README.TXT
*/
			if(ftpsystem == 'U')
			{
				memcpy(mode,record + 1,9);	/* get mode separately, since it may glom into link count */
				mode[9] = '\0';
				p = record + 10;
				if(*p == '+')
					p++;
				if(my_sscanf(p,"%d %s %s %d %s %d %s %n",&links,group,owner,&size,month,&day,hour,&pred) != 7)
					if(my_sscanf(p,"%d %s %d %s %d %s %n",&links,owner,&size,month,&day,hour,&pred) != 6)
					{
						strcpy(mode,"?????????");	/* no permission info */
						fake_date(&size,&year,month,&day,&ihour,&minute);
						strcpy(hour,"06:35");
/* maybe it looks like this?
GPC_PLB/              =  Picture Level Benchmark files
README              685  General info about this site
README.TeX3.141    3237  
TeX3.141.tar.Z  9664553  
a.e.d/                -  Archive of alt.education.disabled postings
anyone/               =  World writable directory for new software
bin/                  -  
describe-1.8.sgi.tar.Z
                 124985  Tools to provide descriptive ls, what you are seeing
 */
						if(strlen(record) >= 23 && (record[22] == '-' || record[22] == '='))
						{
							size = 0;
							for(p = record + 21;*p == ' ';p--);
							if(*p == '/')
							{
								*p = '\0';
								strcpy(fbuf,record);
								record[0] = 'd';	/* record[0] is reported to the first column of the diredit record */
							}
							else
							{
								*++p = '\0';
								strcpy(fbuf,record);
								record[0] = '-';
							}
						}
						else if((p = strchr(record,' ')))
						{
							if(my_sscanf(p,"%d",&size) != 1)
								size = 0;
							*p = '\0';
							strcpy(fbuf,record);
							record[0] = '-';
						}
						else
						{
							strcpy(fbuf,record);
							if(!ftp_record(record))
								break;
							if(my_sscanf(record,"%d",&size) != 1)
								size = 0;
							if(fbuf[strlen(fbuf) - 1] == '/')
								record[0] = 'd';
							else
								record[0] = '-';
						}
						recptr = fbuf;
					}
				if(mode[0] != '?')	/* note that this will not happen if the weird format above obtains */
					for(recptr = p + pred;*recptr == ' ';recptr++);	/* skip spaces after the hour, leaving recptr aimed at file name  */
			}
/* MSDOS system
 124436480           CDISK.TAR   Thu Jul 22 16:53:32 1993
<dir>                      SAV   Thu Jul 22 16:53:46 1993
 */
			else if(ftpsystem == 'M')
			{
				if(!strncmp(record,"<dir>",(pred = strlen("<dir>"))))
					size = 0;
				else
					if(my_sscanf(record,"%d%n",&size,&pred) != 1)
						continue;
				for(p = record + pred;isspace(*p);p++);
				for(q = p + 1;!isspace(*q);q++);
				*q = '\0';
				strcpy(fbuf,p);
				q += 7;	/* skip foward past day-of-week */
				if(my_sscanf(q,"%s%d%d:%d:%d%d",month,&day,&ihour,&minute,&i,&year) != 6)
					continue;
				sprintf(hour,"%02d:%02d",ihour,minute);
				recptr = fbuf;
				record[0] = (record[0] == '<')? 'd' : '-';
			}
/* OS/2 system
                 0           DIR   07-24-93   13:55  DOS
              8440      A          09-08-92   20:35  OS2LDR.MSG
*/
			else if(ftpsystem == 'O')
			{
				if(my_sscanf(record,"%d",&size) != 1)
					continue;
				record[0] = (record[29] == 'D')? 'd' : '-';
				if(my_sscanf(record + 35,"%d-%d-%d%s",&i,&day,&year,hour) != 4)
					continue;
				memcpy(month,months + (i - 1) * 3,3);
				month[3] = '\0';
				year += (year < 70)? 2000 : 1900;
				strcpy(fbuf,record + 53);
				recptr = fbuf;
			}
/* MTS system
r                 Jul 21  1991 ADDHELP.ZIP
*/
			else if(ftpsystem == 'T')
			{
				size = 0;
				if(my_sscanf(record,"%s%s%d%s%s",fbuf,month,&day,hour,fbuf1) != 5)
					if(my_sscanf(record,"%s%d%s%d%s%s",fbuf,&size,month,&day,hour,fbuf1) != 6)
						continue;
				strcpy(mode,"---------");	/* MTS reports as rw or whatever */
				if(strchr(fbuf,'r'))
					mode[0] = mode[3] = mode[6] = 'r';
				if(strchr(fbuf,'w'))
					mode[1] = mode[4] = mode[7] = 'w';
				if(strchr(fbuf,'e') || strchr(fbuf,'x'))
					mode[2] = mode[5] = mode[8] = 'x';
				recptr = fbuf1;
			}
/* VM system */
			else if(ftpsystem == 'I')
			{
/*$READ-ME FIRST    V         71         27          1  4/28/93  3:22:47 PUBLIC*/
				if(my_sscanf(record,"%s%s%s%d%d%d%d/%d/%d%d:%d",fbuf,fbuf1,monthyear,&i,&i,&size,&imonth,&day,&year,&ihour,&minute) == 11)
				{
					size *= 8;	/* it's going to be multiplied by 512 below. assume 4096-byte blocks */
					strcpy(mode,"?????????");	/* VM doesn't report permission info */
					memcpy(month,months + 3 * (imonth - 1),3);
					month[3] = '\0';
					year += (year < 70)? 2000 : 1900;
					strcat(fbuf,".");
					strcat(fbuf,fbuf1);
					recptr = fbuf;
				}
/*APR87                       FILE41 PS FB    133 11440*/
				else if(my_sscanf(record,"%s%s%s%s%d%d",fbuf,fbuf1,fbuf1,fbuf1,&i,&i) == 6)
				{
					strcpy(mode,"?????????");	/* VM doesn't report permission info */
					fake_date(&size,&year,month,&day,&ihour,&minute);
					recptr = fbuf;
				}
				else
				{
					if((p = strchr(record,' ')))
					{
						*p = '\0';
						sprintf(fbuf,"[%s]",record);
						fake_date(&size,&year,month,&day,&ihour,&minute);
						strcpy(mode,"?????????");
						recptr = fbuf;
					}
					else
						continue;
				}
			}
/* WNT system */
			else if(ftpsystem == 'W')
			{
/*
08-10-93  09:50AM                   96 GO.BAT
05-06-93  10:53AM       <DIR>          OADDOS
*/
				if(my_sscanf(record,"%d-%d-%d%d:%s%s%s",&day,&imonth,&year,&ihour,fbuf,fbuf1,monthyear) == 7)
				{
					if(fbuf[2] == 'P')
						ihour += 12;
					fbuf[2] = '\0';
					my_sscanf(fbuf,"%d",&minute);
					sprintf(hour,"%02d:%02d",ihour,minute);
					memcpy(month,months + (imonth - 1) * 3,3);
					month[3] = '\0';
					year += (year < 70)? 2000 : 1900;
					if(!strcmp(fbuf1,"<DIR>"))
					{
						record[0] = 'd';
						size = 0;
					}
					else
					{
						record[0] = '-';
						my_sscanf(fbuf1,"%d",&size);
					}
					recptr = monthyear;
				}
/*
dr-xr-xr-x   1 owner    group             0 Aug 13 11:13 cs
-r-xr-xr-x   1 owner    group          1420 Aug  3  8:53 README.TXT
*/
				else
				{
					memcpy(mode,record + 1,9);	/* get mode separately, since it may glom into link count */
					mode[9] = '\0';
					p = record + 10;
					if(*p == '+')
						p++;
					if(my_sscanf(p,"%d %s %s %d %s %d %s %n",&links,group,owner,&size,month,&day,hour,&pred) != 7)
						if(my_sscanf(p,"%d %s %d %s %d %s %n",&links,owner,&size,month,&day,hour,&pred) != 6)
							continue;
					for(recptr = p + pred;*recptr == ' ';recptr++);	/* skip spaces after the hour, leaving recptr aimed at file name  */
				}
			}
			else if((p = strchr(record,';')))	/* VMS has ; only where filename appears */
			{
/*
22AVG.FIL;1                     File ID:  (69,1,0)
Size:           2/3             Owner: [RHR]
Created:   27-DEC-1987 17:31    Revised:  24-MAY-1989 11:00
Expires:   <None specified>     Backup:   <None specified>
File organization:  Sequential
File attributes:    Allocation: 3, Extend: 0, Global buffer count: 0
                    No version limit
Record format:      Fixed length, maximum 512 bytes
Journaling enabled: None
File protection:    System:RWED,Owner:RWED,Group:RE,World:
Access Cntrl List:  None
 */
				if(strstr(record,"File ID:"))	/* this server uses DIR/FULL listing, yawn */
				{
					while(!isspace(*++p));	/* trim blanks after file name */
					*p = '\0';
					fix_vms_dir(record,fbuf);	/* fix up .DIR;1 files */
					if(!ftp_record(record))
						break;
					for(p = record;!isspace(*p);p++);	/* retrieve file size */
					my_sscanf(p,"%d",&size);
					if(!ftp_record(record))
						break;
					if(!(p = strstr(record,"Revised:")))	/* retrieve revision date */
					{
						fake_date(&size,&year,month,&day,&ihour,&minute);
bad_mode:
						strcpy(mode,"?????????");
					}
					else
					{
						p += 8;
						while(isspace(*p))
							p++;
						for(q = p;(c = *q);q++)
							if(c == '-' || c == ':')
								*q = ' ';
						my_sscanf(p,"%d %s %d %d %d",&day,month,&year,&ihour,&minute);
						month[1] = tolower(month[1]);
						month[2] = tolower(month[2]);
						while(!strstr(record,"File protection:"))	/* read forward until protection info appears */
							if(!(i = ftp_record(record)))
								break;
						if(!i)	/* premature termination of listing */
							break;
						if(!(p = strstr(record,"Owner:")))	/* parse out the protection info */
							goto bad_mode;
						parse_vms_perms(p += strlen("Owner:"),mode);
						if(!(p = strstr(record,"Group:")))
							goto bad_mode;
						parse_vms_perms(p += strlen("Group:"),mode + 3);
						if(!(p = strstr(record,"World:")))
							goto bad_mode;
						parse_vms_perms(p += strlen("World:"),mode + 6);
						mode[9] = '\0';
					}
				}
/* parse other VMS listing formats */
				else
				{
					while(*p && !isspace(*p))	/* find end of record or whitespace */
						p++;
					if(!*p)	/* only the filename appears, it's either a two-liner, or a horrible system */
					{
						strcpy(fbuf,record);
						if(!ftp_record(record))
							break;
						if(!isspace(record[0]))	/* it's one of those horrible systems that don't tell you anything about files */
						{
							fake_date(&size,&year,month,&day,&ihour,&minute);	/* use fake date info */
							strcpy(mode,"?????????");	/* use confused permission info */
							fix_vms_dir(fbuf,fbuf);	/* handle .DIR;1 files */
							new = (rec_ptr)imalloc(sizeof(rec_node));	/* load the previously-read file name */
							insq(new,base->prev);
							new->data = (Char *)imalloc((l = strlen(fbuf)) + 1 + FILENAME_OFFSET);	/* get buffer for record */
							sprintf(new->data,"%c %s %s  %4d %02d-%02d %02d:%02d  %s",((fbuf[0] == '[')? 'd' : ' '),size_string(size),mode,year,i,day,ihour,minute,fbuf);
							new->data[l + FILENAME_OFFSET] = 0;
							new->length = l + FILENAME_OFFSET;
							new->recflags = 1;
							nrecs++;
							while(strchr(record,';'))	/* get the rest of the file names */
							{
/* check for report of files listed */
								ticker = (ticker + 1) & 31;
								if(!ticker)
								{
									thistime = time(0);
									if(thistime - lasttime > 2)
									{
										if(ttychk() == 3)
										{
											ftp_abort();
											break;
										}
										lasttime = thistime;
										sprintf(msgbuf,"Total files so far: %d.",nrecs);
										move(BOTROW,1);
										ers_end();
										reverse();
										putz(msgbuf);
										normal();
										putout();
										mustrestore = 1;
									}
								}
								fix_vms_dir(record,record);
								new = (rec_ptr)imalloc(sizeof(rec_node));	/* get new record */
								insq(new,base->prev);
								new->data = (Char *)imalloc((l = strlen(record)) + 1 + FILENAME_OFFSET);	/* get buffer for record */
								sprintf(new->data,"%c %s %s  %4d %02d-%02d %02d:%02d  %s",((record[0] == '[')? 'd' : ' '),size_string(size),mode,year,i,day,ihour,minute,record);
								new->data[l + FILENAME_OFFSET] = 0;
								new->length = l + FILENAME_OFFSET;
								new->recflags = 1;
								nrecs++;
								if(!ftp_record(record))
									break;
							}
							if(mustrestore && NWINDOWS)
								paint(BOTROW,BOTROW,FIRSTCOL);
							return(nrecs);
						}
						p = record;
					}
					else	/* it was a two-liner */
					{
						*p++ = '\0';
						strcpy(fbuf,record);
					}
					fix_vms_dir(fbuf,fbuf);
/* at this point, we have the filename or [.dirname] in fbuf, and p points at whitespace hopefully preceeding one of the following:

DIGLIB.DIR;1        %RMS-E-PRV, insufficient privilege or file protection violation
LOGIN.COM;2          no privilege for attempted operation
DINK.BCK;1                441   1-FEB-1993 10:02 [ANONYMOUS] (RWED,RWED,RE,RE)
VMS-DECWINDOWS.DIR;1
                            1   1-SEP-1990 17:07 [ASD,BRAND] (RWE,RWE,RE,RE)
USER$DISK:[ANONYMOUS]ETHER.TXT;2                  12/12         4-AUG-1988 11:40
00-INDEX.ALL;4        5-NOV-1992 14:43:39    18092/36     (RWED,RWED,R,R)
00._THIS_SITE_NO_LONGER_EXISTS;1
                     30-JUN-1992 09:04:23        0/0      (RWED,RWED,,)
CAESAR.DIR;1              1  13-JUN-1992 13:18:42.55  (RWED,RWED,RE,RE)
archie.rfa;2                    MAR 22 16:04 1993    24064 (,RWE,RE,RE)
VISIT$DISK:[ANONYMOUS]DDN.TAR;1
FTPSTUFF.DIR;1  1/3     29-APR-1991    [INTERNET,ANONYMOUS]   (RWE,RWE,RE,RE)

*/
/* check for privilege problems */
					if(strchr(p,'%') || strstr(p,"no privilege"))
					{
						fake_date(&size,&year,month,&day,&ihour,&minute);
						strcpy(mode,"?????????");
					}
/* try the known forms one at a time */
					else
					{
						strcpy(mode,"?????????");	/* default is meaningless permission info */
/*DINK.BCK;1                441   1-FEB-1993 10:02 [ANONYMOUS] (RWED,RWED,RE,RE)*/
						if(my_sscanf(p,"%d%d-%s%d:%d",&size,&day,monthyear,&ihour,&minute) == 5)
							parse_monthyear(monthyear,&size,&year,month,&day,&ihour,&minute,mode,p);
/*USER$DISK:[ANONYMOUS]ETHER.TXT;2                  12/12         4-AUG-1988 11:40*/
						else if(my_sscanf(p,"%d/%d%d-%s%d:%d",&size,&i,&day,monthyear,&ihour,&minute) == 6)
							parse_monthyear(monthyear,&size,&year,month,&day,&ihour,&minute,mode,p);
/*00-INDEX.ALL;4        5-NOV-1992 14:43:39    18092/36     (RWED,RWED,R,R)*/
						else if(my_sscanf(p,"%d-%s%d:%d:%d%d/%d",&day,monthyear,&ihour,&minute,&i,&i,&size) == 7)
							parse_monthyear(monthyear,&size,&year,month,&day,&ihour,&minute,mode,p);
/*CAESAR.DIR;1              1  13-JUN-1992 13:18:42.55  (RWED,RWED,RE,RE)*/
						else if(my_sscanf(p,"%d%d-%s%d:%d",&size,&day,monthyear,&ihour,&minute) == 5)
							parse_monthyear(monthyear,&size,&year,month,&day,&ihour,&minute,mode,p);
/*FTPSTUFF.DIR;1  1/3     29-APR-1991    [INTERNET,ANONYMOUS]   (RWE,RWE,RE,RE)*/
						else if(my_sscanf(p,"%d/%d%d-%s",&size,&i,&day,monthyear) == 4)
						{
							ihour = minute = 0;
							parse_monthyear(monthyear,&size,&year,month,&day,&ihour,&minute,mode,p);
						}
/*archie.rfa;2                    MAR 22 16:04 1993    24064 (,RWE,RE,RE)*/
						else if(my_sscanf(p,"%s%d%d:%d%d%d",month,&day,&ihour,&minute,&year,&size) != 6)
							fake_date(&size,&year,month,&day,&ihour,&minute);
					}
					month[1] = tolower(month[1]);
					month[2] = tolower(month[2]);
				}
				if((p = strchr(fbuf,']')) && !strstr(fbuf,"[."))
					recptr = ++p;
				else
					recptr = fbuf;
			}
			else	/* ignore extraneous (non-filename) records */
				continue;
/* add to the buffer */
			new = (rec_ptr)imalloc(sizeof(rec_node));	/* get new record */
			insq(new,base->prev);
			new->data = (Char *)imalloc(FILENAME_OFFSET + (l = strlen(recptr)) + 1);	/* get buffer for record */
			if(ftp_unixy(ftpsystem))
				sprintf(new->data,"%c %s %s  %s  %s",(record[0] == '-')? ' ' : record[0],size_string(size),mode,ftp_date(month,day,hour),recptr);
			else if(ftpsystem == 'T')
			{
				i = ((strstr(months,month) - months) / 3) + 1;
				sprintf(new->data,"  %s %s  %s  %s",size_string(size),mode,ftp_date(month,day,hour),recptr);
			}
			else
			{
				i = ((strstr(months,month) - months) / 3) + 1;
				size <<= 9;	/* VMS reports number of 512-byte blocks */
				sprintf(new->data,"%c %s %s  %4d %02d-%02d %02d:%02d  %s",((fbuf[0] == '[')? 'd' : ' '),size_string(size),mode,year,i,day,ihour,minute,recptr);
			}
			new->data[FILENAME_OFFSET + l] = 0;
			new->length = FILENAME_OFFSET + l;
			new->recflags = 1;
			nrecs++;
		}	/* end of while(ftp_record(record)) */
/* remove any . or .. entries that are not the first record */
		for(new = base->next->next;new != base;new = new->next)
			if(!strcmp(new->data + FILENAME_OFFSET,".") || !strcmp(new->data + FILENAME_OFFSET,".."))
			{
				tmp = new->prev;
				remq(new);
				ifree(new->data);
				ifree(new);
				nrecs--;
				new = tmp;
			}
		if(mustrestore && NWINDOWS)
			paint(BOTROW,BOTROW,FIRSTCOL);
		return(nrecs);
	}
#endif	/* not NO_FTP */
#ifdef GNUDOS
	while((p = strchr(lbuf,'\\')))
		*p = '/';
#endif
/* convert the name to absolute pathname */
#ifndef VMS
	if(lbuf[0] != '/')
	{
#ifdef GNUDOS
		if(lbuf[1] != ':')
		{
#endif
#ifdef NeXT
		getwd(cwd);
#else
		getcwd(cwd,sizeof(cwd));
#endif
#ifdef GNUDOS
		while((p = strchr(cwd,'\\')))
			*p = '/';
#endif
		strcat(cwd,"/");
		strcat(cwd,lbuf);
		strcpy(lbuf,cwd);
#ifdef GNUDOS
		}
#endif
	}
	if(lbuf[strlen(lbuf) - 1] != '/')
		strcat(lbuf,"/");
	while((p = strstr(lbuf,"//")))
		for(q = p + 1;(*p++ = *q++););
	if(!strcmp(lbuf,"/../"))
	{
		strcpy(lbuf,"/");
		while((p = strstr(lbuf,"/./")))
			for(q = p + 2;(*p++ = *q++););
	}
	else
	{
		while((p = strstr(lbuf,"/./")))	/* eliminate all /./ */
			for(q = p + 2;(*p++ = *q++););
		while((p = strstr(lbuf,"/../")))	/* convert /../ to abs pathname */
		{
			for(q = p + 3;*--p != '/';);
			while((*p++ = *q++));
		}
	}
	if(!strncmp(lbuf,"./",2))	/* remove ./ at beginning of name */
		for(q = lbuf + 2,p = lbuf;(*p++ = *q++););
#endif	/* not VMS */
/* set the title */
	strcpy(fname,lbuf);
	nrecs = 0;
/* load the files */
#ifdef VMS
	strcat(lbuf,"*.*;*");
#else
	strcat(lbuf,"/*");
#endif
	if((context = find_files(lbuf)))
	{
#ifdef VMS
		new = (rec_ptr)imalloc(sizeof(rec_node));
		insq(new,base->prev);
		new->data = (Char *)imalloc(FILENAME_OFFSET + 3 + 1);	/* get buffer for record */
		sprintf(new->data,"d        0                              [-]");
		new->length = strlen(new->data);
		new->recflags = 1;
#endif
		while(next_file(context,retbuf))
		{
#ifndef VMS
			if(!strcmp(filename(retbuf),"."))	/* leave off the . directory (unix only) */
				continue;
#endif
			if(!nrecs++)
			{
				p = retbuf;
				l = filename(p) - p;
			}
			if(stat(retbuf,&statbuf))
				statbuf.st_size = 0;
			new = (rec_ptr)imalloc(sizeof(rec_node));	/* get new record */
			insq(new,base->prev);
			new->data = (Char *)imalloc((i = strlen(retbuf) - l) + FILENAME_OFFSET + 1);	/* get buffer for record */
#ifdef VMS
			if(S_ISDIR(statbuf.st_mode))
			{
				strcpy(cwd,filename(retbuf));
				*(strstr(cwd,".DIR")) = '\0';
				sprintf(new->data,"d %s %s  %s  [.%s]",size_string(statbuf.st_size),mode_string(statbuf.st_mode),proper_date(statbuf.st_mtime),cwd);
				i = strlen(cwd) + 3;
			}
			else
#endif
			sprintf(new->data,"%c %s %s  %s  %s",S_ISDIR(statbuf.st_mode)? 'd' : ' ',
				size_string(statbuf.st_size),mode_string(statbuf.st_mode),proper_date(statbuf.st_mtime),retbuf + l);
			new->data[FILENAME_OFFSET + i] = 0;
			new->length = FILENAME_OFFSET + i;
			new->recflags = 1;
		}
		find_files_end(context);
	}
	return(nrecs);
}

#ifndef NO_FTP
/******************************************************************************\
|Routine: read_in_ftp
|Callby: load_file
|Purpose: Downloads a file in FTP mode.
|Arguments:
|	fname is the buffer that receives the title string. If a VMS system, this is
|         in the form disk:[dir.dir...]. Returns total records, or -2 if ftp
|         fails, or -1 if CWD fails.
|	buf is the file name.
|	base is the base of the file's record queue.
|   binary is a flag that the file is to be read in binary mode.
\******************************************************************************/
Int read_in_ftp(fname,buf,base,binary)
Char *fname,*buf;
rec_ptr base;
Char binary;
{
	rec_ptr new;
	Int i,l,m,nrecs,ftpsystem,mustrestore;
	Int lasttime,thistime,ticker,totalread,lasttotal,throughput,filesize,percent;
	Char *p,*q,host[512],dir[512],fil[512],lbuf[512],*record,msgbuf[128];
	Char command[512],usr[64];
	
	ftpsystem = ftp_system(buf);	/* this works because we tried read_in_diredit first */
	strcpy(lbuf,buf);
	i = host_in_name(lbuf);
	p = extract_host(i,lbuf,host,usr);
	strcpy(dir,p);
	if(ftp_unixy(ftpsystem))
	{
		if(dir[(l = strlen(dir) - 1)] == '/')
			dir[l] = '\0';
		if(!(q = strrchr(dir,'/')))
		{
			strcpy(fil,dir);
			strcpy(dir,"/");
		}
		else
		{
			*q++ = '\0';
			strcpy(fil,q);
			if(!strlen(dir))
				strcpy(dir,"/");
		}
	}
	else
	{
		if((q = strchr(dir,']')))
		{
			strcpy(fil,++q);
			*q = '\0';
		}
		else if((q = strchr(dir,':')))
		{
			strcpy(fil,++q);
			*q = '\0';
		}
		else
		{
			strcpy(fil,dir);
			dir[0] = '\0';
		}
	}
/* open the connection to the remote host */
	if(!ftp_open(host,usr))
		return(-2);
	p = host_filename(i,lbuf,fname);
	if(*(q = fname + strlen(fname) - 1) == '/')	/* trim trailing / */
		*q = '\0';
	if(ftpsystem == 'I')
	{
		if(strlen(++p))
		{
			if((q = strchr(p,']')))
				*q = '\0';
			sprintf(command,"CWD %s",p);
			if(!ftp_command(command))
			{
				sprintf(command,"Unable to move to the directory '%s'.",p);
				ftp_error(command);
				return(-2);
			}
		}
	}
	else
	{
		if(ftpsystem == 'T')
		{
			if(dir[0] == '[')
			{
				strcpy(buf,dir + 1);
				if((q = strchr(buf,']')))
					*q = '\0';
				strcpy(dir,buf);
			}
		}
		sprintf(command,"CWD %s",dir);
		ftp_command(command);	/* we ignore errors here because some servers don't let you even CWD to where you are */
	}
	mustrestore = 0;	/* flag that we have issued progress messages and must set them right */
	totalread = 0;	/* count of bytes read from remote host */
	lasttotal = 0;	/* count at last report (for calculating throughput) */
	if(binary)	/* a binary-mode get */
	{
		if(!ftp_command("TYPE i"))
			return(-2);
		sprintf(command,"RETR %s",fil);
		if(!ftp_data(command))
		{
			if(!ftp_command("TYPE a"))
				return(-2);
			return(-2);
		}
		nrecs = 0;
		ftpbin = 1;
		lasttime = time(0);
		ticker = 0;
		filesize = 0;
		if(CURWINDOW >= 0)
			if(CURREC && CURREC != WINDOW[CURWINDOW].base)
				if(my_sscanf(CURREC->data + 1,"%d",&filesize) != 1)
					filesize = 0;
		record = (Char *)imalloc(512);	/* the 512 here must match what ftp_binary expects */
		while((l = ftp_binary(record)))
		{
			p = record;
			while(l > 0)
			{
				m = (l > 32)? 32 : l;	/* limit records to 32 bytes */
				new = (rec_ptr)imalloc(sizeof(rec_node));	/* get new record */
				insq(new,base->prev);
				new->data = (Char *)imalloc(m + 1);	/* get buffer for record */
				memcpy(new->data,p,m);
				new->data[m] = '\0';
				new->length = m;
				new->recflags = 1;
				nrecs++;
				totalread += m;
				if(filesize)	/* report progress */
				{
					ticker = (ticker + 1) & 31;
					if(!ticker)
					{
						thistime = time(0);
						if(thistime - lasttime > 2)
						{
							if(ttychk() == 3)
							{
								ftp_abort();
								goto abort_binary;
							}
							throughput = (totalread - lasttotal) / 2;
							lasttotal = totalread;
							lasttime = thistime;
							percent = (int)((100.0 * (float)totalread) / ((float)filesize));
							sprintf(msgbuf,"Retrieving (binary mode) %s - %d%% (throughput = %d)",fil,percent,throughput);
							move(BOTROW,1);
							ers_end();
							reverse();
							putz(msgbuf);
							normal();
							putout();
							mustrestore = 1;
						}
					}
				}
				l -= 32;
				p += 32;
			}
abort_binary:
			;
		}
		ifree(record);
		if(!ftp_command("TYPE a"))
			return(-2);
	}
	else	/* an ascii mode get */
	{
		lasttime = time(0);
		ticker = 0;
		filesize = 0;
		if(CURWINDOW >= 0)
			if(CURREC && CURREC != WINDOW[CURWINDOW].base)
				if(my_sscanf(CURREC->data + 1,"%d",&filesize) != 1)
					filesize = 0;
		sprintf(command,"RETR %s",fil);
		if(!ftp_data(command))
			return(-2);
		nrecs = 0;
		record = (Char *)imalloc(32768);	/* this limits the size of FTP-acquired ascii records to 32768 bytes */
		while(ftp_ascii(record,32768))
		{
			new = (rec_ptr)imalloc(sizeof(rec_node));	/* get new record */
			insq(new,base->prev);
			if((l = strlen(record)))
				if(record[l - 1] == '\r')
					l--;
			new->data = (Char *)imalloc(l + 1);	/* get buffer for record */
			strcpy(new->data,record);
			new->data[l] = '\0';
			new->length = l;
			new->recflags = 1;
			nrecs++;
			totalread += l;
			if(filesize)
			{
				ticker = (ticker + 1) & 31;
				if(!ticker)
				{
					thistime = time(0);
					if(thistime - lasttime > 2)
					{
						if(ttychk() == 3)
						{
							ftp_abort();
							break;
						}
						throughput = (totalread - lasttotal) / 2;
						lasttotal = totalread;
						lasttime = thistime;
						percent = (100 * totalread) / filesize;
						sprintf(msgbuf,"Retrieving (ascii mode) %s - %d%% (throughput = %d)",fil,percent,throughput);
						move(BOTROW,1);
						ers_end();
						reverse();
						putz(msgbuf);
						normal();
						putout();
						mustrestore = 1;
					}
				}
			}
		}
		ifree(record);
	}
	if(mustrestore)
		paint(BOTROW,BOTROW,FIRSTCOL);
	return(nrecs);
}
#endif

/******************************************************************************\
|Routine: load_file
|Callby: edit include_file main wincom
|Purpose: Reads an entire file into a record queue. Returns the number of records,
|         or -1 if the file doesn't exist, or -2 if it was an ftp file and an
|         error occurred (and has already been reported).
|Arguments:
|    fname is the name of the file to read in.
|    base is the record queue into which the file goes.
|    bookmark is the name of the bookmark file, if one exists.
|    bookpos is the number of lines to move down in the buffer.
|    bookbyt is the byte offset within the marked record.
|    diredit is a returned flag that this is a directory file.
|    binary is a flag that the file should be loaded in binary mode.
|    filebuf is a returned pointer to the mapped file, or NULL.
\******************************************************************************/
Int load_file(fname,base,bookmark,bookpos,bookbyt,diredit,binary,filebuf)
Char *fname;
rec_ptr base;
Char *bookmark;
Int *bookpos,*bookbyt;
Char *diredit;
Char binary;
Char **filebuf;
{
	register Char *p,*q;
	Int i,l,nbytes,bfound;
	Long context;
	Char ext[512];
	Char buffer[512];
	Char buf[512],retbuf[512],lbuf[512];
	Char *mapped;
	struct stat statbuf;
#ifdef VMS
	Int m;
	struct desc_str
	{
		unsigned short length,class;
		Char *string;
	} desc;
#endif

	if(forcebinary)
	{
		forcebinary = 0;
		binary = 1;
	}
	bookmark[0] = '\0';
	maperror = 0;	/* no map_file error has occurred */
	*filebuf = NULL;	/* default is no file buffer */
	strcpy(buf,fname);
	strip_quotes(buf);
	envir_subs(buf);
/* check for -b or -h in file name */
	if(!binary)	/* if they specified binary, we don't need or want to check. if they didn't, and -b or -h is present, it overrides. */
		if((bfound = parse_option(buf,'b')) || parse_option(buf,'h'))
		{
			binary = 1;
			strcpy(fname,buf);	/* parse_option has remove the -b */
		}
	if(!strlen(fname))	/* this happens if they just say -b or -h without a file */
		if(bfound)
			strcpy(fname,"-b");	/* this lets them actually edit a file called -b */
		else
			strcpy(fname,"-h");
	if(strchr(buf,'*') || strchr(buf,'?') || strstr(buf,"..."))	/* find first match if wildcards are present */
		if((context = find_files(buf)))
		{
			if(next_file(context,retbuf))
				strcpy(buf,retbuf);
			find_files_end(context);
		}
	base->next = base->prev = base;
	strcpy(lbuf,fname);
#ifdef VMS
	m = strlen(lbuf) - 1;
	for(i = 0;i <= m;i++)	/* convert <> to [] */
		if(lbuf[i] == '<')
			lbuf[i] = '[';
		else if(lbuf[i] == '>')
			lbuf[i] = ']';
	if(lbuf[m] == ']')	/* is a directory file */
	{
		*diredit = 1;
		if((p = strstr(buf,"[]")))
		{
			desc.class = 0x010e;
			desc.length = sizeof(retbuf);
			desc.string = retbuf;
			SYS$SETDDIR(NULL,&i,&desc);
			retbuf[i] = '\0';
			strcpy(p,retbuf);
		}
		return(read_in_diredit(fname,buf,base));
	}
#endif
#ifndef NO_FTP
	if((i = host_in_name(fname)))
	{
		if(!fileonly)
		{
			if((i = read_in_diredit(fname,buf,base)) > 0)
			{
				*diredit = 1;
				return(i);
			}
			else if(i == -2)	/* we fall through here if it returns -1, which means CWD failed */
				return(-2);
		}
		fileonly = 0;	/* ftp_fileonly is a one-shot deal */
/* open as directory failed, try opening as file */
		*diredit = 0;
		if(buf[(l = strlen(buf) - 1)] == '/')
			buf[l] = '\0';
		return(read_in_ftp(fname,buf,base,binary));
	}
#endif
	fileonly = 0;	/* just to be sure */
	if(!stat(fname,&statbuf))
		if(S_ISDIR(statbuf.st_mode))
		{
			*diredit = 1;
			return(read_in_diredit(fname,buf,base));
		}
	*diredit = 0;
	if(!strncmp(buf,"./",2))
		for(p = buf,q = buf + 2;(*p++ = *q++););
	if(binary)
	{
#ifndef VMS
		if(!strcmp(buf,"-"))
			return(read_stdin_binary(fname,base));
#endif
		if((*filebuf = mapped = map_file(buf,&nbytes)))
		{
			if((i = checkbook(buf,bookmark,bookbyt)))
				*bookpos = i;
			strcpy(fname,buf);
			return(read_in_binary(mapped,nbytes,base));
		}
		else if(maperror)
			return(-2);
	}
	else
	{
#ifndef VMS
		if(!strcmp(buf,"-"))
			return(read_stdin(fname,base));
#endif
		if((*filebuf = mapped = map_file(buf,&nbytes)))
		{
			if((i = checkbook(buf,bookmark,bookbyt)))
				*bookpos = i;
			strcpy(fname,buf);
			return(read_in(mapped,nbytes,base));
		}
		else if(maperror)
			return(-2);
	}
/* file as given not found, try extensions if none present */
	if(!(p = (Char *)extension(buf)))
	{
		p = buf + strlen(buf);	/* skip ahead in the file name, preparing to append extensions */
		*p++ = '.';
		strcpy(buffer,DEFAULT_EXT);	/* prepare to parse extensions from string */
		while(get_token(buffer,ext))
		{
			strcpy(p,ext);
			if((*filebuf = mapped = map_file(buf,&nbytes)))
			{
				if((i = checkbook(buf,bookmark,bookbyt)))
					*bookpos = i;
				strcpy(fname,buf);
				if(binary)
					return(read_in_binary(mapped,nbytes,base));
				else
					return(read_in(mapped,nbytes,base));
			}
			else if(maperror)
				return(-2);
		}
		*--p = '\0';	/* remove the extension before returning to caller */
	}
	return(-1);
}

