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