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