ede665fd1cfb3f1b7f2b7ad01dfb9ca72721b6a8
[BackupPC.git] / bin / BackupPC_ASA_SearchUpdate
1 #!/usr/bin/perl
2
3 use strict;
4 use lib "/usr/local/BackupPC/lib";
5
6 use DBI;
7 use BackupPC::Lib;
8 use BackupPC::View;
9 use Data::Dumper;
10 use Getopt::Std;
11 use Time::HiRes qw/time/;
12 use File::Pid;
13 use POSIX qw/strftime/;
14 use BackupPC::Search;
15 use Cwd qw/abs_path/;
16 use Data::Dump qw(dump);
17
18 use constant BPC_FTYPE_DIR => 5;
19 use constant EST_CHUNK => 4096;
20
21 # daylight saving time change offset for 1h
22 my $dst_offset = 60 * 60;
23
24 my $debug = 0;
25 $|=1;
26
27 my $start_t = time();
28
29 my $pid_path = abs_path($0);
30 $pid_path =~ s/\W+/_/g;
31
32 my $pidfile = new File::Pid({
33         file => "/tmp/search_update.pid",
34 });
35
36 if (my $pid = $pidfile->running ) {
37         die "$0 already running: $pid\n";
38 } elsif ($pidfile->pid ne $$) {
39         $pidfile->remove;
40         $pidfile = new File::Pid;
41 }
42 print STDERR "$0 using pid ",$pidfile->pid," file ",$pidfile->file,"\n";
43 $pidfile->write;
44
45 my $t_fmt = '%Y-%m-%d %H:%M:%S';
46
47 my $hosts;
48 my $bpc = BackupPC::Lib->new || die;
49 my %Conf = $bpc->Conf();
50 my $TopDir = $bpc->TopDir();
51 my $beenThere = {};
52
53 my $dsn = $Conf{SearchDSN} || die "Need SearchDSN in config.pl\n";
54 my $user = $Conf{SearchUser} || '';
55
56 my $index_node_url = $Conf{HyperEstraierIndex};
57
58 my $dbh = DBI->connect($dsn, $user, "", { RaiseError => 1, AutoCommit => 0 });
59
60 my %opt;
61
62 if ( !getopts("cdm:v:ijfqh:", \%opt ) ) {
63         print STDERR <<EOF;
64 usage: $0 [-c|-d] [-m num] [-v|-v level] [-i|-j|-f]
65
66 Options:
67         -c      create database on first use
68         -d      delete database before import
69         -m num  import just num increments for one host
70         -h "h1 h2" import just single HostList hosts
71         -v num  set verbosity (debug) level (default $debug)
72         -i      update Hyper Estraier full text index
73         -j      update full text, don't check existing files
74         -f      don't do anything with full text index
75         -q      be quiet for hosts without changes
76
77 Option -j is variation on -i. It will allow faster initial creation
78 of full-text index from existing database.
79
80 Option -f will create database which is out of sync with full text index. You
81 will have to re-run $0 with -i to fix it.
82
83 EOF
84         exit 1;
85 }
86
87 if ($opt{v}) {
88         print "Debug level at $opt{v}\n";
89         $debug = $opt{v};
90 } elsif ($opt{f}) {
91         print "WARNING: disabling full-text index update. You need to re-run $0 -j !\n";
92         $index_node_url = undef;
93 }
94
95 #---- subs ----
96
97 sub fmt_time {
98         my $t = shift || return;
99         my $out = "";
100         my ($ss,$mm,$hh) = gmtime($t);
101         $out .= "${hh}h" if ($hh);
102         $out .= sprintf("%02d:%02d", $mm,$ss);
103         return $out;
104 }
105
106 sub curr_time {
107         return strftime($t_fmt,localtime());
108 }
109
110 sub hest_update {
111
112         my ($host_id, $share_id, $num) = @_;
113
114         my $skip_check = $opt{j} && print STDERR "Skipping check for existing files -- this should be used only with initital import\n";
115
116         print curr_time," updating fulltext:";
117
118         my $t = time();
119
120         my $offset = 0;
121         my $added = 0;
122
123         my $search = BackupPC::Search->search_module;
124
125         my $results = 0;
126
127         do {
128
129                 my $where = '';
130                 my @data;
131                 if (defined($host_id) && defined($share_id) && defined($num)) {
132                         $where = qq{
133                         WHERE
134                                 hosts.id = ? AND
135                                 shares.id = ? AND
136                                 files.backupnum = ?
137                         };
138                         @data = ( $host_id, $share_id, $num );
139                 }
140
141                 my $limit = sprintf('LIMIT '.EST_CHUNK.' OFFSET %d', $offset);
142
143                 my $sth = $dbh->prepare(qq{
144                         SELECT
145                                 files.id                        AS fid,
146                                 hosts.name                      AS hname,
147                                 shares.name                     AS sname,
148                                 -- shares.share                 AS sharename,
149                                 files.backupnum                 AS backupnum,
150                                 -- files.name                   AS filename,
151                                 files.path                      AS filepath,
152                                 files.date                      AS date,
153                                 files.type                      AS type,
154                                 files.size                      AS size,
155                                 files.shareid                   AS shareid,
156                                 backups.date                    AS backup_date
157                         FROM files 
158                                 INNER JOIN shares       ON files.shareID=shares.ID
159                                 INNER JOIN hosts        ON hosts.ID = shares.hostID
160                                 INNER JOIN backups      ON backups.num = files.backupNum and backups.hostID = hosts.ID AND backups.shareID = shares.ID
161                         $where
162                         $limit
163                 });
164
165                 $sth->execute(@data);
166                 $results = $sth->rows;
167
168                 if ($results == 0) {
169                         print " - no new files\n";
170                         return;
171                 } else {
172                         print "...";
173                 }
174
175                 sub fmt_date {
176                         my $t = shift || return;
177                         my $iso = BackupPC::Lib::timeStamp($t);
178                         $iso =~ s/\s/T/;
179                         return $iso;
180                 }
181
182                 while (my $row = $sth->fetchrow_hashref()) {
183                         next if $search->exists( $row );
184                         $search->add_doc( $row );
185                         $added++;
186                 }
187
188                 print "$added";
189
190                 $offset += EST_CHUNK;
191
192         } while ($results == EST_CHUNK);
193
194         $search->commit;
195
196         my $dur = (time() - $t) || 1;
197         printf(" [%.2f/s dur: %s]\n",
198                 ( $added / $dur ),
199                 fmt_time($dur)
200         );
201 }
202
203 #---- /subs ----
204
205
206 ## update index ##
207 if ( ( $opt{i} || $opt{j} ) && !$opt{c} ) {
208         # update all
209         print "force update of Hyper Estraier index ";
210         print "by -i flag" if ($opt{i});
211         print "by -j flag" if ($opt{j});
212         print "\n";
213         hest_update();
214 }
215
216 ## create tables ##
217 if ($opt{c}) {
218         sub do_index {
219                 my $index = shift || return;
220                 my ($table,$col,$unique) = split(/:/, $index);
221                 $unique ||= '';
222                 $index =~ s/\W+/_/g;
223                 print "$index on $table($col)" . ( $unique ? "u" : "" ) . " ";
224                 $dbh->do(qq{ create $unique index $index on $table($col) });
225         }
226
227         print "creating tables...\n";
228
229         $dbh->do( qq{
230                 create table hosts (
231                         ID      SERIAL          PRIMARY KEY,
232                         name    VARCHAR(30)     NOT NULL,
233                         IP      VARCHAR(15)
234                 );            
235
236                 create table shares (
237                         ID      SERIAL          PRIMARY KEY,
238                         hostID  INTEGER         NOT NULL references hosts(id),
239                         name    VARCHAR(30)     NOT NULL,
240                         share   VARCHAR(200)    NOT NULL
241                 );            
242
243                 create table dvds (
244                         ID      SERIAL          PRIMARY KEY, 
245                         num     INTEGER         NOT NULL,
246                         name    VARCHAR(255)    NOT NULL,
247                         mjesto  VARCHAR(255)
248                 );
249
250                 create table backups (
251                         id      serial,
252                         hostID  INTEGER         NOT NULL references hosts(id),
253                         num     INTEGER         NOT NULL,
254                         date    integer         NOT NULL, 
255                         type    CHAR(4)         not null,
256                         shareID integer         not null references shares(id),
257                         size    bigint          not null,
258                         inc_size bigint         not null default -1,
259                         inc_deleted boolean     default false,
260                         parts   integer         not null default 0,
261                         PRIMARY KEY(id)
262                 );            
263
264                 create table files (
265                         ID              SERIAL,
266                         shareID         INTEGER NOT NULL references shares(id),
267                         backupNum       INTEGER NOT NULL,
268                         name            VARCHAR(255) NOT NULL,
269                         path            VARCHAR(255) NOT NULL,
270                         date            integer NOT NULL,
271                         type            INTEGER NOT NULL,
272                         size            bigint  NOT NULL,
273                         primary key(id)
274                 );
275
276                 create table archive (
277                         id              serial,
278                         dvd_nr          int not null,
279                         total_size      bigint default -1,
280                         note            text,
281                         username        varchar(20) not null,
282                         date            timestamp default now(),
283                         primary key(id)
284                 );      
285
286                 create table archive_backup (
287                         archive_id      int not null references archive(id) on delete cascade,
288                         backup_id       int not null references backups(id),
289                         primary key(archive_id, backup_id)
290                 );
291
292                 create table archive_burned (
293                         archive_id      int references archive(id),
294                         date            timestamp default now(),
295                         part            int not null default 1,
296                         copy            int not null default 1,
297                         iso_size bigint default -1
298                 );
299
300                 create table backup_parts (
301                         id serial,
302                         backup_id int references backups(id),
303                         part_nr int not null check (part_nr > 0),
304                         tar_size bigint not null check (tar_size > 0),
305                         size bigint not null check (size > 0),
306                         md5 text not null,
307                         items int not null check (items > 0),
308                         date timestamp default now(),
309                         primary key(id)
310                 );
311
312                 -- report backups and corresponding dvd
313
314                 create view backups_on_dvds as
315                 select
316                         backups.id as id,
317                         hosts.name || ':' || shares.name as share,
318                         backups.num as num,
319                         backups.type as type,
320                         abstime(backups.date) as backup_date,
321                         backups.size as size,
322                         backups.inc_size as gzip_size,
323                         archive.id as archive_id,
324                         archive.dvd_nr
325                 from backups
326                 join shares on backups.shareid=shares.id
327                 join hosts on shares.hostid = hosts.id
328                 left outer join archive_backup on backups.id = archive_backup.backup_id
329                 left outer join archive on archive_backup.archive_id = archive.id
330                 where backups.parts > 0 and size > 0
331                 order by backups.date
332                 ;
333         });
334
335         print "creating indexes: ";
336
337         foreach my $index (qw(
338                 hosts:name
339                 backups:hostID
340                 backups:num
341                 backups:shareID
342                 shares:hostID
343                 shares:name
344                 files:shareID
345                 files:path
346                 files:name
347                 files:date
348                 files:size
349                 archive:dvd_nr
350                 archive_burned:archive_id
351                 backup_parts:backup_id,part_nr:unique
352         )) {
353                 do_index($index);
354         }
355
356         print " creating sequence: ";
357         foreach my $seq (qw/dvd_nr/) {
358                 print "$seq ";
359                 $dbh->do( qq{ CREATE SEQUENCE $seq } );
360         }
361
362 =cut
363
364         print " creating triggers ";
365         $dbh->do( <<__END_OF_TRIGGER__ );
366
367 create or replace function backup_parts_check() returns trigger as '
368 declare
369         b_parts integer;
370         b_counted integer;
371         b_id    integer;
372 begin
373         -- raise notice ''old/new parts %/% backup_id %/%'', old.parts, new.parts, old.id, new.id;
374         if (TG_OP=''UPDATE'') then
375                 b_id := new.id;
376                 b_parts := new.parts;
377         elsif (TG_OP = ''INSERT'') then
378                 b_id := new.id;
379                 b_parts := new.parts;
380         end if;
381         b_counted := (select count(*) from backup_parts where backup_id = b_id);
382         -- raise notice ''backup % parts %'', b_id, b_parts;
383         if ( b_parts != b_counted ) then
384                 raise exception ''Update of backup % aborted, requested % parts and there are really % parts'', b_id, b_parts, b_counted;
385         end if;
386         return null;
387 end;
388 ' language plpgsql;
389
390 create trigger do_backup_parts_check
391         after insert or update or delete on backups
392         for each row execute procedure backup_parts_check();
393
394 create or replace function backup_backup_parts_check() returns trigger as '
395 declare
396         b_id            integer;
397         my_part_nr      integer;
398         calc_part       integer;
399 begin
400         if (TG_OP = ''INSERT'') then
401                 -- raise notice ''trigger: % backup_id %'', TG_OP, new.backup_id;
402                 b_id = new.backup_id;
403                 my_part_nr = new.part_nr;
404                 execute ''update backups set parts = parts + 1 where id = '' || b_id;
405         elsif (TG_OP = ''DELETE'') then
406                 -- raise notice ''trigger: % backup_id %'', TG_OP, old.backup_id;
407                 b_id = old.backup_id;
408                 my_part_nr = old.part_nr;
409                 execute ''update backups set parts = parts - 1 where id = '' || b_id;
410         end if;
411         calc_part := (select count(part_nr) from backup_parts where backup_id = b_id);
412         if ( my_part_nr != calc_part ) then
413                 raise exception ''Update of backup_parts with backup_id % aborted, requested part_nr is % and calulated next is %'', b_id, my_part_nr, calc_part;
414         end if;
415         return null;
416 end;
417 ' language plpgsql;
418
419 create trigger do_backup_backup_parts_check
420         after insert or update or delete on backup_parts
421         for each row execute procedure backup_backup_parts_check();
422
423 __END_OF_TRIGGER__
424
425 =cut
426
427         print "...\n";
428
429         $dbh->commit;
430
431 }
432
433 ## delete data before inseting ##
434 if ($opt{d}) {
435         print "deleting ";
436         foreach my $table (qw(files dvds backups shares hosts)) {
437                 print "$table ";
438                 $dbh->do(qq{ DELETE FROM $table });
439         }
440         print " done...\n";
441
442         $dbh->commit;
443 }
444
445 ## insert new values ##
446
447 # get hosts
448 $hosts = $bpc->HostInfoRead();
449 my $hostID;
450 my $shareID;
451
452 my $sth;
453
454 $sth->{insert_hosts} = $dbh->prepare(qq{
455 INSERT INTO hosts (name, IP) VALUES (?,?)
456 });
457
458 $sth->{hosts_by_name} = $dbh->prepare(qq{
459 SELECT ID FROM hosts WHERE name=?
460 });
461
462 $sth->{backups_count} = $dbh->prepare(qq{
463 SELECT COUNT(*)
464 FROM backups
465 WHERE hostID=? AND num=? AND shareid=?
466 });
467
468 $sth->{insert_backups} = $dbh->prepare(qq{
469 INSERT INTO backups (hostID, num, date, type, shareid, size)
470 VALUES (?,?,?,?,?,-1)
471 });
472
473 $sth->{update_backups_size} = $dbh->prepare(qq{
474 UPDATE backups SET size = ?
475 WHERE hostID = ? and num = ? and date = ? and type =? and shareid = ?
476 });
477
478 $sth->{insert_files} = $dbh->prepare(qq{
479 INSERT INTO files
480         (shareID, backupNum, name, path, date, type, size)
481         VALUES (?,?,?,?,?,?,?)
482 });
483
484 my @hosts = keys %{$hosts};
485 my $host_nr = 0;
486
487 my $host_regex;
488 if ( exists $opt{h} ) {
489         $host_regex = $opt{h};
490         $host_regex =~ s/\s+/|/g;
491         $host_regex = '^' . $host_regex . '$';
492 }
493
494 foreach my $host_key (@hosts) {
495
496         my $hostname = $hosts->{$host_key}->{'host'} || die "can't find host for $host_key";
497
498         next if $host_regex && $hostname =~ m/$host_regex/;
499
500         $sth->{hosts_by_name}->execute($hostname);
501
502         unless (($hostID) = $sth->{hosts_by_name}->fetchrow_array()) {
503                 $sth->{insert_hosts}->execute(
504                         $hosts->{$host_key}->{'host'},
505                         $hosts->{$host_key}->{'ip'}
506                 );
507
508                 $hostID = $dbh->last_insert_id(undef,undef,'hosts',undef);
509         }
510
511         $host_nr++;
512         # get backups for a host
513         my @backups = $bpc->BackupInfoRead($hostname);
514         my $incs = scalar @backups;
515
516         my $host_header = sprintf("host %s [%d/%d]: %d increments\n",
517                 $hosts->{$host_key}->{'host'},
518                 $host_nr,
519                 ($#hosts + 1),
520                 $incs
521         );
522         print $host_header unless ($opt{q});
523  
524         my $inc_nr = 0;
525         $beenThere = {};
526
527         foreach my $backup (@backups) {
528
529                 $inc_nr++;
530                 last if (defined $opt{m} && $inc_nr > $opt{m});
531
532                 my $backupNum = $backup->{'num'};
533                 my @backupShares = ();
534
535                 my $share_header = sprintf("%-10s %2d/%-2d #%-2d %s %5s/%5s files (date: %s dur: %s)\n", 
536                         $hosts->{$host_key}->{'host'},
537                         $inc_nr, $incs, $backupNum, 
538                         $backup->{type} || '?',
539                         $backup->{nFilesNew} || '?', $backup->{nFiles} || '?',
540                         strftime($t_fmt,localtime($backup->{startTime})),
541                         fmt_time($backup->{endTime} - $backup->{startTime})
542                 );
543                 print $share_header unless ($opt{q});
544
545                 my $files = BackupPC::View->new($bpc, $hostname, \@backups, { only_first => 1 });
546
547                 foreach my $share ($files->shareList($backupNum)) {
548
549                         my $t = time();
550
551                         $shareID = getShareID($share, $hostID, $hostname);
552                 
553                         $sth->{backups_count}->execute($hostID, $backupNum, $shareID);
554                         my ($count) = $sth->{backups_count}->fetchrow_array();
555                         # skip if allready in database!
556                         next if ($count > 0);
557
558                         # dump host and share header for -q
559                         if ($opt{q}) {
560                                 if ($host_header) {
561                                         print $host_header;
562                                         $host_header = undef;
563                                 }
564                                 print $share_header;
565                         }
566
567                         # dump some log
568                         print curr_time," ", $share;
569
570                         $sth->{insert_backups}->execute(
571                                 $hostID,
572                                 $backupNum,
573                                 $backup->{'endTime'},
574                                 substr($backup->{'type'},0,4),
575                                 $shareID,
576                         );
577
578                         my ($f, $nf, $d, $nd, $size) = recurseDir($bpc, $hostname, $files, $backupNum, $share, "", $shareID);
579
580                         eval {
581                                 $sth->{update_backups_size}->execute(
582                                         $size,
583                                         $hostID,
584                                         $backupNum,
585                                         $backup->{'endTime'},
586                                         substr($backup->{'type'},0,4),
587                                         $shareID,
588                                 );
589                                 print " commit";
590                                 $dbh->commit();
591                         };
592                         if ($@) {
593                                 print " rollback";
594                                 $dbh->rollback();
595                         }
596
597                         my $dur = (time() - $t) || 1;
598                         printf(" %d/%d files %d/%d dirs %0.2f MB [%.2f/s dur: %s]\n",
599                                 $nf, $f, $nd, $d,
600                                 ($size / 1024 / 1024),
601                                 ( ($f+$d) / $dur ),
602                                 fmt_time($dur)
603                         );
604
605                         if ($nf + $nd > 0) {
606                                 eval { hest_update($hostID, $shareID, $backupNum) };
607                                 warn "ERROR: $@" if $@;
608                         }
609                 }
610
611         }
612 }
613 undef $sth;
614 $dbh->commit();
615 $dbh->disconnect();
616
617 print "total duration: ",fmt_time(time() - $start_t),"\n";
618
619 $pidfile->remove;
620
621 sub getShareID() {
622
623         my ($share, $hostID, $hostname) = @_;
624
625         $sth->{share_id} ||= $dbh->prepare(qq{
626                 SELECT ID FROM shares WHERE hostID=? AND name=?
627         });
628
629         $sth->{share_id}->execute($hostID,$share);
630
631         my ($id) = $sth->{share_id}->fetchrow_array();
632
633         return $id if (defined($id));
634
635         $sth->{insert_share} ||= $dbh->prepare(qq{
636                 INSERT INTO shares 
637                         (hostID,name,share) 
638                 VALUES (?,?,?)
639         });
640
641         my $drop_down = $hostname . '/' . $share;
642         $drop_down =~ s#//+#/#g;
643
644         $sth->{insert_share}->execute($hostID,$share, $drop_down);
645         return $dbh->last_insert_id(undef,undef,'shares',undef);
646 }
647
648 sub found_in_db {
649
650         my @data = @_;
651         shift @data;
652
653         my ($key, $shareID,undef,$name,$path,$date,undef,$size) = @_;
654
655         return $beenThere->{$key} if (defined($beenThere->{$key}));
656
657         $sth->{file_in_db} ||= $dbh->prepare(qq{
658                 SELECT 1 FROM files
659                 WHERE shareID = ? and
660                         path = ? and 
661                         size = ? and
662                         ( date = ? or date = ? or date = ? )
663                 LIMIT 1
664         });
665
666         my @param = ($shareID,$path,$size,$date, $date-$dst_offset, $date+$dst_offset);
667         $sth->{file_in_db}->execute(@param);
668         my $rows = $sth->{file_in_db}->rows;
669         print STDERR "## found_in_db($shareID,$path,$date,$size) ",( $rows ? '+' : '-' ), join(" ",@param), "\n" if ($debug >= 3);
670
671         $beenThere->{$key}++;
672
673         $sth->{'insert_files'}->execute(@data) unless ($rows);
674         return $rows;
675 }
676
677 ####################################################
678 # recursing through filesystem structure and       #
679 # and returning flattened files list               #
680 ####################################################
681 sub recurseDir($$$$$$$$) {
682
683         my ($bpc, $hostname, $files, $backupNum, $share, $dir, $shareID) = @_;
684
685         print STDERR "\nrecurse($hostname,$backupNum,$share,$dir,$shareID)\n" if ($debug >= 1);
686
687         my ($nr_files, $new_files, $nr_dirs, $new_dirs, $size) = (0,0,0,0,0);
688
689         { # scope
690                 my @stack;
691
692                 print STDERR "# dirAttrib($backupNum, $share, $dir)\n" if ($debug >= 2);
693                 my $filesInBackup = $files->dirAttrib($backupNum, $share, $dir);
694
695                 # first, add all the entries in current directory
696                 foreach my $path_key (keys %{$filesInBackup}) {
697                         print STDERR "# file ",Dumper($filesInBackup->{$path_key}),"\n" if ($debug >= 3);
698                         my @data = (
699                                 $shareID,
700                                 $backupNum,
701                                 $path_key,
702                                 $filesInBackup->{$path_key}->{'relPath'},
703                                 $filesInBackup->{$path_key}->{'mtime'},
704                                 $filesInBackup->{$path_key}->{'type'},
705                                 $filesInBackup->{$path_key}->{'size'}
706                         );
707
708                         my $key = join(" ", (
709                                 $shareID,
710                                 $dir,
711                                 $path_key,
712                                 $filesInBackup->{$path_key}->{'mtime'},
713                                 $filesInBackup->{$path_key}->{'size'}
714                         ));
715
716                         my $key_dst_prev = join(" ", (
717                                 $shareID,
718                                 $dir,
719                                 $path_key,
720                                 $filesInBackup->{$path_key}->{'mtime'} - $dst_offset,
721                                 $filesInBackup->{$path_key}->{'size'}
722                         ));
723
724                         my $key_dst_next = join(" ", (
725                                 $shareID,
726                                 $dir,
727                                 $path_key,
728                                 $filesInBackup->{$path_key}->{'mtime'} + $dst_offset,
729                                 $filesInBackup->{$path_key}->{'size'}
730                         ));
731
732                         my $found;
733                         if (
734                                 ! defined($beenThere->{$key}) &&
735                                 ! defined($beenThere->{$key_dst_prev}) &&
736                                 ! defined($beenThere->{$key_dst_next}) &&
737                                 ! ($found = found_in_db($key, @data))
738                         ) {
739                                 print STDERR "# key: $key [", $beenThere->{$key},"]" if ($debug >= 2);
740
741                                 if ($filesInBackup->{$path_key}->{'type'} == BPC_FTYPE_DIR) {
742                                         $new_dirs++ unless ($found);
743                                         print STDERR " dir\n" if ($debug >= 2);
744                                 } else {
745                                         $new_files++ unless ($found);
746                                         print STDERR " file\n" if ($debug >= 2);
747                                 }
748                                 $size += $filesInBackup->{$path_key}->{'size'} || 0;
749                         }
750
751                         if ($filesInBackup->{$path_key}->{'type'} == BPC_FTYPE_DIR) {
752                                 $nr_dirs++;
753
754                                 my $full_path = $dir . '/' . $path_key;
755                                 push @stack, $full_path;
756                                 print STDERR "### store to stack: $full_path\n" if ($debug >= 3);
757
758 #                               my ($f,$nf,$d,$nd) = recurseDir($bpc, $hostname, $backups, $backupNum, $share, $path_key, $shareID) unless ($beenThere->{$key});
759 #
760 #                               $nr_files += $f;
761 #                               $new_files += $nf;
762 #                               $nr_dirs += $d;
763 #                               $new_dirs += $nd;
764
765                         } else {
766                                 $nr_files++;
767                         }
768                 }
769
770                 print STDERR "## STACK ",join(", ", @stack),"\n" if ($debug >= 2);
771
772                 while ( my $dir = shift @stack ) {
773                         my ($f,$nf,$d,$nd, $s) = recurseDir($bpc, $hostname, $files, $backupNum, $share, $dir, $shareID);
774                         print STDERR "# $dir f: $f nf: $nf d: $d nd: $nd\n" if ($debug >= 1);
775                         $nr_files += $f;
776                         $new_files += $nf;
777                         $nr_dirs += $d;
778                         $new_dirs += $nd;
779                         $size += $s;
780                 }
781         }
782
783         return ($nr_files, $new_files, $nr_dirs, $new_dirs, $size);
784 }
785