1/* 2 * Copyright 2017, Data61 3 * Commonwealth Scientific and Industrial Research Organisation (CSIRO) 4 * ABN 41 687 119 230. 5 * 6 * This software may be distributed and modified according to the terms of 7 * the BSD 2-Clause license. Note that NO WARRANTY is provided. 8 * See "LICENSE_BSD2.txt" for details. 9 * 10 * @TAG(DATA61_BSD) 11 */ 12 13/* Minimal SQLite linter. 14 * 15 * This program takes input consisting of a file containing an SQLite 16 * statement. It returns 0 if the file contains a valid statement or -2 if the 17 * file contains lexical errors. In the case of error, it will also print out a 18 * textual description of the error. 19 * 20 * To build: 21 * 22 * cc -W -Wall -Wextra -Werror -o sqlite-lint sqlite-lint.c -lsqlite3 23 * 24 * The functionality of this program may already exist in an off-the-shelf 25 * tool, but a cursory search at time of writing did not turn up anything 26 * suitable. 27 */ 28#include <errno.h> 29#include <fcntl.h> 30#include <sqlite3.h> 31#include <stddef.h> 32#include <stdio.h> 33#include <string.h> 34#include <sys/mman.h> 35#include <sys/stat.h> 36#include <sys/types.h> 37#include <unistd.h> 38 39int main(int argc, char **argv) { 40 int exit_code = -1; 41 42 if (argc != 2 || strcmp(argv[1], "--help") == 0) { 43 fprintf(stderr, "usage: %s filename\n" 44 " Check SQL statements in a file for correctness\n", 45 argv[0]); 46 return -1; 47 } 48 49 /* Measure the size of the input file. */ 50 struct stat st; 51 if (stat(argv[1], &st) != 0) { 52 fprintf(stderr, "failed to stat %s: ", argv[1]); 53 perror(NULL); 54 goto fail0; 55 } 56 size_t size = (size_t)st.st_size; 57 58 /* Open the input file. */ 59 int fd = open(argv[1], O_RDONLY); 60 if (fd == -1) { 61 fprintf(stderr, "failed to open %s: ", argv[1]); 62 perror(NULL); 63 goto fail0; 64 } 65 66 /* Mmap the input. We could read it into a buffer, but this would require 67 * dynamic memory allocations and other extra complexity. 68 */ 69 void *addr = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); 70 if (addr == MAP_FAILED) { 71 fprintf(stderr, "failed to mmap %s: ", argv[1]); 72 perror(NULL); 73 goto fail1; 74 } 75 76 /* Open an in-memory database. Note that we do not care about persistence 77 * of the database. 78 */ 79 sqlite3 *db = NULL; 80 if (sqlite3_open(":memory:", &db) != SQLITE_OK) { 81 perror("failed to open in-memory database"); 82 goto fail2; 83 } 84 85 /* Try to prepare the statement. This should fail if the input has any 86 * lexical errors. 87 */ 88 sqlite3_stmt *stmt; 89 int result = sqlite3_prepare_v2(db, addr, (int)size, &stmt, NULL); 90 if (result != SQLITE_OK) { 91 const char *msg = sqlite3_errmsg(db); 92 /* Suppress errors resulting from a missing table as these are not 93 * syntactic problems. 94 */ 95 if (result != SQLITE_ERROR || strncmp(msg, "no such table: ", 96 sizeof("no such table: ") - 1) != 0) { 97 fprintf(stderr, "failed to prepare SQL statement: %s\n", msg); 98 exit_code = -2; 99 goto fail3; 100 } 101 } 102 103 /* Success! */ 104 exit_code = 0; 105 106 sqlite3_finalize(stmt); 107fail3: sqlite3_close(db); 108fail2: munmap(addr, size); 109fail1: close(fd); 110fail0: return exit_code; 111} 112