diff options
Diffstat (limited to 'rdiff-backup')
-rw-r--r-- | rdiff-backup/rdiff_backup/log.py | 64 | ||||
-rw-r--r-- | rdiff-backup/rdiff_backup/selection.py | 70 |
2 files changed, 106 insertions, 28 deletions
diff --git a/rdiff-backup/rdiff_backup/log.py b/rdiff-backup/rdiff_backup/log.py index 0f9c4f3..956e578 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 +import Globals, static class LoggerError(Exception): pass @@ -183,3 +183,65 @@ class Logger: Log = Logger() + +class ErrorLog: + """Log each recoverable error in error_log file + + There are three types of recoverable errors: ListError, which + happens trying to list a directory or stat a file, UpdateError, + which happen when trying to update a changed file, and + SpecialFileError, which happen when a special file cannot be + created. See the error policy file for more info. + + """ + log_fileobj = None + log_inc_rp = None + def open(cls, compress = 1): + """Open the error log, prepare for writing""" + assert not cls.log_fileobj and not cls.log_inc_rp, "log already open" + 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_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 + + def write(cls, error_type, rp, exc): + """Add line to log file indicating error exc with file rp""" + s = cls.get_log_string(error_type, rp, exc) + Log(s, 2) + if Globals.null_separator: s += "\0" + else: + s = re.sub("\n", " ", s) + s += "\n" + cls.log_fileobj.write(s) + + def get_indexpath(cls, rp): + """Return filename for logging. rp is a rpath, string, or tuple""" + try: return rp.get_indexpath() + except AttributeError: + if type(rp) is types.TupleTypes: return "/".join(rp) + else: return str(rp) + + def write_if_open(cls, error_type, rp, exc): + """Call cls.write(...) if error log open, only log otherwise""" + if cls.isopen(): cls.write(error_type, rp, exc) + else: Log(cls.get_log_string(error_type, rp, exc), 2) + + def get_log_string(cls, error_type, rp, exc): + """Return log string to put in error log""" + assert (error_type == "ListError" or error_type == "UpdateError" or + error_type == "SpecialFileError"), "Unknown type "+error_type + return "%s %s %s" % (error_type, cls.get_indexpath(rp), str(exc)) + + def close(cls): + """Close the error log file""" + assert not cls.log_fileobj.close() + cls.log_fileobj = cls.log_inc_rp = None + +static.MakeClass(ErrorLog) + diff --git a/rdiff-backup/rdiff_backup/selection.py b/rdiff-backup/rdiff_backup/selection.py index 45d4c8c..6c4cabe 100644 --- a/rdiff-backup/rdiff_backup/selection.py +++ b/rdiff-backup/rdiff_backup/selection.py @@ -26,8 +26,7 @@ documentation on what this code does can be found on the man page. from __future__ import generators import re -from log import Log -import FilenameMapping, robust, rpath, Globals +import FilenameMapping, robust, rpath, Globals, log class SelectError(Exception): @@ -87,7 +86,7 @@ class Select: self.rpath = rootrp self.prefix = self.rpath.path - def set_iter(self, iterate_parents = None, sel_func = None): + def set_iter(self, sel_func = None): """Initialize more variables, get ready to iterate Selection function sel_func is called on each rpath and is @@ -96,17 +95,24 @@ class Select: """ if not sel_func: sel_func = self.Select self.rpath.setdata() # this may have changed since Select init - self.iter = self.Iterate_fast(self.rpath, sel_func) - - # only iterate parents if we are not starting from beginning + self.iter = self.filter_readable(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): - Log("Error initializing file %s/%s" % (rpath.path, filename), 2) + log.ErrorLog.write_if_open("ListError", + rpath.index + (filename,), exc) return None def diryield(rpath): @@ -117,7 +123,7 @@ class Select: and should be included iff something inside is included. """ - for filename in robust.listrp(rpath): + for filename in self.listdir(rpath): new_rpath = robust.check_common_error(error_handler, rpath.append, (filename,)) if new_rpath: @@ -174,13 +180,23 @@ class Select: for rp in iid: yield rp else: assert 0, "Invalid selection result %s" % (str(s),) + def listdir(self, dir_rp): + """List directory rpath with error logging""" + def error_handler(exc): + log.ErrorLog.write_if_open("ListError", dir_rp, exc) + return [] + dir_listing = robust.check_common_error(error_handler, dir_rp.listdir) + dir_listing.sort() + return dir_listing + def iterate_in_dir(self, rpath, rec_func, sel_func): """Iterate the rpaths in directory rpath.""" def error_handler(exc, filename): - Log("Error initializing file %s/%s" % (rpath.path, filename), 2) + log.ErrorLog.write_if_open("ListError", + rpath.index + (filename,), exc) return None - for filename in robust.listrp(rpath): + for filename in self.listdir(rpath): new_rp = robust.check_common_error( error_handler, rpath.append, [filename]) if new_rp: @@ -251,7 +267,7 @@ class Select: def parse_catch_error(self, exc): """Deal with selection error exc""" if isinstance(exc, FilePrefixError): - Log.FatalError( + log.Log.FatalError( """Fatal Error: The file specification ' %s' cannot match any files in the base directory @@ -260,8 +276,8 @@ Useful file specifications begin with the base directory or some pattern (such as '**') which matches the base directory.""" % (exc, self.prefix)) elif isinstance(e, GlobbingError): - Log.FatalError("Fatal Error while processing expression\n" - "%s" % exc) + log.Log.FatalError("Fatal Error while processing expression\n" + "%s" % exc) else: raise def parse_rbdir_exclude(self): @@ -273,7 +289,7 @@ pattern (such as '**') which matches the base directory.""" % """Exit with error if last selection function isn't an exclude""" if (self.selection_functions and not self.selection_functions[-1].exclude): - Log.FatalError( + log.Log.FatalError( """Last selection expression: %s only specifies that files be included. Because the default is to @@ -296,10 +312,10 @@ probably isn't what you meant.""" % filelist_name is just a string used for logging. """ - Log("Reading filelist %s" % filelist_name, 4) + log.Log("Reading filelist %s" % filelist_name, 4) tuple_list, something_excluded = \ self.filelist_read(filelist_fp, inc_default, filelist_name) - Log("Sorting filelist %s" % filelist_name, 4) + log.Log("Sorting filelist %s" % filelist_name, 4) tuple_list.sort() i = [0] # We have to put index in list because of stupid scoping rules @@ -324,11 +340,11 @@ probably isn't what you meant.""" % """Warn if prefix is incorrect""" prefix_warnings[0] += 1 if prefix_warnings[0] < 6: - Log("Warning: file specification '%s' in filelist %s\n" - "doesn't start with correct prefix %s. Ignoring." % - (exc, filelist_name, self.prefix), 2) + log.Log("Warning: file specification '%s' in filelist %s\n" + "doesn't start with correct prefix %s. Ignoring." % + (exc, filelist_name, self.prefix), 2) if prefix_warnings[0] == 5: - Log("Future prefix errors will not be logged.", 2) + log.Log("Future prefix errors will not be logged.", 2) something_excluded, tuple_list = None, [] separator = Globals.null_separator and "\0" or "\n" @@ -341,7 +357,7 @@ probably isn't what you meant.""" % tuple_list.append(tuple) if not tuple[1]: something_excluded = 1 if filelist_fp.close(): - Log("Error closing filelist %s" % filelist_name, 2) + log.Log("Error closing filelist %s" % filelist_name, 2) return (tuple_list, something_excluded) def filelist_parse_line(self, line, include): @@ -399,7 +415,7 @@ probably isn't what you meant.""" % See the man page on --[include/exclude]-globbing-filelist """ - Log("Reading globbing filelist %s" % list_name, 4) + log.Log("Reading globbing filelist %s" % list_name, 4) separator = Globals.null_separator and "\0" or "\n" for line in filelist_fp.read().split(separator): if not line: continue # skip blanks @@ -423,7 +439,7 @@ probably isn't what you meant.""" % assert include == 0 or include == 1 try: regexp = re.compile(regexp_string) except: - Log("Error compiling regular expression %s" % regexp_string, 1) + log.Log("Error compiling regular expression %s" % regexp_string, 1) raise def sel_func(rp): @@ -437,8 +453,8 @@ probably isn't what you meant.""" % def devfiles_get_sf(self, include): """Return a selection function matching all dev files""" if self.selection_functions: - Log("Warning: exclude-device-files is not the first " - "selector.\nThis may not be what you intended", 3) + log.Log("Warning: exclude-device-files is not the first " + "selector.\nThis may not be what you intended", 3) def sel_func(rp): if rp.isdev(): return include else: return None @@ -449,8 +465,8 @@ probably isn't what you meant.""" % def special_get_sf(self, include): """Return sel function matching sockets, symlinks, sockets, devs""" if self.selection_functions: - Log("Warning: exclude-special-files is not the first " - "selector.\nThis may not be what you intended", 3) + log.Log("Warning: exclude-special-files is not the first " + "selector.\nThis may not be what you intended", 3) def sel_func(rp): if rp.issym() or rp.issock() or rp.isfifo() or rp.isdev(): return include |