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