diff options
Diffstat (limited to 'info/filesys.c')
-rw-r--r-- | info/filesys.c | 710 |
1 files changed, 710 insertions, 0 deletions
diff --git a/info/filesys.c b/info/filesys.c new file mode 100644 index 0000000..dcf5c67 --- /dev/null +++ b/info/filesys.c @@ -0,0 +1,710 @@ +/* filesys.c -- filesystem specific functions. + $Id: filesys.c,v 1.12 2008/06/11 09:55:42 gray Exp $ + + Copyright (C) 1993, 1997, 1998, 2000, 2002, 2003, 2004, 2007, 2008 + Free Software Foundation, Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + + Written by Brian Fox (bfox@ai.mit.edu). */ + +#include "info.h" + +#include "tilde.h" +#include "filesys.h" + +/* Local to this file. */ +static char *info_file_in_path (char *filename, char *path); +static char *lookup_info_filename (char *filename); +static char *info_absolute_file (char *fname); + +static void remember_info_filename (char *filename, char *expansion); +static void maybe_initialize_infopath (void); + +typedef struct +{ + char *suffix; + char *decompressor; +} COMPRESSION_ALIST; + +static char *info_suffixes[] = { + ".info", + "-info", + "/index", + ".inf", /* 8+3 file on filesystem which supports long file names */ +#ifdef __MSDOS__ + /* 8+3 file names strike again... */ + ".in", /* for .inz, .igz etc. */ + ".i", +#endif + "", + NULL +}; + +static COMPRESSION_ALIST compress_suffixes[] = { + { ".gz", "gunzip" }, + { ".bz2", "bunzip2" }, + { ".lzma", "unlzma" }, + { ".z", "gunzip" }, + { ".Z", "uncompress" }, + { ".Y", "unyabba" }, +#ifdef __MSDOS__ + { "gz", "gunzip" }, + { "z", "gunzip" }, +#endif + { NULL, NULL } +}; + +/* The path on which we look for info files. You can initialize this + from the environment variable INFOPATH if there is one, or you can + call info_add_path () to add paths to the beginning or end of it. + You can call zap_infopath () to make the path go away. */ +char *infopath = NULL; +static int infopath_size = 0; + +/* Expand the filename in PARTIAL to make a real name for this operating + system. This looks in INFO_PATHS in order to find the correct file. + If it can't find the file, it returns NULL. */ +static char *local_temp_filename = NULL; +static int local_temp_filename_size = 0; + +char * +info_find_fullpath (char *partial) +{ + int initial_character; + char *temp; + + filesys_error_number = 0; + + maybe_initialize_infopath (); + + if (partial && (initial_character = *partial)) + { + char *expansion; + + expansion = lookup_info_filename (partial); + + if (expansion) + return expansion; + + /* If we have the full path to this file, we still may have to add + various extensions to it. I guess we have to stat this file + after all. */ + if (IS_ABSOLUTE (partial)) + temp = info_absolute_file (partial); + else if (initial_character == '~') + { + expansion = tilde_expand_word (partial); + if (IS_ABSOLUTE (expansion)) + { + temp = info_absolute_file (expansion); + free (expansion); + } + else + temp = expansion; + } + else if (initial_character == '.' && + (IS_SLASH (partial[1]) || + (partial[1] == '.' && IS_SLASH (partial[2])))) + { + if (local_temp_filename_size < 1024) + local_temp_filename = xrealloc + (local_temp_filename, (local_temp_filename_size = 1024)); +#if defined (HAVE_GETCWD) + if (!getcwd (local_temp_filename, local_temp_filename_size)) +#else /* !HAVE_GETCWD */ + if (!getwd (local_temp_filename)) +#endif /* !HAVE_GETCWD */ + { + filesys_error_number = errno; + return partial; + } + + strcat (local_temp_filename, "/"); + strcat (local_temp_filename, partial); + temp = info_absolute_file (local_temp_filename); /* try extensions */ + if (!temp) + partial = local_temp_filename; + } + else + temp = info_file_in_path (partial, infopath); + + if (temp) + { + remember_info_filename (partial, temp); + if (strlen (temp) > (unsigned int) local_temp_filename_size) + local_temp_filename = xrealloc + (local_temp_filename, + (local_temp_filename_size = (50 + strlen (temp)))); + strcpy (local_temp_filename, temp); + free (temp); + return local_temp_filename; + } + } + return partial; +} + +/* Scan the list of directories in PATH looking for FILENAME. If we find + one that is a regular file, return it as a new string. Otherwise, return + a NULL pointer. */ +static char * +info_file_in_path (char *filename, char *path) +{ + struct stat finfo; + char *temp_dirname; + int statable, dirname_index; + + /* Reject ridiculous cases up front, to prevent infinite recursion + later on. E.g., someone might say "info '(.)foo'"... */ + if (!*filename || STREQ (filename, ".") || STREQ (filename, "..")) + return NULL; + + dirname_index = 0; + + while ((temp_dirname = extract_colon_unit (path, &dirname_index))) + { + register int i, pre_suffix_length; + char *temp; + + /* Expand a leading tilde if one is present. */ + if (*temp_dirname == '~') + { + char *expanded_dirname; + + expanded_dirname = tilde_expand_word (temp_dirname); + free (temp_dirname); + temp_dirname = expanded_dirname; + } + + temp = xmalloc (30 + strlen (temp_dirname) + strlen (filename)); + strcpy (temp, temp_dirname); + if (!IS_SLASH (temp[(strlen (temp)) - 1])) + strcat (temp, "/"); + strcat (temp, filename); + + pre_suffix_length = strlen (temp); + + free (temp_dirname); + + for (i = 0; info_suffixes[i]; i++) + { + strcpy (temp + pre_suffix_length, info_suffixes[i]); + + statable = (stat (temp, &finfo) == 0); + + /* If we have found a regular file, then use that. Else, if we + have found a directory, look in that directory for this file. */ + if (statable) + { + if (S_ISREG (finfo.st_mode)) + { + return temp; + } + else if (S_ISDIR (finfo.st_mode)) + { + char *newpath, *filename_only, *newtemp; + + newpath = xstrdup (temp); + filename_only = filename_non_directory (filename); + newtemp = info_file_in_path (filename_only, newpath); + + free (newpath); + if (newtemp) + { + free (temp); + return newtemp; + } + } + } + else + { + /* Add various compression suffixes to the name to see if + the file is present in compressed format. */ + register int j, pre_compress_suffix_length; + + pre_compress_suffix_length = strlen (temp); + + for (j = 0; compress_suffixes[j].suffix; j++) + { + strcpy (temp + pre_compress_suffix_length, + compress_suffixes[j].suffix); + + statable = (stat (temp, &finfo) == 0); + if (statable && (S_ISREG (finfo.st_mode))) + return temp; + } + } + } + free (temp); + } + return NULL; +} + +/* Assume FNAME is an absolute file name, and check whether it is + a regular file. If it is, return it as a new string; otherwise + return a NULL pointer. We do it by taking the file name apart + into its directory and basename parts, and calling info_file_in_path.*/ +static char * +info_absolute_file (char *fname) +{ + char *containing_dir = xstrdup (fname); + char *base = filename_non_directory (containing_dir); + + if (base > containing_dir) + base[-1] = '\0'; + + return info_file_in_path (filename_non_directory (fname), containing_dir); +} + + +/* Given a string containing units of information separated by the + PATH_SEP character, return the next one after IDX, or NULL if there + are no more. Advance IDX to the character after the colon. */ + +char * +extract_colon_unit (char *string, int *idx) +{ + unsigned int i = (unsigned int) *idx; + unsigned int start = i; + + if (!string || i >= strlen (string)) + return NULL; + + if (!string[i]) /* end of string */ + return NULL; + + /* Advance to next PATH_SEP. */ + while (string[i] && string[i] != PATH_SEP[0]) + i++; + + { + char *value = xmalloc ((i - start) + 1); + strncpy (value, &string[start], (i - start)); + value[i - start] = 0; + + i++; /* move past PATH_SEP */ + *idx = i; + return value; + } +} + +/* A structure which associates a filename with its expansion. */ +typedef struct +{ + char *filename; + char *expansion; +} FILENAME_LIST; + +/* An array of remembered arguments and results. */ +static FILENAME_LIST **names_and_files = NULL; +static int names_and_files_index = 0; +static int names_and_files_slots = 0; + +/* Find the result for having already called info_find_fullpath () with + FILENAME. */ +static char * +lookup_info_filename (char *filename) +{ + if (filename && names_and_files) + { + register int i; + for (i = 0; names_and_files[i]; i++) + { + if (FILENAME_CMP (names_and_files[i]->filename, filename) == 0) + return names_and_files[i]->expansion; + } + } + return NULL; +} + +/* Add a filename and its expansion to our list. */ +static void +remember_info_filename (char *filename, char *expansion) +{ + FILENAME_LIST *new; + + if (names_and_files_index + 2 > names_and_files_slots) + { + int alloc_size; + names_and_files_slots += 10; + + alloc_size = names_and_files_slots * sizeof (FILENAME_LIST *); + + names_and_files = xrealloc (names_and_files, alloc_size); + } + + new = xmalloc (sizeof (FILENAME_LIST)); + new->filename = xstrdup (filename); + new->expansion = expansion ? xstrdup (expansion) : NULL; + + names_and_files[names_and_files_index++] = new; + names_and_files[names_and_files_index] = NULL; +} + +static void +maybe_initialize_infopath (void) +{ + if (!infopath_size) + { + infopath = (char *) + xmalloc (infopath_size = (1 + strlen (DEFAULT_INFOPATH))); + + strcpy (infopath, DEFAULT_INFOPATH); + } +} + +/* Add PATH to the list of paths found in INFOPATH. 2nd argument says + whether to put PATH at the front or end of INFOPATH. */ +void +info_add_path (char *path, int where) +{ + int len; + + if (!infopath) + { + infopath = xmalloc (infopath_size = 200 + strlen (path)); + infopath[0] = '\0'; + } + + len = strlen (path) + strlen (infopath); + + if (len + 2 >= infopath_size) + infopath = xrealloc (infopath, (infopath_size += (2 * len) + 2)); + + if (!*infopath) + strcpy (infopath, path); + else if (where == INFOPATH_APPEND) + { + strcat (infopath, PATH_SEP); + strcat (infopath, path); + } + else if (where == INFOPATH_PREPEND) + { + char *temp = xstrdup (infopath); + strcpy (infopath, path); + strcat (infopath, PATH_SEP); + strcat (infopath, temp); + free (temp); + } +} + +/* Make INFOPATH have absolutely nothing in it. */ +void +zap_infopath (void) +{ + if (infopath) + free (infopath); + + infopath = NULL; + infopath_size = 0; +} + +/* Given a chunk of text and its length, convert all CRLF pairs at every + end-of-line into a single Newline character. Return the length of + produced text. + + This is required because the rest of code is too entrenched in having + a single newline at each EOL; in particular, searching for various + Info headers and cookies can become extremely tricky if that assumption + breaks. + + FIXME: this could also support Mac-style text files with a single CR + at the EOL, but what about random CR characters in non-Mac files? Can + we afford converting them into newlines as well? Maybe implement some + heuristics here, like in Emacs 20. + + FIXME: is it a good idea to show the EOL type on the modeline? */ +long +convert_eols (char *text, long int textlen) +{ + register char *s = text; + register char *d = text; + + while (textlen--) + { + if (*s == '\r' && textlen && s[1] == '\n') + { + s++; + textlen--; + } + *d++ = *s++; + } + + return d - text; +} + +/* Read the contents of PATHNAME, returning a buffer with the contents of + that file in it, and returning the size of that buffer in FILESIZE. + FINFO is a stat struct which has already been filled in by the caller. + If the file turns out to be compressed, set IS_COMPRESSED to non-zero. + If the file cannot be read, return a NULL pointer. */ +char * +filesys_read_info_file (char *pathname, long int *filesize, + struct stat *finfo, int *is_compressed) +{ + long st_size; + + *filesize = filesys_error_number = 0; + + if (compressed_filename_p (pathname)) + { + *is_compressed = 1; + return filesys_read_compressed (pathname, filesize); + } + else + { + int descriptor; + char *contents; + + *is_compressed = 0; + descriptor = open (pathname, O_RDONLY | O_BINARY, 0666); + + /* If the file couldn't be opened, give up. */ + if (descriptor < 0) + { + filesys_error_number = errno; + return NULL; + } + + /* Try to read the contents of this file. */ + st_size = (long) finfo->st_size; + contents = xmalloc (1 + st_size); + if ((read (descriptor, contents, st_size)) != st_size) + { + filesys_error_number = errno; + close (descriptor); + free (contents); + return NULL; + } + + close (descriptor); + + /* Convert any DOS-style CRLF EOLs into Unix-style NL. + Seems like a good idea to have even on Unix, in case the Info + files are coming from some Windows system across a network. */ + *filesize = convert_eols (contents, st_size); + + /* EOL conversion can shrink the text quite a bit. We don't + want to waste storage. */ + if (*filesize < st_size) + contents = xrealloc (contents, 1 + *filesize); + contents[*filesize] = '\0'; + + return contents; + } +} + +/* Typically, pipe buffers are 4k. */ +#define BASIC_PIPE_BUFFER (4 * 1024) + +/* We use some large multiple of that. */ +#define FILESYS_PIPE_BUFFER_SIZE (16 * BASIC_PIPE_BUFFER) + +char * +filesys_read_compressed (char *pathname, long int *filesize) +{ + FILE *stream; + char *command, *decompressor; + char *contents = NULL; + + *filesize = filesys_error_number = 0; + + decompressor = filesys_decompressor_for_file (pathname); + + if (!decompressor) + return NULL; + + command = xmalloc (15 + strlen (pathname) + strlen (decompressor)); + /* Explicit .exe suffix makes the diagnostics of `popen' + better on systems where COMMAND.COM is the stock shell. */ + sprintf (command, "%s%s < %s", + decompressor, STRIP_DOT_EXE ? ".exe" : "", pathname); + +#if !defined (BUILDING_LIBRARY) + if (info_windows_initialized_p) + { + char *temp; + + temp = xmalloc (5 + strlen (command)); + sprintf (temp, "%s...", command); + message_in_echo_area ("%s", temp, NULL); + free (temp); + } +#endif /* !BUILDING_LIBRARY */ + + stream = popen (command, FOPEN_RBIN); + free (command); + + /* Read chunks from this file until there are none left to read. */ + if (stream) + { + long offset, size; + char *chunk; + + offset = size = 0; + chunk = xmalloc (FILESYS_PIPE_BUFFER_SIZE); + + while (1) + { + int bytes_read; + + bytes_read = fread (chunk, 1, FILESYS_PIPE_BUFFER_SIZE, stream); + + if (bytes_read + offset >= size) + contents = xrealloc + (contents, size += (2 * FILESYS_PIPE_BUFFER_SIZE)); + + memcpy (contents + offset, chunk, bytes_read); + offset += bytes_read; + if (bytes_read != FILESYS_PIPE_BUFFER_SIZE) + break; + } + + free (chunk); + if (pclose (stream) == -1) + { + if (contents) + free (contents); + contents = NULL; + filesys_error_number = errno; + } + else + { + *filesize = convert_eols (contents, offset); + contents = xrealloc (contents, 1 + *filesize); + contents[*filesize] = '\0'; + } + } + else + { + filesys_error_number = errno; + } + +#if !defined (BUILDING_LIBARARY) + if (info_windows_initialized_p) + unmessage_in_echo_area (); +#endif /* !BUILDING_LIBRARY */ + return contents; +} + +/* Return non-zero if FILENAME belongs to a compressed file. */ +int +compressed_filename_p (char *filename) +{ + char *decompressor; + + /* Find the final extension of this filename, and see if it matches one + of our known ones. */ + decompressor = filesys_decompressor_for_file (filename); + + if (decompressor) + return 1; + else + return 0; +} + +/* Return the command string that would be used to decompress FILENAME. */ +char * +filesys_decompressor_for_file (char *filename) +{ + register int i; + char *extension = NULL; + + /* Find the final extension of FILENAME, and see if it appears in our + list of known compression extensions. */ + for (i = strlen (filename) - 1; i > 0; i--) + if (filename[i] == '.') + { + extension = filename + i; + break; + } + + if (!extension) + return NULL; + + for (i = 0; compress_suffixes[i].suffix; i++) + if (FILENAME_CMP (extension, compress_suffixes[i].suffix) == 0) + return compress_suffixes[i].decompressor; + +#if defined (__MSDOS__) + /* If no other suffix matched, allow any extension which ends + with `z' to be decompressed by gunzip. Due to limited 8+3 DOS + file namespace, we can expect many such cases, and supporting + every weird suffix thus produced would be a pain. */ + if (extension[strlen (extension) - 1] == 'z' || + extension[strlen (extension) - 1] == 'Z') + return "gunzip"; +#endif + + return NULL; +} + +/* The number of the most recent file system error. */ +int filesys_error_number = 0; + +/* A function which returns a pointer to a static buffer containing + an error message for FILENAME and ERROR_NUM. */ +static char *errmsg_buf = NULL; +static int errmsg_buf_size = 0; + +char * +filesys_error_string (char *filename, int error_num) +{ + int len; + char *result; + + if (error_num == 0) + return NULL; + + result = strerror (error_num); + + len = 4 + strlen (filename) + strlen (result); + if (len >= errmsg_buf_size) + errmsg_buf = xrealloc (errmsg_buf, (errmsg_buf_size = 2 + len)); + + sprintf (errmsg_buf, "%s: %s", filename, result); + return errmsg_buf; +} + + +/* Check for "dir" with all the possible info and compression suffixes, + in combination. */ + +int +is_dir_name (char *filename) +{ + unsigned i; + + for (i = 0; info_suffixes[i]; i++) + { + unsigned c; + char trydir[50]; + strcpy (trydir, "dir"); + strcat (trydir, info_suffixes[i]); + + if (mbscasecmp (filename, trydir) == 0) + return 1; + + for (c = 0; compress_suffixes[c].suffix; c++) + { + char dir_compressed[50]; /* can be short */ + strcpy (dir_compressed, trydir); + strcat (dir_compressed, compress_suffixes[c].suffix); + if (mbscasecmp (filename, dir_compressed) == 0) + return 1; + } + } + + return 0; +} |