Fix write caching; implement more FUSE functions
* write caching was broken in multiple ways * implemented chmod, rename, truncate, unlink, and utimens
This commit is contained in:
parent
dd20a5a478
commit
fb9731868f
107
gpgfs.py
107
gpgfs.py
|
@ -25,6 +25,7 @@ def decrypt(gpg, path):
|
||||||
if not res.ok:
|
if not res.ok:
|
||||||
raise IOError, "decryption failed, %s" % path
|
raise IOError, "decryption failed, %s" % path
|
||||||
data = zlib.decompress(res.data)
|
data = zlib.decompress(res.data)
|
||||||
|
log.debug('decrypted %s' % path)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def encrypt(gpg, keyid, path, data):
|
def encrypt(gpg, keyid, path, data):
|
||||||
|
@ -34,6 +35,7 @@ def encrypt(gpg, keyid, path, data):
|
||||||
raise IOError, "encryption failed, keyid %s, path %s" % (keyid, path)
|
raise IOError, "encryption failed, keyid %s, path %s" % (keyid, path)
|
||||||
with file(path, 'w') as fd:
|
with file(path, 'w') as fd:
|
||||||
fd.write(res.data)
|
fd.write(res.data)
|
||||||
|
log.debug('encrypted %s' % path)
|
||||||
|
|
||||||
class Entry:
|
class Entry:
|
||||||
'''
|
'''
|
||||||
|
@ -79,7 +81,7 @@ def write_dict(fd, dct):
|
||||||
elif isinstance(val, Entry):
|
elif isinstance(val, Entry):
|
||||||
buf.write('E')
|
buf.write('E')
|
||||||
children.append(val)
|
children.append(val)
|
||||||
elif isinstance(val, int):
|
elif isinstance(val, (int, long)):
|
||||||
buf.write('I')
|
buf.write('I')
|
||||||
buf.write(struct.pack('<I', val))
|
buf.write(struct.pack('<I', val))
|
||||||
elif isinstance(val, str):
|
elif isinstance(val, str):
|
||||||
|
@ -89,7 +91,7 @@ def write_dict(fd, dct):
|
||||||
buf.write('U')
|
buf.write('U')
|
||||||
write_atom(buf, val.encode('utf8'))
|
write_atom(buf, val.encode('utf8'))
|
||||||
else:
|
else:
|
||||||
raise TypeError, val
|
raise TypeError, type(val)
|
||||||
write_atom(fd, buf.getvalue())
|
write_atom(fd, buf.getvalue())
|
||||||
for c in children:
|
for c in children:
|
||||||
write_dict(fd, c)
|
write_dict(fd, c)
|
||||||
|
@ -156,15 +158,13 @@ class GpgFs(LoggingMixIn, fuse.Operations):
|
||||||
self.root = read_index(self.gpg, self.index_path)
|
self.root = read_index(self.gpg, self.index_path)
|
||||||
else:
|
else:
|
||||||
self.root = Entry(type=ENT_DIR, children={},
|
self.root = Entry(type=ENT_DIR, children={},
|
||||||
|
st_mode=0755,
|
||||||
st_mtime=int(time.time()),
|
st_mtime=int(time.time()),
|
||||||
st_ctime=int(time.time()))
|
st_ctime=int(time.time()))
|
||||||
self._write_index()
|
self._write_index()
|
||||||
log.info('created %s', self.index_path)
|
log.info('created %s', self.index_path)
|
||||||
self.fd = 0
|
self.fd = 0
|
||||||
self.write_path = None
|
self._clear_write_cache()
|
||||||
self.write_buf = []
|
|
||||||
self.write_pos = 0
|
|
||||||
self.write_dirty = False
|
|
||||||
|
|
||||||
def _write_index(self):
|
def _write_index(self):
|
||||||
write_index(self.gpg, self.keyid, self.index_path, self.root)
|
write_index(self.gpg, self.keyid, self.index_path, self.root)
|
||||||
|
@ -181,8 +181,22 @@ class GpgFs(LoggingMixIn, fuse.Operations):
|
||||||
node = node.children[name]
|
node = node.children[name]
|
||||||
return node
|
return node
|
||||||
|
|
||||||
|
def _clear_write_cache(self):
|
||||||
|
self.write_path = None
|
||||||
|
self.write_buf = []
|
||||||
|
self.write_len = 0
|
||||||
|
self.write_dirty = False
|
||||||
|
|
||||||
def chmod(self, path, mode):
|
def chmod(self, path, mode):
|
||||||
raise fuse.FuseOSError(ENOSYS)
|
# sanitize mode (clear setuid/gid/sticky bits)
|
||||||
|
mode &= 0777
|
||||||
|
ent = self._find(path)
|
||||||
|
if ent.type == ENT_DIR:
|
||||||
|
ent.st_mode = mode
|
||||||
|
self._write_index()
|
||||||
|
else:
|
||||||
|
encpath = self.encroot + '/' + ent.path
|
||||||
|
os.chmod(encpath, mode)
|
||||||
|
|
||||||
def chown(self, path, uid, gid):
|
def chown(self, path, uid, gid):
|
||||||
raise fuse.FuseOSError(ENOSYS)
|
raise fuse.FuseOSError(ENOSYS)
|
||||||
|
@ -198,18 +212,18 @@ class GpgFs(LoggingMixIn, fuse.Operations):
|
||||||
encdir = self.encroot + '/' + encpath[:2]
|
encdir = self.encroot + '/' + encpath[:2]
|
||||||
if not os.path.exists(encdir):
|
if not os.path.exists(encdir):
|
||||||
os.mkdir(encdir, 0755)
|
os.mkdir(encdir, 0755)
|
||||||
with file(self.encroot + '/' + encpath, 'w'):
|
fd = os.open(self.encroot + '/' + encpath,
|
||||||
pass
|
os.O_WRONLY | os.O_CREAT, mode & 0777)
|
||||||
|
os.close(fd)
|
||||||
self._write_index()
|
self._write_index()
|
||||||
self.fd += 1
|
self.fd += 1
|
||||||
return self.fd
|
return self.fd
|
||||||
|
|
||||||
def flush(self, path, fh):
|
def flush(self, path, fh):
|
||||||
log.debug('flush %s', path)
|
if not self.write_dirty:
|
||||||
if not (path is not None and
|
log.debug('nothing to flush')
|
||||||
path == self.write_path and self.write_dirty):
|
|
||||||
return 0
|
return 0
|
||||||
ent = self._find(path)
|
ent = self._find(self.write_path)
|
||||||
encpath = self.encroot + '/' + ent.path
|
encpath = self.encroot + '/' + ent.path
|
||||||
buf = ''.join(self.write_buf)
|
buf = ''.join(self.write_buf)
|
||||||
encrypt(self.gpg, self.keyid, encpath, buf)
|
encrypt(self.gpg, self.keyid, encpath, buf)
|
||||||
|
@ -217,6 +231,7 @@ class GpgFs(LoggingMixIn, fuse.Operations):
|
||||||
self._write_index()
|
self._write_index()
|
||||||
self.write_buf = [buf]
|
self.write_buf = [buf]
|
||||||
self.write_dirty = False
|
self.write_dirty = False
|
||||||
|
log.debug('flushed %d bytes to %s', len(buf), self.write_path)
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
def getattr(self, path, fh = None):
|
def getattr(self, path, fh = None):
|
||||||
|
@ -225,9 +240,11 @@ class GpgFs(LoggingMixIn, fuse.Operations):
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise fuse.FuseOSError(ENOENT)
|
raise fuse.FuseOSError(ENOENT)
|
||||||
if ent.type == ENT_DIR:
|
if ent.type == ENT_DIR:
|
||||||
return dict(st_mode = stat.S_IFDIR | 0755, st_size = 0,
|
return dict(st_mode = stat.S_IFDIR | ent.st_mode, st_size = 0,
|
||||||
st_ctime = ent.st_ctime, st_mtime = ent.st_mtime,
|
st_ctime = ent.st_ctime, st_mtime = ent.st_mtime,
|
||||||
st_atime = 0, st_nlink = 3)
|
st_atime = 0, st_nlink = 3)
|
||||||
|
# ensure st_size is up-to-date
|
||||||
|
self.flush(path, 0)
|
||||||
encpath = self.encroot + '/' + ent.path
|
encpath = self.encroot + '/' + ent.path
|
||||||
s = os.stat(encpath)
|
s = os.stat(encpath)
|
||||||
return dict(st_mode = s.st_mode, st_size = ent.st_size,
|
return dict(st_mode = s.st_mode, st_size = ent.st_size,
|
||||||
|
@ -245,6 +262,7 @@ class GpgFs(LoggingMixIn, fuse.Operations):
|
||||||
path = path.rsplit('/', 1)[-1]
|
path = path.rsplit('/', 1)[-1]
|
||||||
assert path not in dir.children
|
assert path not in dir.children
|
||||||
dir.children[path] = Entry(type=ENT_DIR, children={},
|
dir.children[path] = Entry(type=ENT_DIR, children={},
|
||||||
|
st_mode=(mode & 0777),
|
||||||
st_mtime=int(time.time()),
|
st_mtime=int(time.time()),
|
||||||
st_ctime=int(time.time()))
|
st_ctime=int(time.time()))
|
||||||
self._write_index()
|
self._write_index()
|
||||||
|
@ -253,6 +271,7 @@ class GpgFs(LoggingMixIn, fuse.Operations):
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
def read(self, path, size, offset, fh):
|
def read(self, path, size, offset, fh):
|
||||||
|
self.flush(path, 0)
|
||||||
ent = self._find(path)
|
ent = self._find(path)
|
||||||
assert ent.type == ENT_FILE
|
assert ent.type == ENT_FILE
|
||||||
encpath = self.encroot + '/' + ent.path
|
encpath = self.encroot + '/' + ent.path
|
||||||
|
@ -270,7 +289,18 @@ class GpgFs(LoggingMixIn, fuse.Operations):
|
||||||
raise fuse.FuseOSError(ENOSYS)
|
raise fuse.FuseOSError(ENOSYS)
|
||||||
|
|
||||||
def rename(self, old, new):
|
def rename(self, old, new):
|
||||||
raise fuse.FuseOSError(ENOSYS)
|
self.flush(old, 0)
|
||||||
|
self._clear_write_cache()
|
||||||
|
old_dir = self._find(old, parent=True)
|
||||||
|
old_name = old.rsplit('/', 1)[-1]
|
||||||
|
new_dir = self._find(new, parent=True)
|
||||||
|
new_name = new.rsplit('/', 1)[-1]
|
||||||
|
if new_name in new_dir.children:
|
||||||
|
ent = new_dir.children[new_name]
|
||||||
|
if ent.type == ENT_FILE:
|
||||||
|
os.remove(self.encroot + '/' + ent.path)
|
||||||
|
new_dir.children[new_name] = old_dir.children.pop(old_name)
|
||||||
|
self._write_index()
|
||||||
|
|
||||||
def rmdir(self, path):
|
def rmdir(self, path):
|
||||||
raise fuse.FuseOSError(ENOSYS)
|
raise fuse.FuseOSError(ENOSYS)
|
||||||
|
@ -285,13 +315,44 @@ class GpgFs(LoggingMixIn, fuse.Operations):
|
||||||
raise fuse.FuseOSError(ENOSYS)
|
raise fuse.FuseOSError(ENOSYS)
|
||||||
|
|
||||||
def truncate(self, path, length, fh = None):
|
def truncate(self, path, length, fh = None):
|
||||||
raise fuse.FuseOSError(ENOSYS)
|
self.flush(path, 0)
|
||||||
|
self._clear_write_cache()
|
||||||
|
ent = self._find(path)
|
||||||
|
encpath = self.encroot + '/' + ent.path
|
||||||
|
if length == 0:
|
||||||
|
with open(encpath, 'r+') as f:
|
||||||
|
f.truncate(0)
|
||||||
|
else:
|
||||||
|
buf = decrypt(self.gpg, encpath)
|
||||||
|
buf = buf[:length]
|
||||||
|
encrypt(self.gpg, self.keyid, encpath, buf)
|
||||||
|
ent.st_size = length
|
||||||
|
self._write_index()
|
||||||
|
|
||||||
def unlink(self, path, times = None):
|
def unlink(self, path):
|
||||||
raise fuse.FuseOSError(ENOSYS)
|
if self.write_path == path:
|
||||||
|
# no need to flush afterwards
|
||||||
|
self._clear_write_cache()
|
||||||
|
dir = self._find(path, parent=True)
|
||||||
|
name = path.rsplit('/', 1)[-1]
|
||||||
|
encpath = self.encroot + '/' + dir.children[name].path
|
||||||
|
os.remove(encpath)
|
||||||
|
del dir.children[name]
|
||||||
|
self._write_index()
|
||||||
|
|
||||||
def utimens(self, path, times = None):
|
def utimens(self, path, times = None):
|
||||||
raise fuse.FuseOSError(ENOSYS)
|
ent = self._find(path)
|
||||||
|
if ent.type == ENT_DIR:
|
||||||
|
if times is None:
|
||||||
|
ent.st_mtime = int(time.time())
|
||||||
|
else:
|
||||||
|
ent.st_mtime = times[1]
|
||||||
|
self._write_index()
|
||||||
|
else:
|
||||||
|
# flush may mess with mtime
|
||||||
|
self.flush(path, 0)
|
||||||
|
encpath = self.encroot + '/' + ent.path
|
||||||
|
os.utime(encpath, times)
|
||||||
|
|
||||||
def write(self, path, data, offset, fh):
|
def write(self, path, data, offset, fh):
|
||||||
ent = self._find(path)
|
ent = self._find(path)
|
||||||
|
@ -300,16 +361,16 @@ class GpgFs(LoggingMixIn, fuse.Operations):
|
||||||
self.flush(self.write_path, None)
|
self.flush(self.write_path, None)
|
||||||
buf = decrypt(self.gpg, encpath)
|
buf = decrypt(self.gpg, encpath)
|
||||||
self.write_buf = [buf]
|
self.write_buf = [buf]
|
||||||
self.write_pos = len(buf)
|
self.write_len = len(buf)
|
||||||
self.write_path = path
|
self.write_path = path
|
||||||
if offset == self.write_pos:
|
if offset == self.write_len:
|
||||||
self.write_buf.append(data)
|
self.write_buf.append(data)
|
||||||
self.write_pos += len(data)
|
self.write_len += len(data)
|
||||||
else:
|
else:
|
||||||
buf = ''.join(self.write_buf)
|
buf = ''.join(self.write_buf)
|
||||||
buf = buf[:offset] + data + buf[offset + len(data):]
|
buf = buf[:offset] + data + buf[offset + len(data):]
|
||||||
self.write_buf = [buf]
|
self.write_buf = [buf]
|
||||||
self.write_pos = len(buf)
|
self.write_len = len(buf)
|
||||||
self.write_dirty = True
|
self.write_dirty = True
|
||||||
return len(data)
|
return len(data)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue