Ejemplo n.º 1
0
func ldbReplaceWithDelete(db *leveldb.DB, folder, device []byte, fs []protocol.FileInfo) uint64 {
	return ldbGenericReplace(db, folder, device, fs, func(db dbReader, batch dbWriter, folder, device, name []byte, dbi iterator.Iterator) uint64 {
		var tf protocol.FileInfoTruncated
		err := tf.UnmarshalXDR(dbi.Value())
		if err != nil {
			panic(err)
		}
		if !tf.IsDeleted() {
			if debugDB {
				l.Debugf("mark deleted; folder=%q device=%v name=%q", folder, protocol.DeviceIDFromBytes(device), name)
			}
			ts := clock(tf.LocalVersion)
			f := protocol.FileInfo{
				Name:         tf.Name,
				Version:      lamport.Default.Tick(tf.Version),
				LocalVersion: ts,
				Flags:        tf.Flags | protocol.FlagDeleted,
				Modified:     tf.Modified,
			}
			bs, _ := f.MarshalXDR()
			if debugDB {
				l.Debugf("batch.Put %p %x", batch, dbi.Key())
			}
			batch.Put(dbi.Key(), bs)
			ldbUpdateGlobal(db, batch, folder, device, deviceKeyName(dbi.Key()), f.Version)
			return ts
		}
		return 0
	})
}
Ejemplo n.º 2
0
func unmarshalTrunc(bs []byte, truncate bool) (protocol.FileIntf, error) {
	if truncate {
		var tf protocol.FileInfoTruncated
		err := tf.UnmarshalXDR(bs)
		return tf, err
	} else {
		var tf protocol.FileInfo
		err := tf.UnmarshalXDR(bs)
		return tf, err
	}
}
Ejemplo n.º 3
0
func ldbGetGlobal(db *leveldb.DB, folder, file []byte) protocol.FileInfo {
	k := globalKey(folder, file)
	snap, err := db.GetSnapshot()
	if err != nil {
		panic(err)
	}
	if debugDB {
		l.Debugf("created snapshot %p", snap)
	}
	defer func() {
		if debugDB {
			l.Debugf("close snapshot %p", snap)
		}
		snap.Release()
	}()

	if debugDB {
		l.Debugf("snap.Get %p %x", snap, k)
	}
	bs, err := snap.Get(k, nil)
	if err == leveldb.ErrNotFound {
		return protocol.FileInfo{}
	}
	if err != nil {
		panic(err)
	}

	var vl versionList
	err = vl.UnmarshalXDR(bs)
	if err != nil {
		panic(err)
	}
	if len(vl.versions) == 0 {
		l.Debugln(k)
		panic("no versions?")
	}

	k = deviceKey(folder, vl.versions[0].device, file)
	if debugDB {
		l.Debugf("snap.Get %p %x", snap, k)
	}
	bs, err = snap.Get(k, nil)
	if err != nil {
		panic(err)
	}

	var f protocol.FileInfo
	err = f.UnmarshalXDR(bs)
	if err != nil {
		panic(err)
	}
	return f
}
Ejemplo n.º 4
0
// Test that updating a file removes it's old blocks from the blockmap
func TestCopierCleanup(t *testing.T) {
	iterFn := func(folder, file string, index uint32) bool {
		return true
	}

	fcfg := config.FolderConfiguration{ID: "default", Path: "testdata"}
	cfg := config.Configuration{Folders: []config.FolderConfiguration{fcfg}}

	db, _ := leveldb.Open(storage.NewMemStorage(), nil)
	m := NewModel(config.Wrap("/tmp/test", cfg), "device", "syncthing", "dev", db)
	m.AddFolder(fcfg)

	// Create a file
	file := protocol.FileInfo{
		Name:     "test",
		Flags:    0,
		Modified: 0,
		Blocks:   []protocol.BlockInfo{blocks[0]},
	}

	// Add file to index
	m.updateLocal("default", file)

	if !m.finder.Iterate(blocks[0].Hash, iterFn) {
		t.Error("Expected block not found")
	}

	file.Blocks = []protocol.BlockInfo{blocks[1]}
	file.Version++
	// Update index (removing old blocks)
	m.updateLocal("default", file)

	if m.finder.Iterate(blocks[0].Hash, iterFn) {
		t.Error("Unexpected block found")
	}

	if !m.finder.Iterate(blocks[1].Hash, iterFn) {
		t.Error("Expected block not found")
	}

	file.Blocks = []protocol.BlockInfo{blocks[0]}
	file.Version++
	// Update index (removing old blocks)
	m.updateLocal("default", file)

	if !m.finder.Iterate(blocks[0].Hash, iterFn) {
		t.Error("Unexpected block found")
	}

	if m.finder.Iterate(blocks[1].Hash, iterFn) {
		t.Error("Expected block not found")
	}
}
Ejemplo n.º 5
0
func (m *Model) updateLocal(folder string, f protocol.FileInfo) {
	f.LocalVersion = 0
	m.fmut.RLock()
	m.folderFiles[folder].Update(protocol.LocalDeviceID, []protocol.FileInfo{f})
	m.fmut.RUnlock()
	events.Default.Log(events.LocalIndexUpdated, map[string]interface{}{
		"folder":   folder,
		"name":     f.Name,
		"modified": time.Unix(f.Modified, 0),
		"flags":    fmt.Sprintf("0%o", f.Flags),
		"size":     f.Size(),
	})
}
Ejemplo n.º 6
0
func ldbInsert(batch dbWriter, folder, device, name []byte, file protocol.FileInfo) uint64 {
	if debug {
		l.Debugf("insert; folder=%q device=%v %v", folder, protocol.DeviceIDFromBytes(device), file)
	}

	if file.LocalVersion == 0 {
		file.LocalVersion = clock(0)
	}

	nk := deviceKey(folder, device, name)
	batch.Put(nk, file.MarshalXDR())

	return file.LocalVersion
}
Ejemplo n.º 7
0
func ldbGet(db *leveldb.DB, folder, device, file []byte) protocol.FileInfo {
	nk := deviceKey(folder, device, file)
	bs, err := db.Get(nk, nil)
	if err == leveldb.ErrNotFound {
		return protocol.FileInfo{}
	}
	if err != nil {
		panic(err)
	}

	var f protocol.FileInfo
	err = f.UnmarshalXDR(bs)
	if err != nil {
		panic(err)
	}
	return f
}
Ejemplo n.º 8
0
// handleFile queues the copies and pulls as necessary for a single new or
// changed file.
func (p *Puller) handleFile(file protocol.FileInfo, copyChan chan<- copyBlocksState, finisherChan chan<- *sharedPullerState) {
	curFile := p.model.CurrentFolderFile(p.folder, file.Name)

	if len(curFile.Blocks) == len(file.Blocks) && scanner.BlocksEqual(curFile.Blocks, file.Blocks) {
		// We are supposed to copy the entire file, and then fetch nothing. We
		// are only updating metadata, so we don't actually *need* to make the
		// copy.
		if debug {
			l.Debugln(p, "taking shortcut on", file.Name)
		}
		if file.IsSymlink() {
			p.shortcutSymlink(curFile, file)
		} else {
			p.shortcutFile(file)
		}
		return
	}

	scanner.PopulateOffsets(file.Blocks)

	// Figure out the absolute filenames we need once and for all
	tempName := filepath.Join(p.dir, defTempNamer.TempName(file.Name))
	realName := filepath.Join(p.dir, file.Name)

	reused := 0
	var blocks []protocol.BlockInfo

	// Check for an old temporary file which might have some blocks we could
	// reuse.
	tempBlocks, err := scanner.HashFile(tempName, protocol.BlockSize)
	if err == nil {
		// Check for any reusable blocks in the temp file
		tempCopyBlocks, _ := scanner.BlockDiff(tempBlocks, file.Blocks)

		// block.String() returns a string unique to the block
		existingBlocks := make(map[string]bool, len(tempCopyBlocks))
		for _, block := range tempCopyBlocks {
			existingBlocks[block.String()] = true
		}

		// Since the blocks are already there, we don't need to get them.
		for _, block := range file.Blocks {
			_, ok := existingBlocks[block.String()]
			if !ok {
				blocks = append(blocks, block)
			}
		}

		// The sharedpullerstate will know which flags to use when opening the
		// temp file depending if we are reusing any blocks or not.
		reused = len(file.Blocks) - len(blocks)
		if reused == 0 {
			// Otherwise, discard the file ourselves in order for the
			// sharedpuller not to panic when it fails to exlusively create a
			// file which already exists
			os.Remove(tempName)
		}
	} else {
		blocks = file.Blocks
	}

	s := sharedPullerState{
		file:       file,
		folder:     p.folder,
		tempName:   tempName,
		realName:   realName,
		copyTotal:  uint32(len(blocks)),
		copyNeeded: uint32(len(blocks)),
		reused:     uint32(reused),
	}

	if debug {
		l.Debugf("%v need file %s; copy %d, reused %v", p, file.Name, len(blocks), reused)
	}

	cs := copyBlocksState{
		sharedPullerState: &s,
		blocks:            blocks,
	}
	copyChan <- cs
}
Ejemplo n.º 9
0
// On the 10th iteration, we start hashing the content which we receive by
// following blockfinder's instructions. Make sure that the copier routine
// hashes the content when asked, and pulls if it fails to find the block.
func TestLastResortPulling(t *testing.T) {
	fcfg := config.FolderConfiguration{ID: "default", Path: "testdata"}
	cfg := config.Configuration{Folders: []config.FolderConfiguration{fcfg}}

	db, _ := leveldb.Open(storage.NewMemStorage(), nil)
	m := NewModel(config.Wrap("/tmp/test", cfg), "device", "syncthing", "dev", db)
	m.AddFolder(fcfg)

	// Add a file to index (with the incorrect block representation, as content
	// doesn't actually match the block list)
	file := protocol.FileInfo{
		Name:     "empty",
		Flags:    0,
		Modified: 0,
		Blocks:   []protocol.BlockInfo{blocks[0]},
	}
	m.updateLocal("default", file)

	// Pretend that we are handling a new file of the same content but
	// with a different name (causing to copy that particular block)
	file.Name = "newfile"

	iterFn := func(folder, file string, index uint32) bool {
		return true
	}

	// Check that that particular block is there
	if !m.finder.Iterate(blocks[0].Hash, iterFn) {
		t.Error("Expected block not found")
	}

	p := Puller{
		folder: "default",
		dir:    "testdata",
		model:  m,
	}

	copyChan := make(chan copyBlocksState)
	pullChan := make(chan pullBlockState, 1)
	finisherChan := make(chan *sharedPullerState, 1)

	// Run a single copier routine with checksumming enabled
	go p.copierRoutine(copyChan, pullChan, finisherChan, true)

	p.handleFile(file, copyChan, finisherChan)

	// Copier should hash empty file, realise that the region it has read
	// doesn't match the hash which was advertised by the block map, fix it
	// and ask to pull the block.
	<-pullChan

	// Verify that it did fix the incorrect hash.
	if m.finder.Iterate(blocks[0].Hash, iterFn) {
		t.Error("Found unexpected block")
	}

	if !m.finder.Iterate(scanner.SHA256OfNothing, iterFn) {
		t.Error("Expected block not found")
	}

	(<-finisherChan).fd.Close()
	os.Remove(filepath.Join("testdata", defTempNamer.TempName("newfile")))
}