1130803Smarcel/* filesys.c -- filesystem specific functions. 2130803Smarcel $Id: filesys.c,v 1.6 2004/07/30 17:17:40 karl Exp $ 3130803Smarcel 4130803Smarcel Copyright (C) 1993, 1997, 1998, 2000, 2002, 2003, 2004 Free Software 5130803Smarcel Foundation, Inc. 6130803Smarcel 7130803Smarcel This program is free software; you can redistribute it and/or modify 8130803Smarcel it under the terms of the GNU General Public License as published by 9130803Smarcel the Free Software Foundation; either version 2, or (at your option) 10130803Smarcel any later version. 11130803Smarcel 12130803Smarcel This program is distributed in the hope that it will be useful, 13130803Smarcel but WITHOUT ANY WARRANTY; without even the implied warranty of 14130803Smarcel MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15130803Smarcel GNU General Public License for more details. 16130803Smarcel 17130803Smarcel You should have received a copy of the GNU General Public License 18130803Smarcel along with this program; if not, write to the Free Software 19130803Smarcel Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 20130803Smarcel 21130803Smarcel Written by Brian Fox (bfox@ai.mit.edu). */ 22130803Smarcel 23130803Smarcel#include "info.h" 24130803Smarcel 25130803Smarcel#include "tilde.h" 26130803Smarcel#include "filesys.h" 27130803Smarcel 28130803Smarcel/* Local to this file. */ 29130803Smarcelstatic char *info_file_in_path (char *filename, char *path); 30130803Smarcelstatic char *lookup_info_filename (char *filename); 31130803Smarcelstatic char *info_absolute_file (char *fname); 32130803Smarcel 33130803Smarcelstatic void remember_info_filename (char *filename, char *expansion); 34130803Smarcelstatic void maybe_initialize_infopath (void); 35130803Smarcel 36130803Smarceltypedef struct 37130803Smarcel{ 38130803Smarcel char *suffix; 39130803Smarcel char *decompressor; 40130803Smarcel} COMPRESSION_ALIST; 41130803Smarcel 42130803Smarcelstatic char *info_suffixes[] = { 43130803Smarcel ".info", 44130803Smarcel "-info", 45130803Smarcel "/index", 46130803Smarcel ".inf", /* 8+3 file on filesystem which supports long file names */ 47130803Smarcel#ifdef __MSDOS__ 48130803Smarcel /* 8+3 file names strike again... */ 49130803Smarcel ".in", /* for .inz, .igz etc. */ 50130803Smarcel ".i", 51130803Smarcel#endif 52130803Smarcel "", 53130803Smarcel NULL 54130803Smarcel}; 55130803Smarcel 56130803Smarcelstatic COMPRESSION_ALIST compress_suffixes[] = { 57130803Smarcel { ".gz", "gunzip" }, 58130803Smarcel { ".bz2", "bunzip2" }, 59130803Smarcel { ".z", "gunzip" }, 60130803Smarcel { ".Z", "uncompress" }, 61130803Smarcel { ".Y", "unyabba" }, 62130803Smarcel#ifdef __MSDOS__ 63130803Smarcel { "gz", "gunzip" }, 64130803Smarcel { "z", "gunzip" }, 65130803Smarcel#endif 66130803Smarcel { (char *)NULL, (char *)NULL } 67130803Smarcel}; 68130803Smarcel 69130803Smarcel/* The path on which we look for info files. You can initialize this 70130803Smarcel from the environment variable INFOPATH if there is one, or you can 71130803Smarcel call info_add_path () to add paths to the beginning or end of it. 72130803Smarcel You can call zap_infopath () to make the path go away. */ 73130803Smarcelchar *infopath = (char *)NULL; 74130803Smarcelstatic int infopath_size = 0; 75130803Smarcel 76130803Smarcel/* Expand the filename in PARTIAL to make a real name for this operating 77130803Smarcel system. This looks in INFO_PATHS in order to find the correct file. 78130803Smarcel If it can't find the file, it returns NULL. */ 79130803Smarcelstatic char *local_temp_filename = (char *)NULL; 80130803Smarcelstatic int local_temp_filename_size = 0; 81130803Smarcel 82130803Smarcelchar * 83130803Smarcelinfo_find_fullpath (char *partial) 84130803Smarcel{ 85130803Smarcel int initial_character; 86130803Smarcel char *temp; 87130803Smarcel 88130803Smarcel filesys_error_number = 0; 89130803Smarcel 90130803Smarcel maybe_initialize_infopath (); 91130803Smarcel 92130803Smarcel if (partial && (initial_character = *partial)) 93130803Smarcel { 94130803Smarcel char *expansion; 95130803Smarcel 96130803Smarcel expansion = lookup_info_filename (partial); 97130803Smarcel 98130803Smarcel if (expansion) 99130803Smarcel return (expansion); 100130803Smarcel 101130803Smarcel /* If we have the full path to this file, we still may have to add 102130803Smarcel various extensions to it. I guess we have to stat this file 103130803Smarcel after all. */ 104130803Smarcel if (IS_ABSOLUTE (partial)) 105130803Smarcel temp = info_absolute_file (partial); 106130803Smarcel else if (initial_character == '~') 107130803Smarcel { 108130803Smarcel expansion = tilde_expand_word (partial); 109130803Smarcel if (IS_ABSOLUTE (expansion)) 110130803Smarcel { 111130803Smarcel temp = info_absolute_file (expansion); 112130803Smarcel free (expansion); 113130803Smarcel } 114130803Smarcel else 115130803Smarcel temp = expansion; 116130803Smarcel } 117130803Smarcel else if (initial_character == '.' && 118130803Smarcel (IS_SLASH (partial[1]) || 119130803Smarcel (partial[1] == '.' && IS_SLASH (partial[2])))) 120130803Smarcel { 121130803Smarcel if (local_temp_filename_size < 1024) 122130803Smarcel local_temp_filename = (char *)xrealloc 123130803Smarcel (local_temp_filename, (local_temp_filename_size = 1024)); 124130803Smarcel#if defined (HAVE_GETCWD) 125130803Smarcel if (!getcwd (local_temp_filename, local_temp_filename_size)) 126130803Smarcel#else /* !HAVE_GETCWD */ 127130803Smarcel if (!getwd (local_temp_filename)) 128130803Smarcel#endif /* !HAVE_GETCWD */ 129130803Smarcel { 130130803Smarcel filesys_error_number = errno; 131130803Smarcel return (partial); 132130803Smarcel } 133130803Smarcel 134130803Smarcel strcat (local_temp_filename, "/"); 135130803Smarcel strcat (local_temp_filename, partial); 136130803Smarcel temp = info_absolute_file (local_temp_filename); /* try extensions */ 137130803Smarcel if (!temp) 138130803Smarcel partial = local_temp_filename; 139130803Smarcel } 140130803Smarcel else 141130803Smarcel temp = info_file_in_path (partial, infopath); 142130803Smarcel 143130803Smarcel if (temp) 144130803Smarcel { 145130803Smarcel remember_info_filename (partial, temp); 146130803Smarcel if (strlen (temp) > (unsigned int) local_temp_filename_size) 147130803Smarcel local_temp_filename = (char *) xrealloc 148130803Smarcel (local_temp_filename, 149130803Smarcel (local_temp_filename_size = (50 + strlen (temp)))); 150130803Smarcel strcpy (local_temp_filename, temp); 151130803Smarcel free (temp); 152130803Smarcel return (local_temp_filename); 153130803Smarcel } 154130803Smarcel } 155130803Smarcel return (partial); 156130803Smarcel} 157130803Smarcel 158130803Smarcel/* Scan the list of directories in PATH looking for FILENAME. If we find 159130803Smarcel one that is a regular file, return it as a new string. Otherwise, return 160130803Smarcel a NULL pointer. */ 161130803Smarcelstatic char * 162130803Smarcelinfo_file_in_path (char *filename, char *path) 163130803Smarcel{ 164130803Smarcel struct stat finfo; 165130803Smarcel char *temp_dirname; 166130803Smarcel int statable, dirname_index; 167130803Smarcel 168130803Smarcel /* Reject ridiculous cases up front, to prevent infinite recursion 169130803Smarcel later on. E.g., someone might say "info '(.)foo'"... */ 170130803Smarcel if (!*filename || STREQ (filename, ".") || STREQ (filename, "..")) 171130803Smarcel return NULL; 172130803Smarcel 173130803Smarcel dirname_index = 0; 174130803Smarcel 175130803Smarcel while ((temp_dirname = extract_colon_unit (path, &dirname_index))) 176130803Smarcel { 177130803Smarcel register int i, pre_suffix_length; 178130803Smarcel char *temp; 179130803Smarcel 180130803Smarcel /* Expand a leading tilde if one is present. */ 181130803Smarcel if (*temp_dirname == '~') 182130803Smarcel { 183130803Smarcel char *expanded_dirname; 184130803Smarcel 185130803Smarcel expanded_dirname = tilde_expand_word (temp_dirname); 186130803Smarcel free (temp_dirname); 187130803Smarcel temp_dirname = expanded_dirname; 188130803Smarcel } 189130803Smarcel 190130803Smarcel temp = (char *)xmalloc (30 + strlen (temp_dirname) + strlen (filename)); 191130803Smarcel strcpy (temp, temp_dirname); 192130803Smarcel if (!IS_SLASH (temp[(strlen (temp)) - 1])) 193130803Smarcel strcat (temp, "/"); 194130803Smarcel strcat (temp, filename); 195130803Smarcel 196130803Smarcel pre_suffix_length = strlen (temp); 197130803Smarcel 198130803Smarcel free (temp_dirname); 199130803Smarcel 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.*/ 258static char * 259info_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 275char * 276extract_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. */ 303typedef struct 304{ 305 char *filename; 306 char *expansion; 307} FILENAME_LIST; 308 309/* An array of remembered arguments and results. */ 310static FILENAME_LIST **names_and_files = (FILENAME_LIST **)NULL; 311static int names_and_files_index = 0; 312static int names_and_files_slots = 0; 313 314/* Find the result for having already called info_find_fullpath () with 315 FILENAME. */ 316static char * 317lookup_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. */ 332static void 333remember_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 356static void 357maybe_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. */ 370void 371info_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. */ 404void 405zap_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? */ 429long 430convert_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. */ 453char * 454filesys_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 515char * 516filesys_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. */ 603int 604compressed_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. */ 619char * 620filesys_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. */ 655int 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. */ 659static char *errmsg_buf = (char *)NULL; 660static int errmsg_buf_size = 0; 661 662char * 663filesys_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 685int 686is_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