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. */ 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. */ 42 static char * 43 extract_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. */ 88 char * 89 get_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. */ 134 void 135 prepend_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 156 void 157 append_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. */ 169 void 170 pop_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 192 char * 193 find_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) 234 error_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. */ 266 static void 267 push_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 277 static void 278 pop_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. */ 284 void 285 pushfile (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. */ 300 void 301 popfile (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. */ 334 void 335 flush_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. */ 349 static int 350 skip_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 364 static char * 365 filename_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. */ 373 char * 374 filename_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. */ 390 char * 391 pathname_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. */ 410 static char * 411 full_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) : 0)); 462 *result = 0; 463 464 if (temp_home) 465 strcpy (result, temp_home); 466 467 strcat (result, &filename[1]); 468 } 469 else 470 { 471 struct passwd *user_entry; 472 int i, c; 473 char *username = xmalloc (257); 474 475 for (i = 1; (c = filename[i]); i++) 476 { 477 if (IS_SLASH (c)) 478 break; 479 else 480 username[i - 1] = c; 481 } 482 if (c) 483 username[i - 1] = 0; 484 485 user_entry = getpwnam (username); 486 487 if (!user_entry) 488 return xstrdup (filename); 489 490 result = xmalloc (1 + strlen (user_entry->pw_dir) 491 + strlen (&filename[i])); 492 strcpy (result, user_entry->pw_dir); 493 strcat (result, &filename[i]); 494 } 495 #endif /* not WIN32 */ 496 } 497 return result; 498 } 499 500 /* Return the expansion of FILENAME. */ 501 char * 502 expand_filename (char *filename, char *input_name) 503 { 504 int i; 505 506 if (filename) 507 { 508 filename = full_pathname (filename); 509 if (IS_ABSOLUTE (filename) 510 || (*filename == '.' && 511 (IS_SLASH (filename[1]) || 512 (filename[1] == '.' && IS_SLASH (filename[2]))))) 513 return filename; 514 } 515 else 516 { 517 filename = filename_non_directory (input_name); 518 519 if (!*filename) 520 { 521 free (filename); 522 filename = xstrdup ("noname.texi"); 523 } 524 525 for (i = strlen (filename) - 1; i; i--) 526 if (filename[i] == '.') 527 break; 528 529 if (!i) 530 i = strlen (filename); 531 532 if (i + 6 > (strlen (filename))) 533 filename = xrealloc (filename, i + 6); 534 strcpy (filename + i, html ? ".html" : ".info"); 535 return filename; 536 } 537 538 if (IS_ABSOLUTE (input_name)) 539 { 540 /* Make it so that relative names work. */ 541 char *result; 542 543 i = strlen (input_name) - 1; 544 545 result = xmalloc (1 + strlen (input_name) + strlen (filename)); 546 strcpy (result, input_name); 547 548 while (!IS_SLASH (result[i]) && i) 549 i--; 550 if (IS_SLASH (result[i])) 551 i++; 552 553 strcpy (&result[i], filename); 554 free (filename); 555 return result; 556 } 557 return filename; 558 } 559 560 char * 561 output_name_from_input_name (char *name) 562 { 563 return expand_filename (NULL, name); 564 } 565 566 567 /* Modify the file name FNAME so that it fits the limitations of the 568 underlying filesystem. In particular, truncate the file name as it 569 would be truncated by the filesystem. We assume the result can 570 never be longer than the original, otherwise we couldn't be sure we 571 have enough space in the original string to modify it in place. */ 572 char * 573 normalize_filename (char *fname) 574 { 575 int maxlen; 576 char orig[PATH_MAX + 1]; 577 int i; 578 char *lastdot, *p; 579 580 #ifdef _PC_NAME_MAX 581 maxlen = pathconf (fname, _PC_NAME_MAX); 582 if (maxlen < 1) 583 #endif 584 maxlen = PATH_MAX; 585 586 i = skip_directory_part (fname); 587 if (fname[i] == '\0') 588 return fname; /* only a directory name -- don't modify */ 589 strcpy (orig, fname + i); 590 591 switch (maxlen) 592 { 593 case 12: /* MS-DOS 8+3 filesystem */ 594 if (orig[0] == '.') /* leading dots are not allowed */ 595 orig[0] = '_'; 596 lastdot = strrchr (orig, '.'); 597 if (!lastdot) 598 lastdot = orig + strlen (orig); 599 strncpy (fname + i, orig, lastdot - orig); 600 for (p = fname + i; 601 p < fname + i + (lastdot - orig) && p < fname + i + 8; 602 p++) 603 if (*p == '.') 604 *p = '_'; 605 *p = '\0'; 606 if (*lastdot == '.') 607 strncat (fname + i, lastdot, 4); 608 break; 609 case 14: /* old Unix systems with 14-char limitation */ 610 strcpy (fname + i, orig); 611 if (strlen (fname + i) > 14) 612 fname[i + 14] = '\0'; 613 break; 614 default: 615 strcpy (fname + i, orig); 616 if (strlen (fname) > maxlen - 1) 617 fname[maxlen - 1] = '\0'; 618 break; 619 } 620 621 return fname; 622 } 623 624 /* Delayed writing functions. A few of the commands 625 needs to be handled at the end, namely @contents, 626 @shortcontents, @printindex and @listoffloats. 627 These functions take care of that. */ 628 static DELAYED_WRITE *delayed_writes = NULL; 629 int handling_delayed_writes = 0; 630 631 void 632 register_delayed_write (char *delayed_command) 633 { 634 DELAYED_WRITE *new; 635 636 if (!current_output_filename || !*current_output_filename) 637 { 638 /* Cannot register if we don't know what the output file is. */ 639 warning (_("`%s' omitted before output filename"), delayed_command); 640 return; 641 } 642 643 if (STREQ (current_output_filename, "-")) 644 { 645 /* Do not register a new write if the output file is not seekable. 646 Let the user know about it first, though. */ 647 warning (_("`%s' omitted since writing to stdout"), delayed_command); 648 return; 649 } 650 651 /* Don't complain if the user is writing /dev/null, since surely they 652 don't care, but don't register the delayed write, either. */ 653 if (FILENAME_CMP (current_output_filename, NULL_DEVICE) == 0 654 || FILENAME_CMP (current_output_filename, ALSO_NULL_DEVICE) == 0) 655 return; 656 657 /* We need the HTML header in the output, 658 to get a proper output_position. */ 659 if (!executing_string && html) 660 html_output_head (); 661 /* Get output_position updated. */ 662 flush_output (); 663 664 new = xmalloc (sizeof (DELAYED_WRITE)); 665 new->command = xstrdup (delayed_command); 666 new->filename = xstrdup (current_output_filename); 667 new->input_filename = xstrdup (input_filename); 668 new->position = output_position; 669 new->calling_line = line_number; 670 new->node = current_node ? xstrdup (current_node): ""; 671 672 new->node_order = node_order; 673 new->index_order = index_counter; 674 675 new->next = delayed_writes; 676 delayed_writes = new; 677 } 678 679 void 680 handle_delayed_writes (void) 681 { 682 DELAYED_WRITE *temp = (DELAYED_WRITE *) reverse_list 683 ((GENERIC_LIST *) delayed_writes); 684 int position_shift_amount, line_number_shift_amount; 685 char *delayed_buf; 686 687 handling_delayed_writes = 1; 688 689 while (temp) 690 { 691 delayed_buf = find_and_load (temp->filename, 0); 692 693 if (output_paragraph_offset > 0) 694 { 695 error (_("Output buffer not empty.")); 696 return; 697 } 698 699 if (!delayed_buf) 700 { 701 fs_error (temp->filename); 702 return; 703 } 704 705 output_stream = fopen (temp->filename, "w"); 706 if (!output_stream) 707 { 708 fs_error (temp->filename); 709 return; 710 } 711 712 if (fwrite (delayed_buf, 1, temp->position, output_stream) != temp->position) 713 { 714 fs_error (temp->filename); 715 return; 716 } 717 718 { 719 int output_position_at_start = output_position; 720 int line_number_at_start = output_line_number; 721 722 /* In order to make warnings and errors 723 refer to the correct line number. */ 724 input_filename = temp->input_filename; 725 line_number = temp->calling_line; 726 727 execute_string ("%s", temp->command); 728 flush_output (); 729 730 /* Since the output file is modified, following delayed writes 731 need to be updated by this amount. */ 732 position_shift_amount = output_position - output_position_at_start; 733 line_number_shift_amount = output_line_number - line_number_at_start; 734 } 735 736 if (fwrite (delayed_buf + temp->position, 1, 737 input_text_length - temp->position, output_stream) 738 != input_text_length - temp->position 739 || fclose (output_stream) != 0) 740 fs_error (temp->filename); 741 742 /* Done with the buffer. */ 743 free (delayed_buf); 744 745 /* Update positions in tag table for nodes that are defined after 746 the line this delayed write is registered. */ 747 if (!html && !xml) 748 { 749 TAG_ENTRY *node; 750 for (node = tag_table; node; node = node->next_ent) 751 if (node->order > temp->node_order) 752 node->position += position_shift_amount; 753 } 754 755 /* Something similar for the line numbers in all of the defined 756 indices. */ 757 { 758 int i; 759 for (i = 0; i < defined_indices; i++) 760 if (name_index_alist[i]) 761 { 762 char *name = ((INDEX_ALIST *) name_index_alist[i])->name; 763 INDEX_ELT *index; 764 for (index = index_list (name); index; index = index->next) 765 if ((no_headers || STREQ (index->node, temp->node)) 766 && index->entry_number > temp->index_order) 767 index->output_line += line_number_shift_amount; 768 } 769 } 770 771 /* Shift remaining delayed positions 772 by the length of this write. */ 773 { 774 DELAYED_WRITE *future_write = temp->next; 775 while (future_write) 776 { 777 if (STREQ (temp->filename, future_write->filename)) 778 future_write->position += position_shift_amount; 779 future_write = future_write->next; 780 } 781 } 782 783 temp = temp->next; 784 } 785 } 786