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