xref: /openbsd/gnu/usr.bin/texinfo/makeinfo/files.c (revision 82ba5b19)
1 /* files.c -- file-related functions for makeinfo.
2    $Id: files.c,v 1.2 2024/08/16 22:53:32 guenther 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 
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 *
extract_colon_unit(char * string,int * index)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 *
get_file_info_in_path(char * filename,char * path,struct stat * finfo)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
prepend_to_include_path(char * path)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
append_to_include_path(char * path)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
pop_path_from_include_path(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 
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 *
find_and_load(char * filename,int use_path)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 
263 /* Pushing and popping files.  */
264 static void
push_node_filename(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
pop_node_filename(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
pushfile(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
popfile(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
flush_file_stack(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
skip_directory_part(char * filename)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 *
filename_non_directory(char * name)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 *
filename_part(char * filename)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 *
pathname_part(char * filename)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 *
full_pathname(char * filename)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 *
expand_filename(char * filename,char * input_name)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 *
output_name_from_input_name(char * name)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 *
normalize_filename(char * fname)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 
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
register_delayed_write(char * delayed_command)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
handle_delayed_writes(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 }
785