1 /* filesys.c -- filesystem specific functions.
2 $Id: filesys.c,v 1.5 2006/07/17 16:12:36 espie Exp $
3
4 Copyright (C) 1993, 1997, 1998, 2000, 2002, 2003, 2004 Free Software
5 Foundation, Inc.
6
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2, or (at your option)
10 any later version.
11
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to the Free Software
19 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20
21 Written by Brian Fox (bfox@ai.mit.edu). */
22
23 #include "info.h"
24
25 #include "tilde.h"
26 #include "filesys.h"
27
28 /* Local to this file. */
29 static char *info_file_in_path (char *filename, char *path);
30 static char *lookup_info_filename (char *filename);
31 static char *info_absolute_file (char *fname);
32
33 static void remember_info_filename (char *filename, char *expansion);
34 static void maybe_initialize_infopath (void);
35
36 typedef struct
37 {
38 char *suffix;
39 char *decompressor;
40 } COMPRESSION_ALIST;
41
42 static char *info_suffixes[] = {
43 ".info",
44 "-info",
45 "/index",
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 { ".gz", "gunzip" },
58 { ".bz2", "bunzip2" },
59 { ".z", "gunzip" },
60 { ".Z", "uncompress" },
61 { ".Y", "unyabba" },
62 #ifdef __MSDOS__
63 { "gz", "gunzip" },
64 { "z", "gunzip" },
65 #endif
66 { (char *)NULL, (char *)NULL }
67 };
68
69 /* The path on which we look for info files. You can initialize this
70 from the environment variable INFOPATH if there is one, or you can
71 call info_add_path () to add paths to the beginning or end of it.
72 You can call zap_infopath () to make the path go away. */
73 char *infopath = (char *)NULL;
74 static int infopath_size = 0;
75
76 /* Expand the filename in PARTIAL to make a real name for this operating
77 system. This looks in INFO_PATHS in order to find the correct file.
78 If it can't find the file, it returns NULL. */
79 static char *local_temp_filename = (char *)NULL;
80 static int local_temp_filename_size = 0;
81
82 char *
info_find_fullpath(char * partial)83 info_find_fullpath (char *partial)
84 {
85 int initial_character;
86 char *temp;
87
88 filesys_error_number = 0;
89
90 maybe_initialize_infopath ();
91
92 if (partial && (initial_character = *partial))
93 {
94 char *expansion;
95
96 expansion = lookup_info_filename (partial);
97
98 if (expansion)
99 return (expansion);
100
101 /* If we have the full path to this file, we still may have to add
102 various extensions to it. I guess we have to stat this file
103 after all. */
104 if (IS_ABSOLUTE (partial))
105 temp = info_absolute_file (partial);
106 else if (initial_character == '~')
107 {
108 expansion = tilde_expand_word (partial);
109 if (IS_ABSOLUTE (expansion))
110 {
111 temp = info_absolute_file (expansion);
112 free (expansion);
113 }
114 else
115 temp = expansion;
116 }
117 else if (initial_character == '.' &&
118 (IS_SLASH (partial[1]) ||
119 (partial[1] == '.' && IS_SLASH (partial[2]))))
120 {
121 if (local_temp_filename_size < 1024)
122 local_temp_filename = (char *)xrealloc
123 (local_temp_filename, (local_temp_filename_size = 1024));
124 #if defined (HAVE_GETCWD)
125 if (!getcwd (local_temp_filename, local_temp_filename_size))
126 #else /* !HAVE_GETCWD */
127 if (!getwd (local_temp_filename))
128 #endif /* !HAVE_GETCWD */
129 {
130 filesys_error_number = errno;
131 return (partial);
132 }
133
134 strcat (local_temp_filename, "/");
135 strcat (local_temp_filename, partial);
136 temp = info_absolute_file (local_temp_filename); /* try extensions */
137 if (!temp)
138 partial = local_temp_filename;
139 }
140 else
141 temp = info_file_in_path (partial, infopath);
142
143 if (temp)
144 {
145 remember_info_filename (partial, temp);
146 if (strlen (temp) > (unsigned int) local_temp_filename_size)
147 local_temp_filename = (char *) xrealloc
148 (local_temp_filename,
149 (local_temp_filename_size = (50 + strlen (temp))));
150 strcpy (local_temp_filename, temp);
151 free (temp);
152 return (local_temp_filename);
153 }
154 }
155 return (partial);
156 }
157
158 /* Scan the list of directories in PATH looking for FILENAME. If we find
159 one that is a regular file, return it as a new string. Otherwise, return
160 a NULL pointer. */
161 static char *
info_file_in_path(char * filename,char * path)162 info_file_in_path (char *filename, char *path)
163 {
164 struct stat finfo;
165 char *temp_dirname;
166 int statable, dirname_index;
167
168 /* Reject ridiculous cases up front, to prevent infinite recursion
169 later on. E.g., someone might say "info '(.)foo'"... */
170 if (!*filename || STREQ (filename, ".") || STREQ (filename, ".."))
171 return NULL;
172
173 dirname_index = 0;
174
175 while ((temp_dirname = extract_colon_unit (path, &dirname_index)))
176 {
177 register int i, pre_suffix_length;
178 char *temp;
179
180 /* Expand a leading tilde if one is present. */
181 if (*temp_dirname == '~')
182 {
183 char *expanded_dirname;
184
185 expanded_dirname = tilde_expand_word (temp_dirname);
186 free (temp_dirname);
187 temp_dirname = expanded_dirname;
188 }
189
190 temp = (char *)xmalloc (30 + strlen (temp_dirname) + strlen (filename));
191 strcpy (temp, temp_dirname);
192 if (!IS_SLASH (temp[(strlen (temp)) - 1]))
193 strcat (temp, "/");
194 strcat (temp, filename);
195
196 pre_suffix_length = strlen (temp);
197
198 free (temp_dirname);
199
200 for (i = 0; info_suffixes[i]; i++)
201 {
202 strcpy (temp + pre_suffix_length, info_suffixes[i]);
203
204 statable = (stat (temp, &finfo) == 0);
205
206 /* If we have found a regular file, then use that. Else, if we
207 have found a directory, look in that directory for this file. */
208 if (statable)
209 {
210 if (S_ISREG (finfo.st_mode))
211 {
212 return (temp);
213 }
214 else if (S_ISDIR (finfo.st_mode))
215 {
216 char *newpath, *filename_only, *newtemp;
217
218 newpath = xstrdup (temp);
219 filename_only = filename_non_directory (filename);
220 newtemp = info_file_in_path (filename_only, newpath);
221
222 free (newpath);
223 if (newtemp)
224 {
225 free (temp);
226 return (newtemp);
227 }
228 }
229 }
230 else
231 {
232 /* Add various compression suffixes to the name to see if
233 the file is present in compressed format. */
234 register int j, pre_compress_suffix_length;
235
236 pre_compress_suffix_length = strlen (temp);
237
238 for (j = 0; compress_suffixes[j].suffix; j++)
239 {
240 strcpy (temp + pre_compress_suffix_length,
241 compress_suffixes[j].suffix);
242
243 statable = (stat (temp, &finfo) == 0);
244 if (statable && (S_ISREG (finfo.st_mode)))
245 return (temp);
246 }
247 }
248 }
249 free (temp);
250 }
251 return ((char *)NULL);
252 }
253
254 /* Assume FNAME is an absolute file name, and check whether it is
255 a regular file. If it is, return it as a new string; otherwise
256 return a NULL pointer. We do it by taking the file name apart
257 into its directory and basename parts, and calling info_file_in_path.*/
258 static char *
info_absolute_file(char * fname)259 info_absolute_file (char *fname)
260 {
261 char *containing_dir = xstrdup (fname);
262 char *base = filename_non_directory (containing_dir);
263
264 if (base > containing_dir)
265 base[-1] = '\0';
266
267 return info_file_in_path (filename_non_directory (fname), containing_dir);
268 }
269
270
271 /* Given a string containing units of information separated by the
272 PATH_SEP character, return the next one after IDX, or NULL if there
273 are no more. Advance IDX to the character after the colon. */
274
275 char *
extract_colon_unit(char * string,int * idx)276 extract_colon_unit (char *string, int *idx)
277 {
278 unsigned int i = (unsigned int) *idx;
279 unsigned int start = i;
280
281 if (!string || i >= strlen (string))
282 return NULL;
283
284 if (!string[i]) /* end of string */
285 return NULL;
286
287 /* Advance to next PATH_SEP. */
288 while (string[i] && string[i] != PATH_SEP[0])
289 i++;
290
291 {
292 char *value = xmalloc ((i - start) + 1);
293 strncpy (value, &string[start], (i - start));
294 value[i - start] = 0;
295
296 i++; /* move past PATH_SEP */
297 *idx = i;
298 return value;
299 }
300 }
301
302 /* A structure which associates a filename with its expansion. */
303 typedef struct
304 {
305 char *filename;
306 char *expansion;
307 } FILENAME_LIST;
308
309 /* An array of remembered arguments and results. */
310 static FILENAME_LIST **names_and_files = (FILENAME_LIST **)NULL;
311 static int names_and_files_index = 0;
312 static int names_and_files_slots = 0;
313
314 /* Find the result for having already called info_find_fullpath () with
315 FILENAME. */
316 static char *
lookup_info_filename(char * filename)317 lookup_info_filename (char *filename)
318 {
319 if (filename && names_and_files)
320 {
321 register int i;
322 for (i = 0; names_and_files[i]; i++)
323 {
324 if (FILENAME_CMP (names_and_files[i]->filename, filename) == 0)
325 return (names_and_files[i]->expansion);
326 }
327 }
328 return (char *)NULL;;
329 }
330
331 /* Add a filename and its expansion to our list. */
332 static void
remember_info_filename(char * filename,char * expansion)333 remember_info_filename (char *filename, char *expansion)
334 {
335 FILENAME_LIST *new;
336
337 if (names_and_files_index + 2 > names_and_files_slots)
338 {
339 int alloc_size;
340 names_and_files_slots += 10;
341
342 alloc_size = names_and_files_slots * sizeof (FILENAME_LIST *);
343
344 names_and_files =
345 (FILENAME_LIST **) xrealloc (names_and_files, alloc_size);
346 }
347
348 new = (FILENAME_LIST *)xmalloc (sizeof (FILENAME_LIST));
349 new->filename = xstrdup (filename);
350 new->expansion = expansion ? xstrdup (expansion) : (char *)NULL;
351
352 names_and_files[names_and_files_index++] = new;
353 names_and_files[names_and_files_index] = (FILENAME_LIST *)NULL;
354 }
355
356 static void
maybe_initialize_infopath(void)357 maybe_initialize_infopath (void)
358 {
359 if (!infopath_size)
360 {
361 infopath = (char *)
362 xmalloc (infopath_size = (1 + strlen (DEFAULT_INFOPATH)));
363
364 strcpy (infopath, DEFAULT_INFOPATH);
365 }
366 }
367
368 /* Add PATH to the list of paths found in INFOPATH. 2nd argument says
369 whether to put PATH at the front or end of INFOPATH. */
370 void
info_add_path(char * path,int where)371 info_add_path (char *path, int where)
372 {
373 int len;
374
375 if (!infopath)
376 {
377 infopath = (char *)xmalloc (infopath_size = 200 + strlen (path));
378 infopath[0] = '\0';
379 }
380
381 len = strlen (path) + strlen (infopath);
382
383 if (len + 2 >= infopath_size)
384 infopath = (char *)xrealloc (infopath, (infopath_size += (2 * len) + 2));
385
386 if (!*infopath)
387 strcpy (infopath, path);
388 else if (where == INFOPATH_APPEND)
389 {
390 strcat (infopath, PATH_SEP);
391 strcat (infopath, path);
392 }
393 else if (where == INFOPATH_PREPEND)
394 {
395 char *temp = xstrdup (infopath);
396 strcpy (infopath, path);
397 strcat (infopath, PATH_SEP);
398 strcat (infopath, temp);
399 free (temp);
400 }
401 }
402
403 /* Make INFOPATH have absolutely nothing in it. */
404 void
zap_infopath(void)405 zap_infopath (void)
406 {
407 if (infopath)
408 free (infopath);
409
410 infopath = (char *)NULL;
411 infopath_size = 0;
412 }
413
414 /* Given a chunk of text and its length, convert all CRLF pairs at every
415 end-of-line into a single Newline character. Return the length of
416 produced text.
417
418 This is required because the rest of code is too entrenched in having
419 a single newline at each EOL; in particular, searching for various
420 Info headers and cookies can become extremely tricky if that assumption
421 breaks.
422
423 FIXME: this could also support Mac-style text files with a single CR
424 at the EOL, but what about random CR characters in non-Mac files? Can
425 we afford converting them into newlines as well? Maybe implement some
426 heuristics here, like in Emacs 20.
427
428 FIXME: is it a good idea to show the EOL type on the modeline? */
429 long
convert_eols(char * text,long int textlen)430 convert_eols (char *text, long int textlen)
431 {
432 register char *s = text;
433 register char *d = text;
434
435 while (textlen--)
436 {
437 if (*s == '\r' && textlen && s[1] == '\n')
438 {
439 s++;
440 textlen--;
441 }
442 *d++ = *s++;
443 }
444
445 return (long)(d - text);
446 }
447
448 /* Read the contents of PATHNAME, returning a buffer with the contents of
449 that file in it, and returning the size of that buffer in FILESIZE.
450 FINFO is a stat struct which has already been filled in by the caller.
451 If the file turns out to be compressed, set IS_COMPRESSED to non-zero.
452 If the file cannot be read, return a NULL pointer. */
453 char *
filesys_read_info_file(char * pathname,long int * filesize,struct stat * finfo,int * is_compressed)454 filesys_read_info_file (char *pathname, long int *filesize,
455 struct stat *finfo, int *is_compressed)
456 {
457 long st_size;
458
459 *filesize = filesys_error_number = 0;
460
461 if (compressed_filename_p (pathname))
462 {
463 *is_compressed = 1;
464 return (filesys_read_compressed (pathname, filesize));
465 }
466 else
467 {
468 int descriptor;
469 char *contents;
470
471 *is_compressed = 0;
472 descriptor = open (pathname, O_RDONLY | O_BINARY, 0666);
473
474 /* If the file couldn't be opened, give up. */
475 if (descriptor < 0)
476 {
477 filesys_error_number = errno;
478 return ((char *)NULL);
479 }
480
481 /* Try to read the contents of this file. */
482 st_size = (long) finfo->st_size;
483 contents = (char *)xmalloc (1 + st_size);
484 if ((read (descriptor, contents, st_size)) != st_size)
485 {
486 filesys_error_number = errno;
487 close (descriptor);
488 free (contents);
489 return ((char *)NULL);
490 }
491
492 close (descriptor);
493
494 /* Convert any DOS-style CRLF EOLs into Unix-style NL.
495 Seems like a good idea to have even on Unix, in case the Info
496 files are coming from some Windows system across a network. */
497 *filesize = convert_eols (contents, st_size);
498
499 /* EOL conversion can shrink the text quite a bit. We don't
500 want to waste storage. */
501 if (*filesize < st_size)
502 contents = (char *)xrealloc (contents, 1 + *filesize);
503 contents[*filesize] = '\0';
504
505 return (contents);
506 }
507 }
508
509 /* Typically, pipe buffers are 4k. */
510 #define BASIC_PIPE_BUFFER (4 * 1024)
511
512 /* We use some large multiple of that. */
513 #define FILESYS_PIPE_BUFFER_SIZE (16 * BASIC_PIPE_BUFFER)
514
515 char *
filesys_read_compressed(char * pathname,long int * filesize)516 filesys_read_compressed (char *pathname, long int *filesize)
517 {
518 FILE *stream;
519 char *command, *decompressor;
520 char *contents = (char *)NULL;
521
522 *filesize = filesys_error_number = 0;
523
524 decompressor = filesys_decompressor_for_file (pathname);
525
526 if (!decompressor)
527 return ((char *)NULL);
528
529 command = (char *)xmalloc (15 + strlen (pathname) + strlen (decompressor));
530 /* Explicit .exe suffix makes the diagnostics of `popen'
531 better on systems where COMMAND.COM is the stock shell. */
532 sprintf (command, "%s%s < %s",
533 decompressor, STRIP_DOT_EXE ? ".exe" : "", pathname);
534
535 #if !defined (BUILDING_LIBRARY)
536 if (info_windows_initialized_p)
537 {
538 char *temp;
539
540 temp = (char *)xmalloc (5 + strlen (command));
541 sprintf (temp, "%s...", command);
542 message_in_echo_area ("%s", temp, NULL);
543 free (temp);
544 }
545 #endif /* !BUILDING_LIBRARY */
546
547 stream = popen (command, FOPEN_RBIN);
548 free (command);
549
550 /* Read chunks from this file until there are none left to read. */
551 if (stream)
552 {
553 long offset, size;
554 char *chunk;
555
556 offset = size = 0;
557 chunk = (char *)xmalloc (FILESYS_PIPE_BUFFER_SIZE);
558
559 while (1)
560 {
561 int bytes_read;
562
563 bytes_read = fread (chunk, 1, FILESYS_PIPE_BUFFER_SIZE, stream);
564
565 if (bytes_read + offset >= size)
566 contents = (char *)xrealloc
567 (contents, size += (2 * FILESYS_PIPE_BUFFER_SIZE));
568
569 memcpy (contents + offset, chunk, bytes_read);
570 offset += bytes_read;
571 if (bytes_read != FILESYS_PIPE_BUFFER_SIZE)
572 break;
573 }
574
575 free (chunk);
576 if (pclose (stream) == -1)
577 {
578 if (contents)
579 free (contents);
580 contents = (char *)NULL;
581 filesys_error_number = errno;
582 }
583 else
584 {
585 *filesize = convert_eols (contents, offset);
586 contents = (char *)xrealloc (contents, 1 + *filesize);
587 contents[*filesize] = '\0';
588 }
589 }
590 else
591 {
592 filesys_error_number = errno;
593 }
594
595 #if !defined (BUILDING_LIBARARY)
596 if (info_windows_initialized_p)
597 unmessage_in_echo_area ();
598 #endif /* !BUILDING_LIBRARY */
599 return (contents);
600 }
601
602 /* Return non-zero if FILENAME belongs to a compressed file. */
603 int
compressed_filename_p(char * filename)604 compressed_filename_p (char *filename)
605 {
606 char *decompressor;
607
608 /* Find the final extension of this filename, and see if it matches one
609 of our known ones. */
610 decompressor = filesys_decompressor_for_file (filename);
611
612 if (decompressor)
613 return (1);
614 else
615 return (0);
616 }
617
618 /* Return the command string that would be used to decompress FILENAME. */
619 char *
filesys_decompressor_for_file(char * filename)620 filesys_decompressor_for_file (char *filename)
621 {
622 register int i;
623 char *extension = (char *)NULL;
624
625 /* Find the final extension of FILENAME, and see if it appears in our
626 list of known compression extensions. */
627 for (i = strlen (filename) - 1; i > 0; i--)
628 if (filename[i] == '.')
629 {
630 extension = filename + i;
631 break;
632 }
633
634 if (!extension)
635 return ((char *)NULL);
636
637 for (i = 0; compress_suffixes[i].suffix; i++)
638 if (FILENAME_CMP (extension, compress_suffixes[i].suffix) == 0)
639 return (compress_suffixes[i].decompressor);
640
641 #if defined (__MSDOS__)
642 /* If no other suffix matched, allow any extension which ends
643 with `z' to be decompressed by gunzip. Due to limited 8+3 DOS
644 file namespace, we can expect many such cases, and supporting
645 every weird suffix thus produced would be a pain. */
646 if (extension[strlen (extension) - 1] == 'z' ||
647 extension[strlen (extension) - 1] == 'Z')
648 return "gunzip";
649 #endif
650
651 return ((char *)NULL);
652 }
653
654 /* The number of the most recent file system error. */
655 int filesys_error_number = 0;
656
657 /* A function which returns a pointer to a static buffer containing
658 an error message for FILENAME and ERROR_NUM. */
659 static char *errmsg_buf = (char *)NULL;
660 static int errmsg_buf_size = 0;
661
662 char *
filesys_error_string(char * filename,int error_num)663 filesys_error_string (char *filename, int error_num)
664 {
665 int len;
666 char *result;
667
668 if (error_num == 0)
669 return ((char *)NULL);
670
671 result = strerror (error_num);
672
673 len = 4 + strlen (filename) + strlen (result);
674 if (len >= errmsg_buf_size)
675 errmsg_buf = (char *)xrealloc (errmsg_buf, (errmsg_buf_size = 2 + len));
676
677 sprintf (errmsg_buf, "%s: %s", filename, result);
678 return (errmsg_buf);
679 }
680
681
682 /* Check for "dir" with all the possible info and compression suffixes,
683 in combination. */
684
685 int
is_dir_name(char * filename)686 is_dir_name (char *filename)
687 {
688 unsigned i;
689
690 for (i = 0; info_suffixes[i]; i++)
691 {
692 unsigned c;
693 char trydir[50];
694 strcpy (trydir, "dir");
695 strcat (trydir, info_suffixes[i]);
696
697 if (strcasecmp (filename, trydir) == 0)
698 return 1;
699
700 for (c = 0; compress_suffixes[c].suffix; c++)
701 {
702 char dir_compressed[50]; /* can be short */
703 strcpy (dir_compressed, trydir);
704 strcat (dir_compressed, compress_suffixes[c].suffix);
705 if (strcasecmp (filename, dir_compressed) == 0)
706 return 1;
707 }
708 }
709
710 return 0;
711 }
712