kernel - TMPFS - Stabilization pass, fix accounting for rmdir
[dragonfly.git] / contrib / texinfo-4 / makeinfo / files.c
blobdb112262ee26470f6ba4f22a32d62e350cb7b82d
1 /* files.c -- file-related functions for makeinfo.
2 $Id: files.c,v 1.5 2004/07/27 00:06:31 karl Exp $
4 Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004 Free Software
5 Foundation, Inc.
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.
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.
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. */
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"
29 FSTACK *filestack = NULL;
31 static int node_filename_stack_index = 0;
32 static int node_filename_stack_size = 0;
33 static char **node_filename_stack = NULL;
35 /* Looking for include files. */
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. */
40 static char *
41 extract_colon_unit (char *string, int *index)
43 int start;
44 int path_sep_char = PATH_SEP[0];
45 int i = *index;
47 if (!string || (i >= strlen (string)))
48 return NULL;
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++;
58 start = i;
59 while (string[i] && string[i] != path_sep_char) i++;
60 *index = i;
62 if (i == start)
64 if (string[i])
65 (*index)++;
67 /* Return "" in the case of a trailing `:'. */
68 return xstrdup ("");
70 else
72 char *value;
74 value = xmalloc (1 + (i - start));
75 memcpy (value, &string[start], (i - start));
76 value [i - start] = 0;
78 return value;
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. */
86 char *
87 get_file_info_in_path (char *filename, char *path, struct stat *finfo)
89 char *dir;
90 int result, index = 0;
92 if (path == NULL)
93 path = ".";
95 /* Handle absolute pathnames. */
96 if (IS_ABSOLUTE (filename)
97 || (*filename == '.'
98 && (IS_SLASH (filename[1])
99 || (filename[1] == '.' && IS_SLASH (filename[2])))))
101 if (stat (filename, finfo) == 0)
102 return xstrdup (filename);
103 else
104 return NULL;
107 while ((dir = extract_colon_unit (path, &index)))
109 char *fullpath;
111 if (!*dir)
113 free (dir);
114 dir = xstrdup (".");
117 fullpath = xmalloc (2 + strlen (dir) + strlen (filename));
118 sprintf (fullpath, "%s/%s", dir, filename);
119 free (dir);
121 result = stat (fullpath, finfo);
123 if (result == 0)
124 return fullpath;
125 else
126 free (fullpath);
128 return NULL;
131 /* Prepend and append new paths to include_files_path. */
132 void
133 prepend_to_include_path (char *path)
135 if (!include_files_path)
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), ".");
142 else
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);
154 void
155 append_to_include_path (char *path)
157 if (!include_files_path)
158 include_files_path = xstrdup (".");
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);
166 /* Remove the first path from the include_files_path. */
167 void
168 pop_path_from_include_path (void)
170 int i = 0;
171 char *tmp;
173 if (include_files_path)
174 for (i = 0; i < strlen (include_files_path)
175 && include_files_path[i] != ':'; i++);
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);
181 free (include_files_path);
182 include_files_path = tmp;
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. */
190 char *
191 find_and_load (char *filename, int use_path)
193 struct stat fileinfo;
194 long file_size;
195 int file = -1, count = 0;
196 char *fullpath, *result;
197 int n, bytes_to_read;
199 result = fullpath = NULL;
201 fullpath
202 = get_file_info_in_path (filename, use_path ? include_files_path : NULL,
203 &fileinfo);
205 if (!fullpath)
206 goto error_exit;
208 filename = fullpath;
209 file_size = (long) fileinfo.st_size;
211 file = open (filename, O_RDONLY);
212 if (file < 0)
213 goto error_exit;
215 /* Load the file, with enough room for a newline and a null. */
216 result = xmalloc (file_size + 2);
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)
226 count += n;
227 bytes_to_read -= n;
229 if (0 < count && count < file_size)
230 result = xrealloc (result, count + 2); /* why waste the slack? */
231 else if (n == -1)
232 error_exit:
234 if (result)
235 free (result);
237 if (fullpath)
238 free (fullpath);
240 if (file != -1)
241 close (file);
243 return NULL;
245 close (file);
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;
263 /* Pushing and popping files. */
264 static void
265 push_node_filename (void)
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 *));
271 node_filename_stack[node_filename_stack_index] = node_filename;
272 node_filename_stack_index++;
275 static void
276 pop_node_filename (void)
278 node_filename = node_filename_stack[--node_filename_stack_index];
281 /* Save the state of the current input file. */
282 void
283 pushfile (void)
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;
293 filestack = newstack;
294 push_node_filename ();
297 /* Make the current file globals be what is on top of the file stack. */
298 void
299 popfile (void)
301 FSTACK *tos = filestack;
303 if (!tos)
304 abort (); /* My fault. I wonder what I did? */
306 if (macro_expansion_output_stream)
308 maybe_write_itext (input_text, input_text_offset);
309 forget_itext (input_text);
312 /* Pop the stack. */
313 filestack = filestack->next;
315 /* Make sure that commands with braces have been satisfied. */
316 if (!executing_string && !me_executing_string)
317 discard_braces ();
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);
327 /* Go back to the (now) current node. */
328 pop_node_filename ();
331 /* Flush all open files on the file stack. */
332 void
333 flush_file_stack (void)
335 while (filestack)
337 char *fname = input_filename;
338 char *text = input_text;
339 popfile ();
340 free (fname);
341 free (text);
345 /* Return the index of the first character in the filename
346 which is past all the leading directory characters. */
347 static int
348 skip_directory_part (char *filename)
350 int i = strlen (filename) - 1;
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;
359 return i;
362 static char *
363 filename_non_directory (char *name)
365 return xstrdup (name + skip_directory_part (name));
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. */
371 char *
372 filename_part (char *filename)
374 char *basename = filename_non_directory (filename);
376 #ifdef REMOVE_OUTPUT_EXTENSIONS
377 /* See if there is an extension to remove. If so, remove it. */
379 char *temp = strrchr (basename, '.');
380 if (temp)
381 *temp = 0;
383 #endif /* REMOVE_OUTPUT_EXTENSIONS */
384 return basename;
387 /* Return the pathname part of filename. This can be NULL. */
388 char *
389 pathname_part (char *filename)
391 char *result = NULL;
392 int i;
394 filename = expand_filename (filename, "");
396 i = skip_directory_part (filename);
397 if (i)
399 result = xmalloc (1 + i);
400 strncpy (result, filename, i);
401 result[i] = 0;
403 free (filename);
404 return result;
407 /* Return the full path to FILENAME. */
408 static char *
409 full_pathname (char *filename)
411 int initial_character;
412 char *result;
414 /* No filename given? */
415 if (!filename || !*filename)
416 return xstrdup ("");
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);
425 initial_character = *filename;
426 if (initial_character != '~')
428 char *localdir = xmalloc (1025);
429 #ifdef HAVE_GETCWD
430 if (!getcwd (localdir, 1024))
431 #else
432 if (!getwd (localdir))
433 #endif
435 fprintf (stderr, _("%s: getwd: %s, %s\n"),
436 progname, filename, localdir);
437 xexit (1);
440 strcat (localdir, "/");
441 strcat (localdir, filename);
442 result = xstrdup (localdir);
443 free (localdir);
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]))
452 /* Return the concatenation of the environment variable HOME
453 and the rest of the string. */
454 char *temp_home;
456 temp_home = (char *) getenv ("HOME");
457 result = xmalloc (strlen (&filename[1])
459 + temp_home ? strlen (temp_home)
460 : 0);
461 *result = 0;
463 if (temp_home)
464 strcpy (result, temp_home);
466 strcat (result, &filename[1]);
468 else
470 struct passwd *user_entry;
471 int i, c;
472 char *username = xmalloc (257);
474 for (i = 1; (c = filename[i]); i++)
476 if (IS_SLASH (c))
477 break;
478 else
479 username[i - 1] = c;
481 if (c)
482 username[i - 1] = 0;
484 user_entry = getpwnam (username);
486 if (!user_entry)
487 return xstrdup (filename);
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]);
494 #endif /* not WIN32 */
496 return result;
499 /* Return the expansion of FILENAME. */
500 char *
501 expand_filename (char *filename, char *input_name)
503 int i;
505 if (filename)
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;
514 else
516 filename = filename_non_directory (input_name);
518 if (!*filename)
520 free (filename);
521 filename = xstrdup ("noname.texi");
524 for (i = strlen (filename) - 1; i; i--)
525 if (filename[i] == '.')
526 break;
528 if (!i)
529 i = strlen (filename);
531 if (i + 6 > (strlen (filename)))
532 filename = xrealloc (filename, i + 6);
533 strcpy (filename + i, html ? ".html" : ".info");
534 return filename;
537 if (IS_ABSOLUTE (input_name))
539 /* Make it so that relative names work. */
540 char *result;
542 i = strlen (input_name) - 1;
544 result = xmalloc (1 + strlen (input_name) + strlen (filename));
545 strcpy (result, input_name);
547 while (!IS_SLASH (result[i]) && i)
548 i--;
549 if (IS_SLASH (result[i]))
550 i++;
552 strcpy (&result[i], filename);
553 free (filename);
554 return result;
556 return filename;
559 char *
560 output_name_from_input_name (char *name)
562 return expand_filename (NULL, name);
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. */
571 char *
572 normalize_filename (char *fname)
574 int maxlen;
575 char orig[PATH_MAX + 1];
576 int i;
577 char *lastdot, *p;
579 #ifdef _PC_NAME_MAX
580 maxlen = pathconf (fname, _PC_NAME_MAX);
581 if (maxlen < 1)
582 #endif
583 maxlen = PATH_MAX;
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);
590 switch (maxlen)
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;
620 return fname;
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. */
627 static DELAYED_WRITE *delayed_writes = NULL;
628 int handling_delayed_writes = 0;
630 void
631 register_delayed_write (char *delayed_command)
633 DELAYED_WRITE *new;
635 if (!current_output_filename || !*current_output_filename)
637 /* Cannot register if we don't know what the output file is. */
638 warning (_("`%s' omitted before output filename"), delayed_command);
639 return;
642 if (STREQ (current_output_filename, "-"))
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;
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;
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 ();
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): "";
671 new->node_order = node_order;
672 new->index_order = index_counter;
674 new->next = delayed_writes;
675 delayed_writes = new;
678 void
679 handle_delayed_writes (void)
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;
686 handling_delayed_writes = 1;
688 while (temp)
690 delayed_buf = find_and_load (temp->filename, 0);
692 if (output_paragraph_offset > 0)
694 error (_("Output buffer not empty."));
695 return;
698 if (!delayed_buf)
700 fs_error (temp->filename);
701 return;
704 output_stream = fopen (temp->filename, "w");
705 if (!output_stream)
707 fs_error (temp->filename);
708 return;
711 if (fwrite (delayed_buf, 1, temp->position, output_stream) != temp->position)
713 fs_error (temp->filename);
714 return;
718 int output_position_at_start = output_position;
719 int line_number_at_start = output_line_number;
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;
726 execute_string ("%s", temp->command);
727 flush_output ();
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;
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);
741 /* Done with the buffer. */
742 free (delayed_buf);
744 /* Update positions in tag table for nodes that are defined after
745 the line this delayed write is registered. */
746 if (!html && !xml)
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;
754 /* Something similar for the line numbers in all of the defined
755 indices. */
757 int i;
758 for (i = 0; i < defined_indices; i++)
759 if (name_index_alist[i])
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;
770 /* Shift remaining delayed positions
771 by the length of this write. */
773 DELAYED_WRITE *future_write = temp->next;
774 while (future_write)
776 if (STREQ (temp->filename, future_write->filename))
777 future_write->position += position_shift_amount;
778 future_write = future_write->next;
782 temp = temp->next;