1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
|
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <sqlite3.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#ifdef HAVE_GETRANDOM
# include <sys/random.h>
# if !defined(SYS_getrandom) && defined(__NR_getrandom)
/* usable kernel-headers, but old glibc-headers */
# define SYS_getrandom __NR_getrandom
# endif
#endif
#include "conffile.h"
#include "reexport_backend.h"
#include "xlog.h"
#define REEXPDB_DBFILE NFS_STATEDIR "/reexpdb.sqlite3"
#define REEXPDB_DBFILE_WAIT_USEC (5000)
static sqlite3 *db;
static int init_done;
#if !defined(HAVE_GETRANDOM) && defined(SYS_getrandom)
/* libc without function, but we have syscall */
static int getrandom(void *buf, size_t buflen, unsigned int flags)
{
return (syscall(SYS_getrandom, buf, buflen, flags));
}
# define HAVE_GETRANDOM
#endif
static int prng_init(void)
{
int seed;
if (getrandom(&seed, sizeof(seed), 0) != sizeof(seed)) {
xlog(L_ERROR, "Unable to obtain seed for PRNG via getrandom()");
return -1;
}
srand(seed);
return 0;
}
static void wait_for_dbaccess(void)
{
usleep(REEXPDB_DBFILE_WAIT_USEC + (rand() % REEXPDB_DBFILE_WAIT_USEC));
}
static bool sqlite_plug_init(void)
{
char *sqlerr;
int ret;
if (init_done)
return true;
if (prng_init() != 0)
return false;
ret = sqlite3_open_v2(conf_get_str_with_def("reexport", "sqlitedb", REEXPDB_DBFILE),
&db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX,
NULL);
if (ret != SQLITE_OK) {
xlog(L_ERROR, "Unable to open reexport database: %s", sqlite3_errstr(ret));
return false;
}
again:
ret = sqlite3_exec(db, "CREATE TABLE IF NOT EXISTS fsidnums (num INTEGER PRIMARY KEY CHECK (num > 0 AND num < 4294967296), path TEXT UNIQUE); CREATE INDEX IF NOT EXISTS idx_ids_path ON fsidnums (path);", NULL, NULL, &sqlerr);
switch (ret) {
case SQLITE_OK:
init_done = 1;
ret = 0;
break;
case SQLITE_BUSY:
case SQLITE_LOCKED:
wait_for_dbaccess();
goto again;
default:
xlog(L_ERROR, "Unable to init reexport database: %s", sqlite3_errstr(ret));
sqlite3_free(sqlerr);
sqlite3_close_v2(db);
ret = -1;
}
return ret == 0 ? true : false;
}
static void sqlite_plug_destroy(void)
{
if (!init_done)
return;
sqlite3_close_v2(db);
}
static bool get_fsidnum_by_path(char *path, uint32_t *fsidnum, bool *found)
{
static const char fsidnum_by_path_sql[] = "SELECT num FROM fsidnums WHERE path = ?1;";
sqlite3_stmt *stmt = NULL;
bool success = false;
int ret;
*found = false;
ret = sqlite3_prepare_v2(db, fsidnum_by_path_sql, sizeof(fsidnum_by_path_sql), &stmt, NULL);
if (ret != SQLITE_OK) {
xlog(L_WARNING, "Unable to prepare SQL query '%s': %s", fsidnum_by_path_sql, sqlite3_errstr(ret));
goto out;
}
ret = sqlite3_bind_text(stmt, 1, path, -1, NULL);
if (ret != SQLITE_OK) {
xlog(L_WARNING, "Unable to bind SQL query '%s': %s", fsidnum_by_path_sql, sqlite3_errstr(ret));
goto out;
}
again:
ret = sqlite3_step(stmt);
switch (ret) {
case SQLITE_ROW:
*fsidnum = sqlite3_column_int(stmt, 0);
success = true;
*found = true;
break;
case SQLITE_DONE:
/* No hit */
success = true;
*found = false;
break;
case SQLITE_BUSY:
case SQLITE_LOCKED:
wait_for_dbaccess();
goto again;
default:
xlog(L_WARNING, "Error while looking up '%s' in database: %s", path, sqlite3_errstr(ret));
}
out:
sqlite3_finalize(stmt);
return success;
}
static bool sqlite_plug_path_by_fsidnum(uint32_t fsidnum, char **path, bool *found)
{
static const char path_by_fsidnum_sql[] = "SELECT path FROM fsidnums WHERE num = ?1;";
sqlite3_stmt *stmt = NULL;
bool success = false;
int ret;
*found = false;
ret = sqlite3_prepare_v2(db, path_by_fsidnum_sql, sizeof(path_by_fsidnum_sql), &stmt, NULL);
if (ret != SQLITE_OK) {
xlog(L_WARNING, "Unable to prepare SQL query '%s': %s", path_by_fsidnum_sql, sqlite3_errstr(ret));
goto out;
}
ret = sqlite3_bind_int(stmt, 1, fsidnum);
if (ret != SQLITE_OK) {
xlog(L_WARNING, "Unable to bind SQL query '%s': %s", path_by_fsidnum_sql, sqlite3_errstr(ret));
goto out;
}
again:
ret = sqlite3_step(stmt);
switch (ret) {
case SQLITE_ROW:
*path = strdup((char *)sqlite3_column_text(stmt, 0));
if (*path) {
*found = true;
success = true;
} else {
xlog(L_WARNING, "Out of memory");
}
break;
case SQLITE_DONE:
/* No hit */
*found = false;
success = true;
break;
case SQLITE_BUSY:
case SQLITE_LOCKED:
wait_for_dbaccess();
goto again;
default:
xlog(L_WARNING, "Error while looking up '%i' in database: %s", fsidnum, sqlite3_errstr(ret));
}
out:
sqlite3_finalize(stmt);
return success;
}
static bool new_fsidnum_by_path(char *path, uint32_t *fsidnum)
{
/*
* This query is a little tricky. We use SQL to find and claim the smallest free fsid number.
* To find a free fsid the fsidnums is left joined to itself but with an offset of 1.
* Everything after the UNION statement is to handle the corner case where fsidnums
* is empty. In this case we want 1 as first fsid number.
*/
static const char new_fsidnum_by_path_sql[] = "INSERT INTO fsidnums VALUES ((SELECT ids1.num + 1 FROM fsidnums AS ids1 LEFT JOIN fsidnums AS ids2 ON ids2.num = ids1.num + 1 WHERE ids2.num IS NULL UNION SELECT 1 WHERE NOT EXISTS (SELECT NULL FROM fsidnums WHERE num = 1) LIMIT 1), ?1) RETURNING num;";
sqlite3_stmt *stmt = NULL;
int ret, check = 0;
bool success = false;
ret = sqlite3_prepare_v2(db, new_fsidnum_by_path_sql, sizeof(new_fsidnum_by_path_sql), &stmt, NULL);
if (ret != SQLITE_OK) {
xlog(L_WARNING, "Unable to prepare SQL query '%s': %s", new_fsidnum_by_path_sql, sqlite3_errstr(ret));
goto out;
}
ret = sqlite3_bind_text(stmt, 1, path, -1, NULL);
if (ret != SQLITE_OK) {
xlog(L_WARNING, "Unable to bind SQL query '%s': %s", new_fsidnum_by_path_sql, sqlite3_errstr(ret));
goto out;
}
again:
ret = sqlite3_step(stmt);
switch (ret) {
case SQLITE_ROW:
*fsidnum = sqlite3_column_int(stmt, 0);
success = true;
break;
case SQLITE_CONSTRAINT:
/* Maybe we lost the race against another writer and the path is now present. */
check = 1;
break;
case SQLITE_BUSY:
case SQLITE_LOCKED:
wait_for_dbaccess();
goto again;
default:
xlog(L_WARNING, "Error while looking up '%s' in database: %s", path, sqlite3_errstr(ret));
}
out:
sqlite3_finalize(stmt);
if (check) {
bool found = false;
get_fsidnum_by_path(path, fsidnum, &found);
if (!found)
xlog(L_WARNING, "SQLITE_CONSTRAINT error while inserting '%s' in database", path);
}
return success;
}
static bool sqlite_plug_fsidnum_by_path(char *path, uint32_t *fsidnum, int may_create, bool *found)
{
bool success;
success = get_fsidnum_by_path(path, fsidnum, found);
if (success) {
if (!*found && may_create) {
success = new_fsidnum_by_path(path, fsidnum);
if (success)
*found = true;
}
}
return success;
}
struct reexpdb_backend_plugin sqlite_plug_ops = {
.fsidnum_by_path = sqlite_plug_fsidnum_by_path,
.path_by_fsidnum = sqlite_plug_path_by_fsidnum,
.initdb = sqlite_plug_init,
.destroydb = sqlite_plug_destroy,
};
|