File: | home/fedora/workspace/kea-dev/clang-static-analyzer/build/meson-private/tmphs1g5w79/../../../src/lib/mysql/mysql_connection.cc |
Warning: | line 67, column 17 Value stored to 'host' during its initialization is never read |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
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 | |
26 | using namespace isc; |
27 | using namespace isc::asiolink; |
28 | using namespace isc::data; |
29 | using namespace std; |
30 | |
31 | namespace isc { |
32 | namespace db { |
33 | |
34 | std::string MySqlConnection::KEA_ADMIN_ = KEA_ADMIN"/usr/local/sbin/kea-admin"; |
35 | |
36 | int 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 |
41 | const int MYSQL_DEFAULT_CONNECTION_TIMEOUT = 5; // seconds |
42 | |
43 | MySqlTransaction::MySqlTransaction(MySqlConnection& conn) |
44 | : conn_(conn), committed_(false) { |
45 | conn_.startTransaction(); |
46 | } |
47 | |
48 | MySqlTransaction::~MySqlTransaction() { |
49 | // Rollback if the MySqlTransaction::commit wasn't explicitly |
50 | // called. |
51 | if (!committed_) { |
52 | conn_.rollback(); |
53 | } |
54 | } |
55 | |
56 | void |
57 | MySqlTransaction::commit() { |
58 | conn_.commit(); |
59 | committed_ = true; |
60 | } |
61 | |
62 | // Open the database using the parameters passed to the constructor. |
63 | |
64 | void |
65 | MySqlConnection::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("../../../src/lib/mysql/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("../../../src/lib/mysql/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("../../../src/lib/mysql/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("../../../src/lib/mysql/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("../../../src/lib/mysql/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("../../../src/lib/mysql/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("../../../src/lib/mysql/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("../../../src/lib/mysql/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("../../../src/lib/mysql/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("../../../src/lib/mysql/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("../../../src/lib/mysql/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("../../../src/lib/mysql/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("../../../src/lib/mysql/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("../../../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 write timeout: " << mysql_error(mysql_); throw DbOpenError("../../../src/lib/mysql/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("../../../src/lib/mysql/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("../../../src/lib/mysql/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("../../../src/lib/mysql/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("../../../src/lib/mysql/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("../../../src/lib/mysql/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 | // Mark this connection as no longer usable. |
286 | markUnusable(); |
287 | |
288 | std::string error_message = mysql_error(mysql_); |
289 | |
290 | auto const& rec = reconnectCtl(); |
291 | if (rec && DatabaseConnection::retry_) { |
292 | |
293 | // Start the connection recovery. |
294 | startRecoverDbConnection(); |
295 | |
296 | std::ostringstream s; |
297 | |
298 | s << " (scheduling retry " << rec->retryIndex() + 1 << " of " << rec->maxRetries() << " in " << rec->retryInterval() << " milliseconds)"; |
299 | |
300 | error_message += s.str(); |
301 | |
302 | isc_throw(DbOpenErrorWithRetry, error_message)do { std::ostringstream oss__; oss__ << error_message; throw DbOpenErrorWithRetry("../../../src/lib/mysql/mysql_connection.cc" , 302, oss__.str().c_str()); } while (1); |
303 | } |
304 | |
305 | isc_throw(DbOpenError, error_message)do { std::ostringstream oss__; oss__ << error_message; throw DbOpenError("../../../src/lib/mysql/mysql_connection.cc", 305 , oss__.str().c_str()); } while (1); |
306 | } |
307 | |
308 | // Enable autocommit. In case transaction is explicitly used, this |
309 | // setting will be overwritten for the transaction. However, there are |
310 | // cases when lack of autocommit could cause transactions to hang |
311 | // until commit or rollback is explicitly called. This already |
312 | // caused issues for some unit tests which were unable to cleanup |
313 | // the database after the test because of pending transactions. |
314 | // Use of autocommit will eliminate this problem. |
315 | my_bool autocommit_result = mysql_autocommit(mysql_, 1); |
316 | if (autocommit_result != 0) { |
317 | isc_throw(DbOperationError, mysql_error(mysql_))do { std::ostringstream oss__; oss__ << mysql_error(mysql_ ); throw DbOperationError("../../../src/lib/mysql/mysql_connection.cc" , 317, oss__.str().c_str()); } while (1); |
318 | } |
319 | |
320 | // To avoid a flush to disk on every commit, the global parameter |
321 | // innodb_flush_log_at_trx_commit should be set to 2. This will cause the |
322 | // changes to be written to the log, but flushed to disk in the background |
323 | // every second. Setting the parameter to that value will speed up the |
324 | // system, but at the risk of losing data if the system crashes. |
325 | } |
326 | |
327 | // Get schema version. |
328 | |
329 | std::pair<uint32_t, uint32_t> |
330 | MySqlConnection::getVersion(const ParameterMap& parameters, |
331 | const IOServiceAccessorPtr& ac, |
332 | const DbCallback& cb, |
333 | const string& timer_name, |
334 | unsigned int id) { |
335 | // Get a connection. |
336 | MySqlConnection conn(parameters, ac, cb); |
337 | |
338 | if (!timer_name.empty()) { |
339 | conn.makeReconnectCtl(timer_name, id); |
340 | } |
341 | |
342 | // Open the database. |
343 | conn.openDatabase(); |
344 | |
345 | // Allocate a new statement. |
346 | MYSQL_STMT *stmt = mysql_stmt_init(conn.mysql_); |
347 | if (stmt == NULL__null) { |
348 | 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" , 349, oss__.str().c_str()); } while (1) |
349 | "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" , 349, oss__.str().c_str()); } while (1); |
350 | } |
351 | |
352 | try { |
353 | |
354 | // Prepare the statement from SQL text. |
355 | const char* version_sql = "SELECT version, minor FROM schema_version"; |
356 | int status = mysql_stmt_prepare(stmt, version_sql, strlen(version_sql)); |
357 | if (status != 0) { |
358 | 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" , 360, oss__.str().c_str()); } while (1) |
359 | << 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" , 360, oss__.str().c_str()); } while (1) |
360 | << 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" , 360, oss__.str().c_str()); } while (1); |
361 | } |
362 | |
363 | // Execute the prepared statement. |
364 | if (MysqlExecuteStatement(stmt) != 0) { |
365 | 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" , 367, oss__.str().c_str()); } while (1) |
366 | << 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" , 367, oss__.str().c_str()); } while (1) |
367 | << 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" , 367, oss__.str().c_str()); } while (1); |
368 | } |
369 | |
370 | // Bind the output of the statement to the appropriate variables. |
371 | MYSQL_BIND bind[2]; |
372 | memset(bind, 0, sizeof(bind)); |
373 | |
374 | uint32_t version; |
375 | bind[0].buffer_type = MYSQL_TYPE_LONG; |
376 | bind[0].is_unsigned = 1; |
377 | bind[0].buffer = &version; |
378 | bind[0].buffer_length = sizeof(version); |
379 | |
380 | uint32_t minor; |
381 | bind[1].buffer_type = MYSQL_TYPE_LONG; |
382 | bind[1].is_unsigned = 1; |
383 | bind[1].buffer = &minor; |
384 | bind[1].buffer_length = sizeof(minor); |
385 | |
386 | if (mysql_stmt_bind_result(stmt, bind)) { |
387 | 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" , 389, oss__.str().c_str()); } while (1) |
388 | << 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" , 389, oss__.str().c_str()); } while (1) |
389 | << 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" , 389, oss__.str().c_str()); } while (1); |
390 | } |
391 | |
392 | // Fetch the data. |
393 | if (mysql_stmt_fetch(stmt)) { |
394 | 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" , 396, oss__.str().c_str()); } while (1) |
395 | << 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" , 396, oss__.str().c_str()); } while (1) |
396 | << 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" , 396, oss__.str().c_str()); } while (1); |
397 | } |
398 | |
399 | // Discard the statement and its resources |
400 | mysql_stmt_close(stmt); |
401 | return (std::make_pair(version, minor)); |
402 | |
403 | } catch (const std::exception&) { |
404 | // Avoid a memory leak on error. |
405 | mysql_stmt_close(stmt); |
406 | |
407 | // Send the exception to the caller. |
408 | throw; |
409 | } |
410 | } |
411 | |
412 | void |
413 | MySqlConnection::ensureSchemaVersion(const ParameterMap& parameters, |
414 | const DbCallback& cb, |
415 | const string& timer_name) { |
416 | // retry-on-startup? |
417 | bool const retry(parameters.count("retry-on-startup") && |
418 | parameters.at("retry-on-startup") == "true"); |
419 | |
420 | IOServiceAccessorPtr ac(new IOServiceAccessor(&DatabaseConnection::getIOService)); |
421 | pair<uint32_t, uint32_t> schema_version; |
422 | try { |
423 | schema_version = getVersion(parameters, ac, cb, retry ? timer_name : string()); |
424 | } catch (DbOpenError const& exception) { |
425 | throw; |
426 | } catch (DbOpenErrorWithRetry const& exception) { |
427 | throw; |
428 | } catch (exception const& exception) { |
429 | // Disable the recovery mechanism in test mode. |
430 | if (DatabaseConnection::test_mode_) { |
431 | throw; |
432 | } |
433 | // This failure may occur for a variety of reasons. We are looking at |
434 | // initializing schema as the only potential mitigation. We could narrow |
435 | // down on the error that would suggest an uninitialized schema |
436 | // which would sound something along the lines of |
437 | // "table schema_version does not exist", but we do not necessarily have |
438 | // to. If the error had another cause, it will fail again during |
439 | // initialization or during the subsequent version retrieval and that is |
440 | // fine, and the error should still be relevant. |
441 | initializeSchema(parameters); |
442 | |
443 | // Retrieve again because the initial retrieval failed. |
444 | schema_version = getVersion(parameters, ac, cb, retry ? timer_name : string()); |
445 | } |
446 | |
447 | // Check that the versions match. |
448 | pair<uint32_t, uint32_t> const expected_version(MYSQL_SCHEMA_VERSION_MAJOR, |
449 | MYSQL_SCHEMA_VERSION_MINOR); |
450 | if (schema_version != expected_version) { |
451 | 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", 454, oss__.str ().c_str()); } while (1) |
452 | << 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", 454, oss__.str ().c_str()); } while (1) |
453 | << ", 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", 454, oss__.str ().c_str()); } while (1) |
454 | << 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", 454, oss__.str ().c_str()); } while (1); |
455 | } |
456 | } |
457 | |
458 | void |
459 | MySqlConnection::initializeSchema(const ParameterMap& parameters) { |
460 | if (parameters.count("readonly") && parameters.at("readonly") == "true") { |
461 | // The readonly flag is historically used for host backends. Still, if |
462 | // enabled, it is a strong indication that we should not meDDLe with it. |
463 | return; |
464 | } |
465 | |
466 | if (!isc::util::file::isFile(KEA_ADMIN_)) { |
467 | // It can happen for kea-admin to not exist, especially with |
468 | // packages that install it in a separate package. |
469 | return; |
470 | } |
471 | |
472 | // Convert parameters. |
473 | vector<string> kea_admin_parameters(toKeaAdminParameters(parameters)); |
474 | ProcessEnvVars const vars; |
475 | kea_admin_parameters.insert(kea_admin_parameters.begin(), "db-init"); |
476 | |
477 | // Run. |
478 | ProcessSpawn kea_admin(ProcessSpawn::SYNC, KEA_ADMIN_, kea_admin_parameters, vars, |
479 | /* inherit_env = */ true); |
480 | DB_LOG_INFO(MYSQL_INITIALIZE_SCHEMA) |
481 | .arg(kea_admin.getCommandLine(std::unordered_set<std::string>{"--password"})); |
482 | pid_t const pid(kea_admin.spawn()); |
483 | if (kea_admin.isRunning(pid)) { |
484 | isc_throw(SchemaInitializationFailed, "kea-admin still running")do { std::ostringstream oss__; oss__ << "kea-admin still running" ; throw SchemaInitializationFailed("../../../src/lib/mysql/mysql_connection.cc" , 484, oss__.str().c_str()); } while (1); |
485 | } |
486 | int const exit_code(kea_admin.getExitStatus(pid)); |
487 | if (exit_code != 0) { |
488 | 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" , 488, oss__.str().c_str()); } while (1); |
489 | } |
490 | } |
491 | |
492 | vector<string> |
493 | MySqlConnection::toKeaAdminParameters(ParameterMap const& params) { |
494 | vector<string> result{"mysql"}; |
495 | for (auto const& p : params) { |
496 | string const& keyword(p.first); |
497 | string const& value(p.second); |
498 | |
499 | // These Kea parameters are the same as the kea-admin parameters. |
500 | if (keyword == "user" || |
501 | keyword == "password" || |
502 | keyword == "host" || |
503 | keyword == "port" || |
504 | keyword == "name") { |
505 | result.push_back("--" + keyword); |
506 | result.push_back(value); |
507 | continue; |
508 | } |
509 | |
510 | // These Kea parameters do not have a direct kea-admin equivalent. |
511 | // But they do have a mariadb client flag equivalent. |
512 | // We pass them to kea-admin using the --extra flag. |
513 | static unordered_map<string, string> conversions{ |
514 | {"connect-timeout", "connect_timeout"}, |
515 | {"cipher-list", "ssl-cipher"}, |
516 | {"cert-file", "ssl-cert"}, |
517 | {"key-file", "ssl-key"}, |
518 | {"trust-anchor", "ssl-ca"}, |
519 | // {"read-timeout", "--net-read-timeout"}, // available in docs, but client says unknown variable? |
520 | // {"write-timeout", "--net-write-timeout"}, // available in docs, but client says unknown variable? |
521 | }; |
522 | if (conversions.count(keyword)) { |
523 | result.push_back("--extra"); |
524 | result.push_back("--" + conversions.at(keyword) + " " + value); |
525 | } |
526 | } |
527 | return result; |
528 | } |
529 | |
530 | // Prepared statement setup. The textual form of an SQL statement is stored |
531 | // in a vector of strings (text_statements_) and is used in the output of |
532 | // error messages. The SQL statement is also compiled into a "prepared |
533 | // statement" (stored in statements_), which avoids the overhead of compilation |
534 | // during use. As prepared statements have resources allocated to them, the |
535 | // class destructor explicitly destroys them. |
536 | |
537 | void |
538 | MySqlConnection::prepareStatement(uint32_t index, const char* text) { |
539 | // Validate that there is space for the statement in the statements array |
540 | // and that nothing has been placed there before. |
541 | if ((index >= statements_.size()) || (statements_[index] != NULL__null)) { |
542 | 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" , 544, oss__.str().c_str()); } while (1) |
543 | 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" , 544, oss__.str().c_str()); } while (1) |
544 | "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" , 544, oss__.str().c_str()); } while (1); |
545 | } |
546 | |
547 | // All OK, so prepare the statement |
548 | text_statements_[index] = std::string(text); |
549 | statements_[index] = mysql_stmt_init(mysql_); |
550 | if (statements_[index] == NULL__null) { |
551 | 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" , 552, oss__.str().c_str()); } while (1) |
552 | "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" , 552, oss__.str().c_str()); } while (1); |
553 | } |
554 | |
555 | int status = mysql_stmt_prepare(statements_[index], text, strlen(text)); |
556 | if (status != 0) { |
557 | 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" , 558, oss__.str().c_str()); } while (1) |
558 | 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" , 558, oss__.str().c_str()); } while (1); |
559 | } |
560 | } |
561 | |
562 | void |
563 | MySqlConnection::prepareStatements(const TaggedStatement* start_statement, |
564 | const TaggedStatement* end_statement) { |
565 | // Created the MySQL prepared statements for each DML statement. |
566 | for (const TaggedStatement* tagged_statement = start_statement; |
567 | tagged_statement != end_statement; ++tagged_statement) { |
568 | if (tagged_statement->index >= statements_.size()) { |
569 | statements_.resize(tagged_statement->index + 1, NULL__null); |
570 | text_statements_.resize(tagged_statement->index + 1, |
571 | std::string("")); |
572 | } |
573 | prepareStatement(tagged_statement->index, |
574 | tagged_statement->text); |
575 | } |
576 | } |
577 | |
578 | /// @brief Destructor |
579 | MySqlConnection::~MySqlConnection() { |
580 | // Free up the prepared statements, ignoring errors. (What would we do |
581 | // about them? We're destroying this object and are not really concerned |
582 | // with errors on a database connection that is about to go away.) |
583 | for (size_t i = 0; i < statements_.size(); ++i) { |
584 | if (statements_[i] != NULL__null) { |
585 | (void) mysql_stmt_close(statements_[i]); |
586 | statements_[i] = NULL__null; |
587 | } |
588 | } |
589 | statements_.clear(); |
590 | text_statements_.clear(); |
591 | } |
592 | |
593 | // Time conversion methods. |
594 | // |
595 | // Note that the MySQL TIMESTAMP data type (used for "expire") converts data |
596 | // from the current timezone to UTC for storage, and from UTC to the current |
597 | // timezone for retrieval. |
598 | // |
599 | // This causes no problems providing that: |
600 | // a) cltt is given in local time |
601 | // b) We let the system take care of timezone conversion when converting |
602 | // from a time read from the database into a local time. |
603 | void |
604 | MySqlConnection::convertToDatabaseTime(const time_t input_time, |
605 | MYSQL_TIME& output_time) { |
606 | MySqlBinding::convertToDatabaseTime(input_time, output_time); |
607 | } |
608 | |
609 | void |
610 | MySqlConnection::convertToDatabaseTime(const time_t cltt, |
611 | const uint32_t valid_lifetime, |
612 | MYSQL_TIME& expire) { |
613 | MySqlBinding::convertToDatabaseTime(cltt, valid_lifetime, expire); |
614 | } |
615 | |
616 | void |
617 | MySqlConnection::convertFromDatabaseTime(const MYSQL_TIME& expire, |
618 | uint32_t valid_lifetime, time_t& cltt) { |
619 | MySqlBinding::convertFromDatabaseTime(expire, valid_lifetime, cltt); |
620 | } |
621 | |
622 | void |
623 | MySqlConnection::startTransaction() { |
624 | // If it is nested transaction, do nothing. |
625 | if (++transaction_ref_count_ > 1) { |
626 | return; |
627 | } |
628 | |
629 | DB_LOG_DEBUG(DB_DBG_TRACE_DETAIL, MYSQL_START_TRANSACTION); |
630 | checkUnusable(); |
631 | // We create prepared statements for all other queries, but MySQL |
632 | // don't support prepared statements for START TRANSACTION. |
633 | int status = mysql_query(mysql_, "START TRANSACTION"); |
634 | if (status != 0) { |
635 | 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", 636, oss__.str ().c_str()); } while (1) |
636 | "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", 636, oss__.str ().c_str()); } while (1); |
637 | } |
638 | } |
639 | |
640 | bool |
641 | MySqlConnection::isTransactionStarted() const { |
642 | return (transaction_ref_count_ > 0); |
643 | } |
644 | |
645 | void |
646 | MySqlConnection::commit() { |
647 | if (transaction_ref_count_ <= 0) { |
648 | 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" , 648, oss__.str().c_str()); } while (1); |
649 | } |
650 | |
651 | // When committing nested transaction, do nothing. |
652 | if (--transaction_ref_count_ > 0) { |
653 | return; |
654 | } |
655 | DB_LOG_DEBUG(DB_DBG_TRACE_DETAIL, MYSQL_COMMIT); |
656 | checkUnusable(); |
657 | if (mysql_commit(mysql_) != 0) { |
658 | isc_throw(DbOperationError, "commit failed: "do { std::ostringstream oss__; oss__ << "commit failed: " << mysql_error(mysql_); throw DbOperationError("../../../src/lib/mysql/mysql_connection.cc" , 659, oss__.str().c_str()); } while (1) |
659 | << mysql_error(mysql_))do { std::ostringstream oss__; oss__ << "commit failed: " << mysql_error(mysql_); throw DbOperationError("../../../src/lib/mysql/mysql_connection.cc" , 659, oss__.str().c_str()); } while (1); |
660 | } |
661 | } |
662 | |
663 | void |
664 | MySqlConnection::rollback() { |
665 | if (transaction_ref_count_ <= 0) { |
666 | 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" , 666, oss__.str().c_str()); } while (1); |
667 | } |
668 | |
669 | // When rolling back nested transaction, do nothing. |
670 | if (--transaction_ref_count_ > 0) { |
671 | return; |
672 | } |
673 | DB_LOG_DEBUG(DB_DBG_TRACE_DETAIL, MYSQL_ROLLBACK); |
674 | checkUnusable(); |
675 | if (mysql_rollback(mysql_) != 0) { |
676 | isc_throw(DbOperationError, "rollback failed: "do { std::ostringstream oss__; oss__ << "rollback failed: " << mysql_error(mysql_); throw DbOperationError("../../../src/lib/mysql/mysql_connection.cc" , 677, oss__.str().c_str()); } while (1) |
677 | << mysql_error(mysql_))do { std::ostringstream oss__; oss__ << "rollback failed: " << mysql_error(mysql_); throw DbOperationError("../../../src/lib/mysql/mysql_connection.cc" , 677, oss__.str().c_str()); } while (1); |
678 | } |
679 | } |
680 | |
681 | template<typename T> |
682 | void |
683 | MySqlConnection::setIntParameterValue(const std::string& name, int64_t min, int64_t max, T& value) { |
684 | string svalue; |
685 | try { |
686 | svalue = getParameter(name); |
687 | } catch (...) { |
688 | // Do nothing if the parameter is not present. |
689 | } |
690 | if (svalue.empty()) { |
691 | return; |
692 | } |
693 | try { |
694 | // Try to convert the value. |
695 | auto parsed_value = boost::lexical_cast<T>(svalue); |
696 | // Check if the value is within the specified range. |
697 | if ((parsed_value < min) || (parsed_value > max)) { |
698 | isc_throw(BadValue, "bad " << svalue << " value")do { std::ostringstream oss__; oss__ << "bad " << svalue << " value"; throw BadValue("../../../src/lib/mysql/mysql_connection.cc" , 698, oss__.str().c_str()); } while (1); |
699 | } |
700 | // Everything is fine. Return the parsed value. |
701 | value = parsed_value; |
702 | |
703 | } catch (...) { |
704 | // We may end up here when lexical_cast fails or when the |
705 | // parsed value is not within the desired range. In both |
706 | // cases let's throw the same general error. |
707 | 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" , 709, oss__.str().c_str()); } while (1) |
708 | 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" , 709, oss__.str().c_str()); } while (1) |
709 | << 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" , 709, oss__.str().c_str()); } while (1); |
710 | } |
711 | } |
712 | |
713 | } // namespace db |
714 | } // namespace isc |