3 # Copyright 2011-2012 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.lib.compat import parse_http_date
45 from pithos.lib.client import OOS_Client, Fault
46 from pithos.lib.fuse import FUSE, FuseOSError, Operations
47 from pithos.lib.util import get_user, get_auth, get_url
53 class StoreFS(Operations):
54 def __init__(self, verbose=False):
55 self.verbose = verbose
56 self.client = OOS_Client(get_url(), get_auth(), get_user())
58 def __call__(self, op, path, *args):
59 container, sep, object = path[1:].partition('/')
61 data = repr(args)[:100]
62 print '-> %s %r %r %r' % (op, container, object, data)
63 ret = '[Unhandled Exception]'
67 func = getattr(self, 'object_' + op, None)
68 funcargs = (container, object) + args
70 func = getattr(self, 'container_' + op, None)
71 funcargs = (container,) + args
73 func = getattr(self, 'account_' + op, None)
77 # Fallback to defaults
78 func = getattr(self, op)
79 funcargs = (path,) + args
83 except FuseOSError, e:
88 print '<-', op, repr(ret)
91 def _get_container_meta(self, container, **kwargs):
93 return self.client.retrieve_container_metadata(container, **kwargs)
95 raise FuseOSError(ENOENT)
97 def _get_object_meta(self, container, object, **kwargs):
99 return self.client.retrieve_object_metadata(container, object,
102 raise FuseOSError(ENOENT)
107 def statfs(self, path):
108 return dict(f_bsize=1024, f_blocks=1024**2, f_bfree=1024**2,
114 def account_chmod(self, mode):
115 self.client.update_account_metadata(mode=str(mode))
117 def account_chown(self, uid, gid):
118 self.client.update_account_metadata(uid=uid, gid=gid)
120 def account_getattr(self, fh=None):
121 meta = self.client.retrieve_account_metadata()
122 mode = int(meta.get('x-account-meta-mode', 0755))
123 last_modified = meta.get('last-modified', None)
124 modified = parse_http_date(last_modified) if last_modified else epoch
125 count = int(meta['x-account-container-count'])
126 uid = int(meta.get('x-account-meta-uid', 0))
127 gid = int(meta.get('x-account-meta-gid', 0))
130 'st_mode': S_IFDIR | mode,
131 'st_nlink': 2 + count,
135 'st_mtime': modified,
136 'st_atime': modified}
138 def account_getxattr(self, name, position=0):
139 meta = self.client.retrieve_account_metadata(restricted=True)
140 return meta.get('xattr-' + name, '')
142 def account_listxattr(self):
143 meta = self.client.retrieve_account_metadata(restricted=True)
145 return [k[len(prefix):] for k in meta if k.startswith(prefix)]
147 def account_readdir(self, fh):
148 return ['.', '..'] + self.client.list_containers() or []
150 def account_removexattr(self, name):
151 attr = 'xattr-' + name
152 self.client.delete_account_metadata([attr])
154 def account_setxattr(self, name, value, options, position=0):
155 attr = 'xattr-' + name
157 self.client.update_account_metadata(**meta)
162 def container_chmod(self, container, mode):
163 self.client.update_container_metadata(container, mode=str(mode))
165 def container_chown(self, container, uid, gid):
166 self.client.update_container_metadata(container, uid=uid, gid=gid)
168 def container_getattr(self, container, fh=None):
169 meta = self._get_container_meta(container)
170 mode = int(meta.get('x-container-meta-mode', 0755))
171 modified = parse_http_date(meta['last-modified'])
172 count = int(meta['x-container-object-count'])
173 uid = int(meta.get('x-account-meta-uid', 0))
174 gid = int(meta.get('x-account-meta-gid', 0))
177 'st_mode': S_IFDIR | mode,
178 'st_nlink': 2 + count,
182 'st_mtime': modified,
183 'st_atime': modified}
185 def container_getxattr(self, container, name, position=0):
186 meta = self._get_container_meta(container)
187 return meta.get('xattr-' + name, '')
189 def container_listxattr(self, container):
190 meta = self._get_container_meta(container, restricted=True)
192 return [k[len(prefix):] for k in meta if k.startswith(prefix)]
194 def container_mkdir(self, container, mode):
195 mode = str(mode & 0777)
196 self.client.create_container(container, mode=mode)
198 def container_readdir(self, container, fh):
199 objects = self.client.list_objects(container, delimiter='/', prefix='')
200 files = [o for o in objects if not o.endswith('/')]
201 return ['.', '..'] + files
203 def container_removexattr(self, container, name):
204 attr = 'xattr-' + name
205 self.client.delete_container_metadata(container, [attr])
207 def container_rename(self, container, path):
208 new_container, sep, new_object = path[1:].partition('/')
209 if not new_container or new_object:
210 raise FuseOSError(EINVAL)
211 self.client.delete_container(container)
212 self.client.create_container(new_container)
214 def container_rmdir(self, container):
216 self.client.delete_container(container)
218 raise FuseOSError(ENOENT)
220 def container_setxattr(self, container, name, value, options, position=0):
221 attr = 'xattr-' + name
223 self.client.update_container_metadata(container, **meta)
228 def object_chmod(self, container, object, mode):
229 self.client.update_object_metadata(container, object, mode=str(mode))
231 def object_chown(self, container, uid, gid):
232 self.client.update_object_metadata(container, object,
233 uid=str(uid), gid=str(gid))
235 def object_create(self, container, object, mode, fi=None):
237 self.client.create_object(container, object,
239 content_type='application/octet-stream',
243 def object_getattr(self, container, object, fh=None):
244 meta = self._get_object_meta(container, object)
245 modified = parse_http_date(meta['last-modified'])
246 uid = int(meta.get('x-account-meta-uid', 0))
247 gid = int(meta.get('x-account-meta-gid', 0))
248 size = int(meta.get('content-length', 0))
250 if meta['content-type'] == 'application/directory':
251 mode = int(meta.get('x-object-meta-mode', 0755))
255 mode = int(meta.get('x-object-meta-mode', 0644))
260 'st_mode': flags | mode,
265 'st_mtime': modified,
266 'st_atime': modified,
269 def object_getxattr(self, container, object, name, position=0):
270 meta = self._get_object_meta(container, object, restricted=True)
271 return meta.get('xattr-' + name, '')
273 def object_listxattr(self, container, object):
274 meta = self._get_object_meta(container, object, restricted=True)
276 return [k[len(prefix):] for k in meta if k.startswith(prefix)]
278 def object_mkdir(self, container, object, mode):
279 mode = str(mode & 0777)
280 self.client.create_directory_marker(container, object)
281 self.client.update_object_metadata(container, object, mode=mode)
283 def object_read(self, container, object, nbyte, offset, fh):
284 data = self.client.retrieve_object(container, object)
285 return data[offset:offset + nbyte]
287 def object_readdir(self, container, object, fh):
288 objects = self.client.list_objects(container, delimiter='/',
290 files = [o.rpartition('/')[2] for o in objects if not o.endswith('/')]
291 return ['.', '..'] + files
293 def object_removexattr(self, container, object, name):
294 attr = 'xattr-' + name
295 self.client.delete_object_metadata(container, object, [attr])
297 def object_rename(self, container, object, path):
298 new_container, sep, new_object = path[1:].partition('/')
299 if not new_container or not new_object:
300 raise FuseOSError(EINVAL)
301 self.client.move_object(container, object, new_container, new_object)
303 def object_rmdir(self, container, object):
304 self.client.delete_object(container, object)
306 def object_setxattr(self, container, object, name, value, options,
308 attr = 'xattr-' + name
310 self.client.update_object_metadata(container, object, **meta)
312 def object_truncate(self, container, object, length, fh=None):
313 data = self.client.retrieve_object(container, object)
314 f = StringIO(data[:length])
315 self.client.update_object(container, object, f)
317 def object_unlink(self, container, object):
318 self.client.delete_object(container, object)
320 def object_write(self, container, object, data, offset, fh):
322 self.client.update_object(container, object, f, offset=offset)
326 if __name__ == '__main__':
328 print 'usage: %s <mountpoint>' % argv[0]
332 fs = StoreFS(verbose=True)
333 fuse = FUSE(fs, argv[1], foreground=True)