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 __RCSID("$MirOS: src/gnu/usr.bin/texinfo/makeinfo/files.c,v 1.2 2005/03/13 15:19:45 tg Exp $");
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)
462                                     : 0);
463           *result = 0;
464 
465           if (temp_home)
466             strcpy (result, temp_home);
467 
468           strcat (result, &filename[1]);
469         }
470       else
471         {
472           struct passwd *user_entry;
473           int i, c;
474           char *username = xmalloc (257);
475 
476           for (i = 1; (c = filename[i]); i++)
477             {
478               if (IS_SLASH (c))
479                 break;
480               else
481                 username[i - 1] = c;
482             }
483           if (c)
484             username[i - 1] = 0;
485 
486           user_entry = getpwnam (username);
487 
488           if (!user_entry)
489             return xstrdup (filename);
490 
491           result = xmalloc (1 + strlen (user_entry->pw_dir)
492                                     + strlen (&filename[i]));
493           strcpy (result, user_entry->pw_dir);
494           strcat (result, &filename[i]);
495         }
496 #endif /* not WIN32 */
497     }
498   return result;
499 }
500 
501 /* Return the expansion of FILENAME. */
502 char *
expand_filename(char * filename,char * input_name)503 expand_filename (char *filename, char *input_name)
504 {
505   int i;
506 
507   if (filename)
508     {
509       filename = full_pathname (filename);
510       if (IS_ABSOLUTE (filename)
511 	  || (*filename == '.' &&
512 	      (IS_SLASH (filename[1]) ||
513 	       (filename[1] == '.' && IS_SLASH (filename[2])))))
514 	return filename;
515     }
516   else
517     {
518       filename = filename_non_directory (input_name);
519 
520       if (!*filename)
521         {
522           free (filename);
523           filename = xstrdup ("noname.texi");
524         }
525 
526       for (i = strlen (filename) - 1; i; i--)
527         if (filename[i] == '.')
528           break;
529 
530       if (!i)
531         i = strlen (filename);
532 
533       if (i + 6 > (strlen (filename)))
534         filename = xrealloc (filename, i + 6);
535       strcpy (filename + i, html ? ".html" : ".info");
536       return filename;
537     }
538 
539   if (IS_ABSOLUTE (input_name))
540     {
541       /* Make it so that relative names work. */
542       char *result;
543 
544       i = strlen (input_name) - 1;
545 
546       result = xmalloc (1 + strlen (input_name) + strlen (filename));
547       strcpy (result, input_name);
548 
549       while (!IS_SLASH (result[i]) && i)
550         i--;
551       if (IS_SLASH (result[i]))
552         i++;
553 
554       strcpy (&result[i], filename);
555       free (filename);
556       return result;
557     }
558   return filename;
559 }
560 
561 char *
output_name_from_input_name(char * name)562 output_name_from_input_name (char *name)
563 {
564   return expand_filename (NULL, name);
565 }
566 
567 
568 /* Modify the file name FNAME so that it fits the limitations of the
569    underlying filesystem.  In particular, truncate the file name as it
570    would be truncated by the filesystem.  We assume the result can
571    never be longer than the original, otherwise we couldn't be sure we
572    have enough space in the original string to modify it in place.  */
573 char *
normalize_filename(char * fname)574 normalize_filename (char *fname)
575 {
576   int maxlen;
577   char orig[PATH_MAX + 1];
578   int i;
579   char *lastdot, *p;
580 
581 #ifdef _PC_NAME_MAX
582   maxlen = pathconf (fname, _PC_NAME_MAX);
583   if (maxlen < 1)
584 #endif
585     maxlen = PATH_MAX;
586 
587   i = skip_directory_part (fname);
588   if (fname[i] == '\0')
589     return fname;	/* only a directory name -- don't modify */
590   strcpy (orig, fname + i);
591 
592   switch (maxlen)
593     {
594       case 12:	/* MS-DOS 8+3 filesystem */
595 	if (orig[0] == '.')	/* leading dots are not allowed */
596 	  orig[0] = '_';
597 	lastdot = strrchr (orig, '.');
598 	if (!lastdot)
599 	  lastdot = orig + strlen (orig);
600 	strncpy (fname + i, orig, lastdot - orig);
601 	for (p = fname + i;
602 	     p < fname + i + (lastdot - orig) && p < fname + i + 8;
603 	     p++)
604 	  if (*p == '.')
605 	    *p = '_';
606 	*p = '\0';
607 	if (*lastdot == '.')
608 	  strncat (fname + i, lastdot, 4);
609 	break;
610       case 14:	/* old Unix systems with 14-char limitation */
611 	strcpy (fname + i, orig);
612 	if (strlen (fname + i) > 14)
613 	  fname[i + 14] = '\0';
614 	break;
615       default:
616 	strcpy (fname + i, orig);
617 	if (strlen (fname) > maxlen - 1)
618 	  fname[maxlen - 1] = '\0';
619 	break;
620     }
621 
622   return fname;
623 }
624 
625 /* Delayed writing functions.  A few of the commands
626    needs to be handled at the end, namely @contents,
627    @shortcontents, @printindex and @listoffloats.
628    These functions take care of that.  */
629 static DELAYED_WRITE *delayed_writes = NULL;
630 int handling_delayed_writes = 0;
631 
632 void
register_delayed_write(char * delayed_command)633 register_delayed_write (char *delayed_command)
634 {
635   DELAYED_WRITE *new;
636 
637   if (!current_output_filename || !*current_output_filename)
638     {
639       /* Cannot register if we don't know what the output file is.  */
640       warning (_("`%s' omitted before output filename"), delayed_command);
641       return;
642     }
643 
644   if (STREQ (current_output_filename, "-"))
645     {
646       /* Do not register a new write if the output file is not seekable.
647          Let the user know about it first, though.  */
648       warning (_("`%s' omitted since writing to stdout"), delayed_command);
649       return;
650     }
651 
652   /* Don't complain if the user is writing /dev/null, since surely they
653      don't care, but don't register the delayed write, either.  */
654   if (FILENAME_CMP (current_output_filename, 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