1 /* files.c -- file-related functions for makeinfo.
2 $Id: files.c,v 1.2 2024/08/16 22:53:32 guenther 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
29 FSTACK *filestack = NULL;
30
31 static int node_filename_stack_index = 0;
32 static int node_filename_stack_size = 0;
33 static 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. */
40 static char *
extract_colon_unit(char * string,int * index)41 extract_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. */
86 char *
get_file_info_in_path(char * filename,char * path,struct stat * finfo)87 get_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. */
132 void
prepend_to_include_path(char * path)133 prepend_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
154 void
append_to_include_path(char * path)155 append_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. */
167 void
pop_path_from_include_path(void)168 pop_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
190 char *
find_and_load(char * filename,int use_path)191 find_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)
232 error_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. */
264 static void
push_node_filename(void)265 push_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
275 static void
pop_node_filename(void)276 pop_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. */
282 void
pushfile(void)283 pushfile (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. */
298 void
popfile(void)299 popfile (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. */
332 void
flush_file_stack(void)333 flush_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. */
347 static int
skip_directory_part(char * filename)348 skip_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
362 static char *
filename_non_directory(char * name)363 filename_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. */
371 char *
filename_part(char * filename)372 filename_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. */
388 char *
pathname_part(char * filename)389 pathname_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. */
408 static char *
full_pathname(char * filename)409 full_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. */
500 char *
expand_filename(char * filename,char * input_name)501 expand_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
559 char *
output_name_from_input_name(char * name)560 output_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. */
571 char *
normalize_filename(char * fname)572 normalize_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. */
627 static DELAYED_WRITE *delayed_writes = NULL;
628 int handling_delayed_writes = 0;
629
630 void
register_delayed_write(char * delayed_command)631 register_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
678 void
handle_delayed_writes(void)679 handle_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