193143Sru/* $FreeBSD$ */
221495Sjmacd/* install-info -- create Info directory entry(ies) for an Info file.
3146521Sru   $Id: install-info.c,v 1.12 2004/04/11 17:56:47 karl Exp $
421495Sjmacd
5146521Sru   Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004 Free Software
6114478Sru   Foundation, Inc.
721495Sjmacd
842664Smarkm   This program is free software; you can redistribute it and/or modify
942664Smarkm   it under the terms of the GNU General Public License as published by
1042664Smarkm   the Free Software Foundation; either version 2 of the License, or
1142664Smarkm   (at your option) any later version.
1221495Sjmacd
1342664Smarkm   This program is distributed in the hope that it will be useful,
1442664Smarkm   but WITHOUT ANY WARRANTY; without even the implied warranty of
1542664Smarkm   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
1642664Smarkm   GNU General Public License for more details.
1721495Sjmacd
1842664Smarkm   You should have received a copy of the GNU General Public License
1942664Smarkm   along with this program; if not, write to the Free Software
2042664Smarkm   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.*/
2121495Sjmacd
2242664Smarkm#include "system.h"
2321495Sjmacd#include <getopt.h>
2421495Sjmacd
2556165Srustatic char *progname = "install-info";
2656165Srustatic char *default_section = NULL;
2721495Sjmacd
28146521Srustruct spec_entry;
29146521Srustruct spec_section;
3021495Sjmacd
31146521Srustruct line_data *findlines (char *data, int size, int *nlinesp);
32146521Sruvoid insert_entry_here (struct spec_entry *entry, int line_number,
33146521Sru                        struct line_data *dir_lines, int n_entries);
34146521Sruint compare_section_names (const void *s1, const void *s2);
35146521Sruint compare_entries_text (const void *e1, const void *e2);
3621495Sjmacd
3721495Sjmacd/* Data structures.  */
3821495Sjmacd
3921495Sjmacd
4042664Smarkm/* Record info about a single line from a file as read into core.  */
4121495Sjmacdstruct line_data
4221495Sjmacd{
4321495Sjmacd  /* The start of the line.  */
4421495Sjmacd  char *start;
4521495Sjmacd  /* The number of characters in the line,
4621495Sjmacd     excluding the terminating newline.  */
4721495Sjmacd  int size;
4821495Sjmacd  /* Vector containing pointers to the entries to add before this line.
4921495Sjmacd     The vector is null-terminated.  */
5021495Sjmacd  struct spec_entry **add_entries_before;
5121495Sjmacd  /* 1 means output any needed new sections before this line.  */
5221495Sjmacd  int add_sections_before;
5321495Sjmacd  /* 1 means don't output this line.  */
5421495Sjmacd  int delete;
5521495Sjmacd};
5621495Sjmacd
5742664Smarkm
5821495Sjmacd/* This is used for a list of the specified menu section names
5921495Sjmacd   in which entries should be added.  */
6021495Sjmacdstruct spec_section
6121495Sjmacd{
6221495Sjmacd  struct spec_section *next;
6321495Sjmacd  char *name;
6421495Sjmacd  /* 1 means we have not yet found an existing section with this name
6521495Sjmacd     in the dir file--so we will need to add a new section.  */
6621495Sjmacd  int missing;
6721495Sjmacd};
6821495Sjmacd
6942664Smarkm
7021495Sjmacd/* This is used for a list of the entries specified to be added.  */
7121495Sjmacdstruct spec_entry
7221495Sjmacd{
7321495Sjmacd  struct spec_entry *next;
7421495Sjmacd  char *text;
7556165Sru  int text_len;
7656165Sru  /* A pointer to the list of sections to which this entry should be
7756165Sru     added.  */
7856165Sru  struct spec_section *entry_sections;
7956165Sru  /* A pointer to a section that is beyond the end of the chain whose
8056165Sru     head is pointed to by entry_sections.  */
8156165Sru  struct spec_section *entry_sections_tail;
8221495Sjmacd};
8342664Smarkm
8442664Smarkm
8521495Sjmacd/* This is used for a list of nodes found by parsing the dir file.  */
8621495Sjmacdstruct node
8721495Sjmacd{
8821495Sjmacd  struct node *next;
8921495Sjmacd  /* The node name.  */
9021495Sjmacd  char *name;
9121495Sjmacd  /* The line number of the line where the node starts.
9221495Sjmacd     This is the line that contains control-underscore.  */
9321495Sjmacd  int start_line;
9421495Sjmacd  /* The line number of the line where the node ends,
9521495Sjmacd     which is the end of the file or where the next line starts.  */
9621495Sjmacd  int end_line;
9721495Sjmacd  /* Start of first line in this node's menu
9821495Sjmacd     (the line after the * Menu: line).  */
9921495Sjmacd  char *menu_start;
10021495Sjmacd  /* The start of the chain of sections in this node's menu.  */
10121495Sjmacd  struct menu_section *sections;
10221495Sjmacd  /* The last menu section in the chain.  */
10321495Sjmacd  struct menu_section *last_section;
10421495Sjmacd};
10521495Sjmacd
10642664Smarkm
10721495Sjmacd/* This is used for a list of sections found in a node's menu.
10821495Sjmacd   Each  struct node  has such a list in the  sections  field.  */
10921495Sjmacdstruct menu_section
11021495Sjmacd{
11121495Sjmacd  struct menu_section *next;
11221495Sjmacd  char *name;
11321495Sjmacd  /* Line number of start of section.  */
11421495Sjmacd  int start_line;
11521495Sjmacd  /* Line number of end of section.  */
11621495Sjmacd  int end_line;
11721495Sjmacd};
11821495Sjmacd
11956165Sru/* This table defines all the long-named options, says whether they
12056165Sru   use an argument, and maps them into equivalent single-letter options.  */
12156165Sru
12256165Srustruct option longopts[] =
12356165Sru{
12456165Sru  { "delete",    no_argument, NULL, 'r' },
12556165Sru  { "defentry",  required_argument, NULL, 'E' },
12656165Sru  { "defsection", required_argument, NULL, 'S' },
12756165Sru  { "dir-file",  required_argument, NULL, 'd' },
12856165Sru  { "entry",     required_argument, NULL, 'e' },
12956165Sru  { "help",      no_argument, NULL, 'h' },
130114478Sru  { "infodir",   required_argument, NULL, 'D' },
13156165Sru  { "info-dir",  required_argument, NULL, 'D' },
13256165Sru  { "info-file", required_argument, NULL, 'i' },
13356165Sru  { "item",      required_argument, NULL, 'e' },
13456165Sru  { "quiet",     no_argument, NULL, 'q' },
13556165Sru  { "remove",    no_argument, NULL, 'r' },
13656165Sru  { "section",   required_argument, NULL, 's' },
13756165Sru  { "version",   no_argument, NULL, 'V' },
13856165Sru  { 0 }
13956165Sru};
14056165Sru
14156165Sru/* Error message functions.  */
14256165Sru
14356165Sru/* Print error message.  S1 is printf control string, S2 and S3 args for it. */
14456165Sru
14556165Sru/* VARARGS1 */
14656165Sruvoid
147146521Sruerror (const char *s1, const char *s2, const char *s3)
14856165Sru{
14956165Sru  fprintf (stderr, "%s: ", progname);
15056165Sru  fprintf (stderr, s1, s2, s3);
15156165Sru  putc ('\n', stderr);
15256165Sru}
15356165Sru
15456165Sru/* VARARGS1 */
15556165Sruvoid
156146521Sruwarning (const char *s1, const char *s2, const char *s3)
15756165Sru{
15856165Sru  fprintf (stderr, _("%s: warning: "), progname);
15956165Sru  fprintf (stderr, s1, s2, s3);
16056165Sru  putc ('\n', stderr);
16156165Sru}
16256165Sru
16356165Sru/* Print error message and exit.  */
16456165Sru
16556165Sruvoid
166146521Srufatal (const char *s1, const char *s2, const char *s3)
16756165Sru{
16856165Sru  error (s1, s2, s3);
16956165Sru  xexit (1);
17056165Sru}
17156165Sru
17242664Smarkm/* Return a newly-allocated string
17342664Smarkm   whose contents concatenate those of S1, S2, S3.  */
17421495Sjmacdchar *
175146521Sruconcat (const char *s1, const char *s2, const char *s3)
17621495Sjmacd{
17721495Sjmacd  int len1 = strlen (s1), len2 = strlen (s2), len3 = strlen (s3);
17821495Sjmacd  char *result = (char *) xmalloc (len1 + len2 + len3 + 1);
17921495Sjmacd
18021495Sjmacd  strcpy (result, s1);
18121495Sjmacd  strcpy (result + len1, s2);
18221495Sjmacd  strcpy (result + len1 + len2, s3);
18321495Sjmacd  *(result + len1 + len2 + len3) = 0;
18421495Sjmacd
18521495Sjmacd  return result;
18621495Sjmacd}
18721495Sjmacd
18821495Sjmacd/* Return a string containing SIZE characters
18921495Sjmacd   copied from starting at STRING.  */
19021495Sjmacd
19121495Sjmacdchar *
192146521Srucopy_string (const char *string, int size)
19321495Sjmacd{
19421495Sjmacd  int i;
19521495Sjmacd  char *copy = (char *) xmalloc (size + 1);
19621495Sjmacd  for (i = 0; i < size; i++)
19721495Sjmacd    copy[i] = string[i];
19821495Sjmacd  copy[size] = 0;
19921495Sjmacd  return copy;
20021495Sjmacd}
20121495Sjmacd
20221495Sjmacd/* Print fatal error message based on errno, with file name NAME.  */
20321495Sjmacd
20421495Sjmacdvoid
205146521Srupfatal_with_name (const char *name)
20621495Sjmacd{
20742664Smarkm  char *s = concat ("", strerror (errno), _(" for %s"));
20893143Sru  fatal (s, name, 0);
20921495Sjmacd}
21021495Sjmacd
211146521Sru/* Compare the menu item names in LINE1 (line length LEN1)
212146521Sru   and LINE2 (line length LEN2).  Return 1 if the item name
213146521Sru   in LINE1 is less, 0 otherwise.  */
214146521Sru
215146521Srustatic int
216146521Srumenu_line_lessp (char *line1, int len1, char *line2, int len2)
217146521Sru{
218146521Sru  int minlen = (len1 < len2 ? len1 : len2);
219146521Sru  int i;
220146521Sru
221146521Sru  for (i = 0; i < minlen; i++)
222146521Sru    {
223146521Sru      /* If one item name is a prefix of the other,
224146521Sru         the former one is less.  */
225146521Sru      if (line1[i] == ':' && line2[i] != ':')
226146521Sru        return 1;
227146521Sru      if (line2[i] == ':' && line1[i] != ':')
228146521Sru        return 0;
229146521Sru      /* If they both continue and differ, one is less.  */
230146521Sru      if (line1[i] < line2[i])
231146521Sru        return 1;
232146521Sru      if (line1[i] > line2[i])
233146521Sru        return 0;
234146521Sru    }
235146521Sru  /* With a properly formatted dir file,
236146521Sru     we can only get here if the item names are equal.  */
237146521Sru  return 0;
238146521Sru}
239146521Sru
240146521Sru/* Compare the menu item names in LINE1 (line length LEN1)
241146521Sru   and LINE2 (line length LEN2).  Return 1 if the item names are equal,
242146521Sru   0 otherwise.  */
243146521Sru
244146521Srustatic int
245146521Srumenu_line_equal (char *line1, int len1, char *line2, int len2)
246146521Sru{
247146521Sru  int minlen = (len1 < len2 ? len1 : len2);
248146521Sru  int i;
249146521Sru
250146521Sru  for (i = 0; i < minlen; i++)
251146521Sru    {
252146521Sru      /* If both item names end here, they are equal.  */
253146521Sru      if (line1[i] == ':' && line2[i] == ':')
254146521Sru        return 1;
255146521Sru      /* If they both continue and differ, one is less.  */
256146521Sru      if (line1[i] != line2[i])
257146521Sru        return 0;
258146521Sru    }
259146521Sru  /* With a properly formatted dir file,
260146521Sru     we can only get here if the item names are equal.  */
261146521Sru  return 1;
262146521Sru}
263146521Sru
264146521Sru
26521495Sjmacd/* Given the full text of a menu entry, null terminated,
26621495Sjmacd   return just the menu item name (copied).  */
26721495Sjmacd
26821495Sjmacdchar *
269146521Sruextract_menu_item_name (char *item_text)
27021495Sjmacd{
27121495Sjmacd  char *p;
27221495Sjmacd
27321495Sjmacd  if (*item_text == '*')
27421495Sjmacd    item_text++;
27521495Sjmacd  while (*item_text == ' ')
27621495Sjmacd    item_text++;
27721495Sjmacd
27821495Sjmacd  p = item_text;
27921495Sjmacd  while (*p && *p != ':') p++;
28021495Sjmacd  return copy_string (item_text, p - item_text);
28121495Sjmacd}
28221495Sjmacd
28321495Sjmacd/* Given the full text of a menu entry, terminated by null or newline,
28421495Sjmacd   return just the menu item file (copied).  */
28521495Sjmacd
28621495Sjmacdchar *
287146521Sruextract_menu_file_name (char *item_text)
28821495Sjmacd{
28921495Sjmacd  char *p = item_text;
29021495Sjmacd
29121495Sjmacd  /* If we have text that looks like * ITEM: (FILE)NODE...,
29221495Sjmacd     extract just FILE.  Otherwise return "(none)".  */
29321495Sjmacd
29421495Sjmacd  if (*p == '*')
29521495Sjmacd    p++;
29621495Sjmacd  while (*p == ' ')
29721495Sjmacd    p++;
29821495Sjmacd
29921495Sjmacd  /* Skip to and past the colon.  */
30021495Sjmacd  while (*p && *p != '\n' && *p != ':') p++;
30121495Sjmacd  if (*p == ':') p++;
30221495Sjmacd
30321495Sjmacd  /* Skip past the open-paren.  */
30421495Sjmacd  while (1)
30521495Sjmacd    {
30621495Sjmacd      if (*p == '(')
30721495Sjmacd        break;
30821495Sjmacd      else if (*p == ' ' || *p == '\t')
30921495Sjmacd        p++;
31021495Sjmacd      else
31121495Sjmacd        return "(none)";
31221495Sjmacd    }
31321495Sjmacd  p++;
31421495Sjmacd
31521495Sjmacd  item_text = p;
31621495Sjmacd
31721495Sjmacd  /* File name ends just before the close-paren.  */
31821495Sjmacd  while (*p && *p != '\n' && *p != ')') p++;
31921495Sjmacd  if (*p != ')')
32021495Sjmacd    return "(none)";
32121495Sjmacd
32221495Sjmacd  return copy_string (item_text, p - item_text);
32321495Sjmacd}
32456165Sru
32556165Sru
32621495Sjmacd
32756165Sru/* Return FNAME with any [.info][.gz] suffix removed.  */
32856165Sru
32956165Srustatic char *
330146521Srustrip_info_suffix (char *fname)
33156165Sru{
33256165Sru  char *ret = xstrdup (fname);
33356165Sru  unsigned len = strlen (ret);
33456165Sru
33556165Sru  if (len > 3 && FILENAME_CMP (ret + len - 3, ".gz") == 0)
33656165Sru    {
33756165Sru      len -= 3;
33856165Sru      ret[len] = 0;
33956165Sru    }
340114478Sru  else if (len > 4 && FILENAME_CMP (ret + len - 4, ".bz2") == 0)
341114478Sru    {
342114478Sru      len -= 4;
343114478Sru      ret[len] = 0;
344114478Sru    }
34556165Sru
34656165Sru  if (len > 5 && FILENAME_CMP (ret + len - 5, ".info") == 0)
34756165Sru    {
34856165Sru      len -= 5;
34956165Sru      ret[len] = 0;
35056165Sru    }
35156165Sru  else if (len > 4 && FILENAME_CMP (ret + len - 4, ".inf") == 0)
35256165Sru    {
35356165Sru      len -= 4;
35456165Sru      ret[len] = 0;
35556165Sru    }
35656165Sru#ifdef __MSDOS__
35756165Sru  else if (len > 4 && (FILENAME_CMP (ret + len - 4, ".inz") == 0
35856165Sru                       || FILENAME_CMP (ret + len - 4, ".igz") == 0))
35956165Sru    {
36056165Sru      len -= 4;
36156165Sru      ret[len] = 0;
36256165Sru    }
36356165Sru#endif /* __MSDOS__ */
36456165Sru
36556165Sru  return ret;
36656165Sru}
36756165Sru
36856165Sru
36956165Sru/* Return true if ITEM matches NAME and is followed by TERM_CHAR.  ITEM
37056165Sru   can also be followed by `.gz', `.info.gz', or `.info' (and then
37156165Sru   TERM_CHAR) and still match.  */
37256165Sru
37356165Srustatic int
374146521Srumenu_item_equal (const char *item, char term_char, const char *name)
37556165Sru{
376146521Sru  int ret;
377146521Sru  const char *item_basename = item;
37856165Sru  unsigned name_len = strlen (name);
379146521Sru
380146521Sru  /* We must compare the basename in ITEM, since we are passed the
381146521Sru     basename of the original info file.  Otherwise, a new entry like
382146521Sru     "lilypond/lilypond" won't match "lilypond".
383146521Sru
384146521Sru     Actually, it seems to me that we should really compare the whole
385146521Sru     name, and not just the basename.  Couldn't there be dir1/foo.info
386146521Sru     and dir2/foo.info?  Also, it seems like we should be using the
387146521Sru     filename from the new dir entries, not the filename on the command
388146521Sru     line.  Not worrying about those things right now, though.  --karl,
389146521Sru     26mar04.  */
390146521Sru  while (*item_basename && !IS_SLASH (*item_basename)
391146521Sru	 && *item_basename != term_char)
392146521Sru    item_basename++;
393146521Sru  if (! *item_basename || *item_basename == term_char)
394146521Sru    item_basename = item;  /* no /, use original */
395146521Sru  else
396146521Sru    item_basename++;       /* have /, move past it */
397146521Sru
39856165Sru  /* First, ITEM must actually match NAME (usually it won't).  */
399146521Sru  ret = strncasecmp (item_basename, name, name_len) == 0;
40056165Sru  if (ret)
40156165Sru    {
40256165Sru      /* Then, `foobar' doesn't match `foo', so be sure we've got all of
40356165Sru         ITEM.  The various suffixes should never actually appear in the
40456165Sru         dir file, but sometimes people put them in.  */
40556165Sru      static char *suffixes[]
40656165Sru        = { "", ".info.gz", ".info", ".inf", ".gz",
40756165Sru#ifdef __MSDOS__
40856165Sru            ".inz", ".igz",
40956165Sru#endif
41056165Sru            NULL };
41156165Sru      unsigned i;
41256165Sru      ret = 0;
41356165Sru      for (i = 0; !ret && suffixes[i]; i++)
41456165Sru        {
41556165Sru          char *suffix = suffixes[i];
41656165Sru          unsigned suffix_len = strlen (suffix);
417146521Sru          ret = strncasecmp (item_basename + name_len, suffix, suffix_len) == 0
418146521Sru                && item_basename[name_len + suffix_len] == term_char;
41956165Sru        }
42056165Sru    }
42156165Sru
42256165Sru  return ret;
42356165Sru}
42456165Sru
42556165Sru
42656165Sru
42721495Sjmacdvoid
428146521Srusuggest_asking_for_help (void)
42921495Sjmacd{
43042664Smarkm  fprintf (stderr, _("\tTry `%s --help' for a complete list of options.\n"),
43121495Sjmacd           progname);
43256165Sru  xexit (1);
43321495Sjmacd}
43421495Sjmacd
43521495Sjmacdvoid
436146521Sruprint_help (void)
43721495Sjmacd{
43842664Smarkm  printf (_("Usage: %s [OPTION]... [INFO-FILE [DIR-FILE]]\n\
43921495Sjmacd\n\
44056165SruInstall or delete dir entries from INFO-FILE in the Info directory file\n\
44156165SruDIR-FILE.\n\
44242664Smarkm\n\
44321495SjmacdOptions:\n\
44456165Sru --delete          delete existing entries for INFO-FILE from DIR-FILE;\n\
44556165Sru                     don't insert any new entries.\n\
44656165Sru --defentry=TEXT   like --entry, but only use TEXT if an entry\n\
44756165Sru                     is not present in INFO-FILE.\n\
44856165Sru --defsection=TEXT like --section, but only use TEXT if a section\n\
44956165Sru                     is not present in INFO-FILE.\n\
45056165Sru --dir-file=NAME   specify file name of Info directory file.\n\
45156165Sru                     This is equivalent to using the DIR-FILE argument.\n\
45256165Sru --entry=TEXT      insert TEXT as an Info directory entry.\n\
45356165Sru                     TEXT should have the form of an Info menu item line\n\
45456165Sru                     plus zero or more extra lines starting with whitespace.\n\
45556165Sru                     If you specify more than one entry, they are all added.\n\
45656165Sru                     If you don't specify any entries, they are determined\n\
45756165Sru                     from information in the Info file itself.\n\
45856165Sru --help            display this help and exit.\n\
45956165Sru --info-file=FILE  specify Info file to install in the directory.\n\
46056165Sru                     This is equivalent to using the INFO-FILE argument.\n\
46156165Sru --info-dir=DIR    same as --dir-file=DIR/dir.\n\
46256165Sru --item=TEXT       same as --entry TEXT.\n\
46356165Sru                     An Info directory entry is actually a menu item.\n\
46456165Sru --quiet           suppress warnings.\n\
46556165Sru --remove          same as --delete.\n\
46656165Sru --section=SEC     put this file's entries in section SEC of the directory.\n\
46756165Sru                     If you specify more than one section, all the entries\n\
46856165Sru                     are added in each of the sections.\n\
46956165Sru                     If you don't specify any sections, they are determined\n\
47056165Sru                     from information in the Info file itself.\n\
47156165Sru --version         display version information and exit.\n\
472100517Sru"), progname);
473100517Sru
474100517Sru  puts (_("\n\
47556165SruEmail bug reports to bug-texinfo@gnu.org,\n\
47656165Srugeneral questions and discussion to help-texinfo@gnu.org.\n\
477100517SruTexinfo home page: http://www.gnu.org/software/texinfo/"));
47821495Sjmacd}
47921495Sjmacd
48021495Sjmacd
48142664Smarkm/* If DIRFILE does not exist, create a minimal one (or abort).  If it
48242664Smarkm   already exists, do nothing.  */
48342664Smarkm
48442664Smarkmvoid
485146521Sruensure_dirfile_exists (char *dirfile)
48642664Smarkm{
48742664Smarkm  int desc = open (dirfile, O_RDONLY);
48842664Smarkm  if (desc < 0 && errno == ENOENT)
48942664Smarkm    {
49042664Smarkm      FILE *f;
49142664Smarkm      char *readerr = strerror (errno);
49242664Smarkm      close (desc);
49342664Smarkm      f = fopen (dirfile, "w");
49442664Smarkm      if (f)
49542664Smarkm        {
49656165Sru          fprintf (f, _("This is the file .../info/dir, which contains the\n\
49742664Smarkmtopmost node of the Info hierarchy, called (dir)Top.\n\
49842664SmarkmThe first time you invoke Info you start off looking at this node.\n\
499146521Sru\x1f\n\
50056165Sru%s\tThis is the top of the INFO tree\n\
50142664Smarkm\n\
50242664Smarkm  This (the Directory node) gives a menu of major topics.\n\
50342664Smarkm  Typing \"q\" exits, \"?\" lists all Info commands, \"d\" returns here,\n\
50442664Smarkm  \"h\" gives a primer for first-timers,\n\
50542664Smarkm  \"mEmacs<Return>\" visits the Emacs manual, etc.\n\
50642664Smarkm\n\
50742664Smarkm  In Emacs, you can click mouse button 2 on a menu item or cross reference\n\
50842664Smarkm  to select it.\n\
50942664Smarkm\n\
510114478Sru%s\n\
511114478Sru"), "File: dir,\tNode: Top",  /* These keywords must not be translated.  */
512114478Sru    "* Menu:"
513114478Sru);
51442664Smarkm          if (fclose (f) < 0)
51542664Smarkm            pfatal_with_name (dirfile);
51642664Smarkm        }
51742664Smarkm      else
51842664Smarkm        {
51942664Smarkm          /* Didn't exist, but couldn't open for writing.  */
52042664Smarkm          fprintf (stderr,
52142664Smarkm                   _("%s: could not read (%s) and could not create (%s)\n"),
52242664Smarkm                   dirfile, readerr, strerror (errno));
52356165Sru          xexit (1);
52442664Smarkm        }
52542664Smarkm    }
52642664Smarkm  else
52742664Smarkm    close (desc); /* It already existed, so fine.  */
52842664Smarkm}
52942664Smarkm
53056165Sru/* Open FILENAME and return the resulting stream pointer.  If it doesn't
53156165Sru   exist, try FILENAME.gz.  If that doesn't exist either, call
53256165Sru   CREATE_CALLBACK (with FILENAME as arg) to create it, if that is
53356165Sru   non-NULL.  If still no luck, fatal error.
53421495Sjmacd
53556165Sru   If we do open it, return the actual name of the file opened in
53656165Sru   OPENED_FILENAME and the compress program to use to (de)compress it in
53756165Sru   COMPRESSION_PROGRAM.  The compression program is determined by the
53856165Sru   magic number, not the filename.  */
53956165Sru
54056165SruFILE *
541146521Sruopen_possibly_compressed_file (char *filename,
542146521Sru    void (*create_callback) (char *),
543146521Sru    char **opened_filename, char **compression_program, int *is_pipe)
54421495Sjmacd{
54556165Sru  char *local_opened_filename, *local_compression_program;
54656165Sru  int nread;
54756165Sru  char data[4];
54856165Sru  FILE *f;
54942664Smarkm
55056165Sru  /* We let them pass NULL if they don't want this info, but it's easier
55156165Sru     to always determine it.  */
55256165Sru  if (!opened_filename)
55356165Sru    opened_filename = &local_opened_filename;
55456165Sru
55556165Sru  *opened_filename = filename;
55656165Sru  f = fopen (*opened_filename, FOPEN_RBIN);
55756165Sru  if (!f)
55856165Sru    {
55956165Sru      *opened_filename = concat (filename, ".gz", "");
56056165Sru      f = fopen (*opened_filename, FOPEN_RBIN);
561114478Sru  if (!f)
562114478Sru    {
563114478Sru      free (*opened_filename);
564114478Sru      *opened_filename = concat (filename, ".bz2", "");
565114478Sru      f = fopen (*opened_filename, FOPEN_RBIN);
566114478Sru    }
567114478Sru
56856165Sru#ifdef __MSDOS__
56956165Sru      if (!f)
57056165Sru        {
57156165Sru          free (*opened_filename);
57256165Sru          *opened_filename = concat (filename, ".igz", "");
57356165Sru          f = fopen (*opened_filename, FOPEN_RBIN);
57456165Sru        }
57556165Sru      if (!f)
57656165Sru        {
57756165Sru          free (*opened_filename);
57856165Sru          *opened_filename = concat (filename, ".inz", "");
57956165Sru          f = fopen (*opened_filename, FOPEN_RBIN);
58056165Sru        }
58156165Sru#endif
58256165Sru      if (!f)
58356165Sru        {
58456165Sru          if (create_callback)
58556165Sru            { /* That didn't work either.  Create the file if we can.  */
58656165Sru              (*create_callback) (filename);
58756165Sru
58856165Sru              /* And try opening it again.  */
58956165Sru              free (*opened_filename);
59056165Sru              *opened_filename = filename;
59156165Sru              f = fopen (*opened_filename, FOPEN_RBIN);
59256165Sru              if (!f)
59356165Sru                pfatal_with_name (filename);
59456165Sru            }
59556165Sru          else
59656165Sru            pfatal_with_name (filename);
59756165Sru        }
59856165Sru    }
59956165Sru
60056165Sru  /* Read first few bytes of file rather than relying on the filename.
60156165Sru     If the file is shorter than this it can't be usable anyway.  */
60256165Sru  nread = fread (data, sizeof (data), 1, f);
60356165Sru  if (nread != 1)
60456165Sru    {
60556165Sru      /* Empty files don't set errno, so we get something like
60656165Sru         "install-info: No error for foo", which is confusing.  */
60756165Sru      if (nread == 0)
60893143Sru        fatal (_("%s: empty file"), *opened_filename, 0);
60956165Sru      pfatal_with_name (*opened_filename);
61056165Sru    }
61156165Sru
61256165Sru  if (!compression_program)
61356165Sru    compression_program = &local_compression_program;
61456165Sru
61556165Sru  if (data[0] == '\x1f' && data[1] == '\x8b')
61656165Sru#if STRIP_DOT_EXE
61756165Sru    /* An explicit .exe yields a better diagnostics from popen below
61856165Sru       if they don't have gzip installed.  */
61956165Sru    *compression_program = "gzip.exe";
62056165Sru#else
62156165Sru    *compression_program = "gzip";
62256165Sru#endif
623114478Sru  else if(data[0] == 'B' && data[1] == 'Z' && data[2] == 'h')
624114478Sru#ifndef STRIP_DOT_EXE
625114478Sru    *compression_program = "bzip2.exe";
626114478Sru#else
627114478Sru    *compression_program = "bzip2";
628114478Sru#endif
629114478Sru  else if(data[0] == 'B' && data[1] == 'Z' && data[2] == '0')
630114478Sru#ifndef STRIP_DOT_EXE
631114478Sru    *compression_program = "bzip.exe";
632114478Sru#else
633114478Sru    *compression_program = "bzip";
634114478Sru#endif
63556165Sru  else
63656165Sru    *compression_program = NULL;
63756165Sru
63856165Sru  if (*compression_program)
63956165Sru    { /* It's compressed, so fclose the file and then open a pipe.  */
64056165Sru      char *command = concat (*compression_program," -cd <", *opened_filename);
64156165Sru      if (fclose (f) < 0)
64256165Sru        pfatal_with_name (*opened_filename);
64356165Sru      f = popen (command, "r");
64456165Sru      if (f)
64556165Sru        *is_pipe = 1;
64656165Sru      else
64756165Sru        pfatal_with_name (command);
64856165Sru    }
64956165Sru  else
65056165Sru    { /* It's a plain file, seek back over the magic bytes.  */
65156165Sru      if (fseek (f, 0, 0) < 0)
65256165Sru        pfatal_with_name (*opened_filename);
65356165Sru#if O_BINARY
65456165Sru      /* Since this is a text file, and we opened it in binary mode,
65556165Sru         switch back to text mode.  */
65656165Sru      f = freopen (*opened_filename, "r", f);
65756165Sru#endif
65856165Sru      *is_pipe = 0;
65956165Sru    }
66056165Sru
66156165Sru  return f;
66256165Sru}
66321495Sjmacd
66456165Sru/* Read all of file FILENAME into memory and return the address of the
66556165Sru   data.  Store the size of the data into SIZEP.  If need be, uncompress
66656165Sru   (i.e., try FILENAME.gz et al. if FILENAME does not exist) and store
66756165Sru   the actual file name that was opened into OPENED_FILENAME (if it is
66856165Sru   non-NULL), and the companion compression program (if any, else NULL)
66956165Sru   into COMPRESSION_PROGRAM (if that is non-NULL).  If trouble, do
67056165Sru   a fatal error.  */
67156165Sru
67256165Sruchar *
673146521Srureadfile (char *filename, int *sizep,
674146521Sru    void (*create_callback) (char *), char **opened_filename,
675146521Sru    char **compression_program)
67656165Sru{
67756165Sru  char *real_name;
67856165Sru  FILE *f;
67956165Sru  int pipe_p;
68056165Sru  int filled = 0;
68156165Sru  int data_size = 8192;
68256165Sru  char *data = xmalloc (data_size);
68356165Sru
68456165Sru  /* If they passed the space for the file name to return, use it.  */
68556165Sru  f = open_possibly_compressed_file (filename, create_callback,
68656165Sru                                     opened_filename ? opened_filename
68756165Sru                                                     : &real_name,
68856165Sru                                     compression_program, &pipe_p);
68956165Sru
69056165Sru  for (;;)
69156165Sru    {
69256165Sru      int nread = fread (data + filled, 1, data_size - filled, f);
69356165Sru      if (nread < 0)
69456165Sru        pfatal_with_name (real_name);
69556165Sru      if (nread == 0)
69656165Sru        break;
69756165Sru
69856165Sru      filled += nread;
69956165Sru      if (filled == data_size)
70056165Sru        {
70156165Sru          data_size += 65536;
70256165Sru          data = xrealloc (data, data_size);
70356165Sru        }
70456165Sru    }
70556165Sru
70656165Sru  /* We'll end up wasting space if we're not passing the filename back
70756165Sru     and it is not just FILENAME, but so what.  */
70856165Sru  /* We need to close the stream, since on some systems the pipe created
70956165Sru     by popen is simulated by a temporary file which only gets removed
71056165Sru     inside pclose.  */
71156165Sru  if (pipe_p)
71256165Sru    pclose (f);
71356165Sru  else
71456165Sru    fclose (f);
71556165Sru
71656165Sru  *sizep = filled;
71756165Sru  return data;
71856165Sru}
71956165Sru
72056165Sru/* Output the old dir file, interpolating the new sections
72156165Sru   and/or new entries where appropriate.  If COMPRESSION_PROGRAM is not
72256165Sru   null, pipe to it to create DIRFILE.  Thus if we read dir.gz on input,
72356165Sru   we'll write dir.gz on output.  */
72456165Sru
72556165Srustatic void
726146521Sruoutput_dirfile (char *dirfile, int dir_nlines, struct line_data *dir_lines,
727146521Sru                int n_entries_to_add, struct spec_entry *entries_to_add,
728146521Sru                struct spec_section *input_sections, char *compression_program)
72956165Sru{
73056165Sru  int i;
73156165Sru  FILE *output;
73256165Sru
73356165Sru  if (compression_program)
73456165Sru    {
73556165Sru      char *command = concat (compression_program, ">", dirfile);
73656165Sru      output = popen (command, "w");
73756165Sru    }
73856165Sru  else
73956165Sru    output = fopen (dirfile, "w");
74056165Sru
74156165Sru  if (!output)
74256165Sru    {
74356165Sru      perror (dirfile);
74456165Sru      xexit (1);
74556165Sru    }
74656165Sru
74756165Sru  for (i = 0; i <= dir_nlines; i++)
74856165Sru    {
74956165Sru      int j;
75056165Sru
75156165Sru      /* If we decided to output some new entries before this line,
75256165Sru         output them now.  */
75356165Sru      if (dir_lines[i].add_entries_before)
75456165Sru        for (j = 0; j < n_entries_to_add; j++)
75556165Sru          {
75656165Sru            struct spec_entry *this = dir_lines[i].add_entries_before[j];
75756165Sru            if (this == 0)
75856165Sru              break;
75956165Sru            fputs (this->text, output);
76056165Sru          }
76156165Sru      /* If we decided to add some sections here
76256165Sru         because there are no such sections in the file,
76356165Sru         output them now.  */
76456165Sru      if (dir_lines[i].add_sections_before)
76556165Sru        {
76656165Sru          struct spec_section *spec;
76756165Sru          struct spec_section **sections;
76856165Sru          int n_sections = 0;
76956165Sru          struct spec_entry *entry;
77056165Sru          struct spec_entry **entries;
77156165Sru          int n_entries = 0;
77256165Sru
77356165Sru          /* Count the sections and allocate a vector for all of them.  */
77456165Sru          for (spec = input_sections; spec; spec = spec->next)
77556165Sru            n_sections++;
77656165Sru          sections = ((struct spec_section **)
77756165Sru                      xmalloc (n_sections * sizeof (struct spec_section *)));
77856165Sru
77956165Sru          /* Fill the vector SECTIONS with pointers to all the sections,
78056165Sru             and sort them.  */
78156165Sru          j = 0;
78256165Sru          for (spec = input_sections; spec; spec = spec->next)
78356165Sru            sections[j++] = spec;
78456165Sru          qsort (sections, n_sections, sizeof (struct spec_section *),
78556165Sru                 compare_section_names);
78656165Sru
78756165Sru          /* Count the entries and allocate a vector for all of them.  */
78856165Sru          for (entry = entries_to_add; entry; entry = entry->next)
78956165Sru            n_entries++;
79056165Sru          entries = ((struct spec_entry **)
79156165Sru                     xmalloc (n_entries * sizeof (struct spec_entry *)));
79256165Sru
79356165Sru          /* Fill the vector ENTRIES with pointers to all the sections,
79456165Sru             and sort them.  */
79556165Sru          j = 0;
79656165Sru          for (entry = entries_to_add; entry; entry = entry->next)
79756165Sru            entries[j++] = entry;
79856165Sru          qsort (entries, n_entries, sizeof (struct spec_entry *),
79956165Sru                 compare_entries_text);
80056165Sru
80156165Sru          /* Generate the new sections in alphabetical order.  In each
80256165Sru             new section, output all of the entries that belong to that
80356165Sru             section, in alphabetical order.  */
80456165Sru          for (j = 0; j < n_sections; j++)
80556165Sru            {
80656165Sru              spec = sections[j];
80756165Sru              if (spec->missing)
80856165Sru                {
80956165Sru                  int k;
81056165Sru
81156165Sru                  putc ('\n', output);
81256165Sru                  fputs (spec->name, output);
81356165Sru                  putc ('\n', output);
81456165Sru                  for (k = 0; k < n_entries; k++)
81556165Sru                    {
81656165Sru                      struct spec_section *spec1;
81756165Sru                      /* Did they at all want this entry to be put into
81856165Sru                         this section?  */
81956165Sru                      entry = entries[k];
82056165Sru                      for (spec1 = entry->entry_sections;
82156165Sru                           spec1 && spec1 != entry->entry_sections_tail;
82256165Sru                           spec1 = spec1->next)
82356165Sru                        {
82456165Sru                          if (!strcmp (spec1->name, spec->name))
82556165Sru                            break;
82656165Sru                        }
82756165Sru                      if (spec1 && spec1 != entry->entry_sections_tail)
82856165Sru                        fputs (entry->text, output);
82956165Sru                    }
83056165Sru                }
83156165Sru            }
83256165Sru
83356165Sru          free (entries);
83456165Sru          free (sections);
83556165Sru        }
83656165Sru
83756165Sru      /* Output the original dir lines unless marked for deletion.  */
83856165Sru      if (i < dir_nlines && !dir_lines[i].delete)
83956165Sru        {
84056165Sru          fwrite (dir_lines[i].start, 1, dir_lines[i].size, output);
84156165Sru          putc ('\n', output);
84256165Sru        }
84356165Sru    }
84456165Sru
84556165Sru  /* Some systems, such as MS-DOS, simulate pipes with temporary files.
84656165Sru     On those systems, the compressor actually gets run inside pclose,
84756165Sru     so we must call pclose.  */
84856165Sru  if (compression_program)
84956165Sru    pclose (output);
85056165Sru  else
85156165Sru    fclose (output);
85256165Sru}
85356165Sru
85456165Sru/* Parse the input to find the section names and the entry names it
85556165Sru   specifies.  Return the number of entries to add from this file.  */
85642664Smarkmint
857146521Sruparse_input (const struct line_data *lines, int nlines,
858146521Sru             struct spec_section **sections, struct spec_entry **entries)
85956165Sru{
86056165Sru  int n_entries = 0;
86156165Sru  int prefix_length = strlen ("INFO-DIR-SECTION ");
86256165Sru  struct spec_section *head = *sections, *tail = NULL;
86356165Sru  int reset_tail = 0;
86456165Sru  char *start_of_this_entry = 0;
86556165Sru  int ignore_sections = *sections != 0;
86656165Sru  int ignore_entries  = *entries  != 0;
86756165Sru
86856165Sru  int i;
86956165Sru
87056165Sru  if (ignore_sections && ignore_entries)
87156165Sru    return 0;
87256165Sru
87356165Sru  /* Loop here processing lines from the input file.  Each
87456165Sru     INFO-DIR-SECTION entry is added to the SECTIONS linked list.
87556165Sru     Each START-INFO-DIR-ENTRY block is added to the ENTRIES linked
87656165Sru     list, and all its entries inherit the chain of SECTION entries
87756165Sru     defined by the last group of INFO-DIR-SECTION entries we have
87856165Sru     seen until that point.  */
87956165Sru  for (i = 0; i < nlines; i++)
88056165Sru    {
88156165Sru      if (!ignore_sections
88256165Sru          && !strncmp ("INFO-DIR-SECTION ", lines[i].start, prefix_length))
88356165Sru        {
88456165Sru          struct spec_section *next
88556165Sru            = (struct spec_section *) xmalloc (sizeof (struct spec_section));
88656165Sru          next->name = copy_string (lines[i].start + prefix_length,
88756165Sru                                    lines[i].size - prefix_length);
88856165Sru          next->next = *sections;
88956165Sru          next->missing = 1;
89056165Sru          if (reset_tail)
89156165Sru            {
89256165Sru              tail = *sections;
89356165Sru              reset_tail = 0;
89456165Sru            }
89556165Sru          *sections = next;
89656165Sru          head = *sections;
89756165Sru        }
89856165Sru      /* If entries were specified explicitly with command options,
89956165Sru         ignore the entries in the input file.  */
90056165Sru      else if (!ignore_entries)
90156165Sru        {
90256165Sru          if (!strncmp ("START-INFO-DIR-ENTRY", lines[i].start, lines[i].size)
90356165Sru              && sizeof ("START-INFO-DIR-ENTRY") - 1 == lines[i].size)
90456165Sru            {
90556165Sru              if (!*sections)
90656165Sru                {
90756165Sru                  /* We found an entry, but didn't yet see any sections
90856165Sru                     specified.  Default to section "Miscellaneous".  */
90956165Sru                  *sections = (struct spec_section *)
91056165Sru                    xmalloc (sizeof (struct spec_section));
91156165Sru                  (*sections)->name =
91256165Sru		    default_section ? default_section : "Miscellaneous";
91356165Sru                  (*sections)->next = 0;
91456165Sru                  (*sections)->missing = 1;
91556165Sru                  head = *sections;
91656165Sru                }
91756165Sru              /* Next time we see INFO-DIR-SECTION, we will reset the
91856165Sru                 tail pointer.  */
91956165Sru              reset_tail = 1;
92056165Sru
92156165Sru              if (start_of_this_entry != 0)
92293143Sru                fatal (_("START-INFO-DIR-ENTRY without matching END-INFO-DIR-ENTRY"), 0, 0);
92356165Sru              start_of_this_entry = lines[i + 1].start;
92456165Sru            }
92556165Sru          else if (start_of_this_entry)
92656165Sru            {
92756165Sru              if ((!strncmp ("* ", lines[i].start, 2)
92856165Sru                   && lines[i].start > start_of_this_entry)
92956165Sru                  || (!strncmp ("END-INFO-DIR-ENTRY",
93056165Sru                                lines[i].start, lines[i].size)
93156165Sru                      && sizeof ("END-INFO-DIR-ENTRY") - 1 == lines[i].size))
93256165Sru                {
93356165Sru                  /* We found an end of this entry.  Allocate another
93456165Sru                     entry, fill its data, and add it to the linked
93556165Sru                     list.  */
93656165Sru                  struct spec_entry *next
93756165Sru                    = (struct spec_entry *) xmalloc (sizeof (struct spec_entry));
93856165Sru                  next->text
93956165Sru                    = copy_string (start_of_this_entry,
94056165Sru                                   lines[i].start - start_of_this_entry);
94156165Sru                  next->text_len = lines[i].start - start_of_this_entry;
94256165Sru                  next->entry_sections = head;
94356165Sru                  next->entry_sections_tail = tail;
94456165Sru                  next->next = *entries;
94556165Sru                  *entries = next;
94656165Sru                  n_entries++;
94756165Sru                  if (!strncmp ("END-INFO-DIR-ENTRY",
94856165Sru                                lines[i].start, lines[i].size)
94956165Sru                      && sizeof ("END-INFO-DIR-ENTRY") - 1 == lines[i].size)
95056165Sru                    start_of_this_entry = 0;
95156165Sru                  else
95256165Sru                    start_of_this_entry = lines[i].start;
95356165Sru                }
95456165Sru              else if (!strncmp ("END-INFO-DIR-ENTRY",
95556165Sru                                 lines[i].start, lines[i].size)
95656165Sru                       && sizeof ("END-INFO-DIR-ENTRY") - 1 == lines[i].size)
95793143Sru                fatal (_("END-INFO-DIR-ENTRY without matching START-INFO-DIR-ENTRY"), 0, 0);
95856165Sru            }
95956165Sru        }
96056165Sru    }
96156165Sru  if (start_of_this_entry != 0)
96293143Sru    fatal (_("START-INFO-DIR-ENTRY without matching END-INFO-DIR-ENTRY"),
963114478Sru           0, 0);
96456165Sru
96556165Sru  /* If we ignored the INFO-DIR-ENTRY directives, we need now go back
96656165Sru     and plug the names of all the sections we found into every
96756165Sru     element of the ENTRIES list.  */
96856165Sru  if (ignore_entries && *entries)
96956165Sru    {
97056165Sru      struct spec_entry *entry;
97156165Sru
97256165Sru      for (entry = *entries; entry; entry = entry->next)
97356165Sru        {
97456165Sru          entry->entry_sections = head;
97556165Sru          entry->entry_sections_tail = tail;
97656165Sru        }
97756165Sru    }
97856165Sru
97956165Sru  return n_entries;
98056165Sru}
98156165Sru
98256165Sru/* Parse the dir file whose basename is BASE_NAME.  Find all the
98356165Sru   nodes, and their menus, and the sections of their menus.  */
98456165Sruint
985146521Sruparse_dir_file (struct line_data *lines, int nlines, struct node **nodes,
986146521Sru                const char *base_name)
98756165Sru{
98856165Sru  int node_header_flag = 0;
98956165Sru  int something_deleted = 0;
99056165Sru  int i;
99156165Sru
99256165Sru  *nodes = 0;
99356165Sru  for (i = 0; i < nlines; i++)
99456165Sru    {
99556165Sru      /* Parse node header lines.  */
99656165Sru      if (node_header_flag)
99756165Sru        {
99856165Sru          int j, end;
99956165Sru          for (j = 0; j < lines[i].size; j++)
100056165Sru            /* Find the node name and store it in the `struct node'.  */
100156165Sru            if (!strncmp ("Node:", lines[i].start + j, 5))
100256165Sru              {
100356165Sru                char *line = lines[i].start;
100456165Sru                /* Find the start of the node name.  */
100556165Sru                j += 5;
100656165Sru                while (line[j] == ' ' || line[j] == '\t')
100756165Sru                  j++;
100856165Sru                /* Find the end of the node name.  */
100956165Sru                end = j;
101056165Sru                while (line[end] != 0 && line[end] != ',' && line[end] != '\n'
101156165Sru                       && line[end] != '\t')
101256165Sru                  end++;
101356165Sru                (*nodes)->name = copy_string (line + j, end - j);
101456165Sru              }
101556165Sru          node_header_flag = 0;
101656165Sru        }
101756165Sru
101856165Sru      /* Notice the start of a node.  */
101956165Sru      if (*lines[i].start == 037)
102056165Sru        {
102156165Sru          struct node *next = (struct node *) xmalloc (sizeof (struct node));
102256165Sru
102356165Sru          next->next = *nodes;
102456165Sru          next->name = NULL;
102556165Sru          next->start_line = i;
102656165Sru          next->end_line = 0;
102756165Sru          next->menu_start = NULL;
102856165Sru          next->sections = NULL;
102956165Sru          next->last_section = NULL;
103056165Sru
103156165Sru          if (*nodes != 0)
103256165Sru            (*nodes)->end_line = i;
103356165Sru          /* Fill in the end of the last menu section
103456165Sru             of the previous node.  */
103556165Sru          if (*nodes != 0 && (*nodes)->last_section != 0)
103656165Sru            (*nodes)->last_section->end_line = i;
103756165Sru
103856165Sru          *nodes = next;
103956165Sru
104056165Sru          /* The following line is the header of this node;
104156165Sru             parse it.  */
104256165Sru          node_header_flag = 1;
104356165Sru        }
104456165Sru
104556165Sru      /* Notice the lines that start menus.  */
104656165Sru      if (*nodes != 0 && !strncmp ("* Menu:", lines[i].start, 7))
104756165Sru        (*nodes)->menu_start = lines[i + 1].start;
104856165Sru
104956165Sru      /* Notice sections in menus.  */
105056165Sru      if (*nodes != 0
105156165Sru          && (*nodes)->menu_start != 0
105256165Sru          && *lines[i].start != '\n'
105356165Sru          && *lines[i].start != '*'
105456165Sru          && *lines[i].start != ' '
105556165Sru          && *lines[i].start != '\t')
105656165Sru        {
105756165Sru          /* Add this menu section to the node's list.
105856165Sru             This list grows in forward order.  */
105956165Sru          struct menu_section *next
106056165Sru            = (struct menu_section *) xmalloc (sizeof (struct menu_section));
106156165Sru
106256165Sru          next->start_line = i + 1;
106356165Sru          next->next = 0;
106456165Sru          next->end_line = 0;
106556165Sru          next->name = copy_string (lines[i].start, lines[i].size);
106656165Sru          if ((*nodes)->sections)
106756165Sru            {
106856165Sru              (*nodes)->last_section->next = next;
106956165Sru              (*nodes)->last_section->end_line = i;
107056165Sru            }
107156165Sru          else
107256165Sru            (*nodes)->sections = next;
107356165Sru          (*nodes)->last_section = next;
107456165Sru        }
107556165Sru
107656165Sru      /* Check for an existing entry that should be deleted.
107756165Sru         Delete all entries which specify this file name.  */
107856165Sru      if (*lines[i].start == '*')
107956165Sru        {
108056165Sru          char *q;
108156165Sru          char *p = lines[i].start;
108256165Sru
108356165Sru          p++; /* skip * */
108456165Sru          while (*p == ' ') p++; /* ignore following spaces */
108556165Sru          q = p; /* remember this, it's the beginning of the menu item.  */
108656165Sru
108756165Sru          /* Read menu item.  */
108856165Sru          while (*p != 0 && *p != ':')
108956165Sru            p++;
109056165Sru          p++; /* skip : */
109156165Sru
109256165Sru          if (*p == ':')
109356165Sru            { /* XEmacs-style entry, as in * Mew::Messaging.  */
109456165Sru              if (menu_item_equal (q, ':', base_name))
109556165Sru                {
109656165Sru                  lines[i].delete = 1;
109756165Sru                  something_deleted = 1;
109856165Sru                }
109956165Sru            }
110056165Sru          else
110156165Sru            { /* Emacs-style entry, as in * Emacs: (emacs).  */
110256165Sru              while (*p == ' ') p++; /* skip spaces after : */
110356165Sru              if (*p == '(')         /* if at parenthesized (FILENAME) */
110456165Sru                {
110556165Sru                  p++;
110656165Sru                  if (menu_item_equal (p, ')', base_name))
110756165Sru                    {
110856165Sru                      lines[i].delete = 1;
110956165Sru                      something_deleted = 1;
111056165Sru                    }
111156165Sru                }
111256165Sru            }
111356165Sru        }
111456165Sru
111556165Sru      /* Treat lines that start with whitespace
111656165Sru         as continuations; if we are deleting an entry,
111756165Sru         delete all its continuations as well.  */
111856165Sru      else if (i > 0 && (*lines[i].start == ' ' || *lines[i].start == '\t'))
111956165Sru        {
112056165Sru          lines[i].delete = lines[i - 1].delete;
112156165Sru        }
112256165Sru    }
112356165Sru
112456165Sru  /* Finish the info about the end of the last node.  */
112556165Sru  if (*nodes != 0)
112656165Sru    {
112756165Sru      (*nodes)->end_line = nlines;
112856165Sru      if ((*nodes)->last_section != 0)
112956165Sru        (*nodes)->last_section->end_line = nlines;
113056165Sru    }
113156165Sru
113256165Sru  return something_deleted;
113356165Sru}
113456165Sru
113556165Sruint
1136146521Srumain (int argc, char **argv)
113721495Sjmacd{
113856165Sru  char *opened_dirfilename;
113956165Sru  char *compression_program;
114056165Sru  char *infile_sans_info;
114121495Sjmacd  char *infile = 0, *dirfile = 0;
114221495Sjmacd
114321495Sjmacd  /* Record the text of the Info file, as a sequence of characters
114421495Sjmacd     and as a sequence of lines.  */
114556165Sru  char *input_data = NULL;
114656165Sru  int input_size = 0;
114756165Sru  struct line_data *input_lines = NULL;
114856165Sru  int input_nlines = 0;
114921495Sjmacd
115021495Sjmacd  /* Record here the specified section names and directory entries.  */
115121495Sjmacd  struct spec_section *input_sections = NULL;
115221495Sjmacd  struct spec_entry *entries_to_add = NULL;
115321495Sjmacd  int n_entries_to_add = 0;
115456165Sru  struct spec_entry *default_entries_to_add = NULL;
115556165Sru  int n_default_entries_to_add = 0;
115621495Sjmacd
115721495Sjmacd  /* Record the old text of the dir file, as plain characters,
115821495Sjmacd     as lines, and as nodes.  */
115921495Sjmacd  char *dir_data;
116021495Sjmacd  int dir_size;
116121495Sjmacd  int dir_nlines;
116221495Sjmacd  struct line_data *dir_lines;
116321495Sjmacd  struct node *dir_nodes;
116421495Sjmacd
116521495Sjmacd  /* Nonzero means --delete was specified (just delete existing entries).  */
116621495Sjmacd  int delete_flag = 0;
116721495Sjmacd  int something_deleted = 0;
116821495Sjmacd  /* Nonzero means -q was specified.  */
116921495Sjmacd  int quiet_flag = 0;
117021495Sjmacd
117121495Sjmacd  int i;
117221495Sjmacd
117342664Smarkm#ifdef HAVE_SETLOCALE
117442664Smarkm  /* Set locale via LC_ALL.  */
117542664Smarkm  setlocale (LC_ALL, "");
117642664Smarkm#endif
117742664Smarkm
117842664Smarkm  /* Set the text message domain.  */
117942664Smarkm  bindtextdomain (PACKAGE, LOCALEDIR);
118042664Smarkm  textdomain (PACKAGE);
118142664Smarkm
118221495Sjmacd  while (1)
118321495Sjmacd    {
118421495Sjmacd      int opt = getopt_long (argc, argv, "i:d:e:s:hHr", longopts, 0);
118521495Sjmacd
118621495Sjmacd      if (opt == EOF)
118721495Sjmacd        break;
118821495Sjmacd
118921495Sjmacd      switch (opt)
119021495Sjmacd        {
119121495Sjmacd        case 0:
119221495Sjmacd          /* If getopt returns 0, then it has already processed a
119321495Sjmacd             long-named option.  We should do nothing.  */
119421495Sjmacd          break;
119521495Sjmacd
119621495Sjmacd        case 1:
119721495Sjmacd          abort ();
119821495Sjmacd
119921495Sjmacd        case 'd':
120021495Sjmacd          if (dirfile)
120121495Sjmacd            {
1202146521Sru              fprintf (stderr, _("%s: already have dir file: %s\n"),
1203146521Sru                       progname, dirfile);
120421495Sjmacd              suggest_asking_for_help ();
120521495Sjmacd            }
120621495Sjmacd          dirfile = optarg;
120721495Sjmacd          break;
120821495Sjmacd
120921495Sjmacd        case 'D':
121021495Sjmacd          if (dirfile)
121121495Sjmacd            {
1212146521Sru              fprintf (stderr, _("%s: already have dir file: %s\n"),
1213146521Sru                       progname, dirfile);
121421495Sjmacd              suggest_asking_for_help ();
121521495Sjmacd            }
121621495Sjmacd          dirfile = concat (optarg, "", "/dir");
121721495Sjmacd          break;
121821495Sjmacd
121943579Speter	case 'E':
122021495Sjmacd        case 'e':
122121495Sjmacd          {
122221495Sjmacd            struct spec_entry *next
122321495Sjmacd              = (struct spec_entry *) xmalloc (sizeof (struct spec_entry));
122456165Sru            int olen = strlen (optarg);
122556165Sru            if (! (*optarg != 0 && optarg[olen - 1] == '\n'))
122656165Sru              {
122756165Sru                optarg = concat (optarg, "\n", "");
122856165Sru                olen++;
122956165Sru              }
123021495Sjmacd            next->text = optarg;
123156165Sru            next->text_len = olen;
123256165Sru            next->entry_sections = NULL;
123356165Sru            next->entry_sections_tail = NULL;
123456165Sru	    if (opt == 'e')
123556165Sru	      {
123656165Sru		next->next = entries_to_add;
123756165Sru		entries_to_add = next;
123856165Sru		n_entries_to_add++;
123956165Sru	      }
124056165Sru	    else
124156165Sru	      {
124256165Sru		next->next = default_entries_to_add;
124356165Sru		default_entries_to_add = next;
124456165Sru		n_default_entries_to_add++;
124556165Sru	      }
124621495Sjmacd          }
124721495Sjmacd          break;
124821495Sjmacd
124921495Sjmacd        case 'h':
125021495Sjmacd        case 'H':
125121495Sjmacd          print_help ();
125256165Sru          xexit (0);
125321495Sjmacd
125421495Sjmacd        case 'i':
125521495Sjmacd          if (infile)
125621495Sjmacd            {
125742664Smarkm              fprintf (stderr, _("%s: Specify the Info file only once.\n"),
125821495Sjmacd                       progname);
125921495Sjmacd              suggest_asking_for_help ();
126021495Sjmacd            }
126121495Sjmacd          infile = optarg;
126221495Sjmacd          break;
126321495Sjmacd
126421495Sjmacd        case 'q':
126521495Sjmacd          quiet_flag = 1;
126621495Sjmacd          break;
126721495Sjmacd
126821495Sjmacd        case 'r':
126921495Sjmacd          delete_flag = 1;
127021495Sjmacd          break;
127121495Sjmacd
127221495Sjmacd        case 's':
127321495Sjmacd          {
127421495Sjmacd            struct spec_section *next
127521495Sjmacd              = (struct spec_section *) xmalloc (sizeof (struct spec_section));
127621495Sjmacd            next->name = optarg;
127721495Sjmacd            next->next = input_sections;
127821495Sjmacd            next->missing = 1;
127921495Sjmacd            input_sections = next;
128021495Sjmacd          }
128121495Sjmacd          break;
128221495Sjmacd
128356165Sru	case 'S':
128456165Sru	  default_section = optarg;
128556165Sru	  break;
128656165Sru
128721495Sjmacd        case 'V':
128842664Smarkm          printf ("install-info (GNU %s) %s\n", PACKAGE, VERSION);
128956165Sru          puts ("");
1290146521Sru          puts ("Copyright (C) 2004 Free Software Foundation, Inc.");
1291146521Sru          printf (_("There is NO warranty.  You may redistribute this software\n\
129221495Sjmacdunder the terms of the GNU General Public License.\n\
1293146521SruFor more information about these matters, see the files named COPYING.\n"));
129456165Sru          xexit (0);
129521495Sjmacd
129621495Sjmacd        default:
129721495Sjmacd          suggest_asking_for_help ();
129821495Sjmacd        }
129921495Sjmacd    }
130021495Sjmacd
130121495Sjmacd  /* Interpret the non-option arguments as file names.  */
130221495Sjmacd  for (; optind < argc; ++optind)
130321495Sjmacd    {
130421495Sjmacd      if (infile == 0)
130521495Sjmacd        infile = argv[optind];
130621495Sjmacd      else if (dirfile == 0)
130721495Sjmacd        dirfile = argv[optind];
130821495Sjmacd      else
130993143Sru        error (_("excess command line argument `%s'"), argv[optind], 0);
131021495Sjmacd    }
131121495Sjmacd
131221495Sjmacd  if (!infile)
131393143Sru    fatal (_("No input file specified; try --help for more information."),
1314114478Sru           0, 0);
131521495Sjmacd  if (!dirfile)
131693143Sru    fatal (_("No dir file specified; try --help for more information."), 0, 0);
131721495Sjmacd
131856165Sru  /* Read the Info file and parse it into lines, unless we're deleting.  */
131956165Sru  if (!delete_flag)
132021495Sjmacd    {
132156165Sru      input_data = readfile (infile, &input_size, NULL, NULL, NULL);
132256165Sru      input_lines = findlines (input_data, input_size, &input_nlines);
132321495Sjmacd    }
132421495Sjmacd
132556165Sru  i = parse_input (input_lines, input_nlines,
132656165Sru                   &input_sections, &entries_to_add);
132756165Sru  if (i > n_entries_to_add)
132856165Sru    n_entries_to_add = i;
132956165Sru  else if (n_entries_to_add == 0)
133021495Sjmacd    {
133156165Sru      entries_to_add = default_entries_to_add;
133256165Sru      n_entries_to_add = n_default_entries_to_add;
133321495Sjmacd    }
133421495Sjmacd
133556165Sru  if (!delete_flag)
133656165Sru    {
133756165Sru      if (entries_to_add == 0)
133856165Sru        { /* No need to abort here, the original info file may not
133956165Sru             have the requisite Texinfo commands.  This is not
134056165Sru             something an installer should have to correct (it's a
134156165Sru             problem for the maintainer), and there's no need to cause
134256165Sru             subsequent parts of `make install' to fail.  */
134393143Sru          warning (_("no info dir entry in `%s'"), infile, 0);
134456165Sru          xexit (0);
134556165Sru        }
134621495Sjmacd
134756165Sru      /* If the entries came from the command-line arguments, their
134856165Sru         entry_sections pointers are not yet set.  Walk the chain of
134956165Sru         the entries and for each entry update entry_sections to point
135056165Sru         to the head of the list of sections where this entry should
135156165Sru         be put.  Note that all the entries specified on the command
135256165Sru         line get put into ALL the sections we've got, either from the
135356165Sru         Info file, or (under --section) from the command line,
135456165Sru         because in the loop below every entry inherits the entire
135556165Sru         chain of sections.  */
135656165Sru      if (n_entries_to_add > 0 && entries_to_add->entry_sections == NULL)
135721495Sjmacd        {
135856165Sru          struct spec_entry *ep;
135956165Sru
136056165Sru          /* If we got no sections, default to "Miscellaneous".  */
136156165Sru          if (input_sections == NULL)
136221495Sjmacd            {
136356165Sru              input_sections = (struct spec_section *)
136456165Sru                xmalloc (sizeof (struct spec_section));
136556165Sru              input_sections->name =
136656165Sru		default_section ? default_section : "Miscellaneous";
136756165Sru              input_sections->next = NULL;
136856165Sru              input_sections->missing = 1;
136921495Sjmacd            }
137056165Sru          for (ep = entries_to_add; ep; ep = ep->next)
137156165Sru            ep->entry_sections = input_sections;
137221495Sjmacd        }
137321495Sjmacd    }
137421495Sjmacd
137521495Sjmacd  /* Now read in the Info dir file.  */
137656165Sru  dir_data = readfile (dirfile, &dir_size, ensure_dirfile_exists,
137756165Sru                       &opened_dirfilename, &compression_program);
137821495Sjmacd  dir_lines = findlines (dir_data, dir_size, &dir_nlines);
137921495Sjmacd
138021495Sjmacd  /* We will be comparing the entries in the dir file against the
138156165Sru     current filename, so need to strip off any directory prefix and/or
138256165Sru     [.info][.gz] suffix.  */
138321495Sjmacd  {
138456165Sru    char *infile_basename = infile + strlen (infile);
138521495Sjmacd
138656165Sru    if (HAVE_DRIVE (infile))
1387114478Sru      infile += 2;      /* get past the drive spec X: */
138856165Sru
138956165Sru    while (infile_basename > infile && !IS_SLASH (infile_basename[-1]))
139056165Sru      infile_basename--;
139156165Sru
139256165Sru    infile_sans_info = strip_info_suffix (infile_basename);
139321495Sjmacd  }
139421495Sjmacd
139556165Sru  something_deleted
139656165Sru    = parse_dir_file (dir_lines, dir_nlines, &dir_nodes, infile_sans_info);
139721495Sjmacd
139821495Sjmacd  /* Decide where to add the new entries (unless --delete was used).
139921495Sjmacd     Find the menu sections to add them in.
140021495Sjmacd     In each section, find the proper alphabetical place to add
140121495Sjmacd     each of the entries.  */
140221495Sjmacd  if (!delete_flag)
140321495Sjmacd    {
140421495Sjmacd      struct node *node;
140521495Sjmacd      struct menu_section *section;
140621495Sjmacd      struct spec_section *spec;
140721495Sjmacd
140821495Sjmacd      for (node = dir_nodes; node; node = node->next)
140921495Sjmacd        for (section = node->sections; section; section = section->next)
141021495Sjmacd          {
141121495Sjmacd            for (i = section->end_line; i > section->start_line; i--)
141221495Sjmacd              if (dir_lines[i - 1].size != 0)
141321495Sjmacd                break;
141421495Sjmacd            section->end_line = i;
141521495Sjmacd
141621495Sjmacd            for (spec = input_sections; spec; spec = spec->next)
141721495Sjmacd              if (!strcmp (spec->name, section->name))
141821495Sjmacd                break;
141921495Sjmacd            if (spec)
142021495Sjmacd              {
142121495Sjmacd                int add_at_line = section->end_line;
142221495Sjmacd                struct spec_entry *entry;
142321495Sjmacd                /* Say we have found at least one section with this name,
142421495Sjmacd                   so we need not add such a section.  */
142521495Sjmacd                spec->missing = 0;
142621495Sjmacd                /* For each entry, find the right place in this section
142721495Sjmacd                   to add it.  */
142821495Sjmacd                for (entry = entries_to_add; entry; entry = entry->next)
142921495Sjmacd                  {
143056165Sru                    /* Did they at all want this entry to be put into
143156165Sru                       this section?  */
143256165Sru                    for (spec = entry->entry_sections;
143356165Sru                         spec && spec != entry->entry_sections_tail;
143456165Sru                         spec = spec->next)
143556165Sru                      {
143656165Sru                        if (!strcmp (spec->name, section->name))
143756165Sru                          break;
143856165Sru                      }
143956165Sru                    if (!spec || spec == entry->entry_sections_tail)
144056165Sru                      continue;
1441116531Sru
144221495Sjmacd                    /* Subtract one because dir_lines is zero-based,
144321495Sjmacd                       but the `end_line' and `start_line' members are
144421495Sjmacd                       one-based.  */
144521495Sjmacd                    for (i = section->end_line - 1;
144621495Sjmacd                         i >= section->start_line - 1; i--)
144721495Sjmacd                      {
144821495Sjmacd                        /* If an entry exists with the same name,
144921495Sjmacd                           and was not marked for deletion
145021495Sjmacd                           (which means it is for some other file),
145121495Sjmacd                           we are in trouble.  */
145221495Sjmacd                        if (dir_lines[i].start[0] == '*'
145356165Sru                            && menu_line_equal (entry->text, entry->text_len,
145421495Sjmacd                                                dir_lines[i].start,
145521495Sjmacd                                                dir_lines[i].size)
145621495Sjmacd                            && !dir_lines[i].delete)
145756535Sru			  {
145856535Sru			    if (quiet_flag)
145956535Sru			      dir_lines[i].delete = 1;
146056535Sru			    else
146156535Sru			      fatal (_("menu item `%s' already exists, for file `%s'"),
146221495Sjmacd                                 extract_menu_item_name (entry->text),
146321495Sjmacd                                 extract_menu_file_name (dir_lines[i].start));
146456535Sru			  }
146521495Sjmacd                        if (dir_lines[i].start[0] == '*'
146656165Sru                            && menu_line_lessp (entry->text, entry->text_len,
146721495Sjmacd                                                dir_lines[i].start,
146821495Sjmacd                                                dir_lines[i].size))
146921495Sjmacd                          add_at_line = i;
147021495Sjmacd                      }
147121495Sjmacd                    insert_entry_here (entry, add_at_line,
147221495Sjmacd                                       dir_lines, n_entries_to_add);
147321495Sjmacd                  }
147421495Sjmacd              }
147521495Sjmacd          }
147621495Sjmacd
147721495Sjmacd      /* Mark the end of the Top node as the place to add any
147821495Sjmacd         new sections that are needed.  */
147921495Sjmacd      for (node = dir_nodes; node; node = node->next)
148021495Sjmacd        if (node->name && strcmp (node->name, "Top") == 0)
148121495Sjmacd          dir_lines[node->end_line].add_sections_before = 1;
148221495Sjmacd    }
148321495Sjmacd
148421495Sjmacd  if (delete_flag && !something_deleted && !quiet_flag)
148593143Sru    warning (_("no entries found for `%s'; nothing deleted"), infile, 0);
148621495Sjmacd
148756165Sru  output_dirfile (opened_dirfilename, dir_nlines, dir_lines, n_entries_to_add,
148856165Sru                  entries_to_add, input_sections, compression_program);
148921495Sjmacd
149056165Sru  xexit (0);
1491116531Sru  return 0; /* Avoid bogus warnings.  */
149221495Sjmacd}
149321495Sjmacd
149421495Sjmacd/* Divide the text at DATA (of SIZE bytes) into lines.
149521495Sjmacd   Return a vector of struct line_data describing the lines.
149621495Sjmacd   Store the length of that vector into *NLINESP.  */
149721495Sjmacd
149821495Sjmacdstruct line_data *
1499146521Srufindlines (char *data, int size, int *nlinesp)
150021495Sjmacd{
150156165Sru  int i;
150256165Sru  int lineflag = 1;
150356165Sru  int lines_allocated = 511;
150421495Sjmacd  int filled = 0;
150556165Sru  struct line_data *lines
150656165Sru    = xmalloc ((lines_allocated + 1) * sizeof (struct line_data));
150721495Sjmacd
150821495Sjmacd  for (i = 0; i < size; i++)
150921495Sjmacd    {
151021495Sjmacd      if (lineflag)
151121495Sjmacd        {
151221495Sjmacd          if (filled == lines_allocated)
151321495Sjmacd            {
151456165Sru              /* try to keep things somewhat page-aligned */
151556165Sru              lines_allocated = ((lines_allocated + 1) * 2) - 1;
151656165Sru              lines = xrealloc (lines, (lines_allocated + 1)
151756165Sru                                       * sizeof (struct line_data));
151821495Sjmacd            }
151921495Sjmacd          lines[filled].start = &data[i];
152021495Sjmacd          lines[filled].add_entries_before = 0;
152121495Sjmacd          lines[filled].add_sections_before = 0;
152221495Sjmacd          lines[filled].delete = 0;
152321495Sjmacd          if (filled > 0)
152421495Sjmacd            lines[filled - 1].size
152521495Sjmacd              = lines[filled].start - lines[filled - 1].start - 1;
152621495Sjmacd          filled++;
152721495Sjmacd        }
152821495Sjmacd      lineflag = (data[i] == '\n');
152921495Sjmacd    }
153021495Sjmacd  if (filled > 0)
153121495Sjmacd    lines[filled - 1].size = &data[i] - lines[filled - 1].start - lineflag;
153221495Sjmacd
153321495Sjmacd  /* Do not leave garbage in the last element.  */
153421495Sjmacd  lines[filled].start = NULL;
153521495Sjmacd  lines[filled].add_entries_before = NULL;
153621495Sjmacd  lines[filled].add_sections_before = 0;
153721495Sjmacd  lines[filled].delete = 0;
153821495Sjmacd  lines[filled].size = 0;
153921495Sjmacd
154021495Sjmacd  *nlinesp = filled;
154121495Sjmacd  return lines;
154221495Sjmacd}
154321495Sjmacd
1544146521Sru/* This is the comparison function for qsort for a vector of pointers to
1545146521Sru   struct spec_section.  (Have to use const void * as the parameter type
1546146521Sru   to avoid incompatible-with-qsort warnings.)
154721495Sjmacd   Compare the section names.  */
154821495Sjmacd
154921495Sjmacdint
1550146521Srucompare_section_names (const void *p1, const void *p2)
155121495Sjmacd{
1552146521Sru  struct spec_section **sec1 = (struct spec_section **) p1;
1553146521Sru  struct spec_section **sec2 = (struct spec_section **) p2;
155421495Sjmacd  char *name1 = (*sec1)->name;
155521495Sjmacd  char *name2 = (*sec2)->name;
155621495Sjmacd  return strcmp (name1, name2);
155721495Sjmacd}
155821495Sjmacd
155956165Sru/* This is the comparison function for qsort
156056165Sru   for a vector of pointers to struct spec_entry.
156156165Sru   Compare the entries' text.  */
156256165Sru
156356165Sruint
1564146521Srucompare_entries_text (const void *p1, const void *p2)
156556165Sru{
1566146521Sru  struct spec_entry **entry1 = (struct spec_entry **) p1;
1567146521Sru  struct spec_entry **entry2 = (struct spec_entry **) p2;
156856165Sru  char *text1 = (*entry1)->text;
156956165Sru  char *text2 = (*entry2)->text;
157056165Sru  char *colon1 = strchr (text1, ':');
157156165Sru  char *colon2 = strchr (text2, ':');
157256165Sru  int len1, len2;
157356165Sru
157456165Sru  if (!colon1)
157556165Sru    len1 = strlen (text1);
157656165Sru  else
157756165Sru    len1 = colon1 - text1;
157856165Sru  if (!colon2)
157956165Sru    len2 = strlen (text2);
158056165Sru  else
158156165Sru    len2 = colon2 - text2;
158256165Sru  return strncmp (text1, text2, len1 <= len2 ? len1 : len2);
158356165Sru}
158456165Sru
158521495Sjmacd/* Insert ENTRY into the add_entries_before vector
158621495Sjmacd   for line number LINE_NUMBER of the dir file.
158721495Sjmacd   DIR_LINES and N_ENTRIES carry information from like-named variables
158821495Sjmacd   in main.  */
158921495Sjmacd
159021495Sjmacdvoid
1591146521Sruinsert_entry_here (struct spec_entry *entry, int line_number,
1592146521Sru                   struct line_data *dir_lines, int n_entries)
159321495Sjmacd{
159456165Sru  int i, j;
159521495Sjmacd
159621495Sjmacd  if (dir_lines[line_number].add_entries_before == 0)
159721495Sjmacd    {
159821495Sjmacd      dir_lines[line_number].add_entries_before
159921495Sjmacd        = (struct spec_entry **) xmalloc (n_entries * sizeof (struct spec_entry *));
160021495Sjmacd      for (i = 0; i < n_entries; i++)
160121495Sjmacd        dir_lines[line_number].add_entries_before[i] = 0;
160221495Sjmacd    }
160321495Sjmacd
160456165Sru  /* Find the place where this entry belongs.  If there are already
160556165Sru     several entries to add before LINE_NUMBER, make sure they are in
160656165Sru     alphabetical order.  */
160721495Sjmacd  for (i = 0; i < n_entries; i++)
160856165Sru    if (dir_lines[line_number].add_entries_before[i] == 0
160956165Sru        || menu_line_lessp (entry->text, strlen (entry->text),
161056165Sru                            dir_lines[line_number].add_entries_before[i]->text,
161156165Sru                            strlen (dir_lines[line_number].add_entries_before[i]->text)))
161221495Sjmacd      break;
161321495Sjmacd
161421495Sjmacd  if (i == n_entries)
161521495Sjmacd    abort ();
161621495Sjmacd
161756165Sru  /* If we need to plug ENTRY into the middle of the
161856165Sru     ADD_ENTRIES_BEFORE array, move the entries which should be output
161956165Sru     after this one down one notch, before adding a new one.  */
162056165Sru  if (dir_lines[line_number].add_entries_before[i] != 0)
162156165Sru    for (j = n_entries - 1; j > i; j--)
162256165Sru      dir_lines[line_number].add_entries_before[j]
162356165Sru        = dir_lines[line_number].add_entries_before[j - 1];
162456165Sru
162521495Sjmacd  dir_lines[line_number].add_entries_before[i] = entry;
162621495Sjmacd}
1627