Compare commits

...

19 Commits

Author SHA1 Message Date
Jakob Borg
7c3b267645 meta: Add test for deprecated package ioutil (#8053) 2021-11-22 10:11:00 +01:00
tomasz1986
7161a99b04 gui: Allow text wrapping in remove folder/device modals (#7976)
Remove the embedded CSS that prevents the lines from wrapping, resulting
in device and folder names being hidden on small screens.
2021-11-22 09:41:43 +01:00
Jakob Borg
1754c93370 lib/config, lib/ignore: Write Windows line endings (fixes #7115) (#8052) 2021-11-22 09:38:24 +01:00
Jakob Borg
4b750b6dc3 all: Remove usage of deprecated io/ioutil (#7971)
As of Go 1.16 io/ioutil is deprecated. This replaces usage with the
corresponding functions in package os and package io.
2021-11-22 08:59:47 +01:00
greatroar
bf89bffb0b lib/config: Decouple VerifyConfiguration from Committer (#7939)
... and remove 8/10 implementations, which were no-ops. This saves code
and time copying configurations.
2021-11-22 08:45:29 +01:00
Jakob Borg
e2288fe441 lib/relay: Send SNI when the address is a host name (fixes #8014) (#8015) 2021-11-22 08:31:03 +01:00
greatroar
8265dac127 lib/nat: Fix race condition in Mapping (#8042)
The locking protocol in nat.Mapping was racy:

* Mapping.addressMap RLock'd, but then returned a map shared between
  caller and Mapping, so the lock didn't do anything.

* Operations inside Service.{verifyExistingMappings,acquireNewMappings}
  would lock the map for every update, but that means callers to
  Mapping.ExternalAddresses can be looping over the map while the
  Service methods are concurrently modifying it. When the Go runtime
  detects that happening, it panics.

* Mapping.expires was read and updated without locking.

The Service methods now lock the map once and release the lock only when
done.

Also, subscribers no longer get the added and removed addresses, because
none of them were using the information. This was changed for a previous
attempt to retain the fine-grained locking and not reverted because it
simplifies the code.
2021-11-22 08:29:44 +01:00
André Colomb
100870e142 cmd/syncthing: Implement generate as a subcommand with optional API credential setting (fixes #8021) (#8043)
Accept a subcommand as an alternative to the --generate option.  It
accepts a custom config directory through either the --home or
--config options, using the default location if neither is given.

Add the options --gui-user and --gui-password to "generate", but not
the "serve --generate" option form.  If either is given, an existing
config will not abort the command, but rather load, modify and save it
with the new credentials.  The password can be read from standard
input by passing only a single dash as argument.

Config modification is skipped if the value matches what's already in
the config.

* cmd/syncthing: Utilize lib/locations package in generate().
Instead of manually joining paths with "magic" file names, get them
from the centralized locations helper lib.

* cmd/syncthing: Simplify logging for --generate option.
Visible change: No more timestamp prefixes.
2021-11-18 22:57:59 +01:00
Jakob Borg
12fb7f2a0a lib/model: Correct "reverting folder" log entry 2021-11-17 12:52:10 +01:00
Jakob Borg
f1bf4d899a lib/model: Correct handling of fakefs cache
We looked under one cache key, then stored under another...
2021-11-17 12:52:10 +01:00
Simon Frei
591e4d8af1 gui, lib: Fix tracking deleted locally-changed on encrypted (fixes #7715) (#7726) 2021-11-10 09:46:21 +01:00
André Colomb
dec6f80d2b lib/config: Move the bcrypt password hashing to GUIConfiguration (#8028)
What hash is used to store the password should ideally be an
implementation detail, so that every user of the GUIConfiguration
object automatically agrees on how to handle it.  That is currently
distribututed over the confighandler.go and api_auth.go files, plus
tests.

Add the SetHasedPassword() / CompareHashedPassword() API to keep the
hashing method encapsulated.  Add a separate test for it and adjust
other users and tests.  Remove all deprecated imports of the bcrypt
package.
2021-11-08 13:32:04 +01:00
André Colomb
ec8a748514 lib/syncthing: Clean up / refactor LoadOrGenerateCertificate() utility function. (#8025)
LoadOrGenerateCertificate() takes two file path arguments, but then
uses the locations package to determine the actual path.  Fix that
with a minimally invasive change, by using the arguments instead.
Factor out GenerateCertificate().

The only caller of this function is cmd/syncthing, which passes the
same values, so this is technically a no-op.

* lib/tlsutil: Make storing generated certificate optional.  Avoid
  temporary cert and key files in tests, keep cert in memory.
2021-11-07 23:59:48 +01:00
greatroar
db15e52743 lib/api: http.Request.BasicAuth instead of custom code (#8039) 2021-11-06 12:38:08 +01:00
André Colomb
41bfb7a330 Normalize CLI options to always use two dashes. (#8037)
Consistently use double dashes and fix typos -conf, -data-dir and
-verify.

Applies also to tests running the syncthing binary for consistency.

* Fix mismatched option name --conf in cli subcommand.

According to the source code comments, the cli option flags should
mirror those from the serve subcommand where applicable.  That one is
actually called --config though.

* cli: Fix help text option placeholders.

The urfave/cli package uses the Value field of StringFlag to provide a
default value, not to name the placeholder.  That is instead done with
backticks around some part of the Usage field.

* cli: Add missing --data flag in subcommand help text.

The urfave/cli based option parsing uses a fake flags collection to
generate help texts matching the used global options.  But the --data
option was omitted from it, although it is definitely required when
using --config as well.  Note that it cannot just be ignored, as some
debug stuff actually uses the DB:

syncthing cli --data=/bar --config=/foo debug index dump
2021-11-04 08:42:55 +01:00
André Colomb
1c2e96a5ca gui: Display identicons for discovered device IDs. (#8022) 2021-10-29 20:23:41 +02:00
greatroar
28ff033da6 cmd/syncthing/cli: indexDumpSize doesn't need a heap (#8024) 2021-10-29 20:21:50 +02:00
greatroar
807a6b1022 lib/model: Optimize jobQueue performance and memory use (#8023)
By truncating time.Time to an int64 nanosecond count, we lose the
ability to precisely order timestamps before 1678 or after 2262, but we
gain (linux/amd64, Go 1.17.1):

name                      old time/op    new time/op    delta
JobQueuePushPopDone10k-8    2.85ms ± 5%    2.29ms ± 2%  -19.80%  (p=0.000 n=20+18)
JobQueueBump-8              34.0µs ± 1%    29.8µs ± 1%  -12.35%  (p=0.000 n=19+19)

name                      old alloc/op   new alloc/op   delta
JobQueuePushPopDone10k-8    2.56MB ± 0%    1.76MB ± 0%  -31.31%  (p=0.000 n=18+13)

name                      old allocs/op  new allocs/op  delta
JobQueuePushPopDone10k-8      23.0 ± 0%      23.0 ± 0%     ~     (all equal)

Results for BenchmarkJobQueueBump are with the fixed version, which no
longer depends on b.N for the amount of work performed. rand.Rand.Intn
is cheap at ~10ns per iteration.
2021-10-29 20:20:46 +02:00
Tomasz Wilczyński
296cc1bca2 lib/model: Limit the number of default hashers on Android (ref #2220)
Like Windows and Mac, Android is also an interactive operating system.
On top of that, it usually runs on much slower hardware than the other
two. Because of that, it makes sense to limit the number of hashes used
by default there too.

Signed-off-by: Tomasz Wilczyński <twilczynski@naver.com>
2021-10-29 10:06:52 +02:00
115 changed files with 1058 additions and 763 deletions

View File

@@ -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))

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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())

View File

@@ -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)

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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:

View File

@@ -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)

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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
}

View 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"`
}

View File

@@ -7,8 +7,8 @@
//go:build !windows
// +build !windows
package main
package cmdutil
type buildServeOptions struct {
type buildCommonOptions struct {
HideConsole bool `hidden:""`
}

View File

@@ -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"`
}

View File

@@ -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

View File

@@ -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
}

View File

@@ -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)
}

View 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
}

View File

@@ -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()

View File

@@ -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)
}

View File

@@ -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 {

View File

@@ -455,12 +455,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>&nbsp;<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>&nbsp;<span translate>Folder Type</span></th>
<td class="text-right">

View File

@@ -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="

View File

@@ -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>&nbsp;&nbsp;{{id}}
</a>
</li>
</ul>
</p>
<p class="help-block">

View File

@@ -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()">

View File

@@ -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.

View File

@@ -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)

View File

@@ -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 {

View File

@@ -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")
}

View File

@@ -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
}

View File

@@ -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")

View File

@@ -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 {

View File

@@ -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).

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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 {

View File

@@ -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

View File

@@ -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
}

View File

@@ -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()

View File

@@ -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 {

View File

@@ -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.

View File

@@ -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()

View File

@@ -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

View File

@@ -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
}

View File

@@ -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")

View File

@@ -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()

View File

@@ -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)
}

View File

@@ -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))
}

View File

@@ -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

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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)

View File

@@ -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 {

View File

@@ -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")
}
}

View File

@@ -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",

View File

@@ -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})
}

View File

@@ -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)
}

View File

@@ -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{

View File

@@ -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 {

View File

@@ -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)

View File

@@ -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)
}

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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()

View File

@@ -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] }

View File

@@ -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)
}
}

View File

@@ -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) {

View File

@@ -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))
}

View File

@@ -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

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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)

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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))

View File

@@ -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'},
}
}

View File

@@ -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")
}

View File

@@ -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
}

View File

@@ -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

View File

@@ -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()

View File

@@ -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)
}

View File

@@ -7,9 +7,7 @@
package syncthing
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
"time"
@@ -23,7 +21,7 @@ import (
func tempCfgFilename(t *testing.T) string {
t.Helper()
f, err := ioutil.TempFile("", "syncthing-testConfig-")
f, err := os.CreateTemp("", "syncthing-testConfig-")
if err != nil {
t.Fatal(err)
}
@@ -57,13 +55,7 @@ func TestShortIDCheck(t *testing.T) {
}
func TestStartupFail(t *testing.T) {
tmpDir, err := ioutil.TempDir("", "syncthing-TestStartupFail-")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmpDir)
cert, err := tlsutil.NewCertificate(filepath.Join(tmpDir, "cert"), filepath.Join(tmpDir, "key"), "syncthing", 365)
cert, err := tlsutil.NewCertificateInMemory("syncthing", 365)
if err != nil {
t.Fatal(err)
}

View File

@@ -10,7 +10,6 @@ import (
"crypto/tls"
"fmt"
"io"
"io/ioutil"
"os"
"github.com/pkg/errors"
@@ -24,23 +23,42 @@ import (
"github.com/syncthing/syncthing/lib/tlsutil"
)
func LoadOrGenerateCertificate(certFile, keyFile string) (tls.Certificate, error) {
cert, err := tls.LoadX509KeyPair(
locations.Get(locations.CertFile),
locations.Get(locations.KeyFile),
)
func EnsureDir(dir string, mode fs.FileMode) error {
fs := fs.NewFilesystem(fs.FilesystemTypeBasic, dir)
err := fs.MkdirAll(".", mode)
if err != nil {
l.Infof("Generating ECDSA key and certificate for %s...", tlsDefaultCommonName)
return tlsutil.NewCertificate(
locations.Get(locations.CertFile),
locations.Get(locations.KeyFile),
tlsDefaultCommonName,
deviceCertLifetimeDays,
)
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 LoadOrGenerateCertificate(certFile, keyFile string) (tls.Certificate, error) {
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
return GenerateCertificate(certFile, keyFile)
}
return cert, nil
}
func GenerateCertificate(certFile, keyFile string) (tls.Certificate, error) {
l.Infof("Generating ECDSA key and certificate for %s...", tlsDefaultCommonName)
return tlsutil.NewCertificate(certFile, keyFile, tlsDefaultCommonName, deviceCertLifetimeDays)
}
func DefaultConfig(path string, myID protocol.DeviceID, evLogger events.Logger, noDefaultFolder bool) (config.Wrapper, error) {
newCfg, err := config.NewWithFreePorts(myID)
if err != nil {
@@ -90,7 +108,7 @@ func LoadConfigAtStartup(path string, cert tls.Certificate, evLogger events.Logg
l.Infof("Now, THAT's what we call a config from the future! Don't worry. As long as you hit that wire with the connecting hook at precisely eighty-eight miles per hour the instant the lightning strikes the tower... everything will be fine.")
}
if originalVersion > config.CurrentVersion && !allowNewerConfig {
return nil, fmt.Errorf("config file version (%d) is newer than supported version (%d). If this is expected, use -allow-newer-config to override.", originalVersion, config.CurrentVersion)
return nil, fmt.Errorf("config file version (%d) is newer than supported version (%d). If this is expected, use --allow-newer-config to override.", originalVersion, config.CurrentVersion)
}
err = archiveAndSaveConfig(cfg, originalVersion)
if err != nil {
@@ -114,12 +132,12 @@ func archiveAndSaveConfig(cfg config.Wrapper, originalVersion int) error {
}
func copyFile(src, dst string) error {
bs, err := ioutil.ReadFile(src)
bs, err := os.ReadFile(src)
if err != nil {
return err
}
if err := ioutil.WriteFile(dst, bs, 0600); err != nil {
if err := os.WriteFile(dst, bs, 0600); err != nil {
// Attempt to clean up
os.Remove(dst)
return err

View File

@@ -86,11 +86,11 @@ func SecureDefaultWithTLS12() *tls.Config {
}
}
// NewCertificate generates and returns a new TLS certificate.
func NewCertificate(certFile, keyFile, commonName string, lifetimeDays int) (tls.Certificate, error) {
// generateCertificate generates a PEM formatted key pair and self-signed certificate in memory.
func generateCertificate(commonName string, lifetimeDays int) (*pem.Block, *pem.Block, error) {
priv, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
if err != nil {
return tls.Certificate{}, errors.Wrap(err, "generate key")
return nil, nil, errors.Wrap(err, "generate key")
}
notBefore := time.Now().Truncate(24 * time.Hour)
@@ -117,19 +117,33 @@ func NewCertificate(certFile, keyFile, commonName string, lifetimeDays int) (tls
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, publicKey(priv), priv)
if err != nil {
return tls.Certificate{}, errors.Wrap(err, "create cert")
return nil, nil, errors.Wrap(err, "create cert")
}
certBlock := &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}
keyBlock, err := pemBlockForKey(priv)
if err != nil {
return nil, nil, errors.Wrap(err, "save key")
}
return certBlock, keyBlock, nil
}
// NewCertificate generates and returns a new TLS certificate, saved to the given PEM files.
func NewCertificate(certFile, keyFile string, commonName string, lifetimeDays int) (tls.Certificate, error) {
certBlock, keyBlock, err := generateCertificate(commonName, lifetimeDays)
if err != nil {
return tls.Certificate{}, err
}
certOut, err := os.Create(certFile)
if err != nil {
return tls.Certificate{}, errors.Wrap(err, "save cert")
}
err = pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
if err != nil {
if err = pem.Encode(certOut, certBlock); err != nil {
return tls.Certificate{}, errors.Wrap(err, "save cert")
}
err = certOut.Close()
if err != nil {
if err = certOut.Close(); err != nil {
return tls.Certificate{}, errors.Wrap(err, "save cert")
}
@@ -137,22 +151,24 @@ func NewCertificate(certFile, keyFile, commonName string, lifetimeDays int) (tls
if err != nil {
return tls.Certificate{}, errors.Wrap(err, "save key")
}
block, err := pemBlockForKey(priv)
if err != nil {
if err = pem.Encode(keyOut, keyBlock); err != nil {
return tls.Certificate{}, errors.Wrap(err, "save key")
}
if err = keyOut.Close(); err != nil {
return tls.Certificate{}, errors.Wrap(err, "save key")
}
err = pem.Encode(keyOut, block)
return tls.X509KeyPair(pem.EncodeToMemory(certBlock), pem.EncodeToMemory(keyBlock))
}
// NewCertificateInMemory generates and returns a new TLS certificate, kept only in memory.
func NewCertificateInMemory(commonName string, lifetimeDays int) (tls.Certificate, error) {
certBlock, keyBlock, err := generateCertificate(commonName, lifetimeDays)
if err != nil {
return tls.Certificate{}, errors.Wrap(err, "save key")
}
err = keyOut.Close()
if err != nil {
return tls.Certificate{}, errors.Wrap(err, "save key")
return tls.Certificate{}, err
}
return tls.LoadX509KeyPair(certFile, keyFile)
return tls.X509KeyPair(pem.EncodeToMemory(certBlock), pem.EncodeToMemory(keyBlock))
}
type DowngradingListener struct {

View File

@@ -19,7 +19,6 @@ import (
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"path"
@@ -284,7 +283,7 @@ func readTarGz(archiveName, dir string, r io.Reader) (string, error) {
}
func readZip(archiveName, dir string, r io.Reader) (string, error) {
body, err := ioutil.ReadAll(r)
body, err := io.ReadAll(r)
if err != nil {
return "", err
}
@@ -357,7 +356,7 @@ func archiveFileVisitor(dir string, tempFile *string, signature *[]byte, archive
case "release.sig":
l.Debugf("found signature %s", archivePath)
*signature, err = ioutil.ReadAll(io.LimitReader(filedata, maxSignatureSize))
*signature, err = io.ReadAll(io.LimitReader(filedata, maxSignatureSize))
if err != nil {
return err
}
@@ -407,7 +406,7 @@ func verifyUpgrade(archiveName, tempName string, sig []byte) error {
func writeBinary(dir string, inFile io.Reader) (filename string, err error) {
// Write the binary to a temporary file.
outFile, err := ioutil.TempFile(dir, "syncthing")
outFile, err := os.CreateTemp(dir, "syncthing")
if err != nil {
return "", err
}

View File

@@ -38,7 +38,7 @@ import (
"context"
"encoding/xml"
"fmt"
"io/ioutil"
"io"
"net"
"net/http"
"net/url"
@@ -467,7 +467,7 @@ func soapRequest(ctx context.Context, url, service, function, message string) ([
return resp, err
}
resp, _ = ioutil.ReadAll(r.Body)
resp, _ = io.ReadAll(r.Body)
l.Debugf("SOAP Response: %s\n\n%s\n\n", r.Status, resp)
r.Body.Close()

View File

@@ -188,10 +188,6 @@ func (h *failureHandler) addReport(data FailureData, evTime time.Time) {
}
}
func (h *failureHandler) VerifyConfiguration(_, _ config.Configuration) error {
return nil
}
func (h *failureHandler) CommitConfiguration(from, to config.Configuration) bool {
if from.Options.CREnabled != to.Options.CREnabled || from.Options.CRURL != to.Options.CRURL {
h.optsChan <- to.Options

View File

@@ -390,10 +390,6 @@ func (s *Service) Serve(ctx context.Context) error {
}
}
func (s *Service) VerifyConfiguration(from, to config.Configuration) error {
return nil
}
func (s *Service) CommitConfiguration(from, to config.Configuration) bool {
if from.Options.URAccepted != to.Options.URAccepted || from.Options.URUniqueID != to.Options.URUniqueID || from.Options.URURL != to.Options.URURL {
select {

View File

@@ -7,7 +7,6 @@
package versioner
import (
"io/ioutil"
"os"
"path/filepath"
"runtime"
@@ -85,7 +84,7 @@ func prepForRemoval(t *testing.T, file string) {
if err := os.MkdirAll(filepath.Dir(file), 0755); err != nil {
t.Fatal(err)
}
if err := ioutil.WriteFile(file, []byte("hello\n"), 0644); err != nil {
if err := os.WriteFile(file, []byte("hello\n"), 0644); err != nil {
t.Fatal(err)
}
}

View File

@@ -7,8 +7,8 @@
package versioner
import (
"io/ioutil"
"math"
"os"
"path/filepath"
"testing"
"time"
@@ -55,7 +55,7 @@ func TestSimpleVersioningVersionCount(t *testing.T) {
t.Skip("Test takes some time, skipping.")
}
dir, err := ioutil.TempDir("", "")
dir, err := os.MkdirTemp("", "")
//defer os.RemoveAll(dir)
if err != nil {
t.Error(err)

View File

@@ -7,7 +7,7 @@
package versioner
import (
"io/ioutil"
"os"
"path/filepath"
"sort"
"strconv"
@@ -133,11 +133,11 @@ func TestCreateVersionPath(t *testing.T) {
)
// Create a test dir and file
tmpDir, err := ioutil.TempDir("", "")
tmpDir, err := os.MkdirTemp("", "")
if err != nil {
t.Fatal(err)
}
if err := ioutil.WriteFile(filepath.Join(tmpDir, archiveFile), []byte("sup"), 0644); err != nil {
if err := os.WriteFile(filepath.Join(tmpDir, archiveFile), []byte("sup"), 0644); err != nil {
t.Fatal(err)
}

View File

@@ -7,7 +7,8 @@
package versioner
import (
"io/ioutil"
"io"
"os"
"testing"
"time"
@@ -19,12 +20,12 @@ func TestTrashcanArchiveRestoreSwitcharoo(t *testing.T) {
// This tests that trashcan versioner restoration correctly archives existing file, because trashcan versioner
// files are untagged, archiving existing file to replace with a restored version technically should collide in
// in names.
tmpDir1, err := ioutil.TempDir("", "")
tmpDir1, err := os.MkdirTemp("", "")
if err != nil {
t.Fatal(err)
}
tmpDir2, err := ioutil.TempDir("", "")
tmpDir2, err := os.MkdirTemp("", "")
if err != nil {
t.Fatal(err)
}
@@ -105,7 +106,7 @@ func readFile(t *testing.T, filesystem fs.Filesystem, name string) string {
t.Fatal(err)
}
defer fd.Close()
buf, err := ioutil.ReadAll(fd)
buf, err := io.ReadAll(fd)
if err != nil {
t.Fatal(err)
}

View File

@@ -9,7 +9,6 @@ package versioner
import (
"context"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"testing"
@@ -56,7 +55,7 @@ func TestVersionerCleanOut(t *testing.T) {
oldTime := time.Now().Add(-8 * 24 * time.Hour)
for file, shouldRemove := range testcases {
os.MkdirAll(filepath.Dir(file), 0777)
if err := ioutil.WriteFile(file, []byte("data"), 0644); err != nil {
if err := os.WriteFile(file, []byte("data"), 0644); err != nil {
t.Fatal(err)
}
if shouldRemove {

View File

@@ -419,10 +419,6 @@ func (a *aggregator) String() string {
return fmt.Sprintf("aggregator/%s:", a.folderCfg.Description())
}
func (a *aggregator) VerifyConfiguration(from, to config.Configuration) error {
return nil
}
func (a *aggregator) CommitConfiguration(from, to config.Configuration) bool {
for _, folderCfg := range to.Folders {
if folderCfg.ID == a.folderID {

View File

@@ -13,7 +13,6 @@ import (
"bytes"
"context"
"io"
"io/ioutil"
"os"
"reflect"
"testing"
@@ -22,7 +21,7 @@ import (
var payload = []byte("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz")
func TestFinder(t *testing.T) {
f, err := ioutil.TempFile("", "")
f, err := os.CreateTemp("", "")
if err != nil {
t.Error(err)
}

View File

@@ -0,0 +1,52 @@
// 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 meta
import (
"bytes"
"os"
"path/filepath"
"strings"
"testing"
)
// Checks for forbidden words in all .go files
func TestForbiddenWords(t *testing.T) {
checkDirs := []string{"../cmd", "../lib", "../test", "../script"}
forbiddenWords := []string{
`"io/ioutil"`, // deprecated and should not be imported
}
for _, dir := range checkDirs {
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if path == ".git" {
return filepath.SkipDir
}
if filepath.Ext(path) != ".go" || strings.HasSuffix(path, ".pb.go") {
return nil
}
bs, err := os.ReadFile(path)
if err != nil {
return nil
}
for _, word := range forbiddenWords {
if bytes.Contains(bs, []byte(word)) {
t.Errorf("%s: forbidden word %q", path, word)
}
}
return nil
})
if err != nil {
t.Fatal(err)
}
}
}

Some files were not shown because too many files have changed in this diff Show More