| Directory: | ./ |
|---|---|
| File: | pdserv/src/Database.cpp |
| Date: | 2025-10-26 04:10:09 |
| Exec | Total | Coverage | |
|---|---|---|---|
| Lines: | 75 | 98 | 76.5% |
| Branches: | 41 | 330 | 12.4% |
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | /***************************************************************************** | ||
| 2 | * | ||
| 3 | * Copyright 2015 Richard Hacker (lerichi at gmx dot net) | ||
| 4 | * | ||
| 5 | * This file is part of the pdserv library. | ||
| 6 | * | ||
| 7 | * The pdserv library is free software: you can redistribute it and/or modify | ||
| 8 | * it under the terms of the GNU Lesser General Public License as published | ||
| 9 | * by the Free Software Foundation, either version 3 of the License, or (at | ||
| 10 | * your option) any later version. | ||
| 11 | * | ||
| 12 | * The pdserv library is distributed in the hope that it will be useful, but | ||
| 13 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY | ||
| 14 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public | ||
| 15 | * License for more details. | ||
| 16 | * | ||
| 17 | * You should have received a copy of the GNU Lesser General Public License | ||
| 18 | * along with the pdserv library. If not, see <http://www.gnu.org/licenses/>. | ||
| 19 | * | ||
| 20 | ****************************************************************************/ | ||
| 21 | |||
| 22 | #include "config.h" | ||
| 23 | |||
| 24 | #include "Database.h" | ||
| 25 | #include "Parameter.h" | ||
| 26 | #include "Debug.h" | ||
| 27 | |||
| 28 | #include <db.h> | ||
| 29 | #include <cstring> | ||
| 30 | #include <ctime> | ||
| 31 | #include <log4cplus/logger.h> | ||
| 32 | #include <log4cplus/loggingmacros.h> | ||
| 33 | |||
| 34 | using namespace PdServ; | ||
| 35 | |||
| 36 | struct pdata { | ||
| 37 | uint8_t version; | ||
| 38 | uint8_t classid; | ||
| 39 | size_t nelem; | ||
| 40 | struct timespec mtime; | ||
| 41 | char value[]; | ||
| 42 | }; | ||
| 43 | |||
| 44 | ///////////////////////////////////////////////////////////////////////////// | ||
| 45 | 12 | Database::Database(const log4cplus::Logger& l, const std::string& path): | |
| 46 | 12 | log(l) | |
| 47 | { | ||
| 48 | int ret; | ||
| 49 | |||
| 50 | 12 | db = 0; | |
| 51 |
1/2✗ Branch 2 not taken.
✓ Branch 3 taken 12 times.
|
12 | if (path.empty()) |
| 52 | ✗ | return; | |
| 53 | |||
| 54 | 12 | ret = db_create(&db, NULL, 0); | |
| 55 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
|
12 | if (ret) { |
| 56 | // System error | ||
| 57 | ✗ | LOG4CPLUS_ERROR(log, | |
| 58 | LOG4CPLUS_TEXT("Fatal BDB system error: db_create(): ") | ||
| 59 | << LOG4CPLUS_C_STR_TO_TSTRING(db_strerror(ret))); | ||
| 60 | ✗ | db = 0; | |
| 61 | ✗ | return; | |
| 62 | } | ||
| 63 | |||
| 64 | 12 | ret = db->open(db, NULL, path.c_str(), NULL, DB_BTREE, DB_CREATE, 0666); | |
| 65 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
|
12 | if (ret != 0) { |
| 66 | ✗ | LOG4CPLUS_ERROR(log, | |
| 67 | LOG4CPLUS_TEXT("db->open(): ") | ||
| 68 | << LOG4CPLUS_C_STR_TO_TSTRING(db_strerror(ret)) | ||
| 69 | << ' ' | ||
| 70 | << LOG4CPLUS_STRING_TO_TSTRING(path)); | ||
| 71 | ✗ | close(); | |
| 72 | } | ||
| 73 | } | ||
| 74 | |||
| 75 | ///////////////////////////////////////////////////////////////////////////// | ||
| 76 | 24 | Database::~Database() | |
| 77 | { | ||
| 78 | 12 | close(); | |
| 79 | 12 | } | |
| 80 | |||
| 81 | ///////////////////////////////////////////////////////////////////////////// | ||
| 82 | 5 | Database::operator bool() const | |
| 83 | { | ||
| 84 | 5 | return db; | |
| 85 | } | ||
| 86 | |||
| 87 | ///////////////////////////////////////////////////////////////////////////// | ||
| 88 | 12 | void Database::close() | |
| 89 | { | ||
| 90 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 12 times.
|
12 | if (!db) |
| 91 | ✗ | return; | |
| 92 | |||
| 93 | 12 | int ret = db->close(db, 0); | |
| 94 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
|
12 | if (ret) { |
| 95 | // Complain | ||
| 96 | ✗ | LOG4CPLUS_ERROR(log, | |
| 97 | LOG4CPLUS_TEXT("db->close(): ") | ||
| 98 | << LOG4CPLUS_C_STR_TO_TSTRING(db_strerror(ret))); | ||
| 99 | } | ||
| 100 | |||
| 101 | 12 | db = 0; | |
| 102 | } | ||
| 103 | |||
| 104 | ///////////////////////////////////////////////////////////////////////////// | ||
| 105 | 6 | bool Database::read(const Parameter* p, | |
| 106 | const char** buf, const struct timespec** mtime) const | ||
| 107 | { | ||
| 108 | 6 | *buf = 0; | |
| 109 | |||
| 110 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
|
6 | if (!db) |
| 111 | ✗ | return false; | |
| 112 | |||
| 113 | 6 | DBT key, value; | |
| 114 | |||
| 115 | // Clear the DBT structures | ||
| 116 | 6 | ::memset(&key, 0, sizeof( key)); | |
| 117 | 6 | ::memset(&value, 0, sizeof(value)); | |
| 118 | |||
| 119 | // Set key structure for path | ||
| 120 |
1/2✗ Branch 2 not taken.
✓ Branch 3 taken 6 times.
|
6 | char pathBuf[p->path.size()]; |
| 121 |
2/4✗ Branch 4 not taken.
✓ Branch 5 taken 6 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 6 times.
|
6 | ::memcpy(pathBuf, p->path.data(), p->path.size()); |
| 122 | 6 | key.data = pathBuf; | |
| 123 | 6 | key.size = p->path.size(); | |
| 124 | |||
| 125 | // Fetch the data | ||
| 126 |
1/2✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
|
6 | int ret = db->get(db, NULL, &key, &value, 0); |
| 127 | 6 | const struct pdata *data = (struct pdata*)value.data; | |
| 128 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 4 times.
|
6 | if (ret != 0) { |
| 129 | // Only complain if the key is found and there is some other error | ||
| 130 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
2 | if (ret != DB_NOTFOUND) |
| 131 | ✗ | LOG4CPLUS_ERROR(log, | |
| 132 | LOG4CPLUS_TEXT("db->get(): ") | ||
| 133 | << LOG4CPLUS_C_STR_TO_TSTRING(db_strerror(ret))); | ||
| 134 | else | ||
| 135 | LOG4CPLUS_TRACE(log, | ||
| 136 | LOG4CPLUS_TEXT("Key does not exist: ") | ||
| 137 | << LOG4CPLUS_STRING_TO_TSTRING(p->path)); | ||
| 138 | } | ||
| 139 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
|
4 | else if (value.size < sizeof(struct pdata)) { |
| 140 | ✗ | LOG4CPLUS_ERROR(log, | |
| 141 | LOG4CPLUS_TEXT("Header length insufficient: ") | ||
| 142 | << value.size << " instead of at least " | ||
| 143 | << sizeof(struct pdata)); | ||
| 144 | } | ||
| 145 |
1/2✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
|
4 | else if (data->version == 1) { |
| 146 | 4 | const size_t len = sizeof(struct pdata) + p->memSize; | |
| 147 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
|
4 | if (value.size < len) { |
| 148 | ✗ | LOG4CPLUS_WARN(log, | |
| 149 | LOG4CPLUS_TEXT("Data size changed: ") | ||
| 150 | << LOG4CPLUS_STRING_TO_TSTRING(p->path)); | ||
| 151 | } | ||
| 152 |
2/4✓ Branch 16 taken 4 times.
✗ Branch 17 not taken.
✗ Branch 18 not taken.
✓ Branch 19 taken 4 times.
|
4 | else if (data->classid != p->dtype.primary()) { |
| 153 | ✗ | LOG4CPLUS_WARN(log, | |
| 154 | LOG4CPLUS_TEXT("Data type changed: ") | ||
| 155 | << LOG4CPLUS_STRING_TO_TSTRING(p->path)); | ||
| 156 | } | ||
| 157 |
1/2✗ Branch 4 not taken.
✓ Branch 5 taken 4 times.
|
4 | else if (data->nelem != p->dim.nelem) { |
| 158 | ✗ | LOG4CPLUS_WARN(log, | |
| 159 | LOG4CPLUS_TEXT("Element count changed: ") | ||
| 160 | << LOG4CPLUS_STRING_TO_TSTRING(p->path)); | ||
| 161 | } | ||
| 162 | else { | ||
| 163 | 4 | *buf = data->value; | |
| 164 | 4 | *mtime = &data->mtime; | |
| 165 | 4 | return true; | |
| 166 | } | ||
| 167 | } | ||
| 168 | else { | ||
| 169 | ✗ | LOG4CPLUS_ERROR(log, | |
| 170 | LOG4CPLUS_TEXT("Unknown header version: ") | ||
| 171 | << data->version); | ||
| 172 | } | ||
| 173 | |||
| 174 | 8 | return false; | |
| 175 | } | ||
| 176 | |||
| 177 | ///////////////////////////////////////////////////////////////////////////// | ||
| 178 | 4 | void Database::save(const Parameter* p, | |
| 179 | const char* buf, const struct timespec* mtime) | ||
| 180 | { | ||
| 181 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
|
4 | if (!db) |
| 182 | ✗ | return; | |
| 183 | |||
| 184 | 4 | DBT key, value; | |
| 185 | |||
| 186 | // Clear the DBT structures | ||
| 187 | 4 | ::memset(&key, 0, sizeof( key)); | |
| 188 | 4 | ::memset(&value, 0, sizeof(value)); | |
| 189 | |||
| 190 | // Set key structure for path | ||
| 191 |
1/2✗ Branch 2 not taken.
✓ Branch 3 taken 4 times.
|
4 | char pathBuf[p->path.size()]; |
| 192 |
2/4✗ Branch 4 not taken.
✓ Branch 5 taken 4 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 4 times.
|
4 | ::memcpy(pathBuf, p->path.data(), p->path.size()); |
| 193 | 4 | key.data = pathBuf; | |
| 194 | 4 | key.size = p->path.size(); | |
| 195 | |||
| 196 | // Set value structure | ||
| 197 | 4 | const size_t buflen = sizeof(struct pdata) + p->memSize; | |
| 198 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
|
4 | char valueBuf[buflen]; |
| 199 | 4 | struct pdata* data = (struct pdata*)valueBuf; | |
| 200 | 4 | data->version = 1; | |
| 201 |
1/2✓ Branch 15 taken 4 times.
✗ Branch 16 not taken.
|
4 | data->classid = p->dtype.primary(); |
| 202 | 4 | data->mtime = *mtime; | |
| 203 | 4 | data->nelem = p->dim.nelem; | |
| 204 |
2/4✗ Branch 3 not taken.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 4 times.
|
4 | ::memcpy(data->value, buf, p->memSize); |
| 205 | 4 | value.data = valueBuf; | |
| 206 | 4 | value.size = buflen; | |
| 207 | |||
| 208 |
1/2✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
|
4 | int ret = db->put(db, NULL, &key, &value, DB_AUTO_COMMIT); |
| 209 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
|
4 | if (ret != 0) { |
| 210 | // Complain | ||
| 211 | ✗ | LOG4CPLUS_ERROR(log, | |
| 212 | LOG4CPLUS_TEXT("db->put(): ") | ||
| 213 | << LOG4CPLUS_C_STR_TO_TSTRING(db_strerror(ret))); | ||
| 214 | 4 | } | |
| 215 | } | ||
| 216 | |||
| 217 | ///////////////////////////////////////////////////////////////////////////// | ||
| 218 | 6 | void Database::checkKeys(const std::set<std::string>& keySet) | |
| 219 | { | ||
| 220 | 6 | DBT key, value; | |
| 221 | 6 | DBC *dbcp; | |
| 222 | int ret; | ||
| 223 | |||
| 224 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
|
6 | if (!db) |
| 225 | ✗ | return; | |
| 226 | |||
| 227 | LOG4CPLUS_TRACE(log, LOG4CPLUS_TEXT("Cleaning up database")); | ||
| 228 | |||
| 229 |
2/4✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 6 times.
|
6 | if ((ret = db->cursor(db, NULL, &dbcp, 0)) != 0) { |
| 230 | ✗ | LOG4CPLUS_ERROR(log, | |
| 231 | LOG4CPLUS_TEXT("db->cursor(): ") | ||
| 232 | << LOG4CPLUS_C_STR_TO_TSTRING(db_strerror(ret))); | ||
| 233 | ✗ | return; | |
| 234 | } | ||
| 235 | |||
| 236 | // Clear the DBT structures | ||
| 237 | 6 | ::memset(&key, 0, sizeof( key)); | |
| 238 | 6 | ::memset(&value, 0, sizeof(value)); | |
| 239 | |||
| 240 |
3/4✓ Branch 2 taken 10 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 4 times.
✓ Branch 5 taken 6 times.
|
10 | while ((ret = dbcp->c_get(dbcp, &key, &value, DB_NEXT)) == 0) { |
| 241 |
1/2✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
|
8 | std::string _key((const char*)key.data, key.size); |
| 242 | LOG4CPLUS_TRACE(log, | ||
| 243 | LOG4CPLUS_TEXT("check key ") | ||
| 244 | << LOG4CPLUS_STRING_TO_TSTRING(_key)); | ||
| 245 |
2/4✓ Branch 5 taken 4 times.
✗ Branch 6 not taken.
✗ Branch 13 not taken.
✓ Branch 14 taken 4 times.
|
4 | if (keySet.find(_key) == keySet.end()) { |
| 246 | ✗ | if ((ret = dbcp->c_del(dbcp, 0)) == 0) { | |
| 247 | LOG4CPLUS_TRACE(log, | ||
| 248 | LOG4CPLUS_TEXT("Erased key ") | ||
| 249 | << LOG4CPLUS_STRING_TO_TSTRING(_key)); | ||
| 250 | } | ||
| 251 | else | ||
| 252 | ✗ | LOG4CPLUS_ERROR(log, | |
| 253 | LOG4CPLUS_TEXT("db->c_del(): ") | ||
| 254 | << LOG4CPLUS_C_STR_TO_TSTRING(db_strerror(ret))); | ||
| 255 | } | ||
| 256 | } | ||
| 257 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
|
6 | if (ret != DB_NOTFOUND) { |
| 258 | ✗ | LOG4CPLUS_ERROR(log, | |
| 259 | LOG4CPLUS_TEXT("db->c_get(): ") | ||
| 260 | << LOG4CPLUS_C_STR_TO_TSTRING(db_strerror(ret))); | ||
| 261 | } | ||
| 262 | } | ||
| 263 |