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