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