3 # FUSE interface to Cumulus, allowing snapshots to be mounted as a virtual
6 # Copyright (C) 2006-2008 The Regents of the University of California
7 # Written by Michael Vrable <mvrable@cs.ucsd.edu>
9 # This program can be distributed under the terms of the GNU GPL, either
10 # version 2 of the License, or (at your option) any later version. See the
13 import itertools, os, stat, errno
17 import cumulus.metadata
19 fuse.fuse_python_api = (0, 2)
21 # TODO: Figure out FUSE option parsing
22 lowlevel = cumulus.LowlevelDataStore('/backups/lbs/corinth')
23 store = cumulus.ObjectStore(lowlevel)
26 if ptr is None: return None
27 return tuple(x[1] for x in ptr)
30 """Strip leading slashe from path, and split apart into components."""
31 if not path.startswith('/'):
36 return path[1:].split('/')
39 def load_metadata(path):
40 if type(path) != type([]):
41 path = parse_path(path)
43 if path is None or len(path) < 2:
47 if path in lookup_cache:
48 return lookup_cache[path]
50 snapshot = cumulus.parse_full(store.load_snapshot(path[0]))
51 metadata = cumulus.metadata.Metadata(store, snapshot['Root'])
52 ptr = metadata.search(lambda x: cmp(x, path[1:]))
53 item = metadata._read(ptr)
54 if metadata._get_path(item) != path[1:]:
55 lookup_cache[path] = None
57 lookup_cache[path] = cumulus.MetadataItem(item, store)
58 return lookup_cache[path]
60 class MyStat(fuse.Stat):
73 class CumulusFS(Fuse):
74 def getattr(self, path):
76 path = parse_path(path)
78 if path is None: return -errno.ENOENT
81 st.st_mode = stat.S_IFDIR | 0755
85 snapshot = cumulus.parse_full(store.load_snapshot(path[0]))
88 st.st_mode = stat.S_IFDIR | 0755
91 # File contained within a snapshot
92 m = load_metadata(path)
97 st.st_uid = m.items.user[0]
98 st.st_gid = m.items.group[0]
99 st.st_mtime = m.items.mtime
100 st.st_ctime = m.items.ctime
101 st.st_atime = m.items.mtime
102 if m.items.type == 'd':
103 st.st_mode = stat.S_IFDIR | m.items.mode
105 elif m.items.type == 'l':
106 st.st_mode = stat.S_IFLNK | m.items.mode
108 st.st_mode = stat.S_IFREG | m.items.mode
109 st.st_size = m.items.size
113 def _cumulus_readdir(self, metadata, snapshot, path):
114 # Find pointer to base directory in metadata
115 ptr1 = metadata.search(lambda x: cmp(x, path))
117 # Find pointer to end of directory contents
120 if len(p2) > len(p1): p2 = p2[0:len(p1)]
126 ptr2 = metadata.search(endcmp(path), ptr1)
128 # Scan through looking for top-level files and directories. Skip over
129 # data for files in subdirectories.
130 while metadata._cmp(ptr1, ptr2) < 0:
131 item = metadata._read(ptr1)
132 m = cumulus.MetadataItem(item, store)
133 if m.items.name == '.':
136 itempath = m.items.name.split('/')
137 assert itempath[0:len(path)] == path
139 if len(itempath) == len(path):
140 ptr1 = metadata._advance(ptr1)
143 if len(itempath) > len(path) + 1:
144 ptr1 = metadata.search(endcmp(itempath[0:len(path)+1]),
148 lookup_cache[(snapshot,) + tuple(itempath)] = m
149 yield itempath[len(path)]
150 ptr1 = metadata._advance(ptr1)
152 def readdir(self, path, offset):
154 for r in itertools.chain(('.', '..'), lowlevel.list_snapshots()):
155 yield fuse.Direntry(r)
157 path = parse_path(path)
160 snapshot = cumulus.parse_full(store.load_snapshot(path[0]))
161 metadata = cumulus.metadata.Metadata(store, snapshot['Root'])
162 for r in itertools.chain(('.', '..'),
163 self._cumulus_readdir(metadata,
166 yield fuse.Direntry(r)
168 def readlink(self, path):
169 m = load_metadata(path)
173 return m.items.target
175 def open(self, path, flags):
176 m = load_metadata(path)
179 accmode = os.O_RDONLY | os.O_WRONLY | os.O_RDWR
180 if (flags & accmode) != os.O_RDONLY:
183 def read(self, path, size, offset):
184 m = load_metadata(path)
192 # Skip over this data block if we can, based on remaining data
193 # offset and block size.
194 (bseg, boff, bcsum, bslice) = store.parse_ref(b)
195 if bslice is not None:
201 # Otherwise, load the data block and read any data out of it we
204 if offset >= len(data):
220 cumulus-fuse: Mount cumulus snapshots as a filesystem
223 server = CumulusFS(version="%prog " + fuse.__version__,
225 dash_s_do='setsingle')
227 server.parser.add_option(mountopt="root", metavar="PATH", default='/',
228 help="read snapshots from PATH [default: %default]")
230 server.parse(errex=1)
231 print server.fuse_args
232 print server.fuse_args.assemble()
236 if __name__ == '__main__':