I was working on indirecting version vectors, and that resulted in some refactoring and improving the existing block indirection stuff. We may or may not end up doing the version vector indirection, but I think these changes are reasonable anyhow and will simplify the diff significantly if we do go there. The main points are: - A bunch of renaming to make the indirection and GC not about "blocks" but about "indirection". - Adding a cutoff so that we don't actually indirect for small block lists. This gets us better performance when handling small files as it cuts out the indirection for quite small loss in space efficiency. - Being paranoid and always recalculating the hash on put. This costs some CPU, but the consequences if a buggy or malicious implementation silently substituted the block list by lying about the hash would be bad.
This commit is contained in:
+41
-33
@@ -78,30 +78,34 @@ func (t readOnlyTransaction) unmarshalTrunc(bs []byte, trunc bool) (FileIntf, er
|
||||
return tf, nil
|
||||
}
|
||||
|
||||
var tf protocol.FileInfo
|
||||
if err := tf.Unmarshal(bs); err != nil {
|
||||
var fi protocol.FileInfo
|
||||
if err := fi.Unmarshal(bs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := t.fillBlockList(&tf); err != nil {
|
||||
if err := t.fillFileInfo(&fi); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tf, nil
|
||||
return fi, nil
|
||||
}
|
||||
|
||||
func (t readOnlyTransaction) fillBlockList(fi *protocol.FileInfo) error {
|
||||
if len(fi.BlocksHash) == 0 {
|
||||
return nil
|
||||
// fillFileInfo follows the (possible) indirection of blocks and fills it out.
|
||||
func (t readOnlyTransaction) fillFileInfo(fi *protocol.FileInfo) error {
|
||||
var key []byte
|
||||
|
||||
if len(fi.Blocks) == 0 && len(fi.BlocksHash) != 0 {
|
||||
// The blocks list is indirected and we need to load it.
|
||||
key = t.keyer.GenerateBlockListKey(key, fi.BlocksHash)
|
||||
bs, err := t.Get(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var bl BlockList
|
||||
if err := bl.Unmarshal(bs); err != nil {
|
||||
return err
|
||||
}
|
||||
fi.Blocks = bl.Blocks
|
||||
}
|
||||
blocksKey := t.keyer.GenerateBlockListKey(nil, fi.BlocksHash)
|
||||
bs, err := t.Get(blocksKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var bl BlockList
|
||||
if err := bl.Unmarshal(bs); err != nil {
|
||||
return err
|
||||
}
|
||||
fi.Blocks = bl.Blocks
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -453,26 +457,33 @@ func (t readWriteTransaction) close() {
|
||||
t.WriteTransaction.Release()
|
||||
}
|
||||
|
||||
func (t readWriteTransaction) putFile(key []byte, fi protocol.FileInfo) error {
|
||||
if fi.Blocks != nil {
|
||||
if fi.BlocksHash == nil {
|
||||
fi.BlocksHash = protocol.BlocksHash(fi.Blocks)
|
||||
}
|
||||
blocksKey := t.keyer.GenerateBlockListKey(nil, fi.BlocksHash)
|
||||
if _, err := t.Get(blocksKey); backend.IsNotFound(err) {
|
||||
func (t readWriteTransaction) putFile(fkey []byte, fi protocol.FileInfo) error {
|
||||
var bkey []byte
|
||||
|
||||
// Always set the blocks hash when there are blocks.
|
||||
if len(fi.Blocks) > 0 {
|
||||
fi.BlocksHash = protocol.BlocksHash(fi.Blocks)
|
||||
} else {
|
||||
fi.BlocksHash = nil
|
||||
}
|
||||
|
||||
// Indirect the blocks if the block list is large enough.
|
||||
if len(fi.Blocks) > blocksIndirectionCutoff {
|
||||
bkey = t.keyer.GenerateBlockListKey(bkey, fi.BlocksHash)
|
||||
if _, err := t.Get(bkey); backend.IsNotFound(err) {
|
||||
// Marshal the block list and save it
|
||||
blocksBs := mustMarshal(&BlockList{Blocks: fi.Blocks})
|
||||
if err := t.Put(blocksKey, blocksBs); err != nil {
|
||||
if err := t.Put(bkey, blocksBs); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
fi.Blocks = nil
|
||||
}
|
||||
|
||||
fi.Blocks = nil
|
||||
fiBs := mustMarshal(&fi)
|
||||
return t.Put(key, fiBs)
|
||||
return t.Put(fkey, fiBs)
|
||||
}
|
||||
|
||||
// updateGlobal adds this device+version to the version list for the given
|
||||
@@ -723,15 +734,12 @@ func (t *readWriteTransaction) withAllFolderTruncated(folder []byte, fn func(dev
|
||||
}
|
||||
continue
|
||||
}
|
||||
var f FileInfoTruncated
|
||||
// The iterator function may keep a reference to the unmarshalled
|
||||
// struct, which in turn references the buffer it was unmarshalled
|
||||
// from. dbi.Value() just returns an internal slice that it reuses, so
|
||||
// we need to copy it.
|
||||
err := f.Unmarshal(append([]byte{}, dbi.Value()...))
|
||||
|
||||
intf, err := t.unmarshalTrunc(dbi.Value(), true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f := intf.(FileInfoTruncated)
|
||||
|
||||
switch f.Name {
|
||||
case "", ".", "..", "/": // A few obviously invalid filenames
|
||||
|
||||
Reference in New Issue
Block a user