summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbescoto <bescoto@2b77aa54-bcbc-44c9-a7ec-4f6cf2b41109>2003-08-25 06:58:33 +0000
committerbescoto <bescoto@2b77aa54-bcbc-44c9-a7ec-4f6cf2b41109>2003-08-25 06:58:33 +0000
commit886dba8a2915252a008c4218627297d54eb1221a (patch)
tree5a9fa9e61a48985fa2c7c949d9dfbb2490f1cefb
parent6a48fc6c34e3d2f8fcaf791a847aebb6854557e7 (diff)
downloadrdiff-backup-886dba8a2915252a008c4218627297d54eb1221a.tar.gz
ACL/EA parsing fixes, use new quoting style
git-svn-id: http://svn.savannah.nongnu.org/svn/rdiff-backup/trunk@403 2b77aa54-bcbc-44c9-a7ec-4f6cf2b41109
-rw-r--r--rdiff-backup/CHANGELOG4
-rw-r--r--rdiff-backup/rdiff_backup/cmodule.c129
-rw-r--r--rdiff-backup/rdiff_backup/eas_acls.py59
-rw-r--r--rdiff-backup/rdiff_backup/metadata.py74
-rw-r--r--rdiff-backup/rdiff_backup/rpath.py21
-rw-r--r--rdiff-backup/testing/ctest.py8
-rw-r--r--rdiff-backup/testing/eas_aclstest.py122
7 files changed, 340 insertions, 77 deletions
diff --git a/rdiff-backup/CHANGELOG b/rdiff-backup/CHANGELOG
index 02c093c..11d316e 100644
--- a/rdiff-backup/CHANGELOG
+++ b/rdiff-backup/CHANGELOG
@@ -12,6 +12,10 @@ If there is data missing from the destination dir (for instance if a
user mistakenly deletes it), only warn when restoring, instead of
exiting with error.
+Fixed bug in EA/ACL restoring, noticed by Greg Freemyer. Also updated
+quoting of filenames and extended attributes names to match
+forthcoming attr/facl utilities.
+
New in v0.13.1 (2003/08/08)
---------------------------
diff --git a/rdiff-backup/rdiff_backup/cmodule.c b/rdiff-backup/rdiff_backup/cmodule.c
index 30aac47..a5f1c65 100644
--- a/rdiff-backup/rdiff_backup/cmodule.c
+++ b/rdiff-backup/rdiff_backup/cmodule.c
@@ -27,6 +27,7 @@
#include <unistd.h>
#include <errno.h>
+
/* Some of the following code to define major/minor taken from code by
* Jörg Schilling's star archiver.
*/
@@ -202,7 +203,6 @@ static PyObject *c_make_file_dict(self, args)
return return_val;
}
-
/* Convert python long into 7 byte string */
static PyObject *long2str(self, args)
PyObject *self;
@@ -247,12 +247,138 @@ static PyObject *str2long(self, args)
}
+/* --------------------------------------------------------------------- *
+ * This section is still GPL'd, but was copied from the libmisc
+ * section of getfacl by Andreas Gruenbacher
+ * <a.gruenbacher@computer.org>. I'm just copying the code to
+ * preserve quoting compatibility between (get|set)f(acl|attr) and
+ * rdiff-backup. Taken on 8/24/2003.
+ * --------------------------------------------------------------------- */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+
+int high_water_alloc(void **buf, size_t *bufsize, size_t newsize)
+{
+#define CHUNK_SIZE 256
+ /*
+ * Goal here is to avoid unnecessary memory allocations by
+ * using static buffers which only grow when necessary.
+ * Size is increased in fixed size chunks (CHUNK_SIZE).
+ */
+ if (*bufsize < newsize) {
+ void *newbuf;
+
+ newsize = (newsize + CHUNK_SIZE-1) & ~(CHUNK_SIZE-1);
+ newbuf = realloc(*buf, newsize);
+ if (!newbuf)
+ return 1;
+
+ *buf = newbuf;
+ *bufsize = newsize;
+ }
+ return 0;
+}
+
+const char *quote(const char *str)
+{
+ static char *quoted_str;
+ static size_t quoted_str_len;
+ const unsigned char *s;
+ char *q;
+ size_t nonpr;
+
+ if (!str)
+ return str;
+
+ for (nonpr = 0, s = (unsigned char *)str; *s != '\0'; s++)
+ if (!isprint(*s) || isspace(*s) || *s == '\\' || *s == '=')
+ nonpr++;
+ if (nonpr == 0)
+ return str;
+
+ if (high_water_alloc((void **)&quoted_str, &quoted_str_len,
+ nonpr * 3 + 1))
+ return NULL;
+ for (s = (unsigned char *)str, q = quoted_str; *s != '\0'; s++) {
+ if (!isprint(*s) || isspace(*s) || *s == '\\' || *s == '=') {
+ *q++ = '\\';
+ *q++ = '0' + ((*s >> 6) );
+ *q++ = '0' + ((*s >> 3) & 7);
+ *q++ = '0' + ((*s ) & 7);
+ } else
+ *q++ = *s;
+ }
+ *q++ = '\0';
+
+ return quoted_str;
+}
+
+char *unquote(char *str)
+{
+ unsigned char *s, *t;
+
+ if (!str)
+ return str;
+
+ for (s = (unsigned char *)str; *s != '\0'; s++)
+ if (*s == '\\')
+ break;
+ if (*s == '\0')
+ return str;
+
+#define isoctal(c) \
+ ((c) >= '0' && (c) <= '7')
+
+ t = s;
+ do {
+ if (*s == '\\' &&
+ isoctal(*(s+1)) && isoctal(*(s+2)) && isoctal(*(s+3))) {
+ *t++ = ((*(s+1) - '0') << 6) +
+ ((*(s+2) - '0') << 3) +
+ ((*(s+3) - '0') );
+ s += 3;
+ } else
+ *t++ = *s;
+ } while (*s++ != '\0');
+
+ return str;
+}
+
+/* ------------- End Gruenbach section --------------------------------- */
+
+/* Translate quote above into python */
+static PyObject *acl_quote(PyObject *self, PyObject *args)
+{
+ char *s;
+
+ if (!PyArg_ParseTuple(args, "s", &s)) return NULL;
+ return Py_BuildValue("s", quote(s));
+}
+
+/* Translate unquote above into python */
+static PyObject *acl_unquote(PyObject *self, PyObject *args)
+{
+ char *s;
+
+ if (!PyArg_ParseTuple(args, "s", &s)) return NULL;
+ return Py_BuildValue("s", unquote(s));
+}
+
+
+/* ------------- Python export lists -------------------------------- */
+
static PyMethodDef CMethods[] = {
{"make_file_dict", c_make_file_dict, METH_VARARGS,
"Make dictionary from file stat"},
{"long2str", long2str, METH_VARARGS, "Convert python long to 7 byte string"},
{"str2long", str2long, METH_VARARGS, "Convert 7 byte string to python long"},
{"sync", my_sync, METH_VARARGS, "sync buffers to disk"},
+ {"acl_quote", acl_quote, METH_VARARGS,
+ "Quote string, escaping non-printables"},
+ {"acl_unquote", acl_unquote, METH_VARARGS,
+ "Unquote string, producing original input to quote"},
{NULL, NULL, 0, NULL}
};
@@ -266,4 +392,3 @@ void initC(void)
NULL, NULL);
PyDict_SetItemString(d, "UnknownFileTypeError", UnknownFileTypeError);
}
-
diff --git a/rdiff-backup/rdiff_backup/eas_acls.py b/rdiff-backup/rdiff_backup/eas_acls.py
index 4b4d169..e543597 100644
--- a/rdiff-backup/rdiff_backup/eas_acls.py
+++ b/rdiff-backup/rdiff_backup/eas_acls.py
@@ -30,8 +30,7 @@ from __future__ import generators
import base64, errno, re
try: import posix1e
except ImportError: pass
-import static, Globals, metadata, connection, rorpiter, log
-
+import static, Globals, metadata, connection, rorpiter, log, C, rpath
class ExtendedAttributes:
"""Hold a file's extended attribute information"""
@@ -104,12 +103,12 @@ def ea_compare_rps(rp1, rp2):
def EA2Record(ea):
"""Convert ExtendedAttributes object to text record"""
- str_list = ['# file: %s' % ea.get_indexpath()]
+ str_list = ['# file: %s' % C.acl_quote(ea.get_indexpath())]
for (name, val) in ea.attr_dict.iteritems():
if not val: str_list.append(name)
else:
encoded_val = base64.encodestring(val).replace('\n', '')
- str_list.append('%s=0s%s' % (name, encoded_val))
+ str_list.append('%s=0s%s' % (C.acl_quote(name), encoded_val))
return '\n'.join(str_list)+'\n'
def Record2EA(record):
@@ -120,7 +119,7 @@ def Record2EA(record):
raise metadata.ParsingError("Bad record beginning: " + first[:8])
filename = first[8:]
if filename == '.': index = ()
- else: index = tuple(filename.split('/'))
+ else: index = tuple(C.acl_unquote(filename).split('/'))
ea = ExtendedAttributes(index)
for line in lines:
@@ -137,27 +136,15 @@ def Record2EA(record):
ea.set(name, base64.decodestring(encoded_val))
return ea
-def quote_path(path):
- """Quote a path for use EA/ACL records.
-
- Right now no quoting!!! Change this to reflect the updated
- quoting style of getfattr/setfattr when they are changed.
-
- """
- return path
-
class EAExtractor(metadata.FlatExtractor):
"""Iterate ExtendedAttributes objects from the EA information file"""
- record_boundary_regexp = re.compile("\\n# file:")
+ record_boundary_regexp = re.compile('(?:\\n|^)(# file: (.*?))\\n')
record_to_object = staticmethod(Record2EA)
- def get_index_re(self, index):
- """Find start of EA record with given index"""
- if not index: indexpath = '.'
- else: indexpath = '/'.join(index)
- # Right now there is no quoting, due to a bug in
- # getfacl/setfacl. Replace later when bug fixed.
- return re.compile('(^|\\n)(# file: %s\\n)' % indexpath)
+ def filename_to_index(self, filename):
+ """Convert possibly quoted filename to index tuple"""
+ if filename == '.': return ()
+ else: return tuple(C.acl_unquote(filename).split('/'))
class ExtendedAttributesFile(metadata.FlatFile):
"""Store/retrieve EAs from extended_attributes file"""
@@ -171,7 +158,7 @@ class ExtendedAttributesFile(metadata.FlatFile):
"""Add EA information in ea_iter to rorp_iter"""
collated = rorpiter.CollateIterators(rorp_iter, ea_iter)
for rorp, ea in collated:
- assert rorp, (rorp, (ea.index, ea.attr_dict), rest_time)
+ assert rorp, (rorp, (ea.index, ea.attr_dict), time)
if not ea: ea = ExtendedAttributes(rorp.index)
rorp.set_ea(ea)
yield rorp
@@ -311,7 +298,7 @@ def acl_compare_rps(rp1, rp2):
def ACL2Record(acl):
"""Convert an AccessControlList object into a text record"""
- start = "# file: %s\n%s" % (acl.get_indexpath(), acl.acl_text)
+ start = "# file: %s\n%s" % (C.acl_quote(acl.get_indexpath()), acl.acl_text)
if not acl.def_acl_text: return start
default_lines = acl.def_acl_text.strip().split('\n')
default_text = '\ndefault:'.join(default_lines)
@@ -325,7 +312,7 @@ def Record2ACL(record):
raise metadata.ParsingError("Bad record beginning: "+ first_line)
filename = first_line[8:]
if filename == '.': index = ()
- else: index = tuple(filename.split('/'))
+ else: index = tuple(C.acl_unquote(filename).split('/'))
normal_entries = []; default_entries = []
for line in lines:
@@ -393,3 +380,25 @@ def GetCombinedMetadataIter(rbdir, time, restrict_index = None,
metadata_iter, rbdir, time, restrict_index)
return metadata_iter
+
+def rpath_acl_get(rp):
+ """Get acls of given rpath rp.
+
+ This overrides a function in the rpath module.
+
+ """
+ acl = AccessControlList(rp.index)
+ if not rp.issym(): acl.read_from_rp(rp)
+ return acl
+rpath.acl_get = rpath_acl_get
+
+def rpath_ea_get(rp):
+ """Get extended attributes of given rpath
+
+ This overrides a function in the rpath module.
+
+ """
+ ea = ExtendedAttributes(rp.index)
+ if not rp.issym(): ea.read_from_rp(rp)
+ return ea
+rpath.ea_get = rpath_ea_get
diff --git a/rdiff-backup/rdiff_backup/metadata.py b/rdiff-backup/rdiff_backup/metadata.py
index eec8e3e..2388cfa 100644
--- a/rdiff-backup/rdiff_backup/metadata.py
+++ b/rdiff-backup/rdiff_backup/metadata.py
@@ -122,9 +122,7 @@ def Record2RORP(record_string):
"""
data_dict = {}
for field, data in line_parsing_regexp.findall(record_string):
- if field == "File":
- if data == ".": index = ()
- else: index = tuple(unquote_path(data).split("/"))
+ if field == "File": index = quoted_filename_to_index(data)
elif field == "Type":
if data == "None": data_dict['type'] = None
else: data_dict['type'] = data
@@ -174,12 +172,23 @@ def unquote_path(quoted_string):
return two_chars
return re.sub("\\\\n|\\\\\\\\", replacement_func, quoted_string)
+def quoted_filename_to_index(quoted_filename):
+ """Return tuple index given quoted filename"""
+ if quoted_filename == '.': return ()
+ else: return tuple(unquote_path(quoted_filename).split('/'))
class FlatExtractor:
"""Controls iterating objects from flat file"""
- # The following two should be set in subclasses
- record_boundary_regexp = None # Matches beginning of next record
- record_to_object = None # Function that converts text record to object
+
+ # Set this in subclass. record_boundary_regexp should match
+ # beginning of next record. The first group should start at the
+ # beginning of the record. The second group should contain the
+ # (possibly quoted) filename.
+ record_boundary_regexp = None
+
+ # Set in subclass to function that converts text record to object
+ record_to_object = None
+
def __init__(self, fileobj):
self.fileobj = fileobj # holds file object we are reading from
self.buf = "" # holds the next part of the file
@@ -187,10 +196,10 @@ class FlatExtractor:
self.blocksize = 32 * 1024
def get_next_pos(self):
- """Return position of next record in buffer"""
+ """Return position of next record in buffer, or end pos if none"""
while 1:
- m = self.record_boundary_regexp.search(self.buf)
- if m: return m.start(0)+1 # the +1 skips the newline
+ m = self.record_boundary_regexp.search(self.buf, 1)
+ if m: return m.start(1)
else: # add next block to the buffer, loop again
newbuf = self.fileobj.read(self.blocksize)
if not newbuf:
@@ -218,27 +227,20 @@ class FlatExtractor:
"""
assert not self.buf or self.buf.endswith("\n")
- begin_re = self.get_index_re(index)
while 1:
- m = begin_re.search(self.buf)
- if m:
- self.buf = self.buf[m.start(2):]
- return
self.buf = self.fileobj.read(self.blocksize)
self.buf += self.fileobj.readline()
if not self.buf:
self.at_end = 1
return
-
- def get_index_re(self, index):
- """Return regular expression used to find index.
-
- Override this in sub classes. The regular expression's second
- group needs to start at the beginning of the record that
- contains information about the object with the given index.
-
- """
- assert 0, "Just a placeholder, must override this in subclasses"
+ while 1:
+ m = self.record_boundary_regexp.search(self.buf)
+ if not m: break
+ cur_index = self.filename_to_index(m.group(2))
+ if cur_index >= index:
+ self.buf = self.buf[m.start(1):]
+ return
+ else: self.buf = self.buf[m.end(1):]
def iterate_starting_with(self, index):
"""Iterate objects whose index starts with given index"""
@@ -256,24 +258,24 @@ class FlatExtractor:
self.buf = self.buf[next_pos:]
assert not self.close()
+ def filename_to_index(self, filename):
+ """Translate filename, possibly quoted, into an index tuple
+
+ The filename is the first group matched by
+ regexp_boundary_regexp.
+
+ """
+ assert 0 # subclass
+
def close(self):
"""Return value of closing associated file"""
return self.fileobj.close()
class RorpExtractor(FlatExtractor):
"""Iterate rorps from metadata file"""
- record_boundary_regexp = re.compile("\\nFile")
+ record_boundary_regexp = re.compile("(?:\\n|^)(File (.*?))\\n")
record_to_object = staticmethod(Record2RORP)
- def get_index_re(self, index):
- """Find start of rorp record with given index"""
- indexpath = index and '/'.join(index) or '.'
- # Must double all backslashes, because they will be
- # reinterpreted. For instance, to search for index \n
- # (newline), it will be \\n (backslash n) in the file, so the
- # regular expression is "File \\\\n\\n" (File two backslash n
- # backslash n)
- double_quote = re.sub("\\\\", "\\\\\\\\", indexpath)
- return re.compile("(^|\\n)(File %s\\n)" % (double_quote,))
+ filename_to_index = staticmethod(quoted_filename_to_index)
class FlatFile:
@@ -339,7 +341,7 @@ class FlatFile:
else: compressed = cls._rp.get_indexpath().endswith('.gz')
fileobj = cls._rp.open('rb', compress = compressed)
- if restrict_index is None: return cls._extractor(fileobj).iterate()
+ if not restrict_index: return cls._extractor(fileobj).iterate()
else:
re = cls._extractor(fileobj)
return re.iterate_starting_with(restrict_index)
diff --git a/rdiff-backup/rdiff_backup/rpath.py b/rdiff-backup/rdiff_backup/rpath.py
index 43f14e3..c1641a5 100644
--- a/rdiff-backup/rdiff_backup/rpath.py
+++ b/rdiff-backup/rdiff_backup/rpath.py
@@ -1001,10 +1001,7 @@ class RPath(RORPath):
def get_acl(self):
"""Return access control list object, setting if necessary"""
try: acl = self.data['acl']
- except KeyError:
- acl = eas_acls.AccessControlList(self.index)
- if not self.issym(): acl.read_from_rp(self)
- self.data['acl'] = acl
+ except KeyError: acl = self.data['acl'] = acl_get(self)
return acl
def write_acl(self, acl):
@@ -1015,14 +1012,7 @@ class RPath(RORPath):
def get_ea(self):
"""Return extended attributes object, setting if necessary"""
try: ea = self.data['ea']
- except KeyError:
- ea = eas_acls.ExtendedAttributes(self.index)
- if not self.issym():
- # Don't read from symlinks because they will be
- # followed. Update this when llistxattr,
- # etc. available
- ea.read_from_rp(self)
- self.data['ea'] = ea
+ except KeyError: ea = self.data['ea'] = ea_get(self)
return ea
def write_ea(self, ea):
@@ -1068,4 +1058,9 @@ class RPathFileHook:
self.closing_thunk()
return result
-import eas_acls # Put at end to avoid regress
+
+# These two are overwritten by the eas_acls.py module. We can't
+# import that module directory because of circular dependency
+# problems.
+def acl_get(rp): assert 0
+def ea_get(rp): assert 0
diff --git a/rdiff-backup/testing/ctest.py b/rdiff-backup/testing/ctest.py
index 16a7882..2f11b1f 100644
--- a/rdiff-backup/testing/ctest.py
+++ b/rdiff-backup/testing/ctest.py
@@ -41,5 +41,13 @@ class CTest(unittest.TestCase):
"""Test running C.sync"""
C.sync()
+ def test_acl_quoting(self):
+ """Test the acl_quote and acl_unquote functions"""
+ assert C.acl_quote('foo') == 'foo', C.acl_quote('foo')
+ assert C.acl_quote('\n') == '\\012', C.acl_quote('\n')
+ assert C.acl_unquote('\\012') == '\n'
+ s = '\\\n\t\145\n\01=='
+ assert C.acl_unquote(C.acl_quote(s)) == s
+
if __name__ == "__main__": unittest.main()
diff --git a/rdiff-backup/testing/eas_aclstest.py b/rdiff-backup/testing/eas_aclstest.py
index ed1a5b4..54d9f05 100644
--- a/rdiff-backup/testing/eas_aclstest.py
+++ b/rdiff-backup/testing/eas_aclstest.py
@@ -1,9 +1,11 @@
-import unittest, os, time
+import unittest, os, time, cStringIO
from commontest import *
from rdiff_backup.eas_acls import *
from rdiff_backup import Globals, rpath, Time
tempdir = rpath.RPath(Globals.local_connection, "testfiles/output")
+restore_dir = rpath.RPath(Globals.local_connection,
+ "testfiles/restore_out")
class EATest(unittest.TestCase):
"""Test extended attributes"""
@@ -27,6 +29,7 @@ class EATest(unittest.TestCase):
"""Make temp directory testfiles/output"""
if tempdir.lstat(): tempdir.delete()
tempdir.mkdir()
+ if restore_dir.lstat(): restore_dir.delete()
def testBasic(self):
"""Test basic writing and reading of extended attributes"""
@@ -61,6 +64,41 @@ class EATest(unittest.TestCase):
(self.sample_ea.index, new_ea.index)
assert 0, "We shouldn't have gotten this far"
+ def testExtractor(self):
+ """Test seeking inside a record list"""
+ record_list = """# file: 0foo
+user.multiline=0sVGhpcyBpcyBhIGZhaXJseSBsb25nIGV4dGVuZGVkIGF0dHJpYnV0ZS4KCQkJIEVuY29kaW5nIGl0IHdpbGwgcmVxdWlyZSBzZXZlcmFsIGxpbmVzIG9mCgkJCSBiYXNlNjQusbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGx
+user.third=0saGVsbG8=
+user.not_empty=0sZm9vYmFy
+user.binary=0sAAECjC89Ig==
+user.empty
+# file: 1foo/bar/baz
+user.multiline=0sVGhpcyBpcyBhIGZhaXJseSBsb25nIGV4dGVuZGVkIGF0dHJpYnV0ZS4KCQkJIEVuY29kaW5nIGl0IHdpbGwgcmVxdWlyZSBzZXZlcmFsIGxpbmVzIG9mCgkJCSBiYXNlNjQusbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGx
+user.third=0saGVsbG8=
+user.binary=0sAAECjC89Ig==
+user.empty
+# file: 2foo/\\012
+user.empty
+"""
+ extractor = EAExtractor(cStringIO.StringIO(record_list))
+ ea_iter = extractor.iterate_starting_with(())
+ first = ea_iter.next()
+ assert first.index == ('0foo',), first
+ second = ea_iter.next()
+ assert second.index == ('1foo', 'bar', 'baz'), second
+ third = ea_iter.next() # Test quoted filenames
+ assert third.index == ('2foo', '\n'), third.index
+ try: ea_iter.next()
+ except StopIteration: pass
+ else: assert 0, "Too many elements in iterator"
+
+ extractor = EAExtractor(cStringIO.StringIO(record_list))
+ ea_iter = extractor.iterate_starting_with(('1foo', 'bar'))
+ assert ea_iter.next().index == ('1foo', 'bar', 'baz')
+ try: ea_iter.next()
+ except StopIteration: pass
+ else: assert 0, "Too many elements in iterator"
+
def make_backup_dirs(self):
"""Create testfiles/ea_test[12] directories
@@ -137,6 +175,21 @@ class EATest(unittest.TestCase):
'testfiles/empty', 'testfiles/ea_test1']
BackupRestoreSeries(None, None, dirlist, compare_eas = 1)
+ def test_final_local(self):
+ """Test backing up and restoring using 'rdiff-backup' script"""
+ self.make_backup_dirs()
+ self.make_temp()
+ rdiff_backup(1, 1, self.ea_testdir1.path, tempdir.path,
+ current_time = 10000)
+ assert CompareRecursive(self.ea_testdir1, tempdir, compare_eas = 1)
+
+ rdiff_backup(1, 1, self.ea_testdir2.path, tempdir.path,
+ current_time = 20000)
+ assert CompareRecursive(self.ea_testdir2, tempdir, compare_eas = 1)
+
+ rdiff_backup(1, 1, tempdir.path, restore_dir.path,
+ extra_options = '-r 10000')
+ assert CompareRecursive(self.ea_testdir1, restore_dir, compare_eas = 1)
class ACLTest(unittest.TestCase):
@@ -181,6 +234,7 @@ other::---""")
"""Make temp directory testfile/output"""
if tempdir.lstat(): tempdir.delete()
tempdir.mkdir()
+ if restore_dir.lstat(): restore_dir.delete()
def testBasic(self):
"""Test basic writing and reading of ACLs"""
@@ -227,6 +281,49 @@ other::---""")
assert new_acl2.eq_verbose(self.dir_acl)
assert 0
+ def testExtractor(self):
+ """Test seeking inside a record list"""
+ record_list = """# file: 0foo
+user::r--
+user:ben:---
+group::---
+group:root:---
+mask::---
+other::---
+# file: 1foo/bar/baz
+user::r--
+user:ben:---
+group::---
+group:root:---
+mask::---
+other::---
+# file: 2foo/\\012
+user::r--
+user:ben:---
+group::---
+group:root:---
+mask::---
+other::---
+"""
+ extractor = ACLExtractor(cStringIO.StringIO(record_list))
+ acl_iter = extractor.iterate_starting_with(())
+ first = acl_iter.next()
+ assert first.index == ('0foo',), first
+ second = acl_iter.next()
+ assert second.index == ('1foo', 'bar', 'baz'), second
+ third = acl_iter.next() # Test quoted filenames
+ assert third.index == ('2foo', '\n'), third.index
+ try: acl_iter.next()
+ except StopIteration: pass
+ else: assert 0, "Too many elements in iterator"
+
+ extractor = ACLExtractor(cStringIO.StringIO(record_list))
+ acl_iter = extractor.iterate_starting_with(('1foo', 'bar'))
+ assert acl_iter.next().index == ('1foo', 'bar', 'baz')
+ try: acl_iter.next()
+ except StopIteration: pass
+ else: assert 0, "Too many elements in iterator"
+
def make_backup_dirs(self):
"""Create testfiles/acl_test[12] directories"""
if self.acl_testdir1.lstat(): self.acl_testdir1.delete()
@@ -295,6 +392,29 @@ other::---""")
'testfiles/empty', 'testfiles/acl_test1']
BackupRestoreSeries(None, None, dirlist, compare_acls = 1)
+ def test_final_local(self):
+ """Test backing up and restoring using 'rdiff-backup' script"""
+ self.make_backup_dirs()
+ self.make_temp()
+ rdiff_backup(1, 1, self.acl_testdir1.path, tempdir.path,
+ current_time = 10000)
+ assert CompareRecursive(self.acl_testdir1, tempdir, compare_acls = 1)
+
+ rdiff_backup(1, 1, self.acl_testdir2.path, tempdir.path,
+ current_time = 20000)
+ assert CompareRecursive(self.acl_testdir2, tempdir, compare_acls = 1)
+
+ rdiff_backup(1, 1, tempdir.path, restore_dir.path,
+ extra_options = '-r 10000')
+ assert CompareRecursive(self.acl_testdir1, restore_dir,
+ compare_acls = 1)
+
+ restore_dir.delete()
+ rdiff_backup(1, 1, tempdir.path, restore_dir.path,
+ extra_options = '-r now')
+ assert CompareRecursive(self.acl_testdir2, restore_dir,
+ compare_acls = 1)
+
class CombinedTest(unittest.TestCase):
"""Test backing up and restoring directories with both EAs and ACLs"""