1 /* Copyright (c) 2000, 2021, Oracle and/or its affiliates.
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 /* This implements 'user defined functions' */
24 
25 /*
26    Known bugs:
27 
28    Memory for functions is never freed!
29    Shared libraries are not closed before mysqld exits;
30      - This is because we can't be sure if some threads are using
31        a function.
32 
33    The bugs only affect applications that create and free a lot of
34    dynamic functions, so this shouldn't be a real problem.
35 */
36 
37 #include "sql_base.h"                           // close_mysql_tables
38 #include "sql_parse.h"                        // check_string_char_length
39 #include "sql_table.h"                        // write_bin_log
40 #include "records.h"          // init_read_record, end_read_record
41 #include "lock.h"                               // MYSQL_LOCK_IGNORE_TIMEOUT
42 #include "log.h"
43 #include "sql_plugin.h"                         // check_valid_path
44 
45 #ifdef HAVE_DLFCN_H
46 #include <dlfcn.h>
47 #endif
48 
49 #ifdef HAVE_DLOPEN
50 #include <stdarg.h>
51 #include <hash.h>
52 
53 static bool initialized = 0;
54 static MEM_ROOT mem;
55 static HASH udf_hash;
56 static mysql_rwlock_t THR_LOCK_udf;
57 
58 
59 static udf_func *add_udf(LEX_STRING *name, Item_result ret,
60                          char *dl, Item_udftype typ);
61 static void del_udf(udf_func *udf);
62 static void *find_udf_dl(const char *dl);
63 
init_syms(udf_func * tmp,char * nm)64 static char *init_syms(udf_func *tmp, char *nm)
65 {
66   char *end;
67 
68   if (!((tmp->func= (Udf_func_any) dlsym(tmp->dlhandle, tmp->name.str))))
69     return tmp->name.str;
70 
71   end=my_stpcpy(nm,tmp->name.str);
72 
73   if (tmp->type == UDFTYPE_AGGREGATE)
74   {
75     (void)my_stpcpy(end, "_clear");
76     if (!((tmp->func_clear= (Udf_func_clear) dlsym(tmp->dlhandle, nm))))
77       return nm;
78     (void)my_stpcpy(end, "_add");
79     if (!((tmp->func_add= (Udf_func_add) dlsym(tmp->dlhandle, nm))))
80       return nm;
81   }
82 
83   (void) my_stpcpy(end,"_deinit");
84   tmp->func_deinit= (Udf_func_deinit) dlsym(tmp->dlhandle, nm);
85 
86   (void) my_stpcpy(end,"_init");
87   tmp->func_init= (Udf_func_init) dlsym(tmp->dlhandle, nm);
88 
89   /*
90     to prefent loading "udf" from, e.g. libc.so
91     let's ensure that at least one auxiliary symbol is defined
92   */
93   if (!tmp->func_init && !tmp->func_deinit && tmp->type != UDFTYPE_AGGREGATE)
94   {
95     if (!opt_allow_suspicious_udfs)
96       return nm;
97     sql_print_warning(ER(ER_CANT_FIND_DL_ENTRY), nm);
98   }
99   return 0;
100 }
101 
102 
get_hash_key(const uchar * buff,size_t * length,my_bool not_used MY_ATTRIBUTE ((unused)))103 extern "C" uchar* get_hash_key(const uchar *buff, size_t *length,
104 			      my_bool not_used MY_ATTRIBUTE((unused)))
105 {
106   udf_func *udf=(udf_func*) buff;
107   *length=(uint) udf->name.length;
108   return (uchar*) udf->name.str;
109 }
110 
111 static PSI_memory_key key_memory_udf_mem;
112 
113 #ifdef HAVE_PSI_INTERFACE
114 static PSI_rwlock_key key_rwlock_THR_LOCK_udf;
115 
116 static PSI_rwlock_info all_udf_rwlocks[]=
117 {
118   { &key_rwlock_THR_LOCK_udf, "THR_LOCK_udf", PSI_FLAG_GLOBAL}
119 };
120 
121 static PSI_memory_info all_udf_memory[]=
122 {
123   { &key_memory_udf_mem, "udf_mem", PSI_FLAG_GLOBAL}
124 };
125 
init_udf_psi_keys(void)126 static void init_udf_psi_keys(void)
127 {
128   const char* category= "sql";
129   int count;
130 
131   count= array_elements(all_udf_rwlocks);
132   mysql_rwlock_register(category, all_udf_rwlocks, count);
133 
134   count= array_elements(all_udf_memory);
135   mysql_memory_register(category, all_udf_memory, count);
136 }
137 #endif
138 
139 /*
140   Read all predeclared functions from mysql.func and accept all that
141   can be used.
142 */
143 
udf_init()144 void udf_init()
145 {
146   udf_func *tmp;
147   TABLE_LIST tables;
148   READ_RECORD read_record_info;
149   TABLE *table;
150   int error;
151   DBUG_ENTER("ufd_init");
152   char db[]= "mysql"; /* A subject to casednstr, can't be constant */
153 
154   if (initialized)
155     DBUG_VOID_RETURN;
156 
157 #ifdef HAVE_PSI_INTERFACE
158   init_udf_psi_keys();
159 #endif
160 
161   mysql_rwlock_init(key_rwlock_THR_LOCK_udf, &THR_LOCK_udf);
162 
163   init_sql_alloc(key_memory_udf_mem, &mem, UDF_ALLOC_BLOCK_SIZE, 0);
164   THD *new_thd = new THD;
165   if (!new_thd ||
166       my_hash_init(&udf_hash,system_charset_info,32,0,0,get_hash_key, NULL, 0,
167                    key_memory_udf_mem))
168   {
169     sql_print_error("Can't allocate memory for udf structures");
170     my_hash_free(&udf_hash);
171     free_root(&mem,MYF(0));
172     delete new_thd;
173     DBUG_VOID_RETURN;
174   }
175   initialized = 1;
176   new_thd->thread_stack= (char*) &new_thd;
177   new_thd->store_globals();
178   {
179     LEX_CSTRING db_lex_cstr= { STRING_WITH_LEN(db) };
180     new_thd->set_db(db_lex_cstr);
181   }
182 
183   tables.init_one_table(db, sizeof(db)-1, "func", 4, "func", TL_READ);
184 
185   if (open_and_lock_tables(new_thd, &tables, MYSQL_LOCK_IGNORE_TIMEOUT))
186   {
187     DBUG_PRINT("error",("Can't open udf table"));
188     sql_print_error("Can't open the mysql.func table. Please "
189                     "run mysql_upgrade to create it.");
190     goto end;
191   }
192 
193   table= tables.table;
194   if (init_read_record(&read_record_info, new_thd, table, NULL, 1, 1, FALSE))
195     goto end;
196   table->use_all_columns();
197   while (!(error= read_record_info.read_record(&read_record_info)))
198   {
199     DBUG_PRINT("info",("init udf record"));
200     LEX_STRING name;
201     name.str=get_field(&mem, table->field[0]);
202 
203     // Check the name.str is NULL or not.
204     if (name.str == NULL)
205     {
206       sql_print_error("Invalid row in mysql.func table for column 'name'");
207       continue;
208     }
209 
210     name.length = strlen(name.str);
211     char *dl_name= get_field(&mem, table->field[2]);
212     if (dl_name == NULL)
213     {
214       sql_print_error("Invalid row in mysql.func table for function '%.64s'",
215                       name.str);
216       continue;
217     }
218     bool new_dl=0;
219     Item_udftype udftype=UDFTYPE_FUNCTION;
220     if (table->s->fields >= 4)			// New func table
221       udftype=(Item_udftype) table->field[3]->val_int();
222 
223     /*
224       Ensure that the .dll doesn't have a path
225       This is done to ensure that only approved dll from the system
226       directories are used (to make this even remotely secure).
227 
228       On windows we must check both FN_LIBCHAR and '/'.
229     */
230 
231     LEX_CSTRING name_cstr= {name.str, name.length};
232     if (check_valid_path(dl_name, strlen(dl_name)) ||
233         check_string_char_length(name_cstr, "", NAME_CHAR_LEN,
234                                  system_charset_info, 1))
235     {
236       sql_print_error("Invalid row in mysql.func table for function '%.64s'",
237                       name.str);
238       continue;
239     }
240 
241     if (!(tmp= add_udf(&name,(Item_result) table->field[1]->val_int(),
242                        dl_name, udftype)))
243     {
244       sql_print_error("Can't alloc memory for udf function: '%.64s'", name.str);
245       continue;
246     }
247 
248     void *dl = find_udf_dl(tmp->dl);
249     if (dl == NULL)
250     {
251       char dlpath[FN_REFLEN];
252       strxnmov(dlpath, sizeof(dlpath) - 1, opt_plugin_dir, "/", tmp->dl,
253                NullS);
254       (void) unpack_filename(dlpath, dlpath);
255       if (!(dl= dlopen(dlpath, RTLD_NOW)))
256       {
257 	const char *errmsg;
258 	int error_number= dlopen_errno;
259 	DLERROR_GENERATE(errmsg, error_number);
260 
261 	/* Print warning to log */
262         sql_print_error(ER(ER_CANT_OPEN_LIBRARY), tmp->dl, error_number, errmsg);
263 	/* Keep the udf in the hash so that we can remove it later */
264 	continue;
265       }
266       new_dl=1;
267     }
268     tmp->dlhandle = dl;
269     {
270       char buf[NAME_LEN+16], *missing;
271       if ((missing= init_syms(tmp, buf)))
272       {
273         sql_print_error(ER(ER_CANT_FIND_DL_ENTRY), missing);
274         del_udf(tmp);
275         if (new_dl)
276           dlclose(dl);
277       }
278     }
279   }
280   if (error > 0)
281     sql_print_error("Got unknown error: %d", my_errno());
282   end_read_record(&read_record_info);
283   table->m_needs_reopen= TRUE;                  // Force close to free memory
284 
285 end:
286   close_mysql_tables(new_thd);
287   delete new_thd;
288   DBUG_VOID_RETURN;
289 }
290 
291 
udf_free()292 void udf_free()
293 {
294   /* close all shared libraries */
295   DBUG_ENTER("udf_free");
296   for (uint idx=0 ; idx < udf_hash.records ; idx++)
297   {
298     udf_func *udf=(udf_func*) my_hash_element(&udf_hash,idx);
299     if (udf->dlhandle)				// Not closed before
300     {
301       /* Mark all versions using the same handler as closed */
302       for (uint j=idx+1 ;  j < udf_hash.records ; j++)
303       {
304 	udf_func *tmp=(udf_func*) my_hash_element(&udf_hash,j);
305 	if (udf->dlhandle == tmp->dlhandle)
306 	  tmp->dlhandle=0;			// Already closed
307       }
308       dlclose(udf->dlhandle);
309     }
310   }
311   my_hash_free(&udf_hash);
312   free_root(&mem,MYF(0));
313   if (initialized)
314   {
315     initialized= 0;
316     mysql_rwlock_destroy(&THR_LOCK_udf);
317   }
318   DBUG_VOID_RETURN;
319 }
320 
321 
del_udf(udf_func * udf)322 static void del_udf(udf_func *udf)
323 {
324   DBUG_ENTER("del_udf");
325   if (!--udf->usage_count)
326   {
327     my_hash_delete(&udf_hash,(uchar*) udf);
328     using_udf_functions=udf_hash.records != 0;
329   }
330   else
331   {
332     /*
333       The functions is in use ; Rename the functions instead of removing it.
334       The functions will be automaticly removed when the least threads
335       doesn't use it anymore
336     */
337     char *name= udf->name.str;
338     size_t name_length=udf->name.length;
339     udf->name.str=(char*) "*";
340     udf->name.length=1;
341     my_hash_update(&udf_hash,(uchar*) udf,(uchar*) name,name_length);
342   }
343   DBUG_VOID_RETURN;
344 }
345 
346 
free_udf(udf_func * udf)347 void free_udf(udf_func *udf)
348 {
349   DBUG_ENTER("free_udf");
350 
351   if (!initialized)
352     DBUG_VOID_RETURN;
353 
354   mysql_rwlock_wrlock(&THR_LOCK_udf);
355   if (!--udf->usage_count)
356   {
357     /*
358       We come here when someone has deleted the udf function
359       while another thread still was using the udf
360     */
361     my_hash_delete(&udf_hash,(uchar*) udf);
362     using_udf_functions=udf_hash.records != 0;
363     if (!find_udf_dl(udf->dl))
364       dlclose(udf->dlhandle);
365   }
366   mysql_rwlock_unlock(&THR_LOCK_udf);
367   DBUG_VOID_RETURN;
368 }
369 
370 
371 /* This is only called if using_udf_functions != 0 */
372 
find_udf(const char * name,size_t length,bool mark_used)373 udf_func *find_udf(const char *name, size_t length,bool mark_used)
374 {
375   udf_func *udf=0;
376   DBUG_ENTER("find_udf");
377 
378   if (!initialized)
379     DBUG_RETURN(NULL);
380 
381   /* TODO: This should be changed to reader locks someday! */
382   if (mark_used)
383     mysql_rwlock_wrlock(&THR_LOCK_udf);  /* Called during fix_fields */
384   else
385     mysql_rwlock_rdlock(&THR_LOCK_udf);  /* Called during parsing */
386 
387   if ((udf=(udf_func*) my_hash_search(&udf_hash,(uchar*) name,
388                                       length ? length : strlen(name))))
389   {
390     if (!udf->dlhandle)
391       udf=0;					// Could not be opened
392     else if (mark_used)
393       udf->usage_count++;
394   }
395   mysql_rwlock_unlock(&THR_LOCK_udf);
396   DBUG_RETURN(udf);
397 }
398 
399 
find_udf_dl(const char * dl)400 static void *find_udf_dl(const char *dl)
401 {
402   DBUG_ENTER("find_udf_dl");
403 
404   /*
405     Because only the function name is hashed, we have to search trough
406     all rows to find the dl.
407   */
408   for (uint idx=0 ; idx < udf_hash.records ; idx++)
409   {
410     udf_func *udf=(udf_func*) my_hash_element(&udf_hash,idx);
411     if (!strcmp(dl, udf->dl) && udf->dlhandle != NULL)
412       DBUG_RETURN(udf->dlhandle);
413   }
414   DBUG_RETURN(0);
415 }
416 
417 
418 /* Assume that name && dl is already allocated */
419 
add_udf(LEX_STRING * name,Item_result ret,char * dl,Item_udftype type)420 static udf_func *add_udf(LEX_STRING *name, Item_result ret, char *dl,
421 			 Item_udftype type)
422 {
423   if (!name || !dl || !(uint) type || (uint) type > (uint) UDFTYPE_AGGREGATE)
424     return 0;
425   udf_func *tmp= (udf_func*) alloc_root(&mem, sizeof(udf_func));
426   if (!tmp)
427     return 0;
428   memset(tmp, 0, sizeof(*tmp));
429   tmp->name = *name; //dup !!
430   tmp->dl = dl;
431   tmp->returns = ret;
432   tmp->type = type;
433   tmp->usage_count=1;
434   if (my_hash_insert(&udf_hash,(uchar*)  tmp))
435     return 0;
436   using_udf_functions=1;
437   return tmp;
438 }
439 
440 
441 /**
442   Create a user defined function.
443 
444   @note Like implementations of other DDL/DML in MySQL, this function
445   relies on the caller to close the thread tables. This is done in the
446   end of dispatch_command().
447 */
448 
mysql_create_function(THD * thd,udf_func * udf)449 int mysql_create_function(THD *thd,udf_func *udf)
450 {
451   int error;
452   void *dl=0;
453   bool new_dl=0;
454   TABLE *table;
455   TABLE_LIST tables;
456   udf_func *u_d;
457   bool save_binlog_row_based;
458   DBUG_ENTER("mysql_create_function");
459 
460   if (!initialized)
461   {
462     if (opt_noacl)
463       my_error(ER_CANT_INITIALIZE_UDF, MYF(0),
464                udf->name.str,
465                "UDFs are unavailable with the --skip-grant-tables option");
466     else
467       my_message(ER_OUT_OF_RESOURCES, ER(ER_OUT_OF_RESOURCES), MYF(0));
468     DBUG_RETURN(1);
469   }
470 
471   /*
472     Ensure that the .dll doesn't have a path
473     This is done to ensure that only approved dll from the system
474     directories are used (to make this even remotely secure).
475   */
476   if (check_valid_path(udf->dl, strlen(udf->dl)))
477   {
478     my_message(ER_UDF_NO_PATHS, ER(ER_UDF_NO_PATHS), MYF(0));
479     DBUG_RETURN(1);
480   }
481   LEX_CSTRING udf_name_cstr= {udf->name.str, udf->name.length};
482   if (check_string_char_length(udf_name_cstr, "", NAME_CHAR_LEN,
483                                system_charset_info, 1))
484   {
485     my_error(ER_TOO_LONG_IDENT, MYF(0), udf->name.str);
486     DBUG_RETURN(1);
487   }
488 
489   tables.init_one_table("mysql", 5, "func", 4, "func", TL_WRITE);
490   if (!(table= open_ltable(thd, &tables, TL_WRITE, MYSQL_LOCK_IGNORE_TIMEOUT)))
491     DBUG_RETURN(1);
492 
493   /*
494     Turn off row binlogging of this statement and use statement-based
495     so that all supporting tables are updated for CREATE FUNCTION command.
496   */
497   if ((save_binlog_row_based= thd->is_current_stmt_binlog_format_row()))
498     thd->clear_current_stmt_binlog_format_row();
499 
500   mysql_rwlock_wrlock(&THR_LOCK_udf);
501   if ((my_hash_search(&udf_hash,(uchar*) udf->name.str, udf->name.length)))
502   {
503     my_error(ER_UDF_EXISTS, MYF(0), udf->name.str);
504     goto err;
505   }
506   if (!(dl = find_udf_dl(udf->dl)))
507   {
508     char dlpath[FN_REFLEN];
509     strxnmov(dlpath, sizeof(dlpath) - 1, opt_plugin_dir, "/", udf->dl, NullS);
510     (void) unpack_filename(dlpath, dlpath);
511 
512     if (!(dl = dlopen(dlpath, RTLD_NOW)))
513     {
514       const char *errmsg;
515       int error_number= dlopen_errno;
516       DLERROR_GENERATE(errmsg, error_number);
517 
518       DBUG_PRINT("error",("dlopen of %s failed, error: %d (%s)",
519                           udf->dl, error_number, errmsg));
520       my_error(ER_CANT_OPEN_LIBRARY, MYF(0),
521                udf->dl, error_number, errmsg);
522       goto err;
523     }
524     new_dl=1;
525   }
526   udf->dlhandle=dl;
527   {
528     char buf[NAME_LEN+16], *missing;
529     if ((missing= init_syms(udf, buf)))
530     {
531       my_error(ER_CANT_FIND_DL_ENTRY, MYF(0), missing);
532       goto err;
533     }
534   }
535   udf->name.str=strdup_root(&mem,udf->name.str);
536   udf->dl=strdup_root(&mem,udf->dl);
537   if (!(u_d=add_udf(&udf->name,udf->returns,udf->dl,udf->type)))
538     goto err;
539   u_d->dlhandle = dl;
540   u_d->func=udf->func;
541   u_d->func_init=udf->func_init;
542   u_d->func_deinit=udf->func_deinit;
543   u_d->func_clear=udf->func_clear;
544   u_d->func_add=udf->func_add;
545 
546   /* create entry in mysql.func table */
547 
548   table->use_all_columns();
549   restore_record(table, s->default_values);	// Default values for fields
550   table->field[0]->store(u_d->name.str, u_d->name.length, system_charset_info);
551   table->field[1]->store((longlong) u_d->returns, TRUE);
552   table->field[2]->store(u_d->dl, strlen(u_d->dl), system_charset_info);
553   if (table->s->fields >= 4)			// If not old func format
554     table->field[3]->store((longlong) u_d->type, TRUE);
555   error = table->file->ha_write_row(table->record[0]);
556 
557   if (error)
558   {
559     char errbuf[MYSYS_STRERROR_SIZE];
560     my_error(ER_ERROR_ON_WRITE, MYF(0), "mysql.func", error,
561              my_strerror(errbuf, sizeof(errbuf), error));
562     del_udf(u_d);
563     goto err;
564   }
565   mysql_rwlock_unlock(&THR_LOCK_udf);
566 
567   /* Binlog the create function. */
568   if (write_bin_log(thd, true, thd->query().str, thd->query().length))
569   {
570     /* Restore the state of binlog format */
571     assert(!thd->is_current_stmt_binlog_format_row());
572     if (save_binlog_row_based)
573       thd->set_current_stmt_binlog_format_row();
574     DBUG_RETURN(1);
575   }
576   /* Restore the state of binlog format */
577   assert(!thd->is_current_stmt_binlog_format_row());
578   if (save_binlog_row_based)
579     thd->set_current_stmt_binlog_format_row();
580   DBUG_RETURN(0);
581 
582  err:
583   if (new_dl)
584     dlclose(dl);
585   mysql_rwlock_unlock(&THR_LOCK_udf);
586   /* Restore the state of binlog format */
587   assert(!thd->is_current_stmt_binlog_format_row());
588   if (save_binlog_row_based)
589     thd->set_current_stmt_binlog_format_row();
590   DBUG_RETURN(1);
591 }
592 
593 
mysql_drop_function(THD * thd,const LEX_STRING * udf_name)594 int mysql_drop_function(THD *thd,const LEX_STRING *udf_name)
595 {
596   TABLE *table;
597   TABLE_LIST tables;
598   udf_func *udf;
599   char *exact_name_str;
600   size_t exact_name_len;
601   bool save_binlog_row_based;
602   int error= 1;
603   DBUG_ENTER("mysql_drop_function");
604 
605   if (!initialized)
606   {
607     if (opt_noacl)
608       my_error(ER_FUNCTION_NOT_DEFINED, MYF(0), udf_name->str);
609     else
610       my_message(ER_OUT_OF_RESOURCES, ER(ER_OUT_OF_RESOURCES), MYF(0));
611     DBUG_RETURN(1);
612   }
613 
614   tables.init_one_table("mysql", 5, "func", 4, "func", TL_WRITE);
615   if (!(table= open_ltable(thd, &tables, TL_WRITE, MYSQL_LOCK_IGNORE_TIMEOUT)))
616     DBUG_RETURN(1);
617 
618   /*
619     Turn off row binlogging of this statement and use statement-based
620     so that all supporting tables are updated for DROP FUNCTION command.
621   */
622   if ((save_binlog_row_based= thd->is_current_stmt_binlog_format_row()))
623     thd->clear_current_stmt_binlog_format_row();
624 
625   mysql_rwlock_wrlock(&THR_LOCK_udf);
626   if (!(udf=(udf_func*) my_hash_search(&udf_hash,(uchar*) udf_name->str,
627                                        (uint) udf_name->length)))
628   {
629     my_error(ER_FUNCTION_NOT_DEFINED, MYF(0), udf_name->str);
630     mysql_rwlock_unlock(&THR_LOCK_udf);
631     goto exit;
632   }
633   exact_name_str= udf->name.str;
634   exact_name_len= udf->name.length;
635   del_udf(udf);
636   /*
637     Close the handle if this was function that was found during boot or
638     CREATE FUNCTION and it's not in use by any other udf function
639   */
640   if (udf->dlhandle && !find_udf_dl(udf->dl))
641     dlclose(udf->dlhandle);
642   mysql_rwlock_unlock(&THR_LOCK_udf);
643 
644   table->use_all_columns();
645   table->field[0]->store(exact_name_str, exact_name_len, &my_charset_bin);
646   if (!table->file->ha_index_read_idx_map(table->record[0], 0,
647                                           table->field[0]->ptr,
648                                           HA_WHOLE_KEY,
649                                           HA_READ_KEY_EXACT))
650   {
651     int delete_err;
652     if ((delete_err = table->file->ha_delete_row(table->record[0])))
653       table->file->print_error(delete_err, MYF(0));
654   }
655 
656   /*
657     Binlog the drop function. Keep the table open and locked
658     while binlogging, to avoid binlog inconsistency.
659   */
660   if (!write_bin_log(thd, true, thd->query().str, thd->query().length))
661     error= 0;
662 exit:
663   /* Restore the state of binlog format */
664   assert(!thd->is_current_stmt_binlog_format_row());
665   if (save_binlog_row_based)
666     thd->set_current_stmt_binlog_format_row();
667   DBUG_RETURN(error);
668 }
669 
670 #endif /* HAVE_DLOPEN */
671