20d5ccd9a0767ec35a2f11ad316b0479073d2be6
[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         print " creating triggers ";
363         $dbh->do( <<__END_OF_TRIGGER__ );
364
365 create or replace function backup_parts_check() returns trigger as '
366 declare
367         b_parts integer;
368         b_counted integer;
369         b_id    integer;
370 begin
371         -- raise notice ''old/new parts %/% backup_id %/%'', old.parts, new.parts, old.id, new.id;
372         if (TG_OP=''UPDATE'') then
373                 b_id := new.id;
374                 b_parts := new.parts;
375         elsif (TG_OP = ''INSERT'') then
376                 b_id := new.id;
377                 b_parts := new.parts;
378         end if;
379         b_counted := (select count(*) from backup_parts where backup_id = b_id);
380         -- raise notice ''backup % parts %'', b_id, b_parts;
381         if ( b_parts != b_counted ) then
382                 raise exception ''Update of backup % aborted, requested % parts and there are really % parts'', b_id, b_parts, b_counted;
383         end if;
384         return null;
385 end;
386 ' language plpgsql;
387
388 create trigger do_backup_parts_check
389         after insert or update or delete on backups
390         for each row execute procedure backup_parts_check();
391
392 create or replace function backup_backup_parts_check() returns trigger as '
393 declare
394         b_id            integer;
395         my_part_nr      integer;
396         calc_part       integer;
397 begin
398         if (TG_OP = ''INSERT'') then
399                 -- raise notice ''trigger: % backup_id %'', TG_OP, new.backup_id;
400                 b_id = new.backup_id;
401                 my_part_nr = new.part_nr;
402                 execute ''update backups set parts = parts + 1 where id = '' || b_id;
403         elsif (TG_OP = ''DELETE'') then
404                 -- raise notice ''trigger: % backup_id %'', TG_OP, old.backup_id;
405                 b_id = old.backup_id;
406                 my_part_nr = old.part_nr;
407                 execute ''update backups set parts = parts - 1 where id = '' || b_id;
408         end if;
409         calc_part := (select count(part_nr) from backup_parts where backup_id = b_id);
410         if ( my_part_nr != calc_part ) then
411                 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;
412         end if;
413         return null;
414 end;
415 ' language plpgsql;
416
417 create trigger do_backup_backup_parts_check
418         after insert or update or delete on backup_parts
419         for each row execute procedure backup_backup_parts_check();
420
421 __END_OF_TRIGGER__
422
423         print "...\n";
424
425         $dbh->commit;
426
427 }
428
429 ## delete data before inseting ##
430 if ($opt{d}) {
431         print "deleting ";
432         foreach my $table (qw(files dvds backups shares hosts)) {
433                 print "$table ";
434                 $dbh->do(qq{ DELETE FROM $table });
435         }
436         print " done...\n";
437
438         $dbh->commit;
439 }
440
441 ## insert new values ##
442
443 # get hosts
444 $hosts = $bpc->HostInfoRead();
445 my $hostID;
446 my $shareID;
447
448 my $sth;
449
450 $sth->{insert_hosts} = $dbh->prepare(qq{
451 INSERT INTO hosts (name, IP) VALUES (?,?)
452 });
453
454 $sth->{hosts_by_name} = $dbh->prepare(qq{
455 SELECT ID FROM hosts WHERE name=?
456 });
457
458 $sth->{backups_count} = $dbh->prepare(qq{
459 SELECT COUNT(*)
460 FROM backups
461 WHERE hostID=? AND num=? AND shareid=?
462 });
463
464 $sth->{insert_backups} = $dbh->prepare(qq{
465 INSERT INTO backups (hostID, num, date, type, shareid, size)
466 VALUES (?,?,?,?,?,-1)
467 });
468
469 $sth->{update_backups_size} = $dbh->prepare(qq{
470 UPDATE backups SET size = ?
471 WHERE hostID = ? and num = ? and date = ? and type =? and shareid = ?
472 });
473
474 $sth->{insert_files} = $dbh->prepare(qq{
475 INSERT INTO files
476         (shareID, backupNum, name, path, date, type, size)
477         VALUES (?,?,?,?,?,?,?)
478 });
479
480 my @hosts = keys %{$hosts};
481 my $host_nr = 0;
482
483 my $host_regex;
484 if ( exists $opt{h} ) {
485         $host_regex = $opt{h};
486         $host_regex =~ s/\s+/|/g;
487         $host_regex = '^' . $host_regex . '$';
488 }
489
490 foreach my $host_key (@hosts) {
491
492         my $hostname = $hosts->{$host_key}->{'host'} || die "can't find host for $host_key";
493
494         next if $host_regex && $hostname =~ m/$host_regex/;
495
496         $sth->{hosts_by_name}->execute($hostname);
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 (defined $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                         if ($nf + $nd > 0) {
602                                 eval { hest_update($hostID, $shareID, $backupNum) };
603                                 warn "ERROR: $@" if $@;
604                         }
605                 }
606
607         }
608 }
609 undef $sth;
610 $dbh->commit();
611 $dbh->disconnect();
612
613 print "total duration: ",fmt_time(time() - $start_t),"\n";
614
615 $pidfile->remove;
616
617 sub getShareID() {
618
619         my ($share, $hostID, $hostname) = @_;
620
621         $sth->{share_id} ||= $dbh->prepare(qq{
622                 SELECT ID FROM shares WHERE hostID=? AND name=?
623         });
624
625         $sth->{share_id}->execute($hostID,$share);
626
627         my ($id) = $sth->{share_id}->fetchrow_array();
628
629         return $id if (defined($id));
630
631         $sth->{insert_share} ||= $dbh->prepare(qq{
632                 INSERT INTO shares 
633                         (hostID,name,share) 
634                 VALUES (?,?,?)
635         });
636
637         my $drop_down = $hostname . '/' . $share;
638         $drop_down =~ s#//+#/#g;
639
640         $sth->{insert_share}->execute($hostID,$share, $drop_down);
641         return $dbh->last_insert_id(undef,undef,'shares',undef);
642 }
643
644 sub found_in_db {
645
646         my @data = @_;
647         shift @data;
648
649         my ($key, $shareID,undef,$name,$path,$date,undef,$size) = @_;
650
651         return $beenThere->{$key} if (defined($beenThere->{$key}));
652
653         $sth->{file_in_db} ||= $dbh->prepare(qq{
654                 SELECT 1 FROM files
655                 WHERE shareID = ? and
656                         path = ? and 
657                         size = ? and
658                         ( date = ? or date = ? or date = ? )
659                 LIMIT 1
660         });
661
662         my @param = ($shareID,$path,$size,$date, $date-$dst_offset, $date+$dst_offset);
663         $sth->{file_in_db}->execute(@param);
664         my $rows = $sth->{file_in_db}->rows;
665         print STDERR "## found_in_db($shareID,$path,$date,$size) ",( $rows ? '+' : '-' ), join(" ",@param), "\n" if ($debug >= 3);
666
667         $beenThere->{$key}++;
668
669         $sth->{'insert_files'}->execute(@data) unless ($rows);
670         return $rows;
671 }
672
673 ####################################################
674 # recursing through filesystem structure and       #
675 # and returning flattened files list               #
676 ####################################################
677 sub recurseDir($$$$$$$$) {
678
679         my ($bpc, $hostname, $files, $backupNum, $share, $dir, $shareID) = @_;
680
681         print STDERR "\nrecurse($hostname,$backupNum,$share,$dir,$shareID)\n" if ($debug >= 1);
682
683         my ($nr_files, $new_files, $nr_dirs, $new_dirs, $size) = (0,0,0,0,0);
684
685         { # scope
686                 my @stack;
687
688                 print STDERR "# dirAttrib($backupNum, $share, $dir)\n" if ($debug >= 2);
689                 my $filesInBackup = $files->dirAttrib($backupNum, $share, $dir);
690
691                 # first, add all the entries in current directory
692                 foreach my $path_key (keys %{$filesInBackup}) {
693                         print STDERR "# file ",Dumper($filesInBackup->{$path_key}),"\n" if ($debug >= 3);
694                         my @data = (
695                                 $shareID,
696                                 $backupNum,
697                                 $path_key,
698                                 $filesInBackup->{$path_key}->{'relPath'},
699                                 $filesInBackup->{$path_key}->{'mtime'},
700                                 $filesInBackup->{$path_key}->{'type'},
701                                 $filesInBackup->{$path_key}->{'size'}
702                         );
703
704                         my $key = join(" ", (
705                                 $shareID,
706                                 $dir,
707                                 $path_key,
708                                 $filesInBackup->{$path_key}->{'mtime'},
709                                 $filesInBackup->{$path_key}->{'size'}
710                         ));
711
712                         my $key_dst_prev = join(" ", (
713                                 $shareID,
714                                 $dir,
715                                 $path_key,
716                                 $filesInBackup->{$path_key}->{'mtime'} - $dst_offset,
717                                 $filesInBackup->{$path_key}->{'size'}
718                         ));
719
720                         my $key_dst_next = join(" ", (
721                                 $shareID,
722                                 $dir,
723                                 $path_key,
724                                 $filesInBackup->{$path_key}->{'mtime'} + $dst_offset,
725                                 $filesInBackup->{$path_key}->{'size'}
726                         ));
727
728                         my $found;
729                         if (
730                                 ! defined($beenThere->{$key}) &&
731                                 ! defined($beenThere->{$key_dst_prev}) &&
732                                 ! defined($beenThere->{$key_dst_next}) &&
733                                 ! ($found = found_in_db($key, @data))
734                         ) {
735                                 print STDERR "# key: $key [", $beenThere->{$key},"]" if ($debug >= 2);
736
737                                 if ($filesInBackup->{$path_key}->{'type'} == BPC_FTYPE_DIR) {
738                                         $new_dirs++ unless ($found);
739                                         print STDERR " dir\n" if ($debug >= 2);
740                                 } else {
741                                         $new_files++ unless ($found);
742                                         print STDERR " file\n" if ($debug >= 2);
743                                 }
744                                 $size += $filesInBackup->{$path_key}->{'size'} || 0;
745                         }
746
747                         if ($filesInBackup->{$path_key}->{'type'} == BPC_FTYPE_DIR) {
748                                 $nr_dirs++;
749
750                                 my $full_path = $dir . '/' . $path_key;
751                                 push @stack, $full_path;
752                                 print STDERR "### store to stack: $full_path\n" if ($debug >= 3);
753
754 #                               my ($f,$nf,$d,$nd) = recurseDir($bpc, $hostname, $backups, $backupNum, $share, $path_key, $shareID) unless ($beenThere->{$key});
755 #
756 #                               $nr_files += $f;
757 #                               $new_files += $nf;
758 #                               $nr_dirs += $d;
759 #                               $new_dirs += $nd;
760
761                         } else {
762                                 $nr_files++;
763                         }
764                 }
765
766                 print STDERR "## STACK ",join(", ", @stack),"\n" if ($debug >= 2);
767
768                 while ( my $dir = shift @stack ) {
769                         my ($f,$nf,$d,$nd, $s) = recurseDir($bpc, $hostname, $files, $backupNum, $share, $dir, $shareID);
770                         print STDERR "# $dir f: $f nf: $nf d: $d nd: $nd\n" if ($debug >= 1);
771                         $nr_files += $f;
772                         $new_files += $nf;
773                         $nr_dirs += $d;
774                         $new_dirs += $nd;
775                         $size += $s;
776                 }
777         }
778
779         return ($nr_files, $new_files, $nr_dirs, $new_dirs, $size);
780 }
781