Переглянути джерело

Upgraded linter to v1.33.0 (#734)

* linter: upgraded to 1.33, disabled some linters

* lint: fixed 'errorlint' errors

This ensures that all error comparisons use errors.Is() or errors.As().
We will be wrapping more errors going forward so it's important that
error checks are not strict everywhere.

Verified that there are no exceptions for errorlint linter which
guarantees that.

* lint: fixed or suppressed wrapcheck errors

* lint: nolintlint and misc cleanups

Co-authored-by: Julio López <julio+gh@kasten.io>
Jarek Kowalski 4 роки тому
батько
коміт
e03971fc59
100 змінених файлів з 394 додано та 271 видалено
  1. 9 10
      .golangci.yml
  2. 4 2
      cli/app.go
  3. 3 1
      cli/command_benchmark_compression.go
  4. 3 1
      cli/command_benchmark_splitters.go
  5. 4 2
      cli/command_blob_gc.go
  6. 4 2
      cli/command_blob_show.go
  7. 3 1
      cli/command_blob_stats.go
  8. 2 2
      cli/command_cache_clear.go
  9. 3 1
      cli/command_content_rm.go
  10. 3 1
      cli/command_content_show.go
  11. 3 1
      cli/command_content_stats.go
  12. 3 3
      cli/command_diff.go
  13. 3 1
      cli/command_index_list.go
  14. 5 3
      cli/command_ls.go
  15. 1 1
      cli/command_manifest_ls.go
  16. 3 1
      cli/command_manifest_rm.go
  17. 2 2
      cli/command_mount.go
  18. 1 1
      cli/command_policy.go
  19. 1 1
      cli/command_policy_edit.go
  20. 3 1
      cli/command_policy_ls.go
  21. 3 1
      cli/command_policy_remove.go
  22. 1 1
      cli/command_policy_set_actions.go
  23. 1 1
      cli/command_repository_connect.go
  24. 1 1
      cli/command_repository_connect_server.go
  25. 4 2
      cli/command_repository_create.go
  26. 8 6
      cli/command_repository_repair.go
  27. 1 1
      cli/command_repository_status.go
  28. 2 2
      cli/command_repository_sync.go
  29. 2 2
      cli/command_restore.go
  30. 1 1
      cli/command_server_start.go
  31. 3 1
      cli/command_server_status.go
  32. 3 1
      cli/command_server_upload.go
  33. 5 3
      cli/command_show.go
  34. 2 2
      cli/command_snapshot_create.go
  35. 1 1
      cli/command_snapshot_delete.go
  36. 2 2
      cli/command_snapshot_estimate.go
  37. 4 2
      cli/command_snapshot_expire.go
  38. 3 1
      cli/command_snapshot_gc.go
  39. 3 3
      cli/command_snapshot_list.go
  40. 3 3
      cli/command_snapshot_migrate.go
  41. 6 6
      cli/command_snapshot_verify.go
  42. 1 1
      cli/config.go
  43. 1 1
      cli/password.go
  44. 3 3
      cli/show_utils.go
  45. 2 1
      cli/storage_gcs.go
  46. 2 1
      cli/storage_rclone.go
  47. 2 2
      cli/storage_sftp.go
  48. 2 1
      fs/cachefs/cache_test.go
  49. 3 0
      fs/cachefs/cachefs.go
  50. 4 2
      fs/entry.go
  51. 3 2
      fs/ignorefs/ignorefs.go
  52. 5 5
      fs/localfs/local_fs.go
  53. 4 2
      fs/localfs/local_fs_test.go
  54. 2 0
      fs/loggingfs/loggingfs.go
  55. 1 0
      go.sum
  56. 4 4
      internal/apiclient/apiclient.go
  57. 9 18
      internal/blobtesting/concurrent.go
  58. 5 5
      internal/diff/diff.go
  59. 6 6
      internal/editor/editor.go
  60. 6 0
      internal/fusemount/fusefs.go
  61. 1 0
      internal/gather/gather_bytes.go
  62. 1 1
      internal/mount/mount_fuse.go
  63. 1 1
      internal/mount/mount_net_use.go
  64. 1 1
      internal/retry/retry_test.go
  65. 4 3
      internal/server/api_content.go
  66. 4 4
      internal/server/api_repo.go
  67. 4 3
      internal/server/api_sources.go
  68. 10 8
      internal/serverapi/client_wrappers.go
  69. 8 1
      internal/throttle/round_tripper.go
  70. 2 2
      internal/tlsutil/tlsutil.go
  71. 3 3
      internal/webdavmount/webdavmount.go
  72. 11 11
      repo/api_server_repository.go
  73. 12 7
      repo/blob/azure/azure_storage.go
  74. 3 1
      repo/blob/azure/azure_storage_test.go
  75. 13 7
      repo/blob/b2/b2_storage.go
  76. 1 1
      repo/blob/config.go
  77. 16 2
      repo/blob/filesystem/filesystem_storage.go
  78. 15 14
      repo/blob/gcs/gcs_storage.go
  79. 7 0
      repo/blob/logging/logging_storage.go
  80. 3 0
      repo/blob/readonly/readonly_storage.go
  81. 13 8
      repo/blob/s3/s3_storage.go
  82. 11 11
      repo/blob/sftp/sftp_storage.go
  83. 4 2
      repo/blob/sharded/sharded.go
  84. 1 1
      repo/blob/storage.go
  85. 16 10
      repo/blob/webdav/webdav_storage.go
  86. 3 2
      repo/connect.go
  87. 1 1
      repo/content/committed_content_index.go
  88. 2 2
      repo/content/committed_content_index_disk_cache.go
  89. 4 2
      repo/content/content_cache.go
  90. 2 1
      repo/content/content_cache_base.go
  91. 2 1
      repo/content/content_cache_data.go
  92. 3 1
      repo/content/content_cache_metadata.go
  93. 4 4
      repo/content/content_cache_test.go
  94. 3 3
      repo/content/content_index_recovery.go
  95. 4 4
      repo/content/content_manager.go
  96. 1 1
      repo/content/content_manager_indexes.go
  97. 6 5
      repo/content/content_manager_lock_free.go
  98. 1 1
      repo/content/content_manager_own_writes.go
  99. 5 5
      repo/content/content_manager_test.go
  100. 1 1
      repo/content/index.go

+ 9 - 10
.golangci.yml

@@ -44,6 +44,9 @@ linters:
     - whitespace
     - nlreturn
     - testpackage
+    - exhaustivestruct
+    - paralleltest
+    - tparallel
 
 run:
   skip-dirs:
@@ -52,7 +55,7 @@ run:
 issues:
   exclude-use-default: false
   exclude-rules:
-    - path: _test\.go|testing|test_env
+    - path: _test\.go|testing|tests|test_env|fshasher
       linters:
       - gomnd
       - gocognit
@@ -60,14 +63,16 @@ issues:
       - errcheck
       - gosec
       - nestif
-      - goerr113
+      - wrapcheck
     - text: "Magic number: 1e"
       linters:
       - gomnd
     - text: "unnecessaryDefer"
-      linters: gocritic
+      linters:
+      - gocritic
     - text: "filepathJoin"
-      linters: gocritic
+      linters:
+      - gocritic
     - text: "weak cryptographic primitive"
       linters:
         - gosec
@@ -86,9 +91,3 @@ issues:
     - path: cli
       linters:
       - gochecknoglobals
-
-# golangci.com configuration
-# https://github.com/golangci/golangci/wiki/Configuration
-service:
-  golangci-lint-version: 1.24.x # use the fixed version to not introduce new linters unexpectedly
-

+ 4 - 2
cli/app.go

@@ -176,12 +176,14 @@ func maybeRunMaintenance(ctx context.Context, rep repo.Repository) error {
 		return nil
 	}
 
-	if _, ok := err.(maintenance.NotOwnedError); ok {
+	var noe maintenance.NotOwnedError
+
+	if errors.As(err, &noe) {
 		// do not report the NotOwnedError to the user since this is automatic maintenance.
 		return nil
 	}
 
-	return err
+	return errors.Wrap(err, "error running maintenance")
 }
 
 func advancedCommand(ctx context.Context) {

+ 3 - 1
cli/command_benchmark_compression.go

@@ -7,6 +7,8 @@ import (
 	"io/ioutil"
 	"sort"
 
+	"github.com/pkg/errors"
+
 	"github.com/kopia/kopia/internal/clock"
 	"github.com/kopia/kopia/internal/units"
 	"github.com/kopia/kopia/repo"
@@ -36,7 +38,7 @@ func runBenchmarkCompressionAction(ctx context.Context, rep repo.Repository) err
 	if *benchmarkCompressionDataFile != "" {
 		d, err := ioutil.ReadFile(*benchmarkCompressionDataFile)
 		if err != nil {
-			return err
+			return errors.Wrap(err, "error reading compression data file")
 		}
 
 		data = d

+ 3 - 1
cli/command_benchmark_splitters.go

@@ -6,6 +6,8 @@ import (
 	"sort"
 	"time"
 
+	"github.com/pkg/errors"
+
 	"github.com/kopia/kopia/internal/clock"
 	"github.com/kopia/kopia/repo"
 	"github.com/kopia/kopia/repo/splitter"
@@ -42,7 +44,7 @@ func runBenchmarkSplitterAction(ctx context.Context, rep repo.Repository) error
 	for i := 0; i < *benchmarkSplitterBlockCount; i++ {
 		b := make([]byte, *benchmarkSplitterBlockSize)
 		if _, err := rnd.Read(b); err != nil {
-			return err
+			return errors.Wrap(err, "error generating random data")
 		}
 
 		dataBlocks = append(dataBlocks, b)

+ 4 - 2
cli/command_blob_gc.go

@@ -3,6 +3,8 @@ package cli
 import (
 	"context"
 
+	"github.com/pkg/errors"
+
 	"github.com/kopia/kopia/repo"
 	"github.com/kopia/kopia/repo/blob"
 	"github.com/kopia/kopia/repo/maintenance"
@@ -28,14 +30,14 @@ func runBlobGarbageCollectCommand(ctx context.Context, rep *repo.DirectRepositor
 
 	n, err := maintenance.DeleteUnreferencedBlobs(ctx, rep, opts)
 	if err != nil {
-		return err
+		return errors.Wrap(err, "error deleting unreferenced blobs")
 	}
 
 	if opts.DryRun && n > 0 {
 		log(ctx).Infof("Pass --delete=yes to delete.")
 	}
 
-	return err
+	return nil
 }
 
 func init() {

+ 4 - 2
cli/command_blob_show.go

@@ -56,9 +56,11 @@ func maybeDecryptBlob(ctx context.Context, w io.Writer, rep *repo.DirectReposito
 		return errors.Wrapf(err, "error getting %v", blobID)
 	}
 
-	_, err = iocopy.Copy(w, bytes.NewReader(d))
+	if _, err := iocopy.Copy(w, bytes.NewReader(d)); err != nil {
+		return errors.Wrap(err, "error copying data")
+	}
 
-	return err
+	return nil
 }
 
 func canDecryptBlob(b blob.ID) bool {

+ 3 - 1
cli/command_blob_stats.go

@@ -5,6 +5,8 @@ import (
 	"fmt"
 	"strconv"
 
+	"github.com/pkg/errors"
+
 	"github.com/kopia/kopia/internal/units"
 	"github.com/kopia/kopia/repo"
 	"github.com/kopia/kopia/repo/blob"
@@ -49,7 +51,7 @@ func runBlobStatsCommand(ctx context.Context, rep *repo.DirectRepository) error
 			}
 			return nil
 		}); err != nil {
-		return err
+		return errors.Wrap(err, "error listing blobs")
 	}
 
 	sizeToString := units.BytesStringBase10

+ 2 - 2
cli/command_cache_clear.go

@@ -25,11 +25,11 @@ func runCacheClearCommand(ctx context.Context, rep *repo.DirectRepository) error
 			return os.RemoveAll(d)
 		}, retry.Always)
 		if err != nil {
-			return err
+			return errors.Wrap(err, "error removing cache directory")
 		}
 
 		if err := os.MkdirAll(d, 0o700); err != nil {
-			return err
+			return errors.Wrap(err, "error creating cache directory")
 		}
 
 		log(ctx).Infof("Cache cleared.")

+ 3 - 1
cli/command_content_rm.go

@@ -3,6 +3,8 @@ package cli
 import (
 	"context"
 
+	"github.com/pkg/errors"
+
 	"github.com/kopia/kopia/repo"
 )
 
@@ -17,7 +19,7 @@ func runContentRemoveCommand(ctx context.Context, rep *repo.DirectRepository) er
 
 	for _, contentID := range toContentIDs(*contentRemoveIDs) {
 		if err := rep.Content.DeleteContent(ctx, contentID); err != nil {
-			return err
+			return errors.Wrapf(err, "error deleting content %v", contentID)
 		}
 	}
 

+ 3 - 1
cli/command_content_show.go

@@ -4,6 +4,8 @@ import (
 	"bytes"
 	"context"
 
+	"github.com/pkg/errors"
+
 	"github.com/kopia/kopia/repo"
 	"github.com/kopia/kopia/repo/content"
 )
@@ -27,7 +29,7 @@ func runContentShowCommand(ctx context.Context, rep *repo.DirectRepository) erro
 func contentShow(ctx context.Context, r *repo.DirectRepository, contentID content.ID) error {
 	data, err := r.Content.GetContent(ctx, contentID)
 	if err != nil {
-		return err
+		return errors.Wrapf(err, "error getting content %v", contentID)
 	}
 
 	return showContent(bytes.NewReader(data))

+ 3 - 1
cli/command_content_stats.go

@@ -5,6 +5,8 @@ import (
 	"fmt"
 	"strconv"
 
+	"github.com/pkg/errors"
+
 	"github.com/kopia/kopia/internal/units"
 	"github.com/kopia/kopia/repo"
 	"github.com/kopia/kopia/repo/content"
@@ -47,7 +49,7 @@ func runContentStatsCommand(ctx context.Context, rep *repo.DirectRepository) err
 			}
 			return nil
 		}); err != nil {
-		return err
+		return errors.Wrap(err, "error iterating contents")
 	}
 
 	sizeToString := units.BytesStringBase10

+ 3 - 3
cli/command_diff.go

@@ -24,12 +24,12 @@ var (
 func runDiffCommand(ctx context.Context, rep repo.Repository) error {
 	ent1, err := snapshotfs.FilesystemEntryFromIDWithPath(ctx, rep, *diffFirstObjectPath, false)
 	if err != nil {
-		return err
+		return errors.Wrapf(err, "error getting filesystem entry for %v", *diffFirstObjectPath)
 	}
 
 	ent2, err := snapshotfs.FilesystemEntryFromIDWithPath(ctx, rep, *diffSecondObjectPath, false)
 	if err != nil {
-		return err
+		return errors.Wrapf(err, "error getting filesystem entry for %v", *diffSecondObjectPath)
 	}
 
 	_, isDir1 := ent1.(fs.Directory)
@@ -41,7 +41,7 @@ func runDiffCommand(ctx context.Context, rep repo.Repository) error {
 
 	d, err := diff.NewComparer(os.Stdout)
 	if err != nil {
-		return err
+		return errors.Wrap(err, "error creating comparer")
 	}
 	defer d.Close() //nolint:errcheck
 

+ 3 - 1
cli/command_index_list.go

@@ -5,6 +5,8 @@ import (
 	"fmt"
 	"sort"
 
+	"github.com/pkg/errors"
+
 	"github.com/kopia/kopia/repo"
 )
 
@@ -18,7 +20,7 @@ var (
 func runListBlockIndexesAction(ctx context.Context, rep *repo.DirectRepository) error {
 	blks, err := rep.Content.IndexBlobs(ctx, *blockIndexListIncludeSuperseded)
 	if err != nil {
-		return err
+		return errors.Wrap(err, "error listing index blobs")
 	}
 
 	switch *blockIndexListSort {

+ 5 - 3
cli/command_ls.go

@@ -6,6 +6,8 @@ import (
 	"os"
 	"strings"
 
+	"github.com/pkg/errors"
+
 	"github.com/kopia/kopia/fs"
 	"github.com/kopia/kopia/repo"
 	"github.com/kopia/kopia/repo/object"
@@ -25,7 +27,7 @@ var (
 func runLSCommand(ctx context.Context, rep repo.Repository) error {
 	dir, err := snapshotfs.FilesystemDirectoryFromIDWithPath(ctx, rep, *lsCommandPath, false)
 	if err != nil {
-		return err
+		return errors.Wrap(err, "unable to get filesystem directory entry")
 	}
 
 	var prefix string
@@ -46,12 +48,12 @@ func init() {
 func listDirectory(ctx context.Context, d fs.Directory, prefix, indent string) error {
 	entries, err := d.Readdir(ctx)
 	if err != nil {
-		return err
+		return errors.Wrap(err, "error reading directory")
 	}
 
 	for _, e := range entries {
 		if err := printDirectoryEntry(ctx, e, prefix, indent); err != nil {
-			return err
+			return errors.Wrap(err, "unable to print directory entry")
 		}
 	}
 

+ 1 - 1
cli/command_manifest_ls.go

@@ -35,7 +35,7 @@ func listManifestItems(ctx context.Context, rep repo.Repository) error {
 
 	items, err := rep.FindManifests(ctx, filter)
 	if err != nil {
-		return err
+		return errors.Wrap(err, "unable to find manifests")
 	}
 
 	sort.Slice(items, func(i, j int) bool {

+ 3 - 1
cli/command_manifest_rm.go

@@ -3,6 +3,8 @@ package cli
 import (
 	"context"
 
+	"github.com/pkg/errors"
+
 	"github.com/kopia/kopia/repo"
 )
 
@@ -16,7 +18,7 @@ func runManifestRemoveCommand(ctx context.Context, rep repo.Repository) error {
 
 	for _, it := range toManifestIDs(*manifestRemoveItems) {
 		if err := rep.DeleteManifest(ctx, it); err != nil {
-			return err
+			return errors.Wrapf(err, "unable to delete manifest %v", it)
 		}
 	}
 

+ 2 - 2
cli/command_mount.go

@@ -35,7 +35,7 @@ func runMountCommand(ctx context.Context, rep repo.Repository) error {
 		var err error
 		entry, err = snapshotfs.FilesystemDirectoryFromIDWithPath(ctx, rep, *mountObjectID, false)
 		if err != nil {
-			return err
+			return errors.Wrapf(err, "unable to get directory entry for %v", *mountObjectID)
 		}
 	}
 
@@ -83,7 +83,7 @@ func runMountCommand(ctx context.Context, rep repo.Repository) error {
 		// "unmount error: exit status 1: fusermount: failed to unmount /tmp/kopia-mount719819963: Device or resource busy, try --help"
 		err := ctrl.Unmount(ctx)
 		if err != nil {
-			return err
+			return errors.Wrap(err, "unmount error")
 		}
 
 	case <-ctrl.Done():

+ 1 - 1
cli/command_policy.go

@@ -33,7 +33,7 @@ func policyTargets(ctx context.Context, rep repo.Repository, globalFlag *bool, t
 
 		target, err := snapshot.ParseSourceInfo(ts, rep.ClientOptions().Hostname, rep.ClientOptions().Username)
 		if err != nil {
-			return nil, err
+			return nil, errors.Wrapf(err, "unable to parse source info: %q", ts)
 		}
 
 		res = append(res, target)

+ 1 - 1
cli/command_policy_edit.go

@@ -86,7 +86,7 @@ func editPolicy(ctx context.Context, rep repo.Repository) error {
 			d.DisallowUnknownFields()
 			return d.Decode(updated)
 		}); err != nil {
-			return err
+			return errors.Wrap(err, "unable to launch editor")
 		}
 
 		if jsonEqual(updated, original) {

+ 3 - 1
cli/command_policy_ls.go

@@ -5,6 +5,8 @@ import (
 	"fmt"
 	"sort"
 
+	"github.com/pkg/errors"
+
 	"github.com/kopia/kopia/repo"
 	"github.com/kopia/kopia/snapshot/policy"
 )
@@ -18,7 +20,7 @@ func init() {
 func listPolicies(ctx context.Context, rep repo.Repository) error {
 	policies, err := policy.ListPolicies(ctx, rep)
 	if err != nil {
-		return err
+		return errors.Wrap(err, "error listing policies")
 	}
 
 	sort.Slice(policies, func(i, j int) bool {

+ 3 - 1
cli/command_policy_remove.go

@@ -3,6 +3,8 @@ package cli
 import (
 	"context"
 
+	"github.com/pkg/errors"
+
 	"github.com/kopia/kopia/repo"
 	"github.com/kopia/kopia/snapshot/policy"
 )
@@ -32,7 +34,7 @@ func removePolicy(ctx context.Context, rep repo.Repository) error {
 		}
 
 		if err := policy.RemovePolicy(ctx, rep, target); err != nil {
-			return err
+			return errors.Wrapf(err, "error removing policy on %v", target)
 		}
 	}
 

+ 1 - 1
cli/command_policy_set_actions.go

@@ -70,7 +70,7 @@ func setActionCommandFromFlags(ctx context.Context, actionName string, cmd **pol
 	if *policySetPersistActionScript {
 		script, err := ioutil.ReadFile(value) //nolint:gosec
 		if err != nil {
-			return err
+			return errors.Wrap(err, "unable to read script file")
 		}
 
 		if len(script) > maxScriptLength {

+ 1 - 1
cli/command_repository_connect.go

@@ -78,7 +78,7 @@ func runConnectCommandWithStorage(ctx context.Context, st blob.Storage) error {
 func runConnectCommandWithStorageAndPassword(ctx context.Context, st blob.Storage, password string) error {
 	configFile := repositoryConfigFileName()
 	if err := repo.Connect(ctx, configFile, st, password, connectOptions()); err != nil {
-		return err
+		return errors.Wrap(err, "error connecting to repository")
 	}
 
 	log(ctx).Infof("Connected to repository.")

+ 1 - 1
cli/command_repository_connect_server.go

@@ -29,7 +29,7 @@ func runConnectAPIServerCommand(ctx context.Context) error {
 
 	configFile := repositoryConfigFileName()
 	if err := repo.ConnectAPIServer(ctx, configFile, as, password, connectOptions()); err != nil {
-		return err
+		return errors.Wrap(err, "error connecting to API server")
 	}
 
 	log(ctx).Infof("Connected to repository API Server.")

+ 4 - 2
cli/command_repository_create.go

@@ -47,13 +47,15 @@ func ensureEmpty(ctx context.Context, s blob.Storage) error {
 	hasDataError := errors.Errorf("has data")
 
 	err := s.ListBlobs(ctx, "", func(cb blob.Metadata) error {
+		// nolint:wrapcheck
 		return hasDataError
 	})
-	if err == hasDataError { //nolint:goerr113
+
+	if errors.Is(err, hasDataError) {
 		return errors.New("found existing data in storage location")
 	}
 
-	return err
+	return errors.Wrap(err, "error listing blobs")
 }
 
 func runCreateCommandWithStorage(ctx context.Context, st blob.Storage) error {

+ 8 - 6
cli/command_repository_repair.go

@@ -68,24 +68,26 @@ func recoverFormatBlob(ctx context.Context, st blob.Storage, prefixes []string)
 			if b, err := repo.RecoverFormatBlob(ctx, st, bi.BlobID, bi.Length); err == nil {
 				if !*repairDryDrun {
 					if puterr := st.PutBlob(ctx, repo.FormatBlobID, gather.FromSlice(b)); puterr != nil {
-						return puterr
+						return errors.Wrap(puterr, "error writing format blob")
 					}
 				}
 
 				log(ctx).Infof("recovered replica block from %v", bi.BlobID)
+
+				// nolint:wrapcheck
 				return errSuccess
 			}
 
 			return nil
 		})
 
-		switch err {
-		case errSuccess:
-			return nil
-		case nil:
+		switch {
+		case err == nil:
 			// do nothing
+		case errors.Is(err, errSuccess):
+			return nil
 		default:
-			return err
+			return errors.Wrap(err, "unexpected error when listing blobs")
 		}
 	}
 

+ 1 - 1
cli/command_repository_status.go

@@ -68,7 +68,7 @@ func runStatusCommand(ctx context.Context, rep repo.Repository) error {
 
 	tok, err := dr.Token(pass)
 	if err != nil {
-		return err
+		return errors.Wrap(err, "error computing repository token")
 	}
 
 	fmt.Printf("\nTo reconnect to the repository use:\n\n$ kopia repository connect from-config --token %v\n\n", tok)

+ 2 - 2
cli/command_repository_sync.go

@@ -92,7 +92,7 @@ func runSyncWithStorage(ctx context.Context, src, dst blob.Storage) error {
 
 		return nil
 	}); err != nil {
-		return err
+		return errors.Wrap(err, "error listing blobs")
 	}
 
 	finishSyncProcess()
@@ -296,7 +296,7 @@ func syncDeleteBlob(ctx context.Context, m blob.Metadata, dst blob.Storage) erro
 		return nil
 	}
 
-	return err
+	return errors.Wrap(err, "error deleting blob")
 }
 
 func ensureRepositoriesHaveSameFormatBlob(ctx context.Context, src, dst blob.Storage) error {

+ 2 - 2
cli/command_restore.go

@@ -91,7 +91,7 @@ func addRestoreFlags(cmd *kingpin.CmdClause) {
 func restoreOutput(ctx context.Context) (restore.Output, error) {
 	p, err := filepath.Abs(restoreTargetPath)
 	if err != nil {
-		return nil, err
+		return nil, errors.Wrap(err, "unable to resolve path")
 	}
 
 	m := detectRestoreMode(ctx, restoreMode)
@@ -214,7 +214,7 @@ func runRestoreCommand(ctx context.Context, rep repo.Repository) error {
 		},
 	})
 	if err != nil {
-		return err
+		return errors.Wrap(err, "error restoring")
 	}
 
 	printRestoreStats(ctx, st)

+ 1 - 1
cli/command_server_start.go

@@ -198,7 +198,7 @@ func requireCredentials(handler http.Handler) (*http.ServeMux, error) {
 	case *serverStartHtpasswdFile != "":
 		f, err := htpasswd.New(*serverStartHtpasswdFile, htpasswd.DefaultSystems, nil)
 		if err != nil {
-			return nil, err
+			return nil, errors.Wrap(err, "error initializing htpasswd")
 		}
 
 		handler = requireAuth{inner: handler, htpasswdFile: f}

+ 3 - 1
cli/command_server_status.go

@@ -4,6 +4,8 @@ import (
 	"context"
 	"fmt"
 
+	"github.com/pkg/errors"
+
 	"github.com/kopia/kopia/internal/apiclient"
 	"github.com/kopia/kopia/internal/serverapi"
 )
@@ -17,7 +19,7 @@ func init() {
 func runServerStatus(ctx context.Context, cli *apiclient.KopiaAPIClient) error {
 	var status serverapi.SourcesResponse
 	if err := cli.Get(ctx, "sources", nil, &status); err != nil {
-		return err
+		return errors.Wrap(err, "unable to list sources")
 	}
 
 	for _, src := range status.Sources {

+ 3 - 1
cli/command_server_upload.go

@@ -4,6 +4,8 @@ import (
 	"context"
 	"fmt"
 
+	"github.com/pkg/errors"
+
 	"github.com/kopia/kopia/internal/apiclient"
 	"github.com/kopia/kopia/internal/serverapi"
 )
@@ -22,7 +24,7 @@ func triggerActionOnMatchingSources(ctx context.Context, cli *apiclient.KopiaAPI
 	var resp serverapi.MultipleSourceActionResponse
 
 	if err := cli.Post(ctx, path, &serverapi.Empty{}, &resp); err != nil {
-		return err
+		return errors.Wrapf(err, "unable to start upload on %v", path)
 	}
 
 	for src, resp := range resp.Sources {

+ 5 - 3
cli/command_show.go

@@ -4,6 +4,8 @@ import (
 	"context"
 	"os"
 
+	"github.com/pkg/errors"
+
 	"github.com/kopia/kopia/internal/iocopy"
 	"github.com/kopia/kopia/repo"
 	"github.com/kopia/kopia/snapshot/snapshotfs"
@@ -17,19 +19,19 @@ var (
 func runCatCommand(ctx context.Context, rep repo.Repository) error {
 	oid, err := snapshotfs.ParseObjectIDWithPath(ctx, rep, *catCommandPath)
 	if err != nil {
-		return err
+		return errors.Wrapf(err, "unable to parse ID: %v", *catCommandPath)
 	}
 
 	r, err := rep.OpenObject(ctx, oid)
 	if err != nil {
-		return err
+		return errors.Wrapf(err, "error opening object %v", oid)
 	}
 
 	defer r.Close() //nolint:errcheck
 
 	_, err = iocopy.Copy(os.Stdout, r)
 
-	return err
+	return errors.Wrap(err, "unable to copy data")
 }
 
 func init() {

+ 2 - 2
cli/command_snapshot_create.go

@@ -176,7 +176,7 @@ func snapshotSingleSource(ctx context.Context, rep repo.Repository, u *snapshotf
 
 	manifest, err := u.Upload(ctx, localEntry, policyTree, sourceInfo, previous...)
 	if err != nil {
-		return err
+		return errors.Wrap(err, "upload error")
 	}
 
 	manifest.Description = *snapshotCreateDescription
@@ -230,7 +230,7 @@ func snapshotSingleSource(ctx context.Context, rep repo.Repository, u *snapshotf
 
 	log(ctx).Infof("Created%v snapshot with root %v and ID %v in %v", maybePartial, manifest.RootObjectID(), snapID, clock.Since(t0).Truncate(time.Second))
 
-	return err
+	return errors.Wrap(err, "error snapshotting")
 }
 
 // findPreviousSnapshotManifest returns the list of previous snapshots for a given source, including

+ 1 - 1
cli/command_snapshot_delete.go

@@ -52,7 +52,7 @@ func deleteSnapshot(ctx context.Context, rep repo.Repository, m *snapshot.Manife
 func deleteSnapshotsByRootObjectID(ctx context.Context, rep repo.Repository, rootID object.ID) error {
 	manifests, err := snapshot.FindSnapshotsByRootObjectID(ctx, rep, rootID)
 	if err != nil {
-		return err
+		return errors.Wrapf(err, "unable to find snapshots by root %v", rootID)
 	}
 
 	if len(manifests) == 0 {

+ 2 - 2
cli/command_snapshot_estimate.go

@@ -103,7 +103,7 @@ func runSnapshotEstimateCommand(ctx context.Context, rep repo.Repository) error
 	if dir, ok := entry.(fs.Directory); ok {
 		policyTree, err := policy.TreeForSource(ctx, rep, sourceInfo)
 		if err != nil {
-			return err
+			return errors.Wrapf(err, "error creating policy tree for %v", sourceInfo)
 		}
 
 		entry = ignorefs.New(dir, policyTree, ignorefs.ReportIgnoredFiles(onIgnoredFile))
@@ -154,7 +154,7 @@ func estimate(ctx context.Context, relativePath string, entry fs.Entry, stats *s
 
 		children, err := entry.Readdir(ctx)
 		if err != nil {
-			return err
+			return errors.Wrap(err, "unable to read directory")
 		}
 
 		for _, child := range children {

+ 4 - 2
cli/command_snapshot_expire.go

@@ -4,6 +4,8 @@ import (
 	"context"
 	"sort"
 
+	"github.com/pkg/errors"
+
 	"github.com/kopia/kopia/repo"
 	"github.com/kopia/kopia/snapshot"
 	"github.com/kopia/kopia/snapshot/policy"
@@ -27,7 +29,7 @@ func getSnapshotSourcesToExpire(ctx context.Context, rep repo.Repository) ([]sna
 	for _, p := range *snapshotExpirePaths {
 		src, err := snapshot.ParseSourceInfo(p, rep.ClientOptions().Hostname, rep.ClientOptions().Username)
 		if err != nil {
-			return nil, err
+			return nil, errors.Wrapf(err, "unable to parse %q", p)
 		}
 
 		result = append(result, src)
@@ -49,7 +51,7 @@ func runExpireCommand(ctx context.Context, rep repo.Repository) error {
 	for _, src := range sources {
 		deleted, err := policy.ApplyRetentionPolicy(ctx, rep, src, *snapshotExpireDelete)
 		if err != nil {
-			return err
+			return errors.Wrapf(err, "error applying retention policy to %v", src)
 		}
 
 		if len(deleted) == 0 {

+ 3 - 1
cli/command_snapshot_gc.go

@@ -3,6 +3,8 @@ package cli
 import (
 	"context"
 
+	"github.com/pkg/errors"
+
 	"github.com/kopia/kopia/internal/units"
 	"github.com/kopia/kopia/repo"
 	"github.com/kopia/kopia/repo/maintenance"
@@ -25,7 +27,7 @@ func runSnapshotGCCommand(ctx context.Context, rep *repo.DirectRepository) error
 	log(ctx).Infof("GC found %v in-use contents (%v bytes)", st.InUseCount, units.BytesStringBase2(st.InUseBytes))
 	log(ctx).Infof("GC found %v in-use system-contents (%v bytes)", st.SystemCount, units.BytesStringBase2(st.SystemBytes))
 
-	return err
+	return errors.Wrap(err, "error running snapshot GC")
 }
 
 func init() {

+ 3 - 3
cli/command_snapshot_list.go

@@ -39,7 +39,7 @@ func findSnapshotsForSource(ctx context.Context, rep repo.Repository, sourceInfo
 	for len(sourceInfo.Path) > 0 {
 		list, err := snapshot.ListSnapshotManifests(ctx, rep, &sourceInfo)
 		if err != nil {
-			return nil, "", err
+			return nil, "", errors.Wrapf(err, "error listing manifests for %v", sourceInfo)
 		}
 
 		if len(list) > 0 {
@@ -68,7 +68,7 @@ func findSnapshotsForSource(ctx context.Context, rep repo.Repository, sourceInfo
 func findManifestIDs(ctx context.Context, rep repo.Repository, source string) ([]manifest.ID, string, error) {
 	if source == "" {
 		man, err := snapshot.ListSnapshotManifests(ctx, rep, nil)
-		return man, "", err
+		return man, "", errors.Wrap(err, "error listing all snapshot manifests")
 	}
 
 	si, err := snapshot.ParseSourceInfo(source, rep.ClientOptions().Hostname, rep.ClientOptions().Username)
@@ -92,7 +92,7 @@ func runSnapshotsCommand(ctx context.Context, rep repo.Repository) error {
 
 	manifests, err := snapshot.LoadSnapshots(ctx, rep, manifestIDs)
 	if err != nil {
-		return err
+		return errors.Wrap(err, "unable to load snapshots")
 	}
 
 	return outputManifestGroups(ctx, rep, manifests, strings.Split(relPath, "/"))

+ 3 - 3
cli/command_snapshot_migrate.go

@@ -201,7 +201,7 @@ func findPreviousSnapshotManifestWithStartTime(ctx context.Context, rep repo.Rep
 func migrateSingleSource(ctx context.Context, uploader *snapshotfs.Uploader, sourceRepo, destRepo repo.Repository, s snapshot.SourceInfo) error {
 	manifests, err := snapshot.ListSnapshotManifests(ctx, sourceRepo, &s)
 	if err != nil {
-		return err
+		return errors.Wrapf(err, "error listing snapshot manifests for %v", s)
 	}
 
 	snapshots, err := snapshot.LoadSnapshots(ctx, sourceRepo, manifests)
@@ -234,7 +234,7 @@ func migrateSingleSourceSnapshot(ctx context.Context, uploader *snapshotfs.Uploa
 
 	sourceEntry, err := snapshotfs.SnapshotRoot(sourceRepo, m)
 	if err != nil {
-		return err
+		return errors.Wrap(err, "error getting snapshot root entry")
 	}
 
 	existing, err := findPreviousSnapshotManifestWithStartTime(ctx, destRepo, m.Source, m.StartTime)
@@ -289,7 +289,7 @@ func getSourcesToMigrate(ctx context.Context, rep repo.Repository) ([]snapshot.S
 		for _, s := range *migrateSources {
 			si, err := snapshot.ParseSourceInfo(s, rep.ClientOptions().Hostname, rep.ClientOptions().Username)
 			if err != nil {
-				return nil, err
+				return nil, errors.Wrapf(err, "unable to parse %q", s)
 			}
 
 			result = append(result, si)

+ 6 - 6
cli/command_snapshot_verify.go

@@ -188,13 +188,13 @@ func (v *verifier) readEntireObject(ctx context.Context, oid object.ID, path str
 	// also read the entire file
 	r, err := v.rep.OpenObject(ctx, oid)
 	if err != nil {
-		return err
+		return errors.Wrapf(err, "unable to open object %v", oid)
 	}
 	defer r.Close() //nolint:errcheck
 
 	_, err = iocopy.Copy(ioutil.Discard, r)
 
-	return err
+	return errors.Wrap(err, "unable to read data")
 }
 
 func runVerifyCommand(ctx context.Context, rep repo.Repository) error {
@@ -257,7 +257,7 @@ func enqueueRootsToVerify(ctx context.Context, v *verifier, rep repo.Repository)
 	for _, oidStr := range *verifyCommandDirObjectIDs {
 		oid, err := snapshotfs.ParseObjectIDWithPath(ctx, rep, oidStr)
 		if err != nil {
-			return err
+			return errors.Wrapf(err, "unable to parse: %q", oidStr)
 		}
 
 		v.enqueueVerifyDirectory(ctx, oid, oidStr)
@@ -266,7 +266,7 @@ func enqueueRootsToVerify(ctx context.Context, v *verifier, rep repo.Repository)
 	for _, oidStr := range *verifyCommandFileObjectIDs {
 		oid, err := snapshotfs.ParseObjectIDWithPath(ctx, rep, oidStr)
 		if err != nil {
-			return err
+			return errors.Wrapf(err, "unable to parse %q", oidStr)
 		}
 
 		v.enqueueVerifyObject(ctx, oid, oidStr)
@@ -281,7 +281,7 @@ func loadSourceManifests(ctx context.Context, rep repo.Repository, sources []str
 	if len(sources)+len(*verifyCommandDirObjectIDs)+len(*verifyCommandFileObjectIDs) == 0 {
 		man, err := snapshot.ListSnapshotManifests(ctx, rep, nil)
 		if err != nil {
-			return nil, err
+			return nil, errors.Wrap(err, "unable to list snapshot manifests")
 		}
 
 		manifestIDs = append(manifestIDs, man...)
@@ -293,7 +293,7 @@ func loadSourceManifests(ctx context.Context, rep repo.Repository, sources []str
 			}
 			man, err := snapshot.ListSnapshotManifests(ctx, rep, &src)
 			if err != nil {
-				return nil, err
+				return nil, errors.Wrapf(err, "unable to list snapshot manifests for %v", src)
 			}
 			manifestIDs = append(manifestIDs, man...)
 		}

+ 1 - 1
cli/config.go

@@ -67,7 +67,7 @@ func openRepository(ctx context.Context, opts *repo.Options, required bool) (rep
 		return nil, errors.New("not connected to a repository, use 'kopia connect'")
 	}
 
-	return r, err
+	return r, errors.Wrap(err, "unable to open repository")
 }
 
 func applyOptionsFromFlags(ctx context.Context, opts *repo.Options) *repo.Options {

+ 1 - 1
cli/password.go

@@ -74,7 +74,7 @@ func askPass(prompt string) (string, error) {
 	for i := 0; i < 5; i++ {
 		p, err := speakeasy.Ask(prompt)
 		if err != nil {
-			return "", err
+			return "", errors.Wrap(err, "password prompt error")
 		}
 
 		if p == "" {

+ 3 - 3
cli/show_utils.go

@@ -47,18 +47,18 @@ func showContentWithFlags(rd io.Reader, unzip, indentJSON bool) error {
 
 	if indentJSON {
 		if _, err := iocopy.Copy(&buf1, rd); err != nil {
-			return err
+			return errors.Wrap(err, "error copying data")
 		}
 
 		if err := json.Indent(&buf2, buf1.Bytes(), "", "  "); err != nil {
-			return err
+			return errors.Wrap(err, "errors indenting JSON")
 		}
 
 		rd = ioutil.NopCloser(&buf2)
 	}
 
 	if _, err := iocopy.Copy(os.Stdout, rd); err != nil {
-		return err
+		return errors.Wrap(err, "error copying data")
 	}
 
 	return nil

+ 2 - 1
cli/storage_gcs.go

@@ -6,6 +6,7 @@ import (
 	"io/ioutil"
 
 	"github.com/alecthomas/kingpin"
+	"github.com/pkg/errors"
 
 	"github.com/kopia/kopia/repo/blob"
 	"github.com/kopia/kopia/repo/blob/gcs"
@@ -32,7 +33,7 @@ func init() {
 			if embedCredentials {
 				data, err := ioutil.ReadFile(options.ServiceAccountCredentialsFile)
 				if err != nil {
-					return nil, err
+					return nil, errors.Wrap(err, "unable to open service account credentials file")
 				}
 
 				options.ServiceAccountCredentialJSON = json.RawMessage(data)

+ 2 - 1
cli/storage_rclone.go

@@ -5,6 +5,7 @@ import (
 	"io/ioutil"
 
 	"github.com/alecthomas/kingpin"
+	"github.com/pkg/errors"
 
 	"github.com/kopia/kopia/repo/blob"
 	"github.com/kopia/kopia/repo/blob/rclone"
@@ -36,7 +37,7 @@ func init() {
 			if embedRCloneConfigFile != "" {
 				cfg, err := ioutil.ReadFile(embedRCloneConfigFile) //nolint:gosec
 				if err != nil {
-					return nil, err
+					return nil, errors.Wrap(err, "unable to read rclone config file")
 				}
 
 				opt.EmbeddedConfig = string(cfg)

+ 2 - 2
cli/storage_sftp.go

@@ -47,7 +47,7 @@ func init() {
 					if sftpo.KeyData == "" {
 						d, err := ioutil.ReadFile(sftpo.Keyfile)
 						if err != nil {
-							return nil, err
+							return nil, errors.Wrap(err, "unable to read key file")
 						}
 
 						sftpo.KeyData = string(d)
@@ -57,7 +57,7 @@ func init() {
 					if sftpo.KnownHostsData == "" && sftpo.KnownHostsFile != "" {
 						d, err := ioutil.ReadFile(sftpo.KnownHostsFile)
 						if err != nil {
-							return nil, err
+							return nil, errors.Wrap(err, "unable to read known hosts file")
 						}
 
 						sftpo.KnownHostsData = string(d)

+ 2 - 1
fs/cachefs/cache_test.go

@@ -2,7 +2,6 @@ package cachefs
 
 import (
 	"context"
-	"errors"
 	"fmt"
 	"path/filepath"
 	"reflect"
@@ -12,6 +11,8 @@ import (
 	"testing"
 	"time"
 
+	"github.com/pkg/errors"
+
 	"github.com/kopia/kopia/fs"
 	"github.com/kopia/kopia/internal/testlogging"
 )

+ 3 - 0
fs/cachefs/cachefs.go

@@ -24,6 +24,7 @@ type directory struct {
 func (d *directory) Child(ctx context.Context, name string) (fs.Entry, error) {
 	e, err := d.Directory.Child(ctx, name)
 	if err != nil {
+		// nolint:wrapcheck
 		return nil, err
 	}
 
@@ -33,6 +34,7 @@ func (d *directory) Child(ctx context.Context, name string) (fs.Entry, error) {
 func (d *directory) Readdir(ctx context.Context) (fs.Entries, error) {
 	entries, err := d.ctx.cacher.Readdir(ctx, d.Directory)
 	if err != nil {
+		// nolint:wrapcheck
 		return entries, err
 	}
 
@@ -41,6 +43,7 @@ func (d *directory) Readdir(ctx context.Context) (fs.Entries, error) {
 		wrapped[i] = wrapWithContext(entry, d.ctx)
 	}
 
+	// nolint:wrapcheck
 	return wrapped, err
 }
 

+ 4 - 2
fs/entry.go

@@ -2,11 +2,12 @@ package fs
 
 import (
 	"context"
-	"errors"
 	"io"
 	"os"
 	"sort"
 	"time"
+
+	"github.com/pkg/errors"
 )
 
 // Entry represents a filesystem entry, which can be Directory, File, or Symlink.
@@ -66,11 +67,12 @@ var ErrEntryNotFound = errors.New("entry not found")
 func ReadDirAndFindChild(ctx context.Context, d Directory, name string) (Entry, error) {
 	children, err := d.Readdir(ctx)
 	if err != nil {
-		return nil, err
+		return nil, errors.Wrap(err, "error reading directory")
 	}
 
 	e := children.FindByName(name)
 	if e == nil {
+		// nolint:wrapcheck
 		return nil, ErrEntryNotFound
 	}
 

+ 3 - 2
fs/ignorefs/ignorefs.go

@@ -78,7 +78,7 @@ func isCorrectCacheDirSignature(ctx context.Context, f fs.File) (bool, error) {
 
 	r, err := f.Open(ctx)
 	if err != nil {
-		return false, err
+		return false, errors.Wrap(err, "unable to open cache dir marker file")
 	}
 
 	defer r.Close() //nolint:errcheck
@@ -86,7 +86,7 @@ func isCorrectCacheDirSignature(ctx context.Context, f fs.File) (bool, error) {
 	sig := make([]byte, validSignatureLen)
 
 	if _, err := r.Read(sig); err != nil {
-		return false, err
+		return false, errors.Wrap(err, "unable to read cache dir marker file")
 	}
 
 	return string(sig) == validSignature, nil
@@ -121,6 +121,7 @@ func (d *ignoreDirectory) skipCacheDirectory(ctx context.Context, entries fs.Ent
 func (d *ignoreDirectory) Readdir(ctx context.Context) (fs.Entries, error) {
 	entries, err := d.Directory.Readdir(ctx)
 	if err != nil {
+		// nolint:wrapcheck
 		return nil, err
 	}
 

+ 5 - 5
fs/localfs/local_fs.go

@@ -137,7 +137,7 @@ func (fsd *filesystemDirectory) Readdir(ctx context.Context) (fs.Entries, error)
 
 	f, direrr := os.Open(fullPath) //nolint:gosec
 	if direrr != nil {
-		return nil, direrr
+		return nil, errors.Wrap(direrr, "unable to read directory")
 	}
 	defer f.Close() //nolint:errcheck,gosec
 
@@ -159,7 +159,7 @@ func (fsd *filesystemDirectory) Readdir(ctx context.Context) (fs.Entries, error)
 				continue
 			}
 
-			if err == io.EOF {
+			if errors.Is(err, io.EOF) {
 				break
 			}
 
@@ -239,7 +239,7 @@ type fileWithMetadata struct {
 func (f *fileWithMetadata) Entry() (fs.Entry, error) {
 	fi, err := f.Stat()
 	if err != nil {
-		return nil, err
+		return nil, errors.Wrap(err, "unable to stat() local file")
 	}
 
 	return &filesystemFile{newEntry(fi, filepath.Dir(f.Name()))}, nil
@@ -248,7 +248,7 @@ func (f *fileWithMetadata) Entry() (fs.Entry, error) {
 func (fsf *filesystemFile) Open(ctx context.Context) (fs.Reader, error) {
 	f, err := os.Open(fsf.fullPath())
 	if err != nil {
-		return nil, err
+		return nil, errors.Wrap(err, "unable to open local file")
 	}
 
 	return &fileWithMetadata{f}, nil
@@ -262,7 +262,7 @@ func (fsl *filesystemSymlink) Readlink(ctx context.Context) (string, error) {
 func NewEntry(path string) (fs.Entry, error) {
 	fi, err := os.Lstat(path)
 	if err != nil {
-		return nil, err
+		return nil, errors.Wrap(err, "unable to determine entry type")
 	}
 
 	switch fi.Mode() & os.ModeType {

+ 4 - 2
fs/localfs/local_fs_test.go

@@ -7,6 +7,8 @@ import (
 	"path/filepath"
 	"testing"
 
+	"github.com/pkg/errors"
+
 	"github.com/kopia/kopia/fs"
 	"github.com/kopia/kopia/internal/clock"
 	"github.com/kopia/kopia/internal/testlogging"
@@ -108,7 +110,7 @@ func verifyChild(t *testing.T, dir fs.Directory) {
 		t.Errorf("child error: %v", err)
 	}
 
-	if _, err = dir.Child(ctx, "f4"); err != fs.ErrEntryNotFound {
+	if _, err = dir.Child(ctx, "f4"); !errors.Is(err, fs.ErrEntryNotFound) {
 		t.Errorf("unexpected child error: %v", err)
 	}
 
@@ -120,7 +122,7 @@ func verifyChild(t *testing.T, dir fs.Directory) {
 		t.Errorf("unexpected child size: %v, want %v", got, want)
 	}
 
-	if _, err = fs.ReadDirAndFindChild(ctx, dir, "f4"); err != fs.ErrEntryNotFound {
+	if _, err = fs.ReadDirAndFindChild(ctx, dir, "f4"); !errors.Is(err, fs.ErrEntryNotFound) {
 		t.Errorf("unexpected child error: %v", err)
 	}
 

+ 2 - 0
fs/loggingfs/loggingfs.go

@@ -26,6 +26,7 @@ func (ld *loggingDirectory) Child(ctx context.Context, name string) (fs.Entry, e
 	ld.options.printf(ld.options.prefix+"Child(%v) took %v and returned %v", ld.relativePath, dt, err)
 
 	if err != nil {
+		// nolint:wrapcheck
 		return nil, err
 	}
 
@@ -43,6 +44,7 @@ func (ld *loggingDirectory) Readdir(ctx context.Context) (fs.Entries, error) {
 		loggingEntries[i] = wrapWithOptions(entry, ld.options, ld.relativePath+"/"+entry.Name())
 	}
 
+	// nolint:wrapcheck
 	return loggingEntries, err
 }
 

+ 1 - 0
go.sum

@@ -814,6 +814,7 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03i
 golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 h1:qwRHBd0NqMbJxfbotnDhm2ByMI1Shq4Y6oRJo21SGJA=
 golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201207232520-09787c993a3a h1:DcqTD9SDLc+1P/r1EmRBwnVsrOwW+kk2vWf9n+1sGhs=
 golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/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-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=

+ 4 - 4
internal/apiclient/apiclient.go

@@ -51,12 +51,12 @@ func (c *KopiaAPIClient) Delete(ctx context.Context, urlSuffix string, reqPayloa
 func (c *KopiaAPIClient) runRequest(ctx context.Context, method, url string, notFoundError error, reqPayload, respPayload interface{}) error {
 	payload, contentType, err := requestReader(reqPayload)
 	if err != nil {
-		return err
+		return errors.Wrap(err, "error getting reader")
 	}
 
 	req, err := http.NewRequestWithContext(ctx, method, url, payload)
 	if err != nil {
-		return err
+		return errors.Wrap(err, "error creating request")
 	}
 
 	if contentType != "" {
@@ -65,7 +65,7 @@ func (c *KopiaAPIClient) runRequest(ctx context.Context, method, url string, not
 
 	resp, err := c.HTTPClient.Do(req)
 	if err != nil {
-		return err
+		return errors.Wrap(err, "error running http request")
 	}
 
 	defer resp.Body.Close() //nolint:errcheck
@@ -180,7 +180,7 @@ func (t loggingTransport) RoundTrip(req *http.Request) (*http.Response, error) {
 	resp, err := t.base.RoundTrip(req)
 	if err != nil {
 		log(req.Context()).Debugf("%v %v took %v and failed with %v", req.Method, req.URL, clock.Since(t0), err)
-		return nil, err
+		return nil, errors.Wrap(err, "round-trip error")
 	}
 
 	log(req.Context()).Debugf("%v %v took %v and returned %v", req.Method, req.URL, clock.Since(t0), resp.Status)

+ 9 - 18
internal/blobtesting/concurrent.go

@@ -64,13 +64,13 @@ func VerifyConcurrentAccess(t testingT, st blob.Storage, options ConcurrentAcces
 				}
 
 				data, err := st.GetBlob(ctx, blobID, offset, length)
-				switch err {
-				case nil:
+				switch {
+				case err == nil:
 					if got, want := string(data), string(blobID); !strings.HasPrefix(got, want) {
 						return errors.Wrapf(err, "GetBlob returned invalid data for %v: %v, want prefix of %v", blobID, got, want)
 					}
 
-				case blob.ErrBlobNotFound:
+				case errors.Is(err, blob.ErrBlobNotFound):
 					// clean error
 
 				default:
@@ -89,11 +89,7 @@ func VerifyConcurrentAccess(t testingT, st blob.Storage, options ConcurrentAcces
 				blobID := randomBlobID()
 				data := fmt.Sprintf("%v-%v", blobID, rand.Int63())
 				err := st.PutBlob(ctx, blobID, gather.FromSlice([]byte(data)))
-				switch err {
-				case nil:
-					// clean success
-
-				default:
+				if err != nil {
 					return errors.Wrapf(err, "PutBlob %v returned unexpected error", blobID)
 				}
 			}
@@ -108,11 +104,11 @@ func VerifyConcurrentAccess(t testingT, st blob.Storage, options ConcurrentAcces
 			for i := 0; i < options.Iterations; i++ {
 				blobID := randomBlobID()
 				err := st.DeleteBlob(ctx, blobID)
-				switch err {
-				case nil:
+				switch {
+				case err == nil:
 					// clean success
 
-				case blob.ErrBlobNotFound:
+				case errors.Is(err, blob.ErrBlobNotFound):
 					// clean error
 
 				default:
@@ -134,14 +130,9 @@ func VerifyConcurrentAccess(t testingT, st blob.Storage, options ConcurrentAcces
 					prefix = "zzz"
 				}
 
-				err := st.ListBlobs(ctx, prefix, func(blob.Metadata) error {
+				if err := st.ListBlobs(ctx, prefix, func(blob.Metadata) error {
 					return nil
-				})
-				switch err {
-				case nil:
-					// clean success
-
-				default:
+				}); err != nil {
 					return errors.Wrapf(err, "ListBlobs(%v) returned unexpected error", prefix)
 				}
 			}

+ 5 - 5
internal/diff/diff.go

@@ -268,25 +268,25 @@ func (c *Comparer) compareFiles(ctx context.Context, f1, f2 fs.File, fname strin
 
 func downloadFile(ctx context.Context, f fs.File, fname string) error {
 	if err := os.MkdirAll(filepath.Dir(fname), 0o700); err != nil {
-		return err
+		return errors.Wrap(err, "error making directory")
 	}
 
 	src, err := f.Open(ctx)
 	if err != nil {
-		return err
+		return errors.Wrap(err, "error opening object")
 	}
 	defer src.Close() //nolint:errcheck
 
 	dst, err := os.Create(fname)
 	if err != nil {
-		return err
+		return errors.Wrap(err, "error creating file to edit")
 	}
 
 	defer dst.Close() //nolint:errcheck,gosec
 
 	_, err = iocopy.Copy(dst, src)
 
-	return err
+	return errors.Wrap(err, "error downloading file")
 }
 
 func (c *Comparer) output(msg string, args ...interface{}) {
@@ -297,7 +297,7 @@ func (c *Comparer) output(msg string, args ...interface{}) {
 func NewComparer(out io.Writer) (*Comparer, error) {
 	tmp, err := ioutil.TempDir("", "kopia")
 	if err != nil {
-		return nil, err
+		return nil, errors.Wrap(err, "error creating temp directory")
 	}
 
 	return &Comparer{out: out, tmpDir: tmp}, nil

+ 6 - 6
internal/editor/editor.go

@@ -25,24 +25,24 @@ var log = logging.GetContextLoggerFunc("editor")
 func EditLoop(ctx context.Context, fname, initial string, parse func(updated string) error) error {
 	tmpDir, err := ioutil.TempDir("", "kopia")
 	if err != nil {
-		return err
+		return errors.Wrap(err, "unable to create temp directory")
 	}
 
 	tmpFile := filepath.Join(tmpDir, fname)
 	defer os.RemoveAll(tmpDir) //nolint:errcheck
 
 	if err := ioutil.WriteFile(tmpFile, []byte(initial), 0o600); err != nil {
-		return err
+		return errors.Wrap(err, "unable to write file to edit")
 	}
 
 	for {
 		if err := editFile(ctx, tmpFile); err != nil {
-			return err
+			return errors.Wrap(err, "error launching editor")
 		}
 
 		txt, err := readAndStripComments(tmpFile)
 		if err != nil {
-			return err
+			return errors.Wrap(err, "error parsing edited file")
 		}
 
 		err = parse(txt)
@@ -66,7 +66,7 @@ func EditLoop(ctx context.Context, fname, initial string, parse func(updated str
 func readAndStripComments(fname string) (string, error) {
 	f, err := os.Open(fname) //nolint:gosec
 	if err != nil {
-		return "", err
+		return "", errors.Wrap(err, "error opening edited file")
 	}
 	defer f.Close() //nolint:errcheck,gosec
 
@@ -101,7 +101,7 @@ func editFile(ctx context.Context, file string) error {
 
 	err := cmd.Run()
 	if err != nil {
-		log(ctx).Errorf("unable to launch editor: %v", err)
+		return errors.Wrap(err, "error running editor command")
 	}
 
 	return nil

+ 6 - 0
internal/fusemount/fusefs.go

@@ -43,6 +43,7 @@ var _ fusefs.NodeOpener = (*fuseFileNode)(nil)
 func (f *fuseFileNode) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.OpenResponse) (fusefs.Handle, error) {
 	reader, err := f.entry.(fs.File).Open(ctx)
 	if err != nil {
+		// nolint:wrapcheck
 		return nil, err
 	}
 
@@ -61,11 +62,13 @@ func (f *fuseFileHandle) Read(ctx context.Context, req *fuse.ReadRequest, resp *
 
 	_, err := f.reader.Seek(req.Offset, io.SeekStart)
 	if err != nil {
+		// nolint:wrapcheck
 		return err
 	}
 
 	n, err := f.reader.Read(resp.Data[:req.Size])
 	if err != nil {
+		// nolint:wrapcheck
 		return err
 	}
 
@@ -86,6 +89,7 @@ var (
 func (f *fuseFileNode) ReadAll(ctx context.Context) ([]byte, error) {
 	reader, err := f.entry.(fs.File).Open(ctx)
 	if err != nil {
+		// nolint:wrapcheck
 		return nil, err
 	}
 	defer reader.Close() //nolint:errcheck
@@ -108,6 +112,7 @@ func (dir *fuseDirectoryNode) Lookup(ctx context.Context, fileName string) (fuse
 			return nil, fuse.ENOENT
 		}
 
+		// nolint:wrapcheck
 		return nil, err
 	}
 
@@ -122,6 +127,7 @@ func (dir *fuseDirectoryNode) Lookup(ctx context.Context, fileName string) (fuse
 func (dir *fuseDirectoryNode) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
 	entries, err := dir.directory().Readdir(ctx)
 	if err != nil {
+		// nolint:wrapcheck
 		return nil, err
 	}
 

+ 1 - 0
internal/gather/gather_bytes.go

@@ -116,6 +116,7 @@ func (b Bytes) WriteTo(w io.Writer) (int64, error) {
 		totalN += int64(n)
 
 		if err != nil {
+			// nolint:wrapcheck
 			return totalN, err
 		}
 	}

+ 1 - 1
internal/mount/mount_fuse.go

@@ -51,7 +51,7 @@ func Directory(ctx context.Context, entry fs.Directory, mountPoint string, mount
 
 		mountPoint, err = ioutil.TempDir("", "kopia-mount")
 		if err != nil {
-			return nil, err
+			return nil, errors.Wrap(err, "error creating temp directory")
 		}
 
 		isTempDir = true

+ 1 - 1
internal/mount/mount_net_use.go

@@ -43,7 +43,7 @@ func netUse(ctx context.Context, args ...string) (string, error) {
 	out, err := nu.Output()
 	log(ctx).Debugf("net use finished with %v %v", string(out), err)
 
-	return string(out), err
+	return string(out), errors.Wrap(err, "error running 'net use'")
 }
 
 func netUseMount(ctx context.Context, driveLetter, webdavURL string) (string, error) {

+ 1 - 1
internal/retry/retry_test.go

@@ -12,7 +12,7 @@ import (
 var errRetriable = errors.New("retriable")
 
 func isRetriable(e error) bool {
-	return e == errRetriable
+	return errors.Is(e, errRetriable)
 }
 
 func TestRetry(t *testing.T) {

+ 4 - 3
internal/server/api_content.go

@@ -37,11 +37,12 @@ func (s *Server) handleContentInfo(ctx context.Context, r *http.Request, body []
 	cid := content.ID(mux.Vars(r)["contentID"])
 
 	ci, err := dr.Content.ContentInfo(ctx, cid)
-	switch err {
-	case nil:
+
+	switch {
+	case err == nil:
 		return ci, nil
 
-	case content.ErrContentNotFound:
+	case errors.Is(err, content.ErrContentNotFound):
 		return nil, notFoundError("content not found")
 
 	default:

+ 4 - 4
internal/server/api_repo.go

@@ -316,12 +316,12 @@ func (s *Server) handleRepoSync(ctx context.Context, r *http.Request, body []byt
 }
 
 func repoErrorToAPIError(err error) *apiError {
-	switch err {
-	case repo.ErrRepositoryNotInitialized:
+	switch {
+	case errors.Is(err, repo.ErrRepositoryNotInitialized):
 		return requestError(serverapi.ErrorNotInitialized, "repository not initialized")
-	case repo.ErrInvalidPassword:
+	case errors.Is(err, repo.ErrInvalidPassword):
 		return requestError(serverapi.ErrorInvalidPassword, "invalid password")
-	case repo.ErrAlreadyInitialized:
+	case errors.Is(err, repo.ErrAlreadyInitialized):
 		return requestError(serverapi.ErrorAlreadyInitialized, "repository already initialized")
 	default:
 		return internalServerError(errors.Wrap(err, "connect error"))

+ 4 - 3
internal/server/api_sources.go

@@ -71,14 +71,15 @@ func (s *Server) handleSourcesCreate(ctx context.Context, r *http.Request, body
 	// ensure we have the policy for this source, otherwise it will not show up in the
 	// list of sources at all.
 	_, err = policy.GetDefinedPolicy(ctx, s.rep, sourceInfo)
-	switch err {
-	case nil:
+
+	switch {
+	case err == nil:
 		// already have policy, do nothing
 		log(ctx).Debugf("policy for %v already exists", sourceInfo)
 
 		resp.Created = false
 
-	case policy.ErrPolicyNotFound:
+	case errors.Is(err, policy.ErrPolicyNotFound):
 		resp.Created = true
 		// don't have policy - create an empty one
 		log(ctx).Debugf("policy for %v not found, creating empty one", sourceInfo)

+ 10 - 8
internal/serverapi/client_wrappers.go

@@ -4,6 +4,8 @@ import (
 	"context"
 	"strings"
 
+	"github.com/pkg/errors"
+
 	"github.com/kopia/kopia/internal/apiclient"
 	"github.com/kopia/kopia/repo/object"
 	"github.com/kopia/kopia/snapshot"
@@ -13,7 +15,7 @@ import (
 func CreateSnapshotSource(ctx context.Context, c *apiclient.KopiaAPIClient, req *CreateSnapshotSourceRequest) (*CreateSnapshotSourceResponse, error) {
 	resp := &CreateSnapshotSourceResponse{}
 	if err := c.Post(ctx, "sources", req, resp); err != nil {
-		return nil, err
+		return nil, errors.Wrap(err, "CreateSnapshotSource")
 	}
 
 	return resp, nil
@@ -23,7 +25,7 @@ func CreateSnapshotSource(ctx context.Context, c *apiclient.KopiaAPIClient, req
 func UploadSnapshots(ctx context.Context, c *apiclient.KopiaAPIClient, match *snapshot.SourceInfo) (*MultipleSourceActionResponse, error) {
 	resp := &MultipleSourceActionResponse{}
 	if err := c.Post(ctx, "sources/upload"+matchSourceParameters(match), &Empty{}, resp); err != nil {
-		return nil, err
+		return nil, errors.Wrap(err, "UploadSnapshots")
 	}
 
 	return resp, nil
@@ -33,7 +35,7 @@ func UploadSnapshots(ctx context.Context, c *apiclient.KopiaAPIClient, match *sn
 func CancelUpload(ctx context.Context, c *apiclient.KopiaAPIClient, match *snapshot.SourceInfo) (*MultipleSourceActionResponse, error) {
 	resp := &MultipleSourceActionResponse{}
 	if err := c.Post(ctx, "sources/cancel"+matchSourceParameters(match), &Empty{}, resp); err != nil {
-		return nil, err
+		return nil, errors.Wrap(err, "CancelUpload")
 	}
 
 	return resp, nil
@@ -63,7 +65,7 @@ func Shutdown(ctx context.Context, c *apiclient.KopiaAPIClient) {
 func Status(ctx context.Context, c *apiclient.KopiaAPIClient) (*StatusResponse, error) {
 	resp := &StatusResponse{}
 	if err := c.Get(ctx, "repo/status", nil, resp); err != nil {
-		return nil, err
+		return nil, errors.Wrap(err, "Status")
 	}
 
 	return resp, nil
@@ -73,7 +75,7 @@ func Status(ctx context.Context, c *apiclient.KopiaAPIClient) (*StatusResponse,
 func ListSources(ctx context.Context, c *apiclient.KopiaAPIClient, match *snapshot.SourceInfo) (*SourcesResponse, error) {
 	resp := &SourcesResponse{}
 	if err := c.Get(ctx, "sources"+matchSourceParameters(match), nil, resp); err != nil {
-		return nil, err
+		return nil, errors.Wrap(err, "ListSources")
 	}
 
 	return resp, nil
@@ -83,7 +85,7 @@ func ListSources(ctx context.Context, c *apiclient.KopiaAPIClient, match *snapsh
 func ListSnapshots(ctx context.Context, c *apiclient.KopiaAPIClient, match *snapshot.SourceInfo) (*SnapshotsResponse, error) {
 	resp := &SnapshotsResponse{}
 	if err := c.Get(ctx, "snapshots"+matchSourceParameters(match), nil, resp); err != nil {
-		return nil, err
+		return nil, errors.Wrap(err, "ListSnapshots")
 	}
 
 	return resp, nil
@@ -93,7 +95,7 @@ func ListSnapshots(ctx context.Context, c *apiclient.KopiaAPIClient, match *snap
 func ListPolicies(ctx context.Context, c *apiclient.KopiaAPIClient, match *snapshot.SourceInfo) (*PoliciesResponse, error) {
 	resp := &PoliciesResponse{}
 	if err := c.Get(ctx, "policies"+matchSourceParameters(match), nil, resp); err != nil {
-		return nil, err
+		return nil, errors.Wrap(err, "ListPolicies")
 	}
 
 	return resp, nil
@@ -104,7 +106,7 @@ func GetObject(ctx context.Context, c *apiclient.KopiaAPIClient, objectID string
 	var b []byte
 
 	if err := c.Get(ctx, "objects/"+objectID, object.ErrObjectNotFound, &b); err != nil {
-		return nil, err
+		return nil, errors.Wrap(err, "GetObject")
 	}
 
 	return b, nil

+ 8 - 1
internal/throttle/round_tripper.go

@@ -4,6 +4,8 @@ package throttle
 import (
 	"io"
 	"net/http"
+
+	"github.com/pkg/errors"
 )
 
 type throttlerPool interface {
@@ -22,15 +24,20 @@ func (rt *throttlingRoundTripper) RoundTrip(req *http.Request) (*http.Response,
 
 		req.Body, err = rt.uploadPool.AddReader(req.Body)
 		if err != nil {
-			return nil, err
+			return nil, errors.Wrap(err, "unable to attach request throttler")
 		}
 	}
 
 	resp, err := rt.base.RoundTrip(req)
+
 	if resp != nil && resp.Body != nil && rt.downloadPool != nil {
 		resp.Body, err = rt.downloadPool.AddReader(resp.Body)
+		if err != nil {
+			return nil, errors.Wrap(err, "unable to attach response throttler")
+		}
 	}
 
+	// nolint:wrapcheck
 	return resp, err
 }
 

+ 2 - 2
internal/tlsutil/tlsutil.go

@@ -83,7 +83,7 @@ func GenerateServerCertificate(ctx context.Context, keySize int, certValid time.
 func WritePrivateKeyToFile(fname string, priv *rsa.PrivateKey) error {
 	f, err := os.OpenFile(fname, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o600) //nolint:gosec
 	if err != nil {
-		return err
+		return errors.Wrap(err, "error opening private key file")
 	}
 	defer f.Close() //nolint:errcheck,gosec
 
@@ -103,7 +103,7 @@ func WritePrivateKeyToFile(fname string, priv *rsa.PrivateKey) error {
 func WriteCertificateToFile(fname string, cert *x509.Certificate) error {
 	f, err := os.OpenFile(fname, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o600) //nolint:gosec
 	if err != nil {
-		return err
+		return errors.Wrap(err, "error opening certificate file")
 	}
 	defer f.Close() //nolint:errcheck,gosec
 

+ 3 - 3
internal/webdavmount/webdavmount.go

@@ -45,7 +45,7 @@ func (f *webdavFile) getReader() (fs.Reader, error) {
 	if f.r == nil {
 		r, err := f.entry.Open(f.ctx)
 		if err != nil {
-			return nil, err
+			return nil, errors.Wrap(err, "error opening webdav file")
 		}
 
 		f.r = r
@@ -98,7 +98,7 @@ type webdavDir struct {
 func (d *webdavDir) Readdir(n int) ([]os.FileInfo, error) {
 	entries, err := d.entry.Readdir(d.ctx)
 	if err != nil {
-		return nil, err
+		return nil, errors.Wrap(err, "error reading directory")
 	}
 
 	if n > 0 && n < len(entries) {
@@ -192,7 +192,7 @@ func (w *webdavFS) findEntry(ctx context.Context, path string) (fs.Entry, error)
 
 		entries, err := d.Readdir(ctx)
 		if err != nil {
-			return nil, err
+			return nil, errors.Wrap(err, "error reading directory")
 		}
 
 		e = entries.FindByName(p)

+ 11 - 11
repo/api_server_repository.go

@@ -70,7 +70,7 @@ func (r *apiServerRepository) GetManifest(ctx context.Context, id manifest.ID, d
 	var mm remoterepoapi.ManifestWithMetadata
 
 	if err := r.cli.Get(ctx, "manifests/"+string(id), manifest.ErrNotFound, &mm); err != nil {
-		return nil, err
+		return nil, errors.Wrap(err, "GetManifest")
 	}
 
 	return mm.Metadata, json.Unmarshal(mm.Payload, data)
@@ -92,7 +92,7 @@ func (r *apiServerRepository) PutManifest(ctx context.Context, labels map[string
 	resp := &manifest.EntryMetadata{}
 
 	if err := r.cli.Post(ctx, "manifests", req, resp); err != nil {
-		return "", err
+		return "", errors.Wrap(err, "PutManifest")
 	}
 
 	return resp.ID, nil
@@ -108,14 +108,14 @@ func (r *apiServerRepository) FindManifests(ctx context.Context, labels map[stri
 	var mm []*manifest.EntryMetadata
 
 	if err := r.cli.Get(ctx, "manifests?"+uv.Encode(), nil, &mm); err != nil {
-		return nil, err
+		return nil, errors.Wrap(err, "FindManifests")
 	}
 
 	return mm, nil
 }
 
 func (r *apiServerRepository) DeleteManifest(ctx context.Context, id manifest.ID) error {
-	return r.cli.Delete(ctx, "manifests/"+string(id), nil, nil)
+	return errors.Wrap(r.cli.Delete(ctx, "manifests/"+string(id), nil, nil), "DeleteManifest")
 }
 
 func (r *apiServerRepository) Time() time.Time {
@@ -127,7 +127,7 @@ func (r *apiServerRepository) Refresh(ctx context.Context) error {
 }
 
 func (r *apiServerRepository) Flush(ctx context.Context) error {
-	return r.cli.Post(ctx, "flush", nil, nil)
+	return errors.Wrap(r.cli.Post(ctx, "flush", nil, nil), "Flush")
 }
 
 func (r *apiServerRepository) Close(ctx context.Context) error {
@@ -135,14 +135,14 @@ func (r *apiServerRepository) Close(ctx context.Context) error {
 		return errors.Wrap(err, "error closing object manager")
 	}
 
-	return r.Flush(ctx)
+	return errors.Wrap(r.Flush(ctx), "Close")
 }
 
 func (r *apiServerRepository) ContentInfo(ctx context.Context, contentID content.ID) (content.Info, error) {
 	var bi content.Info
 
 	if err := r.cli.Get(ctx, "contents/"+string(contentID)+"?info=1", content.ErrContentNotFound, &bi); err != nil {
-		return content.Info{}, err
+		return content.Info{}, errors.Wrap(err, "ContentInfo")
 	}
 
 	return bi, nil
@@ -152,7 +152,7 @@ func (r *apiServerRepository) GetContent(ctx context.Context, contentID content.
 	var result []byte
 
 	if err := r.cli.Get(ctx, "contents/"+string(contentID), content.ErrContentNotFound, &result); err != nil {
-		return nil, err
+		return nil, errors.Wrap(err, "GetContent")
 	}
 
 	return result, nil
@@ -160,7 +160,7 @@ func (r *apiServerRepository) GetContent(ctx context.Context, contentID content.
 
 func (r *apiServerRepository) WriteContent(ctx context.Context, data []byte, prefix content.ID) (content.ID, error) {
 	if err := content.ValidatePrefix(prefix); err != nil {
-		return "", err
+		return "", errors.Wrap(err, "invalid prefix")
 	}
 
 	var hashOutput [128]byte
@@ -168,7 +168,7 @@ func (r *apiServerRepository) WriteContent(ctx context.Context, data []byte, pre
 	contentID := prefix + content.ID(hex.EncodeToString(r.h(hashOutput[:0], data)))
 
 	if err := r.cli.Put(ctx, "contents/"+string(contentID), data, nil); err != nil {
-		return "", err
+		return "", errors.Wrapf(err, "error writing content %v", contentID)
 	}
 
 	return contentID, nil
@@ -232,7 +232,7 @@ func ConnectAPIServer(ctx context.Context, configFile string, si *APIServerInfo,
 
 	d, err := json.MarshalIndent(&lc, "", "  ")
 	if err != nil {
-		return err
+		return errors.Wrap(err, "unable to marshal config JSON")
 	}
 
 	if err = os.MkdirAll(filepath.Dir(configFile), 0o700); err != nil {

+ 12 - 7
repo/blob/azure/azure_storage.go

@@ -44,14 +44,14 @@ func (az *azStorage) GetBlob(ctx context.Context, b blob.ID, offset, length int6
 	attempt := func() (interface{}, error) {
 		reader, err := az.bucket.NewRangeReader(ctx, az.getObjectNameString(b), offset, length, nil)
 		if err != nil {
-			return nil, err
+			return nil, errors.Wrap(err, "NewRangeReader")
 		}
 
 		defer reader.Close() //nolint:errcheck
 
 		throttled, err := az.downloadThrottler.AddReader(reader)
 		if err != nil {
-			return nil, err
+			return nil, errors.Wrap(err, "AddReader")
 		}
 
 		return ioutil.ReadAll(throttled)
@@ -74,7 +74,7 @@ func (az *azStorage) GetMetadata(ctx context.Context, b blob.ID) (blob.Metadata,
 	attempt := func() (interface{}, error) {
 		fi, err := az.bucket.Attributes(ctx, az.getObjectNameString(b))
 		if err != nil {
-			return nil, err
+			return nil, errors.Wrap(err, "Attributes")
 		}
 
 		return blob.Metadata{
@@ -97,7 +97,9 @@ func exponentialBackoff(ctx context.Context, desc string, att retry.AttemptFunc)
 }
 
 func isRetriableError(err error) bool {
-	if me, ok := err.(azblob.ResponseError); ok {
+	var me azblob.ResponseError
+
+	if errors.As(err, &me) {
 		if me.Response() == nil {
 			return true
 		}
@@ -133,12 +135,14 @@ func (az *azStorage) PutBlob(ctx context.Context, b blob.ID, data blob.Bytes) er
 
 	throttled, err := az.uploadThrottler.AddReader(ioutil.NopCloser(data.Reader()))
 	if err != nil {
+		// nolint:wrapcheck
 		return err
 	}
 
 	// create azure Bucket writer
 	writer, err := az.bucket.NewWriter(ctx, az.getObjectNameString(b), &gblob.WriterOptions{ContentType: "application/x-kopia"})
 	if err != nil {
+		// nolint:wrapcheck
 		return err
 	}
 
@@ -188,11 +192,12 @@ func (az *azStorage) ListBlobs(ctx context.Context, prefix blob.ID, callback fun
 	// iterate over list iterator
 	for {
 		lo, err := li.Next(ctx)
-		if err == io.EOF {
+		if errors.Is(err, io.EOF) {
 			break
 		}
 
 		if err != nil {
+			// nolint:wrapcheck
 			return err
 		}
 
@@ -244,7 +249,7 @@ func New(ctx context.Context, opt *Options) (blob.Storage, error) {
 	// create a credentials object.
 	credential, err := azureblob.NewCredential(azureblob.AccountName(opt.StorageAccount), azureblob.AccountKey(opt.StorageKey))
 	if err != nil {
-		return nil, err
+		return nil, errors.Wrap(err, "unable to initialize credentials")
 	}
 
 	// create a Pipeline with credentials.
@@ -253,7 +258,7 @@ func New(ctx context.Context, opt *Options) (blob.Storage, error) {
 	// create a *blob.Bucket.
 	bucket, err := azureblob.OpenBucket(ctx, pipeline, azureblob.AccountName(opt.StorageAccount), opt.Container, &azureblob.Options{Credential: credential})
 	if err != nil {
-		return nil, err
+		return nil, errors.Wrap(err, "unable to open bucket")
 	}
 
 	downloadThrottler := iothrottler.NewIOThrottlerPool(toBandwidth(opt.MaxDownloadSpeedBytesPerSecond))

+ 3 - 1
repo/blob/azure/azure_storage_test.go

@@ -9,6 +9,7 @@ import (
 	"testing"
 
 	"github.com/Azure/azure-storage-blob-go/azblob"
+	"github.com/pkg/errors"
 
 	"github.com/kopia/kopia/internal/blobtesting"
 	"github.com/kopia/kopia/internal/clock"
@@ -53,7 +54,8 @@ func createContainer(t *testing.T, container, storageAccount, storageKey string)
 	}
 
 	// return if already exists
-	if stgErr, ok := err.(azblob.StorageError); ok {
+	var stgErr azblob.StorageError
+	if errors.As(err, &stgErr) {
 		if stgErr.ServiceCode() == azblob.ServiceCodeContainerAlreadyExists {
 			return
 		}

+ 13 - 7
repo/blob/b2/b2_storage.go

@@ -47,18 +47,18 @@ func (s *b2Storage) GetBlob(ctx context.Context, id blob.ID, offset, length int6
 
 		_, r, err := s.bucket.DownloadFileRangeByName(fileName, fileRange)
 		if err != nil {
-			return nil, err
+			return nil, errors.Wrap(err, "DownloadFileRangeByName")
 		}
 		defer r.Close() //nolint:errcheck
 
 		throttled, err := s.downloadThrottler.AddReader(r)
 		if err != nil {
-			return nil, err
+			return nil, errors.Wrap(err, "DownloadFileRangeByName")
 		}
 
 		b, err := ioutil.ReadAll(throttled)
 		if err != nil {
-			return nil, err
+			return nil, errors.Wrap(err, "ReadAll")
 		}
 
 		if len(b) != int(length) && length > 0 {
@@ -83,7 +83,7 @@ func (s *b2Storage) GetBlob(ctx context.Context, id blob.ID, offset, length int6
 func (s *b2Storage) resolveFileID(fileName string) (string, error) {
 	resp, err := s.bucket.ListFileVersions(fileName, "", 1)
 	if err != nil {
-		return "", err
+		return "", errors.Wrap(err, "ListFileVersions")
 	}
 
 	if len(resp.Files) > 0 {
@@ -106,7 +106,7 @@ func (s *b2Storage) GetMetadata(ctx context.Context, id blob.ID) (blob.Metadata,
 
 		fi, err := s.bucket.GetFileInfo(fileID)
 		if err != nil {
-			return nil, err
+			return nil, errors.Wrap(err, "GetFileInfo")
 		}
 
 		return blob.Metadata{
@@ -125,7 +125,8 @@ func (s *b2Storage) GetMetadata(ctx context.Context, id blob.ID) (blob.Metadata,
 }
 
 func translateError(err error) error {
-	if b2err, ok := err.(*backblaze.B2Error); ok {
+	var b2err *backblaze.B2Error
+	if errors.As(err, &b2err) {
 		if b2err.Status == http.StatusNotFound {
 			// Normal "not found". That's fine.
 			return blob.ErrBlobNotFound
@@ -146,7 +147,9 @@ func exponentialBackoff(ctx context.Context, desc string, att retry.AttemptFunc)
 }
 
 func isRetriableError(err error) bool {
-	if b2err, ok := err.(*backblaze.B2Error); ok {
+	var b2err *backblaze.B2Error
+
+	if errors.As(err, &b2err) {
 		switch b2err.Status {
 		case http.StatusRequestTimeout:
 			return true
@@ -174,12 +177,14 @@ func (s *b2Storage) PutBlob(ctx context.Context, id blob.ID, data blob.Bytes) er
 	attempt := func() (interface{}, error) {
 		throttled, err := s.uploadThrottler.AddReader(ioutil.NopCloser(data.Reader()))
 		if err != nil {
+			// nolint:wrapcheck
 			return nil, err
 		}
 
 		fileName := s.getObjectNameString(id)
 		_, err = s.bucket.UploadFile(fileName, nil, throttled)
 
+		// nolint:wrapcheck
 		return nil, err
 	}
 
@@ -224,6 +229,7 @@ func (s *b2Storage) ListBlobs(ctx context.Context, prefix blob.ID, callback func
 	for {
 		resp, err := s.bucket.ListFileNamesWithPrefix(nextFile, maxFileQuery, fullPrefix, "")
 		if err != nil {
+			// nolint:wrapcheck
 			return err
 		}
 

+ 1 - 1
repo/blob/config.go

@@ -20,7 +20,7 @@ func (c *ConnectionInfo) UnmarshalJSON(b []byte) error {
 	}{}
 
 	if err := json.Unmarshal(b, &raw); err != nil {
-		return err
+		return errors.Wrap(err, "error unmarshaling connection info JSON")
 	}
 
 	c.Type = raw.Type

+ 16 - 2
repo/blob/filesystem/filesystem_storage.go

@@ -59,12 +59,14 @@ func isRetriable(err error) bool {
 	}
 
 	// retry errors during file operations
-	if _, ok := err.(*os.PathError); ok {
+	var pe *os.PathError
+	if errors.As(err, &pe) {
 		return true
 	}
 
 	// retry errors during rename
-	if _, ok := err.(*os.LinkError); ok {
+	var le *os.LinkError
+	if errors.As(err, &le) {
 		return true
 	}
 
@@ -75,6 +77,7 @@ func (fs *fsImpl) GetBlobFromPath(ctx context.Context, dirPath, path string, off
 	val, err := retry.WithExponentialBackoff(ctx, "GetBlobFromPath:"+path, func() (interface{}, error) {
 		f, err := os.Open(path) //nolint:gosec
 		if err != nil {
+			//nolint:wrapcheck
 			return nil, err
 		}
 
@@ -91,6 +94,7 @@ func (fs *fsImpl) GetBlobFromPath(ctx context.Context, dirPath, path string, off
 
 		b, err := ioutil.ReadAll(io.LimitReader(f, length))
 		if err != nil {
+			//nolint:wrapcheck
 			return nil, err
 		}
 
@@ -100,6 +104,7 @@ func (fs *fsImpl) GetBlobFromPath(ctx context.Context, dirPath, path string, off
 					// this sometimes fails on macOS for unknown reasons, likely a bug in the filesystem
 					// retry deals with this transient state.
 					// see see https://github.com/kopia/kopia/issues/299
+					// nolint:wrapcheck
 					return nil, errRetriableInvalidLength
 				}
 			}
@@ -114,6 +119,7 @@ func (fs *fsImpl) GetBlobFromPath(ctx context.Context, dirPath, path string, off
 			return nil, blob.ErrBlobNotFound
 		}
 
+		// nolint:wrapcheck
 		return nil, err
 	}
 
@@ -123,6 +129,7 @@ func (fs *fsImpl) GetBlobFromPath(ctx context.Context, dirPath, path string, off
 func (fs *fsImpl) GetMetadataFromPath(ctx context.Context, dirPath, path string) (blob.Metadata, error) {
 	fi, err := os.Stat(path)
 	if err != nil {
+		// nolint:wrapcheck
 		return blob.Metadata{}, err
 	}
 
@@ -131,6 +138,7 @@ func (fs *fsImpl) GetMetadataFromPath(ctx context.Context, dirPath, path string)
 			return blob.Metadata{}, blob.ErrBlobNotFound
 		}
 
+		// nolint:wrapcheck
 		return blob.Metadata{}, err
 	}
 
@@ -177,6 +185,7 @@ func (fs *fsImpl) PutBlobInPath(ctx context.Context, dirPath, path string, data
 				log(ctx).Warningf("can't remove temp file: %v", removeErr)
 			}
 
+			// nolint:wrapcheck
 			return err
 		}
 
@@ -202,6 +211,7 @@ func (fs *fsImpl) createTempFileAndDir(tempFile string) (*os.File, error) {
 		return os.OpenFile(tempFile, flags, fs.fileMode()) //nolint:gosec
 	}
 
+	// nolint:wrapcheck
 	return f, err
 }
 
@@ -212,6 +222,7 @@ func (fs *fsImpl) DeleteBlobInPath(ctx context.Context, dirPath, path string) er
 			return nil
 		}
 
+		// nolint:wrapcheck
 		return err
 	}, isRetriable)
 }
@@ -219,9 +230,11 @@ func (fs *fsImpl) DeleteBlobInPath(ctx context.Context, dirPath, path string) er
 func (fs *fsImpl) ReadDir(ctx context.Context, dirname string) ([]os.FileInfo, error) {
 	v, err := retry.WithExponentialBackoff(ctx, "ReadDir:"+dirname, func() (interface{}, error) {
 		v, err := ioutil.ReadDir(dirname)
+		// nolint:wrapcheck
 		return v, err
 	}, isRetriable)
 	if err != nil {
+		// nolint:wrapcheck
 		return nil, err
 	}
 
@@ -241,6 +254,7 @@ func (fs *fsStorage) TouchBlob(ctx context.Context, blobID blob.ID, threshold ti
 
 	st, err := os.Stat(path)
 	if err != nil {
+		// nolint:wrapcheck
 		return err
 	}
 

+ 15 - 14
repo/blob/gcs/gcs_storage.go

@@ -48,7 +48,7 @@ func (gcs *gcsStorage) GetBlob(ctx context.Context, b blob.ID, offset, length in
 	attempt := func() (interface{}, error) {
 		reader, err := gcs.bucket.Object(gcs.getObjectNameString(b)).NewRangeReader(gcs.ctx, offset, length)
 		if err != nil {
-			return nil, err
+			return nil, errors.Wrap(err, "NewRangeReader")
 		}
 		defer reader.Close() //nolint:errcheck
 
@@ -72,7 +72,7 @@ func (gcs *gcsStorage) GetMetadata(ctx context.Context, b blob.ID) (blob.Metadat
 	attempt := func() (interface{}, error) {
 		attrs, err := gcs.bucket.Object(gcs.getObjectNameString(b)).Attrs(ctx)
 		if err != nil {
-			return nil, err
+			return nil, errors.Wrap(err, "Attrs")
 		}
 
 		return blob.Metadata{
@@ -95,16 +95,17 @@ func exponentialBackoff(ctx context.Context, desc string, att retry.AttemptFunc)
 }
 
 func isRetriableError(err error) bool {
-	if apiError, ok := err.(*googleapi.Error); ok {
+	var apiError *googleapi.Error
+	if errors.As(err, &apiError) {
 		return apiError.Code >= 500
 	}
 
-	switch err {
-	case nil:
+	switch {
+	case err == nil:
 		return false
-	case gcsclient.ErrObjectNotExist:
+	case errors.Is(err, gcsclient.ErrObjectNotExist):
 		return false
-	case gcsclient.ErrBucketNotExist:
+	case errors.Is(err, gcsclient.ErrBucketNotExist):
 		return false
 	default:
 		return true
@@ -112,10 +113,10 @@ func isRetriableError(err error) bool {
 }
 
 func translateError(err error) error {
-	switch err {
-	case nil:
+	switch {
+	case err == nil:
 		return nil
-	case gcsclient.ErrObjectNotExist:
+	case errors.Is(err, gcsclient.ErrObjectNotExist):
 		return blob.ErrBlobNotFound
 	default:
 		return errors.Wrap(err, "unexpected GCS error")
@@ -202,7 +203,7 @@ func (gcs *gcsStorage) ListBlobs(ctx context.Context, prefix blob.ID, callback f
 	}
 
 	if !errors.Is(err, iterator.Done) {
-		return err
+		return errors.Wrap(err, "ListBlobs")
 	}
 
 	return nil
@@ -234,7 +235,7 @@ func toBandwidth(bytesPerSecond int) iothrottler.Bandwidth {
 func tokenSourceFromCredentialsFile(ctx context.Context, fn string, scopes ...string) (oauth2.TokenSource, error) {
 	data, err := ioutil.ReadFile(fn) //nolint:gosec
 	if err != nil {
-		return nil, err
+		return nil, errors.Wrap(err, "error reading credentials file")
 	}
 
 	cfg, err := google.JWTConfigFromJSON(data, scopes...)
@@ -279,7 +280,7 @@ func New(ctx context.Context, opt *Options) (blob.Storage, error) {
 	}
 
 	if err != nil {
-		return nil, err
+		return nil, errors.Wrap(err, "unable to initialize token source")
 	}
 
 	downloadThrottler := iothrottler.NewIOThrottlerPool(toBandwidth(opt.MaxDownloadSpeedBytesPerSecond))
@@ -290,7 +291,7 @@ func New(ctx context.Context, opt *Options) (blob.Storage, error) {
 
 	cli, err := gcsclient.NewClient(ctx, option.WithHTTPClient(hc))
 	if err != nil {
-		return nil, err
+		return nil, errors.Wrap(err, "unable to create GCS client")
 	}
 
 	if opt.BucketName == "" {

+ 7 - 0
repo/blob/logging/logging_storage.go

@@ -28,6 +28,7 @@ func (s *loggingStorage) GetBlob(ctx context.Context, id blob.ID, offset, length
 		s.printf(s.prefix+"GetBlob(%q,%v,%v)=({%v bytes}, %#v) took %v", id, offset, length, len(result), err, dt)
 	}
 
+	// nolint:wrapcheck
 	return result, err
 }
 
@@ -38,6 +39,7 @@ func (s *loggingStorage) GetMetadata(ctx context.Context, id blob.ID) (blob.Meta
 
 	s.printf(s.prefix+"GetMetadata(%q)=(%v, %#v) took %v", id, result, err, dt)
 
+	// nolint:wrapcheck
 	return result, err
 }
 
@@ -47,6 +49,7 @@ func (s *loggingStorage) PutBlob(ctx context.Context, id blob.ID, data blob.Byte
 	dt := clock.Since(t0)
 	s.printf(s.prefix+"PutBlob(%q,len=%v)=%#v took %v", id, data.Length(), err, dt)
 
+	// nolint:wrapcheck
 	return err
 }
 
@@ -56,6 +59,7 @@ func (s *loggingStorage) SetTime(ctx context.Context, id blob.ID, t time.Time) e
 	dt := clock.Since(t0)
 	s.printf(s.prefix+"SetTime(%q,%v)=%#v took %v", id, t, err, dt)
 
+	// nolint:wrapcheck
 	return err
 }
 
@@ -65,6 +69,7 @@ func (s *loggingStorage) DeleteBlob(ctx context.Context, id blob.ID) error {
 	dt := clock.Since(t0)
 	s.printf(s.prefix+"DeleteBlob(%q)=%#v took %v", id, err, dt)
 
+	// nolint:wrapcheck
 	return err
 }
 
@@ -77,6 +82,7 @@ func (s *loggingStorage) ListBlobs(ctx context.Context, prefix blob.ID, callback
 	})
 	s.printf(s.prefix+"ListBlobs(%q)=%v returned %v items and took %v", prefix, err, cnt, clock.Since(t0))
 
+	// nolint:wrapcheck
 	return err
 }
 
@@ -86,6 +92,7 @@ func (s *loggingStorage) Close(ctx context.Context) error {
 	dt := clock.Since(t0)
 	s.printf(s.prefix+"Close()=%#v took %v", err, dt)
 
+	// nolint:wrapcheck
 	return err
 }
 

+ 3 - 0
repo/blob/readonly/readonly_storage.go

@@ -27,14 +27,17 @@ func (s readonlyStorage) GetMetadata(ctx context.Context, id blob.ID) (blob.Meta
 }
 
 func (s readonlyStorage) SetTime(ctx context.Context, id blob.ID, t time.Time) error {
+	// nolint:wrapcheck
 	return ErrReadonly
 }
 
 func (s readonlyStorage) PutBlob(ctx context.Context, id blob.ID, data blob.Bytes) error {
+	// nolint:wrapcheck
 	return ErrReadonly
 }
 
 func (s readonlyStorage) DeleteBlob(ctx context.Context, id blob.ID) error {
+	// nolint:wrapcheck
 	return ErrReadonly
 }
 

+ 13 - 8
repo/blob/s3/s3_storage.go

@@ -48,19 +48,19 @@ func (s *s3Storage) GetBlob(ctx context.Context, b blob.ID, offset, length int64
 
 		o, err := s.cli.GetObject(ctx, s.BucketName, s.getObjectNameString(b), opt)
 		if err != nil {
-			return 0, err
+			return nil, errors.Wrap(err, "GetObject")
 		}
 
 		defer o.Close() //nolint:errcheck
 
 		throttled, err := s.downloadThrottler.AddReader(o)
 		if err != nil {
-			return nil, err
+			return nil, errors.Wrap(err, "AddReader")
 		}
 
 		b, err := ioutil.ReadAll(throttled)
 		if err != nil {
-			return nil, err
+			return nil, errors.Wrap(err, "ReadAll")
 		}
 
 		if len(b) != int(length) && length > 0 {
@@ -87,7 +87,9 @@ func exponentialBackoff(ctx context.Context, desc string, att retry.AttemptFunc)
 }
 
 func isRetriableError(err error) bool {
-	if me, ok := err.(minio.ErrorResponse); ok {
+	var me minio.ErrorResponse
+
+	if errors.As(err, &me) {
 		// retry on server errors, not on client errors
 		return me.StatusCode >= 500
 	}
@@ -101,7 +103,9 @@ func isRetriableError(err error) bool {
 }
 
 func translateError(err error) error {
-	if me, ok := err.(minio.ErrorResponse); ok {
+	var me minio.ErrorResponse
+
+	if errors.As(err, &me) {
 		if me.StatusCode == http.StatusOK {
 			return nil
 		}
@@ -118,7 +122,7 @@ func (s *s3Storage) GetMetadata(ctx context.Context, b blob.ID) (blob.Metadata,
 	v, err := retry.WithExponentialBackoff(ctx, fmt.Sprintf("GetMetadata(%v)", b), func() (interface{}, error) {
 		oi, err := s.cli.StatObject(ctx, s.BucketName, s.getObjectNameString(b), minio.StatObjectOptions{})
 		if err != nil {
-			return blob.Metadata{}, err
+			return blob.Metadata{}, errors.Wrap(err, "StatObject")
 		}
 
 		return blob.Metadata{
@@ -135,7 +139,7 @@ func (s *s3Storage) PutBlob(ctx context.Context, b blob.ID, data blob.Bytes) err
 	return translateError(retry.WithExponentialBackoffNoValue(ctx, fmt.Sprintf("PutBlob(%v)", b), func() error {
 		throttled, err := s.uploadThrottler.AddReader(ioutil.NopCloser(data.Reader()))
 		if err != nil {
-			return err
+			return errors.Wrap(err, "AddReader")
 		}
 
 		combinedLength := data.Length()
@@ -151,13 +155,14 @@ func (s *s3Storage) PutBlob(ctx context.Context, b blob.ID, data blob.Bytes) err
 			Progress:    newProgressReader(progressCallback, string(b), int64(combinedLength)),
 		})
 
-		if err == io.EOF && uploadInfo.Size == 0 {
+		if errors.Is(err, io.EOF) && uploadInfo.Size == 0 {
 			// special case empty stream
 			_, err = s.cli.PutObject(ctx, s.BucketName, s.getObjectNameString(b), bytes.NewBuffer(nil), 0, minio.PutObjectOptions{
 				ContentType: "application/x-kopia",
 			})
 		}
 
+		// nolint:wrapcheck
 		return err
 	}, isRetriableError))
 }

+ 11 - 11
repo/blob/sftp/sftp_storage.go

@@ -55,7 +55,7 @@ func (s *sftpImpl) GetBlobFromPath(ctx context.Context, dirPath, fullPath string
 	}
 
 	if err != nil {
-		return nil, err
+		return nil, errors.Wrapf(err, "unrecognized error when opening SFTP file %v", fullPath)
 	}
 	defer r.Close() //nolint:errcheck
 
@@ -90,7 +90,7 @@ func (s *sftpImpl) GetMetadataFromPath(ctx context.Context, dirPath, fullPath st
 	}
 
 	if err != nil {
-		return blob.Metadata{}, err
+		return blob.Metadata{}, errors.Wrapf(err, "unrecognized error when calling stat() on SFTP file %v", fullPath)
 	}
 
 	return blob.Metadata{
@@ -134,7 +134,7 @@ func (s *sftpImpl) PutBlobInPath(ctx context.Context, dirPath, fullPath string,
 			fmt.Printf("warning: can't remove temp file: %v", removeErr)
 		}
 
-		return err
+		return errors.Wrap(err, "unexpected error renaming file on SFTP")
 	}
 
 	return nil
@@ -157,7 +157,7 @@ func (s *sftpImpl) createTempFileAndDir(tempFile string) (*sftp.File, error) {
 		return s.cli.OpenFile(tempFile, flags)
 	}
 
-	return f, err
+	return f, errors.Wrapf(err, "unrecognized error when creating temp file on SFTP: %v", tempFile)
 }
 
 func isNotExist(err error) bool {
@@ -178,7 +178,7 @@ func (s *sftpImpl) DeleteBlobInPath(ctx context.Context, dirPath, fullPath strin
 		return nil
 	}
 
-	return err
+	return errors.Wrapf(err, "error deleting SFTP file %v", fullPath)
 }
 
 func (s *sftpImpl) ReadDir(ctx context.Context, dirname string) ([]os.FileInfo, error) {
@@ -212,7 +212,7 @@ func (s *sftpStorage) Close(ctx context.Context) error {
 func writeKnownHostsDataStringToTempFile(data string) (string, error) {
 	tf, err := ioutil.TempFile("", "kopia-known-hosts")
 	if err != nil {
-		return "", err
+		return "", errors.Wrap(err, "error creating temp file")
 	}
 
 	defer tf.Close() //nolint:errcheck,gosec
@@ -259,13 +259,13 @@ func getSigner(opts *Options) (ssh.Signer, error) {
 
 		privateKeyData, err = ioutil.ReadFile(opts.Keyfile)
 		if err != nil {
-			return nil, err
+			return nil, errors.Wrap(err, "error reading private key file")
 		}
 	}
 
 	key, err := ssh.ParsePrivateKey(privateKeyData)
 	if err != nil {
-		return nil, err
+		return nil, errors.Wrap(err, "error parsing private key")
 	}
 
 	return key, nil
@@ -321,12 +321,12 @@ func getSFTPClientExternal(ctx context.Context, opt *Options) (*sftp.Client, clo
 	// get stdin and stdout
 	wr, err := cmd.StdinPipe()
 	if err != nil {
-		return nil, nil, err
+		return nil, nil, errors.Wrap(err, "error opening SSH stdin pipe")
 	}
 
 	rd, err := cmd.StdoutPipe()
 	if err != nil {
-		return nil, nil, err
+		return nil, nil, errors.Wrap(err, "error opening SSH stdout pipe")
 	}
 
 	if err = cmd.Start(); err != nil {
@@ -347,7 +347,7 @@ func getSFTPClientExternal(ctx context.Context, opt *Options) (*sftp.Client, clo
 	if err != nil {
 		closeFunc() // nolint:errcheck
 
-		return nil, nil, err
+		return nil, nil, errors.Wrap(err, "error creating sftp client pipe")
 	}
 
 	return c, closeFunc, nil

+ 4 - 2
repo/blob/sharded/sharded.go

@@ -8,6 +8,8 @@ import (
 	"strings"
 	"time"
 
+	"github.com/pkg/errors"
+
 	"github.com/kopia/kopia/repo/blob"
 )
 
@@ -57,7 +59,7 @@ func (s Storage) ListBlobs(ctx context.Context, prefix blob.ID, callback func(bl
 	walkDir = func(directory string, currentPrefix string) error {
 		entries, err := s.Impl.ReadDir(ctx, directory)
 		if err != nil {
-			return err
+			return errors.Wrap(err, "error reading directory")
 		}
 
 		for _, e := range entries {
@@ -103,7 +105,7 @@ func (s Storage) GetMetadata(ctx context.Context, blobID blob.ID) (blob.Metadata
 	m, err := s.Impl.GetMetadataFromPath(ctx, dirPath, filePath)
 	m.BlobID = blobID
 
-	return m, err
+	return m, errors.Wrap(err, "error getting metadata")
 }
 
 // PutBlob implements blob.Storage.

+ 1 - 1
repo/blob/storage.go

@@ -94,7 +94,7 @@ func ListAllBlobs(ctx context.Context, st Storage, prefix ID) ([]Metadata, error
 		return nil
 	})
 
-	return result, err
+	return result, errors.Wrap(err, "error listing all blobs")
 }
 
 // IterateAllPrefixesInParallel invokes the provided callback and returns the first error returned by the callback or nil.

+ 16 - 10
repo/blob/webdav/webdav_storage.go

@@ -88,8 +88,10 @@ func (d *davStorageImpl) GetMetadataFromPath(ctx context.Context, dirPath, path
 }
 
 func httpErrorCode(err error) int {
-	if err, ok := err.(*os.PathError); ok {
-		code, err := strconv.Atoi(strings.Split(err.Err.Error(), " ")[0])
+	var pe *os.PathError
+
+	if errors.As(err, &pe) {
+		code, err := strconv.Atoi(strings.Split(pe.Err.Error(), " ")[0])
 		if err == nil {
 			return code
 		}
@@ -99,9 +101,11 @@ func httpErrorCode(err error) int {
 }
 
 func (d *davStorageImpl) translateError(err error) error {
-	switch err := err.(type) {
-	case *os.PathError:
-		switch httpErrorCode(err) {
+	var pe *os.PathError
+
+	switch {
+	case errors.As(err, &pe):
+		switch httpErrorCode(pe) {
 		case http.StatusNotFound:
 			return blob.ErrBlobNotFound
 		default:
@@ -120,7 +124,7 @@ func (d *davStorageImpl) ReadDir(ctx context.Context, dir string) ([]os.FileInfo
 		return v.([]os.FileInfo), nil
 	}
 
-	return nil, err
+	return nil, errors.Wrap(err, "error reading WebDAV dir")
 }
 
 func (d *davStorageImpl) PutBlobInPath(ctx context.Context, dirPath, filePath string, data blob.Bytes) error {
@@ -180,12 +184,14 @@ func (d *davStorage) Close(ctx context.Context) error {
 }
 
 func isRetriable(err error) bool {
-	switch err := err.(type) {
-	case nil:
+	var pe *os.PathError
+
+	switch {
+	case err == nil:
 		return false
 
-	case *os.PathError:
-		httpCode := httpErrorCode(err)
+	case errors.As(err, &pe):
+		httpCode := httpErrorCode(pe)
 		return httpCode == 429 || httpCode >= 500
 
 	default:

+ 3 - 2
repo/connect.go

@@ -36,6 +36,7 @@ func Connect(ctx context.Context, configFile string, st blob.Storage, password s
 	formatBytes, err := st.GetBlob(ctx, FormatBlobID, 0, -1)
 	if err != nil {
 		if errors.Is(err, blob.ErrBlobNotFound) {
+			// nolint:wrapcheck
 			return ErrRepositoryNotInitialized
 		}
 
@@ -59,7 +60,7 @@ func Connect(ctx context.Context, configFile string, st blob.Storage, password s
 
 	d, err := json.MarshalIndent(&lc, "", "  ")
 	if err != nil {
-		return err
+		return errors.Wrap(err, "unable to serialize JSON")
 	}
 
 	if err = os.MkdirAll(filepath.Dir(configFile), 0o700); err != nil {
@@ -178,7 +179,7 @@ func SetClientOptions(ctx context.Context, configFile string, cliOpt ClientOptio
 
 	d, err := json.MarshalIndent(lc, "", "  ")
 	if err != nil {
-		return err
+		return errors.Wrap(err, "error marshaling config JSON")
 	}
 
 	if err = ioutil.WriteFile(configFile, d, 0o600); err != nil {

+ 1 - 1
repo/content/committed_content_index.go

@@ -43,7 +43,7 @@ func (b *committedContentIndex) getContent(contentID ID) (Info, error) {
 
 func (b *committedContentIndex) addContent(ctx context.Context, indexBlobID blob.ID, data []byte, use bool) error {
 	if err := b.cache.addContentToCache(ctx, indexBlobID, data); err != nil {
-		return err
+		return errors.Wrap(err, "error adding content to cache")
 	}
 
 	if !use {

+ 2 - 2
repo/content/committed_content_index_disk_cache.go

@@ -60,7 +60,7 @@ func mmapOpenWithRetry(ctx context.Context, path string) (*mmap.ReaderAt, error)
 		f, err = mmap.Open(path)
 	}
 
-	return f, err
+	return f, errors.Wrap(err, "mmap() error")
 }
 
 func (c *diskCommittedContentIndexCache) hasIndexBlobID(ctx context.Context, indexBlobID blob.ID) (bool, error) {
@@ -73,7 +73,7 @@ func (c *diskCommittedContentIndexCache) hasIndexBlobID(ctx context.Context, ind
 		return false, nil
 	}
 
-	return false, err
+	return false, errors.Wrapf(err, "error checking %v", indexBlobID)
 }
 
 func (c *diskCommittedContentIndexCache) addContentToCache(ctx context.Context, indexBlobID blob.ID, data []byte) error {

+ 4 - 2
repo/content/content_cache.go

@@ -5,6 +5,8 @@ import (
 	"os"
 	"path/filepath"
 
+	"github.com/pkg/errors"
+
 	"github.com/kopia/kopia/internal/ctxutil"
 	"github.com/kopia/kopia/repo/blob"
 	"github.com/kopia/kopia/repo/blob/filesystem"
@@ -27,7 +29,7 @@ func newCacheStorageOrNil(ctx context.Context, cacheDir string, maxBytes int64,
 
 		if _, err = os.Stat(contentCacheDir); os.IsNotExist(err) {
 			if mkdirerr := os.MkdirAll(contentCacheDir, 0o700); mkdirerr != nil {
-				return nil, mkdirerr
+				return nil, errors.Wrap(mkdirerr, "error creating cache directory")
 			}
 		}
 
@@ -36,7 +38,7 @@ func newCacheStorageOrNil(ctx context.Context, cacheDir string, maxBytes int64,
 			DirectoryShards: []int{2},
 		})
 		if err != nil {
-			return nil, err
+			return nil, errors.Wrap(err, "error initializing filesystem cache")
 		}
 	}
 

+ 2 - 1
repo/content/content_cache_base.go

@@ -177,8 +177,9 @@ func newContentCacheBase(ctx context.Context, cacheStorage blob.Storage, maxSize
 
 	// verify that cache storage is functional by listing from it
 	if err := c.cacheStorage.ListBlobs(ctx, "", func(it blob.Metadata) error {
+		// nolint:wrapcheck
 		return errGood
-	}); err != nil && err != errGood { //nolint:goerr113
+	}); err != nil && !errors.Is(err, errGood) {
 		return nil, errors.Wrap(err, "unable to open cache")
 	}
 

+ 2 - 1
repo/content/content_cache_data.go

@@ -54,6 +54,7 @@ func (c *contentCacheForData) getContent(ctx context.Context, cacheKey cacheKey,
 
 	if errors.Is(err, blob.ErrBlobNotFound) {
 		// not found in underlying storage
+		// nolint:wrapcheck
 		return nil, err
 	}
 
@@ -69,7 +70,7 @@ func (c *contentCacheForData) getContent(ctx context.Context, cacheKey cacheKey,
 		}
 	}
 
-	return b, err
+	return b, errors.Wrap(err, "error getting content from cache")
 }
 
 func (c *contentCacheForData) readAndVerifyCacheContent(ctx context.Context, cacheKey cacheKey) []byte {

+ 3 - 1
repo/content/content_cache_metadata.go

@@ -81,10 +81,12 @@ func (c *contentCacheForMetadata) getContent(ctx context.Context, cacheKey cache
 
 	if errors.Is(err, blob.ErrBlobNotFound) {
 		// not found in underlying storage
+		// nolint:wrapcheck
 		return nil, err
 	}
 
 	if err != nil {
+		// nolint:wrapcheck
 		return nil, err
 	}
 
@@ -101,7 +103,7 @@ func (c *contentCacheForMetadata) getContent(ctx context.Context, cacheKey cache
 	}
 
 	if offset == 0 && length == -1 {
-		return blobData, err
+		return blobData, nil
 	}
 
 	if offset < 0 || offset+length > int64(len(blobData)) {

+ 4 - 4
repo/content/content_cache_test.go

@@ -93,7 +93,7 @@ func TestCacheExpiration(t *testing.T) {
 
 	for _, tc := range cases {
 		_, got := cache.getContent(ctx, cacheKey(tc.blobID), "content-4k", 0, -1)
-		if want := tc.expectedError; got != want {
+		if want := tc.expectedError; !errors.Is(got, want) {
 			t.Errorf("unexpected error when getting content %v: %v wanted %v", tc.blobID, got, want)
 		} else {
 			t.Logf("got correct error %v when reading content %v", tc.expectedError, tc.blobID)
@@ -148,8 +148,8 @@ func verifyContentCache(t *testing.T, cache contentCache) {
 			{"xf0f0f3", "no-such-content", 0, -1, nil, blob.ErrBlobNotFound},
 			{"xf0f0f4", "no-such-content", 10, 5, nil, blob.ErrBlobNotFound},
 			{"f0f0f5", "content-1", 7, 3, []byte{8, 9, 10}, nil},
-			{"xf0f0f6", "content-1", 11, 10, nil, errors.Errorf("invalid offset: 11")},
-			{"xf0f0f6", "content-1", -1, 5, nil, errors.Errorf("invalid offset: -1")},
+			{"xf0f0f6", "content-1", 11, 10, nil, errors.Errorf("error getting content from cache: invalid offset: 11")},
+			{"xf0f0f6", "content-1", -1, 5, nil, errors.Errorf("error getting content from cache: invalid offset: -1")},
 		}
 
 		for _, tc := range cases {
@@ -157,7 +157,7 @@ func verifyContentCache(t *testing.T, cache contentCache) {
 			if (err != nil) != (tc.err != nil) {
 				t.Errorf("unexpected error for %v: %+v, wanted %+v", tc.cacheKey, err, tc.err)
 			} else if err != nil && err.Error() != tc.err.Error() {
-				t.Errorf("unexpected error for %v: %+v, wanted %+v", tc.cacheKey, err, tc.err)
+				t.Errorf("unexpected error for %v: %q, wanted %q", tc.cacheKey, err.Error(), tc.err.Error())
 			}
 			if !bytes.Equal(v, tc.expected) {
 				t.Errorf("unexpected data for %v: %x, wanted %x", tc.cacheKey, v, tc.expected)

+ 3 - 3
repo/content/content_index_recovery.go

@@ -36,7 +36,7 @@ func (bm *Manager) RecoverIndexFromPackBlob(ctx context.Context, packFile blob.I
 		return nil
 	})
 
-	return recovered, err
+	return recovered, errors.Wrap(err, "error iterating index entries")
 }
 
 type packContentPostamble struct {
@@ -180,7 +180,7 @@ func (bm *lockFreeManager) writePackFileIndexRecoveryData(buf *gather.WriteBuffe
 
 	encryptedLocalIndex, err := bm.encryptor.Encrypt(nil, localIndex, localIndexIV)
 	if err != nil {
-		return err
+		return errors.Wrap(err, "encryption error")
 	}
 
 	postamble := packContentPostamble{
@@ -207,7 +207,7 @@ func (bm *lockFreeManager) readPackFileLocalIndex(ctx context.Context, packFile
 
 	payload, err := bm.st.GetBlob(ctx, packFile, 0, -1)
 	if err != nil {
-		return nil, err
+		return nil, errors.Wrapf(err, "error getting blob %v", packFile)
 	}
 
 	postamble := findPostamble(payload)

+ 4 - 4
repo/content/content_manager.go

@@ -311,7 +311,7 @@ func (bm *Manager) flushPackIndexesLocked(ctx context.Context) error {
 
 		indexBlobMD, err := bm.indexBlobManager.writeIndexBlob(ctx, data)
 		if err != nil {
-			return err
+			return errors.Wrap(err, "error writing index blob")
 		}
 
 		if err := bm.committedContents.addContent(ctx, indexBlobMD.BlobID, dataCopy, true); err != nil {
@@ -565,12 +565,12 @@ func (bm *Manager) WriteContent(ctx context.Context, data []byte, prefix ID) (ID
 // GetContent gets the contents of a given content. If the content is not found returns ErrContentNotFound.
 func (bm *Manager) GetContent(ctx context.Context, contentID ID) (v []byte, err error) {
 	defer func() {
-		switch err {
-		case nil:
+		switch {
+		case err == nil:
 			stats.Record(ctx,
 				metricContentGetCount.M(1),
 				metricContentGetBytes.M(int64(len(v))))
-		case ErrContentNotFound:
+		case errors.Is(err, ErrContentNotFound):
 			stats.Record(ctx, metricContentGetNotFoundCount.M(1))
 		default:
 			stats.Record(ctx, metricContentGetErrorCount.M(1))

+ 1 - 1
repo/content/content_manager_indexes.go

@@ -192,7 +192,7 @@ func (bm *Manager) ParseIndexBlob(ctx context.Context, blobID blob.ID) ([]Info,
 		return nil
 	})
 
-	return results, err
+	return results, errors.Wrap(err, "error iterating index entries")
 }
 
 func addBlobsToIndex(ndx map[blob.ID]*IndexBlobInfo, blobs []blob.Metadata) {

+ 6 - 5
repo/content/content_manager_lock_free.go

@@ -78,7 +78,7 @@ func writeRandomBytesToBuffer(b *gather.WriteBuffer, count int) error {
 	var rnd [defaultPaddingUnit]byte
 
 	if _, err := io.ReadFull(cryptorand.Reader, rnd[0:count]); err != nil {
-		return err
+		return errors.Wrap(err, "error getting random bytes")
 	}
 
 	b.Append(rnd[0:count])
@@ -91,6 +91,7 @@ func (bm *lockFreeManager) loadPackIndexesUnlocked(ctx context.Context) ([]Index
 
 	for i := 0; i < indexLoadAttempts; i++ {
 		if err := ctx.Err(); err != nil {
+			// nolint:wrapcheck
 			return nil, false, err
 		}
 
@@ -103,7 +104,7 @@ func (bm *lockFreeManager) loadPackIndexesUnlocked(ctx context.Context) ([]Index
 
 		indexBlobs, err := bm.indexBlobManager.listIndexBlobs(ctx, false)
 		if err != nil {
-			return nil, false, err
+			return nil, false, errors.Wrap(err, "error listing index blobs")
 		}
 
 		err = bm.tryLoadPackIndexBlobsUnlocked(ctx, indexBlobs)
@@ -193,7 +194,7 @@ func (bm *lockFreeManager) unprocessedIndexBlobsUnlocked(ctx context.Context, co
 	for _, c := range contents {
 		has, err := bm.committedContents.cache.hasIndexBlobID(ctx, c.BlobID)
 		if err != nil {
-			return nil, 0, err
+			return nil, 0, errors.Wrapf(err, "error determining whether index blob %v has been downloaded", c.BlobID)
 		}
 
 		if has {
@@ -240,7 +241,7 @@ func (bm *lockFreeManager) getContentDataUnlocked(ctx context.Context, pp *pendi
 
 		payload, err = bm.getCacheForContentID(bi.ID).getContent(ctx, cacheKey(bi.ID), bi.PackBlobID, int64(bi.PackOffset), int64(bi.Length))
 		if err != nil {
-			return nil, err
+			return nil, errors.Wrap(err, "getCacheForContentID")
 		}
 	}
 
@@ -330,7 +331,7 @@ func (bm *lockFreeManager) IndexBlobs(ctx context.Context, includeInactive bool)
 func getPackedContentIV(output []byte, contentID ID) ([]byte, error) {
 	n, err := hex.Decode(output, []byte(contentID[len(contentID)-(aes.BlockSize*2):]))
 	if err != nil {
-		return nil, err
+		return nil, errors.Wrapf(err, "error decoding content IV from %v", contentID)
 	}
 
 	return output[0:n], nil

+ 1 - 1
repo/content/content_manager_own_writes.go

@@ -133,7 +133,7 @@ func (d *persistentOwnWritesCache) merge(ctx context.Context, prefix blob.ID, so
 		return nil
 	})
 
-	return mergeOwnWrites(source, myWrites), err
+	return mergeOwnWrites(source, myWrites), errors.Wrap(err, "error listing blobs")
 }
 
 func (d *persistentOwnWritesCache) delete(ctx context.Context, blobID blob.ID) error {

+ 5 - 5
repo/content/content_manager_test.go

@@ -188,12 +188,12 @@ func TestContentManagerEmpty(t *testing.T) {
 	noSuchContentID := ID(hashValue([]byte("foo")))
 
 	b, err := bm.GetContent(ctx, noSuchContentID)
-	if err != ErrContentNotFound {
+	if !errors.Is(err, ErrContentNotFound) {
 		t.Errorf("unexpected error when getting non-existent content: %v, %v", b, err)
 	}
 
 	bi, err := bm.ContentInfo(ctx, noSuchContentID)
-	if err != ErrContentNotFound {
+	if !errors.Is(err, ErrContentNotFound) {
 		t.Errorf("unexpected error when getting non-existent content info: %v, %v", bi, err)
 	}
 
@@ -1291,7 +1291,7 @@ func TestRewriteDeleted(t *testing.T) {
 					assertNoError(t, bm.DeleteContent(ctx, content1))
 					applyStep(action2)
 
-					if got, want := bm.RewriteContent(ctx, content1), ErrContentNotFound; got != want && got != nil {
+					if got, want := bm.RewriteContent(ctx, content1), ErrContentNotFound; !errors.Is(got, want) && got != nil {
 						t.Errorf("unexpected error %v, wanted %v", got, want)
 					}
 					applyStep(action3)
@@ -1500,7 +1500,7 @@ func TestIterateContents(t *testing.T) {
 				return nil
 			})
 
-			if tc.fail != err {
+			if !errors.Is(err, tc.fail) {
 				t.Errorf("error iterating: %v", err)
 			}
 
@@ -1888,7 +1888,7 @@ func verifyContentNotFound(ctx context.Context, t *testing.T, bm *Manager, conte
 	t.Helper()
 
 	b, err := bm.GetContent(ctx, contentID)
-	if err != ErrContentNotFound {
+	if !errors.Is(err, ErrContentNotFound) {
 		t.Fatalf("unexpected response from GetContent(%q), got %v,%v, expected %v", contentID, b, err, ErrContentNotFound)
 	}
 }

+ 1 - 1
repo/content/index.go

@@ -215,7 +215,7 @@ func (b *index) findEntry(output []byte, contentID ID) ([]byte, error) {
 	}
 
 	if _, err := b.readerAt.ReadAt(entryBuf, int64(packHeaderSize+stride*position)); err != nil {
-		return nil, err
+		return nil, errors.Wrap(err, "error reading header")
 	}
 
 	if bytes.Equal(entryBuf[0:len(key)], key) {

Деякі файли не було показано, через те що забагато файлів було змінено