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 lib.client import OOS_Client, Fault
45 from lib.fuse import FUSE, FuseOSError, Operations
47 from pithos.api.compat import parse_http_date
49 DEFAULT_HOST = 'pithos.dev.grnet.gr'
55 class StoreFS(Operations):
56 def __init__(self, user, token, verbose=False):
57 self.verbose = verbose
58 self.client = OOS_Client(DEFAULT_HOST, token, user)
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 func = getattr(self, 'object_' + op, None)
70 funcargs = (container, object) + args
72 func = getattr(self, 'container_' + op, None)
73 funcargs = (container,) + args
75 func = getattr(self, 'account_' + op, None)
79 # Fallback to defaults
80 func = getattr(self, op)
81 funcargs = (path,) + args
85 except FuseOSError, e:
90 print '<-', op, repr(ret)
93 def _get_container_meta(self, container, **kwargs):
95 return self.client.retrieve_container_metadata(container, **kwargs)
97 raise FuseOSError(ENOENT)
99 def _get_object_meta(self, container, object, **kwargs):
101 return self.client.retrieve_object_metadata(container, object,
104 raise FuseOSError(ENOENT)
109 def statfs(self, path):
110 return dict(f_bsize=1024, f_blocks=1024**2, f_bfree=1024**2,
116 def account_chmod(self, mode):
117 self.client.update_account_metadata(mode=str(mode))
119 def account_chown(self, uid, gid):
120 self.client.update_account_metadata(uid=uid, gid=gid)
122 def account_getattr(self, fh=None):
123 meta = self.client.retrieve_account_metadata()
124 mode = int(meta.get('x-account-meta-mode', 0755))
125 last_modified = meta.get('last-modified', None)
126 modified = parse_http_date(last_modified) if last_modified else epoch
127 count = int(meta['x-account-container-count'])
128 uid = int(meta.get('x-account-meta-uid', 0))
129 gid = int(meta.get('x-account-meta-gid', 0))
132 'st_mode': S_IFDIR | mode,
133 'st_nlink': 2 + count,
137 'st_mtime': modified,
138 'st_atime': modified}
140 def account_getxattr(self, name, position=0):
141 meta = self.client.retrieve_account_metadata(restricted=True)
142 return meta.get('xattr-' + name, '')
144 def account_listxattr(self):
145 meta = self.client.retrieve_account_metadata(restricted=True)
147 return [k[len(prefix):] for k in meta if k.startswith(prefix)]
149 def account_readdir(self, fh):
150 return ['.', '..'] + self.client.list_containers()
152 def account_removexattr(self, name):
153 attr = 'xattr-' + name
154 self.client.delete_account_metadata([attr])
156 def account_setxattr(self, name, value, options, position=0):
157 attr = 'xattr-' + name
159 self.client.update_account_metadata(**meta)
164 def container_chmod(self, container, mode):
165 self.client.update_container_metadata(container, mode=str(mode))
167 def container_chown(self, container, uid, gid):
168 self.client.update_container_metadata(container, uid=uid, gid=gid)
170 def container_getattr(self, container, fh=None):
171 meta = self._get_container_meta(container)
172 mode = int(meta.get('x-container-meta-mode', 0755))
173 modified = parse_http_date(meta['last-modified'])
174 count = int(meta['x-container-object-count'])
175 uid = int(meta.get('x-account-meta-uid', 0))
176 gid = int(meta.get('x-account-meta-gid', 0))
179 'st_mode': S_IFDIR | mode,
180 'st_nlink': 2 + count,
184 'st_mtime': modified,
185 'st_atime': modified}
187 def container_getxattr(self, container, name, position=0):
188 meta = self._get_container_meta(container)
189 return meta.get('xattr-' + name, '')
191 def container_listxattr(self, container):
192 meta = self._get_container_meta(container, restricted=True)
194 return [k[len(prefix):] for k in meta if k.startswith(prefix)]
196 def container_mkdir(self, container, mode):
197 mode = str(mode & 0777)
198 self.client.create_container(container, mode=mode)
200 def container_readdir(self, container, fh):
201 params = {'delimiter': '/', 'prefix': ''}
202 objects = self.client.list_objects(container, params=params)
203 files = [o for o in objects if not o.endswith('/')]
204 return ['.', '..'] + files
206 def container_removexattr(self, container, name):
207 attr = 'xattr-' + name
208 self.client.delete_container_metadata(container, [attr])
210 def container_rename(self, container, path):
211 new_container, sep, new_object = path[1:].partition('/')
212 if not new_container or new_object:
213 raise FuseOSError(EINVAL)
214 self.client.delete_container(container)
215 self.client.create_container(new_container)
217 def container_rmdir(self, container):
219 self.client.delete_container(container)
221 raise FuseOSError(ENOENT)
223 def container_setxattr(self, container, name, value, options, position=0):
224 attr = 'xattr-' + name
226 self.client.update_container_metadata(container, **meta)
231 def object_chmod(self, container, object, mode):
232 self.client.update_object_metadata(container, object, mode=str(mode))
234 def object_chown(self, container, uid, gid):
235 self.client.update_object_metadata(container, object,
236 uid=str(uid), gid=str(gid))
238 def object_create(self, container, object, mode, fi=None):
240 self.client.create_object(container, object,
242 content_type='application/octet-stream',
246 def object_getattr(self, container, object, fh=None):
247 meta = self._get_object_meta(container, object)
248 mode = int(meta.get('x-object-meta-mode', 0644))
249 modified = parse_http_date(meta['last-modified'])
250 uid = int(meta.get('x-account-meta-uid', 0))
251 gid = int(meta.get('x-account-meta-gid', 0))
252 size = int(meta['content-length'])
254 if meta['content-type'] == 'application/directory':
262 'st_mode': flags | mode,
267 'st_mtime': modified,
268 'st_atime': modified,
271 def object_getxattr(self, container, object, name, position=0):
272 meta = self._get_object_meta(container, object, restricted=True)
273 return meta.get('xattr-' + name, '')
275 def object_listxattr(self, container, object):
276 meta = self._get_object_meta(container, object, restricted=True)
278 return [k[len(prefix):] for k in meta if k.startswith(prefix)]
280 def object_mkdir(self, container, object, mode):
281 mode = str(mode & 0777)
282 self.client.create_directory_marker(container, object)
283 self.client.update_object_metadata(container, object, mode=mode)
285 def object_read(self, container, object, nbyte, offset, fh):
286 data = self.client.retrieve_object(container, object)
287 return data[offset:offset + nbyte]
289 def object_readdir(self, container, object, fh):
290 params = {'delimiter': '/', 'prefix': object}
291 objects = self.client.list_objects(container, params=params)
292 files = [o.rpartition('/')[2] for o in objects if not o.endswith('/')]
293 return ['.', '..'] + files
295 def object_removexattr(self, container, object, name):
296 attr = 'xattr-' + name
297 self.client.delete_object_metadata(container, object, [attr])
299 def object_rename(self, container, object, path):
300 new_container, sep, new_object = path[1:].partition('/')
301 if not new_container or not new_object:
302 raise FuseOSError(EINVAL)
303 self.client.move_object(container, object, new_container, new_object)
305 def object_rmdir(self, container, object):
306 self.client.delete_object(container, object)
308 def object_setxattr(self, container, object, name, value, options,
310 attr = 'xattr-' + name
312 self.client.update_object_metadata(container, object, **meta)
314 def object_truncate(self, container, object, length, fh=None):
315 data = self.client.retrieve_object(container, object)
316 f = StringIO(data[:length])
317 self.client.update_object(container, object, f)
319 def object_unlink(self, container, object):
320 self.client.delete_object(container, object)
322 def object_write(self, container, object, data, offset, fh):
324 self.client.update_object(container, object, f, offset=offset)
328 if __name__ == "__main__":
330 print 'usage: %s <user> <token> <mountpoint>' % argv[0]
334 fs = StoreFS(argv[1], argv[2], verbose=True)
335 fuse = FUSE(fs, argv[3], foreground=True)