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