3 # Copyright 2011 GRNET S.A. All rights reserved.
5 # Redistribution and use in source and binary forms, with or
6 # without modification, are permitted provided that the following
9 # 1. Redistributions of source code must retain the above
10 # copyright notice, this list of conditions and the following
13 # 2. Redistributions in binary form must reproduce the above
14 # copyright notice, this list of conditions and the following
15 # disclaimer in the documentation and/or other materials
16 # provided with the distribution.
18 # THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
19 # OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21 # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
22 # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
25 # USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
26 # AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
28 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 # POSSIBILITY OF SUCH DAMAGE.
31 # The views and conclusions contained in the software and
32 # documentation are those of the authors and should not be
33 # interpreted as representing official policies, either expressed
34 # or implied, of GRNET S.A.
36 from cStringIO import StringIO
37 from errno import (EACCES, EBADF, EINVAL, EISDIR, EIO, ENOENT, ENOTDIR,
39 from getpass import getuser
40 from stat import S_IFDIR, S_IFREG
44 from pithos.api.compat import parse_http_date
45 from pithos.lib.client import OOS_Client, Fault
46 from pithos.lib.fuse import FUSE, FuseOSError, Operations
48 DEFAULT_HOST = 'pithos.dev.grnet.gr'
54 class StoreFS(Operations):
55 def __init__(self, user, token, verbose=False):
56 self.verbose = verbose
57 self.client = OOS_Client(DEFAULT_HOST, token, user)
59 def __call__(self, op, path, *args):
60 container, sep, object = path[1:].partition('/')
62 data = repr(args)[:100]
63 print '-> %s %r %r %r' % (op, container, object, data)
64 ret = '[Unhandled Exception]'
68 func = getattr(self, 'object_' + op, None)
69 funcargs = (container, object) + args
71 func = getattr(self, 'container_' + op, None)
72 funcargs = (container,) + args
74 func = getattr(self, 'account_' + op, None)
78 # Fallback to defaults
79 func = getattr(self, op)
80 funcargs = (path,) + args
84 except FuseOSError, e:
89 print '<-', op, repr(ret)
92 def _get_container_meta(self, container, **kwargs):
94 return self.client.retrieve_container_metadata(container, **kwargs)
96 raise FuseOSError(ENOENT)
98 def _get_object_meta(self, container, object, **kwargs):
100 return self.client.retrieve_object_metadata(container, object,
103 raise FuseOSError(ENOENT)
108 def statfs(self, path):
109 return dict(f_bsize=1024, f_blocks=1024**2, f_bfree=1024**2,
115 def account_chmod(self, mode):
116 self.client.update_account_metadata(mode=str(mode))
118 def account_chown(self, uid, gid):
119 self.client.update_account_metadata(uid=uid, gid=gid)
121 def account_getattr(self, fh=None):
122 meta = self.client.retrieve_account_metadata()
123 mode = int(meta.get('x-account-meta-mode', 0755))
124 last_modified = meta.get('last-modified', None)
125 modified = parse_http_date(last_modified) if last_modified else epoch
126 count = int(meta['x-account-container-count'])
127 uid = int(meta.get('x-account-meta-uid', 0))
128 gid = int(meta.get('x-account-meta-gid', 0))
131 'st_mode': S_IFDIR | mode,
132 'st_nlink': 2 + count,
136 'st_mtime': modified,
137 'st_atime': modified}
139 def account_getxattr(self, name, position=0):
140 meta = self.client.retrieve_account_metadata(restricted=True)
141 return meta.get('xattr-' + name, '')
143 def account_listxattr(self):
144 meta = self.client.retrieve_account_metadata(restricted=True)
146 return [k[len(prefix):] for k in meta if k.startswith(prefix)]
148 def account_readdir(self, fh):
149 return ['.', '..'] + self.client.list_containers()
151 def account_removexattr(self, name):
152 attr = 'xattr-' + name
153 self.client.delete_account_metadata([attr])
155 def account_setxattr(self, name, value, options, position=0):
156 attr = 'xattr-' + name
158 self.client.update_account_metadata(**meta)
163 def container_chmod(self, container, mode):
164 self.client.update_container_metadata(container, mode=str(mode))
166 def container_chown(self, container, uid, gid):
167 self.client.update_container_metadata(container, uid=uid, gid=gid)
169 def container_getattr(self, container, fh=None):
170 meta = self._get_container_meta(container)
171 mode = int(meta.get('x-container-meta-mode', 0755))
172 modified = parse_http_date(meta['last-modified'])
173 count = int(meta['x-container-object-count'])
174 uid = int(meta.get('x-account-meta-uid', 0))
175 gid = int(meta.get('x-account-meta-gid', 0))
178 'st_mode': S_IFDIR | mode,
179 'st_nlink': 2 + count,
183 'st_mtime': modified,
184 'st_atime': modified}
186 def container_getxattr(self, container, name, position=0):
187 meta = self._get_container_meta(container)
188 return meta.get('xattr-' + name, '')
190 def container_listxattr(self, container):
191 meta = self._get_container_meta(container, restricted=True)
193 return [k[len(prefix):] for k in meta if k.startswith(prefix)]
195 def container_mkdir(self, container, mode):
196 mode = str(mode & 0777)
197 self.client.create_container(container, mode=mode)
199 def container_readdir(self, container, fh):
200 params = {'delimiter': '/', 'prefix': ''}
201 objects = self.client.list_objects(container, params=params)
202 files = [o for o in objects if not o.endswith('/')]
203 return ['.', '..'] + files
205 def container_removexattr(self, container, name):
206 attr = 'xattr-' + name
207 self.client.delete_container_metadata(container, [attr])
209 def container_rename(self, container, path):
210 new_container, sep, new_object = path[1:].partition('/')
211 if not new_container or new_object:
212 raise FuseOSError(EINVAL)
213 self.client.delete_container(container)
214 self.client.create_container(new_container)
216 def container_rmdir(self, container):
218 self.client.delete_container(container)
220 raise FuseOSError(ENOENT)
222 def container_setxattr(self, container, name, value, options, position=0):
223 attr = 'xattr-' + name
225 self.client.update_container_metadata(container, **meta)
230 def object_chmod(self, container, object, mode):
231 self.client.update_object_metadata(container, object, mode=str(mode))
233 def object_chown(self, container, uid, gid):
234 self.client.update_object_metadata(container, object,
235 uid=str(uid), gid=str(gid))
237 def object_create(self, container, object, mode, fi=None):
239 self.client.create_object(container, object,
241 content_type='application/octet-stream',
245 def object_getattr(self, container, object, fh=None):
246 meta = self._get_object_meta(container, object)
247 mode = int(meta.get('x-object-meta-mode', 0644))
248 modified = parse_http_date(meta['last-modified'])
249 uid = int(meta.get('x-account-meta-uid', 0))
250 gid = int(meta.get('x-account-meta-gid', 0))
251 size = int(meta['content-length'])
253 if meta['content-type'] == 'application/directory':
261 'st_mode': flags | mode,
266 'st_mtime': modified,
267 'st_atime': modified,
270 def object_getxattr(self, container, object, name, position=0):
271 meta = self._get_object_meta(container, object, restricted=True)
272 return meta.get('xattr-' + name, '')
274 def object_listxattr(self, container, object):
275 meta = self._get_object_meta(container, object, restricted=True)
277 return [k[len(prefix):] for k in meta if k.startswith(prefix)]
279 def object_mkdir(self, container, object, mode):
280 mode = str(mode & 0777)
281 self.client.create_directory_marker(container, object)
282 self.client.update_object_metadata(container, object, mode=mode)
284 def object_read(self, container, object, nbyte, offset, fh):
285 data = self.client.retrieve_object(container, object)
286 return data[offset:offset + nbyte]
288 def object_readdir(self, container, object, fh):
289 params = {'delimiter': '/', 'prefix': object}
290 objects = self.client.list_objects(container, params=params)
291 files = [o.rpartition('/')[2] for o in objects if not o.endswith('/')]
292 return ['.', '..'] + files
294 def object_removexattr(self, container, object, name):
295 attr = 'xattr-' + name
296 self.client.delete_object_metadata(container, object, [attr])
298 def object_rename(self, container, object, path):
299 new_container, sep, new_object = path[1:].partition('/')
300 if not new_container or not new_object:
301 raise FuseOSError(EINVAL)
302 self.client.move_object(container, object, new_container, new_object)
304 def object_rmdir(self, container, object):
305 self.client.delete_object(container, object)
307 def object_setxattr(self, container, object, name, value, options,
309 attr = 'xattr-' + name
311 self.client.update_object_metadata(container, object, **meta)
313 def object_truncate(self, container, object, length, fh=None):
314 data = self.client.retrieve_object(container, object)
315 f = StringIO(data[:length])
316 self.client.update_object(container, object, f)
318 def object_unlink(self, container, object):
319 self.client.delete_object(container, object)
321 def object_write(self, container, object, data, offset, fh):
323 self.client.update_object(container, object, f, offset=offset)
327 if __name__ == "__main__":
329 print 'usage: %s <user> <token> <mountpoint>' % argv[0]
333 fs = StoreFS(argv[1], argv[2], verbose=True)
334 fuse = FUSE(fs, argv[3], foreground=True)