math: array parameters can be NULL
[smatch.git] / sindex.c
blob22836a957213f7d16e1a8a49a781f41852e7b954
1 /*
2 * sindex - semantic indexer for C.
4 * Copyright (C) 2020 Alexey Gladkov
5 */
7 #define _GNU_SOURCE
8 #include <sys/types.h>
9 #include <sys/stat.h>
11 #include <unistd.h>
12 #include <limits.h>
13 #include <stdlib.h>
14 #include <string.h>
15 #include <fcntl.h>
16 #include <getopt.h>
17 #include <ctype.h>
18 #include <errno.h>
19 #include <sqlite3.h>
21 #include "dissect.h"
23 #define U_DEF (0x100 << U_SHIFT)
24 #define SINDEX_DATABASE_VERSION 1
26 #define message(fmt, ...) sindex_error(0, 0, (fmt), ##__VA_ARGS__)
28 static const char *progname;
29 static const char *sindex_command = NULL;
31 // common options
32 static const char *sindex_dbfile = "sindex.sqlite";
33 static int sindex_verbose = 0;
34 static char cwd[PATH_MAX];
35 static size_t n_cwd;
37 // 'add' command options
38 static struct string_list *sindex_filelist = NULL;
39 static int sindex_include_local_syms = 0;
41 struct sindex_streams {
42 sqlite3_int64 id;
45 static struct sindex_streams *sindex_streams = NULL;
46 static int sindex_streams_nr = 0;
48 // 'search' command options
49 static int sindex_search_modmask;
50 static int sindex_search_modmask_defined = 0;
51 static int sindex_search_kind = 0;
52 static char *sindex_search_path = NULL;
53 static char *sindex_search_symbol = NULL;
54 static const char *sindex_search_format = "(%m) %f\t%l\t%c\t%C\t%s";
56 #define EXPLAIN_LOCATION 1
57 #define USAGE_BY_LOCATION 2
58 static int sindex_search_by_location;
59 static char *sindex_search_filename;
60 static int sindex_search_line;
61 static int sindex_search_column;
63 static sqlite3 *sindex_db = NULL;
64 static sqlite3_stmt *lock_stmt = NULL;
65 static sqlite3_stmt *unlock_stmt = NULL;
66 static sqlite3_stmt *insert_rec_stmt = NULL;
67 static sqlite3_stmt *select_file_stmt = NULL;
68 static sqlite3_stmt *insert_file_stmt = NULL;
69 static sqlite3_stmt *delete_file_stmt = NULL;
71 struct command {
72 const char *name;
73 int dbflags;
74 void (*parse_cmdline)(int argc, char **argv);
75 void (*handler)(int argc, char **argv);
78 static void show_usage(void)
80 if (sindex_command)
81 printf("Try '%s %s --help' for more information.\n",
82 progname, sindex_command);
83 else
84 printf("Try '%s --help' for more information.\n",
85 progname);
86 exit(1);
89 static void show_help(int ret)
91 printf(
92 "Usage: %1$s [options]\n"
93 " or: %1$s [options] add [command options] [--] [compiler options] [files...]\n"
94 " or: %1$s [options] rm [command options] pattern\n"
95 " or: %1$s [options] search [command options] pattern\n"
96 "\n"
97 "These are common %1$s commands used in various situations:\n"
98 " add Generate or updates semantic index file for c-source code;\n"
99 " rm Remove files from the index by pattern;\n"
100 " search Make index queries.\n"
101 "\n"
102 "Options:\n"
103 " -D, --database=FILE Specify database file (default: %2$s);\n"
104 " -v, --verbose Show information about what is being done;\n"
105 " -h, --help Show this text and exit.\n"
106 "\n"
107 "Environment:\n"
108 " SINDEX_DATABASE Database file location.\n"
109 "\n"
110 "Report bugs to authors.\n"
111 "\n",
112 progname, sindex_dbfile);
113 exit(ret);
116 static void show_help_add(int ret)
118 printf(
119 "Usage: %1$s add [options] [--] [compiler options] files...\n"
120 "\n"
121 "Utility creates or updates a symbol index.\n"
122 "\n"
123 "Options:\n"
124 " --include-local-syms Include into the index local symbols;\n"
125 " -v, --verbose Show information about what is being done;\n"
126 " -h, --help Show this text and exit.\n"
127 "\n"
128 "Environment:\n"
129 " SINDEX_BASEDIRE Project top directory.\n"
130 "\n"
131 "Report bugs to authors.\n"
132 "\n",
133 progname);
134 exit(ret);
138 static void show_help_rm(int ret)
140 printf(
141 "Usage: %1$s rm [options] pattern\n"
142 "\n"
143 "Utility removes source files from the index.\n"
144 "The pattern is a glob(7) wildcard pattern.\n"
145 "\n"
146 "Options:\n"
147 " -v, --verbose Show information about what is being done;\n"
148 " -h, --help Show this text and exit.\n"
149 "\n"
150 "Report bugs to authors.\n"
151 "\n",
152 progname);
153 exit(ret);
156 static void show_help_search(int ret)
158 printf(
159 "Usage: %1$s search [options] [pattern]\n"
160 " or: %1$s search [options] (-e|-l) filename[:linenr[:column]]\n"
161 "\n"
162 "Utility searches information about symbol by pattern.\n"
163 "The pattern is a glob(7) wildcard pattern.\n"
164 "\n"
165 "Options:\n"
166 " -f, --format=STRING Specify an output format;\n"
167 " -p, --path=PATTERN Search symbols only in specified directories;\n"
168 " -m, --mode=MODE Search only the specified type of access;\n"
169 " -k, --kind=KIND Specify a kind of symbol;\n"
170 " -e, --explain Show what happens in the specified file position;\n"
171 " -l, --location Show usage of symbols from a specific file position;\n"
172 " -v, --verbose Show information about what is being done;\n"
173 " -h, --help Show this text and exit.\n"
174 "\n"
175 "The KIND can be one of the following: `s', `f', `v', `m'.\n"
176 "\n"
177 "Report bugs to authors.\n"
178 "\n",
179 progname);
180 exit(ret);
183 static void sindex_print_progname(void)
185 fprintf(stderr, "%s: ", progname);
186 if (sindex_command)
187 fprintf(stderr, "%s: ", sindex_command);
190 static void sindex_error(int status, int errnum, const char *fmt, ...)
192 va_list ap;
193 sindex_print_progname();
195 va_start(ap, fmt);
196 vfprintf(stderr, fmt, ap);
197 va_end(ap);
199 if (errnum > 0)
200 fprintf(stderr, ": %s", strerror(errnum));
202 fprintf(stderr, "\n");
204 if (status)
205 exit(status);
208 static void set_search_modmask(const char *v)
210 size_t n = strlen(v);
212 if (n != 1 && n != 3)
213 sindex_error(1, 0, "the length of mode value must be 1 or 3: %s", v);
215 sindex_search_modmask_defined = 1;
216 sindex_search_modmask = 0;
218 if (n == 1) {
219 switch (v[0]) {
220 case 'r': v = "rrr"; break;
221 case 'w': v = "ww-"; break;
222 case 'm': v = "mmm"; break;
223 case '-': v = "---"; break;
224 default: sindex_error(1, 0, "unknown modificator: %s", v);
226 } else if (!strcmp(v, "def")) {
227 sindex_search_modmask = U_DEF;
228 return;
231 static const int modes[] = {
232 U_R_AOF, U_W_AOF, U_R_AOF | U_W_AOF,
233 U_R_VAL, U_W_VAL, U_R_VAL | U_W_VAL,
234 U_R_PTR, U_W_PTR, U_R_PTR | U_W_PTR,
237 for (int i = 0; i < 3; i++) {
238 switch (v[i]) {
239 case 'r': sindex_search_modmask |= modes[i * 3]; break;
240 case 'w': sindex_search_modmask |= modes[i * 3 + 1]; break;
241 case 'm': sindex_search_modmask |= modes[i * 3 + 2]; break;
242 case '-': break;
243 default: sindex_error(1, 0,
244 "unknown modificator in the mode value"
245 " (`r', `w', `m' or `-' expected): %c", v[i]);
250 static void parse_cmdline(int argc, char **argv)
252 static const struct option long_options[] = {
253 { "database", required_argument, NULL, 'D' },
254 { "verbose", no_argument, NULL, 'v' },
255 { "help", no_argument, NULL, 'h' },
256 { NULL }
258 int c;
259 char *env;
261 if ((env = getenv("SINDEX_DATABASE")) != NULL)
262 sindex_dbfile = env;
264 while ((c = getopt_long(argc, argv, "+D:vh", long_options, NULL)) != -1) {
265 switch (c) {
266 case 'D':
267 sindex_dbfile = optarg;
268 break;
269 case 'v':
270 sindex_verbose++;
271 break;
272 case 'h':
273 show_help(0);
277 if (optind == argc) {
278 message("command required");
279 show_usage();
283 static void parse_cmdline_add(int argc, char **argv)
285 static const struct option long_options[] = {
286 { "include-local-syms", no_argument, NULL, 1 },
287 { "verbose", no_argument, NULL, 'v' },
288 { "help", no_argument, NULL, 'h' },
289 { NULL }
291 int c;
293 opterr = 0;
295 while ((c = getopt_long(argc, argv, "+vh", long_options, NULL)) != -1) {
296 switch (c) {
297 case 1:
298 sindex_include_local_syms = 1;
299 break;
300 case 'v':
301 sindex_verbose++;
302 break;
303 case 'h':
304 show_help_add(0);
305 case '?':
306 goto done;
309 done:
310 if (optind == argc) {
311 message("more arguments required");
312 show_usage();
315 // enforce tabstop
316 tabstop = 1;
318 // step back since sparse_initialize will ignore argv[0].
319 optind--;
321 sparse_initialize(argc - optind, argv + optind, &sindex_filelist);
324 static void parse_cmdline_rm(int argc, char **argv)
326 static const struct option long_options[] = {
327 { "verbose", no_argument, NULL, 'v' },
328 { "help", no_argument, NULL, 'h' },
329 { NULL }
331 int c;
333 while ((c = getopt_long(argc, argv, "+vh", long_options, NULL)) != -1) {
334 switch (c) {
335 case 'v':
336 sindex_verbose++;
337 break;
338 case 'h':
339 show_help_rm(0);
343 if (optind == argc) {
344 message("more arguments required");
345 show_usage();
349 static void parse_cmdline_search(int argc, char **argv)
351 static const struct option long_options[] = {
352 { "explain", no_argument, NULL, 'e' },
353 { "format", required_argument, NULL, 'f' },
354 { "path", required_argument, NULL, 'p' },
355 { "location", no_argument, NULL, 'l' },
356 { "mode", required_argument, NULL, 'm' },
357 { "kind", required_argument, NULL, 'k' },
358 { "verbose", no_argument, NULL, 'v' },
359 { "help", no_argument, NULL, 'h' },
360 { NULL }
362 int c;
364 while ((c = getopt_long(argc, argv, "+ef:m:k:p:lvh", long_options, NULL)) != -1) {
365 switch (c) {
366 case 'e':
367 sindex_search_by_location = EXPLAIN_LOCATION;
368 break;
369 case 'l':
370 sindex_search_by_location = USAGE_BY_LOCATION;
371 break;
372 case 'f':
373 sindex_search_format = optarg;
374 break;
375 case 'm':
376 set_search_modmask(optarg);
377 break;
378 case 'k':
379 sindex_search_kind = tolower(optarg[0]);
380 break;
381 case 'p':
382 sindex_search_path = optarg;
383 break;
384 case 'v':
385 sindex_verbose++;
386 break;
387 case 'h':
388 show_help_search(0);
392 if (sindex_search_by_location) {
393 char *str;
395 if (optind == argc)
396 sindex_error(1, 0, "one argument required");
398 str = argv[optind];
400 while (str) {
401 char *ptr;
403 if ((ptr = strchr(str, ':')) != NULL)
404 *ptr++ = '\0';
406 if (*str != '\0') {
407 if (!sindex_search_filename) {
408 sindex_search_filename = str;
409 } else if (!sindex_search_line) {
410 sindex_search_line = atoi(str);
411 } else if (!sindex_search_column) {
412 sindex_search_column = atoi(str);
415 str = ptr;
417 } else if (optind < argc)
418 sindex_search_symbol = argv[optind++];
421 static int query_appendf(sqlite3_str *query, const char *fmt, ...)
423 int status;
424 va_list args;
426 va_start(args, fmt);
427 sqlite3_str_vappendf(query, fmt, args);
428 va_end(args);
430 if ((status = sqlite3_str_errcode(query)) == SQLITE_OK)
431 return 0;
433 if (status == SQLITE_NOMEM)
434 message("not enough memory");
436 if (status == SQLITE_TOOBIG)
437 message("string too big");
439 return -1;
442 static inline void sqlite_bind_text(sqlite3_stmt *stmt, const char *field, const char *var, int len)
444 if (sqlite3_bind_text(stmt, sqlite3_bind_parameter_index(stmt, field), var, len, SQLITE_STATIC) != SQLITE_OK)
445 sindex_error(1, 0, "unable to bind value for %s: %s", field, sqlite3_errmsg(sindex_db));
448 static inline void sqlite_bind_int64(sqlite3_stmt *stmt, const char *field, long long var)
450 if (sqlite3_bind_int64(stmt, sqlite3_bind_parameter_index(stmt, field), var) != SQLITE_OK)
451 sindex_error(1, 0, "unable to bind value for %s: %s", field, sqlite3_errmsg(sindex_db));
454 static inline void sqlite_prepare(const char *sql, sqlite3_stmt **stmt)
456 int ret;
457 do {
458 ret = sqlite3_prepare_v2(sindex_db, sql, -1, stmt, NULL);
459 if (ret != SQLITE_OK && ret != SQLITE_BUSY)
460 sindex_error(1, 0, "unable to prepare query: %s: %s", sqlite3_errmsg(sindex_db), sql);
461 } while (ret == SQLITE_BUSY);
464 static inline void sqlite_prepare_persistent(const char *sql, sqlite3_stmt **stmt)
466 int ret;
467 do {
468 ret = sqlite3_prepare_v3(sindex_db, sql, -1, SQLITE_PREPARE_PERSISTENT, stmt, NULL);
469 if (ret != SQLITE_OK && ret != SQLITE_BUSY)
470 sindex_error(1, 0, "unable to prepare query: %s: %s", sqlite3_errmsg(sindex_db), sql);
471 } while (ret == SQLITE_BUSY);
474 static inline void sqlite_reset_stmt(sqlite3_stmt *stmt)
476 // Contrary to the intuition of many, sqlite3_reset() does not reset the
477 // bindings on a prepared statement. Use this routine to reset all host
478 // parameters to NULL.
479 sqlite3_clear_bindings(stmt);
480 sqlite3_reset(stmt);
483 static int sqlite_run(sqlite3_stmt *stmt)
485 int ret = sqlite3_step(stmt);
486 if (ret != SQLITE_DONE && ret != SQLITE_ROW)
487 sindex_error(1, 0, "unable to process query: %s: %s", sqlite3_errmsg(sindex_db), sqlite3_sql(stmt));
488 return ret;
491 static void sqlite_command(const char *sql)
493 sqlite3_stmt *stmt;
494 sqlite_prepare(sql, &stmt);
495 sqlite_run(stmt);
496 sqlite3_finalize(stmt);
499 static sqlite3_int64 get_db_version(void)
501 sqlite3_stmt *stmt;
502 sqlite3_int64 dbversion;
504 sqlite_prepare("PRAGMA user_version", &stmt);
505 sqlite_run(stmt);
506 dbversion = sqlite3_column_int64(stmt, 0);
507 sqlite3_finalize(stmt);
509 return dbversion;
512 static void set_db_version(void)
514 char *sql;
515 sqlite3_str *query = sqlite3_str_new(sindex_db);
517 if (query_appendf(query, "PRAGMA user_version = %d", SINDEX_DATABASE_VERSION) < 0)
518 exit(1);
520 sql = sqlite3_str_finish(query);
521 sqlite_command(sql);
522 sqlite3_free(sql);
525 static void open_temp_database(void)
527 static const char *database_schema[] = {
528 "ATTACH ':memory:' AS tempdb",
529 "CREATE TABLE tempdb.sindex ("
530 " file INTEGER NOT NULL,"
531 " line INTEGER NOT NULL,"
532 " column INTEGER NOT NULL,"
533 " symbol TEXT NOT NULL,"
534 " kind INTEGER NOT NULL,"
535 " context TEXT,"
536 " mode INTEGER NOT NULL"
537 ")",
538 NULL,
541 for (int i = 0; database_schema[i]; i++)
542 sqlite_command(database_schema[i]);
545 static void open_database(const char *filename, int flags)
547 static const char *database_schema[] = {
548 "CREATE TABLE file ("
549 " id INTEGER PRIMARY KEY AUTOINCREMENT,"
550 " name TEXT UNIQUE NOT NULL,"
551 " mtime INTEGER NOT NULL"
552 ")",
553 "CREATE TABLE sindex ("
554 " file INTEGER NOT NULL REFERENCES file(id) ON DELETE CASCADE,"
555 " line INTEGER NOT NULL,"
556 " column INTEGER NOT NULL,"
557 " symbol TEXT NOT NULL,"
558 " kind INTEGER NOT NULL,"
559 " context TEXT,"
560 " mode INTEGER NOT NULL"
561 ")",
562 "CREATE UNIQUE INDEX sindex_0 ON sindex (symbol, kind, mode, file, line, column)",
563 "CREATE INDEX sindex_1 ON sindex (file)",
564 NULL,
567 int exists = !access(filename, R_OK);
569 if (sqlite3_open_v2(filename, &sindex_db, flags, NULL) != SQLITE_OK)
570 sindex_error(1, 0, "unable to open database: %s: %s", filename, sqlite3_errmsg(sindex_db));
572 sqlite_command("PRAGMA journal_mode = WAL");
573 sqlite_command("PRAGMA synchronous = OFF");
574 sqlite_command("PRAGMA secure_delete = FAST");
575 sqlite_command("PRAGMA busy_timeout = 2147483647");
576 sqlite_command("PRAGMA foreign_keys = ON");
578 if (exists) {
579 if (get_db_version() < SINDEX_DATABASE_VERSION)
580 sindex_error(1, 0, "%s: Database too old. Please rebuild it.", filename);
581 return;
584 set_db_version();
586 for (int i = 0; database_schema[i]; i++)
587 sqlite_command(database_schema[i]);
590 struct index_record {
591 const char *context;
592 int ctx_len;
594 const char *symbol;
595 int sym_len;
597 int kind;
598 unsigned int mode;
599 long long mtime;
600 sqlite3_int64 file;
601 int line;
602 int col;
605 static void insert_record(struct index_record *rec)
607 sqlite_bind_text(insert_rec_stmt, "@context", rec->context, rec->ctx_len);
608 sqlite_bind_text(insert_rec_stmt, "@symbol", rec->symbol, rec->sym_len);
609 sqlite_bind_int64(insert_rec_stmt, "@kind", rec->kind);
610 sqlite_bind_int64(insert_rec_stmt, "@mode", rec->mode);
611 sqlite_bind_int64(insert_rec_stmt, "@file", rec->file);
612 sqlite_bind_int64(insert_rec_stmt, "@line", rec->line);
613 sqlite_bind_int64(insert_rec_stmt, "@column", rec->col);
614 sqlite_run(insert_rec_stmt);
615 sqlite_reset_stmt(insert_rec_stmt);
618 static void update_stream(void)
620 if (sindex_streams_nr >= input_stream_nr)
621 return;
623 sindex_streams = realloc(sindex_streams, input_stream_nr * sizeof(struct sindex_streams));
624 if (!sindex_streams)
625 sindex_error(1, errno, "realloc");
627 sqlite_run(lock_stmt);
629 for (int i = sindex_streams_nr; i < input_stream_nr; i++) {
630 struct stat st;
631 const char *filename;
632 char fullname[PATH_MAX];
633 sqlite3_int64 cur_mtime = 0;
635 if (input_streams[i].fd != -1) {
637 * FIXME: Files in the input_streams may be duplicated.
639 if (stat(input_streams[i].name, &st) < 0)
640 sindex_error(1, errno, "stat: %s", input_streams[i].name);
642 cur_mtime = st.st_mtime;
644 if (!realpath(input_streams[i].name, fullname))
645 sindex_error(1, errno, "realpath: %s", input_streams[i].name);
647 if (!strncmp(fullname, cwd, n_cwd) && fullname[n_cwd] == '/') {
648 filename = fullname + n_cwd + 1;
649 sindex_streams[i].id = 0;
650 } else {
651 sindex_streams[i].id = -1;
652 continue;
654 } else {
655 sindex_streams[i].id = -1;
656 continue;
659 if (sindex_verbose > 1)
660 message("filename: %s", filename);
662 sqlite_bind_text(select_file_stmt, "@name", filename, -1);
664 if (sqlite_run(select_file_stmt) == SQLITE_ROW) {
665 sqlite3_int64 old_mtime;
667 sindex_streams[i].id = sqlite3_column_int64(select_file_stmt, 0);
668 old_mtime = sqlite3_column_int64(select_file_stmt, 1);
670 sqlite_reset_stmt(select_file_stmt);
672 if (cur_mtime == old_mtime)
673 continue;
675 sqlite_bind_text(delete_file_stmt, "@name", filename, -1);
676 sqlite_run(delete_file_stmt);
677 sqlite_reset_stmt(delete_file_stmt);
680 sqlite_reset_stmt(select_file_stmt);
682 sqlite_bind_text(insert_file_stmt, "@name", filename, -1);
683 sqlite_bind_int64(insert_file_stmt, "@mtime", cur_mtime);
684 sqlite_run(insert_file_stmt);
685 sqlite_reset_stmt(insert_file_stmt);
687 sindex_streams[i].id = sqlite3_last_insert_rowid(sindex_db);
690 sqlite_run(unlock_stmt);
692 sindex_streams_nr = input_stream_nr;
695 static void r_symbol(unsigned mode, struct position *pos, struct symbol *sym)
697 static struct ident null;
698 struct ident *ctx = &null;
699 struct index_record rec;
701 update_stream();
703 if (sindex_streams[pos->stream].id == -1)
704 return;
706 if (!sindex_include_local_syms && sym_is_local(sym))
707 return;
709 if (!sym->ident) {
710 warning(*pos, "empty ident");
711 return;
714 if (dissect_ctx)
715 ctx = dissect_ctx->ident;
717 rec.context = ctx->name;
718 rec.ctx_len = ctx->len;
719 rec.symbol = sym->ident->name;
720 rec.sym_len = sym->ident->len;
721 rec.kind = sym->kind;
722 rec.mode = mode;
723 rec.file = sindex_streams[pos->stream].id;
724 rec.line = pos->line;
725 rec.col = pos->pos;
727 insert_record(&rec);
730 static void r_member(unsigned mode, struct position *pos, struct symbol *sym, struct symbol *mem)
732 static struct ident null;
733 static char memname[1024];
734 struct ident *ni, *si, *mi;
735 struct ident *ctx = &null;
736 struct index_record rec;
738 update_stream();
740 if (sindex_streams[pos->stream].id == -1)
741 return;
743 if (!sindex_include_local_syms && sym_is_local(sym))
744 return;
746 ni = built_in_ident("?");
747 si = sym->ident ?: ni;
748 /* mem == NULL means entire struct accessed */
749 mi = mem ? (mem->ident ?: ni) : built_in_ident("*");
751 if (dissect_ctx)
752 ctx = dissect_ctx->ident;
754 snprintf(memname, sizeof(memname), "%.*s.%.*s", si->len, si->name, mi->len, mi->name);
756 rec.context = ctx->name;
757 rec.ctx_len = ctx->len;
758 rec.symbol = memname;
759 rec.sym_len = si->len + mi->len + 1;
760 rec.kind = 'm';
761 rec.mode = mode;
762 rec.file = sindex_streams[pos->stream].id;
763 rec.line = pos->line;
764 rec.col = pos->pos;
766 insert_record(&rec);
769 static void r_symdef(struct symbol *sym)
771 r_symbol(U_DEF, &sym->pos, sym);
774 static void r_memdef(struct symbol *sym, struct symbol *mem)
776 r_member(U_DEF, &mem->pos, sym, mem);
779 static void command_add(int argc, char **argv)
781 static struct reporter reporter = {
782 .r_symdef = r_symdef,
783 .r_symbol = r_symbol,
784 .r_memdef = r_memdef,
785 .r_member = r_member,
788 open_temp_database();
790 sqlite_prepare_persistent(
791 "BEGIN IMMEDIATE",
792 &lock_stmt);
794 sqlite_prepare_persistent(
795 "COMMIT",
796 &unlock_stmt);
798 sqlite_prepare_persistent(
799 "INSERT OR IGNORE INTO tempdb.sindex "
800 "(context, symbol, kind, mode, file, line, column) "
801 "VALUES (@context, @symbol, @kind, @mode, @file, @line, @column)",
802 &insert_rec_stmt);
804 sqlite_prepare_persistent(
805 "SELECT id, mtime FROM file WHERE name == @name",
806 &select_file_stmt);
808 sqlite_prepare_persistent(
809 "INSERT INTO file (name, mtime) VALUES (@name, @mtime)",
810 &insert_file_stmt);
812 sqlite_prepare_persistent(
813 "DELETE FROM file WHERE name == @name",
814 &delete_file_stmt);
816 dissect(&reporter, sindex_filelist);
818 sqlite_run(lock_stmt);
819 sqlite_command("INSERT OR IGNORE INTO sindex SELECT * FROM tempdb.sindex");
820 sqlite_run(unlock_stmt);
822 sqlite3_finalize(insert_rec_stmt);
823 sqlite3_finalize(select_file_stmt);
824 sqlite3_finalize(insert_file_stmt);
825 sqlite3_finalize(delete_file_stmt);
826 sqlite3_finalize(lock_stmt);
827 sqlite3_finalize(unlock_stmt);
828 free(sindex_streams);
831 static void command_rm(int argc, char **argv)
833 sqlite3_stmt *stmt;
835 sqlite_command("BEGIN IMMEDIATE");
836 sqlite_prepare("DELETE FROM file WHERE name GLOB @file", &stmt);
838 if (sindex_verbose > 1)
839 message("SQL: %s", sqlite3_sql(stmt));
841 for (int i = 0; i < argc; i++) {
842 sqlite_bind_text(stmt, "@file", argv[i], -1);
843 sqlite_run(stmt);
844 sqlite_reset_stmt(stmt);
847 sqlite3_finalize(stmt);
848 sqlite_command("COMMIT");
851 static inline void print_mode(char *value)
853 char str[3];
854 int v = atoi(value);
856 if (v == U_DEF) {
857 printf("def");
858 return;
861 #define U(m) "-rwm"[(v / m) & 3]
862 str[0] = U(U_R_AOF);
863 str[1] = U(U_R_VAL);
864 str[2] = U(U_R_PTR);
866 printf("%.3s", str);
867 #undef U
870 static char *sindex_file_name;
871 static FILE *sindex_file_fd;
872 static int sindex_file_lnum;
873 static char *sindex_line;
874 static size_t sindex_line_buflen;
875 static int sindex_line_len;
877 static void print_file_line(const char *name, int lnum)
880 * All files are sorted by name and line number. So, we can reopen
881 * the file and read it line by line.
883 if (!sindex_file_name || strcmp(sindex_file_name, name)) {
884 if (sindex_file_fd) {
885 fclose(sindex_file_fd);
886 free(sindex_file_name);
889 sindex_file_name = strdup(name);
891 if (!sindex_file_name)
892 sindex_error(1, errno, "strdup");
894 sindex_file_fd = fopen(name, "r");
896 if (!sindex_file_fd)
897 sindex_error(1, errno, "fopen: %s", name);
899 sindex_file_lnum = 0;
902 do {
903 if (sindex_file_lnum == lnum) {
904 if (sindex_line[sindex_line_len-1] == '\n')
905 sindex_line_len--;
906 printf("%.*s", sindex_line_len, sindex_line);
907 break;
909 sindex_file_lnum++;
910 errno = 0;
911 } while((sindex_line_len = getline(&sindex_line, &sindex_line_buflen, sindex_file_fd)) != -1);
913 if (errno && errno != EOF)
914 sindex_error(1, errno, "getline");
917 static int search_query_callback(void *data, int argc, char **argv, char **colname)
919 char *fmt = (char *) sindex_search_format;
920 char buf[32];
921 int quote = 0;
922 int n = 0;
924 while (*fmt != '\0') {
925 char c = *fmt;
927 if (quote) {
928 quote = 0;
929 switch (c) {
930 case 't': c = '\t'; break;
931 case 'r': c = '\r'; break;
932 case 'n': c = '\n'; break;
934 } else if (c == '%') {
935 int colnum = 0;
936 char *pos = ++fmt;
938 c = *fmt;
940 if (c == '\0')
941 sindex_error(1, 0, "unexpected end of format string");
943 switch (c) {
944 case 'f': colnum = 0; goto print_string;
945 case 'l': colnum = 1; goto print_string;
946 case 'c': colnum = 2; goto print_string;
947 case 'C': colnum = 3; goto print_string;
948 case 'n': colnum = 4; goto print_string;
949 case 'm':
950 if (n) {
951 printf("%.*s", n, buf);
952 n = 0;
954 print_mode(argv[5]);
955 fmt++;
956 break;
957 case 'k':
958 if (n) {
959 printf("%.*s", n, buf);
960 n = 0;
962 printf("%c", atoi(argv[6]));
963 fmt++;
964 break;
965 case 's':
966 if (n) {
967 printf("%.*s", n, buf);
968 n = 0;
970 print_file_line(argv[0], atoi(argv[1]));
971 fmt++;
972 break;
974 print_string:
975 if (n) {
976 printf("%.*s", n, buf);
977 n = 0;
979 printf("%s", argv[colnum]);
980 fmt++;
981 break;
982 default:
983 break;
987 if (pos == fmt)
988 sindex_error(1, 0, "invalid format specification: %%%c", c);
990 continue;
991 } else if (c == '\\') {
992 quote = 1;
993 fmt++;
994 continue;
997 if (n == sizeof(buf)) {
998 printf("%.*s", n, buf);
999 n = 0;
1002 buf[n++] = c;
1003 fmt++;
1006 if (n)
1007 printf("%.*s", n, buf);
1008 printf("\n");
1010 return 0;
1013 static void command_search(int argc, char **argv)
1015 char *sql;
1016 char *dberr = NULL;
1017 sqlite3_str *query = sqlite3_str_new(sindex_db);
1019 if (query_appendf(query,
1020 "SELECT"
1021 " file.name,"
1022 " sindex.line,"
1023 " sindex.column,"
1024 " sindex.context,"
1025 " sindex.symbol,"
1026 " sindex.mode,"
1027 " sindex.kind "
1028 "FROM sindex, file "
1029 "WHERE sindex.file == file.id") < 0)
1030 goto fail;
1032 if (sindex_search_kind) {
1033 if (query_appendf(query, " AND sindex.kind == %d", sindex_search_kind) < 0)
1034 goto fail;
1037 if (sindex_search_symbol) {
1038 int ret;
1040 if (query_appendf(query, " AND ") < 0)
1041 goto fail;
1043 if (strpbrk(sindex_search_symbol, "*?[]"))
1044 ret = query_appendf(query, "sindex.symbol GLOB %Q", sindex_search_symbol);
1045 else
1046 ret = query_appendf(query, "sindex.symbol == %Q", sindex_search_symbol);
1048 if (ret < 0)
1049 goto fail;
1052 if (sindex_search_modmask_defined) {
1053 if (!sindex_search_modmask) {
1054 if (query_appendf(query, " AND sindex.mode == %d", sindex_search_modmask) < 0)
1055 goto fail;
1056 } else if (query_appendf(query, " AND (sindex.mode & %d) != 0", sindex_search_modmask) < 0)
1057 goto fail;
1060 if (sindex_search_path) {
1061 if (query_appendf(query, " AND file.name GLOB %Q", sindex_search_path) < 0)
1062 goto fail;
1065 if (sindex_search_by_location == EXPLAIN_LOCATION) {
1066 if (query_appendf(query, " AND file.name == %Q", sindex_search_filename) < 0)
1067 goto fail;
1068 if (sindex_search_line &&
1069 query_appendf(query, " AND sindex.line == %d", sindex_search_line) < 0)
1070 goto fail;
1071 if (sindex_search_column &&
1072 query_appendf(query, " AND sindex.column == %d", sindex_search_column) < 0)
1073 goto fail;
1074 } else if (sindex_search_by_location == USAGE_BY_LOCATION) {
1075 if (query_appendf(query, " AND sindex.symbol IN (") < 0)
1076 goto fail;
1077 if (query_appendf(query,
1078 "SELECT sindex.symbol FROM sindex, file WHERE"
1079 " sindex.file == file.id AND"
1080 " file.name == %Q", sindex_search_filename) < 0)
1081 goto fail;
1082 if (sindex_search_line &&
1083 query_appendf(query, " AND sindex.line == %d", sindex_search_line) < 0)
1084 goto fail;
1085 if (sindex_search_column &&
1086 query_appendf(query, " AND sindex.column == %d", sindex_search_column) < 0)
1087 goto fail;
1088 if (query_appendf(query, ")") < 0)
1089 goto fail;
1092 if (query_appendf(query, " ORDER BY file.name, sindex.line, sindex.column ASC", sindex_search_path) < 0)
1093 goto fail;
1095 sql = sqlite3_str_value(query);
1097 if (sindex_verbose > 1)
1098 message("SQL: %s", sql);
1100 sqlite3_exec(sindex_db, sql, search_query_callback, NULL, &dberr);
1101 if (dberr)
1102 sindex_error(1, 0, "sql query failed: %s", dberr);
1103 fail:
1104 sql = sqlite3_str_finish(query);
1105 sqlite3_free(sql);
1107 if (sindex_file_fd) {
1108 fclose(sindex_file_fd);
1109 free(sindex_file_name);
1111 free(sindex_line);
1115 int main(int argc, char **argv)
1117 static const struct command commands[] = {
1119 .name = "add",
1120 .dbflags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,
1121 .parse_cmdline = parse_cmdline_add,
1122 .handler = command_add
1125 .name = "rm",
1126 .dbflags = SQLITE_OPEN_READWRITE,
1127 .parse_cmdline = parse_cmdline_rm,
1128 .handler = command_rm
1131 .name = "search",
1132 .dbflags = SQLITE_OPEN_READONLY,
1133 .parse_cmdline = parse_cmdline_search,
1134 .handler = command_search
1136 { .name = NULL },
1138 const struct command *cmd;
1140 if (!(progname = rindex(argv[0], '/')))
1141 progname = argv[0];
1142 else
1143 progname++;
1145 if (!realpath(".", cwd))
1146 sindex_error(1, errno, "unable to get current directory");
1147 n_cwd = strlen(cwd);
1149 parse_cmdline(argc, argv);
1151 for (cmd = commands; cmd->name && strcmp(argv[optind], cmd->name); cmd++);
1152 if (!cmd->name)
1153 sindex_error(1, 0, "unknown command: %s", argv[optind]);
1154 optind++;
1156 sindex_command = cmd->name;
1158 if (cmd->parse_cmdline)
1159 cmd->parse_cmdline(argc, argv);
1161 open_database(sindex_dbfile, cmd->dbflags);
1162 cmd->handler(argc - optind, argv + optind);
1164 sqlite3_close(sindex_db);
1166 return 0;