* Added some performance improvements to BackupPC::Xfer::RsyncFileIO
authorcbarratt <cbarratt>
Sun, 15 Apr 2007 07:28:39 +0000 (07:28 +0000)
committercbarratt <cbarratt>
Sun, 15 Apr 2007 07:28:39 +0000 (07:28 +0000)
  for the case of small files with cached checksums.

* Added optional support for IO::Dirent which allows inode information
  to be extracted from the dirent directory structure.  This allows
  BackupPC to order some directory operations by inode, which on
  some file systems (eg: ext3) can results in a 20-30% performance
  gain.  On other file systems there is no real improvement.  This
  optimization is turned on automatically if IO::Dirent is installed.

* Added sorting by column feature to host summary table in CGI
  interface.  Implemented by Jeremy Tietsort.

* Added FreeBSD init.d file provided by Gabriel Rossetti.

* Applied small patch from Sergey to lib/BackupPC/Xfer/Tar.pm that makes
  it ignore "socket ignored" error on incrementals.

* Applied small patch from Sergey to bin/BackupPC_archiveHost.

* Added RsyncdUserName to the config editor.  Reported by Vicent Roca Daniel.

* configure.pl clears $Conf{ParPath} if it doesn't point to a valid
  executable.

* Added freebsd-backuppc init.d script from Gabriel Rossetti.

* Added documentation for BackupPC_tarPCCopy, including use of -P option
  to tar suggested by Daniel Berteaud.

* Config editor now removes white space at start of exec path.
  Reported by Christoph Iwasjuta.

* CgiDateFormatMMDD == 2 gives a YYYY-MM-DD format for CGI dates,
  suggested by Imre.

30 files changed:
ChangeLog
bin/BackupPC_archiveHost
bin/BackupPC_dump
bin/BackupPC_nightly
bin/BackupPC_tarPCCopy
conf/BackupPC_stnd.css
conf/config.pl
conf/sorttable.js [new file with mode: 0644]
configure.pl
doc-src/BackupPC.pod
init.d/README
init.d/src/freebsd-backuppc [new file with mode: 0644]
lib/BackupPC/CGI/Browse.pm
lib/BackupPC/CGI/DirHistory.pm
lib/BackupPC/CGI/EditConfig.pm
lib/BackupPC/CGI/Lib.pm
lib/BackupPC/CGI/Summary.pm
lib/BackupPC/Config/Meta.pm
lib/BackupPC/Lang/de.pm
lib/BackupPC/Lang/en.pm
lib/BackupPC/Lang/es.pm
lib/BackupPC/Lang/fr.pm
lib/BackupPC/Lang/it.pm
lib/BackupPC/Lang/nl.pm
lib/BackupPC/Lang/pt_br.pm
lib/BackupPC/Lib.pm
lib/BackupPC/View.pm
lib/BackupPC/Xfer/RsyncDigest.pm
lib/BackupPC/Xfer/Tar.pm
makeDist

index 6a9400e..ffcc7ca 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
 # Version __VERSION__, __RELEASEDATE__
 #------------------------------------------------------------------------
 
+* Added some performance improvements to BackupPC::Xfer::RsyncFileIO
+  for the case of small files with cached checksums.
+
+* Added optional support for IO::Dirent which allows inode information
+  to be extracted from the dirent directory structure.  This allows
+  BackupPC to order some directory operations by inode, which on
+  some file systems (eg: ext3) can results in a 20-30% performance
+  gain.  On other file systems there is no real improvement.  This
+  optimization is turned on automatically if IO::Dirent is installed.
+
+* Added sorting by column feature to host summary table in CGI
+  interface.  Implemented by Jeremy Tietsort.
+
+* Added FreeBSD init.d file provided by Gabriel Rossetti.
+
+* Applied small patch from Sergey to lib/BackupPC/Xfer/Tar.pm that makes
+  it ignore "socket ignored" error on incrementals.
+
+* Applied small patch from Sergey to bin/BackupPC_archiveHost.
+
+* Added RsyncdUserName to the config editor.  Reported by Vicent Roca Daniel.
+
+* configure.pl clears $Conf{ParPath} if it doesn't point to a valid
+  executable.
+
+* Added freebsd-backuppc init.d script from Gabriel Rossetti.
+
+* Added documentation for BackupPC_tarPCCopy, including use of -P option
+  to tar suggested by Daniel Berteaud.
+
+* Config editor now removes white space at start of exec path.
+  Reported by Christoph Iwasjuta.
+
+* CgiDateFormatMMDD == 2 gives a YYYY-MM-DD format for CGI dates,
+  suggested by Imre.
+
+#------------------------------------------------------------------------
+# Version 3.0.0, 28 Jan 2007
+#------------------------------------------------------------------------
+
 * BackupPC_sendEmail now correctly sends admin email if backups
   were skipped because the disk was too full, reported by Dan
   Pritts.
index 330abec..8e6c483 100755 (executable)
@@ -105,7 +105,9 @@ if ( -x "/bin/csh" ) {
     exit(1);
 }
 my $cmd = "$tarCreate -t -h $host -n $bkupNum -s $share . ";
-$cmd   .= "| $compPath " if ( $compPath ne "cat" && $compPath ne "" );
+$cmd   .= "| $compPath " if ( $compPath ne "cat"
+                           && $compPath ne "/bin/cat
+                           && $compPath ne "" );
 if ( -b $outLoc || -c $outLoc || -f $outLoc ) {
     #
     # Output file is a device or a regular file, so don't use split
@@ -133,7 +135,7 @@ print("$mesg\n");
 #
 my $ret = system(@shell, $cmd);
 if ( $ret ) {
-    print("Executing: @shell -cf $cmd\n");
+    print("Executing: @shell $cmd\n");
     print("Error: $tarCreate, compress or split failed\n");
     exit(1);
 }
index 9235a4c..28f3ef7 100755 (executable)
@@ -875,7 +875,7 @@ for my $shareName ( @$ShareNames ) {
     $stat{xferOK} = 0 if ( $stat{hostError} || $stat{hostAbort} );
     if ( !$stat{xferOK} ) {
         #
-        # kill off the tranfer program, first nicely then forcefully
+        # kill off the transfer program, first nicely then forcefully
         #
        if ( @xferPid ) {
            kill($bpc->sigName2num("INT"), @xferPid);
index 19d1879..22782d1 100755 (executable)
 use strict;
 no  utf8;
 use lib "/usr/local/BackupPC/lib";
-use BackupPC::Lib;
+use BackupPC::Lib qw( :BPC_DT_ALL );
 use BackupPC::FileZIO;
 use Getopt::Std;
 
-use File::Find;
 use File::Path;
 use Data::Dumper;
 
@@ -153,7 +152,7 @@ for my $pool ( qw(pool cpool) ) {
         $fileLinkMax   = 0;
         $fileCntRename = 0;
         %FixList       = ();
-        find({wanted => \&GetPoolStats}, "$TopDir/$pool/$dir")
+        $bpc->find({wanted => \&GetPoolStats}, "$TopDir/$pool/$dir")
                                            if ( -d "$TopDir/$pool/$dir" );
         my $kb   = $blkCnt / 2;
         my $kbRm = $blkCntRm / 2;
@@ -260,7 +259,8 @@ sub doBackupInfoUpdate
 
 sub GetPoolStats
 {
-    my($inode, $nlinks, $nblocks) = (lstat($_))[1, 3, 12];
+    my($file, $fullPath) = @_;
+    my($inode, $nlinks, $nblocks) = (lstat($file))[1, 3, 12];
  
     if ( -d _ ) {
         $dirCnt++;
@@ -279,7 +279,6 @@ sub GetPoolStats
         # pool files vs removing pool files.  (Other aspects of the
         # design should eliminate race conditions.)
         #
-        my $fullPath = $File::Find::name;
         push(@PendingDelete, {
                     inode => $inode,
                     path  => $fullPath
index 6dbf954..b5194d3 100755 (executable)
@@ -6,6 +6,8 @@
 # contain hardlinks to the pool directory, which should be copied
 # before BackupPC_tarPCCopy is run.
 #
+# See the documentation for use.
+#
 # DESCRIPTION
 #  
 #   Usage: BackupPC_tarPCCopy [options] files/directories...
index a50bcab..3bd887a 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * BackupPC standard CSS definitions
  *
- * Version 3.0.0beta2, released 31 Oct 2006.
+ * Version 3.0.0, released 28 Jan 2007.
  *
  * See http://backuppc.sourceforge.net.
  *
@@ -95,6 +95,13 @@ a.navbar {
     background-color:#eeeeee;
 }
 
+table.sortable a.sortheader {
+    background-color:#eeeeee;
+    font-weight: bold;
+    text-decoration: none;
+    display: block;
+}
+
 .border {
     font-size:10pt;
 }
index 9310157..eac455e 100644 (file)
@@ -29,7 +29,7 @@
 #   Craig Barratt  <cbarratt@users.sourceforge.net>
 #
 # COPYRIGHT
-#   Copyright (C) 2001-2003  Craig Barratt
+#   Copyright (C) 2001-2007  Craig Barratt
 #
 #   See http://backuppc.sourceforge.net.
 #
@@ -103,19 +103,17 @@ $Conf{UmaskMode} = 027;
 #
 # Examples:
 #     $Conf{WakeupSchedule} = [22.5];         # once per day at 10:30 pm.
-#     $Conf{WakeupSchedule} = [1..23];        # every hour except midnight
 #     $Conf{WakeupSchedule} = [2,4,6,8,10,12,14,16,18,20,22];  # every 2 hours
 #
 # The default value is every hour except midnight.
 #
-# The first entry of $Conf{WakeupSchedule} is when BackupPC_nightly
-# is run.  No other backups can run while BackupPC_nightly is
-# running.  You might want to re-arrange the entries in
-# $Conf{WakeupSchedule} (they don't have to be ascending) so that
-# the first entry is when you want BackupPC_nightly to run
-# (eg: when you don't expect a lot of regular backups to run).
+# The first entry of $Conf{WakeupSchedule} is when BackupPC_nightly is run.
+# You might want to re-arrange the entries in $Conf{WakeupSchedule}
+# (they don't have to be ascending) so that the first entry is when
+# you want BackupPC_nightly to run (eg: when you don't expect a lot
+# of regular backups to run).
 #
-$Conf{WakeupSchedule} = [1..23];
+$Conf{WakeupSchedule} = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23];
 
 #
 # Maximum number of simultaneous backups to run.  If there
@@ -1897,8 +1895,9 @@ $Conf{CgiUserHomePageCheck} = '';
 $Conf{CgiUserUrlCreate}     = 'mailto:%s';
 
 #
-# Date display format for CGI interface.  True for US-style dates (MM/DD)
-# and zero for international dates (DD/MM).
+# Date display format for CGI interface.  A value of 1 uses US-style
+# dates (MM/DD), a value of 2 uses full YYYY-MM-DD format, and zero
+# for international dates (DD/MM).
 #
 $Conf{CgiDateFormatMMDD} = 1;
 
@@ -2052,6 +2051,7 @@ $Conf{CgiUserConfigEdit} = {
         RsyncShareName            => 1,
         RsyncdClientPort          => 1,
         RsyncdPasswd              => 1,
+        RsyncdUserName            => 1,
         RsyncdAuthRequired        => 1,
         RsyncCsumCacheVerifyProb  => 1,
         RsyncArgs                 => 1,
diff --git a/conf/sorttable.js b/conf/sorttable.js
new file mode 100644 (file)
index 0000000..32197c9
--- /dev/null
@@ -0,0 +1,188 @@
+// Used with under license grant from http://kryogenix.org/code/browser/sorttable/
+// Credit for this code goes to Stuart Langridge - http://kryogenix.org/contact
+
+addEvent(window, "load", sortables_init);
+
+var SORT_COLUMN_INDEX;
+
+function sortables_init() {
+    // Find all tables with class sortable and make them sortable
+    if (!document.getElementsByTagName) return;
+    tbls = document.getElementsByTagName("table");
+    for (ti=0;ti<tbls.length;ti++) {
+        thisTbl = tbls[ti];
+        if (((' '+thisTbl.className+' ').indexOf("sortable") != -1) && (thisTbl.id)) {
+            //initTable(thisTbl.id);
+            ts_makeSortable(thisTbl);
+        }
+    }
+}
+
+function ts_makeSortable(table) {
+    if (table.rows && table.rows.length > 0) {
+        var firstRow = table.rows[0];
+    }
+    if (!firstRow) return;
+    
+    // We have a first row: assume it's the header, and make its contents clickable links
+    for (var i=0;i<firstRow.cells.length;i++) {
+        var cell = firstRow.cells[i];
+        var txt = ts_getInnerText(cell);
+        cell.innerHTML = '<a href="#" class="sortheader" '+ 
+        'onclick="ts_resortTable(this, '+i+');return false;">' + 
+        txt+'<span class="sortarrow">&nbsp;&nbsp;&nbsp;</span></a>';
+    }
+}
+
+function ts_getInnerText(el) {
+       if (typeof el == "string") return el;
+       if (typeof el == "undefined") { return el };
+       if (el.innerText) return el.innerText;  //Not needed but it is faster
+       var str = "";
+       
+       var cs = el.childNodes;
+       var l = cs.length;
+       for (var i = 0; i < l; i++) {
+               switch (cs[i].nodeType) {
+                       case 1: //ELEMENT_NODE
+                               str += ts_getInnerText(cs[i]);
+                               break;
+                       case 3: //TEXT_NODE
+                               str += cs[i].nodeValue;
+                               break;
+               }
+       }
+       return str;
+}
+
+function ts_resortTable(lnk,clid) {
+    // get the span
+    var span;
+    for (var ci=0;ci<lnk.childNodes.length;ci++) {
+        if (lnk.childNodes[ci].tagName && lnk.childNodes[ci].tagName.toLowerCase() == 'span') span = lnk.childNodes[ci];
+    }
+    var spantext = ts_getInnerText(span);
+    var td = lnk.parentNode;
+    var column = clid || td.cellIndex;
+    var table = getParent(td,'TABLE');
+    
+    // Work out a type for the column
+    if (table.rows.length <= 1) return;
+    var itm = ts_getInnerText(table.rows[1].cells[column]);
+    sortfn = ts_sort_caseinsensitive;
+    if (itm.match(/^\d\d[\/-]\d\d[\/-]\d\d\d\d$/)) sortfn = ts_sort_date;
+    if (itm.match(/^\d\d[\/-]\d\d[\/-]\d\d$/)) sortfn = ts_sort_date;
+    if (itm.match(/^[£$]/)) sortfn = ts_sort_currency;
+    if (itm.match(/^[\d\.]+$/)) sortfn = ts_sort_numeric;
+    SORT_COLUMN_INDEX = column;
+    var firstRow = new Array();
+    var newRows = new Array();
+    for (i=0;i<table.rows[0].length;i++) { firstRow[i] = table.rows[0][i]; }
+    for (j=1;j<table.rows.length;j++) { newRows[j-1] = table.rows[j]; }
+
+    newRows.sort(sortfn);
+
+    if (span.getAttribute("sortdir") == 'down') {
+        ARROW = '&nbsp;&nbsp;&uarr;';
+        newRows.reverse();
+        span.setAttribute('sortdir','up');
+    } else {
+        ARROW = '&nbsp;&nbsp;&darr;';
+        span.setAttribute('sortdir','down');
+    }
+    
+    // We appendChild rows that already exist to the tbody, so it moves them rather than creating new ones
+    // don't do sortbottom rows
+    for (i=0;i<newRows.length;i++) { if (!newRows[i].className || (newRows[i].className && (newRows[i].className.indexOf('sortbottom') == -1))) table.tBodies[0].appendChild(newRows[i]);}
+    // do sortbottom rows only
+    for (i=0;i<newRows.length;i++) { if (newRows[i].className && (newRows[i].className.indexOf('sortbottom') != -1)) table.tBodies[0].appendChild(newRows[i]);}
+    
+    // Delete any other arrows there may be showing
+    var allspans = document.getElementsByTagName("span");
+    for (var ci=0;ci<allspans.length;ci++) {
+        if (allspans[ci].className == 'sortarrow') {
+            if (getParent(allspans[ci],"table") == getParent(lnk,"table")) { // in the same table as us?
+                allspans[ci].innerHTML = '&nbsp;&nbsp;&nbsp;';
+            }
+        }
+    }
+        
+    span.innerHTML = ARROW;
+}
+
+function getParent(el, pTagName) {
+       if (el == null) return null;
+       else if (el.nodeType == 1 && el.tagName.toLowerCase() == pTagName.toLowerCase())        // Gecko bug, supposed to be uppercase
+               return el;
+       else
+               return getParent(el.parentNode, pTagName);
+}
+function ts_sort_date(a,b) {
+    // y2k notes: two digit years less than 50 are treated as 20XX, greater than 50 are treated as 19XX
+    aa = ts_getInnerText(a.cells[SORT_COLUMN_INDEX]);
+    bb = ts_getInnerText(b.cells[SORT_COLUMN_INDEX]);
+    if (aa.length == 10) {
+        dt1 = aa.substr(6,4)+aa.substr(3,2)+aa.substr(0,2);
+    } else {
+        yr = aa.substr(6,2);
+        if (parseInt(yr) < 50) { yr = '20'+yr; } else { yr = '19'+yr; }
+        dt1 = yr+aa.substr(3,2)+aa.substr(0,2);
+    }
+    if (bb.length == 10) {
+        dt2 = bb.substr(6,4)+bb.substr(3,2)+bb.substr(0,2);
+    } else {
+        yr = bb.substr(6,2);
+        if (parseInt(yr) < 50) { yr = '20'+yr; } else { yr = '19'+yr; }
+        dt2 = yr+bb.substr(3,2)+bb.substr(0,2);
+    }
+    if (dt1==dt2) return 0;
+    if (dt1<dt2) return -1;
+    return 1;
+}
+
+function ts_sort_currency(a,b) { 
+    aa = ts_getInnerText(a.cells[SORT_COLUMN_INDEX]).replace(/[^0-9.]/g,'');
+    bb = ts_getInnerText(b.cells[SORT_COLUMN_INDEX]).replace(/[^0-9.]/g,'');
+    return parseFloat(aa) - parseFloat(bb);
+}
+
+function ts_sort_numeric(a,b) { 
+    aa = parseFloat(ts_getInnerText(a.cells[SORT_COLUMN_INDEX]));
+    if (isNaN(aa)) aa = 0;
+    bb = parseFloat(ts_getInnerText(b.cells[SORT_COLUMN_INDEX])); 
+    if (isNaN(bb)) bb = 0;
+    return aa-bb;
+}
+
+function ts_sort_caseinsensitive(a,b) {
+    aa = ts_getInnerText(a.cells[SORT_COLUMN_INDEX]).toLowerCase();
+    bb = ts_getInnerText(b.cells[SORT_COLUMN_INDEX]).toLowerCase();
+    if (aa==bb) return 0;
+    if (aa<bb) return -1;
+    return 1;
+}
+
+function ts_sort_default(a,b) {
+    aa = ts_getInnerText(a.cells[SORT_COLUMN_INDEX]);
+    bb = ts_getInnerText(b.cells[SORT_COLUMN_INDEX]);
+    if (aa==bb) return 0;
+    if (aa<bb) return -1;
+    return 1;
+}
+
+
+function addEvent(elm, evType, fn, useCapture)
+// addEvent and removeEvent
+// cross-browser event handling for IE5+,  NS6 and Mozilla
+// By Scott Andrew
+{
+  if (elm.addEventListener){
+    elm.addEventListener(evType, fn, useCapture);
+    return true;
+  } else if (elm.attachEvent){
+    var r = elm.attachEvent("on"+evType, fn);
+    return r;
+  } else {
+    alert("Handler could not be removed");
+  }
+} 
index e92963a..0e5a0df 100755 (executable)
@@ -605,12 +605,14 @@ if ( $Conf{CgiImageDir} ne "" ) {
                "$DestDir$Conf{CgiImageDir}/BackupPC_stnd.css", 0444, 0);
     InstallFile("conf/BackupPC_stnd_orig.css",
                "$DestDir$Conf{CgiImageDir}/BackupPC_stnd_orig.css", 0444, 0);
+    InstallFile("conf/sorttable.js",
+                "$DestDir$Conf{CgiImageDir}/sorttable.js", 0444, 0);
 }
 
 printf("Making init.d scripts\n");
 foreach my $init ( qw(gentoo-backuppc gentoo-backuppc.conf linux-backuppc
-                     solaris-backuppc debian-backuppc suse-backuppc
-                     slackware-backuppc ) ) {
+                     solaris-backuppc debian-backuppc freebsd-backuppc
+                      suse-backuppc slackware-backuppc ) ) {
     InstallFile("init.d/src/$init", "init.d/$init", 0444);
 }
 
@@ -712,6 +714,13 @@ $Conf{CgiNavBarAdminAllHosts} = 1;
 $Conf{IncrFill} = 0;
 
 #
+# Empty $Conf{ParPath} if it isn't a valid executable
+# (pre-3.0.0 configure.pl incorrectly set it to a
+# hardcoded value).
+#
+$Conf{ParPath} = '' if ( $Conf{ParPath} ne '' && !-x $Conf{ParPath} );
+
+#
 # Figure out sensible arguments for the ping command
 #
 if ( defined($Conf{PingArgs}) ) {
index 849bfaf..60f5b01 100644 (file)
@@ -238,7 +238,7 @@ are not always up to date and the searching is limited, so Gmane is
 a good alternative.  See:
 
     http://news.gmane.org/index.php?prefix=gmane.comp.sysutils.backup.backuppc
-    http://sourceforge.net/mailarchive/forum.php?forum_id=503
+    http://sourceforge.net/mailarchive/forum.php?forum=backuppc-users
 
 You can subscribe to these lists by visiting:
 
@@ -1453,6 +1453,43 @@ the -a option and rsync -H.  However, the large number of hardlinks
 in the pool will make the memory usage large and the copy very slow.
 Don't forget to stop BackupPC while the copy runs.
 
+Starting in 3.0.0 a new script bin/BackupPC_tarPCCopy can be
+used to assist the copy process.  Given one or more pc paths
+(eg: TOPDIR/pc/HOST or TOPDIR/pc/HOST/nnn), BackupPC_tarPCCopy 
+creates a tar archive with all the hardlinks pointing to ../cpool/....
+Any files not hardlinked (eg: backups, LOG etc) are included
+verbatim.
+
+You will need to specify the -P option to tar when you extract
+the archive generated by BackupPC_tarPCCopy since the hardlink
+targets are outside of the directory being extracted.
+
+To copy a complete store (ie: __TOPDIR__) using BackupPC_tarPCCopy
+you should:
+
+=over 4
+
+=item *
+
+stop BackupPC so that the store is static.
+
+=item *
+
+copy the cpool, conf and log directory trees using any technique
+(like cp, rsync or tar) wihtout the need to preserve hardlinks.
+
+=item *
+
+copy the pc directory using BackupPC_tarPCCopy:
+
+    su __BACKUPPCUSER__
+    cd NEW_TOPDIR
+    mkdir pc
+    cd pc
+    __INSTALLDIR__/bin/BackupPC_tarPCCopy __TOPDIR__/pc | tar xvPf -
+
+=back
+
 =back
 
 =head2 Fixing installation problems
@@ -2736,7 +2773,7 @@ See L<http://backuppc.sourceforge.net>.
 
 =head1 Copyright
 
-Copyright (C) 2001-2006 Craig Barratt
+Copyright (C) 2001-2007 Craig Barratt
 
 =head1 Credits
 
index e08e77b..afa4a61 100644 (file)
@@ -79,6 +79,24 @@ start automatically at boot (at the default run level):
 
     rc-update add backuppc default
 
+FreeBSD:
+=======
+
+When configure.pl is run, the script freebsd-backuppc is created.
+
+Copy this script to /usr/local/etc/rc.d/backuppc and make execuatble.
+
+Add the following line to /etc/rc.conf to enable BackupPC:
+
+backuppc_enable=(bool):   Set to "NO" by default.
+                          Set it to "YES" to enable BackupPC.
+
+Example:
+
+    backuppc_enable="YES"
+
+The script accepts: start, stop, restart, reload, status
+
 Slackware:
 =========
 
diff --git a/init.d/src/freebsd-backuppc b/init.d/src/freebsd-backuppc
new file mode 100644 (file)
index 0000000..34a825a
--- /dev/null
@@ -0,0 +1,75 @@
+#!/bin/sh
+
+# PROVIDE: backuppc
+# REQUIRE: DAEMON
+# BEFORE:  LOGIN
+# KEYWORD: shutdown
+
+#
+# Copy to /usr/local/etc/rc.d/backuppc and make execuatble
+#
+# Add the following line to /etc/rc.conf to enable BackupPC:
+# backuppc_enable=(bool):   Set to "NO" by default.
+#                          Set it to "YES" to enable BackupPC.
+#
+# Example:
+#
+#       backuppc_enable="YES"
+#
+# It accepts : start, stop, restart, reload, status
+#
+# Provided by : Gabriel Rossetti
+#
+
+. /etc/rc.subr
+
+name="backuppc"
+rcvar=`set_rcvar`
+start_cmd="backuppc_start"
+restart_cmd="backuppc_restart"
+stop_cmd="backuppc_stop"
+status_cmd="backuppc_status"
+reload_cmd="backuppc_reload"
+
+load_rc_config $name
+eval "${rcvar}=\${${rcvar}:-'NO'}"
+
+: ${backuppc_enable="NO"}
+#backuppc_enable=${backuppc_enable:-"NO"}
+
+backuppc_start()
+{
+    su backuppc -c '__INSTALLDIR__/bin/BackupPC -d'
+    echo "${name} started"
+}
+
+backuppc_restart()
+{
+    backuppc_stop
+    sleep 1
+    backuppc_start
+}
+
+backuppc_stop()
+{
+    /usr/bin/pkill -f "__INSTALLDIR__/bin/BackupPC -d"
+    echo "${name} stopped"
+}
+
+backuppc_status()
+{
+    if [ "`ps ax | grep "BackupPC -d" | grep perl`" = "" ] ; then
+        echo "${name} not running"
+    else
+        echo "${name} running"
+    fi
+}    
+
+backuppc_reload()
+{
+    /usr/bin/pkill -1 -f "__INSTALLDIR__/bin/BackupPC -d"
+    echo "${name} reloaded"
+}
+
+extra_commands="reload status"
+run_rc_command "$1"
index d8faed8..6e927f9 100644 (file)
@@ -80,7 +80,7 @@ sub action
     my $backupTime = timeStamp2($Backups[$i]{startTime});
     my $backupAge = sprintf("%.1f", (time - $Backups[$i]{startTime})
                                     / (24 * 3600));
-    my $view = BackupPC::View->new($bpc, $host, \@Backups);
+    my $view = BackupPC::View->new($bpc, $host, \@Backups, {nlink => 1});
 
     if ( $dir eq "" || $dir eq "." || $dir eq ".." ) {
        $attr = $view->dirAttrib($num, "", "");
index e13a921..5abcc36 100644 (file)
@@ -62,7 +62,7 @@ sub action
     ErrorExit($Lang->{Empty_host_name}) if ( $host eq "" );
 
     my @Backups = $bpc->BackupInfoRead($host);
-    my $view = BackupPC::View->new($bpc, $host, \@Backups);
+    my $view = BackupPC::View->new($bpc, $host, \@Backups, {inode => 1});
     my $hist = $view->dirHistory($share, $dir);
     my($backupNumStr, $backupTimeStr, $fileStr);
 
index 2e734db..4811f4e 100644 (file)
@@ -1466,6 +1466,8 @@ sub fieldInputParse
         } else {
             $$value = decode_utf8($In{"v_zZ_$varName"});
             $$value =~ s/\r\n/\n/g;
+            # remove leading space from exec paths
+            $$value =~ s/^\s+// if ( $type->{type} eq "execPath" );
         }
         $$value = undef if ( $type->{undefIfEmpty} && $$value eq "" );
     }
index b515bb9..8370b46 100644 (file)
@@ -180,7 +180,10 @@ sub timeStamp2
     my($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)
               = localtime($_[0] == 0 ? time : $_[0] );
     $mon++;
-    if ( $Conf{CgiDateFormatMMDD} ) {
+    if ( $Conf{CgiDateFormatMMDD} == 2 ) {
+        $year += 1900;
+        return sprintf("%04d-%02d-%02d %02d:%02d", $year, $mon, $mday, $hour, $min);
+    } elsif ( $Conf{CgiDateFormatMMDD} ) {
         return sprintf("$mon/$mday %02d:%02d", $hour, $min);
     } else {
         return sprintf("$mday/$mon %02d:%02d", $hour, $min);
@@ -442,6 +445,7 @@ sub Header
 <title>$title</title>
 <link rel=stylesheet type="text/css" href="$Conf{CgiImageDirURL}/$Conf{CgiCSSFile}" title="CSSFile">
 $Conf{CgiHeaders}
+<script src="$Conf{CgiImageDirURL}/sorttable.js"></script>
 </head><body onLoad="document.getElementById('NavMenu').style.height=document.body.scrollHeight">
 <a href="http://backuppc.sourceforge.net"><img src="$Conf{CgiImageDirURL}/logo.gif" hspace="5" vspace="7" border="0"></a><br>
 EOF
index 5c3cacd..9b26188 100644 (file)
@@ -45,7 +45,7 @@ sub action
        $strNone, $strGood, $hostCntGood, $hostCntNone);
 
     $hostCntGood = $hostCntNone = 0;
-    GetStatusInfo("hosts");
+    GetStatusInfo("hosts info");
     my $Privileged = CheckPermission();
 
     foreach my $host ( GetUserHosts(1) ) {
@@ -160,6 +160,8 @@ EOF
     $fullSizeTot = sprintf("%.2f", $fullSizeTot / 1000);
     $incrSizeTot = sprintf("%.2f", $incrSizeTot / 1000);
     my $now      = timeStamp2(time);
+    my $DUlastTime   = timeStamp2($Info{DUlastValueTime});
+    my $DUmaxTime    = timeStamp2($Info{DUDailyMaxTime});
 
     my $content = eval ("qq{$Lang->{BackupPC_Summary}}");
     Header($Lang->{BackupPC__Server_Summary}, $content);
index 242a789..1ad7d85 100644 (file)
@@ -316,7 +316,7 @@ use vars qw(%ConfigMeta);
     },
     CgiUserHomePageCheck => "string",
     CgiUserUrlCreate    => "string",
-    CgiDateFormatMMDD  => "boolean",
+    CgiDateFormatMMDD  => "integer",
     CgiNavBarAdminAllHosts => "boolean",
     CgiSearchBoxEnable         => "boolean",
     CgiNavBarLinks     => {
@@ -396,6 +396,7 @@ use vars qw(%ConfigMeta);
                 TarClientRestoreCmd       => "boolean",
                 RsyncShareName            => "boolean",
                 RsyncdClientPort          => "boolean",
+                RsyncdUserName            => "boolean",
                 RsyncdPasswd              => "boolean",
                 RsyncdAuthRequired        => "boolean",
                 RsyncCsumCacheVerifyProb  => "boolean",
index 5cb73d8..2d74daa 100644 (file)
@@ -137,7 +137,13 @@ $Lang{BackupPC_Summary}=<<EOF;
 
 \${h1(qq{$Lang{BackupPC__Server_Summary}})}
 <p>
-Dieser Status wurde am \$now generiert.
+<ul>
+<li>Dieser Status wurde am \$now generiert.
+<li>Das Pool Filesystem (Backup-Speicherplatz) ist zu \$Info{DUlastValue}%
+    (\$DUlastTime) voll, das Maximum heute ist \$Info{DUDailyMax}% (\$DUmaxTime)
+    und das Maximum gestern war \$Info{DUDailyMaxPrev}%. (Hinweis: Sollten ca. 70% ?berschritten werden, so
+    ist evtl. bald eine Erweiterung des Backupspeichers erforderlich. Ist weitere Planung n?tig?)
+</ul>
 </p>
 
 \${h2("Computer mit erfolgreichen Backups")}
@@ -150,7 +156,7 @@ Es gibt \$hostCntGood Computer die erfolgreich gesichert wurden, mit insgesamt:
      (vor Pooling und Komprimierung).
 </ul>
 </p>
-<table class="tableStnd" border cellpadding="3" cellspacing="1">
+<table class="sortable" id="host_summary_backups" border cellpadding="3" cellspacing="1">
 <tr class="tableheader"><td> Computer </td>
     <td align="center"> Benutzer </td>
     <td align="center"> #Voll </td>
@@ -169,7 +175,7 @@ Es gibt \$hostCntGood Computer die erfolgreich gesichert wurden, mit insgesamt:
 <p>
 Es gibt \$hostCntNone Computer ohne Backups !!!
 <p>
-<table class="tableStnd" border cellpadding="3" cellspacing="1">
+<table class="sortable" id="host_summary_nobackups" border cellpadding="3" cellspacing="1">
 <tr class="tableheader"><td> Computer </td>
     <td align="center"> Benutzer </td>
     <td align="center"> #Voll </td>
index 7c58322..ba40885 100644 (file)
@@ -125,7 +125,12 @@ $Lang{BackupPC_Summary} = <<EOF;
 
 \${h1(qq{$Lang{BackupPC__Server_Summary}})}
 <p>
-This status was generated at \$now.
+<ul>
+<li>This status was generated at \$now.
+<li>Pool file system was recently at \$Info{DUlastValue}%
+    (\$DUlastTime), today\'s max is \$Info{DUDailyMax}% (\$DUmaxTime)
+        and yesterday\'s max was \$Info{DUDailyMaxPrev}%.
+</ul>
 </p>
 
 \${h2("Hosts with good Backups")}
@@ -138,7 +143,7 @@ There are \$hostCntGood hosts that have been backed up, for a total of:
      (prior to pooling and compression).
 </ul>
 </p>
-<table class="tableStnd" border cellpadding="3" cellspacing="1">
+<table class="sortable" id="host_summary_backups" border cellpadding="3" cellspacing="1">
 <tr class="tableheader"><td> Host </td>
     <td align="center"> User </td>
     <td align="center"> #Full </td>
@@ -157,7 +162,7 @@ There are \$hostCntGood hosts that have been backed up, for a total of:
 <p>
 There are \$hostCntNone hosts with no backups.
 <p>
-<table class="tableStnd" border cellpadding="3" cellspacing="1">
+<table class="sortable" id="host_summary_nobackups" border cellpadding="3" cellspacing="1">
 <tr class="tableheader"><td> Host </td>
     <td align="center"> User </td>
     <td align="center"> #Full </td>
index 8ac23a4..67d2788 100644 (file)
@@ -126,7 +126,12 @@ $Lang{BackupPC_Summary}=<<EOF;
 
 \${h1(qq{$Lang{BackupPC__Server_Summary}})}
 <p>
-Este status ha sido generado el \$now.
+<ul>
+<li>Este status ha sido generado el \$now.
+<li>El sistema de archivos estaba recientemente al \$Info{DUlastValue}%
+    (\$DUlastTime), el m?ximo de hoy es \$Info{DUDailyMax}% (\$DUmaxTime)
+    y el m?ximo de ayer era \$Info{DUDailyMaxPrev}%.
+</ul>
 </p>
 
 \${h2("Hosts con Buenas Copias de Seguridad")}
@@ -139,7 +144,7 @@ Il y a \$hostCntGood hosts tienen copia de seguridad, de un total de :
      (antes de agrupar y comprimir).
 </ul>
 </p>
-<table class="tableStnd" border cellpadding="3" cellspacing="1">
+<table class="sortable" id="host_summary_backups" border cellpadding="3" cellspacing="1">
 <tr class="tableheader"><td> Host </td>
     <td align="center"> Usuario </td>
     <td align="center"> #Completo </td>
@@ -158,7 +163,7 @@ Il y a \$hostCntGood hosts tienen copia de seguridad, de un total de :
 <p>
 Hay \$hostCntNone hosts sin copias de seguridad.
 <p>
-<table class="tableStnd" border cellpadding="3" cellspacing="1">
+<table class="sortable" id="host_summary_nobackups" border cellpadding="3" cellspacing="1">
 <tr class="tableheader"><td> Host </td>
     <td align="center"> Usuario </td>
     <td align="center"> #Completo </td>
index 38ee9d4..af324a9 100644 (file)
@@ -125,7 +125,12 @@ $Lang{BackupPC_Summary}=<<EOF;
 
 \${h1(qq{$Lang{BackupPC__Server_Summary}})}
 <p>
-Ce statut a été généré le \$now.
+<ul>
+<li>Ce statut a été généré le \$now.
+<li>L\'espace de stockage a ?t? r?cemment rempli ? \$Info{DUlastValue}%
+    (\$DUlastTime), le maximum aujourd\'hui a ?t? de \$Info{DUDailyMax}% (\$DUmaxTime)
+    et hier le maximum ?tait \$Info{DUDailyMaxPrev}%.
+</ul>
 </p>
 
 \${h2("Hôtes avec de bonnes sauvegardes")}
@@ -138,7 +143,7 @@ Il y a \$hostCntGood h
      (précédant la mise en commun et la compression).
 </ul>
 </p>
-<table class="tableStnd" border cellpadding="3" cellspacing="1">
+<table class="sortable" id="host_summary_backups" border cellpadding="3" cellspacing="1">
 <tr class="tableheader"><td> Hôte </td>
     <td align="center"> Utilisateur </td>
     <td align="center"> Nb complètes </td>
@@ -157,7 +162,7 @@ Il y a \$hostCntGood h
 <p>
 Il y a \$hostCntNone hôtes sans sauvegardes.
 <p>
-<table class="tableStnd" border cellpadding="3" cellspacing="1">
+<table class="sortable" id="host_summary_nobackups" border cellpadding="3" cellspacing="1">
 <tr class="tableheader"><td> Hôte </td>
     <td align="center"> Utilisateur </td>
     <td align="center"> Nb complètes </td>
index 910bf41..6a54294 100644 (file)
@@ -133,7 +133,13 @@ $Lang{BackupPC_Summary} = <<EOF;
 
 \${h1(qq{$Lang{BackupPC__Server_Summary}})}
 <p>
-Questo rapporto di stato &egrave; stato generato il \$now.
+<ul>
+<li>Questo rapporto di stato &egrave; stato generato il \$now.
+<li>Recentemente il sistema dei file di pool &egrave; stato al
+    \$Info{DUlastValue}% (\$DUlastTime).  Il massimo di oggi
+     &egrave; del \$Info{DUDailyMax}% (\$DUmaxTime), mentre quello
+     di ieri era del \$Info{DUDailyMaxPrev}%.
+</ul>
 </p>
 
 \${h2("Host con backup validi")}
@@ -146,7 +152,7 @@ Ci sono \$hostCntGood host sottoposti a backup per un totale di:
      (prima del processo di pooling e compressione).
 </ul>
 </p>
-<table class="tableStnd" border cellpadding="3" cellspacing="1">
+<table class="sortable" id="host_summary_backups" border cellpadding="3" cellspacing="1">
 <tr class="tableheader"><td> Host </td>
     <td align="center"> Utente </td>
     <td align="center"> Completi </td>
@@ -165,7 +171,7 @@ Ci sono \$hostCntGood host sottoposti a backup per un totale di:
 <p>
 Ci sono \$hostCntNone host senza alcun backup.
 <p>
-<table class="tableStnd" border cellpadding="3" cellspacing="1">
+<table class="sortable" id="host_summary_nobackups" border cellpadding="3" cellspacing="1">
 <tr class="tableheader"><td> Host </td>
     <td align="center"> Utente </td>
     <td align="center"> Completi </td>
index 59e36cb..e690c8b 100644 (file)
@@ -125,7 +125,12 @@ $Lang{BackupPC_Summary}=<<EOF;
 
 \${h1(qq{$Lang{BackupPC__Server_Summary}})}
 <p>
-Dit overzicht dateert van \$now.
+<ul>
+<li>Dit overzicht dateert van \$now.
+<li>Het backup filesystem werd recentelijk aangevuld voor \$Info{DUlastValue}%
+     op (\$DUlastTime), het maximum van vandaag is \$Info{DUDailyMax}% (\$DUmaxTime)
+     en het maximum van gisteren was \$Info{DUDailyMaxPrev}%.
+</ul>
 </p>
 
 \${h2("Machine(s) met geslaagde backups")}
@@ -138,7 +143,7 @@ Er zijn \$hostCntGood hosts gebackupt, wat een totaal geeft van:
      (voor samenvoegen).
 </ul>
 </p>
-<table class="tableStnd" border cellpadding="3" cellspacing="1">
+<table class="sortable" id="host_summary_backups" border cellpadding="3" cellspacing="1">
 <tr class="tableheader"><td> Machine </td>
     <td align="center"> Gebruiker </td>
     <td align="center"> Aantal Voll. </td>
@@ -157,7 +162,7 @@ Er zijn \$hostCntGood hosts gebackupt, wat een totaal geeft van:
 <p>
 Er zijn \$hostCntNone hosts zonder backup.
 <p>
-<table class="tableStnd" border cellpadding="3" cellspacing="1">
+<table class="sortable" id="host_summary_nobackups" border cellpadding="3" cellspacing="1">
 <tr class="tableheader"><td> Machine </td>
     <td align="center"> Gebruiker </td>
     <td align="center"> Aantal Voll. </td>
index 94ea463..22e95a7 100644 (file)
@@ -130,7 +130,12 @@ $Lang{BackupPC_Summary}=<<EOF;
 
 \${h1(qq{$Lang{BackupPC__Server_Summary}})}
 <p>
-Este status foi generado em \$now.
+<ul>
+<li>Este status foi generado em \$now.
+<li>O sistema de arquivos estava recentemente em \$Info{DUlastValue}%
+    (\$DUlastTime), o m?ximo de hoje ? \$Info{DUDailyMax}% (\$DUmaxTime)
+    e o m?ximo de ontem foi \$Info{DUDailyMaxPrev}%.
+</ul>
 </p>
 
 \${h2("Hosts com Backups Completos")}
@@ -143,7 +148,7 @@ Existem \$hostCntGood hosts com backup, de um total de :
      (antes de agrupar e comprimir).
 </ul>
 </p>
-<table class="tableStnd" border cellpadding="3" cellspacing="1">
+<table class="sortable" id="host_summary_backups" border cellpadding="3" cellspacing="1">
 <tr class="tableheader"><td> Host </td>
     <td align="center"> Usuario </td>
     <td align="center"> #Completo </td>
@@ -162,7 +167,7 @@ Existem \$hostCntGood hosts com backup, de um total de :
 <p>
 Existem \$hostCntNone hosts sem backups.
 <p>
-<table class="tableStnd" border cellpadding="3" cellspacing="1">
+<table class="sortable" id="host_summary_nobackups" border cellpadding="3" cellspacing="1">
 <tr class="tableheader"><td> Host </td>
     <td align="center"> Usuario </td>
     <td align="center"> #Completo </td>
index 0c4b69b..44de3c5 100644 (file)
@@ -41,9 +41,8 @@ use strict;
 
 use vars qw(%Conf %Lang);
 use BackupPC::Storage;
-use Fcntl qw/:flock/;
+use Fcntl ':mode';
 use Carp;
-use DirHandle ();
 use File::Path;
 use File::Compare;
 use Socket;
@@ -51,6 +50,42 @@ use Cwd;
 use Digest::MD5;
 use Config;
 
+use vars qw( $IODirentOk );
+use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
+
+require Exporter;
+require DynaLoader;
+
+@ISA = qw(Exporter DynaLoader);
+@EXPORT_OK = qw( BPC_DT_UNKNOWN
+                 BPC_DT_FIFO
+                 BPC_DT_CHR
+                 BPC_DT_DIR
+                 BPC_DT_BLK
+                 BPC_DT_REG
+                 BPC_DT_LNK
+                 BPC_DT_SOCK
+               );
+@EXPORT = qw( );
+%EXPORT_TAGS = ('BPC_DT_ALL' => [@EXPORT, @EXPORT_OK]);
+
+BEGIN {
+    eval "use IO::Dirent qw( readdirent DT_DIR );";
+    $IODirentOk = 1 if ( !$@ );
+};
+
+#
+# The need to match the constants in IO::Dirent
+#
+use constant BPC_DT_UNKNOWN =>   0;
+use constant BPC_DT_FIFO    =>   1;    ## named pipe (fifo)
+use constant BPC_DT_CHR     =>   2;    ## character special
+use constant BPC_DT_DIR     =>   4;    ## directory
+use constant BPC_DT_BLK     =>   6;    ## block special
+use constant BPC_DT_REG     =>   8;    ## regular
+use constant BPC_DT_LNK     =>  10;    ## symbolic link
+use constant BPC_DT_SOCK    =>  12;    ## socket
+
 sub new
 {
     my $class = shift;
@@ -405,10 +440,100 @@ sub HostsMTime
 }
 
 #
+# Read a directory and return the entries in sorted inode order.
+# This relies on the IO::Dirent module being installed.  If not,
+# the inode data is empty and the default directory order is
+# returned.
+#
+# The returned data is a list of hashes with entries {name, type, inode, nlink}.
+# The returned data includes "." and "..".
+#
+# $need is a hash of file attributes we need: type, inode, or nlink.
+# If set, these parameters are added to the returned hash.
+#
+# If IO::Dirent is successful if will get type and inode for free.
+# Otherwise, a stat is done on each file, which is more expensive.
+#
+sub dirRead
+{
+    my($bpc, $path, $need) = @_;
+    my(@entries, $addInode);
+
+    return if ( !opendir(my $fh, $path) );
+    if ( $IODirentOk ) {
+        @entries = sort({ $a->{inode} <=> $b->{inode} } readdirent($fh));
+        map { $_->{type} = 0 + $_->{type} } @entries;   # make type numeric
+    } else {
+        @entries = map { { name => $_} } readdir($fh);
+    }
+    closedir($fh);
+    if ( defined($need) ) {
+        for ( my $i = 0 ; $i < @entries ; $i++ ) {
+            next if ( (!$need->{inode} || defined($entries[$i]{inode}))
+                   && (!$need->{type}  || defined($entries[$i]{type}))
+                   && (!$need->{nlink} || defined($entries[$i]{nlink})) );
+            my @s = stat("$path/$entries[$i]{name}");
+            $entries[$i]{nlink} = $s[3] if ( $need->{nlink} );
+            if ( $need->{inode} && !defined($entries[$i]{inode}) ) {
+                $addInode = 1;
+                $entries[$i]{inode} = $s[1];
+            }
+            if ( $need->{type} && !defined($entries[$i]{type}) ) {
+                my $mode = S_IFMT($s[2]);
+                $entries[$i]{type} = BPC_DT_FIFO if ( S_ISFIFO($mode) );
+                $entries[$i]{type} = BPC_DT_CHR  if ( S_ISCHR($mode) );
+                $entries[$i]{type} = BPC_DT_DIR  if ( S_ISDIR($mode) );
+                $entries[$i]{type} = BPC_DT_BLK  if ( S_ISBLK($mode) );
+                $entries[$i]{type} = BPC_DT_REG  if ( S_ISREG($mode) );
+                $entries[$i]{type} = BPC_DT_LNK  if ( S_ISLNK($mode) );
+                $entries[$i]{type} = BPC_DT_SOCK if ( S_ISSOCK($mode) );
+            }
+        }
+    }
+    #
+    # Sort the entries if inodes were added (the IO::Dirent case already
+    # sorted above)
+    #
+    @entries = sort({ $a->{inode} <=> $b->{inode} } @entries) if ( $addInode );
+    return \@entries;
+}
+
+#
+# Same as dirRead, but only returns the names (which will be sorted in
+# inode order if IO::Dirent is installed)
+#
+sub dirReadNames
+{
+    my($bpc, $path) = @_;
+
+    my $entries = $bpc->dirRead($path);
+    return if ( !defined($entries) );
+    my @names = map { $_->{name} } @$entries;
+    return \@names;
+}
+
+sub find
+{
+    my($bpc, $param, $dir, $dontDoCwd) = @_;
+
+    return if ( !chdir($dir) );
+    my $entries = $bpc->dirRead(".", {inode => 1, type => 1});
+    #print Dumper($entries);
+    foreach my $f ( @$entries ) {
+        next if ( $f->{name} eq ".." || $f->{name} eq "." && $dontDoCwd );
+        $param->{wanted}($f->{name}, "$dir/$f->{name}");
+        next if ( $f->{type} != BPC_DT_DIR || $f->{name} eq "." );
+        chdir($f->{name});
+        $bpc->find($param, "$dir/$f->{name}", 1);
+        return if ( !chdir("..") );
+    }
+}
+
+#
 # Stripped down from File::Path.  In particular we don't print
 # many warnings and we try three times to delete each directory
 # and file -- for some reason the original File::Path rmtree
-# didn't always completely remove a directory tree on the NetApp.
+# didn't always completely remove a directory tree on a NetApp.
 #
 # Warning: this routine changes the cwd.
 #
@@ -433,13 +558,11 @@ sub RmTreeQuiet
        #
        if ( !unlink($root) ) {
             if ( -d $root ) {
-                my $d = DirHandle->new($root);
+                my $d = $bpc->dirReadNames($root);
                if ( !defined($d) ) {
                    print(STDERR "Can't read $pwd/$root: $!\n");
                } else {
-                   @files = $d->read;
-                   $d->close;
-                   @files = grep $_!~/^\.{1,2}$/, @files;
+                   @files = grep $_ !~ /^\.{1,2}$/, @$d;
                    $bpc->RmTreeQuiet("$pwd/$root", \@files);
                    chdir($pwd);
                    rmdir($root) || rmdir($root);
@@ -490,10 +613,8 @@ sub RmTreeTrashEmpty
 
     $cwd = $1 if ( $cwd =~ /(.*)/ );
     return if ( !-d $trashDir );
-    my $d = DirHandle->new($trashDir) or carp "Can't read $trashDir: $!";
-    @files = $d->read;
-    $d->close;
-    @files = grep $_!~/^\.{1,2}$/, @files;
+    my $d = $bpc->dirReadNames($trashDir) or carp "Can't read $trashDir: $!";
+    @files = grep $_ !~ /^\.{1,2}$/, @$d;
     return 0 if ( !@files );
     $bpc->RmTreeQuiet($trashDir, \@files);
     foreach my $f ( @files ) {
index 147c425..cf03983 100644 (file)
@@ -49,16 +49,20 @@ use Data::Dumper;
 
 sub new
 {
-    my($class, $bpc, $host, $backups) = @_;
+    my($class, $bpc, $host, $backups, $options) = @_;
     my $m = bless {
-        bpc       => $bpc,             # BackupPC::Lib object
-        host      => $host,            # host name
-        backups   => $backups,         # all backups for this host
-       num       => -1,                # backup number
-        idx       => -1,               # index into backups for backup
-                                       #   we are viewing
-        dirPath   => undef,            # path to current directory
-        dirAttr   => undef,            # attributes of current directory
+        bpc       => $bpc,     # BackupPC::Lib object
+        host      => $host,    # host name
+        backups   => $backups, # all backups for this host
+       num       => -1,        # backup number
+        idx       => -1,       # index into backups for backup
+                               #   we are viewing
+        dirPath   => undef,    # path to current directory
+        dirAttr   => undef,    # attributes of current directory
+        dirOpts   => $options,  # $options is a hash of file attributes we need:
+                                # type, inode, or nlink.  If set, these parameters
+                                # are added to the returned hash.
+                                # See BackupPC::Lib::dirRead().
     }, $class;
     for ( my $i = 0 ; $i < @{$m->{backups}} ; $i++ ) {
        next if ( defined($m->{backups}[$i]{level}) );
@@ -119,7 +123,9 @@ sub dirCache
         }
         $path .= $sharePathM;
        #print(STDERR "Opening $path (share=$share)\n");
-       if ( !opendir(DIR, $path) ) {
+
+        my $dirInfo = $m->{bpc}->dirRead($path, $m->{dirOpts});
+       if ( !defined($dirInfo) ) {
             if ( $i == $m->{idx} ) {
                 #
                 # Oops, directory doesn't exist.
@@ -129,8 +135,6 @@ sub dirCache
             }
             next;
         }
-        my @dir = readdir(DIR);
-        closedir(DIR);
         my $attr;
        if ( $mangle ) {
            $attr = BackupPC::Attrib->new({ compress => $compress });
@@ -139,8 +143,8 @@ sub dirCache
                $attr = undef;
            }
        }
-        foreach my $file ( @dir ) {
-            $file = $1 if ( $file =~ /(.*)/s );
+        foreach my $entry ( @$dirInfo ) {
+            my $file = $1 if ( $entry->{name} =~ /(.*)/s );
             my $fileUM = $file;
             $fileUM = $m->{bpc}->fileNameUnmangle($fileUM) if ( $mangle );
             #print(STDERR "Doing $fileUM\n");
@@ -152,14 +156,13 @@ sub dirCache
                    || $file eq "."
                    || $file eq "backupInfo"
                    || $mangle && $file eq "attrib" );
-           #
-           # skip directories in earlier backups (each backup always
-           # has the complete directory tree).
-           #
-           my @s = stat("$path/$file");
-           next if ( $i < $m->{idx} && -d _ );
             if ( defined($attr) && defined(my $a = $attr->get($fileUM)) ) {
                 $m->{files}{$fileUM} = $a;
+                #
+                # skip directories in earlier backups (each backup always
+                # has the complete directory tree).
+                #
+                next if ( $i < $m->{idx} && $a->{type} == BPC_FTYPE_DIR );
                $attr->set($fileUM, undef);
             } else {
                 #
@@ -167,6 +170,8 @@ sub dirCache
                 # is on.  We have to stat the file and read compressed files
                 # to determine their size.
                 #
+                my @s = stat("$path/$file");
+                next if ( $i < $m->{idx} && -d _ );
                 $m->{files}{$fileUM} = {
                     type  => -d _ ? BPC_FTYPE_DIR : BPC_FTYPE_FILE,
                     mode  => $s[2],
@@ -199,8 +204,10 @@ sub dirCache
             ($m->{files}{$fileUM}{fullPath}   = "$path/$file") =~ s{//+}{/}g;
             $m->{files}{$fileUM}{backupNum}   = $backupNum;
             $m->{files}{$fileUM}{compress}    = $compress;
-           $m->{files}{$fileUM}{nlink}       = $s[3];
-           $m->{files}{$fileUM}{inode}       = $s[1];
+           $m->{files}{$fileUM}{nlink}       = $entry->{nlink}
+                                                    if ( $m->{dirOpts}{nlink} );
+           $m->{files}{$fileUM}{inode}       = $entry->{inode}
+                                                    if ( $m->{dirOpts}{inode} );
         }
        #
        # Also include deleted files
@@ -384,14 +391,14 @@ sub dirHistory
         }
         $path .= $sharePathM;
        #print(STDERR "Opening $path (share=$share)\n");
-       if ( !opendir(DIR, $path) ) {
+
+        my $dirInfo = $m->{bpc}->dirRead($path, $m->{dirOpts});
+       if ( !defined($dirInfo) ) {
            #
            # Oops, directory doesn't exist.
            #
            next;
         }
-        my @dir = readdir(DIR);
-        closedir(DIR);
         my $attr;
        if ( $mangle ) {
            $attr = BackupPC::Attrib->new({ compress => $compress });
@@ -400,8 +407,8 @@ sub dirHistory
                $attr = undef;
            }
        }
-        foreach my $file ( @dir ) {
-            $file = $1 if ( $file =~ /(.*)/s );
+        foreach my $entry ( @$dirInfo ) {
+            my $file = $1 if ( $entry->{name} =~ /(.*)/s );
             my $fileUM = $file;
             $fileUM = $m->{bpc}->fileNameUnmangle($fileUM) if ( $mangle );
             #print(STDERR "Doing $fileUM\n");
@@ -454,8 +461,10 @@ sub dirHistory
             ($files->{$fileUM}[$i]{fullPath}   = "$path/$file") =~ s{//+}{/}g;
             $files->{$fileUM}[$i]{backupNum}   = $backupNum;
             $files->{$fileUM}[$i]{compress}    = $compress;
-           $files->{$fileUM}[$i]{nlink}       = $s[3];
-           $files->{$fileUM}[$i]{inode}       = $s[1];
+           $files->{$fileUM}[$i]{nlink}       = $entry->{nlink}
+                                                    if ( $m->{dirOpts}{nlink} );
+           $files->{$fileUM}[$i]{inode}       = $entry->{inode}
+                                                    if ( $m->{dirOpts}{inode} );
         }
 
        #
index 584ad41..3af92f7 100644 (file)
@@ -266,10 +266,10 @@ sub digestStart
     if ( $fileSize > 0 && $compress && $doCache >= 0 ) {
         open(my $fh, "<", $fileName) || return -2;
         binmode($fh);
-        return -3 if ( read($fh, $data, 1) != 1 );
+        return -3 if ( sysread($fh, $data, 4096) < 1 );
         my $ret;
 
-        if ( ($data eq chr(0x78) || $data eq chr(0xd6)) && $doCache > 0
+        if ( (vec($data, 0, 8) == 0x78 || vec($data, 0, 8) == 0xd6) && $doCache > 0
                      && $checksumSeed == RSYNC_CSUMSEED_CACHE ) {
             #
             # RSYNC_CSUMSEED_CACHE (32761) is the magic number that
@@ -298,31 +298,44 @@ sub digestStart
             binmode($fh);
             return -5 if ( read($fh, $data, 1) != 1 );
         }
-        if ( $ret >= 0 && $data eq chr(0xd7) ) {
+        if ( $ret >= 0 && vec($data, 0, 8) == 0xd7 ) {
             #
             # Looks like this file has cached checksums
             # Read the last 48 bytes: that's 2 file MD4s (32 bytes)
             # plus 4 words of meta data
             #
-            return -6 if ( !defined(seek($fh, -48, 2)) ); 
-            return -7 if ( read($fh, $data, 48) != 48 );
+            my $cacheInfo;
+            if ( length($data) >= 4096 ) {
+                return -6 if ( !defined(sysseek($fh, -4096, 2)) ); 
+                return -7 if ( sysread($fh, $data, 4096) != 4096 );
+            }
+            $cacheInfo = substr($data, -48);
             ($dg->{md4DigestOld},
              $dg->{md4Digest},
              $dg->{blockSize},
              $dg->{checksumSeed},
              $dg->{nBlocks},
-             $dg->{magic}) = unpack("a16 a16 V V V V", $data);
+             $dg->{magic}) = unpack("a16 a16 V V V V", $cacheInfo);
             if ( $dg->{magic} == 0x5fe3c289
                     && $dg->{checksumSeed} == $checksumSeed
                     && ($blockSize == 0 || $dg->{blockSize} == $blockSize) ) {
                 $dg->{fh}     = $fh;
                 $dg->{cached} = 1;
-                #
-                # position the file at the start of the rsync block checksums
-                # (4 (adler) + 16 (md4) bytes each)
-                #
-                return -8
-                    if ( !defined(seek($fh, -$dg->{nBlocks}*20 - 48, 2)) );
+                if ( length($data) >= $dg->{nBlocks} * 20 + 48 ) {
+                    #
+                    # We have all the data already - just remember it
+                    #
+                    $dg->{digestData} = substr($data,
+                                               length($data) - $dg->{nBlocks} * 20 - 48,
+                                               $dg->{nBlocks} * 20);
+                } else {
+                    #
+                    # position the file at the start of the rsync block checksums
+                    # (4 (adler) + 16 (md4) bytes each)
+                    #
+                    return -8
+                        if ( !defined(sysseek($fh, -$dg->{nBlocks} * 20 - 48, 2)) );
+                }
             } else {
                 #
                 # cached checksums are not valid, so we close the
@@ -365,7 +378,12 @@ sub digestGet
     if ( $dg->{cached} ) {
         my $thisNum = $num;
         $thisNum = $dg->{nBlocks} if ( $thisNum > $dg->{nBlocks} );
-        read($dg->{fh}, $fileData, 20 * $thisNum);
+        if ( defined($dg->{digestData}) ) {
+            $fileData = substr($dg->{digestData}, 0, 20 * $thisNum);
+            $dg->{digestData} = substr($dg->{digestData}, 20 * $thisNum);
+        } else {
+            sysread($dg->{fh}, $fileData, 20 * $thisNum);
+        }
         $dg->{nBlocks} -= $thisNum;
         if ( $thisNum < $num && !$noPad) {
             #
index c34b019..60bfd36 100644 (file)
@@ -244,7 +244,7 @@ sub readOutput
             #
             # Ignore annoying log message on incremental for tar 1.15.x
             #
-            if ( !/: file is unchanged; not dumped$/ ) {
+            if ( !/: file is unchanged; not dumped$/ && !/: socket ignored$/ ) {
                 $t->{XferLOG}->write(\"$_\n") if ( $t->{logLevel} >= 0 );
                 $t->{xferErrCnt}++;
             }
index c2eac74..ad21c6f 100755 (executable)
--- a/makeDist
+++ b/makeDist
@@ -20,7 +20,7 @@
 #   Craig Barratt <cbarratt@users.sourceforge.net>
 #
 # COPYRIGHT
-#   Copyright (C) 2001-2006  Craig Barratt
+#   Copyright (C) 2001-2007  Craig Barratt
 #
 #   This program is free software; you can redistribute it and/or modify
 #   it under the terms of the GNU General Public License as published by
@@ -53,8 +53,8 @@ die("BackupPC::Lib->new failed\n")
 
 umask(0022);
 
-my $Version     = "3.0.0";
-my $ReleaseDate = "28 Jan 2007";
+my $Version     = "3.1.0";
+my $ReleaseDate = "15 Apr 2007";
 my $DistDir     = "dist/BackupPC-$Version";
 
 my @PerlSrc = qw(
@@ -223,8 +223,10 @@ foreach my $file ( (@PerlSrc,
                conf/hosts
                conf/BackupPC_stnd.css
                conf/BackupPC_stnd_orig.css
+                conf/sorttable.js
                init.d/README
                init.d/src/debian-backuppc
+               init.d/src/freebsd-backuppc
                init.d/src/gentoo-backuppc
                init.d/src/gentoo-backuppc.conf
                init.d/src/linux-backuppc