xref: /openbsd-src/usr.bin/less/filename.c (revision b2ea75c1b17e1a9a339660e7ed45cd24946b230e)
1 /*	$OpenBSD: filename.c,v 1.2 2001/01/29 01:58:01 niklas Exp $	*/
2 
3 /*
4  * Copyright (c) 1984,1985,1989,1994,1995  Mark Nudelman
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice in the documentation and/or other materials provided with
14  *    the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY
17  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE
20  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
22  * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
23  * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
24  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
25  * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
26  * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  */
28 
29 
30 /*
31  * Routines to mess around with filenames (and files).
32  * Much of this is very OS dependent.
33  */
34 
35 #include "less.h"
36 #if MSOFTC
37 #include <dos.h>
38 #endif
39 
40 extern int force_open;
41 extern IFILE curr_ifile;
42 extern IFILE old_ifile;
43 
44 /*
45  * Return a pathname that points to a specified file in a specified directory.
46  * Return NULL if the file does not exist in the directory.
47  */
48 	static char *
49 dirfile(dirname, filename)
50 	char *dirname;
51 	char *filename;
52 {
53 	char *pathname;
54 	int f;
55 
56 	if (dirname == NULL || *dirname == '\0')
57 		return (NULL);
58 	/*
59 	 * Construct the full pathname.
60 	 */
61 	pathname = (char *) calloc(strlen(dirname) + strlen(filename) + 2,
62 					sizeof(char));
63 	if (pathname == NULL)
64 		return (NULL);
65 #if MSOFTC || OS2
66 	sprintf(pathname, "%s\\%s", dirname, filename);
67 #else
68 	sprintf(pathname, "%s/%s", dirname, filename);
69 #endif
70 	/*
71 	 * Make sure the file exists.
72 	 */
73 	f = open(pathname, OPEN_READ);
74 	if (f < 0)
75 	{
76 		free(pathname);
77 		pathname = NULL;
78 	} else
79 	{
80 		close (f);
81 	}
82 	return (pathname);
83 }
84 
85 /*
86  * Return the full pathname of the given file in the "home directory".
87  */
88 	public char *
89 homefile(filename)
90 	char *filename;
91 {
92 	register char *pathname;
93 
94 	/*
95 	 * Try $HOME/filename.
96 	 */
97 	pathname = dirfile(getenv("HOME"), filename);
98 	if (pathname != NULL)
99 		return (pathname);
100 #if OS2
101 	/*
102 	 * Try $INIT/filename.
103 	 */
104 	pathname = dirfile(getenv("INIT"), filename);
105 	if (pathname != NULL)
106 		return (pathname);
107 #endif
108 #if MSOFTC || OS2
109 	/*
110 	 * Look for the file anywhere on search path.
111 	 */
112 	pathname = (char *) calloc(_MAX_PATH, sizeof(char));
113 	_searchenv(filename, "PATH", pathname);
114 	if (*pathname != '\0')
115 		return (pathname);
116 	free(pathname);
117 #endif
118 	return (NULL);
119 }
120 
121 /*
122  * Find out where the help file is.
123  */
124 	public char *
125 find_helpfile()
126 {
127 	register char *helpfile;
128 
129 	if ((helpfile = getenv("LESSHELP")) != NULL)
130 		return (save(helpfile));
131 #if MSOFTC || OS2
132 	return (homefile(HELPFILE));
133 #else
134 	return (save(HELPFILE));
135 #endif
136 }
137 
138 /*
139  * Expand a string, substituting any "%" with the current filename,
140  * and any "#" with the previous filename.
141  * {{ This is a lot of work just to support % and #. }}
142  */
143 	public char *
144 fexpand(s)
145 	char *s;
146 {
147 	register char *fr, *to;
148 	register int n;
149 	register char *e;
150 
151 	/*
152 	 * Make one pass to see how big a buffer we
153 	 * need to allocate for the expanded string.
154 	 */
155 	n = 0;
156 	for (fr = s;  *fr != '\0';  fr++)
157 	{
158 		switch (*fr)
159 		{
160 		case '%':
161 			if (curr_ifile == NULL_IFILE)
162 			{
163 				/* error("No current file", NULL_PARG); */
164 				return (save(s));
165 			}
166 			n += strlen(get_filename(curr_ifile));
167 			break;
168 		case '#':
169 			if (old_ifile == NULL_IFILE)
170 			{
171 				/* error("No previous file", NULL_PARG); */
172 				return (save(s));
173 			}
174 			n += strlen(get_filename(old_ifile));
175 			break;
176 		default:
177 			n++;
178 			break;
179 		}
180 	}
181 
182 	e = (char *) ecalloc(n+1, sizeof(char));
183 
184 	/*
185 	 * Now copy the string, expanding any "%" or "#".
186 	 */
187 	to = e;
188 	for (fr = s;  *fr != '\0';  fr++)
189 	{
190 		switch (*fr)
191 		{
192 		case '%':
193 			strcpy(to, get_filename(curr_ifile));
194 			to += strlen(to);
195 			break;
196 		case '#':
197 			strcpy(to, get_filename(old_ifile));
198 			to += strlen(to);
199 			break;
200 		default:
201 			*to++ = *fr;
202 			break;
203 		}
204 	}
205 	*to = '\0';
206 	return (e);
207 }
208 
209 #if TAB_COMPLETE_FILENAME
210 
211 /*
212  * Return a blank-separated list of filenames which "complete"
213  * the given string.
214  */
215 	public char *
216 fcomplete(s)
217 	char *s;
218 {
219 	char *fpat;
220 	/*
221 	 * Complete the filename "s" by globbing "s*".
222 	 */
223 #if MSOFTC
224 	/*
225 	 * But in DOS, we have to glob "s*.*".
226 	 * But if the final component of the filename already has
227 	 * a dot in it, just do "s*".
228 	 * (Thus, "FILE" is globbed as "FILE*.*",
229 	 *  but "FILE.A" is globbed as "FILE.A*").
230 	 */
231 	char *slash;
232 	for (slash = s+strlen(s)-1;  slash > s;  slash--)
233 		if (*slash == '/' || *slash == '\\')
234 			break;
235 	fpat = (char *) ecalloc(strlen(s)+4, sizeof(char));
236 	if (strchr(slash, '.') == NULL)
237 		sprintf(fpat, "%s*.*", s);
238 	else
239 		sprintf(fpat, "%s*", s);
240 #else
241 	fpat = (char *) ecalloc(strlen(s)+2, sizeof(char));
242 	sprintf(fpat, "%s*", s);
243 #endif
244 	s = glob(fpat);
245 	if (strcmp(s,fpat) == 0)
246 	{
247 		/*
248 		 * The filename didn't expand.
249 		 */
250 		free(s);
251 		s = NULL;
252 	}
253 	free(fpat);
254 	return (s);
255 }
256 #endif
257 
258 /*
259  * Try to determine if a file is "binary".
260  * This is just a guess, and we need not try too hard to make it accurate.
261  */
262 	public int
263 bin_file(f)
264 	int f;
265 {
266 	int i;
267 	int n;
268 	unsigned char data[64];
269 
270 	if (!seekable(f))
271 		return (0);
272 	if (lseek(f, (off_t)0, 0) == BAD_LSEEK)
273 		return (0);
274 	n = read(f, data, sizeof(data));
275 	for (i = 0;  i < n;  i++)
276 		if (binary_char(data[i]))
277 			return (1);
278 	return (0);
279 }
280 
281 /*
282  * Try to determine the size of a file by seeking to the end.
283  */
284 	static POSITION
285 seek_filesize(f)
286 	int f;
287 {
288 	off_t spos;
289 
290 	spos = lseek(f, (off_t)0, 2);
291 	if (spos == BAD_LSEEK)
292 		return (NULL_POSITION);
293 	return ((POSITION) spos);
294 }
295 
296 #if GLOB
297 
298 FILE *popen();
299 
300 /*
301  * Read a string from a file.
302  * Return a pointer to the string in memory.
303  */
304 	static char *
305 readfd(fd)
306 	FILE *fd;
307 {
308 	int len;
309 	int ch;
310 	char *buf;
311 	char *p;
312 
313 	/*
314 	 * Make a guess about how many chars in the string
315 	 * and allocate a buffer to hold it.
316 	 */
317 	len = 100;
318 	buf = (char *) ecalloc(len, sizeof(char));
319 	for (p = buf;  ;  p++)
320 	{
321 		if ((ch = getc(fd)) == '\n' || ch == EOF)
322 			break;
323 		if (p - buf >= len-1)
324 		{
325 			/*
326 			 * The string is too big to fit in the buffer we have.
327 			 * Allocate a new buffer, twice as big.
328 			 */
329 			len *= 2;
330 			*p = '\0';
331 			p = (char *) ecalloc(len, sizeof(char));
332 			strcpy(p, buf);
333 			free(buf);
334 			buf = p;
335 			p = buf + strlen(buf);
336 		}
337 		*p = ch;
338 	}
339 	*p = '\0';
340 	return (buf);
341 }
342 
343 /*
344  * Execute a shell command.
345  * Return a pointer to a pipe connected to the shell command's standard output.
346  */
347 	static FILE *
348 shellcmd(cmd, s1, s2)
349 	char *cmd;
350 	char *s1;
351 	char *s2;
352 {
353 	char *scmd;
354 	char *scmd2;
355 	char *shell;
356 	FILE *fd;
357 	int len;
358 
359 	len = strlen(cmd) +
360 		(s1 == NULL ? 0 : strlen(s1)) +
361 		(s2 == NULL ? 0 : strlen(s2)) + 1;
362 	scmd = (char *) ecalloc(len, sizeof(char));
363 	sprintf(scmd, cmd, s1, s2);
364 #if HAVE_SHELL
365 	shell = getenv("SHELL");
366 	if (shell != NULL && *shell != '\0')
367 	{
368 		/*
369 		 * Read the output of <$SHELL -c "cmd">.
370 		 */
371 		scmd2 = (char *) ecalloc(strlen(shell) + strlen(scmd) + 7,
372 					sizeof(char));
373 		sprintf(scmd2, "%s -c \"%s\"", shell, scmd);
374 		free(scmd);
375 		scmd = scmd2;
376 	}
377 #endif
378 	fd = popen(scmd, "r");
379 	free(scmd);
380 	return (fd);
381 }
382 
383 /*
384  * Expand a filename, doing any shell-level substitutions.
385  */
386 	public char *
387 glob(filename)
388 	char *filename;
389 {
390 	char *gfilename;
391 
392 	filename = fexpand(filename);
393 #if OS2
394 {
395 	char **list;
396 	int cnt;
397 	int length;
398 
399 	list = _fnexplode(filename);
400 	if (list == NULL)
401 		return (filename);
402 	length = 0;
403 	for (cnt = 0;  list[cnt] != NULL;  cnt++)
404 	  	length += strlen(list[cnt]) + 1;
405 	gfilename = (char *) ecalloc(length, sizeof(char));
406 	for (cnt = 0;  list[cnt] != NULL;  cnt++)
407 	{
408 		strcat(gfilename, list[cnt]);
409 	  	strcat(gfilename, " ");
410 	}
411 	_fnexplodefree(list);
412 }
413 #else
414 {
415 	FILE *fd;
416 
417 	/*
418 	 * We get the shell to expand the filename for us by passing
419 	 * an "echo" command to the shell and reading its output.
420 	 */
421 	fd = shellcmd("echo %s", filename, (char*)NULL);
422 	if (fd == NULL)
423 	{
424 		/*
425 		 * Cannot create the pipe.
426 		 * Just return the original (fexpanded) filename.
427 		 */
428 		return (filename);
429 	}
430 	gfilename = readfd(fd);
431 	pclose(fd);
432 	if (*gfilename == '\0')
433 	{
434 		free(gfilename);
435 		return (filename);
436 	}
437 	free(filename);
438 }
439 #endif
440 	return (gfilename);
441 }
442 
443 /*
444  * See if we should open a "replacement file"
445  * instead of the file we're about to open.
446  */
447 	public char *
448 open_altfile(filename, pf, pfd)
449 	char *filename;
450 	int *pf;
451 	void **pfd;
452 {
453 	char *lessopen;
454 	char *gfilename;
455 	int returnfd = 0;
456 	FILE *fd;
457 
458 	ch_ungetchar(-1);
459 	if ((lessopen = getenv("LESSOPEN")) == NULL)
460 		return (NULL);
461 	if (strcmp(filename, "-") == 0)
462 		return (NULL);
463 	if (*lessopen == '|')
464 	{
465 		/*
466 		 * If LESSOPEN starts with a |, it indicates
467 		 * a "pipe preprocessor".
468 		 */
469 		lessopen++;
470 		returnfd = 1;
471 	}
472 	fd = shellcmd(lessopen, filename, (char*)NULL);
473 	if (fd == NULL)
474 	{
475 		/*
476 		 * Cannot create the pipe.
477 		 */
478 		return (NULL);
479 	}
480 	if (returnfd)
481 	{
482 #if HAVE_FILENO
483 		int f;
484 		char c;
485 
486 		/*
487 		 * Read one char to see if the pipe will produce any data.
488 		 * If it does, push the char back on the pipe.
489 		 */
490 		f = fileno(fd);
491 		if (read(f, &c, 1) != 1)
492 		{
493 			/*
494 			 * Pipe is empty.  This means there is no alt file.
495 			 */
496 			pclose(fd);
497 			return (NULL);
498 		}
499 		ch_ungetchar(c);
500 		*pfd = (void *) fd;
501 		*pf = f;
502 		return (save("-"));
503 #else
504 		error("LESSOPEN pipe is not supported", NULL_PARG);
505 		return (NULL);
506 #endif
507 	}
508 	gfilename = readfd(fd);
509 	pclose(fd);
510 	if (*gfilename == '\0')
511 		/*
512 		 * Pipe is empty.  This means there is no alt file.
513 		 */
514 		return (NULL);
515 	return (gfilename);
516 }
517 
518 /*
519  * Close a replacement file.
520  */
521 	public void
522 close_altfile(altfilename, filename, pipefd)
523 	char *altfilename;
524 	char *filename;
525 	void *pipefd;
526 {
527 	char *lessclose;
528 	FILE *fd;
529 
530 	if (pipefd != NULL)
531 		pclose((FILE*) pipefd);
532 	if ((lessclose = getenv("LESSCLOSE")) == NULL)
533 	     	return;
534 	fd = shellcmd(lessclose, filename, altfilename);
535 	pclose(fd);
536 }
537 
538 #else
539 #if MSOFTC
540 
541 	public char *
542 glob(filename)
543 	char *filename;
544 {
545 	register char *gfilename;
546 	register char *p;
547 	register int len;
548 	register int n;
549 	struct find_t fnd;
550 	char drive[_MAX_DRIVE];
551 	char dir[_MAX_DIR];
552 	char fname[_MAX_FNAME];
553 	char ext[_MAX_EXT];
554 
555 	filename = fexpand(filename);
556 	if (_dos_findfirst(filename, ~0, &fnd) != 0)
557 		return (filename);
558 
559 	_splitpath(filename, drive, dir, fname, ext);
560 	len = 100;
561 	gfilename = (char *) ecalloc(len, sizeof(char));
562 	p = gfilename;
563 	do {
564 		n = strlen(drive) + strlen(dir) + strlen(fnd.name);
565 		while (p - gfilename + n+2 >= len)
566 		{
567 			len *= 2;
568 			*p = '\0';
569 			p = (char *) ecalloc(len, sizeof(char));
570 			strcpy(p, gfilename);
571 			free(gfilename);
572 			gfilename = p;
573 			p = gfilename + strlen(gfilename);
574 		}
575 		sprintf(p, "%s%s%s", drive, dir, fnd.name);
576 		p += n;
577 		*p++ = ' ';
578 	} while (_dos_findnext(&fnd) == 0);
579 
580 	*--p = '\0';
581 	return (gfilename);
582 }
583 
584 	public char *
585 open_altfile(filename)
586 	char *filename;
587 {
588 	return (NULL);
589 }
590 
591 	public void
592 close_altfile(altfilename, filename)
593 	char *altfilename;
594 	char *filename;
595 {
596 }
597 
598 #else
599 
600 	public char *
601 glob(filename)
602 	char *filename;
603 {
604 	return (fexpand(filename));
605 }
606 
607 
608 	public char *
609 open_altfile(filename)
610 	char *filename;
611 {
612      	return (NULL);
613 }
614 
615 	public void
616 close_altfile(altfilename, filename)
617 	char *altfilename;
618 	char *filename;
619 {
620 }
621 
622 #endif
623 #endif
624 
625 
626 #if HAVE_STAT
627 
628 #include <sys/stat.h>
629 #ifndef S_ISDIR
630 #define	S_ISDIR(m)	(((m) & S_IFMT) == S_IFDIR)
631 #endif
632 #ifndef S_ISREG
633 #define	S_ISREG(m)	(((m) & S_IFMT) == S_IFREG)
634 #endif
635 
636 /*
637  * Returns NULL if the file can be opened and
638  * is an ordinary file, otherwise an error message
639  * (if it cannot be opened or is a directory, etc.)
640  */
641 	public char *
642 bad_file(filename)
643 	char *filename;
644 {
645 	register char *m;
646 	struct stat statbuf;
647 
648 	if (stat(filename, &statbuf) < 0)
649 		return (errno_message(filename));
650 
651 	if (force_open)
652 		return (NULL);
653 
654 	if (S_ISDIR(statbuf.st_mode))
655 	{
656 		static char is_dir[] = " is a directory";
657 		m = (char *) ecalloc(strlen(filename) + sizeof(is_dir),
658 			sizeof(char));
659 		strcpy(m, filename);
660 		strcat(m, is_dir);
661 		return (m);
662 	}
663 	if (!S_ISREG(statbuf.st_mode))
664 	{
665 		static char not_reg[] = " is not a regular file";
666 		m = (char *) ecalloc(strlen(filename) + sizeof(not_reg),
667 			sizeof(char));
668 		strcpy(m, filename);
669 		strcat(m, not_reg);
670 		return (m);
671 	}
672 
673 	return (NULL);
674 }
675 
676 /*
677  * Return the size of a file, as cheaply as possible.
678  * In Unix, we can stat the file.
679  */
680 	public POSITION
681 filesize(f)
682 	int f;
683 {
684 	struct stat statbuf;
685 
686 	if (fstat(f, &statbuf) < 0)
687 		/*
688 		 * Can't stat; try seeking to the end.
689 		 */
690 		return (seek_filesize(f));
691 
692 	return ((POSITION) statbuf.st_size);
693 }
694 
695 #else
696 
697 /*
698  * If we have no way to find out, just say the file is good.
699  */
700 	public char *
701 bad_file(filename)
702 	char *filename;
703 {
704 	return (NULL);
705 }
706 
707 /*
708  * We can find the file size by seeking.
709  */
710 	public POSITION
711 filesize(f)
712 	int f;
713 {
714 	return (seek_filesize(f));
715 }
716 
717 #endif
718