Compare commits

...

12 Commits

Author SHA1 Message Date
Simon Frei
c00c6b3957 build: Bump quic-go to 0.24.0 2021-12-30 13:07:01 +01:00
Gahl Saraf
cc39341eb9 lib: Fix panic due to closed event subscriptions on shutdown (#8079) 2021-12-22 20:16:21 +01:00
ignacy123
bf7f82f7b2 cmd/syncthing/cli: Add command to show pending devices/folders (fixes #8068) (#8069) 2021-12-09 21:18:47 +01:00
greatroar
eb857dbc45 lib/osutil: Use x/sys/windows for SetLowPriority 2021-11-27 15:35:07 +01:00
greatroar
e7620e951d cmd/stdiscosrv: use strconv.Itoa 2021-11-27 15:35:07 +01:00
greatroar
286a25ae49 lib/upgrade: Use strings.Reader instead of bytes.Buffer 2021-11-27 15:35:07 +01:00
greatroar
ae70046b49 lib/protocol: Remove unused sorting boilerplate 2021-11-27 15:35:07 +01:00
greatroar
c366933416 lib/sync: Make the clock a function pointer 2021-11-27 15:35:07 +01:00
greatroar
6a9716e8a1 lib/model: Return index from deviceActivity.leastBusy
This way, we don't need a second loop over the Availabilities to remove
the selected item.
2021-11-26 12:07:43 +01:00
villekalliomaki
b84ee4d240 gui: Notice on folders with ignoreDelete enabled (fixes #8050) (#8054) 2021-11-25 14:04:59 +01:00
greatroar
8a1e54d58a lib/fs: Optimize Canonicalize
When pathSep is a constant, the compiler precomputes pathSep+pathSep and
".."+pathSep instead of emitting function calls to compute "//" and
"../". Benchmark results in lib/osutil:

name                old time/op    new time/op    delta
TraversesSymlink-8    8.86µs ± 3%    8.53µs ± 4%  -3.79%  (p=0.000 n=18+20)

name                old alloc/op   new alloc/op   delta
TraversesSymlink-8    1.06kB ± 0%    1.06kB ± 0%    ~     (all equal)

name                old allocs/op  new allocs/op  delta
TraversesSymlink-8      15.0 ± 0%      15.0 ± 0%    ~     (all equal)
2021-11-23 21:25:29 +01:00
greatroar
3e032c4da6 lib/fs: optimize Windows path checking/sanitizing
name                          old time/op    new time/op    delta
WindowsInvalidFilenameValid-8     875ns ± 1%     150ns ± 1%  -82.84%  (p=0.000 n=9+9)
WindowsInvalidFilenameNUL-8       276ns ± 4%     121ns ± 3%  -56.26%  (p=0.000 n=10+10)

name                          old alloc/op   new alloc/op   delta
WindowsInvalidFilenameValid-8     32.0B ± 0%     16.0B ± 0%  -50.00%  (p=0.000 n=10+10)
WindowsInvalidFilenameNUL-8       32.0B ± 0%     19.0B ± 0%  -40.62%  (p=0.000 n=10+10)

name                          old allocs/op  new allocs/op  delta
WindowsInvalidFilenameValid-8      2.00 ± 0%      1.00 ± 0%  -50.00%  (p=0.000 n=10+10)
WindowsInvalidFilenameNUL-8        2.00 ± 0%      2.00 ± 0%     ~     (all equal)
2021-11-23 21:25:29 +01:00
21 changed files with 148 additions and 143 deletions

View File

@@ -419,7 +419,7 @@ func fixupAddresses(remote *net.TCPAddr, addresses []string) []string {
// If zero port was specified, use remote port.
if port == "0" && remote.Port > 0 {
port = fmt.Sprintf("%d", remote.Port)
port = strconv.Itoa(remote.Port)
}
}

View File

@@ -0,0 +1,29 @@
// Copyright (C) 2021 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
package cli
import (
"github.com/urfave/cli"
)
var pendingCommand = cli.Command{
Name: "pending",
HideHelp: true,
Usage: "Pending subcommand group",
Subcommands: []cli.Command{
{
Name: "devices",
Usage: "Show pending devices",
Action: expects(0, indexDumpOutput("cluster/pending/devices")),
},
{
Name: "folders",
Usage: "Show pending folders",
Action: expects(0, indexDumpOutput("cluster/pending/folders")),
},
},
}

View File

@@ -35,6 +35,7 @@ var showCommand = cli.Command{
Usage: "Report about connections to other devices",
Action: expects(0, indexDumpOutput("system/connections")),
},
pendingCommand,
{
Name: "usage",
Usage: "Show usage report",

2
go.mod
View File

@@ -30,7 +30,7 @@ require (
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
github.com/klauspost/cpuid/v2 v2.0.9 // indirect
github.com/lib/pq v1.10.3
github.com/lucas-clemente/quic-go v0.23.0
github.com/lucas-clemente/quic-go v0.24.0
github.com/maruel/panicparse v1.6.1
github.com/maxbrunsfeld/counterfeiter/v6 v6.3.0
github.com/minio/sha256-simd v1.0.0

4
go.sum
View File

@@ -242,8 +242,8 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lib/pq v1.10.3 h1:v9QZf2Sn6AmjXtQeFpdoq/eaNtYP6IN+7lcrygsIAtg=
github.com/lib/pq v1.10.3/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lucas-clemente/quic-go v0.22.0/go.mod h1:vF5M1XqhBAHgbjKcJOXY3JZz3GP0T3FQhz/uyOUS38Q=
github.com/lucas-clemente/quic-go v0.23.0 h1:5vFnKtZ6nHDFsc/F3uuiF4T3y/AXaQdxjUqiVw26GZE=
github.com/lucas-clemente/quic-go v0.23.0/go.mod h1:paZuzjXCE5mj6sikVLMvqXk8lJV2AsqtJ6bDhjEfxx0=
github.com/lucas-clemente/quic-go v0.24.0 h1:ToR7SIIEdrgOhgVTHvPgdVRJfgVy+N0wQAagH7L4d5g=
github.com/lucas-clemente/quic-go v0.24.0/go.mod h1:paZuzjXCE5mj6sikVLMvqXk8lJV2AsqtJ6bDhjEfxx0=
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/marten-seemann/qpack v0.2.1/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc=

View File

@@ -428,6 +428,13 @@
<div ng-if="model[folder.id].ignorePatterns">
<a href="" ng-click="editFolderExisting(folder, '#folder-ignores')"><i class="small" translate>Reduced by ignore patterns</i></a>
</div>
<div ng-if="folder.ignoreDelete">
<i class="small">
<span translate style="white-space: normal;">Altered by ignoring deletes.</span>
<br>
<a href="https://docs.syncthing.net/advanced/folder-ignoredelete" target="_blank"><span class="fas fa-question-circle"></span>&nbsp;<span translate>Help</span></a>
</i>
</div>
</td>
</tr>
<tr ng-if="model[folder.id].needTotalItems > 0">

View File

@@ -253,7 +253,7 @@ func IsInternal(file string) bool {
// - /foo/bar -> foo/bar
// - / -> "."
func Canonicalize(file string) (string, error) {
pathSep := string(PathSeparator)
const pathSep = string(PathSeparator)
if strings.HasPrefix(file, pathSep+pathSep) {
// The relative path may pretend to be an absolute path within

View File

@@ -47,25 +47,13 @@ func getHomeDir() (string, error) {
return os.UserHomeDir()
}
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,
})
windowsDisallowedNames = []string{"CON", "PRN", "AUX", "NUL",
"COM0", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
"LPT0", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9",
}
)
const windowsDisallowedCharacters = (`<>:"|?*` +
"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f" +
"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f")
func WindowsInvalidFilename(name string) error {
// None of the path components should end in space or period, or be a
// reserved name. COM0 and LPT0 are missing from the Microsoft docs,
// but Windows Explorer treats them as invalid too.
// (https://docs.microsoft.com/windows/win32/fileio/naming-a-file)
// reserved name.
for _, part := range strings.Split(name, `\`) {
if len(part) == 0 {
continue
@@ -110,7 +98,7 @@ func WindowsInvalidFilename(name string) error {
func SanitizePath(path string) string {
var b strings.Builder
disallowed := `<>:"'/\|?*[]{};:!@$%&^#` + windowsDisallowedCharacters
const disallowed = `'/\[]{};:!@$%&^#` + windowsDisallowedCharacters
prev := ' '
for _, c := range path {
if !unicode.IsPrint(c) || c == unicode.ReplacementChar ||
@@ -132,15 +120,27 @@ func SanitizePath(path string) string {
}
func windowsIsReserved(part string) bool {
upperCased := strings.ToUpper(part)
for _, disallowed := range windowsDisallowedNames {
if upperCased == disallowed {
return true
}
if strings.HasPrefix(upperCased, disallowed+".") {
// nul.txt.jpg is also disallowed
return true
}
// nul.txt.jpg is also disallowed.
dot := strings.IndexByte(part, '.')
if dot != -1 {
part = part[:dot]
}
// Check length to skip allocating ToUpper.
if len(part) != 3 && len(part) != 4 {
return false
}
// COM0 and LPT0 are missing from the Microsoft docs,
// but Windows Explorer treats them as invalid too.
// (https://docs.microsoft.com/windows/win32/fileio/naming-a-file)
switch strings.ToUpper(part) {
case "CON", "PRN", "AUX", "NUL",
"COM0", "COM1", "COM2", "COM3", "COM4",
"COM5", "COM6", "COM7", "COM8", "COM9",
"LPT0", "LPT1", "LPT2", "LPT3", "LPT4",
"LPT5", "LPT6", "LPT7", "LPT8", "LPT9":
return true
}
return false
}

View File

@@ -117,3 +117,15 @@ func TestSanitizePathFuzz(t *testing.T) {
}
}
}
func benchmarkWindowsInvalidFilename(b *testing.B, name string) {
for i := 0; i < b.N; i++ {
WindowsInvalidFilename(name)
}
}
func BenchmarkWindowsInvalidFilenameValid(b *testing.B) {
benchmarkWindowsInvalidFilename(b, "License.txt.gz")
}
func BenchmarkWindowsInvalidFilenameNUL(b *testing.B) {
benchmarkWindowsInvalidFilename(b, "nul.txt.gz")
}

View File

@@ -26,20 +26,19 @@ func newDeviceActivity() *deviceActivity {
}
}
func (m *deviceActivity) leastBusy(availability []Availability) (Availability, bool) {
// Returns the index of the least busy device, or -1 if all are too busy.
func (m *deviceActivity) leastBusy(availability []Availability) int {
m.mut.Lock()
low := 2<<30 - 1
found := false
var selected Availability
for _, info := range availability {
if usage := m.act[info.ID]; usage < low {
best := -1
for i := range availability {
if usage := m.act[availability[i].ID]; usage < low {
low = usage
selected = info
found = true
best = i
}
}
m.mut.Unlock()
return selected, found
return best
}
func (m *deviceActivity) using(availability Availability) {

View File

@@ -19,42 +19,42 @@ func TestDeviceActivity(t *testing.T) {
devices := []Availability{n0, n1, n2}
na := newDeviceActivity()
if lb, ok := na.leastBusy(devices); !ok || lb != n0 {
if lb := na.leastBusy(devices); lb != 0 {
t.Errorf("Least busy device should be n0 (%v) not %v", n0, lb)
}
if lb, ok := na.leastBusy(devices); !ok || lb != n0 {
if lb := na.leastBusy(devices); lb != 0 {
t.Errorf("Least busy device should still be n0 (%v) not %v", n0, lb)
}
lb, _ := na.leastBusy(devices)
na.using(lb)
if lb, ok := na.leastBusy(devices); !ok || lb != n1 {
lb := na.leastBusy(devices)
na.using(devices[lb])
if lb := na.leastBusy(devices); lb != 1 {
t.Errorf("Least busy device should be n1 (%v) not %v", n1, lb)
}
lb, _ = na.leastBusy(devices)
na.using(lb)
if lb, ok := na.leastBusy(devices); !ok || lb != n2 {
lb = na.leastBusy(devices)
na.using(devices[lb])
if lb := na.leastBusy(devices); lb != 2 {
t.Errorf("Least busy device should be n2 (%v) not %v", n2, lb)
}
lb, _ = na.leastBusy(devices)
na.using(lb)
if lb, ok := na.leastBusy(devices); !ok || lb != n0 {
lb = na.leastBusy(devices)
na.using(devices[lb])
if lb := na.leastBusy(devices); lb != 0 {
t.Errorf("Least busy device should be n0 (%v) not %v", n0, lb)
}
na.done(n1)
if lb, ok := na.leastBusy(devices); !ok || lb != n1 {
if lb := na.leastBusy(devices); lb != 1 {
t.Errorf("Least busy device should be n1 (%v) not %v", n1, lb)
}
na.done(n2)
if lb, ok := na.leastBusy(devices); !ok || lb != n1 {
if lb := na.leastBusy(devices); lb != 1 {
t.Errorf("Least busy device should still be n1 (%v) not %v", n1, lb)
}
na.done(n0)
if lb, ok := na.leastBusy(devices); !ok || lb != n0 {
if lb := na.leastBusy(devices); lb != 0 {
t.Errorf("Least busy device should be n0 (%v) not %v", n0, lb)
}
}

View File

@@ -1532,8 +1532,8 @@ loop:
// Select the least busy device to pull the block from. If we found no
// feasible device at all, fail the block (and in the long run, the
// file).
selected, found := activity.leastBusy(candidates)
if !found {
found := activity.leastBusy(candidates)
if found == -1 {
if lastError != nil {
state.fail(errors.Wrap(lastError, "pull"))
} else {
@@ -1542,7 +1542,9 @@ loop:
break
}
candidates = removeAvailability(candidates, selected)
selected := candidates[found]
candidates[found] = candidates[len(candidates)-1]
candidates = candidates[:len(candidates)-1]
// Fetch the block, while marking the selected device as in use so that
// leastBusy can select another device when someone else asks.
@@ -1804,16 +1806,6 @@ func (f *sendReceiveFolder) inConflict(current, replacement protocol.Vector) boo
return false
}
func removeAvailability(availabilities []Availability, availability Availability) []Availability {
for i := range availabilities {
if availabilities[i] == availability {
availabilities[i] = availabilities[len(availabilities)-1]
return availabilities[:len(availabilities)-1]
}
}
return availabilities
}
func (f *sendReceiveFolder) moveForConflict(name, lastModBy string, scanChan chan<- string) error {
if isConflict(name) {
l.Infoln("Conflict for", name, "which is already a conflict copy; not copying again.")

View File

@@ -178,7 +178,11 @@ func (c *folderSummaryService) listenForUpdates(ctx context.Context) error {
// This loop needs to be fast so we don't miss too many events.
select {
case ev := <-sub.C():
case ev, ok := <-sub.C():
if !ok {
<-ctx.Done()
return ctx.Err()
}
c.processUpdate(ev)
case <-ctx.Done():
return ctx.Err()

View File

@@ -7,43 +7,19 @@
package osutil
import (
"syscall"
"github.com/pkg/errors"
)
const (
// https://docs.microsoft.com/windows/win32/api/processthreadsapi/nf-processthreadsapi-setpriorityclass
aboveNormalPriorityClass = 0x00008000
belowNormalPriorityClass = 0x00004000
highPriorityClass = 0x00000080
idlePriorityClass = 0x00000040
normalPriorityClass = 0x00000020
processModeBackgroundBegin = 0x00100000
processModeBackgroundEnd = 0x00200000
realtimePriorityClass = 0x00000100
"golang.org/x/sys/windows"
)
// SetLowPriority lowers the process CPU scheduling priority, and possibly
// I/O priority depending on the platform and OS.
func SetLowPriority() error {
modkernel32 := syscall.NewLazyDLL("kernel32.dll")
setPriorityClass := modkernel32.NewProc("SetPriorityClass")
if err := setPriorityClass.Find(); err != nil {
return errors.Wrap(err, "find proc")
}
handle, err := syscall.GetCurrentProcess()
handle, err := windows.GetCurrentProcess()
if err != nil {
return errors.Wrap(err, "get process handler")
return errors.Wrap(err, "get process handle")
}
defer syscall.CloseHandle(handle)
defer windows.CloseHandle(handle)
res, _, err := setPriorityClass.Call(uintptr(handle), belowNormalPriorityClass)
if res != 0 {
// "If the function succeeds, the return value is nonzero."
return nil
}
err = windows.SetPriorityClass(handle, windows.BELOW_NORMAL_PRIORITY_CLASS)
return errors.Wrap(err, "set priority class") // wraps nil as nil
}

View File

@@ -214,18 +214,3 @@ func untypeoify(s string) string {
s = strings.ReplaceAll(s, "8", "B")
return s
}
// DeviceIDs is a sortable slice of DeviceID
type DeviceIDs []DeviceID
func (l DeviceIDs) Len() int {
return len(l)
}
func (l DeviceIDs) Less(a, b int) bool {
return l[a].Compare(l[b]) == -1
}
func (l DeviceIDs) Swap(a, b int) {
l[a], l[b] = l[b], l[a]
}

View File

@@ -19,11 +19,7 @@ import (
"github.com/sasha-s/go-deadlock"
)
type clock interface {
Now() time.Time
}
var defaultClock clock = (*standardClock)(nil)
var timeNow = time.Now
type Mutex interface {
Lock()
@@ -86,7 +82,7 @@ func (h holder) String() string {
if h.at == "" {
return "not held"
}
return fmt.Sprintf("at %s goid: %d for %s", h.at, h.goid, defaultClock.Now().Sub(h.time))
return fmt.Sprintf("at %s goid: %d for %s", h.at, h.goid, timeNow().Sub(h.time))
}
type loggedMutex struct {
@@ -101,7 +97,7 @@ func (m *loggedMutex) Lock() {
func (m *loggedMutex) Unlock() {
currentHolder := m.holder.Load().(holder)
duration := defaultClock.Now().Sub(currentHolder.time)
duration := timeNow().Sub(currentHolder.time)
if duration >= threshold {
l.Debugf("Mutex held for %v. Locked at %s unlocked at %s", duration, currentHolder.at, getHolder().at)
}
@@ -125,7 +121,7 @@ type loggedRWMutex struct {
}
func (m *loggedRWMutex) Lock() {
start := defaultClock.Now()
start := timeNow()
atomic.StoreInt32(&m.logUnlockers, 1)
m.RWMutex.Lock()
@@ -153,7 +149,7 @@ func (m *loggedRWMutex) Lock() {
func (m *loggedRWMutex) Unlock() {
currentHolder := m.holder.Load().(holder)
duration := defaultClock.Now().Sub(currentHolder.time)
duration := timeNow().Sub(currentHolder.time)
if duration >= threshold {
l.Debugf("RWMutex held for %v. Locked at %s unlocked at %s", duration, currentHolder.at, getHolder().at)
}
@@ -205,9 +201,9 @@ type loggedWaitGroup struct {
}
func (wg *loggedWaitGroup) Wait() {
start := defaultClock.Now()
start := timeNow()
wg.WaitGroup.Wait()
duration := defaultClock.Now().Sub(start)
duration := timeNow().Sub(start)
if duration >= threshold {
l.Debugf("WaitGroup took %v at %s", duration, getHolder())
}
@@ -219,7 +215,7 @@ func getHolder() holder {
return holder{
at: fmt.Sprintf("%s:%d", file, line),
goid: goid(),
time: defaultClock.Now(),
time: timeNow(),
}
}
@@ -300,9 +296,3 @@ func (w *TimeoutCondWaiter) Wait() bool {
func (w *TimeoutCondWaiter) Stop() {
w.timer.Stop()
}
type standardClock struct{}
func (*standardClock) Now() time.Time {
return time.Now()
}

View File

@@ -57,10 +57,10 @@ func TestTypes(t *testing.T) {
}
func TestMutex(t *testing.T) {
oldClock := defaultClock
oldClock := timeNow
clock := newTestClock()
defaultClock = clock
defer func() { defaultClock = oldClock }()
timeNow = clock.Now
defer func() { timeNow = oldClock }()
debug = true
l.SetDebug("sync", true)
@@ -97,10 +97,10 @@ func TestMutex(t *testing.T) {
}
func TestRWMutex(t *testing.T) {
oldClock := defaultClock
oldClock := timeNow
clock := newTestClock()
defaultClock = clock
defer func() { defaultClock = oldClock }()
timeNow = clock.Now
defer func() { timeNow = oldClock }()
debug = true
l.SetDebug("sync", true)
@@ -170,10 +170,10 @@ func TestRWMutex(t *testing.T) {
}
func TestWaitGroup(t *testing.T) {
oldClock := defaultClock
oldClock := timeNow
clock := newTestClock()
defaultClock = clock
defer func() { defaultClock = oldClock }()
timeNow = clock.Now
defer func() { timeNow = oldClock }()
debug = true
l.SetDebug("sync", true)

View File

@@ -38,7 +38,11 @@ func (s *auditService) Serve(ctx context.Context) error {
for {
select {
case ev := <-sub.C():
case ev, ok := <-sub.C():
if !ok {
<-ctx.Done()
return ctx.Err()
}
enc.Encode(ev)
case <-ctx.Done():
return ctx.Err()

View File

@@ -31,7 +31,11 @@ func (s *verboseService) Serve(ctx context.Context) error {
defer sub.Unsubscribe()
for {
select {
case ev := <-sub.C():
case ev, ok := <-sub.C():
if !ok {
<-ctx.Done()
return ctx.Err()
}
formatted := s.formatEvent(ev)
if formatted != "" {
l.Verboseln(formatted)

View File

@@ -391,7 +391,7 @@ func verifyUpgrade(archiveName, tempName string, sig []byte) error {
// multireader. This ensures that it is not only a bonafide syncthing
// binary, but it is also of exactly the platform and version we expect.
mr := io.MultiReader(bytes.NewBufferString(archiveName+"\n"), fd)
mr := io.MultiReader(strings.NewReader(archiveName+"\n"), fd)
err = signature.Verify(SigningKey, sig, mr)
fd.Close()

View File

@@ -162,8 +162,10 @@ func (a *aggregator) mainLoop(in <-chan fs.Event, out chan<- []string, cfg confi
select {
case event := <-in:
a.newEvent(event, inProgress)
case event := <-inProgressItemSubscription.C():
updateInProgressSet(event, inProgress)
case event, ok := <-inProgressItemSubscription.C():
if ok {
updateInProgressSet(event, inProgress)
}
case <-a.notifyTimer.C:
a.actOnTimer(out)
case interval := <-a.notifyTimerResetChan: