1 /* $NetBSD: apropos.c,v 1.17 2015/12/20 19:45:29 christos Exp $ */ 2 /*- 3 * Copyright (c) 2011 Abhinav Upadhyay <er.abhinav.upadhyay@gmail.com> 4 * All rights reserved. 5 * 6 * This code was developed as part of Google's Summer of Code 2011 program. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 2. Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in 16 * the documentation and/or other materials provided with the 17 * distribution. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 22 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 23 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 24 * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, 25 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 26 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 27 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 29 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 * SUCH DAMAGE. 31 */ 32 33 #include <sys/cdefs.h> 34 __RCSID("$NetBSD: apropos.c,v 1.17 2015/12/20 19:45:29 christos Exp $"); 35 36 #include <err.h> 37 #include <search.h> 38 #include <stdio.h> 39 #include <stdlib.h> 40 #include <string.h> 41 #include <unistd.h> 42 #include <util.h> 43 44 #include "apropos-utils.h" 45 #include "sqlite3.h" 46 47 typedef struct apropos_flags { 48 int sec_nums[SECMAX]; 49 int nresults; 50 int pager; 51 int no_context; 52 query_format format; 53 int legacy; 54 const char *machine; 55 } apropos_flags; 56 57 typedef struct callback_data { 58 int count; 59 FILE *out; 60 apropos_flags *aflags; 61 } callback_data; 62 63 static char *remove_stopwords(const char *); 64 static int query_callback(void *, const char * , const char *, const char *, 65 const char *, size_t); 66 __dead static void usage(void); 67 68 #define _PATH_PAGER "/usr/bin/more -s" 69 70 static void 71 parseargs(int argc, char **argv, struct apropos_flags *aflags) 72 { 73 int ch; 74 while ((ch = getopt(argc, argv, "123456789Cchiln:PprS:s:")) != -1) { 75 switch (ch) { 76 case '1': 77 case '2': 78 case '3': 79 case '4': 80 case '5': 81 case '6': 82 case '7': 83 case '8': 84 case '9': 85 aflags->sec_nums[ch - '1'] = 1; 86 break; 87 case 'C': 88 aflags->no_context = 1; 89 break; 90 case 'c': 91 aflags->no_context = 0; 92 break; 93 case 'h': 94 aflags->format = APROPOS_HTML; 95 break; 96 case 'i': 97 aflags->format = APROPOS_TERM; 98 break; 99 case 'l': 100 aflags->legacy = 1; 101 aflags->no_context = 1; 102 aflags->format = APROPOS_NONE; 103 break; 104 case 'n': 105 aflags->nresults = atoi(optarg); 106 break; 107 case 'p': // user wants a pager 108 aflags->pager = 1; 109 /*FALLTHROUGH*/ 110 case 'P': 111 aflags->format = APROPOS_PAGER; 112 break; 113 case 'r': 114 aflags->format = APROPOS_NONE; 115 break; 116 case 'S': 117 aflags->machine = optarg; 118 break; 119 case 's': 120 ch = atoi(optarg); 121 if (ch < 1 || ch > 9) 122 errx(EXIT_FAILURE, "Invalid section"); 123 aflags->sec_nums[ch - 1] = 1; 124 break; 125 case '?': 126 default: 127 usage(); 128 } 129 } 130 } 131 132 int 133 main(int argc, char *argv[]) 134 { 135 query_args args; 136 char *query = NULL; // the user query 137 char *errmsg = NULL; 138 char *str; 139 int rc = 0; 140 int s; 141 callback_data cbdata; 142 cbdata.out = stdout; // the default output stream 143 cbdata.count = 0; 144 apropos_flags aflags; 145 cbdata.aflags = &aflags; 146 sqlite3 *db; 147 setprogname(argv[0]); 148 if (argc < 2) 149 usage(); 150 151 memset(&aflags, 0, sizeof(aflags)); 152 153 if (!isatty(STDOUT_FILENO)) 154 aflags.format = APROPOS_NONE; 155 else 156 aflags.format = APROPOS_TERM; 157 158 if ((str = getenv("APROPOS")) != NULL) { 159 char **ptr = emalloc((strlen(str) + 2) * sizeof(*ptr)); 160 #define WS "\t\n\r " 161 ptr[0] = __UNCONST(getprogname()); 162 for (s = 1, str = strtok(str, WS); str; 163 str = strtok(NULL, WS), s++) 164 ptr[s] = str; 165 ptr[s] = NULL; 166 parseargs(s, ptr, &aflags); 167 free(ptr); 168 optreset = 1; 169 optind = 1; 170 } 171 172 parseargs(argc, argv, &aflags); 173 174 /* 175 * If the user specifies a section number as an option, the 176 * corresponding index element in sec_nums is set to the string 177 * representing that section number. 178 */ 179 180 argc -= optind; 181 argv += optind; 182 183 if (!argc) 184 usage(); 185 186 str = NULL; 187 while (argc--) 188 concat(&str, *argv++); 189 /* Eliminate any stopwords from the query */ 190 query = remove_stopwords(lower(str)); 191 192 /* 193 * If the query consisted only of stopwords and we removed all of 194 * them, use the original query. 195 */ 196 if (query == NULL) 197 query = str; 198 else 199 free(str); 200 201 if ((db = init_db(MANDB_READONLY, MANCONF)) == NULL) 202 exit(EXIT_FAILURE); 203 204 /* If user wants to page the output, then set some settings */ 205 if (aflags.pager) { 206 const char *pager = getenv("PAGER"); 207 if (pager == NULL) 208 pager = _PATH_PAGER; 209 /* Open a pipe to the pager */ 210 if ((cbdata.out = popen(pager, "w")) == NULL) { 211 close_db(db); 212 err(EXIT_FAILURE, "pipe failed"); 213 } 214 } 215 216 args.search_str = query; 217 args.sec_nums = aflags.sec_nums; 218 args.legacy = aflags.legacy; 219 args.nrec = aflags.nresults ? aflags.nresults : -1; 220 args.offset = 0; 221 args.machine = aflags.machine; 222 args.callback = &query_callback; 223 args.callback_data = &cbdata; 224 args.errmsg = &errmsg; 225 226 if (aflags.format == APROPOS_HTML) { 227 fprintf(cbdata.out, "<html>\n<header>\n<title>apropos results " 228 "for %s</title></header>\n<body>\n<table cellpadding=\"4\"" 229 "style=\"border: 1px solid #000000; border-collapse:" 230 "collapse;\" border=\"1\">\n", query); 231 } 232 rc = run_query(db, aflags.format, &args); 233 if (aflags.format == APROPOS_HTML) 234 fprintf(cbdata.out, "</table>\n</body>\n</html>\n"); 235 236 free(query); 237 close_db(db); 238 if (errmsg) { 239 warnx("%s", errmsg); 240 free(errmsg); 241 exit(EXIT_FAILURE); 242 } 243 244 if (rc < 0) { 245 /* Something wrong with the database. Exit */ 246 exit(EXIT_FAILURE); 247 } 248 249 if (cbdata.count == 0) { 250 warnx("No relevant results obtained.\n" 251 "Please make sure that you spelled all the terms correctly " 252 "or try using better keywords."); 253 } 254 return 0; 255 } 256 257 /* 258 * query_callback -- 259 * Callback function for run_query. 260 * It simply outputs the results from do_query. If the user specified the -p 261 * option, then the output is sent to a pager, otherwise stdout is the default 262 * output stream. 263 */ 264 static int 265 query_callback(void *data, const char *section, const char *name, 266 const char *name_desc, const char *snippet, size_t snippet_length) 267 { 268 callback_data *cbdata = (callback_data *) data; 269 FILE *out = cbdata->out; 270 cbdata->count++; 271 if (cbdata->aflags->format != APROPOS_HTML) { 272 fprintf(out, cbdata->aflags->legacy ? "%s(%s) - %s\n" : 273 "%s (%s)\t%s\n", name, section, name_desc); 274 if (cbdata->aflags->no_context == 0) 275 fprintf(out, "%s\n\n", snippet); 276 } else { 277 fprintf(out, "<tr><td>%s(%s)</td><td>%s</td></tr>\n", name, 278 section, name_desc); 279 if (cbdata->aflags->no_context == 0) 280 fprintf(out, "<tr><td colspan=2>%s</td></tr>\n", snippet); 281 } 282 283 return 0; 284 } 285 286 #include "stopwords.c" 287 288 /* 289 * remove_stopwords-- 290 * Scans the query and removes any stop words from it. 291 * Returns the modified query or NULL, if it contained only stop words. 292 */ 293 294 static char * 295 remove_stopwords(const char *query) 296 { 297 size_t len, idx; 298 char *output, *buf; 299 const char *sep, *next; 300 301 output = buf = emalloc(strlen(query) + 1); 302 303 for (; query[0] != '\0'; query = next) { 304 sep = strchr(query, ' '); 305 if (sep == NULL) { 306 len = strlen(query); 307 next = query + len; 308 } else { 309 len = sep - query; 310 next = sep + 1; 311 } 312 if (len == 0) 313 continue; 314 idx = stopwords_hash(query, len); 315 if (memcmp(stopwords[idx], query, len) == 0 && 316 stopwords[idx][len] == '\0') 317 continue; 318 memcpy(buf, query, len); 319 buf += len; 320 *buf++ = ' '; 321 } 322 323 if (output == buf) { 324 free(output); 325 return NULL; 326 } 327 buf[-1] = '\0'; 328 return output; 329 } 330 331 /* 332 * usage -- 333 * print usage message and die 334 */ 335 static void 336 usage(void) 337 { 338 fprintf(stderr, "Usage: %s [-123456789Ccilpr] [-n results] " 339 "[-S machine] [-s section] query\n", 340 getprogname()); 341 exit(1); 342 } 343