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