From 4c2de881de86cbb0d5d0abb0fadfb996048849a6 Mon Sep 17 00:00:00 2001
From: Marc Alff <marc.alff@oracle.com>
Date: Wed, 25 Aug 2010 18:59:28 -0600
Subject: [PATCH] Bug#55873 short startup options do not work in 5.5

Before this fix, the server did not recognize 'short' (as in -a)
options but only 'long' (as in --ansi) options
in the startup command line, due to earlier changes in 5.5
introduced for the performance schema.

The root cause is that handle_options() did not honor the
my_getopt_skip_unknown flag when parsing 'short' options.

The fix changes handle_options(), so that my_getopt_skip_unknown is
honored in all cases.

Note that there are limitations to this,
see the added doxygen documentation in handle_options().

The current usage of handle_options() by the server to
parse early performance schema options fits within the limitations.
This has been enforced by an assert for PARSE_EARLY options, for safety.
---
 .../suite/perfschema/r/bad_option_3.result    |   2 +
 .../suite/perfschema/r/bad_option_4.result    |   2 +
 .../suite/perfschema/r/bad_option_5.result    |   2 +
 .../suite/perfschema/r/short_option_1.result  |  27 +++++
 .../suite/perfschema/r/short_option_2.result  |   9 ++
 .../suite/perfschema/t/bad_option_3.test      |  49 +++++++++
 .../suite/perfschema/t/bad_option_4.test      |  49 +++++++++
 .../suite/perfschema/t/bad_option_5.test      |  52 +++++++++
 .../perfschema/t/short_option_1-master.opt    |   1 +
 .../suite/perfschema/t/short_option_1.test    |  35 ++++++
 .../perfschema/t/short_option_2-master.opt    |   1 +
 .../suite/perfschema/t/short_option_2.test    |  29 +++++
 mysys/my_getopt.c                             | 100 ++++++++++++++++--
 sql/set_var.cc                                |  11 ++
 14 files changed, 362 insertions(+), 7 deletions(-)
 create mode 100644 mysql-test/suite/perfschema/r/bad_option_3.result
 create mode 100644 mysql-test/suite/perfschema/r/bad_option_4.result
 create mode 100644 mysql-test/suite/perfschema/r/bad_option_5.result
 create mode 100644 mysql-test/suite/perfschema/r/short_option_1.result
 create mode 100644 mysql-test/suite/perfschema/r/short_option_2.result
 create mode 100644 mysql-test/suite/perfschema/t/bad_option_3.test
 create mode 100644 mysql-test/suite/perfschema/t/bad_option_4.test
 create mode 100644 mysql-test/suite/perfschema/t/bad_option_5.test
 create mode 100644 mysql-test/suite/perfschema/t/short_option_1-master.opt
 create mode 100644 mysql-test/suite/perfschema/t/short_option_1.test
 create mode 100644 mysql-test/suite/perfschema/t/short_option_2-master.opt
 create mode 100644 mysql-test/suite/perfschema/t/short_option_2.test

diff --git a/mysql-test/suite/perfschema/r/bad_option_3.result b/mysql-test/suite/perfschema/r/bad_option_3.result
new file mode 100644
index 0000000000..ec717d6f7d
--- /dev/null
+++ b/mysql-test/suite/perfschema/r/bad_option_3.result
@@ -0,0 +1,2 @@
+Found: unknown option '-x'
+Found: Aborting
diff --git a/mysql-test/suite/perfschema/r/bad_option_4.result b/mysql-test/suite/perfschema/r/bad_option_4.result
new file mode 100644
index 0000000000..812ee54620
--- /dev/null
+++ b/mysql-test/suite/perfschema/r/bad_option_4.result
@@ -0,0 +1,2 @@
+Found: Can't change dir to.*bad_option_h_param
+Found: Aborting
diff --git a/mysql-test/suite/perfschema/r/bad_option_5.result b/mysql-test/suite/perfschema/r/bad_option_5.result
new file mode 100644
index 0000000000..b318b6e048
--- /dev/null
+++ b/mysql-test/suite/perfschema/r/bad_option_5.result
@@ -0,0 +1,2 @@
+Found: unknown option '-X'
+Found: Aborting
diff --git a/mysql-test/suite/perfschema/r/short_option_1.result b/mysql-test/suite/perfschema/r/short_option_1.result
new file mode 100644
index 0000000000..ad48fe05ad
--- /dev/null
+++ b/mysql-test/suite/perfschema/r/short_option_1.result
@@ -0,0 +1,27 @@
+select 'Ok, the server started' as result;
+result
+Ok, the server started
+select @@SQL_MODE;
+@@SQL_MODE
+REAL_AS_FLOAT,PIPES_AS_CONCAT,ANSI_QUOTES,IGNORE_SPACE,ANSI
+show variables like 'sql_mode';
+Variable_name	Value
+sql_mode	REAL_AS_FLOAT,PIPES_AS_CONCAT,ANSI_QUOTES,IGNORE_SPACE,ANSI
+select @@character_set_server;
+@@character_set_server
+utf8
+show variables like 'character_set_system';
+Variable_name	Value
+character_set_system	utf8
+show variables like 'log';
+Variable_name	Value
+log	ON
+show variables like 'general_log';
+Variable_name	Value
+general_log	ON
+show variables like 'new';
+Variable_name	Value
+new	ON
+show variables like 'log_warnings';
+Variable_name	Value
+log_warnings	3
diff --git a/mysql-test/suite/perfschema/r/short_option_2.result b/mysql-test/suite/perfschema/r/short_option_2.result
new file mode 100644
index 0000000000..0587fdef73
--- /dev/null
+++ b/mysql-test/suite/perfschema/r/short_option_2.result
@@ -0,0 +1,9 @@
+select 'Ok, the server started' as result;
+result
+Ok, the server started
+select @@SQL_MODE;
+@@SQL_MODE
+REAL_AS_FLOAT,PIPES_AS_CONCAT,ANSI_QUOTES,IGNORE_SPACE,ANSI
+select @@log_warnings;
+@@log_warnings
+5
diff --git a/mysql-test/suite/perfschema/t/bad_option_3.test b/mysql-test/suite/perfschema/t/bad_option_3.test
new file mode 100644
index 0000000000..1a9efb61fd
--- /dev/null
+++ b/mysql-test/suite/perfschema/t/bad_option_3.test
@@ -0,0 +1,49 @@
+# Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA
+
+# Tests for PERFORMANCE_SCHEMA
+# Check error handling for invalid server start options
+
+--source include/not_embedded.inc
+--source include/have_perfschema.inc
+
+let $outfile= $MYSQLTEST_VARDIR/tmp/bad_option_3.txt;
+--error 0,1
+--remove_file $outfile
+--error 2
+--exec $MYSQLD_BOOTSTRAP_CMD --loose-console -a -x > $outfile 2>&1
+
+perl;
+    use strict;
+    use warnings;
+    my $fname= "$ENV{'MYSQLTEST_VARDIR'}/tmp/bad_option_3.txt";
+    open(FILE, "<", $fname) or die;
+    my @lines= <FILE>;
+    # those must be in the file for the test to pass
+    my @patterns=
+      ("unknown option '-x'",
+       "Aborting");
+    foreach my $one_line (@lines)
+    {
+      foreach my $one_pattern (@patterns)
+      {
+        # print pattern, not line, to get a stable output
+        print "Found: $one_pattern\n" if ($one_line =~ /$one_pattern/);
+      }
+    }
+    close FILE;
+EOF
+--remove_file $outfile
+
diff --git a/mysql-test/suite/perfschema/t/bad_option_4.test b/mysql-test/suite/perfschema/t/bad_option_4.test
new file mode 100644
index 0000000000..c13df1b614
--- /dev/null
+++ b/mysql-test/suite/perfschema/t/bad_option_4.test
@@ -0,0 +1,49 @@
+# Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA
+
+# Tests for PERFORMANCE_SCHEMA
+# Check error handling for invalid server start options
+
+--source include/not_embedded.inc
+--source include/have_perfschema.inc
+
+let $outfile= $MYSQLTEST_VARDIR/tmp/bad_option_4.txt;
+--error 0,1
+--remove_file $outfile
+--error 1
+--exec $MYSQLD_BOOTSTRAP_CMD --loose-console -a -h bad_option_h_param > $outfile 2>&1
+
+perl;
+    use strict;
+    use warnings;
+    my $fname= "$ENV{'MYSQLTEST_VARDIR'}/tmp/bad_option_4.txt";
+    open(FILE, "<", $fname) or die;
+    my @lines= <FILE>;
+    # those must be in the file for the test to pass
+    my @patterns=
+      ("Can't change dir to.*bad_option_h_param",
+       "Aborting");
+    foreach my $one_line (@lines)
+    {
+      foreach my $one_pattern (@patterns)
+      {
+        # print pattern, not line, to get a stable output
+        print "Found: $one_pattern\n" if ($one_line =~ /$one_pattern/);
+      }
+    }
+    close FILE;
+EOF
+--remove_file $outfile
+
diff --git a/mysql-test/suite/perfschema/t/bad_option_5.test b/mysql-test/suite/perfschema/t/bad_option_5.test
new file mode 100644
index 0000000000..d0ae137085
--- /dev/null
+++ b/mysql-test/suite/perfschema/t/bad_option_5.test
@@ -0,0 +1,52 @@
+# Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA
+
+# Tests for PERFORMANCE_SCHEMA
+# Check error handling for invalid server start options
+
+--source include/not_embedded.inc
+--source include/have_perfschema.inc
+
+let $outfile= $MYSQLTEST_VARDIR/tmp/bad_option_5.txt;
+--error 0,1
+--remove_file $outfile
+--error 2
+--exec $MYSQLD_BOOTSTRAP_CMD --loose-console -aXbroken > $outfile 2>&1
+
+# -aXbroken should be parsed as -a -Xbroken, or --ansi -Xbroken,
+# therefore the -X option is what the server should complain about
+
+perl;
+    use strict;
+    use warnings;
+    my $fname= "$ENV{'MYSQLTEST_VARDIR'}/tmp/bad_option_5.txt";
+    open(FILE, "<", $fname) or die;
+    my @lines= <FILE>;
+    # those must be in the file for the test to pass
+    my @patterns=
+      ("unknown option '-X'",
+       "Aborting");
+    foreach my $one_line (@lines)
+    {
+      foreach my $one_pattern (@patterns)
+      {
+        # print pattern, not line, to get a stable output
+        print "Found: $one_pattern\n" if ($one_line =~ /$one_pattern/);
+      }
+    }
+    close FILE;
+EOF
+--remove_file $outfile
+
diff --git a/mysql-test/suite/perfschema/t/short_option_1-master.opt b/mysql-test/suite/perfschema/t/short_option_1-master.opt
new file mode 100644
index 0000000000..6a6e8d4c5c
--- /dev/null
+++ b/mysql-test/suite/perfschema/t/short_option_1-master.opt
@@ -0,0 +1 @@
+-a -n -Cutf8 --collation=utf8_bin -l -T12 -W3
diff --git a/mysql-test/suite/perfschema/t/short_option_1.test b/mysql-test/suite/perfschema/t/short_option_1.test
new file mode 100644
index 0000000000..1d3e083518
--- /dev/null
+++ b/mysql-test/suite/perfschema/t/short_option_1.test
@@ -0,0 +1,35 @@
+# Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA
+
+# Tests for PERFORMANCE_SCHEMA
+# Check server start for short server start options
+
+select 'Ok, the server started' as result;
+
+# See the associated -master.opt file.
+
+select @@SQL_MODE;
+show variables like 'sql_mode';
+
+select @@character_set_server;
+show variables like 'character_set_system';
+
+show variables like 'log';
+show variables like 'general_log';
+
+show variables like 'new';
+
+show variables like 'log_warnings';
+
diff --git a/mysql-test/suite/perfschema/t/short_option_2-master.opt b/mysql-test/suite/perfschema/t/short_option_2-master.opt
new file mode 100644
index 0000000000..5cf7590a1c
--- /dev/null
+++ b/mysql-test/suite/perfschema/t/short_option_2-master.opt
@@ -0,0 +1 @@
+-aW5
diff --git a/mysql-test/suite/perfschema/t/short_option_2.test b/mysql-test/suite/perfschema/t/short_option_2.test
new file mode 100644
index 0000000000..eb528dfcf7
--- /dev/null
+++ b/mysql-test/suite/perfschema/t/short_option_2.test
@@ -0,0 +1,29 @@
+# Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA
+
+# Tests for PERFORMANCE_SCHEMA
+# Check server start for short server start options
+
+select 'Ok, the server started' as result;
+
+# See the associated -master.opt file.
+# -aW5 should be parsed as -a -W5, which are two separate short options
+# stuffed inside a single argv[i] argument.
+
+# Should contain ANSI, since we started the server with -a (stands for --ansi)
+select @@SQL_MODE;
+
+# Should be 5, since we started the server with -W5
+select @@log_warnings;
diff --git a/mysys/my_getopt.c b/mysys/my_getopt.c
index 1c5bf0b6b1..5689e0bafb 100644
--- a/mysys/my_getopt.c
+++ b/mysys/my_getopt.c
@@ -98,6 +98,49 @@ void my_getopt_register_get_addr(my_getopt_value func_addr)
   matches with one of the options in struct 'my_option'.
   Check that option was given an argument if it requires one
   Call the optional 'get_one_option()' function once for each option.
+
+  Note that handle_options() can be invoked multiple times to
+  parse a command line in several steps.
+  In this case, use the global flag @c my_getopt_skip_unknown to indicate
+  that options unknown in the current step should be preserved in the
+  command line for later parsing in subsequent steps.
+
+  For 'long' options (--a_long_option), @c my_getopt_skip_unknown is
+  fully supported. Command line parameters such as:
+  - "--a_long_option"
+  - "--a_long_option=value"
+  - "--a_long_option value"
+  will be preserved as is when the option is not known.
+
+  For 'short' options (-S), support for @c my_getopt_skip_unknown
+  comes with some limitation, because several short options
+  can also be specified together in the same command line argument,
+  as in "-XYZ".
+
+  The first use case supported is: all short options are declared.
+  handle_options() will be able to interpret "-XYZ" as one of:
+  - an unknown X option
+  - "-X -Y -Z", three short options with no arguments
+  - "-X -YZ", where Y is a short option with argument Z
+  - "-XYZ", where X is a short option with argument YZ
+  based on the full short options specifications.
+
+  The second use case supported is: no short option is declared.
+  handle_options() will reject "-XYZ" as unknown, to be parsed later.
+
+  The use case that is explicitly not supported is to provide
+  only a partial list of short options to handle_options().
+  This function can not be expected to extract some option Y
+  in the middle of the string "-XYZ" in these conditions,
+  without knowing if X will be declared an option later.
+
+  Note that this limitation only impacts parsing of several
+  short options from the same command line argument,
+  as in "mysqld -anW5".
+  When each short option is properly separated out in the command line
+  argument, for example in "mysqld -a -n -w5", the code would actually
+  work even with partial options specs given at each stage.
+
   @param [in, out] argc      command line options (count)
   @param [in, out] argv      command line options (values)
   @param [in] longopts       descriptor of all valid options
@@ -464,14 +507,57 @@ int handle_options(int *argc, char ***argv,
 	  }
 	  if (!opt_found)
 	  {
-	    if (my_getopt_print_errors)
-              my_getopt_error_reporter(ERROR_LEVEL,
-                                       "%s: unknown option '-%c'", 
-                                       my_progname, *optend);
-	    return EXIT_UNKNOWN_OPTION;
+            if (my_getopt_skip_unknown)
+            {
+              /*
+                We are currently parsing a single argv[] argument
+                of the form "-XYZ", and parsing is done in multiple phases.
+                One or the argument found is not an option.
+              */
+              if (optend == cur_arg)
+              {
+                /*
+                  The first argument, "-X", is not an option
+                  In this case, the entire argument "-XYZ" is rejected
+                  from this phase, and preserved as is for later parsing.
+                */
+                (*argv)[argvpos++]= *pos;
+              }
+              else
+              {
+                /*
+                  We are in the middle of an "-XYZ" string already,
+                  "-X" has already been parsed, and "Y" (pointed by optend)
+                  is not an option.
+                  Hack the string "-XYZ" to make a "-YZ" substring in it,
+                  and push that to the next parsing phase.
+                */
+                DBUG_ASSERT(optend > *pos);
+                DBUG_ASSERT(optend > cur_arg);
+                DBUG_ASSERT(optend <= *pos + strlen(*pos));
+                DBUG_ASSERT(*optend);
+                optend--;
+                optend[0]= '-'; /* replace 'X' by '-' */
+                (*argv)[argvpos++]= optend;
+              }
+              /*
+                Do not continue to parse at the current "-XYZ" argument,
+                skip to the next argv[] argument instead.
+              */
+              optend= (char*) " ";
+            }
+            else
+            {
+              if (my_getopt_print_errors)
+                my_getopt_error_reporter(ERROR_LEVEL,
+                                         "%s: unknown option '-%c'",
+                                         my_progname, *optend);
+              return EXIT_UNKNOWN_OPTION;
+            }
 	  }
 	}
-	(*argc)--; /* option handled (short), decrease argument count */
+        if (opt_found)
+          (*argc)--; /* option handled (short), decrease argument count */
 	continue;
       }
       if ((error= setval(optp, value, argument, set_maximum_value)))
@@ -479,7 +565,7 @@ int handle_options(int *argc, char ***argv,
       if (get_one_option && get_one_option(optp->id, optp, argument))
         return EXIT_UNSPECIFIED_ERROR;
 
-      (*argc)--; /* option handled (short or long), decrease argument count */
+      (*argc)--; /* option handled (long), decrease argument count */
     }
     else /* non-option found */
       (*argv)[argvpos++]= cur_arg;
diff --git a/sql/set_var.cc b/sql/set_var.cc
index 9daaf883ea..501c638205 100644
--- a/sql/set_var.cc
+++ b/sql/set_var.cc
@@ -153,6 +153,17 @@ sys_var::sys_var(sys_var_chain *chain, const char *name_arg,
   guard(lock), offset(off), on_check(on_check_func), on_update(on_update_func),
   is_os_charset(FALSE)
 {
+  /*
+    There is a limitation in handle_options() related to short options:
+    - either all short options should be declared when parsing in multiple stages,
+    - or none should be declared.
+    Because a lot of short options are used in the normal parsing phase
+    for mysqld, we enforce here that no short option is present
+    in the first (PARSE_EARLY) stage.
+    See handle_options() for details.
+  */
+  DBUG_ASSERT(parse_flag == PARSE_NORMAL || getopt_id <= 0 || getopt_id >= 255);
+
   name.str= name_arg;
   name.length= strlen(name_arg);
   DBUG_ASSERT(name.length <= NAME_CHAR_LEN);
-- 
2.30.9