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 37880 : getFarmPath(char *pathbuf, size_t size, const char *extra)
72 : {
73 37880 : if (_sabaoth_internal_dbfarm == NULL)
74 0 : return(strdup("sabaoth not initialized"));
75 :
76 37880 : if (extra == NULL) {
77 37880 : 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 7203 : getDBPath(char *pathbuf, size_t size, const char *extra)
92 : {
93 7203 : if (_sabaoth_internal_dbfarm == NULL)
94 0 : return(strdup("sabaoth not initialized"));
95 7203 : if (_sabaoth_internal_dbname == NULL)
96 0 : return(strdup("sabaoth was not initialized as active database"));
97 :
98 7203 : if (extra == NULL) {
99 0 : snprintf(pathbuf, size, "%s%c%s",
100 : _sabaoth_internal_dbfarm, DIR_SEP, _sabaoth_internal_dbname);
101 : } else {
102 7203 : 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 2535 : msab_isuuid(const char *restrict s)
112 : {
113 2535 : int hyphens = 0;
114 :
115 : /* correct length */
116 2535 : if (strlen(s) != 36)
117 : return 0;
118 :
119 : /* hyphens at correct locations */
120 10 : if (s[8] != '-' ||
121 10 : s[13] != '-' ||
122 10 : s[18] != '-' ||
123 10 : s[23] != '-')
124 : return 0;
125 : /* only hexadecimals and hypens */
126 370 : while (*s) {
127 360 : if (!isxdigit((unsigned char) *s)) {
128 40 : if (*s == '-')
129 40 : hyphens++;
130 : else
131 : return 0;
132 : }
133 360 : s++;
134 : }
135 : /* correct number of hyphens */
136 10 : return hyphens == 4;
137 : }
138 :
139 : void
140 335 : msab_dbnameinit(const char *dbname)
141 : {
142 335 : if (dbname == NULL) {
143 0 : _sabaoth_internal_dbname = NULL;
144 : } else {
145 335 : _sabaoth_internal_dbname = strdup(dbname);
146 : }
147 335 : }
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 335 : msab_init(const char *dbfarm, const char *dbname)
156 : {
157 335 : size_t len;
158 335 : DIR *d;
159 335 : char *tmp;
160 :
161 335 : assert(dbfarm != NULL);
162 :
163 335 : if (_sabaoth_internal_dbfarm != NULL)
164 10 : free(_sabaoth_internal_dbfarm);
165 335 : 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 335 : if (_sabaoth_internal_uuid == NULL)
172 325 : _sabaoth_internal_uuid = generateUUID();
173 :
174 335 : len = strlen(dbfarm);
175 335 : _sabaoth_internal_dbfarm = strdup(dbfarm);
176 : /* remove trailing slashes, newlines and spaces */
177 335 : len--;
178 335 : while (len > 0 && (
179 335 : _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 335 : msab_dbnameinit(dbname);
187 :
188 : /* clean out old UUID files in case the database crashed in a
189 : * previous incarnation */
190 335 : if (_sabaoth_internal_dbname != NULL &&
191 335 : (tmp = malloc(strlen(_sabaoth_internal_dbfarm) + strlen(_sabaoth_internal_dbname) + 2)) != NULL) {
192 335 : sprintf(tmp, "%s%c%s", _sabaoth_internal_dbfarm, DIR_SEP, _sabaoth_internal_dbname);
193 335 : if ((d = opendir(tmp)) != NULL) {
194 335 : struct dbe {
195 : struct dbe *next;
196 : char path[];
197 335 : } *dbe = NULL, *db;
198 335 : struct dirent *e;
199 335 : len = offsetof(struct dbe, path) + strlen(tmp) + 2;
200 3205 : while ((e = readdir(d)) != NULL) {
201 2535 : if (msab_isuuid(e->d_name) &&
202 10 : (db = malloc(strlen(e->d_name) + len)) != NULL) {
203 10 : db->next = dbe;
204 10 : dbe = db;
205 10 : sprintf(db->path, "%s%c%s", tmp, DIR_SEP, e->d_name);
206 : }
207 : }
208 335 : closedir(d);
209 : /* remove in a separate loop after reading the directory,
210 : * so as to not have any interference */
211 345 : while (dbe != NULL) {
212 10 : (void) MT_remove(dbe->path);
213 10 : db = dbe;
214 10 : dbe = dbe->next;
215 10 : free(db);
216 : }
217 : }
218 335 : free(tmp);
219 : }
220 335 : }
221 : void
222 335 : msab_dbpathinit(const char *dbpath)
223 : {
224 335 : char dbfarm[FILENAME_MAX];
225 335 : const char *p;
226 :
227 335 : p = strrchr(dbpath, DIR_SEP);
228 335 : assert(p != NULL);
229 335 : strncpy(dbfarm, dbpath, p - dbpath);
230 335 : dbfarm[p - dbpath] = 0;
231 335 : msab_init(dbfarm, p + 1);
232 335 : }
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 185 : msab_getUUID(char **ret)
269 : {
270 185 : if (_sabaoth_internal_uuid == NULL)
271 0 : return(strdup("sabaoth not initialized"));
272 185 : *ret = strdup(_sabaoth_internal_uuid);
273 185 : 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 981 : msab_marchScenario(const char *lang)
285 : {
286 981 : FILE *f;
287 981 : char buf[2*FILENAME_MAX];
288 981 : size_t len;
289 981 : char pathbuf[FILENAME_MAX];
290 981 : char *tmp;
291 :
292 981 : if ((tmp = getDBPath(pathbuf, sizeof(pathbuf), SCENARIOFILE)) != NULL)
293 : return(tmp);
294 :
295 981 : if ((f = MT_fopen(pathbuf, "a+")) != NULL) {
296 981 : if ((len = fread(buf, 1, 255, f)) > 0) {
297 646 : char *p;
298 :
299 646 : buf[len] = '\0';
300 646 : tmp = buf;
301 : /* find newlines and evaluate string */
302 1292 : while ((p = strchr(tmp, '\n')) != NULL) {
303 646 : *p = '\0';
304 646 : 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 981 : fprintf(f, "%s\n", lang);
313 981 : (void)fflush(f);
314 981 : (void)fclose(f);
315 981 : 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 646 : msab_retreatScenario(const char *lang)
329 : {
330 646 : FILE *f;
331 646 : char buf[2*FILENAME_MAX]; /* should be enough to hold the entire file */
332 646 : size_t len;
333 646 : char pathbuf[FILENAME_MAX];
334 646 : char *tmp;
335 :
336 646 : if ((tmp = getDBPath(pathbuf, sizeof(pathbuf), SCENARIOFILE)) != NULL)
337 : return(tmp);
338 :
339 646 : if ((f = MT_fopen(pathbuf, "a+")) != NULL) {
340 646 : 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 646 : 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 646 : (void)fclose(f);
382 646 : (void) MT_remove(pathbuf); /* empty file? try to remove */
383 646 : 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 646 : msab_marchConnection(const char *host, const int port)
401 : {
402 646 : FILE *f;
403 646 : char pathbuf[FILENAME_MAX];
404 646 : char *tmp;
405 :
406 646 : if ((tmp = getDBPath(pathbuf, sizeof(pathbuf), CONNECTIONFILE)) != NULL)
407 : return(tmp);
408 :
409 646 : if (port <= 0 && host[0] != '/')
410 0 : return(strdup("UNIX domain connections should be given as "
411 : "absolute path"));
412 :
413 646 : if ((f = MT_fopen(pathbuf, "a")) != NULL) {
414 : /* append to the file */
415 646 : if (port > 0) {
416 323 : fprintf(f, "mapi:monetdb://%s:%i/\n", host, port);
417 : } else {
418 323 : fprintf(f, "mapi:monetdb://%s\n", host);
419 : }
420 646 : (void)fflush(f);
421 646 : (void)fclose(f);
422 646 : 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 658 : msab_wildRetreat(void)
439 : {
440 658 : char pathbuf[FILENAME_MAX];
441 658 : char *tmp;
442 :
443 658 : if ((tmp = getDBPath(pathbuf, sizeof(pathbuf), SCENARIOFILE)) != NULL)
444 : return(tmp);
445 658 : (void) MT_remove(pathbuf);
446 :
447 658 : if ((tmp = getDBPath(pathbuf, sizeof(pathbuf), CONNECTIONFILE)) != NULL)
448 : return(tmp);
449 658 : (void) MT_remove(pathbuf);
450 :
451 658 : if ((tmp = getDBPath(pathbuf, sizeof(pathbuf), STARTEDFILE)) != NULL)
452 : return(tmp);
453 658 : (void) MT_remove(pathbuf);
454 :
455 658 : if ((tmp = getDBPath(pathbuf, sizeof(pathbuf), _sabaoth_internal_uuid)) != NULL)
456 : return(tmp);
457 658 : (void) MT_remove(pathbuf);
458 :
459 658 : 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 335 : 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 335 : FILE *f;
479 335 : char pathbuf[FILENAME_MAX];
480 335 : char *tmp;
481 :
482 335 : if ((tmp = getDBPath(pathbuf, sizeof(pathbuf), UPLOGFILE)) != NULL)
483 : return(tmp);
484 :
485 335 : if ((f = MT_fopen(pathbuf, "a")) != NULL) {
486 : /* append to the file */
487 335 : fprintf(f, "%" PRId64 "\t", (int64_t)time(NULL));
488 335 : (void)fflush(f);
489 335 : (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 335 : if ((tmp = getDBPath(pathbuf, sizeof(pathbuf), _sabaoth_internal_uuid)) != NULL) {
501 0 : free(tmp);
502 0 : return(NULL);
503 : }
504 335 : f = MT_fopen(pathbuf, "w");
505 335 : if (f)
506 335 : fclose(f);
507 :
508 : /* remove any stray file that would suggest we've finished starting up */
509 335 : if ((tmp = getDBPath(pathbuf, sizeof(pathbuf), STARTEDFILE)) != NULL)
510 : return(tmp);
511 335 : (void) MT_remove(pathbuf);
512 :
513 :
514 335 : 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 323 : msab_registerStarted(void)
524 : {
525 323 : char pathbuf[FILENAME_MAX];
526 323 : char *tmp;
527 323 : FILE *fp;
528 :
529 : /* flag this database as started up */
530 323 : if ((tmp = getDBPath(pathbuf, sizeof(pathbuf), STARTEDFILE)) != NULL)
531 : return(tmp);
532 323 : fp = MT_fopen(pathbuf, "w");
533 323 : if (fp)
534 323 : fclose(fp);
535 : else
536 0 : return strdup("sabaoth cannot create " STARTEDFILE);
537 :
538 323 : 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 323 : msab_registerStop(void)
547 : {
548 323 : FILE *f;
549 323 : char pathbuf[FILENAME_MAX];
550 323 : char *tmp;
551 :
552 323 : if ((tmp = getDBPath(pathbuf, sizeof(pathbuf), UPLOGFILE)) != NULL)
553 : return(tmp);
554 :
555 323 : if ((f = MT_fopen(pathbuf, "a")) != NULL) {
556 : /* append to the file */
557 323 : fprintf(f, "%" PRId64 "\n", (int64_t)time(NULL));
558 323 : (void)fflush(f);
559 323 : (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 323 : if ((tmp = getDBPath(pathbuf, sizeof(pathbuf), _sabaoth_internal_uuid)) != NULL)
570 : return(tmp);
571 323 : (void) MT_remove(pathbuf);
572 323 : return(NULL);
573 : }
574 :
575 : #define SECRETFILE ".secret"
576 : #define SECRET_LENGTH (32)
577 : char *
578 324 : msab_pickSecret(char **generated_secret)
579 : {
580 324 : unsigned char bin_secret[SECRET_LENGTH / 2];
581 324 : char *secret;
582 324 : char pathbuf[FILENAME_MAX];
583 324 : char *e;
584 :
585 324 : if ((e = getDBPath(pathbuf, sizeof(pathbuf), SECRETFILE)) != NULL)
586 : return e;
587 :
588 : // delete existing so we can recreate with appropriate permissions
589 324 : 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 324 : secret = malloc(SECRET_LENGTH + 1);
597 324 : secret[SECRET_LENGTH] = '\0';
598 :
599 : #if defined(HAVE_GETENTROPY)
600 324 : 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 5508 : for (size_t i = 0; i < sizeof(bin_secret); i++) {
642 5184 : snprintf(secret + 2 * i, 3, "%02x", bin_secret[i]);
643 : }
644 :
645 324 : 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 324 : 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 324 : bool error;
662 324 : error = fwrite(secret, 1, SECRET_LENGTH, f) < SECRET_LENGTH;
663 324 : error |= fclose(f) < 0;
664 324 : 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 324 : if (generated_secret)
674 324 : *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 37880 : msab_getMyStatus(sabdb** ret)
687 : {
688 37880 : char *err;
689 37880 : if (_sabaoth_internal_dbname == NULL)
690 0 : return(strdup("sabaoth was not initialized as active database"));
691 37880 : err = msab_getStatus(ret, _sabaoth_internal_dbname);
692 37880 : if (err != NULL)
693 : return(err);
694 37880 : 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 37876 : msab_getSingleStatus(const char *pathbuf, const char *dbname, sabdb *next)
703 : {
704 37876 : char buf[FILENAME_MAX];
705 37876 : char data[8096];
706 37876 : char log[FILENAME_MAX];
707 37876 : FILE *f;
708 37876 : int fd;
709 37876 : struct stat statbuf;
710 :
711 37876 : sabdb *sdb;
712 37876 : sdb = NULL;
713 :
714 37876 : snprintf(buf, sizeof(buf), "%s/%s/%s", pathbuf, dbname, UPLOGFILE);
715 37876 : if (MT_stat(buf, &statbuf) == -1)
716 : return next;
717 :
718 37879 : sdb = malloc(sizeof(sabdb));
719 37879 : *sdb = (sabdb) {
720 : .next = next,
721 : };
722 :
723 : /* store the database name */
724 37879 : snprintf(buf, sizeof(buf), "%s/%s", pathbuf, dbname);
725 37879 : sdb->path = strdup(buf);
726 37879 : 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 37879 : snprintf(buf, sizeof(buf), "%s/%s/%s", pathbuf, dbname,
740 : _sabaoth_internal_uuid);
741 37879 : 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 37880 : snprintf(log, sizeof(log), "%s/%s/%s", pathbuf, dbname, UPLOGFILE);
745 37880 : if ((f = MT_fopen(log, "r")) != NULL) {
746 37879 : (void)fseek(f, -1, SEEK_END);
747 37880 : if (fread(data, 1, 1, f) != 1) {
748 : /* the log is empty, assume no crash */
749 0 : sdb->state = SABdbInactive;
750 37880 : } else if (data[0] == '\t') {
751 : /* see if the database has finished starting */
752 37880 : snprintf(buf, sizeof(buf), "%s/%s/%s",
753 : pathbuf, dbname, STARTEDFILE);
754 37880 : if (MT_stat(buf, &statbuf) == -1) {
755 13 : sdb->state = SABdbStarting;
756 : } else {
757 37867 : sdb->state = SABdbRunning;
758 : }
759 : } else { /* should be \n */
760 0 : sdb->state = SABdbInactive;
761 : }
762 37880 : (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 37880 : snprintf(buf, sizeof(buf), "%s/%s/%s", pathbuf, dbname, MAINTENANCEFILE);
814 37880 : sdb->locked = MT_stat(buf, &statbuf) == 0;
815 :
816 : /* add scenarios that are supported */
817 37880 : sdb->scens = NULL;
818 37880 : snprintf(buf, sizeof(buf), "%s/%s/%s", pathbuf, dbname, SCENARIOFILE);
819 37880 : if ((f = MT_fopen(buf, "r")) != NULL) {
820 : sablist* np = NULL;
821 151518 : while (fgets(data, (int) sizeof(data), f) != NULL) {
822 113638 : if (*data != '\0' && data[strlen(data) - 1] == '\n')
823 113637 : data[strlen(data) - 1] = '\0';
824 113638 : if (sdb->scens == NULL) {
825 37880 : np = sdb->scens = malloc(sizeof(sablist));
826 : } else {
827 75758 : np = np->next = malloc(sizeof(sablist));
828 : }
829 113638 : np->val = strdup(data);
830 113638 : np->next = NULL;
831 : }
832 37880 : (void)fclose(f);
833 : }
834 :
835 : /* add how this server can be reached */
836 37880 : sdb->conns = NULL;
837 37880 : snprintf(buf, sizeof(buf), "%s/%s/%s", pathbuf, dbname, CONNECTIONFILE);
838 37880 : if ((f = MT_fopen(buf, "r")) != NULL) {
839 : sablist* np = NULL;
840 113638 : while (fgets(data, (int) sizeof(data), f) != NULL) {
841 75758 : if (*data != '\0' && data[strlen(data) - 1] == '\n')
842 75758 : data[strlen(data) - 1] = '\0';
843 75758 : if (sdb->conns == NULL) {
844 37878 : np = sdb->conns = malloc(sizeof(sablist));
845 : } else {
846 37880 : np = np->next = malloc(sizeof(sablist));
847 : }
848 75758 : np->val = strdup(data);
849 75758 : np->next = NULL;
850 : }
851 37879 : (void)fclose(f);
852 : }
853 :
854 : // read the secret
855 37880 : do {
856 37880 : struct stat stb;
857 37880 : snprintf(buf, sizeof(buf), "%s/%s/%s", pathbuf, dbname, SECRETFILE);
858 37880 : if ((f = MT_fopen(buf, "r")) == NULL)
859 : break;
860 37880 : if (fstat(fileno(f), &stb) < 0) {
861 0 : fclose(f);
862 0 : break;
863 : }
864 37880 : size_t len = (size_t)stb.st_size;
865 37880 : char *secret = malloc(len + 1);
866 37880 : if (!secret) {
867 0 : fclose(f);
868 0 : break;
869 : }
870 37880 : if (fread(secret, 1, len, f) != len) {
871 0 : fclose(f);
872 0 : free(secret);
873 0 : break;
874 : }
875 37880 : fclose(f);
876 37880 : secret[len] = '\0';
877 37880 : 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 37880 : msab_getStatus(sabdb** ret, const char *dbname)
893 : {
894 37880 : DIR *d;
895 37880 : struct dirent *e;
896 37880 : char data[8096];
897 37880 : char pathbuf[FILENAME_MAX];
898 37880 : 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 37880 : sabdb *sdb;
906 37880 : sdb = *ret = NULL;
907 :
908 37880 : 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 37880 : if ((p = getFarmPath(pathbuf, sizeof(pathbuf), NULL)) != NULL)
915 : return(p);
916 37879 : if (dbname) {
917 37879 : *ret = msab_getSingleStatus(pathbuf, dbname, NULL);
918 37879 : 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 37880 : msab_freeStatus(sabdb** ret)
944 : {
945 37880 : sabdb *p, *q;
946 37880 : sablist *r, *s;
947 :
948 37880 : p = *ret;
949 75746 : while (p != NULL) {
950 37866 : free(p->path);
951 37866 : free(p->uri);
952 37866 : free(p->secret);
953 37866 : free(p->uplog);
954 37866 : r = p->scens;
955 151505 : while (r != NULL) {
956 113639 : if (r->val != NULL)
957 113639 : free(r->val);
958 113639 : s = r->next;
959 113639 : free(r);
960 113639 : r = s;
961 : }
962 37866 : r = p->conns;
963 113625 : while (r != NULL) {
964 75759 : if (r->val != NULL)
965 75759 : free(r->val);
966 75759 : s = r->next;
967 75759 : free(r);
968 75759 : r = s;
969 : }
970 37866 : q = p->next;
971 37866 : free(p);
972 37866 : p = q;
973 : }
974 37880 : }
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 : }
|