]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/less/filename.c
This commit was generated by cvs2svn to compensate for changes in r120113,
[FreeBSD/FreeBSD.git] / contrib / less / filename.c
1 /*
2  * Copyright (C) 1984-2000  Mark Nudelman
3  *
4  * You may distribute under the terms of either the GNU General Public
5  * License or the Less License, as specified in the README file.
6  *
7  * For more information about less, or for information on how to 
8  * contact the author, see the README file.
9  */
10
11
12 /*
13  * Routines to mess around with filenames (and files).
14  * Much of this is very OS dependent.
15  */
16
17 #include "less.h"
18 #include "lglob.h"
19 #if MSDOS_COMPILER
20 #include <dos.h>
21 #if MSDOS_COMPILER==WIN32C && !defined(_MSC_VER)
22 #include <dir.h>
23 #endif
24 #if MSDOS_COMPILER==DJGPPC
25 #include <glob.h>
26 #include <dir.h>
27 #define _MAX_PATH       PATH_MAX
28 #endif
29 #endif
30 #ifdef _OSK
31 #include <rbf.h>
32 #ifndef _OSK_MWC32
33 #include <modes.h>
34 #endif
35 #endif
36 #if OS2
37 #include <signal.h>
38 #endif
39
40 #if HAVE_STAT
41 #include <sys/stat.h>
42 #ifndef S_ISDIR
43 #define S_ISDIR(m)      (((m) & S_IFMT) == S_IFDIR)
44 #endif
45 #ifndef S_ISREG
46 #define S_ISREG(m)      (((m) & S_IFMT) == S_IFREG)
47 #endif
48 #endif
49
50
51 extern int force_open;
52 extern int secure;
53 extern IFILE curr_ifile;
54 extern IFILE old_ifile;
55 #if SPACES_IN_FILENAMES
56 extern char openquote;
57 extern char closequote;
58 #endif
59
60 /*
61  * Remove quotes around a filename.
62  */
63         public char *
64 unquote_file(str)
65         char *str;
66 {
67 #if SPACES_IN_FILENAMES
68         char *name;
69         char *p;
70
71         if (*str != openquote)
72                 return (save(str));
73         name = (char *) ecalloc(strlen(str), sizeof(char));
74         strcpy(name, str+1);
75         p = name + strlen(name) - 1;
76         if (*p == closequote)
77                 *p = '\0';
78         return (name);
79 #else
80         return (save(str));
81 #endif
82 }
83
84 /*
85  * Return a pathname that points to a specified file in a specified directory.
86  * Return NULL if the file does not exist in the directory.
87  */
88         static char *
89 dirfile(dirname, filename)
90         char *dirname;
91         char *filename;
92 {
93         char *pathname;
94         char *qpathname;
95         int f;
96
97         if (dirname == NULL || *dirname == '\0')
98                 return (NULL);
99         /*
100          * Construct the full pathname.
101          */
102         pathname = (char *) calloc(strlen(dirname) + strlen(filename) + 2, 
103                                         sizeof(char));
104         if (pathname == NULL)
105                 return (NULL);
106         sprintf(pathname, "%s%s%s", dirname, PATHNAME_SEP, filename);
107         /*
108          * Make sure the file exists.
109          */
110         qpathname = unquote_file(pathname);
111         f = open(qpathname, OPEN_READ);
112         if (f < 0)
113         {
114                 free(pathname);
115                 pathname = NULL;
116         } else
117         {
118                 close(f);
119         }
120         free(qpathname);
121         return (pathname);
122 }
123
124 /*
125  * Return the full pathname of the given file in the "home directory".
126  */
127         public char *
128 homefile(filename)
129         char *filename;
130 {
131         register char *pathname;
132
133         /*
134          * Try $HOME/filename.
135          */
136         pathname = dirfile(lgetenv("HOME"), filename);
137         if (pathname != NULL)
138                 return (pathname);
139 #if OS2
140         /*
141          * Try $INIT/filename.
142          */
143         pathname = dirfile(lgetenv("INIT"), filename);
144         if (pathname != NULL)
145                 return (pathname);
146 #endif
147 #if MSDOS_COMPILER || OS2
148         /*
149          * Look for the file anywhere on search path.
150          */
151         pathname = (char *) calloc(_MAX_PATH, sizeof(char));
152 #if MSDOS_COMPILER==DJGPPC
153         {
154                 char *res = searchpath(filename);
155                 if (res == 0)
156                         *pathname = '\0';
157                 else
158                         strcpy(pathname, res);
159         }
160 #else
161         _searchenv(filename, "PATH", pathname);
162 #endif
163         if (*pathname != '\0')
164                 return (pathname);
165         free(pathname);
166 #endif
167         return (NULL);
168 }
169
170 /*
171  * Expand a string, substituting any "%" with the current filename,
172  * and any "#" with the previous filename.
173  * But a string of N "%"s is just replaced with N-1 "%"s.
174  * Likewise for a string of N "#"s.
175  * {{ This is a lot of work just to support % and #. }}
176  */
177         public char *
178 fexpand(s)
179         char *s;
180 {
181         register char *fr, *to;
182         register int n;
183         register char *e;
184         IFILE ifile;
185
186 #define fchar_ifile(c) \
187         ((c) == '%' ? curr_ifile : \
188          (c) == '#' ? old_ifile : NULL_IFILE)
189
190         /*
191          * Make one pass to see how big a buffer we 
192          * need to allocate for the expanded string.
193          */
194         n = 0;
195         for (fr = s;  *fr != '\0';  fr++)
196         {
197                 switch (*fr)
198                 {
199                 case '%':
200                 case '#':
201                         if (fr > s && fr[-1] == *fr)
202                         {
203                                 /*
204                                  * Second (or later) char in a string
205                                  * of identical chars.  Treat as normal.
206                                  */
207                                 n++;
208                         } else if (fr[1] != *fr)
209                         {
210                                 /*
211                                  * Single char (not repeated).  Treat specially.
212                                  */
213                                 ifile = fchar_ifile(*fr);
214                                 if (ifile == NULL_IFILE)
215                                         n++;
216                                 else
217                                         n += strlen(get_filename(ifile));
218                         }
219                         /*
220                          * Else it is the first char in a string of
221                          * identical chars.  Just discard it.
222                          */
223                         break;
224                 default:
225                         n++;
226                         break;
227                 }
228         }
229
230         e = (char *) ecalloc(n+1, sizeof(char));
231
232         /*
233          * Now copy the string, expanding any "%" or "#".
234          */
235         to = e;
236         for (fr = s;  *fr != '\0';  fr++)
237         {
238                 switch (*fr)
239                 {
240                 case '%':
241                 case '#':
242                         if (fr > s && fr[-1] == *fr)
243                         {
244                                 *to++ = *fr;
245                         } else if (fr[1] != *fr)
246                         {
247                                 ifile = fchar_ifile(*fr);
248                                 if (ifile == NULL_IFILE)
249                                         *to++ = *fr;
250                                 else
251                                 {
252                                         strcpy(to, get_filename(ifile));
253                                         to += strlen(to);
254                                 }
255                         }
256                         break;
257                 default:
258                         *to++ = *fr;
259                         break;
260                 }
261         }
262         *to = '\0';
263         return (e);
264 }
265
266 #if TAB_COMPLETE_FILENAME
267
268 /*
269  * Return a blank-separated list of filenames which "complete"
270  * the given string.
271  */
272         public char *
273 fcomplete(s)
274         char *s;
275 {
276         char *fpat;
277
278         if (secure)
279                 return (NULL);
280         /*
281          * Complete the filename "s" by globbing "s*".
282          */
283 #if MSDOS_COMPILER && (MSDOS_COMPILER == MSOFTC || MSDOS_COMPILER == BORLANDC)
284         /*
285          * But in DOS, we have to glob "s*.*".
286          * But if the final component of the filename already has
287          * a dot in it, just do "s*".  
288          * (Thus, "FILE" is globbed as "FILE*.*", 
289          *  but "FILE.A" is globbed as "FILE.A*").
290          */
291         {
292                 char *slash;
293                 for (slash = s+strlen(s)-1;  slash > s;  slash--)
294                         if (*slash == *PATHNAME_SEP || *slash == '/')
295                                 break;
296                 fpat = (char *) ecalloc(strlen(s)+4, sizeof(char));
297                 if (strchr(slash, '.') == NULL)
298                         sprintf(fpat, "%s*.*", s);
299                 else
300                         sprintf(fpat, "%s*", s);
301         }
302 #else
303         fpat = (char *) ecalloc(strlen(s)+2, sizeof(char));
304         sprintf(fpat, "%s*", s);
305 #endif
306         s = lglob(fpat);
307         if (strcmp(s,fpat) == 0)
308         {
309                 /*
310                  * The filename didn't expand.
311                  */
312                 free(s);
313                 s = NULL;
314         }
315         free(fpat);
316         return (s);
317 }
318 #endif
319
320 /*
321  * Try to determine if a file is "binary".
322  * This is just a guess, and we need not try too hard to make it accurate.
323  */
324         public int
325 bin_file(f)
326         int f;
327 {
328         int i;
329         int n;
330         unsigned char data[64];
331
332         if (!seekable(f))
333                 return (0);
334         if (lseek(f, (off_t)0, 0) == BAD_LSEEK)
335                 return (0);
336         n = read(f, data, sizeof(data));
337         for (i = 0;  i < n;  i++)
338                 if (binary_char(data[i]))
339                         return (1);
340         return (0);
341 }
342
343 /*
344  * Try to determine the size of a file by seeking to the end.
345  */
346         static POSITION
347 seek_filesize(f)
348         int f;
349 {
350         off_t spos;
351
352         spos = lseek(f, (off_t)0, 2);
353         if (spos == BAD_LSEEK)
354                 return (NULL_POSITION);
355         return ((POSITION) spos);
356 }
357
358 /*
359  * Read a string from a file.
360  * Return a pointer to the string in memory.
361  */
362         static char *
363 readfd(fd)
364         FILE *fd;
365 {
366         int len;
367         int ch;
368         char *buf;
369         char *p;
370         
371         /* 
372          * Make a guess about how many chars in the string
373          * and allocate a buffer to hold it.
374          */
375         len = 100;
376         buf = (char *) ecalloc(len, sizeof(char));
377         for (p = buf;  ;  p++)
378         {
379                 if ((ch = getc(fd)) == '\n' || ch == EOF)
380                         break;
381                 if (p - buf >= len-1)
382                 {
383                         /*
384                          * The string is too big to fit in the buffer we have.
385                          * Allocate a new buffer, twice as big.
386                          */
387                         len *= 2;
388                         *p = '\0';
389                         p = (char *) ecalloc(len, sizeof(char));
390                         strcpy(p, buf);
391                         free(buf);
392                         buf = p;
393                         p = buf + strlen(buf);
394                 }
395                 *p = ch;
396         }
397         *p = '\0';
398         return (buf);
399 }
400
401 #if HAVE_SHELL
402
403 /*
404  * Get the shell's escape character.
405  */
406         static char *
407 get_meta_escape()
408 {
409         char *s;
410
411         s = lgetenv("LESSMETAESCAPE");
412         if (s == NULL)
413                 s = DEF_METAESCAPE;
414         return (s);
415 }
416
417 /*
418  * Is this a shell metacharacter?
419  */
420         static int
421 metachar(c)
422         char c;
423 {
424         static char *metachars = NULL;
425
426         if (metachars == NULL)
427         {
428                 metachars = lgetenv("LESSMETACHARS");
429                 if (metachars == NULL)
430                         metachars = DEF_METACHARS;
431         }
432         return (strchr(metachars, c) != NULL);
433 }
434
435 /*
436  * Insert a backslash before each metacharacter in a string.
437  */
438         public char *
439 esc_metachars(s)
440         char *s;
441 {
442         char *p;
443         char *newstr;
444         int len;
445         char *esc;
446         int esclen;
447
448         /*
449          * Determine how big a string we need to allocate.
450          */
451         esc = get_meta_escape();
452         esclen = strlen(esc);
453         len = 1; /* Trailing null byte */
454         for (p = s;  *p != '\0';  p++)
455         {
456                 len++;
457                 if (metachar(*p))
458                 {
459                         if (*esc == '\0')
460                         {
461                                 /*
462                                  * We've got a metachar, but this shell 
463                                  * doesn't support escape chars.  Give up.
464                                  */
465                                 return (NULL);
466                         }
467                         /*
468                          * Allow space for the escape char.
469                          */
470                         len += esclen;
471                 }
472         }
473         /*
474          * Allocate and construct the new string.
475          */
476         newstr = p = (char *) ecalloc(len, sizeof(char));
477         while (*s != '\0')
478         {
479                 if (metachar(*s))
480                 {
481                         /*
482                          * Add the escape char.
483                          */
484                         strcpy(p, esc);
485                         p += esclen;
486                 }
487                 *p++ = *s++;
488         }
489         *p = '\0';
490         return (newstr);
491 }
492
493 #else /* HAVE_SHELL */
494
495         public char *
496 esc_metachars(s)
497         char *s;
498 {
499         return (save(s));
500 }
501
502 #endif /* HAVE_SHELL */
503
504
505 #if HAVE_POPEN
506
507 FILE *popen();
508
509 /*
510  * Execute a shell command.
511  * Return a pointer to a pipe connected to the shell command's standard output.
512  */
513         static FILE *
514 shellcmd(cmd)
515         char *cmd;
516 {
517         FILE *fd;
518
519 #if HAVE_SHELL
520         char *shell;
521
522         shell = lgetenv("SHELL");
523         if (shell != NULL && *shell != '\0')
524         {
525                 char *scmd;
526                 char *esccmd;
527
528                 /*
529                  * Try to escape any metacharacters in the command.
530                  * If we can't do that, just put the command in quotes.
531                  * (But that doesn't work well if the command itself 
532                  * contains quotes.)
533                  */
534                 if ((esccmd = esc_metachars(cmd)) == NULL)
535                 {
536                         /*
537                          * Cannot escape the metacharacters, so use quotes.
538                          * Read the output of <$SHELL -c "cmd">.
539                          */
540                         scmd = (char *) ecalloc(strlen(shell) + strlen(cmd) + 7,
541                                                 sizeof(char));
542                         sprintf(scmd, "%s -c \"%s\"", shell, cmd);
543                 } else
544                 {
545                         /*
546                          * Read the output of <$SHELL -c cmd>.  
547                          * No quotes; use the escaped cmd.
548                          */
549                         scmd = (char *) ecalloc(strlen(shell) + strlen(esccmd) + 5,
550                                                 sizeof(char));
551                         sprintf(scmd, "%s -c %s", shell, esccmd);
552                         free(esccmd);
553                 }
554                 fd = popen(scmd, "r");
555                 free(scmd);
556         } else
557 #endif
558         {
559                 fd = popen(cmd, "r");
560                 /*
561                  * Redirection in `popen' might have messed with the
562                  * standard devices.  Restore binary input mode.
563                  */
564                 SET_BINARY(0);
565         }
566         return (fd);
567 }
568
569 #endif /* HAVE_POPEN */
570
571
572 /*
573  * Expand a filename, doing any system-specific metacharacter substitutions.
574  */
575         public char *
576 lglob(filename)
577         char *filename;
578 {
579         char *gfilename;
580         char *ofilename;
581
582         ofilename = fexpand(filename);
583         if (secure)
584                 return (ofilename);
585         filename = unquote_file(ofilename);
586
587 #ifdef DECL_GLOB_LIST
588 {
589         /*
590          * The globbing function returns a list of names.
591          */
592         int length;
593         char *p;
594         DECL_GLOB_LIST(list)
595
596         GLOB_LIST(filename, list);
597         if (GLOB_LIST_FAILED(list))
598         {
599                 free(filename);
600                 return (ofilename);
601         }
602         length = 1; /* Room for trailing null byte */
603         for (SCAN_GLOB_LIST(list, p))
604         {
605                 INIT_GLOB_LIST(list, p);
606                 length += strlen(p) + 1;
607 #if SPACES_IN_FILENAMES
608                 if (strchr(p, ' ') != NULL)
609                         length += 2; /* Allow for quotes */
610 #endif
611         }
612         gfilename = (char *) ecalloc(length, sizeof(char));
613         for (SCAN_GLOB_LIST(list, p))
614         {
615                 INIT_GLOB_LIST(list, p);
616 #if SPACES_IN_FILENAMES
617                 if (strchr(p, ' ') != NULL)
618                         sprintf(gfilename + strlen(gfilename), "%c%s%c ",
619                                 openquote, p, closequote);
620                 else
621 #endif
622                         sprintf(gfilename + strlen(gfilename), "%s ", p);
623         }
624         /*
625          * Overwrite the final trailing space with a null terminator.
626          */
627         *--p = '\0';
628         GLOB_LIST_DONE(list);
629 }
630 #else
631 #ifdef DECL_GLOB_NAME
632 {
633         /*
634          * The globbing function returns a single name, and
635          * is called multiple times to walk thru all names.
636          */
637         register char *p;
638         register int len;
639         register int n;
640 #if SPACES_IN_FILENAMES
641         register int spaces_in_file;
642 #endif
643         DECL_GLOB_NAME(fnd,drive,dir,fname,ext,handle)
644         
645         GLOB_FIRST_NAME(filename, &fnd, handle);
646         if (GLOB_FIRST_FAILED(handle))
647         {
648                 free(filename);
649                 return (ofilename);
650         }
651
652         _splitpath(filename, drive, dir, fname, ext);
653         len = 100;
654         gfilename = (char *) ecalloc(len, sizeof(char));
655         p = gfilename;
656         do {
657                 n = strlen(drive) + strlen(dir) + strlen(fnd.GLOB_NAME) + 1;
658 #if SPACES_IN_FILENAMES
659                 spaces_in_file = 0;
660                 if (strchr(fnd.GLOB_NAME, ' ') != NULL ||
661                     strchr(filename, ' ') != NULL)
662                 {
663                         spaces_in_file = 1;
664                         n += 2;
665                 }
666 #endif
667                 while (p - gfilename + n+2 >= len)
668                 {
669                         /*
670                          * No room in current buffer.  Allocate a bigger one.
671                          */
672                         len *= 2;
673                         *p = '\0';
674                         p = (char *) ecalloc(len, sizeof(char));
675                         strcpy(p, gfilename);
676                         free(gfilename);
677                         gfilename = p;
678                         p = gfilename + strlen(gfilename);
679                 }
680 #if SPACES_IN_FILENAMES
681                 if (spaces_in_file)
682                         sprintf(p, "%c%s%s%s%c ", openquote, 
683                                 drive, dir, fnd.GLOB_NAME, closequote);
684                 else
685 #endif
686                         sprintf(p, "%s%s%s ", drive, dir, fnd.GLOB_NAME);
687                 p += n;
688         } while (GLOB_NEXT_NAME(handle, &fnd) == 0);
689
690         /*
691          * Overwrite the final trailing space with a null terminator.
692          */
693         *--p = '\0';
694         GLOB_NAME_DONE(handle);
695 }
696 #else
697 #if HAVE_POPEN
698 {
699         /*
700          * We get the shell to glob the filename for us by passing
701          * an "echo" command to the shell and reading its output.
702          */
703         FILE *fd;
704         char *s;
705         char *lessecho;
706         char *cmd;
707
708         lessecho = lgetenv("LESSECHO");
709         if (lessecho == NULL || *lessecho == '\0')
710                 lessecho = "lessecho";
711         s = esc_metachars(filename);
712         if (s == NULL)
713         {
714                 /*
715                  * There may be dangerous metachars in this name.
716                  * We can't risk passing it to the shell.
717                  * {{ For example, do "!;TAB" when the first file 
718                  *    in the dir is named "rm". }}
719                  */
720                 free(filename);
721                 return (ofilename);
722         }
723         /*
724          * Invoke lessecho, and read its output (a globbed list of filenames).
725          */
726         cmd = (char *) ecalloc(strlen(lessecho) + strlen(s) + 24, sizeof(char));
727         sprintf(cmd, "%s -p0x%x -d0x%x -- %s", 
728                 lessecho, openquote, closequote, s);
729         fd = shellcmd(cmd);
730         free(s);
731         free(cmd);
732         if (fd == NULL)
733         {
734                 /*
735                  * Cannot create the pipe.
736                  * Just return the original (fexpanded) filename.
737                  */
738                 free(filename);
739                 return (ofilename);
740         }
741         gfilename = readfd(fd);
742         pclose(fd);
743         if (*gfilename == '\0')
744         {
745                 free(gfilename);
746                 free(filename);
747                 return (ofilename);
748         }
749 }
750 #else
751         /*
752          * No globbing functions at all.  Just use the fexpanded filename.
753          */
754         gfilename = save(filename);
755 #endif
756 #endif
757 #endif
758         free(filename);
759         free(ofilename);
760         return (gfilename);
761 }
762
763 /*
764  * See if we should open a "replacement file" 
765  * instead of the file we're about to open.
766  */
767         public char *
768 open_altfile(filename, pf, pfd)
769         char *filename;
770         int *pf;
771         void **pfd;
772 {
773 #if !HAVE_POPEN
774         return (NULL);
775 #else
776         char *lessopen;
777         char *gfilename;
778         char *cmd;
779         FILE *fd;
780 #if HAVE_FILENO
781         int returnfd = 0;
782 #endif
783         
784         if (secure)
785                 return (NULL);
786         ch_ungetchar(-1);
787         if ((lessopen = lgetenv("LESSOPEN")) == NULL)
788                 return (NULL);
789         if (strcmp(filename, "-") == 0)
790                 return (NULL);
791         if (*lessopen == '|')
792         {
793                 /*
794                  * If LESSOPEN starts with a |, it indicates 
795                  * a "pipe preprocessor".
796                  */
797 #if HAVE_FILENO
798                 lessopen++;
799                 returnfd = 1;
800 #else
801                 error("LESSOPEN pipe is not supported", NULL_PARG);
802                 return (NULL);
803 #endif
804         }
805
806         gfilename = esc_metachars(filename);
807         if (gfilename == NULL)
808         {
809                 /*
810                  * Cannot escape metacharacters.
811                  */
812                 return (NULL);
813         }
814         cmd = (char *) ecalloc(strlen(lessopen) + strlen(gfilename) + 2, 
815                         sizeof(char));
816         sprintf(cmd, lessopen, gfilename);
817         fd = shellcmd(cmd);
818         free(gfilename);
819         free(cmd);
820         if (fd == NULL)
821         {
822                 /*
823                  * Cannot create the pipe.
824                  */
825                 return (NULL);
826         }
827 #if HAVE_FILENO
828         if (returnfd)
829         {
830                 int f;
831                 char c;
832
833                 /*
834                  * Read one char to see if the pipe will produce any data.
835                  * If it does, push the char back on the pipe.
836                  */
837                 f = fileno(fd);
838                 SET_BINARY(f);
839                 if (read(f, &c, 1) != 1)
840                 {
841                         /*
842                          * Pipe is empty.  This means there is no alt file.
843                          */
844                         pclose(fd);
845                         return (NULL);
846                 }
847                 ch_ungetchar(c);
848                 *pfd = (void *) fd;
849                 *pf = f;
850                 return (save("-"));
851         }
852 #endif
853         gfilename = readfd(fd);
854         pclose(fd);
855         if (*gfilename == '\0')
856                 /*
857                  * Pipe is empty.  This means there is no alt file.
858                  */
859                 return (NULL);
860         return (gfilename);
861 #endif /* HAVE_POPEN */
862 }
863
864 /*
865  * Close a replacement file.
866  */
867         public void
868 close_altfile(altfilename, filename, pipefd)
869         char *altfilename;
870         char *filename;
871         void *pipefd;
872 {
873 #if HAVE_POPEN
874         char *lessclose;
875         char *gfilename;
876         char *galtfilename;
877         FILE *fd;
878         char *cmd;
879         
880         if (secure)
881                 return;
882         if (pipefd != NULL)
883         {
884 #if OS2
885                 /*
886                  * The pclose function of OS/2 emx sometimes fails.
887                  * Send SIGINT to the piped process before closing it.
888                  */
889                 kill(((FILE*)pipefd)->_pid, SIGINT);
890 #endif
891                 pclose((FILE*) pipefd);
892         }
893         if ((lessclose = lgetenv("LESSCLOSE")) == NULL)
894                 return;
895         gfilename = esc_metachars(filename);
896         if (gfilename == NULL)
897         {
898                 return;
899         }
900         galtfilename = esc_metachars(altfilename);
901         if (galtfilename == NULL)
902         {
903                 free(gfilename);
904                 return;
905         }
906         cmd = (char *) ecalloc(strlen(lessclose) + strlen(gfilename) + 
907                         strlen(galtfilename) + 2, sizeof(char));
908         sprintf(cmd, lessclose, gfilename, galtfilename);
909         fd = shellcmd(cmd);
910         free(galtfilename);
911         free(gfilename);
912         free(cmd);
913         if (fd != NULL)
914                 pclose(fd);
915 #endif
916 }
917                 
918 /*
919  * Is the specified file a directory?
920  */
921         public int
922 is_dir(filename)
923         char *filename;
924 {
925         int isdir = 0;
926
927         filename = unquote_file(filename);
928 #if HAVE_STAT
929 {
930         int r;
931         struct stat statbuf;
932
933         r = stat(filename, &statbuf);
934         isdir = (r >= 0 && S_ISDIR(statbuf.st_mode));
935 }
936 #else
937 #ifdef _OSK
938 {
939         register int f;
940
941         f = open(filename, S_IREAD | S_IFDIR);
942         if (f >= 0)
943                 close(f);
944         isdir = (f >= 0);
945 }
946 #endif
947 #endif
948         free(filename);
949         return (isdir);
950 }
951
952 /*
953  * Returns NULL if the file can be opened and
954  * is an ordinary file, otherwise an error message
955  * (if it cannot be opened or is a directory, etc.)
956  */
957         public char *
958 bad_file(filename)
959         char *filename;
960 {
961         register char *m = NULL;
962
963         filename = unquote_file(filename);
964         if (is_dir(filename))
965         {
966                 static char is_dir[] = " is a directory";
967
968                 m = (char *) ecalloc(strlen(filename) + sizeof(is_dir), 
969                         sizeof(char));
970                 strcpy(m, filename);
971                 strcat(m, is_dir);
972         } else
973         {
974 #if HAVE_STAT
975                 int r;
976                 struct stat statbuf;
977
978                 r = stat(filename, &statbuf);
979                 if (r < 0)
980                 {
981                         m = errno_message(filename);
982                 } else if (force_open)
983                 {
984                         m = NULL;
985                 } else if (!S_ISREG(statbuf.st_mode))
986                 {
987                         static char not_reg[] = " is not a regular file (use -f to see it)";
988                         m = (char *) ecalloc(strlen(filename) + sizeof(not_reg),
989                                 sizeof(char));
990                         strcpy(m, filename);
991                         strcat(m, not_reg);
992                 }
993 #endif
994         }
995         free(filename);
996         return (m);
997 }
998
999 /*
1000  * Return the size of a file, as cheaply as possible.
1001  * In Unix, we can stat the file.
1002  */
1003         public POSITION
1004 filesize(f)
1005         int f;
1006 {
1007 #if HAVE_STAT
1008         struct stat statbuf;
1009
1010         if (fstat(f, &statbuf) >= 0)
1011                 return ((POSITION) statbuf.st_size);
1012 #else
1013 #ifdef _OSK
1014         long size;
1015
1016         if ((size = (long) _gs_size(f)) >= 0)
1017                 return ((POSITION) size);
1018 #endif
1019 #endif
1020         return (seek_filesize(f));
1021 }
1022