Back to index

tetex-bin  3.0
filesys.c
Go to the documentation of this file.
00001 /* filesys.c -- filesystem specific functions.
00002    $Id: filesys.c,v 1.6 2004/07/30 17:17:40 karl Exp $
00003 
00004    Copyright (C) 1993, 1997, 1998, 2000, 2002, 2003, 2004 Free Software
00005    Foundation, Inc.
00006 
00007    This program is free software; you can redistribute it and/or modify
00008    it under the terms of the GNU General Public License as published by
00009    the Free Software Foundation; either version 2, or (at your option)
00010    any later version.
00011 
00012    This program is distributed in the hope that it will be useful,
00013    but WITHOUT ANY WARRANTY; without even the implied warranty of
00014    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00015    GNU General Public License for more details.
00016 
00017    You should have received a copy of the GNU General Public License
00018    along with this program; if not, write to the Free Software
00019    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
00020 
00021    Written by Brian Fox (bfox@ai.mit.edu). */
00022 
00023 #include "info.h"
00024 
00025 #include "tilde.h"
00026 #include "filesys.h"
00027 
00028 /* Local to this file. */
00029 static char *info_file_in_path (char *filename, char *path);
00030 static char *lookup_info_filename (char *filename);
00031 static char *info_absolute_file (char *fname);
00032 
00033 static void remember_info_filename (char *filename, char *expansion);
00034 static void maybe_initialize_infopath (void);
00035 
00036 typedef struct
00037 {
00038   char *suffix;
00039   char *decompressor;
00040 } COMPRESSION_ALIST;
00041 
00042 static char *info_suffixes[] = {
00043   ".info",
00044   "-info",
00045   "/index",
00046   ".inf",       /* 8+3 file on filesystem which supports long file names */
00047 #ifdef __MSDOS__
00048   /* 8+3 file names strike again...  */
00049   ".in",        /* for .inz, .igz etc. */
00050   ".i",
00051 #endif
00052   "",
00053   NULL
00054 };
00055 
00056 static COMPRESSION_ALIST compress_suffixes[] = {
00057   { ".gz", "gunzip" },
00058   { ".bz2", "bunzip2" },
00059   { ".z", "gunzip" },
00060   { ".Z", "uncompress" },
00061   { ".Y", "unyabba" },
00062 #ifdef __MSDOS__
00063   { "gz", "gunzip" },
00064   { "z", "gunzip" },
00065 #endif
00066   { (char *)NULL, (char *)NULL }
00067 };
00068 
00069 /* The path on which we look for info files.  You can initialize this
00070    from the environment variable INFOPATH if there is one, or you can
00071    call info_add_path () to add paths to the beginning or end of it.
00072    You can call zap_infopath () to make the path go away. */
00073 char *infopath = (char *)NULL;
00074 static int infopath_size = 0;
00075 
00076 /* Expand the filename in PARTIAL to make a real name for this operating
00077    system.  This looks in INFO_PATHS in order to find the correct file.
00078    If it can't find the file, it returns NULL. */
00079 static char *local_temp_filename = (char *)NULL;
00080 static int local_temp_filename_size = 0;
00081 
00082 char *
00083 info_find_fullpath (char *partial)
00084 {
00085   int initial_character;
00086   char *temp;
00087 
00088   filesys_error_number = 0;
00089 
00090   maybe_initialize_infopath ();
00091 
00092   if (partial && (initial_character = *partial))
00093     {
00094       char *expansion;
00095 
00096       expansion = lookup_info_filename (partial);
00097 
00098       if (expansion)
00099         return (expansion);
00100 
00101       /* If we have the full path to this file, we still may have to add
00102          various extensions to it.  I guess we have to stat this file
00103          after all. */
00104       if (IS_ABSOLUTE (partial))
00105        temp = info_absolute_file (partial);
00106       else if (initial_character == '~')
00107         {
00108           expansion = tilde_expand_word (partial);
00109           if (IS_ABSOLUTE (expansion))
00110             {
00111               temp = info_absolute_file (expansion);
00112               free (expansion);
00113             }
00114           else
00115             temp = expansion;
00116         }
00117       else if (initial_character == '.' &&
00118                (IS_SLASH (partial[1]) ||
00119               (partial[1] == '.' && IS_SLASH (partial[2]))))
00120         {
00121           if (local_temp_filename_size < 1024)
00122             local_temp_filename = (char *)xrealloc
00123               (local_temp_filename, (local_temp_filename_size = 1024));
00124 #if defined (HAVE_GETCWD)
00125           if (!getcwd (local_temp_filename, local_temp_filename_size))
00126 #else /*  !HAVE_GETCWD */
00127           if (!getwd (local_temp_filename))
00128 #endif /* !HAVE_GETCWD */
00129             {
00130               filesys_error_number = errno;
00131               return (partial);
00132             }
00133 
00134           strcat (local_temp_filename, "/");
00135           strcat (local_temp_filename, partial);
00136          temp = info_absolute_file (local_temp_filename); /* try extensions */
00137          if (!temp)
00138            partial = local_temp_filename;
00139         }
00140       else
00141         temp = info_file_in_path (partial, infopath);
00142 
00143       if (temp)
00144         {
00145           remember_info_filename (partial, temp);
00146           if (strlen (temp) > (unsigned int) local_temp_filename_size)
00147             local_temp_filename = (char *) xrealloc
00148               (local_temp_filename,
00149                (local_temp_filename_size = (50 + strlen (temp))));
00150           strcpy (local_temp_filename, temp);
00151           free (temp);
00152           return (local_temp_filename);
00153         }
00154     }
00155   return (partial);
00156 }
00157 
00158 /* Scan the list of directories in PATH looking for FILENAME.  If we find
00159    one that is a regular file, return it as a new string.  Otherwise, return
00160    a NULL pointer. */
00161 static char *
00162 info_file_in_path (char *filename, char *path)
00163 {
00164   struct stat finfo;
00165   char *temp_dirname;
00166   int statable, dirname_index;
00167 
00168   /* Reject ridiculous cases up front, to prevent infinite recursion
00169      later on.  E.g., someone might say "info '(.)foo'"...  */
00170   if (!*filename || STREQ (filename, ".") || STREQ (filename, ".."))
00171     return NULL;
00172 
00173   dirname_index = 0;
00174 
00175   while ((temp_dirname = extract_colon_unit (path, &dirname_index)))
00176     {
00177       register int i, pre_suffix_length;
00178       char *temp;
00179 
00180       /* Expand a leading tilde if one is present. */
00181       if (*temp_dirname == '~')
00182         {
00183           char *expanded_dirname;
00184 
00185           expanded_dirname = tilde_expand_word (temp_dirname);
00186           free (temp_dirname);
00187           temp_dirname = expanded_dirname;
00188         }
00189 
00190       temp = (char *)xmalloc (30 + strlen (temp_dirname) + strlen (filename));
00191       strcpy (temp, temp_dirname);
00192       if (!IS_SLASH (temp[(strlen (temp)) - 1]))
00193         strcat (temp, "/");
00194       strcat (temp, filename);
00195 
00196       pre_suffix_length = strlen (temp);
00197 
00198       free (temp_dirname);
00199 
00200       for (i = 0; info_suffixes[i]; i++)
00201         {
00202           strcpy (temp + pre_suffix_length, info_suffixes[i]);
00203 
00204           statable = (stat (temp, &finfo) == 0);
00205 
00206           /* If we have found a regular file, then use that.  Else, if we
00207              have found a directory, look in that directory for this file. */
00208           if (statable)
00209             {
00210               if (S_ISREG (finfo.st_mode))
00211                 {
00212                   return (temp);
00213                 }
00214               else if (S_ISDIR (finfo.st_mode))
00215                 {
00216                   char *newpath, *filename_only, *newtemp;
00217 
00218                   newpath = xstrdup (temp);
00219                   filename_only = filename_non_directory (filename);
00220                   newtemp = info_file_in_path (filename_only, newpath);
00221 
00222                   free (newpath);
00223                   if (newtemp)
00224                     {
00225                       free (temp);
00226                       return (newtemp);
00227                     }
00228                 }
00229             }
00230           else
00231             {
00232               /* Add various compression suffixes to the name to see if
00233                  the file is present in compressed format. */
00234               register int j, pre_compress_suffix_length;
00235 
00236               pre_compress_suffix_length = strlen (temp);
00237 
00238               for (j = 0; compress_suffixes[j].suffix; j++)
00239                 {
00240                   strcpy (temp + pre_compress_suffix_length,
00241                           compress_suffixes[j].suffix);
00242 
00243                   statable = (stat (temp, &finfo) == 0);
00244                   if (statable && (S_ISREG (finfo.st_mode)))
00245                     return (temp);
00246                 }
00247             }
00248         }
00249       free (temp);
00250     }
00251   return ((char *)NULL);
00252 }
00253 
00254 /* Assume FNAME is an absolute file name, and check whether it is
00255    a regular file.  If it is, return it as a new string; otherwise
00256    return a NULL pointer.  We do it by taking the file name apart
00257    into its directory and basename parts, and calling info_file_in_path.*/
00258 static char *
00259 info_absolute_file (char *fname)
00260 {
00261   char *containing_dir = xstrdup (fname);
00262   char *base = filename_non_directory (containing_dir);
00263 
00264   if (base > containing_dir)
00265     base[-1] = '\0';
00266 
00267   return info_file_in_path (filename_non_directory (fname), containing_dir);
00268 }
00269 
00270 
00271 /* Given a string containing units of information separated by the
00272    PATH_SEP character, return the next one after IDX, or NULL if there
00273    are no more.  Advance IDX to the character after the colon. */
00274 
00275 char *
00276 extract_colon_unit (char *string, int *idx)
00277 {
00278   unsigned int i = (unsigned int) *idx;
00279   unsigned int start = i;
00280 
00281   if (!string || i >= strlen (string))
00282     return NULL;
00283 
00284   if (!string[i]) /* end of string */
00285     return NULL;
00286 
00287   /* Advance to next PATH_SEP.  */
00288   while (string[i] && string[i] != PATH_SEP[0])
00289     i++;
00290 
00291   {
00292     char *value = xmalloc ((i - start) + 1);
00293     strncpy (value, &string[start], (i - start));
00294     value[i - start] = 0;
00295 
00296     i++; /* move past PATH_SEP */
00297     *idx = i;
00298     return value;
00299   }
00300 }
00301 
00302 /* A structure which associates a filename with its expansion. */
00303 typedef struct
00304 {
00305   char *filename;
00306   char *expansion;
00307 } FILENAME_LIST;
00308 
00309 /* An array of remembered arguments and results. */
00310 static FILENAME_LIST **names_and_files = (FILENAME_LIST **)NULL;
00311 static int names_and_files_index = 0;
00312 static int names_and_files_slots = 0;
00313 
00314 /* Find the result for having already called info_find_fullpath () with
00315    FILENAME. */
00316 static char *
00317 lookup_info_filename (char *filename)
00318 {
00319   if (filename && names_and_files)
00320     {
00321       register int i;
00322       for (i = 0; names_and_files[i]; i++)
00323         {
00324           if (FILENAME_CMP (names_and_files[i]->filename, filename) == 0)
00325             return (names_and_files[i]->expansion);
00326         }
00327     }
00328   return (char *)NULL;;
00329 }
00330 
00331 /* Add a filename and its expansion to our list. */
00332 static void
00333 remember_info_filename (char *filename, char *expansion)
00334 {
00335   FILENAME_LIST *new;
00336 
00337   if (names_and_files_index + 2 > names_and_files_slots)
00338     {
00339       int alloc_size;
00340       names_and_files_slots += 10;
00341 
00342       alloc_size = names_and_files_slots * sizeof (FILENAME_LIST *);
00343 
00344       names_and_files =
00345         (FILENAME_LIST **) xrealloc (names_and_files, alloc_size);
00346     }
00347 
00348   new = (FILENAME_LIST *)xmalloc (sizeof (FILENAME_LIST));
00349   new->filename = xstrdup (filename);
00350   new->expansion = expansion ? xstrdup (expansion) : (char *)NULL;
00351 
00352   names_and_files[names_and_files_index++] = new;
00353   names_and_files[names_and_files_index] = (FILENAME_LIST *)NULL;
00354 }
00355 
00356 static void
00357 maybe_initialize_infopath (void)
00358 {
00359   if (!infopath_size)
00360     {
00361       infopath = (char *)
00362         xmalloc (infopath_size = (1 + strlen (DEFAULT_INFOPATH)));
00363 
00364       strcpy (infopath, DEFAULT_INFOPATH);
00365     }
00366 }
00367 
00368 /* Add PATH to the list of paths found in INFOPATH.  2nd argument says
00369    whether to put PATH at the front or end of INFOPATH. */
00370 void
00371 info_add_path (char *path, int where)
00372 {
00373   int len;
00374 
00375   if (!infopath)
00376     {
00377       infopath = (char *)xmalloc (infopath_size = 200 + strlen (path));
00378       infopath[0] = '\0';
00379     }
00380 
00381   len = strlen (path) + strlen (infopath);
00382 
00383   if (len + 2 >= infopath_size)
00384     infopath = (char *)xrealloc (infopath, (infopath_size += (2 * len) + 2));
00385 
00386   if (!*infopath)
00387     strcpy (infopath, path);
00388   else if (where == INFOPATH_APPEND)
00389     {
00390       strcat (infopath, PATH_SEP);
00391       strcat (infopath, path);
00392     }
00393   else if (where == INFOPATH_PREPEND)
00394     {
00395       char *temp = xstrdup (infopath);
00396       strcpy (infopath, path);
00397       strcat (infopath, PATH_SEP);
00398       strcat (infopath, temp);
00399       free (temp);
00400     }
00401 }
00402 
00403 /* Make INFOPATH have absolutely nothing in it. */
00404 void
00405 zap_infopath (void)
00406 {
00407   if (infopath)
00408     free (infopath);
00409 
00410   infopath = (char *)NULL;
00411   infopath_size = 0;
00412 }
00413 
00414 /* Given a chunk of text and its length, convert all CRLF pairs at every
00415    end-of-line into a single Newline character.  Return the length of
00416    produced text.
00417 
00418    This is required because the rest of code is too entrenched in having
00419    a single newline at each EOL; in particular, searching for various
00420    Info headers and cookies can become extremely tricky if that assumption
00421    breaks.
00422 
00423    FIXME: this could also support Mac-style text files with a single CR
00424    at the EOL, but what about random CR characters in non-Mac files?  Can
00425    we afford converting them into newlines as well?  Maybe implement some
00426    heuristics here, like in Emacs 20.
00427 
00428    FIXME: is it a good idea to show the EOL type on the modeline?  */
00429 long
00430 convert_eols (char *text, long int textlen)
00431 {
00432   register char *s = text;
00433   register char *d = text;
00434 
00435   while (textlen--)
00436     {
00437       if (*s == '\r' && textlen && s[1] == '\n')
00438        {
00439          s++;
00440          textlen--;
00441        }
00442       *d++ = *s++;
00443     }
00444 
00445   return (long)(d - text);
00446 }
00447 
00448 /* Read the contents of PATHNAME, returning a buffer with the contents of
00449    that file in it, and returning the size of that buffer in FILESIZE.
00450    FINFO is a stat struct which has already been filled in by the caller.
00451    If the file turns out to be compressed, set IS_COMPRESSED to non-zero.
00452    If the file cannot be read, return a NULL pointer. */
00453 char *
00454 filesys_read_info_file (char *pathname, long int *filesize,
00455     struct stat *finfo, int *is_compressed)
00456 {
00457   long st_size;
00458 
00459   *filesize = filesys_error_number = 0;
00460 
00461   if (compressed_filename_p (pathname))
00462     {
00463       *is_compressed = 1;
00464       return (filesys_read_compressed (pathname, filesize));
00465     }
00466   else
00467     {
00468       int descriptor;
00469       char *contents;
00470 
00471       *is_compressed = 0;
00472       descriptor = open (pathname, O_RDONLY | O_BINARY, 0666);
00473 
00474       /* If the file couldn't be opened, give up. */
00475       if (descriptor < 0)
00476         {
00477           filesys_error_number = errno;
00478           return ((char *)NULL);
00479         }
00480 
00481       /* Try to read the contents of this file. */
00482       st_size = (long) finfo->st_size;
00483       contents = (char *)xmalloc (1 + st_size);
00484       if ((read (descriptor, contents, st_size)) != st_size)
00485         {
00486          filesys_error_number = errno;
00487          close (descriptor);
00488          free (contents);
00489          return ((char *)NULL);
00490         }
00491 
00492       close (descriptor);
00493 
00494       /* Convert any DOS-style CRLF EOLs into Unix-style NL.
00495         Seems like a good idea to have even on Unix, in case the Info
00496         files are coming from some Windows system across a network.  */
00497       *filesize = convert_eols (contents, st_size);
00498 
00499       /* EOL conversion can shrink the text quite a bit.  We don't
00500         want to waste storage.  */
00501       if (*filesize < st_size)
00502        contents = (char *)xrealloc (contents, 1 + *filesize);
00503       contents[*filesize] = '\0';
00504 
00505       return (contents);
00506     }
00507 }
00508 
00509 /* Typically, pipe buffers are 4k. */
00510 #define BASIC_PIPE_BUFFER (4 * 1024)
00511 
00512 /* We use some large multiple of that. */
00513 #define FILESYS_PIPE_BUFFER_SIZE (16 * BASIC_PIPE_BUFFER)
00514 
00515 char *
00516 filesys_read_compressed (char *pathname, long int *filesize)
00517 {
00518   FILE *stream;
00519   char *command, *decompressor;
00520   char *contents = (char *)NULL;
00521 
00522   *filesize = filesys_error_number = 0;
00523 
00524   decompressor = filesys_decompressor_for_file (pathname);
00525 
00526   if (!decompressor)
00527     return ((char *)NULL);
00528 
00529   command = (char *)xmalloc (15 + strlen (pathname) + strlen (decompressor));
00530   /* Explicit .exe suffix makes the diagnostics of `popen'
00531      better on systems where COMMAND.COM is the stock shell.  */
00532   sprintf (command, "%s%s < %s",
00533           decompressor, STRIP_DOT_EXE ? ".exe" : "", pathname);
00534 
00535 #if !defined (BUILDING_LIBRARY)
00536   if (info_windows_initialized_p)
00537     {
00538       char *temp;
00539 
00540       temp = (char *)xmalloc (5 + strlen (command));
00541       sprintf (temp, "%s...", command);
00542       message_in_echo_area ("%s", temp, NULL);
00543       free (temp);
00544     }
00545 #endif /* !BUILDING_LIBRARY */
00546 
00547   stream = popen (command, FOPEN_RBIN);
00548   free (command);
00549 
00550   /* Read chunks from this file until there are none left to read. */
00551   if (stream)
00552     {
00553       long offset, size;
00554       char *chunk;
00555     
00556       offset = size = 0;
00557       chunk = (char *)xmalloc (FILESYS_PIPE_BUFFER_SIZE);
00558 
00559       while (1)
00560         {
00561           int bytes_read;
00562 
00563           bytes_read = fread (chunk, 1, FILESYS_PIPE_BUFFER_SIZE, stream);
00564 
00565           if (bytes_read + offset >= size)
00566             contents = (char *)xrealloc
00567               (contents, size += (2 * FILESYS_PIPE_BUFFER_SIZE));
00568 
00569           memcpy (contents + offset, chunk, bytes_read);
00570           offset += bytes_read;
00571           if (bytes_read != FILESYS_PIPE_BUFFER_SIZE)
00572             break;
00573         }
00574 
00575       free (chunk);
00576       if (pclose (stream) == -1)
00577        {
00578          if (contents)
00579            free (contents);
00580          contents = (char *)NULL;
00581          filesys_error_number = errno;
00582        }
00583       else
00584        {
00585          *filesize = convert_eols (contents, offset);
00586          contents = (char *)xrealloc (contents, 1 + *filesize);
00587          contents[*filesize] = '\0';
00588        }
00589     }
00590   else
00591     {
00592       filesys_error_number = errno;
00593     }
00594 
00595 #if !defined (BUILDING_LIBARARY)
00596   if (info_windows_initialized_p)
00597     unmessage_in_echo_area ();
00598 #endif /* !BUILDING_LIBRARY */
00599   return (contents);
00600 }
00601 
00602 /* Return non-zero if FILENAME belongs to a compressed file. */
00603 int
00604 compressed_filename_p (char *filename)
00605 {
00606   char *decompressor;
00607 
00608   /* Find the final extension of this filename, and see if it matches one
00609      of our known ones. */
00610   decompressor = filesys_decompressor_for_file (filename);
00611 
00612   if (decompressor)
00613     return (1);
00614   else
00615     return (0);
00616 }
00617 
00618 /* Return the command string that would be used to decompress FILENAME. */
00619 char *
00620 filesys_decompressor_for_file (char *filename)
00621 {
00622   register int i;
00623   char *extension = (char *)NULL;
00624 
00625   /* Find the final extension of FILENAME, and see if it appears in our
00626      list of known compression extensions. */
00627   for (i = strlen (filename) - 1; i > 0; i--)
00628     if (filename[i] == '.')
00629       {
00630         extension = filename + i;
00631         break;
00632       }
00633 
00634   if (!extension)
00635     return ((char *)NULL);
00636 
00637   for (i = 0; compress_suffixes[i].suffix; i++)
00638     if (FILENAME_CMP (extension, compress_suffixes[i].suffix) == 0)
00639       return (compress_suffixes[i].decompressor);
00640 
00641 #if defined (__MSDOS__)
00642   /* If no other suffix matched, allow any extension which ends
00643      with `z' to be decompressed by gunzip.  Due to limited 8+3 DOS
00644      file namespace, we can expect many such cases, and supporting
00645      every weird suffix thus produced would be a pain.  */
00646   if (extension[strlen (extension) - 1] == 'z' ||
00647       extension[strlen (extension) - 1] == 'Z')
00648     return "gunzip";
00649 #endif
00650 
00651   return ((char *)NULL);
00652 }
00653 
00654 /* The number of the most recent file system error. */
00655 int filesys_error_number = 0;
00656 
00657 /* A function which returns a pointer to a static buffer containing
00658    an error message for FILENAME and ERROR_NUM. */
00659 static char *errmsg_buf = (char *)NULL;
00660 static int errmsg_buf_size = 0;
00661 
00662 char *
00663 filesys_error_string (char *filename, int error_num)
00664 {
00665   int len;
00666   char *result;
00667 
00668   if (error_num == 0)
00669     return ((char *)NULL);
00670 
00671   result = strerror (error_num);
00672 
00673   len = 4 + strlen (filename) + strlen (result);
00674   if (len >= errmsg_buf_size)
00675     errmsg_buf = (char *)xrealloc (errmsg_buf, (errmsg_buf_size = 2 + len));
00676 
00677   sprintf (errmsg_buf, "%s: %s", filename, result);
00678   return (errmsg_buf);
00679 }
00680 
00681 
00682 /* Check for "dir" with all the possible info and compression suffixes,
00683    in combination.  */
00684 
00685 int
00686 is_dir_name (char *filename)
00687 {
00688   unsigned i;
00689 
00690   for (i = 0; info_suffixes[i]; i++)
00691     {
00692       unsigned c;
00693       char trydir[50];
00694       strcpy (trydir, "dir");
00695       strcat (trydir, info_suffixes[i]);
00696       
00697       if (strcasecmp (filename, trydir) == 0)
00698         return 1;
00699 
00700       for (c = 0; compress_suffixes[c].suffix; c++)
00701         {
00702           char dir_compressed[50]; /* can be short */
00703           strcpy (dir_compressed, trydir); 
00704           strcat (dir_compressed, compress_suffixes[c].suffix);
00705           if (strcasecmp (filename, dir_compressed) == 0)
00706             return 1;
00707         }
00708     }  
00709 
00710   return 0;
00711 }