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

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

applied OS/2 patches (manually).

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