]> git.decadent.org.uk Git - nfs-utils.git/blob - utils/nfsdcld/sqlite.c
nfsdcld: add function to remove unreclaimed client records
[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 <dirent.h>
42 #include <errno.h>
43 #include <event.h>
44 #include <stdbool.h>
45 #include <string.h>
46 #include <sys/stat.h>
47 #include <sys/types.h>
48 #include <fcntl.h>
49 #include <unistd.h>
50 #include <sqlite3.h>
51 #include <linux/limits.h>
52
53 #include "xlog.h"
54
55 #define CLD_SQLITE_SCHEMA_VERSION 1
56
57 #ifndef CLD_SQLITE_TOPDIR
58 #define CLD_SQLITE_TOPDIR NFS_STATEDIR "/nfsdcld"
59 #endif
60
61 /* in milliseconds */
62 #define CLD_SQLITE_BUSY_TIMEOUT 10000
63
64 /* private data structures */
65
66 /* global variables */
67
68 /* top level DB directory */
69 static char *sqlite_topdir;
70
71 /* reusable pathname and sql command buffer */
72 static char buf[PATH_MAX];
73
74 /* global database handle */
75 static sqlite3 *dbh;
76
77 /* forward declarations */
78
79 /* make a directory, ignoring EEXIST errors unless it's not a directory */
80 static int
81 mkdir_if_not_exist(char *dirname)
82 {
83         int ret;
84         struct stat statbuf;
85
86         ret = mkdir(dirname, S_IRWXU);
87         if (ret && errno != EEXIST)
88                 return -errno;
89
90         ret = stat(dirname, &statbuf);
91         if (ret)
92                 return -errno;
93
94         if (!S_ISDIR(statbuf.st_mode))
95                 ret = -ENOTDIR;
96
97         return ret;
98 }
99
100 /*
101  * Open the "main" database, and attempt to initialize it by creating the
102  * parameters table and inserting the schema version into it. Ignore any errors
103  * from that, and then attempt to select the version out of it again. If the
104  * version appears wrong, then assume that the DB is corrupt or has been
105  * upgraded, and return an error. If all of that works, then attempt to create
106  * the "clients" table.
107  */
108 int
109 sqlite_maindb_init(char *topdir)
110 {
111         int ret;
112         char *err = NULL;
113         sqlite3_stmt *stmt = NULL;
114
115         sqlite_topdir = topdir ? topdir : CLD_SQLITE_TOPDIR;
116
117         ret = mkdir_if_not_exist(sqlite_topdir);
118         if (ret)
119                 return ret;
120
121         ret = snprintf(buf, PATH_MAX - 1, "%s/main.sqlite", sqlite_topdir);
122         if (ret < 0)
123                 return ret;
124
125         buf[PATH_MAX - 1] = '\0';
126
127         ret = sqlite3_open(buf, &dbh);
128         if (ret != SQLITE_OK) {
129                 xlog(L_ERROR, "Unable to open main database: %d", ret);
130                 return ret;
131         }
132
133         ret = sqlite3_busy_timeout(dbh, CLD_SQLITE_BUSY_TIMEOUT);
134         if (ret != SQLITE_OK) {
135                 xlog(L_ERROR, "Unable to set sqlite busy timeout: %d", ret);
136                 goto out_err;
137         }
138
139         /* Try to create table */
140         ret = sqlite3_exec(dbh, "CREATE TABLE IF NOT EXISTS parameters "
141                                 "(key TEXT PRIMARY KEY, value TEXT);",
142                                 NULL, NULL, &err);
143         if (ret != SQLITE_OK) {
144                 xlog(L_ERROR, "Unable to create parameter table: %d", ret);
145                 goto out_err;
146         }
147
148         /* insert version into table -- ignore error if it fails */
149         ret = snprintf(buf, sizeof(buf),
150                        "INSERT OR IGNORE INTO parameters values (\"version\", "
151                        "\"%d\");", CLD_SQLITE_SCHEMA_VERSION);
152         if (ret < 0) {
153                 goto out_err;
154         } else if ((size_t)ret >= sizeof(buf)) {
155                 ret = -EINVAL;
156                 goto out_err;
157         }
158
159         ret = sqlite3_exec(dbh, (const char *)buf, NULL, NULL, &err);
160         if (ret != SQLITE_OK) {
161                 xlog(L_ERROR, "Unable to insert into parameter table: %d",
162                                 ret);
163                 goto out_err;
164         }
165
166         ret = sqlite3_prepare_v2(dbh,
167                 "SELECT value FROM parameters WHERE key == \"version\";",
168                  -1, &stmt, NULL);
169         if (ret != SQLITE_OK) {
170                 xlog(L_ERROR, "Unable to prepare select statement: %d", ret);
171                 goto out_err;
172         }
173
174         /* check schema version */
175         ret = sqlite3_step(stmt);
176         if (ret != SQLITE_ROW) {
177                 xlog(L_ERROR, "Select statement execution failed: %s",
178                                 sqlite3_errmsg(dbh));
179                 goto out_err;
180         }
181
182         /* process SELECT result */
183         ret = sqlite3_column_int(stmt, 0);
184         if (ret != CLD_SQLITE_SCHEMA_VERSION) {
185                 xlog(L_ERROR, "Unsupported database schema version! "
186                         "Expected %d, got %d.",
187                         CLD_SQLITE_SCHEMA_VERSION, ret);
188                 ret = -EINVAL;
189                 goto out_err;
190         }
191
192         /* now create the "clients" table */
193         ret = sqlite3_exec(dbh, "CREATE TABLE IF NOT EXISTS clients "
194                                 "(id BLOB PRIMARY KEY, time INTEGER);",
195                                 NULL, NULL, &err);
196         if (ret != SQLITE_OK) {
197                 xlog(L_ERROR, "Unable to create clients table: %s", err);
198                 goto out_err;
199         }
200
201         sqlite3_free(err);
202         sqlite3_finalize(stmt);
203         return 0;
204
205 out_err:
206         if (err) {
207                 xlog(L_ERROR, "sqlite error: %s", err);
208                 sqlite3_free(err);
209         }
210         sqlite3_finalize(stmt);
211         sqlite3_close(dbh);
212         return ret;
213 }
214
215 /*
216  * Create a client record
217  *
218  * Returns a non-zero sqlite error code, or SQLITE_OK (aka 0)
219  */
220 int
221 sqlite_insert_client(const unsigned char *clname, const size_t namelen)
222 {
223         int ret;
224         sqlite3_stmt *stmt = NULL;
225
226         ret = sqlite3_prepare_v2(dbh, "INSERT OR REPLACE INTO clients VALUES "
227                                       "(?, strftime('%s', 'now'));", -1,
228                                         &stmt, NULL);
229         if (ret != SQLITE_OK) {
230                 xlog(L_ERROR, "%s: insert statement prepare failed: %s",
231                         __func__, sqlite3_errmsg(dbh));
232                 return ret;
233         }
234
235         ret = sqlite3_bind_blob(stmt, 1, (const void *)clname, namelen,
236                                 SQLITE_STATIC);
237         if (ret != SQLITE_OK) {
238                 xlog(L_ERROR, "%s: bind blob failed: %s", __func__,
239                                 sqlite3_errmsg(dbh));
240                 goto out_err;
241         }
242
243         ret = sqlite3_step(stmt);
244         if (ret == SQLITE_DONE)
245                 ret = SQLITE_OK;
246         else
247                 xlog(L_ERROR, "%s: unexpected return code from insert: %s",
248                                 __func__, sqlite3_errmsg(dbh));
249
250 out_err:
251         xlog(D_GENERAL, "%s: returning %d", __func__, ret);
252         sqlite3_finalize(stmt);
253         return ret;
254 }
255
256 /* Remove a client record */
257 int
258 sqlite_remove_client(const unsigned char *clname, const size_t namelen)
259 {
260         int ret;
261         sqlite3_stmt *stmt = NULL;
262
263         ret = sqlite3_prepare_v2(dbh, "DELETE FROM clients WHERE id==?", -1,
264                                  &stmt, NULL);
265         if (ret != SQLITE_OK) {
266                 xlog(L_ERROR, "%s: statement prepare failed: %s",
267                                 __func__, sqlite3_errmsg(dbh));
268                 goto out_err;
269         }
270
271         ret = sqlite3_bind_blob(stmt, 1, (const void *)clname, namelen,
272                                 SQLITE_STATIC);
273         if (ret != SQLITE_OK) {
274                 xlog(L_ERROR, "%s: bind blob failed: %s", __func__,
275                                 sqlite3_errmsg(dbh));
276                 goto out_err;
277         }
278
279         ret = sqlite3_step(stmt);
280         if (ret == SQLITE_DONE)
281                 ret = SQLITE_OK;
282         else
283                 xlog(L_ERROR, "%s: unexpected return code from delete: %d",
284                                 __func__, ret);
285
286 out_err:
287         xlog(D_GENERAL, "%s: returning %d", __func__, ret);
288         sqlite3_finalize(stmt);
289         return ret;
290 }
291
292 /*
293  * Is the given clname in the clients table? If so, then update its timestamp
294  * and return success. If the record isn't present, or the update fails, then
295  * return an error.
296  */
297 int
298 sqlite_check_client(const unsigned char *clname, const size_t namelen)
299 {
300         int ret;
301         sqlite3_stmt *stmt = NULL;
302
303         ret = sqlite3_prepare_v2(dbh, "SELECT count(*) FROM clients WHERE "
304                                       "id==?", -1, &stmt, NULL);
305         if (ret != SQLITE_OK) {
306                 xlog(L_ERROR, "%s: unable to prepare update statement: %s",
307                                 __func__, sqlite3_errmsg(dbh));
308                 goto out_err;
309         }
310
311         ret = sqlite3_bind_blob(stmt, 1, (const void *)clname, namelen,
312                                 SQLITE_STATIC);
313         if (ret != SQLITE_OK) {
314                 xlog(L_ERROR, "%s: bind blob failed: %s",
315                                 __func__, sqlite3_errmsg(dbh));
316                 goto out_err;
317         }
318
319         ret = sqlite3_step(stmt);
320         if (ret != SQLITE_ROW) {
321                 xlog(L_ERROR, "%s: unexpected return code from select: %d",
322                                 __func__, ret);
323                 goto out_err;
324         }
325
326         ret = sqlite3_column_int(stmt, 0);
327         xlog(D_GENERAL, "%s: select returned %d rows", ret);
328         if (ret != 1) {
329                 ret = -EACCES;
330                 goto out_err;
331         }
332
333         sqlite3_finalize(stmt);
334         stmt = NULL;
335         ret = sqlite3_prepare_v2(dbh, "UPDATE OR FAIL clients SET "
336                                       "time=strftime('%s', 'now') WHERE id==?",
337                                  -1, &stmt, NULL);
338         if (ret != SQLITE_OK) {
339                 xlog(L_ERROR, "%s: unable to prepare update statement: %s",
340                                 __func__, sqlite3_errmsg(dbh));
341                 goto out_err;
342         }
343
344         ret = sqlite3_bind_blob(stmt, 1, (const void *)clname, namelen,
345                                 SQLITE_STATIC);
346         if (ret != SQLITE_OK) {
347                 xlog(L_ERROR, "%s: bind blob failed: %s",
348                                 __func__, sqlite3_errmsg(dbh));
349                 goto out_err;
350         }
351
352         ret = sqlite3_step(stmt);
353         if (ret == SQLITE_DONE)
354                 ret = SQLITE_OK;
355         else
356                 xlog(L_ERROR, "%s: unexpected return code from update: %s",
357                                 __func__, sqlite3_errmsg(dbh));
358
359 out_err:
360         xlog(D_GENERAL, "%s: returning %d", __func__, ret);
361         sqlite3_finalize(stmt);
362         return ret;
363 }
364
365 /*
366  * remove any client records that were not reclaimed since grace_start.
367  */
368 int
369 sqlite_remove_unreclaimed(time_t grace_start)
370 {
371         int ret;
372         char *err = NULL;
373
374         ret = snprintf(buf, sizeof(buf), "DELETE FROM clients WHERE time < %ld",
375                         grace_start);
376         if (ret < 0) {
377                 return ret;
378         } else if ((size_t)ret >= sizeof(buf)) {
379                 ret = -EINVAL;
380                 return ret;
381         }
382
383         ret = sqlite3_exec(dbh, buf, NULL, NULL, &err);
384         if (ret != SQLITE_OK)
385                 xlog(L_ERROR, "%s: delete failed: %s", __func__, err);
386
387         xlog(D_GENERAL, "%s: returning %d", __func__, ret);
388         sqlite3_free(err);
389         return ret;
390 }