4 # burn manager for bacula CD image files
6 # Copyright (C) 2004-2006 Kern Sibbald
8 # Thu Dec 09 2004 D. Scott Barninger <barninger at fairfieldcomputers.com>
9 # ASSIGNMENT OF COPYRIGHT
10 # FOR VALUE RECEIVED, D. Scott Barninger hereby sells, transfers and
11 # assigns unto Kern Sibbald, his successors, assigns and personal representatives,
12 # all right, title and interest in and to the copyright in this software.
13 # D. Scott Barninger warrants good title to said copyright, that it is
14 # free of all liens, encumbrances or any known claims against said copyright.
16 # This program is free software; you can redistribute it and/or
17 # modify it under the terms of the GNU General Public
18 # License version 2 as published by the Free Software Foundation.
20 # This program is distributed in the hope that it will be useful,
21 # but WITHOUT ANY WARRANTY; without even the implied warranty of
22 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
23 # General Public License for more details.
25 # You should have received a copy of the GNU General Public
26 # License along with this program; if not, write to the Free
27 # Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
33 require 5.000; use strict 'vars', 'refs', 'subs';
36 # get program configuration
39 my $prog_config = config->new();
41 ## web server configuration
43 # web server path to program from server root
44 my $prog_name = $prog_config->{'prog_name'};
47 my $http_host = $prog_config->{'http_host'};
49 # path to graphics from document root
50 my $logo_graphic = $prog_config->{'logo_graphic'};
51 my $spacer_graphic = $prog_config->{'spacer_graphic'};
52 my $burn_graphic = $prog_config->{'burn_graphic'};
55 ## database configuration
58 my $database = $prog_config->{'database'} ;
61 my $host = $prog_config->{'host'};
64 my $user = $prog_config->{'user'};
67 my $password = $prog_config->{'password'};
69 # database driver selection
70 my $db_driver = $prog_config->{'db_driver'};
71 my $db_name_param = $prog_config->{'db_name_param'};
72 my $catalog_dump = $prog_config->{'catalog_dump'};
73 my $sqlitebindir = $prog_config->{'sqlitebindir'};
74 my $bacula_working_dir = $prog_config->{'bacula_working_dir'};
76 # path to backup files
77 my $image_path = $prog_config->{'image_path'};
79 ## path to cdrecord and burner settings
80 my $cdrecord = $prog_config->{'cdrecord'};
81 my $mkisofs = $prog_config->{'mkisofs'};
82 my $cdburner = $prog_config->{'cdburner'};
83 my $burner_speed = $prog_config->{'burner_speed'};
84 my $burnfree = $prog_config->{'burnfree'};
88 my $tempfile=$prog_config->{'tempfile'};
89 my $tempfile_path=$prog_config->{'tempfile_path'};
90 my $working_dir=$prog_config->{'working_dir'};
92 # copyright info for page footer
93 my $copyright = "Copyright © 2004-2006 The Bacula Team";
95 my %input = &getcgivars;
96 my $action = $input{'action'};
97 my $vol = $input{'vol'};
102 #-------------------------------------------------------------------#
104 # Description: check requested action and call appropriate subroutine
108 # set default action & department
109 if (!$action) {$action = "display"};
111 if ($action eq "display") {
114 elsif ($action eq "burn") {
117 elsif ($action eq "reset") {
120 elsif ($action eq "version") {
122 print "<div align=\"center\">";
123 print "<br>Bacula CD Image Manager version $VERSION<br><br>";
124 print "Copyright © 2004 D. Scott Barninger<br>";
125 print "Licensed under the GNU GPL version 2.0";
127 print "</body></html>";
130 &HTMLdie("Unknown action $action","So sorry Kimosabe..");
135 #-------------------------------------------------------------------#
137 # Description: main page display routine
140 my ($MediaId,$VolumeName,$LastWritten,$VolWrites,$VolStatus,$data);
145 # connect to database
147 if ( $db_driver eq "SQLite" ) {
148 $dbh = DBI->connect("DBI:$db_driver:$db_name_param=$bacula_working_dir/$database.db","","",
149 {'RaiseError' => 1}) || &HTMLdie("Unable to connect to database.");
152 $dbh = DBI->connect("DBI:$db_driver:$db_name_param=$database;host=$host","$user","$password",
153 {'RaiseError' => 1}) || &HTMLdie("Unable to connect to database.");
155 my $sth = $dbh->prepare("SELECT Media.VolumeName,Media.LastWritten,CDImages.LastBurn,
156 Media.VolWrites,Media.VolStatus
158 WHERE CDImages.MediaId = Media.MediaId");
160 print "<div align=\"center\">";
161 print "<table width=\"80%\" border=\"0\">";
163 print "<td colspan=\"6\"><b>";
164 print "<p>Backup files which need to be committed to CDR disk since their last write date are shown below with a Burn button. ";
165 print "Place a blank CDR disk in the drive and click the Burn button for the volume you wish to burn to disk.</p>";
166 print "<p>When CD recording is complete the popup window will display the output of cdrecord. ";
167 print "A successful burn is indicated by the last line showing that all bytes were successfully recorded.</p>";
168 print "<p>After the popup window indicates that the burn is complete, close the popup and <a href=\"$prog_name\">refresh this window</a>. ";
169 print "If the burn is not successful click the Reset link under the last burn date to restore the Burn button and <a href=\"$prog_name\">refresh this window</a>.</p>";
170 print "<p>To burn a copy of your catalog click the catalog Burn button at the bottom of the page. ";
171 print "Up to date copies of your catalog and all backup volumes ensure that your bacula server can be rebuilt in the event of a catastrophe.</p>";
175 print "<td colspan=\"6\"> </td>";
178 print "<table width=\"80%\" border=\"1\">";
180 print "<td align=\"center\" colspan=\"6\"><h3>Current Volume Information</h3>Make sure the backup file path $image_path is mounted.</td>";
183 print "<td align=\"center\"><b>Volume Name</b></td>";
184 print "<td align=\"center\"><b>Last Written</b></td>";
185 print "<td align=\"center\"><b>Last Burn</b></td>";
186 print "<td align=\"center\"><b>Writes</b></td>";
187 print "<td align=\"center\"><b>Status</b></td>";
188 print "<td align=\"center\"> </td>";
192 while ($data = $sth->fetchrow_arrayref) {
194 print "<td align=\"center\">$$data[0]</td>";
195 print "<td align=\"center\">$$data[1]</td>";
196 print "<td align=\"center\">$$data[2]<br><a href=\"$prog_name?action=reset&vol=$$data[0]\">Reset</a></td>";
197 print "<td align=\"center\">$$data[3]</td>";
198 print "<td align=\"center\">$$data[4]</td>";
199 if ($$data[1] gt $$data[2] && $$data[3] gt "0" && $$data[4] ne "Purged") {
200 print "<td align=\"center\"><form><input type=button value=\"Burn\" onClick=\"BurnWindow=window.open(\'$prog_name?action=burn&vol=$$data[0]\', \'BurnWindow\', \'scrollbars=yes,menubar=no,width=550,height=450,screenX=0,screenY=15\')\"></form></td>";
203 print "<td align=\"center\">No Burn</td>";
211 print "<td align=\"center\" colspan=\"6\"><img src=\"$spacer_graphic\" height=\"18\"><form><input type=button value=\"Burn Catalog\" onClick=\"BurnWindow=window.open(\'$prog_name?action=burn&vol=bacula.sql\', \'BurnWindow\', \'scrollbars=yes,menubar=no,width=550,height=450,screenX=0,screenY=15\')\"><img src=\"$spacer_graphic\" width=\"5\"><input type=button value=\"Blank CDRW\" onClick=\"BurnWindow=window.open(\'$prog_name?action=burn&vol=blank\', \'BurnWindow\', \'scrollbars=yes,menubar=no,width=550,height=450,screenX=0,screenY=15\')\"></form><img src=\"$spacer_graphic\" height=\"1\"></td>";
220 #-------------------------------------------------------------------#
222 # Description: burn cd images
226 my $Volume = "$image_path/$vol";
228 # check to see if this is a catalog request
229 if ($vol eq "bacula.sql") {
230 $Volume = "$working_dir/bacula.sql";
232 # check to see if this is a blanking request
233 if ($vol eq "blank") {
234 $Volume = "Blank CD/RW";
237 # open the burn results file and write header info
238 open(OUTF,">$tempfile_path") || &HTMLdie("Unable to open temporary output file.");
241 print OUTF "<title>Burning Volume $Volume</title>";
242 print OUTF "</head>";
244 print OUTF "<div align=\"center\">";
245 print OUTF "<table width=\"100%\">";
246 print OUTF "<tbody>";
248 print OUTF "<td align=\"center\"> <img src=\"$logo_graphic\"> </td>";
250 print OUTF "</tbody>";
251 print OUTF "</table>";
255 # now send to the burn status window that we are burning
256 print "Content-type: text/html\n\n";
259 print "<meta http-equiv=\"Refresh\" content=\"3; url=http://$http_host/$tempfile\">";
260 print "<title>Burning Volume $Volume</title>";
263 print "<div align=\"center\">";
264 print "<table width=\"100%\">";
267 print "<td align=\"center\"> <img src=\"$logo_graphic\"> </td>";
270 print "<td align=\"center\"> <img src=\"$spacer_graphic\" height=\"10\"> </td>";
273 print "<td align=\"center\"> <img src=\"$burn_graphic\"> </td>";
277 print "<p>Now burning $Volume ...</p>";
282 # check to see if this is a catalog request
283 if ($vol eq "bacula.sql") {
284 system("$catalog_dump > $working_dir/bacula.sql");
287 # check to see if this is a blanking request
288 if ($vol eq "blank") {
289 system("$cdrecord -eject speed=2 dev=$cdburner blank=fast >> $tempfile_path");
292 # burn the image and clean up
293 system("$mkisofs -o $working_dir/temp.iso -J -r -V $vol $Volume");
294 system("$cdrecord -eject $burnfree speed=$burner_speed dev=$cdburner $working_dir/temp.iso >> $tempfile_path");
295 system("rm -f $working_dir/temp.iso");
298 if ($vol eq "bacula.sql") {
299 system("rm -f $working_dir/bacula.sql");
302 # finish up the burn results file
303 open(OUTF,">>$tempfile_path") || &HTMLdie("Unable to open temporary output file.");
304 print OUTF "<table width=\"100%\">";
305 print OUTF "<tbody>";
307 print OUTF "<td><div align=\"center\">If you do not see successful output from cdrecord above the burn has failed.</div></td>";
310 print OUTF "<td><div align=\"center\">Please close this window and refresh the main window.</div></td>";
313 print OUTF "<td><div align=\"center\"><font size=\"-3\"> $copyright </font></div></td>";
315 print OUTF "</tbody>";
316 print OUTF "</table>";
318 print OUTF "</body>";
319 print OUTF "</html>";
322 # now pretty up the burn results file by replacing \n with <br>
323 open(INFILE, "$tempfile_path") || &HTMLdie("Unable to open input file $tempfile_path");
324 open(OUTFILE, ">$working_dir/bimagemgr-temp") || &HTMLdie("Unable to open output file bimagemgr-temp");
325 while(my $line = <INFILE>) {
326 $line =~ s/\n/<br>/g;
327 print OUTFILE ($line);
331 system("cp -f $working_dir/bimagemgr-temp $tempfile_path");
333 if ($vol ne "bacula.sql" && $vol ne "blank") {
334 ## update the burn date in the CDImages table
335 # get current timestamp
336 my($sysdate,$systime,$sysyear,$sysmon,$sysmday) = &SysDate;
337 my $burndate = "$sysdate $systime";
339 # connect to database
341 if ( $db_driver eq "SQLite" ) {
342 $dbh = DBI->connect("DBI:$db_driver:$db_name_param=$bacula_working_dir/$database.db","","",{'RaiseError' => 1}) || &HTMLdie("Unable to connect to database.");
345 $dbh = DBI->connect("DBI:$db_driver:$db_name_param=$database;host=$host","$user","$password",{'RaiseError' => 1}) || &HTMLdie("Unable to connect to database.");
347 # get the MediaId for our volume
348 my $sth = $dbh->prepare("SELECT MediaId from Media WHERE VolumeName = \"$vol\"");
350 my $media_id = $sth->fetchrow_array;
353 $dbh->do("UPDATE CDImages SET LastBurn = \"$burndate\" WHERE MediaId = $media_id");
361 #-------------------------------------------------------------------#
362 # Function UpdateImageTable
363 # Description: update the CDImages table from the Media table
365 sub UpdateImageTable {
367 my ($data,@MediaId,$id,$exists,$sth1,$sth2);
369 # connect to database
371 if ( $db_driver eq "SQLite" ) {
372 $dbh = DBI->connect("DBI:$db_driver:$db_name_param=$bacula_working_dir/$database.db","","",
373 {'RaiseError' => 1}) || &HTMLdie("Unable to connect to database.");
376 $dbh = DBI->connect("DBI:$db_driver:$db_name_param=$database;host=$host","$user","$password",
377 {'RaiseError' => 1}) || &HTMLdie("Unable to connect to database.");
380 # get the list of current MediaId
381 $sth1 = $dbh->prepare("SELECT MediaId from Media");
383 while ($data = $sth1->fetchrow_arrayref) {
384 push(@MediaId,$$data[0]);
388 # now check if we have a matching row in CDImages
389 # if not then insert a record
390 foreach $id (@MediaId) {
391 $sth2 = $dbh->prepare("SELECT MediaId from CDImages
392 WHERE MediaId = $id");
394 $exists = $sth2->fetchrow_array;
395 if ($exists ne $id) {
396 $dbh->do("INSERT into CDImages VALUES ($id,\"0000-00-00 00:00:00\")");
406 #-------------------------------------------------------------------#
408 # Description: reset the Last Burn date to 0
413 # connect to database
415 if ( $db_driver eq "SQLite" ) {
416 $dbh = DBI->connect("DBI:$db_driver:$db_name_param=$bacula_working_dir/$database.db","","",
417 {'RaiseError' => 1}) || &HTMLdie("Unable to connect to database.");
420 $dbh = DBI->connect("DBI:$db_driver:$db_name_param=$database;host=$host","$user","$password",
421 {'RaiseError' => 1}) || &HTMLdie("Unable to connect to database.");
425 $sth = $dbh->prepare("SELECT MediaId FROM Media WHERE VolumeName=\"$vol\"");
427 $id = $sth->fetchrow_array;
431 $dbh->do("UPDATE CDImages SET LastBurn=\"0000-00-00 00:00:00\"
432 WHERE MediaId=$id") || &HTMLdie("Unable to update Last Burn Date.");
436 print "Location:http://$http_host$prog_name\n\n";
439 #-------------------------------------------------------------------#
440 # Function DisplayHeader
441 # Description: main page display header
445 $title || ($title= "Bacula CD Image Manager") ;
446 print "Content-type: text/html\n\n";
449 print "<title>$title</title>";
452 print "<div align=\"center\">";
453 print "<table width=\"100%\">";
456 print "<td align=\"center\"><a href=\"$prog_name?action=version\" alt=\"About bimagemgr\"><img src=\"$logo_graphic\" border=\"0\"></a></td>";
465 #-------------------------------------------------------------------#
466 # Function DisplayFooter
467 # Description: main page display footer
470 print "<table width=\"100%\">";
473 print "<td><div align=\"center\"><font size=\"-3\"> $copyright </font></div></td>";
483 #-------------------------------------------------------------------#
485 # Description: get current date/time
488 # usage: my($sysdate,$systime,$sysyear,$sysmon,$sysmday) = &SysDate;
490 my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)=localtime(time);
491 if (length ($min) == 1) {$min = '0'.$min;}
492 if (length ($sec) == 1) {$sec = '0'.$sec;}
493 # since localtime returns the month as 0-11
495 if (length ($mon) == 1) {$mon = '0'.$mon;}
496 if (length ($mday) == 1) {$mday = '0'.$mday;}
497 # since localtime returns the year as the number of years since 1900
498 # ie year is 100 in the year 2000 (so is y2k OK)
499 $year = $year + 1900;
500 my $date = "$year-$mon-$mday";
501 my $time = "$hour:$min:$sec";
502 return($date,$time,$year,$mon,$mday);
505 #-------------------------------------------------------------------#
506 # Function getcgivars
507 # Read all CGI vars into an associative array.
508 # courtesy James Marshall james@jmarshall.com http://www.jmarshall.com/easy/cgi/
509 # If multiple input fields have the same name, they are concatenated into
510 # one array element and delimited with the \0 character (which fails if
511 # the input has any \0 characters, very unlikely but conceivably possible).
512 # Currently only supports Content-Type of application/x-www-form-urlencoded.
518 # First, read entire string of CGI vars into $in
519 if ( ($ENV{'REQUEST_METHOD'} eq 'GET') ||
520 ($ENV{'REQUEST_METHOD'} eq 'HEAD') ) {
521 $in= $ENV{'QUERY_STRING'} ;
523 } elsif ($ENV{'REQUEST_METHOD'} eq 'POST') {
524 if ($ENV{'CONTENT_TYPE'}=~ m#^application/x-www-form-urlencoded$#i) {
525 length($ENV{'CONTENT_LENGTH'})
526 || &HTMLdie("No Content-Length sent with the POST request.") ;
527 read(STDIN, $in, $ENV{'CONTENT_LENGTH'}) ;
530 &HTMLdie("Unsupported Content-Type: $ENV{'CONTENT_TYPE'}") ;
534 &HTMLdie("Script was called with unsupported REQUEST_METHOD.") ;
537 # Resolve and unencode name/value pairs into %in
538 foreach (split('&', $in)) {
540 ($name, $value)= split('=', $_, 2) ;
541 $name=~ s/%(..)/chr(hex($1))/ge ;
542 $value=~ s/%(..)/chr(hex($1))/ge ;
543 $in{$name}.= "\0" if defined($in{$name}) ; # concatenate multiple vars
544 $in{$name}.= $value ;
551 #-------------------------------------------------------------------#
553 # Description: Die, outputting HTML error page
554 # If no $title, use a default title
556 my ($msg,$title)= @_ ;
557 $title || ($title= "CGI Error") ;
558 print "Content-type: text/html\n\n";
561 print "<title>$title</title>";
564 print "<h1>$title</h1>";
565 print "<h3>$msg</h3>";
567 print "<input type=button name=\"BackButton\" value=\"<- Back\" id=\"Button1\" onClick=\"history.back()\">";