Statistics
| Branch: | Tag: | Revision:

root / tools / backendfs.py @ d1589d7c

History | View | Annotate | Download (6 kB)

1
#!/usr/bin/env python
2

    
3
from django.core.management import setup_environ
4
from pithos import settings
5
setup_environ(settings)
6

    
7
from functools import partial
8
from getpass import getuser
9
from errno import EACCES, EBADF, EINVAL, EISDIR, ENOENT, ENOTDIR, ENOTEMPTY
10
from stat import S_IFDIR, S_IFREG
11
from sys import argv
12
from time import time
13

    
14
from pithos.api.util import hashmap_hash
15
from pithos.backends import backend
16
from pithos.lib.fuse import FUSE, FuseOSError, Operations, LoggingMixIn
17

    
18

    
19
epoch = int(time())
20

    
21

    
22
class BackendProxy(object):
23
    """A proxy object that always passes user and account as first args."""
24
    
25
    def __init__(self, backend, user, account):
26
        self.backend = backend
27
        self.user = user
28
        self.account = account
29
    
30
    def __getattr__(self, name):
31
        func = getattr(self.backend, name)
32
        return partial(func, self.user, self.account)
33

    
34

    
35
def blocksplit(data, blocksize):
36
    """An iterator that splits data into blocks of size `blocksize`."""
37
    
38
    while data:
39
        yield data[:blocksize]
40
        data = data[blocksize:]
41

    
42

    
43
class BackendFS(LoggingMixIn, Operations):
44
    def __init__(self, account):
45
        self.user = account
46
        self.account = account
47
        self.backend = BackendProxy(backend, self.user, self.account)
48
    
49
    def create(self, path, mode, fi=None):
50
        container, sep, object = path[1:].partition('/')
51
        if not object:
52
            raise FuseOSError(EACCES)
53
        
54
        hashmap = []
55
        meta = {'hash': hashmap_hash(hashmap)}
56
        self.backend.update_object_hashmap(container, object, 0, hashmap,
57
                                            meta, True)
58
        return 0
59
    
60
    def getattr(self, path, fh=None):
61
        container, sep, object = path[1:].partition('/')
62
        if not container:
63
            # Root level
64
            containers = self.backend.list_containers()
65
            return {
66
                'st_mode': (S_IFDIR | 0755),
67
                'st_ctime': epoch,
68
                'st_mtime': epoch,
69
                'st_atime': epoch,
70
                'st_nlink': 2 + len(containers)}
71
        elif not object:
72
            # Container level
73
            try:
74
                meta = self.backend.get_container_meta(container)
75
            except NameError:
76
                raise FuseOSError(ENOENT)
77
            
78
            return {
79
                'st_mode': (S_IFDIR | 0755),
80
                'st_ctime': epoch,
81
                'st_mtime': meta['modified'],
82
                'st_atime': meta['modified'],
83
                'st_nlink': 2 + meta['count']}
84
        else:
85
            # Object level
86
            try:
87
                meta = self.backend.get_object_meta(container, object)
88
            except NameError:
89
                raise FuseOSError(ENOENT)
90
            
91
            return {
92
                'st_mode': (S_IFREG | 0644),
93
                'st_ctime': epoch,
94
                'st_mtime': meta['modified'],
95
                'st_atime': meta['modified'],
96
                'st_nlink': 1,
97
                'st_size': meta['bytes']}
98
    
99
    def mkdir(self, path, mode):
100
        container, sep, object = path[1:].partition('/')
101
        if object:
102
            raise FuseOSError(EACCES)
103
        backend.put_container(self.user, self.account, container)
104
    
105
    def read(self, path, nbyte, offset, fh):
106
        container, sep, object = path[1:].partition('/')
107
        if not object:
108
            raise FuseOSError(EBADF)
109
        
110
        # XXX This implementation is inefficient,
111
        # it always reads all the blocks
112
        size, hashmap = self.backend.get_object_hashmap(container, object)
113
        buf = []
114
        for hash in hashmap:
115
            buf.append(backend.get_block(hash))
116
        data = ''.join(buf)[:size]
117
        return data[offset:offset + nbyte]
118
    
119
    def readdir(self, path, fh):
120
        container, sep, object = path[1:].partition('/')
121
        if not container:
122
            # Root level
123
            containers = [c[0] for c in self.backend.list_containers()]
124
            return ['.', '..'] + containers
125
        else:
126
            # Container level
127
            objects = [o[0] for o in self.backend.list_objects(container)]
128
            return ['.', '..'] + objects
129
    
130
    def rmdir(self, path):
131
        container, sep, object = path[1:].partition('/')
132
        if object:
133
            raise FuseOSError(ENOTDIR)
134
        
135
        try:
136
            self.backend.delete_container(container)
137
        except NameError:
138
            raise FuseOSError(ENOENT)
139
        except IndexError:
140
            raise FuseOSError(ENOTEMPTY)
141
    
142
    def truncate(self, path, length, fh=None):
143
        container, sep, object = path[1:].partition('/')
144
        if not object:
145
            raise FuseOSError(EISDIR)
146
        
147
        size, hashmap = self.backend.get_object_hashmap(container, object)
148
        if length > size:
149
            raise FuseOSError(EINVAL)   # Extension not supported
150
        
151
        div, mod = divmod(size, backend.block_size)
152
        nblocks = div + 1 if mod else div
153
        meta = {'hash': hashmap_hash(hashmap)}
154
        self.backend.update_object_hashmap(container, object, size,
155
                                            hashmap[:nblocks], meta, True)
156
    
157
    def unlink(self, path):
158
        container, sep, object = path[1:].partition('/')
159
        if not object:
160
            raise FuseOSError(EACCES)
161
        self.backend.delete_object(container, object)
162
    
163
    def write(self, path, data, offset, fh):
164
        container, sep, object = path[1:].partition('/')
165
        if not object:
166
            raise FuseOSError(EBADF)
167
        
168
        hashmap = []
169
        for block in blocksplit(data, backend.block_size):
170
            hashmap.append(backend.put_block(block))
171
        meta = {'hash': hashmap_hash(hashmap)}
172
        self.backend.update_object_hashmap(container, object, len(data),
173
                                            hashmap, meta, True)
174
        return len(data)
175

    
176

    
177
if __name__ == "__main__":
178
    if len(argv) != 2:
179
        print 'usage: %s <mountpoint>' % argv[0]
180
        exit(1)
181
    account = getuser()
182
    fuse = FUSE(BackendFS(account), argv[1], foreground=True)