source: vendor/texinfo/4.8/makeinfo/files.c

Last change on this file was 2617, checked in by bird, 19 years ago

GNU Texinfo 4.8

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