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-_]+ */ |
24 | char* 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 | |
49 | char* 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 */ |
142 | static 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 | |
189 | char* 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 | |
231 | char* 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 | |
298 | char* 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 | |
338 | char *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 | |