]> git.sur5r.net Git - bacula/bacula/blob - gui/bimagemgr/bimagemgr.pl
Update
[bacula/bacula] / gui / bimagemgr / bimagemgr.pl
1 #!/usr/bin/perl
2 ##
3 # bimagemgr.pl
4 # burn manager for bacula CD image files
5 #
6 # Copyright (C) 2004 Kern Sibbald
7 #
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.
15 #
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.
19 #
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.
24 #
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,
28 # MA 02111-1307, USA.
29 ##
30
31 my $VERSION = "0.2.7";
32
33 require 5.000; use strict 'vars', 'refs', 'subs';
34 use DBI;
35
36 #-------------------------------------------------------------------#
37 # Program Configuration
38
39 ## web server configuration
40 #
41 # web server path to program from server root
42 my $prog_name = "/cgi-bin/bimagemgr.pl";
43 #
44 # web server host
45 my $http_host="localhost";
46 #
47 # path to graphics from document root
48 my $logo_graphic = "/bimagemgr.gif";
49 my $spacer_graphic = "/clearpixel.gif";
50 my $burn_graphic = "/cdrom_spins.gif";
51 ##
52
53 ## database configuration
54 #
55 # database name
56 my $database = "bacula";
57 #
58 # database host
59 my $host = "backup";
60 #
61 # database user
62 my $user = "bacula";
63 #
64 # database password
65 my $password = "";
66 #
67 # database driver selection - uncomment one set
68 # MySQL
69 my $db_driver = "mysql";
70 my $db_name_param = "database";
71 my $catalog_dump = "mysqldump --host=$host --user=$user --password=$password $database";
72 # Postgresql
73 # my $db_driver = "Pg";
74 # my $db_name_param = "dbname";
75 # my $catalog_dump = "pg_dump --host=$host --username=$user --password=$password $database";
76 ##
77
78 # path to backup files
79 my $image_path = "/mnt/backup/backup";
80
81 ## path to cdrecord and burner settings
82 my $cdrecord = "/usr/bin/cdrecord";
83 my $mkisofs = "/usr/bin/mkisofs";
84 my $cdburner = "1,0,0";
85 my $burner_speed = "40";
86 # burnfree option - uncomment one
87 #my $burnfree = "driveropts=noburnfree"; # no buffer underrun protection
88 my $burnfree = "driveropts=burnfree"; # with buffer underrun
89 ##
90
91 # temporary files
92 my $tempfile="temp.html";
93 my $tempfile_path="/var/www/html/temp.html";
94 my $working_dir="/var/tmp";
95
96 # copyright info for page footer
97 my $copyright = "Copyright &copy; 2004 The Bacula Team";
98 #-------------------------------------------------------------------#
99
100 my %input = &getcgivars;
101 my $action = $input{'action'};
102 my $vol = $input{'vol'};
103
104 &Main();
105 exit;
106
107 #-------------------------------------------------------------------#
108 # Function Main
109 # Description: check requested action and call appropriate subroutine
110 #
111
112 sub Main {
113         # set default action & department
114         if (!$action) {$action = "display"};
115
116         if ($action eq "display") {
117                 &Display();
118         }
119         elsif ($action eq "burn") {
120                 &Burn();
121         }
122         elsif ($action eq "reset") {
123                 &Reset();
124         }
125         elsif ($action eq "version") {
126                 &DisplayHeader();
127                 print "<div align=\"center\">";
128                 print "<br>Bacula CD Image Manager version $VERSION<br><br>";
129                 print "Copyright &copy; 2004 D. Scott Barninger<br>";
130                 print "Licensed under the GNU GPL version 2.0";
131                 print "</div>";
132                 print "</body></html>";
133         }
134         else {
135                 &HTMLdie("Unknown action $action","So sorry Kimosabe..");
136         }
137         return;
138 }
139
140 #-------------------------------------------------------------------#
141 # Function Display
142 # Description: main page display routine
143
144 sub Display {
145         my ($MediaId,$VolumeName,$LastWritten,$VolWrites,$VolStatus,$data);
146
147         &DisplayHeader();
148         &UpdateImageTable();
149
150         # connect to database
151         my $dbh = DBI->connect("DBI:$db_driver:$db_name_param=$database;host=$host","$user","$password",
152                         {'RaiseError' => 1}) || &HTMLdie("Unable to connect to database.");
153         my $sth = $dbh->prepare("SELECT Media.VolumeName,Media.LastWritten,CDImages.LastBurn,
154                 Media.VolWrites,Media.VolStatus 
155                 FROM CDImages,Media 
156                 WHERE CDImages.MediaId = Media.MediaId");
157         
158         print "<div align=\"center\">";
159         print "<table width=\"80%\" border=\"0\">";
160         print "<tr>";
161         print "<td colspan=\"6\"><b>";
162         print "<p>Backup files which need to be committed to CDR disk since their last write date are shown below with a Burn button. ";
163         print "Place a blank CDR disk in the drive and click the Burn button for the volume you wish to burn to disk.</p>";
164         print "<p>When CD recording is complete the popup window will display the output of cdrecord. ";
165         print "A successful burn is indicated by the last line showing that all bytes were successfully recorded.</p>";
166         print "<p>After the popup window indicates that the burn is complete, close the popup and <a href=\"$prog_name\">refresh this window</a>. ";
167         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>";
168         print "<p>To burn a copy of your catalog click the catalog Burn button at the bottom of the page. ";
169         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>";
170         print "</b></td>";
171         print "</tr>";
172         print "<tr>";
173         print "<td colspan=\"6\">&nbsp;</td>";
174         print "</tr>";
175         print "</table>";
176         print "<table width=\"80%\" border=\"1\">";
177         print "<tr>";
178         print "<td align=\"center\" colspan=\"6\"><h3>Current Volume Information</h3>Make sure the backup file path $image_path is mounted.</td>";
179         print "</tr>";
180         print "<tr>";
181         print "<td align=\"center\"><b>Volume Name</b></td>";
182         print "<td align=\"center\"><b>Last Written</b></td>";
183         print "<td align=\"center\"><b>Last Burn</b></td>";
184         print "<td align=\"center\"><b>Writes</b></td>";
185         print "<td align=\"center\"><b>Status</b></td>";
186         print "<td align=\"center\">&nbsp;</td>";
187         print "</tr>";
188
189         $sth->execute();
190         while ($data = $sth->fetchrow_arrayref) {
191                 print "<tr>";
192                 print "<td align=\"center\">$$data[0]</td>";
193                 print "<td align=\"center\">$$data[1]</td>";
194                 print "<td align=\"center\">$$data[2]<br><a href=\"$prog_name?action=reset&vol=$$data[0]\">Reset</a></td>";
195                 print "<td align=\"center\">$$data[3]</td>";
196                 print "<td align=\"center\">$$data[4]</td>";
197                 if ($$data[1] gt $$data[2] && $$data[3] gt "0" && $$data[4] ne "Purged") {
198                         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>";
199                 }
200                 else {
201                         print "<td align=\"center\">No Burn</td>";
202                 }
203                 print "</tr>";
204         }
205         $sth->finish();
206         $dbh->disconnect();
207         
208         print "<tr>";
209         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>";
210         print "</tr>";
211         print "</table>";
212         print "</div>";
213         print "<p><p>";
214         &DisplayFooter();
215         return;
216 }
217
218 #-------------------------------------------------------------------#
219 # Function Burn
220 # Description: burn cd images
221
222 sub Burn {
223
224         my $Volume = "$image_path/$vol";
225         
226         # check to see if this is a catalog request
227         if ($vol eq "bacula.sql") {
228                 $Volume = "$working_dir/bacula.sql";
229         }
230         # check to see if this is a blanking request
231         if ($vol eq "blank") {
232                 $Volume = "Blank CD/RW";
233         }
234         
235         # open the burn results file and write header info
236         open(OUTF,">$tempfile_path") || &HTMLdie("Unable to open temporary output file.");
237         print OUTF "<html>";
238         print OUTF "<head>";
239         print OUTF "<title>Burning Volume $Volume</title>";
240         print OUTF "</head>";
241         print OUTF "<body>";
242         print OUTF "<div align=\"center\">";
243         print OUTF "<table width=\"100%\">";
244         print OUTF "<tbody>";
245         print OUTF "<tr>";
246         print OUTF "<td align=\"center\">  <img src=\"$logo_graphic\"> </td>";
247         print OUTF "</tr>";
248         print OUTF "</tbody>";
249         print OUTF "</table>";
250         print OUTF "</div>";
251         close(OUTF);
252         
253         # now send to the burn status window that we are burning
254         print "Content-type: text/html\n\n";
255         print "<html>";
256         print "<head>";
257         print "<meta http-equiv=\"Refresh\" content=\"3; url=http://$http_host/$tempfile\">";
258         print "<title>Burning Volume $Volume</title>";
259         print "</head>";
260         print "<body>";
261         print "<div align=\"center\">";
262         print "<table width=\"100%\">";
263         print "<tbody>";
264         print "<tr>";
265         print "<td align=\"center\">  <img src=\"$logo_graphic\"> </td>";
266         print "</tr>";
267         print "<tr>";
268         print "<td align=\"center\">  <img src=\"$spacer_graphic\" height=\"10\"> </td>";
269         print "</tr>";
270         print "<tr>";
271         print "<td align=\"center\">  <img src=\"$burn_graphic\"> </td>";
272         print "</tr>";
273         print "</tbody>";
274         print "</table>";
275         print "<p>Now burning $Volume ...</p>";
276         print "</div>";
277         print "</body>";
278         print "</html>";
279         
280         # check to see if this is a catalog request
281         if ($vol eq "bacula.sql") {
282                 system("$catalog_dump > $working_dir/bacula.sql");
283         }
284         
285         # check to see if this is a blanking request
286         if ($vol eq "blank") {
287                 system("$cdrecord -eject speed=2 dev=$cdburner blank=fast >> $tempfile_path");
288         }
289         else {
290         # burn the image and clean up
291         system("$mkisofs -o $working_dir/temp.iso -J -r -V $vol $Volume");
292         system("$cdrecord -eject $burnfree speed=$burner_speed dev=$cdburner $working_dir/temp.iso >> $tempfile_path");
293         system("rm -f $working_dir/temp.iso");
294         }
295         
296         if ($vol eq "bacula.sql") {
297                 system("rm -f $working_dir/bacula.sql");
298         }
299                 
300         # finish up the burn results file
301         open(OUTF,">>$tempfile_path") || &HTMLdie("Unable to open temporary output file.");
302         print OUTF "<table width=\"100%\">";
303         print OUTF "<tbody>";
304         print OUTF "<tr>";
305         print OUTF "<td><div align=\"center\">If you do not see successful output from cdrecord above the burn has failed.</div></td>";
306         print OUTF "</tr>";
307         print OUTF "<tr>";
308         print OUTF "<td><div align=\"center\">Please close this window and refresh the main window.</div></td>";
309         print OUTF "</tr>";
310         print OUTF "<tr>";
311         print OUTF "<td><div align=\"center\"><font size=\"-3\"> $copyright </font></div></td>";
312         print OUTF "</tr>";
313         print OUTF "</tbody>";
314         print OUTF "</table>";
315         print OUTF "</div>";
316         print OUTF "</body>";
317         print OUTF "</html>";
318         close(OUTF);
319         
320         # now pretty up the burn results file by replacing \n with <br>
321         open(INFILE, "$tempfile_path") || &HTMLdie("Unable to open input file $tempfile_path");
322         open(OUTFILE, ">$working_dir/bimagemgr-temp") || &HTMLdie("Unable to open output file bimagemgr-temp");
323         while(my $line = <INFILE>) {
324                 $line =~ s/\n/<br>/g;
325                 print OUTFILE ($line);
326         }
327         close(INFILE);
328         close(OUTFILE);
329         system("cp -f $working_dir/bimagemgr-temp $tempfile_path");
330         
331         if ($vol ne "bacula.sql" && $vol ne "blank") {
332                 ## update the burn date in the CDImages table
333                 # get current timestamp
334                 my($sysdate,$systime,$sysyear,$sysmon,$sysmday) = &SysDate;
335                 my $burndate = "$sysdate $systime";
336         
337                 # connect to database
338                 my $dbh = DBI->connect("DBI:$db_driver:$db_name_param=$database;host=$host","$user","$password",
339                         {'RaiseError' => 1}) || &HTMLdie("Unable to connect to database.");
340                 # get the MediaId for our volume
341                 my $sth = $dbh->prepare("SELECT MediaId from Media WHERE VolumeName = \"$vol\"");
342                 $sth->execute();
343                 my $media_id = $sth->fetchrow_array;
344                 $sth->finish();
345                 # set LastBurn date
346                 $dbh->do("UPDATE CDImages SET LastBurn = \"$burndate\" WHERE MediaId = $media_id");
347                 $dbh->disconnect();
348                 ##
349         }
350         
351         return;
352 }
353
354 #-------------------------------------------------------------------#
355 # Function UpdateImageTable
356 # Description: update the CDImages table from the Media table
357
358 sub UpdateImageTable {
359
360         my ($data,@MediaId,$id,$exists,$sth1,$sth2);
361
362         # connect to database
363         my $dbh = DBI->connect("DBI:$db_driver:$db_name_param=$database;host=$host","$user","$password",
364                         {'RaiseError' => 1}) || &HTMLdie("Unable to connect to database.");
365
366         # get the list of current MediaId
367         $sth1 = $dbh->prepare("SELECT MediaId from Media");
368         $sth1->execute();
369         while ($data = $sth1->fetchrow_arrayref) {
370                 push(@MediaId,$$data[0]);
371         }
372         $sth1->finish();
373
374         # now check if we have a matching row in CDImages
375         # if not then insert a record
376         foreach $id (@MediaId) {
377                 $sth2 = $dbh->prepare("SELECT MediaId from CDImages 
378                 WHERE MediaId = $id");
379                 $sth2->execute();
380                 $exists = $sth2->fetchrow_array;
381                 if ($exists ne $id) {
382                         $dbh->do("INSERT into CDImages VALUES ($id,\"0000-00-00 00:00:00\")");
383                 }
384                 $sth2->finish();
385         }
386
387         # disconnect
388         $dbh->disconnect();
389         return;
390 }
391
392 #-------------------------------------------------------------------#
393 # Function Reset
394 # Description: reset the Last Burn date to 0
395
396 sub Reset {
397         my ($id,$sth);
398
399         # connect to database
400         my $dbh = DBI->connect("DBI:$db_driver:$db_name_param=$database;host=$host","$user","$password",
401                         {'RaiseError' => 1}) || &HTMLdie("Unable to connect to database.");
402
403         # get the MediaId
404         $sth = $dbh->prepare("SELECT MediaId FROM Media WHERE VolumeName=\"$vol\"");
405         $sth->execute();
406         $id = $sth->fetchrow_array;
407         $sth->finish();
408         
409         # reset the date
410         $dbh->do("UPDATE CDImages SET LastBurn=\"0000-00-00 00:00:00\" 
411                         WHERE MediaId=$id") || &HTMLdie("Unable to update Last Burn Date.");
412
413         $dbh->disconnect();
414         
415         print "Location:http://$http_host$prog_name\n\n";
416 }
417
418 #-------------------------------------------------------------------#
419 # Function DisplayHeader
420 # Description: main page display header
421
422 sub DisplayHeader {
423         my($title)= @_ ;
424         $title || ($title= "Bacula CD Image Manager") ;
425         print "Content-type: text/html\n\n";
426         print "<html>";
427         print "<head>";
428         print "<title>$title</title>";
429         print "</head>";
430         print "<body>";
431         print "<div align=\"center\">";
432         print "<table width=\"100%\">";
433         print "<tbody>";
434         print "<tr>";
435         print "<td align=\"center\"><a href=\"$prog_name?action=version\" alt=\"About bimagemgr\"><img src=\"$logo_graphic\" border=\"0\"></a></td>";
436         print "</tr>";
437         print "</tbody>";
438         print "</table>";
439         print "</div>";
440         print "<p></p>";
441         return;
442 }
443
444 #-------------------------------------------------------------------#
445 # Function DisplayFooter
446 # Description: main page display footer
447
448 sub DisplayFooter {
449         print "<table width=\"100%\">";
450         print "<tbody>";
451         print "<tr>";
452         print "<td><div align=\"center\"><font size=\"-3\"> $copyright </font></div></td>";
453         print "</tr>";
454         print "</tbody>";
455         print "</table>";
456         print "</div>";
457         print "</body>";
458         print "</html>";
459         return;
460 }
461
462 #-------------------------------------------------------------------#
463 # Function SysDate
464 # Description: get current date/time
465 #
466 sub SysDate {
467         # usage:  my($sysdate,$systime,$sysyear,$sysmon,$sysmday) = &SysDate;
468
469         my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)=localtime(time);
470         if (length ($min) == 1) {$min = '0'.$min;}
471         if (length ($sec) == 1) {$sec = '0'.$sec;}
472         # since localtime returns the month as 0-11
473         $mon = $mon + 1;
474         if (length ($mon) == 1) {$mon = '0'.$mon;}
475         if (length ($mday) == 1) {$mday = '0'.$mday;}
476         # since localtime returns the year as the number of years since 1900
477         # ie year is 100 in the year 2000 (so is y2k OK)
478         $year = $year + 1900;
479         my $date = "$year-$mon-$mday";
480         my $time = "$hour:$min:$sec";
481         return($date,$time,$year,$mon,$mday);
482 }
483
484 #-------------------------------------------------------------------#
485 # Function getcgivars
486 # Read all CGI vars into an associative array.
487 # courtesy James Marshall james@jmarshall.com http://www.jmarshall.com/easy/cgi/
488 # If multiple input fields have the same name, they are concatenated into
489 #   one array element and delimited with the \0 character (which fails if
490 #   the input has any \0 characters, very unlikely but conceivably possible).
491 # Currently only supports Content-Type of application/x-www-form-urlencoded.
492 sub getcgivars {
493         my($in, %in) ;
494         my($name, $value) ;
495
496
497                 # First, read entire string of CGI vars into $in
498         if ( ($ENV{'REQUEST_METHOD'} eq 'GET') ||
499                         ($ENV{'REQUEST_METHOD'} eq 'HEAD') ) {
500                 $in= $ENV{'QUERY_STRING'} ;
501
502         } elsif ($ENV{'REQUEST_METHOD'} eq 'POST') {
503                 if ($ENV{'CONTENT_TYPE'}=~ m#^application/x-www-form-urlencoded$#i) {
504                         length($ENV{'CONTENT_LENGTH'})
505                                 || &HTMLdie("No Content-Length sent with the POST request.") ;
506                         read(STDIN, $in, $ENV{'CONTENT_LENGTH'}) ;
507
508                 } else { 
509                         &HTMLdie("Unsupported Content-Type: $ENV{'CONTENT_TYPE'}") ;
510                 }
511
512         } else {
513                 &HTMLdie("Script was called with unsupported REQUEST_METHOD.") ;
514         }
515
516                 # Resolve and unencode name/value pairs into %in
517         foreach (split('&', $in)) {
518                 s/\+/ /g ;
519                 ($name, $value)= split('=', $_, 2) ;
520                 $name=~ s/%(..)/chr(hex($1))/ge ;
521                 $value=~ s/%(..)/chr(hex($1))/ge ;
522                 $in{$name}.= "\0" if defined($in{$name}) ;  # concatenate multiple vars
523                 $in{$name}.= $value ;
524         }
525
526         return %in ;
527
528 }
529
530 #-------------------------------------------------------------------#
531 # Function HTMLdie
532 # Description: Die, outputting HTML error page
533 # If no $title, use a default title
534 sub HTMLdie {
535         my ($msg,$title)= @_ ;
536         $title || ($title= "CGI Error") ;
537         print "Content-type: text/html\n\n";
538         print "<html>";
539         print "<head>";
540         print "<title>$title</title>";
541         print "</head>";
542         print "<body>";
543         print "<h1>$title</h1>";
544         print "<h3>$msg</h3>";
545         print "<form>";
546         print "<input type=button name=\"BackButton\" value=\"<- Back\" id=\"Button1\" onClick=\"history.back()\">";
547         print "</form>";
548         print "</body>";
549         print "</html>";
550
551         exit ;
552 }
553
554 #-------------------------------------------------------------------#
555 # Changelog
556 #
557 # 0.2 14 Aug 2004
558 # first functional version
559 #
560 # 0.2.1 15 Aug 2004
561 # add configuration option for Postgresql driver
562 #
563 # 0.2.2 21 Aug 2004
564 # add Reset subroutine and version display
565 #
566 # 0.2.3 21 Aug 2004
567 # add burn of catalog
568 # add instructions to the main display
569 #
570 # 0.2.4 23 Aug 2004
571 # correct equivalence operator in Burn function
572 #
573 # 0.2.5 28 Aug 2004
574 # add blank of CD/RW disk
575 #
576 # 0.2.6 29 Aug 2004
577 # add conditional in Burn() to prevent updating of CDImages
578 # for catalog or CD/RW blanking burns
579 #
580 # 0.2.7 06 Nov 2005
581 # bug 461 - correct INSERT syntax in UpdateImageTable to
582 # work with PostgreSQL