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

Generated by: LCOV version 1.14