1#!/usr/local/bin/perl
2#
3# Author: Eric Bollengier, Copyright, 2006-2018
4# License: BSD 2-Clause; see file LICENSE-FOSS
5
6use strict;
7
8=head1 SCRIPT
9
10  This script dumps your Bacula catalog in ASCII format
11  It works for MySQL, SQLite, and PostgreSQL
12
13=head1 USAGE
14
15    make_catalog_backup.pl [-m|--maintenance-only] MyCatalog
16
17=head1 LICENSE
18   Author: Eric Bollengier, 2010
19   License: BSD 2-Clause; see file LICENSE-FOSS
20=cut
21
22my $cat = shift or die "Usage: $0 [-m|--maintenance-only] catalogname";
23my $mode = "dump";
24
25if ($cat eq '-m' || $cat eq '--maintenance-only') {
26    $mode = "analyse";
27    $cat = shift or die "Usage: $0 [-m|--maintenance-only] catalogname";
28}
29
30my $dir_conf='@sbindir@/dbcheck -B -c @sysconfdir@/bacula-dir.conf';
31my $wd = "@working_dir@";
32
33sub dump_sqlite3
34{
35    my %args = @_;
36
37    exec("echo .dump | sqlite3 '$wd/$args{db_name}.db' > '$wd/$args{db_name}.sql'");
38    print "Error while executing sqlite dump $!\n";
39    return 1;
40}
41
42# TODO: use just ENV and drop the pg_service.conf file
43sub setup_env_pgsql
44{
45    my %args = @_;
46    my $username = getpwuid $ENV{'UID'};
47    umask(0077);
48
49    if ($args{db_address}) {
50	$ENV{PGHOST}=$args{db_address};
51    }
52    if ($args{db_socket}) {
53	$ENV{PGHOST}=$args{db_socket};
54    }
55    if ($args{db_port}) {
56	$ENV{PGPORT}=$args{db_port};
57    }
58    if ($args{db_user}) {
59	$ENV{PGUSER}=$args{db_user};
60    }
61    if ($args{db_password}) {
62	$ENV{PGPASSWORD}=$args{db_password};
63    }
64    $ENV{PGDATABASE}=$args{db_name};
65    system("echo '\\q' | HOME='$wd' psql") == 0 or die "$username doesn't have access to the catalog database\n";
66}
67
68sub dump_pgsql
69{
70    my %args = @_;
71    setup_env_pgsql(%args);
72    exec("HOME='$wd' pg_dump -c > '$wd/$args{db_name}.sql'");
73    print "Error while executing postgres dump $!\n";
74    return 1;		    # in case of error
75}
76
77sub analyse_pgsql
78{
79    my %args = @_;
80    setup_env_pgsql(%args);
81    my @output =`LANG=C HOME='$wd' vacuumdb -z 2>&1`;
82    my $exitcode = $? >> 8;
83    print grep { !/^WARNING:\s+skipping\s\"(pg_|sql_)/ } @output;
84    if ($exitcode != 0) {
85	print "Error while executing postgres analyse. Exitcode=$exitcode\n";
86    }
87    return $exitcode;
88}
89
90sub setup_env_mysql
91{
92    my %args = @_;
93    umask(0077);
94    unlink("$wd/.my.cnf");
95    open(MY, ">$wd/.my.cnf")
96	or die "Can't open $wd/.my.cnf for writing $@";
97
98    $args{db_address} = $args{db_address} || "localhost";
99    my $addr = "host=$args{db_address}";
100    if ($args{db_socket}) {	# unix socket is fastest than net socket
101	$addr = "socket=\"$args{db_socket}\"";
102    }
103    my $mode = $args{mode} || 'client';
104    print MY "[$mode]
105$addr
106user=\"$args{db_user}\"
107password=\"$args{db_password}\"
108";
109    if ($args{db_port}) {
110       print MY "port=$args{db_port}\n";
111    }
112    close(MY);
113}
114
115sub dump_mysql
116{
117    my %args = @_;
118
119    setup_env_mysql(%args);
120    exec("HOME='$wd' mysqldump -f --opt $args{db_name} > '$wd/$args{db_name}.sql'");
121    print "Error while executing mysql dump $!\n";
122    return 1;
123}
124
125sub analyse_mysql
126{
127    my %args = @_;
128
129    $args{mode} = 'mysqlcheck';
130    setup_env_mysql(%args);
131
132    exec("HOME='$wd' mysqlcheck -a $args{db_name}");
133    print "Error while executing mysql analyse $!\n";
134    return 1;
135}
136
137sub handle_catalog
138{
139    my ($mode, %args) = @_;
140    if (exists $args{working_dir} and $wd ne $args{working_dir}) {
141	$wd = $args{working_dir};
142    }
143    if ($args{db_type} eq 'SQLite3') {
144	$ENV{PATH}="@SQLITE_BINDIR@:$ENV{PATH}";
145	if ($mode eq 'dump') {
146	    dump_sqlite3(%args);
147	}
148    } elsif ($args{db_type} eq 'PostgreSQL') {
149	$ENV{PATH}="@POSTGRESQL_BINDIR@:$ENV{PATH}";
150	if ($mode eq 'dump') {
151	    dump_pgsql(%args);
152	} else {
153	    analyse_pgsql(%args);
154	}
155    } elsif ($args{db_type} eq 'MySQL') {
156	$ENV{PATH}="@MYSQL_BINDIR@:$ENV{PATH}";
157	if ($mode eq 'dump') {
158	    dump_mysql(%args);
159	} else {
160	    analyse_mysql(%args);
161	}
162    } else {
163	die "This database type isn't supported";
164    }
165}
166
167open(FP, "$dir_conf -C '$cat'|") or die "Can't get catalog information $@";
168# catalog=MyCatalog
169# db_type=SQLite
170# db_name=regress
171# db_driver=
172# db_user=regress
173# db_password=
174# db_address=
175# db_port=0
176# db_socket=
177my %cfg;
178
179while(my $l = <FP>)
180{
181    if ($l =~ /catalog=(.+)/) {
182	if (exists $cfg{catalog} and $cfg{catalog} eq $cat) {
183	    exit handle_catalog($mode, %cfg);
184	}
185	%cfg = ();		# reset
186    }
187
188    if ($l =~ /(\w+)=(.+)/) {
189	$cfg{$1}=$2;
190    }
191}
192
193if (exists $cfg{catalog} and $cfg{catalog} eq $cat) {
194    exit handle_catalog($mode, %cfg);
195}
196
197print "Can't find your catalog ($cat) in director configuration\n";
198exit 1;
199