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 /// @file
24 ///
25 /// mysqltest client - Tool used for executing a .test file.
26 ///
27 /// See @ref PAGE_MYSQL_TEST_RUN "The MySQL Test Framework" for more
28 /// information.
29 
30 #include "client/mysqltest/error_names.h"
31 #include "client/mysqltest/expected_errors.h"
32 #include "client/mysqltest/expected_warnings.h"
33 #include "client/mysqltest/logfile.h"
34 #include "client/mysqltest/regular_expressions.h"
35 #include "client/mysqltest/secondary_engine.h"
36 #include "client/mysqltest/utils.h"
37 #include "compression.h"
38 
39 #include <algorithm>
40 #include <chrono>
41 #include <cmath>  // std::isinf
42 #include <limits>
43 #include <new>
44 #include <sstream>
45 #ifdef _WIN32
46 #include <thread>  // std::thread
47 #endif
48 
49 #include <assert.h>
50 #if defined MY_MSCRT_DEBUG || defined _WIN32
51 #include <crtdbg.h>
52 #endif
53 #ifdef _WIN32
54 #include <direct.h>
55 #endif
56 #include <errno.h>
57 #include <fcntl.h>
58 #include <limits.h>
59 #include <mysql_async.h>
60 #include <mysql_version.h>
61 #include <mysqld_error.h>
62 #include <signal.h>
63 #include <stdarg.h>
64 #include <stdio.h>
65 #include <stdlib.h>
66 #include <sys/types.h>
67 #ifndef _WIN32
68 #include <poll.h>
69 #include <sys/time.h>
70 #include <sys/wait.h>
71 #endif
72 #ifdef _WIN32
73 #include <windows.h>
74 #endif
75 
76 #include "caching_sha2_passwordopt-vars.h"
77 #include "client/client_priv.h"
78 #include "m_ctype.h"
79 #include "map_helpers.h"
80 #include "mf_wcomp.h"  // wild_compare
81 #include "my_compiler.h"
82 #include "my_config.h"
83 #include "my_dbug.h"
84 #include "my_default.h"
85 #include "my_dir.h"
86 #include "my_inttypes.h"
87 #include "my_macros.h"
88 #include "my_pointer_arithmetic.h"
89 #include "my_stacktrace.h"
90 #include "my_systime.h"  // my_sleep()
91 #include "my_thread_local.h"
92 #include "prealloced_array.h"
93 #include "print_version.h"
94 #include "sql_common.h"
95 #include "template_utils.h"
96 #include "typelib.h"
97 #include "violite.h"
98 #include "welcome_copyright_notice.h"  // ORACLE_WELCOME_COPYRIGHT_NOTICE
99 
100 #ifdef _WIN32
101 #define SIGNAL_FMT "exception 0x%x"
102 #else
103 #define SIGNAL_FMT "signal %d"
104 #endif
105 
106 #ifdef _WIN32
107 #define setenv(a, b, c) _putenv_s(a, b)
108 #define popen _popen
109 #define pclose _pclose
110 #endif
111 
112 #define MAX_VAR_NAME_LENGTH 256
113 #define MAX_COLUMNS 256
114 #define MAX_DELIMITER_LENGTH 16
115 #define DEFAULT_MAX_CONN 128
116 #define REPLACE_ROUND_MAX 16
117 
118 /* Flags controlling send and reap */
119 #define QUERY_SEND_FLAG 1
120 #define QUERY_REAP_FLAG 2
121 
122 #define APPEND_TYPE(type)                                                 \
123   {                                                                       \
124     dynstr_append(ds, "-- ");                                             \
125     switch (type) {                                                       \
126       case SESSION_TRACK_SYSTEM_VARIABLES:                                \
127         dynstr_append(ds, "Tracker : SESSION_TRACK_SYSTEM_VARIABLES\n");  \
128         break;                                                            \
129       case SESSION_TRACK_SCHEMA:                                          \
130         dynstr_append(ds, "Tracker : SESSION_TRACK_SCHEMA\n");            \
131         break;                                                            \
132       case SESSION_TRACK_STATE_CHANGE:                                    \
133         dynstr_append(ds, "Tracker : SESSION_TRACK_STATE_CHANGE\n");      \
134         break;                                                            \
135       case SESSION_TRACK_GTIDS:                                           \
136         dynstr_append(ds, "Tracker : SESSION_TRACK_GTIDS\n");             \
137         break;                                                            \
138       case SESSION_TRACK_TRANSACTION_CHARACTERISTICS:                     \
139         dynstr_append(                                                    \
140             ds, "Tracker : SESSION_TRACK_TRANSACTION_CHARACTERISTICS\n"); \
141         break;                                                            \
142       case SESSION_TRACK_TRANSACTION_STATE:                               \
143         dynstr_append(ds, "Tracker : SESSION_TRACK_TRANSACTION_STATE\n"); \
144         break;                                                            \
145       default:                                                            \
146         dynstr_append(ds, "\n");                                          \
147     }                                                                     \
148   }
149 
150 extern CHARSET_INFO my_charset_utf16le_bin;
151 
152 // List of error codes specified with 'error' command.
153 Expected_errors *expected_errors = new Expected_errors();
154 
155 // List of warnings disabled with 'disable_warnings' command.
156 Expected_warnings *disabled_warnings = new Expected_warnings();
157 
158 // List of warnings enabled with 'enable_warnings' command.
159 Expected_warnings *enabled_warnings = new Expected_warnings();
160 
161 enum {
162   OPT_COLORED_DIFF = OPT_MAX_CLIENT_OPTION,
163   OPT_CURSOR_PROTOCOL,
164   OPT_EXPLAIN_PROTOCOL,
165   OPT_JSON_EXPLAIN_PROTOCOL,
166   OPT_LOG_DIR,
167   OPT_MARK_PROGRESS,
168   OPT_MAX_CONNECT_RETRIES,
169   OPT_MAX_CONNECTIONS,
170   OPT_NO_SKIP,
171   OPT_OFFLOAD_COUNT_FILE,
172   OPT_PS_PROTOCOL,
173   OPT_RESULT_FORMAT_VERSION,
174 #ifdef _WIN32
175   OPT_SAFEPROCESS_PID,
176 #endif
177   OPT_SP_PROTOCOL,
178   OPT_TAIL_LINES,
179   OPT_TRACE_EXEC,
180   OPT_TRACE_PROTOCOL,
181   OPT_VIEW_PROTOCOL,
182 };
183 
184 static int record = 0;
185 static char *opt_db = nullptr, *opt_pass = nullptr;
186 const char *opt_user = nullptr, *opt_host = nullptr, *unix_sock = nullptr,
187            *opt_basedir = "./";
188 const char *excluded_string = nullptr;
189 static char *shared_memory_base_name = nullptr;
190 const char *opt_logdir = "";
191 const char *opt_include = nullptr, *opt_charsets_dir;
192 static int opt_port = 0;
193 static int opt_max_connect_retries;
194 static int opt_result_format_version;
195 static int opt_max_connections = DEFAULT_MAX_CONN;
196 static bool opt_colored_diff = false;
197 static bool opt_compress = false, silent = false, verbose = false,
198             trace_exec = false;
199 static bool debug_info_flag = false, debug_check_flag = false;
200 static bool tty_password = false;
201 static bool opt_mark_progress = false;
202 static bool ps_protocol = false, ps_protocol_enabled = false;
203 static bool sp_protocol = false, sp_protocol_enabled = false;
204 static bool no_skip = false;
205 static bool view_protocol = false, view_protocol_enabled = false;
206 static bool opt_trace_protocol = false, opt_trace_protocol_enabled = false;
207 static bool explain_protocol = false, explain_protocol_enabled = false;
208 static bool json_explain_protocol = false,
209             json_explain_protocol_enabled = false;
210 static bool cursor_protocol = false, cursor_protocol_enabled = false;
211 static bool testcase_disabled = false;
212 static bool display_result_vertically = false, display_result_lower = false,
213             display_metadata = false, display_result_sorted = false,
214             display_session_track_info = false;
215 static int start_sort_column = 0;
216 static bool disable_query_log = false, disable_result_log = false;
217 static bool disable_connect_log = true;
218 static bool disable_warnings = false;
219 static bool disable_info = true;
220 static bool abort_on_error = true;
221 static bool server_initialized = false;
222 static bool is_windows = false;
223 static MEM_ROOT argv_alloc{PSI_NOT_INSTRUMENTED, 512};
224 static const char *load_default_groups[] = {"mysqltest", "client", nullptr};
225 static char line_buffer[MAX_DELIMITER_LENGTH], *line_buffer_pos = line_buffer;
226 static bool can_handle_expired_passwords = true;
227 
228 /*
229   These variables control the behavior of the asynchronous operations for
230   mysqltest client. If --async-client is specified, use_async_client is true.
231   Each command checks enable_async_client (which can be forced off or
232   disabled for a single command) to decide the mode it uses to run.
233 */
234 static bool use_async_client = false;
235 static bool enable_async_client = false;
236 
237 // Secondary engine options
238 static const char *opt_offload_count_file;
239 
240 static Secondary_engine *secondary_engine = nullptr;
241 
242 static uint opt_zstd_compress_level = default_zstd_compression_level;
243 static char *opt_compress_algorithm = nullptr;
244 
245 #ifdef _WIN32
246 static DWORD opt_safe_process_pid;
247 static HANDLE mysqltest_thread;
248 // Event handle for stacktrace request event
249 static HANDLE stacktrace_request_event = NULL;
250 static std::thread wait_for_stacktrace_request_event_thread;
251 #endif
252 
253 Logfile log_file;
254 // File to store the progress
255 Logfile progress_file;
256 
257 /// Info on properties that can be set with '--disable_X' and
258 /// '--disable_X' commands.
259 struct Property {
260   bool *var;             // Actual variable
261   bool set;              // Has been set for ONCE command
262   bool old;              // If set, thus is the old value
263   bool reverse;          // Variable is true if disabled
264   const char *env_name;  // Environment variable name
265 };
266 
267 static struct Property prop_list[] = {
268     {&abort_on_error, false, true, false, "$ENABLE_ABORT_ON_ERROR"},
269     {&disable_connect_log, false, true, true, "$ENABLE_CONNECT_LOG"},
270     {&disable_info, false, true, true, "$ENABLE_INFO"},
271     {&display_session_track_info, false, true, true,
272      "$ENABLE_STATE_CHANGE_INFO"},
273     {&display_metadata, false, false, false, "$ENABLE_METADATA"},
274     {&ps_protocol_enabled, false, false, false, "$ENABLE_PS_PROTOCOL"},
275     {&disable_query_log, false, false, true, "$ENABLE_QUERY_LOG"},
276     {&disable_result_log, false, false, true, "$ENABLE_RESULT_LOG"},
277     {&disable_warnings, false, false, true, "$ENABLE_WARNINGS"},
278     {&enable_async_client, false, false, false, "$ENABLE_ASYNC_CLIENT"}};
279 
280 static bool once_property = false;
281 
282 enum enum_prop {
283   P_ABORT = 0,
284   P_CONNECT,
285   P_INFO,
286   P_SESSION_TRACK,
287   P_META,
288   P_PS,
289   P_QUERY,
290   P_RESULT,
291   P_WARN,
292   P_ASYNC,
293   P_MAX
294 };
295 
296 static uint start_lineno = 0; /* Start line of current command */
297 static uint my_end_arg = 0;
298 
299 /* Number of lines of the result to include in failure report */
300 static uint opt_tail_lines = 0;
301 
302 static uint opt_connect_timeout = 0;
303 
304 static char delimiter[MAX_DELIMITER_LENGTH] = ";";
305 static size_t delimiter_length = 1;
306 
307 static char TMPDIR[FN_REFLEN];
308 
309 /* Block stack */
310 enum block_cmd { cmd_none, cmd_if, cmd_while };
311 
312 struct st_block {
313   int line;                         /* Start line of block */
314   bool ok;                          /* Should block be executed */
315   enum block_cmd cmd;               /* Command owning the block */
316   char delim[MAX_DELIMITER_LENGTH]; /* Delimiter before block */
317 };
318 
319 static struct st_block block_stack[32];
320 static struct st_block *cur_block, *block_stack_end;
321 
322 /* Open file stack */
323 struct st_test_file {
324   FILE *file;
325   char *file_name;
326   uint lineno; /* Current line in file */
327 };
328 
329 static struct st_test_file file_stack[16];
330 static struct st_test_file *cur_file;
331 static struct st_test_file *file_stack_end;
332 
333 static const char *default_charset = MYSQL_DEFAULT_CHARSET_NAME;
334 CHARSET_INFO *charset_info =
335     &my_charset_utf8mb4_0900_ai_ci; /* Default charset */
336 
337 /*
338   Timer related variables
339   See the timer_output() definition for details
340 */
341 static char *timer_file = nullptr;
342 static ulonglong timer_start;
343 static void timer_output(void);
344 static ulonglong timer_now(void);
345 
346 static ulong connection_retry_sleep = 100000; /* Microseconds */
347 
348 static char *opt_plugin_dir = nullptr;
349 
350 /* To retrieve a filename from a filepath */
get_filename_from_path(const char * path)351 const char *get_filename_from_path(const char *path) {
352   const char *fname = nullptr;
353   if (is_windows)
354     fname = strrchr(path, '\\');
355   else
356     fname = strrchr(path, '/');
357   if (fname == nullptr)
358     return path;
359   else
360     return ++fname;
361 }
362 
363 static uint opt_protocol = 0;
364 
365 #if defined(_WIN32)
366 static uint opt_protocol_for_default_connection = MYSQL_PROTOCOL_PIPE;
367 #endif
368 
369 struct st_command;
370 typedef Prealloced_array<st_command *, 1024> Q_lines;
371 Q_lines *q_lines;
372 
373 #include "sslopt-vars.h"
374 
375 struct Parser {
376   int read_lines, current_line;
377 } parser;
378 
379 struct MasterPos {
380   char file[FN_REFLEN];
381   ulong pos;
382 } master_pos;
383 
384 /* if set, all results are concated and compared against this file */
385 const char *result_file_name = nullptr;
386 
387 typedef struct {
388   char *name;
389   size_t name_len;
390   char *str_val;
391   size_t str_val_len;
392   int int_val;
393   size_t alloced_len;
394   bool int_dirty; /* do not update string if int is updated until first read */
395   bool is_int;
396   bool alloced;
397 } VAR;
398 
399 /*Perl/shell-like variable registers */
400 VAR var_reg[10];
401 
402 struct var_free {
403   void operator()(VAR *var) const;
404 };
405 
406 collation_unordered_map<std::string, std::unique_ptr<VAR, var_free>> *var_hash;
407 
408 struct st_connection {
409   MYSQL mysql;
410   /* Used when creating views and sp, to avoid implicit commit */
411   MYSQL *util_mysql;
412   char *name;
413   size_t name_len;
414   MYSQL_STMT *stmt;
415   /* Set after send to disallow other queries before reap */
416   bool pending;
417 };
418 
419 struct st_connection *connections = nullptr;
420 struct st_connection *cur_con = nullptr, *next_con, *connections_end;
421 
422 /*
423   List of commands in mysqltest
424   Must match the "command_names" array
425   Add new commands before Q_UNKNOWN!
426 */
427 enum enum_commands {
428   Q_CONNECTION = 1,
429   Q_QUERY,
430   Q_CONNECT,
431   Q_SLEEP,
432   Q_INC,
433   Q_DEC,
434   Q_SOURCE,
435   Q_DISCONNECT,
436   Q_LET,
437   Q_ECHO,
438   Q_EXPR,
439   Q_WHILE,
440   Q_END_BLOCK,
441   Q_SAVE_MASTER_POS,
442   Q_SYNC_WITH_MASTER,
443   Q_SYNC_SLAVE_WITH_MASTER,
444   Q_ERROR,
445   Q_SEND,
446   Q_REAP,
447   Q_DIRTY_CLOSE,
448   Q_REPLACE,
449   Q_REPLACE_COLUMN,
450   Q_PING,
451   Q_EVAL,
452   Q_ENABLE_QUERY_LOG,
453   Q_DISABLE_QUERY_LOG,
454   Q_ENABLE_RESULT_LOG,
455   Q_DISABLE_RESULT_LOG,
456   Q_ENABLE_CONNECT_LOG,
457   Q_DISABLE_CONNECT_LOG,
458   Q_WAIT_FOR_SLAVE_TO_STOP,
459   Q_ENABLE_WARNINGS,
460   Q_DISABLE_WARNINGS,
461   Q_ENABLE_INFO,
462   Q_DISABLE_INFO,
463   Q_ENABLE_SESSION_TRACK_INFO,
464   Q_DISABLE_SESSION_TRACK_INFO,
465   Q_ENABLE_METADATA,
466   Q_DISABLE_METADATA,
467   Q_ENABLE_ASYNC_CLIENT,
468   Q_DISABLE_ASYNC_CLIENT,
469   Q_EXEC,
470   Q_EXECW,
471   Q_EXEC_BACKGROUND,
472   Q_DELIMITER,
473   Q_DISABLE_ABORT_ON_ERROR,
474   Q_ENABLE_ABORT_ON_ERROR,
475   Q_DISPLAY_VERTICAL_RESULTS,
476   Q_DISPLAY_HORIZONTAL_RESULTS,
477   Q_QUERY_VERTICAL,
478   Q_QUERY_HORIZONTAL,
479   Q_SORTED_RESULT,
480   Q_PARTIALLY_SORTED_RESULT,
481   Q_LOWERCASE,
482   Q_START_TIMER,
483   Q_END_TIMER,
484   Q_CHARACTER_SET,
485   Q_DISABLE_PS_PROTOCOL,
486   Q_ENABLE_PS_PROTOCOL,
487   Q_DISABLE_RECONNECT,
488   Q_ENABLE_RECONNECT,
489   Q_IF,
490   Q_DISABLE_TESTCASE,
491   Q_ENABLE_TESTCASE,
492   Q_REPLACE_REGEX,
493   Q_REPLACE_NUMERIC_ROUND,
494   Q_REMOVE_FILE,
495   Q_FILE_EXIST,
496   Q_WRITE_FILE,
497   Q_COPY_FILE,
498   Q_PERL,
499   Q_DIE,
500   Q_EXIT,
501   Q_SKIP,
502   Q_CHMOD_FILE,
503   Q_APPEND_FILE,
504   Q_CAT_FILE,
505   Q_DIFF_FILES,
506   Q_SEND_QUIT,
507   Q_CHANGE_USER,
508   Q_MKDIR,
509   Q_RMDIR,
510   Q_FORCE_RMDIR,
511   Q_FORCE_CPDIR,
512   Q_LIST_FILES,
513   Q_LIST_FILES_WRITE_FILE,
514   Q_LIST_FILES_APPEND_FILE,
515   Q_SEND_SHUTDOWN,
516   Q_SHUTDOWN_SERVER,
517   Q_RESULT_FORMAT_VERSION,
518   Q_MOVE_FILE,
519   Q_REMOVE_FILES_WILDCARD,
520   Q_COPY_FILES_WILDCARD,
521   Q_SEND_EVAL,
522   Q_OUTPUT, /* redirect output to a file */
523   Q_RESET_CONNECTION,
524   Q_UNKNOWN, /* Unknown command.   */
525   Q_COMMENT, /* Comments, ignored. */
526   Q_COMMENT_WITH_COMMAND,
527   Q_EMPTY_LINE
528 };
529 
530 const char *command_names[] = {
531     "connection", "query", "connect", "sleep", "inc", "dec", "source",
532     "disconnect", "let", "echo", "expr", "while", "end", "save_master_pos",
533     "sync_with_master", "sync_slave_with_master", "error", "send", "reap",
534     "dirty_close", "replace_result", "replace_column", "ping", "eval",
535     /* Enable/disable that the _query_ is logged to result file */
536     "enable_query_log", "disable_query_log",
537     /* Enable/disable that the _result_ from a query is logged to result file */
538     "enable_result_log", "disable_result_log", "enable_connect_log",
539     "disable_connect_log", "wait_for_slave_to_stop", "enable_warnings",
540     "disable_warnings", "enable_info", "disable_info",
541     "enable_session_track_info", "disable_session_track_info",
542     "enable_metadata", "disable_metadata", "enable_async_client",
543     "disable_async_client", "exec", "execw", "exec_in_background", "delimiter",
544     "disable_abort_on_error", "enable_abort_on_error", "vertical_results",
545     "horizontal_results", "query_vertical", "query_horizontal", "sorted_result",
546     "partially_sorted_result", "lowercase_result", "start_timer", "end_timer",
547     "character_set", "disable_ps_protocol", "enable_ps_protocol",
548     "disable_reconnect", "enable_reconnect", "if", "disable_testcase",
549     "enable_testcase", "replace_regex", "replace_numeric_round", "remove_file",
550     "file_exists", "write_file", "copy_file", "perl", "die",
551 
552     /* Don't execute any more commands, compare result */
553     "exit", "skip", "chmod", "append_file", "cat_file", "diff_files",
554     "send_quit", "change_user", "mkdir", "rmdir", "force-rmdir", "force-cpdir",
555     "list_files", "list_files_write_file", "list_files_append_file",
556     "send_shutdown", "shutdown_server", "result_format", "move_file",
557     "remove_files_wildcard", "copy_files_wildcard", "send_eval", "output",
558     "reset_connection",
559 
560     nullptr};
561 
562 struct st_command {
563   char *query, *query_buf, *first_argument, *last_argument, *end;
564   DYNAMIC_STRING content;
565   size_t first_word_len, query_len;
566   bool abort_on_error, used_replace;
567   char output_file[FN_REFLEN];
568   enum enum_commands type;
569   // Line number of the command
570   uint lineno;
571 };
572 
573 TYPELIB command_typelib = {array_elements(command_names), "", command_names,
574                            nullptr};
575 
576 DYNAMIC_STRING ds_res;
577 DYNAMIC_STRING ds_result;
578 /* Points to ds_warning in run_query, so it can be freed */
579 DYNAMIC_STRING *ds_warn = nullptr;
580 struct st_command *curr_command = nullptr;
581 
582 char builtin_echo[FN_REFLEN];
583 
584 /* Stores regex substitutions */
585 
586 struct st_replace_regex *glob_replace_regex = nullptr;
587 
588 struct REPLACE;
589 REPLACE *glob_replace = nullptr;
590 void replace_strings_append(REPLACE *rep, DYNAMIC_STRING *ds, const char *from,
591                             size_t len);
592 
593 static void cleanup_and_exit(int exit_code) MY_ATTRIBUTE((noreturn));
594 
595 void die(const char *fmt, ...) MY_ATTRIBUTE((format(printf, 1, 2)))
596     MY_ATTRIBUTE((noreturn));
597 void abort_not_supported_test(const char *fmt, ...)
598     MY_ATTRIBUTE((format(printf, 1, 2))) MY_ATTRIBUTE((noreturn));
599 void verbose_msg(const char *fmt, ...) MY_ATTRIBUTE((format(printf, 1, 2)));
600 void log_msg(const char *fmt, ...) MY_ATTRIBUTE((format(printf, 1, 2)));
601 
602 VAR *var_from_env(const char *, const char *);
603 VAR *var_init(VAR *v, const char *name, size_t name_len, const char *val,
604               size_t val_len);
605 VAR *var_get(const char *var_name, const char **var_name_end, bool raw,
606              bool ignore_not_existing);
607 void eval_expr(VAR *v, const char *p, const char **p_end, bool open_end = false,
608                bool do_eval = true);
609 bool match_delimiter(int c, const char *delim, size_t length);
610 
611 void do_eval(DYNAMIC_STRING *query_eval, const char *query,
612              const char *query_end, bool pass_through_escape_chars);
613 void str_to_file(const char *fname, char *str, size_t size);
614 void str_to_file2(const char *fname, char *str, size_t size, bool append);
615 
616 void fix_win_paths(const char *val, size_t len);
617 
618 #ifdef _WIN32
619 void free_win_path_patterns();
620 #endif
621 
622 /* For replace_column */
623 static char *replace_column[MAX_COLUMNS];
624 static uint max_replace_column = 0;
625 void do_get_replace_column(struct st_command *);
626 void free_replace_column();
627 
628 /* For replace */
629 void do_get_replace(struct st_command *command);
630 void free_replace();
631 
632 /* For replace_regex */
633 void do_get_replace_regex(struct st_command *command);
634 void free_replace_regex();
635 
636 /* For replace numeric round */
637 static int glob_replace_numeric_round = -1;
638 void do_get_replace_numeric_round(struct st_command *command);
639 void free_replace_numeric_round();
640 void replace_numeric_round_append(int round, DYNAMIC_STRING *ds,
641                                   const char *from, size_t len);
642 
643 /* Used by sleep */
644 void check_eol_junk_line(const char *eol);
645 
646 static void var_set(const char *var_name, const char *var_name_end,
647                     const char *var_val, const char *var_val_end);
648 
free_all_replace()649 static void free_all_replace() {
650   free_replace();
651   free_replace_regex();
652   free_replace_column();
653   free_replace_numeric_round();
654 }
655 
656 /*
657   To run tests via the async API, invoke mysqltest with --async-client.
658 */
659 class AsyncTimer {
660  public:
AsyncTimer(std::string label)661   explicit AsyncTimer(std::string label)
662       : label_(label), time_(std::chrono::system_clock::now()), start_(time_) {}
663 
~AsyncTimer()664   ~AsyncTimer() {
665     auto now = std::chrono::system_clock::now();
666     auto delta = now - start_;
667     ulonglong MY_ATTRIBUTE((unused)) micros =
668         std::chrono::duration_cast<std::chrono::microseconds>(delta).count();
669     DBUG_PRINT("async_timing",
670                ("%s total micros: %llu", label_.c_str(), micros));
671   }
672 
check()673   void check() {
674     auto now = std::chrono::system_clock::now();
675     auto delta = now - time_;
676     time_ = now;
677     ulonglong MY_ATTRIBUTE((unused)) micros =
678         std::chrono::duration_cast<std::chrono::microseconds>(delta).count();
679     DBUG_PRINT("async_timing", ("%s op micros: %llu", label_.c_str(), micros));
680   }
681 
682  private:
683   std::string label_;
684   std::chrono::system_clock::time_point time_;
685   std::chrono::system_clock::time_point start_;
686 };
687 
688 #ifdef _WIN32
689 /*
690   Check if any data is available in the socket to be read or written.
691 */
socket_event_listen(my_socket fd)692 static int socket_event_listen(my_socket fd) {
693   int result;
694   fd_set readfds, writefds, exceptfds;
695 
696   FD_ZERO(&readfds);
697   FD_ZERO(&writefds);
698   FD_ZERO(&exceptfds);
699 
700   FD_SET(fd, &exceptfds);
701   FD_SET(fd, &readfds);
702   FD_SET(fd, &writefds);
703 
704   result = select((int)(fd + 1), &readfds, &writefds, &exceptfds, NULL);
705   if (result < 0) {
706     DWORD error_code = WSAGetLastError();
707     verbose_msg("Cannot determine the status due to error :%lu\n", error_code);
708   }
709   return result;
710 }
711 #else
socket_event_listen(my_socket fd)712 static int socket_event_listen(my_socket fd) {
713   int result;
714   pollfd pfd;
715   pfd.fd = fd;
716   /*
717     Listen to both in/out because SSL can perform reads during writes (and
718     vice versa).
719   */
720   pfd.events = POLLIN | POLLOUT;
721   result = poll(&pfd, 1, -1);
722   if (result < 0) {
723     perror("poll");
724   }
725   return result;
726 }
727 #endif
728 
729 /*
730   Below async_mysql_*_wrapper functions are used to measure how much time
731   each nonblocking call spends before completing the operations.
732 i*/
async_mysql_fetch_row_wrapper(MYSQL_RES * res)733 static MYSQL_ROW async_mysql_fetch_row_wrapper(MYSQL_RES *res) {
734   MYSQL_ROW row;
735   MYSQL *mysql = res->handle;
736   AsyncTimer t(__func__);
737   while (mysql_fetch_row_nonblocking(res, &row) == NET_ASYNC_NOT_READY) {
738     t.check();
739     int result = socket_event_listen(mysql_get_socket_descriptor(mysql));
740     if (result == -1) return nullptr;
741   }
742   return row;
743 }
744 
async_mysql_store_result_wrapper(MYSQL * mysql)745 static MYSQL_RES *async_mysql_store_result_wrapper(MYSQL *mysql) {
746   MYSQL_RES *mysql_result;
747   AsyncTimer t(__func__);
748   while (mysql_store_result_nonblocking(mysql, &mysql_result) ==
749          NET_ASYNC_NOT_READY) {
750     t.check();
751     int result = socket_event_listen(mysql_get_socket_descriptor(mysql));
752     if (result == -1) return nullptr;
753   }
754   return mysql_result;
755 }
756 
async_mysql_real_query_wrapper(MYSQL * mysql,const char * query,ulong length)757 static int async_mysql_real_query_wrapper(MYSQL *mysql, const char *query,
758                                           ulong length) {
759   net_async_status status;
760   AsyncTimer t(__func__);
761   while ((status = mysql_real_query_nonblocking(mysql, query, length)) ==
762          NET_ASYNC_NOT_READY) {
763     t.check();
764     int result = socket_event_listen(mysql_get_socket_descriptor(mysql));
765     if (result == -1) return 1;
766   }
767   if (status == NET_ASYNC_ERROR) {
768     return 1;
769   }
770   return 0;
771 }
772 
async_mysql_send_query_wrapper(MYSQL * mysql,const char * query,ulong length)773 static int async_mysql_send_query_wrapper(MYSQL *mysql, const char *query,
774                                           ulong length) {
775   net_async_status status;
776   ASYNC_DATA(mysql)->async_query_length = length;
777   ASYNC_DATA(mysql)->async_query_state = QUERY_SENDING;
778 
779   AsyncTimer t(__func__);
780   while ((status = mysql_send_query_nonblocking(mysql, query, length)) ==
781          NET_ASYNC_NOT_READY) {
782     t.check();
783     int result = socket_event_listen(mysql_get_socket_descriptor(mysql));
784     if (result == -1) return 1;
785   }
786   if (status == NET_ASYNC_ERROR) {
787     return 1;
788   }
789   return 0;
790 }
791 
async_mysql_read_query_result_wrapper(MYSQL * mysql)792 static bool async_mysql_read_query_result_wrapper(MYSQL *mysql) {
793   net_async_status status;
794   AsyncTimer t(__func__);
795   while ((status = (*mysql->methods->read_query_result_nonblocking)(mysql)) ==
796          NET_ASYNC_NOT_READY) {
797     t.check();
798     int result = socket_event_listen(mysql_get_socket_descriptor(mysql));
799     if (result == -1) return true;
800   }
801   if (status == NET_ASYNC_ERROR) {
802     return true;
803   }
804   return false;
805 }
806 
async_mysql_next_result_wrapper(MYSQL * mysql)807 static int async_mysql_next_result_wrapper(MYSQL *mysql) {
808   net_async_status status;
809   AsyncTimer t(__func__);
810   while ((status = mysql_next_result_nonblocking(mysql)) ==
811          NET_ASYNC_NOT_READY) {
812     t.check();
813     int result = socket_event_listen(mysql_get_socket_descriptor(mysql));
814     if (result == -1) return 1;
815   }
816   if (status == NET_ASYNC_ERROR)
817     return 1;
818   else if (status == NET_ASYNC_COMPLETE_NO_MORE_RESULTS)
819     return -1;
820   else
821     return 0;
822 }
823 
async_mysql_real_connect_wrapper(MYSQL * mysql,const char * host,const char * user,const char * passwd,const char * db,uint port,const char * unix_socket,ulong client_flag)824 static MYSQL *async_mysql_real_connect_wrapper(
825     MYSQL *mysql, const char *host, const char *user, const char *passwd,
826     const char *db, uint port, const char *unix_socket, ulong client_flag) {
827   net_async_status status;
828   AsyncTimer t(__func__);
829 
830   while ((status = mysql_real_connect_nonblocking(
831               mysql, host, user, passwd, db, port, unix_socket, client_flag)) ==
832          NET_ASYNC_NOT_READY) {
833     t.check();
834   }
835   if (status == NET_ASYNC_ERROR)
836     return nullptr;
837   else
838     return mysql;
839 }
840 
async_mysql_query_wrapper(MYSQL * mysql,const char * query)841 static int async_mysql_query_wrapper(MYSQL *mysql, const char *query) {
842   net_async_status status;
843   AsyncTimer t(__func__);
844   while ((status = mysql_real_query_nonblocking(mysql, query, strlen(query))) ==
845          NET_ASYNC_NOT_READY) {
846     t.check();
847     int result = socket_event_listen(mysql_get_socket_descriptor(mysql));
848     if (result == -1) return 1;
849   }
850   if (status == NET_ASYNC_ERROR) {
851     return 1;
852   }
853   return 0;
854 }
855 
async_mysql_free_result_wrapper(MYSQL_RES * result)856 static void async_mysql_free_result_wrapper(MYSQL_RES *result) {
857   AsyncTimer t(__func__);
858   while (mysql_free_result_nonblocking(result) == NET_ASYNC_NOT_READY) {
859     t.check();
860     MYSQL *mysql = result->handle;
861     int listen_result = socket_event_listen(mysql_get_socket_descriptor(mysql));
862     if (listen_result == -1) return;
863   }
864   return;
865 }
866 
867 /*
868   Below are the wrapper functions which are defined on top of standard C APIs
869   to make a decision on whether to call blocking or non blocking API based on
870   --async-client option is set or not.
871 */
mysql_fetch_row_wrapper(MYSQL_RES * res)872 static MYSQL_ROW mysql_fetch_row_wrapper(MYSQL_RES *res) {
873   if (enable_async_client)
874     return async_mysql_fetch_row_wrapper(res);
875   else
876     return mysql_fetch_row(res);
877 }
878 
mysql_store_result_wrapper(MYSQL * mysql)879 static MYSQL_RES *mysql_store_result_wrapper(MYSQL *mysql) {
880   if (enable_async_client)
881     return async_mysql_store_result_wrapper(mysql);
882   else
883     return mysql_store_result(mysql);
884 }
885 
mysql_real_query_wrapper(MYSQL * mysql,const char * query,ulong length)886 static int mysql_real_query_wrapper(MYSQL *mysql, const char *query,
887                                     ulong length) {
888   if (enable_async_client)
889     return async_mysql_real_query_wrapper(mysql, query, length);
890   else
891     return mysql_real_query(mysql, query, length);
892 }
893 
mysql_send_query_wrapper(MYSQL * mysql,const char * query,ulong length)894 static int mysql_send_query_wrapper(MYSQL *mysql, const char *query,
895                                     ulong length) {
896   if (enable_async_client)
897     return async_mysql_send_query_wrapper(mysql, query, length);
898   else
899     return mysql_send_query(mysql, query, length);
900 }
901 
mysql_read_query_result_wrapper(MYSQL * mysql)902 static bool mysql_read_query_result_wrapper(MYSQL *mysql) {
903   bool ret;
904   if (enable_async_client)
905     ret = async_mysql_read_query_result_wrapper(mysql);
906   else
907     ret = mysql_read_query_result(mysql);
908   return ret;
909 }
910 
mysql_query_wrapper(MYSQL * mysql,const char * query)911 static int mysql_query_wrapper(MYSQL *mysql, const char *query) {
912   if (enable_async_client)
913     return async_mysql_query_wrapper(mysql, query);
914   else
915     return mysql_query(mysql, query);
916 }
917 
mysql_next_result_wrapper(MYSQL * mysql)918 static int mysql_next_result_wrapper(MYSQL *mysql) {
919   if (enable_async_client)
920     return async_mysql_next_result_wrapper(mysql);
921   else
922     return mysql_next_result(mysql);
923 }
924 
mysql_real_connect_wrapper(MYSQL * mysql,const char * host,const char * user,const char * passwd,const char * db,uint port,const char * unix_socket,ulong client_flag)925 static MYSQL *mysql_real_connect_wrapper(MYSQL *mysql, const char *host,
926                                          const char *user, const char *passwd,
927                                          const char *db, uint port,
928                                          const char *unix_socket,
929                                          ulong client_flag) {
930   if (enable_async_client)
931     return async_mysql_real_connect_wrapper(mysql, host, user, passwd, db, port,
932                                             unix_socket, client_flag);
933   else
934     return mysql_real_connect(mysql, host, user, passwd, db, port, unix_socket,
935                               client_flag);
936 }
937 
mysql_free_result_wrapper(MYSQL_RES * result)938 static void mysql_free_result_wrapper(MYSQL_RES *result) {
939   if (enable_async_client)
940     return async_mysql_free_result_wrapper(result);
941   else
942     return mysql_free_result(result);
943 }
944 
945 /* async client test code (end) */
946 
947 void replace_dynstr_append_mem(DYNAMIC_STRING *ds, const char *val, size_t len);
948 void replace_dynstr_append(DYNAMIC_STRING *ds, const char *val);
949 void replace_dynstr_append_uint(DYNAMIC_STRING *ds, uint val);
950 void dynstr_append_sorted(DYNAMIC_STRING *ds, DYNAMIC_STRING *ds_input,
951                           int start_sort_column);
952 
953 void revert_properties();
954 
do_eval(DYNAMIC_STRING * query_eval,const char * query,const char * query_end,bool pass_through_escape_chars)955 void do_eval(DYNAMIC_STRING *query_eval, const char *query,
956              const char *query_end, bool pass_through_escape_chars) {
957   const char *p;
958   char c, next_c;
959   int escaped = 0;
960   VAR *v;
961   DBUG_TRACE;
962 
963   for (p = query; (c = *p) && p < query_end; ++p) {
964     next_c = *(p + 1);
965     switch (c) {
966       case '$':
967         if (escaped ||
968             // a JSON path expression
969             next_c == '.' || next_c == '[' || next_c == '\'' || next_c == '"') {
970           escaped = 0;
971           dynstr_append_mem(query_eval, p, 1);
972         } else {
973           if (!(v = var_get(p, &p, false, false))) die("Bad variable in eval");
974           dynstr_append_mem(query_eval, v->str_val, v->str_val_len);
975         }
976         break;
977       case '\\':
978         if (escaped) {
979           escaped = 0;
980           dynstr_append_mem(query_eval, p, 1);
981         } else if (next_c == '\\' || next_c == '$' || next_c == '"') {
982           /* Set escaped only if next char is \, " or $ */
983           escaped = 1;
984 
985           if (pass_through_escape_chars) {
986             /* The escape char should be added to the output string. */
987             dynstr_append_mem(query_eval, p, 1);
988           }
989         } else
990           dynstr_append_mem(query_eval, p, 1);
991         break;
992       default:
993         escaped = 0;
994         dynstr_append_mem(query_eval, p, 1);
995         break;
996     }
997   }
998 #ifdef _WIN32
999   fix_win_paths(query_eval->str, query_eval->length);
1000 #endif
1001 }
1002 
1003 /*
1004   Run query and dump the result to stderr in vertical format
1005 
1006   NOTE! This function should be safe to call when an error
1007   has occurred and thus any further errors will be ignored(although logged)
1008 
1009   SYNOPSIS
1010   show_query
1011   mysql - connection to use
1012   query - query to run
1013 
1014 */
1015 
show_query(MYSQL * mysql,const char * query)1016 static void show_query(MYSQL *mysql, const char *query) {
1017   MYSQL_RES *res;
1018   DBUG_TRACE;
1019 
1020   if (!mysql) return;
1021 
1022   if (mysql_query_wrapper(mysql, query)) {
1023     log_msg("Error running query '%s': %d %s", query, mysql_errno(mysql),
1024             mysql_error(mysql));
1025     return;
1026   }
1027 
1028   if ((res = mysql_store_result_wrapper(mysql)) == nullptr) {
1029     /* No result set returned */
1030     return;
1031   }
1032 
1033   {
1034     MYSQL_ROW row;
1035     unsigned int i;
1036     unsigned int row_num = 0;
1037     unsigned int num_fields = mysql_num_fields(res);
1038     MYSQL_FIELD *fields = mysql_fetch_fields(res);
1039 
1040     fprintf(stderr, "=== %s ===\n", query);
1041     while ((row = mysql_fetch_row_wrapper(res))) {
1042       unsigned long *lengths = mysql_fetch_lengths(res);
1043       row_num++;
1044 
1045       fprintf(stderr, "---- %d. ----\n", row_num);
1046       for (i = 0; i < num_fields; i++) {
1047         /* looks ugly , but put here to convince parfait */
1048         assert(lengths);
1049         fprintf(stderr, "%s\t%.*s\n", fields[i].name, (int)lengths[i],
1050                 row[i] ? row[i] : "NULL");
1051       }
1052     }
1053     for (i = 0; i < std::strlen(query) + 8; i++) fprintf(stderr, "=");
1054     fprintf(stderr, "\n\n");
1055   }
1056   mysql_free_result_wrapper(res);
1057 }
1058 
1059 /*
1060   Show any warnings just before the error. Since the last error
1061   is added to the warning stack, only print @@warning_count-1 warnings.
1062 
1063   NOTE! This function should be safe to call when an error
1064   has occurred and this any further errors will be ignored(although logged)
1065 
1066   SYNOPSIS
1067   show_warnings_before_error
1068   mysql - connection to use
1069 
1070 */
1071 
show_warnings_before_error(MYSQL * mysql)1072 static void show_warnings_before_error(MYSQL *mysql) {
1073   MYSQL_RES *res;
1074   const char *query = "SHOW WARNINGS";
1075   DBUG_TRACE;
1076 
1077   if (!mysql) return;
1078 
1079   if (mysql_query_wrapper(mysql, query)) {
1080     log_msg("Error running query '%s': %d %s", query, mysql_errno(mysql),
1081             mysql_error(mysql));
1082     return;
1083   }
1084 
1085   if ((res = mysql_store_result_wrapper(mysql)) == nullptr) {
1086     /* No result set returned */
1087     return;
1088   }
1089 
1090   if (mysql_num_rows(res) <= 1) {
1091     /* Don't display the last row, it's "last error" */
1092   } else {
1093     MYSQL_ROW row;
1094     unsigned int row_num = 0;
1095     unsigned int num_fields = mysql_num_fields(res);
1096 
1097     fprintf(stderr, "\nWarnings from just before the error:\n");
1098     while ((row = mysql_fetch_row_wrapper(res))) {
1099       unsigned int i;
1100       unsigned long *lengths = mysql_fetch_lengths(res);
1101 
1102       if (++row_num >= mysql_num_rows(res)) {
1103         /* Don't display the last row, it's "last error" */
1104         break;
1105       }
1106 
1107       for (i = 0; i < num_fields; i++) {
1108         /* looks ugly , but put here to convince parfait */
1109         assert(lengths);
1110         fprintf(stderr, "%.*s ", (int)lengths[i], row[i] ? row[i] : "NULL");
1111       }
1112       fprintf(stderr, "\n");
1113     }
1114   }
1115   mysql_free_result_wrapper(res);
1116 }
1117 
1118 enum arg_type { ARG_STRING, ARG_REST };
1119 
1120 struct command_arg {
1121   const char *argname;     /* Name of argument   */
1122   enum arg_type type;      /* Type of argument   */
1123   bool required;           /* Argument required  */
1124   DYNAMIC_STRING *ds;      /* Storage for argument */
1125   const char *description; /* Description of the argument */
1126 };
1127 
check_command_args(struct st_command * command,char * arguments,const struct command_arg * args,int num_args,const char delimiter_arg)1128 static void check_command_args(struct st_command *command, char *arguments,
1129                                const struct command_arg *args, int num_args,
1130                                const char delimiter_arg) {
1131   int i;
1132   char *ptr = arguments;
1133   const char *start;
1134   DBUG_TRACE;
1135   DBUG_PRINT("enter", ("num_args: %d", num_args));
1136 
1137   for (i = 0; i < num_args; i++) {
1138     const struct command_arg *arg = &args[i];
1139     char delimiter;
1140 
1141     switch (arg->type) {
1142         /* A string */
1143       case ARG_STRING:
1144         /* Skip leading spaces */
1145         while (*ptr && *ptr == ' ') ptr++;
1146         start = ptr;
1147         delimiter = delimiter_arg;
1148         /* If start of arg is ' ` or " search to matching quote end instead */
1149         if (*ptr && strchr("'`\"", *ptr)) {
1150           delimiter = *ptr;
1151           start = ++ptr;
1152         }
1153         /* Find end of arg, terminated by "delimiter" */
1154         while (*ptr && *ptr != delimiter) ptr++;
1155         if (ptr > start) {
1156           init_dynamic_string(arg->ds, nullptr, ptr - start, 32);
1157           do_eval(arg->ds, start, ptr, false);
1158         } else {
1159           /* Empty string */
1160           init_dynamic_string(arg->ds, "", 0, 0);
1161         }
1162         /* Find real end of arg, terminated by "delimiter_arg" */
1163         /* This will do nothing if arg was not closed by quotes */
1164         while (*ptr && *ptr != delimiter_arg) ptr++;
1165 
1166         command->last_argument = ptr;
1167 
1168         /* Step past the delimiter */
1169         if (*ptr && *ptr == delimiter_arg) ptr++;
1170         DBUG_PRINT("info", ("val: %s", arg->ds->str));
1171         break;
1172 
1173         /* Rest of line */
1174       case ARG_REST:
1175         start = ptr;
1176         init_dynamic_string(arg->ds, nullptr, command->query_len, 256);
1177         do_eval(arg->ds, start, command->end, false);
1178         command->last_argument = command->end;
1179         DBUG_PRINT("info", ("val: %s", arg->ds->str));
1180         break;
1181 
1182       default:
1183         DBUG_ASSERT("Unknown argument type");
1184         break;
1185     }
1186 
1187     /* Check required arg */
1188     if (arg->ds->length == 0 && arg->required)
1189       die("Missing required argument '%s' to command '%.*s'", arg->argname,
1190           static_cast<int>(command->first_word_len), command->query);
1191   }
1192   /* Check for too many arguments passed */
1193   ptr = command->last_argument;
1194   while (ptr <= command->end && *ptr != '#') {
1195     if (*ptr && *ptr != ' ')
1196       die("Extra argument '%s' passed to '%.*s'", ptr,
1197           static_cast<int>(command->first_word_len), command->query);
1198     ptr++;
1199   }
1200 }
1201 
1202 /// Check whether given error is in list of expected errors.
1203 ///
1204 /// @param command      Pointer to the st_command structure which holds the
1205 ///                     arguments and information for the command.
1206 /// @param err_errno    Error number of the error that actually occurred.
1207 /// @param err_sqlstate SQLSTATE that was thrown, or NULL for impossible
1208 ///                     (file-ops, diff, etc.)
1209 ///
1210 /// @retval -1 if the given error is not in the list, index in the
1211 ///         list of expected errors otherwise.
1212 ///
1213 /// @note
1214 /// If caller needs to know whether the list was empty, they should
1215 /// check the value of expected_errors.count.
match_expected_error(struct st_command * command,std::uint32_t err_errno,const char * err_sqlstate)1216 static int match_expected_error(struct st_command *command,
1217                                 std::uint32_t err_errno,
1218                                 const char *err_sqlstate) {
1219   std::uint8_t index = 0;
1220 
1221   // Iterator for list/vector of expected errors
1222   std::vector<std::unique_ptr<Error>>::iterator error =
1223       expected_errors->begin();
1224 
1225   // Iterate over list of expected errors
1226   for (; error != expected_errors->end(); error++) {
1227     if ((*error)->type() == ERR_ERRNO) {
1228       // Error type is ERR_ERRNO
1229       if ((*error)->error_code() == err_errno) return index;
1230     } else if ((*error)->type() == ERR_SQLSTATE) {
1231       // Error type is ERR_SQLSTATE. NULL is quite likely, but not in
1232       // conjunction with a SQL-state expect.
1233       if (unlikely(err_sqlstate == nullptr)) {
1234         die("Expecting a SQLSTATE (%s) from query '%s' which cannot produce "
1235             "one.",
1236             (*error)->sqlstate(), command->query);
1237       }
1238 
1239       if (!std::strncmp((*error)->sqlstate(), err_sqlstate, SQLSTATE_LENGTH))
1240         return index;
1241     }
1242 
1243     index++;
1244   }
1245 
1246   return -1;
1247 }
1248 
1249 /// Handle errors which occurred during execution of a query.
1250 ///
1251 /// @param command      Pointer to the st_command structure which holds the
1252 ///                     arguments and information for the command.
1253 /// @param err_errno    Error number
1254 /// @param err_error    Error message
1255 /// @param err_sqlstate SQLSTATE that was thrown
1256 /// @param ds           Dynamic string to store the result.
1257 ///
1258 /// @note
1259 /// If there is an unexpected error, this function will abort mysqltest
1260 /// immediately.
handle_error(struct st_command * command,std::uint32_t err_errno,const char * err_error,const char * err_sqlstate,DYNAMIC_STRING * ds)1261 void handle_error(struct st_command *command, std::uint32_t err_errno,
1262                   const char *err_error, const char *err_sqlstate,
1263                   DYNAMIC_STRING *ds) {
1264   DBUG_TRACE;
1265 
1266   if (command->abort_on_error)
1267     die("Query '%s' failed.\nERROR %d (%s): %s", command->query, err_errno,
1268         err_sqlstate, err_error);
1269 
1270   DBUG_PRINT("info", ("Expected errors count: %zu", expected_errors->count()));
1271 
1272   int i = match_expected_error(command, err_errno, err_sqlstate);
1273 
1274   if (i >= 0) {
1275     if (!disable_result_log) {
1276       if (expected_errors->count() == 1) {
1277         // Only log error if there is one possible error
1278         dynstr_append_mem(ds, "ERROR ", 6);
1279         replace_dynstr_append(ds, err_sqlstate);
1280         dynstr_append_mem(ds, ": ", 2);
1281         replace_dynstr_append(ds, err_error);
1282         dynstr_append_mem(ds, "\n", 1);
1283       }
1284       // Don't log error if we may not get an error
1285       else if (expected_errors->type() == ERR_SQLSTATE ||
1286                (expected_errors->type() == ERR_ERRNO &&
1287                 expected_errors->error_code() != 0))
1288         dynstr_append(ds, "Got one of the listed errors\n");
1289     }
1290 
1291     revert_properties();
1292     return;
1293   }
1294 
1295   DBUG_PRINT("info",
1296              ("i: %d Expected errors count: %zu", i, expected_errors->count()));
1297 
1298   if (!disable_result_log) {
1299     dynstr_append_mem(ds, "ERROR ", 6);
1300     replace_dynstr_append(ds, err_sqlstate);
1301     dynstr_append_mem(ds, ": ", 2);
1302     replace_dynstr_append(ds, err_error);
1303     dynstr_append_mem(ds, "\n", 1);
1304   }
1305 
1306   if (expected_errors->count()) {
1307     if (expected_errors->count() == 1) {
1308       die("Query '%s' failed with wrong error %d: '%s', should have failed "
1309           "with error '%s'.",
1310           command->query, err_errno, err_error,
1311           expected_errors->error_list().c_str());
1312     } else {
1313       die("Query '%s' failed with wrong error %d: '%s', should have failed "
1314           "with any of '%s' errors.",
1315           command->query, err_errno, err_error,
1316           expected_errors->error_list().c_str());
1317     }
1318   }
1319 
1320   revert_properties();
1321 }
1322 
1323 /// Handle absence of errors after execution.
1324 ///
1325 /// Abort test run if the query succeeds and was expected to fail with
1326 /// an error.
1327 ///
1328 /// @param command Pointer to the st_command structure which holds the
1329 ///                arguments and information for the command.
handle_no_error(struct st_command * command)1330 void handle_no_error(struct st_command *command) {
1331   DBUG_TRACE;
1332 
1333   if (expected_errors->count()) {
1334     int index = match_expected_error(command, 0, "00000");
1335     if (index == -1) {
1336       if (expected_errors->count() == 1) {
1337         die("Query '%s' succeeded, should have failed with error '%s'",
1338             command->query, expected_errors->error_list().c_str());
1339       } else {
1340         die("Query '%s' succeeded, should have failed with any of '%s' errors.",
1341             command->query, expected_errors->error_list().c_str());
1342       }
1343     }
1344   }
1345 }
1346 
1347 /// Save error code returned by a mysqltest command in '$__error'
1348 /// variable.
1349 ///
1350 /// @param error Error code
save_error_code(std::uint32_t error)1351 static void save_error_code(std::uint32_t error) {
1352   char error_value[10];
1353   size_t error_length = std::snprintf(error_value, 10, "%u", error);
1354   error_value[error_length > 9 ? 9 : error_length] = '0';
1355   const char *var_name = "__error";
1356   var_set(var_name, var_name + 7, error_value, error_value + error_length);
1357 }
1358 
1359 /// Handle errors which occurred during execution of mysqltest commands
1360 /// like 'move_file', 'remove_file' etc which are used to perform file
1361 /// system operations.
1362 ///
1363 /// @param command Pointer to the st_command structure which holds the
1364 ///                arguments and information for the command.
1365 /// @param error   Error number
1366 ///
1367 /// @note
1368 /// If there is an unexpected error, this function will abort mysqltest
1369 /// client immediately.
handle_command_error(struct st_command * command,std::uint32_t error)1370 static void handle_command_error(struct st_command *command,
1371                                  std::uint32_t error) {
1372   DBUG_TRACE;
1373   DBUG_PRINT("enter", ("error: %d", error));
1374 
1375   if (error != 0 && command->abort_on_error) {
1376     die("Command \"%s\" failed with error %d. my_errno=%d.",
1377         command_names[command->type - 1], error, my_errno());
1378   }
1379 
1380   if (expected_errors->count()) {
1381     int index = match_expected_error(command, error, nullptr);
1382     if (index == -1) {
1383       if (error != 0) {
1384         if (expected_errors->count() == 1) {
1385           die("Command \"%s\" failed with wrong error: %d, my_errno=%d. should "
1386               "have failed with error '%s'.",
1387               command_names[command->type - 1], error, my_errno(),
1388               expected_errors->error_list().c_str());
1389         } else {
1390           die("Command \"%s\" failed with wrong error: %d, my_errno=%d. should "
1391               "have failed with any of '%s' errors.",
1392               command_names[command->type - 1], error, my_errno(),
1393               expected_errors->error_list().c_str());
1394         }
1395       } else {
1396         // Command succeeded, should have failed with an error
1397         if (expected_errors->count() == 1) {
1398           die("Command \"%s\" succeeded, should have failed with error '%s'.",
1399               command_names[command->type - 1],
1400               expected_errors->error_list().c_str());
1401         } else {
1402           die("Command \"%s\" succeeded, should have failed with any of '%s' "
1403               "errors.",
1404               command_names[command->type - 1],
1405               expected_errors->error_list().c_str());
1406         }
1407       }
1408     }
1409   }
1410 
1411   // Save the error code
1412   save_error_code(error);
1413 
1414   revert_properties();
1415 }
1416 
close_connections()1417 static void close_connections() {
1418   DBUG_TRACE;
1419   for (--next_con; next_con >= connections; --next_con) {
1420     if (next_con->stmt) mysql_stmt_close(next_con->stmt);
1421     next_con->stmt = nullptr;
1422     mysql_close(&next_con->mysql);
1423     if (next_con->util_mysql) mysql_close(next_con->util_mysql);
1424     my_free(next_con->name);
1425   }
1426   my_free(connections);
1427 }
1428 
close_statements()1429 static void close_statements() {
1430   struct st_connection *con;
1431   DBUG_TRACE;
1432   for (con = connections; con < next_con; con++) {
1433     if (con->stmt) mysql_stmt_close(con->stmt);
1434     con->stmt = nullptr;
1435   }
1436 }
1437 
close_files()1438 static void close_files() {
1439   DBUG_TRACE;
1440   for (; cur_file >= file_stack; cur_file--) {
1441     if (cur_file->file && cur_file->file != stdin) {
1442       DBUG_PRINT("info", ("closing file: %s", cur_file->file_name));
1443       fclose(cur_file->file);
1444     }
1445     my_free(cur_file->file_name);
1446     cur_file->file_name = nullptr;
1447   }
1448 }
1449 
free_used_memory()1450 static void free_used_memory() {
1451   // Delete the exptected errors pointer
1452   delete expected_errors;
1453 
1454   // Delete disabled and enabled warning list
1455   delete disabled_warnings;
1456   delete enabled_warnings;
1457 
1458   if (connections) close_connections();
1459   close_files();
1460   delete var_hash;
1461   var_hash = nullptr;
1462 
1463   struct st_command **q;
1464   for (q = q_lines->begin(); q != q_lines->end(); ++q) {
1465     my_free((*q)->query_buf);
1466     if ((*q)->content.str) dynstr_free(&(*q)->content);
1467     my_free((*q));
1468   }
1469 
1470   for (size_t i = 0; i < 10; i++) {
1471     if (var_reg[i].alloced_len) my_free(var_reg[i].str_val);
1472   }
1473 
1474   delete q_lines;
1475   dynstr_free(&ds_res);
1476   dynstr_free(&ds_result);
1477   if (ds_warn) dynstr_free(ds_warn);
1478   free_all_replace();
1479   my_free(opt_pass);
1480 #ifdef _WIN32
1481   free_win_path_patterns();
1482 #endif
1483 
1484   // Only call mysql_server_end if mysql_server_init has been called.
1485   if (server_initialized) mysql_server_end();
1486 
1487   // Don't use DBUG after mysql_server_end()
1488   return;
1489 }
1490 
cleanup_and_exit(int exit_code)1491 static void cleanup_and_exit(int exit_code) {
1492   if (opt_offload_count_file) {
1493     // Check if the current connection is active, if not create one.
1494     if (cur_con->mysql.net.vio == nullptr) {
1495       mysql_real_connect(&cur_con->mysql, opt_host, opt_user, opt_pass, opt_db,
1496                          opt_port, unix_sock,
1497                          CLIENT_MULTI_STATEMENTS | CLIENT_REMEMBER_OPTIONS);
1498     }
1499 
1500     // Save the final value of secondary engine execution status.
1501     if (secondary_engine->offload_count(&cur_con->mysql, "after"))
1502       exit_code = 1;
1503     secondary_engine->report_offload_count(opt_offload_count_file);
1504   }
1505 
1506   free_used_memory();
1507   my_end(my_end_arg);
1508 
1509   if (!silent) {
1510     switch (exit_code) {
1511       case 1:
1512         printf("not ok\n");
1513         break;
1514       case 0:
1515         printf("ok\n");
1516         break;
1517       case 62:
1518         printf("skipped\n");
1519         break;
1520       default:
1521         printf("unknown exit code: %d\n", exit_code);
1522         DBUG_ASSERT(0);
1523     }
1524   }
1525 
1526 // exit() appears to be not 100% reliable on Windows under some conditions.
1527 #ifdef _WIN32
1528   if (opt_safe_process_pid) {
1529     // Close the stack trace request event handle
1530     if (stacktrace_request_event != NULL) CloseHandle(stacktrace_request_event);
1531 
1532     // Detach or stop the thread waiting for stack trace event to occur.
1533     if (wait_for_stacktrace_request_event_thread.joinable())
1534       wait_for_stacktrace_request_event_thread.detach();
1535 
1536     // Close the thread handle
1537     if (!CloseHandle(mysqltest_thread))
1538       die("CloseHandle failed, err = %d.\n", GetLastError());
1539   }
1540 
1541   fflush(stdout);
1542   fflush(stderr);
1543   _exit(exit_code);
1544 #else
1545   exit(exit_code);
1546 #endif
1547 }
1548 
print_file_stack()1549 static void print_file_stack() {
1550   fprintf(stderr, "file %s: %d\n", cur_file->file_name, cur_file->lineno);
1551 
1552   struct st_test_file *err_file;
1553   for (err_file = cur_file - 1; err_file >= file_stack; err_file--) {
1554     fprintf(stderr, "included from %s: %d\n", err_file->file_name,
1555             err_file->lineno);
1556   }
1557 }
1558 
die(const char * fmt,...)1559 void die(const char *fmt, ...) {
1560   static int dying = 0;
1561   va_list args;
1562   DBUG_PRINT("enter", ("start_lineno: %d", start_lineno));
1563 
1564   // Protect against dying twice first time 'die' is called, try to
1565   // write log files second time, just exit.
1566   if (dying) cleanup_and_exit(1);
1567   dying = 1;
1568 
1569   // Print the error message
1570   fprintf(stderr, "mysqltest: ");
1571 
1572   if (start_lineno > 0) fprintf(stderr, "At line %u: ", start_lineno);
1573 
1574   if (fmt) {
1575     va_start(args, fmt);
1576     vfprintf(stderr, fmt, args);
1577     fprintf(stderr, "\n");
1578     va_end(args);
1579   } else
1580     fprintf(stderr, "Unknown error");
1581 
1582   // Print the file stack
1583   if (cur_file && cur_file != file_stack) {
1584     fprintf(stderr, "In included ");
1585     print_file_stack();
1586   }
1587 
1588   fflush(stderr);
1589 
1590   if (result_file_name) log_file.show_tail(opt_tail_lines);
1591 
1592   // Help debugging by displaying any warnings that might have
1593   // been produced prior to the error.
1594   if (cur_con && !cur_con->pending) show_warnings_before_error(&cur_con->mysql);
1595 
1596   cleanup_and_exit(1);
1597 }
1598 
abort_not_supported_test(const char * fmt,...)1599 void abort_not_supported_test(const char *fmt, ...) {
1600   va_list args;
1601   DBUG_TRACE;
1602 
1603   /* Print include filestack */
1604   fprintf(stderr, "The test '%s' is not supported by this installation\n",
1605           file_stack->file_name);
1606   fprintf(stderr, "Detected in ");
1607   print_file_stack();
1608 
1609   /* Print error message */
1610   va_start(args, fmt);
1611   if (fmt) {
1612     fprintf(stderr, "reason: ");
1613     vfprintf(stderr, fmt, args);
1614     fprintf(stderr, "\n");
1615     fflush(stderr);
1616   }
1617   va_end(args);
1618 
1619   cleanup_and_exit(62);
1620 }
1621 
verbose_msg(const char * fmt,...)1622 void verbose_msg(const char *fmt, ...) {
1623   va_list args;
1624   DBUG_TRACE;
1625   if (!verbose) return;
1626 
1627   va_start(args, fmt);
1628   fprintf(stderr, "mysqltest: ");
1629   if (cur_file && cur_file != file_stack)
1630     fprintf(stderr, "In included file \"%s\": ", cur_file->file_name);
1631   if (start_lineno != 0) fprintf(stderr, "At line %u: ", start_lineno);
1632   vfprintf(stderr, fmt, args);
1633   fprintf(stderr, "\n");
1634   va_end(args);
1635 }
1636 
log_msg(const char * fmt,...)1637 void log_msg(const char *fmt, ...) {
1638   va_list args;
1639   char buff[1024];
1640   size_t len;
1641   DBUG_TRACE;
1642 
1643   va_start(args, fmt);
1644   len = vsnprintf(buff, sizeof(buff) - 1, fmt, args);
1645   va_end(args);
1646 
1647   dynstr_append_mem(&ds_res, buff, len);
1648   dynstr_append(&ds_res, "\n");
1649 }
1650 
1651 /*
1652   Read a file and append it to ds
1653 
1654   SYNOPSIS
1655   cat_file
1656   ds - pointer to dynamic string where to add the files content
1657   filename - name of the file to read
1658 
1659 */
1660 
cat_file(DYNAMIC_STRING * ds,const char * filename)1661 static int cat_file(DYNAMIC_STRING *ds, const char *filename) {
1662   int fd;
1663   size_t len;
1664   char buff[512];
1665   bool dangling_cr = false;
1666 
1667   if ((fd = my_open(filename, O_RDONLY, MYF(0))) < 0) return 1;
1668 
1669   std::string file_content;
1670 
1671   while ((len = my_read(fd, (uchar *)&buff, sizeof(buff), MYF(0))) > 0) {
1672     char *p = buff, *start = buff;
1673     if (dangling_cr) {
1674       if (*p != '\n') file_content.append("\r");
1675       dangling_cr = false;
1676     }
1677     while (p < buff + len) {
1678       /* Convert cr/lf to lf */
1679       if (*p == '\r' && *(p + 1) && *(p + 1) == '\n') {
1680         /* Add fake newline instead of cr and output the line */
1681         *p = '\n';
1682         p++; /* Step past the "fake" newline */
1683         file_content.append(start, p - start);
1684         p++; /* Step past the "fake" newline */
1685         start = p;
1686       } else
1687         p++;
1688     }
1689     if (*(p - 1) == '\r' && len == 512) dangling_cr = true;
1690     size_t buf_len;
1691     /* Add any characters that might be left */
1692     if (dangling_cr)
1693       buf_len = p - start - 1;
1694     else
1695       buf_len = p - start;
1696     file_content.append(start, buf_len);
1697   }
1698 
1699   replace_dynstr_append(ds, file_content.c_str());
1700   my_close(fd, MYF(0));
1701 
1702   return 0;
1703 }
1704 
1705 /*
1706   Run the specified command with popen
1707 
1708   SYNOPSIS
1709   run_command
1710   cmd - command to execute(should be properly quoted
1711   ds_res- pointer to dynamic string where to store the result
1712 
1713 */
1714 
run_command(char * cmd,DYNAMIC_STRING * ds_res)1715 static int run_command(char *cmd, DYNAMIC_STRING *ds_res) {
1716   char buf[512] = {0};
1717   FILE *res_file;
1718   int error;
1719 
1720   if (!(res_file = popen(cmd, "r"))) die("popen(\"%s\", \"r\") failed", cmd);
1721 
1722   while (fgets(buf, sizeof(buf), res_file)) {
1723     DBUG_PRINT("info", ("buf: %s", buf));
1724     if (ds_res) {
1725       /* Save the output of this command in the supplied string */
1726       dynstr_append(ds_res, buf);
1727     } else {
1728       /* Print it directly on screen */
1729       fprintf(stdout, "%s", buf);
1730     }
1731   }
1732 
1733   error = pclose(res_file);
1734   return WEXITSTATUS(error);
1735 }
1736 
1737 /*
1738   Run the specified tool with variable number of arguments
1739 
1740   SYNOPSIS
1741   run_tool
1742   tool_path - the name of the tool to run
1743   ds_res - pointer to dynamic string where to store the result
1744   ... - variable number of arguments that will be properly
1745         quoted and appended after the tool's name
1746 
1747 */
1748 
run_tool(const char * tool_path,DYNAMIC_STRING * ds_res,...)1749 static int run_tool(const char *tool_path, DYNAMIC_STRING *ds_res, ...) {
1750   int ret;
1751   const char *arg;
1752   va_list args;
1753   DYNAMIC_STRING ds_cmdline;
1754 
1755   DBUG_TRACE;
1756   DBUG_PRINT("enter", ("tool_path: %s", tool_path));
1757 
1758   if (init_dynamic_string(&ds_cmdline, IF_WIN("\"", ""), FN_REFLEN, FN_REFLEN))
1759     die("Out of memory");
1760 
1761   dynstr_append_os_quoted(&ds_cmdline, tool_path, NullS);
1762   dynstr_append(&ds_cmdline, " ");
1763 
1764   va_start(args, ds_res);
1765 
1766   while ((arg = va_arg(args, char *))) {
1767     /* Options should be os quoted */
1768     if (std::strncmp(arg, "--", 2) == 0)
1769       dynstr_append_os_quoted(&ds_cmdline, arg, NullS);
1770     else
1771       dynstr_append(&ds_cmdline, arg);
1772     dynstr_append(&ds_cmdline, " ");
1773   }
1774 
1775   va_end(args);
1776 
1777 #ifdef _WIN32
1778   dynstr_append(&ds_cmdline, "\"");
1779 #endif
1780 
1781   DBUG_PRINT("info", ("Running: %s", ds_cmdline.str));
1782   ret = run_command(ds_cmdline.str, ds_res);
1783   DBUG_PRINT("exit", ("ret: %d", ret));
1784   dynstr_free(&ds_cmdline);
1785   return ret;
1786 }
1787 
1788 /*
1789   Test if diff is present.  This is needed on Windows systems
1790   as the OS returns 1 whether diff is successful or if it is
1791   not present.
1792 
1793   We run diff -v and look for output in stdout.
1794   We don't redirect stderr to stdout to make for a simplified check
1795   Windows will output '"diff"' is not recognized... to stderr if it is
1796   not present.
1797 */
1798 
1799 #ifdef _WIN32
1800 
diff_check(const char * diff_name)1801 static int diff_check(const char *diff_name) {
1802   FILE *res_file;
1803   char buf[128];
1804   int have_diff = 0;
1805 
1806   snprintf(buf, sizeof(buf), "%s -v", diff_name);
1807 
1808   if (!(res_file = popen(buf, "r"))) die("popen(\"%s\", \"r\") failed", buf);
1809 
1810   /* if diff is not present, nothing will be in stdout to increment have_diff */
1811   if (fgets(buf, sizeof(buf), res_file)) have_diff = 1;
1812 
1813   pclose(res_file);
1814 
1815   return have_diff;
1816 }
1817 
1818 #endif
1819 
1820 /// Show the diff of two files using the systems builtin diff
1821 /// command. If no such diff command exist, just dump the content
1822 /// of the two files and inform about how to get "diff"
1823 ///
1824 /// @param ds        Pointer to dynamic string where to add the
1825 ///                  diff. If NULL, print the diff to stderr.
1826 /// @param filename1 Name of the first file
1827 /// @param filename2 Name of the second file
show_diff(DYNAMIC_STRING * ds,const char * filename1,const char * filename2)1828 static void show_diff(DYNAMIC_STRING *ds, const char *filename1,
1829                       const char *filename2) {
1830   DYNAMIC_STRING ds_diff;
1831   if (init_dynamic_string(&ds_diff, "", 256, 256)) die("Out of memory");
1832 
1833   const char *diff_name = nullptr;
1834 
1835   // Determine if we have diff on Windows. If yes, then needs special
1836   // processing due to return values on that OS. This test is only done
1837   // on Windows since it's only needed there in order to correctly
1838   // detect non-availibility of 'diff', and the way it's implemented
1839   // does not work with default 'diff' on Solaris.
1840 #ifdef _WIN32
1841   if (diff_check("diff"))
1842     diff_name = "diff";
1843   else if (diff_check("mtrdiff"))
1844     diff_name = "mtrdiff";
1845   else
1846     diff_name = 0;
1847 #else
1848   // Otherwise always assume it's called diff
1849   diff_name = "diff";
1850 #endif
1851 
1852   if (diff_name) {
1853     int exit_code = 0;
1854     // Use 'diff --color=always' to print the colored diff if it is enabled
1855     if (opt_colored_diff) {
1856       // Most "diff" tools return '> 1' if error
1857       exit_code = run_tool(diff_name, &ds_diff, "-u --color='always'",
1858                            filename1, filename2, "2>&1", NULL);
1859 
1860       if (exit_code > 1)
1861         die("Option '--colored-diff' is not supported on this machine. "
1862             "To get colored diff output, install GNU diffutils version "
1863             "3.4 or higher.");
1864     } else {
1865       // Colored diff is disabled, clear the diff string and try unified
1866       // diff with "diff -u".
1867       dynstr_set(&ds_diff, "");
1868       exit_code = run_tool(diff_name, &ds_diff, "-u", filename1, filename2,
1869                            "2>&1", NULL);
1870 
1871       if (exit_code > 1) {
1872         // Clear the diff string and fallback to context diff with "diff -c"
1873         dynstr_set(&ds_diff, "");
1874         exit_code = run_tool(diff_name, &ds_diff, "-c", filename1, filename2,
1875                              "2>&1", NULL);
1876 
1877         if (exit_code > 1) {
1878           // Clear the diff string and fallback to simple diff with "diff"
1879           dynstr_set(&ds_diff, "");
1880           exit_code =
1881               run_tool(diff_name, &ds_diff, filename1, filename2, "2>&1", NULL);
1882           if (exit_code > 1) diff_name = nullptr;
1883         }
1884       }
1885     }
1886   }
1887 
1888   if (!diff_name) {
1889     // Fallback to dump both files to result file and inform
1890     // about installing "diff".
1891     dynstr_append(&ds_diff, "\n");
1892     dynstr_append(
1893         &ds_diff,
1894         "\n"
1895         "The two files differ but it was not possible to execute 'diff' in\n"
1896         "order to show only the difference. Instead the whole content of the\n"
1897         "two files was shown for you to diff manually.\n\n"
1898         "To get a better report you should install 'diff' on your system, "
1899         "which you\n"
1900         "for example can get from "
1901         "http://www.gnu.org/software/diffutils/diffutils.html\n"
1902 #ifdef _WIN32
1903         "or http://gnuwin32.sourceforge.net/packages/diffutils.htm\n"
1904 #endif
1905         "\n");
1906 
1907     dynstr_append(&ds_diff, " --- ");
1908     dynstr_append(&ds_diff, filename1);
1909     dynstr_append(&ds_diff, " >>>\n");
1910     cat_file(&ds_diff, filename1);
1911     dynstr_append(&ds_diff, "<<<\n --- ");
1912     dynstr_append(&ds_diff, filename1);
1913     dynstr_append(&ds_diff, " >>>\n");
1914     cat_file(&ds_diff, filename2);
1915     dynstr_append(&ds_diff, "<<<<\n");
1916   }
1917 
1918   if (ds)
1919     // Add the diff to output
1920     dynstr_append_mem(ds, ds_diff.str, ds_diff.length);
1921   else
1922     // Print diff directly to stderr
1923     fprintf(stderr, "%s\n", ds_diff.str);
1924 
1925   dynstr_free(&ds_diff);
1926 }
1927 
1928 enum compare_files_result_enum {
1929   RESULT_OK = 0,
1930   RESULT_CONTENT_MISMATCH = 1,
1931   RESULT_LENGTH_MISMATCH = 2
1932 };
1933 
1934 /*
1935   Compare two files, given a fd to the first file and
1936   name of the second file
1937 
1938   SYNOPSIS
1939   compare_files2
1940   fd - Open file descriptor of the first file
1941   filename2 - Name of second file
1942 
1943   RETURN VALUES
1944   According to the values in "compare_files_result_enum"
1945 
1946 */
1947 
compare_files2(File fd,const char * filename2)1948 static int compare_files2(File fd, const char *filename2) {
1949   int error = RESULT_OK;
1950   File fd2;
1951   size_t len, len2;
1952   char buff[512], buff2[512];
1953 
1954   if ((fd2 = my_open(filename2, O_RDONLY, MYF(0))) < 0) {
1955     my_close(fd, MYF(0));
1956     die("Failed to open second file: '%s'", filename2);
1957   }
1958   while ((len = my_read(fd, (uchar *)&buff, sizeof(buff), MYF(0))) > 0) {
1959     if ((len2 = my_read(fd2, (uchar *)&buff2, sizeof(buff2), MYF(0))) < len) {
1960       /* File 2 was smaller */
1961       error = RESULT_LENGTH_MISMATCH;
1962       break;
1963     }
1964     if (len2 > len) {
1965       /* File 1 was smaller */
1966       error = RESULT_LENGTH_MISMATCH;
1967       break;
1968     }
1969     if ((memcmp(buff, buff2, len))) {
1970       /* Content of this part differed */
1971       error = RESULT_CONTENT_MISMATCH;
1972       break;
1973     }
1974   }
1975   if (!error && my_read(fd2, (uchar *)&buff2, sizeof(buff2), MYF(0)) > 0) {
1976     /* File 1 was smaller */
1977     error = RESULT_LENGTH_MISMATCH;
1978   }
1979 
1980   my_close(fd2, MYF(0));
1981 
1982   return error;
1983 }
1984 
1985 /*
1986   Compare two files, given their filenames
1987 
1988   SYNOPSIS
1989   compare_files
1990   filename1 - Name of first file
1991   filename2 - Name of second file
1992 
1993   RETURN VALUES
1994   See 'compare_files2'
1995 
1996 */
1997 
compare_files(const char * filename1,const char * filename2)1998 static int compare_files(const char *filename1, const char *filename2) {
1999   File fd;
2000   int error;
2001 
2002   if ((fd = my_open(filename1, O_RDONLY, MYF(0))) < 0)
2003     die("Failed to open first file: '%s'", filename1);
2004 
2005   error = compare_files2(fd, filename2);
2006 
2007   my_close(fd, MYF(0));
2008 
2009   return error;
2010 }
2011 
2012 /*
2013   Check the content of log against result file
2014 
2015   SYNOPSIS
2016   check_result
2017 
2018   RETURN VALUES
2019   error - the function will not return
2020 
2021 */
2022 
check_result()2023 static void check_result() {
2024   const char *mess = "Result content mismatch\n";
2025 
2026   DBUG_TRACE;
2027   DBUG_ASSERT(result_file_name);
2028   DBUG_PRINT("enter", ("result_file_name: %s", result_file_name));
2029 
2030   /*
2031     Removing the unnecessary warning messages generated
2032     on GCOV platform.
2033   */
2034 #ifdef HAVE_GCOV
2035   char cmd[FN_REFLEN];
2036   strcpy(cmd, "sed -i '/gcda:Merge mismatch for function/d' ");
2037   std::strcat(cmd, log_file.file_name());
2038   system(cmd);
2039 #endif
2040 
2041   switch (compare_files(log_file.file_name(), result_file_name)) {
2042     case RESULT_OK:
2043       break; /* ok */
2044     case RESULT_LENGTH_MISMATCH:
2045       mess = "Result length mismatch\n";
2046       /* Fallthrough */
2047     case RESULT_CONTENT_MISMATCH: {
2048       /*
2049         Result mismatched, dump results to .reject file
2050         and then show the diff
2051       */
2052       char reject_file[FN_REFLEN];
2053       size_t reject_length;
2054       dirname_part(reject_file, result_file_name, &reject_length);
2055 
2056       /* Put reject file in opt_logdir */
2057       fn_format(reject_file, result_file_name, opt_logdir, ".reject",
2058                 MY_REPLACE_DIR | MY_REPLACE_EXT);
2059 
2060       if (my_copy(log_file.file_name(), reject_file, MYF(0)) != 0)
2061         die("Failed to copy '%s' to '%s', errno: %d", log_file.file_name(),
2062             reject_file, errno);
2063 
2064       show_diff(nullptr, result_file_name, reject_file);
2065       die("%s", mess);
2066       break;
2067     }
2068     default: /* impossible */
2069       die("Unknown error code from dyn_string_cmp()");
2070   }
2071 }
2072 
2073 /*
2074    Remove surrounding chars from string
2075 
2076    Return 1 if first character is found but not last
2077 */
strip_surrounding(char * str,char c1,char c2)2078 static int strip_surrounding(char *str, char c1, char c2) {
2079   char *ptr = str;
2080 
2081   /* Check if the first non space character is c1 */
2082   while (*ptr && my_isspace(charset_info, *ptr)) ptr++;
2083   if (*ptr == c1) {
2084     /* Replace it with a space */
2085     *ptr = ' ';
2086 
2087     /* Last non space charecter should be c2 */
2088     ptr = strend(str) - 1;
2089     while (*ptr && my_isspace(charset_info, *ptr)) ptr--;
2090     if (*ptr == c2) {
2091       /* Replace it with \0 */
2092       *ptr = 0;
2093     } else {
2094       /* Mismatch detected */
2095       return 1;
2096     }
2097   }
2098   return 0;
2099 }
2100 
strip_parentheses(struct st_command * command)2101 static void strip_parentheses(struct st_command *command) {
2102   if (strip_surrounding(command->first_argument, '(', ')'))
2103     die("%.*s - argument list started with '%c' must be ended with '%c'",
2104         static_cast<int>(command->first_word_len), command->query, '(', ')');
2105 }
2106 
operator ()(VAR * var) const2107 void var_free::operator()(VAR *var) const {
2108   my_free(var->str_val);
2109   if (var->alloced) my_free(var);
2110 }
2111 
var_check_int(VAR * v)2112 static void var_check_int(VAR *v) {
2113   char *endptr;
2114   char *str = v->str_val;
2115 
2116   /* Initially assume not a number */
2117   v->int_val = 0;
2118   v->is_int = false;
2119   v->int_dirty = false;
2120   if (!str) return;
2121 
2122   v->int_val = (int)strtol(str, &endptr, 10);
2123   /* It is an int if strtol consumed something up to end/space/tab */
2124   if (endptr > str && (!*endptr || *endptr == ' ' || *endptr == '\t'))
2125     v->is_int = true;
2126 }
2127 
var_init(VAR * v,const char * name,size_t name_len,const char * val,size_t val_len)2128 VAR *var_init(VAR *v, const char *name, size_t name_len, const char *val,
2129               size_t val_len) {
2130   size_t val_alloc_len;
2131   VAR *tmp_var;
2132   if (!name_len && name) name_len = std::strlen(name);
2133   if (!val_len && val) val_len = std::strlen(val);
2134   if (!val) val_len = 0;
2135   val_alloc_len = val_len + 16; /* room to grow */
2136   if (!(tmp_var = v) && !(tmp_var = (VAR *)my_malloc(
2137                               PSI_NOT_INSTRUMENTED,
2138                               sizeof(*tmp_var) + name_len + 2, MYF(MY_WME))))
2139     die("Out of memory");
2140 
2141   if (name != nullptr) {
2142     tmp_var->name = reinterpret_cast<char *>(tmp_var) + sizeof(*tmp_var);
2143     memcpy(tmp_var->name, name, name_len);
2144     tmp_var->name[name_len] = 0;
2145   } else
2146     tmp_var->name = nullptr;
2147 
2148   tmp_var->alloced = (v == nullptr);
2149 
2150   if (!(tmp_var->str_val = (char *)my_malloc(PSI_NOT_INSTRUMENTED,
2151                                              val_alloc_len + 1, MYF(MY_WME))))
2152     die("Out of memory");
2153 
2154   if (val) memcpy(tmp_var->str_val, val, val_len);
2155   tmp_var->str_val[val_len] = 0;
2156 
2157   var_check_int(tmp_var);
2158   tmp_var->name_len = name_len;
2159   tmp_var->str_val_len = val_len;
2160   tmp_var->alloced_len = val_alloc_len;
2161   return tmp_var;
2162 }
2163 
var_from_env(const char * name,const char * def_val)2164 VAR *var_from_env(const char *name, const char *def_val) {
2165   const char *tmp;
2166   VAR *v;
2167   if (!(tmp = getenv(name))) tmp = def_val;
2168 
2169   v = var_init(nullptr, name, std::strlen(name), tmp, std::strlen(tmp));
2170   var_hash->emplace(name, std::unique_ptr<VAR, var_free>(v));
2171   return v;
2172 }
2173 
var_get(const char * var_name,const char ** var_name_end,bool raw,bool ignore_not_existing)2174 VAR *var_get(const char *var_name, const char **var_name_end, bool raw,
2175              bool ignore_not_existing) {
2176   int digit;
2177   VAR *v;
2178   DBUG_TRACE;
2179   DBUG_PRINT("enter", ("var_name: %s", var_name));
2180 
2181   if (*var_name != '$') goto err;
2182   digit = *++var_name - '0';
2183   if (digit < 0 || digit >= 10) {
2184     const char *save_var_name = var_name, *end;
2185     uint length;
2186     end = (var_name_end) ? *var_name_end : nullptr;
2187     while (my_isvar(charset_info, *var_name) && var_name != end) var_name++;
2188     if (var_name == save_var_name) {
2189       if (ignore_not_existing) return nullptr;
2190       die("Empty variable");
2191     }
2192     length = (uint)(var_name - save_var_name);
2193     if (length >= MAX_VAR_NAME_LENGTH)
2194       die("Too long variable name: %s", save_var_name);
2195 
2196     if (!(v = find_or_nullptr(*var_hash, std::string(save_var_name, length)))) {
2197       char buff[MAX_VAR_NAME_LENGTH + 1];
2198       strmake(buff, save_var_name, length);
2199       v = var_from_env(buff, "");
2200     }
2201     var_name--; /* Point at last character */
2202   } else
2203     v = var_reg + digit;
2204 
2205   if (!raw && v->int_dirty) {
2206     sprintf(v->str_val, "%d", v->int_val);
2207     v->int_dirty = false;
2208     v->str_val_len = std::strlen(v->str_val);
2209   }
2210   if (var_name_end) *var_name_end = var_name;
2211   return v;
2212 err:
2213   if (var_name_end) *var_name_end = nullptr;
2214   die("Unsupported variable name: %s", var_name);
2215   return nullptr;
2216 }
2217 
var_obtain(const char * name,int len)2218 static VAR *var_obtain(const char *name, int len) {
2219   VAR *v = find_or_nullptr(*var_hash, std::string(name, len));
2220   if (v == nullptr) {
2221     v = var_init(nullptr, name, len, "", 0);
2222     var_hash->emplace(std::string(name, len),
2223                       std::unique_ptr<VAR, var_free>(v));
2224   }
2225   return v;
2226 }
2227 
2228 /*
2229   - if variable starts with a $ it is regarded as a local test varable
2230   - if not it is treated as a environment variable, and the corresponding
2231   environment variable will be updated
2232 */
2233 
var_set(const char * var_name,const char * var_name_end,const char * var_val,const char * var_val_end)2234 void var_set(const char *var_name, const char *var_name_end,
2235              const char *var_val, const char *var_val_end) {
2236   int digit, env_var = 0;
2237   VAR *v;
2238   DBUG_TRACE;
2239   DBUG_PRINT("enter", ("var_name: '%.*s' = '%.*s' (length: %d)",
2240                        (int)(var_name_end - var_name), var_name,
2241                        (int)(var_val_end - var_val), var_val,
2242                        (int)(var_val_end - var_val)));
2243 
2244   if (*var_name != '$')
2245     env_var = 1;
2246   else
2247     var_name++;
2248 
2249   digit = *var_name - '0';
2250   if (!(digit < 10 && digit >= 0)) {
2251     v = var_obtain(var_name, (uint)(var_name_end - var_name));
2252   } else
2253     v = var_reg + digit;
2254 
2255   eval_expr(v, var_val, (const char **)&var_val_end);
2256 
2257   if (env_var) {
2258     if (v->int_dirty) {
2259       sprintf(v->str_val, "%d", v->int_val);
2260       v->int_dirty = false;
2261       v->str_val_len = std::strlen(v->str_val);
2262     }
2263     /* setenv() expects \0-terminated strings */
2264     DBUG_ASSERT(v->name[v->name_len] == 0);
2265     setenv(v->name, v->str_val, 1);
2266   }
2267 }
2268 
var_set_string(const char * name,const char * value)2269 static void var_set_string(const char *name, const char *value) {
2270   var_set(name, name + std::strlen(name), value, value + std::strlen(value));
2271 }
2272 
var_set_int(const char * name,int value)2273 static void var_set_int(const char *name, int value) {
2274   char buf[21];
2275   snprintf(buf, sizeof(buf), "%d", value);
2276   var_set_string(name, buf);
2277 }
2278 
2279 /*
2280   Store an integer (typically the returncode of the last SQL)
2281   statement in the mysqltest builtin variable $mysql_errno
2282 */
2283 
var_set_errno(int sql_errno)2284 static void var_set_errno(int sql_errno) {
2285   var_set_int("$mysql_errno", sql_errno);
2286   var_set_string("$mysql_errname", get_errname_from_code(sql_errno));
2287 }
2288 
2289 /// Variable '$DISABLED_WARNINGS_LIST' contains comma separated list
2290 /// of disabled warnings and variable '$ENABLED_WARNINGS_LIST' contains
2291 /// comma separated list of enabled warnings.
2292 ///
2293 /// Update the value of these two variables with the latest list of
2294 /// disabled and enabled warnings. The value of these variables will be
2295 /// empty if there are no disabled or enabled warnings.
2296 ///
2297 /// These variables will  always contain the latest list of disabled
2298 /// and enabled warnings, and can be referenced inside a test or inside
2299 /// a test utility file to access the current list of disabled or
2300 /// enabled warnings.
update_disabled_enabled_warnings_list_var()2301 static void update_disabled_enabled_warnings_list_var() {
2302   // Update '$DISABLED_WARNINGS_LIST' variable
2303   std::string disabled_warning_list = disabled_warnings->warnings_list();
2304   var_set_string("DISABLED_WARNINGS_LIST", disabled_warning_list.c_str());
2305 
2306   // Update '$ENABLED_WARNINGS_LIST' variable
2307   std::string enabled_warning_list = enabled_warnings->warnings_list();
2308   var_set_string("ENABLED_WARNINGS_LIST", enabled_warning_list.c_str());
2309 }
2310 
2311 /// Set a property value to either 0 or 1 for a disable_X or a enable_X
2312 /// command, and the new value set will be applicable for next statement
2313 /// only. After that, property value will be reset back to the old value.
2314 ///
2315 /// @param property Enum value representing a Property
2316 /// @param value    Value for the property, either 0 or 1
set_once_property(enum_prop property,bool value)2317 static void set_once_property(enum_prop property, bool value) {
2318   Property &prop = prop_list[property];
2319   prop.set = true;
2320   prop.old = *prop.var;
2321   *prop.var = value;
2322   var_set_int(prop.env_name, (value != prop.reverse));
2323   once_property = true;
2324 }
2325 
2326 /// Set a property value to either 0 or 1 for a disable_X or a enable_X
2327 /// command.
2328 ///
2329 /// @param command  Pointer to the st_command structure which holds the
2330 ///                 arguments and information for the command.
2331 /// @param property Enum value representing a Property
2332 /// @param value    Value for the property, either 0 or 1
set_property(st_command * command,enum_prop property,bool value)2333 static void set_property(st_command *command, enum_prop property, bool value) {
2334   char *arg = command->first_argument;
2335 
2336   // If "ONCE" argument is specified, the new value for the property is
2337   // set for next statement only. After that, property value will be
2338   // reset back to the old value.
2339   if (arg) {
2340     // "ONCE" is the second argument to 'disable_warnings/enable_warnings'
2341     // command.
2342     if (((command->type == Q_DISABLE_WARNINGS ||
2343           command->type == Q_ENABLE_WARNINGS) &&
2344          std::strstr(arg, "ONCE") != nullptr) ||
2345         !std::strcmp(arg, "ONCE")) {
2346       command->last_argument = arg + std::strlen(arg);
2347       set_once_property(property, value);
2348       return;
2349     }
2350   }
2351 
2352   Property &prop = prop_list[property];
2353   prop.set = false;
2354   *prop.var = value;
2355   var_set_int(prop.env_name, (value != prop.reverse));
2356 }
2357 
2358 /// Reset property value to the old value for all properties which are
2359 /// set for the next statement only, i.e properties specified using
2360 /// keyword "ONCE" argument.
revert_properties()2361 void revert_properties() {
2362   if (!once_property) return;
2363 
2364   for (std::size_t i = 0; i < P_MAX; i++) {
2365     Property &prop = prop_list[i];
2366     if (prop.set) {
2367       *prop.var = prop.old;
2368       prop.set = false;
2369       var_set_int(prop.env_name, (prop.old != prop.reverse));
2370     }
2371   }
2372 
2373   // Remove warnings which are disabled or enabled for the next
2374   // statement only.
2375   disabled_warnings->update_list();
2376   enabled_warnings->update_list();
2377 
2378   // Update $DISABLED_WARNINGS_LIST and $ENABLED_WARNINGS_LIST
2379   // variable value.
2380   update_disabled_enabled_warnings_list_var();
2381 
2382   once_property = false;
2383 }
2384 
2385 /*
2386   Set variable from the result of a query
2387 
2388   SYNOPSIS
2389   var_query_set()
2390   var	        variable to set from query
2391   query       start of query string to execute
2392   query_end   end of the query string to execute
2393 
2394 
2395   DESCRIPTION
2396   let @<var_name> = `<query>`
2397 
2398   Execute the query and assign the first row of result to var as
2399   a tab separated strings
2400 
2401   Also assign each column of the result set to
2402   variable "$<var_name>_<column_name>"
2403   Thus the tab separated output can be read from $<var_name> and
2404   and each individual column can be read as $<var_name>_<col_name>
2405 
2406 */
2407 
var_query_set(VAR * var,const char * query,const char ** query_end)2408 static void var_query_set(VAR *var, const char *query, const char **query_end) {
2409   const char *end =
2410       (query_end && *query_end) ? *query_end : query + std::strlen(query);
2411   MYSQL_RES *res = nullptr;
2412   MYSQL_ROW row;
2413   MYSQL *mysql = &cur_con->mysql;
2414   DYNAMIC_STRING ds_query;
2415   DBUG_TRACE;
2416 
2417   /* Only white space or ) allowed past ending ` */
2418   while (end > query && *end != '`') {
2419     if (*end && (*end != ' ' && *end != '\t' && *end != '\n' && *end != ')'))
2420       die("Spurious text after `query` expression");
2421     --end;
2422   }
2423 
2424   if (query == end) die("Syntax error in query, missing '`'");
2425   ++query;
2426 
2427   /* Eval the query, thus replacing all environment variables */
2428   init_dynamic_string(&ds_query, nullptr, (end - query) + 32, 256);
2429   do_eval(&ds_query, query, end, false);
2430 
2431   if (mysql_real_query_wrapper(mysql, ds_query.str,
2432                                static_cast<ulong>(ds_query.length))) {
2433     handle_error(curr_command, mysql_errno(mysql), mysql_error(mysql),
2434                  mysql_sqlstate(mysql), &ds_res);
2435     /* If error was acceptable, return empty string */
2436     dynstr_free(&ds_query);
2437     eval_expr(var, "", nullptr);
2438     return;
2439   }
2440 
2441   if (!(res = mysql_store_result_wrapper(mysql)))
2442     die("Query '%s' didn't return a result set", ds_query.str);
2443   dynstr_free(&ds_query);
2444 
2445   if ((row = mysql_fetch_row_wrapper(res)) && row[0]) {
2446     /*
2447       Concatenate all fields in the first row with tab in between
2448       and assign that string to the $variable
2449     */
2450     DYNAMIC_STRING result;
2451     uint i;
2452     ulong *lengths;
2453 
2454     init_dynamic_string(&result, "", 512, 512);
2455     lengths = mysql_fetch_lengths(res);
2456     for (i = 0; i < mysql_num_fields(res); i++) {
2457       if (row[i]) {
2458         /* Add column to tab separated string */
2459         char *val = row[i];
2460         size_t len = lengths[i];
2461 
2462         if (glob_replace_regex) {
2463           size_t orig_len = len;
2464           // Regex replace
2465           if (!multi_reg_replace(glob_replace_regex, (char *)val, &len)) {
2466             val = glob_replace_regex->buf;
2467           } else {
2468             len = orig_len;
2469           }
2470         }
2471         DYNAMIC_STRING ds_temp;
2472         init_dynamic_string(&ds_temp, "", 512, 512);
2473 
2474         /* Store result from replace_result in ds_temp */
2475         if (glob_replace)
2476           replace_strings_append(glob_replace, &ds_temp, val, len);
2477 
2478         /*
2479           Call the replace_numeric_round function with the specified
2480           precision. It may be used along with replace_result, so use the
2481           output from replace_result as the input for replace_numeric_round.
2482 */
2483         if (glob_replace_numeric_round >= 0) {
2484           /* Copy the result from replace_result if it was used, into buffer */
2485           if (ds_temp.length > 0) {
2486             char buffer[512];
2487             strcpy(buffer, ds_temp.str);
2488             dynstr_free(&ds_temp);
2489             init_dynamic_string(&ds_temp, "", 512, 512);
2490             replace_numeric_round_append(glob_replace_numeric_round, &ds_temp,
2491                                          buffer, std::strlen(buffer));
2492           } else
2493             replace_numeric_round_append(glob_replace_numeric_round, &ds_temp,
2494                                          val, len);
2495         }
2496 
2497         if (!glob_replace && glob_replace_numeric_round < 0)
2498           dynstr_append_mem(&result, val, len);
2499         else
2500           dynstr_append_mem(&result, ds_temp.str, std::strlen(ds_temp.str));
2501         dynstr_free(&ds_temp);
2502       }
2503       dynstr_append_mem(&result, "\t", 1);
2504     }
2505     end = result.str + result.length - 1;
2506     /* Evaluation should not recurse via backtick */
2507     eval_expr(var, result.str, &end, false, false);
2508     dynstr_free(&result);
2509   } else
2510     eval_expr(var, "", nullptr);
2511 
2512   mysql_free_result_wrapper(res);
2513 }
2514 
set_result_format_version(ulong new_version)2515 static void set_result_format_version(ulong new_version) {
2516   switch (new_version) {
2517     case 1:
2518       /* The first format */
2519       break;
2520     case 2:
2521       /* New format that also writes comments and empty lines
2522          from test file to result */
2523       break;
2524     default:
2525       die("Version format %lu has not yet been implemented", new_version);
2526       break;
2527   }
2528   opt_result_format_version = new_version;
2529 }
2530 
2531 /*
2532   Set the result format version to use when generating
2533   the .result file
2534 */
2535 
do_result_format_version(struct st_command * command)2536 static void do_result_format_version(struct st_command *command) {
2537   long version;
2538   static DYNAMIC_STRING ds_version;
2539   const struct command_arg result_format_args[] = {
2540       {"version", ARG_STRING, true, &ds_version, "Version to use"}};
2541 
2542   DBUG_TRACE;
2543 
2544   check_command_args(command, command->first_argument, result_format_args,
2545                      sizeof(result_format_args) / sizeof(struct command_arg),
2546                      ',');
2547 
2548   /* Convert version  number to int */
2549   if (!str2int(ds_version.str, 10, (long)0, (long)INT_MAX, &version))
2550     die("Invalid version number: '%s'", ds_version.str);
2551 
2552   set_result_format_version(version);
2553 
2554   dynstr_append(&ds_res, "result_format: ");
2555   dynstr_append_mem(&ds_res, ds_version.str, ds_version.length);
2556   dynstr_append(&ds_res, "\n");
2557   dynstr_free(&ds_version);
2558 }
2559 
2560 /// Convert between error numbers and error names/strings.
2561 ///
2562 /// @code
2563 /// let $var = convert_error(ER_UNKNOWN_ERROR);
2564 /// let $var = convert_error(1234);
2565 /// @endcode
2566 ///
2567 /// The variable '$var' will be populated with error number if the
2568 /// argument is string. The variable var will be populated with error
2569 /// string if the argument is number.
2570 ///
2571 /// @param command Pointer to the st_command structure which holds the
2572 ///                arguments and information for the command.
2573 /// @param var     Pointer to VAR object containing a variable
2574 ///                information.
var_set_convert_error(struct st_command * command,VAR * var)2575 static void var_set_convert_error(struct st_command *command, VAR *var) {
2576   // The command->query contains the statement convert_error(1234)
2577   char *first = std::strchr(command->query, '(') + 1;
2578   char *last = std::strchr(command->query, ')');
2579 
2580   // Denoting an empty string
2581   if (last == first) {
2582     eval_expr(var, "0", nullptr);
2583     return;
2584   }
2585 
2586   // If the string is an error string , it starts with 'E' as is the norm
2587   if (*first == 'E') {
2588     std::string error_name(first, int(last - first));
2589     int error = get_errcode_from_name(error_name);
2590     if (error == -1) die("Unknown SQL error name '%s'.", error_name.c_str());
2591     char str[100];
2592     std::sprintf(str, "%d", error);
2593     eval_expr(var, str, nullptr);
2594   } else if (my_isdigit(charset_info, *first)) {
2595     // Error number argument
2596     long int err = std::strtol(first, &last, 0);
2597     const char *err_name = get_errname_from_code(err);
2598     eval_expr(var, err_name, nullptr);
2599   } else {
2600     die("Invalid error in input");
2601   }
2602 }
2603 
2604 /*
2605   Set variable from the result of a field in a query
2606 
2607   This function is useful when checking for a certain value
2608   in the output from a query that can't be restricted to only
2609   return some values. A very good example of that is most SHOW
2610   commands.
2611 
2612   SYNOPSIS
2613   var_set_query_get_value()
2614 
2615   DESCRIPTION
2616   let $variable= query_get_value(<query to run>,<column name>,<row no>);
2617 
2618   <query to run> -    The query that should be sent to the server
2619   <column name> -     Name of the column that holds the field be compared
2620                       against the expected value
2621   <row no> -          Number of the row that holds the field to be
2622                       compared against the expected value
2623 
2624 */
2625 
var_set_query_get_value(struct st_command * command,VAR * var)2626 static void var_set_query_get_value(struct st_command *command, VAR *var) {
2627   long row_no;
2628   int col_no = -1;
2629   MYSQL_RES *res = nullptr;
2630   MYSQL *mysql = &cur_con->mysql;
2631 
2632   static DYNAMIC_STRING ds_query;
2633   static DYNAMIC_STRING ds_col;
2634   static DYNAMIC_STRING ds_row;
2635   const struct command_arg query_get_value_args[] = {
2636       {"query", ARG_STRING, true, &ds_query, "Query to run"},
2637       {"column name", ARG_STRING, true, &ds_col, "Name of column"},
2638       {"row number", ARG_STRING, true, &ds_row, "Number for row"}};
2639 
2640   DBUG_TRACE;
2641 
2642   strip_parentheses(command);
2643   DBUG_PRINT("info", ("query: %s", command->query));
2644   check_command_args(command, command->first_argument, query_get_value_args,
2645                      sizeof(query_get_value_args) / sizeof(struct command_arg),
2646                      ',');
2647 
2648   DBUG_PRINT("info", ("query: %s", ds_query.str));
2649   DBUG_PRINT("info", ("col: %s", ds_col.str));
2650 
2651   /* Convert row number to int */
2652   if (!str2int(ds_row.str, 10, (long)0, (long)INT_MAX, &row_no))
2653     die("Invalid row number: '%s'", ds_row.str);
2654   DBUG_PRINT("info", ("row: %s, row_no: %ld", ds_row.str, row_no));
2655   dynstr_free(&ds_row);
2656 
2657   /* Remove any surrounding "'s from the query - if there is any */
2658   if (strip_surrounding(ds_query.str, '"', '"'))
2659     die("Mismatched \"'s around query '%s'", ds_query.str);
2660 
2661   /* Run the query */
2662   if (mysql_real_query_wrapper(mysql, ds_query.str,
2663                                static_cast<ulong>(ds_query.length))) {
2664     handle_error(curr_command, mysql_errno(mysql), mysql_error(mysql),
2665                  mysql_sqlstate(mysql), &ds_res);
2666     /* If error was acceptable, return empty string */
2667     dynstr_free(&ds_query);
2668     dynstr_free(&ds_col);
2669     eval_expr(var, "", nullptr);
2670     return;
2671   }
2672 
2673   if (!(res = mysql_store_result_wrapper(mysql)))
2674     die("Query '%s' didn't return a result set", ds_query.str);
2675 
2676   {
2677     /* Find column number from the given column name */
2678     uint i;
2679     uint num_fields = mysql_num_fields(res);
2680     MYSQL_FIELD *fields = mysql_fetch_fields(res);
2681 
2682     for (i = 0; i < num_fields; i++) {
2683       if (std::strcmp(fields[i].name, ds_col.str) == 0 &&
2684           std::strlen(fields[i].name) == ds_col.length) {
2685         col_no = i;
2686         break;
2687       }
2688     }
2689     if (col_no == -1) {
2690       mysql_free_result_wrapper(res);
2691       die("Could not find column '%s' in the result of '%s'", ds_col.str,
2692           ds_query.str);
2693     }
2694     DBUG_PRINT("info", ("Found column %d with name '%s'", i, fields[i].name));
2695   }
2696   dynstr_free(&ds_col);
2697 
2698   {
2699     /* Get the value */
2700     MYSQL_ROW row;
2701     long rows = 0;
2702     const char *value = "No such row";
2703 
2704     while ((row = mysql_fetch_row_wrapper(res))) {
2705       if (++rows == row_no) {
2706         DBUG_PRINT("info", ("At row %ld, column %d is '%s'", row_no, col_no,
2707                             row[col_no]));
2708         /* Found the row to get */
2709         if (row[col_no])
2710           value = row[col_no];
2711         else
2712           value = "NULL";
2713 
2714         break;
2715       }
2716     }
2717     eval_expr(var, value, nullptr, false, false);
2718   }
2719   dynstr_free(&ds_query);
2720   mysql_free_result_wrapper(res);
2721 }
2722 
var_copy(VAR * dest,VAR * src)2723 static void var_copy(VAR *dest, VAR *src) {
2724   dest->int_val = src->int_val;
2725   dest->is_int = src->is_int;
2726   dest->int_dirty = src->int_dirty;
2727 
2728   /* Alloc/realloc data for str_val in dest */
2729   if (dest->alloced_len < src->alloced_len &&
2730       !(dest->str_val =
2731             dest->str_val
2732                 ? (char *)my_realloc(PSI_NOT_INSTRUMENTED, dest->str_val,
2733                                      src->alloced_len, MYF(MY_WME))
2734                 : (char *)my_malloc(PSI_NOT_INSTRUMENTED, src->alloced_len,
2735                                     MYF(MY_WME))))
2736     die("Out of memory");
2737   else
2738     dest->alloced_len = src->alloced_len;
2739 
2740   /* Copy str_val data to dest */
2741   dest->str_val_len = src->str_val_len;
2742   if (src->str_val_len) memcpy(dest->str_val, src->str_val, src->str_val_len);
2743 }
2744 
eval_expr(VAR * v,const char * p,const char ** p_end,bool open_end,bool do_eval)2745 void eval_expr(VAR *v, const char *p, const char **p_end, bool open_end,
2746                bool do_eval) {
2747   DBUG_TRACE;
2748   if (p_end) {
2749     DBUG_PRINT("enter", ("p: '%.*s'", (int)(*p_end - p), p));
2750   } else {
2751     DBUG_PRINT("enter", ("p: '%s'", p));
2752   }
2753   /* Skip to treat as pure string if no evaluation */
2754   if (!do_eval) goto NO_EVAL;
2755 
2756   if (*p == '$') {
2757     VAR *vp;
2758     const char *expected_end = *p_end;  // Remember var end
2759     if ((vp = var_get(p, p_end, false, false))) var_copy(v, vp);
2760 
2761     /* Apparently it is not safe to assume null-terminated string */
2762     v->str_val[v->str_val_len] = 0;
2763 
2764     /* Make sure there was just a $variable and nothing else */
2765     const char *end = *p_end + 1;
2766     if (end < expected_end && !open_end)
2767       die("Found junk '%.*s' after $variable in expression",
2768           (int)(expected_end - end - 1), end);
2769 
2770     return;
2771   }
2772 
2773   if (*p == '`') {
2774     var_query_set(v, p, p_end);
2775     return;
2776   }
2777 
2778   {
2779     /* Check if this is a "let $var= query_get_value()" */
2780     const char *get_value_str = "query_get_value";
2781     const size_t len = std::strlen(get_value_str);
2782     if (std::strncmp(p, get_value_str, len) == 0) {
2783       struct st_command command;
2784       memset(&command, 0, sizeof(command));
2785       command.query = const_cast<char *>(p);
2786       command.first_word_len = len;
2787       command.first_argument = command.query + len;
2788       command.end = const_cast<char *>(*p_end);
2789       var_set_query_get_value(&command, v);
2790       return;
2791     }
2792     /* Check if this is a "let $var= convert_error()" */
2793     const char *get_value_str1 = "convert_error";
2794     const size_t len1 = std::strlen(get_value_str1);
2795     if (std::strncmp(p, get_value_str1, len1) == 0) {
2796       struct st_command command;
2797       memset(&command, 0, sizeof(command));
2798       command.query = const_cast<char *>(p);
2799       command.first_word_len = len;
2800       command.first_argument = command.query + len;
2801       command.end = const_cast<char *>(*p_end);
2802       var_set_convert_error(&command, v);
2803       return;
2804     }
2805   }
2806 
2807 NO_EVAL : {
2808   size_t new_val_len =
2809       (p_end && *p_end) ? static_cast<size_t>(*p_end - p) : std::strlen(p);
2810   if (new_val_len + 1 >= v->alloced_len) {
2811     static size_t MIN_VAR_ALLOC = 32;
2812     v->alloced_len =
2813         (new_val_len < MIN_VAR_ALLOC - 1) ? MIN_VAR_ALLOC : new_val_len + 1;
2814     if (!(v->str_val =
2815               v->str_val ? (char *)my_realloc(PSI_NOT_INSTRUMENTED, v->str_val,
2816                                               v->alloced_len + 1, MYF(MY_WME))
2817                          : (char *)my_malloc(PSI_NOT_INSTRUMENTED,
2818                                              v->alloced_len + 1, MYF(MY_WME))))
2819       die("Out of memory");
2820   }
2821   v->str_val_len = new_val_len;
2822   memcpy(v->str_val, p, new_val_len);
2823   v->str_val[new_val_len] = 0;
2824   var_check_int(v);
2825 }
2826 }
2827 
open_file(const char * name)2828 static int open_file(const char *name) {
2829   char buff[FN_REFLEN];
2830   size_t length;
2831   DBUG_TRACE;
2832   DBUG_PRINT("enter", ("name: %s", name));
2833 
2834   bool file_exists = false;
2835   /* Extract path from current file and try it as base first */
2836   if (dirname_part(buff, cur_file->file_name, &length)) {
2837     strxmov(buff, buff, name, NullS);
2838     if (access(buff, F_OK) == 0) {
2839       DBUG_PRINT("info", ("The file exists"));
2840       name = buff;
2841       file_exists = true;
2842     }
2843   }
2844 
2845   if (!test_if_hard_path(name) && !file_exists) {
2846     strxmov(buff, opt_basedir, name, NullS);
2847     name = buff;
2848   }
2849   fn_format(buff, name, "", "", MY_UNPACK_FILENAME);
2850 
2851   if (cur_file == file_stack_end) die("Source directives are nesting too deep");
2852   cur_file++;
2853   if (!(cur_file->file = fopen(buff, "rb"))) {
2854     cur_file--;
2855     die("Could not open '%s' for reading, errno: %d", buff, errno);
2856   }
2857   cur_file->file_name = my_strdup(PSI_NOT_INSTRUMENTED, buff, MYF(MY_FAE));
2858   cur_file->lineno = 1;
2859   return 0;
2860 }
2861 
2862 /*
2863   Source and execute the given file
2864 
2865   SYNOPSIS
2866   do_source()
2867   query	called command
2868 
2869   DESCRIPTION
2870   source <file_name>
2871 
2872   Open the file <file_name> and execute it
2873 
2874 */
2875 
do_source(struct st_command * command)2876 static void do_source(struct st_command *command) {
2877   static DYNAMIC_STRING ds_filename;
2878   const struct command_arg source_args[] = {
2879       {"filename", ARG_STRING, true, &ds_filename, "File to source"}};
2880   DBUG_TRACE;
2881 
2882   check_command_args(command, command->first_argument, source_args,
2883                      sizeof(source_args) / sizeof(struct command_arg), ' ');
2884 
2885   /*
2886     If this file has already been sourced, don't source it again.
2887     It's already available in the q_lines cache.
2888   */
2889   if (parser.current_line < (parser.read_lines - 1))
2890     ; /* Do nothing */
2891   else {
2892     DBUG_PRINT("info", ("sourcing file: %s", ds_filename.str));
2893     open_file(ds_filename.str);
2894   }
2895 
2896   dynstr_free(&ds_filename);
2897 }
2898 
my_popen(DYNAMIC_STRING * ds_cmd,const char * mode,struct st_command * command MY_ATTRIBUTE ((unused)))2899 static FILE *my_popen(DYNAMIC_STRING *ds_cmd, const char *mode,
2900                       struct st_command *command MY_ATTRIBUTE((unused))) {
2901 #ifdef _WIN32
2902   /*
2903     --execw is for tests executing commands containing non-ASCII characters.
2904 
2905     To correctly start such a program on Windows, we need to use the "wide"
2906     version of popen, with prior translation of the command line from
2907     the file character set to wide string. We use the current value
2908     of --character_set as a file character set, so before using --execw
2909     make sure to set --character_set properly.
2910 
2911     If we use the non-wide version of popen, Windows internally
2912     converts command line from the current ANSI code page to wide string.
2913     In case when character set of the command line does not match the
2914     current ANSI code page, non-ASCII characters get garbled in most cases.
2915 
2916     On Linux, the command line passed to popen() is considered
2917     as a binary string, no any internal to-wide and from-wide
2918     character set conversion happens, so we don't need to do anything.
2919     On Linux --execw is just a synonym to --exec.
2920 
2921     For simplicity, assume that  command line is limited to 4KB
2922     (like in cmd.exe) and that mode at most 10 characters.
2923   */
2924   if (command->type == Q_EXECW) {
2925     wchar_t wcmd[4096];
2926     wchar_t wmode[10];
2927     const char *cmd = ds_cmd->str;
2928     uint dummy_errors;
2929     size_t len;
2930     len = my_convert((char *)wcmd, sizeof(wcmd) - sizeof(wcmd[0]),
2931                      &my_charset_utf16le_bin, ds_cmd->str,
2932                      std::strlen(ds_cmd->str), charset_info, &dummy_errors);
2933     wcmd[len / sizeof(wchar_t)] = 0;
2934     len = my_convert((char *)wmode, sizeof(wmode) - sizeof(wmode[0]),
2935                      &my_charset_utf16le_bin, mode, std::strlen(mode),
2936                      charset_info, &dummy_errors);
2937     wmode[len / sizeof(wchar_t)] = 0;
2938     return _wpopen(wcmd, wmode);
2939   }
2940 #endif /* _WIN32 */
2941 
2942   return popen(ds_cmd->str, mode);
2943 }
2944 
init_builtin_echo(void)2945 static void init_builtin_echo(void) {
2946 #ifdef _WIN32
2947   size_t echo_length;
2948 
2949   /* Look for "echo.exe" in same dir as mysqltest was started from */
2950   dirname_part(builtin_echo, my_progname, &echo_length);
2951   fn_format(builtin_echo, ".\\echo.exe", builtin_echo, "", MYF(MY_REPLACE_DIR));
2952 
2953   /* Make sure echo.exe exists */
2954   if (access(builtin_echo, F_OK) != 0) builtin_echo[0] = 0;
2955   return;
2956 
2957 #else
2958 
2959   builtin_echo[0] = 0;
2960   return;
2961 
2962 #endif
2963 }
2964 
2965 /*
2966   Replace a substring
2967 
2968   SYNOPSIS
2969     replace
2970     ds_str      The string to search and perform the replace in
2971     search_str  The string to search for
2972     search_len  Length of the string to search for
2973     replace_str The string to replace with
2974     replace_len Length of the string to replace with
2975 
2976   RETURN
2977     0 String replaced
2978     1 Could not find search_str in str
2979 */
2980 
replace(DYNAMIC_STRING * ds_str,const char * search_str,size_t search_len,const char * replace_str,size_t replace_len)2981 static int replace(DYNAMIC_STRING *ds_str, const char *search_str,
2982                    size_t search_len, const char *replace_str,
2983                    size_t replace_len) {
2984   DYNAMIC_STRING ds_tmp;
2985   const char *start = strstr(ds_str->str, search_str);
2986   if (!start) return 1;
2987   init_dynamic_string(&ds_tmp, "", ds_str->length + replace_len, 256);
2988   dynstr_append_mem(&ds_tmp, ds_str->str, start - ds_str->str);
2989   dynstr_append_mem(&ds_tmp, replace_str, replace_len);
2990   dynstr_append(&ds_tmp, start + search_len);
2991   dynstr_set(ds_str, ds_tmp.str);
2992   dynstr_free(&ds_tmp);
2993   return 0;
2994 }
2995 
2996 #ifdef _WIN32
2997 /**
2998  Replace CRLF sequence with LF in place.
2999 
3000  This function is required as a workaround for a bug in the Microsoft
3001  C runtime library introduced in Visual Studio 2015.
3002  See bug#22608247 and bug#22811243
3003 
3004  @param buf  Null terminated buffer.
3005 */
replace_crlf_with_lf(char * buf)3006 static void replace_crlf_with_lf(char *buf) {
3007   char *replace = buf;
3008   while (*buf) {
3009     *replace = *buf++;
3010     if (!((*replace == '\x0D') && (*buf == '\x0A'))) {
3011       replace++;
3012     }
3013   }
3014   *replace = '\x0';
3015 }
3016 #endif
3017 
3018 /// Execute the shell command using the popen() library call. References
3019 /// to variables within the command are replaced with the corresponding
3020 /// values. Use “\\$” to specify a literal “$” character.
3021 ///
3022 /// The error code returned from the subprocess is checked against the
3023 /// expected error array, previously set with the --error command. It can
3024 /// thus be used to execute a command that shall fail.
3025 ///
3026 /// @code
3027 /// exec command [args]
3028 /// @endcode
3029 ///
3030 /// @param command Pointer to the st_command structure which holds the
3031 ///                arguments and information for the command.
3032 /// @param run_in_background Specifies if command should be run in background.
3033 ///                          In such case we don't wait nor attempt to read the
3034 ///                          output.
3035 ///
3036 /// @note
3037 /// It is recommended to use mysqltest command(s) like "remove_file"
3038 /// instead of executing the shell commands using 'exec' command.
do_exec(struct st_command * command,bool run_in_background)3039 static void do_exec(struct st_command *command, bool run_in_background) {
3040   DBUG_TRACE;
3041 
3042   const char *cmd = command->first_argument;
3043   DBUG_PRINT("enter", ("cmd: '%s'", cmd));
3044 
3045   // Skip leading space
3046   while (*cmd && my_isspace(charset_info, *cmd)) cmd++;
3047   if (!*cmd) die("Missing argument in exec");
3048   command->last_argument = command->end;
3049 
3050   DYNAMIC_STRING ds_cmd;
3051   init_dynamic_string(&ds_cmd, nullptr, command->query_len + 256, 256);
3052 
3053   // Eval the command, thus replacing all environment variables
3054   do_eval(&ds_cmd, cmd, command->end, !is_windows);
3055 
3056   // Check if echo should be replaced with "builtin" echo
3057   if (builtin_echo[0] && std::strncmp(cmd, "echo", 4) == 0) {
3058     // Replace echo with our "builtin" echo
3059     replace(&ds_cmd, "echo", 4, builtin_echo, std::strlen(builtin_echo));
3060   }
3061 
3062 #ifdef _WIN32
3063   // Replace "/dev/null" with NUL
3064   while (replace(&ds_cmd, "/dev/null", 9, "NUL", 3) == 0)
3065     ;
3066 
3067   // Replace "closed stdout" with non existing output fd
3068   while (replace(&ds_cmd, ">&-", 3, ">&4", 3) == 0)
3069     ;
3070 #endif
3071 
3072   if (run_in_background) {
3073     /* Add an invocation of "START /B" on Windows, append " &" on Linux*/
3074     DYNAMIC_STRING ds_tmp;
3075 #ifdef WIN32
3076     init_dynamic_string(&ds_tmp, "START /B ", ds_cmd.length + 9, 256);
3077     dynstr_append_mem(&ds_tmp, ds_cmd.str, ds_cmd.length);
3078 #else
3079     init_dynamic_string(&ds_tmp, ds_cmd.str, ds_cmd.length + 2, 256);
3080     dynstr_append_mem(&ds_tmp, " &", 2);
3081 #endif
3082     dynstr_set(&ds_cmd, ds_tmp.str);
3083     dynstr_free(&ds_tmp);
3084   }
3085 
3086   // exec command is interpreted externally and will not take newlines
3087   while (replace(&ds_cmd, "\n", 1, " ", 1) == 0)
3088     ;
3089 
3090   DBUG_PRINT("info",
3091              ("Executing '%s' as '%s'", command->first_argument, ds_cmd.str));
3092 
3093 #ifdef WIN32
3094   // Open pipe in binary mode as part of handling Microsoft _read bug.
3095   // See bug#22608247 and bug#22811243
3096   const char *mode = "rb";
3097 #else
3098   const char *mode = "r";
3099 #endif
3100   FILE *res_file;
3101   if (!(res_file = my_popen(&ds_cmd, mode, command)) &&
3102       command->abort_on_error) {
3103     dynstr_free(&ds_cmd);
3104     die("popen(\"%s\", \"r\") failed", command->first_argument);
3105   }
3106 
3107   if (!run_in_background) {
3108     char buf[512];
3109     std::string str;
3110     while (std::fgets(buf, sizeof(buf), res_file)) {
3111       if (std::strlen(buf) < 1) continue;
3112 
3113 #ifdef WIN32
3114       // Replace CRLF char with LF.
3115       // See bug#22608247 and bug#22811243
3116       DBUG_ASSERT(!std::strcmp(mode, "rb"));
3117       replace_crlf_with_lf(buf);
3118 #endif
3119       if (trace_exec) {
3120         fprintf(stdout, "%s", buf);
3121         fflush(stdout);
3122       }
3123       if (disable_result_log) {
3124         buf[std::strlen(buf) - 1] = 0;
3125         DBUG_PRINT("exec_result", ("%s", buf));
3126       } else {
3127         // Read the file line by line. Check if the buffer read from the
3128         // file ends with EOL character.
3129         if ((buf[std::strlen(buf) - 1] != '\n' &&
3130              std::strlen(buf) < (sizeof(buf) - 1)) ||
3131             (buf[std::strlen(buf) - 1] == '\n')) {
3132           // Found EOL
3133           if (str.length()) {
3134             // Temporary string exists, append the current buffer read
3135             // to the temporary string.
3136             str.append(buf);
3137             replace_dynstr_append(&ds_res, str.c_str());
3138             str.clear();
3139           } else {
3140             // Entire line is read at once
3141             replace_dynstr_append(&ds_res, buf);
3142           }
3143         } else {
3144           // The buffer read from the file doesn't end with EOL character,
3145           // store it in a temporary string.
3146           str.append(buf);
3147         }
3148       }
3149     }
3150   }
3151 
3152   std::uint32_t status = 0;
3153   int error = pclose(res_file);
3154 
3155   if (error != 0) {
3156 #ifdef _WIN32
3157     status = WEXITSTATUS(error);
3158 #else
3159     if (error > 0) {
3160       // Do the same as many shells here: show SIGKILL as 137
3161       if (WIFEXITED(error))
3162         status = WEXITSTATUS(error);
3163       else if (WIFSIGNALED(error))
3164         status = 0x80 + WTERMSIG(error);
3165     }
3166 #endif
3167 
3168     if (command->abort_on_error) {
3169       log_msg("exec of '%s' failed, error: %d, status: %d, errno: %d.",
3170               ds_cmd.str, error, status, errno);
3171       dynstr_free(&ds_cmd);
3172       die("Command \"%s\" failed.\n\nOutput from before failure:\n%s",
3173           command->first_argument, ds_res.str);
3174     }
3175 
3176     if (status == 0) status = error;
3177   }
3178 
3179   dynstr_free(&ds_cmd);
3180   handle_command_error(command, status);
3181 
3182   // Save error code
3183   save_error_code(error);
3184 }
3185 
3186 enum enum_operator { DO_DEC, DO_INC };
3187 
3188 /// Template function that frees memory of the dynamic string
3189 /// passed to the function.
3190 ///
3191 /// @param val Dynamic string whose memory needs to be freed.
3192 template <typename T>
free_dynamic_strings(T * val)3193 static void free_dynamic_strings(T *val) {
3194   dynstr_free(val);
3195 }
3196 
3197 /// Frees the memory of dynamic strings passed to the function.
3198 /// It accepts a variable number of dynamic strings, and through
3199 /// recursion, frees the memory. The other template function
3200 /// which calls dynstr_free() is called here.
3201 ///
3202 /// @param first The dynamic string passed to the function which
3203 ///              gets freed using dynstr_free().
3204 /// @param rest  Rest of the dynamic strings which are passed to
3205 ///              the function, through recursion, end up being
3206 ///              freed by dynstr_free().
3207 template <typename T1, typename... T2>
free_dynamic_strings(T1 * first,T2 * ...rest)3208 static void free_dynamic_strings(T1 *first, T2 *... rest) {
3209   free_dynamic_strings(first);
3210   free_dynamic_strings(rest...);
3211 }
3212 
3213 /*
3214   Decrease or increase the value of a variable
3215 
3216   SYNOPSIS
3217   do_modify_var()
3218   query	called command
3219   op    operation to perform on the var
3220 
3221   DESCRIPTION
3222   dec $var_name
3223   inc $var_name
3224 
3225 */
3226 
do_modify_var(struct st_command * command,enum enum_operator op)3227 static int do_modify_var(struct st_command *command, enum enum_operator op) {
3228   const char *p = command->first_argument;
3229   VAR *v;
3230   if (!*p)
3231     die("Missing argument to %.*s", static_cast<int>(command->first_word_len),
3232         command->query);
3233   if (*p != '$')
3234     die("The argument to %.*s must be a variable (start with $)",
3235         static_cast<int>(command->first_word_len), command->query);
3236   v = var_get(p, &p, true, false);
3237   if (!v->is_int) die("Cannot perform inc/dec on a non-numeric value");
3238   switch (op) {
3239     case DO_DEC:
3240       v->int_val--;
3241       break;
3242     case DO_INC:
3243       v->int_val++;
3244       break;
3245     default:
3246       die("Invalid operator to do_modify_var");
3247       break;
3248   }
3249   v->int_dirty = true;
3250   command->last_argument = const_cast<char *>(++p);
3251   return 0;
3252 }
3253 
3254 /// Removes the file passed as the argument and retries a specified
3255 /// number of times, if it is unsuccessful.
3256 ///
3257 /// @param command Pointer to the st_command structure which holds the
3258 ///                arguments and information for the command.
do_remove_file(struct st_command * command)3259 static void do_remove_file(struct st_command *command) {
3260   int error;
3261   static DYNAMIC_STRING ds_filename;
3262   static DYNAMIC_STRING ds_retry;
3263 
3264   const struct command_arg rm_args[] = {
3265       {"filename", ARG_STRING, true, &ds_filename, "File to delete"},
3266       {"retry", ARG_STRING, false, &ds_retry, "Number of retries"}};
3267   DBUG_TRACE;
3268 
3269   check_command_args(command, command->first_argument, rm_args,
3270                      sizeof(rm_args) / sizeof(struct command_arg), ' ');
3271 
3272   // Check if the retry value is passed, and if it is an integer
3273   int retry = 0;
3274   if (ds_retry.length) {
3275     retry = get_int_val(ds_retry.str);
3276     if (retry < 0) {
3277       // In case of invalid retry, copy the value passed to print later
3278       char buf[32];
3279       strmake(buf, ds_retry.str, sizeof(buf) - 1);
3280       free_dynamic_strings(&ds_filename, &ds_retry);
3281       die("Invalid value '%s' for retry argument given to remove_file "
3282           "command.",
3283           buf);
3284     }
3285   }
3286 
3287   DBUG_PRINT("info", ("removing file: %s", ds_filename.str));
3288   error = my_delete(ds_filename.str, MYF(0)) != 0;
3289 
3290   /*
3291     If the remove command fails due to an environmental issue, the command can
3292     be retried a specified number of times before throwing an error.
3293   */
3294   for (int i = 0; error && (i < retry); i++) {
3295     my_sleep(1000 * 1000);
3296     error = my_delete(ds_filename.str, MYF(0)) != 0;
3297   }
3298 
3299   handle_command_error(command, error);
3300   free_dynamic_strings(&ds_filename, &ds_retry);
3301 }
3302 
3303 /// Removes the files in the specified directory, by matching the
3304 /// file name pattern. Retry of the command can happen optionally with
3305 /// an interval of one second between each retry if the command fails.
3306 ///
3307 /// @param command Pointer to the st_command structure which holds the
3308 ///                arguments and information for the command.
do_remove_files_wildcard(struct st_command * command)3309 static void do_remove_files_wildcard(struct st_command *command) {
3310   int error = 0;
3311   uint i;
3312   MY_DIR *dir_info;
3313   FILEINFO *file;
3314   char dir_separator[2];
3315   static DYNAMIC_STRING ds_directory;
3316   static DYNAMIC_STRING ds_wild;
3317   static DYNAMIC_STRING ds_retry;
3318   char dirname[FN_REFLEN];
3319 
3320   const struct command_arg rm_args[] = {
3321       {"directory", ARG_STRING, true, &ds_directory,
3322        "Directory containing files to delete"},
3323       {"pattern", ARG_STRING, true, &ds_wild, "File pattern to delete"},
3324       {"retry", ARG_STRING, false, &ds_retry, "Number of retries"}};
3325   DBUG_TRACE;
3326 
3327   check_command_args(command, command->first_argument, rm_args,
3328                      sizeof(rm_args) / sizeof(struct command_arg), ' ');
3329   fn_format(dirname, ds_directory.str, "", "", MY_UNPACK_FILENAME);
3330 
3331   // Check if the retry value is passed, and if it is an interger
3332   int retry = 0;
3333   if (ds_retry.length) {
3334     retry = get_int_val(ds_retry.str);
3335     if (retry < 0) {
3336       // In case of invalid retry, copy the value passed to print later
3337       char buf[32];
3338       strmake(buf, ds_retry.str, sizeof(buf) - 1);
3339       free_dynamic_strings(&ds_directory, &ds_wild, &ds_retry);
3340       die("Invalid value '%s' for retry argument given to "
3341           "remove_files_wildcard command.",
3342           buf);
3343     }
3344   }
3345 
3346   static DYNAMIC_STRING ds_file_to_remove;
3347   DBUG_PRINT("info", ("listing directory: %s", dirname));
3348   /* Note that my_dir sorts the list if not given any flags */
3349   if (!(dir_info = my_dir(dirname, MYF(MY_DONT_SORT | MY_WANT_STAT)))) {
3350     error = 1;
3351     goto end;
3352   }
3353   init_dynamic_string(&ds_file_to_remove, dirname, 1024, 1024);
3354   dir_separator[0] = FN_LIBCHAR;
3355   dir_separator[1] = 0;
3356   dynstr_append(&ds_file_to_remove, dir_separator);
3357 
3358   size_t length;
3359   /* Storing the length of the path to the file, so it can be reused */
3360   length = ds_file_to_remove.length;
3361   for (i = 0; i < (uint)dir_info->number_off_files; i++) {
3362     ds_file_to_remove.length = length;
3363     file = dir_info->dir_entry + i;
3364     /* Remove only regular files, i.e. no directories etc. */
3365     /* if (!MY_S_ISREG(file->mystat->st_mode)) */
3366     /* MY_S_ISREG does not work here on Windows, just skip directories */
3367     if (MY_S_ISDIR(file->mystat->st_mode)) continue;
3368     if (wild_compare_full(file->name, std::strlen(file->name), ds_wild.str,
3369                           std::strlen(ds_wild.str), false, 0, '?', '*'))
3370       continue;
3371     /* Not required as the var ds_file_to_remove.length already has the
3372        length in canonnicalized form */
3373     /* ds_file_to_remove.length= ds_directory.length + 1;
3374     ds_file_to_remove.str[ds_directory.length + 1]= 0; */
3375     dynstr_append(&ds_file_to_remove, file->name);
3376     DBUG_PRINT("info", ("removing file: %s", ds_file_to_remove.str));
3377     error = my_delete(ds_file_to_remove.str, MYF(0)) != 0;
3378 
3379     /*
3380       If the remove command fails due to an environmental issue, the command
3381       can be retried a specified number of times before throwing an error.
3382     */
3383     for (int j = 0; error && (j < retry); j++) {
3384       my_sleep(1000 * 1000);
3385       error = my_delete(ds_file_to_remove.str, MYF(0)) != 0;
3386     }
3387     if (error) break;
3388   }
3389   my_dirend(dir_info);
3390 
3391 end:
3392   handle_command_error(command, error);
3393   free_dynamic_strings(&ds_directory, &ds_wild, &ds_file_to_remove, &ds_retry);
3394 }
3395 
3396 /// Copy the source file to destination file. Copy will fail if the
3397 /// destination file exists. Retry of the command can happen optionally with
3398 /// an interval of one second between each retry if the command fails.
3399 ///
3400 /// @param command Pointer to the st_command structure which holds the
3401 ///                arguments and information for the command.
do_copy_file(struct st_command * command)3402 static void do_copy_file(struct st_command *command) {
3403   int error;
3404   static DYNAMIC_STRING ds_from_file;
3405   static DYNAMIC_STRING ds_to_file;
3406   static DYNAMIC_STRING ds_retry;
3407 
3408   const struct command_arg copy_file_args[] = {
3409       {"from_file", ARG_STRING, true, &ds_from_file, "Filename to copy from"},
3410       {"to_file", ARG_STRING, true, &ds_to_file, "Filename to copy to"},
3411       {"retry", ARG_STRING, false, &ds_retry, "Number of retries"}};
3412   DBUG_TRACE;
3413 
3414   check_command_args(command, command->first_argument, copy_file_args,
3415                      sizeof(copy_file_args) / sizeof(struct command_arg), ' ');
3416 
3417   // Check if the retry value is passed, and if it is an interger
3418   int retry = 0;
3419   if (ds_retry.length) {
3420     retry = get_int_val(ds_retry.str);
3421     if (retry < 0) {
3422       // In case of invalid retry, copy the value passed to print later
3423       char buf[32];
3424       strmake(buf, ds_retry.str, sizeof(buf) - 1);
3425       free_dynamic_strings(&ds_from_file, &ds_to_file, &ds_retry);
3426       die("Invalid value '%s' for retry argument given to copy_file "
3427           "command.",
3428           buf);
3429     }
3430   }
3431 
3432   DBUG_PRINT("info", ("Copy %s to %s", ds_from_file.str, ds_to_file.str));
3433   /* MY_HOLD_ORIGINAL_MODES prevents attempts to chown the file */
3434   error = (my_copy(ds_from_file.str, ds_to_file.str,
3435                    MYF(MY_DONT_OVERWRITE_FILE | MY_HOLD_ORIGINAL_MODES)) != 0);
3436 
3437   /*
3438     If the copy command fails due to an environmental issue, the command can
3439     be retried a specified number of times before throwing an error.
3440   */
3441   for (int i = 0; error && (i < retry); i++) {
3442     my_sleep(1000 * 1000);
3443     error =
3444         (my_copy(ds_from_file.str, ds_to_file.str,
3445                  MYF(MY_DONT_OVERWRITE_FILE | MY_HOLD_ORIGINAL_MODES)) != 0);
3446   }
3447 
3448   handle_command_error(command, error);
3449   free_dynamic_strings(&ds_from_file, &ds_to_file, &ds_retry);
3450 }
3451 
3452 /*
3453   SYNOPSIS
3454   recursive_copy
3455   ds_source      - pointer to dynamic string containing source
3456                    directory informtion
3457   ds_destination - pointer to dynamic string containing destination
3458                    directory informtion
3459 
3460   DESCRIPTION
3461   Recursive copy of <ds_source> to <ds_destination>
3462 */
3463 
recursive_copy(DYNAMIC_STRING * ds_source,DYNAMIC_STRING * ds_destination)3464 static int recursive_copy(DYNAMIC_STRING *ds_source,
3465                           DYNAMIC_STRING *ds_destination) {
3466   /* Note that my_dir sorts the list if not given any flags */
3467   MY_DIR *src_dir_info =
3468       my_dir(ds_source->str, MYF(MY_DONT_SORT | MY_WANT_STAT));
3469 
3470   int error = 0;
3471 
3472   /* Source directory exists */
3473   if (src_dir_info) {
3474     /* Note that my_dir sorts the list if not given any flags */
3475     MY_DIR *dest_dir_info =
3476         my_dir(ds_destination->str, MYF(MY_DONT_SORT | MY_WANT_STAT));
3477 
3478     /* Create destination directory if it doesn't exist */
3479     if (!dest_dir_info) {
3480       error = my_mkdir(ds_destination->str, 0777, MYF(0)) != 0;
3481       if (error) {
3482         my_dirend(dest_dir_info);
3483         goto end;
3484       }
3485     } else {
3486       /* Extracting the source directory name */
3487       if (ds_source->str[std::strlen(ds_source->str) - 1] == '/') {
3488         strmake(ds_source->str, ds_source->str,
3489                 std::strlen(ds_source->str) - 1);
3490         ds_source->length = ds_source->length - 1;
3491       }
3492       char *src_dir_name = strrchr(ds_source->str, '/');
3493 
3494       /* Extracting the destination directory name */
3495       if (ds_destination->str[std::strlen(ds_destination->str) - 1] == '/') {
3496         strmake(ds_destination->str, ds_destination->str,
3497                 std::strlen(ds_destination->str) - 1);
3498         ds_destination->length = ds_destination->length - 1;
3499       }
3500       char *dest_dir_name = strrchr(ds_destination->str, '/');
3501 
3502       /*
3503         Destination directory might not exist if source directory
3504         name and destination directory name are not same.
3505 
3506         For example, if source is "abc" and destintion is "def",
3507         check for the existance of directory "def/abc". If it exists
3508         then, copy the files from source directory(i.e "abc") to
3509         destination directory(i.e "def/abc"), otherwise create a new
3510         directory "abc" under "def" and copy the files from source to
3511         destination directory.
3512       */
3513       if (std::strcmp(src_dir_name, dest_dir_name)) {
3514         dynstr_append(ds_destination, src_dir_name);
3515         my_dirend(dest_dir_info);
3516         dest_dir_info =
3517             my_dir(ds_destination->str, MYF(MY_DONT_SORT | MY_WANT_STAT));
3518 
3519         /* Create destination directory if it doesn't exist */
3520         if (!dest_dir_info) {
3521           error = my_mkdir(ds_destination->str, 0777, MYF(0)) != 0;
3522           if (error) {
3523             my_dirend(dest_dir_info);
3524             goto end;
3525           }
3526         }
3527       }
3528     }
3529 
3530     char dir_separator[2] = {FN_LIBCHAR, 0};
3531     dynstr_append(ds_source, dir_separator);
3532     dynstr_append(ds_destination, dir_separator);
3533 
3534     /*
3535       Storing the length of source and destination
3536       directory paths so it can be reused.
3537     */
3538     size_t source_dir_length = ds_source->length;
3539     size_t destination_dir_length = ds_destination->length;
3540     ;
3541 
3542     for (uint i = 0; i < src_dir_info->number_off_files; i++) {
3543       ds_source->length = source_dir_length;
3544       ds_destination->length = destination_dir_length;
3545       FILEINFO *file = src_dir_info->dir_entry + i;
3546 
3547       /* Skip the names "." and ".." */
3548       if (!std::strcmp(file->name, ".") || !std::strcmp(file->name, ".."))
3549         continue;
3550 
3551       dynstr_append(ds_source, file->name);
3552       dynstr_append(ds_destination, file->name);
3553 
3554       if (MY_S_ISDIR(file->mystat->st_mode))
3555         error = (recursive_copy(ds_source, ds_destination) != 0) ? 1 : error;
3556       else {
3557         DBUG_PRINT("info", ("Copying file: %s to %s", ds_source->str,
3558                             ds_destination->str));
3559 
3560         /* MY_HOLD_ORIGINAL_MODES prevents attempts to chown the file */
3561         error = (my_copy(ds_source->str, ds_destination->str,
3562                          MYF(MY_HOLD_ORIGINAL_MODES)) != 0)
3563                     ? 1
3564                     : error;
3565       }
3566     }
3567     my_dirend(dest_dir_info);
3568   }
3569   /* Source directory does not exist or access denied */
3570   else
3571     error = 1;
3572 
3573 end:
3574   my_dirend(src_dir_info);
3575   return error;
3576 }
3577 
3578 /*
3579   SYNOPSIS
3580   do_force_cpdir
3581   command    - command handle
3582 
3583   DESCRIPTION
3584   force-cpdir <from_directory> <to_directory>
3585   Recursive copy of <from_directory> to <to_directory>.
3586   Destination directory is created if it doesn't exist.
3587 
3588   NOTE
3589   Will fail if  <from_directory> doesn't exist.
3590 */
3591 
do_force_cpdir(struct st_command * command)3592 static void do_force_cpdir(struct st_command *command) {
3593   DBUG_TRACE;
3594 
3595   static DYNAMIC_STRING ds_source;
3596   static DYNAMIC_STRING ds_destination;
3597 
3598   const struct command_arg copy_file_args[] = {
3599       {"from_directory", ARG_STRING, true, &ds_source,
3600        "Directory to copy from"},
3601       {"to_directory", ARG_STRING, true, &ds_destination,
3602        "Directory to copy to"}};
3603 
3604   check_command_args(command, command->first_argument, copy_file_args,
3605                      sizeof(copy_file_args) / sizeof(struct command_arg), ' ');
3606 
3607   DBUG_PRINT("info", ("Recursive copy files of %s to %s", ds_source.str,
3608                       ds_destination.str));
3609 
3610   DBUG_PRINT("info", ("listing directory: %s", ds_source.str));
3611 
3612   int error = 0;
3613 
3614   /*
3615     Throw an error if source directory path and
3616     destination directory path are same.
3617   */
3618   if (!std::strcmp(ds_source.str, ds_destination.str)) {
3619     error = 1;
3620     set_my_errno(EEXIST);
3621   } else
3622     error = recursive_copy(&ds_source, &ds_destination);
3623 
3624   handle_command_error(command, error);
3625   dynstr_free(&ds_source);
3626   dynstr_free(&ds_destination);
3627 }
3628 
3629 /// Copy files from source directory to destination directory, by matching
3630 /// a specified file name pattern.
3631 ///
3632 /// Copy will fail if no files match the pattern. It will fail if source
3633 /// directory is empty and/or there are no files in it. Copy will also
3634 /// fail if source directory or destination directory or both do not
3635 /// exist. Retry of the command can happen optionally with an interval of
3636 /// one second between each retry if the command fails.
3637 ///
3638 /// @param command Pointer to the st_command structure which holds the
3639 ///                arguments and information for the command.
do_copy_files_wildcard(struct st_command * command)3640 static void do_copy_files_wildcard(struct st_command *command) {
3641   static DYNAMIC_STRING ds_source;
3642   static DYNAMIC_STRING ds_destination;
3643   static DYNAMIC_STRING ds_wild;
3644   static DYNAMIC_STRING ds_retry;
3645 
3646   const struct command_arg copy_file_args[] = {
3647       {"from_directory", ARG_STRING, true, &ds_source,
3648        "Directory to copy from"},
3649       {"to_directory", ARG_STRING, true, &ds_destination,
3650        "Directory to copy to"},
3651       {"pattern", ARG_STRING, true, &ds_wild, "File name pattern"},
3652       {"retry", ARG_STRING, false, &ds_retry, "Number of retries"}};
3653   DBUG_TRACE;
3654 
3655   check_command_args(command, command->first_argument, copy_file_args,
3656                      sizeof(copy_file_args) / sizeof(struct command_arg), ' ');
3657 
3658   DBUG_PRINT("info",
3659              ("Copy files of %s to %s", ds_source.str, ds_destination.str));
3660 
3661   DBUG_PRINT("info", ("listing directory: %s", ds_source.str));
3662 
3663   int error = 0;
3664 
3665   // Check if the retry value is passed, and if it is an integer
3666   int retry = 0;
3667   if (ds_retry.length) {
3668     retry = get_int_val(ds_retry.str);
3669     if (retry < 0) {
3670       // In case of invalid retry, copy the value passed to print later
3671       char buf[32];
3672       strmake(buf, ds_retry.str, sizeof(buf) - 1);
3673       free_dynamic_strings(&ds_source, &ds_destination, &ds_wild, &ds_retry);
3674       die("Invalid value '%s' for retry argument given to "
3675           "copy_files_wildcard command.",
3676           buf);
3677     }
3678   }
3679 
3680   /* Note that my_dir sorts the list if not given any flags */
3681   MY_DIR *dir_info = my_dir(ds_source.str, MYF(MY_DONT_SORT | MY_WANT_STAT));
3682 
3683   /* Directory does not exist or access denied */
3684   if (!dir_info) {
3685     error = 1;
3686     goto end;
3687   }
3688 
3689   /* The directory exists but is empty */
3690   if (dir_info->number_off_files == 2) {
3691     error = 1;
3692     set_my_errno(ENOENT);
3693     goto end;
3694   }
3695 
3696   char dir_separator[2];
3697   dir_separator[0] = FN_LIBCHAR;
3698   dir_separator[1] = 0;
3699   dynstr_append(&ds_source, dir_separator);
3700   dynstr_append(&ds_destination, dir_separator);
3701 
3702   /* Storing the length of the path to the file, so it can be reused */
3703   size_t source_file_length;
3704   size_t dest_file_length;
3705   dest_file_length = ds_destination.length;
3706   source_file_length = ds_source.length;
3707   uint match_count;
3708   match_count = 0;
3709 
3710   for (uint i = 0; i < dir_info->number_off_files; i++) {
3711     ds_source.length = source_file_length;
3712     ds_destination.length = dest_file_length;
3713     FILEINFO *file = dir_info->dir_entry + i;
3714 
3715     /*
3716       Copy only regular files, i.e. no directories etc.
3717       if (!MY_S_ISREG(file->mystat->st_mode))
3718       MY_S_ISREG does not work here on Windows, just skip directories
3719     */
3720     if (MY_S_ISDIR(file->mystat->st_mode)) continue;
3721 
3722     /* Copy only those files which the pattern matches */
3723     if (wild_compare_full(file->name, std::strlen(file->name), ds_wild.str,
3724                           std::strlen(ds_wild.str), false, 0, '?', '*'))
3725       continue;
3726 
3727     match_count++;
3728     dynstr_append(&ds_source, file->name);
3729     dynstr_append(&ds_destination, file->name);
3730     DBUG_PRINT("info",
3731                ("Copying file: %s to %s", ds_source.str, ds_destination.str));
3732 
3733     /* MY_HOLD_ORIGINAL_MODES prevents attempts to chown the file */
3734     error = (my_copy(ds_source.str, ds_destination.str,
3735                      MYF(MY_HOLD_ORIGINAL_MODES)) != 0);
3736 
3737     /*
3738       If the copy command fails due to an environmental issue, the command can
3739       be retried a specified number of times before throwing an error.
3740     */
3741     for (int j = 0; error && (j < retry); j++) {
3742       my_sleep(1000 * 1000);
3743       error =
3744           (my_copy(ds_source.str, ds_destination.str,
3745                    MYF(MY_DONT_OVERWRITE_FILE | MY_HOLD_ORIGINAL_MODES)) != 0);
3746     }
3747 
3748     if (error) goto end;
3749   }
3750 
3751   /* Pattern did not match any files */
3752   if (!match_count) {
3753     error = 1;
3754     set_my_errno(ENOENT);
3755   }
3756 
3757 end:
3758   my_dirend(dir_info);
3759   handle_command_error(command, error);
3760   free_dynamic_strings(&ds_source, &ds_destination, &ds_wild, &ds_retry);
3761 }
3762 
3763 /*
3764   SYNOPSIS
3765   move_file_by_copy_delete
3766   from  path of source
3767   to    path of destination
3768 
3769   DESCRIPTION
3770   Move <from_file> to <to_file>
3771   Auxiliary function for copying <from_file> to <to_file> followed by
3772   deleting <to_file>.
3773 */
3774 
move_file_by_copy_delete(const char * from,const char * to)3775 static int move_file_by_copy_delete(const char *from, const char *to) {
3776   int error_copy, error_delete;
3777   error_copy = (my_copy(from, to, MYF(MY_HOLD_ORIGINAL_MODES)) != 0);
3778   if (error_copy) {
3779     return error_copy;
3780   }
3781 
3782   error_delete = my_delete(from, MYF(0)) != 0;
3783 
3784   /*
3785     If deleting the source file fails, rollback by deleting the
3786     redundant copy at the destinatiion.
3787   */
3788   if (error_delete) {
3789     my_delete(to, MYF(0));
3790   }
3791   return error_delete;
3792 }
3793 
3794 /// Moves a file to destination file. Retry of the command can happen
3795 /// optionally with an interval of one second between each retry if
3796 /// the command fails.
3797 ///
3798 /// @param command Pointer to the st_command structure which holds the
3799 ///                arguments and information for the command.
do_move_file(struct st_command * command)3800 static void do_move_file(struct st_command *command) {
3801   int error;
3802   static DYNAMIC_STRING ds_from_file;
3803   static DYNAMIC_STRING ds_to_file;
3804   static DYNAMIC_STRING ds_retry;
3805 
3806   const struct command_arg move_file_args[] = {
3807       {"from_file", ARG_STRING, true, &ds_from_file, "Filename to move from"},
3808       {"to_file", ARG_STRING, true, &ds_to_file, "Filename to move to"},
3809       {"retry", ARG_STRING, false, &ds_retry, "Number of retries"}};
3810   DBUG_TRACE;
3811 
3812   check_command_args(command, command->first_argument, move_file_args,
3813                      sizeof(move_file_args) / sizeof(struct command_arg), ' ');
3814 
3815   // Check if the retry value is passed, and if it is an interger
3816   int retry = 0;
3817   if (ds_retry.length) {
3818     retry = get_int_val(ds_retry.str);
3819     if (retry < 0) {
3820       // In case of invalid retry, copy the value passed to print later
3821       char buf[32];
3822       strmake(buf, ds_retry.str, sizeof(buf) - 1);
3823       free_dynamic_strings(&ds_from_file, &ds_to_file, &ds_retry);
3824       die("Invalid value '%s' for retry argument given to move_file "
3825           "command.",
3826           buf);
3827     }
3828   }
3829 
3830   DBUG_PRINT("info", ("Move %s to %s", ds_from_file.str, ds_to_file.str));
3831   error = (my_rename(ds_from_file.str, ds_to_file.str, MYF(0)) != 0);
3832 
3833   /*
3834     Use my_copy() followed by my_delete() for moving a file instead of
3835     my_rename() when my_errno is EXDEV. This is because my_rename() fails
3836     with the error "Invalid cross-device link" while moving a file between
3837     locations having different filesystems in some operating systems.
3838   */
3839   if (error && (my_errno() == EXDEV)) {
3840     error = move_file_by_copy_delete(ds_from_file.str, ds_to_file.str);
3841   }
3842 
3843   /*
3844     If the command fails due to an environmental issue, the command can be
3845     retried a specified number of times before throwing an error.
3846   */
3847   for (int i = 0; error && (i < retry); i++) {
3848     my_sleep(1000 * 1000);
3849     error = (my_rename(ds_from_file.str, ds_to_file.str, MYF(0)) != 0);
3850 
3851     if (error && (my_errno() == EXDEV))
3852       error = move_file_by_copy_delete(ds_from_file.str, ds_to_file.str);
3853   }
3854 
3855   handle_command_error(command, error);
3856   free_dynamic_strings(&ds_from_file, &ds_to_file, &ds_retry);
3857 }
3858 
3859 /*
3860   SYNOPSIS
3861   do_chmod_file
3862   command	command handle
3863 
3864   DESCRIPTION
3865   chmod <octal> <file_name>
3866   Change file permission of <file_name>
3867 
3868 */
3869 
do_chmod_file(struct st_command * command)3870 static void do_chmod_file(struct st_command *command) {
3871   long mode = 0;
3872   int err_code;
3873   static DYNAMIC_STRING ds_mode;
3874   static DYNAMIC_STRING ds_file;
3875   const struct command_arg chmod_file_args[] = {
3876       {"mode", ARG_STRING, true, &ds_mode, "Mode of file(octal) ex. 0660"},
3877       {"filename", ARG_STRING, true, &ds_file, "Filename of file to modify"}};
3878   DBUG_TRACE;
3879 
3880   check_command_args(command, command->first_argument, chmod_file_args,
3881                      sizeof(chmod_file_args) / sizeof(struct command_arg), ' ');
3882 
3883   /* Parse what mode to set */
3884   if (ds_mode.length != 4 ||
3885       str2int(ds_mode.str, 8, 0, INT_MAX, &mode) == NullS)
3886     die("You must write a 4 digit octal number for mode");
3887 
3888   DBUG_PRINT("info", ("chmod %o %s", (uint)mode, ds_file.str));
3889   err_code = chmod(ds_file.str, mode);
3890   if (err_code < 0) err_code = 1;
3891   handle_command_error(command, err_code);
3892   dynstr_free(&ds_mode);
3893   dynstr_free(&ds_file);
3894 }
3895 
3896 /// Check if specified file exists. Retry of the command can happen
3897 /// optionally with an interval of one second between each retry if
3898 /// the command fails.
3899 ///
3900 /// @param command Pointer to the st_command structure which holds the
3901 ///                arguments and information for the command.
do_file_exist(struct st_command * command)3902 static void do_file_exist(struct st_command *command) {
3903   int error;
3904   static DYNAMIC_STRING ds_filename;
3905   static DYNAMIC_STRING ds_retry;
3906 
3907   const struct command_arg file_exist_args[] = {
3908       {"filename", ARG_STRING, true, &ds_filename, "File to check if it exist"},
3909       {"retry", ARG_STRING, false, &ds_retry, "Number of retries"}};
3910   DBUG_TRACE;
3911 
3912   check_command_args(command, command->first_argument, file_exist_args,
3913                      sizeof(file_exist_args) / sizeof(struct command_arg), ' ');
3914 
3915   // Check if the retry value is passed, and if it is an interger
3916   int retry = 0;
3917   if (ds_retry.length) {
3918     retry = get_int_val(ds_retry.str);
3919     if (retry < 0) {
3920       // In case of invalid retry, copy the value passed to print later
3921       char buf[32];
3922       strmake(buf, ds_retry.str, sizeof(buf) - 1);
3923       free_dynamic_strings(&ds_filename, &ds_retry);
3924       die("Invalid value '%s' for retry argument given to file_exists "
3925           "command.",
3926           buf);
3927     }
3928   }
3929 
3930   DBUG_PRINT("info", ("Checking for existence of file: %s", ds_filename.str));
3931   error = (access(ds_filename.str, F_OK) != 0);
3932 
3933   /*
3934     If the file_exists command fails due to an environmental issue, the command
3935     can be retried a specified number of times before throwing an error.
3936   */
3937   for (int i = 0; error && (i < retry); i++) {
3938     my_sleep(1000 * 1000);
3939     error = (access(ds_filename.str, F_OK) != 0);
3940   }
3941 
3942   handle_command_error(command, error);
3943   free_dynamic_strings(&ds_filename, &ds_retry);
3944 }
3945 
3946 /*
3947   SYNOPSIS
3948   do_mkdir
3949   command	called command
3950 
3951   DESCRIPTION
3952   mkdir <dir_name>
3953   Create the directory <dir_name>
3954 */
3955 
do_mkdir(struct st_command * command)3956 static void do_mkdir(struct st_command *command) {
3957   int error;
3958   static DYNAMIC_STRING ds_dirname;
3959   const struct command_arg mkdir_args[] = {
3960       {"dirname", ARG_STRING, true, &ds_dirname, "Directory to create"}};
3961   DBUG_TRACE;
3962 
3963   check_command_args(command, command->first_argument, mkdir_args,
3964                      sizeof(mkdir_args) / sizeof(struct command_arg), ' ');
3965 
3966   DBUG_PRINT("info", ("creating directory: %s", ds_dirname.str));
3967   error = my_mkdir(ds_dirname.str, 0777, MYF(0)) != 0;
3968   handle_command_error(command, error);
3969   dynstr_free(&ds_dirname);
3970 }
3971 
3972 /*
3973   SYNOPSIS
3974   do_force_rmdir
3975   command    - command handle
3976   ds_dirname - pointer to dynamic string containing directory informtion
3977 
3978   DESCRIPTION
3979   force-rmdir <dir_name>
3980   Remove the directory <dir_name>
3981 */
3982 
do_force_rmdir(struct st_command * command,DYNAMIC_STRING * ds_dirname)3983 void do_force_rmdir(struct st_command *command, DYNAMIC_STRING *ds_dirname) {
3984   DBUG_TRACE;
3985 
3986   char dir_name[FN_REFLEN + 1];
3987   strncpy(dir_name, ds_dirname->str, sizeof(dir_name) - 1);
3988   dir_name[FN_REFLEN] = '\0';
3989 
3990   /* Note that my_dir sorts the list if not given any flags */
3991   MY_DIR *dir_info = my_dir(ds_dirname->str, MYF(MY_DONT_SORT | MY_WANT_STAT));
3992 
3993   if (dir_info && dir_info->number_off_files > 2) {
3994     /* Storing the length of the path to the file, so it can be reused */
3995     size_t length = ds_dirname->length;
3996 
3997     /* Delete the directory recursively */
3998     for (uint i = 0; i < dir_info->number_off_files; i++) {
3999       FILEINFO *file = dir_info->dir_entry + i;
4000 
4001       /* Skip the names "." and ".." */
4002       if (!std::strcmp(file->name, ".") || !std::strcmp(file->name, ".."))
4003         continue;
4004 
4005       ds_dirname->length = length;
4006       char dir_separator[2] = {FN_LIBCHAR, 0};
4007       dynstr_append(ds_dirname, dir_separator);
4008       dynstr_append(ds_dirname, file->name);
4009 
4010       if (MY_S_ISDIR(file->mystat->st_mode)) /* It's a directory */
4011         do_force_rmdir(command, ds_dirname);
4012       else
4013         /* It's a file */
4014         my_delete(ds_dirname->str, MYF(0));
4015     }
4016   }
4017 
4018   my_dirend(dir_info);
4019   int error = rmdir(dir_name) != 0;
4020   set_my_errno(errno);
4021   handle_command_error(command, error);
4022 }
4023 
4024 /*
4025   SYNOPSIS
4026   do_rmdir
4027   command	called command
4028   force         Recursively delete a directory if the value is set to true,
4029                 otherwise delete an empty direcory
4030 
4031   DESCRIPTION
4032   rmdir <dir_name>
4033   Remove the empty directory <dir_name>
4034 */
4035 
do_rmdir(struct st_command * command,bool force)4036 static void do_rmdir(struct st_command *command, bool force) {
4037   int error;
4038   static DYNAMIC_STRING ds_dirname;
4039   const struct command_arg rmdir_args[] = {
4040       {"dirname", ARG_STRING, true, &ds_dirname, "Directory to remove"}};
4041   DBUG_TRACE;
4042 
4043   check_command_args(command, command->first_argument, rmdir_args,
4044                      sizeof(rmdir_args) / sizeof(struct command_arg), ' ');
4045 
4046   DBUG_PRINT("info", ("removing directory: %s", ds_dirname.str));
4047   if (force)
4048     do_force_rmdir(command, &ds_dirname);
4049   else {
4050     error = rmdir(ds_dirname.str) != 0;
4051     set_my_errno(errno);
4052     handle_command_error(command, error);
4053   }
4054   dynstr_free(&ds_dirname);
4055 }
4056 
4057 /*
4058   SYNOPSIS
4059   get_list_files
4060   ds          output
4061   ds_dirname  dir to list
4062   ds_wild     wild-card file pattern (can be empty)
4063 
4064   DESCRIPTION
4065   list all entries in directory (matching ds_wild if given)
4066 */
4067 
get_list_files(DYNAMIC_STRING * ds,const DYNAMIC_STRING * ds_dirname,const DYNAMIC_STRING * ds_wild)4068 static int get_list_files(DYNAMIC_STRING *ds, const DYNAMIC_STRING *ds_dirname,
4069                           const DYNAMIC_STRING *ds_wild) {
4070   uint i;
4071   MY_DIR *dir_info;
4072   FILEINFO *file;
4073   DBUG_TRACE;
4074 
4075   DBUG_PRINT("info", ("listing directory: %s", ds_dirname->str));
4076   /* Note that my_dir sorts the list if not given any flags */
4077   if (!(dir_info = my_dir(ds_dirname->str, MYF(0)))) return 1;
4078   for (i = 0; i < (uint)dir_info->number_off_files; i++) {
4079     file = dir_info->dir_entry + i;
4080     if (file->name[0] == '.' &&
4081         (file->name[1] == '\0' ||
4082          (file->name[1] == '.' && file->name[2] == '\0')))
4083       continue; /* . or .. */
4084     if (ds_wild && ds_wild->length &&
4085         wild_compare_full(file->name, std::strlen(file->name), ds_wild->str,
4086                           std::strlen(ds_wild->str), false, 0, '?', '*'))
4087       continue;
4088     replace_dynstr_append(ds, file->name);
4089     dynstr_append(ds, "\n");
4090   }
4091   my_dirend(dir_info);
4092   return 0;
4093 }
4094 
4095 /*
4096   SYNOPSIS
4097   do_list_files
4098   command	called command
4099 
4100   DESCRIPTION
4101   list_files <dir_name> [<file_name>]
4102   List files and directories in directory <dir_name> (like `ls`)
4103   [Matching <file_name>, where wild-cards are allowed]
4104 */
4105 
do_list_files(struct st_command * command)4106 static void do_list_files(struct st_command *command) {
4107   int error;
4108   static DYNAMIC_STRING ds_dirname;
4109   static DYNAMIC_STRING ds_wild;
4110   const struct command_arg list_files_args[] = {
4111       {"dirname", ARG_STRING, true, &ds_dirname, "Directory to list"},
4112       {"file", ARG_STRING, false, &ds_wild, "Filename (incl. wildcard)"}};
4113   DBUG_TRACE;
4114   command->used_replace = true;
4115 
4116   check_command_args(command, command->first_argument, list_files_args,
4117                      sizeof(list_files_args) / sizeof(struct command_arg), ' ');
4118 
4119   error = get_list_files(&ds_res, &ds_dirname, &ds_wild);
4120   handle_command_error(command, error);
4121   dynstr_free(&ds_dirname);
4122   dynstr_free(&ds_wild);
4123 }
4124 
4125 /*
4126   SYNOPSIS
4127   do_list_files_write_file_command
4128   command       called command
4129   append        append file, or create new
4130 
4131   DESCRIPTION
4132   list_files_{write|append}_file <filename> <dir_name> [<match_file>]
4133   List files and directories in directory <dir_name> (like `ls`)
4134   [Matching <match_file>, where wild-cards are allowed]
4135 
4136   Note: File will be truncated if exists and append is not true.
4137 */
4138 
do_list_files_write_file_command(struct st_command * command,bool append)4139 static void do_list_files_write_file_command(struct st_command *command,
4140                                              bool append) {
4141   int error;
4142   static DYNAMIC_STRING ds_content;
4143   static DYNAMIC_STRING ds_filename;
4144   static DYNAMIC_STRING ds_dirname;
4145   static DYNAMIC_STRING ds_wild;
4146   const struct command_arg list_files_args[] = {
4147       {"filename", ARG_STRING, true, &ds_filename, "Filename for write"},
4148       {"dirname", ARG_STRING, true, &ds_dirname, "Directory to list"},
4149       {"file", ARG_STRING, false, &ds_wild, "Filename (incl. wildcard)"}};
4150   DBUG_TRACE;
4151   command->used_replace = true;
4152 
4153   check_command_args(command, command->first_argument, list_files_args,
4154                      sizeof(list_files_args) / sizeof(struct command_arg), ' ');
4155 
4156   init_dynamic_string(&ds_content, "", 1024, 1024);
4157   error = get_list_files(&ds_content, &ds_dirname, &ds_wild);
4158   handle_command_error(command, error);
4159   str_to_file2(ds_filename.str, ds_content.str, ds_content.length, append);
4160   dynstr_free(&ds_content);
4161   dynstr_free(&ds_filename);
4162   dynstr_free(&ds_dirname);
4163   dynstr_free(&ds_wild);
4164 }
4165 
4166 /*
4167   Read characters from line buffer or file. This is needed to allow
4168   my_ungetc() to buffer MAX_DELIMITER_LENGTH characters for a file
4169 
4170   NOTE:
4171   This works as long as one doesn't change files (with 'source file_name')
4172   when there is things pushed into the buffer.  This should however not
4173   happen for any tests in the test suite.
4174 */
4175 
my_getc(FILE * file)4176 static int my_getc(FILE *file) {
4177   if (line_buffer_pos == line_buffer) return fgetc(file);
4178   return *--line_buffer_pos;
4179 }
4180 
my_ungetc(int c)4181 static void my_ungetc(int c) { *line_buffer_pos++ = (char)c; }
4182 
read_until_delimiter(DYNAMIC_STRING * ds,DYNAMIC_STRING * ds_delimiter)4183 static void read_until_delimiter(DYNAMIC_STRING *ds,
4184                                  DYNAMIC_STRING *ds_delimiter) {
4185   char c;
4186   DBUG_TRACE;
4187   DBUG_PRINT("enter", ("delimiter: %s, length: %u", ds_delimiter->str,
4188                        (uint)ds_delimiter->length));
4189 
4190   if (ds_delimiter->length > MAX_DELIMITER_LENGTH)
4191     die("Max delimiter length(%d) exceeded", MAX_DELIMITER_LENGTH);
4192 
4193   /* Read from file until delimiter is found */
4194   while (true) {
4195     c = my_getc(cur_file->file);
4196 
4197     if (c == '\n') {
4198       cur_file->lineno++;
4199 
4200       /* Skip newline from the same line as the command */
4201       if (start_lineno == (cur_file->lineno - 1)) continue;
4202     } else if (start_lineno == cur_file->lineno) {
4203       /*
4204         No characters except \n are allowed on
4205         the same line as the command
4206       */
4207       die("Trailing characters found after command");
4208     }
4209 
4210     if (feof(cur_file->file))
4211       die("End of file encountered before '%s' delimiter was found",
4212           ds_delimiter->str);
4213 
4214     if (match_delimiter(c, ds_delimiter->str, ds_delimiter->length)) {
4215       DBUG_PRINT("exit", ("Found delimiter '%s'", ds_delimiter->str));
4216       break;
4217     }
4218     dynstr_append_mem(ds, (const char *)&c, 1);
4219   }
4220   DBUG_PRINT("exit", ("ds: %s", ds->str));
4221 }
4222 
do_write_file_command(struct st_command * command,bool append)4223 static void do_write_file_command(struct st_command *command, bool append) {
4224   static DYNAMIC_STRING ds_content;
4225   static DYNAMIC_STRING ds_filename;
4226   static DYNAMIC_STRING ds_delimiter;
4227   const struct command_arg write_file_args[] = {
4228       {"filename", ARG_STRING, true, &ds_filename, "File to write to"},
4229       {"delimiter", ARG_STRING, false, &ds_delimiter,
4230        "Delimiter to read until"}};
4231   DBUG_TRACE;
4232 
4233   check_command_args(command, command->first_argument, write_file_args,
4234                      sizeof(write_file_args) / sizeof(struct command_arg), ' ');
4235 
4236   if (!append && access(ds_filename.str, F_OK) == 0) {
4237     /* The file should not be overwritten */
4238     die("File already exist: '%s'", ds_filename.str);
4239   }
4240 
4241   ds_content = command->content;
4242   /* If it hasn't been done already by a loop iteration, fill it in */
4243   if (!ds_content.str) {
4244     /* If no delimiter was provided, use EOF */
4245     if (ds_delimiter.length == 0) dynstr_set(&ds_delimiter, "EOF");
4246 
4247     init_dynamic_string(&ds_content, "", 1024, 1024);
4248     read_until_delimiter(&ds_content, &ds_delimiter);
4249     command->content = ds_content;
4250   }
4251   /* This function could be called even if "false", so check before printing */
4252   if (cur_block->ok) {
4253     DBUG_PRINT("info", ("Writing to file: %s", ds_filename.str));
4254     str_to_file2(ds_filename.str, ds_content.str, ds_content.length, append);
4255   }
4256   dynstr_free(&ds_filename);
4257   dynstr_free(&ds_delimiter);
4258 }
4259 
4260 /*
4261   SYNOPSIS
4262   do_write_file
4263   command	called command
4264 
4265   DESCRIPTION
4266   write_file <file_name> [<delimiter>];
4267   <what to write line 1>
4268   <...>
4269   < what to write line n>
4270   EOF
4271 
4272   --write_file <file_name>;
4273   <what to write line 1>
4274   <...>
4275   < what to write line n>
4276   EOF
4277 
4278   Write everything between the "write_file" command and 'delimiter'
4279   to "file_name"
4280 
4281   NOTE! Will fail if <file_name> exists
4282 
4283   Default <delimiter> is EOF
4284 
4285 */
4286 
do_write_file(struct st_command * command)4287 static void do_write_file(struct st_command *command) {
4288   do_write_file_command(command, false);
4289 }
4290 
4291 /*
4292   SYNOPSIS
4293   do_append_file
4294   command	called command
4295 
4296   DESCRIPTION
4297   append_file <file_name> [<delimiter>];
4298   <what to write line 1>
4299   <...>
4300   < what to write line n>
4301   EOF
4302 
4303   --append_file <file_name>;
4304   <what to write line 1>
4305   <...>
4306   < what to write line n>
4307   EOF
4308 
4309   Append everything between the "append_file" command
4310   and 'delimiter' to "file_name"
4311 
4312   Default <delimiter> is EOF
4313 
4314 */
4315 
do_append_file(struct st_command * command)4316 static void do_append_file(struct st_command *command) {
4317   do_write_file_command(command, true);
4318 }
4319 
4320 /*
4321   SYNOPSIS
4322   do_cat_file
4323   command	called command
4324 
4325   DESCRIPTION
4326   cat_file <file_name>;
4327 
4328   Print the given file to result log
4329 
4330 */
4331 
do_cat_file(struct st_command * command)4332 static void do_cat_file(struct st_command *command) {
4333   int error;
4334   static DYNAMIC_STRING ds_filename;
4335   const struct command_arg cat_file_args[] = {
4336       {"filename", ARG_STRING, true, &ds_filename, "File to read from"}};
4337   DBUG_TRACE;
4338   command->used_replace = true;
4339 
4340   check_command_args(command, command->first_argument, cat_file_args,
4341                      sizeof(cat_file_args) / sizeof(struct command_arg), ' ');
4342 
4343   DBUG_PRINT("info", ("Reading from, file: %s", ds_filename.str));
4344 
4345   error = cat_file(&ds_res, ds_filename.str);
4346   handle_command_error(command, error);
4347   dynstr_free(&ds_filename);
4348 }
4349 
4350 /// Compare the two files.
4351 ///
4352 /// Success if 2 files are same, failure if the files are different or
4353 /// either file does not exist.
4354 ///
4355 /// @code
4356 /// diff_files <file1> <file2>;
4357 /// @endcode
4358 ///
4359 /// @param command Pointer to the st_command structure which holds the
4360 ///                arguments and information for the command.
do_diff_files(struct st_command * command)4361 static void do_diff_files(struct st_command *command) {
4362   static DYNAMIC_STRING ds_filename1;
4363   static DYNAMIC_STRING ds_filename2;
4364 
4365   const struct command_arg diff_file_args[] = {
4366       {"file1", ARG_STRING, true, &ds_filename1, "First file to diff"},
4367       {"file2", ARG_STRING, true, &ds_filename2, "Second file to diff"}};
4368 
4369   check_command_args(command, command->first_argument, diff_file_args,
4370                      sizeof(diff_file_args) / sizeof(struct command_arg), ' ');
4371 
4372   if (access(ds_filename1.str, F_OK) != 0)
4373     die("Command \"diff_files\" failed, file '%s' does not exist.",
4374         ds_filename1.str);
4375 
4376   if (access(ds_filename2.str, F_OK) != 0)
4377     die("Command \"diff_files\" failed, file '%s' does not exist.",
4378         ds_filename2.str);
4379 
4380   int error = 0;
4381   if ((error = compare_files(ds_filename1.str, ds_filename2.str)) &&
4382       match_expected_error(command, error, nullptr) < 0) {
4383     // Compare of the two files failed, append them to output so the
4384     // failure can be analyzed, but only if it was not expected to fail.
4385     show_diff(&ds_res, ds_filename1.str, ds_filename2.str);
4386     if (log_file.write(ds_res.str, ds_res.length) || log_file.flush())
4387       cleanup_and_exit(1);
4388     dynstr_set(&ds_res, nullptr);
4389   }
4390 
4391   free_dynamic_strings(&ds_filename1, &ds_filename2);
4392   handle_command_error(command, error);
4393 }
4394 
find_connection_by_name(const char * name)4395 static struct st_connection *find_connection_by_name(const char *name) {
4396   struct st_connection *con;
4397   for (con = connections; con < next_con; con++) {
4398     if (!std::strcmp(con->name, name)) {
4399       return con;
4400     }
4401   }
4402   return nullptr; /* Connection not found */
4403 }
4404 
4405 /*
4406   SYNOPSIS
4407   do_send_quit
4408   command	called command
4409 
4410   DESCRIPTION
4411   Sends a simple quit command to the server for the named connection.
4412 
4413 */
4414 
do_send_quit(struct st_command * command)4415 static void do_send_quit(struct st_command *command) {
4416   char *p = command->first_argument;
4417   const char *name;
4418   struct st_connection *con;
4419 
4420   DBUG_TRACE;
4421   DBUG_PRINT("enter", ("name: '%s'", p));
4422 
4423   if (!*p) die("Missing connection name in send_quit");
4424   name = p;
4425   while (*p && !my_isspace(charset_info, *p)) p++;
4426 
4427   if (*p) *p++ = 0;
4428   command->last_argument = p;
4429 
4430   if (!(con = find_connection_by_name(name)))
4431     die("connection '%s' not found in connection pool", name);
4432 
4433   simple_command(&con->mysql, COM_QUIT, nullptr, 0, 1);
4434 }
4435 
4436 /*
4437   SYNOPSIS
4438   do_change_user
4439   command       called command
4440 
4441   DESCRIPTION
4442   change_user [<user>], [<passwd>], [<db>]
4443   <user> - user to change to
4444   <passwd> - user password
4445   <db> - default database
4446 
4447   Changes the user and causes the database specified by db to become
4448   the default (current) database for the the current connection.
4449 
4450 */
4451 
do_change_user(struct st_command * command)4452 static void do_change_user(struct st_command *command) {
4453   MYSQL *mysql = &cur_con->mysql;
4454   static DYNAMIC_STRING ds_user, ds_passwd, ds_db;
4455   const struct command_arg change_user_args[] = {
4456       {"user", ARG_STRING, false, &ds_user, "User to connect as"},
4457       {"password", ARG_STRING, false, &ds_passwd,
4458        "Password used when connecting"},
4459       {"database", ARG_STRING, false, &ds_db,
4460        "Database to select after connect"},
4461   };
4462 
4463   DBUG_TRACE;
4464 
4465   check_command_args(command, command->first_argument, change_user_args,
4466                      sizeof(change_user_args) / sizeof(struct command_arg),
4467                      ',');
4468 
4469   if (cur_con->stmt) {
4470     mysql_stmt_close(cur_con->stmt);
4471     cur_con->stmt = nullptr;
4472   }
4473 
4474   if (!ds_user.length) {
4475     dynstr_set(&ds_user, mysql->user);
4476 
4477     if (!ds_passwd.length) dynstr_set(&ds_passwd, mysql->passwd);
4478 
4479     if (!ds_db.length) dynstr_set(&ds_db, mysql->db);
4480   }
4481 
4482   DBUG_PRINT("info",
4483              ("connection: '%s' user: '%s' password: '%s' database: '%s'",
4484               cur_con->name, ds_user.str, ds_passwd.str, ds_db.str));
4485 
4486   if (mysql_change_user(mysql, ds_user.str, ds_passwd.str, ds_db.str)) {
4487     handle_error(curr_command, mysql_errno(mysql), mysql_error(mysql),
4488                  mysql_sqlstate(mysql), &ds_res);
4489     mysql->reconnect = true;
4490     mysql_reconnect(&cur_con->mysql);
4491   }
4492 
4493   dynstr_free(&ds_user);
4494   dynstr_free(&ds_passwd);
4495   dynstr_free(&ds_db);
4496 }
4497 
4498 /*
4499   SYNOPSIS
4500   do_perl
4501   command	command handle
4502 
4503   DESCRIPTION
4504   perl [<delimiter>];
4505   <perlscript line 1>
4506   <...>
4507   <perlscript line n>
4508   EOF
4509 
4510   Execute everything after "perl" until <delimiter> as perl.
4511   Useful for doing more advanced things
4512   but still being able to execute it on all platforms.
4513 
4514   Default <delimiter> is EOF
4515 */
4516 
do_perl(struct st_command * command)4517 static void do_perl(struct st_command *command) {
4518   int error;
4519   File fd;
4520   FILE *res_file;
4521   char buf[FN_REFLEN + 10];
4522   char temp_file_path[FN_REFLEN];
4523   static DYNAMIC_STRING ds_script;
4524   static DYNAMIC_STRING ds_delimiter;
4525   const struct command_arg perl_args[] = {{"delimiter", ARG_STRING, false,
4526                                            &ds_delimiter,
4527                                            "Delimiter to read until"}};
4528   DBUG_TRACE;
4529 
4530   check_command_args(command, command->first_argument, perl_args,
4531                      sizeof(perl_args) / sizeof(struct command_arg), ' ');
4532 
4533   ds_script = command->content;
4534   /* If it hasn't been done already by a loop iteration, fill it in */
4535   if (!ds_script.str) {
4536     /* If no delimiter was provided, use EOF */
4537     if (ds_delimiter.length == 0) dynstr_set(&ds_delimiter, "EOF");
4538 
4539     init_dynamic_string(&ds_script, "", 1024, 1024);
4540     read_until_delimiter(&ds_script, &ds_delimiter);
4541     command->content = ds_script;
4542   }
4543 
4544   /* This function could be called even if "false", so check before doing */
4545   if (cur_block->ok) {
4546     DBUG_PRINT("info", ("Executing perl: %s", ds_script.str));
4547 
4548     /* Create temporary file name */
4549     if ((fd =
4550              create_temp_file(temp_file_path, getenv("MYSQLTEST_VARDIR"), "tmp",
4551                               O_CREAT | O_RDWR, KEEP_FILE, MYF(MY_WME))) < 0)
4552       die("Failed to create temporary file for perl command");
4553     my_close(fd, MYF(0));
4554 
4555     /* Compatibility for Perl 5.24 and newer. */
4556     std::string script = "push @INC, \".\";\n";
4557     script.append(ds_script.str, ds_script.length);
4558 
4559     str_to_file(temp_file_path, &script[0], script.size());
4560 
4561     /* Format the "perl <filename>" command */
4562     snprintf(buf, sizeof(buf), "perl %s", temp_file_path);
4563 
4564     if (!(res_file = popen(buf, "r")) && command->abort_on_error)
4565       die("popen(\"%s\", \"r\") failed", buf);
4566 
4567     while (fgets(buf, sizeof(buf), res_file)) {
4568       if (disable_result_log) {
4569         buf[std::strlen(buf) - 1] = 0;
4570         DBUG_PRINT("exec_result", ("%s", buf));
4571       } else {
4572         replace_dynstr_append(&ds_res, buf);
4573       }
4574     }
4575     error = pclose(res_file);
4576 
4577     /* Remove the temporary file, but keep it if perl failed */
4578     if (!error) my_delete(temp_file_path, MYF(0));
4579 
4580     /* Check for error code that indicates perl could not be started */
4581     int exstat = WEXITSTATUS(error);
4582 #ifdef _WIN32
4583     if (exstat == 1) /* Text must begin 'perl not found' as mtr looks for it */
4584       abort_not_supported_test("perl not found in path or did not start");
4585 #else
4586     if (exstat == 127) abort_not_supported_test("perl not found in path");
4587 #endif
4588     else
4589       handle_command_error(command, exstat);
4590   }
4591   dynstr_free(&ds_delimiter);
4592 }
4593 
4594 /*
4595   Print the content between echo and <delimiter> to result file.
4596   Evaluate all variables in the string before printing, allow
4597   for variable names to be escaped using \
4598 
4599   SYNOPSIS
4600   do_echo()
4601   command  called command
4602 
4603   DESCRIPTION
4604   echo text
4605   Print the text after echo until end of command to result file
4606 
4607   echo $<var_name>
4608   Print the content of the variable <var_name> to result file
4609 
4610   echo Some text $<var_name>
4611   Print "Some text" plus the content of the variable <var_name> to
4612   result file
4613 
4614   echo Some text \$<var_name>
4615   Print "Some text" plus $<var_name> to result file
4616 */
4617 
do_echo(struct st_command * command)4618 static int do_echo(struct st_command *command) {
4619   DYNAMIC_STRING ds_echo;
4620   DBUG_TRACE;
4621 
4622   init_dynamic_string(&ds_echo, "", command->query_len, 256);
4623   do_eval(&ds_echo, command->first_argument, command->end, false);
4624   dynstr_append_mem(&ds_res, ds_echo.str, ds_echo.length);
4625   dynstr_append_mem(&ds_res, "\n", 1);
4626   dynstr_free(&ds_echo);
4627   command->last_argument = command->end;
4628   return 0;
4629 }
4630 
do_wait_for_slave_to_stop(struct st_command * c MY_ATTRIBUTE ((unused)))4631 static void do_wait_for_slave_to_stop(
4632     struct st_command *c MY_ATTRIBUTE((unused))) {
4633   static int SLAVE_POLL_INTERVAL = 300000;
4634   MYSQL *mysql = &cur_con->mysql;
4635   for (;;) {
4636     MYSQL_RES *res = nullptr;
4637     MYSQL_ROW row;
4638     int done;
4639 
4640     if (mysql_query_wrapper(
4641             mysql,
4642             "SELECT 'Slave_running' as Variable_name,"
4643             " IF(count(*)>0,'ON','OFF') as Value FROM"
4644             " performance_schema.replication_applier_status ras,"
4645             "performance_schema.replication_connection_status rcs WHERE "
4646             "ras.SERVICE_STATE='ON' AND rcs.SERVICE_STATE='ON'") ||
4647         !(res = mysql_store_result_wrapper(mysql)))
4648 
4649       die("Query failed while probing slave for stop: %s", mysql_error(mysql));
4650 
4651     if (!(row = mysql_fetch_row_wrapper(res)) || !row[1]) {
4652       mysql_free_result_wrapper(res);
4653       die("Strange result from query while probing slave for stop");
4654     }
4655     done = !std::strcmp(row[1], "OFF");
4656     mysql_free_result_wrapper(res);
4657     if (done) break;
4658     my_sleep(SLAVE_POLL_INTERVAL);
4659   }
4660   return;
4661 }
4662 
do_sync_with_master2(struct st_command * command,long offset)4663 static void do_sync_with_master2(struct st_command *command, long offset) {
4664   MYSQL_RES *res;
4665   MYSQL_ROW row;
4666   MYSQL *mysql = &cur_con->mysql;
4667   char query_buf[FN_REFLEN + 128];
4668   int timeout = 300; /* seconds */
4669 
4670   if (!master_pos.file[0])
4671     die("Calling 'sync_with_master' without calling 'save_master_pos'");
4672 
4673   sprintf(query_buf, "select master_pos_wait('%s', %ld, %d)", master_pos.file,
4674           master_pos.pos + offset, timeout);
4675 
4676   if (mysql_query_wrapper(mysql, query_buf))
4677     die("failed in '%s': %d: %s", query_buf, mysql_errno(mysql),
4678         mysql_error(mysql));
4679 
4680   if (!(res = mysql_store_result_wrapper(mysql)))
4681     die("mysql_store_result() returned NULL for '%s'", query_buf);
4682   if (!(row = mysql_fetch_row_wrapper(res))) {
4683     mysql_free_result_wrapper(res);
4684     die("empty result in %s", query_buf);
4685   }
4686 
4687   int result = -99;
4688   const char *result_str = row[0];
4689   if (result_str) result = atoi(result_str);
4690 
4691   mysql_free_result_wrapper(res);
4692 
4693   if (!result_str || result < 0) {
4694     /* master_pos_wait returned NULL or < 0 */
4695     show_query(mysql, "SHOW MASTER STATUS");
4696     show_query(mysql, "SHOW SLAVE STATUS");
4697     show_query(mysql, "SHOW PROCESSLIST");
4698     fprintf(stderr, "analyze: sync_with_master\n");
4699 
4700     if (!result_str) {
4701       /*
4702         master_pos_wait returned NULL. This indicates that
4703         slave SQL thread is not started, the slave's master
4704         information is not initialized, the arguments are
4705         incorrect, or an error has occurred
4706       */
4707       die("%.*s failed: '%s' returned NULL "
4708           "indicating slave SQL thread failure",
4709           static_cast<int>(command->first_word_len), command->query, query_buf);
4710     }
4711 
4712     if (result == -1)
4713       die("%.*s failed: '%s' returned -1 "
4714           "indicating timeout after %d seconds",
4715           static_cast<int>(command->first_word_len), command->query, query_buf,
4716           timeout);
4717     else
4718       die("%.*s failed: '%s' returned unknown result :%d",
4719           static_cast<int>(command->first_word_len), command->query, query_buf,
4720           result);
4721   }
4722 
4723   return;
4724 }
4725 
do_sync_with_master(struct st_command * command)4726 static void do_sync_with_master(struct st_command *command) {
4727   long offset = 0;
4728   char *p = command->first_argument;
4729   const char *offset_start = p;
4730   if (*offset_start) {
4731     for (; my_isdigit(charset_info, *p); p++) offset = offset * 10 + *p - '0';
4732 
4733     if (*p && !my_isspace(charset_info, *p))
4734       die("Invalid integer argument \"%s\"", offset_start);
4735     command->last_argument = p;
4736   }
4737   do_sync_with_master2(command, offset);
4738   return;
4739 }
4740 
4741 /*
4742   Wait for ndb binlog injector to be up-to-date with all changes
4743   done on the local mysql server
4744 */
4745 
ndb_wait_for_binlog_injector(void)4746 static void ndb_wait_for_binlog_injector(void) {
4747   MYSQL_RES *res;
4748   MYSQL_ROW row;
4749   MYSQL *mysql = &cur_con->mysql;
4750   const char *query;
4751   ulong have_ndbcluster;
4752   if (mysql_query_wrapper(
4753           mysql, query = "select count(*) from information_schema.engines"
4754                          "  where engine = 'ndbcluster' and"
4755                          "        support in ('YES', 'DEFAULT')"))
4756     die("'%s' failed: %d %s", query, mysql_errno(mysql), mysql_error(mysql));
4757   if (!(res = mysql_store_result_wrapper(mysql)))
4758     die("mysql_store_result() returned NULL for '%s'", query);
4759   if (!(row = mysql_fetch_row_wrapper(res)))
4760     die("Query '%s' returned empty result", query);
4761 
4762   have_ndbcluster = std::strcmp(row[0], "1") == 0;
4763   mysql_free_result_wrapper(res);
4764 
4765   if (!have_ndbcluster) {
4766     return;
4767   }
4768 
4769   ulonglong start_epoch = 0, handled_epoch = 0, latest_trans_epoch = 0,
4770             latest_handled_binlog_epoch = 0, start_handled_binlog_epoch = 0;
4771   const int WaitSeconds = 150;
4772 
4773   int count = 0;
4774   int do_continue = 1;
4775   while (do_continue) {
4776     const char binlog[] = "binlog";
4777     const char latest_trans_epoch_str[] = "latest_trans_epoch=";
4778     const char latest_handled_binlog_epoch_str[] =
4779         "latest_handled_binlog_epoch=";
4780     if (count) my_sleep(100 * 1000); /* 100ms */
4781 
4782     if (mysql_query_wrapper(mysql, query = "show engine ndb status"))
4783       die("failed in '%s': %d %s", query, mysql_errno(mysql),
4784           mysql_error(mysql));
4785 
4786     if (!(res = mysql_store_result_wrapper(mysql)))
4787       die("mysql_store_result() returned NULL for '%s'", query);
4788 
4789     while ((row = mysql_fetch_row_wrapper(res))) {
4790       if (std::strcmp(row[1], binlog) == 0) {
4791         const char *status = row[2];
4792 
4793         /* latest_trans_epoch */
4794         while (*status && std::strncmp(status, latest_trans_epoch_str,
4795                                        sizeof(latest_trans_epoch_str) - 1))
4796           status++;
4797         if (*status) {
4798           status += sizeof(latest_trans_epoch_str) - 1;
4799           latest_trans_epoch = my_strtoull(status, (char **)nullptr, 10);
4800         } else
4801           die("result does not contain '%s' in '%s'", latest_trans_epoch_str,
4802               query);
4803 
4804         /* latest_handled_binlog */
4805         while (*status &&
4806                std::strncmp(status, latest_handled_binlog_epoch_str,
4807                             sizeof(latest_handled_binlog_epoch_str) - 1))
4808           status++;
4809         if (*status) {
4810           status += sizeof(latest_handled_binlog_epoch_str) - 1;
4811           latest_handled_binlog_epoch =
4812               my_strtoull(status, (char **)nullptr, 10);
4813         } else
4814           die("result does not contain '%s' in '%s'",
4815               latest_handled_binlog_epoch_str, query);
4816 
4817         if (count == 0) {
4818           start_epoch = latest_trans_epoch;
4819           start_handled_binlog_epoch = latest_handled_binlog_epoch;
4820         }
4821         break;
4822       }
4823     }
4824     if (!row) die("result does not contain '%s' in '%s'", binlog, query);
4825     if (latest_handled_binlog_epoch > handled_epoch) count = 0;
4826     handled_epoch = latest_handled_binlog_epoch;
4827     count++;
4828     if (latest_handled_binlog_epoch >= start_epoch)
4829       do_continue = 0;
4830     else if (count > (WaitSeconds * 10)) {
4831       die("do_save_master_pos() timed out after %u s waiting for "
4832           "last committed epoch to be applied by the "
4833           "Ndb binlog injector.  "
4834           "Ndb epoch %llu/%llu to be handled.  "
4835           "Last handled epoch : %llu/%llu.  "
4836           "First handled epoch : %llu/%llu.",
4837           WaitSeconds, start_epoch >> 32, start_epoch & 0xffffffff,
4838           latest_handled_binlog_epoch >> 32,
4839           latest_handled_binlog_epoch & 0xffffffff,
4840           start_handled_binlog_epoch >> 32,
4841           start_handled_binlog_epoch & 0xffffffff);
4842     }
4843 
4844     mysql_free_result_wrapper(res);
4845   }
4846 }
4847 
do_save_master_pos()4848 static int do_save_master_pos() {
4849   MYSQL_RES *res;
4850   MYSQL_ROW row;
4851   MYSQL *mysql = &cur_con->mysql;
4852   const char *query;
4853   DBUG_TRACE;
4854   /*
4855     when ndb binlog is on, this call will wait until last updated epoch
4856     (locally in the mysqld) has been received into the binlog
4857   */
4858   ndb_wait_for_binlog_injector();
4859 
4860   if (mysql_query_wrapper(mysql, query = "show master status"))
4861     die("failed in 'show master status': %d %s", mysql_errno(mysql),
4862         mysql_error(mysql));
4863 
4864   if (!(res = mysql_store_result_wrapper(mysql)))
4865     die("mysql_store_result() retuned NULL for '%s'", query);
4866   if (!(row = mysql_fetch_row_wrapper(res)))
4867     die("empty result in show master status");
4868   my_stpnmov(master_pos.file, row[0], sizeof(master_pos.file) - 1);
4869   master_pos.pos = strtoul(row[1], (char **)nullptr, 10);
4870   mysql_free_result_wrapper(res);
4871   return 0;
4872 }
4873 
4874 /*
4875   Check if a variable name is valid or not.
4876 
4877   SYNOPSIS
4878   check_variable_name()
4879   var_name     - pointer to the beginning of variable name
4880   var_name_end - pointer to the end of variable name
4881   dollar_flag  - flag to check whether variable name should start with '$'
4882 */
check_variable_name(const char * var_name,const char * var_name_end,const bool dollar_flag)4883 static void check_variable_name(const char *var_name, const char *var_name_end,
4884                                 const bool dollar_flag) {
4885   char save_var_name[MAX_VAR_NAME_LENGTH];
4886   strmake(save_var_name, var_name, (var_name_end - var_name));
4887 
4888   // Check if variable name should start with '$'
4889   if (!dollar_flag && (*var_name != '$'))
4890     die("Variable name '%s' should start with '$'", save_var_name);
4891 
4892   if (*var_name == '$') var_name++;
4893 
4894   // Check if variable name exists or not
4895   if (var_name == var_name_end) die("Missing variable name.");
4896 
4897   // Check for non alphanumeric character(s) in variable name
4898   while ((var_name != var_name_end) && my_isvar(charset_info, *var_name))
4899     var_name++;
4900 
4901   if (var_name != var_name_end)
4902     die("Invalid variable name '%s'", save_var_name);
4903 }
4904 
4905 /*
4906   Check if the pointer points to an operator.
4907 
4908   SYNOPSIS
4909   is_operator()
4910   op - character pointer to mathematical expression
4911 */
is_operator(const char * op)4912 static bool is_operator(const char *op) {
4913   if (*op == '+')
4914     return true;
4915   else if (*op == '-')
4916     return true;
4917   else if (*op == '*')
4918     return true;
4919   else if (*op == '/')
4920     return true;
4921   else if (*op == '%')
4922     return true;
4923   else if (*op == '&' && *(op + 1) == '&')
4924     return true;
4925   else if (*op == '|' && *(op + 1) == '|')
4926     return true;
4927   else if (*op == '&')
4928     return true;
4929   else if (*op == '|')
4930     return true;
4931   else if (*op == '^')
4932     return true;
4933   else if (*op == '>' && *(op + 1) == '>')
4934     return true;
4935   else if (*op == '<' && *(op + 1) == '<')
4936     return true;
4937 
4938   return false;
4939 }
4940 
4941 /*
4942   Perform basic mathematical operation.
4943 
4944   SYNOPSIS
4945   do_expr()
4946   command - command handle
4947 
4948   DESCRIPTION
4949   expr $<var_name>= <operand1> <operator> <operand2>
4950   Perform basic mathematical operation and store the result
4951   in a variable($<var_name>). Both <operand1> and <operand2>
4952   should be valid MTR variables.
4953 
4954   'expr' command supports only binary operators that operates
4955   on two operands and manipulates them to return a result.
4956 
4957   Following mathematical operators are supported.
4958   1 Arithmetic Operators
4959     1.1 Addition
4960     1.2 Subtraction
4961     1.3 Multiplication
4962     1.4 Division
4963     1.5 Modulo
4964 
4965   2 Logical Operators
4966     2.1 Logical AND
4967     2.2 Logical OR
4968 
4969   3 Bitwise Operators
4970     3.1 Binary AND
4971     3.2 Binary OR
4972     3.3 Binary XOR
4973     3.4 Binary Left Shift
4974     3.5 Binary Right Shift
4975 
4976   NOTE
4977   1. Non-integer operand is truncated to integer value for operations
4978      that dont support non-integer operand.
4979   2. If the result is an infinite value, then expr command will return
4980      'inf' keyword to indicate the result is infinity.
4981   3. Division by 0 will result in an infinite value and expr command
4982      will return 'inf' keyword to indicate the result is infinity.
4983 */
do_expr(struct st_command * command)4984 static void do_expr(struct st_command *command) {
4985   DBUG_TRACE;
4986 
4987   const char *p = command->first_argument;
4988   if (!*p) die("Missing arguments to expr command.");
4989 
4990   // Find <var_name>
4991   const char *var_name = p;
4992   while (*p && (*p != '=') && !my_isspace(charset_info, *p)) p++;
4993   const char *var_name_end = p;
4994   check_variable_name(var_name, var_name_end, true);
4995 
4996   // Skip spaces between <var_name> and '='
4997   while (my_isspace(charset_info, *p)) p++;
4998 
4999   if (*p++ != '=') die("Missing assignment operator in expr command.");
5000 
5001   // Skip spaces after '='
5002   while (*p && my_isspace(charset_info, *p)) p++;
5003 
5004   // Save the mathematical expression in a variable
5005   const char *expr = p;
5006 
5007   // First operand in the expression
5008   const char *operand_name = p;
5009   while (*p && !is_operator(p) && !my_isspace(charset_info, *p)) p++;
5010   const char *operand_name_end = p;
5011   check_variable_name(operand_name, operand_name_end, false);
5012   VAR *v1 = var_get(operand_name, &operand_name_end, false, false);
5013 
5014   double operand1;
5015   if ((my_isdigit(charset_info, *v1->str_val)) ||
5016       ((*v1->str_val == '-') && my_isdigit(charset_info, *(v1->str_val + 1))))
5017     operand1 = strtod(v1->str_val, nullptr);
5018   else
5019     die("Undefined/invalid first operand '$%s' in expr command.", v1->name);
5020 
5021   // Skip spaces after the first operand
5022   while (*p && my_isspace(charset_info, *p)) p++;
5023 
5024   // Extract the operator
5025   const char *operator_start = p;
5026   while (*p && (*p != '$') &&
5027          !(my_isspace(charset_info, *p) || my_isvar(charset_info, *p)))
5028     p++;
5029 
5030   char math_operator[3];
5031   strmake(math_operator, operator_start, (p - operator_start));
5032   if (!std::strlen(math_operator))
5033     die("Missing mathematical operator in expr command.");
5034 
5035   // Skip spaces after the operator
5036   while (*p && my_isspace(charset_info, *p)) p++;
5037 
5038   // Second operand in the expression
5039   operand_name = p;
5040   while (*p && !my_isspace(charset_info, *p)) p++;
5041   operand_name_end = p;
5042   check_variable_name(operand_name, operand_name_end, false);
5043   VAR *v2 = var_get(operand_name, &operand_name_end, false, false);
5044 
5045   double operand2;
5046   if ((my_isdigit(charset_info, *v2->str_val)) ||
5047       ((*v2->str_val == '-') && my_isdigit(charset_info, *(v2->str_val + 1))))
5048     operand2 = strtod(v2->str_val, nullptr);
5049   else
5050     die("Undefined/invalid second operand '$%s' in expr command.", v2->name);
5051 
5052   // Skip spaces at the end
5053   while (*p && my_isspace(charset_info, *p)) p++;
5054 
5055   // Check for any spurious text after the second operand
5056   if (*p) die("Invalid mathematical expression '%s' in expr command.", expr);
5057 
5058   double result;
5059   // Arithmetic Operators
5060   if (!std::strcmp(math_operator, "+"))
5061     result = operand1 + operand2;
5062   else if (!std::strcmp(math_operator, "-"))
5063     result = operand1 - operand2;
5064   else if (!std::strcmp(math_operator, "*"))
5065     result = operand1 * operand2;
5066   else if (!std::strcmp(math_operator, "/")) {
5067     if (operand2 == 0.0) {
5068       if (operand1 == 0.0)
5069         result = std::numeric_limits<double>::quiet_NaN();
5070       else
5071         result = std::numeric_limits<double>::infinity();
5072     } else
5073       result = operand1 / operand2;
5074   } else if (!std::strcmp(math_operator, "%"))
5075     result = (int)operand1 % (int)operand2;
5076   // Logical Operators
5077   else if (!std::strcmp(math_operator, "&&"))
5078     result = operand1 && operand2;
5079   else if (!std::strcmp(math_operator, "||"))
5080     result = operand1 || operand2;
5081   // Bitwise Operators
5082   else if (!std::strcmp(math_operator, "&"))
5083     result = (int)operand1 & (int)operand2;
5084   else if (!std::strcmp(math_operator, "|"))
5085     result = (int)operand1 | (int)operand2;
5086   else if (!std::strcmp(math_operator, "^"))
5087     result = (int)operand1 ^ (int)operand2;
5088   else if (!std::strcmp(math_operator, ">>"))
5089     result = (int)operand1 >> (int)operand2;
5090   else if (!std::strcmp(math_operator, "<<"))
5091     result = (int)operand1 << (int)operand2;
5092   else
5093     die("Invalid operator '%s' in expr command", math_operator);
5094 
5095   char buf[128];
5096   size_t result_len;
5097   // Check if result is an infinite value
5098   if (std::isnan(result)) {
5099     // Print 'nan' if result is Not a Number
5100     result_len = snprintf(buf, sizeof(buf), "%s", "nan");
5101   } else if (!std::isinf(result)) {
5102     const char *format = (result < 1e10 && result > -1e10) ? "%f" : "%g";
5103     result_len = snprintf(buf, sizeof(buf), format, result);
5104   } else
5105     // Print 'inf' if result is an infinite value
5106     result_len = snprintf(buf, sizeof(buf), "%s", "inf");
5107 
5108   if (result < 1e10 && result > -1e10) {
5109     /*
5110       Remove the trailing 0's i.e 2.0000000 need to be represented
5111       as 2 for consistency, 2.0010000 also becomes 2.001.
5112     */
5113     while (buf[result_len - 1] == '0') result_len--;
5114 
5115     // Remove trailing '.' if exists
5116     if (buf[result_len - 1] == '.') result_len--;
5117   }
5118 
5119   var_set(var_name, var_name_end, buf, buf + result_len);
5120   command->last_argument = command->end;
5121 }
5122 
5123 /*
5124   Assign the variable <var_name> with <var_val>
5125 
5126   SYNOPSIS
5127   do_let()
5128   query	called command
5129 
5130   DESCRIPTION
5131   let $<var_name>=<var_val><delimiter>
5132 
5133   <var_name>  - is the string string found between the $ and =
5134   <var_val>   - is the content between the = and <delimiter>, it may span
5135   multiple line and contain any characters except <delimiter>
5136   <delimiter> - is a string containing of one or more chars, default is ;
5137 
5138   RETURN VALUES
5139   Program will die if error detected
5140 */
5141 
do_let(struct st_command * command)5142 static void do_let(struct st_command *command) {
5143   const char *p = command->first_argument;
5144   const char *var_name, *var_name_end;
5145   DYNAMIC_STRING let_rhs_expr;
5146   DBUG_TRACE;
5147 
5148   init_dynamic_string(&let_rhs_expr, "", 512, 2048);
5149 
5150   /* Find <var_name> */
5151   if (!*p) die("Missing arguments to let");
5152   var_name = p;
5153   while (*p && (*p != '=') && !my_isspace(charset_info, *p)) p++;
5154   var_name_end = p;
5155   if (var_name == var_name_end ||
5156       (var_name + 1 == var_name_end && *var_name == '$'))
5157     die("Missing variable name in let");
5158   while (my_isspace(charset_info, *p)) p++;
5159   if (*p++ != '=') die("Missing assignment operator in let");
5160 
5161   /* Find start of <var_val> */
5162   while (*p && my_isspace(charset_info, *p)) p++;
5163 
5164   do_eval(&let_rhs_expr, p, command->end, false);
5165 
5166   command->last_argument = command->end;
5167   /* Assign var_val to var_name */
5168   var_set(var_name, var_name_end, let_rhs_expr.str,
5169           (let_rhs_expr.str + let_rhs_expr.length));
5170   dynstr_free(&let_rhs_expr);
5171   revert_properties();
5172 }
5173 
5174 /*
5175   Sleep the number of specified seconds
5176 
5177   SYNOPSIS
5178   do_sleep()
5179   q	       called command
5180 
5181   DESCRIPTION
5182   Sleep <seconds>
5183 
5184   The argument provided to --sleep command is not required to be
5185   a whole number and can have fractional parts as well. For
5186   example, '--sleep 0.1' is valid.
5187 
5188 */
5189 
do_sleep(struct st_command * command)5190 static int do_sleep(struct st_command *command) {
5191   int error = 0;
5192   double sleep_val;
5193   char *p;
5194   static DYNAMIC_STRING ds_sleep;
5195   const struct command_arg sleep_args[] = {{"sleep_delay", ARG_STRING, true,
5196                                             &ds_sleep,
5197                                             "Number of seconds to sleep."}};
5198   check_command_args(command, command->first_argument, sleep_args,
5199                      sizeof(sleep_args) / sizeof(struct command_arg), ' ');
5200 
5201   p = ds_sleep.str;
5202   const char *sleep_end = ds_sleep.str + ds_sleep.length;
5203   while (my_isspace(charset_info, *p)) p++;
5204   if (!*p)
5205     die("Missing argument to %.*s", static_cast<int>(command->first_word_len),
5206         command->query);
5207   const char *sleep_start = p;
5208   /* Check that arg starts with a digit, not handled by my_strtod */
5209   if (!my_isdigit(charset_info, *sleep_start))
5210     die("Invalid argument to %.*s \"%s\"",
5211         static_cast<int>(command->first_word_len), command->query, sleep_start);
5212   sleep_val = my_strtod(sleep_start, &sleep_end, &error);
5213   check_eol_junk_line(sleep_end);
5214   if (error)
5215     die("Invalid argument to %.*s \"%s\"",
5216         static_cast<int>(command->first_word_len), command->query,
5217         command->first_argument);
5218   dynstr_free(&ds_sleep);
5219 
5220   DBUG_PRINT("info", ("sleep_val: %f", sleep_val));
5221   if (sleep_val) my_sleep((ulong)(sleep_val * 1000000L));
5222   return 0;
5223 }
5224 
do_set_charset(struct st_command * command)5225 static void do_set_charset(struct st_command *command) {
5226   char *charset_name = command->first_argument;
5227   char *p;
5228 
5229   if (!charset_name || !*charset_name)
5230     die("Missing charset name in 'character_set'");
5231   /* Remove end space */
5232   p = charset_name;
5233   while (*p && !my_isspace(charset_info, *p)) p++;
5234   if (*p) *p++ = 0;
5235   command->last_argument = p;
5236   charset_info =
5237       get_charset_by_csname(charset_name, MY_CS_PRIMARY, MYF(MY_WME));
5238   if (!charset_info)
5239     abort_not_supported_test("Test requires charset '%s'", charset_name);
5240 }
5241 
5242 /// Check if the bug number argument to disable_testcase is in a proper
5243 /// format.
5244 ///
5245 /// Bug number argument should follow 'BUG#XXXX' format
5246 ///
5247 ///   - Keyword 'BUG" is case-insensitive
5248 ///   - XXXX should contain only digits
5249 ///
5250 /// @param bug_number String representing a bug number
5251 ///
5252 /// @retval True if the bug number argument is in correct format, false
5253 ///         otherwise.
validate_bug_number_argument(std::string bug_number)5254 static bool validate_bug_number_argument(std::string bug_number) {
5255   // Convert the string to lowercase characters
5256   std::transform(bug_number.begin(), bug_number.end(), bug_number.begin(),
5257                  ::tolower);
5258 
5259   // Check if string representing a bug number starts 'BUG' keyword.
5260   // Note: This keyword is case-inseinsitive.
5261   if (bug_number.substr(0, 3).compare("bug") != 0) return false;
5262 
5263   // Check if the string contains '#' after 'BUG' keyword
5264   if (bug_number.at(3) != '#') return false;
5265 
5266   // Check if the bug number string contains only digits after '#'
5267   if (get_int_val(bug_number.substr(4).c_str()) == -1) return false;
5268 
5269   return true;
5270 }
5271 
5272 /// Disable or don't execute the statements or commands appear after
5273 /// this command until the execution is enabled by 'enable_testcase'
5274 /// command. If test cases are already disabled, then throw an error
5275 /// and abort the test execution.
5276 ///
5277 /// This command also takes a mandatory parameter specifying the bug
5278 /// number.
5279 ///
5280 /// @code
5281 /// --disable_testcase BUG#XXXX
5282 /// statements or commands
5283 /// --enable_testcase
5284 /// @endcode
5285 ///
5286 /// @param command Pointer to the st_command structure which holds the
5287 ///                arguments and information for the command.
do_disable_testcase(struct st_command * command)5288 static void do_disable_testcase(struct st_command *command) {
5289   DYNAMIC_STRING ds_bug_number;
5290   const struct command_arg disable_testcase_args[] = {
5291       {"Bug number", ARG_STRING, true, &ds_bug_number, "Bug number"}};
5292 
5293   check_command_args(command, command->first_argument, disable_testcase_args,
5294                      sizeof(disable_testcase_args) / sizeof(struct command_arg),
5295                      ' ');
5296 
5297   /// Check if the bug number argument to disable_testcase is in a
5298   /// proper format.
5299   if (validate_bug_number_argument(ds_bug_number.str) == 0) {
5300     free_dynamic_strings(&ds_bug_number);
5301     die("Bug number mentioned in '%s' command is not in a correct format. "
5302         "It should be 'BUG#XXXX', where keyword 'BUG' is case-insensitive "
5303         "and 'XXXX' should contain only digits.",
5304         command->query);
5305   }
5306 
5307   testcase_disabled = true;
5308   free_dynamic_strings(&ds_bug_number);
5309 }
5310 
5311 /**
5312   Check if process is active.
5313 
5314   @param pid  Process id.
5315 
5316   @return true if process is active, false otherwise.
5317 */
is_process_active(int pid)5318 static bool is_process_active(int pid) {
5319 #ifdef _WIN32
5320   DWORD exit_code;
5321   HANDLE proc;
5322   proc = OpenProcess(PROCESS_QUERY_INFORMATION, false, pid);
5323   if (proc == NULL) return false; /* Process could not be found. */
5324 
5325   if (!GetExitCodeProcess(proc, &exit_code)) exit_code = 0;
5326 
5327   CloseHandle(proc);
5328   if (exit_code != STILL_ACTIVE)
5329     return false; /* Error or process has terminated. */
5330 
5331   return true;
5332 #else
5333   return (kill(pid, 0) == 0);
5334 #endif
5335 }
5336 
5337 /**
5338   kill process.
5339 
5340   @param pid  Process id.
5341 
5342   @return true if process is terminated, false otherwise.
5343 */
kill_process(int pid)5344 static bool kill_process(int pid) {
5345   bool killed = true;
5346 #ifdef _WIN32
5347   HANDLE proc;
5348   proc = OpenProcess(PROCESS_TERMINATE, false, pid);
5349   if (proc == NULL) return true; /* Process could not be found. */
5350 
5351   if (!TerminateProcess(proc, 201)) killed = false;
5352 
5353   CloseHandle(proc);
5354 #else
5355   killed = (kill(pid, SIGKILL) == 0);
5356 #endif
5357   return killed;
5358 }
5359 
5360 /**
5361   Abort process.
5362 
5363   @param pid  Process id.
5364   @param path Path to create minidump file in.
5365 */
abort_process(int pid,const char * path MY_ATTRIBUTE ((unused)))5366 static void abort_process(int pid, const char *path MY_ATTRIBUTE((unused))) {
5367 #ifdef _WIN32
5368   HANDLE proc;
5369   proc = OpenProcess(PROCESS_ALL_ACCESS, false, pid);
5370   verbose_msg("Aborting pid %d (handle: %p)\n", pid, proc);
5371   if (proc != NULL) {
5372     char name[FN_REFLEN], *end;
5373     BOOL is_debugged;
5374 
5375     if (path) {
5376       /* Append mysqld.<pid>.dmp to the path. */
5377       strncpy(name, path, sizeof(name) - 1);
5378       name[sizeof(name) - 1] = '\0';
5379       end = name + std::strlen(name);
5380       /* Make sure "/mysqld.nnnnnnnnnn.dmp" fits */
5381       if ((end - name) < (sizeof(name) - 23)) {
5382         if (!is_directory_separator(end[-1])) {
5383           end[0] = FN_LIBCHAR2;  // datadir path normally uses '/'.
5384           end++;
5385         }
5386         snprintf(end, sizeof(name) + name - end - 1, "mysqld.%d.dmp", pid);
5387 
5388         verbose_msg("Creating minidump.\n");
5389         my_create_minidump(name, proc, pid);
5390       } else
5391         die("Path too long for creating minidump!\n");
5392     }
5393     /* If running in a debugger, send a break, otherwise terminate. */
5394     if (CheckRemoteDebuggerPresent(proc, &is_debugged) && is_debugged) {
5395       if (!DebugBreakProcess(proc)) {
5396         DWORD err = GetLastError();
5397         verbose_msg("DebugBreakProcess failed: %d\n", err);
5398       } else
5399         verbose_msg("DebugBreakProcess succeeded!\n");
5400       CloseHandle(proc);
5401     } else {
5402       CloseHandle(proc);
5403       (void)kill_process(pid);
5404     }
5405   } else {
5406     DWORD err = GetLastError();
5407     verbose_msg("OpenProcess failed: %d\n", err);
5408   }
5409 #else
5410   kill(pid, SIGABRT);
5411 #endif
5412 }
5413 
5414 /// Shutdown or kill the server. If timeout is set to 0 the server is
5415 /// killed or terminated immediately. Otherwise the shutdown command
5416 /// is first sent and then it waits for the server to terminate within
5417 /// 'timeout' seconds. If it has not terminated before 'timeout'
5418 /// seconds the command will fail.
5419 ///
5420 /// @note
5421 /// Currently only works with local server
5422 ///
5423 /// @param command Pointer to the st_command structure which holds the
5424 ///                arguments and information for the command. Optionally
5425 ///                including a timeout else the default of 60 seconds
do_shutdown_server(struct st_command * command)5426 static void do_shutdown_server(struct st_command *command) {
5427   static DYNAMIC_STRING ds_timeout;
5428   const struct command_arg shutdown_args[] = {
5429       {"timeout", ARG_STRING, false, &ds_timeout,
5430        "Timeout before killing server"}};
5431 
5432   check_command_args(command, command->first_argument, shutdown_args,
5433                      sizeof(shutdown_args) / sizeof(struct command_arg), ' ');
5434   if (opt_offload_count_file) {
5435     // Save the value of secondary engine execution status
5436     // before shutting down the server.
5437     if (secondary_engine->offload_count(&cur_con->mysql, "after"))
5438       cleanup_and_exit(1);
5439   }
5440 
5441   long timeout = 60;
5442 
5443   if (ds_timeout.length) {
5444     char *endptr;
5445     timeout = std::strtol(ds_timeout.str, &endptr, 10);
5446     if (*endptr != '\0')
5447       die("Illegal argument for timeout: '%s'", ds_timeout.str);
5448   }
5449 
5450   dynstr_free(&ds_timeout);
5451 
5452   MYSQL *mysql = &cur_con->mysql;
5453   std::string ds_file_name;
5454 
5455   // Get the servers pid_file name and use it to read pid
5456   if (query_get_string(mysql, "SHOW VARIABLES LIKE 'pid_file'", 1,
5457                        &ds_file_name))
5458     die("Failed to get pid_file from server");
5459 
5460   // Read the pid from the file
5461   int fd;
5462   char buff[32];
5463 
5464   if ((fd = my_open(ds_file_name.c_str(), O_RDONLY, MYF(0))) < 0)
5465     die("Failed to open file '%s'", ds_file_name.c_str());
5466 
5467   if (my_read(fd, (uchar *)&buff, sizeof(buff), MYF(0)) <= 0) {
5468     my_close(fd, MYF(0));
5469     die("pid file was empty");
5470   }
5471 
5472   my_close(fd, MYF(0));
5473 
5474   int pid = std::atoi(buff);
5475   if (pid == 0) die("Pidfile didn't contain a valid number");
5476 
5477   int error = 0;
5478   if (timeout) {
5479     // Check if we should generate a minidump on timeout.
5480     if (query_get_string(mysql, "SHOW VARIABLES LIKE 'core_file'", 1,
5481                          &ds_file_name) ||
5482         std::strcmp("ON", ds_file_name.c_str())) {
5483     } else {
5484       // Get the data dir and use it as path for a minidump if needed.
5485       if (query_get_string(mysql, "SHOW VARIABLES LIKE 'datadir'", 1,
5486                            &ds_file_name))
5487         die("Failed to get datadir from server");
5488     }
5489 
5490     const char *var_name = "$MTR_MANUAL_DEBUG";
5491     VAR *var = var_get(var_name, &var_name, false, false);
5492     if (var->int_val) {
5493       if (!kill_process(pid) && is_process_active(pid)) error = 3;
5494     } else {
5495       // Tell server to shutdown if timeout > 0.
5496       if (timeout > 0 && mysql_query_wrapper(mysql, "shutdown")) {
5497         // Failed to issue shutdown command.
5498         error = 1;
5499         goto end;
5500       }
5501 
5502       // Check that server dies
5503       do {
5504         if (!is_process_active(pid)) {
5505           DBUG_PRINT("info", ("Process %d does not exist anymore", pid));
5506           goto end;
5507         }
5508         if (timeout > 0) {
5509           DBUG_PRINT("info", ("Sleeping, timeout: %ld", timeout));
5510           my_sleep(1000000L);
5511         }
5512       } while (timeout-- > 0);
5513 
5514       error = 2;
5515 
5516       // Abort to make it easier to find the hang/problem.
5517       abort_process(pid, ds_file_name.c_str());
5518     }
5519   } else {
5520     // timeout value is 0, kill the server
5521     DBUG_PRINT("info", ("Killing server, pid: %d", pid));
5522 
5523     // kill_process can fail (bad privileges, non existing process on
5524     // *nix etc), so also check if the process is active before setting
5525     // error.
5526     if (!kill_process(pid) && is_process_active(pid)) error = 3;
5527   }
5528 
5529 end:
5530   if (error) handle_command_error(command, error);
5531 }
5532 
5533 /// Evaluate the warning list argument specified with either
5534 /// disable_warnings or enable_warnings command and replace the
5535 /// variables with actual values if there exist any.
5536 ///
5537 /// @param command     Pointer to the st_command structure which holds
5538 ///                    the arguments and information for the command.
5539 /// @param ds_warnings DYNAMIC_STRING object containing the argument.
5540 ///
5541 /// @retval Evaluated string after replacing the variables with values.
eval_warning_argument(struct st_command * command,DYNAMIC_STRING * ds_warnings)5542 const char *eval_warning_argument(struct st_command *command,
5543                                   DYNAMIC_STRING *ds_warnings) {
5544   dynstr_trunc(ds_warnings, ds_warnings->length);
5545   do_eval(ds_warnings, command->first_argument, command->end, false);
5546   return ds_warnings->str;
5547 }
5548 
5549 /// Check if second argument "ONCE" to disable_warnings or enable_warnings
5550 /// command is specified. If yes, filter out the keyword "ONCE" from the
5551 /// argument string.
5552 ///
5553 /// @param ds_property   DYNAMIC_STRING object containing the second argument
5554 /// @param warn_argument String to store the argument string containing only
5555 ///                      the list of warnings.
5556 ///
5557 /// @retval True if the second argument is specified, false otherwise.
check_and_filter_once_property(DYNAMIC_STRING ds_property,std::string * warn_argument)5558 static bool check_and_filter_once_property(DYNAMIC_STRING ds_property,
5559                                            std::string *warn_argument) {
5560   if (ds_property.length) {
5561     // Second argument exists, and it should be "ONCE" keyword.
5562     if (std::strcmp(ds_property.str, "ONCE"))
5563       die("Second argument to '%s' command should always be \"ONCE\" keyword.",
5564           command_names[curr_command->type - 1]);
5565 
5566     // Filter out the keyword and save only the warnings.
5567     std::size_t position = warn_argument->find(" ONCE");
5568     DBUG_ASSERT(position != std::string::npos);
5569     warn_argument->erase(position, 5);
5570     return true;
5571   }
5572 
5573   return false;
5574 }
5575 
5576 /// Handle disabling of warnings.
5577 ///
5578 /// * If all warnings are disabled, don't add the warning to disabled
5579 ///   warning list.
5580 /// * If there exist enabled warnings, remove the disabled warning from
5581 ///   the list of enabled warnings.
5582 /// * If all the warnings are enabled or if there exist disabled warnings,
5583 ///   add or append the new warning to the list of disabled warnings.
5584 ///
5585 /// @param warning_code Warning code
5586 /// @param warning      Warning string
5587 /// @param once_prop    Flag specifying whether a property should be set
5588 ///                     for next statement only.
handle_disable_warnings(std::uint32_t warning_code,std::string warning,bool once_prop)5589 static void handle_disable_warnings(std::uint32_t warning_code,
5590                                     std::string warning, bool once_prop) {
5591   if (enabled_warnings->count()) {
5592     // Remove the warning from list of enabled warnings.
5593     enabled_warnings->remove_warning(warning_code, once_prop);
5594   } else if (!disable_warnings || disabled_warnings->count()) {
5595     // Add the warning to list of expected warnings only if all the
5596     // warnings are not disabled.
5597     disabled_warnings->add_warning(warning_code, warning.c_str(), once_prop);
5598   }
5599 }
5600 
5601 /// Handle enabling of warnings.
5602 ///
5603 /// * If all the warnings are enabled, don't add the warning to enabled
5604 ///   warning list.
5605 /// * If there exist disabled warnings, remove the enabled warning from
5606 ///   the list of disabled warnings.
5607 /// * If all the warnings are disabled or if there exist enabled warnings,
5608 ///   add or append the new warning to the list of enabled warnings.
5609 ///
5610 /// @param warning_code Warning code
5611 /// @param warning      Warning string
5612 /// @param once_prop    Flag specifying whether a property should be set
5613 ///                     for next statement only.
handle_enable_warnings(std::uint32_t warning_code,std::string warning,bool once_prop)5614 static void handle_enable_warnings(std::uint32_t warning_code,
5615                                    std::string warning, bool once_prop) {
5616   if (disabled_warnings->count()) {
5617     // Remove the warning from list of disabled warnings.
5618     disabled_warnings->remove_warning(warning_code, once_prop);
5619   } else if (disabled_warnings) {
5620     // All the warnings are disabled, enable only the warnings specified
5621     // in the argument.
5622     enabled_warnings->add_warning(warning_code, warning.c_str(), once_prop);
5623   }
5624 }
5625 
5626 /// Get an error code corresponding to a warning name. The warning name
5627 /// specified is in symbolic error name format.
5628 ///
5629 /// @param command       Pointer to the st_command structure which holds the
5630 ///                      arguments and information for the command.
5631 /// @param warn_argument String containing warning argument
5632 /// @param once_prop     Flag specifying whether a property should be set for
5633 ///                      next statement only.
get_warning_codes(struct st_command * command,std::string warn_argument,bool once_prop)5634 static void get_warning_codes(struct st_command *command,
5635                               std::string warn_argument, bool once_prop) {
5636   std::string warning;
5637   std::stringstream warnings(warn_argument);
5638 
5639   if (!my_isalnum(charset_info, warn_argument.back())) {
5640     die("Invalid argument '%s' to '%s' command.", command->first_argument,
5641         command_names[command->type - 1]);
5642   }
5643 
5644   while (std::getline(warnings, warning, ',')) {
5645     // Remove any space in a string representing a warning.
5646     warning.erase(remove_if(warning.begin(), warning.end(), isspace),
5647                   warning.end());
5648 
5649     // Check if a warning name is a valid symbolic error name.
5650     if (warning.front() == 'E' || warning.front() == 'W') {
5651       int warning_code = get_errcode_from_name(warning);
5652       if (warning_code == -1)
5653         die("Unknown SQL error name '%s'.", warning.c_str());
5654       if (command->type == Q_DISABLE_WARNINGS)
5655         handle_disable_warnings(warning_code, warning, once_prop);
5656       else if (command->type == Q_ENABLE_WARNINGS) {
5657         handle_enable_warnings(warning_code, warning, once_prop);
5658         // If disable_warnings flag is set, and there are no disabled or
5659         // enabled warnings, set the disable_warnings flag to 0.
5660         if (disable_warnings) {
5661           if (!disabled_warnings->count() && !enabled_warnings->count())
5662             set_property(command, P_WARN, false);
5663         }
5664       }
5665     } else {
5666       // Invalid argument, should only consist of warnings specified in
5667       // symbolic error name format.
5668       die("Invalid argument '%s' to '%s' command, list of disabled or enabled "
5669           "warnings may only consist of symbolic error names.",
5670           command->first_argument, command_names[command->type - 1]);
5671     }
5672   }
5673 }
5674 
5675 /// Parse the warning list argument specified with disable_warnings or
5676 /// enable_warnings command. Check if the second argument "ONCE" is
5677 /// specified, if yes, set once_property flag.
5678 ///
5679 /// @param command Pointer to the st_command structure which holds the
5680 ///                arguments and information for the command.
5681 ///
5682 /// @retval True if second argument "ONCE" is specified, false otherwise.
parse_warning_list_argument(struct st_command * command)5683 static bool parse_warning_list_argument(struct st_command *command) {
5684   DYNAMIC_STRING ds_warnings, ds_property;
5685   const struct command_arg warning_args[] = {
5686       {"Warnings", ARG_STRING, false, &ds_warnings,
5687        "Comma separated list of warnings to be disabled or enabled."},
5688       {"Property", ARG_STRING, false, &ds_property,
5689        "Keyword \"ONCE\" repesenting the property should be set for next "
5690        "statement only."}};
5691 
5692   check_command_args(command, command->first_argument, warning_args,
5693                      sizeof(warning_args) / sizeof(struct command_arg), ' ');
5694 
5695   // Waning list argument can't be an empty string.
5696   if (!ds_warnings.length)
5697     die("Warning list argument to command '%s' can't be an empty string.",
5698         command_names[command->type - 1]);
5699 
5700   // Evaluate warning list argument and replace the variables with
5701   // actual values
5702   std::string warn_argument = eval_warning_argument(command, &ds_warnings);
5703 
5704   // Set once_prop flag to true if keyword "ONCE" is specified as an
5705   // argument to a disable_warnings or a enable_warnings command.
5706   // Filter this keyword from the argument string and save only the
5707   // list of warnings.
5708   bool once_prop = check_and_filter_once_property(ds_property, &warn_argument);
5709 
5710   // Free all the DYNAMIC_STRING objects created
5711   free_dynamic_strings(&ds_warnings, &ds_property);
5712 
5713   // Get warning codes
5714   get_warning_codes(command, warn_argument, once_prop);
5715 
5716   return once_prop;
5717 }
5718 
5719 /// Create a list of disabled warnings that should be suppressed for
5720 /// the next statements until enabled by enable_warnings command.
5721 ///
5722 /// disable_warnings command can take an optional argument specifying
5723 /// a warning or a comma separated list of warnings to be disabled.
5724 /// The warnings specified should be in symbolic error name format.
5725 ///
5726 /// disable_warnings command can also take a second optional argument,
5727 /// which when specified will suppress the warnings for next statement
5728 /// only. The second argument must be a "ONCE" keyword.
5729 ///
5730 /// @param command Pointer to the st_command structure which holds the
5731 ///                arguments and information for the command.
do_disable_warnings(struct st_command * command)5732 static void do_disable_warnings(struct st_command *command) {
5733   // Revert the previously set properties
5734   if (once_property) revert_properties();
5735 
5736   // Check if disable_warnings command has warning list argument.
5737   if (!*command->first_argument) {
5738     // disable_warnings without an argument, disable all the warnings.
5739     if (disabled_warnings->count()) disabled_warnings->clear_list();
5740     if (enabled_warnings->count()) enabled_warnings->clear_list();
5741 
5742     // Update the environment variables containing the list of disabled
5743     // and enabled warnings.
5744     update_disabled_enabled_warnings_list_var();
5745 
5746     // Set 'disable_warnings' property value to 1
5747     set_property(command, P_WARN, true);
5748     return;
5749   } else {
5750     // Parse the warning list argument specified with disable_warnings
5751     // command.
5752     parse_warning_list_argument(command);
5753 
5754     // Update the environment variables containing the list of disabled
5755     // and enabled warnings.
5756     update_disabled_enabled_warnings_list_var();
5757 
5758     // Set 'disable_warnings' property value to 1
5759     set_property(command, P_WARN, true);
5760   }
5761 
5762   command->last_argument = command->end;
5763 }
5764 
5765 /// Create a list of enabled warnings that should be enabled for the
5766 /// next statements until disabled by disable_warnings command.
5767 ///
5768 /// enable_warnings command can take an optional argument specifying
5769 /// a warning or a comma separated list of warnings to be enabled. The
5770 /// warnings specified should be in symbolic error name format.
5771 ///
5772 /// enable_warnings command can also take a second optional argument,
5773 /// which when specified will enable the warnings for next statement
5774 /// only. The second argument must be a "ONCE" keyword.
5775 ///
5776 /// @param command Pointer to the st_command structure which holds the
5777 ///                arguments and information for the command.
do_enable_warnings(struct st_command * command)5778 static void do_enable_warnings(struct st_command *command) {
5779   // Revert the previously set properties
5780   if (once_property) revert_properties();
5781 
5782   bool once_prop = false;
5783   if (!*command->first_argument) {
5784     // enable_warnings without an argument, enable all the warnings.
5785     if (disabled_warnings->count()) disabled_warnings->clear_list();
5786     if (enabled_warnings->count()) enabled_warnings->clear_list();
5787 
5788     // Update the environment variables containing the list of disabled
5789     // and enabled warnings.
5790     update_disabled_enabled_warnings_list_var();
5791 
5792     // Set 'disable_warnings' property value to 0
5793     set_property(command, P_WARN, false);
5794   } else {
5795     // Parse the warning list argument specified with enable_warnings command.
5796     once_prop = parse_warning_list_argument(command);
5797 
5798     // Update the environment variables containing the list of disabled and
5799     // enabled warnings.
5800     update_disabled_enabled_warnings_list_var();
5801   }
5802 
5803   // Call set_once_property() to set once_propetry flag.
5804   if (disable_warnings && once_prop) set_once_property(P_WARN, true);
5805 
5806   command->last_argument = command->end;
5807 }
5808 
5809 /// Create a list of error values that the next statement is expected
5810 /// to return. Each error must be an error number or an SQLSTATE value
5811 /// or a symbolic error name representing an error.
5812 ///
5813 /// SQLSTATE value must start with 'S'. It is also possible to specify
5814 /// client errors with 'error' command.
5815 ///
5816 /// @code
5817 /// --error 1064
5818 /// --error S42S01
5819 /// --error ER_TABLE_EXISTS_ERROR,ER_PARSE_ERROR
5820 /// --error CR_SERVER_GONE_ERROR
5821 /// @endcode
5822 ///
5823 /// @param command Pointer to the st_command structure which holds the
5824 ///                arguments and information for the command.
do_error(struct st_command * command)5825 static void do_error(struct st_command *command) {
5826   if (!*command->first_argument) die("Missing argument(s) to 'error' command.");
5827 
5828   // Check if error command ends with a comment
5829   char *end = command->first_argument + std::strlen(command->first_argument);
5830   while (std::strlen(command->first_argument) > std::strlen(end)) {
5831     end--;
5832     if (*end == '#') break;
5833   }
5834 
5835   if (std::strlen(command->first_argument) == std::strlen(end))
5836     end = command->first_argument + std::strlen(command->first_argument);
5837 
5838   std::string error;
5839   std::stringstream errors(std::string(command->first_argument, end));
5840 
5841   // Get error codes
5842   while (std::getline(errors, error, ',')) {
5843     // Remove any space from the string representing an error.
5844     error.erase(remove_if(error.begin(), error.end(), isspace), error.end());
5845 
5846     // Code to handle a variable containing an error.
5847     if (error.front() == '$') {
5848       const char *varname_end = nullptr;
5849       VAR *var = var_get(error.c_str(), &varname_end, false, false);
5850       error.assign(var->str_val);
5851     }
5852 
5853     if (error.front() == 'S') {
5854       // SQLSTATE string
5855       //   * Must be SQLSTATE_LENGTH long
5856       //   * May contain only digits[0-9] and _uppercase_ letters
5857 
5858       // Step pass 'S' character
5859       error = error.substr(1, error.length());
5860 
5861       if (error.length() != SQLSTATE_LENGTH)
5862         die("The sqlstate must be exactly %d chars long.", SQLSTATE_LENGTH);
5863 
5864       // Check the validity of an SQLSTATE string.
5865       for (std::size_t i = 0; i < error.length(); i++) {
5866         if (!my_isdigit(charset_info, error[i]) &&
5867             !my_isupper(charset_info, error[i]))
5868           die("The sqlstate may only consist of digits[0-9] and _uppercase_ "
5869               "letters.");
5870       }
5871       expected_errors->add_error(error.c_str(), ERR_SQLSTATE);
5872     } else if (error.front() == 's') {
5873       die("The sqlstate definition must start with an uppercase S.");
5874     }
5875     // Checking for both server error names as well as client error names.
5876     else if (error.front() == 'C' || error.front() == 'E') {
5877       // Code to handle --error <error_string>.
5878       int error_code = get_errcode_from_name(error);
5879       if (error_code == -1) die("Unknown SQL error name '%s'.", error.c_str());
5880       expected_errors->add_error(error_code, ERR_ERRNO);
5881     } else if (error.front() == 'c' || error.front() == 'e') {
5882       die("The error name definition must start with an uppercase C or E.");
5883     } else {
5884       // Check that the string passed to error command contains only digits.
5885       int error_code = get_int_val(error.c_str());
5886       if (error_code == -1)
5887         die("Invalid argument '%s' to 'error' command, the argument may "
5888             "consist of either symbolic error names or error codes.",
5889             command->first_argument);
5890       expected_errors->add_error((std::uint32_t)error_code, ERR_ERRNO);
5891     }
5892 
5893     if (expected_errors->count() >= MAX_ERROR_COUNT)
5894       die("Too many errorcodes specified.");
5895   }
5896 
5897   command->last_argument = command->end;
5898 }
5899 
5900 /*
5901   Get a string;  Return ptr to end of string
5902   Strings may be surrounded by " or '
5903 
5904   If string is a '$variable', return the value of the variable.
5905 */
5906 
get_string(char ** to_ptr,const char ** from_ptr,struct st_command * command)5907 static char *get_string(char **to_ptr, const char **from_ptr,
5908                         struct st_command *command) {
5909   char c, sep;
5910   char *to = *to_ptr, *start = to;
5911   const char *from = *from_ptr;
5912   DBUG_TRACE;
5913 
5914   /* Find separator */
5915   if (*from == '"' || *from == '\'')
5916     sep = *from++;
5917   else
5918     sep = ' '; /* Separated with space */
5919 
5920   for (; (c = *from); from++) {
5921     if (c == '\\' && from[1]) { /* Escaped character */
5922       /* We can't translate \0 -> ASCII 0 as replace can't handle ASCII 0 */
5923       switch (*++from) {
5924         case 'n':
5925           *to++ = '\n';
5926           break;
5927         case 't':
5928           *to++ = '\t';
5929           break;
5930         case 'r':
5931           *to++ = '\r';
5932           break;
5933         case 'b':
5934           *to++ = '\b';
5935           break;
5936         case 'Z': /* ^Z must be escaped on Win32 */
5937           *to++ = '\032';
5938           break;
5939         default:
5940           *to++ = *from;
5941           break;
5942       }
5943     } else if (c == sep) {
5944       if (c == ' ' || c != *++from) break; /* Found end of string */
5945       *to++ = c;                           /* Copy duplicated separator */
5946     } else
5947       *to++ = c;
5948   }
5949   if (*from != ' ' && *from) die("Wrong string argument in %s", command->query);
5950 
5951   while (my_isspace(charset_info, *from)) /* Point to next string */
5952     from++;
5953 
5954   *to = 0;          /* End of string marker */
5955   *to_ptr = to + 1; /* Store pointer to end */
5956   *from_ptr = from;
5957 
5958   /* Check if this was a variable */
5959   if (*start == '$') {
5960     const char *end = to;
5961     VAR *var = var_get(start, &end, false, true);
5962     if (var && to == end + 1) {
5963       DBUG_PRINT("info", ("var: '%s' -> '%s'", start, var->str_val));
5964       return var->str_val; /* return found variable value */
5965     }
5966   }
5967   return start;
5968 }
5969 
set_reconnect(MYSQL * mysql,int val)5970 static void set_reconnect(MYSQL *mysql, int val) {
5971   bool reconnect = val;
5972   DBUG_TRACE;
5973   DBUG_PRINT("info", ("val: %d", val));
5974   mysql_options(mysql, MYSQL_OPT_RECONNECT, (char *)&reconnect);
5975 }
5976 
5977 /**
5978   Change the current connection to the given st_connection, and update
5979   $mysql_get_server_version and $CURRENT_CONNECTION accordingly.
5980 */
set_current_connection(struct st_connection * con)5981 static void set_current_connection(struct st_connection *con) {
5982   cur_con = con;
5983   /* Update $mysql_get_server_version to that of current connection */
5984   var_set_int("$mysql_get_server_version",
5985               mysql_get_server_version(&con->mysql));
5986   /* Update $CURRENT_CONNECTION to the name of the current connection */
5987   var_set_string("$CURRENT_CONNECTION", con->name);
5988 }
5989 
select_connection_name(const char * name)5990 static void select_connection_name(const char *name) {
5991   DBUG_TRACE;
5992   DBUG_PRINT("enter", ("name: '%s'", name));
5993   st_connection *con = find_connection_by_name(name);
5994 
5995   if (!con) die("connection '%s' not found in connection pool", name);
5996 
5997   set_current_connection(con);
5998 
5999   /* Connection logging if enabled */
6000   if (!disable_connect_log && !disable_query_log) {
6001     DYNAMIC_STRING *ds = &ds_res;
6002 
6003     dynstr_append_mem(ds, "connection ", 11);
6004     replace_dynstr_append(ds, name);
6005     dynstr_append_mem(ds, ";\n", 2);
6006   }
6007 }
6008 
select_connection(struct st_command * command)6009 static void select_connection(struct st_command *command) {
6010   DBUG_TRACE;
6011   static DYNAMIC_STRING ds_connection;
6012   const struct command_arg connection_args[] = {
6013       {"connection_name", ARG_STRING, true, &ds_connection,
6014        "Name of the connection that we switch to."}};
6015   check_command_args(command, command->first_argument, connection_args,
6016                      sizeof(connection_args) / sizeof(struct command_arg), ' ');
6017 
6018   DBUG_PRINT("info", ("changing connection: %s", ds_connection.str));
6019   select_connection_name(ds_connection.str);
6020   dynstr_free(&ds_connection);
6021 }
6022 
do_close_connection(struct st_command * command)6023 static void do_close_connection(struct st_command *command) {
6024   DBUG_TRACE;
6025 
6026   struct st_connection *con;
6027   static DYNAMIC_STRING ds_connection;
6028   const struct command_arg close_connection_args[] = {
6029       {"connection_name", ARG_STRING, true, &ds_connection,
6030        "Name of the connection to close."}};
6031   check_command_args(command, command->first_argument, close_connection_args,
6032                      sizeof(close_connection_args) / sizeof(struct command_arg),
6033                      ' ');
6034 
6035   DBUG_PRINT("enter", ("connection name: '%s'", ds_connection.str));
6036 
6037   if (!(con = find_connection_by_name(ds_connection.str)))
6038     die("connection '%s' not found in connection pool", ds_connection.str);
6039 
6040   DBUG_PRINT("info", ("Closing connection %s", con->name));
6041   if (command->type == Q_DIRTY_CLOSE) {
6042     if (con->mysql.net.vio) {
6043       vio_delete(con->mysql.net.vio);
6044       con->mysql.net.vio = nullptr;
6045       end_server(&con->mysql);
6046     }
6047   }
6048   if (con->stmt) mysql_stmt_close(con->stmt);
6049   con->stmt = nullptr;
6050 
6051   mysql_close(&con->mysql);
6052 
6053   if (con->util_mysql) mysql_close(con->util_mysql);
6054   con->util_mysql = nullptr;
6055   con->pending = false;
6056 
6057   my_free(con->name);
6058 
6059   /*
6060     When the connection is closed set name to "-closed_connection-"
6061     to make it possible to reuse the connection name.
6062   */
6063   if (!(con->name = my_strdup(PSI_NOT_INSTRUMENTED, "-closed_connection-",
6064                               MYF(MY_WME))))
6065     die("Out of memory");
6066 
6067   if (con == cur_con) {
6068     /* Current connection was closed */
6069     var_set_int("$mysql_get_server_version", 0xFFFFFFFF);
6070     var_set_string("$CURRENT_CONNECTION", con->name);
6071   }
6072 
6073   /* Connection logging if enabled */
6074   if (!disable_connect_log && !disable_query_log) {
6075     DYNAMIC_STRING *ds = &ds_res;
6076 
6077     dynstr_append_mem(ds, "disconnect ", 11);
6078     replace_dynstr_append(ds, ds_connection.str);
6079     dynstr_append_mem(ds, ";\n", 2);
6080   }
6081 
6082   dynstr_free(&ds_connection);
6083 }
6084 
6085 /*
6086   Connect to a server doing several retries if needed.
6087 
6088   SYNOPSIS
6089   safe_connect()
6090   con               - connection structure to be used
6091   host, user, pass, - connection parameters
6092   db, port, sock
6093 
6094   NOTE
6095 
6096   Sometimes in a test the client starts before
6097   the server - to solve the problem, we try again
6098   after some sleep if connection fails the first
6099   time
6100 
6101   This function will try to connect to the given server
6102   "opt_max_connect_retries" times and sleep "connection_retry_sleep"
6103   seconds between attempts before finally giving up.
6104   This helps in situation when the client starts
6105   before the server (which happens sometimes).
6106   It will only ignore connection errors during these retries.
6107 
6108 */
6109 
safe_connect(MYSQL * mysql,const char * name,const char * host,const char * user,const char * pass,const char * db,int port,const char * sock)6110 static void safe_connect(MYSQL *mysql, const char *name, const char *host,
6111                          const char *user, const char *pass, const char *db,
6112                          int port, const char *sock) {
6113   int failed_attempts = 0;
6114 
6115   DBUG_TRACE;
6116 
6117   verbose_msg(
6118       "Connecting to server %s:%d (socket %s) as '%s'"
6119       ", connection '%s', attempt %d ...",
6120       host, port, sock, user, name, failed_attempts);
6121 
6122   mysql_options(mysql, MYSQL_OPT_CONNECT_ATTR_RESET, nullptr);
6123   mysql_options4(mysql, MYSQL_OPT_CONNECT_ATTR_ADD, "program_name",
6124                  "mysqltest");
6125   mysql_options(mysql, MYSQL_OPT_CAN_HANDLE_EXPIRED_PASSWORDS,
6126                 &can_handle_expired_passwords);
6127   while (!mysql_real_connect_wrapper(
6128       mysql, host, user, pass, db, port, sock,
6129       CLIENT_MULTI_STATEMENTS | CLIENT_REMEMBER_OPTIONS)) {
6130     /*
6131       Connect failed
6132 
6133       Only allow retry if this was an error indicating the server
6134       could not be contacted. Error code differs depending
6135       on protocol/connection type
6136     */
6137 
6138     if ((mysql_errno(mysql) == CR_CONN_HOST_ERROR ||
6139          mysql_errno(mysql) == CR_CONNECTION_ERROR ||
6140          mysql_errno(mysql) == CR_NAMEDPIPEOPEN_ERROR) &&
6141         failed_attempts < opt_max_connect_retries) {
6142       verbose_msg("Connect attempt %d/%d failed: %d: %s", failed_attempts,
6143                   opt_max_connect_retries, mysql_errno(mysql),
6144                   mysql_error(mysql));
6145       my_sleep(connection_retry_sleep);
6146     } else {
6147       if (failed_attempts > 0)
6148         die("Could not open connection '%s' after %d attempts: %d %s", name,
6149             failed_attempts, mysql_errno(mysql), mysql_error(mysql));
6150       else
6151         die("Could not open connection '%s': %d %s", name, mysql_errno(mysql),
6152             mysql_error(mysql));
6153     }
6154     failed_attempts++;
6155   }
6156   verbose_msg("... Connected.");
6157 }
6158 
6159 /// Connect to a server and handle connection errors in case they
6160 /// occur.
6161 ///
6162 /// This function will try to establish a connection to server and
6163 /// handle possible errors in the same manner as if "connect" was usual
6164 /// SQL-statement. If an error is expected it will ignore it once it
6165 /// occurs and log the "statement" to the query log. Unlike
6166 /// safe_connect() it won't do several attempts.
6167 ///
6168 /// @param command Pointer to the st_command structure which holds the
6169 ///                     arguments and information for the command.
6170 /// @param con     Connection structure to be used
6171 /// @param host    Host name
6172 /// @param user    User name
6173 /// @param pass    Password
6174 /// @param db      Database name
6175 /// @param port    Port number
6176 /// @param sock    Socket value
6177 ///
6178 /// @retval 1 if connection succeeds, 0 otherwise
connect_n_handle_errors(struct st_command * command,MYSQL * con,const char * host,const char * user,const char * pass,const char * db,int port,const char * sock)6179 static int connect_n_handle_errors(struct st_command *command, MYSQL *con,
6180                                    const char *host, const char *user,
6181                                    const char *pass, const char *db, int port,
6182                                    const char *sock) {
6183   DYNAMIC_STRING *ds;
6184   int failed_attempts = 0;
6185 
6186   ds = &ds_res;
6187 
6188   /* Only log if an error is expected */
6189   if (expected_errors->count() > 0 && !disable_query_log) {
6190     /*
6191       Log the connect to result log
6192     */
6193     dynstr_append_mem(ds, "connect(", 8);
6194     replace_dynstr_append(ds, host);
6195     dynstr_append_mem(ds, ",", 1);
6196     replace_dynstr_append(ds, user);
6197     dynstr_append_mem(ds, ",", 1);
6198     replace_dynstr_append(ds, pass);
6199     dynstr_append_mem(ds, ",", 1);
6200     if (db) replace_dynstr_append(ds, db);
6201     dynstr_append_mem(ds, ",", 1);
6202     replace_dynstr_append_uint(ds, port);
6203     dynstr_append_mem(ds, ",", 1);
6204     if (sock) replace_dynstr_append(ds, sock);
6205     dynstr_append_mem(ds, ")", 1);
6206     dynstr_append_mem(ds, delimiter, delimiter_length);
6207     dynstr_append_mem(ds, "\n", 1);
6208   }
6209   /* Simlified logging if enabled */
6210   if (!disable_connect_log && !disable_query_log) {
6211     replace_dynstr_append(ds, command->query);
6212     dynstr_append_mem(ds, ";\n", 2);
6213   }
6214 
6215   mysql_options(con, MYSQL_OPT_CONNECT_ATTR_RESET, nullptr);
6216   mysql_options4(con, MYSQL_OPT_CONNECT_ATTR_ADD, "program_name", "mysqltest");
6217   mysql_options(con, MYSQL_OPT_CAN_HANDLE_EXPIRED_PASSWORDS,
6218                 &can_handle_expired_passwords);
6219   while (!mysql_real_connect_wrapper(con, host, user, pass, db, port,
6220                                      sock ? sock : nullptr,
6221                                      CLIENT_MULTI_STATEMENTS)) {
6222     /*
6223       If we have used up all our connections check whether this
6224       is expected (by --error). If so, handle the error right away.
6225       Otherwise, give it some extra time to rule out race-conditions.
6226       If extra-time doesn't help, we have an unexpected error and
6227       must abort -- just proceeding to handle_error() when second
6228       and third chances are used up will handle that for us.
6229 
6230       There are various user-limits of which only max_user_connections
6231       and max_connections_per_hour apply at connect time. For the
6232       the second to create a race in our logic, we'd need a limits
6233       test that runs without a FLUSH for longer than an hour, so we'll
6234       stay clear of trying to work out which exact user-limit was
6235       exceeded.
6236     */
6237 
6238     if (((mysql_errno(con) == ER_TOO_MANY_USER_CONNECTIONS) ||
6239          (mysql_errno(con) == ER_USER_LIMIT_REACHED)) &&
6240         (failed_attempts++ < opt_max_connect_retries)) {
6241       int i;
6242 
6243       i = match_expected_error(command, mysql_errno(con), mysql_sqlstate(con));
6244 
6245       if (i >= 0) goto do_handle_error; /* expected error, handle */
6246 
6247       my_sleep(connection_retry_sleep); /* unexpected error, wait */
6248       continue;                         /* and give it 1 more chance */
6249     }
6250 
6251   do_handle_error:
6252     var_set_errno(mysql_errno(con));
6253     handle_error(command, mysql_errno(con), mysql_error(con),
6254                  mysql_sqlstate(con), ds);
6255     return 0; /* Not connected */
6256   }
6257 
6258   var_set_errno(0);
6259   handle_no_error(command);
6260   revert_properties();
6261   return 1; /* Connected */
6262 }
6263 
6264 /*
6265   Open a new connection to MySQL Server with the parameters
6266   specified. Make the new connection the current connection.
6267 
6268   SYNOPSIS
6269   do_connect()
6270   q	       called command
6271 
6272   DESCRIPTION
6273   connect(<name>,<host>,<user>,[<pass>,[<db>,[<port>,<sock>[<opts>]]]]);
6274   connect <name>,<host>,<user>,[<pass>,[<db>,[<port>,<sock>[<opts>]]]];
6275 
6276   <name> - name of the new connection
6277   <host> - hostname of server
6278   <user> - user to connect as
6279   <pass> - password used when connecting
6280   <db>   - initial db when connected
6281   <port> - server port
6282   <sock> - server socket
6283   <opts> - options to use for the connection
6284    * SSL - use SSL if available
6285    * COMPRESS - use compression if available
6286    * SHM - use shared memory if available
6287    * PIPE - use named pipe if available
6288    * SOCKET - use socket protocol
6289    * TCP - use tcp protocol
6290 */
6291 
do_connect(struct st_command * command)6292 static void do_connect(struct st_command *command) {
6293   int con_port = opt_port;
6294   char *con_options;
6295   bool con_ssl = false, con_compress = false;
6296   bool con_pipe = false, con_shm = false, con_cleartext_enable = false;
6297   struct st_connection *con_slot;
6298   uint save_opt_ssl_mode = opt_ssl_mode;
6299 
6300   static DYNAMIC_STRING ds_connection_name;
6301   static DYNAMIC_STRING ds_host;
6302   static DYNAMIC_STRING ds_user;
6303   static DYNAMIC_STRING ds_password;
6304   static DYNAMIC_STRING ds_database;
6305   static DYNAMIC_STRING ds_port;
6306   static DYNAMIC_STRING ds_sock;
6307   static DYNAMIC_STRING ds_options;
6308   static DYNAMIC_STRING ds_default_auth;
6309   static DYNAMIC_STRING ds_shm;
6310   static DYNAMIC_STRING ds_compression_algorithm;
6311   static DYNAMIC_STRING ds_zstd_compression_level;
6312   const struct command_arg connect_args[] = {
6313       {"connection name", ARG_STRING, true, &ds_connection_name,
6314        "Name of the connection"},
6315       {"host", ARG_STRING, true, &ds_host, "Host to connect to"},
6316       {"user", ARG_STRING, false, &ds_user, "User to connect as"},
6317       {"passsword", ARG_STRING, false, &ds_password,
6318        "Password used when connecting"},
6319       {"database", ARG_STRING, false, &ds_database,
6320        "Database to select after connect"},
6321       {"port", ARG_STRING, false, &ds_port, "Port to connect to"},
6322       {"socket", ARG_STRING, false, &ds_sock, "Socket to connect with"},
6323       {"options", ARG_STRING, false, &ds_options,
6324        "Options to use while connecting"},
6325       {"default_auth", ARG_STRING, false, &ds_default_auth,
6326        "Default authentication to use"},
6327       {"default_compression_algorithm", ARG_STRING, false,
6328        &ds_compression_algorithm, "Default compression algorithm to use"},
6329       {"default_zstd_compression_level", ARG_STRING, false,
6330        &ds_zstd_compression_level,
6331        "Default compression level to use "
6332        "when using zstd compression."}};
6333 
6334   DBUG_TRACE;
6335   DBUG_PRINT("enter", ("connect: %s", command->first_argument));
6336 
6337   strip_parentheses(command);
6338   check_command_args(command, command->first_argument, connect_args,
6339                      sizeof(connect_args) / sizeof(struct command_arg), ',');
6340 
6341   /* Port */
6342   if (ds_port.length) {
6343     con_port = atoi(ds_port.str);
6344     if (con_port == 0) die("Illegal argument for port: '%s'", ds_port.str);
6345   }
6346 
6347   /* Shared memory */
6348   init_dynamic_string(&ds_shm, ds_sock.str, 0, 0);
6349 
6350   /* Sock */
6351   if (ds_sock.length) {
6352     /*
6353       If the socket is specified just as a name without path
6354       append tmpdir in front
6355     */
6356     if (*ds_sock.str != FN_LIBCHAR) {
6357       char buff[FN_REFLEN];
6358       fn_format(buff, ds_sock.str, TMPDIR, "", 0);
6359       dynstr_set(&ds_sock, buff);
6360     }
6361   } else {
6362     /* No socket specified, use default */
6363     dynstr_set(&ds_sock, unix_sock);
6364   }
6365   DBUG_PRINT("info", ("socket: %s", ds_sock.str));
6366 
6367   /* Options */
6368   con_options = ds_options.str;
6369   bool con_socket = false, con_tcp = false;
6370   while (*con_options) {
6371     /* Step past any spaces in beginning of option */
6372     while (*con_options && my_isspace(charset_info, *con_options))
6373       con_options++;
6374 
6375     /* Find end of this option */
6376     char *end = con_options;
6377     while (*end && !my_isspace(charset_info, *end)) end++;
6378 
6379     size_t con_option_len = end - con_options;
6380     char cur_con_option[10] = {};
6381     strmake(cur_con_option, con_options, con_option_len);
6382 
6383     if (!std::strcmp(cur_con_option, "SSL"))
6384       con_ssl = true;
6385     else if (!std::strcmp(cur_con_option, "COMPRESS"))
6386       con_compress = true;
6387     else if (!std::strcmp(cur_con_option, "PIPE"))
6388       con_pipe = true;
6389     else if (!std::strcmp(cur_con_option, "SHM"))
6390       con_shm = true;
6391     else if (!std::strcmp(cur_con_option, "CLEARTEXT"))
6392       con_cleartext_enable = true;
6393     else if (!std::strcmp(cur_con_option, "SOCKET"))
6394       con_socket = true;
6395     else if (!std::strcmp(cur_con_option, "TCP"))
6396       con_tcp = true;
6397     else
6398       die("Illegal option to connect: %s", cur_con_option);
6399 
6400     /* Process next option */
6401     con_options = end;
6402   }
6403 
6404   if (find_connection_by_name(ds_connection_name.str))
6405     die("Connection %s already exists", ds_connection_name.str);
6406 
6407   if (next_con != connections_end)
6408     con_slot = next_con;
6409   else {
6410     if (!(con_slot = find_connection_by_name("-closed_connection-")))
6411       die("Connection limit exhausted, you can have max %d connections",
6412           opt_max_connections);
6413   }
6414 
6415   if (!mysql_init(&con_slot->mysql)) die("Failed on mysql_init()");
6416 
6417   if (opt_connect_timeout)
6418     mysql_options(&con_slot->mysql, MYSQL_OPT_CONNECT_TIMEOUT,
6419                   (void *)&opt_connect_timeout);
6420 
6421   if (opt_compress || con_compress)
6422     mysql_options(&con_slot->mysql, MYSQL_OPT_COMPRESS, NullS);
6423   mysql_options(&con_slot->mysql, MYSQL_OPT_LOCAL_INFILE, nullptr);
6424   mysql_options(&con_slot->mysql, MYSQL_SET_CHARSET_NAME, charset_info->csname);
6425   if (opt_charsets_dir)
6426     mysql_options(&con_slot->mysql, MYSQL_SET_CHARSET_DIR, opt_charsets_dir);
6427 
6428   if (ds_compression_algorithm.length)
6429     mysql_options(&con_slot->mysql, MYSQL_OPT_COMPRESSION_ALGORITHMS,
6430                   ds_compression_algorithm.str);
6431   else if (opt_compress_algorithm)
6432     mysql_options(&con_slot->mysql, MYSQL_OPT_COMPRESSION_ALGORITHMS,
6433                   opt_compress_algorithm);
6434   if (ds_zstd_compression_level.length) {
6435     char *end = nullptr;
6436     opt_zstd_compress_level = strtol(ds_zstd_compression_level.str, &end, 10);
6437   }
6438   mysql_options(&con_slot->mysql, MYSQL_OPT_ZSTD_COMPRESSION_LEVEL,
6439                 &opt_zstd_compress_level);
6440 
6441   /*
6442     If mysqltest --ssl-mode option is set to DISABLED
6443     and connect(.., SSL) command used, set proper opt_ssl_mode.
6444 
6445     So, SSL connection is used either:
6446     a) mysqltest --ssl-mode option is NOT set to DISABLED or
6447     b) connect(.., SSL) command used.
6448   */
6449   if (opt_ssl_mode == SSL_MODE_DISABLED && con_ssl) {
6450     opt_ssl_mode =
6451         (opt_ssl_ca || opt_ssl_capath) ? SSL_MODE_VERIFY_CA : SSL_MODE_REQUIRED;
6452   }
6453   if (SSL_SET_OPTIONS(&con_slot->mysql)) die("%s", SSL_SET_OPTIONS_ERROR);
6454   opt_ssl_mode = save_opt_ssl_mode;
6455 
6456   if (con_pipe && !con_ssl) {
6457     opt_protocol = MYSQL_PROTOCOL_PIPE;
6458   }
6459 
6460   if (opt_protocol) {
6461     mysql_options(&con_slot->mysql, MYSQL_OPT_PROTOCOL, (char *)&opt_protocol);
6462     /*
6463       Resetting the opt_protocol value to 0 to avoid the
6464       possible failure in the next connect() command.
6465     */
6466     opt_protocol = 0;
6467   }
6468 
6469   if (con_shm) {
6470     uint protocol = MYSQL_PROTOCOL_MEMORY;
6471     if (!ds_shm.length) die("Missing shared memory base name");
6472 
6473     mysql_options(&con_slot->mysql, MYSQL_SHARED_MEMORY_BASE_NAME, ds_shm.str);
6474     mysql_options(&con_slot->mysql, MYSQL_OPT_PROTOCOL, &protocol);
6475   } else if (shared_memory_base_name) {
6476     mysql_options(&con_slot->mysql, MYSQL_SHARED_MEMORY_BASE_NAME,
6477                   shared_memory_base_name);
6478   }
6479 
6480   if (con_socket) {
6481     uint protocol = MYSQL_PROTOCOL_SOCKET;
6482     mysql_options(&con_slot->mysql, MYSQL_OPT_PROTOCOL, &protocol);
6483   }
6484 
6485   if (con_tcp) {
6486     uint protocol = MYSQL_PROTOCOL_TCP;
6487     mysql_options(&con_slot->mysql, MYSQL_OPT_PROTOCOL, &protocol);
6488   }
6489 
6490   /* Use default db name */
6491   if (ds_database.length == 0) dynstr_set(&ds_database, opt_db);
6492 
6493   if (opt_plugin_dir && *opt_plugin_dir)
6494     mysql_options(&con_slot->mysql, MYSQL_PLUGIN_DIR, opt_plugin_dir);
6495 
6496   if (ds_default_auth.length)
6497     mysql_options(&con_slot->mysql, MYSQL_DEFAULT_AUTH, ds_default_auth.str);
6498 
6499   /* Set server public_key */
6500   set_server_public_key(&con_slot->mysql);
6501 
6502   set_get_server_public_key_option(&con_slot->mysql);
6503 
6504   if (con_cleartext_enable)
6505     mysql_options(&con_slot->mysql, MYSQL_ENABLE_CLEARTEXT_PLUGIN,
6506                   (char *)&con_cleartext_enable);
6507 
6508   /* Special database to allow one to connect without a database name */
6509   if (ds_database.length && !std::strcmp(ds_database.str, "*NO-ONE*"))
6510     dynstr_set(&ds_database, "");
6511 
6512   if (connect_n_handle_errors(command, &con_slot->mysql, ds_host.str,
6513                               ds_user.str, ds_password.str, ds_database.str,
6514                               con_port, ds_sock.str)) {
6515     DBUG_PRINT("info", ("Inserting connection %s in connection pool",
6516                         ds_connection_name.str));
6517     my_free(con_slot->name);
6518     if (!(con_slot->name = my_strdup(PSI_NOT_INSTRUMENTED,
6519                                      ds_connection_name.str, MYF(MY_WME))))
6520       die("Out of memory");
6521     con_slot->name_len = std::strlen(con_slot->name);
6522     set_current_connection(con_slot);
6523 
6524     if (con_slot == next_con)
6525       next_con++; /* if we used the next_con slot, advance the pointer */
6526   }
6527 
6528   dynstr_free(&ds_connection_name);
6529   dynstr_free(&ds_host);
6530   dynstr_free(&ds_user);
6531   dynstr_free(&ds_password);
6532   dynstr_free(&ds_database);
6533   dynstr_free(&ds_port);
6534   dynstr_free(&ds_sock);
6535   dynstr_free(&ds_options);
6536   dynstr_free(&ds_default_auth);
6537   dynstr_free(&ds_shm);
6538   dynstr_free(&ds_compression_algorithm);
6539   dynstr_free(&ds_zstd_compression_level);
6540 }
6541 
do_done(struct st_command * command)6542 static int do_done(struct st_command *command) {
6543   /* Check if empty block stack */
6544   if (cur_block == block_stack) {
6545     if (*command->query != '}')
6546       die("Stray 'end' command - end of block before beginning");
6547     die("Stray '}' - end of block before beginning");
6548   }
6549 
6550   /* Test if inner block has been executed */
6551   if (cur_block->ok && cur_block->cmd == cmd_while) {
6552     /* Pop block from stack, re-execute outer block */
6553     cur_block--;
6554     parser.current_line = cur_block->line;
6555   } else {
6556     if (*cur_block->delim) {
6557       /* Restore "old" delimiter after false if block */
6558       strcpy(delimiter, cur_block->delim);
6559       delimiter_length = std::strlen(delimiter);
6560     }
6561     /* Pop block from stack, goto next line */
6562     cur_block--;
6563     parser.current_line++;
6564   }
6565   return 0;
6566 }
6567 
6568 /* Operands available in if or while conditions */
6569 
6570 enum block_op { EQ_OP, NE_OP, GT_OP, GE_OP, LT_OP, LE_OP, ILLEG_OP };
6571 
find_operand(const char * start)6572 static enum block_op find_operand(const char *start) {
6573   char first = *start;
6574   char next = *(start + 1);
6575 
6576   if (first == '=' && next == '=') return EQ_OP;
6577   if (first == '!' && next == '=') return NE_OP;
6578   if (first == '>' && next == '=') return GE_OP;
6579   if (first == '>') return GT_OP;
6580   if (first == '<' && next == '=') return LE_OP;
6581   if (first == '<') return LT_OP;
6582 
6583   return ILLEG_OP;
6584 }
6585 
6586 /*
6587   Process start of a "if" or "while" statement
6588 
6589   SYNOPSIS
6590   do_block()
6591   cmd        Type of block
6592   q	       called command
6593 
6594   DESCRIPTION
6595   if ([!]<expr>)
6596   {
6597   <block statements>
6598   }
6599 
6600   while ([!]<expr>)
6601   {
6602   <block statements>
6603   }
6604 
6605   Evaluates the <expr> and if it evaluates to
6606   greater than zero executes the following code block.
6607   A '!' can be used before the <expr> to indicate it should
6608   be executed if it evaluates to zero.
6609 
6610   <expr> can also be a simple comparison condition:
6611 
6612   <variable> <op> <expr>
6613 
6614   The left hand side must be a variable, the right hand side can be a
6615   variable, number, string or `query`. Operands are ==, !=, <, <=, >, >=.
6616   == and != can be used for strings, all can be used for numerical values.
6617 */
6618 
do_block(enum block_cmd cmd,struct st_command * command)6619 static void do_block(enum block_cmd cmd, struct st_command *command) {
6620   const char *p = command->first_argument;
6621   const char *expr_start;
6622   const char *expr_end;
6623   VAR v;
6624   const char *cmd_name = (cmd == cmd_while ? "while" : "if");
6625   bool not_expr = false;
6626   DBUG_TRACE;
6627   DBUG_PRINT("enter", ("%s", cmd_name));
6628 
6629   /* Check stack overflow */
6630   if (cur_block == block_stack_end) die("Nesting too deeply");
6631 
6632   /* Set way to find outer block again, increase line counter */
6633   cur_block->line = parser.current_line++;
6634 
6635   /* If this block is ignored */
6636   if (!cur_block->ok) {
6637     /* Inner block should be ignored too */
6638     cur_block++;
6639     cur_block->cmd = cmd;
6640     cur_block->ok = false;
6641     cur_block->delim[0] = '\0';
6642     return;
6643   }
6644 
6645   /* Parse and evaluate test expression */
6646   expr_start = strchr(p, '(');
6647   if (!expr_start) die("missing '(' in %s", cmd_name);
6648   ++expr_start;
6649 
6650   while (my_isspace(charset_info, *expr_start)) expr_start++;
6651 
6652   /* Check for !<expr> */
6653   if (*expr_start == '!') {
6654     not_expr = true;
6655     expr_start++; /* Step past the '!', then any whitespace */
6656     while (*expr_start && my_isspace(charset_info, *expr_start)) expr_start++;
6657   }
6658   /* Find ending ')' */
6659   expr_end = strrchr(expr_start, ')');
6660   if (!expr_end) die("missing ')' in %s", cmd_name);
6661   p = expr_end + 1;
6662 
6663   while (*p && my_isspace(charset_info, *p)) p++;
6664   if (*p && *p != '{') die("Missing '{' after %s. Found \"%s\"", cmd_name, p);
6665 
6666   var_init(&v, nullptr, 0, nullptr, 0);
6667 
6668   /* If expression starts with a variable, it may be a compare condition */
6669 
6670   if (*expr_start == '$') {
6671     const char *curr_ptr = expr_end;
6672     eval_expr(&v, expr_start, &curr_ptr, true);
6673     while (my_isspace(charset_info, *++curr_ptr)) {
6674     }
6675     /* If there was nothing past the variable, skip condition part */
6676     if (curr_ptr == expr_end) goto NO_COMPARE;
6677 
6678     enum block_op operand = find_operand(curr_ptr);
6679     if (operand == ILLEG_OP)
6680       die("Found junk '%.*s' after $variable in condition",
6681           (int)(expr_end - curr_ptr), curr_ptr);
6682 
6683     /* We could silently allow this, but may be confusing */
6684     if (not_expr)
6685       die("Negation and comparison should not be combined, please rewrite");
6686 
6687     /* Skip the 1 or 2 chars of the operand, then white space */
6688     if (operand == LT_OP || operand == GT_OP) {
6689       curr_ptr++;
6690     } else {
6691       curr_ptr += 2;
6692     }
6693     while (my_isspace(charset_info, *curr_ptr)) curr_ptr++;
6694     if (curr_ptr == expr_end) die("Missing right operand in comparison");
6695 
6696     /* Strip off trailing white space */
6697     while (my_isspace(charset_info, expr_end[-1])) expr_end--;
6698     /* strip off ' or " around the string */
6699     if (*curr_ptr == '\'' || *curr_ptr == '"') {
6700       if (expr_end[-1] != *curr_ptr) die("Unterminated string value");
6701       curr_ptr++;
6702       expr_end--;
6703     }
6704     VAR v2;
6705     var_init(&v2, nullptr, 0, nullptr, 0);
6706     eval_expr(&v2, curr_ptr, &expr_end);
6707 
6708     if ((operand != EQ_OP && operand != NE_OP) && !(v.is_int && v2.is_int))
6709       die("Only == and != are supported for string values");
6710 
6711     /* Now we overwrite the first variable with 0 or 1 (for false or true) */
6712 
6713     switch (operand) {
6714       case EQ_OP:
6715         if (v.is_int)
6716           v.int_val = (v2.is_int && v2.int_val == v.int_val);
6717         else
6718           v.int_val = !std::strcmp(v.str_val, v2.str_val);
6719         break;
6720 
6721       case NE_OP:
6722         if (v.is_int)
6723           v.int_val = !(v2.is_int && v2.int_val == v.int_val);
6724         else
6725           v.int_val = (std::strcmp(v.str_val, v2.str_val) != 0);
6726         break;
6727 
6728       case LT_OP:
6729         v.int_val = (v.int_val < v2.int_val);
6730         break;
6731       case LE_OP:
6732         v.int_val = (v.int_val <= v2.int_val);
6733         break;
6734       case GT_OP:
6735         v.int_val = (v.int_val > v2.int_val);
6736         break;
6737       case GE_OP:
6738         v.int_val = (v.int_val >= v2.int_val);
6739         break;
6740       case ILLEG_OP:
6741         die("Impossible operator, this cannot happen");
6742     }
6743 
6744     v.is_int = true;
6745     var_free()(&v2);
6746   } else {
6747     if (*expr_start != '`' && !my_isdigit(charset_info, *expr_start))
6748       die("Expression in if/while must beging with $, ` or a number");
6749     eval_expr(&v, expr_start, &expr_end);
6750   }
6751 
6752 NO_COMPARE:
6753   /* Define inner block */
6754   cur_block++;
6755   cur_block->cmd = cmd;
6756   if (v.is_int) {
6757     cur_block->ok = (v.int_val != 0);
6758   } else
6759   /* Any non-empty string which does not begin with 0 is also true */
6760   {
6761     p = v.str_val;
6762     /* First skip any leading white space or unary -+ */
6763     while (*p && ((my_isspace(charset_info, *p) || *p == '-' || *p == '+')))
6764       p++;
6765 
6766     cur_block->ok = (*p && *p != '0') ? true : false;
6767   }
6768 
6769   if (not_expr) cur_block->ok = !cur_block->ok;
6770 
6771   if (cur_block->ok) {
6772     cur_block->delim[0] = '\0';
6773   } else {
6774     /* Remember "old" delimiter if entering a false if block */
6775     strcpy(cur_block->delim, delimiter);
6776   }
6777 
6778   DBUG_PRINT("info", ("OK: %d", cur_block->ok));
6779 
6780   var_free()(&v);
6781 }
6782 
do_delimiter(struct st_command * command)6783 static void do_delimiter(struct st_command *command) {
6784   char *p = command->first_argument;
6785   DBUG_TRACE;
6786   DBUG_PRINT("enter", ("first_argument: %s", command->first_argument));
6787 
6788   while (*p && my_isspace(charset_info, *p)) p++;
6789 
6790   if (!(*p)) die("Can't set empty delimiter");
6791 
6792   strmake(delimiter, p, sizeof(delimiter) - 1);
6793   delimiter_length = std::strlen(delimiter);
6794 
6795   DBUG_PRINT("exit", ("delimiter: %s", delimiter));
6796   command->last_argument = p + delimiter_length;
6797 }
6798 
6799 /*
6800   do_reset_connection
6801 
6802   DESCRIPTION
6803   Reset the current session.
6804 */
do_reset_connection()6805 static void do_reset_connection() {
6806   MYSQL *mysql = &cur_con->mysql;
6807 
6808   DBUG_TRACE;
6809   if (mysql_reset_connection(mysql))
6810     die("reset connection failed: %s", mysql_error(mysql));
6811   if (cur_con->stmt) {
6812     mysql_stmt_close(cur_con->stmt);
6813     cur_con->stmt = nullptr;
6814   }
6815 }
6816 
match_delimiter(int c,const char * delim,size_t length)6817 bool match_delimiter(int c, const char *delim, size_t length) {
6818   uint i;
6819   char tmp[MAX_DELIMITER_LENGTH];
6820 
6821   if (c != *delim) return false;
6822 
6823   for (i = 1; i < length && (c = my_getc(cur_file->file)) == *(delim + i); i++)
6824     tmp[i] = c;
6825 
6826   if (i == length) return true; /* Found delimiter */
6827 
6828   /* didn't find delimiter, push back things that we read */
6829   my_ungetc(c);
6830   while (i > 1) my_ungetc(tmp[--i]);
6831   return false;
6832 }
6833 
end_of_query(int c)6834 static bool end_of_query(int c) {
6835   return match_delimiter(c, delimiter, delimiter_length);
6836 }
6837 
6838 /*
6839   Read one "line" from the file
6840 
6841   SYNOPSIS
6842   read_line
6843   buf     buffer for the read line
6844   size    size of the buffer i.e max size to read
6845 
6846   DESCRIPTION
6847   This function actually reads several lines and adds them to the
6848   buffer buf. It continues to read until it finds what it believes
6849   is a complete query.
6850 
6851   Normally that means it will read lines until it reaches the
6852   "delimiter" that marks end of query. Default delimiter is ';'
6853   The function should be smart enough not to detect delimiter's
6854   found inside strings surrounded with '"' and '\'' escaped strings.
6855 
6856   If the first line in a query starts with '#' or '-' this line is treated
6857   as a comment. A comment is always terminated when end of line '\n' is
6858   reached.
6859 
6860 */
6861 
read_line(char * buf,int size)6862 static int read_line(char *buf, int size) {
6863   char c, last_quote = 0, last_char = 0;
6864   char *p = buf, *buf_end = buf + size - 1;
6865   int skip_char = 0;
6866   int query_comment = 0, query_comment_start = 0, query_comment_end = 0;
6867   bool have_slash = false;
6868 
6869   enum {
6870     R_NORMAL,
6871     R_Q,
6872     R_SLASH_IN_Q,
6873     R_COMMENT,
6874     R_LINE_START
6875   } state = R_LINE_START;
6876   DBUG_TRACE;
6877 
6878   start_lineno = cur_file->lineno;
6879   DBUG_PRINT("info", ("Starting to read at lineno: %d", start_lineno));
6880   for (; p < buf_end;) {
6881     skip_char = 0;
6882     c = my_getc(cur_file->file);
6883     if (feof(cur_file->file)) {
6884     found_eof:
6885       if (cur_file->file != stdin) {
6886         fclose(cur_file->file);
6887         cur_file->file = nullptr;
6888       }
6889       my_free(cur_file->file_name);
6890       cur_file->file_name = nullptr;
6891       if (cur_file == file_stack) {
6892         /* We're back at the first file, check if
6893            all { have matching }
6894         */
6895         if (cur_block != block_stack) die("Missing end of block");
6896 
6897         *p = 0;
6898         DBUG_PRINT("info", ("end of file at line %d", cur_file->lineno));
6899         return 1;
6900       }
6901       cur_file--;
6902       start_lineno = cur_file->lineno;
6903       continue;
6904     }
6905 
6906     if (c == '\n') {
6907       /* Line counting is independent of state */
6908       cur_file->lineno++;
6909 
6910       /* Convert cr/lf to lf */
6911       if (p != buf && *(p - 1) == '\r') p--;
6912     }
6913 
6914     switch (state) {
6915       case R_NORMAL:
6916         if (end_of_query(c)) {
6917           *p = 0;
6918           DBUG_PRINT("exit", ("Found delimiter '%s' at line %d", delimiter,
6919                               cur_file->lineno));
6920           return 0;
6921         } else if ((c == '{' &&
6922                     (!charset_info->coll->strnncoll(
6923                          charset_info, (const uchar *)"while", 5, (uchar *)buf,
6924                          std::min<ptrdiff_t>(5, p - buf), false) ||
6925                      !charset_info->coll->strnncoll(
6926                          charset_info, (const uchar *)"if", 2, (uchar *)buf,
6927                          std::min<ptrdiff_t>(2, p - buf), false)))) {
6928           /* Only if and while commands can be terminated by { */
6929           *p++ = c;
6930           *p = 0;
6931           DBUG_PRINT("exit", ("Found '{' indicating start of block at line %d",
6932                               cur_file->lineno));
6933           return 0;
6934         } else if (c == '\'' || c == '"' || c == '`') {
6935           if (!have_slash) {
6936             last_quote = c;
6937             state = R_Q;
6938           }
6939         } else if (c == '/') {
6940           if ((query_comment_start == 0) && (query_comment == 0))
6941             query_comment_start = 1;
6942           else if (query_comment_end == 1) {
6943             query_comment = 0;
6944             query_comment_end = 0;
6945           }
6946         } else if (c == '*') {
6947           if ((query_comment == 1) && (query_comment_end == 0))
6948             query_comment_end = 1;
6949           else if (query_comment_start == 1)
6950             query_comment = 1;
6951         } else if ((c == '+') || (c == '!')) {
6952           if ((query_comment_start == 1) && (query_comment == 1)) {
6953             query_comment_start = 0;
6954             query_comment = 0;
6955           }
6956         } else if (query_comment_start == 1)
6957           query_comment_start = 0;
6958         else if (query_comment_end == 1)
6959           query_comment_end = 0;
6960 
6961         have_slash = (c == '\\');
6962         break;
6963 
6964       case R_COMMENT:
6965         if (c == '\n') {
6966           /* Comments are terminated by newline */
6967           *p = 0;
6968           DBUG_PRINT("exit", ("Found newline in comment at line: %d",
6969                               cur_file->lineno));
6970           return 0;
6971         }
6972         break;
6973 
6974       case R_LINE_START:
6975         if (c == '#' || c == '-') {
6976           /* A # or - in the first position of the line - this is a comment */
6977           state = R_COMMENT;
6978         } else if (my_isspace(charset_info, c)) {
6979           if (c == '\n') {
6980             if (last_char == '\n') {
6981               /* Two new lines in a row, return empty line */
6982               DBUG_PRINT("info", ("Found two new lines in a row"));
6983               *p++ = c;
6984               *p = 0;
6985               return 0;
6986             }
6987 
6988             /* Query hasn't started yet */
6989             start_lineno = cur_file->lineno;
6990             DBUG_PRINT("info", ("Query hasn't started yet, start_lineno: %d",
6991                                 start_lineno));
6992           }
6993 
6994           /* Skip all space at begining of line */
6995           skip_char = 1;
6996         } else if (end_of_query(c)) {
6997           *p = 0;
6998           DBUG_PRINT("exit", ("Found delimiter '%s' at line: %d", delimiter,
6999                               cur_file->lineno));
7000           return 0;
7001         } else if (c == '}') {
7002           /* A "}" need to be by itself in the begining of a line to terminate
7003            */
7004           *p++ = c;
7005           *p = 0;
7006           DBUG_PRINT("exit", ("Found '}' in begining of a line at line: %d",
7007                               cur_file->lineno));
7008           return 0;
7009         } else if (c == '\'' || c == '"' || c == '`') {
7010           last_quote = c;
7011           state = R_Q;
7012         } else
7013           state = R_NORMAL;
7014         break;
7015 
7016       case R_Q:
7017         if (c == last_quote)
7018           state = R_NORMAL;
7019         else if (c == '\\')
7020           state = R_SLASH_IN_Q;
7021         else if (query_comment)
7022           state = R_NORMAL;
7023         break;
7024 
7025       case R_SLASH_IN_Q:
7026         state = R_Q;
7027         break;
7028     }
7029 
7030     last_char = c;
7031 
7032     if (!skip_char) {
7033       /* Could be a multibyte character */
7034       /* This code is based on the code in "sql_load.cc" */
7035       uint charlen;
7036       if (my_mbmaxlenlen(charset_info) == 1)
7037         charlen = my_mbcharlen(charset_info, (unsigned char)c);
7038       else {
7039         if (!(charlen = my_mbcharlen(charset_info, (unsigned char)c))) {
7040           int c1 = my_getc(cur_file->file);
7041           if (c1 == EOF) {
7042             *p++ = c;
7043             goto found_eof;
7044           }
7045 
7046           charlen =
7047               my_mbcharlen_2(charset_info, (unsigned char)c, (unsigned char)c1);
7048           my_ungetc(c1);
7049         }
7050       }
7051       if (charlen == 0) return 1;
7052       /* We give up if multibyte character is started but not */
7053       /* completed before we pass buf_end */
7054       if ((charlen > 1) && (p + charlen) <= buf_end) {
7055         char *mb_start = p;
7056 
7057         *p++ = c;
7058 
7059         for (uint i = 1; i < charlen; i++) {
7060           c = my_getc(cur_file->file);
7061           if (feof(cur_file->file)) goto found_eof;
7062           *p++ = c;
7063         }
7064         if (!my_ismbchar(charset_info, mb_start, p)) {
7065           /* It was not a multiline char, push back the characters */
7066           /* We leave first 'c', i.e. pretend it was a normal char */
7067           while (p - 1 > mb_start) my_ungetc(*--p);
7068         }
7069       } else
7070         *p++ = c;
7071     }
7072   }
7073   die("The input buffer is too small for this query.x\n"
7074       "check your query or increase MAX_QUERY and recompile");
7075   return 0;
7076 }
7077 
7078 /*
7079   Convert the read query to result format version 1
7080 
7081   That is: After newline, all spaces need to be skipped
7082   unless the previous char was a quote
7083 
7084   This is due to an old bug that has now been fixed, but the
7085   version 1 output format is preserved by using this function
7086 
7087 */
7088 
convert_to_format_v1(char * query)7089 static void convert_to_format_v1(char *query) {
7090   int last_c_was_quote = 0;
7091   char *p = query, *to = query;
7092   char *end = strend(query);
7093   char last_c;
7094 
7095   while (p <= end) {
7096     if (*p == '\n' && !last_c_was_quote) {
7097       *to++ = *p++; /* Save the newline */
7098 
7099       /* Skip any spaces on next line */
7100       while (*p && my_isspace(charset_info, *p)) p++;
7101 
7102       last_c_was_quote = 0;
7103     } else if (*p == '\'' || *p == '"' || *p == '`') {
7104       last_c = *p;
7105       *to++ = *p++;
7106 
7107       /* Copy anything until the next quote of same type */
7108       while (*p && *p != last_c) *to++ = *p++;
7109 
7110       *to++ = *p++;
7111 
7112       last_c_was_quote = 1;
7113     } else {
7114       *to++ = *p++;
7115       last_c_was_quote = 0;
7116     }
7117   }
7118 }
7119 
7120 /*
7121   Check for unexpected "junk" after the end of query
7122   This is normally caused by missing delimiters or when
7123   switching between different delimiters
7124 */
7125 
check_eol_junk_line(const char * line)7126 void check_eol_junk_line(const char *line) {
7127   const char *p = line;
7128   DBUG_TRACE;
7129   DBUG_PRINT("enter", ("line: %s", line));
7130 
7131   /* Check for extra delimiter */
7132   if (*p && !std::strncmp(p, delimiter, delimiter_length))
7133     die("Extra delimiter \"%s\" found", delimiter);
7134 
7135   /* Allow trailing # comment */
7136   if (*p && *p != '#') {
7137     if (*p == '\n') die("Missing delimiter");
7138     die("End of line junk detected: \"%s\"", p);
7139   }
7140 }
7141 
check_eol_junk(const char * eol)7142 static void check_eol_junk(const char *eol) {
7143   const char *p = eol;
7144   DBUG_TRACE;
7145   DBUG_PRINT("enter", ("eol: %s", eol));
7146 
7147   /* Skip past all spacing chars and comments */
7148   while (*p && (my_isspace(charset_info, *p) || *p == '#' || *p == '\n')) {
7149     /* Skip past comments started with # and ended with newline */
7150     if (*p && *p == '#') {
7151       p++;
7152       while (*p && *p != '\n') p++;
7153     }
7154 
7155     /* Check this line */
7156     if (*p && *p == '\n') check_eol_junk_line(p);
7157 
7158     if (*p) p++;
7159   }
7160 
7161   check_eol_junk_line(p);
7162 }
7163 
is_delimiter(const char * p)7164 static bool is_delimiter(const char *p) {
7165   uint match = 0;
7166   char *delim = delimiter;
7167   while (*p && *p == *delim++) {
7168     match++;
7169     p++;
7170   }
7171 
7172   return (match == delimiter_length);
7173 }
7174 
7175 // 256K -- a test in sp-big is >128K
7176 #define MAX_QUERY (256 * 1024 * 2)
7177 static char read_command_buf[MAX_QUERY];
7178 
7179 /// Create a command from a set of lines.
7180 ///
7181 /// Converts lines returned by read_line into a command, this involves
7182 /// parsing the first word in the read line to find the command type.
7183 ///
7184 /// A '`--`' comment may contain a valid query as the first word after
7185 /// the comment start. Thus it's always checked to see if that is the
7186 /// case. The advantage with this approach is to be able to execute
7187 /// commands terminated by new line '\n' regardless how many "delimiter"
7188 /// it contain.
7189 ///
7190 /// @param [in] command_ptr pointer where to return the new query
7191 ///
7192 /// @retval 0 on success, else 1
read_command(struct st_command ** command_ptr)7193 static int read_command(struct st_command **command_ptr) {
7194   char *p = read_command_buf;
7195   DBUG_TRACE;
7196 
7197   if (parser.current_line < parser.read_lines) {
7198     *command_ptr = q_lines->at(parser.current_line);
7199     // Assign the current command line number
7200     start_lineno = (*command_ptr)->lineno;
7201     return 0;
7202   }
7203 
7204   struct st_command *command;
7205   if (!(*command_ptr = command = (struct st_command *)my_malloc(
7206             PSI_NOT_INSTRUMENTED, sizeof(*command),
7207             MYF(MY_WME | MY_ZEROFILL))) ||
7208       q_lines->push_back(command))
7209     die("Out of memory");
7210   command->type = Q_UNKNOWN;
7211 
7212   read_command_buf[0] = 0;
7213   if (read_line(read_command_buf, sizeof(read_command_buf))) {
7214     check_eol_junk(read_command_buf);
7215     return 1;
7216   }
7217 
7218   // Set the line number for the command
7219   command->lineno = start_lineno;
7220 
7221   if (opt_result_format_version == 1) convert_to_format_v1(read_command_buf);
7222 
7223   DBUG_PRINT("info", ("query: '%s'", read_command_buf));
7224   if (*p == '#') {
7225     command->type = Q_COMMENT;
7226   } else if (p[0] == '-' && p[1] == '-') {
7227     command->type = Q_COMMENT_WITH_COMMAND;
7228     // Skip past '--'
7229     p += 2;
7230   } else if (*p == '\n') {
7231     command->type = Q_EMPTY_LINE;
7232   }
7233 
7234   // Skip leading spaces
7235   while (*p && my_isspace(charset_info, *p)) p++;
7236 
7237   if (!(command->query_buf = command->query =
7238             my_strdup(PSI_NOT_INSTRUMENTED, p, MYF(MY_WME))))
7239     die("Out of memory");
7240 
7241   // Calculate first word length(the command), terminated
7242   // by 'space' , '(' or 'delimiter'
7243   p = command->query;
7244   while (*p && !my_isspace(charset_info, *p) && *p != '(' && !is_delimiter(p))
7245     p++;
7246   command->first_word_len = (uint)(p - command->query);
7247   DBUG_PRINT("info",
7248              ("first_word: %.*s", static_cast<int>(command->first_word_len),
7249               command->query));
7250 
7251   // Skip spaces between command and first argument
7252   while (*p && my_isspace(charset_info, *p)) p++;
7253   command->first_argument = p;
7254 
7255   command->end = strend(command->query);
7256   command->query_len = (command->end - command->query);
7257   parser.read_lines++;
7258   return 0;
7259 }
7260 
7261 static struct my_option my_long_options[] = {
7262 #include "caching_sha2_passwordopt-longopts.h"
7263 #include "sslopt-longopts.h"
7264     {"basedir", 'b', "Basedir for tests.", &opt_basedir, &opt_basedir, nullptr,
7265      GET_STR, REQUIRED_ARG, 0, 0, 0, nullptr, 0, nullptr},
7266     {"character-sets-dir", OPT_CHARSETS_DIR,
7267      "Directory for character set files.", &opt_charsets_dir, &opt_charsets_dir,
7268      nullptr, GET_STR, REQUIRED_ARG, 0, 0, 0, nullptr, 0, nullptr},
7269     {"colored-diff", OPT_COLORED_DIFF, "Colorize the diff outout.",
7270      &opt_colored_diff, &opt_colored_diff, nullptr, GET_BOOL, NO_ARG, 0, 0, 0,
7271      nullptr, 0, nullptr},
7272     {"compress", 'C', "Use the compressed server/client protocol.",
7273      &opt_compress, &opt_compress, nullptr, GET_BOOL, NO_ARG, 0, 0, 0, nullptr,
7274      0, nullptr},
7275     {"connect_timeout", OPT_CONNECT_TIMEOUT,
7276      "Number of seconds before connection timeout.", &opt_connect_timeout,
7277      &opt_connect_timeout, nullptr, GET_UINT, REQUIRED_ARG, 120, 0, 3600 * 12,
7278      nullptr, 0, nullptr},
7279     {"cursor-protocol", OPT_CURSOR_PROTOCOL,
7280      "Use cursors for prepared statements.", &cursor_protocol, &cursor_protocol,
7281      nullptr, GET_BOOL, NO_ARG, 0, 0, 0, nullptr, 0, nullptr},
7282     {"database", 'D', "Database to use.", &opt_db, &opt_db, nullptr, GET_STR,
7283      REQUIRED_ARG, 0, 0, 0, nullptr, 0, nullptr},
7284 #ifdef DBUG_OFF
7285     {"debug", '#', "This is a non-debug version. Catch this and exit.", 0, 0, 0,
7286      GET_DISABLED, OPT_ARG, 0, 0, 0, 0, 0, 0},
7287     {"debug-check", OPT_DEBUG_CHECK,
7288      "This is a non-debug version. Catch this and exit.", 0, 0, 0, GET_DISABLED,
7289      NO_ARG, 0, 0, 0, 0, 0, 0},
7290     {"debug-info", OPT_DEBUG_INFO,
7291      "This is a non-debug version. Catch this and exit.", 0, 0, 0, GET_DISABLED,
7292      NO_ARG, 0, 0, 0, 0, 0, 0},
7293 #else
7294     {"debug", '#', "Output debug log. Often this is 'd:t:o,filename'.", nullptr,
7295      nullptr, nullptr, GET_STR, OPT_ARG, 0, 0, 0, nullptr, 0, nullptr},
7296     {"debug-check", OPT_DEBUG_CHECK,
7297      "Check memory and open file usage at exit.", &debug_check_flag,
7298      &debug_check_flag, nullptr, GET_BOOL, NO_ARG, 0, 0, 0, nullptr, 0,
7299      nullptr},
7300     {"debug-info", OPT_DEBUG_INFO, "Print some debug info at exit.",
7301      &debug_info_flag, &debug_info_flag, nullptr, GET_BOOL, NO_ARG, 0, 0, 0,
7302      nullptr, 0, nullptr},
7303 #endif
7304     {"default-character-set", OPT_DEFAULT_CHARSET,
7305      "Set the default character set.", &default_charset, &default_charset,
7306      nullptr, GET_STR, REQUIRED_ARG, 0, 0, 0, nullptr, 0, nullptr},
7307     {"explain-protocol", OPT_EXPLAIN_PROTOCOL,
7308      "Explain all SELECT/INSERT/REPLACE/UPDATE/DELETE statements",
7309      &explain_protocol, &explain_protocol, nullptr, GET_BOOL, NO_ARG, 0, 0, 0,
7310      nullptr, 0, nullptr},
7311     {"help", '?', "Display this help and exit.", nullptr, nullptr, nullptr,
7312      GET_NO_ARG, NO_ARG, 0, 0, 0, nullptr, 0, nullptr},
7313     {"host", 'h', "Connect to host.", &opt_host, &opt_host, nullptr, GET_STR,
7314      REQUIRED_ARG, 0, 0, 0, nullptr, 0, nullptr},
7315     {"include", 'i', "Include SQL before each test case.", &opt_include,
7316      &opt_include, nullptr, GET_STR, REQUIRED_ARG, 0, 0, 0, nullptr, 0,
7317      nullptr},
7318     {"json-explain-protocol", OPT_JSON_EXPLAIN_PROTOCOL,
7319      "Explain all SELECT/INSERT/REPLACE/UPDATE/DELETE statements with "
7320      "FORMAT=JSON",
7321      &json_explain_protocol, &json_explain_protocol, nullptr, GET_BOOL, NO_ARG,
7322      0, 0, 0, nullptr, 0, nullptr},
7323     {"logdir", OPT_LOG_DIR, "Directory for log files", &opt_logdir, &opt_logdir,
7324      nullptr, GET_STR, REQUIRED_ARG, 0, 0, 0, nullptr, 0, nullptr},
7325     {"mark-progress", OPT_MARK_PROGRESS,
7326      "Write line number and elapsed time to <testname>.progress.",
7327      &opt_mark_progress, &opt_mark_progress, nullptr, GET_BOOL, NO_ARG, 0, 0, 0,
7328      nullptr, 0, nullptr},
7329     {"max-connect-retries", OPT_MAX_CONNECT_RETRIES,
7330      "Maximum number of attempts to connect to server.",
7331      &opt_max_connect_retries, &opt_max_connect_retries, nullptr, GET_INT,
7332      REQUIRED_ARG, 500, 1, 10000, nullptr, 0, nullptr},
7333     {"max-connections", OPT_MAX_CONNECTIONS,
7334      "Max number of open connections to server", &opt_max_connections,
7335      &opt_max_connections, nullptr, GET_INT, REQUIRED_ARG, 128, 8, 5120,
7336      nullptr, 0, nullptr},
7337     {"no-skip", OPT_NO_SKIP, "Force the test to run without skip.", &no_skip,
7338      &no_skip, nullptr, GET_BOOL, NO_ARG, 0, 0, 0, nullptr, 0, nullptr},
7339     {"no-skip-exclude-list", 'n',
7340      "Contains comma seperated list of to be excluded inc files.",
7341      &excluded_string, &excluded_string, nullptr, GET_STR, REQUIRED_ARG, 0, 0,
7342      0, nullptr, 0, nullptr},
7343     {"offload-count-file", OPT_OFFLOAD_COUNT_FILE, "Offload count report file",
7344      &opt_offload_count_file, &opt_offload_count_file, nullptr, GET_STR,
7345      REQUIRED_ARG, 0, 0, 0, nullptr, 0, nullptr},
7346     {"opt-trace-protocol", OPT_TRACE_PROTOCOL,
7347      "Trace DML statements with optimizer trace", &opt_trace_protocol,
7348      &opt_trace_protocol, nullptr, GET_BOOL, NO_ARG, 0, 0, 0, nullptr, 0,
7349      nullptr},
7350     {"password", 'p', "Password to use when connecting to server.", nullptr,
7351      nullptr, nullptr, GET_STR, OPT_ARG, 0, 0, 0, nullptr, 0, nullptr},
7352     {"plugin_dir", OPT_PLUGIN_DIR, "Directory for client-side plugins.",
7353      &opt_plugin_dir, &opt_plugin_dir, nullptr, GET_STR, REQUIRED_ARG, 0, 0, 0,
7354      nullptr, 0, nullptr},
7355     {"port", 'P',
7356      "Port number to use for connection or 0 for default to, in "
7357      "order of preference, my.cnf, $MYSQL_TCP_PORT, "
7358 #if MYSQL_PORT_DEFAULT == 0
7359      "/etc/services, "
7360 #endif
7361      "built-in default (" STRINGIFY_ARG(MYSQL_PORT) ").",
7362      &opt_port, &opt_port, nullptr, GET_INT, REQUIRED_ARG, 0, 0, 0, nullptr, 0,
7363      nullptr},
7364     {"protocol", OPT_MYSQL_PROTOCOL,
7365      "The protocol of connection (tcp,socket,pipe,memory).", nullptr, nullptr,
7366      nullptr, GET_STR, REQUIRED_ARG, 0, 0, 0, nullptr, 0, nullptr},
7367     {"ps-protocol", OPT_PS_PROTOCOL,
7368      "Use prepared-statement protocol for communication.", &ps_protocol,
7369      &ps_protocol, nullptr, GET_BOOL, NO_ARG, 0, 0, 0, nullptr, 0, nullptr},
7370     {"quiet", 's', "Suppress all normal output.", &silent, &silent, nullptr,
7371      GET_BOOL, NO_ARG, 0, 0, 0, nullptr, 0, nullptr},
7372     {"record", 'r', "Record output of test_file into result file.", nullptr,
7373      nullptr, nullptr, GET_NO_ARG, NO_ARG, 0, 0, 0, nullptr, 0, nullptr},
7374     {"result-file", 'R', "Read/store result from/in this file.",
7375      &result_file_name, &result_file_name, nullptr, GET_STR, REQUIRED_ARG, 0, 0,
7376      0, nullptr, 0, nullptr},
7377     {"result-format-version", OPT_RESULT_FORMAT_VERSION,
7378      "Version of the result file format to use", &opt_result_format_version,
7379      &opt_result_format_version, nullptr, GET_INT, REQUIRED_ARG, 1, 1, 2,
7380      nullptr, 0, nullptr},
7381 #ifdef _WIN32
7382     {"safe-process-pid", OPT_SAFEPROCESS_PID, "PID of safeprocess.",
7383      &opt_safe_process_pid, &opt_safe_process_pid, 0, GET_INT, REQUIRED_ARG, 0,
7384      0, 0, 0, 0, 0},
7385 #endif
7386     {"shared-memory-base-name", OPT_SHARED_MEMORY_BASE_NAME,
7387      "Base name of shared memory.", &shared_memory_base_name,
7388      &shared_memory_base_name, nullptr, GET_STR, REQUIRED_ARG, 0, 0, 0, nullptr,
7389      0, nullptr},
7390     {"silent", 's', "Suppress all normal output. Synonym for --quiet.", &silent,
7391      &silent, nullptr, GET_BOOL, NO_ARG, 0, 0, 0, nullptr, 0, nullptr},
7392     {"socket", 'S', "The socket file to use for connection.", &unix_sock,
7393      &unix_sock, nullptr, GET_STR, REQUIRED_ARG, 0, 0, 0, nullptr, 0, nullptr},
7394     {"sp-protocol", OPT_SP_PROTOCOL, "Use stored procedures for select.",
7395      &sp_protocol, &sp_protocol, nullptr, GET_BOOL, NO_ARG, 0, 0, 0, nullptr, 0,
7396      nullptr},
7397     {"tail-lines", OPT_TAIL_LINES,
7398      "Number of lines of the result to include in a failure report.",
7399      &opt_tail_lines, &opt_tail_lines, nullptr, GET_INT, REQUIRED_ARG, 0, 0,
7400      10000, nullptr, 0, nullptr},
7401     {"test-file", 'x', "Read test from/in this file (default stdin).", nullptr,
7402      nullptr, nullptr, GET_STR, REQUIRED_ARG, 0, 0, 0, nullptr, 0, nullptr},
7403     {"timer-file", 'm', "File where the timing in microseconds is stored.",
7404      nullptr, nullptr, nullptr, GET_STR, REQUIRED_ARG, 0, 0, 0, nullptr, 0,
7405      nullptr},
7406     {"tmpdir", 't', "Temporary directory where sockets are put.", nullptr,
7407      nullptr, nullptr, GET_STR, REQUIRED_ARG, 0, 0, 0, nullptr, 0, nullptr},
7408     {"trace-exec", OPT_TRACE_EXEC, "Print output from exec to stdout.",
7409      &trace_exec, &trace_exec, nullptr, GET_BOOL, NO_ARG, 0, 0, 0, nullptr, 0,
7410      nullptr},
7411     {"user", 'u', "User for login.", &opt_user, &opt_user, nullptr, GET_STR,
7412      REQUIRED_ARG, 0, 0, 0, nullptr, 0, nullptr},
7413     {"verbose", 'v', "Write more.", &verbose, &verbose, nullptr, GET_BOOL,
7414      NO_ARG, 0, 0, 0, nullptr, 0, nullptr},
7415     {"version", 'V', "Output version information and exit.", nullptr, nullptr,
7416      nullptr, GET_NO_ARG, NO_ARG, 0, 0, 0, nullptr, 0, nullptr},
7417     {"view-protocol", OPT_VIEW_PROTOCOL, "Use views for select.",
7418      &view_protocol, &view_protocol, nullptr, GET_BOOL, NO_ARG, 0, 0, 0,
7419      nullptr, 0, nullptr},
7420     {"async-client", '*', "Use async client.", &use_async_client,
7421      &use_async_client, nullptr, GET_BOOL, NO_ARG, 0, 0, 0, nullptr, 0,
7422      nullptr},
7423     {"compression-algorithms", 0,
7424      "Use compression algorithm in server/client protocol. Valid values "
7425      "are any combination of 'zstd','zlib','uncompressed'.",
7426      &opt_compress_algorithm, &opt_compress_algorithm, nullptr, GET_STR,
7427      REQUIRED_ARG, 0, 0, 0, nullptr, 0, nullptr},
7428     {"zstd-compression-level", 0,
7429      "Use this compression level in the client/server protocol, in case "
7430      "--compression-algorithms=zstd. Valid range is between 1 and 22, "
7431      "inclusive. Default is 3.",
7432      &opt_zstd_compress_level, &opt_zstd_compress_level, nullptr, GET_UINT,
7433      REQUIRED_ARG, 3, 1, 22, nullptr, 0, nullptr},
7434 
7435     {nullptr, 0, nullptr, nullptr, nullptr, nullptr, GET_NO_ARG, NO_ARG, 0, 0,
7436      0, nullptr, 0, nullptr}};
7437 
usage()7438 static void usage() {
7439   print_version();
7440   puts(ORACLE_WELCOME_COPYRIGHT_NOTICE("2000"));
7441   printf(
7442       "Runs a test against the mysql server and compares output with a results "
7443       "file.\n\n");
7444   printf("Usage: %s [OPTIONS] [database] < test_file\n", my_progname);
7445   my_print_help(my_long_options);
7446   printf(
7447       "  --no-defaults       Don't read default options from any options "
7448       "file.\n");
7449   my_print_variables(my_long_options);
7450 }
7451 
get_one_option(int optid,const struct my_option * opt,char * argument)7452 static bool get_one_option(int optid, const struct my_option *opt,
7453                            char *argument) {
7454   switch (optid) {
7455     case '#':
7456 #ifndef DBUG_OFF
7457       DBUG_PUSH(argument ? argument : "d:t:S:i:O,/tmp/mysqltest.trace");
7458       debug_check_flag = true;
7459 #endif
7460       break;
7461     case 'r':
7462       record = 1;
7463       break;
7464     case 'x': {
7465       char buff[FN_REFLEN];
7466       if (!test_if_hard_path(argument)) {
7467         strxmov(buff, opt_basedir, argument, NullS);
7468         argument = buff;
7469       }
7470       fn_format(buff, argument, "", "", MY_UNPACK_FILENAME);
7471       DBUG_ASSERT(cur_file == file_stack && cur_file->file == nullptr);
7472       if (!(cur_file->file = fopen(buff, "rb")))
7473         die("Could not open '%s' for reading, errno: %d", buff, errno);
7474       cur_file->file_name = my_strdup(PSI_NOT_INSTRUMENTED, buff, MYF(MY_FAE));
7475       cur_file->lineno = 1;
7476       break;
7477     }
7478     case 'm': {
7479       static char buff[FN_REFLEN];
7480       if (!test_if_hard_path(argument)) {
7481         strxmov(buff, opt_basedir, argument, NullS);
7482         argument = buff;
7483       }
7484       fn_format(buff, argument, "", "", MY_UNPACK_FILENAME);
7485       timer_file = buff;
7486       unlink(timer_file); /* Ignore error, may not exist */
7487       break;
7488     }
7489     case 'p':
7490       if (argument == disabled_my_option) {
7491         // Don't require password
7492         static char empty_password[] = {'\0'};
7493         DBUG_ASSERT(empty_password[0] ==
7494                     '\0');  // Check that it has not been overwritten
7495         argument = empty_password;
7496       }
7497       if (argument) {
7498         my_free(opt_pass);
7499         opt_pass = my_strdup(PSI_NOT_INSTRUMENTED, argument, MYF(MY_FAE));
7500         while (*argument) *argument++ = 'x'; /* Destroy argument */
7501         tty_password = false;
7502       } else
7503         tty_password = true;
7504       break;
7505 #include "sslopt-case.h"
7506 
7507     case 't':
7508       my_stpnmov(TMPDIR, argument, sizeof(TMPDIR));
7509       break;
7510     case OPT_LOG_DIR:
7511       /* Check that the file exists */
7512       if (access(opt_logdir, F_OK) != 0)
7513         die("The specified log directory does not exist: '%s'", opt_logdir);
7514       break;
7515     case OPT_RESULT_FORMAT_VERSION:
7516       set_result_format_version(opt_result_format_version);
7517       break;
7518     case 'V':
7519       print_version();
7520       exit(0);
7521     case OPT_MYSQL_PROTOCOL:
7522       opt_protocol =
7523           find_type_or_exit(argument, &sql_protocol_typelib, opt->name);
7524       break;
7525     case '?':
7526       usage();
7527       exit(0);
7528   }
7529   return false;
7530 }
7531 
7532 /**
7533   Test case or the result file names may use alphanumeric characters
7534   (A-Z, a-z, 0-9), dash ('-') or underscore ('_'), but should not
7535   start with dash or underscore.
7536 
7537   Check if a file name conatins any other special characters. If yes,
7538   throw an error and abort the test run.
7539 
7540   @param[in] file_name File name
7541 */
7542 
validate_filename(const char * file_name)7543 static void validate_filename(const char *file_name) {
7544   const char *fname = strrchr(file_name, '/');
7545 
7546   if (fname == nullptr) {
7547     if (is_windows) {
7548       fname = strrchr(file_name, '\\');
7549 
7550       if (fname == nullptr)
7551         fname = file_name;
7552       else
7553         fname++;
7554     } else
7555       fname = file_name;
7556   } else
7557     fname++;
7558 
7559   file_name = fname;
7560 
7561   // Check if first character in the file name is a alphanumeric character
7562   if (!my_isalnum(charset_info, file_name[0])) {
7563     die("Invalid file name '%s', first character must be alpha-numeric.",
7564         file_name);
7565   } else
7566     file_name++;
7567 
7568   // Skip extension('.test' or '.result' or '.inc' etc) in the file name
7569   const char *file_name_end = strrchr(file_name, '.');
7570 
7571   while (*file_name && (file_name != file_name_end) &&
7572          (file_name[0] == '-' || file_name[0] == '_' ||
7573           my_isalnum(charset_info, file_name[0]))) {
7574     file_name++;
7575   }
7576 
7577   if (file_name != file_name_end) {
7578     die("Invalid file name '%s'. Test or result file name should "
7579         "consist of only alpha-numeric characters, dash (-) or "
7580         "underscore (_), but should not start with dash or "
7581         "underscore.",
7582         fname);
7583   }
7584 }
7585 
parse_args(int argc,char ** argv)7586 static int parse_args(int argc, char **argv) {
7587   if (load_defaults("my", load_default_groups, &argc, &argv, &argv_alloc))
7588     exit(1);
7589 
7590   if ((handle_options(&argc, &argv, my_long_options, get_one_option))) exit(1);
7591 
7592   // Check for special characters in test case file name
7593   if (cur_file->file_name) validate_filename(cur_file->file_name);
7594 
7595   // Check for special characters in result file name
7596   if (result_file_name) validate_filename(result_file_name);
7597 
7598   if (argc > 1) {
7599     usage();
7600     exit(1);
7601   }
7602 
7603   if (argc == 1) opt_db = *argv;
7604   if (tty_password) opt_pass = get_tty_password(NullS);
7605   if (debug_info_flag) my_end_arg = MY_CHECK_ERROR | MY_GIVE_INFO;
7606   if (debug_check_flag) my_end_arg = MY_CHECK_ERROR;
7607 
7608   if (!record) {
7609     /* Check that the result file exists */
7610     if (result_file_name && access(result_file_name, F_OK) != 0)
7611       die("The specified result file '%s' does not exist", result_file_name);
7612   }
7613 
7614   return 0;
7615 }
7616 
7617 /*
7618   Write the content of str into file
7619 
7620   SYNOPSIS
7621   str_to_file2
7622   fname - name of file to truncate/create and write to
7623   str - content to write to file
7624   size - size of content witten to file
7625   append - append to file instead of overwriting old file
7626 */
7627 
str_to_file2(const char * fname,char * str,size_t size,bool append)7628 void str_to_file2(const char *fname, char *str, size_t size, bool append) {
7629   int fd;
7630   char buff[FN_REFLEN];
7631   int flags = O_WRONLY | O_CREAT;
7632   if (!test_if_hard_path(fname)) {
7633     strxmov(buff, opt_basedir, fname, NullS);
7634     fname = buff;
7635   }
7636   fn_format(buff, fname, "", "", MY_UNPACK_FILENAME);
7637 
7638   if (!append) flags |= O_TRUNC;
7639   if ((fd = my_open(buff, flags, MYF(MY_WME))) < 0)
7640     die("Could not open '%s' for writing, errno: %d", buff, errno);
7641   if (append && my_seek(fd, 0, SEEK_END, MYF(0)) == MY_FILEPOS_ERROR)
7642     die("Could not find end of file '%s', errno: %d", buff, errno);
7643   if (my_write(fd, (uchar *)str, size, MYF(MY_WME | MY_FNABP)))
7644     die("write failed, errno: %d", errno);
7645   my_close(fd, MYF(0));
7646 }
7647 
7648 /*
7649   Write the content of str into file
7650 
7651   SYNOPSIS
7652   str_to_file
7653   fname - name of file to truncate/create and write to
7654   str - content to write to file
7655   size - size of content witten to file
7656 */
7657 
str_to_file(const char * fname,char * str,size_t size)7658 void str_to_file(const char *fname, char *str, size_t size) {
7659   str_to_file2(fname, str, size, false);
7660 }
7661 
7662 #ifdef _WIN32
7663 
7664 typedef Prealloced_array<const char *, 16> Patterns;
7665 Patterns *patterns;
7666 
7667 /*
7668   init_win_path_patterns
7669 
7670   DESCRIPTION
7671   Setup string patterns that will be used to detect filenames that
7672   needs to be converted from Win to Unix format
7673 
7674 */
7675 
init_win_path_patterns()7676 void init_win_path_patterns() {
7677   /* List of string patterns to match in order to find paths */
7678   const char *paths[] = {
7679       "$MYSQL_TEST_DIR", "$MYSQL_TMP_DIR",  "$MYSQLTEST_VARDIR",
7680       "$MASTER_MYSOCK",  "$MYSQL_SHAREDIR", "$MYSQL_CHARSETSDIR",
7681       "$MYSQL_LIBDIR",   "./test/",         ".ibd",
7682       ".\\ibdata",       ".\\ibtmp",        ".\\undo"};
7683   int num_paths = sizeof(paths) / sizeof(char *);
7684   int i;
7685   char *p;
7686 
7687   DBUG_TRACE;
7688 
7689   patterns = new Patterns(PSI_NOT_INSTRUMENTED);
7690 
7691   /* Loop through all paths in the array */
7692   for (i = 0; i < num_paths; i++) {
7693     VAR *v;
7694     if (*(paths[i]) == '$') {
7695       v = var_get(paths[i], 0, 0, 0);
7696       p = my_strdup(PSI_NOT_INSTRUMENTED, v->str_val, MYF(MY_FAE));
7697     } else
7698       p = my_strdup(PSI_NOT_INSTRUMENTED, paths[i], MYF(MY_FAE));
7699 
7700     /* Don't insert zero length strings in patterns array */
7701     if (std::strlen(p) == 0) {
7702       my_free(p);
7703       continue;
7704     }
7705 
7706     if (patterns->push_back(p)) die("Out of memory");
7707 
7708     DBUG_PRINT("info", ("p: %s", p));
7709     while (*p) {
7710       if (*p == '/') *p = '\\';
7711       p++;
7712     }
7713   }
7714 }
7715 
free_win_path_patterns()7716 void free_win_path_patterns() {
7717   uint i = 0;
7718   const char **pat;
7719   for (pat = patterns->begin(); pat != patterns->end(); ++pat) {
7720     my_free(const_cast<char *>(*pat));
7721   }
7722   delete patterns;
7723   patterns = NULL;
7724 }
7725 
7726 /*
7727   fix_win_paths
7728 
7729   DESCRIPTION
7730   Search the string 'val' for the patterns that are known to be
7731   strings that contain filenames. Convert all \ to / in the
7732   filenames that are found.
7733 
7734   Ex:
7735   val = 'Error "c:\mysql\mysql-test\var\test\t1.frm" didn't exist'
7736   => $MYSQL_TEST_DIR is found by strstr
7737   => all \ from c:\mysql\m... until next space is converted into /
7738 */
7739 
fix_win_paths(const char * val,size_t len)7740 void fix_win_paths(const char *val, size_t len) {
7741   DBUG_TRACE;
7742   const char **pat;
7743   for (pat = patterns->begin(); pat != patterns->end(); ++pat) {
7744     char *p;
7745     DBUG_PRINT("info", ("pattern: %s", *pat));
7746 
7747     /* Find and fix each path in this string */
7748     p = const_cast<char *>(val);
7749     while (p = strstr(p, *pat)) {
7750       DBUG_PRINT("info", ("Found %s in val p: %s", *pat, p));
7751       /* Found the pattern.  Back up to the start of this path */
7752       while (p > val && !my_isspace(charset_info, *(p - 1))) {
7753         p--;
7754       }
7755 
7756       while (*p && !my_isspace(charset_info, *p)) {
7757         if (*p == '\\') *p = '/';
7758         p++;
7759       }
7760       DBUG_PRINT("info", ("Converted \\ to / in %s", val));
7761     }
7762   }
7763   DBUG_PRINT("exit", (" val: %s, len: %d", val, len));
7764 }
7765 #endif
7766 
7767 /*
7768   Append the result for one field to the dynamic string ds
7769 */
7770 
append_field(DYNAMIC_STRING * ds,uint col_idx,MYSQL_FIELD * field,char * val,size_t len,bool is_null)7771 static void append_field(DYNAMIC_STRING *ds, uint col_idx, MYSQL_FIELD *field,
7772                          char *val, size_t len, bool is_null) {
7773   char null[] = "NULL";
7774 
7775   if (col_idx < max_replace_column && replace_column[col_idx]) {
7776     val = replace_column[col_idx];
7777     len = std::strlen(val);
7778   } else if (is_null) {
7779     val = null;
7780     len = 4;
7781   }
7782 #ifdef _WIN32
7783   else if ((field->type == MYSQL_TYPE_DOUBLE ||
7784             field->type == MYSQL_TYPE_FLOAT) &&
7785            field->decimals >= 31) {
7786     /* Convert 1.2e+018 to 1.2e+18 and 1.2e-018 to 1.2e-18 */
7787     char *start = strchr(val, 'e');
7788     if (start && std::strlen(start) >= 5 &&
7789         (start[1] == '-' || start[1] == '+') && start[2] == '0') {
7790       start += 2; /* Now points at first '0' */
7791       if (field->flags & ZEROFILL_FLAG) {
7792         /* Move all chars before the first '0' one step right */
7793         memmove(val + 1, val, start - val);
7794         *val = '0';
7795       } else {
7796         /* Move all chars after the first '0' one step left */
7797         memmove(start, start + 1, std::strlen(start));
7798         len--;
7799       }
7800     }
7801   }
7802 #endif
7803 
7804   if (!display_result_vertically) {
7805     if (col_idx) dynstr_append_mem(ds, "\t", 1);
7806     replace_dynstr_append_mem(ds, val, len);
7807   } else {
7808     dynstr_append(ds, field->name);
7809     dynstr_append_mem(ds, "\t", 1);
7810     replace_dynstr_append_mem(ds, val, len);
7811     dynstr_append_mem(ds, "\n", 1);
7812   }
7813 }
7814 
7815 /*
7816   Append all results to the dynamic string separated with '\t'
7817   Values may be converted with 'replace_column'
7818 */
7819 
append_result(DYNAMIC_STRING * ds,MYSQL_RES * res)7820 static void append_result(DYNAMIC_STRING *ds, MYSQL_RES *res) {
7821   MYSQL_ROW row;
7822   uint num_fields = mysql_num_fields(res);
7823   MYSQL_FIELD *fields = mysql_fetch_fields(res);
7824   ulong *lengths;
7825 
7826   while ((row = mysql_fetch_row_wrapper(res))) {
7827     uint i;
7828     lengths = mysql_fetch_lengths(res);
7829     for (i = 0; i < num_fields; i++) {
7830       /* looks ugly , but put here to convince parfait */
7831       assert(lengths);
7832       append_field(ds, i, &fields[i], row[i], lengths[i], !row[i]);
7833     }
7834     if (!display_result_vertically) dynstr_append_mem(ds, "\n", 1);
7835   }
7836 }
7837 
7838 /*
7839   Append all results from ps execution to the dynamic string separated
7840   with '\t'. Values may be converted with 'replace_column'
7841 */
7842 
append_stmt_result(DYNAMIC_STRING * ds,MYSQL_STMT * stmt,MYSQL_FIELD * fields,uint num_fields)7843 static void append_stmt_result(DYNAMIC_STRING *ds, MYSQL_STMT *stmt,
7844                                MYSQL_FIELD *fields, uint num_fields) {
7845   MYSQL_BIND *my_bind;
7846   bool *is_null;
7847   ulong *length;
7848   uint i;
7849 
7850   /* Allocate array with bind structs, lengths and NULL flags */
7851   my_bind = (MYSQL_BIND *)my_malloc(PSI_NOT_INSTRUMENTED,
7852                                     num_fields * sizeof(MYSQL_BIND),
7853                                     MYF(MY_WME | MY_FAE | MY_ZEROFILL));
7854   length = (ulong *)my_malloc(PSI_NOT_INSTRUMENTED, num_fields * sizeof(ulong),
7855                               MYF(MY_WME | MY_FAE));
7856   is_null = (bool *)my_malloc(PSI_NOT_INSTRUMENTED, num_fields * sizeof(bool),
7857                               MYF(MY_WME | MY_FAE));
7858 
7859   /* Allocate data for the result of each field */
7860   for (i = 0; i < num_fields; i++) {
7861     size_t max_length = fields[i].max_length + 1;
7862     my_bind[i].buffer_type = MYSQL_TYPE_STRING;
7863     my_bind[i].buffer =
7864         my_malloc(PSI_NOT_INSTRUMENTED, max_length, MYF(MY_WME | MY_FAE));
7865     my_bind[i].buffer_length = static_cast<ulong>(max_length);
7866     my_bind[i].is_null = &is_null[i];
7867     my_bind[i].length = &length[i];
7868 
7869     DBUG_PRINT("bind", ("col[%d]: buffer_type: %d, buffer_length: %lu", i,
7870                         my_bind[i].buffer_type, my_bind[i].buffer_length));
7871   }
7872 
7873   if (mysql_stmt_bind_result(stmt, my_bind))
7874     die("mysql_stmt_bind_result failed: %d: %s", mysql_stmt_errno(stmt),
7875         mysql_stmt_error(stmt));
7876 
7877   while (mysql_stmt_fetch(stmt) == 0) {
7878     for (i = 0; i < num_fields; i++)
7879       append_field(ds, i, &fields[i], (char *)my_bind[i].buffer,
7880                    *my_bind[i].length, *my_bind[i].is_null);
7881     if (!display_result_vertically) dynstr_append_mem(ds, "\n", 1);
7882   }
7883 
7884   int rc;
7885   if ((rc = mysql_stmt_fetch(stmt)) != MYSQL_NO_DATA)
7886     die("fetch didn't end with MYSQL_NO_DATA from statement: %d: %s; rc=%d",
7887         mysql_stmt_errno(stmt), mysql_stmt_error(stmt), rc);
7888 
7889   for (i = 0; i < num_fields; i++) {
7890     /* Free data for output */
7891     my_free(my_bind[i].buffer);
7892   }
7893   /* Free array with bind structs, lengths and NULL flags */
7894   my_free(my_bind);
7895   my_free(length);
7896   my_free(is_null);
7897 }
7898 
7899 /*
7900   Append metadata for fields to output
7901 */
7902 
append_metadata(DYNAMIC_STRING * ds,MYSQL_FIELD * field,uint num_fields)7903 static void append_metadata(DYNAMIC_STRING *ds, MYSQL_FIELD *field,
7904                             uint num_fields) {
7905   MYSQL_FIELD *field_end;
7906   dynstr_append(ds,
7907                 "Catalog\tDatabase\tTable\tTable_alias\tColumn\t"
7908                 "Column_alias\tType\tLength\tMax length\tIs_null\t"
7909                 "Flags\tDecimals\tCharsetnr\n");
7910 
7911   for (field_end = field + num_fields; field < field_end; field++) {
7912     dynstr_append_mem(ds, field->catalog, field->catalog_length);
7913     dynstr_append_mem(ds, "\t", 1);
7914     dynstr_append_mem(ds, field->db, field->db_length);
7915     dynstr_append_mem(ds, "\t", 1);
7916     dynstr_append_mem(ds, field->org_table, field->org_table_length);
7917     dynstr_append_mem(ds, "\t", 1);
7918     dynstr_append_mem(ds, field->table, field->table_length);
7919     dynstr_append_mem(ds, "\t", 1);
7920     dynstr_append_mem(ds, field->org_name, field->org_name_length);
7921     dynstr_append_mem(ds, "\t", 1);
7922     dynstr_append_mem(ds, field->name, field->name_length);
7923     dynstr_append_mem(ds, "\t", 1);
7924     replace_dynstr_append_uint(ds, field->type);
7925     dynstr_append_mem(ds, "\t", 1);
7926     replace_dynstr_append_uint(ds, field->length);
7927     dynstr_append_mem(ds, "\t", 1);
7928     replace_dynstr_append_uint(ds, field->max_length);
7929     dynstr_append_mem(ds, "\t", 1);
7930     dynstr_append_mem(ds, (IS_NOT_NULL(field->flags) ? "N" : "Y"), 1);
7931     dynstr_append_mem(ds, "\t", 1);
7932     replace_dynstr_append_uint(ds, field->flags);
7933     dynstr_append_mem(ds, "\t", 1);
7934     replace_dynstr_append_uint(ds, field->decimals);
7935     dynstr_append_mem(ds, "\t", 1);
7936     replace_dynstr_append_uint(ds, field->charsetnr);
7937     dynstr_append_mem(ds, "\n", 1);
7938   }
7939 }
7940 
7941 /*
7942   Append affected row count and other info to output
7943 */
7944 
append_info(DYNAMIC_STRING * ds,ulonglong affected_rows,const char * info)7945 static void append_info(DYNAMIC_STRING *ds, ulonglong affected_rows,
7946                         const char *info) {
7947   char buf[40], buff2[21];
7948   sprintf(buf, "affected rows: %s\n", llstr(affected_rows, buff2));
7949   dynstr_append(ds, buf);
7950   if (info) {
7951     dynstr_append(ds, "info: ");
7952     dynstr_append(ds, info);
7953     dynstr_append_mem(ds, "\n", 1);
7954   }
7955 }
7956 
7957 /**
7958   @brief Append state change information (received through Ok packet) to the
7959   output.
7960 
7961   @param [in,out] ds         Dynamic string to hold the content to be printed.
7962   @param [in] mysql          Connection handle.
7963 */
7964 
append_session_track_info(DYNAMIC_STRING * ds,MYSQL * mysql)7965 static void append_session_track_info(DYNAMIC_STRING *ds, MYSQL *mysql) {
7966   for (unsigned int type = SESSION_TRACK_BEGIN; type <= SESSION_TRACK_END;
7967        type++) {
7968     const char *data;
7969     size_t data_length;
7970 
7971     if (!mysql_session_track_get_first(mysql, (enum_session_state_type)type,
7972                                        &data, &data_length)) {
7973       /*
7974         Append the type information. Please update the definition of APPEND_TYPE
7975         when any changes are made to enum_session_state_type.
7976       */
7977       APPEND_TYPE(type);
7978       dynstr_append(ds, "-- ");
7979       dynstr_append_mem(ds, data, data_length);
7980     } else
7981       continue;
7982     while (!mysql_session_track_get_next(mysql, (enum_session_state_type)type,
7983                                          &data, &data_length)) {
7984       dynstr_append(ds, "\n-- ");
7985       dynstr_append_mem(ds, data, data_length);
7986     }
7987     dynstr_append(ds, "\n\n");
7988   }
7989 }
7990 
7991 /*
7992   Display the table headings with the names tab separated
7993 */
7994 
append_table_headings(DYNAMIC_STRING * ds,MYSQL_FIELD * field,uint num_fields)7995 static void append_table_headings(DYNAMIC_STRING *ds, MYSQL_FIELD *field,
7996                                   uint num_fields) {
7997   uint col_idx;
7998   for (col_idx = 0; col_idx < num_fields; col_idx++) {
7999     if (col_idx) dynstr_append_mem(ds, "\t", 1);
8000     replace_dynstr_append(ds, field[col_idx].name);
8001   }
8002   dynstr_append_mem(ds, "\n", 1);
8003 }
8004 
8005 /// Check whether a given warning is in list of disabled or enabled warnings.
8006 ///
8007 /// @param warnings      List of either disabled or enabled warnings.
8008 /// @param error         Error number
8009 /// @param warning_found Boolean value, should be set to true if warning
8010 ///                      is found in the list, false otherwise.
8011 ///
8012 /// @retval True if the given warning is present in the list, and
8013 ///         ignore flag for that warning is not set, false otherwise.
match_warnings(Expected_warnings * warnings,std::uint32_t error,bool * warning_found)8014 static bool match_warnings(Expected_warnings *warnings, std::uint32_t error,
8015                            bool *warning_found) {
8016   bool match_found = false;
8017   std::vector<std::unique_ptr<Warning>>::iterator warning = warnings->begin();
8018 
8019   for (; warning != warnings->end(); warning++) {
8020     if ((*warning)->warning_code() == error) {
8021       *warning_found = true;
8022       if (!(*warning)->ignore_warning()) {
8023         match_found = true;
8024         break;
8025       }
8026     }
8027   }
8028 
8029   return match_found;
8030 }
8031 
8032 /// Handle one warning which occurred during execution of a query.
8033 ///
8034 /// @param ds      DYNAMIC_STRING object to store the warnings.
8035 /// @param warning Warning string
8036 ///
8037 /// @retval True if a warning is found in the list of disabled or enabled
8038 ///         warnings, false otherwise.
handle_one_warning(DYNAMIC_STRING * ds,std::string warning)8039 static bool handle_one_warning(DYNAMIC_STRING *ds, std::string warning) {
8040   // Each line of show warnings output contains information about
8041   // error level, error code and the error/warning message separated
8042   // by '\t'. Parse each line from the show warnings output to
8043   // extract the error code and compare it with list of expected
8044   // warnings.
8045   bool warning_found = false;
8046   std::string error_code;
8047   std::stringstream warn_msg(warning);
8048 
8049   while (std::getline(warn_msg, error_code, '\t')) {
8050     int errcode = get_int_val(error_code.c_str());
8051     if (errcode != -1) {
8052       if (disabled_warnings->count()) {
8053         // Print the warning if it doesn't match with any of the
8054         // disabled warnings.
8055         if (!match_warnings(disabled_warnings, errcode, &warning_found)) {
8056           dynstr_append_mem(ds, warning.c_str(), warning.length());
8057           dynstr_append_mem(ds, "\n", 1);
8058         }
8059       } else if (enabled_warnings->count()) {
8060         if (match_warnings(enabled_warnings, errcode, &warning_found)) {
8061           dynstr_append_mem(ds, warning.c_str(), warning.length());
8062           dynstr_append_mem(ds, "\n", 1);
8063         }
8064       }
8065     }
8066   }
8067 
8068   return warning_found;
8069 }
8070 
8071 /// Handle warnings which occurred during execution of a query.
8072 ///
8073 /// @param ds          DYNAMIC_STRING object to store the warnings.
8074 /// @param ds_warnings String containing all the generated warnings.
handle_warnings(DYNAMIC_STRING * ds,const char * ds_warnings)8075 static void handle_warnings(DYNAMIC_STRING *ds, const char *ds_warnings) {
8076   bool warning_found = false;
8077   std::string warning;
8078   std::stringstream warnings(ds_warnings);
8079 
8080   // Set warning_found only if at least one of the warning exist
8081   // in expected list of warnings.
8082   while (std::getline(warnings, warning))
8083     if (handle_one_warning(ds, warning)) warning_found = true;
8084 
8085   // Throw an error and abort the test run if a query generates warnings
8086   // which are not listed as expected.
8087   if (!warning_found) {
8088     std::string warning_list;
8089 
8090     if (disabled_warnings->count())
8091       warning_list = disabled_warnings->warnings_list();
8092     else if (enabled_warnings->count())
8093       warning_list = enabled_warnings->warnings_list();
8094 
8095     die("Query '%s' didn't generate any of the expected warning(s) '%s'.",
8096         curr_command->query, warning_list.c_str());
8097   }
8098 }
8099 
8100 /// Fetch warnings generated by server while executing a query and
8101 /// append them to warnings buffer 'ds'.
8102 ///
8103 /// @param ds    DYNAMIC_STRING object to store the warnings
8104 /// @param mysql mysql handle object
8105 ///
8106 /// @retval Number of warnings appended to ds
append_warnings(DYNAMIC_STRING * ds,MYSQL * mysql)8107 static int append_warnings(DYNAMIC_STRING *ds, MYSQL *mysql) {
8108   unsigned int count;
8109   if (!(count = mysql_warning_count(mysql))) return 0;
8110 
8111   // If one day we will support execution of multi-statements
8112   // through PS API we should not issue SHOW WARNINGS until
8113   // we have not read all results.
8114   DBUG_ASSERT(!mysql_more_results(mysql));
8115 
8116   MYSQL_RES *warn_res;
8117   if (mysql_real_query_wrapper(mysql, "SHOW WARNINGS", 13))
8118     die("Error running query \"SHOW WARNINGS\": %s", mysql_error(mysql));
8119 
8120   if (!(warn_res = mysql_store_result_wrapper(mysql)))
8121     die("Warning count is %u but didn't get any warnings", count);
8122 
8123   DYNAMIC_STRING ds_warnings;
8124   init_dynamic_string(&ds_warnings, "", 1024, 1024);
8125   append_result(&ds_warnings, warn_res);
8126   mysql_free_result_wrapper(warn_res);
8127 
8128   if (disable_warnings &&
8129       (disabled_warnings->count() || enabled_warnings->count()))
8130     handle_warnings(ds, ds_warnings.str);
8131   else if (!disable_warnings)
8132     dynstr_append_mem(ds, ds_warnings.str, ds_warnings.length);
8133 
8134   dynstr_free(&ds_warnings);
8135   return ds->length;
8136 }
8137 
8138 /// Run query using MySQL C API
8139 ///
8140 /// @param cn          Connection object
8141 /// @param command     Pointer to the st_command structure which holds the
8142 ///                    arguments and information for the command.
8143 /// @param flags       Flags indicating if we should SEND and/or REAP.
8144 /// @param query       Query string
8145 /// @param query_len   Length of the query string
8146 /// @param ds          Output buffer to store the query result.
8147 /// @param ds_warnings Buffer to store the warnings generated while
8148 ///                    executing the query.
run_query_normal(struct st_connection * cn,struct st_command * command,int flags,const char * query,size_t query_len,DYNAMIC_STRING * ds,DYNAMIC_STRING * ds_warnings)8149 static void run_query_normal(struct st_connection *cn,
8150                              struct st_command *command, int flags,
8151                              const char *query, size_t query_len,
8152                              DYNAMIC_STRING *ds, DYNAMIC_STRING *ds_warnings) {
8153   int error = 0;
8154   std::uint32_t counter = 0;
8155   MYSQL *mysql = &cn->mysql;
8156   MYSQL_RES *res = nullptr;
8157 
8158   if (flags & QUERY_SEND_FLAG) {
8159     /* Send the query */
8160     if (mysql_send_query_wrapper(&cn->mysql, query,
8161                                  static_cast<ulong>(query_len))) {
8162       handle_error(command, mysql_errno(mysql), mysql_error(mysql),
8163                    mysql_sqlstate(mysql), ds);
8164       goto end;
8165     }
8166   }
8167 
8168   if (!(flags & QUERY_REAP_FLAG)) {
8169     cn->pending = true;
8170     return;
8171   }
8172 
8173   do {
8174     /*
8175       When  on first result set, call mysql_read_query_result_wrapper to
8176       retrieve answer to the query sent earlier
8177     */
8178     if ((counter == 0) && mysql_read_query_result_wrapper(&cn->mysql)) {
8179       /* we've failed to collect the result set */
8180       cn->pending = true;
8181       handle_error(command, mysql_errno(mysql), mysql_error(mysql),
8182                    mysql_sqlstate(mysql), ds);
8183       goto end;
8184     }
8185 
8186     /*
8187       Store the result of the query if it will return any fields
8188     */
8189     if (mysql_field_count(mysql) &&
8190         ((res = mysql_store_result_wrapper(mysql)) == nullptr)) {
8191       handle_error(command, mysql_errno(mysql), mysql_error(mysql),
8192                    mysql_sqlstate(mysql), ds);
8193       goto end;
8194     }
8195 
8196     if (!disable_result_log) {
8197       if (res) {
8198         MYSQL_FIELD *fields = mysql_fetch_fields(res);
8199         std::uint32_t num_fields = mysql_num_fields(res);
8200 
8201         if (display_metadata) append_metadata(ds, fields, num_fields);
8202 
8203         if (!display_result_vertically)
8204           append_table_headings(ds, fields, num_fields);
8205 
8206         append_result(ds, res);
8207       }
8208 
8209       // Need to call mysql_affected_rows() before the "new"
8210       // query to find the warnings.
8211       if (!disable_info)
8212         append_info(ds, mysql_affected_rows(mysql), mysql_info(mysql));
8213 
8214       if (display_session_track_info) append_session_track_info(ds, mysql);
8215 
8216       // Add all warnings to the result. We can't do this if we are in
8217       // the middle of processing results from multi-statement, because
8218       // this will break protocol.
8219       if ((!disable_warnings || disabled_warnings->count() ||
8220            enabled_warnings->count()) &&
8221           !mysql_more_results(mysql)) {
8222         if (append_warnings(ds_warnings, mysql) || ds_warnings->length) {
8223           dynstr_append_mem(ds, "Warnings:\n", 10);
8224           dynstr_append_mem(ds, ds_warnings->str, ds_warnings->length);
8225         }
8226       }
8227     }
8228 
8229     if (res) {
8230       mysql_free_result_wrapper(res);
8231       res = nullptr;
8232     }
8233     counter++;
8234   } while (!(error = mysql_next_result_wrapper(mysql)));
8235   if (error > 0) {
8236     // We got an error from mysql_next_result, maybe expected.
8237     handle_error(command, mysql_errno(mysql), mysql_error(mysql),
8238                  mysql_sqlstate(mysql), ds);
8239     goto end;
8240   }
8241 
8242   // Successful and there are no more results.
8243   DBUG_ASSERT(error == -1);
8244 
8245   // If we come here the query is both executed and read successfully.
8246   handle_no_error(command);
8247   revert_properties();
8248 
8249 end:
8250   cn->pending = false;
8251 
8252   // We save the return code (mysql_errno(mysql)) from the last call sent
8253   // to the server into the mysqltest builtin variable $mysql_errno. This
8254   // variable then can be used from the test case itself.
8255   var_set_errno(mysql_errno(mysql));
8256 }
8257 
8258 /// Run query using prepared statement C API
8259 ///
8260 /// @param mysql       mysql handle
8261 /// @param command     Pointer to the st_command structure which holds the
8262 ///                    arguments and information for the command.
8263 /// @param query       Query string
8264 /// @param query_len   Length of the query string
8265 /// @param ds          Output buffer to store the query result.
8266 /// @param ds_warnings Buffer to store the warnings generated while
8267 ///                    executing the query.
run_query_stmt(MYSQL * mysql,struct st_command * command,const char * query,size_t query_len,DYNAMIC_STRING * ds,DYNAMIC_STRING * ds_warnings)8268 static void run_query_stmt(MYSQL *mysql, struct st_command *command,
8269                            const char *query, size_t query_len,
8270                            DYNAMIC_STRING *ds, DYNAMIC_STRING *ds_warnings) {
8271   // Init a new stmt if it's not already one created for this connection.
8272   MYSQL_STMT *stmt;
8273   if (!(stmt = cur_con->stmt)) {
8274     if (!(stmt = mysql_stmt_init(mysql))) die("unable to init stmt structure");
8275     cur_con->stmt = stmt;
8276   }
8277 
8278   DYNAMIC_STRING ds_prepare_warnings;
8279   DYNAMIC_STRING ds_execute_warnings;
8280 
8281   // Init dynamic strings for warnings.
8282   if (!disable_warnings || disabled_warnings->count() ||
8283       enabled_warnings->count()) {
8284     init_dynamic_string(&ds_prepare_warnings, nullptr, 0, 256);
8285     init_dynamic_string(&ds_execute_warnings, nullptr, 0, 256);
8286   }
8287 
8288   // Note that here 'res' is meta data result set
8289   MYSQL_RES *res = nullptr;
8290   int err = 0;
8291 
8292   // Prepare the query
8293   if (mysql_stmt_prepare(stmt, query, static_cast<ulong>(query_len))) {
8294     handle_error(command, mysql_stmt_errno(stmt), mysql_stmt_error(stmt),
8295                  mysql_stmt_sqlstate(stmt), ds);
8296     goto end;
8297   }
8298 
8299   // Get the warnings from mysql_stmt_prepare and keep them in a
8300   // separate string.
8301   if (!disable_warnings || disabled_warnings->count() ||
8302       enabled_warnings->count())
8303     append_warnings(&ds_prepare_warnings, mysql);
8304 
8305   // No need to call mysql_stmt_bind_param() because we have no
8306   // parameter markers.
8307   if (cursor_protocol_enabled) {
8308     // Use cursor when retrieving result.
8309     unsigned long type = CURSOR_TYPE_READ_ONLY;
8310     if (mysql_stmt_attr_set(stmt, STMT_ATTR_CURSOR_TYPE, (void *)&type))
8311       die("mysql_stmt_attr_set(STMT_ATTR_CURSOR_TYPE) failed': %d %s",
8312           mysql_stmt_errno(stmt), mysql_stmt_error(stmt));
8313   }
8314 
8315   // Execute the query
8316   if (mysql_stmt_execute(stmt)) {
8317     handle_error(command, mysql_stmt_errno(stmt), mysql_stmt_error(stmt),
8318                  mysql_stmt_sqlstate(stmt), ds);
8319     goto end;
8320   }
8321 
8322   // When running in cursor_protocol get the warnings from execute here
8323   // and keep them in a separate string for later.
8324   if (cursor_protocol_enabled &&
8325       (!disable_warnings || disabled_warnings->count() ||
8326        enabled_warnings->count()))
8327     append_warnings(&ds_execute_warnings, mysql);
8328 
8329   // We instruct that we want to update the "max_length" field in
8330   // mysql_stmt_store_result(), this is our only way to know how much
8331   // buffer to allocate for result data
8332   {
8333     bool one = true;
8334     if (mysql_stmt_attr_set(stmt, STMT_ATTR_UPDATE_MAX_LENGTH, (void *)&one))
8335       die("mysql_stmt_attr_set(STMT_ATTR_UPDATE_MAX_LENGTH) failed': %d %s",
8336           mysql_stmt_errno(stmt), mysql_stmt_error(stmt));
8337   }
8338 
8339   do {
8340     // If we got here the statement succeeded and was expected to do so,
8341     // get data. Note that this can still give errors found during execution.
8342     // Store the result of the query if if will return any fields
8343     if (mysql_stmt_field_count(stmt) && mysql_stmt_store_result(stmt)) {
8344       handle_error(command, mysql_stmt_errno(stmt), mysql_stmt_error(stmt),
8345                    mysql_stmt_sqlstate(stmt), ds);
8346       goto end;
8347     }
8348 
8349     if (!disable_result_log) {
8350       // Not all statements creates a result set. If there is one we can
8351       // now create another normal result set that contains the meta
8352       // data. This set can be handled almost like any other non prepared
8353       // statement result set.
8354       if ((res = mysql_stmt_result_metadata(stmt)) != nullptr) {
8355         // Take the column count from meta info
8356         MYSQL_FIELD *fields = mysql_fetch_fields(res);
8357         std::uint32_t num_fields = mysql_num_fields(res);
8358 
8359         if (display_metadata) append_metadata(ds, fields, num_fields);
8360 
8361         if (!display_result_vertically)
8362           append_table_headings(ds, fields, num_fields);
8363 
8364         append_stmt_result(ds, stmt, fields, num_fields);
8365 
8366         // Free normal result set with meta data
8367         mysql_free_result_wrapper(res);
8368 
8369         // Clear prepare warnings if there are execute warnings,
8370         // since they are probably duplicated.
8371         if (ds_execute_warnings.length || mysql->warning_count)
8372           dynstr_set(&ds_prepare_warnings, nullptr);
8373       } else {
8374         // This is a query without resultset
8375       }
8376 
8377       // Fetch info before fetching warnings, since it will be reset
8378       // otherwise.
8379       if (!disable_info)
8380         append_info(ds, mysql_affected_rows(stmt->mysql), mysql_info(mysql));
8381 
8382       if (display_session_track_info) append_session_track_info(ds, mysql);
8383 
8384       // Add all warnings to the result. We can't do this if we are in
8385       // the middle of processing results from multi-statement, because
8386       // this will break protocol.
8387       if ((!disable_warnings || disabled_warnings->count() ||
8388            enabled_warnings->count()) &&
8389           !mysql_more_results(stmt->mysql)) {
8390         // Get the warnings from execute. Append warnings to ds,
8391         // if there are any.
8392         append_warnings(&ds_execute_warnings, mysql);
8393         if (ds_execute_warnings.length || ds_prepare_warnings.length ||
8394             ds_warnings->length) {
8395           dynstr_append_mem(ds, "Warnings:\n", 10);
8396 
8397           // Append warnings if exist any
8398           if (ds_warnings->length)
8399             dynstr_append_mem(ds, ds_warnings->str, ds_warnings->length);
8400 
8401           // Append prepare warnings if exist any
8402           if (ds_prepare_warnings.length)
8403             dynstr_append_mem(ds, ds_prepare_warnings.str,
8404                               ds_prepare_warnings.length);
8405 
8406           // Append execute warnings if exist any
8407           if (ds_execute_warnings.length)
8408             dynstr_append_mem(ds, ds_execute_warnings.str,
8409                               ds_execute_warnings.length);
8410         }
8411       }
8412     }
8413   } while ((err = mysql_stmt_next_result(stmt)) == 0);
8414 
8415   if (err > 0) {
8416     // We got an error from mysql_stmt_next_result, maybe expected.
8417     handle_error(command, mysql_stmt_errno(stmt), mysql_stmt_error(stmt),
8418                  mysql_stmt_sqlstate(stmt), ds);
8419     goto end;
8420   }
8421 
8422   // If we got here the statement was both executed and read successfully.
8423   handle_no_error(command);
8424 
8425 end:
8426   if (!disable_warnings || disabled_warnings->count() ||
8427       enabled_warnings->count()) {
8428     dynstr_free(&ds_prepare_warnings);
8429     dynstr_free(&ds_execute_warnings);
8430   }
8431   revert_properties();
8432 
8433   // We save the return code (mysql_stmt_errno(stmt)) from the last call sent
8434   // to the server into the mysqltest builtin variable $mysql_errno. This
8435   // variable then can be used from the test case itself.
8436   var_set_errno(mysql_stmt_errno(stmt));
8437 
8438   // Close the statement if no reconnect, need new prepare.
8439   if (mysql->reconnect) {
8440     mysql_stmt_close(stmt);
8441     cur_con->stmt = nullptr;
8442   }
8443 }
8444 
8445 /*
8446   Create a util connection if one does not already exists
8447   and use that to run the query
8448   This is done to avoid implict commit when creating/dropping objects such
8449   as view, sp etc.
8450 */
8451 
util_query(MYSQL * org_mysql,const char * query)8452 static int util_query(MYSQL *org_mysql, const char *query) {
8453   MYSQL *mysql;
8454   DBUG_TRACE;
8455 
8456   if (!(mysql = cur_con->util_mysql)) {
8457     DBUG_PRINT("info", ("Creating util_mysql"));
8458     if (!(mysql = mysql_init(mysql))) die("Failed in mysql_init()");
8459 
8460     if (opt_connect_timeout)
8461       mysql_options(mysql, MYSQL_OPT_CONNECT_TIMEOUT,
8462                     (void *)&opt_connect_timeout);
8463 
8464     /* enable local infile, in non-binary builds often disabled by default */
8465     mysql_options(mysql, MYSQL_OPT_LOCAL_INFILE, nullptr);
8466     safe_connect(mysql, "util", org_mysql->host, org_mysql->user,
8467                  org_mysql->passwd, org_mysql->db, org_mysql->port,
8468                  org_mysql->unix_socket);
8469 
8470     cur_con->util_mysql = mysql;
8471   }
8472 
8473   return mysql_query_wrapper(mysql, query);
8474 }
8475 
8476 /*
8477   Run query
8478 
8479   SYNPOSIS
8480     run_query()
8481      mysql	mysql handle
8482      command	currrent command pointer
8483 
8484   flags control the phased/stages of query execution to be performed
8485   if QUERY_SEND_FLAG bit is on, the query will be sent. If QUERY_REAP_FLAG
8486   is on the result will be read - for regular query, both bits must be on
8487 */
8488 
run_query(struct st_connection * cn,struct st_command * command,int flags)8489 static void run_query(struct st_connection *cn, struct st_command *command,
8490                       int flags) {
8491   MYSQL *mysql = &cn->mysql;
8492   DYNAMIC_STRING *ds;
8493   DYNAMIC_STRING *save_ds = nullptr;
8494   DYNAMIC_STRING ds_sorted;
8495   DYNAMIC_STRING ds_warnings;
8496   DYNAMIC_STRING eval_query;
8497   const char *query;
8498   size_t query_len;
8499   bool view_created = false, sp_created = false;
8500   bool complete_query =
8501       ((flags & QUERY_SEND_FLAG) && (flags & QUERY_REAP_FLAG));
8502   DBUG_TRACE;
8503   dynstr_set(&ds_result, "");
8504 
8505   if (cn->pending && (flags & QUERY_SEND_FLAG))
8506     die("Cannot run query on connection between send and reap");
8507 
8508   if (!(flags & QUERY_SEND_FLAG) && !cn->pending)
8509     die("Cannot reap on a connection without pending send");
8510 
8511   init_dynamic_string(&ds_warnings, nullptr, 0, 256);
8512   ds_warn = &ds_warnings;
8513 
8514   /*
8515     Evaluate query if this is an eval command
8516   */
8517   if (command->type == Q_EVAL || command->type == Q_SEND_EVAL) {
8518     init_dynamic_string(&eval_query, "", command->query_len + 256, 1024);
8519     do_eval(&eval_query, command->query, command->end, false);
8520     query = eval_query.str;
8521     query_len = eval_query.length;
8522   } else {
8523     query = command->query;
8524     query_len = std::strlen(query);
8525   }
8526 
8527   /*
8528     Create a temporary dynamic string to contain the
8529     output from this query.
8530   */
8531   if (command->output_file[0])
8532     ds = &ds_result;
8533   else
8534     ds = &ds_res;
8535 
8536   /*
8537     Log the query into the output buffer
8538   */
8539   if (!disable_query_log && (flags & QUERY_SEND_FLAG)) {
8540     replace_dynstr_append_mem(ds, query, query_len);
8541     dynstr_append_mem(ds, delimiter, delimiter_length);
8542     dynstr_append_mem(ds, "\n", 1);
8543   }
8544 
8545   if (view_protocol_enabled && complete_query &&
8546       search_protocol_re(&view_re, query)) {
8547     /*
8548       Create the query as a view.
8549       Use replace since view can exist from a failed mysqltest run
8550     */
8551     DYNAMIC_STRING query_str;
8552     init_dynamic_string(&query_str,
8553                         "CREATE OR REPLACE VIEW mysqltest_tmp_v AS ",
8554                         query_len + 64, 256);
8555     dynstr_append_mem(&query_str, query, query_len);
8556     if (util_query(mysql, query_str.str)) {
8557       /*
8558         Failed to create the view, this is not fatal
8559         just run the query the normal way
8560       */
8561       DBUG_PRINT("view_create_error",
8562                  ("Failed to create view '%s': %d: %s", query_str.str,
8563                   mysql_errno(mysql), mysql_error(mysql)));
8564 
8565       /* Log error to create view */
8566       verbose_msg("Failed to create view '%s' %d: %s", query_str.str,
8567                   mysql_errno(mysql), mysql_error(mysql));
8568     } else {
8569       /*
8570         Yes, it was possible to create this query as a view
8571       */
8572       view_created = true;
8573       query = "SELECT * FROM mysqltest_tmp_v";
8574       query_len = std::strlen(query);
8575 
8576       /*
8577         Collect warnings from create of the view that should otherwise
8578         have been produced when the SELECT was executed
8579       */
8580       append_warnings(&ds_warnings, cur_con->util_mysql);
8581     }
8582 
8583     dynstr_free(&query_str);
8584   }
8585 
8586   if (sp_protocol_enabled && complete_query &&
8587       search_protocol_re(&sp_re, query)) {
8588     /*
8589       Create the query as a stored procedure
8590       Drop first since sp can exist from a failed mysqltest run
8591     */
8592     DYNAMIC_STRING query_str;
8593     init_dynamic_string(&query_str,
8594                         "DROP PROCEDURE IF EXISTS mysqltest_tmp_sp;",
8595                         query_len + 64, 256);
8596     util_query(mysql, query_str.str);
8597     dynstr_set(&query_str, "CREATE PROCEDURE mysqltest_tmp_sp()\n");
8598     dynstr_append_mem(&query_str, query, query_len);
8599     if (util_query(mysql, query_str.str)) {
8600       /*
8601         Failed to create the stored procedure for this query,
8602         this is not fatal just run the query the normal way
8603       */
8604       DBUG_PRINT("sp_create_error",
8605                  ("Failed to create sp '%s': %d: %s", query_str.str,
8606                   mysql_errno(mysql), mysql_error(mysql)));
8607 
8608       /* Log error to create sp */
8609       verbose_msg("Failed to create sp '%s' %d: %s", query_str.str,
8610                   mysql_errno(mysql), mysql_error(mysql));
8611 
8612     } else {
8613       sp_created = true;
8614 
8615       query = "CALL mysqltest_tmp_sp()";
8616       query_len = std::strlen(query);
8617     }
8618     dynstr_free(&query_str);
8619   }
8620 
8621   if (display_result_sorted) {
8622     /*
8623        Collect the query output in a separate string
8624        that can be sorted before it's added to the
8625        global result string
8626     */
8627     init_dynamic_string(&ds_sorted, "", 1024, 1024);
8628     save_ds = ds; /* Remember original ds */
8629     ds = &ds_sorted;
8630   }
8631 
8632   /*
8633     Find out how to run this query
8634 
8635     Always run with normal C API if it's not a complete
8636     SEND + REAP
8637 
8638     If it is a '?' in the query it may be a SQL level prepared
8639     statement already and we can't do it twice
8640   */
8641   if (ps_protocol_enabled && complete_query &&
8642       search_protocol_re(&ps_re, query))
8643     run_query_stmt(mysql, command, query, query_len, ds, &ds_warnings);
8644   else
8645     run_query_normal(cn, command, flags, query, query_len, ds, &ds_warnings);
8646 
8647   dynstr_free(&ds_warnings);
8648   ds_warn = nullptr;
8649   if (command->type == Q_EVAL || command->type == Q_SEND_EVAL)
8650     dynstr_free(&eval_query);
8651 
8652   if (display_result_sorted) {
8653     /* Sort the result set and append it to result */
8654     dynstr_append_sorted(save_ds, &ds_sorted, start_sort_column);
8655     ds = save_ds;
8656     dynstr_free(&ds_sorted);
8657   }
8658 
8659   if (sp_created) {
8660     if (util_query(mysql, "DROP PROCEDURE mysqltest_tmp_sp "))
8661       die("Failed to drop sp: %d: %s", mysql_errno(mysql), mysql_error(mysql));
8662   }
8663 
8664   if (view_created) {
8665     if (util_query(mysql, "DROP VIEW mysqltest_tmp_v "))
8666       die("Failed to drop view: %d: %s", mysql_errno(mysql),
8667           mysql_error(mysql));
8668   }
8669   if (command->output_file[0]) {
8670     /* An output file was specified for _this_ query */
8671     str_to_file2(command->output_file, ds_result.str, ds_result.length, false);
8672     command->output_file[0] = 0;
8673   }
8674 }
8675 
8676 /**
8677    Display the optimizer trace produced by the last executed statement.
8678  */
display_opt_trace(struct st_connection * cn,struct st_command * command,int flags)8679 static void display_opt_trace(struct st_connection *cn,
8680                               struct st_command *command, int flags) {
8681   if (!disable_query_log && opt_trace_protocol_enabled && !cn->pending &&
8682       !expected_errors->count() &&
8683       search_protocol_re(&opt_trace_re, command->query)) {
8684     st_command save_command = *command;
8685     DYNAMIC_STRING query_str;
8686     init_dynamic_string(&query_str,
8687                         "SELECT trace FROM information_schema.optimizer_trace"
8688                         " /* injected by --opt-trace-protocol */",
8689                         128, 128);
8690 
8691     command->query = query_str.str;
8692     command->query_len = query_str.length;
8693     command->end = strend(command->query);
8694 
8695     /* Sorted trace is not readable at all, don't bother to lower case */
8696     /* No need to keep old values, will be reset anyway */
8697     display_result_sorted = false;
8698     display_result_lower = false;
8699     run_query(cn, command, flags);
8700 
8701     dynstr_free(&query_str);
8702     *command = save_command;
8703   }
8704 }
8705 
run_explain(struct st_connection * cn,struct st_command * command,int flags,bool json)8706 static void run_explain(struct st_connection *cn, struct st_command *command,
8707                         int flags, bool json) {
8708   if ((flags & QUERY_REAP_FLAG) && !expected_errors->count() &&
8709       search_protocol_re(&explain_re, command->query)) {
8710     st_command save_command = *command;
8711     DYNAMIC_STRING query_str;
8712     DYNAMIC_STRING ds_warning_messages;
8713 
8714     init_dynamic_string(&ds_warning_messages, "", 0, 2048);
8715     init_dynamic_string(&query_str, json ? "EXPLAIN FORMAT=JSON " : "EXPLAIN ",
8716                         256, 256);
8717     dynstr_append_mem(&query_str, command->query,
8718                       command->end - command->query);
8719 
8720     command->query = query_str.str;
8721     command->query_len = query_str.length;
8722     command->end = strend(command->query);
8723 
8724     run_query(cn, command, flags);
8725 
8726     dynstr_free(&query_str);
8727     dynstr_free(&ds_warning_messages);
8728 
8729     *command = save_command;
8730   }
8731 }
8732 
get_command_type(struct st_command * command)8733 static void get_command_type(struct st_command *command) {
8734   char save;
8735   uint type;
8736   DBUG_TRACE;
8737 
8738   if (*command->query == '}') {
8739     command->type = Q_END_BLOCK;
8740     return;
8741   }
8742 
8743   save = command->query[command->first_word_len];
8744   command->query[command->first_word_len] = 0;
8745   type = find_type(command->query, &command_typelib, FIND_TYPE_NO_PREFIX);
8746   command->query[command->first_word_len] = save;
8747   if (type > 0) {
8748     command->type = (enum enum_commands)type; /* Found command */
8749 
8750     /*
8751       Look for case where "query" was explicitly specified to
8752       force command being sent to server
8753     */
8754     if (type == Q_QUERY) {
8755       /* Skip the "query" part */
8756       command->query = command->first_argument;
8757     }
8758   } else {
8759     /* No mysqltest command matched */
8760 
8761     if (command->type != Q_COMMENT_WITH_COMMAND) {
8762       /* A query that will sent to mysqld */
8763       command->type = Q_QUERY;
8764     } else {
8765       /* -- "comment" that didn't contain a mysqltest command */
8766       die("Found line '%s' beginning with -- that didn't contain "
8767           "a valid mysqltest command, check your syntax or "
8768           "use # if you intended to write a comment",
8769           command->query);
8770     }
8771   }
8772 }
8773 
8774 /// Record how many milliseconds it took to execute the test file
8775 /// up until the current line and write it to .progress file.
8776 ///
8777 /// @param progress_file Logfile object to store the progress information
8778 /// @param line          Line number of the progress file where the progress
8779 ///                      information should be recorded.
mark_progress(Logfile * progress_file,int line)8780 static void mark_progress(Logfile *progress_file, int line) {
8781   static unsigned long long int progress_start = 0;
8782   unsigned long long int timer = timer_now();
8783 
8784   if (!progress_start) progress_start = timer;
8785   timer = timer - progress_start;
8786 
8787   std::string str_progress;
8788 
8789   // Milliseconds since start
8790   std::string str_timer = std::to_string(timer);
8791   str_progress.append(str_timer);
8792   str_progress.append("\t");
8793 
8794   // Parse the line number
8795   std::string str_line = std::to_string(line);
8796   str_progress.append(str_line);
8797   str_progress.append("\t");
8798 
8799   // Filename
8800   str_progress.append(cur_file->file_name);
8801   str_progress.append(":");
8802 
8803   // Line in file
8804   str_line = std::to_string(cur_file->lineno);
8805   str_progress.append(str_line);
8806   str_progress.append("\n");
8807 
8808   if (progress_file->write(str_progress.c_str(), str_progress.length()) ||
8809       progress_file->flush()) {
8810     cleanup_and_exit(1);
8811   }
8812 }
8813 
8814 #ifdef HAVE_STACKTRACE
dump_backtrace()8815 static void dump_backtrace() {
8816   struct st_connection *conn = cur_con;
8817 
8818   fprintf(stderr, "mysqltest: ");
8819 
8820   // Print the query and the line number
8821   if (start_lineno > 0) fprintf(stderr, "At line %u: ", start_lineno);
8822   fprintf(stderr, "%s\n", curr_command->query);
8823 
8824   // Print the file stack
8825   if (cur_file && cur_file != file_stack) {
8826     fprintf(stderr, "In included ");
8827     print_file_stack();
8828   }
8829 
8830   if (conn) fprintf(stderr, "conn->name: %s\n", conn->name);
8831 
8832   fprintf(stderr, "Attempting backtrace.\n");
8833   fflush(stderr);
8834   my_print_stacktrace(nullptr, my_thread_stack_size);
8835 }
8836 
8837 #else
dump_backtrace()8838 static void dump_backtrace() { fputs("Backtrace not available.\n", stderr); }
8839 
8840 #endif
8841 
signal_handler(int sig)8842 static void signal_handler(int sig) {
8843   fprintf(stderr, "mysqltest got " SIGNAL_FMT "\n", sig);
8844   dump_backtrace();
8845 
8846   fprintf(stderr, "Writing a core file.\n");
8847   fflush(stderr);
8848   my_write_core(sig);
8849 #ifndef _WIN32
8850   // Shouldn't get here but just in case
8851   exit(1);
8852 #endif
8853 }
8854 
8855 #ifdef _WIN32
8856 
exception_filter(EXCEPTION_POINTERS * exp)8857 LONG WINAPI exception_filter(EXCEPTION_POINTERS *exp) {
8858   __try {
8859     my_set_exception_pointers(exp);
8860     signal_handler(exp->ExceptionRecord->ExceptionCode);
8861   } __except (EXCEPTION_EXECUTE_HANDLER) {
8862     fputs("Got exception in exception handler!\n", stderr);
8863   }
8864 
8865   return EXCEPTION_CONTINUE_SEARCH;
8866 }
8867 
init_signal_handling(void)8868 static void init_signal_handling(void) {
8869   UINT mode;
8870 
8871   mysqltest_thread = OpenThread(THREAD_ALL_ACCESS, FALSE, GetCurrentThreadId());
8872   if (mysqltest_thread == NULL)
8873     die("OpenThread failed, err = %d.", GetLastError());
8874 
8875   /* Set output destination of messages to the standard error stream. */
8876   _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE);
8877   _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR);
8878   _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE);
8879   _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR);
8880   _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE);
8881   _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR);
8882 
8883   /* Do not not display the a error message box. */
8884   mode = SetErrorMode(0) | SEM_FAILCRITICALERRORS | SEM_NOOPENFILEERRORBOX;
8885   SetErrorMode(mode);
8886 
8887   SetUnhandledExceptionFilter(exception_filter);
8888 }
8889 
8890 /// Function to handle the stacktrace request event.
8891 ///
8892 /// - Suspend the thread running the test
8893 /// - Fetch CONTEXT record from the thread handle
8894 /// - Initialize EXCEPTION_RECORD structure
8895 /// - Use EXCEPTION_POINTERS and EXCEPTION_RECORD to set EXCEPTION_POINTERS
8896 ///   structure
8897 /// - Call exception_filter() method to generate to stack trace
8898 /// - Resume the suspended test thread
handle_wait_stacktrace_request_event()8899 static void handle_wait_stacktrace_request_event() {
8900   fprintf(stderr, "Test case timeout failure.\n");
8901 
8902   // Suspend the thread running the test
8903   if (SuspendThread(mysqltest_thread) == -1) {
8904     DWORD error = GetLastError();
8905     CloseHandle(mysqltest_thread);
8906     die("Error suspending thread, err = %d.\n", error);
8907   }
8908 
8909   // Fetch the thread context
8910   CONTEXT test_thread_ctx = {0};
8911   test_thread_ctx.ContextFlags = CONTEXT_FULL;
8912 
8913   if (GetThreadContext(mysqltest_thread, &test_thread_ctx) == FALSE) {
8914     DWORD error = GetLastError();
8915     CloseHandle(mysqltest_thread);
8916     die("Error while fetching thread conext information, err = %d.\n", error);
8917   }
8918 
8919   EXCEPTION_POINTERS exp = {0};
8920   exp.ContextRecord = &test_thread_ctx;
8921 
8922   // Set up an Exception record with EXCEPTION_BREAKPOINT code
8923   EXCEPTION_RECORD exc_rec = {0};
8924   exc_rec.ExceptionCode = EXCEPTION_BREAKPOINT;
8925   exp.ExceptionRecord = &exc_rec;
8926 
8927   exception_filter(&exp);
8928 
8929   // Resume the suspended test thread
8930   if (ResumeThread(mysqltest_thread) == -1) {
8931     DWORD error = GetLastError();
8932     CloseHandle(mysqltest_thread);
8933     die("Error resuming thread, err = %d.\n", error);
8934   }
8935 
8936   my_set_exception_pointers(nullptr);
8937 }
8938 
8939 /// Thread waiting for timeout event to occur. If the event occurs,
8940 /// this method will trigger signal_handler() function.
wait_stacktrace_request_event()8941 static void wait_stacktrace_request_event() {
8942   DWORD wait_res = WaitForSingleObject(stacktrace_request_event, INFINITE);
8943   switch (wait_res) {
8944     case WAIT_OBJECT_0:
8945       handle_wait_stacktrace_request_event();
8946       break;
8947     default:
8948       die("Unexpected result %d from WaitForSingleObject.", wait_res);
8949       break;
8950   }
8951   CloseHandle(stacktrace_request_event);
8952 }
8953 
8954 /// Create an event name from the safeprocess PID value of the form
8955 /// mysqltest[%d]stacktrace and spawn thread waiting for that event
8956 /// to occur.
8957 ///
8958 /// When this event occurs, signal_handler() method is called and
8959 /// stacktrace for the mysqltest client process is printed in the
8960 /// log file.
create_stacktrace_request_event()8961 static void create_stacktrace_request_event() {
8962   char event_name[64];
8963   std::sprintf(event_name, "mysqltest[%d]stacktrace", opt_safe_process_pid);
8964 
8965   // Create an event for the signal handler
8966   if ((stacktrace_request_event = CreateEvent(NULL, TRUE, FALSE, event_name)) ==
8967       NULL)
8968     die("Failed to create timeout_event.");
8969 
8970   wait_for_stacktrace_request_event_thread =
8971       std::thread(wait_stacktrace_request_event);
8972 }
8973 
8974 #else /* _WIN32 */
8975 
init_signal_handling(void)8976 static void init_signal_handling(void) {
8977   struct sigaction sa;
8978   DBUG_TRACE;
8979 
8980 #ifdef HAVE_STACKTRACE
8981   my_init_stacktrace();
8982 #endif
8983 
8984   sa.sa_flags = SA_RESETHAND | SA_NODEFER;
8985   sigemptyset(&sa.sa_mask);
8986   sigprocmask(SIG_SETMASK, &sa.sa_mask, nullptr);
8987 
8988   sa.sa_handler = signal_handler;
8989 
8990   sigaction(SIGSEGV, &sa, nullptr);
8991   sigaction(SIGABRT, &sa, nullptr);
8992 #ifdef SIGBUS
8993   sigaction(SIGBUS, &sa, nullptr);
8994 #endif
8995   sigaction(SIGILL, &sa, nullptr);
8996   sigaction(SIGFPE, &sa, nullptr);
8997 }
8998 
8999 #endif /* !_WIN32 */
9000 
main(int argc,char ** argv)9001 int main(int argc, char **argv) {
9002   struct st_command *command;
9003   bool abort_flag = false;
9004   int q_send_flag = 0;
9005   uint command_executed = 0, last_command_executed = 0;
9006   char output_file[FN_REFLEN];
9007   MY_INIT(argv[0]);
9008 
9009   output_file[0] = 0;
9010   TMPDIR[0] = 0;
9011 
9012   init_signal_handling();
9013 
9014   /* Init file stack */
9015   memset(file_stack, 0, sizeof(file_stack));
9016   file_stack_end =
9017       file_stack + (sizeof(file_stack) / sizeof(struct st_test_file)) - 1;
9018   cur_file = file_stack;
9019 
9020   /* Init block stack */
9021   memset(block_stack, 0, sizeof(block_stack));
9022   block_stack_end =
9023       block_stack + (sizeof(block_stack) / sizeof(struct st_block)) - 1;
9024   cur_block = block_stack;
9025   cur_block->ok = true; /* Outer block should always be executed */
9026   cur_block->cmd = cmd_none;
9027 
9028   q_lines = new Q_lines(PSI_NOT_INSTRUMENTED);
9029 
9030   var_hash =
9031       new collation_unordered_map<std::string, std::unique_ptr<VAR, var_free>>(
9032           charset_info, PSI_NOT_INSTRUMENTED);
9033 
9034   {
9035     char path_separator[] = {FN_LIBCHAR, 0};
9036     var_set_string("SYSTEM_PATH_SEPARATOR", path_separator);
9037   }
9038   var_set_string("MYSQL_SERVER_VERSION", MYSQL_SERVER_VERSION);
9039   var_set_string("MYSQL_SYSTEM_TYPE", SYSTEM_TYPE);
9040   var_set_string("MYSQL_MACHINE_TYPE", MACHINE_TYPE);
9041   if (sizeof(void *) == 8) {
9042     var_set_string("MYSQL_SYSTEM_ARCHITECTURE", "64");
9043   } else {
9044     var_set_string("MYSQL_SYSTEM_ARCHITECTURE", "32");
9045   }
9046 
9047   memset(&master_pos, 0, sizeof(master_pos));
9048 
9049   parser.current_line = parser.read_lines = 0;
9050   memset(&var_reg, 0, sizeof(var_reg));
9051 
9052   init_builtin_echo();
9053 #ifdef _WIN32
9054   is_windows = 1;
9055   init_win_path_patterns();
9056 #endif
9057 
9058   init_dynamic_string(&ds_res, "", 2048, 2048);
9059   init_dynamic_string(&ds_result, "", 1024, 1024);
9060 
9061   parse_args(argc, argv);
9062 
9063 #ifdef _WIN32
9064   // Create an event to request stack trace when timeout occurs
9065   if (opt_safe_process_pid) create_stacktrace_request_event();
9066 #endif
9067 
9068   /* Init connections, allocate 1 extra as buffer + 1 for default */
9069   connections = (struct st_connection *)my_malloc(
9070       PSI_NOT_INSTRUMENTED,
9071       (opt_max_connections + 2) * sizeof(struct st_connection),
9072       MYF(MY_WME | MY_ZEROFILL));
9073   connections_end = connections + opt_max_connections + 1;
9074   next_con = connections + 1;
9075 
9076   var_set_int("$PS_PROTOCOL", ps_protocol);
9077   var_set_int("$SP_PROTOCOL", sp_protocol);
9078   var_set_int("$VIEW_PROTOCOL", view_protocol);
9079   var_set_int("$OPT_TRACE_PROTOCOL", opt_trace_protocol);
9080   var_set_int("$EXPLAIN_PROTOCOL", explain_protocol);
9081   var_set_int("$JSON_EXPLAIN_PROTOCOL", json_explain_protocol);
9082   var_set_int("$CURSOR_PROTOCOL", cursor_protocol);
9083 
9084   var_set_int("$ENABLE_QUERY_LOG", 1);
9085   var_set_int("$ENABLE_ABORT_ON_ERROR", 1);
9086   var_set_int("$ENABLE_RESULT_LOG", 1);
9087   var_set_int("$ENABLE_CONNECT_LOG", 0);
9088   var_set_int("$ENABLE_WARNINGS", 1);
9089   var_set_int("$ENABLE_INFO", 0);
9090   var_set_int("$ENABLE_METADATA", 0);
9091   var_set_int("$ENABLE_ASYNC_CLIENT", 0);
9092 
9093   DBUG_PRINT("info",
9094              ("result_file: '%s'", result_file_name ? result_file_name : ""));
9095   verbose_msg("Results saved in '%s'.",
9096               result_file_name ? result_file_name : "");
9097   if (mysql_server_init(0, nullptr, nullptr))
9098     die("Can't initialize MySQL server");
9099   server_initialized = true;
9100   if (cur_file == file_stack && cur_file->file == nullptr) {
9101     cur_file->file = stdin;
9102     cur_file->file_name =
9103         my_strdup(PSI_NOT_INSTRUMENTED, "<stdin>", MYF(MY_WME));
9104     cur_file->lineno = 1;
9105   }
9106 
9107   if (log_file.open(opt_logdir, result_file_name, ".log")) cleanup_and_exit(1);
9108 
9109   verbose_msg("Logging to '%s'.", log_file.file_name());
9110   enable_async_client = use_async_client;
9111 
9112   // Creating a log file using current file name if result file doesn't exist.
9113   if (result_file_name) {
9114     if (log_file.open(opt_logdir, result_file_name, ".log"))
9115       cleanup_and_exit(1);
9116   } else {
9117     if (std::strcmp(cur_file->file_name, "<stdin>")) {
9118       if (log_file.open(opt_logdir, cur_file->file_name, ".log"))
9119         cleanup_and_exit(1);
9120     } else {
9121       if (log_file.open(opt_logdir, "stdin", ".log")) cleanup_and_exit(1);
9122     }
9123   }
9124 
9125   if (opt_mark_progress) {
9126     if (result_file_name) {
9127       if (progress_file.open(opt_logdir, result_file_name, ".progress"))
9128         cleanup_and_exit(1);
9129     } else {
9130       if (std::strcmp(cur_file->file_name, "<stdin>")) {
9131         if (progress_file.open(opt_logdir, cur_file->file_name, ".progress"))
9132           cleanup_and_exit(1);
9133       } else {
9134         if (progress_file.open(opt_logdir, "stdin", ".progress"))
9135           cleanup_and_exit(1);
9136       }
9137     }
9138     verbose_msg("Tracing progress in '%s'.", progress_file.file_name());
9139   }
9140 
9141   var_set_string("MYSQLTEST_FILE", cur_file->file_name);
9142 
9143   /* Cursor protcol implies ps protocol */
9144   if (cursor_protocol) ps_protocol = true;
9145 
9146   ps_protocol_enabled = ps_protocol;
9147   sp_protocol_enabled = sp_protocol;
9148   view_protocol_enabled = view_protocol;
9149   opt_trace_protocol_enabled = opt_trace_protocol;
9150   explain_protocol_enabled = explain_protocol;
9151   json_explain_protocol_enabled = json_explain_protocol;
9152   cursor_protocol_enabled = cursor_protocol;
9153 
9154   st_connection *con = connections;
9155   if (!(mysql_init(&con->mysql))) die("Failed in mysql_init()");
9156   if (opt_connect_timeout)
9157     mysql_options(&con->mysql, MYSQL_OPT_CONNECT_TIMEOUT,
9158                   (void *)&opt_connect_timeout);
9159   if (opt_compress) mysql_options(&con->mysql, MYSQL_OPT_COMPRESS, NullS);
9160   mysql_options(&con->mysql, MYSQL_OPT_LOCAL_INFILE, nullptr);
9161   if (std::strcmp(default_charset, charset_info->csname) &&
9162       !(charset_info =
9163             get_charset_by_csname(default_charset, MY_CS_PRIMARY, MYF(MY_WME))))
9164     die("Invalid character set specified.");
9165   mysql_options(&con->mysql, MYSQL_SET_CHARSET_NAME, charset_info->csname);
9166   if (opt_charsets_dir)
9167     mysql_options(&con->mysql, MYSQL_SET_CHARSET_DIR, opt_charsets_dir);
9168 
9169   if (opt_protocol)
9170     mysql_options(&con->mysql, MYSQL_OPT_PROTOCOL, (char *)&opt_protocol);
9171 
9172   /* Turn on VERIFY_IDENTITY mode only if host=="localhost". */
9173   if (opt_ssl_mode == SSL_MODE_VERIFY_IDENTITY) {
9174     if (!opt_host || std::strcmp(opt_host, "localhost"))
9175       opt_ssl_mode = SSL_MODE_VERIFY_CA;
9176   }
9177 
9178   if (SSL_SET_OPTIONS(&con->mysql)) die("%s", SSL_SET_OPTIONS_ERROR);
9179 #if defined(_WIN32)
9180   if (shared_memory_base_name)
9181     mysql_options(&con->mysql, MYSQL_SHARED_MEMORY_BASE_NAME,
9182                   shared_memory_base_name);
9183 
9184   if (opt_ssl_mode == SSL_MODE_DISABLED)
9185     mysql_options(&con->mysql, MYSQL_OPT_PROTOCOL,
9186                   (char *)&opt_protocol_for_default_connection);
9187 #endif
9188 
9189   if (!(con->name = my_strdup(PSI_NOT_INSTRUMENTED, "default", MYF(MY_WME))))
9190     die("Out of memory");
9191 
9192   safe_connect(&con->mysql, con->name, opt_host, opt_user, opt_pass, opt_db,
9193                opt_port, unix_sock);
9194 
9195   /* Use all time until exit if no explicit 'start_timer' */
9196   timer_start = timer_now();
9197 
9198   /*
9199     Initialize $mysql_errno with -1, so we can
9200     - distinguish it from valid values ( >= 0 ) and
9201     - detect if there was never a command sent to the server
9202   */
9203   var_set_errno(-1);
9204 
9205   set_current_connection(con);
9206 
9207   if (opt_include) {
9208     open_file(opt_include);
9209   }
9210 
9211   if (opt_offload_count_file) {
9212     secondary_engine = new Secondary_engine();
9213     // Save the initial value of secondary engine execution status.
9214     if (secondary_engine->offload_count(&cur_con->mysql, "before"))
9215       cleanup_and_exit(1);
9216   }
9217 
9218   verbose_msg("Start processing test commands from '%s' ...",
9219               cur_file->file_name);
9220   while (!read_command(&command) && !abort_flag) {
9221     int current_line_inc = 1, processed = 0;
9222     if (command->type == Q_UNKNOWN || command->type == Q_COMMENT_WITH_COMMAND)
9223       get_command_type(command);
9224 
9225     if (command->type == Q_ERROR && expected_errors->count())
9226       // Delete all the error codes from previous 'error' command.
9227       expected_errors->clear_list();
9228 
9229     if (testcase_disabled && command->type != Q_ENABLE_TESTCASE &&
9230         command->type != Q_DISABLE_TESTCASE) {
9231       // Test case is disabled, silently convert this line to a comment
9232       command->type = Q_COMMENT;
9233     }
9234 
9235     /* (Re-)set abort_on_error for this command */
9236     command->abort_on_error = (expected_errors->count() == 0 && abort_on_error);
9237 
9238     /* delimiter needs to be executed so we can continue to parse */
9239     bool ok_to_do = cur_block->ok || command->type == Q_DELIMITER;
9240 
9241     /*
9242       'source' command needs to be "done" the first time if it may get
9243       re-iterated over in a true context. This can only happen if there's
9244       a while loop at some level above the current block.
9245     */
9246     if (!ok_to_do && command->type == Q_SOURCE) {
9247       for (struct st_block *stb = cur_block - 1; stb >= block_stack; stb--) {
9248         if (stb->cmd == cmd_while) {
9249           ok_to_do = true;
9250           break;
9251         }
9252       }
9253     }
9254 
9255     /*
9256       Some commands need to be parsed in false context also to
9257       avoid any parsing errors.
9258     */
9259     if (!ok_to_do &&
9260         (command->type == Q_APPEND_FILE || command->type == Q_PERL ||
9261          command->type == Q_WRITE_FILE)) {
9262       ok_to_do = true;
9263     }
9264 
9265     if (ok_to_do) {
9266       command->last_argument = command->first_argument;
9267       processed = 1;
9268       /* Need to remember this for handle_error() */
9269       curr_command = command;
9270       switch (command->type) {
9271         case Q_CONNECT:
9272           do_connect(command);
9273           break;
9274         case Q_CONNECTION:
9275           select_connection(command);
9276           break;
9277         case Q_DISCONNECT:
9278         case Q_DIRTY_CLOSE:
9279           do_close_connection(command);
9280           break;
9281         case Q_ENABLE_QUERY_LOG:
9282           set_property(command, P_QUERY, false);
9283           break;
9284         case Q_DISABLE_QUERY_LOG:
9285           set_property(command, P_QUERY, true);
9286           break;
9287         case Q_ENABLE_ABORT_ON_ERROR:
9288           set_property(command, P_ABORT, true);
9289           break;
9290         case Q_DISABLE_ABORT_ON_ERROR:
9291           set_property(command, P_ABORT, false);
9292           break;
9293         case Q_ENABLE_RESULT_LOG:
9294           set_property(command, P_RESULT, false);
9295           break;
9296         case Q_DISABLE_RESULT_LOG:
9297           set_property(command, P_RESULT, true);
9298           break;
9299         case Q_ENABLE_CONNECT_LOG:
9300           set_property(command, P_CONNECT, false);
9301           break;
9302         case Q_DISABLE_CONNECT_LOG:
9303           set_property(command, P_CONNECT, true);
9304           break;
9305         case Q_ENABLE_WARNINGS:
9306           do_enable_warnings(command);
9307           break;
9308         case Q_DISABLE_WARNINGS:
9309           do_disable_warnings(command);
9310           break;
9311         case Q_ENABLE_INFO:
9312           set_property(command, P_INFO, false);
9313           break;
9314         case Q_DISABLE_INFO:
9315           set_property(command, P_INFO, true);
9316           break;
9317         case Q_ENABLE_SESSION_TRACK_INFO:
9318           set_property(command, P_SESSION_TRACK, true);
9319           break;
9320         case Q_DISABLE_SESSION_TRACK_INFO:
9321           set_property(command, P_SESSION_TRACK, false);
9322           break;
9323         case Q_ENABLE_METADATA:
9324           set_property(command, P_META, true);
9325           break;
9326         case Q_DISABLE_METADATA:
9327           set_property(command, P_META, false);
9328           break;
9329         case Q_SOURCE:
9330           do_source(command);
9331           break;
9332         case Q_SLEEP:
9333           do_sleep(command);
9334           break;
9335         case Q_WAIT_FOR_SLAVE_TO_STOP:
9336           do_wait_for_slave_to_stop(command);
9337           break;
9338         case Q_INC:
9339           do_modify_var(command, DO_INC);
9340           break;
9341         case Q_DEC:
9342           do_modify_var(command, DO_DEC);
9343           break;
9344         case Q_ECHO:
9345           do_echo(command);
9346           command_executed++;
9347           break;
9348         case Q_REMOVE_FILE:
9349           do_remove_file(command);
9350           break;
9351         case Q_REMOVE_FILES_WILDCARD:
9352           do_remove_files_wildcard(command);
9353           break;
9354         case Q_COPY_FILES_WILDCARD:
9355           do_copy_files_wildcard(command);
9356           break;
9357         case Q_MKDIR:
9358           do_mkdir(command);
9359           break;
9360         case Q_RMDIR:
9361           do_rmdir(command, false);
9362           break;
9363         case Q_FORCE_RMDIR:
9364           do_rmdir(command, true);
9365           break;
9366         case Q_FORCE_CPDIR:
9367           do_force_cpdir(command);
9368           break;
9369         case Q_LIST_FILES:
9370           do_list_files(command);
9371           break;
9372         case Q_LIST_FILES_WRITE_FILE:
9373           do_list_files_write_file_command(command, false);
9374           break;
9375         case Q_LIST_FILES_APPEND_FILE:
9376           do_list_files_write_file_command(command, true);
9377           break;
9378         case Q_FILE_EXIST:
9379           do_file_exist(command);
9380           break;
9381         case Q_WRITE_FILE:
9382           do_write_file(command);
9383           break;
9384         case Q_APPEND_FILE:
9385           do_append_file(command);
9386           break;
9387         case Q_DIFF_FILES:
9388           do_diff_files(command);
9389           break;
9390         case Q_SEND_QUIT:
9391           do_send_quit(command);
9392           break;
9393         case Q_CHANGE_USER:
9394           do_change_user(command);
9395           break;
9396         case Q_CAT_FILE:
9397           do_cat_file(command);
9398           break;
9399         case Q_COPY_FILE:
9400           do_copy_file(command);
9401           break;
9402         case Q_MOVE_FILE:
9403           do_move_file(command);
9404           break;
9405         case Q_CHMOD_FILE:
9406           do_chmod_file(command);
9407           break;
9408         case Q_PERL:
9409           do_perl(command);
9410           break;
9411         case Q_RESULT_FORMAT_VERSION:
9412           do_result_format_version(command);
9413           break;
9414         case Q_DELIMITER:
9415           do_delimiter(command);
9416           break;
9417         case Q_DISPLAY_VERTICAL_RESULTS:
9418           display_result_vertically = true;
9419           break;
9420         case Q_DISPLAY_HORIZONTAL_RESULTS:
9421           display_result_vertically = false;
9422           break;
9423         case Q_SORTED_RESULT:
9424           /*
9425             Turn on sorting of result set, will be reset after next
9426             command
9427           */
9428           display_result_sorted = true;
9429           start_sort_column = 0;
9430           break;
9431         case Q_PARTIALLY_SORTED_RESULT:
9432           /*
9433             Turn on sorting of result set, will be reset after next
9434             command
9435           */
9436           display_result_sorted = true;
9437           start_sort_column = atoi(command->first_argument);
9438           command->last_argument = command->end;
9439           break;
9440         case Q_LOWERCASE:
9441           /*
9442             Turn on lowercasing of result, will be reset after next
9443             command
9444           */
9445           display_result_lower = true;
9446           break;
9447         case Q_LET:
9448           do_let(command);
9449           break;
9450         case Q_EXPR:
9451           do_expr(command);
9452           break;
9453         case Q_EVAL:
9454         case Q_QUERY_VERTICAL:
9455         case Q_QUERY_HORIZONTAL:
9456           if (command->query == command->query_buf) {
9457             /* Skip the first part of command, i.e query_xxx */
9458             command->query = command->first_argument;
9459             command->first_word_len = 0;
9460           }
9461           /* fall through */
9462         case Q_QUERY:
9463         case Q_REAP: {
9464           bool old_display_result_vertically = display_result_vertically;
9465           /* Default is full query, both reap and send  */
9466           int flags = QUERY_REAP_FLAG | QUERY_SEND_FLAG;
9467 
9468           if (q_send_flag) {
9469             // Last command was an empty 'send' or 'send_eval'
9470             flags = QUERY_SEND_FLAG;
9471             if (q_send_flag == 2)
9472               // Last command was an empty 'send_eval' command. Set the command
9473               // type to Q_SEND_EVAL so that the variable gets replaced with its
9474               // value before executing.
9475               command->type = Q_SEND_EVAL;
9476             q_send_flag = 0;
9477           } else if (command->type == Q_REAP) {
9478             flags = QUERY_REAP_FLAG;
9479           }
9480 
9481           /* Check for special property for this query */
9482           display_result_vertically |= (command->type == Q_QUERY_VERTICAL);
9483 
9484           /*
9485             We run EXPLAIN _before_ the query. If query is UPDATE/DELETE is
9486             matters: a DELETE may delete rows, and then EXPLAIN DELETE will
9487             usually terminate quickly with "no matching rows". To make it more
9488             interesting, EXPLAIN is now first.
9489           */
9490           if (explain_protocol_enabled)
9491             run_explain(cur_con, command, flags, false);
9492           if (json_explain_protocol_enabled)
9493             run_explain(cur_con, command, flags, true);
9494 
9495           if (*output_file) {
9496             strmake(command->output_file, output_file, sizeof(output_file) - 1);
9497             *output_file = 0;
9498           }
9499           run_query(cur_con, command, flags);
9500           display_opt_trace(cur_con, command, flags);
9501           command_executed++;
9502           command->last_argument = command->end;
9503 
9504           /* Restore settings */
9505           display_result_vertically = old_display_result_vertically;
9506 
9507           break;
9508         }
9509         case Q_SEND:
9510         case Q_SEND_EVAL:
9511           if (!*command->first_argument) {
9512             // This is a 'send' or 'send_eval' command without arguments, it
9513             // indicates that _next_ query should be send only.
9514             if (command->type == Q_SEND)
9515               q_send_flag = 1;
9516             else if (command->type == Q_SEND_EVAL)
9517               q_send_flag = 2;
9518             break;
9519           }
9520 
9521           /* Remove "send" if this is first iteration */
9522           if (command->query == command->query_buf)
9523             command->query = command->first_argument;
9524 
9525           /*
9526             run_query() can execute a query partially, depending on the flags.
9527             QUERY_SEND_FLAG flag without QUERY_REAP_FLAG tells it to just send
9528             the query and read the result some time later when reap instruction
9529             is given on this connection.
9530           */
9531           run_query(cur_con, command, QUERY_SEND_FLAG);
9532           command_executed++;
9533           command->last_argument = command->end;
9534           break;
9535         case Q_ERROR:
9536           do_error(command);
9537           break;
9538         case Q_REPLACE:
9539           do_get_replace(command);
9540           break;
9541         case Q_REPLACE_REGEX:
9542           do_get_replace_regex(command);
9543           break;
9544         case Q_REPLACE_COLUMN:
9545           do_get_replace_column(command);
9546           break;
9547         case Q_REPLACE_NUMERIC_ROUND:
9548           do_get_replace_numeric_round(command);
9549           break;
9550         case Q_SAVE_MASTER_POS:
9551           do_save_master_pos();
9552           break;
9553         case Q_SYNC_WITH_MASTER:
9554           do_sync_with_master(command);
9555           break;
9556         case Q_SYNC_SLAVE_WITH_MASTER: {
9557           do_save_master_pos();
9558           if (*command->first_argument)
9559             select_connection(command);
9560           else
9561             select_connection_name("slave");
9562           do_sync_with_master2(command, 0);
9563           break;
9564         }
9565         case Q_COMMENT: {
9566           command->last_argument = command->end;
9567 
9568           /* Don't output comments in v1 */
9569           if (opt_result_format_version == 1) break;
9570 
9571           /* Don't output comments if query logging is off */
9572           if (disable_query_log) break;
9573 
9574           /* Write comment's with two starting #'s to result file */
9575           const char *p = command->query;
9576           if (p && *p == '#' && *(p + 1) == '#') {
9577             dynstr_append_mem(&ds_res, command->query, command->query_len);
9578             dynstr_append(&ds_res, "\n");
9579           }
9580           break;
9581         }
9582         case Q_EMPTY_LINE:
9583           /* Don't output newline in v1 */
9584           if (opt_result_format_version == 1) break;
9585 
9586           /* Don't output newline if query logging is off */
9587           if (disable_query_log) break;
9588 
9589           dynstr_append(&ds_res, "\n");
9590           break;
9591         case Q_PING:
9592           handle_command_error(command, mysql_ping(&cur_con->mysql));
9593           break;
9594         case Q_RESET_CONNECTION:
9595           do_reset_connection();
9596           break;
9597         case Q_SEND_SHUTDOWN:
9598           if (opt_offload_count_file) {
9599             // Save the value of secondary engine execution status
9600             // before shutting down the server.
9601             if (secondary_engine->offload_count(&cur_con->mysql, "after"))
9602               cleanup_and_exit(1);
9603           }
9604 
9605           handle_command_error(
9606               command, mysql_query_wrapper(&cur_con->mysql, "shutdown"));
9607           break;
9608         case Q_SHUTDOWN_SERVER:
9609           do_shutdown_server(command);
9610           break;
9611         case Q_EXEC:
9612         case Q_EXECW:
9613           do_exec(command, false);
9614           command_executed++;
9615           break;
9616         case Q_EXEC_BACKGROUND:
9617           do_exec(command, true);
9618           command_executed++;
9619           break;
9620         case Q_START_TIMER:
9621           /* Overwrite possible earlier start of timer */
9622           timer_start = timer_now();
9623           break;
9624         case Q_END_TIMER:
9625           /* End timer before ending mysqltest */
9626           timer_output();
9627           break;
9628         case Q_CHARACTER_SET:
9629           do_set_charset(command);
9630           break;
9631         case Q_DISABLE_PS_PROTOCOL:
9632           set_property(command, P_PS, false);
9633           /* Close any open statements */
9634           close_statements();
9635           break;
9636         case Q_ENABLE_PS_PROTOCOL:
9637           set_property(command, P_PS, ps_protocol);
9638           break;
9639         case Q_DISABLE_RECONNECT:
9640           set_reconnect(&cur_con->mysql, 0);
9641           break;
9642         case Q_ENABLE_RECONNECT:
9643           set_reconnect(&cur_con->mysql, 1);
9644           enable_async_client = false;
9645           /* Close any open statements - no reconnect, need new prepare */
9646           close_statements();
9647           break;
9648         case Q_ENABLE_ASYNC_CLIENT:
9649           set_property(command, P_ASYNC, true);
9650           break;
9651         case Q_DISABLE_ASYNC_CLIENT:
9652           set_property(command, P_ASYNC, false);
9653           break;
9654         case Q_DISABLE_TESTCASE:
9655           if (testcase_disabled == 0)
9656             do_disable_testcase(command);
9657           else
9658             die("Test case is already disabled.");
9659           break;
9660         case Q_ENABLE_TESTCASE:
9661           // Ensure we don't get testcase_disabled < 0 as this would
9662           // accidentally disable code we don't want to have disabled.
9663           if (testcase_disabled == 1)
9664             testcase_disabled = false;
9665           else
9666             die("Test case is already enabled.");
9667           break;
9668         case Q_DIE:
9669           /* Abort test with error code and error message */
9670           die("%s", command->first_argument);
9671           break;
9672         case Q_EXIT:
9673           /* Stop processing any more commands */
9674           abort_flag = true;
9675           break;
9676         case Q_SKIP: {
9677           DYNAMIC_STRING ds_skip_msg;
9678           init_dynamic_string(&ds_skip_msg, nullptr, command->query_len, 256);
9679 
9680           // Evaluate the skip message
9681           do_eval(&ds_skip_msg, command->first_argument, command->end, false);
9682 
9683           char skip_msg[FN_REFLEN];
9684           strmake(skip_msg, ds_skip_msg.str, FN_REFLEN - 1);
9685           dynstr_free(&ds_skip_msg);
9686 
9687           if (!no_skip) {
9688             // --no-skip option is disabled, skip the test case
9689             abort_not_supported_test("%s", skip_msg);
9690           } else {
9691             const char *path = cur_file->file_name;
9692             const char *fn = get_filename_from_path(path);
9693 
9694             // Check if the file is in excluded list
9695             if (excluded_string && strstr(excluded_string, fn)) {
9696               // File is present in excluded list, skip the test case
9697               abort_not_supported_test("%s", skip_msg);
9698             } else {
9699               // File is not present in excluded list, ignore the skip
9700               // and continue running the test case
9701               command->last_argument = command->end;
9702             }
9703           }
9704         } break;
9705         case Q_OUTPUT: {
9706           static DYNAMIC_STRING ds_to_file;
9707           const struct command_arg output_file_args[] = {
9708               {"to_file", ARG_STRING, true, &ds_to_file, "Output filename"}};
9709           check_command_args(command, command->first_argument, output_file_args,
9710                              1, ' ');
9711           strmake(output_file, ds_to_file.str, FN_REFLEN);
9712           dynstr_free(&ds_to_file);
9713           break;
9714         }
9715 
9716         default:
9717           processed = 0;
9718           break;
9719       }
9720     }
9721 
9722     if (!processed) {
9723       current_line_inc = 0;
9724       switch (command->type) {
9725         case Q_WHILE:
9726           do_block(cmd_while, command);
9727           break;
9728         case Q_IF:
9729           do_block(cmd_if, command);
9730           break;
9731         case Q_END_BLOCK:
9732           do_done(command);
9733           break;
9734         default:
9735           current_line_inc = 1;
9736           break;
9737       }
9738     } else
9739       check_eol_junk(command->last_argument);
9740 
9741     if (command->type != Q_ERROR && command->type != Q_COMMENT &&
9742         command->type != Q_IF && command->type != Q_END_BLOCK) {
9743       // As soon as any non "error" command or comment has been executed,
9744       // the array with expected errors should be cleared
9745       expected_errors->clear_list();
9746     }
9747 
9748     if (command_executed != last_command_executed || command->used_replace) {
9749       /*
9750         As soon as any command has been executed,
9751         the replace structures should be cleared
9752       */
9753       free_all_replace();
9754 
9755       /* Also reset "sorted_result" and "lowercase"*/
9756       display_result_sorted = false;
9757       display_result_lower = false;
9758     }
9759     last_command_executed = command_executed;
9760 
9761     parser.current_line += current_line_inc;
9762     if (opt_mark_progress) mark_progress(&progress_file, parser.current_line);
9763 
9764     // Write result from command to log file immediately.
9765     if (log_file.write(ds_res.str, ds_res.length) || log_file.flush())
9766       cleanup_and_exit(1);
9767 
9768     if (!result_file_name) {
9769       std::fwrite(ds_res.str, 1, ds_res.length, stdout);
9770       std::fflush(stdout);
9771     }
9772 
9773     dynstr_set(&ds_res, nullptr);
9774   }
9775 
9776   start_lineno = 0;
9777   verbose_msg("... Done processing test commands.");
9778 
9779   if (testcase_disabled) die("Test ended with test case execution disabled.");
9780 
9781   if (disable_warnings || disabled_warnings->count())
9782     die("The test didn't enable all the disabled warnings, enable "
9783         "all of them before end of the test.");
9784 
9785   bool empty_result = false;
9786 
9787   /*
9788     The whole test has been executed _sucessfully_.
9789     Time to compare result or save it to record file.
9790     The entire output from test is in the log file
9791   */
9792   if (log_file.bytes_written()) {
9793     if (result_file_name) {
9794       /* A result file has been specified */
9795 
9796       if (record) {
9797         /* Recording */
9798 
9799         /* save a copy of the log to result file */
9800         if (my_copy(log_file.file_name(), result_file_name, MYF(0)) != 0)
9801           die("Failed to copy '%s' to '%s', errno: %d", log_file.file_name(),
9802               result_file_name, errno);
9803 
9804       } else {
9805         /* Check that the output from test is equal to result file */
9806         check_result();
9807       }
9808     }
9809   } else {
9810     /* Empty output is an error *unless* we also have an empty result file */
9811     if (!result_file_name || record ||
9812         compare_files(log_file.file_name(), result_file_name)) {
9813       die("The test didn't produce any output");
9814     } else {
9815       empty_result = true; /* Meaning empty was expected */
9816     }
9817   }
9818 
9819   if (!command_executed && result_file_name && !empty_result)
9820     die("No queries executed but non-empty result file found!");
9821 
9822   verbose_msg("Test has succeeded!");
9823   timer_output();
9824   /* Yes, if we got this far the test has suceeded! Sakila smiles */
9825   cleanup_and_exit(0);
9826   return 0; /* Keep compiler happy too */
9827 }
9828 
9829 /*
9830   A primitive timer that give results in milliseconds if the
9831   --timer-file=<filename> is given. The timer result is written
9832   to that file when the result is available. To not confuse
9833   mysql-test-run with an old obsolete result, we remove the file
9834   before executing any commands. The time we measure is
9835 
9836   - If no explicit 'start_timer' or 'end_timer' is given in the
9837   test case, the timer measure how long we execute in mysqltest.
9838 
9839   - If only 'start_timer' is given we measure how long we execute
9840   from that point until we terminate mysqltest.
9841 
9842   - If only 'end_timer' is given we measure how long we execute
9843   from that we enter mysqltest to the 'end_timer' is command is
9844   executed.
9845 
9846   - If both 'start_timer' and 'end_timer' are given we measure
9847   the time between executing the two commands.
9848 */
9849 
timer_output(void)9850 void timer_output(void) {
9851   if (timer_file) {
9852     char buf[32], *end;
9853     ulonglong timer = timer_now() - timer_start;
9854     end = longlong10_to_str(timer, buf, 10);
9855     str_to_file(timer_file, buf, (int)(end - buf));
9856     /* Timer has been written to the file, don't use it anymore */
9857     timer_file = nullptr;
9858   }
9859 }
9860 
timer_now(void)9861 ulonglong timer_now(void) { return my_micro_time() / 1000; }
9862 
9863 /*
9864   Get arguments for replace_columns. The syntax is:
9865   replace-column column_number to_string [column_number to_string ...]
9866   Where each argument may be quoted with ' or "
9867   A argument may also be a variable, in which case the value of the
9868   variable is replaced.
9869 */
9870 
do_get_replace_column(struct st_command * command)9871 void do_get_replace_column(struct st_command *command) {
9872   const char *from = command->first_argument;
9873   char *buff, *start;
9874   DBUG_TRACE;
9875 
9876   free_replace_column();
9877   if (!*from) die("Missing argument in %s", command->query);
9878 
9879   /* Allocate a buffer for results */
9880   start = buff = (char *)my_malloc(PSI_NOT_INSTRUMENTED, std::strlen(from) + 1,
9881                                    MYF(MY_WME | MY_FAE));
9882   while (*from) {
9883     char *to;
9884     uint column_number;
9885     to = get_string(&buff, &from, command);
9886     if (!(column_number = atoi(to)) || column_number > MAX_COLUMNS)
9887       die("Wrong column number to replace_column in '%s'", command->query);
9888     if (!*from)
9889       die("Wrong number of arguments to replace_column in '%s'",
9890           command->query);
9891     to = get_string(&buff, &from, command);
9892     my_free(replace_column[column_number - 1]);
9893     replace_column[column_number - 1] =
9894         my_strdup(PSI_NOT_INSTRUMENTED, to, MYF(MY_WME | MY_FAE));
9895     max_replace_column = std::max(max_replace_column, column_number);
9896   }
9897   my_free(start);
9898   command->last_argument = command->end;
9899 }
9900 
free_replace_column()9901 void free_replace_column() {
9902   uint i;
9903   for (i = 0; i < max_replace_column; i++) {
9904     if (replace_column[i]) {
9905       my_free(replace_column[i]);
9906       replace_column[i] = nullptr;
9907     }
9908   }
9909   max_replace_column = 0;
9910 }
9911 
9912 /*
9913   Functions to round numeric results.
9914 
9915 SYNOPSIS
9916   do_get_replace_numeric_round()
9917   command - command handle
9918 
9919 DESCRIPTION
9920   replace_numeric_round <precision>
9921 
9922   where precision is the number of digits after the decimal point
9923   that the result will be rounded off to. The precision can only
9924   be a number between 0 and 16.
9925   eg. replace_numeric_round 10;
9926   Numbers which are > 1e10 or < -1e10 are represented using the
9927   exponential notation after they are rounded off.
9928   Trailing zeroes after the decimal point are removed from the
9929   numbers.
9930   If the precision is 0, then the value is rounded off to the
9931   nearest whole number.
9932 */
do_get_replace_numeric_round(struct st_command * command)9933 void do_get_replace_numeric_round(struct st_command *command) {
9934   DYNAMIC_STRING ds_round;
9935   const struct command_arg numeric_arg = {
9936       "precision", ARG_STRING, true, &ds_round, "Number of decimal precision"};
9937   DBUG_TRACE;
9938 
9939   check_command_args(command, command->first_argument, &numeric_arg,
9940                      sizeof(numeric_arg) / sizeof(struct command_arg), ' ');
9941 
9942   // Parse the argument string to get the precision
9943   long int v = 0;
9944   if (str2int(ds_round.str, 10, 0, REPLACE_ROUND_MAX, &v) == NullS)
9945     die("A number between 0 and %d is required for the precision "
9946         "in replace_numeric_round",
9947         REPLACE_ROUND_MAX);
9948 
9949   glob_replace_numeric_round = (int)v;
9950   dynstr_free(&ds_round);
9951 }
9952 
free_replace_numeric_round()9953 void free_replace_numeric_round() { glob_replace_numeric_round = -1; }
9954 
9955 /*
9956   Round the digits after the decimal point to the specified precision
9957   by iterating through the result set element, identifying the part to
9958   be rounded off, and rounding that part off.
9959 */
replace_numeric_round_append(int round,DYNAMIC_STRING * result,const char * from,size_t len)9960 void replace_numeric_round_append(int round, DYNAMIC_STRING *result,
9961                                   const char *from, size_t len) {
9962   while (len > 0) {
9963     // Move pointer to the start of the numeric values
9964     size_t size = strcspn(from, "0123456789");
9965     if (size > 0) {
9966       dynstr_append_mem(result, from, size);
9967       from += size;
9968       len -= size;
9969     }
9970 
9971     /*
9972       Move the pointer to the end of the numeric values and the
9973       the start of the non-numeric values such as "." and "e"
9974     */
9975     size = strspn(from, "0123456789");
9976     int r = round;
9977 
9978     /*
9979       If result from one of the rows of the result set is null,
9980       break the loop
9981     */
9982     if (*(from + size) == 0) {
9983       dynstr_append_mem(result, from, size);
9984       break;
9985     }
9986 
9987     switch (*(from + size)) {
9988       // double/float
9989       case '.':
9990         size_t size1;
9991         size1 = strspn(from + size + 1, "0123456789");
9992 
9993         /*
9994           Restrict rounding to less than the
9995           the existing precision to avoid 1.2 being replaced
9996           to 1.2000000
9997         */
9998         if (size1 < (size_t)r) r = size1;
9999       // fallthrough: all cases till next break are executed
10000       case 'e':
10001       case 'E':
10002         if (isdigit(*(from + size + 1))) {
10003           char *end;
10004           double val = strtod(from, &end);
10005           if (end != nullptr) {
10006             const char *format = (val < 1e10 && val > -1e10) ? "%.*f" : "%.*e";
10007             char buf[40];
10008 
10009             size = snprintf(buf, sizeof(buf), format, r, val);
10010             if (val < 1e10 && val > -1e10 && r > 0) {
10011               /*
10012                 2.0000000 need to be represented as 2 for consistency
10013                 2.0010000 also becomes 2.001
10014               */
10015               while (buf[size - 1] == '0') size--;
10016 
10017               // don't leave 100. trailing
10018               if (buf[size - 1] == '.') size--;
10019             }
10020             dynstr_append_mem(result, buf, size);
10021             len -= (end - from);
10022             from = end;
10023             break;
10024           }
10025         }
10026 
10027         /*
10028           This is because strtod didn't convert or there wasn't digits after
10029           [.eE] so output without changing
10030         */
10031         dynstr_append_mem(result, from, size);
10032         from += size;
10033         len -= size;
10034         break;
10035       // int
10036       default:
10037         dynstr_append_mem(result, from, size);
10038         from += size;
10039         len -= size;
10040         break;
10041     }
10042   }
10043 }
10044 
10045 /****************************************************************************/
10046 /*
10047   Replace functions
10048 */
10049 
10050 /* Definitions for replace result */
10051 
10052 struct POINTER_ARRAY {  /* when using array-strings */
10053   TYPELIB typelib;      /* Pointer to strings */
10054   uchar *str{nullptr};  /* Strings is here */
10055   uint8 *flag{nullptr}; /* Flag about each var. */
10056   uint array_allocs{0}, max_count{0}, length{0}, max_length{0};
10057 };
10058 
10059 REPLACE *init_replace(const char **from, const char **to, uint count,
10060                       const char *word_end_chars);
10061 int insert_pointer_name(POINTER_ARRAY *pa, char *name);
10062 void free_pointer_array(POINTER_ARRAY *pa);
10063 
10064 /*
10065   Get arguments for replace. The syntax is:
10066   replace from to [from to ...]
10067   Where each argument may be quoted with ' or "
10068   A argument may also be a variable, in which case the value of the
10069   variable is replaced.
10070 */
10071 
do_get_replace(struct st_command * command)10072 void do_get_replace(struct st_command *command) {
10073   uint i;
10074   const char *from = command->first_argument;
10075   char *buff, *start;
10076   char word_end_chars[256], *pos;
10077   POINTER_ARRAY to_array, from_array;
10078   DBUG_TRACE;
10079 
10080   free_replace();
10081 
10082   if (!*from) die("Missing argument in %s", command->query);
10083   start = buff = (char *)my_malloc(PSI_NOT_INSTRUMENTED, std::strlen(from) + 1,
10084                                    MYF(MY_WME | MY_FAE));
10085   while (*from) {
10086     char *to = buff;
10087     to = get_string(&buff, &from, command);
10088     if (!*from)
10089       die("Wrong number of arguments to replace_result in '%s'",
10090           command->query);
10091 #ifdef _WIN32
10092     fix_win_paths(to, from - to);
10093 #endif
10094     insert_pointer_name(&from_array, to);
10095     to = get_string(&buff, &from, command);
10096     insert_pointer_name(&to_array, to);
10097   }
10098   for (i = 1, pos = word_end_chars; i < 256; i++)
10099     if (my_isspace(charset_info, i)) *pos++ = i;
10100   *pos = 0; /* End pointer */
10101   if (!(glob_replace = init_replace(
10102             from_array.typelib.type_names, to_array.typelib.type_names,
10103             (uint)from_array.typelib.count, word_end_chars)))
10104     die("Can't initialize replace from '%s'", command->query);
10105   free_pointer_array(&from_array);
10106   free_pointer_array(&to_array);
10107   my_free(start);
10108   command->last_argument = command->end;
10109 }
10110 
free_replace()10111 void free_replace() {
10112   DBUG_TRACE;
10113   my_free(glob_replace);
10114   glob_replace = nullptr;
10115 }
10116 
10117 struct REPLACE {
10118   int found;
10119   REPLACE *next[256];
10120 };
10121 
10122 struct REPLACE_STRING {
10123   int found;
10124   const char *replace_string;
10125   uint to_offset;
10126   int from_offset;
10127 };
10128 
replace_strings_append(REPLACE * rep,DYNAMIC_STRING * ds,const char * str,size_t len MY_ATTRIBUTE ((unused)))10129 void replace_strings_append(REPLACE *rep, DYNAMIC_STRING *ds, const char *str,
10130                             size_t len MY_ATTRIBUTE((unused))) {
10131   REPLACE *rep_pos;
10132   REPLACE_STRING *rep_str;
10133   const char *start, *from;
10134   DBUG_TRACE;
10135 
10136   start = from = str;
10137   rep_pos = rep + 1;
10138   for (;;) {
10139     /* Loop through states */
10140     DBUG_PRINT("info", ("Looping through states"));
10141     while (!rep_pos->found) rep_pos = rep_pos->next[(uchar)*from++];
10142 
10143     /* Does this state contain a string to be replaced */
10144     if (!(rep_str = ((REPLACE_STRING *)rep_pos))->replace_string) {
10145       /* No match found */
10146       dynstr_append_mem(ds, start, from - start - 1);
10147       DBUG_PRINT("exit",
10148                  ("Found no more string to replace, appended: %s", start));
10149       return;
10150     }
10151 
10152     /* Found a string that needs to be replaced */
10153     DBUG_PRINT("info", ("found: %d, to_offset: %d, from_offset: %d, string: %s",
10154                         rep_str->found, rep_str->to_offset,
10155                         rep_str->from_offset, rep_str->replace_string));
10156 
10157     /* Append part of original string before replace string */
10158     dynstr_append_mem(ds, start, (from - rep_str->to_offset) - start);
10159 
10160     /* Append replace string */
10161     dynstr_append_mem(ds, rep_str->replace_string,
10162                       std::strlen(rep_str->replace_string));
10163 
10164     if (!*(from -= rep_str->from_offset) && rep_pos->found != 2) {
10165       /* End of from string */
10166       DBUG_PRINT("exit", ("Found end of from string"));
10167       return;
10168     }
10169     DBUG_ASSERT(from <= str + len);
10170     start = from;
10171     rep_pos = rep;
10172   }
10173 }
10174 
10175 /*
10176   Regex replace  functions
10177 */
10178 
10179 /*
10180   Finds the next (non-escaped) '/' in the expression.
10181   (If the character '/' is needed, it can be escaped using '\'.)
10182 */
10183 
10184 #define PARSE_REGEX_ARG     \
10185   while (p < expr_end) {    \
10186     char c = *p;            \
10187     if (c == '/') {         \
10188       if (last_c == '\\') { \
10189         buf_p[-1] = '/';    \
10190       } else {              \
10191         *buf_p++ = 0;       \
10192         break;              \
10193       }                     \
10194     } else                  \
10195       *buf_p++ = c;         \
10196                             \
10197     last_c = c;             \
10198     p++;                    \
10199   }
10200 
10201 /**
10202   Initializes the regular substitution expression to be used in the
10203   result output of test.
10204 
10205   @param  expr  Pointer to string having regular expression to be used
10206                 for substitution.
10207   @retval st_replace_regex structure with pairs of substitutions.
10208 */
init_replace_regex(const char * expr)10209 static struct st_replace_regex *init_replace_regex(const char *expr) {
10210   struct st_replace_regex *res;
10211   char *buf;
10212   const char *expr_end;
10213   const char *p;
10214   char *buf_p;
10215   size_t expr_len = std::strlen(expr);
10216   char last_c = 0;
10217   struct st_regex reg;
10218 
10219   /* my_malloc() will die on fail with MY_FAE */
10220   void *rawmem = my_malloc(PSI_NOT_INSTRUMENTED, sizeof(*res) + expr_len,
10221                            MYF(MY_FAE + MY_WME));
10222   res = new (rawmem) st_replace_regex;
10223 
10224   buf = (char *)res + sizeof(*res);
10225   expr_end = expr + expr_len;
10226   p = expr;
10227   buf_p = buf;
10228 
10229   /* for each regexp substitution statement */
10230   while (p < expr_end) {
10231     memset(&reg, 0, sizeof(reg));
10232     /* find the start of the statement */
10233     while (p < expr_end) {
10234       if (*p == '/') break;
10235       p++;
10236     }
10237 
10238     if (p == expr_end || ++p == expr_end) {
10239       if (res->regex_arr.size())
10240         break;
10241       else
10242         goto err;
10243     }
10244     /* we found the start */
10245     reg.pattern = buf_p;
10246 
10247     /* Find first argument -- pattern string to be removed */
10248     PARSE_REGEX_ARG
10249 
10250     if (p == expr_end || ++p == expr_end) goto err;
10251 
10252     /* buf_p now points to the replacement pattern terminated with \0 */
10253     reg.replace = buf_p;
10254 
10255     /* Find second argument -- replace string to replace pattern */
10256     PARSE_REGEX_ARG
10257 
10258     if (p == expr_end) goto err;
10259 
10260     /* skip the ending '/' in the statement */
10261     p++;
10262 
10263     /* Check if we should do matching case insensitive */
10264     if (p < expr_end && *p == 'i') reg.icase = 1;
10265 
10266     /* done parsing the statement, now place it in regex_arr */
10267     if (res->regex_arr.push_back(reg)) die("Out of memory");
10268   }
10269   res->odd_buf_len = res->even_buf_len = 8192;
10270   res->even_buf = (char *)my_malloc(PSI_NOT_INSTRUMENTED, res->even_buf_len,
10271                                     MYF(MY_WME + MY_FAE));
10272   res->odd_buf = (char *)my_malloc(PSI_NOT_INSTRUMENTED, res->odd_buf_len,
10273                                    MYF(MY_WME + MY_FAE));
10274   res->buf = res->even_buf;
10275 
10276   return res;
10277 
10278 err:
10279   my_free(res);
10280   die("Error parsing replace_regex \"%s\"", expr);
10281   return nullptr;
10282 }
10283 
10284 /*
10285   Parse the regular expression to be used in all result files
10286   from now on.
10287 
10288   The syntax is --replace_regex /from/to/i /from/to/i ...
10289   i means case-insensitive match. If omitted, the match is
10290   case-sensitive
10291 
10292 */
do_get_replace_regex(struct st_command * command)10293 void do_get_replace_regex(struct st_command *command) {
10294   const char *expr = command->first_argument;
10295   free_replace_regex();
10296   /* Allow variable for the *entire* list of replacements */
10297   if (*expr == '$') {
10298     VAR *val = var_get(expr, nullptr, false, true);
10299     expr = val ? val->str_val : nullptr;
10300   }
10301   if (expr && *expr && !(glob_replace_regex = init_replace_regex(expr)))
10302     die("Could not init replace_regex");
10303   command->last_argument = command->end;
10304 }
10305 
free_replace_regex()10306 void free_replace_regex() {
10307   if (glob_replace_regex) {
10308     my_free(glob_replace_regex->even_buf);
10309     my_free(glob_replace_regex->odd_buf);
10310     glob_replace_regex->~st_replace_regex();
10311     my_free(glob_replace_regex);
10312     glob_replace_regex = nullptr;
10313   }
10314 }
10315 
10316 #ifndef WORD_BIT
10317 #define WORD_BIT (8 * sizeof(uint))
10318 #endif
10319 
10320 #define SET_MALLOC_HUNC 64
10321 #define LAST_CHAR_CODE 259
10322 
10323 struct REP_SET {
10324   uint *bits;                 /* Pointer to used sets */
10325   short next[LAST_CHAR_CODE]; /* Pointer to next sets */
10326   uint found_len;             /* Best match to date */
10327   int found_offset;
10328   uint table_offset;
10329   uint size_of_bits; /* For convinience */
10330 };
10331 
10332 struct REP_SETS {
10333   uint count;     /* Number of sets */
10334   uint extra;     /* Extra sets in buffer */
10335   uint invisible; /* Sets not chown */
10336   uint size_of_bits;
10337   REP_SET *set, *set_buffer;
10338   uint *bit_buffer;
10339 };
10340 
10341 struct FOUND_SET {
10342   uint table_offset;
10343   int found_offset;
10344 };
10345 
10346 struct FOLLOWS {
10347   int chr;
10348   uint table_offset;
10349   uint len;
10350 };
10351 
10352 int init_sets(REP_SETS *sets, uint states);
10353 REP_SET *make_new_set(REP_SETS *sets);
10354 void make_sets_invisible(REP_SETS *sets);
10355 void free_last_set(REP_SETS *sets);
10356 void free_sets(REP_SETS *sets);
10357 void internal_set_bit(REP_SET *set, uint bit);
10358 void internal_clear_bit(REP_SET *set, uint bit);
10359 void or_bits(REP_SET *to, REP_SET *from);
10360 void copy_bits(REP_SET *to, REP_SET *from);
10361 int cmp_bits(REP_SET *set1, REP_SET *set2);
10362 int get_next_bit(REP_SET *set, uint lastpos);
10363 int find_set(REP_SETS *sets, REP_SET *find);
10364 int find_found(FOUND_SET *found_set, uint table_offset, int found_offset);
10365 uint start_at_word(const char *pos);
10366 uint end_of_word(const char *pos);
10367 
10368 static uint found_sets = 0;
10369 
replace_len(const char * str)10370 static uint replace_len(const char *str) {
10371   uint len = 0;
10372   while (*str) {
10373     str++;
10374     len++;
10375   }
10376   return len;
10377 }
10378 
10379 /* Init a replace structure for further calls */
10380 
init_replace(const char ** from,const char ** to,uint count,const char * word_end_chars)10381 REPLACE *init_replace(const char **from, const char **to, uint count,
10382                       const char *word_end_chars) {
10383   static const int SPACE_CHAR = 256;
10384   static const int END_OF_LINE = 258;
10385 
10386   uint i, j, states, set_nr, len, result_len, max_length, found_end, bits_set,
10387       bit_nr;
10388   int used_sets, chr, default_state;
10389   char used_chars[LAST_CHAR_CODE], is_word_end[256];
10390   const char *pos, **to_array;
10391   char *to_pos;
10392   REP_SETS sets;
10393   REP_SET *set, *start_states, *word_states, *new_set;
10394   FOLLOWS *follow, *follow_ptr;
10395   REPLACE *replace;
10396   FOUND_SET *found_set;
10397   REPLACE_STRING *rep_str;
10398   DBUG_TRACE;
10399 
10400   /* Count number of states */
10401   for (i = result_len = max_length = 0, states = 2; i < count; i++) {
10402     len = replace_len(from[i]);
10403     if (!len) {
10404       errno = EINVAL;
10405       return nullptr;
10406     }
10407     states += len + 1;
10408     result_len += (uint)std::strlen(to[i]) + 1;
10409     if (len > max_length) max_length = len;
10410   }
10411   memset(is_word_end, 0, sizeof(is_word_end));
10412   for (i = 0; word_end_chars[i]; i++) is_word_end[(uchar)word_end_chars[i]] = 1;
10413 
10414   if (init_sets(&sets, states)) return nullptr;
10415   found_sets = 0;
10416   if (!(found_set = (FOUND_SET *)my_malloc(
10417             PSI_NOT_INSTRUMENTED, sizeof(FOUND_SET) * max_length * count,
10418             MYF(MY_WME)))) {
10419     free_sets(&sets);
10420     return nullptr;
10421   }
10422   (void)make_new_set(&sets);  /* Set starting set */
10423   make_sets_invisible(&sets); /* Hide previus sets */
10424   used_sets = -1;
10425   word_states = make_new_set(&sets);  /* Start of new word */
10426   start_states = make_new_set(&sets); /* This is first state */
10427   if (!(follow = (FOLLOWS *)my_malloc(PSI_NOT_INSTRUMENTED,
10428                                       (states + 2) * sizeof(FOLLOWS),
10429                                       MYF(MY_WME)))) {
10430     free_sets(&sets);
10431     my_free(found_set);
10432     return nullptr;
10433   }
10434 
10435   /* Init follow_ptr[] */
10436   for (i = 0, states = 1, follow_ptr = follow + 1; i < count; i++) {
10437     if (from[i][0] == '\\' && from[i][1] == '^') {
10438       internal_set_bit(start_states, states + 1);
10439       if (!from[i][2]) {
10440         start_states->table_offset = i;
10441         start_states->found_offset = 1;
10442       }
10443     } else if (from[i][0] == '\\' && from[i][1] == '$') {
10444       internal_set_bit(start_states, states);
10445       internal_set_bit(word_states, states);
10446       if (!from[i][2] && start_states->table_offset == (uint)~0) {
10447         start_states->table_offset = i;
10448         start_states->found_offset = 0;
10449       }
10450     } else {
10451       internal_set_bit(word_states, states);
10452       if (from[i][0] == '\\' && (from[i][1] == 'b' && from[i][2]))
10453         internal_set_bit(start_states, states + 1);
10454       else
10455         internal_set_bit(start_states, states);
10456     }
10457     for (pos = from[i], len = 0; *pos; pos++) {
10458       follow_ptr->chr = (uchar)*pos;
10459       follow_ptr->table_offset = i;
10460       follow_ptr->len = ++len;
10461       follow_ptr++;
10462     }
10463     follow_ptr->chr = 0;
10464     follow_ptr->table_offset = i;
10465     follow_ptr->len = len;
10466     follow_ptr++;
10467     states += (uint)len + 1;
10468   }
10469 
10470   for (set_nr = 0, pos = nullptr; set_nr < sets.count; set_nr++) {
10471     set = sets.set + set_nr;
10472     default_state = 0; /* Start from beginning */
10473 
10474     /* If end of found-string not found or start-set with current set */
10475 
10476     for (i = (uint)~0; (i = get_next_bit(set, i));) {
10477       if (!follow[i].chr) {
10478         if (!default_state)
10479           default_state =
10480               find_found(found_set, set->table_offset, set->found_offset + 1);
10481       }
10482     }
10483     copy_bits(sets.set + used_sets, set); /* Save set for changes */
10484     if (!default_state)
10485       or_bits(sets.set + used_sets, sets.set); /* Can restart from start */
10486 
10487     /* Find all chars that follows current sets */
10488     memset(used_chars, 0, sizeof(used_chars));
10489     for (i = (uint)~0; (i = get_next_bit(sets.set + used_sets, i));) {
10490       used_chars[follow[i].chr] = 1;
10491       if ((follow[i].chr == SPACE_CHAR && !follow[i + 1].chr &&
10492            follow[i].len > 1) ||
10493           follow[i].chr == END_OF_LINE)
10494         used_chars[0] = 1;
10495     }
10496 
10497     /* Mark word_chars used if \b is in state */
10498     if (used_chars[SPACE_CHAR])
10499       for (pos = word_end_chars; *pos; pos++) used_chars[(int)(uchar)*pos] = 1;
10500 
10501     /* Handle other used characters */
10502     for (chr = 0; chr < 256; chr++) {
10503       if (!used_chars[chr])
10504         set->next[chr] = chr ? default_state : -1;
10505       else {
10506         new_set = make_new_set(&sets);
10507         assert(new_set);
10508         set = sets.set + set_nr; /* if realloc */
10509         new_set->table_offset = set->table_offset;
10510         new_set->found_len = set->found_len;
10511         new_set->found_offset = set->found_offset + 1;
10512         found_end = 0;
10513 
10514         for (i = (uint)~0; (i = get_next_bit(sets.set + used_sets, i));) {
10515           if (!follow[i].chr || follow[i].chr == chr ||
10516               (follow[i].chr == SPACE_CHAR &&
10517                (is_word_end[chr] ||
10518                 (!chr && follow[i].len > 1 && !follow[i + 1].chr))) ||
10519               (follow[i].chr == END_OF_LINE && !chr)) {
10520             if ((!chr || (follow[i].chr && !follow[i + 1].chr)) &&
10521                 follow[i].len > found_end)
10522               found_end = follow[i].len;
10523             if (chr && follow[i].chr)
10524               internal_set_bit(new_set, i + 1); /* To next set */
10525             else
10526               internal_set_bit(new_set, i);
10527           }
10528         }
10529         if (found_end) {
10530           new_set->found_len = 0; /* Set for testing if first */
10531           bits_set = 0;
10532           for (i = (uint)~0; (i = get_next_bit(new_set, i));) {
10533             if ((follow[i].chr == SPACE_CHAR || follow[i].chr == END_OF_LINE) &&
10534                 !chr)
10535               bit_nr = i + 1;
10536             else
10537               bit_nr = i;
10538             if (follow[bit_nr - 1].len < found_end ||
10539                 (new_set->found_len && (chr == 0 || !follow[bit_nr].chr)))
10540               internal_clear_bit(new_set, i);
10541             else {
10542               if (chr == 0 || !follow[bit_nr].chr) { /* best match  */
10543                 new_set->table_offset = follow[bit_nr].table_offset;
10544                 if (chr || (follow[i].chr == SPACE_CHAR ||
10545                             follow[i].chr == END_OF_LINE))
10546                   new_set->found_offset = found_end; /* New match */
10547                 new_set->found_len = found_end;
10548               }
10549               bits_set++;
10550             }
10551           }
10552           if (bits_set == 1) {
10553             set->next[chr] = find_found(found_set, new_set->table_offset,
10554                                         new_set->found_offset);
10555             free_last_set(&sets);
10556           } else
10557             set->next[chr] = find_set(&sets, new_set);
10558         } else
10559           set->next[chr] = find_set(&sets, new_set);
10560       }
10561     }
10562   }
10563 
10564   /* Alloc replace structure for the replace-state-machine */
10565 
10566   if ((replace =
10567            (REPLACE *)my_malloc(PSI_NOT_INSTRUMENTED,
10568                                 sizeof(REPLACE) * (sets.count) +
10569                                     sizeof(REPLACE_STRING) * (found_sets + 1) +
10570                                     sizeof(char *) * count + result_len,
10571                                 MYF(MY_WME | MY_ZEROFILL)))) {
10572     rep_str = (REPLACE_STRING *)(replace + sets.count);
10573     to_array = pointer_cast<const char **>(rep_str + found_sets + 1);
10574     to_pos = (char *)(to_array + count);
10575     for (i = 0; i < count; i++) {
10576       to_array[i] = to_pos;
10577       to_pos = my_stpcpy(to_pos, to[i]) + 1;
10578     }
10579     rep_str[0].found = 1;
10580     rep_str[0].replace_string = nullptr;
10581     for (i = 1; i <= found_sets; i++) {
10582       pos = from[found_set[i - 1].table_offset];
10583       rep_str[i].found = !memcmp(pos, "\\^", 3) ? 2 : 1;
10584       rep_str[i].replace_string = to_array[found_set[i - 1].table_offset];
10585       rep_str[i].to_offset = found_set[i - 1].found_offset - start_at_word(pos);
10586       rep_str[i].from_offset =
10587           found_set[i - 1].found_offset - replace_len(pos) + end_of_word(pos);
10588     }
10589     for (i = 0; i < sets.count; i++) {
10590       for (j = 0; j < 256; j++)
10591         if (sets.set[i].next[j] >= 0)
10592           replace[i].next[j] = replace + sets.set[i].next[j];
10593         else
10594           replace[i].next[j] =
10595               (REPLACE *)(rep_str + (-sets.set[i].next[j] - 1));
10596     }
10597   }
10598   my_free(follow);
10599   free_sets(&sets);
10600   my_free(found_set);
10601   DBUG_PRINT("exit", ("Replace table has %d states", sets.count));
10602   return replace;
10603 }
10604 
init_sets(REP_SETS * sets,uint states)10605 int init_sets(REP_SETS *sets, uint states) {
10606   memset(sets, 0, sizeof(*sets));
10607   sets->size_of_bits = ((states + 7) / 8);
10608   if (!(sets->set_buffer = (REP_SET *)my_malloc(
10609             PSI_NOT_INSTRUMENTED, sizeof(REP_SET) * SET_MALLOC_HUNC,
10610             MYF(MY_WME))))
10611     return 1;
10612   if (!(sets->bit_buffer = (uint *)my_malloc(
10613             PSI_NOT_INSTRUMENTED,
10614             sizeof(uint) * sets->size_of_bits * SET_MALLOC_HUNC,
10615             MYF(MY_WME)))) {
10616     my_free(sets->set);
10617     return 1;
10618   }
10619   return 0;
10620 }
10621 
10622 /* Make help sets invisible for nicer codeing */
10623 
make_sets_invisible(REP_SETS * sets)10624 void make_sets_invisible(REP_SETS *sets) {
10625   sets->invisible = sets->count;
10626   sets->set += sets->count;
10627   sets->count = 0;
10628 }
10629 
make_new_set(REP_SETS * sets)10630 REP_SET *make_new_set(REP_SETS *sets) {
10631   uint i, count, *bit_buffer;
10632   REP_SET *set;
10633   if (sets->extra) {
10634     sets->extra--;
10635     set = sets->set + sets->count++;
10636     memset(set->bits, 0, sizeof(uint) * sets->size_of_bits);
10637     memset(&set->next[0], 0, sizeof(set->next[0]) * LAST_CHAR_CODE);
10638     set->found_offset = 0;
10639     set->found_len = 0;
10640     set->table_offset = (uint)~0;
10641     set->size_of_bits = sets->size_of_bits;
10642     return set;
10643   }
10644   count = sets->count + sets->invisible + SET_MALLOC_HUNC;
10645   if (!(set = (REP_SET *)my_realloc(PSI_NOT_INSTRUMENTED,
10646                                     (uchar *)sets->set_buffer,
10647                                     sizeof(REP_SET) * count, MYF(MY_WME))))
10648     return nullptr;
10649   sets->set_buffer = set;
10650   sets->set = set + sets->invisible;
10651   if (!(bit_buffer = (uint *)my_realloc(
10652             PSI_NOT_INSTRUMENTED, (uchar *)sets->bit_buffer,
10653             (sizeof(uint) * sets->size_of_bits) * count, MYF(MY_WME))))
10654     return nullptr;
10655   sets->bit_buffer = bit_buffer;
10656   for (i = 0; i < count; i++) {
10657     sets->set_buffer[i].bits = bit_buffer;
10658     bit_buffer += sets->size_of_bits;
10659   }
10660   sets->extra = SET_MALLOC_HUNC;
10661   return make_new_set(sets);
10662 }
10663 
free_last_set(REP_SETS * sets)10664 void free_last_set(REP_SETS *sets) {
10665   sets->count--;
10666   sets->extra++;
10667   return;
10668 }
10669 
free_sets(REP_SETS * sets)10670 void free_sets(REP_SETS *sets) {
10671   my_free(sets->set_buffer);
10672   my_free(sets->bit_buffer);
10673   return;
10674 }
10675 
internal_set_bit(REP_SET * set,uint bit)10676 void internal_set_bit(REP_SET *set, uint bit) {
10677   set->bits[bit / WORD_BIT] |= 1 << (bit % WORD_BIT);
10678   return;
10679 }
10680 
internal_clear_bit(REP_SET * set,uint bit)10681 void internal_clear_bit(REP_SET *set, uint bit) {
10682   set->bits[bit / WORD_BIT] &= ~(1 << (bit % WORD_BIT));
10683   return;
10684 }
10685 
or_bits(REP_SET * to,REP_SET * from)10686 void or_bits(REP_SET *to, REP_SET *from) {
10687   uint i;
10688   for (i = 0; i < to->size_of_bits; i++) to->bits[i] |= from->bits[i];
10689   return;
10690 }
10691 
copy_bits(REP_SET * to,REP_SET * from)10692 void copy_bits(REP_SET *to, REP_SET *from) {
10693   memcpy((uchar *)to->bits, (uchar *)from->bits,
10694          (size_t)(sizeof(uint) * to->size_of_bits));
10695 }
10696 
cmp_bits(REP_SET * set1,REP_SET * set2)10697 int cmp_bits(REP_SET *set1, REP_SET *set2) {
10698   return memcmp(set1->bits, set2->bits, sizeof(uint) * set1->size_of_bits);
10699 }
10700 
10701 /* Get next set bit from set. */
10702 
get_next_bit(REP_SET * set,uint lastpos)10703 int get_next_bit(REP_SET *set, uint lastpos) {
10704   uint pos, *start, *end, bits;
10705 
10706   start = set->bits + ((lastpos + 1) / WORD_BIT);
10707   end = set->bits + set->size_of_bits;
10708   bits = start[0] & ~((1 << ((lastpos + 1) % WORD_BIT)) - 1U);
10709 
10710   while (!bits && ++start < end) bits = start[0];
10711   if (!bits) return 0;
10712   pos = (uint)(start - set->bits) * WORD_BIT;
10713   while (!(bits & 1)) {
10714     bits >>= 1;
10715     pos++;
10716   }
10717   return pos;
10718 }
10719 
10720 /* find if there is a same set in sets. If there is, use it and
10721    free given set, else put in given set in sets and return its
10722    position */
10723 
find_set(REP_SETS * sets,REP_SET * find)10724 int find_set(REP_SETS *sets, REP_SET *find) {
10725   uint i;
10726   for (i = 0; i < sets->count - 1; i++) {
10727     if (!cmp_bits(sets->set + i, find)) {
10728       free_last_set(sets);
10729       return i;
10730     }
10731   }
10732   return i; /* return new position */
10733 }
10734 
10735 /* find if there is a found_set with same table_offset & found_offset
10736    If there is return offset to it, else add new offset and return pos.
10737    Pos returned is -offset-2 in found_set_structure because it is
10738    saved in set->next and set->next[] >= 0 points to next set and
10739    set->next[] == -1 is reserved for end without replaces.
10740 */
10741 
find_found(FOUND_SET * found_set,uint table_offset,int found_offset)10742 int find_found(FOUND_SET *found_set, uint table_offset, int found_offset) {
10743   int i;
10744   for (i = 0; (uint)i < found_sets; i++)
10745     if (found_set[i].table_offset == table_offset &&
10746         found_set[i].found_offset == found_offset)
10747       return -i - 2;
10748   found_set[i].table_offset = table_offset;
10749   found_set[i].found_offset = found_offset;
10750   found_sets++;
10751   return -i - 2; /* return new position */
10752 }
10753 
10754 /* Return 1 if regexp starts with \b or ends with \b*/
10755 
start_at_word(const char * pos)10756 uint start_at_word(const char *pos) {
10757   return (((!memcmp(pos, "\\b", 2) && pos[2]) || !memcmp(pos, "\\^", 2)) ? 1
10758                                                                          : 0);
10759 }
10760 
end_of_word(const char * pos)10761 uint end_of_word(const char *pos) {
10762   const char *end = strend(pos);
10763   return ((end > pos + 2 && !memcmp(end - 2, "\\b", 2)) ||
10764           (end >= pos + 2 && !memcmp(end - 2, "\\$", 2)))
10765              ? 1
10766              : 0;
10767 }
10768 
10769 /****************************************************************************
10770  * Handle replacement of strings
10771  ****************************************************************************/
10772 
10773 #define PC_MALLOC 256 /* Bytes for pointers */
10774 #define PS_MALLOC 512 /* Bytes for data */
10775 
insert_pointer_name(POINTER_ARRAY * pa,char * name)10776 int insert_pointer_name(POINTER_ARRAY *pa, char *name) {
10777   uint i, length, old_count;
10778   uchar *new_pos;
10779   const char **new_array;
10780   DBUG_TRACE;
10781 
10782   if (!pa->typelib.count) {
10783     if (!(pa->typelib.type_names =
10784               (const char **)my_malloc(PSI_NOT_INSTRUMENTED,
10785                                        ((PC_MALLOC - MALLOC_OVERHEAD) /
10786                                         (sizeof(char *) + sizeof(*pa->flag)) *
10787                                         (sizeof(char *) + sizeof(*pa->flag))),
10788                                        MYF(MY_WME))))
10789       return -1;
10790     if (!(pa->str = (uchar *)my_malloc(PSI_NOT_INSTRUMENTED,
10791                                        (uint)(PS_MALLOC - MALLOC_OVERHEAD),
10792                                        MYF(MY_WME)))) {
10793       my_free(pa->typelib.type_names);
10794       return -1;
10795     }
10796     pa->max_count =
10797         (PC_MALLOC - MALLOC_OVERHEAD) / (sizeof(uchar *) + sizeof(*pa->flag));
10798     pa->flag = (uint8 *)(pa->typelib.type_names + pa->max_count);
10799     pa->length = 0;
10800     pa->max_length = PS_MALLOC - MALLOC_OVERHEAD;
10801     pa->array_allocs = 1;
10802   }
10803   length = (uint)std::strlen(name) + 1;
10804   if (pa->length + length >= pa->max_length) {
10805     if (!(new_pos = (uchar *)my_realloc(PSI_NOT_INSTRUMENTED, (uchar *)pa->str,
10806                                         (uint)(pa->length + length + PS_MALLOC),
10807                                         MYF(MY_WME))))
10808       return 1;
10809     if (new_pos != pa->str) {
10810       ptrdiff_t diff = new_pos - pa->str;
10811       for (i = 0; i < pa->typelib.count; i++)
10812         pa->typelib.type_names[i] = pa->typelib.type_names[i] + diff;
10813       pa->str = new_pos;
10814     }
10815     pa->max_length = pa->length + length + PS_MALLOC;
10816   }
10817   if (pa->typelib.count >= pa->max_count - 1) {
10818     int len;
10819     pa->array_allocs++;
10820     len = (PC_MALLOC * pa->array_allocs - MALLOC_OVERHEAD);
10821     if (!(new_array = (const char **)my_realloc(
10822               PSI_NOT_INSTRUMENTED, (uchar *)pa->typelib.type_names,
10823               (uint)len / (sizeof(uchar *) + sizeof(*pa->flag)) *
10824                   (sizeof(uchar *) + sizeof(*pa->flag)),
10825               MYF(MY_WME))))
10826       return 1;
10827     pa->typelib.type_names = new_array;
10828     old_count = pa->max_count;
10829     pa->max_count = len / (sizeof(uchar *) + sizeof(*pa->flag));
10830     pa->flag = (uint8 *)(pa->typelib.type_names + pa->max_count);
10831     memcpy((uchar *)pa->flag, (char *)(pa->typelib.type_names + old_count),
10832            old_count * sizeof(*pa->flag));
10833   }
10834   pa->flag[pa->typelib.count] = 0; /* Reset flag */
10835   pa->typelib.type_names[pa->typelib.count++] = (char *)pa->str + pa->length;
10836   pa->typelib.type_names[pa->typelib.count] = NullS; /* Put end-mark */
10837   (void)my_stpcpy((char *)pa->str + pa->length, name);
10838   pa->length += length;
10839   return 0;
10840 } /* insert_pointer_name */
10841 
10842 /* free pointer array */
10843 
free_pointer_array(POINTER_ARRAY * pa)10844 void free_pointer_array(POINTER_ARRAY *pa) {
10845   if (pa->typelib.count) {
10846     pa->typelib.count = 0;
10847     my_free(pa->typelib.type_names);
10848     pa->typelib.type_names = nullptr;
10849     my_free(pa->str);
10850   }
10851 } /* free_pointer_array */
10852 
10853 /* Functions that uses replace and replace_regex */
10854 
10855 /* Append the string to ds, with optional replace */
replace_dynstr_append_mem(DYNAMIC_STRING * ds,const char * val,size_t len)10856 void replace_dynstr_append_mem(DYNAMIC_STRING *ds, const char *val,
10857                                size_t len) {
10858 #ifdef _WIN32
10859   fix_win_paths(val, len);
10860 #endif
10861 
10862   if (display_result_lower) {
10863     /* Convert to lower case, and do this first */
10864     my_casedn_str(charset_info, const_cast<char *>(val));
10865   }
10866 
10867   if (glob_replace_regex) {
10868     size_t orig_len = len;
10869     // Regex replace
10870     if (!multi_reg_replace(glob_replace_regex, const_cast<char *>(val), &len)) {
10871       val = glob_replace_regex->buf;
10872     } else {
10873       len = orig_len;
10874     }
10875   }
10876 
10877   DYNAMIC_STRING ds_temp;
10878   init_dynamic_string(&ds_temp, "", 512, 512);
10879 
10880   /* Store result from replace_result in ds_temp */
10881   if (glob_replace) {
10882     /* Normal replace */
10883     replace_strings_append(glob_replace, &ds_temp, val, len);
10884   }
10885 
10886   /*
10887     Call the replace_numeric_round function with the specified
10888     precision. It may be used along with replace_result, so use the
10889     output from replace_result as the input for replace_numeric_round.
10890   */
10891   if (glob_replace_numeric_round >= 0) {
10892     /* Copy the result from replace_result if it was used, into buffer */
10893     if (ds_temp.length > 0) {
10894       char buffer[512];
10895       strcpy(buffer, ds_temp.str);
10896       dynstr_free(&ds_temp);
10897       init_dynamic_string(&ds_temp, "", 512, 512);
10898       replace_numeric_round_append(glob_replace_numeric_round, &ds_temp, buffer,
10899                                    std::strlen(buffer));
10900     } else
10901       replace_numeric_round_append(glob_replace_numeric_round, &ds_temp, val,
10902                                    len);
10903   }
10904 
10905   if (!glob_replace && glob_replace_numeric_round < 0)
10906     dynstr_append_mem(ds, val, len);
10907   else
10908     dynstr_append_mem(ds, ds_temp.str, std::strlen(ds_temp.str));
10909   dynstr_free(&ds_temp);
10910 }
10911 
10912 /* Append zero-terminated string to ds, with optional replace */
replace_dynstr_append(DYNAMIC_STRING * ds,const char * val)10913 void replace_dynstr_append(DYNAMIC_STRING *ds, const char *val) {
10914   replace_dynstr_append_mem(ds, val, std::strlen(val));
10915 }
10916 
10917 /* Append uint to ds, with optional replace */
replace_dynstr_append_uint(DYNAMIC_STRING * ds,uint val)10918 void replace_dynstr_append_uint(DYNAMIC_STRING *ds, uint val) {
10919   char buff[22]; /* This should be enough for any int */
10920   char *end = longlong10_to_str(val, buff, 10);
10921   replace_dynstr_append_mem(ds, buff, end - buff);
10922 }
10923 
10924 /*
10925   Build a list of pointer to each line in ds_input, sort
10926   the list and use the sorted list to append the strings
10927   sorted to the output ds
10928 
10929   SYNOPSIS
10930   dynstr_append_sorted
10931   ds - string where the sorted output will be appended
10932   ds_input - string to be sorted
10933   start_sort_column - column to start sorting from (0 for sorting
10934     the entire line); a stable sort will be used
10935 */
10936 
10937 class Comp_lines {
10938  public:
operator ()(const char * a,const char * b)10939   bool operator()(const char *a, const char *b) {
10940     return std::strcmp(a, b) < 0;
10941   }
10942 };
10943 
length_of_n_first_columns(std::string str,int start_sort_column)10944 static size_t length_of_n_first_columns(std::string str,
10945                                         int start_sort_column) {
10946   std::stringstream columns(str);
10947   std::string temp;
10948   size_t size_of_columns = 0;
10949 
10950   int i = 0;
10951   while (getline(columns, temp, '\t') && i < start_sort_column) {
10952     size_of_columns = size_of_columns + temp.length();
10953     i++;
10954   }
10955 
10956   return size_of_columns;
10957 }
10958 
dynstr_append_sorted(DYNAMIC_STRING * ds,DYNAMIC_STRING * ds_input,int start_sort_column)10959 void dynstr_append_sorted(DYNAMIC_STRING *ds, DYNAMIC_STRING *ds_input,
10960                           int start_sort_column) {
10961   char *start = ds_input->str;
10962   char *end = ds_input->str + ds_input->length;
10963   std::vector<std::string> sorted;
10964   DBUG_TRACE;
10965 
10966   if (!*start) return; /* No input */
10967 
10968   /* First line is result header, skip past it */
10969   while (*start && *start != '\n') start++;
10970   start++; /* Skip past \n */
10971   dynstr_append_mem(ds, ds_input->str, start - ds_input->str);
10972 
10973   /*
10974     Traverse through the result set from start to end to avoid
10975     ignoring null characters (0x00), and insert line by line
10976     into array.
10977   */
10978   size_t first_unsorted_row = 0;
10979   while (start < end) {
10980     char *line_end = (char *)start;
10981 
10982     /* Find end of line */
10983     while (*line_end != '\n') line_end++;
10984     *line_end = 0;
10985 
10986     std::string result_row = std::string(start, line_end - start);
10987     if (!sorted.empty() && start_sort_column > 0) {
10988       /*
10989         If doing partial sorting, and the prefix is different from that of the
10990         previous line, the group is done. Sort it and start another one.
10991        */
10992       size_t prev_line_prefix_len =
10993           length_of_n_first_columns(sorted.back(), start_sort_column);
10994       if (sorted.back().compare(0, prev_line_prefix_len, result_row, 0,
10995                                 prev_line_prefix_len) != 0) {
10996         std::sort(sorted.begin() + first_unsorted_row, sorted.end());
10997         first_unsorted_row = sorted.size();
10998       }
10999     }
11000 
11001     /* Insert line into the array */
11002     sorted.push_back(result_row);
11003     start = line_end + 1;
11004   }
11005 
11006   /* Sort array */
11007   std::stable_sort(sorted.begin() + first_unsorted_row, sorted.end());
11008 
11009   /* Create new result */
11010   for (auto i : sorted) {
11011     dynstr_append_mem(ds, i.c_str(), i.length());
11012     dynstr_append(ds, "\n");
11013   }
11014 }
11015