/*
 * $Copyright:	$
 * Copyright (c) 1984, 1985, 1986, 1987, 1988, 1989, 1990 
 * Sequent Computer Systems, Inc.   All rights reserved.
 *  
 * This software is furnished under a license and may be used
 * only in accordance with the terms of that license and with the
 * inclusion of the above copyright notice.   This software may not
 * be provided or otherwise made available to, or used by, any
 * other person.  No title to or ownership of the software is
 * hereby transferred.
 *
 */

/*	Copyright (c) 1984, 1986, 1987, 1988 AT&T	*/
/*	  All Rights Reserved  	*/

/*	THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF AT&T	*/
/*	The copyright notice above does not evidence any   	*/
/*	actual or intended publication of such source code.	*/

#ident	"$Header: sort.c 1.12 90/06/26 $@(#)sort:sort.c	1.18"

#include <stdio.h>
#include <ctype.h>
#include <signal.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <values.h>
#include <euc.h>
#include <getwidth.h>
#include <locale.h>
#include <fcntl.h>
#define SIZE	256		/* size of character conversion table */
#define	N	16
#define	C	20
#define NF	10
#define MTHRESH	 8 /* threshhold for doing median of 3 qksort selection */
#define TREEZ	32 /* no less than N and best if power of 2 */

/*
 * Memory administration
 *
 * Using a lot of memory is great when sorting a lot of data.
 * Using a megabyte to sort the output of `who' loses big.
 * MAXMEM, MINMEM and DEFMEM define the absolute maximum,
 * minimum and default memory requirements.  Administrators
 * can override any or all of these via defines at compile time.
 * Users can override the amount allocated (within the limits
 * of MAXMEM and MINMEM) on the command line.
 *
 * For PDP-11s, memory is limited by the maximum unsigned number, 2^16-1.
 * Administrators can override this too.
 * Arguments to core getting routines must be unsigned.
 * Unsigned long not supported on 11s.
 */

#ifndef	MAXMEM
#ifdef pdp11
#define	MAXMEM ((1L << 16)-1)
#define	MAXUNSIGNED ((1L << 16)-1)
#else 
#define	MAXMEM	1048576	/* Megabyte maximum */
#endif
#endif

#ifndef	MINMEM
#define	MINMEM	  16384	/* 16K minimum */
#endif

#ifndef	DEFMEM
#define	DEFMEM	  32768	/* Same as old sort */
#endif


#define ASC 	0
#define NUM	1
#define MON	2

#define	USE	"invalid use of command line options"

#if i386
static void swapbytes();
#endif

#define	blank(c)	((c)==' ' || (c)=='\t')

FILE	*os;
char	*dirtry[] = {"/usr/tmp", "/tmp", NULL};
char	**dirs;
char	file1[100];
char	*file = file1;
char	*filep;
#define NAMEOHD 12 /* sizeof("/stm00000aa") */
int	nfiles;
int	*lspace;
int	*maxbrk;
unsigned tryfor;
long longtryfor;
unsigned alloc;
char bufin[8*BUFSIZ], bufout[8*BUFSIZ];	/* Use setvbuf's to avoid malloc calls.
					** malloc seems to get heartburn
					** when brk returns storage.
					*/
int	maxrec;
int	cmp(), cmpa();
int	(*compare)() = cmpa;
unsigned char	*eol();
int	term();
int 	mflg;
int	nway;
int	cflg;
int	uflg;
char	*outfil;
int unsafeout;	/*kludge to assure -m -o works*/
unsigned char	*tabchar;
int 	eargc;
char	**eargv;
struct btree {
    char *rp;
    int  rn;
} tree[TREEZ], *treep[TREEZ];
int	blkcnt[TREEZ];
char	**blkcur[TREEZ];
long	wasfirst = 0, notfirst = 0;
int	bonus;

unsigned char zero[256];

unsigned char	fold[256];

unsigned char nofold[256] = {
	0000,0001,0002,0003,0004,0005,0006,0007,
	0010,0011,0012,0013,0014,0015,0016,0017,
	0020,0021,0022,0023,0024,0025,0026,0027,
	0030,0031,0032,0033,0034,0035,0036,0037,
	0040,0041,0042,0043,0044,0045,0046,0047,
	0050,0051,0052,0053,0054,0055,0056,0057,
	0060,0061,0062,0063,0064,0065,0066,0067,
	0070,0071,0072,0073,0074,0075,0076,0077,
	0100,0101,0102,0103,0104,0105,0106,0107,
	0110,0111,0112,0113,0114,0115,0116,0117,
	0120,0121,0122,0123,0124,0125,0126,0127,
	0130,0131,0132,0133,0134,0135,0136,0137,
	0140,0141,0142,0143,0144,0145,0146,0147,
	0150,0151,0152,0153,0154,0155,0156,0157,
	0160,0161,0162,0163,0164,0165,0166,0167,
	0170,0171,0172,0173,0174,0175,0176,0177,
	0200,0201,0202,0203,0204,0205,0206,0207,
	0210,0211,0212,0213,0214,0215,0216,0217,
	0220,0221,0222,0223,0224,0225,0226,0227,
	0230,0231,0232,0233,0234,0235,0236,0237,
	0240,0241,0242,0243,0244,0245,0246,0247,
	0250,0251,0252,0253,0254,0255,0256,0257,
	0260,0261,0262,0263,0264,0265,0266,0267,
	0270,0271,0272,0273,0274,0275,0276,0277,
	0300,0301,0302,0303,0304,0305,0306,0307,
	0310,0311,0312,0313,0314,0315,0316,0317,
	0320,0321,0322,0323,0324,0325,0326,0327,
	0330,0331,0332,0333,0334,0335,0336,0337,
	0340,0341,0342,0343,0344,0345,0346,0347,
	0350,0351,0352,0353,0354,0355,0356,0357,
	0360,0361,0362,0363,0364,0365,0366,0367,
	0370,0371,0372,0373,0374,0375,0376,0377
};

unsigned char	nonprint[256]; 

unsigned char	dict[256]; 

struct	field {
	unsigned char *code;
	unsigned char *ignore;
	int fcmp;
	int rflg;
	int bflg[2];
	int m[2];
	int n[2];
}	*fields;
struct field proto = {
	nofold,
	zero,
	ASC,
	1,
	0,0,
	0,-1,
	0,0
};
int	nfields;
int 	error = 1;
char	*setfil();

char	*months[12] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };

struct	tm	ct = {
	0, 0, 1, 0, 86, 0, 0};

eucwidth_t	wp;
int	tbcw;

int	intl_flag;	/* international flag */
extern	char _cur_locale[][15];

main(argc, argv)
char **argv;
{
	register a;
	char *arg;
	struct field *p, *q;
	int i, nf;
	long ulimit();
	char *malloc();
	char *sbrk();
	char *getenv(), *lang, *envptr;
	tabchar = (unsigned char *)"";

	/* close any file descriptors that may have been */
	/* left open -- we may need them all		*/
	for (i = 3; i < 3 + N; i++)
		(void) close(i);

	getwidth(&wp);

	{	/* See if we should use strcoll */
		char coll_file_nm[128];
		strcpy( coll_file_nm, P_locale );
		strcat( coll_file_nm, _cur_locale[LC_COLLATE] );
		strcat( coll_file_nm, "/LC_COLLATE" );
		if ((i = open(coll_file_nm, O_RDONLY)) >= 0) {
			close(i);
			intl_flag = 1;
		}
		else
			intl_flag = 0;
	}

	fields = (struct field *)malloc(NF*sizeof(struct field));
	nf = NF;
	fields[nfields] = proto;
	initree();
	eargv = argv;
	tryfor = DEFMEM;
	while (--argc > 0) {
		if(**++argv == '-') for(arg = *argv;;) {
			switch(*++arg) {
			case '\0':
				if(arg[-1] == '-')
					eargv[eargc++] = "-";
				break;

			case 'o':
				if (*(arg+1) != '\0')
					outfil = arg + 1;
				else
				if(--argc > 0)
					outfil = *++argv;
				else {
					diag(gettxt("sort:2", "can't identify output file"),"");
					exit(1);
				}
				break;

			case 'T':
				if (--argc > 0) {
					if ((strlen(*++argv) + NAMEOHD) > sizeof(file1)) {
						diag(gettxt("sort:3", "path name too long: "), *argv);
						exit(1);
					}
					else dirtry[0] = *argv;
				}
				break;

			default:
				field(++*argv, nfields>0);
				break;
			}
			break;
		} else if (**argv == '+') {
			if(++nfields >= nf) {
				if((fields = (struct field *)realloc(fields, (nf + NF) * sizeof(struct field))) == NULL) {
					diag(gettxt("sort:4", "too many keys"), "");
					exit(1);
				}
				nf += NF;
			}
			fields[nfields] = proto;
			field(++*argv, 0);
		} else
			eargv[eargc++] = *argv;
	}
	q = &fields[0];
	for(a=1; a<=nfields; a++) {
		p = &fields[a];
		if(p->code != proto.code) continue;
		if(p->ignore != proto.ignore) continue;
		if(p->fcmp != proto.fcmp) continue;
		if(p->rflg != proto.rflg) continue;
		if(p->bflg[0] != proto.bflg[0]) continue;
		if(p->bflg[1] != proto.bflg[1]) continue;
		p->code = q->code;
		p->ignore = q->ignore;
		p->fcmp = q->fcmp;
		p->rflg = q->rflg;
		p->bflg[0] = p->bflg[1] = q->bflg[0];
	}
	if(eargc == 0)
		eargv[eargc++] = "-";
	if(cflg && eargc>1) {
		diag(gettxt("sort:5", "can check only 1 file"), "");
		exit(1);
	}

	safeoutfil();

	lspace = (int *) sbrk(0);
	maxbrk = (int *) ulimit(3,0L);
	if (!mflg && !cflg)
		if ((alloc=grow_core(tryfor,(unsigned) 0)) == 0) {
			diag(gettxt("sort:6", "allocation error before sort"),
				"");
			exit(1);
		}

	a = -1;
	for(dirs=dirtry; *dirs; dirs++) {
		(void) sprintf(filep=file1, "%s/stm%.5uaa", *dirs, getpid());
		while (*filep)
			filep++;
		filep -= 2;
		if ( (a=creat(file, 0600)) >=0)
			break;
	}
	if(a < 0) {
		diag(gettxt("sort:7", "can't locate temp"), "");
		exit(1);
	}
	(void) close(a);
	(void) unlink(file);
	if (sigset(SIGHUP, SIG_IGN) != SIG_IGN)
		(void) sigset(SIGHUP, term);
	if (sigset(SIGINT, SIG_IGN) != SIG_IGN)
		(void) sigset(SIGINT, term);
	(void) sigset(SIGPIPE, term);
	if (sigset(SIGTERM, SIG_IGN) != SIG_IGN)
		(void) sigset(SIGTERM, term);
	nfiles = eargc;
	if(!mflg && !cflg) {
		sort();
		if (ferror(stdin))
			rderror("stdin");
		(void) fclose(stdin);
	}

	if (maxrec == 0)  maxrec = 512;
	alloc = (N + 1) * maxrec + N * 8*BUFSIZ;
	for (nway = N; nway >= 2; --nway) {
		if (alloc < (maxbrk - lspace) * sizeof(int *))
			break;
		alloc -= maxrec + 8*BUFSIZ;
	}
	if (nway < 2 || brk((char *)lspace + alloc) != 0) {
		diag(gettxt("sort:8","allocation error before merge"), "");
		term();
	}

	if (cflg)   checksort();

	wasfirst = notfirst = 0;
	a = mflg || cflg ? 0 : eargc;
	if ((i = nfiles - a) > nway) {	/* Do leftovers early */
		if ((i %= (nway - 1)) == 0)
			i = nway - 1;
		if (i != 1)  {
			newfile();
			setvbuf(os, bufout,_IOFBF,8*BUFSIZ);
			merge(a, a+i);
			a += i;
		}
	}
	for(; a+nway<nfiles || unsafeout&&a<eargc; a=i) {
		i = a+nway;
		if(i>=nfiles)
			i = nfiles;
		newfile();
		setvbuf(os, bufout, _IOFBF, 8*BUFSIZ);
		merge(a, i);
	}
	if(a != nfiles) {
		oldfile();
		setvbuf(os, bufout, _IOFBF, 8*BUFSIZ);
		merge(a, nfiles);
	}
	error = 0;
	term();
}

sort()
{
	register char *cp;
	register char **lp;
	FILE *iop;
	char *keep, *ekeep, **mp, **lmp, **ep;
	int n;
	int done, i, first;
	char *f;

	/*
	** Records are read in from the front of the buffer area.
	** Pointers to the records are allocated from the back of the buffer.
	** If a partially read record exhausts the buffer, it is saved and
	** then copied to the start of the buffer for processing with the
	** next coreload.
	*/
	first = 1;
	done = 0;
	keep = 0;
	i = 0;
	ep = (char **) (((char *) lspace) + alloc);
	if ((f=setfil(i++)) == NULL) /* open first file */
		iop = stdin;
	else if ((iop=fopen(f,"r")) == NULL)
		cant(f);
	setvbuf(iop,bufin,_IOFBF,8*BUFSIZ);
	do {
		lp = ep - 1;
		cp = (char *) lspace;
		*lp-- = cp;
		if (keep != 0) /* move record from previous coreload */
			for(; keep < ekeep; *cp++ = *keep++);
#ifndef pdp11
		while ((char *)lp - cp > 1) {
			if (fgets(cp,(char *) lp - cp, iop) == NULL)
#else
		while ((char *)lp > cp + 1 ) {
			n = (char *)lp -cp;
			if(n < 0)
				n = MAXINT;
			if (fgets(cp,n,iop) == NULL)
#endif
				n = 0;
			else
				n = strlen(cp);
			if (n == 0) {
				if (ferror(iop))
					rderror(iop);

				if (keep != 0 )
					/* The kept record was at
					   the EOF.  Let the code
					   below handle it.       */;
				else
				if (i < eargc) {
					if ((f=setfil(i++)) == NULL)
						iop = stdin;
					else if ((iop=fopen(f,"r")) == NULL )
						cant(f);
					setvbuf(iop,bufin,_IOFBF,8*BUFSIZ);
					continue;
				}
				else {
					done++;
					break;
				}
			}
			cp += n - 1;
			if ( *cp == '\n') {
				cp += 2;
				if ( cp - *(lp+1) > maxrec )
					maxrec = cp - *(lp+1);
				*lp-- = cp;
				keep = 0;
			}
			else
			if ( cp + 2 < (char *) lp ) {
				/* the last record of the input */
				/* file is missing a NEWLINE    */
				if(f == NULL) diag(gettxt("sort:9","warning: missing NEWLINE added at EOF"), "");
				else diag(gettxt("sort:10","warning: missing NEWLINE added at end of input file "), f);
				*++cp = '\n';
				*++cp = '\0';
				*lp-- = ++cp;
				keep = 0;
			}
			else {  /* the buffer is full */
				keep = *(lp+1);
				ekeep = ++cp;
			}

#ifdef pdp11
			if ((char *)lp <= cp + 2 && first == 1) {
#else
			if ((char *)lp - cp <= 2 && first == 1) {
#endif
				/* full buffer */
				tryfor = alloc;
				tryfor = grow_core(tryfor,alloc);
				if (tryfor == 0)
					/* could not grow */
					first = 0;
				else { /* move pointers */
					lmp = ep + 
					   (tryfor/sizeof(char **) - 1);
					for ( mp = ep - 1; mp > lp;)
						*lmp-- = *mp--;
					ep += tryfor/sizeof(char **);
					lp += tryfor/sizeof(char **);
					alloc += tryfor;
				}
			}
		}
		if (keep != 0 && *(lp+1) == (char *) lspace) {
			diag(gettxt("sort:11", "fatal: record too large"),"");
			term();
		}
		first = 0;
		lp += 2;
		if(done == 0 || nfiles != eargc)
			newfile();
		else
			oldfile();
		setvbuf(os, bufout, _IOFBF, 8*BUFSIZ);
		msort(lp, ep);
		if (ferror(os))
			wterror(gettxt("sort:12", "sorting"));
		(void) fclose(os);
	} while(done == 0);
}


msort(a, b)
char **a, **b;
{
	register struct btree **tp;
	register int i, j, n;
	char *save;

	i = (b - a);
	if (i < 1)
		return;
	else if (i == 1) {
		wline(*a);
		return;
	}
	else if (i >= TREEZ)
		n = TREEZ; /* number of blocks of records */
	else n = i;

	/* break into n sorted subgroups of approximately equal size */
	tp = &(treep[0]);
	j = 0;
	do {
		(*tp++)->rn = j;
		b = a + (blkcnt[j] = i / n);
		qksort(a, b);
		blkcur[j] = a = b;
		i -= blkcnt[j++];
	} while (--n > 0);
	n = j;

	/* make a sorted binary tree using the first record in each group */
	for (i = 0; i < n;) {
		(*--tp)->rp = *(--blkcur[--j]);
		insert(tp, ++i);
	}
	wasfirst = notfirst = 0;
	bonus = cmpsave(n);


	j = uflg;
	tp = &(treep[0]);
	while (n > 0)  {
		wline((*tp)->rp);
		if (j) save = (*tp)->rp;

		/* Get another record and insert.  Bypass repeats if uflg */

		do {
			i = (*tp)->rn;
			if (j) while((blkcnt[i] > 1) &&
					(**(blkcur[i]-1) == '\0')) {
				--blkcnt[i];
				--blkcur[i];
			}
			if (--blkcnt[i] > 0) {
				(*tp)->rp = *(--blkcur[i]);
				insert(tp, n);
			} else {
				if (--n <= 0) break;
				bonus = cmpsave(n);
				tp++;
			}
		} while (j && (*compare)((*tp)->rp, save) == 0);
	}
}


/* Insert the element at tp[0] into its proper place in the array of size n */
/* Pretty much Algorith B from 6.2.1 of Knuth, Sorting and Searching */
/* Special case for data that appears to be in correct order */

insert(tp, n)
struct btree **tp;
int n;
{
    register struct btree **lop, **hip, **midp;
    register int c;
    struct btree *hold;

    midp = lop = tp;
    hip = lop++ + (n - 1);
    if ((wasfirst > notfirst) && (n > 2) &&
	((*compare)((*tp)->rp, (*lop)->rp) >= 0)) {
	wasfirst += bonus;
	return;
    }
    while ((c = hip - lop) >= 0) { /* leave midp at the one tp is in front of */
	midp = lop + c / 2;
	if ((c = (*compare)((*tp)->rp, (*midp)->rp)) == 0) break; /* match */
	if (c < 0) lop = ++midp;   /* c < 0 => tp > midp */
	else       hip = midp - 1; /* c > 0 => tp < midp */
    }
    c = midp - tp;
    if (--c > 0) { /* number of moves to get tp just before midp */
	hip = tp;
	lop = hip++;
	hold = *lop;
	do *lop++ = *hip++; while (--c > 0);
	*lop = hold;
	notfirst++;
    } else wasfirst += bonus;
}


merge(a, b)
{
	FILE *tfile[N];
	char *buffer = (char *) lspace;
	register int nf;		/* number of merge files */
	register struct btree **tp;
	register int i, j;
	char	*f;
	char	*save, *iobuf;

	save = (char *) lspace + (nway * maxrec);
	iobuf = save + maxrec;
	tp = &(treep[0]);
	for (nf=0, i=a; i < b; i++)  {
		f = setfil(i);
		if (f == 0)
			tfile[nf] = stdin;
		else if ((tfile[nf] = fopen(f, "r")) == NULL)
			cant(f);
		(*tp)->rp = buffer + (nf * maxrec);
		(*tp)->rn = nf;
		setvbuf(tfile[nf], iobuf, _IOFBF, 8*BUFSIZ);
		iobuf += 8*BUFSIZ;
		if (rline(tfile[nf], (*tp)->rp)==0) {
			nf++;
			tp++;
		} else {
			if(ferror(tfile[nf]))
				rderror(f);
			(void) fclose(tfile[nf]);
		}
	}


	/* make a sorted btree from the first record of each file */
	for (--tp, i = 1; i++ < nf;) insert(--tp, i);

	bonus = cmpsave(nf);
	tp = &(treep[0]);
	j = uflg;
	while (nf > 0) {
		wline((*tp)->rp);
		if (j) cline(save, (*tp)->rp);

		/* Get another record and insert.  Bypass repeats if uflg */

		do {
			i = (*tp)->rn;
			if (rline(tfile[i], (*tp)->rp)) {
				if (ferror(tfile[i]))
					rderror(setfil(i+a));
				(void) fclose(tfile[i]);
				if (--nf <= 0) break;
				++tp;
				bonus = cmpsave(nf);
			} else insert(tp, nf);
		} while (j && (*compare)((*tp)->rp, save) == 0 );
	}


	for (i=a; i < b; i++) {
		if (i >= eargc)
			(void) unlink(setfil(i));
	}
	if (ferror(os))
		wterror(gettxt("sort:13", "merging"));
	(void) fclose(os);
}

cline(tp, fp)
register char *tp, *fp;
{
	while ((*tp++ = *fp++) != '\n');
}

rline(iop, s)
register FILE *iop;
register char *s;
{
	register int n;

	if (fgets(s,maxrec,iop) == NULL )
		n = 0;
	else
		n = strlen(s);
	if ( n == 0 )
		return(1);
	s += n - 1;
	if ( *s == '\n' )
		return(0);
	else
	if ( n < maxrec - 1) {
		diag(gettxt("sort:14", "warning: missing NEWLINE at EOF added"),"");
		*++s = '\n';
		return(0);
	}
	else {
		diag(gettxt("sort:15", "fatal: line too long"),"");
		term();
	}
}

wline(s)
char *s;
{
	(void) fputs(s,os);
}

checksort()
{
	char *f;
	char *lines[2];
	register int i, j, r;
	register char **s;
	register FILE *iop;

	s = &(lines[0]);
	f = setfil(0);
	if (f == 0)
		iop = stdin;
	else if ((iop = fopen(f, "r")) == NULL)
		cant(f);
	setvbuf(iop, bufin,_IOFBF,8*BUFSIZ);

	i = 0;   j = 1;
	s[0] = (char *) lspace;
	s[1] = s[0] + maxrec;
	if ( rline(iop, s[0]) ) {
		if (ferror(iop)) {
			rderror(f);
		}
		(void) fclose(iop);
		exit(0);
	}
	while ( !rline(iop, s[j]) )  {
		r = (*compare)(s[i], s[j]);
		if (r < 0)
			disorder(gettxt("colon:73", "disorder: "), s[j]);
		if (r == 0 && uflg)
			disorder(gettxt("colon:74", "non-unique: "), s[j]);
		r = i;  i = j; j = r;
	}
	if (ferror(iop))
		rderror(f);
	(void) fclose(iop);
	exit(0);
}


disorder(s, t)
char *s, *t;
{
	register char *u;
	for(u=t; *u!='\n';u++) ;
	*u = 0;
	diag(s, t);
	term();
}

newfile()
{
	register char *f;

	f = setfil(nfiles);
	if((os=fopen(f, "w")) == NULL) {
		diag(gettxt("sort:16", "can't create "), f);
		term();
	}
	nfiles++;
}

char *
setfil(i)
register int i;
{
	if(i < eargc)
		if(eargv[i][0] == '-' && eargv[i][1] == '\0')
			return(0);
		else
			return(eargv[i]);
	i -= eargc;
	filep[0] = i/26 + 'a';
	filep[1] = i%26 + 'a';
	return(file);
}

oldfile()
{
	if(outfil) {
		if((os=fopen(outfil, "w")) == NULL) {
			diag(gettxt("sort:16", "can't create "), outfil);
			term();
		}
	} else
		os = stdout;
}

safeoutfil()
{
	register int i;
	struct stat ostat, istat;

	if(!mflg||outfil==0)
		return;
	if(stat(outfil, &ostat)==-1)
		return;
	if ((i = eargc - N) < 0) i = 0;	/*-N is suff., not nec. */
	for (; i < eargc; i++) {
		if(stat(eargv[i], &istat)==-1)
			continue;
		if(ostat.st_dev==istat.st_dev&&
		   ostat.st_ino==istat.st_ino)
			unsafeout++;
	}
}

cant(f)
char *f;
{
	diag(gettxt("sort:17", "can't open "), f);
	term();
}

diag(s, t)
char *s, *t;
{
	register FILE *iop;

	iop = stderr;
	(void) fputs(gettxt("colon:75", "sort: "), iop);
	(void) fputs(s, iop);
	(void) fputs(t, iop);
	(void) fputs("\n", iop);
}

term()
{
	register i;

	if(nfiles == eargc)
		nfiles++;
	for(i=eargc; i<=nfiles; i++) {	/*<= in case of interrupt*/
		(void) unlink(setfil(i));	/*with nfiles not updated*/
	}
	exit(error);
}

cmp(i, j)
char *i, *j;
{
	register unsigned char *pa, *pb;
	register unsigned char *ignore;
	register int sa;
	int sb;
	unsigned char *skip();
	unsigned char *code;
	int a, b;
	int k;
	unsigned char *la, *lb;
	unsigned char *ipa, *ipb, *jpa, *jpb;
	struct field *fp;

	for(k = nfields>0; k<=nfields; k++) {
		fp = &fields[k];
		pa = (unsigned char *)i;
		pb = (unsigned char *)j;
		if(k) {
			la = skip(pa, fp, 1);
			pa = skip(pa, fp, 0);
			lb = skip(pb, fp, 1);
			pb = skip(pb, fp, 0);
		} else {
			la = eol(pa);
			lb = eol(pb);
		}
		if(fp->fcmp==NUM) {
/*	check for field separator or blank */
 			while(blank(*pa))
 				pa++;
 			while(blank(*pb))
 				pb++;
			sa = sb = fp->rflg;
			if(*pa == '-') {
				pa++;
				sa = -sa;
			}
			if(*pb == '-') {
				pb++;
				sb = -sb;
			}
			for(ipa = pa; ipa<la&&isdigit(*ipa); ipa++) ;
			for(ipb = pb; ipb<lb&&isdigit(*ipb); ipb++) ;
			jpa = ipa;
			jpb = ipb;
			a = 0;
			if(sa==sb)
				while(ipa > pa && ipb > pb)
					if(b = *--ipb - *--ipa)
						a = b;
			while(ipa > pa)
				if(*--ipa != '0')
					return(-sa);
			while(ipb > pb)
				if(*--ipb != '0')
					return(sb);
			if(a) return(a*sa);
			if(*(pa=jpa) == '.')
				pa++;
			if(*(pb=jpb) == '.')
				pb++;
			if(sa==sb)
				while(pa<la && isdigit(*pa)
				   && pb<lb && isdigit(*pb))
					if(a = *pb++ - *pa++)
						return(a*sa);
			while(pa<la && isdigit(*pa))
				if(*pa++ != '0')
					return(-sa);
			while(pb<lb && isdigit(*pb))
				if(*pb++ != '0')
					return(sb);
			continue;
		} else if(fp->fcmp==MON)  {
			sa = fp->rflg*(month(pb)-month(pa));
			if(sa)
				return(sa);
			else
				continue;
		}
		code = fp->code;
		ignore = fp->ignore;
loop: 
		while(ignore[*pa])
			pa++;
		while(ignore[*pb])
			pb++;
		if(pa>=la || *pa=='\n')
			if(pb<lb && *pb!='\n')
				return(fp->rflg);
			else continue;
		if(pb>=lb || *pb=='\n')
			return(-fp->rflg);
		if((sa = code[*pb++]-code[*pa++]) == 0)
			goto loop;
		return(sa*fp->rflg);
	}
	if(uflg)
		return(0);
	return(cmpa(i, j));
}

cmpa(pa, pb)
register char *pa, *pb;
{
	if (intl_flag) {
		int i;
		if (!(i = _strcoll(pa, pb, '\n')))
			return 0;
		else if (i < 0)
			return fields[0].rflg;
		else
		return -fields[0].rflg;
	}

	while((unsigned char)*pa == (unsigned char)*pb++)
		if((unsigned char)*pa++ == '\n')
			return(0);
	return(
		(unsigned char)*pa == '\n' ? fields[0].rflg:
		(unsigned char)*--pb == '\n' ?-fields[0].rflg:
		(unsigned char)*pb > (unsigned char)*pa   ? fields[0].rflg:
		-fields[0].rflg
	);
}

unsigned char *
skip(p, fp, j)
struct field *fp;
register unsigned char *p;
{
	register i;
	register unsigned char *tbc;
	int contflag;
	int ew;
	int sw;
	int sswidth;
	int k;

	if( (i=fp->m[j]) < 0)
		return(eol(p));
	if (*tabchar) {
		while (--i >= 0) {
			do {
				contflag = 0;
				tbc = tabchar;
				if (ISASCII(*p)) {
					ew = 1;
				} else {
					if (ISSET2(*p))
						ew = wp._eucw2 + 1;
					else if (ISSET3(*p))
						ew = wp._eucw3 + 1;
					else
						ew = wp._eucw1;
				}
				if (*p != *tabchar)
					if(*p != '\n') {
						p += ew;
						contflag = 1;
					} else goto ret;
				else {
					for (k=1; k < tbcw; k++)
						if (*++p != *++tbc) {
							contflag = 1;
							p += (ew - k);
							break;
						}
				}
			} while (contflag);
			if (i > 0 || j == 0)
				p++;
			else if (j == 1)
				p -= (tbcw -1);
		}
	} else	while (--i >= 0) {
		while(blank(*p))
			p++;
		while(!blank(*p))
			if(*p != '\n')
				p++;
			else goto ret;
	}
	if(fp->bflg[j]) {
		if (j == 1 && fp->m[j] > 0)
			p++;
		while(blank(*p))
			p++;
	}
	i = fp->n[j];
	while((i > 0) && (*p != '\n')) {
		if (ISASCII(*p)) {
			p++;
			i--;
		} else {
			if (ISSET2(*p)) {
				ew = wp._eucw2;
				sw = wp._scrw2;
				sswidth = 1;
			} else if (ISSET3(*p)) {
				ew = wp._eucw3;
				sw = wp._scrw3;
				sswidth = 1;
			} else {
				ew = wp._eucw1;
				sw = wp._scrw1;
				sswidth = 0;
			}
			if ((i - sw) >= 0) {
				p += (ew + sswidth);
				i -= sw;
			} else if (ew == sw) {
				p += (i + sswidth);
				break;
			} else {
				while(*p != '\n')
					p++;
				break;
			}
		}
	}
ret:
	return(p);
}

unsigned char *
eol(p)
register unsigned char *p;
{
	while(*p != '\n') p++;
	return(p);
}

initree()
{
	register struct btree **tpp, *tp;
	register int i;

	for (tp = &(tree[0]), tpp = &(treep[0]), i = TREEZ; --i >= 0;)
	    *tpp++ = tp++;
}

cmpsave(n)
register int n;
{
	register int award;

	if (n < 2) return (0);
	for (n++, award = 0; (n >>= 1) > 0; award++);
	return (award);
}

field(s, k)
char *s;
{
	register struct field *p;
	register d;
	register i;

	p = &fields[nfields];
	d = 0;
	for(; *s!=0; s++) {
		switch(*s) {
		case '\0':
			return;

		case 'b':
			p->bflg[k]++;
			break;

		case 'd':
			for(i=0; i<256; i++)
				dict[i] = (!isalnum(i));
			dict['\t'] = dict[' '] = dict['\n'] = 0;
			p->ignore = dict;
			break;

		case 'f':
			for(i=0; i<SIZE; i++)
				fold[i] = (islower(i)? _toupper(i) : i);
			p->code = fold;
			break;

		case 'i':
			for(i=0; i<256; i++)
				nonprint[i] =  (isprint(i)  == 0);
			p->ignore = nonprint;
			break;

		case 'c':
			cflg = 1;
			continue;

		case 'm':
			mflg = 1;
			continue;

		case 'M':
			for(i=0; i<SIZE; i++)
				fold[i] = (islower(i)? _toupper(i) : i);
			month_init();
			p->fcmp = MON;
			p->bflg[0]++;
			break;

		case 'n':
			p->fcmp = NUM;
			p->bflg[0]++;
			break;

		case 't':
			if (ISASCII(*++s)) {
				tabchar = (unsigned char *)s;
				if(*tabchar == 0) s--;
				tbcw = 1;
			} else {
				if (ISSET2(*s))
					tbcw = wp._eucw2+1;
				else if (ISSET3(*s))
					tbcw = wp._eucw3+1;
				else
					tbcw = wp._eucw1;
				tabchar = (unsigned char *)s;
				s += (tbcw - 1);
			}
			continue;

		case 'r':
			p->rflg = -1;
			continue;

		case 'u':
			uflg = 1;
			continue;

		case 'y':
			if ( *++s )
				if (isdigit(*s))
#ifdef pdp11
					{
					longtryfor = ((long) number(&s)) * 1024L;
					if(longtryfor > MAXUNSIGNED) tryfor = (unsigned) MAXUNSIGNED;
					else tryfor = (unsigned) longtryfor;
					}
#else
					tryfor = number(&s) * 1024;
#endif
				else {
					diag(gettxt("sort:1",USE),"");
					exit(1);
				}
			else {
				--s;
				tryfor = MAXMEM;
			}
			continue;

		case 'z':
			if ( *++s && isdigit(*s))
				maxrec = number(&s);
			else {
				diag(gettxt("sort:1",USE),"");
				exit(1);
			}
			continue;

		case '.':
			if(p->m[k] == -1)	/* -m.n with m missing */
				p->m[k] = 0;
			d = &fields[0].n[0]-&fields[0].m[0];
			if (*++s == '\0') {
				--s;
				p->m[k+d] = 0;
				continue;
			}

		default:
			if (isdigit(*s))
				p->m[k+d] = number(&s);
			else {
				diag(gettxt("sort:1",USE),"");
				exit(1);
			}
		}
		compare = cmp;
	}
}

number(ppa)
register char **ppa;
{
	register int n;
	register char *pa;

	pa = *ppa;
	n = 0;
	while(isdigit(*pa)) {
		n = n*10 + *pa - '0';
		*ppa = pa++;
	}
	return(n);
}

#define qsexc(p,q) t= *p;*p= *q;*q=t
#define qstexc(p,q,r) t= *p;*p= *r;*r= *q;*q=t

qksort(a, l)
char **a, **l;
{
	register char **i, **j;
	register char **lp, **hp;
	char **k;
	int c, delta;
	char *t;
	unsigned n;


start:
	if((n=l-a) <= 1)
		return;

	n /= 2;
	if (n >= MTHRESH) {
		lp = a + n;
		i = lp - 1;
		j = lp + 1;
		delta = 0;
		c = (*compare)(*lp, *i);
		if (c < 0) --delta;
		else if (c > 0) ++delta;
		c = (*compare)(*lp, *j);
		if (c < 0) --delta;
		else if (c > 0) ++delta;
		if ((delta /= 2) && (c = (*compare)(*i, *j)))
		    if (c > 0) n -= delta;
		    else       n += delta;
	}
	hp = lp = a+n;
	i = a;
	j = l-1;


	for(;;) {
		if(i < lp) {
			if((c = (*compare)(*i, *lp)) == 0) {
				--lp;
				qsexc(i, lp);
				continue;
			}
			if(c < 0) {
				++i;
				continue;
			}
		}

loop:
		if(j > hp) {
			if((c = (*compare)(*hp, *j)) == 0) {
				++hp;
				qsexc(hp, j);
				goto loop;
			}
			if(c > 0) {
				if(i == lp) {
					++hp;
					qstexc(i, hp, j);
					i = ++lp;
					goto loop;
				}
				qsexc(i, j);
				--j;
				++i;
				continue;
			}
			--j;
			goto loop;
		}


		if(i == lp) {
			if(uflg)
				for(k=lp; k<hp;) **k++ = '\0';
			if(lp-a >= l-hp) {
				qksort(hp+1, l);
				l = lp;
			} else {
				qksort(a, lp);
				a = hp+1;
			}
			goto start;
		}


		--lp;
		qstexc(j, lp, i);
		j = --hp;
	}
}


month_init()
{
	char	time_buf[20], *ptr;
	int	i, len;

	for(i=0; i<12; i++)
		{
		ct.tm_mon = i;
		ascftime(time_buf, "%b", &ct);
		if ((ptr = malloc(len = strlen(time_buf)+1)) == NULL) {
		    perror("malloc");
		    exit(1);
		    }
		strcpy(months[i] = ptr, time_buf);
		}
}


month(s)
char *s;
{
	register unsigned char *t, *u;
	register i;
	register unsigned char *f = fold ;

	for(i=0; i<12; i++) {
		for(t=(unsigned char *)s, u=(unsigned char *)months[i]; f[*t++]==f[*u++]; )
			if(*u==0 || *u==' ')
				return(i);
	}
	return(-1);
}

rderror(s)
char *s;
{
	diag(gettxt("sort:18", "read error on "), s == 0 ? "stdin" : s);
	term();
}

wterror(s)
char *s;
{
	diag(gettxt("sort:19", "write error while "), s);
	term();
}

grow_core(size,cursize)
	unsigned size, cursize;
{
	unsigned newsize;
	/*The variable below and its associated code was written so this would work on */
	/*pdp11s.  It works on the vax & 3b20 also. */
	long longnewsize;

	longnewsize = (long) size + (long) cursize;
	if (longnewsize < MINMEM)
		longnewsize = MINMEM;
	else
	if (longnewsize > MAXMEM)
		longnewsize = MAXMEM;
	newsize = (unsigned) longnewsize;
	for (; ((char *)lspace+newsize) <= (char *)lspace; newsize >>= 1);
	if (longnewsize > (long) (maxbrk - lspace) * (long) sizeof(int *))
		newsize = (maxbrk - lspace) * sizeof(int *);
	if (newsize <= cursize)
		return(0);
	if ( brk((char *) lspace + newsize) != 0)
		return(0);
	return(newsize - cursize);
}
/*
	If the field separator char has been specified, check for it
	else look for the default field separator chars (space and tab).
*/

#if i386
static void
swapbytes(dst, size)
char dst[];
int size;
{
	char c;
	int i;

	for (i = 0; i < size; i += 2) {
		c = dst[i];
		dst[i] = dst[i+1];
		dst[i+1] = c;
	}
}
#endif
