/* stringfile.c: manage strings, files, and directories for edbrowse. */

#include "eb.h"

#include <dirent.h>
#include <termios.h>
#include <glob.h>
#include <pwd.h>
#include <grp.h>
#include <utime.h>

char emptyString[] = "";
bool showHiddenFiles, isInteractive;
int debugLevel = 1;
FILE *debugFile = NULL;
char *debugFileName;
bool debugClone, debugEvent, debugThrow, debugCSS, debugPromise;
bool debugLayout;
bool demin = false;
bool gotimers = true;
bool uvw;
int timerspeed = 1;
long long fileSize;
int ignore;
char *downDir, *down_prefile, *home;
bool endMarks;		// ^ $ on listed lines
uchar dirWrite;		// directories read write
bool dno; // directory names only
bool displaceFiles; // when moving or copying to another directory
char *recycleBin;
uchar ls_sort;		// sort method for directory listing
bool ls_reverse;		// reverse sort
char lsformat[12];	// size date etc on a directory listing

/*********************************************************************
Allocate and manage memory.
Allocate and copy strings.
If we're out of memory, the program aborts.  No error legs.
Soooooo much easier! With 32gb of RAM, we shouldn't run out.
*********************************************************************/

void *allocMem(size_t n)
{
	void *s;
	if (!n)
		return emptyString;
	if (!(s = malloc(n)))
		i_printfExit(MSG_MemAllocError, n);
	return s;
}

void *allocZeroMem(size_t n)
{
	void *s;
	if (!n)
		return emptyString;
	if (!(s = calloc(n, 1)))
		i_printfExit(MSG_MemCallocError, n);
	return s;
}

void *reallocMem(void *p, size_t n)
{
	void *s;
	if (!n) {
		i_printf(MSG_ReallocP);
		n = 256;
	}
	if (!p) {
		i_printf(MSG_Realloc0, n);
		return allocMem(n);
	}
	if (p == emptyString) {
		p = allocMem(n);
// keep the null byte that was present in emptyString.
// fileIntoMemory() needs this to keep null on the end of an empty file
// that was just read into memory.
		*(char *)p = 0;
	}
	if (!(s = realloc(p, n)))
		i_printfExit(MSG_ErrorRealloc, n);
	return s;
}

/* When you know the allocated thing is a string. */
char *allocString(size_t n)
{
	return (char *)allocMem(n);
}

char *allocZeroString(size_t n)
{
	return (char *)allocZeroMem(n);
}

char *reallocString(void *p, size_t n)
{
	return (char *)reallocMem(p, n);
}

void nzFree(void *s)
{
	if (s && s != emptyString)
		free(s);
}

/* some compilers care whether it's void * or const void * */
void cnzFree(const void *v)
{
	nzFree((void *)v);
}

char *appendString(char *s, const char *p)
{
	int slen = strlen(s);
	int plen = strlen(p);
	s = reallocString(s, slen + plen + 1);
	strcpy(s + slen, p);
	return s;
}

char *prependString(char *s, const char *p)
{
	int slen = strlen(s);
	int plen = strlen(p);
	char *t = allocString(slen + plen + 1);
	strcpy(t, p);
	strcpy(t + plen, s);
	nzFree(s);
	return t;
}

void skipWhite(const char **s)
{
	const char *t = *s;
	while (isspaceByte(*t))
		++t;
	*s = t;
}

void trimWhite(char *s)
{
	int l;
	if (!s)
		return;
	l = strlen(s);
	while (l && isspaceByte(s[l - 1]))
		--l;
	s[l] = 0;
}

void stripWhite(char *s)
{
	const char *t = s;
	skipWhite(&t);
	if (t > s)
		strmove(s, t);
	trimWhite(s);
}

/* compress white space */
void spaceCrunch(char *s, bool onespace, bool unprint)
{
	int i, j;
	char c;
	bool space = true;
	for (i = j = 0; (c = s[i]); ++i) {
		if (isspaceByte(c)) {
			if (!onespace)
				continue;
			if (!space)
				s[j++] = ' ', space = true;
			continue;
		}
		if (unprint && !isprintByte(c))
			continue;
		s[j++] = c, space = false;
	}
	if (space && j)
		--j;		/* drop trailing space */
	s[j] = 0;
}

/* Like strcpy, but able to cope with overlapping strings. */
char *strmove(char *dest, const char *src)
{
	return (char *)memmove(dest, src, strlen(src) + 1);
}

/* A bit like strncpy but:
* Don't pad the array after copying the null byte.
* If we run out of space before the null byte replace the last copied byte with null.
*/
char *copyString(char *dest, const char *src, size_t n)
{
        if (!memccpy(dest, src, 0, n))
                dest[n-1] = 0;
        return dest;
}

// Similar to allocMem and friends for asprintf and vasprintf
int createFormattedStringV(char **strp, const char *fmt, va_list ap)
{
        int ret = vasprintf(strp, fmt, ap);
        if (ret < 0)
            i_printfExit(MSG_CreateFormatStr, strerror(errno));
    return ret;
}

int createFormattedString(char **strp, const char *fmt, ...)
{
        int ret;
        va_list ap;
        va_start(ap, fmt);
        ret = createFormattedStringV(strp, fmt, ap);
        va_end(ap);
        return ret;
}

/*********************************************************************
OO has a lot of unnecessary overhead, and a few inconveniences,
but I really miss it right now.  The following
routines make up for the lack of simple string concatenation in C.
The string space allocated is always a power of 2 - 1, starting with 1.
Each of these routines puts an extra 0 on the end of the "string".
initString is a macro in ebprot.h.
It always starts the string at emptyString, then it grows from there.
*********************************************************************/

void stringAndString(char **s, int *l, const char *t)
{
	char *p = *s;
	int oldlen, newlen, x;
	oldlen = *l;
	newlen = oldlen + strlen(t);
	*l = newlen;
	++newlen;		/* room for the 0 */
	x = oldlen ^ newlen;
	if (x > oldlen) {	/* must realloc */
		newlen |= (newlen >> 1);
		newlen |= (newlen >> 2);
		newlen |= (newlen >> 4);
		newlen |= (newlen >> 8);
		newlen |= (newlen >> 16);
		p = reallocString(p, newlen);
		*s = p;
	}
	strcpy(p + oldlen, t);
}

// some long long versions, until we cut everything over consistently.
// These assume the string length still fits in an int. Squash 8 bytes down to 4.
void stringAndString8(char **s, long long *l, const char *t)
{
	int templength = *l;
	stringAndString(s, &templength, t);
	*l = templength;
}

void stringAndBytes(char **s, int *l, const char *t, int cnt)
{
	char *p = *s;
	int oldlen, newlen, x;
	oldlen = *l;
	newlen = oldlen + cnt;
	*l = newlen;
	++newlen;
	x = oldlen ^ newlen;
	if (x > oldlen) {	/* must realloc */
		newlen |= (newlen >> 1);
		newlen |= (newlen >> 2);
		newlen |= (newlen >> 4);
		newlen |= (newlen >> 8);
		newlen |= (newlen >> 16);
		p = reallocString(p, newlen);
		*s = p;
	}
	memcpy(p + oldlen, t, cnt);
	p[oldlen + cnt] = 0;
}

void stringAndBytes8(char **s, long long *l, const char *t, int cnt)
{
	int templength = *l;
	stringAndBytes(s, &templength, t, cnt);
	*l = templength;
}

void stringAndChar(char **s, int *l, char c)
{
	char *p = *s;
	int oldlen, newlen, x;
	oldlen = *l;
	newlen = oldlen + 1;
	*l = newlen;
	++newlen;
	x = oldlen ^ newlen;
	if (x > oldlen) {	/* must realloc */
		newlen |= (newlen >> 1);
		newlen |= (newlen >> 2);
		newlen |= (newlen >> 4);
		newlen |= (newlen >> 8);
		newlen |= (newlen >> 16);
		p = reallocString(p, newlen);
		*s = p;
	}
	p[oldlen] = c;
	p[oldlen + 1] = 0;
}

void stringAndChar8(char **s, long long *l, char c)
{
	int templength = *l;
	stringAndChar(s, &templength, c);
	*l = templength;
}

void stringAndNum(char **s, int *l, int n)
{
	char a[16];
	sprintf(a, "%d", n);
	stringAndString(s, l, a);
}

void stringAndNum8(char **s, long long *l, int n)
{
	int templength = *l;
	stringAndNum(s, &templength, n);
	*l = templength;
}

void stringAndLongLong(char **s, int *l, long long n)
{
	char a[32];
	sprintf(a, "%lld", n);
	stringAndString(s, l, a);
}

char *cloneString(const char *s)
{
	char *t;
	unsigned len;

	if (!s)
		return 0;
	if (!*s)
		return emptyString;
	len = strlen(s) + 1;
	t = allocString(len);
	strcpy(t, s);
	return t;
}

char *cloneMemory(const char *s, int n)
{
	char *t = allocString(n);
	if (n)
		memcpy(t, s, n);
	return t;
}

void leftClipString(char *s)
{
	char *t;
	if (!s)
		return;
	for (t = s; *t; ++t)
		if (!isspaceByte(*t))
			break;
	if (t > s)
		strmove(s, t);
}

/* shift string one to the right */
void shiftRight(char *s, char first)
{
	int l = strlen(s);
	for (; l >= 0; --l)
		s[l + 1] = s[l];
	s[0] = first;
}

/* pull a substring out of a larger string,
 * and make it its own allocated string */
char *pullString(const char *s, int l)
{
	char *t;
	if (!l)
		return emptyString;
	t = allocString(l + 1);
	memcpy(t, s, l);
	t[l] = 0;
	return t;
}

char *pullString1(const char *s, const char *t)
{
	return pullString(s, t - s);
}

/* return the number, if string is a number, else -1 */
int stringIsNum(const char *s)
{
	int n;
	if (!isdigitByte(s[0]))
		return -1;
	n = strtol(s, (char **)&s, 10);
	if (*s)
		return -1;
	return n;
}

bool stringIsDate(const char *s)
{
	if (!isdigitByte(*s))
		return false;
	++s;
	if (isdigitByte(*s))
		++s;
	if (*s++ != '/')
		return false;
	if (!isdigitByte(*s))
		return false;
	++s;
	if (isdigitByte(*s))
		++s;
	if (*s++ != '/')
		return false;
	if (!isdigitByte(*s))
		return false;
	++s;
	if (isdigitByte(*s))
		++s;
	if (isdigitByte(*s))
		++s;
	if (isdigitByte(*s))
		++s;
	if (*s)
		return false;
	return true;
}

bool stringIsFloat(const char *s, double *dp)
{
	const char *t;
	*dp = strtod(s, (char **)&t);
	if (*t)
		return false;	/* extra stuff at the end */
	return true;
}

bool memEqualCI(const char *s, const char *t, int len)
{
	char c, d;
	if (s == t)
		return true;
	if (!s || !t)
		return false;
	while (len--) {
		c = *s, d = *t;
		if (islowerByte(c))
			c = toupper(c);
		if (islowerByte(d))
			d = toupper(d);
		if (c != d)
			return false;
		++s, ++t;
	}
	return true;
}

bool stringEqual(const char *s, const char *t)
{
/* check equality of strings with handling of null pointers */
	if (s == t)
		return true;
	if (!s || !t)
		return false;
	if (strcmp(s, t))
		return false;
	return true;
}

bool stringEqualCI(const char *s, const char *t)
{
	char c, d;
/* if two pointers are equal we can return */
	if (s == t)
		return true;
/* if one is NULL then the strings can't be equal */
	if (!s || !t)
		return false;
	while ((c = *s) && (d = *t)) {
		if (islowerByte(c))
			c = toupper(c);
		if (islowerByte(d))
			d = toupper(d);
		if (c != d)
			return false;
		++s, ++t;
	}
	if (*s)
		return false;
	if (*t)
		return false;
	return true;
}

// a text line in the buffer isn't a string; you can't use strstr.
// It ends in \n
// Stop as soon as the target string overlaps the end of s to avoid
// peeking past the end of s
const char *stringInBufLine(const char *s, const char *t)
{
	int n = strlen(t);
	int i;
	for (i = 0; i < n - 1; i++) {
		if (s[i] == '\n') return 0; // too short
	}
	while(1) {
		if (!strncmp(s, t, n)) return s;
		if(s[n - 1] == '\n') break;
		++s;
	}
	return 0;
}

const char *charInBufLine(const char *s, char t)
{
	for (; *s != '\n'; ++s) {
		if (*s == t)
			return s;
	}
	return 0;
}

int stringInList(const char *const *list, const char *s)
{
	int i = 0;
	if (!list)
		i_printfExit(MSG_NullStrList);
	if (s)
		while (*list) {
			if (stringEqual(s, *list))
				return i;
			++i;
			++list;
		}
	return -1;
}

int stringInListCI(const char *const *list, const char *s)
{
	int i = 0;
	if (!list) return -1;
//		i_printfExit(MSG_NullStrListCI);
	if (s)
		while (*list) {
			if (stringEqualCI(s, *list))
				return i;
			++i;
			++list;
		}
	return -1;
}

int charInList(const char *list, char c)
{
	char *s;
	if (!list)
		i_printfExit(MSG_NullCharInList);
	s = (char *)strchr(list, c);
	if (!s)
		return -1;
	return s - list;
}

/* In an empty list, next and prev point back to the list, not to 0. */
/* We also allow zero. */
bool listIsEmpty(const struct listHead * l)
{
	return l->next == l || l->next == 0;
}

void initList(struct listHead *l)
{
	l->prev = l->next = l;
}

void delFromList(void *x)
{
	struct listHead *xh = (struct listHead *)x;
	((struct listHead *)xh->next)->prev = xh->prev;
	((struct listHead *)xh->prev)->next = xh->next;
}

void addToListFront(struct listHead *l, void *x)
{
	struct listHead *xh = (struct listHead *)x;
	xh->next = l->next;
	xh->prev = l;
	l->next = (struct listHead *)x;
	((struct listHead *)xh->next)->prev = (struct listHead *)x;
}

void addToListBack(struct listHead *l, void *x)
{
	struct listHead *xh = (struct listHead *)x;
	xh->prev = l->prev;
	xh->next = l;
	l->prev = (struct listHead *)x;
	((struct listHead *)xh->prev)->next = (struct listHead *)x;
}

void addAtPosition(void *p, void *x)
{
	struct listHead *xh = (struct listHead *)x;
	struct listHead *ph = (struct listHead *)p;
	xh->prev = p;
	xh->next = ph->next;
	ph->next = (struct listHead *)x;
	((struct listHead *)xh->next)->prev = (struct listHead *)x;
}

void freeList(struct listHead *l)
{
	while (!listIsEmpty(l)) {
		void *p = l->next;
		delFromList(p);
		nzFree(p);
	}
}

/* like isalnumByte, but allows _ and - */
bool isA(char c)
{
	if (isalnumByte(c))
		return true;
	return (c == '_' || c == '-');
}

bool isquote(char c)
{
	return c == '"' || c == '\'';
}

/* print an error message */
void errorPrint(const char *msg, ...)
{
	char bailflag = 0;
	va_list p;

	va_start(p, msg);

	if (*msg == '@') {
		bailflag = 1;
		++msg;
/* I should internationalize this, but it's never suppose to happen! */
		fprintf(stderr, "disaster, ");
	} else if (isdigitByte(*msg)) {
		bailflag = *msg - '0';
		++msg;
	}

	vfprintf(stderr, msg, p);
	fprintf(stderr, "\n");
	va_end(p);

	if (bailflag)
		exit(bailflag);
}

void debugPrint(int lev, const char *msg, ...)
{
	va_list p;
	if (lev > debugLevel)
		return;
	if (!debugFile || lev <= 2) {
		va_start(p, msg);
		vprintf(msg, p);
		va_end(p);
		printf("\n");
	}
	if (debugFile) {
		if (fseek(debugFile, 0, SEEK_END) >= 10000000) {
			puts("debug file overflow, program aborted");
			fclose(debugFile);
			ebClose(3);
		}
		va_start(p, msg);
		vfprintf(debugFile, msg, p);
		va_end(p);
		fprintf(debugFile, "\n");
	}
	if (lev == 0 && !memcmp(msg, "warning", 7))
		eeCheck();
}

void setDebugFile(const char *name)
{
	if (debugFile)
		fclose(debugFile);
	debugFile = 0;
	nzFree0(debugFileName);
	if (!name || !*name)
		return;
	debugFileName = cloneString(name);
	debugFile = fopen(name, "we");
	if (debugFile) {
		setlinebuf(debugFile);
	} else
		printf("cannot create %s\n", name);
}

void nl(void)
{
	eb_puts("");
}

/* Turn perl string into C string, and complain about nulls. */
int perl2c(char *t)
{
	int n = 0;
	while (*t != '\n') {
		if (*t == 0)
			++n;
		++t;
	}
	*t = 0;			/* now it's a C string */
	return n;		/* number of nulls */
}

/* The length of a perl string includes its terminating newline */
unsigned pstLength(const uchar *s)
{
	const uchar *t;
	if (!s)
		i_printfExit(MSG_NullPtr);
	t = s;
	while (*t != '\n')
		++t;
	return t + 1 - s;
}

pst clonePstring(const uchar *s)
{
	pst t;
	unsigned len;
	if (!s) return 0;
	len = pstLength(s);
	t = (pst) allocMem(len);
	memcpy(t, s, len);
	return t;
}

// Strings are assumed distinct, hence the use of memcpy.
void copyPstring(pst s, const pst t)
{
	int len = pstLength(t);
	memcpy(s, t, len);
}

int comparePstring(const uchar * s, const uchar * t)
{
	uchar c, d;
	while(true) {
		c = *s, d = *t;
		if(c == d) {
			if(c == '\n') return 0;
			 ++s, ++t;
			continue;
		}
		if(c == '\n') return -1;
		if(d == '\n') return 1;
		return c < d ? -1 : 1;
	}
}

/*********************************************************************
fdIntoMemory reads data from a file descriptor, until EOF is reached.
It works even if we don't know the size beforehand.
We can now use it to read /proc files, pipes, and stdin.
This solves an outstanding issue, and it is needed for forthcoming
functionality, such as edpager.
inpart = 0: read the whole file
inpart = 1: read the first part
inpart = 2: read the next part
It is more efficient if PARTSIZE is a multiple of the page size.
*********************************************************************/
#define FILEPARTSIZE 0x4000000

int fdIntoMemory(int fd, char **data, int *len, int inparts)
{
	int length, n, j;
	const int blocksize = 8192;
	char *chunk, *buf;
	static char *leftover;
	static int lolen; // leftover length

	chunk = allocZeroString(blocksize);
	if(inparts <= 1)
		buf = initString(&length);
	else
		buf = leftover, length = lolen;

	while(true) {
		if(length >= 0x7fffff00 - blocksize) {
			nzFree(buf);
			nzFree(chunk);
			*data = emptyString;
			*len = 0;
// fileIntoMemory improves on this error message
			setError(MSG_BigFile);
			errno = EFBIG;
			return 0;
		}
		n = read(fd, chunk, blocksize);
		if (n < 0) {
			nzFree(buf);
			nzFree(chunk);
			*data = emptyString;
			*len = 0;
// fileIntoMemory improves on this error message
			setError(MSG_NoRead2, "file descriptor", strerror(errno));
			return 0;
		}

		if (!n) break;
		stringAndBytes(&buf, &length, chunk, n);
		if(!inparts || length < FILEPARTSIZE) continue;
// Can't read in parts if chars are 16 bit or 32 bit wide
		if(inparts == 1 && byteOrderMark((uchar *) buf, length)) {
			inparts = 0; // back to default read
			continue;
		}
// we have to have some leftover
		for(j = length - 2; j > 0 && buf[j] != '\n'; --j)  ;
		if(!j) continue;
		++j;
		leftover = initString(&lolen);
		stringAndBytes(&leftover, &lolen, buf + j, length - j);
		nzFree(chunk);
// do we need room for the extra \n\0 on the end for a piece of the file?
// I don't think so.
		*data = buf;
		*len = j;
// success, but more to read
		debugPrint(4, "part");
		return 2;
	}

	nzFree(chunk);
	buf = reallocString(buf, length + 2);
	*data = buf;
	*len = length;
	return 1;
}

int fileIntoMemory(const char *filename, char **data, int *len, int inparts)
{
	static int fh;
	char ftype;
	int ret;

	if(inparts == 2) goto fh_set;
	ftype = fileTypeByName(filename, 0);
	if (ftype && ftype != 'f' && ftype != 'p') {
		setError(MSG_RegularFile, filename);
		return 0;
	}
	fh = open(filename, O_RDONLY | O_BINARY | O_CLOEXEC);
	if (fh < 0) {
		setError(MSG_NoOpen, filename, strerror(errno));
		return 0;
	}

fh_set:
	ret = fdIntoMemory(fh, data, len, inparts);
	if (ret == 0 && filename)
		setError(MSG_NoRead2, filename, strerror(errno));
	if(ret <= 1) close(fh);
	return ret;
}

// inverse of the above
bool memoryOutToFile(const char *filename, const char *data, int len)
{
	int fh =
	    open(filename, O_CREAT | O_TRUNC | O_WRONLY | O_BINARY | O_CLOEXEC, MODE_rw);
	if (fh < 0) {
			setError(MSG_TempNoCreate2, filename, strerror(errno));
		return false;
	}
	if (write(fh, data, len) < len) {
			setError(MSG_NoWrite2, filename, strerror(errno));
		close(fh);
		return false;
	}
	close(fh);
	return true;
}

// portable function to truncate to 0
void truncate0(const char *filename, int fh)
{
	if (fh < 0)
		truncate(filename, 0l);
	else
		ftruncate(fh, 0l);
}

long long bufferSizeW(const Window *w, bool browsing)
{
	int ln;
	long long size = 0;
	pst p;
	if (!w)
		return -1;
	for (ln = 1; ln <= w->dol; ++ln) {
		p = w->map[ln].text;
		while (*p != '\n') {
			if (*p == InternalCodeChar && browsing && w->browseMode) {
				++p;
				while (isdigitByte(*p)) ++p;
				if (strchr("<>{}", *p)) ++size;
				++p;
				continue;
			}
			++p, ++size;
		}
		++size;
	}			// loop over lines
	if (w->nlMode)
		--size;
	return size;
}

long long bufferSize(int cx, bool browsing)
{
	const Window *w;
	if (cx <= 0 || cx > maxSession || (w = sessionList[cx].lw) == 0) {
		setError(MSG_SessionInactive, cx);
		return -1;
	}
	return bufferSizeW(w, browsing);
}

// Unfold the buffer into one long, allocated string
bool unfoldBufferW(const Window *w, bool cr, char **data, int *len)
{
	char *buf;
	int l, ln;
	long long size = bufferSizeW(w, false);
	if (size < 0)
		return false;
	if (size >= 2000000000) {
		setError(MSG_BigFile);
		return false;
	}
	if (w->dirMode) {
		setError(MSG_SessionDir, context);
		return false;
	}
	if (cr)
		size += w->dol;
/* a few bytes more, just for safety */
	buf = allocMem(size + 4);
	*data = buf;
	for (ln = 1; ln <= w->dol; ++ln) {
		pst line = w->map[ln].text;
		l = pstLength(line) - 1;
		if (l) {
			memcpy(buf, line, l);
			buf += l;
		}
		if (cr) {
			*buf++ = '\r';
			if (l && buf[-2] == '\r')
				--buf, --size;
		}
		*buf++ = '\n';
	}			// loop over lines
	if (w->dol && w->nlMode) {
		if (cr)
			--size;
	}
	*len = size;
	(*data)[size] = 0;
	return true;
}

bool unfoldBuffer(int cx, bool cr, char **data, int *len)
{
	const Window *w = sessionList[cx].lw;
	return unfoldBufferW(w, cr, data, len);
}

// shift string to upper or lower case
// action is u or l.
void caseShift(char *s, char action)
{
	char c;
	for (; (c = *s); ++s) {
		if (action == 'u') {
			if (isalphaByte(c))
				*s = toupper(c);
			continue;
		}
		if (action == 'l') {
			if (isalphaByte(c))
				*s = tolower(c);
			continue;
		}
	}
}

// support routines for i_caseShift below
// this assumes a utf8 string
static bool i_isalpha(const char *s)
{
	uchar c = *(const uchar*)s, d = *(const uchar*)(s+1);
	if(!(c&0x80)) return isalpha(c) ? true : false;
	if((c&0xe0) != 0xc0) return false;
	uchar u = (c<<6) | (d&0x3f);
	if(u < 0xc0) return false; // out of range
	if(u == 0xd7 || u == 0xf7) return false; // times divide
	return true;
}

static void i_tolower(char *s)
{
	uchar c = *(uchar*)s, d = *(uchar*)(s+1);
	if(!(c&0x80)) {
		*s = tolower(c);
		return;
	}
	uchar u = (c<<6) | (d&0x3f);
	if(u == 0xdf) return;
	 s[1] |= 0x20;
}

static void i_toupper(char *s)
{
	uchar c = *(uchar*)s;
	if(!(c&0x80)) {
		*s = toupper(c);
		return;
	}
	 s[1] &= ~0x20;
}

// shift string to upper, lower, or mixed case.
// action is u, l, or m.
// This is international version, up to unicode 0xff.
void i_caseShift(char *s, char action)
{
	char c;
	int mc = 0;
	bool ws = true;
	char *s0 = s;

	for (; (c = *s); ++s) {
// trailing parts of utf8
	if(((uchar)c&0xc0) == 0x80) continue;
		if (action == 'u') {
			if (i_isalpha(s))
				i_toupper(s);
			continue;
		}
		if (action == 'l') {
			if (i_isalpha(s))
				i_tolower(s);
			continue;
		}
// mixed case left
		if (i_isalpha(s)) {
			if (ws)
				i_toupper(s);
			else
				i_tolower(s);
			if (ws && *s == 'M') mc = 1;
			else if (mc == 1 && *s == 'c') mc = 2;
			else if (mc == 2) {
				i_toupper(s);
				mc = 0;
			} else mc = 0;
			ws = false;
			continue;
		}
		ws = true, mc = 0;
// code for don't and we'll etc
		if((c == '\'' ||
	((unsigned char)c == 0xe0 && (unsigned char)s[1] == 0x80 && (unsigned char)s[2] == 0x9d)) &&
	s > s0 &&
	!(s[-1]&0x80) &&
	isalpha(s[-1]))
		ws = false;
	}
}

// foo-bar has to become fooBar
void camelCase(char *s)
{
	char *t, *w;
	for (t = w = s; *t; ++t)
		if (*t == '-' && isalphaByte(t[1]))
			t[1] = toupper(t[1]);
		else
			*w++ = *t;
	*w = 0;
}

/*********************************************************************
Manage files, directories, and terminal IO.
You'll see some conditional compilation when this program
is ported to other operating systems.
*********************************************************************/

/* Return the type of a file.
 * Make it a capital letter if you are going through a link.
 * I think this will work on Windows, not sure.
 * But the link feature is Unix specific.
 * Remember the stat structure for other things. */
static struct stat this_stat;
static bool this_waslink, this_brokenlink;

char fileTypeByName(const char *name, int showlink)
{
	bool islink = false;
	char c;
	int mode;

	this_waslink = false;
	this_brokenlink = false;

	if(showlink == 2 && dno) {
		this_brokenlink = true;
		return 'f';
	}

	if (lstat(name, &this_stat)) {
		setError(MSG_NoAccess, name);
		return 0;
	}
	mode = this_stat.st_mode & S_IFMT;
	if (mode == S_IFLNK) {	/* symbolic link */
		islink = this_waslink = true;
// If this fails, I'm guessing it's just a file.
		if (stat(name, &this_stat)) {
			this_brokenlink = true;
			return (showlink ? 'F' : 0);
		}
		mode = this_stat.st_mode & S_IFMT;
	}

	c = 'f';
	if (mode == S_IFDIR)
		c = 'd';
	if (mode == S_IFBLK)
		c = 'b';
	if (mode == S_IFCHR)
		c = 'c';
	if (mode == S_IFIFO)
		c = 'p';
	if (mode == S_IFSOCK)
		c = 's';
	if (islink && showlink)
		c = toupper(c);
	return c;
}

char fileTypeByHandle(int fd)
{
	struct stat buf;
	char c;
	int mode;
	if (fstat(fd, &buf)) {
		setError(MSG_NoAccess, "handle");
		return 0;
	}
	mode = buf.st_mode & S_IFMT;
	c = 'f';
	if (mode == S_IFDIR)
		c = 'd';
	if (mode == S_IFBLK)
		c = 'b';
	if (mode == S_IFCHR)
		c = 'c';
	if (mode == S_IFIFO)
		c = 'p';
	if (mode == S_IFSOCK)
		c = 's';
	return c;
}

off_t fileSizeByName(const char *name)
{
	struct stat buf;
	if (stat(name, &buf)) {
		setError(MSG_NoAccess, name);
		return -1;
	}
	return buf.st_size;
}

off_t fileSizeByHandle(int fd)
{
	struct stat buf;
	if (fstat(fd, &buf)) {
/* should never happen */
		return -1;
	}
	return buf.st_size;
}

time_t fileTimeByName(const char *name)
{
	struct stat buf;
	if (stat(name, &buf)) {
		setError(MSG_NoAccess, name);
		return -1;
	}
	return buf.st_mtime;
}

char *conciseSize(long long n)
{
	static char buf[32];
	size_t u;
	unsigned int v;
	if (n >= 1000000000000) {
		u = n/1000000000000;
		v = n/100000000000 % 10;
		if(u >= 10 || !v)
			sprintf(buf, "%zuT", u);
		else
			sprintf(buf, "%zu.%0uT", u, v);
	} else if (n >= 1000000000) {
		u = n/1000000000;
		v = n/100000000 % 10;
		if(u >= 10 || !v)
			sprintf(buf, "%zuG", u);
		else
			sprintf(buf, "%zu.%0uG", u, v);
	} else if (n >= 1000000) {
		u = n/1000000;
		v = n/100000 % 10;
		if(u >= 10 || !v)
			sprintf(buf, "%zuM", u);
		else
			sprintf(buf, "%zu.%0uM", u, v);
	} else if (n >= 1000) {
		u = n/1000;
		v = n/100 % 10;
		if(u >= 10 || !v)
			sprintf(buf, "%zuK", u);
		else
			sprintf(buf, "%zu.%0uK", u, v);
	} else {
		v = n;
		sprintf(buf, "%u", v);
	}
	return buf;
}

char *conciseTime(time_t t)
{
	static char buffer[24];
// longest month is 5 bytes
	static const char *const englishMonths[] = {
		"Jan", "Feb", "Mar", "Apr", "May", "Jun",
		"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
	};
	static const char *const frenchMonths[] = {
		"Janv", "Fév", "Mars", "Avr", "Mai", "Juin",
		"Juill", "Août", "Sept", "Oct", "Nov", "Déc",
		0
	};
	static const char *const portMonths[] = {
		"Jan", "Fev", "Mar", "Abr", "Mai", "Jun",
		"Jul", "Ago", "Set", "Out", "Nov", "Dec",
	};
	static const char *const germanMonths[] = {
		"Jan", "Feb", "Mär", "Apr", "Mai", "Jun",
		"Jul", "Aug", "Sep", "Okt", "Nov", "Dez",
	};
	static const char *const italMonths[] = {
		"Gen", "Feb", "Mar", "Apr", "Mag", "Giu",
		"Lug", "Ago", "Set", "Ott", "Nov", "Dic",
	};
	static const char *const spanishMonths[] = {
		"Ene", "Feb", "Mar", "Abr", "May",
		"Jun", "Jul", "Ago", "Sep", "Oct", "Nov", "Dic"
	};
	static const char *const *const allMonths[] = { 0,
		englishMonths,
		frenchMonths,
		portMonths,
		englishMonths,
		germanMonths,
		englishMonths,
		italMonths,
		spanishMonths,
	};
	struct tm *tm = localtime(&t);
	sprintf(buffer, "%s %2d %d %02d:%02d",
		allMonths[eb_lang][tm->tm_mon], tm->tm_mday, tm->tm_year + 1900,
		tm->tm_hour, tm->tm_min);
	return buffer;
}

/* retain only characters l s t i k p m, for ls attributes */
bool lsattrChars(const char *buf, char *dest)
{
	bool rc = true;
	const char *s;
	char c, *t;
	const char * ok_chars = "lstikpmy";
	if(cw->imapMode2) ok_chars = "uyftdlzr";
	if(cw->imapMode3) ok_chars = "uyftdlz";
	if(cw->ircoMode1) ok_chars = "ct";
	char used[26];
	memset(used, 0, sizeof(used));
	t = dest;
	for (s = buf; (c = *s); ++s) {
		if (isspaceByte(c)) continue;
		if((cw->imapMode2|cw->imapMode3) && c == 's') c = 'z';
		if (!strchr(ok_chars, c)) {
			rc = false;
			continue;
		}
		if (used[c - 'a'])
			continue;
		used[c - 'a'] = 1;
		*t++ = c;
	}
	*t = 0;
	return rc;
}

// expand the ls attributes for a file into a static string.
// This assumes user/group names will not be too long.
// Assumes we just called fileTypeByName.
char *lsattr(const char *path, const char *flags)
{
	static char buf[200 + ABSPATH];
	char p[40];
	struct passwd *pwbuf;
	struct group *grpbuf;
	char *s;
	int l, modebits;
	char newpath[ABSPATH];

	buf[0] = 0;

	if (!path || !path[0] || !flags || !flags[0])
		return buf;

	while (*flags) {
		if (buf[0])
			strcat(buf, " ");
		if (this_brokenlink && *flags != 'y') {
			strcat(buf, "~");
			++flags;
			continue;
		}

		switch (*flags) {
		case 't':
			strcat(buf, conciseTime(this_stat.st_mtime));
			break;
		case 'l':
			sprintf(p, "%lld", (long long)this_stat.st_size);
p:
			strcat(buf, p);
			break;
		case 's':
			strcat(buf, conciseSize(this_stat.st_size));
			break;
		case 'i':
			sprintf(p, "%lu", (unsigned long)this_stat.st_ino);
			goto p;
		case 'k':
			sprintf(p, "%lu", (unsigned long)this_stat.st_nlink);
			goto p;
		case 'm':
			strcpy(p, "-");
			if (this_stat.st_rdev)
				sprintf(p, "%d/%d",
					(int)(this_stat.st_rdev >> 8),
					(int)(this_stat.st_rdev & 0xff));
			goto p;
		case 'p':
			s = buf + strlen(buf);
			pwbuf = getpwuid(this_stat.st_uid);
			if (pwbuf)
// 21 bytes because we want 20 characters plus the null
				copyString(s, pwbuf->pw_name, 21);
			else
				sprintf(s, "%d", this_stat.st_uid);
			s += strlen(s);
			*s++ = ' ';
			grpbuf = getgrgid(this_stat.st_gid);
			if (grpbuf)
                                copyString(s, grpbuf->gr_name, 21);
			else
				sprintf(s, "%d", this_stat.st_gid);
			s += strlen(s);
			*s++ = ' ';
			modebits = this_stat.st_mode;
			modebits &= 07777;
			if (modebits & 07000)
				*s++ = '0' + (modebits >> 9);
			modebits &= 0777;
			*s++ = '0' + (modebits >> 6);
			modebits &= 077;
			*s++ = '0' + (modebits >> 3);
			modebits &= 7;
			*s++ = '0' + modebits;
			*s = 0;
			break;

		case 'y':
			if (!this_waslink) {
				strcat(buf, "-");
				break;
			}
/* yes it's a link, read the path */
			l = readlink(path, newpath, sizeof(newpath));
			if (l <= 0)
				strcat(buf, "...");
			else {
				s = buf + strlen(buf);
// The value written by readlink is not defined to be null-terminated
				memcpy(s, newpath, l);
                                s[l] = 0;
			}
			break;
		}

		++flags;
	}

	return buf;
}

static struct termios savettybuf;
void ttySaveSettings(void)
{
	isInteractive = isatty(0);
	if (isInteractive) {
		if (tcgetattr(0, &savettybuf))
			i_printfExit(MSG_IoctlError);
	}
}

void ttyRestoreSettings(void)
{
	if (isInteractive)
		tcsetattr(0, TCSANOW, &savettybuf);
}

/* put the tty in raw mode.
 * Review your Unix manual on termio.
 * min>0 time>0:  return min chars, or as many as you have received
 *   when time/10 seconds have elapsed between characters.
 * min>0 time=0:  block until min chars are received.
 * min=0 time>0:  return 1 char, or 0 if the timer expires.
 * min=0 time=0:  nonblocking, return whatever chars have been received. */
void ttyRaw(int charcount, int timeout, bool isecho)
{
	struct termios buf = savettybuf;	/* structure copy */
	buf.c_cc[VMIN] = charcount;
	buf.c_cc[VTIME] = timeout;
	buf.c_lflag &= ~(ICANON | ECHO);
	if (isecho)
		buf.c_lflag |= ECHO;
	tcsetattr(0, TCSANOW, &buf);
}

void ttySetEcho(bool enable_echo)
{
	struct termios termios;

	if (!isInteractive)
		return;

	tcgetattr(0, &termios);
	if (enable_echo) {
		termios.c_lflag |= ECHO;
		termios.c_lflag &= ~ECHONL;
	} else {
		termios.c_lflag &= ~ECHO;
		termios.c_lflag |= ECHONL;
	}
	tcsetattr(0, TCSANOW, &termios);
}

/* simulate MSDOS getche() system call */
int getche(void)
{
	char c;
	if(!isInteractive) return getchar();
	fflush(stdout);
	ttyRaw(1, 0, true);
	read(0, &c, 1);
	ttyRestoreSettings();
	return c;
}

int getch(void)
{
	char c;
	if(!isInteractive) return getchar();
	fflush(stdout);
	ttyRaw(1, 0, false);
	read(0, &c, 1);
	ttyRestoreSettings();
	return c;
}


char getLetter(const char *s)
{
	char c;
	while (true) {
		c = getch();
		if (strchr(s, c))
			break;
		if(isInteractive) {
			printf("\a\b");
			fflush(stdout);
		}
	}
	printf("%c", c);
	return c;
}

/* Parameters: message, default file name, must this file be new,
 * and can we except an input of white space,
 * that being converted to a single space. */
char *getFileName(int msg, const char *defname, bool isnew, bool ws, unsigned long long int filesize)
{
	static char buf[ABSPATH];
	static char spacename[] = " ";
	int l;
	char *p;
	bool allspace;

	while (true) {
		i_printf(msg);
// give a heads up on how big this is going to be
		if(filesize) printf("%s ", conciseSize(filesize));
		if (defname)
			printf("[%s] ", defname);
		fflush(stdout);
		if (!fgets(buf, sizeof(buf), stdin))
			exit(0);
		allspace = false;
		for (p = buf; isspaceByte(*p); ++p)
			if (*p == ' ')
				allspace = true;
		l = strlen(p);
		while (l && isspaceByte(p[l - 1]))
			--l;
		p[l] = 0;
		if (!l) {
			if (ws & allspace)
				return spacename;
			if (!defname)
				continue;
// make a copy just to be safe
			copyString(buf, defname,  sizeof(buf));
			p = buf;
		} else
			defname = 0;
// see if file exists
		if (isnew && !stringEqual(p, "x") && !stringEqual(p, "X") && fileTypeByName(p, 0)) {
			i_printf(MSG_FileExists, p);
			defname = 0;
			continue;
		}
		return p;
	}
}

/* Protect a filename from expansion by the shell */
static const char shellmeta[] = "\\\n\t |&;<>(){}#'\"~$*?`";
int shellProtectLength(const char *s)
{
	int l = 0;
	while (*s) {
		if (strchr(shellmeta, *s))
			++l;
		++l, ++s;
	}
	return l;
}

void shellProtect(char *t, const char *s)
{
	while (*s) {
		if (strchr(shellmeta, *s))
			*t++ = '\\';
		*t++ = *s++;
	}
}

// get the directory suffix for a file.
// This only makes sense in directory mode.
char *dirSuffixWindow(int n, const Window *w)
{
	static char suffix[4];
	suffix[0] = 0;
	if (w->dirMode) {
		suffix[0] = w->dmap[DTSIZE*n];
		suffix[1] = w->dmap[DTSIZE*n + 1];
		suffix[2] = 0;
	}
	return suffix;
}

static char *dirSuffixContext(int n, int cx)
{
	return dirSuffixWindow(n, sessionList[cx].lw);
}

char *dirSuffix(int n)
{
	return dirSuffixContext(n, context);
}

char *dirSuffix2(int n, const char *path)
{
	static char suffix[4], *t;
	char ftype, c;
	if(!cw->dnoMode)
		return dirSuffixContext(n, context);
// names only, don't have file type information, have to go get it
	t = suffix;
	ftype = fileTypeByName(path, 1);
	if(isupperByte(ftype)) *t++ = '@', ftype = tolower(ftype);
	c = 0;
	if (ftype == 'd') c = '/';
	if (ftype == 's') c = '^';
	if (ftype == 'c') c = '<';
	if (ftype == 'b') c = '*';
	if (ftype == 'p') c = '|';
	*t++ = c;
	*t = 0;
	return suffix;
}

// create the full pathname for a file that you are viewing in directory mode.
// The return is static, with a limit on path length.
char *makeAbsPath(const char *f)
{
	static char path[ABSPATH];
        char *ret = NULL;

        if (cw->baseDirName) {
                int total = snprintf(path, sizeof(path), "%s/%s", cw->baseDirName, f);
/* I'm not sure if we care about old glibc at this point (probably not) but
handle both the new-style and old-style truncation checks just in case
*/
                if (total > 0 && total < ABSPATH) ret = path;
        } else if (strlen(f) < ABSPATH) {
                strcpy(path, f);
                ret = path;
        }

        if (!ret) setError(MSG_PathNameLong, ABSPATH);
        return ret;
}

/* loop through the files in a directory */
const char *nextScanFile(const char *base)
{
	static DIR *df;
	struct dirent *de;
	const char *s;

	if (!df) {
		if (!base)
			base = ".";
		// Seems safe to assume opendir will O_CLOEXEC under the hood.
		df = opendir(base);
/* directory could be unreadable */
		if (!df) {
			i_puts(MSG_NoDirNoList);
			return 0;
		}
	}

	while ((de = readdir(df))) {
		s = de->d_name;
		if (s[0] == '.') {
			if (stringEqual(s, "."))
				continue;
			if (!showHiddenFiles)
				continue;
		}
		return s;
	}			/* end loop over files in directory */

	closedir(df);
	df = 0;
	return 0;
}

// compare routine for quicksort directory scan, alphabetical
static bool dir_reverse;
static int dircmp_alph(const void *s, const void *t)
{
	int rc = strcoll((const char *)((const struct lineMap *)s)->text,
			 (const char *)((const struct lineMap *)t)->text);
	if (dir_reverse)
		rc = -rc;
	return rc;
}

static bool sortedDirList(const char *dir, struct lineMap ** map_p, int *count_p,
		   int othersort, bool reverse)
{
	const char *f;
	int linecount = 0, cap;
	struct lineMap *t, *map;

	cap = 128;
	map = t = (struct lineMap *)allocZeroMem(cap * LMSIZE);

	while ((f = nextScanFile(dir))) {
		if (linecount == cap) {
			cap *= 2;
			map = (struct lineMap *)reallocMem(map, cap * LMSIZE);
			t = map + linecount;
		}
/* leave room for @ / newline */
		t->text = (pst) allocMem(strlen(f) + 3);
		strcpy((char *)t->text, f);
		++t, ++linecount;
	}

	*count_p = linecount;
	*map_p = map;

	if (!linecount)
		return true;

// Sort the entries alphabetical,
// unless we plan to sort them some other way.
	if (!othersort) {
		dir_reverse = reverse;
		qsort(map, linecount, LMSIZE, dircmp_alph);
	}

	return true;
}

// directory sort record, for nonalphabetical sorts.
struct DSR {
	int idx;
	union {
#ifdef linux
		struct timespec spec;
#else
		time_t t;
#endif
		off_t z;
	} u;
};
static struct DSR *dsr_list;

// compare routine for quicksort directory scan, size or time
static int dircmp_st(const void *s, const void *t)
{
	const struct DSR *q = s;
	const struct DSR *r = t;
	int rc = 0;
	if (ls_sort == 1) {
		rc = 0;
		if (q->u.z < r->u.z)
			rc = -1;
		if (q->u.z > r->u.z)
			rc = 1;
	}
	if (ls_sort == 2) {
		rc = 0;
#ifdef linux
		if (q->u.spec.tv_sec < r->u.spec.tv_sec)
			rc = -1;
		if (q->u.spec.tv_sec > r->u.spec.tv_sec)
			rc = 1;
		if(!rc) {
// Honor sub-second timestamp precision if the operating system supports it.
		if (q->u.spec.tv_nsec < r->u.spec.tv_nsec)
			rc = -1;
		if (q->u.spec.tv_nsec > r->u.spec.tv_nsec)
			rc = 1;
		}
#else
		if (q->u.t < r->u.t)
			rc = -1;
		if (q->u.t > r->u.t)
			rc = 1;
#endif
	}
	if (ls_reverse)
		rc = -rc;
	return rc;
}

// Read the contents of a directory into the current buffer
bool readDirectory(const char *filename, int endline, char cmd, struct lineMap **map_p)
{
	int len, j, linecount;
	char *v;
	char *dmap = 0;
	struct lineMap *mptr;
	struct lineMap *backpiece = 0;
	uchar innersort = (dno ? 0 : ls_sort);
	bool innerrev = (dno ? false : ls_reverse);

	bool has_newlines = false;
	cw->baseDirName = cloneString(filename);
// get rid of trailing slash
	len = strlen(cw->baseDirName);
	if (len && cw->baseDirName[len - 1] == '/')
		cw->baseDirName[len - 1] = 0;
/* Understand that the empty string now means / */

/* get the files, or fail if there is a problem */
	if (!sortedDirList
	    (filename, map_p, &linecount, innersort, innerrev))
		return false;
	if (!cw->dol) {
		cw->dirMode = true;
		cw->dnoMode = dno;
		dmap = allocZeroMem(linecount * DTSIZE);
		if(debugLevel >= 1)
			i_puts(MSG_DirMode);
		if (lsformat[0])
			backpiece = allocZeroMem(LMSIZE * (linecount + 2));
	}

	if (!linecount) {	/* empty directory */
		cw->dot = endline;
		fileSize = 0;
		free(*map_p);
		*map_p = 0;
		nzFree(backpiece);
		nzFree(dmap);
		goto success;
	}

	if (innersort)
		dsr_list = allocZeroMem(sizeof(struct DSR) * linecount);

// change 0 to nl and count bytes
	fileSize = 0;
	mptr = *map_p;
	for (j = 0; j < linecount; ++j, ++mptr) {
		char c, ftype;
		pst t = mptr->text;
		char *abspath = makeAbsPath((char *)t);

// make sure this gets done.
		if (backpiece)
			backpiece[j + 1].text = (uchar *) emptyString;

		while (*t) {
			if (*t == '\n')
				*t = '\t', has_newlines = true;
			++t;
		}
		*t = '\n';
		len = t - mptr->text;
		fileSize += len + 1;
		if (innersort)
			dsr_list[j].idx = j;
		if (!abspath) continue;

		ftype = fileTypeByName(abspath, 2);
		if (!ftype)
			continue;
		if (isupperByte(ftype)) {	/* symbolic link */
			if (!cw->dirMode)
				*t = '@', *++t = '\n';
			else
				dmap[j*DTSIZE] = '@';
			++fileSize;
		}
		ftype = tolower(ftype);
		c = 0;
		if (ftype == 'd') c = '/';
		if (ftype == 's') c = '^';
		if (ftype == 'c') c = '<';
		if (ftype == 'b') c = '*';
		if (ftype == 'p') c = '|';
		if (c) {
			if (!cw->dirMode)
				*t = c, *++t = '\n';
			else if (dmap[j*DTSIZE])
				dmap[DTSIZE*j + 1] = c;
			else
				dmap[DTSIZE*j] = c;
			++fileSize;
		}
// If sorting a different way, get the attribute.
		if (innersort) {
			if (innersort == 1)
				dsr_list[j].u.z = this_stat.st_size;
			if (innersort == 2) {
// Honor sub-second timestamp precision if the operating system supports it.
// Currently only linux - other systems?
#ifdef linux
				dsr_list[j].u.spec = this_stat.st_mtim;
#else
				dsr_list[j].u.t = this_stat.st_mtime;
#endif
			}
		}

/* extra stat entries on the line */
		if (!lsformat[0] || dno)
			continue;
		v = lsattr(abspath, lsformat);
		if (!*v)
			continue;
		len = strlen(v);
		fileSize += len + 1;
		if (cw->dirMode) {
			backpiece[j + 1].text = (uchar *) cloneString(v);
		} else {
/* have to realloc at this point */
			int l1 = t - mptr->text;
			mptr->text = reallocMem(mptr->text, l1 + len + 3);
			t = mptr->text + l1;
			*t++ = ' ';
			strcpy((char *)t, v);
			t += len;
			*t++ = '\n';
			*t = 0;
		}
	}			// loop fixing files in the directory scan
	if(has_newlines)
		i_puts(MSG_WarnNewlines);

	if (innersort) {
		struct lineMap *tmp;
		char *dmap2;
		qsort(dsr_list, linecount, sizeof(struct DSR), dircmp_st);
// Now I have to remap everything.
		tmp = allocMem(LMSIZE * linecount);
		if(dmap) dmap2 = allocZeroMem(DTSIZE * linecount);
		for (j = 0; j < linecount; ++j) {
			tmp[j] = (*map_p)[dsr_list[j].idx];
			if(!dmap) continue;
			dmap2[DTSIZE*j] = dmap[DTSIZE*dsr_list[j].idx];
			dmap2[DTSIZE*j + 1] = dmap[DTSIZE*dsr_list[j].idx + 1];
		}
		free(*map_p);
		*map_p = tmp;
		if(dmap) free(dmap), dmap = dmap2;
		if (backpiece) {
			tmp = allocMem(LMSIZE * (linecount + 2));
			for (j = 0; j < linecount; ++j)
				tmp[j + 1] = backpiece[dsr_list[j].idx + 1];
			memset(tmp, 0, LMSIZE);
			memset(tmp + linecount + 1, 0, LMSIZE);
			free(backpiece);
			backpiece = tmp;
		}
		free(dsr_list);
	}

	addToMap(linecount, endline);
	cw->r_map = backpiece;
	if(dmap) {
		cw->dmap = allocMem((linecount + 1) * DTSIZE);
		memcpy(cw->dmap + DTSIZE, dmap, linecount*DTSIZE);
		free(dmap);
	}

success:
	if (cmd == 'r')
		debugPrint(1, "%lld", fileSize);
	return true;
}

/*********************************************************************
Delete a file or entity. This is the action of d in directory mode.
The only time we can't unlik is when it's a directory tree;
at that point it's easiest to call rm -rf.
Otherwise try unlink, because we get better error messages from strerror.
If dx is not set, we move to trash.
Try rename first, if that doesn't fly because of different filesystems,
then system mv, which will copy it over then delete.
We don't move special files to trash, there isn't much point in that.
path is resolved, either absolute, or relative to the current directory.
*********************************************************************/

static bool delFile(const char *file, const char *path)
{
	uchar action = dirWrite;
// We shouldn't even be here if action is 0, readonly; check anyways
	if(!action) return false;
	char ftype = fileTypeByName(path, 1);
	if(ftype != 'd' && ftype != 'f') // special file
		action = 2; // hard delete
	char *a;
	int j;
	char qc = '\''; // quote character, in case we need a system call
// check formeta chars in path
	if(strchr(path, qc)) {
		qc = '"';
		if(strpbrk(path, "\"$`") ||
// \\ would get squashed by the shell
		strstr(path, "\\\\") ||
// \ at the end escapes "
		(path[0] && path[strlen(path) - 1] == '\\'))
			qc = 0;
	}

	if (action == 2 && ftype == 'd') {
// delete a directory tree
		if(!qc) {
			setError(MSG_MetaChar);
			return false;
		}
		createFormattedString(&a, "rm -rf %c%s%c",
		qc, path, qc);
			j = system(a);
		free(a);
		if(j) {
			setError(MSG_NoDirDelete);
			return false;
		}
		return true;
	}

	if(action == 2) { // hard delete
		if (unlink(path)) {
			setError(MSG_NoRemove, file, strerror(errno));
			return false;
		}
		return true;
	}

// move to trash
	char bin[ABSPATH];
	sprintf(bin, "%s/%s", recycleBin, file);
	if (!rename(path, bin))
		return true;
// Oops, something went wrong
	if (errno == EXDEV) {
// Another filesystem, let mv do the work
		if(!qc || strchr(bin, qc) ||
		(qc == '"' && strpbrk(bin, "\\$`"))) {
			setError(MSG_MetaChar);
			return false;
		}
		createFormattedString(&a, "mv %c%s%c %c%s%c",
		qc, path, qc, qc, bin, qc);
		j = system(a);
		free(a);
		if(j) {
			setError(MSG_MoveFileSystem , path);
			return false;
		}
		return true;
	}
// some other rename error
	setError(MSG_NoMoveToTrash, file, strerror(errno));
	return false;
}

// Delete files from a directory as you delete lines.
// Set dw to move them to your recycle bin.
// Set dx to delete them outright.
bool delFiles(int start, int end, bool withtext, char origcmd, char *cmd_p)
{
	if (!dirWrite) {
		setError(MSG_DirNoWrite);
		return false;
	}

	if (dirWrite == 1 && !recycleBin) {
		setError(MSG_NoRecycle);
		return false;
	}

	if(end < start) return true;
	*cmd_p = 'e';		// show errors

	int ln;
	for (ln = start; ln <= end; ++ln) {
		char *file, *t, *path, *ftype;
		file = (char *)fetchLine(ln, 0);
		t = strchr(file, '\n');
		if (!t)
			i_printfExit(MSG_NoNlOnDir, file);
		*t = 0;
		if(stringEqual(file, "..")) {
			setError(MSG_DelDotDot);
			goto abort;
		}
		path = makeAbsPath(file);
		if (!path) {
abort:
			free(file);
			if (ln != start && withtext)
				delText(start, ln - 1);
			return false;
		}

		ftype = dirSuffix2(ln, path);
		if (dirWrite == 2 || (*ftype && strchr("@<*^|", *ftype)))
			debugPrint(1, "%s%s ↓", file, ftype);
		else
			debugPrint(1, "%s%s → 🗑", file, ftype);
		if(!delFile(file, path))
			goto abort;
		free(file);
	}
	if(withtext) delText(start, end);

// if you type D instead of d, I don't want to lose that.
	*cmd_p = origcmd;
	return true;
}

// Move or copy files from one directory to another
bool moveFiles(int start, int end, int dest, char origcmd, char relative)
{
	Window *cw1 = cw, *cw2 = 0, *w;
	char *path1, *path2;
	int ln, cnt, dol;
	bool fileExists = false;

	if (!dirWrite) {
		setError(MSG_DirNoWrite);
		return false;
	}

	if(!relative) {
				if (dest == 0) {
					setError(MSG_Session0);
					return false;
				}
				if (dest == context) {
					setError(MSG_SessionCurrent, dest);
					return false;
				}
				if (dest > maxSession || !(cw2 = sessionList[dest].lw) || !cw2->dirMode) {
					char numstring[12];
					sprintf(numstring, "%d", dest);
					setError(MSG_NotDir, numstring);
					return false;
				}
	}

	if(relative && !dest) {
		setError(MSG_NoChange);
		return false;
	}

	if(relative == '+') {
		for(cnt = 0, w = cw; w; w = w->prev, ++cnt) ;
		if(dest >= cnt) {
			setError(MSG_NoUp);
			return false;
		}
		for(w = cw; dest; w = w->prev, --dest)  ;
		cw2 = w;
	}

	if(relative == '-') {
		for(cnt = 0, w = cs->lw2; w; w = w->prev, ++cnt) ;
		if(dest > cnt) {
			setError(MSG_NoDown);
			return false;
		}
		cnt -= dest;
		for(w = cs->lw2; cnt; w = w->prev, --cnt)  ;
		cw2 = w;
	}

	ln = start;
	cnt = end - start + 1;
	while (cnt--) {
		char *file, *t, *ftype;
		file = (char *)fetchLine(ln, 0);
		t = strchr(file, '\n');
		if (!t)
			i_printfExit(MSG_NoNlOnDir, file);
		*t = 0;
		path1 = makeAbsPath(file);
		if (!path1) {
			free(file);
			return false;
		}
		path1 = cloneString(path1);
		ftype = dirSuffix2(ln, path1);

		debugPrint(1, "%s%s %s %s",
		file, ftype, (origcmd == 'm' ? "→" : "≡"), cw2->baseDirName);

		cw = cw2;
		path2 = makeAbsPath(file);
		cw = cw1;
		if (!path2) {
			free(file);
			free(path1);
			return false;
		}

		if (!access(path2, 0)) {
			if(!displaceFiles) {
				setError(MSG_DestFileExists, path2);
				free(file);
				free(path1);
				return false;
			}
			if(!delFile(file, path2)) {
				free(file);
				free(path1);
				return false;
			}
			fileExists = true;
		}

		if(tolower(origcmd) == 'l') {
			if((origcmd == 'l' && link(path1, path2)) ||
			(origcmd == 'L' && symlink(path1, path2))) {
				setError(MSG_LinkError, file, strerror(errno));
				free(file);
			free(path1);
				return false;
			}
			goto moved;
		}

		errno = EXDEV;
		if (origcmd == 't' || rename(path1, path2)) {
			if (errno == EXDEV) {
				char *rmbuf;
				int rmlen, j;
				if (*ftype || fileSizeByName(path1) > 200000000) {
// let mv or cp do the work
					char *a, qc = '\'';
					if(strchr(path1, qc) || strchr(path2, qc)) {
						qc = '"';
						if(strpbrk(path1, "\"$`") ||
						strpbrk(path2, "\"$`") ||
						strstr(path1, "\\\\") ||
						strstr(path2, "\\\\") ||
						(path1[0] && path1[strlen(path1) - 1] == '\\') ||
						(path2[0] && path2[strlen(path2) - 1] == '\\'))
							qc = 0;
					}
					if(!qc) {
						setError(MSG_MetaChar);
						free(file);
						free(path1);
						return false;
					}
					createFormattedString(&a, "%s %c%s%c %c%s%c",
					(origcmd == 'm' ? "mv -n" : "cp -an"),
					qc, path1, qc, qc, cw2->baseDirName, qc);
					j = system(a);
					free(a);
					if(j) {
						setError((origcmd == 'm' ? MSG_MoveFileSystem : MSG_CopyFail), file);
						free(file);
						free(path1);
						return false;
					}
					if(origcmd == 'm')
						unlink(path1);
					goto moved;
				}
// A small file, copy it ourselves.
				if (!fileIntoMemory(path1, &rmbuf, &rmlen, 0)) {
					free(file);
					free(path1);
					return false;
				}
				if (!memoryOutToFile(path2, rmbuf, rmlen)) {
					free(file);
					free(path1);
					nzFree(rmbuf);
					return false;
				}

// reset the time stamp, cause that's what cp does
				struct stat buf;
				if (!stat(path1, &buf)) {
// we can march on if the time isn't adjusted
#if defined(__APPLE__)
					struct timespec times[2] = {buf.st_atimespec, buf.st_mtimespec};
#else
					struct timespec times[2] = {buf.st_atim, buf.st_mtim};
#endif
					utimensat(AT_FDCWD, path2, times, 0);
				}

				nzFree(rmbuf);
				if(origcmd == 'm')
					unlink(path1);
				goto moved;
			}

			setError(MSG_MoveError, file, strerror(errno));
			free(file);
			free(path1);
			return false;
		}

moved:
		free(path1);
		if(origcmd == 'm')
			delText(ln, ln);
		else
			cw->dot = ln++;
// add it to the other directory, if we didn't displace
		if(fileExists) { free(file); continue; }
		cw = cw2;
		dol = cw->dol;
		*t++ = '\n';
		addTextToBuffer((pst)file, t-file, dol, false);
		free(file);
		cw->dot = ++dol;
		if(cw->dmap)
			cw->dmap = reallocMem(cw->dmap, DTSIZE * (dol + 1));
		else
			cw->dmap = allocZeroMem(DTSIZE * (dol + 1));
		memset(cw->dmap + DTSIZE*dol, 0, DTSIZE);
		if(origcmd == 'L') { // creating symbolic link
			cw->dmap[DTSIZE*dol] = '@';
			if(ftype[0] && ftype[0] != '@')
				cw->dmap[DTSIZE*dol + 1] = ftype[0];
		} else {
			cw->dmap[DTSIZE*dol] = ftype[0];
			if(ftype[0])
				cw->dmap[DTSIZE*dol + 1] = ftype[1];
		}
// if attributes were displayed in that directory - more work to do.
// I just leave a space for them; I don't try to derive them.
		if(cw->r_map) {
			cw->r_map = reallocMem(cw->r_map, LMSIZE * (dol + 2));
			memset(cw->r_map + dol, 0, LMSIZE*2);
			cw->r_map[dol].text = (uchar*)emptyString;
		}
		cw = cw1; // go back to original window
	}

	return true;
}

/* Expand environment variables, then wild cards.
 * But the result should be one and only one file.
 * Return the new expanded line.
 * Neither the original line nore the new line is allocated.
 * They are static char buffers that are just plain long enough. */

static bool envExpand(const char *line, const char **expanded)
{
	const char *s;
	char *t;
	const char *v;		/* result of getenv call */
	bool inbrace;		// ${foo}
	bool eb_expanded; // our special variables like EB_FILE
	struct passwd *pw;
	const char *udir;	/* user directory */
	int l;
	static char varline[ABSPATH];
	char var1[40];

// quick check
	if (line[0] != '~' && !strchr(line, '$')) {
		*expanded = line;
		return true;
	}

// ok, need to crunch along
	t = varline;
	s = line;

	if (line[0] != '~')
		goto dollars;

	l = 0;
	for (s = line + 1; isalnumByte(*s) || *s == '_'; ++s)
		++l;
	if ((unsigned)l >= sizeof(var1) || isdigitByte(line[1]) || (*s && *s != '/')) {
/* invalid syntax, put things back */
		s = line;
		goto dollars;
	}

	udir = 0;
	copyString(var1, line + 1, l + 1);
	if (l) {
		pw = getpwnam(var1);
		if (!pw) {
			setError(MSG_NoTilde, var1);
			return false;
		}
		if (pw->pw_dir && *pw->pw_dir)
			udir = pw->pw_dir;
	} else
		udir = home;
	if (!udir) {
		s = line;
		goto dollars;
	}
	l = strlen(udir);
	if ((unsigned)l >= sizeof(varline))
		goto longline;
	strcpy(varline, udir);
	t = varline + l;

dollars:
	for (; *s; ++s) {
		if (t - varline == ABSPATH - 1) {
longline:
			setError(MSG_ShellLineLong);
			return false;
		}
		if (*s == '\\' && s[1] == '$') {
// this $ is escaped
			++s;
appendchar:
			*t++ = *s;
			continue;
		}
		if (*s != '$')
			goto appendchar;

// this is $, see if it is $var or ${var}
		inbrace = false;
		v = s + 1;
		if (*v == '{')
			inbrace = true, ++v;
		if (!isalphaByte(*v) && *v != '_')
			goto appendchar;
		l = 0;
		while (isalnumByte(*v) || *v == '_') {
			if (l == sizeof(var1) - 1)
				goto longline;
			var1[l++] = *v++;
		}
		var1[l] = 0;
		if (inbrace) {
			if (*v != '}')
				goto appendchar;
			++v;
		}
		s = v - 1;
		if(!eb_expanded) {
			eb_variables();
			eb_expanded = true;
		}
		v = getenv(var1);
		if (!v) {
			setError(MSG_NoEnvVar, var1);
			return false;
		}
		l = strlen(v);
		if (t - varline + l >= ABSPATH)
			goto longline;
		strcpy(t, v);
		t += l;
	}
	*t = 0;

	*expanded = varline;
	return true;
}

bool envFile(const char *line, const char **expanded)
{
	static char line2[ABSPATH];
	const char *varline;
	const char *s;
	char *t;
	glob_t g;
	int rc, flags;

/* ` disables this stuff */
/* but `` is a literal ` */
	if (line[0] == '`') {
		if (line[1] != '`') {
			*expanded = line + 1;
			return true;
		}
		++line;
	}

	if (!envExpand(line, &varline))
		return false;

/* expanded the environment variables, if any, now time to glob */
	flags = GLOB_NOSORT;
	rc = glob(varline, flags, NULL, &g);

	if (rc == GLOB_NOMATCH) {
/* unescape the metas */
		t = line2;
		for (s = varline; *s; ++s) {
			if (*s == '\\' && s[1] && strchr("*?[", s[1]))
				++s;
			*t++ = *s;
		}
		*t = 0;
		*expanded = line2;
		return true;
	}

	if (rc) {
/* some other syntax error, whereup we can't expand. */
		setError(MSG_ShellExpand);
		globfree(&g);
		return false;
	}

	if (g.gl_pathc != 1) {
		setError(MSG_ShellManyMatch);
		globfree(&g);
		return false;
	}

/* looks good, if it isn't too long */
	s = g.gl_pathv[0];
	if (strlen(s) >= sizeof(line2)) {
		setError(MSG_ShellLineLong);
		globfree(&g);
		return false;
	}

	strcpy(line2, s);
	globfree(&g);
// if you pass dir///// and it matches a directory, glob() trims off
// all the slashes, except one. Let's finish the job.
	t = line2 + strlen(line2) - 1;
	while(t > line2 && *t == '/') *t-- = 0;

	*expanded = line2;
	return true;
}

// Like the above, but string is allocated, and errors are printed
// This is only used to expand pathnames in .ebrc
static char *envFileAlloc0(const char *path)
{
	const char *path2;
	if(envFile(path, &path2))
		return cloneString(path2);
	showError();
	setError(-1);
	return 0;
}

// Like the above, but a base is given,
// in case the path is relative.
// This is only used to expand pathnames in .ebrc
char *envFileAlloc(const char *base, const char *path)
{
// absolute path first, or perhaps we are expanding a shell variable.
// If the latter, I assume the variable contains an absolute path.
	if(path[0] == '/' ||
	(path[0] == '~' && path[1] == '/') ||
	path[0] == '$')
		return envFileAlloc0(path);
	char *mark = strrchr(base, '/');
	if(!mark) // no slash to glom on to
		return envFileAlloc0(path);
	++mark;
	int l = mark - base; // length of directory
	int l2 = l + strlen(path);
	char *path2 = allocMem(l2 + 1);
	memcpy(path2, base, l);
	strcpy(path2 + l, path);
	char *path3 = envFileAlloc0(path2);
	free(path2);
	return path3;
}

/* Call the above routine if filename contains a  slash,
 * or prepend the download directory if it does not.
 * If there is no download directory then always expand as above. */
bool envFileDown(const char *line, const char **expanded)
{
	static char line2[MAXTTYLINE];
        int total;

	if (!downDir || strchr(line, '/'))
/* we don't necessarily expect there to be a file here */
		return envFile(line, expanded);

        total = snprintf(line2, sizeof(line2), "%s/%s", downDir, line);
	if (total == sizeof(line2) || total < 0) {
		setError(MSG_ShellLineLong);
		return false;
        }

	*expanded = line2;
	return true;
}

static int include_cmp(const void *s, const void *t)
{
	return strcmp(*(const char**)s, *(const char**)t);
}

// create a list of include directives, for the files in a given directory.
// This is only used to include files in .ebrc
char *includeList(const char *dir)
{
	char *s;
	int s_l, cnt = 0;
	s = initString(&s_l);
	bool save_hf = showHiddenFiles;
	showHiddenFiles = false;
	const char *filename;
	while((filename = nextScanFile(dir))) {
		stringAndString(&s, &s_l, "include = ");
		stringAndString(&s, &s_l, filename);
		stringAndChar(&s, &s_l, 0);
		++cnt;
	}
	showHiddenFiles = save_hf;
	if(!cnt) return s; // no files
// sort - just so the order is predictable
	const char ** a = allocMem(cnt * sizeof(const char*));
	const char *t = s;
	int i;
	for(i = 0; i < cnt; ++i) {
		a[i] = t;
		t = t + strlen(t) + 1;
	}
	qsort(a, cnt, sizeof(const char*), include_cmp);
// now put the string back together
	char *u;
	int u_l;
	u = initString(&u_l);
	for(i = 0; i < cnt; ++i) {
		stringAndString(&u, &u_l, a[i]);
		stringAndChar(&u, &u_l, '\n');
	}
	nzFree(s);
	return u;
}

FILE *efopen(const char *name, const char *mode)
{
	FILE *f;

	if (name[0] == '-' && name[1] == 0) {
		if (*mode == 'r')
			return stdin;
		if (*mode == 'w' || *mode == 'a')
			return stdout;
	}

	f = fopen(name, mode);
	if (f)
		return f;

	if (*mode == 'r')
		i_printfExit(MSG_NoOpen, name, strerror(errno));
	else if (*mode == 'w' || *mode == 'a')
		i_printfExit(MSG_CreateFail, name);
	else
		i_printfExit(MSG_InvalidFopen, mode);
	return 0;
}

int eopen(const char *name, int mode, int perms)
{
	int fd;
	fd = open(name, mode, perms);
	if (fd >= 0)
		return fd;
	if (mode & O_WRONLY)
		i_printfExit(MSG_CreateFail, name);
	else
		i_printfExit(MSG_NoOpen, name, strerror(errno));
	return -1;
}

void appendFile(const char *fname, const char *message, ...)
{
	FILE *f;
	va_list p;
	f = efopen(fname, "ae");
	va_start(p, message);
	vfprintf(f, message, p);
	va_end(p);
	fprintf(f, "\n");
	fclose(f);
}

/* like the above, but no formatting */
void appendFileNF(const char *filename, const char *msg)
{
	FILE *f = efopen(filename, "ae");
	fprintf(f, "%s\n", msg);
	fclose(f);
}

/* Wrapper around system(). */
int eb_system(const char *cmd, bool print_on_success)
{
	int system_ret = system(cmd);
	if (system_ret != -1) {
		if (print_on_success && debugLevel > 0 && !system_ret)
			i_puts(MSG_OK);
		if(system_ret) setError(MSG_CmdFail);
	} else {
		i_printf(MSG_SystemCmdFail, system_ret);
		nl();
		setError(MSG_CmdFail);
	}
	return system_ret;
}
