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