LCOV - code coverage report
Current view: top level - common/utils - msabaoth.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 304 699 43.5 %
Date: 2024-04-26 00:35:57 Functions: 19 25 76.0 %

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

Generated by: LCOV version 1.14