xref: /openbsd-src/gnu/usr.bin/texinfo/info/filesys.c (revision b2ea75c1b17e1a9a339660e7ed45cd24946b230e)
1 /* filesys.c -- filesystem specific functions.
2    $Id: filesys.c,v 1.3 2000/02/09 02:18:39 espie Exp $
3 
4    Copyright (C) 1993, 97, 98 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   dirname_index = 0;
167 
168   while ((temp_dirname = extract_colon_unit (path, &dirname_index)))
169     {
170       register int i, pre_suffix_length;
171       char *temp;
172 
173       /* Expand a leading tilde if one is present. */
174       if (*temp_dirname == '~')
175         {
176           char *expanded_dirname;
177 
178           expanded_dirname = tilde_expand_word (temp_dirname);
179           free (temp_dirname);
180           temp_dirname = expanded_dirname;
181         }
182 
183       temp = (char *)xmalloc (30 + strlen (temp_dirname) + strlen (filename));
184       strcpy (temp, temp_dirname);
185       if (!IS_SLASH (temp[(strlen (temp)) - 1]))
186         strcat (temp, "/");
187       strcat (temp, filename);
188 
189       pre_suffix_length = strlen (temp);
190 
191       free (temp_dirname);
192 
193       for (i = 0; info_suffixes[i]; i++)
194         {
195           strcpy (temp + pre_suffix_length, info_suffixes[i]);
196 
197           statable = (stat (temp, &finfo) == 0);
198 
199           /* If we have found a regular file, then use that.  Else, if we
200              have found a directory, look in that directory for this file. */
201           if (statable)
202             {
203               if (S_ISREG (finfo.st_mode))
204                 {
205                   return (temp);
206                 }
207               else if (S_ISDIR (finfo.st_mode))
208                 {
209                   char *newpath, *filename_only, *newtemp;
210 
211                   newpath = xstrdup (temp);
212                   filename_only = filename_non_directory (filename);
213                   newtemp = info_file_in_path (filename_only, newpath);
214 
215                   free (newpath);
216                   if (newtemp)
217                     {
218                       free (temp);
219                       return (newtemp);
220                     }
221                 }
222             }
223           else
224             {
225               /* Add various compression suffixes to the name to see if
226                  the file is present in compressed format. */
227               register int j, pre_compress_suffix_length;
228 
229               pre_compress_suffix_length = strlen (temp);
230 
231               for (j = 0; compress_suffixes[j].suffix; j++)
232                 {
233                   strcpy (temp + pre_compress_suffix_length,
234                           compress_suffixes[j].suffix);
235 
236                   statable = (stat (temp, &finfo) == 0);
237                   if (statable && (S_ISREG (finfo.st_mode)))
238                     return (temp);
239                 }
240             }
241         }
242       free (temp);
243     }
244   return ((char *)NULL);
245 }
246 
247 /* Assume FNAME is an absolute file name, and check whether it is
248    a regular file.  If it is, return it as a new string; otherwise
249    return a NULL pointer.  We do it by taking the file name apart
250    into its directory and basename parts, and calling info_file_in_path.*/
251 static char *
252 info_absolute_file (fname)
253      char *fname;
254 {
255   char *containing_dir = xstrdup (fname);
256   char *base = filename_non_directory (containing_dir);
257 
258   if (base > containing_dir)
259     base[-1] = '\0';
260 
261   return info_file_in_path (filename_non_directory (fname), containing_dir);
262 }
263 
264 /* Given a string containing units of information separated by
265    the PATH_SEP character, return the next one pointed to by
266    IDX, or NULL if there are no more.
267    Advance IDX to the character after the colon. */
268 char *
269 extract_colon_unit (string, idx)
270      char *string;
271      int *idx;
272 {
273   register int i, start;
274 
275   i = start = *idx;
276   if ((i >= strlen (string)) || !string)
277     return ((char *) NULL);
278 
279   while (string[i] && string[i] != PATH_SEP[0])
280     i++;
281   if (i == start)
282     {
283       return ((char *) NULL);
284     }
285   else
286     {
287       char *value;
288 
289       value = (char *) xmalloc (1 + (i - start));
290       strncpy (value, &string[start], (i - start));
291       value[i - start] = '\0';
292       if (string[i])
293         ++i;
294       *idx = i;
295       return (value);
296     }
297 }
298 
299 /* A structure which associates a filename with its expansion. */
300 typedef struct {
301   char *filename;
302   char *expansion;
303 } FILENAME_LIST;
304 
305 /* An array of remembered arguments and results. */
306 static FILENAME_LIST **names_and_files = (FILENAME_LIST **)NULL;
307 static int names_and_files_index = 0;
308 static int names_and_files_slots = 0;
309 
310 /* Find the result for having already called info_find_fullpath () with
311    FILENAME. */
312 static char *
313 lookup_info_filename (filename)
314      char *filename;
315 {
316   if (filename && names_and_files)
317     {
318       register int i;
319       for (i = 0; names_and_files[i]; i++)
320         {
321           if (FILENAME_CMP (names_and_files[i]->filename, filename) == 0)
322             return (names_and_files[i]->expansion);
323         }
324     }
325   return (char *)NULL;;
326 }
327 
328 /* Add a filename and its expansion to our list. */
329 static void
330 remember_info_filename (filename, expansion)
331      char *filename, *expansion;
332 {
333   FILENAME_LIST *new;
334 
335   if (names_and_files_index + 2 > names_and_files_slots)
336     {
337       int alloc_size;
338       names_and_files_slots += 10;
339 
340       alloc_size = names_and_files_slots * sizeof (FILENAME_LIST *);
341 
342       names_and_files =
343         (FILENAME_LIST **) xrealloc (names_and_files, alloc_size);
344     }
345 
346   new = (FILENAME_LIST *)xmalloc (sizeof (FILENAME_LIST));
347   new->filename = xstrdup (filename);
348   new->expansion = expansion ? xstrdup (expansion) : (char *)NULL;
349 
350   names_and_files[names_and_files_index++] = new;
351   names_and_files[names_and_files_index] = (FILENAME_LIST *)NULL;
352 }
353 
354 static void
355 maybe_initialize_infopath ()
356 {
357   if (!infopath_size)
358     {
359       infopath = (char *)
360         xmalloc (infopath_size = (1 + strlen (DEFAULT_INFOPATH)));
361 
362       strcpy (infopath, DEFAULT_INFOPATH);
363     }
364 }
365 
366 /* Add PATH to the list of paths found in INFOPATH.  2nd argument says
367    whether to put PATH at the front or end of INFOPATH. */
368 void
369 info_add_path (path, where)
370      char *path;
371      int where;
372 {
373   int len;
374 
375   if (!infopath)
376     {
377       infopath = (char *)xmalloc (infopath_size = 200 + strlen (path));
378       infopath[0] = '\0';
379     }
380 
381   len = strlen (path) + strlen (infopath);
382 
383   if (len + 2 >= infopath_size)
384     infopath = (char *)xrealloc (infopath, (infopath_size += (2 * len) + 2));
385 
386   if (!*infopath)
387     strcpy (infopath, path);
388   else if (where == INFOPATH_APPEND)
389     {
390       strcat (infopath, PATH_SEP);
391       strcat (infopath, path);
392     }
393   else if (where == INFOPATH_PREPEND)
394     {
395       char *temp = xstrdup (infopath);
396       strcpy (infopath, path);
397       strcat (infopath, PATH_SEP);
398       strcat (infopath, temp);
399       free (temp);
400     }
401 }
402 
403 /* Make INFOPATH have absolutely nothing in it. */
404 void
405 zap_infopath ()
406 {
407   if (infopath)
408     free (infopath);
409 
410   infopath = (char *)NULL;
411   infopath_size = 0;
412 }
413 
414 /* Given a chunk of text and its length, convert all CRLF pairs at every
415    end-of-line into a single Newline character.  Return the length of
416    produced text.
417 
418    This is required because the rest of code is too entrenched in having
419    a single newline at each EOL; in particular, searching for various
420    Info headers and cookies can become extremely tricky if that assumption
421    breaks.
422 
423    FIXME: this could also support Mac-style text files with a single CR
424    at the EOL, but what about random CR characters in non-Mac files?  Can
425    we afford converting them into newlines as well?  Maybe implement some
426    heuristics here, like in Emacs 20.
427 
428    FIXME: is it a good idea to show the EOL type on the modeline?  */
429 long
430 convert_eols (text, textlen)
431      char *text;
432      long textlen;
433 {
434   register char *s = text;
435   register char *d = text;
436 
437   while (textlen--)
438     {
439       if (*s == '\r' && textlen && s[1] == '\n')
440 	{
441 	  s++;
442 	  textlen--;
443 	}
444       *d++ = *s++;
445     }
446 
447   return (long)(d - text);
448 }
449 
450 /* Read the contents of PATHNAME, returning a buffer with the contents of
451    that file in it, and returning the size of that buffer in FILESIZE.
452    FINFO is a stat struct which has already been filled in by the caller.
453    If the file turns out to be compressed, set IS_COMPRESSED to non-zero.
454    If the file cannot be read, return a NULL pointer. */
455 char *
456 filesys_read_info_file (pathname, filesize, finfo, is_compressed)
457      char *pathname;
458      long *filesize;
459      struct stat *finfo;
460      int *is_compressed;
461 {
462   long st_size;
463 
464   *filesize = filesys_error_number = 0;
465 
466   if (compressed_filename_p (pathname))
467     {
468       *is_compressed = 1;
469       return (filesys_read_compressed (pathname, filesize, finfo));
470     }
471   else
472     {
473       int descriptor;
474       char *contents;
475 
476       *is_compressed = 0;
477       descriptor = open (pathname, O_RDONLY | O_BINARY, 0666);
478 
479       /* If the file couldn't be opened, give up. */
480       if (descriptor < 0)
481         {
482           filesys_error_number = errno;
483           return ((char *)NULL);
484         }
485 
486       /* Try to read the contents of this file. */
487       st_size = (long) finfo->st_size;
488       contents = (char *)xmalloc (1 + st_size);
489       if ((read (descriptor, contents, st_size)) != st_size)
490         {
491 	  filesys_error_number = errno;
492 	  close (descriptor);
493 	  free (contents);
494 	  return ((char *)NULL);
495         }
496 
497       close (descriptor);
498 
499       /* Convert any DOS-style CRLF EOLs into Unix-style NL.
500 	 Seems like a good idea to have even on Unix, in case the Info
501 	 files are coming from some Windows system across a network.  */
502       *filesize = convert_eols (contents, st_size);
503 
504       /* EOL conversion can shrink the text quite a bit.  We don't
505 	 want to waste storage.  */
506       if (*filesize < st_size)
507 	contents = (char *)xrealloc (contents, 1 + *filesize);
508 
509       return (contents);
510     }
511 }
512 
513 /* Typically, pipe buffers are 4k. */
514 #define BASIC_PIPE_BUFFER (4 * 1024)
515 
516 /* We use some large multiple of that. */
517 #define FILESYS_PIPE_BUFFER_SIZE (16 * BASIC_PIPE_BUFFER)
518 
519 char *
520 filesys_read_compressed (pathname, filesize, finfo)
521      char *pathname;
522      long *filesize;
523      struct stat *finfo;
524 {
525   FILE *stream;
526   char *command, *decompressor;
527   char *contents = (char *)NULL;
528 
529   *filesize = filesys_error_number = 0;
530 
531   decompressor = filesys_decompressor_for_file (pathname);
532 
533   if (!decompressor)
534     return ((char *)NULL);
535 
536   command = (char *)xmalloc (15 + strlen (pathname) + strlen (decompressor));
537   /* Explicit .exe suffix makes the diagnostics of `popen'
538      better on systems where COMMAND.COM is the stock shell.  */
539   sprintf (command, "%s%s < %s",
540 	   decompressor, STRIP_DOT_EXE ? ".exe" : "", pathname);
541 
542 #if !defined (BUILDING_LIBRARY)
543   if (info_windows_initialized_p)
544     {
545       char *temp;
546 
547       temp = (char *)xmalloc (5 + strlen (command));
548       sprintf (temp, "%s...", command);
549       message_in_echo_area ("%s", temp);
550       free (temp);
551     }
552 #endif /* !BUILDING_LIBRARY */
553 
554   stream = popen (command, FOPEN_RBIN);
555   free (command);
556 
557   /* Read chunks from this file until there are none left to read. */
558   if (stream)
559     {
560       int offset, size;
561       char *chunk;
562 
563       offset = size = 0;
564       chunk = (char *)xmalloc (FILESYS_PIPE_BUFFER_SIZE);
565 
566       while (1)
567         {
568           int bytes_read;
569 
570           bytes_read = fread (chunk, 1, FILESYS_PIPE_BUFFER_SIZE, stream);
571 
572           if (bytes_read + offset >= size)
573             contents = (char *)xrealloc
574               (contents, size += (2 * FILESYS_PIPE_BUFFER_SIZE));
575 
576           memcpy (contents + offset, chunk, bytes_read);
577           offset += bytes_read;
578           if (bytes_read != FILESYS_PIPE_BUFFER_SIZE)
579             break;
580         }
581 
582       free (chunk);
583       if (pclose (stream) == -1)
584 	{
585 	  if (contents)
586 	    free (contents);
587 	  contents = (char *)NULL;
588 	  filesys_error_number = errno;
589 	}
590       else
591 	{
592 	  *filesize = convert_eols (contents, offset);
593 	  contents = (char *)xrealloc (contents, 1 + *filesize);
594 	}
595     }
596   else
597     {
598       filesys_error_number = errno;
599     }
600 
601 #if !defined (BUILDING_LIBARARY)
602   if (info_windows_initialized_p)
603     unmessage_in_echo_area ();
604 #endif /* !BUILDING_LIBRARY */
605   return (contents);
606 }
607 
608 /* Return non-zero if FILENAME belongs to a compressed file. */
609 int
610 compressed_filename_p (filename)
611      char *filename;
612 {
613   char *decompressor;
614 
615   /* Find the final extension of this filename, and see if it matches one
616      of our known ones. */
617   decompressor = filesys_decompressor_for_file (filename);
618 
619   if (decompressor)
620     return (1);
621   else
622     return (0);
623 }
624 
625 /* Return the command string that would be used to decompress FILENAME. */
626 char *
627 filesys_decompressor_for_file (filename)
628      char *filename;
629 {
630   register int i;
631   char *extension = (char *)NULL;
632 
633   /* Find the final extension of FILENAME, and see if it appears in our
634      list of known compression extensions. */
635   for (i = strlen (filename) - 1; i > 0; i--)
636     if (filename[i] == '.')
637       {
638         extension = filename + i;
639         break;
640       }
641 
642   if (!extension)
643     return ((char *)NULL);
644 
645   for (i = 0; compress_suffixes[i].suffix; i++)
646     if (FILENAME_CMP (extension, compress_suffixes[i].suffix) == 0)
647       return (compress_suffixes[i].decompressor);
648 
649 #if defined (__MSDOS__)
650   /* If no other suffix matched, allow any extension which ends
651      with `z' to be decompressed by gunzip.  Due to limited 8+3 DOS
652      file namespace, we can expect many such cases, and supporting
653      every weird suffix thus produced would be a pain.  */
654   if (extension[strlen (extension) - 1] == 'z' ||
655       extension[strlen (extension) - 1] == 'Z')
656     return "gunzip";
657 #endif
658 
659   return ((char *)NULL);
660 }
661 
662 /* The number of the most recent file system error. */
663 int filesys_error_number = 0;
664 
665 /* A function which returns a pointer to a static buffer containing
666    an error message for FILENAME and ERROR_NUM. */
667 static char *errmsg_buf = (char *)NULL;
668 static int errmsg_buf_size = 0;
669 
670 char *
671 filesys_error_string (filename, error_num)
672      char *filename;
673      int error_num;
674 {
675   int len;
676   char *result;
677 
678   if (error_num == 0)
679     return ((char *)NULL);
680 
681   result = strerror (error_num);
682 
683   len = 4 + strlen (filename) + strlen (result);
684   if (len >= errmsg_buf_size)
685     errmsg_buf = (char *)xrealloc (errmsg_buf, (errmsg_buf_size = 2 + len));
686 
687   sprintf (errmsg_buf, "%s: %s", filename, result);
688   return (errmsg_buf);
689 }
690 
691 
692 /* Check for FILENAME eq "dir" first, then all the compression
693    suffixes.  */
694 
695 int
696 is_dir_name (filename)
697     char *filename;
698 {
699   unsigned i;
700   if (strcasecmp (filename, "dir") == 0)
701     return 1;
702 
703   for (i = 0; compress_suffixes[i].suffix; i++)
704     {
705       char dir_compressed[50]; /* can be short */
706       strcpy (dir_compressed, "dir");
707       strcat (dir_compressed, compress_suffixes[i].suffix);
708       if (strcasecmp (filename, dir_compressed) == 0)
709         return 1;
710     }
711 
712   return 0;
713 }
714