units: fix the build
[smatch.git] / semind.c
blobad8003ba5911462e672a950bbb3ecb960adc8737
1 /*
2 * semind - 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, ...) semind_error(0, 0, (fmt), ##__VA_ARGS__)
28 static const char *progname;
29 static const char *semind_command = NULL;
31 // common options
32 static const char *semind_dbfile = "semind.sqlite";
33 static int semind_verbose = 0;
34 static char cwd[PATH_MAX];
35 static size_t n_cwd;
37 // 'add' command options
38 static struct string_list *semind_filelist = NULL;
39 static int semind_include_local_syms = 0;
41 struct semind_streams {
42 sqlite3_int64 id;
45 static struct semind_streams *semind_streams = NULL;
46 static int semind_streams_nr = 0;
48 // 'search' command options
49 static int semind_search_modmask;
50 static int semind_search_modmask_defined = 0;
51 static int semind_search_kind = 0;
52 static char *semind_search_path = NULL;
53 static char *semind_search_symbol = NULL;
54 static const char *semind_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 semind_search_by_location;
59 static char *semind_search_filename;
60 static int semind_search_line;
61 static int semind_search_column;
63 static sqlite3 *semind_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 (semind_command)
81 printf("Try '%s %s --help' for more information.\n",
82 progname, semind_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 " -B, --basedir=DIR Define project top directory (default is the current directory);\n"
105 " -v, --verbose Show information about what is being done;\n"
106 " -h, --help Show this text and exit.\n"
107 "\n"
108 "Environment:\n"
109 " SINDEX_DATABASE Database file location.\n"
110 " SINDEX_BASEDIR Project top directory.\n"
111 "\n"
112 "Report bugs to authors.\n"
113 "\n",
114 progname, semind_dbfile);
115 exit(ret);
118 static void show_help_add(int ret)
120 printf(
121 "Usage: %1$s add [options] [--] [compiler options] files...\n"
122 "\n"
123 "Utility creates or updates a symbol index.\n"
124 "\n"
125 "Options:\n"
126 " --include-local-syms Include into the index local symbols;\n"
127 " -v, --verbose Show information about what is being done;\n"
128 " -h, --help Show this text and exit.\n"
129 "\n"
130 "Report bugs to authors.\n"
131 "\n",
132 progname);
133 exit(ret);
137 static void show_help_rm(int ret)
139 printf(
140 "Usage: %1$s rm [options] pattern\n"
141 "\n"
142 "Utility removes source files from the index.\n"
143 "The pattern is a glob(7) wildcard pattern.\n"
144 "\n"
145 "Options:\n"
146 " -v, --verbose Show information about what is being done;\n"
147 " -h, --help Show this text and exit.\n"
148 "\n"
149 "Report bugs to authors.\n"
150 "\n",
151 progname);
152 exit(ret);
155 static void show_help_search(int ret)
157 printf(
158 "Usage: %1$s search [options] [pattern]\n"
159 " or: %1$s search [options] (-e|-l) filename[:linenr[:column]]\n"
160 "\n"
161 "Utility searches information about symbol by pattern.\n"
162 "The pattern is a glob(7) wildcard pattern.\n"
163 "\n"
164 "Options:\n"
165 " -f, --format=STRING Specify an output format;\n"
166 " -p, --path=PATTERN Search symbols only in specified directories;\n"
167 " -m, --mode=MODE Search only the specified type of access;\n"
168 " -k, --kind=KIND Specify a kind of symbol;\n"
169 " -e, --explain Show what happens in the specified file position;\n"
170 " -l, --location Show usage of symbols from a specific file position;\n"
171 " -v, --verbose Show information about what is being done;\n"
172 " -h, --help Show this text and exit.\n"
173 "\n"
174 "The KIND can be one of the following: `s', `f', `v', `m'.\n"
175 "\n"
176 "Report bugs to authors.\n"
177 "\n",
178 progname);
179 exit(ret);
182 static void semind_print_progname(void)
184 fprintf(stderr, "%s: ", progname);
185 if (semind_command)
186 fprintf(stderr, "%s: ", semind_command);
189 static void semind_error(int status, int errnum, const char *fmt, ...)
191 va_list ap;
192 semind_print_progname();
194 va_start(ap, fmt);
195 vfprintf(stderr, fmt, ap);
196 va_end(ap);
198 if (errnum > 0)
199 fprintf(stderr, ": %s", strerror(errnum));
201 fprintf(stderr, "\n");
203 if (status)
204 exit(status);
207 static void set_search_modmask(const char *v)
209 size_t n = strlen(v);
211 if (n != 1 && n != 3)
212 semind_error(1, 0, "the length of mode value must be 1 or 3: %s", v);
214 semind_search_modmask_defined = 1;
215 semind_search_modmask = 0;
217 if (n == 1) {
218 switch (v[0]) {
219 case 'r': v = "rrr"; break;
220 case 'w': v = "ww-"; break;
221 case 'm': v = "mmm"; break;
222 case '-': v = "---"; break;
223 default: semind_error(1, 0, "unknown modificator: %s", v);
225 } else if (!strcmp(v, "def")) {
226 semind_search_modmask = U_DEF;
227 return;
230 static const int modes[] = {
231 U_R_AOF, U_W_AOF, U_R_AOF | U_W_AOF,
232 U_R_VAL, U_W_VAL, U_R_VAL | U_W_VAL,
233 U_R_PTR, U_W_PTR, U_R_PTR | U_W_PTR,
236 for (int i = 0; i < 3; i++) {
237 switch (v[i]) {
238 case 'r': semind_search_modmask |= modes[i * 3]; break;
239 case 'w': semind_search_modmask |= modes[i * 3 + 1]; break;
240 case 'm': semind_search_modmask |= modes[i * 3 + 2]; break;
241 case '-': break;
242 default: semind_error(1, 0,
243 "unknown modificator in the mode value"
244 " (`r', `w', `m' or `-' expected): %c", v[i]);
249 static void parse_cmdline(int argc, char **argv)
251 static const struct option long_options[] = {
252 { "database", required_argument, NULL, 'D' },
253 { "basedir", required_argument, NULL, 'B' },
254 { "verbose", no_argument, NULL, 'v' },
255 { "help", no_argument, NULL, 'h' },
256 { NULL }
258 int c;
259 char *basedir = getenv("SINDEX_BASEDIR");
260 char *env;
262 if ((env = getenv("SINDEX_DATABASE")) != NULL)
263 semind_dbfile = env;
265 while ((c = getopt_long(argc, argv, "+B:D:vh", long_options, NULL)) != -1) {
266 switch (c) {
267 case 'D':
268 semind_dbfile = optarg;
269 break;
270 case 'B':
271 basedir = optarg;
272 break;
273 case 'v':
274 semind_verbose++;
275 break;
276 case 'h':
277 show_help(0);
281 if (optind == argc) {
282 message("command required");
283 show_usage();
286 if (basedir) {
287 if (!realpath(basedir, cwd))
288 semind_error(1, errno, "unable to get project base directory");
289 n_cwd = strlen(cwd);
293 static void parse_cmdline_add(int argc, char **argv)
295 static const struct option long_options[] = {
296 { "include-local-syms", no_argument, NULL, 1 },
297 { "verbose", no_argument, NULL, 'v' },
298 { "help", no_argument, NULL, 'h' },
299 { NULL }
301 int c;
303 opterr = 0;
305 while ((c = getopt_long(argc, argv, "+vh", long_options, NULL)) != -1) {
306 switch (c) {
307 case 1:
308 semind_include_local_syms = 1;
309 break;
310 case 'v':
311 semind_verbose++;
312 break;
313 case 'h':
314 show_help_add(0);
315 case '?':
316 goto done;
319 done:
320 if (optind == argc) {
321 message("more arguments required");
322 show_usage();
325 // enforce tabstop
326 tabstop = 1;
328 // step back since sparse_initialize will ignore argv[0].
329 optind--;
331 sparse_initialize(argc - optind, argv + optind, &semind_filelist);
332 dissect_show_all_symbols = 1;
335 static void parse_cmdline_rm(int argc, char **argv)
337 static const struct option long_options[] = {
338 { "verbose", no_argument, NULL, 'v' },
339 { "help", no_argument, NULL, 'h' },
340 { NULL }
342 int c;
344 while ((c = getopt_long(argc, argv, "+vh", long_options, NULL)) != -1) {
345 switch (c) {
346 case 'v':
347 semind_verbose++;
348 break;
349 case 'h':
350 show_help_rm(0);
354 if (optind == argc) {
355 message("more arguments required");
356 show_usage();
360 static void parse_cmdline_search(int argc, char **argv)
362 static const struct option long_options[] = {
363 { "explain", no_argument, NULL, 'e' },
364 { "format", required_argument, NULL, 'f' },
365 { "path", required_argument, NULL, 'p' },
366 { "location", no_argument, NULL, 'l' },
367 { "mode", required_argument, NULL, 'm' },
368 { "kind", required_argument, NULL, 'k' },
369 { "verbose", no_argument, NULL, 'v' },
370 { "help", no_argument, NULL, 'h' },
371 { NULL }
373 int c;
375 while ((c = getopt_long(argc, argv, "+ef:m:k:p:lvh", long_options, NULL)) != -1) {
376 switch (c) {
377 case 'e':
378 semind_search_by_location = EXPLAIN_LOCATION;
379 break;
380 case 'l':
381 semind_search_by_location = USAGE_BY_LOCATION;
382 break;
383 case 'f':
384 semind_search_format = optarg;
385 break;
386 case 'm':
387 set_search_modmask(optarg);
388 break;
389 case 'k':
390 semind_search_kind = tolower(optarg[0]);
391 break;
392 case 'p':
393 semind_search_path = optarg;
394 break;
395 case 'v':
396 semind_verbose++;
397 break;
398 case 'h':
399 show_help_search(0);
403 if (semind_search_by_location) {
404 char *str;
406 if (optind == argc)
407 semind_error(1, 0, "one argument required");
409 str = argv[optind];
411 while (str) {
412 char *ptr;
414 if ((ptr = strchr(str, ':')) != NULL)
415 *ptr++ = '\0';
417 if (*str != '\0') {
418 if (!semind_search_filename) {
419 semind_search_filename = str;
420 } else if (!semind_search_line) {
421 semind_search_line = atoi(str);
422 } else if (!semind_search_column) {
423 semind_search_column = atoi(str);
426 str = ptr;
428 } else if (optind < argc)
429 semind_search_symbol = argv[optind++];
432 static int query_appendf(sqlite3_str *query, const char *fmt, ...)
434 int status;
435 va_list args;
437 va_start(args, fmt);
438 sqlite3_str_vappendf(query, fmt, args);
439 va_end(args);
441 if ((status = sqlite3_str_errcode(query)) == SQLITE_OK)
442 return 0;
444 if (status == SQLITE_NOMEM)
445 message("not enough memory");
447 if (status == SQLITE_TOOBIG)
448 message("string too big");
450 return -1;
453 static inline void sqlite_bind_text(sqlite3_stmt *stmt, const char *field, const char *var, int len)
455 if (sqlite3_bind_text(stmt, sqlite3_bind_parameter_index(stmt, field), var, len, SQLITE_STATIC) != SQLITE_OK)
456 semind_error(1, 0, "unable to bind value for %s: %s", field, sqlite3_errmsg(semind_db));
459 static inline void sqlite_bind_int64(sqlite3_stmt *stmt, const char *field, long long var)
461 if (sqlite3_bind_int64(stmt, sqlite3_bind_parameter_index(stmt, field), var) != SQLITE_OK)
462 semind_error(1, 0, "unable to bind value for %s: %s", field, sqlite3_errmsg(semind_db));
465 static inline void sqlite_prepare(const char *sql, sqlite3_stmt **stmt)
467 int ret;
468 do {
469 ret = sqlite3_prepare_v2(semind_db, sql, -1, stmt, NULL);
470 if (ret != SQLITE_OK && ret != SQLITE_BUSY)
471 semind_error(1, 0, "unable to prepare query: %s: %s", sqlite3_errmsg(semind_db), sql);
472 } while (ret == SQLITE_BUSY);
475 static inline void sqlite_prepare_persistent(const char *sql, sqlite3_stmt **stmt)
477 int ret;
478 do {
479 ret = sqlite3_prepare_v3(semind_db, sql, -1, SQLITE_PREPARE_PERSISTENT, stmt, NULL);
480 if (ret != SQLITE_OK && ret != SQLITE_BUSY)
481 semind_error(1, 0, "unable to prepare query: %s: %s", sqlite3_errmsg(semind_db), sql);
482 } while (ret == SQLITE_BUSY);
485 static inline void sqlite_reset_stmt(sqlite3_stmt *stmt)
487 // Contrary to the intuition of many, sqlite3_reset() does not reset the
488 // bindings on a prepared statement. Use this routine to reset all host
489 // parameters to NULL.
490 sqlite3_clear_bindings(stmt);
491 sqlite3_reset(stmt);
494 static int sqlite_run(sqlite3_stmt *stmt)
496 int ret = sqlite3_step(stmt);
497 if (ret != SQLITE_DONE && ret != SQLITE_ROW)
498 semind_error(1, 0, "unable to process query: %s: %s", sqlite3_errmsg(semind_db), sqlite3_sql(stmt));
499 return ret;
502 static void sqlite_command(const char *sql)
504 sqlite3_stmt *stmt;
505 sqlite_prepare(sql, &stmt);
506 sqlite_run(stmt);
507 sqlite3_finalize(stmt);
510 static sqlite3_int64 get_db_version(void)
512 sqlite3_stmt *stmt;
513 sqlite3_int64 dbversion;
515 sqlite_prepare("PRAGMA user_version", &stmt);
516 sqlite_run(stmt);
517 dbversion = sqlite3_column_int64(stmt, 0);
518 sqlite3_finalize(stmt);
520 return dbversion;
523 static void set_db_version(void)
525 char *sql;
526 sqlite3_str *query = sqlite3_str_new(semind_db);
528 if (query_appendf(query, "PRAGMA user_version = %d", SINDEX_DATABASE_VERSION) < 0)
529 exit(1);
531 sql = sqlite3_str_finish(query);
532 sqlite_command(sql);
533 sqlite3_free(sql);
536 static void open_temp_database(void)
538 static const char *database_schema[] = {
539 "ATTACH ':memory:' AS tempdb",
540 "CREATE TABLE tempdb.semind ("
541 " file INTEGER NOT NULL,"
542 " line INTEGER NOT NULL,"
543 " column INTEGER NOT NULL,"
544 " symbol TEXT NOT NULL,"
545 " kind INTEGER NOT NULL,"
546 " context TEXT,"
547 " mode INTEGER NOT NULL"
548 ")",
549 NULL,
552 for (int i = 0; database_schema[i]; i++)
553 sqlite_command(database_schema[i]);
556 static void open_database(const char *filename, int flags)
558 static const char *database_schema[] = {
559 "CREATE TABLE file ("
560 " id INTEGER PRIMARY KEY AUTOINCREMENT,"
561 " name TEXT UNIQUE NOT NULL,"
562 " mtime INTEGER NOT NULL"
563 ")",
564 "CREATE TABLE semind ("
565 " file INTEGER NOT NULL REFERENCES file(id) ON DELETE CASCADE,"
566 " line INTEGER NOT NULL,"
567 " column INTEGER NOT NULL,"
568 " symbol TEXT NOT NULL,"
569 " kind INTEGER NOT NULL,"
570 " context TEXT,"
571 " mode INTEGER NOT NULL"
572 ")",
573 "CREATE UNIQUE INDEX semind_0 ON semind (symbol, kind, mode, file, line, column)",
574 "CREATE INDEX semind_1 ON semind (file)",
575 NULL,
578 int exists = !access(filename, R_OK);
580 if (sqlite3_open_v2(filename, &semind_db, flags, NULL) != SQLITE_OK)
581 semind_error(1, 0, "unable to open database: %s: %s", filename, sqlite3_errmsg(semind_db));
583 sqlite_command("PRAGMA journal_mode = WAL");
584 sqlite_command("PRAGMA synchronous = OFF");
585 sqlite_command("PRAGMA secure_delete = FAST");
586 sqlite_command("PRAGMA busy_timeout = 2147483647");
587 sqlite_command("PRAGMA foreign_keys = ON");
589 if (exists) {
590 if (get_db_version() < SINDEX_DATABASE_VERSION)
591 semind_error(1, 0, "%s: Database too old. Please rebuild it.", filename);
592 return;
595 set_db_version();
597 for (int i = 0; database_schema[i]; i++)
598 sqlite_command(database_schema[i]);
601 struct index_record {
602 const char *context;
603 int ctx_len;
605 const char *symbol;
606 int sym_len;
608 int kind;
609 unsigned int mode;
610 long long mtime;
611 sqlite3_int64 file;
612 int line;
613 int col;
616 static void insert_record(struct index_record *rec)
618 sqlite_bind_text(insert_rec_stmt, "@context", rec->context, rec->ctx_len);
619 sqlite_bind_text(insert_rec_stmt, "@symbol", rec->symbol, rec->sym_len);
620 sqlite_bind_int64(insert_rec_stmt, "@kind", rec->kind);
621 sqlite_bind_int64(insert_rec_stmt, "@mode", rec->mode);
622 sqlite_bind_int64(insert_rec_stmt, "@file", rec->file);
623 sqlite_bind_int64(insert_rec_stmt, "@line", rec->line);
624 sqlite_bind_int64(insert_rec_stmt, "@column", rec->col);
625 sqlite_run(insert_rec_stmt);
626 sqlite_reset_stmt(insert_rec_stmt);
629 static void update_stream(void)
631 if (semind_streams_nr >= input_stream_nr)
632 return;
634 semind_streams = realloc(semind_streams, input_stream_nr * sizeof(struct semind_streams));
635 if (!semind_streams)
636 semind_error(1, errno, "realloc");
638 sqlite_run(lock_stmt);
640 for (int i = semind_streams_nr; i < input_stream_nr; i++) {
641 struct stat st;
642 const char *filename;
643 char fullname[PATH_MAX];
644 sqlite3_int64 cur_mtime = 0;
646 if (input_streams[i].fd != -1) {
648 * FIXME: Files in the input_streams may be duplicated.
650 if (stat(input_streams[i].name, &st) < 0)
651 semind_error(1, errno, "stat: %s", input_streams[i].name);
653 cur_mtime = st.st_mtime;
655 if (!realpath(input_streams[i].name, fullname))
656 semind_error(1, errno, "realpath: %s", input_streams[i].name);
658 if (!strncmp(fullname, cwd, n_cwd) && fullname[n_cwd] == '/') {
659 filename = fullname + n_cwd + 1;
660 semind_streams[i].id = 0;
661 } else {
662 semind_streams[i].id = -1;
663 continue;
665 } else {
666 semind_streams[i].id = -1;
667 continue;
670 if (semind_verbose > 1)
671 message("filename: %s", filename);
673 sqlite_bind_text(select_file_stmt, "@name", filename, -1);
675 if (sqlite_run(select_file_stmt) == SQLITE_ROW) {
676 sqlite3_int64 old_mtime;
678 semind_streams[i].id = sqlite3_column_int64(select_file_stmt, 0);
679 old_mtime = sqlite3_column_int64(select_file_stmt, 1);
681 sqlite_reset_stmt(select_file_stmt);
683 if (cur_mtime == old_mtime)
684 continue;
686 sqlite_bind_text(delete_file_stmt, "@name", filename, -1);
687 sqlite_run(delete_file_stmt);
688 sqlite_reset_stmt(delete_file_stmt);
691 sqlite_reset_stmt(select_file_stmt);
693 sqlite_bind_text(insert_file_stmt, "@name", filename, -1);
694 sqlite_bind_int64(insert_file_stmt, "@mtime", cur_mtime);
695 sqlite_run(insert_file_stmt);
696 sqlite_reset_stmt(insert_file_stmt);
698 semind_streams[i].id = sqlite3_last_insert_rowid(semind_db);
701 sqlite_run(unlock_stmt);
703 semind_streams_nr = input_stream_nr;
706 static void r_symbol(unsigned mode, struct position *pos, struct symbol *sym)
708 static struct ident null;
709 struct ident *ctx = &null;
710 struct index_record rec;
712 update_stream();
714 if (semind_streams[pos->stream].id == -1)
715 return;
717 if (!semind_include_local_syms && sym_is_local(sym))
718 return;
720 if (!sym->ident) {
721 warning(*pos, "empty ident");
722 return;
725 if (dissect_ctx)
726 ctx = dissect_ctx->ident;
728 rec.context = ctx->name;
729 rec.ctx_len = ctx->len;
730 rec.symbol = sym->ident->name;
731 rec.sym_len = sym->ident->len;
732 rec.kind = sym->kind;
733 rec.mode = mode;
734 rec.file = semind_streams[pos->stream].id;
735 rec.line = pos->line;
736 rec.col = pos->pos;
738 insert_record(&rec);
741 static void r_member(unsigned mode, struct position *pos, struct symbol *sym, struct symbol *mem)
743 static struct ident null;
744 static char memname[1024];
745 struct ident *ni, *si, *mi;
746 struct ident *ctx = &null;
747 struct index_record rec;
749 update_stream();
751 if (semind_streams[pos->stream].id == -1)
752 return;
754 if (!semind_include_local_syms && sym_is_local(sym))
755 return;
757 ni = built_in_ident("?");
758 si = sym->ident ?: ni;
759 /* mem == NULL means entire struct accessed */
760 mi = mem ? (mem->ident ?: ni) : built_in_ident("*");
762 if (dissect_ctx)
763 ctx = dissect_ctx->ident;
765 snprintf(memname, sizeof(memname), "%.*s.%.*s", si->len, si->name, mi->len, mi->name);
767 rec.context = ctx->name;
768 rec.ctx_len = ctx->len;
769 rec.symbol = memname;
770 rec.sym_len = si->len + mi->len + 1;
771 rec.kind = 'm';
772 rec.mode = mode;
773 rec.file = semind_streams[pos->stream].id;
774 rec.line = pos->line;
775 rec.col = pos->pos;
777 insert_record(&rec);
780 static void r_symdef(struct symbol *sym)
782 r_symbol(U_DEF, &sym->pos, sym);
785 static void r_memdef(struct symbol *sym, struct symbol *mem)
787 r_member(U_DEF, &mem->pos, sym, mem);
790 static void command_add(int argc, char **argv)
792 static struct reporter reporter = {
793 .r_symdef = r_symdef,
794 .r_symbol = r_symbol,
795 .r_memdef = r_memdef,
796 .r_member = r_member,
799 open_temp_database();
801 sqlite_prepare_persistent(
802 "BEGIN IMMEDIATE",
803 &lock_stmt);
805 sqlite_prepare_persistent(
806 "COMMIT",
807 &unlock_stmt);
809 sqlite_prepare_persistent(
810 "INSERT OR IGNORE INTO tempdb.semind "
811 "(context, symbol, kind, mode, file, line, column) "
812 "VALUES (@context, @symbol, @kind, @mode, @file, @line, @column)",
813 &insert_rec_stmt);
815 sqlite_prepare_persistent(
816 "SELECT id, mtime FROM file WHERE name == @name",
817 &select_file_stmt);
819 sqlite_prepare_persistent(
820 "INSERT INTO file (name, mtime) VALUES (@name, @mtime)",
821 &insert_file_stmt);
823 sqlite_prepare_persistent(
824 "DELETE FROM file WHERE name == @name",
825 &delete_file_stmt);
827 dissect(&reporter, semind_filelist);
829 sqlite_run(lock_stmt);
830 sqlite_command("INSERT OR IGNORE INTO semind SELECT * FROM tempdb.semind");
831 sqlite_run(unlock_stmt);
833 sqlite3_finalize(insert_rec_stmt);
834 sqlite3_finalize(select_file_stmt);
835 sqlite3_finalize(insert_file_stmt);
836 sqlite3_finalize(delete_file_stmt);
837 sqlite3_finalize(lock_stmt);
838 sqlite3_finalize(unlock_stmt);
839 free(semind_streams);
842 static void command_rm(int argc, char **argv)
844 sqlite3_stmt *stmt;
846 sqlite_command("BEGIN IMMEDIATE");
847 sqlite_prepare("DELETE FROM file WHERE name GLOB @file", &stmt);
849 if (semind_verbose > 1)
850 message("SQL: %s", sqlite3_sql(stmt));
852 for (int i = 0; i < argc; i++) {
853 sqlite_bind_text(stmt, "@file", argv[i], -1);
854 sqlite_run(stmt);
855 sqlite_reset_stmt(stmt);
858 sqlite3_finalize(stmt);
859 sqlite_command("COMMIT");
862 static inline void print_mode(char *value)
864 char str[3];
865 int v = atoi(value);
867 if (v == U_DEF) {
868 printf("def");
869 return;
872 #define U(m) "-rwm"[(v / m) & 3]
873 str[0] = U(U_R_AOF);
874 str[1] = U(U_R_VAL);
875 str[2] = U(U_R_PTR);
877 printf("%.3s", str);
878 #undef U
881 static char *semind_file_name;
882 static FILE *semind_file_fd;
883 static int semind_file_lnum;
884 static char *semind_line;
885 static size_t semind_line_buflen;
886 static int semind_line_len;
888 static void print_file_line(const char *name, int lnum)
891 * All files are sorted by name and line number. So, we can reopen
892 * the file and read it line by line.
894 if (!semind_file_name || strcmp(semind_file_name, name)) {
895 if (semind_file_fd) {
896 fclose(semind_file_fd);
897 free(semind_file_name);
900 semind_file_name = strdup(name);
902 if (!semind_file_name)
903 semind_error(1, errno, "strdup");
905 semind_file_fd = fopen(name, "r");
907 if (!semind_file_fd)
908 semind_error(1, errno, "fopen: %s", name);
910 semind_file_lnum = 0;
913 do {
914 if (semind_file_lnum == lnum) {
915 if (semind_line[semind_line_len-1] == '\n')
916 semind_line_len--;
917 printf("%.*s", semind_line_len, semind_line);
918 break;
920 semind_file_lnum++;
921 errno = 0;
922 } while((semind_line_len = getline(&semind_line, &semind_line_buflen, semind_file_fd)) != -1);
924 if (errno && errno != EOF)
925 semind_error(1, errno, "getline");
928 static int search_query_callback(void *data, int argc, char **argv, char **colname)
930 char *fmt = (char *) semind_search_format;
931 char buf[32];
932 int quote = 0;
933 int n = 0;
935 while (*fmt != '\0') {
936 char c = *fmt;
938 if (quote) {
939 quote = 0;
940 switch (c) {
941 case 't': c = '\t'; break;
942 case 'r': c = '\r'; break;
943 case 'n': c = '\n'; break;
945 } else if (c == '%') {
946 int colnum = 0;
947 char *pos = ++fmt;
949 c = *fmt;
951 if (c == '\0')
952 semind_error(1, 0, "unexpected end of format string");
954 switch (c) {
955 case 'f': colnum = 0; goto print_string;
956 case 'l': colnum = 1; goto print_string;
957 case 'c': colnum = 2; goto print_string;
958 case 'C': colnum = 3; goto print_string;
959 case 'n': colnum = 4; goto print_string;
960 case 'm':
961 if (n) {
962 printf("%.*s", n, buf);
963 n = 0;
965 print_mode(argv[5]);
966 fmt++;
967 break;
968 case 'k':
969 if (n) {
970 printf("%.*s", n, buf);
971 n = 0;
973 printf("%c", atoi(argv[6]));
974 fmt++;
975 break;
976 case 's':
977 if (n) {
978 printf("%.*s", n, buf);
979 n = 0;
981 print_file_line(argv[0], atoi(argv[1]));
982 fmt++;
983 break;
985 print_string:
986 if (n) {
987 printf("%.*s", n, buf);
988 n = 0;
990 printf("%s", argv[colnum]);
991 fmt++;
992 break;
993 default:
994 break;
998 if (pos == fmt)
999 semind_error(1, 0, "invalid format specification: %%%c", c);
1001 continue;
1002 } else if (c == '\\') {
1003 quote = 1;
1004 fmt++;
1005 continue;
1008 if (n == sizeof(buf)) {
1009 printf("%.*s", n, buf);
1010 n = 0;
1013 buf[n++] = c;
1014 fmt++;
1017 if (n)
1018 printf("%.*s", n, buf);
1019 printf("\n");
1021 return 0;
1024 static void command_search(int argc, char **argv)
1026 char *sql;
1027 char *dberr = NULL;
1028 sqlite3_str *query = sqlite3_str_new(semind_db);
1030 if (chdir(cwd) < 0)
1031 semind_error(1, errno, "unable to change directory: %s", cwd);
1033 if (query_appendf(query,
1034 "SELECT"
1035 " file.name,"
1036 " semind.line,"
1037 " semind.column,"
1038 " semind.context,"
1039 " semind.symbol,"
1040 " semind.mode,"
1041 " semind.kind "
1042 "FROM semind, file "
1043 "WHERE semind.file == file.id") < 0)
1044 goto fail;
1046 if (semind_search_kind) {
1047 if (query_appendf(query, " AND semind.kind == %d", semind_search_kind) < 0)
1048 goto fail;
1051 if (semind_search_symbol) {
1052 int ret;
1054 if (query_appendf(query, " AND ") < 0)
1055 goto fail;
1057 if (strpbrk(semind_search_symbol, "*?[]"))
1058 ret = query_appendf(query, "semind.symbol GLOB %Q", semind_search_symbol);
1059 else
1060 ret = query_appendf(query, "semind.symbol == %Q", semind_search_symbol);
1062 if (ret < 0)
1063 goto fail;
1066 if (semind_search_modmask_defined) {
1067 if (!semind_search_modmask) {
1068 if (query_appendf(query, " AND semind.mode == %d", semind_search_modmask) < 0)
1069 goto fail;
1070 } else if (query_appendf(query, " AND (semind.mode & %d) != 0", semind_search_modmask) < 0)
1071 goto fail;
1074 if (semind_search_path) {
1075 if (query_appendf(query, " AND file.name GLOB %Q", semind_search_path) < 0)
1076 goto fail;
1079 if (semind_search_by_location == EXPLAIN_LOCATION) {
1080 if (query_appendf(query, " AND file.name == %Q", semind_search_filename) < 0)
1081 goto fail;
1082 if (semind_search_line &&
1083 query_appendf(query, " AND semind.line == %d", semind_search_line) < 0)
1084 goto fail;
1085 if (semind_search_column &&
1086 query_appendf(query, " AND semind.column == %d", semind_search_column) < 0)
1087 goto fail;
1088 } else if (semind_search_by_location == USAGE_BY_LOCATION) {
1089 if (query_appendf(query, " AND semind.symbol IN (") < 0)
1090 goto fail;
1091 if (query_appendf(query,
1092 "SELECT semind.symbol FROM semind, file WHERE"
1093 " semind.file == file.id AND"
1094 " file.name == %Q", semind_search_filename) < 0)
1095 goto fail;
1096 if (semind_search_line &&
1097 query_appendf(query, " AND semind.line == %d", semind_search_line) < 0)
1098 goto fail;
1099 if (semind_search_column &&
1100 query_appendf(query, " AND semind.column == %d", semind_search_column) < 0)
1101 goto fail;
1102 if (query_appendf(query, ")") < 0)
1103 goto fail;
1106 if (query_appendf(query, " ORDER BY file.name, semind.line, semind.column ASC", semind_search_path) < 0)
1107 goto fail;
1109 sql = sqlite3_str_value(query);
1111 if (semind_verbose > 1)
1112 message("SQL: %s", sql);
1114 sqlite3_exec(semind_db, sql, search_query_callback, NULL, &dberr);
1115 if (dberr)
1116 semind_error(1, 0, "sql query failed: %s", dberr);
1117 fail:
1118 sql = sqlite3_str_finish(query);
1119 sqlite3_free(sql);
1121 if (semind_file_fd) {
1122 fclose(semind_file_fd);
1123 free(semind_file_name);
1125 free(semind_line);
1129 int main(int argc, char **argv)
1131 static const struct command commands[] = {
1133 .name = "add",
1134 .dbflags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,
1135 .parse_cmdline = parse_cmdline_add,
1136 .handler = command_add
1139 .name = "rm",
1140 .dbflags = SQLITE_OPEN_READWRITE,
1141 .parse_cmdline = parse_cmdline_rm,
1142 .handler = command_rm
1145 .name = "search",
1146 .dbflags = SQLITE_OPEN_READONLY,
1147 .parse_cmdline = parse_cmdline_search,
1148 .handler = command_search
1150 { .name = NULL },
1152 const struct command *cmd;
1154 if (!(progname = rindex(argv[0], '/')))
1155 progname = argv[0];
1156 else
1157 progname++;
1159 if (!realpath(".", cwd))
1160 semind_error(1, errno, "unable to get current directory");
1161 n_cwd = strlen(cwd);
1163 parse_cmdline(argc, argv);
1165 for (cmd = commands; cmd->name && strcmp(argv[optind], cmd->name); cmd++);
1166 if (!cmd->name)
1167 semind_error(1, 0, "unknown command: %s", argv[optind]);
1168 optind++;
1170 semind_command = cmd->name;
1172 if (cmd->parse_cmdline)
1173 cmd->parse_cmdline(argc, argv);
1175 open_database(semind_dbfile, cmd->dbflags);
1176 cmd->handler(argc - optind, argv + optind);
1178 sqlite3_close(semind_db);
1180 return 0;