4 die "bresto is not enabled" if (not $bresto_enable);
8 Bweb - A Bacula web interface
9 Bacula® - The Network Backup Solution
11 Copyright (C) 2000-2006 Free Software Foundation Europe e.V.
13 The main author of Bweb is Eric Bollengier.
14 The main author of Bacula is Kern Sibbald, with contributions from
15 many others, a complete list can be found in the file AUTHORS.
17 This program is Free Software; you can redistribute it and/or
18 modify it under the terms of version two of the GNU General Public
19 License as published by the Free Software Foundation plus additions
20 that are listed in the file LICENSE.
22 This program is distributed in the hope that it will be useful, but
23 WITHOUT ANY WARRANTY; without even the implied warranty of
24 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
25 General Public License for more details.
27 You should have received a copy of the GNU General Public License
28 along with this program; if not, write to the Free Software
29 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
32 Bacula® is a registered trademark of John Walker.
33 The licensor of Bacula is the Free Software Foundation Europe
34 (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zurich,
35 Switzerland, email:ftf@fsfeurope.org.
51 return $self->get_pathid('');
56 my ($self, $pathid) = @_;
57 $self->{cwdid} = $pathid;
65 FROM brestore_pathhierarchy
66 WHERE PathId IN ($self->{cwdid}) ";
68 my $all = $self->dbh_selectall_arrayref($query);
69 return unless ($all); # already at root
71 my $dir = join(',', map { $_->[0] } @$all);
80 return $self->get_path($self->{cwdid});
85 my ($self, $pathid) = @_;
86 $self->debug("Call with pathid = $pathid");
88 "SELECT Path FROM Path WHERE PathId IN (?)";
90 my $sth = $self->dbh_prepare($query);
91 $sth->execute($pathid);
92 my $result = $sth->fetchrow_arrayref();
99 my ($self, @jobids) = @_;
100 $self->{curjobids} = join(',', @jobids);
101 # $self->update_brestore_table(@jobids);
106 my ($self, $dir) = @_;
108 "SELECT PathId FROM Path WHERE Path = ?";
109 my $sth = $self->dbh_prepare($query);
111 my $result = $sth->fetchall_arrayref();
114 return join(',', map { $_->[0] } @$result);
119 my ($self, $offset, $limit) = @_;
120 $self->{limit} = $limit || 100;
121 $self->{offset} = $offset || 0;
128 return undef unless ($self->{curjobids});
130 my $inclause = $self->{curjobids};
131 my $inlistpath = $self->{cwdid};
134 "SELECT File.FilenameId, listfiles.id, listfiles.Name, File.LStat, File.JobId
136 (SELECT Filename.Name, max(File.FileId) as id
138 WHERE File.FilenameId = Filename.FilenameId
139 AND Filename.Name != ''
140 AND File.PathId IN ($inlistpath)
141 AND File.JobId IN ($inclause)
142 GROUP BY Filename.Name
143 ORDER BY Filename.Name) AS listfiles,
145 WHERE File.FileId = listfiles.id";
147 $self->debug($query);
148 my $result = $self->dbh_selectall_arrayref($query);
149 $self->debug($result);
155 # return ($dirid,$dir_basename,$lstat,$jobid)
160 return undef unless ($self->{curjobids});
162 my $pathid = $self->{cwdid};
163 my $jobclause = $self->{curjobids};
165 # Let's retrieve the list of the visible dirs in this dir ...
166 # First, I need the empty filenameid to locate efficiently the dirs in the file table
167 my $query = "SELECT FilenameId FROM Filename WHERE Name = ''";
168 my $sth = $self->dbh_prepare($query);
170 my $result = $sth->fetchrow_arrayref();
172 my $dir_filenameid = $result->[0];
174 # Then we get all the dir entries from File ...
176 SELECT PathId, Path, JobId, Lstat FROM (
178 SELECT Path1.PathId, Path1.Path, lower(Path1.Path),
179 listfile1.JobId, listfile1.Lstat
181 SELECT DISTINCT brestore_pathhierarchy1.PathId
182 FROM brestore_pathhierarchy AS brestore_pathhierarchy1
184 ON (brestore_pathhierarchy1.PathId = Path2.PathId)
185 JOIN brestore_pathvisibility AS brestore_pathvisibility1
186 ON (brestore_pathhierarchy1.PathId = brestore_pathvisibility1.PathId)
187 WHERE brestore_pathhierarchy1.PPathId = $pathid
188 AND brestore_pathvisibility1.jobid IN ($jobclause)) AS listpath1
189 JOIN Path AS Path1 ON (listpath1.PathId = Path1.PathId)
191 SELECT File1.PathId, File1.JobId, File1.Lstat FROM File AS File1
192 WHERE File1.FilenameId = $dir_filenameid
193 AND File1.JobId IN ($jobclause)) AS listfile1
194 ON (listpath1.PathId = listfile1.PathId)
195 ) AS A ORDER BY 2,3 DESC
197 $self->debug($query);
198 $sth=$self->dbh_prepare($query);
200 $result = $sth->fetchall_arrayref();
203 foreach my $refrow (@{$result})
205 my $dirid = $refrow->[0];
206 my $dir = $refrow->[1];
207 my $lstat = $refrow->[3];
208 my $jobid = $refrow->[2] || 0;
209 next if ($dirid eq $prev_dir);
210 # We have to clean up this dirname ... we only want it's 'basename'
214 my @temp = split ('/',$dir);
215 $return_value = pop @temp;
221 my @return_array = ($dirid,$return_value,$lstat,$jobid);
222 push @return_list,(\@return_array);
225 $self->debug(\@return_list);
226 return \@return_list;
229 # TODO : we want be able to restore files from a bad ended backup
230 # we have JobStatus IN ('T', 'A', 'E') and we must
232 # Data acces subs from here. Interaction with SGBD and caching
234 # This sub retrieves the list of jobs corresponding to the jobs selected in the
235 # GUI and stores them in @CurrentJobIds.
236 # date must be quoted
237 sub set_job_ids_for_date
239 my ($self, $client, $date)=@_;
241 if (!$client or !$date) {
245 # The algorithm : for a client, we get all the backups for each
246 # fileset, in reverse order Then, for each fileset, we store the 'good'
247 # incrementals and differentials until we have found a full so it goes
248 # like this : store all incrementals until we have found a differential
249 # or a full, then find the full #
251 my $query = "SELECT JobId, FileSet, Level, JobStatus
252 FROM Job, Client, FileSet
253 WHERE Job.ClientId = Client.ClientId
254 AND FileSet.FileSetId = Job.FileSetId
256 AND Client.Name = '$client'
258 AND JobStatus IN ('T')
259 ORDER BY FileSet, JobTDate DESC";
262 my $result = $self->dbh_selectall_arrayref($query);
264 foreach my $refrow (@$result)
266 my $jobid = $refrow->[0];
267 my $fileset = $refrow->[1];
268 my $level = $refrow->[2];
270 defined $progress{$fileset} or $progress{$fileset}='U'; # U for unknown
272 next if $progress{$fileset} eq 'F'; # It's over for this fileset...
276 next unless ($progress{$fileset} eq 'U' or $progress{$fileset} eq 'I');
277 push @CurrentJobIds,($jobid);
279 elsif ($level eq 'D')
281 next if $progress{$fileset} eq 'D'; # We allready have a differential
282 push @CurrentJobIds,($jobid);
284 elsif ($level eq 'F')
286 push @CurrentJobIds,($jobid);
289 my $status = $refrow->[3] ;
290 if ($status eq 'T') { # good end of job
291 $progress{$fileset} = $level;
295 return @CurrentJobIds;
299 # Returns list of versions of a file that could be restored
300 # returns an array of
301 # (jobid,fileindex,mtime,size,inchanger,md5,volname,fileid)
302 # there will be only one jobid in the array of jobids...
303 sub get_all_file_versions
305 my ($self,$pathid,$fileid,$client,$see_all)=@_;
307 defined $see_all or $see_all=0;
312 "SELECT File.JobId, File.FileId, File.Lstat,
313 File.Md5, Media.VolumeName, Media.InChanger
314 FROM File, Job, Client, JobMedia, Media
315 WHERE File.FilenameId = $fileid
316 AND File.PathId=$pathid
317 AND File.JobId = Job.JobId
318 AND Job.ClientId = Client.ClientId
319 AND Job.JobId = JobMedia.JobId
320 AND File.FileIndex >= JobMedia.FirstIndex
321 AND File.FileIndex <= JobMedia.LastIndex
322 AND JobMedia.MediaId = Media.MediaId
323 AND Client.Name = '$client'";
325 $self->debug($query);
327 my $result = $self->dbh_selectall_arrayref($query);
329 foreach my $refrow (@$result)
331 my ($jobid, $fid, $lstat, $md5, $volname, $inchanger) = @$refrow;
332 my @attribs = parse_lstat($lstat);
333 my $mtime = array_attrib('st_mtime',\@attribs);
334 my $size = array_attrib('st_size',\@attribs);
336 my @list = ($pathid,$fileid,$jobid,
337 $fid, $mtime, $size, $inchanger,
339 push @versions, (\@list);
342 # We have the list of all versions of this file.
343 # We'll sort it by mtime desc, size, md5, inchanger desc
344 # the rest of the algorithm will be simpler
345 # ('FILE:',filename,jobid,fileindex,mtime,size,inchanger,md5,volname)
346 @versions = sort { $b->[4] <=> $a->[4]
347 || $a->[5] <=> $b->[5]
348 || $a->[7] cmp $a->[7]
349 || $b->[6] <=> $a->[6]} @versions;
353 my %allready_seen_by_mtime;
354 my %allready_seen_by_md5;
355 # Now we should create a new array with only the interesting records
356 foreach my $ref (@versions)
360 # The file has a md5. We compare his md5 to other known md5...
361 # We take size into account. It may happen that 2 files
362 # have the same md5sum and are different. size is a supplementary
365 # If we allready have a (better) version
366 next if ( (not $see_all)
367 and $allready_seen_by_md5{$ref->[7] .'-'. $ref->[5]});
369 # we never met this one before...
370 $allready_seen_by_md5{$ref->[7] .'-'. $ref->[5]}=1;
372 # Even if it has a md5, we should also work with mtimes
373 # We allready have a (better) version
374 next if ( (not $see_all)
375 and $allready_seen_by_mtime{$ref->[4] .'-'. $ref->[5]});
376 $allready_seen_by_mtime{$ref->[4] .'-'. $ref->[5] . '-' . $ref->[7]}=1;
378 # We reached there. The file hasn't been seen.
379 push @good_versions,($ref);
382 # To be nice with the user, we re-sort good_versions by
383 # inchanger desc, mtime desc
384 @good_versions = sort { $b->[4] <=> $a->[4]
385 || $b->[2] <=> $a->[2]} @good_versions;
387 return \@good_versions;
390 my %attrib_name_id = ( 'st_dev' => 0,'st_ino' => 1,'st_mode' => 2,
391 'st_nlink' => 3,'st_uid' => 4,'st_gid' => 5,
392 'st_rdev' => 6,'st_size' => 7,'st_blksize' => 8,
393 'st_blocks' => 9,'st_atime' => 10,'st_mtime' => 11,
394 'st_ctime' => 12,'LinkFI' => 13,'st_flags' => 14,
395 'data_stream' => 15);;
398 my ($attrib,$ref_attrib)=@_;
399 return $ref_attrib->[$attrib_name_id{$attrib}];
403 { # $file = [filenameid,listfiles.id,listfiles.Name, File.LStat, File.JobId]
405 my ($file, $attrib)=@_;
407 if (defined $attrib_name_id{$attrib}) {
409 my @d = split(' ', $file->[3]) ; # TODO : cache this
411 return from_base64($d[$attrib_name_id{$attrib}]);
413 } elsif ($attrib eq 'jobid') {
417 } elsif ($attrib eq 'name') {
422 die "Attribute not known : $attrib.\n";
428 my ($lstat,$attrib)=@_;
429 if ($lstat and defined $attrib_name_id{$attrib})
431 my @d = split(' ', $lstat) ; # TODO : cache this
432 return from_base64($d[$attrib_name_id{$attrib}]);
439 # Base 64 functions, directly from recover.pl.
441 # Karl Hakimian <hakimian@aha.com>
442 # This section is also under GPL v2 or later.
449 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
450 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
451 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
452 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
453 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
455 @base64_map = (0) x 128;
457 for (my $i=0; $i<64; $i++) {
458 $base64_map[ord($base64_digits[$i])] = $i;
473 if (substr($where, 0, 1) eq '-') {
475 $where = substr($where, 1);
478 while ($where ne '') {
480 my $d = substr($where, 0, 1);
481 $val += $base64_map[ord(substr($where, 0, 1))];
482 $where = substr($where, 1);
490 my @attribs = split(' ',$lstat);
491 foreach my $element (@attribs)
493 $element = from_base64($element);
500 ################################################################
507 my $conf = new Bweb::Config(config_file => $Bweb::config_file);
510 my $bvfs = new Bvfs(info => $conf);
513 my $action = CGI::param('action') || '';
515 my $args = $bvfs->get_form('pathid', 'filenameid', 'fileid', 'qdate',
516 'limit', 'offset', 'client');
518 my @jobid = grep { /^\d+$/ } CGI::param('jobid');
520 if (!scalar(@jobid) and $args->{qdate} and $args->{client}) {
521 @jobid = $bvfs->set_job_ids_for_date($args->{client}, $args->{qdate});
522 print join(",", $args->{qdate}, $args->{client}, @jobid), "\n";
526 $bvfs->set_curjobids(@jobid);
527 $bvfs->set_limits($args->{limit}, $args->{offset});
529 print CGI::header('application/x-javascript');
531 if ($action eq 'list_client') {
533 my $q = 'SELECT Name FROM Client';
534 my $ret = $bvfs->dbh_selectall_arrayref($q);
537 print join(',', map { "['$_->[0]']" } @$ret);
541 } elsif ($action eq 'list_job') {
544 SELECT Job.EndTime, FileSet.FileSet, Job.Level, Job.JobStatus, Job.JobId
545 FROM Job,Client,FileSet
546 WHERE Job.ClientId=Client.ClientId
547 AND Client.Name = '$args->{client}'
549 AND JobStatus IN ('f', 'T')
550 AND Job.FileSetId = FileSet.FileSetId
551 ORDER BY EndTime desc";
552 my $result = $bvfs->dbh_selectall_arrayref($query);
556 print join(',', map {
557 "[$_->[4], '$_->[0]', '$_->[0] $_->[1] $_->[2] ($_->[3])']"
563 my $pathid = CGI::param('node') || '';
564 my $path = CGI::param('path');
566 if ($pathid =~ /^(\d+)$/) {
569 $pathid = $bvfs->get_pathid($path);
571 $pathid = $bvfs->get_root();
574 $bvfs->ch_dir($pathid);
576 if ($action eq 'list_files') {
578 my $files = $bvfs->ls_files();
579 # [ 1, 2, 3, "Bill", 10, '2007-01-01 00:00:00'],
580 # File.FilenameId, listfiles.id, listfiles.Name, File.LStat, File.JobId
583 map { "[$_->[1], $_->[0], $pathid, \"$_->[2]\", 10, \"2007-01-01 00:00:00\"]" }
587 } elsif ($action eq 'list_dirs') {
590 my $dirs = $bvfs->ls_dirs();
591 # return ($dirid,$dir_basename,$lstat,$jobid)
594 map { "{ 'id': '$_->[0]', 'text': '$_->[1]', 'cls':'folder'}" }
599 } elsif ($action eq 'list_versions') {
603 #($pathid,$fileid,$jobid, $fid, $mtime, $size, $inchanger, $md5, $volname);
604 my $files = $bvfs->get_all_file_versions($args->{pathid}, $args->{filenameid}, $args->{client}, 1);
606 map { "[ $_->[3], $_->[1], $_->[0], $_->[2], '$_->[8]', $_->[6], '$_->[7]', $_->[5], $_->[4] ]" }