Add rollback code when writing the index file fails
This commit is contained in:
parent
37c47591ba
commit
6a9ef541ab
141
gpgfs.py
141
gpgfs.py
|
@ -18,12 +18,17 @@ magic = 'GPGFS1\n'
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
def decrypt(gpg, path):
|
def decrypt(gpg, path):
|
||||||
data = file(path).read()
|
try:
|
||||||
|
data = file(path).read()
|
||||||
|
except IOError, err:
|
||||||
|
log.error("read failed: %s: %s", path, str(err))
|
||||||
|
raise FuseOSError(err.errno)
|
||||||
if not data:
|
if not data:
|
||||||
return data
|
return data
|
||||||
res = gpg.decrypt(data)
|
res = gpg.decrypt(data)
|
||||||
if not res.ok:
|
if not res.ok:
|
||||||
raise IOError, "decryption failed, %s" % path
|
log.error("decryption failed, %s: %s", res.status, path)
|
||||||
|
raise FuseOSError(errno.EIO)
|
||||||
data = zlib.decompress(res.data)
|
data = zlib.decompress(res.data)
|
||||||
log.debug('decrypted %s' % path)
|
log.debug('decrypted %s' % path)
|
||||||
return data
|
return data
|
||||||
|
@ -32,9 +37,19 @@ def encrypt(gpg, keyid, path, data):
|
||||||
data = zlib.compress(data, 1)
|
data = zlib.compress(data, 1)
|
||||||
res = gpg.encrypt(data, keyid, armor=False)
|
res = gpg.encrypt(data, keyid, armor=False)
|
||||||
if not res.ok:
|
if not res.ok:
|
||||||
raise IOError, "encryption failed, keyid %s, path %s" % (keyid, path)
|
log.error("encryption failed (keyid %s), %s: %s",
|
||||||
with file(path, 'w') as fd:
|
keyid, res.status, path)
|
||||||
fd.write(res.data)
|
raise FuseOSError(errno.EIO)
|
||||||
|
try:
|
||||||
|
with file(path+'.tmp', 'w') as fd:
|
||||||
|
fd.write(res.data)
|
||||||
|
os.rename(path+'.tmp', path)
|
||||||
|
except IOError, err:
|
||||||
|
log.error("write failed: %s: %s", path, str(err))
|
||||||
|
raise FuseOSError(err.errno)
|
||||||
|
finally:
|
||||||
|
try: os.remove(path+'.tmp')
|
||||||
|
except: pass
|
||||||
log.debug('encrypted %s' % path)
|
log.debug('encrypted %s' % path)
|
||||||
|
|
||||||
class Entry:
|
class Entry:
|
||||||
|
@ -168,8 +183,8 @@ class GpgFs(LoggingMixIn, Operations):
|
||||||
self.fd = 0
|
self.fd = 0
|
||||||
self._clear_write_cache()
|
self._clear_write_cache()
|
||||||
|
|
||||||
def _write_index(self):
|
def _write_index(self, suffix=''):
|
||||||
write_index(self.gpg, self.keyid, self.index_path, self.root)
|
write_index(self.gpg, self.keyid, self.index_path + suffix, self.root)
|
||||||
|
|
||||||
def _find(self, path, parent=False):
|
def _find(self, path, parent=False):
|
||||||
assert path.startswith('/')
|
assert path.startswith('/')
|
||||||
|
@ -199,8 +214,13 @@ class GpgFs(LoggingMixIn, Operations):
|
||||||
mode &= 0777
|
mode &= 0777
|
||||||
ent = self._find(path)
|
ent = self._find(path)
|
||||||
if ent.type == ENT_DIR:
|
if ent.type == ENT_DIR:
|
||||||
|
prev_mode = ent.st_mode
|
||||||
ent.st_mode = mode
|
ent.st_mode = mode
|
||||||
self._write_index()
|
try:
|
||||||
|
self._write_index()
|
||||||
|
except:
|
||||||
|
ent.st_mode = prev_mode
|
||||||
|
raise
|
||||||
else:
|
else:
|
||||||
encpath = self.encroot + '/' + ent.path
|
encpath = self.encroot + '/' + ent.path
|
||||||
os.chmod(encpath, mode)
|
os.chmod(encpath, mode)
|
||||||
|
@ -214,16 +234,24 @@ class GpgFs(LoggingMixIn, Operations):
|
||||||
dir, path = self._find(path, parent=True)
|
dir, path = self._find(path, parent=True)
|
||||||
if path in dir.children:
|
if path in dir.children:
|
||||||
raise FuseOSError(errno.EEXIST)
|
raise FuseOSError(errno.EEXIST)
|
||||||
dir.children[path] = Entry(type=ENT_FILE, path=encpath, st_size=0)
|
|
||||||
log.debug('new path %s => %s', path, encpath)
|
|
||||||
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)
|
||||||
fd = os.open(self.encroot + '/' + encpath,
|
fd = os.open(self.encroot + '/' + encpath,
|
||||||
os.O_WRONLY | os.O_CREAT, mode & 0777)
|
os.O_WRONLY | os.O_CREAT, mode & 0777)
|
||||||
os.close(fd)
|
os.close(fd)
|
||||||
|
prev_mtime = dir.st_mtime
|
||||||
|
dir.children[path] = Entry(type=ENT_FILE, path=encpath, st_size=0)
|
||||||
|
log.debug('new path %s => %s', path, encpath)
|
||||||
dir.st_mtime = int(time.time())
|
dir.st_mtime = int(time.time())
|
||||||
self._write_index()
|
try:
|
||||||
|
self._write_index()
|
||||||
|
except:
|
||||||
|
try: os.remove(self.encroot + '/' + encpath)
|
||||||
|
except: pass
|
||||||
|
del dir.children[path]
|
||||||
|
dir.st_mtime = prev_mtime
|
||||||
|
raise
|
||||||
self.fd += 1
|
self.fd += 1
|
||||||
return self.fd
|
return self.fd
|
||||||
|
|
||||||
|
@ -234,10 +262,19 @@ class GpgFs(LoggingMixIn, Operations):
|
||||||
ent = self._find(self.write_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)
|
|
||||||
ent.st_size = len(buf)
|
|
||||||
self._write_index()
|
|
||||||
self.write_buf = [buf]
|
self.write_buf = [buf]
|
||||||
|
encrypt(self.gpg, self.keyid, encpath+'.new', buf)
|
||||||
|
prev_size = ent.st_size
|
||||||
|
ent.st_size = len(buf)
|
||||||
|
try:
|
||||||
|
self._write_index(suffix='.new')
|
||||||
|
except:
|
||||||
|
os.remove(encpath+'.new')
|
||||||
|
ent.st_size = prev_size
|
||||||
|
raise
|
||||||
|
# FIXME renames cannot fail, right?
|
||||||
|
os.rename(encpath+'.new', encpath)
|
||||||
|
os.rename(self.index_path+'.new', self.index_path)
|
||||||
self.write_dirty = False
|
self.write_dirty = False
|
||||||
log.debug('flushed %d bytes to %s', len(buf), self.write_path)
|
log.debug('flushed %d bytes to %s', len(buf), self.write_path)
|
||||||
return 0
|
return 0
|
||||||
|
@ -266,12 +303,18 @@ class GpgFs(LoggingMixIn, Operations):
|
||||||
dir, path = self._find(path, parent=True)
|
dir, path = self._find(path, parent=True)
|
||||||
if path in dir.children:
|
if path in dir.children:
|
||||||
raise FuseOSError(errno.EEXIST)
|
raise FuseOSError(errno.EEXIST)
|
||||||
|
prev_mtime = dir.st_mtime
|
||||||
dir.children[path] = Entry(type=ENT_DIR, children={},
|
dir.children[path] = Entry(type=ENT_DIR, children={},
|
||||||
st_mode=(mode & 0777),
|
st_mode=(mode & 0777),
|
||||||
st_mtime=int(time.time()),
|
st_mtime=int(time.time()),
|
||||||
st_ctime=int(time.time()))
|
st_ctime=int(time.time()))
|
||||||
dir.st_mtime = int(time.time())
|
dir.st_mtime = int(time.time())
|
||||||
self._write_index()
|
try:
|
||||||
|
self._write_index()
|
||||||
|
except:
|
||||||
|
del dir.children[path]
|
||||||
|
dir.st_mtime = prev_mtime
|
||||||
|
raise
|
||||||
|
|
||||||
def open(self, path, flags):
|
def open(self, path, flags):
|
||||||
return 0
|
return 0
|
||||||
|
@ -301,15 +344,24 @@ class GpgFs(LoggingMixIn, Operations):
|
||||||
if old_name not in old_dir.children:
|
if old_name not in old_dir.children:
|
||||||
raise FuseOSError(errno.ENOENT)
|
raise FuseOSError(errno.ENOENT)
|
||||||
new_dir, new_name = self._find(new, parent=True)
|
new_dir, new_name = self._find(new, parent=True)
|
||||||
if new_name in new_dir.children:
|
prev_ent = new_dir.children.get(new_name)
|
||||||
ent = new_dir.children[new_name]
|
if prev_ent and prev_ent.type == ENT_DIR and prev_ent.children:
|
||||||
if ent.type == ENT_FILE:
|
raise FuseOSError(errno.ENOTEMPTY)
|
||||||
os.remove(self.encroot + '/' + ent.path)
|
prev_old_mtime = old_dir.st_mtime
|
||||||
elif ent.children:
|
prev_new_mtime = new_dir.st_mtime
|
||||||
raise FuseOSError(errno.ENOTEMPTY)
|
|
||||||
new_dir.children[new_name] = old_dir.children.pop(old_name)
|
new_dir.children[new_name] = old_dir.children.pop(old_name)
|
||||||
old_dir.st_mtime = new_dir.st_mtime = int(time.time())
|
old_dir.st_mtime = new_dir.st_mtime = int(time.time())
|
||||||
self._write_index()
|
try:
|
||||||
|
self._write_index()
|
||||||
|
except:
|
||||||
|
old_dir.children[old_name] = new_dir.children.pop(new_name)
|
||||||
|
if prev_ent:
|
||||||
|
new_dir.children[new_name] = prev_ent
|
||||||
|
old_dir.st_mtime = prev_old_mtime
|
||||||
|
new_dir.st_mtime = prev_new_mtime
|
||||||
|
raise
|
||||||
|
if prev_ent and prev_ent.type == ENT_FILE:
|
||||||
|
os.remove(self.encroot + '/' + prev_ent.path)
|
||||||
|
|
||||||
def rmdir(self, path):
|
def rmdir(self, path):
|
||||||
parent, path = self._find(path, parent=True)
|
parent, path = self._find(path, parent=True)
|
||||||
|
@ -320,9 +372,15 @@ class GpgFs(LoggingMixIn, Operations):
|
||||||
raise FuseOSError(errno.ENOTDIR)
|
raise FuseOSError(errno.ENOTDIR)
|
||||||
if ent.children:
|
if ent.children:
|
||||||
raise FuseOSError(errno.ENOTEMPTY)
|
raise FuseOSError(errno.ENOTEMPTY)
|
||||||
|
prev_mtime = parent.st_mtime
|
||||||
del parent.children[path]
|
del parent.children[path]
|
||||||
parent.st_mtime = int(time.time())
|
parent.st_mtime = int(time.time())
|
||||||
self._write_index()
|
try:
|
||||||
|
self._write_index()
|
||||||
|
except:
|
||||||
|
parent.children[path] = ent
|
||||||
|
parent.st_mtime = prev_mtime
|
||||||
|
raise
|
||||||
|
|
||||||
def setxattr(self, path, name, value, options, position = 0):
|
def setxattr(self, path, name, value, options, position = 0):
|
||||||
raise FuseOSError(errno.ENOSYS)
|
raise FuseOSError(errno.ENOSYS)
|
||||||
|
@ -339,14 +397,23 @@ class GpgFs(LoggingMixIn, Operations):
|
||||||
ent = self._find(path)
|
ent = self._find(path)
|
||||||
encpath = self.encroot + '/' + ent.path
|
encpath = self.encroot + '/' + ent.path
|
||||||
if length == 0:
|
if length == 0:
|
||||||
with open(encpath, 'r+') as f:
|
with open(encpath+'.new', 'w') as f:
|
||||||
f.truncate(0)
|
pass
|
||||||
else:
|
else:
|
||||||
buf = decrypt(self.gpg, encpath)
|
buf = decrypt(self.gpg, encpath)
|
||||||
buf = buf[:length]
|
buf = buf[:length]
|
||||||
encrypt(self.gpg, self.keyid, encpath, buf)
|
encrypt(self.gpg, self.keyid, encpath+'.new', buf)
|
||||||
|
prev_size = ent.st_size
|
||||||
ent.st_size = length
|
ent.st_size = length
|
||||||
self._write_index()
|
try:
|
||||||
|
self._write_index(suffix='.new')
|
||||||
|
except:
|
||||||
|
ent.st_size = prev_size
|
||||||
|
os.remove(encpath+'.new')
|
||||||
|
raise
|
||||||
|
# FIXME renames cannot fail, right?
|
||||||
|
os.rename(encpath+'.new', encpath)
|
||||||
|
os.rename(self.index_path+'.new', self.index_path)
|
||||||
|
|
||||||
def unlink(self, path):
|
def unlink(self, path):
|
||||||
if self.write_path == path:
|
if self.write_path == path:
|
||||||
|
@ -355,20 +422,32 @@ class GpgFs(LoggingMixIn, Operations):
|
||||||
dir, name = self._find(path, parent=True)
|
dir, name = self._find(path, parent=True)
|
||||||
if name not in dir.children:
|
if name not in dir.children:
|
||||||
raise FuseOSError(errno.ENOENT)
|
raise FuseOSError(errno.ENOENT)
|
||||||
encpath = self.encroot + '/' + dir.children[name].path
|
ent = dir.children[name]
|
||||||
os.remove(encpath)
|
encpath = self.encroot + '/' + ent.path
|
||||||
del dir.children[name]
|
del dir.children[name]
|
||||||
|
prev_mtime = dir.st_mtime
|
||||||
dir.st_mtime = int(time.time())
|
dir.st_mtime = int(time.time())
|
||||||
self._write_index()
|
try:
|
||||||
|
self._write_index()
|
||||||
|
except:
|
||||||
|
dir.children[name] = ent
|
||||||
|
dir.st_mtime = prev_mtime
|
||||||
|
raise
|
||||||
|
os.remove(encpath)
|
||||||
|
|
||||||
def utimens(self, path, times = None):
|
def utimens(self, path, times = None):
|
||||||
ent = self._find(path)
|
ent = self._find(path)
|
||||||
if ent.type == ENT_DIR:
|
if ent.type == ENT_DIR:
|
||||||
|
prev_mtime = ent.st_mtime
|
||||||
if times is None:
|
if times is None:
|
||||||
ent.st_mtime = int(time.time())
|
ent.st_mtime = int(time.time())
|
||||||
else:
|
else:
|
||||||
ent.st_mtime = times[1]
|
ent.st_mtime = times[1]
|
||||||
self._write_index()
|
try:
|
||||||
|
self._write_index()
|
||||||
|
except:
|
||||||
|
ent.st_mtime = prev_mtime
|
||||||
|
raise
|
||||||
else:
|
else:
|
||||||
# flush may mess with mtime
|
# flush may mess with mtime
|
||||||
self.flush(path, 0)
|
self.flush(path, 0)
|
||||||
|
|
Loading…
Reference in New Issue