Integrate with Astakos toolbar.
[pithos] / pithos / tools / pithos-fs
1 #!/usr/bin/env python
2
3 # Copyright 2011-2012 GRNET S.A. All rights reserved.
4
5 # Redistribution and use in source and binary forms, with or
6 # without modification, are permitted provided that the following
7 # conditions are met:
8
9 #   1. Redistributions of source code must retain the above
10 #      copyright notice, this list of conditions and the following
11 #      disclaimer.
12
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.
17
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.
30
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.
35
36 from cStringIO import StringIO
37 from errno import (EACCES, EBADF, EINVAL, EISDIR, EIO, ENOENT, ENOTDIR,
38                     ENOTEMPTY)
39 from getpass import getuser
40 from stat import S_IFDIR, S_IFREG
41 from sys import argv
42 from time import time
43
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
48
49
50 epoch = int(time())
51
52
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())
57     
58     def __call__(self, op, path, *args):
59         container, sep, object = path[1:].partition('/')
60         if self.verbose:
61             data = repr(args)[:100]
62             print '-> %s %r %r %r' % (op, container, object, data)
63         ret = '[Unhandled Exception]'
64         
65         try:
66             if object:
67                 func = getattr(self, 'object_' + op, None)
68                 funcargs = (container, object) + args
69             elif container:
70                 func = getattr(self, 'container_' + op, None)
71                 funcargs = (container,) + args
72             else:
73                 func = getattr(self, 'account_' + op, None)
74                 funcargs = args
75
76             if not func:
77                 # Fallback to defaults
78                 func = getattr(self, op)
79                 funcargs = (path,) + args
80             
81             ret = func(*funcargs)
82             return ret
83         except FuseOSError, e:
84             ret = str(e)
85             raise
86         finally:
87             if self.verbose:
88                 print '<-', op, repr(ret)
89     
90     
91     def _get_container_meta(self, container, **kwargs):
92         try:
93             return self.client.retrieve_container_metadata(container, **kwargs)
94         except Fault:
95             raise FuseOSError(ENOENT)
96     
97     def _get_object_meta(self, container, object, **kwargs):
98         try:
99             return self.client.retrieve_object_metadata(container, object,
100                                                         **kwargs)
101         except Fault:
102             raise FuseOSError(ENOENT)
103     
104     
105     # Global
106     
107     def statfs(self, path):
108         return dict(f_bsize=1024, f_blocks=1024**2, f_bfree=1024**2,
109                     f_bavail=1024**2)
110     
111     
112     # Account Level
113     
114     def account_chmod(self, mode):
115         self.client.update_account_metadata(mode=str(mode))
116     
117     def account_chown(self, uid, gid):
118         self.client.update_account_metadata(uid=uid, gid=gid)
119     
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))
128         
129         return {
130             'st_mode': S_IFDIR | mode,
131             'st_nlink': 2 + count,
132             'st_uid': uid,
133             'st_gid': gid,
134             'st_ctime': epoch,
135             'st_mtime': modified,
136             'st_atime': modified}
137     
138     def account_getxattr(self, name, position=0):
139         meta = self.client.retrieve_account_metadata(restricted=True)
140         return meta.get('xattr-' + name, '')
141     
142     def account_listxattr(self):
143         meta = self.client.retrieve_account_metadata(restricted=True)
144         prefix = 'xattr-'
145         return [k[len(prefix):] for k in meta if k.startswith(prefix)]
146     
147     def account_readdir(self, fh):
148         return ['.', '..'] + self.client.list_containers() or []
149     
150     def account_removexattr(self, name):
151         attr = 'xattr-' + name
152         self.client.delete_account_metadata([attr])
153     
154     def account_setxattr(self, name, value, options, position=0):
155         attr = 'xattr-' + name
156         meta = {attr: value}
157         self.client.update_account_metadata(**meta)
158     
159     
160     # Container Level
161     
162     def container_chmod(self, container, mode):
163         self.client.update_container_metadata(container, mode=str(mode))
164     
165     def container_chown(self, container, uid, gid):
166         self.client.update_container_metadata(container, uid=uid, gid=gid)
167     
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))
175         
176         return {
177             'st_mode': S_IFDIR | mode,
178             'st_nlink': 2 + count,
179             'st_uid': uid,
180             'st_gid': gid,
181             'st_ctime': epoch,
182             'st_mtime': modified,
183             'st_atime': modified}
184     
185     def container_getxattr(self, container, name, position=0):
186         meta = self._get_container_meta(container)
187         return meta.get('xattr-' + name, '')
188     
189     def container_listxattr(self, container):
190         meta = self._get_container_meta(container, restricted=True)
191         prefix = 'xattr-'
192         return [k[len(prefix):] for k in meta if k.startswith(prefix)]
193     
194     def container_mkdir(self, container, mode):
195         mode = str(mode & 0777)
196         self.client.create_container(container, mode=mode)
197     
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
202     
203     def container_removexattr(self, container, name):
204         attr = 'xattr-' + name
205         self.client.delete_container_metadata(container, [attr])
206     
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)
213     
214     def container_rmdir(self, container):
215         try:
216             self.client.delete_container(container)
217         except Fault:
218             raise FuseOSError(ENOENT)
219     
220     def container_setxattr(self, container, name, value, options, position=0):
221         attr = 'xattr-' + name
222         meta = {attr: value}
223         self.client.update_container_metadata(container, **meta)
224     
225     
226     # Object Level
227     
228     def object_chmod(self, container, object, mode):
229         self.client.update_object_metadata(container, object, mode=str(mode))
230     
231     def object_chown(self, container, uid, gid):
232         self.client.update_object_metadata(container, object,
233                                             uid=str(uid), gid=str(gid))
234     
235     def object_create(self, container, object, mode, fi=None):
236         mode &= 0777
237         self.client.create_object(container, object,
238                                     f=None,
239                                     content_type='application/octet-stream',
240                                     mode=str(mode))
241         return 0
242     
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))
249         
250         if meta['content-type'] == 'application/directory':
251             mode = int(meta.get('x-object-meta-mode', 0755))
252             flags = S_IFDIR
253             nlink = 2
254         else:
255             mode = int(meta.get('x-object-meta-mode', 0644))
256             flags = S_IFREG
257             nlink = 1
258         
259         return {
260             'st_mode': flags | mode,
261             'st_nlink': nlink,
262             'st_uid': uid,
263             'st_gid': gid,
264             'st_ctime': epoch,
265             'st_mtime': modified,
266             'st_atime': modified,
267             'st_size': size}
268     
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, '')
272     
273     def object_listxattr(self, container, object):
274         meta = self._get_object_meta(container, object, restricted=True)
275         prefix = 'xattr-'
276         return [k[len(prefix):] for k in meta if k.startswith(prefix)]
277     
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)
282     
283     def object_read(self, container, object, nbyte, offset, fh):
284         data = self.client.retrieve_object(container, object)
285         return data[offset:offset + nbyte]
286     
287     def object_readdir(self, container, object, fh):
288         objects = self.client.list_objects(container, delimiter='/',
289                                             prefix=object)
290         files = [o.rpartition('/')[2] for o in objects if not o.endswith('/')]
291         return ['.', '..'] + files
292     
293     def object_removexattr(self, container, object, name):
294         attr = 'xattr-' + name
295         self.client.delete_object_metadata(container, object, [attr])
296     
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)
302     
303     def object_rmdir(self, container, object):
304         self.client.delete_object(container, object)
305     
306     def object_setxattr(self, container, object, name, value, options,
307                         position=0):
308         attr = 'xattr-' + name
309         meta = {attr: value}
310         self.client.update_object_metadata(container, object, **meta)
311     
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)
316     
317     def object_unlink(self, container, object):
318         self.client.delete_object(container, object)
319     
320     def object_write(self, container, object, data, offset, fh):
321         f = StringIO(data)
322         self.client.update_object(container, object, f, offset=offset)
323         return len(data)
324
325
326 if __name__ == '__main__':
327     if len(argv) != 2:
328         print 'usage: %s <mountpoint>' % argv[0]
329         exit(1)
330     
331     user = getuser()
332     fs = StoreFS(verbose=True)
333     fuse = FUSE(fs, argv[1], foreground=True)