Compare commits

...

43 Commits

Author SHA1 Message Date
Simon Frei
6d27cf6563 gui: Prioritize non-idle folder states (fixes #6169) (#6170) 2019-11-21 09:33:39 +01:00
Jacob
0cc77feabb docker: Add stdiscosrv and strelaysrv Dockerfiles (#6143) 2019-11-11 09:37:08 +01:00
Jakob Borg
d19b12d3fe lib/protocol: Buffer allocation when compressing (fixes #6146) (#6147)
We incorrectly gave a too small buffer to lz4.Compress, causing it to
allocate in some cases (when the data actually becomes larger when
compressed). This then panicked when passed to the buffer pool.

This ensures a buffer that is large enough, and adds tripwires closer to
the source in case this ever pops up again. There is a test that
exercises the issue.
2019-11-11 08:36:31 +00:00
Jakob Borg
1d406d62e3 golang-ci: Upgrade, skipping the white space complainer 2019-11-10 10:25:14 +01:00
Jakob Borg
1d99e5277a all: Cleanups enabled by Go 1.12 2019-11-10 10:16:10 +01:00
Jakob Borg
879f51b027 lib/tlsutil: Remove Go 1.12 TLS 1.3 beta opt-in
Go 1.13 enables this by default.
2019-11-10 09:32:48 +01:00
Audrius Butkevicius
d3d7408b17 lib/api: Make theme paths relative (#6142)
* Update theme.css

* Update syncthingController.js
2019-11-09 12:07:46 +00:00
Pablo
9b01e64c66 gui, lib/api: Adds support for prefers-color-scheme (fixes #6115)
* gui, lib/api: Adds support for prefers-color-scheme on default theme (fixes #6115)

- Renames current default theme into a new "light" theme
- Modifies assets serving to allow getting assets from different themes

* lib/api: Serve assets from arbitrary theme when path starts with "theme-assets"

* lib/api: Moves constant out of function

* Loads light theme in browsers without support for prefers-color-scheme

* gui: Disables dark theme when printing

* Prevents repeated injection and adds support for older browsers

The CSS is always loaded if there is no support for `matchMedia`.
2019-11-08 21:44:37 +00:00
Audrius Butkevicius
65c172cd8d lib/api: Reset mtime after theme change (fixes #5810) (#6140) 2019-11-08 22:37:42 +01:00
Simon Frei
85e6a77f25 lib/model: Remove some testing deadlocks (#6138) 2019-11-08 18:53:51 +01:00
Jakob Borg
88244b0c1f lib/model: Add test for previous commit 2019-11-08 17:03:25 +01:00
Simon Frei
cd290d2d05 lib/model: Add initial deviceStatRefs on model creation (fixes #6136) (#6137)
This is a regression introduced in PR #6005 / commit
f7b2e79fdc
2019-11-08 11:32:51 +00:00
Simon Frei
bee7cce081 lib/model: Add folders on start in model (#6135) 2019-11-08 10:56:16 +01:00
Jakob Borg
f15a1528fc cmd/stbench: rm -r cmd/stbench (#6131)
This is apparently an old benchmarking tool. I'd forgotten about it.
Since 67b8ef1f3e the build script tries to
build all binaries explicitly by default, and this fails on Windows as
this tool doesn't build on Windows.

Kill it with fire.
2019-11-07 07:20:21 +00:00
Jakob Borg
6be6de4b4a lib/api: Slightly unflake TestCSRFRequired by allowing longer timeout 2019-11-07 08:14:49 +01:00
Jakob Borg
6755a9ca63 Fix bufferpool puts (ref #4976) (#6125)
* Fix bufferpool puts (ref #4976)

There was a logic error in Put() which made us put all large blocks into
segment zero, where we subsequently did not look for them.

I also added a lowest threshold, as we otherwise allocate a 128KiB
buffer when we need 24 bytes for a header and such.

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* smaller stress

* cap/len

* wip

* wip
2019-11-06 10:53:10 +00:00
Audrius Butkevicius
98a1adebe1 all: Remove dead code, fix lost msgLen checks (#6129) 2019-11-06 07:09:58 +01:00
Aman Gupta
31569debeb lib/upnp: Fix outdated comment (#6110) 2019-11-05 18:56:51 +00:00
Simon Frei
cf420e135e gui: New folder state "Local Additions" for receive-only (fixes #5968) (#6117) 2019-11-01 20:44:23 +01:00
Ruslan Yevdokymov
3b5dff3f34 lib/model: Fix removal of a marker when there are still folders referencing it (#6114) 2019-10-30 15:11:07 +00:00
Jakob Borg
56cdf2f2d9 build: I like long, complicated things, ok? 2019-10-25 10:04:26 +02:00
dependabot-preview[bot]
b1dbe925d4 build(deps): bump github.com/prometheus/client_golang (#6099)
Bumps [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) from 1.1.0 to 1.2.1.
- [Release notes](https://github.com/prometheus/client_golang/releases)
- [Changelog](https://github.com/prometheus/client_golang/blob/master/CHANGELOG.md)
- [Commits](https://github.com/prometheus/client_golang/compare/v1.1.0...v1.2.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-10-22 21:56:32 +02:00
Simon Frei
bbdda059bd lib/model: Check for symlinks before deleting during pull (fixes #6090) (#6100) 2019-10-22 21:55:51 +02:00
Simon Frei
72f26c1e45 gui: Fix loop selecting all devices (fixes #5980) (#6102) 2019-10-22 13:57:10 +01:00
Simon Frei
72194d137c lib/scanner: Don't scan if input path is below symlink (fixes #6090) (#6101) 2019-10-22 11:12:21 +02:00
André Colomb
8b5bd45a29 gui: Split device list in folder sharing options by shared / unshared (#5756) 2019-10-21 21:28:10 +02:00
Jakob Borg
9084510e1b cmd/stdiscosrv: Sort addresses before replication (fixes #6093) (#6094)
This makes sure addresses are sorted when coming in from the API. The
database merge operation still checks for correct ordering (which is
quick) and sorts if it isn't correct (legacy database record or
replication peer), but then does a copy first.

Tested with -race in production...
2019-10-18 10:50:19 +02:00
Audrius Butkevicius
c4f161d8c5 lib/connections: Rate limit quic accept loop (fixes #6081) (#6082) 2019-10-18 09:55:37 +02:00
Jakob Borg
ad2d3702ae all: Upgrade github.com/gogo/protobuf and regenerate (fixes #6085) 2019-10-18 09:53:59 +02:00
Jakob Borg
95acb26249 lib/syncthing: Fixup test after merge 2019-10-17 09:14:27 +02:00
Jakob Borg
4736cccda1 all: Update certificate lifetimes (fixes #6036) (#6078)
This adds a certificate lifetime parameter to our certificate generation
and hard codes it to twenty years in some uninteresting places. In the
main binary there are a couple of constants but it results in twenty
years for the device certificate and 820 days for the HTTPS one. 820 is
less than the 825 maximum Apple allows nowadays.

This also means we must be prepared for certificates to expire, so I add
some handling for that and generate a new certificate when needed. For
self signed certificates we regenerate a month ahead of time. For other
certificates we leave well enough alone.
2019-10-16 20:31:46 +02:00
Simon Frei
1a06ab68eb lib/sync: Cleanly fail instead of panic in tests (#6088) 2019-10-16 10:11:11 +02:00
Simon Frei
b8907b49f9 lib/syncthing: Prevent hangup on error during startup (fixes #6043) (#6047) 2019-10-16 10:10:42 +02:00
Simon Frei
7b33294955 gui, lib/model: Add new state FolderPreparingSync (fixes #6027) (#6028) 2019-10-16 09:08:54 +02:00
Simon Frei
031684116b lib/util: Add caller info to service (ref #5932) (#5973) 2019-10-16 09:06:16 +02:00
Simon Frei
a0c9db1d09 lib/api: Unify JSON marshalling of file infos (#6087) 2019-10-15 11:25:12 +02:00
dependabot-preview[bot]
aa4b918224 build(deps): bump github.com/lucas-clemente/quic-go (#6084)
Bumps [github.com/lucas-clemente/quic-go](https://github.com/lucas-clemente/quic-go) from 0.12.0 to 0.12.1.
- [Release notes](https://github.com/lucas-clemente/quic-go/releases)
- [Changelog](https://github.com/lucas-clemente/quic-go/blob/master/Changelog.md)
- [Commits](https://github.com/lucas-clemente/quic-go/compare/v0.12.0...v0.12.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-10-14 18:30:00 +01:00
dependabot-preview[bot]
7043b1fbba build(deps): bump github.com/mattn/go-isatty from 0.0.9 to 0.0.10 (#6083)
Bumps [github.com/mattn/go-isatty](https://github.com/mattn/go-isatty) from 0.0.9 to 0.0.10.
- [Release notes](https://github.com/mattn/go-isatty/releases)
- [Commits](https://github.com/mattn/go-isatty/compare/v0.0.9...v0.0.10)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-10-14 18:17:01 +01:00
chenrui
9d6b663d1c build: Update golangci for go v1.13 (#6042) 2019-10-13 16:32:20 +02:00
Arkadiusz Tymiński
7dc4ac6e1f gui: Hide select/deselect all buttons if there are no devices (fixes #6056) 2019-10-08 21:57:17 +01:00
Alan Pope
7bad9b3a11 snapcraft: Add desktop plug (#6069)
Without the desktop plug, launching the syncthing snap will not launch a browser window to the admin UI. Adding this one line will fix that. Tested here on my Ubuntu system with a build from tip of master.
2019-10-08 18:20:51 +01:00
Jakob Borg
6b570ee8dc lib/upgrade: Add html_url release field 2019-10-08 09:12:00 +02:00
Cyprien Devillez
6408a116f9 cmd/stdiscosrv: Add support for Traefik 2 as a reverse proxy (#6065) 2019-10-07 12:55:27 +01:00
65 changed files with 1299 additions and 812 deletions

View File

@@ -12,9 +12,12 @@ linters:
- gochecknoglobals
- gofmt
- scopelint
- gocyclo
- funlen
- wsl
service:
golangci-lint-version: 1.17.x
golangci-lint-version: 1.21.x
prepare:
- rm -f go.sum # 1.12 -> 1.13 issues with QUIC-go
- GO111MODULE=on go mod vendor

View File

@@ -19,10 +19,10 @@ RUN apk add --no-cache ca-certificates su-exec
COPY --from=builder /src/syncthing /bin/syncthing
COPY --from=builder /src/script/docker-entrypoint.sh /bin/entrypoint.sh
ENV PUID=1000 PGID=1000
ENV PUID=1000 PGID=1000 HOME=/var/syncthing
HEALTHCHECK --interval=1m --timeout=10s \
CMD nc -z localhost 8384 || exit 1
ENV STGUIADDRESS=0.0.0.0:8384
ENTRYPOINT ["/bin/entrypoint.sh", "-home", "/var/syncthing/config"]
ENTRYPOINT ["/bin/entrypoint.sh", "/bin/syncthing", "-home", "/var/syncthing/config"]

28
Dockerfile.stdiscosrv Normal file
View File

@@ -0,0 +1,28 @@
FROM golang:1.13 AS builder
WORKDIR /src
COPY . .
ENV CGO_ENABLED=0
ENV BUILD_HOST=syncthing.net
ENV BUILD_USER=docker
RUN rm -f stdiscosrv && go run build.go -no-upgrade build stdiscosrv
FROM alpine
EXPOSE 19200 8443
VOLUME ["/var/stdiscosrv"]
RUN apk add --no-cache ca-certificates su-exec
COPY --from=builder /src/stdiscosrv /bin/stdiscosrv
COPY --from=builder /src/script/docker-entrypoint.sh /bin/entrypoint.sh
ENV PUID=1000 PGID=1000 HOME=/var/stdiscosrv
HEALTHCHECK --interval=1m --timeout=10s \
CMD nc -z localhost 8443 || exit 1
WORKDIR /var/stdiscosrv
ENTRYPOINT ["/bin/entrypoint.sh", "/bin/stdiscosrv"]

28
Dockerfile.strelaysrv Normal file
View File

@@ -0,0 +1,28 @@
FROM golang:1.13 AS builder
WORKDIR /src
COPY . .
ENV CGO_ENABLED=0
ENV BUILD_HOST=syncthing.net
ENV BUILD_USER=docker
RUN rm -f strelaysrv && go run build.go -no-upgrade build strelaysrv
FROM alpine
EXPOSE 22067 22070
VOLUME ["/var/strelaysrv"]
RUN apk add --no-cache ca-certificates su-exec
COPY --from=builder /src/strelaysrv /bin/strelaysrv
COPY --from=builder /src/script/docker-entrypoint.sh /bin/entrypoint.sh
ENV PUID=1000 PGID=1000 HOME=/var/strelaysrv
HEALTHCHECK --interval=1m --timeout=10s \
CMD nc -z localhost 22067 || exit 1
WORKDIR /var/strelaysrv
ENTRYPOINT ["/bin/entrypoint.sh", "/bin/strelaysrv"]

View File

@@ -1,143 +0,0 @@
// Copyright (C) 2016 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/.
// This doesn't build on Windows due to the Rusage stuff.
// +build !windows
package main
import (
"flag"
"fmt"
"log"
"runtime"
"syscall"
"time"
"github.com/syncthing/syncthing/lib/rc"
)
var homeDir = "h1"
var syncthingBin = "./bin/syncthing"
var test = "scan"
func main() {
flag.StringVar(&homeDir, "home", homeDir, "Home directory location")
flag.StringVar(&syncthingBin, "bin", syncthingBin, "Binary location")
flag.StringVar(&test, "test", test, "Test to run")
flag.Parse()
switch test {
case "scan":
// scan measures the resource usage required to perform the initial
// scan, without cleaning away the database first.
testScan()
}
}
// testScan starts a process and reports on the resource usage required to
// perform the initial scan.
func testScan() {
log.Println("Starting...")
p := rc.NewProcess("127.0.0.1:8081")
if err := p.Start(syncthingBin, "-home", homeDir, "-no-browser"); err != nil {
log.Println(err)
return
}
defer p.Stop()
wallTime := awaitScanComplete(p)
report(p, wallTime)
}
// awaitScanComplete waits for a folder to transition idle->scanning and
// then scanning->idle and returns the time taken for the scan.
func awaitScanComplete(p *rc.Process) time.Duration {
log.Println("Awaiting scan completion...")
var t0, t1 time.Time
lastEvent := 0
loop:
for {
evs, err := p.Events(lastEvent)
if err != nil {
continue
}
for _, ev := range evs {
if ev.Type == "StateChanged" {
data := ev.Data.(map[string]interface{})
log.Println(ev)
if data["to"].(string) == "scanning" {
t0 = ev.Time
continue
}
if !t0.IsZero() && data["to"].(string) == "idle" {
t1 = ev.Time
break loop
}
}
lastEvent = ev.ID
}
time.Sleep(250 * time.Millisecond)
}
return t1.Sub(t0)
}
// report stops the given process and reports on its resource usage in two
// ways: human readable to stderr, and CSV to stdout.
func report(p *rc.Process, wallTime time.Duration) {
sv, err := p.SystemVersion()
if err != nil {
log.Println(err)
return
}
ss, err := p.SystemStatus()
if err != nil {
log.Println(err)
return
}
proc, err := p.Stop()
if err != nil {
return
}
rusage, ok := proc.SysUsage().(*syscall.Rusage)
if !ok {
return
}
log.Println("Version:", sv.Version)
log.Println("Alloc:", ss.Alloc/1024, "KiB")
log.Println("Sys:", ss.Sys/1024, "KiB")
log.Println("Goroutines:", ss.Goroutines)
log.Println("Wall time:", wallTime)
log.Println("Utime:", time.Duration(rusage.Utime.Nano()))
log.Println("Stime:", time.Duration(rusage.Stime.Nano()))
if runtime.GOOS == "darwin" {
// Darwin reports in bytes, Linux seems to report in KiB even
// though the manpage says otherwise.
rusage.Maxrss /= 1024
}
log.Println("MaxRSS:", rusage.Maxrss, "KiB")
fmt.Printf("%s,%d,%d,%d,%.02f,%.02f,%.02f,%d\n",
sv.Version,
ss.Alloc/1024,
ss.Sys/1024,
ss.Goroutines,
wallTime.Seconds(),
time.Duration(rusage.Utime.Nano()).Seconds(),
time.Duration(rusage.Stime.Nano()).Seconds(),
rusage.Maxrss)
}

View File

@@ -12,7 +12,6 @@ import (
"io/ioutil"
"net/http"
"os"
"text/tabwriter"
"github.com/syncthing/syncthing/lib/config"
"github.com/urfave/cli"
@@ -45,12 +44,6 @@ func dumpOutput(url string) cli.ActionFunc {
}
}
func newTableWriter() *tabwriter.Writer {
writer := new(tabwriter.Writer)
writer.Init(os.Stdout, 0, 8, 0, '\t', 0)
return writer
}
func getConfig(c *APIClient) (config.Configuration, error) {
cfg := config.Configuration{}
response, err := c.Get("system/config")

View File

@@ -18,6 +18,7 @@ import (
"net"
"net/http"
"net/url"
"sort"
"strconv"
"strings"
"sync"
@@ -279,6 +280,10 @@ func (s *apiSrv) handleAnnounce(remote net.IP, deviceID protocol.DeviceID, addre
dbAddrs[i].Expires = expire
}
// The address slice must always be sorted for database merges to work
// properly.
sort.Sort(databaseAddressOrder(dbAddrs))
seen := now.UnixNano()
if s.repl != nil {
s.repl.send(key, dbAddrs, seen)
@@ -295,28 +300,66 @@ func certificateBytes(req *http.Request) []byte {
return req.TLS.PeerCertificates[0].Raw
}
var bs []byte
if hdr := req.Header.Get("X-SSL-Cert"); hdr != "" {
bs := []byte(hdr)
// The certificate is in PEM format but with spaces for newlines. We
// need to reinstate the newlines for the PEM decoder. But we need to
// leave the spaces in the BEGIN and END lines - the first and last
// space - alone.
firstSpace := bytes.Index(bs, []byte(" "))
lastSpace := bytes.LastIndex(bs, []byte(" "))
for i := firstSpace + 1; i < lastSpace; i++ {
if bs[i] == ' ' {
bs[i] = '\n'
if strings.Contains(hdr, "%") {
// Nginx using $ssl_client_escaped_cert
// The certificate is in PEM format with url encoding.
// We need to decode for the PEM decoder
hdr, err := url.QueryUnescape(hdr)
if err != nil {
// Decoding failed
return nil
}
bs = []byte(hdr)
} else {
// Nginx using $ssl_client_cert
// The certificate is in PEM format but with spaces for newlines. We
// need to reinstate the newlines for the PEM decoder. But we need to
// leave the spaces in the BEGIN and END lines - the first and last
// space - alone.
bs = []byte(hdr)
firstSpace := bytes.Index(bs, []byte(" "))
lastSpace := bytes.LastIndex(bs, []byte(" "))
for i := firstSpace + 1; i < lastSpace; i++ {
if bs[i] == ' ' {
bs[i] = '\n'
}
}
}
block, _ := pem.Decode(bs)
if block == nil {
} else if hdr := req.Header.Get("X-Forwarded-Tls-Client-Cert"); hdr != "" {
// Traefik 2 passtlsclientcert
// The certificate is in PEM format with url encoding but without newlines
// and start/end statements. We need to decode, reinstate the newlines every 64
// character and add statements for the PEM decoder
hdr, err := url.QueryUnescape(hdr)
if err != nil {
// Decoding failed
return nil
}
return block.Bytes
for i := 64; i < len(hdr); i += 65 {
hdr = hdr[:i] + "\n" + hdr[i:]
}
hdr = "-----BEGIN CERTIFICATE-----\n" + hdr
hdr = hdr + "\n-----END CERTIFICATE-----\n"
bs = []byte(hdr)
}
return nil
if bs == nil {
return nil
}
block, _ := pem.Decode(bs)
if block == nil {
// Decoding failed
return nil
}
return block.Bytes
}
// fixupAddresses checks the list of addresses, removing invalid ones and

View File

@@ -10,6 +10,7 @@
package main
import (
"log"
"sort"
"time"
@@ -263,12 +264,15 @@ func (s *levelDBStore) Stop() {
// chosen for any duplicates.
func merge(a, b DatabaseRecord) DatabaseRecord {
// Both lists must be sorted for this to work.
sort.Slice(a.Addresses, func(i, j int) bool {
return a.Addresses[i].Address < a.Addresses[j].Address
})
sort.Slice(b.Addresses, func(i, j int) bool {
return b.Addresses[i].Address < b.Addresses[j].Address
})
if !sort.IsSorted(databaseAddressOrder(a.Addresses)) {
log.Println("Warning: bug: addresses not correctly sorted in merge")
a.Addresses = sortedAddressCopy(a.Addresses)
}
if !sort.IsSorted(databaseAddressOrder(b.Addresses)) {
// no warning because this is the side we read from disk and it may
// legitimately predate correct sorting.
b.Addresses = sortedAddressCopy(b.Addresses)
}
res := DatabaseRecord{
Addresses: make([]DatabaseAddress, 0, len(a.Addresses)+len(b.Addresses)),
@@ -352,3 +356,24 @@ func expire(addrs []DatabaseAddress, now int64) []DatabaseAddress {
}
return addrs
}
func sortedAddressCopy(addrs []DatabaseAddress) []DatabaseAddress {
sorted := make([]DatabaseAddress, len(addrs))
copy(sorted, addrs)
sort.Sort(databaseAddressOrder(sorted))
return sorted
}
type databaseAddressOrder []DatabaseAddress
func (s databaseAddressOrder) Less(a, b int) bool {
return s[a].Address < s[b].Address
}
func (s databaseAddressOrder) Swap(a, b int) {
s[a], s[b] = s[b], s[a]
}
func (s databaseAddressOrder) Len() int {
return len(s)
}

View File

@@ -773,6 +773,7 @@ func (m *DatabaseAddress) Unmarshal(dAtA []byte) error {
func skipDatabase(dAtA []byte) (n int, err error) {
l := len(dAtA)
iNdEx := 0
depth := 0
for iNdEx < l {
var wire uint64
for shift := uint(0); ; shift += 7 {
@@ -804,10 +805,8 @@ func skipDatabase(dAtA []byte) (n int, err error) {
break
}
}
return iNdEx, nil
case 1:
iNdEx += 8
return iNdEx, nil
case 2:
var length int
for shift := uint(0); ; shift += 7 {
@@ -828,55 +827,30 @@ func skipDatabase(dAtA []byte) (n int, err error) {
return 0, ErrInvalidLengthDatabase
}
iNdEx += length
if iNdEx < 0 {
return 0, ErrInvalidLengthDatabase
}
return iNdEx, nil
case 3:
for {
var innerWire uint64
var start int = iNdEx
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowDatabase
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
innerWire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
innerWireType := int(innerWire & 0x7)
if innerWireType == 4 {
break
}
next, err := skipDatabase(dAtA[start:])
if err != nil {
return 0, err
}
iNdEx = start + next
if iNdEx < 0 {
return 0, ErrInvalidLengthDatabase
}
}
return iNdEx, nil
depth++
case 4:
return iNdEx, nil
if depth == 0 {
return 0, ErrUnexpectedEndOfGroupDatabase
}
depth--
case 5:
iNdEx += 4
return iNdEx, nil
default:
return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
}
if iNdEx < 0 {
return 0, ErrInvalidLengthDatabase
}
if depth == 0 {
return iNdEx, nil
}
}
panic("unreachable")
return 0, io.ErrUnexpectedEOF
}
var (
ErrInvalidLengthDatabase = fmt.Errorf("proto: negative length found during unmarshaling")
ErrIntOverflowDatabase = fmt.Errorf("proto: integer overflow")
ErrInvalidLengthDatabase = fmt.Errorf("proto: negative length found during unmarshaling")
ErrIntOverflowDatabase = fmt.Errorf("proto: integer overflow")
ErrUnexpectedEndOfGroupDatabase = fmt.Errorf("proto: unexpected end of group")
)

View File

@@ -100,7 +100,7 @@ func main() {
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
log.Println("Failed to load keypair. Generating one, this might take a while...")
cert, err = tlsutil.NewCertificate(certFile, keyFile, "stdiscosrv")
cert, err = tlsutil.NewCertificate(certFile, keyFile, "stdiscosrv", 20*365)
if err != nil {
log.Fatalln("Failed to generate X509 key pair:", err)
}

View File

@@ -633,7 +633,7 @@ func createTestCertificate() tls.Certificate {
}
certFile, keyFile := filepath.Join(tmpDir, "cert.pem"), filepath.Join(tmpDir, "key.pem")
cert, err := tlsutil.NewCertificate(certFile, keyFile, "relaypoolsrv")
cert, err := tlsutil.NewCertificate(certFile, keyFile, "relaypoolsrv", 20*365)
if err != nil {
log.Fatalln("Failed to create test X509 key pair:", err)
}

View File

@@ -155,7 +155,7 @@ func main() {
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
log.Println("Failed to load keypair. Generating one, this might take a while...")
cert, err = tlsutil.NewCertificate(certFile, keyFile, "strelaysrv")
cert, err = tlsutil.NewCertificate(certFile, keyFile, "strelaysrv", 20*365)
if err != nil {
log.Fatalln("Failed to generate X509 key pair:", err)
}

View File

@@ -45,19 +45,8 @@ import (
)
const (
exitSuccess = 0
exitError = 1
exitNoUpgradeAvailable = 2
exitRestarting = 3
exitUpgrading = 4
)
const (
bepProtocolName = "bep/1.0"
tlsDefaultCommonName = "syncthing"
maxSystemErrors = 5
initialSystemLog = 10
maxSystemLog = 250
tlsDefaultCommonName = "syncthing"
deviceCertLifetimeDays = 20 * 365
)
const (
@@ -271,7 +260,7 @@ func main() {
// default location
if options.noRestart && (options.logFile != "" && options.logFile != "-") {
l.Warnln("-logfile may not be used with -no-restart or STNORESTART")
os.Exit(exitError)
os.Exit(syncthing.ExitError.AsInt())
}
if options.hideConsole {
@@ -285,12 +274,12 @@ func main() {
options.confDir, err = filepath.Abs(options.confDir)
if err != nil {
l.Warnln("Failed to make options path absolute:", err)
os.Exit(exitError)
os.Exit(syncthing.ExitError.AsInt())
}
}
if err := locations.SetBaseDir(locations.ConfigBaseDir, options.confDir); err != nil {
l.Warnln(err)
os.Exit(exitError)
os.Exit(syncthing.ExitError.AsInt())
}
}
@@ -328,7 +317,7 @@ func main() {
)
if err != nil {
l.Warnln("Error reading device ID:", err)
os.Exit(exitError)
os.Exit(syncthing.ExitError.AsInt())
}
fmt.Println(protocol.NewDeviceID(cert.Certificate[0]))
@@ -338,7 +327,7 @@ func main() {
if options.browserOnly {
if err := openGUI(protocol.EmptyDeviceID); err != nil {
l.Warnln("Failed to open web UI:", err)
os.Exit(exitError)
os.Exit(syncthing.ExitError.AsInt())
}
return
}
@@ -346,7 +335,7 @@ func main() {
if options.generateDir != "" {
if err := generate(options.generateDir); err != nil {
l.Warnln("Failed to generate config and keys:", err)
os.Exit(exitError)
os.Exit(syncthing.ExitError.AsInt())
}
return
}
@@ -354,14 +343,14 @@ func main() {
// Ensure that our home directory exists.
if err := ensureDir(locations.GetBaseDir(locations.ConfigBaseDir), 0700); err != nil {
l.Warnln("Failure on home directory:", err)
os.Exit(exitError)
os.Exit(syncthing.ExitError.AsInt())
}
if options.upgradeTo != "" {
err := upgrade.ToURL(options.upgradeTo)
if err != nil {
l.Warnln("Error while Upgrading:", err)
os.Exit(exitError)
os.Exit(syncthing.ExitError.AsInt())
}
l.Infoln("Upgraded from", options.upgradeTo)
return
@@ -381,7 +370,7 @@ func main() {
if options.resetDatabase {
if err := resetDB(); err != nil {
l.Warnln("Resetting database:", err)
os.Exit(exitError)
os.Exit(syncthing.ExitError.AsInt())
}
return
}
@@ -424,7 +413,7 @@ func generate(generateDir string) error {
if err == nil {
l.Warnln("Key exists; will not overwrite.")
} else {
cert, err = tlsutil.NewCertificate(certFile, keyFile, tlsDefaultCommonName)
cert, err = tlsutil.NewCertificate(certFile, keyFile, tlsDefaultCommonName, deviceCertLifetimeDays)
if err != nil {
return errors.Wrap(err, "create certificate")
}
@@ -476,13 +465,13 @@ func checkUpgrade() upgrade.Release {
release, err := upgrade.LatestRelease(opts.ReleasesURL, build.Version, opts.UpgradeToPreReleases)
if err != nil {
l.Warnln("Upgrade:", err)
os.Exit(exitError)
os.Exit(syncthing.ExitError.AsInt())
}
if upgrade.CompareVersions(release.Tag, build.Version) <= 0 {
noUpgradeMessage := "No upgrade available (current %q >= latest %q)."
l.Infof(noUpgradeMessage, build.Version, release.Tag)
os.Exit(exitNoUpgradeAvailable)
os.Exit(syncthing.ExitNoUpgradeAvailable.AsInt())
}
l.Infof("Upgrade available (current %q < latest %q)", build.Version, release.Tag)
@@ -496,7 +485,7 @@ func performUpgrade(release upgrade.Release) {
err = upgrade.To(release)
if err != nil {
l.Warnln("Upgrade:", err)
os.Exit(exitError)
os.Exit(syncthing.ExitError.AsInt())
}
l.Infof("Upgraded to %q", release.Tag)
} else {
@@ -504,10 +493,10 @@ func performUpgrade(release upgrade.Release) {
err = upgradeViaRest()
if err != nil {
l.Warnln("Upgrade:", err)
os.Exit(exitError)
os.Exit(syncthing.ExitError.AsInt())
}
l.Infoln("Syncthing upgrading")
os.Exit(exitUpgrading)
os.Exit(syncthing.ExitUpgrade.AsInt())
}
}
@@ -573,7 +562,7 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
cfg, err := syncthing.LoadConfigAtStartup(locations.Get(locations.ConfigFile), cert, evLogger, runtimeOptions.allowNewerConfig, noDefaultFolder)
if err != nil {
l.Warnln("Failed to initialize config:", err)
os.Exit(exitError)
os.Exit(syncthing.ExitError.AsInt())
}
if runtimeOptions.unpaused {
@@ -610,11 +599,11 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
f, err := os.Create(fmt.Sprintf("cpu-%d.pprof", os.Getpid()))
if err != nil {
l.Warnln("Creating profile:", err)
os.Exit(exitError)
os.Exit(syncthing.ExitError.AsInt())
}
if err := pprof.StartCPUProfile(f); err != nil {
l.Warnln("Starting profile:", err)
os.Exit(exitError)
os.Exit(syncthing.ExitError.AsInt())
}
}
@@ -647,7 +636,9 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
}
}
app.Start()
if err := app.Start(); err != nil {
os.Exit(syncthing.ExitError.AsInt())
}
cleanConfigDirectory()
@@ -721,7 +712,7 @@ func auditWriter(auditFile string) io.Writer {
fd, err = os.OpenFile(auditFile, auditFlags, 0600)
if err != nil {
l.Warnln("Audit:", err)
os.Exit(exitError)
os.Exit(syncthing.ExitError.AsInt())
}
auditDest = auditFile
}
@@ -897,6 +888,6 @@ func setPauseState(cfg config.Wrapper, paused bool) {
}
if _, err := cfg.Replace(raw); err != nil {
l.Warnln("Cannot adjust paused state:", err)
os.Exit(exitError)
os.Exit(syncthing.ExitError.AsInt())
}
}

View File

@@ -23,6 +23,7 @@ import (
"github.com/syncthing/syncthing/lib/osutil"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/sync"
"github.com/syncthing/syncthing/lib/syncthing"
)
var (
@@ -81,7 +82,7 @@ func monitorMain(runtimeOptions RuntimeOptions) {
if t := time.Since(restarts[0]); t < loopThreshold {
l.Warnf("%d restarts in %v; not retrying further", countRestarts, t)
os.Exit(exitError)
os.Exit(syncthing.ExitError.AsInt())
}
copy(restarts[0:], restarts[1:])
@@ -150,17 +151,14 @@ func monitorMain(runtimeOptions RuntimeOptions) {
// Successful exit indicates an intentional shutdown
return
} else if exiterr, ok := err.(*exec.ExitError); ok {
if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
switch status.ExitStatus() {
case exitUpgrading:
// Restart the monitor process to release the .old
// binary as part of the upgrade process.
l.Infoln("Restarting monitor...")
if err = restartMonitor(args); err != nil {
l.Warnln("Restart:", err)
}
return
if exiterr.ExitCode() == syncthing.ExitUpgrade.AsInt() {
// Restart the monitor process to release the .old
// binary as part of the upgrade process.
l.Infoln("Restarting monitor...")
if err = restartMonitor(args); err != nil {
l.Warnln("Restart:", err)
}
return
}
}
}

11
go.mod
View File

@@ -15,16 +15,16 @@ require (
github.com/getsentry/raven-go v0.2.0
github.com/go-ole/go-ole v1.2.4 // indirect
github.com/gobwas/glob v0.2.3
github.com/gogo/protobuf v1.3.0
github.com/gogo/protobuf v1.3.1
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6
github.com/golang/mock v1.3.1 // indirect
github.com/jackpal/gateway v1.0.5
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
github.com/kr/pretty v0.1.0 // indirect
github.com/lib/pq v1.2.0
github.com/lucas-clemente/quic-go v0.12.0
github.com/lucas-clemente/quic-go v0.12.1
github.com/maruel/panicparse v1.3.0
github.com/mattn/go-isatty v0.0.9
github.com/mattn/go-isatty v0.0.10
github.com/minio/sha256-simd v0.1.1
github.com/onsi/ginkgo v1.9.0 // indirect
github.com/onsi/gomega v1.6.0 // indirect
@@ -32,9 +32,7 @@ require (
github.com/oschwald/maxminddb-golang v1.4.0 // indirect
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect
github.com/pkg/errors v0.8.1
github.com/prometheus/client_golang v1.1.0
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 // indirect
github.com/prometheus/procfs v0.0.4 // indirect
github.com/prometheus/client_golang v1.2.1
github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563
github.com/sasha-s/go-deadlock v0.2.0
github.com/shirou/gopsutil v0.0.0-20190714054239-47ef3260b6bf
@@ -45,7 +43,6 @@ require (
github.com/vitrun/qart v0.0.0-20160531060029-bf64b92db6b0
golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd // indirect
golang.org/x/text v0.3.2
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect

23
go.sum
View File

@@ -10,7 +10,9 @@ github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrU
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk=
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0=
@@ -25,6 +27,8 @@ github.com/ccding/go-stun v0.0.0-20180726100737-be486d185f3d h1:As4937T5NVbJ/DmZ
github.com/ccding/go-stun v0.0.0-20180726100737-be486d185f3d/go.mod h1:3FK1bMar37f7jqVY7q/63k3OMX1c47pGCufzt3X0sYE=
github.com/certifi/gocertifi v0.0.0-20190905060710-a5e0173ced67 h1:8k9FLYBLKT+9v2HQJ/a95ZemmTx+/ltJcAiRhVushG8=
github.com/certifi/gocertifi v0.0.0-20190905060710-a5e0173ced67/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4=
github.com/cespare/xxhash/v2 v2.1.0 h1:yTUvW7Vhb89inJ+8irsUqiWjh8iT6sQPZiQzI6ReGkA=
github.com/cespare/xxhash/v2 v2.1.0/go.mod h1:dgIUBU3pDso/gPgZ1osOZ0iQf77oPR28Tjxl5dIMyVM=
github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE=
github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
github.com/chmduquesne/rollinghash v0.0.0-20180912150627-a60f8e7142b5 h1:Wg96Dh0MLTanEaPO0OkGtUIaa2jOnShAIOVUIzRHUxo=
@@ -43,6 +47,7 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
github.com/getsentry/raven-go v0.2.0 h1:no+xWJRb5ZI7eE8TWgIq1jLulQiIoLG0IfYxv5JYMGs=
github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-ole/go-ole v1.2.1 h1:2lOsA72HgjxAuMlKpFiCbHTvu44PIVkZ5hqm3RSdI/E=
@@ -55,6 +60,8 @@ github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJA
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.3.0 h1:G8O7TerXerS4F6sx9OV7/nRfJdnXgHZu/S/7F2SN+UE=
github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk=
@@ -95,6 +102,8 @@ github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lucas-clemente/quic-go v0.12.0 h1:TRbvZ6F++sofeGbh+Z2IIyIOhl8KyGnYuA06g2yrHdI=
github.com/lucas-clemente/quic-go v0.12.0/go.mod h1:UXJJPE4RfFef/xPO5wQm0tITK8gNfqwTxjbE7s3Vb8s=
github.com/lucas-clemente/quic-go v0.12.1 h1:BPITli+6KnKogtTxBk2aS4okr5dUHz2LtIDAP1b8UL4=
github.com/lucas-clemente/quic-go v0.12.1/go.mod h1:UXJJPE4RfFef/xPO5wQm0tITK8gNfqwTxjbE7s3Vb8s=
github.com/marten-seemann/qpack v0.1.0/go.mod h1:LFt1NU/Ptjip0C2CPkhimBz5CGE3WGDAUWqna+CNTrI=
github.com/marten-seemann/qtls v0.3.2 h1:O7awy4bHEzSX/K3h+fZig3/Vo03s/RxlxgsAk9sYamI=
github.com/marten-seemann/qtls v0.3.2/go.mod h1:xzjG7avBwGGbdZ8dTGxlBnLArsVKLvwmjgmPuiQEcYk=
@@ -107,6 +116,8 @@ github.com/mattn/go-isatty v0.0.7 h1:UvyT9uN+3r7yLEYSlJsbQGdsaB/a0DlgWP3pql6iwOc
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.9 h1:d5US/mDsogSGW37IV293h//ZFaeajb69h+EHFsv2xGg=
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10=
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
@@ -142,6 +153,8 @@ github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.1.0 h1:BQ53HtBmfOitExawJ6LokA4x8ov/z0SYYb0+HxJfRI8=
github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g=
github.com/prometheus/client_golang v1.2.1 h1:JnMpQc6ppsNgw9QPAGF6Dod479itz7lvlsMzzNayLOI=
github.com/prometheus/client_golang v1.2.1/go.mod h1:XMU6Z2MjaRKVu/dC1qupJI9SiNkDYzz3xecMgSW/F+U=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE=
@@ -152,6 +165,8 @@ github.com/prometheus/common v0.4.1 h1:K0MGApIoQvMw27RTdJkPbr3JZ7DNbtxQNyi5STVM6
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.6.0 h1:kRhiuYSXR3+uv2IbVbZhUxK5zVD/2pp3Gd2PpvPkpEo=
github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc=
github.com/prometheus/common v0.7.0 h1:L+1lyG48J1zAQXA3RBX/nG/B3gjlHq0zTt2tlbJLyCY=
github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2 h1:6LJUbpNm42llc4HRCuvApCSWB/WfhuNo9K98Q9sNGfs=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
@@ -159,6 +174,8 @@ github.com/prometheus/procfs v0.0.3 h1:CTwfnzjQ+8dS6MhHHu4YswVAD99sL2wjPqP+VkURm
github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
github.com/prometheus/procfs v0.0.4 h1:w8DjqFMJDjuVwdZBQoOozr4MVWOnwF7RcL/7uxBjY78=
github.com/prometheus/procfs v0.0.4/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
github.com/prometheus/procfs v0.0.5 h1:3+auTFlqw+ZaQYJARz6ArODtkaIwtvBTx3N2NehQlL8=
github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563 h1:dY6ETXrvDG7Sa4vE8ZQG4yqWg6UnOcbqTAahkV813vQ=
github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
@@ -171,6 +188,7 @@ github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
@@ -221,11 +239,16 @@ golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e h1:ZytStCyV048ZqDsWHiYDdoI2Vd4msMcrDECFxS+tL9c=
golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd h1:DBH9mDw0zluJT/R+nGuV3jWFWLFaHyYZWD4tOT+cjn0=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191008105621-543471e840be h1:QAcqgptGM8IQBC9K/RC4o+O9YmqEm0diQn9QmZw/0mU=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191010194322-b09406accb47 h1:/XfQ9z7ib8eEJX2hdgFTZJ/ntt0swNk5oYBziWeTCvY=
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=

View File

@@ -7,29 +7,5 @@
*/
.panel-progress {
background: #3498db;
}
.identicon rect {
fill: #333;
}
.panel-warning .identicon rect {
fill: #fff;
}
.li-column {
background-color: rgb(236, 240, 241);
border-radius: 3px;
}
.panel-heading:hover, .panel-heading:focus {
text-decoration: none;
}
.fancytree-ext-filter-hide tr.fancytree-submatch span.fancytree-title,
.fancytree-ext-filter-hide span.fancytree-node.fancytree-submatch span.fancytree-title {
color: black !important;
font-weight: lighter !important;
}
@import "../../theme-assets/dark/assets/css/theme.css" screen and (prefers-color-scheme: dark);
@import "../../theme-assets/light/assets/css/theme.css" (prefers-color-scheme: light), (prefers-color-scheme: no-preference);

View File

@@ -323,6 +323,11 @@
<span class="visible-xs" aria-label="{{'Scanning' | translate}}"><i class="fas fa-fw fa-search"></i></span>
</span>
<span ng-switch-when="idle"><span class="hidden-xs" translate>Up to Date</span><span class="visible-xs" aria-label="{{'Up to Date' | translate}}"><i class="fas fa-fw fa-check"></i></span></span>
<span ng-switch-when="localadditions"><span class="hidden-xs" translate>Local Additions</span><span class="visible-xs" aria-label="{{'Local Additions' | translate}}"><i class="fas fa-fw fa-check"></i></span></span>
<span ng-switch-when="sync-preparing">
<span class="hidden-xs" translate>Preparing to Sync</span>
<span class="visible-xs" aria-label="{{'Preparing to Sync' | translate}}"><i class="fas fa-fw fa-hourglass-half"></i></span>
</span>
<span ng-switch-when="syncing">
<span class="hidden-xs" translate>Syncing</span>
<span>({{syncPercentage(folder.id) | percent}}, {{model[folder.id].needBytes | binary}}B)</span>

View File

@@ -66,6 +66,14 @@ function folderCompare(a, b) {
return labelA > labelB;
}
function deviceMap(l) {
var m = {};
l.forEach(function (r) {
m[r.deviceID] = r;
});
return m;
}
function folderMap(l) {
var m = {};
l.forEach(function (r) {

View File

@@ -60,7 +60,9 @@ angular.module('syncthing.core')
} catch (exception) { }
$scope.folderDefaults = {
sharedDevices: {},
selectedDevices: {},
unrelatedDevices: {},
type: "sendreceive",
rescanIntervalS: 3600,
fsWatcherDelayS: 10,
@@ -387,6 +389,7 @@ angular.module('syncthing.core')
});
refreshNoAuthWarning();
setDefaultTheme();
if (!hasConfig) {
$scope.$emit('ConfigLoaded');
@@ -649,6 +652,23 @@ angular.module('syncthing.core')
$scope.remoteNeedDevice = undefined;
}
function setDefaultTheme() {
if (!document.getElementById("fallback-theme-css")){
// check if no support for prefers-color-scheme
var colorSchemeNotSupported = typeof window.matchMedia === "undefined" || window.matchMedia('(prefers-color-scheme: dark)').media === 'not all';
if ($scope.config.gui.theme === "default" && colorSchemeNotSupported) {
document.documentElement.style.display = 'none';
document.head.insertAdjacentHTML(
'beforeend',
'<link id="fallback-theme-css" rel="stylesheet" href="theme-assets/light/assets/css/theme.css" onload="document.documentElement.style.display = \'\'">'
);
}
}
}
function saveIgnores(ignores, cb) {
$http.post(urlbase + '/db/ignores?folder=' + encodeURIComponent($scope.currentFolder.id), {
ignore: ignores
@@ -768,25 +788,31 @@ angular.module('syncthing.core')
return 'paused';
}
var folderInfo = $scope.model[folderCfg.id];
// after restart syncthing process state may be empty
if (!$scope.model[folderCfg.id].state) {
if (!folderInfo.state) {
return 'unknown';
}
var state = '' + $scope.model[folderCfg.id].state;
var state = '' + folderInfo.state;
if (state === 'error') {
return 'stopped'; // legacy, the state is called "stopped" in the GUI
}
if (state === 'idle' && $scope.model[folderCfg.id].needTotalItems > 0) {
if (state !== 'idle') {
return state;
}
if (folderInfo.needTotalItems > 0) {
return 'outofsync';
}
if ($scope.hasFailedFiles(folderCfg.id)) {
return 'faileditems';
}
if (state === 'scanning') {
return state;
if (folderInfo.receiveOnlyTotalItems) {
return 'localadditions';
}
if (folderCfg.devices.length <= 1) {
return 'unshared';
}
@@ -797,13 +823,13 @@ angular.module('syncthing.core')
$scope.folderClass = function (folderCfg) {
var status = $scope.folderStatus(folderCfg);
if (status === 'idle') {
if (status === 'idle' || status === 'localadditions') {
return 'success';
}
if (status == 'paused') {
return 'default';
}
if (status === 'syncing' || status === 'scanning') {
if (status === 'syncing' || status === 'sync-preparing' || status === 'scanning') {
return 'primary';
}
if (status === 'unknown') {
@@ -968,6 +994,7 @@ angular.module('syncthing.core')
for (var i = 0; i < folderListCache.length; i++) {
var status = $scope.folderStatus(folderListCache[i]);
switch (status) {
case 'sync-preparing':
case 'syncing':
syncCount++;
break;
@@ -1398,13 +1425,13 @@ angular.module('syncthing.core')
};
$scope.selectAllFolders = function () {
angular.forEach($scope.folders, function (id) {
angular.forEach($scope.folders, function (_, id) {
$scope.currentDevice.selectedFolders[id] = true;
});
};
$scope.deSelectAllFolders = function () {
angular.forEach($scope.folders, function (id) {
angular.forEach($scope.folders, function (_, id) {
$scope.currentDevice.selectedFolders[id] = false;
});
};
@@ -1678,10 +1705,20 @@ angular.module('syncthing.core')
if ($scope.currentFolder.path.length > 1 && $scope.currentFolder.path.slice(-1) === $scope.system.pathSeparator) {
$scope.currentFolder.path = $scope.currentFolder.path.slice(0, -1);
}
// Cache complete device objects indexed by ID for lookups
var devMap = deviceMap($scope.devices)
$scope.currentFolder.sharedDevices = [];
$scope.currentFolder.selectedDevices = {};
$scope.currentFolder.devices.forEach(function (n) {
if (n.deviceID !== $scope.myID) {
$scope.currentFolder.sharedDevices.push(devMap[n.deviceID]);
}
$scope.currentFolder.selectedDevices[n.deviceID] = true;
});
$scope.currentFolder.unrelatedDevices = $scope.devices.filter(function (n) {
return n.deviceID !== $scope.myID
&& ! $scope.currentFolder.selectedDevices[n.deviceID]
});
if ($scope.currentFolder.versioning && $scope.currentFolder.versioning.type === "trashcan") {
$scope.currentFolder.trashcanFileVersioning = true;
$scope.currentFolder.fileVersioningSelector = "trashcan";
@@ -1732,17 +1769,17 @@ angular.module('syncthing.core')
$scope.editFolderModal();
};
$scope.selectAllDevices = function () {
var devices = $scope.otherDevices();
$scope.selectAllSharedDevices = function (state) {
var devices = $scope.currentFolder.sharedDevices;
for (var i = 0; i < devices.length; i++) {
$scope.currentFolder.selectedDevices[devices[i].deviceID] = true;
$scope.currentFolder.selectedDevices[devices[i].deviceID] = !!state;
}
};
$scope.deSelectAllDevices = function () {
var devices = $scope.otherDevices();
$scope.selectAllUnrelatedDevices = function (state) {
var devices = $scope.currentFolder.unrelatedDevices;
for (var i = 0; i < devices.length; i++) {
$scope.currentFolder.selectedDevices[devices[i].deviceID] = false;
$scope.currentFolder.selectedDevices[devices[i].deviceID] = !!state;
}
};
@@ -1751,6 +1788,7 @@ angular.module('syncthing.core')
$scope.editingExisting = false;
$scope.currentFolder = angular.copy($scope.folderDefaults);
$scope.currentFolder.id = (data.random.substr(0, 5) + '-' + data.random.substr(5, 5)).toLowerCase();
$scope.currentFolder.unrelatedDevices = $scope.otherDevices();
$('#folder-ignores textarea').val("");
$('#folder-ignores textarea').removeAttr('disabled');
$scope.editFolderModal();
@@ -1766,6 +1804,7 @@ angular.module('syncthing.core')
importFromOtherDevice: true
};
$scope.currentFolder.selectedDevices[device] = true;
$scope.currentFolder.unrelatedDevices = $scope.otherDevices();
$('#folder-ignores textarea').val("");
$('#folder-ignores textarea').removeAttr('disabled');
$scope.editFolderModal();
@@ -1791,7 +1830,9 @@ angular.module('syncthing.core')
});
}
}
delete folderCfg.sharedDevices;
delete folderCfg.selectedDevices;
delete folderCfg.unrelatedDevices;
if (folderCfg.fileVersioningSelector === "trashcan") {
folderCfg.versioning = {

View File

@@ -44,15 +44,35 @@
</div>
</div>
<div id="folder-sharing" class="tab-pane">
<div class="form-group">
<label translate for="devices">Share With Devices</label>
<div class="form-group" ng-if="currentFolder.sharedDevices.length">
<label translate>Currently Shared With Devices</label>
<p class="help-block">
<span translate>Select the devices to share this folder with.</span>&emsp;
<small><a href="#" ng-click="selectAllDevices()" translate>Select All</a>&emsp;
<a href="#" ng-click="deSelectAllDevices()" translate>Deselect All</a></small>
<span translate>Deselect devices to stop sharing this folder with.</span>&emsp;
<small><a href="#" ng-click="selectAllSharedDevices(true)" translate>Select All</a>&emsp;
<a href="#" ng-click="selectAllSharedDevices(false)" translate>Deselect All</a></small>
</p>
<div class="row">
<div class="col-md-4" ng-repeat="device in otherDevices()">
<div class="col-md-4" ng-repeat="device in currentFolder.sharedDevices">
<div class="checkbox">
<label>
<input type="checkbox" ng-model="currentFolder.selectedDevices[device.deviceID]" /> {{deviceName(device)}}
</label>
</div>
</div>
</div>
</div>
<div class="form-group" ng-if="currentFolder.unrelatedDevices.length || otherDevices().length <= 0">
<label translate>Unshared Devices</label>
<p class="help-block" ng-if="otherDevices().length > 0">
<span translate>Select additional devices to share this folder with.</span>&emsp;
<small><a href="#" ng-click="selectAllUnrelatedDevices(true)" translate>Select All</a>&emsp;
<a href="#" ng-click="selectAllUnrelatedDevices(false)" translate>Deselect All</a></small>
</p>
<p class="help-block" ng-if="otherDevices().length <= 0">
<span translate>There are no devices to share this folder with.</span>
</p>
<div class="row">
<div class="col-md-4" ng-repeat="device in currentFolder.unrelatedDevices">
<div class="checkbox">
<label>
<input type="checkbox" ng-model="currentFolder.selectedDevices[device.deviceID]" /> {{deviceName(device)}}

View File

@@ -0,0 +1,35 @@
/*
// Copyright (C) 2016 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/.
*/
.panel-progress {
background: #3498db;
}
.identicon rect {
fill: #333;
}
.panel-warning .identicon rect {
fill: #fff;
}
.li-column {
background-color: rgb(236, 240, 241);
border-radius: 3px;
}
.panel-heading:hover, .panel-heading:focus {
text-decoration: none;
}
.fancytree-ext-filter-hide tr.fancytree-submatch span.fancytree-title,
.fancytree-ext-filter-hide span.fancytree-node.fancytree-submatch span.fancytree-title {
color: black !important;
font-weight: lighter !important;
}

View File

@@ -9,7 +9,9 @@ package api
import (
"bytes"
"crypto/tls"
"crypto/x509"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
@@ -56,10 +58,11 @@ import (
var bcryptExpr = regexp.MustCompile(`^\$2[aby]\$\d+\$.{50,}`)
const (
DefaultEventMask = events.AllEvents &^ events.LocalChangeDetected &^ events.RemoteChangeDetected
DiskEventMask = events.LocalChangeDetected | events.RemoteChangeDetected
EventSubBufferSize = 1000
defaultEventTimeout = time.Minute
DefaultEventMask = events.AllEvents &^ events.LocalChangeDetected &^ events.RemoteChangeDetected
DiskEventMask = events.LocalChangeDetected | events.RemoteChangeDetected
EventSubBufferSize = 1000
defaultEventTimeout = time.Minute
httpsCertLifetimeDays = 820
)
type service struct {
@@ -146,6 +149,12 @@ func (s *service) getListener(guiCfg config.GUIConfiguration) (net.Listener, err
httpsCertFile := locations.Get(locations.HTTPSCertFile)
httpsKeyFile := locations.Get(locations.HTTPSKeyFile)
cert, err := tls.LoadX509KeyPair(httpsCertFile, httpsKeyFile)
// If the certificate has expired or will expire in the next month, fail
// it and generate a new one.
if err == nil {
err = checkExpiry(cert)
}
if err != nil {
l.Infoln("Loading HTTPS certificate:", err)
l.Infoln("Creating new HTTPS certificate")
@@ -158,7 +167,7 @@ func (s *service) getListener(guiCfg config.GUIConfiguration) (net.Listener, err
name = s.tlsDefaultCommonName
}
cert, err = tlsutil.NewCertificate(httpsCertFile, httpsKeyFile, name)
cert, err = tlsutil.NewCertificate(httpsCertFile, httpsKeyFile, name, httpsCertLifetimeDays)
}
if err != nil {
return nil, err
@@ -1573,10 +1582,10 @@ func (s *service) getHeapProf(w http.ResponseWriter, r *http.Request) {
pprof.WriteHeapProfile(w)
}
func toJsonFileInfoSlice(fs []db.FileInfoTruncated) []jsonDBFileInfo {
res := make([]jsonDBFileInfo, len(fs))
func toJsonFileInfoSlice(fs []db.FileInfoTruncated) []jsonFileInfoTrunc {
res := make([]jsonFileInfoTrunc, len(fs))
for i, f := range fs {
res[i] = jsonDBFileInfo(f)
res[i] = jsonFileInfoTrunc(f)
}
return res
}
@@ -1586,45 +1595,39 @@ func toJsonFileInfoSlice(fs []db.FileInfoTruncated) []jsonDBFileInfo {
type jsonFileInfo protocol.FileInfo
func (f jsonFileInfo) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]interface{}{
"name": f.Name,
"type": f.Type,
"size": f.Size,
"permissions": fmt.Sprintf("%#o", f.Permissions),
"deleted": f.Deleted,
"invalid": protocol.FileInfo(f).IsInvalid(),
"ignored": protocol.FileInfo(f).IsIgnored(),
"mustRescan": protocol.FileInfo(f).MustRescan(),
"noPermissions": f.NoPermissions,
"modified": protocol.FileInfo(f).ModTime(),
"modifiedBy": f.ModifiedBy.String(),
"sequence": f.Sequence,
"numBlocks": len(f.Blocks),
"version": jsonVersionVector(f.Version),
"localFlags": f.LocalFlags,
})
m := fileIntfJSONMap(protocol.FileInfo(f))
m["numBlocks"] = len(f.Blocks)
return json.Marshal(m)
}
type jsonDBFileInfo db.FileInfoTruncated
type jsonFileInfoTrunc db.FileInfoTruncated
func (f jsonDBFileInfo) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]interface{}{
"name": f.Name,
"type": f.Type.String(),
"size": f.Size,
"permissions": fmt.Sprintf("%#o", f.Permissions),
"deleted": f.Deleted,
"invalid": db.FileInfoTruncated(f).IsInvalid(),
"ignored": db.FileInfoTruncated(f).IsIgnored(),
"mustRescan": db.FileInfoTruncated(f).MustRescan(),
"noPermissions": f.NoPermissions,
"modified": db.FileInfoTruncated(f).ModTime(),
"modifiedBy": f.ModifiedBy.String(),
"sequence": f.Sequence,
"numBlocks": nil, // explicitly unknown
"version": jsonVersionVector(f.Version),
"localFlags": f.LocalFlags,
})
func (f jsonFileInfoTrunc) MarshalJSON() ([]byte, error) {
m := fileIntfJSONMap(db.FileInfoTruncated(f))
m["numBlocks"] = nil // explicitly unknown
return json.Marshal(m)
}
func fileIntfJSONMap(f db.FileIntf) map[string]interface{} {
out := map[string]interface{}{
"name": f.FileName(),
"type": f.FileType().String(),
"size": f.FileSize(),
"deleted": f.IsDeleted(),
"invalid": f.IsInvalid(),
"ignored": f.IsIgnored(),
"mustRescan": f.MustRescan(),
"noPermissions": !f.HasPermissionBits(),
"modified": f.ModTime(),
"modifiedBy": f.FileModifiedBy().String(),
"sequence": f.SequenceNo(),
"version": jsonVersionVector(f.FileVersion()),
"localFlags": f.FileLocalFlags(),
}
if f.HasPermissionBits() {
out["permissions"] = fmt.Sprintf("%#o", f.FilePermissions())
}
return out
}
type jsonVersionVector protocol.Vector
@@ -1678,3 +1681,45 @@ func addressIsLocalhost(addr string) bool {
return ip.IsLoopback()
}
}
func checkExpiry(cert tls.Certificate) error {
leaf := cert.Leaf
if leaf == nil {
// Leaf can be nil or not, depending on how parsed the certificate
// was when we got it.
if len(cert.Certificate) < 1 {
// can't happen
return errors.New("no certificate in certificate")
}
var err error
leaf, err = x509.ParseCertificate(cert.Certificate[0])
if err != nil {
return err
}
}
if leaf.Subject.String() != leaf.Issuer.String() ||
len(leaf.DNSNames) != 0 || len(leaf.IPAddresses) != 0 {
// The certificate is not self signed, or has DNS/IP attributes we don't
// add, so we leave it alone.
return nil
}
if leaf.NotAfter.Before(time.Now()) {
return errors.New("certificate has expired")
}
if leaf.NotAfter.Before(time.Now().Add(30 * 24 * time.Hour)) {
return errors.New("certificate will soon expire")
}
// On macOS, check for certificates issued on or after July 1st, 2019,
// with a longer validity time than 825 days.
cutoff := time.Date(2019, 7, 1, 0, 0, 0, 0, time.UTC)
if runtime.GOOS == "darwin" &&
leaf.NotBefore.After(cutoff) &&
leaf.NotAfter.Sub(leaf.NotBefore) > 825*24*time.Hour {
return errors.New("certificate incompatible with macOS 10.15 (Catalina)")
}
return nil
}

View File

@@ -23,21 +23,25 @@ import (
"github.com/syncthing/syncthing/lib/sync"
)
const themePrefix = "theme-assets/"
type staticsServer struct {
assetDir string
assets map[string][]byte
availableThemes []string
mut sync.RWMutex
theme string
mut sync.RWMutex
theme string
lastThemeChange time.Time
}
func newStaticsServer(theme, assetDir string) *staticsServer {
s := &staticsServer{
assetDir: assetDir,
assets: auto.Assets(),
mut: sync.NewRWMutex(),
theme: theme,
assetDir: assetDir,
assets: auto.Assets(),
mut: sync.NewRWMutex(),
theme: theme,
lastThemeChange: time.Now().UTC(),
}
seen := make(map[string]struct{})
@@ -86,8 +90,23 @@ func (s *staticsServer) serveAsset(w http.ResponseWriter, r *http.Request) {
s.mut.RLock()
theme := s.theme
modificationTime := s.lastThemeChange
s.mut.RUnlock()
// If path starts with special prefix, get theme and file from path
if strings.HasPrefix(file, themePrefix) {
path := file[len(themePrefix):]
i := strings.IndexRune(path, '/')
if i == -1 {
http.NotFound(w, r)
return
}
theme = path[:i]
file = path[i+1:]
}
// Check for an override for the current theme.
if s.assetDir != "" {
p := filepath.Join(s.assetDir, theme, filepath.FromSlash(file))
@@ -125,14 +144,12 @@ func (s *staticsServer) serveAsset(w http.ResponseWriter, r *http.Request) {
}
}
etag := fmt.Sprintf("%d", auto.Generated)
modified := time.Unix(auto.Generated, 0).UTC()
w.Header().Set("Last-Modified", modified.Format(http.TimeFormat))
etag := fmt.Sprintf("%d", modificationTime.Unix())
w.Header().Set("Last-Modified", modificationTime.Format(http.TimeFormat))
w.Header().Set("Etag", etag)
if t, err := time.Parse(http.TimeFormat, r.Header.Get("If-Modified-Since")); err == nil {
if modified.Equal(t) || modified.Before(t) {
if modificationTime.Equal(t) || modificationTime.Before(t) {
w.WriteHeader(http.StatusNotModified)
return
}
@@ -199,6 +216,7 @@ func (s *staticsServer) mimeTypeForFile(file string) string {
func (s *staticsServer) setTheme(theme string) {
s.mut.Lock()
s.theme = theme
s.lastThemeChange = time.Now().UTC()
s.mut.Unlock()
}

View File

@@ -32,6 +32,7 @@ import (
"github.com/syncthing/syncthing/lib/model"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/sync"
"github.com/syncthing/syncthing/lib/tlsutil"
"github.com/syncthing/syncthing/lib/ur"
"github.com/thejerf/suture"
)
@@ -565,7 +566,7 @@ func TestCSRFRequired(t *testing.T) {
}
cli := &http.Client{
Timeout: time.Second,
Timeout: time.Minute,
}
// Getting the base URL (i.e. "/") should succeed.
@@ -1119,6 +1120,44 @@ func TestPrefixMatch(t *testing.T) {
}
}
func TestCheckExpiry(t *testing.T) {
dir, err := ioutil.TempDir("", "syncthing-test")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
// Self signed certificates expiring in less than a month are errored so we
// can regenerate in time.
crt, err := tlsutil.NewCertificate(filepath.Join(dir, "crt"), filepath.Join(dir, "key"), "foo.example.com", 29)
if err != nil {
t.Fatal(err)
}
if err := checkExpiry(crt); err == nil {
t.Error("expected expiry error")
}
// Certificates with at least 31 days of life left are fine.
crt, err = tlsutil.NewCertificate(filepath.Join(dir, "crt"), filepath.Join(dir, "key"), "foo.example.com", 31)
if err != nil {
t.Fatal(err)
}
if err := checkExpiry(crt); err != nil {
t.Error("expected no error:", err)
}
if runtime.GOOS == "darwin" {
// Certificates with too long an expiry time are not allowed on macOS
crt, err = tlsutil.NewCertificate(filepath.Join(dir, "crt"), filepath.Join(dir, "key"), "foo.example.com", 1000)
if err != nil {
t.Fatal(err)
}
if err := checkExpiry(crt); err == nil {
t.Error("expected expiry error")
}
}
}
func equalStrings(a, b []string) bool {
if len(a) != len(b) {
return false

View File

@@ -10,7 +10,6 @@ import (
"net"
"time"
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/connections"
"github.com/syncthing/syncthing/lib/db"
"github.com/syncthing/syncthing/lib/model"
@@ -180,10 +179,4 @@ func (m *mockedModel) GetHello(protocol.DeviceID) protocol.HelloIntf {
return nil
}
func (m *mockedModel) AddFolder(cfg config.FolderConfiguration) {}
func (m *mockedModel) RestartFolder(from, to config.FolderConfiguration) {}
func (m *mockedModel) StartFolder(folder string) {}
func (m *mockedModel) StartDeadlockDetector(timeout time.Duration) {}

View File

@@ -16,6 +16,7 @@ import (
"strings"
"sync"
"sync/atomic"
"time"
"github.com/lucas-clemente/quic-go"
@@ -110,6 +111,9 @@ func (t *quicListener) serve(stop chan struct{}) error {
l.Infof("QUIC listener (%v) starting", packetConn.LocalAddr())
defer l.Infof("QUIC listener (%v) shutting down", packetConn.LocalAddr())
acceptFailures := 0
const maxAcceptFailures = 10
for {
select {
case <-ctx.Done():
@@ -121,9 +125,23 @@ func (t *quicListener) serve(stop chan struct{}) error {
if err == context.Canceled {
return nil
} else if err != nil {
l.Warnln("Listen (BEP/quic): Accepting connection:", err)
l.Infoln("Listen (BEP/quic): Accepting connection:", err)
acceptFailures++
if acceptFailures > maxAcceptFailures {
// Return to restart the listener, because something
// seems permanently damaged.
return err
}
// Slightly increased delay for each failure.
time.Sleep(time.Duration(acceptFailures) * time.Second)
continue
}
acceptFailures = 0
l.Debugln("connect from", session.RemoteAddr())
streamCtx, cancel := context.WithTimeout(ctx, quicOperationTimeout)

View File

@@ -83,7 +83,7 @@ var tlsCipherSuiteNames = map[uint16]string{
var tlsVersionNames = map[uint16]string{
tls.VersionTLS12: "TLS1.2",
772: "TLS1.3", // tls.VersionTLS13 constant available in Go 1.12+
tls.VersionTLS13: "TLS1.3",
}
// Service listens and dials all configured unconnected devices, via supported

View File

@@ -51,6 +51,10 @@ type FileIntf interface {
SequenceNo() int64
BlockSize() int
FileVersion() protocol.Vector
FileType() protocol.FileInfoType
FilePermissions() uint32
FileModifiedBy() protocol.ShortID
ModTime() time.Time
}
// The Iterator is called with either a protocol.FileInfo or a

View File

@@ -116,6 +116,17 @@ func (f FileInfoTruncated) FileVersion() protocol.Vector {
return f.Version
}
func (f FileInfoTruncated) FileType() protocol.FileInfoType {
return f.Type
}
func (f FileInfoTruncated) FilePermissions() uint32 {
return f.Permissions
}
func (f FileInfoTruncated) FileModifiedBy() protocol.ShortID {
return f.ModifiedBy
}
func (f FileInfoTruncated) ConvertToIgnoredFileInfo(by protocol.ShortID) protocol.FileInfo {
return protocol.FileInfo{
Name: f.Name,

View File

@@ -1712,6 +1712,7 @@ func (m *CountsSet) Unmarshal(dAtA []byte) error {
func skipStructs(dAtA []byte) (n int, err error) {
l := len(dAtA)
iNdEx := 0
depth := 0
for iNdEx < l {
var wire uint64
for shift := uint(0); ; shift += 7 {
@@ -1743,10 +1744,8 @@ func skipStructs(dAtA []byte) (n int, err error) {
break
}
}
return iNdEx, nil
case 1:
iNdEx += 8
return iNdEx, nil
case 2:
var length int
for shift := uint(0); ; shift += 7 {
@@ -1767,55 +1766,30 @@ func skipStructs(dAtA []byte) (n int, err error) {
return 0, ErrInvalidLengthStructs
}
iNdEx += length
if iNdEx < 0 {
return 0, ErrInvalidLengthStructs
}
return iNdEx, nil
case 3:
for {
var innerWire uint64
var start int = iNdEx
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowStructs
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
innerWire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
innerWireType := int(innerWire & 0x7)
if innerWireType == 4 {
break
}
next, err := skipStructs(dAtA[start:])
if err != nil {
return 0, err
}
iNdEx = start + next
if iNdEx < 0 {
return 0, ErrInvalidLengthStructs
}
}
return iNdEx, nil
depth++
case 4:
return iNdEx, nil
if depth == 0 {
return 0, ErrUnexpectedEndOfGroupStructs
}
depth--
case 5:
iNdEx += 4
return iNdEx, nil
default:
return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
}
if iNdEx < 0 {
return 0, ErrInvalidLengthStructs
}
if depth == 0 {
return iNdEx, nil
}
}
panic("unreachable")
return 0, io.ErrUnexpectedEOF
}
var (
ErrInvalidLengthStructs = fmt.Errorf("proto: negative length found during unmarshaling")
ErrIntOverflowStructs = fmt.Errorf("proto: integer overflow")
ErrInvalidLengthStructs = fmt.Errorf("proto: negative length found during unmarshaling")
ErrIntOverflowStructs = fmt.Errorf("proto: integer overflow")
ErrUnexpectedEndOfGroupStructs = fmt.Errorf("proto: unexpected end of group")
)

View File

@@ -112,7 +112,7 @@ func TestGlobalOverHTTPS(t *testing.T) {
}
// Generate a server certificate.
cert, err := tlsutil.NewCertificate(dir+"/cert.pem", dir+"/key.pem", "syncthing")
cert, err := tlsutil.NewCertificate(dir+"/cert.pem", dir+"/key.pem", "syncthing", 30)
if err != nil {
t.Fatal(err)
}
@@ -177,7 +177,7 @@ func TestGlobalAnnounce(t *testing.T) {
}
// Generate a server certificate.
cert, err := tlsutil.NewCertificate(dir+"/cert.pem", dir+"/key.pem", "syncthing")
cert, err := tlsutil.NewCertificate(dir+"/cert.pem", dir+"/key.pem", "syncthing", 30)
if err != nil {
t.Fatal(err)
}

View File

@@ -313,6 +313,7 @@ func (m *Announce) Unmarshal(dAtA []byte) error {
func skipLocal(dAtA []byte) (n int, err error) {
l := len(dAtA)
iNdEx := 0
depth := 0
for iNdEx < l {
var wire uint64
for shift := uint(0); ; shift += 7 {
@@ -344,10 +345,8 @@ func skipLocal(dAtA []byte) (n int, err error) {
break
}
}
return iNdEx, nil
case 1:
iNdEx += 8
return iNdEx, nil
case 2:
var length int
for shift := uint(0); ; shift += 7 {
@@ -368,55 +367,30 @@ func skipLocal(dAtA []byte) (n int, err error) {
return 0, ErrInvalidLengthLocal
}
iNdEx += length
if iNdEx < 0 {
return 0, ErrInvalidLengthLocal
}
return iNdEx, nil
case 3:
for {
var innerWire uint64
var start int = iNdEx
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowLocal
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
innerWire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
innerWireType := int(innerWire & 0x7)
if innerWireType == 4 {
break
}
next, err := skipLocal(dAtA[start:])
if err != nil {
return 0, err
}
iNdEx = start + next
if iNdEx < 0 {
return 0, ErrInvalidLengthLocal
}
}
return iNdEx, nil
depth++
case 4:
return iNdEx, nil
if depth == 0 {
return 0, ErrUnexpectedEndOfGroupLocal
}
depth--
case 5:
iNdEx += 4
return iNdEx, nil
default:
return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
}
if iNdEx < 0 {
return 0, ErrInvalidLengthLocal
}
if depth == 0 {
return iNdEx, nil
}
}
panic("unreachable")
return 0, io.ErrUnexpectedEOF
}
var (
ErrInvalidLengthLocal = fmt.Errorf("proto: negative length found during unmarshaling")
ErrIntOverflowLocal = fmt.Errorf("proto: integer overflow")
ErrInvalidLengthLocal = fmt.Errorf("proto: negative length found during unmarshaling")
ErrIntOverflowLocal = fmt.Errorf("proto: integer overflow")
ErrUnexpectedEndOfGroupLocal = fmt.Errorf("proto: unexpected end of group")
)

View File

@@ -35,23 +35,16 @@ func ExpandTilde(path string) (string, error) {
}
func getHomeDir() (string, error) {
var home string
switch runtime.GOOS {
case "windows":
home = filepath.Join(os.Getenv("HomeDrive"), os.Getenv("HomePath"))
if home == "" {
home = os.Getenv("UserProfile")
if runtime.GOOS == "windows" {
// Legacy -- we prioritize this for historical reasons, whereas
// os.UserHomeDir uses %USERPROFILE% always.
home := filepath.Join(os.Getenv("HomeDrive"), os.Getenv("HomePath"))
if home != "" {
return home, nil
}
default:
home = os.Getenv("HOME")
}
if home == "" {
return "", errNoHome
}
return home, nil
return os.UserHomeDir()
}
var windowsDisallowedCharacters = string([]rune{

View File

@@ -54,7 +54,7 @@ func TestRecvOnlyRevertDeletes(t *testing.T) {
// Start the folder. This will cause a scan, should discover the other stuff in the folder
m.StartFolder("ro")
m.startFolder("ro")
m.ScanFolder("ro")
// We should now have two files and two directories.
@@ -125,7 +125,7 @@ func TestRecvOnlyRevertNeeds(t *testing.T) {
// Start the folder. This will cause a scan.
m.StartFolder("ro")
m.startFolder("ro")
m.ScanFolder("ro")
// Everything should be in sync.
@@ -221,7 +221,7 @@ func TestRecvOnlyUndoChanges(t *testing.T) {
// Start the folder. This will cause a scan.
m.StartFolder("ro")
m.startFolder("ro")
m.ScanFolder("ro")
// Everything should be in sync.
@@ -317,7 +317,7 @@ func setupROFolder() (*model, *sendOnlyFolder) {
w.SetFolder(fcfg)
m := newModel(w, myID, "syncthing", "dev", db.OpenMemory(), nil)
m.AddFolder(fcfg)
m.addFolder(fcfg)
f := &sendOnlyFolder{
folder: folder{

View File

@@ -176,7 +176,6 @@ func (f *sendReceiveFolder) pull() bool {
l.Debugf("%v pulling", f)
f.setState(FolderSyncing)
f.clearPullErrors()
scanChan := make(chan string)
@@ -194,6 +193,10 @@ func (f *sendReceiveFolder) pull() bool {
default:
}
// Needs to be set on every loop, as the puller might have set
// it to FolderSyncing during the last iteration.
f.setState(FolderSyncPreparing)
changed := f.pullerIteration(scanChan)
l.Debugln(f, "changed", changed, "on try", tries+1)
@@ -844,10 +847,17 @@ func (f *sendReceiveFolder) deleteFileWithCurrent(file, cur protocol.FileInfo, h
if !hasCur {
// We should never try to pull a deletion for a file we don't have in the DB.
l.Debugln(f, "not deleting file we don't have", file.Name)
l.Debugln(f, "not deleting file we don't have, but update db", file.Name)
dbUpdateChan <- dbUpdateJob{file, dbUpdateDeleteFile}
return
}
if err = osutil.TraversesSymlink(f.fs, filepath.Dir(file.Name)); err != nil {
l.Debugln(f, "not deleting file behind symlink on disk, but update db", file.Name)
dbUpdateChan <- dbUpdateJob{file, dbUpdateDeleteFile}
return
}
if err = f.checkToBeDeleted(cur, scanChan); err != nil {
return
}
@@ -1396,6 +1406,8 @@ func (f *sendReceiveFolder) pullerRoutine(in <-chan pullBlockState, out chan<- *
continue
}
f.setState(FolderSyncing) // Does nothing if already FolderSyncing
// The requestLimiter limits how many pending block requests we have
// ongoing at any given time, based on the size of the blocks
// themselves.
@@ -1834,6 +1846,10 @@ func (f *sendReceiveFolder) deleteItemOnDisk(item protocol.FileInfo, scanChan ch
// deleteDirOnDisk attempts to delete a directory. It checks for files/dirs inside
// the directory and removes them if possible or returns an error if it fails
func (f *sendReceiveFolder) deleteDirOnDisk(dir string, scanChan chan<- string) error {
if err := osutil.TraversesSymlink(f.fs, filepath.Dir(dir)); err != nil {
return err
}
files, _ := f.fs.DirNames(dir)
toBeDeleted := make([]string, 0, len(files))

View File

@@ -23,6 +23,7 @@ import (
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/fs"
"github.com/syncthing/syncthing/lib/ignore"
"github.com/syncthing/syncthing/lib/osutil"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/scanner"
"github.com/syncthing/syncthing/lib/sync"
@@ -92,7 +93,7 @@ func setupSendReceiveFolder(files ...protocol.FileInfo) (*model, *sendReceiveFol
w := createTmpWrapper(defaultCfg)
model := newModel(w, myID, "syncthing", "dev", db.OpenMemory(), nil)
fcfg := testFolderConfigTmp()
model.AddFolder(fcfg)
model.addFolder(fcfg)
f := &sendReceiveFolder{
folder: folder{
@@ -124,7 +125,7 @@ func setupSendReceiveFolder(files ...protocol.FileInfo) (*model, *sendReceiveFol
func cleanupSRFolder(f *sendReceiveFolder, m *model) {
m.evLogger.Stop()
os.Remove(m.cfg.ConfigPath())
os.Remove(f.Filesystem().URI())
os.RemoveAll(f.Filesystem().URI())
}
// Layout of the files: (indexes from the above array)
@@ -252,8 +253,22 @@ func TestCopierFinder(t *testing.T) {
f.handleFile(requiredFile, copyChan, dbUpdateChan)
pulls := []pullBlockState{<-pullChan, <-pullChan, <-pullChan, <-pullChan}
finish := <-finisherChan
timeout := time.After(10 * time.Second)
pulls := make([]pullBlockState, 4)
for i := 0; i < 4; i++ {
select {
case pulls[i] = <-pullChan:
case <-timeout:
t.Fatalf("Timed out before receiving all 4 states on pullChan (already got %v)", i)
}
}
var finish *sharedPullerState
select {
case finish = <-finisherChan:
case <-timeout:
t.Fatal("Timed out before receiving 4 states on pullChan")
}
defer cleanupSharedPullerState(finish)
select {
@@ -907,6 +922,58 @@ func TestSRConflictReplaceFileByLink(t *testing.T) {
}
}
// TestDeleteBehindSymlink checks that we don't delete or schedule a scan
// when trying to delete a file behind a symlink.
func TestDeleteBehindSymlink(t *testing.T) {
m, f := setupSendReceiveFolder()
defer cleanupSRFolder(f, m)
ffs := f.Filesystem()
destDir := createTmpDir()
defer os.RemoveAll(destDir)
destFs := fs.NewFilesystem(fs.FilesystemTypeBasic, destDir)
link := "link"
file := filepath.Join(link, "file")
must(t, ffs.MkdirAll(link, 0755))
fi := createFile(t, file, ffs)
f.updateLocalsFromScanning([]protocol.FileInfo{fi})
must(t, osutil.RenameOrCopy(ffs, destFs, file, "file"))
must(t, ffs.RemoveAll(link))
if err := osutil.DebugSymlinkForTestsOnly(destFs.URI(), filepath.Join(ffs.URI(), link)); err != nil {
if runtime.GOOS == "windows" {
// Probably we require permissions we don't have.
t.Skip("Need admin permissions or developer mode to run symlink test on Windows: " + err.Error())
} else {
t.Fatal(err)
}
}
fi.Deleted = true
fi.Version = fi.Version.Update(device1.Short())
scanChan := make(chan string, 1)
dbUpdateChan := make(chan dbUpdateJob, 1)
f.deleteFile(fi, dbUpdateChan, scanChan)
select {
case f := <-scanChan:
t.Fatalf("Received %v on scanChan", f)
case u := <-dbUpdateChan:
if u.jobType != dbUpdateDeleteFile {
t.Errorf("Expected jobType %v, got %v", dbUpdateDeleteFile, u.jobType)
}
if u.file.Name != fi.Name {
t.Errorf("Expected update for %v, got %v", fi.Name, u.file.Name)
}
default:
t.Fatalf("No db update received")
}
if _, err := destFs.Stat("file"); err != nil {
t.Errorf("Expected no error when stating file behind symlink, got %v", err)
}
}
func cleanupSharedPullerState(s *sharedPullerState) {
s.mut.Lock()
defer s.mut.Unlock()

View File

@@ -19,6 +19,7 @@ const (
FolderIdle folderState = iota
FolderScanning
FolderScanWaiting
FolderSyncPreparing
FolderSyncing
FolderError
)
@@ -31,6 +32,8 @@ func (s folderState) String() string {
return "scanning"
case FolderScanWaiting:
return "scan-waiting"
case FolderSyncPreparing:
return "sync-preparing"
case FolderSyncing:
return "syncing"
case FolderError:
@@ -65,29 +68,32 @@ func (s *stateTracker) setState(newState folderState) {
}
s.mut.Lock()
if newState != s.current {
/* This should hold later...
if s.current != FolderIdle && (newState == FolderScanning || newState == FolderSyncing) {
panic("illegal state transition " + s.current.String() + " -> " + newState.String())
}
*/
defer s.mut.Unlock()
eventData := map[string]interface{}{
"folder": s.folderID,
"to": newState.String(),
"from": s.current.String(),
}
if !s.changed.IsZero() {
eventData["duration"] = time.Since(s.changed).Seconds()
}
s.current = newState
s.changed = time.Now()
s.evLogger.Log(events.StateChanged, eventData)
if newState == s.current {
return
}
s.mut.Unlock()
/* This should hold later...
if s.current != FolderIdle && (newState == FolderScanning || newState == FolderSyncing) {
panic("illegal state transition " + s.current.String() + " -> " + newState.String())
}
*/
eventData := map[string]interface{}{
"folder": s.folderID,
"to": newState.String(),
"from": s.current.String(),
}
if !s.changed.IsZero() {
eventData["duration"] = time.Since(s.changed).Seconds()
}
s.current = newState
s.changed = time.Now()
s.evLogger.Log(events.StateChanged, eventData)
}
// getState returns the current state, the time when it last changed, and the

View File

@@ -72,9 +72,6 @@ type Model interface {
connections.Model
AddFolder(cfg config.FolderConfiguration)
RestartFolder(from, to config.FolderConfiguration)
StartFolder(folder string)
ResetFolder(folder string)
DelayScan(folder string, next time.Duration)
ScanFolder(folder string) error
@@ -210,6 +207,9 @@ func NewModel(cfg config.Wrapper, id protocol.DeviceID, clientName, clientVersio
fmut: sync.NewRWMutex(),
pmut: sync.NewRWMutex(),
}
for devID := range cfg.Devices() {
m.deviceStatRefs[devID] = stats.NewDeviceStatisticsReference(m.db, devID.String())
}
m.Add(m.progressEmitter)
scanLimiter.setCapacity(cfg.Options().MaxConcurrentScans)
cfg.Subscribe(m)
@@ -217,6 +217,19 @@ func NewModel(cfg config.Wrapper, id protocol.DeviceID, clientName, clientVersio
return m
}
func (m *model) Serve() {
// Add and start folders
for _, folderCfg := range m.cfg.Folders() {
if folderCfg.Paused {
folderCfg.CreateRoot()
continue
}
m.addFolder(folderCfg)
m.startFolder(folderCfg.ID)
}
m.Supervisor.Serve()
}
func (m *model) Stop() {
m.Supervisor.Stop()
devs := m.cfg.Devices()
@@ -238,8 +251,8 @@ func (m *model) StartDeadlockDetector(timeout time.Duration) {
detector.Watch("pmut", m.pmut)
}
// StartFolder constructs the folder service and starts it.
func (m *model) StartFolder(folder string) {
// startFolder constructs the folder service and starts it.
func (m *model) startFolder(folder string) {
m.fmut.Lock()
defer m.fmut.Unlock()
folderCfg := m.folderCfgs[folder]
@@ -356,7 +369,7 @@ func (m *model) warnAboutOverwritingProtectedFiles(folder string) {
}
}
func (m *model) AddFolder(cfg config.FolderConfiguration) {
func (m *model) addFolder(cfg config.FolderConfiguration) {
if len(cfg.ID) == 0 {
panic("cannot add empty folder id")
}
@@ -385,12 +398,21 @@ func (m *model) addFolderLocked(cfg config.FolderConfiguration, fset *db.FileSet
m.folderIgnores[cfg.ID] = ignores
}
func (m *model) RemoveFolder(cfg config.FolderConfiguration) {
func (m *model) removeFolder(cfg config.FolderConfiguration) {
m.fmut.Lock()
defer m.fmut.Unlock()
// Delete syncthing specific files
cfg.Filesystem().RemoveAll(config.DefaultMarkerName)
isPathUnique := true
for folderID, folderCfg := range m.folderCfgs {
if folderID != cfg.ID && folderCfg.Path == cfg.Path {
isPathUnique = false
break
}
}
if isPathUnique {
// Delete syncthing specific files
cfg.Filesystem().RemoveAll(config.DefaultMarkerName)
}
m.tearDownFolderLocked(cfg, fmt.Errorf("removing folder %v", cfg.Description()))
// Remove it from the database
@@ -428,7 +450,7 @@ func (m *model) tearDownFolderLocked(cfg config.FolderConfiguration, err error)
delete(m.folderRunnerTokens, cfg.ID)
}
func (m *model) RestartFolder(from, to config.FolderConfiguration) {
func (m *model) restartFolder(from, to config.FolderConfiguration) {
if len(to.ID) == 0 {
panic("bug: cannot restart empty folder ID")
}
@@ -2501,8 +2523,8 @@ func (m *model) CommitConfiguration(from, to config.Configuration) bool {
l.Infoln("Paused folder", cfg.Description())
} else {
l.Infoln("Adding folder", cfg.Description())
m.AddFolder(cfg)
m.StartFolder(folderID)
m.addFolder(cfg)
m.startFolder(folderID)
}
}
}
@@ -2511,7 +2533,7 @@ func (m *model) CommitConfiguration(from, to config.Configuration) bool {
toCfg, ok := toFolders[folderID]
if !ok {
// The folder was removed.
m.RemoveFolder(fromCfg)
m.removeFolder(fromCfg)
continue
}
@@ -2522,7 +2544,7 @@ func (m *model) CommitConfiguration(from, to config.Configuration) bool {
// This folder exists on both sides. Settings might have changed.
// Check if anything differs that requires a restart.
if !reflect.DeepEqual(fromCfg.RequiresRestartOnly(), toCfg.RequiresRestartOnly()) {
m.RestartFolder(fromCfg, toCfg)
m.restartFolder(fromCfg, toCfg)
}
// Emit the folder pause/resume event

View File

@@ -405,8 +405,8 @@ func TestClusterConfig(t *testing.T) {
wrapper := createTmpWrapper(cfg)
m := newModel(wrapper, myID, "syncthing", "dev", db, nil)
m.AddFolder(cfg.Folders[0])
m.AddFolder(cfg.Folders[1])
m.addFolder(cfg.Folders[0])
m.addFolder(cfg.Folders[1])
m.ServeBackground()
defer cleanupModel(m)
@@ -1454,8 +1454,8 @@ func TestIgnores(t *testing.T) {
m := setupModel(defaultCfgWrapper)
defer cleanupModel(m)
m.RemoveFolder(defaultFolderConfig)
m.AddFolder(defaultFolderConfig)
m.removeFolder(defaultFolderConfig)
m.addFolder(defaultFolderConfig)
// Reach in and update the ignore matcher to one that always does
// reloads when asked to, instead of checking file mtimes. This is
// because we will be changing the files on disk often enough that the
@@ -1463,7 +1463,7 @@ func TestIgnores(t *testing.T) {
m.fmut.Lock()
m.folderIgnores["default"] = ignore.New(defaultFs, ignore.WithCache(true), ignore.WithChangeDetector(newAlwaysChanged()))
m.fmut.Unlock()
m.StartFolder("default")
m.startFolder("default")
// Make sure the initial scan has finished (ScanFolders is blocking)
m.ScanFolders()
@@ -1486,7 +1486,7 @@ func TestIgnores(t *testing.T) {
}
// Invalid path, marker should be missing, hence returns an error.
m.AddFolder(config.FolderConfiguration{ID: "fresh", Path: "XXX"})
m.addFolder(config.FolderConfiguration{ID: "fresh", Path: "XXX"})
_, _, err = m.GetIgnores("fresh")
if err == nil {
t.Error("No error")
@@ -1496,7 +1496,7 @@ func TestIgnores(t *testing.T) {
pausedDefaultFolderConfig := defaultFolderConfig
pausedDefaultFolderConfig.Paused = true
m.RestartFolder(defaultFolderConfig, pausedDefaultFolderConfig)
m.restartFolder(defaultFolderConfig, pausedDefaultFolderConfig)
// Here folder initialization is not an issue as a paused folder isn't
// added to the model and thus there is no initial scan happening.
@@ -1555,8 +1555,8 @@ func TestROScanRecovery(t *testing.T) {
testOs.RemoveAll(fcfg.Path)
m := newModel(cfg, myID, "syncthing", "dev", ldb, nil)
m.AddFolder(fcfg)
m.StartFolder("default")
m.addFolder(fcfg)
m.startFolder("default")
m.ServeBackground()
defer cleanupModel(m)
@@ -1608,8 +1608,8 @@ func TestRWScanRecovery(t *testing.T) {
testOs.RemoveAll(fcfg.Path)
m := newModel(cfg, myID, "syncthing", "dev", ldb, nil)
m.AddFolder(fcfg)
m.StartFolder("default")
m.addFolder(fcfg)
m.startFolder("default")
m.ServeBackground()
defer cleanupModel(m)
@@ -1636,7 +1636,7 @@ func TestRWScanRecovery(t *testing.T) {
func TestGlobalDirectoryTree(t *testing.T) {
db := db.OpenMemory()
m := newModel(defaultCfgWrapper, myID, "syncthing", "dev", db, nil)
m.AddFolder(defaultFolderConfig)
m.addFolder(defaultFolderConfig)
m.ServeBackground()
defer cleanupModel(m)
@@ -1888,7 +1888,7 @@ func TestGlobalDirectoryTree(t *testing.T) {
func TestGlobalDirectorySelfFixing(t *testing.T) {
db := db.OpenMemory()
m := newModel(defaultCfgWrapper, myID, "syncthing", "dev", db, nil)
m.AddFolder(defaultFolderConfig)
m.addFolder(defaultFolderConfig)
m.ServeBackground()
defer cleanupModel(m)
@@ -2064,7 +2064,7 @@ func BenchmarkTree_100_10(b *testing.B) {
func benchmarkTree(b *testing.B, n1, n2 int) {
db := db.OpenMemory()
m := newModel(defaultCfgWrapper, myID, "syncthing", "dev", db, nil)
m.AddFolder(defaultFolderConfig)
m.addFolder(defaultFolderConfig)
m.ServeBackground()
defer cleanupModel(m)
@@ -2262,8 +2262,8 @@ func TestIndexesForUnknownDevicesDropped(t *testing.T) {
}
m := newModel(defaultCfgWrapper, myID, "syncthing", "dev", dbi, nil)
m.AddFolder(defaultFolderConfig)
m.StartFolder("default")
m.addFolder(defaultFolderConfig)
m.startFolder("default")
defer cleanupModel(m)
// Remote sequence is cached, hence need to recreated.
@@ -2701,8 +2701,8 @@ func TestCustomMarkerName(t *testing.T) {
defer testOs.RemoveAll(fcfg.Path)
m := newModel(cfg, myID, "syncthing", "dev", ldb, nil)
m.AddFolder(fcfg)
m.StartFolder("default")
m.addFolder(fcfg)
m.startFolder("default")
m.ServeBackground()
defer cleanupModel(m)
@@ -3224,7 +3224,7 @@ func TestRequestLimit(t *testing.T) {
go func() {
second, err := m.Request(device1, "default", file, 2000, 0, nil, 0, false)
if err != nil {
t.Fatalf("Second request failed: %v", err)
t.Errorf("Second request failed: %v", err)
}
close(returned)
second.Close()
@@ -3290,7 +3290,7 @@ func TestConnCloseOnRestart(t *testing.T) {
newFcfg.Paused = true
done := make(chan struct{})
go func() {
m.RestartFolder(fcfg, newFcfg)
m.restartFolder(fcfg, newFcfg)
close(done)
}()
select {
@@ -3384,3 +3384,16 @@ func TestDevicePause(t *testing.T) {
t.Fatal("Timed out before device was paused")
}
}
func TestDeviceWasSeen(t *testing.T) {
m, _, fcfg := setupModelWithConnection()
defer cleanupModelAndRemoveDir(m, fcfg.Filesystem().URI())
m.deviceWasSeen(device1)
stats := m.DeviceStatistics()
entry := stats[device1.String()]
if time.Since(entry.LastSeen) > time.Second {
t.Error("device should have been seen now")
}
}

View File

@@ -244,31 +244,37 @@ func TestRequestVersioningSymlinkAttack(t *testing.T) {
}
fc.mut.Unlock()
waitForIdx := func() {
select {
case c := <-idx:
if c == 0 {
t.Fatal("Got empty index update")
}
case <-time.After(5 * time.Second):
t.Fatal("timed out before receiving index update")
}
}
// Send an update for the test file, wait for it to sync and be reported back.
fc.addFile("foo", 0644, protocol.FileInfoTypeSymlink, []byte(tmpdir))
fc.sendIndexUpdate()
for updates := 0; updates < 1; updates += <-idx {
}
waitForIdx()
// Delete the symlink, hoping for it to get versioned
fc.deleteFile("foo")
fc.sendIndexUpdate()
for updates := 0; updates < 1; updates += <-idx {
}
waitForIdx()
// Recreate foo and a file in it with some data
fc.updateFile("foo", 0755, protocol.FileInfoTypeDirectory, nil)
fc.addFile("foo/test", 0644, protocol.FileInfoTypeFile, []byte("testtesttest"))
fc.sendIndexUpdate()
for updates := 0; updates < 1; updates += <-idx {
}
waitForIdx()
// Remove the test file and see if it escaped
fc.deleteFile("foo/test")
fc.sendIndexUpdate()
for updates := 0; updates < 1; updates += <-idx {
}
waitForIdx()
path := filepath.Join(tmpdir, "test")
if _, err := os.Lstat(path); !os.IsNotExist(err) {
@@ -295,8 +301,8 @@ func pullInvalidIgnored(t *testing.T, ft config.FolderType) {
m := setupModel(w)
defer cleanupModelAndRemoveDir(m, fss.URI())
m.RemoveFolder(fcfg)
m.AddFolder(fcfg)
m.removeFolder(fcfg)
m.addFolder(fcfg)
// Reach in and update the ignore matcher to one that always does
// reloads when asked to, instead of checking file mtimes. This is
// because we might be changing the files on disk often enough that the
@@ -304,7 +310,7 @@ func pullInvalidIgnored(t *testing.T, ft config.FolderType) {
m.fmut.Lock()
m.folderIgnores["default"] = ignore.New(fss, ignore.WithChangeDetector(newAlwaysChanged()))
m.fmut.Unlock()
m.StartFolder(fcfg.ID)
m.startFolder(fcfg.ID)
fc := addFakeConn(m, device1)
fc.folder = "default"
@@ -1032,8 +1038,8 @@ func TestIgnoreDeleteUnignore(t *testing.T) {
tmpDir := fss.URI()
defer cleanupModelAndRemoveDir(m, tmpDir)
m.RemoveFolder(fcfg)
m.AddFolder(fcfg)
m.removeFolder(fcfg)
m.addFolder(fcfg)
// Reach in and update the ignore matcher to one that always does
// reloads when asked to, instead of checking file mtimes. This is
// because we might be changing the files on disk often enough that the
@@ -1041,7 +1047,7 @@ func TestIgnoreDeleteUnignore(t *testing.T) {
m.fmut.Lock()
m.folderIgnores["default"] = ignore.New(fss, ignore.WithChangeDetector(newAlwaysChanged()))
m.fmut.Unlock()
m.StartFolder(fcfg.ID)
m.startFolder(fcfg.ID)
fc := addFakeConn(m, device1)
fc.folder = "default"

View File

@@ -107,8 +107,8 @@ func setupModel(w config.Wrapper) *model {
m.ServeBackground()
for id, cfg := range w.Folders() {
if !cfg.Paused {
m.AddFolder(cfg)
m.StartFolder(id)
m.addFolder(cfg)
m.startFolder(id)
}
}

View File

@@ -7,37 +7,9 @@
package osutil
import (
"bytes"
"net"
)
// ResolveInterfaceAddresses returns available addresses of the given network
// type for a given interface.
func ResolveInterfaceAddresses(network, nameOrMac string) []string {
intf, err := net.InterfaceByName(nameOrMac)
if err == nil {
return interfaceAddresses(network, intf)
}
mac, err := net.ParseMAC(nameOrMac)
if err != nil {
return []string{nameOrMac}
}
intfs, err := net.Interfaces()
if err != nil {
return []string{nameOrMac}
}
for _, intf := range intfs {
if bytes.Equal(intf.HardwareAddr, mac) {
return interfaceAddresses(network, &intf)
}
}
return []string{nameOrMac}
}
func interfaceAddresses(network string, intf *net.Interface) []string {
var out []string
addrs, err := intf.Addrs()

View File

@@ -5045,6 +5045,7 @@ func (m *Close) Unmarshal(dAtA []byte) error {
func skipBep(dAtA []byte) (n int, err error) {
l := len(dAtA)
iNdEx := 0
depth := 0
for iNdEx < l {
var wire uint64
for shift := uint(0); ; shift += 7 {
@@ -5076,10 +5077,8 @@ func skipBep(dAtA []byte) (n int, err error) {
break
}
}
return iNdEx, nil
case 1:
iNdEx += 8
return iNdEx, nil
case 2:
var length int
for shift := uint(0); ; shift += 7 {
@@ -5100,55 +5099,30 @@ func skipBep(dAtA []byte) (n int, err error) {
return 0, ErrInvalidLengthBep
}
iNdEx += length
if iNdEx < 0 {
return 0, ErrInvalidLengthBep
}
return iNdEx, nil
case 3:
for {
var innerWire uint64
var start int = iNdEx
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowBep
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
innerWire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
innerWireType := int(innerWire & 0x7)
if innerWireType == 4 {
break
}
next, err := skipBep(dAtA[start:])
if err != nil {
return 0, err
}
iNdEx = start + next
if iNdEx < 0 {
return 0, ErrInvalidLengthBep
}
}
return iNdEx, nil
depth++
case 4:
return iNdEx, nil
if depth == 0 {
return 0, ErrUnexpectedEndOfGroupBep
}
depth--
case 5:
iNdEx += 4
return iNdEx, nil
default:
return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
}
if iNdEx < 0 {
return 0, ErrInvalidLengthBep
}
if depth == 0 {
return iNdEx, nil
}
}
panic("unreachable")
return 0, io.ErrUnexpectedEOF
}
var (
ErrInvalidLengthBep = fmt.Errorf("proto: negative length found during unmarshaling")
ErrIntOverflowBep = fmt.Errorf("proto: integer overflow")
ErrInvalidLengthBep = fmt.Errorf("proto: negative length found during unmarshaling")
ErrIntOverflowBep = fmt.Errorf("proto: integer overflow")
ErrUnexpectedEndOfGroupBep = fmt.Errorf("proto: unexpected end of group")
)

View File

@@ -124,6 +124,18 @@ func (f FileInfo) FileVersion() Vector {
return f.Version
}
func (f FileInfo) FileType() FileInfoType {
return f.Type
}
func (f FileInfo) FilePermissions() uint32 {
return f.Permissions
}
func (f FileInfo) FileModifiedBy() ShortID {
return f.ModifiedBy
}
// WinsConflict returns true if "f" is the one to choose when it is in
// conflict with "other".
func (f FileInfo) WinsConflict(other FileInfo) bool {

View File

@@ -2,56 +2,71 @@
package protocol
import "sync"
import (
"fmt"
"sync"
"sync/atomic"
)
// Global pool to get buffers from. Requires Blocksizes to be initialised,
// therefore it is initialized in the same init() as BlockSizes
var BufferPool bufferPool
type bufferPool struct {
pools []sync.Pool
puts int64
skips int64
misses int64
pools []sync.Pool
hits []int64 // start of slice allocation is always aligned
}
func newBufferPool() bufferPool {
return bufferPool{make([]sync.Pool, len(BlockSizes))}
return bufferPool{
pools: make([]sync.Pool, len(BlockSizes)),
hits: make([]int64, len(BlockSizes)),
}
}
func (p *bufferPool) Get(size int) []byte {
// Too big, isn't pooled
if size > MaxBlockSize {
atomic.AddInt64(&p.skips, 1)
return make([]byte, size)
}
var i int
for i = range BlockSizes {
if size <= BlockSizes[i] {
break
}
}
var bs []byte
// Try the fitting and all bigger pools
for j := i; j < len(BlockSizes); j++ {
bkt := getBucketForLen(size)
for j := bkt; j < len(BlockSizes); j++ {
if intf := p.pools[j].Get(); intf != nil {
bs = *intf.(*[]byte)
atomic.AddInt64(&p.hits[j], 1)
bs := *intf.(*[]byte)
return bs[:size]
}
}
// All pools are empty, must allocate.
return make([]byte, BlockSizes[i])[:size]
atomic.AddInt64(&p.misses, 1)
// All pools are empty, must allocate. For very small slices where we
// didn't have a block to reuse, just allocate a small slice instead of
// a large one. We won't be able to reuse it, but avoid some overhead.
if size < MinBlockSize/64 {
return make([]byte, size)
}
return make([]byte, BlockSizes[bkt])[:size]
}
// Put makes the given byte slice availabe again in the global pool
// Put makes the given byte slice available again in the global pool.
// You must only Put() slices that were returned by Get() or Upgrade().
func (p *bufferPool) Put(bs []byte) {
c := cap(bs)
// Don't buffer huge byte slices
if c > 2*MaxBlockSize {
// Don't buffer slices outside of our pool range
if cap(bs) > MaxBlockSize || cap(bs) < MinBlockSize {
atomic.AddInt64(&p.skips, 1)
return
}
for i := range BlockSizes {
if c >= BlockSizes[i] {
p.pools[i].Put(&bs)
return
}
}
atomic.AddInt64(&p.puts, 1)
bkt := putBucketForCap(cap(bs))
p.pools[bkt].Put(&bs)
}
// Upgrade grows the buffer to the requested size, while attempting to reuse
@@ -67,3 +82,31 @@ func (p *bufferPool) Upgrade(bs []byte, size int) []byte {
p.Put(bs)
return p.Get(size)
}
// getBucketForLen returns the bucket where we should get a slice of a
// certain length. Each bucket is guaranteed to hold slices that are
// precisely the block size for that bucket, so if the block size is larger
// than our size we are good.
func getBucketForLen(len int) int {
for i, blockSize := range BlockSizes {
if len <= blockSize {
return i
}
}
panic(fmt.Sprintf("bug: tried to get impossible block len %d", len))
}
// putBucketForCap returns the bucket where we should put a slice of a
// certain capacity. Each bucket is guaranteed to hold slices that are
// precisely the block size for that bucket, so we just find the matching
// one.
func putBucketForCap(cap int) int {
for i, blockSize := range BlockSizes {
if cap == blockSize {
return i
}
}
panic(fmt.Sprintf("bug: tried to put impossible block cap %d", cap))
}

View File

@@ -0,0 +1,132 @@
// Copyright (C) 2019 The Protocol Authors.
package protocol
import (
"sync"
"testing"
"time"
"github.com/syncthing/syncthing/lib/rand"
)
func TestGetBucketNumbers(t *testing.T) {
cases := []struct {
size int
bkt int
panics bool
}{
{size: 1024, bkt: 0},
{size: MinBlockSize, bkt: 0},
{size: MinBlockSize + 1, bkt: 1},
{size: 2*MinBlockSize - 1, bkt: 1},
{size: 2 * MinBlockSize, bkt: 1},
{size: 2*MinBlockSize + 1, bkt: 2},
{size: MaxBlockSize, bkt: len(BlockSizes) - 1},
{size: MaxBlockSize + 1, panics: true},
}
for _, tc := range cases {
if tc.panics {
shouldPanic(t, func() { getBucketForLen(tc.size) })
} else {
res := getBucketForLen(tc.size)
if res != tc.bkt {
t.Errorf("block of size %d should get from bucket %d, not %d", tc.size, tc.bkt, res)
}
}
}
}
func TestPutBucketNumbers(t *testing.T) {
cases := []struct {
size int
bkt int
panics bool
}{
{size: 1024, panics: true},
{size: MinBlockSize, bkt: 0},
{size: MinBlockSize + 1, panics: true},
{size: 2 * MinBlockSize, bkt: 1},
{size: MaxBlockSize, bkt: len(BlockSizes) - 1},
{size: MaxBlockSize + 1, panics: true},
}
for _, tc := range cases {
if tc.panics {
shouldPanic(t, func() { putBucketForCap(tc.size) })
} else {
res := putBucketForCap(tc.size)
if res != tc.bkt {
t.Errorf("block of size %d should put into bucket %d, not %d", tc.size, tc.bkt, res)
}
}
}
}
func TestStressBufferPool(t *testing.T) {
if testing.Short() {
t.Skip()
}
const routines = 10
const runtime = 2 * time.Second
bp := newBufferPool()
t0 := time.Now()
var wg sync.WaitGroup
fail := make(chan struct{}, routines)
for i := 0; i < routines; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for time.Since(t0) < runtime {
blocks := make([][]byte, 10)
for i := range blocks {
// Request a block of random size with the range
// covering smaller-than-min to larger-than-max and
// everything in between.
want := rand.Intn(1.5 * MaxBlockSize)
blocks[i] = bp.Get(want)
if len(blocks[i]) != want {
fail <- struct{}{}
return
}
}
for i := range blocks {
bp.Put(blocks[i])
}
}
}()
}
wg.Wait()
select {
case <-fail:
t.Fatal("a block was bad size")
default:
}
t.Log(bp.puts, bp.skips, bp.misses, bp.hits)
if bp.puts == 0 || bp.skips == 0 || bp.misses == 0 {
t.Error("didn't exercise some paths")
}
var hits int64
for _, h := range bp.hits {
hits += h
}
if hits == 0 {
t.Error("didn't exercise some paths")
}
}
func shouldPanic(t *testing.T, fn func()) {
defer func() {
if r := recover(); r == nil {
t.Errorf("did not panic")
}
}()
fn()
}

View File

@@ -400,6 +400,7 @@ func (m *TestNewDeviceID) Unmarshal(dAtA []byte) error {
func skipDeviceidTest(dAtA []byte) (n int, err error) {
l := len(dAtA)
iNdEx := 0
depth := 0
for iNdEx < l {
var wire uint64
for shift := uint(0); ; shift += 7 {
@@ -431,10 +432,8 @@ func skipDeviceidTest(dAtA []byte) (n int, err error) {
break
}
}
return iNdEx, nil
case 1:
iNdEx += 8
return iNdEx, nil
case 2:
var length int
for shift := uint(0); ; shift += 7 {
@@ -455,55 +454,30 @@ func skipDeviceidTest(dAtA []byte) (n int, err error) {
return 0, ErrInvalidLengthDeviceidTest
}
iNdEx += length
if iNdEx < 0 {
return 0, ErrInvalidLengthDeviceidTest
}
return iNdEx, nil
case 3:
for {
var innerWire uint64
var start int = iNdEx
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowDeviceidTest
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
innerWire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
innerWireType := int(innerWire & 0x7)
if innerWireType == 4 {
break
}
next, err := skipDeviceidTest(dAtA[start:])
if err != nil {
return 0, err
}
iNdEx = start + next
if iNdEx < 0 {
return 0, ErrInvalidLengthDeviceidTest
}
}
return iNdEx, nil
depth++
case 4:
return iNdEx, nil
if depth == 0 {
return 0, ErrUnexpectedEndOfGroupDeviceidTest
}
depth--
case 5:
iNdEx += 4
return iNdEx, nil
default:
return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
}
if iNdEx < 0 {
return 0, ErrInvalidLengthDeviceidTest
}
if depth == 0 {
return iNdEx, nil
}
}
panic("unreachable")
return 0, io.ErrUnexpectedEOF
}
var (
ErrInvalidLengthDeviceidTest = fmt.Errorf("proto: negative length found during unmarshaling")
ErrIntOverflowDeviceidTest = fmt.Errorf("proto: integer overflow")
ErrInvalidLengthDeviceidTest = fmt.Errorf("proto: negative length found during unmarshaling")
ErrIntOverflowDeviceidTest = fmt.Errorf("proto: integer overflow")
ErrUnexpectedEndOfGroupDeviceidTest = fmt.Errorf("proto: unexpected end of group")
)

View File

@@ -79,28 +79,6 @@ const (
stateReady
)
// Request message flags
const (
FlagFromTemporary uint32 = 1 << iota
)
// ClusterConfigMessage.Folders flags
const (
FlagFolderReadOnly uint32 = 1 << 0
FlagFolderIgnorePerms = 1 << 1
FlagFolderIgnoreDelete = 1 << 2
FlagFolderDisabledTempIndexes = 1 << 3
FlagFolderAll = 1<<4 - 1
)
// ClusterConfigMessage.Folders.Devices flags
const (
FlagShareTrusted uint32 = 1 << 0
FlagShareReadOnly = 1 << 1
FlagIntroducer = 1 << 2
FlagShareBits = 0x000000ff
)
// FileInfo.LocalFlags flags
const (
FlagLocalUnsupported = 1 << 0 // The kind is unsupported, e.g. symlinks on Windows
@@ -120,15 +98,14 @@ const (
)
var (
ErrClosed = errors.New("connection closed")
ErrTimeout = errors.New("read timeout")
ErrSwitchingConnections = errors.New("switching connections")
errUnknownMessage = errors.New("unknown message")
errInvalidFilename = errors.New("filename is invalid")
errUncleanFilename = errors.New("filename not in canonical format")
errDeletedHasBlocks = errors.New("deleted file with non-empty block list")
errDirectoryHasBlocks = errors.New("directory with non-empty block list")
errFileHasNoBlocks = errors.New("file with empty block list")
ErrClosed = errors.New("connection closed")
ErrTimeout = errors.New("read timeout")
errUnknownMessage = errors.New("unknown message")
errInvalidFilename = errors.New("filename is invalid")
errUncleanFilename = errors.New("filename not in canonical format")
errDeletedHasBlocks = errors.New("deleted file with non-empty block list")
errDirectoryHasBlocks = errors.New("directory with non-empty block list")
errFileHasNoBlocks = errors.New("file with empty block list")
)
type Model interface {
@@ -491,6 +468,8 @@ func (c *rawConnection) readMessageAfterHeader(hdr Header, fourByteBuf []byte) (
msgLen := int32(binary.BigEndian.Uint32(fourByteBuf))
if msgLen < 0 {
return nil, fmt.Errorf("negative message length %d", msgLen)
} else if msgLen > MaxMessageLen {
return nil, fmt.Errorf("message length %d exceeds maximum %d", msgLen, MaxMessageLen)
}
// Then comes the message
@@ -980,14 +959,17 @@ func (c *rawConnection) Statistics() Statistics {
func (c *rawConnection) lz4Compress(src []byte) ([]byte, error) {
var err error
buf := BufferPool.Get(len(src))
buf, err = lz4.Encode(buf, src)
buf := BufferPool.Get(lz4.CompressBound(len(src)))
compressed, err := lz4.Encode(buf, src)
if err != nil {
return nil, err
}
if &compressed[0] != &buf[0] {
panic("bug: lz4.Compress allocated, which it must not (should use buffer pool)")
}
binary.BigEndian.PutUint32(buf, binary.LittleEndian.Uint32(buf))
return buf, nil
binary.BigEndian.PutUint32(compressed, binary.LittleEndian.Uint32(compressed))
return compressed, nil
}
func (c *rawConnection) lz4Decompress(src []byte) ([]byte, error) {
@@ -995,9 +977,12 @@ func (c *rawConnection) lz4Decompress(src []byte) ([]byte, error) {
binary.LittleEndian.PutUint32(src, size)
var err error
buf := BufferPool.Get(int(size))
buf, err = lz4.Decode(buf, src)
decoded, err := lz4.Decode(buf, src)
if err != nil {
return nil, err
}
return buf, nil
if &decoded[0] != &buf[0] {
panic("bug: lz4.Decode allocated, which it must not (should use buffer pool)")
}
return decoded, nil
}

View File

@@ -434,6 +434,38 @@ func TestLZ4Compression(t *testing.T) {
}
}
func TestStressLZ4CompressGrows(t *testing.T) {
c := new(rawConnection)
success := 0
for i := 0; i < 100; i++ {
// Create a slize that is precisely one min block size, fill it with
// random data. This shouldn't compress at all, so will in fact
// become larger when LZ4 does its thing.
data := make([]byte, MinBlockSize)
if _, err := rand.Reader.Read(data); err != nil {
t.Fatal("randomness failure")
}
comp, err := c.lz4Compress(data)
if err != nil {
t.Fatal("unexpected compression error: ", err)
}
if len(comp) < len(data) {
// data size should grow. We must have been really unlucky in
// the random generation, try again.
continue
}
// Putting it into the buffer pool shouldn't panic because the block
// should come from there to begin with.
BufferPool.Put(comp)
success++
}
if success == 0 {
t.Fatal("unable to find data that grows when compressed")
}
}
func TestCheckFilename(t *testing.T) {
cases := []struct {
name string

View File

@@ -16,7 +16,6 @@ var (
ResponseSuccess = Response{0, "success"}
ResponseNotFound = Response{1, "not found"}
ResponseAlreadyConnected = Response{2, "already connected"}
ResponseInternalError = Response{99, "internal error"}
ResponseUnexpectedMessage = Response{100, "unexpected message"}
)

View File

@@ -21,6 +21,7 @@ import (
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/fs"
"github.com/syncthing/syncthing/lib/ignore"
"github.com/syncthing/syncthing/lib/osutil"
"github.com/syncthing/syncthing/lib/protocol"
"golang.org/x/text/unicode/norm"
)
@@ -113,6 +114,10 @@ func (w *walker) walk(ctx context.Context) chan ScanResult {
w.Filesystem.Walk(".", hashFiles)
} else {
for _, sub := range w.Subs {
if err := osutil.TraversesSymlink(w.Filesystem, filepath.Dir(sub)); err != nil {
l.Debugf("Skip walking %v as it is below a symlink", sub)
continue
}
w.Filesystem.Walk(sub, hashFiles)
}
}

View File

@@ -322,7 +322,7 @@ func TestWalkRootSymlink(t *testing.T) {
}
defer os.RemoveAll(tmp)
link := tmp + "/link"
link := filepath.Join(tmp, "link")
dest, _ := filepath.Abs("testdata/dir1")
if err := osutil.DebugSymlinkForTestsOnly(dest, link); err != nil {
if runtime.GOOS == "windows" {
@@ -333,13 +333,33 @@ func TestWalkRootSymlink(t *testing.T) {
}
}
// Scan it
// Scan root with symlink at FS root
files := walkDir(fs.NewFilesystem(fs.FilesystemTypeBasic, link), ".", nil, nil, 0)
// Verify that we got two files
if len(files) != 2 {
t.Errorf("expected two files, not %d", len(files))
}
// Scan symlink below FS root
files = walkDir(fs.NewFilesystem(fs.FilesystemTypeBasic, tmp), "link", nil, nil, 0)
// Verify that we got the one symlink, except on windows
if runtime.GOOS == "windows" {
if len(files) != 0 {
t.Errorf("expected no files, not %d", len(files))
}
} else if len(files) != 1 {
t.Errorf("expected one file, not %d", len(files))
}
// Scan path below symlink
files = walkDir(fs.NewFilesystem(fs.FilesystemTypeBasic, tmp), filepath.Join("link", "cfile"), nil, nil, 0)
// Verify that we get nothing
if len(files) != 0 {
t.Errorf("expected no files, not %d", len(files))
}
}
func TestBlocksizeHysteresis(t *testing.T) {

View File

@@ -28,11 +28,6 @@ const (
minioImpl = "minio/sha256-simd"
)
const (
BlockSize = cryptoSha256.BlockSize
Size = cryptoSha256.Size
)
// May be switched out for another implementation
var (
New = cryptoSha256.New

View File

@@ -152,8 +152,7 @@ func TestRWMutex(t *testing.T) {
if len(messages) != 2 {
t.Errorf("Unexpected message count")
}
if !strings.Contains(messages[1], "RUnlockers while locking:\nat sync") || !strings.Contains(messages[1], "sync_test.go:") {
} else if !strings.Contains(messages[1], "RUnlockers while locking:\nat sync") || !strings.Contains(messages[1], "sync_test.go:") {
t.Error("Unexpected message")
}

View File

@@ -37,20 +37,26 @@ import (
)
const (
bepProtocolName = "bep/1.0"
tlsDefaultCommonName = "syncthing"
maxSystemErrors = 5
initialSystemLog = 10
maxSystemLog = 250
bepProtocolName = "bep/1.0"
tlsDefaultCommonName = "syncthing"
maxSystemErrors = 5
initialSystemLog = 10
maxSystemLog = 250
deviceCertLifetimeDays = 20 * 365
)
type ExitStatus int
func (s ExitStatus) AsInt() int {
return int(s)
}
const (
ExitSuccess ExitStatus = 0
ExitError ExitStatus = 1
ExitRestart ExitStatus = 3
ExitUpgrade ExitStatus = 4
ExitSuccess ExitStatus = 0
ExitError ExitStatus = 1
ExitNoUpgradeAvailable ExitStatus = 2
ExitRestart ExitStatus = 3
ExitUpgrade ExitStatus = 4
)
type Options struct {
@@ -73,14 +79,13 @@ type App struct {
opts Options
exitStatus ExitStatus
err error
startOnce sync.Once
stopOnce sync.Once
stop chan struct{}
stopped chan struct{}
}
func New(cfg config.Wrapper, ll *db.Lowlevel, evLogger events.Logger, cert tls.Certificate, opts Options) *App {
return &App{
a := &App{
cfg: cfg,
ll: ll,
evLogger: evLogger,
@@ -89,25 +94,21 @@ func New(cfg config.Wrapper, ll *db.Lowlevel, evLogger events.Logger, cert tls.C
stop: make(chan struct{}),
stopped: make(chan struct{}),
}
}
// Run does the same as start, but then does not return until the app stops. It
// is equivalent to calling Start and then Wait.
func (a *App) Run() ExitStatus {
a.Start()
return a.Wait()
close(a.stopped) // Hasn't been started, so shouldn't block on Wait.
return a
}
// Start executes the app and returns once all the startup operations are done,
// e.g. the API is ready for use.
func (a *App) Start() {
a.startOnce.Do(func() {
if err := a.startup(); err != nil {
a.stopWithErr(ExitError, err)
return
}
go a.run()
})
// Must be called once only.
func (a *App) Start() error {
if err := a.startup(); err != nil {
a.stopWithErr(ExitError, err)
return err
}
a.stopped = make(chan struct{})
go a.run()
return nil
}
func (a *App) startup() error {
@@ -238,16 +239,6 @@ func (a *App) startup() error {
m.StartDeadlockDetector(20 * time.Minute)
}
// Add and start folders
for _, folderCfg := range a.cfg.Folders() {
if folderCfg.Paused {
folderCfg.CreateRoot()
continue
}
m.AddFolder(folderCfg)
m.StartFolder(folderCfg.ID)
}
a.mainService.Add(m)
// Start discovery
@@ -378,7 +369,8 @@ func (a *App) run() {
close(a.stopped)
}
// Wait blocks until the app stops running.
// Wait blocks until the app stops running. Also returns if the app hasn't been
// started yet.
func (a *App) Wait() ExitStatus {
<-a.stopped
return a.exitStatus
@@ -388,11 +380,11 @@ func (a *App) Wait() ExitStatus {
// for the app to stop before returning.
func (a *App) Error() error {
select {
case <-a.stopped:
return nil
case <-a.stop:
return a.err
default:
}
return a.err
return nil
}
// Stop stops the app and sets its exit status to given reason, unless the app
@@ -403,12 +395,8 @@ func (a *App) Stop(stopReason ExitStatus) ExitStatus {
func (a *App) stopWithErr(stopReason ExitStatus, err error) ExitStatus {
a.stopOnce.Do(func() {
// ExitSuccess is the default value for a.exitStatus. If another status
// was already set, ignore the stop reason given as argument to Stop.
if a.exitStatus == ExitSuccess {
a.exitStatus = stopReason
a.err = err
}
a.exitStatus = stopReason
a.err = err
close(a.stop)
})
return a.exitStatus

View File

@@ -7,20 +7,36 @@
package syncthing
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
"time"
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/tlsutil"
)
func tempCfgFilename(t *testing.T) string {
t.Helper()
f, err := ioutil.TempFile("", "syncthing-testConfig-")
if err != nil {
t.Fatal(err)
}
defer f.Close()
return f.Name()
}
func TestShortIDCheck(t *testing.T) {
cfg := config.Wrap("/tmp/test", config.Configuration{
cfg := config.Wrap(tempCfgFilename(t), config.Configuration{
Devices: []config.DeviceConfiguration{
{DeviceID: protocol.DeviceID{8, 16, 24, 32, 40, 48, 56, 0, 0}},
{DeviceID: protocol.DeviceID{8, 16, 24, 32, 40, 48, 56, 1, 1}}, // first 56 bits same, differ in the first 64 bits
},
}, events.NoopLogger)
defer os.Remove(cfg.ConfigPath())
if err := checkShortIDs(cfg); err != nil {
t.Error("Unexpected error:", err)
@@ -37,3 +53,54 @@ func TestShortIDCheck(t *testing.T) {
t.Error("Should have gotten an error")
}
}
func TestStartupFail(t *testing.T) {
tmpDir, err := ioutil.TempDir("", "syncthing-TestStartupFail-")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmpDir)
cert, err := tlsutil.NewCertificate(filepath.Join(tmpDir, "cert"), filepath.Join(tmpDir, "key"), "syncthing", 365)
if err != nil {
t.Fatal(err)
}
id := protocol.NewDeviceID(cert.Certificate[0])
conflID := protocol.DeviceID{}
copy(conflID[:8], id[:8])
cfg := config.Wrap(tempCfgFilename(t), config.Configuration{
Devices: []config.DeviceConfiguration{
{DeviceID: id},
{DeviceID: conflID},
},
}, events.NoopLogger)
defer os.Remove(cfg.ConfigPath())
app := New(cfg, nil, events.NoopLogger, cert, Options{})
startErr := app.Start()
if startErr == nil {
t.Fatal("Expected an error from Start, got nil")
}
done := make(chan struct{})
var waitE ExitStatus
go func() {
waitE = app.Wait()
close(done)
}()
select {
case <-time.After(time.Second):
t.Fatal("Wait did not return within 1s")
case <-done:
}
if waitE != ExitError {
t.Errorf("Got exit status %v, expected %v", waitE, ExitError)
}
if err = app.Error(); err != startErr {
t.Errorf(`Got different errors "%v" from Start and "%v" from Error`, startErr, err)
}
}

View File

@@ -35,6 +35,7 @@ func LoadOrGenerateCertificate(certFile, keyFile string) (tls.Certificate, error
locations.Get(locations.CertFile),
locations.Get(locations.KeyFile),
tlsDefaultCommonName,
deviceCertLifetimeDays,
)
}
return cert, nil

View File

@@ -20,7 +20,6 @@ import (
"os"
"time"
"github.com/syncthing/syncthing/lib/build"
"github.com/syncthing/syncthing/lib/rand"
)
@@ -70,11 +69,6 @@ var (
func init() {
// Creates the list of ciper suites that SecureDefault uses.
cipherSuites = buildCipherSuites()
if build.IsBeta {
// Append "tls13=1" to GODEBUG before starting TLS, to enable TLS
// 1.3 in Go 1.12.
os.Setenv("GODEBUG", os.Getenv("GODEBUG")+",tls13=1")
}
}
// SecureDefault returns a tls.Config with reasonable, secure defaults set.
@@ -95,15 +89,17 @@ func SecureDefault() *tls.Config {
}
// NewCertificate generates and returns a new TLS certificate.
func NewCertificate(certFile, keyFile, commonName string) (tls.Certificate, error) {
func NewCertificate(certFile, keyFile, commonName string, lifetimeDays int) (tls.Certificate, error) {
priv, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
if err != nil {
return tls.Certificate{}, fmt.Errorf("generate key: %s", err)
}
notBefore := time.Now()
notAfter := time.Date(2049, 12, 31, 23, 59, 59, 0, time.UTC)
notBefore := time.Now().Truncate(24 * time.Hour)
notAfter := notBefore.Add(time.Duration(lifetimeDays*24) * time.Hour)
// NOTE: update checkExpiry() appropriately if you add or change attributes
// in here, especially DNSNames or IPAddresses.
template := x509.Certificate{
SerialNumber: new(big.Int).SetInt64(rand.Int63()),
Subject: pkix.Name{

View File

@@ -21,6 +21,10 @@ type Release struct {
Tag string `json:"tag_name"`
Prerelease bool `json:"prerelease"`
Assets []Asset `json:"assets"`
// The HTML URL is needed for human readable links in the output created
// by cmd/stupgrades.
HTMLURL string `json:"html_url"`
}
type Asset struct {
@@ -33,7 +37,6 @@ type Asset struct {
}
var (
ErrVersionUpToDate = errors.New("current version is up to date")
ErrNoReleaseDownload = errors.New("couldn't find a release to download")
ErrNoVersionToSelect = errors.New("no version to select")
ErrUpgradeUnsupported = errors.New("upgrade unsupported")

View File

@@ -133,7 +133,7 @@ nextResult:
return results
}
// Search for UPnP InternetGatewayDevices for <timeout> seconds, ignoring responses from any devices listed in knownDevices.
// Search for UPnP InternetGatewayDevices for <timeout> seconds.
// The order in which the devices appear in the result list is not deterministic
func discover(intf *net.Interface, deviceType string, timeout time.Duration, results chan<- nat.Device) {
ssdp := &net.UDPAddr{IP: []byte{239, 255, 255, 250}, Port: 1900}

View File

@@ -10,6 +10,7 @@ import (
"fmt"
"net/url"
"reflect"
"runtime"
"strconv"
"strings"
@@ -178,7 +179,7 @@ func Address(network, host string) string {
// AsService wraps the given function to implement suture.Service by calling
// that function on serve and closing the passed channel when Stop is called.
func AsService(fn func(stop chan struct{})) suture.Service {
return AsServiceWithError(func(stop chan struct{}) error {
return asServiceWithError(func(stop chan struct{}) error {
fn(stop)
return nil
})
@@ -186,6 +187,7 @@ func AsService(fn func(stop chan struct{})) suture.Service {
type ServiceWithError interface {
suture.Service
fmt.Stringer
Error() error
SetError(error)
}
@@ -193,7 +195,21 @@ type ServiceWithError interface {
// AsServiceWithError does the same as AsService, except that it keeps track
// of an error returned by the given function.
func AsServiceWithError(fn func(stop chan struct{}) error) ServiceWithError {
return asServiceWithError(fn)
}
// caller retrieves information about the creator of the service, i.e. the stack
// two levels up from itself.
func caller() string {
pc := make([]uintptr, 1)
_ = runtime.Callers(4, pc)
f, _ := runtime.CallersFrames(pc).Next()
return f.Function
}
func asServiceWithError(fn func(stop chan struct{}) error) ServiceWithError {
s := &service{
caller: caller(),
serve: fn,
stop: make(chan struct{}),
stopped: make(chan struct{}),
@@ -204,6 +220,7 @@ func AsServiceWithError(fn func(stop chan struct{}) error) ServiceWithError {
}
type service struct {
caller string
serve func(stop chan struct{}) error
stop chan struct{}
stopped chan struct{}
@@ -235,7 +252,12 @@ func (s *service) Serve() {
func (s *service) Stop() {
s.mut.Lock()
close(s.stop)
select {
case <-s.stop:
panic(fmt.Sprintf("Stop called more than once on %v", s))
default:
close(s.stop)
}
s.mut.Unlock()
<-s.stopped
}
@@ -251,3 +273,7 @@ func (s *service) SetError(err error) {
s.err = err
s.mut.Unlock()
}
func (s *service) String() string {
return fmt.Sprintf("Service@%p created by %v", s, s.caller)
}

View File

@@ -6,7 +6,10 @@
package util
import "testing"
import (
"strings"
"testing"
)
type Defaulter struct {
Value string
@@ -222,3 +225,20 @@ func TestCopyMatching(t *testing.T) {
t.Error("NoCopy")
}
}
func TestUtilStopTwicePanic(t *testing.T) {
s := AsService(func(stop chan struct{}) {
<-stop
})
go s.Serve()
s.Stop()
defer func() {
expected := "lib/util.TestUtilStopTwicePanic"
if r := recover(); r == nil || !strings.Contains(r.(string), expected) {
t.Fatalf(`expected panic containing "%v", got "%v"`, expected, r)
}
}()
s.Stop()
}

View File

@@ -2,7 +2,6 @@
set -eu
chown "${PUID}:${PGID}" /var/syncthing \
chown "${PUID}:${PGID}" "${HOME}" \
&& exec su-exec "${PUID}:${PGID}" \
env HOME=/var/syncthing \
/bin/syncthing "$@"
"$@"

View File

@@ -20,6 +20,7 @@ apps:
HOME: ${SNAP_USER_COMMON}
XDG_CONFIG_HOME: ${SNAP_USER_COMMON}
plugs:
- desktop
- home
- network
- network-bind