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