Merge from vendor branch BINUTILS:
[dragonfly.git] / contrib / texinfo / info / filesys.c
1 /* filesys.c -- filesystem specific functions.
2    $Id: filesys.c,v 1.15 2002/03/23 20:45:24 karl Exp $
3
4    Copyright (C) 1993, 97, 98, 2000 Free Software Foundation, Inc.
5
6    This program is free software; you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation; either version 2, or (at your option)
9    any later version.
10
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15
16    You should have received a copy of the GNU General Public License
17    along with this program; if not, write to the Free Software
18    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19
20    Written by Brian Fox (bfox@ai.mit.edu). */
21
22 #include "info.h"
23
24 #include "tilde.h"
25 #include "filesys.h"
26
27 /* Local to this file. */
28 static char *info_file_in_path (), *lookup_info_filename ();
29 static char *info_absolute_file ();
30 static void remember_info_filename (), maybe_initialize_infopath ();
31
32 typedef struct
33 {
34   char *suffix;
35   char *decompressor;
36 } COMPRESSION_ALIST;
37
38 static char *info_suffixes[] = {
39   ".info",
40   "-info",
41   "/index",
42   ".inf",       /* 8+3 file on filesystem which supports long file names */
43 #ifdef __MSDOS__
44   /* 8+3 file names strike again...  */
45   ".in",        /* for .inz, .igz etc. */
46   ".i",
47 #endif
48   "",
49   NULL
50 };
51
52 static COMPRESSION_ALIST compress_suffixes[] = {
53   { ".gz", "gunzip" },
54   { ".bz2", "bunzip2" },
55   { ".z", "gunzip" },
56   { ".Z", "uncompress" },
57   { ".Y", "unyabba" },
58 #ifdef __MSDOS__
59   { "gz", "gunzip" },
60   { "z", "gunzip" },
61 #endif
62   { (char *)NULL, (char *)NULL }
63 };
64
65 /* The path on which we look for info files.  You can initialize this
66    from the environment variable INFOPATH if there is one, or you can
67    call info_add_path () to add paths to the beginning or end of it.
68    You can call zap_infopath () to make the path go away. */
69 char *infopath = (char *)NULL;
70 static int infopath_size = 0;
71
72 /* Expand the filename in PARTIAL to make a real name for this operating
73    system.  This looks in INFO_PATHS in order to find the correct file.
74    If it can't find the file, it returns NULL. */
75 static char *local_temp_filename = (char *)NULL;
76 static int local_temp_filename_size = 0;
77
78 char *
79 info_find_fullpath (partial)
80      char *partial;
81 {
82   int initial_character;
83   char *temp;
84
85   filesys_error_number = 0;
86
87   maybe_initialize_infopath ();
88
89   if (partial && (initial_character = *partial))
90     {
91       char *expansion;
92
93       expansion = lookup_info_filename (partial);
94
95       if (expansion)
96         return (expansion);
97
98       /* If we have the full path to this file, we still may have to add
99          various extensions to it.  I guess we have to stat this file
100          after all. */
101       if (IS_ABSOLUTE (partial))
102         temp = info_absolute_file (partial);
103       else if (initial_character == '~')
104         {
105           expansion = tilde_expand_word (partial);
106           if (IS_ABSOLUTE (expansion))
107             {
108               temp = info_absolute_file (expansion);
109               free (expansion);
110             }
111           else
112             temp = expansion;
113         }
114       else if (initial_character == '.' &&
115                (IS_SLASH (partial[1]) ||
116                 (partial[1] == '.' && IS_SLASH (partial[2]))))
117         {
118           if (local_temp_filename_size < 1024)
119             local_temp_filename = (char *)xrealloc
120               (local_temp_filename, (local_temp_filename_size = 1024));
121 #if defined (HAVE_GETCWD)
122           if (!getcwd (local_temp_filename, local_temp_filename_size))
123 #else /*  !HAVE_GETCWD */
124           if (!getwd (local_temp_filename))
125 #endif /* !HAVE_GETCWD */
126             {
127               filesys_error_number = errno;
128               return (partial);
129             }
130
131           strcat (local_temp_filename, "/");
132           strcat (local_temp_filename, partial);
133           temp = info_absolute_file (local_temp_filename); /* try extensions */
134           if (!temp)
135             partial = local_temp_filename;
136         }
137       else
138         temp = info_file_in_path (partial, infopath);
139
140       if (temp)
141         {
142           remember_info_filename (partial, temp);
143           if (strlen (temp) > local_temp_filename_size)
144             local_temp_filename = (char *) xrealloc
145               (local_temp_filename,
146                (local_temp_filename_size = (50 + strlen (temp))));
147           strcpy (local_temp_filename, temp);
148           free (temp);
149           return (local_temp_filename);
150         }
151     }
152   return (partial);
153 }
154
155 /* Scan the list of directories in PATH looking for FILENAME.  If we find
156    one that is a regular file, return it as a new string.  Otherwise, return
157    a NULL pointer. */
158 static char *
159 info_file_in_path (filename, path)
160      char *filename, *path;
161 {
162   struct stat finfo;
163   char *temp_dirname;
164   int statable, dirname_index;
165
166   /* Reject ridiculous cases up front, to prevent infinite recursion
167      later on.  E.g., someone might say "info '(.)foo'"...  */
168   if (!*filename || STREQ (filename, ".") || STREQ (filename, ".."))
169     return NULL;
170
171   dirname_index = 0;
172
173   while ((temp_dirname = extract_colon_unit (path, &dirname_index)))
174     {
175       register int i, pre_suffix_length;
176       char *temp;
177
178       /* Expand a leading tilde if one is present. */
179       if (*temp_dirname == '~')
180         {
181           char *expanded_dirname;
182
183           expanded_dirname = tilde_expand_word (temp_dirname);
184           free (temp_dirname);
185           temp_dirname = expanded_dirname;
186         }
187
188       temp = (char *)xmalloc (30 + strlen (temp_dirname) + strlen (filename));
189       strcpy (temp, temp_dirname);
190       if (!IS_SLASH (temp[(strlen (temp)) - 1]))
191         strcat (temp, "/");
192       strcat (temp, filename);
193
194       pre_suffix_length = strlen (temp);
195
196       free (temp_dirname);
197
198       for (i = 0; info_suffixes[i]; i++)
199         {
200           strcpy (temp + pre_suffix_length, info_suffixes[i]);
201
202           statable = (stat (temp, &finfo) == 0);
203
204           /* If we have found a regular file, then use that.  Else, if we
205              have found a directory, look in that directory for this file. */
206           if (statable)
207             {
208               if (S_ISREG (finfo.st_mode))
209                 {
210                   return (temp);
211                 }
212               else if (S_ISDIR (finfo.st_mode))
213                 {
214                   char *newpath, *filename_only, *newtemp;
215
216                   newpath = xstrdup (temp);
217                   filename_only = filename_non_directory (filename);
218                   newtemp = info_file_in_path (filename_only, newpath);
219
220                   free (newpath);
221                   if (newtemp)
222                     {
223                       free (temp);
224                       return (newtemp);
225                     }
226                 }
227             }
228           else
229             {
230               /* Add various compression suffixes to the name to see if
231                  the file is present in compressed format. */
232               register int j, pre_compress_suffix_length;
233
234               pre_compress_suffix_length = strlen (temp);
235
236               for (j = 0; compress_suffixes[j].suffix; j++)
237                 {
238                   strcpy (temp + pre_compress_suffix_length,
239                           compress_suffixes[j].suffix);
240
241                   statable = (stat (temp, &finfo) == 0);
242                   if (statable && (S_ISREG (finfo.st_mode)))
243                     return (temp);
244                 }
245             }
246         }
247       free (temp);
248     }
249   return ((char *)NULL);
250 }
251
252 /* Assume FNAME is an absolute file name, and check whether it is
253    a regular file.  If it is, return it as a new string; otherwise
254    return a NULL pointer.  We do it by taking the file name apart
255    into its directory and basename parts, and calling info_file_in_path.*/
256 static char *
257 info_absolute_file (fname)
258      char *fname;
259 {
260   char *containing_dir = xstrdup (fname);
261   char *base = filename_non_directory (containing_dir);
262
263   if (base > containing_dir)
264     base[-1] = '\0';
265
266   return info_file_in_path (filename_non_directory (fname), containing_dir);
267 }
268
269 /* Given a string containing units of information separated by
270    the PATH_SEP character, return the next one pointed to by
271    IDX, or NULL if there are no more.
272    Advance IDX to the character after the colon. */
273 char *
274 extract_colon_unit (string, idx)
275      char *string;
276      int *idx;
277 {
278   register int i, start;
279
280   i = start = *idx;
281   if ((i >= strlen (string)) || !string)
282     return ((char *) NULL);
283
284   while (string[i] && string[i] != PATH_SEP[0])
285     i++;
286   if (i == start)
287     {
288       return ((char *) NULL);
289     }
290   else
291     {
292       char *value;
293
294       value = (char *) xmalloc (1 + (i - start));
295       strncpy (value, &string[start], (i - start));
296       value[i - start] = '\0';
297       if (string[i])
298         ++i;
299       *idx = i;
300       return (value);
301     }
302 }
303
304 /* A structure which associates a filename with its expansion. */
305 typedef struct {
306   char *filename;
307   char *expansion;
308 } FILENAME_LIST;
309
310 /* An array of remembered arguments and results. */
311 static FILENAME_LIST **names_and_files = (FILENAME_LIST **)NULL;
312 static int names_and_files_index = 0;
313 static int names_and_files_slots = 0;
314
315 /* Find the result for having already called info_find_fullpath () with
316    FILENAME. */
317 static char *
318 lookup_info_filename (filename)
319      char *filename;
320 {
321   if (filename && names_and_files)
322     {
323       register int i;
324       for (i = 0; names_and_files[i]; i++)
325         {
326           if (FILENAME_CMP (names_and_files[i]->filename, filename) == 0)
327             return (names_and_files[i]->expansion);
328         }
329     }
330   return (char *)NULL;;
331 }
332
333 /* Add a filename and its expansion to our list. */
334 static void
335 remember_info_filename (filename, expansion)
336      char *filename, *expansion;
337 {
338   FILENAME_LIST *new;
339
340   if (names_and_files_index + 2 > names_and_files_slots)
341     {
342       int alloc_size;
343       names_and_files_slots += 10;
344
345       alloc_size = names_and_files_slots * sizeof (FILENAME_LIST *);
346
347       names_and_files =
348         (FILENAME_LIST **) xrealloc (names_and_files, alloc_size);
349     }
350
351   new = (FILENAME_LIST *)xmalloc (sizeof (FILENAME_LIST));
352   new->filename = xstrdup (filename);
353   new->expansion = expansion ? xstrdup (expansion) : (char *)NULL;
354
355   names_and_files[names_and_files_index++] = new;
356   names_and_files[names_and_files_index] = (FILENAME_LIST *)NULL;
357 }
358
359 static void
360 maybe_initialize_infopath ()
361 {
362   if (!infopath_size)
363     {
364       infopath = (char *)
365         xmalloc (infopath_size = (1 + strlen (DEFAULT_INFOPATH)));
366
367       strcpy (infopath, DEFAULT_INFOPATH);
368     }
369 }
370
371 /* Add PATH to the list of paths found in INFOPATH.  2nd argument says
372    whether to put PATH at the front or end of INFOPATH. */
373 void
374 info_add_path (path, where)
375      char *path;
376      int where;
377 {
378   int len;
379
380   if (!infopath)
381     {
382       infopath = (char *)xmalloc (infopath_size = 200 + strlen (path));
383       infopath[0] = '\0';
384     }
385
386   len = strlen (path) + strlen (infopath);
387
388   if (len + 2 >= infopath_size)
389     infopath = (char *)xrealloc (infopath, (infopath_size += (2 * len) + 2));
390
391   if (!*infopath)
392     strcpy (infopath, path);
393   else if (where == INFOPATH_APPEND)
394     {
395       strcat (infopath, PATH_SEP);
396       strcat (infopath, path);
397     }
398   else if (where == INFOPATH_PREPEND)
399     {
400       char *temp = xstrdup (infopath);
401       strcpy (infopath, path);
402       strcat (infopath, PATH_SEP);
403       strcat (infopath, temp);
404       free (temp);
405     }
406 }
407
408 /* Make INFOPATH have absolutely nothing in it. */
409 void
410 zap_infopath ()
411 {
412   if (infopath)
413     free (infopath);
414
415   infopath = (char *)NULL;
416   infopath_size = 0;
417 }
418
419 /* Given a chunk of text and its length, convert all CRLF pairs at every
420    end-of-line into a single Newline character.  Return the length of
421    produced text.
422
423    This is required because the rest of code is too entrenched in having
424    a single newline at each EOL; in particular, searching for various
425    Info headers and cookies can become extremely tricky if that assumption
426    breaks.
427
428    FIXME: this could also support Mac-style text files with a single CR
429    at the EOL, but what about random CR characters in non-Mac files?  Can
430    we afford converting them into newlines as well?  Maybe implement some
431    heuristics here, like in Emacs 20.
432
433    FIXME: is it a good idea to show the EOL type on the modeline?  */
434 long
435 convert_eols (text, textlen)
436      char *text;
437      long textlen;
438 {
439   register char *s = text;
440   register char *d = text;
441
442   while (textlen--)
443     {
444       if (*s == '\r' && textlen && s[1] == '\n')
445         {
446           s++;
447           textlen--;
448         }
449       *d++ = *s++;
450     }
451
452   return (long)(d - text);
453 }
454
455 /* Read the contents of PATHNAME, returning a buffer with the contents of
456    that file in it, and returning the size of that buffer in FILESIZE.
457    FINFO is a stat struct which has already been filled in by the caller.
458    If the file turns out to be compressed, set IS_COMPRESSED to non-zero.
459    If the file cannot be read, return a NULL pointer. */
460 char *
461 filesys_read_info_file (pathname, filesize, finfo, is_compressed)
462      char *pathname;
463      long *filesize;
464      struct stat *finfo;
465      int *is_compressed;
466 {
467   long st_size;
468
469   *filesize = filesys_error_number = 0;
470
471   if (compressed_filename_p (pathname))
472     {
473       *is_compressed = 1;
474       return (filesys_read_compressed (pathname, filesize, finfo));
475     }
476   else
477     {
478       int descriptor;
479       char *contents;
480
481       *is_compressed = 0;
482       descriptor = open (pathname, O_RDONLY | O_BINARY, 0666);
483
484       /* If the file couldn't be opened, give up. */
485       if (descriptor < 0)
486         {
487           filesys_error_number = errno;
488           return ((char *)NULL);
489         }
490
491       /* Try to read the contents of this file. */
492       st_size = (long) finfo->st_size;
493       contents = (char *)xmalloc (1 + st_size);
494       if ((read (descriptor, contents, st_size)) != st_size)
495         {
496           filesys_error_number = errno;
497           close (descriptor);
498           free (contents);
499           return ((char *)NULL);
500         }
501
502       close (descriptor);
503
504       /* Convert any DOS-style CRLF EOLs into Unix-style NL.
505          Seems like a good idea to have even on Unix, in case the Info
506          files are coming from some Windows system across a network.  */
507       *filesize = convert_eols (contents, st_size);
508
509       /* EOL conversion can shrink the text quite a bit.  We don't
510          want to waste storage.  */
511       if (*filesize < st_size)
512         contents = (char *)xrealloc (contents, 1 + *filesize);
513       contents[*filesize] = '\0';
514
515       return (contents);
516     }
517 }
518
519 /* Typically, pipe buffers are 4k. */
520 #define BASIC_PIPE_BUFFER (4 * 1024)
521
522 /* We use some large multiple of that. */
523 #define FILESYS_PIPE_BUFFER_SIZE (16 * BASIC_PIPE_BUFFER)
524
525 char *
526 filesys_read_compressed (pathname, filesize, finfo)
527      char *pathname;
528      long *filesize;
529      struct stat *finfo;
530 {
531   FILE *stream;
532   char *command, *decompressor;
533   char *contents = (char *)NULL;
534
535   *filesize = filesys_error_number = 0;
536
537   decompressor = filesys_decompressor_for_file (pathname);
538
539   if (!decompressor)
540     return ((char *)NULL);
541
542   command = (char *)xmalloc (15 + strlen (pathname) + strlen (decompressor));
543   /* Explicit .exe suffix makes the diagnostics of `popen'
544      better on systems where COMMAND.COM is the stock shell.  */
545   sprintf (command, "%s%s < %s",
546            decompressor, STRIP_DOT_EXE ? ".exe" : "", pathname);
547
548 #if !defined (BUILDING_LIBRARY)
549   if (info_windows_initialized_p)
550     {
551       char *temp;
552
553       temp = (char *)xmalloc (5 + strlen (command));
554       sprintf (temp, "%s...", command);
555       message_in_echo_area ("%s", temp);
556       free (temp);
557     }
558 #endif /* !BUILDING_LIBRARY */
559
560   stream = popen (command, FOPEN_RBIN);
561   free (command);
562
563   /* Read chunks from this file until there are none left to read. */
564   if (stream)
565     {
566       long offset, size;
567       char *chunk;
568     
569       offset = size = 0;
570       chunk = (char *)xmalloc (FILESYS_PIPE_BUFFER_SIZE);
571
572       while (1)
573         {
574           int bytes_read;
575
576           bytes_read = fread (chunk, 1, FILESYS_PIPE_BUFFER_SIZE, stream);
577
578           if (bytes_read + offset >= size)
579             contents = (char *)xrealloc
580               (contents, size += (2 * FILESYS_PIPE_BUFFER_SIZE));
581
582           memcpy (contents + offset, chunk, bytes_read);
583           offset += bytes_read;
584           if (bytes_read != FILESYS_PIPE_BUFFER_SIZE)
585             break;
586         }
587
588       free (chunk);
589       if (pclose (stream) == -1)
590         {
591           if (contents)
592             free (contents);
593           contents = (char *)NULL;
594           filesys_error_number = errno;
595         }
596       else
597         {
598           *filesize = convert_eols (contents, offset);
599           contents = (char *)xrealloc (contents, 1 + *filesize);
600           contents[*filesize] = '\0';
601         }
602     }
603   else
604     {
605       filesys_error_number = errno;
606     }
607
608 #if !defined (BUILDING_LIBARARY)
609   if (info_windows_initialized_p)
610     unmessage_in_echo_area ();
611 #endif /* !BUILDING_LIBRARY */
612   return (contents);
613 }
614
615 /* Return non-zero if FILENAME belongs to a compressed file. */
616 int
617 compressed_filename_p (filename)
618      char *filename;
619 {
620   char *decompressor;
621
622   /* Find the final extension of this filename, and see if it matches one
623      of our known ones. */
624   decompressor = filesys_decompressor_for_file (filename);
625
626   if (decompressor)
627     return (1);
628   else
629     return (0);
630 }
631
632 /* Return the command string that would be used to decompress FILENAME. */
633 char *
634 filesys_decompressor_for_file (filename)
635      char *filename;
636 {
637   register int i;
638   char *extension = (char *)NULL;
639
640   /* Find the final extension of FILENAME, and see if it appears in our
641      list of known compression extensions. */
642   for (i = strlen (filename) - 1; i > 0; i--)
643     if (filename[i] == '.')
644       {
645         extension = filename + i;
646         break;
647       }
648
649   if (!extension)
650     return ((char *)NULL);
651
652   for (i = 0; compress_suffixes[i].suffix; i++)
653     if (FILENAME_CMP (extension, compress_suffixes[i].suffix) == 0)
654       return (compress_suffixes[i].decompressor);
655
656 #if defined (__MSDOS__)
657   /* If no other suffix matched, allow any extension which ends
658      with `z' to be decompressed by gunzip.  Due to limited 8+3 DOS
659      file namespace, we can expect many such cases, and supporting
660      every weird suffix thus produced would be a pain.  */
661   if (extension[strlen (extension) - 1] == 'z' ||
662       extension[strlen (extension) - 1] == 'Z')
663     return "gunzip";
664 #endif
665
666   return ((char *)NULL);
667 }
668
669 /* The number of the most recent file system error. */
670 int filesys_error_number = 0;
671
672 /* A function which returns a pointer to a static buffer containing
673    an error message for FILENAME and ERROR_NUM. */
674 static char *errmsg_buf = (char *)NULL;
675 static int errmsg_buf_size = 0;
676
677 char *
678 filesys_error_string (filename, error_num)
679      char *filename;
680      int error_num;
681 {
682   int len;
683   char *result;
684
685   if (error_num == 0)
686     return ((char *)NULL);
687
688   result = strerror (error_num);
689
690   len = 4 + strlen (filename) + strlen (result);
691   if (len >= errmsg_buf_size)
692     errmsg_buf = (char *)xrealloc (errmsg_buf, (errmsg_buf_size = 2 + len));
693
694   sprintf (errmsg_buf, "%s: %s", filename, result);
695   return (errmsg_buf);
696 }
697
698 \f
699 /* Check for "dir" with all the possible info and compression suffixes,
700    in combination.  */
701
702 int
703 is_dir_name (filename)
704     char *filename;
705 {
706   unsigned i;
707
708   for (i = 0; info_suffixes[i]; i++)
709     {
710       unsigned c;
711       char trydir[50];
712       strcpy (trydir, "dir");
713       strcat (trydir, info_suffixes[i]);
714       
715       if (strcasecmp (filename, trydir) == 0)
716         return 1;
717
718       for (c = 0; compress_suffixes[c].suffix; c++)
719         {
720           char dir_compressed[50]; /* can be short */
721           strcpy (dir_compressed, trydir); 
722           strcat (dir_compressed, compress_suffixes[c].suffix);
723           if (strcasecmp (filename, dir_compressed) == 0)
724             return 1;
725         }
726     }  
727
728   return 0;
729 }