Add texinfo 4.8, appropriately stripped down.
[dragonfly.git] / contrib / texinfo-4 / makeinfo / files.c
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
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 \f
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 *
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 *
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
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
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
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 \f
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 *
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 \f
263 /* Pushing and popping files.  */
264 static 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
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
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
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
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
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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 \f
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
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
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 }