Bug Summary

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