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.1.1.1 1996/12/15 21:39:32 downsj 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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 }
1112