]> git.pld-linux.org Git - packages/bacula-backup-mysql.git/blob - bacula-backup-mysql
- v0.3: cleanup tmp dir when backup failed
[packages/bacula-backup-mysql.git] / bacula-backup-mysql
1 #!/usr/bin/perl -ws
2 # This program is free software; you can redistribute it and/or modify
3 # it under the terms of the GNU General Public License as published by
4 # the Free Software Foundation; either version 2 of the License, or
5 # (at your option) any later version.
6 #
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10 # GNU General Public License for more details.
11 #
12 # You should have received a copy of the GNU General Public License
13 # along with this program; if not, write to:
14 #
15 #  Free Software Foundation, Inc.
16 #  59 Temple Place - Suite 330
17 #  Boston, MA 02111-1307, USA.
18
19 # Rudimentary switch parsing. Must be in main package.
20 our $cleanup;
21
22 package BBM;
23 use strict;
24 use POSIX qw(setuid setgid);
25 use DBI;
26 use File::Temp qw(tempdir);
27 use File::Path qw(rmtree);
28
29
30 # path to Apache HTTPd-style config
31 my $config = '/etc/bacula/backup-mysql.conf';
32 my $c = new BBM::Config($config);
33
34 # now change to user mysql after we've read config
35 unless ($<) {
36         my $uid = getpwnam('mysql');
37         my $gid = getgrnam('mysql');
38         die "Can't find user/group mysql\n" unless $uid or $gid;
39
40         # CWD could not be accessible for mysql user
41         chdir("/");
42
43         $) = "$gid $gid";
44         $( = $gid;
45         $> = $< = $uid;
46 }
47
48 # setup tmpdir
49 my $backup_dir = $c->get('options', 'outdir') or die "'outdir' not defined in config\n";
50 my $tmpdir = $c->get('options', 'tmpdir')  or die "'tmpdir' not defined in config\n";
51
52 if (!-d $backup_dir && !mkdir($backup_dir) && !-d $backup_dir) {
53         die "backup dir '$backup_dir' not present and can't be created\n";
54 }
55 if (!-d $tmpdir && !mkdir($tmpdir) && !-d $tmpdir) {
56         die "tmpdir '$tmpdir' not present and can't be created\n";
57 }
58
59 # process each cluster
60 for my $cluster ($c->get('clusters', 'cluster')) {
61         print ">>> $cluster\n";
62         if ($cleanup) {
63                 cleanup_cluster($cluster);
64         } else {
65                 backup_cluster($cluster);
66         }
67         print "<<< $cluster\n";
68 }
69
70 #
71 # Usage: mysqlhotcopy $CLUSTER $DATABASE $USERNAME $PASSWORD $SOCKET
72 #
73 sub mysqlhotcopy {
74         my ($cluster, $database, $user, $password, $socket) = @_;
75
76         print ">>>> mysqlhotcopy $database\n";
77
78         my $dstdir = tempdir("bbm.XXXXXX", DIR => $tmpdir);
79
80         # make backup with mysqlhotcopy
81         my @shell = ('mysqlhotcopy');
82         push(@shell, '-u', $user) if $user;
83         push(@shell, '-p', $password) if $password;
84         push(@shell, '-S', $socket) if $socket;
85         push(@shell, $database, $dstdir);
86         system(@shell) == 0 or die "mysqlhotcopy failed: $?\n";
87
88         # put it to "production dir"
89         my $cluster_dir = "$backup_dir/$cluster";
90         if (!-d $cluster_dir && !mkdir($cluster_dir) && !-d $cluster_dir) {
91                 rmtree($dstdir);
92                 die "cluster dir '$cluster_dir' not present and can't be created\n";
93         }
94
95         my $dirname = "$backup_dir/$cluster/$database";
96         if (-d $dirname) {
97                 rmtree($dirname);
98         }
99
100         my $srcdir = "$dstdir/$database";
101         unless (rename($srcdir, $dirname)) {
102                 rmtree($dstdir);
103                 die "Rename '$srcdir'->'$dirname' failed: $!\n";
104         }
105
106         rmdir($dstdir) or warn $!;
107
108         print "<<<< mysqlhotcopy $database\n";
109 }
110
111 sub cleanup_cluster {
112         my ($cluster) = @_;
113         my $cluster_dir = "$backup_dir/$cluster";
114         print ">>>> cleanup $cluster_dir\n";
115         rmtree($cluster_dir);
116         print "<<<< cleanup $cluster_dir\n";
117 }
118
119 sub backup_cluster {
120         my ($cluster) = @_;
121
122         # get db connection info
123         my $user = $c->get($cluster, 'user') || $c->get('client', 'user');
124         my $password = $c->get($cluster, 'password') || $c->get('client', 'password');
125         my $socket = $c->get($cluster, 'socket') || $c->get('client', 'socket');
126
127         # get databases to backup
128         my @include = $c->get($cluster, 'include_database');
129         my @exclude = $c->get($cluster, 'exclude_database');
130
131         # start with include list
132         my %dbs = map { $_ => 1 } @include;
133
134         if (@exclude or !@include) {
135                 my $dbh = new BBM::DB($user, $password, $socket);
136                 my $sth = $dbh->prepare("show databases");
137                 $sth->execute();
138                 while (my($dbname) = $sth->fetchrow_array) {
139                         next if lc($dbname) eq 'information_schema';
140                         $dbs{$dbname} = 1;
141                 }
142                 undef $dbh;
143         }
144
145         # remove excluded databases
146         delete @dbs{@exclude};
147
148         # now do the backup
149         foreach my $db (keys %dbs) {
150                 mysqlhotcopy($cluster, $db, $user, $password, $socket);
151         }
152 }
153
154 package BBM::DB;
155 use strict;
156
157 # DB class for simple Database connection
158 sub new {
159         my $self = shift;
160         my $class = ref($self) || $self;
161
162         my ($user, $password, $socket) = @_;
163         my $dsn = '';
164         $dsn .= "mysql_socket=$socket" if $socket;
165         my $dbh = DBI->connect("DBI:mysql:$dsn", $user, $password, { PrintError => 0, RaiseError => 1 });
166         return $dbh;
167 }
168
169 package BBM::Config;
170 use strict;
171 use Config::General;
172
173 sub new {
174         my $self = shift;
175         my $class = ref($self) || $self;
176         my $file = shift;
177
178         my $config = new Config::General(-ConfigFile => $file, -LowerCaseNames => 1);
179         my $this = { $config->getall() };
180         bless($this, $class);
181 }
182
183 sub get {
184         my ($self, $section, $key) = @_;
185         my $h = $self;
186
187         # descend to [cluster] if $section not present in root tree
188         unless (exists $h->{$section}) {
189                 $h = $h->{cluster};
190         }
191
192         # pay attention if callee wanted arrays
193         return wantarray ? () : undef unless exists $h->{$section};
194         return wantarray ? () : undef unless exists $h->{$section}->{$key};
195
196         # deref if wanted array and is arrayref
197         return @{$h->{$section}->{$key}} if wantarray && ref $h->{$section}->{$key} eq 'ARRAY';
198
199         return $h->{$section}->{$key};
200 }
201
202
203 __END__
204
205 =head1 NAME
206
207 bacula-backup-mysql - A hook for Bacula to backup mysql databases using mysqlhotcopy.
208
209 =head1 SYNOPSIS
210
211   Job {
212     Name = "example.org-mysql"
213     ...
214     # This prepares the backup
215     Client Run Before Job = "/usr/sbin/bacula-backup-mysql"
216     # This deletes the copy of the catalog
217     Client Run After Job = "/usr/sbin/bacula-backup-mysql -cleanup"
218   }
219
220 =head1 DESCRIPTION
221
222 This is a script to be setup as C<Client Run Before Job> in Bacula.
223
224 =head1 AUTHOR
225
226 Copyright (C) 2009-2010, Elan RuusamE<auml>e <glen@delfi.ee>
227
228 =head1 SEE ALSO
229
230 http://www.bacula.org/
231
232 =cut
This page took 0.069541 seconds and 3 git commands to generate.