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