Compare commits

...
Author SHA1 Message Date
Jakob Borg ee275fee65 build(deps): update dependencies (#10740)
Signed-off-by: Jakob Borg <jakob@kastelo.net>
2026-06-12 08:59:50 +02:00
Jakob Borg 6606e6b4d3 fix(protocol): be more stringent about blocks in non-file entries (#10737)
Directories, but also symlinks, should not have blocks or a size.

Signed-off-by: Jakob Borg <jakob@kastelo.net>
2026-06-11 18:52:24 +02:00
Jakob Borg f6428af4c8 fix(protocol): always expect & validate block hash in requests (#10738)
Verify that block requests have a hash and that it's correct. This helps
prevent certain races and ensure that only expected data is ever
returned in response to a request.

(In Syncthing prior to 1.28.1 the block hash was omitted for encrypted
requests from trusted devices. This breaks compatibility with that
specific config on those versions.)

---------

Signed-off-by: Jakob Borg <jakob@kastelo.net>
2026-06-11 18:51:25 +02:00
Jakob Borg a5cbeeafea fix(fs, model): improve symlink resilience in file shortcut (#10739)
Ensure file was a file before the shortcut as well as after... (This was
implied when talking to a correct implementation, but not enforced.)

Make our file opening operations safe by default by ensuring the last
path component is not a symlink.

---------

Signed-off-by: Jakob Borg <jakob@kastelo.net>
2026-06-11 18:50:11 +02:00
Syncthing Release Automation 5dbf809a4c chore(gui, man, authors): update docs, translations, and contributors 2026-06-08 05:17:05 +00:00
e16b8013a6 chore(db, model): separate methods to drop a device vs its files (#10480)
Fixes #10469

---------

Signed-off-by: Simon Frei <freisim93@gmail.com>
Signed-off-by: Jakob Borg <jakob@kastelo.net>
Co-authored-by: Marcus B Spencer <marcus@marcusspencer.us>
Co-authored-by: Jakob Borg <jakob@kastelo.net>
2026-06-04 18:28:19 +00:00
Jakob Borg 6df85dc95c fix: let umask do the thing (#10723)
We had a few places where we had perhaps too much of an opinion on the
permissions on created files and directories, sometimes fuled by a
misconception about how permissions work in both Unix and Windows. Recap
on the ground rules:

- On all unixes, all file & directory creation (`Mkdir`, `MkdirAll`,
`Create`, `WriteFile`, `Open`) has the given permission bits filtered
via the user's umask. The proper permissions for us to use are in almost
all cases 0o666 for files and 0o777 for directories, strange as that may
look at the call site.
- On Windows, there is no umask but in turn all of the permission bits
except the user write bit are ignored. The absence of user write bit is
converted into the read only attribute. This means that what is proper
for Unix above is also proper for Windows.
- We make an exception when creating files for certificate keys and the
config / database directories, as those contain secrets we think should remain closed
even if the user generally collaborates with other users on the system.

(Also removal of a bugfixed copy of MkdirAll for Windows that hasn't
been necessary for a few years.)

---------

Signed-off-by: Jakob Borg <jakob@kastelo.net>
2026-06-03 10:54:04 +02:00
Jakob Borg f93306c819 fix(gui): properly escape filenames in fancytree
Signed-off-by: Jakob Borg <jakob@kastelo.net>
2026-06-02 16:54:21 +02:00
Jakob Borg 9cdff04877 fix(connections): do not report connection metrics for self (ref #10509) (#10724)
Makes the metrics easier to interpret.

Signed-off-by: Jakob Borg <jakob@kastelo.net>
2026-06-02 14:01:03 +02:00
ShabloneandElias 9bb5f9b4dd fix: on Windows don't allocate console if not opened inside one (#10726)
This change allows Syncthing to be launched from Explorer without
showing a console window, while preserving the existing command-line
behavior.
Previously, launching syncthing.exe from Explorer would always allocate
a console window, which could only be hidden later by using
`--no-console`. It was not possible to avoid console allocation entirely
without introducing other issues.

On Windows 24H2 and later a new application manifest allows us to
achieve it. See [console allocation
policy](https://learn.microsoft.com/en-us/windows/console/console-allocation-policy)
This manifest is built into a syso-file by `goversioninfo`, which is
already used to generate Windows resource files consumed by the Go
compiler.

**Note1:** On Windows 24H2 and later, no console is allocated when
Syncthing is launched from Explorer, even if `--no-console` is set to
`False`. It can still be used as a CLI tool as usual if you call it from
console.

**Note2:** The content of the manifest file may not be formatted. Even a
`newline` would break it.

### Testing

Tested on Windows 11 25H2: No console visible from explorer. CLI works
as usual.

Ref #8046, ref #10633, ref #10481, ref #10600

Signed-off-by: Elias <1elias.bauer@gmail.com>
Co-authored-by: Elias <1elias.bauer@gmail.com>
2026-06-02 14:00:38 +02:00
Syncthing Release Automation 8961f642c6 chore(gui, man, authors): update docs, translations, and contributors 2026-06-01 05:22:49 +00:00
ShabloneandElias d91f8849a2 chore(syncthing): open URLs via Windows API instead via cmd.exe (#10712)
### Purpose
On Windows replace `cmd.exe /C start` with direct `ShellExecute` API for
opening the webpage.

The previous implementation used `exec.Command("cmd.exe", "/C", "start
"+url)` which spawns two extra processes (cmd.exe → start). Launching
cmd.exe resulted in a shortly visible terminal.

Both
-`start`
-and another alternative `exec.Command("rundll32",
"url.dll,FileProtocolHandler", url).Start()`
are just wrappers for `ShellExecute`. So this implementation is even
more direct

### Testing

I executed the compiled syncthing.exe on Windows 11, both from explorer
and console. The webpage opened as expected.

### Screenshots

N/A.

### Documentation

N/A

## Authorship

Name: Elias @Shablone
Email: [1elias.bauer@gmail.com](mailto:1elias.bauer@gmail.com)

Signed-off-by: Elias <1elias.bauer@gmail.com>
Co-authored-by: Elias <1elias.bauer@gmail.com>
2026-05-25 21:37:10 +00:00
Jakob Borg 6be1ff8480 fix(protocol): handle zero-size requests (fixes #10709) (#10710)
- Allow zero-sized requests since they are sent by all current versions
of Syncthing.
- Stop sending zero-sized requests since that's stupid.

---------

Signed-off-by: Jakob Borg <jakob@kastelo.net>
2026-05-25 15:22:07 +02:00
Syncthing Release Automation 3ec73403c1 chore(gui, man, authors): update docs, translations, and contributors 2026-05-25 05:12:14 +00:00
mattn 8ca3cca0a0 chore: use path/filepath for local file system paths (#10705)
Build Syncthing / Gather common facts (push) Successful in 32s
Build Syncthing / Build and test (~1.25.0, macos-latest) (push) Has been cancelled
Build Syncthing / Build and test (~1.25.0, windows-latest) (push) Has been cancelled
Build Syncthing / Build and test (~1.26.0, macos-latest) (push) Has been cancelled
Build Syncthing / Build and test (~1.26.0, ubuntu-latest) (push) Has been cancelled
Build Syncthing / Build and test (~1.26.0, windows-latest) (push) Has been cancelled
Build Syncthing / Basic checks passed (push) Has been cancelled
Build Syncthing / Package for Windows (push) Has been cancelled
Build Syncthing / Build and test (~1.25.0, ubuntu-latest) (push) Has been cancelled
Build Syncthing / Codesign for Windows (push) Has been cancelled
Build Syncthing / Package for Linux (common) (push) Has been cancelled
Mirrors / Mirror to Codeberg (push) Has been cancelled
Build Syncthing / Package for Linux (other) (push) Has been cancelled
Build Syncthing / Package for Linux (mips) (push) Has been cancelled
Build Syncthing / Package for illumos (push) Has been cancelled
Build Syncthing / Package for macOS (push) Has been cancelled
Build Syncthing / Notarize for macOS (push) Has been cancelled
Build Syncthing / Package cross compiled (push) Has been cancelled
Build Syncthing / Package source code (push) Has been cancelled
Build Syncthing / Sign for upgrade (push) Has been cancelled
Build Syncthing / Package for Debian (push) Has been cancelled
Build Syncthing / Publish nightly build (push) Has been cancelled
Build Syncthing / Publish release files (push) Has been cancelled
Build Syncthing / Publish APT (push) Has been cancelled
Build Syncthing / Build and push Docker images (GHCR) (Dockerfile, syncthing, syncthing) (push) Has been cancelled
Build Syncthing / Build and push Docker images (GHCR) (Dockerfile.stdiscosrv, discosrv, stdiscosrv) (push) Has been cancelled
Build Syncthing / Build and push Docker images (GHCR) (Dockerfile.strelaysrv, relaysrv, strelaysrv) (push) Has been cancelled
Build Syncthing / Sync images to Docker hub (push) Has been cancelled
Build Syncthing / Run govulncheck (push) Has been cancelled
Build Syncthing / Run golangci-lint (push) Has been cancelled
Build Syncthing / Run meta checks (push) Has been cancelled
### Purpose

`path` is for slash-separated paths (URLs, BEP protocol); local file
system paths should use `path/filepath`. Fixed in
`cmd/stdiscosrv/database.go` (3 sites) and
`internal/db/sqlite/db_test.go` (1 site).

### Testing

`go build ./cmd/stdiscosrv/...` and `go vet` pass.

Signed-off-by: Yasuhiro Matsumoto <mattn.jp@gmail.com>
2026-05-23 22:31:33 +02:00
mattn 0c489f4ae2 fix(stcrashreceiver): close source loader responses on errors (#10704)
Fix a response body leak in `githubSourceCodeLoader.Load` where the body
was not closed when the HTTP status was non-200.

Signed-off-by: Yasuhiro Matsumoto <mattn.jp@gmail.com>
2026-05-23 13:50:03 +00:00
Jakob Borg deb1e5b38a Revert "build: temporarily disable illumos for release"
This reverts commit c0c401efeb.
2026-05-23 12:00:07 +02:00
Jakob Borg 49a2688caa Merge branch 'infrastructure'
* infrastructure:
  build: let infra containers builds fail individually
  chore(ur): move structs to reduce dependency chain
  chore(stcrashreceiver): add profiler on metrics port
  chore(stcrashreceiver): compact diskstore in-memory representation
  chore(stcrashreceiver): better source cache & metrics
  chore(stcrashreceiver): metrics on ignore matches
2026-05-23 09:36:25 +02:00
Jakob Borg 05b4f6abda build: let infra containers builds fail individually
Signed-off-by: Jakob Borg <jakob@kastelo.net>
2026-05-23 09:18:15 +02:00
Jakob Borg 9152d7fb2f chore(ur): move structs to reduce dependency chain
lib/ur brings in a lot of dependencies we don't need in e.g.
stcrashreceiver, who only needs the small failure reporting structs.
Make those part of the lean `contract` package instead.

Signed-off-by: Jakob Borg <jakob@kastelo.net>
2026-05-23 09:13:47 +02:00
Jakob Borg 4404b4dfb4 chore(stcrashreceiver): add profiler on metrics port
Signed-off-by: Jakob Borg <jakob@kastelo.net>
2026-05-23 08:51:34 +02:00
Jakob Borg b537090d91 chore(stcrashreceiver): compact diskstore in-memory representation
Signed-off-by: Jakob Borg <jakob@kastelo.net>
2026-05-23 08:51:34 +02:00
Jakob Borg 79423edbdf chore(stcrashreceiver): better source cache & metrics
Signed-off-by: Jakob Borg <jakob@kastelo.net>
2026-05-23 08:51:34 +02:00
Jakob Borg 33075974cb chore(stcrashreceiver): metrics on ignore matches
Signed-off-by: Jakob Borg <jakob@kastelo.net>
2026-05-23 08:51:32 +02:00
Jakob Borg 8a3a06f7ca build(deps): x/net for govulncheck (#10703)
Signed-off-by: Jakob Borg <jakob@kastelo.net>
2026-05-23 06:48:21 +00:00
Jakob Borg d0b35021c6 chore(syncthing): include runtime context in GC crashes (#10702)
The runtime prints a lot of context for crashes due to bad pointers etc,
which is required to understand the crash, but this context comes before
the `fatal error: ...` line. Currently those lines get filtered out and
not included in the crash report. This change modifies the criteria so
that we start collecting crash data also at a line that begins with
`runtime:`, and tweaks the parsing later to look for the specific
`panic:` or `fatal error:` which may come later as the subject.

---------

Signed-off-by: Jakob Borg <jakob@kastelo.net>
2026-05-23 08:40:43 +02:00
Jakob Borg 6322091462 fix(discover): only announce wildcard for TCP punching when listening on wildcard address (fixes #10503) (#10691)
If we aren't announcing e.g. tcp://0.0.0.0:22000 then also do not
announce tcp://0.0.0.0:0.

Signed-off-by: Jakob Borg <jakob@kastelo.net>
2026-05-23 06:37:48 +00:00
Jakob Borg 5464970c5d fix(versioner): ensure user read/write/execute on archived dirs (fixes #10532) (#10696)
This makes sure the user running Syncthing, and hence Synchting itself,
has read/write/execute on directories in .stversions. The other
permission bits remain copied from the source directory, ensuring
whatever group and other permissions were set remain in effect.

Closes #10695.

---------

Signed-off-by: Jakob Borg <jakob@kastelo.net>
2026-05-23 06:21:04 +00:00
Jakob Borg 3962a23723 fix(syncthing): properly upgrade via REST when Syncthing is running (fixes #10697) (#10699)
The locking logic for upgrades got inverted in the lockfile changes. If
we got the lock it means Syncthing wasn't already running, so we can do
a direct upgrade. If we failed to get the lock it means Syncthing was
running and we should tell the REST interface to do the upgrade.

Signed-off-by: Jakob Borg <jakob@kastelo.net>
2026-05-21 10:15:18 +02:00
Jakob Borg feaa90408e Merge branch 'infrastructure'
* infrastructure:
  fix(stcrashreceiver): allow extra pre/post data in version line
  chore(stcrashreceiver): improve logging
  chore(stdiscosrv): prewarm counters at startup
2026-05-21 09:57:46 +02:00
Jakob Borg a8ed6e4855 fix(stcrashreceiver): allow extra pre/post data in version line
Signed-off-by: Jakob Borg <jakob@kastelo.net>
2026-05-19 08:46:22 +02:00
Jakob Borg 5b1e1c0520 chore(stcrashreceiver): improve logging
Signed-off-by: Jakob Borg <jakob@kastelo.net>
2026-05-19 08:46:21 +02:00
Jakob Borg c17be06192 chore(stdiscosrv): prewarm counters at startup
Signed-off-by: Jakob Borg <jakob@kastelo.net>
2026-05-18 23:40:38 +02:00
75 changed files with 779 additions and 419 deletions
@@ -22,6 +22,7 @@ jobs:
if: github.repository_owner == 'syncthing'
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
pkg:
- stcrashreceiver
+2 -2
View File
@@ -164,7 +164,7 @@ jobs:
needs:
- build-test
- package-linux
# - package-illumos
- package-illumos
- package-cross
- package-source
- package-debian
@@ -646,7 +646,7 @@ jobs:
needs:
- codesign-windows
- package-linux
# - package-illumos
- package-illumos
- package-macos
- package-cross
- package-source
+3
View File
@@ -116,6 +116,7 @@ Dmitry Saveliev (dsaveliev) <d.e.saveliev@gmail.com>
domain <32405309+szu17dmy@users.noreply.github.com>
Domenic Horner <domenic@tgxn.net>
Dominik Heidler (asdil12) <dominik@heidler.eu>
Elias <1elias.bauer@gmail.com>
Elias Jarlebring (jarlebring) <jarlebring@gmail.com>
Elliot Huffman <thelich2@gmail.com>
Emil Hessman (ceh) <emil@hessman.se>
@@ -216,6 +217,7 @@ Matic Potočnik <hairyfotr@gmail.com>
Matt Burke (burkemw3) <mburke@amplify.com> <burkemw3@gmail.com>
Matt Robenolt <matt@ydekproductions.com>
Matteo Ruina <matteo.ruina@gmail.com>
mattn <mattn.jp@gmail.com>
Maurizio Tomasi <ziotom78@gmail.com>
Max <github@germancoding.com>
Max Schulze (kralo) <max.schulze@online.de> <kralo@users.noreply.github.com>
@@ -286,6 +288,7 @@ Sergey Mishin (ralder) <ralder@yandex.ru>
Sertonix <83883937+Sertonix@users.noreply.github.com>
Severin von Wnuck-Lipinski <ss7@live.de>
Shaarad Dalvi <60266155+shaaraddalvi@users.noreply.github.com> <shdalv@microsoft.com>
Shablone <20610621+Shablone@users.noreply.github.com>
Shivam Kumar <155747305+maishivamhoo123@users.noreply.github.com>
Simon Mwepu <simonmwepu@gmail.com>
Simon Pickup <simon@pickupinfinity.com>
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<application>
<windowsSettings>
<consoleAllocationPolicy xmlns="http://schemas.microsoft.com/SMI/2024/WindowsSettings">detached</consoleAllocationPolicy>
</windowsSettings>
</application>
</assembly>
+15 -6
View File
@@ -718,7 +718,7 @@ func shouldBuildSyso(dir string) (string, error) {
}
jsonPath := filepath.Join(dir, "versioninfo.json")
err = os.WriteFile(jsonPath, bs, 0o644)
err = os.WriteFile(jsonPath, bs, 0o666)
if err != nil {
return "", errors.New("failed to create " + jsonPath + ": " + err.Error())
}
@@ -732,9 +732,18 @@ func shouldBuildSyso(dir string) (string, error) {
sysoPath := filepath.Join(dir, "cmd", "syncthing", "resource.syso")
// See https://github.com/josephspurrier/goversioninfo#command-line-flags
arm := strings.HasPrefix(goarch, "arm")
a64 := strings.Contains(goarch, "64")
if _, err := runError("goversioninfo", "-o", sysoPath, fmt.Sprintf("-arm=%v", arm), fmt.Sprintf("-64=%v", a64)); err != nil {
// For manifest see https://learn.microsoft.com/en-us/windows/console/console-allocation-policy
isARM := strings.HasPrefix(goarch, "arm")
is64Bit := strings.Contains(goarch, "64")
args := []string{
"-manifest=assets/windows/syncthing.exe.manifest", // console-allocation-policy
"-o", sysoPath, // output path
fmt.Sprintf("-arm=%v", isARM),
fmt.Sprintf("-64=%v", is64Bit),
}
if _, err := runError("goversioninfo", args...); err != nil {
return "", errors.New("failed to create " + sysoPath + ": " + err.Error())
}
@@ -774,7 +783,7 @@ func copyFile(src, dst string, perm os.FileMode) error {
}
copy:
os.MkdirAll(filepath.Dir(dst), 0o777)
os.MkdirAll(filepath.Dir(dst), os.ModePerm)
if err := os.WriteFile(dst, in, perm); err != nil {
return err
}
@@ -1423,7 +1432,7 @@ func writeCompatJSON() {
continue
}
bs, _ := json.MarshalIndent(e, "", " ")
if err := os.WriteFile("compat.json", bs, 0o644); err != nil {
if err := os.WriteFile("compat.json", bs, 0o666); err != nil {
log.Fatal("Writing compat.json:", err)
}
return
@@ -0,0 +1,76 @@
2026-05-21 10:03:01 INF syncthing v2.1.1-dev.9.gb3b7d228.dirty-morecrashrep "Hafnium Hornet" (go1.26.3 darwin-arm64) jb@jbo-m3wl72rv 2026-05-21 07:58:11 UTC [stnoupgrade] (log.pkg=main)
2026-05-21 10:03:01 INF No automatic upgrades; STNOUPGRADE environment variable defined (log.pkg=main)
2026-05-21 10:03:01 INF Calculated our device ID (device=I6KAH76-66SLLLB-5PFXSOA-UFJCDZC-YAOMLEK-CP2GB32-BV5RQST-3PSROAU log.pkg=syncthing)
2026-05-21 10:03:01 INF Overall rate limit in use (send="is unlimited" recv="is unlimited" log.pkg=connections)
2026-05-21 10:03:01 INF Using discovery mechanism (identity="IPv4 local broadcast discovery on port 21027" log.pkg=discover)
2026-05-21 10:03:01 INF Using discovery mechanism (identity="IPv6 local multicast discovery on address [ff12::8384]:21027" log.pkg=discover)
2026-05-21 10:03:01 INF TCP listener starting (address=127.0.0.1:22001 log.pkg=connections)
2026-05-21 10:03:01 INF Ready to synchronize (folder.id=default folder.type=sendreceive log.pkg=model)
2026-05-21 10:03:01 INF QUIC listener starting (address=127.0.0.1:22001 log.pkg=connections)
2026-05-21 10:03:01 INF GUI and API listening (address=127.0.0.1:8081 log.pkg=api)
...
2026-05-21 10:03:01 INF Access the GUI via the following URL: http://127.0.0.1:8081/ (log.pkg=api)
2026-05-21 10:03:01 INF Loaded configuration (name=s1 log.pkg=syncthing)
2026-05-21 10:03:01 INF Loaded peer device configuration (device=MRIW7OK name=s2 address="[tcp://127.0.0.1:22002 quic://127.0.0.1:22002]" log.pkg=syncthing)
2026-05-21 10:03:01 INF Completed initial scan (folder.id=default folder.type=sendreceive log.pkg=model)
0xee9de1fe260
2026-05-21 10:03:02 INF Measured hashing performance (perf="2789.71 MB/s" log.pkg=syncthing)
Panic at 2026-05-21T10:03:02+02:00
runtime: marked free object in span 0x108b34d20, elemsize=8 freeindex=34 (bad use of unsafe.Pointer or having race conditions? try -d=checkptr or -race)
0xee9de1fe000 alloc marked
0xee9de1fe008 alloc marked
...
0xee9de1fe250 free unmarked
0xee9de1fe258 free unmarked
0xee9de1fe260 free marked zombie
7 6 5 4 3 2 1 0 f e d c b a 9 8 0123456789abcdef
00000ee9de1fe260: 00000000 00000000 ........
0xee9de1fe268 free unmarked
0xee9de1fe270 free unmarked
...
0xee9de1fff60 free unmarked
0xee9de1fff68 free unmarked
0xee9de1fff70 free unmarked
0xee9de1fff78 free unmarked
fatal error: found pointer to free object
runtime stack:
runtime.throw({0x105881781?, 0x8?})
runtime/panic.go:1229 +0x38 fp=0x16bf82bb0 sp=0x16bf82b80 pc=0x104f0ca48
runtime.(*mspan).reportZombies(0x108b34d20)
runtime/mgcsweep.go:893 +0x314 fp=0x16bf82c30 sp=0x16bf82bb0 pc=0x104ec10b4
runtime.(*sweepLocked).sweep(0x16bf82d88?, 0x0)
runtime/mgcsweep.go:673 +0xbd0 fp=0x16bf82d50 sp=0x16bf82c30 pc=0x104ec0840
runtime.(*mcentral).uncacheSpan(0x16bf82db8?, 0x104ea4954?)
runtime/mcentral.go:237 +0xbc fp=0x16bf82d80 sp=0x16bf82d50 pc=0x104eaac3c
runtime.(*mcache).releaseAll(0x1089a85f0)
runtime/mcache.go:322 +0x188 fp=0x16bf82df0 sp=0x16bf82d80 pc=0x104eaa4e8
runtime.(*mcache).prepareForSweep(0x1089a85f0)
runtime/mcache.go:366 +0x4c fp=0x16bf82e20 sp=0x16bf82df0 pc=0x104eaa61c
runtime.gcMarkTermination.func4(0xee9de005808)
runtime/mgc.go:1546 +0x24 fp=0x16bf82e50 sp=0x16bf82e20 pc=0x104f076e4
runtime.forEachPInternal(0x10656f798)
runtime/proc.go:2167 +0x178 fp=0x16bf82ee0 sp=0x16bf82e50 pc=0x104eda728
runtime.gcMarkTermination.forEachP.func7()
runtime/proc.go:2126 +0x40 fp=0x16bf82f10 sp=0x16bf82ee0 pc=0x104eb3130
runtime.systemstack(0x7fc000)
runtime/asm_arm64.s:399 +0x68 fp=0x16bf82f20 sp=0x16bf82f10 pc=0x104f12888
goroutine 84 gp=0xee9de45c1e0 m=3 mp=0xee9de019008 [flushing proc caches]:
runtime.systemstack_switch()
runtime/asm_arm64.s:347 +0x8 fp=0xee9de805c40 sp=0xee9de805c30 pc=0x104f12808
runtime.forEachP(...)
runtime/proc.go:2112
runtime.gcMarkTermination({0xc0?, 0x1331f928480ca?, 0xc?, 0x0?})
runtime/mgc.go:1545 +0x5f4 fp=0xee9de805e80 sp=0xee9de805c40 pc=0x104eb28c4
runtime.gcMarkDone()
runtime/mgc.go:1173 +0x364 fp=0xee9de805f20 sp=0xee9de805e80 pc=0x104eb1bc4
runtime.gcBgMarkWorker(0xee9de341810)
runtime/mgc.go:1912 +0x29c fp=0xee9de805fb0 sp=0xee9de805f20 pc=0x104eb372c
runtime.gcBgMarkStartWorkers.gowrap1()
runtime/mgc.go:1695 +0x20 fp=0xee9de805fd0 sp=0xee9de805fb0 pc=0x104eb3470
runtime.goexit({})
runtime/asm_arm64.s:1447 +0x4 fp=0xee9de805fd0 sp=0xee9de805fd0 pc=0x104f14a04
created by runtime.gcBgMarkStartWorkers in goroutine 1
runtime/mgc.go:1695 +0x134
+17 -7
View File
@@ -42,7 +42,7 @@ type currentFile struct {
}
func (d *diskStore) Serve(ctx context.Context) {
if err := os.MkdirAll(d.dir, 0o700); err != nil {
if err := os.MkdirAll(d.dir, os.ModePerm); err != nil {
log.Println("Creating directory:", err)
return
}
@@ -62,7 +62,7 @@ func (d *diskStore) Serve(ctx context.Context) {
case entry := <-d.inbox:
path := d.fullPath(entry.path)
if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil {
if err := os.MkdirAll(filepath.Dir(path), os.ModePerm); err != nil {
log.Println("Creating directory:", err)
continue
}
@@ -77,7 +77,7 @@ func (d *diskStore) Serve(ctx context.Context) {
log.Println("Failed to compress crash report:", err)
continue
}
if err := os.WriteFile(path, buf.Bytes(), 0o600); err != nil {
if err := os.WriteFile(path, buf.Bytes(), 0o666); err != nil {
log.Printf("Failed to write %s: %v", entry.path, err)
_ = os.Remove(path)
continue
@@ -136,15 +136,25 @@ func (d *diskStore) Exists(path string) bool {
}
func (d *diskStore) clean() {
for len(d.currentFiles) > 0 && (len(d.currentFiles) > d.maxFiles || d.currentSize > d.maxBytes) {
f := d.currentFiles[0]
numDeleted := 0
for idx := range d.currentFiles {
if len(d.currentFiles)-numDeleted < d.maxFiles && d.currentSize < d.maxBytes {
break
}
f := d.currentFiles[idx]
log.Println("Removing", f.path)
if err := os.Remove(f.path); err != nil {
log.Println("Failed to remove file:", err)
}
d.currentFiles = d.currentFiles[1:]
d.currentSize -= f.size
numDeleted = idx + 1
}
// Compact currentFiles
copy(d.currentFiles, d.currentFiles[numDeleted:])
d.currentFiles = d.currentFiles[:len(d.currentFiles)-numDeleted]
var oldest time.Duration
if len(d.currentFiles) > 0 {
oldest = time.Since(time.Unix(d.currentFiles[0].mtime, 0)).Truncate(time.Minute)
@@ -158,7 +168,7 @@ func (d *diskStore) clean() {
}
func (d *diskStore) inventory() error {
d.currentFiles = nil
d.currentFiles = d.currentFiles[:0]
d.currentSize = 0
err := filepath.Walk(d.dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
+11 -8
View File
@@ -20,6 +20,7 @@ import (
"io"
"log"
"net/http"
"net/http/pprof"
"os"
"path/filepath"
"regexp"
@@ -29,7 +30,7 @@ import (
raven "github.com/getsentry/raven-go"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/syncthing/syncthing/lib/build"
"github.com/syncthing/syncthing/lib/ur"
"github.com/syncthing/syncthing/lib/ur/contract"
)
const maxRequestSize = 1 << 20 // 1 MiB
@@ -89,6 +90,7 @@ func main() {
if params.MetricsListen != "" {
mmux := http.NewServeMux()
mmux.Handle("/metrics", promhttp.Handler())
mmux.HandleFunc("/debug/pprof/", pprof.Index)
go func() {
if err := http.ListenAndServe(params.MetricsListen, mmux); err != nil {
log.Fatalln("HTTP serve metrics:", err)
@@ -123,12 +125,13 @@ func handleFailureFn(dsn, failureDir string, ignore *ignorePatterns) func(w http
return
}
if ignore.match(bs) {
if pat, ok := ignore.match(bs); ok {
metricIgnoreMatchesTotal.WithLabelValues(pat).Inc()
result = "ignored"
return
}
var reports []ur.FailureReport
var reports []contract.FailureReport
err = json.Unmarshal(bs, &reports)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
@@ -176,7 +179,7 @@ func handleFailureFn(dsn, failureDir string, ignore *ignorePatterns) func(w http
}
}
func saveFailureWithGoroutines(data ur.FailureData, failureDir string) (string, error) {
func saveFailureWithGoroutines(data contract.FailureData, failureDir string) (string, error) {
bs := make([]byte, len(data.Description)+len(data.Goroutines))
copy(bs, data.Description)
copy(bs[len(data.Description):], data.Goroutines)
@@ -216,14 +219,14 @@ func loadIgnorePatterns(path string) (*ignorePatterns, error) {
return &ignorePatterns{patterns: patterns}, nil
}
func (i *ignorePatterns) match(report []byte) bool {
func (i *ignorePatterns) match(report []byte) (string, bool) {
if i == nil {
return false
return "", false
}
for _, re := range i.patterns {
if re.Match(report) {
return true
return re.String(), true
}
}
return false
return "", false
}
+20
View File
@@ -37,4 +37,24 @@ var (
Subsystem: "crashreceiver",
Name: "diskstore_oldest_age_seconds",
})
metricSentryReportsTotal = promauto.NewCounterVec(prometheus.CounterOpts{
Namespace: "syncthing",
Subsystem: "crashreceiver",
Name: "sentry_reports_total",
}, []string{"result"})
metricIgnoreMatchesTotal = promauto.NewCounterVec(prometheus.CounterOpts{
Namespace: "syncthing",
Subsystem: "crashreceiver",
Name: "ignore_matches_total",
}, []string{"pattern"})
metricSourceCodeLoadsTotal = promauto.NewCounterVec(prometheus.CounterOpts{
Namespace: "syncthing",
Subsystem: "crashreceiver",
Name: "source_code_loads_total",
}, []string{"result"})
metricSourceCodeCacheSize = promauto.NewGauge(prometheus.GaugeOpts{
Namespace: "syncthing",
Subsystem: "crashreceiver",
Name: "source_code_cache_size",
})
)
+8 -8
View File
@@ -10,6 +10,7 @@ import (
"bytes"
"context"
"errors"
"fmt"
"io"
"log"
"regexp"
@@ -52,11 +53,15 @@ func (s *sentryService) Serve(ctx context.Context) {
pkt, err := parseCrashReport(req.reportID, req.data)
if err != nil {
log.Println("Failed to parse crash report:", err)
metricSentryReportsTotal.WithLabelValues("parse_failure").Inc()
continue
}
if err := sendReport(s.dsn, pkt, req.userID); err != nil {
log.Println("Failed to send crash report:", err)
metricSentryReportsTotal.WithLabelValues("send_failure").Inc()
continue
}
metricSentryReportsTotal.WithLabelValues("success").Inc()
case <-ctx.Done():
return
@@ -69,6 +74,7 @@ func (s *sentryService) Send(reportID, userID string, data []byte) bool {
case s.inbox <- sentryRequest{reportID, userID, data}:
return true
default:
metricCrashReportsTotal.WithLabelValues("overflow").Inc()
return false
}
}
@@ -108,11 +114,10 @@ func parseCrashReport(path string, report []byte) (*raven.Packet, error) {
version, err := build.ParseVersion(string(parts[0]))
if err != nil {
return nil, err
return nil, fmt.Errorf("%w in %q", err, parts[0])
}
report = parts[1]
foundPanic := false
var subjectLine []byte
for {
parts = bytes.SplitN(report, []byte("\n"), 2)
@@ -123,14 +128,9 @@ func parseCrashReport(path string, report []byte) (*raven.Packet, error) {
line := parts[0]
report = parts[1]
if foundPanic {
// The previous line was our "Panic at ..." header. We are now
// at the beginning of the real panic trace and this is our
// subject line.
if bytes.HasPrefix(line, []byte("panic:")) || bytes.HasPrefix(line, []byte("fatal error:")) {
subjectLine = line
break
} else if bytes.HasPrefix(line, []byte("Panic at")) {
foundPanic = true
}
}
+18 -11
View File
@@ -9,26 +9,33 @@ package main
import (
"fmt"
"os"
"path/filepath"
"testing"
)
func TestParseReport(t *testing.T) {
bs, err := os.ReadFile("_testdata/panic.log")
files, err := filepath.Glob("_testdata/*.log")
if err != nil {
t.Fatal(err)
}
for _, file := range files {
bs, err := os.ReadFile(file)
if err != nil {
t.Fatal(err)
}
pkt, err := parseCrashReport("1/2/345", bs)
if err != nil {
t.Fatal(err)
pkt, err := parseCrashReport("1/2/345", bs)
if err != nil {
t.Fatal(err)
}
bs, err = pkt.JSON()
if err != nil {
t.Fatal(err)
}
fmt.Printf("%s\n", bs)
}
bs, err = pkt.JSON()
if err != nil {
t.Fatal(err)
}
fmt.Printf("%s\n", bs)
}
func TestCrashReportFingerprint(t *testing.T) {
+26 -11
View File
@@ -15,23 +15,33 @@ import (
"strings"
"sync"
"time"
lru "github.com/hashicorp/golang-lru/v2"
)
const (
urlPrefix = "https://raw.githubusercontent.com/syncthing/syncthing/"
httpTimeout = 10 * time.Second
urlPrefix = "https://raw.githubusercontent.com/syncthing/syncthing/"
httpTimeout = 10 * time.Second
maxCacheEntries = 1000
)
type cacheKey struct {
version string
file string
}
type githubSourceCodeLoader struct {
mut sync.Mutex
version string
cache map[string]map[string][][]byte // version -> file -> lines
client *http.Client
cache *lru.TwoQueueCache[cacheKey, [][]byte] // version & file -> lines
client *http.Client
}
func newGithubSourceCodeLoader() *githubSourceCodeLoader {
cache, _ := lru.New2Q[cacheKey, [][]byte](maxCacheEntries)
return &githubSourceCodeLoader{
cache: make(map[string]map[string][][]byte),
cache: cache,
client: &http.Client{Timeout: httpTimeout},
}
}
@@ -39,9 +49,6 @@ func newGithubSourceCodeLoader() *githubSourceCodeLoader {
func (l *githubSourceCodeLoader) LockWithVersion(version string) {
l.mut.Lock()
l.version = version
if _, ok := l.cache[version]; !ok {
l.cache[version] = make(map[string][][]byte)
}
}
func (l *githubSourceCodeLoader) Unlock() {
@@ -50,11 +57,13 @@ func (l *githubSourceCodeLoader) Unlock() {
func (l *githubSourceCodeLoader) Load(filename string, line, context int) ([][]byte, int) {
filename = filepath.ToSlash(filename)
lines, ok := l.cache[l.version][filename]
key := cacheKey{version: l.version, file: filename}
lines, ok := l.cache.Get(key)
if !ok {
// Cache whatever we managed to find (or nil if nothing, so we don't try again)
defer func() {
l.cache[l.version][filename] = lines
l.cache.Add(key, lines)
metricSourceCodeCacheSize.Set(float64(l.cache.Len()))
}()
knownPrefixes := []string{"/lib/", "/cmd/"}
@@ -73,19 +82,25 @@ func (l *githubSourceCodeLoader) Load(filename string, line, context int) ([][]b
resp, err := l.client.Get(url)
if err != nil {
fmt.Println("Loading source:", err)
metricSourceCodeLoadsTotal.WithLabelValues("failed").Inc()
return nil, 0
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
fmt.Println("Loading source:", resp.Status)
metricSourceCodeLoadsTotal.WithLabelValues("failed").Inc()
return nil, 0
}
data, err := io.ReadAll(resp.Body)
_ = resp.Body.Close()
if err != nil {
fmt.Println("Loading source:", err.Error())
metricSourceCodeLoadsTotal.WithLabelValues("failed").Inc()
return nil, 0
}
lines = bytes.Split(data, []byte{'\n'})
metricSourceCodeLoadsTotal.WithLabelValues("loaded").Inc()
} else {
metricSourceCodeLoadsTotal.WithLabelValues("cached").Inc()
}
return getLineFromLines(lines, line, context)
+10 -29
View File
@@ -7,21 +7,18 @@
package main
import (
"bytes"
"io"
"log"
"net/http"
"path"
"strings"
"sync"
)
type crashReceiver struct {
store *diskStore
sentry *sentryService
ignore *ignorePatterns
ignoredMut sync.RWMutex
ignored map[string]struct{}
}
func (r *crashReceiver) ServeHTTP(w http.ResponseWriter, req *http.Request) {
@@ -69,12 +66,6 @@ func (r *crashReceiver) serveGet(reportID string, w http.ResponseWriter, _ *http
// serveHead responds to HEAD requests by checking if the named report
// already exists in the system.
func (r *crashReceiver) serveHead(reportID string, w http.ResponseWriter, _ *http.Request) {
r.ignoredMut.RLock()
_, ignored := r.ignored[reportID]
r.ignoredMut.RUnlock()
if ignored {
return // found
}
if !r.store.Exists(reportID) {
http.Error(w, "Not found", http.StatusNotFound)
}
@@ -87,17 +78,7 @@ func (r *crashReceiver) servePut(reportID string, w http.ResponseWriter, req *ht
metricCrashReportsTotal.WithLabelValues(result).Inc()
}()
r.ignoredMut.RLock()
_, ignored := r.ignored[reportID]
r.ignoredMut.RUnlock()
if ignored {
result = "ignored_cached"
io.Copy(io.Discard, req.Body)
return // found
}
// Read at most maxRequestSize of report data.
log.Println("Receiving report", reportID)
lr := io.LimitReader(req.Body, maxRequestSize)
bs, err := io.ReadAll(lr)
if err != nil {
@@ -106,14 +87,12 @@ func (r *crashReceiver) servePut(reportID string, w http.ResponseWriter, req *ht
return
}
if r.ignore.match(bs) {
r.ignoredMut.Lock()
if r.ignored == nil {
r.ignored = make(map[string]struct{})
}
r.ignored[reportID] = struct{}{}
r.ignoredMut.Unlock()
first := string(bytes.TrimSpace(bytes.Split(bs, []byte("\n"))[0]))
if pat, ok := r.ignore.match(bs); ok {
metricIgnoreMatchesTotal.WithLabelValues(pat).Inc()
result = "ignored"
log.Printf("Ignored report %s, matched: %s (%s)", reportID[:8], pat, first)
return
}
@@ -121,13 +100,15 @@ func (r *crashReceiver) servePut(reportID string, w http.ResponseWriter, req *ht
// Store the report
if !r.store.Put(reportID, bs) {
log.Println("Failed to store report (queue full):", reportID)
log.Println("Failed to store report (queue full):", reportID[:8])
result = "queue_failure"
}
// Send the report to Sentry
if !r.sentry.Send(reportID, userIDFor(req), bs) {
log.Println("Failed to send report to sentry (queue full):", reportID)
log.Println("Failed to send report to sentry (queue full):", reportID[:8])
result = "sentry_failure"
}
log.Printf("Received report %s (%s)", reportID[:8], first)
}
+1 -1
View File
@@ -52,5 +52,5 @@ func compressAndWrite(bs []byte, fullPath string) error {
gw.Close()
// Create an output file with the compressed report
return os.WriteFile(fullPath, buf.Bytes(), 0o644)
return os.WriteFile(fullPath, buf.Bytes(), 0o666)
}
+1 -1
View File
@@ -612,7 +612,7 @@ func saveRelays(file string, relays []*relay) error {
for _, relay := range relays {
content += relay.uri.String() + "\n"
}
return os.WriteFile(file, []byte(content), 0o777)
return os.WriteFile(file, []byte(content), 0o666)
}
func createTestCertificate() tls.Certificate {
+4 -4
View File
@@ -15,7 +15,7 @@ import (
"io"
"log/slog"
"os"
"path"
"path/filepath"
"runtime"
"slices"
"strings"
@@ -77,7 +77,7 @@ func newInMemoryStore(dir string, flushInterval time.Duration, blobs blob.Store)
slog.Error("Failed to find database in blob storage", "error", cerr)
return s
}
fd, cerr := os.Create(path.Join(s.dir, "records.db"))
fd, cerr := os.Create(filepath.Join(s.dir, "records.db"))
if cerr != nil {
slog.Error("Failed to create database file", "error", cerr)
return s
@@ -257,7 +257,7 @@ func (s *inMemoryStore) write() (err error) {
}
}()
dbf := path.Join(s.dir, "records.db")
dbf := filepath.Join(s.dir, "records.db")
fd, err := os.Create(dbf + ".tmp")
if err != nil {
return err
@@ -340,7 +340,7 @@ func (s *inMemoryStore) write() (err error) {
}
func (s *inMemoryStore) read() (int, error) {
fd, err := os.Open(path.Join(s.dir, "records.db"))
fd, err := os.Open(filepath.Join(s.dir, "records.db"))
if err != nil {
return 0, err
}
+27 -8
View File
@@ -7,6 +7,8 @@
package main
import (
"net/http"
"github.com/prometheus/client_golang/prometheus"
)
@@ -113,14 +115,11 @@ var (
)
const (
dbOpGet = "get"
dbOpPut = "put"
dbOpMerge = "merge"
dbOpDelete = "delete"
dbResSuccess = "success"
dbResNotFound = "not_found"
dbResError = "error"
dbResUnmarshalError = "unmarsh_err"
dbOpGet = "get"
dbOpPut = "put"
dbOpMerge = "merge"
dbResSuccess = "success"
dbResNotFound = "not_found"
)
func init() {
@@ -132,4 +131,24 @@ func init() {
databaseOperations, databaseOperationSeconds,
databaseWriteSeconds, databaseLastWritten,
retryAfterLevel)
// Prewarm important counters so they're available with zero values at
// startup
apiRequestsTotal.WithLabelValues(http.MethodGet, "200")
apiRequestsTotal.WithLabelValues(http.MethodGet, "404")
apiRequestsTotal.WithLabelValues(http.MethodPost, "204")
apiRequestsTotal.WithLabelValues(http.MethodPost, "400")
apiRequestsTotal.WithLabelValues(http.MethodPost, "403")
lookupRequestsTotal.WithLabelValues("success")
lookupRequestsTotal.WithLabelValues("not_found_ever")
lookupRequestsTotal.WithLabelValues("not_found_recent")
announceRequestsTotal.WithLabelValues("success")
announceRequestsTotal.WithLabelValues("bad_request")
announceRequestsTotal.WithLabelValues("no_certificate")
replicationSendsTotal.WithLabelValues("success")
replicationRecvsTotal.WithLabelValues("success")
}
+1 -1
View File
@@ -167,7 +167,7 @@ func (c *CLI) process(srcFs fs.Filesystem, dstFs fs.Filesystem, path string) err
var plainFd fs.File
if dstFs != nil {
if err := dstFs.MkdirAll(filepath.Dir(plainFi.Name), 0o700); err != nil {
if err := dstFs.MkdirAll(filepath.Dir(plainFi.Name), fs.ModePerm); err != nil {
return fmt.Errorf("%s: %w", plainFi.Name, err)
}
+1 -1
View File
@@ -7,5 +7,5 @@
package main
type buildSpecificOptions struct {
HideConsole bool `name:"no-console" help:"Hide console window" env:"STHIDECONSOLE"`
HideConsole bool `name:"no-console" help:"Hide console window (Always enabled on Windows 11 24H2 and later)" env:"STHIDECONSOLE"`
}
+8 -4
View File
@@ -673,7 +673,7 @@ func auditWriter(auditFile string) io.Writer {
} else {
auditFlags = os.O_WRONLY | os.O_CREATE | os.O_APPEND
}
fd, err = os.OpenFile(auditFile, auditFlags, 0o600)
fd, err = os.OpenFile(auditFile, auditFlags, 0o666)
if err != nil {
slog.Error("Failed to open audit file", slogutil.Error(err))
os.Exit(svcutil.ExitError.AsInt())
@@ -915,10 +915,14 @@ func (u upgradeCmd) Run() error {
case err != nil && !os.IsNotExist(err):
slog.Error("Failed to lock for upgrade", slogutil.Error(err))
os.Exit(1)
case locked:
err = upgradeViaRest()
default:
case locked || os.IsNotExist(err):
// We got the lock, or the config directory didn't exist, so we
// can do a direct upgrade
err = upgrade.To(release)
default:
// We didn't get the lock, because Syncthing was running, so
// upgrade via REST.
err = upgradeViaRest()
}
}
if err != nil {
+2 -2
View File
@@ -233,7 +233,7 @@ func copyStderr(stderr io.Reader, dst io.Writer) {
dst.Write([]byte(line))
if panicFd == nil && (strings.HasPrefix(line, "panic:") || strings.HasPrefix(line, "fatal error:")) {
if panicFd == nil && (strings.HasPrefix(line, "panic:") || strings.HasPrefix(line, "fatal error:") || strings.HasPrefix(line, "runtime:")) {
panicFd, err = os.Create(locations.GetTimestamped(locations.PanicLog))
if err != nil {
slog.Error("Failed to create panic log", slogutil.Error(err))
@@ -479,7 +479,7 @@ func (f *autoclosedFile) ensureOpenLocked() error {
// We open the file for write only, and create it if it doesn't exist.
flags := os.O_WRONLY | os.O_CREATE | os.O_APPEND
fd, err := os.OpenFile(f.name, flags, 0o644)
fd, err := os.OpenFile(f.name, flags, 0o666)
if err != nil {
return err
}
+21 -2
View File
@@ -9,8 +9,27 @@
package main
import "os/exec"
import "golang.org/x/sys/windows"
func openURL(url string) error {
return exec.Command("cmd.exe", "/C", "start "+url).Run()
urlPtr, err := windows.UTF16PtrFromString(url)
if err != nil {
return err
}
verbPtr, err := windows.UTF16PtrFromString("open")
if err != nil {
return err
}
err = windows.ShellExecute(
0, // hwnd
verbPtr, // operation
urlPtr, // file
nil, // parameters
nil, // directory
windows.SW_SHOWNORMAL,
)
return err
}
+15 -18
View File
@@ -22,17 +22,17 @@ require (
github.com/julienschmidt/httprouter v1.3.0
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
github.com/maruel/panicparse/v2 v2.5.0
github.com/mattn/go-sqlite3 v1.14.44
github.com/mattn/go-sqlite3 v1.14.45
github.com/maxmind/geoipupdate/v6 v6.1.0
github.com/miscreant/miscreant.go v0.0.0-20200214223636-26d376326b75
github.com/oschwald/geoip2-golang v1.13.0
github.com/pierrec/lz4/v4 v4.1.26
github.com/pierrec/lz4/v4 v4.1.27
github.com/prometheus/client_golang v1.23.2
github.com/puzpuzpuz/xsync/v3 v3.5.1
github.com/quic-go/quic-go v0.59.0
github.com/quic-go/quic-go v0.60.0
github.com/rabbitmq/amqp091-go v1.11.0
github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9
github.com/shirou/gopsutil/v4 v4.26.4
github.com/shirou/gopsutil/v4 v4.26.5
github.com/syncthing/notify v0.0.0-20250528144937-c7027d4f7465
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d
github.com/thejerf/suture/v4 v4.0.6
@@ -40,14 +40,14 @@ require (
github.com/vitrun/qart v0.0.0-20160531060029-bf64b92db6b0
github.com/willabides/kongplete v0.4.0
github.com/wlynxg/anet v0.0.5
golang.org/x/crypto v0.51.0
golang.org/x/exp v0.0.0-20260508232706-74f9aab9d74a
golang.org/x/net v0.54.0
golang.org/x/sys v0.44.0
golang.org/x/text v0.37.0
golang.org/x/crypto v0.53.0
golang.org/x/exp v0.0.0-20260611194520-c48552f49976
golang.org/x/net v0.56.0
golang.org/x/sys v0.46.0
golang.org/x/text v0.38.0
golang.org/x/time v0.15.0
google.golang.org/protobuf v1.36.11
modernc.org/sqlite v1.50.0
modernc.org/sqlite v1.52.0
sigs.k8s.io/yaml v1.6.0
)
@@ -94,12 +94,12 @@ require (
github.com/tklauser/numcpus v0.11.0 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect
golang.org/x/mod v0.36.0 // indirect
golang.org/x/sync v0.20.0 // indirect
golang.org/x/telemetry v0.0.0-20260508192327-42602be52be6 // indirect
golang.org/x/tools v0.45.0 // indirect
golang.org/x/mod v0.37.0 // indirect
golang.org/x/sync v0.21.0 // indirect
golang.org/x/telemetry v0.0.0-20260610154732-fb80ec83bdd9 // indirect
golang.org/x/tools v0.46.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
modernc.org/libc v1.72.0 // indirect
modernc.org/libc v1.72.3 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.11.0 // indirect
)
@@ -110,9 +110,6 @@ replace github.com/gobwas/glob v0.2.3 => github.com/calmh/glob v0.0.0-2022061508
// https://github.com/jackpal/gateway/pull/49
replace github.com/jackpal/gateway v1.1.1 => github.com/marbens-arch/gateway v1.1.2-0.20260308173556-c567cc04e7d4
// https://github.com/mattn/go-sqlite3/pull/1338
replace github.com/mattn/go-sqlite3 v1.14.44 => github.com/calmh/go-sqlite3 v1.14.35-0.20260509063420-822b4765116d
tool (
github.com/calmh/xdr/cmd/genxdr
github.com/maxbrunsfeld/counterfeiter/v6
+38 -36
View File
@@ -19,8 +19,6 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/calmh/glob v0.0.0-20220615080505-1d823af5017b h1:Fjm4GuJ+TGMgqfGHN42IQArJb77CfD/mAwLbDUoJe6g=
github.com/calmh/glob v0.0.0-20220615080505-1d823af5017b/go.mod h1:91K7jfEsgJSyfSrX+gmrRfZMtntx6JsHolWubGXDopg=
github.com/calmh/go-sqlite3 v1.14.35-0.20260509063420-822b4765116d h1:liVtRMlDBqDyR4qKkMRIFTdUK6+aoG6Oh97DKnhrCHc=
github.com/calmh/go-sqlite3 v1.14.35-0.20260509063420-822b4765116d/go.mod h1:pjEuOr8IwzLJP2MfGeTb0A35jauH+C2kbHKBr7yXKVQ=
github.com/calmh/incontainer v1.0.0 h1:g2cTUtZuFGmMGX8GoykPkN1Judj2uw8/3/aEtq4Z/rg=
github.com/calmh/incontainer v1.0.0/go.mod h1:eOhqnw15c9X+4RNBe0W3HlUZFfX16O0EDsCOInTndHY=
github.com/calmh/xdr v1.2.0 h1:GaGSNH4ZDw9kNdYqle6+RcAENiaQ8/611Ok+jQbBEeU=
@@ -149,6 +147,8 @@ github.com/maruel/panicparse/v2 v2.5.0/go.mod h1:DA2fDiBk63bKfBf4CVZP9gb4fuvzdPb
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/mattn/go-sqlite3 v1.14.45 h1:6KA/spDguL3KV8rnybG7ezSaE4SeMR3KC9VbUoAQaIk=
github.com/mattn/go-sqlite3 v1.14.45/go.mod h1:pjEuOr8IwzLJP2MfGeTb0A35jauH+C2kbHKBr7yXKVQ=
github.com/maxbrunsfeld/counterfeiter/v6 v6.12.0 h1:aOeI7xAOVdK+R6xbVsZuU9HmCZYmQVmZgPf9xJUd2Sg=
github.com/maxbrunsfeld/counterfeiter/v6 v6.12.0/go.mod h1:0hZWbtfeCYUQeAQdPLUzETiBhUSns7O6LDj9vH88xKA=
github.com/maxmind/geoipupdate/v6 v6.1.0 h1:sdtTHzzQNJlXF5+fd/EoPTucRHyMonYt/Cok8xzzfqA=
@@ -179,8 +179,8 @@ github.com/oschwald/geoip2-golang v1.13.0 h1:Q44/Ldc703pasJeP5V9+aFSZFmBN7DKHbNs
github.com/oschwald/geoip2-golang v1.13.0/go.mod h1:P9zG+54KPEFOliZ29i7SeYZ/GM6tfEL+rgSn03hYuUo=
github.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5MbwsmL4MRQE=
github.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8=
github.com/pierrec/lz4/v4 v4.1.26 h1:GrpZw1gZttORinvzBdXPUXATeqlJjqUG/D87TKMnhjY=
github.com/pierrec/lz4/v4 v4.1.26/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4=
github.com/pierrec/lz4/v4 v4.1.27 h1:+PhzhWDrjRj89TH2sw43nE3+4+W8lSxIuQadEHZyjUk=
github.com/pierrec/lz4/v4 v4.1.27/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -200,8 +200,10 @@ github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzM
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
github.com/puzpuzpuz/xsync/v3 v3.5.1 h1:GJYJZwO6IdxN/IKbneznS6yPkVC+c3zyY/j19c++5Fg=
github.com/puzpuzpuz/xsync/v3 v3.5.1/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw=
github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU=
github.com/quic-go/go-ossfuzz-seeds v0.1.0 h1:APacT+iIaNF6fd8AGEiN3bT/Jtkd2jz4v4TzM7MFjy0=
github.com/quic-go/go-ossfuzz-seeds v0.1.0/go.mod h1:3IOHRbJIc+L6YKMwfDtJAM9Vj9k0YY4muhuyUYk5tbk=
github.com/quic-go/quic-go v0.60.0 h1:xcQioE8OM66UQLeUMHltK1CCcOu3JbVB4JAQdDQSB+0=
github.com/quic-go/quic-go v0.60.0/go.mod h1:wpKpjmPpftl30sL6pFh7REVpjbcCVy4zt2vDyK1TuJk=
github.com/rabbitmq/amqp091-go v1.11.0 h1:HxIctVm9Gid/Vtn706necmZ7Wj6pgGI2eqplRbEY8O8=
github.com/rabbitmq/amqp091-go v1.11.0/go.mod h1:Hy4jKW5kQART1u+JkDTF9YYOQUHXqMuhrgxOEeS7G4o=
github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 h1:bsUq1dX0N8AOIL7EB/X911+m4EHsnWEHeJ0c+3TTBrg=
@@ -216,8 +218,8 @@ github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sclevine/spec v1.4.0 h1:z/Q9idDcay5m5irkZ28M7PtQM4aOISzOpj4bUPkDee8=
github.com/sclevine/spec v1.4.0/go.mod h1:LvpgJaFyvQzRvc1kaDs0bulYwzC70PbiYjC4QnFHkOM=
github.com/shirou/gopsutil/v4 v4.26.4 h1:B4SXVbcwTyrocPHEmWBC4uCYr4Xcu3MK1TXqbprAOWY=
github.com/shirou/gopsutil/v4 v4.26.4/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ=
github.com/shirou/gopsutil/v4 v4.26.5 h1:RPcBXkpz7kOj9PqGFQOlBPZHsyaPvPVQc098y9RmCNM=
github.com/shirou/gopsutil/v4 v4.26.5/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
@@ -265,13 +267,13 @@ go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI=
golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8=
golang.org/x/exp v0.0.0-20260508232706-74f9aab9d74a h1:+3jdDGGB8NGb1Zktc737jlt3/A5f6UlwSzmvqUuufxw=
golang.org/x/exp v0.0.0-20260508232706-74f9aab9d74a/go.mod h1:d2fgXJLVs4dYDHUk5lwMIfzRzSrWCfGZb0ZqeLa/Vcw=
golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto=
golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio=
golang.org/x/exp v0.0.0-20260611194520-c48552f49976 h1:X8Hz2ImujgbmetVuW+w2YkyZChE3cBpZi2P158rTG9M=
golang.org/x/exp v0.0.0-20260611194520-c48552f49976/go.mod h1:vnf4pv9iKZXY58sQE1L86zmNWJ4159e1RkcWiLCkeEY=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4=
golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ=
golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ=
golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -280,13 +282,13 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w=
golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ=
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180926160741-c2ed4eda69e7/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -309,25 +311,25 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ=
golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/telemetry v0.0.0-20260508192327-42602be52be6 h1:HjU6IWBiAgRIdAJ9/y1rwCn+UELEmwV+VsTLzj/W4sE=
golang.org/x/telemetry v0.0.0-20260508192327-42602be52be6/go.mod h1:Eqhaxk/wZsWEH8CRxLwj6xzEJbz7k1EFGqx7nyCoabE=
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/telemetry v0.0.0-20260610154732-fb80ec83bdd9 h1:FjUup8XrRy7lv+XHONi6KKUSizeF2NnVrTnz/HhbohQ=
golang.org/x/telemetry v0.0.0-20260610154732-fb80ec83bdd9/go.mod h1:3AWMyWHS+caVoiEXpiq6+tzKA40J4vQT3MYr80ZtQpc=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=
golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8=
golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0=
golang.org/x/tools v0.46.0 h1:7jTurBkPZu4moS/Uy4OQT1M+QBlsj3wejyZwsT8Z7rk=
golang.org/x/tools v0.46.0/go.mod h1:FrD85F8l+NWL+9XWBSyVSHO6Ne4jutsfIFba7AWQ5Ys=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -358,10 +360,10 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
modernc.org/cc/v4 v4.27.3 h1:uNCgn37E5U09mTv1XgskEVUJ8ADKpmFMPxzGJ0TSo+U=
modernc.org/cc/v4 v4.27.3/go.mod h1:3YjcbCqhoTTHPycJDRl2WZKKFj0nwcOIPBfEZK0Hdk8=
modernc.org/ccgo/v4 v4.32.4 h1:L5OB8rpEX4ZsXEQwGozRfJyJSFHbbNVOoQ59DU9/KuU=
modernc.org/ccgo/v4 v4.32.4/go.mod h1:lY7f+fiTDHfcv6YlRgSkxYfhs+UvOEEzj49jAn2TOx0=
modernc.org/cc/v4 v4.28.2 h1:3tQ0lf2ADtoby2EtSP+J7IE2SHwEJdP8ioR59wx7XpY=
modernc.org/cc/v4 v4.28.2/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI=
modernc.org/ccgo/v4 v4.34.0 h1:yRLPFZieg532OT4rp4JFNIVcquwalMX26G95WQDqwCQ=
modernc.org/ccgo/v4 v4.34.0/go.mod h1:AS5WYMyBakQ+fhsHhtP8mWB82KTGPkNNJDGfGQCe0/A=
modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU=
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
@@ -370,18 +372,18 @@ modernc.org/gc/v3 v3.1.2 h1:ZtDCnhonXSZexk/AYsegNRV1lJGgaNZJuKjJSWKyEqo=
modernc.org/gc/v3 v3.1.2/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
modernc.org/libc v1.72.0 h1:IEu559v9a0XWjw0DPoVKtXpO2qt5NVLAnFaBbjq+n8c=
modernc.org/libc v1.72.0/go.mod h1:tTU8DL8A+XLVkEY3x5E/tO7s2Q/q42EtnNWda/L5QhQ=
modernc.org/libc v1.72.3 h1:ZnDF4tXn4NBXFutMMQC4vtbTFSXhhKzR73fv0beZEAU=
modernc.org/libc v1.72.3/go.mod h1:dn0dZNnnn1clLyvRxLxYExxiKRZIRENOfqQ8XEeg4Qs=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg=
modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
modernc.org/sqlite v1.50.0 h1:eMowQSWLK0MeiQTdmz3lqoF5dqclujdlIKeJA11+7oM=
modernc.org/sqlite v1.50.0/go.mod h1:m0w8xhwYUVY3H6pSDwc3gkJ/irZT/0YEXwBlhaxQEew=
modernc.org/sqlite v1.52.0 h1:p4dhYh2tXZCiyaqHwRVJDjIGKWyXayiQpThxgDzJaxo=
modernc.org/sqlite v1.52.0/go.mod h1:tcNzv5p84E0skkmJn038y+hWJbLQXQqEnQfeh5r2JLM=
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
+6
View File
@@ -11,6 +11,7 @@
"Add Device": "Dodaj uređaj",
"Add Folder": "Dodaj mapu",
"Add Remote Device": "Dodaj udaljeni uređaj",
"Add devices from the introducer to our device list, for mutually shared folders.": "Dodaj uređaje od posrednika u našu listu uređaja, za međusobno dijeljene mape.",
"Add filter entry": "Dodaj unos filtra",
"Add ignore patterns": "Dodaj uzorke zanemarivanja",
"Add new folder?": "Dodati novu mapu?",
@@ -187,6 +188,7 @@
"GUI Authentication Password": "Lozinka za GUI autentifikacju",
"GUI Authentication User": "Korisnik za GUI autentifikacju",
"GUI Authentication: Set User and Password": "GUI autentifikacija: Postavite korisnika i lozinku",
"GUI Listen Address": "GUI adresa za slušanje",
"GUI Override Directory": "Direktorij za nadjačavanje GUI-ja",
"GUI Theme": "Tema GUI-ja",
"General": "Općenito",
@@ -214,6 +216,8 @@
"Incorrect user name or password.": "Neispravno korisničko ime ili lozinka.",
"Info": "Informacije",
"Internally used paths:": "Interno korištene staze:",
"Introduced By": "Uveden od",
"Introducer": "Posrednik",
"Introduction": "Uvod",
"Inversion of the given condition (i.e. do not exclude)": "Inverzija zadanog uvjeta (tj. ne isključuj)",
"Keep Versions": "Zadrži verzije",
@@ -248,6 +252,7 @@
"Log tailing paused. Scroll to the bottom to continue.": "Praćenje zapisa je pauzirano. Pomaknite se na dno kako biste nastavili.",
"Login failed, see Syncthing logs for details.": "Prijava nije uspjela. Pogledaj detalje u odjeljku zapisima sinkronizacije.",
"Logs": "Zapisi",
"Maintain an index of all blocks in the folder, enabling reuse of blocks from other files when syncing changes. Disable to reduce database size at the cost of not being able to reuse blocks across files.": "Održavaj indeks svih blokova u mapi, što omogućava ponovnu upotrebu blokova iz drugih datoteka pri sinkronizaciji promjena. Onemogući da smanjiš veličinu baze podataka, uz cijenu nemogućnosti ponovne upotrebe blokova između datoteka.",
"Major Upgrade": "Velika nadogradnja",
"Mass actions": "Masovne radnje",
"Maximum Age": "Maksimalna starost",
@@ -401,6 +406,7 @@
"Support Bundle": "Paket podrške",
"Sync Extended Attributes": "Sinkroniziraj proširena svojstva",
"Sync Ownership": "Sinkroniziraj vlasništvo",
"Sync Protocol Listen Addresses": "Adrese za slušanje sinkronizacijskog protokola",
"Sync Status": "Stanje sinkronizacije",
"Syncing": "Sinkroniziranje",
"Syncthing device ID for \"{%devicename%}\"": "Syncthing ID uređaja za „{{devicename}}“",
@@ -30,7 +30,7 @@
<h4 class="text-center" translate>The Syncthing Authors</h4>
<div class="row">
<div class="col-md-12" id="contributor-list">
Jakob Borg, Audrius Butkevicius, Simon Frei, Tomasz Wilczyński, Alexander Graf, Alexandre Viau, Anderson Mesquita, André Colomb, Antony Male, Ben Schulz, bt90, Caleb Callaway, Daniel Harte, Emil Lundberg, Eric P, Evgeny Kuznetsov, greatroar, Lars K.W. Gohlke, Lode Hoste, Marcus B Spencer, Michael Ploujnikov, Ross Smith II, Stefan Tatschner, Tommy van der Vorst, Wulf Weich, Adam Piggott, Adel Qalieh, Aleksey Vasenev, Alessandro G., Alex Ionescu, Alex Lindeman, Alex Xu, Alexander Seiler, Alexandre Alves, Aman Gupta, Andreas Sommer, andresvia, Andrew Rabert, Andrey D, andyleap, Anjan Momi, Anthony Goeckner, Antoine Lamielle, Anur, Aranjedeath, ardevd, Arkadiusz Tymiński, Aroun, Arthur Axel fREW Schmidt, Artur Zubilewicz, Ashish Bhate, Aurélien Rainone, BAHADIR YILMAZ, Bart De Vries, Beat Reichenbach, Ben Norcombe, Ben Shepherd, Ben Sidhom, Benedikt Heine, Benno Fünfstück, Benny Ng, boomsquared, Boqin Qin, Boris Rybalkin, Brendan Long, Catfriend1, Cathryne Linenweaver, Cedric Staniewski, Chih-Hsuan Yen, Choongkyu, Chris Howie, Chris Joel, Christian Kujau, Christian Prescott, chucic, cjc7373, Colin Kennedy, Cromefire_, cui, Cyprien Devillez, d-volution, Dan, Daniel Barczyk, Daniel Bergmann, Daniel Martí, Daniel Padrta, Daniil Gentili, Darshil Chanpura, dashangcun, David Rimmer, DeflateAwning, Denis A., Dennis Wilson, derekriemer, DerRockWolf, desbma, Devon G. Redekopp, digital, Dimitri Papadopoulos Orfanos, Dmitry Saveliev, domain, Domenic Horner, Dominik Heidler, Elias Jarlebring, Elliot Huffman, Emil Hessman, Eng Zer Jun, entity0xfe, Epifeny, epifeny, Eric Lesiuta, Erik Meitner, Evan Spensley, Federico Castagnini, Felix, Felix Ableitner, Felix Lampe, Felix Unterpaintner, Francois-Xavier Gsell, Frank Isemann, Gahl Saraf, georgespatton, ghjklw, Gilli Sigurdsson, Gleb Sinyavskiy, Graham Miln, Greg, guangwu, gudvinr, Gusted, Han Boetes, HansK-p, Harrison Jones, Hazem Krimi, Heiko Zuerker, Hireworks, Hugo Locurcio, Iain Barnett, Ian Johnson, ignacy123, Iskander Sharipov, Jaakko Hannikainen, Jack Croft, Jacob, Jake Peterson, James O'Beirne, James Patterson, Jaroslav Lichtblau, Jaroslav Malec, Jaspitta, Jaya Chithra, Jaya Kumar, Jeffery To, jelle van der Waa, Jens Diemer, Jochen Voss, Johan Vromans, John Rinehart, Jonas Thelemann, Jonathan, Jose Manuel Delicado, JRNitre, jtagcat, Julian Lehrhuber, Jörg Thalheim, Jędrzej Kula, Kapil Sareen, Karol Różycki, Kebin Liu, Keith Harrison, Kelong Cong, Ken'ichi Kamada, Kevin Allen, Kevin Bushiri, Kevin White, Jr., klemens, Kurt Fitzner, kylosus, Lars Lehtonen, Laurent Etiemble, Leo Arias, Liu Siyuan, Lord Landon Agahnim, LSmithx2, Luiz Angelo Daros de Luca, Lukas Lihotzki, Luke Hamburg, luzpaz, Majed Abdulaziz, Marc Laporte, Marcel Meyer, Marcin Dziadus, Marcus Legendre, Mario Majila, Mark Pulford, Martchus, Mateusz Naściszewski, Mateusz Ż, mathias4833, Matic Potočnik, Matt Burke, Matt Robenolt, Matteo Ruina, Maurizio Tomasi, Max, Max Schulze, MaximAL, Maximilian, Maxwell G, Michael Jephcote, Michael Rienstra, Michael Wang 汪東陽, MichaIng, Migelo, Mike Boone, MikeLund, MikolajTwarog, Mingxuan Lin, mv1005, Nate Morrison, nf, Nicholas Rishel, Nick Busey, Nico Stapelbroek, Nicolas Braud-Santoni, Nicolas Perraut, Niels Peter Roest, Nils Jakobi, NinoM4ster, Nitroretro, NoLooseEnds, Oliver Freyermuth, orangekame3, otbutz, overkill, Oyebanji Jacob Mayowa, Pablo, Pascal Jungblut, Paul Brit, Paul Donald, Pawel Palenica, perewa, Peter Badida, Peter Dave Hello, Peter Hoeg, Peter Marquardt, Phani Rithvij, Phil Davis, Philippe Schommers, Phill Luby, Piotr Bejda, polyfloyd, Prathik P Kulkarni, pullmerge, Quentin Hibon, Rahmi Pruitt, RealCharlesChia, red_led, Robert Carosi, Roberto Santalla, Robin Schoonover, Roman Zaynetdinov, rubenbe, Ruslan Yevdokymov, Ryan Qian, Ryan Sullivan, Sacheendra Talluri, Scott Klupfel, sec65, Sergey Mishin, Sertonix, Severin von Wnuck-Lipinski, Shaarad Dalvi, Shivam Kumar, Simon Mwepu, Simon Pickup, Sly_tom_cat, Sonu Kumar Saw, Stefan Kuntz, Steven Eckhoff, Suhas Gundimeda, Sven Bachmann, Sébastien WENSKE, Tao, Taylor Khan, Terrance, TheCreeper, Thomas, Thomas Hipp, Tim Abell, Tim Howes, Tobias Frölich, Tobias Klauser, Tobias Nygren, Tobias Tom, Tom Jakubowski, Tully Robinson, Tyler Brazier, Tyler Kropp, Umer-Azaz, Unrud, Val Markovic, vapatel2, Veeti Paananen, Victor Buinsky, Vik, Vil Brekin, villekalliomaki, Vladimir Rusinov, vvaswani, wangguoliang, WangXi, Will Rouesnel, William A. Kennington III, wouter bolsterlee, xarx00, Xavier O., xjtdy888, Yannic A., yparitcher, 佛跳墙, 落心
Jakob Borg, Audrius Butkevicius, Simon Frei, Tomasz Wilczyński, Alexander Graf, Alexandre Viau, Anderson Mesquita, André Colomb, Antony Male, Ben Schulz, bt90, Caleb Callaway, Daniel Harte, Emil Lundberg, Eric P, Evgeny Kuznetsov, greatroar, Lars K.W. Gohlke, Lode Hoste, Marcus B Spencer, Michael Ploujnikov, Ross Smith II, Stefan Tatschner, Tommy van der Vorst, Wulf Weich, Adam Piggott, Adel Qalieh, Aleksey Vasenev, Alessandro G., Alex Ionescu, Alex Lindeman, Alex Xu, Alexander Seiler, Alexandre Alves, Aman Gupta, Andreas Sommer, andresvia, Andrew Rabert, Andrey D, andyleap, Anjan Momi, Anthony Goeckner, Antoine Lamielle, Anur, Aranjedeath, ardevd, Arkadiusz Tymiński, Aroun, Arthur Axel fREW Schmidt, Artur Zubilewicz, Ashish Bhate, Aurélien Rainone, BAHADIR YILMAZ, Bart De Vries, Beat Reichenbach, Ben Norcombe, Ben Shepherd, Ben Sidhom, Benedikt Heine, Benno Fünfstück, Benny Ng, boomsquared, Boqin Qin, Boris Rybalkin, Brendan Long, Catfriend1, Cathryne Linenweaver, Cedric Staniewski, Chih-Hsuan Yen, Choongkyu, Chris Howie, Chris Joel, Christian Kujau, Christian Prescott, chucic, cjc7373, Colin Kennedy, Cromefire_, cui, Cyprien Devillez, d-volution, Dan, Daniel Barczyk, Daniel Bergmann, Daniel Martí, Daniel Padrta, Daniil Gentili, Darshil Chanpura, dashangcun, David Rimmer, DeflateAwning, Denis A., Dennis Wilson, derekriemer, DerRockWolf, desbma, Devon G. Redekopp, digital, Dimitri Papadopoulos Orfanos, Dmitry Saveliev, domain, Domenic Horner, Dominik Heidler, Elias, Elias Jarlebring, Elliot Huffman, Emil Hessman, Eng Zer Jun, entity0xfe, Epifeny, epifeny, Eric Lesiuta, Erik Meitner, Evan Spensley, Federico Castagnini, Felix, Felix Ableitner, Felix Lampe, Felix Unterpaintner, Francois-Xavier Gsell, Frank Isemann, Gahl Saraf, georgespatton, ghjklw, Gilli Sigurdsson, Gleb Sinyavskiy, Graham Miln, Greg, guangwu, gudvinr, Gusted, Han Boetes, HansK-p, Harrison Jones, Hazem Krimi, Heiko Zuerker, Hireworks, Hugo Locurcio, Iain Barnett, Ian Johnson, ignacy123, Iskander Sharipov, Jaakko Hannikainen, Jack Croft, Jacob, Jake Peterson, James O'Beirne, James Patterson, Jaroslav Lichtblau, Jaroslav Malec, Jaspitta, Jaya Chithra, Jaya Kumar, Jeffery To, jelle van der Waa, Jens Diemer, Jochen Voss, Johan Vromans, John Rinehart, Jonas Thelemann, Jonathan, Jose Manuel Delicado, JRNitre, jtagcat, Julian Lehrhuber, Jörg Thalheim, Jędrzej Kula, Kapil Sareen, Karol Różycki, Kebin Liu, Keith Harrison, Kelong Cong, Ken'ichi Kamada, Kevin Allen, Kevin Bushiri, Kevin White, Jr., klemens, Kurt Fitzner, kylosus, Lars Lehtonen, Laurent Etiemble, Leo Arias, Liu Siyuan, Lord Landon Agahnim, LSmithx2, Luiz Angelo Daros de Luca, Lukas Lihotzki, Luke Hamburg, luzpaz, Majed Abdulaziz, Marc Laporte, Marcel Meyer, Marcin Dziadus, Marcus Legendre, Mario Majila, Mark Pulford, Martchus, Mateusz Naściszewski, Mateusz Ż, mathias4833, Matic Potočnik, Matt Burke, Matt Robenolt, Matteo Ruina, mattn, Maurizio Tomasi, Max, Max Schulze, MaximAL, Maximilian, Maxwell G, Michael Jephcote, Michael Rienstra, Michael Wang 汪東陽, MichaIng, Migelo, Mike Boone, MikeLund, MikolajTwarog, Mingxuan Lin, mv1005, Nate Morrison, nf, Nicholas Rishel, Nick Busey, Nico Stapelbroek, Nicolas Braud-Santoni, Nicolas Perraut, Niels Peter Roest, Nils Jakobi, NinoM4ster, Nitroretro, NoLooseEnds, Oliver Freyermuth, orangekame3, otbutz, overkill, Oyebanji Jacob Mayowa, Pablo, Pascal Jungblut, Paul Brit, Paul Donald, Pawel Palenica, perewa, Peter Badida, Peter Dave Hello, Peter Hoeg, Peter Marquardt, Phani Rithvij, Phil Davis, Philippe Schommers, Phill Luby, Piotr Bejda, polyfloyd, Prathik P Kulkarni, pullmerge, Quentin Hibon, Rahmi Pruitt, RealCharlesChia, red_led, Robert Carosi, Roberto Santalla, Robin Schoonover, Roman Zaynetdinov, rubenbe, Ruslan Yevdokymov, Ryan Qian, Ryan Sullivan, Sacheendra Talluri, Scott Klupfel, sec65, Sergey Mishin, Sertonix, Severin von Wnuck-Lipinski, Shaarad Dalvi, Shablone, Shivam Kumar, Simon Mwepu, Simon Pickup, Sly_tom_cat, Sonu Kumar Saw, Stefan Kuntz, Steven Eckhoff, Suhas Gundimeda, Sven Bachmann, Sébastien WENSKE, Tao, Taylor Khan, Terrance, TheCreeper, Thomas, Thomas Hipp, Tim Abell, Tim Howes, Tobias Frölich, Tobias Klauser, Tobias Nygren, Tobias Tom, Tom Jakubowski, Tully Robinson, Tyler Brazier, Tyler Kropp, Umer-Azaz, Unrud, Val Markovic, vapatel2, Veeti Paananen, Victor Buinsky, Vik, Vil Brekin, villekalliomaki, Vladimir Rusinov, vvaswani, wangguoliang, WangXi, Will Rouesnel, William A. Kennington III, wouter bolsterlee, xarx00, Xavier O., xjtdy888, Yannic A., yparitcher, 佛跳墙, 落心
</div>
</div>
</div>
@@ -2997,6 +2997,9 @@ angular.module('syncthing.core')
$scope.restoreVersions.tree = $("#restoreTree").fancytree({
extensions: ["table", "filter", "glyph"],
quicksearch: true,
// Node titles are remote-controlled file/path
// components; render them as text, not HTML.
escapeTitles: true,
filter: {
hideExpanders: true,
mode: "hide"
+1
View File
@@ -79,6 +79,7 @@ type DB interface {
// Cleanup
DropAllFiles(folder string, device protocol.DeviceID) error
DropFolderDevice(folder string, device protocol.DeviceID) error
DropDevice(device protocol.DeviceID) error
DropFilesNamed(folder string, device protocol.DeviceID, names []string) error
DropFolder(folder string) error
+5
View File
@@ -128,6 +128,11 @@ func (m metricsDB) DropAllFiles(folder string, device protocol.DeviceID) error {
return m.DB.DropAllFiles(folder, device)
}
func (m metricsDB) DropFolderDevice(folder string, device protocol.DeviceID) error {
defer m.account(folder, "DropFolderDevice")()
return m.DB.DropFolderDevice(folder, device)
}
func (m metricsDB) DropDevice(device protocol.DeviceID) error {
defer m.account("-", "DropDevice")()
return m.DB.DropDevice(device)
+11
View File
@@ -255,6 +255,17 @@ func (s *DB) DropAllFiles(folder string, device protocol.DeviceID) error {
return fdb.DropAllFiles(device)
}
func (s *DB) DropFolderDevice(folder string, device protocol.DeviceID) error {
fdb, err := s.getFolderDB(folder, false)
if errors.Is(err, errNoSuchFolder) {
return nil
}
if err != nil {
return err
}
return fdb.DropDevice(device)
}
func (s *DB) DropFilesNamed(folder string, device protocol.DeviceID, names []string) error {
fdb, err := s.getFolderDB(folder, false)
if errors.Is(err, errNoSuchFolder) {
+83 -2
View File
@@ -13,7 +13,6 @@ import (
"errors"
"iter"
"os"
"path"
"path/filepath"
"sync"
"testing"
@@ -799,6 +798,13 @@ func TestDropAllFiles(t *testing.T) {
t.Fatal(err)
}
// The sequence is non-zero before the drop
if seq, err := db.GetDeviceSequence("a", protocol.DeviceID{1}); err != nil {
t.Fatal(err)
} else if seq == 0 {
t.Error("expected non-zero sequence before drop")
}
// Drop folder A
if err := db.DropAllFiles("a", protocol.DeviceID{1}); err != nil {
t.Fatal(err)
@@ -826,6 +832,20 @@ func TestDropAllFiles(t *testing.T) {
t.Error("expected count to be two")
}
// The device sequence for the dropped folder is reset to zero.
if seq, err := db.GetDeviceSequence("a", protocol.DeviceID{1}); err != nil {
t.Fatal(err)
} else if seq != 0 {
t.Log(seq)
t.Error("expected sequence to be reset to zero after DropAllFiles")
}
// Sequence for the untouched folder is unaffected.
if seq, err := db.GetDeviceSequence("b", protocol.DeviceID{1}); err != nil {
t.Fatal(err)
} else if seq == 0 {
t.Error("expected non-zero sequence for untouched folder")
}
// Drop things that don't exist
if err := db.DropAllFiles("a", protocol.DeviceID{99}); err != nil {
t.Fatal(err)
@@ -838,6 +858,67 @@ func TestDropAllFiles(t *testing.T) {
}
}
func TestDropFolderDevice(t *testing.T) {
db, err := Open(t.TempDir())
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() {
if err := db.Close(); err != nil {
t.Fatal(err)
}
})
// Files from device 1 in folder a
err = db.Update("a", protocol.DeviceID{1}, []protocol.FileInfo{
genFile("test1", 1, 101),
genFile("test2", 2, 102),
})
if err != nil {
t.Fatal(err)
}
// Device 1 has an index ID.
if err := db.SetIndexID("a", protocol.DeviceID{1}, protocol.IndexID(0xdeadbeef)); err != nil {
t.Fatal(err)
}
if id, err := db.GetIndexID("a", protocol.DeviceID{1}); err != nil {
t.Fatal(err)
} else if id != protocol.IndexID(0xdeadbeef) {
t.Errorf("expected index ID to be set, got %v", id)
}
// Drop device 1 from folder a
if err := db.DropFolderDevice("a", protocol.DeviceID{1}); err != nil {
t.Fatal(err)
}
// Files for device 1 in folder a are gone
if _, ok, err := db.GetDeviceFile("a", protocol.DeviceID{1}, "test1"); err != nil || ok {
t.Log(err, ok)
t.Error("expected device 1 file in folder A to not exist")
}
if c, err := db.CountLocal("a", protocol.DeviceID{1}); err != nil {
t.Fatal(err)
} else if c.Files != 0 {
t.Log(c)
t.Error("expected device 1 count in folder A to be zero")
}
// The index ID for device 1 in folder A is gone.
if id, err := db.GetIndexID("a", protocol.DeviceID{1}); err != nil {
t.Fatal(err)
} else if id != 0 {
t.Errorf("expected index ID to be cleared, got %v", id)
}
if seq, err := db.GetDeviceSequence("a", protocol.DeviceID{1}); err != nil {
t.Fatal(err)
} else if seq != 0 {
t.Log(seq)
t.Error("expected sequence to be zero after DropFolderDevice")
}
}
func TestConcurrentUpdate(t *testing.T) {
t.Parallel()
@@ -1168,7 +1249,7 @@ func TestOpenSpecialName(t *testing.T) {
// Create a "base" dir that is in the way if the path becomes
// incorrectly truncated in the next steps.
base := path.Join(dir, "test")
base := filepath.Join(dir, "test")
if err := os.Mkdir(base, 0o755); err != nil {
t.Fatal(err)
}
+7
View File
@@ -235,6 +235,13 @@ func (s *folderDB) DropAllFiles(device protocol.DeviceID) error {
defer tx.Rollback() //nolint:errcheck
txp := &txPreparedStmts{Tx: tx}
if _, err := tx.Exec(`
UPDATE indexids SET sequence = 0
WHERE device_idx = ?
`, deviceIdx); err != nil {
return wrap(err)
}
// Drop all the file entries
result, err := tx.Exec(`
+1 -1
View File
@@ -1248,7 +1248,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 := os.WriteFile(zipFilePath, zipFilesBuffer.Bytes(), 0o600); err != nil {
if err := os.WriteFile(zipFilePath, zipFilesBuffer.Bytes(), 0o666); err != nil {
slog.Warn("Failed to create support bundle zip (file)", slogutil.FilePath(zipFilePath), slogutil.Error(err))
}
+1 -1
View File
@@ -16,7 +16,7 @@ import (
// or, somewhere along the way the "+" in the version tag disappeared:
// syncthing v1.23.7-dev.26.gdf7b56ae.dirty-stversionextra "Fermium Flea" (go1.20.5 darwin-arm64) jb@ok.kastelo.net 2023-07-12 06:55:26 UTC [Some Wrapper, purego, stnoupgrade]
var (
longVersionRE = regexp.MustCompile(`syncthing\s+(v[^\s]+)\s+"([^"]+)"\s\(([^\s]+)\s+([^-]+)-([^)]+)\)\s+([^\s]+)[^\[]*(?:\[(.+)\])?$`)
longVersionRE = regexp.MustCompile(`syncthing\s+(v[^\s]+)\s+"([^"]+)"\s\(([^\s]+)\s+([^-]+)-([^)]+)\)\s+([^\s]+)[^\[]*(?:\[(.+)\])?`)
gitExtraRE = regexp.MustCompile(`\.\d+\.g[0-9a-f]+`) // ".1.g6aaae618"
gitExtraSepRE = regexp.MustCompile(`[.-]`) // dot or dash
)
+14
View File
@@ -57,6 +57,20 @@ func TestParseVersion(t *testing.T) {
Extra: []string{"Some Wrapper", "purego", "stnoupgrade"},
},
},
{
longVersion: `2026-05-18 14:53:32 INF syncthing v2.0.3 "Hafnium Hornet" (go1.25.0 darwin-amd64) builder@github.syncthing.net 2025-08-22 07:00:05 UTC [stnoupgrade] (log.pkg=main)`,
parsed: VersionParts{
Version: "v2.0.3",
Tag: "v2.0.3",
Commit: "",
Codename: "Hafnium Hornet",
Runtime: "go1.25.0",
GOOS: "darwin",
GOARCH: "amd64",
Builder: "builder@github.syncthing.net",
Extra: []string{"stnoupgrade"},
},
},
}
for _, tc := range cases {
+3 -12
View File
@@ -168,7 +168,7 @@ func (f *FolderConfiguration) CreateMarker() error {
ffs := f.Filesystem()
// Create the marker as a directory
err := ffs.Mkdir(DefaultMarkerName, 0o755)
err := ffs.Mkdir(DefaultMarkerName, fs.ModePerm)
if err != nil {
return err
}
@@ -176,7 +176,7 @@ func (f *FolderConfiguration) CreateMarker() error {
// Create a file inside it, reducing the risk of the marker directory
// being removed by automated cleanup tools.
markerFile := filepath.Join(DefaultMarkerName, f.markerFilename())
if err := fs.WriteFile(ffs, markerFile, f.markerContents(), 0o644); err != nil {
if err := fs.WriteFile(ffs, markerFile, f.markerContents(), 0o666); err != nil {
return err
}
@@ -246,19 +246,10 @@ func (f *FolderConfiguration) checkFilesystemPath(ffs fs.Filesystem, path string
}
func (f *FolderConfiguration) CreateRoot() (err error) {
// Directory permission bits. Will be filtered down to something
// sane by umask on Unixes.
permBits := fs.FileMode(0o777)
if build.IsWindows {
// Windows has no umask so we must chose a safer set of bits to
// begin with.
permBits = 0o700
}
filesystem := f.Filesystem()
if _, err = filesystem.Stat("."); fs.IsNotExist(err) {
err = filesystem.MkdirAll(".", permBits)
err = filesystem.MkdirAll(".", fs.ModePerm)
}
return err
+6 -14
View File
@@ -18,7 +18,6 @@ import (
"sync"
"github.com/syncthing/syncthing/internal/slogutil"
"github.com/syncthing/syncthing/lib/build"
"github.com/syncthing/syncthing/lib/fs"
"github.com/syncthing/syncthing/lib/netutil"
"github.com/syncthing/syncthing/lib/upgrade"
@@ -224,27 +223,20 @@ func migrateToConfigV24(cfg *Configuration) {
}
func migrateToConfigV23(cfg *Configuration) {
permBits := fs.FileMode(0o777)
if build.IsWindows {
// Windows has no umask so we must chose a safer set of bits to
// begin with.
permBits = 0o700
}
// Upgrade code remains hardcoded for .stfolder despite configurable
// marker name in later versions.
for i := range cfg.Folders {
fs := cfg.Folders[i].Filesystem()
ffs := cfg.Folders[i].Filesystem()
// Invalid config posted, or tests.
if fs == nil {
if ffs == nil {
continue
}
if stat, err := fs.Stat(DefaultMarkerName); err == nil && !stat.IsDir() {
err = fs.Remove(DefaultMarkerName)
if stat, err := ffs.Stat(DefaultMarkerName); err == nil && !stat.IsDir() {
err = ffs.Remove(DefaultMarkerName)
if err == nil {
err = fs.Mkdir(DefaultMarkerName, permBits)
fs.Hide(DefaultMarkerName) // ignore error
err = ffs.Mkdir(DefaultMarkerName, fs.ModePerm)
ffs.Hide(DefaultMarkerName) // ignore error
}
if err != nil {
slog.Warn("Failed to upgrade folder marker", slogutil.Error(err))
+5
View File
@@ -827,6 +827,11 @@ func (s *service) logListenAddressesChangedEvent(l ListenerAddresses) {
func (s *service) CommitConfiguration(from, to config.Configuration) bool {
newDevices := make(map[protocol.DeviceID]bool, len(to.Devices))
for _, dev := range to.Devices {
if dev.DeviceID == s.myID {
// Do not report connection metrics for ourselves
continue
}
newDevices[dev.DeviceID] = true
registerDeviceMetrics(dev.DeviceID.String())
}
+5 -3
View File
@@ -13,6 +13,7 @@ import (
"log/slog"
"net"
"net/url"
"slices"
"sync"
"time"
@@ -185,9 +186,10 @@ func (t *tcpListener) WANAddresses() []*url.URL {
t.mut.RUnlock()
// If we support ReusePort, add an unspecified zero port address, which will be resolved by the discovery server
// in hopes that TCP punch through works.
if dialer.SupportsReusePort {
// If we support ReusePort, and we are already announcing an unspecified
// address, add an unspecified zero port address, which will be resolved
// by the discovery server in hopes that TCP punch through works.
if dialer.SupportsReusePort && slices.ContainsFunc(uris, func(u *url.URL) bool { return u.Hostname() == "0.0.0.0" }) {
uri := *t.uri
uri.Host = "0.0.0.0:0"
uris = append([]*url.URL{&uri}, uris...)
-1
View File
@@ -289,7 +289,6 @@ func (c *globalClient) sendAnnouncement(ctx context.Context, timer *time.Timer)
resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode > 299 {
slog.DebugContext(ctx, "announce POST", "server", c.server, "status", resp.Status)
c.setError(errors.New(resp.Status))
if h := resp.Header.Get("Retry-After"); h != "" {
+4 -19
View File
@@ -174,7 +174,7 @@ func (f *BasicFilesystem) MkdirAll(path string, perm FileMode) error {
return err
}
return f.mkdirAll(path, os.FileMode(perm))
return os.MkdirAll(path, os.FileMode(perm))
}
func (f *BasicFilesystem) Lstat(name string) (FileInfo, error) {
@@ -241,15 +241,7 @@ func (f *BasicFilesystem) DirNames(name string) ([]string, error) {
}
func (f *BasicFilesystem) Open(name string) (File, error) {
rootedName, err := f.rooted(name)
if err != nil {
return nil, err
}
fd, err := os.Open(rootedName)
if err != nil {
return nil, err
}
return basicFile{fd, name}, err
return f.OpenFile(name, os.O_RDONLY, 0)
}
func (f *BasicFilesystem) OpenFile(name string, flags int, mode FileMode) (File, error) {
@@ -257,6 +249,7 @@ func (f *BasicFilesystem) OpenFile(name string, flags int, mode FileMode) (File,
if err != nil {
return nil, err
}
flags |= alwaysOpenFlags // enforce extra bits in flags
fd, err := os.OpenFile(rootedName, flags, os.FileMode(mode))
if err != nil {
return nil, err
@@ -265,15 +258,7 @@ func (f *BasicFilesystem) OpenFile(name string, flags int, mode FileMode) (File,
}
func (f *BasicFilesystem) Create(name string) (File, error) {
rootedName, err := f.rooted(name)
if err != nil {
return nil, err
}
fd, err := os.Create(rootedName)
if err != nil {
return nil, err
}
return basicFile{fd, name}, err
return f.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o666)
}
func (*BasicFilesystem) Walk(_ string, _ WalkFunc) error {
+3 -4
View File
@@ -14,8 +14,11 @@ import (
"path/filepath"
"strconv"
"strings"
"syscall"
)
const alwaysOpenFlags = syscall.O_NOFOLLOW // never open symlinks as the final path component
func (f *BasicFilesystem) CreateSymlink(target, name string) error {
name, err := f.rooted(name)
if err != nil {
@@ -32,10 +35,6 @@ func (f *BasicFilesystem) ReadSymlink(name string) (string, error) {
return os.Readlink(name)
}
func (*BasicFilesystem) mkdirAll(path string, perm os.FileMode) error {
return os.MkdirAll(path, perm)
}
// Unhide is a noop on unix, as unhiding files requires renaming them.
// We still check that the relative path does not try to escape the root
func (f *BasicFilesystem) Unhide(name string) error {
+2 -51
View File
@@ -21,6 +21,8 @@ import (
"golang.org/x/sys/windows"
)
const alwaysOpenFlags = 0 // no extra flags
var errNotSupported = errors.New("symlinks not supported")
func (BasicFilesystem) ReadSymlink(path string) (string, error) {
@@ -31,57 +33,6 @@ func (BasicFilesystem) CreateSymlink(target, name string) error {
return errNotSupported
}
// Required due to https://github.com/golang/go/issues/10900
func (f *BasicFilesystem) mkdirAll(path string, perm os.FileMode) error {
// Fast path: if we can tell whether path is a directory or file, stop with success or error.
dir, err := os.Stat(path)
if err == nil {
if dir.IsDir() {
return nil
}
return &os.PathError{
Op: "mkdir",
Path: path,
Err: syscall.ENOTDIR,
}
}
// Slow path: make sure parent exists and then call Mkdir for path.
i := len(path)
for i > 0 && IsPathSeparator(path[i-1]) { // Skip trailing path separator.
i--
}
j := i
for j > 0 && !IsPathSeparator(path[j-1]) { // Scan backward over element.
j--
}
if j > 1 {
// Create parent
parent := path[0 : j-1]
if parent != filepath.VolumeName(parent) {
err = f.mkdirAll(parent, perm)
if err != nil {
return err
}
}
}
// Parent now exists; invoke Mkdir and use its result.
err = os.Mkdir(path, perm)
if err != nil {
// Handle arguments like "foo/." by
// double-checking that directory doesn't exist.
dir, err1 := os.Lstat(path)
if err1 == nil && dir.IsDir() {
return nil
}
return err
}
return nil
}
func (f *BasicFilesystem) Unhide(name string) error {
name, err := f.rooted(name)
if err != nil {
+2 -2
View File
@@ -153,7 +153,7 @@ func newFakeFilesystem(rootURI string, _ ...Option) *fakeFS {
for (files == 0 || createdFiles < files) && (maxsize == 0 || writtenData>>20 < int64(maxsize)) {
dir := filepath.Join(fmt.Sprintf("%02x", rng.Intn(255)), fmt.Sprintf("%02x", rng.Intn(255)))
file := fmt.Sprintf("%016x", rng.Int63())
_ = fs.MkdirAll(dir, 0o755)
_ = fs.MkdirAll(dir, ModePerm)
fd, _ := fs.Create(filepath.Join(dir, file))
createdFiles++
@@ -169,7 +169,7 @@ func newFakeFilesystem(rootURI string, _ ...Option) *fakeFS {
if !nostfolder {
// Also create a default folder marker for good measure
_ = fs.Mkdir(".stfolder", 0o700)
_ = fs.Mkdir(".stfolder", ModePerm)
}
// We only set the latency after doing the operations required to create
+15 -2
View File
@@ -382,7 +382,7 @@ loop:
if err != nil {
return nil, nil, err
}
if hasCurFile && file.BlocksEqual(curFile) {
if hasCurFile && curFile.Type == file.Type && file.BlocksEqual(curFile) {
// We are supposed to copy the entire file, and then fetch nothing. We
// are only updating metadata, so we don't actually *need* to make the
// copy.
@@ -701,7 +701,7 @@ func (f *sendReceiveFolder) checkParent(file string, scanChan chan<- string) boo
return true
}
f.sl.Debug("Creating parent directory", slogutil.FilePath(file))
if err := f.mtimefs.MkdirAll(parent, 0o755); err != nil {
if err := f.mtimefs.MkdirAll(parent, fs.ModePerm); err != nil {
f.newPullError(file, fmt.Errorf("creating parent dir: %w", err))
return false
}
@@ -1345,6 +1345,12 @@ func (f *sendReceiveFolder) copierRoutine(ctx context.Context, in <-chan copyBlo
default:
}
if block.Size == 0 {
// Copying zero bytes is a no-op.
state.copyDone(block)
continue
}
if !f.DisableSparseFiles && state.reused == 0 && block.IsEmpty() {
// The block is a block of all zeroes, and we are not reusing
// a temp file, so there is no need to do anything with it.
@@ -1534,6 +1540,13 @@ func (f *sendReceiveFolder) pullerRoutine(ctx context.Context, in <-chan pullBlo
bytes := state.block.Size
if bytes == 0 {
// Pulling zero bytes is a no-op.
state.pullDone(state.block)
out <- state.sharedPullerState
continue
}
if err := requestLimiter.TakeWithContext(ctx, bytes); err != nil {
state.fail(err)
out <- state.sharedPullerState
+1 -1
View File
@@ -891,7 +891,7 @@ func TestPullCtxCancel(t *testing.T) {
emptyState := func() pullBlockState {
return pullBlockState{
sharedPullerState: newSharedPullerState(protocol.FileInfo{}, nil, f.folderID, "", nil, nil, false, false, protocol.FileInfo{}, false, false),
block: protocol.BlockInfo{},
block: protocol.BlockInfo{Size: 42},
}
}
+2 -2
View File
@@ -20,7 +20,7 @@ import (
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/svcutil"
"github.com/syncthing/syncthing/lib/ur"
"github.com/syncthing/syncthing/lib/ur/contract"
)
type indexHandler struct {
@@ -470,7 +470,7 @@ func (s *indexHandler) logSequenceAnomaly(msg string, extra map[string]any) {
extraStrs[k] = fmt.Sprint(v)
}
s.evLogger.Log(events.Failure, ur.FailureData{
s.evLogger.Log(events.Failure, contract.FailureData{
Description: msg,
Extra: extraStrs,
})
+3 -3
View File
@@ -371,7 +371,7 @@ func (m *model) addAndStartFolderLockedWithIgnores(cfg config.FolderConfiguratio
for _, available := range devs {
if _, ok := expected[available]; !ok {
l.Debugln("dropping", folder, "state for", available)
_ = m.sdb.DropAllFiles(folder, available)
_ = m.sdb.DropFolderDevice(folder, available)
}
}
@@ -1784,7 +1784,7 @@ func (m *model) handleAutoAccepts(deviceID protocol.DeviceID, folder protocol.Fo
// Attempt to create it to make sure it does, now.
fullPath := filepath.Join(defaultFolderCfg.Path, path)
if err := defaultPathFs.MkdirAll(path, 0o700); err != nil {
if err := defaultPathFs.MkdirAll(path, fs.ModePerm); err != nil {
slog.Error("Failed to create path for auto-accepted folder", folder.LogAttr(), slogutil.FilePath(fullPath), slogutil.Error(err))
continue
}
@@ -2079,7 +2079,7 @@ func (m *model) Request(conn protocol.Connection, req *protocol.Request) (out pr
return nil, protocol.ErrGeneric
}
if folderCfg.Type != config.FolderTypeReceiveEncrypted && len(req.Hash) > 0 && !scanner.Validate(res.data[:n], req.Hash) {
if folderCfg.Type != config.FolderTypeReceiveEncrypted && !scanner.Validate(res.data[:n], req.Hash) {
m.recheckFile(deviceID, req.Folder, req.Name, req.Offset, req.Hash)
l.Debugf("%v REQ(in) failed validating data: %s: %q / %q o=%d s=%d", m, deviceID.Short(), req.Folder, req.Name, req.Offset, req.Size)
return nil, protocol.ErrNoSuchFile
+20 -9
View File
@@ -9,6 +9,7 @@ package model
import (
"bytes"
"context"
"crypto/sha256"
"encoding/json"
"errors"
"fmt"
@@ -93,8 +94,10 @@ func TestRequest(t *testing.T) {
m.ScanFolder("default")
foobarHash := sha256.Sum256([]byte("foobar"))
// Existing, shared file
res, err := m.Request(device1Conn, &protocol.Request{Folder: "default", Name: "foo", Size: 6})
res, err := m.Request(device1Conn, &protocol.Request{Folder: "default", Name: "foo", Size: 6, Hash: foobarHash[:]})
if err != nil {
t.Fatal(err)
}
@@ -104,35 +107,42 @@ func TestRequest(t *testing.T) {
}
// Existing, nonshared file
_, err = m.Request(device2Conn, &protocol.Request{Folder: "default", Name: "foo", Size: 6})
_, err = m.Request(device2Conn, &protocol.Request{Folder: "default", Name: "foo", Size: 6, Hash: foobarHash[:]})
if err == nil {
t.Error("Unexpected nil error on insecure file read")
}
// Nonexistent file
_, err = m.Request(device1Conn, &protocol.Request{Folder: "default", Name: "nonexistent", Size: 6})
_, err = m.Request(device1Conn, &protocol.Request{Folder: "default", Name: "nonexistent", Size: 6, Hash: foobarHash[:]})
if err == nil {
t.Error("Unexpected nil error on insecure file read")
}
// Shared folder, but disallowed file name
_, err = m.Request(device1Conn, &protocol.Request{Folder: "default", Name: "../walk.go", Size: 6})
_, err = m.Request(device1Conn, &protocol.Request{Folder: "default", Name: "../walk.go", Size: 6, Hash: foobarHash[:]})
if err == nil {
t.Error("Unexpected nil error on insecure file read")
}
// Negative size
_, err = m.Request(device1Conn, &protocol.Request{Folder: "default", Name: "foo", Size: -4})
_, err = m.Request(device1Conn, &protocol.Request{Folder: "default", Name: "foo", Size: -4, Hash: foobarHash[:]})
if err == nil {
t.Error("Unexpected nil error on insecure file read")
}
// Larger block than available
// Missing hash
_, err = m.Request(device1Conn, &protocol.Request{Folder: "default", Name: "foo", Size: 6})
if err == nil {
t.Error("Unexpected nil error on request without hash")
}
// Larger block than available, with a mismatched hash
_, err = m.Request(device1Conn, &protocol.Request{Folder: "default", Name: "foo", Size: 42, Hash: []byte("hash necessary but not checked")})
if err == nil {
t.Error("Unexpected nil error on read past end of file")
}
_, err = m.Request(device1Conn, &protocol.Request{Folder: "default", Name: "foo", Size: 42})
// Larger block than available, with the matching hash of the short read
_, err = m.Request(device1Conn, &protocol.Request{Folder: "default", Name: "foo", Size: 42, Hash: foobarHash[:]})
if err != nil {
t.Error("Unexpected error when large read should be permitted")
}
@@ -2979,15 +2989,16 @@ func TestRequestLimit(t *testing.T) {
defer cleanupModel(m)
m.ScanFolder("default")
emptyHash := sha256.Sum256(nil)
befReq := time.Now()
first, err := m.Request(conn, &protocol.Request{Folder: "default", Name: file, Size: 2000})
first, err := m.Request(conn, &protocol.Request{Folder: "default", Name: file, Size: 2000, Hash: emptyHash[:]})
if err != nil {
t.Fatalf("First request failed: %v", err)
}
reqDur := time.Since(befReq)
returned := make(chan struct{})
go func() {
second, err := m.Request(conn, &protocol.Request{Folder: "default", Name: file, Size: 2000})
second, err := m.Request(conn, &protocol.Request{Folder: "default", Name: file, Size: 2000, Hash: emptyHash[:]})
if err != nil {
t.Errorf("Second request failed: %v", err)
}
+13 -19
View File
@@ -93,31 +93,25 @@ func (e encryptedModel) Request(req *Request) (RequestResponse, error) {
}
realSize := req.Size - blockOverhead
realOffset := req.Offset - int64(req.BlockNo*blockOverhead)
if realOffset < 0 {
panic("bug: realOffset underflow")
}
if req.Size < minPaddedSize {
return nil, errors.New("short request")
}
// Attempt to decrypt the block hash; it may be nil depending on what
// type of device the request comes from. Trusted devices with
// encryption enabled know the hash but don't bother to encrypt & send
// it to us. Untrusted devices have the hash from the encrypted index
// data and do send it. The model knows to only verify the hash if it
// actually gets one.
var realHash []byte
// Decrypt the block hash.
fileKey := e.keyGen.FileKey(realName, folderKey)
if len(req.Hash) > 0 {
var additional [8]byte
binary.BigEndian.PutUint64(additional[:], uint64(realOffset))
realHash, err = decryptDeterministic(req.Hash, fileKey, additional[:])
if err != nil {
// "Legacy", no offset additional data?
realHash, err = decryptDeterministic(req.Hash, fileKey, nil)
}
if err != nil {
return nil, fmt.Errorf("decrypting block hash: %w", err)
}
var additional [8]byte
binary.BigEndian.PutUint64(additional[:], uint64(realOffset))
realHash, err := decryptDeterministic(req.Hash, fileKey, additional[:])
if err != nil {
// "Legacy", no offset additional data?
realHash, err = decryptDeterministic(req.Hash, fileKey, nil)
}
if err != nil {
return nil, fmt.Errorf("decrypting block hash: %w", err)
}
// Perform that request and grab the data.
+24 -13
View File
@@ -68,15 +68,16 @@ const (
)
var (
ErrClosed = errors.New("connection closed")
ErrTimeout = errors.New("read timeout")
errNotCompressible = errors.New("not compressible")
errUnknownMessage = errors.New("unknown message")
errInvalidFilename = errors.New("filename is invalid")
errUncleanFilename = errors.New("filename not in canonical format")
errDeletedHasBlocks = errors.New("deleted file with non-empty block list")
errDirectoryHasBlocks = errors.New("directory with non-empty block list")
errFileHasNoBlocks = errors.New("file with empty block list")
ErrClosed = errors.New("connection closed")
ErrTimeout = errors.New("read timeout")
errNotCompressible = errors.New("not compressible")
errUnknownMessage = errors.New("unknown message")
errInvalidFilename = errors.New("filename is invalid")
errUncleanFilename = errors.New("filename not in canonical format")
errDeletedHasBlocks = errors.New("deleted file with non-empty block list")
errNonFileHasBlocks = errors.New("non-file type with non-empty block list")
errNonFileHasSize = errors.New("non-file type with nonzero size")
errFileHasNoBlocks = errors.New("file with empty block list")
)
type Model interface {
@@ -483,12 +484,18 @@ func (c *rawConnection) dispatcherLoop() (err error) {
if err := checkFilename(msg.Name); err != nil {
return newProtocolError(err, msgContext)
}
if msg.Size <= 0 {
if msg.Size < 0 {
return newProtocolError(fmt.Errorf("request size %d too small", msg.Size), msgContext)
}
if msg.Size > MaxRequestSize {
return newProtocolError(fmt.Errorf("request size %d exceeds maximum allowed", msg.Size), msgContext)
}
if len(msg.Hash) == 0 {
// Syncthing versions older than v1.28.1 omit the hash in
// encrypted requests from trusted devices (a rare config)
// and will run into this.
return newProtocolError(errors.New("request missing block hash"), msgContext)
}
go c.handleRequest(requestFromWire(msg))
case *bep.Response:
@@ -629,9 +636,13 @@ func checkFileInfoConsistency(f FileInfo) error {
// Deleted files should have no blocks
return errDeletedHasBlocks
case f.Type == FileInfoTypeDirectory && len(f.Blocks) != 0:
// Directories should have no blocks
return errDirectoryHasBlocks
case f.Type != FileInfoTypeFile && len(f.Blocks) != 0:
// Only files should have blocks
return errNonFileHasBlocks
case f.Type != FileInfoTypeFile && f.Size != 0:
// Only files should have a size
return errNonFileHasSize
case !f.Deleted && !f.IsInvalid() && f.Type == FileInfoTypeFile && len(f.Blocks) == 0:
// Non-deleted, non-invalid files should have at least one block
+33 -1
View File
@@ -544,7 +544,7 @@ func TestDispatcherToCloseDeadlock(t *testing.T) {
}
func TestRequestMaxSize(t *testing.T) {
invalidSize := []int{-65536, 0, MaxRequestSize + 1}
invalidSize := []int{-65536, -1, MaxRequestSize + 1}
for _, s := range invalidSize {
t.Run(fmt.Sprintf("invalid/%d", s), func(t *testing.T) {
m := newTestModel()
@@ -560,6 +560,7 @@ func TestRequestMaxSize(t *testing.T) {
Id: 1,
Name: "valid",
Size: MaxRequestSize,
Hash: []byte{42},
}
res := <-c.outbox
@@ -573,6 +574,7 @@ func TestRequestMaxSize(t *testing.T) {
Id: 2,
Name: "invalid",
Size: int32(s),
Hash: []byte{42},
}
select {
@@ -592,6 +594,35 @@ func TestRequestMaxSize(t *testing.T) {
}
}
func TestRequestZeroSize(t *testing.T) {
// A zero-sized request should be accepted, since current versions of
// Syncthing send these. See https://github.com/syncthing/syncthing/issues/10709.
m := newTestModel()
rw := testutil.NewBlockingRW()
c := getRawConnection(NewConnection(c0ID, rw, &testutil.NoopRW{}, testutil.NoopCloser{}, m, new(mockedConnectionInfo), CompressionAlways, testKeyGen))
c.Start()
defer closeAndWait(c, rw)
c.inbox <- &bep.ClusterConfig{}
c.inbox <- &bep.Request{
Id: 1,
Name: "valid",
Size: 0,
Hash: []byte{42},
}
select {
case res := <-c.outbox:
if msg, ok := res.msg.(*bep.Response); !ok || msg.Id != 1 {
t.Errorf("bad response %#v", msg)
}
case <-c.dispatcherLoopStopped:
t.Fatal("dispatcher loop terminated, expected zero-sized request to be accepted")
case <-time.After(time.Second):
t.Fatal("timed out waiting for response")
}
}
func TestRequestInvalidFilename(t *testing.T) {
m := newTestModel()
rw := testutil.NewBlockingRW()
@@ -604,6 +635,7 @@ func TestRequestInvalidFilename(t *testing.T) {
Id: 1,
Name: "../escape",
Size: 1024,
Hash: []byte{42},
}
select {
+3 -16
View File
@@ -120,25 +120,12 @@ func Blocks(ctx context.Context, r io.Reader, blocksize int, sizehint int64, cou
return blocks, nil
}
// Validate validates the hash, if len(hash)>0.
// Validate validates the hash.
func Validate(buf, hash []byte) bool {
if len(hash) > 0 {
hbuf := sha256.Sum256(buf)
return bytes.Equal(hbuf[:], hash)
}
return true
hbuf := sha256.Sum256(buf)
return bytes.Equal(hbuf[:], hash)
}
type noopHash struct{}
func (noopHash) Sum32() uint32 { return 0 }
func (noopHash) BlockSize() int { return 0 }
func (noopHash) Size() int { return 0 }
func (noopHash) Reset() {}
func (noopHash) Sum([]byte) []byte { return nil }
func (noopHash) Write([]byte) (int, error) { return 0, nil }
type noopCounter struct{}
func (*noopCounter) Update(_ int64) {}
+13
View File
@@ -282,3 +282,16 @@ func clear(v interface{}, since int) error {
}
return nil
}
type FailureReport struct {
FailureData
Count int
Version string
}
type FailureData struct {
Description string
Goroutines string
Extra map[string]string
}
+14 -26
View File
@@ -23,6 +23,7 @@ import (
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/svcutil"
"github.com/syncthing/syncthing/lib/tlsutil"
"github.com/syncthing/syncthing/lib/ur/contract"
"github.com/thejerf/suture/v4"
)
@@ -39,23 +40,10 @@ var (
invalidEventDataType = "failure event data is not a string"
)
type FailureReport struct {
FailureData
Count int
Version string
}
type FailureData struct {
Description string
Goroutines string
Extra map[string]string
}
func FailureDataWithGoroutines(description string) FailureData {
func FailureDataWithGoroutines(description string) contract.FailureData {
var buf strings.Builder
pprof.Lookup("goroutine").WriteTo(&buf, 1)
return FailureData{
return contract.FailureData{
Description: description,
Goroutines: buf.String(),
Extra: make(map[string]string),
@@ -86,7 +74,7 @@ type failureHandler struct {
type failureStat struct {
first, last time.Time
count int
data FailureData
data contract.FailureData
}
func (h *failureHandler) Serve(ctx context.Context) error {
@@ -105,24 +93,24 @@ func (h *failureHandler) Serve(ctx context.Context) error {
if !ok {
// Just to be safe - shouldn't ever happen, as
// evChan is set to nil when unsubscribing.
h.addReport(FailureData{Description: evChanClosed}, time.Now())
h.addReport(contract.FailureData{Description: evChanClosed}, time.Now())
evChan = nil
continue
}
var data FailureData
var data contract.FailureData
switch d := e.Data.(type) {
case string:
data.Description = d
case FailureData:
case contract.FailureData:
data = d
default:
// Same here, shouldn't ever happen.
h.addReport(FailureData{Description: invalidEventDataType}, time.Now())
h.addReport(contract.FailureData{Description: invalidEventDataType}, time.Now())
continue
}
h.addReport(data, e.Time)
case <-timer.C:
reports := make([]FailureReport, 0, len(h.buf))
reports := make([]contract.FailureReport, 0, len(h.buf))
now := time.Now()
for descr, stat := range h.buf {
if now.Sub(stat.last) > minDelay || now.Sub(stat.first) > maxDelay {
@@ -152,7 +140,7 @@ func (h *failureHandler) Serve(ctx context.Context) error {
if sub != nil {
sub.Unsubscribe()
if len(h.buf) > 0 {
reports := make([]FailureReport, 0, len(h.buf))
reports := make([]contract.FailureReport, 0, len(h.buf))
for _, stat := range h.buf {
reports = append(reports, newFailureReport(stat))
}
@@ -179,7 +167,7 @@ func (h *failureHandler) applyOpts(opts config.OptionsConfiguration, sub events.
return url, nil, nil
}
func (h *failureHandler) addReport(data FailureData, evTime time.Time) {
func (h *failureHandler) addReport(data contract.FailureData, evTime time.Time) {
if stat, ok := h.buf[data.Description]; ok {
stat.last = evTime
stat.count++
@@ -204,7 +192,7 @@ func (*failureHandler) String() string {
return "FailureHandler"
}
func sendFailureReports(ctx context.Context, reports []FailureReport, url string) {
func sendFailureReports(ctx context.Context, reports []contract.FailureReport, url string) {
var b bytes.Buffer
if err := json.NewEncoder(&b).Encode(reports); err != nil {
panic(err)
@@ -235,8 +223,8 @@ func sendFailureReports(ctx context.Context, reports []FailureReport, url string
resp.Body.Close()
}
func newFailureReport(stat *failureStat) FailureReport {
return FailureReport{
func newFailureReport(stat *failureStat) contract.FailureReport {
return contract.FailureReport{
FailureData: stat.data,
Count: stat.count,
Version: build.LongVersion,
+55
View File
@@ -15,6 +15,7 @@ import (
"github.com/syncthing/syncthing/lib/build"
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/fs"
)
func TestTaggedFilename(t *testing.T) {
@@ -256,3 +257,57 @@ func TestArchiveFoldersCreationPermission(t *testing.T) {
t.Errorf("földer2 permissions %v, want %v", folder2VersionsInfo.Mode(), folder2Perms)
}
}
func TestDupDirTreeWritePermissions(t *testing.T) {
// The structure should be replicated, with user permission bits set along the way
srcFs := fs.NewFilesystem(fs.FilesystemTypeFake, "TestDupDirTreeWritePermissions/srcFs")
dstFs := fs.NewFilesystem(fs.FilesystemTypeFake, "TestDupDirTreeWritePermissions/dstFs")
// A source dir to duplicate
_ = srcFs.Mkdir("foo", 0o444)
_ = srcFs.Mkdir("foo/bar", 0o555)
_ = srcFs.Mkdir("foo/bar/baz", 0o000)
// Duplication should succeed
if err := dupDirTree(srcFs, dstFs, "foo/bar/baz"); err != nil {
t.Fatal(err)
}
// Permissions should be the same, but with read/write/execute bits for
// the user
if info, err := dstFs.Lstat("foo"); err != nil || info.Mode() != 0o744 {
t.Fatalf("foo: 0o%o", info.Mode())
}
if info, err := dstFs.Lstat("foo/bar"); err != nil || info.Mode() != 0o755 {
t.Fatalf("foo/bar: 0o%o", info.Mode())
}
if info, err := dstFs.Lstat("foo/bar/baz"); err != nil || info.Mode() != 0o700 {
t.Fatalf("foo/bar/baz: 0o%o", info.Mode())
}
}
func TestDupDirFastPath(t *testing.T) {
srcFs := fs.NewFilesystem(fs.FilesystemTypeFake, "TestDupDirFastPath/srcFs")
dstFs := fs.NewFilesystem(fs.FilesystemTypeFake, "TestDupDirFastPath/dstFs")
// A source dir to duplicate
_ = srcFs.Mkdir("foo", 0o444)
_ = srcFs.Mkdir("foo/bar", 0o555)
_ = srcFs.Mkdir("foo/bar/baz", 0o000)
// The destination exists, but with too few permission bits
_ = dstFs.MkdirAll("foo/bar/baz", 0o555)
// Duplication should succeed
if err := dupDirTree(srcFs, dstFs, "foo/bar/baz"); err != nil {
t.Fatal(err)
}
// Permissions for the destination should have been updated. (This
// differs from what would have been created by the duplication of the
// 0o000 dir in the src, because it already existed.)
if info, err := dstFs.Lstat("foo/bar/baz"); err != nil || info.Mode() != 0o755 {
t.Fatalf("foo/bar/baz: 0o%o", info.Mode())
}
}
+68 -38
View File
@@ -155,7 +155,7 @@ func archiveFile(method fs.CopyRangeMethod, srcFs, dstFs fs.Filesystem, filePath
if err != nil {
if fs.IsNotExist(err) {
slog.Debug("Creating versions dir")
err := dstFs.MkdirAll(".", 0o755)
err := dstFs.MkdirAll(".", fs.ModePerm)
if err != nil {
return err
}
@@ -192,48 +192,78 @@ func archiveFile(method fs.CopyRangeMethod, srcFs, dstFs fs.Filesystem, filePath
return err
}
func dupDirTree(srcFs, dstFs fs.Filesystem, folderPath string) error {
// Return early if the folder already exists.
_, err := dstFs.Stat(folderPath)
if err == nil || !fs.IsNotExist(err) {
return err
// dupDirTree ensures folderPath exists in dstFs, copying permissions mostly
// from srcFs. Permissions are altered to have the user read, write, and
// execute bits set so that Syncthing file operations are possible within
// the destination directory.
//
// We want to retain the source group and other bits so that we do not
// inadvertently open up a directory for users who shouldn't have access to
// it, but we do not consider it a security issue to open up the permissions
// for the current user.
//
// This is based on os.MkdirAll with our srcFs adjustments.
func dupDirTree(srcFs, dstFs fs.Filesystem, path string) error {
const (
allPerms = 0o777
minDirPerms = 0o700
)
// Fast path: if we can tell whether path is a directory or file, stop with success or error.
if dir, err := dstFs.Lstat(path); err == nil {
if !dir.IsDir() {
return errors.New("destination exists but is not a directory")
}
if dir.Mode()&minDirPerms != minDirPerms {
// We want all the required permission bits set
_ = dstFs.Chmod(path, dir.Mode()&allPerms|minDirPerms)
}
return nil
}
hadParent := true
for i := range folderPath {
if os.IsPathSeparator(folderPath[i]) {
// If the parent folder didn't exist, then this folder doesn't exist
// so we can skip the check
if hadParent {
_, err := dstFs.Stat(folderPath[:i])
if err == nil {
continue
}
if !fs.IsNotExist(err) {
return err
}
}
hadParent = false
err := dupDirWithPerms(srcFs, dstFs, folderPath[:i])
if err != nil {
return err
}
// Slow path: make sure parent exists and then call Mkdir for path.
// Extract the parent folder from path by first removing any trailing
// path separator and then scanning backward until finding a path
// separator or reaching the beginning of the string.
i := len(path) - 1
for i >= 0 && os.IsPathSeparator(path[i]) {
i--
}
for i >= 0 && !os.IsPathSeparator(path[i]) {
i--
}
if i < 0 {
i = 0
}
// If there is a parent directory, and it is not the volume name,
// recurse to ensure parent directory exists.
if parent := path[:i]; len(parent) > len(filepath.VolumeName(path)) {
if err := dupDirTree(srcFs, dstFs, parent); err != nil {
return err
}
}
return dupDirWithPerms(srcFs, dstFs, folderPath)
}
func dupDirWithPerms(srcFs, dstFs fs.Filesystem, folderPath string) error {
srcStat, err := srcFs.Stat(folderPath)
if err != nil {
// Parent now exists; invoke Mkdir and use its result.
srcPerms := fs.FileMode(minDirPerms)
if srcDir, err := srcFs.Lstat(path); err == nil {
srcPerms = srcDir.Mode()&allPerms | minDirPerms
}
if err := dstFs.Mkdir(path, srcPerms); err != nil {
// Handle arguments like "foo/." by
// double-checking that directory doesn't exist.
dir, err1 := dstFs.Lstat(path)
if err1 == nil && dir.IsDir() {
return nil
}
return err
}
// If we call Mkdir with srcStat.Mode(), we won't get the expected perms because of umask
// So, we create the folder with 0700, and then change the perms to the srcStat.Mode()
err = dstFs.Mkdir(folderPath, 0o700)
if err != nil {
return err
}
return dstFs.Chmod(folderPath, srcStat.Mode())
// Extra chmod to ensure our permissions override umask
_ = dstFs.Chmod(path, srcPerms)
return nil
}
func restoreFile(method fs.CopyRangeMethod, src, dst fs.Filesystem, filePath string, versionTime time.Time, tagger fileTagger) error {
@@ -298,7 +328,7 @@ func restoreFile(method fs.CopyRangeMethod, src, dst fs.Filesystem, filePath str
return err
}
_ = dst.MkdirAll(filepath.Dir(filePath), 0o755)
_ = dst.MkdirAll(filepath.Dir(filePath), fs.ModePerm)
err := osutil.RenameOrCopy(method, src, dst, sourceFile, filePath)
_ = dst.Chtimes(filePath, sourceMtime, sourceMtime)
return err
+1 -1
View File
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
..
.TH "STDISCOSRV" "1" "May 13, 2026" "v2.1.0" "Syncthing"
.TH "STDISCOSRV" "1" "Jun 07, 2026" "v2.1.0" "Syncthing"
.SH NAME
stdiscosrv \- Syncthing Discovery Server
.SH SYNOPSIS
+1 -1
View File
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
..
.TH "STRELAYSRV" "1" "May 13, 2026" "v2.1.0" "Syncthing"
.TH "STRELAYSRV" "1" "Jun 07, 2026" "v2.1.0" "Syncthing"
.SH NAME
strelaysrv \- Syncthing Relay Server
.SH SYNOPSIS
+1 -1
View File
@@ -28,7 +28,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
..
.TH "SYNCTHING-BEP" "7" "May 13, 2026" "v2.1.0" "Syncthing"
.TH "SYNCTHING-BEP" "7" "Jun 07, 2026" "v2.1.0" "Syncthing"
.SH NAME
syncthing-bep \- Block Exchange Protocol v1
.SH INTRODUCTION AND DEFINITIONS
+1 -1
View File
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
..
.TH "SYNCTHING-CONFIG" "5" "May 13, 2026" "v2.1.0" "Syncthing"
.TH "SYNCTHING-CONFIG" "5" "Jun 07, 2026" "v2.1.0" "Syncthing"
.SH NAME
syncthing-config \- Syncthing Configuration
.SH OVERVIEW
+1 -1
View File
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
..
.TH "SYNCTHING-DEVICE-IDS" "7" "May 13, 2026" "v2.1.0" "Syncthing"
.TH "SYNCTHING-DEVICE-IDS" "7" "Jun 07, 2026" "v2.1.0" "Syncthing"
.SH NAME
syncthing-device-ids \- Understanding Device IDs
.sp
+1 -1
View File
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
..
.TH "SYNCTHING-EVENT-API" "7" "May 13, 2026" "v2.1.0" "Syncthing"
.TH "SYNCTHING-EVENT-API" "7" "Jun 07, 2026" "v2.1.0" "Syncthing"
.SH NAME
syncthing-event-api \- Event API
.SH DESCRIPTION
+1 -1
View File
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
..
.TH "SYNCTHING-FAQ" "7" "May 13, 2026" "v2.1.0" "Syncthing"
.TH "SYNCTHING-FAQ" "7" "Jun 07, 2026" "v2.1.0" "Syncthing"
.SH NAME
syncthing-faq \- Frequently Asked Questions
.INDENT 0.0
+1 -1
View File
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
..
.TH "SYNCTHING-GLOBALDISCO" "7" "May 13, 2026" "v2.1.0" "Syncthing"
.TH "SYNCTHING-GLOBALDISCO" "7" "Jun 07, 2026" "v2.1.0" "Syncthing"
.SH NAME
syncthing-globaldisco \- Global Discovery Protocol v3
.SH ANNOUNCEMENTS
+1 -1
View File
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
..
.TH "SYNCTHING-LOCALDISCO" "7" "May 13, 2026" "v2.1.0" "Syncthing"
.TH "SYNCTHING-LOCALDISCO" "7" "Jun 07, 2026" "v2.1.0" "Syncthing"
.SH NAME
syncthing-localdisco \- Local Discovery Protocol v4
.SH MODE OF OPERATION
+1 -1
View File
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
..
.TH "SYNCTHING-NETWORKING" "7" "May 13, 2026" "v2.1.0" "Syncthing"
.TH "SYNCTHING-NETWORKING" "7" "Jun 07, 2026" "v2.1.0" "Syncthing"
.SH NAME
syncthing-networking \- Firewall Setup
.SH ROUTER SETUP
+1 -1
View File
@@ -28,7 +28,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
..
.TH "SYNCTHING-RELAY" "7" "May 13, 2026" "v2.1.0" "Syncthing"
.TH "SYNCTHING-RELAY" "7" "Jun 07, 2026" "v2.1.0" "Syncthing"
.SH NAME
syncthing-relay \- Relay Protocol v1
.SH WHAT IS A RELAY?
+1 -1
View File
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
..
.TH "SYNCTHING-REST-API" "7" "May 13, 2026" "v2.1.0" "Syncthing"
.TH "SYNCTHING-REST-API" "7" "Jun 07, 2026" "v2.1.0" "Syncthing"
.SH NAME
syncthing-rest-api \- REST API
.sp
+1 -1
View File
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
..
.TH "SYNCTHING-SECURITY" "7" "May 13, 2026" "v2.1.0" "Syncthing"
.TH "SYNCTHING-SECURITY" "7" "Jun 07, 2026" "v2.1.0" "Syncthing"
.SH NAME
syncthing-security \- Security Principles
.sp
+1 -1
View File
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
..
.TH "SYNCTHING-STIGNORE" "5" "May 13, 2026" "v2.1.0" "Syncthing"
.TH "SYNCTHING-STIGNORE" "5" "Jun 07, 2026" "v2.1.0" "Syncthing"
.SH NAME
syncthing-stignore \- Prevent files from being synchronized to other nodes
.SH SYNOPSIS
+1 -1
View File
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
..
.TH "SYNCTHING-VERSIONING" "7" "May 13, 2026" "v2.1.0" "Syncthing"
.TH "SYNCTHING-VERSIONING" "7" "Jun 07, 2026" "v2.1.0" "Syncthing"
.SH NAME
syncthing-versioning \- Keep automatic backups of deleted files by other nodes
.sp
+1 -1
View File
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
..
.TH "SYNCTHING" "1" "May 13, 2026" "v2.1.0" "Syncthing"
.TH "SYNCTHING" "1" "Jun 07, 2026" "v2.1.0" "Syncthing"
.SH NAME
syncthing \- Syncthing
.SH SYNOPSIS
+1 -1
View File
@@ -93,7 +93,7 @@ func main() {
}
bs = authorsRe.ReplaceAll(bs, []byte("id=\"contributor-list\">\n"+replacement+"\n </div>"))
if err := os.WriteFile(htmlFile, bs, 0o644); err != nil {
if err := os.WriteFile(htmlFile, bs, 0o666); err != nil {
log.Fatal(err)
}
+1 -1
View File
@@ -265,7 +265,7 @@ func readAll(path string) []byte {
}
func writeFile(path string, data string) {
err := os.WriteFile(path, []byte(data), 0o644)
err := os.WriteFile(path, []byte(data), 0o666)
if err != nil {
log.Fatal(err)
}