summaryrefslogtreecommitdiff
path: root/rdiff-backup/rdiff_backup
diff options
context:
space:
mode:
Diffstat (limited to 'rdiff-backup/rdiff_backup')
-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
15 files changed, 210 insertions, 119 deletions
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