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