From b90b0c7eb28993ca1abe5e288702efb309ba27a2 Mon Sep 17 00:00:00 2001
From: "tsmith@quadxeon.mysql.com" <>
Date: Sat, 28 Apr 2007 01:27:54 +0200
Subject: [PATCH] Bug #27390: mysqld_multi --config-file= not working as
 documented

Recognize the --no-defaults, --defaults-file and --defaults-extra-file
options.  Treat old --config-file argument as if --defaults-extra-file
had been specified instead.

Plus a few other defaults-related cleanups.
---
 extra/my_print_defaults.c |  23 ++-
 mysys/default.c           |   2 +-
 scripts/mysqld_multi.sh   | 312 ++++++++++++++++----------------------
 3 files changed, 146 insertions(+), 191 deletions(-)

diff --git a/extra/my_print_defaults.c b/extra/my_print_defaults.c
index eb077f91ece..f5f7e68c9e6 100644
--- a/extra/my_print_defaults.c
+++ b/extra/my_print_defaults.c
@@ -33,7 +33,20 @@ const char *default_dbug_option="d:t:o,/tmp/my_print_defaults.trace";
 
 static struct my_option my_long_options[] =
 {
-  {"config-file", 'c', "The config file to be used.",
+  /*
+    NB: --config-file is troublesome, because get_defaults_options() doesn't
+    know about it, but we pretend --config-file is like --defaults-file.  In
+    fact they behave differently: see the comments at the top of
+    mysys/default.c for how --defaults-file should behave.
+
+    This --config-file option behaves as:
+    - If it has a directory name part (absolute or relative), then only this
+      file is read; no error is given if the file doesn't exist
+    - If the file has no directory name part, the standard locations are
+      searched for a file of this name (and standard filename extensions are
+      added if the file has no extension)
+  */
+  {"config-file", 'c', "Deprecated, please use --defaults-file instead.  Name of config file to read; if no extension is given, default extension (e.g., .ini or .cnf) will be added",
    (gptr*) &config_file, (gptr*) &config_file, 0, GET_STR, REQUIRED_ARG,
    0, 0, 0, 0, 0, 0},
 #ifdef DBUG_OFF
@@ -43,11 +56,11 @@ static struct my_option my_long_options[] =
   {"debug", '#', "Output debug log", (gptr*) &default_dbug_option,
    (gptr*) &default_dbug_option, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0},
 #endif
-  {"defaults-file", 'c', "Synonym for --config-file.",
+  {"defaults-file", 'c', "Like --config-file, except: if first option, then read this file only, do not read global or per-user config files; should be the first option",
    (gptr*) &config_file, (gptr*) &config_file, 0, GET_STR, REQUIRED_ARG,
    0, 0, 0, 0, 0, 0},
   {"defaults-extra-file", 'e',
-   "Read this file after the global /etc config file and before the config file in the users home directory.",
+   "Read this file after the global config file and before the config file in the users home directory; should be the first option",
    (gptr*) &my_defaults_extra_file, (gptr*) &my_defaults_extra_file, 0,
    GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
   {"defaults-group-suffix", 'g',
@@ -55,7 +68,7 @@ static struct my_option my_long_options[] =
    (gptr*) &my_defaults_group_suffix, (gptr*) &my_defaults_group_suffix,
    0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
   {"extra-file", 'e',
-   "Synonym for --defaults-extra-file.",
+   "Deprecated. Synonym for --defaults-extra-file.",
    (gptr*) &my_defaults_extra_file,
    (gptr*) &my_defaults_extra_file, 0, GET_STR,
    REQUIRED_ARG, 0, 0, 0, 0, 0, 0},
@@ -86,7 +99,7 @@ static void usage(my_bool version)
   my_print_help(my_long_options);
   my_print_default_files(config_file);
   my_print_variables(my_long_options);
-  printf("\nExample usage:\n%s --config-file=my client mysql\n", my_progname);
+  printf("\nExample usage:\n%s --defaults-file=example.cnf client mysql\n", my_progname);
 }
 
 #include <help_end.h>
diff --git a/mysys/default.c b/mysys/default.c
index dc1c5a698b8..aff38b6af0b 100644
--- a/mysys/default.c
+++ b/mysys/default.c
@@ -877,8 +877,8 @@ void my_print_default_files(const char *conf_file)
 	fputs(name,stdout);
       }
     }
-    puts("");
   }
+  puts("");
 }
 
 void print_defaults(const char *conf_file, const char **groups)
diff --git a/scripts/mysqld_multi.sh b/scripts/mysqld_multi.sh
index a664bcfd3ad..92cfbe3ef22 100644
--- a/scripts/mysqld_multi.sh
+++ b/scripts/mysqld_multi.sh
@@ -4,9 +4,10 @@ use Getopt::Long;
 use POSIX qw(strftime);
 
 $|=1;
-$VER="2.15";
+$VER="2.16";
+
+my @defaults_options;   #  Leading --no-defaults, --defaults-file, etc.
 
-$opt_config_file   = undef();
 $opt_example       = 0;
 $opt_help          = 0;
 $opt_log           = undef();
@@ -49,54 +50,52 @@ sub main
     print "MySQL distribution.\n";
     $my_print_defaults_exists= 0;
   }
-  if ($my_print_defaults_exists)
+
+  # Remove leading defaults options from @ARGV
+  while (@ARGV > 0)
+  {
+    last unless $ARGV[0] =~
+      /^--(?:no-defaults$|(?:defaults-file|defaults-extra-file)=)/;
+    push @defaults_options, (shift @ARGV);
+  }
+
+  # Handle deprecated --config-file option: convert to --defaults-extra-file
+  foreach my $arg (@ARGV)
   {
-    foreach my $arg (@ARGV)
+    if ($arg =~ m/^--config-file=(.*)/)
     {
-      if ($arg =~ m/^--config-file=(.*)/)
-      {
-	if (!length($1))
-	{
-	  die "Option config-file requires an argument\n";
-	}
-	elsif (!( -e $1 && -r $1))
-	{
-	  die "Option file '$1' doesn't exists, or is not readable\n";
-	}
-	else
-	{
-	  $opt_config_file= $1;
-	  if (!($opt_config_file =~ m/\//))
-	  {
-	    # No path. Use current working directory
-	    $opt_config_file= "./" . $opt_config_file;
-	  }
-	}
-      }
+      # Put it at the beginning of the list, so it has lower precedence
+      # than a correct --defaults-extra-file option
+
+      unshift @defaults_options, "--defaults-extra-file=$1";
     }
-    my $com= "my_print_defaults ";
-    $com.= "--config-file=$opt_config_file " if (defined($opt_config_file));
-    $com.= "mysqld_multi";
-    my @defops = `$com`;
-    chop @defops;
-    splice @ARGV, 0, 0, @defops;
   }
-  if (!GetOptions("help","example","version","mysqld=s","mysqladmin=s",
-		  "config-file=s","user=s","password=s","log=s","no-log",
-		  "tcp-ip", "silent","verbose"))
+
+  foreach (@defaults_options)
   {
-    $flag_exit= 1;
+    $_ = quote_shell_word($_);
   }
-  if (defined($opt_config_file) && !($opt_config_file =~ m/\//))
+
+  # Add [mysqld_multi] options to front of @ARGV, ready for GetOptions()
+  unshift @ARGV, defaults_for_group('mysqld_multi');
+
+  # The --config-file option can be ignored; if passed on the command
+  # line, it's already handled; if specified in the configuration file,
+  # it's redundant and not useful
+  @ARGV= grep { not /^--config-file=/ } @ARGV;
+
+  # We've already handled --no-defaults, --defaults-file, etc.
+  if (!GetOptions("help", "example", "version", "mysqld=s", "mysqladmin=s",
+                  "user=s", "password=s", "log=s", "no-log",
+                  "tcp-ip",  "silent", "verbose"))
   {
-    # No path. Use current working directory
-    $opt_config_file= "./" . $opt_config_file;
+    $flag_exit= 1;
   }
   usage() if ($opt_help);
 
   if ($opt_verbose && $opt_silent)
   {
-    print "Both --verbose and --silent has been given. Some of the warnings ";
+    print "Both --verbose and --silent have been given. Some of the warnings ";
     print "will be disabled\nand some will be enabled.\n\n";
   }
 
@@ -168,51 +167,42 @@ sub main
   }
 }
 
-####
-#### Quote option argument. Add double quotes around the argument
-#### and escape the following: $, \, "
-#### This function is needed, because my_print_defaults drops possible
-#### quotes, single or double, from in front of an argument and from
-#### the end.
-####
+#
+# Quote word for shell
+#
 
-sub quote_opt_arg
+sub quote_shell_word
 {
   my ($option)= @_;
 
-  if ($option =~ m/(\-\-[a-zA-Z0-9\_\-]+)=(.*)/)
-  {
-    $option= $1;
-    $arg= $2;
-    $arg=~ s/\\/\\\\/g; # Escape escape character first to avoid doubling.
-    $arg=~ s/\$/\\\$/g;
-    $arg=~ s/\"/\\\"/g;
-    $arg= "\"" . $arg . "\"";
-    $option= $option . "=" . $arg;
-  }
+  $option =~ s!([^\w=./-])!\\$1!g;
   return $option;
 }
 
+sub defaults_for_group
+{
+  my ($group) = @_;
+
+  return () unless $my_print_defaults_exists;
+
+  my $com= join ' ', 'my_print_defaults', @defaults_options, $group;
+  my @defaults = `$com`;
+  chomp @defaults;
+  return @defaults;
+}
+
 ####
 #### Init log file. Check for appropriate place for log file, in the following
-#### order my_print_defaults mysqld datadir, @datadir@, /var/log, /tmp
+#### order:  my_print_defaults mysqld datadir, @datadir@
 ####
 
 sub init_log
 {
-  if ($my_print_defaults_exists)
+  foreach my $opt (defaults_for_group('mysqld'))
   {
-    @mysqld_opts= `my_print_defaults mysqld`;
-    chomp @mysqld_opts;
-    foreach my $opt (@mysqld_opts)
+    if ($opt =~ m/^--datadir=(.*)/ && -d "$1" && -w "$1")
     {
-      if ($opt =~ m/^\-\-datadir[=](.*)/)
-      {
-        if (-d "$1" && -w "$1")
-        {
-	  $logdir= $1;
-        }
-      }
+      $logdir= $1;
     }
   }
   if (!defined($logdir))
@@ -303,11 +293,7 @@ sub start_mysqlds()
   @groups = &find_groups($groupids);
   for ($i = 0; defined($groups[$i]); $i++)
   {
-    $com = "my_print_defaults";
-    $com.= defined($opt_config_file) ? " --config-file=$opt_config_file" : "";
-    $com.= " $groups[$i]";
-    @options = `$com`;
-    chop @options;
+    @options = defaults_for_group($groups[$i]);
 
     $mysqld_found= 1; # The default
     $mysqld_found= 0 if (!length($mysqld));
@@ -326,7 +312,7 @@ sub start_mysqlds()
       }
       else
       {
-	$options[$j]= quote_opt_arg($options[$j]);
+	$options[$j]= quote_shell_word($options[$j]);
 	$tmp.= " $options[$j]";
       }
     }
@@ -401,11 +387,7 @@ sub get_mysqladmin_options
   my ($i, @groups)= @_;
   my ($mysqladmin_found, $com, $tmp, $j);
 
-  $com = "my_print_defaults";
-  $com.= defined($opt_config_file) ? " --config-file=$opt_config_file" : "";
-  $com.= " $groups[$i]";
-  @options = `$com`;
-  chop @options;
+  @options = defaults_for_group($groups[$i]);
 
   $mysqladmin_found= 1; # The default
   $mysqladmin_found= 0 if (!length($mysqladmin));
@@ -445,129 +427,81 @@ sub get_mysqladmin_options
   return $com;
 }
 
-####
-#### Find groups. Takes the valid group numbers as an argument, parses
-#### them, puts them in the ascending order, removes duplicates and
-#### returns the wanted groups accordingly.
-####
+# Return a list of option files which can be opened.  Similar, but not
+# identical, to behavior of my_search_option_files()
+sub list_defaults_files
+{
+  my %opt;
+  foreach (@defaults_options)
+  {
+    return () if /^--no-defaults$/;
+    $opt{$1} = $2 if /^--defaults-(extra-file|file)=(.*)$/;
+  }
+
+  return ($opt{file}) if exists $opt{file};
+
+  my %seen;  # Don't list the same file more than once
+  return grep { defined $_ and not $seen{$_}++ and -f $_ and -r $_ }
+              ('/etc/my.cnf',
+               '/etc/mysql/my.cnf',
+               '@sysconfdir@/my.cnf',
+               ($ENV{MYSQL_HOME} ? "$ENV{MYSQL_HOME}/my.cnf" : undef),
+               $opt{'extra-file'},
+               ($ENV{HOME} ? "$ENV{HOME}/.my.cnf" : undef));
+}
+
 
+# Takes a specification of GNRs (see --help), and returns a list of matching
+# groups which actually are mentioned in a relevant config file
 sub find_groups
 {
   my ($raw_gids) = @_;
-  my (@groups, @data, @tmp, $line, $i, $k, @pre_gids, @gids, @tmp2,
-      $prev_value);
 
-  # Read the lines from the config file to variable 'data'
-  if (defined($opt_config_file))
-  {
-    open(MY_CNF, "<$opt_config_file") && (@data=<MY_CNF>) && close(MY_CNF);
-  }
-  else
-  {
-    if (-f "@sysconfdir@/my.cnf" && -r "@sysconfdir@/my.cnf")
-    {
-      open(MY_CNF, "<@sysconfdir@/my.cnf") && (@tmp=<MY_CNF>) && close(MY_CNF);
-    } elsif (-f "/etc/my.cnf" && -r "/etc/my.cnf")
-    {
-      open(MY_CNF, "</etc/my.cnf") && (@tmp=<MY_CNF>) && close(MY_CNF);
-    }
-    for ($i = 0; ($line = shift @tmp); $i++)
-    {
-      $data[$i] = $line;
-    }
-    if (-f "/etc/mysql/my.cnf" && -r "/etc/mysql/my.cnf")
-    {
-      open(MY_CNF, "</etc/mysql/my.cnf") && (@tmp=<MY_CNF>) && close(MY_CNF);
-    }
-    for (; ($line = shift @tmp); $i++)
-    {
-      $data[$i] = $line;
-    }
-    if (defined($ENV{MYSQL_HOME}) && -f "$ENV{MYSQL_HOME}/my.cnf" &&
-	-r "$ENV{MYSQL_HOME}/my.cnf")
-    {
-      open(MY_CNF, "<$ENV{MYSQL_HOME}/my.cnf") && (@tmp=<MY_CNF>) &&
-      close(MY_CNF);
-    }
-    for (; ($line = shift @tmp); $i++)
-    {
-      $data[$i] = $line;
-    }
-    if (-f "$homedir/.my.cnf" && -r "$homedir/.my.cnf")
-    {
-      open(MY_CNF, "<$homedir/.my.cnf") && (@tmp=<MY_CNF>) && close(MY_CNF);
-    }
-    for (; ($line = shift @tmp); $i++)
-    {
-      $data[$i] = $line;
-    }
-  }
-  chomp @data;
-  # Make a list of the wanted group ids
-  if (defined($raw_gids))
-  {
-    @pre_gids = split(',', $raw_gids);
-  }
+  my %gids;
+  my @groups;
+
   if (defined($raw_gids))
   {
-    for ($i = 0, $j = 0; defined($pre_gids[$i]); $i++)
+    # Make a hash of the wanted group ids
+    foreach my $raw_gid (split ',', $raw_gids)
     {
-      if ($pre_gids[$i] =~ m/^(\d+)$/)
+      # Match 123 or 123-456
+      my ($start, $end) = ($raw_gid =~ /^\s*(\d+)(?:\s*-\s*(\d+))?\s*$/);
+      $end = $start if not defined $end;
+      if (not defined $start or $end < $start or $start < 0)
       {
-	$gids[$j] = $1;
-	$j++;
+        print "ABORT: Bad GNR: $raw_gid; see $my_progname --help\n";
+        exit(1);
       }
-      elsif ($pre_gids[$i] =~ m/^(\d+)(\-)(\d+)$/)
-      {
-	for ($k = $1; $k <= $3; $k++)
-	{
-	  $gids[$j] = $k;
-	  $j++;
-	}
-      }
-      else
+
+      foreach my $i ($start .. $end)
       {
-	print "ABORT: Bad GNR: $pre_gids[$i] See $my_progname --help\n";
-	exit(1);
+        # Use $i + 0 to normalize numbers (002 + 0 -> 2)
+        $gids{$i + 0}= 1;
       }
     }
   }
-  # Sort the list of gids numerically in ascending order
-  @gids = sort {$a <=> $b} @gids;
-  # Remove non-positive integers and duplicates
-  for ($i = 0, $j = 0; defined($gids[$i]); $i++)
-  {
-    next if ($gids[$i] <= 0);
-    if (!$i || $prev_value != $gids[$i])
-    {
-      $tmp2[$j] = $gids[$i];
-      $j++;
-    }
-    $prev_value = $gids[$i];
-  }
-  @gids = @tmp2;
-  # Find and return the wanted groups
-  for ($i = 0, $j = 0; defined($data[$i]); $i++)
+
+  my @defaults_files = list_defaults_files();
+  #warn "@{[sort keys %gids]} -> @defaults_files\n";
+  foreach my $file (@defaults_files)
   {
-    if ($data[$i] =~ m/^(\s*\[\s*)(mysqld)(\d+)(\s*\]\s*)$/)
+    next unless open CONF, "< $file";
+
+    while (<CONF>)
     {
-      if (defined($raw_gids))
-      {
-	for ($k = 0; defined($gids[$k]); $k++)
-	{
-	  if ($gids[$k] == $3)
-	  {
-	    $groups[$j] = $2 . $3;
-	    $j++;
-	  }
-	}
-      }
-      else
+      if (/^\s*\[\s*(mysqld)(\d+)\s*\]\s*$/)
       {
-	$groups[$j] = $2 . $3;
-	$j++;
+        #warn "Found a group: $1$2\n";
+        # Use $2 + 0 to normalize numbers (002 + 0 -> 2)
+        if (not defined($raw_gids) or $gids{$2 + 0})
+        {
+          push @groups, "$1$2";
+        }
       }
     }
+
+    close CONF;
   }
   return @groups;
 }
@@ -806,8 +740,16 @@ groups found will either be started, stopped, or reported. Note that
 syntax for specifying GNRs must appear without spaces.
 
 Options:
---config-file=...  Alternative config file.
-                   Using: $opt_config_file
+
+These options must be given before any others:
+--no-defaults      Do not read any defaults file
+--defaults-file=...  Read only this configuration file, do not read the
+                   standard system-wide and user-specific files
+--defaults-extra-file=...  Read this configuration file in addition to the
+                   standard system-wide and user-specific files
+Using:  @{[join ' ', @defaults_options]}
+
+--config-file=...  Deprecated, please use --defaults-extra-file instead
 --example          Give an example of a config file with extra information.
 --help             Print this help and exit.
 --log=...          Log file. Full path to and the name for the log file. NOTE:
-- 
2.30.9