1 /* Copyright (c) 2000, 2016, 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 Foundation,
21    51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA */
22 
23 
24 /*
25   The servers are saved in the system table "servers"
26 
27   Currently, when the user performs an ALTER SERVER or a DROP SERVER
28   operation, it will cause all open tables which refer to the named
29   server connection to be flushed. This may cause some undesirable
30   behaviour with regard to currently running transactions. It is
31   expected that the DBA knows what s/he is doing when s/he performs
32   the ALTER SERVER or DROP SERVER operation.
33 
34   TODO:
35   It is desirable for us to implement a callback mechanism instead where
36   callbacks can be registered for specific server protocols. The callback
37   will be fired when such a server name has been created/altered/dropped
38   or when statistics are to be gathered such as how many actual connections.
39   Storage engines etc will be able to make use of the callback so that
40   currently running transactions etc will not be disrupted.
41 */
42 
43 #include "sql_priv.h"
44 #include "sql_servers.h"
45 #include "unireg.h"
46 #include "sql_base.h"                           // close_mysql_tables
47 #include "records.h"          // init_read_record, end_read_record
48 #include "hash_filo.h"
49 #include <m_ctype.h>
50 #include <stdarg.h>
51 #include "sp_head.h"
52 #include "sp.h"
53 #include "transaction.h"
54 #include "lock.h"                               // MYSQL_LOCK_IGNORE_TIMEOUT
55 
56 /*
57   We only use 1 mutex to guard the data structures - THR_LOCK_servers.
58   Read locked when only reading data and write-locked for all other access.
59 */
60 
61 static HASH servers_cache;
62 static MEM_ROOT mem;
63 static mysql_rwlock_t THR_LOCK_servers;
64 
65 static bool get_server_from_table_to_cache(TABLE *table);
66 
67 /* insert functions */
68 static bool insert_server(THD *thd, FOREIGN_SERVER *server_options);
69 static bool insert_server_record(TABLE *table, FOREIGN_SERVER *server);
70 static bool insert_server_record_into_cache(FOREIGN_SERVER *server);
71 static FOREIGN_SERVER *
72 prepare_server_struct_for_insert(LEX_SERVER_OPTIONS *server_options);
73 /* drop functions */
74 static bool delete_server_record(TABLE *table,
75                                  char *server_name,
76                                  size_t server_name_length,
77                                  bool if_exists);
78 static bool delete_server_record_in_cache(LEX_SERVER_OPTIONS *server_options,
79                                           bool if_exists);
80 
81 /* update functions */
82 static void prepare_server_struct_for_update(LEX_SERVER_OPTIONS *server_options,
83                                              FOREIGN_SERVER *existing,
84                                              FOREIGN_SERVER *altered);
85 static bool update_server(THD *thd, FOREIGN_SERVER *existing,
86                           FOREIGN_SERVER *altered);
87 static bool update_server_record(TABLE *table, FOREIGN_SERVER *server);
88 static bool update_server_record_in_cache(FOREIGN_SERVER *existing,
89                                           FOREIGN_SERVER *altered);
90 /* utility functions */
91 static void merge_server_struct(FOREIGN_SERVER *from, FOREIGN_SERVER *to);
92 
93 
94 
servers_cache_get_key(FOREIGN_SERVER * server,size_t * length,my_bool not_used MY_ATTRIBUTE ((unused)))95 static uchar *servers_cache_get_key(FOREIGN_SERVER *server, size_t *length,
96 			       my_bool not_used MY_ATTRIBUTE((unused)))
97 {
98   DBUG_ENTER("servers_cache_get_key");
99   DBUG_PRINT("info", ("server_name_length %d server_name %s",
100                       server->server_name_length,
101                       server->server_name));
102 
103   *length= (uint) server->server_name_length;
104   DBUG_RETURN((uchar*) server->server_name);
105 }
106 
107 #ifdef HAVE_PSI_INTERFACE
108 static PSI_rwlock_key key_rwlock_THR_LOCK_servers;
109 
110 static PSI_rwlock_info all_servers_cache_rwlocks[]=
111 {
112   { &key_rwlock_THR_LOCK_servers, "THR_LOCK_servers", PSI_FLAG_GLOBAL}
113 };
114 
init_servers_cache_psi_keys(void)115 static void init_servers_cache_psi_keys(void)
116 {
117   const char* category= "sql";
118   int count;
119 
120   count= array_elements(all_servers_cache_rwlocks);
121   mysql_rwlock_register(category, all_servers_cache_rwlocks, count);
122 }
123 #endif /* HAVE_PSI_INTERFACE */
124 
125 /*
126   Initialize structures responsible for servers used in federated
127   server scheme information for them from the server
128   table in the 'mysql' database.
129 
130   SYNOPSIS
131     servers_init()
132       dont_read_server_table  TRUE if we want to skip loading data from
133                             server table and disable privilege checking.
134 
135   NOTES
136     This function is mostly responsible for preparatory steps, main work
137     on initialization and grants loading is done in servers_reload().
138 
139   RETURN VALUES
140     0	ok
141     1	Could not initialize servers
142 */
143 
servers_init(bool dont_read_servers_table)144 bool servers_init(bool dont_read_servers_table)
145 {
146   THD  *thd;
147   bool return_val= FALSE;
148   DBUG_ENTER("servers_init");
149 
150 #ifdef HAVE_PSI_INTERFACE
151   init_servers_cache_psi_keys();
152 #endif
153 
154   /* init the mutex */
155   if (mysql_rwlock_init(key_rwlock_THR_LOCK_servers, &THR_LOCK_servers))
156     DBUG_RETURN(TRUE);
157 
158   /* initialise our servers cache */
159   if (my_hash_init(&servers_cache, system_charset_info, 32, 0, 0,
160                    (my_hash_get_key) servers_cache_get_key, 0, 0))
161   {
162     return_val= TRUE; /* we failed, out of memory? */
163     goto end;
164   }
165 
166   /* Initialize the mem root for data */
167   init_sql_alloc(&mem, ACL_ALLOC_BLOCK_SIZE, 0);
168 
169   if (dont_read_servers_table)
170     goto end;
171 
172   /*
173     To be able to run this from boot, we allocate a temporary THD
174   */
175   if (!(thd=new THD))
176     DBUG_RETURN(TRUE);
177   thd->thread_stack= (char*) &thd;
178   thd->store_globals();
179   /*
180     It is safe to call servers_reload() since servers_* arrays and hashes which
181     will be freed there are global static objects and thus are initialized
182     by zeros at startup.
183   */
184   return_val= servers_reload(thd);
185   delete thd;
186   /* Remember that we don't have a THD */
187   my_pthread_setspecific_ptr(THR_THD,  0);
188 
189 end:
190   DBUG_RETURN(return_val);
191 }
192 
193 /*
194   Initialize server structures
195 
196   SYNOPSIS
197     servers_load()
198       thd     Current thread
199       tables  List containing open "mysql.servers"
200 
201   RETURN VALUES
202     FALSE  Success
203     TRUE   Error
204 
205   TODO
206     Revert back to old list if we failed to load new one.
207 */
208 
servers_load(THD * thd,TABLE_LIST * tables)209 static bool servers_load(THD *thd, TABLE_LIST *tables)
210 {
211   TABLE *table;
212   READ_RECORD read_record_info;
213   bool return_val= TRUE;
214   DBUG_ENTER("servers_load");
215 
216   my_hash_reset(&servers_cache);
217   free_root(&mem, MYF(0));
218   init_sql_alloc(&mem, ACL_ALLOC_BLOCK_SIZE, 0);
219 
220   if (init_read_record(&read_record_info, thd, table=tables[0].table,
221                        NULL, 1, 1, FALSE))
222     DBUG_RETURN(TRUE);
223 
224   while (!(read_record_info.read_record(&read_record_info)))
225   {
226     /* return_val is already TRUE, so no need to set */
227     if ((get_server_from_table_to_cache(table)))
228       goto end;
229   }
230 
231   return_val= FALSE;
232 
233 end:
234   end_read_record(&read_record_info);
235   DBUG_RETURN(return_val);
236 }
237 
238 
239 /*
240   Forget current servers cache and read new servers
241   from the conneciton table.
242 
243   SYNOPSIS
244     servers_reload()
245       thd  Current thread
246 
247   NOTE
248     All tables of calling thread which were open and locked by LOCK TABLES
249     statement will be unlocked and closed.
250     This function is also used for initialization of structures responsible
251     for user/db-level privilege checking.
252 
253   RETURN VALUE
254     FALSE  Success
255     TRUE   Failure
256 */
257 
servers_reload(THD * thd)258 bool servers_reload(THD *thd)
259 {
260   TABLE_LIST tables[1];
261   bool return_val= TRUE;
262   DBUG_ENTER("servers_reload");
263 
264   DBUG_PRINT("info", ("locking servers_cache"));
265   mysql_rwlock_wrlock(&THR_LOCK_servers);
266 
267   tables[0].init_one_table("mysql", 5, "servers", 7, "servers", TL_READ);
268 
269   if (open_and_lock_tables(thd, tables, FALSE, MYSQL_LOCK_IGNORE_TIMEOUT))
270   {
271     /*
272       Execution might have been interrupted; only print the error message
273       if an error condition has been raised.
274     */
275     if (thd->get_stmt_da()->is_error())
276       sql_print_error("Can't open and lock privilege tables: %s",
277                       thd->get_stmt_da()->message());
278     return_val= FALSE;
279     goto end;
280   }
281 
282   if ((return_val= servers_load(thd, tables)))
283   {					// Error. Revert to old list
284     /* blast, for now, we have no servers, discuss later way to preserve */
285 
286     DBUG_PRINT("error",("Reverting to old privileges"));
287     servers_free();
288   }
289 
290 end:
291   close_mysql_tables(thd);
292   DBUG_PRINT("info", ("unlocking servers_cache"));
293   mysql_rwlock_unlock(&THR_LOCK_servers);
294   DBUG_RETURN(return_val);
295 }
296 
297 
298 /*
299   Initialize structures responsible for servers used in federated
300   server scheme information for them from the server
301   table in the 'mysql' database.
302 
303   SYNOPSIS
304     get_server_from_table_to_cache()
305       TABLE *table         open table pointer
306 
307 
308   NOTES
309     This function takes a TABLE pointer (pointing to an opened
310     table). With this open table, a FOREIGN_SERVER struct pointer
311     is allocated into root memory, then each member of the FOREIGN_SERVER
312     struct is populated. A char pointer takes the return value of get_field
313     for each column we're interested in obtaining, and if that pointer
314     isn't 0x0, the FOREIGN_SERVER member is set to that value, otherwise,
315     is set to the value of an empty string, since get_field would set it to
316     0x0 if the column's value is empty, even if the default value for that
317     column is NOT NULL.
318 
319   RETURN VALUES
320     0	ok
321     1	could not insert server struct into global servers cache
322 */
323 
324 static bool
get_server_from_table_to_cache(TABLE * table)325 get_server_from_table_to_cache(TABLE *table)
326 {
327   /* alloc a server struct */
328   char *ptr;
329   char * const blank= (char*)"";
330   FOREIGN_SERVER *server= (FOREIGN_SERVER *)alloc_root(&mem,
331                                                        sizeof(FOREIGN_SERVER));
332   DBUG_ENTER("get_server_from_table_to_cache");
333   table->use_all_columns();
334 
335   /* get each field into the server struct ptr */
336   ptr= get_field(&mem, table->field[0]);
337   server->server_name= ptr ? ptr : blank;
338   server->server_name_length= (uint) strlen(server->server_name);
339   ptr= get_field(&mem, table->field[1]);
340   server->host= ptr ? ptr : blank;
341   ptr= get_field(&mem, table->field[2]);
342   server->db= ptr ? ptr : blank;
343   ptr= get_field(&mem, table->field[3]);
344   server->username= ptr ? ptr : blank;
345   ptr= get_field(&mem, table->field[4]);
346   server->password= ptr ? ptr : blank;
347   ptr= get_field(&mem, table->field[5]);
348   server->sport= ptr ? ptr : blank;
349 
350   server->port= server->sport ? atoi(server->sport) : 0;
351 
352   ptr= get_field(&mem, table->field[6]);
353   server->socket= ptr && strlen(ptr) ? ptr : blank;
354   ptr= get_field(&mem, table->field[7]);
355   server->scheme= ptr ? ptr : blank;
356   ptr= get_field(&mem, table->field[8]);
357   server->owner= ptr ? ptr : blank;
358   DBUG_PRINT("info", ("server->server_name %s", server->server_name));
359   DBUG_PRINT("info", ("server->host %s", server->host));
360   DBUG_PRINT("info", ("server->db %s", server->db));
361   DBUG_PRINT("info", ("server->username %s", server->username));
362   DBUG_PRINT("info", ("server->password %s", server->password));
363   DBUG_PRINT("info", ("server->socket %s", server->socket));
364   if (my_hash_insert(&servers_cache, (uchar*) server))
365   {
366     DBUG_PRINT("info", ("had a problem inserting server %s at %lx",
367                         server->server_name, (long unsigned int) server));
368     // error handling needed here
369     DBUG_RETURN(TRUE);
370   }
371   DBUG_RETURN(FALSE);
372 }
373 
374 
375 /**
376    This function takes a server object that is has all members properly
377    prepared, ready to be inserted both into the mysql.servers table and
378    the servers cache.
379 
380    @param thd     thread pointer
381    @param server  pointer to prepared FOREIGN_SERVER struct
382 
383    @note THR_LOCK_servers must be write locked.
384 
385    @retval false OK
386    @retval true  Error
387 */
388 
insert_server(THD * thd,FOREIGN_SERVER * server)389 static bool insert_server(THD *thd, FOREIGN_SERVER *server)
390 {
391   DBUG_ENTER("insert_server");
392 
393   TABLE_LIST tables;
394   tables.init_one_table("mysql", 5, "servers", 7, "servers", TL_WRITE);
395 
396   /* need to open before acquiring THR_LOCK_plugin or it will deadlock */
397   TABLE *table= open_ltable(thd, &tables, TL_WRITE, MYSQL_LOCK_IGNORE_TIMEOUT);
398   if (!table)
399     DBUG_RETURN(true);
400 
401   /* insert the server into the table and the cache */
402   bool error= (insert_server_record(table, server) ||
403                insert_server_record_into_cache(server));
404 
405   close_mysql_tables(thd);
406 
407   DBUG_RETURN(error);
408 }
409 
410 
411 /**
412    This function takes a FOREIGN_SERVER pointer to an allocated (root mem)
413    and inserts it into the global servers cache
414 
415    @param server  pointer to prepared FOREIGN_SERVER struct
416 
417    @note THR_LOCK_servers must be write locked.
418 
419    @retval false OK
420    @retval true  Error
421 */
422 
insert_server_record_into_cache(FOREIGN_SERVER * server)423 static bool insert_server_record_into_cache(FOREIGN_SERVER *server)
424 {
425   DBUG_ENTER("insert_server_record_into_cache");
426   /*
427     We succeded in insertion of the server to the table, now insert
428     the server to the cache
429   */
430   DBUG_PRINT("info", ("inserting server %s at %lx, length %d",
431                         server->server_name, (long unsigned int) server,
432                         server->server_name_length));
433   if (my_hash_insert(&servers_cache, (uchar*) server))
434   {
435     DBUG_PRINT("info", ("had a problem inserting server %s at %lx",
436                         server->server_name, (long unsigned int) server));
437     my_error(ER_OUT_OF_RESOURCES, MYF(0));
438     DBUG_RETURN(true);
439   }
440 
441   DBUG_RETURN(false);
442 }
443 
444 
445 /*
446   SYNOPSIS
447     store_server_fields()
448       TABLE *table
449       FOREIGN_SERVER *server
450 
451   NOTES
452     This function takes an opened table object, and a pointer to an
453     allocated FOREIGN_SERVER struct, and then stores each member of
454     the FOREIGN_SERVER to the appropriate fields in the table, in
455     advance of insertion into the mysql.servers table
456 
457   RETURN VALUE
458     VOID
459 
460 */
461 
462 static void
store_server_fields(TABLE * table,FOREIGN_SERVER * server)463 store_server_fields(TABLE *table, FOREIGN_SERVER *server)
464 {
465 
466   table->use_all_columns();
467   /*
468     "server" has already been prepped by prepare_server_struct_for_<>
469     so, all we need to do is check if the value is set (> -1 for port)
470 
471     If this happens to be an update, only the server members that
472     have changed will be set. If an insert, then all will be set,
473     even if with empty strings
474   */
475   if (server->host)
476     table->field[1]->store(server->host,
477                            (uint) strlen(server->host), system_charset_info);
478   if (server->db)
479     table->field[2]->store(server->db,
480                            (uint) strlen(server->db), system_charset_info);
481   if (server->username)
482     table->field[3]->store(server->username,
483                            (uint) strlen(server->username), system_charset_info);
484   if (server->password)
485     table->field[4]->store(server->password,
486                            (uint) strlen(server->password), system_charset_info);
487   if (server->port > -1)
488     table->field[5]->store(server->port);
489 
490   if (server->socket)
491     table->field[6]->store(server->socket,
492                            (uint) strlen(server->socket), system_charset_info);
493   if (server->scheme)
494     table->field[7]->store(server->scheme,
495                            (uint) strlen(server->scheme), system_charset_info);
496   if (server->owner)
497     table->field[8]->store(server->owner,
498                            (uint) strlen(server->owner), system_charset_info);
499 }
500 
501 /**
502    This function takes the arguments of an open table object and a pointer
503    to an allocated FOREIGN_SERVER struct. It stores the server_name into
504    the first field of the table (the primary key, server_name column). With
505    this, index_read_idx is called, if the record is found, an error is set
506    to ER_FOREIGN_SERVER_EXISTS (the server with that server name exists in the
507    table), if not, then store_server_fields stores all fields of the
508    FOREIGN_SERVER to the table, then ha_write_row is inserted. If an error
509    is encountered in either index_read_idx or ha_write_row, then that error
510    is returned
511 
512    @param table   table for storing server record
513    @param server  pointer to prepared FOREIGN_SERVER struct
514 
515    @retval false OK
516    @retval true  Error
517 */
518 
insert_server_record(TABLE * table,FOREIGN_SERVER * server)519 static bool insert_server_record(TABLE *table, FOREIGN_SERVER *server)
520 {
521   int error;
522   DBUG_ENTER("insert_server_record");
523   tmp_disable_binlog(table->in_use);
524   table->use_all_columns();
525 
526   empty_record(table);
527 
528   /* set the field that's the PK to the value we're looking for */
529   table->field[0]->store(server->server_name,
530                          server->server_name_length,
531                          system_charset_info);
532 
533   /* read index until record is that specified in server_name */
534   error= table->file->ha_index_read_idx_map(table->record[0], 0,
535                                             (uchar *)table->field[0]->ptr,
536                                             HA_WHOLE_KEY,
537                                             HA_READ_KEY_EXACT);
538   if (error)
539   {
540     /* if not found, err */
541     if (error != HA_ERR_KEY_NOT_FOUND && error != HA_ERR_END_OF_FILE)
542       table->file->print_error(error, MYF(0));
543 
544     /* store each field to be inserted */
545     store_server_fields(table, server);
546 
547     DBUG_PRINT("info",("record for server '%s' not found!",
548                        server->server_name));
549     /* write/insert the new server */
550     if ((error=table->file->ha_write_row(table->record[0])))
551       table->file->print_error(error, MYF(0));
552   }
553   else
554   {
555     my_error(ER_FOREIGN_SERVER_EXISTS, MYF(0), server->server_name);
556     error= 1;
557   }
558 
559   reenable_binlog(table->in_use);
560   DBUG_RETURN(error != 0);
561 }
562 
563 
564 /**
565    This function takes as its arguments a THD object pointer and a pointer
566    to a LEX_SERVER_OPTIONS struct from the parser. The member 'server_name'
567    of this LEX_SERVER_OPTIONS struct contains the value of the server to be
568    deleted. The mysql.servers table is opened via open_ltable, a table object
569    returned, the servers cache mutex locked, then delete_server_record is
570    called with this table object and LEX_SERVER_OPTIONS server_name and
571    server_name_length passed, containing the name of the server to be
572    dropped/deleted, then delete_server_record_in_cache is called to delete
573    the server from the servers cache.
574 
575    @param thd              thread pointer
576    @param server_options   LEX_SERVER_OPTIONS from parser
577    @param if_exists        true if DROP IF EXISTS
578 
579    @retval false OK
580    @retval true  Error
581 */
582 
drop_server(THD * thd,LEX_SERVER_OPTIONS * server_options,bool if_exists)583 bool drop_server(THD *thd, LEX_SERVER_OPTIONS *server_options, bool if_exists)
584 {
585   DBUG_ENTER("drop_server");
586   DBUG_PRINT("info", ("server name server->server_name %s",
587                       server_options->server_name));
588 
589   TABLE_LIST tables;
590   tables.init_one_table("mysql", 5, "servers", 7, "servers", TL_WRITE);
591 
592   mysql_rwlock_wrlock(&THR_LOCK_servers);
593 
594   TABLE *table= open_ltable(thd, &tables, TL_WRITE, MYSQL_LOCK_IGNORE_TIMEOUT);
595   if (!table)
596   {
597     mysql_rwlock_unlock(&THR_LOCK_servers);
598     DBUG_RETURN(true);
599   }
600 
601   LEX_STRING name= { server_options->server_name,
602                      server_options->server_name_length };
603 
604   bool error= (delete_server_record_in_cache(server_options, if_exists) ||
605                delete_server_record(table, name.str, name.length, if_exists));
606 
607   /* close the servers table before we call closed_cached_connection_tables */
608   close_mysql_tables(thd);
609 
610   if (close_cached_connection_tables(thd, &name))
611   {
612     push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
613                         ER_UNKNOWN_ERROR, "Server connection in use");
614   }
615 
616   mysql_rwlock_unlock(&THR_LOCK_servers);
617   DBUG_RETURN(error || thd->killed);
618 }
619 
620 
621 /**
622    This function's  argument is a LEX_SERVER_OPTIONS struct pointer. This
623    function uses the "server_name" and "server_name_length" members of the
624    lex->server_options to search for the server in the servers_cache. Upon
625    returned the server (pointer to a FOREIGN_SERVER struct), it then deletes
626    that server from the servers_cache hash.
627 
628    @param server_options   LEX_SERVER_OPTIONS from parser
629    @param if_exists        true if DROP IF EXISTS
630 
631    @retval false OK
632    @retval true  Error
633 */
634 
delete_server_record_in_cache(LEX_SERVER_OPTIONS * server_options,bool if_exists)635 static bool delete_server_record_in_cache(LEX_SERVER_OPTIONS *server_options,
636                                           bool if_exists)
637 {
638   DBUG_ENTER("delete_server_record_in_cache");
639   DBUG_PRINT("info",("trying to obtain server name %s length %d",
640                      server_options->server_name,
641                      server_options->server_name_length));
642 
643   FOREIGN_SERVER *server=
644     (FOREIGN_SERVER *)my_hash_search(&servers_cache,
645                                      (uchar*) server_options->server_name,
646                                      server_options->server_name_length);
647   if (!server)
648   {
649     DBUG_PRINT("info", ("server_name %s length %d not found!",
650                         server_options->server_name,
651                         server_options->server_name_length));
652     if (!if_exists)
653       my_error(ER_FOREIGN_SERVER_DOESNT_EXIST, MYF(0),
654                server_options->server_name);
655     DBUG_RETURN(true);
656   }
657   /*
658     We succeded in deletion of the server to the table, now delete
659     the server from the cache
660   */
661   DBUG_PRINT("info",("deleting server %s length %d",
662                      server->server_name,
663                      server->server_name_length));
664 
665   my_hash_delete(&servers_cache, (uchar*) server);
666 
667   DBUG_RETURN(false);
668 }
669 
670 
671 /**
672    This function takes as arguments a THD object pointer, and two pointers,
673    one pointing to the existing FOREIGN_SERVER struct "existing" (which is
674    the current record as it is) and another pointer pointing to the
675    FOREIGN_SERVER struct with the members containing the modified/altered
676    values that need to be updated in both the mysql.servers table and the
677    servers_cache. It opens a table, passes the table and the altered
678    FOREIGN_SERVER pointer, which will be used to update the mysql.servers
679    table for the particular server via the call to update_server_record,
680    and in the servers_cache via update_server_record_in_cache.
681 
682    @param thd       thread pointer
683    @param existing  pointer to FOREIGN_SERVER struct for current server values
684    @param altered   pointer to FOREIGN_SERVER struct for modified server values
685 
686    @note THR_LOCK_servers must be write locked.
687 
688    @retval false OK
689    @retval true  Error
690 */
691 
update_server(THD * thd,FOREIGN_SERVER * existing,FOREIGN_SERVER * altered)692 bool update_server(THD *thd, FOREIGN_SERVER *existing, FOREIGN_SERVER *altered)
693 {
694   DBUG_ENTER("update_server");
695 
696   TABLE_LIST tables;
697   tables.init_one_table("mysql", 5, "servers", 7, "servers", TL_WRITE);
698 
699   TABLE *table= open_ltable(thd, &tables, TL_WRITE, MYSQL_LOCK_IGNORE_TIMEOUT);
700   if (!table)
701     DBUG_RETURN(true);
702 
703   bool error= (update_server_record(table, altered) ||
704                update_server_record_in_cache(existing, altered));
705 
706   /* Perform a reload so we don't have a 'hole' in our mem_root */
707   servers_load(thd, &tables);
708 
709   close_mysql_tables(thd);
710 
711   DBUG_RETURN(error);
712 }
713 
714 
715 /**
716    This function takes as an argument the FOREIGN_SERVER structi pointer
717    for the existing server and the FOREIGN_SERVER struct populated with only
718    the members which have been updated. It then "merges" the "altered" struct
719    members to the existing server, the existing server then represents an
720    updated server. Then, the existing record is deleted from the servers_cache
721    HASH, then the updated record inserted, in essence replacing the old
722    record.
723 
724    @param existing  pointer to FOREIGN_SERVER struct for current server values
725    @param altered   pointer to FOREIGN_SERVER struct for modified server values
726 
727    @note THR_LOCK_servers must be write locked.
728 
729    @retval false OK
730    @retval true  Error
731 */
732 
update_server_record_in_cache(FOREIGN_SERVER * existing,FOREIGN_SERVER * altered)733 bool update_server_record_in_cache(FOREIGN_SERVER *existing,
734                                    FOREIGN_SERVER *altered)
735 {
736   DBUG_ENTER("update_server_record_in_cache");
737 
738   /*
739     update the members that haven't been change in the altered server struct
740     with the values of the existing server struct
741   */
742   merge_server_struct(existing, altered);
743 
744   /* delete the existing server struct from the server cache */
745   my_hash_delete(&servers_cache, (uchar*)existing);
746 
747   /* Insert the altered server struct into the server cache */
748   if (my_hash_insert(&servers_cache, (uchar*)altered))
749   {
750     DBUG_PRINT("info", ("had a problem inserting server %s at %lx",
751                         altered->server_name, (long unsigned int) altered));
752     my_error(ER_OUT_OF_RESOURCES, MYF(0));
753     DBUG_RETURN(true);
754   }
755 
756   DBUG_RETURN(false);
757 }
758 
759 
760 /*
761 
762   SYNOPSIS
763     merge_server_struct()
764       FOREIGN_SERVER *from
765       FOREIGN_SERVER *to
766 
767   NOTES
768     This function takes as its arguments two pointers each to an allocated
769     FOREIGN_SERVER struct. The first FOREIGN_SERVER struct represents the struct
770     that we will obtain values from (hence the name "from"), the second
771     FOREIGN_SERVER struct represents which FOREIGN_SERVER struct we will be
772     "copying" any members that have a value to (hence the name "to")
773 
774   RETURN VALUE
775     VOID
776 
777 */
778 
merge_server_struct(FOREIGN_SERVER * from,FOREIGN_SERVER * to)779 void merge_server_struct(FOREIGN_SERVER *from, FOREIGN_SERVER *to)
780 {
781   DBUG_ENTER("merge_server_struct");
782   if (!to->host)
783     to->host= strdup_root(&mem, from->host);
784   if (!to->db)
785     to->db= strdup_root(&mem, from->db);
786   if (!to->username)
787     to->username= strdup_root(&mem, from->username);
788   if (!to->password)
789     to->password= strdup_root(&mem, from->password);
790   if (to->port == -1)
791     to->port= from->port;
792   if (!to->socket && from->socket)
793     to->socket= strdup_root(&mem, from->socket);
794   if (!to->scheme && from->scheme)
795     to->scheme= strdup_root(&mem, from->scheme);
796   if (!to->owner)
797     to->owner= strdup_root(&mem, from->owner);
798 
799   DBUG_VOID_RETURN;
800 }
801 
802 
803 /**
804    This function takes as its arguments an open TABLE pointer, and a pointer
805    to an allocated FOREIGN_SERVER structure representing an updated record
806    which needs to be inserted. The primary key, server_name is stored to field
807    0, then index_read_idx is called to read the index to that record, the
808    record then being ready to be updated, if found. If not found an error is
809    set and error message printed. If the record is found, store_record is
810    called, then store_server_fields stores each field from the the members of
811    the updated FOREIGN_SERVER struct.
812 
813    @param table   table for storing server record
814    @param server  pointer to prepared FOREIGN_SERVER struct
815 
816    @retval false OK
817    @retval true  Error
818 */
819 
820 
update_server_record(TABLE * table,FOREIGN_SERVER * server)821 static bool update_server_record(TABLE *table, FOREIGN_SERVER *server)
822 {
823   int error;
824   DBUG_ENTER("update_server_record");
825   tmp_disable_binlog(table->in_use);
826   table->use_all_columns();
827   /* set the field that's the PK to the value we're looking for */
828   table->field[0]->store(server->server_name,
829                          server->server_name_length,
830                          system_charset_info);
831 
832   error= table->file->ha_index_read_idx_map(table->record[0], 0,
833                                             (uchar *)table->field[0]->ptr,
834                                             ~(longlong)0,
835                                             HA_READ_KEY_EXACT);
836   if (error)
837   {
838     if (error != HA_ERR_KEY_NOT_FOUND && error != HA_ERR_END_OF_FILE)
839       table->file->print_error(error, MYF(0));
840     DBUG_PRINT("info",("server not found!"));
841     my_error(ER_FOREIGN_SERVER_DOESNT_EXIST, MYF(0), server->server_name);
842   }
843   else
844   {
845     /* ok, so we can update since the record exists in the table */
846     store_record(table,record[1]);
847     store_server_fields(table, server);
848     if ((error=table->file->ha_update_row(table->record[1],
849                                           table->record[0])) &&
850         error != HA_ERR_RECORD_IS_THE_SAME)
851     {
852       table->file->print_error(error, MYF(0));
853       DBUG_PRINT("info",("problems with ha_update_row %d", error));
854     }
855     else
856       error= 0;
857   }
858 
859   reenable_binlog(table->in_use);
860   DBUG_RETURN(error != 0);
861 }
862 
863 
864 /**
865    @param table               table where to delete server record
866    @param server_name         name of server info to delete
867    @param server_name_length  length of name
868    @param if_exists           true if DROP IF EXISTS
869 
870    @retval false OK
871    @retval true  Error
872 */
873 
delete_server_record(TABLE * table,char * server_name,size_t server_name_length,bool if_exists)874 static bool delete_server_record(TABLE *table, char *server_name,
875                                  size_t server_name_length, bool if_exists)
876 {
877   int error;
878   DBUG_ENTER("delete_server_record");
879   tmp_disable_binlog(table->in_use);
880   table->use_all_columns();
881 
882   /* set the field that's the PK to the value we're looking for */
883   table->field[0]->store(server_name, server_name_length, system_charset_info);
884 
885   error= table->file->ha_index_read_idx_map(table->record[0], 0,
886                                             (uchar *)table->field[0]->ptr,
887                                             HA_WHOLE_KEY,
888                                             HA_READ_KEY_EXACT);
889   if (error)
890   {
891     if (error != HA_ERR_KEY_NOT_FOUND && error != HA_ERR_END_OF_FILE)
892       table->file->print_error(error, MYF(0));
893     DBUG_PRINT("info",("server not found!"));
894     if (!if_exists)
895       my_error(ER_FOREIGN_SERVER_DOESNT_EXIST, MYF(0), server_name);
896   }
897   else
898   {
899     if ((error= table->file->ha_delete_row(table->record[0])))
900       table->file->print_error(error, MYF(0));
901   }
902 
903   reenable_binlog(table->in_use);
904   DBUG_RETURN(error != 0);
905 }
906 
907 
908 /**
909    @param thd             thread pointer
910    @param server_options  LEX_SERVER_OPTIONS from parser
911 
912    @retval false OK
913    @retval true  Error
914 */
915 
create_server(THD * thd,LEX_SERVER_OPTIONS * server_options)916 bool create_server(THD *thd, LEX_SERVER_OPTIONS *server_options)
917 {
918   bool error= true;
919   FOREIGN_SERVER *server;
920 
921   DBUG_ENTER("create_server");
922   DBUG_PRINT("info", ("server_options->server_name %s",
923                       server_options->server_name));
924 
925   mysql_rwlock_wrlock(&THR_LOCK_servers);
926 
927   /* hit the memory first */
928   if (my_hash_search(&servers_cache, (uchar*) server_options->server_name,
929                      server_options->server_name_length))
930   {
931     my_error(ER_FOREIGN_SERVER_EXISTS, MYF(0), server_options->server_name);
932     goto end;
933   }
934 
935   server= prepare_server_struct_for_insert(server_options);
936   if (!server)
937   {
938     my_error(ER_OUT_OF_RESOURCES, MYF(0));
939     goto end;
940   }
941 
942   error= insert_server(thd, server);
943 
944 end:
945   mysql_rwlock_unlock(&THR_LOCK_servers);
946   DBUG_RETURN(error || thd->killed);
947 }
948 
949 
950 /**
951    @param thd             thread pointer
952    @param server_options  LEX_SERVER_OPTIONS from parser
953 
954   @retval false OK
955   @retval true  Error
956 */
957 
alter_server(THD * thd,LEX_SERVER_OPTIONS * server_options)958 bool alter_server(THD *thd, LEX_SERVER_OPTIONS *server_options)
959 {
960   bool error= true;
961   FOREIGN_SERVER *altered, *existing;
962   LEX_STRING name= { server_options->server_name,
963                      server_options->server_name_length };
964 
965   DBUG_ENTER("alter_server");
966   DBUG_PRINT("info", ("server_options->server_name %s",
967                       server_options->server_name));
968 
969   mysql_rwlock_wrlock(&THR_LOCK_servers);
970 
971   existing= (FOREIGN_SERVER *) my_hash_search(&servers_cache,
972                                               (uchar*) name.str,
973                                               name.length);
974   if (!existing)
975   {
976     my_error(ER_FOREIGN_SERVER_DOESNT_EXIST, MYF(0), name.str);
977     goto end;
978   }
979 
980   altered= (FOREIGN_SERVER *)alloc_root(&mem, sizeof(FOREIGN_SERVER));
981 
982   prepare_server_struct_for_update(server_options, existing, altered);
983 
984   error= update_server(thd, existing, altered);
985 
986   if (close_cached_connection_tables(thd, &name))
987   {
988     push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
989                         ER_UNKNOWN_ERROR, "Server connection in use");
990   }
991 
992 end:
993   mysql_rwlock_unlock(&THR_LOCK_servers);
994   DBUG_RETURN(error || thd->killed);
995 }
996 
997 
998 /*
999 
1000   SYNOPSIS
1001     prepare_server_struct_for_insert()
1002       LEX_SERVER_OPTIONS *server_options
1003 
1004   NOTES
1005     As FOREIGN_SERVER members are allocated on mem_root, we do not need to
1006     free them in case of error.
1007 
1008   RETURN VALUE
1009     On success filled FOREIGN_SERVER, or NULL in case out of memory.
1010 
1011 */
1012 
1013 static FOREIGN_SERVER *
prepare_server_struct_for_insert(LEX_SERVER_OPTIONS * server_options)1014 prepare_server_struct_for_insert(LEX_SERVER_OPTIONS *server_options)
1015 {
1016   char *unset_ptr= (char*)"";
1017   FOREIGN_SERVER *server;
1018   DBUG_ENTER("prepare_server_struct");
1019 
1020   if (!(server= (FOREIGN_SERVER *)alloc_root(&mem, sizeof(FOREIGN_SERVER))))
1021     DBUG_RETURN(NULL); /* purecov: inspected */
1022 
1023   /* these two MUST be set */
1024   if (!(server->server_name= strdup_root(&mem, server_options->server_name)))
1025     DBUG_RETURN(NULL); /* purecov: inspected */
1026   server->server_name_length= server_options->server_name_length;
1027 
1028   if (!(server->host= server_options->host ?
1029           strdup_root(&mem, server_options->host) : unset_ptr))
1030     DBUG_RETURN(NULL); /* purecov: inspected */
1031 
1032   if (!(server->db= server_options->db ?
1033           strdup_root(&mem, server_options->db) : unset_ptr))
1034     DBUG_RETURN(NULL); /* purecov: inspected */
1035 
1036   if (!(server->username= server_options->username ?
1037           strdup_root(&mem, server_options->username) : unset_ptr))
1038     DBUG_RETURN(NULL); /* purecov: inspected */
1039 
1040   if (!(server->password= server_options->password ?
1041           strdup_root(&mem, server_options->password) : unset_ptr))
1042     DBUG_RETURN(NULL); /* purecov: inspected */
1043 
1044   /* set to 0 if not specified */
1045   server->port= server_options->port > -1 ?
1046     server_options->port : 0;
1047 
1048   if (!(server->socket= server_options->socket ?
1049           strdup_root(&mem, server_options->socket) : unset_ptr))
1050     DBUG_RETURN(NULL); /* purecov: inspected */
1051 
1052   if (!(server->scheme= server_options->scheme ?
1053           strdup_root(&mem, server_options->scheme) : unset_ptr))
1054     DBUG_RETURN(NULL); /* purecov: inspected */
1055 
1056   if (!(server->owner= server_options->owner ?
1057           strdup_root(&mem, server_options->owner) : unset_ptr))
1058     DBUG_RETURN(NULL); /* purecov: inspected */
1059 
1060   DBUG_RETURN(server);
1061 }
1062 
1063 /*
1064 
1065   SYNOPSIS
1066     prepare_server_struct_for_update()
1067       LEX_SERVER_OPTIONS *server_options
1068 
1069   NOTES
1070 
1071   RETURN VALUE
1072     0 - no error
1073 
1074 */
1075 
1076 static void
prepare_server_struct_for_update(LEX_SERVER_OPTIONS * server_options,FOREIGN_SERVER * existing,FOREIGN_SERVER * altered)1077 prepare_server_struct_for_update(LEX_SERVER_OPTIONS *server_options,
1078                                  FOREIGN_SERVER *existing,
1079                                  FOREIGN_SERVER *altered)
1080 {
1081   DBUG_ENTER("prepare_server_struct_for_update");
1082 
1083   altered->server_name= strdup_root(&mem, server_options->server_name);
1084   altered->server_name_length= server_options->server_name_length;
1085   DBUG_PRINT("info", ("existing name %s altered name %s",
1086                       existing->server_name, altered->server_name));
1087 
1088   /*
1089     The logic here is this: is this value set AND is it different
1090     than the existing value?
1091   */
1092   altered->host=
1093     (server_options->host && (strcmp(server_options->host, existing->host))) ?
1094      strdup_root(&mem, server_options->host) : 0;
1095 
1096   altered->db=
1097       (server_options->db && (strcmp(server_options->db, existing->db))) ?
1098         strdup_root(&mem, server_options->db) : 0;
1099 
1100   altered->username=
1101       (server_options->username &&
1102       (strcmp(server_options->username, existing->username))) ?
1103         strdup_root(&mem, server_options->username) : 0;
1104 
1105   altered->password=
1106       (server_options->password &&
1107       (strcmp(server_options->password, existing->password))) ?
1108         strdup_root(&mem, server_options->password) : 0;
1109 
1110   /*
1111     port is initialised to -1, so if unset, it will be -1
1112   */
1113   altered->port= (server_options->port > -1 &&
1114                  server_options->port != existing->port) ?
1115     server_options->port : -1;
1116 
1117   altered->socket=
1118     (server_options->socket &&
1119     (strcmp(server_options->socket, existing->socket))) ?
1120       strdup_root(&mem, server_options->socket) : 0;
1121 
1122   altered->scheme=
1123     (server_options->scheme &&
1124     (strcmp(server_options->scheme, existing->scheme))) ?
1125       strdup_root(&mem, server_options->scheme) : 0;
1126 
1127   altered->owner=
1128     (server_options->owner &&
1129     (strcmp(server_options->owner, existing->owner))) ?
1130       strdup_root(&mem, server_options->owner) : 0;
1131 
1132   DBUG_VOID_RETURN;
1133 }
1134 
1135 /*
1136 
1137   SYNOPSIS
1138     servers_free()
1139       bool end
1140 
1141   NOTES
1142 
1143   RETURN VALUE
1144     void
1145 
1146 */
1147 
servers_free(bool end)1148 void servers_free(bool end)
1149 {
1150   DBUG_ENTER("servers_free");
1151   if (!my_hash_inited(&servers_cache))
1152     DBUG_VOID_RETURN;
1153   if (!end)
1154   {
1155     free_root(&mem, MYF(MY_MARK_BLOCKS_FREE));
1156 	my_hash_reset(&servers_cache);
1157     DBUG_VOID_RETURN;
1158   }
1159   mysql_rwlock_destroy(&THR_LOCK_servers);
1160   free_root(&mem,MYF(0));
1161   my_hash_free(&servers_cache);
1162   DBUG_VOID_RETURN;
1163 }
1164 
1165 
1166 /*
1167   SYNOPSIS
1168 
1169   clone_server(MEM_ROOT *mem_root, FOREIGN_SERVER *orig, FOREIGN_SERVER *buff)
1170 
1171   Create a clone of FOREIGN_SERVER. If the supplied mem_root is of
1172   thd->mem_root then the copy is automatically disposed at end of statement.
1173 
1174   NOTES
1175 
1176   ARGS
1177    MEM_ROOT pointer (strings are copied into this mem root)
1178    FOREIGN_SERVER pointer (made a copy of)
1179    FOREIGN_SERVER buffer (if not-NULL, this pointer is returned)
1180 
1181   RETURN VALUE
1182    FOREIGN_SEVER pointer (copy of one supplied FOREIGN_SERVER)
1183 */
1184 
clone_server(MEM_ROOT * mem,const FOREIGN_SERVER * server,FOREIGN_SERVER * buffer)1185 static FOREIGN_SERVER *clone_server(MEM_ROOT *mem, const FOREIGN_SERVER *server,
1186                                     FOREIGN_SERVER *buffer)
1187 {
1188   DBUG_ENTER("sql_server.cc:clone_server");
1189 
1190   if (!buffer)
1191     buffer= (FOREIGN_SERVER *) alloc_root(mem, sizeof(FOREIGN_SERVER));
1192 
1193   buffer->server_name= strmake_root(mem, server->server_name,
1194                                     server->server_name_length);
1195   buffer->port= server->port;
1196   buffer->server_name_length= server->server_name_length;
1197 
1198   /* TODO: We need to examine which of these can really be NULL */
1199   buffer->db= server->db ? strdup_root(mem, server->db) : NULL;
1200   buffer->scheme= server->scheme ? strdup_root(mem, server->scheme) : NULL;
1201   buffer->username= server->username? strdup_root(mem, server->username): NULL;
1202   buffer->password= server->password? strdup_root(mem, server->password): NULL;
1203   buffer->socket= server->socket ? strdup_root(mem, server->socket) : NULL;
1204   buffer->owner= server->owner ? strdup_root(mem, server->owner) : NULL;
1205   buffer->host= server->host ? strdup_root(mem, server->host) : NULL;
1206 
1207  DBUG_RETURN(buffer);
1208 }
1209 
1210 
1211 /*
1212 
1213   SYNOPSIS
1214     get_server_by_name()
1215       const char *server_name
1216 
1217   NOTES
1218 
1219   RETURN VALUE
1220    FOREIGN_SERVER *
1221 
1222 */
1223 
get_server_by_name(MEM_ROOT * mem,const char * server_name,FOREIGN_SERVER * buff)1224 FOREIGN_SERVER *get_server_by_name(MEM_ROOT *mem, const char *server_name,
1225                                    FOREIGN_SERVER *buff)
1226 {
1227   size_t server_name_length;
1228   FOREIGN_SERVER *server;
1229   DBUG_ENTER("get_server_by_name");
1230   DBUG_PRINT("info", ("server_name %s", server_name));
1231 
1232   server_name_length= strlen(server_name);
1233 
1234   if (! server_name || !strlen(server_name))
1235   {
1236     DBUG_PRINT("info", ("server_name not defined!"));
1237     DBUG_RETURN((FOREIGN_SERVER *)NULL);
1238   }
1239 
1240   DBUG_PRINT("info", ("locking servers_cache"));
1241   mysql_rwlock_rdlock(&THR_LOCK_servers);
1242   if (!(server= (FOREIGN_SERVER *) my_hash_search(&servers_cache,
1243                                                   (uchar*) server_name,
1244                                                   server_name_length)))
1245   {
1246     DBUG_PRINT("info", ("server_name %s length %u not found!",
1247                         server_name, (unsigned) server_name_length));
1248     server= (FOREIGN_SERVER *) NULL;
1249   }
1250   /* otherwise, make copy of server */
1251   else
1252     server= clone_server(mem, server, buff);
1253 
1254   DBUG_PRINT("info", ("unlocking servers_cache"));
1255   mysql_rwlock_unlock(&THR_LOCK_servers);
1256   DBUG_RETURN(server);
1257 
1258 }
1259