install-info.c revision 114478
1/* $FreeBSD: head/contrib/texinfo/util/install-info.c 114478 2003-05-02 00:50:59Z ru $ */
2/* install-info -- create Info directory entry(ies) for an Info file.
3   $Id: install-info.c,v 1.7 2003/01/19 18:46:51 karl Exp $
4
5   Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003 Free Software
6   Foundation, Inc.
7
8   This program is free software; you can redistribute it and/or modify
9   it under the terms of the GNU General Public License as published by
10   the Free Software Foundation; either version 2 of the License, or
11   (at your option) any later version.
12
13   This program is distributed in the hope that it will be useful,
14   but WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16   GNU General Public License for more details.
17
18   You should have received a copy of the GNU General Public License
19   along with this program; if not, write to the Free Software
20   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.*/
21
22#include "system.h"
23#include <getopt.h>
24
25static char *progname = "install-info";
26static char *default_section = NULL;
27
28struct line_data *findlines ();
29void insert_entry_here ();
30int compare_section_names (), compare_entries_text ();
31
32struct spec_entry;
33
34/* Data structures.  */
35
36
37/* Record info about a single line from a file as read into core.  */
38struct line_data
39{
40  /* The start of the line.  */
41  char *start;
42  /* The number of characters in the line,
43     excluding the terminating newline.  */
44  int size;
45  /* Vector containing pointers to the entries to add before this line.
46     The vector is null-terminated.  */
47  struct spec_entry **add_entries_before;
48  /* 1 means output any needed new sections before this line.  */
49  int add_sections_before;
50  /* 1 means don't output this line.  */
51  int delete;
52};
53
54
55/* This is used for a list of the specified menu section names
56   in which entries should be added.  */
57struct spec_section
58{
59  struct spec_section *next;
60  char *name;
61  /* 1 means we have not yet found an existing section with this name
62     in the dir file--so we will need to add a new section.  */
63  int missing;
64};
65
66
67/* This is used for a list of the entries specified to be added.  */
68struct spec_entry
69{
70  struct spec_entry *next;
71  char *text;
72  int text_len;
73  /* A pointer to the list of sections to which this entry should be
74     added.  */
75  struct spec_section *entry_sections;
76  /* A pointer to a section that is beyond the end of the chain whose
77     head is pointed to by entry_sections.  */
78  struct spec_section *entry_sections_tail;
79};
80
81
82/* This is used for a list of nodes found by parsing the dir file.  */
83struct node
84{
85  struct node *next;
86  /* The node name.  */
87  char *name;
88  /* The line number of the line where the node starts.
89     This is the line that contains control-underscore.  */
90  int start_line;
91  /* The line number of the line where the node ends,
92     which is the end of the file or where the next line starts.  */
93  int end_line;
94  /* Start of first line in this node's menu
95     (the line after the * Menu: line).  */
96  char *menu_start;
97  /* The start of the chain of sections in this node's menu.  */
98  struct menu_section *sections;
99  /* The last menu section in the chain.  */
100  struct menu_section *last_section;
101};
102
103
104/* This is used for a list of sections found in a node's menu.
105   Each  struct node  has such a list in the  sections  field.  */
106struct menu_section
107{
108  struct menu_section *next;
109  char *name;
110  /* Line number of start of section.  */
111  int start_line;
112  /* Line number of end of section.  */
113  int end_line;
114};
115
116/* This table defines all the long-named options, says whether they
117   use an argument, and maps them into equivalent single-letter options.  */
118
119struct option longopts[] =
120{
121  { "delete",    no_argument, NULL, 'r' },
122  { "defentry",  required_argument, NULL, 'E' },
123  { "defsection", required_argument, NULL, 'S' },
124  { "dir-file",  required_argument, NULL, 'd' },
125  { "entry",     required_argument, NULL, 'e' },
126  { "help",      no_argument, NULL, 'h' },
127  { "infodir",   required_argument, NULL, 'D' },
128  { "info-dir",  required_argument, NULL, 'D' },
129  { "info-file", required_argument, NULL, 'i' },
130  { "item",      required_argument, NULL, 'e' },
131  { "quiet",     no_argument, NULL, 'q' },
132  { "remove",    no_argument, NULL, 'r' },
133  { "section",   required_argument, NULL, 's' },
134  { "version",   no_argument, NULL, 'V' },
135  { 0 }
136};
137
138/* Error message functions.  */
139
140/* Print error message.  S1 is printf control string, S2 and S3 args for it. */
141
142/* VARARGS1 */
143void
144error (s1, s2, s3)
145     char *s1, *s2, *s3;
146{
147  fprintf (stderr, "%s: ", progname);
148  fprintf (stderr, s1, s2, s3);
149  putc ('\n', stderr);
150}
151
152/* VARARGS1 */
153void
154warning (s1, s2, s3)
155     char *s1, *s2, *s3;
156{
157  fprintf (stderr, _("%s: warning: "), progname);
158  fprintf (stderr, s1, s2, s3);
159  putc ('\n', stderr);
160}
161
162/* Print error message and exit.  */
163
164void
165fatal (s1, s2, s3)
166     char *s1, *s2, *s3;
167{
168  error (s1, s2, s3);
169  xexit (1);
170}
171
172/* Memory allocation and string operations.  */
173
174/* Like malloc but get fatal error if memory is exhausted.  */
175void *
176xmalloc (size)
177     unsigned int size;
178{
179  extern void *malloc ();
180  void *result = malloc (size);
181  if (result == NULL)
182    fatal (_("virtual memory exhausted"), 0, 0);
183  return result;
184}
185
186/* Like realloc but get fatal error if memory is exhausted.  */
187void *
188xrealloc (obj, size)
189     void *obj;
190     unsigned int size;
191{
192  extern void *realloc ();
193  void *result = realloc (obj, size);
194  if (result == NULL)
195    fatal (_("virtual memory exhausted"), 0, 0);
196  return result;
197}
198
199/* Return a newly-allocated string
200   whose contents concatenate those of S1, S2, S3.  */
201char *
202concat (s1, s2, s3)
203     char *s1, *s2, *s3;
204{
205  int len1 = strlen (s1), len2 = strlen (s2), len3 = strlen (s3);
206  char *result = (char *) xmalloc (len1 + len2 + len3 + 1);
207
208  strcpy (result, s1);
209  strcpy (result + len1, s2);
210  strcpy (result + len1 + len2, s3);
211  *(result + len1 + len2 + len3) = 0;
212
213  return result;
214}
215
216/* Return a string containing SIZE characters
217   copied from starting at STRING.  */
218
219char *
220copy_string (string, size)
221     char *string;
222     int size;
223{
224  int i;
225  char *copy = (char *) xmalloc (size + 1);
226  for (i = 0; i < size; i++)
227    copy[i] = string[i];
228  copy[size] = 0;
229  return copy;
230}
231
232/* Print fatal error message based on errno, with file name NAME.  */
233
234void
235pfatal_with_name (name)
236     char *name;
237{
238  char *s = concat ("", strerror (errno), _(" for %s"));
239  fatal (s, name, 0);
240}
241
242/* Given the full text of a menu entry, null terminated,
243   return just the menu item name (copied).  */
244
245char *
246extract_menu_item_name (item_text)
247     char *item_text;
248{
249  char *p;
250
251  if (*item_text == '*')
252    item_text++;
253  while (*item_text == ' ')
254    item_text++;
255
256  p = item_text;
257  while (*p && *p != ':') p++;
258  return copy_string (item_text, p - item_text);
259}
260
261/* Given the full text of a menu entry, terminated by null or newline,
262   return just the menu item file (copied).  */
263
264char *
265extract_menu_file_name (item_text)
266     char *item_text;
267{
268  char *p = item_text;
269
270  /* If we have text that looks like * ITEM: (FILE)NODE...,
271     extract just FILE.  Otherwise return "(none)".  */
272
273  if (*p == '*')
274    p++;
275  while (*p == ' ')
276    p++;
277
278  /* Skip to and past the colon.  */
279  while (*p && *p != '\n' && *p != ':') p++;
280  if (*p == ':') p++;
281
282  /* Skip past the open-paren.  */
283  while (1)
284    {
285      if (*p == '(')
286        break;
287      else if (*p == ' ' || *p == '\t')
288        p++;
289      else
290        return "(none)";
291    }
292  p++;
293
294  item_text = p;
295
296  /* File name ends just before the close-paren.  */
297  while (*p && *p != '\n' && *p != ')') p++;
298  if (*p != ')')
299    return "(none)";
300
301  return copy_string (item_text, p - item_text);
302}
303
304
305
306/* Return FNAME with any [.info][.gz] suffix removed.  */
307
308static char *
309strip_info_suffix (fname)
310     char *fname;
311{
312  char *ret = xstrdup (fname);
313  unsigned len = strlen (ret);
314
315  if (len > 3 && FILENAME_CMP (ret + len - 3, ".gz") == 0)
316    {
317      len -= 3;
318      ret[len] = 0;
319    }
320  else if (len > 4 && FILENAME_CMP (ret + len - 4, ".bz2") == 0)
321    {
322      len -= 4;
323      ret[len] = 0;
324    }
325
326  if (len > 5 && FILENAME_CMP (ret + len - 5, ".info") == 0)
327    {
328      len -= 5;
329      ret[len] = 0;
330    }
331  else if (len > 4 && FILENAME_CMP (ret + len - 4, ".inf") == 0)
332    {
333      len -= 4;
334      ret[len] = 0;
335    }
336#ifdef __MSDOS__
337  else if (len > 4 && (FILENAME_CMP (ret + len - 4, ".inz") == 0
338                       || FILENAME_CMP (ret + len - 4, ".igz") == 0))
339    {
340      len -= 4;
341      ret[len] = 0;
342    }
343#endif /* __MSDOS__ */
344
345  return ret;
346}
347
348
349/* Return true if ITEM matches NAME and is followed by TERM_CHAR.  ITEM
350   can also be followed by `.gz', `.info.gz', or `.info' (and then
351   TERM_CHAR) and still match.  */
352
353static int
354menu_item_equal (item, term_char, name)
355     char *item;
356     char term_char;
357     char *name;
358{
359  unsigned name_len = strlen (name);
360  /* First, ITEM must actually match NAME (usually it won't).  */
361  int ret = strncasecmp (item, name, name_len) == 0;
362  if (ret)
363    {
364      /* Then, `foobar' doesn't match `foo', so be sure we've got all of
365         ITEM.  The various suffixes should never actually appear in the
366         dir file, but sometimes people put them in.  */
367      static char *suffixes[]
368        = { "", ".info.gz", ".info", ".inf", ".gz",
369#ifdef __MSDOS__
370            ".inz", ".igz",
371#endif
372            NULL };
373      unsigned i;
374      ret = 0;
375      for (i = 0; !ret && suffixes[i]; i++)
376        {
377          char *suffix = suffixes[i];
378          unsigned suffix_len = strlen (suffix);
379          ret = strncasecmp (item + name_len, suffix, suffix_len) == 0
380                && item[name_len + suffix_len] == term_char;
381        }
382    }
383
384  return ret;
385}
386
387
388
389void
390suggest_asking_for_help ()
391{
392  fprintf (stderr, _("\tTry `%s --help' for a complete list of options.\n"),
393           progname);
394  xexit (1);
395}
396
397void
398print_help ()
399{
400  printf (_("Usage: %s [OPTION]... [INFO-FILE [DIR-FILE]]\n\
401\n\
402Install or delete dir entries from INFO-FILE in the Info directory file\n\
403DIR-FILE.\n\
404\n\
405Options:\n\
406 --delete          delete existing entries for INFO-FILE from DIR-FILE;\n\
407                     don't insert any new entries.\n\
408 --defentry=TEXT   like --entry, but only use TEXT if an entry\n\
409                     is not present in INFO-FILE.\n\
410 --defsection=TEXT like --section, but only use TEXT if a section\n\
411                     is not present in INFO-FILE.\n\
412 --dir-file=NAME   specify file name of Info directory file.\n\
413                     This is equivalent to using the DIR-FILE argument.\n\
414 --entry=TEXT      insert TEXT as an Info directory entry.\n\
415                     TEXT should have the form of an Info menu item line\n\
416                     plus zero or more extra lines starting with whitespace.\n\
417                     If you specify more than one entry, they are all added.\n\
418                     If you don't specify any entries, they are determined\n\
419                     from information in the Info file itself.\n\
420 --help            display this help and exit.\n\
421 --info-file=FILE  specify Info file to install in the directory.\n\
422                     This is equivalent to using the INFO-FILE argument.\n\
423 --info-dir=DIR    same as --dir-file=DIR/dir.\n\
424 --item=TEXT       same as --entry TEXT.\n\
425                     An Info directory entry is actually a menu item.\n\
426 --quiet           suppress warnings.\n\
427 --remove          same as --delete.\n\
428 --section=SEC     put this file's entries in section SEC of the directory.\n\
429                     If you specify more than one section, all the entries\n\
430                     are added in each of the sections.\n\
431                     If you don't specify any sections, they are determined\n\
432                     from information in the Info file itself.\n\
433 --version         display version information and exit.\n\
434"), progname);
435
436  puts (_("\n\
437Email bug reports to bug-texinfo@gnu.org,\n\
438general questions and discussion to help-texinfo@gnu.org.\n\
439Texinfo home page: http://www.gnu.org/software/texinfo/"));
440}
441
442
443/* If DIRFILE does not exist, create a minimal one (or abort).  If it
444   already exists, do nothing.  */
445
446void
447ensure_dirfile_exists (dirfile)
448     char *dirfile;
449{
450  int desc = open (dirfile, O_RDONLY);
451  if (desc < 0 && errno == ENOENT)
452    {
453      FILE *f;
454      char *readerr = strerror (errno);
455      close (desc);
456      f = fopen (dirfile, "w");
457      if (f)
458        {
459          fprintf (f, _("This is the file .../info/dir, which contains the\n\
460topmost node of the Info hierarchy, called (dir)Top.\n\
461The first time you invoke Info you start off looking at this node.\n\
462\n\
463%s\tThis is the top of the INFO tree\n\
464\n\
465  This (the Directory node) gives a menu of major topics.\n\
466  Typing \"q\" exits, \"?\" lists all Info commands, \"d\" returns here,\n\
467  \"h\" gives a primer for first-timers,\n\
468  \"mEmacs<Return>\" visits the Emacs manual, etc.\n\
469\n\
470  In Emacs, you can click mouse button 2 on a menu item or cross reference\n\
471  to select it.\n\
472\n\
473%s\n\
474"), "File: dir,\tNode: Top",  /* These keywords must not be translated.  */
475    "* Menu:"
476);
477          if (fclose (f) < 0)
478            pfatal_with_name (dirfile);
479        }
480      else
481        {
482          /* Didn't exist, but couldn't open for writing.  */
483          fprintf (stderr,
484                   _("%s: could not read (%s) and could not create (%s)\n"),
485                   dirfile, readerr, strerror (errno));
486          xexit (1);
487        }
488    }
489  else
490    close (desc); /* It already existed, so fine.  */
491}
492
493/* Open FILENAME and return the resulting stream pointer.  If it doesn't
494   exist, try FILENAME.gz.  If that doesn't exist either, call
495   CREATE_CALLBACK (with FILENAME as arg) to create it, if that is
496   non-NULL.  If still no luck, fatal error.
497
498   If we do open it, return the actual name of the file opened in
499   OPENED_FILENAME and the compress program to use to (de)compress it in
500   COMPRESSION_PROGRAM.  The compression program is determined by the
501   magic number, not the filename.  */
502
503FILE *
504open_possibly_compressed_file (filename, create_callback,
505                               opened_filename, compression_program, is_pipe)
506     char *filename;
507     void (*create_callback) ();
508     char **opened_filename;
509     char **compression_program;
510     int  *is_pipe;
511{
512  char *local_opened_filename, *local_compression_program;
513  int nread;
514  char data[4];
515  FILE *f;
516
517  /* We let them pass NULL if they don't want this info, but it's easier
518     to always determine it.  */
519  if (!opened_filename)
520    opened_filename = &local_opened_filename;
521
522  *opened_filename = filename;
523  f = fopen (*opened_filename, FOPEN_RBIN);
524  if (!f)
525    {
526      *opened_filename = concat (filename, ".gz", "");
527      f = fopen (*opened_filename, FOPEN_RBIN);
528  if (!f)
529    {
530      free (*opened_filename);
531      *opened_filename = concat (filename, ".bz2", "");
532      f = fopen (*opened_filename, FOPEN_RBIN);
533    }
534
535#ifdef __MSDOS__
536      if (!f)
537        {
538          free (*opened_filename);
539          *opened_filename = concat (filename, ".igz", "");
540          f = fopen (*opened_filename, FOPEN_RBIN);
541        }
542      if (!f)
543        {
544          free (*opened_filename);
545          *opened_filename = concat (filename, ".inz", "");
546          f = fopen (*opened_filename, FOPEN_RBIN);
547        }
548#endif
549      if (!f)
550        {
551          if (create_callback)
552            { /* That didn't work either.  Create the file if we can.  */
553              (*create_callback) (filename);
554
555              /* And try opening it again.  */
556              free (*opened_filename);
557              *opened_filename = filename;
558              f = fopen (*opened_filename, FOPEN_RBIN);
559              if (!f)
560                pfatal_with_name (filename);
561            }
562          else
563            pfatal_with_name (filename);
564        }
565    }
566
567  /* Read first few bytes of file rather than relying on the filename.
568     If the file is shorter than this it can't be usable anyway.  */
569  nread = fread (data, sizeof (data), 1, f);
570  if (nread != 1)
571    {
572      /* Empty files don't set errno, so we get something like
573         "install-info: No error for foo", which is confusing.  */
574      if (nread == 0)
575        fatal (_("%s: empty file"), *opened_filename, 0);
576      pfatal_with_name (*opened_filename);
577    }
578
579  if (!compression_program)
580    compression_program = &local_compression_program;
581
582  if (data[0] == '\x1f' && data[1] == '\x8b')
583#if STRIP_DOT_EXE
584    /* An explicit .exe yields a better diagnostics from popen below
585       if they don't have gzip installed.  */
586    *compression_program = "gzip.exe";
587#else
588    *compression_program = "gzip";
589#endif
590  else if(data[0] == 'B' && data[1] == 'Z' && data[2] == 'h')
591#ifndef STRIP_DOT_EXE
592    *compression_program = "bzip2.exe";
593#else
594    *compression_program = "bzip2";
595#endif
596  else if(data[0] == 'B' && data[1] == 'Z' && data[2] == '0')
597#ifndef STRIP_DOT_EXE
598    *compression_program = "bzip.exe";
599#else
600    *compression_program = "bzip";
601#endif
602  else
603    *compression_program = NULL;
604
605  if (*compression_program)
606    { /* It's compressed, so fclose the file and then open a pipe.  */
607      char *command = concat (*compression_program," -cd <", *opened_filename);
608      if (fclose (f) < 0)
609        pfatal_with_name (*opened_filename);
610      f = popen (command, "r");
611      if (f)
612        *is_pipe = 1;
613      else
614        pfatal_with_name (command);
615    }
616  else
617    { /* It's a plain file, seek back over the magic bytes.  */
618      if (fseek (f, 0, 0) < 0)
619        pfatal_with_name (*opened_filename);
620#if O_BINARY
621      /* Since this is a text file, and we opened it in binary mode,
622         switch back to text mode.  */
623      f = freopen (*opened_filename, "r", f);
624#endif
625      *is_pipe = 0;
626    }
627
628  return f;
629}
630
631/* Read all of file FILENAME into memory and return the address of the
632   data.  Store the size of the data into SIZEP.  If need be, uncompress
633   (i.e., try FILENAME.gz et al. if FILENAME does not exist) and store
634   the actual file name that was opened into OPENED_FILENAME (if it is
635   non-NULL), and the companion compression program (if any, else NULL)
636   into COMPRESSION_PROGRAM (if that is non-NULL).  If trouble, do
637   a fatal error.  */
638
639char *
640readfile (filename, sizep, create_callback,
641          opened_filename, compression_program)
642     char *filename;
643     int *sizep;
644     void (*create_callback) ();
645     char **opened_filename;
646     char **compression_program;
647{
648  char *real_name;
649  FILE *f;
650  int pipe_p;
651  int filled = 0;
652  int data_size = 8192;
653  char *data = xmalloc (data_size);
654
655  /* If they passed the space for the file name to return, use it.  */
656  f = open_possibly_compressed_file (filename, create_callback,
657                                     opened_filename ? opened_filename
658                                                     : &real_name,
659                                     compression_program, &pipe_p);
660
661  for (;;)
662    {
663      int nread = fread (data + filled, 1, data_size - filled, f);
664      if (nread < 0)
665        pfatal_with_name (real_name);
666      if (nread == 0)
667        break;
668
669      filled += nread;
670      if (filled == data_size)
671        {
672          data_size += 65536;
673          data = xrealloc (data, data_size);
674        }
675    }
676
677  /* We'll end up wasting space if we're not passing the filename back
678     and it is not just FILENAME, but so what.  */
679  /* We need to close the stream, since on some systems the pipe created
680     by popen is simulated by a temporary file which only gets removed
681     inside pclose.  */
682  if (pipe_p)
683    pclose (f);
684  else
685    fclose (f);
686
687  *sizep = filled;
688  return data;
689}
690
691/* Output the old dir file, interpolating the new sections
692   and/or new entries where appropriate.  If COMPRESSION_PROGRAM is not
693   null, pipe to it to create DIRFILE.  Thus if we read dir.gz on input,
694   we'll write dir.gz on output.  */
695
696static void
697output_dirfile (dirfile, dir_nlines, dir_lines,
698                n_entries_to_add, entries_to_add, input_sections,
699                compression_program)
700      char *dirfile;
701      int dir_nlines;
702      struct line_data *dir_lines;
703      int n_entries_to_add;
704      struct spec_entry *entries_to_add;
705      struct spec_section *input_sections;
706      char *compression_program;
707{
708  int i;
709  FILE *output;
710
711  if (compression_program)
712    {
713      char *command = concat (compression_program, ">", dirfile);
714      output = popen (command, "w");
715    }
716  else
717    output = fopen (dirfile, "w");
718
719  if (!output)
720    {
721      perror (dirfile);
722      xexit (1);
723    }
724
725  for (i = 0; i <= dir_nlines; i++)
726    {
727      int j;
728
729      /* If we decided to output some new entries before this line,
730         output them now.  */
731      if (dir_lines[i].add_entries_before)
732        for (j = 0; j < n_entries_to_add; j++)
733          {
734            struct spec_entry *this = dir_lines[i].add_entries_before[j];
735            if (this == 0)
736              break;
737            fputs (this->text, output);
738          }
739      /* If we decided to add some sections here
740         because there are no such sections in the file,
741         output them now.  */
742      if (dir_lines[i].add_sections_before)
743        {
744          struct spec_section *spec;
745          struct spec_section **sections;
746          int n_sections = 0;
747          struct spec_entry *entry;
748          struct spec_entry **entries;
749          int n_entries = 0;
750
751          /* Count the sections and allocate a vector for all of them.  */
752          for (spec = input_sections; spec; spec = spec->next)
753            n_sections++;
754          sections = ((struct spec_section **)
755                      xmalloc (n_sections * sizeof (struct spec_section *)));
756
757          /* Fill the vector SECTIONS with pointers to all the sections,
758             and sort them.  */
759          j = 0;
760          for (spec = input_sections; spec; spec = spec->next)
761            sections[j++] = spec;
762          qsort (sections, n_sections, sizeof (struct spec_section *),
763                 compare_section_names);
764
765          /* Count the entries and allocate a vector for all of them.  */
766          for (entry = entries_to_add; entry; entry = entry->next)
767            n_entries++;
768          entries = ((struct spec_entry **)
769                     xmalloc (n_entries * sizeof (struct spec_entry *)));
770
771          /* Fill the vector ENTRIES with pointers to all the sections,
772             and sort them.  */
773          j = 0;
774          for (entry = entries_to_add; entry; entry = entry->next)
775            entries[j++] = entry;
776          qsort (entries, n_entries, sizeof (struct spec_entry *),
777                 compare_entries_text);
778
779          /* Generate the new sections in alphabetical order.  In each
780             new section, output all of the entries that belong to that
781             section, in alphabetical order.  */
782          for (j = 0; j < n_sections; j++)
783            {
784              spec = sections[j];
785              if (spec->missing)
786                {
787                  int k;
788
789                  putc ('\n', output);
790                  fputs (spec->name, output);
791                  putc ('\n', output);
792                  for (k = 0; k < n_entries; k++)
793                    {
794                      struct spec_section *spec1;
795                      /* Did they at all want this entry to be put into
796                         this section?  */
797                      entry = entries[k];
798                      for (spec1 = entry->entry_sections;
799                           spec1 && spec1 != entry->entry_sections_tail;
800                           spec1 = spec1->next)
801                        {
802                          if (!strcmp (spec1->name, spec->name))
803                            break;
804                        }
805                      if (spec1 && spec1 != entry->entry_sections_tail)
806                        fputs (entry->text, output);
807                    }
808                }
809            }
810
811          free (entries);
812          free (sections);
813        }
814
815      /* Output the original dir lines unless marked for deletion.  */
816      if (i < dir_nlines && !dir_lines[i].delete)
817        {
818          fwrite (dir_lines[i].start, 1, dir_lines[i].size, output);
819          putc ('\n', output);
820        }
821    }
822
823  /* Some systems, such as MS-DOS, simulate pipes with temporary files.
824     On those systems, the compressor actually gets run inside pclose,
825     so we must call pclose.  */
826  if (compression_program)
827    pclose (output);
828  else
829    fclose (output);
830}
831
832/* Parse the input to find the section names and the entry names it
833   specifies.  Return the number of entries to add from this file.  */
834int
835parse_input (lines, nlines, sections, entries)
836     const struct line_data *lines;
837     int nlines;
838     struct spec_section **sections;
839     struct spec_entry **entries;
840{
841  int n_entries = 0;
842  int prefix_length = strlen ("INFO-DIR-SECTION ");
843  struct spec_section *head = *sections, *tail = NULL;
844  int reset_tail = 0;
845  char *start_of_this_entry = 0;
846  int ignore_sections = *sections != 0;
847  int ignore_entries  = *entries  != 0;
848
849  int i;
850
851  if (ignore_sections && ignore_entries)
852    return 0;
853
854  /* Loop here processing lines from the input file.  Each
855     INFO-DIR-SECTION entry is added to the SECTIONS linked list.
856     Each START-INFO-DIR-ENTRY block is added to the ENTRIES linked
857     list, and all its entries inherit the chain of SECTION entries
858     defined by the last group of INFO-DIR-SECTION entries we have
859     seen until that point.  */
860  for (i = 0; i < nlines; i++)
861    {
862      if (!ignore_sections
863          && !strncmp ("INFO-DIR-SECTION ", lines[i].start, prefix_length))
864        {
865          struct spec_section *next
866            = (struct spec_section *) xmalloc (sizeof (struct spec_section));
867          next->name = copy_string (lines[i].start + prefix_length,
868                                    lines[i].size - prefix_length);
869          next->next = *sections;
870          next->missing = 1;
871          if (reset_tail)
872            {
873              tail = *sections;
874              reset_tail = 0;
875            }
876          *sections = next;
877          head = *sections;
878        }
879      /* If entries were specified explicitly with command options,
880         ignore the entries in the input file.  */
881      else if (!ignore_entries)
882        {
883          if (!strncmp ("START-INFO-DIR-ENTRY", lines[i].start, lines[i].size)
884              && sizeof ("START-INFO-DIR-ENTRY") - 1 == lines[i].size)
885            {
886              if (!*sections)
887                {
888                  /* We found an entry, but didn't yet see any sections
889                     specified.  Default to section "Miscellaneous".  */
890                  *sections = (struct spec_section *)
891                    xmalloc (sizeof (struct spec_section));
892                  (*sections)->name =
893		    default_section ? default_section : "Miscellaneous";
894                  (*sections)->next = 0;
895                  (*sections)->missing = 1;
896                  head = *sections;
897                }
898              /* Next time we see INFO-DIR-SECTION, we will reset the
899                 tail pointer.  */
900              reset_tail = 1;
901
902              if (start_of_this_entry != 0)
903                fatal (_("START-INFO-DIR-ENTRY without matching END-INFO-DIR-ENTRY"), 0, 0);
904              start_of_this_entry = lines[i + 1].start;
905            }
906          else if (start_of_this_entry)
907            {
908              if ((!strncmp ("* ", lines[i].start, 2)
909                   && lines[i].start > start_of_this_entry)
910                  || (!strncmp ("END-INFO-DIR-ENTRY",
911                                lines[i].start, lines[i].size)
912                      && sizeof ("END-INFO-DIR-ENTRY") - 1 == lines[i].size))
913                {
914                  /* We found an end of this entry.  Allocate another
915                     entry, fill its data, and add it to the linked
916                     list.  */
917                  struct spec_entry *next
918                    = (struct spec_entry *) xmalloc (sizeof (struct spec_entry));
919                  next->text
920                    = copy_string (start_of_this_entry,
921                                   lines[i].start - start_of_this_entry);
922                  next->text_len = lines[i].start - start_of_this_entry;
923                  next->entry_sections = head;
924                  next->entry_sections_tail = tail;
925                  next->next = *entries;
926                  *entries = next;
927                  n_entries++;
928                  if (!strncmp ("END-INFO-DIR-ENTRY",
929                                lines[i].start, lines[i].size)
930                      && sizeof ("END-INFO-DIR-ENTRY") - 1 == lines[i].size)
931                    start_of_this_entry = 0;
932                  else
933                    start_of_this_entry = lines[i].start;
934                }
935              else if (!strncmp ("END-INFO-DIR-ENTRY",
936                                 lines[i].start, lines[i].size)
937                       && sizeof ("END-INFO-DIR-ENTRY") - 1 == lines[i].size)
938                fatal (_("END-INFO-DIR-ENTRY without matching START-INFO-DIR-ENTRY"), 0, 0);
939            }
940        }
941    }
942  if (start_of_this_entry != 0)
943    fatal (_("START-INFO-DIR-ENTRY without matching END-INFO-DIR-ENTRY"),
944           0, 0);
945
946  /* If we ignored the INFO-DIR-ENTRY directives, we need now go back
947     and plug the names of all the sections we found into every
948     element of the ENTRIES list.  */
949  if (ignore_entries && *entries)
950    {
951      struct spec_entry *entry;
952
953      for (entry = *entries; entry; entry = entry->next)
954        {
955          entry->entry_sections = head;
956          entry->entry_sections_tail = tail;
957        }
958    }
959
960  return n_entries;
961}
962
963/* Parse the dir file whose basename is BASE_NAME.  Find all the
964   nodes, and their menus, and the sections of their menus.  */
965int
966parse_dir_file (lines, nlines, nodes, base_name)
967     struct line_data *lines;
968     int nlines;
969     struct node **nodes;
970     const char *base_name;
971{
972  int node_header_flag = 0;
973  int something_deleted = 0;
974  int i;
975
976  *nodes = 0;
977  for (i = 0; i < nlines; i++)
978    {
979      /* Parse node header lines.  */
980      if (node_header_flag)
981        {
982          int j, end;
983          for (j = 0; j < lines[i].size; j++)
984            /* Find the node name and store it in the `struct node'.  */
985            if (!strncmp ("Node:", lines[i].start + j, 5))
986              {
987                char *line = lines[i].start;
988                /* Find the start of the node name.  */
989                j += 5;
990                while (line[j] == ' ' || line[j] == '\t')
991                  j++;
992                /* Find the end of the node name.  */
993                end = j;
994                while (line[end] != 0 && line[end] != ',' && line[end] != '\n'
995                       && line[end] != '\t')
996                  end++;
997                (*nodes)->name = copy_string (line + j, end - j);
998              }
999          node_header_flag = 0;
1000        }
1001
1002      /* Notice the start of a node.  */
1003      if (*lines[i].start == 037)
1004        {
1005          struct node *next = (struct node *) xmalloc (sizeof (struct node));
1006
1007          next->next = *nodes;
1008          next->name = NULL;
1009          next->start_line = i;
1010          next->end_line = 0;
1011          next->menu_start = NULL;
1012          next->sections = NULL;
1013          next->last_section = NULL;
1014
1015          if (*nodes != 0)
1016            (*nodes)->end_line = i;
1017          /* Fill in the end of the last menu section
1018             of the previous node.  */
1019          if (*nodes != 0 && (*nodes)->last_section != 0)
1020            (*nodes)->last_section->end_line = i;
1021
1022          *nodes = next;
1023
1024          /* The following line is the header of this node;
1025             parse it.  */
1026          node_header_flag = 1;
1027        }
1028
1029      /* Notice the lines that start menus.  */
1030      if (*nodes != 0 && !strncmp ("* Menu:", lines[i].start, 7))
1031        (*nodes)->menu_start = lines[i + 1].start;
1032
1033      /* Notice sections in menus.  */
1034      if (*nodes != 0
1035          && (*nodes)->menu_start != 0
1036          && *lines[i].start != '\n'
1037          && *lines[i].start != '*'
1038          && *lines[i].start != ' '
1039          && *lines[i].start != '\t')
1040        {
1041          /* Add this menu section to the node's list.
1042             This list grows in forward order.  */
1043          struct menu_section *next
1044            = (struct menu_section *) xmalloc (sizeof (struct menu_section));
1045
1046          next->start_line = i + 1;
1047          next->next = 0;
1048          next->end_line = 0;
1049          next->name = copy_string (lines[i].start, lines[i].size);
1050          if ((*nodes)->sections)
1051            {
1052              (*nodes)->last_section->next = next;
1053              (*nodes)->last_section->end_line = i;
1054            }
1055          else
1056            (*nodes)->sections = next;
1057          (*nodes)->last_section = next;
1058        }
1059
1060      /* Check for an existing entry that should be deleted.
1061         Delete all entries which specify this file name.  */
1062      if (*lines[i].start == '*')
1063        {
1064          char *q;
1065          char *p = lines[i].start;
1066
1067          p++; /* skip * */
1068          while (*p == ' ') p++; /* ignore following spaces */
1069          q = p; /* remember this, it's the beginning of the menu item.  */
1070
1071          /* Read menu item.  */
1072          while (*p != 0 && *p != ':')
1073            p++;
1074          p++; /* skip : */
1075
1076          if (*p == ':')
1077            { /* XEmacs-style entry, as in * Mew::Messaging.  */
1078              if (menu_item_equal (q, ':', base_name))
1079                {
1080                  lines[i].delete = 1;
1081                  something_deleted = 1;
1082                }
1083            }
1084          else
1085            { /* Emacs-style entry, as in * Emacs: (emacs).  */
1086              while (*p == ' ') p++; /* skip spaces after : */
1087              if (*p == '(')         /* if at parenthesized (FILENAME) */
1088                {
1089                  p++;
1090                  if (menu_item_equal (p, ')', base_name))
1091                    {
1092                      lines[i].delete = 1;
1093                      something_deleted = 1;
1094                    }
1095                }
1096            }
1097        }
1098
1099      /* Treat lines that start with whitespace
1100         as continuations; if we are deleting an entry,
1101         delete all its continuations as well.  */
1102      else if (i > 0 && (*lines[i].start == ' ' || *lines[i].start == '\t'))
1103        {
1104          lines[i].delete = lines[i - 1].delete;
1105        }
1106    }
1107
1108  /* Finish the info about the end of the last node.  */
1109  if (*nodes != 0)
1110    {
1111      (*nodes)->end_line = nlines;
1112      if ((*nodes)->last_section != 0)
1113        (*nodes)->last_section->end_line = nlines;
1114    }
1115
1116  return something_deleted;
1117}
1118
1119int
1120main (argc, argv)
1121     int argc;
1122     char **argv;
1123{
1124  char *opened_dirfilename;
1125  char *compression_program;
1126  char *infile_sans_info;
1127  char *infile = 0, *dirfile = 0;
1128  unsigned infilelen_sans_info;
1129
1130  /* Record the text of the Info file, as a sequence of characters
1131     and as a sequence of lines.  */
1132  char *input_data = NULL;
1133  int input_size = 0;
1134  struct line_data *input_lines = NULL;
1135  int input_nlines = 0;
1136
1137  /* Record here the specified section names and directory entries.  */
1138  struct spec_section *input_sections = NULL;
1139  struct spec_entry *entries_to_add = NULL;
1140  int n_entries_to_add = 0;
1141  struct spec_entry *default_entries_to_add = NULL;
1142  int n_default_entries_to_add = 0;
1143
1144  /* Record the old text of the dir file, as plain characters,
1145     as lines, and as nodes.  */
1146  char *dir_data;
1147  int dir_size;
1148  int dir_nlines;
1149  struct line_data *dir_lines;
1150  struct node *dir_nodes;
1151
1152  /* Nonzero means --delete was specified (just delete existing entries).  */
1153  int delete_flag = 0;
1154  int something_deleted = 0;
1155  /* Nonzero means -q was specified.  */
1156  int quiet_flag = 0;
1157
1158  int i;
1159
1160#ifdef HAVE_SETLOCALE
1161  /* Set locale via LC_ALL.  */
1162  setlocale (LC_ALL, "");
1163#endif
1164
1165  /* Set the text message domain.  */
1166  bindtextdomain (PACKAGE, LOCALEDIR);
1167  textdomain (PACKAGE);
1168
1169  while (1)
1170    {
1171      int opt = getopt_long (argc, argv, "i:d:e:s:hHr", longopts, 0);
1172
1173      if (opt == EOF)
1174        break;
1175
1176      switch (opt)
1177        {
1178        case 0:
1179          /* If getopt returns 0, then it has already processed a
1180             long-named option.  We should do nothing.  */
1181          break;
1182
1183        case 1:
1184          abort ();
1185
1186        case 'd':
1187          if (dirfile)
1188            {
1189              fprintf (stderr, _("%s: Specify the Info directory only once.\n"),
1190                       progname);
1191              suggest_asking_for_help ();
1192            }
1193          dirfile = optarg;
1194          break;
1195
1196        case 'D':
1197          if (dirfile)
1198            {
1199              fprintf (stderr, _("%s: Specify the Info directory only once.\n"),
1200                       progname);
1201              suggest_asking_for_help ();
1202            }
1203          dirfile = concat (optarg, "", "/dir");
1204          break;
1205
1206	case 'E':
1207        case 'e':
1208          {
1209            struct spec_entry *next
1210              = (struct spec_entry *) xmalloc (sizeof (struct spec_entry));
1211            int olen = strlen (optarg);
1212            if (! (*optarg != 0 && optarg[olen - 1] == '\n'))
1213              {
1214                optarg = concat (optarg, "\n", "");
1215                olen++;
1216              }
1217            next->text = optarg;
1218            next->text_len = olen;
1219            next->entry_sections = NULL;
1220            next->entry_sections_tail = NULL;
1221	    if (opt == 'e')
1222	      {
1223		next->next = entries_to_add;
1224		entries_to_add = next;
1225		n_entries_to_add++;
1226	      }
1227	    else
1228	      {
1229		next->next = default_entries_to_add;
1230		default_entries_to_add = next;
1231		n_default_entries_to_add++;
1232	      }
1233          }
1234          break;
1235
1236        case 'h':
1237        case 'H':
1238          print_help ();
1239          xexit (0);
1240
1241        case 'i':
1242          if (infile)
1243            {
1244              fprintf (stderr, _("%s: Specify the Info file only once.\n"),
1245                       progname);
1246              suggest_asking_for_help ();
1247            }
1248          infile = optarg;
1249          break;
1250
1251        case 'q':
1252          quiet_flag = 1;
1253          break;
1254
1255        case 'r':
1256          delete_flag = 1;
1257          break;
1258
1259        case 's':
1260          {
1261            struct spec_section *next
1262              = (struct spec_section *) xmalloc (sizeof (struct spec_section));
1263            next->name = optarg;
1264            next->next = input_sections;
1265            next->missing = 1;
1266            input_sections = next;
1267          }
1268          break;
1269
1270	case 'S':
1271	  default_section = optarg;
1272	  break;
1273
1274        case 'V':
1275          printf ("install-info (GNU %s) %s\n", PACKAGE, VERSION);
1276          puts ("");
1277          printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\
1278There is NO warranty.  You may redistribute this software\n\
1279under the terms of the GNU General Public License.\n\
1280For more information about these matters, see the files named COPYING.\n"),
1281                  "2003");
1282          xexit (0);
1283
1284        default:
1285          suggest_asking_for_help ();
1286        }
1287    }
1288
1289  /* Interpret the non-option arguments as file names.  */
1290  for (; optind < argc; ++optind)
1291    {
1292      if (infile == 0)
1293        infile = argv[optind];
1294      else if (dirfile == 0)
1295        dirfile = argv[optind];
1296      else
1297        error (_("excess command line argument `%s'"), argv[optind], 0);
1298    }
1299
1300  if (!infile)
1301    fatal (_("No input file specified; try --help for more information."),
1302           0, 0);
1303  if (!dirfile)
1304    fatal (_("No dir file specified; try --help for more information."), 0, 0);
1305
1306  /* Read the Info file and parse it into lines, unless we're deleting.  */
1307  if (!delete_flag)
1308    {
1309      input_data = readfile (infile, &input_size, NULL, NULL, NULL);
1310      input_lines = findlines (input_data, input_size, &input_nlines);
1311    }
1312
1313  i = parse_input (input_lines, input_nlines,
1314                   &input_sections, &entries_to_add);
1315  if (i > n_entries_to_add)
1316    n_entries_to_add = i;
1317  else if (n_entries_to_add == 0)
1318    {
1319      entries_to_add = default_entries_to_add;
1320      n_entries_to_add = n_default_entries_to_add;
1321    }
1322
1323  if (!delete_flag)
1324    {
1325      if (entries_to_add == 0)
1326        { /* No need to abort here, the original info file may not
1327             have the requisite Texinfo commands.  This is not
1328             something an installer should have to correct (it's a
1329             problem for the maintainer), and there's no need to cause
1330             subsequent parts of `make install' to fail.  */
1331          warning (_("no info dir entry in `%s'"), infile, 0);
1332          xexit (0);
1333        }
1334
1335      /* If the entries came from the command-line arguments, their
1336         entry_sections pointers are not yet set.  Walk the chain of
1337         the entries and for each entry update entry_sections to point
1338         to the head of the list of sections where this entry should
1339         be put.  Note that all the entries specified on the command
1340         line get put into ALL the sections we've got, either from the
1341         Info file, or (under --section) from the command line,
1342         because in the loop below every entry inherits the entire
1343         chain of sections.  */
1344      if (n_entries_to_add > 0 && entries_to_add->entry_sections == NULL)
1345        {
1346          struct spec_entry *ep;
1347
1348          /* If we got no sections, default to "Miscellaneous".  */
1349          if (input_sections == NULL)
1350            {
1351              input_sections = (struct spec_section *)
1352                xmalloc (sizeof (struct spec_section));
1353              input_sections->name =
1354		default_section ? default_section : "Miscellaneous";
1355              input_sections->next = NULL;
1356              input_sections->missing = 1;
1357            }
1358          for (ep = entries_to_add; ep; ep = ep->next)
1359            ep->entry_sections = input_sections;
1360        }
1361    }
1362
1363  /* Now read in the Info dir file.  */
1364  dir_data = readfile (dirfile, &dir_size, ensure_dirfile_exists,
1365                       &opened_dirfilename, &compression_program);
1366  dir_lines = findlines (dir_data, dir_size, &dir_nlines);
1367
1368  /* We will be comparing the entries in the dir file against the
1369     current filename, so need to strip off any directory prefix and/or
1370     [.info][.gz] suffix.  */
1371  {
1372    char *infile_basename = infile + strlen (infile);
1373
1374    if (HAVE_DRIVE (infile))
1375      infile += 2;      /* get past the drive spec X: */
1376
1377    while (infile_basename > infile && !IS_SLASH (infile_basename[-1]))
1378      infile_basename--;
1379
1380    infile_sans_info = strip_info_suffix (infile_basename);
1381    infilelen_sans_info = strlen (infile_sans_info);
1382  }
1383
1384  something_deleted
1385    = parse_dir_file (dir_lines, dir_nlines, &dir_nodes, infile_sans_info);
1386
1387  /* Decide where to add the new entries (unless --delete was used).
1388     Find the menu sections to add them in.
1389     In each section, find the proper alphabetical place to add
1390     each of the entries.  */
1391
1392  if (!delete_flag)
1393    {
1394      struct node *node;
1395      struct menu_section *section;
1396      struct spec_section *spec;
1397
1398      for (node = dir_nodes; node; node = node->next)
1399        for (section = node->sections; section; section = section->next)
1400          {
1401            for (i = section->end_line; i > section->start_line; i--)
1402              if (dir_lines[i - 1].size != 0)
1403                break;
1404            section->end_line = i;
1405
1406            for (spec = input_sections; spec; spec = spec->next)
1407              if (!strcmp (spec->name, section->name))
1408                break;
1409            if (spec)
1410              {
1411                int add_at_line = section->end_line;
1412                struct spec_entry *entry;
1413                /* Say we have found at least one section with this name,
1414                   so we need not add such a section.  */
1415                spec->missing = 0;
1416                /* For each entry, find the right place in this section
1417                   to add it.  */
1418                for (entry = entries_to_add; entry; entry = entry->next)
1419                  {
1420                    /* Did they at all want this entry to be put into
1421                       this section?  */
1422                    for (spec = entry->entry_sections;
1423                         spec && spec != entry->entry_sections_tail;
1424                         spec = spec->next)
1425                      {
1426                        if (!strcmp (spec->name, section->name))
1427                          break;
1428                      }
1429                    if (!spec || spec == entry->entry_sections_tail)
1430                      continue;
1431
1432                    /* Subtract one because dir_lines is zero-based,
1433                       but the `end_line' and `start_line' members are
1434                       one-based.  */
1435                    for (i = section->end_line - 1;
1436                         i >= section->start_line - 1; i--)
1437                      {
1438                        /* If an entry exists with the same name,
1439                           and was not marked for deletion
1440                           (which means it is for some other file),
1441                           we are in trouble.  */
1442                        if (dir_lines[i].start[0] == '*'
1443                            && menu_line_equal (entry->text, entry->text_len,
1444                                                dir_lines[i].start,
1445                                                dir_lines[i].size)
1446                            && !dir_lines[i].delete)
1447			  {
1448			    if (quiet_flag)
1449			      dir_lines[i].delete = 1;
1450			    else
1451			      fatal (_("menu item `%s' already exists, for file `%s'"),
1452                                 extract_menu_item_name (entry->text),
1453                                 extract_menu_file_name (dir_lines[i].start));
1454			  }
1455                        if (dir_lines[i].start[0] == '*'
1456                            && menu_line_lessp (entry->text, entry->text_len,
1457                                                dir_lines[i].start,
1458                                                dir_lines[i].size))
1459                          add_at_line = i;
1460                      }
1461                    insert_entry_here (entry, add_at_line,
1462                                       dir_lines, n_entries_to_add);
1463                  }
1464              }
1465          }
1466
1467      /* Mark the end of the Top node as the place to add any
1468         new sections that are needed.  */
1469      for (node = dir_nodes; node; node = node->next)
1470        if (node->name && strcmp (node->name, "Top") == 0)
1471          dir_lines[node->end_line].add_sections_before = 1;
1472    }
1473
1474  if (delete_flag && !something_deleted && !quiet_flag)
1475    warning (_("no entries found for `%s'; nothing deleted"), infile, 0);
1476
1477  output_dirfile (opened_dirfilename, dir_nlines, dir_lines, n_entries_to_add,
1478                  entries_to_add, input_sections, compression_program);
1479
1480  xexit (0);
1481}
1482
1483/* Divide the text at DATA (of SIZE bytes) into lines.
1484   Return a vector of struct line_data describing the lines.
1485   Store the length of that vector into *NLINESP.  */
1486
1487struct line_data *
1488findlines (data, size, nlinesp)
1489     char *data;
1490     int size;
1491     int *nlinesp;
1492{
1493  int i;
1494  int lineflag = 1;
1495  int lines_allocated = 511;
1496  int filled = 0;
1497  struct line_data *lines
1498    = xmalloc ((lines_allocated + 1) * sizeof (struct line_data));
1499
1500  for (i = 0; i < size; i++)
1501    {
1502      if (lineflag)
1503        {
1504          if (filled == lines_allocated)
1505            {
1506              /* try to keep things somewhat page-aligned */
1507              lines_allocated = ((lines_allocated + 1) * 2) - 1;
1508              lines = xrealloc (lines, (lines_allocated + 1)
1509                                       * sizeof (struct line_data));
1510            }
1511          lines[filled].start = &data[i];
1512          lines[filled].add_entries_before = 0;
1513          lines[filled].add_sections_before = 0;
1514          lines[filled].delete = 0;
1515          if (filled > 0)
1516            lines[filled - 1].size
1517              = lines[filled].start - lines[filled - 1].start - 1;
1518          filled++;
1519        }
1520      lineflag = (data[i] == '\n');
1521    }
1522  if (filled > 0)
1523    lines[filled - 1].size = &data[i] - lines[filled - 1].start - lineflag;
1524
1525  /* Do not leave garbage in the last element.  */
1526  lines[filled].start = NULL;
1527  lines[filled].add_entries_before = NULL;
1528  lines[filled].add_sections_before = 0;
1529  lines[filled].delete = 0;
1530  lines[filled].size = 0;
1531
1532  *nlinesp = filled;
1533  return lines;
1534}
1535
1536/* Compare the menu item names in LINE1 (line length LEN1)
1537   and LINE2 (line length LEN2).  Return 1 if the item name
1538   in LINE1 is less, 0 otherwise.  */
1539
1540int
1541menu_line_lessp (line1, len1, line2, len2)
1542     char *line1;
1543     int len1;
1544     char *line2;
1545     int len2;
1546{
1547  int minlen = (len1 < len2 ? len1 : len2);
1548  int i;
1549
1550  for (i = 0; i < minlen; i++)
1551    {
1552      /* If one item name is a prefix of the other,
1553         the former one is less.  */
1554      if (line1[i] == ':' && line2[i] != ':')
1555        return 1;
1556      if (line2[i] == ':' && line1[i] != ':')
1557        return 0;
1558      /* If they both continue and differ, one is less.  */
1559      if (line1[i] < line2[i])
1560        return 1;
1561      if (line1[i] > line2[i])
1562        return 0;
1563    }
1564  /* With a properly formatted dir file,
1565     we can only get here if the item names are equal.  */
1566  return 0;
1567}
1568
1569/* Compare the menu item names in LINE1 (line length LEN1)
1570   and LINE2 (line length LEN2).  Return 1 if the item names are equal,
1571   0 otherwise.  */
1572
1573int
1574menu_line_equal (line1, len1, line2, len2)
1575     char *line1;
1576     int len1;
1577     char *line2;
1578     int len2;
1579{
1580  int minlen = (len1 < len2 ? len1 : len2);
1581  int i;
1582
1583  for (i = 0; i < minlen; i++)
1584    {
1585      /* If both item names end here, they are equal.  */
1586      if (line1[i] == ':' && line2[i] == ':')
1587        return 1;
1588      /* If they both continue and differ, one is less.  */
1589      if (line1[i] != line2[i])
1590        return 0;
1591    }
1592  /* With a properly formatted dir file,
1593     we can only get here if the item names are equal.  */
1594  return 1;
1595}
1596
1597/* This is the comparison function for qsort
1598   for a vector of pointers to struct spec_section.
1599   Compare the section names.  */
1600
1601int
1602compare_section_names (sec1, sec2)
1603     struct spec_section **sec1, **sec2;
1604{
1605  char *name1 = (*sec1)->name;
1606  char *name2 = (*sec2)->name;
1607  return strcmp (name1, name2);
1608}
1609
1610/* This is the comparison function for qsort
1611   for a vector of pointers to struct spec_entry.
1612   Compare the entries' text.  */
1613
1614int
1615compare_entries_text (entry1, entry2)
1616     struct spec_entry **entry1, **entry2;
1617{
1618  char *text1 = (*entry1)->text;
1619  char *text2 = (*entry2)->text;
1620  char *colon1 = strchr (text1, ':');
1621  char *colon2 = strchr (text2, ':');
1622  int len1, len2;
1623
1624  if (!colon1)
1625    len1 = strlen (text1);
1626  else
1627    len1 = colon1 - text1;
1628  if (!colon2)
1629    len2 = strlen (text2);
1630  else
1631    len2 = colon2 - text2;
1632  return strncmp (text1, text2, len1 <= len2 ? len1 : len2);
1633}
1634
1635/* Insert ENTRY into the add_entries_before vector
1636   for line number LINE_NUMBER of the dir file.
1637   DIR_LINES and N_ENTRIES carry information from like-named variables
1638   in main.  */
1639
1640void
1641insert_entry_here (entry, line_number, dir_lines, n_entries)
1642     struct spec_entry *entry;
1643     int line_number;
1644     struct line_data *dir_lines;
1645     int n_entries;
1646{
1647  int i, j;
1648
1649  if (dir_lines[line_number].add_entries_before == 0)
1650    {
1651      dir_lines[line_number].add_entries_before
1652        = (struct spec_entry **) xmalloc (n_entries * sizeof (struct spec_entry *));
1653      for (i = 0; i < n_entries; i++)
1654        dir_lines[line_number].add_entries_before[i] = 0;
1655    }
1656
1657  /* Find the place where this entry belongs.  If there are already
1658     several entries to add before LINE_NUMBER, make sure they are in
1659     alphabetical order.  */
1660  for (i = 0; i < n_entries; i++)
1661    if (dir_lines[line_number].add_entries_before[i] == 0
1662        || menu_line_lessp (entry->text, strlen (entry->text),
1663                            dir_lines[line_number].add_entries_before[i]->text,
1664                            strlen (dir_lines[line_number].add_entries_before[i]->text)))
1665      break;
1666
1667  if (i == n_entries)
1668    abort ();
1669
1670  /* If we need to plug ENTRY into the middle of the
1671     ADD_ENTRIES_BEFORE array, move the entries which should be output
1672     after this one down one notch, before adding a new one.  */
1673  if (dir_lines[line_number].add_entries_before[i] != 0)
1674    for (j = n_entries - 1; j > i; j--)
1675      dir_lines[line_number].add_entries_before[j]
1676        = dir_lines[line_number].add_entries_before[j - 1];
1677
1678  dir_lines[line_number].add_entries_before[i] = entry;
1679}
1680