mirror of
https://github.com/syncthing/syncthing.git
synced 2026-01-05 12:29:14 -05:00
Compare commits
43 Commits
v1.3.1
...
v1.3.2-rc.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6d27cf6563 | ||
|
|
0cc77feabb | ||
|
|
d19b12d3fe | ||
|
|
1d406d62e3 | ||
|
|
1d99e5277a | ||
|
|
879f51b027 | ||
|
|
d3d7408b17 | ||
|
|
9b01e64c66 | ||
|
|
65c172cd8d | ||
|
|
85e6a77f25 | ||
|
|
88244b0c1f | ||
|
|
cd290d2d05 | ||
|
|
bee7cce081 | ||
|
|
f15a1528fc | ||
|
|
6be6de4b4a | ||
|
|
6755a9ca63 | ||
|
|
98a1adebe1 | ||
|
|
31569debeb | ||
|
|
cf420e135e | ||
|
|
3b5dff3f34 | ||
|
|
56cdf2f2d9 | ||
|
|
b1dbe925d4 | ||
|
|
bbdda059bd | ||
|
|
72f26c1e45 | ||
|
|
72194d137c | ||
|
|
8b5bd45a29 | ||
|
|
9084510e1b | ||
|
|
c4f161d8c5 | ||
|
|
ad2d3702ae | ||
|
|
95acb26249 | ||
|
|
4736cccda1 | ||
|
|
1a06ab68eb | ||
|
|
b8907b49f9 | ||
|
|
7b33294955 | ||
|
|
031684116b | ||
|
|
a0c9db1d09 | ||
|
|
aa4b918224 | ||
|
|
7043b1fbba | ||
|
|
9d6b663d1c | ||
|
|
7dc4ac6e1f | ||
|
|
7bad9b3a11 | ||
|
|
6b570ee8dc | ||
|
|
6408a116f9 |
@@ -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
|
||||
|
||||
@@ -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
28
Dockerfile.stdiscosrv
Normal 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
28
Dockerfile.strelaysrv
Normal 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"]
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
11
go.mod
@@ -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
23
go.sum
@@ -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=
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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> 
|
||||
<small><a href="#" ng-click="selectAllDevices()" translate>Select All</a> 
|
||||
<a href="#" ng-click="deSelectAllDevices()" translate>Deselect All</a></small>
|
||||
<span translate>Deselect devices to stop sharing this folder with.</span> 
|
||||
<small><a href="#" ng-click="selectAllSharedDevices(true)" translate>Select All</a> 
|
||||
<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> 
|
||||
<small><a href="#" ng-click="selectAllUnrelatedDevices(true)" translate>Select All</a> 
|
||||
<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)}}
|
||||
|
||||
35
gui/light/assets/css/theme.css
Normal file
35
gui/light/assets/css/theme.css
Normal 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;
|
||||
}
|
||||
133
lib/api/api.go
133
lib/api/api.go
@@ -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
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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")
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
)
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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")
|
||||
)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
132
lib/protocol/bufferpool_test.go
Normal file
132
lib/protocol/bufferpool_test.go
Normal 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()
|
||||
}
|
||||
@@ -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")
|
||||
)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"}
|
||||
)
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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 "$@"
|
||||
"$@"
|
||||
|
||||
@@ -20,6 +20,7 @@ apps:
|
||||
HOME: ${SNAP_USER_COMMON}
|
||||
XDG_CONFIG_HOME: ${SNAP_USER_COMMON}
|
||||
plugs:
|
||||
- desktop
|
||||
- home
|
||||
- network
|
||||
- network-bind
|
||||
|
||||
Reference in New Issue
Block a user