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