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 Client, Fault
46 from pithos.lib.fuse import FUSE, FuseOSError, Operations
48 DEFAULT_HOST = 'pithos.dev.grnet.gr'
55 class StoreFS(Operations):
56 def __init__(self, user, verbose=False):
57 self.verbose = verbose
58 self.client = Client(DEFAULT_HOST, user, DEFAULT_API)
60 def __call__(self, op, path, *args):
61 container, sep, object = path[1:].partition('/')
63 data = repr(args)[:100]
64 print '-> %s %r %r %r' % (op, container, object, data)
65 ret = '[Unhandled Exception]'
69 ret = getattr(self, 'object_' + op)(container, object, *args)
71 ret = getattr(self, 'container_' + op)(container, *args)
73 ret = getattr(self, 'account_' + op)(*args)
75 except AttributeError:
76 ret = getattr(self, op)(path, *args)
78 except FuseOSError, e:
83 print '<-', op, repr(ret)
86 def _get_container_meta(self, container, **kwargs):
88 return self.client.retrieve_container_metadata(container, **kwargs)
90 raise FuseOSError(ENOENT)
92 def _get_object_meta(self, container, object, **kwargs):
94 return self.client.retrieve_object_metadata(container, object,
97 raise FuseOSError(ENOENT)
102 def statfs(self, path):
103 return dict(f_bsize=1024, f_blocks=1024**2, f_bfree=1024**2,
109 def account_chmod(self, mode):
110 self.client.update_account_metadata(mode=mode)
112 def account_chown(self, uid, gid):
113 self.client.update_account_metadata(uid=uid, gid=gid)
115 def account_getattr(self, fh=None):
116 meta = self.client.account_metadata()
117 mode = int(meta.get('x-account-meta-mode', 0755))
118 modified = parse_http_date(meta['last-modified'])
119 count = int(meta['x-account-container-count'])
120 uid = int(meta.get('x-account-meta-uid', 0))
121 gid = int(meta.get('x-account-meta-gid', 0))
124 'st_mode': S_IFDIR | mode,
125 'st_nlink': 2 + count,
129 'st_mtime': modified,
130 'st_atime': modified}
132 def account_getxattr(self, name, position=0):
133 meta = self.client.retrieve_account_metadata(restricted=True)
134 return meta.get('xattr-' + name, '')
136 def account_listxattr(self):
137 meta = self.client.retrieve_account_metadata(restricted=True)
139 return [k[len(prefix):] for k in meta if k.startswith(prefix)]
141 def account_readdir(self, fh):
142 return ['.', '..'] + self.client.list_containers()
144 def account_removexattr(self, name):
145 attr = 'xattr-' + name
146 self.client.delete_account_metadata([attr])
148 def account_setxattr(self, name, value, options, position=0):
149 attr = 'xattr-' + name
151 self.client.update_account_metadata(**meta)
156 def container_chmod(self, container, mode):
157 self.client.update_container_metadata(container, mode=mode)
159 def container_chown(self, container, uid, gid):
160 self.client.update_container_metadata(container, uid=uid, gid=gid)
162 def container_getattr(self, container, fh=None):
163 meta = self._get_container_meta(container)
164 mode = int(meta.get('x-container-meta-mode', 0755))
165 modified = parse_http_date(meta['last-modified'])
166 count = int(meta['x-container-object-count'])
167 uid = int(meta.get('x-account-meta-uid', 0))
168 gid = int(meta.get('x-account-meta-gid', 0))
171 'st_mode': S_IFDIR | mode,
172 'st_nlink': 2 + count,
176 'st_mtime': modified,
177 'st_atime': modified}
179 def container_getxattr(self, container, name, position=0):
180 meta = self._get_container_meta(container)
181 return meta.get('xattr-' + name, '')
183 def container_listxattr(self, container):
184 meta = self._get_container_meta(container, restricted=True)
186 return [k[len(prefix):] for k in meta if k.startswith(prefix)]
188 def container_mkdir(self, container, mode):
189 self.client.create_container(container)
190 self.client.update_container_metadata(container, mode=mode)
192 def container_readdir(self, container, fh):
193 params = {'delimiter': '/', 'prefix': ''}
194 objects = self.client.list_objects(container, params=params)
195 files = [o for o in objects if not o.endswith('/')]
196 return ['.', '..'] + files
198 def container_removexattr(self, container, name):
199 attr = 'xattr-' + name
200 self.client.delete_container_metadata(container, [attr])
202 def container_rename(self, container, path):
203 new_container, sep, new_object = path[1:].partition('/')
204 if not new_container or new_object:
205 raise FuseOSError(EINVAL)
206 self.client.delete_container(container)
207 self.client.create_container(new_container)
209 def container_rmdir(self, container):
211 self.client.delete_container(container)
213 raise FuseOSError(ENOENT)
215 def container_setxattr(self, container, name, value, options, position=0):
216 attr = 'xattr-' + name
218 self.client.update_container_metadata(container, **meta)
223 def object_chmod(self, container, object, mode):
224 self.client.update_object_metadata(container, object, mode=mode)
226 def object_chown(self, container, uid, gid):
227 self.client.update_object_metadata(container, object, uid=uid, gid=gid)
229 def object_create(self, container, object, mode, fi=None):
231 headers = {'Content-Type': 'application/octet-stream'}
232 self.client.create_object(container, object, f=None, headers=headers)
233 self.client.update_object_metadata(container, object, mode=mode)
236 def object_getattr(self, container, object, fh=None):
237 meta = self._get_object_meta(container, object)
238 mode = int(meta.get('x-object-meta-mode', 0644))
239 modified = parse_http_date(meta['last-modified'])
240 uid = int(meta.get('x-account-meta-uid', 0))
241 gid = int(meta.get('x-account-meta-gid', 0))
242 size = int(meta['content-length'])
244 if meta['content-type'] == 'application/directory':
252 'st_mode': flags | mode,
257 'st_mtime': modified,
258 'st_atime': modified,
261 def object_getxattr(self, container, object, name, position=0):
262 meta = self._get_object_meta(container, object, restricted=True)
263 return meta.get('xattr-' + name, '')
265 def object_listxattr(self, container, object):
266 meta = self._get_object_meta(container, object, restricted=True)
268 return [k[len(prefix):] for k in meta if k.startswith(prefix)]
270 def object_mkdir(self, container, object, mode):
271 self.client.create_directory_marker(container, object)
272 self.client.update_object_metadata(container, object, mode=mode)
274 def object_read(self, container, object, nbyte, offset, fh):
275 data = self.client.retrieve_object(container, object)
276 return data[offset:offset + nbyte]
278 def object_readdir(self, container, object, fh):
279 params = {'delimiter': '/', 'prefix': object}
280 objects = self.client.list_objects(container, params=params)
281 files = [o.rpartition('/')[2] for o in objects if not o.endswith('/')]
282 return ['.', '..'] + files
284 def object_removexattr(self, container, object, name):
285 attr = 'xattr-' + name
286 self.client.delete_object_metadata(container, object, [attr])
288 def object_rename(self, container, object, path):
289 new_container, sep, new_object = path[1:].partition('/')
290 if not new_container or not new_object:
291 raise FuseOSError(EINVAL)
292 self.client.move_object(container, object, new_container, new_object)
294 def object_rmdir(self, container, object):
295 self.client.delete_object(container, object)
297 def object_setxattr(self, container, object, name, value, options,
299 attr = 'xattr-' + name
301 self.client.update_object_metadata(container, object, **meta)
303 def object_truncate(self, container, object, length, fh=None):
304 data = self.client.retrieve_object(container, object)
305 f = StringIO(data[:length])
306 self.client.update_object(container, object, f)
308 def object_unlink(self, container, object):
309 self.client.delete_object(container, object)
311 def object_write(self, container, object, data, offset, fh):
315 headers['CONTENT_RANGE'] = 'bytes %d-/*' % offset
317 headers['CONTENT_RANGE'] = 'bytes */*'
319 self.client.update_object(container, object, f, headers=headers)
323 if __name__ == "__main__":
325 print 'usage: %s <mountpoint>' % argv[0]
329 fs = StoreFS(user, verbose=True)
330 fuse = FUSE(fs, argv[1], foreground=True)