From 5fd83771641d15c418f747bd343ba6738d3875f7 Mon Sep 17 00:00:00 2001 From: Cameron Katri Date: Sun, 9 May 2021 14:20:58 -0400 Subject: Import macOS userland adv_cmds-176 basic_cmds-55 bootstrap_cmds-116.100.1 developer_cmds-66 diskdev_cmds-667.40.1 doc_cmds-53.60.1 file_cmds-321.40.3 mail_cmds-35 misc_cmds-34 network_cmds-606.40.1 patch_cmds-17 remote_cmds-63 shell_cmds-216.60.1 system_cmds-880.60.2 text_cmds-106 --- doc_cmds/makewhatis/makewhatis.c | 1094 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 1094 insertions(+) create mode 100644 doc_cmds/makewhatis/makewhatis.c (limited to 'doc_cmds/makewhatis/makewhatis.c') diff --git a/doc_cmds/makewhatis/makewhatis.c b/doc_cmds/makewhatis/makewhatis.c new file mode 100644 index 0000000..1a98842 --- /dev/null +++ b/doc_cmds/makewhatis/makewhatis.c @@ -0,0 +1,1094 @@ +/*- + * Copyright (c) 2002 John Rochester + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer, + * in this position and unchanged. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD: src/usr.bin/makewhatis/makewhatis.c,v 1.9 2002/09/04 23:29:04 dwmalone Exp $"); + +#include +#include +#include +#include + +/* Workaround for missing #define in sys/queue.h (3806865) */ +#ifndef SLIST_HEAD_INITIALIZER +#define SLIST_HEAD_INITIALIZER(head) { NULL } +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DEFAULT_MANPATH "/usr/share/man" +#define LINE_ALLOC 4096 + +static char blank[] = ""; + +/* + * Information collected about each man page in a section. + */ +struct page_info { + char * filename; + char * name; + char * suffix; + int gzipped; + ino_t inode; +}; + +/* + * An entry kept for each visited directory. + */ +struct visited_dir { + dev_t device; + ino_t inode; + SLIST_ENTRY(visited_dir) next; +}; + +/* + * an expanding string + */ +struct sbuf { + char * content; /* the start of the buffer */ + char * end; /* just past the end of the content */ + char * last; /* the last allocated character */ +}; + +/* + * Removes the last amount characters from the sbuf. + */ +#define sbuf_retract(sbuf, amount) \ + ((sbuf)->end -= (amount)) +/* + * Returns the length of the sbuf content. + */ +#define sbuf_length(sbuf) \ + ((sbuf)->end - (sbuf)->content) + +typedef char *edited_copy(char *from, char *to, int length); + +static int append; /* -a flag: append to existing whatis */ +static int verbose; /* -v flag: be verbose with warnings */ +static int indent = 24; /* -i option: description indentation */ +static const char *whatis_name="whatis";/* -n option: the name */ +static char *common_output; /* -o option: the single output file */ +static char *locale; /* user's locale if -L is used */ +static char *lang_locale; /* short form of locale */ +#ifndef __APPLE__ +static const char *machine; +#endif /* !__APPLE__ */ + +static int exit_code; /* exit code to use when finished */ +static SLIST_HEAD(, visited_dir) visited_dirs = + SLIST_HEAD_INITIALIZER(visited_dirs); + +/* + * While the whatis line is being formed, it is stored in whatis_proto. + * When finished, it is reformatted into whatis_final and then appended + * to whatis_lines. + */ +static struct sbuf *whatis_proto; +static struct sbuf *whatis_final; +static StringList *whatis_lines; /* collected output lines */ + +static char tmp_file[MAXPATHLEN]; /* path of temporary file, if any */ +static char tmp_rel_file[MAXPATHLEN]; + +/* A set of possible names for the NAME man page section */ +static const char *name_section_titles[] = { + "NAME", "Name", "NAMN", "BEZEICHNUNG", "\xcc\xbe\xbe\xce", + "\xee\xe1\xfa\xf7\xe1\xee\xe9\xe5", NULL +}; + +/* A subset of the mdoc(7) commands to ignore */ +static char mdoc_commands[] = "ArDvErEvFlLiNmPa"; + +/* + * Frees a struct page_info and its content. + */ +static void +free_page_info(struct page_info *info) +{ + free(info->filename); + free(info->name); + free(info->suffix); + free(info); +} + +/* + * Allocates and fills in a new struct page_info given the + * name of the man section directory and the dirent of the file. + * If the file is not a man page, returns NULL. + */ +static struct page_info * +new_page_info(char *dir, struct dirent *dirent) +{ + struct page_info *info; + int basename_length; + char *suffix; + struct stat st; + + info = (struct page_info *) malloc(sizeof(struct page_info)); + if (info == NULL) + err(1, "malloc"); + basename_length = strlen(dirent->d_name); + suffix = &dirent->d_name[basename_length]; + asprintf(&info->filename, "%s/%s", dir, dirent->d_name); + if ((info->gzipped = basename_length >= 4 && strcmp(&dirent->d_name[basename_length - 3], ".gz") == 0)) { + suffix -= 3; + *suffix = '\0'; + } + for (;;) { + if (--suffix == dirent->d_name || !isalnum(*suffix)) { + if (*suffix == '.') + break; + if (verbose) + warnx("%s: invalid man page name", info->filename); + free(info->filename); + free(info); + return NULL; + } + } + *suffix++ = '\0'; + info->name = strdup(dirent->d_name); + info->suffix = strdup(suffix); + if (stat(info->filename, &st) < 0) { + warn("%s", info->filename); + free_page_info(info); + return NULL; + } + if (!S_ISREG(st.st_mode)) { + if (verbose && !S_ISDIR(st.st_mode)) + warnx("%s: not a regular file", info->filename); + free_page_info(info); + return NULL; + } + info->inode = st.st_ino; + return info; +} + +/* + * Reset an sbuf's length to 0. + */ +static void +sbuf_clear(struct sbuf *sbuf) +{ + sbuf->end = sbuf->content; +} + +/* + * Allocate a new sbuf. + */ +static struct sbuf * +new_sbuf(void) +{ + struct sbuf *sbuf = (struct sbuf *) malloc(sizeof(struct sbuf)); + sbuf->content = (char *) malloc(LINE_ALLOC); + sbuf->last = sbuf->content + LINE_ALLOC - 1; + sbuf_clear(sbuf); + return sbuf; +} + +/* + * Ensure that there is enough room in the sbuf for nchars more characters. + */ +static void +sbuf_need(struct sbuf *sbuf, int nchars) +{ + char *new_content; + size_t size, cntsize; + + /* double the size of the allocation until the buffer is big enough */ + while (sbuf->end + nchars > sbuf->last) { + size = sbuf->last + 1 - sbuf->content; + size *= 2; + cntsize = sbuf->end - sbuf->content; + + new_content = (char *)malloc(size); + memcpy(new_content, sbuf->content, cntsize); + free(sbuf->content); + sbuf->content = new_content; + sbuf->end = new_content + cntsize; + sbuf->last = new_content + size - 1; + } +} + +/* + * Appends a string of a given length to the sbuf. + */ +static void +sbuf_append(struct sbuf *sbuf, const char *text, int length) +{ + if (length > 0) { + sbuf_need(sbuf, length); + memcpy(sbuf->end, text, length); + sbuf->end += length; + } +} + +/* + * Appends a null-terminated string to the sbuf. + */ +static void +sbuf_append_str(struct sbuf *sbuf, char *text) +{ + sbuf_append(sbuf, text, strlen(text)); +} + +/* + * Appends an edited null-terminated string to the sbuf. + */ +static void +sbuf_append_edited(struct sbuf *sbuf, char *text, edited_copy copy) +{ + int length = strlen(text); + if (length > 0) { + sbuf_need(sbuf, length); + sbuf->end = copy(text, sbuf->end, length); + } +} + +/* + * Strips any of a set of chars from the end of the sbuf. + */ +static void +sbuf_strip(struct sbuf *sbuf, const char *set) +{ + while (sbuf->end > sbuf->content && strchr(set, sbuf->end[-1]) != NULL) + sbuf->end--; +} + +/* + * Returns the null-terminated string built by the sbuf. + */ +static char * +sbuf_content(struct sbuf *sbuf) +{ + *sbuf->end = '\0'; + return sbuf->content; +} + +/* + * Returns true if no man page exists in the directory with + * any of the names in the StringList. + */ +static int +no_page_exists(char *dir, StringList *names, char *suffix) +{ + char path[MAXPATHLEN]; + size_t i; + + for (i = 0; i < names->sl_cur; i++) { + snprintf(path, sizeof path, "%s/%s.%s.gz", dir, names->sl_str[i], suffix); + if (access(path, F_OK) < 0) { + path[strlen(path) - 3] = '\0'; + if (access(path, F_OK) < 0) + continue; + } + return 0; + } + return 1; +} + +static void +trap_signal(int sig __unused) +{ + if (tmp_file[0] != '\0') + unlink(tmp_file); + exit(1); +} + +/* + * Attempts to open an output file. Returns NULL if unsuccessful. + */ +static FILE * +open_output(char *name, int dir_fd, char *rel_name) +{ + FILE *output; + int output_fd; + struct stat statbuf; + + whatis_lines = sl_init(); + if (append) { + char line[LINE_ALLOC]; + + output_fd = openat(dir_fd, rel_name, O_RDONLY); + if (output_fd == -1) { + warn("%s", name); + exit_code = 1; + return NULL; + } + output = fdopen(output_fd, "r"); + if (output == NULL) { + warn("%s", name); + exit_code = 1; + return NULL; + } + while (fgets(line, sizeof line, output) != NULL) { + line[strlen(line) - 1] = '\0'; + sl_add(whatis_lines, strdup(line)); + } + fclose(output); + } + if (common_output == NULL) { + snprintf(tmp_file, sizeof tmp_file, "%s.tmp", name); + snprintf(tmp_rel_file, sizeof tmp_rel_file, "%s.tmp", rel_name); + name = tmp_file; + rel_name = tmp_rel_file; + } + /* Bail out if the file is actually a symlink or has another link. + * This script can run as root and we don't want to risk writing + * into a random location. + * See rdar://problem/55280616 + */ + output_fd = openat(dir_fd, rel_name, O_WRONLY | O_NOFOLLOW | O_CREAT | O_TRUNC, 0644); + if (output_fd == -1) { + warn("%s", name); + exit_code = 1; + return NULL; + } + if (fstat(output_fd, &statbuf) == -1) { + warn("%s: unable to stat", name); + close(output_fd); + exit_code = 1; + return NULL; + } + if (statbuf.st_nlink > 1) { + warnx("%s: is a hardlink", name); + close(output_fd); + exit_code = 1; + return NULL; + } + output = fdopen(output_fd, "w"); + if (output == NULL) { + warn("%s", name); + exit_code = 1; + return NULL; + } + return output; +} + +static int +linesort(const void *a, const void *b) +{ + return strcmp((*(const char * const *)a), (*(const char * const *)b)); +} + +/* + * Writes the unique sorted lines to the output file. + */ +static void +finish_output(FILE *output, char *name, int dir_fd, char *rel_name) +{ + size_t i; + char *prev = NULL; + + qsort(whatis_lines->sl_str, whatis_lines->sl_cur, sizeof(char *), linesort); + for (i = 0; i < whatis_lines->sl_cur; i++) { + char *line = whatis_lines->sl_str[i]; + if (i > 0 && strcmp(line, prev) == 0) + continue; + prev = line; + fputs(line, output); + putc('\n', output); + } + fclose(output); + sl_free(whatis_lines, 1); + if (common_output == NULL) { + renameat(dir_fd, tmp_rel_file, dir_fd, rel_name); + unlinkat(dir_fd, tmp_rel_file, 0); + } +} + +static FILE * +open_whatis(char *mandir, int mandir_fd) +{ + char filename[MAXPATHLEN]; + + snprintf(filename, sizeof filename, "%s/%s", mandir, whatis_name); + return open_output(filename, mandir_fd, whatis_name); +} + +static void +finish_whatis(FILE *output, char *mandir, int mandir_fd) +{ + char filename[MAXPATHLEN]; + + snprintf(filename, sizeof filename, "%s/%s", mandir, whatis_name); + finish_output(output, filename, mandir_fd, whatis_name); +} + +/* + * Tests to see if the given directory has already been visited. + */ +static int +already_visited(char *dir) +{ + struct stat st; + struct visited_dir *visit; + + if (stat(dir, &st) < 0) { + warn("%s", dir); + exit_code = 1; + return 1; + } + SLIST_FOREACH(visit, &visited_dirs, next) { + if (visit->inode == st.st_ino && + visit->device == st.st_dev) { + warnx("already visited %s", dir); + return 1; + } + } + visit = (struct visited_dir *) malloc(sizeof(struct visited_dir)); + visit->device = st.st_dev; + visit->inode = st.st_ino; + SLIST_INSERT_HEAD(&visited_dirs, visit, next); + return 0; +} + +/* + * Removes trailing spaces from a string, returning a pointer to just + * beyond the new last character. + */ +static char * +trim_rhs(char *str) +{ + char *rhs = &str[strlen(str)]; + while (--rhs > str && isspace(*rhs)) + ; + *++rhs = '\0'; + return rhs; +} + +/* + * Returns a pointer to the next non-space character in the string. + */ +static char * +skip_spaces(char *s) +{ + while (*s != '\0' && isspace(*s)) + s++; + return s; +} + +/* + * Returns whether the string contains only digits. + */ +static int +only_digits(char *line) +{ + if (!isdigit(*line++)) + return 0; + while (isdigit(*line)) + line++; + return *line == '\0'; +} + +/* + * Returns whether the line is of one of the forms: + * .Sh NAME + * .Sh "NAME" + * etc. + * assuming that section_start is ".Sh". + */ +static int +name_section_line(char *line, const char *section_start) +{ + char *rhs; + const char **title; + + if (strncmp(line, section_start, 3) != 0) + return 0; + line = skip_spaces(line + 3); + rhs = trim_rhs(line); + if (*line == '"') { + line++; + if (*--rhs == '"') + *rhs = '\0'; + } + for (title = name_section_titles; *title != NULL; title++) + if (strcmp(*title, line) == 0) + return 1; + return 0; +} + +/* + * Copies characters while removing the most common nroff/troff + * markup: + * \(em, \(mi, \s[+-N], \& + * \fF, \f(fo, \f[font] + * \*s, \*(st, \*[stringvar] + */ +static char * +de_nroff_copy(char *from, char *to, int fromlen) +{ + char *from_end = &from[fromlen]; + while (from < from_end) { + switch (*from) { + case '\\': + switch (*++from) { + case '(': + if (strncmp(&from[1], "em", 2) == 0 || + strncmp(&from[1], "mi", 2) == 0) { + from += 3; + continue; + } + break; + case 's': + if (*++from == '-') + from++; + while (isdigit(*from)) + from++; + continue; + case 'f': + case '*': + if (*++from == '(') + from += 3; + else if (*from == '[') { + while (*++from != ']' && from < from_end); + from++; + } else + from++; + continue; + case '&': + from++; + continue; + } + break; + } + *to++ = *from++; + } + return to; +} + +/* + * Appends a string with the nroff formatting removed. + */ +static void +add_nroff(char *text) +{ + sbuf_append_edited(whatis_proto, text, de_nroff_copy); +} + +/* + * Appends "name(suffix), " to whatis_final. + */ +static void +add_whatis_name(char *name, char *suffix) +{ + if (*name != '\0') { + sbuf_append_str(whatis_final, name); + sbuf_append(whatis_final, "(", 1); + sbuf_append_str(whatis_final, suffix); + sbuf_append(whatis_final, "), ", 3); + } +} + +/* + * Processes an old-style man(7) line. This ignores commands with only + * a single number argument. + */ +static void +process_man_line(char *line) +{ + if (*line == '.') { + while (isalpha(*++line)) + ; + line = skip_spaces(line); + if (only_digits(line)) + return; + } else + line = skip_spaces(line); +#ifdef __APPLE__ + /* 4454557 */ + if (*line == '"') + ++line; +#endif /* __APPLE__ */ + if (*line != '\0') { + add_nroff(line); + sbuf_append(whatis_proto, " ", 1); + } +} + +/* + * Processes a new-style mdoc(7) line. + */ +static void +process_mdoc_line(char *line) +{ + int xref; + int arg = 0; + char *line_end = &line[strlen(line)]; + int orig_length = sbuf_length(whatis_proto); + char *next; + + if (*line == '\0') + return; + if (line[0] != '.' || !isupper(line[1]) || !islower(line[2])) { + add_nroff(skip_spaces(line)); + sbuf_append(whatis_proto, " ", 1); + return; + } + xref = strncmp(line, ".Xr", 3) == 0; + line += 3; + while ((line = skip_spaces(line)) < line_end) { + if (*line == '"') { + next = ++line; + for (;;) { + next = strchr(next, '"'); + if (next == NULL) + break; + memmove(next, next + 1, strlen(next)); + line_end--; + if (*next != '"') + break; + next++; + } + } else + next = strpbrk(line, " \t"); + if (next != NULL) + *next++ = '\0'; + else + next = line_end; + if (isupper(*line) && islower(line[1]) && line[2] == '\0') { + if (strcmp(line, "Ns") == 0) { + arg = 0; + line = next; + continue; + } + if (strstr(mdoc_commands, line) != NULL) { + line = next; + continue; + } + } + if (arg > 0 && strchr(",.:;?!)]", *line) == 0) { + if (xref) { + sbuf_append(whatis_proto, "(", 1); + add_nroff(line); + sbuf_append(whatis_proto, ")", 1); + xref = 0; + line = blank; + } else + sbuf_append(whatis_proto, " ", 1); + } + add_nroff(line); + arg++; + line = next; + } + if (sbuf_length(whatis_proto) > orig_length) + sbuf_append(whatis_proto, " ", 1); +} + +/* + * Collects a list of comma-separated names from the text. + */ +static void +collect_names(StringList *names, char *text) +{ + char *arg; + + for (;;) { + arg = text; + text = strchr(text, ','); + if (text != NULL) + *text++ = '\0'; + sl_add(names, arg); + if (text == NULL) + return; + if (*text == ' ') + text++; + } +} + +enum { STATE_UNKNOWN, STATE_MANSTYLE, STATE_MDOCNAME, STATE_MDOCDESC }; + +/* + * Processes a man page source into a single whatis line and adds it + * to whatis_lines. + */ +static void +process_page(struct page_info *page, char *section_dir) +{ + gzFile *in; + char buffer[4096]; + char *line; + StringList *names; + char *descr; + int state = STATE_UNKNOWN; + size_t i; + + sbuf_clear(whatis_proto); + if ((in = gzopen(page->filename, "r")) == NULL) { + warn("%s", page->filename); + exit_code = 1; + return; + } + while (gzgets(in, buffer, sizeof buffer) != NULL) { + line = buffer; + if (strncmp(line, ".\\\"", 3) == 0) /* ignore comments */ + continue; + switch (state) { + /* + * haven't reached the NAME section yet. + */ + case STATE_UNKNOWN: + if (name_section_line(line, ".SH")) + state = STATE_MANSTYLE; + else if (name_section_line(line, ".Sh")) + state = STATE_MDOCNAME; + continue; + /* + * Inside an old-style .SH NAME section. + */ + case STATE_MANSTYLE: + if ((strncmp(line, ".SH", 3) == 0) || (strncmp(line, ".SS", 3) == 0)) + break; + trim_rhs(line); + if (strcmp(line, ".") == 0) + continue; + if (strncmp(line, ".IX", 3) == 0) { + line += 3; + line = skip_spaces(line); + } + process_man_line(line); + continue; + /* + * Inside a new-style .Sh NAME section (the .Nm part). + */ + case STATE_MDOCNAME: + trim_rhs(line); + if (strncmp(line, ".Nm", 3) == 0) { + process_mdoc_line(line); + continue; + } else { + if (strcmp(line, ".") == 0) + continue; + sbuf_append(whatis_proto, "- ", 2); + state = STATE_MDOCDESC; + } + /* fall through */ + /* + * Inside a new-style .Sh NAME section (after the .Nm-s). + */ + case STATE_MDOCDESC: + if (strncmp(line, ".Sh", 3) == 0) + break; + trim_rhs(line); + if (strcmp(line, ".") == 0) + continue; + process_mdoc_line(line); + continue; + } + break; + } + gzclose(in); + sbuf_strip(whatis_proto, " \t.-"); + line = sbuf_content(whatis_proto); + /* + * line now contains the appropriate data, but without + * the proper indentation or the section appended to each name. + */ + descr = strstr(line, " - "); + if (descr == NULL) { + descr = strchr(line, ' '); + if (descr == NULL) { + if (verbose) + fprintf(stderr, " ignoring junk description \"%s\"\n", line); + return; + } + *descr++ = '\0'; + } else { + *descr = '\0'; + descr += 3; + } + names = sl_init(); + collect_names(names, line); + sbuf_clear(whatis_final); + if (!sl_find(names, page->name) && no_page_exists(section_dir, names, page->suffix)) { + /* + * Add the page name since that's the only thing that + * man(1) will find. + */ + add_whatis_name(page->name, page->suffix); + } + for (i = 0; i < names->sl_cur; i++) + add_whatis_name(names->sl_str[i], page->suffix); + sl_free(names, 0); + sbuf_retract(whatis_final, 2); /* remove last ", " */ + while (sbuf_length(whatis_final) < indent) + sbuf_append(whatis_final, " ", 1); + sbuf_append(whatis_final, " - ", 3); + sbuf_append_str(whatis_final, skip_spaces(descr)); + sl_add(whatis_lines, strdup(sbuf_content(whatis_final))); +} + +/* + * Sorts pages first by inode number, then by name. + */ +static int +pagesort(const void *a, const void *b) +{ + const struct page_info *p1 = *(struct page_info * const *) a; + const struct page_info *p2 = *(struct page_info * const *) b; + if (p1->inode == p2->inode) + return strcmp(p1->name, p2->name); + return p1->inode - p2->inode; +} + +/* + * Processes a single man section. + */ +static void +process_section(char *section_dir) +{ + struct dirent **entries; + int nentries; + struct page_info **pages; + int npages = 0; + int i; + ino_t prev_inode = 0; + + if (verbose) + fprintf(stderr, " %s\n", section_dir); + + /* + * scan the man section directory for pages + */ + nentries = scandir(section_dir, &entries, NULL, alphasort); + if (nentries < 0) { + warn("%s", section_dir); + exit_code = 1; + return; + } + /* + * collect information about man pages + */ + pages = (struct page_info **) calloc(nentries, sizeof(struct page_info *)); + for (i = 0; i < nentries; i++) { + struct page_info *info = new_page_info(section_dir, entries[i]); + if (info != NULL) + pages[npages++] = info; + free(entries[i]); + } + free(entries); + qsort(pages, npages, sizeof(struct page_info *), pagesort); + /* + * process each unique page + */ + for (i = 0; i < npages; i++) { + struct page_info *page = pages[i]; + if (page->inode != prev_inode) { + prev_inode = page->inode; + if (verbose) + fprintf(stderr, " reading %s\n", page->filename); + process_page(page, section_dir); + } else if (verbose) + fprintf(stderr, " skipping %s, duplicate\n", page->filename); + free_page_info(page); + } + free(pages); +} + +/* + * Returns whether the directory entry is a man page section. + */ +static int +select_sections(struct dirent *entry) +{ + char *p = &entry->d_name[3]; + + if (strncmp(entry->d_name, "man", 3) != 0) + return 0; + while (*p != '\0') { + if (!isalnum(*p++)) + return 0; + } + return 1; +} + +/* + * Processes a single top-level man directory by finding all the + * sub-directories named man* and processing each one in turn. + */ +static void +process_mandir(char *dir_name) +{ + int dir_fd; + DIR *dir; + FILE *fp = NULL; + struct dirent *entry; + struct stat st; + + if (already_visited(dir_name)) + return; + if (verbose) + fprintf(stderr, "man directory %s\n", dir_name); + + dir_fd = open(dir_name, O_RDONLY | O_DIRECTORY | O_NOFOLLOW); + if (dir_fd == -1) { + warn("%s", dir_name); + exit_code = 1; + return; + } + dir = fdopendir(dir_fd); + if (dir == NULL) { + warn("%s", dir_name); + close(dir_fd); + exit_code = 1; + return; + } + if (common_output == NULL && (fp = open_whatis(dir_name, dir_fd)) == NULL) { + closedir(dir); + return; + } + while ((entry = readdir(dir)) != NULL) { + char section_dir[MAXPATHLEN]; + if (select_sections(entry) == 0) + continue; + snprintf(section_dir, sizeof section_dir, "%s/%s", dir_name, entry->d_name); + process_section(section_dir); +#ifndef __APPLE__ + snprintf(section_dir, sizeof section_dir, "%s/%s/%s", dir_name, + entry->d_name, machine); + if (stat(section_dir, &st) == 0 && S_ISDIR(st.st_mode)) + process_section(section_dir); +#endif /* !__APPLE__ */ + } + if (common_output == NULL) + finish_whatis(fp, dir_name, dir_fd); + closedir(dir); +} + +/* + * Processes one argument, which may be a colon-separated list of + * directories. + */ +static void +process_argument(const char *arg) +{ + char *dir; + char *mandir; + char *parg; + + parg = strdup(arg); + if (parg == NULL) + err(1, "out of memory"); + while ((dir = strsep(&parg, ":")) != NULL) { + if (locale != NULL) { + asprintf(&mandir, "%s/%s", dir, locale); + process_mandir(mandir); + free(mandir); + if (lang_locale != NULL) { + asprintf(&mandir, "%s/%s", dir, lang_locale); + process_mandir(mandir); + free(mandir); + } + } else { + process_mandir(dir); + } + } + free(parg); +} + + +int +main(int argc, char **argv) +{ + int opt; + FILE *fp = NULL; + + while ((opt = getopt(argc, argv, "ai:n:o:vL")) != -1) { + switch (opt) { + case 'a': + append++; + break; + case 'i': + indent = atoi(optarg); + break; + case 'n': + whatis_name = optarg; + break; + case 'o': + common_output = optarg; + break; + case 'v': + verbose++; + break; + case 'L': + locale = getenv("LC_ALL"); + if (locale == NULL) + locale = getenv("LC_CTYPE"); + if (locale == NULL) + locale = getenv("LANG"); + if (locale != NULL) { + char *sep = strchr(locale, '_'); + if (sep != NULL && isupper(sep[1]) && + isupper(sep[2])) { + asprintf(&lang_locale, "%.*s%s", sep - locale, locale, &sep[3]); + } + } + break; + default: + fprintf(stderr, "usage: %s [-a] [-i indent] [-n name] [-o output_file] [-v] [-L] [directories...]\n", argv[0]); + exit(1); + } + } + + signal(SIGINT, trap_signal); + signal(SIGHUP, trap_signal); + signal(SIGQUIT, trap_signal); + signal(SIGTERM, trap_signal); + SLIST_INIT(&visited_dirs); + whatis_proto = new_sbuf(); + whatis_final = new_sbuf(); + +#ifndef __APPLE__ + if ((machine = getenv("MACHINE")) == NULL) + machine = MACHINE; +#endif /* !__APPLE__ */ + + if (common_output != NULL && (fp = open_output(common_output, AT_FDCWD, common_output)) == NULL) + err(1, "%s", common_output); + if (optind == argc) { + const char *manpath = getenv("MANPATH"); + if (manpath == NULL) + manpath = DEFAULT_MANPATH; + process_argument(manpath); + } else { + while (optind < argc) + process_argument(argv[optind++]); + } + if (common_output != NULL) + finish_output(fp, common_output, AT_FDCWD, common_output); + exit(exit_code); +} -- cgit v1.2.3-56-ge451