From fa796670faf1300cf4576593a7a3ac42a9210846 Mon Sep 17 00:00:00 2001 From: bescoto Date: Mon, 23 Jun 2003 17:59:08 +0000 Subject: Add new code to determine fs abilities at runtime git-svn-id: http://svn.savannah.nongnu.org/svn/rdiff-backup/trunk@325 2b77aa54-bcbc-44c9-a7ec-4f6cf2b41109 --- rdiff-backup/rdiff_backup/fs_abilities.py | 218 ++++++++++++++++++++++++++++++ rdiff-backup/testing/fs_abilitiestest.py | 50 +++++++ 2 files changed, 268 insertions(+) create mode 100644 rdiff-backup/rdiff_backup/fs_abilities.py create mode 100644 rdiff-backup/testing/fs_abilitiestest.py diff --git a/rdiff-backup/rdiff_backup/fs_abilities.py b/rdiff-backup/rdiff_backup/fs_abilities.py new file mode 100644 index 0000000..7aeae23 --- /dev/null +++ b/rdiff-backup/rdiff_backup/fs_abilities.py @@ -0,0 +1,218 @@ +# Copyright 2002 Ben Escoto +# +# This file is part of rdiff-backup. +# +# rdiff-backup is free software; you can redistribute it and/or modify +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; either version 2 of the License, or (at your +# option) any later version. +# +# rdiff-backup is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with rdiff-backup; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +# USA + +"""Determine the capabilities of given file system + +rdiff-backup needs to read and write to file systems with varying +abilities. For instance, some file systems and not others have ACLs, +are case-sensitive, or can store ownership information. The code in +this module tests the file system for various features, and returns an +FSAbilities object describing it. + +""" + +import errno +import Globals, log, TempFile + +class FSAbilities: + """Store capabilities of given file system""" + chars_to_quote = None # Hold characters not allowable in file names + ownership = None # True if chown works on this filesystem + acls = None # True if access control lists supported + eas = None # True if extended attributes supported + hardlinks = None # True if hard linking supported + fsync_dirs = None # True if directories can be fsync'd + read_only = None # True if capabilities were determined non-destructively + + def init_readonly(self, rp): + """Set variables using fs tested at RPath rp + + This method does not write to the file system at all, and + should be run on the file system when the file system will + only need to be read. + + Only self.acls and self.eas are set. + + """ + self.read_only = 1 + self.set_eas(rp, 0) + self.set_acls(rp) + return self + + def init_readwrite(self, rp_base): + """Set variables using fs tested at rp_base + + This method creates a temp directory in rp_base and writes to + it in order to test various features. Use on a file system + that will be written to. + + This sets self.chars_to_quote, self.ownership, self.acls, + self.eas, self.hardlinks, and self.fsync_dirs. + + """ + assert rp_base.isdir() + self.read_only = 0 + + subdir = TempFile.new_in_dir(rp_base) + subdir.mkdir() + self.set_ownership(subdir) + self.set_hardlinks(subdir) + self.set_fsync_dirs(subdir) + self.set_chars_to_quote(subdir) + self.set_eas(subdir, 1) + self.set_acls(subdir) + subdir.delete() + return self + + def set_ownership(self, testdir): + """Set self.ownership to true iff testdir's ownership can be changed""" + tmp_rp = testdir.append("foo") + tmp_rp.touch() + uid, gid = tmp_rp.getuidgid() + try: + tmp_rp.chown(uid+1, gid+1) # just choose random uid/gid + tmp_rp.chown(0, 0) + except (IOError, OSError), exc: + if exc[0] == errno.EPERM: + log.Log("Warning: ownership cannot be changed on filesystem " + "at device %s" % (testdir.getdevloc(),), 2) + self.ownership = 0 + else: raise + else: self.ownership = 1 + tmp_rp.delete() + + def set_hardlinks(self, testdir): + """Set self.hardlinks to true iff hard linked files can be made""" + hl_source = testdir.append("hardlinked_file1") + hl_dest = testdir.append("hardlinked_file2") + hl_source.touch() + try: + hl_dest.hardlink(hl_source.path) + assert hl_source.getinode() == hl_dest.getinode() + except (IOError, OSError), exc: + if exc[0] in (errno.EOPNOTSUPP, errno.EPERM): + log.Log("Warning: hard linking not supported by filesystem %s" + % (testdir.getdevloc(),), 2) + self.hardlinks = 0 + else: raise + else: self.hardlinks = 1 + + def set_fsync_dirs(self, testdir): + """Set self.fsync_dirs if directories can be fsync'd""" + try: testdir.fsync() + except (IOError, OSError), exc: + log.Log("Warning: Directories on file system at %s are not " + "fsyncable.\nAssuming it's unnecessary." % + (testdir.getdevloc(),), 2) + self.fsync_dirs = 0 + else: self.fsync_dirs = 1 + + def set_chars_to_quote(self, subdir): + """Set self.chars_to_quote by trying to write various paths""" + def is_case_sensitive(): + """Return true if file system is case sensitive""" + upper_a = subdir.append("A") + upper_a.touch() + lower_a = subdir.append("a") + if lower_a.lstat(): + lower_a.delete() + upper_a.setdata() + assert not upper_a.lstat() + return 0 + else: + upper_a.delete() + return 1 + + def supports_unusual_chars(): + """Test handling of several chars sometimes not supported""" + for filename in [':', '\\', chr(175)]: + rp = subdir.append(filename) + try: rp.touch() + except IOError: + assert not rp.lstat() + return 0 + assert rp.lstat() + rp.delete() + return 1 + + def sanity_check(): + """Make sure basic filenames writable""" + for filename in ['5-_ a']: + rp = subdir.append(filename) + rp.touch() + assert rp.lstat() + rp.delete() + + sanity_check() + if is_case_sensitive(): + if supports_unusual_chars(): self.chars_to_quote = "" + else: self.chars_to_quote = "^A-Za-z0-9_ -" + else: + if supports_unusual_chars(): self.chars_to_quote = "A-Z" + else: self.chars_to_quote = "^a-z0-9_ -" + + def set_acls(self, rp): + """Set self.acls based on rp. Does not write. Needs to be local""" + assert Globals.local_connection is rp.conn + assert rp.lstat() + try: import posix1e + except ImportError: + log.Log("Warning: Unable to import module posix1e from pylibacl " + "package.\nACLs not supported on device %s" % + (rp.getdevloc(),), 2) + self.acls = 0 + return + + try: posix1e.ACL(file=rp.path) + except IOError, exc: + if exc[0] == errno.EOPNOTSUPP: + log.Log("Warning: ACLs appear not to be supported by " + "filesystem on device %s" % (rp.getdevloc(),), 2) + self.acls = 0 + else: raise + else: self.acls = 1 + + def set_eas(self, rp, write): + """Set extended attributes from rp. Run locally. + + Tests writing if write is true. + + """ + assert Globals.local_connection is rp.conn + assert rp.lstat() + try: import xattr + except ImportError: + log.Log("Warning: Unable to import module xattr. ACLs not " + "supported on device %s" % (rp.getdevloc(),), 2) + self.eas = 0 + return + + try: + xattr.listxattr(rp.path) + if write: + xattr.setxattr(rp.path, "user.test", "test val") + assert xattr.getxattr(rp.path, "user.test") == "test val" + except IOError, exc: + if exc[0] == errno.EOPNOTSUPP: + log.Log("Warning: Extended attributes not supported by " + "filesystem on device %s" % (rp.getdevloc(),), 2) + self.eas = 0 + else: raise + else: self.eas = 1 + diff --git a/rdiff-backup/testing/fs_abilitiestest.py b/rdiff-backup/testing/fs_abilitiestest.py new file mode 100644 index 0000000..0ac75b0 --- /dev/null +++ b/rdiff-backup/testing/fs_abilitiestest.py @@ -0,0 +1,50 @@ +import unittest, os +from commontest import * +from rdiff_backup import Globals, rpath, fs_abilities + +class FSAbilitiesTest(unittest.TestCase): + """Test testing of file system abilities + + Some of these tests assume that the actual file system tested has + the given abilities. If the file system this is run on differs + from the original test system, this test may/should fail. Change + the expected values below. + + """ + # Describes standard linux file system + dir_to_test = "testfiles" + eas = acls = 1 + chars_to_quote = "" + ownership = (os.getuid() == 0) + hardlinks = fsync_dirs = 1 + + # Describes MS-Windows style file system + #dir_to_test = "/mnt/fat" + #eas = acls = 0 + #chars_to_quote = "^a-z0-9_ -" + #ownership = hardlinks = 0 + #fsync_dirs = 1 + + def testReadOnly(self): + """Test basic querying read only""" + base_dir = rpath.RPath(Globals.local_connection, self.dir_to_test) + fsa = fs_abilities.FSAbilities().init_readonly(base_dir) + assert fsa.read_only == 1, fsa.read_only + assert fsa.eas == self.eas, fsa.eas + assert fsa.acls == self.acls, fsa.acls + + def testReadWrite(self): + """Test basic querying read/write""" + base_dir = rpath.RPath(Globals.local_connection, self.dir_to_test) + fsa = fs_abilities.FSAbilities().init_readwrite(base_dir) + assert fsa.read_only == 0, fsa.read_only + assert fsa.eas == self.eas, fsa.eas + assert fsa.acls == self.acls, fsa.acls + assert fsa.chars_to_quote == self.chars_to_quote, fsa.chars_to_quote + assert fsa.ownership == self.ownership, fsa.ownership + assert fsa.hardlinks == self.hardlinks, fsa.hardlinks + assert fsa.fsync_dirs == self.fsync_dirs, fsa.fsync_dirs + + +if __name__ == "__main__": unittest.main() + -- cgit v1.2.1