1 /* Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.
2 
3    This program is free software; you can redistribute it and/or modify
4    it under the terms of the GNU General Public License, version 2.0,
5    as published by the Free Software Foundation.
6 
7    This program is also distributed with certain software (including
8    but not limited to OpenSSL) that is licensed under separate terms,
9    as designated in a particular file or component or in included license
10    documentation.  The authors of MySQL hereby grant you an additional
11    permission to link the program and your derivative works with the
12    separately licensed software that they have included with MySQL.
13 
14    This program is distributed in the hope that it will be useful,
15    but WITHOUT ANY WARRANTY; without even the implied warranty of
16    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17    GNU General Public License, version 2.0, for more details.
18 
19    You should have received a copy of the GNU General Public License
20    along with this program; if not, write to the Free Software
21    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA */
22 
23 /*
24   The servers are saved in the system table "servers"
25 
26   Currently, when the user performs an ALTER SERVER or a DROP SERVER
27   operation, it will cause all open tables which refer to the named
28   server connection to be flushed. This may cause some undesirable
29   behaviour with regard to currently running transactions. It is
30   expected that the DBA knows what s/he is doing when s/he performs
31   the ALTER SERVER or DROP SERVER operation.
32 
33   TODO:
34   It is desirable for us to implement a callback mechanism instead where
35   callbacks can be registered for specific server protocols. The callback
36   will be fired when such a server name has been created/altered/dropped
37   or when statistics are to be gathered such as how many actual connections.
38   Storage engines etc will be able to make use of the callback so that
39   currently running transactions etc will not be disrupted.
40 */
41 
42 #include "sql/sql_servers.h"
43 
44 #include <stdlib.h>
45 #include <string.h>
46 #include <atomic>
47 #include <memory>
48 #include <string>
49 #include <unordered_map>
50 #include <utility>
51 
52 #include "m_ctype.h"
53 #include "m_string.h"
54 #include "map_helpers.h"
55 #include "my_alloc.h"
56 #include "my_base.h"
57 #include "my_dbug.h"
58 #include "my_inttypes.h"
59 #include "my_loglevel.h"
60 #include "my_macros.h"
61 #include "my_psi_config.h"
62 #include "my_sys.h"
63 #include "mysql/components/services/log_builtins.h"
64 #include "mysql/components/services/mysql_rwlock_bits.h"
65 #include "mysql/components/services/psi_memory_bits.h"
66 #include "mysql/components/services/psi_rwlock_bits.h"
67 #include "mysql/psi/mysql_memory.h"
68 #include "mysql/psi/mysql_mutex.h"
69 #include "mysql/psi/mysql_rwlock.h"
70 #include "mysql/psi/psi_base.h"
71 #include "mysqld_error.h"
72 #include "sql/auth/auth_acls.h"
73 #include "sql/auth/auth_common.h"
74 #include "sql/field.h"
75 #include "sql/handler.h"
76 #include "sql/psi_memory_key.h"  // key_memory_servers
77 #include "sql/records.h"         // init_read_record
78 #include "sql/row_iterator.h"
79 #include "sql/sql_backup_lock.h"  // acquire_shared_backup_lock
80 #include "sql/sql_base.h"         // close_mysql_tables
81 #include "sql/sql_class.h"
82 #include "sql/sql_const.h"
83 #include "sql/sql_error.h"
84 #include "sql/sql_system_table_check.h"  // System_table_intact
85 #include "sql/system_variables.h"
86 #include "sql/table.h"
87 #include "sql/thd_raii.h"
88 #include "sql/thr_malloc.h"
89 #include "sql/transaction.h"  // trans_rollback_stmt, trans_commit_stmt
90 #include "thr_lock.h"
91 
92 /*
93   We only use 1 mutex to guard the data structures - THR_LOCK_servers.
94   Read locked when only reading data and write-locked for all other access.
95 */
96 
97 static collation_unordered_map<std::string, FOREIGN_SERVER *> *servers_cache;
98 static MEM_ROOT mem;
99 static mysql_rwlock_t THR_LOCK_servers;
100 
101 /**
102    This enum describes the structure of the mysql.servers table.
103 */
104 enum enum_servers_table_field {
105   SERVERS_FIELD_NAME = 0,
106   SERVERS_FIELD_HOST,
107   SERVERS_FIELD_DB,
108   SERVERS_FIELD_USERNAME,
109   SERVERS_FIELD_PASSWORD,
110   SERVERS_FIELD_PORT,
111   SERVERS_FIELD_SOCKET,
112   SERVERS_FIELD_SCHEME,
113   SERVERS_FIELD_OWNER
114 };
115 
116 // mysql.servers table definition.
117 static const int MYSQL_SERVERS_TABLE_FIELD_COUNT = 9;
118 static const TABLE_FIELD_TYPE
119     mysql_servers_table_fields[MYSQL_SERVERS_TABLE_FIELD_COUNT] = {
120         {{STRING_WITH_LEN("Server_name")},
121          {STRING_WITH_LEN("char(64)")},
122          {nullptr, 0}},
123         {{STRING_WITH_LEN("Host")},
124          {STRING_WITH_LEN("char(255)")},
125          {STRING_WITH_LEN("ascii")}},
126         {{STRING_WITH_LEN("Db")}, {STRING_WITH_LEN("char(64)")}, {nullptr, 0}},
127         {{STRING_WITH_LEN("Username")},
128          {STRING_WITH_LEN("char(64)")},
129          {nullptr, 0}},
130         {{STRING_WITH_LEN("Password")},
131          {STRING_WITH_LEN("char(64)")},
132          {nullptr, 0}},
133         {{STRING_WITH_LEN("Port")}, {STRING_WITH_LEN("int")}, {nullptr, 0}},
134         {{STRING_WITH_LEN("Socket")},
135          {STRING_WITH_LEN("char(64)")},
136          {nullptr, 0}},
137         {{STRING_WITH_LEN("Wrapper")},
138          {STRING_WITH_LEN("char(64)")},
139          {nullptr, 0}},
140         {{STRING_WITH_LEN("Owner")},
141          {STRING_WITH_LEN("char(64)")},
142          {nullptr, 0}}};
143 static const TABLE_FIELD_DEF mysql_servers_table_def = {
144     MYSQL_SERVERS_TABLE_FIELD_COUNT, mysql_servers_table_fields};
145 
146 static bool get_server_from_table_to_cache(TABLE *table);
147 
148 #ifdef HAVE_PSI_INTERFACE
149 static PSI_rwlock_key key_rwlock_THR_LOCK_servers;
150 
151 static PSI_rwlock_info all_servers_cache_rwlocks[] = {
152     {&key_rwlock_THR_LOCK_servers, "THR_LOCK_servers", PSI_FLAG_SINGLETON, 0,
153      PSI_DOCUMENT_ME}};
154 
155 static PSI_memory_info all_servers_cache_memory[] = {
156     {&key_memory_servers, "servers_cache", PSI_FLAG_ONLY_GLOBAL_STAT, 0,
157      PSI_DOCUMENT_ME}};
158 
init_servers_cache_psi_keys(void)159 static void init_servers_cache_psi_keys(void) {
160   const char *category = "sql";
161   int count;
162 
163   count = static_cast<int>(array_elements(all_servers_cache_rwlocks));
164   mysql_rwlock_register(category, all_servers_cache_rwlocks, count);
165 
166   count = static_cast<int>(array_elements(all_servers_cache_memory));
167   mysql_memory_register(category, all_servers_cache_memory, count);
168 }
169 #endif /* HAVE_PSI_INTERFACE */
170 
171 /*
172   Initialize structures responsible for servers used in federated
173   server scheme information for them from the server
174   table in the 'mysql' database.
175 
176   SYNOPSIS
177     servers_init()
178       dont_read_server_table  true if we want to skip loading data from
179                             server table and disable privilege checking.
180 
181   NOTES
182     This function is mostly responsible for preparatory steps, main work
183     on initialization and grants loading is done in servers_reload().
184 
185   RETURN VALUES
186     0	ok
187     1	Could not initialize servers
188 */
189 
servers_init(bool dont_read_servers_table)190 bool servers_init(bool dont_read_servers_table) {
191   THD *thd;
192   bool return_val = false;
193   DBUG_TRACE;
194 
195 #ifdef HAVE_PSI_INTERFACE
196   init_servers_cache_psi_keys();
197 #endif
198 
199   /* init the mutex */
200   if (mysql_rwlock_init(key_rwlock_THR_LOCK_servers, &THR_LOCK_servers))
201     return true;
202 
203   /* initialise our servers cache */
204   servers_cache = new collation_unordered_map<std::string, FOREIGN_SERVER *>(
205       system_charset_info, key_memory_servers);
206 
207   /* Initialize the mem root for data */
208   init_sql_alloc(key_memory_servers, &mem, ACL_ALLOC_BLOCK_SIZE, 0);
209 
210   if (dont_read_servers_table) goto end;
211 
212   /*
213     To be able to run this from boot, we allocate a temporary THD
214   */
215   if (!(thd = new THD)) return true;
216   thd->thread_stack = (char *)&thd;
217   thd->store_globals();
218   /*
219     It is safe to call servers_reload() since servers_* arrays and hashes which
220     will be freed there are global static objects and thus are initialized
221     by zeros at startup.
222   */
223   return_val = servers_reload(thd);
224   delete thd;
225 
226 end:
227   return return_val;
228 }
229 
230 /*
231   Initialize server structures
232 
233   SYNOPSIS
234     servers_load()
235       thd     Current thread
236       tables  List containing open "mysql.servers"
237 
238   RETURN VALUES
239     false  Success
240     true   Error
241 
242   TODO
243     Revert back to old list if we failed to load new one.
244 */
245 
servers_load(THD * thd,TABLE * table)246 static bool servers_load(THD *thd, TABLE *table) {
247   DBUG_TRACE;
248 
249   if (servers_cache != nullptr) {
250     servers_cache->clear();
251   }
252   free_root(&mem, MYF(0));
253   init_sql_alloc(key_memory_servers, &mem, ACL_ALLOC_BLOCK_SIZE, 0);
254 
255   unique_ptr_destroy_only<RowIterator> iterator =
256       init_table_iterator(thd, table, nullptr, false,
257                           /*ignore_not_found_rows=*/false);
258   if (iterator == nullptr) return true;
259 
260   while (!(iterator->Read())) {
261     if ((get_server_from_table_to_cache(table))) return true;
262   }
263 
264   return false;
265 }
266 
267 /*
268   Forget current servers cache and read new servers
269   from the conneciton table.
270 
271   SYNOPSIS
272     servers_reload()
273       thd  Current thread
274 
275   NOTE
276     All tables of calling thread which were open and locked by LOCK TABLES
277     statement will be unlocked and closed.
278     This function is also used for initialization of structures responsible
279     for user/db-level privilege checking.
280 
281   RETURN VALUE
282     false  Success
283     true   Failure
284 */
285 
servers_reload(THD * thd)286 bool servers_reload(THD *thd) {
287   bool return_val = true;
288   DBUG_TRACE;
289 
290   DBUG_PRINT("info", ("locking servers_cache"));
291   mysql_rwlock_wrlock(&THR_LOCK_servers);
292 
293   TABLE_LIST tables("mysql", "servers", TL_READ);
294   if (open_trans_system_tables_for_read(thd, &tables)) {
295     /*
296       Execution might have been interrupted; only print the error message
297       if an error condition has been raised.
298     */
299     if (thd->get_stmt_da()->is_error())
300       LogErr(ERROR_LEVEL, ER_CANT_OPEN_AND_LOCK_PRIVILEGE_TABLES,
301              thd->get_stmt_da()->message_text());
302     goto end;
303   }
304 
305   if ((return_val =
306            servers_load(thd, tables.table))) {  // Error. Revert to old list
307     /* blast, for now, we have no servers, discuss later way to preserve */
308 
309     DBUG_PRINT("error", ("Reverting to old privileges"));
310     servers_free();
311   }
312 
313   close_trans_system_tables(thd);
314 end:
315   DBUG_PRINT("info", ("unlocking servers_cache"));
316   mysql_rwlock_unlock(&THR_LOCK_servers);
317   return return_val;
318 }
319 
320 /*
321   Initialize structures responsible for servers used in federated
322   server scheme information for them from the server
323   table in the 'mysql' database.
324 
325   SYNOPSIS
326     get_server_from_table_to_cache()
327       TABLE *table         open table pointer
328 
329 
330   NOTES
331     This function takes a TABLE pointer (pointing to an opened
332     table). With this open table, a FOREIGN_SERVER struct pointer
333     is allocated into root memory, then each member of the FOREIGN_SERVER
334     struct is populated. A char pointer takes the return value of get_field
335     for each column we're interested in obtaining, and if that pointer
336     isn't 0x0, the FOREIGN_SERVER member is set to that value, otherwise,
337     is set to the value of an empty string, since get_field would set it to
338     0x0 if the column's value is empty, even if the default value for that
339     column is NOT NULL.
340 
341   RETURN VALUES
342     0	ok
343     1	could not insert server struct into global servers cache
344 */
345 
get_server_from_table_to_cache(TABLE * table)346 static bool get_server_from_table_to_cache(TABLE *table) {
347   /* alloc a server struct */
348   char *ptr;
349   char *blank = const_cast<char *>("");
350   FOREIGN_SERVER *server = new (&mem) FOREIGN_SERVER();
351 
352   DBUG_TRACE;
353   table->use_all_columns();
354 
355   /* get each field into the server struct ptr */
356   ptr = get_field(&mem, table->field[SERVERS_FIELD_NAME]);
357   server->server_name = ptr ? ptr : blank;
358   server->server_name_length = strlen(server->server_name);
359   ptr = get_field(&mem, table->field[SERVERS_FIELD_HOST]);
360   server->host = ptr ? ptr : blank;
361   ptr = get_field(&mem, table->field[SERVERS_FIELD_DB]);
362   server->db = ptr ? ptr : blank;
363   ptr = get_field(&mem, table->field[SERVERS_FIELD_USERNAME]);
364   server->username = ptr ? ptr : blank;
365   ptr = get_field(&mem, table->field[SERVERS_FIELD_PASSWORD]);
366   server->password = ptr ? ptr : blank;
367   ptr = get_field(&mem, table->field[SERVERS_FIELD_PORT]);
368   server->sport = ptr ? ptr : blank;
369 
370   server->port = server->sport ? atoi(server->sport) : 0;
371 
372   ptr = get_field(&mem, table->field[SERVERS_FIELD_SOCKET]);
373   server->socket = ptr && strlen(ptr) ? ptr : blank;
374   ptr = get_field(&mem, table->field[SERVERS_FIELD_SCHEME]);
375   server->scheme = ptr ? ptr : blank;
376   ptr = get_field(&mem, table->field[SERVERS_FIELD_OWNER]);
377   server->owner = ptr ? ptr : blank;
378   DBUG_PRINT("info", ("server->server_name %s", server->server_name));
379   DBUG_PRINT("info", ("server->host %s", server->host));
380   DBUG_PRINT("info", ("server->db %s", server->db));
381   DBUG_PRINT("info", ("server->username %s", server->username));
382   DBUG_PRINT("info", ("server->password %s", server->password));
383   DBUG_PRINT("info", ("server->socket %s", server->socket));
384   servers_cache->emplace(server->server_name, server);
385   return false;
386 }
387 
388 /**
389   Close all tables which match specified connection string or
390   if specified string is NULL, then any table with a connection string.
391 */
392 
close_cached_connection_tables(THD * thd,const char * connection_string,size_t connection_length)393 static bool close_cached_connection_tables(THD *thd,
394                                            const char *connection_string,
395                                            size_t connection_length) {
396   TABLE_LIST tmp, *tables = nullptr;
397   bool result = false;
398   DBUG_TRACE;
399   DBUG_ASSERT(thd);
400 
401   mysql_mutex_lock(&LOCK_open);
402 
403   for (const auto &key_and_value : *table_def_cache) {
404     TABLE_SHARE *share = key_and_value.second.get();
405 
406     /*
407       Skip table shares being opened to avoid comparison reading into
408       uninitialized memory further below.
409 
410       Thus, in theory, there is a risk that shares are left in the
411       cache that should really be closed (matching the submitted
412       connection string), and this risk is already present since
413       LOCK_open is unlocked before calling this function. However,
414       this function is called as the final step of DROP/ALTER SERVER,
415       so its goal is to flush all tables which were open before
416       DROP/ALTER SERVER started. Thus, if a share gets opened after
417       this function is called, the information about the server has
418       already been updated, so the new table will use the new
419       definition of the server.
420 
421       It might have been an issue, however if one thread started
422       opening a federated table, read the old server definition into a
423       share, and then a switch to another thread doing ALTER SERVER
424       happened right before setting m_open_in_progress to false for
425       the share. Because in this case ALTER SERVER would not flush
426       the share opened by the first thread as it should have been. But
427       luckily, server definitions affected by * SERVER statements are
428       not read into TABLE_SHARE structures, but are read when we
429       create the TABLE object in ha_federated::open().
430 
431       This means that ignoring shares that are in the process of being
432       opened is safe, because such shares don't have TABLE objects
433       associated with them yet.
434     */
435     if (share->m_open_in_progress) continue;
436 
437     /* Ignore if table is not open or does not have a connect_string */
438     if (!share->connect_string.length || share->ref_count() == 0) continue;
439 
440     /* Compare the connection string */
441     if (connection_string &&
442         (connection_length > share->connect_string.length ||
443          (connection_length < share->connect_string.length &&
444           (share->connect_string.str[connection_length] != '/' &&
445            share->connect_string.str[connection_length] != '\\')) ||
446          native_strncasecmp(connection_string, share->connect_string.str,
447                             connection_length)))
448       continue;
449 
450     /* close_cached_tables() only uses these elements */
451     tmp.db = share->db.str;
452     tmp.table_name = share->table_name.str;
453     tmp.next_local = tables;
454 
455     tables = new (thd->mem_root) TABLE_LIST(tmp);
456   }
457   mysql_mutex_unlock(&LOCK_open);
458 
459   if (tables) result = close_cached_tables(thd, tables, false, LONG_TIMEOUT);
460 
461   return result;
462 }
463 
reset()464 void Server_options::reset() {
465   m_server_name.str = nullptr;
466   m_server_name.length = 0;
467   m_port = PORT_NOT_SET;
468   m_host.str = nullptr;
469   m_host.length = 0;
470   m_db.str = nullptr;
471   m_db.length = 0;
472   m_username.str = nullptr;
473   m_db.length = 0;
474   m_password.str = nullptr;
475   m_password.length = 0;
476   m_scheme.str = nullptr;
477   m_scheme.length = 0;
478   m_socket.str = nullptr;
479   m_socket.length = 0;
480   m_owner.str = nullptr;
481   m_owner.length = 0;
482 }
483 
insert_into_cache() const484 bool Server_options::insert_into_cache() const {
485   char *unset_ptr = const_cast<char *>("");
486   DBUG_TRACE;
487 
488   FOREIGN_SERVER *server = new (&mem) FOREIGN_SERVER();
489   if (!server) return true;
490 
491   /* these two MUST be set */
492   if (!(server->server_name = strdup_root(&mem, m_server_name.str)))
493     return true;
494   server->server_name_length = m_server_name.length;
495 
496   if (!(server->host = m_host.str ? strdup_root(&mem, m_host.str) : unset_ptr))
497     return true;
498 
499   if (!(server->db = m_db.str ? strdup_root(&mem, m_db.str) : unset_ptr))
500     return true;
501 
502   if (!(server->username =
503             m_username.str ? strdup_root(&mem, m_username.str) : unset_ptr))
504     return true;
505 
506   if (!(server->password =
507             m_password.str ? strdup_root(&mem, m_password.str) : unset_ptr))
508     return true;
509 
510   /* set to 0 if not specified */
511   server->port = m_port != PORT_NOT_SET ? m_port : 0;
512 
513   if (!(server->socket =
514             m_socket.str ? strdup_root(&mem, m_socket.str) : unset_ptr))
515     return true;
516 
517   if (!(server->scheme =
518             m_scheme.str ? strdup_root(&mem, m_scheme.str) : unset_ptr))
519     return true;
520 
521   if (!(server->owner =
522             m_owner.str ? strdup_root(&mem, m_owner.str) : unset_ptr))
523     return true;
524 
525   servers_cache->emplace(
526       std::string(server->server_name, server->server_name_length), server);
527   return false;
528 }
529 
update_cache(FOREIGN_SERVER * existing) const530 bool Server_options::update_cache(FOREIGN_SERVER *existing) const {
531   DBUG_TRACE;
532 
533   /*
534     Note: Since the name can't change, we don't need to set it.
535     This also means we can just update the existing cache entry.
536   */
537 
538   /*
539     The logic here is this: is this value set AND is it different
540     than the existing value?
541   */
542   if (m_host.str && strcmp(m_host.str, existing->host) &&
543       !(existing->host = strdup_root(&mem, m_host.str)))
544     return true;
545 
546   if (m_db.str && strcmp(m_db.str, existing->db) &&
547       !(existing->db = strdup_root(&mem, m_db.str)))
548     return true;
549 
550   if (m_username.str && strcmp(m_username.str, existing->username) &&
551       !(existing->username = strdup_root(&mem, m_username.str)))
552     return true;
553 
554   if (m_password.str && strcmp(m_password.str, existing->password) &&
555       !(existing->password = strdup_root(&mem, m_password.str)))
556     return true;
557 
558   /*
559     port is initialised to PORT_NOT_SET, so if unset, it will be -1
560   */
561   if (m_port != PORT_NOT_SET && m_port != existing->port)
562     existing->port = m_port;
563 
564   if (m_socket.str && strcmp(m_socket.str, existing->socket) &&
565       !(existing->socket = strdup_root(&mem, m_socket.str)))
566     return true;
567 
568   if (m_scheme.str && strcmp(m_scheme.str, existing->scheme) &&
569       !(existing->scheme = strdup_root(&mem, m_scheme.str)))
570     return true;
571 
572   if (m_owner.str && strcmp(m_owner.str, existing->owner) &&
573       !(existing->owner = strdup_root(&mem, m_owner.str)))
574     return true;
575 
576   return false;
577 }
578 
579 /**
580    Helper function for creating a record for inserting
581    a new server into the mysql.servers table.
582 
583    Set a field to the given parser string. If the parser
584    string is empty, set the field to "" instead.
585 */
586 
store_new_field(TABLE * table,enum_servers_table_field field,const LEX_STRING * val)587 static inline void store_new_field(TABLE *table, enum_servers_table_field field,
588                                    const LEX_STRING *val) {
589   if (val->str)
590     table->field[field]->store(val->str, val->length, system_charset_info);
591   else
592     table->field[field]->store("", 0U, system_charset_info);
593 }
594 
store_new_server(TABLE * table) const595 void Server_options::store_new_server(TABLE *table) const {
596   store_new_field(table, SERVERS_FIELD_HOST, &m_host);
597   store_new_field(table, SERVERS_FIELD_DB, &m_db);
598   store_new_field(table, SERVERS_FIELD_USERNAME, &m_username);
599   store_new_field(table, SERVERS_FIELD_PASSWORD, &m_password);
600 
601   if (m_port != PORT_NOT_SET)
602     table->field[SERVERS_FIELD_PORT]->store(m_port);
603   else
604     table->field[SERVERS_FIELD_PORT]->store(0);
605 
606   store_new_field(table, SERVERS_FIELD_SOCKET, &m_socket);
607   store_new_field(table, SERVERS_FIELD_SCHEME, &m_scheme);
608   store_new_field(table, SERVERS_FIELD_OWNER, &m_owner);
609 }
610 
611 /**
612    Helper function for creating a record for updating
613    an existing server in the mysql.servers table.
614 
615    Set a field to the given parser string unless
616    the parser string is empty or equal to the existing value.
617 */
618 
store_updated_field(TABLE * table,enum_servers_table_field field,const char * existing_val,const LEX_STRING * new_val)619 static inline void store_updated_field(TABLE *table,
620                                        enum_servers_table_field field,
621                                        const char *existing_val,
622                                        const LEX_STRING *new_val) {
623   if (new_val->str && strcmp(new_val->str, existing_val))
624     table->field[field]->store(new_val->str, new_val->length,
625                                system_charset_info);
626 }
627 
store_altered_server(TABLE * table,FOREIGN_SERVER * existing) const628 void Server_options::store_altered_server(TABLE *table,
629                                           FOREIGN_SERVER *existing) const {
630   store_updated_field(table, SERVERS_FIELD_HOST, existing->host, &m_host);
631   store_updated_field(table, SERVERS_FIELD_DB, existing->db, &m_db);
632   store_updated_field(table, SERVERS_FIELD_USERNAME, existing->username,
633                       &m_username);
634   store_updated_field(table, SERVERS_FIELD_PASSWORD, existing->password,
635                       &m_password);
636 
637   if (m_port != PORT_NOT_SET && m_port != existing->port)
638     table->field[SERVERS_FIELD_PORT]->store(m_port);
639 
640   store_updated_field(table, SERVERS_FIELD_SOCKET, existing->socket, &m_socket);
641   store_updated_field(table, SERVERS_FIELD_SCHEME, existing->scheme, &m_scheme);
642   store_updated_field(table, SERVERS_FIELD_OWNER, existing->owner, &m_owner);
643 }
644 
check_and_open_table(THD * thd)645 bool Sql_cmd_common_server::check_and_open_table(THD *thd) {
646   if (check_global_access(thd, SUPER_ACL) ||
647       acquire_shared_backup_lock(thd, thd->variables.lock_wait_timeout))
648     return true;
649 
650   TABLE_LIST tables("mysql", "servers", TL_WRITE);
651 
652   table = open_ltable(thd, &tables, TL_WRITE, MYSQL_LOCK_IGNORE_TIMEOUT);
653   if (table == nullptr) return true;
654 
655   /*
656     System table mysql.servers is supported by only InnoDB engine. Changing
657     table's engine is not allowed. But to support logical upgrade creating
658     system table is allowed in MyISAM engine. CREATE, ALTER and DROP SERVER
659     operations are not allowed in this case.
660   */
661   if ((table->file->ht->is_supported_system_table != nullptr) &&
662       !table->file->ht->is_supported_system_table(tables.db, tables.table_name,
663                                                   true)) {
664     my_error(ER_UNSUPPORTED_ENGINE, MYF(0),
665              ha_resolve_storage_engine_name(table->file->ht), tables.db,
666              tables.table_name);
667     return true;
668   }
669 
670   /*
671     CREATE, ALTER and DROP SERVER operations are *not* allowed if table
672     structure is changed.
673   */
674   System_table_intact table_intact(thd);
675   if (table_intact.check(thd, table, &mysql_servers_table_def)) return true;
676 
677   return false;
678 }
679 
execute(THD * thd)680 bool Sql_cmd_create_server::execute(THD *thd) {
681   DBUG_TRACE;
682 
683   if (Sql_cmd_common_server::check_and_open_table(thd)) return true;
684 
685   // Check for existing cache entries with same name
686   mysql_rwlock_wrlock(&THR_LOCK_servers);
687   const auto it =
688       servers_cache->find(to_string(m_server_options->m_server_name));
689   if (it != servers_cache->end()) {
690     mysql_rwlock_unlock(&THR_LOCK_servers);
691     my_error(ER_FOREIGN_SERVER_EXISTS, MYF(0),
692              m_server_options->m_server_name.str);
693     trans_rollback_stmt(thd);
694     close_mysql_tables(thd);
695     return true;
696   }
697 
698   int error;
699   {
700     Disable_binlog_guard binlog_guard(thd);
701     table->use_all_columns();
702     empty_record(table);
703 
704     /* set the field that's the PK to the value we're looking for */
705     table->field[SERVERS_FIELD_NAME]->store(
706         m_server_options->m_server_name.str,
707         m_server_options->m_server_name.length, system_charset_info);
708 
709     /* read index until record is that specified in server_name */
710     error = table->file->ha_index_read_idx_map(
711         table->record[0], 0, table->field[SERVERS_FIELD_NAME]->field_ptr(),
712         HA_WHOLE_KEY, HA_READ_KEY_EXACT);
713 
714     if (!error) {
715       my_error(ER_FOREIGN_SERVER_EXISTS, MYF(0),
716                m_server_options->m_server_name.str);
717       error = 1;
718     } else if (error != HA_ERR_KEY_NOT_FOUND && error != HA_ERR_END_OF_FILE) {
719       /* if not found, err */
720       table->file->print_error(error, MYF(0));
721     } else {
722       /* store each field to be inserted */
723       m_server_options->store_new_server(table);
724 
725       /* write/insert the new server */
726       if ((error = table->file->ha_write_row(table->record[0])))
727         table->file->print_error(error, MYF(0));
728       else {
729         /* insert the server into the cache */
730         if ((error = m_server_options->insert_into_cache()))
731           my_error(ER_OUT_OF_RESOURCES, MYF(0));
732       }
733     }
734   }
735 
736   mysql_rwlock_unlock(&THR_LOCK_servers);
737 
738   if (error)
739     trans_rollback_stmt(thd);
740   else
741     trans_commit_stmt(thd);
742   close_mysql_tables(thd);
743 
744   if (error == 0 && !thd->killed) my_ok(thd, 1);
745   return error != 0 || thd->killed;
746 }
747 
execute(THD * thd)748 bool Sql_cmd_alter_server::execute(THD *thd) {
749   DBUG_TRACE;
750 
751   if (Sql_cmd_common_server::check_and_open_table(thd)) return true;
752 
753   // Find existing cache entry to update
754   mysql_rwlock_wrlock(&THR_LOCK_servers);
755   const auto it =
756       servers_cache->find(to_string(m_server_options->m_server_name));
757   if (it == servers_cache->end()) {
758     my_error(ER_FOREIGN_SERVER_DOESNT_EXIST, MYF(0),
759              m_server_options->m_server_name.str);
760     mysql_rwlock_unlock(&THR_LOCK_servers);
761     trans_rollback_stmt(thd);
762     close_mysql_tables(thd);
763     return true;
764   }
765 
766   FOREIGN_SERVER *existing = it->second;
767 
768   int error;
769   {
770     Disable_binlog_guard binlog_guard(table->in_use);
771     table->use_all_columns();
772 
773     /* set the field that's the PK to the value we're looking for */
774     table->field[SERVERS_FIELD_NAME]->store(
775         m_server_options->m_server_name.str,
776         m_server_options->m_server_name.length, system_charset_info);
777 
778     error = table->file->ha_index_read_idx_map(
779         table->record[0], 0, table->field[SERVERS_FIELD_NAME]->field_ptr(),
780         ~(longlong)0, HA_READ_KEY_EXACT);
781     if (error) {
782       if (error != HA_ERR_KEY_NOT_FOUND && error != HA_ERR_END_OF_FILE)
783         table->file->print_error(error, MYF(0));
784       else
785         my_error(ER_FOREIGN_SERVER_DOESNT_EXIST, MYF(0),
786                  m_server_options->m_server_name.str);
787     } else {
788       /* ok, so we can update since the record exists in the table */
789       store_record(table, record[1]);
790       m_server_options->store_altered_server(table, existing);
791       if ((error = table->file->ha_update_row(table->record[1],
792                                               table->record[0])) &&
793           error != HA_ERR_RECORD_IS_THE_SAME)
794         table->file->print_error(error, MYF(0));
795       else {
796         // Update cache entry
797         if ((error = m_server_options->update_cache(existing)))
798           my_error(ER_OUT_OF_RESOURCES, MYF(0));
799       }
800     }
801   }
802 
803   /* Perform a reload so we don't have a 'hole' in our mem_root */
804   servers_load(thd, table);
805 
806   // NOTE: servers_load() must be called under acquired THR_LOCK_servers.
807   mysql_rwlock_unlock(&THR_LOCK_servers);
808 
809   if (error)
810     trans_rollback_stmt(thd);
811   else
812     trans_commit_stmt(thd);
813   close_mysql_tables(thd);
814 
815   if (close_cached_connection_tables(thd, m_server_options->m_server_name.str,
816                                      m_server_options->m_server_name.length)) {
817     push_warning(thd, Sql_condition::SL_WARNING, ER_UNKNOWN_ERROR,
818                  "Server connection in use");
819   }
820 
821   if (error == 0 && !thd->killed) my_ok(thd, 1);
822   return error != 0 || thd->killed;
823 }
824 
execute(THD * thd)825 bool Sql_cmd_drop_server::execute(THD *thd) {
826   DBUG_TRACE;
827 
828   if (Sql_cmd_common_server::check_and_open_table(thd)) return true;
829 
830   int error;
831   mysql_rwlock_wrlock(&THR_LOCK_servers);
832   {
833     Disable_binlog_guard binlog_guard(table->in_use);
834     table->use_all_columns();
835 
836     /* set the field that's the PK to the value we're looking for */
837     table->field[SERVERS_FIELD_NAME]->store(
838         m_server_name.str, m_server_name.length, system_charset_info);
839 
840     error = table->file->ha_index_read_idx_map(
841         table->record[0], 0, table->field[SERVERS_FIELD_NAME]->field_ptr(),
842         HA_WHOLE_KEY, HA_READ_KEY_EXACT);
843     if (error) {
844       if (error != HA_ERR_KEY_NOT_FOUND && error != HA_ERR_END_OF_FILE)
845         table->file->print_error(error, MYF(0));
846       else if (!m_if_exists)
847         my_error(ER_FOREIGN_SERVER_DOESNT_EXIST, MYF(0), m_server_name.str);
848       else
849         error = 0;  // Reset error - we will report my_ok() in this case.
850     } else {
851       // Delete from table
852       if ((error = table->file->ha_delete_row(table->record[0])))
853         table->file->print_error(error, MYF(0));
854       else {
855         // Remove from cache
856         size_t num_erased = servers_cache->erase(to_string(m_server_name));
857         if (num_erased == 0 && !m_if_exists) {
858           my_error(ER_FOREIGN_SERVER_DOESNT_EXIST, MYF(0), m_server_name.str);
859           error = 1;
860         }
861       }
862     }
863   }
864 
865   mysql_rwlock_unlock(&THR_LOCK_servers);
866 
867   if (error)
868     trans_rollback_stmt(thd);
869   else
870     trans_commit_stmt(thd);
871   close_mysql_tables(thd);
872 
873   if (close_cached_connection_tables(thd, m_server_name.str,
874                                      m_server_name.length)) {
875     push_warning(thd, Sql_condition::SL_WARNING, ER_UNKNOWN_ERROR,
876                  "Server connection in use");
877   }
878 
879   if (error == 0 && !thd->killed) my_ok(thd, 1);
880   return error != 0 || thd->killed;
881 }
882 
servers_free(bool end)883 void servers_free(bool end) {
884   DBUG_TRACE;
885   if (servers_cache == nullptr) return;
886   if (!end) {
887     free_root(&mem, MYF(MY_MARK_BLOCKS_FREE));
888     servers_cache->clear();
889     return;
890   }
891   mysql_rwlock_destroy(&THR_LOCK_servers);
892   free_root(&mem, MYF(0));
893   delete servers_cache;
894   servers_cache = nullptr;
895 }
896 
897 /*
898   SYNOPSIS
899 
900   clone_server(MEM_ROOT *mem_root, FOREIGN_SERVER *orig, FOREIGN_SERVER *buff)
901 
902   Create a clone of FOREIGN_SERVER. If the supplied mem_root is of
903   thd->mem_root then the copy is automatically disposed at end of statement.
904 
905   NOTES
906 
907   ARGS
908    MEM_ROOT pointer (strings are copied into this mem root)
909    FOREIGN_SERVER pointer (made a copy of)
910    FOREIGN_SERVER buffer (if not-NULL, this pointer is returned)
911 
912   RETURN VALUE
913    FOREIGN_SEVER pointer (copy of one supplied FOREIGN_SERVER)
914 */
915 
clone_server(MEM_ROOT * mem,const FOREIGN_SERVER * server,FOREIGN_SERVER * buffer)916 static FOREIGN_SERVER *clone_server(MEM_ROOT *mem, const FOREIGN_SERVER *server,
917                                     FOREIGN_SERVER *buffer) {
918   DBUG_TRACE;
919 
920   if (!buffer) buffer = new (mem) FOREIGN_SERVER();
921 
922   buffer->server_name =
923       strmake_root(mem, server->server_name, server->server_name_length);
924   buffer->port = server->port;
925   buffer->server_name_length = server->server_name_length;
926 
927   /* TODO: We need to examine which of these can really be NULL */
928   buffer->db = server->db ? strdup_root(mem, server->db) : nullptr;
929   buffer->scheme = server->scheme ? strdup_root(mem, server->scheme) : nullptr;
930   buffer->username =
931       server->username ? strdup_root(mem, server->username) : nullptr;
932   buffer->password =
933       server->password ? strdup_root(mem, server->password) : nullptr;
934   buffer->socket = server->socket ? strdup_root(mem, server->socket) : nullptr;
935   buffer->owner = server->owner ? strdup_root(mem, server->owner) : nullptr;
936   buffer->host = server->host ? strdup_root(mem, server->host) : nullptr;
937 
938   return buffer;
939 }
940 
get_server_by_name(MEM_ROOT * mem,const char * server_name,FOREIGN_SERVER * buff)941 FOREIGN_SERVER *get_server_by_name(MEM_ROOT *mem, const char *server_name,
942                                    FOREIGN_SERVER *buff) {
943   size_t server_name_length;
944   FOREIGN_SERVER *server;
945   DBUG_TRACE;
946   DBUG_PRINT("info", ("server_name %s", server_name));
947 
948   server_name_length = strlen(server_name);
949 
950   if (!server_name || !strlen(server_name)) {
951     DBUG_PRINT("info", ("server_name not defined!"));
952     return (FOREIGN_SERVER *)nullptr;
953   }
954 
955   DBUG_PRINT("info", ("locking servers_cache"));
956   mysql_rwlock_rdlock(&THR_LOCK_servers);
957   const auto it =
958       servers_cache->find(std::string(server_name, server_name_length));
959   if (it == servers_cache->end()) {
960     DBUG_PRINT("info", ("server_name %s length %u not found!", server_name,
961                         (unsigned)server_name_length));
962     server = (FOREIGN_SERVER *)nullptr;
963   }
964   /* otherwise, make copy of server */
965   else
966     server = clone_server(mem, it->second, buff);
967 
968   DBUG_PRINT("info", ("unlocking servers_cache"));
969   mysql_rwlock_unlock(&THR_LOCK_servers);
970   return server;
971 }
972