Merge from vendor branch GCC:
[dragonfly.git] / contrib / texinfo / makeinfo / files.c
1 /* files.c -- file-related functions for makeinfo.
2    $Id: files.c,v 1.10 2002/01/16 15:52:45 karl Exp $
3
4    Copyright (C) 1998, 99, 2000, 01, 02 Free Software Foundation, Inc.
5
6    This program is free software; you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation; either version 2, or (at your option)
9    any later version.
10
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15
16    You should have received a copy of the GNU General Public License
17    along with this program; if not, write to the Free Software Foundation,
18    Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
19
20 #include "system.h"
21 #include "files.h"
22 #include "macro.h"
23 #include "makeinfo.h"
24
25 FSTACK *filestack = NULL;
26
27 static int node_filename_stack_index = 0;
28 static int node_filename_stack_size = 0;
29 static char **node_filename_stack = NULL;
30
31 \f
32 /* Looking for include files.  */
33
34 /* Given a string containing units of information separated by colons,
35    return the next one pointed to by INDEX, or NULL if there are no more.
36    Advance INDEX to the character after the colon. */
37 static char *
38 extract_colon_unit (string, index)
39      char *string;
40      int *index;
41 {
42   int start;
43   int path_sep_char = PATH_SEP[0];
44   int i = *index;
45
46   if (!string || (i >= strlen (string)))
47     return NULL;
48
49   /* Each call to this routine leaves the index pointing at a colon if
50      there is more to the path.  If i > 0, then increment past the
51      `:'.  If i == 0, then the path has a leading colon.  Trailing colons
52      are handled OK by the `else' part of the if statement; an empty
53      string is returned in that case. */
54   if (i && string[i] == path_sep_char)
55     i++;
56
57   start = i;
58   while (string[i] && string[i] != path_sep_char) i++;
59   *index = i;
60
61   if (i == start)
62     {
63       if (string[i])
64         (*index)++;
65
66       /* Return "" in the case of a trailing `:'. */
67       return xstrdup ("");
68     }
69   else
70     {
71       char *value;
72
73       value = xmalloc (1 + (i - start));
74       memcpy (value, &string[start], (i - start));
75       value [i - start] = 0;
76
77       return value;
78     }
79 }
80
81 /* Return the full pathname for FILENAME by searching along PATH.
82    When found, return the stat () info for FILENAME in FINFO.
83    If PATH is NULL, only the current directory is searched.
84    If the file could not be found, return a NULL pointer. */
85 static char *
86 get_file_info_in_path (filename, path, finfo)
87      char *filename, *path;
88      struct stat *finfo;
89 {
90   char *dir;
91   int result, index = 0;
92
93   if (path == NULL)
94     path = ".";
95
96   /* Handle absolute pathnames.  */
97   if (IS_ABSOLUTE (filename)
98       || (*filename == '.'
99           && (IS_SLASH (filename[1])
100               || (filename[1] == '.' && IS_SLASH (filename[2])))))
101     {
102       if (stat (filename, finfo) == 0)
103         return xstrdup (filename);
104       else
105         return NULL;
106     }
107
108   while ((dir = extract_colon_unit (path, &index)))
109     {
110       char *fullpath;
111
112       if (!*dir)
113         {
114           free (dir);
115           dir = xstrdup (".");
116         }
117
118       fullpath = xmalloc (2 + strlen (dir) + strlen (filename));
119       sprintf (fullpath, "%s/%s", dir, filename);
120       free (dir);
121
122       result = stat (fullpath, finfo);
123
124       if (result == 0)
125         return fullpath;
126       else
127         free (fullpath);
128     }
129   return NULL;
130 }
131 \f
132 /* Find and load the file named FILENAME.  Return a pointer to
133    the loaded file, or NULL if it can't be loaded. */
134 char *
135 find_and_load (filename)
136      char *filename;
137 {
138   struct stat fileinfo;
139   long file_size;
140   int file = -1, count = 0;
141   char *fullpath, *result;
142   int n, bytes_to_read;
143
144   result = fullpath = NULL;
145
146   fullpath = get_file_info_in_path (filename, include_files_path, &fileinfo);
147
148   if (!fullpath)
149     goto error_exit;
150
151   filename = fullpath;
152   file_size = (long) fileinfo.st_size;
153
154   file = open (filename, O_RDONLY);
155   if (file < 0)
156     goto error_exit;
157
158   /* Load the file, with enough room for a newline and a null. */
159   result = xmalloc (file_size + 2);
160
161   /* VMS stat lies about the st_size value.  The actual number of
162      readable bytes is always less than this value.  The arcane
163      mysteries of VMS/RMS are too much to probe, so this hack
164     suffices to make things work.  It's also needed on Cygwin.  And so
165     we might as well use it everywhere.  */
166   bytes_to_read = file_size;
167   while ((n = read (file, result + count, bytes_to_read)) > 0)
168     {
169       count += n;
170       bytes_to_read -= n;
171     }
172   if (0 < count && count < file_size)
173     result = xrealloc (result, count + 2); /* why waste the slack? */
174   else if (n == -1)
175 error_exit:
176     {
177       if (result)
178         free (result);
179
180       if (fullpath)
181         free (fullpath);
182
183       if (file != -1)
184         close (file);
185
186       return NULL;
187     }
188   close (file);
189
190   /* Set the globals to the new file. */
191   input_text = result;
192   input_text_length = count;
193   input_filename = fullpath;
194   node_filename = xstrdup (fullpath);
195   input_text_offset = 0;
196   line_number = 1;
197   /* Not strictly necessary.  This magic prevents read_token () from doing
198      extra unnecessary work each time it is called (that is a lot of times).
199      INPUT_TEXT_LENGTH is one past the actual end of the text. */
200   input_text[input_text_length] = '\n';
201   /* This, on the other hand, is always necessary.  */
202   input_text[input_text_length+1] = 0;
203   return result;
204 }
205 \f
206 /* Pushing and popping files.  */
207 void
208 push_node_filename ()
209 {
210   if (node_filename_stack_index + 1 > node_filename_stack_size)
211     node_filename_stack = xrealloc
212     (node_filename_stack, (node_filename_stack_size += 10) * sizeof (char *));
213
214   node_filename_stack[node_filename_stack_index] = node_filename;
215   node_filename_stack_index++;
216 }
217
218 void
219 pop_node_filename ()
220 {
221   node_filename = node_filename_stack[--node_filename_stack_index];
222 }
223
224 /* Save the state of the current input file. */
225 void
226 pushfile ()
227 {
228   FSTACK *newstack = xmalloc (sizeof (FSTACK));
229   newstack->filename = input_filename;
230   newstack->text = input_text;
231   newstack->size = input_text_length;
232   newstack->offset = input_text_offset;
233   newstack->line_number = line_number;
234   newstack->next = filestack;
235
236   filestack = newstack;
237   push_node_filename ();
238 }
239
240 /* Make the current file globals be what is on top of the file stack. */
241 void
242 popfile ()
243 {
244   FSTACK *tos = filestack;
245
246   if (!tos)
247     abort ();                   /* My fault.  I wonder what I did? */
248
249   if (macro_expansion_output_stream)
250     {
251       maybe_write_itext (input_text, input_text_offset);
252       forget_itext (input_text);
253     }
254
255   /* Pop the stack. */
256   filestack = filestack->next;
257
258   /* Make sure that commands with braces have been satisfied. */
259   if (!executing_string && !me_executing_string)
260     discard_braces ();
261
262   /* Get the top of the stack into the globals. */
263   input_filename = tos->filename;
264   input_text = tos->text;
265   input_text_length = tos->size;
266   input_text_offset = tos->offset;
267   line_number = tos->line_number;
268   free (tos);
269
270   /* Go back to the (now) current node. */
271   pop_node_filename ();
272 }
273
274 /* Flush all open files on the file stack. */
275 void
276 flush_file_stack ()
277 {
278   while (filestack)
279     {
280       char *fname = input_filename;
281       char *text = input_text;
282       popfile ();
283       free (fname);
284       free (text);
285     }
286 }
287
288 /* Return the index of the first character in the filename
289    which is past all the leading directory characters.  */
290 static int
291 skip_directory_part (filename)
292      char *filename;
293 {
294   int i = strlen (filename) - 1;
295
296   while (i && !IS_SLASH (filename[i]))
297     i--;
298   if (IS_SLASH (filename[i]))
299     i++;
300   else if (filename[i] && HAVE_DRIVE (filename))
301     i = 2;
302
303   return i;
304 }
305
306 char *
307 filename_non_directory (name)
308      char *name;
309 {
310   return xstrdup (name + skip_directory_part (name));
311 }
312
313 /* Return just the simple part of the filename; i.e. the
314    filename without the path information, or extensions.
315    This conses up a new string. */
316 char *
317 filename_part (filename)
318      char *filename;
319 {
320   char *basename = filename_non_directory (filename);
321
322 #ifdef REMOVE_OUTPUT_EXTENSIONS
323   /* See if there is an extension to remove.  If so, remove it. */
324   {
325     char *temp;
326
327     temp = strrchr (basename, '.');
328     if (temp)
329       *temp = 0;
330   }
331 #endif /* REMOVE_OUTPUT_EXTENSIONS */
332   return basename;
333 }
334
335 /* Return the pathname part of filename.  This can be NULL. */
336 char *
337 pathname_part (filename)
338      char *filename;
339 {
340   char *expand_filename ();
341   char *result = NULL;
342   int i;
343
344   filename = expand_filename (filename, "");
345
346   i = skip_directory_part (filename);
347   if (i)
348     {
349       result = xmalloc (1 + i);
350       strncpy (result, filename, i);
351       result[i] = 0;
352     }
353   free (filename);
354   return result;
355 }
356
357 /* Return the expansion of FILENAME. */
358 char *
359 expand_filename (filename, input_name)
360      char *filename, *input_name;
361 {
362   int i;
363   char *full_pathname ();
364
365   if (filename)
366     {
367       filename = full_pathname (filename);
368       if (IS_ABSOLUTE (filename)
369           || (*filename == '.' &&
370               (IS_SLASH (filename[1]) ||
371                (filename[1] == '.' && IS_SLASH (filename[2])))))
372         return filename;
373     }
374   else
375     {
376       filename = filename_non_directory (input_name);
377
378       if (!*filename)
379         {
380           free (filename);
381           filename = xstrdup ("noname.texi");
382         }
383
384       for (i = strlen (filename) - 1; i; i--)
385         if (filename[i] == '.')
386           break;
387
388       if (!i)
389         i = strlen (filename);
390
391       if (i + 6 > (strlen (filename)))
392         filename = xrealloc (filename, i + 6);
393       strcpy (filename + i, html ? ".html" : ".info");
394       return filename;
395     }
396
397   if (IS_ABSOLUTE (input_name))
398     {
399       /* Make it so that relative names work. */
400       char *result;
401       
402       i = strlen (input_name) - 1;
403
404       result = xmalloc (1 + strlen (input_name) + strlen (filename));
405       strcpy (result, input_name);
406
407       while (!IS_SLASH (result[i]) && i)
408         i--;
409       if (IS_SLASH (result[i]))
410         i++;
411
412       strcpy (&result[i], filename);
413       free (filename);
414       return result;
415     }
416   return filename;
417 }
418
419 /* Return the full path to FILENAME. */
420 char *
421 full_pathname (filename)
422      char *filename;
423 {
424   int initial_character;
425   char *result;
426
427   /* No filename given? */
428   if (!filename || !*filename)
429     return xstrdup ("");
430   
431   /* Already absolute? */
432   if (IS_ABSOLUTE (filename) ||
433       (*filename == '.' &&
434        (IS_SLASH (filename[1]) ||
435         (filename[1] == '.' && IS_SLASH (filename[2])))))
436     return xstrdup (filename);
437
438   initial_character = *filename;
439   if (initial_character != '~')
440     {
441       char *localdir = xmalloc (1025);
442 #ifdef HAVE_GETCWD
443       if (!getcwd (localdir, 1024))
444 #else
445       if (!getwd (localdir))
446 #endif
447         {
448           fprintf (stderr, _("%s: getwd: %s, %s\n"),
449                    progname, filename, localdir);
450           xexit (1);
451         }
452
453       strcat (localdir, "/");
454       strcat (localdir, filename);
455       result = xstrdup (localdir);
456       free (localdir);
457     }
458   else
459     { /* Does anybody know why WIN32 doesn't want to support $HOME?
460          If the reason is they don't have getpwnam, they should
461          only disable the else clause below.  */
462 #ifndef WIN32
463       if (IS_SLASH (filename[1]))
464         {
465           /* Return the concatenation of the environment variable HOME
466              and the rest of the string. */
467           char *temp_home;
468
469           temp_home = (char *) getenv ("HOME");
470           result = xmalloc (strlen (&filename[1])
471                                     + 1
472                                     + temp_home ? strlen (temp_home)
473                                     : 0);
474           *result = 0;
475
476           if (temp_home)
477             strcpy (result, temp_home);
478
479           strcat (result, &filename[1]);
480         }
481       else
482         {
483           struct passwd *user_entry;
484           int i, c;
485           char *username = xmalloc (257);
486
487           for (i = 1; (c = filename[i]); i++)
488             {
489               if (IS_SLASH (c))
490                 break;
491               else
492                 username[i - 1] = c;
493             }
494           if (c)
495             username[i - 1] = 0;
496
497           user_entry = getpwnam (username);
498
499           if (!user_entry)
500             return xstrdup (filename);
501
502           result = xmalloc (1 + strlen (user_entry->pw_dir)
503                                     + strlen (&filename[i]));
504           strcpy (result, user_entry->pw_dir);
505           strcat (result, &filename[i]);
506         }
507 #endif /* not WIN32 */
508     }
509   return result;
510 }
511
512 char *
513 output_name_from_input_name (name)
514      char *name;
515 {
516   return expand_filename (NULL, name);
517 }
518
519
520 /* Modify the file name FNAME so that it fits the limitations of the
521    underlying filesystem.  In particular, truncate the file name as it
522    would be truncated by the filesystem.  We assume the result can
523    never be longer than the original, otherwise we couldn't be sure we
524    have enough space in the original string to modify it in place.  */
525 char *
526 normalize_filename (fname)
527      char *fname;
528 {
529   int maxlen;
530   char orig[PATH_MAX + 1];
531   int i;
532   char *lastdot, *p;
533
534 #ifdef _PC_NAME_MAX
535   maxlen = pathconf (fname, _PC_NAME_MAX);
536   if (maxlen < 1)
537 #endif
538     maxlen = PATH_MAX;
539
540   i = skip_directory_part (fname);
541   if (fname[i] == '\0')
542     return fname;       /* only a directory name -- don't modify */
543   strcpy (orig, fname + i);
544
545   switch (maxlen)
546     {
547       case 12:  /* MS-DOS 8+3 filesystem */
548         if (orig[0] == '.')     /* leading dots are not allowed */
549           orig[0] = '_';
550         lastdot = strrchr (orig, '.');
551         if (!lastdot)
552           lastdot = orig + strlen (orig);
553         strncpy (fname + i, orig, lastdot - orig);
554         for (p = fname + i;
555              p < fname + i + (lastdot - orig) && p < fname + i + 8;
556              p++)
557           if (*p == '.')
558             *p = '_';
559         *p = '\0';
560         if (*lastdot == '.')
561           strncat (fname + i, lastdot, 4);
562         break;
563       case 14:  /* old Unix systems with 14-char limitation */
564         strcpy (fname + i, orig);
565         if (strlen (fname + i) > 14)
566           fname[i + 14] = '\0';
567         break;
568       default:
569         strcpy (fname + i, orig);
570         if (strlen (fname) > maxlen - 1)
571           fname[maxlen - 1] = '\0';
572         break;
573     }
574
575   return fname;
576 }