--- /dev/null
+#!/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 <mvrable@cs.ucsd.edu>
+#
+# 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()