1#!/usr/bin/env perl
2
3
4use strict;
5use warnings;
6
7my $usage= <<EOF;
8This program tests that the options
9--aria-force-start-after-recovery-failures --aria-recover work as
10expected.
11It has to be run from directory mysql-test, and works with non-debug
12and debug binaries.
13Pass it option -d or -i (to test corruption of data or index file).
14EOF
15
16# -d currently exhibits BUG#36578
17# "Maria: maria-recover may fail to autorepair a table"
18
19die($usage) if (@ARGV == 0);
20
21my $corrupt_index;
22
23if ($ARGV[0] eq '-d')
24  {
25    $corrupt_index= 0;
26  }
27elsif ($ARGV[0] eq '-i')
28  {
29    $corrupt_index= 1;
30  }
31else
32  {
33    die($usage);
34  }
35
36my $force_after= 3;
37my $corrupt_file= $corrupt_index ? "MAI" : "MAD";
38my $corrupt_message=
39  "\\[ERROR\\] mysqld(.exe)*: Table '..test.t1' is marked as crashed and should be repaired";
40
41my $sql_name= "./var/tmp/create_table.sql";
42my $error_log_name= "./var/log/master.err";
43my @cmd_output;
44my $whatever; # garbage data
45$ENV{MTR_VERSION} = 1; # MTR2 does not have --start-and-exit
46my $base_server_cmd= "perl mysql-test-run.pl --mysqld=--aria-force-start-after-recovery-failures=$force_after --suite=maria maria.maria-recover ";
47if ($^O =~ /^mswin/i)
48  {
49    print <<EOF;
50WARNING: with Activestate Perl, mysql-test-run.pl --start-and-exit has a bug:
51it does not exit; cygwin perl recommended
52EOF
53  }
54my $iswindows= ( $^O =~ /win/i  && $^O !~ /darwin/i );
55$base_server_cmd.= ($iswindows ? "--mysqld=--console" : "--mem");
56my $server_cmd;
57my $server_pid_name="./var/run/master.pid";
58my $server_pid;
59my $i; # count of server restarts
60sub kill_server;
61
62my $suffix= ($iswindows ? ".exe" : "");
63my $client_exe_path= "../client/release";
64# we use -f, sometimes -x is unexpectedly false in Cygwin
65if ( ! -f "$client_exe_path/mysql$suffix" )
66  {
67    $client_exe_path= "../client/relwithdebinfo";
68    if ( ! -f "$client_exe_path/mysql$suffix" )
69    {
70      $client_exe_path= "../client/debug";
71      if ( ! -f "$client_exe_path/mysql$suffix" )
72      {
73        $client_exe_path= "../client";
74        if ( ! -f "$client_exe_path/mysql$suffix" )
75        {
76          die("Cannot find 'mysql' executable\n");
77        }
78      }
79    }
80  }
81
82print "starting mysqld\n";
83$server_cmd= $base_server_cmd . " --start-and-exit 2>&1";
84@cmd_output=`$server_cmd`;
85die if $?;
86my $master_port= (grep (/Using MASTER_MYPORT .*= (\d+)$/, @cmd_output))[0];
87$master_port =~ s/.*= //;
88chomp $master_port;
89die unless $master_port > 0;
90
91my $client_cmd= "$client_exe_path/mysql -u root -h 127.0.0.1 -P $master_port test < $sql_name";
92
93open(FILE, ">", $sql_name) or die;
94
95# To exhibit BUG#36578 with -d, we don't create an index if -d. This is
96# because the presence of an index will cause repair-by-sort to be used,
97# where sort_get_next_record() is only called inside
98#_ma_create_index_by_sort(), so the latter function fails and in this
99# case retry_repair is set, so bug does not happen. Whereas without
100# an index, repair-with-key-cache is called, which calls
101# sort_get_next_record() whose failure itself does not cause a retry.
102
103print FILE "create table t1 (a varchar(1000)".
104  ($corrupt_index ? ", index(a)" : "") .") engine=aria;\n";
105print FILE <<EOF;
106insert into t1 values("ThursdayMorningsMarket");
107# If Recovery executes REDO_INDEX_NEW_PAGE it will overwrite our
108# intentional corruption; we make Recovery skip this record by bumping
109# create_rename_lsn using OPTIMIZE TABLE. This also makes sure to put
110# the pages on disk, so that we can corrupt them.
111optimize table t1;
112# mark table open, so that --aria-recover repairs it
113insert into t1 select concat(a,'b') from t1 limit 1;
114EOF
115close FILE;
116
117print "creating table\n";
118`$client_cmd`;
119die if $?;
120
121print "killing mysqld hard\n";
122kill_server(9);
123
124print "ruining " .
125  ($corrupt_index ? "first page of keys" : "bitmap page") .
126  " in table to test aria-recover\n";
127open(FILE, "+<", "./var/master-data/test/t1.$corrupt_file") or die;
128$whatever= ("\xAB" x 100);
129sysseek (FILE, $corrupt_index ? 8192 : (8192-100-100), 0) or die;
130syswrite (FILE, $whatever) or die;
131close FILE;
132
133print "ruining log to make recovery fail; mysqld should fail the $force_after first restarts\n";
134open(FILE, "+<", "./var/tmp/aria_log.00000001") or die;
135$whatever= ("\xAB" x 8192);
136sysseek (FILE, 99, 0) or die;
137syswrite (FILE, $whatever) or die;
138close FILE;
139
140$server_cmd= $base_server_cmd . " --start-dirty 2>&1";
141for($i= 1; $i <= $force_after; $i= $i + 1)
142  {
143    print "mysqld restart number $i... ";
144    unlink($error_log_name) or die;
145    `$server_cmd`;
146    # mysqld should return 1 when can't read log
147    die unless (($? >> 8) == 1);
148    open(FILE, "<", $error_log_name) or die;
149    @cmd_output= <FILE>;
150    close FILE;
151    die unless grep(/\[ERROR\] mysqld(.exe)*: Aria engine: log initialization failed/, @cmd_output);
152    die unless grep(/\[ERROR\] Plugin 'Aria' init function returned error./, @cmd_output);
153    print "failed - ok\n";
154  }
155
156print "mysqld restart number $i... ";
157unlink($error_log_name) or die;
158@cmd_output=`$server_cmd`;
159die if $?;
160open(FILE, "<", $error_log_name) or die;
161@cmd_output= <FILE>;
162close FILE;
163die unless grep(/\[Warning\] mysqld(.exe)*: Aria engine: removed all logs after [\d]+ consecutive failures of recovery from logs/, @cmd_output);
164die unless grep(/\[ERROR\] mysqld(.exe)*: File '.*tmp.aria_log.00000001' not found \(Errcode: 2\)/, @cmd_output);
165print "success - ok\n";
166
167open(FILE, ">", $sql_name) or die;
168print FILE <<EOF;
169set global aria_recover=normal;
170insert into t1 values('aaa');
171EOF
172close FILE;
173
174# verify corruption has not yet been noticed
175open(FILE, "<", $error_log_name) or die;
176@cmd_output= <FILE>;
177close FILE;
178die if grep(/$corrupt_message/, @cmd_output);
179
180print "inserting in table\n";
181`$client_cmd`;
182die if $?;
183print "table is usable - ok\n";
184
185open(FILE, "<", $error_log_name) or die;
186@cmd_output= <FILE>;
187close FILE;
188die unless grep(/$corrupt_message/, @cmd_output);
189die unless grep(/\[Warning\] Recovering table: '..test.t1'/, @cmd_output);
190print "was corrupted and automatically repaired - ok\n";
191
192# remove our traces
193kill_server(15);
194
195print "TEST ALL OK\n";
196
197# kills mysqld with signal given in parameter
198sub kill_server
199  {
200    my ($sig)= @_;
201    my $wait_count= 0;
202    my $kill_cmd;
203    my @kill_output;
204    open(FILE, "<", $server_pid_name) or die;
205    @cmd_output= <FILE>;
206    close FILE;
207    $server_pid= $cmd_output[0];
208    chomp $server_pid;
209    die unless $server_pid > 0;
210    if ($iswindows)
211      {
212        # On Windows, server_pid_name is not the "main" process id
213        # so perl's kill() does not see this process id.
214        # But taskkill works, though only with /F ("-9"-style kill).
215        $kill_cmd= "taskkill /F /PID $server_pid 2>&1";
216        @kill_output= `$kill_cmd`;
217        die unless grep(/has been terminated/, @kill_output);
218      }
219    else
220      {
221        kill($sig, $server_pid) or die;
222      }
223    while (1) # wait until mysqld process gone
224      {
225        if ($iswindows)
226          {
227            @kill_output= `$kill_cmd`;
228            last if grep(/not found/, @kill_output);
229          }
230        else
231          {
232            kill (0, $server_pid) or last;
233          }
234        print "waiting for mysqld to die\n" if ($wait_count > 30);
235        $wait_count= $wait_count + 1;
236        select(undef, undef, undef, 0.1);
237      }
238  }
239