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