Bug Summary

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