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