Line data Source code
1 : /*
2 : * SPDX-License-Identifier: MPL-2.0
3 : *
4 : * This Source Code Form is subject to the terms of the Mozilla Public
5 : * License, v. 2.0. If a copy of the MPL was not distributed with this
6 : * file, You can obtain one at http://mozilla.org/MPL/2.0/.
7 : *
8 : * Copyright 2024 MonetDB Foundation;
9 : * Copyright August 2008 - 2023 MonetDB B.V.;
10 : * Copyright 1997 - July 2008 CWI.
11 : */
12 :
13 : /**
14 : * Sabaoth
15 : * Fabian Groffen
16 : * Cluster support
17 : *
18 : * The cluster facilitation currently only deals with (de-)registering
19 : * of services offered by the local server to other servers. This
20 : * module allows programs to be aware of mservers in a dbfarm on a local
21 : * machine.
22 : */
23 :
24 : #include "monetdb_config.h"
25 : #include <unistd.h> /* unlink and friends */
26 : #include <sys/types.h>
27 : #ifdef HAVE_DIRENT_H
28 : #include <dirent.h> /* readdir, DIR */
29 : #endif
30 : #include <sys/stat.h>
31 : #include <fcntl.h>
32 : #include <time.h>
33 : #include <string.h> /* for getting error messages */
34 : #include <stddef.h>
35 : #include <ctype.h>
36 : #if defined(HAVE_GETENTROPY) && defined(HAVE_SYS_RANDOM_H)
37 : #include <sys/random.h>
38 : #endif
39 :
40 : #include "msabaoth.h"
41 : #include "mutils.h"
42 : #include "muuid.h"
43 : #include "mstring.h"
44 :
45 : #if defined(_MSC_VER) && _MSC_VER >= 1400
46 : #define close _close
47 : #define unlink _unlink
48 : #define fdopen _fdopen
49 : #define fileno _fileno
50 : #endif
51 :
52 : #ifndef O_CLOEXEC
53 : #ifdef _O_NOINHERIT
54 : #define O_CLOEXEC _O_NOINHERIT /* Windows */
55 : #else
56 : #define O_CLOEXEC 0
57 : #endif
58 : #endif
59 :
60 : /** the directory where the databases are (aka dbfarm) */
61 : static char *_sabaoth_internal_dbfarm = NULL;
62 : /** the database which is "active" */
63 : static char *_sabaoth_internal_dbname = NULL;
64 : /** identifier of the current process */
65 : static char *_sabaoth_internal_uuid = NULL;
66 :
67 : /**
68 : * Retrieves the dbfarm path plus an optional extra component added
69 : */
70 : static char *
71 37647 : getFarmPath(char *pathbuf, size_t size, const char *extra)
72 : {
73 37647 : if (_sabaoth_internal_dbfarm == NULL)
74 0 : return(strdup("sabaoth not initialized"));
75 :
76 37647 : if (extra == NULL) {
77 37647 : snprintf(pathbuf, size, "%s", _sabaoth_internal_dbfarm);
78 : } else {
79 0 : snprintf(pathbuf, size, "%s%c%s",
80 : _sabaoth_internal_dbfarm, DIR_SEP, extra);
81 : }
82 :
83 : return(NULL);
84 : }
85 :
86 : /**
87 : * Retrieves the path to the database directory with an optional extra
88 : * component added
89 : */
90 : static char *
91 7313 : getDBPath(char *pathbuf, size_t size, const char *extra)
92 : {
93 7313 : if (_sabaoth_internal_dbfarm == NULL)
94 0 : return(strdup("sabaoth not initialized"));
95 7313 : if (_sabaoth_internal_dbname == NULL)
96 0 : return(strdup("sabaoth was not initialized as active database"));
97 :
98 7313 : if (extra == NULL) {
99 0 : snprintf(pathbuf, size, "%s%c%s",
100 : _sabaoth_internal_dbfarm, DIR_SEP, _sabaoth_internal_dbname);
101 : } else {
102 7313 : snprintf(pathbuf, size, "%s%c%s%c%s",
103 : _sabaoth_internal_dbfarm, DIR_SEP,
104 : _sabaoth_internal_dbname, DIR_SEP, extra);
105 : }
106 :
107 : return(NULL);
108 : }
109 :
110 : static inline int
111 2605 : msab_isuuid(const char *restrict s)
112 : {
113 2605 : int hyphens = 0;
114 :
115 : /* correct length */
116 2605 : if (strlen(s) != 36)
117 : return 0;
118 :
119 : /* hyphens at correct locations */
120 15 : if (s[8] != '-' ||
121 15 : s[13] != '-' ||
122 15 : s[18] != '-' ||
123 15 : s[23] != '-')
124 : return 0;
125 : /* only hexadecimals and hypens */
126 555 : while (*s) {
127 540 : if (!isxdigit((unsigned char) *s)) {
128 60 : if (*s == '-')
129 60 : hyphens++;
130 : else
131 : return 0;
132 : }
133 540 : s++;
134 : }
135 : /* correct number of hyphens */
136 15 : return hyphens == 4;
137 : }
138 :
139 : void
140 340 : msab_dbnameinit(const char *dbname)
141 : {
142 340 : if (dbname == NULL) {
143 0 : _sabaoth_internal_dbname = NULL;
144 : } else {
145 340 : _sabaoth_internal_dbname = strdup(dbname);
146 : }
147 340 : }
148 :
149 : /**
150 : * Initialises this Sabaoth instance to use the given dbfarm and dbname.
151 : * dbname may be NULL to indicate that there is no active database. The
152 : * arguments are copied for internal use.
153 : */
154 : static void
155 340 : msab_init(const char *dbfarm, const char *dbname)
156 : {
157 340 : size_t len;
158 340 : DIR *d;
159 340 : char *tmp;
160 :
161 340 : assert(dbfarm != NULL);
162 :
163 340 : if (_sabaoth_internal_dbfarm != NULL)
164 10 : free(_sabaoth_internal_dbfarm);
165 340 : if (_sabaoth_internal_dbname != NULL)
166 10 : free(_sabaoth_internal_dbname);
167 :
168 : /* this UUID is supposed to be unique per-process, we use it later on
169 : * to determine if a database is (started by) the current process,
170 : * since locking always succeeds for the same process */
171 340 : if (_sabaoth_internal_uuid == NULL)
172 330 : _sabaoth_internal_uuid = generateUUID();
173 :
174 340 : len = strlen(dbfarm);
175 340 : _sabaoth_internal_dbfarm = strdup(dbfarm);
176 : /* remove trailing slashes, newlines and spaces */
177 340 : len--;
178 340 : while (len > 0 && (
179 340 : _sabaoth_internal_dbfarm[len] == '/' ||
180 : _sabaoth_internal_dbfarm[len] == '\n' ||
181 : _sabaoth_internal_dbfarm[len] == ' ')) {
182 0 : _sabaoth_internal_dbfarm[len] = '\0';
183 0 : len--;
184 : }
185 :
186 340 : msab_dbnameinit(dbname);
187 :
188 : /* clean out old UUID files in case the database crashed in a
189 : * previous incarnation */
190 340 : if (_sabaoth_internal_dbname != NULL &&
191 340 : (tmp = malloc(strlen(_sabaoth_internal_dbfarm) + strlen(_sabaoth_internal_dbname) + 2)) != NULL) {
192 340 : sprintf(tmp, "%s%c%s", _sabaoth_internal_dbfarm, DIR_SEP, _sabaoth_internal_dbname);
193 340 : if ((d = opendir(tmp)) != NULL) {
194 340 : struct dbe {
195 : struct dbe *next;
196 : char path[];
197 340 : } *dbe = NULL, *db;
198 340 : struct dirent *e;
199 340 : len = offsetof(struct dbe, path) + strlen(tmp) + 2;
200 3285 : while ((e = readdir(d)) != NULL) {
201 2605 : if (msab_isuuid(e->d_name) &&
202 15 : (db = malloc(strlen(e->d_name) + len)) != NULL) {
203 15 : db->next = dbe;
204 15 : dbe = db;
205 15 : sprintf(db->path, "%s%c%s", tmp, DIR_SEP, e->d_name);
206 : }
207 : }
208 340 : closedir(d);
209 : /* remove in a separate loop after reading the directory,
210 : * so as to not have any interference */
211 355 : while (dbe != NULL) {
212 15 : (void) MT_remove(dbe->path);
213 15 : db = dbe;
214 15 : dbe = dbe->next;
215 15 : free(db);
216 : }
217 : }
218 340 : free(tmp);
219 : }
220 340 : }
221 : void
222 340 : msab_dbpathinit(const char *dbpath)
223 : {
224 340 : char dbfarm[FILENAME_MAX];
225 340 : const char *p;
226 :
227 340 : p = strrchr(dbpath, DIR_SEP);
228 340 : assert(p != NULL);
229 340 : strncpy(dbfarm, dbpath, p - dbpath);
230 340 : dbfarm[p - dbpath] = 0;
231 340 : msab_init(dbfarm, p + 1);
232 340 : }
233 : void
234 0 : msab_dbfarminit(const char *dbfarm)
235 : {
236 0 : msab_init(dbfarm, NULL);
237 0 : }
238 :
239 : /**
240 : * Returns the dbfarm as received during msab_init. Returns an
241 : * exception if not initialised.
242 : */
243 : char *
244 0 : msab_getDBfarm(char **ret)
245 : {
246 0 : if (_sabaoth_internal_dbfarm == NULL)
247 0 : return(strdup("sabaoth not initialized"));
248 0 : *ret = strdup(_sabaoth_internal_dbfarm);
249 0 : return(NULL);
250 : }
251 :
252 : /**
253 : * Returns the dbname as received during msab_init. Throws an
254 : * exception if not initialised or dbname was set to NULL.
255 : */
256 : char *
257 0 : msab_getDBname(char **ret)
258 : {
259 0 : if (_sabaoth_internal_dbfarm == NULL)
260 0 : return(strdup("sabaoth not initialized"));
261 0 : if (_sabaoth_internal_dbname == NULL)
262 0 : return(strdup("sabaoth was not initialized as active database"));
263 0 : *ret = strdup(_sabaoth_internal_dbname);
264 0 : return(NULL);
265 : }
266 :
267 : char *
268 181 : msab_getUUID(char **ret)
269 : {
270 181 : if (_sabaoth_internal_uuid == NULL)
271 0 : return(strdup("sabaoth not initialized"));
272 181 : *ret = strdup(_sabaoth_internal_uuid);
273 181 : return NULL;
274 : }
275 :
276 : #define SCENARIOFILE ".scen"
277 :
278 : /**
279 : * Writes the given language to the scenarios file. If the file doesn't
280 : * exist, it is created. Multiple invocations of this function for the
281 : * same language are ignored.
282 : */
283 : char *
284 996 : msab_marchScenario(const char *lang)
285 : {
286 996 : FILE *f;
287 996 : char buf[2*FILENAME_MAX];
288 996 : size_t len;
289 996 : char pathbuf[FILENAME_MAX];
290 996 : char *tmp;
291 :
292 996 : if ((tmp = getDBPath(pathbuf, sizeof(pathbuf), SCENARIOFILE)) != NULL)
293 : return(tmp);
294 :
295 996 : if ((f = MT_fopen(pathbuf, "a+")) != NULL) {
296 996 : if ((len = fread(buf, 1, 255, f)) > 0) {
297 656 : char *p;
298 :
299 656 : buf[len] = '\0';
300 656 : tmp = buf;
301 : /* find newlines and evaluate string */
302 1312 : while ((p = strchr(tmp, '\n')) != NULL) {
303 656 : *p = '\0';
304 656 : if (strcmp(tmp, lang) == 0) {
305 0 : (void)fclose(f);
306 0 : return(NULL);
307 : }
308 : tmp = p;
309 : }
310 : }
311 : /* append to the file */
312 996 : fprintf(f, "%s\n", lang);
313 996 : (void)fflush(f);
314 996 : (void)fclose(f);
315 996 : return(NULL);
316 : }
317 0 : snprintf(buf, sizeof(buf), "failed to open file: %s (%s)",
318 0 : strerror(errno), pathbuf);
319 0 : return(strdup(buf));
320 : }
321 :
322 : /**
323 : * Removes the given language from the scenarios file. If the scenarios
324 : * file is empty (before or) after removing the language, the file is
325 : * removed.
326 : */
327 : char *
328 656 : msab_retreatScenario(const char *lang)
329 : {
330 656 : FILE *f;
331 656 : char buf[2*FILENAME_MAX]; /* should be enough to hold the entire file */
332 656 : size_t len;
333 656 : char pathbuf[FILENAME_MAX];
334 656 : char *tmp;
335 :
336 656 : if ((tmp = getDBPath(pathbuf, sizeof(pathbuf), SCENARIOFILE)) != NULL)
337 : return(tmp);
338 :
339 656 : if ((f = MT_fopen(pathbuf, "a+")) != NULL) {
340 656 : if ((len = fread(buf, 1, 255, f)) > 0) {
341 0 : char *p;
342 0 : char written = 0;
343 :
344 0 : buf[len] = '\0';
345 0 : tmp = buf;
346 : /* find newlines and evaluate string */
347 0 : while ((p = strchr(tmp, '\n')) != NULL) {
348 0 : *p = '\0';
349 0 : if (strcmp(tmp, lang) == 0) {
350 0 : memmove(tmp, p + 1, strlen(p + 1) + 1);
351 0 : written = 1;
352 : } else {
353 0 : *p = '\n';
354 0 : tmp = p+1;
355 : }
356 : }
357 0 : if (written != 0) {
358 0 : rewind(f);
359 0 : len = strlen(buf) + 1;
360 0 : if (fwrite(buf, 1, len, f) < len) {
361 0 : snprintf(buf, sizeof(buf), "failed to write: %s (%s)",
362 0 : strerror(errno), pathbuf);
363 0 : (void)fclose(f);
364 0 : return(strdup(buf));
365 : }
366 0 : fflush(f);
367 0 : fclose(f);
368 0 : return(NULL);
369 : }
370 0 : (void)fclose(f);
371 0 : (void) MT_remove(pathbuf);
372 0 : return(NULL);
373 : } else {
374 656 : if (ferror(f)) {
375 : /* some error */
376 0 : snprintf(buf, sizeof(buf), "failed to write: %s (%s)",
377 0 : strerror(errno), pathbuf);
378 0 : (void)fclose(f);
379 0 : return strdup(buf);
380 : }
381 656 : (void)fclose(f);
382 656 : (void) MT_remove(pathbuf); /* empty file? try to remove */
383 656 : return(NULL);
384 : }
385 : }
386 0 : snprintf(buf, sizeof(buf), "failed to open file: %s (%s)",
387 0 : strerror(errno), pathbuf);
388 0 : return(strdup(buf));
389 : }
390 :
391 : #define CONNECTIONFILE ".conn"
392 : /**
393 : * Writes an URI to the connection file based on the given arguments.
394 : * If the file doesn't exist, it is created. Multiple invocations of
395 : * this function for the same arguments are NOT ignored. If port is set
396 : * to <= 0, this function treats the host argument as UNIX domain
397 : * socket, in which case host must start with a '/'.
398 : */
399 : char *
400 656 : msab_marchConnection(const char *host, const int port)
401 : {
402 656 : FILE *f;
403 656 : char pathbuf[FILENAME_MAX];
404 656 : char *tmp;
405 :
406 656 : if ((tmp = getDBPath(pathbuf, sizeof(pathbuf), CONNECTIONFILE)) != NULL)
407 : return(tmp);
408 :
409 656 : if (port <= 0 && host[0] != '/')
410 0 : return(strdup("UNIX domain connections should be given as "
411 : "absolute path"));
412 :
413 656 : if ((f = MT_fopen(pathbuf, "a")) != NULL) {
414 : /* append to the file */
415 656 : if (port > 0) {
416 328 : fprintf(f, "mapi:monetdb://%s:%i/\n", host, port);
417 : } else {
418 328 : fprintf(f, "mapi:monetdb://%s\n", host);
419 : }
420 656 : (void)fflush(f);
421 656 : (void)fclose(f);
422 656 : return(NULL);
423 : } else {
424 0 : char buf[FILENAME_MAX + 1024];
425 0 : snprintf(buf, sizeof(buf), "failed to open file: %s (%s)",
426 0 : strerror(errno), pathbuf);
427 0 : return(strdup(buf));
428 : }
429 : }
430 :
431 : #define STARTEDFILE ".started"
432 : /**
433 : * Removes all known publications of available services. The function
434 : * name is a nostalgic phrase from "Defender of the Crown" from the
435 : * Commodore Amiga age.
436 : */
437 : char *
438 668 : msab_wildRetreat(void)
439 : {
440 668 : char pathbuf[FILENAME_MAX];
441 668 : char *tmp;
442 :
443 668 : if ((tmp = getDBPath(pathbuf, sizeof(pathbuf), SCENARIOFILE)) != NULL)
444 : return(tmp);
445 668 : (void) MT_remove(pathbuf);
446 :
447 668 : if ((tmp = getDBPath(pathbuf, sizeof(pathbuf), CONNECTIONFILE)) != NULL)
448 : return(tmp);
449 668 : (void) MT_remove(pathbuf);
450 :
451 668 : if ((tmp = getDBPath(pathbuf, sizeof(pathbuf), STARTEDFILE)) != NULL)
452 : return(tmp);
453 668 : (void) MT_remove(pathbuf);
454 :
455 668 : if ((tmp = getDBPath(pathbuf, sizeof(pathbuf), _sabaoth_internal_uuid)) != NULL)
456 : return(tmp);
457 668 : (void) MT_remove(pathbuf);
458 :
459 668 : return(NULL);
460 : }
461 :
462 : #define UPLOGFILE ".uplog"
463 : /**
464 : * Writes a start attempt to the sabaoth start/stop log. Examination of
465 : * the log at a later stage reveals crashes of the server. In addition
466 : * to updating the uplog file, it also leaves the unique signature of
467 : * the current process behind.
468 : */
469 : char *
470 340 : msab_registerStarting(void)
471 : {
472 : /* The sabaoth uplog is in fact a simple two column table that
473 : * contains a start time and a stop time. Start times are followed
474 : * by a tab character, while stop times are followed by a newline.
475 : * This allows to detect crashes, while sabaoth only appends to the
476 : * uplog. */
477 :
478 340 : FILE *f;
479 340 : char pathbuf[FILENAME_MAX];
480 340 : char *tmp;
481 :
482 340 : if ((tmp = getDBPath(pathbuf, sizeof(pathbuf), UPLOGFILE)) != NULL)
483 : return(tmp);
484 :
485 340 : if ((f = MT_fopen(pathbuf, "a")) != NULL) {
486 : /* append to the file */
487 340 : fprintf(f, "%" PRId64 "\t", (int64_t)time(NULL));
488 340 : (void)fflush(f);
489 340 : (void)fclose(f);
490 : } else {
491 0 : char buf[2*FILENAME_MAX];
492 0 : snprintf(buf, sizeof(buf), "failed to open file: %s (%s)",
493 0 : strerror(errno), pathbuf);
494 0 : return(strdup(buf));
495 : }
496 :
497 : /* we treat errors here (albeit being quite unlikely) as non-fatal,
498 : * since they will cause wrong state information in the worst case
499 : * later on */
500 340 : if ((tmp = getDBPath(pathbuf, sizeof(pathbuf), _sabaoth_internal_uuid)) != NULL) {
501 0 : free(tmp);
502 0 : return(NULL);
503 : }
504 340 : f = MT_fopen(pathbuf, "w");
505 340 : if (f)
506 340 : fclose(f);
507 :
508 : /* remove any stray file that would suggest we've finished starting up */
509 340 : if ((tmp = getDBPath(pathbuf, sizeof(pathbuf), STARTEDFILE)) != NULL)
510 : return(tmp);
511 340 : (void) MT_remove(pathbuf);
512 :
513 :
514 340 : return(NULL);
515 : }
516 :
517 : /**
518 : * Removes the starting state, and turns this into a fully started
519 : * engine. The caller is responsible for calling registerStarting()
520 : * first.
521 : */
522 : char *
523 328 : msab_registerStarted(void)
524 : {
525 328 : char pathbuf[FILENAME_MAX];
526 328 : char *tmp;
527 328 : FILE *fp;
528 :
529 : /* flag this database as started up */
530 328 : if ((tmp = getDBPath(pathbuf, sizeof(pathbuf), STARTEDFILE)) != NULL)
531 : return(tmp);
532 328 : fp = MT_fopen(pathbuf, "w");
533 328 : if (fp)
534 328 : fclose(fp);
535 : else
536 0 : return strdup("sabaoth cannot create " STARTEDFILE);
537 :
538 328 : return(NULL);
539 : }
540 :
541 : /**
542 : * Writes a start attempt to the sabaoth start/stop log. Examination of
543 : * the log at a later stage reveals crashes of the server.
544 : */
545 : char *
546 328 : msab_registerStop(void)
547 : {
548 328 : FILE *f;
549 328 : char pathbuf[FILENAME_MAX];
550 328 : char *tmp;
551 :
552 328 : if ((tmp = getDBPath(pathbuf, sizeof(pathbuf), UPLOGFILE)) != NULL)
553 : return(tmp);
554 :
555 328 : if ((f = MT_fopen(pathbuf, "a")) != NULL) {
556 : /* append to the file */
557 328 : fprintf(f, "%" PRId64 "\n", (int64_t)time(NULL));
558 328 : (void)fflush(f);
559 328 : (void)fclose(f);
560 : } else {
561 0 : char buf[2*FILENAME_MAX];
562 0 : snprintf(buf, sizeof(buf), "failed to open file: %s (%s)",
563 0 : strerror(errno), pathbuf);
564 0 : return(strdup(buf));
565 : }
566 :
567 : /* remove server signature, it's no problem when it's left behind,
568 : * but for the sake of keeping things clean ... */
569 328 : if ((tmp = getDBPath(pathbuf, sizeof(pathbuf), _sabaoth_internal_uuid)) != NULL)
570 : return(tmp);
571 328 : (void) MT_remove(pathbuf);
572 328 : return(NULL);
573 : }
574 :
575 : #define SECRETFILE ".secret"
576 : #define SECRET_LENGTH (32)
577 : char *
578 329 : msab_pickSecret(char **generated_secret)
579 : {
580 329 : unsigned char bin_secret[SECRET_LENGTH / 2];
581 329 : char *secret;
582 329 : char pathbuf[FILENAME_MAX];
583 329 : char *e;
584 :
585 329 : if ((e = getDBPath(pathbuf, sizeof(pathbuf), SECRETFILE)) != NULL)
586 : return e;
587 :
588 : // delete existing so we can recreate with appropriate permissions
589 329 : if (MT_remove(pathbuf) < 0 && errno != ENOENT) {
590 0 : char err[FILENAME_MAX + 512];
591 0 : snprintf(err, sizeof(err), "unable to remove '%s': %s",
592 : pathbuf, strerror(errno));
593 0 : return strdup(err);
594 : }
595 :
596 329 : secret = malloc(SECRET_LENGTH + 1);
597 329 : secret[SECRET_LENGTH] = '\0';
598 :
599 : #if defined(HAVE_GETENTROPY)
600 329 : if (getentropy(bin_secret, sizeof(bin_secret)) < 0) {
601 0 : free(secret);
602 0 : return strdup("getentropy failed");
603 : }
604 : #elif defined(HAVE_RAND_S)
605 : for (size_t i = 0; i < sizeof(bin_secret) / sizeof(unsigned int); i++) {
606 : unsigned int r;
607 : if (rand_s(&r) != 0) {
608 : if (generated_secret)
609 : *generated_secret = NULL;
610 : free(secret);
611 : return NULL;
612 : }
613 : for (size_t j = 0; j < sizeof(unsigned int); j++) {
614 : bin_secret[i] = (unsigned char) (r & 0xFF);
615 : r >>= 8;
616 : }
617 : }
618 : #else
619 : int rfd = open("/dev/urandom", O_RDONLY);
620 : if (rfd >= 0) {
621 : ssize_t nr;
622 : for (size_t n = 0; n < sizeof(bin_secret); n += nr) {
623 : nr = read(rfd, bin_secret + n, sizeof(bin_secret) - n);
624 : if (nr < 0) {
625 : free(secret);
626 : return strdup("reading /dev/urandom failed");
627 : }
628 : }
629 : close(rfd);
630 : } else {
631 : (void)bin_secret;
632 : if (generated_secret)
633 : // do not return an error, just continue without a secret
634 : *generated_secret = NULL;
635 : free(secret);
636 : return NULL;
637 : }
638 : #endif
639 : int fd;
640 : FILE *f;
641 5593 : for (size_t i = 0; i < sizeof(bin_secret); i++) {
642 5264 : snprintf(secret + 2 * i, 3, "%02x", bin_secret[i]);
643 : }
644 :
645 329 : if ((fd = MT_open(pathbuf, O_CREAT | O_WRONLY | O_CLOEXEC)) == -1) {
646 0 : char err[FILENAME_MAX + 512];
647 0 : snprintf(err, sizeof(err), "unable to open '%s': %s",
648 0 : pathbuf, strerror(errno));
649 0 : free(secret);
650 0 : return strdup(err);
651 : }
652 329 : if ((f = fdopen(fd, "w")) == NULL) {
653 0 : char err[FILENAME_MAX + 512];
654 0 : snprintf(err, sizeof(err), "unable to open '%s': %s",
655 0 : pathbuf, strerror(errno));
656 0 : close(fd);
657 0 : (void)MT_remove(pathbuf);
658 0 : free(secret);
659 0 : return strdup(err);
660 : }
661 329 : bool error;
662 329 : error = fwrite(secret, 1, SECRET_LENGTH, f) < SECRET_LENGTH;
663 329 : error |= fclose(f) < 0;
664 329 : if (error) {
665 0 : char err[512];
666 0 : snprintf(err, sizeof(err), "cannot write secret: %s",
667 0 : strerror(errno));
668 0 : (void)MT_remove(pathbuf);
669 0 : free(secret);
670 0 : return strdup(err);
671 : }
672 :
673 329 : if (generated_secret)
674 329 : *generated_secret = secret;
675 : else
676 0 : free(secret);
677 : return NULL;
678 : }
679 :
680 : /**
681 : * Returns the status as NULL terminated sabdb struct list for the
682 : * current database. Since the current database should always exist,
683 : * this function never returns NULL.
684 : */
685 : char *
686 37641 : msab_getMyStatus(sabdb** ret)
687 : {
688 37641 : char *err;
689 37641 : if (_sabaoth_internal_dbname == NULL)
690 0 : return(strdup("sabaoth was not initialized as active database"));
691 37641 : err = msab_getStatus(ret, _sabaoth_internal_dbname);
692 37647 : if (err != NULL)
693 : return(err);
694 37649 : if (*ret == NULL)
695 0 : return(strdup("could not find my own database?!?"));
696 : return(NULL);
697 : }
698 :
699 : #define MAINTENANCEFILE ".maintenance"
700 :
701 : static sabdb *
702 37652 : msab_getSingleStatus(const char *pathbuf, const char *dbname, sabdb *next)
703 : {
704 37652 : char buf[FILENAME_MAX];
705 37652 : char data[8096];
706 37652 : char log[FILENAME_MAX];
707 37652 : FILE *f;
708 37652 : int fd;
709 37652 : struct stat statbuf;
710 :
711 37652 : sabdb *sdb;
712 37652 : sdb = NULL;
713 :
714 37652 : snprintf(buf, sizeof(buf), "%s/%s/%s", pathbuf, dbname, UPLOGFILE);
715 37652 : if (MT_stat(buf, &statbuf) == -1)
716 : return next;
717 :
718 37635 : sdb = malloc(sizeof(sabdb));
719 37635 : *sdb = (sabdb) {
720 : .next = next,
721 : };
722 :
723 : /* store the database name */
724 37635 : snprintf(buf, sizeof(buf), "%s/%s", pathbuf, dbname);
725 37635 : sdb->path = strdup(buf);
726 37635 : sdb->dbname = sdb->path + strlen(sdb->path) - strlen(dbname);
727 :
728 :
729 : /* check the state of the server by looking at its gdk lock:
730 : * - if we can lock it, the server has crashed or isn't running
731 : * - if we can't open it because it's locked, the server is
732 : * running
733 : * - to distinguish between a crash and proper shutdown, consult
734 : * the uplog
735 : * - one exception to all above; if this is the same process, we
736 : * cannot lock (it always succeeds), hence, if we have the
737 : * same signature, we assume running if the uplog states so.
738 : */
739 37635 : snprintf(buf, sizeof(buf), "%s/%s/%s", pathbuf, dbname,
740 : _sabaoth_internal_uuid);
741 37635 : if (MT_stat(buf, &statbuf) == 0) {
742 : /* database has the same process signature as ours, which
743 : * means, it must be us, rely on the uplog state */
744 37642 : snprintf(log, sizeof(log), "%s/%s/%s", pathbuf, dbname, UPLOGFILE);
745 37642 : if ((f = MT_fopen(log, "r")) != NULL) {
746 37570 : (void)fseek(f, -1, SEEK_END);
747 37635 : if (fread(data, 1, 1, f) != 1) {
748 : /* the log is empty, assume no crash */
749 0 : sdb->state = SABdbInactive;
750 37610 : } else if (data[0] == '\t') {
751 : /* see if the database has finished starting */
752 37610 : snprintf(buf, sizeof(buf), "%s/%s/%s",
753 : pathbuf, dbname, STARTEDFILE);
754 37610 : if (MT_stat(buf, &statbuf) == -1) {
755 13 : sdb->state = SABdbStarting;
756 : } else {
757 37589 : sdb->state = SABdbRunning;
758 : }
759 : } else { /* should be \n */
760 0 : sdb->state = SABdbInactive;
761 : }
762 37602 : (void)fclose(f);
763 : }
764 0 : } else if (snprintf(buf, sizeof(buf), "%s/%s/%s", pathbuf, dbname, ".gdk_lock"),
765 0 : ((fd = MT_lockf(buf, F_TLOCK)) == -2)) {
766 : /* Locking failed; this can be because the lockfile couldn't
767 : * be created. Probably there is no Mserver running for
768 : * that case also.
769 : */
770 0 : sdb->state = SABdbInactive;
771 0 : } else if (fd == -1) {
772 : #ifndef WIN32
773 : /* extract process ID from lock file */
774 0 : if ((f = MT_fopen(buf, "r")) != NULL) {
775 0 : int pid;
776 0 : if (fscanf(f, "USR=%*d PID=%d TIME=", &pid) == 1)
777 0 : sdb->pid = pid;
778 0 : fclose(f);
779 : }
780 : #endif
781 : /* see if the database has finished starting */
782 0 : snprintf(buf, sizeof(buf), "%s/%s/%s", pathbuf, dbname, STARTEDFILE);
783 0 : if (MT_stat(buf, &statbuf) == -1) {
784 0 : sdb->state = SABdbStarting;
785 : } else {
786 0 : sdb->state = SABdbRunning;
787 : }
788 : } else {
789 : /* file was not locked (we just locked it), check for a crash
790 : * in the uplog */
791 0 : snprintf(log, sizeof(log), "%s/%s/%s", pathbuf, dbname, STARTEDFILE);
792 : /* just to be sure, remove the .started file */
793 0 : (void) MT_remove(log); /* may fail, that's fine */
794 0 : snprintf(log, sizeof(log), "%s/%s/%s", pathbuf, dbname, UPLOGFILE);
795 0 : if ((f = MT_fopen(log, "r")) != NULL) {
796 0 : (void)fseek(f, -1, SEEK_END);
797 0 : if (fread(data, 1, 1, f) != 1) {
798 : /* the log is empty, assume no crash */
799 0 : sdb->state = SABdbInactive;
800 0 : } else if (data[0] == '\n') {
801 0 : sdb->state = SABdbInactive;
802 : } else { /* should be \t */
803 0 : sdb->state = SABdbCrashed;
804 : }
805 0 : (void)fclose(f);
806 : } else {
807 : /* no uplog, so presumably never started */
808 0 : sdb->state = SABdbInactive;
809 : }
810 0 : MT_lockf(buf, F_ULOCK);
811 0 : close(fd);
812 : }
813 37645 : snprintf(buf, sizeof(buf), "%s/%s/%s", pathbuf, dbname, MAINTENANCEFILE);
814 37645 : sdb->locked = MT_stat(buf, &statbuf) == 0;
815 :
816 : /* add scenarios that are supported */
817 37586 : sdb->scens = NULL;
818 37586 : snprintf(buf, sizeof(buf), "%s/%s/%s", pathbuf, dbname, SCENARIOFILE);
819 37586 : if ((f = MT_fopen(buf, "r")) != NULL) {
820 : sablist* np = NULL;
821 150506 : while (fgets(data, (int) sizeof(data), f) != NULL) {
822 112896 : if (*data != '\0' && data[strlen(data) - 1] == '\n')
823 112856 : data[strlen(data) - 1] = '\0';
824 112896 : if (sdb->scens == NULL) {
825 37606 : np = sdb->scens = malloc(sizeof(sablist));
826 : } else {
827 75290 : np = np->next = malloc(sizeof(sablist));
828 : }
829 112896 : np->val = strdup(data);
830 112896 : np->next = NULL;
831 : }
832 37601 : (void)fclose(f);
833 : }
834 :
835 : /* add how this server can be reached */
836 37646 : sdb->conns = NULL;
837 37646 : snprintf(buf, sizeof(buf), "%s/%s/%s", pathbuf, dbname, CONNECTIONFILE);
838 37646 : if ((f = MT_fopen(buf, "r")) != NULL) {
839 : sablist* np = NULL;
840 112895 : while (fgets(data, (int) sizeof(data), f) != NULL) {
841 75267 : if (*data != '\0' && data[strlen(data) - 1] == '\n')
842 75251 : data[strlen(data) - 1] = '\0';
843 75267 : if (sdb->conns == NULL) {
844 37622 : np = sdb->conns = malloc(sizeof(sablist));
845 : } else {
846 37645 : np = np->next = malloc(sizeof(sablist));
847 : }
848 75267 : np->val = strdup(data);
849 75267 : np->next = NULL;
850 : }
851 37638 : (void)fclose(f);
852 : }
853 :
854 : // read the secret
855 37650 : do {
856 37650 : struct stat stb;
857 37650 : snprintf(buf, sizeof(buf), "%s/%s/%s", pathbuf, dbname, SECRETFILE);
858 37650 : if ((f = MT_fopen(buf, "r")) == NULL)
859 : break;
860 37635 : if (fstat(fileno(f), &stb) < 0) {
861 0 : fclose(f);
862 0 : break;
863 : }
864 37643 : size_t len = (size_t)stb.st_size;
865 37643 : char *secret = malloc(len + 1);
866 37643 : if (!secret) {
867 0 : fclose(f);
868 0 : break;
869 : }
870 37643 : if (fread(secret, 1, len, f) != len) {
871 0 : fclose(f);
872 0 : free(secret);
873 0 : break;
874 : }
875 37547 : fclose(f);
876 37650 : secret[len] = '\0';
877 37650 : sdb->secret = secret;
878 : } while (0);
879 :
880 : return sdb;
881 : }
882 :
883 : /**
884 : * Returns a list of populated sabdb structs. If dbname == NULL, the
885 : * list contains sabdb structs for all found databases in the dbfarm.
886 : * Otherwise, at most one sabdb struct is returned for the database from
887 : * the dbfarm that matches dbname.
888 : * If no database could be found, an empty list is returned. Each list
889 : * is terminated by a NULL entry.
890 : */
891 : char *
892 37640 : msab_getStatus(sabdb** ret, const char *dbname)
893 : {
894 37640 : DIR *d;
895 37640 : struct dirent *e;
896 37640 : char data[8096];
897 37640 : char pathbuf[FILENAME_MAX];
898 37640 : char *p;
899 :
900 : /* Caching strategies (might be nice) should create a new struct with
901 : * the last modified time_t of the files involved, such that a stat is
902 : * sufficient to see if reparsing is necessary. The gdk_lock always has
903 : * to be checked to detect crashes. */
904 :
905 37640 : sabdb *sdb;
906 37640 : sdb = *ret = NULL;
907 :
908 37640 : if (dbname && strpbrk(dbname, "/\\") != NULL) {
909 0 : snprintf(data, sizeof(data),
910 : "database name contains disallowed characters");
911 0 : return strdup(data);
912 : }
913 : /* scan the parent for directories */
914 37640 : if ((p = getFarmPath(pathbuf, sizeof(pathbuf), NULL)) != NULL)
915 : return(p);
916 37641 : if (dbname) {
917 37641 : *ret = msab_getSingleStatus(pathbuf, dbname, NULL);
918 37649 : return NULL;
919 : }
920 :
921 0 : d = opendir(pathbuf);
922 0 : if (d == NULL) {
923 0 : snprintf(data, sizeof(data), "failed to open directory %s: %s",
924 0 : pathbuf, strerror(errno));
925 0 : return(strdup(data));
926 : }
927 0 : while ((e = readdir(d)) != NULL) {
928 0 : if (strcmp(e->d_name, "..") == 0 || strcmp(e->d_name, ".") == 0)
929 0 : continue;
930 :
931 0 : sdb = msab_getSingleStatus(pathbuf, e->d_name, sdb);
932 : }
933 0 : (void)closedir(d);
934 :
935 0 : *ret = sdb;
936 0 : return(NULL);
937 : }
938 :
939 : /**
940 : * Frees up the sabdb structure returned by getStatus.
941 : */
942 : void
943 37640 : msab_freeStatus(sabdb** ret)
944 : {
945 37640 : sabdb *p, *q;
946 37640 : sablist *r, *s;
947 :
948 37640 : p = *ret;
949 75249 : while (p != NULL) {
950 37609 : free(p->path);
951 37609 : free(p->uri);
952 37609 : free(p->secret);
953 37609 : free(p->uplog);
954 37609 : r = p->scens;
955 150549 : while (r != NULL) {
956 112940 : if (r->val != NULL)
957 112940 : free(r->val);
958 112940 : s = r->next;
959 112940 : free(r);
960 112940 : r = s;
961 : }
962 37609 : r = p->conns;
963 112901 : while (r != NULL) {
964 75292 : if (r->val != NULL)
965 75292 : free(r->val);
966 75292 : s = r->next;
967 75292 : free(r);
968 75292 : r = s;
969 : }
970 37609 : q = p->next;
971 37609 : free(p);
972 37609 : p = q;
973 : }
974 37640 : }
975 :
976 : /**
977 : * Parses the .uplog file for the given database, and fills ret with the
978 : * parsed information.
979 : */
980 : char *
981 0 : msab_getUplogInfo(sabuplog *ret, const sabdb *db)
982 : {
983 0 : char log[FILENAME_MAX];
984 0 : char data[24];
985 0 : char *p;
986 0 : FILE *f;
987 0 : time_t start, stop, up;
988 0 : int avg10[10];
989 0 : int avg30[30];
990 0 : int i = 0;
991 :
992 : /* early bailout if cached */
993 0 : if (db->uplog != NULL) {
994 0 : *ret = *db->uplog;
995 0 : return(NULL);
996 : }
997 :
998 0 : memset(avg10, 0, sizeof(int) * 10);
999 0 : memset(avg30, 0, sizeof(int) * 30);
1000 :
1001 : /* clear the struct */
1002 0 : *ret = (sabuplog) {
1003 : .minuptime = -1,
1004 : .lastcrash = -1,
1005 : .laststop = -1,
1006 : };
1007 :
1008 0 : snprintf(log, sizeof(log), "%s/%s", db->path, UPLOGFILE);
1009 0 : if ((f = MT_fopen(log, "r")) != NULL) {
1010 : int c;
1011 0 : start = stop = up = 0;
1012 : p = data;
1013 0 : while ((c = getc(f)) != EOF) {
1014 0 : switch (c) {
1015 0 : case '\t':
1016 : /* start attempt */
1017 0 : ret->startcntr++;
1018 0 : if (start != 0)
1019 0 : ret->lastcrash = start;
1020 0 : memmove(&avg10[0], &avg10[1], sizeof(int) * 9);
1021 0 : memmove(&avg30[0], &avg30[1], sizeof(int) * 29);
1022 0 : avg10[9] = avg30[29] = ret->crashavg1 =
1023 0 : (start != 0);
1024 0 : *p = '\0';
1025 0 : ret->laststart = start = (time_t)atol(data);
1026 0 : p = data;
1027 0 : break;
1028 0 : case '\n':
1029 : /* successful stop */
1030 0 : ret->stopcntr++;
1031 0 : *p = '\0';
1032 0 : ret->laststop = stop = (time_t)atol(data);
1033 0 : p = data;
1034 0 : i = (int) (stop - start);
1035 0 : if (i > ret->maxuptime)
1036 0 : ret->maxuptime = i;
1037 0 : if (ret->minuptime == -1 || ret->minuptime > stop - start)
1038 0 : ret->minuptime = stop - start;
1039 0 : up += i;
1040 0 : start = 0;
1041 0 : break;
1042 0 : default:
1043 : /* timestamp */
1044 0 : *p++ = c;
1045 0 : break;
1046 : }
1047 : }
1048 0 : if (start != 0 && db->state != SABdbRunning)
1049 0 : ret->lastcrash = start;
1050 0 : memmove(&avg10[0], &avg10[1], sizeof(int) * 9);
1051 0 : memmove(&avg30[0], &avg30[1], sizeof(int) * 29);
1052 0 : avg10[9] = avg30[29] = ret->crashavg1 =
1053 0 : (start != 0 ? (db->state != SABdbRunning) : 0);
1054 0 : ret->crashcntr =
1055 0 : ret->startcntr - (db->state == SABdbRunning) -
1056 0 : ret->stopcntr;
1057 0 : for (i = 0; i < 10; i++)
1058 0 : ret->crashavg10 += avg10[i];
1059 0 : ret->crashavg10 = ret->crashavg10 / 10.0;
1060 0 : for (i = 0; i < 30; i++)
1061 0 : ret->crashavg30 += avg30[i];
1062 0 : ret->crashavg30 = ret->crashavg30 / 30.0;
1063 :
1064 0 : if (ret->stopcntr > 0) {
1065 0 : ret->avguptime = (time_t)(((double)up / (double)ret->stopcntr) + 0.5);
1066 : } else {
1067 0 : ret->avguptime = 0;
1068 0 : ret->minuptime = 0;
1069 0 : ret->maxuptime = 0;
1070 : }
1071 0 : (void)fclose(f);
1072 : } else {
1073 0 : char buf[2*FILENAME_MAX];
1074 0 : snprintf(buf, sizeof(buf), "could not open file %s: %s",
1075 0 : log, strerror(errno));
1076 0 : return(strdup(buf));
1077 : }
1078 :
1079 : /* Don't store the sabuplog in the sabdb as there is no good reason
1080 : * to retrieve the sabuplog struct more than once for a database
1081 : * (without refetching the sabdb struct). Currently, only a
1082 : * serialisation/deserialisation of a sabdb struct will prefill the
1083 : * sabuplog struct. */
1084 0 : return(NULL);
1085 : }
1086 :
1087 : /* used in the serialisation to be able to change it in the future */
1088 : #define SABDBVER "2"
1089 :
1090 : /**
1091 : * Produces a string representation suitable for storage/sending.
1092 : */
1093 : char *
1094 0 : msab_serialise(char **ret, const sabdb *db)
1095 : {
1096 0 : char buf[8096];
1097 0 : char scens[64];
1098 0 : sablist *l;
1099 0 : sabuplog dbu;
1100 0 : char *p;
1101 0 : size_t avail;
1102 0 : size_t len;
1103 :
1104 0 : scens[0] = '\0';
1105 0 : p = scens;
1106 0 : avail = sizeof(scens) - 1;
1107 0 : for (l = db->scens; l != NULL; l = l->next) {
1108 0 : len = strlen(l->val);
1109 0 : if (len > avail)
1110 : break;
1111 0 : memcpy(p, l->val, len);
1112 0 : p += len + 1;
1113 0 : avail -= len + 1;
1114 0 : memcpy(p - 1, "'", 2);
1115 : }
1116 0 : if (p != scens)
1117 0 : p[-1] = '\0';
1118 :
1119 0 : if ((p = msab_getUplogInfo(&dbu, db)) != NULL)
1120 : return(p);
1121 :
1122 : /* sabdb + sabuplog structs in one */
1123 0 : snprintf(buf, sizeof(buf), "sabdb:" SABDBVER ":"
1124 : "%s,%s,%d,%d,%s,"
1125 : "%d,%d,%d,"
1126 : "%" PRId64 ",%" PRId64 ",%" PRId64 ","
1127 : "%" PRId64 ",%" PRId64 ",%" PRId64 ","
1128 : "%d,%f,%f",
1129 0 : db->dbname, db->uri ? db->uri : "", db->locked,
1130 0 : (int) db->state, scens,
1131 : dbu.startcntr, dbu.stopcntr, dbu.crashcntr,
1132 0 : (int64_t) dbu.avguptime, (int64_t) dbu.maxuptime,
1133 0 : (int64_t) dbu.minuptime, (int64_t) dbu.lastcrash,
1134 0 : (int64_t) dbu.laststart, (int64_t) dbu.laststop,
1135 : dbu.crashavg1, dbu.crashavg10, dbu.crashavg30);
1136 :
1137 0 : *ret = strdup(buf);
1138 0 : return(NULL);
1139 : }
1140 :
1141 : /**
1142 : * Produces a sabdb struct out of a serialised string.
1143 : */
1144 : char *
1145 0 : msab_deserialise(sabdb **ret, const char *sdb)
1146 : {
1147 0 : char *dbname;
1148 0 : char *uri;
1149 0 : char *scens;
1150 0 : sabdb *s;
1151 0 : sabuplog *u;
1152 0 : const char *lasts;
1153 0 : char buf[FILENAME_MAX];
1154 :
1155 0 : if (strncmp(sdb, "sabdb:", 6) != 0) {
1156 0 : snprintf(buf, sizeof(buf),
1157 : "string is not a sabdb struct: %s", sdb);
1158 0 : return(strdup(buf));
1159 : }
1160 0 : sdb += 6;
1161 : /* Protocol 1 was used uptil Oct2012 and is no longer supported.
1162 : * Since Jul2012 a new state
1163 : * SABdbStarting was introduced, but not exposed to the client
1164 : * in serialise. In Feb2013, the path component was removed
1165 : * and replaced by a URI field. This meant dbname could no
1166 : * longer be deduced from path, and hence sent separately.
1167 : * Since the conns property became useless in the light of the
1168 : * added uri, it was dropped. On top of this, a laststop
1169 : * property was added to the uplog struct.
1170 : * These four changes were effectuated in protocol 2. When
1171 : * reading protocol 1, we use the path field to set dbname, but
1172 : * ignore the path information (and set uri to "<unknown>". The
1173 : * SABdbStarting state never occurs. */
1174 0 : if (strncmp(sdb, SABDBVER ":", sizeof(SABDBVER)) != 0) {
1175 0 : snprintf(buf, sizeof(buf),
1176 : "string has unsupported version: %s", sdb);
1177 0 : return(strdup(buf));
1178 : }
1179 0 : sdb += sizeof(SABDBVER);
1180 0 : lasts = strchr(sdb, ',');
1181 0 : if (lasts == NULL) {
1182 0 : snprintf(buf, sizeof(buf),
1183 : "string does not contain dbname: %s", sdb);
1184 0 : return(strdup(buf));
1185 : }
1186 0 : dbname = malloc(lasts - sdb + 1);
1187 0 : strcpy_len(dbname, sdb, lasts - sdb + 1);
1188 0 : sdb = ++lasts;
1189 0 : lasts = strchr(sdb, ',');
1190 0 : if (lasts == NULL) {
1191 0 : snprintf(buf, sizeof(buf),
1192 : "string does not contain uri: %s", sdb);
1193 0 : free(dbname);
1194 0 : return(strdup(buf));
1195 : }
1196 0 : uri = malloc(lasts - sdb + 1);
1197 0 : strcpy_len(uri, sdb, lasts - sdb + 1);
1198 0 : sdb = ++lasts;
1199 0 : int locked, state, n;
1200 0 : switch (sscanf(sdb, "%d,%d%n", &locked, &state, &n)) {
1201 0 : case 0:
1202 0 : free(uri);
1203 0 : free(dbname);
1204 0 : snprintf(buf, sizeof(buf),
1205 : "string does not contain locked state: %s", lasts);
1206 0 : return(strdup(buf));
1207 0 : case 1:
1208 0 : free(uri);
1209 0 : free(dbname);
1210 0 : snprintf(buf, sizeof(buf),
1211 : "string does not contain state: %s", lasts);
1212 0 : return(strdup(buf));
1213 0 : case -1:
1214 0 : free(uri);
1215 0 : free(dbname);
1216 0 : return strdup("should not happen");
1217 : default:
1218 0 : break;
1219 : }
1220 0 : sdb += n;
1221 0 : if (*sdb++ != ',' || (lasts = strchr(sdb, ',')) == NULL) {
1222 0 : snprintf(buf, sizeof(buf),
1223 : "string does not contain scenarios: %s", lasts);
1224 0 : free(uri);
1225 0 : free(dbname);
1226 0 : return(strdup(buf));
1227 : }
1228 0 : if (lasts > sdb) {
1229 0 : scens = malloc(lasts - sdb + 1);
1230 0 : strcpy_len(scens, sdb, lasts - sdb + 1);
1231 : } else {
1232 : scens = NULL;
1233 : }
1234 0 : sdb = ++lasts;
1235 0 : int startcntr, stopcntr, crashcntr;
1236 0 : int64_t avguptime, maxuptime, minuptime, lastcrash, laststart, laststop;
1237 0 : int crashavg1;
1238 0 : double crashavg10, crashavg30;
1239 0 : switch (sscanf(sdb, "%d,%d,%d,%" SCNd64 ",%" SCNd64 ",%" SCNd64 ",%" SCNd64 ",%" SCNd64 ",%" SCNd64 ",%d,%lf,%lf%n", &startcntr, &stopcntr, &crashcntr, &avguptime, &maxuptime, &minuptime, &lastcrash, &laststart, &laststop, &crashavg1, &crashavg10, &crashavg30, &n)) {
1240 0 : case -1:
1241 0 : free(dbname);
1242 0 : free(uri);
1243 0 : free(scens);
1244 0 : return strdup("should not happen");
1245 0 : case 0:
1246 0 : snprintf(buf, sizeof(buf),
1247 : "string does not contain startcounter: %s", sdb);
1248 0 : goto bailout;
1249 0 : case 1:
1250 0 : snprintf(buf, sizeof(buf),
1251 : "string does not contain stopcounter: %s", sdb);
1252 0 : goto bailout;
1253 0 : case 2:
1254 0 : snprintf(buf, sizeof(buf),
1255 : "string does not contain crashcounter: %s", sdb);
1256 0 : goto bailout;
1257 0 : case 3:
1258 0 : snprintf(buf, sizeof(buf),
1259 : "string does not contain avguptime: %s", sdb);
1260 0 : goto bailout;
1261 0 : case 4:
1262 0 : snprintf(buf, sizeof(buf),
1263 : "string does not contain maxuptime: %s", sdb);
1264 0 : goto bailout;
1265 0 : case 5:
1266 0 : snprintf(buf, sizeof(buf),
1267 : "string does not contain minuptime: %s", sdb);
1268 0 : goto bailout;
1269 0 : case 6:
1270 0 : snprintf(buf, sizeof(buf),
1271 : "string does not contain lastcrash: %s", sdb);
1272 0 : goto bailout;
1273 0 : case 7:
1274 0 : snprintf(buf, sizeof(buf),
1275 : "string does not contain laststart: %s", sdb);
1276 0 : goto bailout;
1277 0 : case 8:
1278 0 : snprintf(buf, sizeof(buf),
1279 : "string does not contain laststop: %s", sdb);
1280 0 : goto bailout;
1281 0 : case 9:
1282 0 : snprintf(buf, sizeof(buf),
1283 : "string does not contain crashavg1: %s", sdb);
1284 0 : goto bailout;
1285 0 : case 10:
1286 0 : snprintf(buf, sizeof(buf),
1287 : "string does not contain crashavg10: %s", sdb);
1288 0 : goto bailout;
1289 0 : case 11:
1290 0 : snprintf(buf, sizeof(buf),
1291 : "string does not contain crashavg30: %s", sdb);
1292 0 : goto bailout;
1293 : case 12:
1294 : break;
1295 : }
1296 0 : sdb += n;
1297 0 : if (*sdb) {
1298 0 : snprintf(buf, sizeof(buf),
1299 : "string contains additional garbage after crashavg30: %s",
1300 : sdb);
1301 0 : goto bailout;
1302 : }
1303 :
1304 0 : u = malloc(sizeof(sabuplog));
1305 0 : s = malloc(sizeof(sabdb));
1306 0 : *u = (sabuplog) {
1307 : .startcntr = startcntr,
1308 : .stopcntr = stopcntr,
1309 : .crashcntr = crashcntr,
1310 0 : .avguptime = (time_t) avguptime,
1311 0 : .maxuptime = (time_t) maxuptime,
1312 0 : .minuptime = (time_t) minuptime,
1313 0 : .lastcrash = (time_t) lastcrash,
1314 0 : .laststart = (time_t) laststart,
1315 0 : .laststop = (time_t) laststop,
1316 : .crashavg1 = crashavg1,
1317 : .crashavg10 = crashavg10,
1318 : .crashavg30 = crashavg30,
1319 : };
1320 0 : *s = (sabdb) {
1321 : .dbname = dbname,
1322 : .path = dbname,
1323 : .uri = uri,
1324 : .locked = locked,
1325 0 : .state = (SABdbState) state,
1326 : .uplog = u,
1327 : };
1328 0 : if (scens) {
1329 0 : sablist **sp = &s->scens;
1330 0 : char *sc = scens;
1331 0 : while (sc) {
1332 0 : *sp = malloc(sizeof(sablist));
1333 0 : char *p = strchr(sc, '\'');
1334 0 : if (p)
1335 0 : *p++ = 0;
1336 0 : **sp = (sablist) {
1337 0 : .val = strdup(sc),
1338 : };
1339 0 : sc = p;
1340 0 : sp = &(*sp)->next;
1341 : }
1342 0 : free(scens);
1343 : }
1344 :
1345 0 : *ret = s;
1346 0 : return(NULL);
1347 0 : bailout:
1348 0 : free(dbname);
1349 0 : free(uri);
1350 0 : free(scens);
1351 0 : return strdup(buf);
1352 : }
|