summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbescoto <bescoto@2b77aa54-bcbc-44c9-a7ec-4f6cf2b41109>2003-02-25 08:18:43 +0000
committerbescoto <bescoto@2b77aa54-bcbc-44c9-a7ec-4f6cf2b41109>2003-02-25 08:18:43 +0000
commit3f23be3b6e8f61de2ef2d8408c474e22df8cfeb9 (patch)
tree103076fa7e3c0e636b3ca77f6a58fb5a7664801e
parent39f64abf52669a32d2d58a7a056b89e6aa5feae7 (diff)
downloadrdiff-backup-3f23be3b6e8f61de2ef2d8408c474e22df8cfeb9.tar.gz
More changes in preparation for 0.11.2
git-svn-id: http://svn.savannah.nongnu.org/svn/rdiff-backup/trunk@284 2b77aa54-bcbc-44c9-a7ec-4f6cf2b41109
-rw-r--r--rdiff-backup/CHANGELOG32
-rw-r--r--rdiff-backup/rdiff-backup.110
-rw-r--r--rdiff-backup/rdiff_backup/Globals.py6
-rw-r--r--rdiff-backup/rdiff_backup/Main.py26
-rw-r--r--rdiff-backup/rdiff_backup/MiscStats.py4
-rw-r--r--rdiff-backup/rdiff_backup/Rdiff.py2
-rw-r--r--rdiff-backup/rdiff_backup/SetConnections.py4
-rw-r--r--rdiff-backup/rdiff_backup/Time.py3
-rw-r--r--rdiff-backup/rdiff_backup/backup.py88
-rw-r--r--rdiff-backup/rdiff_backup/connection.py21
-rw-r--r--rdiff-backup/rdiff_backup/increment.py32
-rw-r--r--rdiff-backup/rdiff_backup/log.py56
-rw-r--r--rdiff-backup/rdiff_backup/restore.py2
-rw-r--r--rdiff-backup/rdiff_backup/robust.py31
-rw-r--r--rdiff-backup/rdiff_backup/rpath.py42
-rw-r--r--rdiff-backup/rdiff_backup/selection.py10
-rw-r--r--rdiff-backup/rdiff_backup/statistics.py2
-rw-r--r--rdiff-backup/testing/commontest.py23
-rw-r--r--rdiff-backup/testing/finaltest.py11
-rw-r--r--rdiff-backup/testing/hardlinktest.py24
-rw-r--r--rdiff-backup/testing/regressiontest.py16
-rw-r--r--rdiff-backup/testing/robusttest.py36
-rw-r--r--rdiff-backup/testing/roottest.py66
23 files changed, 341 insertions, 206 deletions
diff --git a/rdiff-backup/CHANGELOG b/rdiff-backup/CHANGELOG
index 57948b4..dcaba8a 100644
--- a/rdiff-backup/CHANGELOG
+++ b/rdiff-backup/CHANGELOG
@@ -10,6 +10,38 @@ Fixed selection bug: In 0.11.1, files which were included in one
backup would be automatically included in the next. Now you can
include/exclude files session-by-session.
+Fixed ownership compare bug: In 0.11.1, backups where the destination
+side was not root would preserve ownership information by recording it
+in the metadata file. However, mere ownership changes would not
+trigger creation of new increments. This has been fixed.
+
+Added the --no-inode-compare switch. You probably don't need to use
+it though.
+
+If a special file cannot be created on the destination side, a 0
+length regular file will be written instead as a placeholder.
+(Restores should work fine because of the metadata file.)
+
+Yet another error handling strategy (hopefully this is the last one
+for a while, because this stuff isn't very exciting, and takes a long
+time to write):
+
+ All recoverable errors are classified into one of three groups:
+ ListErrors, UpdateErrors, and SpecialFileErrors. rdiff-backup's
+ reaction to each error is more formally defined (see the error
+ policy page, currently at
+ http://rdiff-backup.stanford.edu/error_policy.html).
+
+ rdiff-backup makes no attempt to recover or clean up after
+ unrecoverable errors.
+
+Improved error logging. Instead of the old haphazard reporting
+method, which sometimes didn't indicate the file an error occurred on,
+now all recoverable errors are reported in a standard format and also
+written to the error_log.<time>.data file in the rdiff-backup-data
+directory.
+
+
New in v0.11.1 (2002/12/31)
---------------------------
diff --git a/rdiff-backup/rdiff-backup.1 b/rdiff-backup/rdiff-backup.1
index 0f16d49..53b662d 100644
--- a/rdiff-backup/rdiff-backup.1
+++ b/rdiff-backup/rdiff-backup.1
@@ -1,5 +1,3 @@
-
-
.TH RDIFF-BACKUP 1 "AUGUST 2001" "Version 0.2.1" "User Manuals" \" -*- nroff -*-
.SH NAME
rdiff-backup \- local/remote mirror and incremental backup
@@ -207,6 +205,14 @@ Do not create an rdiff-backup-data directory or make any increments.
In this mode rdiff-backup is similar to rsync (but usually
slower).
.TP
+.B --no-compare-inode
+This relative esoteric option prevents rdiff-backup from flagging a
+file as changed when its inode changes. This option may be useful if
+you are backing up two different directories to the same rdiff-backup
+destination directory. The downside is that hard link information may
+get messed up, as the metadata file may no longer have the correct
+inode information.
+.TP
.B --no-compression
Disable the default gzip compression of most of the .snapshot and .diff
increment files stored in the rdiff-backup-data directory. A backup
diff --git a/rdiff-backup/rdiff_backup/Globals.py b/rdiff-backup/rdiff_backup/Globals.py
index 35c465b..f472512 100644
--- a/rdiff-backup/rdiff_backup/Globals.py
+++ b/rdiff-backup/rdiff_backup/Globals.py
@@ -40,7 +40,7 @@ conn_bufsize = 98304
# This is used in rorpiter.CacheIndexable. The number represents the
# number of rpaths which may be stuck in buffers when moving over a
# remote connection.
-pipeline_max_length = int(conn_bufsize / 150)
+pipeline_max_length = int(conn_bufsize / 150)*2
# True if script is running as a server
server = None
@@ -171,6 +171,10 @@ security_level = "all"
# deal with paths inside of restrict_path.
restrict_path = None
+# If set, a file will be marked as changed if its inode changes. See
+# the man page under --no-compare-inode for more information.
+compare_inode = 1
+
def get(name):
"""Return the value of something in this module"""
diff --git a/rdiff-backup/rdiff_backup/Main.py b/rdiff-backup/rdiff_backup/Main.py
index b9120fb..859bf97 100644
--- a/rdiff-backup/rdiff_backup/Main.py
+++ b/rdiff-backup/rdiff_backup/Main.py
@@ -21,7 +21,7 @@
from __future__ import generators
import getopt, sys, re, os
-from log import Log, LoggerError
+from log import Log, LoggerError, ErrorLog
import Globals, Time, SetConnections, selection, robust, rpath, \
manage, backup, connection, restore, FilenameMapping, \
Security, Hardlink, regress, C
@@ -51,7 +51,8 @@ def parse_cmdlineoptions(arglist):
"exclude-regexp=", "exclude-special-files", "force",
"include=", "include-filelist=", "include-filelist-stdin",
"include-globbing-filelist=", "include-regexp=",
- "list-changed-since=", "list-increments", "no-compression",
+ "list-changed-since=", "list-increments",
+ "no-compare-inode", "no-compression",
"no-compression-regexp=", "no-hard-links", "null-separator",
"parsable-output", "print-statistics", "quoting-char=",
"remote-cmd=", "remote-schema=", "remove-older-than=",
@@ -104,6 +105,7 @@ def parse_cmdlineoptions(arglist):
restore_timestr, action = arg, "list-changed-since"
elif opt == "-l" or opt == "--list-increments":
action = "list-increments"
+ elif opt == "--no-compare-inode": Globals.set("compare_inode", 0)
elif opt == "--no-compression": Globals.set("compression", None)
elif opt == "--no-compression-regexp":
Globals.set("no_compression_regexp_string", arg)
@@ -219,6 +221,7 @@ def take_action(rps):
def cleanup():
"""Do any last minute cleaning before exiting"""
Log("Cleaning up", 6)
+ if ErrorLog.isopen(): ErrorLog.close()
Log.close_logfile()
if not Globals.server: SetConnections.CloseConnections()
@@ -296,6 +299,7 @@ option.""" % rpout.path)
if not datadir.lstat(): datadir.mkdir()
if Log.verbosity > 0:
Log.open_logfile(datadir.append("backup.log"))
+ ErrorLog.open(Time.curtimestr, compress = Globals.compression)
backup_warn_if_infinite_regress(rpin, rpout)
def backup_warn_if_infinite_regress(rpin, rpout):
@@ -517,10 +521,11 @@ def RemoveOlderThan(rootrp):
Log("Deleting increment(s) before %s" % timep, 4)
times_in_secs = [inc.getinctime() for inc in
- restore.get_inclist(datadir.append_path("increments"))]
+ restore.get_inclist(Globals.rbdir.append_path("increments"))]
times_in_secs = filter(lambda t: t < time, times_in_secs)
if not times_in_secs:
- Log.FatalError("No increments older than %s found" % timep)
+ Log.FatalError("No increments older than %s found, exiting."
+ % (timep,), 1)
times_in_secs.sort()
inc_pretty_time = "\n".join(map(Time.timetopretty, times_in_secs))
@@ -532,7 +537,7 @@ def RemoveOlderThan(rootrp):
if len(times_in_secs) == 1:
Log("Deleting increment at time:\n" + inc_pretty_time, 3)
else: Log("Deleting increments at times:\n" + inc_pretty_time, 3)
- manage.delete_earlier_than(datadir, time)
+ manage.delete_earlier_than(Globals.rbdir, time)
def rom_check_dir(rootrp):
"""Check destination dir before RemoveOlderThan"""
@@ -571,7 +576,16 @@ def checkdest_need_check(dest_rp):
if not dest_rp.isdir() or not Globals.rbdir.isdir(): return None
curmirroot = Globals.rbdir.append("current_mirror")
curmir_incs = restore.get_inclist(curmirroot)
- if not curmir_incs: return None
+ if not curmir_incs:
+ Log.FatalError(
+"""Bad rdiff-backup-data dir on destination side
+
+The rdiff-backup data directory
+%s
+exists, but we cannot find a valid current_mirror marker. You can
+avoid this message by removing this directory; however any data in it
+will be lost.
+""" % (Globals.rbdir.path,))
elif len(curmir_incs) == 1: return 0
else:
assert len(curmir_incs) == 2, "Found too many current_mirror incs!"
diff --git a/rdiff-backup/rdiff_backup/MiscStats.py b/rdiff-backup/rdiff_backup/MiscStats.py
index 75a7bf9..1d30c99 100644
--- a/rdiff-backup/rdiff_backup/MiscStats.py
+++ b/rdiff-backup/rdiff_backup/MiscStats.py
@@ -44,7 +44,7 @@ def open_dir_stats_file():
if Globals.compression: suffix = "data.gz"
else: suffix = "data"
_dir_stats_rp = increment.get_inc(
- Globals.rbdir.append("directory_statistics"), Time.curtime, suffix)
+ Globals.rbdir.append("directory_statistics"), suffix, Time.curtime)
if _dir_stats_rp.lstat():
log.Log("Warning, statistics file %s already exists, appending" %
@@ -69,7 +69,7 @@ def close_dir_stats_file():
def write_session_statistics(statobj):
"""Write session statistics into file, log"""
stat_inc = increment.get_inc(
- Globals.rbdir.append("session_statistics"), Time.curtime, "data")
+ Globals.rbdir.append("session_statistics"), "data", Time.curtime)
statobj.StartTime = Time.curtime
statobj.EndTime = time.time()
diff --git a/rdiff-backup/rdiff_backup/Rdiff.py b/rdiff-backup/rdiff_backup/Rdiff.py
index a14bd32..ee688c1 100644
--- a/rdiff-backup/rdiff_backup/Rdiff.py
+++ b/rdiff-backup/rdiff_backup/Rdiff.py
@@ -25,7 +25,7 @@ import Globals, log, static, TempFile, rpath
def get_signature(rp):
"""Take signature of rpin file and return in file object"""
- log.Log("Getting signature of %s" % rp.path, 7)
+ log.Log("Getting signature of %s" % rp.get_indexpath(), 7)
return librsync.SigFile(rp.open("rb"))
def get_delta_sigfileobj(sig_fileobj, rp_new):
diff --git a/rdiff-backup/rdiff_backup/SetConnections.py b/rdiff-backup/rdiff_backup/SetConnections.py
index f1c54fe..b74aec5 100644
--- a/rdiff-backup/rdiff_backup/SetConnections.py
+++ b/rdiff-backup/rdiff_backup/SetConnections.py
@@ -172,8 +172,8 @@ def init_connection_routing(conn, conn_number, remote_cmd):
def init_connection_settings(conn):
"""Tell new conn about log settings and updated globals"""
- conn.Log.setverbosity(Log.verbosity)
- conn.Log.setterm_verbosity(Log.term_verbosity)
+ conn.log.Log.setverbosity(Log.verbosity)
+ conn.log.Log.setterm_verbosity(Log.term_verbosity)
for setting_name in Globals.changed_settings:
conn.Globals.set(setting_name, Globals.get(setting_name))
FilenameMapping.set_init_quote_vals()
diff --git a/rdiff-backup/rdiff_backup/Time.py b/rdiff-backup/rdiff_backup/Time.py
index bdcfb52..1b2975e 100644
--- a/rdiff-backup/rdiff_backup/Time.py
+++ b/rdiff-backup/rdiff_backup/Time.py
@@ -49,7 +49,8 @@ def setcurtime_local(timeinseconds):
def setprevtime(timeinseconds):
"""Sets the previous inc time in prevtime and prevtimestr"""
- assert timeinseconds > 0, timeinseconds
+ assert 0 < timeinseconds < curtime, \
+ "Time %s is out of bounds" % (timeinseconds,)
timestr = timetostring(timeinseconds)
for conn in Globals.connections:
conn.Time.setprevtime_local(timeinseconds, timestr)
diff --git a/rdiff-backup/rdiff_backup/backup.py b/rdiff-backup/rdiff_backup/backup.py
index 78bfbe9..6e87048 100644
--- a/rdiff-backup/rdiff_backup/backup.py
+++ b/rdiff-backup/rdiff_backup/backup.py
@@ -30,7 +30,7 @@ def Mirror(src_rpath, dest_rpath):
source_rpiter = SourceS.get_source_select()
DestS.set_rorp_cache(dest_rpath, source_rpiter, 0)
- dest_sigiter = DestS.get_sigs()
+ dest_sigiter = DestS.get_sigs(dest_rpath)
source_diffiter = SourceS.get_diffs(dest_sigiter)
DestS.patch(dest_rpath, source_diffiter)
@@ -41,7 +41,7 @@ def Mirror_and_increment(src_rpath, dest_rpath, inc_rpath):
source_rpiter = SourceS.get_source_select()
DestS.set_rorp_cache(dest_rpath, source_rpiter, 1)
- dest_sigiter = DestS.get_sigs()
+ dest_sigiter = DestS.get_sigs(dest_rpath)
source_diffiter = SourceS.get_diffs(dest_sigiter)
DestS.patch_and_increment(dest_rpath, source_diffiter, inc_rpath)
@@ -74,23 +74,37 @@ class SourceStruct:
def get_diffs(cls, dest_sigiter):
"""Return diffs of any files with signature in dest_sigiter"""
source_rps = cls.source_select
- def get_one_diff(dest_sig):
+ error_handler = robust.get_error_handler("ListError")
+ def attach_snapshot(diff_rorp, src_rp):
+ """Attach file of snapshot to diff_rorp, w/ error checking"""
+ fileobj = robust.check_common_error(
+ error_handler, rpath.RPath.open, (src_rp, "rb"))
+ if fileobj: diff_rorp.setfile(fileobj)
+ else: diff_rorp.zero()
+ diff_rorp.set_attached_filetype('snapshot')
+
+ def attach_diff(diff_rorp, src_rp, dest_sig):
+ """Attach file of diff to diff_rorp, w/ error checking"""
+ fileobj = robust.check_common_error(
+ error_handler, Rdiff.get_delta_sigrp, (dest_sig, src_rp))
+ if fileobj:
+ diff_rorp.setfile(fileobj)
+ diff_rorp.set_attached_filetype('diff')
+ else:
+ diff_rorp.zero()
+ diff_rorp.set_attached_filetype('snapshot')
+
+ for dest_sig in dest_sigiter:
src_rp = (source_rps.get(dest_sig.index) or
rpath.RORPath(dest_sig.index))
diff_rorp = src_rp.getRORPath()
if dest_sig.isflaglinked():
diff_rorp.flaglinked(dest_sig.get_link_flag())
elif dest_sig.isreg() and src_rp.isreg():
- diff_rorp.setfile(Rdiff.get_delta_sigrp(dest_sig, src_rp))
- diff_rorp.set_attached_filetype('diff')
- else:
- diff_rorp.set_attached_filetype('snapshot')
- if src_rp.isreg(): diff_rorp.setfile(src_rp.open("rb"))
- return diff_rorp
-
- for dest_sig in dest_sigiter:
- diff = robust.check_common_error(None, get_one_diff, [dest_sig])
- if diff: yield diff
+ attach_diff(diff_rorp, src_rp, dest_sig)
+ elif src_rp.isreg(): attach_snapshot(diff_rorp, src_rp)
+ else: diff_rorp.set_attached_filetype('snapshot')
+ yield diff_rorp
static.MakeClass(SourceStruct)
@@ -127,7 +141,7 @@ class DestinationStruct:
cls.CCPP = CacheCollatedPostProcess(collated,
Globals.pipeline_max_length*2)
- def get_sigs(cls):
+ def get_sigs(cls, dest_base_rpath):
"""Yield signatures of any changed destination files"""
for src_rorp, dest_rorp in cls.CCPP:
if (src_rorp and dest_rorp and src_rorp == dest_rorp and
@@ -142,7 +156,9 @@ class DestinationStruct:
elif dest_rorp:
dest_sig = dest_rorp.getRORPath()
if dest_rorp.isreg():
- dest_sig.setfile(Rdiff.get_signature(dest_rorp))
+ dest_rp = dest_base_rpath.new_index(index)
+ assert dest_rp.isreg()
+ dest_sig.setfile(Rdiff.get_signature(dest_rp))
else: dest_sig = rpath.RORPath(index)
yield dest_sig
@@ -250,14 +266,12 @@ class CacheCollatedPostProcess:
"""
if not changed or success:
- self.statfileobj.add_source_file(source_rorp)
- self.statfileobj.add_dest_file(dest_rorp)
+ if source_rorp: self.statfileobj.add_source_file(source_rorp)
+ if dest_rorp: self.statfileobj.add_dest_file(dest_rorp)
if success:
self.statfileobj.add_changed(source_rorp, dest_rorp)
metadata_rorp = source_rorp
- else:
- metadata_rorp = dest_rorp
- if changed: self.statfileobj.add_error()
+ else: metadata_rorp = dest_rorp
if metadata_rorp and metadata_rorp.lstat():
metadata.WriteMetadata(metadata_rorp)
@@ -294,7 +308,7 @@ class PatchITRB(rorpiter.ITRBranch):
contents.
"""
- def __init__(self, basis_root_rp, rorp_cache):
+ def __init__(self, basis_root_rp, CCPP):
"""Set basis_root_rp, the base of the tree to be incremented"""
self.basis_root_rp = basis_root_rp
assert basis_root_rp.conn is Globals.local_connection
@@ -302,6 +316,8 @@ class PatchITRB(rorpiter.ITRBranch):
statistics.StatFileObj())
self.dir_replacement, self.dir_update = None, None
self.cached_rp = None
+ self.CCPP = CCPP
+ self.error_handler = robust.get_error_handler("UpdateError")
def get_rp_from_root(self, index):
"""Return RPath by adding index to self.basis_root_rp"""
@@ -318,19 +334,36 @@ class PatchITRB(rorpiter.ITRBranch):
"""Patch base_rp with diff_rorp (case where neither is directory)"""
rp = self.get_rp_from_root(index)
tf = TempFile.new(rp)
- self.patch_to_temp(rp, diff_rorp, tf)
- rpath.rename(tf, rp)
+ if self.patch_to_temp(rp, diff_rorp, tf):
+ if tf.lstat(): rpath.rename(tf, rp)
+ elif rp.lstat(): rp.delete()
+ self.CCPP.flag_success(index)
+ else:
+ tf.setdata()
+ if tf.lstat(): tf.delete()
def patch_to_temp(self, basis_rp, diff_rorp, new):
"""Patch basis_rp, writing output in new, which doesn't exist yet"""
if diff_rorp.isflaglinked():
Hardlink.link_rp(diff_rorp, new, self.basis_root_rp)
elif diff_rorp.get_attached_filetype() == 'snapshot':
- rpath.copy(diff_rorp, new)
+ if diff_rorp.isspecial(): self.write_special(diff_rorp, new)
+ elif robust.check_common_error(self.error_handler, rpath.copy,
+ (diff_rorp, new)) == 0: return 0
else:
assert diff_rorp.get_attached_filetype() == 'diff'
- Rdiff.patch_local(basis_rp, diff_rorp, new)
+ if robust.check_common_error(self.error_handler,
+ Rdiff.patch_local, (basis_rp, diff_rorp, new)) == 0: return 0
if new.lstat(): rpath.copy_attribs(diff_rorp, new)
+ return 1
+
+ def write_special(self, diff_rorp, new):
+ """Write diff_rorp (which holds special file) to new"""
+ eh = robust.get_error_handler("SpecialFileError")
+ if robust.check_common_error(eh, rpath.copy, (diff_rorp, new)) == 0:
+ new.setdata()
+ if new.lstat(): new.delete()
+ new.touch()
def start_process(self, index, diff_rorp):
"""Start processing directory - record information for later"""
@@ -368,6 +401,7 @@ class PatchITRB(rorpiter.ITRBranch):
self.base_rp.rmdir()
if self.dir_replacement.lstat():
rpath.rename(self.dir_replacement, self.base_rp)
+ self.CCPP.flag_success(self.base_rp.index)
class IncrementITRB(PatchITRB):
@@ -393,7 +427,9 @@ class IncrementITRB(PatchITRB):
tf = TempFile.new(rp)
self.patch_to_temp(rp, diff_rorp, tf)
increment.Increment(tf, rp, self.get_incrp(index))
- rpath.rename(tf, rp)
+ if tf.lstat(): rpath.rename(tf, rp)
+ else: rp.delete()
+ self.CCPP.flag_success(index)
def start_process(self, index, diff_rorp):
"""Start processing directory"""
diff --git a/rdiff-backup/rdiff_backup/connection.py b/rdiff-backup/rdiff_backup/connection.py
index 90b8ea3..8b0da50 100644
--- a/rdiff-backup/rdiff_backup/connection.py
+++ b/rdiff-backup/rdiff_backup/connection.py
@@ -20,7 +20,8 @@
"""Support code for remote execution and data transfer"""
from __future__ import generators
-import types, os, tempfile, cPickle, shutil, traceback, pickle, socket, sys
+import types, os, tempfile, cPickle, shutil, traceback, pickle, \
+ socket, sys, gzip
class ConnectionError(Exception): pass
@@ -39,6 +40,7 @@ class Connection:
"""
def __repr__(self): return self.__str__()
def __str__(self): return "Simple Connection" # override later
+ def __nonzero__(self): return 1
class LocalConnection(Connection):
"""Local connection
@@ -117,7 +119,7 @@ class LowLevelPipeConnection(Connection):
def _put(self, obj, req_num):
"""Put an object into the pipe (will send raw if string)"""
- Log.conn("sending", obj, req_num)
+ log.Log.conn("sending", obj, req_num)
if type(obj) is types.StringType: self._putbuf(obj, req_num)
elif isinstance(obj, connection.Connection):self._putconn(obj, req_num)
elif isinstance(obj, rpath.RPath): self._putrpath(obj, req_num)
@@ -231,7 +233,7 @@ class LowLevelPipeConnection(Connection):
else:
assert format_string == "c", header_string
result = Globals.connection_dict[int(data)]
- Log.conn("received", result, req_num)
+ log.Log.conn("received", result, req_num)
return (req_num, result)
def _getrorpath(self, raw_rorpath_buf):
@@ -315,17 +317,17 @@ class PipeConnection(LowLevelPipeConnection):
def extract_exception(self):
"""Return active exception"""
- if Log.verbosity >= 5 or Log.term_verbosity >= 5:
- Log("Sending back exception %s of type %s: \n%s" %
- (sys.exc_info()[1], sys.exc_info()[0],
- "".join(traceback.format_tb(sys.exc_info()[2]))), 5)
+ if log.Log.verbosity >= 5 or log.Log.term_verbosity >= 5:
+ log.Log("Sending back exception %s of type %s: \n%s" %
+ (sys.exc_info()[1], sys.exc_info()[0],
+ "".join(traceback.format_tb(sys.exc_info()[2]))), 5)
return sys.exc_info()[1]
def Server(self):
"""Start server's read eval return loop"""
Globals.server = 1
Globals.connections.append(self)
- Log("Starting server", 6)
+ log.Log("Starting server", 6)
self.get_response(-1)
def reval(self, function_string, *args):
@@ -510,8 +512,7 @@ class VirtualFile:
import Globals, Time, Rdiff, Hardlink, FilenameMapping, C, Security, \
Main, rorpiter, selection, increment, statistics, manage, lazy, \
iterfile, rpath, robust, restore, manage, backup, connection, \
- TempFile, SetConnections, librsync
-from log import Log
+ TempFile, SetConnections, librsync, log
Globals.local_connection = LocalConnection()
Globals.connections.append(Globals.local_connection)
diff --git a/rdiff-backup/rdiff_backup/increment.py b/rdiff-backup/rdiff_backup/increment.py
index 76578fe..8df04dc 100644
--- a/rdiff-backup/rdiff_backup/increment.py
+++ b/rdiff-backup/rdiff_backup/increment.py
@@ -50,7 +50,7 @@ def Increment(new, mirror, incpref):
def makemissing(incpref):
"""Signify that mirror file was missing"""
- incrp = get_inc_ext(incpref, "missing")
+ incrp = get_inc(incpref, "missing")
incrp.touch()
return incrp
@@ -62,16 +62,16 @@ def iscompressed(mirror):
def makesnapshot(mirror, incpref):
"""Copy mirror to incfile, since new is quite different"""
compress = iscompressed(mirror)
- if compress: snapshotrp = get_inc_ext(incpref, "snapshot.gz")
- else: snapshotrp = get_inc_ext(incpref, "snapshot")
+ if compress: snapshotrp = get_inc(incpref, "snapshot.gz")
+ else: snapshotrp = get_inc(incpref, "snapshot")
rpath.copy_with_attribs(mirror, snapshotrp, compress)
return snapshotrp
def makediff(new, mirror, incpref):
"""Make incfile which is a diff new -> mirror"""
compress = iscompressed(mirror)
- if compress: diff = get_inc_ext(incpref, "diff.gz")
- else: diff = get_inc_ext(incpref, "diff")
+ if compress: diff = get_inc(incpref, "diff.gz")
+ else: diff = get_inc(incpref, "diff")
Rdiff.write_delta(new, mirror, diff, compress)
rpath.copy_attribs(mirror, diff)
@@ -79,18 +79,19 @@ def makediff(new, mirror, incpref):
def makedir(mirrordir, incpref):
"""Make file indicating directory mirrordir has changed"""
- dirsign = get_inc_ext(incpref, "dir")
+ dirsign = get_inc(incpref, "dir")
dirsign.touch()
rpath.copy_attribs(mirrordir, dirsign)
return dirsign
-def get_inc(rp, time, typestr):
+def get_inc(rp, typestr, time = None):
"""Return increment like rp but with time and typestr suffixes
To avoid any quoting, the returned rpath has empty index, and the
whole filename is in the base (which is not quoted).
"""
+ if time is None: time = Time.prevtime
addtostr = lambda s: "%s.%s.%s" % (s, Time.timetostring(time), typestr)
if rp.index:
incrp = rp.__class__(rp.conn, rp.base, rp.index[:-1] +
@@ -98,22 +99,7 @@ def get_inc(rp, time, typestr):
else:
dirname, basename = rp.dirsplit()
incrp = rp.__class__(rp.conn, dirname, (addtostr(basename),))
+ assert not incrp.lstat()
return incrp
-def get_inc_ext(rp, typestr, inctime = None):
- """Return increment with specified type and time t
-
- If the file exists, then probably a previous backup has been
- aborted. We then keep asking FindTime to get a time later
- than the one that already has an inc file.
-
- """
- if inctime is None: inctime = Time.prevtime
- while 1:
- incrp = get_inc(rp, inctime, typestr)
- if not incrp.lstat(): break
- else:
- inctime += 1
- log.Log("Warning, increment %s already exists" % (incrp.path,), 2)
- return incrp
diff --git a/rdiff-backup/rdiff_backup/log.py b/rdiff-backup/rdiff_backup/log.py
index 421f78e..60ef434 100644
--- a/rdiff-backup/rdiff_backup/log.py
+++ b/rdiff-backup/rdiff_backup/log.py
@@ -20,7 +20,7 @@
"""Manage logging, displaying and recording messages with required verbosity"""
import time, sys, traceback, types
-import Globals, static
+import Globals, static, re
class LoggerError(Exception): pass
@@ -58,9 +58,9 @@ class Logger:
"""
assert not self.log_file_open
- rpath.conn.Log.open_logfile_local(rpath)
+ rpath.conn.log.Log.open_logfile_local(rpath)
for conn in Globals.connections:
- conn.Log.open_logfile_allconn(rpath.conn)
+ conn.log.Log.open_logfile_allconn(rpath.conn)
def open_logfile_allconn(self, log_file_conn):
"""Run on all connections to signal log file is open"""
@@ -81,8 +81,8 @@ class Logger:
"""Close logfile and inform all connections"""
if self.log_file_open:
for conn in Globals.connections:
- conn.Log.close_logfile_allconn()
- self.log_file_conn.Log.close_logfile_local()
+ conn.log.Log.close_logfile_allconn()
+ self.log_file_conn.log.Log.close_logfile_local()
def close_logfile_allconn(self):
"""Run on every connection"""
@@ -125,7 +125,7 @@ class Logger:
if self.log_file_open:
if self.log_file_local:
self.logfp.write(self.format(message, self.verbosity))
- else: self.log_file_conn.Log.log_to_file(message)
+ else: self.log_file_conn.log.Log.log_to_file(message)
def log_to_term(self, message, verbosity):
"""Write message to stdout/stderr"""
@@ -150,8 +150,12 @@ class Logger:
self.log_to_term("%s %s (%d): %s" %
(conn_str, direction, req_num, result_repr), 9)
- def FatalError(self, message):
- self("Fatal Error: " + message, 1)
+ def FatalError(self, message, no_fatal_message = 0):
+ """Log a fatal error and exit"""
+ assert no_fatal_message == 0 or no_fatal_message == 1
+ if no_fatal_message: prefix_string = ""
+ else: prefix_string = "Fatal Error: "
+ self(prefix_string + message, 1)
import Main
Main.cleanup()
sys.exit(1)
@@ -196,22 +200,35 @@ class ErrorLog:
"""
_log_fileobj = None
_log_inc_rp = None
- def open(cls, compress = 1):
+ def open(cls, time_string, compress = 1):
"""Open the error log, prepare for writing"""
+ if not Globals.isbackup_writer:
+ return Globals.backup_writer.log.ErrorLog.open(time_string,
+ compress)
assert not cls._log_fileobj and not cls._log_inc_rp, "log already open"
+ assert Globals.isbackup_writer
if compress: typestr = 'data.gz'
else: typestr = 'data'
- cls._log_inc_rp = Global.rbdir.append("error_log.%s.%s" %
- (Time.curtimestr, typestr))
- assert not cls._log_inc_rp.lstat(), "Error file already exists"
+ cls._log_inc_rp = Globals.rbdir.append("error_log.%s.%s" %
+ (time_string, typestr))
+ assert not cls._log_inc_rp.lstat(), ("""Error file %s already exists.
+
+This is probably caused by your attempting to run two backups simultaneously
+or within one second of each other. Wait a second and try again.""" %
+ (cls._log_inc_rp.path,))
cls._log_fileobj = cls._log_inc_rp.open("wb", compress = compress)
def isopen(cls):
"""True if the error log file is currently open"""
- return cls._log_fileobj is not None
+ if Globals.isbackup_writer or not Globals.backup_writer:
+ return cls._log_fileobj is not None
+ else: return Globals.backup_writer.log.ErrorLog.isopen()
def write(cls, error_type, rp, exc):
"""Add line to log file indicating error exc with file rp"""
+ if not Globals.isbackup_writer:
+ return Globals.backup_writer.log.ErrorLog.write(error_type,
+ rp, exc)
s = cls.get_log_string(error_type, rp, exc)
Log(s, 2)
if Globals.null_separator: s += "\0"
@@ -220,15 +237,18 @@ class ErrorLog:
s += "\n"
cls._log_fileobj.write(s)
- def get_indexpath(cls, rp):
+ def get_indexpath(cls, obj):
"""Return filename for logging. rp is a rpath, string, or tuple"""
- try: return rp.get_indexpath()
+ try: return obj.get_indexpath()
except AttributeError:
- if type(rp) is types.TupleTypes: return "/".join(rp)
- else: return str(rp)
+ if type(obj) is types.TupleType: return "/".join(obj)
+ else: return str(obj)
def write_if_open(cls, error_type, rp, exc):
"""Call cls.write(...) if error log open, only log otherwise"""
+ if not Globals.isbackup_writer:
+ return Globals.backup_writer.log.ErrorLog.write_if_open(
+ error_type, rp, exc)
if cls.isopen(): cls.write(error_type, rp, exc)
else: Log(cls.get_log_string(error_type, rp, exc), 2)
@@ -240,6 +260,8 @@ class ErrorLog:
def close(cls):
"""Close the error log file"""
+ if not Globals.isbackup_writer:
+ return Globals.backup_writer.log.ErrorLog.close()
assert not cls._log_fileobj.close()
cls._log_fileobj = cls._log_inc_rp = None
diff --git a/rdiff-backup/rdiff_backup/restore.py b/rdiff-backup/rdiff_backup/restore.py
index 54d29d2..67b8bad 100644
--- a/rdiff-backup/rdiff_backup/restore.py
+++ b/rdiff-backup/rdiff_backup/restore.py
@@ -472,8 +472,6 @@ class PatchITRB(rorpiter.ITRBranch):
"""Set basis_root_rp, the base of the tree to be incremented"""
self.basis_root_rp = basis_root_rp
assert basis_root_rp.conn is Globals.local_connection
- self.statfileobj = (statistics.get_active_statfileobj() or
- statistics.StatFileObj())
self.dir_replacement, self.dir_update = None, None
self.cached_rp = None
diff --git a/rdiff-backup/rdiff_backup/robust.py b/rdiff-backup/rdiff_backup/robust.py
index e43ceea..f7f9c01 100644
--- a/rdiff-backup/rdiff_backup/robust.py
+++ b/rdiff-backup/rdiff_backup/robust.py
@@ -19,7 +19,8 @@
"""Catch various exceptions given system call"""
-import librsync, errno, signal, C, static, rpath, Globals, log, statistics
+import errno, signal
+import librsync, C, static, rpath, Globals, log, statistics
def check_common_error(error_handler, function, args = []):
"""Apply function to args, if error, run error_handler on exception
@@ -34,9 +35,9 @@ def check_common_error(error_handler, function, args = []):
if catch_error(exc):
log.Log.exception()
conn = Globals.backup_writer
- if conn is not None: statistics.record_error()
+ if conn is not None: conn.statistics.record_error()
if error_handler: return error_handler(exc, *args)
- else: return
+ else: return None
log.Log.exception(1, 2)
raise
@@ -46,13 +47,29 @@ def catch_error(exc):
librsync.librsyncError, C.UnknownFileTypeError):
if isinstance(exc, exception_class): return 1
if (isinstance(exc, EnvironmentError) and
- errno.errorcode[exc[0]] in ('EPERM', 'ENOENT', 'EACCES', 'EBUSY',
- 'EEXIST', 'ENOTDIR', 'ENAMETOOLONG',
- 'EINTR', 'ENOTEMPTY', 'EIO', 'ETXTBSY',
- 'ESRCH', 'EINVAL')):
+ # the invalid mode shows up in backups of /proc for some reason
+ (exc[0] == 'invalid mode: rb' or
+ errno.errorcode.has_key(exc[0]) and
+ errno.errorcode[exc[0]] in ('EPERM', 'ENOENT', 'EACCES', 'EBUSY',
+ 'EEXIST', 'ENOTDIR', 'ENAMETOOLONG',
+ 'EINTR', 'ENOTEMPTY', 'EIO', 'ETXTBSY',
+ 'ESRCH', 'EINVAL'))):
return 1
return 0
+def get_error_handler(error_type):
+ """Return error handler function that can be used above
+
+ Function will just log error to the error_log and then return
+ None. First two arguments must be the exception and then an rp
+ (from which the filename will be extracted).
+
+ """
+ def error_handler(exc, rp, *args):
+ log.ErrorLog.write_if_open(error_type, rp, exc)
+ return 0
+ return error_handler
+
def listrp(rp):
"""Like rp.listdir() but return [] if error, and sort results"""
def error_handler(exc):
diff --git a/rdiff-backup/rdiff_backup/rpath.py b/rdiff-backup/rdiff_backup/rpath.py
index 7d7c440..16cc577 100644
--- a/rdiff-backup/rdiff_backup/rpath.py
+++ b/rdiff-backup/rdiff_backup/rpath.py
@@ -209,6 +209,8 @@ def rename(rp_source, rp_dest):
if not rp_source.lstat(): rp_dest.delete()
else:
if rp_dest.lstat() and rp_source.getinode() == rp_dest.getinode():
+ assert 0, ("Rename over same inode: %s to %s" %
+ (rp_source.path, rp_dest.path))
# You can't rename one hard linked file over another
rp_source.delete()
else: rp_source.conn.os.rename(rp_source.path, rp_dest.path)
@@ -266,24 +268,30 @@ class RORPath:
else: self.data = {'type':None} # signify empty file
self.file = None
+ def zero(self):
+ """Set inside of self to type None"""
+ self.data = {'type': None}
+ self.file = None
+
def __eq__(self, other):
"""True iff the two rorpaths are equivalent"""
if self.index != other.index: return None
for key in self.data.keys(): # compare dicts key by key
- if ((key == 'uid' or key == 'gid') and
- (not Globals.change_ownership or self.issym())):
- # Don't compare gid/uid for symlinks or if not change_ownership
+ if (key == 'uid' or key == 'gid') and self.issym():
+ # Don't compare gid/uid for symlinks
pass
elif key == 'atime' and not Globals.preserve_atime: pass
- elif key == 'devloc' or key == 'inode' or key == 'nlink': pass
- elif key == 'size' and not self.isreg():
- pass # size only matters for regular files
+ elif key == 'devloc' or key == 'nlink': pass
+ elif key == 'size' and not self.isreg(): pass
+ elif key == 'inode' and (not self.isreg() or
+ not Globals.compare_inode): pass
elif (not other.data.has_key(key) or
self.data[key] != other.data[key]): return None
return 1
- def equal_verbose(self, other, check_index = 1):
+ def equal_verbose(self, other, check_index = 1,
+ compare_inodes = 0, compare_ownership = 0):
"""Like __eq__, but log more information. Useful when testing"""
if check_index and self.index != other.index:
log.Log("Index %s != index %s" % (self.index, other.index), 2)
@@ -291,12 +299,14 @@ class RORPath:
for key in self.data.keys(): # compare dicts key by key
if ((key == 'uid' or key == 'gid') and
- (not Globals.change_ownership or self.issym())):
- # Don't compare gid/uid for symlinks or if not change_ownership
+ (self.issym() or not compare_ownership)):
+ # Don't compare gid/uid for symlinks, or if told not to
pass
elif key == 'atime' and not Globals.preserve_atime: pass
- elif key == 'devloc' or key == 'inode' or key == 'nlink': pass
+ elif key == 'devloc' or key == 'nlink': pass
elif key == 'size' and not self.isreg(): pass
+ elif key == 'inode' and (not self.isreg() or not compare_inodes):
+ pass
elif (not other.data.has_key(key) or
self.data[key] != other.data[key]):
if not other.data.has_key(key):
@@ -312,6 +322,10 @@ class RORPath:
"""Pretty print file statistics"""
return "Index: %s\nData: %s" % (self.index, self.data)
+ def summary_string(self):
+ """Return summary string"""
+ return "%s %s" % (self.get_indexpath(), self.lstat())
+
def __getstate__(self):
"""Return picklable state
@@ -373,6 +387,12 @@ class RORPath:
"""True if path is a socket"""
return self.data['type'] == 'sock'
+ def isspecial(self):
+ """True if the file is a sock, symlink, device, or fifo"""
+ type = self.data['type']
+ return (type == 'dev' or type == 'sock' or
+ type == 'fifo' or type == 'sym')
+
def getperms(self):
"""Return permission block of file"""
return self.data['perms']
@@ -662,7 +682,7 @@ class RPath(RORPath):
log.Log("Touching " + self.path, 7)
self.conn.open(self.path, "w").close()
self.setdata()
- assert self.isreg()
+ assert self.isreg(), self.path
def hasfullperms(self):
"""Return true if current process has full permissions on the file"""
diff --git a/rdiff-backup/rdiff_backup/selection.py b/rdiff-backup/rdiff_backup/selection.py
index 6c4cabe..1dfaf5d 100644
--- a/rdiff-backup/rdiff_backup/selection.py
+++ b/rdiff-backup/rdiff_backup/selection.py
@@ -95,19 +95,11 @@ class Select:
"""
if not sel_func: sel_func = self.Select
self.rpath.setdata() # this may have changed since Select init
- self.iter = self.filter_readable(self.Iterate_fast(self.rpath,
- sel_func))
+ self.iter = self.Iterate_fast(self.rpath, sel_func)
self.next = self.iter.next
self.__iter__ = lambda: self
return self
- def filter_readable(self, rp_iter):
- """Yield rps in iter except the unreadable regular files"""
- for rp in rp_iter:
- if not rp.isreg() or rp.readable(): yield rp
- else: log.ErrorLog.write_if_open("ListError", rp,
- "Regular file lacks read permissions")
-
def Iterate_fast(self, rpath, sel_func):
"""Like Iterate, but don't recur, saving time"""
def error_handler(exc, filename):
diff --git a/rdiff-backup/rdiff_backup/statistics.py b/rdiff-backup/rdiff_backup/statistics.py
index f344472..6356101 100644
--- a/rdiff-backup/rdiff_backup/statistics.py
+++ b/rdiff-backup/rdiff_backup/statistics.py
@@ -338,7 +338,7 @@ def write_active_statfileobj():
global _active_statfileobj
assert _active_statfileobj
rp_base = Globals.rbdir.append("session_statistics")
- session_stats_rp = increment.get_inc_ext(rp_base, 'data', Time.curtime)
+ session_stats_rp = increment.get_inc(rp_base, 'data', Time.curtime)
_active_statfileobj.finish()
_active_statfileobj.write_stats_to_rp(session_stats_rp)
_active_statfileobj = None
diff --git a/rdiff-backup/testing/commontest.py b/rdiff-backup/testing/commontest.py
index c527e83..8bd0f53 100644
--- a/rdiff-backup/testing/commontest.py
+++ b/rdiff-backup/testing/commontest.py
@@ -112,14 +112,7 @@ def InternalMirror(source_local, dest_local, src_dir, dest_dir):
src_root = rpath.RPath(Globals.local_connection, src_dir)
dest_root = rpath.RPath(Globals.local_connection, dest_dir)
dest_rbdir = dest_root.append("rdiff-backup-data")
- dest_incdir = dest_rbdir.append("increments")
- # We need to create these directories or else failure because
- # --force option not given.
- if not dest_root.lstat(): dest_root.mkdir()
- if not dest_rbdir.lstat(): dest_rbdir.mkdir()
- if not dest_incdir.lstat(): dest_incdir.mkdir()
-
InternalBackup(source_local, dest_local, src_dir, dest_dir)
dest_root.setdata()
Myrm(dest_rbdir.path)
@@ -173,7 +166,7 @@ def _reset_connections(src_rp, dest_rp):
def CompareRecursive(src_rp, dest_rp, compare_hardlinks = 1,
equality_func = None, exclude_rbdir = 1,
- ignore_tmp_files = None):
+ ignore_tmp_files = None, compare_ownership = 0):
"""Compare src_rp and dest_rp, which can be directories
This only compares file attributes, not the actual data. This
@@ -218,7 +211,9 @@ def CompareRecursive(src_rp, dest_rp, compare_hardlinks = 1,
dsiter1, dsiter2 = src_select.set_iter(), dest_select.set_iter()
def hardlink_equal(src_rorp, dest_rorp):
- if not src_rorp.equal_verbose(dest_rorp): return None
+ if not src_rorp.equal_verbose(dest_rorp,
+ compare_ownership = compare_ownership):
+ return None
if Hardlink.rorp_eq(src_rorp, dest_rorp): return 1
Log("%s: %s" % (src_rorp.index, Hardlink.get_indicies(src_rorp, 1)), 3)
Log("%s: %s" % (dest_rorp.index,
@@ -240,7 +235,9 @@ def CompareRecursive(src_rp, dest_rp, compare_hardlinks = 1,
if dest_rorp.index[-1].endswith('.missing'): return 1
if compare_hardlinks:
if Hardlink.rorp_eq(src_rorp, dest_rorp): return 1
- elif src_rorp.equal_verbose(dest_rorp): return 1
+ elif src_rorp.equal_verbose(dest_rorp,
+ compare_ownership = compare_ownership):
+ return 1
Log("%s: %s" % (src_rorp.index, Hardlink.get_indicies(src_rorp, 1)), 3)
Log("%s: %s" % (dest_rorp.index,
Hardlink.get_indicies(dest_rorp, None)), 3)
@@ -256,7 +253,8 @@ def CompareRecursive(src_rp, dest_rp, compare_hardlinks = 1,
else: result = lazy.Iter.equal(dsiter1, dsiter2, 1, rbdir_equal)
elif not exclude_rbdir:
result = lazy.Iter.equal(dsiter1, dsiter2, 1, rbdir_equal)
- else: result = lazy.Iter.equal(dsiter1, dsiter2, 1)
+ else: result = lazy.Iter.equal(dsiter1, dsiter2, 1,
+ lambda x, y: x.equal_verbose(y, compare_ownership = compare_ownership))
for i in dsiter1: pass # make sure all files processed anyway
for i in dsiter2: pass
@@ -321,6 +319,8 @@ def MirrorTest(source_local, dest_local, list_of_dirnames,
"""Mirror each of list_of_dirnames, and compare after each"""
Globals.set('preserve_hardlinks', compare_hardlinks)
dest_rp = rpath.RPath(Globals.local_connection, dest_dirname)
+ old_force_val = Main.force
+ Main.force = 1
Myrm(dest_dirname)
for dirname in list_of_dirnames:
@@ -331,3 +331,4 @@ def MirrorTest(source_local, dest_local, list_of_dirnames,
InternalMirror(source_local, dest_local, dirname, dest_dirname)
_reset_connections(src_rp, dest_rp)
assert CompareRecursive(src_rp, dest_rp, compare_hardlinks)
+ Main.force = old_force_val
diff --git a/rdiff-backup/testing/finaltest.py b/rdiff-backup/testing/finaltest.py
index 44a8a2a..7945eb5 100644
--- a/rdiff-backup/testing/finaltest.py
+++ b/rdiff-backup/testing/finaltest.py
@@ -42,8 +42,9 @@ class PathSetter(unittest.TestCase):
self.reset_schema()
def reset_schema(self):
- self.rb_schema = SourceDir + \
- "/../rdiff-backup -v5 --remote-schema './chdir-wrapper2 %s' "
+ self.rb_schema = (SourceDir +
+ "/../rdiff-backup -v3 --no-compare-inode "
+ "--remote-schema './chdir-wrapper2 %s' ")
def refresh(self, *rp_list):
"""Reread data for the given rps"""
@@ -215,6 +216,12 @@ class Final(PathSetter):
self.set_connections(None, None, "test2/tmp/", "../../")
self.exec_rb(None, '../../../../../../proc', 'testfiles/procoutput')
+ def testProcRemote2(self):
+ """Test mirroring proc, this time when proc is remote, dest local"""
+ Myrm("testfiles/procoutput")
+ self.set_connections("test1/", "../", None, None)
+ self.exec_rb(None, '../../../../../../proc', 'testfiles/procoutput')
+
def testWindowsMode(self):
"""Test backup with the --windows-mode option"""
old_schema = self.rb_schema
diff --git a/rdiff-backup/testing/hardlinktest.py b/rdiff-backup/testing/hardlinktest.py
index a675e8a..22dc31d 100644
--- a/rdiff-backup/testing/hardlinktest.py
+++ b/rdiff-backup/testing/hardlinktest.py
@@ -2,7 +2,7 @@ import os, unittest, time
from commontest import *
from rdiff_backup import Globals, Hardlink, selection, rpath
-Log.setverbosity(7)
+Log.setverbosity(3)
class HardlinkTest(unittest.TestCase):
"""Test cases for Hard links"""
@@ -142,10 +142,27 @@ class HardlinkTest(unittest.TestCase):
hl2_4.hardlink(hl2_1.path)
rpath.copy_attribs(hlout1_sub, hlout2_sub)
+ # Now try backing up twice, making sure hard links are preserved
InternalBackup(1, 1, hlout1.path, output.path)
+ out_subdir = output.append("subdir")
+ assert out_subdir.append("hardlink1").getinode() == \
+ out_subdir.append("hardlink2").getinode()
+ assert out_subdir.append("hardlink3").getinode() == \
+ out_subdir.append("hardlink4").getinode()
+ assert out_subdir.append("hardlink1").getinode() != \
+ out_subdir.append("hardlink3").getinode()
+
time.sleep(1)
InternalBackup(1, 1, hlout2.path, output.path)
-
+ out_subdir.setdata()
+ assert out_subdir.append("hardlink1").getinode() == \
+ out_subdir.append("hardlink4").getinode()
+ assert out_subdir.append("hardlink2").getinode() == \
+ out_subdir.append("hardlink3").getinode()
+ assert out_subdir.append("hardlink1").getinode() != \
+ out_subdir.append("hardlink2").getinode()
+
+ # Now try restoring, still checking hard links.
out2 = rpath.RPath(Globals.local_connection, "testfiles/out2")
hlout1 = out2.append("hardlink1")
hlout2 = out2.append("hardlink2")
@@ -165,7 +182,8 @@ class HardlinkTest(unittest.TestCase):
int(time.time()))
out2.setdata()
for rp in [hlout1, hlout2, hlout3, hlout4]: rp.setdata()
- assert hlout1.getinode() == hlout4.getinode()
+ assert hlout1.getinode() == hlout4.getinode(), \
+ "%s %s" % (hlout1.path, hlout4.path)
assert hlout2.getinode() == hlout3.getinode()
assert hlout1.getinode() != hlout2.getinode()
diff --git a/rdiff-backup/testing/regressiontest.py b/rdiff-backup/testing/regressiontest.py
index 5c55986..e0eeeaf 100644
--- a/rdiff-backup/testing/regressiontest.py
+++ b/rdiff-backup/testing/regressiontest.py
@@ -245,21 +245,15 @@ class MirrorTest(PathSetter):
def testPermSkipLocal(self):
"""Test to see if rdiff-backup will skip unreadable files"""
self.setPathnames(None, None, None, None)
- Globals.change_source_perms = None
Time.setcurtime()
self.Mirror(self.one_unreadable, self.one_unreadable_out)
- Globals.change_source_perms = 1
- self.Mirror(self.one_unreadable, self.one_unreadable_out)
# Could add test, but for now just make sure it doesn't exit
def testPermSkipRemote(self):
"""Test skip of unreadable files remote"""
self.setPathnames('test1', '../', 'test2/tmp', '../../')
- Globals.change_source_perms = None
Time.setcurtime()
self.Mirror(self.one_unreadable, self.one_unreadable_out)
- Globals.change_source_perms = 1
- self.Mirror(self.one_unreadable, self.one_unreadable_out)
# Could add test, but for now just make sure it doesn't exit
def refresh(self, *rps):
@@ -294,8 +288,6 @@ class MirrorTest(PathSetter):
def deleteoutput(self):
assert not os.system("rm -rf testfiles/output*")
self.rbdir = self.rpout.append('rdiff-backup-data')
- self.rpout.mkdir()
- self.rbdir.mkdir()
self.reset_rps()
def reset_rps(self):
@@ -309,8 +301,9 @@ class MirrorTest(PathSetter):
rp.setdata()
def runtest(self):
+ self.deleteoutput()
Time.setcurtime()
- assert self.rbdir.lstat()
+ assert not self.rbdir.lstat()
self.Mirror(self.inc1rp, self.rpout)
assert CompareRecursive(Local.inc1rp, Local.rpout)
@@ -328,6 +321,7 @@ class MirrorTest(PathSetter):
self.Mirror(self.inc1rp, self.rpout)
#rpath.RPath.copy_attribs(self.inc1rp, self.rpout)
assert CompareRecursive(Local.inc1rp, Local.rpout)
+ Myrm(Local.rpout.append("rdiff-backup-data").path)
self.Mirror(self.inc2rp, self.rpout)
assert CompareRecursive(Local.inc2rp, Local.rpout)
@@ -335,11 +329,13 @@ class MirrorTest(PathSetter):
def Mirror(self, rpin, rpout):
"""Like backup.Mirror, but setup first, cleanup later"""
Main.force = 1
+ assert not rpout.append("rdiff-backup-data").lstat()
Main.misc_setup([rpin, rpout])
Main.backup_set_select(rpin)
Main.backup_init_dirs(rpin, rpout)
backup.Mirror(rpin, rpout)
- Log.close_logfile()
+ log.ErrorLog.close()
+ log.Log.close_logfile()
Hardlink.clear_dictionaries()
if __name__ == "__main__": unittest.main()
diff --git a/rdiff-backup/testing/robusttest.py b/rdiff-backup/testing/robusttest.py
index 6b9e356..17b4ac2 100644
--- a/rdiff-backup/testing/robusttest.py
+++ b/rdiff-backup/testing/robusttest.py
@@ -4,42 +4,6 @@ from commontest import *
from rdiff_backup import rpath, robust, TempFile, Globals
-class TempFileTest(unittest.TestCase):
- """Test creation and management of tempfiles in TempFile module"""
- rp_base = rpath.RPath(Globals.local_connection,
- "./testfiles/robust/testfile_base")
- def testBasic(self):
- """Make a temp file, write to it, and then delete it
-
- Also test tempfile accounting and file name prefixing.
-
- """
- assert not TempFile._tempfiles
- tf = TempFile.new(self.rp_base)
- assert TempFile._tempfiles == [tf]
- assert tf.dirsplit()[0] == "testfiles/robust", tf.dirsplit()[0]
- assert not tf.lstat()
- fp = tf.open("w")
- fp.write("hello")
- assert not fp.close()
- fp = tf.open("r")
- assert fp.read() == "hello"
- assert not fp.close()
- tf.delete()
- assert not TempFile._tempfiles
-
- def testRename(self):
- """Test renaming of tempfile"""
- tf = TempFile.new(self.rp_base)
- assert TempFile._tempfiles
- tf.touch()
- destination = rpath.RPath(Globals.local_connection,
- "./testfiles/robust/testfile_dest")
- tf.rename(destination)
- assert not TempFile._tempfiles
- assert destination.lstat()
- destination.delete()
-
class RobustTest(unittest.TestCase):
"""Test robust module"""
def test_check_common_error(self):
diff --git a/rdiff-backup/testing/roottest.py b/rdiff-backup/testing/roottest.py
index 81292b2..6193893 100644
--- a/rdiff-backup/testing/roottest.py
+++ b/rdiff-backup/testing/roottest.py
@@ -10,7 +10,7 @@ that are meant to be run as root.
Globals.set('change_source_perms', None)
Globals.counter = 0
-log.Log.setverbosity(4)
+log.Log.setverbosity(6)
def Run(cmd):
print "Running: ", cmd
@@ -33,9 +33,9 @@ class NonRoot(unittest.TestCase):
"""
user = 'ben'
- def make_root_dir(self):
+ def make_root_dirs(self):
"""Make directory createable only by root"""
- rp = rpath.RPath(Globals.local_connection, "testfiles/root_out")
+ rp = rpath.RPath(Globals.local_connection, "testfiles/root_out1")
if rp.lstat(): Myrm(rp.path)
rp.mkdir()
rp1 = rp.append("1")
@@ -48,39 +48,59 @@ class NonRoot(unittest.TestCase):
rp3.chown(2, 2)
rp4 = rp.append("dev")
rp4.makedev('c', 4, 28)
- return rp
+
+ sp = rpath.RPath(Globals.local_connection, "testfiles/root_out2")
+ if sp.lstat(): Myrm(sp.path)
+ Run("cp -a %s %s" % (rp.path, sp.path))
+ rp2 = sp.append("2")
+ rp2.chown(2, 2)
+ rp3 = sp.append("3")
+ rp3.chown(1, 1)
+ assert not CompareRecursive(rp, sp, compare_ownership = 1)
+
+ return rp, sp
+
+ def backup(self, input_rp, output_rp, time):
+ backup_cmd = ("rdiff-backup --no-compare-inode "
+ "--current-time %s %s %s" %
+ (time, input_rp.path, output_rp.path))
+ Run("su %s -c '%s'" % (self.user, backup_cmd))
+
+ def restore(self, dest_rp, restore_rp, time = None):
+ assert restore_rp.path == "testfiles/rest_out"
+ Myrm(restore_rp.path)
+ if time is None: time = "now"
+ restore_cmd = "rdiff-backup -r %s %s %s" % (time, dest_rp.path,
+ restore_rp.path,)
+ Run(restore_cmd)
def test_non_root(self):
"""Main non-root -> root test"""
Myrm("testfiles/output")
- input_rp = self.make_root_dir()
+ input_rp1, input_rp2 = self.make_root_dirs()
Globals.change_ownership = 1
output_rp = rpath.RPath(Globals.local_connection, "testfiles/output")
restore_rp = rpath.RPath(Globals.local_connection,
"testfiles/rest_out")
empty_rp = rpath.RPath(Globals.local_connection, "testfiles/empty")
- backup_cmd = "rdiff-backup %s %s" % (input_rp.path, output_rp.path)
- Run("su %s -c '%s'" % (self.user, backup_cmd))
+ self.backup(input_rp1, output_rp, 1000000)
+ self.restore(output_rp, restore_rp)
+ assert CompareRecursive(input_rp1, restore_rp, compare_ownership = 1)
- Myrm("testfiles/rest_out")
- restore_cmd = "rdiff-backup -r now %s %s" % (output_rp.path,
- restore_rp.path,)
- Run(restore_cmd)
- assert CompareRecursive(input_rp, restore_rp)
+ self.backup(input_rp2, output_rp, 2000000)
+ self.restore(output_rp, restore_rp)
+ assert CompareRecursive(input_rp2, restore_rp, compare_ownership = 1)
- backup_cmd = "rdiff-backup %s %s" % (empty_rp.path, output_rp.path)
- Run("su %s -c '%s'" % (self.user, backup_cmd))
+ self.backup(empty_rp, output_rp, 3000000)
+ self.restore(output_rp, restore_rp)
+ assert CompareRecursive(empty_rp, restore_rp, compare_ownership = 1)
+
+ self.restore(output_rp, restore_rp, 1000000)
+ assert CompareRecursive(input_rp1, restore_rp, compare_ownership = 1)
- Myrm("testfiles/rest_out")
- Run(restore_cmd)
- assert CompareRecursive(empty_rp, restore_rp)
+ self.restore(output_rp, restore_rp, 2000000)
+ assert CompareRecursive(input_rp2, restore_rp, compare_ownership = 1)
- Myrm("testfiles/rest_out")
- restore_cmd = "rdiff-backup -r 1 %s %s" % (output_rp.path,
- restore_rp.path,)
- Run(restore_cmd)
- assert CompareRecursive(input_rp, restore_rp)
-
if __name__ == "__main__": unittest.main()