forked from qznc/kvfs
-
Notifications
You must be signed in to change notification settings - Fork 0
/
kvfs.py
180 lines (160 loc) · 4.56 KB
/
kvfs.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
from blobtree import BlobTree
import errno
import cPickle as pickle
import stat
import time
import os
class _MetaData(dict):
"""A dict that serializes to a pickled string"""
def __init__(self, data=None):
dict.__init__(self)
if data:
# load pickled data
for key, val in pickle.loads(data).items():
self[key] = val
else:
# initialize defaults
self.update({
"st_mode": 0600,
"st_size": 4096,
"st_atime": int(time.time()),
"st_mtime": int(time.time()),
"st_ctime": int(time.time()),
})
def __getattr__(self, name):
if name.startswith("__"):
raise AttributeError()
try:
return self[name]
except KeyError:
print "no meta data:", name
def __str__(self):
return pickle.dumps(self)
def _raise_io(number, path=None):
# behave according to Fuse
msg = os.strerror(number)
if path:
msg = path+": "+msg
err = IOError(msg)
err.errno = number
raise err
class KVFS:
"""A Key-Value-File-System
This class is initialized with a key value store
and implements a file system on top of it,
providing methods like mkdir, create, read, write, ...
In a failure case IOError gets raised.
Some features like permissions or hardlinks are not yet supported."""
def __init__(self, kv_store):
self._bt = BlobTree(kv_store)
m = _MetaData()
m['st_mode'] = m['st_mode'] | stat.S_IFDIR
self.root_meta = m
def getattr(self, path):
"""returns the attributes of the object at `path`."""
if path == "/":
return self.root_meta
try:
return _MetaData(self._bt.get_meta_data(path))
except KeyError:
_raise_io(errno.ENOENT, path)
def setattr(self, path, attr):
"""sets the attributes of the object at `path`."""
if path == "/":
_raise_io(errno.EPERM, path)
try:
self._bt.set_meta_data(path, attr)
except (KeyError, IndexError):
_raise_io(errno.ENOENT, path)
def create(self, path):
"""create a file"""
if self._bt.exists(path):
_raise_io(errno.EEXIST, path)
m = _MetaData()
m['st_mode'] = m['st_mode'] | stat.S_IFREG
m['st_size'] = 0
self._bt.create_data(path, str(m))
def mkdir(self, path):
"""creates a directory"""
if self._bt.exists(path):
_raise_io(errno.EEXIST, path)
m = _MetaData()
m['st_mode'] = m['st_mode'] | stat.S_IFDIR
self._bt.create_subtree(path, str(m))
def readdir(self, path):
"""read contents of a directory"""
try:
files = self._bt.list_dir(path)
except KeyError:
_raise_io(errno.ENOENT, path)
yield '.'
yield '..'
for f in files:
yield f
def readlink(self, path):
"""resolves a symbolic link"""
# TODO recursive?
meta = self.getattr(path)
try:
return meta['symlink']
except KeyError:
_raise_io(errno.ENOLINK, path)
def symlink(self, target, name):
"""create a symbolic link target->name"""
if self._bt.exists(target):
_raise_io(errno.EEXIST, target)
m = _MetaData()
# use attributes to save target and link property
m['symlink'] = name
m['st_mode'] = m['st_mode'] | stat.S_IFLNK
self._bt.create_data(target, str(m))
def remove(self, path):
"""removes a file or directory"""
try:
self._bt.unlink(path)
except KeyError:
_raise_io(errno.ENOENT, path)
def rename(self, old, new):
"""rename a file (note that directories may change)"""
try:
self._bt.rename(old, new)
except KeyError:
_raise_io(errno.ENOENT, old)
def link(self, target, name):
"""create a hardlink"""
self._bt.create_data(target, self.getattr(name))
self._bt.set_data(target, self._bt.get_data(name))
# FIXME this is a copy, not a hardlink!
# Subsequent changes won't be applied.
# A transparent link blob type would be needed,
# but that should rather be called a symlink.
def _get_data(self, path):
"""get data from path or raise IOERROR"""
try:
return self._bt.get_data(path)
except KeyError:
_raise_io(errno.ENOENT, path)
except TypeError:
_raise_io(errno.EISDIR, path)
def read(self, path, length=2000000000, offset=0):
"""read data from a file"""
data = self._get_data(path)
return data[offset:offset+length]
def write(self, path, buf, offset=0):
"""write data to a file"""
meta = self.getattr(path)
if offset==0 and len(buf) >= meta['st_size']:
data = buf
else:
data = self._get_data(path)
data = data[:offset] + buf + data[offset+len(buf):]
meta['st_mtime'] = time.time()
meta['st_size'] = len(data)
self._bt.set_data(path, data, str(meta))
def flush(self, path="/"):
"""clear all buffers, finish all pending operations"""
self._bt.flush()
def truncate(self, path, length):
"""truncate file to given length"""
data = self._get_data(path)
self._bt.set_data(path, data[:length])