Compare commits

...

112 Commits

Author SHA1 Message Date
Jakob Borg
325c3c1fa7 lib/db, lib/protocol: Compact FileInfo and BlockInfo alignment (#6215)
* lib/db, lib/protocol: Compact FileInfo and BlockInfo alignment

This fixes the following two lint warnings

    FileInfo: struct of size 160 bytes could be of size 136 bytes
    BlockInfo: struct of size 48 bytes could be of size 40 bytes

by reordering fields in alignment order (64 bit fields, then 32 bit
fields, then 16 bit fields (if any), then small ones). The end result is
a slightly less aesthetically pleasing struct field order, but since
these are the objects we often juggle in bulk and keep large queues of I
think it's worth it.

It's a micro optimization, but a cheap one.
2019-12-08 13:31:26 +01:00
Jakob Borg
be0508cf26 lib/model, lib/protocol: Use error handling to avoid panic on non-started folder (fixes #6174) (#6212)
This adds error returns to model methods called by the protocol layer.
Returning an error will cause the connection to be torn down as the
message couldn't be handled. Using this to signal that a folder isn't
currently available will then cause a reconnection a few moments later,
when it'll hopefully work better.

Tested manually by running with STRECHECKDBEVERY=0 on a nontrivially
sized setup. This panics reliably before this patch, but just causes a
disconnect/reconnect now.
2019-12-04 10:46:55 +01:00
Simon Frei
6fd5e78740 lib: Consistently unsubscribe from config-wrapper (fixes #6133) (#6205) 2019-12-04 07:15:00 +01:00
Jakob Borg
a9e490adfa cmd/ursrv: Show more architectures (fixes #6211) 2019-12-03 21:34:32 +01:00
Jakob Borg
d8e7e92512 build: Generalize code signing
Should, at the least, also codesign when building zip for mac.
2019-12-03 08:37:43 +01:00
Paul Brit
eca156fd7f lib/osutil: Increase maxfiles on macOS properly (fixes #6206) (#6207) 2019-12-03 07:26:22 +01:00
Marcus Legendre
b3fd9a8d53 lib/ignore: Don't create empty ".stignore" files (fixes #6190) (#6197)
This will:

1. prevent creation of a new .stignore if there are no ignore patterns
2. delete an existing .stignore if all ignore patterns are removed
2019-12-02 08:19:02 +01:00
Simon Frei
0bec01b827 lib/db: Remove *instance by making everything *Lowlevel (#6204) 2019-12-02 08:18:04 +01:00
Jakob Borg
e82a7e3dfa all: Propagate errors from NamespacedKV (#6203)
As foretold by the prophecy, "once the database refactor is merged, then
shall appear a request to propagate errors from the store known
throughout the land as the NamedspacedKV, and it shall be good".
2019-11-30 13:03:24 +01:00
Jakob Borg
928767e316 lib/model: gofmt lol :( 2019-11-29 09:29:59 +01:00
Evgeny Kuznetsov
1c277fc096 lib/fs: Add case-insensitive fakefs (#6074) 2019-11-29 09:17:42 +01:00
Jakob Borg
c71116ee94 Implement database abstraction, error checking (ref #5907) (#6107)
This PR does two things, because one lead to the other:

- Move the leveldb specific stuff into a small "backend" package that
defines a backend interface and the leveldb implementation. This allows,
potentially, in the future, switching the db implementation so another
KV store should we wish to do so.

- Add proper error handling all along the way. The db and backend
packages are now errcheck clean. However, I drew the line at modifying
the FileSet API in order to keep this manageable and not continue
refactoring all of the rest of Syncthing. As such, the FileSet methods
still panic on database errors, except for the "database is closed"
error which is instead handled by silently returning as quickly as
possible, with the assumption that we're anyway "on the way out".
2019-11-29 09:11:52 +01:00
Robin Schoonover
a5bbc12625 gui: Sort versions by date in restore dropdown (#6201) 2019-11-29 08:32:25 +01:00
Simon Frei
606154b183 lib/model: Also send folder summary from sync-preparing (ref #6028) (#6202) 2019-11-29 08:30:17 +01:00
Jakob Borg
f9c380d45b cmd/syncthing: Implement log rotation (fixes #6104) (#6198)
Since we've taken upon ourselves to create a log file by default on
Windows, this adds proper management of that log file. There are two new
options:

  -log-max-old-files="3"    Number of old files to keep (zero to keep only current).
  -log-max-size="10485760"  Maximum size of any file (zero to disable log rotation).

The default values result in four files (syncthing.log, synchting.0.log,
..., syncthing.3.log) each up to 10 MiB in size. To not use log rotation
at all, the user can say --log-max-size=0.
2019-11-28 12:26:14 +01:00
Aman Gupta
a04f54a16a lib/upnp: Use simple continue in loop (#6192) 2019-11-26 22:55:34 +00:00
Aman Gupta
509d123251 lib/upnp: Ensure uPnP http requests have trailing \r\n (#6193) 2019-11-26 22:54:46 +00:00
Simon Frei
b32821a586 lib/config, lib/connections: Remove ListenAddresses hack (#6188) 2019-11-26 17:07:25 +01:00
Otiel
8ced8ad562 github: bump Syncthing version to v1... (#6191) 2019-11-26 08:25:50 +00:00
Simon Frei
1bae4b7f50 all: Use context in lib/dialer (#6177)
* all: Use context in lib/dialer

* a bit slimmer

* https://github.com/syncthing/syncthing/pull/5753

* bot

* missed adding debug.go

* errors.Cause

* simultaneous dialing

* anti-leak
2019-11-26 07:39:51 +00:00
Jakob Borg
4e151d380c lib/versioner: Reduce surface area (#6186)
* lib/versioner: Reduce surface area

This is a refactor while I was anyway rooting around in the versioner.
Instead of exporting every possible implementation and the factory and
letting the caller do whatever, this now encapsulates all that and
exposes a New() that takes a config.VersioningConfiguration.

Given that and that we don't know (from the outside) how a versioner
works or what state it keeps, we now just construct it once per folder
and keep it around. Previously it was recreated for each restore
request.

* unparam

* wip
2019-11-26 07:39:31 +00:00
Simon Frei
f747ba6d69 lib/ignore: Keep skipping ignored dirs for rooted patterns (#6151)
* lib/ignore: Keep skipping ignored dirs for rooted patterns

* review

* clarify comment and lint

* glob.QuoteMeta

* review
2019-11-26 07:37:41 +00:00
Simon Frei
33258b06f4 lib/connections: Dialer code deduplication (#6187) 2019-11-26 07:36:58 +00:00
Jakob Borg
4340589501 Merge branch 'release'
Discarding the commit on that branch...
2019-11-25 11:10:09 +01:00
Simon Frei
4d368a37e2 lib/model, lib/protocol: Add contexts sending indexes and download-progress (#6176) 2019-11-25 11:07:36 +01:00
dependabot-preview[bot]
999647b7d6 build(deps): bump github.com/urfave/cli from 1.22.1 to 1.22.2 (#6183)
Bumps [github.com/urfave/cli](https://github.com/urfave/cli) from 1.22.1 to 1.22.2.
- [Release notes](https://github.com/urfave/cli/releases)
- [Changelog](https://github.com/urfave/cli/blob/master/docs/CHANGELOG.md)
- [Commits](https://github.com/urfave/cli/compare/v1.22.1...v1.22.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-11-25 11:05:55 +01:00
Jakob Borg
45a711570e Revert "lib/model: Add folders on start in model (#6135)"
This reverts commit bee7cce081.
2019-11-24 09:33:58 +01:00
Simon Frei
cf312abc72 lib: Wrap errors with errors.Wrap instead of fmt.Errorf (#6181) 2019-11-23 15:20:54 +00:00
Mateusz Ż
e2f6d0d6c4 UI enhancements on mobile (#6180)
* Set fallback font for log viewer

* Enable logo scaling in About view

* Don't split "dependency list" into 2 columns on mobile
2019-11-23 12:25:25 +00:00
Simon Frei
65d4dd32cb lib/model: Also handle ServeBackground (#6173) 2019-11-22 21:30:16 +01:00
Simon Frei
de886b3f22 lib/relay: Prevent lock nil deref when creation dynamic client (#6175) 2019-11-21 17:45:06 +00:00
Jakob Borg
8c91e012c7 Merge branch 'release'
* release:
  gui: Prioritize non-idle folder states (fixes #6169) (#6170)
2019-11-21 09:36:03 +01:00
Simon Frei
6d27cf6563 gui: Prioritize non-idle folder states (fixes #6169) (#6170) 2019-11-21 09:33:39 +01:00
Simon Frei
57d668ed1d lib/config: Do introductions in a single config change (#6162) 2019-11-21 08:41:41 +01:00
Simon Frei
90d85fd0a2 lib: Replace done channel with contexts in and add names to util services (#6166) 2019-11-21 08:41:15 +01:00
Simon Frei
552ea68672 gui: Prioritize non-idle folder states (fixes #6169) (#6170) 2019-11-20 19:06:03 +01:00
Artur Zubilewicz
80eac473d9 gui: Make 'Nearby devices' look like links (fixes #6057) (#6165)
- The 'help-box' with nearby devices will appear only when there are
  any nearby devices
- IDs of nearby devices will look like links (i.e. underlined when
  hovered over)
2019-11-19 22:15:27 +01:00
Ruslan Yevdokymov
c1db8b2680 gui: Add upgrade confirmation dialog (fixes #5887) (#6167) 2019-11-19 22:05:41 +01:00
Jakob Borg
df866e10c8 gui: Increase padding a bit again (ref #6153)
I change my mind on this, the modals need *some* padding to not look weird.
2019-11-19 22:03:31 +01:00
Simon Frei
0d14ee4142 lib/model: Don't info log repeat pull errors (#6149) 2019-11-19 09:56:53 +01:00
Simon Frei
28edf2f5bb lib/model: Keep fmut locked while adding/starting/restarting folders (#6156) 2019-11-18 21:15:26 +01:00
Jakob Borg
e7100bc573 golang-ci: Skip "cognitive complexity" check for now 2019-11-17 08:54:59 +01:00
Simon Frei
5edf4660e2 lib/model: Prevent cleanup-race in testing (ref #6152) (#6155) 2019-11-14 23:08:40 +01:00
Domenic Horner
a5699d40a8 gui: Decrease padding on the panel and modal bodies (#6153)
This allows better viewing when on a condensed screen, and reduces screen real estate slightly.
2019-11-13 15:14:00 +01:00
Simon Frei
f80ce17497 lib/model: In tests prevent goroutine leaks and increase timeouts (#6152) 2019-11-13 10:21:54 +01:00
Simon Frei
ce72bee576 lib/model: Simplify pull error/retry logic (fixes #6139) (#6141) 2019-11-11 15:50:28 +01:00
Jacob
0cc77feabb docker: Add stdiscosrv and strelaysrv Dockerfiles (#6143) 2019-11-11 09:37:08 +01:00
Jakob Borg
d19b12d3fe lib/protocol: Buffer allocation when compressing (fixes #6146) (#6147)
We incorrectly gave a too small buffer to lz4.Compress, causing it to
allocate in some cases (when the data actually becomes larger when
compressed). This then panicked when passed to the buffer pool.

This ensures a buffer that is large enough, and adds tripwires closer to
the source in case this ever pops up again. There is a test that
exercises the issue.
2019-11-11 08:36:31 +00:00
Jakob Borg
1d406d62e3 golang-ci: Upgrade, skipping the white space complainer 2019-11-10 10:25:14 +01:00
Jakob Borg
1d99e5277a all: Cleanups enabled by Go 1.12 2019-11-10 10:16:10 +01:00
Jakob Borg
879f51b027 lib/tlsutil: Remove Go 1.12 TLS 1.3 beta opt-in
Go 1.13 enables this by default.
2019-11-10 09:32:48 +01:00
Audrius Butkevicius
d3d7408b17 lib/api: Make theme paths relative (#6142)
* Update theme.css

* Update syncthingController.js
2019-11-09 12:07:46 +00:00
Pablo
9b01e64c66 gui, lib/api: Adds support for prefers-color-scheme (fixes #6115)
* gui, lib/api: Adds support for prefers-color-scheme on default theme (fixes #6115)

- Renames current default theme into a new "light" theme
- Modifies assets serving to allow getting assets from different themes

* lib/api: Serve assets from arbitrary theme when path starts with "theme-assets"

* lib/api: Moves constant out of function

* Loads light theme in browsers without support for prefers-color-scheme

* gui: Disables dark theme when printing

* Prevents repeated injection and adds support for older browsers

The CSS is always loaded if there is no support for `matchMedia`.
2019-11-08 21:44:37 +00:00
Audrius Butkevicius
65c172cd8d lib/api: Reset mtime after theme change (fixes #5810) (#6140) 2019-11-08 22:37:42 +01:00
Simon Frei
85e6a77f25 lib/model: Remove some testing deadlocks (#6138) 2019-11-08 18:53:51 +01:00
Jakob Borg
88244b0c1f lib/model: Add test for previous commit 2019-11-08 17:03:25 +01:00
Simon Frei
cd290d2d05 lib/model: Add initial deviceStatRefs on model creation (fixes #6136) (#6137)
This is a regression introduced in PR #6005 / commit
f7b2e79fdc
2019-11-08 11:32:51 +00:00
Simon Frei
bee7cce081 lib/model: Add folders on start in model (#6135) 2019-11-08 10:56:16 +01:00
Jakob Borg
f15a1528fc cmd/stbench: rm -r cmd/stbench (#6131)
This is apparently an old benchmarking tool. I'd forgotten about it.
Since 67b8ef1f3e the build script tries to
build all binaries explicitly by default, and this fails on Windows as
this tool doesn't build on Windows.

Kill it with fire.
2019-11-07 07:20:21 +00:00
Jakob Borg
6be6de4b4a lib/api: Slightly unflake TestCSRFRequired by allowing longer timeout 2019-11-07 08:14:49 +01:00
Jakob Borg
6755a9ca63 Fix bufferpool puts (ref #4976) (#6125)
* Fix bufferpool puts (ref #4976)

There was a logic error in Put() which made us put all large blocks into
segment zero, where we subsequently did not look for them.

I also added a lowest threshold, as we otherwise allocate a 128KiB
buffer when we need 24 bytes for a header and such.

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* smaller stress

* cap/len

* wip

* wip
2019-11-06 10:53:10 +00:00
Audrius Butkevicius
98a1adebe1 all: Remove dead code, fix lost msgLen checks (#6129) 2019-11-06 07:09:58 +01:00
Aman Gupta
31569debeb lib/upnp: Fix outdated comment (#6110) 2019-11-05 18:56:51 +00:00
Simon Frei
cf420e135e gui: New folder state "Local Additions" for receive-only (fixes #5968) (#6117) 2019-11-01 20:44:23 +01:00
Ruslan Yevdokymov
3b5dff3f34 lib/model: Fix removal of a marker when there are still folders referencing it (#6114) 2019-10-30 15:11:07 +00:00
Jakob Borg
56cdf2f2d9 build: I like long, complicated things, ok? 2019-10-25 10:04:26 +02:00
dependabot-preview[bot]
b1dbe925d4 build(deps): bump github.com/prometheus/client_golang (#6099)
Bumps [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) from 1.1.0 to 1.2.1.
- [Release notes](https://github.com/prometheus/client_golang/releases)
- [Changelog](https://github.com/prometheus/client_golang/blob/master/CHANGELOG.md)
- [Commits](https://github.com/prometheus/client_golang/compare/v1.1.0...v1.2.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-10-22 21:56:32 +02:00
Simon Frei
bbdda059bd lib/model: Check for symlinks before deleting during pull (fixes #6090) (#6100) 2019-10-22 21:55:51 +02:00
Simon Frei
72f26c1e45 gui: Fix loop selecting all devices (fixes #5980) (#6102) 2019-10-22 13:57:10 +01:00
Simon Frei
72194d137c lib/scanner: Don't scan if input path is below symlink (fixes #6090) (#6101) 2019-10-22 11:12:21 +02:00
André Colomb
8b5bd45a29 gui: Split device list in folder sharing options by shared / unshared (#5756) 2019-10-21 21:28:10 +02:00
Jakob Borg
9084510e1b cmd/stdiscosrv: Sort addresses before replication (fixes #6093) (#6094)
This makes sure addresses are sorted when coming in from the API. The
database merge operation still checks for correct ordering (which is
quick) and sorts if it isn't correct (legacy database record or
replication peer), but then does a copy first.

Tested with -race in production...
2019-10-18 10:50:19 +02:00
Audrius Butkevicius
c4f161d8c5 lib/connections: Rate limit quic accept loop (fixes #6081) (#6082) 2019-10-18 09:55:37 +02:00
Jakob Borg
ad2d3702ae all: Upgrade github.com/gogo/protobuf and regenerate (fixes #6085) 2019-10-18 09:53:59 +02:00
Jakob Borg
95acb26249 lib/syncthing: Fixup test after merge 2019-10-17 09:14:27 +02:00
Jakob Borg
4736cccda1 all: Update certificate lifetimes (fixes #6036) (#6078)
This adds a certificate lifetime parameter to our certificate generation
and hard codes it to twenty years in some uninteresting places. In the
main binary there are a couple of constants but it results in twenty
years for the device certificate and 820 days for the HTTPS one. 820 is
less than the 825 maximum Apple allows nowadays.

This also means we must be prepared for certificates to expire, so I add
some handling for that and generate a new certificate when needed. For
self signed certificates we regenerate a month ahead of time. For other
certificates we leave well enough alone.
2019-10-16 20:31:46 +02:00
Simon Frei
1a06ab68eb lib/sync: Cleanly fail instead of panic in tests (#6088) 2019-10-16 10:11:11 +02:00
Simon Frei
b8907b49f9 lib/syncthing: Prevent hangup on error during startup (fixes #6043) (#6047) 2019-10-16 10:10:42 +02:00
Simon Frei
7b33294955 gui, lib/model: Add new state FolderPreparingSync (fixes #6027) (#6028) 2019-10-16 09:08:54 +02:00
Simon Frei
031684116b lib/util: Add caller info to service (ref #5932) (#5973) 2019-10-16 09:06:16 +02:00
Simon Frei
a0c9db1d09 lib/api: Unify JSON marshalling of file infos (#6087) 2019-10-15 11:25:12 +02:00
dependabot-preview[bot]
aa4b918224 build(deps): bump github.com/lucas-clemente/quic-go (#6084)
Bumps [github.com/lucas-clemente/quic-go](https://github.com/lucas-clemente/quic-go) from 0.12.0 to 0.12.1.
- [Release notes](https://github.com/lucas-clemente/quic-go/releases)
- [Changelog](https://github.com/lucas-clemente/quic-go/blob/master/Changelog.md)
- [Commits](https://github.com/lucas-clemente/quic-go/compare/v0.12.0...v0.12.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-10-14 18:30:00 +01:00
dependabot-preview[bot]
7043b1fbba build(deps): bump github.com/mattn/go-isatty from 0.0.9 to 0.0.10 (#6083)
Bumps [github.com/mattn/go-isatty](https://github.com/mattn/go-isatty) from 0.0.9 to 0.0.10.
- [Release notes](https://github.com/mattn/go-isatty/releases)
- [Commits](https://github.com/mattn/go-isatty/compare/v0.0.9...v0.0.10)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-10-14 18:17:01 +01:00
chenrui
9d6b663d1c build: Update golangci for go v1.13 (#6042) 2019-10-13 16:32:20 +02:00
Arkadiusz Tymiński
7dc4ac6e1f gui: Hide select/deselect all buttons if there are no devices (fixes #6056) 2019-10-08 21:57:17 +01:00
Alan Pope
7bad9b3a11 snapcraft: Add desktop plug (#6069)
Without the desktop plug, launching the syncthing snap will not launch a browser window to the admin UI. Adding this one line will fix that. Tested here on my Ubuntu system with a build from tip of master.
2019-10-08 18:20:51 +01:00
Jakob Borg
6b570ee8dc lib/upgrade: Add html_url release field 2019-10-08 09:12:00 +02:00
Cyprien Devillez
6408a116f9 cmd/stdiscosrv: Add support for Traefik 2 as a reverse proxy (#6065) 2019-10-07 12:55:27 +01:00
Jakob Borg
67b8ef1f3e cmd/*, lib/build: Set correct LongVersion (fixes #5993) (#5997)
The relay and discosrv didn't use the new lib/build package, now they
do. Conversely the lib/build package wasn't aware there might be other
users and hard coded the program name - now it's set by the build
script
2019-10-07 13:30:25 +02:00
Evgeny Kuznetsov
999d4a0e23 gui: Better info for stalled and lengthy scans (fixes #5627) (#6061) 2019-10-05 11:34:42 +02:00
Lukas Lihotzki
96bb1c8e29 all, lib/logger: Refactor SetDebug calls (#6054) 2019-10-04 13:03:34 +02:00
Audrius Butkevicius
8fb576ed54 lib/model: Adjust blocks reported in usage reporting (fixes #5995) (#6037)
* lib/model: Adjust blocks reported in usage reporting (fixes #5995)

* Use variables, fix go.mod
2019-10-04 12:03:13 +01:00
Jakob Borg
5e31e6356f lib/api: Report actual listener address (fixes #6049) (#6060) 2019-10-04 11:25:41 +01:00
Jakob Borg
1b5a61e03e build: Upgrade github.com/syndtr/goleveldb
Newer is always better. Always.
2019-10-03 17:45:45 +02:00
Jakob Borg
755e689627 lib/db: Always use small db settings on 32 bit archs (#6053) 2019-10-03 13:40:14 +01:00
boomsquared
3f5c9b578c gui: Fix tab headers in black and dark themes (fixes #5583) 2019-10-01 20:09:52 +01:00
Simon Frei
a2a14c8424 lib/model: Set empty version when unignoring deleted files (fixes 6038) (#6039) 2019-10-01 15:34:59 +02:00
Lukas Lihotzki
cff7a091f5 gui: Don't show auth warning when listening on UNIX socket (fixes #6040) (#6041) 2019-10-01 13:22:33 +02:00
Jakob Borg
757d9a5333 Merge branch 'release'
* release:
  readme: Fix broken link to README-Docker.md (#6025)
  docker: Make it easy to disable the GUI, document it (#6021)
2019-10-01 07:46:59 +02:00
Jakob Borg
52d80d8144 lib/fs: Improve root check (#6033)
The root check would allow things like c:\foobar\baz if the root was
c:\foo, because string wise that's a prefix. Now it doesn't.
2019-09-29 23:38:11 +08:00
Ilya Brin
fd2e91c82d readme: Fix broken link to README-Docker.md (#6025) 2019-09-23 13:28:42 +09:00
Jakob Borg
c744a75cdd docker: Make it easy to disable the GUI, document it (#6021) 2019-09-22 11:33:29 +01:00
Simon Frei
35b699dc77 lib/fs: Check events against both the user and eval root (#6013) 2019-09-22 08:03:22 +01:00
Jakob Borg
7127c13f18 build: Tweak golang-ci config to build (#6022) 2019-09-22 07:57:58 +01:00
Jakob Borg
dab29287da Merge branch 'release'
* release:
  gui, lib/api: Use effective listen address for no auth warning
  docker: Build using Go 1.13
2019-09-21 12:10:04 +02:00
Jakob Borg
db0ba2555a gui, lib/api: Use effective listen address for no auth warning
This adds a field `guiAddressUsed` to the system status response, that
holds the current listening address actually in use. This may be
different from the one stored in the config because it may have been
overridden by environment or command line flag.

The GUI now checks this field to see if we are listening on localhost.
If we are not, the authentication required warning is displayed,
regardless of the *configured* listening address.
2019-09-20 16:23:33 +02:00
Jakob Borg
1398fbb681 docker: Build using Go 1.13 2019-09-20 11:02:43 +02:00
dependabot-preview[bot]
f653f540f8 build(deps): bump github.com/urfave/cli from 1.21.0 to 1.22.1 (#6015)
Bumps [github.com/urfave/cli](https://github.com/urfave/cli) from 1.21.0 to 1.22.1.
- [Release notes](https://github.com/urfave/cli/releases)
- [Changelog](https://github.com/urfave/cli/blob/master/CHANGELOG.md)
- [Commits](https://github.com/urfave/cli/compare/v1.21.0...v1.22.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-09-16 08:31:00 +01:00
dependabot-preview[bot]
078923bd1a build(deps): bump github.com/minio/sha256-simd from 0.1.0 to 0.1.1 (#6014)
Bumps [github.com/minio/sha256-simd](https://github.com/minio/sha256-simd) from 0.1.0 to 0.1.1.
- [Release notes](https://github.com/minio/sha256-simd/releases)
- [Commits](https://github.com/minio/sha256-simd/compare/v0.1.0...v0.1.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-09-16 08:28:46 +01:00
ghjklw
80a83b605c lib/config: Remove stun.voxgratia.org (fixes #6010) (#6011)
DNS resolution fails for this server:
named[9495]: REFUSED unexpected RCODE resolving 'stun.voxgratia.org/A/IN': 2600:9000:5303:ae00::1#53
named[9495]: REFUSED unexpected RCODE resolving 'stun.voxgratia.org/A/IN': 205.251.198.31#53
2019-09-13 09:05:25 +01:00
Simon Frei
28b6e8b063 lib/db: Update db when only local flags change (fixes #6008) (#6007) 2019-09-12 08:47:39 +02:00
Simon Frei
f7b2e79fdc lib/model: Use read-locks wherever possible (#6005) 2019-09-12 05:55:23 +01:00
212 changed files with 7085 additions and 4277 deletions

View File

@@ -36,7 +36,7 @@ its entirety.
### Version Information
Syncthing Version: v0.x.y
Syncthing Version: v1.x.y
OS Version: Windows 7 / Ubuntu 14.04 / ...
Browser Version: (if applicable, for GUI issues)

View File

@@ -12,9 +12,14 @@ linters:
- gochecknoglobals
- gofmt
- scopelint
- gocyclo
- funlen
- wsl
- gocognit
service:
golangci-lint-version: 1.16.x
golangci-lint-version: 1.21.x
prepare:
- rm -f go.sum # 1.12 -> 1.13 issues with QUIC-go
- GO111MODULE=on go mod vendor
- go run build.go assets

View File

@@ -19,10 +19,10 @@ RUN apk add --no-cache ca-certificates su-exec
COPY --from=builder /src/syncthing /bin/syncthing
COPY --from=builder /src/script/docker-entrypoint.sh /bin/entrypoint.sh
ENV PUID=1000 PGID=1000
ENV PUID=1000 PGID=1000 HOME=/var/syncthing
HEALTHCHECK --interval=1m --timeout=10s \
CMD nc -z localhost 8384 || exit 1
ENV STGUIADDRESS=0.0.0.0:8384
ENTRYPOINT ["/bin/entrypoint.sh", "-home", "/var/syncthing/config"]
ENTRYPOINT ["/bin/entrypoint.sh", "/bin/syncthing", "-home", "/var/syncthing/config"]

28
Dockerfile.stdiscosrv Normal file
View File

@@ -0,0 +1,28 @@
FROM golang:1.13 AS builder
WORKDIR /src
COPY . .
ENV CGO_ENABLED=0
ENV BUILD_HOST=syncthing.net
ENV BUILD_USER=docker
RUN rm -f stdiscosrv && go run build.go -no-upgrade build stdiscosrv
FROM alpine
EXPOSE 19200 8443
VOLUME ["/var/stdiscosrv"]
RUN apk add --no-cache ca-certificates su-exec
COPY --from=builder /src/stdiscosrv /bin/stdiscosrv
COPY --from=builder /src/script/docker-entrypoint.sh /bin/entrypoint.sh
ENV PUID=1000 PGID=1000 HOME=/var/stdiscosrv
HEALTHCHECK --interval=1m --timeout=10s \
CMD nc -z localhost 8443 || exit 1
WORKDIR /var/stdiscosrv
ENTRYPOINT ["/bin/entrypoint.sh", "/bin/stdiscosrv"]

28
Dockerfile.strelaysrv Normal file
View File

@@ -0,0 +1,28 @@
FROM golang:1.13 AS builder
WORKDIR /src
COPY . .
ENV CGO_ENABLED=0
ENV BUILD_HOST=syncthing.net
ENV BUILD_USER=docker
RUN rm -f strelaysrv && go run build.go -no-upgrade build strelaysrv
FROM alpine
EXPOSE 22067 22070
VOLUME ["/var/strelaysrv"]
RUN apk add --no-cache ca-certificates su-exec
COPY --from=builder /src/strelaysrv /bin/strelaysrv
COPY --from=builder /src/script/docker-entrypoint.sh /bin/entrypoint.sh
ENV PUID=1000 PGID=1000 HOME=/var/strelaysrv
HEALTHCHECK --interval=1m --timeout=10s \
CMD nc -z localhost 22067 || exit 1
WORKDIR /var/strelaysrv
ENTRYPOINT ["/bin/entrypoint.sh", "/bin/strelaysrv"]

View File

@@ -24,6 +24,7 @@ import (
"os"
"os/exec"
"os/user"
"path"
"path/filepath"
"regexp"
"runtime"
@@ -59,7 +60,7 @@ type target struct {
debpre string
debpost string
description string
buildPkg string
buildPkgs []string
binaryName string
archiveFiles []archiveFile
systemdServices []string
@@ -76,9 +77,8 @@ type archiveFile struct {
var targets = map[string]target{
"all": {
// Only valid for the "build" and "install" commands as it lacks all
// the archive creation stuff.
buildPkg: "github.com/syncthing/syncthing/cmd/...",
tags: []string{"purego"},
// the archive creation stuff. buildPkgs gets filled out in init()
tags: []string{"purego"},
},
"syncthing": {
// The default target for "build", "install", "tar", "zip", "deb", etc.
@@ -87,7 +87,7 @@ var targets = map[string]target{
debdeps: []string{"libc6", "procps"},
debpost: "script/post-upgrade",
description: "Open Source Continuous File Synchronization",
buildPkg: "github.com/syncthing/syncthing/cmd/syncthing",
buildPkgs: []string{"github.com/syncthing/syncthing/cmd/syncthing"},
binaryName: "syncthing", // .exe will be added automatically for Windows builds
archiveFiles: []archiveFile{
{src: "{{binary}}", dst: "{{binary}}", perm: 0755},
@@ -131,7 +131,7 @@ var targets = map[string]target{
debdeps: []string{"libc6"},
debpre: "cmd/stdiscosrv/scripts/preinst",
description: "Syncthing Discovery Server",
buildPkg: "github.com/syncthing/syncthing/cmd/stdiscosrv",
buildPkgs: []string{"github.com/syncthing/syncthing/cmd/stdiscosrv"},
binaryName: "stdiscosrv", // .exe will be added automatically for Windows builds
archiveFiles: []archiveFile{
{src: "{{binary}}", dst: "{{binary}}", perm: 0755},
@@ -159,7 +159,7 @@ var targets = map[string]target{
debdeps: []string{"libc6"},
debpre: "cmd/strelaysrv/scripts/preinst",
description: "Syncthing Relay Server",
buildPkg: "github.com/syncthing/syncthing/cmd/strelaysrv",
buildPkgs: []string{"github.com/syncthing/syncthing/cmd/strelaysrv"},
binaryName: "strelaysrv", // .exe will be added automatically for Windows builds
archiveFiles: []archiveFile{
{src: "{{binary}}", dst: "{{binary}}", perm: 0755},
@@ -187,7 +187,7 @@ var targets = map[string]target{
debname: "syncthing-relaypoolsrv",
debdeps: []string{"libc6"},
description: "Syncthing Relay Pool Server",
buildPkg: "github.com/syncthing/syncthing/cmd/strelaypoolsrv",
buildPkgs: []string{"github.com/syncthing/syncthing/cmd/strelaypoolsrv"},
binaryName: "strelaypoolsrv", // .exe will be added automatically for Windows builds
archiveFiles: []archiveFile{
{src: "{{binary}}", dst: "{{binary}}", perm: 0755},
@@ -217,6 +217,18 @@ var dependencyRepos = []dependencyRepo{
}
func init() {
all := targets["all"]
pkgs, _ := filepath.Glob("cmd/*")
for _, pkg := range pkgs {
pkg = filepath.Base(pkg)
if strings.HasPrefix(pkg, ".") {
// ignore dotfiles
continue
}
all.buildPkgs = append(all.buildPkgs, fmt.Sprintf("github.com/syncthing/syncthing/cmd/%s", pkg))
}
targets["all"] = all
// The "syncthing" target includes a few more files found in the "etc"
// and "extra" dirs.
syncthingPkg := targets["syncthing"]
@@ -382,9 +394,6 @@ func install(target target, tags []string) {
}
os.Setenv("GOBIN", filepath.Join(cwd, "bin"))
args := []string{"install", "-v"}
args = appendParameters(args, tags, target)
os.Setenv("GOOS", goos)
os.Setenv("GOARCH", goarch)
os.Setenv("CC", cc)
@@ -400,19 +409,20 @@ func install(target target, tags []string) {
defer shouldCleanupSyso(sysoPath)
}
runPrint(goCmd, args...)
for _, pkg := range target.buildPkgs {
args := []string{"install", "-v"}
args = appendParameters(args, tags, pkg)
runPrint(goCmd, args...)
}
}
func build(target target, tags []string) {
lazyRebuildAssets()
tags = append(target.tags, tags...)
rmr(target.BinaryName())
args := []string{"build", "-v"}
args = appendParameters(args, tags, target)
os.Setenv("GOOS", goos)
os.Setenv("GOARCH", goarch)
os.Setenv("CC", cc)
@@ -432,10 +442,15 @@ func build(target target, tags []string) {
defer shouldCleanupSyso(sysoPath)
}
runPrint(goCmd, args...)
for _, pkg := range target.buildPkgs {
args := []string{"build", "-v"}
args = appendParameters(args, tags, pkg)
runPrint(goCmd, args...)
}
}
func appendParameters(args []string, tags []string, target target) []string {
func appendParameters(args []string, tags []string, pkg string) []string {
if pkgdir != "" {
args = append(args, "-pkgdir", pkgdir)
}
@@ -451,7 +466,7 @@ func appendParameters(args []string, tags []string, target target) []string {
if !debugBinary {
// Regular binaries get version tagged and skip some debug symbols
args = append(args, "-ldflags", ldflags())
args = append(args, "-ldflags", ldflags(path.Base(pkg)))
} else {
// -gcflags to disable optimizations and inlining. Skip -ldflags
// because `Could not launch program: decoding dwarf section info at
@@ -460,7 +475,7 @@ func appendParameters(args []string, tags []string, target target) []string {
args = append(args, "-gcflags", "-N -l")
}
return append(args, target.buildPkg)
return append(args, pkg)
}
func buildTar(target target) {
@@ -474,10 +489,7 @@ func buildTar(target target) {
}
build(target, tags)
if goos == "darwin" {
macosCodesign(target.BinaryName())
}
codesign(target)
for i := range target.archiveFiles {
target.archiveFiles[i].src = strings.Replace(target.archiveFiles[i].src, "{{binary}}", target.BinaryName(), 1)
@@ -500,10 +512,7 @@ func buildZip(target target) {
}
build(target, tags)
if goos == "windows" {
windowsCodesign(target.BinaryName())
}
codesign(target)
for i := range target.archiveFiles {
target.archiveFiles[i].src = strings.Replace(target.archiveFiles[i].src, "{{binary}}", target.BinaryName(), 1)
@@ -708,6 +717,7 @@ func listFiles(dir string) []string {
if err != nil {
return err
}
if fi.Mode().IsRegular() {
res = append(res, path)
}
@@ -789,7 +799,7 @@ func transifex() {
runPrint(goCmd, "run", "../../../../script/transifexdl.go")
}
func ldflags() string {
func ldflags(program string) string {
sep := '='
if goVersion > 0 && goVersion < 1.5 {
sep = ' '
@@ -801,8 +811,9 @@ func ldflags() string {
fmt.Fprintf(b, " -X github.com/syncthing/syncthing/lib/build.Stamp%c%d", sep, buildStamp())
fmt.Fprintf(b, " -X github.com/syncthing/syncthing/lib/build.User%c%s", sep, buildUser())
fmt.Fprintf(b, " -X github.com/syncthing/syncthing/lib/build.Host%c%s", sep, buildHost())
fmt.Fprintf(b, " -X github.com/syncthing/syncthing/lib/build.Program%c%s", sep, program)
if v := os.Getenv("EXTRA_LDFLAGS"); v != "" {
fmt.Fprintf(b, " %s", v);
fmt.Fprintf(b, " %s", v)
}
return b.String()
}
@@ -1162,6 +1173,15 @@ func zipFile(out string, files []archiveFile) {
}
}
func codesign(target target) {
switch goos {
case "windows":
windowsCodesign(target.BinaryName())
case "darwin":
macosCodesign(target.BinaryName())
}
}
func macosCodesign(file string) {
if pass := os.Getenv("CODESIGN_KEYCHAIN_PASS"); pass != "" {
bs, err := runError("security", "unlock-keychain", "-p", pass)

View File

@@ -1,143 +0,0 @@
// Copyright (C) 2016 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
// This doesn't build on Windows due to the Rusage stuff.
// +build !windows
package main
import (
"flag"
"fmt"
"log"
"runtime"
"syscall"
"time"
"github.com/syncthing/syncthing/lib/rc"
)
var homeDir = "h1"
var syncthingBin = "./bin/syncthing"
var test = "scan"
func main() {
flag.StringVar(&homeDir, "home", homeDir, "Home directory location")
flag.StringVar(&syncthingBin, "bin", syncthingBin, "Binary location")
flag.StringVar(&test, "test", test, "Test to run")
flag.Parse()
switch test {
case "scan":
// scan measures the resource usage required to perform the initial
// scan, without cleaning away the database first.
testScan()
}
}
// testScan starts a process and reports on the resource usage required to
// perform the initial scan.
func testScan() {
log.Println("Starting...")
p := rc.NewProcess("127.0.0.1:8081")
if err := p.Start(syncthingBin, "-home", homeDir, "-no-browser"); err != nil {
log.Println(err)
return
}
defer p.Stop()
wallTime := awaitScanComplete(p)
report(p, wallTime)
}
// awaitScanComplete waits for a folder to transition idle->scanning and
// then scanning->idle and returns the time taken for the scan.
func awaitScanComplete(p *rc.Process) time.Duration {
log.Println("Awaiting scan completion...")
var t0, t1 time.Time
lastEvent := 0
loop:
for {
evs, err := p.Events(lastEvent)
if err != nil {
continue
}
for _, ev := range evs {
if ev.Type == "StateChanged" {
data := ev.Data.(map[string]interface{})
log.Println(ev)
if data["to"].(string) == "scanning" {
t0 = ev.Time
continue
}
if !t0.IsZero() && data["to"].(string) == "idle" {
t1 = ev.Time
break loop
}
}
lastEvent = ev.ID
}
time.Sleep(250 * time.Millisecond)
}
return t1.Sub(t0)
}
// report stops the given process and reports on its resource usage in two
// ways: human readable to stderr, and CSV to stdout.
func report(p *rc.Process, wallTime time.Duration) {
sv, err := p.SystemVersion()
if err != nil {
log.Println(err)
return
}
ss, err := p.SystemStatus()
if err != nil {
log.Println(err)
return
}
proc, err := p.Stop()
if err != nil {
return
}
rusage, ok := proc.SysUsage().(*syscall.Rusage)
if !ok {
return
}
log.Println("Version:", sv.Version)
log.Println("Alloc:", ss.Alloc/1024, "KiB")
log.Println("Sys:", ss.Sys/1024, "KiB")
log.Println("Goroutines:", ss.Goroutines)
log.Println("Wall time:", wallTime)
log.Println("Utime:", time.Duration(rusage.Utime.Nano()))
log.Println("Stime:", time.Duration(rusage.Stime.Nano()))
if runtime.GOOS == "darwin" {
// Darwin reports in bytes, Linux seems to report in KiB even
// though the manpage says otherwise.
rusage.Maxrss /= 1024
}
log.Println("MaxRSS:", rusage.Maxrss, "KiB")
fmt.Printf("%s,%d,%d,%d,%.02f,%.02f,%.02f,%d\n",
sv.Version,
ss.Alloc/1024,
ss.Sys/1024,
ss.Goroutines,
wallTime.Seconds(),
time.Duration(rusage.Utime.Nano()).Seconds(),
time.Duration(rusage.Stime.Nano()).Seconds(),
rusage.Maxrss)
}

View File

@@ -14,7 +14,6 @@ import (
"log"
"os"
"reflect"
"strings"
"github.com/AudriusButkevicius/recli"
"github.com/flynn-archive/go-shlex"
@@ -128,7 +127,7 @@ func main() {
app.HelpName = app.Name
app.Author = "The Syncthing Authors"
app.Usage = "Syncthing command line interface"
app.Version = strings.Replace(build.LongVersion, "syncthing", app.Name, 1)
app.Version = build.Version
app.Flags = fakeFlags
app.Metadata = map[string]interface{}{
"client": client,

View File

@@ -12,7 +12,6 @@ import (
"io/ioutil"
"net/http"
"os"
"text/tabwriter"
"github.com/syncthing/syncthing/lib/config"
"github.com/urfave/cli"
@@ -45,12 +44,6 @@ func dumpOutput(url string) cli.ActionFunc {
}
}
func newTableWriter() *tabwriter.Writer {
writer := new(tabwriter.Writer)
writer.Init(os.Stdout, 0, 8, 0, '\t', 0)
return writer
}
func getConfig(c *APIClient) (config.Configuration, error) {
cfg := config.Configuration{}
response, err := c.Get("system/config")

View File

@@ -18,6 +18,7 @@ import (
"net"
"net/http"
"net/url"
"sort"
"strconv"
"strings"
"sync"
@@ -279,6 +280,10 @@ func (s *apiSrv) handleAnnounce(remote net.IP, deviceID protocol.DeviceID, addre
dbAddrs[i].Expires = expire
}
// The address slice must always be sorted for database merges to work
// properly.
sort.Sort(databaseAddressOrder(dbAddrs))
seen := now.UnixNano()
if s.repl != nil {
s.repl.send(key, dbAddrs, seen)
@@ -295,28 +300,66 @@ func certificateBytes(req *http.Request) []byte {
return req.TLS.PeerCertificates[0].Raw
}
var bs []byte
if hdr := req.Header.Get("X-SSL-Cert"); hdr != "" {
bs := []byte(hdr)
// The certificate is in PEM format but with spaces for newlines. We
// need to reinstate the newlines for the PEM decoder. But we need to
// leave the spaces in the BEGIN and END lines - the first and last
// space - alone.
firstSpace := bytes.Index(bs, []byte(" "))
lastSpace := bytes.LastIndex(bs, []byte(" "))
for i := firstSpace + 1; i < lastSpace; i++ {
if bs[i] == ' ' {
bs[i] = '\n'
if strings.Contains(hdr, "%") {
// Nginx using $ssl_client_escaped_cert
// The certificate is in PEM format with url encoding.
// We need to decode for the PEM decoder
hdr, err := url.QueryUnescape(hdr)
if err != nil {
// Decoding failed
return nil
}
bs = []byte(hdr)
} else {
// Nginx using $ssl_client_cert
// The certificate is in PEM format but with spaces for newlines. We
// need to reinstate the newlines for the PEM decoder. But we need to
// leave the spaces in the BEGIN and END lines - the first and last
// space - alone.
bs = []byte(hdr)
firstSpace := bytes.Index(bs, []byte(" "))
lastSpace := bytes.LastIndex(bs, []byte(" "))
for i := firstSpace + 1; i < lastSpace; i++ {
if bs[i] == ' ' {
bs[i] = '\n'
}
}
}
block, _ := pem.Decode(bs)
if block == nil {
} else if hdr := req.Header.Get("X-Forwarded-Tls-Client-Cert"); hdr != "" {
// Traefik 2 passtlsclientcert
// The certificate is in PEM format with url encoding but without newlines
// and start/end statements. We need to decode, reinstate the newlines every 64
// character and add statements for the PEM decoder
hdr, err := url.QueryUnescape(hdr)
if err != nil {
// Decoding failed
return nil
}
return block.Bytes
for i := 64; i < len(hdr); i += 65 {
hdr = hdr[:i] + "\n" + hdr[i:]
}
hdr = "-----BEGIN CERTIFICATE-----\n" + hdr
hdr = hdr + "\n-----END CERTIFICATE-----\n"
bs = []byte(hdr)
}
return nil
if bs == nil {
return nil
}
block, _ := pem.Decode(bs)
if block == nil {
// Decoding failed
return nil
}
return block.Bytes
}
// fixupAddresses checks the list of addresses, removing invalid ones and

View File

@@ -10,6 +10,7 @@
package main
import (
"log"
"sort"
"time"
@@ -263,12 +264,15 @@ func (s *levelDBStore) Stop() {
// chosen for any duplicates.
func merge(a, b DatabaseRecord) DatabaseRecord {
// Both lists must be sorted for this to work.
sort.Slice(a.Addresses, func(i, j int) bool {
return a.Addresses[i].Address < a.Addresses[j].Address
})
sort.Slice(b.Addresses, func(i, j int) bool {
return b.Addresses[i].Address < b.Addresses[j].Address
})
if !sort.IsSorted(databaseAddressOrder(a.Addresses)) {
log.Println("Warning: bug: addresses not correctly sorted in merge")
a.Addresses = sortedAddressCopy(a.Addresses)
}
if !sort.IsSorted(databaseAddressOrder(b.Addresses)) {
// no warning because this is the side we read from disk and it may
// legitimately predate correct sorting.
b.Addresses = sortedAddressCopy(b.Addresses)
}
res := DatabaseRecord{
Addresses: make([]DatabaseAddress, 0, len(a.Addresses)+len(b.Addresses)),
@@ -352,3 +356,24 @@ func expire(addrs []DatabaseAddress, now int64) []DatabaseAddress {
}
return addrs
}
func sortedAddressCopy(addrs []DatabaseAddress) []DatabaseAddress {
sorted := make([]DatabaseAddress, len(addrs))
copy(sorted, addrs)
sort.Sort(databaseAddressOrder(sorted))
return sorted
}
type databaseAddressOrder []DatabaseAddress
func (s databaseAddressOrder) Less(a, b int) bool {
return s[a].Address < s[b].Address
}
func (s databaseAddressOrder) Swap(a, b int) {
s[a], s[b] = s[b], s[a]
}
func (s databaseAddressOrder) Len() int {
return len(s)
}

View File

@@ -773,6 +773,7 @@ func (m *DatabaseAddress) Unmarshal(dAtA []byte) error {
func skipDatabase(dAtA []byte) (n int, err error) {
l := len(dAtA)
iNdEx := 0
depth := 0
for iNdEx < l {
var wire uint64
for shift := uint(0); ; shift += 7 {
@@ -804,10 +805,8 @@ func skipDatabase(dAtA []byte) (n int, err error) {
break
}
}
return iNdEx, nil
case 1:
iNdEx += 8
return iNdEx, nil
case 2:
var length int
for shift := uint(0); ; shift += 7 {
@@ -828,55 +827,30 @@ func skipDatabase(dAtA []byte) (n int, err error) {
return 0, ErrInvalidLengthDatabase
}
iNdEx += length
if iNdEx < 0 {
return 0, ErrInvalidLengthDatabase
}
return iNdEx, nil
case 3:
for {
var innerWire uint64
var start int = iNdEx
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowDatabase
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
innerWire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
innerWireType := int(innerWire & 0x7)
if innerWireType == 4 {
break
}
next, err := skipDatabase(dAtA[start:])
if err != nil {
return 0, err
}
iNdEx = start + next
if iNdEx < 0 {
return 0, ErrInvalidLengthDatabase
}
}
return iNdEx, nil
depth++
case 4:
return iNdEx, nil
if depth == 0 {
return 0, ErrUnexpectedEndOfGroupDatabase
}
depth--
case 5:
iNdEx += 4
return iNdEx, nil
default:
return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
}
if iNdEx < 0 {
return 0, ErrInvalidLengthDatabase
}
if depth == 0 {
return iNdEx, nil
}
}
panic("unreachable")
return 0, io.ErrUnexpectedEOF
}
var (
ErrInvalidLengthDatabase = fmt.Errorf("proto: negative length found during unmarshaling")
ErrIntOverflowDatabase = fmt.Errorf("proto: integer overflow")
ErrInvalidLengthDatabase = fmt.Errorf("proto: negative length found during unmarshaling")
ErrIntOverflowDatabase = fmt.Errorf("proto: integer overflow")
ErrUnexpectedEndOfGroupDatabase = fmt.Errorf("proto: unexpected end of group")
)

View File

@@ -9,17 +9,15 @@ package main
import (
"crypto/tls"
"flag"
"fmt"
"log"
"net"
"net/http"
"os"
"runtime"
"strconv"
"strings"
"time"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/syncthing/syncthing/lib/build"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/tlsutil"
"github.com/syndtr/goleveldb/leveldb/opt"
@@ -65,24 +63,6 @@ var levelDBOptions = &opt.Options{
WriteBuffer: 32 << 20, // default 4<<20
}
var (
Version string
BuildStamp string
BuildUser string
BuildHost string
BuildDate time.Time
LongVersion string
)
func init() {
stamp, _ := strconv.Atoi(BuildStamp)
BuildDate = time.Unix(int64(stamp), 0)
date := BuildDate.UTC().Format("2006-01-02 15:04:05 MST")
LongVersion = fmt.Sprintf(`stdiscosrv %s (%s %s-%s) %s@%s %s`, Version, runtime.Version(), runtime.GOOS, runtime.GOARCH, BuildUser, BuildHost, date)
}
var (
debug = false
)
@@ -109,14 +89,18 @@ func main() {
flag.StringVar(&metricsListen, "metrics-listen", "", "Metrics listen address")
flag.StringVar(&replicationPeers, "replicate", "", "Replication peers, id@address, comma separated")
flag.StringVar(&replicationListen, "replication-listen", ":19200", "Replication listen address")
showVersion := flag.Bool("version", false, "Show version")
flag.Parse()
log.Println(LongVersion)
log.Println(build.LongVersion)
if *showVersion {
return
}
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
log.Println("Failed to load keypair. Generating one, this might take a while...")
cert, err = tlsutil.NewCertificate(certFile, keyFile, "stdiscosrv")
cert, err = tlsutil.NewCertificate(certFile, keyFile, "stdiscosrv", 20*365)
if err != nil {
log.Fatalln("Failed to generate X509 key pair:", err)
}

View File

@@ -13,11 +13,15 @@ import (
"time"
"github.com/syncthing/syncthing/lib/db"
"github.com/syncthing/syncthing/lib/db/backend"
"github.com/syncthing/syncthing/lib/protocol"
)
func dump(ldb *db.Lowlevel) {
it := ldb.NewIterator(nil, nil)
func dump(ldb backend.Backend) {
it, err := ldb.NewPrefixIterator(nil)
if err != nil {
log.Fatal(err)
}
for it.Next() {
key := it.Key()
switch key[0] {

View File

@@ -10,8 +10,10 @@ import (
"container/heap"
"encoding/binary"
"fmt"
"log"
"github.com/syncthing/syncthing/lib/db"
"github.com/syncthing/syncthing/lib/db/backend"
)
type SizedElement struct {
@@ -37,11 +39,14 @@ func (h *ElementHeap) Pop() interface{} {
return x
}
func dumpsize(ldb *db.Lowlevel) {
func dumpsize(ldb backend.Backend) {
h := &ElementHeap{}
heap.Init(h)
it := ldb.NewIterator(nil, nil)
it, err := ldb.NewPrefixIterator(nil)
if err != nil {
log.Fatal(err)
}
var ele SizedElement
for it.Next() {
key := it.Key()

View File

@@ -10,8 +10,10 @@ import (
"bytes"
"encoding/binary"
"fmt"
"log"
"github.com/syncthing/syncthing/lib/db"
"github.com/syncthing/syncthing/lib/db/backend"
"github.com/syncthing/syncthing/lib/protocol"
)
@@ -31,7 +33,7 @@ type sequenceKey struct {
sequence uint64
}
func idxck(ldb *db.Lowlevel) (success bool) {
func idxck(ldb backend.Backend) (success bool) {
folders := make(map[uint32]string)
devices := make(map[uint32]string)
deviceToIDs := make(map[string]uint32)
@@ -42,7 +44,10 @@ func idxck(ldb *db.Lowlevel) (success bool) {
var localDeviceKey uint32
success = true
it := ldb.NewIterator(nil, nil)
it, err := ldb.NewPrefixIterator(nil)
if err != nil {
log.Fatal(err)
}
for it.Next() {
key := it.Key()
switch key[0] {

View File

@@ -13,7 +13,7 @@ import (
"os"
"path/filepath"
"github.com/syncthing/syncthing/lib/db"
"github.com/syncthing/syncthing/lib/db/backend"
)
func main() {
@@ -30,7 +30,7 @@ func main() {
path = filepath.Join(defaultConfigDir(), "index-v0.14.0.db")
}
ldb, err := db.OpenRO(path)
ldb, err := backend.OpenLevelDBRO(path)
if err != nil {
log.Fatal(err)
}

View File

@@ -7,6 +7,7 @@ package main
import (
"bytes"
"compress/gzip"
"context"
"crypto/tls"
"encoding/json"
"flag"
@@ -480,7 +481,7 @@ func handleRelayTest(request request) {
if debug {
log.Println("Request for", request.relay)
}
if !client.TestRelay(request.relay.uri, []tls.Certificate{testCert}, time.Second, 2*time.Second, 3) {
if !client.TestRelay(context.TODO(), request.relay.uri, []tls.Certificate{testCert}, time.Second, 2*time.Second, 3) {
if debug {
log.Println("Test for relay", request.relay, "failed")
}
@@ -633,7 +634,7 @@ func createTestCertificate() tls.Certificate {
}
certFile, keyFile := filepath.Join(tmpDir, "cert.pem"), filepath.Join(tmpDir, "key.pem")
cert, err := tlsutil.NewCertificate(certFile, keyFile, "relaypoolsrv")
cert, err := tlsutil.NewCertificate(certFile, keyFile, "relaypoolsrv", 20*365)
if err != nil {
log.Fatalln("Failed to create test X509 key pair:", err)
}

View File

@@ -14,12 +14,12 @@ import (
"os/signal"
"path/filepath"
"runtime"
"strconv"
"strings"
"sync/atomic"
"syscall"
"time"
"github.com/syncthing/syncthing/lib/build"
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/osutil"
"github.com/syncthing/syncthing/lib/relay/protocol"
@@ -34,24 +34,6 @@ import (
syncthingprotocol "github.com/syncthing/syncthing/lib/protocol"
)
var (
Version string
BuildStamp string
BuildUser string
BuildHost string
BuildDate time.Time
LongVersion string
)
func init() {
stamp, _ := strconv.Atoi(BuildStamp)
BuildDate = time.Unix(int64(stamp), 0)
date := BuildDate.UTC().Format("2006-01-02 15:04:05 MST")
LongVersion = fmt.Sprintf(`strelaysrv %s (%s %s-%s) %s@%s %s`, Version, runtime.Version(), runtime.GOOS, runtime.GOARCH, BuildUser, BuildHost, date)
}
var (
listen string
debug bool
@@ -117,8 +99,14 @@ func main() {
flag.IntVar(&natTimeout, "nat-timeout", 10, "NAT discovery timeout in seconds")
flag.BoolVar(&pprofEnabled, "pprof", false, "Enable the built in profiling on the status server")
flag.IntVar(&networkBufferSize, "network-buffer", 2048, "Network buffer size (two of these per proxied connection)")
showVersion := flag.Bool("version", false, "Show version")
flag.Parse()
if *showVersion {
fmt.Println(build.LongVersion)
return
}
if extAddress == "" {
extAddress = listen
}
@@ -147,7 +135,7 @@ func main() {
}
}
log.Println(LongVersion)
log.Println(build.LongVersion)
maxDescriptors, err := osutil.MaximizeOpenFileLimit()
if maxDescriptors > 0 {
@@ -167,7 +155,7 @@ func main() {
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
log.Println("Failed to load keypair. Generating one, this might take a while...")
cert, err = tlsutil.NewCertificate(certFile, keyFile, "strelaysrv")
cert, err = tlsutil.NewCertificate(certFile, keyFile, "strelaysrv", 20*365)
if err != nil {
log.Fatalln("Failed to generate X509 key pair:", err)
}

View File

@@ -10,6 +10,8 @@ import (
"runtime"
"sync/atomic"
"time"
"github.com/syncthing/syncthing/lib/build"
)
var rc *rateCalculator
@@ -40,10 +42,10 @@ func getStatus(w http.ResponseWriter, r *http.Request) {
sessionMut.Lock()
// This can potentially be double the number of pending sessions, as each session has two keys, one for each side.
status["version"] = Version
status["buildHost"] = BuildHost
status["buildUser"] = BuildUser
status["buildDate"] = BuildDate
status["version"] = build.Version
status["buildHost"] = build.Host
status["buildUser"] = build.User
status["buildDate"] = build.Date
status["startTime"] = rc.startTime
status["uptimeSeconds"] = time.Since(rc.startTime) / time.Second
status["numPendingSessionKeys"] = len(pendingSessions)

View File

@@ -4,6 +4,7 @@ package main
import (
"bufio"
"context"
"crypto/tls"
"flag"
"log"
@@ -19,6 +20,8 @@ import (
)
func main() {
ctx := context.Background()
log.SetOutput(os.Stdout)
log.SetFlags(log.LstdFlags | log.Lshortfile)
@@ -76,7 +79,7 @@ func main() {
}()
for {
conn, err := client.JoinSession(<-recv)
conn, err := client.JoinSession(ctx, <-recv)
if err != nil {
log.Fatalln("Failed to join", err)
}
@@ -90,13 +93,13 @@ func main() {
log.Fatal(err)
}
invite, err := client.GetInvitationFromRelay(uri, id, []tls.Certificate{cert}, 10*time.Second)
invite, err := client.GetInvitationFromRelay(ctx, uri, id, []tls.Certificate{cert}, 10*time.Second)
if err != nil {
log.Fatal(err)
}
log.Println("Received invitation", invite)
conn, err := client.JoinSession(invite)
conn, err := client.JoinSession(ctx, invite)
if err != nil {
log.Fatalln("Failed to join", err)
}
@@ -104,7 +107,7 @@ func main() {
connectToStdio(stdin, conn)
log.Println("Finished", conn.RemoteAddr(), conn.LocalAddr())
} else if test {
if client.TestRelay(uri, []tls.Certificate{cert}, time.Second, 2*time.Second, 4) {
if client.TestRelay(ctx, uri, []tls.Certificate{cert}, time.Second, 2*time.Second, 4) {
log.Println("OK")
} else {
log.Println("FAIL")

View File

@@ -7,16 +7,9 @@
package main
import (
"os"
"strings"
"github.com/syncthing/syncthing/lib/logger"
)
var (
l = logger.DefaultLogger.NewFacility("main", "Main package")
)
func init() {
l.SetDebug("main", strings.Contains(os.Getenv("STTRACE"), "main") || os.Getenv("STTRACE") == "all")
}

View File

@@ -45,19 +45,8 @@ import (
)
const (
exitSuccess = 0
exitError = 1
exitNoUpgradeAvailable = 2
exitRestarting = 3
exitUpgrading = 4
)
const (
bepProtocolName = "bep/1.0"
tlsDefaultCommonName = "syncthing"
maxSystemErrors = 5
initialSystemLog = 10
maxSystemLog = 250
tlsDefaultCommonName = "syncthing"
deviceCertLifetimeDays = 20 * 365
)
const (
@@ -165,6 +154,8 @@ type RuntimeOptions struct {
browserOnly bool
hideConsole bool
logFile string
logMaxSize int
logMaxFiles int
auditEnabled bool
auditFile string
paused bool
@@ -191,6 +182,8 @@ func defaultRuntimeOptions() RuntimeOptions {
cpuProfile: os.Getenv("STCPUPROFILE") != "",
stRestarting: os.Getenv("STRESTART") != "",
logFlags: log.Ltime,
logMaxSize: 10 << 20, // 10 MiB
logMaxFiles: 3, // plus the current one
}
if os.Getenv("STTRACE") != "" {
@@ -233,6 +226,8 @@ func parseCommandLineOptions() RuntimeOptions {
flag.BoolVar(&options.paused, "paused", false, "Start with all devices and folders paused")
flag.BoolVar(&options.unpaused, "unpaused", false, "Start with all devices and folders unpaused")
flag.StringVar(&options.logFile, "logfile", options.logFile, "Log file name (still always logs to stdout). Cannot be used together with -no-restart/STNORESTART environment variable.")
flag.IntVar(&options.logMaxSize, "log-max-size", options.logMaxSize, "Maximum size of any file (zero to disable log rotation).")
flag.IntVar(&options.logMaxFiles, "log-max-old-files", options.logMaxFiles, "Number of old files to keep (zero to keep only current).")
flag.StringVar(&options.auditFile, "auditfile", options.auditFile, "Specify audit file (use \"-\" for stdout, \"--\" for stderr)")
flag.BoolVar(&options.allowNewerConfig, "allow-newer-config", false, "Allow loading newer than current config version")
if runtime.GOOS == "windows" {
@@ -271,7 +266,7 @@ func main() {
// default location
if options.noRestart && (options.logFile != "" && options.logFile != "-") {
l.Warnln("-logfile may not be used with -no-restart or STNORESTART")
os.Exit(exitError)
os.Exit(syncthing.ExitError.AsInt())
}
if options.hideConsole {
@@ -285,12 +280,12 @@ func main() {
options.confDir, err = filepath.Abs(options.confDir)
if err != nil {
l.Warnln("Failed to make options path absolute:", err)
os.Exit(exitError)
os.Exit(syncthing.ExitError.AsInt())
}
}
if err := locations.SetBaseDir(locations.ConfigBaseDir, options.confDir); err != nil {
l.Warnln(err)
os.Exit(exitError)
os.Exit(syncthing.ExitError.AsInt())
}
}
@@ -328,7 +323,7 @@ func main() {
)
if err != nil {
l.Warnln("Error reading device ID:", err)
os.Exit(exitError)
os.Exit(syncthing.ExitError.AsInt())
}
fmt.Println(protocol.NewDeviceID(cert.Certificate[0]))
@@ -338,7 +333,7 @@ func main() {
if options.browserOnly {
if err := openGUI(protocol.EmptyDeviceID); err != nil {
l.Warnln("Failed to open web UI:", err)
os.Exit(exitError)
os.Exit(syncthing.ExitError.AsInt())
}
return
}
@@ -346,7 +341,7 @@ func main() {
if options.generateDir != "" {
if err := generate(options.generateDir); err != nil {
l.Warnln("Failed to generate config and keys:", err)
os.Exit(exitError)
os.Exit(syncthing.ExitError.AsInt())
}
return
}
@@ -354,14 +349,14 @@ func main() {
// Ensure that our home directory exists.
if err := ensureDir(locations.GetBaseDir(locations.ConfigBaseDir), 0700); err != nil {
l.Warnln("Failure on home directory:", err)
os.Exit(exitError)
os.Exit(syncthing.ExitError.AsInt())
}
if options.upgradeTo != "" {
err := upgrade.ToURL(options.upgradeTo)
if err != nil {
l.Warnln("Error while Upgrading:", err)
os.Exit(exitError)
os.Exit(syncthing.ExitError.AsInt())
}
l.Infoln("Upgraded from", options.upgradeTo)
return
@@ -381,7 +376,7 @@ func main() {
if options.resetDatabase {
if err := resetDB(); err != nil {
l.Warnln("Resetting database:", err)
os.Exit(exitError)
os.Exit(syncthing.ExitError.AsInt())
}
return
}
@@ -424,7 +419,7 @@ func generate(generateDir string) error {
if err == nil {
l.Warnln("Key exists; will not overwrite.")
} else {
cert, err = tlsutil.NewCertificate(certFile, keyFile, tlsDefaultCommonName)
cert, err = tlsutil.NewCertificate(certFile, keyFile, tlsDefaultCommonName, deviceCertLifetimeDays)
if err != nil {
return errors.Wrap(err, "create certificate")
}
@@ -476,13 +471,13 @@ func checkUpgrade() upgrade.Release {
release, err := upgrade.LatestRelease(opts.ReleasesURL, build.Version, opts.UpgradeToPreReleases)
if err != nil {
l.Warnln("Upgrade:", err)
os.Exit(exitError)
os.Exit(syncthing.ExitError.AsInt())
}
if upgrade.CompareVersions(release.Tag, build.Version) <= 0 {
noUpgradeMessage := "No upgrade available (current %q >= latest %q)."
l.Infof(noUpgradeMessage, build.Version, release.Tag)
os.Exit(exitNoUpgradeAvailable)
os.Exit(syncthing.ExitNoUpgradeAvailable.AsInt())
}
l.Infof("Upgrade available (current %q < latest %q)", build.Version, release.Tag)
@@ -496,7 +491,7 @@ func performUpgrade(release upgrade.Release) {
err = upgrade.To(release)
if err != nil {
l.Warnln("Upgrade:", err)
os.Exit(exitError)
os.Exit(syncthing.ExitError.AsInt())
}
l.Infof("Upgraded to %q", release.Tag)
} else {
@@ -504,10 +499,10 @@ func performUpgrade(release upgrade.Release) {
err = upgradeViaRest()
if err != nil {
l.Warnln("Upgrade:", err)
os.Exit(exitError)
os.Exit(syncthing.ExitError.AsInt())
}
l.Infoln("Syncthing upgrading")
os.Exit(exitUpgrading)
os.Exit(syncthing.ExitUpgrade.AsInt())
}
}
@@ -523,7 +518,7 @@ func upgradeViaRest() error {
r.Header.Set("X-API-Key", cfg.GUI().APIKey)
tr := &http.Transport{
Dial: dialer.Dial,
DialContext: dialer.DialContext,
Proxy: http.ProxyFromEnvironment,
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
@@ -573,7 +568,7 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
cfg, err := syncthing.LoadConfigAtStartup(locations.Get(locations.ConfigFile), cert, evLogger, runtimeOptions.allowNewerConfig, noDefaultFolder)
if err != nil {
l.Warnln("Failed to initialize config:", err)
os.Exit(exitError)
os.Exit(syncthing.ExitError.AsInt())
}
if runtimeOptions.unpaused {
@@ -610,11 +605,11 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
f, err := os.Create(fmt.Sprintf("cpu-%d.pprof", os.Getpid()))
if err != nil {
l.Warnln("Creating profile:", err)
os.Exit(exitError)
os.Exit(syncthing.ExitError.AsInt())
}
if err := pprof.StartCPUProfile(f); err != nil {
l.Warnln("Starting profile:", err)
os.Exit(exitError)
os.Exit(syncthing.ExitError.AsInt())
}
}
@@ -647,7 +642,9 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
}
}
app.Start()
if err := app.Start(); err != nil {
os.Exit(syncthing.ExitError.AsInt())
}
cleanConfigDirectory()
@@ -721,7 +718,7 @@ func auditWriter(auditFile string) io.Writer {
fd, err = os.OpenFile(auditFile, auditFlags, 0600)
if err != nil {
l.Warnln("Audit:", err)
os.Exit(exitError)
os.Exit(syncthing.ExitError.AsInt())
}
auditDest = auditFile
}
@@ -897,6 +894,6 @@ func setPauseState(cfg config.Wrapper, paused bool) {
}
if _, err := cfg.Replace(raw); err != nil {
l.Warnln("Cannot adjust paused state:", err)
os.Exit(exitError)
os.Exit(syncthing.ExitError.AsInt())
}
}

View File

@@ -9,10 +9,12 @@ package main
import (
"bufio"
"context"
"fmt"
"io"
"os"
"os/exec"
"os/signal"
"path/filepath"
"runtime"
"strings"
"syscall"
@@ -23,6 +25,7 @@ import (
"github.com/syncthing/syncthing/lib/osutil"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/sync"
"github.com/syncthing/syncthing/lib/syncthing"
)
var (
@@ -47,7 +50,15 @@ func monitorMain(runtimeOptions RuntimeOptions) {
logFile := runtimeOptions.logFile
if logFile != "-" {
var fileDst io.Writer = newAutoclosedFile(logFile, logFileAutoCloseDelay, logFileMaxOpenTime)
var fileDst io.Writer
if runtimeOptions.logMaxSize > 0 {
open := func(name string) (io.WriteCloser, error) {
return newAutoclosedFile(name, logFileAutoCloseDelay, logFileMaxOpenTime), nil
}
fileDst = newRotatedFile(logFile, open, int64(runtimeOptions.logMaxSize), runtimeOptions.logMaxFiles)
} else {
fileDst = newAutoclosedFile(logFile, logFileAutoCloseDelay, logFileMaxOpenTime)
}
if runtime.GOOS == "windows" {
// Translate line breaks to Windows standard
@@ -81,7 +92,7 @@ func monitorMain(runtimeOptions RuntimeOptions) {
if t := time.Since(restarts[0]); t < loopThreshold {
l.Warnf("%d restarts in %v; not retrying further", countRestarts, t)
os.Exit(exitError)
os.Exit(syncthing.ExitError.AsInt())
}
copy(restarts[0:], restarts[1:])
@@ -150,17 +161,14 @@ func monitorMain(runtimeOptions RuntimeOptions) {
// Successful exit indicates an intentional shutdown
return
} else if exiterr, ok := err.(*exec.ExitError); ok {
if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
switch status.ExitStatus() {
case exitUpgrading:
// Restart the monitor process to release the .old
// binary as part of the upgrade process.
l.Infoln("Restarting monitor...")
if err = restartMonitor(args); err != nil {
l.Warnln("Restart:", err)
}
return
if exiterr.ExitCode() == syncthing.ExitUpgrade.AsInt() {
// Restart the monitor process to release the .old
// binary as part of the upgrade process.
l.Infoln("Restarting monitor...")
if err = restartMonitor(args); err != nil {
l.Warnln("Restart:", err)
}
return
}
}
}
@@ -319,6 +327,81 @@ func restartMonitorWindows(args []string) error {
return cmd.Start()
}
// rotatedFile keeps a set of rotating logs. There will be the base file plus up
// to maxFiles rotated ones, each ~ maxSize bytes large.
type rotatedFile struct {
name string
create createFn
maxSize int64 // bytes
maxFiles int
currentFile io.WriteCloser
currentSize int64
}
// the createFn should act equivalently to os.Create
type createFn func(name string) (io.WriteCloser, error)
func newRotatedFile(name string, create createFn, maxSize int64, maxFiles int) *rotatedFile {
return &rotatedFile{
name: name,
create: create,
maxSize: maxSize,
maxFiles: maxFiles,
}
}
func (r *rotatedFile) Write(bs []byte) (int, error) {
// Check if we're about to exceed the max size, and if so close this
// file so we'll start on a new one.
if r.currentSize+int64(len(bs)) > r.maxSize {
r.currentFile.Close()
r.currentFile = nil
r.currentSize = 0
}
// If we have no current log, rotate old files out of the way and create
// a new one.
if r.currentFile == nil {
r.rotate()
fd, err := r.create(r.name)
if err != nil {
return 0, err
}
r.currentFile = fd
}
n, err := r.currentFile.Write(bs)
r.currentSize += int64(n)
return n, err
}
func (r *rotatedFile) rotate() {
// The files are named "name", "name.0", "name.1", ...
// "name.(r.maxFiles-1)". Increase the numbers on the
// suffixed ones.
for i := r.maxFiles - 1; i > 0; i-- {
from := numberedFile(r.name, i-1)
to := numberedFile(r.name, i)
err := os.Rename(from, to)
if err != nil && !os.IsNotExist(err) {
fmt.Println("LOG: Rotating logs:", err)
}
}
// Rename the base to base.0
err := os.Rename(r.name, numberedFile(r.name, 0))
if err != nil && !os.IsNotExist(err) {
fmt.Println("LOG: Rotating logs:", err)
}
}
// numberedFile adds the number between the file name and the extension.
func numberedFile(name string, num int) string {
ext := filepath.Ext(name) // contains the dot
withoutExt := name[:len(name)-len(ext)]
return fmt.Sprintf("%s.%d%s", withoutExt, num, ext)
}
// An autoclosedFile is an io.WriteCloser that opens itself for appending on
// Write() and closes itself after an interval of no writes (closeDelay) or
// when the file has been open for too long (maxOpenTime). A call to Write()

View File

@@ -7,6 +7,7 @@
package main
import (
"io"
"io/ioutil"
"os"
"path/filepath"
@@ -14,6 +15,123 @@ import (
"time"
)
func TestRotatedFile(t *testing.T) {
// Verify that log rotation happens.
dir, err := ioutil.TempDir("", "syncthing")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
open := func(name string) (io.WriteCloser, error) {
return os.Create(name)
}
logName := filepath.Join(dir, "log.txt")
testData := []byte("12345678\n")
maxSize := int64(len(testData) + len(testData)/2)
// We allow the log file plus two rotated copies.
rf := newRotatedFile(logName, open, maxSize, 2)
// Write some bytes.
if _, err := rf.Write(testData); err != nil {
t.Fatal(err)
}
// They should be in the log.
checkSize(t, logName, len(testData))
checkNotExist(t, logName+".0")
// Write some more bytes. We should rotate and write into a new file as the
// new bytes don't fit.
if _, err := rf.Write(testData); err != nil {
t.Fatal(err)
}
checkSize(t, logName, len(testData))
checkSize(t, numberedFile(logName, 0), len(testData))
checkNotExist(t, logName+".1")
// Write another byte. That should fit without causing an extra rotate.
_, _ = rf.Write([]byte{42})
checkSize(t, logName, len(testData)+1)
checkSize(t, numberedFile(logName, 0), len(testData))
checkNotExist(t, numberedFile(logName, 1))
// Write some more bytes. We should rotate and write into a new file as the
// new bytes don't fit.
if _, err := rf.Write(testData); err != nil {
t.Fatal(err)
}
checkSize(t, logName, len(testData))
checkSize(t, numberedFile(logName, 0), len(testData)+1) // the one we wrote extra to, now rotated
checkSize(t, numberedFile(logName, 1), len(testData))
checkNotExist(t, numberedFile(logName, 2))
// Write some more bytes. We should rotate and write into a new file as the
// new bytes don't fit.
if _, err := rf.Write(testData); err != nil {
t.Fatal(err)
}
checkSize(t, logName, len(testData))
checkSize(t, numberedFile(logName, 0), len(testData))
checkSize(t, numberedFile(logName, 1), len(testData)+1)
checkNotExist(t, numberedFile(logName, 2)) // exceeds maxFiles so deleted
}
func TestNumberedFile(t *testing.T) {
// Mostly just illustrates where the number ends up and makes sure it
// doesn't crash without an extension.
cases := []struct {
in string
num int
out string
}{
{
in: "syncthing.log",
num: 42,
out: "syncthing.42.log",
},
{
in: filepath.Join("asdfasdf", "syncthing.log.txt"),
num: 42,
out: filepath.Join("asdfasdf", "syncthing.log.42.txt"),
},
{
in: "syncthing-log",
num: 42,
out: "syncthing-log.42",
},
}
for _, tc := range cases {
res := numberedFile(tc.in, tc.num)
if res != tc.out {
t.Errorf("numberedFile(%q, %d) => %q, expected %q", tc.in, tc.num, res, tc.out)
}
}
}
func checkSize(t *testing.T, name string, size int) {
t.Helper()
info, err := os.Lstat(name)
if err != nil {
t.Fatal(err)
}
if info.Size() != int64(size) {
t.Errorf("%s wrong size: %d != expected %d", name, info.Size(), size)
}
}
func checkNotExist(t *testing.T, name string) {
t.Helper()
_, err := os.Lstat(name)
if !os.IsNotExist(err) {
t.Errorf("%s should not exist", name)
}
}
func TestAutoClosedFile(t *testing.T) {
os.RemoveAll("_autoclose")
defer os.RemoveAll("_autoclose")

View File

@@ -1,39 +0,0 @@
// Copyright (C) 2014 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
// +build ignore
package main
import (
"bytes"
"fmt"
"io"
"os"
)
func main() {
buf := make([]byte, 4096)
var err error
for err == nil {
n, err := io.ReadFull(os.Stdin, buf)
if n > 0 {
buf = buf[:n]
repl := bytes.Replace(buf, []byte("\n"), []byte("\r\n"), -1)
_, err = os.Stdout.Write(repl)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
}
if err == io.EOF {
return
}
buf = buf[:cap(buf)]
}
fmt.Println(err)
os.Exit(1)
}

View File

@@ -1416,7 +1416,7 @@ func getReport(db *sql.DB) map[string]interface{} {
r["categories"] = categories
r["versions"] = group(byVersion, analyticsFor(versions, 2000), 10)
r["versionPenetrations"] = penetrationLevels(analyticsFor(versions, 2000), []float64{50, 75, 90, 95})
r["platforms"] = group(byPlatform, analyticsFor(platforms, 2000), 5)
r["platforms"] = group(byPlatform, analyticsFor(platforms, 2000), 10)
r["compilers"] = group(byCompiler, analyticsFor(compilers, 2000), 5)
r["builders"] = analyticsFor(builders, 12)
r["distributions"] = analyticsFor(distributions, 10)

17
go.mod
View File

@@ -15,37 +15,34 @@ require (
github.com/getsentry/raven-go v0.2.0
github.com/go-ole/go-ole v1.2.4 // indirect
github.com/gobwas/glob v0.2.3
github.com/gogo/protobuf v1.3.0
github.com/gogo/protobuf v1.3.1
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6
github.com/golang/mock v1.3.1 // indirect
github.com/jackpal/gateway v1.0.5
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
github.com/kr/pretty v0.1.0 // indirect
github.com/lib/pq v1.2.0
github.com/lucas-clemente/quic-go v0.12.0
github.com/lucas-clemente/quic-go v0.12.1
github.com/maruel/panicparse v1.3.0
github.com/mattn/go-isatty v0.0.9
github.com/minio/sha256-simd v0.1.0
github.com/mattn/go-isatty v0.0.10
github.com/minio/sha256-simd v0.1.1
github.com/onsi/ginkgo v1.9.0 // indirect
github.com/onsi/gomega v1.6.0 // indirect
github.com/oschwald/geoip2-golang v1.3.0
github.com/oschwald/maxminddb-golang v1.4.0 // indirect
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect
github.com/pkg/errors v0.8.1
github.com/prometheus/client_golang v1.1.0
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 // indirect
github.com/prometheus/procfs v0.0.4 // indirect
github.com/prometheus/client_golang v1.2.1
github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563
github.com/sasha-s/go-deadlock v0.2.0
github.com/shirou/gopsutil v0.0.0-20190714054239-47ef3260b6bf
github.com/syncthing/notify v0.0.0-20190709140112-69c7a957d3e2
github.com/syndtr/goleveldb v1.0.1-0.20190318030020-c3a204f8e965
github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d
github.com/thejerf/suture v3.0.2+incompatible
github.com/urfave/cli v1.21.0
github.com/urfave/cli v1.22.2
github.com/vitrun/qart v0.0.0-20160531060029-bf64b92db6b0
golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd // indirect
golang.org/x/text v0.3.2
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect

41
go.sum
View File

@@ -10,7 +10,9 @@ github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrU
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk=
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0=
@@ -25,10 +27,14 @@ github.com/ccding/go-stun v0.0.0-20180726100737-be486d185f3d h1:As4937T5NVbJ/DmZ
github.com/ccding/go-stun v0.0.0-20180726100737-be486d185f3d/go.mod h1:3FK1bMar37f7jqVY7q/63k3OMX1c47pGCufzt3X0sYE=
github.com/certifi/gocertifi v0.0.0-20190905060710-a5e0173ced67 h1:8k9FLYBLKT+9v2HQJ/a95ZemmTx+/ltJcAiRhVushG8=
github.com/certifi/gocertifi v0.0.0-20190905060710-a5e0173ced67/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4=
github.com/cespare/xxhash/v2 v2.1.0 h1:yTUvW7Vhb89inJ+8irsUqiWjh8iT6sQPZiQzI6ReGkA=
github.com/cespare/xxhash/v2 v2.1.0/go.mod h1:dgIUBU3pDso/gPgZ1osOZ0iQf77oPR28Tjxl5dIMyVM=
github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE=
github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
github.com/chmduquesne/rollinghash v0.0.0-20180912150627-a60f8e7142b5 h1:Wg96Dh0MLTanEaPO0OkGtUIaa2jOnShAIOVUIzRHUxo=
github.com/chmduquesne/rollinghash v0.0.0-20180912150627-a60f8e7142b5/go.mod h1:Uc2I36RRfTAf7Dge82bi3RU0OQUmXT9iweIcPqvr8A0=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/d4l3k/messagediff v1.2.1 h1:ZcAIMYsUg0EAp9X+tt8/enBE/Q8Yd5kzPynLyKptt9U=
github.com/d4l3k/messagediff v1.2.1/go.mod h1:Oozbb1TVXFac9FtSIxHBMnBCq2qeH/2KkEQxENCrlLo=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -41,6 +47,7 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
github.com/getsentry/raven-go v0.2.0 h1:no+xWJRb5ZI7eE8TWgIq1jLulQiIoLG0IfYxv5JYMGs=
github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-ole/go-ole v1.2.1 h1:2lOsA72HgjxAuMlKpFiCbHTvu44PIVkZ5hqm3RSdI/E=
@@ -53,6 +60,8 @@ github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJA
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.3.0 h1:G8O7TerXerS4F6sx9OV7/nRfJdnXgHZu/S/7F2SN+UE=
github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk=
@@ -93,6 +102,8 @@ github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lucas-clemente/quic-go v0.12.0 h1:TRbvZ6F++sofeGbh+Z2IIyIOhl8KyGnYuA06g2yrHdI=
github.com/lucas-clemente/quic-go v0.12.0/go.mod h1:UXJJPE4RfFef/xPO5wQm0tITK8gNfqwTxjbE7s3Vb8s=
github.com/lucas-clemente/quic-go v0.12.1 h1:BPITli+6KnKogtTxBk2aS4okr5dUHz2LtIDAP1b8UL4=
github.com/lucas-clemente/quic-go v0.12.1/go.mod h1:UXJJPE4RfFef/xPO5wQm0tITK8gNfqwTxjbE7s3Vb8s=
github.com/marten-seemann/qpack v0.1.0/go.mod h1:LFt1NU/Ptjip0C2CPkhimBz5CGE3WGDAUWqna+CNTrI=
github.com/marten-seemann/qtls v0.3.2 h1:O7awy4bHEzSX/K3h+fZig3/Vo03s/RxlxgsAk9sYamI=
github.com/marten-seemann/qtls v0.3.2/go.mod h1:xzjG7avBwGGbdZ8dTGxlBnLArsVKLvwmjgmPuiQEcYk=
@@ -105,12 +116,14 @@ github.com/mattn/go-isatty v0.0.7 h1:UvyT9uN+3r7yLEYSlJsbQGdsaB/a0DlgWP3pql6iwOc
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.9 h1:d5US/mDsogSGW37IV293h//ZFaeajb69h+EHFsv2xGg=
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10=
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/minio/sha256-simd v0.1.0 h1:U41/2erhAKcmSI14xh/ZTUdBPOzDOIfS93ibzUSl8KM=
github.com/minio/sha256-simd v0.1.0/go.mod h1:2FMWW+8GMoPweT6+pI63m9YE3Lmw4J71hV56Chs1E/U=
github.com/minio/sha256-simd v0.1.1 h1:5QHSlgo3nt5yKOJrC7W8w7X+NFl8cMPZm96iu8kKUJU=
github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
@@ -140,6 +153,8 @@ github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.1.0 h1:BQ53HtBmfOitExawJ6LokA4x8ov/z0SYYb0+HxJfRI8=
github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g=
github.com/prometheus/client_golang v1.2.1 h1:JnMpQc6ppsNgw9QPAGF6Dod479itz7lvlsMzzNayLOI=
github.com/prometheus/client_golang v1.2.1/go.mod h1:XMU6Z2MjaRKVu/dC1qupJI9SiNkDYzz3xecMgSW/F+U=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE=
@@ -150,6 +165,8 @@ github.com/prometheus/common v0.4.1 h1:K0MGApIoQvMw27RTdJkPbr3JZ7DNbtxQNyi5STVM6
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.6.0 h1:kRhiuYSXR3+uv2IbVbZhUxK5zVD/2pp3Gd2PpvPkpEo=
github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc=
github.com/prometheus/common v0.7.0 h1:L+1lyG48J1zAQXA3RBX/nG/B3gjlHq0zTt2tlbJLyCY=
github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2 h1:6LJUbpNm42llc4HRCuvApCSWB/WfhuNo9K98Q9sNGfs=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
@@ -157,14 +174,21 @@ github.com/prometheus/procfs v0.0.3 h1:CTwfnzjQ+8dS6MhHHu4YswVAD99sL2wjPqP+VkURm
github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
github.com/prometheus/procfs v0.0.4 h1:w8DjqFMJDjuVwdZBQoOozr4MVWOnwF7RcL/7uxBjY78=
github.com/prometheus/procfs v0.0.4/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
github.com/prometheus/procfs v0.0.5 h1:3+auTFlqw+ZaQYJARz6ArODtkaIwtvBTx3N2NehQlL8=
github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563 h1:dY6ETXrvDG7Sa4vE8ZQG4yqWg6UnOcbqTAahkV813vQ=
github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sasha-s/go-deadlock v0.2.0 h1:lMqc+fUb7RrFS3gQLtoQsJ7/6TV/pAIFvBsqX73DK8Y=
github.com/sasha-s/go-deadlock v0.2.0/go.mod h1:StQn567HiB1fF2yJ44N9au7wOhrPS3iZqiDbRupzT10=
github.com/shirou/gopsutil v0.0.0-20190714054239-47ef3260b6bf h1:c9SV5NzG4KOk448TUE7iqCmb4E4y79CZF4zDdc1Jx3Q=
github.com/shirou/gopsutil v0.0.0-20190714054239-47ef3260b6bf/go.mod h1:WWnYX4lzhCH5h/3YBfyVA3VbLYjlMZZAQcW9ojMexNc=
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
@@ -175,12 +199,16 @@ github.com/syncthing/notify v0.0.0-20190709140112-69c7a957d3e2 h1:6tuEEEpg+mxM82
github.com/syncthing/notify v0.0.0-20190709140112-69c7a957d3e2/go.mod h1:Sn4ChoS7e4FxjCN1XHPVBT43AgnRLbuaB8pEc1Zcdjg=
github.com/syndtr/goleveldb v1.0.1-0.20190318030020-c3a204f8e965 h1:1oFLiOyVl+W7bnBzGhf7BbIv9loSFQcieWWYIjLqcAw=
github.com/syndtr/goleveldb v1.0.1-0.20190318030020-c3a204f8e965/go.mod h1:9OrXJhf154huy1nPWmuSrkgjPUtUNhA+Zmy+6AESzuA=
github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d h1:gZZadD8H+fF+n9CmNhYL1Y0dJB+kLOmKd7FbPJLeGHs=
github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d/go.mod h1:9OrXJhf154huy1nPWmuSrkgjPUtUNhA+Zmy+6AESzuA=
github.com/thejerf/suture v3.0.2+incompatible h1:GtMydYcnK4zBJ0KL6Lx9vLzl6Oozb65wh252FTBxrvM=
github.com/thejerf/suture v3.0.2+incompatible/go.mod h1:ibKwrVj+Uzf3XZdAiNWUouPaAbSoemxOHLmJmwheEMc=
github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.21.0 h1:wYSSj06510qPIzGSua9ZqsncMmWE3Zr55KBERygyrxE=
github.com/urfave/cli v1.21.0/go.mod h1:lxDj6qX9Q6lWQxIrbrT0nwecwUtRnhVZAJjJZrVUZZQ=
github.com/urfave/cli v1.22.1 h1:+mkCCcOFKPnCmVYVcURKps1Xe+3zP90gSYGNfRkjoIY=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli v1.22.2 h1:gsqYFH8bb9ekPA12kRo0hfjngWQjkJPlN9R0N78BoUo=
github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/vitrun/qart v0.0.0-20160531060029-bf64b92db6b0 h1:okhMind4q9H1OxF44gNegWkiP4H/gsTFLalHFa4OOUI=
github.com/vitrun/qart v0.0.0-20160531060029-bf64b92db6b0/go.mod h1:TTbGUfE+cXXceWtbTHq6lqcTvYPBKLNejBEbnUsQJtU=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
@@ -213,11 +241,16 @@ golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e h1:ZytStCyV048ZqDsWHiYDdoI2Vd4msMcrDECFxS+tL9c=
golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd h1:DBH9mDw0zluJT/R+nGuV3jWFWLFaHyYZWD4tOT+cjn0=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191008105621-543471e840be h1:QAcqgptGM8IQBC9K/RC4o+O9YmqEm0diQn9QmZw/0mU=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191010194322-b09406accb47 h1:/XfQ9z7ib8eEJX2hdgFTZJ/ntt0swNk5oYBziWeTCvY=
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=

View File

@@ -24,6 +24,27 @@ a:hover,a:focus,a.focus{
border-width: 2px !important;
}
.nav-tabs > li.active > a,
.nav-tabs > li.active > a:hover,
.nav-tabs > li.active > a:focus {
color: #3498db !important;
background-color: #222222 !important;
border: 1px solid #222222 !important;
border-bottom-color: transparent !important;
cursor: default;
}
.nav-tabs{
border-bottom: 1px solid #333;
}
.nav-tabs > li > a:hover,
.nav-tabs > li > a:focus {
background-color: #222222 !important;
border: none !important;
}
.navbar-text, .dropdown>a, .dropdown-menu>li>a, .hidden-xs>a, .navbar-link {
color: #aaa !important;
}

View File

@@ -28,6 +28,27 @@ a:hover,a:focus,a.focus{
}
.nav-tabs > li.active > a,
.nav-tabs > li.active > a:hover,
.nav-tabs > li.active > a:focus {
color: #3498db !important;
background-color: #424242 !important;
border: 1px solid #424242 !important;
border-bottom-color: transparent !important;
cursor: default;
}
.nav-tabs{
border-bottom: 1px solid #333;
}
.nav-tabs > li > a:hover,
.nav-tabs > li > a:focus {
background-color: #424242 !important;
border: none !important;
}
.navbar-text, .dropdown>a, .dropdown-menu>li>a, .hidden-xs>a, .navbar-link {
color: #aaa !important;
}

View File

@@ -246,6 +246,14 @@ a.toggler:hover {
text-decoration: none;
}
/**
* Panel padding decrease
*/
.panel-collapse .panel-body {
padding: 5px;
}
/**
* Progress bars with centered text
*/
@@ -348,6 +356,12 @@ ul.three-columns li, ul.two-columns li {
* columns. */
white-space: normal;
}
.two-columns {
-webkit-column-count: 1;
-moz-column-count: 1;
column-count: 1;
}
}
@media (max-width:479px) {
@@ -392,7 +406,7 @@ ul.three-columns li, ul.two-columns li {
max-width: 100%;
width: 100%;
}
/* all buttons, except panel headings, get bottom margin, as they won't fit
beside each other anymore */
.btn:not(.panel-heading),
@@ -400,4 +414,4 @@ ul.three-columns li, ul.two-columns li {
.btn:not(.panel-heading) + .btn:not(.panel-heading) {
margin-bottom: 1rem;
}
}
}

View File

@@ -7,29 +7,5 @@
*/
.panel-progress {
background: #3498db;
}
.identicon rect {
fill: #333;
}
.panel-warning .identicon rect {
fill: #fff;
}
.li-column {
background-color: rgb(236, 240, 241);
border-radius: 3px;
}
.panel-heading:hover, .panel-heading:focus {
text-decoration: none;
}
.fancytree-ext-filter-hide tr.fancytree-submatch span.fancytree-title,
.fancytree-ext-filter-hide span.fancytree-node.fancytree-submatch span.fancytree-title {
color: black !important;
font-weight: lighter !important;
}
@import "../../theme-assets/dark/assets/css/theme.css" screen and (prefers-color-scheme: dark);
@import "../../theme-assets/light/assets/css/theme.css" (prefers-color-scheme: light), (prefers-color-scheme: no-preference);

View File

@@ -42,7 +42,7 @@
<p class="navbar-text hidden-xs" ng-class="{'hidden-sm':upgradeInfo && upgradeInfo.newer}">{{thisDeviceName()}}</p>
<ul class="nav navbar-nav navbar-right">
<li ng-if="upgradeInfo && upgradeInfo.newer" class="upgrade-newer">
<button type="button" class="btn navbar-btn btn-primary btn-sm" ng-click="upgrade()">
<button type="button" class="btn navbar-btn btn-primary btn-sm" data-toggle="modal" data-target="#upgrade">
<span class="fas fa-arrow-circle-up"></span>
<span class="hidden-xs" translate translate-value-version="{{upgradeInfo.latest}}">Upgrade To {%version%}</span>
</button>
@@ -323,6 +323,11 @@
<span class="visible-xs" aria-label="{{'Scanning' | translate}}"><i class="fas fa-fw fa-search"></i></span>
</span>
<span ng-switch-when="idle"><span class="hidden-xs" translate>Up to Date</span><span class="visible-xs" aria-label="{{'Up to Date' | translate}}"><i class="fas fa-fw fa-check"></i></span></span>
<span ng-switch-when="localadditions"><span class="hidden-xs" translate>Local Additions</span><span class="visible-xs" aria-label="{{'Local Additions' | translate}}"><i class="fas fa-fw fa-check"></i></span></span>
<span ng-switch-when="sync-preparing">
<span class="hidden-xs" translate>Preparing to Sync</span>
<span class="visible-xs" aria-label="{{'Preparing to Sync' | translate}}"><i class="fas fa-fw fa-hourglass-half"></i></span>
</span>
<span ng-switch-when="syncing">
<span class="hidden-xs" translate>Syncing</span>
<span>({{syncPercentage(folder.id) | percent}}, {{model[folder.id].needBytes | binary}}B)</span>
@@ -836,6 +841,7 @@
<ng-include src="'syncthing/transfer/failedFilesModalView.html'"></ng-include>
<ng-include src="'syncthing/transfer/remoteNeededFilesModalView.html'"></ng-include>
<ng-include src="'syncthing/transfer/localChangedFilesModalView.html'"></ng-include>
<ng-include src="'syncthing/core/upgradeModalView.html'"></ng-include>
<ng-include src="'syncthing/core/majorUpgradeModalView.html'"></ng-include>
<ng-include src="'syncthing/core/aboutModalView.html'"></ng-include>
<ng-include src="'syncthing/core/discoveryFailuresModalView.html'"></ng-include>

View File

@@ -66,6 +66,14 @@ function folderCompare(a, b) {
return labelA > labelB;
}
function deviceMap(l) {
var m = {};
l.forEach(function (r) {
m[r.deviceID] = r;
});
return m;
}
function folderMap(l) {
var m = {};
l.forEach(function (r) {

View File

@@ -1,7 +1,7 @@
<modal id="about" status="info" icon="far fa-heart" heading="{{'About' | translate}}" large="yes" closeable="yes">
<div class="modal-body">
<h1 class="text-center">
<img alt="Syncthing" src="assets/img/logo-horizontal.svg" style="vertical-align: -16px" height="100" width="366" />
<img alt="Syncthing" src="assets/img/logo-horizontal.svg" style="max-width: 366px; vertical-align: -16px" />
<br />
<small>{{versionString()}}</small>
<br />

View File

@@ -8,7 +8,7 @@
<div id="log-viewer-log" class="tab-pane in active">
<label translate ng-if="logging.logEntries.length == 0">Loading...</label>
<textarea id="logViewerText" class="form-control" rows="20" ng-if="logging.logEntries.length != 0" readonly style="font-family: Consolas; font-size: 11px; overflow: auto;">{{ logging.content() }}</textarea>
<textarea id="logViewerText" class="form-control" rows="20" ng-if="logging.logEntries.length != 0" readonly style="font-family: Consolas, monospace; font-size: 11px; overflow: auto;">{{ logging.content() }}</textarea>
<p translate class="help-block" ng-style="{'visibility': logging.paused ? 'visible' : 'hidden'}">Log tailing paused. Scroll to the bottom to continue.</p>
</div>

View File

@@ -60,7 +60,9 @@ angular.module('syncthing.core')
} catch (exception) { }
$scope.folderDefaults = {
sharedDevices: {},
selectedDevices: {},
unrelatedDevices: {},
type: "sendreceive",
rescanIntervalS: 3600,
fsWatcherDelayS: 10,
@@ -387,6 +389,7 @@ angular.module('syncthing.core')
});
refreshNoAuthWarning();
setDefaultTheme();
if (!hasConfig) {
$scope.$emit('ConfigLoaded');
@@ -439,6 +442,7 @@ angular.module('syncthing.core')
var guiCfg = $scope.config.gui;
$scope.openNoAuth = addr.substr(0, 4) !== "127."
&& addr.substr(0, 6) !== "[::1]:"
&& addr.substr(0, 1) !== "/"
&& (!guiCfg.user || !guiCfg.password)
&& guiCfg.authMode !== 'ldap'
&& !guiCfg.insecureAdminAccess;
@@ -648,6 +652,23 @@ angular.module('syncthing.core')
$scope.remoteNeedDevice = undefined;
}
function setDefaultTheme() {
if (!document.getElementById("fallback-theme-css")){
// check if no support for prefers-color-scheme
var colorSchemeNotSupported = typeof window.matchMedia === "undefined" || window.matchMedia('(prefers-color-scheme: dark)').media === 'not all';
if ($scope.config.gui.theme === "default" && colorSchemeNotSupported) {
document.documentElement.style.display = 'none';
document.head.insertAdjacentHTML(
'beforeend',
'<link id="fallback-theme-css" rel="stylesheet" href="theme-assets/light/assets/css/theme.css" onload="document.documentElement.style.display = \'\'">'
);
}
}
}
function saveIgnores(ignores, cb) {
$http.post(urlbase + '/db/ignores?folder=' + encodeURIComponent($scope.currentFolder.id), {
ignore: ignores
@@ -767,25 +788,31 @@ angular.module('syncthing.core')
return 'paused';
}
var folderInfo = $scope.model[folderCfg.id];
// after restart syncthing process state may be empty
if (!$scope.model[folderCfg.id].state) {
if (!folderInfo.state) {
return 'unknown';
}
var state = '' + $scope.model[folderCfg.id].state;
var state = '' + folderInfo.state;
if (state === 'error') {
return 'stopped'; // legacy, the state is called "stopped" in the GUI
}
if (state === 'idle' && $scope.model[folderCfg.id].needTotalItems > 0) {
if (state !== 'idle') {
return state;
}
if (folderInfo.needTotalItems > 0) {
return 'outofsync';
}
if ($scope.hasFailedFiles(folderCfg.id)) {
return 'faileditems';
}
if (state === 'scanning') {
return state;
if (folderInfo.receiveOnlyTotalItems) {
return 'localadditions';
}
if (folderCfg.devices.length <= 1) {
return 'unshared';
}
@@ -796,13 +823,13 @@ angular.module('syncthing.core')
$scope.folderClass = function (folderCfg) {
var status = $scope.folderStatus(folderCfg);
if (status === 'idle') {
if (status === 'idle' || status === 'localadditions') {
return 'success';
}
if (status == 'paused') {
return 'default';
}
if (status === 'syncing' || status === 'scanning') {
if (status === 'syncing' || status === 'sync-preparing' || status === 'scanning') {
return 'primary';
}
if (status === 'unknown') {
@@ -852,6 +879,9 @@ angular.module('syncthing.core')
// 32m 40s
// 2h 32m
// 4d 2h
// In case remaining scan time appears to be >31d, omit the
// details, i.e.:
// > 1 month
if (!$scope.scanProgress[folder]) {
return "";
@@ -871,6 +901,9 @@ angular.module('syncthing.core')
var res = [];
if (seconds >= 86400) {
days = Math.floor(seconds / 86400);
if (days > 31) {
return '> 1 month';
}
res.push('' + days + 'd')
seconds = seconds % 86400;
}
@@ -961,6 +994,7 @@ angular.module('syncthing.core')
for (var i = 0; i < folderListCache.length; i++) {
var status = $scope.folderStatus(folderListCache[i]);
switch (status) {
case 'sync-preparing':
case 'syncing':
syncCount++;
break;
@@ -1353,6 +1387,7 @@ angular.module('syncthing.core')
$scope.upgrade = function () {
restarting = true;
$('#upgrade').modal('hide');
$('#majorUpgrade').modal('hide');
$('#upgrading').modal();
$http.post(urlbase + '/system/upgrade').success(function () {
@@ -1391,13 +1426,13 @@ angular.module('syncthing.core')
};
$scope.selectAllFolders = function () {
angular.forEach($scope.folders, function (id) {
angular.forEach($scope.folders, function (_, id) {
$scope.currentDevice.selectedFolders[id] = true;
});
};
$scope.deSelectAllFolders = function () {
angular.forEach($scope.folders, function (id) {
angular.forEach($scope.folders, function (_, id) {
$scope.currentDevice.selectedFolders[id] = false;
});
};
@@ -1671,10 +1706,20 @@ angular.module('syncthing.core')
if ($scope.currentFolder.path.length > 1 && $scope.currentFolder.path.slice(-1) === $scope.system.pathSeparator) {
$scope.currentFolder.path = $scope.currentFolder.path.slice(0, -1);
}
// Cache complete device objects indexed by ID for lookups
var devMap = deviceMap($scope.devices)
$scope.currentFolder.sharedDevices = [];
$scope.currentFolder.selectedDevices = {};
$scope.currentFolder.devices.forEach(function (n) {
if (n.deviceID !== $scope.myID) {
$scope.currentFolder.sharedDevices.push(devMap[n.deviceID]);
}
$scope.currentFolder.selectedDevices[n.deviceID] = true;
});
$scope.currentFolder.unrelatedDevices = $scope.devices.filter(function (n) {
return n.deviceID !== $scope.myID
&& ! $scope.currentFolder.selectedDevices[n.deviceID]
});
if ($scope.currentFolder.versioning && $scope.currentFolder.versioning.type === "trashcan") {
$scope.currentFolder.trashcanFileVersioning = true;
$scope.currentFolder.fileVersioningSelector = "trashcan";
@@ -1725,17 +1770,17 @@ angular.module('syncthing.core')
$scope.editFolderModal();
};
$scope.selectAllDevices = function () {
var devices = $scope.otherDevices();
$scope.selectAllSharedDevices = function (state) {
var devices = $scope.currentFolder.sharedDevices;
for (var i = 0; i < devices.length; i++) {
$scope.currentFolder.selectedDevices[devices[i].deviceID] = true;
$scope.currentFolder.selectedDevices[devices[i].deviceID] = !!state;
}
};
$scope.deSelectAllDevices = function () {
var devices = $scope.otherDevices();
$scope.selectAllUnrelatedDevices = function (state) {
var devices = $scope.currentFolder.unrelatedDevices;
for (var i = 0; i < devices.length; i++) {
$scope.currentFolder.selectedDevices[devices[i].deviceID] = false;
$scope.currentFolder.selectedDevices[devices[i].deviceID] = !!state;
}
};
@@ -1744,6 +1789,7 @@ angular.module('syncthing.core')
$scope.editingExisting = false;
$scope.currentFolder = angular.copy($scope.folderDefaults);
$scope.currentFolder.id = (data.random.substr(0, 5) + '-' + data.random.substr(5, 5)).toLowerCase();
$scope.currentFolder.unrelatedDevices = $scope.otherDevices();
$('#folder-ignores textarea').val("");
$('#folder-ignores textarea').removeAttr('disabled');
$scope.editFolderModal();
@@ -1759,6 +1805,7 @@ angular.module('syncthing.core')
importFromOtherDevice: true
};
$scope.currentFolder.selectedDevices[device] = true;
$scope.currentFolder.unrelatedDevices = $scope.otherDevices();
$('#folder-ignores textarea').val("");
$('#folder-ignores textarea').removeAttr('disabled');
$scope.editFolderModal();
@@ -1784,7 +1831,9 @@ angular.module('syncthing.core')
});
}
}
delete folderCfg.sharedDevices;
delete folderCfg.selectedDevices;
delete folderCfg.unrelatedDevices;
if (folderCfg.fileVersioningSelector === "trashcan") {
folderCfg.versioning = {
@@ -2007,6 +2056,9 @@ angular.module('syncthing.core')
value.modTime = new Date(value.modTime);
value.versionTime = new Date(value.versionTime);
});
values.sort(function (a, b) {
return b.versionTime - a.versionTime;
});
});
if (closed) return;
$scope.restoreVersions.versions = data;

View File

@@ -0,0 +1,18 @@
<modal id="upgrade" status="warning" icon="fas fa-arrow-circle-up" heading="{{'Upgrade' | translate}}" large="no" closeable="yes">
<div class="modal-body">
<p>
<span translate>Are you sure you want to upgrade?</span>
</p>
<p>
<a ng-href="https://github.com/syncthing/syncthing/releases/tag/{{upgradeInfo.latest}}" target="_blank" translate>Release Notes</a>
</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary btn-sm" ng-click="upgrade()">
<span class="fas fa-check"></span>&nbsp;<span translate>Upgrade</span>
</button>
<button type="button" class="btn btn-default btn-sm" data-dismiss="modal">
<span class="fas fa-times"></span>&nbsp;<span translate>Close</span>
</button>
</div>
</modal>

View File

@@ -15,10 +15,10 @@
<datalist id="discovery-list">
<option ng-repeat="id in discovery" value="{{id}}" />
</datalist>
<p class="help-block" ng-if="discovery">
<p class="help-block" ng-if="discovery && discovery.length !== 0">
<span translate>You can also select one of these nearby devices:</span>
<ul>
<li ng-repeat="id in discovery"><a ng-click="currentDevice.deviceID = id">{{id}}</a></li>
<li ng-repeat="id in discovery"><a href="#" ng-click="currentDevice.deviceID = id">{{id}}</a></li>
</ul>
</p>
<p class="help-block">

View File

@@ -44,15 +44,35 @@
</div>
</div>
<div id="folder-sharing" class="tab-pane">
<div class="form-group">
<label translate for="devices">Share With Devices</label>
<div class="form-group" ng-if="currentFolder.sharedDevices.length">
<label translate>Currently Shared With Devices</label>
<p class="help-block">
<span translate>Select the devices to share this folder with.</span>&emsp;
<small><a href="#" ng-click="selectAllDevices()" translate>Select All</a>&emsp;
<a href="#" ng-click="deSelectAllDevices()" translate>Deselect All</a></small>
<span translate>Deselect devices to stop sharing this folder with.</span>&emsp;
<small><a href="#" ng-click="selectAllSharedDevices(true)" translate>Select All</a>&emsp;
<a href="#" ng-click="selectAllSharedDevices(false)" translate>Deselect All</a></small>
</p>
<div class="row">
<div class="col-md-4" ng-repeat="device in otherDevices()">
<div class="col-md-4" ng-repeat="device in currentFolder.sharedDevices">
<div class="checkbox">
<label>
<input type="checkbox" ng-model="currentFolder.selectedDevices[device.deviceID]" /> {{deviceName(device)}}
</label>
</div>
</div>
</div>
</div>
<div class="form-group" ng-if="currentFolder.unrelatedDevices.length || otherDevices().length <= 0">
<label translate>Unshared Devices</label>
<p class="help-block" ng-if="otherDevices().length > 0">
<span translate>Select additional devices to share this folder with.</span>&emsp;
<small><a href="#" ng-click="selectAllUnrelatedDevices(true)" translate>Select All</a>&emsp;
<a href="#" ng-click="selectAllUnrelatedDevices(false)" translate>Deselect All</a></small>
</p>
<p class="help-block" ng-if="otherDevices().length <= 0">
<span translate>There are no devices to share this folder with.</span>
</p>
<div class="row">
<div class="col-md-4" ng-repeat="device in currentFolder.unrelatedDevices">
<div class="checkbox">
<label>
<input type="checkbox" ng-model="currentFolder.selectedDevices[device.deviceID]" /> {{deviceName(device)}}

View File

@@ -0,0 +1,35 @@
/*
// Copyright (C) 2016 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
*/
.panel-progress {
background: #3498db;
}
.identicon rect {
fill: #333;
}
.panel-warning .identicon rect {
fill: #fff;
}
.li-column {
background-color: rgb(236, 240, 241);
border-radius: 3px;
}
.panel-heading:hover, .panel-heading:focus {
text-decoration: none;
}
.fancytree-ext-filter-hide tr.fancytree-submatch span.fancytree-title,
.fancytree-ext-filter-hide span.fancytree-node.fancytree-submatch span.fancytree-title {
color: black !important;
font-weight: lighter !important;
}

View File

@@ -8,8 +8,11 @@ package api
import (
"bytes"
"context"
"crypto/tls"
"crypto/x509"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
@@ -56,10 +59,11 @@ import (
var bcryptExpr = regexp.MustCompile(`^\$2[aby]\$\d+\$.{50,}`)
const (
DefaultEventMask = events.AllEvents &^ events.LocalChangeDetected &^ events.RemoteChangeDetected
DiskEventMask = events.LocalChangeDetected | events.RemoteChangeDetected
EventSubBufferSize = 1000
defaultEventTimeout = time.Minute
DefaultEventMask = events.AllEvents &^ events.LocalChangeDetected &^ events.RemoteChangeDetected
DiskEventMask = events.LocalChangeDetected | events.RemoteChangeDetected
EventSubBufferSize = 1000
defaultEventTimeout = time.Minute
httpsCertLifetimeDays = 820
)
type service struct {
@@ -85,6 +89,7 @@ type service struct {
started chan string // signals startup complete by sending the listener address, for testing only
startedOnce chan struct{} // the service has started successfully at least once
startupErr error
listenerAddr net.Addr
guiErrors logger.Recorder
systemLog logger.Recorder
@@ -132,7 +137,7 @@ func New(id protocol.DeviceID, cfg config.Wrapper, assetDir, tlsDefaultCommonNam
configChanged: make(chan struct{}),
startedOnce: make(chan struct{}),
}
s.Service = util.AsService(s.serve)
s.Service = util.AsService(s.serve, s.String())
return s
}
@@ -145,6 +150,12 @@ func (s *service) getListener(guiCfg config.GUIConfiguration) (net.Listener, err
httpsCertFile := locations.Get(locations.HTTPSCertFile)
httpsKeyFile := locations.Get(locations.HTTPSKeyFile)
cert, err := tls.LoadX509KeyPair(httpsCertFile, httpsKeyFile)
// If the certificate has expired or will expire in the next month, fail
// it and generate a new one.
if err == nil {
err = checkExpiry(cert)
}
if err != nil {
l.Infoln("Loading HTTPS certificate:", err)
l.Infoln("Creating new HTTPS certificate")
@@ -157,7 +168,7 @@ func (s *service) getListener(guiCfg config.GUIConfiguration) (net.Listener, err
name = s.tlsDefaultCommonName
}
cert, err = tlsutil.NewCertificate(httpsCertFile, httpsKeyFile, name)
cert, err = tlsutil.NewCertificate(httpsCertFile, httpsKeyFile, name, httpsCertLifetimeDays)
}
if err != nil {
return nil, err
@@ -197,7 +208,7 @@ func sendJSON(w http.ResponseWriter, jsonObject interface{}) {
fmt.Fprintf(w, "%s\n", bs)
}
func (s *service) serve(stop chan struct{}) {
func (s *service) serve(ctx context.Context) {
listener, err := s.getListener(s.cfg.GUI())
if err != nil {
select {
@@ -222,6 +233,7 @@ func (s *service) serve(stop chan struct{}) {
return
}
s.listenerAddr = listener.Addr()
defer listener.Close()
s.cfg.Subscribe(s)
@@ -370,7 +382,7 @@ func (s *service) serve(stop chan struct{}) {
// Wait for stop, restart or error signals
select {
case <-stop:
case <-ctx.Done():
// Shutting down permanently
l.Debugln("shutting down (stop)")
case <-s.configChanged:
@@ -757,11 +769,21 @@ func (s *service) getSystemConnections(w http.ResponseWriter, r *http.Request) {
}
func (s *service) getDeviceStats(w http.ResponseWriter, r *http.Request) {
sendJSON(w, s.model.DeviceStatistics())
stats, err := s.model.DeviceStatistics()
if err != nil {
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
sendJSON(w, stats)
}
func (s *service) getFolderStats(w http.ResponseWriter, r *http.Request) {
sendJSON(w, s.model.FolderStatistics())
stats, err := s.model.FolderStatistics()
if err != nil {
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
sendJSON(w, stats)
}
func (s *service) getDBFile(w http.ResponseWriter, r *http.Request) {
@@ -913,7 +935,7 @@ func (s *service) getSystemStatus(w http.ResponseWriter, r *http.Request) {
res["uptime"] = s.urService.UptimeS()
res["startTime"] = ur.StartTime
res["guiAddressOverridden"] = s.cfg.GUI().IsOverridden()
res["guiAddressUsed"] = s.cfg.GUI().Address()
res["guiAddressUsed"] = s.listenerAddr.String()
sendJSON(w, res)
}
@@ -1571,10 +1593,10 @@ func (s *service) getHeapProf(w http.ResponseWriter, r *http.Request) {
pprof.WriteHeapProfile(w)
}
func toJsonFileInfoSlice(fs []db.FileInfoTruncated) []jsonDBFileInfo {
res := make([]jsonDBFileInfo, len(fs))
func toJsonFileInfoSlice(fs []db.FileInfoTruncated) []jsonFileInfoTrunc {
res := make([]jsonFileInfoTrunc, len(fs))
for i, f := range fs {
res[i] = jsonDBFileInfo(f)
res[i] = jsonFileInfoTrunc(f)
}
return res
}
@@ -1584,45 +1606,39 @@ func toJsonFileInfoSlice(fs []db.FileInfoTruncated) []jsonDBFileInfo {
type jsonFileInfo protocol.FileInfo
func (f jsonFileInfo) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]interface{}{
"name": f.Name,
"type": f.Type,
"size": f.Size,
"permissions": fmt.Sprintf("%#o", f.Permissions),
"deleted": f.Deleted,
"invalid": protocol.FileInfo(f).IsInvalid(),
"ignored": protocol.FileInfo(f).IsIgnored(),
"mustRescan": protocol.FileInfo(f).MustRescan(),
"noPermissions": f.NoPermissions,
"modified": protocol.FileInfo(f).ModTime(),
"modifiedBy": f.ModifiedBy.String(),
"sequence": f.Sequence,
"numBlocks": len(f.Blocks),
"version": jsonVersionVector(f.Version),
"localFlags": f.LocalFlags,
})
m := fileIntfJSONMap(protocol.FileInfo(f))
m["numBlocks"] = len(f.Blocks)
return json.Marshal(m)
}
type jsonDBFileInfo db.FileInfoTruncated
type jsonFileInfoTrunc db.FileInfoTruncated
func (f jsonDBFileInfo) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]interface{}{
"name": f.Name,
"type": f.Type.String(),
"size": f.Size,
"permissions": fmt.Sprintf("%#o", f.Permissions),
"deleted": f.Deleted,
"invalid": db.FileInfoTruncated(f).IsInvalid(),
"ignored": db.FileInfoTruncated(f).IsIgnored(),
"mustRescan": db.FileInfoTruncated(f).MustRescan(),
"noPermissions": f.NoPermissions,
"modified": db.FileInfoTruncated(f).ModTime(),
"modifiedBy": f.ModifiedBy.String(),
"sequence": f.Sequence,
"numBlocks": nil, // explicitly unknown
"version": jsonVersionVector(f.Version),
"localFlags": f.LocalFlags,
})
func (f jsonFileInfoTrunc) MarshalJSON() ([]byte, error) {
m := fileIntfJSONMap(db.FileInfoTruncated(f))
m["numBlocks"] = nil // explicitly unknown
return json.Marshal(m)
}
func fileIntfJSONMap(f db.FileIntf) map[string]interface{} {
out := map[string]interface{}{
"name": f.FileName(),
"type": f.FileType().String(),
"size": f.FileSize(),
"deleted": f.IsDeleted(),
"invalid": f.IsInvalid(),
"ignored": f.IsIgnored(),
"mustRescan": f.MustRescan(),
"noPermissions": !f.HasPermissionBits(),
"modified": f.ModTime(),
"modifiedBy": f.FileModifiedBy().String(),
"sequence": f.SequenceNo(),
"version": jsonVersionVector(f.FileVersion()),
"localFlags": f.FileLocalFlags(),
}
if f.HasPermissionBits() {
out["permissions"] = fmt.Sprintf("%#o", f.FilePermissions())
}
return out
}
type jsonVersionVector protocol.Vector
@@ -1676,3 +1692,45 @@ func addressIsLocalhost(addr string) bool {
return ip.IsLoopback()
}
}
func checkExpiry(cert tls.Certificate) error {
leaf := cert.Leaf
if leaf == nil {
// Leaf can be nil or not, depending on how parsed the certificate
// was when we got it.
if len(cert.Certificate) < 1 {
// can't happen
return errors.New("no certificate in certificate")
}
var err error
leaf, err = x509.ParseCertificate(cert.Certificate[0])
if err != nil {
return err
}
}
if leaf.Subject.String() != leaf.Issuer.String() ||
len(leaf.DNSNames) != 0 || len(leaf.IPAddresses) != 0 {
// The certificate is not self signed, or has DNS/IP attributes we don't
// add, so we leave it alone.
return nil
}
if leaf.NotAfter.Before(time.Now()) {
return errors.New("certificate has expired")
}
if leaf.NotAfter.Before(time.Now().Add(30 * 24 * time.Hour)) {
return errors.New("certificate will soon expire")
}
// On macOS, check for certificates issued on or after July 1st, 2019,
// with a longer validity time than 825 days.
cutoff := time.Date(2019, 7, 1, 0, 0, 0, 0, time.UTC)
if runtime.GOOS == "darwin" &&
leaf.NotBefore.After(cutoff) &&
leaf.NotAfter.Sub(leaf.NotBefore) > 825*24*time.Hour {
return errors.New("certificate incompatible with macOS 10.15 (Catalina)")
}
return nil
}

View File

@@ -23,21 +23,25 @@ import (
"github.com/syncthing/syncthing/lib/sync"
)
const themePrefix = "theme-assets/"
type staticsServer struct {
assetDir string
assets map[string][]byte
availableThemes []string
mut sync.RWMutex
theme string
mut sync.RWMutex
theme string
lastThemeChange time.Time
}
func newStaticsServer(theme, assetDir string) *staticsServer {
s := &staticsServer{
assetDir: assetDir,
assets: auto.Assets(),
mut: sync.NewRWMutex(),
theme: theme,
assetDir: assetDir,
assets: auto.Assets(),
mut: sync.NewRWMutex(),
theme: theme,
lastThemeChange: time.Now().UTC(),
}
seen := make(map[string]struct{})
@@ -86,8 +90,23 @@ func (s *staticsServer) serveAsset(w http.ResponseWriter, r *http.Request) {
s.mut.RLock()
theme := s.theme
modificationTime := s.lastThemeChange
s.mut.RUnlock()
// If path starts with special prefix, get theme and file from path
if strings.HasPrefix(file, themePrefix) {
path := file[len(themePrefix):]
i := strings.IndexRune(path, '/')
if i == -1 {
http.NotFound(w, r)
return
}
theme = path[:i]
file = path[i+1:]
}
// Check for an override for the current theme.
if s.assetDir != "" {
p := filepath.Join(s.assetDir, theme, filepath.FromSlash(file))
@@ -125,14 +144,12 @@ func (s *staticsServer) serveAsset(w http.ResponseWriter, r *http.Request) {
}
}
etag := fmt.Sprintf("%d", auto.Generated)
modified := time.Unix(auto.Generated, 0).UTC()
w.Header().Set("Last-Modified", modified.Format(http.TimeFormat))
etag := fmt.Sprintf("%d", modificationTime.Unix())
w.Header().Set("Last-Modified", modificationTime.Format(http.TimeFormat))
w.Header().Set("Etag", etag)
if t, err := time.Parse(http.TimeFormat, r.Header.Get("If-Modified-Since")); err == nil {
if modified.Equal(t) || modified.Before(t) {
if modificationTime.Equal(t) || modificationTime.Before(t) {
w.WriteHeader(http.StatusNotModified)
return
}
@@ -199,6 +216,7 @@ func (s *staticsServer) mimeTypeForFile(file string) string {
func (s *staticsServer) setTheme(theme string) {
s.mut.Lock()
s.theme = theme
s.lastThemeChange = time.Now().UTC()
s.mut.Unlock()
}

View File

@@ -32,6 +32,7 @@ import (
"github.com/syncthing/syncthing/lib/model"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/sync"
"github.com/syncthing/syncthing/lib/tlsutil"
"github.com/syncthing/syncthing/lib/ur"
"github.com/thejerf/suture"
)
@@ -565,7 +566,7 @@ func TestCSRFRequired(t *testing.T) {
}
cli := &http.Client{
Timeout: time.Second,
Timeout: time.Minute,
}
// Getting the base URL (i.e. "/") should succeed.
@@ -1119,6 +1120,44 @@ func TestPrefixMatch(t *testing.T) {
}
}
func TestCheckExpiry(t *testing.T) {
dir, err := ioutil.TempDir("", "syncthing-test")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
// Self signed certificates expiring in less than a month are errored so we
// can regenerate in time.
crt, err := tlsutil.NewCertificate(filepath.Join(dir, "crt"), filepath.Join(dir, "key"), "foo.example.com", 29)
if err != nil {
t.Fatal(err)
}
if err := checkExpiry(crt); err == nil {
t.Error("expected expiry error")
}
// Certificates with at least 31 days of life left are fine.
crt, err = tlsutil.NewCertificate(filepath.Join(dir, "crt"), filepath.Join(dir, "key"), "foo.example.com", 31)
if err != nil {
t.Fatal(err)
}
if err := checkExpiry(crt); err != nil {
t.Error("expected no error:", err)
}
if runtime.GOOS == "darwin" {
// Certificates with too long an expiry time are not allowed on macOS
crt, err = tlsutil.NewCertificate(filepath.Join(dir, "crt"), filepath.Join(dir, "key"), "foo.example.com", 1000)
if err != nil {
t.Fatal(err)
}
if err := checkExpiry(crt); err == nil {
t.Error("expected expiry error")
}
}
}
func equalStrings(a, b []string) bool {
if len(a) != len(b) {
return false

View File

@@ -7,9 +7,6 @@
package api
import (
"os"
"strings"
"github.com/syncthing/syncthing/lib/logger"
)
@@ -24,5 +21,7 @@ func shouldDebugHTTP() bool {
func init() {
// The debug facility was originally named "http", changed in:
// https://github.com/syncthing/syncthing/pull/5548
l.SetDebug("api", strings.Contains(os.Getenv("STTRACE"), "api") || strings.Contains(os.Getenv("STTRACE"), "http") || os.Getenv("STTRACE") == "all")
if l.IsTraced("http") {
l.SetDebug("api", true)
}
}

View File

@@ -10,7 +10,6 @@ import (
"net"
"time"
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/connections"
"github.com/syncthing/syncthing/lib/db"
"github.com/syncthing/syncthing/lib/model"
@@ -49,12 +48,12 @@ func (m *mockedModel) ConnectionStats() map[string]interface{} {
return nil
}
func (m *mockedModel) DeviceStatistics() map[string]stats.DeviceStatistics {
return nil
func (m *mockedModel) DeviceStatistics() (map[string]stats.DeviceStatistics, error) {
return nil, nil
}
func (m *mockedModel) FolderStatistics() map[string]stats.FolderStatistics {
return nil
func (m *mockedModel) FolderStatistics() (map[string]stats.FolderStatistics, error) {
return nil, nil
}
func (m *mockedModel) CurrentFolderFile(folder string, file string) (protocol.FileInfo, bool) {
@@ -153,21 +152,29 @@ func (m *mockedModel) LocalChangedFiles(folder string, page, perpage int) []db.F
return nil
}
func (m *mockedModel) Serve() {}
func (m *mockedModel) Stop() {}
func (m *mockedModel) Index(deviceID protocol.DeviceID, folder string, files []protocol.FileInfo) {}
func (m *mockedModel) IndexUpdate(deviceID protocol.DeviceID, folder string, files []protocol.FileInfo) {
func (m *mockedModel) Serve() {}
func (m *mockedModel) Stop() {}
func (m *mockedModel) Index(deviceID protocol.DeviceID, folder string, files []protocol.FileInfo) error {
return nil
}
func (m *mockedModel) IndexUpdate(deviceID protocol.DeviceID, folder string, files []protocol.FileInfo) error {
return nil
}
func (m *mockedModel) Request(deviceID protocol.DeviceID, folder, name string, size int32, offset int64, hash []byte, weakHash uint32, fromTemporary bool) (protocol.RequestResponse, error) {
return nil, nil
}
func (m *mockedModel) ClusterConfig(deviceID protocol.DeviceID, config protocol.ClusterConfig) {}
func (m *mockedModel) ClusterConfig(deviceID protocol.DeviceID, config protocol.ClusterConfig) error {
return nil
}
func (m *mockedModel) Closed(conn protocol.Connection, err error) {}
func (m *mockedModel) DownloadProgress(deviceID protocol.DeviceID, folder string, updates []protocol.FileDownloadProgressUpdate) {
func (m *mockedModel) DownloadProgress(deviceID protocol.DeviceID, folder string, updates []protocol.FileDownloadProgressUpdate) error {
return nil
}
func (m *mockedModel) AddConnection(conn connections.Connection, hello protocol.HelloResult) {}
@@ -180,10 +187,4 @@ func (m *mockedModel) GetHello(protocol.DeviceID) protocol.HelloIntf {
return nil
}
func (m *mockedModel) AddFolder(cfg config.FolderConfiguration) {}
func (m *mockedModel) RestartFolder(from, to config.FolderConfiguration) {}
func (m *mockedModel) StartFolder(folder string) {}
func (m *mockedModel) StartDeadlockDetector(timeout time.Duration) {}

View File

@@ -7,6 +7,7 @@
package beacon
import (
"context"
"fmt"
"net"
"time"
@@ -63,23 +64,23 @@ func newCast(name string) *cast {
}
}
func (c *cast) addReader(svc func(chan struct{}) error) {
func (c *cast) addReader(svc func(context.Context) error) {
c.reader = c.createService(svc, "reader")
c.Add(c.reader)
}
func (c *cast) addWriter(svc func(stop chan struct{}) error) {
func (c *cast) addWriter(svc func(ctx context.Context) error) {
c.writer = c.createService(svc, "writer")
c.Add(c.writer)
}
func (c *cast) createService(svc func(chan struct{}) error, suffix string) util.ServiceWithError {
return util.AsServiceWithError(func(stop chan struct{}) error {
func (c *cast) createService(svc func(context.Context) error, suffix string) util.ServiceWithError {
return util.AsServiceWithError(func(ctx context.Context) error {
l.Debugln("Starting", c.name, suffix)
err := svc(stop)
err := svc(ctx)
l.Debugf("Stopped %v %v: %v", c.name, suffix, err)
return err
})
}, fmt.Sprintf("%s/%s", c, suffix))
}
func (c *cast) Stop() {

View File

@@ -7,34 +7,32 @@
package beacon
import (
"context"
"net"
"time"
)
func NewBroadcast(port int) Interface {
c := newCast("broadcastBeacon")
c.addReader(func(stop chan struct{}) error {
return readBroadcasts(c.outbox, port, stop)
c.addReader(func(ctx context.Context) error {
return readBroadcasts(ctx, c.outbox, port)
})
c.addWriter(func(stop chan struct{}) error {
return writeBroadcasts(c.inbox, port, stop)
c.addWriter(func(ctx context.Context) error {
return writeBroadcasts(ctx, c.inbox, port)
})
return c
}
func writeBroadcasts(inbox <-chan []byte, port int, stop chan struct{}) error {
func writeBroadcasts(ctx context.Context, inbox <-chan []byte, port int) error {
conn, err := net.ListenUDP("udp4", nil)
if err != nil {
l.Debugln(err)
return err
}
done := make(chan struct{})
defer close(done)
doneCtx, cancel := context.WithCancel(ctx)
defer cancel()
go func() {
select {
case <-stop:
case <-done:
}
<-doneCtx.Done()
conn.Close()
}()
@@ -42,7 +40,7 @@ func writeBroadcasts(inbox <-chan []byte, port int, stop chan struct{}) error {
var bs []byte
select {
case bs = <-inbox:
case <-stop:
case <-doneCtx.Done():
return nil
}
@@ -99,19 +97,17 @@ func writeBroadcasts(inbox <-chan []byte, port int, stop chan struct{}) error {
}
}
func readBroadcasts(outbox chan<- recv, port int, stop chan struct{}) error {
func readBroadcasts(ctx context.Context, outbox chan<- recv, port int) error {
conn, err := net.ListenUDP("udp4", &net.UDPAddr{Port: port})
if err != nil {
l.Debugln(err)
return err
}
done := make(chan struct{})
defer close(done)
doneCtx, cancel := context.WithCancel(ctx)
defer cancel()
go func() {
select {
case <-stop:
case <-done:
}
<-doneCtx.Done()
conn.Close()
}()
@@ -129,7 +125,7 @@ func readBroadcasts(outbox chan<- recv, port int, stop chan struct{}) error {
copy(c, bs)
select {
case outbox <- recv{c, addr}:
case <-stop:
case <-doneCtx.Done():
return nil
default:
l.Debugln("dropping message")

View File

@@ -7,16 +7,9 @@
package beacon
import (
"os"
"strings"
"github.com/syncthing/syncthing/lib/logger"
)
var (
l = logger.DefaultLogger.NewFacility("beacon", "Multicast and broadcast discovery")
)
func init() {
l.SetDebug("beacon", strings.Contains(os.Getenv("STTRACE"), "beacon") || os.Getenv("STTRACE") == "all")
}

View File

@@ -7,6 +7,7 @@
package beacon
import (
"context"
"errors"
"net"
"time"
@@ -16,16 +17,16 @@ import (
func NewMulticast(addr string) Interface {
c := newCast("multicastBeacon")
c.addReader(func(stop chan struct{}) error {
return readMulticasts(c.outbox, addr, stop)
c.addReader(func(ctx context.Context) error {
return readMulticasts(ctx, c.outbox, addr)
})
c.addWriter(func(stop chan struct{}) error {
return writeMulticasts(c.inbox, addr, stop)
c.addWriter(func(ctx context.Context) error {
return writeMulticasts(ctx, c.inbox, addr)
})
return c
}
func writeMulticasts(inbox <-chan []byte, addr string, stop chan struct{}) error {
func writeMulticasts(ctx context.Context, inbox <-chan []byte, addr string) error {
gaddr, err := net.ResolveUDPAddr("udp6", addr)
if err != nil {
l.Debugln(err)
@@ -37,13 +38,10 @@ func writeMulticasts(inbox <-chan []byte, addr string, stop chan struct{}) error
l.Debugln(err)
return err
}
done := make(chan struct{})
defer close(done)
doneCtx, cancel := context.WithCancel(ctx)
defer cancel()
go func() {
select {
case <-stop:
case <-done:
}
<-doneCtx.Done()
conn.Close()
}()
@@ -57,7 +55,7 @@ func writeMulticasts(inbox <-chan []byte, addr string, stop chan struct{}) error
var bs []byte
select {
case bs = <-inbox:
case <-stop:
case <-doneCtx.Done():
return nil
}
@@ -84,7 +82,7 @@ func writeMulticasts(inbox <-chan []byte, addr string, stop chan struct{}) error
success++
select {
case <-stop:
case <-doneCtx.Done():
return nil
default:
}
@@ -96,7 +94,7 @@ func writeMulticasts(inbox <-chan []byte, addr string, stop chan struct{}) error
}
}
func readMulticasts(outbox chan<- recv, addr string, stop chan struct{}) error {
func readMulticasts(ctx context.Context, outbox chan<- recv, addr string) error {
gaddr, err := net.ResolveUDPAddr("udp6", addr)
if err != nil {
l.Debugln(err)
@@ -108,13 +106,10 @@ func readMulticasts(outbox chan<- recv, addr string, stop chan struct{}) error {
l.Debugln(err)
return err
}
done := make(chan struct{})
defer close(done)
doneCtx, cancel := context.WithCancel(ctx)
defer cancel()
go func() {
select {
case <-stop:
case <-done:
}
<-doneCtx.Done()
conn.Close()
}()
@@ -144,7 +139,7 @@ func readMulticasts(outbox chan<- recv, addr string, stop chan struct{}) error {
bs := make([]byte, 65536)
for {
select {
case <-stop:
case <-doneCtx.Done():
return nil
default:
}

View File

@@ -18,10 +18,11 @@ import (
var (
// Injected by build script
Program = "syncthing"
Version = "unknown-dev"
Host = "unknown" // Set by build script
User = "unknown" // Set by build script
Stamp = "0" // Set by build script
Host = "unknown"
User = "unknown"
Stamp = "0"
// Static
Codename = "Fermium Flea"
@@ -73,7 +74,7 @@ func setBuildData() {
Date = time.Unix(int64(stamp), 0)
date := Date.UTC().Format("2006-01-02 15:04:05 MST")
LongVersion = fmt.Sprintf(`syncthing %s "%s" (%s %s-%s) %s@%s %s`, Version, Codename, runtime.Version(), runtime.GOOS, runtime.GOARCH, User, Host, date)
LongVersion = fmt.Sprintf(`%s %s "%s" (%s %s-%s) %s@%s %s`, Program, Version, Codename, runtime.Version(), runtime.GOOS, runtime.GOARCH, User, Host, date)
if len(Tags) > 0 {
LongVersion = fmt.Sprintf("%s [%s]", LongVersion, strings.Join(Tags, ", "))

View File

@@ -10,7 +10,6 @@ package config
import (
"encoding/json"
"encoding/xml"
"errors"
"fmt"
"io"
"io/ioutil"
@@ -22,6 +21,8 @@ import (
"strconv"
"strings"
"github.com/pkg/errors"
"github.com/syncthing/syncthing/lib/fs"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/rand"
@@ -87,7 +88,6 @@ var (
"stun.voiparound.com:3478",
"stun.voipbuster.com:3478",
"stun.voipstunt.com:3478",
"stun.voxgratia.org:3478",
"stun.xten.com:3478",
}
)
@@ -121,18 +121,18 @@ func NewWithFreePorts(myID protocol.DeviceID) (Configuration, error) {
port, err := getFreePort("127.0.0.1", DefaultGUIPort)
if err != nil {
return Configuration{}, fmt.Errorf("get free port (GUI): %v", err)
return Configuration{}, errors.Wrap(err, "get free port (GUI)")
}
cfg.GUI.RawAddress = fmt.Sprintf("127.0.0.1:%d", port)
port, err = getFreePort("0.0.0.0", DefaultTCPPort)
if err != nil {
return Configuration{}, fmt.Errorf("get free port (BEP): %v", err)
return Configuration{}, errors.Wrap(err, "get free port (BEP)")
}
if port == DefaultTCPPort {
cfg.Options.ListenAddresses = []string{"default"}
cfg.Options.RawListenAddresses = []string{"default"}
} else {
cfg.Options.ListenAddresses = []string{
cfg.Options.RawListenAddresses = []string{
fmt.Sprintf("tcp://%s", net.JoinHostPort("0.0.0.0", strconv.Itoa(port))),
"dynamic+https://relays.syncthing.net/endpoint",
}
@@ -305,8 +305,8 @@ func (cfg *Configuration) clean() error {
existingFolders[folder.ID] = folder
}
cfg.Options.ListenAddresses = util.UniqueTrimmedStrings(cfg.Options.ListenAddresses)
cfg.Options.GlobalAnnServers = util.UniqueTrimmedStrings(cfg.Options.GlobalAnnServers)
cfg.Options.RawListenAddresses = util.UniqueTrimmedStrings(cfg.Options.RawListenAddresses)
cfg.Options.RawGlobalAnnServers = util.UniqueTrimmedStrings(cfg.Options.RawGlobalAnnServers)
if cfg.Version > 0 && cfg.Version < OldestHandledVersion {
l.Warnf("Configuration version %d is deprecated. Attempting best effort conversion, but please verify manually.", cfg.Version)
@@ -396,7 +396,7 @@ nextPendingDevice:
// Deprecated protocols are removed from the list of listeners and
// device addresses. So far just kcp*.
for _, prefix := range []string{"kcp"} {
cfg.Options.ListenAddresses = filterURLSchemePrefix(cfg.Options.ListenAddresses, prefix)
cfg.Options.RawListenAddresses = filterURLSchemePrefix(cfg.Options.RawListenAddresses, prefix)
for i := range cfg.Devices {
dev := &cfg.Devices[i]
dev.Addresses = filterURLSchemePrefix(dev.Addresses, prefix)

View File

@@ -37,8 +37,8 @@ func init() {
func TestDefaultValues(t *testing.T) {
expected := OptionsConfiguration{
ListenAddresses: []string{"default"},
GlobalAnnServers: []string{"default"},
RawListenAddresses: []string{"default"},
RawGlobalAnnServers: []string{"default"},
GlobalAnnEnabled: true,
LocalAnnEnabled: true,
LocalAnnPort: 21027,
@@ -74,7 +74,7 @@ func TestDefaultValues(t *testing.T) {
CREnabled: true,
StunKeepaliveStartS: 180,
StunKeepaliveMinS: 20,
StunServers: []string{"default"},
RawStunServers: []string{"default"},
}
cfg := New(device1)
@@ -175,16 +175,16 @@ func TestNoListenAddresses(t *testing.T) {
}
expected := []string{""}
actual := cfg.Options().ListenAddresses
actual := cfg.Options().RawListenAddresses
if diff, equal := messagediff.PrettyDiff(expected, actual); !equal {
t.Errorf("Unexpected ListenAddresses. Diff:\n%s", diff)
t.Errorf("Unexpected RawListenAddresses. Diff:\n%s", diff)
}
}
func TestOverriddenValues(t *testing.T) {
expected := OptionsConfiguration{
ListenAddresses: []string{"tcp://:23000"},
GlobalAnnServers: []string{"udp4://syncthing.nym.se:22026"},
RawListenAddresses: []string{"tcp://:23000"},
RawGlobalAnnServers: []string{"udp4://syncthing.nym.se:22026"},
GlobalAnnEnabled: false,
LocalAnnEnabled: false,
LocalAnnPort: 42123,
@@ -222,7 +222,7 @@ func TestOverriddenValues(t *testing.T) {
CREnabled: false,
StunKeepaliveStartS: 9000,
StunKeepaliveMinS: 900,
StunServers: []string{"foo"},
RawStunServers: []string{"foo"},
}
os.Unsetenv("STNOUPGRADE")
@@ -424,20 +424,20 @@ func TestIssue1750(t *testing.T) {
t.Fatal(err)
}
if cfg.Options().ListenAddresses[0] != "tcp://:23000" {
t.Errorf("%q != %q", cfg.Options().ListenAddresses[0], "tcp://:23000")
if cfg.Options().RawListenAddresses[0] != "tcp://:23000" {
t.Errorf("%q != %q", cfg.Options().RawListenAddresses[0], "tcp://:23000")
}
if cfg.Options().ListenAddresses[1] != "tcp://:23001" {
t.Errorf("%q != %q", cfg.Options().ListenAddresses[1], "tcp://:23001")
if cfg.Options().RawListenAddresses[1] != "tcp://:23001" {
t.Errorf("%q != %q", cfg.Options().RawListenAddresses[1], "tcp://:23001")
}
if cfg.Options().GlobalAnnServers[0] != "udp4://syncthing.nym.se:22026" {
t.Errorf("%q != %q", cfg.Options().GlobalAnnServers[0], "udp4://syncthing.nym.se:22026")
if cfg.Options().RawGlobalAnnServers[0] != "udp4://syncthing.nym.se:22026" {
t.Errorf("%q != %q", cfg.Options().RawGlobalAnnServers[0], "udp4://syncthing.nym.se:22026")
}
if cfg.Options().GlobalAnnServers[1] != "udp4://syncthing.nym.se:22027" {
t.Errorf("%q != %q", cfg.Options().GlobalAnnServers[1], "udp4://syncthing.nym.se:22027")
if cfg.Options().RawGlobalAnnServers[1] != "udp4://syncthing.nym.se:22027" {
t.Errorf("%q != %q", cfg.Options().RawGlobalAnnServers[1], "udp4://syncthing.nym.se:22027")
}
}
@@ -553,13 +553,13 @@ func TestNewSaveLoad(t *testing.T) {
func TestPrepare(t *testing.T) {
var cfg Configuration
if cfg.Folders != nil || cfg.Devices != nil || cfg.Options.ListenAddresses != nil {
if cfg.Folders != nil || cfg.Devices != nil || cfg.Options.RawListenAddresses != nil {
t.Error("Expected nil")
}
cfg.prepare(device1)
if cfg.Folders == nil || cfg.Devices == nil || cfg.Options.ListenAddresses == nil {
if cfg.Folders == nil || cfg.Devices == nil || cfg.Options.RawListenAddresses == nil {
t.Error("Unexpected nil")
}
}
@@ -580,7 +580,7 @@ func TestCopy(t *testing.T) {
cfg.Devices[0].Addresses[0] = "wrong"
cfg.Folders[0].Devices[0].DeviceID = protocol.DeviceID{0, 1, 2, 3}
cfg.Options.ListenAddresses[0] = "wrong"
cfg.Options.RawListenAddresses[0] = "wrong"
cfg.GUI.APIKey = "wrong"
bsChanged, err := json.MarshalIndent(cfg, "", " ")
@@ -771,7 +771,7 @@ func TestV14ListenAddressesMigration(t *testing.T) {
cfg := Configuration{
Version: 13,
Options: OptionsConfiguration{
ListenAddresses: tc[0],
RawListenAddresses: tc[0],
DeprecatedRelayServers: tc[1],
},
}
@@ -781,8 +781,8 @@ func TestV14ListenAddressesMigration(t *testing.T) {
}
sort.Strings(tc[2])
if !reflect.DeepEqual(cfg.Options.ListenAddresses, tc[2]) {
t.Errorf("Migration error; actual %#v != expected %#v", cfg.Options.ListenAddresses, tc[2])
if !reflect.DeepEqual(cfg.Options.RawListenAddresses, tc[2]) {
t.Errorf("Migration error; actual %#v != expected %#v", cfg.Options.RawListenAddresses, tc[2])
}
}
}

View File

@@ -7,16 +7,9 @@
package config
import (
"os"
"strings"
"github.com/syncthing/syncthing/lib/logger"
)
var (
l = logger.DefaultLogger.NewFacility("config", "Configuration loading and saving")
)
func init() {
l.SetDebug("config", strings.Contains(os.Getenv("STTRACE"), "config") || os.Getenv("STTRACE") == "all")
}

View File

@@ -18,7 +18,6 @@ import (
"github.com/syncthing/syncthing/lib/fs"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/util"
"github.com/syncthing/syncthing/lib/versioner"
)
var (
@@ -105,18 +104,6 @@ func (f FolderConfiguration) Filesystem() fs.Filesystem {
return f.cachedFilesystem
}
func (f FolderConfiguration) Versioner() versioner.Versioner {
if f.Versioning.Type == "" {
return nil
}
versionerFactory, ok := versioner.Factories[f.Versioning.Type]
if !ok {
panic(fmt.Sprintf("Requested versioning type %q that does not exist", f.Versioning.Type))
}
return versionerFactory(f.ID, f.Filesystem(), f.Versioning.Params)
}
func (f FolderConfiguration) ModTimeWindow() time.Duration {
return f.cachedModTimeWindow
}

View File

@@ -216,12 +216,12 @@ func migrateToConfigV18(cfg *Configuration) {
func migrateToConfigV15(cfg *Configuration) {
// Undo v0.13.0 broken migration
for i, addr := range cfg.Options.GlobalAnnServers {
for i, addr := range cfg.Options.RawGlobalAnnServers {
switch addr {
case "default-v4v2/":
cfg.Options.GlobalAnnServers[i] = "default-v4"
cfg.Options.RawGlobalAnnServers[i] = "default-v4"
case "default-v6v2/":
cfg.Options.GlobalAnnServers[i] = "default-v6"
cfg.Options.RawGlobalAnnServers[i] = "default-v6"
}
}
}
@@ -248,9 +248,9 @@ func migrateToConfigV14(cfg *Configuration) {
hasDefault := false
for _, raddr := range cfg.Options.DeprecatedRelayServers {
if raddr == "dynamic+https://relays.syncthing.net/endpoint" {
for i, addr := range cfg.Options.ListenAddresses {
for i, addr := range cfg.Options.RawListenAddresses {
if addr == "tcp://0.0.0.0:22000" {
cfg.Options.ListenAddresses[i] = "default"
cfg.Options.RawListenAddresses[i] = "default"
hasDefault = true
break
}
@@ -269,16 +269,16 @@ func migrateToConfigV14(cfg *Configuration) {
if addr == "" {
continue
}
cfg.Options.ListenAddresses = append(cfg.Options.ListenAddresses, addr)
cfg.Options.RawListenAddresses = append(cfg.Options.RawListenAddresses, addr)
}
cfg.Options.DeprecatedRelayServers = nil
// For consistency
sort.Strings(cfg.Options.ListenAddresses)
sort.Strings(cfg.Options.RawListenAddresses)
var newAddrs []string
for _, addr := range cfg.Options.GlobalAnnServers {
for _, addr := range cfg.Options.RawGlobalAnnServers {
uri, err := url.Parse(addr)
if err != nil {
// That's odd. Skip the broken address.
@@ -291,7 +291,7 @@ func migrateToConfigV14(cfg *Configuration) {
newAddrs = append(newAddrs, addr)
}
cfg.Options.GlobalAnnServers = newAddrs
cfg.Options.RawGlobalAnnServers = newAddrs
for i, fcfg := range cfg.Folders {
if fcfg.DeprecatedReadOnly {
@@ -315,9 +315,9 @@ func migrateToConfigV13(cfg *Configuration) {
func migrateToConfigV12(cfg *Configuration) {
// Change listen address schema
for i, addr := range cfg.Options.ListenAddresses {
for i, addr := range cfg.Options.RawListenAddresses {
if len(addr) > 0 && !strings.HasPrefix(addr, "tcp://") {
cfg.Options.ListenAddresses[i] = util.Address("tcp", addr)
cfg.Options.RawListenAddresses[i] = util.Address("tcp", addr)
}
}
@@ -332,7 +332,7 @@ func migrateToConfigV12(cfg *Configuration) {
// Use new discovery server
var newDiscoServers []string
var useDefault bool
for _, addr := range cfg.Options.GlobalAnnServers {
for _, addr := range cfg.Options.RawGlobalAnnServers {
if addr == "udp4://announce.syncthing.net:22026" {
useDefault = true
} else if addr == "udp6://announce-v6.syncthing.net:22026" {
@@ -344,7 +344,7 @@ func migrateToConfigV12(cfg *Configuration) {
if useDefault {
newDiscoServers = append(newDiscoServers, "default")
}
cfg.Options.GlobalAnnServers = newDiscoServers
cfg.Options.RawGlobalAnnServers = newDiscoServers
// Use new multicast group
if cfg.Options.LocalAnnMCAddr == "[ff32::5222]:21026" {

View File

@@ -9,12 +9,13 @@ package config
import (
"fmt"
"github.com/syncthing/syncthing/lib/rand"
"github.com/syncthing/syncthing/lib/util"
)
type OptionsConfiguration struct {
ListenAddresses []string `xml:"listenAddress" json:"listenAddresses" default:"default"`
GlobalAnnServers []string `xml:"globalAnnounceServer" json:"globalAnnounceServers" default:"default" restart:"true"`
RawListenAddresses []string `xml:"listenAddress" json:"listenAddresses" default:"default"`
RawGlobalAnnServers []string `xml:"globalAnnounceServer" json:"globalAnnounceServers" default:"default" restart:"true"`
GlobalAnnEnabled bool `xml:"globalAnnounceEnabled" json:"globalAnnounceEnabled" default:"true" restart:"true"`
LocalAnnEnabled bool `xml:"localAnnounceEnabled" json:"localAnnounceEnabled" default:"true" restart:"true"`
LocalAnnPort int `xml:"localAnnouncePort" json:"localAnnouncePort" default:"21027" restart:"true"`
@@ -56,7 +57,7 @@ type OptionsConfiguration struct {
CREnabled bool `xml:"crashReportingEnabled" json:"crashReportingEnabled" default:"true" restart:"true"`
StunKeepaliveStartS int `xml:"stunKeepaliveStartS" json:"stunKeepaliveStartS" default:"180"` // 0 for off
StunKeepaliveMinS int `xml:"stunKeepaliveMinS" json:"stunKeepaliveMinS" default:"20"` // 0 for off
StunServers []string `xml:"stunServer" json:"stunServers" default:"default"`
RawStunServers []string `xml:"stunServer" json:"stunServers" default:"default"`
DatabaseTuning Tuning `xml:"databaseTuning" json:"databaseTuning" restart:"true"`
DeprecatedUPnPEnabled bool `xml:"upnpEnabled,omitempty" json:"-"`
@@ -69,10 +70,10 @@ type OptionsConfiguration struct {
func (opts OptionsConfiguration) Copy() OptionsConfiguration {
optsCopy := opts
optsCopy.ListenAddresses = make([]string, len(opts.ListenAddresses))
copy(optsCopy.ListenAddresses, opts.ListenAddresses)
optsCopy.GlobalAnnServers = make([]string, len(opts.GlobalAnnServers))
copy(optsCopy.GlobalAnnServers, opts.GlobalAnnServers)
optsCopy.RawListenAddresses = make([]string, len(opts.RawListenAddresses))
copy(optsCopy.RawListenAddresses, opts.RawListenAddresses)
optsCopy.RawGlobalAnnServers = make([]string, len(opts.RawGlobalAnnServers))
copy(optsCopy.RawGlobalAnnServers, opts.RawGlobalAnnServers)
optsCopy.AlwaysLocalNets = make([]string, len(opts.AlwaysLocalNets))
copy(optsCopy.AlwaysLocalNets, opts.AlwaysLocalNets)
optsCopy.UnackedNotificationIDs = make([]string, len(opts.UnackedNotificationIDs))
@@ -97,3 +98,57 @@ func (opts OptionsConfiguration) RequiresRestartOnly() OptionsConfiguration {
func (opts OptionsConfiguration) IsStunDisabled() bool {
return opts.StunKeepaliveMinS < 1 || opts.StunKeepaliveStartS < 1 || !opts.NATEnabled
}
func (opts OptionsConfiguration) ListenAddresses() []string {
var addresses []string
for _, addr := range opts.RawListenAddresses {
switch addr {
case "default":
addresses = append(addresses, DefaultListenAddresses...)
default:
addresses = append(addresses, addr)
}
}
return util.UniqueTrimmedStrings(addresses)
}
func (opts OptionsConfiguration) StunServers() []string {
var addresses []string
for _, addr := range opts.RawStunServers {
switch addr {
case "default":
defaultPrimaryAddresses := make([]string, len(DefaultPrimaryStunServers))
copy(defaultPrimaryAddresses, DefaultPrimaryStunServers)
rand.Shuffle(defaultPrimaryAddresses)
addresses = append(addresses, defaultPrimaryAddresses...)
defaultSecondaryAddresses := make([]string, len(DefaultSecondaryStunServers))
copy(defaultSecondaryAddresses, DefaultSecondaryStunServers)
rand.Shuffle(defaultSecondaryAddresses)
addresses = append(addresses, defaultSecondaryAddresses...)
default:
addresses = append(addresses, addr)
}
}
addresses = util.UniqueTrimmedStrings(addresses)
return addresses
}
func (opts OptionsConfiguration) GlobalDiscoveryServers() []string {
var servers []string
for _, srv := range opts.RawGlobalAnnServers {
switch srv {
case "default":
servers = append(servers, DefaultDiscoveryServers...)
case "default-v4":
servers = append(servers, DefaultDiscoveryServersV4...)
case "default-v6":
servers = append(servers, DefaultDiscoveryServersV6...)
default:
servers = append(servers, srv)
}
}
return util.UniqueTrimmedStrings(servers)
}

View File

@@ -10,17 +10,17 @@ import (
"testing"
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/db"
"github.com/syncthing/syncthing/lib/db/backend"
)
func TestTuningMatches(t *testing.T) {
if int(config.TuningAuto) != int(db.TuningAuto) {
if int(config.TuningAuto) != int(backend.TuningAuto) {
t.Error("mismatch for TuningAuto")
}
if int(config.TuningSmall) != int(db.TuningSmall) {
if int(config.TuningSmall) != int(backend.TuningSmall) {
t.Error("mismatch for TuningSmall")
}
if int(config.TuningLarge) != int(db.TuningLarge) {
if int(config.TuningLarge) != int(backend.TuningLarge) {
t.Error("mismatch for TuningLarge")
}
}

View File

@@ -14,9 +14,7 @@ import (
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/osutil"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/rand"
"github.com/syncthing/syncthing/lib/sync"
"github.com/syncthing/syncthing/lib/util"
)
// The Committer interface is implemented by objects that need to know about
@@ -87,10 +85,6 @@ type Wrapper interface {
IgnoredDevice(id protocol.DeviceID) bool
IgnoredFolder(device protocol.DeviceID, folder string) bool
ListenAddresses() []string
GlobalDiscoveryServers() []string
StunServers() []string
Subscribe(c Committer)
Unsubscribe(c Committer)
}
@@ -100,6 +94,7 @@ type wrapper struct {
path string
evLogger events.Logger
waiter Waiter // Latest ongoing config change
deviceMap map[protocol.DeviceID]DeviceConfiguration
folderMap map[string]FolderConfiguration
subs []Committer
@@ -108,30 +103,6 @@ type wrapper struct {
requiresRestart uint32 // an atomic bool
}
func (w *wrapper) StunServers() []string {
var addresses []string
for _, addr := range w.cfg.Options.StunServers {
switch addr {
case "default":
defaultPrimaryAddresses := make([]string, len(DefaultPrimaryStunServers))
copy(defaultPrimaryAddresses, DefaultPrimaryStunServers)
rand.Shuffle(defaultPrimaryAddresses)
addresses = append(addresses, defaultPrimaryAddresses...)
defaultSecondaryAddresses := make([]string, len(DefaultSecondaryStunServers))
copy(defaultSecondaryAddresses, DefaultSecondaryStunServers)
rand.Shuffle(defaultSecondaryAddresses)
addresses = append(addresses, defaultSecondaryAddresses...)
default:
addresses = append(addresses, addr)
}
}
addresses = util.UniqueTrimmedStrings(addresses)
return addresses
}
// Wrap wraps an existing Configuration structure and ties it to a file on
// disk.
func Wrap(path string, cfg Configuration, evLogger events.Logger) Wrapper {
@@ -139,6 +110,7 @@ func Wrap(path string, cfg Configuration, evLogger events.Logger) Wrapper {
cfg: cfg,
path: path,
evLogger: evLogger,
waiter: noopWaiter{}, // Noop until first config change
mut: sync.NewMutex(),
}
return w
@@ -174,7 +146,8 @@ func (w *wrapper) Subscribe(c Committer) {
}
// Unsubscribe de-registers the given handler from any future calls to
// configuration changes
// configuration changes and only returns after a potential ongoing config
// change is done.
func (w *wrapper) Unsubscribe(c Committer) {
w.mut.Lock()
for i := range w.subs {
@@ -185,7 +158,11 @@ func (w *wrapper) Unsubscribe(c Committer) {
break
}
}
waiter := w.waiter
w.mut.Unlock()
// Waiting mustn't be done under lock, as the goroutines in notifyListener
// may dead-lock when trying to access lock on config read operations.
waiter.Wait()
}
// RawCopy returns a copy of the currently wrapped Configuration object.
@@ -221,7 +198,9 @@ func (w *wrapper) replaceLocked(to Configuration) (Waiter, error) {
w.deviceMap = nil
w.folderMap = nil
return w.notifyListeners(from.Copy(), to.Copy()), nil
w.waiter = w.notifyListeners(from.Copy(), to.Copy())
return w.waiter, nil
}
func (w *wrapper) notifyListeners(from, to Configuration) Waiter {
@@ -456,36 +435,6 @@ func (w *wrapper) Save() error {
return nil
}
func (w *wrapper) GlobalDiscoveryServers() []string {
var servers []string
for _, srv := range w.Options().GlobalAnnServers {
switch srv {
case "default":
servers = append(servers, DefaultDiscoveryServers...)
case "default-v4":
servers = append(servers, DefaultDiscoveryServersV4...)
case "default-v6":
servers = append(servers, DefaultDiscoveryServersV6...)
default:
servers = append(servers, srv)
}
}
return util.UniqueTrimmedStrings(servers)
}
func (w *wrapper) ListenAddresses() []string {
var addresses []string
for _, addr := range w.Options().ListenAddresses {
switch addr {
case "default":
addresses = append(addresses, DefaultListenAddresses...)
default:
addresses = append(addresses, addr)
}
}
return util.UniqueTrimmedStrings(addresses)
}
func (w *wrapper) RequiresRestart() bool {
return atomic.LoadUint32(&w.requiresRestart) != 0
}

View File

@@ -7,16 +7,9 @@
package connections
import (
"os"
"strings"
"github.com/syncthing/syncthing/lib/logger"
)
var (
l = logger.DefaultLogger.NewFacility("connections", "Connection handling")
)
func init() {
l.SetDebug("connections", strings.Contains(os.Getenv("STTRACE"), "connections") || os.Getenv("STTRACE") == "all")
}

View File

@@ -11,12 +11,12 @@ package connections
import (
"context"
"crypto/tls"
"fmt"
"net"
"net/url"
"time"
"github.com/lucas-clemente/quic-go"
"github.com/pkg/errors"
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/connections/registry"
@@ -39,11 +39,10 @@ func init() {
}
type quicDialer struct {
cfg config.Wrapper
tlsCfg *tls.Config
commonDialer
}
func (d *quicDialer) Dial(_ protocol.DeviceID, uri *url.URL) (internalConn, error) {
func (d *quicDialer) Dial(ctx context.Context, _ protocol.DeviceID, uri *url.URL) (internalConn, error) {
uri = fixupPort(uri, config.DefaultQUICPort)
addr, err := net.ResolveUDPAddr("udp", uri.Host)
@@ -67,7 +66,7 @@ func (d *quicDialer) Dial(_ protocol.DeviceID, uri *url.URL) (internalConn, erro
}
}
ctx, cancel := context.WithTimeout(context.Background(), quicOperationTimeout)
ctx, cancel := context.WithTimeout(ctx, quicOperationTimeout)
defer cancel()
session, err := quic.DialContext(ctx, conn, addr, uri.Host, d.tlsCfg, quicConfig)
@@ -75,7 +74,7 @@ func (d *quicDialer) Dial(_ protocol.DeviceID, uri *url.URL) (internalConn, erro
if createdConn != nil {
_ = createdConn.Close()
}
return internalConn{}, fmt.Errorf("dial: %v", err)
return internalConn{}, errors.Wrap(err, "dial")
}
stream, err := session.OpenStreamSync(ctx)
@@ -85,26 +84,22 @@ func (d *quicDialer) Dial(_ protocol.DeviceID, uri *url.URL) (internalConn, erro
if createdConn != nil {
_ = createdConn.Close()
}
return internalConn{}, fmt.Errorf("open stream: %v", err)
return internalConn{}, errors.Wrap(err, "open stream")
}
return internalConn{&quicTlsConn{session, stream, createdConn}, connTypeQUICClient, quicPriority}, nil
}
func (d *quicDialer) RedialFrequency() time.Duration {
return time.Duration(d.cfg.Options().ReconnectIntervalS) * time.Second
}
type quicDialerFactory struct {
cfg config.Wrapper
tlsCfg *tls.Config
}
func (quicDialerFactory) New(cfg config.Wrapper, tlsCfg *tls.Config) genericDialer {
return &quicDialer{
cfg: cfg,
tlsCfg: tlsCfg,
}
func (quicDialerFactory) New(opts config.OptionsConfiguration, tlsCfg *tls.Config) genericDialer {
return &quicDialer{commonDialer{
reconnectInterval: time.Duration(opts.ReconnectIntervalS) * time.Second,
tlsCfg: tlsCfg,
}}
}
func (quicDialerFactory) Priority() int {

View File

@@ -16,6 +16,7 @@ import (
"strings"
"sync"
"sync/atomic"
"time"
"github.com/lucas-clemente/quic-go"
@@ -77,13 +78,9 @@ func (t *quicListener) OnExternalAddressChanged(address *stun.Host, via string)
}
}
func (t *quicListener) serve(stop chan struct{}) error {
func (t *quicListener) serve(ctx context.Context) error {
network := strings.Replace(t.uri.Scheme, "quic", "udp", -1)
// Convert the stop channel into a context
ctx, cancel := context.WithCancel(context.Background())
go func() { <-stop; cancel() }()
packetConn, err := net.ListenPacket(network, t.uri.Host)
if err != nil {
l.Infoln("Listen (BEP/quic):", err)
@@ -110,6 +107,9 @@ func (t *quicListener) serve(stop chan struct{}) error {
l.Infof("QUIC listener (%v) starting", packetConn.LocalAddr())
defer l.Infof("QUIC listener (%v) shutting down", packetConn.LocalAddr())
acceptFailures := 0
const maxAcceptFailures = 10
for {
select {
case <-ctx.Done():
@@ -121,9 +121,23 @@ func (t *quicListener) serve(stop chan struct{}) error {
if err == context.Canceled {
return nil
} else if err != nil {
l.Warnln("Listen (BEP/quic): Accepting connection:", err)
l.Infoln("Listen (BEP/quic): Accepting connection:", err)
acceptFailures++
if acceptFailures > maxAcceptFailures {
// Return to restart the listener, because something
// seems permanently damaged.
return err
}
// Slightly increased delay for each failure.
time.Sleep(time.Duration(acceptFailures) * time.Second)
continue
}
acceptFailures = 0
l.Debugln("connect from", session.RemoteAddr())
streamCtx, cancel := context.WithTimeout(ctx, quicOperationTimeout)
@@ -187,7 +201,7 @@ func (f *quicListenerFactory) New(uri *url.URL, cfg config.Wrapper, tlsCfg *tls.
conns: conns,
factory: f,
}
l.ServiceWithError = util.AsServiceWithError(l.serve)
l.ServiceWithError = util.AsServiceWithError(l.serve, l.String())
l.nat.Store(stun.NATUnknown)
return l
}

View File

@@ -7,6 +7,7 @@
package connections
import (
"context"
"crypto/tls"
"net/url"
"time"
@@ -24,17 +25,16 @@ func init() {
}
type relayDialer struct {
cfg config.Wrapper
tlsCfg *tls.Config
commonDialer
}
func (d *relayDialer) Dial(id protocol.DeviceID, uri *url.URL) (internalConn, error) {
inv, err := client.GetInvitationFromRelay(uri, id, d.tlsCfg.Certificates, 10*time.Second)
func (d *relayDialer) Dial(ctx context.Context, id protocol.DeviceID, uri *url.URL) (internalConn, error) {
inv, err := client.GetInvitationFromRelay(ctx, uri, id, d.tlsCfg.Certificates, 10*time.Second)
if err != nil {
return internalConn{}, err
}
conn, err := client.JoinSession(inv)
conn, err := client.JoinSession(ctx, inv)
if err != nil {
return internalConn{}, err
}
@@ -45,7 +45,7 @@ func (d *relayDialer) Dial(id protocol.DeviceID, uri *url.URL) (internalConn, er
return internalConn{}, err
}
err = dialer.SetTrafficClass(conn, d.cfg.Options().TrafficClass)
err = dialer.SetTrafficClass(conn, d.trafficClass)
if err != nil {
l.Debugln("Dial (BEP/relay): setting traffic class:", err)
}
@@ -66,17 +66,14 @@ func (d *relayDialer) Dial(id protocol.DeviceID, uri *url.URL) (internalConn, er
return internalConn{tc, connTypeRelayClient, relayPriority}, nil
}
func (d *relayDialer) RedialFrequency() time.Duration {
return time.Duration(d.cfg.Options().RelayReconnectIntervalM) * time.Minute
}
type relayDialerFactory struct{}
func (relayDialerFactory) New(cfg config.Wrapper, tlsCfg *tls.Config) genericDialer {
return &relayDialer{
cfg: cfg,
tlsCfg: tlsCfg,
}
func (relayDialerFactory) New(opts config.OptionsConfiguration, tlsCfg *tls.Config) genericDialer {
return &relayDialer{commonDialer{
trafficClass: opts.TrafficClass,
reconnectInterval: time.Duration(opts.RelayReconnectIntervalM) * time.Minute,
tlsCfg: tlsCfg,
}}
}
func (relayDialerFactory) Priority() int {

View File

@@ -7,11 +7,14 @@
package connections
import (
"context"
"crypto/tls"
"net/url"
"sync"
"time"
"github.com/pkg/errors"
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/dialer"
"github.com/syncthing/syncthing/lib/nat"
@@ -40,7 +43,7 @@ type relayListener struct {
mut sync.RWMutex
}
func (t *relayListener) serve(stop chan struct{}) error {
func (t *relayListener) serve(ctx context.Context) error {
clnt, err := client.NewClient(t.uri, t.tlsCfg.Certificates, nil, 10*time.Second)
if err != nil {
l.Infoln("Listen (BEP/relay):", err)
@@ -69,9 +72,11 @@ func (t *relayListener) serve(stop chan struct{}) error {
return err
}
conn, err := client.JoinSession(inv)
conn, err := client.JoinSession(ctx, inv)
if err != nil {
l.Infoln("Listen (BEP/relay): joining session:", err)
if errors.Cause(err) != context.Canceled {
l.Infoln("Listen (BEP/relay): joining session:", err)
}
continue
}
@@ -112,7 +117,7 @@ func (t *relayListener) serve(stop chan struct{}) error {
t.notifyAddressesChanged(t)
}
case <-stop:
case <-ctx.Done():
return nil
}
}
@@ -178,7 +183,7 @@ func (f *relayListenerFactory) New(uri *url.URL, cfg config.Wrapper, tlsCfg *tls
conns: conns,
factory: f,
}
t.ServiceWithError = util.AsServiceWithError(t.serve)
t.ServiceWithError = util.AsServiceWithError(t.serve, t.String())
return t
}

View File

@@ -7,8 +7,8 @@
package connections
import (
"context"
"crypto/tls"
"errors"
"fmt"
"net"
"net/url"
@@ -30,6 +30,7 @@ import (
_ "github.com/syncthing/syncthing/lib/pmp"
_ "github.com/syncthing/syncthing/lib/upnp"
"github.com/pkg/errors"
"github.com/thejerf/suture"
"golang.org/x/time/rate"
)
@@ -83,7 +84,7 @@ var tlsCipherSuiteNames = map[uint16]string{
var tlsVersionNames = map[uint16]string{
tls.VersionTLS12: "TLS1.2",
772: "TLS1.3", // tls.VersionTLS13 constant available in Go 1.12+
tls.VersionTLS13: "TLS1.3",
}
// Service listens and dials all configured unconnected devices, via supported
@@ -185,18 +186,24 @@ func NewService(cfg config.Wrapper, myID protocol.DeviceID, mdl Model, tlsCfg *t
// the common handling regardless of whether the connection was
// incoming or outgoing.
service.Add(util.AsService(service.connect))
service.Add(util.AsService(service.handle))
service.Add(util.AsService(service.connect, fmt.Sprintf("%s/connect", service)))
service.Add(util.AsService(service.handle, fmt.Sprintf("%s/handle", service)))
service.Add(service.listenerSupervisor)
return service
}
func (s *service) handle(stop chan struct{}) {
func (s *service) Stop() {
s.cfg.Unsubscribe(s.limiter)
s.cfg.Unsubscribe(s)
s.Supervisor.Stop()
}
func (s *service) handle(ctx context.Context) {
var c internalConn
for {
select {
case <-stop:
case <-ctx.Done():
return
case c = <-s.conns:
}
@@ -324,7 +331,7 @@ func (s *service) handle(stop chan struct{}) {
}
}
func (s *service) connect(stop chan struct{}) {
func (s *service) connect(ctx context.Context) {
nextDial := make(map[string]time.Time)
// Used as delay for the first few connection attempts, increases
@@ -441,7 +448,7 @@ func (s *service) connect(stop chan struct{}) {
continue
}
dialer := dialerFactory.New(s.cfg, s.tlsCfg)
dialer := dialerFactory.New(s.cfg.Options(), s.tlsCfg)
nextDial[nextDialKey] = now.Add(dialer.RedialFrequency())
// For LAN addresses, increase the priority so that we
@@ -462,7 +469,7 @@ func (s *service) connect(stop chan struct{}) {
})
}
conn, ok := s.dialParallel(deviceCfg.DeviceID, dialTargets)
conn, ok := s.dialParallel(ctx, deviceCfg.DeviceID, dialTargets)
if ok {
s.conns <- conn
}
@@ -480,7 +487,7 @@ func (s *service) connect(stop chan struct{}) {
select {
case <-time.After(sleep):
case <-stop:
case <-ctx.Done():
return
}
}
@@ -581,7 +588,7 @@ func (s *service) CommitConfiguration(from, to config.Configuration) bool {
s.listenersMut.Lock()
seen := make(map[string]struct{})
for _, addr := range config.Wrap("", to, s.evLogger).ListenAddresses() {
for _, addr := range to.Options.ListenAddresses() {
if addr == "" {
// We can get an empty address if there is an empty listener
// element in the config, indicating no listeners should be
@@ -700,6 +707,10 @@ func (s *service) ConnectionStatus() map[string]ConnectionStatusEntry {
}
func (s *service) setConnectionStatus(address string, err error) {
if errors.Cause(err) != context.Canceled {
return
}
status := ConnectionStatusEntry{When: time.Now().UTC().Truncate(time.Second)}
if err != nil {
errStr := err.Error()
@@ -827,7 +838,7 @@ func IsAllowedNetwork(host string, allowed []string) bool {
return false
}
func (s *service) dialParallel(deviceID protocol.DeviceID, dialTargets []dialTarget) (internalConn, bool) {
func (s *service) dialParallel(ctx context.Context, deviceID protocol.DeviceID, dialTargets []dialTarget) (internalConn, bool) {
// Group targets into buckets by priority
dialTargetBuckets := make(map[int][]dialTarget, len(dialTargets))
for _, tgt := range dialTargets {
@@ -850,7 +861,7 @@ func (s *service) dialParallel(deviceID protocol.DeviceID, dialTargets []dialTar
for _, tgt := range tgts {
wg.Add(1)
go func(tgt dialTarget) {
conn, err := tgt.Dial()
conn, err := tgt.Dial(ctx)
if err == nil {
// Closes the connection on error
err = s.validateIdentity(conn, deviceID)

View File

@@ -7,6 +7,7 @@
package connections
import (
"context"
"crypto/tls"
"fmt"
"io"
@@ -146,15 +147,25 @@ func (c internalConn) String() string {
}
type dialerFactory interface {
New(config.Wrapper, *tls.Config) genericDialer
New(config.OptionsConfiguration, *tls.Config) genericDialer
Priority() int
AlwaysWAN() bool
Valid(config.Configuration) error
String() string
}
type commonDialer struct {
trafficClass int
reconnectInterval time.Duration
tlsCfg *tls.Config
}
func (d *commonDialer) RedialFrequency() time.Duration {
return d.reconnectInterval
}
type genericDialer interface {
Dial(protocol.DeviceID, *url.URL) (internalConn, error)
Dial(context.Context, protocol.DeviceID, *url.URL) (internalConn, error)
RedialFrequency() time.Duration
}
@@ -213,7 +224,7 @@ type dialTarget struct {
deviceID protocol.DeviceID
}
func (t dialTarget) Dial() (internalConn, error) {
func (t dialTarget) Dial(ctx context.Context) (internalConn, error) {
l.Debugln("dialing", t.deviceID, t.uri, "prio", t.priority)
return t.dialer.Dial(t.deviceID, t.uri)
return t.dialer.Dial(ctx, t.deviceID, t.uri)
}

View File

@@ -7,6 +7,7 @@
package connections
import (
"context"
"crypto/tls"
"net/url"
"time"
@@ -26,14 +27,15 @@ func init() {
}
type tcpDialer struct {
cfg config.Wrapper
tlsCfg *tls.Config
commonDialer
}
func (d *tcpDialer) Dial(_ protocol.DeviceID, uri *url.URL) (internalConn, error) {
func (d *tcpDialer) Dial(ctx context.Context, _ protocol.DeviceID, uri *url.URL) (internalConn, error) {
uri = fixupPort(uri, config.DefaultTCPPort)
conn, err := dialer.DialTimeout(uri.Scheme, uri.Host, 10*time.Second)
timeoutCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
conn, err := dialer.DialContext(timeoutCtx, uri.Scheme, uri.Host)
if err != nil {
return internalConn{}, err
}
@@ -43,7 +45,7 @@ func (d *tcpDialer) Dial(_ protocol.DeviceID, uri *url.URL) (internalConn, error
l.Debugln("Dial (BEP/tcp): setting tcp options:", err)
}
err = dialer.SetTrafficClass(conn, d.cfg.Options().TrafficClass)
err = dialer.SetTrafficClass(conn, d.trafficClass)
if err != nil {
l.Debugln("Dial (BEP/tcp): setting traffic class:", err)
}
@@ -58,17 +60,14 @@ func (d *tcpDialer) Dial(_ protocol.DeviceID, uri *url.URL) (internalConn, error
return internalConn{tc, connTypeTCPClient, tcpPriority}, nil
}
func (d *tcpDialer) RedialFrequency() time.Duration {
return time.Duration(d.cfg.Options().ReconnectIntervalS) * time.Second
}
type tcpDialerFactory struct{}
func (tcpDialerFactory) New(cfg config.Wrapper, tlsCfg *tls.Config) genericDialer {
return &tcpDialer{
cfg: cfg,
tlsCfg: tlsCfg,
}
func (tcpDialerFactory) New(opts config.OptionsConfiguration, tlsCfg *tls.Config) genericDialer {
return &tcpDialer{commonDialer{
trafficClass: opts.TrafficClass,
reconnectInterval: time.Duration(opts.ReconnectIntervalS) * time.Second,
tlsCfg: tlsCfg,
}}
}
func (tcpDialerFactory) Priority() int {

View File

@@ -7,6 +7,7 @@
package connections
import (
"context"
"crypto/tls"
"net"
"net/url"
@@ -42,7 +43,7 @@ type tcpListener struct {
mut sync.RWMutex
}
func (t *tcpListener) serve(stop chan struct{}) error {
func (t *tcpListener) serve(ctx context.Context) error {
tcaddr, err := net.ResolveTCPAddr(t.uri.Scheme, t.uri.Host)
if err != nil {
l.Infoln("Listen (BEP/tcp):", err)
@@ -76,7 +77,7 @@ func (t *tcpListener) serve(stop chan struct{}) error {
listener.SetDeadline(time.Now().Add(time.Second))
conn, err := listener.Accept()
select {
case <-stop:
case <-ctx.Done():
if err == nil {
conn.Close()
}
@@ -183,7 +184,7 @@ func (f *tcpListenerFactory) New(uri *url.URL, cfg config.Wrapper, tlsCfg *tls.C
natService: natService,
factory: f,
}
l.ServiceWithError = util.AsServiceWithError(l.serve)
l.ServiceWithError = util.AsServiceWithError(l.serve, l.String())
return l
}

170
lib/db/backend/backend.go Normal file
View File

@@ -0,0 +1,170 @@
// Copyright (C) 2019 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
package backend
import (
"sync"
)
// The Reader interface specifies the read-only operations available on the
// main database and on read-only transactions (snapshots). Note that when
// called directly on the database handle these operations may take implicit
// transactions and performance may suffer.
type Reader interface {
Get(key []byte) ([]byte, error)
NewPrefixIterator(prefix []byte) (Iterator, error)
NewRangeIterator(first, last []byte) (Iterator, error)
}
// The Writer interface specifies the mutating operations available on the
// main database and on writable transactions. Note that when called
// directly on the database handle these operations may take implicit
// transactions and performance may suffer.
type Writer interface {
Put(key, val []byte) error
Delete(key []byte) error
}
// The ReadTransaction interface specifies the operations on read-only
// transactions. Every ReadTransaction must be released when no longer
// required.
type ReadTransaction interface {
Reader
Release()
}
// The WriteTransaction interface specifies the operations on writable
// transactions. Every WriteTransaction must be either committed or released
// (i.e., discarded) when no longer required. No further operations must be
// performed after release or commit (regardless of whether commit succeeded),
// with one exception -- it's fine to release an already committed or released
// transaction.
//
// A Checkpoint is a potential partial commit of the transaction so far, for
// purposes of saving memory when transactions are in-RAM. Note that
// transactions may be checkpointed *anyway* even if this is not called, due to
// resource constraints, but this gives you a chance to decide when.
type WriteTransaction interface {
ReadTransaction
Writer
Checkpoint() error
Commit() error
}
// The Iterator interface specifies the operations available on iterators
// returned by NewPrefixIterator and NewRangeIterator. The iterator pattern
// is to loop while Next returns true, then check Error after the loop. Next
// will return false when iteration is complete (Error() == nil) or when
// there is an error preventing iteration, which is then returned by
// Error(). For example:
//
// it, err := db.NewPrefixIterator(nil)
// if err != nil {
// // problem preventing iteration
// }
// defer it.Release()
// for it.Next() {
// // ...
// }
// if err := it.Error(); err != nil {
// // there was a database problem while iterating
// }
//
// An iterator must be Released when no longer required. The Error method
// can be called either before or after Release with the same results. If an
// iterator was created in a transaction (whether read-only or write) it
// must be released before the transaction is released (or committed).
type Iterator interface {
Next() bool
Key() []byte
Value() []byte
Error() error
Release()
}
// The Backend interface represents the main database handle. It supports
// both read/write operations and opening read-only or writable
// transactions. Depending on the actual implementation, individual
// read/write operations may be implicitly wrapped in transactions, making
// them perform quite badly when used repeatedly. For bulk operations,
// consider always using a transaction of the appropriate type. The
// transaction isolation level is "read committed" - there are no dirty
// reads.
type Backend interface {
Reader
Writer
NewReadTransaction() (ReadTransaction, error)
NewWriteTransaction() (WriteTransaction, error)
Close() error
}
type Tuning int
const (
// N.b. these constants must match those in lib/config.Tuning!
TuningAuto Tuning = iota
TuningSmall
TuningLarge
)
func Open(path string, tuning Tuning) (Backend, error) {
return OpenLevelDB(path, tuning)
}
func OpenMemory() Backend {
return OpenLevelDBMemory()
}
type errClosed struct{}
func (errClosed) Error() string { return "database is closed" }
type errNotFound struct{}
func (errNotFound) Error() string { return "key not found" }
func IsClosed(err error) bool {
if _, ok := err.(errClosed); ok {
return true
}
if _, ok := err.(*errClosed); ok {
return true
}
return false
}
func IsNotFound(err error) bool {
if _, ok := err.(errNotFound); ok {
return true
}
if _, ok := err.(*errNotFound); ok {
return true
}
return false
}
// releaser manages counting on top of a waitgroup
type releaser struct {
wg *sync.WaitGroup
once *sync.Once
}
func newReleaser(wg *sync.WaitGroup) *releaser {
wg.Add(1)
return &releaser{
wg: wg,
once: new(sync.Once),
}
}
func (r releaser) Release() {
// We use the Once because we may get called multiple times from
// Commit() and deferred Release().
r.once.Do(func() {
r.wg.Done()
})
}

View File

@@ -0,0 +1,53 @@
// Copyright (C) 2019 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
package backend
import "testing"
// testBackendBehavior is the generic test suite that must be fulfilled by
// every backend implementation. It should be called by each implementation
// as (part of) their test suite.
func testBackendBehavior(t *testing.T, open func() Backend) {
t.Run("WriteIsolation", func(t *testing.T) { testWriteIsolation(t, open) })
t.Run("DeleteNonexisten", func(t *testing.T) { testDeleteNonexistent(t, open) })
}
func testWriteIsolation(t *testing.T, open func() Backend) {
// Values written during a transaction should not be read back, our
// updateGlobal depends on this.
db := open()
defer db.Close()
// Sanity check
_ = db.Put([]byte("a"), []byte("a"))
v, _ := db.Get([]byte("a"))
if string(v) != "a" {
t.Fatal("read back should work")
}
// Now in a transaction we should still see the old value
tx, _ := db.NewWriteTransaction()
defer tx.Release()
_ = tx.Put([]byte("a"), []byte("b"))
v, _ = tx.Get([]byte("a"))
if string(v) != "a" {
t.Fatal("read in transaction should read the old value")
}
}
func testDeleteNonexistent(t *testing.T, open func() Backend) {
// Deleting a non-existent key is not an error
db := open()
defer db.Close()
err := db.Delete([]byte("a"))
if err != nil {
t.Error(err)
}
}

15
lib/db/backend/debug.go Normal file
View File

@@ -0,0 +1,15 @@
// Copyright (C) 2019 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
package backend
import (
"github.com/syncthing/syncthing/lib/logger"
)
var (
l = logger.DefaultLogger.NewFacility("backend", "The database backend")
)

View File

@@ -0,0 +1,173 @@
// Copyright (C) 2018 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
package backend
import (
"sync"
"github.com/syndtr/goleveldb/leveldb"
"github.com/syndtr/goleveldb/leveldb/util"
)
const (
// Never flush transactions smaller than this, even on Checkpoint()
dbFlushBatchMin = 1 << MiB
// Once a transaction reaches this size, flush it unconditionally.
dbFlushBatchMax = 128 << MiB
)
// leveldbBackend implements Backend on top of a leveldb
type leveldbBackend struct {
ldb *leveldb.DB
closeWG sync.WaitGroup
}
func (b *leveldbBackend) NewReadTransaction() (ReadTransaction, error) {
return b.newSnapshot()
}
func (b *leveldbBackend) newSnapshot() (leveldbSnapshot, error) {
snap, err := b.ldb.GetSnapshot()
if err != nil {
return leveldbSnapshot{}, wrapLeveldbErr(err)
}
return leveldbSnapshot{
snap: snap,
rel: newReleaser(&b.closeWG),
}, nil
}
func (b *leveldbBackend) NewWriteTransaction() (WriteTransaction, error) {
snap, err := b.newSnapshot()
if err != nil {
return nil, err // already wrapped
}
return &leveldbTransaction{
leveldbSnapshot: snap,
ldb: b.ldb,
batch: new(leveldb.Batch),
rel: newReleaser(&b.closeWG),
}, nil
}
func (b *leveldbBackend) Close() error {
b.closeWG.Wait()
return wrapLeveldbErr(b.ldb.Close())
}
func (b *leveldbBackend) Get(key []byte) ([]byte, error) {
val, err := b.ldb.Get(key, nil)
return val, wrapLeveldbErr(err)
}
func (b *leveldbBackend) NewPrefixIterator(prefix []byte) (Iterator, error) {
return b.ldb.NewIterator(util.BytesPrefix(prefix), nil), nil
}
func (b *leveldbBackend) NewRangeIterator(first, last []byte) (Iterator, error) {
return b.ldb.NewIterator(&util.Range{Start: first, Limit: last}, nil), nil
}
func (b *leveldbBackend) Put(key, val []byte) error {
return wrapLeveldbErr(b.ldb.Put(key, val, nil))
}
func (b *leveldbBackend) Delete(key []byte) error {
return wrapLeveldbErr(b.ldb.Delete(key, nil))
}
// leveldbSnapshot implements backend.ReadTransaction
type leveldbSnapshot struct {
snap *leveldb.Snapshot
rel *releaser
}
func (l leveldbSnapshot) Get(key []byte) ([]byte, error) {
val, err := l.snap.Get(key, nil)
return val, wrapLeveldbErr(err)
}
func (l leveldbSnapshot) NewPrefixIterator(prefix []byte) (Iterator, error) {
return l.snap.NewIterator(util.BytesPrefix(prefix), nil), nil
}
func (l leveldbSnapshot) NewRangeIterator(first, last []byte) (Iterator, error) {
return l.snap.NewIterator(&util.Range{Start: first, Limit: last}, nil), nil
}
func (l leveldbSnapshot) Release() {
l.snap.Release()
l.rel.Release()
}
// leveldbTransaction implements backend.WriteTransaction using a batch (not
// an actual leveldb transaction)
type leveldbTransaction struct {
leveldbSnapshot
ldb *leveldb.DB
batch *leveldb.Batch
rel *releaser
}
func (t *leveldbTransaction) Delete(key []byte) error {
t.batch.Delete(key)
return t.checkFlush(dbFlushBatchMax)
}
func (t *leveldbTransaction) Put(key, val []byte) error {
t.batch.Put(key, val)
return t.checkFlush(dbFlushBatchMax)
}
func (t *leveldbTransaction) Checkpoint() error {
return t.checkFlush(dbFlushBatchMin)
}
func (t *leveldbTransaction) Commit() error {
err := wrapLeveldbErr(t.flush())
t.leveldbSnapshot.Release()
t.rel.Release()
return err
}
func (t *leveldbTransaction) Release() {
t.leveldbSnapshot.Release()
t.rel.Release()
}
// checkFlush flushes and resets the batch if its size exceeds the given size.
func (t *leveldbTransaction) checkFlush(size int) error {
if len(t.batch.Dump()) < size {
return nil
}
return t.flush()
}
func (t *leveldbTransaction) flush() error {
if t.batch.Len() == 0 {
return nil
}
if err := t.ldb.Write(t.batch, nil); err != nil {
return wrapLeveldbErr(err)
}
t.batch.Reset()
return nil
}
// wrapLeveldbErr wraps errors so that the backend package can recognize them
func wrapLeveldbErr(err error) error {
if err == nil {
return nil
}
if err == leveldb.ErrClosed {
return errClosed{}
}
if err == leveldb.ErrNotFound {
return errNotFound{}
}
return err
}

View File

@@ -0,0 +1,226 @@
// Copyright (C) 2018 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
package backend
import (
"fmt"
"os"
"strconv"
"strings"
"github.com/syndtr/goleveldb/leveldb"
"github.com/syndtr/goleveldb/leveldb/errors"
"github.com/syndtr/goleveldb/leveldb/opt"
"github.com/syndtr/goleveldb/leveldb/storage"
"github.com/syndtr/goleveldb/leveldb/util"
)
const (
dbMaxOpenFiles = 100
// A large database is > 200 MiB. It's a mostly arbitrary value, but
// it's also the case that each file is 2 MiB by default and when we
// have dbMaxOpenFiles of them we will need to start thrashing fd:s.
// Switching to large database settings causes larger files to be used
// when compacting, reducing the number.
dbLargeThreshold = dbMaxOpenFiles * (2 << MiB)
KiB = 10
MiB = 20
)
// Open attempts to open the database at the given location, and runs
// recovery on it if opening fails. Worst case, if recovery is not possible,
// the database is erased and created from scratch.
func OpenLevelDB(location string, tuning Tuning) (Backend, error) {
opts := optsFor(location, tuning)
ldb, err := open(location, opts)
if err != nil {
return nil, err
}
return &leveldbBackend{ldb: ldb}, nil
}
// OpenRO attempts to open the database at the given location, read only.
func OpenLevelDBRO(location string) (Backend, error) {
opts := &opt.Options{
OpenFilesCacheCapacity: dbMaxOpenFiles,
ReadOnly: true,
}
ldb, err := open(location, opts)
if err != nil {
return nil, err
}
return &leveldbBackend{ldb: ldb}, nil
}
// OpenMemory returns a new Backend referencing an in-memory database.
func OpenLevelDBMemory() Backend {
ldb, _ := leveldb.Open(storage.NewMemStorage(), nil)
return &leveldbBackend{ldb: ldb}
}
// optsFor returns the database options to use when opening a database with
// the given location and tuning. Settings can be overridden by debug
// environment variables.
func optsFor(location string, tuning Tuning) *opt.Options {
large := false
switch tuning {
case TuningLarge:
large = true
case TuningAuto:
large = dbIsLarge(location)
}
var (
// Set defaults used for small databases.
defaultBlockCacheCapacity = 0 // 0 means let leveldb use default
defaultBlockSize = 0
defaultCompactionTableSize = 0
defaultCompactionTableSizeMultiplier = 0
defaultWriteBuffer = 16 << MiB // increased from leveldb default of 4 MiB
defaultCompactionL0Trigger = opt.DefaultCompactionL0Trigger // explicit because we use it as base for other stuff
)
if large {
// Change the parameters for better throughput at the price of some
// RAM and larger files. This results in larger batches of writes
// and compaction at a lower frequency.
l.Infoln("Using large-database tuning")
defaultBlockCacheCapacity = 64 << MiB
defaultBlockSize = 64 << KiB
defaultCompactionTableSize = 16 << MiB
defaultCompactionTableSizeMultiplier = 20 // 2.0 after division by ten
defaultWriteBuffer = 64 << MiB
defaultCompactionL0Trigger = 8 // number of l0 files
}
opts := &opt.Options{
BlockCacheCapacity: debugEnvValue("BlockCacheCapacity", defaultBlockCacheCapacity),
BlockCacheEvictRemoved: debugEnvValue("BlockCacheEvictRemoved", 0) != 0,
BlockRestartInterval: debugEnvValue("BlockRestartInterval", 0),
BlockSize: debugEnvValue("BlockSize", defaultBlockSize),
CompactionExpandLimitFactor: debugEnvValue("CompactionExpandLimitFactor", 0),
CompactionGPOverlapsFactor: debugEnvValue("CompactionGPOverlapsFactor", 0),
CompactionL0Trigger: debugEnvValue("CompactionL0Trigger", defaultCompactionL0Trigger),
CompactionSourceLimitFactor: debugEnvValue("CompactionSourceLimitFactor", 0),
CompactionTableSize: debugEnvValue("CompactionTableSize", defaultCompactionTableSize),
CompactionTableSizeMultiplier: float64(debugEnvValue("CompactionTableSizeMultiplier", defaultCompactionTableSizeMultiplier)) / 10.0,
CompactionTotalSize: debugEnvValue("CompactionTotalSize", 0),
CompactionTotalSizeMultiplier: float64(debugEnvValue("CompactionTotalSizeMultiplier", 0)) / 10.0,
DisableBufferPool: debugEnvValue("DisableBufferPool", 0) != 0,
DisableBlockCache: debugEnvValue("DisableBlockCache", 0) != 0,
DisableCompactionBackoff: debugEnvValue("DisableCompactionBackoff", 0) != 0,
DisableLargeBatchTransaction: debugEnvValue("DisableLargeBatchTransaction", 0) != 0,
NoSync: debugEnvValue("NoSync", 0) != 0,
NoWriteMerge: debugEnvValue("NoWriteMerge", 0) != 0,
OpenFilesCacheCapacity: debugEnvValue("OpenFilesCacheCapacity", dbMaxOpenFiles),
WriteBuffer: debugEnvValue("WriteBuffer", defaultWriteBuffer),
// The write slowdown and pause can be overridden, but even if they
// are not and the compaction trigger is overridden we need to
// adjust so that we don't pause writes for L0 compaction before we
// even *start* L0 compaction...
WriteL0SlowdownTrigger: debugEnvValue("WriteL0SlowdownTrigger", 2*debugEnvValue("CompactionL0Trigger", defaultCompactionL0Trigger)),
WriteL0PauseTrigger: debugEnvValue("WriteL0SlowdownTrigger", 3*debugEnvValue("CompactionL0Trigger", defaultCompactionL0Trigger)),
}
return opts
}
func open(location string, opts *opt.Options) (*leveldb.DB, error) {
db, err := leveldb.OpenFile(location, opts)
if leveldbIsCorrupted(err) {
db, err = leveldb.RecoverFile(location, opts)
}
if leveldbIsCorrupted(err) {
// The database is corrupted, and we've tried to recover it but it
// didn't work. At this point there isn't much to do beyond dropping
// the database and reindexing...
l.Infoln("Database corruption detected, unable to recover. Reinitializing...")
if err := os.RemoveAll(location); err != nil {
return nil, errorSuggestion{err, "failed to delete corrupted database"}
}
db, err = leveldb.OpenFile(location, opts)
}
if err != nil {
return nil, errorSuggestion{err, "is another instance of Syncthing running?"}
}
if debugEnvValue("CompactEverything", 0) != 0 {
if err := db.CompactRange(util.Range{}); err != nil {
l.Warnln("Compacting database:", err)
}
}
return db, nil
}
func debugEnvValue(key string, def int) int {
v, err := strconv.ParseInt(os.Getenv("STDEBUG_"+key), 10, 63)
if err != nil {
return def
}
return int(v)
}
// A "better" version of leveldb's errors.IsCorrupted.
func leveldbIsCorrupted(err error) bool {
switch {
case err == nil:
return false
case errors.IsCorrupted(err):
return true
case strings.Contains(err.Error(), "corrupted"):
return true
}
return false
}
// dbIsLarge returns whether the estimated size of the database at location
// is large enough to warrant optimization for large databases.
func dbIsLarge(location string) bool {
if ^uint(0)>>63 == 0 {
// We're compiled for a 32 bit architecture. We've seen trouble with
// large settings there.
// (https://forum.syncthing.net/t/many-small-ldb-files-with-database-tuning/13842)
return false
}
dir, err := os.Open(location)
if err != nil {
return false
}
fis, err := dir.Readdir(-1)
if err != nil {
return false
}
var size int64
for _, fi := range fis {
if fi.Name() == "LOG" {
// don't count the size
continue
}
size += fi.Size()
}
return size > dbLargeThreshold
}
type errorSuggestion struct {
inner error
suggestion string
}
func (e errorSuggestion) Error() string {
return fmt.Sprintf("%s (%s)", e.inner.Error(), e.suggestion)
}

View File

@@ -0,0 +1,13 @@
// Copyright (C) 2019 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
package backend
import "testing"
func TestLevelDBBackendBehavior(t *testing.T) {
testBackendBehavior(t, OpenLevelDBMemory)
}

View File

@@ -11,6 +11,7 @@ import (
"testing"
"github.com/syncthing/syncthing/lib/db"
"github.com/syncthing/syncthing/lib/db/backend"
"github.com/syncthing/syncthing/lib/fs"
"github.com/syncthing/syncthing/lib/protocol"
)
@@ -40,7 +41,7 @@ func lazyInitBenchFiles() {
func getBenchFileSet() (*db.Lowlevel, *db.FileSet) {
lazyInitBenchFiles()
ldb := db.OpenMemory()
ldb := db.NewLowlevel(backend.OpenMemory())
benchS := db.NewFileSet("test)", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
replace(benchS, remoteDevice0, files)
replace(benchS, protocol.LocalDeviceID, firstHalf)
@@ -49,7 +50,7 @@ func getBenchFileSet() (*db.Lowlevel, *db.FileSet) {
}
func BenchmarkReplaceAll(b *testing.B) {
ldb := db.OpenMemory()
ldb := db.NewLowlevel(backend.OpenMemory())
defer ldb.Close()
b.ResetTimer()
@@ -157,7 +158,7 @@ func BenchmarkNeedHalf(b *testing.B) {
}
func BenchmarkNeedHalfRemote(b *testing.B) {
ldb := db.OpenMemory()
ldb := db.NewLowlevel(backend.OpenMemory())
defer ldb.Close()
fset := db.NewFileSet("test)", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
replace(fset, remoteDevice0, firstHalf)

View File

@@ -11,14 +11,12 @@ import (
"fmt"
"github.com/syncthing/syncthing/lib/osutil"
"github.com/syndtr/goleveldb/leveldb/util"
)
var blockFinder *BlockFinder
type BlockFinder struct {
db *instance
db *Lowlevel
}
func NewBlockFinder(db *Lowlevel) *BlockFinder {
@@ -27,7 +25,7 @@ func NewBlockFinder(db *Lowlevel) *BlockFinder {
}
return &BlockFinder{
db: newInstance(db),
db: db,
}
}
@@ -41,13 +39,22 @@ func (f *BlockFinder) String() string {
// reason. The iterator finally returns the result, whether or not a
// satisfying block was eventually found.
func (f *BlockFinder) Iterate(folders []string, hash []byte, iterFn func(string, string, int32) bool) bool {
t := f.db.newReadOnlyTransaction()
t, err := f.db.newReadOnlyTransaction()
if err != nil {
return false
}
defer t.close()
var key []byte
for _, folder := range folders {
key = f.db.keyer.GenerateBlockMapKey(key, []byte(folder), hash, nil)
iter := t.NewIterator(util.BytesPrefix(key), nil)
key, err = f.db.keyer.GenerateBlockMapKey(key, []byte(folder), hash, nil)
if err != nil {
return false
}
iter, err := t.NewPrefixIterator(key)
if err != nil {
return false
}
for iter.Next() && iter.Error() == nil {
file := string(f.db.keyer.NameFromBlockMapKey(iter.Key()))

View File

@@ -10,23 +10,10 @@ import (
"encoding/binary"
"testing"
"github.com/syncthing/syncthing/lib/db/backend"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syndtr/goleveldb/leveldb/util"
)
func genBlocks(n int) []protocol.BlockInfo {
b := make([]protocol.BlockInfo, n)
for i := range b {
h := make([]byte, 32)
for j := range h {
h[j] = byte(i + j)
}
b[i].Size = int32(i)
b[i].Hash = h
}
return b
}
var f1, f2, f3 protocol.FileInfo
var folders = []string{"folder1", "folder2"}
@@ -49,21 +36,27 @@ func init() {
}
}
func setup() (*instance, *BlockFinder) {
func setup() (*Lowlevel, *BlockFinder) {
// Setup
db := OpenMemory()
return newInstance(db), NewBlockFinder(db)
db := NewLowlevel(backend.OpenMemory())
return db, NewBlockFinder(db)
}
func dbEmpty(db *instance) bool {
iter := db.NewIterator(util.BytesPrefix([]byte{KeyTypeBlock}), nil)
func dbEmpty(db *Lowlevel) bool {
iter, err := db.NewPrefixIterator([]byte{KeyTypeBlock})
if err != nil {
panic(err)
}
defer iter.Release()
return !iter.Next()
}
func addToBlockMap(db *instance, folder []byte, fs []protocol.FileInfo) {
t := db.newReadWriteTransaction()
func addToBlockMap(db *Lowlevel, folder []byte, fs []protocol.FileInfo) error {
t, err := db.newReadWriteTransaction()
if err != nil {
return err
}
defer t.close()
var keyBuf []byte
@@ -73,15 +66,24 @@ func addToBlockMap(db *instance, folder []byte, fs []protocol.FileInfo) {
name := []byte(f.Name)
for i, block := range f.Blocks {
binary.BigEndian.PutUint32(blockBuf, uint32(i))
keyBuf = t.keyer.GenerateBlockMapKey(keyBuf, folder, block.Hash, name)
t.Put(keyBuf, blockBuf)
keyBuf, err = t.keyer.GenerateBlockMapKey(keyBuf, folder, block.Hash, name)
if err != nil {
return err
}
if err := t.Put(keyBuf, blockBuf); err != nil {
return err
}
}
}
}
return t.commit()
}
func discardFromBlockMap(db *instance, folder []byte, fs []protocol.FileInfo) {
t := db.newReadWriteTransaction()
func discardFromBlockMap(db *Lowlevel, folder []byte, fs []protocol.FileInfo) error {
t, err := db.newReadWriteTransaction()
if err != nil {
return err
}
defer t.close()
var keyBuf []byte
@@ -89,11 +91,17 @@ func discardFromBlockMap(db *instance, folder []byte, fs []protocol.FileInfo) {
if !ef.IsDirectory() && !ef.IsDeleted() && !ef.IsInvalid() {
name := []byte(ef.Name)
for _, block := range ef.Blocks {
keyBuf = t.keyer.GenerateBlockMapKey(keyBuf, folder, block.Hash, name)
t.Delete(keyBuf)
keyBuf, err = t.keyer.GenerateBlockMapKey(keyBuf, folder, block.Hash, name)
if err != nil {
return err
}
if err := t.Delete(keyBuf); err != nil {
return err
}
}
}
}
return t.commit()
}
func TestBlockMapAddUpdateWipe(t *testing.T) {
@@ -107,7 +115,9 @@ func TestBlockMapAddUpdateWipe(t *testing.T) {
f3.Type = protocol.FileInfoTypeDirectory
addToBlockMap(db, folder, []protocol.FileInfo{f1, f2, f3})
if err := addToBlockMap(db, folder, []protocol.FileInfo{f1, f2, f3}); err != nil {
t.Fatal(err)
}
f.Iterate(folders, f1.Blocks[0].Hash, func(folder, file string, index int32) bool {
if folder != "folder1" || file != "f1" || index != 0 {
@@ -128,12 +138,16 @@ func TestBlockMapAddUpdateWipe(t *testing.T) {
return true
})
discardFromBlockMap(db, folder, []protocol.FileInfo{f1, f2, f3})
if err := discardFromBlockMap(db, folder, []protocol.FileInfo{f1, f2, f3}); err != nil {
t.Fatal(err)
}
f1.Deleted = true
f2.LocalFlags = protocol.FlagLocalMustRescan // one of the invalid markers
addToBlockMap(db, folder, []protocol.FileInfo{f1, f2, f3})
if err := addToBlockMap(db, folder, []protocol.FileInfo{f1, f2, f3}); err != nil {
t.Fatal(err)
}
f.Iterate(folders, f1.Blocks[0].Hash, func(folder, file string, index int32) bool {
t.Fatal("Unexpected block")
@@ -152,14 +166,18 @@ func TestBlockMapAddUpdateWipe(t *testing.T) {
return true
})
db.dropFolder(folder)
if err := db.dropFolder(folder); err != nil {
t.Fatal(err)
}
if !dbEmpty(db) {
t.Fatal("db not empty")
}
// Should not add
addToBlockMap(db, folder, []protocol.FileInfo{f1, f2})
if err := addToBlockMap(db, folder, []protocol.FileInfo{f1, f2}); err != nil {
t.Fatal(err)
}
if !dbEmpty(db) {
t.Fatal("db not empty")
@@ -179,8 +197,12 @@ func TestBlockFinderLookup(t *testing.T) {
folder1 := []byte("folder1")
folder2 := []byte("folder2")
addToBlockMap(db, folder1, []protocol.FileInfo{f1})
addToBlockMap(db, folder2, []protocol.FileInfo{f1})
if err := addToBlockMap(db, folder1, []protocol.FileInfo{f1}); err != nil {
t.Fatal(err)
}
if err := addToBlockMap(db, folder2, []protocol.FileInfo{f1}); err != nil {
t.Fatal(err)
}
counter := 0
f.Iterate(folders, f1.Blocks[0].Hash, func(folder, file string, index int32) bool {
@@ -204,11 +226,15 @@ func TestBlockFinderLookup(t *testing.T) {
t.Fatal("Incorrect count", counter)
}
discardFromBlockMap(db, folder1, []protocol.FileInfo{f1})
if err := discardFromBlockMap(db, folder1, []protocol.FileInfo{f1}); err != nil {
t.Fatal(err)
}
f1.Deleted = true
addToBlockMap(db, folder1, []protocol.FileInfo{f1})
if err := addToBlockMap(db, folder1, []protocol.FileInfo{f1}); err != nil {
t.Fatal(err)
}
counter = 0
f.Iterate(folders, f1.Blocks[0].Hash, func(folder, file string, index int32) bool {

View File

@@ -1,237 +0,0 @@
// Copyright (C) 2014 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
// this is a really tedious test for an old issue
// +build ignore
package db_test
import (
"crypto/rand"
"log"
"os"
"testing"
"time"
"github.com/syncthing/syncthing/lib/sync"
"github.com/syndtr/goleveldb/leveldb"
"github.com/syndtr/goleveldb/leveldb/opt"
"github.com/syndtr/goleveldb/leveldb/util"
)
var keys [][]byte
func init() {
for i := 0; i < nItems; i++ {
keys = append(keys, randomData(1))
}
}
const nItems = 10000
func randomData(prefix byte) []byte {
data := make([]byte, 1+32+64+32)
_, err := rand.Reader.Read(data)
if err != nil {
panic(err)
}
return append([]byte{prefix}, data...)
}
func setItems(db *leveldb.DB) error {
batch := new(leveldb.Batch)
for _, k1 := range keys {
k2 := randomData(2)
// k2 -> data
batch.Put(k2, randomData(42))
// k1 -> k2
batch.Put(k1, k2)
}
if testing.Verbose() {
log.Printf("batch write (set) %p", batch)
}
return db.Write(batch, nil)
}
func clearItems(db *leveldb.DB) error {
snap, err := db.GetSnapshot()
if err != nil {
return err
}
defer snap.Release()
// Iterate over k2
it := snap.NewIterator(util.BytesPrefix([]byte{1}), nil)
defer it.Release()
batch := new(leveldb.Batch)
for it.Next() {
k1 := it.Key()
k2 := it.Value()
// k2 should exist
_, err := snap.Get(k2, nil)
if err != nil {
return err
}
// Delete the k1 => k2 mapping first
batch.Delete(k1)
// Then the k2 => data mapping
batch.Delete(k2)
}
if testing.Verbose() {
log.Printf("batch write (clear) %p", batch)
}
return db.Write(batch, nil)
}
func scanItems(db *leveldb.DB) error {
snap, err := db.GetSnapshot()
if testing.Verbose() {
log.Printf("snap create %p", snap)
}
if err != nil {
return err
}
defer func() {
if testing.Verbose() {
log.Printf("snap release %p", snap)
}
snap.Release()
}()
// Iterate from the start of k2 space to the end
it := snap.NewIterator(util.BytesPrefix([]byte{1}), nil)
defer it.Release()
i := 0
for it.Next() {
// k2 => k1 => data
k1 := it.Key()
k2 := it.Value()
_, err := snap.Get(k2, nil)
if err != nil {
log.Printf("k1: %x", k1)
log.Printf("k2: %x (missing)", k2)
return err
}
i++
}
if testing.Verbose() {
log.Println("scanned", i)
}
return nil
}
func TestConcurrentSetClear(t *testing.T) {
if testing.Short() {
return
}
dur := 30 * time.Second
t0 := time.Now()
wg := sync.NewWaitGroup()
os.RemoveAll("testdata/concurrent-set-clear.db")
db, err := leveldb.OpenFile("testdata/concurrent-set-clear.db", &opt.Options{OpenFilesCacheCapacity: 10})
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll("testdata/concurrent-set-clear.db")
errChan := make(chan error, 3)
wg.Add(1)
go func() {
defer wg.Done()
for time.Since(t0) < dur {
if err := setItems(db); err != nil {
errChan <- err
return
}
if err := clearItems(db); err != nil {
errChan <- err
return
}
}
}()
wg.Add(1)
go func() {
defer wg.Done()
for time.Since(t0) < dur {
if err := scanItems(db); err != nil {
errChan <- err
return
}
}
}()
go func() {
wg.Wait()
errChan <- nil
}()
err = <-errChan
if err != nil {
t.Error(err)
}
db.Close()
}
func TestConcurrentSetOnly(t *testing.T) {
if testing.Short() {
return
}
dur := 30 * time.Second
t0 := time.Now()
wg := sync.NewWaitGroup()
os.RemoveAll("testdata/concurrent-set-only.db")
db, err := leveldb.OpenFile("testdata/concurrent-set-only.db", &opt.Options{OpenFilesCacheCapacity: 10})
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll("testdata/concurrent-set-only.db")
errChan := make(chan error, 3)
wg.Add(1)
go func() {
defer wg.Done()
for time.Since(t0) < dur {
if err := setItems(db); err != nil {
errChan <- err
return
}
}
}()
wg.Add(1)
go func() {
defer wg.Done()
for time.Since(t0) < dur {
if err := scanItems(db); err != nil {
errChan <- err
return
}
}
}()
go func() {
wg.Wait()
errChan <- nil
}()
err = <-errChan
if err != nil {
t.Error(err)
}
}

View File

@@ -9,17 +9,33 @@ package db
import (
"testing"
"github.com/syncthing/syncthing/lib/db/backend"
"github.com/syncthing/syncthing/lib/fs"
"github.com/syncthing/syncthing/lib/protocol"
)
func genBlocks(n int) []protocol.BlockInfo {
b := make([]protocol.BlockInfo, n)
for i := range b {
h := make([]byte, 32)
for j := range h {
h[j] = byte(i + j)
}
b[i].Size = int32(i)
b[i].Hash = h
}
return b
}
func TestIgnoredFiles(t *testing.T) {
ldb, err := openJSONS("testdata/v0.14.48-ignoredfiles.db.jsons")
if err != nil {
t.Fatal(err)
}
db := NewLowlevel(ldb, "<memory>")
UpdateSchema(db)
db := NewLowlevel(ldb)
if err := UpdateSchema(db); err != nil {
t.Fatal(err)
}
fs := NewFileSet("test", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), db)
@@ -142,25 +158,35 @@ func TestUpdate0to3(t *testing.T) {
t.Fatal(err)
}
db := newInstance(NewLowlevel(ldb, "<memory>"))
db := NewLowlevel(ldb)
updater := schemaUpdater{db}
folder := []byte(update0to3Folder)
updater.updateSchema0to1()
if err := updater.updateSchema0to1(); err != nil {
t.Fatal(err)
}
if _, ok := db.getFileDirty(folder, protocol.LocalDeviceID[:], []byte(slashPrefixed)); ok {
if _, ok, err := db.getFileDirty(folder, protocol.LocalDeviceID[:], []byte(slashPrefixed)); err != nil {
t.Fatal(err)
} else if ok {
t.Error("File prefixed by '/' was not removed during transition to schema 1")
}
if _, err := db.Get(db.keyer.GenerateGlobalVersionKey(nil, folder, []byte(invalid)), nil); err != nil {
key, err := db.keyer.GenerateGlobalVersionKey(nil, folder, []byte(invalid))
if err != nil {
t.Fatal(err)
}
if _, err := db.Get(key); err != nil {
t.Error("Invalid file wasn't added to global list")
}
updater.updateSchema1to2()
if err := updater.updateSchema1to2(); err != nil {
t.Fatal(err)
}
found := false
db.withHaveSequence(folder, 0, func(fi FileIntf) bool {
_ = db.withHaveSequence(folder, 0, func(fi FileIntf) bool {
f := fi.(protocol.FileInfo)
l.Infoln(f)
if found {
@@ -178,14 +204,16 @@ func TestUpdate0to3(t *testing.T) {
t.Error("Local file wasn't added to sequence bucket", err)
}
updater.updateSchema2to3()
if err := updater.updateSchema2to3(); err != nil {
t.Fatal(err)
}
need := map[string]protocol.FileInfo{
haveUpdate0to3[remoteDevice0][0].Name: haveUpdate0to3[remoteDevice0][0],
haveUpdate0to3[remoteDevice1][0].Name: haveUpdate0to3[remoteDevice1][0],
haveUpdate0to3[remoteDevice0][2].Name: haveUpdate0to3[remoteDevice0][2],
}
db.withNeed(folder, protocol.LocalDeviceID[:], false, func(fi FileIntf) bool {
_ = db.withNeed(folder, protocol.LocalDeviceID[:], false, func(fi FileIntf) bool {
e, ok := need[fi.FileName()]
if !ok {
t.Error("Got unexpected needed file:", fi.FileName())
@@ -203,12 +231,17 @@ func TestUpdate0to3(t *testing.T) {
}
func TestDowngrade(t *testing.T) {
db := OpenMemory()
UpdateSchema(db) // sets the min version etc
db := NewLowlevel(backend.OpenMemory())
// sets the min version etc
if err := UpdateSchema(db); err != nil {
t.Fatal(err)
}
// Bump the database version to something newer than we actually support
miscDB := NewMiscDataNamespace(db)
miscDB.PutInt64("dbVersion", dbVersion+1)
if err := miscDB.PutInt64("dbVersion", dbVersion+1); err != nil {
t.Fatal(err)
}
l.Infoln(dbVersion)
// Pretend we just opened the DB and attempt to update it again

View File

@@ -7,9 +7,6 @@
package db
import (
"os"
"strings"
"github.com/syncthing/syncthing/lib/logger"
)
@@ -17,10 +14,6 @@ var (
l = logger.DefaultLogger.NewFacility("db", "The database layer")
)
func init() {
l.SetDebug("db", strings.Contains(os.Getenv("STTRACE"), "db") || os.Getenv("STTRACE") == "all")
}
func shouldDebug() bool {
return l.ShouldDebug("db")
}

View File

@@ -1,568 +0,0 @@
// Copyright (C) 2014 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
package db
import (
"bytes"
"encoding/binary"
"fmt"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syndtr/goleveldb/leveldb"
"github.com/syndtr/goleveldb/leveldb/util"
)
type instance struct {
*Lowlevel
keyer keyer
}
func newInstance(ll *Lowlevel) *instance {
return &instance{
Lowlevel: ll,
keyer: newDefaultKeyer(ll.folderIdx, ll.deviceIdx),
}
}
// updateRemoteFiles adds a list of fileinfos to the database and updates the
// global versionlist and metadata.
func (db *instance) updateRemoteFiles(folder, device []byte, fs []protocol.FileInfo, meta *metadataTracker) {
t := db.newReadWriteTransaction()
defer t.close()
var dk, gk, keyBuf []byte
devID := protocol.DeviceIDFromBytes(device)
for _, f := range fs {
name := []byte(f.Name)
dk = db.keyer.GenerateDeviceFileKey(dk, folder, device, name)
ef, ok := t.getFileTrunc(dk, true)
if ok && unchanged(f, ef) {
continue
}
if ok {
meta.removeFile(devID, ef)
}
meta.addFile(devID, f)
l.Debugf("insert; folder=%q device=%v %v", folder, devID, f)
t.Put(dk, mustMarshal(&f))
gk = db.keyer.GenerateGlobalVersionKey(gk, folder, name)
keyBuf, _ = t.updateGlobal(gk, keyBuf, folder, device, f, meta)
t.checkFlush()
}
}
// updateLocalFiles adds fileinfos to the db, and updates the global versionlist,
// metadata, sequence and blockmap buckets.
func (db *instance) updateLocalFiles(folder []byte, fs []protocol.FileInfo, meta *metadataTracker) {
t := db.newReadWriteTransaction()
defer t.close()
var dk, gk, keyBuf []byte
blockBuf := make([]byte, 4)
for _, f := range fs {
name := []byte(f.Name)
dk = db.keyer.GenerateDeviceFileKey(dk, folder, protocol.LocalDeviceID[:], name)
ef, ok := t.getFileByKey(dk)
if ok && unchanged(f, ef) {
continue
}
if ok {
if !ef.IsDirectory() && !ef.IsDeleted() && !ef.IsInvalid() {
for _, block := range ef.Blocks {
keyBuf = db.keyer.GenerateBlockMapKey(keyBuf, folder, block.Hash, name)
t.Delete(keyBuf)
}
}
keyBuf = db.keyer.GenerateSequenceKey(keyBuf, folder, ef.SequenceNo())
t.Delete(keyBuf)
l.Debugf("removing sequence; folder=%q sequence=%v %v", folder, ef.SequenceNo(), ef.FileName())
}
f.Sequence = meta.nextLocalSeq()
if ok {
meta.removeFile(protocol.LocalDeviceID, ef)
}
meta.addFile(protocol.LocalDeviceID, f)
l.Debugf("insert (local); folder=%q %v", folder, f)
t.Put(dk, mustMarshal(&f))
gk = db.keyer.GenerateGlobalVersionKey(gk, folder, []byte(f.Name))
keyBuf, _ = t.updateGlobal(gk, keyBuf, folder, protocol.LocalDeviceID[:], f, meta)
keyBuf = db.keyer.GenerateSequenceKey(keyBuf, folder, f.Sequence)
t.Put(keyBuf, dk)
l.Debugf("adding sequence; folder=%q sequence=%v %v", folder, f.Sequence, f.Name)
if !f.IsDirectory() && !f.IsDeleted() && !f.IsInvalid() {
for i, block := range f.Blocks {
binary.BigEndian.PutUint32(blockBuf, uint32(i))
keyBuf = db.keyer.GenerateBlockMapKey(keyBuf, folder, block.Hash, name)
t.Put(keyBuf, blockBuf)
}
}
t.checkFlush()
}
}
func (db *instance) withHave(folder, device, prefix []byte, truncate bool, fn Iterator) {
t := db.newReadOnlyTransaction()
defer t.close()
if len(prefix) > 0 {
unslashedPrefix := prefix
if bytes.HasSuffix(prefix, []byte{'/'}) {
unslashedPrefix = unslashedPrefix[:len(unslashedPrefix)-1]
} else {
prefix = append(prefix, '/')
}
if f, ok := t.getFileTrunc(db.keyer.GenerateDeviceFileKey(nil, folder, device, unslashedPrefix), true); ok && !fn(f) {
return
}
}
dbi := t.NewIterator(util.BytesPrefix(db.keyer.GenerateDeviceFileKey(nil, folder, device, prefix)), nil)
defer dbi.Release()
for dbi.Next() {
name := db.keyer.NameFromDeviceFileKey(dbi.Key())
if len(prefix) > 0 && !bytes.HasPrefix(name, prefix) {
return
}
f, err := unmarshalTrunc(dbi.Value(), truncate)
if err != nil {
l.Debugln("unmarshal error:", err)
continue
}
if !fn(f) {
return
}
}
}
func (db *instance) withHaveSequence(folder []byte, startSeq int64, fn Iterator) {
t := db.newReadOnlyTransaction()
defer t.close()
dbi := t.NewIterator(&util.Range{Start: db.keyer.GenerateSequenceKey(nil, folder, startSeq), Limit: db.keyer.GenerateSequenceKey(nil, folder, maxInt64)}, nil)
defer dbi.Release()
for dbi.Next() {
f, ok := t.getFileByKey(dbi.Value())
if !ok {
l.Debugln("missing file for sequence number", db.keyer.SequenceFromSequenceKey(dbi.Key()))
continue
}
if shouldDebug() {
if seq := db.keyer.SequenceFromSequenceKey(dbi.Key()); f.Sequence != seq {
l.Warnf("Sequence index corruption (folder %v, file %v): sequence %d != expected %d", string(folder), f.Name, f.Sequence, seq)
panic("sequence index corruption")
}
}
if !fn(f) {
return
}
}
}
func (db *instance) withAllFolderTruncated(folder []byte, fn func(device []byte, f FileInfoTruncated) bool) {
t := db.newReadWriteTransaction()
defer t.close()
dbi := t.NewIterator(util.BytesPrefix(db.keyer.GenerateDeviceFileKey(nil, folder, nil, nil).WithoutNameAndDevice()), nil)
defer dbi.Release()
var gk, keyBuf []byte
for dbi.Next() {
device, ok := db.keyer.DeviceFromDeviceFileKey(dbi.Key())
if !ok {
// Not having the device in the index is bad. Clear it.
t.Delete(dbi.Key())
t.checkFlush()
continue
}
var f FileInfoTruncated
// The iterator function may keep a reference to the unmarshalled
// struct, which in turn references the buffer it was unmarshalled
// from. dbi.Value() just returns an internal slice that it reuses, so
// we need to copy it.
err := f.Unmarshal(append([]byte{}, dbi.Value()...))
if err != nil {
l.Debugln("unmarshal error:", err)
continue
}
switch f.Name {
case "", ".", "..", "/": // A few obviously invalid filenames
l.Infof("Dropping invalid filename %q from database", f.Name)
name := []byte(f.Name)
gk = db.keyer.GenerateGlobalVersionKey(gk, folder, name)
keyBuf = t.removeFromGlobal(gk, keyBuf, folder, device, name, nil)
t.Delete(dbi.Key())
t.checkFlush()
continue
}
if !fn(device, f) {
return
}
}
}
func (db *instance) getFileDirty(folder, device, file []byte) (protocol.FileInfo, bool) {
t := db.newReadOnlyTransaction()
defer t.close()
return t.getFile(folder, device, file)
}
func (db *instance) getGlobalDirty(folder, file []byte, truncate bool) (FileIntf, bool) {
t := db.newReadOnlyTransaction()
defer t.close()
_, f, ok := t.getGlobal(nil, folder, file, truncate)
return f, ok
}
func (db *instance) withGlobal(folder, prefix []byte, truncate bool, fn Iterator) {
t := db.newReadOnlyTransaction()
defer t.close()
if len(prefix) > 0 {
unslashedPrefix := prefix
if bytes.HasSuffix(prefix, []byte{'/'}) {
unslashedPrefix = unslashedPrefix[:len(unslashedPrefix)-1]
} else {
prefix = append(prefix, '/')
}
if _, f, ok := t.getGlobal(nil, folder, unslashedPrefix, truncate); ok && !fn(f) {
return
}
}
dbi := t.NewIterator(util.BytesPrefix(db.keyer.GenerateGlobalVersionKey(nil, folder, prefix)), nil)
defer dbi.Release()
var dk []byte
for dbi.Next() {
name := db.keyer.NameFromGlobalVersionKey(dbi.Key())
if len(prefix) > 0 && !bytes.HasPrefix(name, prefix) {
return
}
vl, ok := unmarshalVersionList(dbi.Value())
if !ok {
continue
}
dk = db.keyer.GenerateDeviceFileKey(dk, folder, vl.Versions[0].Device, name)
f, ok := t.getFileTrunc(dk, truncate)
if !ok {
continue
}
if !fn(f) {
return
}
}
}
func (db *instance) availability(folder, file []byte) []protocol.DeviceID {
k := db.keyer.GenerateGlobalVersionKey(nil, folder, file)
bs, err := db.Get(k, nil)
if err == leveldb.ErrNotFound {
return nil
}
if err != nil {
l.Debugln("surprise error:", err)
return nil
}
vl, ok := unmarshalVersionList(bs)
if !ok {
return nil
}
var devices []protocol.DeviceID
for _, v := range vl.Versions {
if !v.Version.Equal(vl.Versions[0].Version) {
break
}
if v.Invalid {
continue
}
n := protocol.DeviceIDFromBytes(v.Device)
devices = append(devices, n)
}
return devices
}
func (db *instance) withNeed(folder, device []byte, truncate bool, fn Iterator) {
if bytes.Equal(device, protocol.LocalDeviceID[:]) {
db.withNeedLocal(folder, truncate, fn)
return
}
t := db.newReadOnlyTransaction()
defer t.close()
dbi := t.NewIterator(util.BytesPrefix(db.keyer.GenerateGlobalVersionKey(nil, folder, nil).WithoutName()), nil)
defer dbi.Release()
var dk []byte
devID := protocol.DeviceIDFromBytes(device)
for dbi.Next() {
vl, ok := unmarshalVersionList(dbi.Value())
if !ok {
continue
}
haveFV, have := vl.Get(device)
// XXX: This marks Concurrent (i.e. conflicting) changes as
// needs. Maybe we should do that, but it needs special
// handling in the puller.
if have && haveFV.Version.GreaterEqual(vl.Versions[0].Version) {
continue
}
name := db.keyer.NameFromGlobalVersionKey(dbi.Key())
needVersion := vl.Versions[0].Version
needDevice := protocol.DeviceIDFromBytes(vl.Versions[0].Device)
for i := range vl.Versions {
if !vl.Versions[i].Version.Equal(needVersion) {
// We haven't found a valid copy of the file with the needed version.
break
}
if vl.Versions[i].Invalid {
// The file is marked invalid, don't use it.
continue
}
dk = db.keyer.GenerateDeviceFileKey(dk, folder, vl.Versions[i].Device, name)
gf, ok := t.getFileTrunc(dk, truncate)
if !ok {
continue
}
if gf.IsDeleted() && !have {
// We don't need deleted files that we don't have
break
}
l.Debugf("need folder=%q device=%v name=%q have=%v invalid=%v haveV=%v globalV=%v globalDev=%v", folder, devID, name, have, haveFV.Invalid, haveFV.Version, needVersion, needDevice)
if !fn(gf) {
return
}
// This file is handled, no need to look further in the version list
break
}
}
}
func (db *instance) withNeedLocal(folder []byte, truncate bool, fn Iterator) {
t := db.newReadOnlyTransaction()
defer t.close()
dbi := t.NewIterator(util.BytesPrefix(db.keyer.GenerateNeedFileKey(nil, folder, nil).WithoutName()), nil)
defer dbi.Release()
var keyBuf []byte
var f FileIntf
var ok bool
for dbi.Next() {
keyBuf, f, ok = t.getGlobal(keyBuf, folder, db.keyer.NameFromGlobalVersionKey(dbi.Key()), truncate)
if !ok {
continue
}
if !fn(f) {
return
}
}
}
func (db *instance) dropFolder(folder []byte) {
t := db.newReadWriteTransaction()
defer t.close()
for _, key := range [][]byte{
// Remove all items related to the given folder from the device->file bucket
db.keyer.GenerateDeviceFileKey(nil, folder, nil, nil).WithoutNameAndDevice(),
// Remove all sequences related to the folder
db.keyer.GenerateSequenceKey(nil, []byte(folder), 0).WithoutSequence(),
// Remove all items related to the given folder from the global bucket
db.keyer.GenerateGlobalVersionKey(nil, folder, nil).WithoutName(),
// Remove all needs related to the folder
db.keyer.GenerateNeedFileKey(nil, folder, nil).WithoutName(),
// Remove the blockmap of the folder
db.keyer.GenerateBlockMapKey(nil, folder, nil, nil).WithoutHashAndName(),
} {
t.deleteKeyPrefix(key)
}
}
func (db *instance) dropDeviceFolder(device, folder []byte, meta *metadataTracker) {
t := db.newReadWriteTransaction()
defer t.close()
dbi := t.NewIterator(util.BytesPrefix(db.keyer.GenerateDeviceFileKey(nil, folder, device, nil)), nil)
defer dbi.Release()
var gk, keyBuf []byte
for dbi.Next() {
name := db.keyer.NameFromDeviceFileKey(dbi.Key())
gk = db.keyer.GenerateGlobalVersionKey(gk, folder, name)
keyBuf = t.removeFromGlobal(gk, keyBuf, folder, device, name, meta)
t.Delete(dbi.Key())
t.checkFlush()
}
if bytes.Equal(device, protocol.LocalDeviceID[:]) {
t.deleteKeyPrefix(db.keyer.GenerateBlockMapKey(nil, folder, nil, nil).WithoutHashAndName())
}
}
func (db *instance) checkGlobals(folder []byte, meta *metadataTracker) {
t := db.newReadWriteTransaction()
defer t.close()
dbi := t.NewIterator(util.BytesPrefix(db.keyer.GenerateGlobalVersionKey(nil, folder, nil).WithoutName()), nil)
defer dbi.Release()
var dk []byte
for dbi.Next() {
vl, ok := unmarshalVersionList(dbi.Value())
if !ok {
continue
}
// Check the global version list for consistency. An issue in previous
// versions of goleveldb could result in reordered writes so that
// there are global entries pointing to no longer existing files. Here
// we find those and clear them out.
name := db.keyer.NameFromGlobalVersionKey(dbi.Key())
var newVL VersionList
for i, version := range vl.Versions {
dk = db.keyer.GenerateDeviceFileKey(dk, folder, version.Device, name)
_, err := t.Get(dk, nil)
if err == leveldb.ErrNotFound {
continue
}
if err != nil {
l.Debugln("surprise error:", err)
return
}
newVL.Versions = append(newVL.Versions, version)
if i == 0 {
if fi, ok := t.getFileByKey(dk); ok {
meta.addFile(protocol.GlobalDeviceID, fi)
}
}
}
if len(newVL.Versions) != len(vl.Versions) {
t.Put(dbi.Key(), mustMarshal(&newVL))
t.checkFlush()
}
}
l.Debugf("db check completed for %q", folder)
}
func (db *instance) getIndexID(device, folder []byte) protocol.IndexID {
cur, err := db.Get(db.keyer.GenerateIndexIDKey(nil, device, folder), nil)
if err != nil {
return 0
}
var id protocol.IndexID
if err := id.Unmarshal(cur); err != nil {
return 0
}
return id
}
func (db *instance) setIndexID(device, folder []byte, id protocol.IndexID) {
bs, _ := id.Marshal() // marshalling can't fail
if err := db.Put(db.keyer.GenerateIndexIDKey(nil, device, folder), bs, nil); err != nil && err != leveldb.ErrClosed {
panic("storing index ID: " + err.Error())
}
}
func (db *instance) dropMtimes(folder []byte) {
db.dropPrefix(db.keyer.GenerateMtimesKey(nil, folder))
}
func (db *instance) dropFolderMeta(folder []byte) {
db.dropPrefix(db.keyer.GenerateFolderMetaKey(nil, folder))
}
func (db *instance) dropPrefix(prefix []byte) {
t := db.newReadWriteTransaction()
defer t.close()
t.deleteKeyPrefix(prefix)
}
func unmarshalTrunc(bs []byte, truncate bool) (FileIntf, error) {
if truncate {
var tf FileInfoTruncated
err := tf.Unmarshal(bs)
return tf, err
}
var tf protocol.FileInfo
err := tf.Unmarshal(bs)
return tf, err
}
func unmarshalVersionList(data []byte) (VersionList, bool) {
var vl VersionList
if err := vl.Unmarshal(data); err != nil {
l.Debugln("unmarshal error:", err)
return VersionList{}, false
}
if len(vl.Versions) == 0 {
l.Debugln("empty version list")
return VersionList{}, false
}
return vl, true
}
type errorSuggestion struct {
inner error
suggestion string
}
func (e errorSuggestion) Error() string {
return fmt.Sprintf("%s (%s)", e.inner.Error(), e.suggestion)
}
// unchanged checks if two files are the same and thus don't need to be updated.
// Local flags or the invalid bit might change without the version
// being bumped. The IsInvalid() method handles both.
func unchanged(nf, ef FileIntf) bool {
return ef.FileVersion().Equal(nf.FileVersion()) && ef.IsInvalid() == nf.IsInvalid()
}

View File

@@ -63,36 +63,36 @@ const (
type keyer interface {
// device file key stuff
GenerateDeviceFileKey(key, folder, device, name []byte) deviceFileKey
GenerateDeviceFileKey(key, folder, device, name []byte) (deviceFileKey, error)
NameFromDeviceFileKey(key []byte) []byte
DeviceFromDeviceFileKey(key []byte) ([]byte, bool)
FolderFromDeviceFileKey(key []byte) ([]byte, bool)
// global version key stuff
GenerateGlobalVersionKey(key, folder, name []byte) globalVersionKey
GenerateGlobalVersionKey(key, folder, name []byte) (globalVersionKey, error)
NameFromGlobalVersionKey(key []byte) []byte
FolderFromGlobalVersionKey(key []byte) ([]byte, bool)
// block map key stuff (former BlockMap)
GenerateBlockMapKey(key, folder, hash, name []byte) blockMapKey
GenerateBlockMapKey(key, folder, hash, name []byte) (blockMapKey, error)
NameFromBlockMapKey(key []byte) []byte
// file need index
GenerateNeedFileKey(key, folder, name []byte) needFileKey
GenerateNeedFileKey(key, folder, name []byte) (needFileKey, error)
// file sequence index
GenerateSequenceKey(key, folder []byte, seq int64) sequenceKey
GenerateSequenceKey(key, folder []byte, seq int64) (sequenceKey, error)
SequenceFromSequenceKey(key []byte) int64
// index IDs
GenerateIndexIDKey(key, device, folder []byte) indexIDKey
GenerateIndexIDKey(key, device, folder []byte) (indexIDKey, error)
DeviceFromIndexIDKey(key []byte) ([]byte, bool)
// Mtimes
GenerateMtimesKey(key, folder []byte) mtimesKey
GenerateMtimesKey(key, folder []byte) (mtimesKey, error)
// Folder metadata
GenerateFolderMetaKey(key, folder []byte) folderMetaKey
GenerateFolderMetaKey(key, folder []byte) (folderMetaKey, error)
}
// defaultKeyer implements our key scheme. It needs folder and device
@@ -115,13 +115,21 @@ func (k deviceFileKey) WithoutNameAndDevice() []byte {
return k[:keyPrefixLen+keyFolderLen]
}
func (k defaultKeyer) GenerateDeviceFileKey(key, folder, device, name []byte) deviceFileKey {
func (k defaultKeyer) GenerateDeviceFileKey(key, folder, device, name []byte) (deviceFileKey, error) {
folderID, err := k.folderIdx.ID(folder)
if err != nil {
return nil, err
}
deviceID, err := k.deviceIdx.ID(device)
if err != nil {
return nil, err
}
key = resize(key, keyPrefixLen+keyFolderLen+keyDeviceLen+len(name))
key[0] = KeyTypeDevice
binary.BigEndian.PutUint32(key[keyPrefixLen:], k.folderIdx.ID(folder))
binary.BigEndian.PutUint32(key[keyPrefixLen+keyFolderLen:], k.deviceIdx.ID(device))
binary.BigEndian.PutUint32(key[keyPrefixLen:], folderID)
binary.BigEndian.PutUint32(key[keyPrefixLen+keyFolderLen:], deviceID)
copy(key[keyPrefixLen+keyFolderLen+keyDeviceLen:], name)
return key
return key, nil
}
func (k defaultKeyer) NameFromDeviceFileKey(key []byte) []byte {
@@ -142,12 +150,16 @@ func (k globalVersionKey) WithoutName() []byte {
return k[:keyPrefixLen+keyFolderLen]
}
func (k defaultKeyer) GenerateGlobalVersionKey(key, folder, name []byte) globalVersionKey {
func (k defaultKeyer) GenerateGlobalVersionKey(key, folder, name []byte) (globalVersionKey, error) {
folderID, err := k.folderIdx.ID(folder)
if err != nil {
return nil, err
}
key = resize(key, keyPrefixLen+keyFolderLen+len(name))
key[0] = KeyTypeGlobal
binary.BigEndian.PutUint32(key[keyPrefixLen:], k.folderIdx.ID(folder))
binary.BigEndian.PutUint32(key[keyPrefixLen:], folderID)
copy(key[keyPrefixLen+keyFolderLen:], name)
return key
return key, nil
}
func (k defaultKeyer) NameFromGlobalVersionKey(key []byte) []byte {
@@ -160,13 +172,17 @@ func (k defaultKeyer) FolderFromGlobalVersionKey(key []byte) ([]byte, bool) {
type blockMapKey []byte
func (k defaultKeyer) GenerateBlockMapKey(key, folder, hash, name []byte) blockMapKey {
func (k defaultKeyer) GenerateBlockMapKey(key, folder, hash, name []byte) (blockMapKey, error) {
folderID, err := k.folderIdx.ID(folder)
if err != nil {
return nil, err
}
key = resize(key, keyPrefixLen+keyFolderLen+keyHashLen+len(name))
key[0] = KeyTypeBlock
binary.BigEndian.PutUint32(key[keyPrefixLen:], k.folderIdx.ID(folder))
binary.BigEndian.PutUint32(key[keyPrefixLen:], folderID)
copy(key[keyPrefixLen+keyFolderLen:], hash)
copy(key[keyPrefixLen+keyFolderLen+keyHashLen:], name)
return key
return key, nil
}
func (k defaultKeyer) NameFromBlockMapKey(key []byte) []byte {
@@ -183,12 +199,16 @@ func (k needFileKey) WithoutName() []byte {
return k[:keyPrefixLen+keyFolderLen]
}
func (k defaultKeyer) GenerateNeedFileKey(key, folder, name []byte) needFileKey {
func (k defaultKeyer) GenerateNeedFileKey(key, folder, name []byte) (needFileKey, error) {
folderID, err := k.folderIdx.ID(folder)
if err != nil {
return nil, err
}
key = resize(key, keyPrefixLen+keyFolderLen+len(name))
key[0] = KeyTypeNeed
binary.BigEndian.PutUint32(key[keyPrefixLen:], k.folderIdx.ID(folder))
binary.BigEndian.PutUint32(key[keyPrefixLen:], folderID)
copy(key[keyPrefixLen+keyFolderLen:], name)
return key
return key, nil
}
type sequenceKey []byte
@@ -197,12 +217,16 @@ func (k sequenceKey) WithoutSequence() []byte {
return k[:keyPrefixLen+keyFolderLen]
}
func (k defaultKeyer) GenerateSequenceKey(key, folder []byte, seq int64) sequenceKey {
func (k defaultKeyer) GenerateSequenceKey(key, folder []byte, seq int64) (sequenceKey, error) {
folderID, err := k.folderIdx.ID(folder)
if err != nil {
return nil, err
}
key = resize(key, keyPrefixLen+keyFolderLen+keySequenceLen)
key[0] = KeyTypeSequence
binary.BigEndian.PutUint32(key[keyPrefixLen:], k.folderIdx.ID(folder))
binary.BigEndian.PutUint32(key[keyPrefixLen:], folderID)
binary.BigEndian.PutUint64(key[keyPrefixLen+keyFolderLen:], uint64(seq))
return key
return key, nil
}
func (k defaultKeyer) SequenceFromSequenceKey(key []byte) int64 {
@@ -211,12 +235,20 @@ func (k defaultKeyer) SequenceFromSequenceKey(key []byte) int64 {
type indexIDKey []byte
func (k defaultKeyer) GenerateIndexIDKey(key, device, folder []byte) indexIDKey {
func (k defaultKeyer) GenerateIndexIDKey(key, device, folder []byte) (indexIDKey, error) {
deviceID, err := k.deviceIdx.ID(device)
if err != nil {
return nil, err
}
folderID, err := k.folderIdx.ID(folder)
if err != nil {
return nil, err
}
key = resize(key, keyPrefixLen+keyDeviceLen+keyFolderLen)
key[0] = KeyTypeIndexID
binary.BigEndian.PutUint32(key[keyPrefixLen:], k.deviceIdx.ID(device))
binary.BigEndian.PutUint32(key[keyPrefixLen+keyDeviceLen:], k.folderIdx.ID(folder))
return key
binary.BigEndian.PutUint32(key[keyPrefixLen:], deviceID)
binary.BigEndian.PutUint32(key[keyPrefixLen+keyDeviceLen:], folderID)
return key, nil
}
func (k defaultKeyer) DeviceFromIndexIDKey(key []byte) ([]byte, bool) {
@@ -225,20 +257,28 @@ func (k defaultKeyer) DeviceFromIndexIDKey(key []byte) ([]byte, bool) {
type mtimesKey []byte
func (k defaultKeyer) GenerateMtimesKey(key, folder []byte) mtimesKey {
func (k defaultKeyer) GenerateMtimesKey(key, folder []byte) (mtimesKey, error) {
folderID, err := k.folderIdx.ID(folder)
if err != nil {
return nil, err
}
key = resize(key, keyPrefixLen+keyFolderLen)
key[0] = KeyTypeVirtualMtime
binary.BigEndian.PutUint32(key[keyPrefixLen:], k.folderIdx.ID(folder))
return key
binary.BigEndian.PutUint32(key[keyPrefixLen:], folderID)
return key, nil
}
type folderMetaKey []byte
func (k defaultKeyer) GenerateFolderMetaKey(key, folder []byte) folderMetaKey {
func (k defaultKeyer) GenerateFolderMetaKey(key, folder []byte) (folderMetaKey, error) {
folderID, err := k.folderIdx.ID(folder)
if err != nil {
return nil, err
}
key = resize(key, keyPrefixLen+keyFolderLen)
key[0] = KeyTypeFolderMeta
binary.BigEndian.PutUint32(key[keyPrefixLen:], k.folderIdx.ID(folder))
return key
binary.BigEndian.PutUint32(key[keyPrefixLen:], folderID)
return key, nil
}
// resize returns a byte slice of the specified size, reusing bs if possible

View File

@@ -9,6 +9,8 @@ package db
import (
"bytes"
"testing"
"github.com/syncthing/syncthing/lib/db/backend"
)
func TestDeviceKey(t *testing.T) {
@@ -16,9 +18,12 @@ func TestDeviceKey(t *testing.T) {
dev := []byte("device67890123456789012345678901")
name := []byte("name")
db := newInstance(OpenMemory())
db := NewLowlevel(backend.OpenMemory())
key := db.keyer.GenerateDeviceFileKey(nil, fld, dev, name)
key, err := db.keyer.GenerateDeviceFileKey(nil, fld, dev, name)
if err != nil {
t.Fatal(err)
}
fld2, ok := db.keyer.FolderFromDeviceFileKey(key)
if !ok {
@@ -44,9 +49,12 @@ func TestGlobalKey(t *testing.T) {
fld := []byte("folder6789012345678901234567890123456789012345678901234567890123")
name := []byte("name")
db := newInstance(OpenMemory())
db := NewLowlevel(backend.OpenMemory())
key := db.keyer.GenerateGlobalVersionKey(nil, fld, name)
key, err := db.keyer.GenerateGlobalVersionKey(nil, fld, name)
if err != nil {
t.Fatal(err)
}
fld2, ok := db.keyer.FolderFromGlobalVersionKey(key)
if !ok {
@@ -69,10 +77,13 @@ func TestGlobalKey(t *testing.T) {
func TestSequenceKey(t *testing.T) {
fld := []byte("folder6789012345678901234567890123456789012345678901234567890123")
db := newInstance(OpenMemory())
db := NewLowlevel(backend.OpenMemory())
const seq = 1234567890
key := db.keyer.GenerateSequenceKey(nil, fld, seq)
key, err := db.keyer.GenerateSequenceKey(nil, fld, seq)
if err != nil {
t.Fatal(err)
}
outSeq := db.keyer.SequenceFromSequenceKey(key)
if outSeq != seq {
t.Errorf("sequence number mangled, %d != %d", outSeq, seq)

View File

File diff suppressed because it is too large Load Diff

View File

@@ -56,8 +56,11 @@ func (m *metadataTracker) Marshal() ([]byte, error) {
// toDB saves the marshalled metadataTracker to the given db, under the key
// corresponding to the given folder
func (m *metadataTracker) toDB(db *instance, folder []byte) error {
key := db.keyer.GenerateFolderMetaKey(nil, folder)
func (m *metadataTracker) toDB(db *Lowlevel, folder []byte) error {
key, err := db.keyer.GenerateFolderMetaKey(nil, folder)
if err != nil {
return err
}
m.mut.RLock()
defer m.mut.RUnlock()
@@ -70,7 +73,7 @@ func (m *metadataTracker) toDB(db *instance, folder []byte) error {
if err != nil {
return err
}
err = db.Put(key, bs, nil)
err = db.Put(key, bs)
if err == nil {
m.dirty = false
}
@@ -80,9 +83,12 @@ func (m *metadataTracker) toDB(db *instance, folder []byte) error {
// fromDB initializes the metadataTracker from the marshalled data found in
// the database under the key corresponding to the given folder
func (m *metadataTracker) fromDB(db *instance, folder []byte) error {
key := db.keyer.GenerateFolderMetaKey(nil, folder)
bs, err := db.Get(key, nil)
func (m *metadataTracker) fromDB(db *Lowlevel, folder []byte) error {
key, err := db.keyer.GenerateFolderMetaKey(nil, folder)
if err != nil {
return err
}
bs, err := db.Get(key)
if err != nil {
return err
}

View File

@@ -10,7 +10,7 @@ import (
"encoding/binary"
"time"
"github.com/syndtr/goleveldb/leveldb/util"
"github.com/syncthing/syncthing/lib/db/backend"
)
// NamespacedKV is a simple key-value store using a specific namespace within
@@ -34,112 +34,99 @@ func NewNamespacedKV(db *Lowlevel, prefix string) *NamespacedKV {
}
}
// Reset removes all entries in this namespace.
func (n *NamespacedKV) Reset() {
it := n.db.NewIterator(util.BytesPrefix(n.prefix), nil)
defer it.Release()
batch := n.db.newBatch()
for it.Next() {
batch.Delete(it.Key())
batch.checkFlush()
}
batch.flush()
}
// PutInt64 stores a new int64. Any existing value (even if of another type)
// is overwritten.
func (n *NamespacedKV) PutInt64(key string, val int64) {
func (n *NamespacedKV) PutInt64(key string, val int64) error {
var valBs [8]byte
binary.BigEndian.PutUint64(valBs[:], uint64(val))
n.db.Put(n.prefixedKey(key), valBs[:], nil)
return n.db.Put(n.prefixedKey(key), valBs[:])
}
// Int64 returns the stored value interpreted as an int64 and a boolean that
// is false if no value was stored at the key.
func (n *NamespacedKV) Int64(key string) (int64, bool) {
valBs, err := n.db.Get(n.prefixedKey(key), nil)
func (n *NamespacedKV) Int64(key string) (int64, bool, error) {
valBs, err := n.db.Get(n.prefixedKey(key))
if err != nil {
return 0, false
return 0, false, filterNotFound(err)
}
val := binary.BigEndian.Uint64(valBs)
return int64(val), true
return int64(val), true, nil
}
// PutTime stores a new time.Time. Any existing value (even if of another
// type) is overwritten.
func (n *NamespacedKV) PutTime(key string, val time.Time) {
func (n *NamespacedKV) PutTime(key string, val time.Time) error {
valBs, _ := val.MarshalBinary() // never returns an error
n.db.Put(n.prefixedKey(key), valBs, nil)
return n.db.Put(n.prefixedKey(key), valBs)
}
// Time returns the stored value interpreted as a time.Time and a boolean
// that is false if no value was stored at the key.
func (n NamespacedKV) Time(key string) (time.Time, bool) {
func (n NamespacedKV) Time(key string) (time.Time, bool, error) {
var t time.Time
valBs, err := n.db.Get(n.prefixedKey(key), nil)
valBs, err := n.db.Get(n.prefixedKey(key))
if err != nil {
return t, false
return t, false, filterNotFound(err)
}
err = t.UnmarshalBinary(valBs)
return t, err == nil
return t, err == nil, err
}
// PutString stores a new string. Any existing value (even if of another type)
// is overwritten.
func (n *NamespacedKV) PutString(key, val string) {
n.db.Put(n.prefixedKey(key), []byte(val), nil)
func (n *NamespacedKV) PutString(key, val string) error {
return n.db.Put(n.prefixedKey(key), []byte(val))
}
// String returns the stored value interpreted as a string and a boolean that
// is false if no value was stored at the key.
func (n NamespacedKV) String(key string) (string, bool) {
valBs, err := n.db.Get(n.prefixedKey(key), nil)
func (n NamespacedKV) String(key string) (string, bool, error) {
valBs, err := n.db.Get(n.prefixedKey(key))
if err != nil {
return "", false
return "", false, filterNotFound(err)
}
return string(valBs), true
return string(valBs), true, nil
}
// PutBytes stores a new byte slice. Any existing value (even if of another type)
// is overwritten.
func (n *NamespacedKV) PutBytes(key string, val []byte) {
n.db.Put(n.prefixedKey(key), val, nil)
func (n *NamespacedKV) PutBytes(key string, val []byte) error {
return n.db.Put(n.prefixedKey(key), val)
}
// Bytes returns the stored value as a raw byte slice and a boolean that
// is false if no value was stored at the key.
func (n NamespacedKV) Bytes(key string) ([]byte, bool) {
valBs, err := n.db.Get(n.prefixedKey(key), nil)
func (n NamespacedKV) Bytes(key string) ([]byte, bool, error) {
valBs, err := n.db.Get(n.prefixedKey(key))
if err != nil {
return nil, false
return nil, false, filterNotFound(err)
}
return valBs, true
return valBs, true, nil
}
// PutBool stores a new boolean. Any existing value (even if of another type)
// is overwritten.
func (n *NamespacedKV) PutBool(key string, val bool) {
func (n *NamespacedKV) PutBool(key string, val bool) error {
if val {
n.db.Put(n.prefixedKey(key), []byte{0x0}, nil)
} else {
n.db.Put(n.prefixedKey(key), []byte{0x1}, nil)
return n.db.Put(n.prefixedKey(key), []byte{0x0})
}
return n.db.Put(n.prefixedKey(key), []byte{0x1})
}
// Bool returns the stored value as a boolean and a boolean that
// is false if no value was stored at the key.
func (n NamespacedKV) Bool(key string) (bool, bool) {
valBs, err := n.db.Get(n.prefixedKey(key), nil)
func (n NamespacedKV) Bool(key string) (bool, bool, error) {
valBs, err := n.db.Get(n.prefixedKey(key))
if err != nil {
return false, false
return false, false, filterNotFound(err)
}
return valBs[0] == 0x0, true
return valBs[0] == 0x0, true, nil
}
// Delete deletes the specified key. It is allowed to delete a nonexistent
// key.
func (n NamespacedKV) Delete(key string) {
n.db.Delete(n.prefixedKey(key), nil)
func (n NamespacedKV) Delete(key string) error {
return n.db.Delete(n.prefixedKey(key))
}
func (n NamespacedKV) prefixedKey(key string) []byte {
@@ -165,3 +152,10 @@ func NewFolderStatisticsNamespace(db *Lowlevel, folder string) *NamespacedKV {
func NewMiscDataNamespace(db *Lowlevel) *NamespacedKV {
return NewNamespacedKV(db, string(KeyTypeMiscData))
}
func filterNotFound(err error) error {
if backend.IsNotFound(err) {
return nil
}
return err
}

View File

@@ -9,104 +9,167 @@ package db
import (
"testing"
"time"
"github.com/syncthing/syncthing/lib/db/backend"
)
func TestNamespacedInt(t *testing.T) {
ldb := OpenMemory()
ldb := NewLowlevel(backend.OpenMemory())
n1 := NewNamespacedKV(ldb, "foo")
n2 := NewNamespacedKV(ldb, "bar")
// Key is missing to start with
if v, ok := n1.Int64("test"); v != 0 || ok {
if v, ok, err := n1.Int64("test"); err != nil {
t.Error("Unexpected error:", err)
} else if v != 0 || ok {
t.Errorf("Incorrect return v %v != 0 || ok %v != false", v, ok)
}
n1.PutInt64("test", 42)
if err := n1.PutInt64("test", 42); err != nil {
t.Fatal(err)
}
// It should now exist in n1
if v, ok := n1.Int64("test"); v != 42 || !ok {
if v, ok, err := n1.Int64("test"); err != nil {
t.Error("Unexpected error:", err)
} else if v != 42 || !ok {
t.Errorf("Incorrect return v %v != 42 || ok %v != true", v, ok)
}
// ... but not in n2, which is in a different namespace
if v, ok := n2.Int64("test"); v != 0 || ok {
if v, ok, err := n2.Int64("test"); err != nil {
t.Error("Unexpected error:", err)
} else if v != 0 || ok {
t.Errorf("Incorrect return v %v != 0 || ok %v != false", v, ok)
}
n1.Delete("test")
if err := n1.Delete("test"); err != nil {
t.Fatal(err)
}
// It should no longer exist
if v, ok := n1.Int64("test"); v != 0 || ok {
if v, ok, err := n1.Int64("test"); err != nil {
t.Error("Unexpected error:", err)
} else if v != 0 || ok {
t.Errorf("Incorrect return v %v != 0 || ok %v != false", v, ok)
}
}
func TestNamespacedTime(t *testing.T) {
ldb := OpenMemory()
ldb := NewLowlevel(backend.OpenMemory())
n1 := NewNamespacedKV(ldb, "foo")
if v, ok := n1.Time("test"); !v.IsZero() || ok {
if v, ok, err := n1.Time("test"); err != nil {
t.Error("Unexpected error:", err)
} else if !v.IsZero() || ok {
t.Errorf("Incorrect return v %v != %v || ok %v != false", v, time.Time{}, ok)
}
now := time.Now()
n1.PutTime("test", now)
if err := n1.PutTime("test", now); err != nil {
t.Fatal(err)
}
if v, ok := n1.Time("test"); !v.Equal(now) || !ok {
if v, ok, err := n1.Time("test"); err != nil {
t.Error("Unexpected error:", err)
} else if !v.Equal(now) || !ok {
t.Errorf("Incorrect return v %v != %v || ok %v != true", v, now, ok)
}
}
func TestNamespacedString(t *testing.T) {
ldb := OpenMemory()
ldb := NewLowlevel(backend.OpenMemory())
n1 := NewNamespacedKV(ldb, "foo")
if v, ok := n1.String("test"); v != "" || ok {
if v, ok, err := n1.String("test"); err != nil {
t.Error("Unexpected error:", err)
} else if v != "" || ok {
t.Errorf("Incorrect return v %q != \"\" || ok %v != false", v, ok)
}
n1.PutString("test", "yo")
if err := n1.PutString("test", "yo"); err != nil {
t.Fatal(err)
}
if v, ok := n1.String("test"); v != "yo" || !ok {
if v, ok, err := n1.String("test"); err != nil {
t.Error("Unexpected error:", err)
} else if v != "yo" || !ok {
t.Errorf("Incorrect return v %q != \"yo\" || ok %v != true", v, ok)
}
}
func TestNamespacedReset(t *testing.T) {
ldb := OpenMemory()
ldb := NewLowlevel(backend.OpenMemory())
n1 := NewNamespacedKV(ldb, "foo")
n1.PutString("test1", "yo1")
n1.PutString("test2", "yo2")
n1.PutString("test3", "yo3")
if err := n1.PutString("test1", "yo1"); err != nil {
t.Fatal(err)
}
if err := n1.PutString("test2", "yo2"); err != nil {
t.Fatal(err)
}
if err := n1.PutString("test3", "yo3"); err != nil {
t.Fatal(err)
}
if v, ok := n1.String("test1"); v != "yo1" || !ok {
if v, ok, err := n1.String("test1"); err != nil {
t.Error("Unexpected error:", err)
} else if v != "yo1" || !ok {
t.Errorf("Incorrect return v %q != \"yo1\" || ok %v != true", v, ok)
}
if v, ok := n1.String("test2"); v != "yo2" || !ok {
if v, ok, err := n1.String("test2"); err != nil {
t.Error("Unexpected error:", err)
} else if v != "yo2" || !ok {
t.Errorf("Incorrect return v %q != \"yo2\" || ok %v != true", v, ok)
}
if v, ok := n1.String("test3"); v != "yo3" || !ok {
if v, ok, err := n1.String("test3"); err != nil {
t.Error("Unexpected error:", err)
} else if v != "yo3" || !ok {
t.Errorf("Incorrect return v %q != \"yo3\" || ok %v != true", v, ok)
}
n1.Reset()
reset(n1)
if v, ok := n1.String("test1"); v != "" || ok {
if v, ok, err := n1.String("test1"); err != nil {
t.Error("Unexpected error:", err)
} else if v != "" || ok {
t.Errorf("Incorrect return v %q != \"\" || ok %v != false", v, ok)
}
if v, ok := n1.String("test2"); v != "" || ok {
if v, ok, err := n1.String("test2"); err != nil {
t.Error("Unexpected error:", err)
} else if v != "" || ok {
t.Errorf("Incorrect return v %q != \"\" || ok %v != false", v, ok)
}
if v, ok := n1.String("test3"); v != "" || ok {
if v, ok, err := n1.String("test3"); err != nil {
t.Error("Unexpected error:", err)
} else if v != "" || ok {
t.Errorf("Incorrect return v %q != \"\" || ok %v != false", v, ok)
}
}
// reset removes all entries in this namespace.
func reset(n *NamespacedKV) {
tr, err := n.db.NewWriteTransaction()
if err != nil {
return
}
defer tr.Release()
it, err := tr.NewPrefixIterator(n.prefix)
if err != nil {
return
}
for it.Next() {
_ = tr.Delete(it.Key())
}
it.Release()
_ = tr.Commit()
}

View File

@@ -11,7 +11,6 @@ import (
"strings"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syndtr/goleveldb/leveldb/util"
)
// List of all dbVersion to dbMinSyncthingVersion pairs for convenience
@@ -39,22 +38,27 @@ func (e databaseDowngradeError) Error() string {
return fmt.Sprintf("Syncthing %s required", e.minSyncthingVersion)
}
func UpdateSchema(ll *Lowlevel) error {
updater := &schemaUpdater{newInstance(ll)}
func UpdateSchema(db *Lowlevel) error {
updater := &schemaUpdater{db}
return updater.updateSchema()
}
type schemaUpdater struct {
*instance
*Lowlevel
}
func (db *schemaUpdater) updateSchema() error {
miscDB := NewMiscDataNamespace(db.Lowlevel)
prevVersion, _ := miscDB.Int64("dbVersion")
prevVersion, _, err := miscDB.Int64("dbVersion")
if err != nil {
return err
}
if prevVersion > dbVersion {
err := databaseDowngradeError{}
if minSyncthingVersion, ok := miscDB.String("dbMinSyncthingVersion"); ok {
if minSyncthingVersion, ok, dbErr := miscDB.String("dbMinSyncthingVersion"); dbErr != nil {
return dbErr
} else if ok {
err.minSyncthingVersion = minSyncthingVersion
}
return err
@@ -65,36 +69,58 @@ func (db *schemaUpdater) updateSchema() error {
}
if prevVersion < 1 {
db.updateSchema0to1()
if err := db.updateSchema0to1(); err != nil {
return err
}
}
if prevVersion < 2 {
db.updateSchema1to2()
if err := db.updateSchema1to2(); err != nil {
return err
}
}
if prevVersion < 3 {
db.updateSchema2to3()
if err := db.updateSchema2to3(); err != nil {
return err
}
}
// This update fixes problems existing in versions 3 and 4
if prevVersion == 3 || prevVersion == 4 {
db.updateSchemaTo5()
if err := db.updateSchemaTo5(); err != nil {
return err
}
}
if prevVersion < 6 {
db.updateSchema5to6()
if err := db.updateSchema5to6(); err != nil {
return err
}
}
if prevVersion < 7 {
db.updateSchema6to7()
if err := db.updateSchema6to7(); err != nil {
return err
}
}
miscDB.PutInt64("dbVersion", dbVersion)
miscDB.PutString("dbMinSyncthingVersion", dbMinSyncthingVersion)
if err := miscDB.PutInt64("dbVersion", dbVersion); err != nil {
return err
}
if err := miscDB.PutString("dbMinSyncthingVersion", dbMinSyncthingVersion); err != nil {
return err
}
return nil
}
func (db *schemaUpdater) updateSchema0to1() {
t := db.newReadWriteTransaction()
func (db *schemaUpdater) updateSchema0to1() error {
t, err := db.newReadWriteTransaction()
if err != nil {
return err
}
defer t.close()
dbi := t.NewIterator(util.BytesPrefix([]byte{KeyTypeDevice}), nil)
dbi, err := t.NewPrefixIterator([]byte{KeyTypeDevice})
if err != nil {
return err
}
defer dbi.Release()
symlinkConv := 0
@@ -104,18 +130,20 @@ func (db *schemaUpdater) updateSchema0to1() {
var gk, buf []byte
for dbi.Next() {
t.checkFlush()
folder, ok := db.keyer.FolderFromDeviceFileKey(dbi.Key())
if !ok {
// not having the folder in the index is bad; delete and continue
t.Delete(dbi.Key())
if err := t.Delete(dbi.Key()); err != nil {
return err
}
continue
}
device, ok := db.keyer.DeviceFromDeviceFileKey(dbi.Key())
if !ok {
// not having the device in the index is bad; delete and continue
t.Delete(dbi.Key())
if err := t.Delete(dbi.Key()); err != nil {
return err
}
continue
}
name := db.keyer.NameFromDeviceFileKey(dbi.Key())
@@ -125,9 +153,17 @@ func (db *schemaUpdater) updateSchema0to1() {
if _, ok := changedFolders[string(folder)]; !ok {
changedFolders[string(folder)] = struct{}{}
}
gk = db.keyer.GenerateGlobalVersionKey(gk, folder, name)
buf = t.removeFromGlobal(gk, buf, folder, device, nil, nil)
t.Delete(dbi.Key())
gk, err = db.keyer.GenerateGlobalVersionKey(gk, folder, name)
if err != nil {
return err
}
buf, err = t.removeFromGlobal(gk, buf, folder, device, nil, nil)
if err != nil {
return err
}
if err := t.Delete(dbi.Key()); err != nil {
return err
}
continue
}
@@ -147,14 +183,21 @@ func (db *schemaUpdater) updateSchema0to1() {
if err != nil {
panic("can't happen: " + err.Error())
}
t.Put(dbi.Key(), bs)
if err := t.Put(dbi.Key(), bs); err != nil {
return err
}
symlinkConv++
}
// Add invalid files to global list
if f.IsInvalid() {
gk = db.keyer.GenerateGlobalVersionKey(gk, folder, name)
if buf, ok = t.updateGlobal(gk, buf, folder, device, f, meta); ok {
gk, err = db.keyer.GenerateGlobalVersionKey(gk, folder, name)
if err != nil {
return err
}
if buf, ok, err = t.updateGlobal(gk, buf, folder, device, f, meta); err != nil {
return err
} else if ok {
if _, ok = changedFolders[string(folder)]; !ok {
changedFolders[string(folder)] = struct{}{}
}
@@ -164,86 +207,139 @@ func (db *schemaUpdater) updateSchema0to1() {
}
for folder := range changedFolders {
db.dropFolderMeta([]byte(folder))
if err := db.dropFolderMeta([]byte(folder)); err != nil {
return err
}
}
return t.commit()
}
// updateSchema1to2 introduces a sequenceKey->deviceKey bucket for local items
// to allow iteration in sequence order (simplifies sending indexes).
func (db *schemaUpdater) updateSchema1to2() {
t := db.newReadWriteTransaction()
func (db *schemaUpdater) updateSchema1to2() error {
t, err := db.newReadWriteTransaction()
if err != nil {
return err
}
defer t.close()
var sk []byte
var dk []byte
for _, folderStr := range db.ListFolders() {
folder := []byte(folderStr)
db.withHave(folder, protocol.LocalDeviceID[:], nil, true, func(f FileIntf) bool {
sk = db.keyer.GenerateSequenceKey(sk, folder, f.SequenceNo())
dk = db.keyer.GenerateDeviceFileKey(dk, folder, protocol.LocalDeviceID[:], []byte(f.FileName()))
t.Put(sk, dk)
t.checkFlush()
return true
var putErr error
err := db.withHave(folder, protocol.LocalDeviceID[:], nil, true, func(f FileIntf) bool {
sk, putErr = db.keyer.GenerateSequenceKey(sk, folder, f.SequenceNo())
if putErr != nil {
return false
}
dk, putErr = db.keyer.GenerateDeviceFileKey(dk, folder, protocol.LocalDeviceID[:], []byte(f.FileName()))
if putErr != nil {
return false
}
putErr = t.Put(sk, dk)
return putErr == nil
})
if putErr != nil {
return putErr
}
if err != nil {
return err
}
}
return t.commit()
}
// updateSchema2to3 introduces a needKey->nil bucket for locally needed files.
func (db *schemaUpdater) updateSchema2to3() {
t := db.newReadWriteTransaction()
func (db *schemaUpdater) updateSchema2to3() error {
t, err := db.newReadWriteTransaction()
if err != nil {
return err
}
defer t.close()
var nk []byte
var dk []byte
for _, folderStr := range db.ListFolders() {
folder := []byte(folderStr)
db.withGlobal(folder, nil, true, func(f FileIntf) bool {
var putErr error
err := db.withGlobal(folder, nil, true, func(f FileIntf) bool {
name := []byte(f.FileName())
dk = db.keyer.GenerateDeviceFileKey(dk, folder, protocol.LocalDeviceID[:], name)
dk, putErr = db.keyer.GenerateDeviceFileKey(dk, folder, protocol.LocalDeviceID[:], name)
if putErr != nil {
return false
}
var v protocol.Vector
haveFile, ok := t.getFileTrunc(dk, true)
haveFile, ok, err := t.getFileTrunc(dk, true)
if err != nil {
putErr = err
return false
}
if ok {
v = haveFile.FileVersion()
}
if !need(f, ok, v) {
return true
}
nk = t.keyer.GenerateNeedFileKey(nk, folder, []byte(f.FileName()))
t.Put(nk, nil)
t.checkFlush()
return true
nk, putErr = t.keyer.GenerateNeedFileKey(nk, folder, []byte(f.FileName()))
if putErr != nil {
return false
}
putErr = t.Put(nk, nil)
return putErr == nil
})
if putErr != nil {
return putErr
}
if err != nil {
return err
}
}
return t.commit()
}
// updateSchemaTo5 resets the need bucket due to bugs existing in the v0.14.49
// release candidates (dbVersion 3 and 4)
// https://github.com/syncthing/syncthing/issues/5007
// https://github.com/syncthing/syncthing/issues/5053
func (db *schemaUpdater) updateSchemaTo5() {
t := db.newReadWriteTransaction()
func (db *schemaUpdater) updateSchemaTo5() error {
t, err := db.newReadWriteTransaction()
if err != nil {
return err
}
var nk []byte
for _, folderStr := range db.ListFolders() {
nk = db.keyer.GenerateNeedFileKey(nk, []byte(folderStr), nil)
t.deleteKeyPrefix(nk[:keyPrefixLen+keyFolderLen])
nk, err = db.keyer.GenerateNeedFileKey(nk, []byte(folderStr), nil)
if err != nil {
return err
}
if err := t.deleteKeyPrefix(nk[:keyPrefixLen+keyFolderLen]); err != nil {
return err
}
}
if err := t.commit(); err != nil {
return err
}
t.close()
db.updateSchema2to3()
return db.updateSchema2to3()
}
func (db *schemaUpdater) updateSchema5to6() {
func (db *schemaUpdater) updateSchema5to6() error {
// For every local file with the Invalid bit set, clear the Invalid bit and
// set LocalFlags = FlagLocalIgnored.
t := db.newReadWriteTransaction()
t, err := db.newReadWriteTransaction()
if err != nil {
return err
}
defer t.close()
var dk []byte
for _, folderStr := range db.ListFolders() {
folder := []byte(folderStr)
db.withHave(folder, protocol.LocalDeviceID[:], nil, false, func(f FileIntf) bool {
var putErr error
err := db.withHave(folder, protocol.LocalDeviceID[:], nil, false, func(f FileIntf) bool {
if !f.IsInvalid() {
return true
}
@@ -253,19 +349,31 @@ func (db *schemaUpdater) updateSchema5to6() {
fi.LocalFlags = protocol.FlagLocalIgnored
bs, _ := fi.Marshal()
dk = db.keyer.GenerateDeviceFileKey(dk, folder, protocol.LocalDeviceID[:], []byte(fi.Name))
t.Put(dk, bs)
dk, putErr = db.keyer.GenerateDeviceFileKey(dk, folder, protocol.LocalDeviceID[:], []byte(fi.Name))
if putErr != nil {
return false
}
putErr = t.Put(dk, bs)
t.checkFlush()
return true
return putErr == nil
})
if putErr != nil {
return putErr
}
if err != nil {
return err
}
}
return t.commit()
}
// updateSchema6to7 checks whether all currently locally needed files are really
// needed and removes them if not.
func (db *schemaUpdater) updateSchema6to7() {
t := db.newReadWriteTransaction()
func (db *schemaUpdater) updateSchema6to7() error {
t, err := db.newReadWriteTransaction()
if err != nil {
return err
}
defer t.close()
var gk []byte
@@ -273,15 +381,24 @@ func (db *schemaUpdater) updateSchema6to7() {
for _, folderStr := range db.ListFolders() {
folder := []byte(folderStr)
db.withNeedLocal(folder, false, func(f FileIntf) bool {
var delErr error
err := db.withNeedLocal(folder, false, func(f FileIntf) bool {
name := []byte(f.FileName())
global := f.(protocol.FileInfo)
gk = db.keyer.GenerateGlobalVersionKey(gk, folder, name)
svl, err := t.Get(gk, nil)
gk, delErr = db.keyer.GenerateGlobalVersionKey(gk, folder, name)
if delErr != nil {
return false
}
svl, err := t.Get(gk)
if err != nil {
// If there is no global list, we hardly need it.
t.Delete(t.keyer.GenerateNeedFileKey(nk, folder, name))
return true
key, err := t.keyer.GenerateNeedFileKey(nk, folder, name)
if err != nil {
delErr = err
return false
}
delErr = t.Delete(key)
return delErr == nil
}
var fl VersionList
err = fl.Unmarshal(svl)
@@ -291,9 +408,18 @@ func (db *schemaUpdater) updateSchema6to7() {
return true
}
if localFV, haveLocalFV := fl.Get(protocol.LocalDeviceID[:]); !need(global, haveLocalFV, localFV.Version) {
t.Delete(t.keyer.GenerateNeedFileKey(nk, folder, name))
key, err := t.keyer.GenerateNeedFileKey(nk, folder, name)
if err != nil {
delErr = err
return false
}
delErr = t.Delete(key)
}
return true
return delErr == nil
})
if err != nil {
return err
}
}
return t.commit()
}

View File

@@ -16,17 +16,17 @@ import (
"os"
"time"
"github.com/syncthing/syncthing/lib/db/backend"
"github.com/syncthing/syncthing/lib/fs"
"github.com/syncthing/syncthing/lib/osutil"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/sync"
"github.com/syndtr/goleveldb/leveldb/util"
)
type FileSet struct {
folder string
fs fs.Filesystem
db *instance
db *Lowlevel
meta *metadataTracker
updateMutex sync.Mutex // protects database updates and the corresponding metadata changes
@@ -51,6 +51,10 @@ type FileIntf interface {
SequenceNo() int64
BlockSize() int
FileVersion() protocol.Vector
FileType() protocol.FileInfoType
FilePermissions() uint32
FileModifiedBy() protocol.ShortID
ModTime() time.Time
}
// The Iterator is called with either a protocol.FileInfo or a
@@ -66,9 +70,7 @@ func init() {
}
}
func NewFileSet(folder string, fs fs.Filesystem, ll *Lowlevel) *FileSet {
db := newInstance(ll)
func NewFileSet(folder string, fs fs.Filesystem, db *Lowlevel) *FileSet {
var s = FileSet{
folder: folder,
fs: fs,
@@ -79,29 +81,42 @@ func NewFileSet(folder string, fs fs.Filesystem, ll *Lowlevel) *FileSet {
if err := s.meta.fromDB(db, []byte(folder)); err != nil {
l.Infof("No stored folder metadata for %q: recalculating", folder)
s.recalcCounts()
if err := s.recalcCounts(); backend.IsClosed(err) {
return nil
} else if err != nil {
panic(err)
}
} else if age := time.Since(s.meta.Created()); age > databaseRecheckInterval {
l.Infof("Stored folder metadata for %q is %v old; recalculating", folder, age)
s.recalcCounts()
if err := s.recalcCounts(); backend.IsClosed(err) {
return nil
} else if err != nil {
panic(err)
}
}
return &s
}
func (s *FileSet) recalcCounts() {
func (s *FileSet) recalcCounts() error {
s.meta = newMetadataTracker()
s.db.checkGlobals([]byte(s.folder), s.meta)
if err := s.db.checkGlobals([]byte(s.folder), s.meta); err != nil {
return err
}
var deviceID protocol.DeviceID
s.db.withAllFolderTruncated([]byte(s.folder), func(device []byte, f FileInfoTruncated) bool {
err := s.db.withAllFolderTruncated([]byte(s.folder), func(device []byte, f FileInfoTruncated) bool {
copy(deviceID[:], device)
s.meta.addFile(deviceID, f)
return true
})
if err != nil {
return err
}
s.meta.SetCreated()
s.meta.toDB(s.db, []byte(s.folder))
return s.meta.toDB(s.db, []byte(s.folder))
}
func (s *FileSet) Drop(device protocol.DeviceID) {
@@ -110,7 +125,11 @@ func (s *FileSet) Drop(device protocol.DeviceID) {
s.updateMutex.Lock()
defer s.updateMutex.Unlock()
s.db.dropDeviceFolder(device[:], []byte(s.folder), s.meta)
if err := s.db.dropDeviceFolder(device[:], []byte(s.folder), s.meta); backend.IsClosed(err) {
return
} else if err != nil {
panic(err)
}
if device == protocol.LocalDeviceID {
s.meta.resetCounts(device)
@@ -127,7 +146,11 @@ func (s *FileSet) Drop(device protocol.DeviceID) {
s.meta.resetAll(device)
}
s.meta.toDB(s.db, []byte(s.folder))
if err := s.meta.toDB(s.db, []byte(s.folder)); backend.IsClosed(err) {
return
} else if err != nil {
panic(err)
}
}
func (s *FileSet) Update(device protocol.DeviceID, fs []protocol.FileInfo) {
@@ -141,73 +164,110 @@ func (s *FileSet) Update(device protocol.DeviceID, fs []protocol.FileInfo) {
s.updateMutex.Lock()
defer s.updateMutex.Unlock()
defer s.meta.toDB(s.db, []byte(s.folder))
defer func() {
if err := s.meta.toDB(s.db, []byte(s.folder)); err != nil && !backend.IsClosed(err) {
panic(err)
}
}()
if device == protocol.LocalDeviceID {
// For the local device we have a bunch of metadata to track.
s.db.updateLocalFiles([]byte(s.folder), fs, s.meta)
if err := s.db.updateLocalFiles([]byte(s.folder), fs, s.meta); err != nil && !backend.IsClosed(err) {
panic(err)
}
return
}
// Easy case, just update the files and we're done.
s.db.updateRemoteFiles([]byte(s.folder), device[:], fs, s.meta)
if err := s.db.updateRemoteFiles([]byte(s.folder), device[:], fs, s.meta); err != nil && !backend.IsClosed(err) {
panic(err)
}
}
func (s *FileSet) WithNeed(device protocol.DeviceID, fn Iterator) {
l.Debugf("%s WithNeed(%v)", s.folder, device)
s.db.withNeed([]byte(s.folder), device[:], false, nativeFileIterator(fn))
if err := s.db.withNeed([]byte(s.folder), device[:], false, nativeFileIterator(fn)); err != nil && !backend.IsClosed(err) {
panic(err)
}
}
func (s *FileSet) WithNeedTruncated(device protocol.DeviceID, fn Iterator) {
l.Debugf("%s WithNeedTruncated(%v)", s.folder, device)
s.db.withNeed([]byte(s.folder), device[:], true, nativeFileIterator(fn))
if err := s.db.withNeed([]byte(s.folder), device[:], true, nativeFileIterator(fn)); err != nil && !backend.IsClosed(err) {
panic(err)
}
}
func (s *FileSet) WithHave(device protocol.DeviceID, fn Iterator) {
l.Debugf("%s WithHave(%v)", s.folder, device)
s.db.withHave([]byte(s.folder), device[:], nil, false, nativeFileIterator(fn))
if err := s.db.withHave([]byte(s.folder), device[:], nil, false, nativeFileIterator(fn)); err != nil && !backend.IsClosed(err) {
panic(err)
}
}
func (s *FileSet) WithHaveTruncated(device protocol.DeviceID, fn Iterator) {
l.Debugf("%s WithHaveTruncated(%v)", s.folder, device)
s.db.withHave([]byte(s.folder), device[:], nil, true, nativeFileIterator(fn))
if err := s.db.withHave([]byte(s.folder), device[:], nil, true, nativeFileIterator(fn)); err != nil && !backend.IsClosed(err) {
panic(err)
}
}
func (s *FileSet) WithHaveSequence(startSeq int64, fn Iterator) {
l.Debugf("%s WithHaveSequence(%v)", s.folder, startSeq)
s.db.withHaveSequence([]byte(s.folder), startSeq, nativeFileIterator(fn))
if err := s.db.withHaveSequence([]byte(s.folder), startSeq, nativeFileIterator(fn)); err != nil && !backend.IsClosed(err) {
panic(err)
}
}
// Except for an item with a path equal to prefix, only children of prefix are iterated.
// E.g. for prefix "dir", "dir/file" is iterated, but "dir.file" is not.
func (s *FileSet) WithPrefixedHaveTruncated(device protocol.DeviceID, prefix string, fn Iterator) {
l.Debugf(`%s WithPrefixedHaveTruncated(%v, "%v")`, s.folder, device, prefix)
s.db.withHave([]byte(s.folder), device[:], []byte(osutil.NormalizedFilename(prefix)), true, nativeFileIterator(fn))
if err := s.db.withHave([]byte(s.folder), device[:], []byte(osutil.NormalizedFilename(prefix)), true, nativeFileIterator(fn)); err != nil && !backend.IsClosed(err) {
panic(err)
}
}
func (s *FileSet) WithGlobal(fn Iterator) {
l.Debugf("%s WithGlobal()", s.folder)
s.db.withGlobal([]byte(s.folder), nil, false, nativeFileIterator(fn))
if err := s.db.withGlobal([]byte(s.folder), nil, false, nativeFileIterator(fn)); err != nil && !backend.IsClosed(err) {
panic(err)
}
}
func (s *FileSet) WithGlobalTruncated(fn Iterator) {
l.Debugf("%s WithGlobalTruncated()", s.folder)
s.db.withGlobal([]byte(s.folder), nil, true, nativeFileIterator(fn))
if err := s.db.withGlobal([]byte(s.folder), nil, true, nativeFileIterator(fn)); err != nil && !backend.IsClosed(err) {
panic(err)
}
}
// Except for an item with a path equal to prefix, only children of prefix are iterated.
// E.g. for prefix "dir", "dir/file" is iterated, but "dir.file" is not.
func (s *FileSet) WithPrefixedGlobalTruncated(prefix string, fn Iterator) {
l.Debugf(`%s WithPrefixedGlobalTruncated("%v")`, s.folder, prefix)
s.db.withGlobal([]byte(s.folder), []byte(osutil.NormalizedFilename(prefix)), true, nativeFileIterator(fn))
if err := s.db.withGlobal([]byte(s.folder), []byte(osutil.NormalizedFilename(prefix)), true, nativeFileIterator(fn)); err != nil && !backend.IsClosed(err) {
panic(err)
}
}
func (s *FileSet) Get(device protocol.DeviceID, file string) (protocol.FileInfo, bool) {
f, ok := s.db.getFileDirty([]byte(s.folder), device[:], []byte(osutil.NormalizedFilename(file)))
f, ok, err := s.db.getFileDirty([]byte(s.folder), device[:], []byte(osutil.NormalizedFilename(file)))
if backend.IsClosed(err) {
return protocol.FileInfo{}, false
} else if err != nil {
panic(err)
}
f.Name = osutil.NativeFilename(f.Name)
return f, ok
}
func (s *FileSet) GetGlobal(file string) (protocol.FileInfo, bool) {
fi, ok := s.db.getGlobalDirty([]byte(s.folder), []byte(osutil.NormalizedFilename(file)), false)
fi, ok, err := s.db.getGlobalDirty([]byte(s.folder), []byte(osutil.NormalizedFilename(file)), false)
if backend.IsClosed(err) {
return protocol.FileInfo{}, false
} else if err != nil {
panic(err)
}
if !ok {
return protocol.FileInfo{}, false
}
@@ -217,7 +277,12 @@ func (s *FileSet) GetGlobal(file string) (protocol.FileInfo, bool) {
}
func (s *FileSet) GetGlobalTruncated(file string) (FileInfoTruncated, bool) {
fi, ok := s.db.getGlobalDirty([]byte(s.folder), []byte(osutil.NormalizedFilename(file)), true)
fi, ok, err := s.db.getGlobalDirty([]byte(s.folder), []byte(osutil.NormalizedFilename(file)), true)
if backend.IsClosed(err) {
return FileInfoTruncated{}, false
} else if err != nil {
panic(err)
}
if !ok {
return FileInfoTruncated{}, false
}
@@ -227,7 +292,13 @@ func (s *FileSet) GetGlobalTruncated(file string) (FileInfoTruncated, bool) {
}
func (s *FileSet) Availability(file string) []protocol.DeviceID {
return s.db.availability([]byte(s.folder), []byte(osutil.NormalizedFilename(file)))
av, err := s.db.availability([]byte(s.folder), []byte(osutil.NormalizedFilename(file)))
if backend.IsClosed(err) {
return nil
} else if err != nil {
panic(err)
}
return av
}
func (s *FileSet) Sequence(device protocol.DeviceID) int64 {
@@ -251,11 +322,21 @@ func (s *FileSet) GlobalSize() Counts {
}
func (s *FileSet) IndexID(device protocol.DeviceID) protocol.IndexID {
id := s.db.getIndexID(device[:], []byte(s.folder))
id, err := s.db.getIndexID(device[:], []byte(s.folder))
if backend.IsClosed(err) {
return 0
} else if err != nil {
panic(err)
}
if id == 0 && device == protocol.LocalDeviceID {
// No index ID set yet. We create one now.
id = protocol.NewIndexID()
s.db.setIndexID(device[:], []byte(s.folder), id)
err := s.db.setIndexID(device[:], []byte(s.folder), id)
if backend.IsClosed(err) {
return 0
} else if err != nil {
panic(err)
}
}
return id
}
@@ -264,12 +345,19 @@ func (s *FileSet) SetIndexID(device protocol.DeviceID, id protocol.IndexID) {
if device == protocol.LocalDeviceID {
panic("do not explicitly set index ID for local device")
}
s.db.setIndexID(device[:], []byte(s.folder), id)
if err := s.db.setIndexID(device[:], []byte(s.folder), id); err != nil && !backend.IsClosed(err) {
panic(err)
}
}
func (s *FileSet) MtimeFS() *fs.MtimeFS {
prefix := s.db.keyer.GenerateMtimesKey(nil, []byte(s.folder))
kv := NewNamespacedKV(s.db.Lowlevel, string(prefix))
prefix, err := s.db.keyer.GenerateMtimesKey(nil, []byte(s.folder))
if backend.IsClosed(err) {
return nil
} else if err != nil {
panic(err)
}
kv := NewNamespacedKV(s.db, string(prefix))
return fs.NewMtimeFS(s.fs, kv)
}
@@ -279,23 +367,39 @@ func (s *FileSet) ListDevices() []protocol.DeviceID {
// DropFolder clears out all information related to the given folder from the
// database.
func DropFolder(ll *Lowlevel, folder string) {
db := newInstance(ll)
db.dropFolder([]byte(folder))
db.dropMtimes([]byte(folder))
db.dropFolderMeta([]byte(folder))
// Also clean out the folder ID mapping.
db.folderIdx.Delete([]byte(folder))
func DropFolder(db *Lowlevel, folder string) {
droppers := []func([]byte) error{
db.dropFolder,
db.dropMtimes,
db.dropFolderMeta,
db.folderIdx.Delete,
}
for _, drop := range droppers {
if err := drop([]byte(folder)); backend.IsClosed(err) {
return
} else if err != nil {
panic(err)
}
}
}
// DropDeltaIndexIDs removes all delta index IDs from the database.
// This will cause a full index transmission on the next connection.
func DropDeltaIndexIDs(db *Lowlevel) {
dbi := db.NewIterator(util.BytesPrefix([]byte{KeyTypeIndexID}), nil)
dbi, err := db.NewPrefixIterator([]byte{KeyTypeIndexID})
if backend.IsClosed(err) {
return
} else if err != nil {
panic(err)
}
defer dbi.Release()
for dbi.Next() {
db.Delete(dbi.Key(), nil)
if err := db.Delete(dbi.Key()); err != nil && !backend.IsClosed(err) {
panic(err)
}
}
if err := dbi.Error(); err != nil && !backend.IsClosed(err) {
panic(err)
}
}

View File

@@ -17,6 +17,7 @@ import (
"github.com/d4l3k/messagediff"
"github.com/syncthing/syncthing/lib/db"
"github.com/syncthing/syncthing/lib/db/backend"
"github.com/syncthing/syncthing/lib/fs"
"github.com/syncthing/syncthing/lib/protocol"
)
@@ -117,7 +118,7 @@ func (l fileList) String() string {
}
func TestGlobalSet(t *testing.T) {
ldb := db.OpenMemory()
ldb := db.NewLowlevel(backend.OpenMemory())
m := db.NewFileSet("test", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
@@ -332,7 +333,7 @@ func TestGlobalSet(t *testing.T) {
}
func TestNeedWithInvalid(t *testing.T) {
ldb := db.OpenMemory()
ldb := db.NewLowlevel(backend.OpenMemory())
s := db.NewFileSet("test", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
@@ -369,7 +370,7 @@ func TestNeedWithInvalid(t *testing.T) {
}
func TestUpdateToInvalid(t *testing.T) {
ldb := db.OpenMemory()
ldb := db.NewLowlevel(backend.OpenMemory())
folder := "test"
s := db.NewFileSet(folder, fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
@@ -425,7 +426,7 @@ func TestUpdateToInvalid(t *testing.T) {
}
func TestInvalidAvailability(t *testing.T) {
ldb := db.OpenMemory()
ldb := db.NewLowlevel(backend.OpenMemory())
s := db.NewFileSet("test", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
@@ -463,7 +464,7 @@ func TestInvalidAvailability(t *testing.T) {
}
func TestGlobalReset(t *testing.T) {
ldb := db.OpenMemory()
ldb := db.NewLowlevel(backend.OpenMemory())
m := db.NewFileSet("test", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
@@ -501,7 +502,7 @@ func TestGlobalReset(t *testing.T) {
}
func TestNeed(t *testing.T) {
ldb := db.OpenMemory()
ldb := db.NewLowlevel(backend.OpenMemory())
m := db.NewFileSet("test", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
@@ -539,7 +540,7 @@ func TestNeed(t *testing.T) {
}
func TestSequence(t *testing.T) {
ldb := db.OpenMemory()
ldb := db.NewLowlevel(backend.OpenMemory())
m := db.NewFileSet("test", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
@@ -569,7 +570,7 @@ func TestSequence(t *testing.T) {
}
func TestListDropFolder(t *testing.T) {
ldb := db.OpenMemory()
ldb := db.NewLowlevel(backend.OpenMemory())
s0 := db.NewFileSet("test0", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
local1 := []protocol.FileInfo{
@@ -619,7 +620,7 @@ func TestListDropFolder(t *testing.T) {
}
func TestGlobalNeedWithInvalid(t *testing.T) {
ldb := db.OpenMemory()
ldb := db.NewLowlevel(backend.OpenMemory())
s := db.NewFileSet("test1", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
@@ -660,7 +661,7 @@ func TestGlobalNeedWithInvalid(t *testing.T) {
}
func TestLongPath(t *testing.T) {
ldb := db.OpenMemory()
ldb := db.NewLowlevel(backend.OpenMemory())
s := db.NewFileSet("test", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
@@ -671,7 +672,7 @@ func TestLongPath(t *testing.T) {
name := b.String() // 5000 characters
local := []protocol.FileInfo{
{Name: string(name), Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}},
{Name: name, Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}},
}
replace(s, protocol.LocalDeviceID, local)
@@ -686,39 +687,6 @@ func TestLongPath(t *testing.T) {
}
}
func TestCommitted(t *testing.T) {
// Verify that the Committed counter increases when we change things and
// doesn't increase when we don't.
ldb := db.OpenMemory()
s := db.NewFileSet("test", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
local := []protocol.FileInfo{
{Name: string("file"), Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}},
}
// Adding a file should increase the counter
c0 := ldb.Committed()
replace(s, protocol.LocalDeviceID, local)
c1 := ldb.Committed()
if c1 <= c0 {
t.Errorf("committed data didn't increase; %d <= %d", c1, c0)
}
// Updating with something identical should not do anything
s.Update(protocol.LocalDeviceID, local)
c2 := ldb.Committed()
if c2 > c1 {
t.Errorf("replace with same contents should do nothing but %d > %d", c2, c1)
}
}
func BenchmarkUpdateOneFile(b *testing.B) {
local0 := fileList{
protocol.FileInfo{Name: "a", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}, Blocks: genBlocks(1)},
@@ -729,10 +697,11 @@ func BenchmarkUpdateOneFile(b *testing.B) {
protocol.FileInfo{Name: "zajksdhaskjdh/askjdhaskjdashkajshd/kasjdhaskjdhaskdjhaskdjash/dkjashdaksjdhaskdjahskdjh", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1000}}}, Blocks: genBlocks(8)},
}
ldb, err := db.Open("testdata/benchmarkupdate.db", db.TuningAuto)
be, err := backend.Open("testdata/benchmarkupdate.db", backend.TuningAuto)
if err != nil {
b.Fatal(err)
}
ldb := db.NewLowlevel(be)
defer func() {
ldb.Close()
os.RemoveAll("testdata/benchmarkupdate.db")
@@ -751,7 +720,7 @@ func BenchmarkUpdateOneFile(b *testing.B) {
}
func TestIndexID(t *testing.T) {
ldb := db.OpenMemory()
ldb := db.NewLowlevel(backend.OpenMemory())
s := db.NewFileSet("test", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
@@ -783,7 +752,7 @@ func TestIndexID(t *testing.T) {
}
func TestDropFiles(t *testing.T) {
ldb := db.OpenMemory()
ldb := db.NewLowlevel(backend.OpenMemory())
m := db.NewFileSet("test", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
@@ -846,7 +815,7 @@ func TestDropFiles(t *testing.T) {
}
func TestIssue4701(t *testing.T) {
ldb := db.OpenMemory()
ldb := db.NewLowlevel(backend.OpenMemory())
s := db.NewFileSet("test", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
@@ -887,7 +856,7 @@ func TestIssue4701(t *testing.T) {
}
func TestWithHaveSequence(t *testing.T) {
ldb := db.OpenMemory()
ldb := db.NewLowlevel(backend.OpenMemory())
folder := "test"
s := db.NewFileSet(folder, fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
@@ -915,14 +884,14 @@ func TestWithHaveSequence(t *testing.T) {
func TestStressWithHaveSequence(t *testing.T) {
// This races two loops against each other: one that contiously does
// updates, and one that continously does sequence walks. The test fails
// updates, and one that continuously does sequence walks. The test fails
// if the sequence walker sees a discontinuity.
if testing.Short() {
t.Skip("Takes a long time")
}
ldb := db.OpenMemory()
ldb := db.NewLowlevel(backend.OpenMemory())
folder := "test"
s := db.NewFileSet(folder, fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
@@ -945,7 +914,7 @@ func TestStressWithHaveSequence(t *testing.T) {
close(done)
}()
var prevSeq int64 = 0
var prevSeq int64
loop:
for {
select {
@@ -964,7 +933,7 @@ loop:
}
func TestIssue4925(t *testing.T) {
ldb := db.OpenMemory()
ldb := db.NewLowlevel(backend.OpenMemory())
folder := "test"
s := db.NewFileSet(folder, fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
@@ -990,7 +959,7 @@ func TestIssue4925(t *testing.T) {
}
func TestMoveGlobalBack(t *testing.T) {
ldb := db.OpenMemory()
ldb := db.NewLowlevel(backend.OpenMemory())
folder := "test"
file := "foo"
@@ -1054,7 +1023,7 @@ func TestMoveGlobalBack(t *testing.T) {
// needed files.
// https://github.com/syncthing/syncthing/issues/5007
func TestIssue5007(t *testing.T) {
ldb := db.OpenMemory()
ldb := db.NewLowlevel(backend.OpenMemory())
folder := "test"
file := "foo"
@@ -1081,7 +1050,7 @@ func TestIssue5007(t *testing.T) {
// TestNeedDeleted checks that a file that doesn't exist locally isn't needed
// when the global file is deleted.
func TestNeedDeleted(t *testing.T) {
ldb := db.OpenMemory()
ldb := db.NewLowlevel(backend.OpenMemory())
folder := "test"
file := "foo"
@@ -1115,7 +1084,7 @@ func TestNeedDeleted(t *testing.T) {
}
func TestReceiveOnlyAccounting(t *testing.T) {
ldb := db.OpenMemory()
ldb := db.NewLowlevel(backend.OpenMemory())
folder := "test"
s := db.NewFileSet(folder, fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
@@ -1219,7 +1188,7 @@ func TestReceiveOnlyAccounting(t *testing.T) {
}
func TestNeedAfterUnignore(t *testing.T) {
ldb := db.OpenMemory()
ldb := db.NewLowlevel(backend.OpenMemory())
folder := "test"
file := "foo"
@@ -1251,7 +1220,7 @@ func TestNeedAfterUnignore(t *testing.T) {
func TestRemoteInvalidNotAccounted(t *testing.T) {
// Remote files with the invalid bit should not count.
ldb := db.OpenMemory()
ldb := db.NewLowlevel(backend.OpenMemory())
s := db.NewFileSet("test", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
files := []protocol.FileInfo{
@@ -1270,7 +1239,7 @@ func TestRemoteInvalidNotAccounted(t *testing.T) {
}
func TestNeedWithNewerInvalid(t *testing.T) {
ldb := db.OpenMemory()
ldb := db.NewLowlevel(backend.OpenMemory())
s := db.NewFileSet("default", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
@@ -1308,7 +1277,7 @@ func TestNeedWithNewerInvalid(t *testing.T) {
}
func TestNeedAfterDeviceRemove(t *testing.T) {
ldb := db.OpenMemory()
ldb := db.NewLowlevel(backend.OpenMemory())
file := "foo"
s := db.NewFileSet("test", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
@@ -1335,7 +1304,7 @@ func TestNeedAfterDeviceRemove(t *testing.T) {
func TestCaseSensitive(t *testing.T) {
// Normal case sensitive lookup should work
ldb := db.OpenMemory()
ldb := db.NewLowlevel(backend.OpenMemory())
s := db.NewFileSet("test", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
local := []protocol.FileInfo{
@@ -1372,7 +1341,7 @@ func TestSequenceIndex(t *testing.T) {
// Set up a db and a few files that we will manipulate.
ldb := db.OpenMemory()
ldb := db.NewLowlevel(backend.OpenMemory())
s := db.NewFileSet("test", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
local := []protocol.FileInfo{
@@ -1462,6 +1431,33 @@ func TestSequenceIndex(t *testing.T) {
}
}
func TestIgnoreAfterReceiveOnly(t *testing.T) {
ldb := db.NewLowlevel(backend.OpenMemory())
file := "foo"
s := db.NewFileSet("test", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), ldb)
fs := fileList{{
Name: file,
Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1}}},
LocalFlags: protocol.FlagLocalReceiveOnly,
}}
s.Update(protocol.LocalDeviceID, fs)
fs[0].LocalFlags = protocol.FlagLocalIgnored
s.Update(protocol.LocalDeviceID, fs)
if f, ok := s.Get(protocol.LocalDeviceID, file); !ok {
t.Error("File missing in db")
} else if f.IsReceiveOnlyChanged() {
t.Error("File is still receive-only changed")
} else if !f.IsIgnored() {
t.Error("File is not ignored")
}
}
func replace(fs *db.FileSet, device protocol.DeviceID, files []protocol.FileInfo) {
fs.Drop(device)
fs.Update(device, files)

View File

@@ -10,16 +10,15 @@ import (
"encoding/binary"
"sort"
"github.com/syncthing/syncthing/lib/db/backend"
"github.com/syncthing/syncthing/lib/sync"
"github.com/syndtr/goleveldb/leveldb"
"github.com/syndtr/goleveldb/leveldb/util"
)
// A smallIndex is an in memory bidirectional []byte to uint32 map. It gives
// fast lookups in both directions and persists to the database. Don't use for
// storing more items than fit comfortably in RAM.
type smallIndex struct {
db *leveldb.DB
db backend.Backend
prefix []byte
id2val map[uint32]string
val2id map[string]uint32
@@ -27,7 +26,7 @@ type smallIndex struct {
mut sync.Mutex
}
func newSmallIndex(db *leveldb.DB, prefix []byte) *smallIndex {
func newSmallIndex(db backend.Backend, prefix []byte) *smallIndex {
idx := &smallIndex{
db: db,
prefix: prefix,
@@ -42,7 +41,10 @@ func newSmallIndex(db *leveldb.DB, prefix []byte) *smallIndex {
// load iterates over the prefix space in the database and populates the in
// memory maps.
func (i *smallIndex) load() {
it := i.db.NewIterator(util.BytesPrefix(i.prefix), nil)
it, err := i.db.NewPrefixIterator(i.prefix)
if err != nil {
panic("loading small index: " + err.Error())
}
defer it.Release()
for it.Next() {
val := string(it.Value())
@@ -60,7 +62,7 @@ func (i *smallIndex) load() {
// ID returns the index number for the given byte slice, allocating a new one
// and persisting this to the database if necessary.
func (i *smallIndex) ID(val []byte) uint32 {
func (i *smallIndex) ID(val []byte) (uint32, error) {
i.mut.Lock()
// intentionally avoiding defer here as we want this call to be as fast as
// possible in the general case (folder ID already exists). The map lookup
@@ -69,7 +71,7 @@ func (i *smallIndex) ID(val []byte) uint32 {
// here.
if id, ok := i.val2id[string(val)]; ok {
i.mut.Unlock()
return id
return id, nil
}
id := i.nextID
@@ -82,10 +84,13 @@ func (i *smallIndex) ID(val []byte) uint32 {
key := make([]byte, len(i.prefix)+8) // prefix plus uint32 id
copy(key, i.prefix)
binary.BigEndian.PutUint32(key[len(i.prefix):], id)
i.db.Put(key, val, nil)
if err := i.db.Put(key, val); err != nil {
i.mut.Unlock()
return 0, err
}
i.mut.Unlock()
return id
return id, nil
}
// Val returns the value for the given index number, or (nil, false) if there
@@ -101,7 +106,7 @@ func (i *smallIndex) Val(id uint32) ([]byte, bool) {
return []byte(val), true
}
func (i *smallIndex) Delete(val []byte) {
func (i *smallIndex) Delete(val []byte) error {
i.mut.Lock()
defer i.mut.Unlock()
@@ -115,7 +120,9 @@ func (i *smallIndex) Delete(val []byte) {
// Put an empty value into the database. This indicates that the
// entry does not exist any more and prevents the ID from being
// reused in the future.
i.db.Put(key, []byte{}, nil)
if err := i.db.Put(key, []byte{}); err != nil {
return err
}
// Delete reverse mapping.
delete(i.id2val, id)
@@ -123,6 +130,7 @@ func (i *smallIndex) Delete(val []byte) {
// Delete forward mapping.
delete(i.val2id, string(val))
return nil
}
// Values returns the set of values in the index

View File

@@ -6,11 +6,15 @@
package db
import "testing"
import (
"testing"
"github.com/syncthing/syncthing/lib/db/backend"
)
func TestSmallIndex(t *testing.T) {
db := OpenMemory()
idx := newSmallIndex(db.DB, []byte{12, 34})
db := NewLowlevel(backend.OpenMemory())
idx := newSmallIndex(db, []byte{12, 34})
// ID zero should be unallocated
if val, ok := idx.Val(0); ok || val != nil {
@@ -18,7 +22,9 @@ func TestSmallIndex(t *testing.T) {
}
// A new key should get ID zero
if id := idx.ID([]byte("hello")); id != 0 {
if id, err := idx.ID([]byte("hello")); err != nil {
t.Fatal(err)
} else if id != 0 {
t.Fatal("Expected 0, not", id)
}
// Looking up ID zero should work
@@ -30,23 +36,29 @@ func TestSmallIndex(t *testing.T) {
idx.Delete([]byte("hello"))
// Next ID should be one
if id := idx.ID([]byte("key2")); id != 1 {
if id, err := idx.ID([]byte("key2")); err != nil {
t.Fatal(err)
} else if id != 1 {
t.Fatal("Expected 1, not", id)
}
// Now lets create a new index instance based on what's actually serialized to the database.
idx = newSmallIndex(db.DB, []byte{12, 34})
idx = newSmallIndex(db, []byte{12, 34})
// Status should be about the same as before.
if val, ok := idx.Val(0); ok || val != nil {
t.Fatal("Unexpected return for deleted ID 0")
}
if id := idx.ID([]byte("key2")); id != 1 {
if id, err := idx.ID([]byte("key2")); err != nil {
t.Fatal(err)
} else if id != 1 {
t.Fatal("Expected 1, not", id)
}
// Setting "hello" again should get us ID 2, not 0 as it was originally.
if id := idx.ID([]byte("hello")); id != 2 {
if id, err := idx.ID([]byte("hello")); err != nil {
t.Fatal(err)
} else if id != 2 {
t.Fatal("Expected 2, not", id)
}
}

View File

@@ -116,6 +116,17 @@ func (f FileInfoTruncated) FileVersion() protocol.Vector {
return f.Version
}
func (f FileInfoTruncated) FileType() protocol.FileInfoType {
return f.Type
}
func (f FileInfoTruncated) FilePermissions() uint32 {
return f.Permissions
}
func (f FileInfoTruncated) FileModifiedBy() protocol.ShortID {
return f.ModifiedBy
}
func (f FileInfoTruncated) ConvertToIgnoredFileInfo(by protocol.ShortID) protocol.FileInfo {
return protocol.FileInfo{
Name: f.Name,
@@ -164,7 +175,7 @@ func (vl VersionList) String() string {
// update brings the VersionList up to date with file. It returns the updated
// VersionList, a potentially removed old FileVersion and its index, as well as
// the index where the new FileVersion was inserted.
func (vl VersionList) update(folder, device []byte, file protocol.FileInfo, t readOnlyTransaction) (_ VersionList, removedFV FileVersion, removedAt int, insertedAt int) {
func (vl VersionList) update(folder, device []byte, file protocol.FileInfo, t readOnlyTransaction) (_ VersionList, removedFV FileVersion, removedAt int, insertedAt int, err error) {
vl, removedFV, removedAt = vl.pop(device)
nv := FileVersion{
@@ -187,7 +198,7 @@ func (vl VersionList) update(folder, device []byte, file protocol.FileInfo, t re
// The version at this point in the list is equal to or lesser
// ("older") than us. We insert ourselves in front of it.
vl = vl.insertAt(i, nv)
return vl, removedFV, removedAt, i
return vl, removedFV, removedAt, i, nil
case protocol.ConcurrentLesser, protocol.ConcurrentGreater:
// The version at this point is in conflict with us. We must pull
@@ -198,9 +209,11 @@ func (vl VersionList) update(folder, device []byte, file protocol.FileInfo, t re
// to determine the winner.)
//
// A surprise missing file entry here is counted as a win for us.
if of, ok := t.getFile(folder, vl.Versions[i].Device, []byte(file.Name)); !ok || file.WinsConflict(of) {
if of, ok, err := t.getFile(folder, vl.Versions[i].Device, []byte(file.Name)); err != nil {
return vl, removedFV, removedAt, i, err
} else if !ok || file.WinsConflict(of) {
vl = vl.insertAt(i, nv)
return vl, removedFV, removedAt, i
return vl, removedFV, removedAt, i, nil
}
}
}
@@ -208,7 +221,7 @@ func (vl VersionList) update(folder, device []byte, file protocol.FileInfo, t re
// We didn't find a position for an insert above, so append to the end.
vl.Versions = append(vl.Versions, nv)
return vl, removedFV, removedAt, len(vl.Versions) - 1
return vl, removedFV, removedAt, len(vl.Versions) - 1, nil
}
func (vl VersionList) insertAt(i int, v FileVersion) VersionList {

View File

@@ -102,23 +102,23 @@ var xxx_messageInfo_VersionList proto.InternalMessageInfo
// Must be the same as FileInfo but without the blocks field
type FileInfoTruncated struct {
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
Type protocol.FileInfoType `protobuf:"varint,2,opt,name=type,proto3,enum=protocol.FileInfoType" json:"type,omitempty"`
Size int64 `protobuf:"varint,3,opt,name=size,proto3" json:"size,omitempty"`
Permissions uint32 `protobuf:"varint,4,opt,name=permissions,proto3" json:"permissions,omitempty"`
ModifiedS int64 `protobuf:"varint,5,opt,name=modified_s,json=modifiedS,proto3" json:"modified_s,omitempty"`
ModifiedNs int32 `protobuf:"varint,11,opt,name=modified_ns,json=modifiedNs,proto3" json:"modified_ns,omitempty"`
ModifiedBy github_com_syncthing_syncthing_lib_protocol.ShortID `protobuf:"varint,12,opt,name=modified_by,json=modifiedBy,proto3,customtype=github.com/syncthing/syncthing/lib/protocol.ShortID" json:"modified_by"`
Deleted bool `protobuf:"varint,6,opt,name=deleted,proto3" json:"deleted,omitempty"`
RawInvalid bool `protobuf:"varint,7,opt,name=invalid,proto3" json:"invalid,omitempty"`
NoPermissions bool `protobuf:"varint,8,opt,name=no_permissions,json=noPermissions,proto3" json:"no_permissions,omitempty"`
Version protocol.Vector `protobuf:"bytes,9,opt,name=version,proto3" json:"version"`
Sequence int64 `protobuf:"varint,10,opt,name=sequence,proto3" json:"sequence,omitempty"`
RawBlockSize int32 `protobuf:"varint,13,opt,name=block_size,json=blockSize,proto3" json:"block_size,omitempty"`
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
Size int64 `protobuf:"varint,3,opt,name=size,proto3" json:"size,omitempty"`
ModifiedS int64 `protobuf:"varint,5,opt,name=modified_s,json=modifiedS,proto3" json:"modified_s,omitempty"`
ModifiedBy github_com_syncthing_syncthing_lib_protocol.ShortID `protobuf:"varint,12,opt,name=modified_by,json=modifiedBy,proto3,customtype=github.com/syncthing/syncthing/lib/protocol.ShortID" json:"modified_by"`
Version protocol.Vector `protobuf:"bytes,9,opt,name=version,proto3" json:"version"`
Sequence int64 `protobuf:"varint,10,opt,name=sequence,proto3" json:"sequence,omitempty"`
// repeated BlockInfo Blocks = 16
SymlinkTarget string `protobuf:"bytes,17,opt,name=symlink_target,json=symlinkTarget,proto3" json:"symlink_target,omitempty"`
SymlinkTarget string `protobuf:"bytes,17,opt,name=symlink_target,json=symlinkTarget,proto3" json:"symlink_target,omitempty"`
Type protocol.FileInfoType `protobuf:"varint,2,opt,name=type,proto3,enum=protocol.FileInfoType" json:"type,omitempty"`
Permissions uint32 `protobuf:"varint,4,opt,name=permissions,proto3" json:"permissions,omitempty"`
ModifiedNs int32 `protobuf:"varint,11,opt,name=modified_ns,json=modifiedNs,proto3" json:"modified_ns,omitempty"`
RawBlockSize int32 `protobuf:"varint,13,opt,name=block_size,json=blockSize,proto3" json:"block_size,omitempty"`
// see bep.proto
LocalFlags uint32 `protobuf:"varint,1000,opt,name=local_flags,json=localFlags,proto3" json:"local_flags,omitempty"`
LocalFlags uint32 `protobuf:"varint,1000,opt,name=local_flags,json=localFlags,proto3" json:"local_flags,omitempty"`
Deleted bool `protobuf:"varint,6,opt,name=deleted,proto3" json:"deleted,omitempty"`
RawInvalid bool `protobuf:"varint,7,opt,name=invalid,proto3" json:"invalid,omitempty"`
NoPermissions bool `protobuf:"varint,8,opt,name=no_permissions,json=noPermissions,proto3" json:"no_permissions,omitempty"`
}
func (m *FileInfoTruncated) Reset() { *m = FileInfoTruncated{} }
@@ -249,49 +249,49 @@ func init() { proto.RegisterFile("structs.proto", fileDescriptor_e774e8f5f348d14
var fileDescriptor_e774e8f5f348d14d = []byte{
// 683 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x53, 0x4f, 0x6b, 0xdb, 0x4e,
0x10, 0xb5, 0x62, 0xf9, 0xdf, 0xd8, 0xce, 0x2f, 0x59, 0x42, 0x10, 0x86, 0x9f, 0x2c, 0x5c, 0x0a,
0xa2, 0x07, 0xbb, 0x4d, 0x6e, 0xed, 0xcd, 0x0d, 0x01, 0x43, 0x69, 0xcb, 0x3a, 0xe4, 0x54, 0x30,
0xfa, 0xb3, 0x76, 0x96, 0xc8, 0x5a, 0x47, 0xbb, 0x4e, 0x70, 0x3e, 0x45, 0x8f, 0x3d, 0xe6, 0xe3,
0xe4, 0x98, 0x63, 0xe9, 0xc1, 0xa4, 0x76, 0x0f, 0xfd, 0x18, 0x65, 0x77, 0x25, 0x45, 0xcd, 0xa9,
0xb7, 0x79, 0x6f, 0x66, 0x77, 0x67, 0xe6, 0xbd, 0x85, 0x36, 0x17, 0xc9, 0x32, 0x10, 0xbc, 0xbf,
0x48, 0x98, 0x60, 0x68, 0x27, 0xf4, 0x3b, 0x2f, 0x12, 0xb2, 0x60, 0x7c, 0xa0, 0x08, 0x7f, 0x39,
0x1d, 0xcc, 0xd8, 0x8c, 0x29, 0xa0, 0x22, 0x5d, 0xd8, 0x39, 0x8c, 0xa8, 0xaf, 0x4b, 0x02, 0x16,
0x0d, 0x7c, 0xb2, 0xd0, 0x7c, 0xef, 0x0a, 0x9a, 0xa7, 0x34, 0x22, 0xe7, 0x24, 0xe1, 0x94, 0xc5,
0xe8, 0x35, 0xd4, 0xae, 0x75, 0x68, 0x19, 0x8e, 0xe1, 0x36, 0x8f, 0xf6, 0xfa, 0xd9, 0xa1, 0xfe,
0x39, 0x09, 0x04, 0x4b, 0x86, 0xe6, 0xfd, 0xba, 0x5b, 0xc2, 0x59, 0x19, 0x3a, 0x84, 0x6a, 0x48,
0xae, 0x69, 0x40, 0xac, 0x1d, 0xc7, 0x70, 0x5b, 0x38, 0x45, 0xc8, 0x82, 0x1a, 0x8d, 0xaf, 0xbd,
0x88, 0x86, 0x56, 0xd9, 0x31, 0xdc, 0x3a, 0xce, 0x60, 0xef, 0x14, 0x9a, 0xe9, 0x73, 0x1f, 0x28,
0x17, 0xe8, 0x0d, 0xd4, 0xd3, 0xbb, 0xb8, 0x65, 0x38, 0x65, 0xb7, 0x79, 0xf4, 0x5f, 0x3f, 0xf4,
0xfb, 0x85, 0xae, 0xd2, 0x27, 0xf3, 0xb2, 0xb7, 0xe6, 0xb7, 0xbb, 0x6e, 0xa9, 0xf7, 0x68, 0xc2,
0xbe, 0xac, 0x1a, 0xc5, 0x53, 0x76, 0x96, 0x2c, 0xe3, 0xc0, 0x13, 0x24, 0x44, 0x08, 0xcc, 0xd8,
0x9b, 0x13, 0xd5, 0x7e, 0x03, 0xab, 0x18, 0xbd, 0x02, 0x53, 0xac, 0x16, 0xba, 0xc3, 0xdd, 0xa3,
0xc3, 0xa7, 0x91, 0xf2, 0xe3, 0xab, 0x05, 0xc1, 0xaa, 0x46, 0x9e, 0xe7, 0xf4, 0x96, 0xa8, 0xa6,
0xcb, 0x58, 0xc5, 0xc8, 0x81, 0xe6, 0x82, 0x24, 0x73, 0xca, 0x75, 0x97, 0xa6, 0x63, 0xb8, 0x6d,
0x5c, 0xa4, 0xd0, 0xff, 0x00, 0x73, 0x16, 0xd2, 0x29, 0x25, 0xe1, 0x84, 0x5b, 0x15, 0x75, 0xb6,
0x91, 0x31, 0x63, 0xd4, 0x85, 0x66, 0x9e, 0x8e, 0xb9, 0xd5, 0x74, 0x0c, 0xb7, 0x82, 0xf3, 0x13,
0x1f, 0x39, 0xfa, 0x52, 0x28, 0xf0, 0x57, 0x56, 0xcb, 0x31, 0x5c, 0x73, 0xf8, 0x4e, 0x8e, 0xfd,
0x63, 0xdd, 0x3d, 0x9e, 0x51, 0x71, 0xb1, 0xf4, 0xfb, 0x01, 0x9b, 0x0f, 0xf8, 0x2a, 0x0e, 0xc4,
0x05, 0x8d, 0x67, 0x85, 0xa8, 0x28, 0x6d, 0x7f, 0x7c, 0xc1, 0x12, 0x31, 0x3a, 0x79, 0xba, 0x7d,
0xb8, 0x92, 0x5a, 0x84, 0x24, 0x22, 0x82, 0x84, 0x56, 0x55, 0x6b, 0x91, 0x42, 0xe4, 0x3e, 0xa9,
0x54, 0x93, 0x99, 0xe1, 0xee, 0x66, 0xdd, 0x05, 0xec, 0xdd, 0x8c, 0x34, 0x9b, 0xab, 0x86, 0x5e,
0xc2, 0x6e, 0xcc, 0x26, 0xc5, 0x35, 0xd4, 0xd5, 0x55, 0xed, 0x98, 0x7d, 0x2e, 0x2c, 0xa2, 0x60,
0xa0, 0xc6, 0xbf, 0x19, 0xa8, 0x03, 0x75, 0x4e, 0xae, 0x96, 0x24, 0x0e, 0x88, 0x05, 0x6a, 0x71,
0x39, 0x46, 0x03, 0x00, 0x3f, 0x62, 0xc1, 0xe5, 0x44, 0x49, 0xd2, 0x96, 0x6b, 0x1b, 0xee, 0x6d,
0xd6, 0xdd, 0x16, 0xf6, 0x6e, 0x86, 0x32, 0x31, 0xa6, 0xb7, 0x04, 0x37, 0xfc, 0x2c, 0x94, 0x5d,
0xf2, 0xd5, 0x3c, 0xa2, 0xf1, 0xe5, 0x44, 0x78, 0xc9, 0x8c, 0x08, 0x6b, 0x5f, 0xf9, 0xa0, 0x9d,
0xb2, 0x67, 0x8a, 0x94, 0x82, 0x46, 0x2c, 0xf0, 0xa2, 0xc9, 0x34, 0xf2, 0x66, 0xdc, 0xfa, 0x5d,
0x53, 0x8a, 0x82, 0xe2, 0x4e, 0x25, 0x95, 0x5a, 0xec, 0x97, 0x01, 0xd5, 0xf7, 0x6c, 0x19, 0x0b,
0x8e, 0x0e, 0xa0, 0x32, 0xa5, 0x11, 0xe1, 0xca, 0x58, 0x15, 0xac, 0x81, 0xbc, 0x28, 0xa4, 0x89,
0x9a, 0x8b, 0x12, 0xae, 0x0c, 0x56, 0xc1, 0x45, 0x4a, 0x8d, 0xa7, 0xdf, 0xe6, 0xca, 0x53, 0x15,
0x9c, 0xe3, 0xa2, 0x2e, 0xa6, 0x4a, 0xe5, 0xba, 0x1c, 0x40, 0xc5, 0x5f, 0x09, 0x92, 0x59, 0x49,
0x83, 0xbf, 0x56, 0x55, 0x7d, 0xb6, 0xaa, 0x0e, 0xd4, 0xf5, 0xcf, 0x1b, 0x9d, 0xa8, 0x99, 0x5b,
0x38, 0xc7, 0xc8, 0x86, 0xc2, 0x68, 0x16, 0x7a, 0x3e, 0x6c, 0xef, 0x13, 0x34, 0xf4, 0x94, 0x63,
0x22, 0x90, 0x0b, 0xd5, 0x40, 0x81, 0xf4, 0x37, 0x82, 0xfc, 0x8d, 0x3a, 0x9d, 0x4a, 0x97, 0xe6,
0x65, 0xfb, 0x41, 0x42, 0xe4, 0xaf, 0x53, 0x83, 0x97, 0x71, 0x06, 0x87, 0xce, 0xfd, 0x4f, 0xbb,
0x74, 0xbf, 0xb1, 0x8d, 0x87, 0x8d, 0x6d, 0x3c, 0x6e, 0xec, 0xd2, 0xd7, 0xad, 0x5d, 0xba, 0xdb,
0xda, 0xc6, 0xc3, 0xd6, 0x2e, 0x7d, 0xdf, 0xda, 0x25, 0xbf, 0xaa, 0x5c, 0x71, 0xfc, 0x27, 0x00,
0x00, 0xff, 0xff, 0x38, 0xe1, 0x29, 0xbf, 0xd0, 0x04, 0x00, 0x00,
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x53, 0x4f, 0x6f, 0xda, 0x4e,
0x10, 0xc5, 0xc1, 0x10, 0x18, 0x20, 0xbf, 0x64, 0x15, 0x45, 0x16, 0xd2, 0xcf, 0x58, 0x54, 0x95,
0xac, 0x1e, 0xa0, 0x4d, 0x6e, 0xed, 0x8d, 0x46, 0x91, 0x90, 0xaa, 0xb6, 0x5a, 0xa2, 0x9c, 0x2a,
0x21, 0xff, 0x59, 0xc8, 0x2a, 0xc6, 0x4b, 0xbc, 0x4b, 0x22, 0xf2, 0x29, 0x7a, 0xec, 0x31, 0x1f,
0x27, 0xc7, 0x1c, 0xab, 0x1e, 0x50, 0x0a, 0x3d, 0xf4, 0x63, 0x54, 0xbb, 0x6b, 0x1b, 0x37, 0xa7,
0xde, 0xe6, 0xbd, 0x19, 0x7b, 0x66, 0xde, 0xbc, 0x85, 0x16, 0x17, 0xc9, 0x22, 0x10, 0xbc, 0x37,
0x4f, 0x98, 0x60, 0x68, 0x27, 0xf4, 0xdb, 0x2f, 0x12, 0x32, 0x67, 0xbc, 0xaf, 0x08, 0x7f, 0x31,
0xe9, 0x4f, 0xd9, 0x94, 0x29, 0xa0, 0x22, 0x5d, 0xd8, 0x3e, 0x8a, 0xa8, 0xaf, 0x4b, 0x02, 0x16,
0xf5, 0x7d, 0x32, 0xd7, 0x7c, 0xf7, 0x1a, 0x1a, 0x67, 0x34, 0x22, 0x17, 0x24, 0xe1, 0x94, 0xc5,
0xe8, 0x35, 0xec, 0xde, 0xe8, 0xd0, 0x32, 0x1c, 0xc3, 0x6d, 0x1c, 0xef, 0xf7, 0xb2, 0x8f, 0x7a,
0x17, 0x24, 0x10, 0x2c, 0x19, 0x98, 0x0f, 0xab, 0x4e, 0x09, 0x67, 0x65, 0xe8, 0x08, 0xaa, 0x21,
0xb9, 0xa1, 0x01, 0xb1, 0x76, 0x1c, 0xc3, 0x6d, 0xe2, 0x14, 0x21, 0x0b, 0x76, 0x69, 0x7c, 0xe3,
0x45, 0x34, 0xb4, 0xca, 0x8e, 0xe1, 0xd6, 0x70, 0x06, 0xbb, 0x67, 0xd0, 0x48, 0xdb, 0x7d, 0xa0,
0x5c, 0xa0, 0x37, 0x50, 0x4b, 0xff, 0xc5, 0x2d, 0xc3, 0x29, 0xbb, 0x8d, 0xe3, 0xff, 0x7a, 0xa1,
0xdf, 0x2b, 0x4c, 0x95, 0xb6, 0xcc, 0xcb, 0xde, 0x9a, 0xdf, 0xee, 0x3b, 0xa5, 0xee, 0x93, 0x09,
0x07, 0xb2, 0x6a, 0x18, 0x4f, 0xd8, 0x79, 0xb2, 0x88, 0x03, 0x4f, 0x90, 0x10, 0x21, 0x30, 0x63,
0x6f, 0x46, 0xd4, 0xf8, 0x75, 0xac, 0x62, 0xc9, 0x71, 0x7a, 0x47, 0xd4, 0x20, 0x65, 0xac, 0x62,
0xf4, 0x3f, 0xc0, 0x8c, 0x85, 0x74, 0x42, 0x49, 0x38, 0xe6, 0x56, 0x45, 0x65, 0xea, 0x19, 0x33,
0x42, 0x5f, 0xa0, 0x91, 0xa7, 0xfd, 0xa5, 0xd5, 0x74, 0x0c, 0xd7, 0x1c, 0xbc, 0x93, 0x73, 0xfc,
0x58, 0x75, 0x4e, 0xa6, 0x54, 0x5c, 0x2e, 0xfc, 0x5e, 0xc0, 0x66, 0x7d, 0xbe, 0x8c, 0x03, 0x71,
0x49, 0xe3, 0x69, 0x21, 0x2a, 0x6a, 0xdd, 0x1b, 0x5d, 0xb2, 0x44, 0x0c, 0x4f, 0x71, 0xde, 0x6e,
0xb0, 0x2c, 0xca, 0x5c, 0xff, 0x37, 0x99, 0xdb, 0x50, 0xe3, 0xe4, 0x7a, 0x41, 0xe2, 0x80, 0x58,
0xa0, 0x86, 0xcd, 0x31, 0x7a, 0x09, 0x7b, 0x7c, 0x39, 0x8b, 0x68, 0x7c, 0x35, 0x16, 0x5e, 0x32,
0x25, 0xc2, 0x3a, 0x50, 0xcb, 0xb7, 0x52, 0xf6, 0x5c, 0x91, 0xe8, 0x15, 0x98, 0x62, 0x39, 0xd7,
0x77, 0xda, 0x3b, 0x3e, 0xda, 0x76, 0xcc, 0x45, 0x5c, 0xce, 0x09, 0x56, 0x35, 0xc8, 0x81, 0xc6,
0x9c, 0x24, 0x33, 0xca, 0xf5, 0x5d, 0x4c, 0xc7, 0x70, 0x5b, 0xb8, 0x48, 0xa1, 0x4e, 0x41, 0xa0,
0x98, 0x5b, 0x0d, 0xc7, 0x70, 0x2b, 0xdb, 0x1d, 0x3f, 0x72, 0xd4, 0x07, 0xf0, 0x23, 0x16, 0x5c,
0x8d, 0x95, 0xf4, 0x2d, 0x99, 0x1f, 0xec, 0xaf, 0x57, 0x9d, 0x26, 0xf6, 0x6e, 0x07, 0x32, 0x31,
0xa2, 0x77, 0x04, 0xd7, 0xfd, 0x2c, 0x94, 0x3d, 0x23, 0x16, 0x78, 0xd1, 0x78, 0x12, 0x79, 0x53,
0x6e, 0xfd, 0xde, 0x55, 0x4d, 0x41, 0x71, 0x67, 0x92, 0x92, 0x9e, 0x0a, 0x49, 0x44, 0x04, 0x09,
0xad, 0xaa, 0xf6, 0x54, 0x0a, 0x91, 0xbb, 0x75, 0x9b, 0xfc, 0xac, 0x36, 0xd8, 0x5b, 0xaf, 0x3a,
0x80, 0xbd, 0xdb, 0xa1, 0x66, 0x73, 0xf7, 0x49, 0xb1, 0x62, 0x36, 0x2e, 0x2e, 0x57, 0x53, 0xbf,
0x6a, 0xc5, 0xec, 0xf3, 0x96, 0x4c, 0x2d, 0xf6, 0xcb, 0x80, 0xea, 0x7b, 0xb6, 0x88, 0x05, 0x47,
0x87, 0x50, 0x99, 0xd0, 0x88, 0x70, 0x65, 0xac, 0x0a, 0xd6, 0x40, 0xce, 0x1c, 0xd2, 0x44, 0x5d,
0x8c, 0x12, 0xae, 0xa4, 0xad, 0xe0, 0x22, 0xa5, 0x0e, 0xa7, 0xcf, 0xc0, 0x95, 0xff, 0x2a, 0x38,
0xc7, 0xc5, 0x7d, 0x4c, 0x95, 0xca, 0xf7, 0x39, 0x84, 0x8a, 0xbf, 0x14, 0x24, 0x33, 0xa6, 0x06,
0x7f, 0x99, 0xa0, 0xfa, 0xcc, 0x04, 0x6d, 0xa8, 0xe9, 0x97, 0x37, 0x3c, 0x55, 0xe7, 0x6f, 0xe2,
0x1c, 0x23, 0x1b, 0x0a, 0x2a, 0x5a, 0xe8, 0xb9, 0xae, 0xdd, 0x4f, 0x50, 0xd7, 0x5b, 0x8e, 0x88,
0x40, 0x2e, 0x54, 0x03, 0x05, 0xd2, 0xd7, 0x08, 0xf2, 0x35, 0xea, 0x74, 0x6a, 0xca, 0x34, 0x2f,
0xc7, 0x0f, 0x12, 0x22, 0x5f, 0x9d, 0x5a, 0xbc, 0x8c, 0x33, 0x38, 0x70, 0x1e, 0x7e, 0xda, 0xa5,
0x87, 0xb5, 0x6d, 0x3c, 0xae, 0x6d, 0xe3, 0x69, 0x6d, 0x97, 0xbe, 0x6e, 0xec, 0xd2, 0xfd, 0xc6,
0x36, 0x1e, 0x37, 0x76, 0xe9, 0xfb, 0xc6, 0x2e, 0xf9, 0x55, 0xe5, 0xbe, 0x93, 0x3f, 0x01, 0x00,
0x00, 0xff, 0xff, 0x7e, 0x87, 0x05, 0xb2, 0xd0, 0x04, 0x00, 0x00,
}
func (m *FileVersion) Marshal() (dAtA []byte, err error) {
@@ -1712,6 +1712,7 @@ func (m *CountsSet) Unmarshal(dAtA []byte) error {
func skipStructs(dAtA []byte) (n int, err error) {
l := len(dAtA)
iNdEx := 0
depth := 0
for iNdEx < l {
var wire uint64
for shift := uint(0); ; shift += 7 {
@@ -1743,10 +1744,8 @@ func skipStructs(dAtA []byte) (n int, err error) {
break
}
}
return iNdEx, nil
case 1:
iNdEx += 8
return iNdEx, nil
case 2:
var length int
for shift := uint(0); ; shift += 7 {
@@ -1767,55 +1766,30 @@ func skipStructs(dAtA []byte) (n int, err error) {
return 0, ErrInvalidLengthStructs
}
iNdEx += length
if iNdEx < 0 {
return 0, ErrInvalidLengthStructs
}
return iNdEx, nil
case 3:
for {
var innerWire uint64
var start int = iNdEx
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowStructs
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
innerWire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
innerWireType := int(innerWire & 0x7)
if innerWireType == 4 {
break
}
next, err := skipStructs(dAtA[start:])
if err != nil {
return 0, err
}
iNdEx = start + next
if iNdEx < 0 {
return 0, ErrInvalidLengthStructs
}
}
return iNdEx, nil
depth++
case 4:
return iNdEx, nil
if depth == 0 {
return 0, ErrUnexpectedEndOfGroupStructs
}
depth--
case 5:
iNdEx += 4
return iNdEx, nil
default:
return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
}
if iNdEx < 0 {
return 0, ErrInvalidLengthStructs
}
if depth == 0 {
return iNdEx, nil
}
}
panic("unreachable")
return 0, io.ErrUnexpectedEOF
}
var (
ErrInvalidLengthStructs = fmt.Errorf("proto: negative length found during unmarshaling")
ErrIntOverflowStructs = fmt.Errorf("proto: integer overflow")
ErrInvalidLengthStructs = fmt.Errorf("proto: negative length found during unmarshaling")
ErrIntOverflowStructs = fmt.Errorf("proto: integer overflow")
ErrUnexpectedEndOfGroupStructs = fmt.Errorf("proto: unexpected end of group")
)

View File

@@ -27,23 +27,24 @@ message VersionList {
message FileInfoTruncated {
option (gogoproto.goproto_stringer) = false;
string name = 1;
protocol.FileInfoType type = 2;
int64 size = 3;
uint32 permissions = 4;
int64 modified_s = 5;
int32 modified_ns = 11;
uint64 modified_by = 12 [(gogoproto.customtype) = "github.com/syncthing/syncthing/lib/protocol.ShortID", (gogoproto.nullable) = false];
bool deleted = 6;
bool invalid = 7 [(gogoproto.customname) = "RawInvalid"];
bool no_permissions = 8;
protocol.Vector version = 9 [(gogoproto.nullable) = false];
int64 sequence = 10;
int32 block_size = 13 [(gogoproto.customname) = "RawBlockSize"];
// repeated BlockInfo Blocks = 16
string symlink_target = 17;
protocol.FileInfoType type = 2;
uint32 permissions = 4;
int32 modified_ns = 11;
int32 block_size = 13 [(gogoproto.customname) = "RawBlockSize"];
// see bep.proto
uint32 local_flags = 1000;
bool deleted = 6;
bool invalid = 7 [(gogoproto.customname) = "RawInvalid"];
bool no_permissions = 8;
}
// For each folder and device we keep one of these to track the current

View File

@@ -7,111 +7,146 @@
package db
import (
"github.com/syncthing/syncthing/lib/db/backend"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syndtr/goleveldb/leveldb"
"github.com/syndtr/goleveldb/leveldb/util"
)
// A readOnlyTransaction represents a database snapshot.
type readOnlyTransaction struct {
snapshot
backend.ReadTransaction
keyer keyer
}
func (db *instance) newReadOnlyTransaction() readOnlyTransaction {
return readOnlyTransaction{
snapshot: db.GetSnapshot(),
keyer: db.keyer,
func (db *Lowlevel) newReadOnlyTransaction() (readOnlyTransaction, error) {
tran, err := db.NewReadTransaction()
if err != nil {
return readOnlyTransaction{}, err
}
return readOnlyTransaction{
ReadTransaction: tran,
keyer: db.keyer,
}, nil
}
func (t readOnlyTransaction) close() {
t.Release()
}
func (t readOnlyTransaction) getFile(folder, device, file []byte) (protocol.FileInfo, bool) {
return t.getFileByKey(t.keyer.GenerateDeviceFileKey(nil, folder, device, file))
}
func (t readOnlyTransaction) getFileByKey(key []byte) (protocol.FileInfo, bool) {
if f, ok := t.getFileTrunc(key, false); ok {
return f.(protocol.FileInfo), true
func (t readOnlyTransaction) getFile(folder, device, file []byte) (protocol.FileInfo, bool, error) {
key, err := t.keyer.GenerateDeviceFileKey(nil, folder, device, file)
if err != nil {
return protocol.FileInfo{}, false, err
}
return protocol.FileInfo{}, false
return t.getFileByKey(key)
}
func (t readOnlyTransaction) getFileTrunc(key []byte, trunc bool) (FileIntf, bool) {
bs, err := t.Get(key, nil)
if err == leveldb.ErrNotFound {
return nil, false
func (t readOnlyTransaction) getFileByKey(key []byte) (protocol.FileInfo, bool, error) {
f, ok, err := t.getFileTrunc(key, false)
if err != nil || !ok {
return protocol.FileInfo{}, false, err
}
return f.(protocol.FileInfo), true, nil
}
func (t readOnlyTransaction) getFileTrunc(key []byte, trunc bool) (FileIntf, bool, error) {
bs, err := t.Get(key)
if backend.IsNotFound(err) {
return nil, false, nil
}
if err != nil {
l.Debugln("surprise error:", err)
return nil, false
return nil, false, err
}
f, err := unmarshalTrunc(bs, trunc)
if err != nil {
l.Debugln("unmarshal error:", err)
return nil, false
return nil, false, err
}
return f, true
return f, true, nil
}
func (t readOnlyTransaction) getGlobal(keyBuf, folder, file []byte, truncate bool) ([]byte, FileIntf, bool) {
keyBuf = t.keyer.GenerateGlobalVersionKey(keyBuf, folder, file)
bs, err := t.Get(keyBuf, nil)
func (t readOnlyTransaction) getGlobal(keyBuf, folder, file []byte, truncate bool) ([]byte, FileIntf, bool, error) {
var err error
keyBuf, err = t.keyer.GenerateGlobalVersionKey(keyBuf, folder, file)
if err != nil {
return keyBuf, nil, false
return nil, nil, false, err
}
bs, err := t.Get(keyBuf)
if backend.IsNotFound(err) {
return keyBuf, nil, false, nil
}
if err != nil {
return nil, nil, false, err
}
vl, ok := unmarshalVersionList(bs)
if !ok {
return keyBuf, nil, false
return keyBuf, nil, false, nil
}
keyBuf = t.keyer.GenerateDeviceFileKey(keyBuf, folder, vl.Versions[0].Device, file)
if fi, ok := t.getFileTrunc(keyBuf, truncate); ok {
return keyBuf, fi, true
keyBuf, err = t.keyer.GenerateDeviceFileKey(keyBuf, folder, vl.Versions[0].Device, file)
if err != nil {
return nil, nil, false, err
}
return keyBuf, nil, false
fi, ok, err := t.getFileTrunc(keyBuf, truncate)
if err != nil || !ok {
return keyBuf, nil, false, err
}
return keyBuf, fi, true, nil
}
// A readWriteTransaction is a readOnlyTransaction plus a batch for writes.
// The batch will be committed on close() or by checkFlush() if it exceeds the
// batch size.
type readWriteTransaction struct {
backend.WriteTransaction
readOnlyTransaction
*batch
}
func (db *instance) newReadWriteTransaction() readWriteTransaction {
return readWriteTransaction{
readOnlyTransaction: db.newReadOnlyTransaction(),
batch: db.newBatch(),
func (db *Lowlevel) newReadWriteTransaction() (readWriteTransaction, error) {
tran, err := db.NewWriteTransaction()
if err != nil {
return readWriteTransaction{}, err
}
return readWriteTransaction{
WriteTransaction: tran,
readOnlyTransaction: readOnlyTransaction{
ReadTransaction: tran,
keyer: db.keyer,
},
}, nil
}
func (t readWriteTransaction) commit() error {
t.readOnlyTransaction.close()
return t.WriteTransaction.Commit()
}
func (t readWriteTransaction) close() {
t.flush()
t.readOnlyTransaction.close()
t.WriteTransaction.Release()
}
// updateGlobal adds this device+version to the version list for the given
// file. If the device is already present in the list, the version is updated.
// If the file does not have an entry in the global list, it is created.
func (t readWriteTransaction) updateGlobal(gk, keyBuf, folder, device []byte, file protocol.FileInfo, meta *metadataTracker) ([]byte, bool) {
func (t readWriteTransaction) updateGlobal(gk, keyBuf, folder, device []byte, file protocol.FileInfo, meta *metadataTracker) ([]byte, bool, error) {
l.Debugf("update global; folder=%q device=%v file=%q version=%v invalid=%v", folder, protocol.DeviceIDFromBytes(device), file.Name, file.Version, file.IsInvalid())
var fl VersionList
if svl, err := t.Get(gk, nil); err == nil {
fl.Unmarshal(svl) // Ignore error, continue with empty fl
svl, err := t.Get(gk)
if err == nil {
_ = fl.Unmarshal(svl) // Ignore error, continue with empty fl
} else if !backend.IsNotFound(err) {
return nil, false, err
}
fl, removedFV, removedAt, insertedAt, err := fl.update(folder, device, file, t.readOnlyTransaction)
if err != nil {
return nil, false, err
}
fl, removedFV, removedAt, insertedAt := fl.update(folder, device, file, t.readOnlyTransaction)
if insertedAt == -1 {
l.Debugln("update global; same version, global unchanged")
return keyBuf, false
return keyBuf, false, nil
}
name := []byte(file.Name)
@@ -121,24 +156,29 @@ func (t readWriteTransaction) updateGlobal(gk, keyBuf, folder, device []byte, fi
// Inserted a new newest version
global = file
} else {
keyBuf = t.keyer.GenerateDeviceFileKey(keyBuf, folder, fl.Versions[0].Device, name)
if new, ok := t.getFileByKey(keyBuf); ok {
global = new
} else {
// This file must exist in the db, so this must be caused
// by the db being closed - bail out.
l.Debugln("File should exist:", name)
return keyBuf, false
keyBuf, err = t.keyer.GenerateDeviceFileKey(keyBuf, folder, fl.Versions[0].Device, name)
if err != nil {
return nil, false, err
}
new, ok, err := t.getFileByKey(keyBuf)
if err != nil || !ok {
return keyBuf, false, err
}
global = new
}
// Fixup the list of files we need.
keyBuf = t.updateLocalNeed(keyBuf, folder, name, fl, global)
keyBuf, err = t.updateLocalNeed(keyBuf, folder, name, fl, global)
if err != nil {
return nil, false, err
}
if removedAt != 0 && insertedAt != 0 {
l.Debugf(`new global for "%v" after update: %v`, file.Name, fl)
t.Put(gk, mustMarshal(&fl))
return keyBuf, true
if err := t.Put(gk, mustMarshal(&fl)); err != nil {
return nil, false, err
}
return keyBuf, true, nil
}
// Remove the old global from the global size counter
@@ -149,8 +189,15 @@ func (t readWriteTransaction) updateGlobal(gk, keyBuf, folder, device []byte, fi
// The previous newest version is now at index 1
oldGlobalFV = fl.Versions[1]
}
keyBuf = t.keyer.GenerateDeviceFileKey(keyBuf, folder, oldGlobalFV.Device, name)
if oldFile, ok := t.getFileByKey(keyBuf); ok {
keyBuf, err = t.keyer.GenerateDeviceFileKey(keyBuf, folder, oldGlobalFV.Device, name)
if err != nil {
return nil, false, err
}
oldFile, ok, err := t.getFileByKey(keyBuf)
if err != nil {
return nil, false, err
}
if ok {
// A failure to get the file here is surprising and our
// global size data will be incorrect until a restart...
meta.removeFile(protocol.GlobalDeviceID, oldFile)
@@ -160,27 +207,41 @@ func (t readWriteTransaction) updateGlobal(gk, keyBuf, folder, device []byte, fi
meta.addFile(protocol.GlobalDeviceID, global)
l.Debugf(`new global for "%v" after update: %v`, file.Name, fl)
t.Put(gk, mustMarshal(&fl))
if err := t.Put(gk, mustMarshal(&fl)); err != nil {
return nil, false, err
}
return keyBuf, true
return keyBuf, true, nil
}
// updateLocalNeed checks whether the given file is still needed on the local
// device according to the version list and global FileInfo given and updates
// the db accordingly.
func (t readWriteTransaction) updateLocalNeed(keyBuf, folder, name []byte, fl VersionList, global protocol.FileInfo) []byte {
keyBuf = t.keyer.GenerateNeedFileKey(keyBuf, folder, name)
hasNeeded, _ := t.Has(keyBuf, nil)
func (t readWriteTransaction) updateLocalNeed(keyBuf, folder, name []byte, fl VersionList, global protocol.FileInfo) ([]byte, error) {
var err error
keyBuf, err = t.keyer.GenerateNeedFileKey(keyBuf, folder, name)
if err != nil {
return nil, err
}
_, err = t.Get(keyBuf)
if err != nil && !backend.IsNotFound(err) {
return nil, err
}
hasNeeded := err == nil
if localFV, haveLocalFV := fl.Get(protocol.LocalDeviceID[:]); need(global, haveLocalFV, localFV.Version) {
if !hasNeeded {
l.Debugf("local need insert; folder=%q, name=%q", folder, name)
t.Put(keyBuf, nil)
if err := t.Put(keyBuf, nil); err != nil {
return nil, err
}
}
} else if hasNeeded {
l.Debugf("local need delete; folder=%q, name=%q", folder, name)
t.Delete(keyBuf)
if err := t.Delete(keyBuf); err != nil {
return nil, err
}
}
return keyBuf
return keyBuf, nil
}
func need(global FileIntf, haveLocal bool, localVersion protocol.Vector) bool {
@@ -202,71 +263,94 @@ func need(global FileIntf, haveLocal bool, localVersion protocol.Vector) bool {
// removeFromGlobal removes the device from the global version list for the
// given file. If the version list is empty after this, the file entry is
// removed entirely.
func (t readWriteTransaction) removeFromGlobal(gk, keyBuf, folder, device []byte, file []byte, meta *metadataTracker) []byte {
func (t readWriteTransaction) removeFromGlobal(gk, keyBuf, folder, device []byte, file []byte, meta *metadataTracker) ([]byte, error) {
l.Debugf("remove from global; folder=%q device=%v file=%q", folder, protocol.DeviceIDFromBytes(device), file)
svl, err := t.Get(gk, nil)
if err != nil {
svl, err := t.Get(gk)
if backend.IsNotFound(err) {
// We might be called to "remove" a global version that doesn't exist
// if the first update for the file is already marked invalid.
return keyBuf
return keyBuf, nil
} else if err != nil {
return nil, err
}
var fl VersionList
err = fl.Unmarshal(svl)
if err != nil {
l.Debugln("unmarshal error:", err)
return keyBuf
return nil, err
}
fl, _, removedAt := fl.pop(device)
if removedAt == -1 {
// There is no version for the given device
return keyBuf
return keyBuf, nil
}
if removedAt == 0 {
// A failure to get the file here is surprising and our
// global size data will be incorrect until a restart...
keyBuf = t.keyer.GenerateDeviceFileKey(keyBuf, folder, device, file)
if f, ok := t.getFileByKey(keyBuf); ok {
keyBuf, err = t.keyer.GenerateDeviceFileKey(keyBuf, folder, device, file)
if err != nil {
return nil, err
}
if f, ok, err := t.getFileByKey(keyBuf); err != nil {
return keyBuf, nil
} else if ok {
meta.removeFile(protocol.GlobalDeviceID, f)
}
}
if len(fl.Versions) == 0 {
keyBuf = t.keyer.GenerateNeedFileKey(keyBuf, folder, file)
t.Delete(keyBuf)
t.Delete(gk)
return keyBuf
keyBuf, err = t.keyer.GenerateNeedFileKey(keyBuf, folder, file)
if err != nil {
return nil, err
}
if err := t.Delete(keyBuf); err != nil {
return nil, err
}
if err := t.Delete(gk); err != nil {
return nil, err
}
return keyBuf, nil
}
if removedAt == 0 {
keyBuf = t.keyer.GenerateDeviceFileKey(keyBuf, folder, fl.Versions[0].Device, file)
global, ok := t.getFileByKey(keyBuf)
if !ok {
// This file must exist in the db, so this must be caused
// by the db being closed - bail out.
l.Debugln("File should exist:", file)
return keyBuf
keyBuf, err = t.keyer.GenerateDeviceFileKey(keyBuf, folder, fl.Versions[0].Device, file)
if err != nil {
return nil, err
}
global, ok, err := t.getFileByKey(keyBuf)
if err != nil || !ok {
return keyBuf, err
}
keyBuf, err = t.updateLocalNeed(keyBuf, folder, file, fl, global)
if err != nil {
return nil, err
}
keyBuf = t.updateLocalNeed(keyBuf, folder, file, fl, global)
meta.addFile(protocol.GlobalDeviceID, global)
}
l.Debugf("new global after remove: %v", fl)
t.Put(gk, mustMarshal(&fl))
if err := t.Put(gk, mustMarshal(&fl)); err != nil {
return nil, err
}
return keyBuf
return keyBuf, nil
}
func (t readWriteTransaction) deleteKeyPrefix(prefix []byte) {
dbi := t.NewIterator(util.BytesPrefix(prefix), nil)
for dbi.Next() {
t.Delete(dbi.Key())
t.checkFlush()
func (t readWriteTransaction) deleteKeyPrefix(prefix []byte) error {
dbi, err := t.NewPrefixIterator(prefix)
if err != nil {
return err
}
dbi.Release()
defer dbi.Release()
for dbi.Next() {
if err := t.Delete(dbi.Key()); err != nil {
return err
}
}
return dbi.Error()
}
type marshaller interface {

View File

@@ -11,22 +11,26 @@ import (
"io"
"os"
"github.com/syndtr/goleveldb/leveldb"
"github.com/syndtr/goleveldb/leveldb/storage"
"github.com/syndtr/goleveldb/leveldb/util"
"github.com/syncthing/syncthing/lib/db/backend"
)
// writeJSONS serializes the database to a JSON stream that can be checked
// in to the repo and used for tests.
func writeJSONS(w io.Writer, db *leveldb.DB) {
it := db.NewIterator(&util.Range{}, nil)
func writeJSONS(w io.Writer, db backend.Backend) {
it, err := db.NewPrefixIterator(nil)
if err != nil {
panic(err)
}
defer it.Release()
enc := json.NewEncoder(w)
for it.Next() {
enc.Encode(map[string][]byte{
err := enc.Encode(map[string][]byte{
"k": it.Key(),
"v": it.Value(),
})
if err != nil {
panic(err)
}
}
}
@@ -34,15 +38,15 @@ func writeJSONS(w io.Writer, db *leveldb.DB) {
// here and the linter to not complain.
var _ = writeJSONS
// openJSONS reads a JSON stream file into a leveldb.DB
func openJSONS(file string) (*leveldb.DB, error) {
// openJSONS reads a JSON stream file into a backend DB
func openJSONS(file string) (backend.Backend, error) {
fd, err := os.Open(file)
if err != nil {
return nil, err
}
dec := json.NewDecoder(fd)
db, _ := leveldb.Open(storage.NewMemStorage(), nil)
db := backend.OpenMemory()
for {
var row map[string][]byte
@@ -54,7 +58,9 @@ func openJSONS(file string) (*leveldb.DB, error) {
return nil, err
}
db.Put(row["k"], row["v"], nil)
if err := db.Put(row["k"], row["v"]); err != nil {
return nil, err
}
}
return db, nil

23
lib/dialer/debug.go Normal file
View File

@@ -0,0 +1,23 @@
// Copyright (C) 2019 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
package dialer
import (
"os"
"strings"
"github.com/syncthing/syncthing/lib/logger"
)
var (
l = logger.DefaultLogger.NewFacility("dialer", "Dialing connections")
// To run before init() of other files that log on init.
_ = func() error {
l.SetDebug("dialer", strings.Contains(os.Getenv("STTRACE"), "dialer") || os.Getenv("STTRACE") == "all")
return nil
}()
)

View File

@@ -7,37 +7,24 @@
package dialer
import (
"net"
"net/http"
"net/url"
"os"
"strings"
"time"
"golang.org/x/net/proxy"
"github.com/syncthing/syncthing/lib/logger"
)
var (
l = logger.DefaultLogger.NewFacility("dialer", "Dialing connections")
proxyDialer proxy.Dialer
usingProxy bool
noFallback = os.Getenv("ALL_PROXY_NO_FALLBACK") != ""
noFallback = os.Getenv("ALL_PROXY_NO_FALLBACK") != ""
)
type dialFunc func(network, addr string) (net.Conn, error)
func init() {
l.SetDebug("dialer", strings.Contains(os.Getenv("STTRACE"), "dialer") || os.Getenv("STTRACE") == "all")
proxy.RegisterDialerType("socks", socksDialerFunction)
proxyDialer = getDialer(proxy.Direct)
usingProxy = proxyDialer != proxy.Direct
if usingProxy {
if proxyDialer := proxy.FromEnvironment(); proxyDialer != proxy.Direct {
http.DefaultTransport = &http.Transport{
Dial: Dial,
DialContext: DialContext,
Proxy: http.ProxyFromEnvironment,
TLSHandshakeTimeout: 10 * time.Second,
}
@@ -58,31 +45,6 @@ func init() {
}
}
func dialWithFallback(proxyDialFunc dialFunc, fallbackDialFunc dialFunc, network, addr string) (net.Conn, error) {
conn, err := proxyDialFunc(network, addr)
if err == nil {
l.Debugf("Dialing %s address %s via proxy - success, %s -> %s", network, addr, conn.LocalAddr(), conn.RemoteAddr())
SetTCPOptions(conn)
return dialerConn{
conn, newDialerAddr(network, addr),
}, nil
}
l.Debugf("Dialing %s address %s via proxy - error %s", network, addr, err)
if noFallback {
return conn, err
}
conn, err = fallbackDialFunc(network, addr)
if err == nil {
l.Debugf("Dialing %s address %s via fallback - success, %s -> %s", network, addr, conn.LocalAddr(), conn.RemoteAddr())
SetTCPOptions(conn)
} else {
l.Debugf("Dialing %s address %s via fallback - error %s", network, addr, err)
}
return conn, err
}
// This is a rip off of proxy.FromURL for "socks" URL scheme
func socksDialerFunction(u *url.URL, forward proxy.Dialer) (proxy.Dialer, error) {
var auth *proxy.Auth
@@ -96,67 +58,3 @@ func socksDialerFunction(u *url.URL, forward proxy.Dialer) (proxy.Dialer, error)
return proxy.SOCKS5("tcp", u.Host, auth, forward)
}
// This is a rip off of proxy.FromEnvironment with a custom forward dialer
func getDialer(forward proxy.Dialer) proxy.Dialer {
allProxy := os.Getenv("all_proxy")
if len(allProxy) == 0 {
return forward
}
proxyURL, err := url.Parse(allProxy)
if err != nil {
return forward
}
prxy, err := proxy.FromURL(proxyURL, forward)
if err != nil {
return forward
}
noProxy := os.Getenv("no_proxy")
if len(noProxy) == 0 {
return prxy
}
perHost := proxy.NewPerHost(prxy, forward)
perHost.AddFromString(noProxy)
return perHost
}
type timeoutDirectDialer struct {
timeout time.Duration
}
func (d *timeoutDirectDialer) Dial(network, addr string) (net.Conn, error) {
return net.DialTimeout(network, addr, d.timeout)
}
type dialerConn struct {
net.Conn
addr net.Addr
}
func (c dialerConn) RemoteAddr() net.Addr {
return c.addr
}
func newDialerAddr(network, addr string) net.Addr {
netaddr, err := net.ResolveIPAddr(network, addr)
if err == nil {
return netaddr
}
return fallbackAddr{network, addr}
}
type fallbackAddr struct {
network string
addr string
}
func (a fallbackAddr) Network() string {
return a.network
}
func (a fallbackAddr) String() string {
return a.addr
}

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