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