| 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 | |