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