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