Initial revision
[gcc.git] / texinfo / util / install-info.c
1 /* install-info -- create Info directory entry(ies) for an Info file.
2 Copyright (C) 1996 Free Software Foundation, Inc.
3
4 $Id: install-info.c,v 1.12 1996/10/03 23:13:36 karl Exp $
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 of the License, or
9 (at your option) 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
18 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
19
20 #define INSTALL_INFO_VERSION_STRING "GNU install-info (Texinfo 3.9) 1.2"
21
22 #include <stdio.h>
23 #include <errno.h>
24 #include <getopt.h>
25 #include <sys/types.h>
26
27 /* Get O_RDONLY. */
28 #ifdef HAVE_SYS_FCNTL_H
29 #include <sys/fcntl.h>
30 #else
31 #include <fcntl.h>
32 #endif /* !HAVE_SYS_FCNTL_H */
33 #ifdef HAVE_SYS_FILE_H
34 #include <sys/file.h>
35 #endif
36
37 /* Name this program was invoked with. */
38 char *progname;
39
40 char *readfile ();
41 struct line_data *findlines ();
42 char *my_strerror ();
43 void fatal ();
44 void insert_entry_here ();
45 int compare_section_names ();
46
47 struct spec_entry;
48 \f
49 /* Data structures. */
50
51 /* Record info about a single line from a file
52 as read into core. */
53
54 struct line_data
55 {
56 /* The start of the line. */
57 char *start;
58 /* The number of characters in the line,
59 excluding the terminating newline. */
60 int size;
61 /* Vector containing pointers to the entries to add before this line.
62 The vector is null-terminated. */
63 struct spec_entry **add_entries_before;
64 /* 1 means output any needed new sections before this line. */
65 int add_sections_before;
66 /* 1 means don't output this line. */
67 int delete;
68 };
69
70 /* This is used for a list of the specified menu section names
71 in which entries should be added. */
72
73 struct spec_section
74 {
75 struct spec_section *next;
76 char *name;
77 /* 1 means we have not yet found an existing section with this name
78 in the dir file--so we will need to add a new section. */
79 int missing;
80 };
81
82 /* This is used for a list of the entries specified to be added. */
83
84 struct spec_entry
85 {
86 struct spec_entry *next;
87 char *text;
88 };
89 \f
90 /* This is used for a list of nodes found by parsing the dir file. */
91
92 struct node
93 {
94 struct node *next;
95 /* The node name. */
96 char *name;
97 /* The line number of the line where the node starts.
98 This is the line that contains control-underscore. */
99 int start_line;
100 /* The line number of the line where the node ends,
101 which is the end of the file or where the next line starts. */
102 int end_line;
103 /* Start of first line in this node's menu
104 (the line after the * Menu: line). */
105 char *menu_start;
106 /* The start of the chain of sections in this node's menu. */
107 struct menu_section *sections;
108 /* The last menu section in the chain. */
109 struct menu_section *last_section;
110 };
111
112 /* This is used for a list of sections found in a node's menu.
113 Each struct node has such a list in the sections field. */
114
115 struct menu_section
116 {
117 struct menu_section *next;
118 char *name;
119 /* Line number of start of section. */
120 int start_line;
121 /* Line number of end of section. */
122 int end_line;
123 };
124 \f
125 /* Memory allocation and string operations. */
126
127 /* Like malloc but get fatal error if memory is exhausted. */
128
129 void *
130 xmalloc (size)
131 unsigned int size;
132 {
133 extern void *malloc ();
134 void *result = malloc (size);
135 if (result == NULL)
136 fatal ("virtual memory exhausted", 0);
137 return result;
138 }
139
140 /* Like malloc but get fatal error if memory is exhausted. */
141
142 void *
143 xrealloc (obj, size)
144 void *obj;
145 unsigned int size;
146 {
147 extern void *realloc ();
148 void *result = realloc (obj, size);
149 if (result == NULL)
150 fatal ("virtual memory exhausted", 0);
151 return result;
152 }
153
154 /* Return a newly-allocated string whose contents concatenate those of s1, s2, s3. */
155
156 char *
157 concat (s1, s2, s3)
158 char *s1, *s2, *s3;
159 {
160 int len1 = strlen (s1), len2 = strlen (s2), len3 = strlen (s3);
161 char *result = (char *) xmalloc (len1 + len2 + len3 + 1);
162
163 strcpy (result, s1);
164 strcpy (result + len1, s2);
165 strcpy (result + len1 + len2, s3);
166 *(result + len1 + len2 + len3) = 0;
167
168 return result;
169 }
170
171 /* Return a string containing SIZE characters
172 copied from starting at STRING. */
173
174 char *
175 copy_string (string, size)
176 char *string;
177 int size;
178 {
179 int i;
180 char *copy = (char *) xmalloc (size + 1);
181 for (i = 0; i < size; i++)
182 copy[i] = string[i];
183 copy[size] = 0;
184 return copy;
185 }
186 \f
187 /* Error message functions. */
188
189 /* Print error message. `s1' is printf control string, `s2' is arg for it. */
190
191 /* VARARGS1 */
192 void
193 error (s1, s2, s3)
194 char *s1, *s2, *s3;
195 {
196 fprintf (stderr, "%s: ", progname);
197 fprintf (stderr, s1, s2, s3);
198 fprintf (stderr, "\n");
199 }
200
201 /* VARARGS1 */
202 void
203 warning (s1, s2, s3)
204 char *s1, *s2, *s3;
205 {
206 fprintf (stderr, "%s: Warning: ", progname);
207 fprintf (stderr, s1, s2, s3);
208 fprintf (stderr, "\n");
209 }
210
211 /* Print error message and exit. */
212
213 void
214 fatal (s1, s2, s3)
215 char *s1, *s2, *s3;
216 {
217 error (s1, s2, s3);
218 exit (1);
219 }
220
221 /* Print fatal error message based on errno, with file name NAME. */
222
223 void
224 pfatal_with_name (name)
225 char *name;
226 {
227 char *s = concat ("", my_strerror (errno), " for %s");
228 fatal (s, name);
229 }
230 \f
231 /* Given the full text of a menu entry, null terminated,
232 return just the menu item name (copied). */
233
234 char *
235 extract_menu_item_name (item_text)
236 char *item_text;
237 {
238 char *p;
239
240 if (*item_text == '*')
241 item_text++;
242 while (*item_text == ' ')
243 item_text++;
244
245 p = item_text;
246 while (*p && *p != ':') p++;
247 return copy_string (item_text, p - item_text);
248 }
249
250 /* Given the full text of a menu entry, terminated by null or newline,
251 return just the menu item file (copied). */
252
253 char *
254 extract_menu_file_name (item_text)
255 char *item_text;
256 {
257 char *p = item_text;
258
259 /* If we have text that looks like * ITEM: (FILE)NODE...,
260 extract just FILE. Otherwise return "(none)". */
261
262 if (*p == '*')
263 p++;
264 while (*p == ' ')
265 p++;
266
267 /* Skip to and past the colon. */
268 while (*p && *p != '\n' && *p != ':') p++;
269 if (*p == ':') p++;
270
271 /* Skip past the open-paren. */
272 while (1)
273 {
274 if (*p == '(')
275 break;
276 else if (*p == ' ' || *p == '\t')
277 p++;
278 else
279 return "(none)";
280 }
281 p++;
282
283 item_text = p;
284
285 /* File name ends just before the close-paren. */
286 while (*p && *p != '\n' && *p != ')') p++;
287 if (*p != ')')
288 return "(none)";
289
290 return copy_string (item_text, p - item_text);
291 }
292 \f
293 void
294 suggest_asking_for_help ()
295 {
296 fprintf (stderr, "\tTry `%s --help' for a complete list of options.\n",
297 progname);
298 exit (1);
299 }
300
301 void
302 print_help ()
303 {
304 printf ("%s [OPTION]... [INFO-FILE [DIR-FILE]]\n\
305 Install INFO-FILE in the Info directory file DIR-FILE.\n\
306 \n\
307 Options:\n\
308 --delete Delete existing entries in INFO-FILE;\n\
309 don't insert any new entries.\n\
310 --dir-file=NAME Specify file name of Info directory file.\n\
311 This is equivalent to using the DIR-FILE argument.\n\
312 --entry=TEXT Insert TEXT as an Info directory entry.\n\
313 TEXT should have the form of an Info menu item line\n\
314 plus zero or more extra lines starting with whitespace.\n\
315 If you specify more than one entry, they are all added.\n\
316 If you don't specify any entries, they are determined\n\
317 from information in the Info file itself.\n\
318 --help Display this help and exit.\n\
319 --info-file=FILE Specify Info file to install in the directory.\n\
320 This is equivalent to using the INFO-FILE argument.\n\
321 --info-dir=DIR Same as --dir-file=DIR/dir.\n\
322 --item=TEXT Same as --entry TEXT.\n\
323 An Info directory entry is actually a menu item.\n\
324 --quiet Suppress warnings.\n\
325 --remove Same as --delete.\n\
326 --section=SEC Put this file's entries in section SEC of the directory.\n\
327 If you specify more than one section, all the entries\n\
328 are added in each of the sections.\n\
329 If you don't specify any sections, they are determined\n\
330 from information in the Info file itself.\n\
331 --version Display version information and exit.\n\
332 \n\
333 Email bug reports to bug-texinfo@prep.ai.mit.edu.\n\
334 ", progname);
335 }
336
337 /* Convert an errno value into a string describing the error.
338 We define this function here rather than using strerror
339 because not all systems have strerror. */
340
341 char *
342 my_strerror (errnum)
343 int errnum;
344 {
345 extern char *sys_errlist[];
346 extern int sys_nerr;
347
348 if (errnum >= 0 && errnum < sys_nerr)
349 return sys_errlist[errnum];
350 return (char *) "Unknown error";
351 }
352 \f
353 /* This table defines all the long-named options, says whether they
354 use an argument, and maps them into equivalent single-letter options. */
355
356 struct option longopts[] =
357 {
358 { "delete", no_argument, NULL, 'r' },
359 { "dir-file", required_argument, NULL, 'd' },
360 { "entry", required_argument, NULL, 'e' },
361 { "help", no_argument, NULL, 'h' },
362 { "info-dir", required_argument, NULL, 'D' },
363 { "info-file", required_argument, NULL, 'i' },
364 { "item", required_argument, NULL, 'e' },
365 { "quiet", no_argument, NULL, 'q' },
366 { "remove", no_argument, NULL, 'r' },
367 { "section", required_argument, NULL, 's' },
368 { "version", no_argument, NULL, 'V' },
369 { 0 }
370 };
371 \f
372 main (argc, argv)
373 int argc;
374 char **argv;
375 {
376 char *infile = 0, *dirfile = 0;
377 char *infile_sans_info;
378 unsigned infilelen_sans_info;
379 FILE *output;
380
381 /* Record the text of the Info file, as a sequence of characters
382 and as a sequence of lines. */
383 char *input_data;
384 int input_size;
385 struct line_data *input_lines;
386 int input_nlines;
387
388 /* Record here the specified section names and directory entries. */
389 struct spec_section *input_sections = NULL;
390 struct spec_entry *entries_to_add = NULL;
391 int n_entries_to_add = 0;
392
393 /* Record the old text of the dir file, as plain characters,
394 as lines, and as nodes. */
395 char *dir_data;
396 int dir_size;
397 int dir_nlines;
398 struct line_data *dir_lines;
399 struct node *dir_nodes;
400
401 /* Nonzero means --delete was specified (just delete existing entries). */
402 int delete_flag = 0;
403 int something_deleted = 0;
404 /* Nonzero means -q was specified. */
405 int quiet_flag = 0;
406
407 int node_header_flag;
408 int prefix_length;
409 int i;
410
411 progname = argv[0];
412
413 while (1)
414 {
415 int opt = getopt_long (argc, argv, "i:d:e:s:hHr", longopts, 0);
416
417 if (opt == EOF)
418 break;
419
420 switch (opt)
421 {
422 case 0:
423 /* If getopt returns 0, then it has already processed a
424 long-named option. We should do nothing. */
425 break;
426
427 case 1:
428 abort ();
429
430 case 'd':
431 if (dirfile)
432 {
433 fprintf (stderr, "%s: Specify the Info directory only once.\n",
434 progname);
435 suggest_asking_for_help ();
436 }
437 dirfile = optarg;
438 break;
439
440 case 'D':
441 if (dirfile)
442 {
443 fprintf (stderr, "%s: Specify the Info directory only once.\n",
444 progname);
445 suggest_asking_for_help ();
446 }
447 dirfile = concat (optarg, "", "/dir");
448 break;
449
450 case 'e':
451 {
452 struct spec_entry *next
453 = (struct spec_entry *) xmalloc (sizeof (struct spec_entry));
454 if (! (*optarg != 0 && optarg[strlen (optarg) - 1] == '\n'))
455 optarg = concat (optarg, "\n", "");
456 next->text = optarg;
457 next->next = entries_to_add;
458 entries_to_add = next;
459 n_entries_to_add++;
460 }
461 break;
462
463 case 'h':
464 case 'H':
465 print_help ();
466 exit (0);
467
468 case 'i':
469 if (infile)
470 {
471 fprintf (stderr, "%s: Specify the Info file only once.\n",
472 progname);
473 suggest_asking_for_help ();
474 }
475 infile = optarg;
476 break;
477
478 case 'q':
479 quiet_flag = 1;
480 break;
481
482 case 'r':
483 delete_flag = 1;
484 break;
485
486 case 's':
487 {
488 struct spec_section *next
489 = (struct spec_section *) xmalloc (sizeof (struct spec_section));
490 next->name = optarg;
491 next->next = input_sections;
492 next->missing = 1;
493 input_sections = next;
494 }
495 break;
496
497 case 'V':
498 puts (INSTALL_INFO_VERSION_STRING);
499 puts ("Copyright (C) 1996 Free Software Foundation, Inc.\n\
500 There is NO warranty. You may redistribute this software\n\
501 under the terms of the GNU General Public License.\n\
502 For more information about these matters, see the files named COPYING.");
503 exit (0);
504
505 default:
506 suggest_asking_for_help ();
507 }
508 }
509
510 /* Interpret the non-option arguments as file names. */
511 for (; optind < argc; ++optind)
512 {
513 if (infile == 0)
514 infile = argv[optind];
515 else if (dirfile == 0)
516 dirfile = argv[optind];
517 else
518 error ("excess command line argument `%s'", argv[optind]);
519 }
520
521 if (!infile)
522 fatal ("No input file specified");
523 if (!dirfile)
524 fatal ("No dir file specified");
525
526 /* Read the Info file and parse it into lines. */
527
528 input_data = readfile (infile, &input_size);
529 input_lines = findlines (input_data, input_size, &input_nlines);
530
531 /* Parse the input file to find the section names it specifies. */
532
533 if (input_sections == 0)
534 {
535 prefix_length = strlen ("INFO-DIR-SECTION ");
536 for (i = 0; i < input_nlines; i++)
537 {
538 if (!strncmp ("INFO-DIR-SECTION ", input_lines[i].start,
539 prefix_length))
540 {
541 struct spec_section *next
542 = (struct spec_section *) xmalloc (sizeof (struct spec_section));
543 next->name = copy_string (input_lines[i].start + prefix_length,
544 input_lines[i].size - prefix_length);
545 next->next = input_sections;
546 next->missing = 1;
547 input_sections = next;
548 }
549 }
550 }
551
552 /* Default to section "Miscellaneous" if no sections specified. */
553 if (input_sections == 0)
554 {
555 input_sections
556 = (struct spec_section *) xmalloc (sizeof (struct spec_section));
557 input_sections->name = "Miscellaneous";
558 input_sections->next = 0;
559 input_sections->missing = 1;
560 }
561
562 /* Now find the directory entries specified in the file
563 and put them on entries_to_add. But not if entries
564 were specified explicitly with command options. */
565
566 if (entries_to_add == 0)
567 {
568 char *start_of_this_entry = 0;
569 for (i = 0; i < input_nlines; i++)
570 {
571 if (!strncmp ("START-INFO-DIR-ENTRY", input_lines[i].start,
572 input_lines[i].size)
573 && sizeof ("START-INFO-DIR-ENTRY") - 1 == input_lines[i].size)
574 {
575 if (start_of_this_entry != 0)
576 fatal ("START-INFO-DIR-ENTRY without matching END-INFO-DIR-ENTRY");
577 start_of_this_entry = input_lines[i + 1].start;
578 }
579 if (!strncmp ("END-INFO-DIR-ENTRY", input_lines[i].start,
580 input_lines[i].size)
581 && sizeof ("END-INFO-DIR-ENTRY") - 1 == input_lines[i].size)
582 {
583 if (start_of_this_entry != 0)
584 {
585 struct spec_entry *next
586 = (struct spec_entry *) xmalloc (sizeof (struct spec_entry));
587 next->text = copy_string (start_of_this_entry,
588 input_lines[i].start - start_of_this_entry);
589 next->next = entries_to_add;
590 entries_to_add = next;
591 n_entries_to_add++;
592 start_of_this_entry = 0;
593 }
594 else
595 fatal ("END-INFO-DIR-ENTRY without matching START-INFO-DIR-ENTRY");
596 }
597 }
598 if (start_of_this_entry != 0)
599 fatal ("START-INFO-DIR-ENTRY without matching END-INFO-DIR-ENTRY");
600 }
601
602 if (!delete_flag)
603 if (entries_to_add == 0)
604 fatal ("no info dir entry in `%s'", infile);
605
606 /* Now read in the Info dir file. */
607 dir_data = readfile (dirfile, &dir_size);
608 dir_lines = findlines (dir_data, dir_size, &dir_nlines);
609
610 /* We will be comparing the entries in the dir file against the
611 current filename, so need to strip off any directory prefix and any
612 .info suffix. */
613 {
614 unsigned basename_len;
615 extern char *strrchr ();
616 char *infile_basename = strrchr (infile, '/');
617 if (infile_basename)
618 infile_basename++;
619 else
620 infile_basename = infile;
621
622 basename_len = strlen (infile_basename);
623 infile_sans_info
624 = (strlen (infile_basename) > 5
625 && strcmp (infile_basename + basename_len - 5, ".info") == 0)
626 ? copy_string (infile_basename, basename_len - 5)
627 : infile_basename;
628
629 infilelen_sans_info = strlen (infile_sans_info);
630 }
631
632 /* Parse the dir file. Find all the nodes, and their menus,
633 and the sections of their menus. */
634
635 dir_nodes = 0;
636 node_header_flag = 0;
637 for (i = 0; i < dir_nlines; i++)
638 {
639 /* Parse node header lines. */
640 if (node_header_flag)
641 {
642 int j, end;
643 for (j = 0; j < dir_lines[i].size; j++)
644 /* Find the node name and store it in the `struct node'. */
645 if (!strncmp ("Node:", dir_lines[i].start + j, 5))
646 {
647 char *line = dir_lines[i].start;
648 /* Find the start of the node name. */
649 j += 5;
650 while (line[j] == ' ' || line[j] == '\t')
651 j++;
652 /* Find the end of the node name. */
653 end = j;
654 while (line[end] != 0 && line[end] != ',' && line[end] != '\n'
655 && line[end] != '\t')
656 end++;
657 dir_nodes->name = copy_string (line + j, end - j);
658 }
659 node_header_flag = 0;
660 }
661
662 /* Notice the start of a node. */
663 if (*dir_lines[i].start == 037)
664 {
665 struct node *next
666 = (struct node *) xmalloc (sizeof (struct node));
667 next->next = dir_nodes;
668 next->name = NULL;
669 next->start_line = i;
670 next->end_line = 0;
671 next->menu_start = NULL;
672 next->sections = NULL;
673 next->last_section = NULL;
674
675 if (dir_nodes != 0)
676 dir_nodes->end_line = i;
677 /* Fill in the end of the last menu section
678 of the previous node. */
679 if (dir_nodes != 0 && dir_nodes->last_section != 0)
680 dir_nodes->last_section->end_line = i;
681
682 dir_nodes = next;
683
684 /* The following line is the header of this node;
685 parse it. */
686 node_header_flag = 1;
687 }
688
689 /* Notice the lines that start menus. */
690 if (dir_nodes != 0
691 && !strncmp ("* Menu:", dir_lines[i].start, 7))
692 dir_nodes->menu_start = dir_lines[i + 1].start;
693
694 /* Notice sections in menus. */
695 if (dir_nodes != 0
696 && dir_nodes->menu_start != 0
697 && *dir_lines[i].start != '\n'
698 && *dir_lines[i].start != '*'
699 && *dir_lines[i].start != ' '
700 && *dir_lines[i].start != '\t')
701 {
702 /* Add this menu section to the node's list.
703 This list grows in forward order. */
704 struct menu_section *next
705 = (struct menu_section *) xmalloc (sizeof (struct menu_section));
706 next->start_line = i + 1;
707 next->next = 0;
708 next->end_line = 0;
709 next->name = copy_string (dir_lines[i].start, dir_lines[i].size);
710 if (dir_nodes->sections)
711 {
712 dir_nodes->last_section->next = next;
713 dir_nodes->last_section->end_line = i;
714 }
715 else
716 dir_nodes->sections = next;
717 dir_nodes->last_section = next;
718 }
719
720 /* Check for an existing entry that should be deleted.
721 Delete all entries which specify this file name. */
722 if (*dir_lines[i].start == '*')
723 {
724 char *p = dir_lines[i].start;
725
726 while (*p != 0 && *p != ':')
727 p++;
728 p++;
729 while (*p == ' ') p++;
730 if (*p == '(')
731 {
732 p++;
733 if ((dir_lines[i].size
734 > (p - dir_lines[i].start + infilelen_sans_info))
735 && !strncmp (p, infile_sans_info, infilelen_sans_info)
736 && p[infilelen_sans_info] == ')')
737 dir_lines[i].delete = 1;
738 }
739 }
740 /* Treat lines that start with whitespace
741 as continuations; if we are deleting an entry,
742 delete all its continuations as well. */
743 else if (i > 0
744 && (*dir_lines[i].start == ' '
745 || *dir_lines[i].start == '\t'))
746 {
747 dir_lines[i].delete = dir_lines[i - 1].delete;
748 something_deleted = 1;
749 }
750 }
751
752 /* Finish the info about the end of the last node. */
753 if (dir_nodes != 0)
754 {
755 dir_nodes->end_line = dir_nlines;
756 if (dir_nodes->last_section != 0)
757 dir_nodes->last_section->end_line = dir_nlines;
758 }
759
760 /* Decide where to add the new entries (unless --delete was used).
761 Find the menu sections to add them in.
762 In each section, find the proper alphabetical place to add
763 each of the entries. */
764
765 if (!delete_flag)
766 {
767 struct node *node;
768 struct menu_section *section;
769 struct spec_section *spec;
770
771 for (node = dir_nodes; node; node = node->next)
772 for (section = node->sections; section; section = section->next)
773 {
774 for (i = section->end_line; i > section->start_line; i--)
775 if (dir_lines[i - 1].size != 0)
776 break;
777 section->end_line = i;
778
779 for (spec = input_sections; spec; spec = spec->next)
780 if (!strcmp (spec->name, section->name))
781 break;
782 if (spec)
783 {
784 int add_at_line = section->end_line;
785 struct spec_entry *entry;
786 /* Say we have found at least one section with this name,
787 so we need not add such a section. */
788 spec->missing = 0;
789 /* For each entry, find the right place in this section
790 to add it. */
791 for (entry = entries_to_add; entry; entry = entry->next)
792 {
793 int textlen = strlen (entry->text);
794 /* Subtract one because dir_lines is zero-based,
795 but the `end_line' and `start_line' members are
796 one-based. */
797 for (i = section->end_line - 1;
798 i >= section->start_line - 1; i--)
799 {
800 /* If an entry exists with the same name,
801 and was not marked for deletion
802 (which means it is for some other file),
803 we are in trouble. */
804 if (dir_lines[i].start[0] == '*'
805 && menu_line_equal (entry->text, textlen,
806 dir_lines[i].start,
807 dir_lines[i].size)
808 && !dir_lines[i].delete)
809 fatal ("menu item `%s' already exists, for file `%s'",
810 extract_menu_item_name (entry->text),
811 extract_menu_file_name (dir_lines[i].start));
812 if (dir_lines[i].start[0] == '*'
813 && menu_line_lessp (entry->text, textlen,
814 dir_lines[i].start,
815 dir_lines[i].size))
816 add_at_line = i;
817 }
818 insert_entry_here (entry, add_at_line,
819 dir_lines, n_entries_to_add);
820 }
821 }
822 }
823
824 /* Mark the end of the Top node as the place to add any
825 new sections that are needed. */
826 for (node = dir_nodes; node; node = node->next)
827 if (node->name && strcmp (node->name, "Top") == 0)
828 dir_lines[node->end_line].add_sections_before = 1;
829 }
830
831 if (delete_flag && !something_deleted && !quiet_flag)
832 warning ("no entries found for `%s'; nothing deleted", infile);
833
834 /* Output the old dir file, interpolating the new sections
835 and/or new entries where appropriate. */
836
837 output = fopen (dirfile, "w");
838 if (!output)
839 {
840 perror (dirfile);
841 exit (1);
842 }
843
844 for (i = 0; i <= dir_nlines; i++)
845 {
846 int j;
847
848 /* If we decided to output some new entries before this line,
849 output them now. */
850 if (dir_lines[i].add_entries_before)
851 for (j = 0; j < n_entries_to_add; j++)
852 {
853 struct spec_entry *this = dir_lines[i].add_entries_before[j];
854 if (this == 0)
855 break;
856 fputs (this->text, output);
857 }
858 /* If we decided to add some sections here
859 because there are no such sections in the file,
860 output them now. */
861 if (dir_lines[i].add_sections_before)
862 {
863 struct spec_section *spec;
864 struct spec_section **sections;
865 int n_sections = 0;
866
867 /* Count the sections and allocate a vector for all of them. */
868 for (spec = input_sections; spec; spec = spec->next)
869 n_sections++;
870 sections = ((struct spec_section **)
871 xmalloc (n_sections * sizeof (struct spec_section *)));
872
873 /* Fill the vector SECTIONS with pointers to all the sections,
874 and sort them. */
875 j = 0;
876 for (spec = input_sections; spec; spec = spec->next)
877 sections[j++] = spec;
878 qsort (sections, n_sections, sizeof (struct spec_section *),
879 compare_section_names);
880
881 /* Generate the new sections in alphabetical order.
882 In each new section, output all of our entries. */
883 for (j = 0; j < n_sections; j++)
884 {
885 spec = sections[j];
886 if (spec->missing)
887 {
888 struct spec_entry *entry;
889
890 putc ('\n', output);
891 fputs (spec->name, output);
892 putc ('\n', output);
893 for (entry = entries_to_add; entry; entry = entry->next)
894 fputs (entry->text, output);
895 }
896 }
897
898 free (sections);
899 }
900
901 /* Output the original dir lines unless marked for deletion. */
902 if (i < dir_nlines && !dir_lines[i].delete)
903 {
904 fwrite (dir_lines[i].start, 1, dir_lines[i].size, output);
905 putc ('\n', output);
906 }
907 }
908
909 fclose (output);
910
911 exit (0);
912 }
913 \f
914 /* Read all of file FILNAME into memory
915 and return the address of the data.
916 Store the size into SIZEP.
917 If there is trouble, do a fatal error. */
918
919 char *
920 readfile (filename, sizep)
921 char *filename;
922 int *sizep;
923 {
924 int data_size = 1024;
925 char *data = (char *) xmalloc (data_size);
926 int filled = 0;
927 int nread = 0;
928
929 int desc = open (filename, O_RDONLY);
930
931 if (desc < 0)
932 pfatal_with_name (filename);
933
934 while (1)
935 {
936 nread = read (desc, data + filled, data_size - filled);
937 if (nread < 0)
938 pfatal_with_name (filename);
939 if (nread == 0)
940 break;
941
942 filled += nread;
943 if (filled == data_size)
944 {
945 data_size *= 2;
946 data = (char *) xrealloc (data, data_size);
947 }
948 }
949
950 *sizep = filled;
951 return data;
952 }
953 \f
954 /* Divide the text at DATA (of SIZE bytes) into lines.
955 Return a vector of struct line_data describing the lines.
956 Store the length of that vector into *NLINESP. */
957
958 struct line_data *
959 findlines (data, size, nlinesp)
960 char *data;
961 int size;
962 int *nlinesp;
963 {
964 struct line_data *lines;
965 int lines_allocated = 512;
966 int filled = 0;
967 int i = 0;
968 int lineflag;
969
970 lines = (struct line_data *) xmalloc (lines_allocated * sizeof (struct line_data));
971
972 lineflag = 1;
973 for (i = 0; i < size; i++)
974 {
975 if (lineflag)
976 {
977 if (filled == lines_allocated)
978 {
979 lines_allocated *= 2;
980 lines = (struct line_data *) xrealloc (lines, lines_allocated * sizeof (struct line_data));
981 }
982 lines[filled].start = &data[i];
983 lines[filled].add_entries_before = 0;
984 lines[filled].add_sections_before = 0;
985 lines[filled].delete = 0;
986 if (filled > 0)
987 lines[filled - 1].size
988 = lines[filled].start - lines[filled - 1].start - 1;
989 filled++;
990 }
991 lineflag = (data[i] == '\n');
992 }
993 if (filled > 0)
994 lines[filled - 1].size = &data[i] - lines[filled - 1].start - lineflag;
995
996 /* Do not leave garbage in the last element. */
997 lines[filled].start = NULL;
998 lines[filled].add_entries_before = NULL;
999 lines[filled].add_sections_before = 0;
1000 lines[filled].delete = 0;
1001 lines[filled].size = 0;
1002
1003 *nlinesp = filled;
1004 return lines;
1005 }
1006 \f
1007 /* Compare the menu item names in LINE1 (line length LEN1)
1008 and LINE2 (line length LEN2). Return 1 if the item name
1009 in LINE1 is less, 0 otherwise. */
1010
1011 int
1012 menu_line_lessp (line1, len1, line2, len2)
1013 char *line1;
1014 int len1;
1015 char *line2;
1016 int len2;
1017 {
1018 int minlen = (len1 < len2 ? len1 : len2);
1019 int i;
1020
1021 for (i = 0; i < minlen; i++)
1022 {
1023 /* If one item name is a prefix of the other,
1024 the former one is less. */
1025 if (line1[i] == ':' && line2[i] != ':')
1026 return 1;
1027 if (line2[i] == ':' && line1[i] != ':')
1028 return 0;
1029 /* If they both continue and differ, one is less. */
1030 if (line1[i] < line2[i])
1031 return 1;
1032 if (line1[i] > line2[i])
1033 return 0;
1034 }
1035 /* With a properly formatted dir file,
1036 we can only get here if the item names are equal. */
1037 return 0;
1038 }
1039
1040 /* Compare the menu item names in LINE1 (line length LEN1)
1041 and LINE2 (line length LEN2). Return 1 if the item names are equal,
1042 0 otherwise. */
1043
1044 int
1045 menu_line_equal (line1, len1, line2, len2)
1046 char *line1;
1047 int len1;
1048 char *line2;
1049 int len2;
1050 {
1051 int minlen = (len1 < len2 ? len1 : len2);
1052 int i;
1053
1054 for (i = 0; i < minlen; i++)
1055 {
1056 /* If both item names end here, they are equal. */
1057 if (line1[i] == ':' && line2[i] == ':')
1058 return 1;
1059 /* If they both continue and differ, one is less. */
1060 if (line1[i] != line2[i])
1061 return 0;
1062 }
1063 /* With a properly formatted dir file,
1064 we can only get here if the item names are equal. */
1065 return 1;
1066 }
1067 \f
1068 /* This is the comparison function for qsort
1069 for a vector of pointers to struct spec_section.
1070 Compare the section names. */
1071
1072 int
1073 compare_section_names (sec1, sec2)
1074 struct spec_section **sec1, **sec2;
1075 {
1076 char *name1 = (*sec1)->name;
1077 char *name2 = (*sec2)->name;
1078 return strcmp (name1, name2);
1079 }
1080
1081 /* Insert ENTRY into the add_entries_before vector
1082 for line number LINE_NUMBER of the dir file.
1083 DIR_LINES and N_ENTRIES carry information from like-named variables
1084 in main. */
1085
1086 void
1087 insert_entry_here (entry, line_number, dir_lines, n_entries)
1088 struct spec_entry *entry;
1089 int line_number;
1090 struct line_data *dir_lines;
1091 int n_entries;
1092 {
1093 int i;
1094
1095 if (dir_lines[line_number].add_entries_before == 0)
1096 {
1097 dir_lines[line_number].add_entries_before
1098 = (struct spec_entry **) xmalloc (n_entries * sizeof (struct spec_entry *));
1099 for (i = 0; i < n_entries; i++)
1100 dir_lines[line_number].add_entries_before[i] = 0;
1101 }
1102
1103 for (i = 0; i < n_entries; i++)
1104 if (dir_lines[line_number].add_entries_before[i] == 0)
1105 break;
1106
1107 if (i == n_entries)
1108 abort ();
1109
1110 dir_lines[line_number].add_entries_before[i] = entry;
1111 }