Home | History | Annotate | Line # | Download | only in info
filesys.c revision 1.1
      1 /*	$NetBSD: filesys.c,v 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 (at) 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 *
     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 *
    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 *
    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 *
    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 *
    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
    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
    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
    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
    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
    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 *
    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 *
    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
    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 *
    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 *
    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,
    686    in combination.  */
    687 
    688 int
    689 is_dir_name (char *filename)
    690 {
    691   unsigned i;
    692 
    693   for (i = 0; info_suffixes[i]; i++)
    694     {
    695       unsigned c;
    696       char trydir[50];
    697       strcpy (trydir, "dir");
    698       strcat (trydir, info_suffixes[i]);
    699 
    700       if (strcasecmp (filename, trydir) == 0)
    701         return 1;
    702 
    703       for (c = 0; compress_suffixes[c].suffix; c++)
    704         {
    705           char dir_compressed[50]; /* can be short */
    706           strcpy (dir_compressed, trydir);
    707           strcat (dir_compressed, compress_suffixes[c].suffix);
    708           if (strcasecmp (filename, dir_compressed) == 0)
    709             return 1;
    710         }
    711     }
    712 
    713   return 0;
    714 }
    715