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 * 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 * 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 * 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 * 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 * 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 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 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 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 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 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 * 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 * 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 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 * 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 * 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 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