#include "system.h"
#include <getopt.h>
#ifdef HAVE_LIBZ
#include <zlib.h>
#endif
char *progname;
char *readfile ();
struct line_data *findlines ();
void fatal ();
void insert_entry_here ();
int compare_section_names ();
struct spec_entry;
struct line_data
{
char *start;
int size;
struct spec_entry **add_entries_before;
int add_sections_before;
int delete;
};
struct spec_section
{
struct spec_section *next;
char *name;
int missing;
};
struct spec_entry
{
struct spec_entry *next;
char *text;
};
struct node
{
struct node *next;
char *name;
int start_line;
int end_line;
char *menu_start;
struct menu_section *sections;
struct menu_section *last_section;
};
struct menu_section
{
struct menu_section *next;
char *name;
int start_line;
int end_line;
};
void *
xmalloc (size)
unsigned int size;
{
extern void *malloc ();
void *result = malloc (size);
if (result == NULL)
fatal (_("virtual memory exhausted"), 0);
return result;
}
void *
xrealloc (obj, size)
void *obj;
unsigned int size;
{
extern void *realloc ();
void *result = realloc (obj, size);
if (result == NULL)
fatal (_("virtual memory exhausted"), 0);
return result;
}
char *
concat (s1, s2, s3)
char *s1, *s2, *s3;
{
int len1 = strlen (s1), len2 = strlen (s2), len3 = strlen (s3);
char *result = (char *) xmalloc (len1 + len2 + len3 + 1);
strcpy (result, s1);
strcpy (result + len1, s2);
strcpy (result + len1 + len2, s3);
*(result + len1 + len2 + len3) = 0;
return result;
}
char *
copy_string (string, size)
char *string;
int size;
{
int i;
char *copy = (char *) xmalloc (size + 1);
for (i = 0; i < size; i++)
copy[i] = string[i];
copy[size] = 0;
return copy;
}
void
error (s1, s2, s3)
char *s1, *s2, *s3;
{
fprintf (stderr, "%s: ", progname);
fprintf (stderr, s1, s2, s3);
putc ('\n', stderr);
}
void
warning (s1, s2, s3)
char *s1, *s2, *s3;
{
fprintf (stderr, _("%s: warning: "), progname);
fprintf (stderr, s1, s2, s3);
putc ('\n', stderr);
}
void
fatal (s1, s2, s3)
char *s1, *s2, *s3;
{
error (s1, s2, s3);
exit (1);
}
void
pfatal_with_name (name)
char *name;
{
char *s = concat ("", strerror (errno), _(" for %s"));
fatal (s, name);
}
char *
extract_menu_item_name (item_text)
char *item_text;
{
char *p;
if (*item_text == '*')
item_text++;
while (*item_text == ' ')
item_text++;
p = item_text;
while (*p && *p != ':') p++;
return copy_string (item_text, p - item_text);
}
char *
extract_menu_file_name (item_text)
char *item_text;
{
char *p = item_text;
if (*p == '*')
p++;
while (*p == ' ')
p++;
while (*p && *p != '\n' && *p != ':') p++;
if (*p == ':') p++;
while (1)
{
if (*p == '(')
break;
else if (*p == ' ' || *p == '\t')
p++;
else
return "(none)";
}
p++;
item_text = p;
while (*p && *p != '\n' && *p != ')') p++;
if (*p != ')')
return "(none)";
return copy_string (item_text, p - item_text);
}
void
suggest_asking_for_help ()
{
fprintf (stderr, _("\tTry `%s --help' for a complete list of options.\n"),
progname);
exit (1);
}
void
print_help ()
{
printf (_("Usage: %s [OPTION]... [INFO-FILE [DIR-FILE]]\n\
\n\
Install INFO-FILE in the Info directory file DIR-FILE.\n\
\n\
Options:\n\
--delete Delete existing entries in INFO-FILE;\n\
don't insert any new entries.\n\
--dir-file=NAME Specify file name of Info directory file.\n\
This is equivalent to using the DIR-FILE argument.\n\
--entry=TEXT Insert TEXT as an Info directory entry.\n\
TEXT should have the form of an Info menu item line\n\
plus zero or more extra lines starting with whitespace.\n\
If you specify more than one entry, they are all added.\n\
If you don't specify any entries, they are determined\n\
from information in the Info file itself.\n\
--help Display this help and exit.\n\
--info-file=FILE Specify Info file to install in the directory.\n\
This is equivalent to using the INFO-FILE argument.\n\
--info-dir=DIR Same as --dir-file=DIR/dir.\n\
--item=TEXT Same as --entry TEXT.\n\
An Info directory entry is actually a menu item.\n\
--quiet Suppress warnings.\n\
--remove Same as --delete.\n\
--section=SEC Put this file's entries in section SEC of the directory.\n\
If you specify more than one section, all the entries\n\
are added in each of the sections.\n\
If you don't specify any sections, they are determined\n\
from information in the Info file itself.\n\
--version Display version information and exit.\n\
\n\
Email bug reports to bug-texinfo@gnu.org.\n\
"), progname);
}
void
ensure_dirfile_exists (dirfile)
char *dirfile;
{
int desc = open (dirfile, O_RDONLY);
if (desc < 0 && errno == ENOENT)
{
FILE *f;
char *readerr = strerror (errno);
close (desc);
f = fopen (dirfile, "w");
if (f)
{
fputs (_("This is the file .../info/dir, which contains the\n\
topmost node of the Info hierarchy, called (dir)Top.\n\
The first time you invoke Info you start off looking at this node.\n\
\n\
File: dir,\tNode: Top,\tThis is the top of the INFO tree\n\
\n\
This (the Directory node) gives a menu of major topics.\n\
Typing \"q\" exits, \"?\" lists all Info commands, \"d\" returns here,\n\
\"h\" gives a primer for first-timers,\n\
\"mEmacs<Return>\" visits the Emacs manual, etc.\n\
\n\
In Emacs, you can click mouse button 2 on a menu item or cross reference\n\
to select it.\n\
\n\
* Menu:\n\
"), f);
if (fclose (f) < 0)
pfatal_with_name (dirfile);
}
else
{
fprintf (stderr,
_("%s: could not read (%s) and could not create (%s)\n"),
dirfile, readerr, strerror (errno));
exit (1);
}
}
else
close (desc);
}
struct option longopts[] =
{
{ "delete", no_argument, NULL, 'r' },
{ "dir-file", required_argument, NULL, 'd' },
{ "entry", required_argument, NULL, 'e' },
{ "help", no_argument, NULL, 'h' },
{ "info-dir", required_argument, NULL, 'D' },
{ "info-file", required_argument, NULL, 'i' },
{ "item", required_argument, NULL, 'e' },
{ "quiet", no_argument, NULL, 'q' },
{ "remove", no_argument, NULL, 'r' },
{ "section", required_argument, NULL, 's' },
{ "version", no_argument, NULL, 'V' },
{ 0 }
};
int
main (argc, argv)
int argc;
char **argv;
{
char *infile = 0, *dirfile = 0;
char *infile_sans_info;
unsigned infilelen_sans_info;
FILE *output;
char *input_data;
int input_size;
struct line_data *input_lines;
int input_nlines;
struct spec_section *input_sections = NULL;
struct spec_entry *entries_to_add = NULL;
int n_entries_to_add = 0;
char *dir_data;
int dir_size;
int dir_nlines;
struct line_data *dir_lines;
struct node *dir_nodes;
int delete_flag = 0;
int something_deleted = 0;
int quiet_flag = 0;
int node_header_flag;
int prefix_length;
int i;
progname = argv[0];
#ifdef HAVE_SETLOCALE
setlocale (LC_ALL, "");
#endif
bindtextdomain (PACKAGE, LOCALEDIR);
textdomain (PACKAGE);
while (1)
{
int opt = getopt_long (argc, argv, "i:d:e:s:hHr", longopts, 0);
if (opt == EOF)
break;
switch (opt)
{
case 0:
break;
case 1:
abort ();
case 'd':
if (dirfile)
{
fprintf (stderr, _("%s: Specify the Info directory only once.\n"),
progname);
suggest_asking_for_help ();
}
dirfile = optarg;
break;
case 'D':
if (dirfile)
{
fprintf (stderr, _("%s: Specify the Info directory only once.\n"),
progname);
suggest_asking_for_help ();
}
dirfile = concat (optarg, "", "/dir");
break;
case 'e':
{
struct spec_entry *next
= (struct spec_entry *) xmalloc (sizeof (struct spec_entry));
if (! (*optarg != 0 && optarg[strlen (optarg) - 1] == '\n'))
optarg = concat (optarg, "\n", "");
next->text = optarg;
next->next = entries_to_add;
entries_to_add = next;
n_entries_to_add++;
}
break;
case 'h':
case 'H':
print_help ();
exit (0);
case 'i':
if (infile)
{
fprintf (stderr, _("%s: Specify the Info file only once.\n"),
progname);
suggest_asking_for_help ();
}
infile = optarg;
break;
case 'q':
quiet_flag = 1;
break;
case 'r':
delete_flag = 1;
break;
case 's':
{
struct spec_section *next
= (struct spec_section *) xmalloc (sizeof (struct spec_section));
next->name = optarg;
next->next = input_sections;
next->missing = 1;
input_sections = next;
}
break;
case 'V':
printf ("install-info (GNU %s) %s\n", PACKAGE, VERSION);
printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\
There is NO warranty. You may redistribute this software\n\
under the terms of the GNU General Public License.\n\
For more information about these matters, see the files named COPYING.\n"),
"1998");
exit (0);
default:
suggest_asking_for_help ();
}
}
for (; optind < argc; ++optind)
{
if (infile == 0)
infile = argv[optind];
else if (dirfile == 0)
dirfile = argv[optind];
else
error (_("excess command line argument `%s'"), argv[optind]);
}
if (!infile)
fatal (_("No input file specified; try --help for more information."));
if (!dirfile)
fatal (_("No dir file specified; try --help for more information."));
input_data = readfile (infile, &input_size);
input_lines = findlines (input_data, input_size, &input_nlines);
if (input_sections == 0)
{
prefix_length = strlen ("INFO-DIR-SECTION ");
for (i = 0; i < input_nlines; i++)
{
if (!strncmp ("INFO-DIR-SECTION ", input_lines[i].start,
prefix_length))
{
struct spec_section *next
= (struct spec_section *) xmalloc (sizeof (struct spec_section));
next->name = copy_string (input_lines[i].start + prefix_length,
input_lines[i].size - prefix_length);
next->next = input_sections;
next->missing = 1;
input_sections = next;
}
}
}
if (input_sections == 0)
{
input_sections
= (struct spec_section *) xmalloc (sizeof (struct spec_section));
input_sections->name = "Miscellaneous";
input_sections->next = 0;
input_sections->missing = 1;
}
if (entries_to_add == 0)
{
char *start_of_this_entry = 0;
for (i = 0; i < input_nlines; i++)
{
if (!strncmp ("START-INFO-DIR-ENTRY", input_lines[i].start,
input_lines[i].size)
&& sizeof ("START-INFO-DIR-ENTRY") - 1 == input_lines[i].size)
{
if (start_of_this_entry != 0)
fatal (_("START-INFO-DIR-ENTRY without matching END-INFO-DIR-ENTRY"));
start_of_this_entry = input_lines[i + 1].start;
}
if (!strncmp ("END-INFO-DIR-ENTRY", input_lines[i].start,
input_lines[i].size)
&& sizeof ("END-INFO-DIR-ENTRY") - 1 == input_lines[i].size)
{
if (start_of_this_entry != 0)
{
struct spec_entry *next
= (struct spec_entry *) xmalloc (sizeof (struct spec_entry));
next->text = copy_string (start_of_this_entry,
input_lines[i].start - start_of_this_entry);
next->next = entries_to_add;
entries_to_add = next;
n_entries_to_add++;
start_of_this_entry = 0;
}
else
fatal (_("END-INFO-DIR-ENTRY without matching START-INFO-DIR-ENTRY"));
}
}
if (start_of_this_entry != 0)
fatal (_("START-INFO-DIR-ENTRY without matching END-INFO-DIR-ENTRY"));
}
if (!delete_flag)
if (entries_to_add == 0)
{
warning (_("no info dir entry in `%s'"), infile);
exit (0);
}
ensure_dirfile_exists (dirfile);
dir_data = readfile (dirfile, &dir_size);
dir_lines = findlines (dir_data, dir_size, &dir_nlines);
{
unsigned basename_len;
char *infile_basename = strrchr (infile, '/');
if (infile_basename)
infile_basename++;
else
infile_basename = infile;
basename_len = strlen (infile_basename);
infile_sans_info
= (strlen (infile_basename) > 5
&& strcmp (infile_basename + basename_len - 5, ".info") == 0)
? copy_string (infile_basename, basename_len - 5)
: infile_basename;
infilelen_sans_info = strlen (infile_sans_info);
}
dir_nodes = 0;
node_header_flag = 0;
for (i = 0; i < dir_nlines; i++)
{
if (node_header_flag)
{
int j, end;
for (j = 0; j < dir_lines[i].size; j++)
if (!strncmp ("Node:", dir_lines[i].start + j, 5))
{
char *line = dir_lines[i].start;
j += 5;
while (line[j] == ' ' || line[j] == '\t')
j++;
end = j;
while (line[end] != 0 && line[end] != ',' && line[end] != '\n'
&& line[end] != '\t')
end++;
dir_nodes->name = copy_string (line + j, end - j);
}
node_header_flag = 0;
}
if (*dir_lines[i].start == 037)
{
struct node *next
= (struct node *) xmalloc (sizeof (struct node));
next->next = dir_nodes;
next->name = NULL;
next->start_line = i;
next->end_line = 0;
next->menu_start = NULL;
next->sections = NULL;
next->last_section = NULL;
if (dir_nodes != 0)
dir_nodes->end_line = i;
if (dir_nodes != 0 && dir_nodes->last_section != 0)
dir_nodes->last_section->end_line = i;
dir_nodes = next;
node_header_flag = 1;
}
if (dir_nodes != 0
&& !strncmp ("* Menu:", dir_lines[i].start, 7))
dir_nodes->menu_start = dir_lines[i + 1].start;
if (dir_nodes != 0
&& dir_nodes->menu_start != 0
&& *dir_lines[i].start != '\n'
&& *dir_lines[i].start != '*'
&& *dir_lines[i].start != ' '
&& *dir_lines[i].start != '\t')
{
struct menu_section *next
= (struct menu_section *) xmalloc (sizeof (struct menu_section));
next->start_line = i + 1;
next->next = 0;
next->end_line = 0;
next->name = copy_string (dir_lines[i].start, dir_lines[i].size);
if (dir_nodes->sections)
{
dir_nodes->last_section->next = next;
dir_nodes->last_section->end_line = i;
}
else
dir_nodes->sections = next;
dir_nodes->last_section = next;
}
if (*dir_lines[i].start == '*')
{
char *p = dir_lines[i].start;
while (*p != 0 && *p != ':')
p++;
p++;
while (*p == ' ') p++;
if (*p == '(')
{
p++;
if ((dir_lines[i].size
> (p - dir_lines[i].start + infilelen_sans_info))
&& !strncmp (p, infile_sans_info, infilelen_sans_info)
&& (p[infilelen_sans_info] == ')'
|| !strncmp (p + infilelen_sans_info, ".info)", 6)))
{
dir_lines[i].delete = 1;
something_deleted = 1;
}
}
}
else if (i > 0
&& (*dir_lines[i].start == ' '
|| *dir_lines[i].start == '\t'))
{
dir_lines[i].delete = dir_lines[i - 1].delete;
something_deleted = 1;
}
}
if (dir_nodes != 0)
{
dir_nodes->end_line = dir_nlines;
if (dir_nodes->last_section != 0)
dir_nodes->last_section->end_line = dir_nlines;
}
if (!delete_flag)
{
struct node *node;
struct menu_section *section;
struct spec_section *spec;
for (node = dir_nodes; node; node = node->next)
for (section = node->sections; section; section = section->next)
{
for (i = section->end_line; i > section->start_line; i--)
if (dir_lines[i - 1].size != 0)
break;
section->end_line = i;
for (spec = input_sections; spec; spec = spec->next)
if (!strcmp (spec->name, section->name))
break;
if (spec)
{
int add_at_line = section->end_line;
struct spec_entry *entry;
spec->missing = 0;
for (entry = entries_to_add; entry; entry = entry->next)
{
int textlen = strlen (entry->text);
for (i = section->end_line - 1;
i >= section->start_line - 1; i--)
{
if (dir_lines[i].start[0] == '*'
&& menu_line_equal (entry->text, textlen,
dir_lines[i].start,
dir_lines[i].size)
&& !dir_lines[i].delete)
fatal (_("menu item `%s' already exists, for file `%s'"),
extract_menu_item_name (entry->text),
extract_menu_file_name (dir_lines[i].start));
if (dir_lines[i].start[0] == '*'
&& menu_line_lessp (entry->text, textlen,
dir_lines[i].start,
dir_lines[i].size))
add_at_line = i;
}
insert_entry_here (entry, add_at_line,
dir_lines, n_entries_to_add);
}
}
}
for (node = dir_nodes; node; node = node->next)
if (node->name && strcmp (node->name, "Top") == 0)
dir_lines[node->end_line].add_sections_before = 1;
}
if (delete_flag && !something_deleted && !quiet_flag)
warning (_("no entries found for `%s'; nothing deleted"), infile);
output = fopen (dirfile, "w");
if (!output)
{
perror (dirfile);
exit (1);
}
for (i = 0; i <= dir_nlines; i++)
{
int j;
if (dir_lines[i].add_entries_before)
for (j = 0; j < n_entries_to_add; j++)
{
struct spec_entry *this = dir_lines[i].add_entries_before[j];
if (this == 0)
break;
fputs (this->text, output);
}
if (dir_lines[i].add_sections_before)
{
struct spec_section *spec;
struct spec_section **sections;
int n_sections = 0;
for (spec = input_sections; spec; spec = spec->next)
n_sections++;
sections = ((struct spec_section **)
xmalloc (n_sections * sizeof (struct spec_section *)));
j = 0;
for (spec = input_sections; spec; spec = spec->next)
sections[j++] = spec;
qsort (sections, n_sections, sizeof (struct spec_section *),
compare_section_names);
for (j = 0; j < n_sections; j++)
{
spec = sections[j];
if (spec->missing)
{
struct spec_entry *entry;
putc ('\n', output);
fputs (spec->name, output);
putc ('\n', output);
for (entry = entries_to_add; entry; entry = entry->next)
fputs (entry->text, output);
}
}
free (sections);
}
if (i < dir_nlines && !dir_lines[i].delete)
{
fwrite (dir_lines[i].start, 1, dir_lines[i].size, output);
putc ('\n', output);
}
}
fclose (output);
exit (0);
}
char *
readfile (filename, sizep)
char *filename;
int *sizep;
{
int desc;
int data_size = 1024;
char *data = (char *) xmalloc (data_size);
int filled = 0;
int nread = 0;
#ifdef HAVE_LIBZ
int isGZ = 0;
gzFile zdesc;
#endif
desc = open (filename, O_RDONLY);
if (desc < 0)
pfatal_with_name (filename);
#ifdef HAVE_LIBZ
if (read (desc, data, 2) != 2)
pfatal_with_name (filename);
lseek (desc, 0, SEEK_SET);
if (data[0] == '\x1f' && data[1] == '\x8b')
{
isGZ = 1;
zdesc = gzdopen (desc, "r");
if (zdesc == NULL) {
close (desc);
pfatal_with_name (filename);
}
}
#endif
while (1)
{
#ifdef HAVE_LIBZ
if (isGZ)
nread = gzread (zdesc, data + filled, data_size - filled);
else
#endif
nread = read (desc, data + filled, data_size - filled);
if (nread < 0)
pfatal_with_name (filename);
if (nread == 0)
break;
filled += nread;
if (filled == data_size)
{
data_size *= 2;
data = (char *) xrealloc (data, data_size);
}
}
*sizep = filled;
#ifdef HAVE_LIBZ
if (isGZ)
gzclose (zdesc);
else
#endif
close(desc);
return data;
}
struct line_data *
findlines (data, size, nlinesp)
char *data;
int size;
int *nlinesp;
{
struct line_data *lines;
int lines_allocated = 512;
int filled = 0;
int i = 0;
int lineflag;
lines = (struct line_data *) xmalloc (lines_allocated * sizeof (struct line_data));
lineflag = 1;
for (i = 0; i < size; i++)
{
if (lineflag)
{
if (filled == lines_allocated)
{
lines_allocated *= 2;
lines = (struct line_data *) xrealloc (lines, lines_allocated * sizeof (struct line_data));
}
lines[filled].start = &data[i];
lines[filled].add_entries_before = 0;
lines[filled].add_sections_before = 0;
lines[filled].delete = 0;
if (filled > 0)
lines[filled - 1].size
= lines[filled].start - lines[filled - 1].start - 1;
filled++;
}
lineflag = (data[i] == '\n');
}
if (filled > 0)
lines[filled - 1].size = &data[i] - lines[filled - 1].start - lineflag;
lines[filled].start = NULL;
lines[filled].add_entries_before = NULL;
lines[filled].add_sections_before = 0;
lines[filled].delete = 0;
lines[filled].size = 0;
*nlinesp = filled;
return lines;
}
int
menu_line_lessp (line1, len1, line2, len2)
char *line1;
int len1;
char *line2;
int len2;
{
int minlen = (len1 < len2 ? len1 : len2);
int i;
for (i = 0; i < minlen; i++)
{
if (line1[i] == ':' && line2[i] != ':')
return 1;
if (line2[i] == ':' && line1[i] != ':')
return 0;
if (line1[i] < line2[i])
return 1;
if (line1[i] > line2[i])
return 0;
}
return 0;
}
int
menu_line_equal (line1, len1, line2, len2)
char *line1;
int len1;
char *line2;
int len2;
{
int minlen = (len1 < len2 ? len1 : len2);
int i;
for (i = 0; i < minlen; i++)
{
if (line1[i] == ':' && line2[i] == ':')
return 1;
if (line1[i] != line2[i])
return 0;
}
return 1;
}
int
compare_section_names (sec1, sec2)
struct spec_section **sec1, **sec2;
{
char *name1 = (*sec1)->name;
char *name2 = (*sec2)->name;
return strcmp (name1, name2);
}
void
insert_entry_here (entry, line_number, dir_lines, n_entries)
struct spec_entry *entry;
int line_number;
struct line_data *dir_lines;
int n_entries;
{
int i;
if (dir_lines[line_number].add_entries_before == 0)
{
dir_lines[line_number].add_entries_before
= (struct spec_entry **) xmalloc (n_entries * sizeof (struct spec_entry *));
for (i = 0; i < n_entries; i++)
dir_lines[line_number].add_entries_before[i] = 0;
}
for (i = 0; i < n_entries; i++)
if (dir_lines[line_number].add_entries_before[i] == 0)
break;
if (i == n_entries)
abort ();
dir_lines[line_number].add_entries_before[i] = entry;
}