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