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 |
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"/home/fedora/workspace/kea-dev/clang-static-analyzer/kea-bin/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("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 | |
325 | std::pair<uint32_t, uint32_t> |
326 | MySqlConnection::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 | |
408 | void |
409 | MySqlConnection::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 | |
454 | void |
455 | MySqlConnection::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 | |
487 | vector<string> |
488 | MySqlConnection::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 | |
532 | void |
533 | MySqlConnection::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 | |
557 | void |
558 | MySqlConnection::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 |
574 | MySqlConnection::~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. |
598 | void |
599 | MySqlConnection::convertToDatabaseTime(const time_t input_time, |
600 | MYSQL_TIME& output_time) { |
601 | MySqlBinding::convertToDatabaseTime(input_time, output_time); |
602 | } |
603 | |
604 | void |
605 | MySqlConnection::convertToDatabaseTime(const time_t cltt, |
606 | const uint32_t valid_lifetime, |
607 | MYSQL_TIME& expire) { |
608 | MySqlBinding::convertToDatabaseTime(cltt, valid_lifetime, expire); |
609 | } |
610 | |
611 | void |
612 | MySqlConnection::convertFromDatabaseTime(const MYSQL_TIME& expire, |
613 | uint32_t valid_lifetime, time_t& cltt) { |
614 | MySqlBinding::convertFromDatabaseTime(expire, valid_lifetime, cltt); |
615 | } |
616 | |
617 | void |
618 | MySqlConnection::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 | |
635 | bool |
636 | MySqlConnection::isTransactionStarted() const { |
637 | return (transaction_ref_count_ > 0); |
638 | } |
639 | |
640 | void |
641 | MySqlConnection::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 | |
658 | void |
659 | MySqlConnection::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 | |
676 | template<typename T> |
677 | void |
678 | MySqlConnection::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 |