(root)/
texinfo-7.1/
info/
filesys.c
       1  /* filesys.c -- filesystem specific functions.
       2  
       3     Copyright 1993-2023 Free Software Foundation, Inc.
       4  
       5     This program is free software: you can redistribute it and/or modify
       6     it under the terms of the GNU General Public License as published by
       7     the Free Software Foundation, either version 3 of the License, or
       8     (at your option) any later version.
       9  
      10     This program is distributed in the hope that it will be useful,
      11     but WITHOUT ANY WARRANTY; without even the implied warranty of
      12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      13     GNU General Public License for more details.
      14  
      15     You should have received a copy of the GNU General Public License
      16     along with this program.  If not, see <http://www.gnu.org/licenses/>.
      17  
      18     Originally written by Brian Fox. */
      19  
      20  #include "info.h"
      21  #include "tilde.h"
      22  #include "filesys.h"
      23  #include "tag.h"
      24  #include "session.h"
      25  
      26  /* Local to this file. */
      27  static char *info_file_in_path (char *filename, struct stat *finfo);
      28  char *info_add_extension (char *dirname, char *fname,
      29                                   struct stat *finfo);
      30  
      31  static char *filesys_read_compressed (char *pathname, size_t *filesize);
      32  
      33  /* Return the command string that would be used to decompress FILENAME. */
      34  static char *filesys_decompressor_for_file (char *filename);
      35  static int compressed_filename_p (char *filename);
      36  
      37  typedef struct
      38  {
      39    char *suffix;
      40    char *decompressor;
      41  } COMPRESSION_ALIST;
      42  
      43  static char *info_suffixes[] = {
      44    ".info",
      45    "-info",
      46    ".inf",       /* 8+3 file on filesystem which supports long file names */
      47  #ifdef __MSDOS__
      48    /* 8+3 file names strike again...  */
      49    ".in",        /* for .inz, .igz etc. */
      50    ".i",
      51  #endif
      52    "",
      53    NULL
      54  };
      55  
      56  static COMPRESSION_ALIST compress_suffixes[] = {
      57  #if STRIP_DOT_EXE
      58    { ".gz", "gunzip" },
      59    { ".lz", "lunzip" },
      60  #else
      61    { ".gz", "gzip -d" },
      62    { ".lz", "lzip -d" },
      63  #endif
      64    { ".xz", "unxz" },
      65    { ".bz2", "bunzip2" },
      66    { ".z", "gunzip" },
      67    { ".lzma", "unlzma" },
      68    { ".Z", "uncompress" },
      69    { ".zst", "unzstd --rm -q" },
      70    { ".Y", "unyabba" },
      71  #ifdef __MSDOS__
      72    { "gz", "gunzip" },
      73    { "z", "gunzip" },
      74  #endif
      75    { NULL, NULL }
      76  };
      77  
      78  /* Look for the filename PARTIAL in INFOPATH in order to find the correct file.
      79     Return file name and set *FINFO with information about file.  If it
      80     can't find the file, it returns NULL, and sets filesys_error_number.
      81     Return value should be freed by caller. */
      82  char *
      83  info_find_fullpath (char *partial, struct stat *finfo)
      84  {
      85    char *fullpath = 0;
      86    struct stat dummy;
      87  
      88    debug(1, (_("looking for file \"%s\""), partial));
      89  
      90    if (!finfo)
      91      finfo = &dummy;
      92  
      93    filesys_error_number = 0;
      94  
      95    if (!partial || !*partial)
      96      return 0;
      97    
      98    /* IS_SLASH and IS_ABSOLUTE defined in ../system.h. */
      99  
     100    /* If path is absolute already, see if it needs an extension. */
     101    if (IS_ABSOLUTE (partial)
     102        || partial[0] == '.' && IS_SLASH(partial[1]))
     103      {
     104        fullpath = info_add_extension (0, partial, finfo);
     105      }
     106  
     107    /* Tilde expansion.  Could come from user input in echo area. */
     108    else if (partial[0] == '~')
     109      {
     110        partial = tilde_expand_word (partial);
     111        fullpath = info_add_extension (0, partial, finfo);
     112      }
     113  
     114    /* If just a simple name element, look for it in the path. */
     115    else
     116      fullpath = info_file_in_path (partial, finfo);
     117  
     118    if (!fullpath)
     119      filesys_error_number = ENOENT;
     120  
     121    return fullpath;
     122  }
     123  
     124  /* Scan the directories in search path looking for FILENAME.  If we find
     125     one that is a regular file, return it as a new string.  Otherwise, return
     126     a NULL pointer.  Set *FINFO with information about file. */
     127  char *
     128  info_file_find_next_in_path (char *filename, int *path_index, struct stat *finfo)
     129  {
     130    struct stat dummy;
     131  
     132    /* Used for output of stat in case the caller doesn't care about
     133       its value. */
     134    if (!finfo)
     135      finfo = &dummy;
     136  
     137    /* Reject ridiculous cases up front, to prevent infinite recursion
     138       later on.  E.g., someone might say "info '(.)foo'"...  */
     139    if (!*filename || STREQ (filename, ".") || STREQ (filename, ".."))
     140      return NULL;
     141  
     142    while (1)
     143      {
     144        char *dirname, *with_extension = 0;
     145  
     146        dirname = infopath_next (path_index);
     147        if (!dirname)
     148          break;
     149  
     150        debug(1, (_("looking for file %s in %s"), filename, dirname));
     151  
     152        /* Expand a leading tilde if one is present. */
     153        if (*dirname == '~')
     154          {
     155            char *expanded_dirname = tilde_expand_word (dirname);
     156            dirname = expanded_dirname;
     157          }
     158  
     159        with_extension = info_add_extension (dirname, filename, finfo);
     160  
     161        if (with_extension)
     162          {
     163            if (!IS_ABSOLUTE (with_extension))
     164              {
     165                /* Prefix "./" to it. */
     166                char *s;
     167                xasprintf (&s, "%s%s", "./", with_extension);
     168                free (with_extension);
     169                return s;
     170              }
     171            else
     172              return with_extension;
     173          }
     174      }
     175    return NULL;
     176  }
     177  
     178  /* Return full path of first Info file known as FILENAME in
     179     search path.  If relative to current directory, precede it with './'. */
     180  static char *
     181  info_file_in_path (char *filename, struct stat *finfo)
     182  {
     183    int i = 0;
     184    return info_file_find_next_in_path (filename, &i, finfo);
     185  }
     186  
     187  /* Check if TRY_FILENAME exists, possibly compressed.  If so, return
     188     filename in TRY_FILENAME. */
     189  char *
     190  info_check_compressed (char *try_filename, struct stat *finfo)
     191  {
     192    int statable = (stat (try_filename, finfo) == 0);
     193  
     194    if (statable)
     195      {
     196        if (S_ISREG (finfo->st_mode))
     197          {
     198            debug(1, (_("found file %s"), try_filename));
     199            return try_filename;
     200          }
     201      }
     202    else
     203      {
     204        /* Add various compression suffixes to the name to see if
     205           the file is present in compressed format. */
     206        register int j, pre_compress_suffix_length;
     207  
     208        pre_compress_suffix_length = strlen (try_filename);
     209  
     210        for (j = 0; compress_suffixes[j].suffix; j++)
     211          {
     212            strcpy (try_filename + pre_compress_suffix_length,
     213                    compress_suffixes[j].suffix);
     214  
     215            statable = (stat (try_filename, finfo) == 0);
     216            if (statable && (S_ISREG (finfo->st_mode)))
     217              {
     218                debug(1, (_("found file %s"), try_filename));
     219                return try_filename;
     220              }
     221          }
     222      }
     223    return 0;
     224  }
     225  
     226  /* Look for a file called FILENAME in a directory called DIRNAME, adding file
     227     extensions if necessary.  FILENAME can be an absolute path or a path
     228     relative to the current directory, in which case DIRNAME should be
     229     null.  Return it as a new string; otherwise return a NULL pointer. */
     230  char *
     231  info_add_extension (char *dirname, char *filename, struct stat *finfo)
     232  {
     233    char *try_filename;
     234    register int i, pre_suffix_length = 0;
     235    struct stat dummy;
     236  
     237    if (!finfo)
     238      finfo = &dummy;
     239  
     240    if (dirname)
     241      pre_suffix_length += strlen (dirname);
     242  
     243    pre_suffix_length += strlen (filename);
     244  
     245    /* Add enough space for any file extensions at end. */
     246    try_filename = xmalloc (pre_suffix_length + 30);
     247    try_filename[0] = '\0';
     248  
     249    if (dirname)
     250      {
     251        strcpy (try_filename, dirname);
     252        if (!IS_SLASH (try_filename[(strlen (try_filename)) - 1]))
     253          {
     254            strcat (try_filename, "/");
     255            pre_suffix_length++;
     256          }
     257      }
     258  
     259    strcat (try_filename, filename);
     260  
     261    for (i = 0; info_suffixes[i]; i++)
     262      {
     263        char *result;
     264        strcpy (try_filename + pre_suffix_length, info_suffixes[i]);
     265  
     266        result = info_check_compressed (try_filename, finfo);
     267        if (result)
     268          return result;
     269      }
     270    /* Nothing was found. */
     271    free (try_filename);
     272    return 0;
     273  }
     274  
     275  #if defined (__MSDOS__) || defined (__MINGW32__)
     276  /* Given a chunk of text and its length, convert all CRLF pairs at every
     277     end-of-line into a single Newline character.  Return the length of
     278     produced text.
     279  
     280     This is required because the rest of code is too entrenched in having
     281     a single newline at each EOL; in particular, searching for various
     282     Info headers and cookies can become extremely tricky if that assumption
     283     breaks. */
     284  static long
     285  convert_eols (char *text, long int textlen)
     286  {
     287    register char *s = text;
     288    register char *d = text;
     289  
     290    while (textlen--)
     291      {
     292        if (*s == '\r' && textlen && s[1] == '\n')
     293         {
     294           s++;
     295           textlen--;
     296         }
     297        *d++ = *s++;
     298      }
     299  
     300    return d - text;
     301  }
     302  #endif
     303  
     304  /* Read the contents of PATHNAME, returning a buffer with the contents of
     305     that file in it, and returning the size of that buffer in FILESIZE.
     306     If the file turns out to be compressed, set IS_COMPRESSED to non-zero.
     307     If the file cannot be read, set filesys_error_number and return a NULL
     308     pointer.  Set *FINFO with information about file. */
     309  char *
     310  filesys_read_info_file (char *pathname, size_t *filesize,
     311  			struct stat *finfo, int *is_compressed)
     312  {
     313    size_t fsize;
     314    char *contents;
     315  
     316    fsize = filesys_error_number = 0;
     317  
     318    stat (pathname, finfo);
     319    fsize = (long) finfo->st_size;
     320  
     321    if (compressed_filename_p (pathname))
     322      {
     323        *is_compressed = 1;
     324        contents = filesys_read_compressed (pathname, &fsize);
     325      }
     326    else
     327      {
     328        int descriptor;
     329  
     330        *is_compressed = 0;
     331        descriptor = open (pathname, O_RDONLY | O_BINARY, 0666);
     332  
     333        /* If the file couldn't be opened, give up. */
     334        if (descriptor < 0)
     335          {
     336            filesys_error_number = errno;
     337            return NULL;
     338          }
     339  
     340        /* Try to read the contents of this file. */
     341        contents = xmalloc (1 + fsize);
     342        if ((read (descriptor, contents, fsize)) != fsize)
     343          {
     344  	  filesys_error_number = errno;
     345  	  close (descriptor);
     346  	  free (contents);
     347  	  return NULL;
     348          }
     349        contents[fsize] = 0;
     350        close (descriptor);
     351      }
     352  
     353  #if defined (__MSDOS__) || defined (__MINGW32__)
     354    /* Old versions of makeinfo on MS-DOS or MS-Windows generated Info files
     355       with CR-LF line endings which are only counted as one byte in the file
     356       tag table.  Convert any of these DOS-style CRLF EOLs into Unix-style NL
     357       so that these files can be read correctly on such operating systems.
     358  
     359       Don't do this on GNU/Linux (or other Unix-type operating system), so
     360       as not to encourage Info files with CR-LF line endings to be distributed
     361       widely beyond their native operating system, which would cause only
     362       problems.  (If someone really needs to, they can convert the line endings
     363       themselves with a separate program.)
     364       Also, this will allow any Info files that contain any CR-LF endings by
     365       mistake to work as expected (except on MS-DOS/Windows). */
     366  
     367    fsize = convert_eols (contents, fsize);
     368  
     369    /* EOL conversion can shrink the text quite a bit.  We don't
     370       want to waste storage.  */
     371    contents = xrealloc (contents, 1 + fsize);
     372    contents[fsize] = '\0';
     373  #endif
     374  
     375    *filesize = fsize;
     376  
     377    return contents;
     378  }
     379  
     380  /* Typically, pipe buffers are 4k. */
     381  #define BASIC_PIPE_BUFFER (4 * 1024)
     382  
     383  /* We use some large multiple of that. */
     384  #define FILESYS_PIPE_BUFFER_SIZE (16 * BASIC_PIPE_BUFFER)
     385  
     386  static char *
     387  filesys_read_compressed (char *pathname, size_t *filesize)
     388  {
     389    FILE *stream;
     390    char *command, *decompressor;
     391    char *contents = NULL;
     392  
     393    *filesize = filesys_error_number = 0;
     394  
     395    decompressor = filesys_decompressor_for_file (pathname);
     396  
     397    if (!decompressor)
     398      return NULL;
     399  
     400    command = xmalloc (15 + strlen (pathname) + strlen (decompressor));
     401    /* Explicit .exe suffix makes the diagnostics of `popen'
     402       better on systems where COMMAND.COM is the stock shell.  */
     403    sprintf (command, "%s%s < %s",
     404  	   decompressor, STRIP_DOT_EXE ? ".exe" : "", pathname);
     405  
     406    if (info_windows_initialized_p)
     407      {
     408        char *temp;
     409  
     410        temp = xmalloc (5 + strlen (command));
     411        sprintf (temp, "%s...", command);
     412        message_in_echo_area ("%s", temp);
     413        free (temp);
     414      }
     415  
     416    stream = popen (command, FOPEN_RBIN);
     417    free (command);
     418  
     419    /* Read chunks from this file until there are none left to read. */
     420    if (stream)
     421      {
     422        size_t offset, size;
     423        char *chunk;
     424      
     425        offset = size = 0;
     426        chunk = xmalloc (FILESYS_PIPE_BUFFER_SIZE);
     427  
     428        while (1)
     429          {
     430            size_t bytes_read;
     431  
     432            bytes_read = fread (chunk, 1, FILESYS_PIPE_BUFFER_SIZE, stream);
     433  
     434            if (bytes_read + offset >= size)
     435              contents = xrealloc
     436                (contents, size += (2 * FILESYS_PIPE_BUFFER_SIZE));
     437  
     438            memcpy (contents + offset, chunk, bytes_read);
     439            offset += bytes_read;
     440            if (bytes_read != FILESYS_PIPE_BUFFER_SIZE)
     441              break;
     442          }
     443  
     444        free (chunk);
     445        if (pclose (stream) == -1)
     446  	{
     447  	  if (contents)
     448  	    free (contents);
     449  	  contents = NULL;
     450  	  filesys_error_number = errno;
     451  	}
     452        else
     453  	{
     454  	  contents = xrealloc (contents, 1 + offset);
     455  	  contents[offset] = '\0';
     456  	  *filesize = offset;
     457  	}
     458      }
     459    else
     460      {
     461        filesys_error_number = errno;
     462      }
     463  
     464    if (info_windows_initialized_p)
     465      unmessage_in_echo_area ();
     466    return contents;
     467  }
     468  
     469  /* Return non-zero if FILENAME belongs to a compressed file. */
     470  static int
     471  compressed_filename_p (char *filename)
     472  {
     473    char *decompressor;
     474  
     475    /* Find the final extension of this filename, and see if it matches one
     476       of our known ones. */
     477    decompressor = filesys_decompressor_for_file (filename);
     478  
     479    if (decompressor)
     480      return 1;
     481    else
     482      return 0;
     483  }
     484  
     485  /* Return the command string that would be used to decompress FILENAME. */
     486  static char *
     487  filesys_decompressor_for_file (char *filename)
     488  {
     489    register int i;
     490    char *extension = NULL;
     491  
     492    /* Find the final extension of FILENAME, and see if it appears in our
     493       list of known compression extensions. */
     494    for (i = strlen (filename) - 1; i > 0; i--)
     495      if (filename[i] == '.')
     496        {
     497          extension = filename + i;
     498          break;
     499        }
     500  
     501    if (!extension)
     502      return NULL;
     503  
     504    for (i = 0; compress_suffixes[i].suffix; i++)
     505      if (FILENAME_CMP (extension, compress_suffixes[i].suffix) == 0)
     506        return compress_suffixes[i].decompressor;
     507  
     508  #if defined (__MSDOS__)
     509    /* If no other suffix matched, allow any extension which ends
     510       with `z' to be decompressed by gunzip.  Due to limited 8+3 DOS
     511       file namespace, we can expect many such cases, and supporting
     512       every weird suffix thus produced would be a pain.  */
     513    if (extension[strlen (extension) - 1] == 'z' ||
     514        extension[strlen (extension) - 1] == 'Z')
     515      return "gunzip";
     516  #endif
     517  
     518    return NULL;
     519  }
     520  
     521  /* The number of the most recent file system error. */
     522  int filesys_error_number = 0;
     523  
     524  /* A function which returns a pointer to a static buffer containing
     525     an error message for FILENAME and ERROR_NUM. */
     526  static char *errmsg_buf = NULL;
     527  static int errmsg_buf_size = 0;
     528  
     529  /* Return string for ERROR_NUM when opening file.  Return value should not
     530     be freed by caller. */
     531  char *
     532  filesys_error_string (char *filename, int error_num)
     533  {
     534    int len;
     535    const char *result;
     536  
     537    if (error_num == 0)
     538      return NULL;
     539  
     540    result = strerror (error_num);
     541  
     542    len = 4 + strlen (filename) + strlen (result);
     543    if (len >= errmsg_buf_size)
     544      errmsg_buf = xrealloc (errmsg_buf, (errmsg_buf_size = 2 + len));
     545  
     546    sprintf (errmsg_buf, "%s: %s", filename, result);
     547    return errmsg_buf;
     548  }
     549  
     550  
     551  /* Check for "dir" with all the possible info and compression suffixes,
     552     in combination.  */
     553  
     554  int
     555  is_dir_name (char *filename)
     556  {
     557    unsigned i;
     558  
     559    for (i = 0; info_suffixes[i]; i++)
     560      {
     561        unsigned c;
     562        char trydir[50];
     563        strcpy (trydir, "dir");
     564        strcat (trydir, info_suffixes[i]);
     565        
     566        if (mbscasecmp (filename, trydir) == 0)
     567          return 1;
     568  
     569        for (c = 0; compress_suffixes[c].suffix; c++)
     570          {
     571            char dir_compressed[50]; /* can be short */
     572            strcpy (dir_compressed, trydir); 
     573            strcat (dir_compressed, compress_suffixes[c].suffix);
     574            if (mbscasecmp (filename, dir_compressed) == 0)
     575              return 1;
     576          }
     577      }  
     578  
     579    return 0;
     580  }