1/* files.c -- file-related functions for makeinfo. 2 $Id: files.c,v 1.5 2004/07/27 00:06:31 karl Exp $ 3 4 Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004 Free Software 5 Foundation, Inc. 6 7 This program is free software; you can redistribute it and/or modify 8 it under the terms of the GNU General Public License as published by 9 the Free Software Foundation; either version 2, or (at your option) 10 any later version. 11 12 This program is distributed in the hope that it will be useful, 13 but WITHOUT ANY WARRANTY; without even the implied warranty of 14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 GNU General Public License for more details. 16 17 You should have received a copy of the GNU General Public License 18 along with this program; if not, write to the Free Software Foundation, 19 Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ 20 21#include "system.h" 22#include "files.h" 23#include "html.h" 24#include "index.h" 25#include "macro.h" 26#include "makeinfo.h" 27#include "node.h" 28 29FSTACK *filestack = NULL; 30 31static int node_filename_stack_index = 0; 32static int node_filename_stack_size = 0; 33static char **node_filename_stack = NULL; 34 35/* Looking for include files. */ 36 37/* Given a string containing units of information separated by colons, 38 return the next one pointed to by INDEX, or NULL if there are no more. 39 Advance INDEX to the character after the colon. */ 40static char * 41extract_colon_unit (char *string, int *index) 42{ 43 int start; 44 int path_sep_char = PATH_SEP[0]; 45 int i = *index; 46 47 if (!string || (i >= strlen (string))) 48 return NULL; 49 50 /* Each call to this routine leaves the index pointing at a colon if 51 there is more to the path. If i > 0, then increment past the 52 `:'. If i == 0, then the path has a leading colon. Trailing colons 53 are handled OK by the `else' part of the if statement; an empty 54 string is returned in that case. */ 55 if (i && string[i] == path_sep_char) 56 i++; 57 58 start = i; 59 while (string[i] && string[i] != path_sep_char) i++; 60 *index = i; 61 62 if (i == start) 63 { 64 if (string[i]) 65 (*index)++; 66 67 /* Return "" in the case of a trailing `:'. */ 68 return xstrdup (""); 69 } 70 else 71 { 72 char *value; 73 74 value = xmalloc (1 + (i - start)); 75 memcpy (value, &string[start], (i - start)); 76 value [i - start] = 0; 77 78 return value; 79 } 80} 81 82/* Return the full pathname for FILENAME by searching along PATH. 83 When found, return the stat () info for FILENAME in FINFO. 84 If PATH is NULL, only the current directory is searched. 85 If the file could not be found, return a NULL pointer. */ 86char * 87get_file_info_in_path (char *filename, char *path, struct stat *finfo) 88{ 89 char *dir; 90 int result, index = 0; 91 92 if (path == NULL) 93 path = "."; 94 95 /* Handle absolute pathnames. */ 96 if (IS_ABSOLUTE (filename) 97 || (*filename == '.' 98 && (IS_SLASH (filename[1]) 99 || (filename[1] == '.' && IS_SLASH (filename[2]))))) 100 { 101 if (stat (filename, finfo) == 0) 102 return xstrdup (filename); 103 else 104 return NULL; 105 } 106 107 while ((dir = extract_colon_unit (path, &index))) 108 { 109 char *fullpath; 110 111 if (!*dir) 112 { 113 free (dir); 114 dir = xstrdup ("."); 115 } 116 117 fullpath = xmalloc (2 + strlen (dir) + strlen (filename)); 118 sprintf (fullpath, "%s/%s", dir, filename); 119 free (dir); 120 121 result = stat (fullpath, finfo); 122 123 if (result == 0) 124 return fullpath; 125 else 126 free (fullpath); 127 } 128 return NULL; 129} 130 131/* Prepend and append new paths to include_files_path. */ 132void 133prepend_to_include_path (char *path) 134{ 135 if (!include_files_path) 136 { 137 include_files_path = xstrdup (path); 138 include_files_path = xrealloc (include_files_path, 139 strlen (include_files_path) + 3); /* 3 for ":.\0" */ 140 strcat (strcat (include_files_path, PATH_SEP), "."); 141 } 142 else 143 { 144 char *tmp = xstrdup (include_files_path); 145 include_files_path = xrealloc (include_files_path, 146 strlen (include_files_path) + strlen (path) + 2); /* 2 for ":\0" */ 147 strcpy (include_files_path, path); 148 strcat (include_files_path, PATH_SEP); 149 strcat (include_files_path, tmp); 150 free (tmp); 151 } 152} 153 154void 155append_to_include_path (char *path) 156{ 157 if (!include_files_path) 158 include_files_path = xstrdup ("."); 159 160 include_files_path = (char *) xrealloc (include_files_path, 161 2 + strlen (include_files_path) + strlen (path)); 162 strcat (include_files_path, PATH_SEP); 163 strcat (include_files_path, path); 164} 165 166/* Remove the first path from the include_files_path. */ 167void 168pop_path_from_include_path (void) 169{ 170 int i = 0; 171 char *tmp; 172 173 if (include_files_path) 174 for (i = 0; i < strlen (include_files_path) 175 && include_files_path[i] != ':'; i++); 176 177 /* Advance include_files_path to the next char from ':' */ 178 tmp = (char *) xmalloc (strlen (include_files_path) - i); 179 strcpy (tmp, (char *) include_files_path + i + 1); 180 181 free (include_files_path); 182 include_files_path = tmp; 183} 184 185/* Find and load the file named FILENAME. Return a pointer to 186 the loaded file, or NULL if it can't be loaded. If USE_PATH is zero, 187 just look for the given file (this is used in handle_delayed_writes), 188 else search along include_files_path. */ 189 190char * 191find_and_load (char *filename, int use_path) 192{ 193 struct stat fileinfo; 194 long file_size; 195 int file = -1, count = 0; 196 char *fullpath, *result; 197 int n, bytes_to_read; 198 199 result = fullpath = NULL; 200 201 fullpath 202 = get_file_info_in_path (filename, use_path ? include_files_path : NULL, 203 &fileinfo); 204 205 if (!fullpath) 206 goto error_exit; 207 208 filename = fullpath; 209 file_size = (long) fileinfo.st_size; 210 211 file = open (filename, O_RDONLY); 212 if (file < 0) 213 goto error_exit; 214 215 /* Load the file, with enough room for a newline and a null. */ 216 result = xmalloc (file_size + 2); 217 218 /* VMS stat lies about the st_size value. The actual number of 219 readable bytes is always less than this value. The arcane 220 mysteries of VMS/RMS are too much to probe, so this hack 221 suffices to make things work. It's also needed on Cygwin. And so 222 we might as well use it everywhere. */ 223 bytes_to_read = file_size; 224 while ((n = read (file, result + count, bytes_to_read)) > 0) 225 { 226 count += n; 227 bytes_to_read -= n; 228 } 229 if (0 < count && count < file_size) 230 result = xrealloc (result, count + 2); /* why waste the slack? */ 231 else if (n == -1) 232error_exit: 233 { 234 if (result) 235 free (result); 236 237 if (fullpath) 238 free (fullpath); 239 240 if (file != -1) 241 close (file); 242 243 return NULL; 244 } 245 close (file); 246 247 /* Set the globals to the new file. */ 248 input_text = result; 249 input_text_length = count; 250 input_filename = fullpath; 251 node_filename = xstrdup (fullpath); 252 input_text_offset = 0; 253 line_number = 1; 254 /* Not strictly necessary. This magic prevents read_token () from doing 255 extra unnecessary work each time it is called (that is a lot of times). 256 INPUT_TEXT_LENGTH is one past the actual end of the text. */ 257 input_text[input_text_length] = '\n'; 258 /* This, on the other hand, is always necessary. */ 259 input_text[input_text_length+1] = 0; 260 return result; 261} 262 263/* Pushing and popping files. */ 264static void 265push_node_filename (void) 266{ 267 if (node_filename_stack_index + 1 > node_filename_stack_size) 268 node_filename_stack = xrealloc 269 (node_filename_stack, (node_filename_stack_size += 10) * sizeof (char *)); 270 271 node_filename_stack[node_filename_stack_index] = node_filename; 272 node_filename_stack_index++; 273} 274 275static void 276pop_node_filename (void) 277{ 278 node_filename = node_filename_stack[--node_filename_stack_index]; 279} 280 281/* Save the state of the current input file. */ 282void 283pushfile (void) 284{ 285 FSTACK *newstack = xmalloc (sizeof (FSTACK)); 286 newstack->filename = input_filename; 287 newstack->text = input_text; 288 newstack->size = input_text_length; 289 newstack->offset = input_text_offset; 290 newstack->line_number = line_number; 291 newstack->next = filestack; 292 293 filestack = newstack; 294 push_node_filename (); 295} 296 297/* Make the current file globals be what is on top of the file stack. */ 298void 299popfile (void) 300{ 301 FSTACK *tos = filestack; 302 303 if (!tos) 304 abort (); /* My fault. I wonder what I did? */ 305 306 if (macro_expansion_output_stream) 307 { 308 maybe_write_itext (input_text, input_text_offset); 309 forget_itext (input_text); 310 } 311 312 /* Pop the stack. */ 313 filestack = filestack->next; 314 315 /* Make sure that commands with braces have been satisfied. */ 316 if (!executing_string && !me_executing_string) 317 discard_braces (); 318 319 /* Get the top of the stack into the globals. */ 320 input_filename = tos->filename; 321 input_text = tos->text; 322 input_text_length = tos->size; 323 input_text_offset = tos->offset; 324 line_number = tos->line_number; 325 free (tos); 326 327 /* Go back to the (now) current node. */ 328 pop_node_filename (); 329} 330 331/* Flush all open files on the file stack. */ 332void 333flush_file_stack (void) 334{ 335 while (filestack) 336 { 337 char *fname = input_filename; 338 char *text = input_text; 339 popfile (); 340 free (fname); 341 free (text); 342 } 343} 344 345/* Return the index of the first character in the filename 346 which is past all the leading directory characters. */ 347static int 348skip_directory_part (char *filename) 349{ 350 int i = strlen (filename) - 1; 351 352 while (i && !IS_SLASH (filename[i])) 353 i--; 354 if (IS_SLASH (filename[i])) 355 i++; 356 else if (filename[i] && HAVE_DRIVE (filename)) 357 i = 2; 358 359 return i; 360} 361 362static char * 363filename_non_directory (char *name) 364{ 365 return xstrdup (name + skip_directory_part (name)); 366} 367 368/* Return just the simple part of the filename; i.e. the 369 filename without the path information, or extensions. 370 This conses up a new string. */ 371char * 372filename_part (char *filename) 373{ 374 char *basename = filename_non_directory (filename); 375 376#ifdef REMOVE_OUTPUT_EXTENSIONS 377 /* See if there is an extension to remove. If so, remove it. */ 378 { 379 char *temp = strrchr (basename, '.'); 380 if (temp) 381 *temp = 0; 382 } 383#endif /* REMOVE_OUTPUT_EXTENSIONS */ 384 return basename; 385} 386 387/* Return the pathname part of filename. This can be NULL. */ 388char * 389pathname_part (char *filename) 390{ 391 char *result = NULL; 392 int i; 393 394 filename = expand_filename (filename, ""); 395 396 i = skip_directory_part (filename); 397 if (i) 398 { 399 result = xmalloc (1 + i); 400 strncpy (result, filename, i); 401 result[i] = 0; 402 } 403 free (filename); 404 return result; 405} 406 407/* Return the full path to FILENAME. */ 408static char * 409full_pathname (char *filename) 410{ 411 int initial_character; 412 char *result; 413 414 /* No filename given? */ 415 if (!filename || !*filename) 416 return xstrdup (""); 417 418 /* Already absolute? */ 419 if (IS_ABSOLUTE (filename) || 420 (*filename == '.' && 421 (IS_SLASH (filename[1]) || 422 (filename[1] == '.' && IS_SLASH (filename[2]))))) 423 return xstrdup (filename); 424 425 initial_character = *filename; 426 if (initial_character != '~') 427 { 428 char *localdir = xmalloc (1025); 429#ifdef HAVE_GETCWD 430 if (!getcwd (localdir, 1024)) 431#else 432 if (!getwd (localdir)) 433#endif 434 { 435 fprintf (stderr, _("%s: getwd: %s, %s\n"), 436 progname, filename, localdir); 437 xexit (1); 438 } 439 440 strcat (localdir, "/"); 441 strcat (localdir, filename); 442 result = xstrdup (localdir); 443 free (localdir); 444 } 445 else 446 { /* Does anybody know why WIN32 doesn't want to support $HOME? 447 If the reason is they don't have getpwnam, they should 448 only disable the else clause below. */ 449#ifndef WIN32 450 if (IS_SLASH (filename[1])) 451 { 452 /* Return the concatenation of the environment variable HOME 453 and the rest of the string. */ 454 char *temp_home; 455 456 temp_home = (char *) getenv ("HOME"); 457 result = xmalloc (strlen (&filename[1]) 458 + 1 459 + temp_home ? strlen (temp_home) 460 : 0); 461 *result = 0; 462 463 if (temp_home) 464 strcpy (result, temp_home); 465 466 strcat (result, &filename[1]); 467 } 468 else 469 { 470 struct passwd *user_entry; 471 int i, c; 472 char *username = xmalloc (257); 473 474 for (i = 1; (c = filename[i]); i++) 475 { 476 if (IS_SLASH (c)) 477 break; 478 else 479 username[i - 1] = c; 480 } 481 if (c) 482 username[i - 1] = 0; 483 484 user_entry = getpwnam (username); 485 486 if (!user_entry) 487 return xstrdup (filename); 488 489 result = xmalloc (1 + strlen (user_entry->pw_dir) 490 + strlen (&filename[i])); 491 strcpy (result, user_entry->pw_dir); 492 strcat (result, &filename[i]); 493 } 494#endif /* not WIN32 */ 495 } 496 return result; 497} 498 499/* Return the expansion of FILENAME. */ 500char * 501expand_filename (char *filename, char *input_name) 502{ 503 int i; 504 505 if (filename) 506 { 507 filename = full_pathname (filename); 508 if (IS_ABSOLUTE (filename) 509 || (*filename == '.' && 510 (IS_SLASH (filename[1]) || 511 (filename[1] == '.' && IS_SLASH (filename[2]))))) 512 return filename; 513 } 514 else 515 { 516 filename = filename_non_directory (input_name); 517 518 if (!*filename) 519 { 520 free (filename); 521 filename = xstrdup ("noname.texi"); 522 } 523 524 for (i = strlen (filename) - 1; i; i--) 525 if (filename[i] == '.') 526 break; 527 528 if (!i) 529 i = strlen (filename); 530 531 if (i + 6 > (strlen (filename))) 532 filename = xrealloc (filename, i + 6); 533 strcpy (filename + i, html ? ".html" : ".info"); 534 return filename; 535 } 536 537 if (IS_ABSOLUTE (input_name)) 538 { 539 /* Make it so that relative names work. */ 540 char *result; 541 542 i = strlen (input_name) - 1; 543 544 result = xmalloc (1 + strlen (input_name) + strlen (filename)); 545 strcpy (result, input_name); 546 547 while (!IS_SLASH (result[i]) && i) 548 i--; 549 if (IS_SLASH (result[i])) 550 i++; 551 552 strcpy (&result[i], filename); 553 free (filename); 554 return result; 555 } 556 return filename; 557} 558 559char * 560output_name_from_input_name (char *name) 561{ 562 return expand_filename (NULL, name); 563} 564 565 566/* Modify the file name FNAME so that it fits the limitations of the 567 underlying filesystem. In particular, truncate the file name as it 568 would be truncated by the filesystem. We assume the result can 569 never be longer than the original, otherwise we couldn't be sure we 570 have enough space in the original string to modify it in place. */ 571char * 572normalize_filename (char *fname) 573{ 574 int maxlen; 575 char orig[PATH_MAX + 1]; 576 int i; 577 char *lastdot, *p; 578 579#ifdef _PC_NAME_MAX 580 maxlen = pathconf (fname, _PC_NAME_MAX); 581 if (maxlen < 1) 582#endif 583 maxlen = PATH_MAX; 584 585 i = skip_directory_part (fname); 586 if (fname[i] == '\0') 587 return fname; /* only a directory name -- don't modify */ 588 strcpy (orig, fname + i); 589 590 switch (maxlen) 591 { 592 case 12: /* MS-DOS 8+3 filesystem */ 593 if (orig[0] == '.') /* leading dots are not allowed */ 594 orig[0] = '_'; 595 lastdot = strrchr (orig, '.'); 596 if (!lastdot) 597 lastdot = orig + strlen (orig); 598 strncpy (fname + i, orig, lastdot - orig); 599 for (p = fname + i; 600 p < fname + i + (lastdot - orig) && p < fname + i + 8; 601 p++) 602 if (*p == '.') 603 *p = '_'; 604 *p = '\0'; 605 if (*lastdot == '.') 606 strncat (fname + i, lastdot, 4); 607 break; 608 case 14: /* old Unix systems with 14-char limitation */ 609 strcpy (fname + i, orig); 610 if (strlen (fname + i) > 14) 611 fname[i + 14] = '\0'; 612 break; 613 default: 614 strcpy (fname + i, orig); 615 if (strlen (fname) > maxlen - 1) 616 fname[maxlen - 1] = '\0'; 617 break; 618 } 619 620 return fname; 621} 622 623/* Delayed writing functions. A few of the commands 624 needs to be handled at the end, namely @contents, 625 @shortcontents, @printindex and @listoffloats. 626 These functions take care of that. */ 627static DELAYED_WRITE *delayed_writes = NULL; 628int handling_delayed_writes = 0; 629 630void 631register_delayed_write (char *delayed_command) 632{ 633 DELAYED_WRITE *new; 634 635 if (!current_output_filename || !*current_output_filename) 636 { 637 /* Cannot register if we don't know what the output file is. */ 638 warning (_("`%s' omitted before output filename"), delayed_command); 639 return; 640 } 641 642 if (STREQ (current_output_filename, "-")) 643 { 644 /* Do not register a new write if the output file is not seekable. 645 Let the user know about it first, though. */ 646 warning (_("`%s' omitted since writing to stdout"), delayed_command); 647 return; 648 } 649 650 /* Don't complain if the user is writing /dev/null, since surely they 651 don't care, but don't register the delayed write, either. */ 652 if (FILENAME_CMP (current_output_filename, NULL_DEVICE) == 0 653 || FILENAME_CMP (current_output_filename, ALSO_NULL_DEVICE) == 0) 654 return; 655 656 /* We need the HTML header in the output, 657 to get a proper output_position. */ 658 if (!executing_string && html) 659 html_output_head (); 660 /* Get output_position updated. */ 661 flush_output (); 662 663 new = xmalloc (sizeof (DELAYED_WRITE)); 664 new->command = xstrdup (delayed_command); 665 new->filename = xstrdup (current_output_filename); 666 new->input_filename = xstrdup (input_filename); 667 new->position = output_position; 668 new->calling_line = line_number; 669 new->node = current_node ? xstrdup (current_node): ""; 670 671 new->node_order = node_order; 672 new->index_order = index_counter; 673 674 new->next = delayed_writes; 675 delayed_writes = new; 676} 677 678void 679handle_delayed_writes (void) 680{ 681 DELAYED_WRITE *temp = (DELAYED_WRITE *) reverse_list 682 ((GENERIC_LIST *) delayed_writes); 683 int position_shift_amount, line_number_shift_amount; 684 char *delayed_buf; 685 686 handling_delayed_writes = 1; 687 688 while (temp) 689 { 690 delayed_buf = find_and_load (temp->filename, 0); 691 692 if (output_paragraph_offset > 0) 693 { 694 error (_("Output buffer not empty.")); 695 return; 696 } 697 698 if (!delayed_buf) 699 { 700 fs_error (temp->filename); 701 return; 702 } 703 704 output_stream = fopen (temp->filename, "w"); 705 if (!output_stream) 706 { 707 fs_error (temp->filename); 708 return; 709 } 710 711 if (fwrite (delayed_buf, 1, temp->position, output_stream) != temp->position) 712 { 713 fs_error (temp->filename); 714 return; 715 } 716 717 { 718 int output_position_at_start = output_position; 719 int line_number_at_start = output_line_number; 720 721 /* In order to make warnings and errors 722 refer to the correct line number. */ 723 input_filename = temp->input_filename; 724 line_number = temp->calling_line; 725 726 execute_string ("%s", temp->command); 727 flush_output (); 728 729 /* Since the output file is modified, following delayed writes 730 need to be updated by this amount. */ 731 position_shift_amount = output_position - output_position_at_start; 732 line_number_shift_amount = output_line_number - line_number_at_start; 733 } 734 735 if (fwrite (delayed_buf + temp->position, 1, 736 input_text_length - temp->position, output_stream) 737 != input_text_length - temp->position 738 || fclose (output_stream) != 0) 739 fs_error (temp->filename); 740 741 /* Done with the buffer. */ 742 free (delayed_buf); 743 744 /* Update positions in tag table for nodes that are defined after 745 the line this delayed write is registered. */ 746 if (!html && !xml) 747 { 748 TAG_ENTRY *node; 749 for (node = tag_table; node; node = node->next_ent) 750 if (node->order > temp->node_order) 751 node->position += position_shift_amount; 752 } 753 754 /* Something similar for the line numbers in all of the defined 755 indices. */ 756 { 757 int i; 758 for (i = 0; i < defined_indices; i++) 759 if (name_index_alist[i]) 760 { 761 char *name = ((INDEX_ALIST *) name_index_alist[i])->name; 762 INDEX_ELT *index; 763 for (index = index_list (name); index; index = index->next) 764 if ((no_headers || STREQ (index->node, temp->node)) 765 && index->entry_number > temp->index_order) 766 index->output_line += line_number_shift_amount; 767 } 768 } 769 770 /* Shift remaining delayed positions 771 by the length of this write. */ 772 { 773 DELAYED_WRITE *future_write = temp->next; 774 while (future_write) 775 { 776 if (STREQ (temp->filename, future_write->filename)) 777 future_write->position += position_shift_amount; 778 future_write = future_write->next; 779 } 780 } 781 782 temp = temp->next; 783 } 784} 785