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