1/*
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 *
6 * Copyright 1997 - July 2008 CWI, August 2008 - 2019 MonetDB B.V.
7 */
8
9/* NOTE: for this file to work correctly, msab_init must be called. */
10
11#include "monetdb_config.h"
12#include "msabaoth.h"
13#include <unistd.h> /* stat, rmdir, unlink, ioctl */
14#include <dirent.h> /* readdir */
15#include <sys/stat.h> /* mkdir, stat, umask */
16#include <sys/types.h> /* mkdir, readdir */
17#include <string.h>
18#include <ctype.h>
19#include "utils.h"
20#include "mutils.h"
21#include "database.h"
22
23/* check if dbname matches [A-Za-z0-9-_]+ */
24char* db_validname(char *dbname) {
25 size_t c;
26 char buf[8096];
27
28 if (dbname[0] == '\0')
29 return(strdup("database name should not be an empty string"));
30 for (c = 0; dbname[c] != '\0'; c++) {
31 if (
32 !(dbname[c] >= 'A' && dbname[c] <= 'Z') &&
33 !(dbname[c] >= 'a' && dbname[c] <= 'z') &&
34 !isdigit((unsigned char) dbname[c]) &&
35 !(dbname[c] == '-') &&
36 !(dbname[c] == '_')
37 )
38 {
39 snprintf(buf, sizeof(buf), "invalid character "
40 "'%c' at %zu in database name '%s'",
41 dbname[c], c, dbname);
42 return(strdup(buf));
43 }
44 }
45
46 return(NULL);
47}
48
49char* db_create(char* dbname) {
50 sabdb *stats;
51 size_t c;
52 char* e;
53 char* dbfarm;
54 char buf[8096];
55 char path[8096];
56 FILE *f;
57
58 if ((e = db_validname(dbname)) != NULL)
59 return(e);
60
61 /* the argument is the database to create, see what Sabaoth can
62 * tell us about it */
63 if ((e = msab_getStatus(&stats, dbname)) != NULL) {
64 snprintf(buf, sizeof(buf), "internal error: %s", e);
65 free(e);
66 return(strdup(buf));
67 }
68
69 /* if sabaoth doesn't know, then it's green light for us! */
70 if (stats != NULL) {
71 msab_freeStatus(&stats);
72 snprintf(buf, sizeof(buf), "database '%s' already exists", dbname);
73 return(strdup(buf));
74 }
75
76 if ((e = msab_getDBfarm(&dbfarm)) != NULL) {
77 snprintf(buf, sizeof(buf), "internal error: %s", e);
78 free(e);
79 return(strdup(buf));
80 }
81
82 /* create the directory */
83 c = snprintf(path, sizeof(path), "%s/%s", dbfarm, dbname);
84 if (c >= sizeof(path)) {
85 free(dbfarm);
86 return(strdup("path/dbname combination too long, "
87 "path would get truncated"));
88 }
89 if (mkdir(path, 0755) == -1) {
90 snprintf(buf, sizeof(buf), "unable to create %s: %s",
91 dbname, strerror(errno));
92 free(dbfarm);
93 return(strdup(buf));
94 }
95
96 /* perform another length check, with the .maintenance file,
97 * which happens to be the longest */
98 c = snprintf(path, sizeof(path), "%s/%s/.maintenance",
99 dbfarm, dbname);
100 if (c >= sizeof(path)) {
101 /* try to cleanup */
102 snprintf(path, sizeof(path), "%s/%s", dbfarm, dbname);
103 rmdir(path);
104 free(dbfarm);
105 return(strdup("path/dbname combination too long, "
106 "filenames inside would get truncated"));
107 }
108
109 /* put this database under maintenance, make sure no race condition
110 * ever can happen, by putting it under maintenance before it even
111 * exists for Merovingian */
112 if ((f = fopen(path, "w")) != NULL)
113 fclose(f); /* if this fails, below probably fails too */
114
115 /* avoid GDK making fugly complaints */
116 snprintf(path, sizeof(path), "%s/%s/.gdk_lock", dbfarm, dbname);
117 if ((f = fopen(path, "w")) == NULL) {
118 snprintf(buf, sizeof(buf), "cannot write lock file: %s",
119 strerror(errno));
120 free(dbfarm);
121 return(strdup(buf));
122 }
123 fclose(f);
124
125 /* generate a vault key */
126 snprintf(path, sizeof(path), "%s/%s/.vaultkey", dbfarm, dbname);
127 if ((e = generatePassphraseFile(path)) != NULL) {
128 free(dbfarm);
129 return(e);
130 }
131
132 /* without an .uplog file, Merovingian won't work, this
133 * needs to be last to avoid race conditions */
134 snprintf(path, sizeof(path), "%s/%s/.uplog", dbfarm, dbname);
135 fclose(fopen(path, "w"));
136
137 free(dbfarm);
138 return(NULL);
139}
140
141/* recursive helper function to delete a directory */
142static char* deletedir(const char *dir) {
143 DIR *d;
144 struct dirent *e;
145 char buf[8192];
146 char path[4096];
147
148 d = opendir(dir);
149 if (d == NULL) {
150 /* silently return if we cannot find the directory; it's
151 * probably already deleted */
152 if (errno == ENOENT)
153 return(NULL);
154 if (errno == ENOTDIR) {
155 if (remove(dir) != 0 && errno != ENOENT) {
156 snprintf(buf, sizeof(buf),
157 "unable to remove file %s: %s",
158 dir, strerror(errno));
159 return(strdup(buf));
160 }
161 return NULL;
162 }
163 snprintf(buf, sizeof(buf), "unable to open dir %s: %s",
164 dir, strerror(errno));
165 return(strdup(buf));
166 }
167 while ((e = readdir(d)) != NULL) {
168 /* ignore . and .. */
169 if (strcmp(e->d_name, ".") != 0 &&
170 strcmp(e->d_name, "..") != 0) {
171 char* er;
172 snprintf(path, sizeof(path), "%s/%s", dir, e->d_name);
173 if ((er = deletedir(path)) != NULL) {
174 closedir(d);
175 return(er);
176 }
177 }
178 }
179 closedir(d);
180 if (rmdir(dir) == -1 && errno != ENOENT) {
181 snprintf(buf, sizeof(buf), "unable to remove directory %s: %s",
182 dir, strerror(errno));
183 return(strdup(buf));
184 }
185
186 return(NULL);
187}
188
189char* db_destroy(char* dbname) {
190 sabdb* stats;
191 char* e;
192 char buf[8096];
193
194 if (dbname[0] == '\0')
195 return(strdup("database name should not be an empty string"));
196
197 /* the argument is the database to destroy, see what Sabaoth can
198 * tell us about it */
199 if ((e = msab_getStatus(&stats, dbname)) != NULL) {
200 snprintf(buf, sizeof(buf), "internal error: %s", e);
201 free(e);
202 return(strdup(buf));
203 }
204
205 if (stats == NULL) {
206 snprintf(buf, sizeof(buf), "no such database: %s", dbname);
207 return(strdup(buf));
208 }
209
210 if (stats->state == SABdbRunning) {
211 snprintf(buf, sizeof(buf), "database '%s' is still running, "
212 "please stop database first", dbname);
213 msab_freeStatus(&stats);
214 return(strdup(buf));
215 }
216
217 /* annoyingly we have to delete file by file, and
218 * directories recursively... */
219 if ((e = deletedir(stats->path)) != NULL) {
220 snprintf(buf, sizeof(buf), "failed to destroy '%s': %s",
221 dbname, e);
222 free(e);
223 msab_freeStatus(&stats);
224 return(strdup(buf));
225 }
226 msab_freeStatus(&stats);
227
228 return(NULL);
229}
230
231char* db_rename(char *olddb, char *newdb) {
232 char new[1024];
233 char buf[8096];
234 char *p;
235 sabdb* stats;
236
237 if (olddb[0] == '\0' || newdb[0] == '\0')
238 return(strdup("database name should not be an empty string"));
239
240 /* check if dbname matches [A-Za-z0-9-_]+ */
241 if ((p = db_validname(newdb)) != NULL)
242 return(p);
243
244 if ((p = msab_getStatus(&stats, newdb)) != NULL) {
245 snprintf(buf, sizeof(buf), "internal error: %s", p);
246 free(p);
247 return(strdup(buf));
248 }
249 if (stats != NULL) {
250 msab_freeStatus(&stats);
251 snprintf(buf, sizeof(buf), "a database with the same name "
252 "already exists: %s", newdb);
253 return(strdup(buf));
254 }
255
256 if ((p = msab_getStatus(&stats, olddb)) != NULL) {
257 snprintf(buf, sizeof(buf), "internal error: %s", p);
258 free(p);
259 return(strdup(buf));
260 }
261
262 if (stats == NULL) {
263 snprintf(buf, sizeof(buf), "no such database: %s", olddb);
264 return(strdup(buf));
265 }
266
267 if (stats->state == SABdbRunning) {
268 snprintf(buf, sizeof(buf), "database '%s' is still running, "
269 "please stop database first", olddb);
270 msab_freeStatus(&stats);
271 return(strdup(buf));
272 }
273
274 /* construct path to new database */
275 snprintf(new, sizeof(new), "%s", stats->path);
276 p = strrchr(new, '/');
277 if (p == NULL) {
278 snprintf(buf, sizeof(buf), "non-absolute database path? '%s'",
279 stats->path);
280 msab_freeStatus(&stats);
281 return(strdup(buf));
282 }
283 snprintf(p + 1, sizeof(new) - (p + 1 - new), "%s", newdb);
284
285 /* Renaming is as simple as changing the directory name. Since the
286 * logdir is below it, we don't need to bother about that either. */
287 if (rename(stats->path, new) != 0) {
288 snprintf(buf, sizeof(buf), "failed to rename database from "
289 "'%s' to '%s': %s\n", stats->path, new, strerror(errno));
290 msab_freeStatus(&stats);
291 return(strdup(buf));
292 }
293
294 msab_freeStatus(&stats);
295 return(NULL);
296}
297
298char* db_lock(char *dbname) {
299 char *e;
300 sabdb *stats;
301 char path[8096];
302 char buf[8096];
303 FILE *f;
304
305 /* the argument is the database to take under maintenance, see
306 * what Sabaoth can tell us about it */
307 if ((e = msab_getStatus(&stats, dbname)) != NULL) {
308 snprintf(buf, sizeof(buf), "internal error: %s", e);
309 free(e);
310 return(strdup(buf));
311 }
312
313 if (stats == NULL) {
314 snprintf(buf, sizeof(buf), "no such database: %s", dbname);
315 return(strdup(buf));
316 }
317
318 if (stats->locked) {
319 msab_freeStatus(&stats);
320 snprintf(buf, sizeof(buf), "database '%s' already is "
321 "under maintenance", dbname);
322 return(strdup(buf));
323 }
324
325 /* put this database in maintenance mode */
326 snprintf(path, sizeof(path), "%s/.maintenance", stats->path);
327 msab_freeStatus(&stats);
328 if ((f = fopen(path, "w")) == NULL) {
329 snprintf(buf, sizeof(buf), "could not create '%s' for '%s': %s",
330 path, dbname, strerror(errno));
331 return(strdup(buf));
332 }
333 fclose(f); /* no biggie if it fails, file is already there */
334
335 return(NULL);
336}
337
338char *db_release(char *dbname) {
339 char *e;
340 sabdb *stats;
341 char path[8096];
342 char buf[8096];
343
344 /* the argument is the database to take under maintenance, see
345 * what Sabaoth can tell us about it */
346 if ((e = msab_getStatus(&stats, dbname)) != NULL) {
347 snprintf(buf, sizeof(buf), "internal error: %s", e);
348 free(e);
349 return(strdup(buf));
350 }
351
352 if (stats == NULL) {
353 snprintf(buf, sizeof(buf), "no such database: %s", dbname);
354 return(strdup(buf));
355 }
356
357 if (!stats->locked) {
358 msab_freeStatus(&stats);
359 snprintf(buf, sizeof(buf), "database '%s' is not "
360 "under maintenance", dbname);
361 return(strdup(buf));
362 }
363
364 /* get this database out of maintenance mode */
365 snprintf(path, sizeof(path), "%s/.maintenance", stats->path);
366 msab_freeStatus(&stats);
367 if (remove(path) != 0) {
368 snprintf(buf, sizeof(buf), "could not remove '%s' for '%s': %s",
369 path, dbname, strerror(errno));
370 return(strdup(buf));
371 }
372
373 return(NULL);
374}
375