Commit 05b43732 authored by bescoto's avatar bescoto

ACL/EA parsing fixes, use new quoting style


git-svn-id: http://svn.savannah.nongnu.org/svn/rdiff-backup@403 2b77aa54-bcbc-44c9-a7ec-4f6cf2b41109
parent 2525563c
...@@ -12,6 +12,10 @@ If there is data missing from the destination dir (for instance if a ...@@ -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 user mistakenly deletes it), only warn when restoring, instead of
exiting with error. 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) New in v0.13.1 (2003/08/08)
--------------------------- ---------------------------
......
...@@ -27,6 +27,7 @@ ...@@ -27,6 +27,7 @@
#include <unistd.h> #include <unistd.h>
#include <errno.h> #include <errno.h>
/* Some of the following code to define major/minor taken from code by /* Some of the following code to define major/minor taken from code by
* Jrg Schilling's star archiver. * Jrg Schilling's star archiver.
*/ */
...@@ -202,7 +203,6 @@ static PyObject *c_make_file_dict(self, args) ...@@ -202,7 +203,6 @@ static PyObject *c_make_file_dict(self, args)
return return_val; return return_val;
} }
/* Convert python long into 7 byte string */ /* Convert python long into 7 byte string */
static PyObject *long2str(self, args) static PyObject *long2str(self, args)
PyObject *self; PyObject *self;
...@@ -247,12 +247,138 @@ static PyObject *str2long(self, args) ...@@ -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[] = { static PyMethodDef CMethods[] = {
{"make_file_dict", c_make_file_dict, METH_VARARGS, {"make_file_dict", c_make_file_dict, METH_VARARGS,
"Make dictionary from file stat"}, "Make dictionary from file stat"},
{"long2str", long2str, METH_VARARGS, "Convert python long to 7 byte string"}, {"long2str", long2str, METH_VARARGS, "Convert python long to 7 byte string"},
{"str2long", str2long, METH_VARARGS, "Convert 7 byte string to python long"}, {"str2long", str2long, METH_VARARGS, "Convert 7 byte string to python long"},
{"sync", my_sync, METH_VARARGS, "sync buffers to disk"}, {"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} {NULL, NULL, 0, NULL}
}; };
...@@ -266,4 +392,3 @@ void initC(void) ...@@ -266,4 +392,3 @@ void initC(void)
NULL, NULL); NULL, NULL);
PyDict_SetItemString(d, "UnknownFileTypeError", UnknownFileTypeError); PyDict_SetItemString(d, "UnknownFileTypeError", UnknownFileTypeError);
} }
...@@ -30,8 +30,7 @@ from __future__ import generators ...@@ -30,8 +30,7 @@ from __future__ import generators
import base64, errno, re import base64, errno, re
try: import posix1e try: import posix1e
except ImportError: pass except ImportError: pass
import static, Globals, metadata, connection, rorpiter, log import static, Globals, metadata, connection, rorpiter, log, C, rpath
class ExtendedAttributes: class ExtendedAttributes:
"""Hold a file's extended attribute information""" """Hold a file's extended attribute information"""
...@@ -104,12 +103,12 @@ def ea_compare_rps(rp1, rp2): ...@@ -104,12 +103,12 @@ def ea_compare_rps(rp1, rp2):
def EA2Record(ea): def EA2Record(ea):
"""Convert ExtendedAttributes object to text record""" """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(): for (name, val) in ea.attr_dict.iteritems():
if not val: str_list.append(name) if not val: str_list.append(name)
else: else:
encoded_val = base64.encodestring(val).replace('\n', '') 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' return '\n'.join(str_list)+'\n'
def Record2EA(record): def Record2EA(record):
...@@ -120,7 +119,7 @@ def Record2EA(record): ...@@ -120,7 +119,7 @@ def Record2EA(record):
raise metadata.ParsingError("Bad record beginning: " + first[:8]) raise metadata.ParsingError("Bad record beginning: " + first[:8])
filename = first[8:] filename = first[8:]
if filename == '.': index = () if filename == '.': index = ()
else: index = tuple(filename.split('/')) else: index = tuple(C.acl_unquote(filename).split('/'))
ea = ExtendedAttributes(index) ea = ExtendedAttributes(index)
for line in lines: for line in lines:
...@@ -137,27 +136,15 @@ def Record2EA(record): ...@@ -137,27 +136,15 @@ def Record2EA(record):
ea.set(name, base64.decodestring(encoded_val)) ea.set(name, base64.decodestring(encoded_val))
return ea 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): class EAExtractor(metadata.FlatExtractor):
"""Iterate ExtendedAttributes objects from the EA information file""" """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) record_to_object = staticmethod(Record2EA)
def get_index_re(self, index): def filename_to_index(self, filename):
"""Find start of EA record with given index""" """Convert possibly quoted filename to index tuple"""
if not index: indexpath = '.' if filename == '.': return ()
else: indexpath = '/'.join(index) else: return tuple(C.acl_unquote(filename).split('/'))
# 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)
class ExtendedAttributesFile(metadata.FlatFile): class ExtendedAttributesFile(metadata.FlatFile):
"""Store/retrieve EAs from extended_attributes file""" """Store/retrieve EAs from extended_attributes file"""
...@@ -171,7 +158,7 @@ class ExtendedAttributesFile(metadata.FlatFile): ...@@ -171,7 +158,7 @@ class ExtendedAttributesFile(metadata.FlatFile):
"""Add EA information in ea_iter to rorp_iter""" """Add EA information in ea_iter to rorp_iter"""
collated = rorpiter.CollateIterators(rorp_iter, ea_iter) collated = rorpiter.CollateIterators(rorp_iter, ea_iter)
for rorp, ea in collated: 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) if not ea: ea = ExtendedAttributes(rorp.index)
rorp.set_ea(ea) rorp.set_ea(ea)
yield rorp yield rorp
...@@ -311,7 +298,7 @@ def acl_compare_rps(rp1, rp2): ...@@ -311,7 +298,7 @@ def acl_compare_rps(rp1, rp2):
def ACL2Record(acl): def ACL2Record(acl):
"""Convert an AccessControlList object into a text record""" """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 if not acl.def_acl_text: return start
default_lines = acl.def_acl_text.strip().split('\n') default_lines = acl.def_acl_text.strip().split('\n')
default_text = '\ndefault:'.join(default_lines) default_text = '\ndefault:'.join(default_lines)
...@@ -325,7 +312,7 @@ def Record2ACL(record): ...@@ -325,7 +312,7 @@ def Record2ACL(record):
raise metadata.ParsingError("Bad record beginning: "+ first_line) raise metadata.ParsingError("Bad record beginning: "+ first_line)
filename = first_line[8:] filename = first_line[8:]
if filename == '.': index = () if filename == '.': index = ()
else: index = tuple(filename.split('/')) else: index = tuple(C.acl_unquote(filename).split('/'))
normal_entries = []; default_entries = [] normal_entries = []; default_entries = []
for line in lines: for line in lines:
...@@ -393,3 +380,25 @@ def GetCombinedMetadataIter(rbdir, time, restrict_index = None, ...@@ -393,3 +380,25 @@ def GetCombinedMetadataIter(rbdir, time, restrict_index = None,
metadata_iter, rbdir, time, restrict_index) metadata_iter, rbdir, time, restrict_index)
return metadata_iter 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
...@@ -122,9 +122,7 @@ def Record2RORP(record_string): ...@@ -122,9 +122,7 @@ def Record2RORP(record_string):
""" """
data_dict = {} data_dict = {}
for field, data in line_parsing_regexp.findall(record_string): for field, data in line_parsing_regexp.findall(record_string):
if field == "File": if field == "File": index = quoted_filename_to_index(data)
if data == ".": index = ()
else: index = tuple(unquote_path(data).split("/"))
elif field == "Type": elif field == "Type":
if data == "None": data_dict['type'] = None if data == "None": data_dict['type'] = None
else: data_dict['type'] = data else: data_dict['type'] = data
...@@ -174,12 +172,23 @@ def unquote_path(quoted_string): ...@@ -174,12 +172,23 @@ def unquote_path(quoted_string):
return two_chars return two_chars
return re.sub("\\\\n|\\\\\\\\", replacement_func, quoted_string) 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: class FlatExtractor:
"""Controls iterating objects from flat file""" """Controls iterating objects from flat file"""
# The following two should be set in subclasses
record_boundary_regexp = None # Matches beginning of next record # Set this in subclass. record_boundary_regexp should match
record_to_object = None # Function that converts text record to object # 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): def __init__(self, fileobj):
self.fileobj = fileobj # holds file object we are reading from self.fileobj = fileobj # holds file object we are reading from
self.buf = "" # holds the next part of the file self.buf = "" # holds the next part of the file
...@@ -187,10 +196,10 @@ class FlatExtractor: ...@@ -187,10 +196,10 @@ class FlatExtractor:
self.blocksize = 32 * 1024 self.blocksize = 32 * 1024
def get_next_pos(self): 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: while 1:
m = self.record_boundary_regexp.search(self.buf) m = self.record_boundary_regexp.search(self.buf, 1)
if m: return m.start(0)+1 # the +1 skips the newline if m: return m.start(1)
else: # add next block to the buffer, loop again else: # add next block to the buffer, loop again
newbuf = self.fileobj.read(self.blocksize) newbuf = self.fileobj.read(self.blocksize)
if not newbuf: if not newbuf:
...@@ -218,27 +227,20 @@ class FlatExtractor: ...@@ -218,27 +227,20 @@ class FlatExtractor:
""" """
assert not self.buf or self.buf.endswith("\n") assert not self.buf or self.buf.endswith("\n")
begin_re = self.get_index_re(index)
while 1: 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.read(self.blocksize)
self.buf += self.fileobj.readline() self.buf += self.fileobj.readline()
if not self.buf: if not self.buf:
self.at_end = 1 self.at_end = 1
return return
while 1:
def get_index_re(self, index): m = self.record_boundary_regexp.search(self.buf)
"""Return regular expression used to find index. if not m: break
cur_index = self.filename_to_index(m.group(2))
Override this in sub classes. The regular expression's second if cur_index >= index:
group needs to start at the beginning of the record that self.buf = self.buf[m.start(1):]
contains information about the object with the given index. return
else: self.buf = self.buf[m.end(1):]
"""
assert 0, "Just a placeholder, must override this in subclasses"
def iterate_starting_with(self, index): def iterate_starting_with(self, index):
"""Iterate objects whose index starts with given index""" """Iterate objects whose index starts with given index"""
...@@ -256,24 +258,24 @@ class FlatExtractor: ...@@ -256,24 +258,24 @@ class FlatExtractor:
self.buf = self.buf[next_pos:] self.buf = self.buf[next_pos:]
assert not self.close() 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): def close(self):
"""Return value of closing associated file""" """Return value of closing associated file"""
return self.fileobj.close() return self.fileobj.close()
class RorpExtractor(FlatExtractor): class RorpExtractor(FlatExtractor):
"""Iterate rorps from metadata file""" """Iterate rorps from metadata file"""
record_boundary_regexp = re.compile("\\nFile") record_boundary_regexp = re.compile("(?:\\n|^)(File (.*?))\\n")
record_to_object = staticmethod(Record2RORP) record_to_object = staticmethod(Record2RORP)
def get_index_re(self, index): filename_to_index = staticmethod(quoted_filename_to_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,))
class FlatFile: class FlatFile:
...@@ -339,7 +341,7 @@ class FlatFile: ...@@ -339,7 +341,7 @@ class FlatFile:
else: compressed = cls._rp.get_indexpath().endswith('.gz') else: compressed = cls._rp.get_indexpath().endswith('.gz')
fileobj = cls._rp.open('rb', compress = compressed) 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: else:
re = cls._extractor(fileobj) re = cls._extractor(fileobj)
return re.iterate_starting_with(restrict_index) return re.iterate_starting_with(restrict_index)
......
...@@ -1001,10 +1001,7 @@ class RPath(RORPath): ...@@ -1001,10 +1001,7 @@ class RPath(RORPath):
def get_acl(self): def get_acl(self):
"""Return access control list object, setting if necessary""" """Return access control list object, setting if necessary"""
try: acl = self.data['acl'] try: acl = self.data['acl']
except KeyError: except KeyError: acl = self.data['acl'] = acl_get(self)
acl = eas_acls.AccessControlList(self.index)
if not self.issym(): acl.read_from_rp(self)
self.data['acl'] = acl
return acl return acl
def write_acl(self, acl): def write_acl(self, acl):
...@@ -1015,14 +1012,7 @@ class RPath(RORPath): ...@@ -1015,14 +1012,7 @@ class RPath(RORPath):
def get_ea(self): def get_ea(self):
"""Return extended attributes object, setting if necessary""" """Return extended attributes object, setting if necessary"""
try: ea = self.data['ea'] try: ea = self.data['ea']
except KeyError: except KeyError: ea = self.data['ea'] = ea_get(self)
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
return ea return ea
def write_ea(self, ea): def write_ea(self, ea):
...@@ -1068,4 +1058,9 @@ class RPathFileHook: ...@@ -1068,4 +1058,9 @@ class RPathFileHook:
self.closing_thunk() self.closing_thunk()
return result 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
...@@ -41,5 +41,13 @@ class CTest(unittest.TestCase): ...@@ -41,5 +41,13 @@ class CTest(unittest.TestCase):
"""Test running C.sync""" """Test running C.sync"""
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() if __name__ == "__main__": unittest.main()
import unittest, os, time import unittest, os, time, cStringIO
from commontest import * from commontest import *
from rdiff_backup.eas_acls import * from rdiff_backup.eas_acls import *
from rdiff_backup import Globals, rpath, Time from rdiff_backup import Globals, rpath, Time
tempdir = rpath.RPath(Globals.local_connection, "testfiles/output") tempdir = rpath.RPath(Globals.local_connection, "testfiles/output")
restore_dir = rpath.RPath(Globals.local_connection,
"testfiles/restore_out")
class EATest(unittest.TestCase): class EATest(unittest.TestCase):
"""Test extended attributes""" """Test extended attributes"""
...@@ -27,6 +29,7 @@ class EATest(unittest.TestCase): ...@@ -27,6 +29,7 @@ class EATest(unittest.TestCase):
"""Make temp directory testfiles/output""" """Make temp directory testfiles/output"""
if tempdir.lstat(): tempdir.delete() if tempdir.lstat(): tempdir.delete()
tempdir.mkdir() tempdir.mkdir()
if restore_dir.lstat(): restore_dir.delete()
def testBasic(self): def testBasic(self):
"""Test basic writing and reading of extended attributes""" """Test basic writing and reading of extended attributes"""
...@@ -61,6 +64,41 @@ class EATest(unittest.TestCase): ...@@ -61,6 +64,41 @@ class EATest(unittest.TestCase):
(self.sample_ea.index, new_ea.index) (self.sample_ea.index, new_ea.index)
assert 0, "We shouldn't have gotten this far" 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): def make_backup_dirs(self):
"""Create testfiles/ea_test[12] directories """Create testfiles/ea_test[12] directories
...@@ -137,6 +175,21 @@ class EATest(unittest.TestCase): ...@@ -137,6 +175,21 @@ class EATest(unittest.TestCase):
'testfiles/empty', 'testfiles/ea_test1'] 'testfiles/empty', 'testfiles/ea_test1']
BackupRestoreSeries(None, None, dirlist, compare_eas = 1) 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): class ACLTest(unittest.TestCase):
...@@ -181,6 +234,7 @@ other::---""") ...@@ -181,6 +234,7 @@ other::---""")
"""Make temp directory testfile/output""" """Make temp directory testfile/output"""
if tempdir.lstat(): tempdir.delete() if tempdir.lstat(): tempdir.delete()
tempdir.mkdir() tempdir.mkdir()
if restore_dir.lstat(): restore_dir.delete()
def testBasic(self): def testBasic(self):
"""Test basic writing and reading of ACLs""" """Test basic writing and reading of ACLs"""
...@@ -227,6 +281,49 @@ other::---""") ...@@ -227,6 +281,49 @@ other::---""")
assert new_acl2.eq_verbose(self.dir_acl) assert new_acl2.eq_verbose(self.dir_acl)
assert 0 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): def make_backup_dirs(self):
"""Create testfiles/acl_test[12] directories""" """Create testfiles/acl_test[12] directories"""
if self.acl_testdir1.lstat(): self.acl_testdir1.delete() if self.acl_testdir1.lstat(): self.acl_testdir1.delete()
...@@ -295,6 +392,29 @@ other::---""") ...@@ -295,6 +392,29 @@ other::---""")
'testfiles/empty', 'testfiles/acl_test1'] 'testfiles/empty', 'testfiles/acl_test1']
BackupRestoreSeries(None, None, dirlist, compare_acls = 1) 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): class CombinedTest(unittest.TestCase):
"""Test backing up and restoring directories with both EAs and ACLs""" """Test backing up and restoring directories with both EAs and ACLs"""
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment