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
29FSTACK *filestack = NULL;
30
31static int node_filename_stack_index = 0;
32static int node_filename_stack_size = 0;
33static 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. */
40static char *
41extract_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. */
86char *
87get_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.  */
132void
133prepend_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
154void
155append_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.  */
167void
168pop_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
190char *
191find_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)
232error_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.  */
264static void
265push_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
275static void
276pop_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. */
282void
283pushfile (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. */
298void
299popfile (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. */
332void
333flush_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.  */
347static int
348skip_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
362static char *
363filename_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. */
371char *
372filename_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. */
388char *
389pathname_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. */
408static char *
409full_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. */
500char *
501expand_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
559char *
560output_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.  */
571char *
572normalize_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.  */
627static DELAYED_WRITE *delayed_writes = NULL;
628int handling_delayed_writes = 0;
629
630void
631register_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
678void
679handle_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