# # This file and its contents are supplied under the terms of the # Common Development and Distribution License ("CDDL"), version 1.0. # You may only use this file in accordance with the terms of version # 1.0 of the CDDL. # # A full copy of the text of the CDDL should have accompanied this # source. A copy of the CDDL is also available via the Internet at # http://www.illumos.org/license/CDDL. # # # Copyright 2012 Nexenta Systems, Inc. All rights reserved. # Copyright 2014 Garrett D'Amore # Copyright 2019 OmniOS Community Edition (OmniOSce) Association. # PROG= man LINKS= apropos whatis catman LIBLINKS = makewhatis OBJS= makewhatis.o man.o stringlist.o MANIFEST= update-man-index.xml SVCMETHOD= update-man-index include $(SRC)/cmd/Makefile.cmd ROOTMANIFESTDIR= $(ROOTSVCSYSTEM) CFLAGS += $(CCVERBOSE) ROOTLINKS= $(LINKS:%=$(ROOTBIN)/%) $(LIBLINKS:%=$(ROOTLIB)/%) .KEEP_STATE : all: $(PROG) clean: $(RM) $(OBJS) install: all $(ROOTPROG) $(ROOTLINKS) $(ROOTMANIFEST) $(ROOTSVCMETHOD) check: $(CHKMANIFEST) $(PROG): $(OBJS) $(LINK.c) $(OBJS) -o $@ $(LDLIBS) $(POST_PROCESS) $(ROOTLINKS): $(ROOTPROG) $(RM) $@; $(LN) $(ROOTPROG) $@ include $(SRC)/cmd/Makefile.targ man.c: Copyright (c) 1980 Regents of the University of California. 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. 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. All advertising materials mentioning features or use of this software must display the following acknowledgement: This product includes software developed by the University of California, Berkeley and its contributors. 4. Neither the name of the University nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR CONTRIBUTORS 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. makewhatis.c: 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. stringlist.c, stringlist.h: Copyright (c) 1994 Christos Zoulas 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. 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. 4. 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. PORTIONS OF MAN COMMAND FUNCTIONALITY /* * 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. */ /* * Copyright 2012 Nexenta Systems, Inc. All rights reserved. * Copyright 2014 Garrett D'Amore * Copyright 2022 Oxide Computer Company */ #include #include #include #include #include #include #include #include #include #include #include #include #include "man.h" #include "stringlist.h" /* Information collected about each man page in a section */ struct page_info { char *filename; char *name; char *suffix; ino_t inode; }; /* 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 */ }; /* Remove the last amount characters from the sbuf */ #define sbuf_retract(sbuf, amount) ((sbuf)->end -= (amount)) /* Return the length of the sbuf content */ #define sbuf_length(sbuf) ((sbuf)->end - (sbuf)->content) typedef char *edited_copy(char *from, char *to, int length); /* * 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 tempfile[MAXPATHLEN]; /* path of temporary file, if any */ #define MDOC_COMMANDS "ArDvErEvFlLiNmPa" /* Free 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); } /* * Allocate and fill 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, return 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; if ((info = malloc(sizeof (struct page_info))) == NULL) err(1, "malloc"); basename_length = strlen(dirent->d_name); suffix = &dirent->d_name[basename_length]; if (asprintf(&info->filename, "%s/%s", dir, dirent->d_name) == -1) err(1, "asprintf"); for (;;) { if (--suffix == dirent->d_name || !isalnum(*suffix)) { if (*suffix == '.') break; 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)) { free_page_info(info); return (NULL); } info->inode = st.st_ino; return (info); } /* * Reset sbuf 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; if ((sbuf = malloc(sizeof (struct sbuf))) == NULL) err(1, "malloc"); if ((sbuf->content = (char *)malloc(LINE_ALLOC)) == NULL) err(1, "malloc"); 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; size_t grow = 128; while (grow < nchars) { grow += 128; /* we grow in chunks of 128 bytes */ } /* Grow if the buffer isn't big enough */ if (sbuf->end + nchars > sbuf->last) { size = sbuf->last + 1 - sbuf->content; size += grow; cntsize = sbuf->end - sbuf->content; if ((new_content = realloc(sbuf->content, size)) == NULL) { perror("realloc"); if (tempfile[0] != '\0') (void) unlink(tempfile); exit(1); } sbuf->content = new_content; sbuf->end = new_content + cntsize; sbuf->last = new_content + size - 1; } } /* * Append 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); (void) memcpy(sbuf->end, text, length); sbuf->end += length; } } /* * Append a null-terminated string to the sbuf. */ static void sbuf_append_str(struct sbuf *sbuf, char *text) { sbuf_append(sbuf, text, strlen(text)); } /* * Append an edited null-terminated string to the sbuf. */ static void sbuf_append_edited(struct sbuf *sbuf, char *text, edited_copy copy) { int length; if ((length = strlen(text)) > 0) { sbuf_need(sbuf, length); sbuf->end = copy(text, sbuf->end, length); } } /* * Strip 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--; } /* * Return the null-terminated string built by the sbuf. */ static char * sbuf_content(struct sbuf *sbuf) { *sbuf->end = '\0'; return (sbuf->content); } /* * Return 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]; char *suffixes[] = { "", ".gz", ".bz2", NULL }; size_t i; int j; for (i = 0; i < names->sl_cur; i++) { for (j = 0; suffixes[j] != NULL; j++) { (void) snprintf(path, MAXPATHLEN, "%s/%s.%s%s", dir, names->sl_str[i], suffix, suffixes[j]); if (access(path, F_OK) == 0) { return (0); } } } return (1); } /* ARGSUSED sig */ static void trap_signal(int sig) { if (tempfile[0] != '\0') (void) unlink(tempfile); exit(1); } /* * Attempt to open an output file. * Return NULL if unsuccessful. */ static FILE * open_output(char *name) { FILE *output; whatis_lines = sl_init(); (void) snprintf(tempfile, MAXPATHLEN, "%s.tmp", name); name = tempfile; if ((output = fopen(name, "w")) == NULL) { warn("%s", name); return (NULL); } return (output); } static int linesort(const void *a, const void *b) { return (strcmp((*(const char * const *)a), (*(const char * const *)b))); } /* * Write the unique sorted lines to the output file. */ static void finish_output(FILE *output, char *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; (void) fputs(line, output); (void) putc('\n', output); } (void) fclose(output); sl_free(whatis_lines, 1); (void) rename(tempfile, name); (void) unlink(tempfile); } static FILE * open_whatis(char *mandir) { char filename[MAXPATHLEN]; (void) snprintf(filename, MAXPATHLEN, "%s/%s", mandir, WHATIS); return (open_output(filename)); } static void finish_whatis(FILE *output, char *mandir) { char filename[MAXPATHLEN]; (void) snprintf(filename, MAXPATHLEN, "%s/%s", mandir, WHATIS); finish_output(output, filename); } /* * Remove trailing spaces from a string, returning a pointer to just * beyond the new last character. */ static char * trim_rhs(char *str) { char *rhs; rhs = &str[strlen(str)]; while (--rhs > str && isspace(*rhs)) ; *++rhs = '\0'; return (rhs); } /* * Return 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); } /* * Return 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; 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'; } if (strcmp(line, "NAME") == 0) return (1); return (0); } /* * Copy 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); } /* * Append 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) { char *p; if (*line == '.') { while (isalpha(*++line)) ; p = line = skip_spaces(line); while (*p != '\0') { if (!isdigit(*p)) break; p++; } if (*p == '\0') return; } else line = skip_spaces(line); 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; (void) 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(line, MDOC_COMMANDS) != 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; } else { sbuf_append(whatis_proto, " ", 1); } } add_nroff(line); arg++; line = next; } if (sbuf_length(whatis_proto) > orig_length) sbuf_append(whatis_proto, " ", 1); } /* * Collect 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'; (void) sl_add(names, arg); if (text == NULL) return; if (*text == ' ') text++; } } enum { STATE_UNKNOWN, STATE_MANSTYLE, STATE_MDOCNAME, STATE_MDOCDESC }; /* * Process a man page source into a single whatis line and add it * to whatis_lines. */ static void process_page(struct page_info *page, char *section_dir) { FILE *fp; stringlist *names; char *descr; int state = STATE_UNKNOWN; size_t i; char *line = NULL; size_t linecap = 0; sbuf_clear(whatis_proto); if ((fp = fopen(page->filename, "r")) == NULL) { warn("%s", page->filename); return; } while (getline(&line, &linecap, fp) > 0) { /* Skip comments */ if (strncmp(line, ".\\\"", 3) == 0) 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: { char *altline; if (strncmp(line, ".SH", 3) == 0 || strncmp(line, ".SS", 3) == 0) break; (void) trim_rhs(line); if (strcmp(line, ".") == 0) continue; altline = line; if (strncmp(altline, ".IX", 3) == 0) { altline += 3; altline = skip_spaces(altline); } process_man_line(altline); continue; } /* Inside a new-style .Sh NAME section (the .Nm part) */ case STATE_MDOCNAME: (void) 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; } /* FALLTHROUGH */ /* Inside a new-style .Sh NAME section (after the .Nm-s) */ case STATE_MDOCDESC: if (strncmp(line, ".Sh", 3) == 0) break; (void) trim_rhs(line); if (strcmp(line, ".") == 0) continue; process_mdoc_line(line); continue; } break; } (void) fclose(fp); 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) 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); /* Remove last ", " */ sbuf_retract(whatis_final, 2); while (sbuf_length(whatis_final) < INDENT) sbuf_append(whatis_final, " ", 1); sbuf_append(whatis_final, " - ", 3); sbuf_append_str(whatis_final, skip_spaces(descr)); (void) sl_add(whatis_lines, strdup(sbuf_content(whatis_final))); } /* * Sort 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); } /* * Process 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; /* Scan the man section directory for pages */ nentries = scandir(section_dir, &entries, NULL, alphasort); /* 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; process_page(page, section_dir); } free_page_info(page); } free(pages); } /* * Return whether the directory entry is a man page section. */ static int select_sections(const struct dirent *entry) { const 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); } /* * Process a single top-level man directory by finding all the * sub-directories named man* and processing each one in turn. */ void mwpath(char *path) { FILE *fp = NULL; struct dirent **entries; int nsections; int i; (void) signal(SIGINT, trap_signal); (void) signal(SIGHUP, trap_signal); (void) signal(SIGQUIT, trap_signal); (void) signal(SIGTERM, trap_signal); whatis_proto = new_sbuf(); whatis_final = new_sbuf(); nsections = scandir(path, &entries, select_sections, alphasort); if ((fp = open_whatis(path)) == NULL) return; for (i = 0; i < nsections; i++) { char section_dir[MAXPATHLEN]; (void) snprintf(section_dir, MAXPATHLEN, "%s/%s", path, entries[i]->d_name); process_section(section_dir); free(entries[i]); } free(entries); finish_whatis(fp, path); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 1990, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2012, Josef 'Jeff' Sipek . All rights reserved. * Copyright 2014 Garrett D'Amore * Copyright 2016 Nexenta Systems, Inc. * Copyright 2019 Joyent, Inc. */ /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T. */ /* All rights reserved. */ /* * University Copyright- Copyright (c) 1982, 1986, 1988 * The Regents of the University of California * All Rights Reserved * * University Acknowledgment- Portions of this document are derived from * software developed by the University of California, Berkeley, and its * contributors. */ /* * Find and display reference manual pages. This version includes makewhatis * functionality as well. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "man.h" /* Mapping of old directories to new directories */ static const struct map_entry { char *old_name; char *new_name; } map[] = { { "1m", "8" }, { "3b", "3ucb" }, { "3e", "3elf" }, { "3g", "3gen" }, { "3k", "3kstat" }, { "3n", "3socket" }, { "3r", "3rt" }, { "3s", "3c" }, { "3t", "3thr" }, { "3x", "3curses" }, { "3xc", "3xcurses" }, { "3xn", "3xnet" }, { "4", "5" }, { "5", "7" }, { "7", "4" }, { "7b", "4b" }, { "7d", "4d" }, { "7fs", "4fs" }, { "7i", "4i" }, { "7ipp", "4ipp" }, { "7m", "4m" }, { "7p", "4p" }, { NULL, NULL } }; struct suffix { char *ds; char *fs; }; /* * Flags that control behavior of build_manpath() * * BMP_ISPATH pathv is a vector constructed from PATH. * Perform appropriate path translations for * manpath. * BMP_APPEND_DEFMANDIR Add DEFMANDIR to the end if it hasn't * already appeared earlier. * BMP_FALLBACK_DEFMANDIR Append /usr/share/man only if no other * manpath (including derived from PATH) * elements are valid. */ #define BMP_ISPATH 1 #define BMP_APPEND_DEFMANDIR 2 #define BMP_FALLBACK_DEFMANDIR 4 /* * When doing equality comparisons of directories, device and inode * comparisons are done. The secnode and dupnode structures are used * to form a list of lists for this processing. */ struct secnode { char *secp; struct secnode *next; }; struct dupnode { dev_t dev; /* from struct stat st_dev */ ino_t ino; /* from struct stat st_ino */ struct secnode *secl; /* sections already considered */ struct dupnode *next; }; /* * Map directories that may appear in PATH to the corresponding * man directory. */ static struct pathmap { char *bindir; char *mandir; dev_t dev; ino_t ino; } bintoman[] = { { "/sbin", "/usr/share/man,8,1m", 0, 0 }, { "/usr/sbin", "/usr/share/man,8,1m", 0, 0 }, { "/usr/ucb", "/usr/share/man,1b", 0, 0 }, { "/usr/bin", "/usr/share/man,1,8,1m,1s,1t,1c", 0, 0 }, { "/usr/xpg4/bin", "/usr/share/man,1", 0, 0 }, { "/usr/xpg6/bin", "/usr/share/man,1", 0, 0 }, { NULL, NULL, 0, 0 } }; struct man_node { char *path; /* mandir path */ char **secv; /* submandir suffixes */ int defsrch; /* hint for man -p */ int frompath; /* hint for man -d */ struct man_node *next; }; static int all = 0; static int apropos = 0; static int debug = 0; static int found = 0; static int list = 0; static int makewhatis = 0; static int printmp = 0; static int psoutput = 0; static int lintout = 0; static int whatis = 0; static int makewhatishere = 0; static char *mansec = NULL; static char *pager = NULL; static char *addlocale(char *); static struct man_node *build_manpath(char **, char *, int); static void do_makewhatis(struct man_node *); static char *check_config(char *); static int cmp(const void *, const void *); static int dupcheck(struct man_node *, struct dupnode **); static int format(char *, char *, char *, char *); static void free_dupnode(struct dupnode *); static void free_manp(struct man_node *manp); static void freev(char **); static void fullpaths(struct man_node **); static void get_all_sect(struct man_node *); static int getdirs(char *, char ***, int); static void getpath(struct man_node *, char **); static void getsect(struct man_node *, char **, char *); static void init_bintoman(void); static void lower(char *); static void mandir(char **, char *, char *, int); static int manual(struct man_node *, char *, char *); static char *map_section(char *, char *); static char *path_to_manpath(char *); static void print_manpath(struct man_node *); static void search_whatis(char *, char *); static int searchdir(char *, char *, char *); static void sortdir(DIR *, char ***); static char **split(char *, char); static void usage_man(void); static void usage_whatapro(void); static void usage_catman(void); static void usage_makewhatis(void); static void whatapro(struct man_node *, char *); static char language[MAXPATHLEN]; /* LC_MESSAGES */ static char localedir[MAXPATHLEN]; /* locale specific path component */ static char *newsection = NULL; static int manwidth = 0; extern const char *__progname; int main(int argc, char **argv) { int c, i; char **pathv; char *manpath = NULL; static struct man_node *mandirs = NULL; int bmp_flags = 0; int ret = 0; char *opts; char *mwstr; int catman = 0; (void) setlocale(LC_ALL, ""); (void) strcpy(language, setlocale(LC_MESSAGES, (char *)NULL)); if (strcmp("C", language) != 0) (void) strlcpy(localedir, language, MAXPATHLEN); #if !defined(TEXT_DOMAIN) #define TEXT_DOMAIN "SYS_TEST" #endif (void) textdomain(TEXT_DOMAIN); if (strcmp(__progname, "apropos") == 0) { apropos++; opts = "M:ds:"; } else if (strcmp(__progname, "whatis") == 0) { apropos++; whatis++; opts = "M:ds:"; } else if (strcmp(__progname, "catman") == 0) { catman++; makewhatis++; opts = "P:M:w"; } else if (strcmp(__progname, "makewhatis") == 0) { makewhatis++; makewhatishere++; manpath = "."; opts = ""; } else { opts = "FM:P:T:adfklprs:tw"; if (argc > 1 && strcmp(argv[1], "-") == 0) { pager = "cat"; optind++; } } opterr = 0; while ((c = getopt(argc, argv, opts)) != -1) { switch (c) { case 'M': /* Respecify path for man pages */ manpath = optarg; break; case 'a': all++; break; case 'd': debug++; break; case 'f': whatis++; /*FALLTHROUGH*/ case 'k': apropos++; break; case 'l': list++; all++; break; case 'p': printmp++; break; case 's': mansec = optarg; break; case 'r': lintout++; break; case 't': psoutput++; break; case 'T': case 'P': case 'F': /* legacy options, compatibility only and ignored */ break; case 'w': makewhatis++; break; case '?': default: if (apropos) usage_whatapro(); else if (catman) usage_catman(); else if (makewhatishere) usage_makewhatis(); else usage_man(); } } argc -= optind; argv += optind; if (argc == 0) { if (apropos) { (void) fprintf(stderr, gettext("%s what?\n"), __progname); exit(1); } else if (!printmp && !makewhatis) { (void) fprintf(stderr, gettext("What manual page do you want?\n")); exit(1); } } init_bintoman(); if (manpath == NULL && (manpath = getenv("MANPATH")) == NULL) { if ((manpath = getenv("PATH")) != NULL) bmp_flags = BMP_ISPATH | BMP_APPEND_DEFMANDIR; else manpath = DEFMANDIR; } pathv = split(manpath, ':'); mandirs = build_manpath(pathv, mansec, bmp_flags); fullpaths(&mandirs); if (makewhatis) { do_makewhatis(mandirs); exit(0); } if (printmp) { print_manpath(mandirs); exit(0); } /* Collect environment information */ if (isatty(STDOUT_FILENO) && (mwstr = getenv("MANWIDTH")) != NULL && *mwstr != '\0') { if (strcasecmp(mwstr, "tty") == 0) { struct winsize ws; if (ioctl(0, TIOCGWINSZ, &ws) != 0) warn("TIOCGWINSZ"); else manwidth = ws.ws_col; } else { manwidth = (int)strtol(mwstr, (char **)NULL, 10); if (manwidth < 0) manwidth = 0; } } if (manwidth != 0) { DPRINTF("-- Using non-standard page width: %d\n", manwidth); } if (pager == NULL) { if ((pager = getenv("PAGER")) == NULL || *pager == '\0') pager = PAGER; } DPRINTF("-- Using pager: %s\n", pager); for (i = 0; i < argc; i++) { char *cmd; static struct man_node *mp; char *pv[2] = {NULL, NULL}; /* * If full path to command specified, customize * the manpath accordingly. */ if ((cmd = strrchr(argv[i], '/')) != NULL) { *cmd = '\0'; if ((pv[0] = strdup(argv[i])) == NULL) err(1, "strdup"); pv[1] = NULL; *cmd = '/'; mp = build_manpath(pv, mansec, BMP_ISPATH | BMP_FALLBACK_DEFMANDIR); } else { mp = mandirs; } if (apropos) { whatapro(mp, argv[i]); } else { /* * If a page is specified with an embedded section, * such as 'printf.3c' First try to find it literally * (which has historically worked due to the * implementation of mandir() and has come to be * relied upon), if that doesn't work split it at the * right most '.' to separate a hypothetical name and * section, and explicitly search under the specified * section, which will trigger the section name * compatibility logic. * * The error that the page they initially requested * does not exist will still be produced at this * point, and indicate (unless clobbered by the pager) * what has been done. */ int lret = 0; lret = manual(mp, argv[i], NULL); if (lret != 0) { char *sec = NULL; if ((sec = strrchr(argv[i], '.')) != NULL) { char *page = NULL; *sec++ = '\0'; if ((page = strdup(argv[i])) == NULL) err(1, "strdup"); mp = build_manpath(pathv, sec, 0); lret = manual(mp, page, sec); free(page); } } ret += lret; } if (mp != NULL && mp != mandirs) { free(pv[0]); free_manp(mp); } } freev(pathv); return (ret == 0 ? 0 : 1); } /* * This routine builds the manpage structure from MANPATH or PATH, * depending on flags. See BMP_* definitions above for valid * flags. */ static struct man_node * build_manpath(char **pathv, char *sec, int flags) { struct man_node *manpage = NULL; struct man_node *currp = NULL; struct man_node *lastp = NULL; char **p; char **q; char *mand = NULL; char *mandir = DEFMANDIR; int s; struct dupnode *didup = NULL; struct stat sb; s = sizeof (struct man_node); for (p = pathv; *p != NULL; ) { if (flags & BMP_ISPATH) { if ((mand = path_to_manpath(*p)) == NULL) goto next; free(*p); *p = mand; } q = split(*p, ','); if (stat(q[0], &sb) != 0 || (sb.st_mode & S_IFDIR) == 0) { freev(q); goto next; } if (access(q[0], R_OK | X_OK) == 0) { /* * Some element exists. Do not append DEFMANDIR as a * fallback. */ flags &= ~BMP_FALLBACK_DEFMANDIR; if ((currp = (struct man_node *)calloc(1, s)) == NULL) err(1, "calloc"); currp->frompath = (flags & BMP_ISPATH); if (manpage == NULL) lastp = manpage = currp; getpath(currp, p); getsect(currp, p, sec); /* * If there are no new elements in this path, * do not add it to the manpage list. */ if (dupcheck(currp, &didup) != 0) { freev(currp->secv); free(currp); } else { currp->next = NULL; if (currp != manpage) lastp->next = currp; lastp = currp; } } freev(q); next: /* * Special handling of appending DEFMANDIR. After all pathv * elements have been processed, append DEFMANDIR if needed. */ if (p == &mandir) break; p++; if (*p != NULL) continue; if (flags & (BMP_APPEND_DEFMANDIR | BMP_FALLBACK_DEFMANDIR)) { p = &mandir; flags &= ~BMP_ISPATH; } } free_dupnode(didup); return (manpage); } /* * Store the mandir path into the manp structure. */ static void getpath(struct man_node *manp, char **pv) { char *s = *pv; int i = 0; while (*s != '\0' && *s != ',') i++, s++; if ((manp->path = (char *)malloc(i + 1)) == NULL) err(1, "malloc"); (void) strlcpy(manp->path, *pv, i + 1); } /* * Store the mandir's corresponding sections (submandir * directories) into the manp structure. */ static void getsect(struct man_node *manp, char **pv, char *explicit_sec) { char *sections; char **sectp; /* Just store all sections when doing makewhatis or apropos/whatis */ if (makewhatis || apropos) { manp->defsrch = 1; DPRINTF("-- Adding %s\n", manp->path); manp->secv = NULL; get_all_sect(manp); } else if (explicit_sec != NULL) { DPRINTF("-- Adding %s: sections=%s\n", manp->path, explicit_sec); manp->secv = split(explicit_sec, ','); for (sectp = manp->secv; *sectp; sectp++) lower(*sectp); } else if ((sections = strchr(*pv, ',')) != NULL) { sections++; DPRINTF("-- Adding %s: sections=%s\n", manp->path, sections); manp->secv = split(sections, ','); for (sectp = manp->secv; *sectp; sectp++) lower(*sectp); if (*manp->secv == NULL) get_all_sect(manp); } else if ((sections = check_config(*pv)) != NULL) { manp->defsrch = 1; DPRINTF("-- Adding %s: sections=%s (from %s)\n", manp->path, sections, CONFIG); manp->secv = split(sections, ','); for (sectp = manp->secv; *sectp; sectp++) lower(*sectp); if (*manp->secv == NULL) get_all_sect(manp); } else { manp->defsrch = 1; DPRINTF("-- Adding %s: default search order\n", manp->path); manp->secv = NULL; get_all_sect(manp); } } /* * Get suffices of all sub-mandir directories in a mandir. */ static void get_all_sect(struct man_node *manp) { DIR *dp; char **dirv; char **dv; char **p; char *prev = NULL; char *tmp = NULL; int maxentries = MAXTOKENS; int entries = 0; if ((dp = opendir(manp->path)) == 0) return; sortdir(dp, &dirv); (void) closedir(dp); if (manp->secv == NULL) { if ((manp->secv = malloc(maxentries * sizeof (char *))) == NULL) err(1, "malloc"); } for (dv = dirv, p = manp->secv; *dv; dv++) { if (strcmp(*dv, CONFIG) == 0) { free(*dv); continue; } free(tmp); if ((tmp = strdup(*dv + 3)) == NULL) err(1, "strdup"); if (prev != NULL && strcmp(prev, tmp) == 0) { free(*dv); continue; } free(prev); if ((prev = strdup(*dv + 3)) == NULL) err(1, "strdup"); if ((*p = strdup(*dv + 3)) == NULL) err(1, "strdup"); p++; entries++; if (entries == maxentries) { maxentries += MAXTOKENS; if ((manp->secv = realloc(manp->secv, sizeof (char *) * maxentries)) == NULL) err(1, "realloc"); p = manp->secv + entries; } free(*dv); } free(tmp); free(prev); *p = NULL; free(dirv); } /* * Build whatis databases. */ static void do_makewhatis(struct man_node *manp) { struct man_node *p; char *ldir; for (p = manp; p != NULL; p = p->next) { ldir = addlocale(p->path); if (*localedir != '\0' && getdirs(ldir, NULL, 0) > 0) mwpath(ldir); free(ldir); mwpath(p->path); } } /* * Count mandirs under the given manpath */ static int getdirs(char *path, char ***dirv, int flag) { DIR *dp; struct dirent *d; int n = 0; int maxentries = MAXDIRS; char **dv = NULL; if ((dp = opendir(path)) == NULL) return (0); if (flag) { if ((*dirv = malloc(sizeof (char *) * maxentries)) == NULL) err(1, "malloc"); dv = *dirv; } while ((d = readdir(dp))) { if (strncmp(d->d_name, "man", 3) != 0) continue; n++; if (flag) { if ((*dv = strdup(d->d_name + 3)) == NULL) err(1, "strdup"); dv++; if ((dv - *dirv) == maxentries) { int entries = maxentries; maxentries += MAXTOKENS; if ((*dirv = realloc(*dirv, sizeof (char *) * maxentries)) == NULL) err(1, "realloc"); dv = *dirv + entries; } } } (void) closedir(dp); return (n); } /* * Find matching whatis or apropos entries. */ static void whatapro(struct man_node *manp, char *word) { char whatpath[MAXPATHLEN]; struct man_node *b; char *ldir; for (b = manp; b != NULL; b = b->next) { if (*localedir != '\0') { ldir = addlocale(b->path); if (getdirs(ldir, NULL, 0) != 0) { (void) snprintf(whatpath, sizeof (whatpath), "%s/%s", ldir, WHATIS); search_whatis(whatpath, word); } free(ldir); } (void) snprintf(whatpath, sizeof (whatpath), "%s/%s", b->path, WHATIS); search_whatis(whatpath, word); } } static void search_whatis(char *whatpath, char *word) { FILE *fp; char *line = NULL; size_t linecap = 0; char *pkwd; regex_t preg; char **ss = NULL; char s[MAXNAMELEN]; int i; if ((fp = fopen(whatpath, "r")) == NULL) { perror(whatpath); return; } DPRINTF("-- Found %s: %s\n", WHATIS, whatpath); /* Build keyword regex */ if (asprintf(&pkwd, "%s%s%s", (whatis) ? "\\<" : "", word, (whatis) ? "\\>" : "") == -1) err(1, "asprintf"); if (regcomp(&preg, pkwd, REG_BASIC | REG_ICASE | REG_NOSUB) != 0) err(1, "regcomp"); if (mansec != NULL) ss = split(mansec, ','); while (getline(&line, &linecap, fp) > 0) { if (regexec(&preg, line, 0, NULL, 0) == 0) { if (mansec != NULL) { /* Section-restricted search */ for (i = 0; ss[i] != NULL; i++) { (void) snprintf(s, sizeof (s), "(%s)", ss[i]); if (strstr(line, s) != NULL) { (void) printf("%s", line); break; } } } else { (void) printf("%s", line); } } } if (ss != NULL) freev(ss); free(pkwd); (void) fclose(fp); } /* * Split a string by specified separator. */ static char ** split(char *s1, char sep) { char **tokv, **vp; char *mp = s1, *tp; int maxentries = MAXTOKENS; int entries = 0; if ((tokv = vp = malloc(maxentries * sizeof (char *))) == NULL) err(1, "malloc"); for (; mp && *mp; mp = tp) { tp = strchr(mp, sep); if (mp == tp) { tp++; continue; } if (tp) { size_t len; len = tp - mp; if ((*vp = (char *)malloc(sizeof (char) * len + 1)) == NULL) err(1, "malloc"); (void) strncpy(*vp, mp, len); *(*vp + len) = '\0'; tp++; vp++; } else { if ((*vp = strdup(mp)) == NULL) err(1, "strdup"); vp++; } entries++; if (entries == maxentries) { maxentries += MAXTOKENS; if ((tokv = realloc(tokv, maxentries * sizeof (char *))) == NULL) err(1, "realloc"); vp = tokv + entries; } } *vp = 0; return (tokv); } /* * Free a vector allocated by split() */ static void freev(char **v) { int i; if (v != NULL) { for (i = 0; v[i] != NULL; i++) { free(v[i]); } free(v); } } /* * Convert paths to full paths if necessary */ static void fullpaths(struct man_node **manp_head) { char *cwd = NULL; char *p; int cwd_gotten = 0; struct man_node *manp = *manp_head; struct man_node *b; struct man_node *prev = NULL; for (b = manp; b != NULL; b = b->next) { if (*(b->path) == '/') { prev = b; continue; } if (!cwd_gotten) { cwd = getcwd(NULL, MAXPATHLEN); cwd_gotten = 1; } if (cwd) { /* Relative manpath with cwd: make absolute */ if (asprintf(&p, "%s/%s", cwd, b->path) == -1) err(1, "asprintf"); free(b->path); b->path = p; } else { /* Relative manpath but no cwd: omit path entry */ if (prev) prev->next = b->next; else *manp_head = b->next; free_manp(b); } } free(cwd); } /* * Free a man_node structure and its contents */ static void free_manp(struct man_node *manp) { char **p; free(manp->path); p = manp->secv; while ((p != NULL) && (*p != NULL)) { free(*p); p++; } free(manp->secv); free(manp); } /* * Map (in place) to lower case. */ static void lower(char *s) { if (s == 0) return; while (*s) { if (isupper(*s)) *s = tolower(*s); s++; } } /* * Compare function for qsort(). * Sort first by section, then by prefix. */ static int cmp(const void *arg1, const void *arg2) { int n; char **p1 = (char **)arg1; char **p2 = (char **)arg2; /* By section */ if ((n = strcmp(*p1 + 3, *p2 + 3)) != 0) return (n); /* By prefix reversed */ return (strncmp(*p2, *p1, 3)); } /* * Find a manpage. */ static int manual(struct man_node *manp, char *name, char *sec) { struct man_node *p; struct man_node *local; int ndirs = 0; char *ldir; char *ldirs[2]; char *fullname = name; char *slash; if ((slash = strrchr(name, '/')) != NULL) name = slash + 1; /* For each path in MANPATH */ found = 0; for (p = manp; p != NULL; p = p->next) { DPRINTF("-- Searching mandir: %s\n", p->path); if (*localedir != '\0') { ldir = addlocale(p->path); ndirs = getdirs(ldir, NULL, 0); if (ndirs != 0) { ldirs[0] = ldir; ldirs[1] = NULL; local = build_manpath(ldirs, mansec, 0); DPRINTF("-- Locale specific subdir: %s\n", ldir); mandir(local->secv, ldir, name, 1); free_manp(local); } free(ldir); } /* * Locale mandir not valid, man page in locale * mandir not found, or -a option present */ if (ndirs == 0 || !found || all) mandir(p->secv, p->path, name, 0); if (found && !all) break; } if (!found) { if (sec != NULL) { (void) fprintf(stderr, gettext( "No manual entry for %s in section(s) %s\n"), fullname, sec); } else { (void) fprintf(stderr, gettext("No manual entry for %s\n"), fullname); } } return (!found); } /* * For a specified manual directory, read, store and sort section subdirs. * For each section specified, find and search matching subdirs. */ static void mandir(char **secv, char *path, char *name, int lspec) { DIR *dp; char **dirv; char **dv, **pdv; int len, dslen; if ((dp = opendir(path)) == NULL) return; if (lspec) DPRINTF("-- Searching mandir: %s\n", path); sortdir(dp, &dirv); /* Search in the order specified by MANSECTS */ for (; *secv; secv++) { len = strlen(*secv); for (dv = dirv; *dv; dv++) { dslen = strlen(*dv + 3); if (dslen > len) len = dslen; if (**secv == '\\') { if (strcmp(*secv + 1, *dv + 3) != 0) continue; } else if (strncasecmp(*secv, *dv + 3, len) != 0) { if (!all && (newsection = map_section(*secv, path)) == NULL) { continue; } if (newsection == NULL) newsection = ""; if (strncmp(newsection, *dv + 3, len) != 0) { continue; } } if (searchdir(path, *dv, name) == 0) continue; if (!all) { pdv = dirv; while (*pdv) { free(*pdv); pdv++; } (void) closedir(dp); free(dirv); return; } if (all && **dv == 'm' && *(dv + 1) && strcmp(*(dv + 1) + 3, *dv + 3) == 0) dv++; } } pdv = dirv; while (*pdv != NULL) { free(*pdv); pdv++; } free(dirv); (void) closedir(dp); } /* * Sort directories. */ static void sortdir(DIR *dp, char ***dirv) { struct dirent *d; char **dv; int maxentries = MAXDIRS; int entries = 0; if ((dv = *dirv = malloc(sizeof (char *) * maxentries)) == NULL) err(1, "malloc"); dv = *dirv; while ((d = readdir(dp))) { if (strcmp(d->d_name, ".") == 0 || strcmp(d->d_name, "..") == 0) continue; if (strncmp(d->d_name, "man", 3) == 0 || strncmp(d->d_name, "cat", 3) == 0) { if ((*dv = strdup(d->d_name)) == NULL) err(1, "strdup"); dv++; entries++; if (entries == maxentries) { maxentries += MAXDIRS; if ((*dirv = realloc(*dirv, sizeof (char *) * maxentries)) == NULL) err(1, "realloc"); dv = *dirv + entries; } } } *dv = 0; qsort((void *)*dirv, dv - *dirv, sizeof (char *), cmp); } /* * Search a section subdir for a given manpage. */ static int searchdir(char *path, char *dir, char *name) { DIR *sdp; struct dirent *sd; char sectpath[MAXPATHLEN]; char file[MAXNAMLEN]; char dname[MAXPATHLEN]; char *last; int nlen; (void) snprintf(sectpath, sizeof (sectpath), "%s/%s", path, dir); (void) snprintf(file, sizeof (file), "%s.", name); if ((sdp = opendir(sectpath)) == NULL) return (0); while ((sd = readdir(sdp))) { char *pname, *upper = NULL; if ((pname = strdup(sd->d_name)) == NULL) err(1, "strdup"); if ((last = strrchr(pname, '.')) != NULL && (strcmp(last, ".gz") == 0 || strcmp(last, ".bz2") == 0)) *last = '\0'; last = strrchr(pname, '.'); nlen = last - pname; (void) snprintf(dname, sizeof (dname), "%.*s.", nlen, pname); /* * Check for a case where name has something like foo.3C because * the user reasonably thought that the section name was * capitalized in the file. This relies on the fact that all of * our section names are currently 7-bit ASCII. */ if (last != NULL) { char *c; if ((upper = strdup(pname)) == NULL) { err(1, "strdup"); } c = strrchr(upper, '.'); c++; while (*c != '\0') { *c = toupper(*c); c++; } } if (strcmp(dname, file) == 0 || strcmp(pname, name) == 0 || (upper != NULL && strcmp(upper, name) == 0)) { (void) format(path, dir, name, sd->d_name); (void) closedir(sdp); free(pname); free(upper); return (1); } free(pname); free(upper); } (void) closedir(sdp); return (0); } /* * Check the hash table of old directory names to see if there is a * new directory name. */ static char * map_section(char *section, char *path) { int i; char fullpath[MAXPATHLEN]; if (list) /* -l option fall through */ return (NULL); for (i = 0; map[i].new_name != NULL; i++) { if (strcmp(section, map[i].old_name) == 0) { (void) snprintf(fullpath, sizeof (fullpath), "%s/man%s", path, map[i].new_name); if (!access(fullpath, R_OK | X_OK)) { return (map[i].new_name); } else { return (NULL); } } } return (NULL); } /* * Format the manpage. */ static int format(char *path, char *dir, char *name, char *pg) { char manpname[MAXPATHLEN], catpname[MAXPATHLEN]; char cmdbuf[BUFSIZ], tmpbuf[BUFSIZ]; char *cattool; struct stat sbman, sbcat; found++; if (list) { (void) printf(gettext("%s(%s)\t-M %s\n"), name, dir + 3, path); return (-1); } (void) snprintf(manpname, sizeof (manpname), "%s/man%s/%s", path, dir + 3, pg); (void) snprintf(catpname, sizeof (catpname), "%s/cat%s/%s", path, dir + 3, pg); /* Can't do PS output if manpage doesn't exist */ if (stat(manpname, &sbman) != 0 && (psoutput|lintout)) return (-1); /* * If both manpage and catpage do not exist, manpname is * broken symlink, most likely. */ if (stat(catpname, &sbcat) != 0 && stat(manpname, &sbman) != 0) err(1, "%s", manpname); /* Setup cattool */ if (fnmatch("*.gz", manpname, 0) == 0) cattool = "gzcat"; else if (fnmatch("*.bz2", manpname, 0) == 0) cattool = "bzcat"; else cattool = "cat"; if (psoutput) { (void) snprintf(cmdbuf, BUFSIZ, "cd %s; %s %s | mandoc -Tps | lp -Tpostscript", path, cattool, manpname); DPRINTF("-- Using manpage: %s\n", manpname); goto cmd; } else if (lintout) { (void) snprintf(cmdbuf, BUFSIZ, "cd %s; %s %s | mandoc -Tlint", path, cattool, manpname); DPRINTF("-- Linting manpage: %s\n", manpname); goto cmd; } /* * Output catpage if: * - manpage doesn't exist * - output width is standard and catpage is recent enough */ if (stat(manpname, &sbman) != 0 || (manwidth == 0 && stat(catpname, &sbcat) == 0 && sbcat.st_mtime >= sbman.st_mtime)) { DPRINTF("-- Using catpage: %s\n", catpname); (void) snprintf(cmdbuf, BUFSIZ, "%s %s", pager, catpname); goto cmd; } DPRINTF("-- Using manpage: %s\n", manpname); if (manwidth > 0) (void) snprintf(tmpbuf, BUFSIZ, "-Owidth=%d ", manwidth); (void) snprintf(cmdbuf, BUFSIZ, "cd %s; %s %s | mandoc %s| %s", path, cattool, manpname, (manwidth > 0) ? tmpbuf : "", pager); cmd: DPRINTF("-- Command: %s\n", cmdbuf); if (!debug) return (system(cmdbuf) == 0); else return (0); } /* * Add to the path. */ static char * addlocale(char *path) { char *tmp; if (asprintf(&tmp, "%s/%s", path, localedir) == -1) err(1, "asprintf"); return (tmp); } /* * Get the order of sections from man.cf. */ static char * check_config(char *path) { FILE *fp; char *rc = NULL; char *sect = NULL; char fname[MAXPATHLEN]; char *line = NULL; char *nl; size_t linecap = 0; (void) snprintf(fname, MAXPATHLEN, "%s/%s", path, CONFIG); if ((fp = fopen(fname, "r")) == NULL) return (NULL); while (getline(&line, &linecap, fp) > 0) { if ((rc = strstr(line, "MANSECTS=")) != NULL) break; } (void) fclose(fp); if (rc != NULL) { if ((nl = strchr(rc, '\n')) != NULL) *nl = '\0'; sect = strchr(rc, '=') + 1; } return (sect); } /* * Initialize the bintoman array with appropriate device and inode info. */ static void init_bintoman(void) { int i; struct stat sb; for (i = 0; bintoman[i].bindir != NULL; i++) { if (stat(bintoman[i].bindir, &sb) == 0) { bintoman[i].dev = sb.st_dev; bintoman[i].ino = sb.st_ino; } else { bintoman[i].dev = NODEV; } } } /* * If a duplicate is found, return 1. * If a duplicate is not found, add it to the dupnode list and return 0. */ static int dupcheck(struct man_node *mnp, struct dupnode **dnp) { struct dupnode *curdnp; struct secnode *cursnp; struct stat sb; int i; int rv = 1; int dupfound; /* If the path doesn't exist, treat it as a duplicate */ if (stat(mnp->path, &sb) != 0) return (1); /* If no sections were found in the man dir, treat it as duplicate */ if (mnp->secv == NULL) return (1); /* * Find the dupnode structure for the previous time this directory * was looked at. Device and inode numbers are compared so that * directories that are reached via different paths (e.g. /usr/man and * /usr/share/man) are treated as equivalent. */ for (curdnp = *dnp; curdnp != NULL; curdnp = curdnp->next) { if (curdnp->dev == sb.st_dev && curdnp->ino == sb.st_ino) break; } /* * First time this directory has been seen. Add a new node to the * head of the list. Since all entries are guaranteed to be unique * copy all sections to new node. */ if (curdnp == NULL) { if ((curdnp = calloc(1, sizeof (struct dupnode))) == NULL) err(1, "calloc"); for (i = 0; mnp->secv[i] != NULL; i++) { if ((cursnp = calloc(1, sizeof (struct secnode))) == NULL) err(1, "calloc"); cursnp->next = curdnp->secl; curdnp->secl = cursnp; if ((cursnp->secp = strdup(mnp->secv[i])) == NULL) err(1, "strdup"); } curdnp->dev = sb.st_dev; curdnp->ino = sb.st_ino; curdnp->next = *dnp; *dnp = curdnp; return (0); } /* * Traverse the section vector in the man_node and the section list * in dupnode cache to eliminate all duplicates from man_node. */ for (i = 0; mnp->secv[i] != NULL; i++) { dupfound = 0; for (cursnp = curdnp->secl; cursnp != NULL; cursnp = cursnp->next) { if (strcmp(mnp->secv[i], cursnp->secp) == 0) { dupfound = 1; break; } } if (dupfound) { mnp->secv[i][0] = '\0'; continue; } /* * Update curdnp and set return value to indicate that this * was not all duplicates. */ if ((cursnp = calloc(1, sizeof (struct secnode))) == NULL) err(1, "calloc"); cursnp->next = curdnp->secl; curdnp->secl = cursnp; if ((cursnp->secp = strdup(mnp->secv[i])) == NULL) err(1, "strdup"); rv = 0; } return (rv); } /* * Given a bindir, return corresponding mandir. */ static char * path_to_manpath(char *bindir) { char *mand, *p; int i; struct stat sb; /* First look for known translations for specific bin paths */ if (stat(bindir, &sb) != 0) { return (NULL); } for (i = 0; bintoman[i].bindir != NULL; i++) { if (sb.st_dev == bintoman[i].dev && sb.st_ino == bintoman[i].ino) { if ((mand = strdup(bintoman[i].mandir)) == NULL) err(1, "strdup"); if ((p = strchr(mand, ',')) != NULL) *p = '\0'; if (stat(mand, &sb) != 0) { free(mand); return (NULL); } if (p != NULL) *p = ','; return (mand); } } /* * No specific translation found. Try `dirname $bindir`/share/man * and `dirname $bindir`/man */ if ((mand = malloc(MAXPATHLEN)) == NULL) err(1, "malloc"); if (strlcpy(mand, bindir, MAXPATHLEN) >= MAXPATHLEN) { free(mand); return (NULL); } /* * Advance to end of buffer, strip trailing /'s then remove last * directory component. */ for (p = mand; *p != '\0'; p++) ; for (; p > mand && *p == '/'; p--) ; for (; p > mand && *p != '/'; p--) ; if (p == mand && *p == '.') { if (realpath("..", mand) == NULL) { free(mand); return (NULL); } for (; *p != '\0'; p++) ; } else { *p = '\0'; } if (strlcat(mand, "/share/man", MAXPATHLEN) >= MAXPATHLEN) { free(mand); return (NULL); } if ((stat(mand, &sb) == 0) && S_ISDIR(sb.st_mode)) { return (mand); } /* * Strip the /share/man off and try /man */ *p = '\0'; if (strlcat(mand, "/man", MAXPATHLEN) >= MAXPATHLEN) { free(mand); return (NULL); } if ((stat(mand, &sb) == 0) && S_ISDIR(sb.st_mode)) { return (mand); } /* * No man or share/man directory found */ free(mand); return (NULL); } /* * Free a linked list of dupnode structs. */ void free_dupnode(struct dupnode *dnp) { struct dupnode *dnp2; struct secnode *snp; while (dnp != NULL) { dnp2 = dnp; dnp = dnp->next; while (dnp2->secl != NULL) { snp = dnp2->secl; dnp2->secl = dnp2->secl->next; free(snp->secp); free(snp); } free(dnp2); } } /* * Print manp linked list to stdout. */ void print_manpath(struct man_node *manp) { char colon[2] = { '\0', '\0' }; char **secp; for (; manp != NULL; manp = manp->next) { (void) printf("%s%s", colon, manp->path); colon[0] = ':'; /* * If man.cf or a directory scan was used to create section * list, do not print section list again. If the output of * man -p is used to set MANPATH, subsequent runs of man * will re-read man.cf and/or scan man directories as * required. */ if (manp->defsrch != 0) continue; for (secp = manp->secv; *secp != NULL; secp++) { /* * Section deduplication may have eliminated some * sections from the vector. Avoid displaying this * detail which would appear as ",," in output */ if ((*secp)[0] != '\0') (void) printf(",%s", *secp); } } (void) printf("\n"); } static void usage_man(void) { (void) fprintf(stderr, gettext( "usage: man [-alptw] [-M path] [-s section] name ...\n" " man [-M path] [-s section] -k keyword ...\n" " man [-M path] [-s section] -f keyword ...\n")); exit(1); } static void usage_whatapro(void) { (void) fprintf(stderr, gettext( "usage: %s [-M path] [-s section] keyword ...\n"), whatis ? "whatis" : "apropos"); exit(1); } static void usage_catman(void) { (void) fprintf(stderr, gettext( "usage: catman [-M path] [-w]\n")); exit(1); } static void usage_makewhatis(void) { (void) fprintf(stderr, gettext("usage: makewhatis\n")); exit(1); } /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2014 Garrett D'Amore * Copyright 2015 Nexenta Systems, Inc. All rights reserved. */ /* * Common definitions */ #ifndef _MAN_H_ #define _MAN_H_ #define CONFIG "man.cf" #define DEFMANDIR "/usr/share/man" #define INDENT 24 #define PAGER "less -ins" #define WHATIS "whatis" #define LINE_ALLOC 4096 #define MAXDIRS 128 #define MAXTOKENS 64 #define DPRINTF if (debug) \ (void) printf void mwpath(char *path); #endif /* _MAN_H_ */ /* * Copyright (c) 1994 Christos Zoulas * 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. * 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. * 4. 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. */ /* * Copyright 2012 Nexenta Systems, Inc. All rights reserved. */ #include #include #include #include #include "stringlist.h" #define _SL_CHUNKSIZE 20 stringlist * sl_init(void) { stringlist *sl; if ((sl = malloc(sizeof (stringlist))) == NULL) err(1, "malloc"); sl->sl_cur = 0; sl->sl_max = _SL_CHUNKSIZE; sl->sl_str = malloc(sl->sl_max * sizeof (char *)); if (sl->sl_str == NULL) err(1, "malloc"); return (sl); } int sl_add(stringlist *sl, char *name) { if (sl->sl_cur == sl->sl_max - 1) { sl->sl_max += _SL_CHUNKSIZE; sl->sl_str = realloc(sl->sl_str, sl->sl_max * sizeof (char *)); if (sl->sl_str == NULL) return (-1); } sl->sl_str[sl->sl_cur++] = name; return (0); } void sl_free(stringlist *sl, int all) { size_t i; if (sl == NULL) return; if (sl->sl_str) { if (all) for (i = 0; i < sl->sl_cur; i++) free(sl->sl_str[i]); free(sl->sl_str); } free(sl); } char * sl_find(stringlist *sl, char *name) { size_t i; for (i = 0; i < sl->sl_cur; i++) if (strcmp(sl->sl_str[i], name) == 0) return (sl->sl_str[i]); return (NULL); } /* * Copyright (c) 1994 Christos Zoulas * 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. * 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. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by Christos Zoulas. * 4. 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. */ /* * Copyright 2012 Nexenta Systems, Inc. All rights reserved. */ #ifndef _STRINGLIST_H_ #define _STRINGLIST_H_ #include typedef struct _stringlist { char **sl_str; size_t sl_max; size_t sl_cur; } stringlist; stringlist *sl_init(void); int sl_add(stringlist *, char *); void sl_free(stringlist *, int); char *sl_find(stringlist *, char *); #endif /* _STRINGLIST_H_ */ #!/bin/ksh # # This file and its contents are supplied under the terms of the # Common Development and Distribution License ("CDDL"), version 1.0. # You may only use this file in accordance with the terms of version # 1.0 of the CDDL. # # A full copy of the text of the CDDL should have accompanied this # source. A copy of the CDDL is also available via the Internet at # http://www.illumos.org/license/CDDL. # # Copyright 2019 OmniOS Community Edition (OmniOSce) Association. # [ -f /lib/svc/share/smf_include.sh ] || exit 1 . /lib/svc/share/smf_include.sh # Associative array to hold unique components for manpath typeset -A manpath default_system_path= if [ -f /etc/default/login ]; then default_system_path="`grep '^PATH=' /etc/default/login | sed -n ' s/PATH=// p q '`" fi oIFS="$IFS"; IFS=":" # The config/manpath property from the service will have been passed as # arguments to this method script. for p in $@; do manpath["$p"]=1 done # Add any additional man directories from the default system path for p in $default_system_path; do dir="`dirname "$p"`" for suffix in man share/man; do [ -d "$dir/$suffix" ] && manpath["$dir/$suffix"]=1 done done IFS="$oIFS" MANPATH= for p in "${!manpath[@]}"; do MANPATH+="${MANPATH:+:}$p" done echo "Rebuilding man page index using $MANPATH" export MANPATH /usr/bin/man -w exit 0