#!/usr/bin/perl ## # bimagemgr.pl # burn manager for bacula CD image files # # Copyright (C) 2004 Kern Sibbald # # Thu Dec 09 2004 D. Scott Barninger # ASSIGNMENT OF COPYRIGHT # FOR VALUE RECEIVED, D. Scott Barninger hereby sells, transfers and # assigns unto Kern Sibbald, his successors, assigns and personal representatives, # all right, title and interest in and to the copyright in this software. # D. Scott Barninger warrants good title to said copyright, that it is # free of all liens, encumbrances or any known claims against said copyright. # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public # License version 2 as published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public # License along with this program; if not, write to the Free # Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, # MA 02111-1307, USA. ## my $VERSION = "0.2.7"; require 5.000; use strict 'vars', 'refs', 'subs'; use DBI; #-------------------------------------------------------------------# # Program Configuration ## web server configuration # # web server path to program from server root my $prog_name = "/cgi-bin/bimagemgr.pl"; # # web server host my $http_host="localhost"; # # path to graphics from document root my $logo_graphic = "/bimagemgr.gif"; my $spacer_graphic = "/clearpixel.gif"; my $burn_graphic = "/cdrom_spins.gif"; ## ## database configuration # # database name my $database = "bacula"; # # database host my $host = "backup"; # # database user my $user = "bacula"; # # database password my $password = ""; # # database driver selection - uncomment one set # MySQL my $db_driver = "mysql"; my $db_name_param = "database"; my $catalog_dump = "mysqldump --host=$host --user=$user --password=$password $database"; # Postgresql # my $db_driver = "Pg"; # my $db_name_param = "dbname"; # my $catalog_dump = "pg_dump --host=$host --username=$user --password=$password $database"; ## # path to backup files my $image_path = "/mnt/backup/backup"; ## path to cdrecord and burner settings my $cdrecord = "/usr/bin/cdrecord"; my $mkisofs = "/usr/bin/mkisofs"; my $cdburner = "1,0,0"; my $burner_speed = "40"; # burnfree option - uncomment one #my $burnfree = "driveropts=noburnfree"; # no buffer underrun protection my $burnfree = "driveropts=burnfree"; # with buffer underrun ## # temporary files my $tempfile="temp.html"; my $tempfile_path="/var/www/html/temp.html"; my $working_dir="/var/tmp"; # copyright info for page footer my $copyright = "Copyright © 2004 The Bacula Team"; #-------------------------------------------------------------------# my %input = &getcgivars; my $action = $input{'action'}; my $vol = $input{'vol'}; &Main(); exit; #-------------------------------------------------------------------# # Function Main # Description: check requested action and call appropriate subroutine # sub Main { # set default action & department if (!$action) {$action = "display"}; if ($action eq "display") { &Display(); } elsif ($action eq "burn") { &Burn(); } elsif ($action eq "reset") { &Reset(); } elsif ($action eq "version") { &DisplayHeader(); print "
"; print "
Bacula CD Image Manager version $VERSION

"; print "Copyright © 2004 D. Scott Barninger
"; print "Licensed under the GNU GPL version 2.0"; print "
"; print ""; } else { &HTMLdie("Unknown action $action","So sorry Kimosabe.."); } return; } #-------------------------------------------------------------------# # Function Display # Description: main page display routine sub Display { my ($MediaId,$VolumeName,$LastWritten,$VolWrites,$VolStatus,$data); &DisplayHeader(); &UpdateImageTable(); # connect to database my $dbh = DBI->connect("DBI:$db_driver:$db_name_param=$database;host=$host","$user","$password", {'RaiseError' => 1}) || &HTMLdie("Unable to connect to database."); my $sth = $dbh->prepare("SELECT Media.VolumeName,Media.LastWritten,CDImages.LastBurn, Media.VolWrites,Media.VolStatus FROM CDImages,Media WHERE CDImages.MediaId = Media.MediaId"); print "
"; print ""; print ""; print ""; print ""; print ""; print ""; print ""; print "
"; print "

Backup files which need to be committed to CDR disk since their last write date are shown below with a Burn button. "; print "Place a blank CDR disk in the drive and click the Burn button for the volume you wish to burn to disk.

"; print "

When CD recording is complete the popup window will display the output of cdrecord. "; print "A successful burn is indicated by the last line showing that all bytes were successfully recorded.

"; print "

After the popup window indicates that the burn is complete, close the popup and refresh this window. "; print "If the burn is not successful click the Reset link under the last burn date to restore the Burn button and refresh this window.

"; print "

To burn a copy of your catalog click the catalog Burn button at the bottom of the page. "; 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.

"; print "
 
"; print ""; print ""; print ""; print ""; print ""; print ""; print ""; print ""; print ""; print ""; print ""; print ""; $sth->execute(); while ($data = $sth->fetchrow_arrayref) { print ""; print ""; print ""; print ""; print ""; print ""; if ($$data[1] gt $$data[2] && $$data[3] gt "0" && $$data[4] ne "Purged") { print ""; } else { print ""; } print ""; } $sth->finish(); $dbh->disconnect(); print ""; print ""; print ""; print "

Current Volume Information

Make sure the backup file path $image_path is mounted.
Volume NameLast WrittenLast BurnWritesStatus 
$$data[0]$$data[1]$$data[2]
Reset
$$data[3]$$data[4]
No Burn
"; print "
"; print "

"; &DisplayFooter(); return; } #-------------------------------------------------------------------# # Function Burn # Description: burn cd images sub Burn { my $Volume = "$image_path/$vol"; # check to see if this is a catalog request if ($vol eq "bacula.sql") { $Volume = "$working_dir/bacula.sql"; } # check to see if this is a blanking request if ($vol eq "blank") { $Volume = "Blank CD/RW"; } # open the burn results file and write header info open(OUTF,">$tempfile_path") || &HTMLdie("Unable to open temporary output file."); print OUTF ""; print OUTF ""; print OUTF "Burning Volume $Volume"; print OUTF ""; print OUTF ""; print OUTF "

"; print OUTF ""; print OUTF ""; print OUTF ""; print OUTF ""; print OUTF ""; print OUTF ""; print OUTF "
"; print OUTF "
"; close(OUTF); # now send to the burn status window that we are burning print "Content-type: text/html\n\n"; print ""; print ""; print ""; print "Burning Volume $Volume"; print ""; print ""; print "
"; print ""; print ""; print ""; print ""; print ""; print ""; print ""; print ""; print ""; print ""; print ""; print ""; print "
"; print "

Now burning $Volume ...

"; print "
"; print ""; print ""; # check to see if this is a catalog request if ($vol eq "bacula.sql") { system("$catalog_dump > $working_dir/bacula.sql"); } # check to see if this is a blanking request if ($vol eq "blank") { system("$cdrecord -eject speed=2 dev=$cdburner blank=fast >> $tempfile_path"); } else { # burn the image and clean up system("$mkisofs -o $working_dir/temp.iso -J -r -V $vol $Volume"); system("$cdrecord -eject $burnfree speed=$burner_speed dev=$cdburner $working_dir/temp.iso >> $tempfile_path"); system("rm -f $working_dir/temp.iso"); } if ($vol eq "bacula.sql") { system("rm -f $working_dir/bacula.sql"); } # finish up the burn results file open(OUTF,">>$tempfile_path") || &HTMLdie("Unable to open temporary output file."); print OUTF ""; print OUTF ""; print OUTF ""; print OUTF ""; print OUTF ""; print OUTF ""; print OUTF ""; print OUTF ""; print OUTF ""; print OUTF ""; print OUTF ""; print OUTF ""; print OUTF "
If you do not see successful output from cdrecord above the burn has failed.
Please close this window and refresh the main window.
$copyright
"; print OUTF ""; print OUTF ""; print OUTF ""; close(OUTF); # now pretty up the burn results file by replacing \n with
open(INFILE, "$tempfile_path") || &HTMLdie("Unable to open input file $tempfile_path"); open(OUTFILE, ">$working_dir/bimagemgr-temp") || &HTMLdie("Unable to open output file bimagemgr-temp"); while(my $line = ) { $line =~ s/\n/
/g; print OUTFILE ($line); } close(INFILE); close(OUTFILE); system("cp -f $working_dir/bimagemgr-temp $tempfile_path"); if ($vol ne "bacula.sql" && $vol ne "blank") { ## update the burn date in the CDImages table # get current timestamp my($sysdate,$systime,$sysyear,$sysmon,$sysmday) = &SysDate; my $burndate = "$sysdate $systime"; # connect to database my $dbh = DBI->connect("DBI:$db_driver:$db_name_param=$database;host=$host","$user","$password", {'RaiseError' => 1}) || &HTMLdie("Unable to connect to database."); # get the MediaId for our volume my $sth = $dbh->prepare("SELECT MediaId from Media WHERE VolumeName = \"$vol\""); $sth->execute(); my $media_id = $sth->fetchrow_array; $sth->finish(); # set LastBurn date $dbh->do("UPDATE CDImages SET LastBurn = \"$burndate\" WHERE MediaId = $media_id"); $dbh->disconnect(); ## } return; } #-------------------------------------------------------------------# # Function UpdateImageTable # Description: update the CDImages table from the Media table sub UpdateImageTable { my ($data,@MediaId,$id,$exists,$sth1,$sth2); # connect to database my $dbh = DBI->connect("DBI:$db_driver:$db_name_param=$database;host=$host","$user","$password", {'RaiseError' => 1}) || &HTMLdie("Unable to connect to database."); # get the list of current MediaId $sth1 = $dbh->prepare("SELECT MediaId from Media"); $sth1->execute(); while ($data = $sth1->fetchrow_arrayref) { push(@MediaId,$$data[0]); } $sth1->finish(); # now check if we have a matching row in CDImages # if not then insert a record foreach $id (@MediaId) { $sth2 = $dbh->prepare("SELECT MediaId from CDImages WHERE MediaId = $id"); $sth2->execute(); $exists = $sth2->fetchrow_array; if ($exists ne $id) { $dbh->do("INSERT into CDImages VALUES ($id,\"0000-00-00 00:00:00\")"); } $sth2->finish(); } # disconnect $dbh->disconnect(); return; } #-------------------------------------------------------------------# # Function Reset # Description: reset the Last Burn date to 0 sub Reset { my ($id,$sth); # connect to database my $dbh = DBI->connect("DBI:$db_driver:$db_name_param=$database;host=$host","$user","$password", {'RaiseError' => 1}) || &HTMLdie("Unable to connect to database."); # get the MediaId $sth = $dbh->prepare("SELECT MediaId FROM Media WHERE VolumeName=\"$vol\""); $sth->execute(); $id = $sth->fetchrow_array; $sth->finish(); # reset the date $dbh->do("UPDATE CDImages SET LastBurn=\"0000-00-00 00:00:00\" WHERE MediaId=$id") || &HTMLdie("Unable to update Last Burn Date."); $dbh->disconnect(); print "Location:http://$http_host$prog_name\n\n"; } #-------------------------------------------------------------------# # Function DisplayHeader # Description: main page display header sub DisplayHeader { my($title)= @_ ; $title || ($title= "Bacula CD Image Manager") ; print "Content-type: text/html\n\n"; print ""; print ""; print "$title"; print ""; print ""; print "
"; print ""; print ""; print ""; print ""; print ""; print ""; print "
"; print "
"; print "

"; return; } #-------------------------------------------------------------------# # Function DisplayFooter # Description: main page display footer sub DisplayFooter { print ""; print ""; print ""; print ""; print ""; print ""; print "
$copyright
"; print ""; print ""; print ""; return; } #-------------------------------------------------------------------# # Function SysDate # Description: get current date/time # sub SysDate { # usage: my($sysdate,$systime,$sysyear,$sysmon,$sysmday) = &SysDate; my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)=localtime(time); if (length ($min) == 1) {$min = '0'.$min;} if (length ($sec) == 1) {$sec = '0'.$sec;} # since localtime returns the month as 0-11 $mon = $mon + 1; if (length ($mon) == 1) {$mon = '0'.$mon;} if (length ($mday) == 1) {$mday = '0'.$mday;} # since localtime returns the year as the number of years since 1900 # ie year is 100 in the year 2000 (so is y2k OK) $year = $year + 1900; my $date = "$year-$mon-$mday"; my $time = "$hour:$min:$sec"; return($date,$time,$year,$mon,$mday); } #-------------------------------------------------------------------# # Function getcgivars # Read all CGI vars into an associative array. # courtesy James Marshall james@jmarshall.com http://www.jmarshall.com/easy/cgi/ # If multiple input fields have the same name, they are concatenated into # one array element and delimited with the \0 character (which fails if # the input has any \0 characters, very unlikely but conceivably possible). # Currently only supports Content-Type of application/x-www-form-urlencoded. sub getcgivars { my($in, %in) ; my($name, $value) ; # First, read entire string of CGI vars into $in if ( ($ENV{'REQUEST_METHOD'} eq 'GET') || ($ENV{'REQUEST_METHOD'} eq 'HEAD') ) { $in= $ENV{'QUERY_STRING'} ; } elsif ($ENV{'REQUEST_METHOD'} eq 'POST') { if ($ENV{'CONTENT_TYPE'}=~ m#^application/x-www-form-urlencoded$#i) { length($ENV{'CONTENT_LENGTH'}) || &HTMLdie("No Content-Length sent with the POST request.") ; read(STDIN, $in, $ENV{'CONTENT_LENGTH'}) ; } else { &HTMLdie("Unsupported Content-Type: $ENV{'CONTENT_TYPE'}") ; } } else { &HTMLdie("Script was called with unsupported REQUEST_METHOD.") ; } # Resolve and unencode name/value pairs into %in foreach (split('&', $in)) { s/\+/ /g ; ($name, $value)= split('=', $_, 2) ; $name=~ s/%(..)/chr(hex($1))/ge ; $value=~ s/%(..)/chr(hex($1))/ge ; $in{$name}.= "\0" if defined($in{$name}) ; # concatenate multiple vars $in{$name}.= $value ; } return %in ; } #-------------------------------------------------------------------# # Function HTMLdie # Description: Die, outputting HTML error page # If no $title, use a default title sub HTMLdie { my ($msg,$title)= @_ ; $title || ($title= "CGI Error") ; print "Content-type: text/html\n\n"; print ""; print ""; print "$title"; print ""; print ""; print "

$title

"; print "

$msg

"; print "
"; print ""; print "
"; print ""; print ""; exit ; } #-------------------------------------------------------------------# # Changelog # # 0.2 14 Aug 2004 # first functional version # # 0.2.1 15 Aug 2004 # add configuration option for Postgresql driver # # 0.2.2 21 Aug 2004 # add Reset subroutine and version display # # 0.2.3 21 Aug 2004 # add burn of catalog # add instructions to the main display # # 0.2.4 23 Aug 2004 # correct equivalence operator in Burn function # # 0.2.5 28 Aug 2004 # add blank of CD/RW disk # # 0.2.6 29 Aug 2004 # add conditional in Burn() to prevent updating of CDImages # for catalog or CD/RW blanking burns # # 0.2.7 06 Nov 2005 # bug 461 - correct INSERT syntax in UpdateImageTable to # work with PostgreSQL