mirror of
https://github.com/syncthing/syncthing.git
synced 2026-01-13 08:19:27 -05:00
Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c00c6b3957 | ||
|
|
cc39341eb9 | ||
|
|
bf7f82f7b2 | ||
|
|
eb857dbc45 | ||
|
|
e7620e951d | ||
|
|
286a25ae49 | ||
|
|
ae70046b49 | ||
|
|
c366933416 | ||
|
|
6a9716e8a1 | ||
|
|
b84ee4d240 | ||
|
|
8a1e54d58a | ||
|
|
3e032c4da6 | ||
|
|
7c3b267645 | ||
|
|
7161a99b04 | ||
|
|
1754c93370 | ||
|
|
4b750b6dc3 | ||
|
|
bf89bffb0b | ||
|
|
e2288fe441 | ||
|
|
8265dac127 | ||
|
|
100870e142 | ||
|
|
12fb7f2a0a | ||
|
|
f1bf4d899a | ||
|
|
591e4d8af1 | ||
|
|
dec6f80d2b | ||
|
|
ec8a748514 | ||
|
|
db15e52743 | ||
|
|
41bfb7a330 | ||
|
|
1c2e96a5ca | ||
|
|
28ff033da6 | ||
|
|
807a6b1022 | ||
|
|
296cc1bca2 |
15
build.go
15
build.go
@@ -20,7 +20,6 @@ import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
@@ -723,7 +722,7 @@ func shouldBuildSyso(dir string) (string, error) {
|
||||
}
|
||||
|
||||
jsonPath := filepath.Join(dir, "versioninfo.json")
|
||||
err = ioutil.WriteFile(jsonPath, bs, 0644)
|
||||
err = os.WriteFile(jsonPath, bs, 0644)
|
||||
if err != nil {
|
||||
return "", errors.New("failed to create " + jsonPath + ": " + err.Error())
|
||||
}
|
||||
@@ -762,12 +761,12 @@ func shouldCleanupSyso(sysoFilePath string) {
|
||||
// exists. The permission bits are copied as well. If dst already exists and
|
||||
// the contents are identical to src the modification time is not updated.
|
||||
func copyFile(src, dst string, perm os.FileMode) error {
|
||||
in, err := ioutil.ReadFile(src)
|
||||
in, err := os.ReadFile(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
out, err := ioutil.ReadFile(dst)
|
||||
out, err := os.ReadFile(dst)
|
||||
if err != nil {
|
||||
// The destination probably doesn't exist, we should create
|
||||
// it.
|
||||
@@ -783,7 +782,7 @@ func copyFile(src, dst string, perm os.FileMode) error {
|
||||
|
||||
copy:
|
||||
os.MkdirAll(filepath.Dir(dst), 0777)
|
||||
if err := ioutil.WriteFile(dst, in, perm); err != nil {
|
||||
if err := os.WriteFile(dst, in, perm); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -958,7 +957,7 @@ func rmr(paths ...string) {
|
||||
}
|
||||
|
||||
func getReleaseVersion() (string, error) {
|
||||
bs, err := ioutil.ReadFile("RELEASE")
|
||||
bs, err := os.ReadFile("RELEASE")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -1290,11 +1289,11 @@ func zipFile(out string, files []archiveFile) {
|
||||
|
||||
if strings.HasSuffix(f.dst, ".txt") {
|
||||
// Text file. Read it and convert line endings.
|
||||
bs, err := ioutil.ReadAll(sf)
|
||||
bs, err := io.ReadAll(sf)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
bs = bytes.Replace(bs, []byte{'\n'}, []byte{'\n', '\r'}, -1)
|
||||
bs = bytes.Replace(bs, []byte{'\n'}, []byte{'\r', '\n'}, -1)
|
||||
fh.UncompressedSize = uint32(len(bs))
|
||||
fh.UncompressedSize64 = uint64(len(bs))
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@ import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
@@ -58,7 +57,7 @@ func main() {
|
||||
func handleFailureFn(dsn, failureDir string) func(w http.ResponseWriter, req *http.Request) {
|
||||
return func(w http.ResponseWriter, req *http.Request) {
|
||||
lr := io.LimitReader(req.Body, maxRequestSize)
|
||||
bs, err := ioutil.ReadAll(lr)
|
||||
bs, err := io.ReadAll(lr)
|
||||
req.Body.Close()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
|
||||
@@ -9,7 +9,7 @@ package main
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -93,7 +93,7 @@ func parseCrashReport(path string, report []byte) (*raven.Packet, error) {
|
||||
}
|
||||
|
||||
r := bytes.NewReader(report)
|
||||
ctx, err := stack.ParseDump(r, ioutil.Discard, false)
|
||||
ctx, err := stack.ParseDump(r, io.Discard, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -59,7 +59,7 @@ func TestParseVersion(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestParseReport(t *testing.T) {
|
||||
bs, err := ioutil.ReadFile("_testdata/panic.log")
|
||||
bs, err := os.ReadFile("_testdata/panic.log")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ package main
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -80,7 +80,7 @@ func (l *githubSourceCodeLoader) Load(filename string, line, context int) ([][]b
|
||||
fmt.Println("Loading source:", resp.Status)
|
||||
return nil, 0
|
||||
}
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
_ = resp.Body.Close()
|
||||
if err != nil {
|
||||
fmt.Println("Loading source:", err.Error())
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
@@ -96,7 +95,7 @@ func (r *crashReceiver) servePut(reportID, fullPath string, w http.ResponseWrite
|
||||
// Read at most maxRequestSize of report data.
|
||||
log.Println("Receiving report", reportID)
|
||||
lr := io.LimitReader(req.Body, maxRequestSize)
|
||||
bs, err := ioutil.ReadAll(lr)
|
||||
bs, err := io.ReadAll(lr)
|
||||
if err != nil {
|
||||
log.Println("Reading report:", err)
|
||||
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||||
@@ -110,7 +109,7 @@ func (r *crashReceiver) servePut(reportID, fullPath string, w http.ResponseWrite
|
||||
gw.Close()
|
||||
|
||||
// Create an output file with the compressed report
|
||||
err = ioutil.WriteFile(fullPath, buf.Bytes(), 0644)
|
||||
err = os.WriteFile(fullPath, buf.Bytes(), 0644)
|
||||
if err != nil {
|
||||
log.Println("Saving report:", err)
|
||||
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||||
|
||||
@@ -10,9 +10,9 @@ import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
@@ -53,5 +53,5 @@ func compressAndWrite(bs []byte, fullPath string) error {
|
||||
gw.Close()
|
||||
|
||||
// Create an output file with the compressed report
|
||||
return ioutil.WriteFile(fullPath, buf.Bytes(), 0644)
|
||||
return os.WriteFile(fullPath, buf.Bytes(), 0644)
|
||||
}
|
||||
|
||||
@@ -419,7 +419,7 @@ func fixupAddresses(remote *net.TCPAddr, addresses []string) []string {
|
||||
|
||||
// If zero port was specified, use remote port.
|
||||
if port == "0" && remote.Port > 0 {
|
||||
port = fmt.Sprintf("%d", remote.Port)
|
||||
port = strconv.Itoa(remote.Port)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
@@ -560,7 +559,7 @@ func limit(addr string, cache *lru.Cache, lock sync.Mutex, intv time.Duration, b
|
||||
}
|
||||
|
||||
func loadRelays(file string) []*relay {
|
||||
content, err := ioutil.ReadFile(file)
|
||||
content, err := os.ReadFile(file)
|
||||
if err != nil {
|
||||
log.Println("Failed to load relays: " + err.Error())
|
||||
return nil
|
||||
@@ -598,11 +597,11 @@ func saveRelays(file string, relays []*relay) error {
|
||||
for _, relay := range relays {
|
||||
content += relay.uri.String() + "\n"
|
||||
}
|
||||
return ioutil.WriteFile(file, []byte(content), 0777)
|
||||
return os.WriteFile(file, []byte(content), 0777)
|
||||
}
|
||||
|
||||
func createTestCertificate() tls.Certificate {
|
||||
tmpDir, err := ioutil.TempDir("", "relaypoolsrv")
|
||||
tmpDir, err := os.MkdirTemp("", "relaypoolsrv")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -200,7 +200,7 @@ func main() {
|
||||
go natSvc.Serve(ctx)
|
||||
defer cancel()
|
||||
found := make(chan struct{})
|
||||
mapping.OnChanged(func(_ *nat.Mapping, _, _ []nat.Address) {
|
||||
mapping.OnChanged(func() {
|
||||
select {
|
||||
case found <- struct{}{}:
|
||||
default:
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
@@ -56,7 +56,7 @@ func poolHandler(pool string, uri *url.URL, mapping mapping, ownCert tls.Certifi
|
||||
continue
|
||||
}
|
||||
|
||||
bs, err := ioutil.ReadAll(resp.Body)
|
||||
bs, err := io.ReadAll(resp.Body)
|
||||
resp.Body.Close()
|
||||
if err != nil {
|
||||
log.Printf("Error joining pool %v: reading response: %v", pool, err)
|
||||
|
||||
@@ -9,7 +9,6 @@ package main
|
||||
import (
|
||||
"flag"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
@@ -69,7 +68,7 @@ func gen() {
|
||||
}
|
||||
|
||||
func sign(keyname, dataname string) {
|
||||
privkey, err := ioutil.ReadFile(keyname)
|
||||
privkey, err := os.ReadFile(keyname)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
@@ -95,7 +94,7 @@ func sign(keyname, dataname string) {
|
||||
}
|
||||
|
||||
func verifyWithFile(signame, dataname, keyname string) {
|
||||
pubkey, err := ioutil.ReadFile(keyname)
|
||||
pubkey, err := os.ReadFile(keyname)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
@@ -103,7 +102,7 @@ func verifyWithFile(signame, dataname, keyname string) {
|
||||
}
|
||||
|
||||
func verifyWithKey(signame, dataname string, pubkey []byte) {
|
||||
sig, err := ioutil.ReadFile(signame)
|
||||
sig, err := os.ReadFile(signame)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -7,53 +7,35 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"container/heap"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/urfave/cli"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/db"
|
||||
)
|
||||
|
||||
type SizedElement struct {
|
||||
key string
|
||||
size int
|
||||
}
|
||||
|
||||
type ElementHeap []SizedElement
|
||||
|
||||
func (h ElementHeap) Len() int { return len(h) }
|
||||
func (h ElementHeap) Less(i, j int) bool { return h[i].size > h[j].size }
|
||||
func (h ElementHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
|
||||
|
||||
func (h *ElementHeap) Push(x interface{}) {
|
||||
*h = append(*h, x.(SizedElement))
|
||||
}
|
||||
|
||||
func (h *ElementHeap) Pop() interface{} {
|
||||
old := *h
|
||||
n := len(old)
|
||||
x := old[n-1]
|
||||
*h = old[0 : n-1]
|
||||
return x
|
||||
}
|
||||
|
||||
func indexDumpSize(*cli.Context) error {
|
||||
type sizedElement struct {
|
||||
key string
|
||||
size int
|
||||
}
|
||||
|
||||
ldb, err := getDB()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
h := &ElementHeap{}
|
||||
heap.Init(h)
|
||||
|
||||
it, err := ldb.NewPrefixIterator(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var ele SizedElement
|
||||
|
||||
var elems []sizedElement
|
||||
for it.Next() {
|
||||
var ele sizedElement
|
||||
|
||||
key := it.Key()
|
||||
switch key[0] {
|
||||
case db.KeyTypeDevice:
|
||||
@@ -94,11 +76,13 @@ func indexDumpSize(*cli.Context) error {
|
||||
ele.key = fmt.Sprintf("UNKNOWN:%x", key)
|
||||
}
|
||||
ele.size = len(it.Value())
|
||||
heap.Push(h, ele)
|
||||
elems = append(elems, ele)
|
||||
}
|
||||
|
||||
for h.Len() > 0 {
|
||||
ele = heap.Pop(h).(SizedElement)
|
||||
sort.Slice(elems, func(i, j int) bool {
|
||||
return elems[i].size > elems[j].size
|
||||
})
|
||||
for _, ele := range elems {
|
||||
fmt.Println(ele.key, ele.size)
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ package cli
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
@@ -61,23 +61,23 @@ func Run() error {
|
||||
fakeFlags := []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "gui-address",
|
||||
Value: "URL",
|
||||
Usage: "Override GUI address (e.g. \"http://192.0.2.42:8443\")",
|
||||
Usage: "Override GUI address to `URL` (e.g. \"http://192.0.2.42:8443\")",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "gui-apikey",
|
||||
Value: "API-KEY",
|
||||
Usage: "Override GUI API key",
|
||||
Usage: "Override GUI API key to `API-KEY`",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "home",
|
||||
Value: "PATH",
|
||||
Usage: "Set configuration and data directory",
|
||||
Usage: "Set configuration and data directory to `PATH`",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "conf",
|
||||
Value: "PATH",
|
||||
Usage: "Set configuration directory (config and keys)",
|
||||
Name: "config",
|
||||
Usage: "Set configuration directory (config and keys) to `PATH`",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "data",
|
||||
Usage: "Set data directory (database and logs) to `PATH`",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -151,7 +151,7 @@ func parseFlags(c *preCli) error {
|
||||
}
|
||||
}
|
||||
// We don't want kong to print anything nor os.Exit (e.g. on -h)
|
||||
parser, err := kong.New(c, kong.Writers(ioutil.Discard, ioutil.Discard), kong.Exit(func(int) {}))
|
||||
parser, err := kong.New(c, kong.Writers(io.Discard, io.Discard), kong.Exit(func(int) {}))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
29
cmd/syncthing/cli/pending.go
Normal file
29
cmd/syncthing/cli/pending.go
Normal file
@@ -0,0 +1,29 @@
|
||||
// Copyright (C) 2021 The Syncthing Authors.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var pendingCommand = cli.Command{
|
||||
Name: "pending",
|
||||
HideHelp: true,
|
||||
Usage: "Pending subcommand group",
|
||||
Subcommands: []cli.Command{
|
||||
{
|
||||
Name: "devices",
|
||||
Usage: "Show pending devices",
|
||||
Action: expects(0, indexDumpOutput("cluster/pending/devices")),
|
||||
},
|
||||
{
|
||||
Name: "folders",
|
||||
Usage: "Show pending folders",
|
||||
Action: expects(0, indexDumpOutput("cluster/pending/folders")),
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -35,6 +35,7 @@ var showCommand = cli.Command{
|
||||
Usage: "Report about connections to other devices",
|
||||
Action: expects(0, indexDumpOutput("system/connections")),
|
||||
},
|
||||
pendingCommand,
|
||||
{
|
||||
Name: "usage",
|
||||
Usage: "Show usage report",
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"mime"
|
||||
"net/http"
|
||||
"os"
|
||||
@@ -23,7 +23,7 @@ import (
|
||||
)
|
||||
|
||||
func responseToBArray(response *http.Response) ([]byte, error) {
|
||||
bytes, err := ioutil.ReadAll(response.Body)
|
||||
bytes, err := io.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
15
cmd/syncthing/cmdutil/options_common.go
Normal file
15
cmd/syncthing/cmdutil/options_common.go
Normal file
@@ -0,0 +1,15 @@
|
||||
// Copyright (C) 2021 The Syncthing Authors.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
package cmdutil
|
||||
|
||||
// CommonOptions are reused among several subcommands
|
||||
type CommonOptions struct {
|
||||
buildCommonOptions
|
||||
ConfDir string `name:"config" placeholder:"PATH" help:"Set configuration directory (config and keys)"`
|
||||
HomeDir string `name:"home" placeholder:"PATH" help:"Set configuration and data directory"`
|
||||
NoDefaultFolder bool `env:"STNODEFAULTFOLDER" help:"Don't create the \"default\" folder on first startup"`
|
||||
}
|
||||
@@ -7,8 +7,8 @@
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package main
|
||||
package cmdutil
|
||||
|
||||
type buildServeOptions struct {
|
||||
type buildCommonOptions struct {
|
||||
HideConsole bool `hidden:""`
|
||||
}
|
||||
@@ -4,8 +4,8 @@
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
package main
|
||||
package cmdutil
|
||||
|
||||
type buildServeOptions struct {
|
||||
type buildCommonOptions struct {
|
||||
HideConsole bool `name:"no-console" help:"Hide console window"`
|
||||
}
|
||||
@@ -18,9 +18,9 @@ func SetConfigDataLocationsFromFlags(homeDir, confDir, dataDir string) error {
|
||||
dataSet := dataDir != ""
|
||||
switch {
|
||||
case dataSet != confSet:
|
||||
return errors.New("either both or none of -conf and -data must be given, use -home to set both at once")
|
||||
return errors.New("either both or none of --config and --data must be given, use --home to set both at once")
|
||||
case homeSet && dataSet:
|
||||
return errors.New("-home must not be used together with -conf and -data")
|
||||
return errors.New("--home must not be used together with --config and --data")
|
||||
case homeSet:
|
||||
confDir = homeDir
|
||||
dataDir = homeDir
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -62,7 +61,7 @@ func uploadPanicLogs(ctx context.Context, urlBase, dir string) {
|
||||
// the log contents. A HEAD request is made to see if the log has already
|
||||
// been reported. If not, a PUT is made with the log contents.
|
||||
func uploadPanicLog(ctx context.Context, urlBase, file string) error {
|
||||
data, err := ioutil.ReadFile(file)
|
||||
data, err := os.ReadFile(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -12,8 +12,8 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/config"
|
||||
@@ -45,7 +45,7 @@ func (c *CLI) Run() error {
|
||||
log.SetFlags(0)
|
||||
|
||||
if c.To == "" && !c.VerifyOnly {
|
||||
return fmt.Errorf("must set --to or --verify")
|
||||
return fmt.Errorf("must set --to or --verify-only")
|
||||
}
|
||||
|
||||
if c.TokenPath == "" {
|
||||
@@ -112,7 +112,7 @@ func (c *CLI) withContinue(err error) error {
|
||||
// error.
|
||||
func (c *CLI) getFolderID() (string, error) {
|
||||
tokenPath := filepath.Join(c.Path, c.TokenPath)
|
||||
bs, err := ioutil.ReadFile(tokenPath)
|
||||
bs, err := os.ReadFile(tokenPath)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("reading folder token: %w", err)
|
||||
}
|
||||
|
||||
144
cmd/syncthing/generate/generate.go
Normal file
144
cmd/syncthing/generate/generate.go
Normal file
@@ -0,0 +1,144 @@
|
||||
// Copyright (C) 2021 The Syncthing Authors.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
// Package generate implements the `syncthing generate` subcommand.
|
||||
package generate
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/syncthing/syncthing/cmd/syncthing/cmdutil"
|
||||
"github.com/syncthing/syncthing/lib/config"
|
||||
"github.com/syncthing/syncthing/lib/events"
|
||||
"github.com/syncthing/syncthing/lib/fs"
|
||||
"github.com/syncthing/syncthing/lib/locations"
|
||||
"github.com/syncthing/syncthing/lib/osutil"
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
"github.com/syncthing/syncthing/lib/syncthing"
|
||||
)
|
||||
|
||||
type CLI struct {
|
||||
cmdutil.CommonOptions
|
||||
GUIUser string `placeholder:"STRING" help:"Specify new GUI authentication user name"`
|
||||
GUIPassword string `placeholder:"STRING" help:"Specify new GUI authentication password (use - to read from standard input)"`
|
||||
}
|
||||
|
||||
func (c *CLI) Run() error {
|
||||
log.SetFlags(0)
|
||||
|
||||
if c.HideConsole {
|
||||
osutil.HideConsole()
|
||||
}
|
||||
|
||||
if c.HomeDir != "" {
|
||||
if c.ConfDir != "" {
|
||||
return fmt.Errorf("--home must not be used together with --config")
|
||||
}
|
||||
c.ConfDir = c.HomeDir
|
||||
}
|
||||
if c.ConfDir == "" {
|
||||
c.ConfDir = locations.GetBaseDir(locations.ConfigBaseDir)
|
||||
}
|
||||
|
||||
// Support reading the password from a pipe or similar
|
||||
if c.GUIPassword == "-" {
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
password, _, err := reader.ReadLine()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed reading GUI password: %w", err)
|
||||
}
|
||||
c.GUIPassword = string(password)
|
||||
}
|
||||
|
||||
if err := Generate(c.ConfDir, c.GUIUser, c.GUIPassword, c.NoDefaultFolder); err != nil {
|
||||
return fmt.Errorf("Failed to generate config and keys: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Generate(confDir, guiUser, guiPassword string, noDefaultFolder bool) error {
|
||||
dir, err := fs.ExpandTilde(confDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := syncthing.EnsureDir(dir, 0700); err != nil {
|
||||
return err
|
||||
}
|
||||
locations.SetBaseDir(locations.ConfigBaseDir, dir)
|
||||
|
||||
var myID protocol.DeviceID
|
||||
certFile, keyFile := locations.Get(locations.CertFile), locations.Get(locations.KeyFile)
|
||||
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
|
||||
if err == nil {
|
||||
log.Println("WARNING: Key exists; will not overwrite.")
|
||||
} else {
|
||||
cert, err = syncthing.GenerateCertificate(certFile, keyFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create certificate: %w", err)
|
||||
}
|
||||
}
|
||||
myID = protocol.NewDeviceID(cert.Certificate[0])
|
||||
log.Println("Device ID:", myID)
|
||||
|
||||
cfgFile := locations.Get(locations.ConfigFile)
|
||||
var cfg config.Wrapper
|
||||
if _, err := os.Stat(cfgFile); err == nil {
|
||||
if guiUser == "" && guiPassword == "" {
|
||||
log.Println("WARNING: Config exists; will not overwrite.")
|
||||
return nil
|
||||
}
|
||||
|
||||
if cfg, _, err = config.Load(cfgFile, myID, events.NoopLogger); err != nil {
|
||||
return fmt.Errorf("load config: %w", err)
|
||||
}
|
||||
} else {
|
||||
if cfg, err = syncthing.DefaultConfig(cfgFile, myID, events.NoopLogger, noDefaultFolder); err != nil {
|
||||
return fmt.Errorf("create config: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
go cfg.Serve(ctx)
|
||||
defer cancel()
|
||||
|
||||
var updateErr error
|
||||
waiter, err := cfg.Modify(func(cfg *config.Configuration) {
|
||||
updateErr = updateGUIAuthentication(&cfg.GUI, guiUser, guiPassword)
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("modify config: %w", err)
|
||||
}
|
||||
|
||||
waiter.Wait()
|
||||
if updateErr != nil {
|
||||
return updateErr
|
||||
}
|
||||
if err := cfg.Save(); err != nil {
|
||||
return fmt.Errorf("save config: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func updateGUIAuthentication(guiCfg *config.GUIConfiguration, guiUser, guiPassword string) error {
|
||||
if guiUser != "" && guiCfg.User != guiUser {
|
||||
guiCfg.User = guiUser
|
||||
log.Println("Updated GUI authentication user name:", guiUser)
|
||||
}
|
||||
|
||||
if guiPassword != "" && guiCfg.Password != guiPassword {
|
||||
if err := guiCfg.HashAndSetPassword(guiPassword); err != nil {
|
||||
return fmt.Errorf("Failed to set GUI authentication password: %w", err)
|
||||
}
|
||||
log.Println("Updated GUI authentication password.")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
_ "net/http/pprof" // Need to import this to support STPROFILER.
|
||||
@@ -36,6 +35,7 @@ import (
|
||||
"github.com/syncthing/syncthing/cmd/syncthing/cli"
|
||||
"github.com/syncthing/syncthing/cmd/syncthing/cmdutil"
|
||||
"github.com/syncthing/syncthing/cmd/syncthing/decrypt"
|
||||
"github.com/syncthing/syncthing/cmd/syncthing/generate"
|
||||
"github.com/syncthing/syncthing/lib/build"
|
||||
"github.com/syncthing/syncthing/lib/config"
|
||||
"github.com/syncthing/syncthing/lib/db"
|
||||
@@ -49,16 +49,13 @@ import (
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
"github.com/syncthing/syncthing/lib/svcutil"
|
||||
"github.com/syncthing/syncthing/lib/syncthing"
|
||||
"github.com/syncthing/syncthing/lib/tlsutil"
|
||||
"github.com/syncthing/syncthing/lib/upgrade"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
tlsDefaultCommonName = "syncthing"
|
||||
deviceCertLifetimeDays = 20 * 365
|
||||
sigTerm = syscall.Signal(15)
|
||||
sigTerm = syscall.Signal(15)
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -76,9 +73,9 @@ above). The value 0 is used to disable all of the above. The default is to
|
||||
show time only (2).
|
||||
|
||||
Logging always happens to the command line (stdout) and optionally to the
|
||||
file at the path specified by -logfile=path. In addition to an path, the special
|
||||
file at the path specified by --logfile=path. In addition to an path, the special
|
||||
values "default" and "-" may be used. The former logs to DATADIR/syncthing.log
|
||||
(see -data-dir), which is the default on Windows, and the latter only to stdout,
|
||||
(see --data), which is the default on Windows, and the latter only to stdout,
|
||||
no file, which is the default anywhere else.
|
||||
|
||||
|
||||
@@ -135,32 +132,30 @@ var (
|
||||
// commands and options here are top level commands to syncthing.
|
||||
// Cli is just a placeholder for the help text (see main).
|
||||
var entrypoint struct {
|
||||
Serve serveOptions `cmd:"" help:"Run Syncthing"`
|
||||
Decrypt decrypt.CLI `cmd:"" help:"Decrypt or verify an encrypted folder"`
|
||||
Cli struct{} `cmd:"" help:"Command line interface for Syncthing"`
|
||||
Serve serveOptions `cmd:"" help:"Run Syncthing"`
|
||||
Generate generate.CLI `cmd:"" help:"Generate key and config, then exit"`
|
||||
Decrypt decrypt.CLI `cmd:"" help:"Decrypt or verify an encrypted folder"`
|
||||
Cli struct{} `cmd:"" help:"Command line interface for Syncthing"`
|
||||
}
|
||||
|
||||
// serveOptions are the options for the `syncthing serve` command.
|
||||
type serveOptions struct {
|
||||
buildServeOptions
|
||||
cmdutil.CommonOptions
|
||||
AllowNewerConfig bool `help:"Allow loading newer than current config version"`
|
||||
Audit bool `help:"Write events to audit file"`
|
||||
AuditFile string `name:"auditfile" placeholder:"PATH" help:"Specify audit file (use \"-\" for stdout, \"--\" for stderr)"`
|
||||
BrowserOnly bool `help:"Open GUI in browser"`
|
||||
ConfDir string `name:"config" placeholder:"PATH" help:"Set configuration directory (config and keys)"`
|
||||
DataDir string `name:"data" placeholder:"PATH" help:"Set data directory (database and logs)"`
|
||||
DeviceID bool `help:"Show the device ID"`
|
||||
GenerateDir string `name:"generate" placeholder:"PATH" help:"Generate key and config in specified dir, then exit"`
|
||||
GenerateDir string `name:"generate" placeholder:"PATH" help:"Generate key and config in specified dir, then exit"` //DEPRECATED: replaced by subcommand!
|
||||
GUIAddress string `name:"gui-address" placeholder:"URL" help:"Override GUI address (e.g. \"http://192.0.2.42:8443\")"`
|
||||
GUIAPIKey string `name:"gui-apikey" placeholder:"API-KEY" help:"Override GUI API key"`
|
||||
HomeDir string `name:"home" placeholder:"PATH" help:"Set configuration and data directory"`
|
||||
LogFile string `name:"logfile" default:"${logFile}" placeholder:"PATH" help:"Log file name (see below)"`
|
||||
LogFlags int `name:"logflags" default:"${logFlags}" placeholder:"BITS" help:"Select information in log line prefix (see below)"`
|
||||
LogMaxFiles int `placeholder:"N" default:"${logMaxFiles}" name:"log-max-old-files" help:"Number of old files to keep (zero to keep only current)"`
|
||||
LogMaxSize int `placeholder:"BYTES" default:"${logMaxSize}" help:"Maximum size of any file (zero to disable log rotation)"`
|
||||
NoBrowser bool `help:"Do not start browser"`
|
||||
NoRestart bool `env:"STNORESTART" help:"Do not restart Syncthing when exiting due to API/GUI command, upgrade, or crash"`
|
||||
NoDefaultFolder bool `env:"STNODEFAULTFOLDER" help:"Don't create the \"default\" folder on first startup"`
|
||||
NoUpgrade bool `env:"STNOUPGRADE" help:"Disable automatic upgrades"`
|
||||
Paths bool `help:"Show configuration paths"`
|
||||
Paused bool `help:"Start with all devices and folders paused"`
|
||||
@@ -342,7 +337,7 @@ func (options serveOptions) Run() error {
|
||||
}
|
||||
|
||||
if options.GenerateDir != "" {
|
||||
if err := generate(options.GenerateDir, options.NoDefaultFolder); err != nil {
|
||||
if err := generate.Generate(options.GenerateDir, "", "", options.NoDefaultFolder); err != nil {
|
||||
l.Warnln("Failed to generate config and keys:", err)
|
||||
os.Exit(svcutil.ExitError.AsInt())
|
||||
}
|
||||
@@ -350,7 +345,7 @@ func (options serveOptions) Run() error {
|
||||
}
|
||||
|
||||
// Ensure that our home directory exists.
|
||||
if err := ensureDir(locations.GetBaseDir(locations.ConfigBaseDir), 0700); err != nil {
|
||||
if err := syncthing.EnsureDir(locations.GetBaseDir(locations.ConfigBaseDir), 0700); err != nil {
|
||||
l.Warnln("Failure on home directory:", err)
|
||||
os.Exit(svcutil.ExitError.AsInt())
|
||||
}
|
||||
@@ -416,8 +411,8 @@ func openGUI(myID protocol.DeviceID) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if cfg.GUI().Enabled {
|
||||
if err := openURL(cfg.GUI().URL()); err != nil {
|
||||
if guiCfg := cfg.GUI(); guiCfg.Enabled {
|
||||
if err := openURL(guiCfg.URL()); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
@@ -426,46 +421,6 @@ func openGUI(myID protocol.DeviceID) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func generate(generateDir string, noDefaultFolder bool) error {
|
||||
dir, err := fs.ExpandTilde(generateDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := ensureDir(dir, 0700); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var myID protocol.DeviceID
|
||||
certFile, keyFile := filepath.Join(dir, "cert.pem"), filepath.Join(dir, "key.pem")
|
||||
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
|
||||
if err == nil {
|
||||
l.Warnln("Key exists; will not overwrite.")
|
||||
} else {
|
||||
cert, err = tlsutil.NewCertificate(certFile, keyFile, tlsDefaultCommonName, deviceCertLifetimeDays)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "create certificate")
|
||||
}
|
||||
}
|
||||
myID = protocol.NewDeviceID(cert.Certificate[0])
|
||||
l.Infoln("Device ID:", myID)
|
||||
|
||||
cfgFile := filepath.Join(dir, "config.xml")
|
||||
if _, err := os.Stat(cfgFile); err == nil {
|
||||
l.Warnln("Config exists; will not overwrite.")
|
||||
return nil
|
||||
}
|
||||
cfg, err := syncthing.DefaultConfig(cfgFile, myID, events.NoopLogger, noDefaultFolder)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = cfg.Save()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "save config")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func debugFacilities() string {
|
||||
facilities := l.Facilities()
|
||||
|
||||
@@ -544,7 +499,7 @@ func upgradeViaRest() error {
|
||||
return err
|
||||
}
|
||||
if resp.StatusCode != 200 {
|
||||
bs, err := ioutil.ReadAll(resp.Body)
|
||||
bs, err := io.ReadAll(resp.Body)
|
||||
defer resp.Body.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -803,29 +758,6 @@ func resetDB() error {
|
||||
return os.RemoveAll(locations.Get(locations.Database))
|
||||
}
|
||||
|
||||
func ensureDir(dir string, mode fs.FileMode) error {
|
||||
fs := fs.NewFilesystem(fs.FilesystemTypeBasic, dir)
|
||||
err := fs.MkdirAll(".", mode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if fi, err := fs.Stat("."); err == nil {
|
||||
// Apprently the stat may fail even though the mkdirall passed. If it
|
||||
// does, we'll just assume things are in order and let other things
|
||||
// fail (like loading or creating the config...).
|
||||
currentMode := fi.Mode() & 0777
|
||||
if currentMode != mode {
|
||||
err := fs.Chmod(".", mode)
|
||||
// This can fail on crappy filesystems, nothing we can do about it.
|
||||
if err != nil {
|
||||
l.Warnln(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func standbyMonitor(app *syncthing.App, cfg config.Wrapper) {
|
||||
restartDelay := 60 * time.Second
|
||||
now := time.Now()
|
||||
|
||||
@@ -8,7 +8,6 @@ package main
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
@@ -18,7 +17,7 @@ import (
|
||||
func TestRotatedFile(t *testing.T) {
|
||||
// Verify that log rotation happens.
|
||||
|
||||
dir, err := ioutil.TempDir("", "syncthing")
|
||||
dir, err := os.MkdirTemp("", "syncthing")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -179,7 +178,7 @@ func TestAutoClosedFile(t *testing.T) {
|
||||
}
|
||||
|
||||
// The file should have both writes in it.
|
||||
bs, err := ioutil.ReadFile(file)
|
||||
bs, err := os.ReadFile(file)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -199,7 +198,7 @@ func TestAutoClosedFile(t *testing.T) {
|
||||
}
|
||||
|
||||
// It should now contain three writes, as the file is always opened for appending
|
||||
bs, err = ioutil.ReadFile(file)
|
||||
bs, err = os.ReadFile(file)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
"encoding/json"
|
||||
"html/template"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
@@ -162,7 +161,7 @@ func main() {
|
||||
if err != nil {
|
||||
log.Fatalln("template:", err)
|
||||
}
|
||||
bs, err := ioutil.ReadAll(fd)
|
||||
bs, err := io.ReadAll(fd)
|
||||
if err != nil {
|
||||
log.Fatalln("template:", err)
|
||||
}
|
||||
@@ -324,7 +323,7 @@ func newDataHandler(db *sql.DB, w http.ResponseWriter, r *http.Request) {
|
||||
rep.Address = addr
|
||||
|
||||
lr := &io.LimitedReader{R: r.Body, N: 40 * 1024}
|
||||
bs, _ := ioutil.ReadAll(lr)
|
||||
bs, _ := io.ReadAll(lr)
|
||||
if err := json.Unmarshal(bs, &rep); err != nil {
|
||||
log.Println("decode:", err)
|
||||
if debug {
|
||||
|
||||
2
go.mod
2
go.mod
@@ -30,7 +30,7 @@ require (
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
|
||||
github.com/klauspost/cpuid/v2 v2.0.9 // indirect
|
||||
github.com/lib/pq v1.10.3
|
||||
github.com/lucas-clemente/quic-go v0.23.0
|
||||
github.com/lucas-clemente/quic-go v0.24.0
|
||||
github.com/maruel/panicparse v1.6.1
|
||||
github.com/maxbrunsfeld/counterfeiter/v6 v6.3.0
|
||||
github.com/minio/sha256-simd v1.0.0
|
||||
|
||||
4
go.sum
4
go.sum
@@ -242,8 +242,8 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/lib/pq v1.10.3 h1:v9QZf2Sn6AmjXtQeFpdoq/eaNtYP6IN+7lcrygsIAtg=
|
||||
github.com/lib/pq v1.10.3/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lucas-clemente/quic-go v0.22.0/go.mod h1:vF5M1XqhBAHgbjKcJOXY3JZz3GP0T3FQhz/uyOUS38Q=
|
||||
github.com/lucas-clemente/quic-go v0.23.0 h1:5vFnKtZ6nHDFsc/F3uuiF4T3y/AXaQdxjUqiVw26GZE=
|
||||
github.com/lucas-clemente/quic-go v0.23.0/go.mod h1:paZuzjXCE5mj6sikVLMvqXk8lJV2AsqtJ6bDhjEfxx0=
|
||||
github.com/lucas-clemente/quic-go v0.24.0 h1:ToR7SIIEdrgOhgVTHvPgdVRJfgVy+N0wQAagH7L4d5g=
|
||||
github.com/lucas-clemente/quic-go v0.24.0/go.mod h1:paZuzjXCE5mj6sikVLMvqXk8lJV2AsqtJ6bDhjEfxx0=
|
||||
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
|
||||
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/marten-seemann/qpack v0.2.1/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc=
|
||||
|
||||
@@ -428,6 +428,13 @@
|
||||
<div ng-if="model[folder.id].ignorePatterns">
|
||||
<a href="" ng-click="editFolderExisting(folder, '#folder-ignores')"><i class="small" translate>Reduced by ignore patterns</i></a>
|
||||
</div>
|
||||
<div ng-if="folder.ignoreDelete">
|
||||
<i class="small">
|
||||
<span translate style="white-space: normal;">Altered by ignoring deletes.</span>
|
||||
<br>
|
||||
<a href="https://docs.syncthing.net/advanced/folder-ignoredelete" target="_blank"><span class="fas fa-question-circle"></span> <span translate>Help</span></a>
|
||||
</i>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="model[folder.id].needTotalItems > 0">
|
||||
@@ -455,12 +462,6 @@
|
||||
<a href="" ng-click="showLocalChanged(folder.id, folder.type)">{{model[folder.id].receiveOnlyTotalItems | alwaysNumber | localeNumber}} <span translate>items</span>, ~{{model[folder.id].receiveOnlyChangedBytes | binary}}B</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="hasReceiveEncryptedItems(folder)">
|
||||
<th><span class="fas fa-fw fa-exclamation-circle"></span> <span translate>Locally Changed Items</span></th>
|
||||
<td class="text-right">
|
||||
<a href="" ng-click="showLocalChanged(folder.id, folder.type)">{{receiveEncryptedItemsCount(folder) | alwaysNumber | localeNumber}} <span translate>items</span>, ~{{model[folder.id].receiveOnlyChangedBytes | binary}}B</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr ng-if="folder.type != 'sendreceive'">
|
||||
<th><span class="fas fa-fw fa-folder"></span> <span translate>Folder Type</span></th>
|
||||
<td class="text-right">
|
||||
|
||||
@@ -931,9 +931,9 @@ angular.module('syncthing.core')
|
||||
return 'faileditems';
|
||||
}
|
||||
if ($scope.hasReceiveOnlyChanged(folderCfg)) {
|
||||
return 'localadditions';
|
||||
}
|
||||
if ($scope.hasReceiveEncryptedItems(folderCfg)) {
|
||||
if (folderCfg.type === "receiveonly") {
|
||||
return 'localadditions';
|
||||
}
|
||||
return 'localunencrypted';
|
||||
}
|
||||
if (folderCfg.devices.length <= 1) {
|
||||
@@ -2609,28 +2609,13 @@ angular.module('syncthing.core')
|
||||
};
|
||||
|
||||
$scope.hasReceiveOnlyChanged = function (folderCfg) {
|
||||
if (!folderCfg || folderCfg.type !== "receiveonly") {
|
||||
if (!folderCfg || folderCfg.type !== ["receiveonly", "receiveencrypted"].indexOf(folderCfg.type) === -1) {
|
||||
return false;
|
||||
}
|
||||
var counts = $scope.model[folderCfg.id];
|
||||
return counts && counts.receiveOnlyTotalItems > 0;
|
||||
};
|
||||
|
||||
$scope.hasReceiveEncryptedItems = function (folderCfg) {
|
||||
if (!folderCfg || folderCfg.type !== "receiveencrypted") {
|
||||
return false;
|
||||
}
|
||||
return $scope.receiveEncryptedItemsCount(folderCfg) > 0;
|
||||
};
|
||||
|
||||
$scope.receiveEncryptedItemsCount = function (folderCfg) {
|
||||
var counts = $scope.model[folderCfg.id];
|
||||
if (!counts) {
|
||||
return 0;
|
||||
}
|
||||
return counts.receiveOnlyTotalItems - counts.receiveOnlyChangedDeletes;
|
||||
}
|
||||
|
||||
$scope.revertOverride = function () {
|
||||
$http.post(
|
||||
urlbase + "/db/" + $scope.revertOverrideParams.operation +"?folder="
|
||||
|
||||
@@ -25,7 +25,11 @@
|
||||
<p class="help-block" ng-if="discovery && discovery.length !== 0">
|
||||
<span translate>You can also select one of these nearby devices:</span>
|
||||
<ul>
|
||||
<li ng-repeat="id in discovery"><a href="#" ng-click="currentDevice.deviceID = id">{{id}}</a></li>
|
||||
<li ng-repeat="id in discovery" style="list-style-type: none;">
|
||||
<a href="#" ng-click="currentDevice.deviceID = id">
|
||||
<identicon data-value="id"></identicon> {{id}}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</p>
|
||||
<p class="help-block">
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
<modal id="remove-device-confirmation" status="warning" icon="fas fa-question-circle" heading="{{'Remove Device' | translate}}" large="no" closeable="yes">
|
||||
<div class="modal-body">
|
||||
<p ng-model="currentDevice.name" style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
|
||||
<span translate translate-value-name="{{currentDevice.name}}">Are you sure you want to remove device {%name%}?</span>
|
||||
<p ng-model="currentDevice.name" translate translate-value-name="{{currentDevice.name}}">
|
||||
Are you sure you want to remove device {%name%}?
|
||||
</p>
|
||||
<p ng-if="willBeReintroducedBy" translate translate-value-reintroducer="{{willBeReintroducedBy}}">
|
||||
{%reintroducer%} might reintroduce this device.
|
||||
</p>
|
||||
<p ng-if="willBeReintroducedBy" translate translate-value-reintroducer="{{willBeReintroducedBy}}">{%reintroducer%} might reintroduce this device.</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-warning pull-left btn-sm" data-dismiss="modal" ng-click="deleteDevice()">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<modal id="remove-folder-confirmation" status="warning" icon="fas fa-question-circle" heading="{{'Remove Folder' | translate}}" large="no" closeable="yes">
|
||||
<div class="modal-body">
|
||||
<p style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
|
||||
<span translate translate-value-label="{{currentFolder.label}}">Are you sure you want to remove folder {%label%}?</span>
|
||||
<p translate translate-value-label="{{currentFolder.label}}">
|
||||
Are you sure you want to remove folder {%label%}?
|
||||
</p>
|
||||
<p translate>
|
||||
No files will be deleted as a result of this operation.
|
||||
|
||||
@@ -15,7 +15,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
@@ -94,6 +93,8 @@ type service struct {
|
||||
systemLog logger.Recorder
|
||||
}
|
||||
|
||||
var _ config.Verifier = &service{}
|
||||
|
||||
type Service interface {
|
||||
suture.Service
|
||||
config.Committer
|
||||
@@ -380,7 +381,7 @@ func (s *service) Serve(ctx context.Context) error {
|
||||
ReadTimeout: 15 * time.Second,
|
||||
// Prevent the HTTP server from logging stuff on its own. The things we
|
||||
// care about we log ourselves from the handlers.
|
||||
ErrorLog: log.New(ioutil.Discard, "", 0),
|
||||
ErrorLog: log.New(io.Discard, "", 0),
|
||||
}
|
||||
|
||||
l.Infoln("GUI and API listening on", listener.Addr())
|
||||
@@ -1096,7 +1097,7 @@ func (s *service) getSystemError(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func (s *service) postSystemError(w http.ResponseWriter, r *http.Request) {
|
||||
bs, _ := ioutil.ReadAll(r.Body)
|
||||
bs, _ := io.ReadAll(r.Body)
|
||||
r.Body.Close()
|
||||
l.Warnln(string(bs))
|
||||
}
|
||||
@@ -1163,7 +1164,7 @@ func (s *service) getSupportBundle(w http.ResponseWriter, r *http.Request) {
|
||||
// Panic files
|
||||
if panicFiles, err := filepath.Glob(filepath.Join(locations.GetBaseDir(locations.ConfigBaseDir), "panic*")); err == nil {
|
||||
for _, f := range panicFiles {
|
||||
if panicFile, err := ioutil.ReadFile(f); err != nil {
|
||||
if panicFile, err := os.ReadFile(f); err != nil {
|
||||
l.Warnf("Support bundle: failed to load %s: %s", filepath.Base(f), err)
|
||||
} else {
|
||||
files = append(files, fileEntry{name: filepath.Base(f), data: panicFile})
|
||||
@@ -1172,7 +1173,7 @@ func (s *service) getSupportBundle(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// Archived log (default on Windows)
|
||||
if logFile, err := ioutil.ReadFile(locations.Get(locations.LogFile)); err == nil {
|
||||
if logFile, err := os.ReadFile(locations.Get(locations.LogFile)); err == nil {
|
||||
files = append(files, fileEntry{name: "log-ondisk.txt", data: logFile})
|
||||
}
|
||||
|
||||
@@ -1231,7 +1232,7 @@ func (s *service) getSupportBundle(w http.ResponseWriter, r *http.Request) {
|
||||
zipFilePath := filepath.Join(locations.GetBaseDir(locations.ConfigBaseDir), zipFileName)
|
||||
|
||||
// Write buffer zip to local zip file (back up)
|
||||
if err := ioutil.WriteFile(zipFilePath, zipFilesBuffer.Bytes(), 0600); err != nil {
|
||||
if err := os.WriteFile(zipFilePath, zipFilesBuffer.Bytes(), 0600); err != nil {
|
||||
l.Warnln("Support bundle: support bundle zip could not be created:", err)
|
||||
}
|
||||
|
||||
@@ -1321,7 +1322,7 @@ func (s *service) getDBIgnores(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *service) postDBIgnores(w http.ResponseWriter, r *http.Request) {
|
||||
qs := r.URL.Query()
|
||||
|
||||
bs, err := ioutil.ReadAll(r.Body)
|
||||
bs, err := io.ReadAll(r.Body)
|
||||
r.Body.Close()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
@@ -1612,7 +1613,7 @@ func (s *service) getFolderVersions(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *service) postFolderVersionsRestore(w http.ResponseWriter, r *http.Request) {
|
||||
qs := r.URL.Query()
|
||||
|
||||
bs, err := ioutil.ReadAll(r.Body)
|
||||
bs, err := io.ReadAll(r.Body)
|
||||
r.Body.Close()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
|
||||
@@ -7,9 +7,7 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
@@ -21,7 +19,6 @@ import (
|
||||
"github.com/syncthing/syncthing/lib/events"
|
||||
"github.com/syncthing/syncthing/lib/rand"
|
||||
"github.com/syncthing/syncthing/lib/sync"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -66,28 +63,12 @@ func basicAuthAndSessionMiddleware(cookieName string, guiCfg config.GUIConfigura
|
||||
http.Error(w, "Not Authorized", http.StatusUnauthorized)
|
||||
}
|
||||
|
||||
hdr := r.Header.Get("Authorization")
|
||||
if !strings.HasPrefix(hdr, "Basic ") {
|
||||
username, password, ok := r.BasicAuth()
|
||||
if !ok {
|
||||
error()
|
||||
return
|
||||
}
|
||||
|
||||
hdr = hdr[6:]
|
||||
bs, err := base64.StdEncoding.DecodeString(hdr)
|
||||
if err != nil {
|
||||
error()
|
||||
return
|
||||
}
|
||||
|
||||
fields := bytes.SplitN(bs, []byte(":"), 2)
|
||||
if len(fields) != 2 {
|
||||
error()
|
||||
return
|
||||
}
|
||||
|
||||
username := string(fields[0])
|
||||
password := string(fields[1])
|
||||
|
||||
authOk := auth(username, password, guiCfg, ldapCfg)
|
||||
if !authOk {
|
||||
usernameIso := string(iso88591ToUTF8([]byte(username)))
|
||||
@@ -135,14 +116,12 @@ func auth(username string, password string, guiCfg config.GUIConfiguration, ldap
|
||||
if guiCfg.AuthMode == config.AuthModeLDAP {
|
||||
return authLDAP(username, password, ldapCfg)
|
||||
} else {
|
||||
return authStatic(username, password, guiCfg.User, guiCfg.Password)
|
||||
return authStatic(username, password, guiCfg)
|
||||
}
|
||||
}
|
||||
|
||||
func authStatic(username string, password string, configUser string, configPassword string) bool {
|
||||
configPasswordBytes := []byte(configPassword)
|
||||
passwordBytes := []byte(password)
|
||||
return bcrypt.CompareHashAndPassword(configPasswordBytes, passwordBytes) == nil && username == configUser
|
||||
func authStatic(username string, password string, guiCfg config.GUIConfiguration) bool {
|
||||
return guiCfg.CompareHashedPassword(password) == nil && username == guiCfg.User
|
||||
}
|
||||
|
||||
func authLDAP(username string, password string, cfg config.LDAPConfiguration) bool {
|
||||
|
||||
@@ -9,19 +9,20 @@ package api
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"github.com/syncthing/syncthing/lib/config"
|
||||
)
|
||||
|
||||
var passwordHashBytes []byte
|
||||
var guiCfg config.GUIConfiguration
|
||||
|
||||
func init() {
|
||||
passwordHashBytes, _ = bcrypt.GenerateFromPassword([]byte("pass"), 0)
|
||||
guiCfg.User = "user"
|
||||
guiCfg.HashAndSetPassword("pass")
|
||||
}
|
||||
|
||||
func TestStaticAuthOK(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ok := authStatic("user", "pass", "user", string(passwordHashBytes))
|
||||
ok := authStatic("user", "pass", guiCfg)
|
||||
if !ok {
|
||||
t.Fatalf("should pass auth")
|
||||
}
|
||||
@@ -30,7 +31,7 @@ func TestStaticAuthOK(t *testing.T) {
|
||||
func TestSimpleAuthUsernameFail(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ok := authStatic("userWRONG", "pass", "user", string(passwordHashBytes))
|
||||
ok := authStatic("userWRONG", "pass", guiCfg)
|
||||
if ok {
|
||||
t.Fatalf("should fail auth")
|
||||
}
|
||||
@@ -39,7 +40,7 @@ func TestSimpleAuthUsernameFail(t *testing.T) {
|
||||
func TestStaticAuthPasswordFail(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ok := authStatic("user", "passWRONG", "user", string(passwordHashBytes))
|
||||
ok := authStatic("user", "passWRONG", guiCfg)
|
||||
if ok {
|
||||
t.Fatalf("should fail auth")
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
@@ -223,7 +222,7 @@ func expectURLToContain(t *testing.T, url, exp string) {
|
||||
return
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadAll(res.Body)
|
||||
data, err := io.ReadAll(res.Body)
|
||||
res.Body.Close()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
@@ -508,7 +507,7 @@ func testHTTPRequest(t *testing.T, baseURL string, tc httpTestCase, apikey strin
|
||||
return
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error reading %s: %v", tc.URL, err)
|
||||
return
|
||||
@@ -1137,7 +1136,7 @@ func TestBrowse(t *testing.T) {
|
||||
|
||||
pathSep := string(os.PathSeparator)
|
||||
|
||||
tmpDir, err := ioutil.TempDir("", "syncthing")
|
||||
tmpDir, err := os.MkdirTemp("", "syncthing")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -1146,7 +1145,7 @@ func TestBrowse(t *testing.T) {
|
||||
if err := os.Mkdir(filepath.Join(tmpDir, "dir"), 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := ioutil.WriteFile(filepath.Join(tmpDir, "file"), []byte("hello"), 0644); err != nil {
|
||||
if err := os.WriteFile(filepath.Join(tmpDir, "file"), []byte("hello"), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.Mkdir(filepath.Join(tmpDir, "MiXEDCase"), 0755); err != nil {
|
||||
@@ -1209,15 +1208,9 @@ func TestPrefixMatch(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestShouldRegenerateCertificate(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)
|
||||
crt, err := tlsutil.NewCertificateInMemory("foo.example.com", 29)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -1226,7 +1219,7 @@ func TestShouldRegenerateCertificate(t *testing.T) {
|
||||
}
|
||||
|
||||
// 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)
|
||||
crt, err = tlsutil.NewCertificateInMemory("foo.example.com", 31)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -1236,7 +1229,7 @@ func TestShouldRegenerateCertificate(t *testing.T) {
|
||||
|
||||
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)
|
||||
crt, err = tlsutil.NewCertificateInMemory("foo.example.com", 1000)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -1257,7 +1250,7 @@ func TestConfigChanges(t *testing.T) {
|
||||
APIKey: testAPIKey,
|
||||
},
|
||||
}
|
||||
tmpFile, err := ioutil.TempFile("", "syncthing-testConfig-")
|
||||
tmpFile, err := os.CreateTemp("", "syncthing-testConfig-")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -1399,7 +1392,7 @@ func runningInContainer() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
bs, err := ioutil.ReadFile("/proc/1/cgroup")
|
||||
bs, err := os.ReadFile("/proc/1/cgroup")
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ package auto_test
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -28,7 +28,7 @@ func TestAssets(t *testing.T) {
|
||||
|
||||
var gr *gzip.Reader
|
||||
gr, _ = gzip.NewReader(strings.NewReader(idx.Content))
|
||||
html, _ := ioutil.ReadAll(gr)
|
||||
html, _ := io.ReadAll(gr)
|
||||
|
||||
if !bytes.Contains(html, []byte("<html")) {
|
||||
t.Fatal("No html in index.html")
|
||||
|
||||
@@ -9,11 +9,9 @@ package api
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"github.com/julienschmidt/httprouter"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/config"
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
@@ -290,11 +288,13 @@ func (c *configMuxBuilder) adjustConfig(w http.ResponseWriter, r *http.Request)
|
||||
var errMsg string
|
||||
var status int
|
||||
waiter, err := c.cfg.Modify(func(cfg *config.Configuration) {
|
||||
if to.GUI.Password, err = checkGUIPassword(cfg.GUI.Password, to.GUI.Password); err != nil {
|
||||
l.Warnln("bcrypting password:", err)
|
||||
errMsg = err.Error()
|
||||
status = http.StatusInternalServerError
|
||||
return
|
||||
if to.GUI.Password != cfg.GUI.Password {
|
||||
if err := to.GUI.HashAndSetPassword(to.GUI.Password); err != nil {
|
||||
l.Warnln("hashing password:", err)
|
||||
errMsg = err.Error()
|
||||
status = http.StatusInternalServerError
|
||||
return
|
||||
}
|
||||
}
|
||||
*cfg = to
|
||||
})
|
||||
@@ -370,11 +370,13 @@ func (c *configMuxBuilder) adjustGUI(w http.ResponseWriter, r *http.Request, gui
|
||||
var errMsg string
|
||||
var status int
|
||||
waiter, err := c.cfg.Modify(func(cfg *config.Configuration) {
|
||||
if gui.Password, err = checkGUIPassword(oldPassword, gui.Password); err != nil {
|
||||
l.Warnln("bcrypting password:", err)
|
||||
errMsg = err.Error()
|
||||
status = http.StatusInternalServerError
|
||||
return
|
||||
if gui.Password != oldPassword {
|
||||
if err := gui.HashAndSetPassword(gui.Password); err != nil {
|
||||
l.Warnln("hashing password:", err)
|
||||
errMsg = err.Error()
|
||||
status = http.StatusInternalServerError
|
||||
return
|
||||
}
|
||||
}
|
||||
cfg.GUI = gui
|
||||
})
|
||||
@@ -404,7 +406,7 @@ func (c *configMuxBuilder) adjustLDAP(w http.ResponseWriter, r *http.Request, ld
|
||||
|
||||
// Unmarshals the content of the given body and stores it in to (i.e. to must be a pointer).
|
||||
func unmarshalTo(body io.ReadCloser, to interface{}) error {
|
||||
bs, err := ioutil.ReadAll(body)
|
||||
bs, err := io.ReadAll(body)
|
||||
body.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -418,14 +420,6 @@ func unmarshalToRawMessages(body io.ReadCloser) ([]json.RawMessage, error) {
|
||||
return data, err
|
||||
}
|
||||
|
||||
func checkGUIPassword(oldPassword, newPassword string) (string, error) {
|
||||
if newPassword == oldPassword {
|
||||
return newPassword, nil
|
||||
}
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(newPassword), 0)
|
||||
return string(hash), err
|
||||
}
|
||||
|
||||
func (c *configMuxBuilder) finish(w http.ResponseWriter, waiter config.Waiter) {
|
||||
waiter.Wait()
|
||||
if err := c.cfg.Save(); err != nil {
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strconv"
|
||||
@@ -31,7 +30,7 @@ func compress(s string) string {
|
||||
func decompress(p []byte) (out []byte) {
|
||||
r, err := gzip.NewReader(bytes.NewBuffer(p))
|
||||
if err == nil {
|
||||
out, err = ioutil.ReadAll(r)
|
||||
out, err = io.ReadAll(r)
|
||||
}
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -81,7 +80,7 @@ func testServe(t *testing.T, gzip bool) {
|
||||
t.Errorf("unexpected ETag %q", etag)
|
||||
}
|
||||
|
||||
body, _ := ioutil.ReadAll(res.Body)
|
||||
body, _ := io.ReadAll(res.Body)
|
||||
|
||||
// Content-Length is the number of bytes in the encoded (compressed) body
|
||||
// (https://stackoverflow.com/a/3819303).
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
@@ -163,7 +162,7 @@ func ReadXML(r io.Reader, myID protocol.DeviceID) (Configuration, int, error) {
|
||||
}
|
||||
|
||||
func ReadJSON(r io.Reader, myID protocol.DeviceID) (Configuration, error) {
|
||||
bs, err := ioutil.ReadAll(r)
|
||||
bs, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
return Configuration{}, err
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
@@ -502,7 +501,7 @@ func TestFolderPath(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFolderCheckPath(t *testing.T) {
|
||||
n, err := ioutil.TempDir("", "")
|
||||
n, err := os.MkdirTemp("", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -595,6 +594,41 @@ func TestNewSaveLoad(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestWindowsLineEndings(t *testing.T) {
|
||||
if runtime.GOOS != "windows" {
|
||||
t.Skip("Windows specific")
|
||||
}
|
||||
|
||||
dir, err := os.MkdirTemp("", "syncthing-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
path := filepath.Join(dir, "config.xml")
|
||||
os.Remove(path)
|
||||
defer os.Remove(path)
|
||||
|
||||
intCfg := New(device1)
|
||||
cfg := wrap(path, intCfg, device1)
|
||||
defer cfg.stop()
|
||||
|
||||
if err := cfg.Save(); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
bs, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
unixLineEndings := bytes.Count(bs, []byte("\n"))
|
||||
windowsLineEndings := bytes.Count(bs, []byte("\r\n"))
|
||||
if unixLineEndings == 0 || windowsLineEndings != unixLineEndings {
|
||||
t.Error("expected there to be a non-zero number of Windows line endings")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrepare(t *testing.T) {
|
||||
var cfg Configuration
|
||||
|
||||
@@ -643,8 +677,8 @@ func TestCopy(t *testing.T) {
|
||||
t.Error("Config should have changed")
|
||||
}
|
||||
if !bytes.Equal(bsOrig, bsCopy) {
|
||||
// ioutil.WriteFile("a", bsOrig, 0644)
|
||||
// ioutil.WriteFile("b", bsCopy, 0644)
|
||||
// os.WriteFile("a", bsOrig, 0644)
|
||||
// os.WriteFile("b", bsCopy, 0644)
|
||||
t.Error("Copy should be unchanged")
|
||||
}
|
||||
}
|
||||
@@ -739,6 +773,27 @@ func TestGUIConfigURL(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGUIPasswordHash(t *testing.T) {
|
||||
var c GUIConfiguration
|
||||
|
||||
testPass := "pass"
|
||||
if err := c.HashAndSetPassword(testPass); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if c.Password == testPass {
|
||||
t.Error("Password hashing resulted in plaintext")
|
||||
}
|
||||
|
||||
if err := c.CompareHashedPassword(testPass); err != nil {
|
||||
t.Errorf("No match on same password: %v", err)
|
||||
}
|
||||
|
||||
failPass := "different"
|
||||
if err := c.CompareHashedPassword(failPass); err == nil {
|
||||
t.Errorf("Match on different password: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDuplicateDevices(t *testing.T) {
|
||||
// Duplicate devices should be removed
|
||||
|
||||
@@ -1245,7 +1300,7 @@ func copyToTmp(path string) (string, error) {
|
||||
return "", err
|
||||
}
|
||||
defer orig.Close()
|
||||
temp, err := ioutil.TempFile("", "syncthing-configTest-")
|
||||
temp, err := os.CreateTemp("", "syncthing-configTest-")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/rand"
|
||||
)
|
||||
|
||||
@@ -113,6 +115,23 @@ func (c GUIConfiguration) URL() string {
|
||||
return u.String()
|
||||
}
|
||||
|
||||
// SetHashedPassword hashes the given plaintext password and stores the new hash.
|
||||
func (c *GUIConfiguration) HashAndSetPassword(password string) error {
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(password), 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.Password = string(hash)
|
||||
return nil
|
||||
}
|
||||
|
||||
// CompareHashedPassword returns nil when the given plaintext password matches the stored hash.
|
||||
func (c GUIConfiguration) CompareHashedPassword(password string) error {
|
||||
configPasswordBytes := []byte(c.Password)
|
||||
passwordBytes := []byte(password)
|
||||
return bcrypt.CompareHashAndPassword(configPasswordBytes, passwordBytes)
|
||||
}
|
||||
|
||||
// IsValidAPIKey returns true when the given API key is valid, including both
|
||||
// the value in config and any overrides
|
||||
func (c GUIConfiguration) IsValidAPIKey(apiKey string) bool {
|
||||
|
||||
@@ -31,14 +31,14 @@ const (
|
||||
|
||||
var errTooManyModifications = errors.New("too many concurrent config modifications")
|
||||
|
||||
// The Committer interface is implemented by objects that need to know about
|
||||
// or have a say in configuration changes.
|
||||
// The Committer and Verifier interfaces are implemented by objects
|
||||
// that need to know about or have a say in configuration changes.
|
||||
//
|
||||
// When the configuration is about to be changed, VerifyConfiguration() is
|
||||
// called for each subscribing object, with the old and new configuration. A
|
||||
// nil error is returned if the new configuration is acceptable (i.e. does not
|
||||
// contain any errors that would prevent it from being a valid config).
|
||||
// Otherwise an error describing the problem is returned.
|
||||
// called for each subscribing object that implements it, with copies of the
|
||||
// old and new configuration. A nil error is returned if the new configuration
|
||||
// is acceptable (i.e., does not contain any errors that would prevent it from
|
||||
// being a valid config). Otherwise an error describing the problem is returned.
|
||||
//
|
||||
// If any subscriber returns an error from VerifyConfiguration(), the
|
||||
// configuration change is not committed and an error is returned to whoever
|
||||
@@ -55,11 +55,16 @@ var errTooManyModifications = errors.New("too many concurrent config modificatio
|
||||
// configuration (e.g. calling Wrapper.SetFolder), that are also acquired in any
|
||||
// methods of the Committer interface.
|
||||
type Committer interface {
|
||||
VerifyConfiguration(from, to Configuration) error
|
||||
CommitConfiguration(from, to Configuration) (handled bool)
|
||||
String() string
|
||||
}
|
||||
|
||||
// A Verifier can determine if a new configuration is acceptable.
|
||||
// See the description for Committer, above.
|
||||
type Verifier interface {
|
||||
VerifyConfiguration(from, to Configuration) error
|
||||
}
|
||||
|
||||
// Waiter allows to wait for the given config operation to complete.
|
||||
type Waiter interface {
|
||||
Wait()
|
||||
@@ -300,6 +305,10 @@ func (w *wrapper) replaceLocked(to Configuration) (Waiter, error) {
|
||||
}
|
||||
|
||||
for _, sub := range w.subs {
|
||||
sub, ok := sub.(Verifier)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
l.Debugln(sub, "verifying configuration")
|
||||
if err := sub.VerifyConfiguration(from.Copy(), to.Copy()); err != nil {
|
||||
l.Debugln(sub, "rejected config:", err)
|
||||
@@ -493,7 +502,7 @@ func (w *wrapper) Save() error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := w.cfg.WriteXML(fd); err != nil {
|
||||
if err := w.cfg.WriteXML(osutil.LineEndingsWriter(fd)); err != nil {
|
||||
l.Debugln("WriteXML:", err)
|
||||
fd.Close()
|
||||
return err
|
||||
|
||||
@@ -11,11 +11,9 @@ import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -470,21 +468,9 @@ func withConnectionPair(b *testing.B, connUri string, h func(client, server inte
|
||||
}
|
||||
|
||||
func mustGetCert(b *testing.B) tls.Certificate {
|
||||
f1, err := ioutil.TempFile("", "")
|
||||
cert, err := tlsutil.NewCertificateInMemory("bench", 10)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
f1.Close()
|
||||
f2, err := ioutil.TempFile("", "")
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
f2.Close()
|
||||
cert, err := tlsutil.NewCertificate(f1.Name(), f2.Name(), "bench", 10)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
_ = os.Remove(f1.Name())
|
||||
_ = os.Remove(f2.Name())
|
||||
return cert
|
||||
}
|
||||
|
||||
@@ -122,10 +122,6 @@ func (lim *limiter) processDevicesConfigurationLocked(from, to config.Configurat
|
||||
}
|
||||
}
|
||||
|
||||
func (lim *limiter) VerifyConfiguration(from, to config.Configuration) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (lim *limiter) CommitConfiguration(from, to config.Configuration) bool {
|
||||
// to ensure atomic update of configuration
|
||||
lim.mu.Lock()
|
||||
|
||||
@@ -706,10 +706,6 @@ func (s *service) logListenAddressesChangedEvent(l ListenerAddresses) {
|
||||
})
|
||||
}
|
||||
|
||||
func (s *service) VerifyConfiguration(from, to config.Configuration) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *service) CommitConfiguration(from, to config.Configuration) bool {
|
||||
newDevices := make(map[protocol.DeviceID]bool, len(to.Devices))
|
||||
for _, dev := range to.Devices {
|
||||
|
||||
@@ -76,7 +76,7 @@ func (t *tcpListener) serve(ctx context.Context) error {
|
||||
defer l.Infof("TCP listener (%v) shutting down", tcaddr)
|
||||
|
||||
mapping := t.natService.NewMapping(nat.TCP, tcaddr.IP, tcaddr.Port)
|
||||
mapping.OnChanged(func(_ *nat.Mapping, _, _ []nat.Address) {
|
||||
mapping.OnChanged(func() {
|
||||
t.notifyAddressesChanged(t)
|
||||
})
|
||||
// Should be called after t.mapping is nil'ed out.
|
||||
|
||||
@@ -223,35 +223,10 @@ func (db *Lowlevel) updateLocalFiles(folder []byte, fs []protocol.FileInfo, meta
|
||||
blocksHashSame := ok && bytes.Equal(ef.BlocksHash, f.BlocksHash)
|
||||
|
||||
if ok {
|
||||
if len(ef.Blocks) != 0 && !ef.IsInvalid() && ef.Size > 0 {
|
||||
for _, block := range ef.Blocks {
|
||||
keyBuf, err = db.keyer.GenerateBlockMapKey(keyBuf, folder, block.Hash, name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := t.Delete(keyBuf); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if !blocksHashSame {
|
||||
keyBuf, err := db.keyer.GenerateBlockListMapKey(keyBuf, folder, ef.BlocksHash, name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = t.Delete(keyBuf); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
keyBuf, err = db.keyer.GenerateSequenceKey(keyBuf, folder, ef.SequenceNo())
|
||||
keyBuf, err = db.removeLocalBlockAndSequenceInfo(keyBuf, folder, name, ef, !blocksHashSame, &t)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := t.Delete(keyBuf); err != nil {
|
||||
return err
|
||||
}
|
||||
l.Debugf("removing sequence; folder=%q sequence=%v %v", folder, ef.SequenceNo(), ef.FileName())
|
||||
}
|
||||
|
||||
f.Sequence = meta.nextLocalSeq()
|
||||
@@ -314,6 +289,96 @@ func (db *Lowlevel) updateLocalFiles(folder []byte, fs []protocol.FileInfo, meta
|
||||
return t.Commit()
|
||||
}
|
||||
|
||||
func (db *Lowlevel) removeLocalFiles(folder []byte, nameStrs []string, meta *metadataTracker) error {
|
||||
db.gcMut.RLock()
|
||||
defer db.gcMut.RUnlock()
|
||||
|
||||
t, err := db.newReadWriteTransaction(meta.CommitHook(folder))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer t.close()
|
||||
|
||||
var dk, gk, buf []byte
|
||||
for _, nameStr := range nameStrs {
|
||||
name := []byte(nameStr)
|
||||
dk, err = db.keyer.GenerateDeviceFileKey(dk, folder, protocol.LocalDeviceID[:], name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ef, ok, err := t.getFileByKey(dk)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !ok {
|
||||
l.Debugf("remove (local); folder=%q %v: file doesn't exist", folder, nameStr)
|
||||
continue
|
||||
}
|
||||
|
||||
buf, err = db.removeLocalBlockAndSequenceInfo(buf, folder, name, ef, true, &t)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
meta.removeFile(protocol.LocalDeviceID, ef)
|
||||
|
||||
gk, err = db.keyer.GenerateGlobalVersionKey(gk, folder, name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
buf, err = t.removeFromGlobal(gk, buf, folder, protocol.LocalDeviceID[:], name, meta)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = t.Delete(dk)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := t.Checkpoint(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return t.Commit()
|
||||
}
|
||||
|
||||
func (db *Lowlevel) removeLocalBlockAndSequenceInfo(keyBuf, folder, name []byte, ef protocol.FileInfo, removeFromBlockListMap bool, t *readWriteTransaction) ([]byte, error) {
|
||||
var err error
|
||||
if len(ef.Blocks) != 0 && !ef.IsInvalid() && ef.Size > 0 {
|
||||
for _, block := range ef.Blocks {
|
||||
keyBuf, err = db.keyer.GenerateBlockMapKey(keyBuf, folder, block.Hash, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := t.Delete(keyBuf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if removeFromBlockListMap {
|
||||
keyBuf, err := db.keyer.GenerateBlockListMapKey(keyBuf, folder, ef.BlocksHash, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = t.Delete(keyBuf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
keyBuf, err = db.keyer.GenerateSequenceKey(keyBuf, folder, ef.SequenceNo())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := t.Delete(keyBuf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
l.Debugf("removing sequence; folder=%q sequence=%v %v", folder, ef.SequenceNo(), ef.FileName())
|
||||
return keyBuf, nil
|
||||
}
|
||||
|
||||
func (db *Lowlevel) dropFolder(folder []byte) error {
|
||||
db.gcMut.RLock()
|
||||
defer db.gcMut.RUnlock()
|
||||
|
||||
@@ -144,6 +144,22 @@ func (s *FileSet) Update(device protocol.DeviceID, fs []protocol.FileInfo) {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *FileSet) RemoveLocalItems(items []string) {
|
||||
opStr := fmt.Sprintf("%s RemoveLocalItems([%d])", s.folder, len(items))
|
||||
l.Debugf(opStr)
|
||||
|
||||
s.updateMutex.Lock()
|
||||
defer s.updateMutex.Unlock()
|
||||
|
||||
for i := range items {
|
||||
items[i] = osutil.NormalizedFilename(items[i])
|
||||
}
|
||||
|
||||
if err := s.db.removeLocalFiles([]byte(s.folder), items, s.meta); err != nil && !backend.IsClosed(err) {
|
||||
fatalError(err, opStr, s.db)
|
||||
}
|
||||
}
|
||||
|
||||
type Snapshot struct {
|
||||
folder string
|
||||
t readOnlyTransaction
|
||||
|
||||
@@ -14,7 +14,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
@@ -173,7 +172,7 @@ func (c *globalClient) Lookup(ctx context.Context, device protocol.DeviceID) (ad
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bs, err := ioutil.ReadAll(resp.Body)
|
||||
bs, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ package discover
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
@@ -107,13 +107,8 @@ func TestGlobalOverHTTP(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGlobalOverHTTPS(t *testing.T) {
|
||||
dir, err := ioutil.TempDir("", "syncthing")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Generate a server certificate.
|
||||
cert, err := tlsutil.NewCertificate(dir+"/cert.pem", dir+"/key.pem", "syncthing", 30)
|
||||
cert, err := tlsutil.NewCertificateInMemory("syncthing", 30)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -172,13 +167,8 @@ func TestGlobalOverHTTPS(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGlobalAnnounce(t *testing.T) {
|
||||
dir, err := ioutil.TempDir("", "syncthing")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Generate a server certificate.
|
||||
cert, err := tlsutil.NewCertificate(dir+"/cert.pem", dir+"/key.pem", "syncthing", 30)
|
||||
cert, err := tlsutil.NewCertificateInMemory("syncthing", 30)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -242,7 +232,7 @@ func (s *fakeDiscoveryServer) handler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
if r.Method == "POST" {
|
||||
s.announce, _ = ioutil.ReadAll(r.Body)
|
||||
s.announce, _ = io.ReadAll(r.Body)
|
||||
w.WriteHeader(204)
|
||||
} else {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
@@ -227,10 +227,6 @@ func (m *manager) Cache() map[protocol.DeviceID]CacheEntry {
|
||||
return res
|
||||
}
|
||||
|
||||
func (m *manager) VerifyConfiguration(_, _ config.Configuration) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *manager) CommitConfiguration(_, to config.Configuration) (handled bool) {
|
||||
m.mut.Lock()
|
||||
defer m.mut.Unlock()
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
package fs
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
@@ -21,7 +20,7 @@ import (
|
||||
|
||||
func setup(t *testing.T) (*BasicFilesystem, string) {
|
||||
t.Helper()
|
||||
dir, err := ioutil.TempDir("", "")
|
||||
dir, err := os.MkdirTemp("", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
@@ -530,7 +529,7 @@ func renameTestFile(name string, old string, new string) {
|
||||
func modifyTestFile(name string, file string, content string) {
|
||||
joined := filepath.Join(testDirAbs, name, file)
|
||||
|
||||
err := ioutil.WriteFile(joined, []byte(content), 0755)
|
||||
err := os.WriteFile(joined, []byte(content), 0755)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Failed to modify test file %s: %s", joined, err))
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"net/url"
|
||||
"os"
|
||||
@@ -89,11 +88,9 @@ func newFakeFilesystem(rootURI string, _ ...Option) *fakeFS {
|
||||
fakeFSMut.Lock()
|
||||
defer fakeFSMut.Unlock()
|
||||
|
||||
root := rootURI
|
||||
var params url.Values
|
||||
uri, err := url.Parse(rootURI)
|
||||
if err == nil {
|
||||
root = uri.Path
|
||||
params = uri.Query()
|
||||
}
|
||||
|
||||
@@ -157,7 +154,7 @@ func newFakeFilesystem(rootURI string, _ ...Option) *fakeFS {
|
||||
// the filesystem initially.
|
||||
fs.latency, _ = time.ParseDuration(params.Get("latency"))
|
||||
|
||||
fakeFSCache[root] = fs
|
||||
fakeFSCache[rootURI] = fs
|
||||
return fs
|
||||
}
|
||||
|
||||
@@ -789,7 +786,7 @@ func (f *fakeFile) readShortAt(p []byte, offs int64) (int, error) {
|
||||
diff := offs - minOffs
|
||||
if diff > 0 {
|
||||
lr := io.LimitReader(f.rng, diff)
|
||||
io.Copy(ioutil.Discard, lr)
|
||||
io.Copy(io.Discard, lr)
|
||||
}
|
||||
|
||||
f.offset = offs
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
@@ -88,7 +87,7 @@ func TestFakeFS(t *testing.T) {
|
||||
}
|
||||
|
||||
// Read
|
||||
bs0, err := ioutil.ReadAll(fd)
|
||||
bs0, err := io.ReadAll(fd)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -101,7 +100,7 @@ func TestFakeFS(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
bs1, err := ioutil.ReadAll(fd)
|
||||
bs1, err := io.ReadAll(fd)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -139,7 +138,7 @@ func testFakeFSRead(t *testing.T, fs Filesystem) {
|
||||
|
||||
// Read
|
||||
fd.Seek(0, io.SeekStart)
|
||||
bs0, err := ioutil.ReadAll(fd)
|
||||
bs0, err := io.ReadAll(fd)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -154,7 +153,7 @@ func testFakeFSRead(t *testing.T, fs Filesystem) {
|
||||
if n != len(buf0) {
|
||||
t.Fatal("short read")
|
||||
}
|
||||
buf1, err := ioutil.ReadAll(fd)
|
||||
buf1, err := io.ReadAll(fd)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -252,7 +251,7 @@ func TestFakeFSCaseInsensitive(t *testing.T) {
|
||||
func createTestDir(t *testing.T) (string, bool) {
|
||||
t.Helper()
|
||||
|
||||
testDir, err := ioutil.TempDir("", "")
|
||||
testDir, err := os.MkdirTemp("", "")
|
||||
if err != nil {
|
||||
t.Fatalf("could not create temporary dir for testing: %s", err)
|
||||
}
|
||||
@@ -328,7 +327,7 @@ func testFakeFSCaseInsensitive(t *testing.T, fs Filesystem) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
bs2, err := ioutil.ReadAll(fd2)
|
||||
bs2, err := io.ReadAll(fd2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -253,7 +253,7 @@ func IsInternal(file string) bool {
|
||||
// - /foo/bar -> foo/bar
|
||||
// - / -> "."
|
||||
func Canonicalize(file string) (string, error) {
|
||||
pathSep := string(PathSeparator)
|
||||
const pathSep = string(PathSeparator)
|
||||
|
||||
if strings.HasPrefix(file, pathSep+pathSep) {
|
||||
// The relative path may pretend to be an absolute path within
|
||||
|
||||
@@ -9,7 +9,6 @@ package fs
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -257,7 +256,7 @@ func TestCopyRange(tttt *testing.T) {
|
||||
paths = []string{""}
|
||||
}
|
||||
for _, path := range paths {
|
||||
testPath, err := ioutil.TempDir(path, "")
|
||||
testPath, err := os.MkdirTemp(path, "")
|
||||
if err != nil {
|
||||
tttt.Fatal(err)
|
||||
}
|
||||
@@ -273,7 +272,7 @@ func TestCopyRange(tttt *testing.T) {
|
||||
tt.Run(testCase.name, func(t *testing.T) {
|
||||
srcBuf := make([]byte, testCase.srcSize)
|
||||
dstBuf := make([]byte, testCase.dstSize)
|
||||
td, err := ioutil.TempDir(testPath, "")
|
||||
td, err := os.MkdirTemp(testPath, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ package fs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
@@ -20,9 +19,9 @@ func TestMtimeFS(t *testing.T) {
|
||||
os.RemoveAll("testdata")
|
||||
defer os.RemoveAll("testdata")
|
||||
os.Mkdir("testdata", 0755)
|
||||
ioutil.WriteFile("testdata/exists0", []byte("hello"), 0644)
|
||||
ioutil.WriteFile("testdata/exists1", []byte("hello"), 0644)
|
||||
ioutil.WriteFile("testdata/exists2", []byte("hello"), 0644)
|
||||
os.WriteFile("testdata/exists0", []byte("hello"), 0644)
|
||||
os.WriteFile("testdata/exists1", []byte("hello"), 0644)
|
||||
os.WriteFile("testdata/exists2", []byte("hello"), 0644)
|
||||
|
||||
// a random time with nanosecond precision
|
||||
testTime := time.Unix(1234567890, 123456789)
|
||||
@@ -83,7 +82,7 @@ func TestMtimeFS(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMtimeFSWalk(t *testing.T) {
|
||||
dir, err := ioutil.TempDir("", "")
|
||||
dir, err := os.MkdirTemp("", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -93,7 +92,7 @@ func TestMtimeFSWalk(t *testing.T) {
|
||||
mtimefs := newMtimeFS(underlying, make(mapStore))
|
||||
mtimefs.chtimes = failChtimes
|
||||
|
||||
if err := ioutil.WriteFile(filepath.Join(dir, "file"), []byte("hello"), 0644); err != nil {
|
||||
if err := os.WriteFile(filepath.Join(dir, "file"), []byte("hello"), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -137,7 +136,7 @@ func TestMtimeFSWalk(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMtimeFSOpen(t *testing.T) {
|
||||
dir, err := ioutil.TempDir("", "")
|
||||
dir, err := os.MkdirTemp("", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -147,7 +146,7 @@ func TestMtimeFSOpen(t *testing.T) {
|
||||
mtimefs := newMtimeFS(underlying, make(mapStore))
|
||||
mtimefs.chtimes = failChtimes
|
||||
|
||||
if err := ioutil.WriteFile(filepath.Join(dir, "file"), []byte("hello"), 0644); err != nil {
|
||||
if err := os.WriteFile(filepath.Join(dir, "file"), []byte("hello"), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -200,7 +199,7 @@ func TestMtimeFSInsensitive(t *testing.T) {
|
||||
os.RemoveAll("testdata")
|
||||
defer os.RemoveAll("testdata")
|
||||
os.Mkdir("testdata", 0755)
|
||||
ioutil.WriteFile("testdata/FiLe", []byte("hello"), 0644)
|
||||
os.WriteFile("testdata/FiLe", []byte("hello"), 0644)
|
||||
|
||||
// a random time with nanosecond precision
|
||||
testTime := time.Unix(1234567890, 123456789)
|
||||
|
||||
@@ -47,25 +47,13 @@ func getHomeDir() (string, error) {
|
||||
return os.UserHomeDir()
|
||||
}
|
||||
|
||||
var (
|
||||
windowsDisallowedCharacters = string([]rune{
|
||||
'<', '>', ':', '"', '|', '?', '*',
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
|
||||
11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
|
||||
21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
|
||||
31,
|
||||
})
|
||||
windowsDisallowedNames = []string{"CON", "PRN", "AUX", "NUL",
|
||||
"COM0", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
|
||||
"LPT0", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9",
|
||||
}
|
||||
)
|
||||
const windowsDisallowedCharacters = (`<>:"|?*` +
|
||||
"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f" +
|
||||
"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f")
|
||||
|
||||
func WindowsInvalidFilename(name string) error {
|
||||
// None of the path components should end in space or period, or be a
|
||||
// reserved name. COM0 and LPT0 are missing from the Microsoft docs,
|
||||
// but Windows Explorer treats them as invalid too.
|
||||
// (https://docs.microsoft.com/windows/win32/fileio/naming-a-file)
|
||||
// reserved name.
|
||||
for _, part := range strings.Split(name, `\`) {
|
||||
if len(part) == 0 {
|
||||
continue
|
||||
@@ -110,7 +98,7 @@ func WindowsInvalidFilename(name string) error {
|
||||
func SanitizePath(path string) string {
|
||||
var b strings.Builder
|
||||
|
||||
disallowed := `<>:"'/\|?*[]{};:!@$%&^#` + windowsDisallowedCharacters
|
||||
const disallowed = `'/\[]{};:!@$%&^#` + windowsDisallowedCharacters
|
||||
prev := ' '
|
||||
for _, c := range path {
|
||||
if !unicode.IsPrint(c) || c == unicode.ReplacementChar ||
|
||||
@@ -132,15 +120,27 @@ func SanitizePath(path string) string {
|
||||
}
|
||||
|
||||
func windowsIsReserved(part string) bool {
|
||||
upperCased := strings.ToUpper(part)
|
||||
for _, disallowed := range windowsDisallowedNames {
|
||||
if upperCased == disallowed {
|
||||
return true
|
||||
}
|
||||
if strings.HasPrefix(upperCased, disallowed+".") {
|
||||
// nul.txt.jpg is also disallowed
|
||||
return true
|
||||
}
|
||||
// nul.txt.jpg is also disallowed.
|
||||
dot := strings.IndexByte(part, '.')
|
||||
if dot != -1 {
|
||||
part = part[:dot]
|
||||
}
|
||||
|
||||
// Check length to skip allocating ToUpper.
|
||||
if len(part) != 3 && len(part) != 4 {
|
||||
return false
|
||||
}
|
||||
|
||||
// COM0 and LPT0 are missing from the Microsoft docs,
|
||||
// but Windows Explorer treats them as invalid too.
|
||||
// (https://docs.microsoft.com/windows/win32/fileio/naming-a-file)
|
||||
switch strings.ToUpper(part) {
|
||||
case "CON", "PRN", "AUX", "NUL",
|
||||
"COM0", "COM1", "COM2", "COM3", "COM4",
|
||||
"COM5", "COM6", "COM7", "COM8", "COM9",
|
||||
"LPT0", "LPT1", "LPT2", "LPT3", "LPT4",
|
||||
"LPT5", "LPT6", "LPT7", "LPT8", "LPT9":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -117,3 +117,15 @@ func TestSanitizePathFuzz(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkWindowsInvalidFilename(b *testing.B, name string) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
WindowsInvalidFilename(name)
|
||||
}
|
||||
}
|
||||
func BenchmarkWindowsInvalidFilenameValid(b *testing.B) {
|
||||
benchmarkWindowsInvalidFilename(b, "License.txt.gz")
|
||||
}
|
||||
func BenchmarkWindowsInvalidFilenameNUL(b *testing.B) {
|
||||
benchmarkWindowsInvalidFilename(b, "nul.txt.gz")
|
||||
}
|
||||
|
||||
@@ -595,8 +595,9 @@ func WriteIgnores(filesystem fs.Filesystem, path string, content []string) error
|
||||
return err
|
||||
}
|
||||
|
||||
wr := osutil.LineEndingsWriter(fd)
|
||||
for _, line := range content {
|
||||
fmt.Fprintln(fd, line)
|
||||
fmt.Fprintln(wr, line)
|
||||
}
|
||||
|
||||
if err := fd.Close(); err != nil {
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
@@ -232,7 +231,7 @@ func TestCaseSensitivity(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCaching(t *testing.T) {
|
||||
dir, err := ioutil.TempDir("", "")
|
||||
dir, err := os.MkdirTemp("", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -425,7 +424,7 @@ flamingo
|
||||
*.crow
|
||||
`
|
||||
// Caches per file, hence write the patterns to a file.
|
||||
dir, err := ioutil.TempDir("", "")
|
||||
dir, err := os.MkdirTemp("", "")
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
@@ -466,7 +465,7 @@ flamingo
|
||||
}
|
||||
|
||||
func TestCacheReload(t *testing.T) {
|
||||
dir, err := ioutil.TempDir("", "")
|
||||
dir, err := os.MkdirTemp("", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -989,7 +988,7 @@ func TestIssue4689(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestIssue4901(t *testing.T) {
|
||||
dir, err := ioutil.TempDir("", "")
|
||||
dir, err := os.MkdirTemp("", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -1001,7 +1000,7 @@ func TestIssue4901(t *testing.T) {
|
||||
puppy
|
||||
`
|
||||
|
||||
if err := ioutil.WriteFile(filepath.Join(dir, ".stignore"), []byte(stignore), 0777); err != nil {
|
||||
if err := os.WriteFile(filepath.Join(dir, ".stignore"), []byte(stignore), 0777); err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
|
||||
@@ -1020,7 +1019,7 @@ func TestIssue4901(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(filepath.Join(dir, "unicorn-lazor-death"), []byte(" "), 0777); err != nil {
|
||||
if err := os.WriteFile(filepath.Join(dir, "unicorn-lazor-death"), []byte(" "), 0777); err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
|
||||
@@ -1193,3 +1192,42 @@ func TestEmptyPatterns(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestWindowsLineEndings(t *testing.T) {
|
||||
if runtime.GOOS != "windows" {
|
||||
t.Skip("Windows specific")
|
||||
}
|
||||
|
||||
lines := "foo\nbar\nbaz\n"
|
||||
|
||||
dir, err := os.MkdirTemp("", "syncthing-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
ffs := fs.NewFilesystem(fs.FilesystemTypeBasic, dir)
|
||||
m := New(ffs)
|
||||
if err := m.Parse(strings.NewReader(lines), ".stignore"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := WriteIgnores(ffs, ".stignore", m.Lines()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
fd, err := ffs.Open(".stignore")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
bs, err := io.ReadAll(fd)
|
||||
fd.Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
unixLineEndings := bytes.Count(bs, []byte("\n"))
|
||||
windowsLineEndings := bytes.Count(bs, []byte("\r\n"))
|
||||
if unixLineEndings == 0 || windowsLineEndings != unixLineEndings {
|
||||
t.Error("expected there to be a non-zero number of Windows line endings")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,10 +40,10 @@ const (
|
||||
type BaseDirEnum string
|
||||
|
||||
const (
|
||||
// Overridden by -home flag
|
||||
// Overridden by --home flag
|
||||
ConfigBaseDir BaseDirEnum = "config"
|
||||
DataBaseDir BaseDirEnum = "data"
|
||||
// User's home directory, *not* -home flag
|
||||
// User's home directory, *not* --home flag
|
||||
UserHomeBaseDir BaseDirEnum = "userHome"
|
||||
|
||||
LevelDBDir = "index-v0.14.0.db"
|
||||
@@ -98,7 +98,7 @@ var locationTemplates = map[LocationEnum]string{
|
||||
HTTPSCertFile: "${config}/https-cert.pem",
|
||||
HTTPSKeyFile: "${config}/https-key.pem",
|
||||
Database: "${data}/" + LevelDBDir,
|
||||
LogFile: "${data}/syncthing.log", // -logfile on Windows
|
||||
LogFile: "${data}/syncthing.log", // --logfile on Windows
|
||||
CsrfTokens: "${data}/csrftokens.txt",
|
||||
PanicLog: "${data}/panic-${timestamp}.log",
|
||||
AuditLog: "${data}/audit-${timestamp}.log",
|
||||
|
||||
@@ -10,7 +10,6 @@ package logger
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
@@ -75,7 +74,7 @@ func New() Logger {
|
||||
if os.Getenv("LOGGER_DISCARD") != "" {
|
||||
// Hack to completely disable logging, for example when running
|
||||
// benchmarks.
|
||||
return newLogger(ioutil.Discard)
|
||||
return newLogger(io.Discard)
|
||||
}
|
||||
return newLogger(controlStripper{os.Stdout})
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ package logger
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"log"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -186,12 +186,12 @@ func TestControlStripper(t *testing.T) {
|
||||
}
|
||||
|
||||
func BenchmarkLog(b *testing.B) {
|
||||
l := newLogger(controlStripper{ioutil.Discard})
|
||||
l := newLogger(controlStripper{io.Discard})
|
||||
benchmarkLogger(b, l)
|
||||
}
|
||||
|
||||
func BenchmarkLogNoStripper(b *testing.B) {
|
||||
l := newLogger(ioutil.Discard)
|
||||
l := newLogger(io.Discard)
|
||||
benchmarkLogger(b, l)
|
||||
}
|
||||
|
||||
|
||||
@@ -26,20 +26,19 @@ func newDeviceActivity() *deviceActivity {
|
||||
}
|
||||
}
|
||||
|
||||
func (m *deviceActivity) leastBusy(availability []Availability) (Availability, bool) {
|
||||
// Returns the index of the least busy device, or -1 if all are too busy.
|
||||
func (m *deviceActivity) leastBusy(availability []Availability) int {
|
||||
m.mut.Lock()
|
||||
low := 2<<30 - 1
|
||||
found := false
|
||||
var selected Availability
|
||||
for _, info := range availability {
|
||||
if usage := m.act[info.ID]; usage < low {
|
||||
best := -1
|
||||
for i := range availability {
|
||||
if usage := m.act[availability[i].ID]; usage < low {
|
||||
low = usage
|
||||
selected = info
|
||||
found = true
|
||||
best = i
|
||||
}
|
||||
}
|
||||
m.mut.Unlock()
|
||||
return selected, found
|
||||
return best
|
||||
}
|
||||
|
||||
func (m *deviceActivity) using(availability Availability) {
|
||||
|
||||
@@ -19,42 +19,42 @@ func TestDeviceActivity(t *testing.T) {
|
||||
devices := []Availability{n0, n1, n2}
|
||||
na := newDeviceActivity()
|
||||
|
||||
if lb, ok := na.leastBusy(devices); !ok || lb != n0 {
|
||||
if lb := na.leastBusy(devices); lb != 0 {
|
||||
t.Errorf("Least busy device should be n0 (%v) not %v", n0, lb)
|
||||
}
|
||||
if lb, ok := na.leastBusy(devices); !ok || lb != n0 {
|
||||
if lb := na.leastBusy(devices); lb != 0 {
|
||||
t.Errorf("Least busy device should still be n0 (%v) not %v", n0, lb)
|
||||
}
|
||||
|
||||
lb, _ := na.leastBusy(devices)
|
||||
na.using(lb)
|
||||
if lb, ok := na.leastBusy(devices); !ok || lb != n1 {
|
||||
lb := na.leastBusy(devices)
|
||||
na.using(devices[lb])
|
||||
if lb := na.leastBusy(devices); lb != 1 {
|
||||
t.Errorf("Least busy device should be n1 (%v) not %v", n1, lb)
|
||||
}
|
||||
lb, _ = na.leastBusy(devices)
|
||||
na.using(lb)
|
||||
if lb, ok := na.leastBusy(devices); !ok || lb != n2 {
|
||||
lb = na.leastBusy(devices)
|
||||
na.using(devices[lb])
|
||||
if lb := na.leastBusy(devices); lb != 2 {
|
||||
t.Errorf("Least busy device should be n2 (%v) not %v", n2, lb)
|
||||
}
|
||||
|
||||
lb, _ = na.leastBusy(devices)
|
||||
na.using(lb)
|
||||
if lb, ok := na.leastBusy(devices); !ok || lb != n0 {
|
||||
lb = na.leastBusy(devices)
|
||||
na.using(devices[lb])
|
||||
if lb := na.leastBusy(devices); lb != 0 {
|
||||
t.Errorf("Least busy device should be n0 (%v) not %v", n0, lb)
|
||||
}
|
||||
|
||||
na.done(n1)
|
||||
if lb, ok := na.leastBusy(devices); !ok || lb != n1 {
|
||||
if lb := na.leastBusy(devices); lb != 1 {
|
||||
t.Errorf("Least busy device should be n1 (%v) not %v", n1, lb)
|
||||
}
|
||||
|
||||
na.done(n2)
|
||||
if lb, ok := na.leastBusy(devices); !ok || lb != n1 {
|
||||
if lb := na.leastBusy(devices); lb != 1 {
|
||||
t.Errorf("Least busy device should still be n1 (%v) not %v", n1, lb)
|
||||
}
|
||||
|
||||
na.done(n0)
|
||||
if lb, ok := na.leastBusy(devices); !ok || lb != n0 {
|
||||
if lb := na.leastBusy(devices); lb != 0 {
|
||||
t.Errorf("Least busy device should be n0 (%v) not %v", n0, lb)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,6 +162,7 @@ func (f *fakeConnection) sendIndexUpdate() {
|
||||
|
||||
func addFakeConn(m *testModel, dev protocol.DeviceID, folderID string) *fakeConnection {
|
||||
fc := newFakeConnection(dev, m)
|
||||
fc.folder = folderID
|
||||
m.AddConnection(fc, protocol.Hello{})
|
||||
|
||||
m.ClusterConfig(dev, protocol.ClusterConfig{
|
||||
|
||||
@@ -532,16 +532,20 @@ func (f *folder) scanSubdirs(subDirs []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
const maxToRemove = 1000
|
||||
|
||||
type scanBatch struct {
|
||||
*db.FileInfoBatch
|
||||
f *folder
|
||||
f *folder
|
||||
updateBatch *db.FileInfoBatch
|
||||
toRemove []string
|
||||
}
|
||||
|
||||
func (f *folder) newScanBatch() *scanBatch {
|
||||
b := &scanBatch{
|
||||
f: f,
|
||||
f: f,
|
||||
toRemove: make([]string, 0, maxToRemove),
|
||||
}
|
||||
b.FileInfoBatch = db.NewFileInfoBatch(func(fs []protocol.FileInfo) error {
|
||||
b.updateBatch = db.NewFileInfoBatch(func(fs []protocol.FileInfo) error {
|
||||
if err := b.f.getHealthErrorWithoutIgnores(); err != nil {
|
||||
l.Debugf("Stopping scan of folder %s due to: %s", b.f.Description(), err)
|
||||
return err
|
||||
@@ -552,9 +556,32 @@ func (f *folder) newScanBatch() *scanBatch {
|
||||
return b
|
||||
}
|
||||
|
||||
// Append adds the fileinfo to the batch for updating, and does a few checks.
|
||||
func (b *scanBatch) Remove(item string) {
|
||||
b.toRemove = append(b.toRemove, item)
|
||||
}
|
||||
|
||||
func (b *scanBatch) flushToRemove() {
|
||||
if len(b.toRemove) > 0 {
|
||||
b.f.fset.RemoveLocalItems(b.toRemove)
|
||||
b.toRemove = b.toRemove[:0]
|
||||
}
|
||||
}
|
||||
|
||||
func (b *scanBatch) Flush() error {
|
||||
b.flushToRemove()
|
||||
return b.updateBatch.Flush()
|
||||
}
|
||||
|
||||
func (b *scanBatch) FlushIfFull() error {
|
||||
if len(b.toRemove) >= maxToRemove {
|
||||
b.flushToRemove()
|
||||
}
|
||||
return b.updateBatch.FlushIfFull()
|
||||
}
|
||||
|
||||
// Update adds the fileinfo to the batch for updating, and does a few checks.
|
||||
// It returns false if the checks result in the file not going to be updated or removed.
|
||||
func (b *scanBatch) Append(fi protocol.FileInfo, snap *db.Snapshot) bool {
|
||||
func (b *scanBatch) Update(fi protocol.FileInfo, snap *db.Snapshot) bool {
|
||||
// Check for a "virtual" parent directory of encrypted files. We don't track
|
||||
// it, but check if anything still exists within and delete it otherwise.
|
||||
if b.f.Type == config.FolderTypeReceiveEncrypted && fi.IsDirectory() && protocol.IsEncryptedParent(fs.PathComponents(fi.Name)) {
|
||||
@@ -565,20 +592,21 @@ func (b *scanBatch) Append(fi protocol.FileInfo, snap *db.Snapshot) bool {
|
||||
}
|
||||
// Resolve receive-only items which are identical with the global state or
|
||||
// the global item is our own receive-only item.
|
||||
// Except if they are in a receive-encrypted folder and are locally added.
|
||||
// Those must never be sent in index updates and thus must retain the flag.
|
||||
switch gf, ok := snap.GetGlobal(fi.Name); {
|
||||
case !ok:
|
||||
case gf.IsReceiveOnlyChanged():
|
||||
if b.f.Type == config.FolderTypeReceiveOnly && fi.Deleted {
|
||||
l.Debugf("%v scanning: Marking deleted item as not locally changed", b.f, fi)
|
||||
fi.LocalFlags &^= protocol.FlagLocalReceiveOnly
|
||||
if fi.IsDeleted() {
|
||||
// Our item is deleted and the global item is our own receive only
|
||||
// file. No point in keeping track of that.
|
||||
b.Remove(fi.Name)
|
||||
return true
|
||||
}
|
||||
case gf.IsEquivalentOptional(fi, b.f.modTimeWindow, false, false, protocol.FlagLocalReceiveOnly):
|
||||
l.Debugf("%v scanning: Replacing scanned file info with global as it's equivalent", b.f, fi)
|
||||
// What we have locally is equivalent to the global file.
|
||||
l.Debugf("%v scanning: Merging identical locally changed item with global", b.f, fi)
|
||||
fi = gf
|
||||
}
|
||||
b.FileInfoBatch.Append(fi)
|
||||
b.updateBatch.Append(fi)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -634,7 +662,7 @@ func (f *folder) scanSubdirsChangedAndNew(subDirs []string, batch *scanBatch) (i
|
||||
return changes, err
|
||||
}
|
||||
|
||||
if batch.Append(res.File, snap) {
|
||||
if batch.Update(res.File, snap) {
|
||||
changes++
|
||||
}
|
||||
|
||||
@@ -642,7 +670,7 @@ func (f *folder) scanSubdirsChangedAndNew(subDirs []string, batch *scanBatch) (i
|
||||
case config.FolderTypeReceiveOnly, config.FolderTypeReceiveEncrypted:
|
||||
default:
|
||||
if nf, ok := f.findRename(snap, res.File, alreadyUsedOrExisting); ok {
|
||||
if batch.Append(nf, snap) {
|
||||
if batch.Update(nf, snap) {
|
||||
changes++
|
||||
}
|
||||
}
|
||||
@@ -683,7 +711,7 @@ func (f *folder) scanSubdirsDeletedAndIgnored(subDirs []string, batch *scanBatch
|
||||
for _, file := range toIgnore {
|
||||
l.Debugln("marking file as ignored", file)
|
||||
nf := file.ConvertToIgnoredFileInfo()
|
||||
if batch.Append(nf, snap) {
|
||||
if batch.Update(nf, snap) {
|
||||
changes++
|
||||
}
|
||||
if err := batch.FlushIfFull(); err != nil {
|
||||
@@ -713,7 +741,7 @@ func (f *folder) scanSubdirsDeletedAndIgnored(subDirs []string, batch *scanBatch
|
||||
|
||||
l.Debugln("marking file as ignored", file)
|
||||
nf := file.ConvertToIgnoredFileInfo()
|
||||
if batch.Append(nf, snap) {
|
||||
if batch.Update(nf, snap) {
|
||||
changes++
|
||||
}
|
||||
|
||||
@@ -743,24 +771,24 @@ func (f *folder) scanSubdirsDeletedAndIgnored(subDirs []string, batch *scanBatch
|
||||
nf.Version = protocol.Vector{}
|
||||
}
|
||||
l.Debugln("marking file as deleted", nf)
|
||||
if batch.Append(nf, snap) {
|
||||
if batch.Update(nf, snap) {
|
||||
changes++
|
||||
}
|
||||
case file.IsDeleted() && file.IsReceiveOnlyChanged():
|
||||
switch f.Type {
|
||||
case config.FolderTypeReceiveOnly, config.FolderTypeReceiveEncrypted:
|
||||
if gf, _ := snap.GetGlobal(file.Name); gf.IsDeleted() {
|
||||
switch gf, ok := snap.GetGlobal(file.Name); {
|
||||
case !ok:
|
||||
case gf.IsReceiveOnlyChanged():
|
||||
l.Debugln("removing deleted, receive-only item that is globally receive-only from db", file)
|
||||
batch.Remove(file.Name)
|
||||
changes++
|
||||
case gf.IsDeleted():
|
||||
// Our item is deleted and the global item is deleted too. We just
|
||||
// pretend it is a normal deleted file (nobody cares about that).
|
||||
// Except if this is a receive-encrypted folder and it
|
||||
// is a locally added file. Those must never be sent
|
||||
// in index updates and thus must retain the flag.
|
||||
if f.Type == config.FolderTypeReceiveEncrypted && gf.IsReceiveOnlyChanged() {
|
||||
return true
|
||||
}
|
||||
l.Debugf("%v scanning: Marking globally deleted item as not locally changed: %v", f, file.Name)
|
||||
file.LocalFlags &^= protocol.FlagLocalReceiveOnly
|
||||
if batch.Append(file.ConvertDeletedToFileInfo(), snap) {
|
||||
if batch.Update(file.ConvertDeletedToFileInfo(), snap) {
|
||||
changes++
|
||||
}
|
||||
}
|
||||
@@ -769,7 +797,7 @@ func (f *folder) scanSubdirsDeletedAndIgnored(subDirs []string, batch *scanBatch
|
||||
// deleted and just the folder type/local flags changed.
|
||||
file.LocalFlags &^= protocol.FlagLocalReceiveOnly
|
||||
l.Debugln("removing receive-only flag on deleted item", file)
|
||||
if batch.Append(file.ConvertDeletedToFileInfo(), snap) {
|
||||
if batch.Update(file.ConvertDeletedToFileInfo(), snap) {
|
||||
changes++
|
||||
}
|
||||
}
|
||||
@@ -788,7 +816,7 @@ func (f *folder) scanSubdirsDeletedAndIgnored(subDirs []string, batch *scanBatch
|
||||
for _, file := range toIgnore {
|
||||
l.Debugln("marking file as ignored", f)
|
||||
nf := file.ConvertToIgnoredFileInfo()
|
||||
if batch.Append(nf, snap) {
|
||||
if batch.Update(nf, snap) {
|
||||
changes++
|
||||
}
|
||||
if iterError = batch.FlushIfFull(); iterError != nil {
|
||||
|
||||
@@ -68,7 +68,7 @@ func (f *receiveOnlyFolder) Revert() {
|
||||
}
|
||||
|
||||
func (f *receiveOnlyFolder) revert() error {
|
||||
l.Infof("Reverting folder %v", f.Description)
|
||||
l.Infof("Reverting folder %v", f.Description())
|
||||
|
||||
f.setState(FolderScanning)
|
||||
defer f.setState(FolderIdle)
|
||||
|
||||
@@ -37,9 +37,9 @@ func TestRecvOnlyRevertDeletes(t *testing.T) {
|
||||
for _, dir := range []string{".stfolder", "ignDir", "unknownDir"} {
|
||||
must(t, ffs.MkdirAll(dir, 0755))
|
||||
}
|
||||
must(t, writeFile(ffs, "ignDir/ignFile", []byte("hello\n"), 0644))
|
||||
must(t, writeFile(ffs, "unknownDir/unknownFile", []byte("hello\n"), 0644))
|
||||
must(t, writeFile(ffs, ".stignore", []byte("ignDir\n"), 0644))
|
||||
writeFilePerm(t, ffs, "ignDir/ignFile", []byte("hello\n"), 0644)
|
||||
writeFilePerm(t, ffs, "unknownDir/unknownFile", []byte("hello\n"), 0644)
|
||||
writeFilePerm(t, ffs, ".stignore", []byte("ignDir\n"), 0644)
|
||||
|
||||
knownFiles := setupKnownFiles(t, ffs, []byte("hello\n"))
|
||||
|
||||
@@ -151,7 +151,7 @@ func TestRecvOnlyRevertNeeds(t *testing.T) {
|
||||
// Update the file.
|
||||
|
||||
newData := []byte("totally different data\n")
|
||||
must(t, writeFile(ffs, "knownDir/knownFile", newData, 0644))
|
||||
writeFilePerm(t, ffs, "knownDir/knownFile", newData, 0644)
|
||||
|
||||
// Rescan.
|
||||
|
||||
@@ -241,8 +241,8 @@ func TestRecvOnlyUndoChanges(t *testing.T) {
|
||||
// Create a file and modify another
|
||||
|
||||
const file = "foo"
|
||||
must(t, writeFile(ffs, file, []byte("hello\n"), 0644))
|
||||
must(t, writeFile(ffs, "knownDir/knownFile", []byte("bye\n"), 0644))
|
||||
writeFilePerm(t, ffs, file, []byte("hello\n"), 0644)
|
||||
writeFilePerm(t, ffs, "knownDir/knownFile", []byte("bye\n"), 0644)
|
||||
|
||||
must(t, m.ScanFolder("ro"))
|
||||
|
||||
@@ -254,7 +254,7 @@ func TestRecvOnlyUndoChanges(t *testing.T) {
|
||||
// Remove the file again and undo the modification
|
||||
|
||||
must(t, ffs.Remove(file))
|
||||
must(t, writeFile(ffs, "knownDir/knownFile", oldData, 0644))
|
||||
writeFilePerm(t, ffs, "knownDir/knownFile", oldData, 0644)
|
||||
must(t, ffs.Chtimes("knownDir/knownFile", knownFiles[1].ModTime(), knownFiles[1].ModTime()))
|
||||
|
||||
must(t, m.ScanFolder("ro"))
|
||||
@@ -377,8 +377,8 @@ func TestRecvOnlyRemoteUndoChanges(t *testing.T) {
|
||||
|
||||
const file = "foo"
|
||||
knownFile := filepath.Join("knownDir", "knownFile")
|
||||
must(t, writeFile(ffs, file, []byte("hello\n"), 0644))
|
||||
must(t, writeFile(ffs, knownFile, []byte("bye\n"), 0644))
|
||||
writeFilePerm(t, ffs, file, []byte("hello\n"), 0644)
|
||||
writeFilePerm(t, ffs, knownFile, []byte("bye\n"), 0644)
|
||||
|
||||
must(t, m.ScanFolder("ro"))
|
||||
|
||||
@@ -434,7 +434,7 @@ func TestRecvOnlyRevertOwnID(t *testing.T) {
|
||||
must(t, ffs.MkdirAll(".stfolder", 0755))
|
||||
data := []byte("hello\n")
|
||||
name := "foo"
|
||||
must(t, writeFile(ffs, name, data, 0644))
|
||||
writeFilePerm(t, ffs, name, data, 0644)
|
||||
|
||||
// Make sure the file is scanned and locally changed
|
||||
must(t, m.ScanFolder("ro"))
|
||||
@@ -484,7 +484,7 @@ func setupKnownFiles(t *testing.T, ffs fs.Filesystem, data []byte) []protocol.Fi
|
||||
t.Helper()
|
||||
|
||||
must(t, ffs.MkdirAll("knownDir", 0755))
|
||||
must(t, writeFile(ffs, "knownDir/knownFile", data, 0644))
|
||||
writeFilePerm(t, ffs, "knownDir/knownFile", data, 0644)
|
||||
|
||||
t0 := time.Now().Add(-1 * time.Minute)
|
||||
must(t, ffs.Chtimes("knownDir/knownFile", t0, t0))
|
||||
@@ -541,18 +541,3 @@ func setupROFolder(t *testing.T) (*testModel, *receiveOnlyFolder, context.Cancel
|
||||
|
||||
return m, f, cancel
|
||||
}
|
||||
|
||||
func writeFile(fs fs.Filesystem, filename string, data []byte, perm fs.FileMode) error {
|
||||
fd, err := fs.Create(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = fd.Write(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := fd.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
return fs.Chmod(filename, perm)
|
||||
}
|
||||
|
||||
@@ -1532,8 +1532,8 @@ loop:
|
||||
// Select the least busy device to pull the block from. If we found no
|
||||
// feasible device at all, fail the block (and in the long run, the
|
||||
// file).
|
||||
selected, found := activity.leastBusy(candidates)
|
||||
if !found {
|
||||
found := activity.leastBusy(candidates)
|
||||
if found == -1 {
|
||||
if lastError != nil {
|
||||
state.fail(errors.Wrap(lastError, "pull"))
|
||||
} else {
|
||||
@@ -1542,7 +1542,9 @@ loop:
|
||||
break
|
||||
}
|
||||
|
||||
candidates = removeAvailability(candidates, selected)
|
||||
selected := candidates[found]
|
||||
candidates[found] = candidates[len(candidates)-1]
|
||||
candidates = candidates[:len(candidates)-1]
|
||||
|
||||
// Fetch the block, while marking the selected device as in use so that
|
||||
// leastBusy can select another device when someone else asks.
|
||||
@@ -1804,16 +1806,6 @@ func (f *sendReceiveFolder) inConflict(current, replacement protocol.Vector) boo
|
||||
return false
|
||||
}
|
||||
|
||||
func removeAvailability(availabilities []Availability, availability Availability) []Availability {
|
||||
for i := range availabilities {
|
||||
if availabilities[i] == availability {
|
||||
availabilities[i] = availabilities[len(availabilities)-1]
|
||||
return availabilities[:len(availabilities)-1]
|
||||
}
|
||||
}
|
||||
return availabilities
|
||||
}
|
||||
|
||||
func (f *sendReceiveFolder) moveForConflict(name, lastModBy string, scanChan chan<- string) error {
|
||||
if isConflict(name) {
|
||||
l.Infoln("Conflict for", name, "which is already a conflict copy; not copying again.")
|
||||
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
@@ -77,12 +76,10 @@ func setupFile(filename string, blockNumbers []int) protocol.FileInfo {
|
||||
}
|
||||
}
|
||||
|
||||
func createFile(t *testing.T, name string, fs fs.Filesystem) protocol.FileInfo {
|
||||
func createEmptyFileInfo(t *testing.T, name string, fs fs.Filesystem) protocol.FileInfo {
|
||||
t.Helper()
|
||||
|
||||
f, err := fs.Create(name)
|
||||
must(t, err)
|
||||
f.Close()
|
||||
writeFile(t, fs, name, nil)
|
||||
fi, err := fs.Stat(name)
|
||||
must(t, err)
|
||||
file, err := scanner.CreateFileInfo(fi, name, fs)
|
||||
@@ -682,8 +679,8 @@ func TestIssue3164(t *testing.T) {
|
||||
ignDir := filepath.Join("issue3164", "oktodelete")
|
||||
subDir := filepath.Join(ignDir, "foobar")
|
||||
must(t, ffs.MkdirAll(subDir, 0777))
|
||||
must(t, ioutil.WriteFile(filepath.Join(tmpDir, subDir, "file"), []byte("Hello"), 0644))
|
||||
must(t, ioutil.WriteFile(filepath.Join(tmpDir, ignDir, "file"), []byte("Hello"), 0644))
|
||||
must(t, os.WriteFile(filepath.Join(tmpDir, subDir, "file"), []byte("Hello"), 0644))
|
||||
must(t, os.WriteFile(filepath.Join(tmpDir, ignDir, "file"), []byte("Hello"), 0644))
|
||||
file := protocol.FileInfo{
|
||||
Name: "issue3164",
|
||||
}
|
||||
@@ -913,7 +910,7 @@ func TestSRConflictReplaceFileByDir(t *testing.T) {
|
||||
name := "foo"
|
||||
|
||||
// create local file
|
||||
file := createFile(t, name, ffs)
|
||||
file := createEmptyFileInfo(t, name, ffs)
|
||||
file.Version = protocol.Vector{}.Update(myID.Short())
|
||||
f.updateLocalsFromScanning([]protocol.FileInfo{file})
|
||||
|
||||
@@ -945,7 +942,7 @@ func TestSRConflictReplaceFileByLink(t *testing.T) {
|
||||
name := "foo"
|
||||
|
||||
// create local file
|
||||
file := createFile(t, name, ffs)
|
||||
file := createEmptyFileInfo(t, name, ffs)
|
||||
file.Version = protocol.Vector{}.Update(myID.Short())
|
||||
f.updateLocalsFromScanning([]protocol.FileInfo{file})
|
||||
|
||||
@@ -983,7 +980,7 @@ func TestDeleteBehindSymlink(t *testing.T) {
|
||||
file := filepath.Join(link, "file")
|
||||
|
||||
must(t, ffs.MkdirAll(link, 0755))
|
||||
fi := createFile(t, file, ffs)
|
||||
fi := createEmptyFileInfo(t, file, ffs)
|
||||
f.updateLocalsFromScanning([]protocol.FileInfo{fi})
|
||||
must(t, osutil.RenameOrCopy(fs.CopyRangeMethodStandard, ffs, destFs, file, "file"))
|
||||
must(t, ffs.RemoveAll(link))
|
||||
@@ -1099,7 +1096,7 @@ func TestPullCaseOnlyPerformFinish(t *testing.T) {
|
||||
|
||||
name := "foo"
|
||||
contents := []byte("contents")
|
||||
must(t, writeFile(ffs, name, contents, 0644))
|
||||
writeFile(t, ffs, name, contents)
|
||||
must(t, f.scanSubdirs(nil))
|
||||
|
||||
var cur protocol.FileInfo
|
||||
@@ -1122,7 +1119,7 @@ func TestPullCaseOnlyPerformFinish(t *testing.T) {
|
||||
remote.Version = protocol.Vector{}.Update(device1.Short())
|
||||
remote.Name = strings.ToUpper(cur.Name)
|
||||
temp := fs.TempName(remote.Name)
|
||||
must(t, writeFile(ffs, temp, contents, 0644))
|
||||
writeFile(t, ffs, temp, contents)
|
||||
scanChan := make(chan string, 1)
|
||||
dbUpdateChan := make(chan dbUpdateJob, 1)
|
||||
|
||||
|
||||
@@ -178,7 +178,11 @@ func (c *folderSummaryService) listenForUpdates(ctx context.Context) error {
|
||||
// This loop needs to be fast so we don't miss too many events.
|
||||
|
||||
select {
|
||||
case ev := <-sub.C():
|
||||
case ev, ok := <-sub.C():
|
||||
if !ok {
|
||||
<-ctx.Done()
|
||||
return ctx.Err()
|
||||
}
|
||||
c.processUpdate(ev)
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
|
||||
@@ -168,6 +168,8 @@ type model struct {
|
||||
foldersRunning int32
|
||||
}
|
||||
|
||||
var _ config.Verifier = &model{}
|
||||
|
||||
type folderFactory func(*model, *db.FileSet, *ignore.Matcher, config.FolderConfiguration, versioner.Versioner, events.Logger, *util.Semaphore) service
|
||||
|
||||
var (
|
||||
@@ -1053,7 +1055,6 @@ func (m *model) RemoteNeedFolderFiles(folder string, device protocol.DeviceID, p
|
||||
func (m *model) LocalChangedFolderFiles(folder string, page, perpage int) ([]db.FileInfoTruncated, error) {
|
||||
m.fmut.RLock()
|
||||
rf, ok := m.folderFiles[folder]
|
||||
cfg := m.folderCfgs[folder]
|
||||
m.fmut.RUnlock()
|
||||
|
||||
if !ok {
|
||||
@@ -1071,11 +1072,10 @@ func (m *model) LocalChangedFolderFiles(folder string, page, perpage int) ([]db.
|
||||
}
|
||||
|
||||
p := newPager(page, perpage)
|
||||
recvEnc := cfg.Type == config.FolderTypeReceiveEncrypted
|
||||
files := make([]db.FileInfoTruncated, 0, perpage)
|
||||
|
||||
snap.WithHaveTruncated(protocol.LocalDeviceID, func(f protocol.FileIntf) bool {
|
||||
if !f.IsReceiveOnlyChanged() || (recvEnc && f.IsDeleted()) {
|
||||
if !f.IsReceiveOnlyChanged() {
|
||||
return true
|
||||
}
|
||||
if p.skip() {
|
||||
@@ -2403,7 +2403,7 @@ func (m *model) numHashers(folder string) int {
|
||||
return folderCfg.Hashers
|
||||
}
|
||||
|
||||
if runtime.GOOS == "windows" || runtime.GOOS == "darwin" {
|
||||
if runtime.GOOS == "windows" || runtime.GOOS == "darwin" || runtime.GOOS == "android" {
|
||||
// Interactive operating systems; don't load the system too heavily by
|
||||
// default.
|
||||
return 1
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -299,7 +298,7 @@ func BenchmarkRequestInSingleFile(b *testing.B) {
|
||||
mustRemove(b, defaultFs.RemoveAll("request"))
|
||||
defer func() { mustRemove(b, defaultFs.RemoveAll("request")) }()
|
||||
must(b, defaultFs.MkdirAll("request/for/a/file/in/a/couple/of/dirs", 0755))
|
||||
writeFile(defaultFs, "request/for/a/file/in/a/couple/of/dirs/128k", buf, 0644)
|
||||
writeFile(b, defaultFs, "request/for/a/file/in/a/couple/of/dirs/128k", buf)
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
@@ -1489,7 +1488,7 @@ func TestIgnores(t *testing.T) {
|
||||
// Assure a clean start state
|
||||
mustRemove(t, defaultFs.RemoveAll(config.DefaultMarkerName))
|
||||
mustRemove(t, defaultFs.MkdirAll(config.DefaultMarkerName, 0644))
|
||||
writeFile(defaultFs, ".stignore", []byte(".*\nquux\n"), 0644)
|
||||
writeFile(t, defaultFs, ".stignore", []byte(".*\nquux\n"))
|
||||
|
||||
m := setupModel(t, defaultCfgWrapper)
|
||||
defer cleanupModel(m)
|
||||
@@ -2030,8 +2029,8 @@ func benchmarkTree(b *testing.B, n1, n2 int) {
|
||||
func TestIssue3028(t *testing.T) {
|
||||
// Create two files that we'll delete, one with a name that is a prefix of the other.
|
||||
|
||||
must(t, writeFile(defaultFs, "testrm", []byte("Hello"), 0644))
|
||||
must(t, writeFile(defaultFs, "testrm2", []byte("Hello"), 0644))
|
||||
writeFile(t, defaultFs, "testrm", []byte("Hello"))
|
||||
writeFile(t, defaultFs, "testrm2", []byte("Hello"))
|
||||
defer func() {
|
||||
mustRemove(t, defaultFs.Remove("testrm"))
|
||||
mustRemove(t, defaultFs.Remove("testrm2"))
|
||||
@@ -2152,7 +2151,7 @@ func TestIssue2782(t *testing.T) {
|
||||
if err := os.MkdirAll(testDir+"/syncdir", 0755); err != nil {
|
||||
t.Skip(err)
|
||||
}
|
||||
if err := ioutil.WriteFile(testDir+"/syncdir/file", []byte("hello, world\n"), 0644); err != nil {
|
||||
if err := os.WriteFile(testDir+"/syncdir/file", []byte("hello, world\n"), 0644); err != nil {
|
||||
t.Skip(err)
|
||||
}
|
||||
if err := os.Symlink("syncdir", testDir+"/synclink"); err != nil {
|
||||
@@ -2763,7 +2762,7 @@ func TestVersionRestore(t *testing.T) {
|
||||
// In each file, we write the filename as the content
|
||||
// We verify that the content matches at the expected filenames
|
||||
// after the restore operation.
|
||||
dir, err := ioutil.TempDir("", "")
|
||||
dir, err := os.MkdirTemp("", "")
|
||||
must(t, err)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
@@ -2900,7 +2899,7 @@ func TestVersionRestore(t *testing.T) {
|
||||
}
|
||||
defer fd.Close()
|
||||
|
||||
content, err := ioutil.ReadAll(fd)
|
||||
content, err := io.ReadAll(fd)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -2930,7 +2929,7 @@ func TestVersionRestore(t *testing.T) {
|
||||
must(t, err)
|
||||
defer fd.Close()
|
||||
|
||||
content, err := ioutil.ReadAll(fd)
|
||||
content, err := io.ReadAll(fd)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@@ -3403,7 +3402,7 @@ func TestRenameSequenceOrder(t *testing.T) {
|
||||
ffs := fcfg.Filesystem()
|
||||
for i := 0; i < numFiles; i++ {
|
||||
v := fmt.Sprintf("%d", i)
|
||||
must(t, writeFile(ffs, v, []byte(v), 0644))
|
||||
writeFile(t, ffs, v, []byte(v))
|
||||
}
|
||||
|
||||
m.ScanFolders()
|
||||
@@ -3426,7 +3425,7 @@ func TestRenameSequenceOrder(t *testing.T) {
|
||||
continue
|
||||
}
|
||||
v := fmt.Sprintf("%d", i)
|
||||
must(t, writeFile(ffs, v, []byte(v+"-new"), 0644))
|
||||
writeFile(t, ffs, v, []byte(v+"-new"))
|
||||
}
|
||||
// Rename
|
||||
must(t, ffs.Rename("3", "17"))
|
||||
@@ -3470,7 +3469,7 @@ func TestRenameSameFile(t *testing.T) {
|
||||
defer cleanupModel(m)
|
||||
|
||||
ffs := fcfg.Filesystem()
|
||||
must(t, writeFile(ffs, "file", []byte("file"), 0644))
|
||||
writeFile(t, ffs, "file", []byte("file"))
|
||||
|
||||
m.ScanFolders()
|
||||
|
||||
@@ -3522,8 +3521,8 @@ func TestRenameEmptyFile(t *testing.T) {
|
||||
|
||||
ffs := fcfg.Filesystem()
|
||||
|
||||
must(t, writeFile(ffs, "file", []byte("data"), 0644))
|
||||
must(t, writeFile(ffs, "empty", nil, 0644))
|
||||
writeFile(t, ffs, "file", []byte("data"))
|
||||
writeFile(t, ffs, "empty", nil)
|
||||
|
||||
m.ScanFolders()
|
||||
|
||||
@@ -3598,11 +3597,11 @@ func TestBlockListMap(t *testing.T) {
|
||||
defer cleanupModel(m)
|
||||
|
||||
ffs := fcfg.Filesystem()
|
||||
must(t, writeFile(ffs, "one", []byte("content"), 0644))
|
||||
must(t, writeFile(ffs, "two", []byte("content"), 0644))
|
||||
must(t, writeFile(ffs, "three", []byte("content"), 0644))
|
||||
must(t, writeFile(ffs, "four", []byte("content"), 0644))
|
||||
must(t, writeFile(ffs, "five", []byte("content"), 0644))
|
||||
writeFile(t, ffs, "one", []byte("content"))
|
||||
writeFile(t, ffs, "two", []byte("content"))
|
||||
writeFile(t, ffs, "three", []byte("content"))
|
||||
writeFile(t, ffs, "four", []byte("content"))
|
||||
writeFile(t, ffs, "five", []byte("content"))
|
||||
|
||||
m.ScanFolders()
|
||||
|
||||
@@ -3631,7 +3630,7 @@ func TestBlockListMap(t *testing.T) {
|
||||
|
||||
// Modify
|
||||
must(t, ffs.Remove("two"))
|
||||
must(t, writeFile(ffs, "two", []byte("mew-content"), 0644))
|
||||
writeFile(t, ffs, "two", []byte("mew-content"))
|
||||
|
||||
// Rename
|
||||
must(t, ffs.Rename("three", "new-three"))
|
||||
@@ -3667,7 +3666,7 @@ func TestScanRenameCaseOnly(t *testing.T) {
|
||||
|
||||
ffs := fcfg.Filesystem()
|
||||
name := "foo"
|
||||
must(t, writeFile(ffs, name, []byte("contents"), 0644))
|
||||
writeFile(t, ffs, name, []byte("contents"))
|
||||
|
||||
m.ScanFolders()
|
||||
|
||||
@@ -3791,7 +3790,7 @@ func TestScanDeletedROChangedOnSR(t *testing.T) {
|
||||
|
||||
name := "foo"
|
||||
|
||||
must(t, writeFile(ffs, name, []byte(name), 0644))
|
||||
writeFile(t, ffs, name, []byte(name))
|
||||
m.ScanFolders()
|
||||
|
||||
file, ok := m.testCurrentFolderFile(fcfg.ID, name)
|
||||
@@ -4255,6 +4254,46 @@ func TestPendingFolder(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeletedNotLocallyChangedReceiveOnly(t *testing.T) {
|
||||
deletedNotLocallyChanged(t, config.FolderTypeReceiveOnly)
|
||||
}
|
||||
|
||||
func TestDeletedNotLocallyChangedReceiveEncrypted(t *testing.T) {
|
||||
deletedNotLocallyChanged(t, config.FolderTypeReceiveEncrypted)
|
||||
}
|
||||
|
||||
func deletedNotLocallyChanged(t *testing.T, ft config.FolderType) {
|
||||
w, fcfg, wCancel := tmpDefaultWrapper()
|
||||
tfs := fcfg.Filesystem()
|
||||
fcfg.Type = ft
|
||||
setFolder(t, w, fcfg)
|
||||
defer wCancel()
|
||||
m := setupModel(t, w)
|
||||
defer cleanupModelAndRemoveDir(m, tfs.URI())
|
||||
|
||||
name := "foo"
|
||||
writeFile(t, tfs, name, nil)
|
||||
must(t, m.ScanFolder(fcfg.ID))
|
||||
|
||||
fi, ok, err := m.CurrentFolderFile(fcfg.ID, name)
|
||||
must(t, err)
|
||||
if !ok {
|
||||
t.Fatal("File hasn't been added")
|
||||
}
|
||||
if !fi.IsReceiveOnlyChanged() {
|
||||
t.Fatal("File isn't receive-only-changed")
|
||||
}
|
||||
|
||||
must(t, tfs.Remove(name))
|
||||
must(t, m.ScanFolder(fcfg.ID))
|
||||
|
||||
_, ok, err = m.CurrentFolderFile(fcfg.ID, name)
|
||||
must(t, err)
|
||||
if ok {
|
||||
t.Error("Expected file to be removed from db")
|
||||
}
|
||||
}
|
||||
|
||||
func equalStringsInAnyOrder(a, b []string) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
|
||||
@@ -212,11 +212,6 @@ func (t *ProgressEmitter) computeProgressUpdates() []progressUpdate {
|
||||
return progressUpdates
|
||||
}
|
||||
|
||||
// VerifyConfiguration implements the config.Committer interface
|
||||
func (t *ProgressEmitter) VerifyConfiguration(from, to config.Configuration) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// CommitConfiguration implements the config.Committer interface
|
||||
func (t *ProgressEmitter) CommitConfiguration(_, to config.Configuration) bool {
|
||||
t.mut.Lock()
|
||||
|
||||
@@ -23,7 +23,7 @@ type jobQueue struct {
|
||||
type jobQueueEntry struct {
|
||||
name string
|
||||
size int64
|
||||
modified time.Time
|
||||
modified int64
|
||||
}
|
||||
|
||||
func newJobQueue() *jobQueue {
|
||||
@@ -34,7 +34,8 @@ func newJobQueue() *jobQueue {
|
||||
|
||||
func (q *jobQueue) Push(file string, size int64, modified time.Time) {
|
||||
q.mut.Lock()
|
||||
q.queued = append(q.queued, jobQueueEntry{file, size, modified})
|
||||
// The range of UnixNano covers a range of reasonable timestamps.
|
||||
q.queued = append(q.queued, jobQueueEntry{file, size, modified.UnixNano()})
|
||||
q.mut.Unlock()
|
||||
}
|
||||
|
||||
@@ -191,5 +192,5 @@ func (q smallestFirst) Swap(a, b int) { q[a], q[b] = q[b], q[a] }
|
||||
type oldestFirst []jobQueueEntry
|
||||
|
||||
func (q oldestFirst) Len() int { return len(q) }
|
||||
func (q oldestFirst) Less(a, b int) bool { return q[a].modified.Before(q[b].modified) }
|
||||
func (q oldestFirst) Less(a, b int) bool { return q[a].modified < q[b].modified }
|
||||
func (q oldestFirst) Swap(a, b int) { q[a], q[b] = q[b], q[a] }
|
||||
|
||||
@@ -8,6 +8,7 @@ package model
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -251,16 +252,19 @@ func TestSortByAge(t *testing.T) {
|
||||
}
|
||||
|
||||
func BenchmarkJobQueueBump(b *testing.B) {
|
||||
files := genFiles(b.N)
|
||||
files := genFiles(10000)
|
||||
|
||||
q := newJobQueue()
|
||||
for _, f := range files {
|
||||
q.Push(f.Name, 0, time.Time{})
|
||||
}
|
||||
|
||||
rng := rand.New(rand.NewSource(int64(b.N)))
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
q.BringToFront(files[i].Name)
|
||||
r := rng.Intn(len(files))
|
||||
q.BringToFront(files[r].Name)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
@@ -236,7 +235,7 @@ func TestRequestVersioningSymlinkAttack(t *testing.T) {
|
||||
|
||||
// Create a temporary directory that we will use as target to see if
|
||||
// we can escape to it
|
||||
tmpdir, err := ioutil.TempDir("", "syncthing-test")
|
||||
tmpdir, err := os.MkdirTemp("", "syncthing-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -330,9 +329,7 @@ func pullInvalidIgnored(t *testing.T, ft config.FolderType) {
|
||||
fc.deleteFile(invDel)
|
||||
fc.addFile(ign, 0644, protocol.FileInfoTypeFile, contents)
|
||||
fc.addFile(ignExisting, 0644, protocol.FileInfoTypeFile, contents)
|
||||
if err := writeFile(fss, ignExisting, otherContents, 0644); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
writeFile(t, fss, ignExisting, otherContents)
|
||||
|
||||
done := make(chan struct{})
|
||||
fc.setIndexFn(func(_ context.Context, folder string, fs []protocol.FileInfo) error {
|
||||
@@ -486,7 +483,7 @@ func TestRescanIfHaveInvalidContent(t *testing.T) {
|
||||
|
||||
payload := []byte("hello")
|
||||
|
||||
must(t, writeFile(tfs, "foo", payload, 0777))
|
||||
writeFile(t, tfs, "foo", payload)
|
||||
|
||||
received := make(chan []protocol.FileInfo)
|
||||
fc.setIndexFn(func(_ context.Context, _ string, fs []protocol.FileInfo) error {
|
||||
@@ -526,7 +523,7 @@ func TestRescanIfHaveInvalidContent(t *testing.T) {
|
||||
payload = []byte("bye")
|
||||
buf = make([]byte, len(payload))
|
||||
|
||||
must(t, writeFile(tfs, "foo", payload, 0777))
|
||||
writeFile(t, tfs, "foo", payload)
|
||||
|
||||
_, err = m.Request(device1, "default", "foo", 0, int32(len(payload)), 0, f.Blocks[0].Hash, f.Blocks[0].WeakHash, false)
|
||||
if err == nil {
|
||||
@@ -683,7 +680,7 @@ func TestRequestSymlinkWindows(t *testing.T) {
|
||||
}
|
||||
|
||||
func equalContents(path string, contents []byte) error {
|
||||
if bs, err := ioutil.ReadFile(path); err != nil {
|
||||
if bs, err := os.ReadFile(path); err != nil {
|
||||
return err
|
||||
} else if !bytes.Equal(bs, contents) {
|
||||
return errors.New("incorrect data")
|
||||
@@ -1066,9 +1063,7 @@ func TestIgnoreDeleteUnignore(t *testing.T) {
|
||||
return nil
|
||||
})
|
||||
|
||||
if err := writeFile(fss, file, contents, 0644); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
writeFile(t, fss, file, contents)
|
||||
m.ScanFolders()
|
||||
|
||||
select {
|
||||
@@ -1430,6 +1425,14 @@ func TestRequestReceiveEncrypted(t *testing.T) {
|
||||
// Simulate request from device that is untrusted too, i.e. with non-empty, but garbage hash
|
||||
_, err := m.Request(device1, fcfg.ID, name, 0, 1064, 0, []byte("garbage"), 0, false)
|
||||
must(t, err)
|
||||
|
||||
changed, err := m.LocalChangedFolderFiles(fcfg.ID, 1, 10)
|
||||
must(t, err)
|
||||
if l := len(changed); l != 1 {
|
||||
t.Errorf("Expected one locally changed file, got %v", l)
|
||||
} else if changed[0].Name != files[0].Name {
|
||||
t.Errorf("Expected %v, got %v", files[0].Name, changed[0].Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequestGlobalInvalidToValid(t *testing.T) {
|
||||
|
||||
@@ -8,7 +8,6 @@ package model
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -76,7 +75,7 @@ func init() {
|
||||
}
|
||||
|
||||
func createTmpWrapper(cfg config.Configuration) (config.Wrapper, context.CancelFunc) {
|
||||
tmpFile, err := ioutil.TempFile("", "syncthing-testConfig-")
|
||||
tmpFile, err := os.CreateTemp("", "syncthing-testConfig-")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -215,7 +214,7 @@ func cleanupModelAndRemoveDir(m *testModel, dir string) {
|
||||
}
|
||||
|
||||
func createTmpDir() string {
|
||||
tmpDir, err := ioutil.TempDir("", "syncthing_testFolder-")
|
||||
tmpDir, err := os.MkdirTemp("", "syncthing_testFolder-")
|
||||
if err != nil {
|
||||
panic("Failed to create temporary testing dir")
|
||||
}
|
||||
@@ -435,3 +434,18 @@ func addDevice2(t testing.TB, w config.Wrapper, fcfg config.FolderConfiguration)
|
||||
must(t, err)
|
||||
waiter.Wait()
|
||||
}
|
||||
|
||||
func writeFile(t testing.TB, filesystem fs.Filesystem, name string, data []byte) {
|
||||
t.Helper()
|
||||
fd, err := filesystem.Create(name)
|
||||
must(t, err)
|
||||
defer fd.Close()
|
||||
_, err = fd.Write(data)
|
||||
must(t, err)
|
||||
}
|
||||
|
||||
func writeFilePerm(t testing.TB, filesystem fs.Filesystem, name string, data []byte, perm fs.FileMode) {
|
||||
t.Helper()
|
||||
writeFile(t, filesystem, name, data)
|
||||
must(t, filesystem.Chmod(name, perm))
|
||||
}
|
||||
|
||||
@@ -45,10 +45,6 @@ func NewService(id protocol.DeviceID, cfg config.Wrapper) *Service {
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *Service) VerifyConfiguration(from, to config.Configuration) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) CommitConfiguration(from, to config.Configuration) bool {
|
||||
s.mut.Lock()
|
||||
if !s.enabled && to.Options.NATEnabled {
|
||||
@@ -125,11 +121,15 @@ func (s *Service) process(ctx context.Context) (int, time.Duration) {
|
||||
|
||||
s.mut.RLock()
|
||||
for _, mapping := range s.mappings {
|
||||
if mapping.expires.Before(time.Now()) {
|
||||
mapping.mut.RLock()
|
||||
expires := mapping.expires
|
||||
mapping.mut.RUnlock()
|
||||
|
||||
if expires.Before(time.Now()) {
|
||||
toRenew = append(toRenew, mapping)
|
||||
} else {
|
||||
toUpdate = append(toUpdate, mapping)
|
||||
mappingRenewIn := time.Until(mapping.expires)
|
||||
mappingRenewIn := time.Until(expires)
|
||||
if mappingRenewIn < renewIn {
|
||||
renewIn = mappingRenewIn
|
||||
}
|
||||
@@ -206,41 +206,36 @@ func (s *Service) RemoveMapping(mapping *Mapping) {
|
||||
// Optionally takes renew flag which indicates whether or not we should renew
|
||||
// mappings with existing natds
|
||||
func (s *Service) updateMapping(ctx context.Context, mapping *Mapping, nats map[string]Device, renew bool) {
|
||||
var added, removed []Address
|
||||
|
||||
renewalTime := time.Duration(s.cfg.Options().NATRenewalM) * time.Minute
|
||||
|
||||
mapping.mut.Lock()
|
||||
|
||||
mapping.expires = time.Now().Add(renewalTime)
|
||||
change := s.verifyExistingLocked(ctx, mapping, nats, renew)
|
||||
add := s.acquireNewLocked(ctx, mapping, nats)
|
||||
|
||||
newAdded, newRemoved := s.verifyExistingMappings(ctx, mapping, nats, renew)
|
||||
added = append(added, newAdded...)
|
||||
removed = append(removed, newRemoved...)
|
||||
mapping.mut.Unlock()
|
||||
|
||||
newAdded, newRemoved = s.acquireNewMappings(ctx, mapping, nats)
|
||||
added = append(added, newAdded...)
|
||||
removed = append(removed, newRemoved...)
|
||||
|
||||
if len(added) > 0 || len(removed) > 0 {
|
||||
mapping.notify(added, removed)
|
||||
if change || add {
|
||||
mapping.notify()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) verifyExistingMappings(ctx context.Context, mapping *Mapping, nats map[string]Device, renew bool) ([]Address, []Address) {
|
||||
var added, removed []Address
|
||||
|
||||
func (s *Service) verifyExistingLocked(ctx context.Context, mapping *Mapping, nats map[string]Device, renew bool) (change bool) {
|
||||
leaseTime := time.Duration(s.cfg.Options().NATLeaseM) * time.Minute
|
||||
|
||||
for id, address := range mapping.addressMap() {
|
||||
for id, address := range mapping.extAddresses {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, nil
|
||||
return false
|
||||
default:
|
||||
}
|
||||
|
||||
// Delete addresses for NATDevice's that do not exist anymore
|
||||
nat, ok := nats[id]
|
||||
if !ok {
|
||||
mapping.removeAddress(id)
|
||||
removed = append(removed, address)
|
||||
mapping.removeAddressLocked(id)
|
||||
change = true
|
||||
continue
|
||||
} else if renew {
|
||||
// Only perform renewals on the nat's that have the right local IP
|
||||
@@ -256,35 +251,32 @@ func (s *Service) verifyExistingMappings(ctx context.Context, mapping *Mapping,
|
||||
addr, err := s.tryNATDevice(ctx, nat, mapping.address.Port, address.Port, leaseTime)
|
||||
if err != nil {
|
||||
l.Debugf("Failed to renew %s -> mapping on %s", mapping, address, id)
|
||||
mapping.removeAddress(id)
|
||||
removed = append(removed, address)
|
||||
mapping.removeAddressLocked(id)
|
||||
change = true
|
||||
continue
|
||||
}
|
||||
|
||||
l.Debugf("Renewed %s -> %s mapping on %s", mapping, address, id)
|
||||
|
||||
if !addr.Equal(address) {
|
||||
mapping.removeAddress(id)
|
||||
mapping.setAddress(id, addr)
|
||||
removed = append(removed, address)
|
||||
added = append(added, address)
|
||||
mapping.removeAddressLocked(id)
|
||||
mapping.setAddressLocked(id, addr)
|
||||
change = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return added, removed
|
||||
return change
|
||||
}
|
||||
|
||||
func (s *Service) acquireNewMappings(ctx context.Context, mapping *Mapping, nats map[string]Device) ([]Address, []Address) {
|
||||
var added, removed []Address
|
||||
|
||||
func (s *Service) acquireNewLocked(ctx context.Context, mapping *Mapping, nats map[string]Device) (change bool) {
|
||||
leaseTime := time.Duration(s.cfg.Options().NATLeaseM) * time.Minute
|
||||
addrMap := mapping.addressMap()
|
||||
addrMap := mapping.extAddresses
|
||||
|
||||
for id, nat := range nats {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, nil
|
||||
return false
|
||||
default:
|
||||
}
|
||||
|
||||
@@ -310,11 +302,11 @@ func (s *Service) acquireNewMappings(ctx context.Context, mapping *Mapping, nats
|
||||
|
||||
l.Debugf("Acquired %s -> %s mapping on %s", mapping, addr, id)
|
||||
|
||||
mapping.setAddress(id, addr)
|
||||
added = append(added, addr)
|
||||
mapping.setAddressLocked(id, addr)
|
||||
change = true
|
||||
}
|
||||
|
||||
return added, removed
|
||||
return change
|
||||
}
|
||||
|
||||
// tryNATDevice tries to acquire a port mapping for the given internal address to
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
"github.com/syncthing/syncthing/lib/sync"
|
||||
)
|
||||
|
||||
type MappingChangeSubscriber func(*Mapping, []Address, []Address)
|
||||
type MappingChangeSubscriber func()
|
||||
|
||||
type Mapping struct {
|
||||
protocol Protocol
|
||||
@@ -26,55 +26,41 @@ type Mapping struct {
|
||||
mut sync.RWMutex
|
||||
}
|
||||
|
||||
func (m *Mapping) setAddress(id string, address Address) {
|
||||
m.mut.Lock()
|
||||
if existing, ok := m.extAddresses[id]; !ok || !existing.Equal(address) {
|
||||
l.Infof("New NAT port mapping: external %s address %s to local address %s.", m.protocol, address, m.address)
|
||||
m.extAddresses[id] = address
|
||||
}
|
||||
m.mut.Unlock()
|
||||
func (m *Mapping) setAddressLocked(id string, address Address) {
|
||||
l.Infof("New NAT port mapping: external %s address %s to local address %s.", m.protocol, address, m.address)
|
||||
m.extAddresses[id] = address
|
||||
}
|
||||
|
||||
func (m *Mapping) removeAddress(id string) {
|
||||
m.mut.Lock()
|
||||
func (m *Mapping) removeAddressLocked(id string) {
|
||||
addr, ok := m.extAddresses[id]
|
||||
if ok {
|
||||
l.Infof("Removing NAT port mapping: external %s address %s, NAT %s is no longer available.", m.protocol, addr, id)
|
||||
delete(m.extAddresses, id)
|
||||
}
|
||||
m.mut.Unlock()
|
||||
}
|
||||
|
||||
func (m *Mapping) clearAddresses() {
|
||||
m.mut.Lock()
|
||||
var removed []Address
|
||||
change := len(m.extAddresses) > 0
|
||||
for id, addr := range m.extAddresses {
|
||||
l.Debugf("Clearing mapping %s: ID: %s Address: %s", m, id, addr)
|
||||
removed = append(removed, addr)
|
||||
delete(m.extAddresses, id)
|
||||
}
|
||||
m.expires = time.Time{}
|
||||
m.mut.Unlock()
|
||||
if len(removed) > 0 {
|
||||
m.notify(nil, removed)
|
||||
if change {
|
||||
m.notify()
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Mapping) notify(added, removed []Address) {
|
||||
func (m *Mapping) notify() {
|
||||
m.mut.RLock()
|
||||
for _, subscriber := range m.subscribers {
|
||||
subscriber(m, added, removed)
|
||||
subscriber()
|
||||
}
|
||||
m.mut.RUnlock()
|
||||
}
|
||||
|
||||
func (m *Mapping) addressMap() map[string]Address {
|
||||
m.mut.RLock()
|
||||
addrMap := m.extAddresses
|
||||
m.mut.RUnlock()
|
||||
return addrMap
|
||||
}
|
||||
|
||||
func (m *Mapping) Protocol() Protocol {
|
||||
return m.protocol
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
package nat
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"testing"
|
||||
@@ -60,7 +59,7 @@ func TestMappingValidGateway(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMappingClearAddresses(t *testing.T) {
|
||||
tmpFile, err := ioutil.TempFile("", "syncthing-testConfig-")
|
||||
tmpFile, err := os.CreateTemp("", "syncthing-testConfig-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ func CreateAtomic(path string) (*AtomicWriter, error) {
|
||||
// permissions.
|
||||
func CreateAtomicFilesystem(filesystem fs.Filesystem, path string) (*AtomicWriter, error) {
|
||||
// The security of this depends on the tempfile having secure
|
||||
// permissions, 0600, from the beginning. This is what ioutil.TempFile
|
||||
// permissions, 0600, from the beginning. This is what os.CreateTemp
|
||||
// does. We have a test that verifies that that is the case, should this
|
||||
// ever change in the standard library in the future.
|
||||
fd, err := TempFile(filesystem, filepath.Dir(path), TempPrefix)
|
||||
|
||||
@@ -8,7 +8,6 @@ package osutil
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
@@ -35,7 +34,7 @@ func TestCreateAtomicCreate(t *testing.T) {
|
||||
t.Fatal("written bytes", n, "!= 5")
|
||||
}
|
||||
|
||||
if _, err := ioutil.ReadFile("testdata/file"); err == nil {
|
||||
if _, err := os.ReadFile("testdata/file"); err == nil {
|
||||
t.Fatal("file should not exist")
|
||||
}
|
||||
|
||||
@@ -43,7 +42,7 @@ func TestCreateAtomicCreate(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
bs, err := ioutil.ReadFile("testdata/file")
|
||||
bs, err := os.ReadFile("testdata/file")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -62,7 +61,7 @@ func TestCreateAtomicReplaceReadOnly(t *testing.T) {
|
||||
func testCreateAtomicReplace(t *testing.T, oldPerms os.FileMode) {
|
||||
t.Helper()
|
||||
|
||||
testdir, err := ioutil.TempDir("", "syncthing")
|
||||
testdir, err := os.MkdirTemp("", "syncthing")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -75,7 +74,7 @@ func testCreateAtomicReplace(t *testing.T, oldPerms os.FileMode) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(testfile, []byte("some old data"), oldPerms); err != nil {
|
||||
if err := os.WriteFile(testfile, []byte("some old data"), oldPerms); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -103,7 +102,7 @@ func testCreateAtomicReplace(t *testing.T, oldPerms os.FileMode) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
bs, err := ioutil.ReadFile(testfile)
|
||||
bs, err := os.ReadFile(testfile)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
package osutil
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"syscall"
|
||||
"testing"
|
||||
@@ -24,7 +23,7 @@ func TestTempFilePermissions(t *testing.T) {
|
||||
oldMask := syscall.Umask(0)
|
||||
defer syscall.Umask(oldMask)
|
||||
|
||||
fd, err := ioutil.TempFile("", "test")
|
||||
fd, err := os.CreateTemp("", "test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -7,43 +7,19 @@
|
||||
package osutil
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
// https://docs.microsoft.com/windows/win32/api/processthreadsapi/nf-processthreadsapi-setpriorityclass
|
||||
aboveNormalPriorityClass = 0x00008000
|
||||
belowNormalPriorityClass = 0x00004000
|
||||
highPriorityClass = 0x00000080
|
||||
idlePriorityClass = 0x00000040
|
||||
normalPriorityClass = 0x00000020
|
||||
processModeBackgroundBegin = 0x00100000
|
||||
processModeBackgroundEnd = 0x00200000
|
||||
realtimePriorityClass = 0x00000100
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
// SetLowPriority lowers the process CPU scheduling priority, and possibly
|
||||
// I/O priority depending on the platform and OS.
|
||||
func SetLowPriority() error {
|
||||
modkernel32 := syscall.NewLazyDLL("kernel32.dll")
|
||||
setPriorityClass := modkernel32.NewProc("SetPriorityClass")
|
||||
|
||||
if err := setPriorityClass.Find(); err != nil {
|
||||
return errors.Wrap(err, "find proc")
|
||||
}
|
||||
|
||||
handle, err := syscall.GetCurrentProcess()
|
||||
handle, err := windows.GetCurrentProcess()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "get process handler")
|
||||
return errors.Wrap(err, "get process handle")
|
||||
}
|
||||
defer syscall.CloseHandle(handle)
|
||||
defer windows.CloseHandle(handle)
|
||||
|
||||
res, _, err := setPriorityClass.Call(uintptr(handle), belowNormalPriorityClass)
|
||||
if res != 0 {
|
||||
// "If the function succeeds, the return value is nonzero."
|
||||
return nil
|
||||
}
|
||||
err = windows.SetPriorityClass(handle, windows.BELOW_NORMAL_PRIORITY_CLASS)
|
||||
return errors.Wrap(err, "set priority class") // wraps nil as nil
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
package osutil_test
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
@@ -83,7 +83,7 @@ func TestIsDeleted(t *testing.T) {
|
||||
func TestRenameOrCopy(t *testing.T) {
|
||||
mustTempDir := func() string {
|
||||
t.Helper()
|
||||
tmpDir, err := ioutil.TempDir("", "")
|
||||
tmpDir, err := os.MkdirTemp("", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -131,7 +131,7 @@ func TestRenameOrCopy(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
buf, err := ioutil.ReadAll(fd)
|
||||
buf, err := io.ReadAll(fd)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -147,7 +147,7 @@ func TestRenameOrCopy(t *testing.T) {
|
||||
if fd, err := test.dst.Open("new"); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
if buf, err := ioutil.ReadAll(fd); err != nil {
|
||||
if buf, err := io.ReadAll(fd); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if string(buf) != content {
|
||||
t.Fatalf("expected %s got %s", content, string(buf))
|
||||
|
||||
@@ -9,6 +9,7 @@ package osutil
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
type ReplacingWriter struct {
|
||||
@@ -46,3 +47,16 @@ func (w ReplacingWriter) Write(bs []byte) (int, error) {
|
||||
|
||||
return written, err
|
||||
}
|
||||
|
||||
// LineEndingsWriter returns a writer that writes platform-appropriate line
|
||||
// endings. (This is a no-op on non-Windows platforms.)
|
||||
func LineEndingsWriter(w io.Writer) io.Writer {
|
||||
if runtime.GOOS != "windows" {
|
||||
return w
|
||||
}
|
||||
return &ReplacingWriter{
|
||||
Writer: w,
|
||||
From: '\n',
|
||||
To: []byte{'\r', '\n'},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
package osutil_test
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
@@ -18,7 +17,7 @@ import (
|
||||
)
|
||||
|
||||
func TestTraversesSymlink(t *testing.T) {
|
||||
tmpDir, err := ioutil.TempDir(".", ".test-TraversesSymlink-")
|
||||
tmpDir, err := os.MkdirTemp(".", ".test-TraversesSymlink-")
|
||||
if err != nil {
|
||||
panic("Failed to create temporary testing dir")
|
||||
}
|
||||
@@ -71,7 +70,7 @@ func TestTraversesSymlink(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestIssue4875(t *testing.T) {
|
||||
tmpDir, err := ioutil.TempDir("", ".test-Issue4875-")
|
||||
tmpDir, err := os.MkdirTemp("", ".test-Issue4875-")
|
||||
if err != nil {
|
||||
panic("Failed to create temporary testing dir")
|
||||
}
|
||||
|
||||
@@ -214,18 +214,3 @@ func untypeoify(s string) string {
|
||||
s = strings.ReplaceAll(s, "8", "B")
|
||||
return s
|
||||
}
|
||||
|
||||
// DeviceIDs is a sortable slice of DeviceID
|
||||
type DeviceIDs []DeviceID
|
||||
|
||||
func (l DeviceIDs) Len() int {
|
||||
return len(l)
|
||||
}
|
||||
|
||||
func (l DeviceIDs) Less(a, b int) bool {
|
||||
return l[a].Compare(l[b]) == -1
|
||||
}
|
||||
|
||||
func (l DeviceIDs) Swap(a, b int) {
|
||||
l[a], l[b] = l[b], l[a]
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"runtime"
|
||||
"sync"
|
||||
"testing"
|
||||
@@ -432,8 +432,8 @@ func testMarshal(t *testing.T, prefix string, m1, m2 message) bool {
|
||||
bs1, _ := json.MarshalIndent(m1, "", " ")
|
||||
bs2, _ := json.MarshalIndent(m2, "", " ")
|
||||
if !bytes.Equal(bs1, bs2) {
|
||||
ioutil.WriteFile(prefix+"-1.txt", bs1, 0644)
|
||||
ioutil.WriteFile(prefix+"-2.txt", bs2, 0644)
|
||||
os.WriteFile(prefix+"-1.txt", bs1, 0644)
|
||||
os.WriteFile(prefix+"-2.txt", bs2, 0644)
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
@@ -393,7 +392,7 @@ func (p *Process) Model(folder string) (Model, error) {
|
||||
}
|
||||
|
||||
func (p *Process) readResponse(resp *http.Response) ([]byte, error) {
|
||||
bs, err := ioutil.ReadAll(resp.Body)
|
||||
bs, err := io.ReadAll(resp.Body)
|
||||
resp.Body.Close()
|
||||
if err != nil {
|
||||
return bs, err
|
||||
|
||||
@@ -141,7 +141,17 @@ func (c *staticClient) connect(ctx context.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
conn := tls.Client(tcpConn, c.config)
|
||||
// Copy the TLS config and set the server name we're connecting to. In
|
||||
// many cases this will be an IP address, in which case it's a no-op. In
|
||||
// other cases it will be a hostname, which will cause the TLS stack to
|
||||
// send SNI.
|
||||
cfg := c.config
|
||||
if host, _, err := net.SplitHostPort(c.uri.Host); err == nil {
|
||||
cfg = cfg.Clone()
|
||||
cfg.ServerName = host
|
||||
}
|
||||
|
||||
conn := tls.Client(tcpConn, cfg)
|
||||
|
||||
if err := conn.SetDeadline(time.Now().Add(c.connectTimeout)); err != nil {
|
||||
conn.Close()
|
||||
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
@@ -376,7 +375,7 @@ func TestWalkSymlinkWindows(t *testing.T) {
|
||||
|
||||
func TestWalkRootSymlink(t *testing.T) {
|
||||
// Create a folder with a symlink in it
|
||||
tmp, err := ioutil.TempDir("", "")
|
||||
tmp, err := os.MkdirTemp("", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -709,7 +708,7 @@ func TestStopWalk(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestIssue4799(t *testing.T) {
|
||||
tmp, err := ioutil.TempDir("", "")
|
||||
tmp, err := os.MkdirTemp("", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -771,7 +770,7 @@ func TestRecurseInclude(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestIssue4841(t *testing.T) {
|
||||
tmp, err := ioutil.TempDir("", "")
|
||||
tmp, err := os.MkdirTemp("", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -19,11 +19,7 @@ import (
|
||||
"github.com/sasha-s/go-deadlock"
|
||||
)
|
||||
|
||||
type clock interface {
|
||||
Now() time.Time
|
||||
}
|
||||
|
||||
var defaultClock clock = (*standardClock)(nil)
|
||||
var timeNow = time.Now
|
||||
|
||||
type Mutex interface {
|
||||
Lock()
|
||||
@@ -86,7 +82,7 @@ func (h holder) String() string {
|
||||
if h.at == "" {
|
||||
return "not held"
|
||||
}
|
||||
return fmt.Sprintf("at %s goid: %d for %s", h.at, h.goid, defaultClock.Now().Sub(h.time))
|
||||
return fmt.Sprintf("at %s goid: %d for %s", h.at, h.goid, timeNow().Sub(h.time))
|
||||
}
|
||||
|
||||
type loggedMutex struct {
|
||||
@@ -101,7 +97,7 @@ func (m *loggedMutex) Lock() {
|
||||
|
||||
func (m *loggedMutex) Unlock() {
|
||||
currentHolder := m.holder.Load().(holder)
|
||||
duration := defaultClock.Now().Sub(currentHolder.time)
|
||||
duration := timeNow().Sub(currentHolder.time)
|
||||
if duration >= threshold {
|
||||
l.Debugf("Mutex held for %v. Locked at %s unlocked at %s", duration, currentHolder.at, getHolder().at)
|
||||
}
|
||||
@@ -125,7 +121,7 @@ type loggedRWMutex struct {
|
||||
}
|
||||
|
||||
func (m *loggedRWMutex) Lock() {
|
||||
start := defaultClock.Now()
|
||||
start := timeNow()
|
||||
|
||||
atomic.StoreInt32(&m.logUnlockers, 1)
|
||||
m.RWMutex.Lock()
|
||||
@@ -153,7 +149,7 @@ func (m *loggedRWMutex) Lock() {
|
||||
|
||||
func (m *loggedRWMutex) Unlock() {
|
||||
currentHolder := m.holder.Load().(holder)
|
||||
duration := defaultClock.Now().Sub(currentHolder.time)
|
||||
duration := timeNow().Sub(currentHolder.time)
|
||||
if duration >= threshold {
|
||||
l.Debugf("RWMutex held for %v. Locked at %s unlocked at %s", duration, currentHolder.at, getHolder().at)
|
||||
}
|
||||
@@ -205,9 +201,9 @@ type loggedWaitGroup struct {
|
||||
}
|
||||
|
||||
func (wg *loggedWaitGroup) Wait() {
|
||||
start := defaultClock.Now()
|
||||
start := timeNow()
|
||||
wg.WaitGroup.Wait()
|
||||
duration := defaultClock.Now().Sub(start)
|
||||
duration := timeNow().Sub(start)
|
||||
if duration >= threshold {
|
||||
l.Debugf("WaitGroup took %v at %s", duration, getHolder())
|
||||
}
|
||||
@@ -219,7 +215,7 @@ func getHolder() holder {
|
||||
return holder{
|
||||
at: fmt.Sprintf("%s:%d", file, line),
|
||||
goid: goid(),
|
||||
time: defaultClock.Now(),
|
||||
time: timeNow(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -300,9 +296,3 @@ func (w *TimeoutCondWaiter) Wait() bool {
|
||||
func (w *TimeoutCondWaiter) Stop() {
|
||||
w.timer.Stop()
|
||||
}
|
||||
|
||||
type standardClock struct{}
|
||||
|
||||
func (*standardClock) Now() time.Time {
|
||||
return time.Now()
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user