Compare commits

..

1 Commits

Author SHA1 Message Date
915776161f Use S3 ListObjectsV2 for listing files
ListObjects has been deprecated since 2016 and ListObjectsV2 with use of
explicit pagination tokens is more performant for large listings as well.

This also mitigates an issue with iDrive E2 where the StartAfter/Marker
is included in the output, leading to duplicate entries. Right now this
causes an exhaustive prune to delete chunks erroneously flagged as
duplicate, destroying the storage.
2023-09-23 22:17:22 -05:00
29 changed files with 751 additions and 1921 deletions

View File

@@ -1,45 +1,3 @@
# Dupluxe
An experimental Duplicacy derivative with improved support for preserving state on UNIX like systems. Produces snapshots compatible with Duplicacy.
NOTE: This project/repository is not affiliated with nor endorsed by Duplicacy, Acrosync or their associated rights holders. This project is open source but is not free/libre software. It is developed and distributed in accordance with the associated LICENSE. Commercial use may require purchase of a license from Acrosync, please contact them if you have any doubts.
## Added Features
* Support for hard links. Hard links are tracked during local file listing. All linked entries will reuse the same chunk data, so this can give a time and space saving benefit as hard-linked files only need to be packed once. Hard links are supported to everything (regular files, symlinks, special files) except directories.
* Optional File flags, that is chflags(1) on BSD/Darwin, and ioctl_iflags(2) on Linux. The primary use case is to preserve iflags used by btrfs for no-COW and compression.
* Optional Special files (character/block devices, FIFOs, and sockets) are preserved along with associated metadata.
## Assorted Changes
* The S3 backend uses the newer ListObjectsV2 interface originally because of a bug with some providers with the old, obsolete interface, but now because this API is considerably faster on a number of providers tested.
* B2 client max listing per request increased to 10,000
* A fix for the exclude_by_attribute feature on BSD/Unix which has been broken upstream for ages.
## Snapshot Format
The generated preserves snapshots are backward compatible with vanilla versions of duplicacy and also do not increase the encoding size of metadata significantly. Unfortunately duplicacy does not have a formal forward-compatible snapshot versioning system, but that's not too surprising. This does mean that the data encoding is somewhat abusive of the existing format.
### Hard links
The storage differs for regular files vs. every other target. Entry records contain a `Link` string field for the symlink target. When a likely hard linked file is encountered (`st_nlink > 1`) that entry is marked as a hard link root with the string "/" in the `Link` field and it is placed in an array, the index of this array will serve as a link address. Plain duplicacy only uses the `Link` field for symlinks. Files that hard link to this initial file have the index of the array of root files encoded as a base-16 integer into their `Link` field. These entries are placed in the snapshot with valid start/end chunk and offset values and all metadata is cloned so official Duplicacy will recover them as regular files with all metadata, it just will never make hard links.
For hard links to symlinks and special files, the `Link` isn't used. Instead, since these files never have content the `EndChunk/EndOffset` fields are used. A magic number (-9) is encoded in `EndChunk` for root entries and (-10) for clone/child entries. The `EndOffset` contains the index into the root entry array.
### Special Files
Duplicacy simply skips special files. DuplicacyIX does not skip them. The `st_rdev` (device number) for character and block devices is stored with the lower 32-bits in `StartChunk` and the upper 32-bits in `StartOffset`, though no actual supported system uses anything bigger than 32-bits. The packing of this quantity is OS specific, but major, minor numbers are also OS specific.
### File flags
Files flags are stored in the extended attributes table with a short (2 character) OS specific key prefixed with a null-byte. Duplicacy will try to set these xattrs, however they will be ignored as the name appears to be empty with the initial null-byte.
## Motivation
Arguably system root directories are better preserved in a filesystem image format, however the line becomes blurred for home and data directories the former which tends to become a magnet for all kinds of data layout. This gives the option of a convenient random addressable cloud backup with easy partial restore while also being able to backup an nearly exact replica for use in disaster recovery. Nearly exact, the only metadata not preserved are times other than mtimes and ACLs on BSD-like systems.
Hard links are a pain and might be better to not exist but in actual use, but things like git repos and SDKs have a tendency to use them. Often one has no choice but to deal with them, and forgoing preserving them is painful.
File flags are primarily for the use case of btrfs snapshot backups, specifically with regards to compression and no-COW. The implementation applies certain flags immediately on open so that these flags apply to written blocks.
Special files serve a couple purposes. Backup of FIFOs and sockets are primarily for preserving metadata since these files have no useful content and can always be created on the fly. The other is support for backup of overlay2 file systems. overlay2 uses character mode dev-nodes for whiteouts in addition to trusted namespace xattrs. DuplicacyIX should be able to faithfully reproduce overlay2 fs layers.
## Caveats/TODO
* Improve command line options around processing hard links and special files, especially for restore. Right now if you don't have `mknod` capabilities and the snapshot has special device files you're going to need to use filters.
* File flags for immutability aren't handled smartly. Specifically immutable and append only files will break badly with hardlinks, since hardlink creation is deferred to after flags application. The solution is not complicated but this is not a pressing use case.
* Some corner cases of replacing existing files with hard links might end up breaking links if not doing a full restore. Again not a pressing use case. For the primary use of disaster recovery of large portions or an entire volume it works fine.
* Possibly encode ACLs on Mac OS/FreeBSD. On Linux the crappy POSIX 1e ACLs that no one should use are picked up in the xattrs.
# Duplicacy: A lock-free deduplication cloud backup tool # Duplicacy: A lock-free deduplication cloud backup tool
Duplicacy is a new generation cross-platform cloud backup tool based on the idea of [Lock-Free Deduplication](https://github.com/gilbertchen/duplicacy/wiki/Lock-Free-Deduplication). Duplicacy is a new generation cross-platform cloud backup tool based on the idea of [Lock-Free Deduplication](https://github.com/gilbertchen/duplicacy/wiki/Lock-Free-Deduplication).

View File

@@ -22,7 +22,9 @@ import (
"github.com/gilbertchen/cli" "github.com/gilbertchen/cli"
duplicacy "github.com/gilbertchen/duplicacy/src" "io/ioutil"
"github.com/gilbertchen/duplicacy/src"
) )
const ( const (
@@ -314,7 +316,7 @@ func configRepository(context *cli.Context, init bool) {
// write real path into .duplicacy file inside repository // write real path into .duplicacy file inside repository
duplicacyFileName := path.Join(repository, duplicacy.DUPLICACY_FILE) duplicacyFileName := path.Join(repository, duplicacy.DUPLICACY_FILE)
d1 := []byte(preferencePath) d1 := []byte(preferencePath)
err = os.WriteFile(duplicacyFileName, d1, 0644) err = ioutil.WriteFile(duplicacyFileName, d1, 0644)
if err != nil { if err != nil {
duplicacy.LOG_ERROR("REPOSITORY_PATH", "Failed to write %s file inside repository %v", duplicacyFileName, err) duplicacy.LOG_ERROR("REPOSITORY_PATH", "Failed to write %s file inside repository %v", duplicacyFileName, err)
return return
@@ -703,7 +705,7 @@ func changePassword(context *cli.Context) {
} }
configPath := path.Join(duplicacy.GetDuplicacyPreferencePath(), "config") configPath := path.Join(duplicacy.GetDuplicacyPreferencePath(), "config")
err = os.WriteFile(configPath, description, 0600) err = ioutil.WriteFile(configPath, description, 0600)
if err != nil { if err != nil {
duplicacy.LOG_ERROR("CONFIG_SAVE", "Failed to save the old config to %s: %v", configPath, err) duplicacy.LOG_ERROR("CONFIG_SAVE", "Failed to save the old config to %s: %v", configPath, err)
return return
@@ -787,17 +789,7 @@ func backupRepository(context *cli.Context) {
uploadRateLimit := context.Int("limit-rate") uploadRateLimit := context.Int("limit-rate")
enumOnly := context.Bool("enum-only") enumOnly := context.Bool("enum-only")
storage.SetRateLimits(0, uploadRateLimit) storage.SetRateLimits(0, uploadRateLimit)
backupManager := duplicacy.CreateBackupManager(preference.SnapshotID, storage, repository, password, backupManager := duplicacy.CreateBackupManager(preference.SnapshotID, storage, repository, password, preference.NobackupFile, preference.FiltersFile, preference.ExcludeByAttribute)
&duplicacy.BackupManagerOptions{
NobackupFile: preference.NobackupFile,
FiltersFile: preference.FiltersFile,
ExcludeByAttribute: preference.ExcludeByAttribute,
ExcludeXattrs: preference.ExcludeXattrs,
NormalizeXattrs: preference.NormalizeXattrs,
IncludeFileFlags: preference.IncludeFileFlags,
IncludeSpecials: preference.IncludeSpecials,
FileFlagsMask: uint32(preference.FileFlagsMask),
})
duplicacy.SavePassword(*preference, "password", password) duplicacy.SavePassword(*preference, "password", password)
backupManager.SetupSnapshotCache(preference.Name) backupManager.SetupSnapshotCache(preference.Name)
@@ -858,6 +850,14 @@ func restoreRepository(context *cli.Context) {
password = duplicacy.GetPassword(*preference, "password", "Enter storage password:", false, false) password = duplicacy.GetPassword(*preference, "password", "Enter storage password:", false, false)
} }
quickMode := !context.Bool("hash")
overwrite := context.Bool("overwrite")
deleteMode := context.Bool("delete")
setOwner := !context.Bool("ignore-owner")
showStatistics := context.Bool("stats")
persist := context.Bool("persist")
var patterns []string var patterns []string
for _, pattern := range context.Args() { for _, pattern := range context.Args() {
@@ -881,38 +881,13 @@ func restoreRepository(context *cli.Context) {
duplicacy.LOG_INFO("SNAPSHOT_FILTER", "Loaded %d include/exclude pattern(s)", len(patterns)) duplicacy.LOG_INFO("SNAPSHOT_FILTER", "Loaded %d include/exclude pattern(s)", len(patterns))
storage.SetRateLimits(context.Int("limit-rate"), 0) storage.SetRateLimits(context.Int("limit-rate"), 0)
backupManager := duplicacy.CreateBackupManager(preference.SnapshotID, storage, repository, password, preference.NobackupFile, preference.FiltersFile, preference.ExcludeByAttribute)
excludeOwner := preference.ExcludeOwner
// TODO: for backward compat, eventually make them all overridable?
if context.IsSet("ignore-owner") {
excludeOwner = context.Bool("ignore-owner")
}
backupManager := duplicacy.CreateBackupManager(preference.SnapshotID, storage, repository, password,
&duplicacy.BackupManagerOptions{
NobackupFile: preference.NobackupFile,
FiltersFile: preference.FiltersFile,
ExcludeByAttribute: preference.ExcludeByAttribute,
SetOwner: excludeOwner,
ExcludeXattrs: preference.ExcludeXattrs,
NormalizeXattrs: preference.NormalizeXattrs,
IncludeSpecials: preference.IncludeSpecials,
FileFlagsMask: uint32(preference.FileFlagsMask),
})
duplicacy.SavePassword(*preference, "password", password) duplicacy.SavePassword(*preference, "password", password)
loadRSAPrivateKey(context.String("key"), context.String("key-passphrase"), preference, backupManager, false) loadRSAPrivateKey(context.String("key"), context.String("key-passphrase"), preference, backupManager, false)
backupManager.SetupSnapshotCache(preference.Name) backupManager.SetupSnapshotCache(preference.Name)
failed := backupManager.Restore(repository, revision, &duplicacy.RestoreOptions{ failed := backupManager.Restore(repository, revision, true, quickMode, threads, overwrite, deleteMode, setOwner, showStatistics, patterns, persist)
InPlace: true,
QuickMode: !context.Bool("hash"),
Overwrite: context.Bool("overwrite"),
DeleteMode: context.Bool("delete"),
ShowStatistics: context.Bool("stats"),
AllowFailures: context.Bool("persist"),
})
if failed > 0 { if failed > 0 {
duplicacy.LOG_ERROR("RESTORE_FAIL", "%d file(s) were not restored correctly", failed) duplicacy.LOG_ERROR("RESTORE_FAIL", "%d file(s) were not restored correctly", failed)
return return
@@ -952,8 +927,7 @@ func listSnapshots(context *cli.Context) {
tag := context.String("t") tag := context.String("t")
revisions := getRevisions(context) revisions := getRevisions(context)
backupManager := duplicacy.CreateBackupManager(preference.SnapshotID, storage, repository, password, backupManager := duplicacy.CreateBackupManager(preference.SnapshotID, storage, repository, password, "", "", preference.ExcludeByAttribute)
&duplicacy.BackupManagerOptions{ExcludeByAttribute: preference.ExcludeByAttribute})
duplicacy.SavePassword(*preference, "password", password) duplicacy.SavePassword(*preference, "password", password)
id := preference.SnapshotID id := preference.SnapshotID
@@ -1009,7 +983,7 @@ func checkSnapshots(context *cli.Context) {
tag := context.String("t") tag := context.String("t")
revisions := getRevisions(context) revisions := getRevisions(context)
backupManager := duplicacy.CreateBackupManager(preference.SnapshotID, storage, repository, password, nil) backupManager := duplicacy.CreateBackupManager(preference.SnapshotID, storage, repository, password, "", "", false)
duplicacy.SavePassword(*preference, "password", password) duplicacy.SavePassword(*preference, "password", password)
loadRSAPrivateKey(context.String("key"), context.String("key-passphrase"), preference, backupManager, false) loadRSAPrivateKey(context.String("key"), context.String("key-passphrase"), preference, backupManager, false)
@@ -1069,7 +1043,8 @@ func printFile(context *cli.Context) {
snapshotID = context.String("id") snapshotID = context.String("id")
} }
backupManager := duplicacy.CreateBackupManager(preference.SnapshotID, storage, repository, password, nil)
backupManager := duplicacy.CreateBackupManager(preference.SnapshotID, storage, repository, password, "", "", false)
duplicacy.SavePassword(*preference, "password", password) duplicacy.SavePassword(*preference, "password", password)
loadRSAPrivateKey(context.String("key"), context.String("key-passphrase"), preference, backupManager, false) loadRSAPrivateKey(context.String("key"), context.String("key-passphrase"), preference, backupManager, false)
@@ -1127,14 +1102,13 @@ func diff(context *cli.Context) {
} }
compareByHash := context.Bool("hash") compareByHash := context.Bool("hash")
backupManager := duplicacy.CreateBackupManager(preference.SnapshotID, storage, repository, password, nil) backupManager := duplicacy.CreateBackupManager(preference.SnapshotID, storage, repository, password, "", "", false)
duplicacy.SavePassword(*preference, "password", password) duplicacy.SavePassword(*preference, "password", password)
loadRSAPrivateKey(context.String("key"), context.String("key-passphrase"), preference, backupManager, false) loadRSAPrivateKey(context.String("key"), context.String("key-passphrase"), preference, backupManager, false)
backupManager.SetupSnapshotCache(preference.Name) backupManager.SetupSnapshotCache(preference.Name)
backupManager.SnapshotManager.Diff(repository, snapshotID, revisions, path, compareByHash, backupManager.SnapshotManager.Diff(repository, snapshotID, revisions, path, compareByHash, preference.NobackupFile, preference.FiltersFile, preference.ExcludeByAttribute)
duplicacy.NewListFilesOptions(preference))
runScript(context, preference.Name, "post") runScript(context, preference.Name, "post")
} }
@@ -1173,7 +1147,7 @@ func showHistory(context *cli.Context) {
revisions := getRevisions(context) revisions := getRevisions(context)
showLocalHash := context.Bool("hash") showLocalHash := context.Bool("hash")
backupManager := duplicacy.CreateBackupManager(preference.SnapshotID, storage, repository, password, nil) backupManager := duplicacy.CreateBackupManager(preference.SnapshotID, storage, repository, password, "", "", false)
duplicacy.SavePassword(*preference, "password", password) duplicacy.SavePassword(*preference, "password", password)
backupManager.SetupSnapshotCache(preference.Name) backupManager.SetupSnapshotCache(preference.Name)
@@ -1236,7 +1210,7 @@ func pruneSnapshots(context *cli.Context) {
os.Exit(ArgumentExitCode) os.Exit(ArgumentExitCode)
} }
backupManager := duplicacy.CreateBackupManager(preference.SnapshotID, storage, repository, password, nil) backupManager := duplicacy.CreateBackupManager(preference.SnapshotID, storage, repository, password, "", "", false)
duplicacy.SavePassword(*preference, "password", password) duplicacy.SavePassword(*preference, "password", password)
backupManager.SetupSnapshotCache(preference.Name) backupManager.SetupSnapshotCache(preference.Name)
@@ -1281,7 +1255,7 @@ func copySnapshots(context *cli.Context) {
sourcePassword = duplicacy.GetPassword(*source, "password", "Enter source storage password:", false, false) sourcePassword = duplicacy.GetPassword(*source, "password", "Enter source storage password:", false, false)
} }
sourceManager := duplicacy.CreateBackupManager(source.SnapshotID, sourceStorage, repository, sourcePassword, nil) sourceManager := duplicacy.CreateBackupManager(source.SnapshotID, sourceStorage, repository, sourcePassword, "", "", false)
sourceManager.SetupSnapshotCache(source.Name) sourceManager.SetupSnapshotCache(source.Name)
duplicacy.SavePassword(*source, "password", sourcePassword) duplicacy.SavePassword(*source, "password", sourcePassword)
@@ -1316,7 +1290,7 @@ func copySnapshots(context *cli.Context) {
destinationStorage.SetRateLimits(0, context.Int("upload-limit-rate")) destinationStorage.SetRateLimits(0, context.Int("upload-limit-rate"))
destinationManager := duplicacy.CreateBackupManager(destination.SnapshotID, destinationStorage, repository, destinationManager := duplicacy.CreateBackupManager(destination.SnapshotID, destinationStorage, repository,
destinationPassword, nil) destinationPassword, "", "", false)
duplicacy.SavePassword(*destination, "password", destinationPassword) duplicacy.SavePassword(*destination, "password", destinationPassword)
destinationManager.SetupSnapshotCache(destination.Name) destinationManager.SetupSnapshotCache(destination.Name)
@@ -1441,7 +1415,7 @@ func benchmark(context *cli.Context) {
if storage == nil { if storage == nil {
return return
} }
duplicacy.Benchmark(repository, storage, int64(fileSize)*1024*1024, chunkSize*1024*1024, chunkCount, uploadThreads, downloadThreads) duplicacy.Benchmark(repository, storage, int64(fileSize) * 1024 * 1024, chunkSize * 1024 * 1024, chunkCount, uploadThreads, downloadThreads)
} }
func main() { func main() {
@@ -1480,8 +1454,8 @@ func main() {
Argument: "<level>", Argument: "<level>",
}, },
cli.BoolFlag{ cli.BoolFlag{
Name: "zstd", Name: "zstd",
Usage: "short for -zstd default", Usage: "short for -zstd default",
}, },
cli.IntFlag{ cli.IntFlag{
Name: "iterations", Name: "iterations",
@@ -1556,8 +1530,8 @@ func main() {
Argument: "<level>", Argument: "<level>",
}, },
cli.BoolFlag{ cli.BoolFlag{
Name: "zstd", Name: "zstd",
Usage: "short for -zstd default", Usage: "short for -zstd default",
}, },
cli.BoolFlag{ cli.BoolFlag{
Name: "vss", Name: "vss",
@@ -1590,6 +1564,7 @@ func main() {
Usage: "the maximum number of entries kept in memory (defaults to 1M)", Usage: "the maximum number of entries kept in memory (defaults to 1M)",
Argument: "<number>", Argument: "<number>",
}, },
}, },
Usage: "Save a snapshot of the repository to the storage", Usage: "Save a snapshot of the repository to the storage",
ArgsUsage: " ", ArgsUsage: " ",
@@ -1649,7 +1624,7 @@ func main() {
cli.BoolFlag{ cli.BoolFlag{
Name: "persist", Name: "persist",
Usage: "continue processing despite chunk errors or existing files (without -overwrite), reporting any affected files", Usage: "continue processing despite chunk errors or existing files (without -overwrite), reporting any affected files",
}, },
cli.StringFlag{ cli.StringFlag{
Name: "key-passphrase", Name: "key-passphrase",
Usage: "the passphrase to decrypt the RSA private key", Usage: "the passphrase to decrypt the RSA private key",
@@ -2007,8 +1982,8 @@ func main() {
Argument: "<level>", Argument: "<level>",
}, },
cli.BoolFlag{ cli.BoolFlag{
Name: "zstd", Name: "zstd",
Usage: "short for -zstd default", Usage: "short for -zstd default",
}, },
cli.IntFlag{ cli.IntFlag{
Name: "iterations", Name: "iterations",
@@ -2273,8 +2248,8 @@ func main() {
Usage: "add a comment to identify the process", Usage: "add a comment to identify the process",
}, },
cli.StringSliceFlag{ cli.StringSliceFlag{
Name: "suppress, s", Name: "suppress, s",
Usage: "suppress logs with the specified id", Usage: "suppress logs with the specified id",
Argument: "<id>", Argument: "<id>",
}, },
cli.BoolFlag{ cli.BoolFlag{
@@ -2287,7 +2262,7 @@ func main() {
app.Name = "duplicacy" app.Name = "duplicacy"
app.HelpName = "duplicacy" app.HelpName = "duplicacy"
app.Usage = "A new generation cloud backup tool based on lock-free deduplication" app.Usage = "A new generation cloud backup tool based on lock-free deduplication"
app.Version = "3.2.2" + " (" + GitCommit + ")" app.Version = "3.2.0" + " (" + GitCommit + ")"
// Exit with code 2 if an invalid command is provided // Exit with code 2 if an invalid command is provided
app.CommandNotFound = func(context *cli.Context, command string) { app.CommandNotFound = func(context *cli.Context, command string) {

46
go.mod
View File

@@ -1,6 +1,6 @@
module github.com/gilbertchen/duplicacy module github.com/gilbertchen/duplicacy
go 1.20 go 1.16
require ( require (
cloud.google.com/go v0.38.0 cloud.google.com/go v0.38.0
@@ -13,23 +13,20 @@ require (
github.com/gilbertchen/go-ole v1.2.0 github.com/gilbertchen/go-ole v1.2.0
github.com/gilbertchen/goamz v0.0.0-20170712012135-eada9f4e8cc2 github.com/gilbertchen/goamz v0.0.0-20170712012135-eada9f4e8cc2
github.com/gilbertchen/gopass v0.0.0-20170109162249-bf9dde6d0d2c github.com/gilbertchen/gopass v0.0.0-20170109162249-bf9dde6d0d2c
github.com/gilbertchen/highwayhash v0.0.0-20221109044721-eeab1f4799d8
github.com/gilbertchen/keyring v0.0.0-20221004152639-1661cbebc508 github.com/gilbertchen/keyring v0.0.0-20221004152639-1661cbebc508
github.com/hirochachacha/go-smb2 v1.1.0 github.com/gilbertchen/xattr v0.0.0-20160926155429-68e7a6806b01
github.com/klauspost/compress v1.16.3
github.com/klauspost/reedsolomon v1.9.9 github.com/klauspost/reedsolomon v1.9.9
github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1
github.com/minio/highwayhash v1.0.2 github.com/minio/highwayhash v1.0.2
github.com/ncw/swift/v2 v2.0.1 github.com/ncw/swift/v2 v2.0.1
github.com/pkg/sftp v1.11.0 github.com/pkg/sftp v1.11.0
github.com/pkg/xattr v0.4.9 github.com/pkg/xattr v0.4.1
github.com/vmihailenco/msgpack v4.0.4+incompatible github.com/vmihailenco/msgpack v4.0.4+incompatible
golang.org/x/crypto v0.12.0 golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838
golang.org/x/net v0.10.0 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
golang.org/x/sys v0.11.0
google.golang.org/api v0.21.0 google.golang.org/api v0.21.0
storj.io/uplink v1.12.0 storj.io/uplink v1.9.0
) )
require ( require (
@@ -37,41 +34,38 @@ require (
github.com/calebcase/tmpfile v1.0.3 // indirect github.com/calebcase/tmpfile v1.0.3 // indirect
github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect
github.com/dnaeon/go-vcr v1.2.0 // indirect github.com/dnaeon/go-vcr v1.2.0 // indirect
github.com/flynn/noise v1.0.0 // indirect github.com/gilbertchen/highwayhash v0.0.0-20221109044721-eeab1f4799d8 // indirect
github.com/geoffgarside/ber v1.1.0 // indirect
github.com/goamz/goamz v0.0.0-20180131231218-8b901b531db8 // indirect github.com/goamz/goamz v0.0.0-20180131231218-8b901b531db8 // indirect
github.com/godbus/dbus v4.1.0+incompatible // indirect github.com/godbus/dbus v4.1.0+incompatible // indirect
github.com/gogo/protobuf v1.3.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
github.com/golang/protobuf v1.5.0 // indirect
github.com/googleapis/gax-go/v2 v2.0.5 // indirect github.com/googleapis/gax-go/v2 v2.0.5 // indirect
github.com/hirochachacha/go-smb2 v1.1.0 // indirect
github.com/jmespath/go-jmespath v0.3.0 // indirect github.com/jmespath/go-jmespath v0.3.0 // indirect
github.com/jtolio/eventkit v0.0.0-20221004135224-074cf276595b // indirect github.com/klauspost/compress v1.16.3 // indirect
github.com/jtolio/noiseconn v0.0.0-20230111204749-d7ec1a08b0b8 // indirect
github.com/klauspost/cpuid v1.3.1 // indirect github.com/klauspost/cpuid v1.3.1 // indirect
github.com/klauspost/cpuid/v2 v2.0.12 // indirect
github.com/kr/fs v0.1.0 // indirect github.com/kr/fs v0.1.0 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/marstr/guid v1.1.0 // indirect github.com/marstr/guid v1.1.0 // indirect
github.com/mmcloughlin/avo v0.0.0-20200803215136-443f81d77104 // indirect github.com/mmcloughlin/avo v0.0.0-20200803215136-443f81d77104 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/satori/go.uuid v1.2.0 // indirect github.com/satori/go.uuid v1.2.0 // indirect
github.com/segmentio/go-env v1.1.0 // indirect github.com/segmentio/go-env v1.1.0 // indirect
github.com/spacemonkeygo/monkit/v3 v3.0.20-0.20230227152157-d00b379de191 // indirect github.com/spacemonkeygo/monkit/v3 v3.0.17 // indirect
github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec // indirect github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec // indirect
github.com/vivint/infectious v0.0.0-20200605153912-25a574ae18a3 // indirect github.com/vivint/infectious v0.0.0-20200605153912-25a574ae18a3 // indirect
github.com/zeebo/blake3 v0.2.3 // indirect
github.com/zeebo/errs v1.3.0 // indirect github.com/zeebo/errs v1.3.0 // indirect
go.opencensus.io v0.22.3 // indirect go.opencensus.io v0.22.3 // indirect
golang.org/x/mod v0.10.0 // indirect golang.org/x/mod v0.4.2 // indirect
golang.org/x/sync v0.3.0 // indirect golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
golang.org/x/term v0.11.0 // indirect golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27 // indirect
golang.org/x/text v0.12.0 // indirect golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 // indirect
golang.org/x/tools v0.9.1 // indirect golang.org/x/text v0.3.6 // indirect
golang.org/x/tools v0.1.1 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
google.golang.org/appengine v1.6.5 // indirect google.golang.org/appengine v1.6.5 // indirect
google.golang.org/genproto v0.0.0-20200409111301-baae70f3302d // indirect google.golang.org/genproto v0.0.0-20200409111301-baae70f3302d // indirect
google.golang.org/grpc v1.28.1 // indirect google.golang.org/grpc v1.28.1 // indirect
google.golang.org/protobuf v1.28.1 // indirect storj.io/common v0.0.0-20220414110316-a5cb7172d6bf // indirect
storj.io/common v0.0.0-20230907123639-5fd0608fd947 // indirect storj.io/drpc v0.0.30 // indirect
storj.io/drpc v0.0.33 // indirect
storj.io/picobuf v0.0.2-0.20230906122608-c4ba17033c6c // indirect
) )

302
go.sum
View File

@@ -1,40 +1,67 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo=
cloud.google.com/go v0.38.0 h1:ROfEUZz+Gh5pa62DJWXSaonyu3StP6EA6lPEXPI6mCo= cloud.google.com/go v0.38.0 h1:ROfEUZz+Gh5pa62DJWXSaonyu3StP6EA6lPEXPI6mCo=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU=
dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU=
dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
github.com/Azure/go-autorest v10.15.5+incompatible h1:vdxx6wM1rVkKt/3niByPVjguoLWkWImOcJNvEykgBzY= github.com/Azure/go-autorest v10.15.5+incompatible h1:vdxx6wM1rVkKt/3niByPVjguoLWkWImOcJNvEykgBzY=
github.com/Azure/go-autorest v10.15.5+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest v10.15.5+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a h1:pv34s756C4pEXnjgPfGYgdhg/ZdajGhyOvzx8k+23nw= github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a h1:pv34s756C4pEXnjgPfGYgdhg/ZdajGhyOvzx8k+23nw=
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
github.com/aws/aws-sdk-go v1.30.7 h1:IaXfqtioP6p9SFAnNfsqdNczbR5UNbYqvcZUSsCAdTY= github.com/aws/aws-sdk-go v1.30.7 h1:IaXfqtioP6p9SFAnNfsqdNczbR5UNbYqvcZUSsCAdTY=
github.com/aws/aws-sdk-go v1.30.7/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= github.com/aws/aws-sdk-go v1.30.7/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/bkaradzic/go-lz4 v1.0.0 h1:RXc4wYsyz985CkXXeX04y4VnZFGG8Rd43pRaHsOXAKk= github.com/bkaradzic/go-lz4 v1.0.0 h1:RXc4wYsyz985CkXXeX04y4VnZFGG8Rd43pRaHsOXAKk=
github.com/bkaradzic/go-lz4 v1.0.0/go.mod h1:0YdlkowM3VswSROI7qDxhRvJ3sLhlFrRRwjwegp5jy4= github.com/bkaradzic/go-lz4 v1.0.0/go.mod h1:0YdlkowM3VswSROI7qDxhRvJ3sLhlFrRRwjwegp5jy4=
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
github.com/calebcase/tmpfile v1.0.3 h1:BZrOWZ79gJqQ3XbAQlihYZf/YCV0H4KPIdM5K5oMpJo= github.com/calebcase/tmpfile v1.0.3 h1:BZrOWZ79gJqQ3XbAQlihYZf/YCV0H4KPIdM5K5oMpJo=
github.com/calebcase/tmpfile v1.0.3/go.mod h1:UAUc01aHeC+pudPagY/lWvt2qS9ZO5Zzof6/tIUzqeI= github.com/calebcase/tmpfile v1.0.3/go.mod h1:UAUc01aHeC+pudPagY/lWvt2qS9ZO5Zzof6/tIUzqeI=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI= github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI=
github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
github.com/dsnet/try v0.0.3 h1:ptR59SsrcFUYbT/FhAbKTV6iLkeD6O18qfIWRml2fqI= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/flynn/noise v1.0.0 h1:DlTHqmzmvcEiKj+4RYo/imoswx/4r6iBlCMfVtrMXpQ= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/flynn/noise v1.0.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag= github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/geoffgarside/ber v1.1.0 h1:qTmFG4jJbwiSzSXoNJeHcOprVzZ8Ulde2Rrrifu5U9w= github.com/geoffgarside/ber v1.1.0 h1:qTmFG4jJbwiSzSXoNJeHcOprVzZ8Ulde2Rrrifu5U9w=
github.com/geoffgarside/ber v1.1.0/go.mod h1:jVPKeCbj6MvQZhwLYsGwaGI52oUorHoHKNecGT85ZCc= github.com/geoffgarside/ber v1.1.0/go.mod h1:jVPKeCbj6MvQZhwLYsGwaGI52oUorHoHKNecGT85ZCc=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gilbertchen/azure-sdk-for-go v14.1.2-0.20180323033227-8fd4663cab7c+incompatible h1:2fZxTUw5D9uGWnYTsU/obVavn+1qTF+TsVok3U8uN2Q= github.com/gilbertchen/azure-sdk-for-go v14.1.2-0.20180323033227-8fd4663cab7c+incompatible h1:2fZxTUw5D9uGWnYTsU/obVavn+1qTF+TsVok3U8uN2Q=
github.com/gilbertchen/azure-sdk-for-go v14.1.2-0.20180323033227-8fd4663cab7c+incompatible/go.mod h1:qsVRCpBUm2l0eMUeI9wZ47yzra2+lv2YkGhMZpzBVUc= github.com/gilbertchen/azure-sdk-for-go v14.1.2-0.20180323033227-8fd4663cab7c+incompatible/go.mod h1:qsVRCpBUm2l0eMUeI9wZ47yzra2+lv2YkGhMZpzBVUc=
github.com/gilbertchen/cli v1.2.1-0.20160223210219-1de0a1836ce9 h1:uMgtTp4sRJ7kMQMF3xEKeFntf3XatwkLNL/byj8v97g= github.com/gilbertchen/cli v1.2.1-0.20160223210219-1de0a1836ce9 h1:uMgtTp4sRJ7kMQMF3xEKeFntf3XatwkLNL/byj8v97g=
github.com/gilbertchen/cli v1.2.1-0.20160223210219-1de0a1836ce9/go.mod h1:WOnN3JdZiZwUaYtLH2DRxe5PpD43wuOIvc/Wem/39M0= github.com/gilbertchen/cli v1.2.1-0.20160223210219-1de0a1836ce9/go.mod h1:WOnN3JdZiZwUaYtLH2DRxe5PpD43wuOIvc/Wem/39M0=
github.com/gilbertchen/go-dropbox v0.0.0-20221004154447-61204091e804 h1:JZ0P02xoeaITbKLFAdBfiH8SNNvKGE2Y/RLdYtWoEVE=
github.com/gilbertchen/go-dropbox v0.0.0-20221004154447-61204091e804/go.mod h1:85+2CRHC/klHy4vEM+TYtbhDo2wMjPa4JNdVzUHsDIk=
github.com/gilbertchen/go-dropbox v0.0.0-20221128142034-9910c19f1d13 h1:54e1HiEXNXGif2PaQHizdGvszIDFE+2yIGzHMUYOQnQ=
github.com/gilbertchen/go-dropbox v0.0.0-20221128142034-9910c19f1d13/go.mod h1:85+2CRHC/klHy4vEM+TYtbhDo2wMjPa4JNdVzUHsDIk=
github.com/gilbertchen/go-dropbox v0.0.0-20221207034530-08c0c180a4f9 h1:3hJHxOyf/rAWWz9GNyai0hSt56vGMATS9B6yjw/bzzk=
github.com/gilbertchen/go-dropbox v0.0.0-20221207034530-08c0c180a4f9/go.mod h1:85+2CRHC/klHy4vEM+TYtbhDo2wMjPa4JNdVzUHsDIk=
github.com/gilbertchen/go-dropbox v0.0.0-20230321030224-087ef8db1916 h1:7VpJiGwW51MB7yJ5e27Ar/ej8Yu7WuU2SEo409qPoNs= github.com/gilbertchen/go-dropbox v0.0.0-20230321030224-087ef8db1916 h1:7VpJiGwW51MB7yJ5e27Ar/ej8Yu7WuU2SEo409qPoNs=
github.com/gilbertchen/go-dropbox v0.0.0-20230321030224-087ef8db1916/go.mod h1:85+2CRHC/klHy4vEM+TYtbhDo2wMjPa4JNdVzUHsDIk= github.com/gilbertchen/go-dropbox v0.0.0-20230321030224-087ef8db1916/go.mod h1:85+2CRHC/klHy4vEM+TYtbhDo2wMjPa4JNdVzUHsDIk=
github.com/gilbertchen/go-ole v1.2.0 h1:ay65uwxo6w8UVOxN0+fuCqUXGaXxbmkGs5m4uY6e1Zw= github.com/gilbertchen/go-ole v1.2.0 h1:ay65uwxo6w8UVOxN0+fuCqUXGaXxbmkGs5m4uY6e1Zw=
@@ -47,50 +74,77 @@ github.com/gilbertchen/highwayhash v0.0.0-20221109044721-eeab1f4799d8 h1:ijgl4Y+
github.com/gilbertchen/highwayhash v0.0.0-20221109044721-eeab1f4799d8/go.mod h1:0lQcVva56+L1PuUFXLOsJ6arJQaU0baIH8q+IegeBhg= github.com/gilbertchen/highwayhash v0.0.0-20221109044721-eeab1f4799d8/go.mod h1:0lQcVva56+L1PuUFXLOsJ6arJQaU0baIH8q+IegeBhg=
github.com/gilbertchen/keyring v0.0.0-20221004152639-1661cbebc508 h1:SqTyk5KkNXp7zTdTttIZSDcTrL5uau4K/2OpKvgBZVI= github.com/gilbertchen/keyring v0.0.0-20221004152639-1661cbebc508 h1:SqTyk5KkNXp7zTdTttIZSDcTrL5uau4K/2OpKvgBZVI=
github.com/gilbertchen/keyring v0.0.0-20221004152639-1661cbebc508/go.mod h1:w/pisxUZezf2XzU9Ewjphcf6q1mZtOzKPHhJiuc8cag= github.com/gilbertchen/keyring v0.0.0-20221004152639-1661cbebc508/go.mod h1:w/pisxUZezf2XzU9Ewjphcf6q1mZtOzKPHhJiuc8cag=
github.com/gilbertchen/xattr v0.0.0-20160926155429-68e7a6806b01 h1:LqwS9qL6SrDkp0g0iwUkETrDdtB9gTKaIbSn9imUq5o=
github.com/gilbertchen/xattr v0.0.0-20160926155429-68e7a6806b01/go.mod h1:TMlibuxKfkdtHyltooAw7+DHqRpaXs9nxaffk00Sh1Q=
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/goamz/goamz v0.0.0-20180131231218-8b901b531db8 h1:G1U0vew/vA/1/hBmf1XNeyIzJJbPFVv+kb+HPl6rj6c= github.com/goamz/goamz v0.0.0-20180131231218-8b901b531db8 h1:G1U0vew/vA/1/hBmf1XNeyIzJJbPFVv+kb+HPl6rj6c=
github.com/goamz/goamz v0.0.0-20180131231218-8b901b531db8/go.mod h1:/Ya1YZsqLQp17bDgHdyE9/XBR1uIH1HKasTvLxcoM/A= github.com/goamz/goamz v0.0.0-20180131231218-8b901b531db8/go.mod h1:/Ya1YZsqLQp17bDgHdyE9/XBR1uIH1HKasTvLxcoM/A=
github.com/godbus/dbus v4.1.0+incompatible h1:WqqLRTsQic3apZUK9qC5sGNfXthmPXzUZ7nQPrNITa4= github.com/godbus/dbus v4.1.0+incompatible h1:WqqLRTsQic3apZUK9qC5sGNfXthmPXzUZ7nQPrNITa4=
github.com/godbus/dbus v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= github.com/godbus/dbus v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4= github.com/golang/protobuf v1.3.5 h1:F768QJ1E9tib+q5Sc8MkdJi1RxLTbRcTf8LJV56aRls=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20211108044417-e9b028704de0 h1:rsq1yB2xiFLDYYaYdlGBsSkwVzsCo500wMhxvW5A/bk= github.com/google/pprof v0.0.0-20211108044417-e9b028704de0 h1:rsq1yB2xiFLDYYaYdlGBsSkwVzsCo500wMhxvW5A/bk=
github.com/google/pprof v0.0.0-20211108044417-e9b028704de0/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg=
github.com/googleapis/gax-go v2.0.0+incompatible h1:j0GKcs05QVmm7yesiZq2+9cxHkNK9YM6zKx4D2qucQU=
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hirochachacha/go-smb2 v1.1.0 h1:b6hs9qKIql9eVXAiN0M2wSFY5xnhbHAQoCwRKbaRTZI= github.com/hirochachacha/go-smb2 v1.1.0 h1:b6hs9qKIql9eVXAiN0M2wSFY5xnhbHAQoCwRKbaRTZI=
github.com/hirochachacha/go-smb2 v1.1.0/go.mod h1:8F1A4d5EZzrGu5R7PU163UcMRDJQl4FtcxjBfsY8TZE= github.com/hirochachacha/go-smb2 v1.1.0/go.mod h1:8F1A4d5EZzrGu5R7PU163UcMRDJQl4FtcxjBfsY8TZE=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2yCeUc= github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2yCeUc=
github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jtolio/eventkit v0.0.0-20221004135224-074cf276595b h1:tO4MX3k5bvV0Sjv5jYrxStMTJxf1m/TW24XRyHji4aU=
github.com/jtolio/eventkit v0.0.0-20221004135224-074cf276595b/go.mod h1:q7yMR8BavTz/gBNtIT/uF487LMgcuEpNGKISLAjNQes=
github.com/jtolio/noiseconn v0.0.0-20230111204749-d7ec1a08b0b8 h1:+A1uT26XjTsxiUUZjAAuveILWWy+Sy2TPX8OIgGvPQE=
github.com/jtolio/noiseconn v0.0.0-20230111204749-d7ec1a08b0b8/go.mod h1:f0ijQHcvHYAuxX6JA/JUr/Z0FVn12D9REaT/HAWVgP4=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.16.3 h1:XuJt9zzcnaz6a16/OU53ZjWp/v7/42WcR5t2a0PcNQY= github.com/klauspost/compress v1.16.3 h1:XuJt9zzcnaz6a16/OU53ZjWp/v7/42WcR5t2a0PcNQY=
@@ -98,29 +152,55 @@ github.com/klauspost/compress v1.16.3/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQs
github.com/klauspost/cpuid v1.2.4/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid v1.2.4/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/cpuid v1.3.1 h1:5JNjFYYQrZeKRJ0734q51WCEEn2huer72Dc7K+R/b6s= github.com/klauspost/cpuid v1.3.1 h1:5JNjFYYQrZeKRJ0734q51WCEEn2huer72Dc7K+R/b6s=
github.com/klauspost/cpuid v1.3.1/go.mod h1:bYW4mA6ZgKPob1/Dlai2LviZJO7KGI3uoWLd42rAQw4= github.com/klauspost/cpuid v1.3.1/go.mod h1:bYW4mA6ZgKPob1/Dlai2LviZJO7KGI3uoWLd42rAQw4=
github.com/klauspost/cpuid/v2 v2.0.12 h1:p9dKCg8i4gmOxtv35DvrYoWqYzQrvEVdjQ762Y0OqZE=
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
github.com/klauspost/reedsolomon v1.9.9 h1:qCL7LZlv17xMixl55nq2/Oa1Y86nfO8EqDfv2GHND54= github.com/klauspost/reedsolomon v1.9.9 h1:qCL7LZlv17xMixl55nq2/Oa1Y86nfO8EqDfv2GHND54=
github.com/klauspost/reedsolomon v1.9.9/go.mod h1:O7yFFHiQwDR6b2t63KPUpccPtNdp5ADgh1gg4fd12wo= github.com/klauspost/reedsolomon v1.9.9/go.mod h1:O7yFFHiQwDR6b2t63KPUpccPtNdp5ADgh1gg4fd12wo=
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lucas-clemente/quic-go v0.25.0/go.mod h1:YtzP8bxRVCBlO77yRanE264+fY/T2U9ZlW1AaHOsMOg=
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/marstr/guid v1.1.0 h1:/M4H/1G4avsieL6BbUwCOBzulmoeKVP5ux/3mQNnbyI= github.com/marstr/guid v1.1.0 h1:/M4H/1G4avsieL6BbUwCOBzulmoeKVP5ux/3mQNnbyI=
github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho= github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho=
github.com/marten-seemann/qpack v0.2.1/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc=
github.com/marten-seemann/qtls-go1-15 v0.1.4/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I=
github.com/marten-seemann/qtls-go1-16 v0.1.4/go.mod h1:gNpI2Ol+lRS3WwSOtIUUtRwZEQMXjYK+dQSBFbethAk=
github.com/marten-seemann/qtls-go1-17 v0.1.0/go.mod h1:fz4HIxByo+LlWcreM4CZOYNuz3taBQ8rN2X6FqvaWo8=
github.com/marten-seemann/qtls-go1-18 v0.1.0-beta.1/go.mod h1:PUhIQk19LoFt2174H4+an8TYvWOGjb/hHwphBeaDHwI=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 h1:lYpkrQH5ajf0OXOcUbGjvZxxijuBwbbmlSxLiuofa+g= github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 h1:lYpkrQH5ajf0OXOcUbGjvZxxijuBwbbmlSxLiuofa+g=
github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ= github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ=
github.com/minio/highwayhash v1.0.1 h1:dZ6IIu8Z14VlC0VpfKofAhCy74wu/Qb5gcn52yWoz/0=
github.com/minio/highwayhash v1.0.1/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY=
github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g= github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g=
github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY= github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY=
github.com/mmcloughlin/avo v0.0.0-20200803215136-443f81d77104 h1:ULR/QWMgcgRiZLUjSSJMU+fW+RDMstRdmnDWj9Q+AsA= github.com/mmcloughlin/avo v0.0.0-20200803215136-443f81d77104 h1:ULR/QWMgcgRiZLUjSSJMU+fW+RDMstRdmnDWj9Q+AsA=
github.com/mmcloughlin/avo v0.0.0-20200803215136-443f81d77104/go.mod h1:wqKykBG2QzQDJEzvRkcS8x6MiSJkF52hXZsXcjaB3ls= github.com/mmcloughlin/avo v0.0.0-20200803215136-443f81d77104/go.mod h1:wqKykBG2QzQDJEzvRkcS8x6MiSJkF52hXZsXcjaB3ls=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8=
github.com/ncw/swift/v2 v2.0.1 h1:q1IN8hNViXEv8Zvg3Xdis4a3c4IlIGezkYz09zQL5J0= github.com/ncw/swift/v2 v2.0.1 h1:q1IN8hNViXEv8Zvg3Xdis4a3c4IlIGezkYz09zQL5J0=
github.com/ncw/swift/v2 v2.0.1/go.mod h1:z0A9RVdYPjNjXVo2pDOPxZ4eu3oarO1P91fTItcb+Kg= github.com/ncw/swift/v2 v2.0.1/go.mod h1:z0A9RVdYPjNjXVo2pDOPxZ4eu3oarO1P91fTItcb+Kg=
github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E=
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY=
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -128,55 +208,92 @@ github.com/pkg/sftp v1.11.0 h1:4Zv0OGbpkg4yNuUtH0s8rvoYxRCNyT29NVUo6pgPmxI=
github.com/pkg/sftp v1.11.0/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pkg/sftp v1.11.0/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
github.com/pkg/xattr v0.4.1 h1:dhclzL6EqOXNaPDWqoeb9tIxATfBSmjqL0b4DpSjwRw= github.com/pkg/xattr v0.4.1 h1:dhclzL6EqOXNaPDWqoeb9tIxATfBSmjqL0b4DpSjwRw=
github.com/pkg/xattr v0.4.1/go.mod h1:W2cGD0TBEus7MkUgv0tNZ9JutLtVO3cXu+IBRuHqnFs= github.com/pkg/xattr v0.4.1/go.mod h1:W2cGD0TBEus7MkUgv0tNZ9JutLtVO3cXu+IBRuHqnFs=
github.com/pkg/xattr v0.4.9 h1:5883YPCtkSd8LFbs13nXplj9g9tlrwoJRjgpgMu1/fE=
github.com/pkg/xattr v0.4.9/go.mod h1:di8WF84zAKk8jzR1UBTEWh9AUlIZZ7M/JNt8e9B6ktU=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/quic-go/qtls-go1-20 v0.3.2 h1:rRgN3WfnKbyik4dBV8A6girlJVxGand/d+jVKbQq5GI= github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/quic-go/quic-go v0.38.0 h1:T45lASr5q/TrVwt+jrVccmqHhPL2XuSyoCLVCpfOSLc= github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/segmentio/go-env v1.1.0 h1:AGJ7OnCx9M5NWpkYPGYELS6III/pFSnAs1GvKWStiEo= github.com/segmentio/go-env v1.1.0 h1:AGJ7OnCx9M5NWpkYPGYELS6III/pFSnAs1GvKWStiEo=
github.com/segmentio/go-env v1.1.0/go.mod h1:pEKO2ieHe8zF098OMaAHw21SajMuONlnI/vJNB3pB7I= github.com/segmentio/go-env v1.1.0/go.mod h1:pEKO2ieHe8zF098OMaAHw21SajMuONlnI/vJNB3pB7I=
github.com/spacemonkeygo/monkit/v3 v3.0.20-0.20230227152157-d00b379de191 h1:QVUfVxilbPp8fBJ7701LL/WEUjBSiSxbs9LUaCIe5qM= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/spacemonkeygo/monkit/v3 v3.0.20-0.20230227152157-d00b379de191/go.mod h1:kj1ViJhlyADa7DiA4xVnTuPA46lFKbM7mxQTrXCuJP4= github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=
github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM=
github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0=
github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw=
github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI=
github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU=
github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag=
github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg=
github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw=
github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y=
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q=
github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ=
github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I=
github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0=
github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ=
github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk=
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4=
github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw=
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
github.com/spacemonkeygo/monkit/v3 v3.0.4/go.mod h1:JcK1pCbReQsOsMKF/POFSZCq7drXFybgGmbc27tuwes=
github.com/spacemonkeygo/monkit/v3 v3.0.17 h1:rqIuLhRUr2UtS3WNVbPY/BwvjlwKVvSOVY5p0QVocxE=
github.com/spacemonkeygo/monkit/v3 v3.0.17/go.mod h1:kj1ViJhlyADa7DiA4xVnTuPA46lFKbM7mxQTrXCuJP4=
github.com/spacemonkeygo/monotime v0.0.0-20180824235756-e3f48a95f98a/go.mod h1:ul4bvvnCOPZgq8w0nTkSmWVg/hauVpFS97Am1YM1XXo=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec h1:DGmKwyZwEB8dI7tbLt/I/gQuP559o/0FrAkHKlQM/Ks= github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec h1:DGmKwyZwEB8dI7tbLt/I/gQuP559o/0FrAkHKlQM/Ks=
github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec/go.mod h1:owBmyHYMLkxyrugmfwE/DLJyW8Ro9mkphwuVErQ0iUw= github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec/go.mod h1:owBmyHYMLkxyrugmfwE/DLJyW8Ro9mkphwuVErQ0iUw=
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
github.com/vivint/infectious v0.0.0-20200605153912-25a574ae18a3 h1:zMsHhfK9+Wdl1F7sIKLyx3wrOFofpb3rWFbA4HgcK5k= github.com/vivint/infectious v0.0.0-20200605153912-25a574ae18a3 h1:zMsHhfK9+Wdl1F7sIKLyx3wrOFofpb3rWFbA4HgcK5k=
github.com/vivint/infectious v0.0.0-20200605153912-25a574ae18a3/go.mod h1:R0Gbuw7ElaGSLOZUSwBm/GgVwMd30jWxBDdAyMOeTuc= github.com/vivint/infectious v0.0.0-20200605153912-25a574ae18a3/go.mod h1:R0Gbuw7ElaGSLOZUSwBm/GgVwMd30jWxBDdAyMOeTuc=
github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI= github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI=
github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/zeebo/assert v1.3.1 h1:vukIABvugfNMZMQO1ABsyQDJDTVQbn+LWSMy1ol1h6A= github.com/zeebo/admission/v3 v3.0.3/go.mod h1:2OWyAS5yo0Xvj2AEUosOjTUHxaY0oIIiCrXGKCYzWpo=
github.com/zeebo/assert v1.3.1/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg= github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvvKCaQ= github.com/zeebo/errs v1.2.2/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4=
github.com/zeebo/errs v1.3.0 h1:hmiaKqgYZzcVgRL1Vkc1Mn2914BbzB0IBxs+ebeutGs= github.com/zeebo/errs v1.3.0 h1:hmiaKqgYZzcVgRL1Vkc1Mn2914BbzB0IBxs+ebeutGs=
github.com/zeebo/errs v1.3.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= github.com/zeebo/errs v1.3.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4=
github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo= github.com/zeebo/float16 v0.1.0/go.mod h1:fssGvvXu+XS8MH57cKmyrLB/cqioYeYX/2mXCN3a5wo=
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4= github.com/zeebo/incenc v0.0.0-20180505221441-0d92902eec54/go.mod h1:EI8LcOBDlSL3POyqwC1eJhOYlMBMidES+613EtmmT5w=
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.3 h1:8sGtKOrtQqkN1bp2AtX+misvLIlOmsEsNd+9NIcPEm8= go.opencensus.io v0.22.3 h1:8sGtKOrtQqkN1bp2AtX+misvLIlOmsEsNd+9NIcPEm8=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
golang.org/x/arch v0.0.0-20190909030613-46d78d1859ac/go.mod h1:flIaEI6LNU6xOCD5PaJvn9wGP0agmIOqjrtsKGRguv4= golang.org/x/arch v0.0.0-20190909030613-46d78d1859ac/go.mod h1:flIaEI6LNU6xOCD5PaJvn9wGP0agmIOqjrtsKGRguv4=
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838 h1:71vQrMauZZhcTVK6KdYM+rklehEEwb3E+ZhaE5jrPrE=
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db h1:D/cFflL63o2KSLJIwjlcIt8PR064j/xsmdEJL/YvY/o= golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -184,13 +301,17 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
@@ -198,15 +319,21 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -214,65 +341,93 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181021155630-eda9bb28ed51/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181021155630-eda9bb28ed51/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190130150945-aca44879d564/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190130150945-aca44879d564/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27 h1:XDXtA5hveEEV8JB2l7nhMTp3t3cHp9ZpwcdjqyEWLlo=
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0=
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200425043458-8463f397d07c/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200425043458-8463f397d07c/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo= golang.org/x/tools v0.1.1 h1:wGiQel/hW0NnEkJUk8lbzkX2gFJU6PFxf1v5OlCfuOs=
golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.21.0 h1:zS+Q/CJJnVlXpXQVIz+lH0ZT2lBuT2ac7XD8Y/3w6hY= google.golang.org/api v0.21.0 h1:zS+Q/CJJnVlXpXQVIz+lH0ZT2lBuT2ac7XD8Y/3w6hY=
google.golang.org/api v0.21.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.21.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM= google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200409111301-baae70f3302d h1:I7Vuu5Ejagca+VcgfBINHke3xwjCTYnIG4Q57fv0wYY= google.golang.org/genproto v0.0.0-20200409111301-baae70f3302d h1:I7Vuu5Ejagca+VcgfBINHke3xwjCTYnIG4Q57fv0wYY=
google.golang.org/genproto v0.0.0-20200409111301-baae70f3302d/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200409111301-baae70f3302d/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
@@ -280,25 +435,42 @@ google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQ
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.1 h1:C1QC6KzgSiLyBabDi87BbjaGreoRgGUF5nOyvfrAZ1k= google.golang.org/grpc v1.28.1 h1:C1QC6KzgSiLyBabDi87BbjaGreoRgGUF5nOyvfrAZ1k=
google.golang.org/grpc v1.28.1/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= google.golang.org/grpc v1.28.1/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
storj.io/common v0.0.0-20230907123639-5fd0608fd947 h1:X75A5hX1nFjQH8GIvei4T1LNQTLa++bsDKMxXxfPHE8= sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=
storj.io/common v0.0.0-20230907123639-5fd0608fd947/go.mod h1:FMVOxf2+SgsmfjxwFCM1MZCKwXis4U7l22M/6nIhIas= sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=
storj.io/drpc v0.0.33 h1:yCGZ26r66ZdMP0IcTYsj7WDAUIIjzXk6DJhbhvt9FHI= storj.io/common v0.0.0-20220414110316-a5cb7172d6bf h1:D5xZTDOlTTQWdAWeKKm2pFLcz1sceH+f/pVAcYB9jL8=
storj.io/drpc v0.0.33/go.mod h1:vR804UNzhBa49NOJ6HeLjd2H3MakC1j5Gv8bsOQT6N4= storj.io/common v0.0.0-20220414110316-a5cb7172d6bf/go.mod h1:LBJrpAqL4MNSrhGEwc8SJ+tIVtgfCtFEZqDy6/0j67A=
storj.io/picobuf v0.0.2-0.20230906122608-c4ba17033c6c h1:or/DtG5uaZpzimL61ahlgAA+MTYn/U3txz4fe+XBFUg= storj.io/drpc v0.0.30 h1:jqPe4T9KEu3CDBI05A2hCMgMSHLtd/E0N0yTF9QreIE=
storj.io/picobuf v0.0.2-0.20230906122608-c4ba17033c6c/go.mod h1:JCuc3C0gzCJHQ4J6SOx/Yjg+QTpX0D+Fvs5H46FETCk= storj.io/drpc v0.0.30/go.mod h1:6rcOyR/QQkSTX/9L5ZGtlZaE2PtXTTZl8d+ulSeeYEg=
storj.io/uplink v1.12.0 h1:rTODjbKRo/lzz5Hp0isjoRfqDcH7kJg6aujD2M9v9Ro= storj.io/uplink v1.9.0 h1:Zg1kX1VqOQIKm0yAukteKpLuT68Be3euyNRML612ERM=
storj.io/uplink v1.12.0/go.mod h1:nMAuoWi5AHio+8NQa33VRzCiRg0B0UhYKuT0a0CdXOg= storj.io/uplink v1.9.0/go.mod h1:f6D8306j5mnRHnPDKWCiwtPM6ukyGg77to9LaAY9l6k=

View File

@@ -396,7 +396,7 @@ type B2ListFileNamesOutput struct {
func (client *B2Client) ListFileNames(threadIndex int, startFileName string, singleFile bool, includeVersions bool) (files []*B2Entry, err error) { func (client *B2Client) ListFileNames(threadIndex int, startFileName string, singleFile bool, includeVersions bool) (files []*B2Entry, err error) {
maxFileCount := 10_000 maxFileCount := 1000
if singleFile { if singleFile {
if includeVersions { if includeVersions {
maxFileCount = 4 maxFileCount = 4

View File

@@ -7,9 +7,9 @@ package duplicacy
import ( import (
"bytes" "bytes"
"encoding/hex" "encoding/hex"
"encoding/json"
"fmt" "fmt"
"io" "io"
"math"
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
@@ -21,11 +21,12 @@ import (
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/vmihailenco/msgpack" "github.com/vmihailenco/msgpack"
) )
// BackupManager performs the two major operations, backup and restore, and passes other operations, mostly related to // BackupManager performs the two major operations, backup and restore, and passes other operations, mostly related to
// snapshot management, to the snapshot manager. // snapshot management, to the snapshot manager.
type BackupManager struct { type BackupManager struct {
snapshotID string // Unique id for each repository snapshotID string // Unique id for each repository
storage Storage // the storage for storing backups storage Storage // the storage for storing backups
@@ -33,35 +34,15 @@ type BackupManager struct {
SnapshotManager *SnapshotManager // the snapshot manager SnapshotManager *SnapshotManager // the snapshot manager
snapshotCache *FileStorage // for copies of chunks needed by snapshots snapshotCache *FileStorage // for copies of chunks needed by snapshots
config *Config // contains a number of options config *Config // contains a number of options
options BackupManagerOptions
nobackupFile string // don't backup directory when this file name is found
filtersFile string // the path to the filters file
excludeByAttribute bool // don't backup file based on file attribute
cachePath string cachePath string
} }
type BackupManagerOptions struct {
NobackupFile string // don't backup directory when this file name is found
FiltersFile string // the path to the filters file
ExcludeByAttribute bool // don't backup file based on file attribute
SetOwner bool
ExcludeXattrs bool
NormalizeXattrs bool
IncludeFileFlags bool
IncludeSpecials bool
FileFlagsMask uint32
}
type RestoreOptions struct {
Threads int
Patterns []string
InPlace bool
QuickMode bool
Overwrite bool
DeleteMode bool
ShowStatistics bool
AllowFailures bool
}
func (manager *BackupManager) SetDryRun(dryRun bool) { func (manager *BackupManager) SetDryRun(dryRun bool) {
manager.config.dryRun = dryRun manager.config.dryRun = dryRun
} }
@@ -73,8 +54,7 @@ func (manager *BackupManager) SetCompressionLevel(level int) {
// CreateBackupManager creates a backup manager using the specified 'storage'. 'snapshotID' is a unique id to // CreateBackupManager creates a backup manager using the specified 'storage'. 'snapshotID' is a unique id to
// identify snapshots created for this repository. 'top' is the top directory of the repository. 'password' is the // identify snapshots created for this repository. 'top' is the top directory of the repository. 'password' is the
// master key which can be nil if encryption is not enabled. // master key which can be nil if encryption is not enabled.
func CreateBackupManager(snapshotID string, storage Storage, top string, password string, func CreateBackupManager(snapshotID string, storage Storage, top string, password string, nobackupFile string, filtersFile string, excludeByAttribute bool) *BackupManager {
options *BackupManagerOptions) *BackupManager {
config, _, err := DownloadConfig(storage, password) config, _, err := DownloadConfig(storage, password)
if err != nil { if err != nil {
@@ -94,11 +74,13 @@ func CreateBackupManager(snapshotID string, storage Storage, top string, passwor
SnapshotManager: snapshotManager, SnapshotManager: snapshotManager,
config: config, config: config,
options: *options,
} nobackupFile: nobackupFile,
if options != nil {
backupManager.options = *options filtersFile: filtersFile,
excludeByAttribute: excludeByAttribute,
} }
if IsDebugging() { if IsDebugging() {
@@ -149,7 +131,8 @@ func (manager *BackupManager) SetupSnapshotCache(storageName string) bool {
func (manager *BackupManager) Backup(top string, quickMode bool, threads int, tag string, func (manager *BackupManager) Backup(top string, quickMode bool, threads int, tag string,
showStatistics bool, shadowCopy bool, shadowCopyTimeout int, enumOnly bool, metadataChunkSize int, maximumInMemoryEntries int) bool { showStatistics bool, shadowCopy bool, shadowCopyTimeout int, enumOnly bool, metadataChunkSize int, maximumInMemoryEntries int) bool {
top, err := filepath.Abs(top) var err error
top, err = filepath.Abs(top)
if err != nil { if err != nil {
LOG_ERROR("REPOSITORY_ERR", "Failed to obtain the absolute path of the repository: %v", err) LOG_ERROR("REPOSITORY_ERR", "Failed to obtain the absolute path of the repository: %v", err)
return false return false
@@ -163,14 +146,14 @@ func (manager *BackupManager) Backup(top string, quickMode bool, threads int, ta
if manager.config.DataShards != 0 && manager.config.ParityShards != 0 { if manager.config.DataShards != 0 && manager.config.ParityShards != 0 {
LOG_INFO("BACKUP_ERASURECODING", "Erasure coding is enabled with %d data shards and %d parity shards", LOG_INFO("BACKUP_ERASURECODING", "Erasure coding is enabled with %d data shards and %d parity shards",
manager.config.DataShards, manager.config.ParityShards) manager.config.DataShards, manager.config.ParityShards)
} }
if manager.config.rsaPublicKey != nil && len(manager.config.FileKey) > 0 { if manager.config.rsaPublicKey != nil && len(manager.config.FileKey) > 0 {
LOG_INFO("BACKUP_KEY", "RSA encryption is enabled") LOG_INFO("BACKUP_KEY", "RSA encryption is enabled")
} }
if manager.options.ExcludeByAttribute { if manager.excludeByAttribute {
LOG_INFO("BACKUP_EXCLUDE", "Exclude files with no-backup attributes") LOG_INFO("BACKUP_EXCLUDE", "Exclude files with no-backup attributes")
} }
@@ -204,7 +187,7 @@ func (manager *BackupManager) Backup(top string, quickMode bool, threads int, ta
// If the listing operation is fast and this is an initial backup, list all chunks and // If the listing operation is fast and this is an initial backup, list all chunks and
// put them in the cache. // put them in the cache.
if manager.storage.IsFastListing() && remoteSnapshot.Revision == 0 { if (manager.storage.IsFastListing() && remoteSnapshot.Revision == 0) {
LOG_INFO("BACKUP_LIST", "Listing all chunks") LOG_INFO("BACKUP_LIST", "Listing all chunks")
allChunks, _ := manager.SnapshotManager.ListAllFiles(manager.storage, "chunks/") allChunks, _ := manager.SnapshotManager.ListAllFiles(manager.storage, "chunks/")
@@ -239,7 +222,7 @@ func (manager *BackupManager) Backup(top string, quickMode bool, threads int, ta
var totalModifiedFileSize int64 // total size of modified files var totalModifiedFileSize int64 // total size of modified files
var uploadedModifiedFileSize int64 // portions that have been uploaded (including cache hits) var uploadedModifiedFileSize int64 // portions that have been uploaded (including cache hits)
var preservedFileSize int64 // total size of unmodified files var preservedFileSize int64 // total size of unmodified files
localSnapshot := CreateEmptySnapshot(manager.snapshotID) localSnapshot := CreateEmptySnapshot(manager.snapshotID)
localSnapshot.Revision = remoteSnapshot.Revision + 1 localSnapshot.Revision = remoteSnapshot.Revision + 1
@@ -255,17 +238,8 @@ func (manager *BackupManager) Backup(top string, quickMode bool, threads int, ta
go func() { go func() {
// List local files // List local files
defer CatchLogException() defer CatchLogException()
localSnapshot.ListLocalFiles(shadowTop, localListingChannel, &skippedDirectories, &skippedFiles, localSnapshot.ListLocalFiles(shadowTop, manager.nobackupFile, manager.filtersFile, manager.excludeByAttribute, localListingChannel, &skippedDirectories, &skippedFiles)
&ListFilesOptions{ } ()
NoBackupFile: manager.options.NobackupFile,
FiltersFile: manager.options.FiltersFile,
ExcludeByAttribute: manager.options.ExcludeByAttribute,
ExcludeXattrs: manager.options.ExcludeXattrs,
NormalizeXattr: manager.options.NormalizeXattrs,
IncludeFileFlags: manager.options.IncludeFileFlags,
IncludeSpecials: manager.options.IncludeSpecials,
})
}()
go func() { go func() {
// List remote files // List remote files
@@ -287,7 +261,7 @@ func (manager *BackupManager) Backup(top string, quickMode bool, threads int, ta
}) })
} }
close(remoteListingChannel) close(remoteListingChannel)
}() } ()
// Create the local file list // Create the local file list
localEntryList, err := CreateEntryList(manager.snapshotID, manager.cachePath, maximumInMemoryEntries) localEntryList, err := CreateEntryList(manager.snapshotID, manager.cachePath, maximumInMemoryEntries)
@@ -301,7 +275,7 @@ func (manager *BackupManager) Backup(top string, quickMode bool, threads int, ta
var remoteEntry *Entry var remoteEntry *Entry
remoteListingOK := true remoteListingOK := true
for { for {
localEntry := <-localListingChannel localEntry := <- localListingChannel
if localEntry == nil { if localEntry == nil {
break break
} }
@@ -315,7 +289,7 @@ func (manager *BackupManager) Backup(top string, quickMode bool, threads int, ta
compareResult = localEntry.Compare(remoteEntry) compareResult = localEntry.Compare(remoteEntry)
} else { } else {
if remoteListingOK { if remoteListingOK {
remoteEntry, remoteListingOK = <-remoteListingChannel remoteEntry, remoteListingOK = <- remoteListingChannel
} }
if !remoteListingOK { if !remoteListingOK {
compareResult = -1 compareResult = -1
@@ -330,7 +304,7 @@ func (manager *BackupManager) Backup(top string, quickMode bool, threads int, ta
remoteEntry = nil remoteEntry = nil
} }
if compareResult == 0 { if compareResult == 0 {
// No need to check if it is in hash mode -- in that case remote listing is nil // No need to check if it is in hash mode -- in that case remote listing is nil
if localEntry.IsSameAs(remoteEntry) && localEntry.IsFile() { if localEntry.IsSameAs(remoteEntry) && localEntry.IsFile() {
if localEntry.Size > 0 { if localEntry.Size > 0 {
@@ -365,8 +339,8 @@ func (manager *BackupManager) Backup(top string, quickMode bool, threads int, ta
// compareResult must be < 0; the local file is new // compareResult must be < 0; the local file is new
totalModifiedFileSize += localEntry.Size totalModifiedFileSize += localEntry.Size
if localEntry.Size > 0 { if localEntry.Size > 0 {
// A size of -1 indicates this is a modified file that will be uploaded // A size of -1 indicates this is a modified file that will be uploaded
localEntry.Size = -1 localEntry.Size = -1
} }
} }
@@ -474,7 +448,7 @@ func (manager *BackupManager) Backup(top string, quickMode bool, threads int, ta
_, found := chunkCache[chunkID] _, found := chunkCache[chunkID]
if found { if found {
if time.Now().Unix()-lastUploadingTime > keepUploadAlive { if time.Now().Unix() - lastUploadingTime > keepUploadAlive {
LOG_INFO("UPLOAD_KEEPALIVE", "Skip chunk cache to keep connection alive") LOG_INFO("UPLOAD_KEEPALIVE", "Skip chunk cache to keep connection alive")
found = false found = false
} }
@@ -584,7 +558,7 @@ func (manager *BackupManager) Backup(top string, quickMode bool, threads int, ta
if showStatistics { if showStatistics {
LOG_INFO("BACKUP_STATS", "Files: %d total, %s bytes; %d new, %s bytes", LOG_INFO("BACKUP_STATS", "Files: %d total, %s bytes; %d new, %s bytes",
localEntryList.NumberOfEntries-int64(len(skippedFiles)), localEntryList.NumberOfEntries - int64(len(skippedFiles)),
PrettyNumber(preservedFileSize+uploadedFileSize), PrettyNumber(preservedFileSize+uploadedFileSize),
len(localEntryList.ModifiedEntries), PrettyNumber(uploadedFileSize)) len(localEntryList.ModifiedEntries), PrettyNumber(uploadedFileSize))
@@ -649,34 +623,21 @@ func (manager *BackupManager) Backup(top string, quickMode bool, threads int, ta
} }
// Restore downloads the specified snapshot, compares it with what's on the repository, and then downloads // Restore downloads the specified snapshot, compares it with what's on the repository, and then downloads
// files that are different.'QuickMode' will bypass files with unchanged sizes and timestamps. 'DeleteMode' will // files that are different. 'base' is a directory that contains files at a different revision which can
// remove local files that don't exist in the snapshot. 'Patterns' is used to include/exclude certain files. // serve as a local cache to avoid download chunks available locally. It is perfectly ok for 'base' to be
func (manager *BackupManager) Restore(top string, revision int, options *RestoreOptions) int { // the same as 'top'. 'quickMode' will bypass files with unchanged sizes and timestamps. 'deleteMode' will
if options.Threads < 1 { // remove local files that don't exist in the snapshot. 'patterns' is used to include/exclude certain files.
options.Threads = 1 func (manager *BackupManager) Restore(top string, revision int, inPlace bool, quickMode bool, threads int, overwrite bool,
} deleteMode bool, setOwner bool, showStatistics bool, patterns []string, allowFailures bool) int {
patterns := options.Patterns
overwrite := options.Overwrite
allowFailures := options.AllowFailures
metadataOptions := RestoreMetadataOptions{
SetOwner: manager.options.SetOwner,
ExcludeXattrs: manager.options.ExcludeXattrs,
NormalizeXattrs: manager.options.NormalizeXattrs,
IncludeFileFlags: manager.options.IncludeFileFlags,
FileFlagsMask: manager.options.FileFlagsMask,
}
startTime := time.Now().Unix() startTime := time.Now().Unix()
LOG_DEBUG("RESTORE_PARAMETERS", "top: %s, revision: %d, in-place: %t, quick: %t, delete: %t", LOG_DEBUG("RESTORE_PARAMETERS", "top: %s, revision: %d, in-place: %t, quick: %t, delete: %t",
top, revision, options.InPlace, options.QuickMode, options.DeleteMode) top, revision, inPlace, quickMode, deleteMode)
if !strings.HasPrefix(GetDuplicacyPreferencePath(), top) { if !strings.HasPrefix(GetDuplicacyPreferencePath(), top) {
LOG_INFO("RESTORE_INPLACE", "Forcing in-place mode with a non-default preference path") LOG_INFO("RESTORE_INPLACE", "Forcing in-place mode with a non-default preference path")
options.InPlace = true inPlace = true
} }
if len(patterns) > 0 { if len(patterns) > 0 {
@@ -718,24 +679,14 @@ func (manager *BackupManager) Restore(top string, revision int, options *Restore
localListingChannel := make(chan *Entry) localListingChannel := make(chan *Entry)
remoteListingChannel := make(chan *Entry) remoteListingChannel := make(chan *Entry)
chunkOperator := CreateChunkOperator(manager.config, manager.storage, manager.snapshotCache, options.ShowStatistics, chunkOperator := CreateChunkOperator(manager.config, manager.storage, manager.snapshotCache, showStatistics, false, threads, allowFailures)
false, options.Threads, allowFailures)
LOG_INFO("RESTORE_INDEXING", "Indexing %s", top) LOG_INFO("RESTORE_INDEXING", "Indexing %s", top)
go func() { go func() {
// List local files // List local files
defer CatchLogException() defer CatchLogException()
localSnapshot.ListLocalFiles(top, localListingChannel, nil, nil, localSnapshot.ListLocalFiles(top, manager.nobackupFile, manager.filtersFile, manager.excludeByAttribute, localListingChannel, nil, nil)
&ListFilesOptions{ } ()
NoBackupFile: manager.options.NobackupFile,
FiltersFile: manager.options.FiltersFile,
ExcludeByAttribute: manager.options.ExcludeByAttribute,
ExcludeXattrs: manager.options.ExcludeXattrs,
NormalizeXattr: manager.options.NormalizeXattrs,
IncludeFileFlags: manager.options.IncludeFileFlags,
IncludeSpecials: manager.options.IncludeSpecials,
})
}()
remoteSnapshot := manager.SnapshotManager.DownloadSnapshot(manager.snapshotID, revision) remoteSnapshot := manager.SnapshotManager.DownloadSnapshot(manager.snapshotID, revision)
manager.SnapshotManager.DownloadSnapshotSequences(remoteSnapshot) manager.SnapshotManager.DownloadSnapshotSequences(remoteSnapshot)
@@ -747,47 +698,13 @@ func (manager *BackupManager) Restore(top string, revision int, options *Restore
return true return true
}) })
close(remoteListingChannel) close(remoteListingChannel)
}() } ()
var localEntry *Entry var localEntry *Entry
localListingOK := true localListingOK := true
type hardLinkEntry struct {
entry *Entry
willExist bool
}
var hardLinkTable []hardLinkEntry
var hardLinks []*Entry
restoreHardLink := func(entry *Entry, fullPath string) bool {
if entry.IsHardLinkRoot() {
hardLinkTable[len(hardLinkTable)-1].willExist = true
} else if entry.IsHardLinkChild() {
i, err := entry.GetHardLinkId()
if err != nil {
LOG_ERROR("RESTORE_HARDLINK", "Decode error for hard link entry %s: %v", entry.Path, err)
return false
}
if !hardLinkTable[i].willExist {
hardLinkTable[i] = hardLinkEntry{entry, true}
} else {
sourcePath := joinPath(top, hardLinkTable[i].entry.Path)
if err := MakeHardlink(sourcePath, fullPath); err != nil {
LOG_ERROR("RESTORE_HARDLINK", "Failed to create hard link %s to %s: %v", fullPath, sourcePath, err)
}
LOG_TRACE("DOWNLOAD_DONE", "Hard linked %s to %s", entry.Path, hardLinkTable[i].entry.Path)
return true
}
}
return false
}
for remoteEntry := range remoteListingChannel { for remoteEntry := range remoteListingChannel {
if remoteEntry.IsHardLinkRoot() {
hardLinkTable = append(hardLinkTable, hardLinkEntry{remoteEntry, false})
}
if len(patterns) > 0 && !MatchPath(remoteEntry.Path, patterns) { if len(patterns) > 0 && !MatchPath(remoteEntry.Path, patterns) {
continue continue
} }
@@ -797,7 +714,7 @@ func (manager *BackupManager) Restore(top string, revision int, options *Restore
for { for {
if localEntry == nil && localListingOK { if localEntry == nil && localListingOK {
localEntry, localListingOK = <-localListingChannel localEntry, localListingOK = <- localListingChannel
} }
if localEntry == nil { if localEntry == nil {
compareResult = 1 compareResult = 1
@@ -813,7 +730,7 @@ func (manager *BackupManager) Restore(top string, revision int, options *Restore
} }
if compareResult == 0 { if compareResult == 0 {
if options.QuickMode && localEntry.IsFile() && localEntry.IsSameAs(remoteEntry) { if quickMode && localEntry.IsFile() && localEntry.IsSameAs(remoteEntry) {
LOG_TRACE("RESTORE_SKIP", "File %s unchanged (by size and timestamp)", localEntry.Path) LOG_TRACE("RESTORE_SKIP", "File %s unchanged (by size and timestamp)", localEntry.Path)
skippedFileSize += localEntry.Size skippedFileSize += localEntry.Size
skippedFileCount++ skippedFileCount++
@@ -824,40 +741,27 @@ func (manager *BackupManager) Restore(top string, revision int, options *Restore
} }
fullPath := joinPath(top, remoteEntry.Path) fullPath := joinPath(top, remoteEntry.Path)
if remoteEntry.IsLink() { if remoteEntry.IsLink() {
if stat, _ := os.Lstat(fullPath); stat != nil { stat, err := os.Lstat(fullPath)
if stat != nil {
if stat.Mode()&os.ModeSymlink != 0 { if stat.Mode()&os.ModeSymlink != 0 {
isRegular, link, err := Readlink(fullPath) isRegular, link, err := Readlink(fullPath)
if err == nil && link == remoteEntry.Link && !isRegular { if err == nil && link == remoteEntry.Link && !isRegular {
remoteEntry.RestoreMetadata(fullPath, stat, metadataOptions) remoteEntry.RestoreMetadata(fullPath, nil, setOwner)
if remoteEntry.IsHardLinkRoot() {
hardLinkTable[len(hardLinkTable)-1].willExist = true
}
continue continue
} }
} }
if !overwrite {
LOG_WERROR(allowFailures, "DOWNLOAD_OVERWRITE",
"File %s already exists. Please specify the -overwrite option to overwrite", remoteEntry.Path)
continue
}
os.Remove(fullPath) os.Remove(fullPath)
} }
if restoreHardLink(remoteEntry, fullPath) { err = os.Symlink(remoteEntry.Link, fullPath)
continue if err != nil {
}
if err := os.Symlink(remoteEntry.Link, fullPath); err != nil {
LOG_ERROR("RESTORE_SYMLINK", "Can't create symlink %s: %v", remoteEntry.Path, err) LOG_ERROR("RESTORE_SYMLINK", "Can't create symlink %s: %v", remoteEntry.Path, err)
return 0 return 0
} }
remoteEntry.RestoreMetadata(fullPath, nil, metadataOptions) remoteEntry.RestoreMetadata(fullPath, nil, setOwner)
LOG_TRACE("DOWNLOAD_DONE", "Symlink %s updated", remoteEntry.Path) LOG_TRACE("DOWNLOAD_DONE", "Symlink %s updated", remoteEntry.Path)
} else if remoteEntry.IsDir() { } else if remoteEntry.IsDir() {
stat, err := os.Stat(fullPath) stat, err := os.Stat(fullPath)
@@ -876,58 +780,8 @@ func (manager *BackupManager) Restore(top string, revision int, options *Restore
return 0 return 0
} }
} }
if metadataOptions.IncludeFileFlags {
err = remoteEntry.RestoreEarlyDirFlags(fullPath, manager.options.FileFlagsMask)
if err != nil {
LOG_WARN("DOWNLOAD_FLAGS", "Failed to set early file flags on %s: %v", fullPath, err)
}
}
directoryEntries = append(directoryEntries, remoteEntry) directoryEntries = append(directoryEntries, remoteEntry)
} else if remoteEntry.IsSpecial() && manager.options.IncludeSpecials {
if stat, _ := os.Lstat(fullPath); stat != nil {
if remoteEntry.IsSameSpecial(stat) {
remoteEntry.RestoreMetadata(fullPath, nil, metadataOptions)
if remoteEntry.IsHardLinkRoot() {
hardLinkTable[len(hardLinkTable)-1].willExist = true
}
continue
}
if !overwrite {
LOG_WERROR(allowFailures, "DOWNLOAD_OVERWRITE",
"File %s already exists. Please specify the -overwrite option to overwrite", remoteEntry.Path)
continue
}
os.Remove(fullPath)
}
if restoreHardLink(remoteEntry, fullPath) {
continue
}
if err := remoteEntry.RestoreSpecial(fullPath); err != nil {
LOG_ERROR("RESTORE_SPECIAL", "Failed to restore special file %s: %v", fullPath, err)
return 0
}
remoteEntry.RestoreMetadata(fullPath, nil, metadataOptions)
LOG_TRACE("DOWNLOAD_DONE", "Special %s %s restored", remoteEntry.Path, remoteEntry.FmtSpecial())
} else { } else {
if remoteEntry.IsHardLinkRoot() {
hardLinkTable[len(hardLinkTable)-1].willExist = true
} else if remoteEntry.IsHardLinkChild() {
i, err := remoteEntry.GetHardLinkId()
if err != nil {
LOG_ERROR("RESTORE_HARDLINK", "Decode error for hard link entry %s: %v", remoteEntry.Path, err)
return 0
}
if !hardLinkTable[i].willExist {
hardLinkTable[i] = hardLinkEntry{remoteEntry, true}
} else {
hardLinks = append(hardLinks, remoteEntry)
continue
}
}
// We can't download files here since fileEntries needs to be sorted // We can't download files here since fileEntries needs to be sorted
fileEntries = append(fileEntries, remoteEntry) fileEntries = append(fileEntries, remoteEntry)
totalFileSize += remoteEntry.Size totalFileSize += remoteEntry.Size
@@ -939,7 +793,7 @@ func (manager *BackupManager) Restore(top string, revision int, options *Restore
} }
for localListingOK { for localListingOK {
localEntry, localListingOK = <-localListingChannel localEntry, localListingOK = <- localListingChannel
if localEntry != nil { if localEntry != nil {
extraFiles = append(extraFiles, localEntry.Path) extraFiles = append(extraFiles, localEntry.Path)
} }
@@ -982,7 +836,7 @@ func (manager *BackupManager) Restore(top string, revision int, options *Restore
fullPath := joinPath(top, file.Path) fullPath := joinPath(top, file.Path)
stat, _ := os.Stat(fullPath) stat, _ := os.Stat(fullPath)
if stat != nil { if stat != nil {
if options.QuickMode { if quickMode {
if file.IsSameAsFileInfo(stat) { if file.IsSameAsFileInfo(stat) {
LOG_TRACE("RESTORE_SKIP", "File %s unchanged (by size and timestamp)", file.Path) LOG_TRACE("RESTORE_SKIP", "File %s unchanged (by size and timestamp)", file.Path)
skippedFileSize += file.Size skippedFileSize += file.Size
@@ -1014,8 +868,8 @@ func (manager *BackupManager) Restore(top string, revision int, options *Restore
} }
newFile.Close() newFile.Close()
file.RestoreMetadata(fullPath, nil, metadataOptions) file.RestoreMetadata(fullPath, nil, setOwner)
if !options.ShowStatistics { if !showStatistics {
LOG_INFO("DOWNLOAD_DONE", "Downloaded %s (0)", file.Path) LOG_INFO("DOWNLOAD_DONE", "Downloaded %s (0)", file.Path)
downloadedFileSize += file.Size downloadedFileSize += file.Size
downloadedFiles = append(downloadedFiles, file) downloadedFiles = append(downloadedFiles, file)
@@ -1024,13 +878,8 @@ func (manager *BackupManager) Restore(top string, revision int, options *Restore
continue continue
} }
fileFlagsMask := metadataOptions.FileFlagsMask downloaded, err := manager.RestoreFile(chunkDownloader, chunkMaker, file, top, inPlace, overwrite, showStatistics,
if !metadataOptions.IncludeFileFlags { totalFileSize, downloadedFileSize, startDownloadingTime, allowFailures)
fileFlagsMask = math.MaxUint32
}
downloaded, err := manager.RestoreFile(chunkDownloader, chunkMaker, file, top, options.InPlace, overwrite,
options.ShowStatistics, totalFileSize, downloadedFileSize, startDownloadingTime, allowFailures,
fileFlagsMask)
if err != nil { if err != nil {
// RestoreFile returned an error; if allowFailures is false RestoerFile would error out and not return so here // RestoreFile returned an error; if allowFailures is false RestoerFile would error out and not return so here
// we just need to show a warning // we just need to show a warning
@@ -1049,42 +898,10 @@ func (manager *BackupManager) Restore(top string, revision int, options *Restore
skippedFileSize += file.Size skippedFileSize += file.Size
skippedFileCount++ skippedFileCount++
} }
file.RestoreMetadata(fullPath, nil, metadataOptions) file.RestoreMetadata(fullPath, nil, setOwner)
} }
for _, linkEntry := range hardLinks { if deleteMode && len(patterns) == 0 {
i, _ := linkEntry.GetHardLinkId()
sourcePath := joinPath(top, hardLinkTable[i].entry.Path)
fullPath := joinPath(top, linkEntry.Path)
if stat, _ := os.Lstat(fullPath); stat != nil {
sourceStat, _ := os.Lstat(sourcePath)
if os.SameFile(stat, sourceStat) {
continue
}
if sourceStat == nil {
LOG_WERROR(allowFailures, "RESTORE_HARDLINK",
"Target %s for hard link %s is missing", sourcePath, linkEntry.Path)
continue
}
if !overwrite {
LOG_WERROR(allowFailures, "DOWNLOAD_OVERWRITE",
"File %s already exists. Please specify the -overwrite option to overwrite", linkEntry.Path)
continue
}
os.Remove(fullPath)
}
if err := MakeHardlink(sourcePath, fullPath); err != nil {
LOG_ERROR("RESTORE_HARDLINK", "Failed to create hard link %s to %s: %v", fullPath, sourcePath, err)
return 0
}
LOG_TRACE("RESTORE_HARDLINK", "Hard linked %s to %s", linkEntry.Path, hardLinkTable[i].entry.Path)
}
if options.DeleteMode && len(patterns) == 0 {
// Reverse the order to make sure directories are empty before being deleted // Reverse the order to make sure directories are empty before being deleted
for i := range extraFiles { for i := range extraFiles {
file := extraFiles[len(extraFiles)-1-i] file := extraFiles[len(extraFiles)-1-i]
@@ -1096,10 +913,10 @@ func (manager *BackupManager) Restore(top string, revision int, options *Restore
for _, entry := range directoryEntries { for _, entry := range directoryEntries {
dir := joinPath(top, entry.Path) dir := joinPath(top, entry.Path)
entry.RestoreMetadata(dir, nil, metadataOptions) entry.RestoreMetadata(dir, nil, setOwner)
} }
if options.ShowStatistics { if showStatistics {
for _, file := range downloadedFiles { for _, file := range downloadedFiles {
LOG_INFO("DOWNLOAD_DONE", "Downloaded %s (%d)", file.Path, file.Size) LOG_INFO("DOWNLOAD_DONE", "Downloaded %s (%d)", file.Path, file.Size)
} }
@@ -1110,7 +927,7 @@ func (manager *BackupManager) Restore(top string, revision int, options *Restore
} }
LOG_INFO("RESTORE_END", "Restored %s to revision %d", top, revision) LOG_INFO("RESTORE_END", "Restored %s to revision %d", top, revision)
if options.ShowStatistics { if showStatistics {
LOG_INFO("RESTORE_STATS", "Files: %d total, %s bytes", len(fileEntries), PrettySize(totalFileSize)) LOG_INFO("RESTORE_STATS", "Files: %d total, %s bytes", len(fileEntries), PrettySize(totalFileSize))
LOG_INFO("RESTORE_STATS", "Downloaded %d file, %s bytes, %d chunks", LOG_INFO("RESTORE_STATS", "Downloaded %d file, %s bytes, %d chunks",
len(downloadedFiles), PrettySize(downloadedFileSize), chunkDownloader.numberOfDownloadedChunks) len(downloadedFiles), PrettySize(downloadedFileSize), chunkDownloader.numberOfDownloadedChunks)
@@ -1129,6 +946,55 @@ func (manager *BackupManager) Restore(top string, revision int, options *Restore
return 0 return 0
} }
// fileEncoder encodes one file at a time to avoid loading the full json description of the entire file tree
// in the memory
type fileEncoder struct {
top string
readAttributes bool
files []*Entry
currentIndex int
buffer *bytes.Buffer
}
// Read reads data from the embedded buffer
func (encoder fileEncoder) Read(data []byte) (n int, err error) {
return encoder.buffer.Read(data)
}
// NextFile switches to the next file and generates its json description in the buffer. It also takes care of
// the ending ']' and the commas between files.
func (encoder *fileEncoder) NextFile() (io.Reader, bool) {
if encoder.currentIndex == len(encoder.files) {
return nil, false
}
if encoder.currentIndex == len(encoder.files)-1 {
encoder.buffer.Write([]byte("]"))
encoder.currentIndex++
return encoder, true
}
encoder.currentIndex++
entry := encoder.files[encoder.currentIndex]
if encoder.readAttributes {
entry.ReadAttributes(encoder.top)
}
description, err := json.Marshal(entry)
if err != nil {
LOG_FATAL("SNAPSHOT_ENCODE", "Failed to encode file %s: %v", encoder.files[encoder.currentIndex].Path, err)
return nil, false
}
if encoder.readAttributes {
entry.Attributes = nil
}
if encoder.currentIndex != 0 {
encoder.buffer.Write([]byte(","))
}
encoder.buffer.Write(description)
return encoder, true
}
// UploadSnapshot uploads the specified snapshot to the storage. It turns Files, ChunkHashes, and ChunkLengths into // UploadSnapshot uploads the specified snapshot to the storage. It turns Files, ChunkHashes, and ChunkLengths into
// sequences of chunks, and uploads these chunks, and finally the snapshot file. // sequences of chunks, and uploads these chunks, and finally the snapshot file.
func (manager *BackupManager) UploadSnapshot(chunkOperator *ChunkOperator, top string, snapshot *Snapshot, func (manager *BackupManager) UploadSnapshot(chunkOperator *ChunkOperator, top string, snapshot *Snapshot,
@@ -1180,19 +1046,14 @@ func (manager *BackupManager) UploadSnapshot(chunkOperator *ChunkOperator, top s
encoder := msgpack.NewEncoder(buffer) encoder := msgpack.NewEncoder(buffer)
metadataChunkMaker := CreateMetaDataChunkMaker(manager.config, metadataChunkSize) metadataChunkMaker := CreateMetaDataChunkMaker(manager.config, metadataChunkSize)
var chunkHashes []string var chunkHashes []string
var chunkLengths []int var chunkLengths []int
lastChunk := -1 lastChunk := -1
lastEndChunk := 0 lastEndChunk := 0
type hardLinkEntry struct {
entry *Entry
startChunk int
}
var hardLinkTable []hardLinkEntry
uploadEntryInfoFunc := func(entry *Entry) error { uploadEntryInfoFunc := func(entry *Entry) error {
if entry.IsFile() && entry.Size > 0 { if entry.IsFile() && entry.Size > 0 {
delta := entry.StartChunk - len(chunkHashes) + 1 delta := entry.StartChunk - len(chunkHashes) + 1
if entry.StartChunk != lastChunk { if entry.StartChunk != lastChunk {
@@ -1210,33 +1071,10 @@ func (manager *BackupManager) UploadSnapshot(chunkOperator *ChunkOperator, top s
entry.StartChunk -= delta entry.StartChunk -= delta
entry.EndChunk -= delta entry.EndChunk -= delta
if entry.IsHardLinkRoot() {
hardLinkTable = append(hardLinkTable, hardLinkEntry{entry, entry.StartChunk})
}
delta = entry.EndChunk - entry.StartChunk delta = entry.EndChunk - entry.StartChunk
entry.StartChunk -= lastEndChunk entry.StartChunk -= lastEndChunk
lastEndChunk = entry.EndChunk lastEndChunk = entry.EndChunk
entry.EndChunk = delta entry.EndChunk = delta
} else if entry.IsHardLinkChild() {
i, err := entry.GetHardLinkId()
if err != nil {
LOG_ERROR("SNAPSHOT_UPLOAD", "Decode error for hard link entry %s: %v", entry.Link, err)
return err
}
targetEntry := hardLinkTable[i].entry
var startChunk, endChunk int
if targetEntry.IsFile() && targetEntry.Size > 0 {
startChunk = hardLinkTable[i].startChunk - lastEndChunk
endChunk = targetEntry.EndChunk
lastEndChunk = hardLinkTable[i].startChunk + endChunk
}
entry = entry.HardLinkTo(targetEntry, startChunk, endChunk)
} else if entry.IsHardLinkRoot() {
hardLinkTable = append(hardLinkTable, hardLinkEntry{entry, 0})
} }
buffer.Reset() buffer.Reset()
@@ -1305,13 +1143,11 @@ func (manager *BackupManager) UploadSnapshot(chunkOperator *ChunkOperator, top s
// Restore downloads a file from the storage. If 'inPlace' is false, the download file is saved first to a temporary // Restore downloads a file from the storage. If 'inPlace' is false, the download file is saved first to a temporary
// file under the .duplicacy directory and then replaces the existing one. Otherwise, the existing file will be // file under the .duplicacy directory and then replaces the existing one. Otherwise, the existing file will be
// overwritten directly. // overwritten directly.
// Return: true, nil: Restored file; // Return: true, nil: Restored file;
// // false, nil: Skipped file;
// false, nil: Skipped file; // false, error: Failure to restore file (only if allowFailures == true)
// false, error: Failure to restore file (only if allowFailures == true)
func (manager *BackupManager) RestoreFile(chunkDownloader *ChunkDownloader, chunkMaker *ChunkMaker, entry *Entry, top string, inPlace bool, overwrite bool, func (manager *BackupManager) RestoreFile(chunkDownloader *ChunkDownloader, chunkMaker *ChunkMaker, entry *Entry, top string, inPlace bool, overwrite bool,
showStatistics bool, totalFileSize int64, downloadedFileSize int64, startTime int64, allowFailures bool, showStatistics bool, totalFileSize int64, downloadedFileSize int64, startTime int64, allowFailures bool) (bool, error) {
fileFlagsMask uint32) (bool, error) {
LOG_TRACE("DOWNLOAD_START", "Downloading %s", entry.Path) LOG_TRACE("DOWNLOAD_START", "Downloading %s", entry.Path)
@@ -1358,10 +1194,6 @@ func (manager *BackupManager) RestoreFile(chunkDownloader *ChunkDownloader, chun
LOG_ERROR("DOWNLOAD_CREATE", "Failed to create the file %s for in-place writing: %v", fullPath, err) LOG_ERROR("DOWNLOAD_CREATE", "Failed to create the file %s for in-place writing: %v", fullPath, err)
return false, nil return false, nil
} }
err = entry.RestoreEarlyFileFlags(existingFile, fileFlagsMask)
if err != nil {
LOG_WARN("DOWNLOAD_FLAGS", "Failed to set early file flags on %s: %v", fullPath, err)
}
n := int64(1) n := int64(1)
// There is a go bug on Windows (https://github.com/golang/go/issues/21681) that causes Seek to fail // There is a go bug on Windows (https://github.com/golang/go/issues/21681) that causes Seek to fail
@@ -1488,7 +1320,7 @@ func (manager *BackupManager) RestoreFile(chunkDownloader *ChunkDownloader, chun
// fileHash != entry.Hash, warn/error depending on -overwrite option // fileHash != entry.Hash, warn/error depending on -overwrite option
if !overwrite && !isNewFile { if !overwrite && !isNewFile {
LOG_WERROR(allowFailures, "DOWNLOAD_OVERWRITE", LOG_WERROR(allowFailures, "DOWNLOAD_OVERWRITE",
"File %s already exists. Please specify the -overwrite option to overwrite", entry.Path) "File %s already exists. Please specify the -overwrite option to overwrite", entry.Path)
return false, fmt.Errorf("file exists") return false, fmt.Errorf("file exists")
} }
@@ -1545,10 +1377,6 @@ func (manager *BackupManager) RestoreFile(chunkDownloader *ChunkDownloader, chun
return false, nil return false, nil
} }
} }
err = entry.RestoreEarlyFileFlags(existingFile, fileFlagsMask)
if err != nil {
LOG_WARN("DOWNLOAD_FLAGS", "Failed to set early file flags on %s: %v", fullPath, err)
}
existingFile.Seek(0, 0) existingFile.Seek(0, 0)
@@ -1631,10 +1459,6 @@ func (manager *BackupManager) RestoreFile(chunkDownloader *ChunkDownloader, chun
LOG_ERROR("DOWNLOAD_OPEN", "Failed to open file for writing: %v", err) LOG_ERROR("DOWNLOAD_OPEN", "Failed to open file for writing: %v", err)
return false, nil return false, nil
} }
err = entry.RestoreEarlyFileFlags(newFile, fileFlagsMask)
if err != nil {
LOG_WARN("DOWNLOAD_FLAGS", "Failed to set early file flags on %s: %v", fullPath, err)
}
hasher := manager.config.NewFileHasher() hasher := manager.config.NewFileHasher()
@@ -1741,7 +1565,7 @@ func (manager *BackupManager) CopySnapshots(otherManager *BackupManager, snapsho
if otherManager.config.DataShards != 0 && otherManager.config.ParityShards != 0 { if otherManager.config.DataShards != 0 && otherManager.config.ParityShards != 0 {
LOG_INFO("BACKUP_ERASURECODING", "Erasure coding is enabled for the destination storage with %d data shards and %d parity shards", LOG_INFO("BACKUP_ERASURECODING", "Erasure coding is enabled for the destination storage with %d data shards and %d parity shards",
otherManager.config.DataShards, otherManager.config.ParityShards) otherManager.config.DataShards, otherManager.config.ParityShards)
} }
if otherManager.config.rsaPublicKey != nil && len(otherManager.config.FileKey) > 0 { if otherManager.config.rsaPublicKey != nil && len(otherManager.config.FileKey) > 0 {
@@ -1842,15 +1666,15 @@ func (manager *BackupManager) CopySnapshots(otherManager *BackupManager, snapsho
LOG_TRACE("SNAPSHOT_COPY", "Copying snapshot %s at revision %d", snapshot.ID, snapshot.Revision) LOG_TRACE("SNAPSHOT_COPY", "Copying snapshot %s at revision %d", snapshot.ID, snapshot.Revision)
for _, chunkHash := range snapshot.FileSequence { for _, chunkHash := range snapshot.FileSequence {
chunks[chunkHash] = true // The chunk is a snapshot chunk chunks[chunkHash] = true // The chunk is a snapshot chunk
} }
for _, chunkHash := range snapshot.ChunkSequence { for _, chunkHash := range snapshot.ChunkSequence {
chunks[chunkHash] = true // The chunk is a snapshot chunk chunks[chunkHash] = true // The chunk is a snapshot chunk
} }
for _, chunkHash := range snapshot.LengthSequence { for _, chunkHash := range snapshot.LengthSequence {
chunks[chunkHash] = true // The chunk is a snapshot chunk chunks[chunkHash] = true // The chunk is a snapshot chunk
} }
description := manager.SnapshotManager.DownloadSequence(snapshot.ChunkSequence) description := manager.SnapshotManager.DownloadSequence(snapshot.ChunkSequence)
@@ -1863,7 +1687,7 @@ func (manager *BackupManager) CopySnapshots(otherManager *BackupManager, snapsho
for _, chunkHash := range snapshot.ChunkHashes { for _, chunkHash := range snapshot.ChunkHashes {
if _, found := chunks[chunkHash]; !found { if _, found := chunks[chunkHash]; !found {
chunks[chunkHash] = false // The chunk is a file chunk chunks[chunkHash] = false // The chunk is a file chunk
} }
} }
@@ -1895,7 +1719,7 @@ func (manager *BackupManager) CopySnapshots(otherManager *BackupManager, snapsho
} }
} }
LOG_INFO("SNAPSHOT_COPY", "Chunks to copy: %d, to skip: %d, total: %d", len(chunksToCopy), len(chunks)-len(chunksToCopy), len(chunks)) LOG_INFO("SNAPSHOT_COPY", "Chunks to copy: %d, to skip: %d, total: %d", len(chunksToCopy), len(chunks) - len(chunksToCopy), len(chunks))
chunkDownloader := CreateChunkOperator(manager.config, manager.storage, nil, false, false, downloadingThreads, false) chunkDownloader := CreateChunkOperator(manager.config, manager.storage, nil, false, false, downloadingThreads, false)
@@ -1904,7 +1728,7 @@ func (manager *BackupManager) CopySnapshots(otherManager *BackupManager, snapsho
copiedChunks := 0 copiedChunks := 0
chunkUploader := CreateChunkOperator(otherManager.config, otherManager.storage, nil, false, false, uploadingThreads, false) chunkUploader := CreateChunkOperator(otherManager.config, otherManager.storage, nil, false, false, uploadingThreads, false)
chunkUploader.UploadCompletionFunc = func(chunk *Chunk, chunkIndex int, skipped bool, chunkSize int, uploadSize int) { chunkUploader.UploadCompletionFunc = func(chunk *Chunk, chunkIndex int, skipped bool, chunkSize int, uploadSize int) {
action := "Skipped" action := "Skipped"
if !skipped { if !skipped {
copiedChunks++ copiedChunks++
@@ -1915,11 +1739,11 @@ func (manager *BackupManager) CopySnapshots(otherManager *BackupManager, snapsho
elapsedTime := time.Now().Sub(startTime).Seconds() elapsedTime := time.Now().Sub(startTime).Seconds()
speed := int64(float64(atomic.LoadInt64(&uploadedBytes)) / elapsedTime) speed := int64(float64(atomic.LoadInt64(&uploadedBytes)) / elapsedTime)
remainingTime := int64(float64(len(chunksToCopy)-chunkIndex-1) / float64(chunkIndex+1) * elapsedTime) remainingTime := int64(float64(len(chunksToCopy) - chunkIndex - 1) / float64(chunkIndex + 1) * elapsedTime)
percentage := float64(chunkIndex+1) / float64(len(chunksToCopy)) * 100.0 percentage := float64(chunkIndex + 1) / float64(len(chunksToCopy)) * 100.0
LOG_INFO("COPY_PROGRESS", "%s chunk %s (%d/%d) %sB/s %s %.1f%%", LOG_INFO("COPY_PROGRESS", "%s chunk %s (%d/%d) %sB/s %s %.1f%%",
action, chunk.GetID(), chunkIndex+1, len(chunksToCopy), action, chunk.GetID(), chunkIndex + 1, len(chunksToCopy),
PrettySize(speed), PrettyTime(remainingTime), percentage) PrettySize(speed), PrettyTime(remainingTime), percentage)
otherManager.config.PutChunk(chunk) otherManager.config.PutChunk(chunk)
} }
@@ -1942,7 +1766,7 @@ func (manager *BackupManager) CopySnapshots(otherManager *BackupManager, snapsho
chunkDownloader.Stop() chunkDownloader.Stop()
chunkUploader.Stop() chunkUploader.Stop()
LOG_INFO("SNAPSHOT_COPY", "Copied %d new chunks and skipped %d existing chunks", copiedChunks, len(chunks)-copiedChunks) LOG_INFO("SNAPSHOT_COPY", "Copied %d new chunks and skipped %d existing chunks", copiedChunks, len(chunks) - copiedChunks)
for _, snapshot := range snapshots { for _, snapshot := range snapshots {
if revisionMap[snapshot.ID][snapshot.Revision] == false { if revisionMap[snapshot.ID][snapshot.Revision] == false {

View File

@@ -176,6 +176,8 @@ func assertRestoreFailures(t *testing.T, failedFiles int, expectedFailedFiles in
} }
func TestBackupManager(t *testing.T) { func TestBackupManager(t *testing.T) {
rand.Seed(time.Now().UnixNano())
setTestingT(t) setTestingT(t)
SetLoggingLevel(INFO) SetLoggingLevel(INFO)
@@ -251,23 +253,15 @@ func TestBackupManager(t *testing.T) {
time.Sleep(time.Duration(delay) * time.Second) time.Sleep(time.Duration(delay) * time.Second)
SetDuplicacyPreferencePath(testDir + "/repository1/.duplicacy") SetDuplicacyPreferencePath(testDir + "/repository1/.duplicacy")
backupManager := CreateBackupManager("host1", storage, testDir, password, nil) backupManager := CreateBackupManager("host1", storage, testDir, password, "", "", false)
backupManager.SetupSnapshotCache("default") backupManager.SetupSnapshotCache("default")
SetDuplicacyPreferencePath(testDir + "/repository1/.duplicacy") SetDuplicacyPreferencePath(testDir + "/repository1/.duplicacy")
backupManager.Backup(testDir+"/repository1" /*quickMode=*/, true, threads, "first", false, false, 0, false, 1024, 1024) backupManager.Backup(testDir+"/repository1" /*quickMode=*/, true, threads, "first", false, false, 0, false, 1024, 1024)
time.Sleep(time.Duration(delay) * time.Second) time.Sleep(time.Duration(delay) * time.Second)
SetDuplicacyPreferencePath(testDir + "/repository2/.duplicacy") SetDuplicacyPreferencePath(testDir + "/repository2/.duplicacy")
failedFiles := backupManager.Restore(testDir+"/repository2", 1, &RestoreOptions{ failedFiles := backupManager.Restore(testDir+"/repository2", threads /*inPlace=*/, false /*quickMode=*/, false, threads /*overwrite=*/, true,
Threads: threads, /*deleteMode=*/ false /*setowner=*/, false /*showStatistics=*/, false /*patterns=*/, nil /*allowFailures=*/, false)
Patterns: nil,
InPlace: false,
QuickMode: false,
Overwrite: true,
DeleteMode: false,
ShowStatistics: false,
AllowFailures: false,
})
assertRestoreFailures(t, failedFiles, 0) assertRestoreFailures(t, failedFiles, 0)
for _, f := range []string{"file1", "file2", "dir1/file3"} { for _, f := range []string{"file1", "file2", "dir1/file3"} {
@@ -291,16 +285,8 @@ func TestBackupManager(t *testing.T) {
backupManager.Backup(testDir+"/repository1" /*quickMode=*/, true, threads, "second", false, false, 0, false, 1024, 1024) backupManager.Backup(testDir+"/repository1" /*quickMode=*/, true, threads, "second", false, false, 0, false, 1024, 1024)
time.Sleep(time.Duration(delay) * time.Second) time.Sleep(time.Duration(delay) * time.Second)
SetDuplicacyPreferencePath(testDir + "/repository2/.duplicacy") SetDuplicacyPreferencePath(testDir + "/repository2/.duplicacy")
failedFiles = backupManager.Restore(testDir+"/repository2", 2, &RestoreOptions{ failedFiles = backupManager.Restore(testDir+"/repository2", 2 /*inPlace=*/, true /*quickMode=*/, true, threads /*overwrite=*/, true,
Threads: threads, /*deleteMode=*/ false /*setowner=*/, false /*showStatistics=*/, false /*patterns=*/, nil /*allowFailures=*/, false)
Patterns: nil,
InPlace: true,
QuickMode: true,
Overwrite: true,
DeleteMode: false,
ShowStatistics: false,
AllowFailures: false,
})
assertRestoreFailures(t, failedFiles, 0) assertRestoreFailures(t, failedFiles, 0)
for _, f := range []string{"file1", "file2", "dir1/file3"} { for _, f := range []string{"file1", "file2", "dir1/file3"} {
@@ -328,16 +314,8 @@ func TestBackupManager(t *testing.T) {
createRandomFile(testDir+"/repository2/dir5/file5", 100) createRandomFile(testDir+"/repository2/dir5/file5", 100)
SetDuplicacyPreferencePath(testDir + "/repository2/.duplicacy") SetDuplicacyPreferencePath(testDir + "/repository2/.duplicacy")
failedFiles = backupManager.Restore(testDir+"/repository2", 3, &RestoreOptions{ failedFiles = backupManager.Restore(testDir+"/repository2", 3 /*inPlace=*/, true /*quickMode=*/, false, threads /*overwrite=*/, true,
Threads: threads, /*deleteMode=*/ true /*setowner=*/, false /*showStatistics=*/, false /*patterns=*/, nil /*allowFailures=*/, false)
Patterns: nil,
InPlace: true,
QuickMode: false,
Overwrite: true,
DeleteMode: true,
ShowStatistics: false,
AllowFailures: false,
})
assertRestoreFailures(t, failedFiles, 0) assertRestoreFailures(t, failedFiles, 0)
for _, f := range []string{"file1", "file2", "dir1/file3"} { for _, f := range []string{"file1", "file2", "dir1/file3"} {
@@ -364,16 +342,8 @@ func TestBackupManager(t *testing.T) {
os.Remove(testDir + "/repository1/file2") os.Remove(testDir + "/repository1/file2")
os.Remove(testDir + "/repository1/dir1/file3") os.Remove(testDir + "/repository1/dir1/file3")
SetDuplicacyPreferencePath(testDir + "/repository1/.duplicacy") SetDuplicacyPreferencePath(testDir + "/repository1/.duplicacy")
failedFiles = backupManager.Restore(testDir+"/repository1", 3, &RestoreOptions{ failedFiles = backupManager.Restore(testDir+"/repository1", 3 /*inPlace=*/, true /*quickMode=*/, false, threads /*overwrite=*/, true,
Threads: threads, /*deleteMode=*/ false /*setowner=*/, false /*showStatistics=*/, false /*patterns=*/, []string{"+file2", "+dir1/file3", "-*"} /*allowFailures=*/, false)
Patterns: []string{"+file2", "+dir1/file3", "-*"},
InPlace: true,
QuickMode: false,
Overwrite: true,
DeleteMode: false,
ShowStatistics: false,
AllowFailures: false,
})
assertRestoreFailures(t, failedFiles, 0) assertRestoreFailures(t, failedFiles, 0)
for _, f := range []string{"file1", "file2", "dir1/file3"} { for _, f := range []string{"file1", "file2", "dir1/file3"} {
@@ -388,17 +358,17 @@ func TestBackupManager(t *testing.T) {
if numberOfSnapshots != 3 { if numberOfSnapshots != 3 {
t.Errorf("Expected 3 snapshots but got %d", numberOfSnapshots) t.Errorf("Expected 3 snapshots but got %d", numberOfSnapshots)
} }
backupManager.SnapshotManager.CheckSnapshots( /*snapshotID*/ "host1" /*revisions*/, []int{1, 2, 3} /*tag*/, "" /*showStatistics*/, false, backupManager.SnapshotManager.CheckSnapshots( /*snapshotID*/ "host1", /*revisions*/ []int{1, 2, 3}, /*tag*/ "", /*showStatistics*/ false,
/*showTabular*/ false /*checkFiles*/, false /*checkChunks*/, false /*searchFossils*/, false /*resurrect*/, false /*rewiret*/, false, 1 /*allowFailures*/, false) /*showTabular*/ false, /*checkFiles*/ false, /*checkChunks*/ false, /*searchFossils*/ false, /*resurrect*/ false, /*rewiret*/ false, 1, /*allowFailures*/false)
backupManager.SnapshotManager.PruneSnapshots("host1", "host1" /*revisions*/, []int{1} /*tags*/, nil /*retentions*/, nil, backupManager.SnapshotManager.PruneSnapshots("host1", "host1" /*revisions*/, []int{1} /*tags*/, nil /*retentions*/, nil,
/*exhaustive*/ false /*exclusive=*/, false /*ignoredIDs*/, nil /*dryRun*/, false /*deleteOnly*/, false /*collectOnly*/, false, 1) /*exhaustive*/ false /*exclusive=*/, false /*ignoredIDs*/, nil /*dryRun*/, false /*deleteOnly*/, false /*collectOnly*/, false, 1)
numberOfSnapshots = backupManager.SnapshotManager.ListSnapshots( /*snapshotID*/ "host1" /*revisionsToList*/, nil /*tag*/, "" /*showFiles*/, false /*showChunks*/, false) numberOfSnapshots = backupManager.SnapshotManager.ListSnapshots( /*snapshotID*/ "host1" /*revisionsToList*/, nil /*tag*/, "" /*showFiles*/, false /*showChunks*/, false)
if numberOfSnapshots != 2 { if numberOfSnapshots != 2 {
t.Errorf("Expected 2 snapshots but got %d", numberOfSnapshots) t.Errorf("Expected 2 snapshots but got %d", numberOfSnapshots)
} }
backupManager.SnapshotManager.CheckSnapshots( /*snapshotID*/ "host1" /*revisions*/, []int{2, 3} /*tag*/, "" /*showStatistics*/, false, backupManager.SnapshotManager.CheckSnapshots( /*snapshotID*/ "host1", /*revisions*/ []int{2, 3}, /*tag*/ "", /*showStatistics*/ false,
/*showTabular*/ false /*checkFiles*/, false /*checkChunks*/, false /*searchFossils*/, false /*resurrect*/, false /*rewiret*/, false, 1 /*allowFailures*/, false) /*showTabular*/ false, /*checkFiles*/ false, /*checkChunks*/ false, /*searchFossils*/ false, /*resurrect*/ false, /*rewiret*/ false, 1, /*allowFailures*/ false)
backupManager.Backup(testDir+"/repository1" /*quickMode=*/, false, threads, "fourth", false, false, 0, false, 1024, 1024) backupManager.Backup(testDir+"/repository1" /*quickMode=*/, false, threads, "fourth", false, false, 0, false, 1024, 1024)
backupManager.SnapshotManager.PruneSnapshots("host1", "host1" /*revisions*/, nil /*tags*/, nil /*retentions*/, nil, backupManager.SnapshotManager.PruneSnapshots("host1", "host1" /*revisions*/, nil /*tags*/, nil /*retentions*/, nil,
/*exhaustive*/ false /*exclusive=*/, true /*ignoredIDs*/, nil /*dryRun*/, false /*deleteOnly*/, false /*collectOnly*/, false, 1) /*exhaustive*/ false /*exclusive=*/, true /*ignoredIDs*/, nil /*dryRun*/, false /*deleteOnly*/, false /*collectOnly*/, false, 1)
@@ -406,8 +376,8 @@ func TestBackupManager(t *testing.T) {
if numberOfSnapshots != 3 { if numberOfSnapshots != 3 {
t.Errorf("Expected 3 snapshots but got %d", numberOfSnapshots) t.Errorf("Expected 3 snapshots but got %d", numberOfSnapshots)
} }
backupManager.SnapshotManager.CheckSnapshots( /*snapshotID*/ "host1" /*revisions*/, []int{2, 3, 4} /*tag*/, "" /*showStatistics*/, false, backupManager.SnapshotManager.CheckSnapshots( /*snapshotID*/ "host1", /*revisions*/ []int{2, 3, 4}, /*tag*/ "", /*showStatistics*/ false,
/*showTabular*/ false /*checkFiles*/, false /*checkChunks*/, false /*searchFossils*/, false /*resurrect*/, false /*rewiret*/, false, 1 /*allowFailures*/, false) /*showTabular*/ false, /*checkFiles*/ false, /*checkChunks*/ false, /*searchFossils*/ false, /*resurrect*/ false, /*rewiret*/ false, 1, /*allowFailures*/ false)
/*buf := make([]byte, 1<<16) /*buf := make([]byte, 1<<16)
runtime.Stack(buf, true) runtime.Stack(buf, true)
@@ -416,7 +386,7 @@ func TestBackupManager(t *testing.T) {
// Create file with random file with certain seed // Create file with random file with certain seed
func createRandomFileSeeded(path string, maxSize int, seed int64) { func createRandomFileSeeded(path string, maxSize int, seed int64) {
r := rand.New(rand.NewSource(seed)) rand.Seed(seed)
file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil { if err != nil {
LOG_ERROR("RANDOM_FILE", "Can't open %s for writing: %v", path, err) LOG_ERROR("RANDOM_FILE", "Can't open %s for writing: %v", path, err)
@@ -433,7 +403,7 @@ func createRandomFileSeeded(path string, maxSize int, seed int64) {
if bytes > cap(buffer) { if bytes > cap(buffer) {
bytes = cap(buffer) bytes = cap(buffer)
} }
r.Read(buffer[:bytes]) rand.Read(buffer[:bytes])
bytes, err = file.Write(buffer[:bytes]) bytes, err = file.Write(buffer[:bytes])
if err != nil { if err != nil {
LOG_ERROR("RANDOM_FILE", "Failed to write to %s: %v", path, err) LOG_ERROR("RANDOM_FILE", "Failed to write to %s: %v", path, err)
@@ -444,7 +414,7 @@ func createRandomFileSeeded(path string, maxSize int, seed int64) {
} }
func corruptFile(path string, start int, length int, seed int64) { func corruptFile(path string, start int, length int, seed int64) {
r := rand.New(rand.NewSource(seed)) rand.Seed(seed)
file, err := os.OpenFile(path, os.O_WRONLY, 0644) file, err := os.OpenFile(path, os.O_WRONLY, 0644)
if err != nil { if err != nil {
@@ -465,7 +435,7 @@ func corruptFile(path string, start int, length int, seed int64) {
} }
buffer := make([]byte, length) buffer := make([]byte, length)
r.Read(buffer) rand.Read(buffer)
_, err = file.Write(buffer) _, err = file.Write(buffer)
if err != nil { if err != nil {
@@ -508,9 +478,9 @@ func TestPersistRestore(t *testing.T) {
maxFileSize := 1000000 maxFileSize := 1000000
//maxFileSize := 200000 //maxFileSize := 200000
createRandomFileSeeded(testDir+"/repository1/file1", maxFileSize, 1) createRandomFileSeeded(testDir+"/repository1/file1", maxFileSize,1)
createRandomFileSeeded(testDir+"/repository1/file2", maxFileSize, 2) createRandomFileSeeded(testDir+"/repository1/file2", maxFileSize,2)
createRandomFileSeeded(testDir+"/repository1/dir1/file3", maxFileSize, 3) createRandomFileSeeded(testDir+"/repository1/dir1/file3", maxFileSize,3)
threads := 1 threads := 1
@@ -560,83 +530,85 @@ func TestPersistRestore(t *testing.T) {
// do unencrypted backup // do unencrypted backup
SetDuplicacyPreferencePath(testDir + "/repository1/.duplicacy") SetDuplicacyPreferencePath(testDir + "/repository1/.duplicacy")
unencBackupManager := CreateBackupManager("host1", unencStorage, testDir, "", nil) unencBackupManager := CreateBackupManager("host1", unencStorage, testDir, "", "", "", false)
unencBackupManager.SetupSnapshotCache("default") unencBackupManager.SetupSnapshotCache("default")
SetDuplicacyPreferencePath(testDir + "/repository1/.duplicacy") SetDuplicacyPreferencePath(testDir + "/repository1/.duplicacy")
unencBackupManager.Backup(testDir+"/repository1" /*quickMode=*/, true, threads, "first", false, false, 0, false, 1024, 1024) unencBackupManager.Backup(testDir+"/repository1" /*quickMode=*/, true, threads, "first", false, false, 0, false, 1024, 1024)
time.Sleep(time.Duration(delay) * time.Second) time.Sleep(time.Duration(delay) * time.Second)
// do encrypted backup // do encrypted backup
SetDuplicacyPreferencePath(testDir + "/repository1/.duplicacy") SetDuplicacyPreferencePath(testDir + "/repository1/.duplicacy")
encBackupManager := CreateBackupManager("host1", storage, testDir, password, nil) encBackupManager := CreateBackupManager("host1", storage, testDir, password, "", "", false)
encBackupManager.SetupSnapshotCache("default") encBackupManager.SetupSnapshotCache("default")
SetDuplicacyPreferencePath(testDir + "/repository1/.duplicacy") SetDuplicacyPreferencePath(testDir + "/repository1/.duplicacy")
encBackupManager.Backup(testDir+"/repository1" /*quickMode=*/, true, threads, "first", false, false, 0, false, 1024, 1024) encBackupManager.Backup(testDir+"/repository1" /*quickMode=*/, true, threads, "first", false, false, 0, false, 1024, 1024)
time.Sleep(time.Duration(delay) * time.Second) time.Sleep(time.Duration(delay) * time.Second)
// check snapshots // check snapshots
unencBackupManager.SnapshotManager.CheckSnapshots( /*snapshotID*/ "host1" /*revisions*/, []int{1} /*tag*/, "", unencBackupManager.SnapshotManager.CheckSnapshots( /*snapshotID*/ "host1", /*revisions*/ []int{1}, /*tag*/ "",
/*showStatistics*/ true /*showTabular*/, false /*checkFiles*/, true /*checkChunks*/, false, /*showStatistics*/ true, /*showTabular*/ false, /*checkFiles*/ true, /*checkChunks*/ false,
/*searchFossils*/ false /*resurrect*/, false /*rewiret*/, false, 1 /*allowFailures*/, false) /*searchFossils*/ false, /*resurrect*/ false, /*rewiret*/ false, 1, /*allowFailures*/ false)
encBackupManager.SnapshotManager.CheckSnapshots( /*snapshotID*/ "host1" /*revisions*/, []int{1} /*tag*/, "",
/*showStatistics*/ true /*showTabular*/, false /*checkFiles*/, true /*checkChunks*/, false,
/*searchFossils*/ false /*resurrect*/, false /*rewiret*/, false, 1 /*allowFailures*/, false)
encBackupManager.SnapshotManager.CheckSnapshots( /*snapshotID*/ "host1", /*revisions*/ []int{1}, /*tag*/ "",
/*showStatistics*/ true, /*showTabular*/ false, /*checkFiles*/ true, /*checkChunks*/ false,
/*searchFossils*/ false, /*resurrect*/ false, /*rewiret*/ false, 1, /*allowFailures*/ false)
// check functions // check functions
checkAllUncorrupted := func(cmpRepository string) { checkAllUncorrupted := func(cmpRepository string) {
for _, f := range []string{"file1", "file2", "dir1/file3"} { for _, f := range []string{"file1", "file2", "dir1/file3"} {
if _, err := os.Stat(testDir + cmpRepository + "/" + f); os.IsNotExist(err) { if _, err := os.Stat(testDir + cmpRepository + "/" + f); os.IsNotExist(err) {
t.Errorf("File %s does not exist", f) t.Errorf("File %s does not exist", f)
continue continue
} }
hash1 := getFileHash(testDir + "/repository1/" + f) hash1 := getFileHash(testDir + "/repository1/" + f)
hash2 := getFileHash(testDir + cmpRepository + "/" + f) hash2 := getFileHash(testDir + cmpRepository + "/" + f)
if hash1 != hash2 { if hash1 != hash2 {
t.Errorf("File %s has different hashes: %s vs %s", f, hash1, hash2) t.Errorf("File %s has different hashes: %s vs %s", f, hash1, hash2)
} }
} }
} }
checkMissingFile := func(cmpRepository string, expectMissing string) { checkMissingFile := func(cmpRepository string, expectMissing string) {
for _, f := range []string{"file1", "file2", "dir1/file3"} { for _, f := range []string{"file1", "file2", "dir1/file3"} {
_, err := os.Stat(testDir + cmpRepository + "/" + f) _, err := os.Stat(testDir + cmpRepository + "/" + f)
if err == nil { if err==nil {
if f == expectMissing { if f==expectMissing {
t.Errorf("File %s exists, expected to be missing", f) t.Errorf("File %s exists, expected to be missing", f)
}
continue
} }
continue if os.IsNotExist(err) {
} if f!=expectMissing {
if os.IsNotExist(err) { t.Errorf("File %s does not exist", f)
if f != expectMissing { }
t.Errorf("File %s does not exist", f) continue
} }
continue
}
hash1 := getFileHash(testDir + "/repository1/" + f) hash1 := getFileHash(testDir + "/repository1/" + f)
hash2 := getFileHash(testDir + cmpRepository + "/" + f) hash2 := getFileHash(testDir + cmpRepository + "/" + f)
if hash1 != hash2 { if hash1 != hash2 {
t.Errorf("File %s has different hashes: %s vs %s", f, hash1, hash2) t.Errorf("File %s has different hashes: %s vs %s", f, hash1, hash2)
} }
} }
} }
checkCorruptedFile := func(cmpRepository string, expectCorrupted string) { checkCorruptedFile := func(cmpRepository string, expectCorrupted string) {
for _, f := range []string{"file1", "file2", "dir1/file3"} { for _, f := range []string{"file1", "file2", "dir1/file3"} {
if _, err := os.Stat(testDir + cmpRepository + "/" + f); os.IsNotExist(err) { if _, err := os.Stat(testDir + cmpRepository + "/" + f); os.IsNotExist(err) {
t.Errorf("File %s does not exist", f) t.Errorf("File %s does not exist", f)
continue continue
} }
hash1 := getFileHash(testDir + "/repository1/" + f) hash1 := getFileHash(testDir + "/repository1/" + f)
hash2 := getFileHash(testDir + cmpRepository + "/" + f) hash2 := getFileHash(testDir + cmpRepository + "/" + f)
if f == expectCorrupted { if (f==expectCorrupted) {
if hash1 == hash2 { if hash1 == hash2 {
t.Errorf("File %s has same hashes, expected to be corrupted: %s vs %s", f, hash1, hash2) t.Errorf("File %s has same hashes, expected to be corrupted: %s vs %s", f, hash1, hash2)
} }
} else { } else {
if hash1 != hash2 { if hash1 != hash2 {
t.Errorf("File %s has different hashes: %s vs %s", f, hash1, hash2) t.Errorf("File %s has different hashes: %s vs %s", f, hash1, hash2)
@@ -647,35 +619,27 @@ func TestPersistRestore(t *testing.T) {
// test restore all uncorrupted to repository3 // test restore all uncorrupted to repository3
SetDuplicacyPreferencePath(testDir + "/repository3/.duplicacy") SetDuplicacyPreferencePath(testDir + "/repository3/.duplicacy")
failedFiles := unencBackupManager.Restore(testDir+"/repository3", 1, &RestoreOptions{ failedFiles := unencBackupManager.Restore(testDir+"/repository3", threads /*inPlace=*/, true /*quickMode=*/, false, threads /*overwrite=*/, false,
Threads: threads, /*deleteMode=*/ false /*setowner=*/, false /*showStatistics=*/, false /*patterns=*/, nil /*allowFailures=*/, false)
Patterns: nil,
InPlace: true,
QuickMode: false,
Overwrite: false,
DeleteMode: false,
ShowStatistics: false,
AllowFailures: false,
})
assertRestoreFailures(t, failedFiles, 0) assertRestoreFailures(t, failedFiles, 0)
checkAllUncorrupted("/repository3") checkAllUncorrupted("/repository3")
// test for corrupt files and -persist // test for corrupt files and -persist
// corrupt a chunk // corrupt a chunk
chunkToCorrupt1 := "/4d/538e5dfd2b08e782bfeb56d1360fb5d7eb9d8c4b2531cc2fca79efbaec910c" chunkToCorrupt1 := "/4d/538e5dfd2b08e782bfeb56d1360fb5d7eb9d8c4b2531cc2fca79efbaec910c"
// this should affect file1 // this should affect file1
chunkToCorrupt2 := "/2b/f953a766d0196ce026ae259e76e3c186a0e4bcd3ce10f1571d17f86f0a5497" chunkToCorrupt2 := "/2b/f953a766d0196ce026ae259e76e3c186a0e4bcd3ce10f1571d17f86f0a5497"
// this should affect dir1/file3 // this should affect dir1/file3
for i := 0; i < 2; i++ { for i := 0; i < 2; i++ {
if i == 0 { if i==0 {
// test corrupt chunks // test corrupt chunks
corruptFile(testDir+"/unenc_storage"+"/chunks"+chunkToCorrupt1, 128, 128, 4) corruptFile(testDir+"/unenc_storage"+"/chunks"+chunkToCorrupt1, 128, 128, 4)
corruptFile(testDir+"/enc_storage"+"/chunks"+chunkToCorrupt2, 128, 128, 4) corruptFile(testDir+"/enc_storage"+"/chunks"+chunkToCorrupt2, 128, 128, 4)
} else { } else {
// test missing chunks // test missing chunks
os.Remove(testDir + "/unenc_storage" + "/chunks" + chunkToCorrupt1) os.Remove(testDir+"/unenc_storage"+"/chunks"+chunkToCorrupt1)
os.Remove(testDir + "/enc_storage" + "/chunks" + chunkToCorrupt2) os.Remove(testDir+"/enc_storage"+"/chunks"+chunkToCorrupt2)
} }
// This is to make sure that allowFailures is set to true. Note that this is not needed // This is to make sure that allowFailures is set to true. Note that this is not needed
@@ -690,44 +654,30 @@ func TestPersistRestore(t *testing.T) {
// check snapshots with --persist (allowFailures == true) // check snapshots with --persist (allowFailures == true)
// this would cause a panic and os.Exit from duplicacy_log if allowFailures == false // this would cause a panic and os.Exit from duplicacy_log if allowFailures == false
unencBackupManager.SnapshotManager.CheckSnapshots( /*snapshotID*/ "host1" /*revisions*/, []int{1} /*tag*/, "", unencBackupManager.SnapshotManager.CheckSnapshots( /*snapshotID*/ "host1", /*revisions*/ []int{1}, /*tag*/ "",
/*showStatistics*/ true /*showTabular*/, false /*checkFiles*/, true /*checkChunks*/, false, /*showStatistics*/ true, /*showTabular*/ false, /*checkFiles*/ true, /*checkChunks*/ false,
/*searchFossils*/ false /*resurrect*/, false /*rewrite*/, false, 1 /*allowFailures*/, true) /*searchFossils*/ false, /*resurrect*/ false, /*rewrite*/ false, 1, /*allowFailures*/ true)
encBackupManager.SnapshotManager.CheckSnapshots( /*snapshotID*/ "host1" /*revisions*/, []int{1} /*tag*/, "", encBackupManager.SnapshotManager.CheckSnapshots( /*snapshotID*/ "host1", /*revisions*/ []int{1}, /*tag*/ "",
/*showStatistics*/ true /*showTabular*/, false /*checkFiles*/, true /*checkChunks*/, false, /*showStatistics*/ true, /*showTabular*/ false, /*checkFiles*/ true, /*checkChunks*/ false,
/*searchFossils*/ false /*resurrect*/, false /*rewrite*/, false, 1 /*allowFailures*/, true) /*searchFossils*/ false, /*resurrect*/ false, /*rewrite*/ false, 1, /*allowFailures*/ true)
// test restore corrupted, inPlace = true, corrupted files will have hash failures // test restore corrupted, inPlace = true, corrupted files will have hash failures
os.RemoveAll(testDir + "/repository2") os.RemoveAll(testDir+"/repository2")
SetDuplicacyPreferencePath(testDir + "/repository2/.duplicacy") SetDuplicacyPreferencePath(testDir + "/repository2/.duplicacy")
failedFiles = unencBackupManager.Restore(testDir+"/repository2", 1, &RestoreOptions{ failedFiles = unencBackupManager.Restore(testDir+"/repository2", threads /*inPlace=*/, true /*quickMode=*/, false, threads /*overwrite=*/, false,
Threads: threads, /*deleteMode=*/ false /*setowner=*/, false /*showStatistics=*/, false /*patterns=*/, nil /*allowFailures=*/, true)
Patterns: nil,
InPlace: true,
QuickMode: false,
Overwrite: false,
DeleteMode: false,
ShowStatistics: false,
AllowFailures: true,
})
assertRestoreFailures(t, failedFiles, 1) assertRestoreFailures(t, failedFiles, 1)
// check restore, expect file1 to be corrupted // check restore, expect file1 to be corrupted
checkCorruptedFile("/repository2", "file1") checkCorruptedFile("/repository2", "file1")
os.RemoveAll(testDir + "/repository2")
os.RemoveAll(testDir+"/repository2")
SetDuplicacyPreferencePath(testDir + "/repository2/.duplicacy") SetDuplicacyPreferencePath(testDir + "/repository2/.duplicacy")
failedFiles = encBackupManager.Restore(testDir+"/repository2", 1, &RestoreOptions{ failedFiles = encBackupManager.Restore(testDir+"/repository2", threads /*inPlace=*/, true /*quickMode=*/, false, threads /*overwrite=*/, false,
Threads: threads, /*deleteMode=*/ false /*setowner=*/, false /*showStatistics=*/, false /*patterns=*/, nil /*allowFailures=*/, true)
Patterns: nil,
InPlace: true,
QuickMode: false,
Overwrite: false,
DeleteMode: false,
ShowStatistics: false,
AllowFailures: true,
})
assertRestoreFailures(t, failedFiles, 1) assertRestoreFailures(t, failedFiles, 1)
// check restore, expect file3 to be corrupted // check restore, expect file3 to be corrupted
@@ -735,35 +685,20 @@ func TestPersistRestore(t *testing.T) {
//SetLoggingLevel(DEBUG) //SetLoggingLevel(DEBUG)
// test restore corrupted, inPlace = false, corrupted files will be missing // test restore corrupted, inPlace = false, corrupted files will be missing
os.RemoveAll(testDir + "/repository2") os.RemoveAll(testDir+"/repository2")
SetDuplicacyPreferencePath(testDir + "/repository2/.duplicacy") SetDuplicacyPreferencePath(testDir + "/repository2/.duplicacy")
failedFiles = unencBackupManager.Restore(testDir+"/repository2", 1, &RestoreOptions{ failedFiles = unencBackupManager.Restore(testDir+"/repository2", threads /*inPlace=*/, false /*quickMode=*/, false, threads /*overwrite=*/, false,
Threads: threads, /*deleteMode=*/ false /*setowner=*/, false /*showStatistics=*/, false /*patterns=*/, nil /*allowFailures=*/, true)
Patterns: nil,
InPlace: false,
QuickMode: false,
Overwrite: false,
DeleteMode: false,
ShowStatistics: false,
AllowFailures: true,
})
assertRestoreFailures(t, failedFiles, 1) assertRestoreFailures(t, failedFiles, 1)
// check restore, expect file1 to be corrupted // check restore, expect file1 to be corrupted
checkMissingFile("/repository2", "file1") checkMissingFile("/repository2", "file1")
os.RemoveAll(testDir + "/repository2")
os.RemoveAll(testDir+"/repository2")
SetDuplicacyPreferencePath(testDir + "/repository2/.duplicacy") SetDuplicacyPreferencePath(testDir + "/repository2/.duplicacy")
failedFiles = encBackupManager.Restore(testDir+"/repository2", 1, &RestoreOptions{ failedFiles = encBackupManager.Restore(testDir+"/repository2", threads /*inPlace=*/, false /*quickMode=*/, false, threads /*overwrite=*/, false,
Threads: threads, /*deleteMode=*/ false /*setowner=*/, false /*showStatistics=*/, false /*patterns=*/, nil /*allowFailures=*/, true)
Patterns: nil,
InPlace: false,
QuickMode: false,
Overwrite: false,
DeleteMode: false,
ShowStatistics: false,
AllowFailures: true,
})
assertRestoreFailures(t, failedFiles, 1) assertRestoreFailures(t, failedFiles, 1)
// check restore, expect file3 to be corrupted // check restore, expect file3 to be corrupted
@@ -772,60 +707,28 @@ func TestPersistRestore(t *testing.T) {
// test restore corrupted files from different backups, inPlace = true // test restore corrupted files from different backups, inPlace = true
// with overwrite=true, corrupted file1 from unenc will be restored correctly from enc // with overwrite=true, corrupted file1 from unenc will be restored correctly from enc
// the latter will not touch the existing file3 with correct hash // the latter will not touch the existing file3 with correct hash
os.RemoveAll(testDir + "/repository2") os.RemoveAll(testDir+"/repository2")
failedFiles = unencBackupManager.Restore(testDir+"/repository2", 1, &RestoreOptions{ failedFiles = unencBackupManager.Restore(testDir+"/repository2", threads /*inPlace=*/, true /*quickMode=*/, false, threads /*overwrite=*/, false,
Threads: threads, /*deleteMode=*/ false /*setowner=*/, false /*showStatistics=*/, false /*patterns=*/, nil /*allowFailures=*/, true)
Patterns: nil,
InPlace: true,
QuickMode: false,
Overwrite: false,
DeleteMode: false,
ShowStatistics: false,
AllowFailures: true,
})
assertRestoreFailures(t, failedFiles, 1) assertRestoreFailures(t, failedFiles, 1)
failedFiles = encBackupManager.Restore(testDir+"/repository2", 1, &RestoreOptions{ failedFiles = encBackupManager.Restore(testDir+"/repository2", threads /*inPlace=*/, true /*quickMode=*/, false, threads /*overwrite=*/, true,
Threads: threads, /*deleteMode=*/ false /*setowner=*/, false /*showStatistics=*/, false /*patterns=*/, nil /*allowFailures=*/, true)
Patterns: nil,
InPlace: true,
QuickMode: false,
Overwrite: true,
DeleteMode: false,
ShowStatistics: false,
AllowFailures: true,
})
assertRestoreFailures(t, failedFiles, 0) assertRestoreFailures(t, failedFiles, 0)
checkAllUncorrupted("/repository2") checkAllUncorrupted("/repository2")
// restore to repository3, with overwrite and allowFailures (true/false), quickMode = false (use hashes) // restore to repository3, with overwrite and allowFailures (true/false), quickMode = false (use hashes)
// should always succeed as uncorrupted files already exist with correct hash, so these will be ignored // should always succeed as uncorrupted files already exist with correct hash, so these will be ignored
SetDuplicacyPreferencePath(testDir + "/repository3/.duplicacy") SetDuplicacyPreferencePath(testDir + "/repository3/.duplicacy")
failedFiles = unencBackupManager.Restore(testDir+"/repository3", 1, &RestoreOptions{ failedFiles = unencBackupManager.Restore(testDir+"/repository3", threads /*inPlace=*/, true /*quickMode=*/, false, threads /*overwrite=*/, true,
Threads: threads, /*deleteMode=*/ false /*setowner=*/, false /*showStatistics=*/, false /*patterns=*/, nil /*allowFailures=*/, false)
Patterns: nil,
InPlace: true,
QuickMode: false,
Overwrite: true,
DeleteMode: false,
ShowStatistics: false,
AllowFailures: false,
})
assertRestoreFailures(t, failedFiles, 0) assertRestoreFailures(t, failedFiles, 0)
checkAllUncorrupted("/repository3") checkAllUncorrupted("/repository3")
failedFiles = unencBackupManager.Restore(testDir+"/repository3", 1, &RestoreOptions{ failedFiles = unencBackupManager.Restore(testDir+"/repository3", threads /*inPlace=*/, true /*quickMode=*/, false, threads /*overwrite=*/, true,
Threads: threads, /*deleteMode=*/ false /*setowner=*/, false /*showStatistics=*/, false /*patterns=*/, nil /*allowFailures=*/, true)
Patterns: nil,
InPlace: true,
QuickMode: false,
Overwrite: true,
DeleteMode: false,
ShowStatistics: false,
AllowFailures: true,
})
assertRestoreFailures(t, failedFiles, 0) assertRestoreFailures(t, failedFiles, 0)
checkAllUncorrupted("/repository3") checkAllUncorrupted("/repository3")
} }
} }

View File

@@ -85,8 +85,8 @@ type Config struct {
FileKey []byte `json:"-"` FileKey []byte `json:"-"`
// for erasure coding // for erasure coding
DataShards int `json:"data-shards"` DataShards int `json:'data-shards'`
ParityShards int `json:"parity-shards"` ParityShards int `json:'parity-shards'`
// for RSA encryption // for RSA encryption
rsaPrivateKey *rsa.PrivateKey rsaPrivateKey *rsa.PrivateKey

View File

@@ -4,11 +4,8 @@
package duplicacy package duplicacy
import ( import (
"bytes"
"crypto/sha256"
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
@@ -19,13 +16,11 @@ import (
"strconv" "strconv"
"strings" "strings"
"time" "time"
"bytes"
"crypto/sha256"
"github.com/vmihailenco/msgpack" "github.com/vmihailenco/msgpack"
)
const (
entryHardLinkRootChunkMarker = -9
entryHardLinkTargetChunkMarker = -10
) )
// This is the hidden directory in the repository for storing various files. // This is the hidden directory in the repository for storing various files.
@@ -98,7 +93,7 @@ func CreateEntryFromFileInfo(fileInfo os.FileInfo, directory string) *Entry {
Mode: uint32(mode), Mode: uint32(mode),
} }
GetOwner(entry, fileInfo) GetOwner(entry, &fileInfo)
return entry return entry
} }
@@ -115,45 +110,15 @@ func (entry *Entry) Copy() *Entry {
UID: entry.UID, UID: entry.UID,
GID: entry.GID, GID: entry.GID,
StartChunk: entry.StartChunk, StartChunk: entry.StartChunk,
StartOffset: entry.StartOffset, StartOffset: entry.StartOffset,
EndChunk: entry.EndChunk, EndChunk: entry.EndChunk,
EndOffset: entry.EndOffset, EndOffset: entry.EndOffset,
Attributes: entry.Attributes, Attributes: entry.Attributes,
} }
} }
func (entry *Entry) HardLinkTo(target *Entry, startChunk int, endChunk int) *Entry {
endOffset := target.EndOffset
link := entry.Link
if !target.IsFile() {
startChunk = target.StartChunk
endChunk = entry.EndChunk
endOffset = entry.EndOffset
link = target.Link
}
return &Entry{
Path: entry.Path,
Size: target.Size,
Time: target.Time,
Mode: target.Mode,
Link: link,
Hash: target.Hash,
UID: target.UID,
GID: target.GID,
StartChunk: startChunk,
StartOffset: target.StartOffset,
EndChunk: endChunk,
EndOffset: endOffset,
Attributes: target.Attributes,
}
}
// CreateEntryFromJSON creates an entry from a json description. // CreateEntryFromJSON creates an entry from a json description.
func (entry *Entry) UnmarshalJSON(description []byte) (err error) { func (entry *Entry) UnmarshalJSON(description []byte) (err error) {
@@ -397,12 +362,12 @@ func (entry *Entry) EncodeMsgpack(encoder *msgpack.Encoder) error {
if entry.Attributes != nil { if entry.Attributes != nil {
attributes := make([]string, numberOfAttributes) attributes := make([]string, numberOfAttributes)
i := 0 i := 0
for attribute := range *entry.Attributes { for attribute := range *entry.Attributes {
attributes[i] = attribute attributes[i] = attribute
i++ i++
} }
sort.Strings(attributes) sort.Strings(attributes)
for _, attribute := range attributes { for _, attribute := range attributes {
err = encoder.EncodeString(attribute) err = encoder.EncodeString(attribute)
if err != nil { if err != nil {
@@ -415,7 +380,7 @@ func (entry *Entry) EncodeMsgpack(encoder *msgpack.Encoder) error {
} }
} }
return nil return nil
} }
func (entry *Entry) DecodeMsgpack(decoder *msgpack.Decoder) error { func (entry *Entry) DecodeMsgpack(decoder *msgpack.Decoder) error {
@@ -523,42 +488,18 @@ func (entry *Entry) IsLink() bool {
return entry.Mode&uint32(os.ModeSymlink) != 0 return entry.Mode&uint32(os.ModeSymlink) != 0
} }
func (entry *Entry) IsSpecial() bool {
return entry.Mode&uint32(os.ModeNamedPipe|os.ModeDevice|os.ModeCharDevice|os.ModeSocket) != 0
}
func (entry *Entry) IsComplete() bool { func (entry *Entry) IsComplete() bool {
return entry.Size >= 0 return entry.Size >= 0
} }
func (entry *Entry) IsHardLinkChild() bool {
return (entry.IsFile() && len(entry.Link) > 0 && entry.Link != "/") || (!entry.IsDir() && entry.EndChunk == entryHardLinkTargetChunkMarker)
}
func (entry *Entry) IsHardLinkRoot() bool {
return (entry.IsFile() && entry.Link == "/") || (!entry.IsDir() && entry.EndChunk == entryHardLinkRootChunkMarker)
}
func (entry *Entry) GetHardLinkId() (int, error) {
if entry.IsFile() {
i, err := strconv.ParseUint(entry.Link, 16, 64)
return int(i), err
} else {
if entry.EndChunk != entryHardLinkTargetChunkMarker {
return 0, errors.New("Entry not marked as hard link child")
}
return entry.EndOffset, nil
}
}
func (entry *Entry) GetPermissions() os.FileMode { func (entry *Entry) GetPermissions() os.FileMode {
return os.FileMode(entry.Mode) & fileModeMask return os.FileMode(entry.Mode) & fileModeMask
} }
func (entry *Entry) GetParent() string { func (entry *Entry) GetParent() string {
path := entry.Path path := entry.Path
if path != "" && path[len(path)-1] == '/' { if path != "" && path[len(path) - 1] == '/' {
path = path[:len(path)-1] path = path[:len(path) - 1]
} }
i := strings.LastIndex(path, "/") i := strings.LastIndex(path, "/")
if i == -1 { if i == -1 {
@@ -582,64 +523,45 @@ func (entry *Entry) String(maxSizeDigits int) string {
return fmt.Sprintf("%*d %s %64s %s", maxSizeDigits, entry.Size, modifiedTime, entry.Hash, entry.Path) return fmt.Sprintf("%*d %s %64s %s", maxSizeDigits, entry.Size, modifiedTime, entry.Hash, entry.Path)
} }
type RestoreMetadataOptions struct { func (entry *Entry) RestoreMetadata(fullPath string, fileInfo *os.FileInfo, setOwner bool) bool {
SetOwner bool
ExcludeXattrs bool
NormalizeXattrs bool
IncludeFileFlags bool
FileFlagsMask uint32
}
func (entry *Entry) RestoreMetadata(fullPath string, fileInfo os.FileInfo,
options RestoreMetadataOptions) bool {
if fileInfo == nil { if fileInfo == nil {
var err error stat, err := os.Lstat(fullPath)
fileInfo, err = os.Lstat(fullPath) fileInfo = &stat
if err != nil { if err != nil {
LOG_ERROR("RESTORE_STAT", "Failed to retrieve the file info on %s: %v", entry.Path, err) LOG_ERROR("RESTORE_STAT", "Failed to retrieve the file info: %v", err)
return false return false
} }
} }
if !options.ExcludeXattrs {
err := entry.SetAttributesToFile(fullPath, options.NormalizeXattrs)
if err != nil {
LOG_WARN("RESTORE_ATTR", "Failed to set extended attributes on %s: %v", entry.Path, err)
}
}
// Note that chown can remove setuid/setgid bits so should be called before chmod // Note that chown can remove setuid/setgid bits so should be called before chmod
if options.SetOwner { if setOwner {
if !SetOwner(fullPath, entry, fileInfo) { if !SetOwner(fullPath, entry, fileInfo) {
return false return false
} }
} }
// Only set the permission if the file is not a symlink // Only set the permission if the file is not a symlink
if !entry.IsLink() && fileInfo.Mode()&fileModeMask != entry.GetPermissions() { if !entry.IsLink() && (*fileInfo).Mode()&fileModeMask != entry.GetPermissions() {
err := os.Chmod(fullPath, entry.GetPermissions()) err := os.Chmod(fullPath, entry.GetPermissions())
if err != nil { if err != nil {
LOG_ERROR("RESTORE_CHMOD", "Failed to set the file permissions on %s: %v", entry.Path, err) LOG_ERROR("RESTORE_CHMOD", "Failed to set the file permissions: %v", err)
return false return false
} }
} }
// Only set the time if the file is not a symlink // Only set the time if the file is not a symlink
if !entry.IsLink() && fileInfo.ModTime().Unix() != entry.Time { if !entry.IsLink() && (*fileInfo).ModTime().Unix() != entry.Time {
modifiedTime := time.Unix(entry.Time, 0) modifiedTime := time.Unix(entry.Time, 0)
err := os.Chtimes(fullPath, modifiedTime, modifiedTime) err := os.Chtimes(fullPath, modifiedTime, modifiedTime)
if err != nil { if err != nil {
LOG_ERROR("RESTORE_CHTIME", "Failed to set the modification time on %s: %v", entry.Path, err) LOG_ERROR("RESTORE_CHTIME", "Failed to set the modification time: %v", err)
return false return false
} }
} }
if options.IncludeFileFlags { if entry.Attributes != nil && len(*entry.Attributes) > 0 {
err := entry.RestoreLateFileFlags(fullPath, fileInfo, options.FileFlagsMask) entry.SetAttributesToFile(fullPath)
if err != nil {
LOG_WARN("RESTORE_FLAGS", "Failed to set file flags on %s: %v", entry.Path, err)
}
} }
return true return true
@@ -674,7 +596,7 @@ func ComparePaths(left string, right string) int {
for i := p; i < len(left); i++ { for i := p; i < len(left); i++ {
c3 = left[i] c3 = left[i]
if c3 == '/' { if c3 == '/' {
last1 = i == len(left)-1 last1 = i == len(left) - 1
break break
} }
} }
@@ -684,7 +606,7 @@ func ComparePaths(left string, right string) int {
for i := p; i < len(right); i++ { for i := p; i < len(right); i++ {
c4 = right[i] c4 = right[i]
if c4 == '/' { if c4 == '/' {
last2 = i == len(right)-1 last2 = i == len(right) - 1
break break
} }
} }
@@ -772,39 +694,10 @@ func (files FileInfoCompare) Less(i, j int) bool {
} }
} }
type EntryListerOptions struct { // ListEntries returns a list of entries representing file and subdirectories under the directory 'path'. Entry paths
Patterns []string // are normalized as relative to 'top'. 'patterns' are used to exclude or include certain files.
NoBackupFile string func ListEntries(top string, path string, patterns []string, nobackupFile string, excludeByAttribute bool, listingChannel chan *Entry) (directoryList []*Entry,
ExcludeByAttribute bool skippedFiles []string, err error) {
ExcludeXattrs bool
NormalizeXattr bool
IncludeFileFlags bool
IncludeSpecials bool
}
type EntryLister interface {
ListDir(top string, path string, listingChannel chan *Entry, options *EntryListerOptions) (directoryList []*Entry, skippedFiles []string, err error)
}
type LocalDirectoryLister struct {
linkIndex int
linkTable map[listEntryLinkKey]int // map unique inode details to initially found path
}
func NewLocalDirectoryLister() *LocalDirectoryLister {
return &LocalDirectoryLister{
linkTable: make(map[listEntryLinkKey]int),
}
}
// ListDir returns a list of entries representing file and subdirectories under the directory 'path'.
// Entry paths are normalized as relative to 'top'.
func (dl *LocalDirectoryLister) ListDir(top string, path string, listingChannel chan *Entry,
options *EntryListerOptions) (directoryList []*Entry, skippedFiles []string, err error) {
if options == nil {
options = &EntryListerOptions{}
}
LOG_DEBUG("LIST_ENTRIES", "Listing %s", path) LOG_DEBUG("LIST_ENTRIES", "Listing %s", path)
@@ -817,12 +710,10 @@ func (dl *LocalDirectoryLister) ListDir(top string, path string, listingChannel
return directoryList, nil, err return directoryList, nil, err
} }
patterns := options.Patterns
// This binary search works because ioutil.ReadDir returns files sorted by Name() by default // This binary search works because ioutil.ReadDir returns files sorted by Name() by default
if options.NoBackupFile != "" { if nobackupFile != "" {
ii := sort.Search(len(files), func(ii int) bool { return strings.Compare(files[ii].Name(), options.NoBackupFile) >= 0 }) ii := sort.Search(len(files), func(ii int) bool { return strings.Compare(files[ii].Name(), nobackupFile) >= 0 })
if ii < len(files) && files[ii].Name() == options.NoBackupFile { if ii < len(files) && files[ii].Name() == nobackupFile {
LOG_DEBUG("LIST_NOBACKUP", "%s is excluded due to nobackup file", path) LOG_DEBUG("LIST_NOBACKUP", "%s is excluded due to nobackup file", path)
return directoryList, skippedFiles, nil return directoryList, skippedFiles, nil
} }
@@ -844,44 +735,13 @@ func (dl *LocalDirectoryLister) ListDir(top string, path string, listingChannel
if f.Name() == DUPLICACY_DIRECTORY { if f.Name() == DUPLICACY_DIRECTORY {
continue continue
} }
entry := CreateEntryFromFileInfo(f, normalizedPath) entry := CreateEntryFromFileInfo(f, normalizedPath)
if len(patterns) > 0 && !MatchPath(entry.Path, patterns) { if len(patterns) > 0 && !MatchPath(entry.Path, patterns) {
continue continue
} }
linkKey, isHardLinked := entry.getHardLinkKey(f)
if isHardLinked {
if linkIndex, seen := dl.linkTable[linkKey]; seen {
if linkIndex == -1 {
LOG_DEBUG("LIST_EXCLUDE", "%s was excluded or skipped (hard link)", entry.Path)
continue
}
entry.Size = 0
if entry.IsFile() {
entry.Link = strconv.FormatInt(int64(linkIndex), 16)
} else {
entry.EndChunk = entryHardLinkTargetChunkMarker
entry.EndOffset = linkIndex
}
listingChannel <- entry
continue
} else {
if entry.IsFile() {
entry.Link = "/"
} else {
entry.EndChunk = entryHardLinkRootChunkMarker
}
dl.linkTable[linkKey] = -1
}
}
fullPath := joinPath(top, entry.Path)
if entry.IsLink() { if entry.IsLink() {
isRegular := false isRegular := false
isRegular, entry.Link, err = Readlink(fullPath) isRegular, entry.Link, err = Readlink(joinPath(top, entry.Path))
if err != nil { if err != nil {
LOG_WARN("LIST_LINK", "Failed to read the symlink %s: %v", entry.Path, err) LOG_WARN("LIST_LINK", "Failed to read the symlink %s: %v", entry.Path, err)
skippedFiles = append(skippedFiles, entry.Path) skippedFiles = append(skippedFiles, entry.Path)
@@ -891,7 +751,7 @@ func (dl *LocalDirectoryLister) ListDir(top string, path string, listingChannel
if isRegular { if isRegular {
entry.Mode ^= uint32(os.ModeSymlink) entry.Mode ^= uint32(os.ModeSymlink)
} else if path == "" && (filepath.IsAbs(entry.Link) || filepath.HasPrefix(entry.Link, `\\`)) && !strings.HasPrefix(entry.Link, normalizedTop) { } else if path == "" && (filepath.IsAbs(entry.Link) || filepath.HasPrefix(entry.Link, `\\`)) && !strings.HasPrefix(entry.Link, normalizedTop) {
stat, err := os.Stat(fullPath) stat, err := os.Stat(joinPath(top, entry.Path))
if err != nil { if err != nil {
LOG_WARN("LIST_LINK", "Failed to read the symlink: %v", err) LOG_WARN("LIST_LINK", "Failed to read the symlink: %v", err)
skippedFiles = append(skippedFiles, entry.Path) skippedFiles = append(skippedFiles, entry.Path)
@@ -909,35 +769,19 @@ func (dl *LocalDirectoryLister) ListDir(top string, path string, listingChannel
} }
entry = newEntry entry = newEntry
} }
} else if options.IncludeSpecials && entry.IsSpecial() {
if err := entry.ReadSpecial(fullPath, f); err != nil {
LOG_WARN("LIST_DEV", "Failed to save device node %s: %v", entry.Path, err)
skippedFiles = append(skippedFiles, entry.Path)
continue
}
} }
if !options.ExcludeXattrs { entry.ReadAttributes(top)
if err := entry.ReadAttributes(f, fullPath, false); err != nil {
LOG_WARN("LIST_ATTR", "Failed to read xattrs on %s: %v", entry.Path, err)
}
}
// if the flags are already in the FileInfo we can keep them if excludeByAttribute && entry.Attributes != nil && excludedByAttribute(*entry.Attributes) {
if !entry.GetFileFlags(f) && options.IncludeFileFlags {
if err := entry.ReadFileFlags(f, fullPath); err != nil {
LOG_WARN("LIST_ATTR", "Failed to read file flags on %s: %v", entry.Path, err)
}
}
if options.ExcludeByAttribute && entry.Attributes != nil && excludedByAttribute(*entry.Attributes) {
LOG_DEBUG("LIST_EXCLUDE", "%s is excluded by attribute", entry.Path) LOG_DEBUG("LIST_EXCLUDE", "%s is excluded by attribute", entry.Path)
continue continue
} }
if isHardLinked { if f.Mode()&(os.ModeNamedPipe|os.ModeSocket|os.ModeDevice) != 0 {
dl.linkTable[linkKey] = dl.linkIndex LOG_WARN("LIST_SKIP", "Skipped non-regular file %s", entry.Path)
dl.linkIndex++ skippedFiles = append(skippedFiles, entry.Path)
continue
} }
if entry.IsDir() { if entry.IsDir() {

View File

@@ -5,8 +5,7 @@
package duplicacy package duplicacy
import ( import (
"bytes" "io/ioutil"
"encoding/json"
"math/rand" "math/rand"
"os" "os"
"path/filepath" "path/filepath"
@@ -14,10 +13,11 @@ import (
"sort" "sort"
"strings" "strings"
"testing" "testing"
"bytes"
"encoding/json"
"github.com/pkg/xattr" "github.com/gilbertchen/xattr"
"github.com/vmihailenco/msgpack"
"github.com/vmihailenco/msgpack"
) )
func TestEntrySort(t *testing.T) { func TestEntrySort(t *testing.T) {
@@ -165,25 +165,23 @@ func TestEntryOrder(t *testing.T) {
continue continue
} }
err := os.WriteFile(fullPath, []byte(file), 0700) err := ioutil.WriteFile(fullPath, []byte(file), 0700)
if err != nil { if err != nil {
t.Errorf("WriteFile(%s) returned an error: %s", fullPath, err) t.Errorf("WriteFile(%s) returned an error: %s", fullPath, err)
} }
} }
lister := NewLocalDirectoryLister()
directories := make([]*Entry, 0, 4) directories := make([]*Entry, 0, 4)
directories = append(directories, CreateEntry("", 0, 0, 0)) directories = append(directories, CreateEntry("", 0, 0, 0))
entries := make([]*Entry, 0, 4) entries := make([]*Entry, 0, 4)
entryChannel := make(chan *Entry, 1024) entryChannel := make(chan *Entry, 1024)
entries = append(entries, CreateEntry("", 0, 0, 0)) entries = append(entries, CreateEntry("", 0, 0, 0))
for len(directories) > 0 { for len(directories) > 0 {
directory := directories[len(directories)-1] directory := directories[len(directories)-1]
directories = directories[:len(directories)-1] directories = directories[:len(directories)-1]
subdirectories, _, err := lister.ListDir(testDir, directory.Path, entryChannel, nil) subdirectories, _, err := ListEntries(testDir, directory.Path, nil, "", false, entryChannel)
if err != nil { if err != nil {
t.Errorf("ListEntries(%s, %s) returned an error: %s", testDir, directory.Path, err) t.Errorf("ListEntries(%s, %s) returned an error: %s", testDir, directory.Path, err)
} }
@@ -235,18 +233,8 @@ func TestEntryOrder(t *testing.T) {
// TestEntryExcludeByAttribute tests the excludeByAttribute parameter to the ListEntries function // TestEntryExcludeByAttribute tests the excludeByAttribute parameter to the ListEntries function
func TestEntryExcludeByAttribute(t *testing.T) { func TestEntryExcludeByAttribute(t *testing.T) {
var excludeAttrName string if !(runtime.GOOS == "darwin" || runtime.GOOS == "linux") {
var excludeAttrValue []byte t.Skip("skipping test not darwin or linux")
if runtime.GOOS == "darwin" {
excludeAttrName = "com.apple.metadata:com_apple_backup_excludeItem"
excludeAttrValue = []byte("com.apple.backupd")
} else if runtime.GOOS == "linux" {
excludeAttrName = "user.duplicacy_exclude"
} else if runtime.GOOS == "freebsd" || runtime.GOOS == "netbsd" {
excludeAttrName = "duplicacy_exclude"
} else {
t.Skip("skipping test, not darwin, linux, freebsd, or netbsd")
} }
testDir := filepath.Join(os.TempDir(), "duplicacy_test") testDir := filepath.Join(os.TempDir(), "duplicacy_test")
@@ -276,7 +264,7 @@ func TestEntryExcludeByAttribute(t *testing.T) {
continue continue
} }
err := os.WriteFile(fullPath, []byte(file), 0700) err := ioutil.WriteFile(fullPath, []byte(file), 0700)
if err != nil { if err != nil {
t.Errorf("WriteFile(%s) returned an error: %s", fullPath, err) t.Errorf("WriteFile(%s) returned an error: %s", fullPath, err)
} }
@@ -285,14 +273,12 @@ func TestEntryExcludeByAttribute(t *testing.T) {
for _, file := range DATA { for _, file := range DATA {
fullPath := filepath.Join(testDir, file) fullPath := filepath.Join(testDir, file)
if strings.Contains(file, "exclude") { if strings.Contains(file, "exclude") {
xattr.Set(fullPath, excludeAttrName, excludeAttrValue) xattr.Setxattr(fullPath, "com.apple.metadata:com_apple_backup_excludeItem", []byte("com.apple.backupd"))
} }
} }
for _, excludeByAttribute := range [2]bool{true, false} { for _, excludeByAttribute := range [2]bool{true, false} {
t.Logf("testing excludeByAttribute: %t", excludeByAttribute) t.Logf("testing excludeByAttribute: %t", excludeByAttribute)
lister := NewLocalDirectoryLister()
directories := make([]*Entry, 0, 4) directories := make([]*Entry, 0, 4)
directories = append(directories, CreateEntry("", 0, 0, 0)) directories = append(directories, CreateEntry("", 0, 0, 0))
@@ -303,11 +289,7 @@ func TestEntryExcludeByAttribute(t *testing.T) {
for len(directories) > 0 { for len(directories) > 0 {
directory := directories[len(directories)-1] directory := directories[len(directories)-1]
directories = directories[:len(directories)-1] directories = directories[:len(directories)-1]
subdirectories, _, err := lister.ListDir(testDir, directory.Path, entryChannel, subdirectories, _, err := ListEntries(testDir, directory.Path, nil, "", excludeByAttribute, entryChannel)
&EntryListerOptions{
ExcludeByAttribute: excludeByAttribute,
})
if err != nil { if err != nil {
t.Errorf("ListEntries(%s, %s) returned an error: %s", testDir, directory.Path, err) t.Errorf("ListEntries(%s, %s) returned an error: %s", testDir, directory.Path, err)
} }
@@ -390,4 +372,4 @@ func TestEntryEncoding(t *testing.T) {
t.Error("Decoded entry is different than the original one") t.Error("Decoded entry is different than the original one")
} }
} }

View File

@@ -111,12 +111,12 @@ func (entryList *EntryList)createOnDiskFile() error {
// Add an entry to the entry list // Add an entry to the entry list
func (entryList *EntryList)AddEntry(entry *Entry) error { func (entryList *EntryList)AddEntry(entry *Entry) error {
if entry.IsFile() { if !entry.IsDir() && !entry.IsLink() {
entryList.NumberOfEntries++ entryList.NumberOfEntries++
} }
if !entry.IsComplete() { if !entry.IsComplete() {
if !entry.IsFile() { if entry.IsDir() || entry.IsLink() {
entry.Size = 0 entry.Size = 0
} else { } else {
modifiedEntry := ModifiedEntry { modifiedEntry := ModifiedEntry {

View File

@@ -6,7 +6,7 @@ package duplicacy
import ( import (
"encoding/json" "encoding/json"
"os" "io/ioutil"
"syscall" "syscall"
"unsafe" "unsafe"
) )
@@ -86,7 +86,7 @@ func keyringGet(key string) (value string) {
return "" return ""
} }
description, err := os.ReadFile(keyringFile) description, err := ioutil.ReadFile(keyringFile)
if err != nil { if err != nil {
LOG_DEBUG("KEYRING_READ", "Keyring file not read: %v", err) LOG_DEBUG("KEYRING_READ", "Keyring file not read: %v", err)
return "" return ""
@@ -125,7 +125,7 @@ func keyringSet(key string, value string) bool {
keyring := make(map[string][]byte) keyring := make(map[string][]byte)
description, err := os.ReadFile(keyringFile) description, err := ioutil.ReadFile(keyringFile)
if err == nil { if err == nil {
err = json.Unmarshal(description, &keyring) err = json.Unmarshal(description, &keyring)
if err != nil { if err != nil {
@@ -160,7 +160,7 @@ func keyringSet(key string, value string) bool {
return false return false
} }
err = os.WriteFile(keyringFile, description, 0600) err = ioutil.WriteFile(keyringFile, description, 0600)
if err != nil { if err != nil {
LOG_DEBUG("KEYRING_WRITE", "Failed to save the keyring storage to file %s: %v", keyringFile, err) LOG_DEBUG("KEYRING_WRITE", "Failed to save the keyring storage to file %s: %v", keyringFile, err)
return false return false

View File

@@ -6,57 +6,27 @@ package duplicacy
import ( import (
"encoding/json" "encoding/json"
"fmt" "io/ioutil"
"os" "os"
"path" "path"
"reflect" "reflect"
"strconv"
"strings" "strings"
) )
type flagsMask uint32
func (f flagsMask) MarshalJSON() ([]byte, error) {
return json.Marshal(fmt.Sprintf("0x%.8x", f))
}
func (f *flagsMask) UnmarshalJSON(data []byte) error {
var str string
if err := json.Unmarshal(data, &str); err != nil {
return err
}
if str[0] == '0' && (str[1] == 'x' || str[1] == 'X') {
str = str[2:]
}
v, err := strconv.ParseUint(string(str), 16, 32)
if err != nil {
return err
}
*f = flagsMask(v)
return nil
}
// Preference stores options for each storage. // Preference stores options for each storage.
type Preference struct { type Preference struct {
Name string `json:"name"` Name string `json:"name"`
SnapshotID string `json:"id"` SnapshotID string `json:"id"`
RepositoryPath string `json:"repository"` RepositoryPath string `json:"repository"`
StorageURL string `json:"storage"` StorageURL string `json:"storage"`
Encrypted bool `json:"encrypted"` Encrypted bool `json:"encrypted"`
BackupProhibited bool `json:"no_backup"` BackupProhibited bool `json:"no_backup"`
RestoreProhibited bool `json:"no_restore"` RestoreProhibited bool `json:"no_restore"`
DoNotSavePassword bool `json:"no_save_password"` DoNotSavePassword bool `json:"no_save_password"`
NobackupFile string `json:"nobackup_file"` NobackupFile string `json:"nobackup_file"`
Keys map[string]string `json:"keys"` Keys map[string]string `json:"keys"`
FiltersFile string `json:"filters"` FiltersFile string `json:"filters"`
ExcludeOwner bool `json:"exclude_owner"` ExcludeByAttribute bool `json:"exclude_by_attribute"`
ExcludeByAttribute bool `json:"exclude_by_attribute"`
ExcludeXattrs bool `json:"exclude_xattrs"`
NormalizeXattrs bool `json:"normalize_xattrs"`
IncludeFileFlags bool `json:"include_file_flags"`
IncludeSpecials bool `json:"include_specials"`
FileFlagsMask flagsMask `json:"file_flags_mask"`
} }
var preferencePath string var preferencePath string
@@ -73,7 +43,7 @@ func LoadPreferences(repository string) bool {
} }
if !stat.IsDir() { if !stat.IsDir() {
content, err := os.ReadFile(preferencePath) content, err := ioutil.ReadFile(preferencePath)
if err != nil { if err != nil {
LOG_ERROR("DOT_DUPLICACY_PATH", "Failed to locate the preference path: %v", err) LOG_ERROR("DOT_DUPLICACY_PATH", "Failed to locate the preference path: %v", err)
return false return false
@@ -91,7 +61,7 @@ func LoadPreferences(repository string) bool {
preferencePath = realPreferencePath preferencePath = realPreferencePath
} }
description, err := os.ReadFile(path.Join(preferencePath, "preferences")) description, err := ioutil.ReadFile(path.Join(preferencePath, "preferences"))
if err != nil { if err != nil {
LOG_ERROR("PREFERENCE_OPEN", "Failed to read the preference file from repository %s: %v", repository, err) LOG_ERROR("PREFERENCE_OPEN", "Failed to read the preference file from repository %s: %v", repository, err)
return false return false
@@ -140,7 +110,7 @@ func SavePreferences() bool {
} }
preferenceFile := path.Join(GetDuplicacyPreferencePath(), "preferences") preferenceFile := path.Join(GetDuplicacyPreferencePath(), "preferences")
err = os.WriteFile(preferenceFile, description, 0600) err = ioutil.WriteFile(preferenceFile, description, 0600)
if err != nil { if err != nil {
LOG_ERROR("PREFERENCE_WRITE", "Failed to save the preference file %s: %v", preferenceFile, err) LOG_ERROR("PREFERENCE_WRITE", "Failed to save the preference file %s: %v", preferenceFile, err)
return false return false

View File

@@ -10,6 +10,7 @@ package duplicacy
import ( import (
"context" "context"
"errors" "errors"
"io/ioutil"
"os" "os"
"os/exec" "os/exec"
"regexp" "regexp"
@@ -135,7 +136,7 @@ func CreateShadowCopy(top string, shadowCopy bool, timeoutInSeconds int) (shadow
} }
// Create mount point // Create mount point
snapshotPath, err = os.MkdirTemp("/tmp/", "snp_") snapshotPath, err = ioutil.TempDir("/tmp/", "snp_")
if err != nil { if err != nil {
LOG_ERROR("VSS_CREATE", "Failed to create temporary mount directory") LOG_ERROR("VSS_CREATE", "Failed to create temporary mount directory")
return top return top

View File

@@ -9,13 +9,15 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"sort"
"strings" "strings"
"time" "time"
"sort"
"github.com/vmihailenco/msgpack"
"github.com/vmihailenco/msgpack"
) )
// Snapshot represents a backup of the repository. // Snapshot represents a backup of the repository.
@@ -58,41 +60,19 @@ func CreateEmptySnapshot(id string) (snapshto *Snapshot) {
type DirectoryListing struct { type DirectoryListing struct {
directory string directory string
files *[]Entry files *[]Entry
} }
type ListFilesOptions struct { func (snapshot *Snapshot) ListLocalFiles(top string, nobackupFile string,
NoBackupFile string filtersFile string, excludeByAttribute bool, listingChannel chan *Entry,
FiltersFile string skippedDirectories *[]string, skippedFiles *[]string) {
ExcludeByAttribute bool
ExcludeXattrs bool
NormalizeXattr bool
IncludeFileFlags bool
IncludeSpecials bool
}
func NewListFilesOptions(p *Preference) *ListFilesOptions { var patterns []string
return &ListFilesOptions{
NoBackupFile: p.NobackupFile, if filtersFile == "" {
FiltersFile: p.FiltersFile, filtersFile = joinPath(GetDuplicacyPreferencePath(), "filters")
ExcludeByAttribute: p.ExcludeByAttribute,
ExcludeXattrs: p.ExcludeXattrs,
NormalizeXattr: p.NormalizeXattrs,
IncludeFileFlags: p.IncludeFileFlags,
IncludeSpecials: p.IncludeSpecials,
} }
} patterns = ProcessFilters(filtersFile)
func (snapshot *Snapshot) ListLocalFiles(top string,
listingChannel chan *Entry, skippedDirectories *[]string, skippedFiles *[]string,
options *ListFilesOptions) {
if options.FiltersFile == "" {
options.FiltersFile = joinPath(GetDuplicacyPreferencePath(), "filters")
}
patterns := ProcessFilters(options.FiltersFile)
lister := NewLocalDirectoryLister()
directories := make([]*Entry, 0, 256) directories := make([]*Entry, 0, 256)
directories = append(directories, CreateEntry("", 0, 0, 0)) directories = append(directories, CreateEntry("", 0, 0, 0))
@@ -101,16 +81,7 @@ func (snapshot *Snapshot) ListLocalFiles(top string,
directory := directories[len(directories)-1] directory := directories[len(directories)-1]
directories = directories[:len(directories)-1] directories = directories[:len(directories)-1]
subdirectories, skipped, err := lister.ListDir(top, directory.Path, listingChannel, subdirectories, skipped, err := ListEntries(top, directory.Path, patterns, nobackupFile, excludeByAttribute, listingChannel)
&EntryListerOptions{
Patterns: patterns,
NoBackupFile: options.NoBackupFile,
ExcludeByAttribute: options.ExcludeByAttribute,
ExcludeXattrs: options.ExcludeXattrs,
NormalizeXattr: options.NormalizeXattr,
IncludeFileFlags: options.IncludeFileFlags,
IncludeSpecials: options.IncludeSpecials,
})
if err != nil { if err != nil {
if directory.Path == "" { if directory.Path == "" {
LOG_ERROR("LIST_FAILURE", "Failed to list the repository root: %v", err) LOG_ERROR("LIST_FAILURE", "Failed to list the repository root: %v", err)
@@ -133,7 +104,7 @@ func (snapshot *Snapshot) ListLocalFiles(top string,
close(listingChannel) close(listingChannel)
} }
func (snapshot *Snapshot) ListRemoteFiles(config *Config, chunkOperator *ChunkOperator, entryOut func(*Entry) bool) { func (snapshot *Snapshot)ListRemoteFiles(config *Config, chunkOperator *ChunkOperator, entryOut func(*Entry) bool) {
var chunks []string var chunks []string
for _, chunkHash := range snapshot.FileSequence { for _, chunkHash := range snapshot.FileSequence {
@@ -153,12 +124,12 @@ func (snapshot *Snapshot) ListRemoteFiles(config *Config, chunkOperator *ChunkOp
if chunk != nil { if chunk != nil {
config.PutChunk(chunk) config.PutChunk(chunk)
} }
}() } ()
// Normally if Version is 0 then the snapshot is created by CLI v2 but unfortunately CLI 3.0.1 does not set the // Normally if Version is 0 then the snapshot is created by CLI v2 but unfortunately CLI 3.0.1 does not set the
// version bit correctly when copying old backups. So we need to check the first byte -- if it is '[' then it is // version bit correctly when copying old backups. So we need to check the first byte -- if it is '[' then it is
// the old format. The new format starts with a string encoded in msgpack and the first byte can't be '['. // the old format. The new format starts with a string encoded in msgpack and the first byte can't be '['.
if snapshot.Version == 0 || reader.GetFirstByte() == '[' { if snapshot.Version == 0 || reader.GetFirstByte() == '['{
LOG_INFO("SNAPSHOT_VERSION", "snapshot %s at revision %d is encoded in an old version format", snapshot.ID, snapshot.Revision) LOG_INFO("SNAPSHOT_VERSION", "snapshot %s at revision %d is encoded in an old version format", snapshot.ID, snapshot.Revision)
files := make([]*Entry, 0) files := make([]*Entry, 0)
decoder := json.NewDecoder(reader) decoder := json.NewDecoder(reader)
@@ -229,7 +200,7 @@ func (snapshot *Snapshot) ListRemoteFiles(config *Config, chunkOperator *ChunkOp
} else { } else {
LOG_ERROR("SNAPSHOT_VERSION", "snapshot %s at revision %d is encoded in unsupported version %d format", LOG_ERROR("SNAPSHOT_VERSION", "snapshot %s at revision %d is encoded in unsupported version %d format",
snapshot.ID, snapshot.Revision, snapshot.Version) snapshot.ID, snapshot.Revision, snapshot.Version)
return return
} }
@@ -272,7 +243,7 @@ func ProcessFilterFile(patternFile string, includedFiles []string) (patterns []s
} }
includedFiles = append(includedFiles, patternFile) includedFiles = append(includedFiles, patternFile)
LOG_INFO("SNAPSHOT_FILTER", "Parsing filter file %s", patternFile) LOG_INFO("SNAPSHOT_FILTER", "Parsing filter file %s", patternFile)
patternFileContent, err := os.ReadFile(patternFile) patternFileContent, err := ioutil.ReadFile(patternFile)
if err == nil { if err == nil {
patternFileLines := strings.Split(string(patternFileContent), "\n") patternFileLines := strings.Split(string(patternFileContent), "\n")
patterns = ProcessFilterLines(patternFileLines, includedFiles) patterns = ProcessFilterLines(patternFileLines, includedFiles)
@@ -292,7 +263,7 @@ func ProcessFilterLines(patternFileLines []string, includedFiles []string) (patt
if patternIncludeFile == "" { if patternIncludeFile == "" {
continue continue
} }
if !filepath.IsAbs(patternIncludeFile) { if ! filepath.IsAbs(patternIncludeFile) {
basePath := "" basePath := ""
if len(includedFiles) == 0 { if len(includedFiles) == 0 {
basePath, _ = os.Getwd() basePath, _ = os.Getwd()
@@ -519,3 +490,4 @@ func encodeSequence(sequence []string) []string {
return sequenceInHex return sequenceInHex
} }

View File

@@ -18,10 +18,10 @@ import (
"sort" "sort"
"strconv" "strconv"
"strings" "strings"
"sync"
"sync/atomic"
"text/tabwriter" "text/tabwriter"
"time" "time"
"sync"
"sync/atomic"
"github.com/aryann/difflib" "github.com/aryann/difflib"
) )
@@ -191,7 +191,7 @@ type SnapshotManager struct {
fileChunk *Chunk fileChunk *Chunk
snapshotCache *FileStorage snapshotCache *FileStorage
chunkOperator *ChunkOperator chunkOperator *ChunkOperator
} }
// CreateSnapshotManager creates a snapshot manager // CreateSnapshotManager creates a snapshot manager
@@ -738,7 +738,7 @@ func (manager *SnapshotManager) ListSnapshots(snapshotID string, revisionsToList
totalFileSize := int64(0) totalFileSize := int64(0)
lastChunk := 0 lastChunk := 0
snapshot.ListRemoteFiles(manager.config, manager.chunkOperator, func(file *Entry) bool { snapshot.ListRemoteFiles(manager.config, manager.chunkOperator, func(file *Entry)bool {
if file.IsFile() { if file.IsFile() {
totalFiles++ totalFiles++
totalFileSize += file.Size totalFileSize += file.Size
@@ -753,7 +753,7 @@ func (manager *SnapshotManager) ListSnapshots(snapshotID string, revisionsToList
return true return true
}) })
snapshot.ListRemoteFiles(manager.config, manager.chunkOperator, func(file *Entry) bool { snapshot.ListRemoteFiles(manager.config, manager.chunkOperator, func(file *Entry)bool {
if file.IsFile() { if file.IsFile() {
LOG_INFO("SNAPSHOT_FILE", "%s", file.String(maxSizeDigits)) LOG_INFO("SNAPSHOT_FILE", "%s", file.String(maxSizeDigits))
} }
@@ -908,7 +908,7 @@ func (manager *SnapshotManager) CheckSnapshots(snapshotID string, revisionsToChe
_, exist, _, err := manager.storage.FindChunk(0, chunkID, false) _, exist, _, err := manager.storage.FindChunk(0, chunkID, false)
if err != nil { if err != nil {
LOG_WARN("SNAPSHOT_VALIDATE", "Failed to check the existence of chunk %s: %v", LOG_WARN("SNAPSHOT_VALIDATE", "Failed to check the existence of chunk %s: %v",
chunkID, err) chunkID, err)
} else if exist { } else if exist {
LOG_INFO("SNAPSHOT_VALIDATE", "Chunk %s is confirmed to exist", chunkID) LOG_INFO("SNAPSHOT_VALIDATE", "Chunk %s is confirmed to exist", chunkID)
continue continue
@@ -1031,7 +1031,7 @@ func (manager *SnapshotManager) CheckSnapshots(snapshotID string, revisionsToChe
if err != nil { if err != nil {
LOG_WARN("SNAPSHOT_VERIFY", "Failed to save the verified chunks file: %v", err) LOG_WARN("SNAPSHOT_VERIFY", "Failed to save the verified chunks file: %v", err)
} else { } else {
LOG_INFO("SNAPSHOT_VERIFY", "Added %d chunks to the list of verified chunks", len(verifiedChunks)-numberOfVerifiedChunks) LOG_INFO("SNAPSHOT_VERIFY", "Added %d chunks to the list of verified chunks", len(verifiedChunks) - numberOfVerifiedChunks)
} }
} }
} }
@@ -1073,7 +1073,7 @@ func (manager *SnapshotManager) CheckSnapshots(snapshotID string, revisionsToChe
defer CatchLogException() defer CatchLogException()
for { for {
chunkIndex, ok := <-chunkChannel chunkIndex, ok := <- chunkChannel
if !ok { if !ok {
wg.Done() wg.Done()
return return
@@ -1093,14 +1093,14 @@ func (manager *SnapshotManager) CheckSnapshots(snapshotID string, revisionsToChe
elapsedTime := time.Now().Sub(startTime).Seconds() elapsedTime := time.Now().Sub(startTime).Seconds()
speed := int64(float64(downloadedChunkSize) / elapsedTime) speed := int64(float64(downloadedChunkSize) / elapsedTime)
remainingTime := int64(float64(totalChunks-downloadedChunks) / float64(downloadedChunks) * elapsedTime) remainingTime := int64(float64(totalChunks - downloadedChunks) / float64(downloadedChunks) * elapsedTime)
percentage := float64(downloadedChunks) / float64(totalChunks) * 100.0 percentage := float64(downloadedChunks) / float64(totalChunks) * 100.0
LOG_INFO("VERIFY_PROGRESS", "Verified chunk %s (%d/%d), %sB/s %s %.1f%%", LOG_INFO("VERIFY_PROGRESS", "Verified chunk %s (%d/%d), %sB/s %s %.1f%%",
chunkID, downloadedChunks, totalChunks, PrettySize(speed), PrettyTime(remainingTime), percentage) chunkID, downloadedChunks, totalChunks, PrettySize(speed), PrettyTime(remainingTime), percentage)
manager.config.PutChunk(chunk) manager.config.PutChunk(chunk)
} }
}() } ()
} }
for chunkIndex := range chunkHashes { for chunkIndex := range chunkHashes {
@@ -1289,10 +1289,10 @@ func (manager *SnapshotManager) PrintSnapshot(snapshot *Snapshot) bool {
} }
// Don't print the ending bracket // Don't print the ending bracket
fmt.Printf("%s", string(description[:len(description)-2])) fmt.Printf("%s", string(description[:len(description) - 2]))
fmt.Printf(",\n \"files\": [\n") fmt.Printf(",\n \"files\": [\n")
isFirstFile := true isFirstFile := true
snapshot.ListRemoteFiles(manager.config, manager.chunkOperator, func(file *Entry) bool { snapshot.ListRemoteFiles(manager.config, manager.chunkOperator, func (file *Entry) bool {
fileDescription, _ := json.MarshalIndent(file.convertToObject(false), "", " ") fileDescription, _ := json.MarshalIndent(file.convertToObject(false), "", " ")
@@ -1322,7 +1322,7 @@ func (manager *SnapshotManager) VerifySnapshot(snapshot *Snapshot) bool {
} }
files := make([]*Entry, 0) files := make([]*Entry, 0)
snapshot.ListRemoteFiles(manager.config, manager.chunkOperator, func(file *Entry) bool { snapshot.ListRemoteFiles(manager.config, manager.chunkOperator, func (file *Entry) bool {
if file.IsFile() && file.Size != 0 { if file.IsFile() && file.Size != 0 {
file.Attributes = nil file.Attributes = nil
files = append(files, file) files = append(files, file)
@@ -1426,7 +1426,7 @@ func (manager *SnapshotManager) RetrieveFile(snapshot *Snapshot, file *Entry, la
func (manager *SnapshotManager) FindFile(snapshot *Snapshot, filePath string, suppressError bool) *Entry { func (manager *SnapshotManager) FindFile(snapshot *Snapshot, filePath string, suppressError bool) *Entry {
var found *Entry var found *Entry
snapshot.ListRemoteFiles(manager.config, manager.chunkOperator, func(entry *Entry) bool { snapshot.ListRemoteFiles(manager.config, manager.chunkOperator, func (entry *Entry) bool {
if entry.Path == filePath { if entry.Path == filePath {
found = entry found = entry
return false return false
@@ -1479,8 +1479,8 @@ func (manager *SnapshotManager) PrintFile(snapshotID string, revision int, path
file := manager.FindFile(snapshot, path, false) file := manager.FindFile(snapshot, path, false)
if !manager.RetrieveFile(snapshot, file, nil, func(chunk []byte) { if !manager.RetrieveFile(snapshot, file, nil, func(chunk []byte) {
fmt.Printf("%s", chunk) fmt.Printf("%s", chunk)
}) { }) {
LOG_ERROR("SNAPSHOT_RETRIEVE", "File %s is corrupted in snapshot %s at revision %d", LOG_ERROR("SNAPSHOT_RETRIEVE", "File %s is corrupted in snapshot %s at revision %d",
path, snapshot.ID, snapshot.Revision) path, snapshot.ID, snapshot.Revision)
return false return false
@@ -1491,8 +1491,7 @@ func (manager *SnapshotManager) PrintFile(snapshotID string, revision int, path
// Diff compares two snapshots, or two revision of a file if the file argument is given. // Diff compares two snapshots, or two revision of a file if the file argument is given.
func (manager *SnapshotManager) Diff(top string, snapshotID string, revisions []int, func (manager *SnapshotManager) Diff(top string, snapshotID string, revisions []int,
filePath string, compareByHash bool, filePath string, compareByHash bool, nobackupFile string, filtersFile string, excludeByAttribute bool) bool {
options *ListFilesOptions) bool {
LOG_DEBUG("DIFF_PARAMETERS", "top: %s, id: %s, revision: %v, path: %s, compareByHash: %t", LOG_DEBUG("DIFF_PARAMETERS", "top: %s, id: %s, revision: %v, path: %s, compareByHash: %t",
top, snapshotID, revisions, filePath, compareByHash) top, snapshotID, revisions, filePath, compareByHash)
@@ -1501,7 +1500,7 @@ func (manager *SnapshotManager) Diff(top string, snapshotID string, revisions []
defer func() { defer func() {
manager.chunkOperator.Stop() manager.chunkOperator.Stop()
manager.chunkOperator = nil manager.chunkOperator = nil
}() } ()
var leftSnapshot *Snapshot var leftSnapshot *Snapshot
var rightSnapshot *Snapshot var rightSnapshot *Snapshot
@@ -1517,11 +1516,11 @@ func (manager *SnapshotManager) Diff(top string, snapshotID string, revisions []
localListingChannel := make(chan *Entry) localListingChannel := make(chan *Entry)
go func() { go func() {
defer CatchLogException() defer CatchLogException()
rightSnapshot.ListLocalFiles(top, localListingChannel, nil, nil, options) rightSnapshot.ListLocalFiles(top, nobackupFile, filtersFile, excludeByAttribute, localListingChannel, nil, nil)
}() } ()
for entry := range localListingChannel { for entry := range localListingChannel {
entry.Attributes = nil // attributes are not compared entry.Attributes = nil // attributes are not compared
rightSnapshotFiles = append(rightSnapshotFiles, entry) rightSnapshotFiles = append(rightSnapshotFiles, entry)
} }
@@ -1726,7 +1725,7 @@ func (manager *SnapshotManager) ShowHistory(top string, snapshotID string, revis
defer func() { defer func() {
manager.chunkOperator.Stop() manager.chunkOperator.Stop()
manager.chunkOperator = nil manager.chunkOperator = nil
}() } ()
var err error var err error
@@ -1822,16 +1821,15 @@ func (manager *SnapshotManager) resurrectChunk(fossilPath string, chunkID string
// PruneSnapshots deletes snapshots by revisions, tags, or a retention policy. The main idea is two-step // PruneSnapshots deletes snapshots by revisions, tags, or a retention policy. The main idea is two-step
// fossil collection. // fossil collection.
// 1. Delete snapshots specified by revision, retention policy, with a tag. Find any resulting unreferenced
// chunks, and mark them as fossils (by renaming). After that, create a fossil collection file containing
// fossils collected during current run, and temporary files encountered. Also in the file is the latest
// revision for each snapshot id. Save this file to a local directory.
// //
// 1. Delete snapshots specified by revision, retention policy, with a tag. Find any resulting unreferenced // 2. On next run, check if there is any new revision for each snapshot. Or if the lastest revision is too
// chunks, and mark them as fossils (by renaming). After that, create a fossil collection file containing // old, for instance, more than 7 days. This step is to identify snapshots that were being created while
// fossils collected during current run, and temporary files encountered. Also in the file is the latest // step 1 is in progress. For each fossil reference by any of these snapshots, move them back to the
// revision for each snapshot id. Save this file to a local directory. // normal chunk directory.
//
// 2. On next run, check if there is any new revision for each snapshot. Or if the lastest revision is too
// old, for instance, more than 7 days. This step is to identify snapshots that were being created while
// step 1 is in progress. For each fossil reference by any of these snapshots, move them back to the
// normal chunk directory.
// //
// Note that a snapshot being created when step 2 is in progress may reference a fossil. To avoid this // Note that a snapshot being created when step 2 is in progress may reference a fossil. To avoid this
// problem, never remove the lastest revision (unless exclusive is true), and only cache chunks referenced // problem, never remove the lastest revision (unless exclusive is true), and only cache chunks referenced
@@ -1855,7 +1853,7 @@ func (manager *SnapshotManager) PruneSnapshots(selfID string, snapshotID string,
defer func() { defer func() {
manager.chunkOperator.Stop() manager.chunkOperator.Stop()
manager.chunkOperator = nil manager.chunkOperator = nil
}() } ()
prefPath := GetDuplicacyPreferencePath() prefPath := GetDuplicacyPreferencePath()
logDir := path.Join(prefPath, "logs") logDir := path.Join(prefPath, "logs")
@@ -2429,13 +2427,7 @@ func (manager *SnapshotManager) pruneSnapshotsExhaustive(referencedFossils map[s
} }
allFiles, _ := manager.ListAllFiles(manager.storage, chunkDir) allFiles, _ := manager.ListAllFiles(manager.storage, chunkDir)
uniqueFiles := make(map[string]bool)
for _, file := range allFiles { for _, file := range allFiles {
if _, found := uniqueFiles[file]; found {
continue
}
uniqueFiles[file] = true
if len(file) == 0 || file[len(file)-1] == '/' { if len(file) == 0 || file[len(file)-1] == '/' {
continue continue
} }
@@ -2546,7 +2538,7 @@ func (manager *SnapshotManager) CheckSnapshot(snapshot *Snapshot) (err error) {
numberOfChunks, len(snapshot.ChunkLengths)) numberOfChunks, len(snapshot.ChunkLengths))
} }
snapshot.ListRemoteFiles(manager.config, manager.chunkOperator, func(entry *Entry) bool { snapshot.ListRemoteFiles(manager.config, manager.chunkOperator, func (entry *Entry) bool {
if lastEntry != nil && lastEntry.Compare(entry) >= 0 && !strings.Contains(lastEntry.Path, "\ufffd") { if lastEntry != nil && lastEntry.Compare(entry) >= 0 && !strings.Contains(lastEntry.Path, "\ufffd") {
err = fmt.Errorf("The entry %s appears before the entry %s", lastEntry.Path, entry.Path) err = fmt.Errorf("The entry %s appears before the entry %s", lastEntry.Path, entry.Path)
@@ -2570,7 +2562,7 @@ func (manager *SnapshotManager) CheckSnapshot(snapshot *Snapshot) (err error) {
} }
if entry.EndChunk < entry.StartChunk { if entry.EndChunk < entry.StartChunk {
err = fmt.Errorf("The file %s starts at chunk %d and ends at chunk %d", fmt.Errorf("The file %s starts at chunk %d and ends at chunk %d",
entry.Path, entry.StartChunk, entry.EndChunk) entry.Path, entry.StartChunk, entry.EndChunk)
return false return false
} }
@@ -2600,7 +2592,7 @@ func (manager *SnapshotManager) CheckSnapshot(snapshot *Snapshot) (err error) {
if entry.Size != fileSize { if entry.Size != fileSize {
err = fmt.Errorf("The file %s has a size of %d but the total size of chunks is %d", err = fmt.Errorf("The file %s has a size of %d but the total size of chunks is %d",
entry.Path, entry.Size, fileSize) entry.Path, entry.Size, fileSize)
return false return false
} }
return true return true
@@ -2649,7 +2641,7 @@ func (manager *SnapshotManager) DownloadFile(path string, derivationKey string)
err = manager.storage.UploadFile(0, path, newChunk.GetBytes()) err = manager.storage.UploadFile(0, path, newChunk.GetBytes())
if err != nil { if err != nil {
LOG_WARN("DOWNLOAD_REWRITE", "Failed to re-uploaded the file %s: %v", path, err) LOG_WARN("DOWNLOAD_REWRITE", "Failed to re-uploaded the file %s: %v", path, err)
} else { } else{
LOG_INFO("DOWNLOAD_REWRITE", "The file %s has been re-uploaded", path) LOG_INFO("DOWNLOAD_REWRITE", "The file %s has been re-uploaded", path)
} }
} }

View File

@@ -756,8 +756,6 @@ func CreateStorage(preference Preference, resetPassword bool, threads int) (stor
LOG_ERROR("STORAGE_CREATE", "Failed to load the Storj storage at %s: %v", storageURL, err) LOG_ERROR("STORAGE_CREATE", "Failed to load the Storj storage at %s: %v", storageURL, err)
return nil return nil
} }
SavePassword(preference, "storj_key", apiKey)
SavePassword(preference, "storj_passphrase", passphrase)
return storjStorage return storjStorage
} else if matched[1] == "smb" { } else if matched[1] == "smb" {
server := matched[3] server := matched[3]

View File

@@ -7,15 +7,14 @@ package duplicacy
import ( import (
"bufio" "bufio"
"crypto/sha256" "crypto/sha256"
"encoding/json"
"fmt" "fmt"
"io" "io"
"os" "os"
"regexp" "regexp"
"runtime"
"strconv" "strconv"
"strings" "strings"
"time" "time"
"runtime"
"github.com/gilbertchen/gopass" "github.com/gilbertchen/gopass"
"golang.org/x/crypto/pbkdf2" "golang.org/x/crypto/pbkdf2"
@@ -57,7 +56,7 @@ func IsEmptyFilter(pattern string) bool {
} }
func IsUnspecifiedFilter(pattern string) bool { func IsUnspecifiedFilter(pattern string) bool {
if pattern[0] != '+' && pattern[0] != '-' && !strings.HasPrefix(pattern, "i:") && !strings.HasPrefix(pattern, "e:") { if pattern[0] != '+' && pattern[0] != '-' && !strings.HasPrefix(pattern, "i:") && !strings.HasPrefix(pattern, "e:") {
return true return true
} else { } else {
return false return false
@@ -276,6 +275,7 @@ func SavePassword(preference Preference, passwordType string, password string) {
// The following code was modified from the online article 'Matching Wildcards: An Algorithm', by Kirk J. Krauss, // The following code was modified from the online article 'Matching Wildcards: An Algorithm', by Kirk J. Krauss,
// Dr. Dobb's, August 26, 2008. However, the version in the article doesn't handle cases like matching 'abcccd' // Dr. Dobb's, August 26, 2008. However, the version in the article doesn't handle cases like matching 'abcccd'
// against '*ccd', and the version here fixed that issue. // against '*ccd', and the version here fixed that issue.
//
func matchPattern(text string, pattern string) bool { func matchPattern(text string, pattern string) bool {
textLength := len(text) textLength := len(text)
@@ -469,39 +469,8 @@ func PrintMemoryUsage() {
runtime.ReadMemStats(&m) runtime.ReadMemStats(&m)
LOG_INFO("MEMORY_STATS", "Currently allocated: %s, total allocated: %s, system memory: %s, number of GCs: %d", LOG_INFO("MEMORY_STATS", "Currently allocated: %s, total allocated: %s, system memory: %s, number of GCs: %d",
PrettySize(int64(m.Alloc)), PrettySize(int64(m.TotalAlloc)), PrettySize(int64(m.Sys)), m.NumGC) PrettySize(int64(m.Alloc)), PrettySize(int64(m.TotalAlloc)), PrettySize(int64(m.Sys)), m.NumGC)
time.Sleep(time.Second) time.Sleep(time.Second)
} }
} }
func (entry *Entry) dump() map[string]interface{} {
object := make(map[string]interface{})
object["path"] = entry.Path
object["size"] = entry.Size
object["time"] = entry.Time
object["mode"] = entry.Mode
object["hash"] = entry.Hash
object["link"] = entry.Link
object["content"] = fmt.Sprintf("%d:%d:%d:%d",
entry.StartChunk, entry.StartOffset, entry.EndChunk, entry.EndOffset)
if entry.UID != -1 && entry.GID != -1 {
object["uid"] = entry.UID
object["gid"] = entry.GID
}
if entry.Attributes != nil && len(*entry.Attributes) > 0 {
object["attributes"] = entry.Attributes
}
return object
}
func (entry *Entry) dumpString() string {
data, _ := json.Marshal(entry.dump())
return string(data)
}

View File

@@ -5,34 +5,10 @@
package duplicacy package duplicacy
import ( import (
"encoding/binary"
"os"
"strings" "strings"
"golang.org/x/sys/unix"
) )
func excludedByAttribute(attributes map[string][]byte) bool { func excludedByAttribute(attirbutes map[string][]byte) bool {
value, ok := attributes["com.apple.metadata:com_apple_backup_excludeItem"] value, ok := attirbutes["com.apple.metadata:com_apple_backup_excludeItem"]
excluded := ok && strings.Contains(string(value), "com.apple.backupd") return ok && strings.Contains(string(value), "com.apple.backupd")
if !excluded {
flags, ok := attributes[darwinFileFlagsKey]
excluded = ok && (binary.LittleEndian.Uint32(flags)&unix.UF_NODUMP) != 0
}
return excluded
}
func (entry *Entry) RestoreSpecial(fullPath string) error {
mode := entry.Mode & uint32(fileModeMask)
if entry.Mode&uint32(os.ModeNamedPipe) != 0 {
mode |= unix.S_IFIFO
} else if entry.Mode&uint32(os.ModeCharDevice) != 0 {
mode |= unix.S_IFCHR
} else if entry.Mode&uint32(os.ModeDevice) != 0 {
mode |= unix.S_IFBLK
} else {
return nil
}
return unix.Mknod(fullPath, mode, int(entry.GetRdev()))
} }

View File

@@ -0,0 +1,13 @@
// Copyright (c) Acrosync LLC. All rights reserved.
// Free for personal use and commercial trial
// Commercial use requires per-user licenses available from https://duplicacy.com
package duplicacy
import (
)
func excludedByAttribute(attirbutes map[string][]byte) bool {
_, ok := attirbutes["duplicacy_exclude"]
return ok
}

View File

@@ -5,34 +5,9 @@
package duplicacy package duplicacy
import ( import (
"encoding/binary"
"os"
"golang.org/x/sys/unix"
) )
func excludedByAttribute(attributes map[string][]byte) bool { func excludedByAttribute(attirbutes map[string][]byte) bool {
_, excluded := attributes["user.duplicacy_exclude"] _, ok := attirbutes["duplicacy_exclude"]
if !excluded { return ok
flags, ok := attributes[linuxFileFlagsKey]
excluded = ok && (binary.LittleEndian.Uint32(flags)&linux_FS_NODUMP_FL) != 0
}
return excluded
}
func (entry *Entry) RestoreSpecial(fullPath string) error {
mode := entry.Mode & uint32(fileModeMask)
if entry.Mode&uint32(os.ModeNamedPipe) != 0 {
mode |= unix.S_IFIFO
} else if entry.Mode&uint32(os.ModeCharDevice) != 0 {
mode |= unix.S_IFCHR
} else if entry.Mode&uint32(os.ModeDevice) != 0 {
mode |= unix.S_IFBLK
} else if entry.Mode&uint32(os.ModeSocket) != 0 {
mode |= unix.S_IFSOCK
} else {
return nil
}
return unix.Mknod(fullPath, mode, int(entry.GetRdev()))
} }

View File

@@ -2,18 +2,18 @@
// Free for personal use and commercial trial // Free for personal use and commercial trial
// Commercial use requires per-user licenses available from https://duplicacy.com // Commercial use requires per-user licenses available from https://duplicacy.com
//go:build !windows
// +build !windows // +build !windows
package duplicacy package duplicacy
import ( import (
"fmt" "bytes"
"os" "os"
"path" "path"
"path/filepath"
"syscall" "syscall"
"golang.org/x/sys/unix" "github.com/pkg/xattr"
) )
func Readlink(path string) (isRegular bool, s string, err error) { func Readlink(path string) (isRegular bool, s string, err error) {
@@ -21,19 +21,24 @@ func Readlink(path string) (isRegular bool, s string, err error) {
return false, s, err return false, s, err
} }
func GetOwner(entry *Entry, fileInfo os.FileInfo) { func GetOwner(entry *Entry, fileInfo *os.FileInfo) {
stat := fileInfo.Sys().(*syscall.Stat_t) stat, ok := (*fileInfo).Sys().(*syscall.Stat_t)
entry.UID = int(stat.Uid) if ok && stat != nil {
entry.GID = int(stat.Gid) entry.UID = int(stat.Uid)
entry.GID = int(stat.Gid)
} else {
entry.UID = -1
entry.GID = -1
}
} }
func SetOwner(fullPath string, entry *Entry, fileInfo os.FileInfo) bool { func SetOwner(fullPath string, entry *Entry, fileInfo *os.FileInfo) bool {
stat := fileInfo.Sys().(*syscall.Stat_t) stat, ok := (*fileInfo).Sys().(*syscall.Stat_t)
if (int(stat.Uid) != entry.UID || int(stat.Gid) != entry.GID) { if ok && stat != nil && (int(stat.Uid) != entry.UID || int(stat.Gid) != entry.GID) {
if entry.UID != -1 && entry.GID != -1 { if entry.UID != -1 && entry.GID != -1 {
err := os.Lchown(fullPath, entry.UID, entry.GID) err := os.Lchown(fullPath, entry.UID, entry.GID)
if err != nil { if err != nil {
LOG_ERROR("RESTORE_CHOWN", "Failed to change uid or gid on %s: %v", entry.Path, err) LOG_ERROR("RESTORE_CHOWN", "Failed to change uid or gid: %v", err)
return false return false
} }
} }
@@ -42,67 +47,43 @@ func SetOwner(fullPath string, entry *Entry, fileInfo os.FileInfo) bool {
return true return true
} }
type listEntryLinkKey struct { func (entry *Entry) ReadAttributes(top string) {
dev uint64
ino uint64
}
func (entry *Entry) getHardLinkKey(f os.FileInfo) (key listEntryLinkKey, linked bool) { fullPath := filepath.Join(top, entry.Path)
if entry.IsDir() { attributes, _ := xattr.List(fullPath)
return if len(attributes) > 0 {
entry.Attributes = &map[string][]byte{}
for _, name := range attributes {
attribute, err := xattr.Get(fullPath, name)
if err == nil {
(*entry.Attributes)[name] = attribute
}
}
} }
stat := f.Sys().(*syscall.Stat_t)
if stat.Nlink <= 1 {
return
}
key.dev = uint64(stat.Dev)
key.ino = uint64(stat.Ino)
linked = true
return
} }
func (entry *Entry) ReadSpecial(fullPath string, fileInfo os.FileInfo) error { func (entry *Entry) SetAttributesToFile(fullPath string) {
if fileInfo.Mode()&(os.ModeDevice|os.ModeCharDevice) == 0 { names, _ := xattr.List(fullPath)
return nil
}
rdev := uint64(fileInfo.Sys().(*syscall.Stat_t).Rdev)
entry.Size = 0
entry.StartChunk = int(rdev & 0xFFFFFFFF)
entry.StartOffset = int(rdev >> 32)
return nil
}
func (entry *Entry) GetRdev() uint64 { for _, name := range names {
return uint64(entry.StartChunk) | uint64(entry.StartOffset)<<32
}
func (entry *Entry) IsSameSpecial(fileInfo os.FileInfo) bool {
stat := fileInfo.Sys().(*syscall.Stat_t)
return (uint32(fileInfo.Mode()) == entry.Mode) && (uint64(stat.Rdev) == entry.GetRdev())
}
func (entry *Entry) FmtSpecial() string { newAttribute, found := (*entry.Attributes)[name]
var c string if found {
mode := entry.Mode & uint32(os.ModeType) oldAttribute, _ := xattr.Get(fullPath, name)
if !bytes.Equal(oldAttribute, newAttribute) {
if mode&uint32(os.ModeNamedPipe) != 0 { xattr.Set(fullPath, name, newAttribute)
c = "p" }
} else if mode&uint32(os.ModeCharDevice) != 0 { delete(*entry.Attributes, name)
c = "c" } else {
} else if mode&uint32(os.ModeDevice) != 0 { xattr.Remove(fullPath, name)
c = "b" }
} else if mode&uint32(os.ModeSocket) != 0 {
c = "s"
} else {
return ""
} }
rdev := entry.GetRdev() for name, attribute := range *entry.Attributes {
return fmt.Sprintf("%s (%d, %d)", c, unix.Major(rdev), unix.Minor(rdev)) xattr.Set(fullPath, name, attribute)
} }
func MakeHardlink(source string, target string) error {
return unix.Linkat(unix.AT_FDCWD, source, unix.AT_FDCWD, target, 0)
} }
func joinPath(components ...string) string { func joinPath(components ...string) string {

View File

@@ -56,11 +56,7 @@ const (
// Readlink returns the destination of the named symbolic link. // Readlink returns the destination of the named symbolic link.
func Readlink(path string) (isRegular bool, s string, err error) { func Readlink(path string) (isRegular bool, s string, err error) {
pPath, err := syscall.UTF16PtrFromString(path) fd, err := syscall.CreateFile(syscall.StringToUTF16Ptr(path), FILE_READ_ATTRIBUTES,
if err != nil {
return false, "", err
}
fd, err := syscall.CreateFile(pPath, FILE_READ_ATTRIBUTES,
syscall.FILE_SHARE_READ, nil, syscall.OPEN_EXISTING, syscall.FILE_SHARE_READ, nil, syscall.OPEN_EXISTING,
syscall.FILE_FLAG_OPEN_REPARSE_POINT|syscall.FILE_FLAG_BACKUP_SEMANTICS, 0) syscall.FILE_FLAG_OPEN_REPARSE_POINT|syscall.FILE_FLAG_BACKUP_SEMANTICS, 0)
if err != nil { if err != nil {
@@ -105,17 +101,20 @@ func Readlink(path string) (isRegular bool, s string, err error) {
return false, s, nil return false, s, nil
} }
func GetOwner(entry *Entry, fileInfo os.FileInfo) { func GetOwner(entry *Entry, fileInfo *os.FileInfo) {
entry.UID = -1 entry.UID = -1
entry.GID = -1 entry.GID = -1
} }
func SetOwner(fullPath string, entry *Entry, fileInfo os.FileInfo) bool { func SetOwner(fullPath string, entry *Entry, fileInfo *os.FileInfo) bool {
return true return true
} }
func MakeHardlink(source string, target string) error { func (entry *Entry) ReadAttributes(top string) {
return os.Link(source, target) }
func (entry *Entry) SetAttributesToFile(fullPath string) {
} }
func joinPath(components ...string) string { func joinPath(components ...string) string {
@@ -133,28 +132,6 @@ func SplitDir(fullPath string) (dir string, file string) {
return fullPath[:i+1], fullPath[i+1:] return fullPath[:i+1], fullPath[i+1:]
} }
func excludedByAttribute(attributes map[string][]byte) bool { func excludedByAttribute(attirbutes map[string][]byte) bool {
return false return false
} }
type listEntryLinkKey struct{}
func (entry *Entry) getHardLinkKey(f os.FileInfo) (key listEntryLinkKey, linked bool) {
return
}
func (entry *Entry) ReadSpecial(fullPath string, fileInfo os.FileInfo) error {
return nil
}
func (entry *Entry) IsSameSpecial(fileInfo os.FileInfo) bool {
return false
}
func (entry *Entry) RestoreSpecial(fullPath string) error {
return nil
}
func (entry *Entry) FmtSpecial() string {
return ""
}

View File

@@ -1,38 +0,0 @@
// Copyright (c) Acrosync LLC. All rights reserved.
// Free for personal use and commercial trial
// Commercial use requires per-user licenses available from https://duplicacy.com
//go:build freebsd
// +build freebsd
package duplicacy
import (
"encoding/binary"
"os"
"syscall"
)
func excludedByAttribute(attributes map[string][]byte) bool {
_, excluded := attributes["duplicacy_exclude"]
if !excluded {
flags, ok := attributes[bsdFileFlagsKey]
excluded = ok && (binary.LittleEndian.Uint32(flags)&bsd_UF_NODUMP) != 0
}
return excluded
}
func (entry *Entry) RestoreSpecial(fullPath string) error {
mode := entry.Mode & uint32(fileModeMask)
if entry.Mode&uint32(os.ModeNamedPipe) != 0 {
mode |= syscall.S_IFIFO
} else if entry.Mode&uint32(os.ModeCharDevice) != 0 {
mode |= syscall.S_IFCHR
} else if entry.Mode&uint32(os.ModeDevice) != 0 {
mode |= syscall.S_IFBLK
} else {
return nil
}
return syscall.Mknod(fullPath, mode, entry.GetRdev())
}

View File

@@ -1,35 +0,0 @@
// Copyright (c) Acrosync LLC. All rights reserved.
// Free for personal use and commercial trial
// Commercial use requires per-user licenses available from https://duplicacy.com
package duplicacy
import "os"
func (entry *Entry) ReadAttributes(fi os.FileInfo, fullPath string, normalize bool) error {
return entry.readAttributes(fi, fullPath, normalize)
}
func (entry *Entry) GetFileFlags(fileInfo os.FileInfo) bool {
return entry.getFileFlags(fileInfo)
}
func (entry *Entry) ReadFileFlags(fileInfo os.FileInfo, fullPath string) error {
return entry.readFileFlags(fileInfo, fullPath)
}
func (entry *Entry) RestoreEarlyDirFlags(fullPath string, mask uint32) error {
return entry.restoreEarlyDirFlags(fullPath, mask)
}
func (entry *Entry) RestoreEarlyFileFlags(f *os.File, mask uint32) error {
return entry.restoreEarlyFileFlags(f, mask)
}
func (entry *Entry) RestoreLateFileFlags(fullPath string, fileInfo os.FileInfo, mask uint32) error {
return entry.restoreLateFileFlags(fullPath, fileInfo, mask)
}
func (entry *Entry) SetAttributesToFile(fullPath string, normalize bool) error {
return entry.setAttributesToFile(fullPath, normalize)
}

View File

@@ -1,149 +0,0 @@
// Copyright (c) Acrosync LLC. All rights reserved.
// Free for personal use and commercial trial
// Commercial use requires per-user licenses available from https://duplicacy.com
package duplicacy
import (
"bytes"
"encoding/binary"
"errors"
"math"
"os"
"syscall"
"github.com/pkg/xattr"
"golang.org/x/sys/unix"
)
const (
darwinFileFlagsKey = "\x00bf"
)
var darwinIsSuperUser bool
func init() {
darwinIsSuperUser = unix.Geteuid() == 0
}
func (entry *Entry) readAttributes(fi os.FileInfo, fullPath string, normalize bool) error {
if entry.IsSpecial() {
return nil
}
attributes, err := xattr.LList(fullPath)
if err != nil {
return err
}
if len(attributes) > 0 {
entry.Attributes = &map[string][]byte{}
}
var allErrors error
for _, name := range attributes {
value, err := xattr.LGet(fullPath, name)
if err != nil {
allErrors = errors.Join(allErrors, err)
} else {
(*entry.Attributes)[name] = value
}
}
return allErrors
}
func (entry *Entry) getFileFlags(fileInfo os.FileInfo) bool {
stat := fileInfo.Sys().(*syscall.Stat_t)
if stat.Flags != 0 {
if entry.Attributes == nil {
entry.Attributes = &map[string][]byte{}
}
v := make([]byte, 4)
binary.LittleEndian.PutUint32(v, stat.Flags)
(*entry.Attributes)[darwinFileFlagsKey] = v
}
return true
}
func (entry *Entry) readFileFlags(fileInfo os.FileInfo, fullPath string) error {
return nil
}
func (entry *Entry) setAttributesToFile(fullPath string, normalize bool) error {
if entry.Attributes == nil || len(*entry.Attributes) == 0 || entry.IsSpecial() {
return nil
}
attributes := *entry.Attributes
if _, haveFlags := attributes[darwinFileFlagsKey]; haveFlags && len(attributes) <= 1 {
return nil
}
names, err := xattr.LList(fullPath)
if err != nil {
return err
}
for _, name := range names {
newAttribute, found := attributes[name]
if found {
oldAttribute, _ := xattr.LGet(fullPath, name)
if !bytes.Equal(oldAttribute, newAttribute) {
err = errors.Join(err, xattr.LSet(fullPath, name, newAttribute))
}
delete(attributes, name)
} else {
err = errors.Join(err, xattr.LRemove(fullPath, name))
}
}
for name, attribute := range attributes {
if len(name) > 0 && name[0] == '\x00' {
continue
}
err = errors.Join(err, xattr.LSet(fullPath, name, attribute))
}
return err
}
func (entry *Entry) restoreEarlyDirFlags(fullPath string, mask uint32) error {
return nil
}
func (entry *Entry) restoreEarlyFileFlags(f *os.File, mask uint32) error {
return nil
}
func (entry *Entry) restoreLateFileFlags(fullPath string, fileInfo os.FileInfo, mask uint32) error {
if mask == math.MaxUint32 {
return nil
}
if darwinIsSuperUser {
mask |= ^uint32(unix.UF_SETTABLE | unix.SF_SETTABLE)
} else {
mask |= ^uint32(unix.UF_SETTABLE)
}
var flags uint32
if entry.Attributes != nil {
if v, have := (*entry.Attributes)[darwinFileFlagsKey]; have {
flags = binary.LittleEndian.Uint32(v)
}
}
stat := fileInfo.Sys().(*syscall.Stat_t)
flags = (flags & ^mask) | (stat.Flags & mask)
if flags != stat.Flags {
f, err := os.OpenFile(fullPath, os.O_RDONLY|unix.O_SYMLINK, 0)
if err != nil {
return err
}
err = unix.Fchflags(int(f.Fd()), int(flags))
f.Close()
return err
}
return nil
}

View File

@@ -1,234 +0,0 @@
// Copyright (c) Acrosync LLC. All rights reserved.
// Free for personal use and commercial trial
// Commercial use requires per-user licenses available from https://duplicacy.com
package duplicacy
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"math"
"os"
"unsafe"
"github.com/pkg/xattr"
"golang.org/x/sys/unix"
)
const (
linux_FS_SECRM_FL = 0x00000001 /* Secure deletion */
linux_FS_UNRM_FL = 0x00000002 /* Undelete */
linux_FS_COMPR_FL = 0x00000004 /* Compress file */
linux_FS_SYNC_FL = 0x00000008 /* Synchronous updates */
linux_FS_IMMUTABLE_FL = 0x00000010 /* Immutable file */
linux_FS_APPEND_FL = 0x00000020 /* writes to file may only append */
linux_FS_NODUMP_FL = 0x00000040 /* do not dump file */
linux_FS_NOATIME_FL = 0x00000080 /* do not update atime */
linux_FS_NOCOMP_FL = 0x00000400 /* Don't compress */
linux_FS_JOURNAL_DATA_FL = 0x00004000 /* Reserved for ext3 */
linux_FS_NOTAIL_FL = 0x00008000 /* file tail should not be merged */
linux_FS_DIRSYNC_FL = 0x00010000 /* dirsync behaviour (directories only) */
linux_FS_TOPDIR_FL = 0x00020000 /* Top of directory hierarchies*/
linux_FS_NOCOW_FL = 0x00800000 /* Do not cow file */
linux_FS_PROJINHERIT_FL = 0x20000000 /* Create with parents projid */
linuxIocFlagsFileEarly = linux_FS_SECRM_FL | linux_FS_UNRM_FL | linux_FS_COMPR_FL | linux_FS_NODUMP_FL | linux_FS_NOATIME_FL | linux_FS_NOCOMP_FL | linux_FS_JOURNAL_DATA_FL | linux_FS_NOTAIL_FL | linux_FS_NOCOW_FL
linuxIocFlagsDirEarly = linux_FS_TOPDIR_FL | linux_FS_PROJINHERIT_FL
linuxIocFlagsLate = linux_FS_SYNC_FL | linux_FS_IMMUTABLE_FL | linux_FS_APPEND_FL | linux_FS_DIRSYNC_FL
linuxFileFlagsKey = "\x00lf"
)
var (
errENOTTY error = unix.ENOTTY
)
func ignoringEINTR(fn func() error) (err error) {
for {
err = fn()
if err != unix.EINTR {
break
}
}
return err
}
func ioctl(f *os.File, request uintptr, attrp *uint32) error {
return ignoringEINTR(func() error {
argp := uintptr(unsafe.Pointer(attrp))
_, _, errno := unix.Syscall(unix.SYS_IOCTL, f.Fd(), request, argp)
if errno == 0 {
return nil
} else if errno == unix.ENOTTY {
return errENOTTY
}
return errno
})
}
func (entry *Entry) readAttributes(fi os.FileInfo, fullPath string, normalize bool) error {
attributes, err := xattr.LList(fullPath)
if err != nil {
return err
}
if len(attributes) > 0 {
entry.Attributes = &map[string][]byte{}
}
var allErrors error
for _, name := range attributes {
value, err := xattr.LGet(fullPath, name)
if err != nil {
allErrors = errors.Join(allErrors, err)
} else {
(*entry.Attributes)[name] = value
}
}
return allErrors
}
func (entry *Entry) getFileFlags(fileInfo os.FileInfo) bool {
return false
}
func (entry *Entry) readFileFlags(fileInfo os.FileInfo, fullPath string) error {
// the linux file flags interface is quite depressing. The half assed attempt at statx
// doesn't even cover the flags we're usually interested in for btrfs
if !(entry.IsFile() || entry.IsDir()) {
return nil
}
f, err := os.OpenFile(fullPath, os.O_RDONLY|unix.O_NONBLOCK|unix.O_NOFOLLOW|unix.O_NOATIME, 0)
if err != nil {
return err
}
var flags uint32
err = ioctl(f, unix.FS_IOC_GETFLAGS, &flags)
f.Close()
if err != nil {
// inappropriate ioctl for device means flags aren't a thing on that FS
if err == unix.ENOTTY {
return nil
}
return err
}
if flags != 0 {
if entry.Attributes == nil {
entry.Attributes = &map[string][]byte{}
}
v := make([]byte, 4)
binary.LittleEndian.PutUint32(v, flags)
(*entry.Attributes)[linuxFileFlagsKey] = v
}
return nil
}
func (entry *Entry) setAttributesToFile(fullPath string, normalize bool) error {
if entry.Attributes == nil || len(*entry.Attributes) == 0 {
return nil
}
attributes := *entry.Attributes
if _, haveFlags := attributes[linuxFileFlagsKey]; haveFlags && len(attributes) <= 1 {
return nil
}
names, err := xattr.LList(fullPath)
if err != nil {
return err
}
for _, name := range names {
newAttribute, found := (*entry.Attributes)[name]
if found {
oldAttribute, _ := xattr.LGet(fullPath, name)
if !bytes.Equal(oldAttribute, newAttribute) {
err = errors.Join(err, xattr.LSet(fullPath, name, newAttribute))
}
delete(*entry.Attributes, name)
} else {
err = errors.Join(err, xattr.LRemove(fullPath, name))
}
}
for name, attribute := range *entry.Attributes {
if len(name) > 0 && name[0] == '\x00' {
continue
}
err = errors.Join(err, xattr.LSet(fullPath, name, attribute))
}
return err
}
func (entry *Entry) restoreEarlyDirFlags(fullPath string, mask uint32) error {
if entry.Attributes == nil || mask == math.MaxUint32 {
return nil
}
var flags uint32
if v, have := (*entry.Attributes)[linuxFileFlagsKey]; have {
flags = binary.LittleEndian.Uint32(v) & linuxIocFlagsDirEarly & ^mask
}
if flags != 0 {
f, err := os.OpenFile(fullPath, os.O_RDONLY|unix.O_DIRECTORY, 0)
if err != nil {
return err
}
err = ioctl(f, unix.FS_IOC_SETFLAGS, &flags)
f.Close()
if err != nil {
return fmt.Errorf("Set flags 0x%.8x failed: %w", flags, err)
}
}
return nil
}
func (entry *Entry) restoreEarlyFileFlags(f *os.File, mask uint32) error {
if entry.Attributes == nil || mask == math.MaxUint32 {
return nil
}
var flags uint32
if v, have := (*entry.Attributes)[linuxFileFlagsKey]; have {
flags = binary.LittleEndian.Uint32(v) & linuxIocFlagsFileEarly & ^mask
}
if flags != 0 {
err := ioctl(f, unix.FS_IOC_SETFLAGS, &flags)
if err != nil {
return fmt.Errorf("Set flags 0x%.8x failed: %w", flags, err)
}
}
return nil
}
func (entry *Entry) restoreLateFileFlags(fullPath string, fileInfo os.FileInfo, mask uint32) error {
if entry.IsLink() || entry.Attributes == nil || mask == math.MaxUint32 {
return nil
}
var flags uint32
if v, have := (*entry.Attributes)[linuxFileFlagsKey]; have {
flags = binary.LittleEndian.Uint32(v) & (linuxIocFlagsFileEarly | linuxIocFlagsDirEarly | linuxIocFlagsLate) & ^mask
}
if flags != 0 {
f, err := os.OpenFile(fullPath, os.O_RDONLY|unix.O_NOFOLLOW, 0)
if err != nil {
return err
}
err = ioctl(f, unix.FS_IOC_SETFLAGS, &flags)
f.Close()
if err != nil {
return fmt.Errorf("Set flags 0x%.8x failed: %w", flags, err)
}
}
return nil
}

View File

@@ -1,35 +0,0 @@
// Copyright (c) Acrosync LLC. All rights reserved.
// Free for personal use and commercial trial
// Commercial use requires per-user licenses available from https://duplicacy.com
package duplicacy
import "os"
func (entry *Entry) readAttributes(fi os.FileInfo, fullPath string, normalize bool) error {
return nil
}
func (entry *Entry) getFileFlags(fileInfo os.FileInfo) bool {
return true
}
func (entry *Entry) readFileFlags(fileInfo os.FileInfo, fullPath string) error {
return nil
}
func (entry *Entry) setAttributesToFile(fullPath string, normalize bool) error {
return nil
}
func (entry *Entry) restoreEarlyDirFlags(fullPath string, mask uint32) error {
return nil
}
func (entry *Entry) restoreEarlyFileFlags(f *os.File, mask uint32) error {
return nil
}
func (entry *Entry) restoreLateFileFlags(fullPath string, fileInfo os.FileInfo, mask uint32) error {
return nil
}

View File

@@ -1,155 +0,0 @@
// Copyright (c) Acrosync LLC. All rights reserved.
// Free for personal use and commercial trial
// Commercial use requires per-user licenses available from https://duplicacy.com
//go:build freebsd || netbsd
// +build freebsd netbsd
package duplicacy
import (
"bytes"
"encoding/binary"
"errors"
"math"
"os"
"syscall"
"unsafe"
"github.com/pkg/xattr"
)
const (
bsd_UF_NODUMP = 0x1
bsd_SF_SETTABLE = 0xffff0000
bsd_UF_SETTABLE = 0x0000ffff
bsdFileFlagsKey = "\x00bf"
)
var bsdIsSuperUser bool
func init() {
bsdIsSuperUser = syscall.Geteuid() == 0
}
func (entry *Entry) readAttributes(fi os.FileInfo, fullPath string, normalize bool) error {
if entry.IsSpecial() {
return nil
}
attributes, err := xattr.LList(fullPath)
if err != nil {
return err
}
if len(attributes) > 0 {
entry.Attributes = &map[string][]byte{}
}
var allErrors error
for _, name := range attributes {
value, err := xattr.LGet(fullPath, name)
if err != nil {
allErrors = errors.Join(allErrors, err)
} else {
(*entry.Attributes)[name] = value
}
}
return allErrors
}
func (entry *Entry) getFileFlags(fileInfo os.FileInfo) bool {
stat := fileInfo.Sys().(*syscall.Stat_t)
if stat.Flags != 0 {
if entry.Attributes == nil {
entry.Attributes = &map[string][]byte{}
}
v := make([]byte, 4)
binary.LittleEndian.PutUint32(v, stat.Flags)
(*entry.Attributes)[bsdFileFlagsKey] = v
}
return true
}
func (entry *Entry) readFileFlags(fileInfo os.FileInfo, fullPath string) error {
return nil
}
func (entry *Entry) setAttributesToFile(fullPath string, normalize bool) error {
if entry.Attributes == nil || len(*entry.Attributes) == 0 || entry.IsSpecial() {
return nil
}
attributes := *entry.Attributes
if _, haveFlags := attributes[bsdFileFlagsKey]; haveFlags && len(attributes) <= 1 {
return nil
}
names, err := xattr.LList(fullPath)
if err != nil {
return err
}
for _, name := range names {
newAttribute, found := attributes[name]
if found {
oldAttribute, _ := xattr.LGet(fullPath, name)
if !bytes.Equal(oldAttribute, newAttribute) {
err = errors.Join(err, xattr.LSet(fullPath, name, newAttribute))
}
delete(attributes, name)
} else {
err = errors.Join(err, xattr.LRemove(fullPath, name))
}
}
for name, attribute := range attributes {
if len(name) > 0 && name[0] == '\x00' {
continue
}
err = errors.Join(err, xattr.LSet(fullPath, name, attribute))
}
return err
}
func (entry *Entry) restoreEarlyDirFlags(fullPath string, mask uint32) error {
return nil
}
func (entry *Entry) restoreEarlyFileFlags(f *os.File, mask uint32) error {
return nil
}
func (entry *Entry) restoreLateFileFlags(fullPath string, fileInfo os.FileInfo, mask uint32) error {
if mask == math.MaxUint32 {
return nil
}
if bsdIsSuperUser {
mask |= ^uint32(bsd_UF_SETTABLE | bsd_SF_SETTABLE)
} else {
mask |= ^uint32(bsd_UF_SETTABLE)
}
var flags uint32
if entry.Attributes != nil {
if v, have := (*entry.Attributes)[bsdFileFlagsKey]; have {
flags = binary.LittleEndian.Uint32(v)
}
}
stat := fileInfo.Sys().(*syscall.Stat_t)
flags = (flags & ^mask) | (stat.Flags & mask)
if flags != stat.Flags {
pPath, _ := syscall.BytePtrFromString(fullPath)
if _, _, errno := syscall.Syscall(syscall.SYS_LCHFLAGS,
uintptr(unsafe.Pointer(pPath)),
uintptr(flags), 0); errno != 0 {
return os.NewSyscallError("lchflags", errno)
}
}
return nil
}