1 /* stringfile.c: manage strings, files, and directories for edbrowse.
2 * This file is part of the edbrowse project, released under GPL.
3  */
4 
5 #include "eb.h"
6 
7 #include <dirent.h>
8 #ifdef DOSLIKE
9 #include <dos.h>
10 #else
11 #include <termios.h>
12 #include <unistd.h>
13 #include <glob.h>
14 #include <pwd.h>
15 #include <grp.h>
16 #endif
17 
18 char emptyString[] = "";
19 bool showHiddenFiles, isInteractive;
20 int debugLevel = 1;
21 bool debugClone, debugEvent, debugThrow, debugCSS;
22 FILE *debugFile = NULL;
23 char *debugFileName;
24 bool demin = false;
25 bool gotimers = true;
26 bool uvw;
27 char *downDir, *home;
28 
29 /*********************************************************************
30 Allocate and manage memory.
31 Allocate and copy strings.
32 If we're out of memory, the program aborts.  No error legs.
33 Soooooo much easier! With 32gb of RAM, we shouldn't run out.
34 *********************************************************************/
35 
allocMem(size_t n)36 void *allocMem(size_t n)
37 {
38 	void *s;
39 	if (!n)
40 		return emptyString;
41 	if (!(s = malloc(n)))
42 		i_printfExit(MSG_MemAllocError, n);
43 	return s;
44 }				/* allocMem */
45 
allocZeroMem(size_t n)46 void *allocZeroMem(size_t n)
47 {
48 	void *s;
49 	if (!n)
50 		return emptyString;
51 	if (!(s = calloc(n, 1)))
52 		i_printfExit(MSG_MemCallocError, n);
53 	return s;
54 }				/* allocZeroMem */
55 
reallocMem(void * p,size_t n)56 void *reallocMem(void *p, size_t n)
57 {
58 	void *s;
59 	if (!n)
60 		i_printfExit(MSG_ReallocP);
61 	if (!p)
62 		i_printfExit(MSG_Realloc0, n);
63 	if (p == emptyString) {
64 		p = allocMem(n);
65 // keep the null byte that was present in emptyString.
66 // fileIntoMemory() needs this to keep null on the end of an empty file
67 // that was just read into memory.
68 		*(char *)p = 0;
69 	}
70 	if (!(s = realloc(p, n)))
71 		i_printfExit(MSG_ErrorRealloc, n);
72 	return s;
73 }				/* reallocMem */
74 
75 /* When you know the allocated thing is a string. */
allocString(size_t n)76 char *allocString(size_t n)
77 {
78 	return (char *)allocMem(n);
79 }				/* allocString */
80 
allocZeroString(size_t n)81 char *allocZeroString(size_t n)
82 {
83 	return (char *)allocZeroMem(n);
84 }				/* allocZeroString */
85 
reallocString(void * p,size_t n)86 char *reallocString(void *p, size_t n)
87 {
88 	return (char *)reallocMem(p, n);
89 }				/* reallocString */
90 
nzFree(void * s)91 void nzFree(void *s)
92 {
93 	if (s && s != emptyString)
94 		free(s);
95 }				/* nzFree */
96 
97 /* some compilers care whether it's void * or const void * */
cnzFree(const void * v)98 void cnzFree(const void *v)
99 {
100 	nzFree((void *)v);
101 }				/* cnzFree */
102 
appendString(char * s,const char * p)103 char *appendString(char *s, const char *p)
104 {
105 	int slen = strlen(s);
106 	int plen = strlen(p);
107 	s = reallocString(s, slen + plen + 1);
108 	strcpy(s + slen, p);
109 	return s;
110 }				/* appendstring */
111 
prependString(char * s,const char * p)112 char *prependString(char *s, const char *p)
113 {
114 	int slen = strlen(s);
115 	int plen = strlen(p);
116 	char *t = allocString(slen + plen + 1);
117 	strcpy(t, p);
118 	strcpy(t + plen, s);
119 	nzFree(s);
120 	return t;
121 }				/* prependstring */
122 
skipWhite(const char ** s)123 void skipWhite(const char **s)
124 {
125 	const char *t = *s;
126 	while (isspaceByte(*t))
127 		++t;
128 	*s = t;
129 }				/* skipWhite */
130 
trimWhite(char * s)131 void trimWhite(char *s)
132 {
133 	int l;
134 	if (!s)
135 		return;
136 	l = strlen(s);
137 	while (l && isspaceByte(s[l - 1]))
138 		--l;
139 	s[l] = 0;
140 }				/* trimWhite */
141 
stripWhite(char * s)142 void stripWhite(char *s)
143 {
144 	const char *t = s;
145 	skipWhite(&t);
146 	if (t > s)
147 		strmove(s, t);
148 	trimWhite(s);
149 }				/* stripWhite */
150 
151 /* compress white space */
spaceCrunch(char * s,bool onespace,bool unprint)152 void spaceCrunch(char *s, bool onespace, bool unprint)
153 {
154 	int i, j;
155 	char c;
156 	bool space = true;
157 	for (i = j = 0; (c = s[i]); ++i) {
158 		if (isspaceByte(c)) {
159 			if (!onespace)
160 				continue;
161 			if (!space)
162 				s[j++] = ' ', space = true;
163 			continue;
164 		}
165 		if (unprint && !isprintByte(c))
166 			continue;
167 		s[j++] = c, space = false;
168 	}
169 	if (space && j)
170 		--j;		/* drop trailing space */
171 	s[j] = 0;
172 }				/* spaceCrunch */
173 
174 /* Like strcpy, but able to cope with overlapping strings. */
strmove(char * dest,const char * src)175 char *strmove(char *dest, const char *src)
176 {
177 	return (char *)memmove(dest, src, strlen(src) + 1);
178 }				/* strmove */
179 
180 /* OO has a lot of unnecessary overhead, and a few inconveniences,
181  * but I really miss it right now.  The following
182  * routines make up for the lack of simple string concatenation in C.
183  * The string space allocated is always a power of 2 - 1, starting with 1.
184  * Each of these routines puts an extra 0 on the end of the "string". */
185 
initString(int * l)186 char *initString(int *l)
187 {
188 	*l = 0;
189 	return emptyString;
190 }
191 
192 /* String management routines realloc to one less than a power of 2 */
stringAndString(char ** s,int * l,const char * t)193 void stringAndString(char **s, int *l, const char *t)
194 {
195 	char *p = *s;
196 	int oldlen, newlen, x;
197 	oldlen = *l;
198 	newlen = oldlen + strlen(t);
199 	*l = newlen;
200 	++newlen;		/* room for the 0 */
201 	x = oldlen ^ newlen;
202 	if (x > oldlen) {	/* must realloc */
203 		newlen |= (newlen >> 1);
204 		newlen |= (newlen >> 2);
205 		newlen |= (newlen >> 4);
206 		newlen |= (newlen >> 8);
207 		newlen |= (newlen >> 16);
208 		p = reallocString(p, newlen);
209 		*s = p;
210 	}
211 	strcpy(p + oldlen, t);
212 }				/* stringAndString */
213 
stringAndBytes(char ** s,int * l,const char * t,int cnt)214 void stringAndBytes(char **s, int *l, const char *t, int cnt)
215 {
216 	char *p = *s;
217 	int oldlen, newlen, x;
218 	oldlen = *l;
219 	newlen = oldlen + cnt;
220 	*l = newlen;
221 	++newlen;
222 	x = oldlen ^ newlen;
223 	if (x > oldlen) {	/* must realloc */
224 		newlen |= (newlen >> 1);
225 		newlen |= (newlen >> 2);
226 		newlen |= (newlen >> 4);
227 		newlen |= (newlen >> 8);
228 		newlen |= (newlen >> 16);
229 		p = reallocString(p, newlen);
230 		*s = p;
231 	}
232 	memcpy(p + oldlen, t, cnt);
233 	p[oldlen + cnt] = 0;
234 }				/* stringAndBytes */
235 
stringAndChar(char ** s,int * l,char c)236 void stringAndChar(char **s, int *l, char c)
237 {
238 	char *p = *s;
239 	int oldlen, newlen, x;
240 	oldlen = *l;
241 	newlen = oldlen + 1;
242 	*l = newlen;
243 	++newlen;
244 	x = oldlen ^ newlen;
245 	if (x > oldlen) {	/* must realloc */
246 		newlen |= (newlen >> 1);
247 		newlen |= (newlen >> 2);
248 		newlen |= (newlen >> 4);
249 		newlen |= (newlen >> 8);
250 		newlen |= (newlen >> 16);
251 		p = reallocString(p, newlen);
252 		*s = p;
253 	}
254 	p[oldlen] = c;
255 	p[oldlen + 1] = 0;
256 }				/* stringAndChar */
257 
stringAndNum(char ** s,int * l,int n)258 void stringAndNum(char **s, int *l, int n)
259 {
260 	char a[16];
261 	sprintf(a, "%d", n);
262 	stringAndString(s, l, a);
263 }				/* stringAndNum */
264 
265 /* 64M 16K etc */
stringAndKnum(char ** s,int * l,int n)266 void stringAndKnum(char **s, int *l, int n)
267 {
268 	char a[16];
269 	if (n && n / (1024 * 1024) * (1024 * 1024) == n)
270 		sprintf(a, "%dM", n / (1024 * 1024));
271 	else if (n && n / 1024 * 1024 == n)
272 		sprintf(a, "%dK", n / 1024);
273 	else
274 		sprintf(a, "%d", n);
275 	stringAndString(s, l, a);
276 }				/* stringAndKnum */
277 
cloneString(const char * s)278 char *cloneString(const char *s)
279 {
280 	char *t;
281 	unsigned len;
282 
283 	if (!s)
284 		return 0;
285 	if (!*s)
286 		return emptyString;
287 	len = strlen(s) + 1;
288 	t = allocString(len);
289 	strcpy(t, s);
290 	return t;
291 }				/* cloneString */
292 
cloneMemory(const char * s,int n)293 char *cloneMemory(const char *s, int n)
294 {
295 	char *t = allocString(n);
296 	if (n)
297 		memcpy(t, s, n);
298 	return t;
299 }				/* cloneMemory */
300 
leftClipString(char * s)301 void leftClipString(char *s)
302 {
303 	char *t;
304 	if (!s)
305 		return;
306 	for (t = s; *t; ++t)
307 		if (!isspace(*t))
308 			break;
309 	if (t > s)
310 		strmove(s, t);
311 }				/* leftClipString */
312 
313 /* shift string one to the right */
shiftRight(char * s,char first)314 void shiftRight(char *s, char first)
315 {
316 	int l = strlen(s);
317 	for (; l >= 0; --l)
318 		s[l + 1] = s[l];
319 	s[0] = first;
320 }				/* shiftRight */
321 
Cify(const char * s,int n)322 char *Cify(const char *s, int n)
323 {
324 	char *u;
325 	char *t = allocString(n + 1);
326 	if (n)
327 		memcpy(t, s, n);
328 	for (u = t; u < t + n; ++u)
329 		if (*u == 0)
330 			*u = ' ';
331 	*u = 0;
332 	return t;
333 }				/* Cify */
334 
335 /* pull a substring out of a larger string,
336  * and make it its own allocated string */
pullString(const char * s,int l)337 char *pullString(const char *s, int l)
338 {
339 	char *t;
340 	if (!l)
341 		return emptyString;
342 	t = allocString(l + 1);
343 	memcpy(t, s, l);
344 	t[l] = 0;
345 	return t;
346 }				/* pullString */
347 
pullString1(const char * s,const char * t)348 char *pullString1(const char *s, const char *t)
349 {
350 	return pullString(s, t - s);
351 }
352 
353 /* return the number, if string is a number, else -1 */
stringIsNum(const char * s)354 int stringIsNum(const char *s)
355 {
356 	int n;
357 	if (!isdigitByte(s[0]))
358 		return -1;
359 	n = strtol(s, (char **)&s, 10);
360 	if (*s)
361 		return -1;
362 	return n;
363 }				/* stringIsNum */
364 
stringIsDate(const char * s)365 bool stringIsDate(const char *s)
366 {
367 	if (!isdigit(*s))
368 		return false;
369 	++s;
370 	if (isdigit(*s))
371 		++s;
372 	if (*s++ != '/')
373 		return false;
374 	if (!isdigit(*s))
375 		return false;
376 	++s;
377 	if (isdigit(*s))
378 		++s;
379 	if (*s++ != '/')
380 		return false;
381 	if (!isdigit(*s))
382 		return false;
383 	++s;
384 	if (isdigit(*s))
385 		++s;
386 	if (isdigit(*s))
387 		++s;
388 	if (isdigit(*s))
389 		++s;
390 	if (*s)
391 		return false;
392 	return true;
393 }				/* stringIsDate */
394 
stringIsFloat(const char * s,double * dp)395 bool stringIsFloat(const char *s, double *dp)
396 {
397 	const char *t;
398 	*dp = strtod(s, (char **)&t);
399 	if (*t)
400 		return false;	/* extra stuff at the end */
401 	return true;
402 }				/* stringIsFloat */
403 
memEqualCI(const char * s,const char * t,int len)404 bool memEqualCI(const char *s, const char *t, int len)
405 {
406 	char c, d;
407 	if (s == t)
408 		return true;
409 	if (!s || !t)
410 		return false;
411 	while (len--) {
412 		c = *s, d = *t;
413 		if (islowerByte(c))
414 			c = toupper(c);
415 		if (islowerByte(d))
416 			d = toupper(d);
417 		if (c != d)
418 			return false;
419 		++s, ++t;
420 	}
421 	return true;
422 }				/* memEqualCI */
423 
strstrCI(const char * base,const char * search)424 char *strstrCI(const char *base, const char *search)
425 {
426 	int l = strlen(search);
427 	while (*base) {
428 		if (memEqualCI(base, search, l))
429 			return (char *)base;
430 		++base;
431 	}
432 	return 0;
433 }				/* strstrCI */
434 
stringEqual(const char * s,const char * t)435 bool stringEqual(const char *s, const char *t)
436 {
437 /* check equality of strings with handling of null pointers */
438 	if (s == t)
439 		return true;
440 	if (!s || !t)
441 		return false;
442 	if (strcmp(s, t))
443 		return false;
444 	return true;
445 }				/* stringEqual */
446 
stringEqualCI(const char * s,const char * t)447 bool stringEqualCI(const char *s, const char *t)
448 {
449 	char c, d;
450 /* if two pointers are equal we can return */
451 	if (s == t)
452 		return true;
453 /* if one is NULL then the strings can't be equal */
454 	if (!s || !t)
455 		return false;
456 	while ((c = *s) && (d = *t)) {
457 		if (islowerByte(c))
458 			c = toupper(c);
459 		if (islowerByte(d))
460 			d = toupper(d);
461 		if (c != d)
462 			return false;
463 		++s, ++t;
464 	}
465 	if (*s)
466 		return false;
467 	if (*t)
468 		return false;
469 	return true;
470 }				/* stringEqualCI */
471 
stringInList(const char * const * list,const char * s)472 int stringInList(const char *const *list, const char *s)
473 {
474 	int i = 0;
475 	if (!list)
476 		i_printfExit(MSG_NullStrList);
477 	if (s)
478 		while (*list) {
479 			if (stringEqual(s, *list))
480 				return i;
481 			++i;
482 			++list;
483 		}
484 	return -1;
485 }				/* stringInList */
486 
stringInListCI(const char * const * list,const char * s)487 int stringInListCI(const char *const *list, const char *s)
488 {
489 	int i = 0;
490 	if (!list)
491 		i_printfExit(MSG_NullStrListCI);
492 	if (s)
493 		while (*list) {
494 			if (stringEqualCI(s, *list))
495 				return i;
496 			++i;
497 			++list;
498 		}
499 	return -1;
500 }				/* stringInListCI */
501 
charInList(const char * list,char c)502 int charInList(const char *list, char c)
503 {
504 	char *s;
505 	if (!list)
506 		i_printfExit(MSG_NullCharInList);
507 	s = (char *)strchr(list, c);
508 	if (!s)
509 		return -1;
510 	return s - list;
511 }				/* charInList */
512 
513 /* In an empty list, next and prev point back to the list, not to 0. */
514 /* We also allow zero. */
listIsEmpty(const struct listHead * l)515 bool listIsEmpty(const struct listHead * l)
516 {
517 	return l->next == l || l->next == 0;
518 }				/* listIsEmpty */
519 
initList(struct listHead * l)520 void initList(struct listHead *l)
521 {
522 	l->prev = l->next = l;
523 }				/* initList */
524 
delFromList(void * x)525 void delFromList(void *x)
526 {
527 	struct listHead *xh = (struct listHead *)x;
528 	((struct listHead *)xh->next)->prev = xh->prev;
529 	((struct listHead *)xh->prev)->next = xh->next;
530 }				/* delFromList */
531 
addToListFront(struct listHead * l,void * x)532 void addToListFront(struct listHead *l, void *x)
533 {
534 	struct listHead *xh = (struct listHead *)x;
535 	xh->next = l->next;
536 	xh->prev = l;
537 	l->next = (struct listHead *)x;
538 	((struct listHead *)xh->next)->prev = (struct listHead *)x;
539 }				/* addToListFront */
540 
addToListBack(struct listHead * l,void * x)541 void addToListBack(struct listHead *l, void *x)
542 {
543 	struct listHead *xh = (struct listHead *)x;
544 	xh->prev = l->prev;
545 	xh->next = l;
546 	l->prev = (struct listHead *)x;
547 	((struct listHead *)xh->prev)->next = (struct listHead *)x;
548 }				/* addToListBack */
549 
addAtPosition(void * p,void * x)550 void addAtPosition(void *p, void *x)
551 {
552 	struct listHead *xh = (struct listHead *)x;
553 	struct listHead *ph = (struct listHead *)p;
554 	xh->prev = p;
555 	xh->next = ph->next;
556 	ph->next = (struct listHead *)x;
557 	((struct listHead *)xh->next)->prev = (struct listHead *)x;
558 }				/* addAtPosition */
559 
freeList(struct listHead * l)560 void freeList(struct listHead *l)
561 {
562 	while (!listIsEmpty(l)) {
563 		void *p = l->next;
564 		delFromList(p);
565 		nzFree(p);
566 	}
567 }				/* freeList */
568 
569 /* like isalnumByte, but allows _ and - */
isA(char c)570 bool isA(char c)
571 {
572 	if (isalnumByte(c))
573 		return true;
574 	return (c == '_' || c == '-');
575 }				/* isA */
576 
isquote(char c)577 bool isquote(char c)
578 {
579 	return c == '"' || c == '\'';
580 }				/* isquote */
581 
582 /* print an error message */
errorPrint(const char * msg,...)583 void errorPrint(const char *msg, ...)
584 {
585 	char bailflag = 0;
586 	va_list p;
587 
588 	va_start(p, msg);
589 
590 	if (*msg == '@') {
591 		bailflag = 1;
592 		++msg;
593 /* I should internationalize this, but it's never suppose to happen! */
594 		fprintf(stderr, "disaster, ");
595 	} else if (isdigitByte(*msg)) {
596 		bailflag = *msg - '0';
597 		++msg;
598 	}
599 
600 	vfprintf(stderr, msg, p);
601 	fprintf(stderr, "\n");
602 	va_end(p);
603 
604 	if (bailflag)
605 		exit(bailflag);
606 }				/* errorPrint */
607 
debugPrint(int lev,const char * msg,...)608 void debugPrint(int lev, const char *msg, ...)
609 {
610 	va_list p;
611 	if (lev > debugLevel)
612 		return;
613 	if (!debugFile || lev <= 2) {
614 		va_start(p, msg);
615 		vprintf(msg, p);
616 		va_end(p);
617 		printf("\n");
618 	}
619 	if (debugFile) {
620 		if (fseek(debugFile, 0, SEEK_END) >= 10000000) {
621 			puts("debug file overflow, program aborted");
622 			fclose(debugFile);
623 			ebClose(3);
624 		}
625 		va_start(p, msg);
626 		vfprintf(debugFile, msg, p);
627 		va_end(p);
628 		fprintf(debugFile, "\n");
629 	}
630 	if (lev == 0 && !memcmp(msg, "warning", 7))
631 		eeCheck();
632 }				/* debugPrint */
633 
setDebugFile(const char * name)634 void setDebugFile(const char *name)
635 {
636 	if (debugFile)
637 		fclose(debugFile);
638 	debugFile = 0;
639 	nzFree(debugFileName);
640 	debugFileName = 0;
641 	if (!name || !*name)
642 		return;
643 	debugFileName = cloneString(name);
644 	debugFile = fopen(name, (whichproc == 'e' ? "w" : "a"));
645 	if (debugFile) {
646 #ifndef _MSC_VER		// port setlinebuf(debugFile);, if required...
647 		setlinebuf(debugFile);
648 #else
649 		;
650 #endif // !_MSC_VER
651 	} else if (whichproc == 'e')
652 		printf("cannot create %s\n", name);
653 }				/* setDebugFile */
654 
nl(void)655 void nl(void)
656 {
657 	eb_puts("");
658 }				/* nl */
659 
660 /* Turn perl string into C string, and complain about nulls. */
perl2c(char * t)661 int perl2c(char *t)
662 {
663 	int n = 0;
664 	while (*t != '\n') {
665 		if (*t == 0)
666 			++n;
667 		++t;
668 	}
669 	*t = 0;			/* now it's a C string */
670 	return n;		/* number of nulls */
671 }				/* perl2c */
672 
673 /* The length of a perl string includes its terminating newline */
pstLength(pst s)674 unsigned pstLength(pst s)
675 {
676 	pst t;
677 	if (!s)
678 		i_printfExit(MSG_NullPtr);
679 	t = s;
680 	while (*t != '\n')
681 		++t;
682 	return t + 1 - s;
683 }				/* pstLength */
684 
clonePstring(pst s)685 pst clonePstring(pst s)
686 {
687 	pst t;
688 	unsigned len;
689 	if (!s)
690 		return s;
691 	len = pstLength(s);
692 	t = (pst) allocMem(len);
693 	memcpy(t, s, len);
694 	return t;
695 }				/* clonePstring */
696 
697 // Strings are assumed distinct, hence the use of memcpy.
copyPstring(pst s,const pst t)698 void copyPstring(pst s, const pst t)
699 {
700 	int len = pstLength(t);
701 	memcpy(s, t, len);
702 }				/* copyPstring */
703 
704 /*
705  * fdIntoMemory reads data from a file descriptor, until EOF is reached.
706  * It works even if we don't know the size beforehand.
707  * We can now use it to read /proc files, pipes, and stdin.
708  * This solves an outstanding issue, and it is needed for forthcoming
709  * functionality, such as edpager.
710  */
fdIntoMemory(int fd,char ** data,int * len)711 bool fdIntoMemory(int fd, char **data, int *len)
712 {
713 	int length, n;
714 	const int blocksize = 8192;
715 	char *chunk, *buf;
716 
717 	chunk = allocZeroString(blocksize);
718 	buf = initString(&length);
719 
720 	n = 0;
721 	do {
722 		n = read(fd, chunk, blocksize);
723 		if (n < 0) {
724 			nzFree(buf);
725 			nzFree(chunk);
726 			*data = emptyString;
727 			*len = 0;
728 			setError(MSG_NoRead, "file descriptor");
729 			return false;
730 		}
731 
732 		if (n > 0)
733 			stringAndBytes(&buf, &length, chunk, n);
734 	} while (n != 0);
735 
736 	nzFree(chunk);
737 	buf = reallocString(buf, length + 2);
738 	*data = buf;
739 	*len = length;
740 	return true;
741 }				/* fdIntoMemory */
742 
fileIntoMemory(const char * filename,char ** data,int * len)743 bool fileIntoMemory(const char *filename, char **data, int *len)
744 {
745 	int fh;
746 	char ftype = fileTypeByName(filename, false);
747 	bool ret;
748 	if (ftype && ftype != 'f' && ftype != 'p') {
749 		setError(MSG_RegularFile, filename);
750 		return false;
751 	}
752 	fh = open(filename, O_RDONLY | O_BINARY);
753 	if (fh < 0) {
754 		setError(MSG_NoOpen, filename);
755 		return false;
756 	}
757 
758 	ret = fdIntoMemory(fh, data, len);
759 	if (ret == false)
760 		setError(MSG_NoRead2, filename);
761 
762 	close(fh);
763 	return ret;
764 }				/* fileIntoMemory */
765 
766 /* inverse of the above */
memoryOutToFile(const char * filename,const char * data,int len,int msgcreate,int msgwrite)767 bool memoryOutToFile(const char *filename, const char *data, int len,
768 /* specify the error messages */
769 		     int msgcreate, int msgwrite)
770 {
771 	int fh =
772 	    open(filename, O_CREAT | O_TRUNC | O_WRONLY | O_BINARY, MODE_rw);
773 	if (fh < 0) {
774 		setError(msgcreate, filename, errno);
775 		return false;
776 	}
777 	if (write(fh, data, len) < len) {
778 		setError(msgwrite, filename, errno);
779 		close(fh);
780 		return false;
781 	}
782 	close(fh);
783 	return true;
784 }				/* memoryOutToFile */
785 
786 // portable function to truncate to 0
truncate0(const char * filename,int fh)787 void truncate0(const char *filename, int fh)
788 {
789 #ifndef DOSLIKE
790 // unix is easy
791 	if (fh < 0)
792 		truncate(filename, 0l);
793 	else
794 		ftruncate(fh, 0l);
795 #else
796 	int fh2;
797 	if (fh >= 0)
798 		lseek(fh, 0L, 0);
799 	fh2 = open(filename, O_WRONLY | O_TRUNC);
800 	if (fh2 >= 0)		// it should be
801 		close(fh2);
802 #endif
803 }
804 
805 /* shift string to upper, lower, or mixed case */
806 /* action is u, l, or m. */
caseShift(char * s,char action)807 void caseShift(char *s, char action)
808 {
809 	char c;
810 	int mc = 0;
811 	bool ws = true;
812 
813 	for (; (c = *s); ++s) {
814 		if (action == 'u') {
815 			if (isalphaByte(c))
816 				*s = toupper(c);
817 			continue;
818 		}
819 		if (action == 'l') {
820 			if (isalphaByte(c))
821 				*s = tolower(c);
822 			continue;
823 		}
824 /* mixed case left */
825 		if (isalphaByte(c)) {
826 			if (ws)
827 				c = toupper(c);
828 			else
829 				c = tolower(c);
830 			if (ws && c == 'M')
831 				mc = 1;
832 			else if (mc == 1 && c == 'c')
833 				mc = 2;
834 			else if (mc == 2) {
835 				c = toupper(c);
836 				mc = 0;
837 			} else
838 				mc = 0;
839 			*s = c;
840 			ws = false;
841 			continue;
842 		}
843 		ws = true, mc = 0;
844 	}			/* loop */
845 }				/* caseShift */
846 
847 // foo-bar has to become fooBar
camelCase(char * s)848 void camelCase(char *s)
849 {
850 	char *t, *w;
851 	for (t = w = s; *t; ++t)
852 		if (*t == '-' && isalpha(t[1]))
853 			t[1] = toupper(t[1]);
854 		else
855 			*w++ = *t;
856 	*w = 0;
857 }
858 
859 /*********************************************************************
860 Manage files, directories, and terminal IO.
861 You'll see some conditional compilation when this program
862 is ported to other operating systems.
863 *********************************************************************/
864 
865 /* Return the type of a file.
866  * Make it a capital letter if you are going through a link.
867  * I think this will work on Windows, not sure.
868  * But the link feature is Unix specific.
869  * Remember the stat structure for other things. */
870 struct stat this_stat;
871 static bool this_waslink, this_brokenlink;
872 
fileTypeByName(const char * name,bool showlink)873 char fileTypeByName(const char *name, bool showlink)
874 {
875 	bool islink = false;
876 	char c;
877 	int mode;
878 
879 	this_waslink = false;
880 	this_brokenlink = false;
881 
882 #ifdef DOSLIKE
883 	if (stat(name, &this_stat)) {
884 		this_brokenlink = true;
885 		setError(MSG_NoAccess, name);
886 		return 0;
887 	}
888 	mode = this_stat.st_mode & S_IFMT;
889 #else // !DOSLIKE
890 
891 	if (lstat(name, &this_stat)) {
892 		this_brokenlink = true;
893 		setError(MSG_NoAccess, name);
894 		return 0;
895 	}
896 	mode = this_stat.st_mode & S_IFMT;
897 	if (mode == S_IFLNK) {	/* symbolic link */
898 		islink = this_waslink = true;
899 /* If this fails, I'm guessing it's just a file. */
900 		if (stat(name, &this_stat)) {
901 			this_brokenlink = true;
902 			return (showlink ? 'F' : 0);
903 		}
904 		mode = this_stat.st_mode & S_IFMT;
905 	}
906 #endif // DOSLIKE y/n
907 
908 	c = 'f';
909 	if (mode == S_IFDIR)
910 		c = 'd';
911 #ifndef DOSLIKE
912 /* I don't think these are Windows constructs. */
913 	if (mode == S_IFBLK)
914 		c = 'b';
915 	if (mode == S_IFCHR)
916 		c = 'c';
917 	if (mode == S_IFIFO)
918 		c = 'p';
919 	if (mode == S_IFSOCK)
920 		c = 's';
921 #endif
922 	if (islink & showlink)
923 		c = toupper(c);
924 	return c;
925 }				/* fileTypeByName */
926 
fileTypeByHandle(int fd)927 char fileTypeByHandle(int fd)
928 {
929 	struct stat buf;
930 	char c;
931 	int mode;
932 	if (fstat(fd, &buf)) {
933 		setError(MSG_NoAccess, "handle");
934 		return 0;
935 	}
936 	mode = buf.st_mode & S_IFMT;
937 	c = 'f';
938 	if (mode == S_IFDIR)
939 		c = 'd';
940 #ifndef DOSLIKE
941 /* I don't think these are Windows constructs. */
942 	if (mode == S_IFBLK)
943 		c = 'b';
944 	if (mode == S_IFCHR)
945 		c = 'c';
946 	if (mode == S_IFIFO)
947 		c = 'p';
948 	if (mode == S_IFSOCK)
949 		c = 's';
950 #endif
951 	return c;
952 }				/* fileTypeByHandle */
953 
fileSizeByName(const char * name)954 off_t fileSizeByName(const char *name)
955 {
956 	struct stat buf;
957 	if (stat(name, &buf)) {
958 		setError(MSG_NoAccess, name);
959 		return -1;
960 	}
961 	return buf.st_size;
962 }				/* fileSizeByName */
963 
fileSizeByHandle(int fd)964 off_t fileSizeByHandle(int fd)
965 {
966 	struct stat buf;
967 	if (fstat(fd, &buf)) {
968 /* should never happen */
969 		return -1;
970 	}
971 	return buf.st_size;
972 }				/* fileSizeByHandle */
973 
fileTimeByName(const char * name)974 time_t fileTimeByName(const char *name)
975 {
976 	struct stat buf;
977 	if (stat(name, &buf)) {
978 		setError(MSG_NoAccess, name);
979 		return -1;
980 	}
981 	return buf.st_mtime;
982 }				/* fileTimeByName */
983 
conciseSize(size_t n)984 char *conciseSize(size_t n)
985 {
986 	static char buf[32];
987 	unsigned long u, v;
988 	if (n >= 1000000000) {
989 		u = n/1000000000;
990 		v = n/100000000 % 10;
991 		if(u >= 10 || !v)
992 			sprintf(buf, "%luG", u);
993 		else
994 			sprintf(buf, "%lu.%0luG", u, v);
995 	} else if (n >= 1000000) {
996 		u = n/1000000;
997 		v = n/100000 % 10;
998 		if(u >= 10 || !v)
999 			sprintf(buf, "%luM", u);
1000 		else
1001 			sprintf(buf, "%lu.%0luM", u, v);
1002 	} else if (n >= 1000) {
1003 		u = n/1000;
1004 		v = n/100 % 10;
1005 		if(u >= 10 || !v)
1006 			sprintf(buf, "%luK", u);
1007 		else
1008 			sprintf(buf, "%lu.%0luK", u, v);
1009 	} else {
1010 		u = n;
1011 		sprintf(buf, "%lu", u);
1012 	}
1013 	return buf;
1014 }				/* conciseSize */
1015 
conciseTime(time_t t)1016 char *conciseTime(time_t t)
1017 {
1018 	static char buffer[20];
1019 	static const char *const englishMonths[] = {
1020 		"Jan", "Feb", "Mar", "Apr", "May", "Jun",
1021 		"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
1022 	};
1023 	static const char *const frenchMonths[] = {
1024 		"Janv", "Fév", "Mars", "Avr", "Mai", "Juin",
1025 		"Juill", "Août", "Sept", "Oct", "Nov", "Déc",
1026 		0
1027 	};
1028 	static const char *const portMonths[] = {
1029 		"Jan", "Fev", "Mar", "Abr", "Mai", "Jun",
1030 		"Jul", "Ago", "Set", "Out", "Nov", "Dec",
1031 	};
1032 	static const char *const germanMonths[] = {
1033 		"Jan", "Feb", "Mär", "Apr", "Mai", "Jun",
1034 		"Jul", "Aug", "Sep", "Okt", "Nov", "Dez",
1035 	};
1036 	static const char *const italMonths[] = {
1037 		"Gen", "Feb", "Mar", "Apr", "Mag", "Giu",
1038 		"Lug", "Ago", "Set", "Ott", "Nov", "Dic",
1039 		0
1040 	};
1041 	static const char *const *const allMonths[] = { 0,
1042 		englishMonths,
1043 		frenchMonths,
1044 		portMonths,
1045 		englishMonths,
1046 		germanMonths,
1047 		englishMonths,
1048 		italMonths,
1049 	};
1050 	struct tm *tm = localtime(&t);
1051 	sprintf(buffer, "%s %2d %d %02d:%02d",
1052 		allMonths[eb_lang][tm->tm_mon], tm->tm_mday, tm->tm_year + 1900,
1053 		tm->tm_hour, tm->tm_min);
1054 	return buffer;
1055 }				/* conciseTime */
1056 
1057 /* retain only characters l s t i k p m, for ls attributes */
lsattrChars(const char * buf,char * dest)1058 bool lsattrChars(const char *buf, char *dest)
1059 {
1060 	bool rc = true;
1061 	const char *s;
1062 	char c, *t;
1063 #ifdef DOSLIKE
1064 	static const char ok_chars[] = "lst";
1065 #else
1066 	static const char ok_chars[] = "lstikpmy";
1067 #endif
1068 	char used[26];
1069 	memset(used, 0, sizeof(used));
1070 	t = dest;
1071 	for (s = buf; (c = *s); ++s) {
1072 		if (isspaceByte(c))
1073 			continue;
1074 		if (!strchr(ok_chars, c)) {
1075 			rc = false;
1076 			continue;
1077 		}
1078 		if (used[c - 'a'])
1079 			continue;
1080 		used[c - 'a'] = 1;
1081 		*t++ = c;
1082 	}
1083 	*t = 0;
1084 	return rc;
1085 }				/* lsattrChars */
1086 
1087 /* expand the ls attributes for a file into a static string. */
1088 /* This assumes user/group names will not be too long. */
1089 /* Assumes we just called fileTypeByName. */
lsattr(const char * path,const char * flags)1090 char *lsattr(const char *path, const char *flags)
1091 {
1092 	static char buf[200 + ABSPATH];
1093 	char p[40];
1094 	struct passwd *pwbuf;
1095 	struct group *grpbuf;
1096 	char *s;
1097 	int l, modebits;
1098 	char newpath[ABSPATH];
1099 
1100 	buf[0] = 0;
1101 
1102 	if (!path || !path[0] || !flags || !flags[0])
1103 		return buf;
1104 
1105 	while (*flags) {
1106 		if (buf[0])
1107 			strcat(buf, " ");
1108 		if (this_brokenlink && *flags != 'y') {
1109 			strcat(buf, "~");
1110 			++flags;
1111 			continue;
1112 		}
1113 
1114 		switch (*flags) {
1115 		case 't':
1116 			strcat(buf, conciseTime(this_stat.st_mtime));
1117 			break;
1118 		case 'l':
1119 			sprintf(p, "%lld", (long long)this_stat.st_size);
1120 #ifndef DOSLIKE
1121 p:
1122 #endif // #ifndef DOSLIKE
1123 			strcat(buf, p);
1124 			break;
1125 		case 's':
1126 			strcat(buf, conciseSize(this_stat.st_size));
1127 			break;
1128 #ifndef DOSLIKE
1129 /* not sure any of these work under windows */
1130 
1131 		case 'i':
1132 			sprintf(p, "%lu", (unsigned long)this_stat.st_ino);
1133 			goto p;
1134 		case 'k':
1135 			sprintf(p, "%lu", (unsigned long)this_stat.st_nlink);
1136 			goto p;
1137 		case 'm':
1138 			strcpy(p, "-");
1139 			if (this_stat.st_rdev)
1140 				sprintf(p, "%d/%d",
1141 					(int)(this_stat.st_rdev >> 8),
1142 					(int)(this_stat.st_rdev & 0xff));
1143 			goto p;
1144 		case 'p':
1145 			s = buf + strlen(buf);
1146 			pwbuf = getpwuid(this_stat.st_uid);
1147 			if (pwbuf) {
1148 				l = strlen(pwbuf->pw_name);
1149 				if (l > 20)
1150 					l = 20;
1151 				strncpy(s, pwbuf->pw_name, l);
1152 				s[l] = 0;
1153 			} else
1154 				sprintf(s, "%d", this_stat.st_uid);
1155 			s += strlen(s);
1156 			*s++ = ' ';
1157 			grpbuf = getgrgid(this_stat.st_gid);
1158 			if (grpbuf) {
1159 				l = strlen(grpbuf->gr_name);
1160 				if (l > 20)
1161 					l = 20;
1162 				strncpy(s, grpbuf->gr_name, l);
1163 				s[l] = 0;
1164 			} else
1165 				sprintf(s, "%d", this_stat.st_gid);
1166 			s += strlen(s);
1167 			*s++ = ' ';
1168 			modebits = this_stat.st_mode;
1169 			modebits &= 07777;
1170 			if (modebits & 07000)
1171 				*s++ = '0' + (modebits >> 9);
1172 			modebits &= 0777;
1173 			*s++ = '0' + (modebits >> 6);
1174 			modebits &= 077;
1175 			*s++ = '0' + (modebits >> 3);
1176 			modebits &= 7;
1177 			*s++ = '0' + modebits;
1178 			*s = 0;
1179 			break;
1180 
1181 		case 'y':
1182 			if (!this_waslink) {
1183 				strcat(buf, "-");
1184 				break;
1185 			}
1186 /* yes it's a link, read the path */
1187 			l = readlink(path, newpath, sizeof(newpath));
1188 			if (l <= 0)
1189 				strcat(buf, "...");
1190 			else {
1191 				s = buf + strlen(buf);
1192 				strncpy(s, newpath, l);
1193 				s[l] = 0;
1194 			}
1195 			break;
1196 
1197 #endif
1198 
1199 		}
1200 
1201 		++flags;
1202 	}
1203 
1204 	return buf;
1205 }				/* lsattr */
1206 
1207 #ifdef DOSLIKE
ttySaveSettings(void)1208 void ttySaveSettings(void)
1209 {
1210 	// TODO: Anything needed here for WIN32?
1211 	isInteractive = _isatty(0);
1212 }
1213 #else // !#ifdef DOSLIKE
1214 static struct termios savettybuf;
ttySaveSettings(void)1215 void ttySaveSettings(void)
1216 {
1217 	isInteractive = isatty(0);
1218 	if (isInteractive) {
1219 		if (tcgetattr(0, &savettybuf))
1220 			i_printfExit(MSG_IoctlError);
1221 	}
1222 }				/* ttySaveSettings */
1223 
ttyRestoreSettings(void)1224 static void ttyRestoreSettings(void)
1225 {
1226 	if (isInteractive)
1227 		tcsetattr(0, TCSANOW, &savettybuf);
1228 }				/* ttyRestoreSettings */
1229 
1230 /* put the tty in raw mode.
1231  * Review your Unix manual on termio.
1232  * min>0 time>0:  return min chars, or as many as you have received
1233  *   when time/10 seconds have elapsed between characters.
1234  * min>0 time=0:  block until min chars are received.
1235  * min=0 time>0:  return 1 char, or 0 if the timer expires.
1236  * min=0 time=0:  nonblocking, return whatever chars have been received. */
ttyRaw(int charcount,int timeout,bool isecho)1237 static void ttyRaw(int charcount, int timeout, bool isecho)
1238 {
1239 	struct termios buf = savettybuf;	/* structure copy */
1240 	buf.c_cc[VMIN] = charcount;
1241 	buf.c_cc[VTIME] = timeout;
1242 	buf.c_lflag &= ~(ICANON | ECHO);
1243 	if (isecho)
1244 		buf.c_lflag |= ECHO;
1245 	tcsetattr(0, TCSANOW, &buf);
1246 }				/* ttyRaw */
1247 #endif // #ifdef DOSLIKE y/n
1248 
ttySetEcho(bool enable_echo)1249 void ttySetEcho(bool enable_echo)
1250 {
1251 #ifndef DOSLIKE
1252 	struct termios termios;
1253 
1254 	if (!isInteractive)
1255 		return;
1256 
1257 	tcgetattr(0, &termios);
1258 	if (enable_echo) {
1259 		termios.c_lflag |= ECHO;
1260 		termios.c_lflag &= ~ECHONL;
1261 	} else {
1262 		termios.c_lflag &= ~ECHO;
1263 		termios.c_lflag |= ECHONL;
1264 	}
1265 	tcsetattr(0, TCSANOW, &termios);
1266 #endif // #ifndef DOSLIKE
1267 }
1268 
1269 #ifndef DOSLIKE
1270 /* simulate MSDOS getche() system call */
getche(void)1271 int getche(void)
1272 {
1273 	char c;
1274 	fflush(stdout);
1275 	ttyRaw(1, 0, true);
1276 	read(0, &c, 1);
1277 	ttyRestoreSettings();
1278 	return c;
1279 }				/* getche */
1280 
getch(void)1281 int getch(void)
1282 {
1283 	char c;
1284 	fflush(stdout);
1285 	ttyRaw(1, 0, false);
1286 	read(0, &c, 1);
1287 	ttyRestoreSettings();
1288 	return c;
1289 }				/* getche */
1290 
1291 #endif // #ifndef DOSLIKE
1292 
getLetter(const char * s)1293 char getLetter(const char *s)
1294 {
1295 	char c;
1296 	while (true) {
1297 		c = getch();
1298 		if (strchr(s, c))
1299 			break;
1300 		printf("\a\b");
1301 		fflush(stdout);
1302 	}
1303 	printf("%c", c);
1304 	return c;
1305 }				/* getLetter */
1306 
1307 /* Parameters: message, default file name, must this file be new,
1308  * and can we except an input of white space,
1309  * that being converted to a single space. */
getFileName(int msg,const char * defname,bool isnew,bool ws)1310 char *getFileName(int msg, const char *defname, bool isnew, bool ws)
1311 {
1312 	static char buf[ABSPATH];
1313 	static char spacename[] = " ";
1314 	int l;
1315 	char *p;
1316 	bool allspace;
1317 
1318 	while (true) {
1319 		i_printf(msg);
1320 		if (defname)
1321 			printf("[%s] ", defname);
1322 		fflush(stdout);
1323 		if (!fgets(buf, sizeof(buf), stdin))
1324 			exit(0);
1325 		allspace = false;
1326 		for (p = buf; isspaceByte(*p); ++p)
1327 			if (*p == ' ')
1328 				allspace = true;
1329 		l = strlen(p);
1330 		while (l && isspaceByte(p[l - 1]))
1331 			--l;
1332 		p[l] = 0;
1333 		if (!l) {
1334 			if (ws & allspace)
1335 				return spacename;
1336 			if (!defname)
1337 				continue;
1338 /* make a copy just to be safe */
1339 			l = strlen(defname);
1340 			if (l >= ABSPATH)
1341 				l = ABSPATH - 1;
1342 			strncpy(buf, defname, l);
1343 			buf[l] = 0;
1344 			p = buf;
1345 		} else
1346 			defname = 0;
1347 		if (isnew && fileTypeByName(p, false)) {
1348 			i_printf(MSG_FileExists, p);
1349 			defname = 0;
1350 			continue;
1351 		}
1352 		return p;
1353 	}
1354 }				/* getFileName */
1355 
1356 /* Protect a filename from expansion by the shell */
1357 static const char shellmeta[] = "\\\n\t |&;<>(){}#'\"~$*?";
shellProtectLength(const char * s)1358 int shellProtectLength(const char *s)
1359 {
1360 	int l = 0;
1361 	while (*s) {
1362 		if (strchr(shellmeta, *s))
1363 			++l;
1364 		++l, ++s;
1365 	}
1366 	return l;
1367 }				/* shellProtectLength */
1368 
shellProtect(char * t,const char * s)1369 void shellProtect(char *t, const char *s)
1370 {
1371 	while (*s) {
1372 		if (strchr(shellmeta, *s))
1373 			*t++ = '\\';
1374 		*t++ = *s++;
1375 	}
1376 }				/* shellProtect */
1377 
1378 /* loop through the files in a directory */
nextScanFile(const char * base)1379 const char *nextScanFile(const char *base)
1380 {
1381 	static DIR *df;
1382 	struct dirent *de;
1383 	const char *s;
1384 
1385 	if (!df) {
1386 		if (!base)
1387 			base = ".";
1388 		df = opendir(base);
1389 /* directory could be unreadable */
1390 		if (!df) {
1391 			i_puts(MSG_NoDirNoList);
1392 			return 0;
1393 		}
1394 	}
1395 
1396 	while ((de = readdir(df))) {
1397 		s = de->d_name;
1398 		if (s[0] == '.') {
1399 			if (stringEqual(s, "."))
1400 				continue;
1401 			if (!showHiddenFiles)
1402 				continue;
1403 		}
1404 		return s;
1405 	}			/* end loop over files in directory */
1406 
1407 	closedir(df);
1408 	df = 0;
1409 	return 0;
1410 }				/* nextScanFile */
1411 
1412 /* compare routine for quicksort directory scan */
1413 static bool dir_reverse;
dircmp(const void * s,const void * t)1414 static int dircmp(const void *s, const void *t)
1415 {
1416 	int rc = strcoll((const char *)((const struct lineMap *)s)->text,
1417 			 (const char *)((const struct lineMap *)t)->text);
1418 	if (dir_reverse)
1419 		rc = -rc;
1420 	return rc;
1421 }				/* dircmp */
1422 
sortedDirList(const char * dir,struct lineMap ** map_p,int * count_p,int othersort,bool reverse)1423 bool sortedDirList(const char *dir, struct lineMap ** map_p, int *count_p,
1424 		   int othersort, bool reverse)
1425 {
1426 	const char *f;
1427 	int linecount = 0, cap;
1428 	struct lineMap *t, *map;
1429 
1430 	cap = 128;
1431 	map = t = (struct lineMap *)allocZeroMem(cap * LMSIZE);
1432 
1433 	while ((f = nextScanFile(dir))) {
1434 		if (linecount == cap) {
1435 			cap *= 2;
1436 			map = (struct lineMap *)reallocMem(map, cap * LMSIZE);
1437 			t = map + linecount;
1438 		}
1439 /* leave room for @ / newline */
1440 		t->text = (pst) allocMem(strlen(f) + 3);
1441 		strcpy((char *)t->text, f);
1442 		t->ds1 = t->ds2 = 0;
1443 		++t, ++linecount;
1444 	}
1445 
1446 	*count_p = linecount;
1447 	*map_p = map;
1448 
1449 	if (!linecount)
1450 		return true;
1451 
1452 // Sort the entries alphabetical,
1453 // unless we plan to sort them some other way.
1454 	if (!othersort) {
1455 		dir_reverse = reverse;
1456 		qsort(map, linecount, LMSIZE, dircmp);
1457 	}
1458 
1459 	return true;
1460 }				/* sortedDirList */
1461 
1462 /* Expand environment variables, then wild cards.
1463  * But the result should be one and only one file.
1464  * Return the new expanded line.
1465  * Neither the original line nore the new line is allocated.
1466  * They are static char buffers that are just plain long enough. */
1467 
envExpand(const char * line,const char ** expanded)1468 static bool envExpand(const char *line, const char **expanded)
1469 {
1470 	const char *s;
1471 	char *t;
1472 	const char *v;		/* result of getenv call */
1473 	bool inbrace;		/* ${foo} */
1474 	struct passwd *pw;
1475 	const char *udir;	/* user directory */
1476 	int l;
1477 	static char varline[ABSPATH];
1478 	char var1[40];
1479 
1480 /* quick check */
1481 	if (line[0] != '~' && !strchr(line, '$')) {
1482 		*expanded = line;
1483 		return true;
1484 	}
1485 
1486 /* ok, need to crunch along */
1487 	t = varline;
1488 	s = line;
1489 
1490 	if (line[0] != '~')
1491 		goto dollars;
1492 
1493 	l = 0;
1494 	for (s = line + 1; isalnum(*s) || *s == '_'; ++s)
1495 		++l;
1496 	if (l >= sizeof(var1) || isdigit(line[1]) || (*s && *s != '/')) {
1497 /* invalid syntax, put things back */
1498 		s = line;
1499 		goto dollars;
1500 	}
1501 
1502 	udir = 0;
1503 	strncpy(var1, line + 1, l);
1504 	var1[l] = 0;
1505 #ifndef DOSLIKE
1506 	if (l) {
1507 		pw = getpwnam(var1);
1508 		if (!pw) {
1509 			setError(MSG_NoTilde, var1);
1510 			return false;
1511 		}
1512 		if (pw->pw_dir && *pw->pw_dir)
1513 			udir = pw->pw_dir;
1514 	} else
1515 #endif
1516 		udir = home;
1517 	if (!udir) {
1518 		s = line;
1519 		goto dollars;
1520 	}
1521 	l = strlen(udir);
1522 	if (l >= sizeof(varline))
1523 		goto longline;
1524 	strcpy(varline, udir);
1525 	t = varline + l;
1526 
1527 dollars:
1528 	for (; *s; ++s) {
1529 		if (t - varline == ABSPATH - 1) {
1530 longline:
1531 			setError(MSG_ShellLineLong);
1532 			return false;
1533 		}
1534 		if (*s == '\\' && s[1] == '$') {
1535 /* this $ is escaped */
1536 			++s;
1537 appendchar:
1538 			*t++ = *s;
1539 			continue;
1540 		}
1541 		if (*s != '$')
1542 			goto appendchar;
1543 
1544 /* this is $, see if it is $var or ${var} */
1545 		inbrace = false;
1546 		v = s + 1;
1547 		if (*v == '{')
1548 			inbrace = true, ++v;
1549 		if (!isalphaByte(*v) && *v != '_')
1550 			goto appendchar;
1551 		l = 0;
1552 		while (isalnumByte(*v) || *v == '_') {
1553 			if (l == sizeof(var1) - 1)
1554 				goto longline;
1555 			var1[l++] = *v++;
1556 		}
1557 		var1[l] = 0;
1558 		if (inbrace) {
1559 			if (*v != '}')
1560 				goto appendchar;
1561 			++v;
1562 		}
1563 		s = v - 1;
1564 		v = getenv(var1);
1565 		if (!v) {
1566 			setError(MSG_NoEnvVar, var1);
1567 			return false;
1568 		}
1569 		l = strlen(v);
1570 		if (t - varline + l >= ABSPATH)
1571 			goto longline;
1572 		strcpy(t, v);
1573 		t += l;
1574 	}
1575 	*t = 0;
1576 
1577 	*expanded = varline;
1578 	return true;
1579 }				/* envExpand */
1580 
envFile(const char * line,const char ** expanded)1581 bool envFile(const char *line, const char **expanded)
1582 {
1583 	static char line2[ABSPATH];
1584 	const char *varline;
1585 	const char *s;
1586 	char *t;
1587 #ifndef DOSLIKE
1588 	glob_t g;
1589 #endif // #ifndef DOSLIKE
1590 	int rc, flags;
1591 
1592 /* ` disables this stuff */
1593 /* but `` is a literal ` */
1594 	if (line[0] == '`') {
1595 		if (line[1] != '`') {
1596 			*expanded = line + 1;
1597 			return true;
1598 		}
1599 		++line;
1600 	}
1601 
1602 	if (!envExpand(line, &varline))
1603 		return false;
1604 
1605 #ifdef DOSLIKE
1606 	*expanded = varline;
1607 	return true;		// TODO: WIN32: Expand like glob...
1608 #else // !#ifdef DOSLIKE
1609 
1610 /* expanded the environment variables, if any, now time to glob */
1611 	flags = GLOB_NOSORT;
1612 	rc = glob(varline, flags, NULL, &g);
1613 
1614 	if (rc == GLOB_NOMATCH) {
1615 /* unescape the metas */
1616 		t = line2;
1617 		for (s = varline; *s; ++s) {
1618 			if (*s == '\\' && s[1] && strchr("*?[", s[1]))
1619 				++s;
1620 			*t++ = *s;
1621 		}
1622 		*t = 0;
1623 		*expanded = line2;
1624 		return true;
1625 	}
1626 
1627 	if (rc) {
1628 /* some other syntax error, whereup we can't expand. */
1629 		setError(MSG_ShellExpand);
1630 		globfree(&g);
1631 		return false;
1632 	}
1633 
1634 	if (g.gl_pathc != 1) {
1635 		setError(MSG_ShellManyMatch);
1636 		globfree(&g);
1637 		return false;
1638 	}
1639 
1640 /* looks good, if it isn't too long */
1641 	s = g.gl_pathv[0];
1642 	if (strlen(s) >= sizeof(line2)) {
1643 		setError(MSG_ShellLineLong);
1644 		globfree(&g);
1645 		return false;
1646 	}
1647 
1648 	strcpy(line2, s);
1649 	globfree(&g);
1650 	*expanded = line2;
1651 	return true;
1652 #endif // #ifdef DOSLIKE y/n
1653 
1654 }				/* envFile */
1655 
1656 /* Call the above routine if filename contains a  slash,
1657  * or prepend the download directory if it does not.
1658  * If there is no download directory then always expand as above. */
envFileDown(const char * line,const char ** expanded)1659 bool envFileDown(const char *line, const char **expanded)
1660 {
1661 	static char line2[MAXTTYLINE];
1662 
1663 	if (!downDir || strchr(line, '/'))
1664 /* we don't necessarily expect there to be a file here */
1665 		return envFile(line, expanded);
1666 
1667 	if (strlen(downDir) + strlen(line) >= sizeof(line2) - 1) {
1668 		setError(MSG_ShellLineLong);
1669 		return false;
1670 	}
1671 
1672 	sprintf(line2, "%s/%s", downDir, line);
1673 	*expanded = line2;
1674 	return true;
1675 }				/* envFileDown */
1676 
efopen(const char * name,const char * mode)1677 FILE *efopen(const char *name, const char *mode)
1678 {
1679 	FILE *f;
1680 
1681 	if (name[0] == '-' && name[1] == 0) {
1682 		if (*mode == 'r')
1683 			return stdin;
1684 		if (*mode == 'w' || *mode == 'a')
1685 			return stdout;
1686 	}
1687 
1688 	f = fopen(name, mode);
1689 	if (f)
1690 		return f;
1691 
1692 	if (*mode == 'r')
1693 		i_printfExit(MSG_OpenFail, name);
1694 	else if (*mode == 'w' || *mode == 'a')
1695 		i_printfExit(MSG_CreateFail, name);
1696 	else
1697 		i_printfExit(MSG_InvalidFopen, mode);
1698 	return 0;
1699 }				/* efopen */
1700 
eopen(const char * name,int mode,int perms)1701 int eopen(const char *name, int mode, int perms)
1702 {
1703 	int fd;
1704 	fd = open(name, mode, perms);
1705 	if (fd >= 0)
1706 		return fd;
1707 	if (mode & O_WRONLY)
1708 		i_printfExit(MSG_CreateFail, name);
1709 	else
1710 		i_printfExit(MSG_OpenFail, name);
1711 	return -1;
1712 }				/* eopen */
1713 
appendFile(const char * fname,const char * message,...)1714 void appendFile(const char *fname, const char *message, ...)
1715 {
1716 	FILE *f;
1717 	va_list p;
1718 	f = efopen(fname, "a");
1719 	va_start(p, message);
1720 	vfprintf(f, message, p);
1721 	va_end(p);
1722 	fprintf(f, "\n");
1723 	fclose(f);
1724 }				/* appendFile */
1725 
1726 /* like the above, but no formatting */
appendFileNF(const char * filename,const char * msg)1727 void appendFileNF(const char *filename, const char *msg)
1728 {
1729 	FILE *f = efopen(filename, "a");
1730 	fprintf(f, "%s\n", msg);
1731 	fclose(f);
1732 }				/* appendFileNF */
1733 
1734 /* Wrapper around system(). */
eb_system(const char * cmd,bool print_on_success)1735 int eb_system(const char *cmd, bool print_on_success)
1736 {
1737 	int system_ret = system(cmd);
1738 	if (system_ret != -1) {
1739 		if (print_on_success)
1740 			i_puts(MSG_OK);
1741 	} else {
1742 		i_printf(MSG_SystemCmdFail, system_ret);
1743 		nl();
1744 	}
1745 	return system_ret;
1746 }				/* eb_system */
1747