Merge branch 'master' of https://code.grnet.gr/git/pithos
[pithos] / tools / pithos-fs
1 #!/usr/bin/env python
2
3 # Copyright 2011 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 lib.compat import parse_http_date
45
46 from lib.client import OOS_Client, Fault
47 from lib.fuse import FUSE, FuseOSError, Operations
48 from lib.util import get_user, get_auth, get_server
49
50
51 epoch = int(time())
52
53
54 class StoreFS(Operations):
55     def __init__(self, verbose=False):
56         self.verbose = verbose
57         self.client = OOS_Client(get_server(), get_auth(), get_user())
58     
59     def __call__(self, op, path, *args):
60         container, sep, object = path[1:].partition('/')
61         if self.verbose:
62             data = repr(args)[:100]
63             print '-> %s %r %r %r' % (op, container, object, data)
64         ret = '[Unhandled Exception]'
65         
66         try:
67             if object:
68                 func = getattr(self, 'object_' + op, None)
69                 funcargs = (container, object) + args
70             elif container:
71                 func = getattr(self, 'container_' + op, None)
72                 funcargs = (container,) + args
73             else:
74                 func = getattr(self, 'account_' + op, None)
75                 funcargs = args
76
77             if not func:
78                 # Fallback to defaults
79                 func = getattr(self, op)
80                 funcargs = (path,) + args
81             
82             ret = func(*funcargs)
83             return ret
84         except FuseOSError, e:
85             ret = str(e)
86             raise
87         finally:
88             if self.verbose:
89                 print '<-', op, repr(ret)
90     
91     
92     def _get_container_meta(self, container, **kwargs):
93         try:
94             return self.client.retrieve_container_metadata(container, **kwargs)
95         except Fault:
96             raise FuseOSError(ENOENT)
97     
98     def _get_object_meta(self, container, object, **kwargs):
99         try:
100             return self.client.retrieve_object_metadata(container, object,
101                                                         **kwargs)
102         except Fault:
103             raise FuseOSError(ENOENT)
104     
105     
106     # Global
107     
108     def statfs(self, path):
109         return dict(f_bsize=1024, f_blocks=1024**2, f_bfree=1024**2,
110                     f_bavail=1024**2)
111     
112     
113     # Account Level
114     
115     def account_chmod(self, mode):
116         self.client.update_account_metadata(mode=str(mode))
117     
118     def account_chown(self, uid, gid):
119         self.client.update_account_metadata(uid=uid, gid=gid)
120     
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))
129         
130         return {
131             'st_mode': S_IFDIR | mode,
132             'st_nlink': 2 + count,
133             'st_uid': uid,
134             'st_gid': gid,
135             'st_ctime': epoch,
136             'st_mtime': modified,
137             'st_atime': modified}
138     
139     def account_getxattr(self, name, position=0):
140         meta = self.client.retrieve_account_metadata(restricted=True)
141         return meta.get('xattr-' + name, '')
142     
143     def account_listxattr(self):
144         meta = self.client.retrieve_account_metadata(restricted=True)
145         prefix = 'xattr-'
146         return [k[len(prefix):] for k in meta if k.startswith(prefix)]
147     
148     def account_readdir(self, fh):
149         return ['.', '..'] + self.client.list_containers() or []
150     
151     def account_removexattr(self, name):
152         attr = 'xattr-' + name
153         self.client.delete_account_metadata([attr])
154     
155     def account_setxattr(self, name, value, options, position=0):
156         attr = 'xattr-' + name
157         meta = {attr: value}
158         self.client.update_account_metadata(**meta)
159     
160     
161     # Container Level
162     
163     def container_chmod(self, container, mode):
164         self.client.update_container_metadata(container, mode=str(mode))
165     
166     def container_chown(self, container, uid, gid):
167         self.client.update_container_metadata(container, uid=uid, gid=gid)
168     
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))
176         
177         return {
178             'st_mode': S_IFDIR | mode,
179             'st_nlink': 2 + count,
180             'st_uid': uid,
181             'st_gid': gid,
182             'st_ctime': epoch,
183             'st_mtime': modified,
184             'st_atime': modified}
185     
186     def container_getxattr(self, container, name, position=0):
187         meta = self._get_container_meta(container)
188         return meta.get('xattr-' + name, '')
189     
190     def container_listxattr(self, container):
191         meta = self._get_container_meta(container, restricted=True)
192         prefix = 'xattr-'
193         return [k[len(prefix):] for k in meta if k.startswith(prefix)]
194     
195     def container_mkdir(self, container, mode):
196         mode = str(mode & 0777)
197         self.client.create_container(container, mode=mode)
198     
199     def container_readdir(self, container, fh):
200         objects = self.client.list_objects(container, delimiter='/', prefix='')
201         files = [o for o in objects if not o.endswith('/')]
202         return ['.', '..'] + files
203     
204     def container_removexattr(self, container, name):
205         attr = 'xattr-' + name
206         self.client.delete_container_metadata(container, [attr])
207     
208     def container_rename(self, container, path):
209         new_container, sep, new_object = path[1:].partition('/')
210         if not new_container or new_object:
211             raise FuseOSError(EINVAL)
212         self.client.delete_container(container)
213         self.client.create_container(new_container)
214     
215     def container_rmdir(self, container):
216         try:
217             self.client.delete_container(container)
218         except Fault:
219             raise FuseOSError(ENOENT)
220     
221     def container_setxattr(self, container, name, value, options, position=0):
222         attr = 'xattr-' + name
223         meta = {attr: value}
224         self.client.update_container_metadata(container, **meta)
225     
226     
227     # Object Level
228     
229     def object_chmod(self, container, object, mode):
230         self.client.update_object_metadata(container, object, mode=str(mode))
231     
232     def object_chown(self, container, uid, gid):
233         self.client.update_object_metadata(container, object,
234                                             uid=str(uid), gid=str(gid))
235     
236     def object_create(self, container, object, mode, fi=None):
237         mode &= 0777
238         self.client.create_object(container, object,
239                                     f=None,
240                                     content_type='application/octet-stream',
241                                     mode=str(mode))
242         return 0
243     
244     def object_getattr(self, container, object, fh=None):
245         meta = self._get_object_meta(container, object)
246         modified = parse_http_date(meta['last-modified'])
247         uid = int(meta.get('x-account-meta-uid', 0))
248         gid = int(meta.get('x-account-meta-gid', 0))
249         size = int(meta.get('content-length', 0))
250         
251         if meta['content-type'] == 'application/directory':
252             mode = int(meta.get('x-object-meta-mode', 0755))
253             flags = S_IFDIR
254             nlink = 2
255         else:
256             mode = int(meta.get('x-object-meta-mode', 0644))
257             flags = S_IFREG
258             nlink = 1
259         
260         return {
261             'st_mode': flags | mode,
262             'st_nlink': nlink,
263             'st_uid': uid,
264             'st_gid': gid,
265             'st_ctime': epoch,
266             'st_mtime': modified,
267             'st_atime': modified,
268             'st_size': size}
269     
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, '')
273     
274     def object_listxattr(self, container, object):
275         meta = self._get_object_meta(container, object, restricted=True)
276         prefix = 'xattr-'
277         return [k[len(prefix):] for k in meta if k.startswith(prefix)]
278     
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)
283     
284     def object_read(self, container, object, nbyte, offset, fh):
285         data = self.client.retrieve_object(container, object)
286         return data[offset:offset + nbyte]
287     
288     def object_readdir(self, container, object, fh):
289         objects = self.client.list_objects(container, delimiter='/',
290                                             prefix=object)
291         files = [o.rpartition('/')[2] for o in objects if not o.endswith('/')]
292         return ['.', '..'] + files
293     
294     def object_removexattr(self, container, object, name):
295         attr = 'xattr-' + name
296         self.client.delete_object_metadata(container, object, [attr])
297     
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)
303     
304     def object_rmdir(self, container, object):
305         self.client.delete_object(container, object)
306     
307     def object_setxattr(self, container, object, name, value, options,
308                         position=0):
309         attr = 'xattr-' + name
310         meta = {attr: value}
311         self.client.update_object_metadata(container, object, **meta)
312     
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)
317     
318     def object_unlink(self, container, object):
319         self.client.delete_object(container, object)
320     
321     def object_write(self, container, object, data, offset, fh):
322         f = StringIO(data)
323         self.client.update_object(container, object, f, offset=offset)
324         return len(data)
325
326
327 if __name__ == '__main__':
328     if len(argv) != 2:
329         print 'usage: %s <mountpoint>' % argv[0]
330         exit(1)
331     
332     user = getuser()
333     fs = StoreFS(verbose=True)
334     fuse = FUSE(fs, argv[1], foreground=True)