| /* install-info -- merge Info directory entries from an Info file. |
| |
| Copyright 1996-2022 Free Software Foundation, Inc. |
| |
| This program is free software: you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation, either version 3 of the License, or |
| (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with this program. If not, see <http://www.gnu.org/licenses/>. */ |
| |
| #include "system.h" |
| #include <getopt.h> |
| #include <regex.h> |
| #include <argz.h> |
| |
| #define TAB_WIDTH 8 |
| |
| static char *progname = "install-info"; |
| static char *default_section = NULL; |
| |
| struct spec_entry; |
| struct spec_section; |
| |
| struct line_data *findlines (char *data, int size, int *nlinesp); |
| void insert_entry_here (struct spec_entry *entry, int line_number, |
| struct line_data *dir_lines, int n_entries); |
| int compare_section_names (const void *s1, const void *s2); |
| int compare_entries_text (const void *e1, const void *e2); |
| |
| /* Data structures. */ |
| |
| |
| /* Record info about a single line from a file as read into core. */ |
| struct line_data |
| { |
| /* The start of the line. */ |
| char *start; |
| /* The number of characters in the line, |
| excluding the terminating newline. */ |
| int size; |
| /* Vector containing pointers to the entries to add before this line. |
| The vector is null-terminated. */ |
| struct spec_entry **add_entries_before; |
| /* Vector containing pointers to the sections to add before this line. |
| The vector is not null-terminated. */ |
| struct spec_section **add_sections_before; |
| /* The vector ADD_SECTIONS_BEFORE_HERE contains exactly this many |
| pointers to sections. */ |
| int num_sections_to_add; |
| /* 1 means don't output this line. */ |
| int delete; |
| }; |
| |
| |
| /* This is used for a list of the specified menu section names |
| in which entries should be added. */ |
| struct spec_section |
| { |
| struct spec_section *next; |
| char *name; |
| /* 1 means we have not yet found an existing section with this name |
| in the dir file--so we will need to add a new section. */ |
| int missing; |
| }; |
| |
| |
| /* This is used for a list of the entries specified to be added. */ |
| struct spec_entry |
| { |
| struct spec_entry *next; |
| char *text; |
| size_t text_len; |
| /* A pointer to the list of sections to which this entry should be |
| added. */ |
| struct spec_section *entry_sections; |
| /* A pointer to a section that is beyond the end of the chain whose |
| head is pointed to by entry_sections. */ |
| struct spec_section *entry_sections_tail; |
| /* Non-zero means that the entry doesn't have a name specified. This |
| can only happen if a --description preceeds a --name option. */ |
| int missing_name; |
| /* Non-zero means that the entry doesn't have a description. This |
| happens when a --name option is given prior to a --description |
| option. */ |
| int missing_description; |
| /* Non-zero means that the entry doesn't have an Info file specified. |
| This means that the entry was taken from the command-line but it |
| only contains the name, and not the info file's basename, which |
| we get later on. This only happens on entries that originate |
| from --name options. */ |
| int missing_basename; |
| }; |
| |
| |
| /* This is used for a list of nodes found by parsing the dir file. */ |
| struct node |
| { |
| struct node *next; |
| /* The node name. */ |
| char *name; |
| /* The line number of the line where the node starts. |
| This is the line that contains control-underscore. */ |
| int start_line; |
| /* The line number of the line where the node ends, |
| which is the end of the file or where the next line starts. */ |
| int end_line; |
| /* Start of first line in this node's menu |
| (the line after the * Menu: line). */ |
| char *menu_start; |
| /* The start of the chain of sections in this node's menu. */ |
| struct menu_section *sections; |
| /* The last menu section in the chain. */ |
| struct menu_section *last_section; |
| }; |
| |
| |
| /* This is used for a list of sections found in a node's menu. |
| Each struct node has such a list in the sections field. */ |
| struct menu_section |
| { |
| struct menu_section *next; |
| char *name; |
| /* Line number of start of section. */ |
| int start_line; |
| /* Line number of end of section. */ |
| int end_line; |
| }; |
| |
| /* This table defines all the long-named options, says whether they |
| use an argument, and maps them into equivalent single-letter options. */ |
| |
| struct option longopts[] = |
| { |
| { "add-once", no_argument, NULL, '1'}, |
| { "align", required_argument, NULL, 'A'}, |
| { "append-new-sections", no_argument, NULL, 'a'}, |
| { "calign", required_argument, NULL, 'C'}, |
| { "debug", no_argument, NULL, 'g' }, |
| { "delete", no_argument, NULL, 'r' }, |
| { "defentry", required_argument, NULL, 'E' }, |
| { "defsection", required_argument, NULL, 'S' }, |
| { "dir-file", required_argument, NULL, 'd' }, |
| { "entry", required_argument, NULL, 'e' }, |
| { "name", required_argument, NULL, 't' }, |
| { "menuentry", required_argument, NULL, 't' }, |
| { "description", required_argument, NULL, 'c' }, |
| { "help", no_argument, NULL, 'h' }, |
| { "no-indent", no_argument, NULL, 'I' }, |
| { "infodir", required_argument, NULL, 'D' }, |
| { "info-dir", required_argument, NULL, 'D' }, |
| { "info-file", required_argument, NULL, 'i' }, |
| { "item", required_argument, NULL, 'e' }, |
| { "keep-old", no_argument, NULL, 'k' }, |
| { "maxwidth", required_argument, NULL, 'W'}, |
| { "max-width", required_argument, NULL, 'W'}, |
| { "quiet", no_argument, NULL, 'q' }, |
| { "remove", no_argument, NULL, 'r' }, |
| { "remove-exactly", no_argument, NULL, 'x' }, |
| { "section", required_argument, NULL, 's' }, |
| { "regex", required_argument, NULL, 'R' }, |
| { "silent", no_argument, NULL, 'q' }, |
| { "test", no_argument, NULL, 'n' }, |
| { "dry-run", no_argument, NULL, 'n' }, |
| { "version", no_argument, NULL, 'V' }, |
| { 0 } |
| }; |
| |
| regex_t *psecreg = NULL; |
| |
| /* Nonzero means that the name specified for the Info file will be used |
| (without removing .gz, .info extension or leading path) to match the |
| entries that must be removed. */ |
| int remove_exactly = 0; |
| |
| /* Nonzero means that sections that don't have entries in them will be |
| deleted. */ |
| int remove_empty_sections = 1; |
| |
| /* Nonzero means that new Info entries into the DIR file go into all |
| sections that match with --section-regex or --section. Zero means |
| that new entries go into only the first section that matches. */ |
| int add_entries_into_all_matching_sections = 1; |
| |
| /* Nonzero means we do not replace same-named info entries. */ |
| int keep_old_flag = 0; |
| |
| /* Nonzero means --test was specified, to inhibit updating the dir file. */ |
| int chicken_flag = 0; |
| |
| /* Zero means that entries will not be formatted when they are either |
| added or replaced. */ |
| int indent_flag = 1; |
| |
| /* Zero means that new sections will be added at the end of the DIR file. */ |
| int order_new_sections_alphabetically_flag = 1; |
| |
| |
| /* Error message functions. */ |
| |
| void |
| vdiag (const char *fmt, const char *diagtype, va_list ap) |
| { |
| fprintf (stderr, "%s: ", progname); |
| if (diagtype) |
| fprintf (stderr, "%s: ", diagtype); |
| vfprintf (stderr, fmt, ap); |
| putc ('\n', stderr); |
| } |
| |
| void |
| error (const char *fmt, ...) |
| { |
| va_list ap; |
| |
| va_start (ap, fmt); |
| vdiag (fmt, NULL, ap); |
| va_end (ap); |
| } |
| |
| /* VARARGS1 */ |
| void |
| warning (const char *fmt, ...) |
| { |
| va_list ap; |
| |
| va_start (ap, fmt); |
| vdiag (fmt, "warning", ap); |
| va_end (ap); |
| } |
| |
| /* Print error message and exit. */ |
| |
| void |
| fatal (const char *fmt, ...) |
| { |
| va_list ap; |
| |
| va_start (ap, fmt); |
| vdiag (fmt, NULL, ap); |
| va_end (ap); |
| exit (EXIT_FAILURE); |
| } |
| |
| /* Return a newly-allocated string |
| whose contents concatenate those of S1, S2, S3. */ |
| char * |
| concat (const char *s1, const char *s2, const char *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; |
| } |
| |
| /* Return a string containing SIZE characters |
| copied from starting at STRING. */ |
| |
| char * |
| copy_string (const 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; |
| } |
| |
| /* Print fatal error message based on errno, with file name NAME. */ |
| |
| void |
| pfatal_with_name (const char *name) |
| { |
| /* Empty files don't set errno, so we get something like |
| "install-info: No error for foo", which is confusing. */ |
| if (errno == 0) |
| fatal (_("%s: empty file"), name); |
| |
| fatal (_("%s for %s"), strerror (errno), name); |
| } |
| |
| /* Compare the menu item names in LINE1 (line length LEN1) |
| and LINE2 (line length LEN2). Return 1 if the item name |
| in LINE1 is less, 0 otherwise. */ |
| |
| static int |
| menu_line_lessp (char *line1, int len1, char *line2, int len2) |
| { |
| int minlen = (len1 < len2 ? len1 : len2); |
| int i; |
| |
| for (i = 0; i < minlen; i++) |
| { |
| /* If one item name is a prefix of the other, |
| the former one is less. */ |
| if (line1[i] == ':' && line2[i] != ':') |
| return 1; |
| if (line2[i] == ':' && line1[i] != ':') |
| return 0; |
| /* If they both continue and differ, one is less. */ |
| if (line1[i] < line2[i]) |
| return 1; |
| if (line1[i] > line2[i]) |
| return 0; |
| } |
| /* With a properly formatted dir file, |
| we can only get here if the item names are equal. */ |
| return 0; |
| } |
| |
| /* Compare the menu item names in LINE1 (line length LEN1) |
| and LINE2 (line length LEN2). Return 1 if the item names are equal, |
| 0 otherwise. */ |
| |
| static int |
| menu_line_equal (char *line1, int len1, char *line2, int len2) |
| { |
| int minlen = (len1 < len2 ? len1 : len2); |
| int i; |
| |
| for (i = 0; i < minlen; i++) |
| { |
| /* If both item names end here, they are equal. */ |
| if (line1[i] == ':' && line2[i] == ':') |
| return 1; |
| /* If they both continue and differ, one is less. */ |
| if (line1[i] != line2[i]) |
| return 0; |
| } |
| /* With a properly formatted dir file, |
| we can only get here if the item names are equal. */ |
| return 1; |
| } |
| |
| |
| /* Given the full text of a menu entry, null terminated, |
| return just the menu item name (copied). */ |
| |
| char * |
| extract_menu_item_name (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); |
| } |
| |
| |
| |
| /* Return FNAME with any [.info][.gz] suffix removed. */ |
| |
| static char * |
| strip_info_suffix (char *fname) |
| { |
| char *ret = xstrdup (fname); |
| unsigned len = strlen (ret); |
| |
| if (len > 3 && FILENAME_CMP (ret + len - 3, ".gz") == 0) |
| { |
| len -= 3; |
| ret[len] = 0; |
| } |
| else if (len > 3 && FILENAME_CMP (ret + len - 3, ".xz") == 0) |
| { |
| len -= 3; |
| ret[len] = 0; |
| } |
| else if (len > 4 && FILENAME_CMP (ret + len - 4, ".bz2") == 0) |
| { |
| len -= 4; |
| ret[len] = 0; |
| } |
| else if (len > 4 && FILENAME_CMP (ret + len - 4, ".zst") == 0) |
| { |
| len -= 4; |
| ret[len] = 0; |
| } |
| else if (len > 3 && FILENAME_CMP (ret + len - 3, ".lz") == 0) |
| { |
| len -= 3; |
| ret[len] = 0; |
| } |
| else if (len > 5 && FILENAME_CMP (ret + len - 5, ".lzma") == 0) |
| { |
| len -= 5; |
| ret[len] =0; |
| } |
| |
| if (len > 5 && FILENAME_CMP (ret + len - 5, ".info") == 0) |
| { |
| len -= 5; |
| ret[len] = 0; |
| } |
| else if (len > 4 && FILENAME_CMP (ret + len - 4, ".inf") == 0) |
| { |
| len -= 4; |
| ret[len] = 0; |
| } |
| #ifdef __MSDOS__ |
| else if (len > 4 && (FILENAME_CMP (ret + len - 4, ".inz") == 0 |
| || FILENAME_CMP (ret + len - 4, ".igz") == 0)) |
| { |
| len -= 4; |
| ret[len] = 0; |
| } |
| #endif /* __MSDOS__ */ |
| |
| return ret; |
| } |
| |
| |
| /* Return true if ITEM matches NAME and is followed by TERM_CHAR. ITEM |
| can also be followed by `.gz', `.info.gz', or `.info' (and then |
| TERM_CHAR) and still match. */ |
| |
| static int |
| menu_item_equal (const char *item, char term_char, const char *name) |
| { |
| int ret; |
| const char *item_basename = item; |
| unsigned name_len = strlen (name); |
| |
| /* We must compare the basename in ITEM, since we are passed the |
| basename of the original info file. Otherwise, a new entry like |
| "lilypond/lilypond" won't match "lilypond". |
| |
| Actually, it seems to me that we should really compare the whole |
| name, and not just the basename. Couldn't there be dir1/foo.info |
| and dir2/foo.info? Also, it seems like we should be using the |
| filename from the new dir entries, not the filename on the command |
| line. Not worrying about those things right now, though. --karl, |
| 26mar04. */ |
| if (!remove_exactly) { |
| while (*item_basename && !IS_SLASH (*item_basename) |
| && *item_basename != term_char) |
| item_basename++; |
| if (! *item_basename || *item_basename == term_char) |
| item_basename = item; /* no /, use original */ |
| else |
| item_basename++; /* have /, move past it */ |
| } |
| |
| /* First, ITEM must actually match NAME (usually it won't). */ |
| ret = mbsncasecmp (item_basename, name, name_len) == 0; |
| if (ret) |
| { |
| /* Then, `foobar' doesn't match `foo', so be sure we've got all of |
| ITEM. The various suffixes should never actually appear in the |
| dir file, but sometimes people put them in. */ |
| static char *suffixes[] |
| = { "", ".info.gz", ".info", ".inf", ".gz", |
| #ifdef __MSDOS__ |
| ".inz", ".igz", |
| #endif |
| NULL }; |
| unsigned i; |
| ret = 0; |
| for (i = 0; !ret && suffixes[i]; i++) |
| { |
| char *suffix = suffixes[i]; |
| unsigned suffix_len = strlen (suffix); |
| ret = mbsncasecmp (item_basename + name_len, suffix, suffix_len) == 0 |
| && item_basename[name_len + suffix_len] == term_char; |
| } |
| } |
| |
| return ret; |
| } |
| |
| |
| |
| void |
| suggest_asking_for_help (void) |
| { |
| fprintf (stderr, _("\tTry `%s --help' for a complete list of options.\n"), |
| progname); |
| exit (EXIT_FAILURE); |
| } |
| |
| void |
| print_help (void) |
| { |
| printf (_("Usage: %s [OPTION]... [INFO-FILE [DIR-FILE]]\n"), progname); |
| puts (""); |
| puts (_("Add or remove entries in INFO-FILE from the Info directory DIR-FILE.")); |
| puts (_("INFO-FILE and DIR-FILE are required unless the --info-file\n\ |
| or --dir-file (or --info-dir) options are given, respectively.")); |
| puts (""); |
| |
| puts (_("\ |
| Options:\n\ |
| --add-once add only to first matching section, not all.\n\ |
| --align=COL start description of new entries at column COL.\n\ |
| --calign=COL format second and subsequent description lines to\n\ |
| start at column COL.")); |
| |
| puts (_("\ |
| --debug report what is being done.\n\ |
| --delete delete existing entries for INFO-FILE from DIR-FILE;\n\ |
| don't insert any new entries.\n\ |
| --defsection=TEXT like --section, but only use TEXT if no sections\n\ |
| are present in INFO-FILE (replacing \"Miscellaneous\").\n\ |
| --description=TEXT the description of the entry is TEXT; used with\n\ |
| the --name option to become synonymous with the\n\ |
| --entry option.\n\ |
| --dir-file=NAME specify file name of Info directory file;\n\ |
| equivalent to using the DIR-FILE argument.\n\ |
| --dry-run same as --test.")); |
| |
| puts (_("\ |
| --entry=TEXT insert TEXT as an Info directory entry,\n\ |
| overriding any corresponding entry from DIR-FILE.\n\ |
| TEXT is written as an Info menu item line followed\n\ |
| by zero or more extra lines starting with whitespace.\n\ |
| If you specify more than one entry, all are added.\n\ |
| If you don't specify any entries, they are determined\n\ |
| from information in the Info file itself.")); |
| |
| puts (_("\ |
| --help display this help and exit.\n\ |
| --info-dir=DIR same as --dir-file=DIR/dir.\n\ |
| --info-file=FILE specify Info file to install in the directory;\n\ |
| equivalent to using the INFO-FILE argument.\n\ |
| --item=TEXT same as --entry=TEXT.\n\ |
| --keep-old do not replace entries, or remove empty sections.")); |
| |
| puts (_("\ |
| --maxwidth, --max-width=COL wrap description at column COL.\n\ |
| --menuentry=TEXT same as --name=TEXT.\n\ |
| --name=TEXT the name of the entry is TEXT; used with --description\n\ |
| to become synonymous with the --entry option.\n\ |
| --no-indent do not format new entries in the DIR file.\n\ |
| --quiet suppress warnings.")); |
| |
| puts (_("\ |
| --regex=R put this file's entries in all sections that match the\n\ |
| regular expression R (ignoring case).\n\ |
| --remove same as --delete.\n\ |
| --remove-exactly only remove if the info file name matches exactly;\n\ |
| suffixes such as .info and .gz are not ignored.\n\ |
| --section=SEC put 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\ |
| if nothing is available there, the --defsection\n\ |
| value is used; if that is not specified, the\n\ |
| final default is \"Miscellaneous\".\n\ |
| --section R SEC equivalent to --regex=R --section=SEC --add-once.")); |
| |
| puts (_("\ |
| --silent suppress warnings.\n\ |
| --test suppress updating of DIR-FILE.\n\ |
| --version display version information and exit.")); |
| |
| puts (""); |
| |
| puts (_("\ |
| Email bug reports to bug-texinfo@gnu.org,\n\ |
| general questions and discussion to help-texinfo@gnu.org.\n\ |
| Texinfo home page: http://www.gnu.org/software/texinfo/")); |
| } |
| |
| |
| /* If DIRFILE does not exist, and we are not in test mode, create a |
| minimal one (or abort). If it already exists, do nothing. */ |
| |
| static void |
| ensure_dirfile_exists (char *dirfile) |
| { |
| int desc; |
| |
| if (chicken_flag) |
| return; |
| |
| desc = open (dirfile, O_RDONLY); |
| if (desc < 0 && errno == ENOENT) |
| { |
| FILE *f; |
| const char *readerr = strerror (errno); |
| f = fopen (dirfile, "w"); |
| if (f) |
| { |
| fprintf (f, _("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\ |
| %c\n\ |
| %s\tThis is the top of the INFO tree\n\ |
| \n\ |
| This (the Directory node) gives a menu of major topics.\n\ |
| Typing \"q\" exits, \"H\" 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\ |
| %s\n\ |
| "), /* These keywords must not be translated: */ |
| '\x1f', "File: dir,\tNode: Top", "* Menu:" |
| ); |
| if (fclose (f) < 0) |
| pfatal_with_name (dirfile); |
| } |
| else |
| { |
| /* Didn't exist, but couldn't open for writing. */ |
| fatal (_("%s: could not read (%s) and could not create (%s)"), |
| dirfile, readerr, strerror (errno)); |
| } |
| } |
| else |
| close (desc); /* It already existed, so fine. */ |
| } |
| |
| |
| /* Open FILENAME and return the resulting stream pointer. If it |
| doesn't exist, look for a compressed version and return a pipe reading |
| the output of the compression program. If still not found, call |
| CREATE_CALLBACK (with FILENAME as arg) to create it, if that is |
| non-NULL. If still no luck, return a null pointer. |
| |
| Return the actual name of the file we tried to open in |
| OPENED_FILENAME and the compress program to (de)compress it in |
| COMPRESSION_PROGRAM. */ |
| FILE * |
| open_possibly_compressed_file (char *filename, |
| void (*create_callback) (char *), |
| char **opened_filename, char **compression_program) |
| { |
| char *local_opened_filename, *local_compression_program; |
| int nread; |
| char data[13]; |
| FILE *f; |
| |
| /* We let them pass NULL if they don't want this info, but it's easier |
| to always determine it. */ |
| if (!opened_filename) |
| opened_filename = &local_opened_filename; |
| |
| *opened_filename = filename; |
| f = fopen (*opened_filename, FOPEN_RBIN); |
| if (!f) |
| { |
| *opened_filename = concat (filename, ".gz", ""); |
| f = fopen (*opened_filename, FOPEN_RBIN); |
| } |
| if (!f) |
| { |
| free (*opened_filename); |
| *opened_filename = concat (filename, ".xz", ""); |
| f = fopen (*opened_filename, FOPEN_RBIN); |
| } |
| if (!f) |
| { |
| free (*opened_filename); |
| *opened_filename = concat (filename, ".bz2", ""); |
| f = fopen (*opened_filename, FOPEN_RBIN); |
| } |
| if (!f) |
| { |
| free (*opened_filename); |
| *opened_filename = concat (filename, ".zst", ""); |
| f = fopen (*opened_filename, FOPEN_RBIN); |
| } |
| if (!f) |
| { |
| free (*opened_filename); |
| *opened_filename = concat (filename, ".lz", ""); |
| f = fopen (*opened_filename, FOPEN_RBIN); |
| } |
| if (!f) |
| { |
| free (*opened_filename); |
| *opened_filename = concat (filename, ".lzma", ""); |
| f = fopen (*opened_filename, FOPEN_RBIN); |
| } |
| #ifdef __MSDOS__ |
| if (!f) |
| { |
| free (*opened_filename); |
| *opened_filename = concat (filename, ".igz", ""); |
| f = fopen (*opened_filename, FOPEN_RBIN); |
| } |
| if (!f) |
| { |
| free (*opened_filename); |
| *opened_filename = concat (filename, ".inz", ""); |
| f = fopen (*opened_filename, FOPEN_RBIN); |
| } |
| #endif /* __MSDOS__ */ |
| if (!f) |
| { |
| /* The file was not found with any extention added. Try the |
| original file again. */ |
| free (*opened_filename); |
| *opened_filename = filename; |
| |
| if (create_callback) |
| { |
| /* Create the file if we can. */ |
| (*create_callback) (filename); |
| |
| /* And try opening it again. */ |
| f = fopen (*opened_filename, FOPEN_RBIN); |
| if (!f) |
| return 0; |
| } |
| else |
| return 0; |
| } |
| |
| /* Read first few bytes of file rather than relying on the filename. |
| If the file is shorter than this it can't be usable anyway. */ |
| nread = fread (data, sizeof (data), 1, f); |
| if (nread != 1) |
| { |
| if (nread == 0) |
| { |
| /* Try to create the file if its empty. */ |
| if (feof (f) && create_callback) |
| { |
| if (fclose (f) != 0) |
| return 0; /* unknown error closing file */ |
| |
| if (remove (filename) != 0) |
| return 0; /* unknown error deleting file */ |
| |
| (*create_callback) (filename); |
| f = fopen (*opened_filename, FOPEN_RBIN); |
| if (!f) |
| return 0; |
| nread = fread (data, sizeof (data), 1, f); |
| if (nread == 0) |
| return 0; |
| goto determine_file_type; /* success */ |
| } |
| } |
| errno = 0; |
| return 0; /* unknown error */ |
| } |
| |
| determine_file_type: |
| if (!compression_program) |
| compression_program = &local_compression_program; |
| |
| if (data[0] == '\x1f' && data[1] == '\x8b') |
| #if STRIP_DOT_EXE |
| /* An explicit .exe yields a better diagnostics from popen below |
| if they don't have gzip installed. */ |
| *compression_program = "gzip.exe"; |
| #else |
| *compression_program = "gzip"; |
| #endif |
| |
| else if (data[0] == '\xFD' && data[1] == '7' && data[2] == 'z' |
| && data[3] == 'X' && data[4] == 'Z' && data[5] == 0) |
| #ifndef STRIP_DOT_EXE |
| *compression_program = "xz.exe"; |
| #else |
| *compression_program = "xz"; |
| #endif |
| |
| else if (data[0] == 'B' && data[1] == 'Z' && data[2] == 'h') |
| #ifndef STRIP_DOT_EXE |
| *compression_program = "bzip2.exe"; |
| #else |
| *compression_program = "bzip2"; |
| #endif |
| |
| else if (data[0] == 'B' && data[1] == 'Z' && data[2] == '0') |
| #ifndef STRIP_DOT_EXE |
| *compression_program = "bzip.exe"; |
| #else |
| *compression_program = "bzip"; |
| #endif |
| |
| else if (data[0] == 0x4C && data[1] == 0x5A && data[2] == 0x49 |
| && data[3] == 0x50 && data[4] == 1) /* "LZIP" */ |
| #ifndef STRIP_DOT_EXE |
| *compression_program = "lzip.exe"; |
| #else |
| *compression_program = "lzip"; |
| #endif |
| |
| /* We (try to) match against old lzma format (which lacks proper |
| header, two first matches), as well as the new format (last match). */ |
| else if ((data[9] == 0x00 && data[10] == 0x00 && data[11] == 0x00 |
| && data[12] == 0x00) |
| || (data[5] == '\xFF' && data[6] == '\xFF' && data[7] == '\xFF' |
| && data[8] == '\xFF' && data[9] == '\xFF' && data[10] == '\xFF' |
| && data[11] == '\xFF' && data[12] == '\xFF') |
| || (data[0] == '\xFF' && data[1] == 'L' && data[2] == 'Z' |
| && data[3] == 'M' && data[4] == 'A' && data[5] == 0x00)) |
| #ifndef STRIP_DOT_EXE |
| *compression_program = "lzma.exe"; |
| #else |
| *compression_program = "lzma"; |
| #endif |
| |
| else |
| *compression_program = NULL; |
| |
| if (*compression_program) |
| { |
| /* Redirect stdin to the file and fork the decompression process |
| reading from stdin. This allows shell metacharacters in filenames. */ |
| char *command = concat (*compression_program, " -d", ""); |
| |
| if (fclose (f) < 0) |
| return 0; |
| f = freopen (*opened_filename, FOPEN_RBIN, stdin); |
| if (!f) |
| return 0; |
| f = popen (command, "r"); |
| if (!f) |
| { |
| /* Used for error message in calling code. */ |
| *opened_filename = command; |
| return 0; |
| } |
| } |
| else |
| { |
| #if O_BINARY |
| /* Since this is a text file, and we opened it in binary mode, |
| switch back to text mode. */ |
| f = freopen (*opened_filename, "r", f); |
| if (! f) |
| return 0; |
| #else |
| /* Seek back over the magic bytes. */ |
| if (fseek (f, 0, 0) < 0) |
| return 0; |
| #endif |
| } |
| |
| return f; |
| } |
| |
| /* Read all of file FILENAME into memory and return the address of the |
| data. Store the size of the data into SIZEP. If need be, uncompress |
| (i.e., try FILENAME.gz et al. if FILENAME does not exist) and store |
| the actual file name that was opened into OPENED_FILENAME (if it is |
| non-NULL), and the companion compression program (if any, else NULL) |
| into COMPRESSION_PROGRAM (if that is non-NULL). If trouble, return |
| a null pointer. */ |
| |
| char * |
| readfile (char *filename, int *sizep, |
| void (*create_callback) (char *), char **opened_filename, |
| char **compression_program) |
| { |
| FILE *f; |
| int filled = 0; |
| int data_size = 8192; |
| char *data = xmalloc (data_size); |
| |
| /* If they passed the space for the file name to return, use it. */ |
| f = open_possibly_compressed_file (filename, create_callback, |
| opened_filename, |
| compression_program); |
| |
| if (!f) |
| return 0; |
| |
| for (;;) |
| { |
| int nread = fread (data + filled, 1, data_size - filled, f); |
| if (nread < 0) |
| return 0; |
| if (nread == 0) |
| break; |
| |
| filled += nread; |
| if (filled == data_size) |
| { |
| data_size += 65536; |
| data = xrealloc (data, data_size); |
| } |
| } |
| |
| /* We need to close the stream, since on some systems the pipe created |
| by popen is simulated by a temporary file which only gets removed |
| inside pclose. */ |
| if (compression_program) |
| pclose (f); |
| else |
| fclose (f); |
| |
| *sizep = filled; |
| return data; |
| } |
| |
| /* Output the old dir file, interpolating the new sections |
| and/or new entries where appropriate. If COMPRESSION_PROGRAM is not |
| null, pipe to it to create DIRFILE. Thus if we read dir.gz on input, |
| we'll write dir.gz on output. */ |
| |
| static void |
| output_dirfile (char *dirfile, int dir_nlines, struct line_data *dir_lines, |
| int n_entries_to_add, struct spec_entry *entries_to_add, |
| struct spec_section *input_sections, char *compression_program) |
| { |
| int n_entries_added = 0; |
| int i; |
| FILE *output; |
| |
| if (compression_program) |
| { |
| char *command = concat (compression_program, ">", dirfile); |
| output = popen (command, "w"); |
| } |
| else |
| output = fopen (dirfile, "w"); |
| |
| if (!output) |
| { |
| perror (dirfile); |
| exit (EXIT_FAILURE); |
| } |
| |
| for (i = 0; i <= dir_nlines; i++) |
| { |
| int j; |
| |
| /* If we decided to output some new entries before this line, |
| output them now. */ |
| 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; |
| if (n_entries_added >= 1 && |
| !add_entries_into_all_matching_sections) |
| break; |
| fputs (this->text, output); |
| n_entries_added++; |
| } |
| /* If we decided to add some sections here |
| because there are no such sections in the file, |
| output them now. |
| FIXME: we add all sections here, but they should |
| be interspersed throughout the DIR file in |
| alphabetic order. */ |
| if (dir_lines[i].add_sections_before) |
| { |
| struct spec_section *spec; |
| struct spec_entry *entry; |
| struct spec_entry **entries; |
| int n_entries = 0; |
| |
| /* If we specified --add-once, and we've added an entry, then |
| it's time to bail. */ |
| if (n_entries_added >= 1 && |
| !add_entries_into_all_matching_sections) |
| break; |
| |
| qsort (dir_lines[i].add_sections_before, |
| dir_lines[i].num_sections_to_add, |
| sizeof (struct spec_section *), compare_section_names); |
| |
| /* Count the entries and allocate a vector for all of them. */ |
| for (entry = entries_to_add; entry; entry = entry->next) |
| n_entries++; |
| entries = ((struct spec_entry **) |
| xmalloc (n_entries * sizeof (struct spec_entry *))); |
| |
| /* Fill the vector ENTRIES with pointers to all the sections, |
| and sort them. */ |
| j = 0; |
| for (entry = entries_to_add; entry; entry = entry->next) |
| entries[j++] = entry; |
| qsort (entries, n_entries, sizeof (struct spec_entry *), |
| compare_entries_text); |
| |
| /* Generate the new sections in alphabetical order. In each |
| new section, output all of the entries that belong to that |
| section, in alphabetical order. */ |
| for (j = 0; j < dir_lines[i].num_sections_to_add; j++) |
| { |
| spec = dir_lines[i].add_sections_before[j]; |
| if (spec->missing) |
| { |
| int k; |
| |
| putc ('\n', output); |
| fputs (spec->name, output); |
| putc ('\n', output); |
| spec->missing = 0; |
| for (k = 0; k < n_entries; k++) |
| { |
| struct spec_section *spec1; |
| /* Did they at all want this entry to be put into |
| this section? */ |
| entry = entries[k]; |
| for (spec1 = entry->entry_sections; |
| spec1 && spec1 != entry->entry_sections_tail; |
| spec1 = spec1->next) |
| { |
| if (!strcmp (spec1->name, spec->name)) |
| break; |
| } |
| if (spec1 && spec1 != entry->entry_sections_tail) |
| fputs (entry->text, output); |
| } |
| } |
| } |
| |
| n_entries_added++; |
| free (entries); |
| } |
| |
| /* Output the original dir lines unless marked for deletion. */ |
| if (i < dir_nlines && !dir_lines[i].delete) |
| { |
| fwrite (dir_lines[i].start, 1, dir_lines[i].size, output); |
| putc ('\n', output); |
| } |
| } |
| |
| /* Some systems, such as MS-DOS, simulate pipes with temporary files. |
| On those systems, the compressor actually gets run inside pclose, |
| so we must call pclose. */ |
| if (compression_program) |
| pclose (output); |
| else |
| fclose (output); |
| } |
| |
| /* Read through the input LINES, to find the section names and the |
| entry names it specifies. Each INFO-DIR-SECTION entry is added |
| to the SECTIONS linked list. Each START-INFO-DIR-ENTRY block is added to |
| the ENTRIES linked list, and the last group of INFO-DIR-SECTION entries |
| is recorded in next->entry_sections and next->entry_sections_tail, where |
| next is the new entry. Return the number of entries to add from this |
| file. */ |
| int |
| parse_input (const struct line_data *lines, int nlines, |
| struct spec_section **sections, struct spec_entry **entries, |
| int delete_flag) |
| { |
| int n_entries = 0; |
| int prefix_length = strlen ("INFO-DIR-SECTION "); |
| struct spec_section *head = *sections, *tail = NULL; |
| int reset_tail = 0; |
| char *start_of_this_entry = 0; |
| int ignore_sections = *sections != 0; |
| int ignore_entries = delete_flag ? 0: *entries != 0; |
| |
| int i; |
| |
| if (ignore_sections && ignore_entries) |
| return 0; |
| |
| for (i = 0; i < nlines; i++) |
| { |
| if (!ignore_sections |
| && !strncmp ("INFO-DIR-SECTION ", lines[i].start, prefix_length)) |
| { |
| struct spec_section *next |
| = (struct spec_section *) xmalloc (sizeof (struct spec_section)); |
| next->name = copy_string (lines[i].start + prefix_length, |
| lines[i].size - prefix_length); |
| next->next = *sections; |
| next->missing = 1; |
| if (reset_tail) |
| { |
| tail = *sections; |
| reset_tail = 0; |
| } |
| *sections = next; |
| head = *sections; |
| } |
| /* If entries were specified explicitly with command options, |
| ignore the entries in the input file. */ |
| else if (!ignore_entries) |
| { |
| if (!strncmp ("START-INFO-DIR-ENTRY", lines[i].start, lines[i].size) |
| && sizeof ("START-INFO-DIR-ENTRY") - 1 == lines[i].size) |
| { |
| if (!*sections) |
| { |
| /* We found an entry, but didn't yet see any sections |
| specified. Default to section "Miscellaneous". */ |
| *sections = (struct spec_section *) |
| xmalloc (sizeof (struct spec_section)); |
| (*sections)->name = "Miscellaneous"; |
| (*sections)->next = 0; |
| (*sections)->missing = 1; |
| head = *sections; |
| } |
| /* Next time we see INFO-DIR-SECTION, we will reset the |
| tail pointer. */ |
| reset_tail = 1; |
| |
| /* Save start of the entry. If this is non-zero, we're |
| already inside an entry, so fail. */ |
| if (start_of_this_entry != 0) |
| fatal (_("START-INFO-DIR-ENTRY without matching END-INFO-DIR-ENTRY")); |
| start_of_this_entry = lines[i + 1].start; |
| } |
| else if (start_of_this_entry) |
| { |
| if ((!strncmp ("* ", lines[i].start, 2) |
| && lines[i].start > start_of_this_entry) |
| || (!strncmp ("END-INFO-DIR-ENTRY", |
| lines[i].start, lines[i].size) |
| && sizeof ("END-INFO-DIR-ENTRY") - 1 == lines[i].size)) |
| { |
| /* We found the end of this entry. Save its contents |
| in a new entry in the linked list. */ |
| struct spec_entry *next |
| = (struct spec_entry *) xmalloc (sizeof (struct spec_entry)); |
| next->text |
| = copy_string (start_of_this_entry, |
| lines[i].start - start_of_this_entry); |
| next->text_len = lines[i].start - start_of_this_entry; |
| next->entry_sections = head; |
| next->entry_sections_tail = tail; |
| next->missing_basename = 0; |
| next->missing_name = 0; |
| next->missing_description = 0; |
| next->next = *entries; |
| *entries = next; |
| n_entries++; |
| if (!strncmp ("END-INFO-DIR-ENTRY", |
| lines[i].start, lines[i].size) |
| && sizeof ("END-INFO-DIR-ENTRY") - 1 == lines[i].size) |
| start_of_this_entry = 0; |
| else |
| start_of_this_entry = lines[i].start; |
| } |
| else if (!strncmp ("END-INFO-DIR-ENTRY", |
| lines[i].start, lines[i].size) |
| && sizeof ("END-INFO-DIR-ENTRY") - 1 == lines[i].size) |
| 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 we ignored the INFO-DIR-ENTRY directives, we need now go back |
| and plug the names of all the sections we found into every |
| element of the ENTRIES list. */ |
| if (ignore_entries && *entries) |
| { |
| struct spec_entry *entry; |
| |
| for (entry = *entries; entry; entry = entry->next) |
| { |
| entry->entry_sections = head; |
| entry->entry_sections_tail = tail; |
| } |
| } |
| |
| return n_entries; |
| } |
| |
| |
| /* Parse the dir file whose basename is BASE_NAME. Find all the |
| nodes, and their menus, and the sections of their menus. */ |
| static void |
| parse_dir_file (struct line_data *lines, int nlines, struct node **nodes) |
| { |
| int node_header_flag = 0; |
| int i; |
| |
| *nodes = 0; |
| for (i = 0; i < nlines; i++) |
| { |
| /* Parse node header lines. */ |
| if (node_header_flag) |
| { |
| int j, end; |
| for (j = 0; j < lines[i].size; j++) |
| /* Find the node name and store it in the `struct node'. */ |
| if (!strncmp ("Node:", lines[i].start + j, 5)) |
| { |
| char *line = lines[i].start; |
| /* Find the start of the node name. */ |
| j += 5; |
| while (line[j] == ' ' || line[j] == '\t') |
| j++; |
| /* Find the end of the node name. */ |
| end = j; |
| while (line[end] != 0 && line[end] != ',' && line[end] != '\n' |
| && line[end] != '\t') |
| end++; |
| (*nodes)->name = copy_string (line + j, end - j); |
| } |
| node_header_flag = 0; |
| } |
| |
| /* Notice the start of a node. */ |
| if (*lines[i].start == 037) |
| { |
| struct node *next = (struct node *) xmalloc (sizeof (struct node)); |
| |
| next->next = *nodes; |
| next->name = NULL; |
| next->start_line = i; |
| next->end_line = 0; |
| next->menu_start = NULL; |
| next->sections = NULL; |
| next->last_section = NULL; |
| |
| if (*nodes != 0) |
| (*nodes)->end_line = i; |
| /* Fill in the end of the last menu section |
| of the previous node. */ |
| if (*nodes != 0 && (*nodes)->last_section != 0) |
| (*nodes)->last_section->end_line = i; |
| |
| *nodes = next; |
| |
| /* The following line is the header of this node; |
| parse it. */ |
| node_header_flag = 1; |
| } |
| |
| /* Notice the lines that start menus. */ |
| if (*nodes != 0 && !strncmp ("* Menu:", lines[i].start, 7)) |
| (*nodes)->menu_start = lines[i + 1].start; |
| |
| /* Notice sections in menus. */ |
| if (*nodes != 0 |
| && (*nodes)->menu_start != 0 |
| && *lines[i].start != '\n' |
| && *lines[i].start != '*' |
| && *lines[i].start != ' ' |
| && *lines[i].start != '\t') |
| { |
| /* Add this menu section to the node's list. |
| This list grows in forward order. */ |
| 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 (lines[i].start, lines[i].size); |
| if ((*nodes)->sections) |
| { |
| (*nodes)->last_section->next = next; |
| (*nodes)->last_section->end_line = i; |
| } |
| else |
| (*nodes)->sections = next; |
| (*nodes)->last_section = next; |
| } |
| |
| } |
| |
| /* Finish the info about the end of the last node. */ |
| if (*nodes != 0) |
| { |
| (*nodes)->end_line = nlines; |
| if ((*nodes)->last_section != 0) |
| (*nodes)->last_section->end_line = nlines; |
| } |
| } |
| |
| |
| /* Iterate through NLINES LINES looking for an entry that has a name |
| that matches NAME. If such an entry is found, flag the entry for |
| deletion later on. */ |
| |
| int |
| mark_entry_for_deletion (struct line_data *lines, int nlines, char *name) |
| { |
| int something_deleted = 0; |
| int i; |
| for (i = 0; i < nlines; i++) |
| { |
| /* Check for an existing entry that should be deleted. |
| Delete all entries which specify this file name. */ |
| if (*lines[i].start == '*') |
| { |
| char *q; |
| char *p = lines[i].start; |
| |
| p++; /* skip * */ |
| while (*p == ' ') p++; /* ignore following spaces */ |
| q = p; /* remember this, it's the beginning of the menu item. */ |
| |
| /* Read menu item. */ |
| while (*p != 0 && *p != ':') |
| p++; |
| p++; /* skip : */ |
| |
| if (*p == ':') |
| { /* XEmacs-style entry, as in * Mew::Messaging. */ |
| if (menu_item_equal (q, ':', name)) |
| { |
| lines[i].delete = 1; |
| something_deleted = 1; |
| } |
| } |
| else |
| { /* Emacs-style entry, as in * Emacs: (emacs). */ |
| while (*p == ' ') p++; /* skip spaces after : */ |
| if (*p == '(') /* if at parenthesized (FILENAME) */ |
| { |
| p++; |
| if (menu_item_equal (p, ')', name)) |
| { |
| lines[i].delete = 1; |
| something_deleted = 1; |
| } |
| } |
| } |
| } |
| |
| /* Treat lines that start with whitespace |
| as continuations; if we are deleting an entry, |
| delete all its continuations as well. */ |
| else if (i > 0 && (*lines[i].start == ' ' || *lines[i].start == '\t')) |
| { |
| lines[i].delete = lines[i - 1].delete; |
| } |
| } |
| return something_deleted; |
| } |
| |
| |
| /* Assuming the current column is COLUMN, return the column that |
| printing C will move the cursor to. |
| The first column is 0. |
| This function is used to assist in indenting of entries. */ |
| |
| static size_t |
| adjust_column (size_t column, char c) |
| { |
| if (c == '\b') |
| { |
| if (column > 0) |
| column--; |
| } |
| else if (c == '\r') |
| column = 0; |
| else if (c == '\t') |
| column += TAB_WIDTH - column % TAB_WIDTH; |
| else /* if (isprint (c)) */ |
| column++; |
| return column; |
| } |
| |
| /* Format the Info entry's NAME and DESCRIPTION. |
| NAME is of the form "* TEXT (TEXT)[:TEXT].". |
| The description on the first line is indented at the CALIGN-th column, and |
| all subsequent lines are indented at the ALIGN-th column. |
| Lines are wrapped at the WIDTH column. |
| The resulting Info entry is put into OUTSTR. |
| */ |
| static int |
| format_entry (char *name, size_t name_len, char *desc, size_t desc_len, |
| int calign, int align, size_t width, |
| char **outstr_out, size_t *outstr_len) |
| { |
| int i, j; |
| char c; |
| size_t column = 0; /* Screen column where next char will go */ |
| |
| /* Used to collect a line at a time, before transferring to outstr. */ |
| static char *line_out = NULL; |
| size_t offset_out = 0; /* Index in `line_out' for next char. */ |
| static size_t allocated_out = 0; /* Space allocated in `line_out'. */ |
| char *outstr; |
| |
| if (!desc || !name) |
| return 1; |
| |
| outstr = xmalloc (width |
| + (desc_len + width) / (width - align) * width * 2 * sizeof (char)); |
| outstr[0] = '\0'; |
| |
| strncat (outstr, name, name_len); |
| |
| column = name_len; |
| |
| if (name_len > calign - 2) |
| { |
| /* Name is too long to have description on the same line. */ |
| if (desc_len > 1) |
| { |
| strcat (outstr, "\n"); |
| column = 0; |
| for (j = 0; j < calign - 1; j++) |
| { |
| column = adjust_column (column, ' '); |
| strcat (outstr, " "); |
| } |
| } |
| } |
| else |
| for (j = 0; j < calign - name_len - 1; j++) |
| { |
| if (desc_len <= 2) |
| break; |
| column = adjust_column (column, ' '); |
| strcat (outstr, " "); |
| } |
| |
| for (i = 0; i < desc_len; i++) |
| { |
| if (desc_len <= 2) |
| break; |
| c = desc[i]; |
| if (offset_out + 1 >= allocated_out) |
| { |
| allocated_out = offset_out + 1; |
| line_out = (char *) xrealloc ((void *)line_out, allocated_out + 1); |
| /* The + 1 here shouldn't be necessary, but a crash was reported |
| for a following strncat call. */ |
| } |
| |
| if (c == '\n') |
| { |
| line_out[offset_out++] = c; |
| strncat (outstr, line_out, offset_out); |
| column = offset_out = 0; |
| continue; |
| } |
| |
| /* Come here from inside "column > width" block below. */ |
| rescan: |
| column = adjust_column (column, c); |
| |
| if (column > width) |
| { |
| /* This character would make the line too long. |
| Print the line plus a newline, and make this character |
| start the next line. */ |
| |
| int found_blank = 0; |
| size_t logical_end = offset_out; |
| |
| /* Look for the last blank. */ |
| while (logical_end) |
| { |
| --logical_end; |
| if (line_out[logical_end] == ' ' |
| || line_out[logical_end] == '\t') |
| { |
| found_blank = 1; |
| break; |
| } |
| } |
| |
| if (found_blank) |
| { |
| size_t i; |
| |
| /* Found a blank. Don't output the part after it. */ |
| logical_end++; |
| strncat (outstr, line_out, logical_end); |
| strcat (outstr, "\n"); |
| for (j = 0; j < align - 1; j++) |
| { |
| column = adjust_column (column, ' '); |
| strcat (outstr, " "); |
| } |
| |
| /* Move the remainder to the beginning of the next |
| line. |
| The areas being copied here might overlap. */ |
| memmove (line_out, line_out + logical_end, |
| offset_out - logical_end); |
| offset_out -= logical_end; |
| for (column = i = 0; i < offset_out; i++) |
| column = adjust_column (column, line_out[i]); |
| goto rescan; |
| } |
| |
| if (offset_out == 0) |
| { |
| line_out[offset_out++] = c; |
| continue; |
| } |
| |
| line_out[offset_out++] = '\n'; |
| strncat (outstr, line_out, offset_out); |
| column = offset_out = 0; |
| goto rescan; |
| } |
| line_out[offset_out++] = c; |
| } |
| |
| if (desc_len <= 2) |
| strcat (outstr, "\n"); |
| |
| if (offset_out) |
| strncat (outstr, line_out, offset_out); |
| |
| free (*outstr_out); |
| *outstr_out = outstr; |
| *outstr_len = strlen (outstr); |
| return 1; |
| } |
| |
| |
| /* Extract the NAME and DESCRIPTION from ENTRY. NAME and DESCRIPTION must be |
| free'd. |
| */ |
| static void |
| split_entry (const char *entry, char **name, size_t *name_len, |
| char **description, size_t *description_len) |
| { |
| char *endptr; |
| |
| /* on the first line, the description starts after the first ". "; |
| that's a period and space -- our heuristic to handle item names like |
| "config.status", and node names like "config.status Invocation". |
| Also accept period-tab and period-newline. */ |
| char *ptr = strchr (entry, '.'); |
| while (ptr && ptr[1] != ' ' && ptr[1] != '\t' && ptr[1] != '\n') { |
| ptr = strchr (ptr + 1, '.'); |
| } |
| |
| /* Maybe there's no period, and no description */ |
| if (!ptr) |
| { |
| size_t length = strlen (entry); |
| if (length == 0) |
| return; |
| *name = strdup (entry); |
| *name_len = length + 1; |
| return; |
| } |
| |
| /* The name is everything up to and including the period. */ |
| *name_len = (size_t) (ptr - entry + 1); |
| *name = xmalloc (*name_len + 1); |
| (*name)[0] = '\0'; |
| strncat (*name, entry, *name_len); |
| |
| ptr++; |
| *description = xmalloc (strlen (entry)); |
| (*description)[0] = '\0'; |
| |
| while (ptr[0] != '\0') |
| { |
| /* Eat up the whitespace after the name, and at the start of a line. */ |
| while (isspace(ptr[0])) |
| ptr++; |
| |
| /* Okay, we're at the start of the description. */ |
| if (ptr[0] == '\0') |
| continue; |
| |
| /* See how far the description goes... */ |
| endptr = strchr (ptr, '\n'); |
| /* Either the description continues up to the next newline. */ |
| if (endptr) |
| { |
| size_t length = (size_t) (endptr - ptr) / sizeof (char); |
| strncat (*description, ptr, length); |
| ptr = endptr; |
| /* First of all, we eat the newline here. But then what? |
| Sometimes the newline separates 2 sentences, so we |
| end up with the next word starting directly after the period, |
| instead of after the customary 2 spaces in english. |
| If the previous character was a `.', then we should add 2 |
| spaces if there is anything on the next line. |
| if it's a comma, then we should put one space. |
| If it's neither, we just put a space. |
| If it's some other whitespace, we shouldn't do anything. */ |
| ptr++; |
| if (length > 1 && strlen (ptr) > 0) |
| { |
| endptr--; |
| /* *ENDPTR is the 2nd last character */ |
| if (*endptr == '.') |
| strcat (*description, " "); |
| else if (!isspace (*endptr)) |
| strcat (*description, " "); |
| } |
| } |
| /* Or the description continues to the end of the string. */ |
| else |
| { |
| /* Just show the rest when there's no newline. */ |
| size_t length = strlen (ptr); |
| strncat (*description, ptr, length); |
| ptr += length; |
| } |
| } |
| /* Descriptions end in a new line. */ |
| strcat (*description, "\n"); |
| *description_len = strlen (*description); |
| } |
| |
| |
| /* Indent all ENTRIES according to some formatting options. |
| CALIGN_CLI is the starting column for the first line of the description. |
| ALIGN_CLI is the starting column for all subsequent lines of the |
| description. MAXWIDTH_CLI is the number of columns in the line. |
| When CALIGN_CLI, ALIGN_CLI, or MAXWIDTH_CLI is -1, choose a sane default. */ |
| |
| static void |
| reformat_new_entries (struct spec_entry *entries, int calign_cli, int align_cli, |
| int maxwidth_cli) |
| { |
| struct spec_entry *entry; |
| for (entry = entries; entry; entry = entry->next) |
| { |
| int calign = -1, align = -1, maxwidth = -1; |
| char *name = NULL, *desc = NULL; |
| size_t name_len = 0, desc_len = 0; |
| split_entry (entry->text, &name, &name_len, &desc, &desc_len); |
| |
| /* Specify sane defaults if we need to */ |
| if (calign_cli == -1 || align_cli == -1) |
| { |
| struct spec_section *section; |
| calign = calign_cli; |
| align = align_cli; |
| for (section = entry->entry_sections; |
| section && section != entry->entry_sections_tail; |
| section = section->next) |
| { |
| if (!strcmp (section->name, "Individual utilities")) |
| { |
| if (calign == -1) |
| calign = 48 + 1; |
| if (align == -1) |
| align = 50 + 1; |
| break; |
| } |
| } |
| if (calign == -1) |
| calign = 32 + 1; |
| if (align == -1) |
| align = 34 + 1; |
| } |
| else |
| { |
| calign = calign_cli; |
| align = align_cli; |
| } |
| |
| maxwidth = maxwidth_cli == -1 ? 79 : maxwidth_cli; |
| |
| format_entry (name, name_len, desc, desc_len, calign, align, |
| maxwidth, &entry->text, &entry->text_len); |
| } |
| } |
| |
| /* Insert NAME into every entry in ENTRIES that requires it. |
| NAME is the basename of the Info file being installed. |
| The idea here is that there was a --name on the command-line |
| and we need to put the basename in the empty parentheses. */ |
| void |
| add_missing_basenames (struct spec_entry *entries, char *name) |
| { |
| struct spec_entry *entry; |
| for (entry = entries; entry; entry = entry->next) |
| { |
| if (entry->missing_basename) |
| { |
| /* Insert NAME into the right place in ENTRY->TEXT. */ |
| char *info, *rest, *text; |
| size_t name_len = strlen (name); |
| char *ptr = strstr (entry->text, ": (). "); |
| if (!ptr) |
| return; |
| ptr[0] = '\0'; |
| rest = ptr += strlen (": (). "); |
| |
| info = xmalloc (name_len + 7); |
| snprintf (info, name_len + 7, ": (%s). ", name); |
| text = concat (entry->text, info, rest); |
| free (info); |
| if (entry->text) |
| free (entry->text); |
| entry->text = text; |
| entry->text_len = strlen (entry->text); |
| entry->missing_name = 0; |
| entry->missing_basename = 0; |
| } |
| } |
| } |
| |
| |
| /* Add NAME to the start of any entry in ENTRIES that is missing a name |
| component. If NAME doesn't start with `*', it is formatted to look |
| like an Info entry. */ |
| void |
| add_missing_names (struct spec_entry *entries, char *name) |
| { |
| struct spec_entry *entry; |
| for (entry = entries; entry; entry = entry->next) |
| { |
| if (entry->missing_name) |
| { |
| char *text; |
| /* Prepend NAME onto ENTRY->TEXT. */ |
| int add_nl = 1; |
| if (entry->text) |
| if (entry->text[entry->text_len - 1] == '\n') |
| add_nl = 0; |
| |
| if (name[0] == '*') |
| text = concat (name, entry->text == NULL ? "" : entry->text, |
| add_nl ? "\n" : ""); |
| else |
| { |
| size_t full_name_len = strlen (name) * 2 + 9; |
| char *full_name = xmalloc (full_name_len); |
| snprintf (full_name, full_name_len, "* %s: (%s).", name, name); |
| text = concat (full_name, |
| entry->text == NULL ? "" : entry->text, |
| add_nl ? "\n" : ""); |
| free (full_name); |
| } |
| if (entry->text) |
| free (entry->text); |
| entry->text = text; |
| entry->text_len = strlen (entry->text); |
| entry->missing_name = 0; |
| entry->missing_basename = 0; |
| } |
| } |
| } |
| |
| /* Append DESC to every entry in ENTRIES that needs it. */ |
| |
| void |
| add_missing_descriptions (struct spec_entry *entries, char *desc) |
| { |
| struct spec_entry *entry; |
| for (entry = entries; entry; entry = entry->next) |
| { |
| if (entry->missing_description) |
| { |
| char *text; |
| int add_nl = 1; |
| if (strlen (desc) > 1) |
| if (desc[strlen (desc) - 1] == '\n') |
| add_nl = 0; |
| /* Append DESC onto ENTRY->TEXT. */ |
| text = concat (entry->text == NULL ? "" : entry->text, desc, |
| add_nl ? "\n" : ""); |
| if (entry->text) |
| free (entry->text); |
| entry->text = text; |
| entry->text_len = strlen (entry->text); |
| } |
| } |
| } |
| |
| |
| /* Detect old-style Debian `--section REGEX TITLE' semantics in ARGV. |
| When detected the options are munged to look like: |
| `--regex REGEX --section TITLE --add-once' |
| Return 1 if munging took place, return 0 if not. |
| Otherwise return a negative number if something went wrong. |
| NEW_ARGC, and NEW_ARGV are filled with the newly munged options |
| when munging took place. |
| */ |
| static int |
| munge_old_style_debian_options (int argc, char **argv, |
| int *new_argc, char ***new_argv) |
| { |
| char *opt = NULL; |
| int i, err; |
| char *argz = NULL; |
| size_t argz_len = 0; |
| const char *regex, *title; |
| int munge = 0; |
| |
| /* Flip through the options to detect the old `--section REGEX TITLE' |
| syntax */ |
| for (i = 0; i < argc; i++) |
| { |
| if (strcmp (argv[i], "--section") == 0) |
| { |
| FILE *fileptr; |
| /* Go forward one arg and obtain the REGEX. */ |
| if (i + 1 < argc) |
| i++; |
| else |
| return -1; |
| regex = argv[i]; |
| /* Go forward another arg and obtain the TITLE. */ |
| if (i + 1 < argc) |
| i++; |
| else |
| return -1; |
| title = argv[i]; |
| /* When the title starts with a `-' it's probably an option, |
| and not a title. */ |
| if (title[0] == '-') |
| break; |
| /* When the title is a filename it's probably an Info file, or |
| a dir file, and not a title. */ |
| fileptr = fopen (title, "r"); |
| if (fileptr) |
| { |
| fclose (fileptr); |
| break; |
| } |
| /* Okay, it looks like we're using the old debian syntax |
| for --section. */ |
| munge = 1; |
| |
| /* Okay, we munge the options to look like this: |
| --regex=REGEX --section=TITLE --add-once */ |
| opt = xmalloc (strlen (regex) + sizeof ("--regex=")); |
| if (sprintf (opt, "--regex=%s", regex) == -1) |
| err = 1; |
| if (!err) |
| err = argz_add (&argz, &argz_len, opt); |
| free (opt); opt = NULL; |
| |
| opt = xmalloc (strlen (title) + sizeof ("--section=")); |
| if (sprintf (opt, "--section=%s", title) == -1) |
| err = 1; |
| if (!err) |
| err = argz_add (&argz, &argz_len, opt); |
| free (opt); opt = NULL; |
| |
| if (!err) |
| err = argz_add (&argz, &argz_len, "--add-once"); |
| } |
| else |
| err = argz_add (&argz, &argz_len, argv[i]); |
| if (err) |
| return -1; |
| } |
| |
| if (munge) |
| { |
| *new_argc = argz_count (argz, argz_len); |
| *new_argv = xmalloc ((*new_argc + 1) * sizeof (char *)); |
| |
| opt = NULL; i = 0; |
| while ((opt = argz_next (argz, argz_len, opt))) |
| { |
| (*new_argv)[i] = xstrdup (opt); |
| i++; |
| } |
| (*new_argv)[*new_argc] = NULL; |
| } |
| free (argz); |
| return munge; |
| } |
| |
| |
| int |
| main (int argc, char *argv[]) |
| { |
| char *opened_dirfilename; |
| char *compression_program; |
| char *infile_sans_info; |
| char *infile = 0, *dirfile = 0; |
| int calign = -1; |
| int align = -1; |
| int maxwidth = -1; |
| |
| /* Record the text of the Info file, as a sequence of characters |
| and as a sequence of lines. */ |
| char *input_data = NULL; |
| int input_size = 0; |
| struct line_data *input_lines = NULL; |
| int input_nlines = 0; |
| |
| /* Record here the specified section names and directory entries. */ |
| struct spec_section *input_sections = NULL; |
| struct spec_entry *entries_to_add = NULL; |
| struct spec_entry *entries_to_add_from_file = NULL; |
| int n_entries_to_add = 0; |
| struct spec_entry *default_entries_to_add = NULL; |
| int n_default_entries_to_add = 0; |
| |
| /* Record the old text of the dir file, as plain characters, |
| as lines, and as nodes. */ |
| char *dir_data; |
| int dir_size; |
| int dir_nlines; |
| struct line_data *dir_lines; |
| struct node *dir_nodes; |
| |
| /* Nonzero means --delete was specified (just delete existing entries). */ |
| int delete_flag = 0; |
| int something_deleted = 0; |
| |
| /* Nonzero means -quiet/--silent was specified. */ |
| int quiet_flag = 0; |
| |
| /* Nonzero means --debug was specified. */ |
| int debug_flag = 0; |
| |
| int i; |
| |
| #ifdef HAVE_SETLOCALE |
| /* Set locale via LC_ALL. */ |
| setlocale (LC_ALL, ""); |
| #endif |
| |
| /* Set the text message domain. */ |
| bindtextdomain (PACKAGE, LOCALEDIR); |
| textdomain (PACKAGE); |
| |
| /* Make sure standard input can be freopened at will. Otherwise, |
| when stdin starts off closed, bad things could happen if a plain fopen |
| returns stdin before open_possibly_compressed_file freopens it. */ |
| if (! freopen (NULL_DEVICE, "r", stdin)) |
| pfatal_with_name (NULL_DEVICE); |
| |
| munge_old_style_debian_options (argc, argv, &argc, &argv); |
| |
| while (1) |
| { |
| int opt = getopt_long (argc, argv, |
| "i:d:e:s:t:E:c:C:W:A:hHrk1Ia", longopts, 0); |
| |
| if (opt == EOF) |
| break; |
| |
| switch (opt) |
| { |
| case 0: |
| /* If getopt returns 0, then it has already processed a |
| long-named option. We should do nothing. */ |
| break; |
| |
| case 1: |
| abort (); |
| |
| case '1': |
| add_entries_into_all_matching_sections = 0; |
| break; |
| |
| case 'a': |
| order_new_sections_alphabetically_flag = 0; |
| break; |
| |
| case 'A': |
| { |
| char *end = NULL; |
| unsigned long int val; |
| val = strtoul (optarg, &end, 0); |
| if (end == NULL || end == optarg || *end != '\0') |
| suggest_asking_for_help (); |
| align = val; |
| if (align <= 0) |
| suggest_asking_for_help (); |
| } |
| break; |
| |
| case 'c': |
| |
| { |
| struct spec_entry *next; |
| size_t length = strlen (optarg); |
| |
| if (!entries_to_add) |
| { |
| next = |
| (struct spec_entry *) xmalloc (sizeof (struct spec_entry)); |
| |
| next->text = NULL; |
| next->text_len = 0; |
| next->entry_sections = NULL; |
| next->entry_sections_tail = NULL; |
| next->missing_name = 1; |
| next->missing_basename = 1; |
| next->next = entries_to_add; |
| entries_to_add = next; |
| n_entries_to_add++; |
| } |
| else |
| next = entries_to_add; |
| |
| next->missing_description = 0; |
| if (next->text) |
| { |
| char *nl = strrchr (next->text, '\n'); |
| if (nl) |
| nl[0] = '\0'; |
| } |
| /* Concat the description onto the current entry, adding a |
| newline if we need one. Prepend a space if we have no |
| previous text, since eventually we will be adding the |
| "* foo ()." and we want to end up with a ". " for parsing. */ |
| next->text = concat (next->text ? next->text : " ", |
| optarg, |
| optarg[length - 1] == '\n' ? "" : "\n"); |
| next->text_len = strlen (next->text); |
| } |
| break; |
| |
| case 'C': |
| { |
| char *end = NULL; |
| unsigned long int val; |
| val = strtoul (optarg, &end, 0); |
| if (end == NULL || end == optarg || *end != '\0') |
| suggest_asking_for_help (); |
| calign = val; |
| if (calign <= 0) |
| suggest_asking_for_help (); |
| } |
| break; |
| |
| case 'd': |
| if (dirfile) |
| { |
| fprintf (stderr, _("%s: already have dir file: %s\n"), |
| progname, dirfile); |
| suggest_asking_for_help (); |
| } |
| dirfile = optarg; |
| break; |
| |
| case 'D': |
| if (dirfile) |
| { |
| fprintf (stderr, _("%s: already have dir file: %s\n"), |
| progname, dirfile); |
| suggest_asking_for_help (); |
| } |
| dirfile = concat (optarg, "", "/dir"); |
| break; |
| |
| case 't': |
| { |
| struct spec_entry *next |
| = (struct spec_entry *) xmalloc (sizeof (struct spec_entry)); |
| |
| size_t length; |
| if (optarg[0] != '*') |
| { |
| /* Make enough space for "* foo: (). ". */ |
| length = strlen (optarg) + 9; |
| next->text = xmalloc (length); |
| snprintf (next->text, length, "* %s: (). ", optarg); |
| next->missing_basename = 1; |
| /* The basename will be inserted in between the parentheses |
| at a later time. See add_missing_basenames. */ |
| } |
| else |
| { |
| /* Make enough space for "foo ". */ |
| length = strlen (optarg) + 2; |
| next->text = xmalloc (length); |
| snprintf (next->text, length, "%s ", optarg); |
| next->missing_basename = 0; |
| /* FIXME: check for info entry correctness in TEXT. |
| e.g. `* Aaa: (bbb).' */ |
| } |
| |
| next->text_len = length - 1; |
| next->entry_sections = NULL; |
| next->entry_sections_tail = NULL; |
| next->next = entries_to_add; |
| next->missing_name = 0; |
| next->missing_description = 1; |
| entries_to_add = next; |
| n_entries_to_add++; |
| } |
| break; |
| |
| case 'E': |
| case 'e': |
| { |
| struct spec_entry *next |
| = (struct spec_entry *) xmalloc (sizeof (struct spec_entry)); |
| int olen = strlen (optarg); |
| if (! (*optarg != 0 && optarg[olen - 1] == '\n')) |
| { |
| optarg = concat (optarg, "\n", ""); |
| olen++; |
| } |
| next->text = optarg; |
| next->text_len = olen; |
| next->entry_sections = NULL; |
| next->entry_sections_tail = NULL; |
| next->missing_name = 0; |
| next->missing_basename = 0; |
| next->missing_description = 0; |
| if (opt == 'e') |
| { |
| next->next = entries_to_add; |
| entries_to_add = next; |
| n_entries_to_add++; |
| } |
| else |
| { |
| /* Although this list is maintained, nothing is ever |
| done with it. So it seems no one cares about the |
| feature. The intended --help string was: |
| --defentry=TEXT like --entry, but only use TEXT if an entry\n\ |
| is not present in INFO-FILE.\n\ |
| in case anyone ever wants to finish it. */ |
| next->next = default_entries_to_add; |
| default_entries_to_add = next; |
| n_default_entries_to_add++; |
| } |
| } |
| break; |
| |
| case 'g': |
| debug_flag = 1; |
| break; |
| |
| case 'h': |
| case 'H': |
| print_help (); |
| exit (EXIT_SUCCESS); |
| |
| case 'i': |
| if (infile) |
| { |
| fprintf (stderr, _("%s: Specify the Info file only once.\n"), |
| progname); |
| suggest_asking_for_help (); |
| } |
| infile = optarg; |
| break; |
| |
| case 'I': |
| indent_flag = 0; |
| break; |
| |
| case 'k': |
| keep_old_flag = 1; |
| break; |
| |
| case 'n': |
| chicken_flag = 1; |
| break; |
| |
| case 'q': |
| quiet_flag = 1; |
| break; |
| |
| case 'r': |
| delete_flag = 1; |
| break; |
| |
| case 'R': |
| { |
| int error; |
| if (psecreg) |
| { |
| warning |
| (_("Extra regular expression specified, ignoring `%s'"), |
| optarg); |
| break; |
| } |
| psecreg = (regex_t *) xmalloc (sizeof (regex_t)); |
| |
| error = regcomp (psecreg, optarg, REG_ICASE|REG_NOSUB); |
| if (error != 0) |
| { |
| int errbuf_size = regerror (error, psecreg, NULL, 0); |
| char *errbuf = (char *) xmalloc (errbuf_size); |
| regerror (error, psecreg, errbuf, errbuf_size); |
| fatal (_("Error in regular expression `%s': %s"), |
| optarg, errbuf); |
| }; |
| } |
| break; |
| |
| case 'S': |
| default_section = optarg; |
| 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); |
| puts (""); |
| printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\ |
| License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>\n\ |
| This is free software: you are free to change and redistribute it.\n\ |
| There is NO WARRANTY, to the extent permitted by law.\n"), |
| "2022"); |
| exit (EXIT_SUCCESS); |
| |
| case 'W': |
| { |
| char *end = NULL; |
| unsigned long int val; |
| val = strtoul (optarg, &end, 0); |
| if (end == NULL || end == optarg || *end != '\0') |
| suggest_asking_for_help (); |
| maxwidth = val; |
| if (maxwidth <= 0) |
| suggest_asking_for_help (); |
| } |
| break; |
| |
| case 'x': |
| delete_flag = 1; |
| remove_exactly = 1; |
| break; |
| |
| default: |
| suggest_asking_for_help (); |
| } |
| } |
| |
| /* Interpret the non-option arguments as file names. */ |
| 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.")); |
| |
| /* Now read in the Info dir file. */ |
| if (debug_flag) |
| printf ("debug: reading dir file %s\n", dirfile); |
| |
| if (!delete_flag) |
| { |
| dir_data = readfile (dirfile, &dir_size, ensure_dirfile_exists, |
| &opened_dirfilename, &compression_program); |
| if (!dir_data) |
| pfatal_with_name (opened_dirfilename); |
| } |
| else |
| { |
| /* For "--remove" operation, it is not an error for the dir file |
| not to exist. */ |
| dir_data = readfile (dirfile, &dir_size, NULL, |
| &opened_dirfilename, &compression_program); |
| if (!dir_data) |
| { |
| warning (_("Could not read %s."), opened_dirfilename); |
| exit (EXIT_SUCCESS); |
| } |
| } |
| |
| dir_lines = findlines (dir_data, dir_size, &dir_nlines); |
| |
| parse_dir_file (dir_lines, dir_nlines, &dir_nodes); |
| |
| if (!delete_flag) |
| { |
| /* Find which sections match our regular expression. */ |
| if (psecreg) |
| { |
| struct node *node; |
| struct menu_section *section; |
| for (node = dir_nodes; node ; node = node->next) |
| for (section = node->sections; section ; section = section->next) |
| if (regexec (psecreg, section->name, 0, NULL, 0) == 0) |
| { |
| /* we have a match! */ |
| struct spec_section *next = |
| (struct spec_section *) xmalloc |
| (sizeof (struct spec_section)); |
| next->name = section->name; |
| next->next = input_sections; |
| next->missing = 0; |
| input_sections = next; |
| } |
| } |
| |
| } |
| |
| /* We will be comparing the entries in the dir file against the |
| current filename, so need to strip off any directory prefix and/or |
| [.info][.gz] suffix. */ |
| if (!remove_exactly) { |
| char *infile_basename = infile + strlen (infile); |
| |
| if (HAVE_DRIVE (infile)) |
| infile += 2; /* get past the drive spec X: */ |
| |
| while (infile_basename > infile && !IS_SLASH (infile_basename[-1])) |
| infile_basename--; |
| |
| infile_sans_info = strip_info_suffix (infile_basename); |
| } else |
| infile_sans_info = xstrdup(infile); |
| |
| /* Now Read the Info file and parse it into lines, unless we're |
| removing exactly. */ |
| if (!remove_exactly) |
| { |
| char *opened_infilename; |
| |
| if (debug_flag) |
| printf ("debug: reading input file %s\n", infile); |
| input_data = readfile (infile, &input_size, NULL, |
| &opened_infilename, NULL); |
| if (!input_data) |
| pfatal_with_name (opened_infilename); |
| input_lines = findlines (input_data, input_size, &input_nlines); |
| } |
| |
| i = parse_input (input_lines, input_nlines, |
| &input_sections, &entries_to_add_from_file, delete_flag); |
| if (!delete_flag) |
| { |
| /* If there are no entries on the command-line at all, use the entries |
| found in the Info file itself (if any). */ |
| if (entries_to_add == NULL) |
| { |
| entries_to_add = entries_to_add_from_file; |
| n_entries_to_add = i; |
| } |
| /* There are entries on the command-line, and they override the entries |
| found in the Info file. */ |
| else if (entries_to_add) |
| { |
| if (entries_to_add_from_file == NULL) |
| { |
| /* No entries found in the file anyway. Fill in any |
| missing names with the info file's basename. We're out |
| of luck for any missing descriptions. */ |
| add_missing_names (entries_to_add, infile_sans_info); |
| /* add_missing_descriptions (entries_to_add, "\n"); */ |
| } |
| else |
| { |
| /* Fill in any missing names or descriptions with what was |
| found in the Info file. */ |
| char *desc = NULL; |
| size_t desc_len = 0; |
| char *name = NULL; |
| size_t name_len = 0; |
| split_entry (entries_to_add_from_file->text, &name, &name_len, |
| &desc, &desc_len); |
| if (name) |
| { |
| /* If the name doesn't look right, bail and use the |
| name based on the Info file. */ |
| if (name[0] != '*') |
| add_missing_names (entries_to_add, infile_sans_info); |
| else |
| add_missing_names (entries_to_add, name); |
| free (name); |
| } |
| |
| if (desc) |
| { |
| add_missing_descriptions (entries_to_add, desc); |
| free (desc); |
| } |
| } |
| } |
| |
| /* Lastly, fill in any missing basenames that might still be hanging |
| around from --name options on the command-line. */ |
| add_missing_basenames (entries_to_add, infile_sans_info); |
| |
| /* Reformat the new entries if we're doing that. */ |
| if (indent_flag) |
| { |
| char *no_indent = getenv ("INSTALL_INFO_NO_INDENT"); |
| if (!no_indent) |
| reformat_new_entries (entries_to_add, calign, align, maxwidth); |
| } |
| |
| /* If we got no sections, use the --defsection value if it was |
| given, else "Miscellaneous". */ |
| if (input_sections == NULL) |
| { |
| input_sections = (struct spec_section *) |
| xmalloc (sizeof (struct spec_section)); |
| input_sections->name = default_section ? default_section |
| : "Miscellaneous"; |
| input_sections->next = NULL; |
| input_sections->missing = 1; |
| } |
| |
| if (entries_to_add == 0) |
| { /* No need to abort here, the original info file may not |
| have the requisite Texinfo commands. This is not |
| something an installer should have to correct (it's a |
| problem for the maintainer), and there's no need to cause |
| subsequent parts of `make install' to fail. */ |
| if (!quiet_flag) |
| warning (_("no info dir entry in `%s'"), infile); |
| exit (EXIT_SUCCESS); |
| } |
| |
| /* If the entries came from the command-line arguments, their |
| entry_sections pointers are not yet set. Walk the chain of |
| the entries and for each entry update entry_sections to point |
| to the head of the list of sections where this entry should |
| be put. Note that all the entries specified on the command |
| line get put into ALL the sections we've got, either from the |
| Info file, or (under --section) from the command line, |
| because in the loop below every entry inherits the entire |
| chain of sections. */ |
| if (n_entries_to_add > 0 && entries_to_add->entry_sections == NULL) |
| { |
| struct spec_entry *ep; |
| |
| for (ep = entries_to_add; ep; ep = ep->next) |
| ep->entry_sections = input_sections; |
| } |
| } |
| |
| if (delete_flag) |
| { |
| something_deleted = mark_entry_for_deletion (dir_lines, dir_nlines, |
| infile_sans_info); |
| if (!something_deleted && !remove_exactly) |
| { |
| struct spec_entry *entry; |
| for (entry = entries_to_add; entry; entry = entry->next) |
| { |
| /* If the entry came from the info file... */ |
| if (entry->entry_sections != NULL) |
| { |
| char *name = extract_menu_item_name (entry->text); |
| something_deleted = |
| mark_entry_for_deletion (dir_lines, dir_nlines, name); |
| free (name); |
| } |
| } |
| |
| if (!something_deleted) |
| { |
| struct spec_entry *entry; |
| for (entry = entries_to_add; entry; entry = entry->next) |
| { |
| /* If the entry came from the command-line... */ |
| if (entry->entry_sections == NULL) |
| something_deleted = |
| mark_entry_for_deletion (dir_lines, dir_nlines, |
| entry->text); |
| } |
| } |
| } |
| } |
| |
| /* Check for sections with zero entries and mark them for deletion. */ |
| if (delete_flag && something_deleted && !keep_old_flag) |
| { |
| struct node *node; |
| struct menu_section *section; |
| int section_empty; |
| |
| for (node = dir_nodes; node ; node = node->next) |
| for (section = node->sections; section ; section = section->next) |
| { |
| section_empty = 1; |
| for (i = section->end_line; i > section->start_line; i--) |
| { |
| if (dir_lines[i - 1].delete == 0 && |
| dir_lines[i - 1].size != 0) |
| { |
| section_empty = 0; |
| break; |
| } |
| } |
| |
| if (section_empty) |
| { |
| /* This gets rid of any trailing empty lines at the end |
| of the section, and the title too. */ |
| for (i = section->end_line; i >= section->start_line; i--) |
| dir_lines[i - 1].delete = 1; |
| } |
| } |
| } |
| |
| /* Decide where to add the new entries (unless --delete was used). |
| Find the menu sections to add them in. |
| In each section, find the proper alphabetical place to add |
| each of the entries. */ |
| 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; |
| /* Say we have found at least one section with this name, |
| so we need not add such a section. */ |
| spec->missing = 0; |
| /* For each entry, find the right place in this section |
| to add it. */ |
| for (entry = entries_to_add; entry; entry = entry->next) |
| { |
| /* Did they at all want this entry to be put into |
| this section? */ |
| for (spec = entry->entry_sections; |
| spec && spec != entry->entry_sections_tail; |
| spec = spec->next) |
| { |
| if (!strcmp (spec->name, section->name)) |
| break; |
| } |
| if (!spec || spec == entry->entry_sections_tail) |
| continue; |
| |
| /* Subtract one because dir_lines is zero-based, |
| but the `end_line' and `start_line' members are |
| one-based. */ |
| for (i = section->end_line - 1; |
| i >= section->start_line - 1; i--) |
| { |
| /* If an entry exists with the same name, |
| and was not marked for deletion |
| (which means it is for some other file), |
| we are in trouble. */ |
| if (dir_lines[i].start[0] == '*' |
| && menu_line_equal (entry->text, entry->text_len, |
| dir_lines[i].start, |
| dir_lines[i].size) |
| && !dir_lines[i].delete) |
| { |
| if (keep_old_flag) |
| { |
| add_at_line = -1; |
| break; |
| } |
| else |
| { |
| int j; |
| dir_lines[i].delete = 1; |
| for (j = i + 1; j < section->end_line; j++) |
| { |
| if (dir_lines[j].start[0] == '*') |
| break; |
| dir_lines[j].delete = 1; |
| } |
| } |
| } |
| if (dir_lines[i].start[0] == '*' |
| && menu_line_lessp (entry->text, entry->text_len, |
| dir_lines[i].start, |
| dir_lines[i].size)) |
| add_at_line = i; |
| } |
| if (add_at_line < 0) |
| continue; |
| insert_entry_here (entry, add_at_line, |
| dir_lines, n_entries_to_add); |
| } |
| } |
| } |
| |
| } |
| /* Decide where to add the new sections (unless --delete was used). |
| Alphabetically find the menu sections to add them before. */ |
| if (!delete_flag) |
| { |
| struct node *node; |
| struct node *top = NULL; |
| |
| /* Find the `Top' node. */ |
| for (node = dir_nodes; node; node = node->next) |
| if (node->name && strcmp (node->name, "Top") == 0) |
| top = node; |
| |
| if (top) |
| { |
| struct spec_section *spec; |
| int found = 0; |
| struct line_data *target_line = NULL; |
| for (spec = input_sections; spec; spec = spec->next) |
| { |
| found = 0; |
| target_line = NULL; |
| if (!spec->missing) |
| continue; |
| if (order_new_sections_alphabetically_flag) |
| { |
| struct menu_section *section; |
| struct menu_section *prev_section = NULL; |
| |
| /* Look for the first section name that |
| exceeds SPEC->NAME. */ |
| for (section = top->sections; section ; |
| section = section->next) |
| { |
| found = (mbscasecmp (spec->name, section->name) < 0); |
| if (found) |
| { |
| /* Mark the section for addition at this point. */ |
| if (prev_section) |
| target_line = &dir_lines[prev_section->end_line]; |
| else |
| target_line = |
| &dir_lines[top->sections->start_line - 2]; |
| |
| break; |
| } |
| prev_section = section; |
| } |
| } |
| |
| /* When we can't put a section anywhere, we put it at the |
| bottom of the file. */ |
| if (!found) |
| target_line = &dir_lines[top->end_line]; |
| |
| /* Add the section to our list of sections being added |
| at this point of the DIR file. */ |
| target_line->num_sections_to_add++; |
| target_line->add_sections_before = |
| (struct spec_section **) xrealloc |
| (target_line->add_sections_before, |
| (target_line->num_sections_to_add * |
| sizeof (struct spec_section *))); |
| i = target_line->num_sections_to_add - 1; |
| target_line->add_sections_before[i] = spec; |
| } |
| } |
| } |
| |
| if (delete_flag && !something_deleted && !quiet_flag) |
| warning (_("no entries found for `%s'; nothing deleted"), infile); |
| |
| if (debug_flag) |
| printf ("debug: writing dir file %s\n", opened_dirfilename); |
| if (chicken_flag) |
| printf ("test mode, not updating dir file %s\n", opened_dirfilename); |
| else |
| output_dirfile (opened_dirfilename, dir_nlines, dir_lines, |
| n_entries_to_add, entries_to_add, |
| input_sections, compression_program); |
| |
| exit (EXIT_SUCCESS); |
| } |
| |
| /* Divide the text at DATA (of SIZE bytes) into lines. |
| Return a vector of struct line_data describing the lines. |
| Store the length of that vector into *NLINESP. */ |
| |
| struct line_data * |
| findlines (char *data, int size, int *nlinesp) |
| { |
| int i; |
| int lineflag = 1; |
| int lines_allocated = 511; |
| int filled = 0; |
| struct line_data *lines |
| = xmalloc ((lines_allocated + 1) * sizeof (struct line_data)); |
| |
| for (i = 0; i < size; i++) |
| { |
| if (lineflag) |
| { |
| if (filled == lines_allocated) |
| { |
| /* try to keep things somewhat page-aligned */ |
| lines_allocated = ((lines_allocated + 1) * 2) - 1; |
| lines = xrealloc (lines, (lines_allocated + 1) |
| * sizeof (struct line_data)); |
| } |
| lines[filled].start = &data[i]; |
| lines[filled].add_entries_before = 0; |
| lines[filled].add_sections_before = NULL; |
| lines[filled].num_sections_to_add = 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; |
| |
| /* Do not leave garbage in the last element. */ |
| lines[filled].start = NULL; |
| lines[filled].add_entries_before = NULL; |
| lines[filled].add_sections_before = NULL; |
| lines[filled].num_sections_to_add = 0; |
| lines[filled].delete = 0; |
| lines[filled].size = 0; |
| |
| *nlinesp = filled; |
| return lines; |
| } |
| |
| /* This is the comparison function for qsort for a vector of pointers to |
| struct spec_section. (Have to use const void * as the parameter type |
| to avoid incompatible-with-qsort warnings.) |
| Compare the section names. */ |
| |
| int |
| compare_section_names (const void *p1, const void *p2) |
| { |
| struct spec_section **sec1 = (struct spec_section **) p1; |
| struct spec_section **sec2 = (struct spec_section **) p2; |
| char *name1 = (*sec1)->name; |
| char *name2 = (*sec2)->name; |
| return strcmp (name1, name2); |
| } |
| |
| /* This is the comparison function for qsort |
| for a vector of pointers to struct spec_entry. |
| Compare the entries' text. */ |
| |
| int |
| compare_entries_text (const void *p1, const void *p2) |
| { |
| struct spec_entry **entry1 = (struct spec_entry **) p1; |
| struct spec_entry **entry2 = (struct spec_entry **) p2; |
| char *text1 = (*entry1)->text; |
| char *text2 = (*entry2)->text; |
| char *colon1 = strchr (text1, ':'); |
| char *colon2 = strchr (text2, ':'); |
| int len1, len2; |
| |
| if (!colon1) |
| len1 = strlen (text1); |
| else |
| len1 = colon1 - text1; |
| if (!colon2) |
| len2 = strlen (text2); |
| else |
| len2 = colon2 - text2; |
| return mbsncasecmp (text1, text2, len1 <= len2 ? len1 : len2); |
| } |
| |
| /* Insert ENTRY into the ADD_ENTRIES_BEFORE vector for line number LINE_NUMBER |
| of the dir file. DIR_LINES and N_ENTRIES carry information from like-named |
| variables in main. */ |
| void |
| insert_entry_here (struct spec_entry *entry, int line_number, |
| struct line_data *dir_lines, int n_entries) |
| { |
| int i, j; |
| |
| 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; |
| } |
| |
| /* Find the place where this entry belongs. If there are already |
| several entries to add before LINE_NUMBER, make sure they are in |
| alphabetical order. */ |
| for (i = 0; i < n_entries; i++) |
| if (dir_lines[line_number].add_entries_before[i] == 0 |
| || menu_line_lessp (entry->text, strlen (entry->text), |
| dir_lines[line_number].add_entries_before[i]->text, |
| strlen (dir_lines[line_number].add_entries_before[i]->text))) |
| break; |
| |
| if (i == n_entries) |
| abort (); |
| |
| /* If we need to plug ENTRY into the middle of the |
| ADD_ENTRIES_BEFORE array, move the entries which should be output |
| after this one down one notch, before adding a new one. */ |
| if (dir_lines[line_number].add_entries_before[i] != 0) |
| for (j = n_entries - 1; j > i; j--) |
| dir_lines[line_number].add_entries_before[j] |
| = dir_lines[line_number].add_entries_before[j - 1]; |
| |
| dir_lines[line_number].add_entries_before[i] = entry; |
| } |