all: Add invalid/ignored files to global list, announce to peers (fixes #623)

This lets us determine accurate completion status for remote peers when they
have ignored files.

GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/4460
This commit is contained in:
Simon Frei 2017-11-11 19:18:17 +00:00 committed by Jakob Borg
parent ec4c3bae0d
commit c080f677cb
17 changed files with 446 additions and 254 deletions

View File

@ -761,6 +761,17 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
// Converts old symlink types to new in the entire database. // Converts old symlink types to new in the entire database.
ldb.ConvertSymlinkTypes() ldb.ConvertSymlinkTypes()
} }
if cfg.RawCopy().OriginalVersion < 26 {
// Adds invalid (ignored) files to global list of files
changed := 0
for folderID, folderCfg := range folders {
changed += ldb.AddInvalidToGlobal([]byte(folderID), protocol.LocalDeviceID[:])
for _, deviceCfg := range folderCfg.Devices {
changed += ldb.AddInvalidToGlobal([]byte(folderID), deviceCfg.DeviceID[:])
}
}
l.Infof("Database update: Added %d ignored files to the global list", changed)
}
m := model.NewModel(cfg, myID, "syncthing", Version, ldb, protectedFiles) m := model.NewModel(cfg, myID, "syncthing", Version, ldb, protectedFiles)

View File

@ -32,7 +32,7 @@ import (
const ( const (
OldestHandledVersion = 10 OldestHandledVersion = 10
CurrentVersion = 25 CurrentVersion = 26
MaxRescanIntervalS = 365 * 24 * 60 * 60 MaxRescanIntervalS = 365 * 24 * 60 * 60
) )
@ -329,6 +329,9 @@ func (cfg *Configuration) clean() error {
if cfg.Version == 24 { if cfg.Version == 24 {
convertV24V25(cfg) convertV24V25(cfg)
} }
if cfg.Version == 25 {
convertV25V26(cfg)
}
// Build a list of available devices // Build a list of available devices
existingDevices := make(map[protocol.DeviceID]bool) existingDevices := make(map[protocol.DeviceID]bool)
@ -378,6 +381,11 @@ func (cfg *Configuration) clean() error {
return nil return nil
} }
func convertV25V26(cfg *Configuration) {
// triggers database update
cfg.Version = 26
}
func convertV24V25(cfg *Configuration) { func convertV24V25(cfg *Configuration) {
for i := range cfg.Folders { for i := range cfg.Folders {
cfg.Folders[i].FSWatcherDelayS = 10 cfg.Folders[i].FSWatcherDelayS = 10

16
lib/config/testdata/v26.xml vendored Normal file
View File

@ -0,0 +1,16 @@
<configuration version="26">
<folder id="test" path="testdata" type="readonly" ignorePerms="false" rescanIntervalS="600" fsNotifications="false" notifyDelayS="10" autoNormalize="true">
<filesystemType>basic</filesystemType>
<device id="AIR6LPZ-7K4PTTV-UXQSMUU-CPQ5YWH-OEDFIIQ-JUG777G-2YQXXR5-YD6AWQR"></device>
<device id="P56IOI7-MZJNU2Y-IQGDREY-DM2MGTI-MGL3BXN-PQ6W5BM-TBBZ4TJ-XZWICQ2"></device>
<minDiskFree unit="%">1</minDiskFree>
<maxConflicts>-1</maxConflicts>
<fsync>true</fsync>
</folder>
<device id="AIR6LPZ-7K4PTTV-UXQSMUU-CPQ5YWH-OEDFIIQ-JUG777G-2YQXXR5-YD6AWQR" name="node one" compression="metadata">
<address>tcp://a</address>
</device>
<device id="P56IOI7-MZJNU2Y-IQGDREY-DM2MGTI-MGL3BXN-PQ6W5BM-TBBZ4TJ-XZWICQ2" name="node two" compression="metadata">
<address>tcp://b</address>
</device>
</configuration>

View File

@ -134,11 +134,7 @@ func (db *Instance) genericReplace(folder, device []byte, fs []protocol.FileInfo
if isLocalDevice { if isLocalDevice {
localSize.addFile(fs[fsi]) localSize.addFile(fs[fsi])
} }
if fs[fsi].IsInvalid() { t.updateGlobal(folder, device, fs[fsi], globalSize)
t.removeFromGlobal(folder, device, newName, globalSize)
} else {
t.updateGlobal(folder, device, fs[fsi], globalSize)
}
fsi++ fsi++
case moreFs && moreDb && cmp == 0: case moreFs && moreDb && cmp == 0:
@ -155,11 +151,7 @@ func (db *Instance) genericReplace(folder, device []byte, fs []protocol.FileInfo
localSize.removeFile(ef) localSize.removeFile(ef)
localSize.addFile(fs[fsi]) localSize.addFile(fs[fsi])
} }
if fs[fsi].IsInvalid() { t.updateGlobal(folder, device, fs[fsi], globalSize)
t.removeFromGlobal(folder, device, newName, globalSize)
} else {
t.updateGlobal(folder, device, fs[fsi], globalSize)
}
} else { } else {
l.Debugln("generic replace; equal - ignore") l.Debugln("generic replace; equal - ignore")
} }
@ -219,11 +211,7 @@ func (db *Instance) updateFiles(folder, device []byte, fs []protocol.FileInfo, l
} }
t.insertFile(folder, device, f) t.insertFile(folder, device, f)
if f.IsInvalid() { t.updateGlobal(folder, device, f, globalSize)
t.removeFromGlobal(folder, device, name, globalSize)
} else {
t.updateGlobal(folder, device, f, globalSize)
}
// Write out and reuse the batch every few records, to avoid the batch // Write out and reuse the batch every few records, to avoid the batch
// growing too large and thus allocating unnecessarily much memory. // growing too large and thus allocating unnecessarily much memory.
@ -415,6 +403,9 @@ func (db *Instance) availability(folder, file []byte) []protocol.DeviceID {
if !v.Version.Equal(vl.Versions[0].Version) { if !v.Version.Equal(vl.Versions[0].Version) {
break break
} }
if v.Invalid {
continue
}
n := protocol.DeviceIDFromBytes(v.Device) n := protocol.DeviceIDFromBytes(v.Device)
devices = append(devices, n) devices = append(devices, n)
} }
@ -422,7 +413,7 @@ func (db *Instance) availability(folder, file []byte) []protocol.DeviceID {
return devices return devices
} }
func (db *Instance) withNeed(folder, device []byte, truncate bool, fn Iterator) { func (db *Instance) withNeed(folder, device []byte, truncate bool, needAllInvalid bool, fn Iterator) {
t := db.newReadOnlyTransaction() t := db.newReadOnlyTransaction()
defer t.close() defer t.close()
@ -444,11 +435,17 @@ func (db *Instance) withNeed(folder, device []byte, truncate bool, fn Iterator)
have := false // If we have the file, any version have := false // If we have the file, any version
need := false // If we have a lower version of the file need := false // If we have a lower version of the file
var haveVersion protocol.Vector var haveFileVersion FileVersion
for _, v := range vl.Versions { for _, v := range vl.Versions {
if bytes.Equal(v.Device, device) { if bytes.Equal(v.Device, device) {
have = true have = true
haveVersion = v.Version haveFileVersion = v
// We need invalid files regardless of version when
// ignore patterns changed
if v.Invalid && needAllInvalid {
need = true
break
}
// XXX: This marks Concurrent (i.e. conflicting) changes as // XXX: This marks Concurrent (i.e. conflicting) changes as
// needs. Maybe we should do that, but it needs special // needs. Maybe we should do that, but it needs special
// handling in the puller. // handling in the puller.
@ -463,12 +460,19 @@ func (db *Instance) withNeed(folder, device []byte, truncate bool, fn Iterator)
name := db.globalKeyName(dbi.Key()) name := db.globalKeyName(dbi.Key())
needVersion := vl.Versions[0].Version needVersion := vl.Versions[0].Version
needDevice := protocol.DeviceIDFromBytes(vl.Versions[0].Device)
for i := range vl.Versions { for i := range vl.Versions {
if !vl.Versions[i].Version.Equal(needVersion) { if !vl.Versions[i].Version.Equal(needVersion) {
// We haven't found a valid copy of the file with the needed version. // We haven't found a valid copy of the file with the needed version.
break break
} }
if vl.Versions[i].Invalid {
// The file is marked invalid, don't use it.
continue
}
fk = db.deviceKeyInto(fk[:cap(fk)], folder, vl.Versions[i].Device, name) fk = db.deviceKeyInto(fk[:cap(fk)], folder, vl.Versions[i].Device, name)
bs, err := t.Get(fk, nil) bs, err := t.Get(fk, nil)
if err != nil { if err != nil {
@ -482,17 +486,12 @@ func (db *Instance) withNeed(folder, device []byte, truncate bool, fn Iterator)
continue continue
} }
if gf.IsInvalid() {
// The file is marked invalid for whatever reason, don't use it.
continue
}
if gf.IsDeleted() && !have { if gf.IsDeleted() && !have {
// We don't need deleted files that we don't have // We don't need deleted files that we don't have
break break
} }
l.Debugf("need folder=%q device=%v name=%q need=%v have=%v haveV=%d globalV=%d", folder, protocol.DeviceIDFromBytes(device), name, need, have, haveVersion, vl.Versions[0].Version) l.Debugf("need folder=%q device=%v name=%q need=%v have=%v invalid=%v haveV=%d globalV=%d globalDev=%v", folder, protocol.DeviceIDFromBytes(device), name, need, have, haveFileVersion.Invalid, haveFileVersion.Version, needVersion, needDevice)
if cont := fn(gf); !cont { if cont := fn(gf); !cont {
return return
@ -640,6 +639,94 @@ func (db *Instance) ConvertSymlinkTypes() {
l.Infof("Updated symlink type for %d index entries", conv) l.Infof("Updated symlink type for %d index entries", conv)
} }
// AddInvalidToGlobal searches for invalid files and adds them to the global list.
// Invalid files exist in the db if they once were not ignored and subsequently
// ignored. In the new system this is still valid, but invalid files must also be
// in the global list such that they cannot be mistaken for missing files.
func (db *Instance) AddInvalidToGlobal(folder, device []byte) int {
t := db.newReadWriteTransaction()
defer t.close()
dbi := t.NewIterator(util.BytesPrefix(db.deviceKey(folder, device, nil)[:keyPrefixLen+keyFolderLen+keyDeviceLen]), nil)
defer dbi.Release()
changed := 0
for dbi.Next() {
var file protocol.FileInfo
if err := file.Unmarshal(dbi.Value()); err != nil {
// probably can't happen
continue
}
if file.Invalid {
changed++
l.Debugf("add invalid to global; folder=%q device=%v file=%q version=%d", folder, protocol.DeviceIDFromBytes(device), file.Name, file.Version)
// this is an adapted version of readWriteTransaction.updateGlobal
name := []byte(file.Name)
gk := t.db.globalKey(folder, name)
var fl VersionList
if svl, err := t.Get(gk, nil); err == nil {
fl.Unmarshal(svl) // skip error, range handles success case
}
nv := FileVersion{
Device: device,
Version: file.Version,
Invalid: file.Invalid,
}
inserted := false
// Find a position in the list to insert this file. The file at the front
// of the list is the newer, the "global".
insert:
for i := range fl.Versions {
switch fl.Versions[i].Version.Compare(file.Version) {
case protocol.Equal:
// Invalid files should go after a valid file of equal version
if nv.Invalid {
continue insert
}
fallthrough
case protocol.Lesser:
// The version at this point in the list is equal to or lesser
// ("older") than us. We insert ourselves in front of it.
fl.Versions = insertVersion(fl.Versions, i, nv)
inserted = true
break insert
case protocol.ConcurrentLesser, protocol.ConcurrentGreater:
// The version at this point is in conflict with us. We must pull
// the actual file metadata to determine who wins. If we win, we
// insert ourselves in front of the loser here. (The "Lesser" and
// "Greater" in the condition above is just based on the device
// IDs in the version vector, which is not the only thing we use
// to determine the winner.)
//
// A surprise missing file entry here is counted as a win for us.
of, ok := t.getFile(folder, fl.Versions[i].Device, name)
if !ok || file.WinsConflict(of) {
fl.Versions = insertVersion(fl.Versions, i, nv)
inserted = true
break insert
}
}
}
if !inserted {
// We didn't find a position for an insert above, so append to the end.
fl.Versions = append(fl.Versions, nv)
}
t.Put(gk, mustMarshal(&fl))
}
}
return changed
}
// deviceKey returns a byte slice encoding the following information: // deviceKey returns a byte slice encoding the following information:
// keyTypeDevice (1 byte) // keyTypeDevice (1 byte)
// folder (4 bytes) // folder (4 bytes)

View File

@ -99,7 +99,7 @@ func (t readWriteTransaction) updateGlobal(folder, device []byte, file protocol.
fl.Unmarshal(svl) // skip error, range handles success case fl.Unmarshal(svl) // skip error, range handles success case
for i := range fl.Versions { for i := range fl.Versions {
if bytes.Equal(fl.Versions[i].Device, device) { if bytes.Equal(fl.Versions[i].Device, device) {
if fl.Versions[i].Version.Equal(file.Version) { if fl.Versions[i].Version.Equal(file.Version) && fl.Versions[i].Invalid == file.Invalid {
// No need to do anything // No need to do anything
return false return false
} }
@ -119,19 +119,27 @@ func (t readWriteTransaction) updateGlobal(folder, device []byte, file protocol.
nv := FileVersion{ nv := FileVersion{
Device: device, Device: device,
Version: file.Version, Version: file.Version,
Invalid: file.Invalid,
} }
var insertedAt int insertedAt := -1
// Find a position in the list to insert this file. The file at the front // Find a position in the list to insert this file. The file at the front
// of the list is the newer, the "global". // of the list is the newer, the "global".
insert:
for i := range fl.Versions { for i := range fl.Versions {
switch fl.Versions[i].Version.Compare(file.Version) { switch fl.Versions[i].Version.Compare(file.Version) {
case protocol.Equal, protocol.Lesser: case protocol.Equal:
if nv.Invalid {
continue insert
}
fallthrough
case protocol.Lesser:
// The version at this point in the list is equal to or lesser // The version at this point in the list is equal to or lesser
// ("older") than us. We insert ourselves in front of it. // ("older") than us. We insert ourselves in front of it.
fl.Versions = insertVersion(fl.Versions, i, nv) fl.Versions = insertVersion(fl.Versions, i, nv)
insertedAt = i insertedAt = i
goto done break insert
case protocol.ConcurrentLesser, protocol.ConcurrentGreater: case protocol.ConcurrentLesser, protocol.ConcurrentGreater:
// The version at this point is in conflict with us. We must pull // The version at this point is in conflict with us. We must pull
@ -146,16 +154,17 @@ func (t readWriteTransaction) updateGlobal(folder, device []byte, file protocol.
if !ok || file.WinsConflict(of) { if !ok || file.WinsConflict(of) {
fl.Versions = insertVersion(fl.Versions, i, nv) fl.Versions = insertVersion(fl.Versions, i, nv)
insertedAt = i insertedAt = i
goto done break insert
} }
} }
} }
// We didn't find a position for an insert above, so append to the end. if insertedAt == -1 {
fl.Versions = append(fl.Versions, nv) // We didn't find a position for an insert above, so append to the end.
insertedAt = len(fl.Versions) - 1 fl.Versions = append(fl.Versions, nv)
insertedAt = len(fl.Versions) - 1
}
done:
if insertedAt == 0 { if insertedAt == 0 {
// We just inserted a new newest version. Fixup the global size // We just inserted a new newest version. Fixup the global size
// calculation. // calculation.
@ -221,15 +230,15 @@ func (t readWriteTransaction) removeFromGlobal(folder, device, file []byte, glob
if len(fl.Versions) == 0 { if len(fl.Versions) == 0 {
t.Delete(gk) t.Delete(gk)
} else { return
l.Debugf("new global after remove: %v", fl) }
t.Put(gk, mustMarshal(&fl)) l.Debugf("new global after remove: %v", fl)
if removed { t.Put(gk, mustMarshal(&fl))
if f, ok := t.getFile(folder, fl.Versions[0].Device, file); ok { if removed {
// A failure to get the file here is surprising and our if f, ok := t.getFile(folder, fl.Versions[0].Device, file); ok {
// global size data will be incorrect until a restart... // A failure to get the file here is surprising and our
globalSize.addFile(f) // global size data will be incorrect until a restart...
} globalSize.addFile(f)
} }
} }
} }

View File

@ -206,12 +206,24 @@ func (s *FileSet) Update(device protocol.DeviceID, fs []protocol.FileInfo) {
func (s *FileSet) WithNeed(device protocol.DeviceID, fn Iterator) { func (s *FileSet) WithNeed(device protocol.DeviceID, fn Iterator) {
l.Debugf("%s WithNeed(%v)", s.folder, device) l.Debugf("%s WithNeed(%v)", s.folder, device)
s.db.withNeed([]byte(s.folder), device[:], false, nativeFileIterator(fn)) s.db.withNeed([]byte(s.folder), device[:], false, false, nativeFileIterator(fn))
} }
func (s *FileSet) WithNeedTruncated(device protocol.DeviceID, fn Iterator) { func (s *FileSet) WithNeedTruncated(device protocol.DeviceID, fn Iterator) {
l.Debugf("%s WithNeedTruncated(%v)", s.folder, device) l.Debugf("%s WithNeedTruncated(%v)", s.folder, device)
s.db.withNeed([]byte(s.folder), device[:], true, nativeFileIterator(fn)) s.db.withNeed([]byte(s.folder), device[:], true, false, nativeFileIterator(fn))
}
// WithNeedOrInvalid considers all invalid files as needed, regardless of their version
// (e.g. for pulling when ignore patterns changed)
func (s *FileSet) WithNeedOrInvalid(device protocol.DeviceID, fn Iterator) {
l.Debugf("%s WithNeedExcludingInvalid(%v)", s.folder, device)
s.db.withNeed([]byte(s.folder), device[:], false, true, nativeFileIterator(fn))
}
func (s *FileSet) WithNeedOrInvalidTruncated(device protocol.DeviceID, fn Iterator) {
l.Debugf("%s WithNeedExcludingInvalidTruncated(%v)", s.folder, device)
s.db.withNeed([]byte(s.folder), device[:], true, true, nativeFileIterator(fn))
} }
func (s *FileSet) WithHave(device protocol.DeviceID, fn Iterator) { func (s *FileSet) WithHave(device protocol.DeviceID, fn Iterator) {

View File

@ -63,3 +63,15 @@ func (f FileInfoTruncated) FileName() string {
func (f FileInfoTruncated) ModTime() time.Time { func (f FileInfoTruncated) ModTime() time.Time {
return time.Unix(f.ModifiedS, int64(f.ModifiedNs)) return time.Unix(f.ModifiedS, int64(f.ModifiedNs))
} }
func (f FileInfoTruncated) ConvertToInvalidFileInfo(invalidatedBy protocol.ShortID) protocol.FileInfo {
return protocol.FileInfo{
Name: f.Name,
Type: f.Type,
ModifiedS: f.ModifiedS,
ModifiedNs: f.ModifiedNs,
ModifiedBy: invalidatedBy,
Invalid: true,
Version: f.Version,
}
}

View File

@ -1,6 +1,5 @@
// Code generated by protoc-gen-gogo. // Code generated by protoc-gen-gogo. DO NOT EDIT.
// source: structs.proto // source: structs.proto
// DO NOT EDIT!
/* /*
Package db is a generated protocol buffer package. Package db is a generated protocol buffer package.
@ -39,6 +38,7 @@ const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package
type FileVersion struct { type FileVersion struct {
Version protocol.Vector `protobuf:"bytes,1,opt,name=version" json:"version"` Version protocol.Vector `protobuf:"bytes,1,opt,name=version" json:"version"`
Device []byte `protobuf:"bytes,2,opt,name=device,proto3" json:"device,omitempty"` Device []byte `protobuf:"bytes,2,opt,name=device,proto3" json:"device,omitempty"`
Invalid bool `protobuf:"varint,3,opt,name=invalid,proto3" json:"invalid,omitempty"`
} }
func (m *FileVersion) Reset() { *m = FileVersion{} } func (m *FileVersion) Reset() { *m = FileVersion{} }
@ -109,6 +109,16 @@ func (m *FileVersion) MarshalTo(dAtA []byte) (int, error) {
i = encodeVarintStructs(dAtA, i, uint64(len(m.Device))) i = encodeVarintStructs(dAtA, i, uint64(len(m.Device)))
i += copy(dAtA[i:], m.Device) i += copy(dAtA[i:], m.Device)
} }
if m.Invalid {
dAtA[i] = 0x18
i++
if m.Invalid {
dAtA[i] = 1
} else {
dAtA[i] = 0
}
i++
}
return i, nil return i, nil
} }
@ -283,6 +293,9 @@ func (m *FileVersion) ProtoSize() (n int) {
if l > 0 { if l > 0 {
n += 1 + l + sovStructs(uint64(l)) n += 1 + l + sovStructs(uint64(l))
} }
if m.Invalid {
n += 2
}
return n return n
} }
@ -447,6 +460,26 @@ func (m *FileVersion) Unmarshal(dAtA []byte) error {
m.Device = []byte{} m.Device = []byte{}
} }
iNdEx = postIndex iNdEx = postIndex
case 3:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field Invalid", wireType)
}
var v int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowStructs
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
v |= (int(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
m.Invalid = bool(v != 0)
default: default:
iNdEx = preIndex iNdEx = preIndex
skippy, err := skipStructs(dAtA[iNdEx:]) skippy, err := skipStructs(dAtA[iNdEx:])
@ -988,36 +1021,36 @@ var (
func init() { proto.RegisterFile("structs.proto", fileDescriptorStructs) } func init() { proto.RegisterFile("structs.proto", fileDescriptorStructs) }
var fileDescriptorStructs = []byte{ var fileDescriptorStructs = []byte{
// 483 bytes of a gzipped FileDescriptorProto // 487 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x52, 0x4f, 0x6b, 0xdb, 0x4e, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x52, 0xc1, 0x6a, 0xdb, 0x40,
0x10, 0xf5, 0xc6, 0x8a, 0xff, 0xac, 0xe2, 0xfc, 0x7e, 0x59, 0x4a, 0x58, 0x0c, 0x95, 0x85, 0xa1, 0x10, 0xf5, 0xc6, 0x4a, 0x6c, 0xaf, 0xe2, 0xb4, 0x59, 0x4a, 0x58, 0x0c, 0x95, 0x85, 0xa1, 0x20,
0x20, 0x0a, 0x95, 0x5b, 0x87, 0x5e, 0xda, 0x9b, 0x29, 0x81, 0x40, 0x29, 0x45, 0x09, 0xe9, 0xa5, 0x0a, 0x95, 0x5b, 0x87, 0x5e, 0xda, 0x9b, 0x29, 0x81, 0x40, 0x29, 0x45, 0x09, 0x39, 0x15, 0x8c,
0x60, 0x2c, 0x69, 0x2c, 0x2f, 0x95, 0x76, 0x55, 0xed, 0xca, 0xa0, 0x7e, 0x92, 0x1e, 0xf3, 0x71, 0x25, 0x8d, 0xe5, 0xa5, 0xd2, 0xae, 0xa2, 0x5d, 0x19, 0xd4, 0x2f, 0xe9, 0x31, 0x9f, 0xe3, 0x63,
0x7c, 0xec, 0xb9, 0x87, 0xd0, 0xba, 0x9f, 0xa3, 0x50, 0xb4, 0x92, 0x15, 0x1d, 0xdb, 0xdb, 0xbc, 0xcf, 0x3d, 0x84, 0xd6, 0xfd, 0x8e, 0x42, 0xd1, 0x4a, 0x56, 0xd4, 0x5b, 0x7b, 0x9b, 0x37, 0x7a,
0xd9, 0xf7, 0xe6, 0xbd, 0x61, 0x16, 0x8f, 0xa4, 0xca, 0xf2, 0x40, 0x49, 0x37, 0xcd, 0x84, 0x12, 0x6f, 0xdf, 0x9b, 0x19, 0xe1, 0xa1, 0x54, 0x59, 0x1e, 0x28, 0xe9, 0xa6, 0x99, 0x50, 0x82, 0x1c,
0xe4, 0x28, 0xf4, 0xc7, 0xcf, 0x22, 0xa6, 0x36, 0xb9, 0xef, 0x06, 0x22, 0x99, 0x45, 0x22, 0x12, 0x84, 0xfe, 0xe8, 0x45, 0xc4, 0xd4, 0x3a, 0xf7, 0xdd, 0x40, 0x24, 0xd3, 0x48, 0x44, 0x62, 0xaa,
0x33, 0xfd, 0xe4, 0xe7, 0x6b, 0x8d, 0x34, 0xd0, 0x55, 0x25, 0x19, 0xbf, 0x6c, 0xd1, 0x65, 0xc1, 0x3f, 0xf9, 0xf9, 0x4a, 0x23, 0x0d, 0x74, 0x55, 0x49, 0x46, 0xaf, 0x5b, 0x74, 0x59, 0xf0, 0x40,
0x03, 0xb5, 0x61, 0x3c, 0x6a, 0x55, 0x31, 0xf3, 0xab, 0x09, 0x81, 0x88, 0x67, 0x3e, 0xa4, 0x95, 0xad, 0x19, 0x8f, 0x5a, 0x55, 0xcc, 0xfc, 0xea, 0x85, 0x40, 0xc4, 0x53, 0x1f, 0xd2, 0x4a, 0x36,
0x6c, 0xfa, 0x01, 0x9b, 0x97, 0x2c, 0x86, 0x5b, 0xc8, 0x24, 0x13, 0x9c, 0x3c, 0xc7, 0xfd, 0x6d, 0xb9, 0xc5, 0xe6, 0x05, 0x8b, 0xe1, 0x06, 0x32, 0xc9, 0x04, 0x27, 0x2f, 0x71, 0x6f, 0x53, 0x95,
0x55, 0x52, 0x64, 0x23, 0xc7, 0x9c, 0xff, 0xef, 0x1e, 0x44, 0xee, 0x2d, 0x04, 0x4a, 0x64, 0x0b, 0x14, 0xd9, 0xc8, 0x31, 0x67, 0x8f, 0xdd, 0xbd, 0xc8, 0xbd, 0x81, 0x40, 0x89, 0x6c, 0x6e, 0x6c,
0x63, 0x77, 0x3f, 0xe9, 0x78, 0x07, 0x1a, 0x39, 0xc7, 0xbd, 0x10, 0xb6, 0x2c, 0x00, 0x7a, 0x64, 0xef, 0xc7, 0x1d, 0x6f, 0x4f, 0x23, 0x67, 0xf8, 0x28, 0x84, 0x0d, 0x0b, 0x80, 0x1e, 0xd8, 0xc8,
0x23, 0xe7, 0xc4, 0xab, 0xd1, 0xf4, 0x12, 0x9b, 0xf5, 0xd0, 0xb7, 0x4c, 0x2a, 0xf2, 0x02, 0x0f, 0x39, 0xf6, 0x6a, 0x44, 0x28, 0xee, 0x31, 0xbe, 0x59, 0xc6, 0x2c, 0xa4, 0x5d, 0x1b, 0x39, 0x7d,
0x6a, 0x85, 0xa4, 0xc8, 0xee, 0x3a, 0xe6, 0xfc, 0x3f, 0x37, 0xf4, 0xdd, 0x96, 0x77, 0x3d, 0xb8, 0x6f, 0x0f, 0x27, 0x17, 0xd8, 0xac, 0xed, 0xde, 0x33, 0xa9, 0xc8, 0x2b, 0xdc, 0xaf, 0xdf, 0x92,
0xa1, 0xbd, 0x32, 0xbe, 0xde, 0x4d, 0x3a, 0xd3, 0xdf, 0x5d, 0x7c, 0x56, 0xb2, 0xae, 0xf8, 0x5a, 0x14, 0xd9, 0x5d, 0xc7, 0x9c, 0x3d, 0x72, 0x43, 0xdf, 0x6d, 0xa5, 0xaa, 0x2d, 0x1b, 0xda, 0x1b,
0xdc, 0x64, 0x39, 0x0f, 0x56, 0x0a, 0x42, 0x42, 0xb0, 0xc1, 0x57, 0x09, 0xe8, 0x90, 0x43, 0x4f, 0xe3, 0xeb, 0xdd, 0xb8, 0x33, 0xf9, 0xdd, 0xc5, 0xa7, 0x25, 0xeb, 0x92, 0xaf, 0xc4, 0x75, 0x96,
0xd7, 0xe4, 0x29, 0x36, 0x54, 0x91, 0x56, 0x39, 0x4e, 0xe7, 0xe7, 0x0f, 0xc1, 0x1b, 0x79, 0x91, 0xf3, 0x60, 0xa9, 0x20, 0x24, 0x04, 0x1b, 0x7c, 0x99, 0x80, 0x8e, 0x3f, 0xf0, 0x74, 0x4d, 0x9e,
0x82, 0xa7, 0x39, 0xa5, 0x5e, 0xb2, 0x2f, 0x40, 0xbb, 0x36, 0x72, 0xba, 0x9e, 0xae, 0x89, 0x8d, 0x63, 0x43, 0x15, 0x69, 0x95, 0xf0, 0x64, 0x76, 0xf6, 0x30, 0x52, 0x23, 0x2f, 0x52, 0xf0, 0x34,
0xcd, 0x14, 0xb2, 0x84, 0xc9, 0x2a, 0xa5, 0x61, 0x23, 0x67, 0xe4, 0xb5, 0x5b, 0xe4, 0x31, 0xc6, 0xa7, 0xd4, 0x4b, 0xf6, 0x05, 0x74, 0xe8, 0xae, 0xa7, 0x6b, 0x62, 0x63, 0x33, 0x85, 0x2c, 0x61,
0x89, 0x08, 0xd9, 0x9a, 0x41, 0xb8, 0x94, 0xf4, 0x58, 0x6b, 0x87, 0x87, 0xce, 0x35, 0xa1, 0xb8, 0xb2, 0x4a, 0x69, 0xd8, 0xc8, 0x19, 0x7a, 0xed, 0x16, 0x79, 0x8a, 0x71, 0x22, 0x42, 0xb6, 0x62,
0x1f, 0x42, 0x0c, 0x0a, 0x42, 0xda, 0xb3, 0x91, 0x33, 0xf0, 0x0e, 0xb0, 0x7c, 0x61, 0x7c, 0xbb, 0x10, 0x2e, 0x24, 0x3d, 0xd4, 0xda, 0xc1, 0xbe, 0x73, 0x55, 0x2e, 0x23, 0x84, 0x18, 0x14, 0x84,
0x8a, 0x59, 0x48, 0xfb, 0xd5, 0x4b, 0x0d, 0xc9, 0x13, 0x7c, 0xca, 0xc5, 0xb2, 0xed, 0x3b, 0xd0, 0xf4, 0xa8, 0x5a, 0x46, 0x0d, 0xdb, 0x6b, 0xea, 0xfd, 0xb5, 0x26, 0xf2, 0x0c, 0x9f, 0x70, 0xb1,
0x84, 0x11, 0x17, 0xef, 0x5b, 0xce, 0xad, 0xbb, 0x0c, 0xff, 0xee, 0x2e, 0x63, 0x3c, 0x90, 0xf0, 0x68, 0xfb, 0xf6, 0x35, 0x61, 0xc8, 0xc5, 0xc7, 0x96, 0x73, 0xeb, 0x62, 0x83, 0x7f, 0xbb, 0xd8,
0x39, 0x07, 0x1e, 0x00, 0xc5, 0x3a, 0x69, 0x83, 0xc9, 0x04, 0x9b, 0xcd, 0x1e, 0x5c, 0x52, 0xd3, 0x08, 0xf7, 0x25, 0xdc, 0xe6, 0xc0, 0x03, 0xa0, 0x58, 0x27, 0x6d, 0x30, 0x19, 0x63, 0xb3, 0x99,
0x46, 0xce, 0xb1, 0xd7, 0xac, 0xf6, 0x4e, 0x92, 0x8f, 0x2d, 0x82, 0x5f, 0xd0, 0x13, 0x1b, 0x39, 0x83, 0x4b, 0x6a, 0xda, 0xc8, 0x39, 0xf4, 0x9a, 0xd1, 0x3e, 0x48, 0xf2, 0xa9, 0x45, 0xf0, 0x0b,
0xc6, 0xe2, 0x75, 0x69, 0xf0, 0xfd, 0x7e, 0x72, 0xf1, 0x0f, 0x3f, 0xcd, 0xbd, 0xde, 0x88, 0x4c, 0x7a, 0x6c, 0x23, 0xc7, 0x98, 0xbf, 0x2d, 0x0d, 0xbe, 0xdf, 0x8f, 0xcf, 0xff, 0xe3, 0x1f, 0x74,
0x5d, 0xbd, 0x79, 0x98, 0xbe, 0x28, 0xca, 0x9d, 0x65, 0x91, 0xc4, 0x8c, 0x7f, 0x5a, 0xaa, 0x55, 0xaf, 0xd6, 0x22, 0x53, 0x97, 0xef, 0x1e, 0x5e, 0x9f, 0x17, 0xe5, 0xcc, 0xb2, 0x48, 0x62, 0xc6,
0x16, 0x81, 0xa2, 0x67, 0xfa, 0x8c, 0xa3, 0xba, 0x7b, 0xa3, 0x9b, 0xd5, 0xfd, 0x17, 0x8f, 0x76, 0x3f, 0x2f, 0xd4, 0x32, 0x8b, 0x40, 0xd1, 0x53, 0x7d, 0xc6, 0x61, 0xdd, 0xbd, 0xd6, 0xcd, 0xea,
0x3f, 0xad, 0xce, 0x6e, 0x6f, 0xa1, 0x6f, 0x7b, 0x0b, 0xfd, 0xd8, 0x5b, 0x9d, 0xbb, 0x5f, 0x16, 0xfe, 0xf3, 0x27, 0xdb, 0x9f, 0x56, 0x67, 0xbb, 0xb3, 0xd0, 0xb7, 0x9d, 0x85, 0x7e, 0xec, 0xac,
0xf2, 0x7b, 0xda, 0xe0, 0xe2, 0x4f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x9e, 0xcd, 0xe3, 0xfd, 0x38, 0xce, 0xdd, 0x2f, 0x0b, 0xf9, 0x47, 0xda, 0xe0, 0xfc, 0x4f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xfe,
0x03, 0x00, 0x00, 0xcd, 0x11, 0xef, 0x52, 0x03, 0x00, 0x00,
} }

View File

@ -12,6 +12,7 @@ option (gogoproto.protosizer_all) = true;
message FileVersion { message FileVersion {
protocol.Vector version = 1 [(gogoproto.nullable) = false]; protocol.Vector version = 1 [(gogoproto.nullable) = false];
bytes device = 2; bytes device = 2;
bool invalid = 3;
} }
message VersionList { message VersionList {

View File

@ -1,6 +1,5 @@
// Code generated by protoc-gen-gogo. // Code generated by protoc-gen-gogo. DO NOT EDIT.
// source: local.proto // source: local.proto
// DO NOT EDIT!
/* /*
Package discover is a generated protocol buffer package. Package discover is a generated protocol buffer package.

View File

@ -53,3 +53,27 @@ func getHomeDir() (string, error) {
return home, nil return home, nil
} }
var windowsDisallowedCharacters = string([]rune{
'<', '>', ':', '"', '|', '?', '*',
0, 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,
})
func WindowsInvalidFilename(name string) bool {
// None of the path components should end in space
for _, part := range strings.Split(name, `\`) {
if len(part) == 0 {
continue
}
if part[len(part)-1] == ' ' {
// Names ending in space are not valid.
return true
}
}
// The path must not contain any disallowed characters
return strings.ContainsAny(name, windowsDisallowedCharacters)
}

View File

@ -595,7 +595,6 @@ type FolderCompletion struct {
func (m *Model) Completion(device protocol.DeviceID, folder string) FolderCompletion { func (m *Model) Completion(device protocol.DeviceID, folder string) FolderCompletion {
m.fmut.RLock() m.fmut.RLock()
rf, ok := m.folderFiles[folder] rf, ok := m.folderFiles[folder]
ignores := m.folderIgnores[folder]
m.fmut.RUnlock() m.fmut.RUnlock()
if !ok { if !ok {
return FolderCompletion{} // Folder doesn't exist, so we hardly have any of it return FolderCompletion{} // Folder doesn't exist, so we hardly have any of it
@ -615,10 +614,6 @@ func (m *Model) Completion(device protocol.DeviceID, folder string) FolderComple
var need, fileNeed, downloaded, deletes int64 var need, fileNeed, downloaded, deletes int64
rf.WithNeedTruncated(device, func(f db.FileIntf) bool { rf.WithNeedTruncated(device, func(f db.FileIntf) bool {
if ignores.Match(f.FileName()).IsIgnored() {
return true
}
ft := f.(db.FileInfoTruncated) ft := f.(db.FileInfoTruncated)
// If the file is deleted, we account it only in the deleted column. // If the file is deleted, we account it only in the deleted column.
@ -703,10 +698,9 @@ func (m *Model) NeedSize(folder string) db.Counts {
var result db.Counts var result db.Counts
if rf, ok := m.folderFiles[folder]; ok { if rf, ok := m.folderFiles[folder]; ok {
ignores := m.folderIgnores[folder]
cfg := m.folderCfgs[folder] cfg := m.folderCfgs[folder]
rf.WithNeedTruncated(protocol.LocalDeviceID, func(f db.FileIntf) bool { rf.WithNeedTruncated(protocol.LocalDeviceID, func(f db.FileIntf) bool {
if shouldIgnore(f, ignores, cfg.IgnoreDelete) { if cfg.IgnoreDelete && f.IsDeleted() {
return true return true
} }
@ -767,10 +761,9 @@ func (m *Model) NeedFolderFiles(folder string, page, perpage int) ([]db.FileInfo
} }
rest = make([]db.FileInfoTruncated, 0, perpage) rest = make([]db.FileInfoTruncated, 0, perpage)
ignores := m.folderIgnores[folder]
cfg := m.folderCfgs[folder] cfg := m.folderCfgs[folder]
rf.WithNeedTruncated(protocol.LocalDeviceID, func(f db.FileIntf) bool { rf.WithNeedTruncated(protocol.LocalDeviceID, func(f db.FileIntf) bool {
if shouldIgnore(f, ignores, cfg.IgnoreDelete) { if cfg.IgnoreDelete && f.IsDeleted() {
return true return true
} }
@ -1721,6 +1714,13 @@ func (m *Model) diskChangeDetected(folderCfg config.FolderConfiguration, files [
objType := "file" objType := "file"
action := "modified" action := "modified"
switch {
case file.IsDeleted():
action = "deleted"
case file.Invalid:
action = "ignored" // invalidated seems not very user friendly
// If our local vector is version 1 AND it is the only version // If our local vector is version 1 AND it is the only version
// vector so far seen for this file then it is a new file. Else if // vector so far seen for this file then it is a new file. Else if
// it is > 1 it's not new, and if it is 1 but another shortId // it is > 1 it's not new, and if it is 1 but another shortId
@ -1728,16 +1728,13 @@ func (m *Model) diskChangeDetected(folderCfg config.FolderConfiguration, files [
// so the file is still not new but modified by us. Only if it is // so the file is still not new but modified by us. Only if it is
// truly new do we change this to 'added', else we leave it as // truly new do we change this to 'added', else we leave it as
// 'modified'. // 'modified'.
if len(file.Version.Counters) == 1 && file.Version.Counters[0].Value == 1 { case len(file.Version.Counters) == 1 && file.Version.Counters[0].Value == 1:
action = "added" action = "added"
} }
if file.IsDirectory() { if file.IsDirectory() {
objType = "dir" objType = "dir"
} }
if file.IsDeleted() {
action = "deleted"
}
// Two different events can be fired here based on what EventType is passed into function // Two different events can be fired here based on what EventType is passed into function
events.Default.Log(typeOfEvent, map[string]string{ events.Default.Log(typeOfEvent, map[string]string{
@ -1971,18 +1968,7 @@ func (m *Model) internalScanFolderSubdirs(ctx context.Context, folder string, su
case !f.IsInvalid() && ignores.Match(f.Name).IsIgnored(): case !f.IsInvalid() && ignores.Match(f.Name).IsIgnored():
// File was valid at last pass but has been ignored. Set invalid bit. // File was valid at last pass but has been ignored. Set invalid bit.
l.Debugln("setting invalid bit on ignored", f) l.Debugln("setting invalid bit on ignored", f)
nf := protocol.FileInfo{ nf := f.ConvertToInvalidFileInfo(m.id.Short())
Name: f.Name,
Type: f.Type,
Size: f.Size,
ModifiedS: f.ModifiedS,
ModifiedNs: f.ModifiedNs,
ModifiedBy: m.id.Short(),
Permissions: f.Permissions,
NoPermissions: f.NoPermissions,
Invalid: true,
Version: f.Version, // The file is still the same, so don't bump version
}
batch = append(batch, nf) batch = append(batch, nf)
batchSizeBytes += nf.ProtoSize() batchSizeBytes += nf.ProtoSize()
@ -2167,6 +2153,10 @@ func (m *Model) Override(folder string) {
} }
have, ok := fs.Get(protocol.LocalDeviceID, need.Name) have, ok := fs.Get(protocol.LocalDeviceID, need.Name)
// Don't override invalid (e.g. ignored) files
if ok && have.Invalid {
return true
}
if !ok || have.Name != need.Name { if !ok || have.Name != need.Name {
// We are missing the file // We are missing the file
need.Deleted = true need.Deleted = true

View File

@ -26,10 +26,9 @@ func TestRequestSimple(t *testing.T) {
// Verify that the model performs a request and creates a file based on // Verify that the model performs a request and creates a file based on
// an incoming index update. // an incoming index update.
defer os.RemoveAll("_tmpfolder") m, fc, tmpFolder := setupModelWithConnection()
m, fc := setupModelWithConnection()
defer m.Stop() defer m.Stop()
defer os.RemoveAll(tmpFolder)
// We listen for incoming index updates and trigger when we see one for // We listen for incoming index updates and trigger when we see one for
// the expected test file. // the expected test file.
@ -52,7 +51,7 @@ func TestRequestSimple(t *testing.T) {
<-done <-done
// Verify the contents // Verify the contents
bs, err := ioutil.ReadFile("_tmpfolder/testfile") bs, err := ioutil.ReadFile(filepath.Join(tmpFolder, "testfile"))
if err != nil { if err != nil {
t.Error("File did not sync correctly:", err) t.Error("File did not sync correctly:", err)
return return
@ -70,10 +69,9 @@ func TestSymlinkTraversalRead(t *testing.T) {
return return
} }
defer os.RemoveAll("_tmpfolder") m, fc, tmpFolder := setupModelWithConnection()
m, fc := setupModelWithConnection()
defer m.Stop() defer m.Stop()
defer os.RemoveAll(tmpFolder)
// We listen for incoming index updates and trigger when we see one for // We listen for incoming index updates and trigger when we see one for
// the expected test file. // the expected test file.
@ -111,10 +109,9 @@ func TestSymlinkTraversalWrite(t *testing.T) {
return return
} }
defer os.RemoveAll("_tmpfolder") m, fc, tmpFolder := setupModelWithConnection()
m, fc := setupModelWithConnection()
defer m.Stop() defer m.Stop()
defer os.RemoveAll(tmpFolder)
// We listen for incoming index updates and trigger when we see one for // We listen for incoming index updates and trigger when we see one for
// the expected names. // the expected names.
@ -170,22 +167,25 @@ func TestSymlinkTraversalWrite(t *testing.T) {
} }
func TestRequestCreateTmpSymlink(t *testing.T) { func TestRequestCreateTmpSymlink(t *testing.T) {
// Verify that the model performs a request and creates a file based on // Test that an update for a temporary file is invalidated
// an incoming index update.
defer os.RemoveAll("_tmpfolder") m, fc, tmpFolder := setupModelWithConnection()
m, fc := setupModelWithConnection()
defer m.Stop() defer m.Stop()
defer os.RemoveAll(tmpFolder)
// We listen for incoming index updates and trigger when we see one for // We listen for incoming index updates and trigger when we see one for
// the expected test file. // the expected test file.
badIdx := make(chan string) goodIdx := make(chan struct{})
name := fs.TempName("testlink")
fc.mut.Lock() fc.mut.Lock()
fc.indexFn = func(folder string, fs []protocol.FileInfo) { fc.indexFn = func(folder string, fs []protocol.FileInfo) {
for _, f := range fs { for _, f := range fs {
if f.Name == ".syncthing.testlink.tmp" { if f.Name == name {
badIdx <- f.Name if f.Invalid {
goodIdx <- struct{}{}
} else {
t.Fatal("Received index with non-invalid temporary file")
}
return return
} }
} }
@ -193,16 +193,13 @@ func TestRequestCreateTmpSymlink(t *testing.T) {
fc.mut.Unlock() fc.mut.Unlock()
// Send an update for the test file, wait for it to sync and be reported back. // Send an update for the test file, wait for it to sync and be reported back.
fc.addFile(".syncthing.testlink.tmp", 0644, protocol.FileInfoTypeSymlink, []byte("..")) fc.addFile(name, 0644, protocol.FileInfoTypeSymlink, []byte(".."))
fc.sendIndexUpdate() fc.sendIndexUpdate()
select { select {
case name := <-badIdx: case <-goodIdx:
t.Fatal("Should not have sent the index entry for", name)
case <-time.After(3 * time.Second): case <-time.After(3 * time.Second):
// Unfortunately not much else to trigger on here. The puller sleep t.Fatal("Timed out without index entry being sent")
// interval is 1s so if we didn't get any requests within two
// iterations we should be fine.
} }
} }
@ -214,8 +211,12 @@ func TestRequestVersioningSymlinkAttack(t *testing.T) {
// Sets up a folder with trashcan versioning and tries to use a // Sets up a folder with trashcan versioning and tries to use a
// deleted symlink to escape // deleted symlink to escape
tmpFolder, err := ioutil.TempDir(".", "_request-")
if err != nil {
panic("Failed to create temporary testing dir")
}
cfg := defaultConfig.RawCopy() cfg := defaultConfig.RawCopy()
cfg.Folders[0] = config.NewFolderConfiguration("default", fs.FilesystemTypeBasic, "_tmpfolder") cfg.Folders[0] = config.NewFolderConfiguration("default", fs.FilesystemTypeBasic, tmpFolder)
cfg.Folders[0].Devices = []config.FolderDeviceConfiguration{ cfg.Folders[0].Devices = []config.FolderDeviceConfiguration{
{DeviceID: device1}, {DeviceID: device1},
{DeviceID: device2}, {DeviceID: device2},
@ -232,7 +233,7 @@ func TestRequestVersioningSymlinkAttack(t *testing.T) {
m.StartFolder("default") m.StartFolder("default")
defer m.Stop() defer m.Stop()
defer os.RemoveAll("_tmpfolder") defer os.RemoveAll(tmpFolder)
fc := addFakeConn(m, device2) fc := addFakeConn(m, device2)
fc.folder = "default" fc.folder = "default"
@ -285,9 +286,13 @@ func TestRequestVersioningSymlinkAttack(t *testing.T) {
} }
} }
func setupModelWithConnection() (*Model, *fakeConnection) { func setupModelWithConnection() (*Model, *fakeConnection, string) {
tmpFolder, err := ioutil.TempDir(".", "_request-")
if err != nil {
panic("Failed to create temporary testing dir")
}
cfg := defaultConfig.RawCopy() cfg := defaultConfig.RawCopy()
cfg.Folders[0] = config.NewFolderConfiguration("default", fs.FilesystemTypeBasic, "_tmpfolder") cfg.Folders[0] = config.NewFolderConfiguration("default", fs.FilesystemTypeBasic, tmpFolder)
cfg.Folders[0].Devices = []config.FolderDeviceConfiguration{ cfg.Folders[0].Devices = []config.FolderDeviceConfiguration{
{DeviceID: device1}, {DeviceID: device1},
{DeviceID: device2}, {DeviceID: device2},
@ -303,5 +308,5 @@ func setupModelWithConnection() (*Model, *fakeConnection) {
fc := addFakeConn(m, device2) fc := addFakeConn(m, device2)
fc.folder = "default" fc.folder = "default"
return m, fc return m, fc, tmpFolder
} }

View File

@ -69,6 +69,7 @@ const (
dbUpdateDeleteFile dbUpdateDeleteFile
dbUpdateShortcutFile dbUpdateShortcutFile
dbUpdateHandleSymlink dbUpdateHandleSymlink
dbUpdateInvalidate
) )
const ( const (
@ -234,7 +235,9 @@ func (f *sendReceiveFolder) pull(prevSeq int64, prevIgnoreHash string) (curSeq i
f.model.fmut.RUnlock() f.model.fmut.RUnlock()
curSeq = prevSeq curSeq = prevSeq
if curIgnoreHash = curIgnores.Hash(); curIgnoreHash != prevIgnoreHash { curIgnoreHash = curIgnores.Hash()
ignoresChanged := curIgnoreHash != prevIgnoreHash
if ignoresChanged {
// The ignore patterns have changed. We need to re-evaluate if // The ignore patterns have changed. We need to re-evaluate if
// there are files we need now that were ignored before. // there are files we need now that were ignored before.
l.Debugln(f, "ignore patterns have changed, resetting curSeq") l.Debugln(f, "ignore patterns have changed, resetting curSeq")
@ -263,7 +266,7 @@ func (f *sendReceiveFolder) pull(prevSeq int64, prevIgnoreHash string) (curSeq i
for { for {
tries++ tries++
changed = f.pullerIteration(curIgnores) changed := f.pullerIteration(curIgnores, ignoresChanged)
l.Debugln(f, "changed", changed) l.Debugln(f, "changed", changed)
if changed == 0 { if changed == 0 {
@ -317,7 +320,7 @@ func (f *sendReceiveFolder) pull(prevSeq int64, prevIgnoreHash string) (curSeq i
// returns the number items that should have been synced (even those that // returns the number items that should have been synced (even those that
// might have failed). One puller iteration handles all files currently // might have failed). One puller iteration handles all files currently
// flagged as needed in the folder. // flagged as needed in the folder.
func (f *sendReceiveFolder) pullerIteration(ignores *ignore.Matcher) int { func (f *sendReceiveFolder) pullerIteration(ignores *ignore.Matcher, ignoresChanged bool) int {
pullChan := make(chan pullBlockState) pullChan := make(chan pullBlockState)
copyChan := make(chan copyBlocksState) copyChan := make(chan copyBlocksState)
finisherChan := make(chan *sharedPullerState) finisherChan := make(chan *sharedPullerState)
@ -374,15 +377,21 @@ func (f *sendReceiveFolder) pullerIteration(ignores *ignore.Matcher) int {
// (directories, symlinks and deletes) goes into the "process directly" // (directories, symlinks and deletes) goes into the "process directly"
// pile. // pile.
folderFiles.WithNeed(protocol.LocalDeviceID, func(intf db.FileIntf) bool { // Don't iterate over invalid/ignored files unless ignores have changed
if shouldIgnore(intf, ignores, f.IgnoreDelete) { iterate := folderFiles.WithNeed
if ignoresChanged {
iterate = folderFiles.WithNeedOrInvalid
}
iterate(protocol.LocalDeviceID, func(intf db.FileIntf) bool {
if f.IgnoreDelete && intf.IsDeleted() {
return true return true
} }
if err := fileValid(intf); err != nil { // If filename isn't valid, we can terminate early with an appropriate error.
// The file isn't valid so we can't process it. Pretend that we // in case it is deleted, we don't care about the filename, so don't complain.
// tried and set the error for the file. if !intf.IsDeleted() && runtime.GOOS == "windows" && fs.WindowsInvalidFilename(intf.FileName()) {
f.newError("need", intf.FileName(), err) f.newError("need", intf.FileName(), fs.ErrInvalidFilename)
changed++ changed++
return true return true
} }
@ -390,6 +399,11 @@ func (f *sendReceiveFolder) pullerIteration(ignores *ignore.Matcher) int {
file := intf.(protocol.FileInfo) file := intf.(protocol.FileInfo)
switch { switch {
case ignores.ShouldIgnore(file.Name):
file.Invalidate(f.model.id.Short())
l.Debugln(f, "Handling ignored file", file)
f.dbUpdates <- dbUpdateJob{file, dbUpdateInvalidate}
case file.IsDeleted(): case file.IsDeleted():
processDirectly = append(processDirectly, file) processDirectly = append(processDirectly, file)
changed++ changed++
@ -403,9 +417,15 @@ func (f *sendReceiveFolder) pullerIteration(ignores *ignore.Matcher) int {
if f.model.ConnectedTo(dev) { if f.model.ConnectedTo(dev) {
f.queue.Push(file.Name, file.Size, file.ModTime()) f.queue.Push(file.Name, file.Size, file.ModTime())
changed++ changed++
break return true
} }
} }
l.Debugln(f, "Needed file is unavailable", file)
case runtime.GOOS == "windows" && file.IsSymlink():
file.Invalidate(f.model.id.Short())
l.Debugln(f, "Invalidating symlink (unsupported)", file.Name)
f.dbUpdates <- dbUpdateJob{file, dbUpdateInvalidate}
default: default:
// Directories, symlinks // Directories, symlinks
@ -449,7 +469,7 @@ func (f *sendReceiveFolder) pullerIteration(ignores *ignore.Matcher) int {
// number, hence the deletion coming in again as part of // number, hence the deletion coming in again as part of
// WithNeed, furthermore, the file can simply be of the wrong // WithNeed, furthermore, the file can simply be of the wrong
// type if we haven't yet managed to pull it. // type if we haven't yet managed to pull it.
if ok && !df.IsDeleted() && !df.IsSymlink() && !df.IsDirectory() { if ok && !df.IsDeleted() && !df.IsSymlink() && !df.IsDirectory() && !df.IsInvalid() {
// Put files into buckets per first hash // Put files into buckets per first hash
key := string(df.Blocks[0].Hash) key := string(df.Blocks[0].Hash)
buckets[key] = append(buckets[key], df) buckets[key] = append(buckets[key], df)
@ -457,11 +477,11 @@ func (f *sendReceiveFolder) pullerIteration(ignores *ignore.Matcher) int {
} }
case fi.IsDirectory() && !fi.IsSymlink(): case fi.IsDirectory() && !fi.IsSymlink():
l.Debugln("Handling directory", fi.Name) l.Debugln(f, "Handling directory", fi.Name)
f.handleDir(fi) f.handleDir(fi)
case fi.IsSymlink(): case fi.IsSymlink():
l.Debugln("Handling symlink", fi.Name) l.Debugln(f, "Handling symlink", fi.Name)
f.handleSymlink(fi) f.handleSymlink(fi)
default: default:
@ -566,13 +586,13 @@ nextFile:
doneWg.Wait() doneWg.Wait()
for _, file := range fileDeletions { for _, file := range fileDeletions {
l.Debugln("Deleting file", file.Name) l.Debugln(f, "Deleting file", file.Name)
f.deleteFile(file) f.deleteFile(file)
} }
for i := range dirDeletions { for i := range dirDeletions {
dir := dirDeletions[len(dirDeletions)-i-1] dir := dirDeletions[len(dirDeletions)-i-1]
l.Debugln("Deleting dir", dir.Name) l.Debugln(f, "Deleting dir", dir.Name)
f.deleteDir(dir, ignores) f.deleteDir(dir, ignores)
} }
@ -1516,8 +1536,9 @@ func (f *sendReceiveFolder) dbUpdaterRoutine() {
changedDirs[filepath.Dir(job.file.Name)] = struct{}{} changedDirs[filepath.Dir(job.file.Name)] = struct{}{}
case dbUpdateHandleDir: case dbUpdateHandleDir:
changedDirs[job.file.Name] = struct{}{} changedDirs[job.file.Name] = struct{}{}
case dbUpdateHandleSymlink: case dbUpdateHandleSymlink, dbUpdateInvalidate:
// fsyncing symlinks is only supported by MacOS, ignore // fsyncing symlinks is only supported by MacOS
// and invalidated files are db only changes -> no sync
} }
if job.file.IsInvalid() || (job.file.IsDirectory() && !job.file.IsSymlink()) { if job.file.IsInvalid() || (job.file.IsDirectory() && !job.file.IsSymlink()) {
@ -1722,47 +1743,6 @@ func (l fileErrorList) Swap(a, b int) {
l[a], l[b] = l[b], l[a] l[a], l[b] = l[b], l[a]
} }
// fileValid returns nil when the file is valid for processing, or an error if it's not
func fileValid(file db.FileIntf) error {
switch {
case file.IsDeleted():
// We don't care about file validity if we're not supposed to have it
return nil
case runtime.GOOS == "windows" && file.IsSymlink():
return errSymlinksUnsupported
case runtime.GOOS == "windows" && windowsInvalidFilename(file.FileName()):
return fs.ErrInvalidFilename
}
return nil
}
var windowsDisallowedCharacters = string([]rune{
'<', '>', ':', '"', '|', '?', '*',
0, 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,
})
func windowsInvalidFilename(name string) bool {
// None of the path components should end in space
for _, part := range strings.Split(name, `\`) {
if len(part) == 0 {
continue
}
if part[len(part)-1] == ' ' {
// Names ending in space are not valid.
return true
}
}
// The path must not contain any disallowed characters
return strings.ContainsAny(name, windowsDisallowedCharacters)
}
// byComponentCount sorts by the number of path components in Name, that is // byComponentCount sorts by the number of path components in Name, that is
// "x/y" sorts before "foo/bar/baz". // "x/y" sorts before "foo/bar/baz".
type byComponentCount []protocol.FileInfo type byComponentCount []protocol.FileInfo

View File

@ -1,6 +1,5 @@
// Code generated by protoc-gen-gogo. // Code generated by protoc-gen-gogo. DO NOT EDIT.
// source: bep.proto // source: bep.proto
// DO NOT EDIT!
/* /*
Package protocol is a generated protocol buffer package. Package protocol is a generated protocol buffer package.
@ -307,7 +306,7 @@ type FileInfo struct {
NoPermissions bool `protobuf:"varint,8,opt,name=no_permissions,json=noPermissions,proto3" json:"no_permissions,omitempty"` NoPermissions bool `protobuf:"varint,8,opt,name=no_permissions,json=noPermissions,proto3" json:"no_permissions,omitempty"`
Version Vector `protobuf:"bytes,9,opt,name=version" json:"version"` Version Vector `protobuf:"bytes,9,opt,name=version" json:"version"`
Sequence int64 `protobuf:"varint,10,opt,name=sequence,proto3" json:"sequence,omitempty"` Sequence int64 `protobuf:"varint,10,opt,name=sequence,proto3" json:"sequence,omitempty"`
Blocks []BlockInfo `protobuf:"bytes,16,rep,name=Blocks" json:"Blocks"` Blocks []BlockInfo `protobuf:"bytes,16,rep,name=Blocks,json=blocks" json:"Blocks"`
SymlinkTarget string `protobuf:"bytes,17,opt,name=symlink_target,json=symlinkTarget,proto3" json:"symlink_target,omitempty"` SymlinkTarget string `protobuf:"bytes,17,opt,name=symlink_target,json=symlinkTarget,proto3" json:"symlink_target,omitempty"`
} }
@ -4218,61 +4217,61 @@ var fileDescriptorBep = []byte{
0xf3, 0xb0, 0x17, 0x27, 0x71, 0xb8, 0xbe, 0x5c, 0x51, 0x85, 0xa3, 0x3c, 0x4e, 0x42, 0xd3, 0xc3, 0xf3, 0xb0, 0x17, 0x27, 0x71, 0xb8, 0xbe, 0x5c, 0x51, 0x85, 0xa3, 0x3c, 0x4e, 0x42, 0xd3, 0xc3,
0xe3, 0x36, 0x80, 0xd1, 0x52, 0xae, 0xb2, 0x5a, 0x7c, 0x2f, 0xae, 0xc5, 0xc1, 0x05, 0xf5, 0x82, 0xe3, 0x36, 0x80, 0xd1, 0x52, 0xae, 0xb2, 0x5a, 0x7c, 0x2f, 0xae, 0xc5, 0xc1, 0x05, 0xf5, 0x82,
0x4e, 0x7b, 0xb3, 0xa2, 0xb9, 0x44, 0x3f, 0x82, 0x42, 0xd3, 0xa1, 0xe3, 0x17, 0xb1, 0xd2, 0xef, 0x4e, 0x7b, 0xb3, 0xa2, 0xb9, 0x44, 0x3f, 0x82, 0x42, 0xd3, 0xa1, 0xe3, 0x17, 0xb1, 0xd2, 0xef,
0x6c, 0xee, 0xc7, 0xec, 0xa9, 0x7c, 0x46, 0xc0, 0x30, 0x74, 0x7f, 0x39, 0x73, 0x6c, 0xf7, 0x85, 0x6c, 0xee, 0xc7, 0xec, 0xa9, 0x7c, 0x16, 0x46, 0x0c, 0x18, 0x86, 0xee, 0x2f, 0x67, 0x8e, 0xed,
0x11, 0x98, 0xde, 0x94, 0x04, 0xf2, 0x1e, 0x6f, 0xf8, 0x91, 0x75, 0xc8, 0x8c, 0x3f, 0x17, 0xff, 0xbe, 0x30, 0x02, 0xd3, 0x9b, 0x92, 0x40, 0xde, 0xe3, 0x0d, 0x3f, 0xb2, 0x0e, 0x99, 0xf1, 0xe7,
0xf8, 0xcd, 0x61, 0xa6, 0xee, 0x42, 0x39, 0xd9, 0x27, 0x2c, 0x29, 0x3a, 0x99, 0xf8, 0x24, 0x60, 0xe2, 0x1f, 0xbf, 0x39, 0xcc, 0xd4, 0x5d, 0x28, 0x27, 0xfb, 0x84, 0x25, 0x45, 0x27, 0x13, 0x9f,
0xf9, 0xcf, 0xe1, 0x68, 0x96, 0x64, 0x35, 0xcb, 0x02, 0xe2, 0x59, 0x45, 0x20, 0x5e, 0x98, 0xfe, 0x04, 0x2c, 0xff, 0x39, 0x1c, 0xcd, 0x92, 0xac, 0x66, 0x59, 0x40, 0x3c, 0xab, 0x08, 0xc4, 0x0b,
0x05, 0xcb, 0x74, 0x15, 0xb3, 0x71, 0xa8, 0xe3, 0x97, 0xc4, 0x7c, 0x61, 0x30, 0x07, 0xcf, 0x73, 0xd3, 0xbf, 0x60, 0x99, 0xae, 0x62, 0x36, 0x0e, 0x75, 0xfc, 0x92, 0x98, 0x2f, 0x0c, 0xe6, 0xe0,
0x29, 0x34, 0x3c, 0x31, 0xfd, 0x8b, 0xe8, 0xbc, 0x5f, 0x42, 0x81, 0xf3, 0x8a, 0xbe, 0x80, 0xd2, 0x79, 0x2e, 0x85, 0x86, 0x27, 0xa6, 0x7f, 0x11, 0x9d, 0xf7, 0x4b, 0x28, 0x70, 0x5e, 0xd1, 0x17,
0x98, 0x2e, 0xdc, 0x60, 0xd3, 0xeb, 0xf7, 0xd2, 0xad, 0x82, 0x79, 0xa2, 0xc8, 0x12, 0x60, 0xfd, 0x50, 0x1a, 0xd3, 0x85, 0x1b, 0x6c, 0x7a, 0xfd, 0x5e, 0xba, 0x55, 0x30, 0x4f, 0x14, 0x59, 0x02,
0x14, 0x8a, 0x91, 0x0b, 0x3d, 0x4c, 0xfa, 0x98, 0xd8, 0xbc, 0x77, 0x8d, 0xc2, 0xed, 0xe6, 0x7f, 0xac, 0x9f, 0x42, 0x31, 0x72, 0xa1, 0x87, 0x49, 0x1f, 0x13, 0x9b, 0xf7, 0xae, 0x51, 0xb8, 0xdd,
0x69, 0x3a, 0x0b, 0x7e, 0x79, 0x11, 0xf3, 0x49, 0xfd, 0x2f, 0x02, 0x14, 0x71, 0x98, 0x36, 0x3f, 0xfc, 0x2f, 0x4d, 0x67, 0xc1, 0x2f, 0x2f, 0x62, 0x3e, 0xa9, 0xff, 0x45, 0x80, 0x22, 0x0e, 0xd3,
0x48, 0x3d, 0x1b, 0xf9, 0xad, 0x67, 0x63, 0x23, 0xb0, 0xec, 0x96, 0xc0, 0x62, 0x8d, 0xe4, 0x52, 0xe6, 0x07, 0xa9, 0x67, 0x23, 0xbf, 0xf5, 0x6c, 0x6c, 0x04, 0x96, 0xdd, 0x12, 0x58, 0xac, 0x91,
0x1a, 0xd9, 0x30, 0x27, 0xbe, 0x95, 0xb9, 0xfc, 0x5b, 0x98, 0x2b, 0xa4, 0x98, 0x7b, 0x08, 0xbb, 0x5c, 0x4a, 0x23, 0x1b, 0xe6, 0xc4, 0xb7, 0x32, 0x97, 0x7f, 0x0b, 0x73, 0x85, 0x14, 0x73, 0x0f,
0x13, 0x8f, 0xce, 0xd8, 0xc3, 0x40, 0x3d, 0xd3, 0x5b, 0x46, 0xf5, 0xbc, 0x13, 0x5a, 0x87, 0xb1, 0x61, 0x77, 0xe2, 0xd1, 0x19, 0x7b, 0x18, 0xa8, 0x67, 0x7a, 0xcb, 0xa8, 0x9e, 0x77, 0x42, 0xeb,
0xb1, 0x6e, 0x40, 0x09, 0x13, 0x7f, 0x4e, 0x5d, 0x9f, 0xdc, 0x7a, 0x6d, 0x04, 0xa2, 0x65, 0x06, 0x30, 0x36, 0xd6, 0x0d, 0x28, 0x61, 0xe2, 0xcf, 0xa9, 0xeb, 0x93, 0x5b, 0xaf, 0x8d, 0x40, 0xb4,
0x26, 0xbb, 0x74, 0x15, 0xb3, 0x31, 0x7a, 0x04, 0xe2, 0x98, 0x5a, 0xfc, 0xca, 0xbb, 0xe9, 0x1a, 0xcc, 0xc0, 0x64, 0x97, 0xae, 0x62, 0x36, 0x46, 0x8f, 0x40, 0x1c, 0x53, 0x8b, 0x5f, 0x79, 0x37,
0xd2, 0x3c, 0x8f, 0x7a, 0x2d, 0x6a, 0x11, 0xcc, 0x00, 0xf5, 0x39, 0x48, 0x6d, 0xfa, 0xd2, 0x75, 0x5d, 0x43, 0x9a, 0xe7, 0x51, 0xaf, 0x45, 0x2d, 0x82, 0x19, 0xa0, 0x3e, 0x07, 0xa9, 0x4d, 0x5f,
0xa8, 0x69, 0xf5, 0x3d, 0x3a, 0x0d, 0x1b, 0xf4, 0xad, 0x8d, 0xa6, 0x0d, 0xc5, 0x05, 0x6b, 0x45, 0xba, 0x0e, 0x35, 0xad, 0xbe, 0x47, 0xa7, 0x61, 0x83, 0xbe, 0xb5, 0xd1, 0xb4, 0xa1, 0xb8, 0x60,
0x71, 0xab, 0x79, 0xb0, 0xdd, 0x1a, 0xae, 0x6f, 0xc4, 0xfb, 0x56, 0xac, 0xa7, 0x68, 0x69, 0xfd, 0xad, 0x28, 0x6e, 0x35, 0x0f, 0xb6, 0x5b, 0xc3, 0xf5, 0x8d, 0x78, 0xdf, 0x8a, 0xf5, 0x14, 0x2d,
0x6f, 0x02, 0x28, 0xb7, 0xa3, 0x51, 0x07, 0x2a, 0x1c, 0x69, 0xa4, 0xfe, 0x49, 0x8e, 0xde, 0xe5, 0xad, 0xff, 0x4d, 0x00, 0xe5, 0x76, 0x34, 0xea, 0x40, 0x85, 0x23, 0x8d, 0xd4, 0x3f, 0xc9, 0xd1,
0x20, 0xd6, 0x95, 0x60, 0x91, 0x8c, 0xdf, 0xfa, 0xa0, 0xa5, 0xf4, 0x9f, 0x7b, 0x37, 0xfd, 0x3f, 0xbb, 0x1c, 0xc4, 0xba, 0x12, 0x2c, 0x92, 0xf1, 0x5b, 0x1f, 0xb4, 0x94, 0xfe, 0x73, 0xef, 0xa6,
0x82, 0x9d, 0x51, 0x28, 0x98, 0xe4, 0xf9, 0x16, 0xd5, 0xdc, 0x51, 0xbe, 0x99, 0x95, 0x32, 0xb8, 0xff, 0x47, 0xb0, 0xc3, 0x74, 0x96, 0x3c, 0xdf, 0xa2, 0x9a, 0x3b, 0xca, 0x37, 0xb3, 0x52, 0x06,
0x3a, 0xe2, 0x4a, 0x62, 0xf6, 0x7a, 0x01, 0xc4, 0xbe, 0xed, 0x4e, 0xeb, 0x87, 0x90, 0x6f, 0x39, 0x57, 0x47, 0x5c, 0x49, 0xcc, 0x5e, 0x2f, 0x80, 0xd8, 0xb7, 0xdd, 0x69, 0xfd, 0x10, 0xf2, 0x2d,
0x94, 0x25, 0xac, 0xe0, 0x11, 0xd3, 0xa7, 0x6e, 0xcc, 0x23, 0x9f, 0x1d, 0xff, 0x35, 0x0b, 0x95, 0x87, 0xb2, 0x84, 0x15, 0x3c, 0x62, 0xfa, 0xd4, 0x8d, 0x79, 0xe4, 0xb3, 0xe3, 0xbf, 0x66, 0xa1,
0xd4, 0xaf, 0x15, 0x7a, 0x0c, 0xbb, 0xad, 0xee, 0xf9, 0x60, 0xa8, 0x61, 0xa3, 0xd5, 0xd3, 0x4f, 0x92, 0xfa, 0xb5, 0x42, 0x8f, 0x61, 0xb7, 0xd5, 0x3d, 0x1f, 0x0c, 0x35, 0x6c, 0xb4, 0x7a, 0xfa,
0x3b, 0x67, 0x52, 0x46, 0x39, 0x58, 0xad, 0x55, 0x79, 0xb6, 0x01, 0x6d, 0xff, 0x35, 0x1d, 0x42, 0x69, 0xe7, 0x4c, 0xca, 0x28, 0x07, 0xab, 0xb5, 0x2a, 0xcf, 0x36, 0xa0, 0xed, 0xbf, 0xa6, 0x43,
0xbe, 0xa3, 0xb7, 0xb5, 0xdf, 0x4a, 0x82, 0x72, 0x77, 0xb5, 0x56, 0xa5, 0x14, 0x90, 0x3f, 0x41, 0xc8, 0x77, 0xf4, 0xb6, 0xf6, 0x5b, 0x49, 0x50, 0xee, 0xae, 0xd6, 0xaa, 0x94, 0x02, 0xf2, 0x27,
0x9f, 0x40, 0x95, 0x01, 0x8c, 0xf3, 0x7e, 0xbb, 0x31, 0xd4, 0xa4, 0xac, 0xa2, 0xac, 0xd6, 0xea, 0xe8, 0x13, 0xa8, 0x32, 0x80, 0x71, 0xde, 0x6f, 0x37, 0x86, 0x9a, 0x94, 0x55, 0x94, 0xd5, 0x5a,
0xfe, 0x75, 0x5c, 0xc4, 0xf9, 0x87, 0x50, 0xc4, 0xda, 0x6f, 0xce, 0xb5, 0xc1, 0x50, 0xca, 0x29, 0xdd, 0xbf, 0x8e, 0x8b, 0x38, 0xff, 0x10, 0x8a, 0x58, 0xfb, 0xcd, 0xb9, 0x36, 0x18, 0x4a, 0x39,
0xfb, 0xab, 0xb5, 0x8a, 0x52, 0xc0, 0x58, 0x35, 0x0f, 0xa1, 0x84, 0xb5, 0x41, 0xbf, 0xa7, 0x0f, 0x65, 0x7f, 0xb5, 0x56, 0x51, 0x0a, 0x18, 0xab, 0xe6, 0x21, 0x94, 0xb0, 0x36, 0xe8, 0xf7, 0xf4,
0x34, 0x49, 0x54, 0x7e, 0xb0, 0x5a, 0xab, 0x77, 0xb6, 0x50, 0x51, 0x95, 0xfe, 0x04, 0xf6, 0xda, 0x81, 0x26, 0x89, 0xca, 0x0f, 0x56, 0x6b, 0xf5, 0xce, 0x16, 0x2a, 0xaa, 0xd2, 0x9f, 0xc0, 0x5e,
0xbd, 0xaf, 0xf4, 0x6e, 0xaf, 0xd1, 0x36, 0xfa, 0xb8, 0x77, 0x86, 0xb5, 0xc1, 0x40, 0xca, 0x2b, 0xbb, 0xf7, 0x95, 0xde, 0xed, 0x35, 0xda, 0x46, 0x1f, 0xf7, 0xce, 0xb0, 0x36, 0x18, 0x48, 0x79,
0x87, 0xab, 0xb5, 0xfa, 0x7e, 0x0a, 0x7f, 0xa3, 0xe8, 0x3e, 0x00, 0xb1, 0xdf, 0xd1, 0xcf, 0xa4, 0xe5, 0x70, 0xb5, 0x56, 0xdf, 0x4f, 0xe1, 0x6f, 0x14, 0xdd, 0x07, 0x20, 0xf6, 0x3b, 0xfa, 0x99,
0x82, 0x72, 0x67, 0xb5, 0x56, 0xdf, 0x4b, 0x41, 0x43, 0x52, 0xc3, 0x88, 0x5b, 0xdd, 0xde, 0x40, 0x54, 0x50, 0xee, 0xac, 0xd6, 0xea, 0x7b, 0x29, 0x68, 0x48, 0x6a, 0x18, 0x71, 0xab, 0xdb, 0x1b,
0x93, 0x8a, 0x37, 0x22, 0x66, 0x64, 0x1f, 0xff, 0x0e, 0xd0, 0xcd, 0x9f, 0x4f, 0xf4, 0x00, 0x44, 0x68, 0x52, 0xf1, 0x46, 0xc4, 0x8c, 0xec, 0xe3, 0xdf, 0x01, 0xba, 0xf9, 0xf3, 0x89, 0x1e, 0x80,
0xbd, 0xa7, 0x6b, 0x52, 0x86, 0xc7, 0x7f, 0x13, 0xa1, 0x53, 0x97, 0xa0, 0x3a, 0xe4, 0xba, 0x5f, 0xa8, 0xf7, 0x74, 0x4d, 0xca, 0xf0, 0xf8, 0x6f, 0x22, 0x74, 0xea, 0x12, 0x54, 0x87, 0x5c, 0xf7,
0x7f, 0x29, 0x09, 0xca, 0x0f, 0x57, 0x6b, 0xf5, 0xde, 0x4d, 0x50, 0xf7, 0xeb, 0x2f, 0x8f, 0x29, 0xeb, 0x2f, 0x25, 0x41, 0xf9, 0xe1, 0x6a, 0xad, 0xde, 0xbb, 0x09, 0xea, 0x7e, 0xfd, 0xe5, 0x31,
0x54, 0xd2, 0x1b, 0xd7, 0xa1, 0xf4, 0x54, 0x1b, 0x36, 0xda, 0x8d, 0x61, 0x43, 0xca, 0xf0, 0x2b, 0x85, 0x4a, 0x7a, 0xe3, 0x3a, 0x94, 0x9e, 0x6a, 0xc3, 0x46, 0xbb, 0x31, 0x6c, 0x48, 0x19, 0x7e,
0xc5, 0xee, 0xa7, 0x24, 0x30, 0x99, 0x08, 0x0f, 0x20, 0xaf, 0x6b, 0xcf, 0x34, 0x2c, 0x09, 0xca, 0xa5, 0xd8, 0xfd, 0x94, 0x04, 0x26, 0x13, 0xe1, 0x01, 0xe4, 0x75, 0xed, 0x99, 0x86, 0x25, 0x41,
0xde, 0x6a, 0xad, 0xee, 0xc4, 0x00, 0x9d, 0x5c, 0x12, 0x0f, 0xd5, 0xa0, 0xd0, 0xe8, 0x7e, 0xd5, 0xd9, 0x5b, 0xad, 0xd5, 0x9d, 0x18, 0xa0, 0x93, 0x4b, 0xe2, 0xa1, 0x1a, 0x14, 0x1a, 0xdd, 0xaf,
0x78, 0x3e, 0x90, 0xb2, 0x0a, 0x5a, 0xad, 0xd5, 0xdd, 0xd8, 0xdd, 0x70, 0x5e, 0x9a, 0x4b, 0xff, 0x1a, 0xcf, 0x07, 0x52, 0x56, 0x41, 0xab, 0xb5, 0xba, 0x1b, 0xbb, 0x1b, 0xce, 0x4b, 0x73, 0xe9,
0xf8, 0xbf, 0x02, 0x54, 0xd3, 0x0f, 0x2e, 0xaa, 0x81, 0x78, 0xda, 0xe9, 0x6a, 0xf1, 0x71, 0x69, 0x1f, 0xff, 0x57, 0x80, 0x6a, 0xfa, 0xc1, 0x45, 0x35, 0x10, 0x4f, 0x3b, 0x5d, 0x2d, 0x3e, 0x2e,
0x5f, 0x38, 0x46, 0x47, 0x50, 0x6e, 0x77, 0xb0, 0xd6, 0x1a, 0xf6, 0xf0, 0xf3, 0x38, 0x96, 0x34, 0xed, 0x0b, 0xc7, 0xe8, 0x08, 0xca, 0xed, 0x0e, 0xd6, 0x5a, 0xc3, 0x1e, 0x7e, 0x1e, 0xc7, 0x92,
0xa8, 0x6d, 0x7b, 0xac, 0xc0, 0x97, 0xe8, 0x67, 0x50, 0x1d, 0x3c, 0x7f, 0xda, 0xed, 0xe8, 0xbf, 0x06, 0xb5, 0x6d, 0x8f, 0x15, 0xf8, 0x12, 0xfd, 0x0c, 0xaa, 0x83, 0xe7, 0x4f, 0xbb, 0x1d, 0xfd,
0x36, 0xd8, 0x8e, 0x59, 0xe5, 0xd1, 0x6a, 0xad, 0xde, 0xdf, 0x02, 0x93, 0xb9, 0x47, 0xc6, 0x66, 0xd7, 0x06, 0xdb, 0x31, 0xab, 0x3c, 0x5a, 0xad, 0xd5, 0xfb, 0x5b, 0x60, 0x32, 0xf7, 0xc8, 0xd8,
0x40, 0xac, 0x01, 0x7f, 0x44, 0x42, 0x67, 0x49, 0x40, 0x2d, 0xd8, 0x8b, 0x97, 0x6e, 0x0e, 0xcb, 0x0c, 0x88, 0x35, 0xe0, 0x8f, 0x48, 0xe8, 0x2c, 0x09, 0xa8, 0x05, 0x7b, 0xf1, 0xd2, 0xcd, 0x61,
0x29, 0x9f, 0xac, 0xd6, 0xea, 0x47, 0xdf, 0xbb, 0x3e, 0x39, 0xbd, 0x24, 0xa0, 0x07, 0x50, 0x8c, 0x39, 0xe5, 0x93, 0xd5, 0x5a, 0xfd, 0xe8, 0x7b, 0xd7, 0x27, 0xa7, 0x97, 0x04, 0xf4, 0x00, 0x8a,
0x36, 0x89, 0x2b, 0x29, 0xbd, 0x34, 0x5a, 0x70, 0xfc, 0x67, 0x01, 0xca, 0x49, 0xbb, 0x0a, 0x09, 0xd1, 0x26, 0x71, 0x25, 0xa5, 0x97, 0x46, 0x0b, 0x8e, 0xff, 0x2c, 0x40, 0x39, 0x69, 0x57, 0x21,
0xd7, 0x7b, 0x86, 0x86, 0x71, 0x0f, 0xc7, 0x0c, 0x24, 0x4e, 0x9d, 0xb2, 0x21, 0xba, 0x0f, 0xc5, 0xe1, 0x7a, 0xcf, 0xd0, 0x30, 0xee, 0xe1, 0x98, 0x81, 0xc4, 0xa9, 0x53, 0x36, 0x44, 0xf7, 0xa1,
0x33, 0x4d, 0xd7, 0x70, 0xa7, 0x15, 0x0b, 0x23, 0x81, 0x9c, 0x11, 0x97, 0x78, 0xf6, 0x18, 0x7d, 0x78, 0xa6, 0xe9, 0x1a, 0xee, 0xb4, 0x62, 0x61, 0x24, 0x90, 0x33, 0xe2, 0x12, 0xcf, 0x1e, 0xa3,
0x0c, 0x55, 0xbd, 0x67, 0x0c, 0xce, 0x5b, 0x4f, 0xe2, 0xd0, 0xd9, 0xf9, 0xa9, 0xad, 0x06, 0x8b, 0x8f, 0xa1, 0xaa, 0xf7, 0x8c, 0xc1, 0x79, 0xeb, 0x49, 0x1c, 0x3a, 0x3b, 0x3f, 0xb5, 0xd5, 0x60,
0xf1, 0x05, 0xe3, 0xf3, 0x38, 0xd4, 0xd0, 0xb3, 0x46, 0xb7, 0xd3, 0xe6, 0xd0, 0x9c, 0x22, 0xaf, 0x31, 0xbe, 0x60, 0x7c, 0x1e, 0x87, 0x1a, 0x7a, 0xd6, 0xe8, 0x76, 0xda, 0x1c, 0x9a, 0x53, 0xe4,
0xd6, 0xea, 0xdd, 0x04, 0xda, 0xe1, 0x7f, 0x1e, 0x21, 0xf6, 0xd8, 0x82, 0xda, 0xf7, 0x37, 0x26, 0xd5, 0x5a, 0xbd, 0x9b, 0x40, 0x3b, 0xfc, 0xcf, 0x23, 0xc4, 0x1e, 0x5b, 0x50, 0xfb, 0xfe, 0xc6,
0xa4, 0x42, 0xa1, 0xd1, 0xef, 0x6b, 0x7a, 0x3b, 0xbe, 0xfd, 0xc6, 0xd7, 0x98, 0xcf, 0x89, 0x6b, 0x84, 0x54, 0x28, 0x34, 0xfa, 0x7d, 0x4d, 0x6f, 0xc7, 0xb7, 0xdf, 0xf8, 0x1a, 0xf3, 0x39, 0x71,
0x85, 0x88, 0xd3, 0x1e, 0x3e, 0xd3, 0x86, 0xf1, 0xe5, 0x37, 0x88, 0x53, 0x1a, 0xbe, 0xe0, 0xcd, 0xad, 0x10, 0x71, 0xda, 0xc3, 0x67, 0xda, 0x30, 0xbe, 0xfc, 0x06, 0x71, 0x4a, 0xc3, 0x17, 0xbc,
0x83, 0xd7, 0xdf, 0xd5, 0x32, 0xdf, 0x7e, 0x57, 0xcb, 0xbc, 0xbe, 0xaa, 0x09, 0xdf, 0x5e, 0xd5, 0x79, 0xf0, 0xfa, 0xbb, 0x5a, 0xe6, 0xdb, 0xef, 0x6a, 0x99, 0xd7, 0x57, 0x35, 0xe1, 0xdb, 0xab,
0x84, 0x7f, 0x5c, 0xd5, 0x32, 0xff, 0xba, 0xaa, 0x09, 0xdf, 0xfc, 0xb3, 0x26, 0x8c, 0x0a, 0xac, 0x9a, 0xf0, 0x8f, 0xab, 0x5a, 0xe6, 0x5f, 0x57, 0x35, 0xe1, 0x9b, 0x7f, 0xd6, 0x84, 0x51, 0x81,
0x91, 0x7d, 0xf1, 0xbf, 0x00, 0x00, 0x00, 0xff, 0xff, 0xad, 0x8a, 0xef, 0x7f, 0x8f, 0x0e, 0x00, 0x35, 0xb2, 0x2f, 0xfe, 0x17, 0x00, 0x00, 0xff, 0xff, 0xe3, 0x49, 0x45, 0xc4, 0x8f, 0x0e, 0x00,
0x00, 0x00,
} }

View File

@ -113,6 +113,13 @@ func (f FileInfo) WinsConflict(other FileInfo) bool {
return f.Version.Compare(other.Version) == ConcurrentGreater return f.Version.Compare(other.Version) == ConcurrentGreater
} }
func (f *FileInfo) Invalidate(invalidatedBy ShortID) {
f.Invalid = true
f.ModifiedBy = invalidatedBy
f.Blocks = nil
f.Sequence = 0
}
func (b BlockInfo) String() string { func (b BlockInfo) String() string {
return fmt.Sprintf("Block{%d/%d/%d/%x}", b.Offset, b.Size, b.WeakHash, b.Hash) return fmt.Sprintf("Block{%d/%d/%d/%x}", b.Offset, b.Size, b.WeakHash, b.Hash)
} }

View File

@ -1,6 +1,5 @@
// Code generated by protoc-gen-gogo. // Code generated by protoc-gen-gogo. DO NOT EDIT.
// source: deviceid_test.proto // source: deviceid_test.proto
// DO NOT EDIT!
/* /*
Package protocol is a generated protocol buffer package. Package protocol is a generated protocol buffer package.