Statistics
| Branch: | Tag: | Revision:

root / pithos / tools / pithos-fs @ 93abb8b2

History | View | Annotate | Download (12.2 kB)

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)