1 1.51 rin /* $NetBSD: apropos-utils.c,v 1.51 2023/08/03 07:49:23 rin Exp $ */ 2 1.1 joerg /*- 3 1.1 joerg * Copyright (c) 2011 Abhinav Upadhyay <er.abhinav.upadhyay (at) gmail.com> 4 1.1 joerg * All rights reserved. 5 1.1 joerg * 6 1.1 joerg * This code was developed as part of Google's Summer of Code 2011 program. 7 1.1 joerg * 8 1.1 joerg * Redistribution and use in source and binary forms, with or without 9 1.1 joerg * modification, are permitted provided that the following conditions 10 1.1 joerg * are met: 11 1.1 joerg * 12 1.1 joerg * 1. Redistributions of source code must retain the above copyright 13 1.1 joerg * notice, this list of conditions and the following disclaimer. 14 1.1 joerg * 2. Redistributions in binary form must reproduce the above copyright 15 1.1 joerg * notice, this list of conditions and the following disclaimer in 16 1.1 joerg * the documentation and/or other materials provided with the 17 1.1 joerg * distribution. 18 1.1 joerg * 19 1.1 joerg * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 1.1 joerg * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 1.1 joerg * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 22 1.1 joerg * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 23 1.1 joerg * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 24 1.1 joerg * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, 25 1.1 joerg * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 26 1.1 joerg * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 27 1.1 joerg * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 1.1 joerg * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 29 1.1 joerg * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 1.1 joerg * SUCH DAMAGE. 31 1.1 joerg */ 32 1.1 joerg 33 1.1 joerg #include <sys/cdefs.h> 34 1.51 rin __RCSID("$NetBSD: apropos-utils.c,v 1.51 2023/08/03 07:49:23 rin Exp $"); 35 1.1 joerg 36 1.7 wiz #include <sys/queue.h> 37 1.1 joerg #include <sys/stat.h> 38 1.1 joerg 39 1.1 joerg #include <assert.h> 40 1.1 joerg #include <ctype.h> 41 1.1 joerg #include <err.h> 42 1.1 joerg #include <math.h> 43 1.1 joerg #include <stdio.h> 44 1.1 joerg #include <stdlib.h> 45 1.1 joerg #include <string.h> 46 1.1 joerg #include <util.h> 47 1.1 joerg #include <zlib.h> 48 1.9 christos #include <term.h> 49 1.30 kamil #include <unistd.h> 50 1.9 christos #undef tab // XXX: manconf.h 51 1.1 joerg 52 1.1 joerg #include "apropos-utils.h" 53 1.38 abhinav #include "custom_apropos_tokenizer.h" 54 1.7 wiz #include "manconf.h" 55 1.38 abhinav #include "fts3_tokenizer.h" 56 1.1 joerg 57 1.1 joerg typedef struct orig_callback_data { 58 1.1 joerg void *data; 59 1.40 abhinav int (*callback) (query_callback_args*); 60 1.1 joerg } orig_callback_data; 61 1.1 joerg 62 1.1 joerg typedef struct inverse_document_frequency { 63 1.1 joerg double value; 64 1.1 joerg int status; 65 1.1 joerg } inverse_document_frequency; 66 1.1 joerg 67 1.1 joerg /* weights for individual columns */ 68 1.1 joerg static const double col_weights[] = { 69 1.1 joerg 2.0, // NAME 70 1.1 joerg 2.00, // Name-description 71 1.1 joerg 0.55, // DESCRIPTION 72 1.1 joerg 0.10, // LIBRARY 73 1.1 joerg 0.001, //RETURN VALUES 74 1.1 joerg 0.20, //ENVIRONMENT 75 1.1 joerg 0.01, //FILES 76 1.1 joerg 0.001, //EXIT STATUS 77 1.1 joerg 2.00, //DIAGNOSTICS 78 1.1 joerg 0.05, //ERRORS 79 1.1 joerg 0.00, //md5_hash 80 1.1 joerg 1.00 //machine 81 1.1 joerg }; 82 1.1 joerg 83 1.39 abhinav #ifndef APROPOS_DEBUG 84 1.38 abhinav static int 85 1.38 abhinav register_tokenizer(sqlite3 *db) 86 1.38 abhinav { 87 1.38 abhinav int rc; 88 1.38 abhinav sqlite3_stmt *stmt; 89 1.38 abhinav const sqlite3_tokenizer_module *p; 90 1.38 abhinav const char *name = "custom_apropos_tokenizer"; 91 1.38 abhinav get_custom_apropos_tokenizer(&p); 92 1.38 abhinav const char *sql = "SELECT fts3_tokenizer(?, ?)"; 93 1.38 abhinav 94 1.38 abhinav sqlite3_db_config(db, SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER, 1, 0); 95 1.38 abhinav rc = sqlite3_prepare_v2(db, sql, -1, &stmt, 0); 96 1.38 abhinav if (rc != SQLITE_OK) 97 1.38 abhinav return rc; 98 1.38 abhinav 99 1.38 abhinav sqlite3_bind_text(stmt, 1, name, -1, SQLITE_STATIC); 100 1.38 abhinav sqlite3_bind_blob(stmt, 2, &p, sizeof(p), SQLITE_STATIC); 101 1.38 abhinav sqlite3_step(stmt); 102 1.38 abhinav 103 1.38 abhinav return sqlite3_finalize(stmt); 104 1.38 abhinav } 105 1.39 abhinav #endif 106 1.38 abhinav 107 1.1 joerg /* 108 1.1 joerg * lower -- 109 1.1 joerg * Converts the string str to lower case 110 1.1 joerg */ 111 1.1 joerg char * 112 1.1 joerg lower(char *str) 113 1.1 joerg { 114 1.1 joerg assert(str); 115 1.1 joerg int i = 0; 116 1.1 joerg char c; 117 1.36 abhinav while ((c = str[i]) != '\0') 118 1.36 abhinav str[i++] = tolower((unsigned char) c); 119 1.1 joerg return str; 120 1.1 joerg } 121 1.1 joerg 122 1.1 joerg /* 123 1.1 joerg * concat-- 124 1.10 christos * Utility function. Concatenates together: dst, a space character and src. 125 1.10 christos * dst + " " + src 126 1.1 joerg */ 127 1.1 joerg void 128 1.1 joerg concat(char **dst, const char *src) 129 1.1 joerg { 130 1.1 joerg concat2(dst, src, strlen(src)); 131 1.1 joerg } 132 1.1 joerg 133 1.1 joerg void 134 1.1 joerg concat2(char **dst, const char *src, size_t srclen) 135 1.1 joerg { 136 1.27 abhinav size_t totallen, dstlen; 137 1.35 abhinav char *mydst = *dst; 138 1.1 joerg assert(src != NULL); 139 1.1 joerg 140 1.24 christos /* 141 1.24 christos * If destination buffer dst is NULL, then simply 142 1.24 christos * strdup the source buffer 143 1.24 christos */ 144 1.35 abhinav if (mydst == NULL) { 145 1.35 abhinav mydst = estrndup(src, srclen); 146 1.35 abhinav *dst = mydst; 147 1.1 joerg return; 148 1.1 joerg } 149 1.1 joerg 150 1.35 abhinav dstlen = strlen(mydst); 151 1.1 joerg /* 152 1.1 joerg * NUL Byte and separator space 153 1.1 joerg */ 154 1.27 abhinav totallen = dstlen + srclen + 2; 155 1.1 joerg 156 1.35 abhinav mydst = erealloc(mydst, totallen); 157 1.1 joerg 158 1.1 joerg /* Append a space at the end of dst */ 159 1.35 abhinav mydst[dstlen++] = ' '; 160 1.1 joerg 161 1.10 christos /* Now, copy src at the end of dst */ 162 1.35 abhinav memcpy(mydst + dstlen, src, srclen); 163 1.35 abhinav mydst[dstlen + srclen] = '\0'; 164 1.35 abhinav *dst = mydst; 165 1.1 joerg } 166 1.1 joerg 167 1.1 joerg void 168 1.1 joerg close_db(sqlite3 *db) 169 1.1 joerg { 170 1.1 joerg sqlite3_close(db); 171 1.1 joerg sqlite3_shutdown(); 172 1.1 joerg } 173 1.1 joerg 174 1.1 joerg /* 175 1.1 joerg * create_db -- 176 1.1 joerg * Creates the database schema. 177 1.1 joerg */ 178 1.1 joerg static int 179 1.1 joerg create_db(sqlite3 *db) 180 1.1 joerg { 181 1.1 joerg const char *sqlstr = NULL; 182 1.1 joerg char *schemasql; 183 1.1 joerg char *errmsg = NULL; 184 1.10 christos 185 1.1 joerg /*------------------------ Create the tables------------------------------*/ 186 1.1 joerg 187 1.1 joerg #if NOTYET 188 1.1 joerg sqlite3_exec(db, "PRAGMA journal_mode = WAL", NULL, NULL, NULL); 189 1.1 joerg #else 190 1.1 joerg sqlite3_exec(db, "PRAGMA journal_mode = DELETE", NULL, NULL, NULL); 191 1.1 joerg #endif 192 1.1 joerg 193 1.1 joerg schemasql = sqlite3_mprintf("PRAGMA user_version = %d", 194 1.1 joerg APROPOS_SCHEMA_VERSION); 195 1.1 joerg sqlite3_exec(db, schemasql, NULL, NULL, &errmsg); 196 1.1 joerg if (errmsg != NULL) 197 1.1 joerg goto out; 198 1.1 joerg sqlite3_free(schemasql); 199 1.1 joerg 200 1.24 christos sqlstr = 201 1.24 christos //mandb 202 1.24 christos "CREATE VIRTUAL TABLE mandb USING fts4(section, name, " 203 1.24 christos "name_desc, desc, lib, return_vals, env, files, " 204 1.24 christos "exit_status, diagnostics, errors, md5_hash UNIQUE, machine, " 205 1.51 rin #ifndef APROPOS_DEBUG 206 1.39 abhinav "compress=zip, uncompress=unzip, tokenize=custom_apropos_tokenizer, " 207 1.39 abhinav #else 208 1.39 abhinav "tokenize=porter, " 209 1.51 rin #endif 210 1.39 abhinav "notindexed=section, notindexed=md5_hash); " 211 1.24 christos //mandb_meta 212 1.24 christos "CREATE TABLE IF NOT EXISTS mandb_meta(device, inode, mtime, " 213 1.24 christos "file UNIQUE, md5_hash UNIQUE, id INTEGER PRIMARY KEY); " 214 1.24 christos //mandb_links 215 1.31 abhinav "CREATE TABLE IF NOT EXISTS mandb_links(link COLLATE NOCASE, target, section, " 216 1.44 abhinav "machine, md5_hash, name_desc); "; 217 1.1 joerg 218 1.1 joerg sqlite3_exec(db, sqlstr, NULL, NULL, &errmsg); 219 1.1 joerg if (errmsg != NULL) 220 1.1 joerg goto out; 221 1.1 joerg 222 1.24 christos sqlstr = 223 1.24 christos "CREATE INDEX IF NOT EXISTS index_mandb_links ON mandb_links " 224 1.24 christos "(link); " 225 1.24 christos "CREATE INDEX IF NOT EXISTS index_mandb_meta_dev ON mandb_meta " 226 1.24 christos "(device, inode); " 227 1.24 christos "CREATE INDEX IF NOT EXISTS index_mandb_links_md5 ON mandb_links " 228 1.24 christos "(md5_hash);"; 229 1.1 joerg sqlite3_exec(db, sqlstr, NULL, NULL, &errmsg); 230 1.1 joerg if (errmsg != NULL) 231 1.1 joerg goto out; 232 1.1 joerg return 0; 233 1.1 joerg 234 1.1 joerg out: 235 1.1 joerg warnx("%s", errmsg); 236 1.1 joerg free(errmsg); 237 1.1 joerg sqlite3_close(db); 238 1.1 joerg sqlite3_shutdown(); 239 1.1 joerg return -1; 240 1.1 joerg } 241 1.1 joerg 242 1.1 joerg /* 243 1.1 joerg * zip -- 244 1.50 gutterid * User defined SQLite function to compress the FTS table 245 1.1 joerg */ 246 1.1 joerg static void 247 1.1 joerg zip(sqlite3_context *pctx, int nval, sqlite3_value **apval) 248 1.10 christos { 249 1.1 joerg int nin; 250 1.1 joerg long int nout; 251 1.1 joerg const unsigned char * inbuf; 252 1.1 joerg unsigned char *outbuf; 253 1.1 joerg 254 1.1 joerg assert(nval == 1); 255 1.1 joerg nin = sqlite3_value_bytes(apval[0]); 256 1.1 joerg inbuf = (const unsigned char *) sqlite3_value_blob(apval[0]); 257 1.1 joerg nout = nin + 13 + (nin + 999) / 1000; 258 1.1 joerg outbuf = emalloc(nout); 259 1.1 joerg compress(outbuf, (unsigned long *) &nout, inbuf, nin); 260 1.1 joerg sqlite3_result_blob(pctx, outbuf, nout, free); 261 1.1 joerg } 262 1.1 joerg 263 1.1 joerg /* 264 1.1 joerg * unzip -- 265 1.50 gutterid * User defined SQLite function to uncompress the FTS table. 266 1.1 joerg */ 267 1.1 joerg static void 268 1.1 joerg unzip(sqlite3_context *pctx, int nval, sqlite3_value **apval) 269 1.1 joerg { 270 1.1 joerg unsigned int rc; 271 1.1 joerg unsigned char *outbuf; 272 1.1 joerg z_stream stream; 273 1.41 christos long total_out; 274 1.1 joerg 275 1.1 joerg assert(nval == 1); 276 1.41 christos memset(&stream, 0, sizeof(stream)); 277 1.1 joerg stream.next_in = __UNCONST(sqlite3_value_blob(apval[0])); 278 1.1 joerg stream.avail_in = sqlite3_value_bytes(apval[0]); 279 1.1 joerg stream.zalloc = NULL; 280 1.1 joerg stream.zfree = NULL; 281 1.1 joerg 282 1.1 joerg if (inflateInit(&stream) != Z_OK) { 283 1.1 joerg return; 284 1.1 joerg } 285 1.1 joerg 286 1.41 christos total_out = stream.avail_out = stream.avail_in * 2 + 100; 287 1.41 christos stream.next_out = outbuf = emalloc(stream.avail_out); 288 1.1 joerg while ((rc = inflate(&stream, Z_SYNC_FLUSH)) != Z_STREAM_END) { 289 1.1 joerg if (rc != Z_OK || 290 1.1 joerg (stream.avail_out != 0 && stream.avail_in == 0)) { 291 1.1 joerg free(outbuf); 292 1.1 joerg return; 293 1.1 joerg } 294 1.41 christos total_out <<= 1; 295 1.41 christos outbuf = erealloc(outbuf, total_out); 296 1.1 joerg stream.next_out = outbuf + stream.total_out; 297 1.41 christos stream.avail_out = total_out - stream.total_out; 298 1.1 joerg } 299 1.1 joerg if (inflateEnd(&stream) != Z_OK) { 300 1.1 joerg free(outbuf); 301 1.1 joerg return; 302 1.1 joerg } 303 1.41 christos if (stream.total_out == 0) { 304 1.41 christos free(outbuf); 305 1.41 christos return; 306 1.41 christos } 307 1.1 joerg outbuf = erealloc(outbuf, stream.total_out); 308 1.24 christos sqlite3_result_text(pctx, (const char *)outbuf, stream.total_out, free); 309 1.1 joerg } 310 1.1 joerg 311 1.7 wiz /* 312 1.7 wiz * get_dbpath -- 313 1.7 wiz * Read the path of the database from man.conf and return. 314 1.7 wiz */ 315 1.7 wiz char * 316 1.7 wiz get_dbpath(const char *manconf) 317 1.7 wiz { 318 1.7 wiz TAG *tp; 319 1.7 wiz char *dbpath; 320 1.7 wiz 321 1.7 wiz config(manconf); 322 1.7 wiz tp = gettag("_mandb", 1); 323 1.7 wiz if (!tp) 324 1.7 wiz return NULL; 325 1.10 christos 326 1.7 wiz if (TAILQ_EMPTY(&tp->entrylist)) 327 1.7 wiz return NULL; 328 1.7 wiz 329 1.7 wiz dbpath = TAILQ_LAST(&tp->entrylist, tqh)->s; 330 1.7 wiz return dbpath; 331 1.7 wiz } 332 1.7 wiz 333 1.1 joerg /* init_db -- 334 1.1 joerg * Prepare the database. Register the compress/uncompress functions and the 335 1.1 joerg * stopword tokenizer. 336 1.10 christos * db_flag specifies the mode in which to open the database. 3 options are 337 1.1 joerg * available: 338 1.1 joerg * 1. DB_READONLY: Open in READONLY mode. An error if db does not exist. 339 1.1 joerg * 2. DB_READWRITE: Open in read-write mode. An error if db does not exist. 340 1.1 joerg * 3. DB_CREATE: Open in read-write mode. It will try to create the db if 341 1.1 joerg * it does not exist already. 342 1.1 joerg * RETURN VALUES: 343 1.24 christos * The function will return NULL in case the db does not exist 344 1.24 christos * and DB_CREATE 345 1.10 christos * was not specified. And in case DB_CREATE was specified and yet NULL is 346 1.1 joerg * returned, then there was some other error. 347 1.1 joerg * In normal cases the function should return a handle to the db. 348 1.1 joerg */ 349 1.1 joerg sqlite3 * 350 1.23 christos init_db(mandb_access_mode db_flag, const char *manconf) 351 1.1 joerg { 352 1.1 joerg sqlite3 *db = NULL; 353 1.1 joerg sqlite3_stmt *stmt; 354 1.1 joerg struct stat sb; 355 1.1 joerg int rc; 356 1.1 joerg int create_db_flag = 0; 357 1.1 joerg 358 1.7 wiz char *dbpath = get_dbpath(manconf); 359 1.7 wiz if (dbpath == NULL) 360 1.7 wiz errx(EXIT_FAILURE, "_mandb entry not found in man.conf"); 361 1.23 christos 362 1.7 wiz if (!(stat(dbpath, &sb) == 0 && S_ISREG(sb.st_mode))) { 363 1.23 christos /* Database does not exist, check if DB_CREATE was specified, 364 1.23 christos * and set flag to create the database schema 365 1.1 joerg */ 366 1.1 joerg if (db_flag != (MANDB_CREATE)) { 367 1.1 joerg warnx("Missing apropos database. " 368 1.1 joerg "Please run makemandb to create it."); 369 1.1 joerg return NULL; 370 1.1 joerg } 371 1.1 joerg create_db_flag = 1; 372 1.23 christos } else { 373 1.23 christos /* 374 1.23 christos * Database exists. Check if we have the permissions 375 1.23 christos * to read/write the files 376 1.23 christos */ 377 1.23 christos int access_mode = R_OK; 378 1.25 christos switch (db_flag) { 379 1.23 christos case MANDB_CREATE: 380 1.23 christos case MANDB_WRITE: 381 1.23 christos access_mode |= W_OK; 382 1.23 christos break; 383 1.23 christos default: 384 1.23 christos break; 385 1.23 christos } 386 1.23 christos if ((access(dbpath, access_mode)) != 0) { 387 1.23 christos warnx("Unable to access the database, please check" 388 1.23 christos " permissions for `%s'", dbpath); 389 1.23 christos return NULL; 390 1.23 christos } 391 1.1 joerg } 392 1.1 joerg 393 1.1 joerg sqlite3_initialize(); 394 1.7 wiz rc = sqlite3_open_v2(dbpath, &db, db_flag, NULL); 395 1.10 christos 396 1.1 joerg if (rc != SQLITE_OK) { 397 1.1 joerg warnx("%s", sqlite3_errmsg(db)); 398 1.23 christos goto error; 399 1.1 joerg } 400 1.1 joerg 401 1.38 abhinav sqlite3_extended_result_codes(db, 1); 402 1.38 abhinav 403 1.39 abhinav #ifndef APROPOS_DEBUG 404 1.38 abhinav rc = register_tokenizer(db); 405 1.38 abhinav if (rc != SQLITE_OK) { 406 1.38 abhinav warnx("Unable to register custom tokenizer: %s", sqlite3_errmsg(db)); 407 1.38 abhinav goto error; 408 1.38 abhinav } 409 1.39 abhinav #endif 410 1.38 abhinav 411 1.1 joerg if (create_db_flag && create_db(db) < 0) { 412 1.1 joerg warnx("%s", "Unable to create database schema"); 413 1.1 joerg goto error; 414 1.1 joerg } 415 1.1 joerg 416 1.1 joerg rc = sqlite3_prepare_v2(db, "PRAGMA user_version", -1, &stmt, NULL); 417 1.1 joerg if (rc != SQLITE_OK) { 418 1.3 apb warnx("Unable to query schema version: %s", 419 1.3 apb sqlite3_errmsg(db)); 420 1.1 joerg goto error; 421 1.1 joerg } 422 1.1 joerg if (sqlite3_step(stmt) != SQLITE_ROW) { 423 1.1 joerg sqlite3_finalize(stmt); 424 1.3 apb warnx("Unable to query schema version: %s", 425 1.3 apb sqlite3_errmsg(db)); 426 1.1 joerg goto error; 427 1.1 joerg } 428 1.1 joerg if (sqlite3_column_int(stmt, 0) != APROPOS_SCHEMA_VERSION) { 429 1.1 joerg sqlite3_finalize(stmt); 430 1.1 joerg warnx("Incorrect schema version found. " 431 1.1 joerg "Please run makemandb -f."); 432 1.1 joerg goto error; 433 1.1 joerg } 434 1.1 joerg sqlite3_finalize(stmt); 435 1.1 joerg 436 1.10 christos 437 1.1 joerg /* Register the zip and unzip functions for FTS compression */ 438 1.24 christos rc = sqlite3_create_function(db, "zip", 1, SQLITE_ANY, NULL, zip, 439 1.24 christos NULL, NULL); 440 1.1 joerg if (rc != SQLITE_OK) { 441 1.3 apb warnx("Unable to register function: compress: %s", 442 1.3 apb sqlite3_errmsg(db)); 443 1.1 joerg goto error; 444 1.1 joerg } 445 1.1 joerg 446 1.10 christos rc = sqlite3_create_function(db, "unzip", 1, SQLITE_ANY, NULL, 447 1.1 joerg unzip, NULL, NULL); 448 1.1 joerg if (rc != SQLITE_OK) { 449 1.3 apb warnx("Unable to register function: uncompress: %s", 450 1.3 apb sqlite3_errmsg(db)); 451 1.1 joerg goto error; 452 1.1 joerg } 453 1.1 joerg return db; 454 1.7 wiz 455 1.1 joerg error: 456 1.23 christos close_db(db); 457 1.1 joerg return NULL; 458 1.1 joerg } 459 1.1 joerg 460 1.1 joerg /* 461 1.1 joerg * rank_func -- 462 1.50 gutterid * SQLite user defined function for ranking the documents. 463 1.1 joerg * For each phrase of the query, it computes the tf and idf and adds them over. 464 1.1 joerg * It computes the final rank, by multiplying tf and idf together. 465 1.10 christos * Weight of term t for document d = (term frequency of t in d * 466 1.10 christos * inverse document frequency of t) 467 1.1 joerg * 468 1.10 christos * Term Frequency of term t in document d = Number of times t occurs in d / 469 1.24 christos * Number of times t appears in all documents 470 1.1 joerg * 471 1.10 christos * Inverse document frequency of t = log(Total number of documents / 472 1.1 joerg * Number of documents in which t occurs) 473 1.1 joerg */ 474 1.1 joerg static void 475 1.1 joerg rank_func(sqlite3_context *pctx, int nval, sqlite3_value **apval) 476 1.1 joerg { 477 1.1 joerg inverse_document_frequency *idf = sqlite3_user_data(pctx); 478 1.1 joerg double tf = 0.0; 479 1.1 joerg const unsigned int *matchinfo; 480 1.1 joerg int ncol; 481 1.1 joerg int nphrase; 482 1.1 joerg int iphrase; 483 1.1 joerg int ndoc; 484 1.1 joerg int doclen = 0; 485 1.1 joerg const double k = 3.75; 486 1.24 christos /* 487 1.24 christos * Check that the number of arguments passed to this 488 1.24 christos * function is correct. 489 1.24 christos */ 490 1.1 joerg assert(nval == 1); 491 1.1 joerg 492 1.1 joerg matchinfo = (const unsigned int *) sqlite3_value_blob(apval[0]); 493 1.1 joerg nphrase = matchinfo[0]; 494 1.1 joerg ncol = matchinfo[1]; 495 1.1 joerg ndoc = matchinfo[2 + 3 * ncol * nphrase + ncol]; 496 1.1 joerg for (iphrase = 0; iphrase < nphrase; iphrase++) { 497 1.1 joerg int icol; 498 1.24 christos const unsigned int *phraseinfo = 499 1.24 christos &matchinfo[2 + ncol + iphrase * ncol * 3]; 500 1.1 joerg for(icol = 1; icol < ncol; icol++) { 501 1.10 christos 502 1.24 christos /* nhitcount: number of times the current phrase occurs 503 1.24 christos * in the current column in the current document. 504 1.24 christos * nglobalhitcount: number of times current phrase 505 1.24 christos * occurs in the current column in all documents. 506 1.24 christos * ndocshitcount: number of documents in which the 507 1.24 christos * current phrase occurs in the current column at 508 1.24 christos * least once. 509 1.1 joerg */ 510 1.1 joerg int nhitcount = phraseinfo[3 * icol]; 511 1.1 joerg int nglobalhitcount = phraseinfo[3 * icol + 1]; 512 1.1 joerg int ndocshitcount = phraseinfo[3 * icol + 2]; 513 1.1 joerg doclen = matchinfo[2 + icol ]; 514 1.1 joerg double weight = col_weights[icol - 1]; 515 1.1 joerg if (idf->status == 0 && ndocshitcount) 516 1.24 christos idf->value += 517 1.24 christos log(((double)ndoc / ndocshitcount))* weight; 518 1.1 joerg 519 1.24 christos /* 520 1.24 christos * Dividing the tf by document length to normalize 521 1.24 christos * the effect of longer documents. 522 1.1 joerg */ 523 1.1 joerg if (nglobalhitcount > 0 && nhitcount) 524 1.24 christos tf += (((double)nhitcount * weight) 525 1.24 christos / (nglobalhitcount * doclen)); 526 1.1 joerg } 527 1.1 joerg } 528 1.1 joerg idf->status = 1; 529 1.10 christos 530 1.24 christos /* 531 1.24 christos * Final score: Dividing by k + tf further normalizes the weight 532 1.24 christos * leading to better results. The value of k is experimental 533 1.1 joerg */ 534 1.24 christos double score = (tf * idf->value) / (k + tf); 535 1.1 joerg sqlite3_result_double(pctx, score); 536 1.1 joerg return; 537 1.1 joerg } 538 1.1 joerg 539 1.1 joerg /* 540 1.26 abhinav * generates sql query for matching the user entered query 541 1.1 joerg */ 542 1.26 abhinav static char * 543 1.26 abhinav generate_search_query(query_args *args, const char *snippet_args[3]) 544 1.1 joerg { 545 1.1 joerg const char *default_snippet_args[3]; 546 1.1 joerg char *section_clause = NULL; 547 1.1 joerg char *limit_clause = NULL; 548 1.1 joerg char *machine_clause = NULL; 549 1.33 abhinav char *query = NULL; 550 1.1 joerg 551 1.33 abhinav if (args->machine) { 552 1.33 abhinav machine_clause = sqlite3_mprintf("AND mandb.machine=%Q", args->machine); 553 1.33 abhinav if (machine_clause == NULL) 554 1.33 abhinav goto RETURN; 555 1.33 abhinav } 556 1.1 joerg 557 1.33 abhinav if (args->nrec >= 0) { 558 1.33 abhinav /* Use the provided number of records and offset */ 559 1.33 abhinav limit_clause = sqlite3_mprintf(" LIMIT %d OFFSET %d", 560 1.33 abhinav args->nrec, args->offset); 561 1.33 abhinav if (limit_clause == NULL) 562 1.33 abhinav goto RETURN; 563 1.33 abhinav } 564 1.10 christos 565 1.1 joerg /* We want to build a query of the form: "select x,y,z from mandb where 566 1.34 abhinav * mandb match :query [AND (section IN ('1', '2')] 567 1.34 abhinav * ORDER BY rank DESC [LIMIT 10 OFFSET 0]" 568 1.24 christos * NOTES: 569 1.34 abhinav * 1. The portion in first pair of square brackets is optional. 570 1.34 abhinav * It will be there only if the user has specified an option 571 1.24 christos * to search in one or more specific sections. 572 1.34 abhinav * 2. The LIMIT portion will be there if the user has specified 573 1.34 abhinav * a limit using the -n option. 574 1.1 joerg */ 575 1.37 abhinav if (args->sections && args->sections[0]) { 576 1.37 abhinav concat(§ion_clause, " AND mandb.section IN ("); 577 1.37 abhinav for (size_t i = 0; args->sections[i]; i++) { 578 1.37 abhinav char *temp; 579 1.37 abhinav char c = args->sections[i + 1]? ',': ')'; 580 1.37 abhinav if ((temp = sqlite3_mprintf("%Q%c", args->sections[i], c)) == NULL) 581 1.37 abhinav goto RETURN; 582 1.37 abhinav concat(§ion_clause, temp); 583 1.43 abhinav sqlite3_free(temp); 584 1.1 joerg } 585 1.1 joerg } 586 1.26 abhinav 587 1.1 joerg if (snippet_args == NULL) { 588 1.1 joerg default_snippet_args[0] = ""; 589 1.1 joerg default_snippet_args[1] = ""; 590 1.1 joerg default_snippet_args[2] = "..."; 591 1.1 joerg snippet_args = default_snippet_args; 592 1.1 joerg } 593 1.26 abhinav 594 1.12 christos if (args->legacy) { 595 1.13 christos char *wild; 596 1.13 christos easprintf(&wild, "%%%s%%", args->search_str); 597 1.26 abhinav query = sqlite3_mprintf("SELECT section, name, name_desc, machine" 598 1.12 christos " FROM mandb" 599 1.13 christos " WHERE name LIKE %Q OR name_desc LIKE %Q " 600 1.12 christos "%s" 601 1.12 christos "%s", 602 1.20 christos wild, wild, 603 1.12 christos section_clause ? section_clause : "", 604 1.12 christos limit_clause ? limit_clause : ""); 605 1.13 christos free(wild); 606 1.31 abhinav } else if (strchr(args->search_str, ' ') == NULL) { 607 1.31 abhinav /* 608 1.31 abhinav * If it's a single word query, we want to search in the 609 1.31 abhinav * links table as well. If the link table contains an entry 610 1.31 abhinav * for the queried keyword, we want to use that as the name of 611 1.31 abhinav * the man page. 612 1.31 abhinav * For example, for `apropos realloc` the output should be 613 1.31 abhinav * realloc(3) and not malloc(3). 614 1.31 abhinav */ 615 1.31 abhinav query = sqlite3_mprintf( 616 1.31 abhinav "SELECT section, name, name_desc, machine," 617 1.31 abhinav " snippet(mandb, %Q, %Q, %Q, -1, 40 )," 618 1.31 abhinav " rank_func(matchinfo(mandb, \"pclxn\")) AS rank" 619 1.31 abhinav " FROM mandb WHERE name NOT IN (" 620 1.31 abhinav " SELECT target FROM mandb_links WHERE link=%Q AND" 621 1.31 abhinav " mandb_links.section=mandb.section) AND mandb MATCH %Q %s %s" 622 1.31 abhinav " UNION" 623 1.31 abhinav " SELECT mandb.section, mandb_links.link AS name, mandb.name_desc," 624 1.31 abhinav " mandb.machine, '' AS snippet, 100.00 AS rank" 625 1.31 abhinav " FROM mandb JOIN mandb_links ON mandb.name=mandb_links.target and" 626 1.31 abhinav " mandb.section=mandb_links.section WHERE mandb_links.link=%Q" 627 1.31 abhinav " %s %s" 628 1.31 abhinav " ORDER BY rank DESC %s", 629 1.31 abhinav snippet_args[0], snippet_args[1], snippet_args[2], 630 1.31 abhinav args->search_str, args->search_str, section_clause ? section_clause : "", 631 1.31 abhinav machine_clause ? machine_clause : "", args->search_str, 632 1.31 abhinav machine_clause ? machine_clause : "", 633 1.31 abhinav section_clause ? section_clause : "", 634 1.31 abhinav limit_clause ? limit_clause : ""); 635 1.12 christos } else { 636 1.12 christos query = sqlite3_mprintf("SELECT section, name, name_desc, machine," 637 1.12 christos " snippet(mandb, %Q, %Q, %Q, -1, 40 )," 638 1.12 christos " rank_func(matchinfo(mandb, \"pclxn\")) AS rank" 639 1.12 christos " FROM mandb" 640 1.12 christos " WHERE mandb MATCH %Q %s " 641 1.12 christos "%s" 642 1.12 christos " ORDER BY rank DESC" 643 1.12 christos "%s", 644 1.12 christos snippet_args[0], snippet_args[1], snippet_args[2], 645 1.12 christos args->search_str, machine_clause ? machine_clause : "", 646 1.12 christos section_clause ? section_clause : "", 647 1.12 christos limit_clause ? limit_clause : ""); 648 1.12 christos } 649 1.1 joerg 650 1.33 abhinav RETURN: 651 1.43 abhinav sqlite3_free(machine_clause); 652 1.45 leot free(section_clause); 653 1.43 abhinav sqlite3_free(limit_clause); 654 1.26 abhinav return query; 655 1.26 abhinav } 656 1.26 abhinav 657 1.46 christos static const char * 658 1.46 christos get_stmt_col_text(sqlite3_stmt *stmt, int col) 659 1.46 christos { 660 1.46 christos const char *t = (const char *) sqlite3_column_text(stmt, col); 661 1.46 christos return t == NULL ? "*?*" : t; 662 1.46 christos } 663 1.46 christos 664 1.26 abhinav /* 665 1.26 abhinav * Execute the full text search query and return the number of results 666 1.26 abhinav * obtained. 667 1.26 abhinav */ 668 1.49 gutterid static int 669 1.26 abhinav execute_search_query(sqlite3 *db, char *query, query_args *args) 670 1.26 abhinav { 671 1.26 abhinav sqlite3_stmt *stmt; 672 1.26 abhinav char *name; 673 1.26 abhinav char *slash_ptr; 674 1.26 abhinav const char *name_temp; 675 1.26 abhinav char *m = NULL; 676 1.26 abhinav int rc; 677 1.40 abhinav query_callback_args callback_args; 678 1.26 abhinav inverse_document_frequency idf = {0, 0}; 679 1.1 joerg 680 1.26 abhinav if (!args->legacy) { 681 1.26 abhinav /* Register the rank function */ 682 1.26 abhinav rc = sqlite3_create_function(db, "rank_func", 1, SQLITE_ANY, 683 1.26 abhinav (void *) &idf, rank_func, NULL, NULL); 684 1.26 abhinav if (rc != SQLITE_OK) { 685 1.26 abhinav warnx("Unable to register the ranking function: %s", 686 1.26 abhinav sqlite3_errmsg(db)); 687 1.26 abhinav sqlite3_close(db); 688 1.26 abhinav sqlite3_shutdown(); 689 1.26 abhinav exit(EXIT_FAILURE); 690 1.26 abhinav } 691 1.1 joerg } 692 1.26 abhinav 693 1.1 joerg rc = sqlite3_prepare_v2(db, query, -1, &stmt, NULL); 694 1.1 joerg if (rc == SQLITE_IOERR) { 695 1.1 joerg warnx("Corrupt database. Please rerun makemandb"); 696 1.1 joerg return -1; 697 1.1 joerg } else if (rc != SQLITE_OK) { 698 1.1 joerg warnx("%s", sqlite3_errmsg(db)); 699 1.1 joerg return -1; 700 1.1 joerg } 701 1.1 joerg 702 1.49 gutterid int nresults = rc = 0; 703 1.49 gutterid while (rc == 0 && sqlite3_step(stmt) == SQLITE_ROW) { 704 1.26 abhinav nresults++; 705 1.46 christos callback_args.section = get_stmt_col_text(stmt, 0); 706 1.46 christos name_temp = get_stmt_col_text(stmt, 1); 707 1.46 christos callback_args.name_desc = get_stmt_col_text(stmt, 2); 708 1.47 abhinav callback_args.machine = (const char *) sqlite3_column_text(stmt, 3); 709 1.42 abhinav if (!args->legacy) { 710 1.46 christos callback_args.snippet = get_stmt_col_text(stmt, 4); 711 1.46 christos callback_args.snippet_length = 712 1.46 christos strlen(callback_args.snippet); 713 1.42 abhinav } else { 714 1.40 abhinav callback_args.snippet = ""; 715 1.42 abhinav callback_args.snippet_length = 1; 716 1.42 abhinav } 717 1.4 wiz if ((slash_ptr = strrchr(name_temp, '/')) != NULL) 718 1.4 wiz name_temp = slash_ptr + 1; 719 1.40 abhinav if (callback_args.machine && callback_args.machine[0]) { 720 1.40 abhinav m = estrdup(callback_args.machine); 721 1.24 christos easprintf(&name, "%s/%s", lower(m), name_temp); 722 1.1 joerg free(m); 723 1.1 joerg } else { 724 1.46 christos name = estrdup(get_stmt_col_text(stmt, 1)); 725 1.1 joerg } 726 1.40 abhinav callback_args.name = name; 727 1.40 abhinav callback_args.other_data = args->callback_data; 728 1.49 gutterid rc = (args->callback)(&callback_args); 729 1.26 abhinav free(name); 730 1.26 abhinav } 731 1.26 abhinav sqlite3_finalize(stmt); 732 1.49 gutterid return (rc < 0) ? rc : nresults; 733 1.26 abhinav } 734 1.1 joerg 735 1.26 abhinav 736 1.26 abhinav /* 737 1.26 abhinav * run_query_internal -- 738 1.26 abhinav * Performs the searches for the keywords entered by the user. 739 1.26 abhinav * The 2nd param: snippet_args is an array of strings providing values for the 740 1.26 abhinav * last three parameters to the snippet function of sqlite. (Look at the docs). 741 1.26 abhinav * The 3rd param: args contains rest of the search parameters. Look at 742 1.26 abhinav * arpopos-utils.h for the description of individual fields. 743 1.26 abhinav * 744 1.26 abhinav */ 745 1.26 abhinav static int 746 1.26 abhinav run_query_internal(sqlite3 *db, const char *snippet_args[3], query_args *args) 747 1.26 abhinav { 748 1.26 abhinav char *query; 749 1.26 abhinav query = generate_search_query(args, snippet_args); 750 1.26 abhinav if (query == NULL) { 751 1.26 abhinav *args->errmsg = estrdup("malloc failed"); 752 1.26 abhinav return -1; 753 1.1 joerg } 754 1.1 joerg 755 1.49 gutterid int rc = execute_search_query(db, query, args); 756 1.1 joerg sqlite3_free(query); 757 1.49 gutterid return (rc < 0 || *(args->errmsg) != NULL) ? -1 : 0; 758 1.1 joerg } 759 1.1 joerg 760 1.21 christos static char * 761 1.21 christos get_escaped_html_string(const char *src, size_t *slen) 762 1.1 joerg { 763 1.21 christos static const char trouble[] = "<>\"&\002\003"; 764 1.21 christos /* 765 1.21 christos * First scan the src to find out the number of occurrences 766 1.21 christos * of {'>', '<' '"', '&'}. Then allocate a new buffer with 767 1.21 christos * sufficient space to be able to store the quoted versions 768 1.21 christos * of the special characters {>, <, ", &}. 769 1.21 christos * Copy over the characters from the original src into 770 1.21 christos * this buffer while replacing the special characters with 771 1.21 christos * their quoted versions. 772 1.21 christos */ 773 1.21 christos char *dst, *ddst; 774 1.21 christos size_t count; 775 1.21 christos const char *ssrc; 776 1.21 christos 777 1.21 christos for (count = 0, ssrc = src; *src; count++) { 778 1.21 christos size_t sz = strcspn(src, trouble); 779 1.21 christos src += sz + 1; 780 1.21 christos } 781 1.1 joerg 782 1.1 joerg 783 1.24 christos #define append(a) \ 784 1.24 christos do { \ 785 1.24 christos memcpy(dst, (a), sizeof(a) - 1); \ 786 1.24 christos dst += sizeof(a) - 1; \ 787 1.48 rillig } while (0) 788 1.24 christos 789 1.1 joerg 790 1.21 christos ddst = dst = emalloc(*slen + count * 5 + 1); 791 1.21 christos for (src = ssrc; *src; src++) { 792 1.21 christos switch (*src) { 793 1.1 joerg case '<': 794 1.21 christos append("<"); 795 1.1 joerg break; 796 1.1 joerg case '>': 797 1.21 christos append(">"); 798 1.1 joerg break; 799 1.1 joerg case '\"': 800 1.21 christos append("""); 801 1.1 joerg break; 802 1.1 joerg case '&': 803 1.21 christos /* 804 1.21 christos * Don't perform the quoting if this & is part of 805 1.21 christos * an mdoc escape sequence, e.g. \& 806 1.1 joerg */ 807 1.21 christos if (src != ssrc && src[-1] != '\\') 808 1.21 christos append("&"); 809 1.21 christos else 810 1.21 christos append("&"); 811 1.1 joerg break; 812 1.1 joerg case '\002': 813 1.21 christos append("<b>"); 814 1.1 joerg break; 815 1.1 joerg case '\003': 816 1.21 christos append("</b>"); 817 1.1 joerg break; 818 1.1 joerg default: 819 1.21 christos *dst++ = *src; 820 1.1 joerg break; 821 1.1 joerg } 822 1.1 joerg } 823 1.21 christos *dst = '\0'; 824 1.21 christos *slen = dst - ddst; 825 1.21 christos return ddst; 826 1.21 christos } 827 1.21 christos 828 1.21 christos 829 1.21 christos /* 830 1.21 christos * callback_html -- 831 1.21 christos * Callback function for run_query_html. It builds the html output and then 832 1.21 christos * calls the actual user supplied callback function. 833 1.21 christos */ 834 1.21 christos static int 835 1.40 abhinav callback_html(query_callback_args *callback_args) 836 1.21 christos { 837 1.40 abhinav struct orig_callback_data *orig_data = callback_args->other_data; 838 1.40 abhinav int (*callback)(query_callback_args*) = orig_data->callback; 839 1.40 abhinav size_t length = callback_args->snippet_length; 840 1.40 abhinav size_t name_description_length = strlen(callback_args->name_desc); 841 1.40 abhinav char *qsnippet = get_escaped_html_string(callback_args->snippet, &length); 842 1.40 abhinav char *qname_description = get_escaped_html_string(callback_args->name_desc, 843 1.21 christos &name_description_length); 844 1.40 abhinav callback_args->name_desc = qname_description; 845 1.40 abhinav callback_args->snippet = qsnippet; 846 1.40 abhinav callback_args->snippet_length = length; 847 1.40 abhinav callback_args->other_data = orig_data->data; 848 1.49 gutterid int rc = (*callback)(callback_args); 849 1.1 joerg free(qsnippet); 850 1.21 christos free(qname_description); 851 1.49 gutterid return rc; 852 1.1 joerg } 853 1.1 joerg 854 1.1 joerg /* 855 1.1 joerg * run_query_html -- 856 1.1 joerg * Utility function to output query result in HTML format. 857 1.17 snj * It internally calls run_query only, but it first passes the output to its 858 1.1 joerg * own custom callback function, which preprocess the snippet for quoting 859 1.1 joerg * inline HTML fragments. 860 1.1 joerg * After that it delegates the call the actual user supplied callback function. 861 1.1 joerg */ 862 1.15 christos static int 863 1.1 joerg run_query_html(sqlite3 *db, query_args *args) 864 1.1 joerg { 865 1.1 joerg struct orig_callback_data orig_data; 866 1.1 joerg orig_data.callback = args->callback; 867 1.1 joerg orig_data.data = args->callback_data; 868 1.1 joerg const char *snippet_args[] = {"\002", "\003", "..."}; 869 1.1 joerg args->callback = &callback_html; 870 1.1 joerg args->callback_data = (void *) &orig_data; 871 1.15 christos return run_query_internal(db, snippet_args, args); 872 1.1 joerg } 873 1.1 joerg 874 1.1 joerg /* 875 1.9 christos * underline a string, pager style. 876 1.9 christos */ 877 1.9 christos static char * 878 1.14 christos ul_pager(int ul, const char *s) 879 1.9 christos { 880 1.9 christos size_t len; 881 1.9 christos char *dst, *d; 882 1.9 christos 883 1.14 christos if (!ul) 884 1.14 christos return estrdup(s); 885 1.14 christos 886 1.9 christos // a -> _\ba 887 1.9 christos len = strlen(s) * 3 + 1; 888 1.9 christos 889 1.9 christos d = dst = emalloc(len); 890 1.9 christos while (*s) { 891 1.9 christos *d++ = '_'; 892 1.9 christos *d++ = '\b'; 893 1.9 christos *d++ = *s++; 894 1.9 christos } 895 1.9 christos *d = '\0'; 896 1.9 christos return dst; 897 1.9 christos } 898 1.9 christos 899 1.9 christos /* 900 1.1 joerg * callback_pager -- 901 1.1 joerg * A callback similar to callback_html. It overstrikes the matching text in 902 1.1 joerg * the snippet so that it appears emboldened when viewed using a pager like 903 1.1 joerg * more or less. 904 1.1 joerg */ 905 1.1 joerg static int 906 1.40 abhinav callback_pager(query_callback_args *callback_args) 907 1.1 joerg { 908 1.40 abhinav struct orig_callback_data *orig_data = callback_args->other_data; 909 1.1 joerg char *psnippet; 910 1.40 abhinav const char *temp = callback_args->snippet; 911 1.1 joerg int count = 0; 912 1.14 christos int i = 0, did; 913 1.1 joerg size_t sz = 0; 914 1.1 joerg size_t psnippet_length; 915 1.1 joerg 916 1.24 christos /* Count the number of bytes of matching text. For each of these 917 1.24 christos * bytes we will use 2 extra bytes to overstrike it so that it 918 1.24 christos * appears bold when viewed using a pager. 919 1.1 joerg */ 920 1.1 joerg while (*temp) { 921 1.1 joerg sz = strcspn(temp, "\002\003"); 922 1.1 joerg temp += sz; 923 1.1 joerg if (*temp == '\003') { 924 1.1 joerg count += 2 * (sz); 925 1.1 joerg } 926 1.1 joerg temp++; 927 1.1 joerg } 928 1.1 joerg 929 1.40 abhinav psnippet_length = callback_args->snippet_length + count; 930 1.1 joerg psnippet = emalloc(psnippet_length + 1); 931 1.1 joerg 932 1.1 joerg /* Copy the bytes from snippet to psnippet: 933 1.1 joerg * 1. Copy the bytes before \002 as it is. 934 1.24 christos * 2. The bytes after \002 need to be overstriked till we 935 1.24 christos * encounter \003. 936 1.1 joerg * 3. To overstrike a byte 'A' we need to write 'A\bA' 937 1.1 joerg */ 938 1.14 christos did = 0; 939 1.40 abhinav const char *snippet = callback_args->snippet; 940 1.1 joerg while (*snippet) { 941 1.1 joerg sz = strcspn(snippet, "\002"); 942 1.1 joerg memcpy(&psnippet[i], snippet, sz); 943 1.1 joerg snippet += sz; 944 1.1 joerg i += sz; 945 1.1 joerg 946 1.1 joerg /* Don't change this. Advancing the pointer without reading the byte 947 1.1 joerg * is causing strange behavior. 948 1.1 joerg */ 949 1.1 joerg if (*snippet == '\002') 950 1.1 joerg snippet++; 951 1.1 joerg while (*snippet && *snippet != '\003') { 952 1.14 christos did = 1; 953 1.1 joerg psnippet[i++] = *snippet; 954 1.1 joerg psnippet[i++] = '\b'; 955 1.1 joerg psnippet[i++] = *snippet++; 956 1.1 joerg } 957 1.1 joerg if (*snippet) 958 1.1 joerg snippet++; 959 1.1 joerg } 960 1.1 joerg 961 1.1 joerg psnippet[i] = 0; 962 1.40 abhinav char *ul_section = ul_pager(did, callback_args->section); 963 1.40 abhinav char *ul_name = ul_pager(did, callback_args->name); 964 1.40 abhinav char *ul_name_desc = ul_pager(did, callback_args->name_desc); 965 1.40 abhinav callback_args->section = ul_section; 966 1.40 abhinav callback_args->name = ul_name; 967 1.40 abhinav callback_args->name_desc = ul_name_desc; 968 1.40 abhinav callback_args->snippet = psnippet; 969 1.40 abhinav callback_args->snippet_length = psnippet_length; 970 1.40 abhinav callback_args->other_data = orig_data->data; 971 1.49 gutterid int rc = (orig_data->callback)(callback_args); 972 1.9 christos free(ul_section); 973 1.9 christos free(ul_name); 974 1.9 christos free(ul_name_desc); 975 1.1 joerg free(psnippet); 976 1.49 gutterid return rc; 977 1.1 joerg } 978 1.1 joerg 979 1.9 christos struct term_args { 980 1.9 christos struct orig_callback_data *orig_data; 981 1.9 christos const char *smul; 982 1.9 christos const char *rmul; 983 1.9 christos }; 984 1.9 christos 985 1.9 christos /* 986 1.9 christos * underline a string, pager style. 987 1.9 christos */ 988 1.9 christos static char * 989 1.9 christos ul_term(const char *s, const struct term_args *ta) 990 1.9 christos { 991 1.9 christos char *dst; 992 1.9 christos 993 1.9 christos easprintf(&dst, "%s%s%s", ta->smul, s, ta->rmul); 994 1.9 christos return dst; 995 1.9 christos } 996 1.9 christos 997 1.9 christos /* 998 1.9 christos * callback_term -- 999 1.9 christos * A callback similar to callback_html. It overstrikes the matching text in 1000 1.9 christos * the snippet so that it appears emboldened when viewed using a pager like 1001 1.9 christos * more or less. 1002 1.9 christos */ 1003 1.9 christos static int 1004 1.40 abhinav callback_term(query_callback_args *callback_args) 1005 1.9 christos { 1006 1.40 abhinav struct term_args *ta = callback_args->other_data; 1007 1.9 christos struct orig_callback_data *orig_data = ta->orig_data; 1008 1.9 christos 1009 1.40 abhinav char *ul_section = ul_term(callback_args->section, ta); 1010 1.40 abhinav char *ul_name = ul_term(callback_args->name, ta); 1011 1.40 abhinav char *ul_name_desc = ul_term(callback_args->name_desc, ta); 1012 1.40 abhinav callback_args->section = ul_section; 1013 1.40 abhinav callback_args->name = ul_name; 1014 1.40 abhinav callback_args->name_desc = ul_name_desc; 1015 1.40 abhinav callback_args->other_data = orig_data->data; 1016 1.49 gutterid int rc = (orig_data->callback)(callback_args); 1017 1.9 christos free(ul_section); 1018 1.9 christos free(ul_name); 1019 1.9 christos free(ul_name_desc); 1020 1.49 gutterid return rc; 1021 1.9 christos } 1022 1.9 christos 1023 1.1 joerg /* 1024 1.1 joerg * run_query_pager -- 1025 1.1 joerg * Utility function similar to run_query_html. This function tries to 1026 1.1 joerg * pre-process the result assuming it will be piped to a pager. 1027 1.17 snj * For this purpose it first calls its own callback function callback_pager 1028 1.1 joerg * which then delegates the call to the user supplied callback. 1029 1.1 joerg */ 1030 1.15 christos static int 1031 1.6 joerg run_query_pager(sqlite3 *db, query_args *args) 1032 1.1 joerg { 1033 1.1 joerg struct orig_callback_data orig_data; 1034 1.1 joerg orig_data.callback = args->callback; 1035 1.1 joerg orig_data.data = args->callback_data; 1036 1.15 christos const char *snippet_args[3] = { "\002", "\003", "..." }; 1037 1.1 joerg args->callback = &callback_pager; 1038 1.1 joerg args->callback_data = (void *) &orig_data; 1039 1.15 christos return run_query_internal(db, snippet_args, args); 1040 1.1 joerg } 1041 1.9 christos 1042 1.18 christos struct nv { 1043 1.18 christos char *s; 1044 1.18 christos size_t l; 1045 1.18 christos }; 1046 1.18 christos 1047 1.18 christos static int 1048 1.18 christos term_putc(int c, void *p) 1049 1.18 christos { 1050 1.18 christos struct nv *nv = p; 1051 1.18 christos nv->s[nv->l++] = c; 1052 1.18 christos return 0; 1053 1.18 christos } 1054 1.18 christos 1055 1.18 christos static char * 1056 1.18 christos term_fix_seq(TERMINAL *ti, const char *seq) 1057 1.18 christos { 1058 1.18 christos char *res = estrdup(seq); 1059 1.18 christos struct nv nv; 1060 1.18 christos 1061 1.19 christos if (ti == NULL) 1062 1.19 christos return res; 1063 1.19 christos 1064 1.18 christos nv.s = res; 1065 1.18 christos nv.l = 0; 1066 1.18 christos ti_puts(ti, seq, 1, term_putc, &nv); 1067 1.18 christos nv.s[nv.l] = '\0'; 1068 1.18 christos 1069 1.18 christos return res; 1070 1.18 christos } 1071 1.18 christos 1072 1.9 christos static void 1073 1.9 christos term_init(int fd, const char *sa[5]) 1074 1.9 christos { 1075 1.9 christos TERMINAL *ti; 1076 1.9 christos int error; 1077 1.9 christos const char *bold, *sgr0, *smso, *rmso, *smul, *rmul; 1078 1.9 christos 1079 1.9 christos if (ti_setupterm(&ti, NULL, fd, &error) == -1) { 1080 1.9 christos bold = sgr0 = NULL; 1081 1.9 christos smso = rmso = smul = rmul = ""; 1082 1.9 christos ti = NULL; 1083 1.9 christos } else { 1084 1.9 christos bold = ti_getstr(ti, "bold"); 1085 1.9 christos sgr0 = ti_getstr(ti, "sgr0"); 1086 1.9 christos if (bold == NULL || sgr0 == NULL) { 1087 1.9 christos smso = ti_getstr(ti, "smso"); 1088 1.9 christos 1089 1.9 christos if (smso == NULL || 1090 1.9 christos (rmso = ti_getstr(ti, "rmso")) == NULL) 1091 1.9 christos smso = rmso = ""; 1092 1.9 christos bold = sgr0 = NULL; 1093 1.9 christos } else 1094 1.9 christos smso = rmso = ""; 1095 1.9 christos 1096 1.9 christos smul = ti_getstr(ti, "smul"); 1097 1.9 christos if (smul == NULL || (rmul = ti_getstr(ti, "rmul")) == NULL) 1098 1.9 christos smul = rmul = ""; 1099 1.9 christos } 1100 1.9 christos 1101 1.18 christos sa[0] = term_fix_seq(ti, bold ? bold : smso); 1102 1.18 christos sa[1] = term_fix_seq(ti, sgr0 ? sgr0 : rmso); 1103 1.9 christos sa[2] = estrdup("..."); 1104 1.18 christos sa[3] = term_fix_seq(ti, smul); 1105 1.18 christos sa[4] = term_fix_seq(ti, rmul); 1106 1.18 christos 1107 1.9 christos if (ti) 1108 1.9 christos del_curterm(ti); 1109 1.9 christos } 1110 1.9 christos 1111 1.9 christos /* 1112 1.9 christos * run_query_term -- 1113 1.9 christos * Utility function similar to run_query_html. This function tries to 1114 1.9 christos * pre-process the result assuming it will be displayed on a terminal 1115 1.17 snj * For this purpose it first calls its own callback function callback_pager 1116 1.9 christos * which then delegates the call to the user supplied callback. 1117 1.9 christos */ 1118 1.15 christos static int 1119 1.9 christos run_query_term(sqlite3 *db, query_args *args) 1120 1.9 christos { 1121 1.9 christos struct orig_callback_data orig_data; 1122 1.9 christos struct term_args ta; 1123 1.9 christos orig_data.callback = args->callback; 1124 1.9 christos orig_data.data = args->callback_data; 1125 1.9 christos const char *snippet_args[5]; 1126 1.15 christos 1127 1.15 christos term_init(STDOUT_FILENO, snippet_args); 1128 1.9 christos ta.smul = snippet_args[3]; 1129 1.9 christos ta.rmul = snippet_args[4]; 1130 1.9 christos ta.orig_data = (void *) &orig_data; 1131 1.9 christos 1132 1.9 christos args->callback = &callback_term; 1133 1.9 christos args->callback_data = &ta; 1134 1.15 christos return run_query_internal(db, snippet_args, args); 1135 1.15 christos } 1136 1.15 christos 1137 1.15 christos static int 1138 1.15 christos run_query_none(sqlite3 *db, query_args *args) 1139 1.15 christos { 1140 1.15 christos struct orig_callback_data orig_data; 1141 1.15 christos orig_data.callback = args->callback; 1142 1.15 christos orig_data.data = args->callback_data; 1143 1.15 christos const char *snippet_args[3] = { "", "", "..." }; 1144 1.15 christos args->callback = &callback_pager; 1145 1.15 christos args->callback_data = (void *) &orig_data; 1146 1.15 christos return run_query_internal(db, snippet_args, args); 1147 1.15 christos } 1148 1.15 christos 1149 1.15 christos int 1150 1.15 christos run_query(sqlite3 *db, query_format fmt, query_args *args) 1151 1.15 christos { 1152 1.15 christos switch (fmt) { 1153 1.15 christos case APROPOS_NONE: 1154 1.15 christos return run_query_none(db, args); 1155 1.15 christos case APROPOS_HTML: 1156 1.15 christos return run_query_html(db, args); 1157 1.15 christos case APROPOS_TERM: 1158 1.15 christos return run_query_term(db, args); 1159 1.15 christos case APROPOS_PAGER: 1160 1.15 christos return run_query_pager(db, args); 1161 1.15 christos default: 1162 1.15 christos warnx("Unknown query format %d", (int)fmt); 1163 1.15 christos return -1; 1164 1.15 christos } 1165 1.9 christos } 1166