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 37185 : getFarmPath(char *pathbuf, size_t size, const char *extra)
72 : {
73 37185 : if (_sabaoth_internal_dbfarm == NULL)
74 0 : return(strdup("sabaoth not initialized"));
75 :
76 37185 : if (extra == NULL) {
77 37185 : 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 7555 : getDBPath(char *pathbuf, size_t size, const char *extra)
92 : {
93 7555 : if (_sabaoth_internal_dbfarm == NULL)
94 0 : return(strdup("sabaoth not initialized"));
95 7555 : if (_sabaoth_internal_dbname == NULL)
96 0 : return(strdup("sabaoth was not initialized as active database"));
97 :
98 7555 : if (extra == NULL) {
99 0 : snprintf(pathbuf, size, "%s%c%s",
100 : _sabaoth_internal_dbfarm, DIR_SEP, _sabaoth_internal_dbname);
101 : } else {
102 7555 : 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 2679 : msab_isuuid(const char *restrict s)
112 : {
113 2679 : int hyphens = 0;
114 :
115 : /* correct length */
116 2679 : 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 hyphens */
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 351 : msab_dbnameinit(const char *dbname)
141 : {
142 351 : if (dbname == NULL) {
143 0 : _sabaoth_internal_dbname = NULL;
144 : } else {
145 351 : _sabaoth_internal_dbname = strdup(dbname);
146 : }
147 351 : }
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 351 : msab_init(const char *dbfarm, const char *dbname)
156 : {
157 351 : size_t len;
158 351 : DIR *d;
159 351 : char *tmp;
160 :
161 351 : assert(dbfarm != NULL);
162 :
163 351 : if (_sabaoth_internal_dbfarm != NULL)
164 10 : free(_sabaoth_internal_dbfarm);
165 351 : 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 351 : if (_sabaoth_internal_uuid == NULL)
172 341 : _sabaoth_internal_uuid = generateUUID();
173 :
174 351 : len = strlen(dbfarm);
175 351 : _sabaoth_internal_dbfarm = strdup(dbfarm);
176 : /* remove trailing slashes, newlines and spaces */
177 351 : len--;
178 351 : while (len > 0 && (
179 351 : _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 351 : msab_dbnameinit(dbname);
187 :
188 : /* clean out old UUID files in case the database crashed in a
189 : * previous incarnation */
190 351 : if (_sabaoth_internal_dbname != NULL &&
191 351 : (tmp = malloc(strlen(_sabaoth_internal_dbfarm) + strlen(_sabaoth_internal_dbname) + 2)) != NULL) {
192 351 : sprintf(tmp, "%s%c%s", _sabaoth_internal_dbfarm, DIR_SEP, _sabaoth_internal_dbname);
193 351 : if ((d = opendir(tmp)) != NULL) {
194 351 : struct dbe {
195 : struct dbe *next;
196 : char path[];
197 351 : } *dbe = NULL, *db;
198 351 : struct dirent *e;
199 351 : len = offsetof(struct dbe, path) + strlen(tmp) + 2;
200 3381 : while ((e = readdir(d)) != NULL) {
201 2679 : 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 351 : closedir(d);
209 : /* remove in a separate loop after reading the directory,
210 : * so as to not have any interference */
211 361 : 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 351 : free(tmp);
219 : }
220 351 : }
221 : void
222 351 : msab_dbpathinit(const char *dbpath)
223 : {
224 351 : char dbfarm[FILENAME_MAX];
225 351 : const char *p;
226 :
227 351 : p = strrchr(dbpath, DIR_SEP);
228 351 : assert(p != NULL);
229 351 : strncpy(dbfarm, dbpath, p - dbpath);
230 351 : dbfarm[p - dbpath] = 0;
231 351 : msab_init(dbfarm, p + 1);
232 351 : }
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 186 : msab_getUUID(char **ret)
269 : {
270 186 : if (_sabaoth_internal_uuid == NULL)
271 0 : return(strdup("sabaoth not initialized"));
272 186 : *ret = strdup(_sabaoth_internal_uuid);
273 186 : 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 1029 : msab_marchScenario(const char *lang)
285 : {
286 1029 : FILE *f;
287 1029 : char buf[2*FILENAME_MAX];
288 1029 : size_t len;
289 1029 : char pathbuf[FILENAME_MAX];
290 1029 : char *tmp;
291 :
292 1029 : if ((tmp = getDBPath(pathbuf, sizeof(pathbuf), SCENARIOFILE)) != NULL)
293 : return(tmp);
294 :
295 1029 : if ((f = MT_fopen(pathbuf, "a+")) != NULL) {
296 1029 : if ((len = fread(buf, 1, 255, f)) > 0) {
297 678 : char *p;
298 :
299 678 : buf[len] = '\0';
300 678 : tmp = buf;
301 : /* find newlines and evaluate string */
302 1356 : while ((p = strchr(tmp, '\n')) != NULL) {
303 678 : *p = '\0';
304 678 : 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 1029 : fprintf(f, "%s\n", lang);
313 1029 : (void)fflush(f);
314 1029 : (void)fclose(f);
315 1029 : 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 678 : msab_retreatScenario(const char *lang)
329 : {
330 678 : FILE *f;
331 678 : char buf[2*FILENAME_MAX]; /* should be enough to hold the entire file */
332 678 : size_t len;
333 678 : char pathbuf[FILENAME_MAX];
334 678 : char *tmp;
335 :
336 678 : if ((tmp = getDBPath(pathbuf, sizeof(pathbuf), SCENARIOFILE)) != NULL)
337 : return(tmp);
338 :
339 678 : if ((f = MT_fopen(pathbuf, "a+")) != NULL) {
340 678 : 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 678 : 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 678 : (void)fclose(f);
382 678 : (void) MT_remove(pathbuf); /* empty file? try to remove */
383 678 : 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 678 : msab_marchConnection(const char *host, const int port)
401 : {
402 678 : FILE *f;
403 678 : char pathbuf[FILENAME_MAX];
404 678 : char *tmp;
405 :
406 678 : if ((tmp = getDBPath(pathbuf, sizeof(pathbuf), CONNECTIONFILE)) != NULL)
407 : return(tmp);
408 :
409 678 : if (port <= 0 && host[0] != '/')
410 0 : return(strdup("UNIX domain connections should be given as "
411 : "absolute path"));
412 :
413 678 : if ((f = MT_fopen(pathbuf, "a")) != NULL) {
414 : /* append to the file */
415 678 : if (port > 0) {
416 339 : fprintf(f, "mapi:monetdb://%s:%i/\n", host, port);
417 : } else {
418 339 : fprintf(f, "mapi:monetdb://%s\n", host);
419 : }
420 678 : (void)fflush(f);
421 678 : (void)fclose(f);
422 678 : 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 690 : msab_wildRetreat(void)
439 : {
440 690 : char pathbuf[FILENAME_MAX];
441 690 : char *tmp;
442 :
443 690 : if ((tmp = getDBPath(pathbuf, sizeof(pathbuf), SCENARIOFILE)) != NULL)
444 : return(tmp);
445 690 : (void) MT_remove(pathbuf);
446 :
447 690 : if ((tmp = getDBPath(pathbuf, sizeof(pathbuf), CONNECTIONFILE)) != NULL)
448 : return(tmp);
449 690 : (void) MT_remove(pathbuf);
450 :
451 690 : if ((tmp = getDBPath(pathbuf, sizeof(pathbuf), STARTEDFILE)) != NULL)
452 : return(tmp);
453 690 : (void) MT_remove(pathbuf);
454 :
455 690 : if ((tmp = getDBPath(pathbuf, sizeof(pathbuf), _sabaoth_internal_uuid)) != NULL)
456 : return(tmp);
457 690 : (void) MT_remove(pathbuf);
458 :
459 690 : 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 351 : 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 351 : FILE *f;
479 351 : char pathbuf[FILENAME_MAX];
480 351 : char *tmp;
481 :
482 351 : if ((tmp = getDBPath(pathbuf, sizeof(pathbuf), UPLOGFILE)) != NULL)
483 : return(tmp);
484 :
485 351 : if ((f = MT_fopen(pathbuf, "a")) != NULL) {
486 : /* append to the file */
487 351 : fprintf(f, "%" PRId64 "\t", (int64_t)time(NULL));
488 351 : (void)fflush(f);
489 351 : (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 351 : if ((tmp = getDBPath(pathbuf, sizeof(pathbuf), _sabaoth_internal_uuid)) != NULL) {
501 0 : free(tmp);
502 0 : return(NULL);
503 : }
504 351 : f = MT_fopen(pathbuf, "w");
505 351 : if (f)
506 351 : fclose(f);
507 :
508 : /* remove any stray file that would suggest we've finished starting up */
509 351 : if ((tmp = getDBPath(pathbuf, sizeof(pathbuf), STARTEDFILE)) != NULL)
510 : return(tmp);
511 351 : (void) MT_remove(pathbuf);
512 :
513 :
514 351 : 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 339 : msab_registerStarted(void)
524 : {
525 339 : char pathbuf[FILENAME_MAX];
526 339 : char *tmp;
527 339 : FILE *fp;
528 :
529 : /* flag this database as started up */
530 339 : if ((tmp = getDBPath(pathbuf, sizeof(pathbuf), STARTEDFILE)) != NULL)
531 : return(tmp);
532 339 : fp = MT_fopen(pathbuf, "w");
533 339 : if (fp)
534 339 : fclose(fp);
535 : else
536 0 : return strdup("sabaoth cannot create " STARTEDFILE);
537 :
538 339 : 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 339 : msab_registerStop(void)
547 : {
548 339 : FILE *f;
549 339 : char pathbuf[FILENAME_MAX];
550 339 : char *tmp;
551 :
552 339 : if ((tmp = getDBPath(pathbuf, sizeof(pathbuf), UPLOGFILE)) != NULL)
553 : return(tmp);
554 :
555 339 : if ((f = MT_fopen(pathbuf, "a")) != NULL) {
556 : /* append to the file */
557 339 : fprintf(f, "%" PRId64 "\n", (int64_t)time(NULL));
558 339 : (void)fflush(f);
559 339 : (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 339 : if ((tmp = getDBPath(pathbuf, sizeof(pathbuf), _sabaoth_internal_uuid)) != NULL)
570 : return(tmp);
571 339 : (void) MT_remove(pathbuf);
572 339 : return(NULL);
573 : }
574 :
575 : #define SECRETFILE ".secret"
576 : #define SECRET_LENGTH (32)
577 : char *
578 340 : msab_pickSecret(char **generated_secret)
579 : {
580 340 : unsigned char bin_secret[SECRET_LENGTH / 2];
581 340 : char *secret;
582 340 : char pathbuf[FILENAME_MAX];
583 340 : char *e;
584 :
585 340 : if ((e = getDBPath(pathbuf, sizeof(pathbuf), SECRETFILE)) != NULL)
586 : return e;
587 :
588 : // delete existing so we can recreate with appropriate permissions
589 340 : 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 340 : secret = malloc(SECRET_LENGTH + 1);
597 340 : secret[SECRET_LENGTH] = '\0';
598 :
599 : #if defined(HAVE_GETENTROPY)
600 340 : 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 5780 : for (size_t i = 0; i < sizeof(bin_secret); i++) {
642 5440 : snprintf(secret + 2 * i, 3, "%02x", bin_secret[i]);
643 : }
644 :
645 340 : 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 340 : 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 340 : bool error;
662 340 : error = fwrite(secret, 1, SECRET_LENGTH, f) < SECRET_LENGTH;
663 340 : error |= fclose(f) < 0;
664 340 : 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 340 : if (generated_secret)
674 340 : *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 37181 : msab_getMyStatus(sabdb** ret)
687 : {
688 37181 : char *err;
689 37181 : if (_sabaoth_internal_dbname == NULL)
690 0 : return(strdup("sabaoth was not initialized as active database"));
691 37181 : err = msab_getStatus(ret, _sabaoth_internal_dbname);
692 37187 : if (err != NULL)
693 : return(err);
694 37190 : 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 37189 : msab_getSingleStatus(const char *pathbuf, const char *dbname, sabdb *next)
703 : {
704 37189 : char buf[FILENAME_MAX];
705 37189 : char data[8096];
706 37189 : char log[FILENAME_MAX];
707 37189 : FILE *f;
708 37189 : int fd;
709 37189 : struct stat statbuf;
710 :
711 37189 : sabdb *sdb;
712 37189 : sdb = NULL;
713 :
714 37189 : snprintf(buf, sizeof(buf), "%s/%s/%s", pathbuf, dbname, UPLOGFILE);
715 37189 : if (MT_stat(buf, &statbuf) == -1)
716 : return next;
717 :
718 37153 : sdb = malloc(sizeof(sabdb));
719 37153 : *sdb = (sabdb) {
720 : .next = next,
721 : };
722 :
723 : /* store the database name */
724 37153 : int dbnamestart;
725 : #ifdef _MSC_VER
726 : dbnamestart = snprintf(buf, sizeof(buf), "%s/", pathbuf);
727 : snprintf(buf + dbnamestart, sizeof(buf) - dbnamestart, "%s", dbname);
728 : #else
729 37153 : snprintf(buf, sizeof(buf), "%s/%n%s", pathbuf, &dbnamestart, dbname);
730 : #endif
731 37153 : sdb->path = strdup(buf);
732 37153 : sdb->dbname = sdb->path + dbnamestart;
733 :
734 :
735 : /* check the state of the server by looking at its gdk lock:
736 : * - if we can lock it, the server has crashed or isn't running
737 : * - if we can't open it because it's locked, the server is
738 : * running
739 : * - to distinguish between a crash and proper shutdown, consult
740 : * the uplog
741 : * - one exception to all above; if this is the same process, we
742 : * cannot lock (it always succeeds), hence, if we have the
743 : * same signature, we assume running if the uplog states so.
744 : */
745 37153 : snprintf(buf, sizeof(buf), "%s/%s/%s", pathbuf, dbname,
746 : _sabaoth_internal_uuid);
747 37153 : if (MT_stat(buf, &statbuf) == 0) {
748 : /* database has the same process signature as ours, which
749 : * means, it must be us, rely on the uplog state */
750 37185 : snprintf(log, sizeof(log), "%s/%s/%s", pathbuf, dbname, UPLOGFILE);
751 37185 : if ((f = MT_fopen(log, "r")) != NULL) {
752 37094 : (void)fseek(f, -1, SEEK_END);
753 37154 : if (fread(data, 1, 1, f) != 1) {
754 : /* the log is empty, assume no crash */
755 0 : sdb->state = SABdbInactive;
756 37132 : } else if (data[0] == '\t') {
757 : /* see if the database has finished starting */
758 37132 : snprintf(buf, sizeof(buf), "%s/%s/%s",
759 : pathbuf, dbname, STARTEDFILE);
760 37132 : if (MT_stat(buf, &statbuf) == -1) {
761 12 : sdb->state = SABdbStarting;
762 : } else {
763 37099 : sdb->state = SABdbRunning;
764 : }
765 : } else { /* should be \n */
766 0 : sdb->state = SABdbInactive;
767 : }
768 37111 : (void)fclose(f);
769 : }
770 0 : } else if (snprintf(buf, sizeof(buf), "%s/%s/%s", pathbuf, dbname, ".gdk_lock"),
771 0 : ((fd = MT_lockf(buf, F_TLOCK)) == -2)) {
772 : /* Locking failed; this can be because the lockfile couldn't
773 : * be created. Probably there is no Mserver running for
774 : * that case also.
775 : */
776 0 : sdb->state = SABdbInactive;
777 0 : } else if (fd == -1) {
778 : #ifndef WIN32
779 : /* extract process ID from lock file */
780 0 : if ((f = MT_fopen(buf, "r")) != NULL) {
781 0 : int pid;
782 0 : if (fscanf(f, "USR=%*d PID=%d TIME=", &pid) == 1)
783 0 : sdb->pid = pid;
784 0 : fclose(f);
785 : }
786 : #endif
787 : /* see if the database has finished starting */
788 0 : snprintf(buf, sizeof(buf), "%s/%s/%s", pathbuf, dbname, STARTEDFILE);
789 0 : if (MT_stat(buf, &statbuf) == -1) {
790 0 : sdb->state = SABdbStarting;
791 : } else {
792 0 : sdb->state = SABdbRunning;
793 : }
794 : } else {
795 : /* file was not locked (we just locked it), check for a crash
796 : * in the uplog */
797 0 : snprintf(log, sizeof(log), "%s/%s/%s", pathbuf, dbname, STARTEDFILE);
798 : /* just to be sure, remove the .started file */
799 0 : (void) MT_remove(log); /* may fail, that's fine */
800 0 : snprintf(log, sizeof(log), "%s/%s/%s", pathbuf, dbname, UPLOGFILE);
801 0 : if ((f = MT_fopen(log, "r")) != NULL) {
802 0 : (void)fseek(f, -1, SEEK_END);
803 0 : if (fread(data, 1, 1, f) != 1) {
804 : /* the log is empty, assume no crash */
805 0 : sdb->state = SABdbInactive;
806 0 : } else if (data[0] == '\n') {
807 0 : sdb->state = SABdbInactive;
808 : } else { /* should be \t */
809 0 : sdb->state = SABdbCrashed;
810 : }
811 0 : (void)fclose(f);
812 : } else {
813 : /* no uplog, so presumably never started */
814 0 : sdb->state = SABdbInactive;
815 : }
816 0 : MT_lockf(buf, F_ULOCK);
817 0 : close(fd);
818 : }
819 37178 : snprintf(buf, sizeof(buf), "%s/%s/%s", pathbuf, dbname, MAINTENANCEFILE);
820 37178 : sdb->locked = MT_stat(buf, &statbuf) == 0;
821 :
822 : /* add scenarios that are supported */
823 37107 : sdb->scens = NULL;
824 37107 : snprintf(buf, sizeof(buf), "%s/%s/%s", pathbuf, dbname, SCENARIOFILE);
825 37107 : if ((f = MT_fopen(buf, "r")) != NULL) {
826 : sablist* np = NULL;
827 148627 : while (fgets(data, (int) sizeof(data), f) != NULL) {
828 111482 : if (*data != '\0' && data[strlen(data) - 1] == '\n')
829 111470 : data[strlen(data) - 1] = '\0';
830 111482 : if (sdb->scens == NULL) {
831 37113 : np = sdb->scens = malloc(sizeof(sablist));
832 : } else {
833 74369 : np = np->next = malloc(sizeof(sablist));
834 : }
835 111482 : np->val = strdup(data);
836 111482 : np->next = NULL;
837 : }
838 37141 : (void)fclose(f);
839 : }
840 :
841 : /* add how this server can be reached */
842 37185 : sdb->conns = NULL;
843 37185 : snprintf(buf, sizeof(buf), "%s/%s/%s", pathbuf, dbname, CONNECTIONFILE);
844 37185 : if ((f = MT_fopen(buf, "r")) != NULL) {
845 : sablist* np = NULL;
846 111502 : while (fgets(data, (int) sizeof(data), f) != NULL) {
847 74336 : if (*data != '\0' && data[strlen(data) - 1] == '\n')
848 74308 : data[strlen(data) - 1] = '\0';
849 74336 : if (sdb->conns == NULL) {
850 37151 : np = sdb->conns = malloc(sizeof(sablist));
851 : } else {
852 37185 : np = np->next = malloc(sizeof(sablist));
853 : }
854 74336 : np->val = strdup(data);
855 74336 : np->next = NULL;
856 : }
857 37178 : (void)fclose(f);
858 : }
859 :
860 : // read the secret
861 37190 : do {
862 37190 : struct stat stb;
863 37190 : snprintf(buf, sizeof(buf), "%s/%s/%s", pathbuf, dbname, SECRETFILE);
864 37190 : if ((f = MT_fopen(buf, "r")) == NULL)
865 : break;
866 37175 : if (fstat(fileno(f), &stb) < 0) {
867 0 : fclose(f);
868 0 : break;
869 : }
870 37182 : size_t len = (size_t)stb.st_size;
871 37182 : char *secret = malloc(len + 1);
872 37182 : if (!secret) {
873 0 : fclose(f);
874 0 : break;
875 : }
876 37182 : if (fread(secret, 1, len, f) != len) {
877 0 : fclose(f);
878 0 : free(secret);
879 0 : break;
880 : }
881 37069 : fclose(f);
882 37187 : secret[len] = '\0';
883 37187 : sdb->secret = secret;
884 : } while (0);
885 :
886 : return sdb;
887 : }
888 :
889 : /**
890 : * Returns a list of populated sabdb structs. If dbname == NULL, the
891 : * list contains sabdb structs for all found databases in the dbfarm.
892 : * Otherwise, at most one sabdb struct is returned for the database from
893 : * the dbfarm that matches dbname.
894 : * If no database could be found, an empty list is returned. Each list
895 : * is terminated by a NULL entry.
896 : */
897 : char *
898 37175 : msab_getStatus(sabdb** ret, const char *dbname)
899 : {
900 37175 : DIR *d;
901 37175 : struct dirent *e;
902 37175 : char data[8096];
903 37175 : char pathbuf[FILENAME_MAX];
904 37175 : char *p;
905 :
906 : /* Caching strategies (might be nice) should create a new struct with
907 : * the last modified time_t of the files involved, such that a stat is
908 : * sufficient to see if reparsing is necessary. The gdk_lock always has
909 : * to be checked to detect crashes. */
910 :
911 37175 : sabdb *sdb;
912 37175 : sdb = *ret = NULL;
913 :
914 37175 : if (dbname && strpbrk(dbname, "/\\") != NULL) {
915 0 : snprintf(data, sizeof(data),
916 : "database name contains disallowed characters");
917 0 : return strdup(data);
918 : }
919 : /* scan the parent for directories */
920 37175 : if ((p = getFarmPath(pathbuf, sizeof(pathbuf), NULL)) != NULL)
921 : return(p);
922 37178 : if (dbname) {
923 37178 : *ret = msab_getSingleStatus(pathbuf, dbname, NULL);
924 37189 : return NULL;
925 : }
926 :
927 0 : d = opendir(pathbuf);
928 0 : if (d == NULL) {
929 0 : snprintf(data, sizeof(data), "failed to open directory %s: %s",
930 0 : pathbuf, strerror(errno));
931 0 : return(strdup(data));
932 : }
933 0 : while ((e = readdir(d)) != NULL) {
934 0 : if (strcmp(e->d_name, "..") == 0 || strcmp(e->d_name, ".") == 0)
935 0 : continue;
936 :
937 0 : sdb = msab_getSingleStatus(pathbuf, e->d_name, sdb);
938 : }
939 0 : (void)closedir(d);
940 :
941 0 : *ret = sdb;
942 0 : return(NULL);
943 : }
944 :
945 : /**
946 : * Frees up the sabdb structure returned by getStatus.
947 : */
948 : void
949 37180 : msab_freeStatus(sabdb** ret)
950 : {
951 37180 : sabdb *p, *q;
952 37180 : sablist *r, *s;
953 :
954 37180 : p = *ret;
955 74338 : while (p != NULL) {
956 37158 : free(p->path);
957 37158 : free(p->uri);
958 37158 : free(p->secret);
959 37158 : free(p->uplog);
960 37158 : r = p->scens;
961 148713 : while (r != NULL) {
962 111555 : if (r->val != NULL)
963 111555 : free(r->val);
964 111555 : s = r->next;
965 111555 : free(r);
966 111555 : r = s;
967 : }
968 37158 : r = p->conns;
969 111524 : while (r != NULL) {
970 74366 : if (r->val != NULL)
971 74366 : free(r->val);
972 74366 : s = r->next;
973 74366 : free(r);
974 74366 : r = s;
975 : }
976 37158 : q = p->next;
977 37158 : free(p);
978 37158 : p = q;
979 : }
980 37180 : }
981 :
982 : /**
983 : * Parses the .uplog file for the given database, and fills ret with the
984 : * parsed information.
985 : */
986 : char *
987 0 : msab_getUplogInfo(sabuplog *ret, const sabdb *db)
988 : {
989 0 : char log[FILENAME_MAX];
990 0 : char data[24];
991 0 : char *p;
992 0 : FILE *f;
993 0 : time_t start, stop, up;
994 0 : int avg10[10];
995 0 : int avg30[30];
996 0 : int i = 0;
997 :
998 : /* early bailout if cached */
999 0 : if (db->uplog != NULL) {
1000 0 : *ret = *db->uplog;
1001 0 : return(NULL);
1002 : }
1003 :
1004 0 : memset(avg10, 0, sizeof(int) * 10);
1005 0 : memset(avg30, 0, sizeof(int) * 30);
1006 :
1007 : /* clear the struct */
1008 0 : *ret = (sabuplog) {
1009 : .minuptime = -1,
1010 : .lastcrash = -1,
1011 : .laststop = -1,
1012 : };
1013 :
1014 0 : snprintf(log, sizeof(log), "%s/%s", db->path, UPLOGFILE);
1015 0 : if ((f = MT_fopen(log, "r")) != NULL) {
1016 : int c;
1017 0 : start = stop = up = 0;
1018 : p = data;
1019 0 : while ((c = getc(f)) != EOF) {
1020 0 : switch (c) {
1021 0 : case '\t':
1022 : /* start attempt */
1023 0 : ret->startcntr++;
1024 0 : if (start != 0)
1025 0 : ret->lastcrash = start;
1026 0 : memmove(&avg10[0], &avg10[1], sizeof(int) * 9);
1027 0 : memmove(&avg30[0], &avg30[1], sizeof(int) * 29);
1028 0 : avg10[9] = avg30[29] = ret->crashavg1 =
1029 0 : (start != 0);
1030 0 : *p = '\0';
1031 0 : ret->laststart = start = (time_t)atol(data);
1032 0 : p = data;
1033 0 : break;
1034 0 : case '\n':
1035 : /* successful stop */
1036 0 : ret->stopcntr++;
1037 0 : *p = '\0';
1038 0 : ret->laststop = stop = (time_t)atol(data);
1039 0 : p = data;
1040 0 : i = (int) (stop - start);
1041 0 : if (i > ret->maxuptime)
1042 0 : ret->maxuptime = i;
1043 0 : if (ret->minuptime == -1 || ret->minuptime > stop - start)
1044 0 : ret->minuptime = stop - start;
1045 0 : up += i;
1046 0 : start = 0;
1047 0 : break;
1048 0 : default:
1049 : /* timestamp */
1050 0 : *p++ = c;
1051 0 : break;
1052 : }
1053 : }
1054 0 : if (start != 0 && db->state != SABdbRunning)
1055 0 : ret->lastcrash = start;
1056 0 : memmove(&avg10[0], &avg10[1], sizeof(int) * 9);
1057 0 : memmove(&avg30[0], &avg30[1], sizeof(int) * 29);
1058 0 : avg10[9] = avg30[29] = ret->crashavg1 =
1059 0 : (start != 0 ? (db->state != SABdbRunning) : 0);
1060 0 : ret->crashcntr =
1061 0 : ret->startcntr - (db->state == SABdbRunning) -
1062 0 : ret->stopcntr;
1063 0 : for (i = 0; i < 10; i++)
1064 0 : ret->crashavg10 += avg10[i];
1065 0 : ret->crashavg10 = ret->crashavg10 / 10.0;
1066 0 : for (i = 0; i < 30; i++)
1067 0 : ret->crashavg30 += avg30[i];
1068 0 : ret->crashavg30 = ret->crashavg30 / 30.0;
1069 :
1070 0 : if (ret->stopcntr > 0) {
1071 0 : ret->avguptime = (time_t)(((double)up / (double)ret->stopcntr) + 0.5);
1072 : } else {
1073 0 : ret->avguptime = 0;
1074 0 : ret->minuptime = 0;
1075 0 : ret->maxuptime = 0;
1076 : }
1077 0 : (void)fclose(f);
1078 : } else {
1079 0 : char buf[2*FILENAME_MAX];
1080 0 : snprintf(buf, sizeof(buf), "could not open file %s: %s",
1081 0 : log, strerror(errno));
1082 0 : return(strdup(buf));
1083 : }
1084 :
1085 : /* Don't store the sabuplog in the sabdb as there is no good reason
1086 : * to retrieve the sabuplog struct more than once for a database
1087 : * (without refetching the sabdb struct). Currently, only a
1088 : * serialisation/deserialisation of a sabdb struct will prefill the
1089 : * sabuplog struct. */
1090 0 : return(NULL);
1091 : }
1092 :
1093 : /* used in the serialisation to be able to change it in the future */
1094 : #define SABDBVER "2"
1095 :
1096 : /**
1097 : * Produces a string representation suitable for storage/sending.
1098 : */
1099 : char *
1100 0 : msab_serialise(char **ret, const sabdb *db)
1101 : {
1102 0 : char buf[8096];
1103 0 : char scens[64];
1104 0 : sablist *l;
1105 0 : sabuplog dbu;
1106 0 : char *p;
1107 0 : size_t avail;
1108 0 : size_t len;
1109 :
1110 0 : scens[0] = '\0';
1111 0 : p = scens;
1112 0 : avail = sizeof(scens) - 1;
1113 0 : for (l = db->scens; l != NULL; l = l->next) {
1114 0 : len = strlen(l->val);
1115 0 : if (len > avail)
1116 : break;
1117 0 : memcpy(p, l->val, len);
1118 0 : p += len + 1;
1119 0 : avail -= len + 1;
1120 0 : memcpy(p - 1, "'", 2);
1121 : }
1122 0 : if (p != scens)
1123 0 : p[-1] = '\0';
1124 :
1125 0 : if ((p = msab_getUplogInfo(&dbu, db)) != NULL)
1126 : return(p);
1127 :
1128 : /* sabdb + sabuplog structs in one */
1129 0 : snprintf(buf, sizeof(buf), "sabdb:" SABDBVER ":"
1130 : "%s,%s,%d,%d,%s,"
1131 : "%d,%d,%d,"
1132 : "%" PRId64 ",%" PRId64 ",%" PRId64 ","
1133 : "%" PRId64 ",%" PRId64 ",%" PRId64 ","
1134 : "%d,%f,%f",
1135 0 : db->dbname, db->uri ? db->uri : "", db->locked,
1136 0 : (int) db->state, scens,
1137 : dbu.startcntr, dbu.stopcntr, dbu.crashcntr,
1138 0 : (int64_t) dbu.avguptime, (int64_t) dbu.maxuptime,
1139 0 : (int64_t) dbu.minuptime, (int64_t) dbu.lastcrash,
1140 0 : (int64_t) dbu.laststart, (int64_t) dbu.laststop,
1141 : dbu.crashavg1, dbu.crashavg10, dbu.crashavg30);
1142 :
1143 0 : *ret = strdup(buf);
1144 0 : return(NULL);
1145 : }
1146 :
1147 : /**
1148 : * Produces a sabdb struct out of a serialised string.
1149 : */
1150 : char *
1151 0 : msab_deserialise(sabdb **ret, const char *sdb)
1152 : {
1153 0 : char *dbname;
1154 0 : char *uri;
1155 0 : char *scens;
1156 0 : sabdb *s;
1157 0 : sabuplog *u;
1158 0 : const char *lasts;
1159 0 : char buf[FILENAME_MAX];
1160 :
1161 0 : if (strncmp(sdb, "sabdb:", 6) != 0) {
1162 0 : snprintf(buf, sizeof(buf),
1163 : "string is not a sabdb struct: %s", sdb);
1164 0 : return(strdup(buf));
1165 : }
1166 0 : sdb += 6;
1167 : /* Protocol 1 was used uptil Oct2012 and is no longer supported.
1168 : * Since Jul2012 a new state
1169 : * SABdbStarting was introduced, but not exposed to the client
1170 : * in serialise. In Feb2013, the path component was removed
1171 : * and replaced by a URI field. This meant dbname could no
1172 : * longer be deduced from path, and hence sent separately.
1173 : * Since the conns property became useless in the light of the
1174 : * added uri, it was dropped. On top of this, a laststop
1175 : * property was added to the uplog struct.
1176 : * These four changes were effectuated in protocol 2. When
1177 : * reading protocol 1, we use the path field to set dbname, but
1178 : * ignore the path information (and set uri to "<unknown>". The
1179 : * SABdbStarting state never occurs. */
1180 0 : if (strncmp(sdb, SABDBVER ":", sizeof(SABDBVER)) != 0) {
1181 0 : snprintf(buf, sizeof(buf),
1182 : "string has unsupported version: %s", sdb);
1183 0 : return(strdup(buf));
1184 : }
1185 0 : sdb += sizeof(SABDBVER);
1186 0 : lasts = strchr(sdb, ',');
1187 0 : if (lasts == NULL) {
1188 0 : snprintf(buf, sizeof(buf),
1189 : "string does not contain dbname: %s", sdb);
1190 0 : return(strdup(buf));
1191 : }
1192 0 : dbname = malloc(lasts - sdb + 1);
1193 0 : strcpy_len(dbname, sdb, lasts - sdb + 1);
1194 0 : sdb = ++lasts;
1195 0 : lasts = strchr(sdb, ',');
1196 0 : if (lasts == NULL) {
1197 0 : snprintf(buf, sizeof(buf),
1198 : "string does not contain uri: %s", sdb);
1199 0 : free(dbname);
1200 0 : return(strdup(buf));
1201 : }
1202 0 : uri = malloc(lasts - sdb + 1);
1203 0 : strcpy_len(uri, sdb, lasts - sdb + 1);
1204 0 : sdb = ++lasts;
1205 0 : int locked, state, n;
1206 0 : switch (sscanf(sdb, "%d,%d%n", &locked, &state, &n)) {
1207 0 : case 0:
1208 0 : free(uri);
1209 0 : free(dbname);
1210 0 : snprintf(buf, sizeof(buf),
1211 : "string does not contain locked state: %s", lasts);
1212 0 : return(strdup(buf));
1213 0 : case 1:
1214 0 : free(uri);
1215 0 : free(dbname);
1216 0 : snprintf(buf, sizeof(buf),
1217 : "string does not contain state: %s", lasts);
1218 0 : return(strdup(buf));
1219 0 : case -1:
1220 0 : free(uri);
1221 0 : free(dbname);
1222 0 : return strdup("should not happen");
1223 : default:
1224 0 : break;
1225 : }
1226 0 : sdb += n;
1227 0 : if (*sdb++ != ',' || (lasts = strchr(sdb, ',')) == NULL) {
1228 0 : snprintf(buf, sizeof(buf),
1229 : "string does not contain scenarios: %s", lasts);
1230 0 : free(uri);
1231 0 : free(dbname);
1232 0 : return(strdup(buf));
1233 : }
1234 0 : if (lasts > sdb) {
1235 0 : scens = malloc(lasts - sdb + 1);
1236 0 : strcpy_len(scens, sdb, lasts - sdb + 1);
1237 : } else {
1238 : scens = NULL;
1239 : }
1240 0 : sdb = ++lasts;
1241 0 : int startcntr, stopcntr, crashcntr;
1242 0 : int64_t avguptime, maxuptime, minuptime, lastcrash, laststart, laststop;
1243 0 : int crashavg1;
1244 0 : double crashavg10, crashavg30;
1245 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)) {
1246 0 : case -1:
1247 0 : free(dbname);
1248 0 : free(uri);
1249 0 : free(scens);
1250 0 : return strdup("should not happen");
1251 0 : case 0:
1252 0 : snprintf(buf, sizeof(buf),
1253 : "string does not contain startcounter: %s", sdb);
1254 0 : goto bailout;
1255 0 : case 1:
1256 0 : snprintf(buf, sizeof(buf),
1257 : "string does not contain stopcounter: %s", sdb);
1258 0 : goto bailout;
1259 0 : case 2:
1260 0 : snprintf(buf, sizeof(buf),
1261 : "string does not contain crashcounter: %s", sdb);
1262 0 : goto bailout;
1263 0 : case 3:
1264 0 : snprintf(buf, sizeof(buf),
1265 : "string does not contain avguptime: %s", sdb);
1266 0 : goto bailout;
1267 0 : case 4:
1268 0 : snprintf(buf, sizeof(buf),
1269 : "string does not contain maxuptime: %s", sdb);
1270 0 : goto bailout;
1271 0 : case 5:
1272 0 : snprintf(buf, sizeof(buf),
1273 : "string does not contain minuptime: %s", sdb);
1274 0 : goto bailout;
1275 0 : case 6:
1276 0 : snprintf(buf, sizeof(buf),
1277 : "string does not contain lastcrash: %s", sdb);
1278 0 : goto bailout;
1279 0 : case 7:
1280 0 : snprintf(buf, sizeof(buf),
1281 : "string does not contain laststart: %s", sdb);
1282 0 : goto bailout;
1283 0 : case 8:
1284 0 : snprintf(buf, sizeof(buf),
1285 : "string does not contain laststop: %s", sdb);
1286 0 : goto bailout;
1287 0 : case 9:
1288 0 : snprintf(buf, sizeof(buf),
1289 : "string does not contain crashavg1: %s", sdb);
1290 0 : goto bailout;
1291 0 : case 10:
1292 0 : snprintf(buf, sizeof(buf),
1293 : "string does not contain crashavg10: %s", sdb);
1294 0 : goto bailout;
1295 0 : case 11:
1296 0 : snprintf(buf, sizeof(buf),
1297 : "string does not contain crashavg30: %s", sdb);
1298 0 : goto bailout;
1299 : case 12:
1300 : break;
1301 : }
1302 0 : sdb += n;
1303 0 : if (*sdb) {
1304 0 : snprintf(buf, sizeof(buf),
1305 : "string contains additional garbage after crashavg30: %s",
1306 : sdb);
1307 0 : goto bailout;
1308 : }
1309 :
1310 0 : u = malloc(sizeof(sabuplog));
1311 0 : s = malloc(sizeof(sabdb));
1312 0 : *u = (sabuplog) {
1313 : .startcntr = startcntr,
1314 : .stopcntr = stopcntr,
1315 : .crashcntr = crashcntr,
1316 0 : .avguptime = (time_t) avguptime,
1317 0 : .maxuptime = (time_t) maxuptime,
1318 0 : .minuptime = (time_t) minuptime,
1319 0 : .lastcrash = (time_t) lastcrash,
1320 0 : .laststart = (time_t) laststart,
1321 0 : .laststop = (time_t) laststop,
1322 : .crashavg1 = crashavg1,
1323 : .crashavg10 = crashavg10,
1324 : .crashavg30 = crashavg30,
1325 : };
1326 0 : *s = (sabdb) {
1327 : .dbname = dbname,
1328 : .path = dbname,
1329 : .uri = uri,
1330 : .locked = locked,
1331 0 : .state = (SABdbState) state,
1332 : .uplog = u,
1333 : };
1334 0 : if (scens) {
1335 0 : sablist **sp = &s->scens;
1336 0 : char *sc = scens;
1337 0 : while (sc) {
1338 0 : *sp = malloc(sizeof(sablist));
1339 0 : char *p = strchr(sc, '\'');
1340 0 : if (p)
1341 0 : *p++ = 0;
1342 0 : **sp = (sablist) {
1343 0 : .val = strdup(sc),
1344 : };
1345 0 : sc = p;
1346 0 : sp = &(*sp)->next;
1347 : }
1348 0 : free(scens);
1349 : }
1350 :
1351 0 : *ret = s;
1352 0 : return(NULL);
1353 0 : bailout:
1354 0 : free(dbname);
1355 0 : free(uri);
1356 0 : free(scens);
1357 0 : return strdup(buf);
1358 : }
|