Bug Summary

File:home/fedora/workspace/kea-dev/clang-static-analyzer/src/lib/mysql/mysql_connection.cc
Warning:line 65, column 17
Value stored to 'host' during its initialization is never read

Annotated Source Code

Press '?' to see keyboard shortcuts

clang -cc1 -cc1 -triple x86_64-redhat-linux-gnu -analyze -disable-free -clear-ast-before-backend -disable-llvm-verifier -discard-value-names -main-file-name mysql_connection.cc -analyzer-checker=core -analyzer-checker=apiModeling -analyzer-checker=unix -analyzer-checker=deadcode -analyzer-checker=cplusplus -analyzer-checker=security.insecureAPI.UncheckedReturn -analyzer-checker=security.insecureAPI.getpw -analyzer-checker=security.insecureAPI.gets -analyzer-checker=security.insecureAPI.mktemp -analyzer-checker=security.insecureAPI.mkstemp -analyzer-checker=security.insecureAPI.vfork -analyzer-checker=nullability.NullPassedToNonnull -analyzer-checker=nullability.NullReturnedFromNonnull -analyzer-output plist -w -setup-static-analyzer -mrelocation-model pic -pic-level 2 -fhalf-no-semantic-interposition -mframe-pointer=all -fmath-errno -ffp-contract=on -fno-rounding-math -mconstructor-aliases -funwind-tables=2 -target-cpu x86-64 -tune-cpu generic -debugger-tuning=gdb -fcoverage-compilation-dir=/home/fedora/workspace/kea-dev/clang-static-analyzer/src/lib/mysql -resource-dir /usr/lib64/clang/16 -D HAVE_CONFIG_H -I . -I ../../.. -D KEA_ADMIN="/home/fedora/workspace/kea-dev/clang-static-analyzer/kea-bin/sbin/kea-admin" -I ../../../src/lib -I ../../../src/lib -I /usr/include/mysql -I /usr/include/mysql/mysql -D OS_LINUX -I ../../.. -I ../../.. -D PIC -internal-isystem /usr/bin/../lib/gcc/x86_64-redhat-linux/13/../../../../include/c++/13 -internal-isystem /usr/bin/../lib/gcc/x86_64-redhat-linux/13/../../../../include/c++/13/x86_64-redhat-linux -internal-isystem /usr/bin/../lib/gcc/x86_64-redhat-linux/13/../../../../include/c++/13/backward -internal-isystem /usr/lib64/clang/16/include -internal-isystem /usr/local/include -internal-isystem /usr/bin/../lib/gcc/x86_64-redhat-linux/13/../../../../x86_64-redhat-linux/include -internal-externc-isystem /include -internal-externc-isystem /usr/include -O0 -Wwrite-strings -Wno-sign-compare -Wno-missing-field-initializers -std=c++20 -fdeprecated-macro -fdebug-compilation-dir=/home/fedora/workspace/kea-dev/clang-static-analyzer/src/lib/mysql -ferror-limit 19 -stack-protector 2 -fgnuc-version=4.2.1 -fno-implicit-modules -fcxx-exceptions -fexceptions -analyzer-output=html -faddrsig -D__GCC_HAVE_DWARF2_CFI_ASM=1 -o /home/fedora/workspace/kea-dev/clang-static-analyzer/report/2024-05-17-164749-18469-1 -x c++ mysql_connection.cc
1// Copyright (C) 2012-2024 Internet Systems Consortium, Inc. ("ISC")
2//
3// This Source Code Form is subject to the terms of the Mozilla Public
4// License, v. 2.0. If a copy of the MPL was not distributed with this
5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7#include <config.h>
8
9#include <asiolink/io_service.h>
10#include <asiolink/process_spawn.h>
11#include <database/database_connection.h>
12#include <database/db_log.h>
13#include <exceptions/exceptions.h>
14#include <mysql/mysql_connection.h>
15#include <util/filesystem.h>
16
17#include <boost/lexical_cast.hpp>
18
19#include <cstdint>
20#include <exception>
21#include <limits>
22#include <string>
23#include <unordered_map>
24
25using namespace isc;
26using namespace isc::asiolink;
27using namespace std;
28
29namespace isc {
30namespace db {
31
32std::string MySqlConnection::KEA_ADMIN_ = KEA_ADMIN"/home/fedora/workspace/kea-dev/clang-static-analyzer/kea-bin/sbin/kea-admin";
33
34int MySqlHolder::atexit_ = [] {
35 return atexit([] { mysql_library_endmysql_server_end(); });
36}();
37
38/// @todo: Migrate this default value to src/bin/dhcpX/simple_parserX.cc
39const int MYSQL_DEFAULT_CONNECTION_TIMEOUT = 5; // seconds
40
41MySqlTransaction::MySqlTransaction(MySqlConnection& conn)
42 : conn_(conn), committed_(false) {
43 conn_.startTransaction();
44}
45
46MySqlTransaction::~MySqlTransaction() {
47 // Rollback if the MySqlTransaction::commit wasn't explicitly
48 // called.
49 if (!committed_) {
50 conn_.rollback();
51 }
52}
53
54void
55MySqlTransaction::commit() {
56 conn_.commit();
57 committed_ = true;
58}
59
60// Open the database using the parameters passed to the constructor.
61
62void
63MySqlConnection::openDatabase() {
64 // Set up the values of the parameters
65 const char* host = "localhost";
Value stored to 'host' during its initialization is never read
66 string shost;
67 try {
68 shost = getParameter("host");
69 host = shost.c_str();
70 } catch (...) {
71 // No host. Fine, we'll use "localhost"
72 }
73
74 unsigned int port = 0;
75 try {
76 setIntParameterValue("port", 0, numeric_limits<uint16_t>::max(), port);
77
78 } catch (const std::exception& ex) {
79 isc_throw(DbInvalidPort, ex.what())do { std::ostringstream oss__; oss__ << ex.what(); throw
DbInvalidPort("mysql_connection.cc", 79, oss__.str().c_str()
); } while (1)
;
80 }
81
82 const char* user = NULL__null;
83 string suser;
84 try {
85 suser = getParameter("user");
86 user = suser.c_str();
87 } catch (...) {
88 // No user. Fine, we'll use NULL
89 }
90
91 const char* password = NULL__null;
92 string spassword;
93 try {
94 spassword = getParameter("password");
95 password = spassword.c_str();
96 } catch (...) {
97 // No password. Fine, we'll use NULL
98 }
99
100 const char* name = NULL__null;
101 string sname;
102 try {
103 sname = getParameter("name");
104 name = sname.c_str();
105 } catch (...) {
106 // No database name. Throw a "NoName" exception
107 isc_throw(NoDatabaseName, "must specify a name for the database")do { std::ostringstream oss__; oss__ << "must specify a name for the database"
; throw NoDatabaseName("mysql_connection.cc", 107, oss__.str(
).c_str()); } while (1)
;
108 }
109
110 unsigned int connect_timeout = MYSQL_DEFAULT_CONNECTION_TIMEOUT;
111 unsigned int read_timeout = 0;
112 unsigned int write_timeout = 0;
113 try {
114 // The timeout is only valid if greater than zero, as depending on the
115 // database, a zero timeout might signify something like "wait
116 // indefinitely".
117 setIntParameterValue("connect-timeout", 1, numeric_limits<int>::max(), connect_timeout);
118 // Other timeouts can be 0, meaning that the database client will follow a default
119 // behavior. Earlier MySQL versions didn't have these parameters, so we allow 0
120 // to skip setting them.
121 setIntParameterValue("read-timeout", 0, numeric_limits<int>::max(), read_timeout);
122 setIntParameterValue("write-timeout", 0, numeric_limits<int>::max(), write_timeout);
123
124 } catch (const std::exception& ex) {
125 isc_throw(DbInvalidTimeout, ex.what())do { std::ostringstream oss__; oss__ << ex.what(); throw
DbInvalidTimeout("mysql_connection.cc", 125, oss__.str().c_str
()); } while (1)
;
126 }
127
128 const char* ca_file(0);
129 const char* ca_dir(0);
130 string sca;
131 try {
132 sca = getParameter("trust-anchor");
133 tls_ = true;
134 if (util::file::isDir(sca)) {
135 ca_dir = sca.c_str();
136 } else {
137 ca_file = sca.c_str();
138 }
139 } catch (...) {
140 // No trust anchor
141 }
142
143 const char* cert_file(0);
144 string scert;
145 try {
146 scert = getParameter("cert-file");
147 tls_ = true;
148 cert_file = scert.c_str();
149 } catch (...) {
150 // No client certificate file
151 }
152
153 const char* key_file(0);
154 string skey;
155 try {
156 skey = getParameter("key-file");
157 tls_ = true;
158 key_file = skey.c_str();
159 } catch (...) {
160 // No private key file
161 }
162
163 const char* cipher_list(0);
164 string scipher;
165 try {
166 scipher = getParameter("cipher-list");
167 tls_ = true;
168 cipher_list = scipher.c_str();
169 } catch (...) {
170 // No cipher list
171 }
172
173 // Set options for the connection:
174 //
175 int result;
176#ifdef HAS_MYSQL_OPT_RECONNECT
177 // Though still supported by Mariadb (as of 11.5.0), MYSQL_OPT_RECONNECT is
178 // deprecated as of MySQL 8.0.34. Where it is still supported we should
179 // continue to ensure it is off. Enabling it leaves us with an unusable
180 // connection after a reconnect as among other things, it drops all our
181 // pre-compiled statements.
182 my_bool auto_reconnect = MLM_FALSE;
183 result = mysql_options(mysql_, MYSQL_OPT_RECONNECT, &auto_reconnect);
184 if (result != 0) {
185 isc_throw(DbOpenError, "unable to set auto-reconnect option: " <<do { std::ostringstream oss__; oss__ << "unable to set auto-reconnect option: "
<< mysql_error(mysql_); throw DbOpenError("mysql_connection.cc"
, 186, oss__.str().c_str()); } while (1)
186 mysql_error(mysql_))do { std::ostringstream oss__; oss__ << "unable to set auto-reconnect option: "
<< mysql_error(mysql_); throw DbOpenError("mysql_connection.cc"
, 186, oss__.str().c_str()); } while (1)
;
187 }
188#endif
189
190 // Make sure we have a large idle time window ... say 30 days...
191 const char *wait_time = "SET SESSION wait_timeout = 30 * 86400";
192 result = mysql_options(mysql_, MYSQL_INIT_COMMAND, wait_time);
193 if (result != 0) {
194 isc_throw(DbOpenError, "unable to set wait_timeout " <<do { std::ostringstream oss__; oss__ << "unable to set wait_timeout "
<< mysql_error(mysql_); throw DbOpenError("mysql_connection.cc"
, 195, oss__.str().c_str()); } while (1)
195 mysql_error(mysql_))do { std::ostringstream oss__; oss__ << "unable to set wait_timeout "
<< mysql_error(mysql_); throw DbOpenError("mysql_connection.cc"
, 195, oss__.str().c_str()); } while (1)
;
196 }
197
198 // Set SQL mode options for the connection: SQL mode governs how what
199 // constitutes insertable data for a given column, and how to handle
200 // invalid data. We want to ensure we get the strictest behavior and
201 // to reject invalid data with an error.
202 const char *sql_mode = "SET SESSION sql_mode ='STRICT_ALL_TABLES'";
203 result = mysql_options(mysql_, MYSQL_INIT_COMMAND, sql_mode);
204 if (result != 0) {
205 isc_throw(DbOpenError, "unable to set SQL mode options: " <<do { std::ostringstream oss__; oss__ << "unable to set SQL mode options: "
<< mysql_error(mysql_); throw DbOpenError("mysql_connection.cc"
, 206, oss__.str().c_str()); } while (1)
206 mysql_error(mysql_))do { std::ostringstream oss__; oss__ << "unable to set SQL mode options: "
<< mysql_error(mysql_); throw DbOpenError("mysql_connection.cc"
, 206, oss__.str().c_str()); } while (1)
;
207 }
208
209 // Connection timeout, the amount of time taken for the client to drop
210 // the connection if the server is not responding.
211 result = mysql_options(mysql_, MYSQL_OPT_CONNECT_TIMEOUT, &connect_timeout);
212 if (result != 0) {
213 isc_throw(DbOpenError, "unable to set database connection timeout: " <<do { std::ostringstream oss__; oss__ << "unable to set database connection timeout: "
<< mysql_error(mysql_); throw DbOpenError("mysql_connection.cc"
, 214, oss__.str().c_str()); } while (1)
214 mysql_error(mysql_))do { std::ostringstream oss__; oss__ << "unable to set database connection timeout: "
<< mysql_error(mysql_); throw DbOpenError("mysql_connection.cc"
, 214, oss__.str().c_str()); } while (1)
;
215 }
216
217 // Set the read timeout if it has been specified. Otherwise, the timeout is
218 // not used.
219 if (read_timeout > 0) {
220 result = mysql_options(mysql_, MYSQL_OPT_READ_TIMEOUT, &read_timeout);
221 if (result != 0) {
222 isc_throw(DbOpenError, "unable to set database read timeout: " <<do { std::ostringstream oss__; oss__ << "unable to set database read timeout: "
<< mysql_error(mysql_); throw DbOpenError("mysql_connection.cc"
, 223, oss__.str().c_str()); } while (1)
223 mysql_error(mysql_))do { std::ostringstream oss__; oss__ << "unable to set database read timeout: "
<< mysql_error(mysql_); throw DbOpenError("mysql_connection.cc"
, 223, oss__.str().c_str()); } while (1)
;
224 }
225 }
226
227 // Set the write timeout if it has been specified. Otherwise, the timeout
228 // is not used.
229 if (write_timeout > 0) {
230 result = mysql_options(mysql_, MYSQL_OPT_WRITE_TIMEOUT, &write_timeout);
231 if (result != 0) {
232 isc_throw(DbOpenError, "unable to set database write timeout: " <<do { std::ostringstream oss__; oss__ << "unable to set database write timeout: "
<< mysql_error(mysql_); throw DbOpenError("mysql_connection.cc"
, 233, oss__.str().c_str()); } while (1)
233 mysql_error(mysql_))do { std::ostringstream oss__; oss__ << "unable to set database write timeout: "
<< mysql_error(mysql_); throw DbOpenError("mysql_connection.cc"
, 233, oss__.str().c_str()); } while (1)
;
234 }
235 }
236
237 // If TLS is enabled set it. If something should go wrong it will happen
238 // later at the mysql_real_connect call.
239 if (tls_) {
240 mysql_ssl_set(mysql_, key_file, cert_file, ca_file, ca_dir,
241 cipher_list);
242 }
243
244 // Open the database.
245 //
246 // The option CLIENT_FOUND_ROWS is specified so that in an UPDATE,
247 // the affected rows are the number of rows found that match the
248 // WHERE clause of the SQL statement, not the rows changed. The reason
249 // here is that MySQL apparently does not update a row if data has not
250 // changed and so the "affected rows" (retrievable from MySQL) is zero.
251 // This makes it hard to distinguish whether the UPDATE changed no rows
252 // because no row matching the WHERE clause was found, or because a
253 // row was found but no data was altered.
254 MYSQL* status = mysql_real_connect(mysql_, host, user, password, name,
255 port, NULL__null, CLIENT_FOUND_ROWS2);
256 if (status != mysql_) {
257 std::string error_message = mysql_error(mysql_);
258
259 auto const& rec = reconnectCtl();
260 if (rec && DatabaseConnection::retry_) {
261 // Start the connection recovery.
262 startRecoverDbConnection();
263
264 std::ostringstream s;
265
266 s << " (scheduling retry " << rec->retryIndex() + 1 << " of " << rec->maxRetries() << " in " << rec->retryInterval() << " milliseconds)";
267
268 error_message += s.str();
269
270 isc_throw(DbOpenErrorWithRetry, error_message)do { std::ostringstream oss__; oss__ << error_message; throw
DbOpenErrorWithRetry("mysql_connection.cc", 270, oss__.str()
.c_str()); } while (1)
;
271 }
272
273 isc_throw(DbOpenError, error_message)do { std::ostringstream oss__; oss__ << error_message; throw
DbOpenError("mysql_connection.cc", 273, oss__.str().c_str())
; } while (1)
;
274 }
275
276 // Enable autocommit. In case transaction is explicitly used, this
277 // setting will be overwritten for the transaction. However, there are
278 // cases when lack of autocommit could cause transactions to hang
279 // until commit or rollback is explicitly called. This already
280 // caused issues for some unit tests which were unable to cleanup
281 // the database after the test because of pending transactions.
282 // Use of autocommit will eliminate this problem.
283 my_bool autocommit_result = mysql_autocommit(mysql_, 1);
284 if (autocommit_result != 0) {
285 isc_throw(DbOperationError, mysql_error(mysql_))do { std::ostringstream oss__; oss__ << mysql_error(mysql_
); throw DbOperationError("mysql_connection.cc", 285, oss__.str
().c_str()); } while (1)
;
286 }
287
288 // To avoid a flush to disk on every commit, the global parameter
289 // innodb_flush_log_at_trx_commit should be set to 2. This will cause the
290 // changes to be written to the log, but flushed to disk in the background
291 // every second. Setting the parameter to that value will speed up the
292 // system, but at the risk of losing data if the system crashes.
293}
294
295// Get schema version.
296
297std::pair<uint32_t, uint32_t>
298MySqlConnection::getVersion(const ParameterMap& parameters,
299 const IOServiceAccessorPtr& ac,
300 const DbCallback& cb,
301 const string& timer_name) {
302 // Get a connection.
303 MySqlConnection conn(parameters, ac, cb);
304
305 if (!timer_name.empty()) {
306 conn.makeReconnectCtl(timer_name);
307 }
308
309 // Open the database.
310 conn.openDatabase();
311
312 // Allocate a new statement.
313 MYSQL_STMT *stmt = mysql_stmt_init(conn.mysql_);
314 if (stmt == NULL__null) {
315 isc_throw(DbOperationError, "unable to allocate MySQL prepared "do { std::ostringstream oss__; oss__ << "unable to allocate MySQL prepared "
"statement structure, reason: " << mysql_error(conn.mysql_
); throw DbOperationError("mysql_connection.cc", 316, oss__.str
().c_str()); } while (1)
316 "statement structure, reason: " << mysql_error(conn.mysql_))do { std::ostringstream oss__; oss__ << "unable to allocate MySQL prepared "
"statement structure, reason: " << mysql_error(conn.mysql_
); throw DbOperationError("mysql_connection.cc", 316, oss__.str
().c_str()); } while (1)
;
317 }
318
319 try {
320
321 // Prepare the statement from SQL text.
322 const char* version_sql = "SELECT version, minor FROM schema_version";
323 int status = mysql_stmt_prepare(stmt, version_sql, strlen(version_sql));
324 if (status != 0) {
325 isc_throw(DbOperationError, "unable to prepare MySQL statement <"do { std::ostringstream oss__; oss__ << "unable to prepare MySQL statement <"
<< version_sql << ">, reason: " << mysql_error
(conn.mysql_); throw DbOperationError("mysql_connection.cc", 327
, oss__.str().c_str()); } while (1)
326 << version_sql << ">, reason: "do { std::ostringstream oss__; oss__ << "unable to prepare MySQL statement <"
<< version_sql << ">, reason: " << mysql_error
(conn.mysql_); throw DbOperationError("mysql_connection.cc", 327
, oss__.str().c_str()); } while (1)
327 << mysql_error(conn.mysql_))do { std::ostringstream oss__; oss__ << "unable to prepare MySQL statement <"
<< version_sql << ">, reason: " << mysql_error
(conn.mysql_); throw DbOperationError("mysql_connection.cc", 327
, oss__.str().c_str()); } while (1)
;
328 }
329
330 // Execute the prepared statement.
331 if (MysqlExecuteStatement(stmt) != 0) {
332 isc_throw(DbOperationError, "cannot execute schema version query <"do { std::ostringstream oss__; oss__ << "cannot execute schema version query <"
<< version_sql << ">, reason: " << mysql_errno
(conn.mysql_); throw DbOperationError("mysql_connection.cc", 334
, oss__.str().c_str()); } while (1)
333 << version_sql << ">, reason: "do { std::ostringstream oss__; oss__ << "cannot execute schema version query <"
<< version_sql << ">, reason: " << mysql_errno
(conn.mysql_); throw DbOperationError("mysql_connection.cc", 334
, oss__.str().c_str()); } while (1)
334 << mysql_errno(conn.mysql_))do { std::ostringstream oss__; oss__ << "cannot execute schema version query <"
<< version_sql << ">, reason: " << mysql_errno
(conn.mysql_); throw DbOperationError("mysql_connection.cc", 334
, oss__.str().c_str()); } while (1)
;
335 }
336
337 // Bind the output of the statement to the appropriate variables.
338 MYSQL_BIND bind[2];
339 memset(bind, 0, sizeof(bind));
340
341 uint32_t version;
342 bind[0].buffer_type = MYSQL_TYPE_LONG;
343 bind[0].is_unsigned = 1;
344 bind[0].buffer = &version;
345 bind[0].buffer_length = sizeof(version);
346
347 uint32_t minor;
348 bind[1].buffer_type = MYSQL_TYPE_LONG;
349 bind[1].is_unsigned = 1;
350 bind[1].buffer = &minor;
351 bind[1].buffer_length = sizeof(minor);
352
353 if (mysql_stmt_bind_result(stmt, bind)) {
354 isc_throw(DbOperationError, "unable to bind result set for <"do { std::ostringstream oss__; oss__ << "unable to bind result set for <"
<< version_sql << ">, reason: " << mysql_errno
(conn.mysql_); throw DbOperationError("mysql_connection.cc", 356
, oss__.str().c_str()); } while (1)
355 << version_sql << ">, reason: "do { std::ostringstream oss__; oss__ << "unable to bind result set for <"
<< version_sql << ">, reason: " << mysql_errno
(conn.mysql_); throw DbOperationError("mysql_connection.cc", 356
, oss__.str().c_str()); } while (1)
356 << mysql_errno(conn.mysql_))do { std::ostringstream oss__; oss__ << "unable to bind result set for <"
<< version_sql << ">, reason: " << mysql_errno
(conn.mysql_); throw DbOperationError("mysql_connection.cc", 356
, oss__.str().c_str()); } while (1)
;
357 }
358
359 // Fetch the data.
360 if (mysql_stmt_fetch(stmt)) {
361 isc_throw(DbOperationError, "unable to bind result set for <"do { std::ostringstream oss__; oss__ << "unable to bind result set for <"
<< version_sql << ">, reason: " << mysql_errno
(conn.mysql_); throw DbOperationError("mysql_connection.cc", 363
, oss__.str().c_str()); } while (1)
362 << version_sql << ">, reason: "do { std::ostringstream oss__; oss__ << "unable to bind result set for <"
<< version_sql << ">, reason: " << mysql_errno
(conn.mysql_); throw DbOperationError("mysql_connection.cc", 363
, oss__.str().c_str()); } while (1)
363 << mysql_errno(conn.mysql_))do { std::ostringstream oss__; oss__ << "unable to bind result set for <"
<< version_sql << ">, reason: " << mysql_errno
(conn.mysql_); throw DbOperationError("mysql_connection.cc", 363
, oss__.str().c_str()); } while (1)
;
364 }
365
366 // Discard the statement and its resources
367 mysql_stmt_close(stmt);
368 return (std::make_pair(version, minor));
369
370 } catch (const std::exception&) {
371 // Avoid a memory leak on error.
372 mysql_stmt_close(stmt);
373
374 // Send the exception to the caller.
375 throw;
376 }
377}
378
379void
380MySqlConnection::ensureSchemaVersion(const ParameterMap& parameters,
381 const DbCallback& cb,
382 const string& timer_name) {
383 // retry-on-startup?
384 bool const retry(parameters.count("retry-on-startup") &&
385 parameters.at("retry-on-startup") == "true");
386
387 IOServiceAccessorPtr ac(new IOServiceAccessor(&DatabaseConnection::getIOService));
388 pair<uint32_t, uint32_t> schema_version;
389 try {
390 schema_version = getVersion(parameters, ac, cb, retry ? timer_name : string());
391 } catch (DbOpenError const& exception) {
392 throw;
393 } catch (DbOpenErrorWithRetry const& exception) {
394 throw;
395 } catch (exception const& exception) {
396 // This failure may occur for a variety of reasons. We are looking at
397 // initializing schema as the only potential mitigation. We could narrow
398 // down on the error that would suggest an uninitialized schema
399 // which would sound something along the lines of
400 // "table schema_version does not exist", but we do not necessarily have
401 // to. If the error had another cause, it will fail again during
402 // initialization or during the subsequent version retrieval and that is
403 // fine, and the error should still be relevant.
404 initializeSchema(parameters);
405
406 // Retrieve again because the initial retrieval failed.
407 schema_version = getVersion(parameters, ac, cb, retry ? timer_name : string());
408 }
409
410 // Check that the versions match.
411 pair<uint32_t, uint32_t> const expected_version(MYSQL_SCHEMA_VERSION_MAJOR,
412 MYSQL_SCHEMA_VERSION_MINOR);
413 if (schema_version != expected_version) {
414 isc_throw(DbOpenError, "MySQL schema version mismatch: expected version: "do { std::ostringstream oss__; oss__ << "MySQL schema version mismatch: expected version: "
<< expected_version.first << "." << expected_version
.second << ", found version: " << schema_version.
first << "." << schema_version.second; throw DbOpenError
("mysql_connection.cc", 417, oss__.str().c_str()); } while (1
)
415 << expected_version.first << "." << expected_version.seconddo { std::ostringstream oss__; oss__ << "MySQL schema version mismatch: expected version: "
<< expected_version.first << "." << expected_version
.second << ", found version: " << schema_version.
first << "." << schema_version.second; throw DbOpenError
("mysql_connection.cc", 417, oss__.str().c_str()); } while (1
)
416 << ", found version: " << schema_version.first << "."do { std::ostringstream oss__; oss__ << "MySQL schema version mismatch: expected version: "
<< expected_version.first << "." << expected_version
.second << ", found version: " << schema_version.
first << "." << schema_version.second; throw DbOpenError
("mysql_connection.cc", 417, oss__.str().c_str()); } while (1
)
417 << schema_version.second)do { std::ostringstream oss__; oss__ << "MySQL schema version mismatch: expected version: "
<< expected_version.first << "." << expected_version
.second << ", found version: " << schema_version.
first << "." << schema_version.second; throw DbOpenError
("mysql_connection.cc", 417, oss__.str().c_str()); } while (1
)
;
418 }
419}
420
421void
422MySqlConnection::initializeSchema(const ParameterMap& parameters) {
423 if (parameters.count("readonly") && parameters.at("readonly") == "true") {
424 // The readonly flag is historically used for host backends. Still, if
425 // enabled, it is a strong indication that we should not meDDLe with it.
426 return;
427 }
428
429 if (!isc::util::file::isFile(KEA_ADMIN_)) {
430 // It can happen for kea-admin to not exist, especially with
431 // packages that install it in a separate package.
432 return;
433 }
434
435 // Convert parameters.
436 vector<string> kea_admin_parameters(toKeaAdminParameters(parameters));
437 ProcessEnvVars const vars;
438 kea_admin_parameters.insert(kea_admin_parameters.begin(), "db-init");
439
440 // Run.
441 ProcessSpawn kea_admin(ProcessSpawn::SYNC, KEA_ADMIN_, kea_admin_parameters, vars,
442 /* inherit_env = */ true);
443 DB_LOG_INFO(MYSQL_INITIALIZE_SCHEMA).arg(kea_admin.getCommandLine());
444 pid_t const pid(kea_admin.spawn());
445 if (kea_admin.isRunning(pid)) {
446 isc_throw(SchemaInitializationFailed, "kea-admin still running")do { std::ostringstream oss__; oss__ << "kea-admin still running"
; throw SchemaInitializationFailed("mysql_connection.cc", 446
, oss__.str().c_str()); } while (1)
;
447 }
448 int const exit_code(kea_admin.getExitStatus(pid));
449 if (exit_code != 0) {
450 isc_throw(SchemaInitializationFailed, "Expected exit code 0 for kea-admin. Got " << exit_code)do { std::ostringstream oss__; oss__ << "Expected exit code 0 for kea-admin. Got "
<< exit_code; throw SchemaInitializationFailed("mysql_connection.cc"
, 450, oss__.str().c_str()); } while (1)
;
451 }
452}
453
454vector<string>
455MySqlConnection::toKeaAdminParameters(ParameterMap const& params) {
456 vector<string> result{"mysql"};
457 for (auto const& p : params) {
458 string const& keyword(p.first);
459 string const& value(p.second);
460
461 // These Kea parameters are the same as the kea-admin parameters.
462 if (keyword == "user" ||
463 keyword == "password" ||
464 keyword == "host" ||
465 keyword == "port" ||
466 keyword == "name") {
467 result.push_back("--" + keyword);
468 result.push_back(value);
469 continue;
470 }
471
472 // These Kea parameters do not have a direct kea-admin equivalent.
473 // But they do have a mariadb client flag equivalent.
474 // We pass them to kea-admin using the --extra flag.
475 static unordered_map<string, string> conversions{
476 {"connect-timeout", "connect_timeout"},
477 {"cipher-list", "ssl-cipher"},
478 {"cert-file", "ssl-cert"},
479 {"key-file", "ssl-key"},
480 {"trust-anchor", "ssl-ca"},
481 // {"read-timeout", "--net-read-timeout"}, // available in docs, but client says unknown variable?
482 // {"write-timeout", "--net-write-timeout"}, // available in docs, but client says unknown variable?
483 };
484 if (conversions.count(keyword)) {
485 result.push_back("--extra");
486 result.push_back("--" + conversions.at(keyword) + " " + value);
487 }
488 }
489 return result;
490}
491
492// Prepared statement setup. The textual form of an SQL statement is stored
493// in a vector of strings (text_statements_) and is used in the output of
494// error messages. The SQL statement is also compiled into a "prepared
495// statement" (stored in statements_), which avoids the overhead of compilation
496// during use. As prepared statements have resources allocated to them, the
497// class destructor explicitly destroys them.
498
499void
500MySqlConnection::prepareStatement(uint32_t index, const char* text) {
501 // Validate that there is space for the statement in the statements array
502 // and that nothing has been placed there before.
503 if ((index >= statements_.size()) || (statements_[index] != NULL__null)) {
504 isc_throw(InvalidParameter, "invalid prepared statement index (" <<do { std::ostringstream oss__; oss__ << "invalid prepared statement index ("
<< static_cast<int>(index) << ") or indexed prepared "
<< "statement is not null"; throw InvalidParameter("mysql_connection.cc"
, 506, oss__.str().c_str()); } while (1)
505 static_cast<int>(index) << ") or indexed prepared " <<do { std::ostringstream oss__; oss__ << "invalid prepared statement index ("
<< static_cast<int>(index) << ") or indexed prepared "
<< "statement is not null"; throw InvalidParameter("mysql_connection.cc"
, 506, oss__.str().c_str()); } while (1)
506 "statement is not null")do { std::ostringstream oss__; oss__ << "invalid prepared statement index ("
<< static_cast<int>(index) << ") or indexed prepared "
<< "statement is not null"; throw InvalidParameter("mysql_connection.cc"
, 506, oss__.str().c_str()); } while (1)
;
507 }
508
509 // All OK, so prepare the statement
510 text_statements_[index] = std::string(text);
511 statements_[index] = mysql_stmt_init(mysql_);
512 if (statements_[index] == NULL__null) {
513 isc_throw(DbOperationError, "unable to allocate MySQL prepared "do { std::ostringstream oss__; oss__ << "unable to allocate MySQL prepared "
"statement structure, reason: " << mysql_error(mysql_)
; throw DbOperationError("mysql_connection.cc", 514, oss__.str
().c_str()); } while (1)
514 "statement structure, reason: " << mysql_error(mysql_))do { std::ostringstream oss__; oss__ << "unable to allocate MySQL prepared "
"statement structure, reason: " << mysql_error(mysql_)
; throw DbOperationError("mysql_connection.cc", 514, oss__.str
().c_str()); } while (1)
;
515 }
516
517 int status = mysql_stmt_prepare(statements_[index], text, strlen(text));
518 if (status != 0) {
519 isc_throw(DbOperationError, "unable to prepare MySQL statement <" <<do { std::ostringstream oss__; oss__ << "unable to prepare MySQL statement <"
<< text << ">, reason: " << mysql_error
(mysql_); throw DbOperationError("mysql_connection.cc", 520, oss__
.str().c_str()); } while (1)
520 text << ">, reason: " << mysql_error(mysql_))do { std::ostringstream oss__; oss__ << "unable to prepare MySQL statement <"
<< text << ">, reason: " << mysql_error
(mysql_); throw DbOperationError("mysql_connection.cc", 520, oss__
.str().c_str()); } while (1)
;
521 }
522}
523
524void
525MySqlConnection::prepareStatements(const TaggedStatement* start_statement,
526 const TaggedStatement* end_statement) {
527 // Created the MySQL prepared statements for each DML statement.
528 for (const TaggedStatement* tagged_statement = start_statement;
529 tagged_statement != end_statement; ++tagged_statement) {
530 if (tagged_statement->index >= statements_.size()) {
531 statements_.resize(tagged_statement->index + 1, NULL__null);
532 text_statements_.resize(tagged_statement->index + 1,
533 std::string(""));
534 }
535 prepareStatement(tagged_statement->index,
536 tagged_statement->text);
537 }
538}
539
540/// @brief Destructor
541MySqlConnection::~MySqlConnection() {
542 // Free up the prepared statements, ignoring errors. (What would we do
543 // about them? We're destroying this object and are not really concerned
544 // with errors on a database connection that is about to go away.)
545 for (size_t i = 0; i < statements_.size(); ++i) {
546 if (statements_[i] != NULL__null) {
547 (void) mysql_stmt_close(statements_[i]);
548 statements_[i] = NULL__null;
549 }
550 }
551 statements_.clear();
552 text_statements_.clear();
553}
554
555// Time conversion methods.
556//
557// Note that the MySQL TIMESTAMP data type (used for "expire") converts data
558// from the current timezone to UTC for storage, and from UTC to the current
559// timezone for retrieval.
560//
561// This causes no problems providing that:
562// a) cltt is given in local time
563// b) We let the system take care of timezone conversion when converting
564// from a time read from the database into a local time.
565void
566MySqlConnection::convertToDatabaseTime(const time_t input_time,
567 MYSQL_TIME& output_time) {
568 MySqlBinding::convertToDatabaseTime(input_time, output_time);
569}
570
571void
572MySqlConnection::convertToDatabaseTime(const time_t cltt,
573 const uint32_t valid_lifetime,
574 MYSQL_TIME& expire) {
575 MySqlBinding::convertToDatabaseTime(cltt, valid_lifetime, expire);
576}
577
578void
579MySqlConnection::convertFromDatabaseTime(const MYSQL_TIME& expire,
580 uint32_t valid_lifetime, time_t& cltt) {
581 MySqlBinding::convertFromDatabaseTime(expire, valid_lifetime, cltt);
582}
583
584void
585MySqlConnection::startTransaction() {
586 // If it is nested transaction, do nothing.
587 if (++transaction_ref_count_ > 1) {
588 return;
589 }
590
591 DB_LOG_DEBUG(DB_DBG_TRACE_DETAIL, MYSQL_START_TRANSACTION);
592 checkUnusable();
593 // We create prepared statements for all other queries, but MySQL
594 // don't support prepared statements for START TRANSACTION.
595 int status = mysql_query(mysql_, "START TRANSACTION");
596 if (status != 0) {
597 isc_throw(DbOperationError, "unable to start transaction, "do { std::ostringstream oss__; oss__ << "unable to start transaction, "
"reason: " << mysql_error(mysql_); throw DbOperationError
("mysql_connection.cc", 598, oss__.str().c_str()); } while (1
)
598 "reason: " << mysql_error(mysql_))do { std::ostringstream oss__; oss__ << "unable to start transaction, "
"reason: " << mysql_error(mysql_); throw DbOperationError
("mysql_connection.cc", 598, oss__.str().c_str()); } while (1
)
;
599 }
600}
601
602bool
603MySqlConnection::isTransactionStarted() const {
604 return (transaction_ref_count_ > 0);
605}
606
607void
608MySqlConnection::commit() {
609 if (transaction_ref_count_ <= 0) {
610 isc_throw(Unexpected, "commit called for not started transaction - coding error")do { std::ostringstream oss__; oss__ << "commit called for not started transaction - coding error"
; throw Unexpected("mysql_connection.cc", 610, oss__.str().c_str
()); } while (1)
;
611 }
612
613 // When committing nested transaction, do nothing.
614 if (--transaction_ref_count_ > 0) {
615 return;
616 }
617 DB_LOG_DEBUG(DB_DBG_TRACE_DETAIL, MYSQL_COMMIT);
618 checkUnusable();
619 if (mysql_commit(mysql_) != 0) {
620 isc_throw(DbOperationError, "commit failed: "do { std::ostringstream oss__; oss__ << "commit failed: "
<< mysql_error(mysql_); throw DbOperationError("mysql_connection.cc"
, 621, oss__.str().c_str()); } while (1)
621 << mysql_error(mysql_))do { std::ostringstream oss__; oss__ << "commit failed: "
<< mysql_error(mysql_); throw DbOperationError("mysql_connection.cc"
, 621, oss__.str().c_str()); } while (1)
;
622 }
623}
624
625void
626MySqlConnection::rollback() {
627 if (transaction_ref_count_ <= 0) {
628 isc_throw(Unexpected, "rollback called for not started transaction - coding error")do { std::ostringstream oss__; oss__ << "rollback called for not started transaction - coding error"
; throw Unexpected("mysql_connection.cc", 628, oss__.str().c_str
()); } while (1)
;
629 }
630
631 // When rolling back nested transaction, do nothing.
632 if (--transaction_ref_count_ > 0) {
633 return;
634 }
635 DB_LOG_DEBUG(DB_DBG_TRACE_DETAIL, MYSQL_ROLLBACK);
636 checkUnusable();
637 if (mysql_rollback(mysql_) != 0) {
638 isc_throw(DbOperationError, "rollback failed: "do { std::ostringstream oss__; oss__ << "rollback failed: "
<< mysql_error(mysql_); throw DbOperationError("mysql_connection.cc"
, 639, oss__.str().c_str()); } while (1)
639 << mysql_error(mysql_))do { std::ostringstream oss__; oss__ << "rollback failed: "
<< mysql_error(mysql_); throw DbOperationError("mysql_connection.cc"
, 639, oss__.str().c_str()); } while (1)
;
640 }
641}
642
643template<typename T>
644void
645MySqlConnection::setIntParameterValue(const std::string& name, int64_t min, int64_t max, T& value) {
646 string svalue;
647 try {
648 svalue = getParameter(name);
649 } catch (...) {
650 // Do nothing if the parameter is not present.
651 }
652 if (svalue.empty()) {
653 return;
654 }
655 try {
656 // Try to convert the value.
657 auto parsed_value = boost::lexical_cast<T>(svalue);
658 // Check if the value is within the specified range.
659 if ((parsed_value < min) || (parsed_value > max)) {
660 isc_throw(BadValue, "bad " << svalue << " value")do { std::ostringstream oss__; oss__ << "bad " <<
svalue << " value"; throw BadValue("mysql_connection.cc"
, 660, oss__.str().c_str()); } while (1)
;
661 }
662 // Everything is fine. Return the parsed value.
663 value = parsed_value;
664
665 } catch (...) {
666 // We may end up here when lexical_cast fails or when the
667 // parsed value is not within the desired range. In both
668 // cases let's throw the same general error.
669 isc_throw(BadValue, name << " parameter (" <<do { std::ostringstream oss__; oss__ << name << " parameter ("
<< svalue << ") must be an integer between " <<
min << " and " << max; throw BadValue("mysql_connection.cc"
, 671, oss__.str().c_str()); } while (1)
670 svalue << ") must be an integer between "do { std::ostringstream oss__; oss__ << name << " parameter ("
<< svalue << ") must be an integer between " <<
min << " and " << max; throw BadValue("mysql_connection.cc"
, 671, oss__.str().c_str()); } while (1)
671 << min << " and " << max)do { std::ostringstream oss__; oss__ << name << " parameter ("
<< svalue << ") must be an integer between " <<
min << " and " << max; throw BadValue("mysql_connection.cc"
, 671, oss__.str().c_str()); } while (1)
;
672 }
673}
674
675} // namespace db
676} // namespace isc