1/* filesys.c -- File system specific functions for hacking this system.
2   $Id: filesys.c,v 1.1 2004/10/28 18:14:09 zooey Exp $
3
4   Copyright (C) 1993, 97, 98 Free Software Foundation, Inc.
5
6   This program is free software; you can redistribute it and/or modify
7   it under the terms of the GNU General Public License as published by
8   the Free Software Foundation; either version 2, or (at your option)
9   any later version.
10
11   This program is distributed in the hope that it will be useful,
12   but WITHOUT ANY WARRANTY; without even the implied warranty of
13   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14   GNU General Public License for more details.
15
16   You should have received a copy of the GNU General Public License
17   along with this program; if not, write to the Free Software
18   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19
20   Written by Brian Fox (bfox@ai.mit.edu). */
21
22#include "info.h"
23
24#include "tilde.h"
25#include "filesys.h"
26
27/* Local to this file. */
28static char *info_file_in_path (), *lookup_info_filename ();
29static void remember_info_filename (), maybe_initialize_infopath ();
30
31typedef struct
32{
33  char *suffix;
34  char *decompressor;
35} COMPRESSION_ALIST;
36
37static char *info_suffixes[] = {
38  "",
39  ".info",
40  "-info",
41  "/index",
42  (char *)NULL
43};
44
45static COMPRESSION_ALIST compress_suffixes[] = {
46  { ".Z", "uncompress" },
47  { ".Y", "unyabba" },
48  { ".z", "gunzip" },
49  { ".gz", "gunzip" },
50  { (char *)NULL, (char *)NULL }
51};
52
53/* The path on which we look for info files.  You can initialize this
54   from the environment variable INFOPATH if there is one, or you can
55   call info_add_path () to add paths to the beginning or end of it.
56   You can call zap_infopath () to make the path go away. */
57char *infopath = (char *)NULL;
58static int infopath_size = 0;
59
60/* Expand the filename in PARTIAL to make a real name for this operating
61   system.  This looks in INFO_PATHS in order to find the correct file.
62   If it can't find the file, it returns NULL. */
63static char *local_temp_filename = (char *)NULL;
64static int local_temp_filename_size = 0;
65
66char *
67info_find_fullpath (partial)
68     char *partial;
69{
70  int initial_character;
71  char *temp;
72
73  filesys_error_number = 0;
74
75  maybe_initialize_infopath ();
76
77  if (partial && (initial_character = *partial))
78    {
79      char *expansion;
80
81      expansion = lookup_info_filename (partial);
82
83      if (expansion)
84        return (expansion);
85
86      /* If we have the full path to this file, we still may have to add
87         various extensions to it.  I guess we have to stat this file
88         after all. */
89      if (initial_character == '/')
90        temp = info_file_in_path (partial + 1, "/");
91      else if (initial_character == '~')
92        {
93          expansion = tilde_expand_word (partial);
94          if (*expansion == '/')
95            {
96              temp = info_file_in_path (expansion + 1, "/");
97              free (expansion);
98            }
99          else
100            temp = expansion;
101        }
102      else if (initial_character == '.' &&
103               (partial[1] == '/' || (partial[1] == '.' && partial[2] == '/')))
104        {
105          if (local_temp_filename_size < 1024)
106            local_temp_filename = (char *)xrealloc
107              (local_temp_filename, (local_temp_filename_size = 1024));
108#if defined (HAVE_GETCWD)
109          if (!getcwd (local_temp_filename, local_temp_filename_size))
110#else /*  !HAVE_GETCWD */
111          if (!getwd (local_temp_filename))
112#endif /* !HAVE_GETCWD */
113            {
114              filesys_error_number = errno;
115              return (partial);
116            }
117
118          strcat (local_temp_filename, "/");
119          strcat (local_temp_filename, partial);
120          return (local_temp_filename);
121        }
122      else
123        temp = info_file_in_path (partial, infopath);
124
125      if (temp)
126        {
127          remember_info_filename (partial, temp);
128          if (strlen (temp) > local_temp_filename_size)
129            local_temp_filename = (char *) xrealloc
130              (local_temp_filename,
131               (local_temp_filename_size = (50 + strlen (temp))));
132          strcpy (local_temp_filename, temp);
133          free (temp);
134          return (local_temp_filename);
135        }
136    }
137  return (partial);
138}
139
140/* Scan the list of directories in PATH looking for FILENAME.  If we find
141   one that is a regular file, return it as a new string.  Otherwise, return
142   a NULL pointer. */
143static char *
144info_file_in_path (filename, path)
145     char *filename, *path;
146{
147  struct stat finfo;
148  char *temp_dirname;
149  int statable, dirname_index;
150
151  dirname_index = 0;
152
153  while ((temp_dirname = extract_colon_unit (path, &dirname_index)))
154    {
155      register int i, pre_suffix_length;
156      char *temp;
157
158      /* Expand a leading tilde if one is present. */
159      if (*temp_dirname == '~')
160        {
161          char *expanded_dirname;
162
163          expanded_dirname = tilde_expand_word (temp_dirname);
164          free (temp_dirname);
165          temp_dirname = expanded_dirname;
166        }
167
168      temp = (char *)xmalloc (30 + strlen (temp_dirname) + strlen (filename));
169      strcpy (temp, temp_dirname);
170      if (temp[(strlen (temp)) - 1] != '/')
171        strcat (temp, "/");
172      strcat (temp, filename);
173
174      pre_suffix_length = strlen (temp);
175
176      free (temp_dirname);
177
178      for (i = 0; info_suffixes[i]; i++)
179        {
180          strcpy (temp + pre_suffix_length, info_suffixes[i]);
181
182          statable = (stat (temp, &finfo) == 0);
183
184          /* If we have found a regular file, then use that.  Else, if we
185             have found a directory, look in that directory for this file. */
186          if (statable)
187            {
188              if (S_ISREG (finfo.st_mode))
189                {
190                  return (temp);
191                }
192              else if (S_ISDIR (finfo.st_mode))
193                {
194                  char *newpath, *filename_only, *newtemp;
195
196                  newpath = xstrdup (temp);
197                  filename_only = filename_non_directory (filename);
198                  newtemp = info_file_in_path (filename_only, newpath);
199
200                  free (newpath);
201                  if (newtemp)
202                    {
203                      free (temp);
204                      return (newtemp);
205                    }
206                }
207            }
208          else
209            {
210              /* Add various compression suffixes to the name to see if
211                 the file is present in compressed format. */
212              register int j, pre_compress_suffix_length;
213
214              pre_compress_suffix_length = strlen (temp);
215
216              for (j = 0; compress_suffixes[j].suffix; j++)
217                {
218                  strcpy (temp + pre_compress_suffix_length,
219                          compress_suffixes[j].suffix);
220
221                  statable = (stat (temp, &finfo) == 0);
222                  if (statable && (S_ISREG (finfo.st_mode)))
223                    return (temp);
224                }
225            }
226        }
227      free (temp);
228    }
229  return ((char *)NULL);
230}
231
232/* Given a string containing units of information separated by colons,
233   return the next one pointed to by IDX, or NULL if there are no more.
234   Advance IDX to the character after the colon. */
235char *
236extract_colon_unit (string, idx)
237     char *string;
238     int *idx;
239{
240  register int i, start;
241
242  i = start = *idx;
243  if ((i >= strlen (string)) || !string)
244    return ((char *) NULL);
245
246  while (string[i] && string[i] != ':')
247    i++;
248  if (i == start)
249    {
250      return ((char *) NULL);
251    }
252  else
253    {
254      char *value;
255
256      value = (char *) xmalloc (1 + (i - start));
257      strncpy (value, &string[start], (i - start));
258      value[i - start] = '\0';
259      if (string[i])
260        ++i;
261      *idx = i;
262      return (value);
263    }
264}
265
266/* A structure which associates a filename with its expansion. */
267typedef struct {
268  char *filename;
269  char *expansion;
270} FILENAME_LIST;
271
272/* An array of remembered arguments and results. */
273static FILENAME_LIST **names_and_files = (FILENAME_LIST **)NULL;
274static int names_and_files_index = 0;
275static int names_and_files_slots = 0;
276
277/* Find the result for having already called info_find_fullpath () with
278   FILENAME. */
279static char *
280lookup_info_filename (filename)
281     char *filename;
282{
283  if (filename && names_and_files)
284    {
285      register int i;
286      for (i = 0; names_and_files[i]; i++)
287        {
288          if (strcmp (names_and_files[i]->filename, filename) == 0)
289            return (names_and_files[i]->expansion);
290        }
291    }
292  return (char *)NULL;;
293}
294
295/* Add a filename and its expansion to our list. */
296static void
297remember_info_filename (filename, expansion)
298     char *filename, *expansion;
299{
300  FILENAME_LIST *new;
301
302  if (names_and_files_index + 2 > names_and_files_slots)
303    {
304      int alloc_size;
305      names_and_files_slots += 10;
306
307      alloc_size = names_and_files_slots * sizeof (FILENAME_LIST *);
308
309      names_and_files =
310        (FILENAME_LIST **) xrealloc (names_and_files, alloc_size);
311    }
312
313  new = (FILENAME_LIST *)xmalloc (sizeof (FILENAME_LIST));
314  new->filename = xstrdup (filename);
315  new->expansion = expansion ? xstrdup (expansion) : (char *)NULL;
316
317  names_and_files[names_and_files_index++] = new;
318  names_and_files[names_and_files_index] = (FILENAME_LIST *)NULL;
319}
320
321static void
322maybe_initialize_infopath ()
323{
324  if (!infopath_size)
325    {
326      infopath = (char *)
327        xmalloc (infopath_size = (1 + strlen (DEFAULT_INFOPATH)));
328
329      strcpy (infopath, DEFAULT_INFOPATH);
330    }
331}
332
333/* Add PATH to the list of paths found in INFOPATH.  2nd argument says
334   whether to put PATH at the front or end of INFOPATH. */
335void
336info_add_path (path, where)
337     char *path;
338     int where;
339{
340  int len;
341
342  if (!infopath)
343    {
344      infopath = (char *)xmalloc (infopath_size = 200 + strlen (path));
345      infopath[0] = '\0';
346    }
347
348  len = strlen (path) + strlen (infopath);
349
350  if (len + 2 >= infopath_size)
351    infopath = (char *)xrealloc (infopath, (infopath_size += (2 * len) + 2));
352
353  if (!*infopath)
354    strcpy (infopath, path);
355  else if (where == INFOPATH_APPEND)
356    {
357      strcat (infopath, ":");
358      strcat (infopath, path);
359    }
360  else if (where == INFOPATH_PREPEND)
361    {
362      char *temp = xstrdup (infopath);
363      strcpy (infopath, path);
364      strcat (infopath, ":");
365      strcat (infopath, temp);
366      free (temp);
367    }
368}
369
370/* Make INFOPATH have absolutely nothing in it. */
371void
372zap_infopath ()
373{
374  if (infopath)
375    free (infopath);
376
377  infopath = (char *)NULL;
378  infopath_size = 0;
379}
380
381/* Read the contents of PATHNAME, returning a buffer with the contents of
382   that file in it, and returning the size of that buffer in FILESIZE.
383   FINFO is a stat struct which has already been filled in by the caller.
384   If the file cannot be read, return a NULL pointer. */
385char *
386filesys_read_info_file (pathname, filesize, finfo)
387     char *pathname;
388     long *filesize;
389     struct stat *finfo;
390{
391  long st_size;
392
393  *filesize = filesys_error_number = 0;
394
395  if (compressed_filename_p (pathname))
396    return (filesys_read_compressed (pathname, filesize, finfo));
397  else
398    {
399      int descriptor;
400      char *contents;
401
402      descriptor = open (pathname, O_RDONLY, 0666);
403
404      /* If the file couldn't be opened, give up. */
405      if (descriptor < 0)
406        {
407          filesys_error_number = errno;
408          return ((char *)NULL);
409        }
410
411      /* Try to read the contents of this file. */
412      st_size = (long) finfo->st_size;
413      contents = (char *)xmalloc (1 + st_size);
414      if ((read (descriptor, contents, st_size)) != st_size)
415        {
416          filesys_error_number = errno;
417          close (descriptor);
418          free (contents);
419          return ((char *)NULL);
420        }
421
422      close (descriptor);
423
424      *filesize = st_size;
425      return (contents);
426    }
427}
428
429/* Typically, pipe buffers are 4k. */
430#define BASIC_PIPE_BUFFER (4 * 1024)
431
432/* We use some large multiple of that. */
433#define FILESYS_PIPE_BUFFER_SIZE (16 * BASIC_PIPE_BUFFER)
434
435char *
436filesys_read_compressed (pathname, filesize, finfo)
437     char *pathname;
438     long *filesize;
439     struct stat *finfo;
440{
441  FILE *stream;
442  char *command, *decompressor;
443  char *contents = (char *)NULL;
444
445  *filesize = filesys_error_number = 0;
446
447  decompressor = filesys_decompressor_for_file (pathname);
448
449  if (!decompressor)
450    return ((char *)NULL);
451
452  command = (char *)xmalloc (10 + strlen (pathname) + strlen (decompressor));
453  sprintf (command, "%s < %s", decompressor, pathname);
454
455#if !defined (BUILDING_LIBRARY)
456  if (info_windows_initialized_p)
457    {
458      char *temp;
459
460      temp = (char *)xmalloc (5 + strlen (command));
461      sprintf (temp, "%s...", command);
462      message_in_echo_area ("%s", temp);
463      free (temp);
464    }
465#endif /* !BUILDING_LIBRARY */
466
467  stream = popen (command, "r");
468  free (command);
469
470  /* Read chunks from this file until there are none left to read. */
471  if (stream)
472    {
473      int offset, size;
474      char *chunk;
475
476      offset = size = 0;
477      chunk = (char *)xmalloc (FILESYS_PIPE_BUFFER_SIZE);
478
479      while (1)
480        {
481          int bytes_read;
482
483          bytes_read = fread (chunk, 1, FILESYS_PIPE_BUFFER_SIZE, stream);
484
485          if (bytes_read + offset >= size)
486            contents = (char *)xrealloc
487              (contents, size += (2 * FILESYS_PIPE_BUFFER_SIZE));
488
489          memcpy (contents + offset, chunk, bytes_read);
490          offset += bytes_read;
491          if (bytes_read != FILESYS_PIPE_BUFFER_SIZE)
492            break;
493        }
494
495      free (chunk);
496      pclose (stream);
497      contents = (char *)xrealloc (contents, offset + 1);
498      *filesize = offset;
499    }
500  else
501    {
502      filesys_error_number = errno;
503    }
504
505#if !defined (BUILDING_LIBARARY)
506  if (info_windows_initialized_p)
507    unmessage_in_echo_area ();
508#endif /* !BUILDING_LIBRARY */
509  return (contents);
510}
511
512/* Return non-zero if FILENAME belongs to a compressed file. */
513int
514compressed_filename_p (filename)
515     char *filename;
516{
517  char *decompressor;
518
519  /* Find the final extension of this filename, and see if it matches one
520     of our known ones. */
521  decompressor = filesys_decompressor_for_file (filename);
522
523  if (decompressor)
524    return (1);
525  else
526    return (0);
527}
528
529/* Return the command string that would be used to decompress FILENAME. */
530char *
531filesys_decompressor_for_file (filename)
532     char *filename;
533{
534  register int i;
535  char *extension = (char *)NULL;
536
537  /* Find the final extension of FILENAME, and see if it appears in our
538     list of known compression extensions. */
539  for (i = strlen (filename) - 1; i > 0; i--)
540    if (filename[i] == '.')
541      {
542        extension = filename + i;
543        break;
544      }
545
546  if (!extension)
547    return ((char *)NULL);
548
549  for (i = 0; compress_suffixes[i].suffix; i++)
550    if (strcmp (extension, compress_suffixes[i].suffix) == 0)
551      return (compress_suffixes[i].decompressor);
552
553  return ((char *)NULL);
554}
555
556/* The number of the most recent file system error. */
557int filesys_error_number = 0;
558
559/* A function which returns a pointer to a static buffer containing
560   an error message for FILENAME and ERROR_NUM. */
561static char *errmsg_buf = (char *)NULL;
562static int errmsg_buf_size = 0;
563
564char *
565filesys_error_string (filename, error_num)
566     char *filename;
567     int error_num;
568{
569  int len;
570  char *result;
571
572  if (error_num == 0)
573    return ((char *)NULL);
574
575  result = strerror (error_num);
576
577  len = 4 + strlen (filename) + strlen (result);
578  if (len >= errmsg_buf_size)
579    errmsg_buf = (char *)xrealloc (errmsg_buf, (errmsg_buf_size = 2 + len));
580
581  sprintf (errmsg_buf, "%s: %s", filename, result);
582  return (errmsg_buf);
583}
584
585