1/*	$NetBSD: files.c,v 1.8 2008/09/02 08:00:24 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
31FSTACK *filestack = NULL;
32
33static int node_filename_stack_index = 0;
34static int node_filename_stack_size = 0;
35static 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. */
42static char *
43extract_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. */
88char *
89get_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.  */
134void
135prepend_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
156void
157append_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.  */
169void
170pop_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
192char *
193find_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)
234error_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.  */
266static void
267push_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
277static void
278pop_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. */
284void
285pushfile (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. */
300void
301popfile (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. */
334void
335flush_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.  */
349static int
350skip_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
364static char *
365filename_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. */
373char *
374filename_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. */
390char *
391pathname_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. */
410static char *
411full_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. */
502char *
503expand_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
561char *
562output_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.  */
573char *
574normalize_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.  */
629static DELAYED_WRITE *delayed_writes = NULL;
630int handling_delayed_writes = 0;
631
632void
633register_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      || FILENAME_CMP (current_output_filename, ALSO_NULL_DEVICE) == 0)
656    return;
657
658  /* We need the HTML header in the output,
659     to get a proper output_position.  */
660  if (!executing_string && html)
661    html_output_head ();
662  /* Get output_position updated.  */
663  flush_output ();
664
665  new = xmalloc (sizeof (DELAYED_WRITE));
666  new->command = xstrdup (delayed_command);
667  new->filename = xstrdup (current_output_filename);
668  new->input_filename = xstrdup (input_filename);
669  new->position = output_position;
670  new->calling_line = line_number;
671  new->node = current_node ? xstrdup (current_node): "";
672
673  new->node_order = node_order;
674  new->index_order = index_counter;
675
676  new->next = delayed_writes;
677  delayed_writes = new;
678}
679
680void
681handle_delayed_writes (void)
682{
683  DELAYED_WRITE *temp = (DELAYED_WRITE *) reverse_list
684    ((GENERIC_LIST *) delayed_writes);
685  int position_shift_amount, line_number_shift_amount;
686  char *delayed_buf;
687
688  handling_delayed_writes = 1;
689
690  while (temp)
691    {
692      delayed_buf = find_and_load (temp->filename, 0);
693
694      if (output_paragraph_offset > 0)
695        {
696          error (_("Output buffer not empty."));
697          return;
698        }
699
700      if (!delayed_buf)
701        {
702          fs_error (temp->filename);
703          return;
704        }
705
706      output_stream = fopen (temp->filename, "w");
707      if (!output_stream)
708        {
709          fs_error (temp->filename);
710          return;
711        }
712
713      if (fwrite (delayed_buf, 1, temp->position, output_stream) != temp->position)
714        {
715          fs_error (temp->filename);
716          return;
717        }
718
719      {
720        int output_position_at_start = output_position;
721        int line_number_at_start = output_line_number;
722
723        /* In order to make warnings and errors
724           refer to the correct line number.  */
725        input_filename = temp->input_filename;
726        line_number = temp->calling_line;
727
728        execute_string ("%s", temp->command);
729        flush_output ();
730
731        /* Since the output file is modified, following delayed writes
732           need to be updated by this amount.  */
733        position_shift_amount = output_position - output_position_at_start;
734        line_number_shift_amount = output_line_number - line_number_at_start;
735      }
736
737      if (fwrite (delayed_buf + temp->position, 1,
738            input_text_length - temp->position, output_stream)
739          != input_text_length - temp->position
740          || fclose (output_stream) != 0)
741        fs_error (temp->filename);
742
743      /* Done with the buffer.  */
744      free (delayed_buf);
745
746      /* Update positions in tag table for nodes that are defined after
747         the line this delayed write is registered.  */
748      if (!html && !xml)
749        {
750          TAG_ENTRY *node;
751          for (node = tag_table; node; node = node->next_ent)
752            if (node->order > temp->node_order)
753              node->position += position_shift_amount;
754        }
755
756      /* Something similar for the line numbers in all of the defined
757         indices.  */
758      {
759        int i;
760        for (i = 0; i < defined_indices; i++)
761          if (name_index_alist[i])
762            {
763              char *name = ((INDEX_ALIST *) name_index_alist[i])->name;
764              INDEX_ELT *index;
765              for (index = index_list (name); index; index = index->next)
766                if ((no_headers || STREQ (index->node, temp->node))
767                    && index->entry_number > temp->index_order)
768                  index->output_line += line_number_shift_amount;
769            }
770      }
771
772      /* Shift remaining delayed positions
773         by the length of this write.  */
774      {
775        DELAYED_WRITE *future_write = temp->next;
776        while (future_write)
777          {
778            if (STREQ (temp->filename, future_write->filename))
779              future_write->position += position_shift_amount;
780            future_write = future_write->next;
781          }
782      }
783
784      temp = temp->next;
785    }
786}
787