diff options
author | Aleksey Midenkov <midenok@gmail.com> | 2022-12-19 23:15:10 +0300 |
---|---|---|
committer | Aleksey Midenkov <midenok@gmail.com> | 2022-12-22 15:46:25 +0300 |
commit | b9ce66327c4bb847e91aa99d84f8bfe68c9e5dc0 (patch) | |
tree | 9776b2e56d2a1cc5f436d1f0c4a3d69af878a6d7 | |
parent | c194db34d93d8d94bd52b17349063fa401e3f942 (diff) | |
download | mariadb-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.pm | 264 | ||||
-rwxr-xr-x | mysql-test/mariadb-test-run.pl | 23 |
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 { |