source: trunk/texinfo/makeinfo/files.c@ 2636

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

woops.

File size: 21.0 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#if defined (__EMX__)
372/* Convert DOS slashes to UNIX slashes. */
373static char *slashify (char *filename)
374{
375 char *s;
376 if (filename)
377 for (s = strchr (filename, '/'); s; s = strchr (s + 1, '/'))
378 *s = '/';
379 return filename;
380}
381#endif
382
383/* Return just the simple part of the filename; i.e. the
384 filename without the path information, or extensions.
385 This conses up a new string. */
386char *
387filename_part (char *filename)
388{
389#if defined (__EMX__)
390 char *basename = filename_non_directory (slashify (filename));
391#else
392 char *basename = filename_non_directory (filename);
393#endif
394
395#ifdef REMOVE_OUTPUT_EXTENSIONS
396 /* See if there is an extension to remove. If so, remove it. */
397 {
398 char *temp = strrchr (basename, '.');
399 if (temp)
400 *temp = 0;
401 }
402#endif /* REMOVE_OUTPUT_EXTENSIONS */
403 return basename;
404}
405
406/* Return the pathname part of filename. This can be NULL. */
407char *
408pathname_part (char *filename)
409{
410 char *result = NULL;
411 int i;
412#ifdef __EMX__
413 slashify (filename);
414#endif
415
416 filename = expand_filename (filename, "");
417
418 i = skip_directory_part (filename);
419 if (i)
420 {
421 result = xmalloc (1 + i);
422 strncpy (result, filename, i);
423 result[i] = 0;
424 }
425 free (filename);
426 return result;
427}
428
429/* Return the full path to FILENAME. */
430static char *
431full_pathname (char *filename)
432{
433 int initial_character;
434 char *result;
435#ifdef __EMX__
436 slashify (filename);
437#endif
438
439 /* No filename given? */
440 if (!filename || !*filename)
441 return xstrdup ("");
442
443 /* Already absolute? */
444 if (IS_ABSOLUTE (filename) ||
445 (*filename == '.' &&
446 (IS_SLASH (filename[1]) ||
447 (filename[1] == '.' && IS_SLASH (filename[2])))))
448 return xstrdup (filename);
449
450 initial_character = *filename;
451 if (initial_character != '~')
452 {
453 char *localdir = xmalloc (1025);
454#ifdef HAVE_GETCWD
455 if (!getcwd (localdir, 1024))
456#else
457 if (!getwd (localdir))
458#endif
459 {
460 fprintf (stderr, _("%s: getwd: %s, %s\n"),
461 progname, filename, localdir);
462 xexit (1);
463 }
464
465 strcat (localdir, "/");
466 strcat (localdir, filename);
467#ifdef __EMX__
468 slashify (localdir);
469#endif
470 result = xstrdup (localdir);
471 free (localdir);
472 }
473 else
474 { /* Does anybody know why WIN32 doesn't want to support $HOME?
475 If the reason is they don't have getpwnam, they should
476 only disable the else clause below. */
477#ifndef WIN32
478 if (IS_SLASH (filename[1]))
479 {
480 /* Return the concatenation of the environment variable HOME
481 and the rest of the string. */
482 char *temp_home;
483
484 temp_home = (char *) getenv ("HOME");
485 result = xmalloc (strlen (&filename[1])
486 + 1
487 + temp_home ? strlen (temp_home)
488 : 0);
489 *result = 0;
490
491 if (temp_home)
492 strcpy (result, temp_home);
493
494 strcat (result, &filename[1]);
495 }
496 else
497 {
498 struct passwd *user_entry;
499 int i, c;
500 char *username = xmalloc (257);
501
502 for (i = 1; (c = filename[i]); i++)
503 {
504 if (IS_SLASH (c))
505 break;
506 else
507 username[i - 1] = c;
508 }
509 if (c)
510 username[i - 1] = 0;
511
512 user_entry = getpwnam (username);
513
514 if (!user_entry)
515 return xstrdup (filename);
516
517 result = xmalloc (1 + strlen (user_entry->pw_dir)
518 + strlen (&filename[i]));
519 strcpy (result, user_entry->pw_dir);
520 strcat (result, &filename[i]);
521 }
522#endif /* not WIN32 */
523 }
524 return result;
525}
526
527/* Return the expansion of FILENAME. */
528char *
529expand_filename (char *filename, char *input_name)
530{
531 int i;
532#ifdef __EMX__
533 slashify (filename);
534 slashify (input_name);
535#endif
536
537 if (filename)
538 {
539 filename = full_pathname (filename);
540 if (IS_ABSOLUTE (filename)
541 || (*filename == '.' &&
542 (IS_SLASH (filename[1]) ||
543 (filename[1] == '.' && IS_SLASH (filename[2])))))
544 return filename;
545 }
546 else
547 {
548 filename = filename_non_directory (input_name);
549
550 if (!*filename)
551 {
552 free (filename);
553 filename = xstrdup ("noname.texi");
554 }
555
556 for (i = strlen (filename) - 1; i; i--)
557 if (filename[i] == '.')
558 break;
559
560 if (!i)
561 i = strlen (filename);
562
563 if (i + 6 > (strlen (filename)))
564 filename = xrealloc (filename, i + 6);
565 strcpy (filename + i, html ? ".html" : ".info");
566 return filename;
567 }
568
569 if (IS_ABSOLUTE (input_name))
570 {
571 /* Make it so that relative names work. */
572 char *result;
573
574 i = strlen (input_name) - 1;
575
576 result = xmalloc (1 + strlen (input_name) + strlen (filename));
577 strcpy (result, input_name);
578
579 while (!IS_SLASH (result[i]) && i)
580 i--;
581 if (IS_SLASH (result[i]))
582 i++;
583
584 strcpy (&result[i], filename);
585 free (filename);
586 return result;
587 }
588 return filename;
589}
590
591char *
592output_name_from_input_name (char *name)
593{
594 return expand_filename (NULL, name);
595}
596
597
598/* Modify the file name FNAME so that it fits the limitations of the
599 underlying filesystem. In particular, truncate the file name as it
600 would be truncated by the filesystem. We assume the result can
601 never be longer than the original, otherwise we couldn't be sure we
602 have enough space in the original string to modify it in place. */
603char *
604normalize_filename (char *fname)
605{
606 int maxlen;
607 char orig[PATH_MAX + 1];
608 int i;
609 char *lastdot, *p;
610
611#if defined (_PC_NAME_MAX) && !defined(__EMX__) /* bird: _PC_NAME_MAX => 14 on OS/2. FIXME!!! */
612 maxlen = pathconf (fname, _PC_NAME_MAX);
613 if (maxlen < 1)
614#endif
615 maxlen = PATH_MAX;
616
617 i = skip_directory_part (fname);
618 if (fname[i] == '\0')
619 return fname; /* only a directory name -- don't modify */
620 strcpy (orig, fname + i);
621
622 switch (maxlen)
623 {
624 case 12: /* MS-DOS 8+3 filesystem */
625 if (orig[0] == '.') /* leading dots are not allowed */
626 orig[0] = '_';
627 lastdot = strrchr (orig, '.');
628 if (!lastdot)
629 lastdot = orig + strlen (orig);
630 strncpy (fname + i, orig, lastdot - orig);
631 for (p = fname + i;
632 p < fname + i + (lastdot - orig) && p < fname + i + 8;
633 p++)
634 if (*p == '.')
635 *p = '_';
636 *p = '\0';
637 if (*lastdot == '.')
638 strncat (fname + i, lastdot, 4);
639 break;
640 case 14: /* old Unix systems with 14-char limitation */
641 strcpy (fname + i, orig);
642 if (strlen (fname + i) > 14)
643 fname[i + 14] = '\0';
644 break;
645 default:
646 strcpy (fname + i, orig);
647 if (strlen (fname) > maxlen - 1)
648 fname[maxlen - 1] = '\0';
649 break;
650 }
651
652 return fname;
653}
654
655
656/* Delayed writing functions. A few of the commands
657 needs to be handled at the end, namely @contents,
658 @shortcontents, @printindex and @listoffloats.
659 These functions take care of that. */
660static DELAYED_WRITE *delayed_writes = NULL;
661int handling_delayed_writes = 0;
662
663void
664register_delayed_write (char *delayed_command)
665{
666 DELAYED_WRITE *new;
667
668 if (!current_output_filename || !*current_output_filename)
669 {
670 /* Cannot register if we don't know what the output file is. */
671 warning (_("`%s' omitted before output filename"), delayed_command);
672 return;
673 }
674
675 if (STREQ (current_output_filename, "-"))
676 {
677 /* Do not register a new write if the output file is not seekable.
678 Let the user know about it first, though. */
679 warning (_("`%s' omitted since writing to stdout"), delayed_command);
680 return;
681 }
682
683 /* Don't complain if the user is writing /dev/null, since surely they
684 don't care, but don't register the delayed write, either. */
685 if (FILENAME_CMP (current_output_filename, NULL_DEVICE) == 0
686 || FILENAME_CMP (current_output_filename, ALSO_NULL_DEVICE) == 0)
687 return;
688
689 /* We need the HTML header in the output,
690 to get a proper output_position. */
691 if (!executing_string && html)
692 html_output_head ();
693 /* Get output_position updated. */
694 flush_output ();
695
696 new = xmalloc (sizeof (DELAYED_WRITE));
697 new->command = xstrdup (delayed_command);
698 new->filename = xstrdup (current_output_filename);
699 new->input_filename = xstrdup (input_filename);
700 new->position = output_position;
701 new->calling_line = line_number;
702 new->node = current_node ? xstrdup (current_node): "";
703
704 new->node_order = node_order;
705 new->index_order = index_counter;
706
707 new->next = delayed_writes;
708 delayed_writes = new;
709}
710
711void
712handle_delayed_writes (void)
713{
714 DELAYED_WRITE *temp = (DELAYED_WRITE *) reverse_list
715 ((GENERIC_LIST *) delayed_writes);
716 int position_shift_amount, line_number_shift_amount;
717 char *delayed_buf;
718
719 handling_delayed_writes = 1;
720
721 while (temp)
722 {
723 delayed_buf = find_and_load (temp->filename, 0);
724
725 if (output_paragraph_offset > 0)
726 {
727 error (_("Output buffer not empty."));
728 return;
729 }
730
731 if (!delayed_buf)
732 {
733 fs_error (temp->filename);
734 return;
735 }
736
737 output_stream = fopen (temp->filename, "w");
738 if (!output_stream)
739 {
740 fs_error (temp->filename);
741 return;
742 }
743
744 if (fwrite (delayed_buf, 1, temp->position, output_stream) != temp->position)
745 {
746 fs_error (temp->filename);
747 return;
748 }
749
750 {
751 int output_position_at_start = output_position;
752 int line_number_at_start = output_line_number;
753
754 /* In order to make warnings and errors
755 refer to the correct line number. */
756 input_filename = temp->input_filename;
757 line_number = temp->calling_line;
758
759 execute_string ("%s", temp->command);
760 flush_output ();
761
762 /* Since the output file is modified, following delayed writes
763 need to be updated by this amount. */
764 position_shift_amount = output_position - output_position_at_start;
765 line_number_shift_amount = output_line_number - line_number_at_start;
766 }
767
768 if (fwrite (delayed_buf + temp->position, 1,
769 input_text_length - temp->position, output_stream)
770 != input_text_length - temp->position
771 || fclose (output_stream) != 0)
772 fs_error (temp->filename);
773
774 /* Done with the buffer. */
775 free (delayed_buf);
776
777 /* Update positions in tag table for nodes that are defined after
778 the line this delayed write is registered. */
779 if (!html && !xml)
780 {
781 TAG_ENTRY *node;
782 for (node = tag_table; node; node = node->next_ent)
783 if (node->order > temp->node_order)
784 node->position += position_shift_amount;
785 }
786
787 /* Something similar for the line numbers in all of the defined
788 indices. */
789 {
790 int i;
791 for (i = 0; i < defined_indices; i++)
792 if (name_index_alist[i])
793 {
794 char *name = ((INDEX_ALIST *) name_index_alist[i])->name;
795 INDEX_ELT *index;
796 for (index = index_list (name); index; index = index->next)
797 if ((no_headers || STREQ (index->node, temp->node))
798 && index->entry_number > temp->index_order)
799 index->output_line += line_number_shift_amount;
800 }
801 }
802
803 /* Shift remaining delayed positions
804 by the length of this write. */
805 {
806 DELAYED_WRITE *future_write = temp->next;
807 while (future_write)
808 {
809 if (STREQ (temp->filename, future_write->filename))
810 future_write->position += position_shift_amount;
811 future_write = future_write->next;
812 }
813 }
814
815 temp = temp->next;
816 }
817}
Note: See TracBrowser for help on using the repository browser.