]> git.decadent.org.uk Git - nfs-utils.git/blob - utils/nfsdcld/sqlite.c
nfsdcld: add routines for a sqlite backend database
[nfs-utils.git] / utils / nfsdcld / sqlite.c
1 /*
2  * Copyright (C) 2011  Red Hat, Jeff Layton <jlayton@redhat.com>
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19
20 /*
21  * Explanation:
22  *
23  * This file contains the code to manage the sqlite backend database for the
24  * clstated upcall daemon.
25  *
26  * The main database is called main.sqlite and contains the following tables:
27  *
28  * parameters: simple key/value pairs for storing database info
29  *
30  * clients: one column containing a BLOB with the as sent by the client
31  *          and a timestamp (in epoch seconds) of when the record was
32  *          established
33  *
34  * FIXME: should we also record the fsid being accessed?
35  */
36
37 #ifdef HAVE_CONFIG_H
38 #include "config.h"
39 #endif /* HAVE_CONFIG_H */
40
41 #include <errno.h>
42 #include <event.h>
43 #include <stdbool.h>
44 #include <string.h>
45 #include <sys/stat.h>
46 #include <sys/types.h>
47 #include <fcntl.h>
48 #include <unistd.h>
49 #include <sqlite3.h>
50 #include <linux/limits.h>
51
52 #include "xlog.h"
53
54 #define CLD_SQLITE_SCHEMA_VERSION 1
55
56 #ifndef CLD_SQLITE_TOPDIR
57 #define CLD_SQLITE_TOPDIR NFS_STATEDIR "/nfsdcld"
58 #endif
59
60 /* in milliseconds */
61 #define CLD_SQLITE_BUSY_TIMEOUT 10000
62
63 /* private data structures */
64
65 /* global variables */
66
67 /* top level DB directory */
68 static char *sqlite_topdir;
69
70 /* reusable pathname and sql command buffer */
71 static char buf[PATH_MAX];
72
73 /* global database handle */
74 static sqlite3 *dbh;
75
76 /* forward declarations */
77
78 /* make a directory, ignoring EEXIST errors unless it's not a directory */
79 static int
80 mkdir_if_not_exist(char *dirname)
81 {
82         int ret;
83         struct stat statbuf;
84
85         ret = mkdir(dirname, S_IRWXU);
86         if (ret && errno != EEXIST)
87                 return -errno;
88
89         ret = stat(dirname, &statbuf);
90         if (ret)
91                 return -errno;
92
93         if (!S_ISDIR(statbuf.st_mode))
94                 ret = -ENOTDIR;
95
96         return ret;
97 }
98
99 /*
100  * Open the "main" database, and attempt to initialize it by creating the
101  * parameters table and inserting the schema version into it. Ignore any errors
102  * from that, and then attempt to select the version out of it again. If the
103  * version appears wrong, then assume that the DB is corrupt or has been
104  * upgraded, and return an error. If all of that works, then attempt to create
105  * the "clients" table.
106  */
107 int
108 sqlite_maindb_init(char *topdir)
109 {
110         int ret;
111         char *err = NULL;
112         sqlite3_stmt *stmt = NULL;
113
114         sqlite_topdir = topdir ? topdir : CLD_SQLITE_TOPDIR;
115
116         ret = mkdir_if_not_exist(sqlite_topdir);
117         if (ret)
118                 return ret;
119
120         ret = snprintf(buf, PATH_MAX - 1, "%s/main.sqlite", sqlite_topdir);
121         if (ret < 0)
122                 return ret;
123
124         buf[PATH_MAX - 1] = '\0';
125
126         ret = sqlite3_open(buf, &dbh);
127         if (ret != SQLITE_OK) {
128                 xlog(L_ERROR, "Unable to open main database: %d", ret);
129                 return ret;
130         }
131
132         ret = sqlite3_busy_timeout(dbh, CLD_SQLITE_BUSY_TIMEOUT);
133         if (ret != SQLITE_OK) {
134                 xlog(L_ERROR, "Unable to set sqlite busy timeout: %d", ret);
135                 goto out_err;
136         }
137
138         /* Try to create table */
139         ret = sqlite3_exec(dbh, "CREATE TABLE IF NOT EXISTS parameters "
140                                 "(key TEXT PRIMARY KEY, value TEXT);",
141                                 NULL, NULL, &err);
142         if (ret != SQLITE_OK) {
143                 xlog(L_ERROR, "Unable to create parameter table: %d", ret);
144                 goto out_err;
145         }
146
147         /* insert version into table -- ignore error if it fails */
148         ret = snprintf(buf, sizeof(buf),
149                        "INSERT OR IGNORE INTO parameters values (\"version\", "
150                        "\"%d\");", CLD_SQLITE_SCHEMA_VERSION);
151         if (ret < 0) {
152                 goto out_err;
153         } else if ((size_t)ret >= sizeof(buf)) {
154                 ret = -EINVAL;
155                 goto out_err;
156         }
157
158         ret = sqlite3_exec(dbh, (const char *)buf, NULL, NULL, &err);
159         if (ret != SQLITE_OK) {
160                 xlog(L_ERROR, "Unable to insert into parameter table: %d",
161                                 ret);
162                 goto out_err;
163         }
164
165         ret = sqlite3_prepare_v2(dbh,
166                 "SELECT value FROM parameters WHERE key == \"version\";",
167                  -1, &stmt, NULL);
168         if (ret != SQLITE_OK) {
169                 xlog(L_ERROR, "Unable to prepare select statement: %d", ret);
170                 goto out_err;
171         }
172
173         /* check schema version */
174         ret = sqlite3_step(stmt);
175         if (ret != SQLITE_ROW) {
176                 xlog(L_ERROR, "Select statement execution failed: %s",
177                                 sqlite3_errmsg(dbh));
178                 goto out_err;
179         }
180
181         /* process SELECT result */
182         ret = sqlite3_column_int(stmt, 0);
183         if (ret != CLD_SQLITE_SCHEMA_VERSION) {
184                 xlog(L_ERROR, "Unsupported database schema version! "
185                         "Expected %d, got %d.",
186                         CLD_SQLITE_SCHEMA_VERSION, ret);
187                 ret = -EINVAL;
188                 goto out_err;
189         }
190
191         /* now create the "clients" table */
192         ret = sqlite3_exec(dbh, "CREATE TABLE IF NOT EXISTS clients "
193                                 "(id BLOB PRIMARY KEY, time INTEGER);",
194                                 NULL, NULL, &err);
195         if (ret != SQLITE_OK) {
196                 xlog(L_ERROR, "Unable to create clients table: %s", err);
197                 goto out_err;
198         }
199
200         sqlite3_free(err);
201         sqlite3_finalize(stmt);
202         return 0;
203
204 out_err:
205         if (err) {
206                 xlog(L_ERROR, "sqlite error: %s", err);
207                 sqlite3_free(err);
208         }
209         sqlite3_finalize(stmt);
210         sqlite3_close(dbh);
211         return ret;
212 }
213
214 /*
215  * Create a client record
216  *
217  * Returns a non-zero sqlite error code, or SQLITE_OK (aka 0)
218  */
219 int
220 sqlite_insert_client(const unsigned char *clname, const size_t namelen)
221 {
222         int ret;
223         sqlite3_stmt *stmt = NULL;
224
225         ret = sqlite3_prepare_v2(dbh, "INSERT OR REPLACE INTO clients VALUES "
226                                       "(?, strftime('%s', 'now'));", -1,
227                                         &stmt, NULL);
228         if (ret != SQLITE_OK) {
229                 xlog(L_ERROR, "%s: insert statement prepare failed: %s",
230                         __func__, sqlite3_errmsg(dbh));
231                 return ret;
232         }
233
234         ret = sqlite3_bind_blob(stmt, 1, (const void *)clname, namelen,
235                                 SQLITE_STATIC);
236         if (ret != SQLITE_OK) {
237                 xlog(L_ERROR, "%s: bind blob failed: %s", __func__,
238                                 sqlite3_errmsg(dbh));
239                 goto out_err;
240         }
241
242         ret = sqlite3_step(stmt);
243         if (ret == SQLITE_DONE)
244                 ret = SQLITE_OK;
245         else
246                 xlog(L_ERROR, "%s: unexpected return code from insert: %s",
247                                 __func__, sqlite3_errmsg(dbh));
248
249 out_err:
250         xlog(D_GENERAL, "%s: returning %d", __func__, ret);
251         sqlite3_finalize(stmt);
252         return ret;
253 }