1#!@PERL_PATH@
2
3# Copyright (c) 2000, 2017, Oracle and/or its affiliates.
4#
5# This program is free software; you can redistribute it and/or
6# modify it under the terms of the GNU Library General Public
7# License as published by the Free Software Foundation; version 2
8# of the License.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13# Library General Public License for more details.
14#
15# You should have received a copy of the GNU Library General Public
16# License along with this library; if not, write to the Free
17# Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
18# MA 02110-1335  USA
19
20use strict;
21use Getopt::Long;
22use Data::Dumper;
23use File::Basename;
24use File::Path;
25use DBI;
26use Sys::Hostname;
27use File::Copy;
28use File::Temp qw(tempfile);
29
30=head1 NAME
31
32mysqlhotcopy - fast on-line hot-backup utility for local MySQL databases and tables
33
34=head1 SYNOPSIS
35
36  mysqlhotcopy db_name
37
38  mysqlhotcopy --suffix=_copy db_name_1 ... db_name_n
39
40  mysqlhotcopy db_name_1 ... db_name_n /path/to/new_directory
41
42  mysqlhotcopy db_name./regex/
43
44  mysqlhotcopy db_name./^\(foo\|bar\)/
45
46  mysqlhotcopy db_name./~regex/
47
48  mysqlhotcopy db_name_1./regex_1/ db_name_1./regex_2/ ... db_name_n./regex_n/ /path/to/new_directory
49
50  mysqlhotcopy --method='scp -Bq -i /usr/home/foo/.ssh/identity' --user=root --password=secretpassword \
51         db_1./^nice_table/ user@some.system.dom:~/path/to/new_directory
52
53WARNING: THIS PROGRAM IS STILL IN BETA. Comments/patches welcome.
54
55=cut
56
57# Documentation continued at end of file
58
59# fix CORE::GLOBAL::die to return a predictable exit code
60BEGIN { *CORE::GLOBAL::die= sub { warn @_; exit 1; }; }
61
62my $VERSION = "1.23";
63
64my $opt_tmpdir = $ENV{TMPDIR} || "/tmp";
65
66my $OPTIONS = <<"_OPTIONS";
67
68$0 Ver $VERSION
69
70Usage: $0 db_name[./table_regex/] [new_db_name | directory]
71
72  -?, --help           display this help-screen and exit
73  -u, --user=#         user for database login if not current user
74  -p, --password=#     password to use when connecting to server (if not set
75                       in my.cnf, which is recommended)
76  -h, --host=#         hostname for local server when connecting over TCP/IP
77  -P, --port=#         port to use when connecting to local server with TCP/IP
78  -S, --socket=#       socket to use when connecting to local server
79      --old_server     connect to old MySQL-server (before v5.5) which
80                       doesn't have FLUSH TABLES WITH READ LOCK fully implemented.
81
82  --allowold           don\'t abort if target dir already exists (rename it _old)
83  --addtodest          don\'t rename target dir if it exists, just add files to it
84  --keepold            don\'t delete previous (now renamed) target when done
85  --noindices          don\'t include full index files in copy
86  --method=#           method for copy (only "cp" currently supported)
87
88  -q, --quiet          be silent except for errors
89  --debug              enable debug
90  -n, --dryrun         report actions without doing them
91
92  --regexp=#           copy all databases with names matching regexp
93  --suffix=#           suffix for names of copied databases
94  --checkpoint=#       insert checkpoint entry into specified db.table
95  --flushlog           flush logs once all tables are locked
96  --resetmaster        reset the binlog once all tables are locked
97  --resetslave         reset the master.info once all tables are locked
98  --tmpdir=#	       temporary directory (instead of $opt_tmpdir)
99  --record_log_pos=#   record slave and master status in specified db.table
100  --chroot=#           base directory of chroot jail in which mysqld operates
101
102  Try \'perldoc $0\' for more complete documentation
103_OPTIONS
104
105sub usage {
106    die @_, $OPTIONS;
107}
108
109# Do not initialize user or password options; that way, any user/password
110# options specified in option files will be used.  If no values are specified
111# at all, the defaults will be used (login name, no password).
112
113my %opt = (
114    noindices	=> 0,
115    allowold	=> 0,	# for safety
116    keepold	=> 0,
117    method	=> "cp",
118    flushlog    => 0,
119);
120Getopt::Long::Configure(qw(no_ignore_case)); # disambiguate -p and -P
121GetOptions( \%opt,
122    "help",
123    "host|h=s",
124    "user|u=s",
125    "password|p=s",
126    "port|P=s",
127    "socket|S=s",
128    "old_server",
129    "allowold!",
130    "keepold!",
131    "addtodest!",
132    "noindices!",
133    "method=s",
134    "debug",
135    "quiet|q",
136    "mv!",
137    "regexp=s",
138    "suffix=s",
139    "checkpoint=s",
140    "record_log_pos=s",
141    "flushlog",
142    "resetmaster",
143    "resetslave",
144    "tmpdir|t=s",
145    "dryrun|n",
146    "chroot=s",
147) or usage("Invalid option");
148
149# @db_desc
150# ==========
151# a list of hash-refs containing:
152#
153#   'src'     - name of the db to copy
154#   't_regex' - regex describing tables in src
155#   'target'  - destination directory of the copy
156#   'tables'  - array-ref to list of tables in the db
157#   'files'   - array-ref to list of files to be copied
158#   'index'   - array-ref to list of indexes to be copied
159#
160
161my @db_desc = ();
162my $tgt_name = undef;
163
164usage("") if ($opt{help});
165
166if ( $opt{regexp} || $opt{suffix} || @ARGV > 2 ) {
167    $tgt_name   = pop @ARGV unless ( exists $opt{suffix} );
168    @db_desc = map { s{^([^\.]+)\./(.+)/$}{$1}; { 'src' => $_, 't_regex' => ( $2 ? $2 : '.*' ) } } @ARGV;
169}
170else {
171    usage("Database name to hotcopy not specified") unless ( @ARGV );
172
173    $ARGV[0] =~ s{^([^\.]+)\./(.+)/$}{$1};
174    @db_desc = ( { 'src' => $ARGV[0], 't_regex' => ( $2 ? $2 : '.*' ) } );
175
176    if ( @ARGV == 2 ) {
177	$tgt_name   = $ARGV[1];
178    }
179    else {
180	$opt{suffix} = "_copy";
181    }
182}
183
184my %mysqld_vars;
185my $start_time = time;
186$opt_tmpdir= $opt{tmpdir} if $opt{tmpdir};
187$0 = $1 if $0 =~ m:/([^/]+)$:;
188$opt{quiet} = 0 if $opt{debug};
189$opt{allowold} = 1 if $opt{keepold};
190
191# --- connect to the database ---
192my $dsn;
193$dsn  = ";host=" . (defined($opt{host}) ? $opt{host} : "localhost");
194$dsn .= ";port=$opt{port}" if $opt{port};
195$dsn .= ";mysql_socket=$opt{socket}" if $opt{socket};
196
197# use mysql_read_default_group=mysqlhotcopy so that [client] and
198# [mysqlhotcopy] groups will be read from standard options files.
199
200my $dbh = DBI->connect("dbi:mysql:$dsn;mysql_read_default_group=mysqlhotcopy",
201                        $opt{user}, $opt{password},
202{
203    RaiseError => 1,
204    PrintError => 0,
205    AutoCommit => 1,
206});
207
208# --- check that checkpoint table exists if specified ---
209if ( $opt{checkpoint} ) {
210    $opt{checkpoint} = quote_names( $opt{checkpoint} );
211    eval { $dbh->do( qq{ select time_stamp, src, dest, msg
212			 from $opt{checkpoint} where 1 != 1} );
213       };
214
215    die "Error accessing Checkpoint table ($opt{checkpoint}): $@"
216      if ( $@ );
217}
218
219# --- check that log_pos table exists if specified ---
220if ( $opt{record_log_pos} ) {
221    $opt{record_log_pos} = quote_names( $opt{record_log_pos} );
222
223    eval { $dbh->do( qq{ select host, time_stamp, log_file, log_pos, master_host, master_log_file, master_log_pos
224			 from $opt{record_log_pos} where 1 != 1} );
225       };
226
227    die "Error accessing log_pos table ($opt{record_log_pos}): $@"
228      if ( $@ );
229}
230
231# --- get variables from database ---
232my $sth_vars = $dbh->prepare("show variables like 'datadir'");
233$sth_vars->execute;
234while ( my ($var,$value) = $sth_vars->fetchrow_array ) {
235    $mysqld_vars{ $var } = $value;
236}
237my $datadir = $mysqld_vars{'datadir'}
238    || die "datadir not in mysqld variables";
239    $datadir= $opt{chroot}.$datadir if ($opt{chroot});
240$datadir =~ s:/$::;
241
242
243# --- get target path ---
244my ($tgt_dirname, $to_other_database);
245$to_other_database=0;
246if (defined($tgt_name) && $tgt_name =~ m:^\w+$: && @db_desc <= 1)
247{
248    $tgt_dirname = "$datadir/$tgt_name";
249    $to_other_database=1;
250}
251elsif (defined($tgt_name) && ($tgt_name =~ m:/: || $tgt_name eq '.')) {
252    $tgt_dirname = $tgt_name;
253}
254elsif ( $opt{suffix} ) {
255    print "Using copy suffix '$opt{suffix}'\n" unless $opt{quiet};
256}
257else
258{
259  $tgt_name="" if (!defined($tgt_name));
260  die "Target '$tgt_name' doesn't look like a database name or directory path.\n";
261}
262
263# --- resolve database names from regexp ---
264if ( defined $opt{regexp} ) {
265    my $t_regex = '.*';
266    if ( $opt{regexp} =~ s{^/(.+)/\./(.+)/$}{$1} ) {
267        $t_regex = $2;
268    }
269
270    my $sth_dbs = $dbh->prepare("show databases");
271    $sth_dbs->execute;
272    while ( my ($db_name) = $sth_dbs->fetchrow_array ) {
273	next if $db_name =~ m/^information_schema$/i;
274	push @db_desc, { 'src' => $db_name, 't_regex' => $t_regex } if ( $db_name =~ m/$opt{regexp}/o );
275    }
276}
277
278# --- get list of tables and views to hotcopy ---
279
280my $hc_locks = "";
281my $hc_tables = "";
282my $hc_base_tables = "";
283my $hc_views = "";
284my $num_base_tables = 0;
285my $num_views = 0;
286my $num_tables = 0;
287my $num_files = 0;
288
289foreach my $rdb ( @db_desc ) {
290    my $db = $rdb->{src};
291    my @dbh_base_tables = get_list_of_tables( $db );
292    my @dbh_views = get_list_of_views( $db );
293
294    ## filter out certain system non-lockable tables.
295    ## keep in sync with mysqldump.
296    if ($db =~ m/^mysql$/i)
297    {
298      @dbh_base_tables = grep
299        { !/^(apply_status|schema|general_log|slow_log|transaction_registry)$/ } @dbh_base_tables
300    }
301
302    ## generate regex for tables/files
303    my $t_regex;
304    my $negated;
305    if ($rdb->{t_regex}) {
306        $t_regex = $rdb->{t_regex};        ## assign temporary regex
307        $negated = $t_regex =~ s/^~//;     ## note and remove negation operator
308
309        $t_regex = qr/$t_regex/;           ## make regex string from
310                                           ## user regex
311
312        ## filter (out) tables specified in t_regex
313        print "Filtering tables with '$t_regex'\n" if $opt{debug};
314        @dbh_base_tables = ( $negated
315                             ? grep { $_ !~ $t_regex } @dbh_base_tables
316                             : grep { $_ =~ $t_regex } @dbh_base_tables );
317
318        ## filter (out) views specified in t_regex
319        print "Filtering tables with '$t_regex'\n" if $opt{debug};
320        @dbh_views = ( $negated
321                       ? grep { $_ !~ $t_regex } @dbh_views
322                       : grep { $_ =~ $t_regex } @dbh_views );
323    }
324
325    ## Now concatenate the base table and view arrays.
326    my @dbh_tables = (@dbh_base_tables, @dbh_views);
327
328    ## get list of files to copy
329    my $db_dir = "$datadir/$db";
330    opendir(DBDIR, $db_dir )
331      or die "Cannot open dir '$db_dir': $!";
332
333    my %db_files;
334
335    while ( defined( my $name = readdir DBDIR ) ) {
336        $db_files{$name} = $1 if ( $name =~ /(.+)\.\w+$/ );
337    }
338    closedir( DBDIR );
339
340    unless( keys %db_files ) {
341	warn "'$db' is an empty database\n";
342    }
343
344    ## filter (out) files specified in t_regex
345    my @db_files;
346    if ($rdb->{t_regex}) {
347        @db_files = ($negated
348                     ? grep { $db_files{$_} !~ $t_regex } keys %db_files
349                     : grep { $db_files{$_} =~ $t_regex } keys %db_files );
350    }
351    else {
352        @db_files = keys %db_files;
353    }
354
355    @db_files = sort @db_files;
356
357    my @index_files=();
358
359    ## remove indices unless we're told to keep them
360    if ($opt{noindices}) {
361        @index_files= grep { /\.(ISM|MYI)$/ } @db_files;
362	@db_files = grep { not /\.(ISM|MYI)$/ } @db_files;
363    }
364
365    $rdb->{files}  = [ @db_files ];
366    $rdb->{index}  = [ @index_files ];
367    my @hc_base_tables = map { quote_names("$db.$_") } @dbh_base_tables;
368    my @hc_views = map { quote_names("$db.$_") } @dbh_views;
369
370    my @hc_tables = (@hc_base_tables, @hc_views);
371    $rdb->{tables} = [ @hc_tables ];
372
373    $hc_locks .= ", "  if ( length $hc_locks && @hc_tables );
374    $hc_locks .= join ", ", map { "$_ READ" } @hc_tables;
375
376    $hc_base_tables .= ", "  if ( length $hc_base_tables && @hc_base_tables );
377    $hc_base_tables .= join ", ", @hc_base_tables;
378    $hc_views .= ", "  if ( length $hc_views && @hc_views );
379    $hc_views .= join " READ, ", @hc_views;
380
381    @hc_tables = (@hc_base_tables, @hc_views);
382
383    $num_base_tables += scalar @hc_base_tables;
384    $num_views += scalar @hc_views;
385    $num_tables += $num_base_tables + $num_views;
386    $num_files  += scalar @{$rdb->{files}};
387}
388
389# --- resolve targets for copies ---
390
391if (defined($tgt_name) && length $tgt_name ) {
392    # explicit destination directory specified
393
394    # GNU `cp -r` error message
395    die "copying multiple databases, but last argument ($tgt_dirname) is not a directory\n"
396      if ( @db_desc > 1 && !(-e $tgt_dirname && -d $tgt_dirname ) );
397
398    if ($to_other_database)
399    {
400      foreach my $rdb ( @db_desc ) {
401	$rdb->{target} = "$tgt_dirname";
402      }
403    }
404    elsif ($opt{method} =~ /^scp\b/)
405    {   # we have to trust scp to hit the target
406	foreach my $rdb ( @db_desc ) {
407	    $rdb->{target} = "$tgt_dirname/$rdb->{src}";
408	}
409    }
410    else
411    {
412      die "Last argument ($tgt_dirname) is not a directory\n"
413	if (!(-e $tgt_dirname && -d $tgt_dirname ) );
414      foreach my $rdb ( @db_desc ) {
415	$rdb->{target} = "$tgt_dirname/$rdb->{src}";
416      }
417    }
418  }
419else {
420  die "Error: expected \$opt{suffix} to exist" unless ( exists $opt{suffix} );
421
422  foreach my $rdb ( @db_desc ) {
423    $rdb->{target} = "$datadir/$rdb->{src}$opt{suffix}";
424  }
425}
426
427print Dumper( \@db_desc ) if ( $opt{debug} );
428
429# --- bail out if all specified databases are empty ---
430
431die "No tables to hot-copy" unless ( length $hc_locks );
432
433# --- create target directories if we are using 'cp' ---
434
435my @existing = ();
436
437if ($opt{method} =~ /^cp\b/)
438{
439  foreach my $rdb ( @db_desc ) {
440    push @existing, $rdb->{target} if ( -d  $rdb->{target} );
441  }
442
443  if ( @existing && !($opt{allowold} || $opt{addtodest}) )
444  {
445    $dbh->disconnect();
446    die "Can't hotcopy to '", join( "','", @existing ), "' because directory\nalready exist and the --allowold or --addtodest options were not given.\n"
447  }
448}
449
450retire_directory( @existing ) if @existing && !$opt{addtodest};
451
452foreach my $rdb ( @db_desc ) {
453    my $tgt_dirpath = "$rdb->{target}";
454    # Remove trailing slashes (needed for Mac OS X)
455    substr($tgt_dirpath, 1) =~ s|/+$||;
456    if ( $opt{dryrun} ) {
457        print "mkdir $tgt_dirpath, 0750\n";
458    }
459    elsif ($opt{method} =~ /^scp\b/) {
460        ## assume it's there?
461        ## ...
462    }
463    else {
464        mkdir($tgt_dirpath, 0750) or die "Can't create '$tgt_dirpath': $!\n"
465            unless -d $tgt_dirpath;
466        my @f_info= stat "$datadir/$rdb->{src}";
467        chown $f_info[4], $f_info[5], $tgt_dirpath;
468    }
469}
470
471##############################
472# --- PERFORM THE HOT-COPY ---
473#
474# Note that we try to keep the time between the LOCK and the UNLOCK
475# as short as possible, and only start when we know that we should
476# be able to complete without error.
477
478# read lock all the tables we'll be copying
479# in order to get a consistent snapshot of the database
480
481if ( $opt{checkpoint} || $opt{record_log_pos} ) {
482  # convert existing READ lock on checkpoint and/or log_pos table into WRITE lock
483  foreach my $table ( grep { defined } ( $opt{checkpoint}, $opt{record_log_pos} ) ) {
484    $hc_locks .= ", $table WRITE"
485	unless ( $hc_locks =~ s/$table\s+READ/$table WRITE/ );
486  }
487}
488
489my $hc_started = time;	# count from time lock is granted
490
491if ( $opt{dryrun} ) {
492    if ( $opt{old_server} ) {
493        print "LOCK TABLES $hc_locks\n";
494        print "FLUSH TABLES /*!32323 $hc_tables */\n";
495    }
496    else {
497        # Lock base tables and views separately.
498        print "FLUSH TABLES $hc_base_tables WITH READ LOCK\n"
499          if ( $hc_base_tables );
500        print "LOCK TABLES $hc_views READ\n" if ( $hc_views );
501    }
502
503    print "FLUSH LOGS\n" if ( $opt{flushlog} );
504    print "RESET MASTER\n" if ( $opt{resetmaster} );
505    print "RESET SLAVE\n" if ( $opt{resetslave} );
506}
507else {
508    my $start = time;
509    if ( $opt{old_server} ) {
510        $dbh->do("LOCK TABLES $hc_locks");
511        printf "Locked $num_tables tables in %d seconds.\n", time-$start unless $opt{quiet};
512        $hc_started = time;	# count from time lock is granted
513
514        # flush tables to make on-disk copy up to date
515        $start = time;
516        $dbh->do("FLUSH TABLES /*!32323 $hc_tables */");
517        printf "Flushed tables ($hc_tables) in %d seconds.\n", time-$start unless $opt{quiet};
518    }
519    else {
520        # Lock base tables and views separately, as 'FLUSH TABLES <tbl_name>
521        # ... WITH READ LOCK' (introduced in 5.5) would fail for views.
522        # Also, flush tables to make on-disk copy up to date
523        $dbh->do("FLUSH TABLES $hc_base_tables WITH READ LOCK")
524          if ( $hc_base_tables );
525        printf "Flushed $num_base_tables tables with read lock ($hc_base_tables) in %d seconds.\n",
526               time-$start unless $opt{quiet};
527
528        $start = time;
529        $dbh->do("LOCK TABLES $hc_views READ") if ( $hc_views );
530        printf "Locked $num_views views ($hc_views) in %d seconds.\n",
531               time-$start unless $opt{quiet};
532
533        $hc_started = time;	# count from time lock is granted
534    }
535    $dbh->do( "FLUSH LOGS" ) if ( $opt{flushlog} );
536    $dbh->do( "RESET MASTER" ) if ( $opt{resetmaster} );
537    $dbh->do( "RESET SLAVE" ) if ( $opt{resetslave} );
538
539    if ( $opt{record_log_pos} ) {
540	record_log_pos( $dbh, $opt{record_log_pos} );
541	$dbh->do("FLUSH TABLES /*!32323 $hc_tables */");
542    }
543}
544
545my @failed = ();
546
547foreach my $rdb ( @db_desc )
548{
549  my @files = map { "$datadir/$rdb->{src}/$_" } @{$rdb->{files}};
550  next unless @files;
551
552  eval { copy_files($opt{method}, \@files, $rdb->{target}); };
553  push @failed, "$rdb->{src} -> $rdb->{target} failed: $@"
554    if ( $@ );
555
556  @files = @{$rdb->{index}};
557  if ($rdb->{index})
558  {
559    copy_index($opt{method}, \@files,
560	       "$datadir/$rdb->{src}", $rdb->{target} );
561  }
562
563  if ( $opt{checkpoint} ) {
564    my $msg = ( $@ ) ? "Failed: $@" : "Succeeded";
565
566    eval {
567      $dbh->do( qq{ insert into $opt{checkpoint} (src, dest, msg)
568		      VALUES ( '$rdb->{src}', '$rdb->{target}', '$msg' )
569		    } );
570    };
571
572    if ( $@ ) {
573      warn "Failed to update checkpoint table: $@\n";
574    }
575  }
576}
577
578if ( $opt{dryrun} ) {
579    print "UNLOCK TABLES\n";
580    if ( @existing && !$opt{keepold} ) {
581	my @oldies = map { $_ . '_old' } @existing;
582	print "rm -rf @oldies\n"
583    }
584    $dbh->disconnect();
585    exit(0);
586}
587else {
588    $dbh->do("UNLOCK TABLES");
589}
590
591my $hc_dur = time - $hc_started;
592printf "Unlocked tables.\n" unless $opt{quiet};
593
594#
595# --- HOT-COPY COMPLETE ---
596###########################
597
598$dbh->disconnect;
599
600if ( @failed ) {
601    # hotcopy failed - cleanup
602    # delete any @targets
603    # rename _old copy back to original
604
605    my @targets = ();
606    foreach my $rdb ( @db_desc ) {
607        push @targets, $rdb->{target} if ( -d  $rdb->{target} );
608    }
609    print "Deleting @targets \n" if $opt{debug};
610
611    print "Deleting @targets \n" if $opt{debug};
612    rmtree([@targets]);
613    if (@existing) {
614	print "Restoring @existing from back-up\n" if $opt{debug};
615        foreach my $dir ( @existing ) {
616	    rename("${dir}_old", $dir )
617	      or warn "Can't rename ${dir}_old to $dir: $!\n";
618	}
619    }
620
621    die join( "\n", @failed );
622}
623else {
624    # hotcopy worked
625    # delete _old unless $opt{keepold}
626
627    if ( @existing && !$opt{keepold} ) {
628	my @oldies = map { $_ . '_old' } @existing;
629	print "Deleting previous copy in @oldies\n" if $opt{debug};
630	rmtree([@oldies]);
631    }
632
633    printf "$0 copied %d tables (%d files) in %d second%s (%d seconds overall).\n",
634	    $num_tables, $num_files,
635	    $hc_dur, ($hc_dur==1)?"":"s", time - $start_time
636	unless $opt{quiet};
637}
638
639exit 0;
640
641
642# ---
643
644sub copy_files {
645    my ($method, $files, $target) = @_;
646    my @cmd;
647    print "Copying ".@$files." files...\n" unless $opt{quiet};
648
649    if ($method =~ /^s?cp\b/)  # cp or scp with optional flags
650    {
651	my $cp = $method;
652	# add option to preserve mod time etc of copied files
653	# not critical, but nice to have
654	$cp.= " -p" if $^O =~ m/^(solaris|linux|freebsd|darwin)$/;
655
656	# add recursive option for scp
657	$cp.= " -r" if $^O =~ /m^(solaris|linux|freebsd|darwin)$/ && $method =~ /^scp\b/;
658
659	# perform the actual copy
660	safe_system( $cp, (map { "'$_'" } @$files), "'$target'" );
661    }
662    else
663    {
664	die "Can't use unsupported method '$method'\n";
665    }
666}
667
668#
669# Copy only the header of the index file
670#
671
672sub copy_index
673{
674  my ($method, $files, $source, $target) = @_;
675
676  print "Copying indices for ".@$files." files...\n" unless $opt{quiet};
677  foreach my $file (@$files)
678  {
679    my $from="$source/$file";
680    my $to="$target/$file";
681    my $buff;
682    open(INPUT, "<$from") || die "Can't open file $from: $!\n";
683    binmode(INPUT, ":raw");
684    my $length=read INPUT, $buff, 2048;
685    die "Can't read index header from $from\n" if ($length < 1024);
686    close INPUT;
687
688    if ( $opt{dryrun} )
689    {
690      print "$opt{method}-header $from $to\n";
691    }
692    elsif ($opt{method} eq 'cp')
693    {
694      open(OUTPUT,">$to")   || die "Can\'t create file $to: $!\n";
695      if (syswrite(OUTPUT,$buff) != length($buff))
696      {
697	die "Error when writing data to $to: $!\n";
698      }
699      close OUTPUT	   || die "Error on close of $to: $!\n";
700    }
701    elsif ($opt{method} =~ /^scp\b/)
702    {
703      my ($fh, $tmp)= tempfile('mysqlhotcopy-XXXXXX', DIR => $opt_tmpdir) or
704	die "Can\'t create/open file in $opt_tmpdir\n";
705      if (syswrite($fh,$buff) != length($buff))
706      {
707	die "Error when writing data to $tmp: $!\n";
708      }
709      close $fh || die "Error on close of $tmp: $!\n";
710      safe_system("$opt{method} $tmp $to");
711      unlink $tmp;
712    }
713    else
714    {
715      die "Can't use unsupported method '$opt{method}'\n";
716    }
717  }
718}
719
720
721sub safe_system {
722  my @sources= @_;
723  my $method= shift @sources;
724  my $target= pop @sources;
725  ## @sources = list of source file names
726
727  ## We have to deal with very long command lines, otherwise they may generate
728  ## "Argument list too long".
729  ## With 10000 tables the command line can be around 1MB, much more than 128kB
730  ## which is the common limit on Linux (can be read from
731  ## /usr/src/linux/include/linux/binfmts.h
732  ## see http://www.linuxjournal.com/article.php?sid=6060).
733
734  my $chunk_limit= 100 * 1024; # 100 kB
735  my @chunk= ();
736  my $chunk_length= 0;
737  foreach (@sources) {
738      push @chunk, $_;
739      $chunk_length+= length($_);
740      if ($chunk_length > $chunk_limit) {
741          safe_simple_system($method, @chunk, $target);
742          @chunk=();
743          $chunk_length= 0;
744      }
745  }
746  if ($chunk_length > 0) { # do not forget last small chunk
747      safe_simple_system($method, @chunk, $target);
748  }
749}
750
751sub safe_simple_system {
752    my @cmd= @_;
753
754    if ( $opt{dryrun} ) {
755        print "@cmd\n";
756    }
757    else {
758        ## for some reason system fails but backticks works ok for scp...
759        print "Executing '@cmd'\n" if $opt{debug};
760        my $cp_status = system "@cmd > /dev/null";
761        if ($cp_status != 0) {
762            warn "Executing command failed ($cp_status). Trying backtick execution...\n";
763            ## try something else
764            `@cmd` || die "Error: @cmd failed ($?) while copying files.\n";
765        }
766    }
767}
768
769sub retire_directory {
770    my ( @dir ) = @_;
771
772    foreach my $dir ( @dir ) {
773	my $tgt_oldpath = $dir . '_old';
774	if ( $opt{dryrun} ) {
775	    print "rmtree $tgt_oldpath\n" if ( -d $tgt_oldpath );
776	    print "rename $dir, $tgt_oldpath\n";
777	    next;
778	}
779
780	if ( -d $tgt_oldpath ) {
781	    print "Deleting previous 'old' hotcopy directory ('$tgt_oldpath')\n" unless $opt{quiet};
782	    rmtree([$tgt_oldpath],0,1);
783	}
784	rename($dir, $tgt_oldpath)
785	  or die "Can't rename $dir=>$tgt_oldpath: $!\n";
786	print "Existing hotcopy directory renamed to '$tgt_oldpath'\n" unless $opt{quiet};
787    }
788}
789
790sub record_log_pos {
791    my ( $dbh, $table_name ) = @_;
792
793    eval {
794	my ($file,$position) = get_row( $dbh, "show master status" );
795	die "master status is undefined" if !defined $file || !defined $position;
796
797	my $row_hash = get_row_hash( $dbh, "show slave status" );
798	my ($master_host, $log_file, $log_pos );
799	if ( $dbh->{mysql_serverinfo} =~ /^3\.23/ ) {
800	    ($master_host, $log_file, $log_pos )
801	      = @{$row_hash}{ qw / Master_Host Log_File Pos / };
802	} else {
803	    ($master_host, $log_file, $log_pos )
804	      = @{$row_hash}{ qw / Master_Host Relay_Master_Log_File Exec_Master_Log_Pos / };
805	}
806	my $hostname = hostname();
807
808	$dbh->do( qq{ replace into $table_name
809			  set host=?, log_file=?, log_pos=?,
810                          master_host=?, master_log_file=?, master_log_pos=? },
811		  undef,
812		  $hostname, $file, $position,
813		  $master_host, $log_file, $log_pos  );
814
815    };
816
817    if ( $@ ) {
818	warn "Failed to store master position: $@\n";
819    }
820}
821
822sub get_row {
823  my ( $dbh, $sql ) = @_;
824
825  my $sth = $dbh->prepare($sql);
826  $sth->execute;
827  return $sth->fetchrow_array();
828}
829
830sub get_row_hash {
831  my ( $dbh, $sql ) = @_;
832
833  my $sth = $dbh->prepare($sql);
834  $sth->execute;
835  return $sth->fetchrow_hashref();
836}
837
838sub get_list_of_tables {
839    my ( $db ) = @_;
840
841    my $tables =
842        eval {
843            $dbh->selectall_arrayref('SHOW FULL TABLES FROM ' .
844                                     $dbh->quote_identifier($db) .
845                                     ' WHERE Table_type = \'BASE TABLE\'')
846        } || [];
847    warn "Unable to retrieve list of tables in $db: $@" if $@;
848
849    return (map { $_->[0] } @$tables);
850}
851
852sub get_list_of_views {
853    my ( $db ) = @_;
854
855    my $views =
856        eval {
857            $dbh->selectall_arrayref('SHOW FULL TABLES FROM ' .
858                                     $dbh->quote_identifier($db) .
859                                     ' WHERE Table_type = \'VIEW\'')
860        } || [];
861    warn "Unable to retrieve list of views in $db: $@" if $@;
862
863    return (map { $_->[0] } @$views);
864}
865
866sub quote_names {
867  my ( $name ) = @_;
868  # given a db.table name, add quotes
869
870  my ($db, $table, @cruft) = split( /\./, $name );
871  die "Invalid db.table name '$name'" if (@cruft || !defined $db || !defined $table );
872
873  # Earlier versions of DBD return table name non-quoted,
874  # such as DBD-2.1012 and the newer ones, such as DBD-2.9002
875  # returns it quoted. Let's have a support for both.
876  $table=~ s/\`//g;
877  return "`$db`.`$table`";
878}
879
880__END__
881
882=head1 DESCRIPTION
883
884mysqlhotcopy is designed to make stable copies of live MySQL databases.
885
886Here "live" means that the database server is running and the database
887may be in active use. And "stable" means that the copy will not have
888any corruptions that could occur if the table files were simply copied
889without first being locked and flushed from within the server.
890
891=head1 OPTIONS
892
893=over 4
894
895=item --checkpoint checkpoint-table
896
897As each database is copied, an entry is written to the specified
898checkpoint-table.  This has the happy side-effect of updating the
899MySQL update-log (if it is switched on) giving a good indication of
900where roll-forward should begin for backup+rollforward schemes.
901
902The name of the checkpoint table should be supplied in database.table format.
903The checkpoint-table must contain at least the following fields:
904
905=over 4
906
907  time_stamp timestamp not null
908  src varchar(32)
909  dest varchar(60)
910  msg varchar(255)
911
912=back
913
914=item --record_log_pos log-pos-table
915
916Just before the database files are copied, update the record in the
917log-pos-table from the values returned from "show master status" and
918"show slave status". The master status values are stored in the
919log_file and log_pos columns, and establish the position in the binary
920logs that any slaves of this host should adopt if initialised from
921this dump.  The slave status values are stored in master_host,
922master_log_file, and master_log_pos, corresponding to the coordinates
923of the next to the last event the slave has executed. The slave or its
924siblings can connect to the master next time and request replication
925starting from the recorded values.
926
927The name of the log-pos table should be supplied in database.table format.
928A sample log-pos table definition:
929
930=over 4
931
932CREATE TABLE log_pos (
933  host            varchar(60) NOT null,
934  time_stamp      timestamp NOT NULL,
935  log_file        varchar(32) default NULL,
936  log_pos         int(11)     default NULL,
937  master_host     varchar(60) NULL,
938  master_log_file varchar(32) NULL,
939  master_log_pos  int NULL,
940
941  PRIMARY KEY  (host)
942);
943
944=back
945
946
947=item --suffix suffix
948
949Each database is copied back into the originating datadir under
950a new name. The new name is the original name with the suffix
951appended.
952
953If only a single db_name is supplied and the --suffix flag is not
954supplied, then "--suffix=_copy" is assumed.
955
956=item --allowold
957
958Move any existing version of the destination to a backup directory for
959the duration of the copy. If the copy successfully completes, the backup
960directory is deleted - unless the --keepold flag is set.  If the copy fails,
961the backup directory is restored.
962
963The backup directory name is the original name with "_old" appended.
964Any existing versions of the backup directory are deleted.
965
966=item --keepold
967
968Behaves as for the --allowold, with the additional feature
969of keeping the backup directory after the copy successfully completes.
970
971=item --addtodest
972
973Don't rename target directory if it already exists, just add the
974copied files into it.
975
976This is most useful when backing up a database with many large
977tables and you don't want to have all the tables locked for the
978whole duration.
979
980In this situation, I<if> you are happy for groups of tables to be
981backed up separately (and thus possibly not be logically consistent
982with one another) then you can run mysqlhotcopy several times on
983the same database each with different db_name./table_regex/.
984All but the first should use the --addtodest option so the tables
985all end up in the same directory.
986
987=item --flushlog
988
989Rotate the log files by executing "FLUSH LOGS" after all tables are
990locked, and before they are copied.
991
992=item --resetmaster
993
994Reset the bin-log by executing "RESET MASTER" after all tables are
995locked, and before they are copied. Useful if you are recovering a
996slave in a replication setup.
997
998=item --resetslave
999
1000Reset the master.info by executing "RESET SLAVE" after all tables are
1001locked, and before they are copied. Useful if you are recovering a
1002server in a mutual replication setup.
1003
1004=item --regexp pattern
1005
1006Copy all databases with names matching the pattern.
1007
1008=item --regexp /pattern1/./pattern2/
1009
1010Copy all tables with names matching pattern2 from all databases with
1011names matching pattern1. For example, to select all tables which
1012names begin with 'bar' from all databases which names end with 'foo':
1013
1014   mysqlhotcopy --indices --method=cp --regexp /foo$/./^bar/
1015
1016=item db_name./pattern/
1017
1018Copy only tables matching pattern. Shell metacharacters ( (, ), |, !,
1019etc.) have to be escaped (e.g., \). For example, to select all tables
1020in database db1 whose names begin with 'foo' or 'bar':
1021
1022    mysqlhotcopy --indices --method=cp db1./^\(foo\|bar\)/
1023
1024=item db_name./~pattern/
1025
1026Copy only tables not matching pattern. For example, to copy tables
1027that do not begin with foo nor bar:
1028
1029    mysqlhotcopy --indices --method=cp db1./~^\(foo\|bar\)/
1030
1031=item -?, --help
1032
1033Display help-screen and exit.
1034
1035=item -u, --user=#
1036
1037User for database login if not current user.
1038
1039=item -p, --password=#
1040
1041Password to use when connecting to the server. Note that you are strongly
1042encouraged *not* to use this option as every user would be able to see the
1043password in the process list. Instead use the '[mysqlhotcopy]' section in
1044one of the config files, normally /etc/my.cnf or your personal ~/.my.cnf.
1045(See the chapter 'my.cnf Option Files' in the manual.)
1046
1047=item -h, -h, --host=#
1048
1049Hostname for local server when connecting over TCP/IP.  By specifying this
1050different from 'localhost' will trigger mysqlhotcopy to use TCP/IP connection.
1051
1052=item -P, --port=#
1053
1054Port to use when connecting to MySQL server with TCP/IP.  This is only used
1055when using the --host option.
1056
1057=item -S, --socket=#
1058
1059UNIX domain socket to use when connecting to local server.
1060
1061=item --old_server
1062
1063Use old server (pre v5.5) commands.
1064
1065=item  --noindices
1066
1067Don\'t include index files in copy. Only up to the first 2048 bytes
1068are copied;  You can restore the indexes with isamchk -r or myisamchk -r
1069on the backup.
1070
1071=item  --method=#
1072
1073Method for copy (only "cp" currently supported). Alpha support for
1074"scp" was added in November 2000. Your experience with the scp method
1075will vary with your ability to understand how scp works. 'man scp'
1076and 'man ssh' are your friends.
1077
1078The destination directory _must exist_ on the target machine using the
1079scp method. --keepold and --allowold are meaningless with scp.
1080Liberal use of the --debug option will help you figure out what\'s
1081really going on when you do an scp.
1082
1083Note that using scp will lock your tables for a _long_ time unless
1084your network connection is _fast_. If this is unacceptable to you,
1085use the 'cp' method to copy the tables to some temporary area and then
1086scp or rsync the files at your leisure.
1087
1088=item -q, --quiet
1089
1090Be silent except for errors.
1091
1092=item  --debug
1093
1094Debug messages are displayed.
1095
1096=item -n, --dryrun
1097
1098Display commands without actually doing them.
1099
1100=back
1101
1102=head1 WARRANTY
1103
1104This software is free and comes without warranty of any kind. You
1105should never trust backup software without studying the code yourself.
1106Study the code inside this script and only rely on it if I<you> believe
1107that it does the right thing for you.
1108
1109Patches adding bug fixes, documentation and new features are welcome.
1110Please send these to internals@lists.mysql.com.
1111
1112=head1 TO DO
1113
1114Extend the individual table copy to allow multiple subsets of tables
1115to be specified on the command line:
1116
1117  mysqlhotcopy db newdb  t1 t2 /^foo_/ : t3 /^bar_/ : +
1118
1119where ":" delimits the subsets, the /^foo_/ indicates all tables
1120with names beginning with "foo_" and the "+" indicates all tables
1121not copied by the previous subsets.
1122
1123'newdb' is either the name of the new database, or the full path name
1124of the new database file. The database should not already exist.
1125
1126Add option to lock each table in turn for people who don\'t need
1127cross-table integrity.
1128
1129Add option to FLUSH STATUS just before UNLOCK TABLES.
1130
1131Add support for other copy methods (e.g., tar to single file?).
1132
1133Add support for forthcoming MySQL ``RAID'' table subdirectory layouts.
1134
1135=head1 AUTHOR
1136
1137Tim Bunce
1138
1139Martin Waite - Added checkpoint, flushlog, regexp and dryrun options.
1140               Fixed cleanup of targets when hotcopy fails.
1141               Added --record_log_pos.
1142               RAID tables are now copied (don't know if this works over scp).
1143
1144Ralph Corderoy - Added synonyms for commands.
1145
1146Scott Wiersdorf - Added table regex and scp support.
1147
1148Monty - Working --noindex (copy only first 2048 bytes of index file).
1149        Fixes for --method=scp.
1150
1151Ask Bjoern Hansen - Cleanup code to fix a few bugs and enable -w again.
1152
1153Emil S. Hansen - Added resetslave and resetmaster.
1154
1155Jeremy D. Zawodny - Removed deprecated DBI calls.  Fixed bug which
1156resulted in nothing being copied when a regexp was specified but no
1157database name(s).
1158
1159Martin Waite - Fix to handle database name that contains space.
1160
1161Paul DuBois - Remove end '/' from directory names.
1162