~ubuntu-branches/ubuntu/feisty/elvis/feisty

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
/* io.c */
/* Copyright 1995 by Steve Kirkendall */


#include "elvis.h"
#ifdef FEATURE_RCSID
char id_io[] = "$Id: io.c,v 2.62 2003/10/18 04:47:18 steve Exp $";
#endif

#if USE_PROTOTYPES
extern char *parseurl(char *url);
#endif

/* This file contains some generic I/O functions.  They can read/write to
 * either a file or a filter.  They perform efficient character-at-a-time
 * semantics by buffering the I/O requests.
 */


#ifdef FEATURE_COMPLETE
/* This is a list of characters which may have special meaning with filenames */
static CHAR dangerous[] = {' ', '#', '%', '*', '?', '[', '{', '\0'};
#endif

static ELVBOOL	reading;  /* ElvTrue if file/program open for reading */
static ELVBOOL	writing;  /* ElvTrue if file/program open for writing */
static ELVBOOL	forfile;  /* ElvTrue if I/O to file; ElvFalse for program */
static ELVBOOL	forstdio; /* ElvTrue if using stdin/stdout; ElvFalse otherwise */
static ELVBOOL	beautify; /* ElvTrue if we're supposed to strip control chars */
static char	convert;  /* One of {unix, dos, mac} else no conversion */
static ELVBOOL	cvtcr;	  /* did last DOS read end with a CR, before LF? */
static CHAR	tinybuf[100];
static int	tinyqty;
static int	tinyused;

#if defined(PROTOCOL_HTTP) || defined(PROTOCOL_FTP)
static ELVBOOL	forurl;   /* ElvTrue if I/O via HTTP; false otherwise */
#endif

#ifdef DEBUG_ALLOC
static char	*openfile; /* name of source file which opened current file */
static int	openline; /* line number within source file */
#endif

/* This function opens a file or program for either input or output.
 * It returns ElvTrue if successful.  If there are any errors, it issues an
 * error message and returns ElvFalse.
 *
 * "name" is the name of the file to open.  If the name begins with a '!'
 * then the rest of the name is interpretted as a program to be executed
 * instead of a program name.
 *
 * "rwa" can be 'r' to open the file/program for reading, 'w' to open the
 * file/program for writing, or 'a' to open a file for appending.  'a' can't
 * be used with programs.
 *
 * "prgsafe" is only significant if security!=normal, in which case this
 * function will fail if "name" refers to a program and prgsafe is ElvFalse.
 *
 * "force" can cause a file to be overwritten even if "name" and "oldname"
 * don't match.
 */
#ifdef DEBUG_ALLOC
ELVBOOL	_ioopen(file, line, name, rwa, prgsafe, force, eol)
	char	*file;	/* name of caller's source file */
	int	line;	/* line number within caller's source file */
#else
ELVBOOL	ioopen(name, rwa, prgsafe, force, eol)
#endif
	char	*name;	/* name of file, or "!program" */
	_char_	rwa;	/* one of {read, write, append} */
	ELVBOOL	prgsafe;/* If ElvTrue, allow "!program"; else refuse */
	ELVBOOL	force;	/* if ElvTrue, allow files to be clobbered */
	_char_	eol;	/* one of {unix, dos, mac, text, binary} */
{
	DIRPERM perms;

#ifdef DEBUG_ALLOC
	if (openfile)
		msg(MSG_FATAL, "[sdsd]$1,$2: file still open from $3,$4", file, line, openfile, openline);
	openfile = file;
	openline = line;
#endif
	assert(!reading && !writing);

	/* are we going to beautify this text? */
	beautify = (ELVBOOL)(o_beautify && eol != 'b');

	/* if conversion is equivalent to binary, then use binary */
	if ((eol == 'm' && '\n' == 015)
	 || (eol == 'u' && '\n' == 012))
	{
		eol = 'b';
	}

	/* nothing in the tiny buffer */
	tinyqty = tinyused = 0;

	/* If no file name, then fail */
	if (!name || !*name)
	{
		msg(MSG_WARNING, "missing file name");
		return ElvFalse;
	}

	/* Is this stdin/stdout? */
	if (!strcmp(name, "-"))
	{
#ifdef FEATURE_STDIN
		reading = (ELVBOOL)(rwa == 'r');
#else
		if (rwa == 'r')
			return ElvFalse;
		reading = ElvFalse;
#endif
		writing = (ELVBOOL)!reading;
		forstdio = ElvTrue;
		return ElvTrue;
	}
  	/* Is this a program? */
	else if (*name == '!')
	{
		/* check safety */
		if (o_security != 'n' /* normal */ && !prgsafe)
		{
			msg(MSG_ERROR, "unsafe filter");
#ifdef DEBUG_ALLOC
			openfile = NULL;
#endif
			return ElvFalse;
		}

		/* will we be reading or writing? */
		forfile = forstdio = ElvFalse;
		switch (rwa)
		{
		  case 'r':
			/* Open the program for reading, using the GUI-
			 * dependent version of prgopen() if there is one.
			 * Then call prggo() right away, since we aren't going
			 * to be calling prgwrite().
			 */
		  	if ((gui->prgopen
		  		? (*gui->prgopen)(name + 1, ElvFalse, ElvTrue)
				: prgopen(name + 1, ElvFalse, ElvTrue))
			    && prggo())
			{
				/* success! */
				reading = ElvTrue;
				return ElvTrue;
			}
			else
			{
				msg(MSG_ERROR, "[s]can't run $1", name + 1);
			}
#ifdef DEBUG_ALLOC
			openfile = NULL;
#endif
			return ElvFalse;

		  case 'w':
			/* Open the program.  Note that if there is no GUI-
			 * dependent version of prgopen(), then we'll need to
			 * explicitly read the program's output and display it
			 * in the window, so we call the generic prgopen()
			 * for both writing and reading.
			 */
		  	if (gui->prgopen
		  		? (*gui->prgopen)(name + 1, ElvTrue, ElvFalse)
				: prgopen(name + 1, ElvTrue, ElvTrue))
		  	{
				writing = ElvTrue;
				return ElvTrue;
			}
			msg(MSG_ERROR, "[s]can't run $1", name + 1);
#ifdef DEBUG_ALLOC
			openfile = NULL;
#endif
			return ElvFalse;

		  default:
			msg(MSG_ERROR, "can't append to filter");
#ifdef DEBUG_ALLOC
			openfile = NULL;
#endif
			return ElvFalse;
		}
	}

#if defined(PROTOCOL_HTTP) || defined(PROTOCOL_FTP)
	/* is it a remote URL? */
	if (urlremote(name))
	{
		/* try to open the resource via the network */
		if (!urlopen(name, force, rwa))
		{
			/* error message already given */
#ifdef DEBUG_ALLOC
			openfile = NULL;
#endif
			return ElvFalse;
		}

		/* remember that we're using an URL */
		forurl = ElvTrue;
		forstdio = forfile = ElvFalse;
		reading = (ELVBOOL)(rwa == 'r');
		writing = (ELVBOOL)!reading;
		return ElvTrue;
	}
#endif

	/* Anything else must be a plain old file */
	name = urllocal(name);
	if (!name)
	{
		msg(MSG_ERROR, "unsupported protocol");
#ifdef DEBUG_ALLOC
			openfile = NULL;
#endif
		return ElvFalse;
	}

	/* try to open the file */
	forstdio = ElvFalse;
	forfile = ElvTrue;
	convert = eol;
	cvtcr = ElvFalse;
	perms = urlperm(name);
	switch (rwa)
	{
	  case 'r':
		if ((perms == DIR_READONLY || perms == DIR_READWRITE)
			&& txtopen(name, 'r', (ELVBOOL)(convert != 't')) == 0)
		{
			reading = ElvTrue;
			return ElvTrue;
		}
		break;

	  case 'a':
		if (perms == DIR_READWRITE
		     && txtopen(name, 'a', (ELVBOOL)(convert != 't')) == 0)
		{
			writing = ElvTrue;
			return ElvTrue;
		}
		/* else fall through to the 'w' case */

	  case 'w':
		if ((perms == DIR_NEW || perms == DIR_NOTFILE ||
			(perms == DIR_READWRITE && (force || o_writeany)))
		     && txtopen(name, 'w', (ELVBOOL)(convert != 't')) == 0)
		{
			writing = ElvTrue;
			return ElvTrue;
		}
		break;

	}

	/* If we get here, we failed.  "perms" gives clue as to why */
	switch (perms)
	{
	  case DIR_INVALID:	msg(MSG_ERROR, "[s]malformed file name $1", name);	break;
	  case DIR_BADPATH:	msg(MSG_ERROR, "[s]bad path $1", name);			break;
	  case DIR_NOTFILE:	msg(MSG_ERROR, "[s]$1 is not a file", name);		break;
	  case DIR_DIRECTORY:	msg(MSG_ERROR, "[s]$1 is a directory", name);		break;
	  case DIR_NEW:		msg(MSG_ERROR, "[s]$1 doesn't exist", name);		break;
	  case DIR_UNREADABLE:	msg(MSG_ERROR, "[s]$1 unreadable", name);		break;
	  case DIR_READONLY:	msg(MSG_ERROR, "[s]$1 unwritable", name);		break;
	  case DIR_READWRITE:	msg(MSG_ERROR, "[s]$1 exists", name);			break;
	}
#ifdef DEBUG_ALLOC
	openfile = NULL;
#endif
	return ElvFalse;
}

/* This function writes the contents of a given I/O buffer.  "iobuf" points
 * to the buffer, and "len" is the number of CHARs in that buffer.  This
 * function returns the number of CHARs actually written; values less than
 * "len" indicate trouble.
 */
int iowrite(iobuf, len)
	CHAR	*iobuf;	/* RAM buffer containing text */
	int	len;	/* number of CHARs in iobuf */
{
	int	base, okay;
 static	CHAR	crlf[2] = {015, 012};
	assert(writing);

	/* write to the file/program */
	if (forstdio)
	{
		return fwrite(iobuf, sizeof(CHAR), (size_t)len, stdout);
	}
	else if (forfile)
	{
		switch (convert)
		{
		  case 'u':
		  case 'd':
		  case 'm':
			for (base = okay = 0; base + okay < len; okay++)
			{
				if (iobuf[base + okay] == '\n')
				{
					if (okay > 0)
						txtwrite(&iobuf[base], okay);
					switch (convert)
					{
					  case 'u': txtwrite(crlf+1, 1); break;
					  case 'd': txtwrite(crlf, 2);	 break;
					  case 'm': txtwrite(crlf, 1);	 break;
					}
					base += okay + 1;
					okay = -1;
				}
			}
			if (okay > 0)
				txtwrite(&iobuf[base], okay);
			return base + okay;

		  default:
			return txtwrite(iobuf, len);
		}
	}
#if defined(PROTOCOL_HTTP) || defined(PROTOCOL_FTP)
	else if (forurl)
	{
		return urlwrite(iobuf, len);
	}
#endif
	else
	{
		return prgwrite(iobuf, len);
	}
}

/* This function fills a given I/O buffer with text read from the file/program.
 * "iobuf" points to the buffer, and "len" is the maximum number of CHARs that
 * it can hold.  This function returns the number of CHARs actually read, or
 * 0 at the end of the file.  If the "beautify" option is ElvTrue, it strips
 * control characters.
 */
int ioread(iobuf, len)
	CHAR	*iobuf;	/* RAM buffer to receive input text */
	int	len;	/* maximum number of bytes to read */
{
	int	nread;	/* number of CHARs read */
	int	i, j;

	assert(reading);

	/* if trying to read a suitably tiny amount, then use the tinybuf
	 * instead of the user's buffer, so we can reduce syscalls.
	 */
	if (iobuf != tinybuf && len < QTY(tinybuf) && tinyused >= tinyqty)
	{
		tinyqty = ioread(tinybuf, QTY(tinybuf));
		tinyused = 0;
	}

	/* maybe we can fetch from the tiny buffer? */
	if (tinyqty > tinyused)
	{
		if (len > tinyqty - tinyused)
			len = tinyqty - tinyused;
		memcpy(iobuf, tinybuf + tinyused, len);
		tinyused += len;
		return len;
	}

	/* read from the file/program */
	if (forstdio)
	{
#ifdef FEATURE_STDIN
		nread = fread(iobuf, sizeof(CHAR), (size_t)len, origstdin);
#else
		nread = 0;
#endif
	}
	else if (forfile)
	{
		if (cvtcr)
		{
			iobuf[0] = 015;
			nread = 1 + txtread(iobuf + 1, len - 1);
			cvtcr = ElvFalse;
		}
		else
			nread = txtread(iobuf, len);

		/* convert, if necessary */
		switch (convert)
		{
		  case 'u':
		  	if ('\n' == 012)
		  		break;
			for (i = 0; i < nread; i++)
			{
				if (iobuf[nread] == 012)
					iobuf[nread] = '\n';
			}
			break;

		  case 'd':
			for (i = j = 0; i < nread; i++, j++)
			{
				if (iobuf[i] == 015 && nread > 1)
				{
					if (i + 1 >= nread)
						cvtcr = ElvTrue, j--;
					else if (iobuf[i + 1] == 012)
						iobuf[j] = '\n', i++;
					else
						iobuf[j] = iobuf[i];
				}
				else
					iobuf[j] = iobuf[i];
			}
			nread = j;
			break;

		  case 'm':
			for (i = 0; i < nread; i++)
			{
				if (iobuf[i] == 015)
					iobuf[i] = '\n';
			}
			break;
		}
	}
#if defined(PROTOCOL_HTTP) || defined(PROTOCOL_FTP)
	else if (forurl)
	{
		nread = urlread(iobuf, len);
	}
#endif
	else
	{
		nread = prgread(iobuf, len);
	}

	/* maybe strip control characters */
	if (beautify && nread > 0)
	{
		for (i = j = 0; i < nread; i++)
		{
			if (iobuf[i] >= ' ' || iobuf[i] == '\t' || iobuf[i] == '\n' || iobuf[i] == '\f')
			{
				iobuf[j++] = iobuf[i];
			}
		}
		nread = j;
	}

	/* return number of bytes read */
	return nread;
}

/* Close a file that was opened via ioopen().  Return TRUE if successful, or
 * FALSE if something went wrong.  Generally, the only way something could go
 * wrong is if you're writing to a program, and the program's exit code != 0
 */
ELVBOOL	ioclose()
{
	CHAR	*rdbuf;
	int	nbytes;
	ELVBOOL	origrefresh;

	assert(reading || writing);
#ifdef DEBUG_ALLOC
	openfile = NULL;
#endif

	/* don't really close stdin/stdout; just reset variables */
	if (forstdio)
	{
		if (writing)
			fflush(stdout);
		forstdio = reading = writing = ElvFalse;
		return ElvTrue;
	}

	/* completing I/O for a file is easy */
	if (forfile)
	{
		txtclose();
		reading = writing = ElvFalse;
		return ElvTrue;
	}

#if defined(PROTOCOL_HTTP) || defined(PROTOCOL_FTP)
	/* completing for HTTP is easy */
	if (forurl)
	{
		urlclose();
		reading = writing = forurl = ElvFalse;
		return ElvTrue;
	}
#endif

	/* Writing to a program; we need to call prggo() now.  Also, if there
	 * is no gui->prgopen() function then we need to explicitly copy the
	 * program's output to the current window, so the user can see error
	 * messages and other results.
	 */
	if (writing && prggo() && !gui->prgopen && windefault)
	{
		drawopencomplete(windefault);
		origrefresh = o_exrefresh;
		o_exrefresh = ElvTrue;
		rdbuf = (CHAR *)safealloc(1024, sizeof(CHAR));
		while ((nbytes = prgread(rdbuf, 1024)) > 0)
		{
			drawextext(windefault, rdbuf, nbytes);
		}
		safefree(rdbuf);
		o_exrefresh = origrefresh;
	}

	/* if we wrote to a program with an explicit prgopen() command, then
	 * there may have been output that we don't know about.  Force the
	 * window into DRAW_OPENOUTPUT mode so we have to hit <Enter> to
	 * continue.
	 */
	if (writing && gui->prgopen && windefault)
	{
		drawopencomplete(windefault);
	}

	/* wait for the program to exit.  Succeed only if its exit code is 0 */
	reading = writing = ElvFalse;
	return (ELVBOOL)((gui->prgclose ? (*gui->prgclose)() : prgclose()) == 0);
}



/* This function parses a "path" string into directory names, and checks each
 * directory until finds a readable file named "filename".  If the "usefile"
 * flag is ElvTrue, then if an element of the path happens to be a file name
 * instead of a directory name, then it will also use that as the file name.
 *
 * If NULL is passed instead of a "path" string, then this function will
 * continue the previous path search instead of starting a new one.
 *
 * Returns the full pathname of the first readable file that it finds, or
 * NULL if it reaches the end of the path without finding one.
 */
char *iopath(path, filename, usefile)
	char	    *path;	/* list of directories to search */
	char	    *filename;	/* name of file to look for in each directory */
	ELVBOOL	    usefile;	/* allow path to contain filenames */
{
	static char *next;	/* the directory after this one */
	static char name[256];	/* full pathname, possibly of a readable file */
	int	    i;		/* used for building name */
	DIRPERM	    perms;	/* permissions of the file */

	/* if path was named, start from there; else continue previous search */
	if (path)
	{
		next = path;
	}

	/* repeat until we find a readable file... */
	do
	{
		/* if no place left to try, then quit */
		if (!next)
		{
			return (char *)0;
		}

		/* if next path element starts with ~, then use HOME instead */
		if (next[0] == '~' && !elvalnum(next[1]))
		{
			/* copy HOME into name buffer */
			strcpy(name, tochar8(o_home));
			i = strlen(name);
			next++;

			/* bug in MS network, we don't want \\ in filename */
			if (i > 0 && (name[i-1] == '/' || name[i-1] == '\\') &&
			    next != NULL && (*next == '/' || *next == '\\'))
                                i--;
		}
		/* else if path element starts with "./" then use the current
		 * file's directory.  (unless there is no current file)
		 */
		else if (next[0] == '.'
		     && (next[1] == '/' || next[1] == '\\')
		     && bufdefault
		     && o_filename(bufdefault))
		{
			/* copy file's directory into name buffer */
			strcpy(name, dirdir(tochar8(o_filename(bufdefault))));
			i = strlen(name);
			next++;
		}
		else
		{
			i = 0;
		}

		/* copy characters up to next delimiter or end */
		while (*next && *next != OSPATHDELIM)
		{
			name[i++] = *next++;
		}
		name[i] = '\0';

		/* if this was the end of the path, then there is no "next" */
		if (!*next)
		{
			next = (char *)0;
		}
		else
		{
			/* skip delimiter */
			next++;
		}

		/* If files are allowed and this is a readable file, use it.
		 * Otherwise we'll need to append the filename and try that.
		 */
		if (!usefile ||
		   ((perms = dirperm(name)) != DIR_READONLY && perms != DIR_READWRITE))
		{
			/* append the filename */
			path = dirpath(*name ? name : ".", filename);
			strcpy(name, path);
			perms = dirperm(name);
		}
	} while (perms != DIR_READONLY && perms != DIR_READWRITE);

	/* return the found name */
	return name;
}

#ifdef FEATURE_COMPLETE

# if FILES_IGNORE_CASE
/* Compare two strings for equality, ignoring case differences.  Note that
 * unlike strcmp(), this function does *NOT* indicate which string comes first
 * if they don't match.
 */
static int ustrncmp(s1, s2, len)
	char	*s1, *s2;
	int	len;
{
	while (--len >= 0 && (*s1 || *s2))
	{
		if (elvtoupper(*s1) != elvtoupper(*s2))
			return 1;
		s1++;
		s2++;
	}
	return 0;
}
# endif /* FILES_IGNORE_CASE */

/* This function implements filename completion.  You pass it a partial
 * filename and it uses dirfirst()/dirnext() to extend the name.  If you've
 * given enough to uniquely identify a file, then it will also append a
 * tab after the filename.
 */
char *iofilename(partial, endchar)
	char	*partial;	/* a partial filenam to expand */
	_char_	endchar;	/* char to append if match found */
{
	char		homed[256];	/* partial, with "~" replaced by home */
	static char	match[256];	/* the matching text */
	unsigned	matchlen;	/* # of leading identical characters */
	int		nmatches;	/* number of matches */
	char		*fname;		/* name of a matching file */
	char		*bname;		/* basename (fname without path) */
	CHAR		*str;		/* name with/without quotes */
	int		col;		/* width of directory listing */
	ELVFNR		rules;		/* file name parsing rules */
	CHAR		slashstr[1];

	/* If paranoid, then don't allow filename completion either */
	if (o_security == 'r' /* restricted */)
		return NULL;

	/* parse the rules */
	rules = exfilenamerules(o_filenamerules);

	/* remove quotes (backslashes) from before certain characters */
	str = removequotes(dangerous, toCHAR(partial));

	/* replace ~ with home directory name, ~+ with current directory name,
	 * ~- with previous directory name, or (for Unix only) ~user with the
	 * home directory of the named user.
	 */
	if (rules & ELVFNR_TILDE)
	{
#if defined(ANY_UNIX) && defined(FEATURE_MISC)
		if (str[0] == '~' && elvalpha(str[1]))
		{
			expanduserhome(tochar8(str), homed);
		}
		else
#endif /* ANY_UNIX && FEATURE_MISC */
		if (str[0] == '~' && str[1] == OSDIRDELIM)
		{
			strcpy(homed, dirpath(tochar8(o_home), tochar8(str + 2)));
		}
		else if (str[0] == '~' && str[1] == '+' && str[2] == OSDIRDELIM)
		{
			strcpy(homed, dirpath(dircwd(), tochar8(str + 3)));
		}
		else if (str[0] == '~' && str[1] == '-' && str[2] == OSDIRDELIM)
		{
			strcpy(homed, dirpath(tochar8(o_previousdir), tochar8(str + 3)));
		}
		else
		{
			strcpy(homed, tochar8(str));
		}
	}
	else
	{
		strcpy(homed, tochar8(str));
	}
	partial = homed;
	safefree(str);

	/* count the matching filenames */
	for (nmatches = matchlen = 0, fname = dirfirst(partial, ElvTrue);
	     fname;
	     nmatches++, fname = dirnext())
	{
		/* skip if binary.  Keep directories, though */
		if (!o_completebinary		    /* we want to skip binaries */
		 && dirperm(fname) != DIR_DIRECTORY /* but not directories */
		 && *ioeol(fname) == 'b')	    /* and this is a binary */
		{
			nmatches--;
			continue;
		}

		if (nmatches == 0)
		{
			strcpy(match, fname);
			matchlen = strlen(match);
		}
		else
		{
#if FILES_IGNORE_CASE
			while (matchlen > 0 && ustrncmp(match, fname, (size_t)matchlen))
				matchlen--;
#else
			while (matchlen > 0 && strncmp(match, fname, (size_t)matchlen))
				matchlen--;
#endif
		}
	}

	/* Filename completion should never reduce the partial file name!
	 * We need to guard against this, because if the filesystem is
	 * case-insensitive (as with Windows, but not Unix), then the list
	 * of matching file names could contain both upper- and lowercase
	 * names, which have *zero* matching characters.
	 */
	if (matchlen < strlen(partial))
		matchlen = strlen(partial);

	/* so what did we come up with? */
	if (nmatches == 1)
	{
		/* unique match... */

		/* decide whether to append a slash or tab */
		if (dirperm(match) == DIR_DIRECTORY)
			endchar = OSDIRDELIM;
		
		/* append a tab or slash, and return it */
		match[matchlen] = endchar;
		match[matchlen + 1] = '\0';
		return match;
	}
	else if (nmatches > 0 && matchlen > 0)
	{
		/* multiple matches... */

		/* can we add any chars to the partial name? */
		if ((unsigned)matchlen <= strlen(partial) && !strncmp(partial, match, matchlen))
		{
			/* No - list all matches */
			for (fname = dirfirst(partial, ElvTrue), col = 0;
			     fname;
			     fname = dirnext())
			{
				/* skip binary files */
				if (!o_completebinary
				 && dirperm(fname) != DIR_DIRECTORY
				 && *ioeol(fname) == 'b')
					continue;

				/* space between names */
				bname = dirfile(fname);
				if (col + strlen(bname) + 2 >= (unsigned)o_columns(windefault))
				{
					drawextext(windefault, toCHAR("\n"), 1);
					col = 0;
				}
				else if (col > 0)
				{
					drawextext(windefault, toCHAR(" "), 1);
					col++;
				}

				/* show the name, without its path */
				drawextext(windefault, toCHAR(bname), strlen(bname));
				col += strlen(bname);

				/* if directory, then append a slash */
				if (dirperm(fname) == DIR_DIRECTORY)
				{
					slashstr[0] = OSDIRDELIM;
					drawextext(windefault, slashstr, 1);
					col++;
				}
			}
			if (col > 0)
			{
				drawextext(windefault, toCHAR("\n"), 1);
			}
		}

		/* Either way - return the common part of all matches */
		match[matchlen] = '\0';
		return match;
	}
	else
	{
		/* no match, or matches have nothing in common */
		return (char *)0;
	}
}
#endif /* FEATURE_COMPLETE */


/* This function tries to guess what type of newline a given file uses.
 * Returns "binary" if there is a NUL in the first few bytes.
 *         "unix"   if there is a solitary LF character.
 *         "dos"    if the first CR is followed by a LF.
 *         "mac"    if the first CR is followed by anything but LF.
 *         "text"   otherwise; e.g., for new or empty files.
 */
char *ioeol(filename)
	char	*filename;	/* name of file to check */
{
	int	nbytes;	/* number of bytes read from file */
	int	i;

#ifdef PROTOCOL_HTTP
	/* check for a protocol */
	for (i = 0; elvalpha(filename[i]); i++)
	{
	}
	if (i < 2 || filename[i] != ':')
		i = 0;

	/* assume all protocols are binary except "file:" */
	if (!strncmp(filename, "file:", 5))
		filename += i + 1;
	else if (i > 0)
		return "binary";
#endif

#ifdef FEATURE_STDIN
	/* we don't dare read from stdin to check */
	if (!strcmp(filename, "-"))
		return "text";
#endif

	/* fill tinybuf with bytes from the beginning of the file */
	nbytes = 0;
	if (txtopen(filename, 'r', ElvTrue) == 0)
	{
		nbytes = txtread(tinybuf, QTY(tinybuf));
		txtclose();
	}

	/* look for a NUL */
	for (i = 0; i < nbytes; i++)
	{
		if (tinybuf[i] == 0)
			return "binary";
	}

	/* look for an LF that isn't preceeded by a CR */
	for (i = 0; i < nbytes - 1; i++)
	{
		if (tinybuf[i] == 012 /* linefeed */
		 && (i == 0 || tinybuf[i - 1] != 015)) /* carriage return */
			return "unix";
	}

	/* look for the CR -- is it followed by a LF? */
	for (i = 0; i < nbytes - 1; i++)
	{
		if (tinybuf[i] == 015) /* carriage return */
		{
			if (tinybuf[i + 1] == 012) /* linefeed */
				return "dos";
			else
				return "mac";
		}
	}

	/* if all else fails, assume "text" */
	return "text";
}

/* return an absolute version of a filename */
char *ioabsolute(filename)
	char	*filename;
{
	char	*c, *dir;

	/* If not a local URL then just return it unchanged */
	c = urllocal(filename);
	if (!c)
		return filename;

#ifdef FEATURE_STDIN
	/* if "-" (meaning stdin/stdout) then leave it unchanged */
	if (!strcmp(filename, "-"))
		return filename;
#endif

	/* Combine the given filename and the current working directory.
	 * Note that dirpath() is smart enough to leave names that are already
	 * absolute unchanged.
	 */
	filename = dirpath(dircwd(), c);

	/* Now we need to eliminate "dir/.." from the name, if it occurs */
	for (c = filename; *c; c++)
	{
		/* skip if not "/../" */
		if (c[0] != OSDIRDELIM
		 || c[1] != '.'
		 || c[2] != '.'
		 || (c[3] != OSDIRDELIM && c[3] != '\0'))
			continue;

		/* search backward for the previous directory */
		c[0] = '\0';
		dir = strrchr(filename, OSDIRDELIM);
		if (!dir)
			dir = c;
		c += 3;
		memmove(dir, c, strlen(c)+1);
		c = dir - 1; /* plus 1, in the for-loop reinitializer */
	}

	/* We also want to eliminate "/./" from the name, if it occurs */
	for (c = filename; *c; c++)
	{
		/* skip if not "/./" */
		if (c[0] != OSDIRDELIM
		 || c[1] != '.'
		 || c[2] != OSDIRDELIM)
			continue;

		/* eliminate it */
		memmove(c, c+2, strlen(c+2) + 1);

		/* decrement c to counteract the "c++" in the for loop,
		 * because we want to recheck this char to detect consecutive
		 * instances of "/./"
		 */
		c--;
	}

	/* If the name was reduced to "" (which can only happen if we're given
	 * a directory name ending with "/..", by the way) then use "/" instead.
	 */
	if (!*filename)
	{
		filename[0] = OSDIRDELIM;
		filename[1] = '\0';
	}

	/* return the name */
	return filename;
}