1/*	$NetBSD: apropos-utils.c,v 1.2.2.1 2012/04/19 20:03:00 riz 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-utils.c,v 1.2.2.1 2012/04/19 20:03:00 riz Exp $");
35
36#include <sys/stat.h>
37
38#include <assert.h>
39#include <ctype.h>
40#include <err.h>
41#include <math.h>
42#include <stdio.h>
43#include <stdlib.h>
44#include <string.h>
45#include <util.h>
46#include <zlib.h>
47
48#include "apropos-utils.h"
49#include "mandoc.h"
50#include "sqlite3.h"
51
52typedef struct orig_callback_data {
53	void *data;
54	int (*callback) (void *, const char *, const char *, const char *,
55		const char *, size_t);
56} orig_callback_data;
57
58typedef struct inverse_document_frequency {
59	double value;
60	int status;
61} inverse_document_frequency;
62
63/* weights for individual columns */
64static const double col_weights[] = {
65	2.0,	// NAME
66	2.00,	// Name-description
67	0.55,	// DESCRIPTION
68	0.10,	// LIBRARY
69	0.001,	//RETURN VALUES
70	0.20,	//ENVIRONMENT
71	0.01,	//FILES
72	0.001,	//EXIT STATUS
73	2.00,	//DIAGNOSTICS
74	0.05,	//ERRORS
75	0.00,	//md5_hash
76	1.00	//machine
77};
78
79/*
80 * lower --
81 *  Converts the string str to lower case
82 */
83char *
84lower(char *str)
85{
86	assert(str);
87	int i = 0;
88	char c;
89	while (str[i] != '\0') {
90		c = tolower((unsigned char) str[i]);
91		str[i++] = c;
92	}
93	return str;
94}
95
96/*
97* concat--
98*  Utility function. Concatenates together: dst, a space character and src.
99* dst + " " + src
100*/
101void
102concat(char **dst, const char *src)
103{
104	concat2(dst, src, strlen(src));
105}
106
107void
108concat2(char **dst, const char *src, size_t srclen)
109{
110	size_t total_len, dst_len;
111	assert(src != NULL);
112
113	/* If destination buffer dst is NULL, then simply strdup the source buffer */
114	if (*dst == NULL) {
115		*dst = estrdup(src);
116		return;
117	}
118
119	dst_len = strlen(*dst);
120	/*
121	 * NUL Byte and separator space
122	 */
123	total_len = dst_len + srclen + 2;
124
125	*dst = erealloc(*dst, total_len);
126
127	/* Append a space at the end of dst */
128	(*dst)[dst_len++] = ' ';
129
130	/* Now, copy src at the end of dst */
131	memcpy(*dst + dst_len, src, srclen + 1);
132}
133
134void
135close_db(sqlite3 *db)
136{
137	sqlite3_close(db);
138	sqlite3_shutdown();
139}
140
141/*
142 * create_db --
143 *  Creates the database schema.
144 */
145static int
146create_db(sqlite3 *db)
147{
148	const char *sqlstr = NULL;
149	char *schemasql;
150	char *errmsg = NULL;
151
152/*------------------------ Create the tables------------------------------*/
153
154#if NOTYET
155	sqlite3_exec(db, "PRAGMA journal_mode = WAL", NULL, NULL, NULL);
156#else
157	sqlite3_exec(db, "PRAGMA journal_mode = DELETE", NULL, NULL, NULL);
158#endif
159
160	schemasql = sqlite3_mprintf("PRAGMA user_version = %d",
161	    APROPOS_SCHEMA_VERSION);
162	sqlite3_exec(db, schemasql, NULL, NULL, &errmsg);
163	if (errmsg != NULL)
164		goto out;
165	sqlite3_free(schemasql);
166
167	sqlstr = "CREATE VIRTUAL TABLE mandb USING fts4(section, name, "
168			    "name_desc, desc, lib, return_vals, env, files, "
169			    "exit_status, diagnostics, errors, md5_hash UNIQUE, machine, "
170			    "compress=zip, uncompress=unzip, tokenize=porter); "	//mandb
171			"CREATE TABLE IF NOT EXISTS mandb_meta(device, inode, mtime, "
172			    "file UNIQUE, md5_hash UNIQUE, id  INTEGER PRIMARY KEY); "
173				//mandb_meta
174			"CREATE TABLE IF NOT EXISTS mandb_links(link, target, section, "
175			    "machine, md5_hash); ";	//mandb_links
176
177	sqlite3_exec(db, sqlstr, NULL, NULL, &errmsg);
178	if (errmsg != NULL)
179		goto out;
180
181	sqlstr = "CREATE INDEX IF NOT EXISTS index_mandb_links ON mandb_links "
182			"(link); "
183			"CREATE INDEX IF NOT EXISTS index_mandb_meta_dev ON mandb_meta "
184			"(device, inode); "
185			"CREATE INDEX IF NOT EXISTS index_mandb_links_md5 ON mandb_links "
186			"(md5_hash);";
187	sqlite3_exec(db, sqlstr, NULL, NULL, &errmsg);
188	if (errmsg != NULL)
189		goto out;
190	return 0;
191
192out:
193	warnx("%s", errmsg);
194	free(errmsg);
195	sqlite3_close(db);
196	sqlite3_shutdown();
197	return -1;
198}
199
200/*
201 * zip --
202 *  User defined Sqlite function to compress the FTS table
203 */
204static void
205zip(sqlite3_context *pctx, int nval, sqlite3_value **apval)
206{
207	int nin;
208	long int nout;
209	const unsigned char * inbuf;
210	unsigned char *outbuf;
211
212	assert(nval == 1);
213	nin = sqlite3_value_bytes(apval[0]);
214	inbuf = (const unsigned char *) sqlite3_value_blob(apval[0]);
215	nout = nin + 13 + (nin + 999) / 1000;
216	outbuf = emalloc(nout);
217	compress(outbuf, (unsigned long *) &nout, inbuf, nin);
218	sqlite3_result_blob(pctx, outbuf, nout, free);
219}
220
221/*
222 * unzip --
223 *  User defined Sqlite function to uncompress the FTS table.
224 */
225static void
226unzip(sqlite3_context *pctx, int nval, sqlite3_value **apval)
227{
228	unsigned int rc;
229	unsigned char *outbuf;
230	z_stream stream;
231
232	assert(nval == 1);
233	stream.next_in = __UNCONST(sqlite3_value_blob(apval[0]));
234	stream.avail_in = sqlite3_value_bytes(apval[0]);
235	stream.avail_out = stream.avail_in * 2 + 100;
236	stream.next_out = outbuf = emalloc(stream.avail_out);
237	stream.zalloc = NULL;
238	stream.zfree = NULL;
239
240	if (inflateInit(&stream) != Z_OK) {
241		free(outbuf);
242		return;
243	}
244
245	while ((rc = inflate(&stream, Z_SYNC_FLUSH)) != Z_STREAM_END) {
246		if (rc != Z_OK ||
247		    (stream.avail_out != 0 && stream.avail_in == 0)) {
248			free(outbuf);
249			return;
250		}
251		outbuf = erealloc(outbuf, stream.total_out * 2);
252		stream.next_out = outbuf + stream.total_out;
253		stream.avail_out = stream.total_out;
254	}
255	if (inflateEnd(&stream) != Z_OK) {
256		free(outbuf);
257		return;
258	}
259	outbuf = erealloc(outbuf, stream.total_out);
260	sqlite3_result_text(pctx, (const char *) outbuf, stream.total_out, free);
261}
262
263/* init_db --
264 *   Prepare the database. Register the compress/uncompress functions and the
265 *   stopword tokenizer.
266 *	 db_flag specifies the mode in which to open the database. 3 options are
267 *   available:
268 *   	1. DB_READONLY: Open in READONLY mode. An error if db does not exist.
269 *  	2. DB_READWRITE: Open in read-write mode. An error if db does not exist.
270 *  	3. DB_CREATE: Open in read-write mode. It will try to create the db if
271 *			it does not exist already.
272 *  RETURN VALUES:
273 *		The function will return NULL in case the db does not exist and DB_CREATE
274 *  	was not specified. And in case DB_CREATE was specified and yet NULL is
275 *  	returned, then there was some other error.
276 *  	In normal cases the function should return a handle to the db.
277 */
278sqlite3 *
279init_db(int db_flag)
280{
281	sqlite3 *db = NULL;
282	sqlite3_stmt *stmt;
283	struct stat sb;
284	int rc;
285	int create_db_flag = 0;
286
287	/* Check if the database exists or not */
288	if (!(stat(DBPATH, &sb) == 0 && S_ISREG(sb.st_mode))) {
289		/* Database does not exist, check if DB_CREATE was specified, and set
290		 * flag to create the database schema
291		 */
292		if (db_flag != (MANDB_CREATE)) {
293			warnx("Missing apropos database. "
294			      "Please run makemandb to create it.");
295			return NULL;
296		}
297		create_db_flag = 1;
298	}
299
300	/* Now initialize the database connection */
301	sqlite3_initialize();
302	rc = sqlite3_open_v2(DBPATH, &db, db_flag, NULL);
303
304	if (rc != SQLITE_OK) {
305		warnx("%s", sqlite3_errmsg(db));
306		sqlite3_shutdown();
307		return NULL;
308	}
309
310	if (create_db_flag && create_db(db) < 0) {
311		warnx("%s", "Unable to create database schema");
312		goto error;
313	}
314
315	rc = sqlite3_prepare_v2(db, "PRAGMA user_version", -1, &stmt, NULL);
316	if (rc != SQLITE_OK) {
317		warnx("Unable to query schema version: %s",
318		    sqlite3_errmsg(db));
319		goto error;
320	}
321	if (sqlite3_step(stmt) != SQLITE_ROW) {
322		sqlite3_finalize(stmt);
323		warnx("Unable to query schema version: %s",
324		    sqlite3_errmsg(db));
325		goto error;
326	}
327	if (sqlite3_column_int(stmt, 0) != APROPOS_SCHEMA_VERSION) {
328		sqlite3_finalize(stmt);
329		warnx("Incorrect schema version found. "
330		      "Please run makemandb -f.");
331		goto error;
332	}
333	sqlite3_finalize(stmt);
334
335	sqlite3_extended_result_codes(db, 1);
336
337	/* Register the zip and unzip functions for FTS compression */
338	rc = sqlite3_create_function(db, "zip", 1, SQLITE_ANY, NULL, zip, NULL, NULL);
339	if (rc != SQLITE_OK) {
340		warnx("Unable to register function: compress: %s",
341		    sqlite3_errmsg(db));
342		goto error;
343	}
344
345	rc = sqlite3_create_function(db, "unzip", 1, SQLITE_ANY, NULL,
346                                 unzip, NULL, NULL);
347	if (rc != SQLITE_OK) {
348		warnx("Unable to register function: uncompress: %s",
349		    sqlite3_errmsg(db));
350		goto error;
351	}
352	return db;
353error:
354	sqlite3_close(db);
355	sqlite3_shutdown();
356	return NULL;
357}
358
359/*
360 * rank_func --
361 *  Sqlite user defined function for ranking the documents.
362 *  For each phrase of the query, it computes the tf and idf and adds them over.
363 *  It computes the final rank, by multiplying tf and idf together.
364 *  Weight of term t for document d = (term frequency of t in d *
365 *                                      inverse document frequency of t)
366 *
367 *  Term Frequency of term t in document d = Number of times t occurs in d /
368 *	                                        Number of times t appears in all
369 *											documents
370 *
371 *  Inverse document frequency of t = log(Total number of documents /
372 *										Number of documents in which t occurs)
373 */
374static void
375rank_func(sqlite3_context *pctx, int nval, sqlite3_value **apval)
376{
377	inverse_document_frequency *idf = sqlite3_user_data(pctx);
378	double tf = 0.0;
379	const unsigned int *matchinfo;
380	int ncol;
381	int nphrase;
382	int iphrase;
383	int ndoc;
384	int doclen = 0;
385	const double k = 3.75;
386	/* Check that the number of arguments passed to this function is correct. */
387	assert(nval == 1);
388
389	matchinfo = (const unsigned int *) sqlite3_value_blob(apval[0]);
390	nphrase = matchinfo[0];
391	ncol = matchinfo[1];
392	ndoc = matchinfo[2 + 3 * ncol * nphrase + ncol];
393	for (iphrase = 0; iphrase < nphrase; iphrase++) {
394		int icol;
395		const unsigned int *phraseinfo = &matchinfo[2 + ncol+ iphrase * ncol * 3];
396		for(icol = 1; icol < ncol; icol++) {
397
398			/* nhitcount: number of times the current phrase occurs in the current
399			 *            column in the current document.
400			 * nglobalhitcount: number of times current phrase occurs in the current
401			 *                  column in all documents.
402			 * ndocshitcount:   number of documents in which the current phrase
403			 *                  occurs in the current column at least once.
404			 */
405  			int nhitcount = phraseinfo[3 * icol];
406			int nglobalhitcount = phraseinfo[3 * icol + 1];
407			int ndocshitcount = phraseinfo[3 * icol + 2];
408			doclen = matchinfo[2 + icol ];
409			double weight = col_weights[icol - 1];
410			if (idf->status == 0 && ndocshitcount)
411				idf->value += log(((double)ndoc / ndocshitcount))* weight;
412
413			/* Dividing the tf by document length to normalize the effect of
414			 * longer documents.
415			 */
416			if (nglobalhitcount > 0 && nhitcount)
417				tf += (((double)nhitcount  * weight) / (nglobalhitcount * doclen));
418		}
419	}
420	idf->status = 1;
421
422	/* Final score = (tf * idf)/ ( k + tf)
423	 *	Dividing by k+ tf further normalizes the weight leading to better
424	 *  results.
425	 *  The value of k is experimental
426	 */
427	double score = (tf * idf->value/ ( k + tf)) ;
428	sqlite3_result_double(pctx, score);
429	return;
430}
431
432/*
433 *  run_query --
434 *  Performs the searches for the keywords entered by the user.
435 *  The 2nd param: snippet_args is an array of strings providing values for the
436 *  last three parameters to the snippet function of sqlite. (Look at the docs).
437 *  The 3rd param: args contains rest of the search parameters. Look at
438 *  arpopos-utils.h for the description of individual fields.
439 *
440 */
441int
442run_query(sqlite3 *db, const char *snippet_args[3], query_args *args)
443{
444	const char *default_snippet_args[3];
445	char *section_clause = NULL;
446	char *limit_clause = NULL;
447	char *machine_clause = NULL;
448	char *query;
449	const char *section;
450	char *name;
451	const char *name_desc;
452	const char *machine;
453	const char *snippet;
454	const char *name_temp;
455	char *slash_ptr;
456	char *m = NULL;
457	int rc;
458	inverse_document_frequency idf = {0, 0};
459	sqlite3_stmt *stmt;
460
461	if (args->machine)
462		easprintf(&machine_clause, "AND machine = \'%s\' ", args->machine);
463
464	/* Register the rank function */
465	rc = sqlite3_create_function(db, "rank_func", 1, SQLITE_ANY, (void *)&idf,
466	                             rank_func, NULL, NULL);
467	if (rc != SQLITE_OK) {
468		warnx("Unable to register the ranking function: %s",
469		    sqlite3_errmsg(db));
470		sqlite3_close(db);
471		sqlite3_shutdown();
472		exit(EXIT_FAILURE);
473	}
474
475	/* We want to build a query of the form: "select x,y,z from mandb where
476	 * mandb match :query [AND (section LIKE '1' OR section LIKE '2' OR...)]
477	 * ORDER BY rank DESC..."
478	 * NOTES: 1. The portion in square brackets is optional, it will be there
479	 * only if the user has specified an option on the command line to search in
480	 * one or more specific sections.
481	 * 2. I am using LIKE operator because '=' or IN operators do not seem to be
482	 * working with the compression option enabled.
483	 */
484
485	if (args->sec_nums) {
486		char *temp;
487		int i;
488
489		for (i = 0; i < SECMAX; i++) {
490			if (args->sec_nums[i] == 0)
491				continue;
492			easprintf(&temp, " OR section = \'%d\'", i + 1);
493			if (section_clause) {
494				concat(&section_clause, temp);
495				free(temp);
496			} else {
497				section_clause = temp;
498			}
499		}
500		if (section_clause) {
501			/*
502			 * At least one section requested, add glue for query.
503			 */
504			temp = section_clause;
505			/* Skip " OR " before first term. */
506			easprintf(&section_clause, " AND (%s)", temp + 4);
507			free(temp);
508		}
509	}
510	if (args->nrec >= 0) {
511		/* Use the provided number of records and offset */
512		easprintf(&limit_clause, " LIMIT %d OFFSET %d",
513		    args->nrec, args->offset);
514	}
515
516	if (snippet_args == NULL) {
517		default_snippet_args[0] = "";
518		default_snippet_args[1] = "";
519		default_snippet_args[2] = "...";
520		snippet_args = default_snippet_args;
521	}
522	query = sqlite3_mprintf("SELECT section, name, name_desc, machine,"
523	    " snippet(mandb, %Q, %Q, %Q, -1, 40 ),"
524	    " rank_func(matchinfo(mandb, \"pclxn\")) AS rank"
525	    " FROM mandb"
526	    " WHERE mandb MATCH %Q %s "
527	    "%s"
528	    " ORDER BY rank DESC"
529	    "%s",
530	    snippet_args[0], snippet_args[1], snippet_args[2], args->search_str,
531	    machine_clause ? machine_clause : "",
532	    section_clause ? section_clause : "",
533	    limit_clause ? limit_clause : "");
534
535	free(machine_clause);
536	free(section_clause);
537	free(limit_clause);
538
539	if (query == NULL) {
540		*args->errmsg = estrdup("malloc failed");
541		return -1;
542	}
543	rc = sqlite3_prepare_v2(db, query, -1, &stmt, NULL);
544	if (rc == SQLITE_IOERR) {
545		warnx("Corrupt database. Please rerun makemandb");
546		sqlite3_free(query);
547		return -1;
548	} else if (rc != SQLITE_OK) {
549		warnx("%s", sqlite3_errmsg(db));
550		sqlite3_free(query);
551		return -1;
552	}
553
554	while (sqlite3_step(stmt) == SQLITE_ROW) {
555		section = (const char *) sqlite3_column_text(stmt, 0);
556		name_temp = (const char *) sqlite3_column_text(stmt, 1);
557		name_desc = (const char *) sqlite3_column_text(stmt, 2);
558		machine = (const char *) sqlite3_column_text(stmt, 3);
559		snippet = (const char *) sqlite3_column_text(stmt, 4);
560		if ((slash_ptr = strrchr(name_temp, '/')) != NULL)
561			name_temp = slash_ptr + 1;
562		if (machine && machine[0]) {
563			m = estrdup(machine);
564			easprintf(&name, "%s/%s", lower(m),
565				name_temp);
566			free(m);
567		} else {
568			name = estrdup((const char *) sqlite3_column_text(stmt, 1));
569		}
570
571		(args->callback)(args->callback_data, section, name, name_desc, snippet,
572			strlen(snippet));
573
574		free(name);
575	}
576
577	sqlite3_finalize(stmt);
578	sqlite3_free(query);
579	return *(args->errmsg) == NULL ? 0 : -1;
580}
581
582/*
583 * callback_html --
584 *  Callback function for run_query_html. It builds the html output and then
585 *  calls the actual user supplied callback function.
586 */
587static int
588callback_html(void *data, const char *section, const char *name,
589	const char *name_desc, const char *snippet, size_t snippet_length)
590{
591	const char *temp = snippet;
592	int i = 0;
593	size_t sz = 0;
594	int count = 0;
595	struct orig_callback_data *orig_data = (struct orig_callback_data *) data;
596	int (*callback) (void *, const char *, const char *, const char *,
597		const char *, size_t) = orig_data->callback;
598
599	/* First scan the snippet to find out the number of occurrences of {'>', '<'
600	 * '"', '&'}.
601	 * Then allocate a new buffer with sufficient space to be able to store the
602	 * quoted versions of the special characters {&gt;, &lt;, &quot;, &amp;}.
603	 * Copy over the characters from the original snippet to this buffer while
604	 * replacing the special characters with their quoted versions.
605	 */
606
607	while (*temp) {
608		sz = strcspn(temp, "<>\"&\002\003");
609		temp += sz + 1;
610		count++;
611	}
612	size_t qsnippet_length = snippet_length + count * 5;
613	char *qsnippet = emalloc(qsnippet_length + 1);
614	sz = 0;
615	while (*snippet) {
616		sz = strcspn(snippet, "<>\"&\002\003");
617		if (sz) {
618			memcpy(&qsnippet[i], snippet, sz);
619			snippet += sz;
620			i += sz;
621		}
622
623		switch (*snippet++) {
624		case '<':
625			memcpy(&qsnippet[i], "&lt;", 4);
626			i += 4;
627			break;
628		case '>':
629			memcpy(&qsnippet[i], "&gt;", 4);
630			i += 4;
631			break;
632		case '\"':
633			memcpy(&qsnippet[i], "&quot;", 6);
634			i += 6;
635			break;
636		case '&':
637			/* Don't perform the quoting if this & is part of an mdoc escape
638			 * sequence, e.g. \&
639			 */
640			if (i && *(snippet - 2) != '\\') {
641				memcpy(&qsnippet[i], "&amp;", 5);
642				i += 5;
643			} else {
644				qsnippet[i++] = '&';
645			}
646			break;
647		case '\002':
648			memcpy(&qsnippet[i], "<b>", 3);
649			i += 3;
650			break;
651		case '\003':
652			memcpy(&qsnippet[i], "</b>", 4);
653			i += 4;
654			break;
655		default:
656			break;
657		}
658	}
659	qsnippet[++i] = 0;
660	(*callback)(orig_data->data, section, name, name_desc,
661		(const char *)qsnippet,	qsnippet_length);
662	free(qsnippet);
663	return 0;
664}
665
666/*
667 * run_query_html --
668 *  Utility function to output query result in HTML format.
669 *  It internally calls run_query only, but it first passes the output to it's
670 *  own custom callback function, which preprocess the snippet for quoting
671 *  inline HTML fragments.
672 *  After that it delegates the call the actual user supplied callback function.
673 */
674int
675run_query_html(sqlite3 *db, query_args *args)
676{
677	struct orig_callback_data orig_data;
678	orig_data.callback = args->callback;
679	orig_data.data = args->callback_data;
680	const char *snippet_args[] = {"\002", "\003", "..."};
681	args->callback = &callback_html;
682	args->callback_data = (void *) &orig_data;
683	return run_query(db, snippet_args, args);
684}
685
686/*
687 * callback_pager --
688 *  A callback similar to callback_html. It overstrikes the matching text in
689 *  the snippet so that it appears emboldened when viewed using a pager like
690 *  more or less.
691 */
692static int
693callback_pager(void *data, const char *section, const char *name,
694	const char *name_desc, const char *snippet, size_t snippet_length)
695{
696	struct orig_callback_data *orig_data = (struct orig_callback_data *) data;
697	char *psnippet;
698	const char *temp = snippet;
699	int count = 0;
700	int i = 0;
701	size_t sz = 0;
702	size_t psnippet_length;
703
704	/* Count the number of bytes of matching text. For each of these bytes we
705	 * will use 2 extra bytes to overstrike it so that it appears bold when
706	 * viewed using a pager.
707	 */
708	while (*temp) {
709		sz = strcspn(temp, "\002\003");
710		temp += sz;
711		if (*temp == '\003') {
712			count += 2 * (sz);
713		}
714		temp++;
715	}
716
717	psnippet_length = snippet_length + count;
718	psnippet = emalloc(psnippet_length + 1);
719
720	/* Copy the bytes from snippet to psnippet:
721	 * 1. Copy the bytes before \002 as it is.
722	 * 2. The bytes after \002 need to be overstriked till we encounter \003.
723	 * 3. To overstrike a byte 'A' we need to write 'A\bA'
724	 */
725	while (*snippet) {
726		sz = strcspn(snippet, "\002");
727		memcpy(&psnippet[i], snippet, sz);
728		snippet += sz;
729		i += sz;
730
731		/* Don't change this. Advancing the pointer without reading the byte
732		 * is causing strange behavior.
733		 */
734		if (*snippet == '\002')
735			snippet++;
736		while (*snippet && *snippet != '\003') {
737			psnippet[i++] = *snippet;
738			psnippet[i++] = '\b';
739			psnippet[i++] = *snippet++;
740		}
741		if (*snippet)
742			snippet++;
743	}
744
745	psnippet[i] = 0;
746	(orig_data->callback)(orig_data->data, section, name, name_desc, psnippet,
747		psnippet_length);
748	free(psnippet);
749	return 0;
750}
751
752/*
753 * run_query_pager --
754 *  Utility function similar to run_query_html. This function tries to
755 *  pre-process the result assuming it will be piped to a pager.
756 *  For this purpose it first calls it's own callback function callback_pager
757 *  which then delegates the call to the user supplied callback.
758 */
759int run_query_pager(sqlite3 *db, query_args *args)
760{
761	struct orig_callback_data orig_data;
762	orig_data.callback = args->callback;
763	orig_data.data = args->callback_data;
764	const char *snippet_args[] = {"\002", "\003", "..."};
765	args->callback = &callback_pager;
766	args->callback_data = (void *) &orig_data;
767	return run_query(db, snippet_args, args);
768}
769