Compare commits

..
Author SHA1 Message Date
Gitea Actions 647c2fd5bd apply local Syncthing patches for v2.1.3 2026-08-06 06:18:02 +02:00
180 changed files with 471 additions and 1870 deletions
@@ -1,88 +0,0 @@
name: Build Infrastructure Images
on:
push:
branches:
- infrastructure
- infra-*
env:
GO_VERSION: "~1.26.0"
CGO_ENABLED: "0"
BUILD_USER: docker
BUILD_HOST: github.syncthing.net
permissions:
contents: read
packages: write
jobs:
docker-syncthing:
name: Build and push Docker images
if: github.repository_owner == 'syncthing'
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
pkg:
- stcrashreceiver
- strelaypoolsrv
- stupgrades
- ursrv
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- uses: actions/setup-go@v6
with:
go-version: ${{ env.GO_VERSION }}
check-latest: true
- name: Login to Docker Hub
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GHCR
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build binaries
run: |
for arch in arm64 amd64; do
go run build.go -goos linux -goarch "$arch" build ${{ matrix.pkg }}
mv ${{ matrix.pkg }} ${{ matrix.pkg }}-linux-"$arch"
done
- name: Set up QEMU
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
- name: Set Docker tags (all branches)
run: |
tags=docker.io/syncthing/${{ matrix.pkg }}:${{ github.sha }},ghcr.io/syncthing/infra/${{ matrix.pkg }}:${{ github.sha }}
echo "TAGS=$tags" >> $GITHUB_ENV
- name: Set Docker tags (latest)
if: github.ref == 'refs/heads/infrastructure'
run: |
tags=docker.io/syncthing/${{ matrix.pkg }}:latest,ghcr.io/syncthing/infra/${{ matrix.pkg }}:latest,${{ env.TAGS }}
echo "TAGS=$tags" >> $GITHUB_ENV
- name: Build and push
uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25 # v5
with:
context: .
file: ./Dockerfile.${{ matrix.pkg }}
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ env.TAGS }}
labels: |
org.opencontainers.image.revision=${{ github.sha }}
File diff suppressed because it is too large Load Diff
-21
View File
@@ -1,21 +0,0 @@
name: Mirrors
on: [push, delete]
permissions:
contents: read
jobs:
codeberg:
name: Mirror to Codeberg
if: github.repository_owner == 'syncthing'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: yesolutions/mirror-action@662fce0eced8996f64d7fa264d76cddd84827f33 # master
with:
REMOTE: ssh://git@codeberg.org/${{ github.repository }}.git
GIT_SSH_PRIVATE_KEY: ${{ secrets.CODEBERG_PUSH_KEY }}
GIT_SSH_NO_VERIFY_HOST: "true"
-21
View File
@@ -1,21 +0,0 @@
name: Org membership recommendations
on:
workflow_dispatch:
schedule:
- cron: '0 0 1 * *'
jobs:
run-recommendation:
name: Check for a recommendation
if: github.repository_owner == 'syncthing'
runs-on: ubuntu-latest
steps:
- uses: docker://ghcr.io/calmh/github-org-members:latest
env:
GITHUB_ORGANISATION: syncthing
GITHUB_TOKEN: ${{ secrets.GOM_GITHUB_TOKEN }}
GOM_IGNORE_USERS: ${{ secrets.GOM_IGNORE_USERS }}
GOM_ALSO_REPOS: ${{ secrets.GOM_ALSO_REPOS }}
-28
View File
@@ -1,28 +0,0 @@
name: PR metadata
on:
pull_request_target:
types:
- opened
- reopened
- edited
- synchronize
permissions:
contents: read
pull-requests: write
jobs:
#
# Set labels on PRs, which are then used to categorise release notes
#
labels:
name: Set labels
if: github.repository_owner == 'syncthing'
runs-on: ubuntu-latest
steps:
- uses: srvaroa/labeler@9c29ad1ef33d169f9ef33c52722faf47a566bcf3 # v1
env:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
-60
View File
@@ -1,60 +0,0 @@
name: Release Syncthing
on:
push:
branches:
- release
- release-rc*
permissions:
contents: write
jobs:
create-release-tag:
name: Create release tag
if: github.repository_owner == 'syncthing'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
ref: ${{ github.ref }} # https://github.com/actions/checkout/issues/882
token: ${{ secrets.ACTIONS_GITHUB_TOKEN }}
- uses: actions/setup-go@v6
with:
go-version: stable
- name: Determine version to release
run: |
if [[ "$GITHUB_REF_NAME" == "release" ]] ; then
next=$(go run ./script/next-version.go)
else
next=$(go run ./script/next-version.go --pre)
fi
echo "NEXT=$next" >> $GITHUB_ENV
echo "Next version is $next"
prev=$(git describe --exclude "*-*" --abbrev=0)
echo "PREV=$prev" >> $GITHUB_ENV
echo "Previous version is $prev"
- name: Determine release notes
run: |
go run ./script/relnotes.go --new-ver "$NEXT" --branch "$GITHUB_REF_NAME" --prev-ver "$PREV" > notes.md
env:
GITHUB_TOKEN: ${{ secrets.ACTIONS_GITHUB_TOKEN }}
- name: Create and push tag
run: |
git config --global user.name 'Syncthing Release Automation'
git config --global user.email 'release@syncthing.net'
git tag -a -F notes.md --cleanup=whitespace "$NEXT"
git push origin "$NEXT"
- name: Trigger the build
uses: benc-uk/workflow-dispatch@7a027648b88c2413826b6ddd6c76114894dc5ec4 # v1
with:
workflow: build-syncthing.yaml
ref: refs/tags/${{ env.NEXT }}
token: ${{ secrets.ACTIONS_GITHUB_TOKEN }}
-25
View File
@@ -1,25 +0,0 @@
name: Trigger nightly build & release
on:
workflow_dispatch:
schedule:
# Run nightly build at 01:00 UTC
- cron: '00 01 * * *'
permissions:
contents: write
jobs:
trigger-nightly:
name: Push to release-nightly to trigger build
if: github.repository_owner == 'syncthing'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
token: ${{ secrets.ACTIONS_GITHUB_TOKEN }}
fetch-depth: 0
- run: |
git push origin main:release-nightly
@@ -1,31 +0,0 @@
name: Update translations and documentation
on:
workflow_dispatch:
schedule:
- cron: '42 3 * * 1'
permissions:
contents: write
jobs:
update_transifex_docs:
runs-on: ubuntu-latest
name: Update translations and documentation
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
token: ${{ secrets.ACTIONS_GITHUB_TOKEN }}
- uses: actions/setup-go@v6
with:
go-version: stable
- run: |
set -euo pipefail
git config --global user.name 'Syncthing Release Automation'
git config --global user.email 'release@syncthing.net'
bash build.sh translate
bash build.sh prerelease
git push
env:
WEBLATE_TOKEN: ${{ secrets.WEBLATE_TOKEN }}
+1 -1
View File
@@ -20,7 +20,7 @@ type event struct {
ID int `json:"id"` ID int `json:"id"`
Type string `json:"type"` Type string `json:"type"`
Time time.Time `json:"time"` Time time.Time `json:"time"`
Data map[string]any `json:"data"` Data map[string]interface{} `json:"data"`
} }
func main() { func main() {
+1
View File
@@ -60,6 +60,7 @@ func checkServers(deviceID protocol.DeviceID, servers ...string) {
t0 := time.Now() t0 := time.Now()
resc := make(chan checkResult) resc := make(chan checkResult)
for _, srv := range servers { for _, srv := range servers {
srv := srv
go func() { go func() {
res := checkServer(deviceID, srv) res := checkServer(deviceID, srv)
res.server = srv res.server = srv
+5 -2
View File
@@ -34,7 +34,7 @@ func generateFiles(dir string, files, maxexp int, srcname string) error {
return err return err
} }
for range files { for i := 0; i < files; i++ {
n := randomName() n := randomName()
if rand.Float64() < 0.05 { if rand.Float64() < 0.05 {
@@ -51,7 +51,10 @@ func generateFiles(dir string, files, maxexp int, srcname string) error {
p1 := filepath.Join(p0, n) p1 := filepath.Join(p0, n)
s := int64(1 << uint(rand.Intn(maxexp))) s := int64(1 << uint(rand.Intn(maxexp)))
a := min(int64(128*1024), s) a := int64(128 * 1024)
if a > s {
a = s
}
s += rand.Int63n(a) s += rand.Int63n(a)
if err := generateOneFile(fd, p1, s); err != nil { if err := generateOneFile(fd, p1, s); err != nil {
+2 -2
View File
@@ -138,7 +138,7 @@ func printProgress(prefix string, count *atomic.Int64) {
} }
} }
func saveCert(priv any, derBytes []byte) { func saveCert(priv interface{}, derBytes []byte) {
certOut, err := os.Create("cert.pem") certOut, err := os.Create("cert.pem")
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
@@ -179,7 +179,7 @@ func saveCert(priv any, derBytes []byte) {
} }
} }
func pemBlockForKey(priv any) (*pem.Block, error) { func pemBlockForKey(priv interface{}) (*pem.Block, error) {
switch k := priv.(type) { switch k := priv.(type) {
case *rsa.PrivateKey: case *rsa.PrivateKey:
return &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(k)}, nil return &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(k)}, nil
+1 -1
View File
@@ -203,7 +203,7 @@ func loadIgnorePatterns(path string) (*ignorePatterns, error) {
} }
var patterns []*regexp.Regexp var patterns []*regexp.Regexp
for line := range strings.SplitSeq(string(bs), "\n") { for _, line := range strings.Split(string(bs), "\n") {
line = strings.TrimSpace(line) line = strings.TrimSpace(line)
if line == "" { if line == "" {
continue continue
@@ -5,6 +5,7 @@
// You can obtain one at https://mozilla.org/MPL/2.0/. // You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build noassets //go:build noassets
// +build noassets
package auto package auto
+1 -1
View File
@@ -587,7 +587,7 @@ func loadRelays(file string, geoip *geoip.Provider) []*relay {
} }
var relays []*relay var relays []*relay
for line := range strings.SplitSeq(string(content), "\n") { for _, line := range strings.Split(string(content), "\n") {
if line == "" { if line == "" {
continue continue
} }
+1 -1
View File
@@ -17,7 +17,7 @@ import (
) )
func init() { func init() {
for i := range 10 { for i := 0; i < 10; i++ {
u := fmt.Sprintf("permanent%d", i) u := fmt.Sprintf("permanent%d", i)
permanentRelays = append(permanentRelays, &relay{URL: u}) permanentRelays = append(permanentRelays, &relay{URL: u})
} }
+1 -1
View File
@@ -188,7 +188,7 @@ func (p *proxy) ServeHTTP(w http.ResponseWriter, req *http.Request) {
w.WriteHeader(resp.StatusCode) w.WriteHeader(resp.StatusCode)
if strings.HasPrefix(ct, "application/json") { if strings.HasPrefix(ct, "application/json") {
// Special JSON handling; clean it up a bit. // Special JSON handling; clean it up a bit.
var v any var v interface{}
if err := json.NewDecoder(resp.Body).Decode(&v); err != nil { if err := json.NewDecoder(resp.Body).Decode(&v); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
+4 -1
View File
@@ -402,7 +402,10 @@ func (s *apiSrv) certificateBytes(req *http.Request) ([]byte, error) {
b.WriteByte('\n') b.WriteByte('\n')
for i := 0; i < len(cert); i += 64 { for i := 0; i < len(cert); i += 64 {
end := min(i+64, len(cert)) end := i + 64
if end > len(cert) {
end = len(cert)
}
b.WriteString(cert[i:end]) b.WriteString(cert[i:end])
b.WriteByte('\n') b.WriteByte('\n')
} }
+8 -3
View File
@@ -7,6 +7,7 @@
package main package main
import ( import (
"context"
"crypto/tls" "crypto/tls"
"fmt" "fmt"
"io" "io"
@@ -119,7 +120,7 @@ func TestRetryAfterSHistogram(t *testing.T) {
numBuckets := (notFoundRetryUnknownMaxSeconds + bucketSize - 1) / bucketSize numBuckets := (notFoundRetryUnknownMaxSeconds + bucketSize - 1) / bucketSize
buckets := make([]int, numBuckets) buckets := make([]int, numBuckets)
for range n { for i := 0; i < n; i++ {
v := tracker.retryAfterS() v := tracker.retryAfterS()
if v < notFoundRetryUnknownMinSeconds || v > notFoundRetryUnknownMaxSeconds { if v < notFoundRetryUnknownMinSeconds || v > notFoundRetryUnknownMaxSeconds {
t.Fatalf("retryAfterS() = %d, out of range [%d, %d]", v, notFoundRetryUnknownMinSeconds, notFoundRetryUnknownMaxSeconds) t.Fatalf("retryAfterS() = %d, out of range [%d, %d]", v, notFoundRetryUnknownMinSeconds, notFoundRetryUnknownMaxSeconds)
@@ -141,7 +142,10 @@ func TestRetryAfterSHistogram(t *testing.T) {
barWidth := 60 barWidth := 60
for i, c := range buckets { for i, c := range buckets {
lo := i*bucketSize + 1 lo := i*bucketSize + 1
hi := min((i+1)*bucketSize, notFoundRetryUnknownMaxSeconds) hi := (i + 1) * bucketSize
if hi > notFoundRetryUnknownMaxSeconds {
hi = notFoundRetryUnknownMaxSeconds
}
bar := "" bar := ""
if maxCount > 0 { if maxCount > 0 {
bar = strings.Repeat("#", c*barWidth/maxCount) bar = strings.Repeat("#", c*barWidth/maxCount)
@@ -152,7 +156,8 @@ func TestRetryAfterSHistogram(t *testing.T) {
func BenchmarkAPIRequests(b *testing.B) { func BenchmarkAPIRequests(b *testing.B) {
db := newInMemoryStore(b.TempDir(), 0, nil) db := newInMemoryStore(b.TempDir(), 0, nil)
ctx := b.Context() ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go db.Serve(ctx) go db.Serve(ctx)
api := newAPISrv("127.0.0.1:0", tls.Certificate{}, db, nil, true, true, 1000, 1000) api := newAPISrv("127.0.0.1:0", tls.Certificate{}, db, nil, true, true, 1000, 1000)
srv := httptest.NewServer(http.HandlerFunc(api.handler)) srv := httptest.NewServer(http.HandlerFunc(api.handler))
+4 -4
View File
@@ -18,7 +18,7 @@ import (
var ( var (
outboxesMut = sync.RWMutex{} outboxesMut = sync.RWMutex{}
outboxes = make(map[syncthingprotocol.DeviceID]chan any) outboxes = make(map[syncthingprotocol.DeviceID]chan interface{})
numConnections atomic.Int64 numConnections atomic.Int64
) )
@@ -97,9 +97,9 @@ func protocolConnectionHandler(tcpConn net.Conn, config *tls.Config, token strin
id := syncthingprotocol.NewDeviceID(certs[0].Raw) id := syncthingprotocol.NewDeviceID(certs[0].Raw)
messages := make(chan any) messages := make(chan interface{})
errors := make(chan error, 1) errors := make(chan error, 1)
outbox := make(chan any) outbox := make(chan interface{})
// Read messages from the connection and send them on the messages // Read messages from the connection and send them on the messages
// channel. When there is an error, send it on the error channel and // channel. When there is an error, send it on the error channel and
@@ -364,7 +364,7 @@ func sessionConnectionHandler(conn net.Conn) {
} }
} }
func messageReader(conn net.Conn, messages chan<- any, errors chan<- error) { func messageReader(conn net.Conn, messages chan<- interface{}, errors chan<- error) {
numConnections.Add(1) numConnections.Add(1)
defer numConnections.Add(-1) defer numConnections.Add(-1)
+4 -1
View File
@@ -330,7 +330,10 @@ func take(tokens int, ls ...*rate.Limiter) {
for tokens > 0 { for tokens > 0 {
// chunk is how many tokens we can consume at a time // chunk is how many tokens we can consume at a time
chunk := min(tokens, minBurst) chunk := tokens
if chunk > minBurst {
chunk = minBurst
}
// maxDelay is the longest delay mandated by any of the limiters for // maxDelay is the longest delay mandated by any of the limiters for
// the chosen chunk size. // the chosen chunk size.
+2 -2
View File
@@ -38,7 +38,7 @@ func statusService(addr string) {
func getStatus(w http.ResponseWriter, _ *http.Request) { func getStatus(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Origin", "*")
status := make(map[string]any) status := make(map[string]interface{})
sessionMut.Lock() sessionMut.Lock()
// This can potentially be double the number of pending sessions, as each session has two keys, one for each side. // This can potentially be double the number of pending sessions, as each session has two keys, one for each side.
@@ -67,7 +67,7 @@ func getStatus(w http.ResponseWriter, _ *http.Request) {
rc.rate(30*60/10) * 8 / 1000, rc.rate(30*60/10) * 8 / 1000,
rc.rate(60*60/10) * 8 / 1000, rc.rate(60*60/10) * 8 / 1000,
} }
status["options"] = map[string]any{ status["options"] = map[string]interface{}{
"network-timeout": networkTimeout / time.Second, "network-timeout": networkTimeout / time.Second,
"ping-interval": pingInterval / time.Second, "ping-interval": pingInterval / time.Second,
"message-timeout": messageTimeout / time.Second, "message-timeout": messageTimeout / time.Second,
+3 -3
View File
@@ -27,7 +27,7 @@ import (
type APIClient interface { type APIClient interface {
Get(url string) (*http.Response, error) Get(url string) (*http.Response, error)
Post(url, body string) (*http.Response, error) Post(url, body string) (*http.Response, error)
PutJSON(url string, o any) (*http.Response, error) PutJSON(url string, o interface{}) (*http.Response, error)
} }
type apiClient struct { type apiClient struct {
@@ -134,7 +134,7 @@ func (c *apiClient) RequestString(url, method, data string) (*http.Response, err
return c.Request(url, method, bytes.NewBufferString(data)) return c.Request(url, method, bytes.NewBufferString(data))
} }
func (c *apiClient) RequestJSON(url, method string, o any) (*http.Response, error) { func (c *apiClient) RequestJSON(url, method string, o interface{}) (*http.Response, error) {
data, err := json.Marshal(o) data, err := json.Marshal(o)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -150,7 +150,7 @@ func (c *apiClient) Post(url, body string) (*http.Response, error) {
return c.RequestString(url, "POST", body) return c.RequestString(url, "POST", body)
} }
func (c *apiClient) PutJSON(url string, o any) (*http.Response, error) { func (c *apiClient) PutJSON(url string, o interface{}) (*http.Response, error) {
return c.RequestJSON(url, "PUT", o) return c.RequestJSON(url, "PUT", o)
} }
+1 -1
View File
@@ -81,7 +81,7 @@ func (c *configCommand) Run(ctx Context, outerCtx *kong.Context) error {
app.Name = "syncthing cli config" app.Name = "syncthing cli config"
app.HelpName = "syncthing cli config" app.HelpName = "syncthing cli config"
app.Description = outerCtx.Selected().Help app.Description = outerCtx.Selected().Help
app.Metadata = map[string]any{ app.Metadata = map[string]interface{}{
"clientFactory": ctx.clientFactory, "clientFactory": ctx.clientFactory,
} }
app.CustomAppHelpTemplate = customAppHelpTemplate app.CustomAppHelpTemplate = customAppHelpTemplate
+2 -2
View File
@@ -112,7 +112,7 @@ func getConfig(c APIClient) (config.Configuration, error) {
return cfg, nil return cfg, nil
} }
func prettyPrintJSON(data any) error { func prettyPrintJSON(data interface{}) error {
enc := json.NewEncoder(os.Stdout) enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ") enc.SetIndent("", " ")
return enc.Encode(data) return enc.Encode(data)
@@ -123,7 +123,7 @@ func prettyPrintResponse(response *http.Response) error {
if err != nil { if err != nil {
return err return err
} }
var data any var data interface{}
if err := json.Unmarshal(bytes, &data); err != nil { if err := json.Unmarshal(bytes, &data); err != nil {
return err return err
} }
+1 -1
View File
@@ -125,7 +125,7 @@ func uploadPanicLog(ctx context.Context, urlBase, file string) error {
func filterLogLines(data []byte) []byte { func filterLogLines(data []byte) []byte {
filtered := data[:0] filtered := data[:0]
matched := false matched := false
for line := range bytes.SplitSeq(data, []byte("\n")) { for _, line := range bytes.Split(data, []byte("\n")) {
switch { switch {
case !matched && bytes.HasPrefix(line, []byte("Panic ")): case !matched && bytes.HasPrefix(line, []byte("Panic ")):
// This begins the panic trace, set the matched flag and append. // This begins the panic trace, set the matched flag and append.
+1
View File
@@ -5,6 +5,7 @@
// You can obtain one at https://mozilla.org/MPL/2.0/. // You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build !windows //go:build !windows
// +build !windows
package main package main
+35 -55
View File
@@ -313,6 +313,21 @@ func (c *serveCmd) Run() error {
return nil return nil
} }
func openGUI() error {
cfg, err := loadOrDefaultConfig()
if err != nil {
return err
}
if guiCfg := cfg.GUI(); guiCfg.Enabled {
if err := openURL(guiCfg.URL()); err != nil {
return err
}
} else {
slog.Error("Browser: GUI is currently disabled")
}
return nil
}
func logPackages() string { func logPackages() string {
packages := slogutil.PackageDescrs() packages := slogutil.PackageDescrs()
@@ -403,28 +418,6 @@ func upgradeViaRest() error {
} }
func (c *serveCmd) syncthingMain() { func (c *serveCmd) syncthingMain() {
// Ensure we are the only running instance
lf := flock.New(locations.Get(locations.LockFile))
locked, err := lf.TryLock()
switch {
case err != nil:
slog.Error("Failed to acquire lock", slogutil.Error(err))
os.Exit(svcutil.ExitError.AsInt())
case !locked && c.NoBrowser:
slog.Error("Failed to acquire lock: is another Syncthing instance already running?")
os.Exit(svcutil.ExitError.AsInt())
case !locked:
slog.Info("Seems to already be running, launching GUI instead (use --no-browser to prevent)")
cmd := browserCmd{Verify: true}
if err := cmd.Run(); err != nil {
slog.Error("Failed to open browser", slogutil.Error(err))
os.Exit(svcutil.ExitNoRestart.AsInt())
}
return
}
if c.DebugProfileBlock { if c.DebugProfileBlock {
startBlockProfiler() startBlockProfiler()
} }
@@ -443,7 +436,18 @@ func (c *serveCmd) syncthingMain() {
) )
if err != nil { if err != nil {
slog.Error("Failed to load/generate certificate", slogutil.Error(err)) slog.Error("Failed to load/generate certificate", slogutil.Error(err))
os.Exit(svcutil.ExitError.AsInt()) os.Exit(1)
}
// Ensure we are the only running instance
lf := flock.New(locations.Get(locations.LockFile))
locked, err := lf.TryLock()
if err != nil {
slog.Error("Failed to acquire lock", slogutil.Error(err))
os.Exit(1)
} else if !locked {
slog.Error("Failed to acquire lock: is another Syncthing instance already running?")
os.Exit(1)
} }
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
@@ -498,13 +502,13 @@ func (c *serveCmd) syncthingMain() {
if err := syncthing.TryMigrateDatabase(ctx, c.DBDeleteRetentionInterval); err != nil { if err := syncthing.TryMigrateDatabase(ctx, c.DBDeleteRetentionInterval); err != nil {
slog.Error("Failed to migrate old-style database", slogutil.Error(err)) slog.Error("Failed to migrate old-style database", slogutil.Error(err))
os.Exit(svcutil.ExitError.AsInt()) os.Exit(1)
} }
sdb, err := syncthing.OpenDatabase(locations.Get(locations.Database), c.DBDeleteRetentionInterval) sdb, err := syncthing.OpenDatabase(locations.Get(locations.Database), c.DBDeleteRetentionInterval)
if err != nil { if err != nil {
slog.Error("Error opening database", slogutil.Error(err)) slog.Error("Error opening database", slogutil.Error(err))
os.Exit(svcutil.ExitError.AsInt()) os.Exit(1)
} }
if c.DebugPerfStats { if c.DebugPerfStats {
@@ -911,7 +915,7 @@ func (u upgradeCmd) Run() error {
switch { switch {
case err != nil && !os.IsNotExist(err): case err != nil && !os.IsNotExist(err):
slog.Error("Failed to lock for upgrade", slogutil.Error(err)) slog.Error("Failed to lock for upgrade", slogutil.Error(err))
os.Exit(svcutil.ExitError.AsInt()) os.Exit(1)
case locked || os.IsNotExist(err): case locked || os.IsNotExist(err):
// We got the lock, or the config directory didn't exist, so we // We got the lock, or the config directory didn't exist, so we
// can do a direct upgrade // can do a direct upgrade
@@ -931,38 +935,14 @@ func (u upgradeCmd) Run() error {
return nil return nil
} }
type browserCmd struct { type browserCmd struct{}
Verify bool `help:"Verify that the GUI is reachable before launching browser"`
}
func (c browserCmd) Run() error { func (browserCmd) Run() error {
cfg, err := loadOrDefaultConfig() if err := openGUI(); err != nil {
if err != nil { slog.Error("Failed to open web UI", slogutil.Error(err))
return err
}
guiCfg := cfg.GUI()
if !guiCfg.Enabled {
slog.Error("Browser: GUI is currently disabled")
os.Exit(svcutil.ExitError.AsInt()) os.Exit(svcutil.ExitError.AsInt())
} }
url := guiCfg.URL() return nil
if c.Verify {
// Do an HTTP request to verify the GUI/API is up and available
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return err
}
_, err = http.DefaultClient.Do(req) //nolint:bodyclose // we're exiting in a millisecond
if err != nil {
slog.Error("GUI not available", slogutil.Error(err))
os.Exit(svcutil.ExitError.AsInt()) //nolint:gocritic // deferred cancel
}
}
return openURL(url)
} }
type debugCmd struct { type debugCmd struct {
+3 -8
View File
@@ -171,11 +171,10 @@ func (c *serveCmd) monitorMain() {
exiterr := &exec.ExitError{} exiterr := &exec.ExitError{}
if errors.As(err, &exiterr) { if errors.As(err, &exiterr) {
exitCode := exiterr.ExitCode() exitCode := exiterr.ExitCode()
switch { if stopped || c.NoRestart {
case stopped || c.NoRestart:
os.Exit(exitCode) os.Exit(exitCode)
}
case exitCode == svcutil.ExitUpgrade.AsInt(): if exitCode == svcutil.ExitUpgrade.AsInt() {
// Restart the monitor process to release the .old // Restart the monitor process to release the .old
// binary as part of the upgrade process. // binary as part of the upgrade process.
slog.Info("Restarting monitor...") slog.Info("Restarting monitor...")
@@ -183,10 +182,6 @@ func (c *serveCmd) monitorMain() {
slog.Error("Failed to restart monitor", slogutil.Error(err)) slog.Error("Failed to restart monitor", slogutil.Error(err))
} }
os.Exit(exitCode) os.Exit(exitCode)
case exitCode == svcutil.ExitNoRestart.AsInt():
// Requested to not restart the child
os.Exit(exitCode)
} }
} }
+1
View File
@@ -5,6 +5,7 @@
// You can obtain one at https://mozilla.org/MPL/2.0/. // You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build !windows //go:build !windows
// +build !windows
package main package main
+1
View File
@@ -5,6 +5,7 @@
// You can obtain one at https://mozilla.org/MPL/2.0/. // You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build windows //go:build windows
// +build windows
package main package main
+1
View File
@@ -5,6 +5,7 @@
// You can obtain one at https://mozilla.org/MPL/2.0/. // You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build !solaris && !windows //go:build !solaris && !windows
// +build !solaris,!windows
package main package main
+1
View File
@@ -5,6 +5,7 @@
// You can obtain one at https://mozilla.org/MPL/2.0/. // You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build solaris || windows //go:build solaris || windows
// +build solaris windows
package main package main
+1
View File
@@ -5,6 +5,7 @@
// You can obtain one at https://mozilla.org/MPL/2.0/. // You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build go1.7 //go:build go1.7
// +build go1.7
package main package main
-2
View File
@@ -125,8 +125,6 @@
"Discovery Status": "حالة الاكتشاف", "Discovery Status": "حالة الاكتشاف",
"Dismiss": "رفض", "Dismiss": "رفض",
"Do not add it to the ignore list, so this notification may recur.": "لا تقم بإضافته إلى قائمة التجاهل، لذلك قد يتكرر هذا الإشعار.", "Do not add it to the ignore list, so this notification may recur.": "لا تقم بإضافته إلى قائمة التجاهل، لذلك قد يتكرر هذا الإشعار.",
"Do not add it to the ignore list, so this notification will reappear if the device connects again.": "لا تقم بإضافته إلى قائمة التجاهل، حتى يتم اخطارك عند اعادة اتصال الجهاز.",
"Do not add it to the ignore list, so this notification will reappear if the device offering this folder connects again.": "لا تقم بإضافته إلى قائمة التجاهل، حتى يتم اخطارك عند اعادة اتصال الجهاز المزوّد لهذا المجلد.",
"Do not restore": "الغاء الاستعادة", "Do not restore": "الغاء الاستعادة",
"Do not restore all": "الغاء استعادة الكل", "Do not restore all": "الغاء استعادة الكل",
"Do you want to enable watching for changes for all your folders?": "هل تريد تفعيل مراقبة التغيرات على كل المجلدات؟", "Do you want to enable watching for changes for all your folders?": "هل تريد تفعيل مراقبة التغيرات على كل المجلدات؟",
-2
View File
@@ -125,8 +125,6 @@
"Discovery Status": "Discovery Status", "Discovery Status": "Discovery Status",
"Dismiss": "Dismiss", "Dismiss": "Dismiss",
"Do not add it to the ignore list, so this notification may recur.": "Do not add it to the ignore list, so this notification may recur.", "Do not add it to the ignore list, so this notification may recur.": "Do not add it to the ignore list, so this notification may recur.",
"Do not add it to the ignore list, so this notification will reappear if the device connects again.": "Do not add it to the ignore list, so this notification will reappear if the device connects again.",
"Do not add it to the ignore list, so this notification will reappear if the device offering this folder connects again.": "Do not add it to the ignore list, so this notification will reappear if the device offering this folder connects again.",
"Do not restore": "Do not restore", "Do not restore": "Do not restore",
"Do not restore all": "Do not restore all", "Do not restore all": "Do not restore all",
"Do you want to enable watching for changes for all your folders?": "Do you want to enable watching for changes for all your folders?", "Do you want to enable watching for changes for all your folders?": "Do you want to enable watching for changes for all your folders?",
-3
View File
@@ -125,8 +125,6 @@
"Discovery Status": "탐지 현황", "Discovery Status": "탐지 현황",
"Dismiss": "나중에", "Dismiss": "나중에",
"Do not add it to the ignore list, so this notification may recur.": "무시 항목에 추가되지 않으니 이 알림이 다시 표시될 수 있습니다.", "Do not add it to the ignore list, so this notification may recur.": "무시 항목에 추가되지 않으니 이 알림이 다시 표시될 수 있습니다.",
"Do not add it to the ignore list, so this notification will reappear if the device connects again.": "무시 항목에 추가되지 않으니 해당 기기가 다시 연결되면 이 알림은 다시 표시될 것입니다.",
"Do not add it to the ignore list, so this notification will reappear if the device offering this folder connects again.": "무시 항목에 추가되지 않으니 이 폴더를 공유하는 기가가 연결되면 이 알림은 다시 표시될 것입니다.",
"Do not restore": "복구하지 않기", "Do not restore": "복구하지 않기",
"Do not restore all": "모두 복구하지 않기", "Do not restore all": "모두 복구하지 않기",
"Do you want to enable watching for changes for all your folders?": "변경 항목 감시를 모든 폴더에서 활성화하시겠습니까?", "Do you want to enable watching for changes for all your folders?": "변경 항목 감시를 모든 폴더에서 활성화하시겠습니까?",
@@ -397,7 +395,6 @@
"Staggered": "시차제", "Staggered": "시차제",
"Staggered File Versioning": "시차제 파일 버전 관리", "Staggered File Versioning": "시차제 파일 버전 관리",
"Start Browser": "브라우저 열기", "Start Browser": "브라우저 열기",
"Starting": "시작 중",
"Statistics": "통계", "Statistics": "통계",
"Stay logged in": "로그인 상태 유지", "Stay logged in": "로그인 상태 유지",
"Stopped": "중지됨", "Stopped": "중지됨",
+2 -2
View File
@@ -125,8 +125,8 @@
"Discovery Status": "Stan odnajdywania", "Discovery Status": "Stan odnajdywania",
"Dismiss": "Odrzuć", "Dismiss": "Odrzuć",
"Do not add it to the ignore list, so this notification may recur.": "Nie dodaje do listy ignorowanych, więc powiadomienie to może się powtórzyć.", "Do not add it to the ignore list, so this notification may recur.": "Nie dodaje do listy ignorowanych, więc powiadomienie to może się powtórzyć.",
"Do not add it to the ignore list, so this notification will reappear if the device connects again.": "Nie dodaje do listy ignorowanych, więc powiadomienie pojawi się ponownie, gdy urządzenie po raz kolejny się połączy.", "Do not add it to the ignore list, so this notification will reappear if the device connects again.": "Nie dodaje do listy ignorowanych, więc powiadomienie pojawi się ponownie, gdy urządzenie połączy się ponownie.",
"Do not add it to the ignore list, so this notification will reappear if the device offering this folder connects again.": "Nie dodaje do listy ignorowanych, więc powiadomienie pojawi się ponownie, gdy urządzenie oferujące ten folder po raz kolejny się połączy.", "Do not add it to the ignore list, so this notification will reappear if the device offering this folder connects again.": "Nie dodaje do listy ignorowanych, więc powiadomienie pojawi się ponownie, gdy urządzenie oferujące ten folder ponownie się połączy.",
"Do not restore": "Nie przywracaj", "Do not restore": "Nie przywracaj",
"Do not restore all": "Nie przywracaj wszystkich", "Do not restore all": "Nie przywracaj wszystkich",
"Do you want to enable watching for changes for all your folders?": "Czy chcesz włączyć obserwowanie zmian we wszystkich folderach?", "Do you want to enable watching for changes for all your folders?": "Czy chcesz włączyć obserwowanie zmian we wszystkich folderach?",
+1 -2
View File
@@ -137,7 +137,7 @@
"Edit Device Defaults": "Изменить настройки устройств", "Edit Device Defaults": "Изменить настройки устройств",
"Edit Folder": "Редактирование папки", "Edit Folder": "Редактирование папки",
"Edit Folder Defaults": "Изменить настройки папок", "Edit Folder Defaults": "Изменить настройки папок",
"Editing {%path%}.": "Редактирование {{path}}.", "Editing {%path%}.": "Правка {{path}}.",
"Enable Crash Reporting": "Включить отчёты о сбоях", "Enable Crash Reporting": "Включить отчёты о сбоях",
"Enable NAT traversal": "Использовать обход NAT", "Enable NAT traversal": "Использовать обход NAT",
"Enable Relaying": "Использовать ретрансляторы", "Enable Relaying": "Использовать ретрансляторы",
@@ -396,7 +396,6 @@
"Staggered": "Ступенчато", "Staggered": "Ступенчато",
"Staggered File Versioning": "Ступенчатое управление версиями файлов", "Staggered File Versioning": "Ступенчатое управление версиями файлов",
"Start Browser": "Запускать браузер", "Start Browser": "Запускать браузер",
"Starting": "Запуск",
"Statistics": "Статистика", "Statistics": "Статистика",
"Stay logged in": "Оставаться в системе", "Stay logged in": "Оставаться в системе",
"Stopped": "Остановлено", "Stopped": "Остановлено",
+3
View File
@@ -1017,6 +1017,9 @@
</div> <!-- /row --> </div> <!-- /row -->
</div> <!-- /container --> </div> <!-- /container -->
<footer class="container text-center text-muted small" aria-label="Custom build marker">
It syncs .stignore now!
</footer>
</div> <!-- /ng-cloak --> </div> <!-- /ng-cloak -->
<ng-include src="'syncthing/core/networkErrorDialogView.html'"></ng-include> <ng-include src="'syncthing/core/networkErrorDialogView.html'"></ng-include>
+1 -1
View File
@@ -313,7 +313,7 @@ nextScript:
// files on lines containing only a semicolon and execute them // files on lines containing only a semicolon and execute them
// separately. We require it on a separate line because there are // separately. We require it on a separate line because there are
// also statement-internal semicolons in the triggers. // also statement-internal semicolons in the triggers.
for stmt := range strings.SplitSeq(string(bs), "\n;") { for _, stmt := range strings.Split(string(bs), "\n;") {
if _, err := tx.Exec(s.expandTemplateVars(stmt)); err != nil { if _, err := tx.Exec(s.expandTemplateVars(stmt)); err != nil {
if strings.Contains(stmt, "syncthing:ignore-failure") { if strings.Contains(stmt, "syncthing:ignore-failure") {
// We're ok with this failing. Just note it. // We're ok with this failing. Just note it.
+4 -1
View File
@@ -474,7 +474,10 @@ func (s *folderDB) recalcGlobalForFile(txp *txPreparedStmts, file string) error
// The global version is the first one in the list that is not invalid, // The global version is the first one in the list that is not invalid,
// or just the first one in the list if all are invalid. // or just the first one in the list if all are invalid.
var global fileRow var global fileRow
globIdx := max(slices.IndexFunc(es, func(e fileRow) bool { return !e.IsInvalid() }), 0) globIdx := slices.IndexFunc(es, func(e fileRow) bool { return !e.IsInvalid() })
if globIdx < 0 {
globIdx = 0
}
global = es[globIdx] global = es[globIdx]
// We "have" the file if the position in the list of versions is at the // We "have" the file if the position in the list of versions is at the
+2 -2
View File
@@ -41,8 +41,8 @@ func SetDefaultLevel(level slog.Level) {
} }
func SetLevelOverrides(sttrace string) { func SetLevelOverrides(sttrace string) {
pkgs := strings.SplitSeq(sttrace, ",") pkgs := strings.Split(sttrace, ",")
for pkg := range pkgs { for _, pkg := range pkgs {
pkg = strings.TrimSpace(pkg) pkg = strings.TrimSpace(pkg)
if pkg == "" { if pkg == "" {
continue continue
+2 -2
View File
@@ -45,11 +45,11 @@ type adapter struct {
l *slog.Logger l *slog.Logger
} }
func (a adapter) Debugln(vals ...any) { func (a adapter) Debugln(vals ...interface{}) {
a.log(strings.TrimSpace(fmt.Sprintln(vals...)), slog.LevelDebug) a.log(strings.TrimSpace(fmt.Sprintln(vals...)), slog.LevelDebug)
} }
func (a adapter) Debugf(format string, vals ...any) { func (a adapter) Debugf(format string, vals ...interface{}) {
a.log(fmt.Sprintf(format, vals...), slog.LevelDebug) a.log(fmt.Sprintf(format, vals...), slog.LevelDebug)
} }
+20 -28
View File
@@ -202,7 +202,7 @@ func (s *service) getListener(guiCfg config.GUIConfiguration) (net.Listener, err
return listener, nil return listener, nil
} }
func sendJSON(w http.ResponseWriter, jsonObject any) { func sendJSON(w http.ResponseWriter, jsonObject interface{}) {
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
// Marshalling might fail, in which case we should return a 500 with the // Marshalling might fail, in which case we should return a 500 with the
// actual error. // actual error.
@@ -696,7 +696,7 @@ func (*service) getSystemPaths(w http.ResponseWriter, _ *http.Request) {
} }
func (s *service) getJSMetadata(w http.ResponseWriter, _ *http.Request) { func (s *service) getJSMetadata(w http.ResponseWriter, _ *http.Request) {
meta, _ := json.Marshal(map[string]any{ meta, _ := json.Marshal(map[string]interface{}{
"deviceID": s.id.String(), "deviceID": s.id.String(),
"deviceIDShort": s.id.Short().String(), "deviceIDShort": s.id.Short().String(),
"authenticated": true, "authenticated": true,
@@ -706,7 +706,7 @@ func (s *service) getJSMetadata(w http.ResponseWriter, _ *http.Request) {
} }
func (*service) getSystemVersion(w http.ResponseWriter, _ *http.Request) { func (*service) getSystemVersion(w http.ResponseWriter, _ *http.Request) {
sendJSON(w, map[string]any{ sendJSON(w, map[string]interface{}{
"version": build.Version, "version": build.Version,
"codename": build.Codename, "codename": build.Codename,
"longVersion": build.LongVersion, "longVersion": build.LongVersion,
@@ -838,7 +838,7 @@ func (s *service) getDBNeed(w http.ResponseWriter, r *http.Request) {
} }
// Convert the struct to a more loose structure, and inject the size. // Convert the struct to a more loose structure, and inject the size.
sendJSON(w, map[string]any{ sendJSON(w, map[string]interface{}{
"progress": toJsonFileInfoSlice(progress), "progress": toJsonFileInfoSlice(progress),
"queued": toJsonFileInfoSlice(queued), "queued": toJsonFileInfoSlice(queued),
"rest": toJsonFileInfoSlice(rest), "rest": toJsonFileInfoSlice(rest),
@@ -866,7 +866,7 @@ func (s *service) getDBRemoteNeed(w http.ResponseWriter, r *http.Request) {
return return
} }
sendJSON(w, map[string]any{ sendJSON(w, map[string]interface{}{
"files": toJsonFileInfoSlice(files), "files": toJsonFileInfoSlice(files),
"page": page, "page": page,
"perpage": perpage, "perpage": perpage,
@@ -886,7 +886,7 @@ func (s *service) getDBLocalChanged(w http.ResponseWriter, r *http.Request) {
return return
} }
sendJSON(w, map[string]any{ sendJSON(w, map[string]interface{}{
"files": toJsonFileInfoSlice(files), "files": toJsonFileInfoSlice(files),
"page": page, "page": page,
"perpage": perpage, "perpage": perpage,
@@ -951,7 +951,7 @@ func (s *service) getDBFile(w http.ResponseWriter, r *http.Request) {
return return
} }
sendJSON(w, map[string]any{ sendJSON(w, map[string]interface{}{
"global": jsonFileInfo(gf), "global": jsonFileInfo(gf),
"local": jsonFileInfo(lf), "local": jsonFileInfo(lf),
"availability": av, "availability": av,
@@ -967,7 +967,7 @@ func (s *service) getDebugFile(w http.ResponseWriter, r *http.Request) {
gf, _, _ := s.model.CurrentGlobalFile(folder, file) gf, _, _ := s.model.CurrentGlobalFile(folder, file)
av, _ := s.model.Availability(folder, protocol.FileInfo{Name: file}, protocol.BlockInfo{}) av, _ := s.model.Availability(folder, protocol.FileInfo{Name: file}, protocol.BlockInfo{})
sendJSON(w, map[string]any{ sendJSON(w, map[string]interface{}{
"global": jsonFileInfo(gf), "global": jsonFileInfo(gf),
"local": jsonFileInfo(lf), "local": jsonFileInfo(lf),
"availability": av, "availability": av,
@@ -1037,7 +1037,7 @@ func (s *service) getSystemStatus(w http.ResponseWriter, _ *http.Request) {
runtime.ReadMemStats(&m) runtime.ReadMemStats(&m)
tilde, _ := fs.ExpandTilde("~") tilde, _ := fs.ExpandTilde("~")
res := make(map[string]any) res := make(map[string]interface{})
res["myID"] = s.id.String() res["myID"] = s.id.String()
res["goroutines"] = runtime.NumGoroutine() res["goroutines"] = runtime.NumGoroutine()
res["alloc"] = m.Alloc res["alloc"] = m.Alloc
@@ -1184,7 +1184,10 @@ func (s *service) getSupportBundle(w http.ResponseWriter, r *http.Request) {
} }
// Metrics data as text // Metrics data as text
files = append(files, fileEntry{name: "metrics.txt", data: prometheusMetrics()}) var metricsBuf bytes.Buffer
wr := bufferedResponseWriter{Writer: &metricsBuf}
promhttp.Handler().ServeHTTP(wr, &http.Request{Method: http.MethodGet})
files = append(files, fileEntry{name: "metrics.txt", data: metricsBuf.Bytes()})
// Connection data as JSON // Connection data as JSON
connStats := s.model.ConnectionStats() connStats := s.model.ConnectionStats()
@@ -1255,16 +1258,6 @@ func (s *service) getSupportBundle(w http.ResponseWriter, r *http.Request) {
io.Copy(w, &zipFilesBuffer) io.Copy(w, &zipFilesBuffer)
} }
func prometheusMetrics() []byte {
var metricsBuf bytes.Buffer
wr := bufferedResponseWriter{Writer: &metricsBuf}
promhttp.Handler().ServeHTTP(wr, &http.Request{
Method: http.MethodGet,
URL: &url.URL{Scheme: "http://", Host: "localhost", Path: "/metrics"},
})
return metricsBuf.Bytes()
}
func (s *service) getSystemDiscovery(w http.ResponseWriter, _ *http.Request) { func (s *service) getSystemDiscovery(w http.ResponseWriter, _ *http.Request) {
devices := make(map[string]discover.CacheEntry) devices := make(map[string]discover.CacheEntry)
@@ -1309,7 +1302,7 @@ func (s *service) getDBIgnores(w http.ResponseWriter, r *http.Request) {
folder := qs.Get("folder") folder := qs.Get("folder")
lines, patterns, err := s.model.LoadIgnores(folder) lines, patterns, err := s.model.LoadIgnores(folder)
sendJSON(w, map[string]any{ sendJSON(w, map[string]interface{}{
"ignore": lines, "ignore": lines,
"expanded": patterns, "expanded": patterns,
"error": errorString(err), "error": errorString(err),
@@ -1418,7 +1411,7 @@ func (s *service) getSystemUpgrade(w http.ResponseWriter, _ *http.Request) {
httpError(w, err) httpError(w, err)
return return
} }
res := make(map[string]any) res := make(map[string]interface{})
res["running"] = build.Version res["running"] = build.Version
res["latest"] = rel.Tag res["latest"] = rel.Tag
res["newer"] = upgrade.CompareVersions(rel.Tag, build.Version) == upgrade.Newer res["newer"] = upgrade.CompareVersions(rel.Tag, build.Version) == upgrade.Newer
@@ -1446,7 +1439,7 @@ func (*service) getDeviceID(w http.ResponseWriter, r *http.Request) {
func (*service) getLang(w http.ResponseWriter, r *http.Request) { func (*service) getLang(w http.ResponseWriter, r *http.Request) {
lang := r.Header.Get("Accept-Language") lang := r.Header.Get("Accept-Language")
weights := make(map[string]float64) weights := make(map[string]float64)
for l := range strings.SplitSeq(lang, ",") { for _, l := range strings.Split(lang, ",") {
parts := strings.SplitN(l, ";", 2) parts := strings.SplitN(l, ";", 2)
code := strings.ToLower(strings.TrimSpace(parts[0])) code := strings.ToLower(strings.TrimSpace(parts[0]))
weights[code] = 1.0 weights[code] = 1.0
@@ -1645,7 +1638,7 @@ func (s *service) getFolderErrors(w http.ResponseWriter, r *http.Request) {
} }
} }
sendJSON(w, map[string]any{ sendJSON(w, map[string]interface{}{
"folder": folder, "folder": folder,
"errors": errors, "errors": errors,
"page": page, "page": page,
@@ -1777,8 +1770,8 @@ func (f jsonFileInfo) MarshalJSON() ([]byte, error) {
return json.Marshal(m) return json.Marshal(m)
} }
func fileIntfJSONMap(f protocol.FileInfo) map[string]any { func fileIntfJSONMap(f protocol.FileInfo) map[string]interface{} {
out := map[string]any{ out := map[string]interface{}{
"name": f.FileName(), "name": f.FileName(),
"type": f.FileType().String(), "type": f.FileType().String(),
"size": f.Size, "size": f.Size,
@@ -1953,8 +1946,7 @@ func sanitizedHostname(name string) (string, error) {
return r > unicode.MaxASCII || return r > unicode.MaxASCII ||
!unicode.IsLetter(r) && !unicode.IsNumber(r) && !unicode.IsLetter(r) && !unicode.IsNumber(r) &&
r != '.' && r != '-' r != '.' && r != '-'
})), })))
)
name, _, err := transform.String(t, name) name, _, err := transform.String(t, name)
if err != nil { if err != nil {
return "", err return "", err
+4 -1
View File
@@ -311,7 +311,10 @@ func authLDAP(username string, password string, cfg config.LDAPConfiguration) bo
func formatOptionalPercentS(template string, username string) string { func formatOptionalPercentS(template string, username string) string {
var replacements []any var replacements []any
nReps := max(strings.Count(template, "%s")-strings.Count(template, "%%s"), 0) nReps := strings.Count(template, "%s") - strings.Count(template, "%%s")
if nReps < 0 {
nReps = 0
}
for range nReps { for range nReps {
replacements = append(replacements, username) replacements = append(replacements, username)
} }
+2 -9
View File
@@ -433,6 +433,7 @@ func TestAPIServiceRequests(t *testing.T) {
} }
for _, tc := range cases { for _, tc := range cases {
tc := tc
t.Run(tc.URL, func(t *testing.T) { t.Run(tc.URL, func(t *testing.T) {
t.Parallel() t.Parallel()
testHTTPRequest(t, baseURL, tc, testAPIKey) testHTTPRequest(t, baseURL, tc, testAPIKey)
@@ -1722,7 +1723,7 @@ func TestConfigChanges(t *testing.T) {
return resp return resp
} }
mod := func(method, path string, data any) { mod := func(method, path string, data interface{}) {
t.Helper() t.Helper()
bs, err := json.Marshal(data) bs, err := json.Marshal(data)
if err != nil { if err != nil {
@@ -1829,14 +1830,6 @@ func TestSanitizedHostname(t *testing.T) {
} }
} }
func TestPrometheusMetrics(t *testing.T) {
// We should get some form of reasonable metrics response
bs := prometheusMetrics()
if !bytes.Contains(bs, []byte("TYPE go_info gauge")) {
t.Error("metrics should include go_info gauge")
}
}
// runningInContainer returns true if we are inside Docker or LXC. It might // runningInContainer returns true if we are inside Docker or LXC. It might
// be prone to false negatives if things change in the future, but likely // be prone to false negatives if things change in the future, but likely
// not false positives. // not false positives.
+42
View File
@@ -0,0 +1,42 @@
// Copyright (C) 2026 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 auto
import (
"compress/gzip"
"io"
"strings"
"testing"
)
const customBuildMarker = "It syncs .stignore now!"
func TestCustomBuildMarkerIsEmbedded(t *testing.T) {
asset, ok := Assets()["default/index.html"]
if !ok {
t.Fatal("default/index.html is missing from embedded GUI assets")
}
content := asset.Content
if asset.Gzipped {
reader, err := gzip.NewReader(strings.NewReader(asset.Content))
if err != nil {
t.Fatal(err)
}
defer reader.Close()
data, err := io.ReadAll(reader)
if err != nil {
t.Fatal(err)
}
content = string(data)
}
if !strings.Contains(content, customBuildMarker) {
t.Fatalf("embedded GUI assets do not contain custom build marker %q", customBuildMarker)
}
}
+1
View File
@@ -5,6 +5,7 @@
// You can obtain one at https://mozilla.org/MPL/2.0/. // You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build noassets //go:build noassets
// +build noassets
package auto package auto
+1 -1
View File
@@ -441,7 +441,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). // 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 any) error { func unmarshalTo(body io.ReadCloser, to interface{}) error {
bs, err := io.ReadAll(body) bs, err := io.ReadAll(body)
body.Close() body.Close()
if err != nil { if err != nil {
+1
View File
@@ -5,6 +5,7 @@
// You can obtain one at https://mozilla.org/MPL/2.0/. // You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build race //go:build race
// +build race
package build package build
+2 -2
View File
@@ -664,7 +664,7 @@ func (defaults *Defaults) prepare(myID protocol.DeviceID, existingDevices map[pr
defaults.Device.prepare(nil) defaults.Device.prepare(nil)
} }
func ensureZeroForNodefault(empty any, target any) { func ensureZeroForNodefault(empty interface{}, target interface{}) {
copyMatchingTag(empty, target, "nodefault", func(v string) bool { copyMatchingTag(empty, target, "nodefault", func(v string) bool {
if len(v) > 0 && v != "true" { if len(v) > 0 && v != "true" {
panic(fmt.Sprintf(`unexpected tag value: %s. expected untagged or "true"`, v)) panic(fmt.Sprintf(`unexpected tag value: %s. expected untagged or "true"`, v))
@@ -674,7 +674,7 @@ func ensureZeroForNodefault(empty any, target any) {
} }
// copyMatchingTag copies fields tagged tag:"value" from "from" struct onto "to" struct. // copyMatchingTag copies fields tagged tag:"value" from "from" struct onto "to" struct.
func copyMatchingTag(from any, to any, tag string, shouldCopy func(value string) bool) { func copyMatchingTag(from interface{}, to interface{}, tag string, shouldCopy func(value string) bool) {
fromStruct := reflect.ValueOf(from).Elem() fromStruct := reflect.ValueOf(from).Elem()
fromType := fromStruct.Type() fromType := fromStruct.Type()
+6 -6
View File
@@ -1158,8 +1158,8 @@ func TestInvalidDeviceIDRejected(t *testing.T) {
// Change the device ID of the first device to "invalid". Fast and loose // Change the device ID of the first device to "invalid". Fast and loose
// with the type assertions as we know what the JSON decoder returns. // with the type assertions as we know what the JSON decoder returns.
devs := cfg["devices"].([]any) devs := cfg["devices"].([]interface{})
dev0 := devs[0].(map[string]any) dev0 := devs[0].(map[string]interface{})
dev0["deviceID"] = tc.id dev0["deviceID"] = tc.id
devs[0] = dev0 devs[0] = dev0
@@ -1197,8 +1197,8 @@ func TestInvalidFolderIDRejected(t *testing.T) {
// Change the folder ID of the first folder to the empty string. // Change the folder ID of the first folder to the empty string.
// Fast and loose with the type assertions as we know what the JSON // Fast and loose with the type assertions as we know what the JSON
// decoder returns. // decoder returns.
devs := cfg["folders"].([]any) devs := cfg["folders"].([]interface{})
dev0 := devs[0].(map[string]any) dev0 := devs[0].(map[string]interface{})
dev0["id"] = tc.id dev0["id"] = tc.id
devs[0] = dev0 devs[0] = dev0
@@ -1324,7 +1324,7 @@ func adjustFolderConfiguration(cfg *FolderConfiguration, id, label string, fsTyp
// defaultConfigAsMap returns a valid default config as a JSON-decoded // defaultConfigAsMap returns a valid default config as a JSON-decoded
// map[string]interface{}. This is useful to override random elements and // map[string]interface{}. This is useful to override random elements and
// re-encode into JSON. // re-encode into JSON.
func defaultConfigAsMap() map[string]any { func defaultConfigAsMap() map[string]interface{} {
cfg := New(device1) cfg := New(device1)
dev := cfg.Defaults.Device.Copy() dev := cfg.Defaults.Device.Copy()
adjustDeviceConfiguration(&dev, device2, "name") adjustDeviceConfiguration(&dev, device2, "name")
@@ -1337,7 +1337,7 @@ func defaultConfigAsMap() map[string]any {
// can't happen // can't happen
panic(err) panic(err)
} }
var tmp map[string]any var tmp map[string]interface{}
if err := json.Unmarshal(bs, &tmp); err != nil { if err := json.Unmarshal(bs, &tmp); err != nil {
// can't happen // can't happen
panic(err) panic(err)
+3 -2
View File
@@ -9,7 +9,6 @@ package config
import ( import (
"encoding/json" "encoding/json"
"encoding/xml" "encoding/xml"
"maps"
"slices" "slices"
"strings" "strings"
@@ -46,7 +45,9 @@ type internalParam struct {
func (c VersioningConfiguration) Copy() VersioningConfiguration { func (c VersioningConfiguration) Copy() VersioningConfiguration {
cp := c cp := c
cp.Params = make(map[string]string, len(c.Params)) cp.Params = make(map[string]string, len(c.Params))
maps.Copy(cp.Params, c.Params) for k, v := range c.Params {
cp.Params[k] = v
}
return cp return cp
} }
+2 -2
View File
@@ -401,7 +401,7 @@ func TestConnectionEstablishment(t *testing.T) {
} }
} }
func withConnectionPair(b interface{ Fatal(...any) }, connUri string, h func(client, server internalConn)) { func withConnectionPair(b interface{ Fatal(...interface{}) }, connUri string, h func(client, server internalConn)) {
// Root of the service tree. // Root of the service tree.
supervisor := suture.New("main", suture.Spec{ supervisor := suture.New("main", suture.Spec{
PassThroughPanics: true, PassThroughPanics: true,
@@ -494,7 +494,7 @@ func withConnectionPair(b interface{ Fatal(...any) }, connUri string, h func(cli
_ = serverConn.Close() _ = serverConn.Close()
} }
func mustGetCert(b interface{ Fatal(...any) }) tls.Certificate { func mustGetCert(b interface{ Fatal(...interface{}) }) tls.Certificate {
cert, err := tlsutil.NewCertificateInMemory("bench", 10) cert, err := tlsutil.NewCertificateInMemory("bench", 10)
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
+2 -2
View File
@@ -54,7 +54,7 @@ func TestDialQueueSort(t *testing.T) {
var seen1, seen2 int var seen1, seen2 int
for range 100 { for i := 0; i < 100; i++ {
queue.Sort() queue.Sort()
res := shortDevices(queue) res := shortDevices(queue)
if reflect.DeepEqual(res, expected1) { if reflect.DeepEqual(res, expected1) {
@@ -90,7 +90,7 @@ func TestDialQueueSort(t *testing.T) {
var seen1, seen2 int var seen1, seen2 int
for range 100 { for i := 0; i < 100; i++ {
queue.Sort() queue.Sort()
res := shortDevices(queue) res := shortDevices(queue)
if reflect.DeepEqual(res, expected1) { if reflect.DeepEqual(res, expected1) {
+8 -4
View File
@@ -257,13 +257,17 @@ func (w *limitedWriter) Write(buf []byte) (int, error) {
// KiB up to the limiter burst size, aiming for about a write every // KiB up to the limiter burst size, aiming for about a write every
// 10ms. // 10ms.
singleWriteSize := int(w.waiter.Limit() / 100) // 10ms worth of data singleWriteSize := int(w.waiter.Limit() / 100) // 10ms worth of data
singleWriteSize = min( singleWriteSize = ((singleWriteSize / 1024) + 1) * 1024 // round up to the next kibibyte
// round up to the next kibibyte if singleWriteSize > limiterBurstSize {
((singleWriteSize/1024)+1)*1024, limiterBurstSize) singleWriteSize = limiterBurstSize
}
written := 0 written := 0
for written < len(buf) { for written < len(buf) {
toWrite := min(singleWriteSize, len(buf)-written) toWrite := singleWriteSize
if toWrite > len(buf)-written {
toWrite = len(buf) - written
}
w.take(toWrite) w.take(toWrite)
n, err := w.writer.Write(buf[written : written+toWrite]) n, err := w.writer.Write(buf[written : written+toWrite])
written += n written += n
+1
View File
@@ -5,6 +5,7 @@
// You can obtain one at https://mozilla.org/MPL/2.0/. // You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build go1.15 && !noquic //go:build go1.15 && !noquic
// +build go1.15,!noquic
package connections package connections
+1
View File
@@ -5,6 +5,7 @@
// You can obtain one at http://mozilla.org/MPL/2.0/. // You can obtain one at http://mozilla.org/MPL/2.0/.
//go:build !noquic //go:build !noquic
// +build !noquic
package connections package connections
+1
View File
@@ -5,6 +5,7 @@
// You can obtain one at http://mozilla.org/MPL/2.0/. // You can obtain one at http://mozilla.org/MPL/2.0/.
//go:build !noquic //go:build !noquic
// +build !noquic
package connections package connections
+1
View File
@@ -5,6 +5,7 @@
// You can obtain one at http://mozilla.org/MPL/2.0/. // You can obtain one at http://mozilla.org/MPL/2.0/.
//go:build noquic //go:build noquic
// +build noquic
package connections package connections
+6 -6
View File
@@ -18,23 +18,23 @@ import (
type Registry struct { type Registry struct {
mut sync.Mutex mut sync.Mutex
available map[string][]any available map[string][]interface{}
} }
func New() *Registry { func New() *Registry {
return &Registry{ return &Registry{
available: make(map[string][]any), available: make(map[string][]interface{}),
} }
} }
func (r *Registry) Register(scheme string, item any) { func (r *Registry) Register(scheme string, item interface{}) {
r.mut.Lock() r.mut.Lock()
defer r.mut.Unlock() defer r.mut.Unlock()
r.available[scheme] = append(r.available[scheme], item) r.available[scheme] = append(r.available[scheme], item)
} }
func (r *Registry) Unregister(scheme string, item any) { func (r *Registry) Unregister(scheme string, item interface{}) {
r.mut.Lock() r.mut.Lock()
defer r.mut.Unlock() defer r.mut.Unlock()
@@ -49,12 +49,12 @@ func (r *Registry) Unregister(scheme string, item any) {
// Get returns an item for a schema compatible with the given scheme. // Get returns an item for a schema compatible with the given scheme.
// If any item satisfies preferred, that has precedence over other items. // If any item satisfies preferred, that has precedence over other items.
func (r *Registry) Get(scheme string, preferred func(any) bool) any { func (r *Registry) Get(scheme string, preferred func(interface{}) bool) interface{} {
r.mut.Lock() r.mut.Lock()
defer r.mut.Unlock() defer r.mut.Unlock()
var ( var (
best any best interface{}
bestPref bool bestPref bool
bestScheme string bestScheme string
) )
+4 -4
View File
@@ -14,8 +14,8 @@ import (
func TestRegistry(t *testing.T) { func TestRegistry(t *testing.T) {
r := New() r := New()
want := func(i int) func(any) bool { want := func(i int) func(interface{}) bool {
return func(x any) bool { return x.(int) == i } return func(x interface{}) bool { return x.(int) == i }
} }
if res := r.Get("int", want(1)); res != nil { if res := r.Get("int", want(1)); res != nil {
@@ -73,7 +73,7 @@ func TestShortSchemeFirst(t *testing.T) {
r.Register("foobar", 1) r.Register("foobar", 1)
// If we don't care about the value, we should get the one with "foo". // If we don't care about the value, we should get the one with "foo".
res := r.Get("foo", func(any) bool { return false }) res := r.Get("foo", func(interface{}) bool { return false })
if res != 0 { if res != 0 {
t.Error("unexpected", res) t.Error("unexpected", res)
} }
@@ -89,7 +89,7 @@ func BenchmarkGet(b *testing.B) {
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
r.Get("tcp", func(x any) bool { r.Get("tcp", func(x interface{}) bool {
return x.(*net.TCPAddr).IP.IsUnspecified() return x.(*net.TCPAddr).IP.IsUnspecified()
}) })
} }
+4 -3
View File
@@ -19,7 +19,6 @@ import (
"fmt" "fmt"
"io" "io"
"log/slog" "log/slog"
"maps"
"math" "math"
"net" "net"
"net/url" "net/url"
@@ -818,7 +817,7 @@ func (s *service) createListener(factory listenerFactory, uri *url.URL) bool {
} }
func (s *service) logListenAddressesChangedEvent(l ListenerAddresses) { func (s *service) logListenAddressesChangedEvent(l ListenerAddresses) {
s.evLogger.Log(events.ListenAddressesChanged, map[string]any{ s.evLogger.Log(events.ListenAddressesChanged, map[string]interface{}{
"address": l.URI, "address": l.URI,
"lan": l.LANAddresses, "lan": l.LANAddresses,
"wan": l.WANAddresses, "wan": l.WANAddresses,
@@ -996,7 +995,9 @@ func newConnectionStatusHandler() connectionStatusHandler {
func (s *connectionStatusHandler) ConnectionStatus() map[string]ConnectionStatusEntry { func (s *connectionStatusHandler) ConnectionStatus() map[string]ConnectionStatusEntry {
result := make(map[string]ConnectionStatusEntry) result := make(map[string]ConnectionStatusEntry)
s.connectionStatusMut.RLock() s.connectionStatusMut.RLock()
maps.Copy(result, s.connectionStatus) for k, v := range s.connectionStatus {
result[k] = v
}
s.connectionStatusMut.RUnlock() s.connectionStatusMut.RUnlock()
return result return result
} }
+1
View File
@@ -5,6 +5,7 @@
// You can obtain one at https://mozilla.org/MPL/2.0/. // You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build !solaris && !windows //go:build !solaris && !windows
// +build !solaris,!windows
package dialer package dialer
+1
View File
@@ -5,6 +5,7 @@
// You can obtain one at https://mozilla.org/MPL/2.0/. // You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build solaris //go:build solaris
// +build solaris
package dialer package dialer
+1
View File
@@ -5,6 +5,7 @@
// You can obtain one at https://mozilla.org/MPL/2.0/. // You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build windows //go:build windows
// +build windows
package dialer package dialer
+1 -1
View File
@@ -111,7 +111,7 @@ func DialContextReusePortFunc(registry *registry.Registry) func(ctx context.Cont
return DialContext(ctx, network, addr) return DialContext(ctx, network, addr)
} }
localAddrInterface := registry.Get(network, func(addr any) bool { localAddrInterface := registry.Get(network, func(addr interface{}) bool {
return addr.(*net.TCPAddr).IP.IsUnspecified() return addr.(*net.TCPAddr).IP.IsUnspecified()
}) })
if localAddrInterface == nil { if localAddrInterface == nil {
+3 -2
View File
@@ -7,7 +7,6 @@
package discover package discover
import ( import (
"maps"
"sync" "sync"
"time" "time"
@@ -61,7 +60,9 @@ func (c *cache) Get(id protocol.DeviceID) (CacheEntry, bool) {
func (c *cache) Cache() map[protocol.DeviceID]CacheEntry { func (c *cache) Cache() map[protocol.DeviceID]CacheEntry {
c.mut.Lock() c.mut.Lock()
m := make(map[protocol.DeviceID]CacheEntry, len(c.entries)) m := make(map[protocol.DeviceID]CacheEntry, len(c.entries))
maps.Copy(m, c.entries) for k, v := range c.entries {
m[k] = v
}
c.mut.Unlock() c.mut.Unlock()
return m return m
} }
+1 -1
View File
@@ -294,7 +294,7 @@ func (c *localClient) registerDevice(src net.Addr, device *discoproto.Announce)
}) })
if isNewDevice { if isNewDevice {
c.evLogger.Log(events.DeviceDiscovered, map[string]any{ c.evLogger.Log(events.DeviceDiscovered, map[string]interface{}{
"device": id.String(), "device": id.String(),
"addrs": validAddresses, "addrs": validAddresses,
}) })
+4 -4
View File
@@ -235,7 +235,7 @@ const BufferSize = 64
type Logger interface { type Logger interface {
suture.Service suture.Service
Log(t EventType, data any) Log(t EventType, data interface{})
Subscribe(mask EventType) Subscription Subscribe(mask EventType) Subscription
} }
@@ -256,7 +256,7 @@ type Event struct {
GlobalID int `json:"globalID"` GlobalID int `json:"globalID"`
Time time.Time `json:"time"` Time time.Time `json:"time"`
Type EventType `json:"type"` Type EventType `json:"type"`
Data any `json:"data"` Data interface{} `json:"data"`
} }
type Subscription interface { type Subscription interface {
@@ -325,7 +325,7 @@ loop:
return nil return nil
} }
func (l *logger) Log(t EventType, data any) { func (l *logger) Log(t EventType, data interface{}) {
l.events <- Event{ l.events <- Event{
Time: time.Now(), // intentionally high precision Time: time.Now(), // intentionally high precision
Type: t, Type: t,
@@ -559,7 +559,7 @@ var NoopLogger Logger = &noopLogger{}
func (*noopLogger) Serve(_ context.Context) error { return nil } func (*noopLogger) Serve(_ context.Context) error { return nil }
func (*noopLogger) Log(_ EventType, _ any) {} func (*noopLogger) Log(_ EventType, _ interface{}) {}
func (*noopLogger) Subscribe(_ EventType) Subscription { func (*noopLogger) Subscribe(_ EventType) Subscription {
return &noopSubscription{} return &noopSubscription{}
+4 -4
View File
@@ -127,7 +127,7 @@ func TestBufferOverflow(t *testing.T) {
t0 := time.Now() t0 := time.Now()
const nEvents = BufferSize * 2 const nEvents = BufferSize * 2
for range nEvents { for i := 0; i < nEvents; i++ {
l.Log(DeviceConnected, "foo") l.Log(DeviceConnected, "foo")
} }
if d := time.Since(t0); d > 15*time.Second { if d := time.Since(t0); d > 15*time.Second {
@@ -237,7 +237,7 @@ func TestBufferedSub(t *testing.T) {
bs := NewBufferedSubscription(s, 10*BufferSize) bs := NewBufferedSubscription(s, 10*BufferSize)
go func() { go func() {
for i := range 10 * BufferSize { for i := 0; i < 10*BufferSize; i++ {
l.Log(DeviceConnected, fmt.Sprintf("event-%d", i)) l.Log(DeviceConnected, fmt.Sprintf("event-%d", i))
if i%30 == 0 { if i%30 == 0 {
// Give the buffer routine time to pick up the events // Give the buffer routine time to pick up the events
@@ -378,7 +378,7 @@ func TestUnsubscribeContention(t *testing.T) {
stopListeners := make(chan struct{}) stopListeners := make(chan struct{})
var listenerWg sync.WaitGroup var listenerWg sync.WaitGroup
for range listeners { for i := 0; i < listeners; i++ {
listenerWg.Go(func() { listenerWg.Go(func() {
s := l.Subscribe(AllEvents) s := l.Subscribe(AllEvents)
defer s.Unsubscribe() defer s.Unsubscribe()
@@ -400,7 +400,7 @@ func TestUnsubscribeContention(t *testing.T) {
stopSenders := make(chan struct{}) stopSenders := make(chan struct{})
defer close(stopSenders) defer close(stopSenders)
var senderWg sync.WaitGroup var senderWg sync.WaitGroup
for range senders { for i := 0; i < senders; i++ {
senderWg.Go(func() { senderWg.Go(func() {
t := time.NewTicker(time.Millisecond) t := time.NewTicker(time.Millisecond)
@@ -5,6 +5,7 @@
// You can obtain one at https://mozilla.org/MPL/2.0/. // You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build linux //go:build linux
// +build linux
package fs package fs
@@ -5,6 +5,7 @@
// You can obtain one at https://mozilla.org/MPL/2.0/. // You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build windows //go:build windows
// +build windows
package fs package fs
+1
View File
@@ -5,6 +5,7 @@
// You can obtain one at https://mozilla.org/MPL/2.0/. // You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build linux //go:build linux
// +build linux
package fs package fs
+1
View File
@@ -5,6 +5,7 @@
// You can obtain one at https://mozilla.org/MPL/2.0/. // You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build linux || solaris //go:build linux || solaris
// +build linux solaris
package fs package fs
+1
View File
@@ -5,6 +5,7 @@
// You can obtain one at https://mozilla.org/MPL/2.0/. // You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build !windows //go:build !windows
// +build !windows
package fs package fs
+1
View File
@@ -5,6 +5,7 @@
// You can obtain one at https://mozilla.org/MPL/2.0/. // You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build linux || android //go:build linux || android
// +build linux android
package fs package fs
+1
View File
@@ -5,6 +5,7 @@
// You can obtain one at https://mozilla.org/MPL/2.0/. // You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build !linux && !android && !windows //go:build !linux && !android && !windows
// +build !linux,!android,!windows
package fs package fs
+1
View File
@@ -5,6 +5,7 @@
// You can obtain one at https://mozilla.org/MPL/2.0/. // You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build windows //go:build windows
// +build windows
package fs package fs
+1
View File
@@ -5,6 +5,7 @@
// You can obtain one at https://mozilla.org/MPL/2.0/. // You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build !windows //go:build !windows
// +build !windows
package fs package fs
+1 -1
View File
@@ -580,7 +580,7 @@ func TestXattr(t *testing.T) {
// Create a set of random attributes that we will set and read back // Create a set of random attributes that we will set and read back
var attrs []protocol.Xattr var attrs []protocol.Xattr
for i := range 10 { for i := 0; i < 10; i++ {
key := fmt.Sprintf("user.test-%d", i) key := fmt.Sprintf("user.test-%d", i)
value := make([]byte, xattrSize()) value := make([]byte, xattrSize())
rand.Read(value) rand.Read(value)
+1
View File
@@ -5,6 +5,7 @@
// You can obtain one at https://mozilla.org/MPL/2.0/. // You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build !windows //go:build !windows
// +build !windows
package fs package fs
+1
View File
@@ -5,6 +5,7 @@
// You can obtain one at http://mozilla.org/MPL/2.0/. // You can obtain one at http://mozilla.org/MPL/2.0/.
//go:build linux //go:build linux
// +build linux
package fs package fs
+1
View File
@@ -5,6 +5,7 @@
// You can obtain one at http://mozilla.org/MPL/2.0/. // You can obtain one at http://mozilla.org/MPL/2.0/.
//go:build !linux //go:build !linux
// +build !linux
package fs package fs
@@ -5,6 +5,7 @@
// You can obtain one at http://mozilla.org/MPL/2.0/. // You can obtain one at http://mozilla.org/MPL/2.0/.
//go:build darwin && !kqueue && cgo && !ios //go:build darwin && !kqueue && cgo && !ios
// +build darwin,!kqueue,cgo,!ios
package fs package fs
+1
View File
@@ -5,6 +5,7 @@
// You can obtain one at http://mozilla.org/MPL/2.0/. // You can obtain one at http://mozilla.org/MPL/2.0/.
//go:build solaris && cgo //go:build solaris && cgo
// +build solaris,cgo
package fs package fs
@@ -5,6 +5,7 @@
// You can obtain one at http://mozilla.org/MPL/2.0/. // You can obtain one at http://mozilla.org/MPL/2.0/.
//go:build linux //go:build linux
// +build linux
package fs package fs
@@ -5,6 +5,7 @@
// You can obtain one at http://mozilla.org/MPL/2.0/. // You can obtain one at http://mozilla.org/MPL/2.0/.
//go:build dragonfly || freebsd || netbsd || openbsd || ios || kqueue //go:build dragonfly || freebsd || netbsd || openbsd || ios || kqueue
// +build dragonfly freebsd netbsd openbsd ios kqueue
package fs package fs
+1
View File
@@ -5,6 +5,7 @@
// You can obtain one at http://mozilla.org/MPL/2.0/. // You can obtain one at http://mozilla.org/MPL/2.0/.
//go:build !linux && !windows && !dragonfly && !freebsd && !netbsd && !openbsd && !solaris && !darwin && !cgo && !ios //go:build !linux && !windows && !dragonfly && !freebsd && !netbsd && !openbsd && !solaris && !darwin && !cgo && !ios
// +build !linux,!windows,!dragonfly,!freebsd,!netbsd,!openbsd,!solaris,!darwin,!cgo,!ios
// Catch all platforms that are not specifically handled to use the generic // Catch all platforms that are not specifically handled to use the generic
// event types. // event types.
@@ -5,6 +5,7 @@
// You can obtain one at http://mozilla.org/MPL/2.0/. // You can obtain one at http://mozilla.org/MPL/2.0/.
//go:build windows //go:build windows
// +build windows
package fs package fs
+1
View File
@@ -5,6 +5,7 @@
// You can obtain one at http://mozilla.org/MPL/2.0/. // You can obtain one at http://mozilla.org/MPL/2.0/.
//go:build !dragonfly && !freebsd && !netbsd && !openbsd && !kqueue && !ios //go:build !dragonfly && !freebsd && !netbsd && !openbsd && !kqueue && !ios
// +build !dragonfly,!freebsd,!netbsd,!openbsd,!kqueue,!ios
package fs package fs
+4 -2
View File
@@ -5,6 +5,7 @@
// You can obtain one at http://mozilla.org/MPL/2.0/. // You can obtain one at http://mozilla.org/MPL/2.0/.
//go:build (!solaris && !darwin) || (solaris && cgo) || (darwin && cgo) //go:build (!solaris && !darwin) || (solaris && cgo) || (darwin && cgo)
// +build !solaris,!darwin solaris,cgo darwin,cgo
package fs package fs
@@ -363,7 +364,8 @@ func TestWatchSymlinkedRoot(t *testing.T) {
linkedFs := NewFilesystem(FilesystemTypeBasic, filepath.Join(testFs.URI(), link)) linkedFs := NewFilesystem(FilesystemTypeBasic, filepath.Join(testFs.URI(), link))
ctx := t.Context() ctx, cancel := context.WithCancel(context.Background())
defer cancel()
if _, _, err := linkedFs.Watch(".", fakeMatcher{}, ctx, false); err != nil { if _, _, err := linkedFs.Watch(".", fakeMatcher{}, ctx, false); err != nil {
panic(err) panic(err)
} }
@@ -633,6 +635,6 @@ func (fakeEventInfo) Event() notify.Event {
return notify.Write return notify.Write
} }
func (fakeEventInfo) Sys() any { func (fakeEventInfo) Sys() interface{} {
return nil return nil
} }
+1
View File
@@ -5,6 +5,7 @@
// You can obtain one at http://mozilla.org/MPL/2.0/. // You can obtain one at http://mozilla.org/MPL/2.0/.
//go:build (solaris && !cgo) || (darwin && !cgo) || (darwin && kqueue) //go:build (solaris && !cgo) || (darwin && !cgo) || (darwin && kqueue)
// +build solaris,!cgo darwin,!cgo darwin,kqueue
package fs package fs
+1
View File
@@ -5,6 +5,7 @@
// You can obtain one at https://mozilla.org/MPL/2.0/. // You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build windows //go:build windows
// +build windows
package fs package fs
+1
View File
@@ -5,6 +5,7 @@
// You can obtain one at https://mozilla.org/MPL/2.0/. // You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build freebsd || netbsd //go:build freebsd || netbsd
// +build freebsd netbsd
package fs package fs
+1
View File
@@ -5,6 +5,7 @@
// You can obtain one at https://mozilla.org/MPL/2.0/. // You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build linux || darwin //go:build linux || darwin
// +build linux darwin
package fs package fs
+1
View File
@@ -5,6 +5,7 @@
// You can obtain one at https://mozilla.org/MPL/2.0/. // You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build !windows && !dragonfly && !illumos && !solaris && !openbsd //go:build !windows && !dragonfly && !illumos && !solaris && !openbsd
// +build !windows,!dragonfly,!illumos,!solaris,!openbsd
package fs package fs
+1
View File
@@ -5,6 +5,7 @@
// You can obtain one at https://mozilla.org/MPL/2.0/. // You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build windows || dragonfly || illumos || solaris || openbsd //go:build windows || dragonfly || illumos || solaris || openbsd
// +build windows dragonfly illumos solaris openbsd
package fs package fs
+1 -1
View File
@@ -1017,6 +1017,6 @@ func (f *fakeFileInfo) Group() int {
return f.gid return f.gid
} }
func (*fakeFileInfo) Sys() any { func (*fakeFileInfo) Sys() interface{} {
return nil return nil
} }

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