X-Git-Url: http://git.vrable.net/?a=blobdiff_plain;f=contrib%2Fcumulus-fuse;fp=contrib%2Fcumulus-fuse;h=884933527300e7659848c8720aed0443a909266c;hb=f51789bfa9d0f6f9826b72f3c32ff89307ec3dc6;hp=0000000000000000000000000000000000000000;hpb=2ee97034047db53780a52d803b1c577b4c23c303;p=cumulus.git diff --git a/contrib/cumulus-fuse b/contrib/cumulus-fuse new file mode 100755 index 0000000..8849335 --- /dev/null +++ b/contrib/cumulus-fuse @@ -0,0 +1,199 @@ +#!/usr/bin/python +# +# FUSE interface to Cumulus, allowing snapshots to be mounted as a virtual +# filesystem. +# +# Copyright (C) 2006-2008 The Regents of the University of California +# Written by Michael Vrable +# +# This program can be distributed under the terms of the GNU GPL, either +# version 2 of the License, or (at your option) any later version. See the +# file COPYING. + +import itertools, os, stat, errno +import fuse +from fuse import Fuse +import cumulus +import cumulus.metadata + +fuse.fuse_python_api = (0, 2) + +# TODO: Figure out FUSE option parsing +lowlevel = cumulus.LowlevelDataStore('/backups/lbs/turin') +store = cumulus.ObjectStore(lowlevel) + +def _printable(ptr): + if ptr is None: return None + return tuple(x[1] for x in ptr) + +def parse_path(path): + """Strip leading slashe from path, and split apart into components.""" + if not path.startswith('/'): + return None + if path == '/': + return [] + else: + return path[1:].split('/') + +def load_metadata(path): + if type(path) != type([]): + path = parse_path(path) + + if path is None or len(path) < 2: + return None + + snapshot = cumulus.parse_full(store.load_snapshot(path[0])) + metadata = cumulus.metadata.Metadata(store, snapshot['Root']) + ptr = metadata.search(lambda x: cmp(x, path[1:])) + item = metadata._read(ptr) + if metadata._get_path(item) != path[1:]: + return None + return cumulus.MetadataItem(item, store) + +class MyStat(fuse.Stat): + def __init__(self): + self.st_mode = 0 + self.st_ino = 0 + self.st_dev = 0 + self.st_nlink = 0 + self.st_uid = 0 + self.st_gid = 0 + self.st_size = 0 + self.st_atime = 0 + self.st_mtime = 0 + self.st_ctime = 0 + +class CumulusFS(Fuse): + def getattr(self, path): + st = MyStat() + path = parse_path(path) + + if path is None: return -errno.ENOENT + if path == []: + # Root directory + st.st_mode = stat.S_IFDIR | 0755 + st.st_nlink = 2 + return st + + snapshot = cumulus.parse_full(store.load_snapshot(path[0])) + if len(path) == 1: + # Snapshot directory + st.st_mode = stat.S_IFDIR | 0755 + st.st_nlink = 2 + else: + # File contained within a snapshot + m = load_metadata(path) + if m is None: + return -errno.ENOENT + + st.st_nlink = 1 + st.st_uid = m.items.user[0] + st.st_gid = m.items.group[0] + st.st_mtime = m.items.mtime + st.st_ctime = m.items.ctime + st.st_atime = m.items.mtime + if m.items.type == 'd': + st.st_mode = stat.S_IFDIR | m.items.mode + st.st_nlink = 2 + elif m.items.type == 'l': + st.st_mode = stat.S_IFLNK | m.items.mode + else: + st.st_mode = stat.S_IFREG | m.items.mode + st.st_size = m.items.size + + return st + + def _cumulus_readdir(self, metadata, path): + # Find pointer to base directory in metadata + ptr1 = metadata.search(lambda x: cmp(x, path)) + + # Find pointer to end of directory contents + def endcmp(p1): + def _cmp(p2): + if len(p2) > len(p1): p2 = p2[0:len(p1)] + if p2 > p1: + return 1 + else: + return -1 + return _cmp + ptr2 = metadata.search(endcmp(path), ptr1) + + # Scan through looking for top-level files and directories. Skip over + # data for files in subdirectories. + while metadata._cmp(ptr1, ptr2) < 0: + item = metadata._read(ptr1) + m = cumulus.MetadataItem(item, store) + if m.items.name == '.': + itempath = [] + else: + itempath = m.items.name.split('/') + assert itempath[0:len(path)] == path + + if len(itempath) == len(path): + ptr1 = metadata._advance(ptr1) + continue + + if len(itempath) > len(path) + 1: + ptr1 = metadata.search(endcmp(itempath[0:len(path)+1]), + ptr1, ptr2) + continue + + yield itempath[len(path)] + ptr1 = metadata._advance(ptr1) + + def readdir(self, path, offset): + if path == '/': + for r in itertools.chain(('.', '..'), lowlevel.list_snapshots()): + yield fuse.Direntry(r) + else: + path = parse_path(path) + if path is None: + return + snapshot = cumulus.parse_full(store.load_snapshot(path[0])) + metadata = cumulus.metadata.Metadata(store, snapshot['Root']) + for r in itertools.chain(('.', '..'), + self._cumulus_readdir(metadata, path[1:])): + yield fuse.Direntry(r) + + def readlink(self, path): + m = load_metadata(path) + if m is None: + return -errno.ENOENT + else: + return m.items.target + + def open(self, path, flags): + m = load_metadata(path) + if m is None: + return -errno.ENOENT + accmode = os.O_RDONLY | os.O_WRONLY | os.O_RDWR + if (flags & accmode) != os.O_RDONLY: + return -errno.EACCES + + def read(self, path, size, offset): + m = load_metadata(path) + if m is None: + return -errno.ENOENT + + return '\0' * size + +def main(): + usage=""" +cumulus-fuse: Mount cumulus snapshots as a filesystem + +""" + Fuse.fusage + server = CumulusFS(version="%prog " + fuse.__version__, + usage=usage, + dash_s_do='setsingle') + + server.parser.add_option(mountopt="root", metavar="PATH", default='/', + help="read snapshots from PATH [default: %default]") + + server.parse(errex=1) + print server.fuse_args + print server.fuse_args.assemble() + server.main() + store.cleanup() + +if __name__ == '__main__': + main()