summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAleksey Midenkov <midenok@gmail.com>2022-12-19 23:15:10 +0300
committerAleksey Midenkov <midenok@gmail.com>2022-12-22 15:46:25 +0300
commitb9ce66327c4bb847e91aa99d84f8bfe68c9e5dc0 (patch)
tree9776b2e56d2a1cc5f436d1f0c4a3d69af878a6d7
parentc194db34d93d8d94bd52b17349063fa401e3f942 (diff)
downloadmariadb-git-b9ce66327c4bb847e91aa99d84f8bfe68c9e5dc0.tar.gz
MDEV-30281 MTR options from .cnf file
MTR options are read from standard MariaDB conf file from [mtr] section. Command-line options take precedence over configuration file. There are two variants of config file location: 1. Default behaviour to look for global, server and user configs as described in https://mariadb.com/kb/en/configuring-mariadb-with-option-files/ 2. Custom config location specified by MTR_CONFIG environment variable. None of other config files are read in this case. If there is wrong setting in configuration file MTR fails and displays the error message. It is clear from the error message from what source the wrong option comes: config file or command-line. The config file name and the line number is not printed which is the subject for further improvement. Implementation is based on CPAN module MySQL::Config because of its compatibility with Getopt::Long. The module merges parsed config into @ARGV array for further validation in Getopt::Long. The module was changed to suite the task's needs: - Merge into beginning of the array instead of the end because command-line options take precedence. Config options are separated from command-line options by ---end-of-config--- which is used as a marker for the source of failed options after Getopt::Long; - Improved behaviour for finding conf files. There was global config, but no server config. There was no ability to specify config file explicitly, now there is; - Same config file is not processed twice (f.ex. when MARIADB_HOME=/etc); - Some not needed code was removed (parse_defaults()); - Argument-less options are processed differently. In the original module they were concatenated with =1. That doesn't work for MTR options, argument-less options are passed as is; - The module doesn't support !include and !includedir directives, they are just ignored (and any strings starting with !). Look at Config::Extend::MySQL for the example of how this can be implemented.
-rw-r--r--mysql-test/lib/My/File/Config.pm264
-rwxr-xr-xmysql-test/mariadb-test-run.pl23
2 files changed, 285 insertions, 2 deletions
diff --git a/mysql-test/lib/My/File/Config.pm b/mysql-test/lib/My/File/Config.pm
new file mode 100644
index 00000000000..1fd0b68a78c
--- /dev/null
+++ b/mysql-test/lib/My/File/Config.pm
@@ -0,0 +1,264 @@
+package My::File::Config;
+
+# ----------------------------------------------------------------------
+# My::File::Config - Parse and utilize MariaDB's /etc/my.cnf and ~/.my.cnf files
+#
+# Based on MySQL::Config 1.04
+#
+# Copyright (c) 2003 Darren Chamberlain <darren@cpan.org>
+# Copyright (c) 2022 MariaDB Corporation
+#
+# ----------------------------------------------------------------------
+
+use strict;
+use base qw(Exporter);
+use vars qw($VERSION @GLOBAL_CNF @SERVER_CNF @EXPORT);
+
+use Carp qw(carp);
+use File::Spec;
+use Data::Dumper;
+
+$VERSION = '1.04-mariadb0';
+@GLOBAL_CNF = ("/etc/%s.cnf", "/etc/mysql/%s.cnf") unless @GLOBAL_CNF;
+if (!@SERVER_CNF) {
+ for (qw'MARIADB_HOME MYSQL_HOME') {
+ if (exists $ENV{$_}) {
+ push @SERVER_CNF, "$ENV{$_}/%s.cnf";
+ }
+ }
+}
+@EXPORT = qw(load_defaults);
+
+# ======================================================================
+# --- Public Functions ---
+# ======================================================================
+
+sub load_defaults {
+ my ($conf_file, $user_cnf, $groups, $argc, $argv);
+ if (! ref $_[0]) {
+ ($conf_file, $groups, $argc, $argv) = @_;
+ } elsif (ref $_[0] eq 'HASH') {
+ $conf_file= $_[0]->{conf_name};
+ $user_cnf= $_[0]->{conf_file};
+ $groups= $_[0]->{groups};
+ $argc= $_[0]->{argc};
+ $argv= $_[0]->{argv};
+ }
+
+ my ($ini, $field, @argv, %parsed);
+ $ini = [ ];
+
+ # ------------------------------------------------------------------
+ # Sanity checking:
+ # * $conf_file should be a string, defaults to "my"
+ # * $groups should be a ref to an array
+ # * $argc should be a ref to a scalar
+ # * $argv should be a ref to an array
+ # ------------------------------------------------------------------
+ if (!defined $user_cnf) {
+ $conf_file = "my" unless defined $conf_file && ! ref($conf_file);
+ $user_cnf = File::Spec->catfile($ENV{HOME}, ".${conf_file}.cnf");
+ }
+
+ $groups = [ $groups ]
+ unless ref $groups eq 'ARRAY';
+
+ if (defined $argc) {
+ $argc = \$argc
+ unless ref $argc eq 'SCALAR';
+ }
+ else {
+ $argc = \(my $i = 0);
+ }
+
+ $argv = \@ARGV unless defined $argv;
+ $argv = [ $argv ]
+ unless ref $argv eq 'ARRAY';
+
+ # ------------------------------------------------------------------
+ # Parse the global, server config and user's config
+ # ------------------------------------------------------------------
+ if (defined $conf_file) {
+ for (@GLOBAL_CNF, @SERVER_CNF) {
+ next if $parsed{$_};
+ $parsed{$_} = 1;
+ last if defined _parse($ini, sprintf $_, $conf_file);
+ }
+ }
+ if (!$parsed{$user_cnf}) {
+ _parse($ini, $user_cnf);
+ }
+
+ # ------------------------------------------------------------------
+ # Pull out the appropriate pieces, based on @$groups
+ # ------------------------------------------------------------------
+ @$groups = map { $_->[0] } @$ini unless @$groups;
+ $groups = join '|', map { quotemeta($_) } @$groups;
+
+ # print Dumper($ini);
+ push @argv, map { $$argc++; "--". $_->[1]. (defined $_->[2] ? "=". $_->[2] : "") }
+ grep { $_->[0] =~ /^$groups$/ } @$ini;
+ @$argv = (@argv, "---end-of-config---", @$argv);
+
+ 1;
+}
+
+# ======================================================================
+# --- Private Functions ---
+# ======================================================================
+
+# ----------------------------------------------------------------------
+# _parse($file)
+#
+# Parses an ini-style file into an array of [ group, name, value ]
+# array refs.
+# ----------------------------------------------------------------------
+sub _parse {
+ my $ini = shift;
+ my $file = shift;
+ my $current;
+ local ($_, *INI);
+ # print ("+++Parsing $file\n");
+
+ return undef unless -f $file && -r _;
+
+ $ini ||= [ ];
+ unless (open INI, $file) {
+ carp "Couldn't open $file: $!";
+ return undef;
+ }
+ while (<INI>) {
+ s/[;#].*$//;
+ s/^\s*//;
+ s/\s*$//;
+ s/^\!.*$//;
+
+ next unless length;
+
+ /^\s*\[(.*)\]\s*$/
+ and $current = $1, next;
+
+ my ($n, $v) = split /\s*=\s*/, $_, 2;
+ if ($v =~ /\s/) {
+ $v =~ s/"/\\"/g;
+ $v = qq("$v")
+ }
+
+ push @$ini, [ $current, $n, $v ];
+ }
+
+ unless (close INI) {
+ carp "Couldn't close $file: $!";
+ }
+
+ return $ini;
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+My::File::Config - Parse and utilize MariaDB's /etc/my.cnf and ~/.my.cnf files
+
+=head1 SYNOPSIS
+
+ use MySQL::Config;
+
+ my @groups = qw(client myclient);
+ my $argc = 0;
+ my @argv = ();
+
+ load_defaults "my", \@groups, \$argc, \@argv;
+
+=head1 DESCRIPTION
+
+C<My::File::Config> emulates the C<load_defaults> function from
+F<libmysqlclient>. Just like C<load_defaults>, it will fill an aray
+with long options, ready to be parsed by C<getopt_long>, a.k.a.
+C<Getopt::Long>.
+
+=head1 THE my.cnf FILE
+
+MySQL's F<my.cnf> file is a mechanism for storing and reusing command
+line arguments. These command line arguments are grouped into
+I<groups> using a simple INI-style format:
+
+ ; file: ~/.my.cnf
+
+ [client]
+ user = darren
+ host = db1
+ pager = less -SignMEX
+
+ [mytop]
+ color = 1
+ header = 0
+
+Each element in C<[>, C<]> pairs is a I<group>, and each call to
+C<load_defaults> will specify 0 or more groups from which to grab
+options. For example, grabbing the I<client> group from the above
+config file would return the I<user>, I<host>, and I<pager> items.
+These items will be formatted as command line options, e.g.,
+I<--user=darren>.
+
+=head1 USING My::File::Config
+
+=head2 load_defaults("name", \@groups, \$count, \@ary)
+
+C<load_defaults> takes 4 arguments: a string denoting the name of the
+config file (which should generally be I<my>); a reference to an array
+of groups from which options should be returned; a reference to a
+scalar that will hold the total number of parsed elements; and a
+reference to an array that will hold the final versions of the
+extracted name, value pairs. This final array will be in a format
+suitable for processing with C<Getopt::Long>:
+
+ --user=username
+ --password=password
+
+and so on.
+
+If the final array reference is missing, C<@ARGV> will be used. Options
+will be pushed onto the end of the array, leaving what is already in
+place undisturbed.
+
+The scalar (the third argument to C<load_defaults>) will contain the
+number of elements parsed from the config files.
+
+=head1 USING SOMETHING OTHER THAN "my" AS THE FIRST STRING
+
+This string controls the name of the configuration file; the names
+work out to, basically F<~/.${cfg_name}.cnf> and
+F</etc/${cnf_name}.cnf>.
+
+If you are using this module for mysql clients, then this should
+probably remain I<my>. Otherwise, you are free to mangle this however
+you choose:
+
+ $ini = parse_defaults 'superapp', [ 'foo' ];
+
+=head1 SUPPORT
+
+C<My::File::Config> is supported by the author.
+
+=head1 VERSION
+
+This is C<My::File::Config>, version 1.04.
+
+=head1 AUTHOR
+
+Darren Chamberlain E<lt>darren@cpan.orgE<gt>
+
+=head1 COPYRIGHT
+
+(c) 2003 Darren Chamberlain
+(c) 2022 MariaDB Corporation
+
+This library is free software; you may distribute it and/or modify it
+under the same terms as Perl itself.
+
+=head1 SEE ALSO
+
+L<Perl>
diff --git a/mysql-test/mariadb-test-run.pl b/mysql-test/mariadb-test-run.pl
index db1ebdccfe8..91df58db13d 100755
--- a/mysql-test/mariadb-test-run.pl
+++ b/mysql-test/mariadb-test-run.pl
@@ -80,6 +80,7 @@ use Cwd ;
use POSIX ":sys_wait_h";
use Getopt::Long qw(:config bundling);
use My::File::Path; # Patched version of File::Path
+use My::File::Config;
use File::Basename;
use File::Copy;
use File::Find;
@@ -1210,8 +1211,16 @@ sub command_line_setup {
My::CoreDump::options()
);
- # fix options (that take an optional argument and *only* after = sign
+ # 1. Merge config file into @ARGV. Options from command-line take precedence,
+ # they come after the config file options.
+ My::File::Config::load_defaults({
+ conf_file => $ENV{MTR_CONFIG},
+ groups => ['mtr']});
+
+ # 2. Fix options (that take an optional argument and *only* after = sign
@ARGV = My::Debugger::fix_options(@ARGV);
+
+ # 3. Run GetOptions() on the merged config (config file + command-line)
GetOptions(%options) or usage("Can't read options");
usage("") if $opt_usage;
list_options(\%options) if $opt_list_options;
@@ -1351,6 +1360,7 @@ sub command_line_setup {
}
}
+ my $opt_source= (grep { /^---end-of-config---$/ } @ARGV) ? 'config file' : 'command-line';
foreach my $arg ( @ARGV )
{
if ( $arg =~ /^--skip-/ )
@@ -1363,9 +1373,18 @@ sub command_line_setup {
# that the lone '--' separating options from arguments survives,
# simply ignore it.
}
+ elsif ( $arg =~ /^---end-of-config---$/ )
+ {
+ $opt_source= 'command-line';
+ }
elsif ( $arg =~ /^-/ )
{
- usage("Invalid option \"$arg\"");
+ if ($opt_source eq 'config file')
+ {
+ $arg=~ m/^--(.+)=?/;
+ $arg= $1;
+ }
+ usage("Invalid ${opt_source} option \"${arg}\"");
}
else
{