]> git.sur5r.net Git - bacula/bacula/blob - bacula/scripts/manual_prune.pl
Fix #1545 about fix in manual_prune.pl script with large number of volumes
[bacula/bacula] / bacula / scripts / manual_prune.pl
1 #!/usr/bin/perl -w
2 #
3 # Copyright (C) 2000-2015 Kern Sibbald
4 # License: BSD 2-Clause; see file LICENSE-FOSS
5 #
6 use strict;
7
8 =head1 DESCRIPTION
9
10     manual_prune.pl -- prune volumes
11
12 =head2 USAGE
13
14     manual_prune.pl [--bconsole=/path/to/bconsole] [--help] [--doprune] [--expired] [--fixerror] [--fileprune]
15
16     This program when run will manually prune all Volumes that it finds
17     in your Bacula catalog. It will respect all the Retention periods.
18     
19     manual_prune must have access to bconsole. It will execute bconsole
20       from /opt/bacula/bin.  If bconsole is in a different location,
21       you must specify the path to it with the --bconsole=... option.
22
23     If you do not add --doprune, you will see what the script proposes
24       to do, but it will not prune.
25
26     If you add --fixerror, it will change the status of any Volume
27       that is marked Error to Used so that it will be pruned.
28
29     If you add --expired, it will attempt to prune only those 
30       Volumes where the Volume Retention period has expired.
31
32     If you use --fileprune, the script will prune files and pathvisibility
33       useful to avoid blocking Bacula during pruning.
34
35     Adding --debug will print additional debug information.
36
37
38 =head1 LICENSE
39
40    Copyright (C) 2008-2014 Bacula Systems SA
41
42    Bacula(R) is a registered trademark of Kern Sibbald.
43    The licensor of Bacula Enterprise is Bacula Systems SA,
44    Rue Galilee 5, 1400 Yverdon-les-Bains, Switzerland.
45
46   This file has been made available for your personal use in the
47   hopes that it will allow community users to make better use of
48   Bacula.
49
50 =head1 VERSION
51
52     1.4
53
54 =cut
55
56 use Getopt::Long qw/:config no_ignore_case/;
57 use Pod::Usage;
58 use File::Temp;
59
60 my $help;
61 my $do_prune;
62 my $expired;
63 my $debug;
64 # set to your bconsole prog
65 my $bconsole = "/opt/bacula/bin/bconsole";
66 my $do_fix;
67 my $do_file_prune;
68
69 GetOptions('help'       => \$help,
70            'bconsole=s' => \$bconsole,
71            'expired'    => \$expired,
72            'debug'      => \$debug,
73            'fixerror'   => \$do_fix,
74            'fileprune'  => \$do_file_prune,
75            'doprune'    => \$do_prune)
76     || Pod::Usage::pod2usage(-exitval => 2, -verbose => 2) ;
77
78 if ($help) {
79     Pod::Usage::pod2usage(-exitval => 2, -verbose => 2) ;
80 }
81
82 if (! -x $bconsole) {
83     die "Can't exec $bconsole, please specify --bconsole option $!";
84 }
85
86 my @vol;
87 my @vol_purged;
88
89 # This fix can work with File based device. Don't use it for Tape media
90 if ($do_fix) {
91     my ($fh, $file) = File::Temp::tempfile();
92     print $fh "sql
93 SELECT VolumeName AS \"?vol?\" FROM Media WHERE VolStatus = 'Error';
94
95 ";
96     close($fh);
97     open(FP, "cat $file | $bconsole|") or die "Can't open $bconsole (ERR=$!), adjust your \$PATH";
98     while (my $l = <FP>)
99     {
100         if ($l =~ /^\s*\|\s*([\w\d:\. \-]+?)\s*\|/) {
101             if ($debug) {
102                 print $l;
103             }
104             push @vol, $1;
105         } 
106     }
107     close(FP);
108     unlink($file);
109
110     if (scalar(@vol) > 0) {
111         print "Will try to fix volume in Error: ", join(",", @vol), "\n";
112         open(FP, "|$bconsole") or die "Can't send commands to $bconsole";
113         print FP map { "update volume=$_ volstatus=Used\n" } @vol; 
114         close(FP);
115         @vol = ();
116     }
117 }
118
119 if ($do_file_prune) {
120     my ($fh, $file) = File::Temp::tempfile();
121     if ($do_prune) {
122         print $fh "sql
123 BEGIN;
124 CREATE TEMPORARY TABLE temp AS
125 SELECT DISTINCT JobId FROM Job JOIN JobMedia USING (JobId) JOIN
126 (SELECT Media.MediaId  AS MediaId 
127 FROM      Media 
128 WHERE   VolStatus IN ('Full', 'Used')
129   AND (    (Media.LastWritten) 
130          +  interval '1 second' * (Media.VolRetention)
131       ) < NOW()) AS M USING (MediaId)
132     WHERE Job.JobFiles > 50000 AND Job.PurgedFiles=0;
133 SELECT JobId FROM temp;
134 DELETE FROM File WHERE JobId IN (SELECT JobId FROM temp);
135 DELETE FROM PathVisibility WHERE JobId IN (SELECT JobId FROM temp);
136 UPDATE Job SET PurgedFiles=1 WHERE JobId IN (SELECT JobId FROM temp);
137 DROP TABLE temp;
138 COMMIT;
139
140 quit
141 ";
142     } else {
143         print $fh "sql
144 SELECT DISTINCT JobId FROM Job JOIN JobMedia USING (JobId) JOIN
145 (SELECT Media.MediaId  AS MediaId 
146 FROM      Media 
147 WHERE   VolStatus IN ('Full', 'Used')
148   AND (    (Media.LastWritten) 
149          +  interval '1 second' * (Media.VolRetention)
150       ) < NOW()) AS M USING (MediaId)
151     WHERE Job.JobFiles > 50000 AND Job.PurgedFiles=0;
152
153 quit
154 ";
155     }
156     close($fh);
157     open(FP, "cat $file | $bconsole|") or die "Can't open $bconsole (ERR=$!), adjust your \$PATH";
158     while (my $l = <FP>)
159     {
160        if ($debug || !$do_prune) {
161           print $l;
162        }
163     }
164     close(FP);
165     unlink($file);
166     exit 0;
167 }
168
169 # TODO: Fix it for SQLite
170 # works only for postgresql and MySQL at the moment
171 # One of the two query will fail, but it's not a problem
172 if ($expired) {
173     my ($fh, $file) = File::Temp::tempfile();
174     print $fh "sql
175 SELECT Media.VolumeName  AS volumename, 
176        Media.LastWritten AS lastwritten,
177        (
178           (Media.LastWritten) 
179         +  interval '1 second' * (Media.VolRetention)
180        ) AS expire
181 FROM      Media 
182 WHERE   VolStatus IN ('Full', 'Used')
183   AND (    (Media.LastWritten) 
184          +  interval '1 second' * (Media.VolRetention)
185       ) < NOW();
186 SELECT Media.VolumeName  AS volumename, 
187        Media.LastWritten AS lastwritten,
188        (
189           Media.LastWritten +  Media.VolRetention
190        ) AS expire
191 FROM      Media 
192 WHERE   VolStatus IN ('Full', 'Used')
193   AND (    Media.LastWritten +  Media.VolRetention
194       ) < NOW();
195
196 quit
197 ";
198     close($fh);
199     open(FP, "cat $file | $bconsole|") or die "Can't open $bconsole (ERR=$!), adjust your \$PATH";
200     while (my $l = <FP>)
201     {
202         #  | TestVolume001 | 2011-06-17 14:36:59 | 2011-06-17 14:37:00
203         if ($l =~ /^\s*\|\s*([\w\d:\. \-]+?)\s*\|\s*\d/) {
204             if ($debug) {
205                 print $l;
206             }
207             push @vol, $1;
208         }
209     }
210     close(FP);
211     unlink($file);
212
213 } else {
214
215     open(FP, "echo list volumes | $bconsole|") or die "Can't open $bconsole (ERR=$!), adjust your \$PATH";
216     while (my $l = <FP>)
217     {
218                   # |        1 |   TestVolume001 | Used
219         if ($l =~ /^\s*\|\s*[\d,]+\s*\|\s*([\w\d-]+)\s*\|\s*Used/) {
220             push @vol, $1;
221         }
222         if ($l =~ /^\s*\|\s*[\d,]+\s*\|\s*([\w\d-]+)\s*\|\s*Full/) {
223             push @vol, $1;
224         }
225         if ($l =~ /^\s*\|\s*[\d,]+\s*\|\s*([\w\d-]+)\s*\|\s*Purged/) {
226             push @vol_purged, $1;
227         }
228     }
229     close(FP);
230
231     if ($? != 0) {
232         system("echo list volumes | $bconsole");
233         die "bconsole returns a non zero status, please check that you can execute it";
234         
235     }
236 }
237
238 if (!scalar(@vol)) {
239     print "No Volume(s) found to prune.\n";
240
241 } else {
242     if ($do_prune) {
243         print "Attempting to to prune ", join(",", @vol), "\n";
244         open(FP, "|$bconsole") or die "Can't send commands to $bconsole";
245         print FP map { "prune volume=$_ yes\n" } @vol; 
246         close(FP);
247     } else {
248         print "Would have attempted to prune ", join(",", @vol), "\n";
249         print "You can actually prune by specifying the --doprune option.\n"
250     }
251 }